Compare commits

...
29 Commits
Author SHA1 Message Date
Thore Cimbal 09aaf1b2b5 docs: Document age key recovery from an already-running cluster
install.md only covered generating a brand-new key during fresh setup.
Added the actual recovery path (retrieve the existing key from the
sops-age secret) - what today's session actually needed after the Mac
reinstall. Also flags the age key's single-backup-location weakness,
tracked in issue #20.
2026-07-28 12:00:00 +00:00
Thore Cimbal 337dbe85ac docs: Migrate backlog to Gitea issues, remove stale completed items
Created issues #11-#31 for the remaining backlog (VP9 retry, ThreadNet-Web
build bug, MAS template link, WAF, and 17 security/infra hardening items),
plus #13 for the previously-noted MAS custom-template idea. Removed/marked
done the stale checklist items that no longer reflected reality (Authentik
Stage 2/E2E-test/invite-links, Hetzner firewall, SSH hardening - all
already completed weeks ago). TASKS.md's detailed backlog section now
points to Gitea issues as the single source of truth instead of
duplicating tracking in two places.
2026-07-28 12:00:00 +00:00
Thore Cimbal e462980dac docs: Document simulcast middle layer and H.264 codec switch for calls 2026-07-28 12:00:00 +00:00
Thore Cimbal 79db6a8e49 feat: Try H.264 codec for calls instead of VP8
H.264 uses classic simulcast (like VP8), not SVC, so it's compatible
with the fork's simulcast-shaped publish options without needing a
code fix - unlike VP9/AV1 (see the 2026-07-28 incident notes). Also
tends to be hardware-accelerated on more devices, notably iOS/Safari.
Server-side: LiveKit has no codec restriction configured, supports
H.264 by default.
2026-07-28 12:00:00 +00:00
Thore Cimbal 25d1742d61 fix: Add 720p simulcast middle layer for smoother video quality
User reported visible artifacts/blockiness during calls. Root cause:
without an explicit simulcast_layers config, the fork defaulted to
only [180p, 360p] fallback layers below the 1440p top layer - any
minor network hiccup caused a hard cliff straight to blocky 360p
instead of a graceful step-down. Added a 720p middle rung.

VP9 retry deferred: LiveKit uses SVC for vp9/av1 instead of classic
simulcast, and the fork's buildPublishOptions() always builds
simulcast-shaped layers regardless of codec - likely the real reason
forcing vp9 broke calls entirely. Needs a code fix before retrying.
2026-07-28 12:00:00 +00:00
Thore Cimbal 736c39a413 docs: Document NetworkPolicies rollout (issue #10), incidents, OOM fix, and identity cleanup 2026-07-28 12:00:00 +00:00
Thore Cimbal 22a0823b7e fix: Raise matrixRTC authorisation service memory limit (20Mi -> 128Mi)
Chart default of 20Mi request+limit OOM-killed the service today after
~74 days of uptime (3rd OOM over its lifetime), causing intermittent
call setup failures unrelated to the NetworkPolicy work happening in
parallel. 20Mi is razor-thin for a long-running Go service; bumping to
a still-modest 64Mi/128Mi request/limit.
2026-07-28 12:00:00 +00:00
Thore Cimbal 3054037480 fix: Allow synapse-main to reach MAS's port 8080 for token introspection
Second port-config incident from this rollout: synapse calls MAS's
oauth2/introspect endpoint on port 8080 (not 8081 as assumed) for
every single authenticated request (sync, pushrules, capabilities,
etc). The rule only allowed kube-system (Traefik) on 8080, so every
authenticated API call failed with 503 "Unable to introspect the
access token" - all clients showed a lost connection. Patched live
immediately, this commit brings Git back in sync with that patch.
2026-07-28 12:00:00 +00:00
Thore Cimbal 8f1d39b7a8 feat: Default-deny ingress NetworkPolicy for matrix namespace
Ingress-only default-deny plus per-component allow rules, using named
container ports throughout (not Service ports) after the authentik
port-mismatch incident. Traefik -> element-web/element-admin/haproxy
(fronts both matrix.axion1337.chat and well-known)/MAS; synapse <-
haproxy+MAS; postgres <- synapse+MAS; RTC signalling via Traefik, RTC
media NodePorts left open to the internet by design; monitoring scrape
allows for synapse/postgres/rtc-sfu metrics; ACME solver allow. coturn
(hostNetwork) needs nothing - NetworkPolicy doesn't apply to it. Part
of issue #10.
2026-07-28 12:00:00 +00:00
Thore Cimbal edf224e450 fix: Correct authentik-server NetworkPolicy ports (container port, not Service port)
Caused a live 502 immediately after deploy: the rule allowed 80/443
(the Service's external ports), but NetworkPolicy filters on the pod's
actual container port after kube-proxy's DNAT - authentik-server's
Service maps 80->9000 and 443->9443. Confirmed root cause by suspending
Flux reconciliation (it was silently re-applying my manual test
deletions) and testing with the policies truly absent.
2026-07-28 12:00:00 +00:00
Thore Cimbal b32920c48f feat: Default-deny ingress NetworkPolicy for authentik namespace
Ingress-only default-deny (egress untouched) plus explicit allows:
Traefik (kube-system) + MAS (matrix ns) -> authentik-server on 80/443,
and Traefik -> cert-manager's ACME HTTP-01 solver pods on 8089.
authentik-postgresql already has its own Bitnami-chart-managed policy,
left alone. Part of issue #10.
2026-07-28 12:00:00 +00:00
Thore Cimbal cb2ffa6a08 docs: Update status summary, add session log, sync element-customization guide with wiki 2026-07-27 12:00:00 +00:00
Thore Cimbal 7d352fbf20 docs: Document VP9 incident and v0.2.1 fix for Element Call fork 2026-07-27 12:00:00 +00:00
Thore Cimbal 1bb1bc9610 fix: Retry Element Call fork without forced VP9 codec preference
v0.2.0 forced video_codec: vp9, which broke calls entirely (no audio/
video transmitted, despite server logs showing the codec regression
fallback to VP8 working). Root cause not fully confirmed. This build
keeps the 1440p/60fps/bitrate defaults but leaves video_codec unset
(defaults to vp8) to isolate whether the codec preference itself was
the trigger.
2026-07-27 12:00:00 +00:00
Thore Cimbal 37aea0254b revert: Roll back Element Call fork image - calls broken (no audio/video)
User reports calls transmit neither video nor audio with
v0.2.0-elementcall-mediaquality. Rolling back to known-good v0.1.0
while investigating; likely the video_codec: vp9 preference, not the
resolution/framerate bump.
2026-07-27 12:00:00 +00:00
Thore Cimbal fad91b6a05 docs: Document Element Call fork completion and threadnet-web build bug 2026-07-27 12:00:00 +00:00
Thore Cimbal af73cf770b feat: Element Call fork with 1440p/60fps + VP9 defaults
Bumps elementWeb image to a build with the embedded Element Call
widget replaced by our own fork (rohana.axion1337.de/sorb/threadnet-call,
based on emmick4/element-call:livekit / upstream PR #3736), raising
default call quality to up to 1440p/60fps camera (VP9) and 1440p/30fps
screen share. These are seeded defaults users can still adjust in
Settings, not hard-enforced caps.
2026-07-27 12:00:00 +00:00
Thore Cimbal 09e4225de5 fix: Replace broken netstat liveness probe on coturn with tcpSocket check
coturn started up cleanly every time (all TCP/UDP listeners on 3478/5349
opened fine per logs) but was killed every ~10s because the liveness
probe ran "netstat -uln", which doesn't exist in coturn/coturn:latest
(sh: netstat: not found) - 36000+ restarts over 88 days for a healthy
process. Switched to a tcpSocket probe against the TCP listener coturn
already opens on 3478, no in-container tooling required.
2026-07-27 12:00:00 +00:00
Thore Cimbal c0cb864ca2 docs: Add security.html with Passkey/TOTP self-service setup links
Neither Element Web's config.json nor MAS's branding config expose a
generic custom-link mechanism, and /if/user/ is blocked for type=external
Matrix accounts, so the working Authentik MFA setup flows had nowhere to
be discoverable. Added a docs/setup/security.html page, following the
exact existing pattern (ConfigMap -> initContainer cp -> nginx), linked
from index.html's Support section.
2026-07-27 12:00:00 +00:00
Thore Cimbal 235306a840 fix: Set Brand.default_application so "/" doesn't dead-end at /if/user/
Several dead ends (TOTP/WebAuthn setup after an anonymous visit bounces
through login and lands on "/") trace back to Brand.default_application
being unset, which falls back to /if/user/ - blocked for type=external
Matrix accounts. Set it to the matrix Application. Only changes the bare
"/" fallback; explicit URLs like /if/admin/ are unaffected.
2026-07-27 12:00:00 +00:00
Thore Cimbal e9b24a6d1f fix: Append redirect stage to TOTP/WebAuthn self-service setup flows
2FA is intentionally optional (not_configured_action=skip on the login
flow's validate stage). Users who opt in use the built-in single-stage
default-authenticator-totp-setup / -webauthn-setup flows directly, since
/if/user/ is blocked for type=external Matrix accounts. Both flows had
nothing after the setup stage, so completing enrollment fell back to
that same blocked dashboard. Appended the shared redirect stage.
2026-07-27 12:00:00 +00:00
Thore Cimbal 0274f9316c fix: Build matrix-recovery flow and link it as the login page's recovery flow
matrix-recovery existed but had zero stage bindings, and the real login
flow (default-authentication-flow, MAS's authentication_flow) never
linked to it, so users had no working "forgot password" path. Reused
the same default-recovery-* stages the built-in default-recovery-flow
already uses successfully, added our redirect stage at the end, and
set default-authentication-identification.recovery_flow accordingly.
2026-07-27 12:00:00 +00:00
Thore Cimbal 027f567c8b fix: Add redirect stage so matrix-invitation ends up at Element, not authentik dashboard
After the login stage, the flow had no destination, so it fell back to
authentik's own /if/user/ interface - which refuses type=external users
(the correct type for Matrix-only accounts), showing "Die Oberflaeche
kann nur von internen Nutzern geoeffnet werden". Added a static redirect
to https://axion1337.chat as the final stage.
2026-07-27 12:00:00 +00:00
Thore Cimbal d2bcd90291 fix: Clear erroneous validation_policies on matrix-invitation-prompt
The prompt stage had 16 unrelated system policies bound (OOBE, user
settings, recovery, etc.), likely from a "select all" slip while
configuring it manually. They crash when evaluated in an anonymous
enrollment context (AnonymousUser has no group_attributes, etc.),
surfacing as opaque errors after form submit. Cleared live and via
the blueprint so re-application doesn't reintroduce them.
2026-07-27 12:00:00 +00:00
Thore Cimbal 80714fe901 fix: Repair matrix-invitation enrollment flow via Authentik Blueprint
The matrix-invitation flow only had Invite+Prompt stage bindings, both
at order=0 (undefined order), missing the Write/Password/Login stages
entirely — invited users were never written to the DB. Applied the fix
live in-cluster (mirroring the working matrix-enrollment stage chain),
and captured it as an Authentik Blueprint (ConfigMap, mounted via
blueprints.configMaps) so the flow state is reproducible via GitOps
instead of manual admin-UI clicks.
2026-07-27 12:00:00 +00:00
Scrublord MacBadandClaude Haiku 4.5 5bbb03bc52 refactor: Organize troubleshooting docs into docs/troubleshooting/
Move all Authentik troubleshooting guides into dedicated subdirectory:
- DIAGNOSTIK-AUTHENTIK-FLOW.md
- AUTHENTIK-FIX-TEMPLATE.md
- AUTHENTIK-INVITATION-FLOW-FIX.md
- AUTHENTIK-CREATE-INVITATION-FLOW.md

Add README.md with:
- Quick reference guide for each document
- Scenario-based navigation
- Known issues tracking
- Tips and best practices

This keeps the root directory clean and organizes related guides together.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-18 13:03:33 +02:00
Scrublord MacBadandClaude Haiku 4.5 af13688993 docs: Add step-by-step guide to create Authentik Invitation Flow
Root cause analysis:
- Only one matrix-enrollment flow exists
- Used for both standard signup AND invitations
- Causes flow conflicts: 'Found existing plan for other flow'
- Error when Klaus enrollment attempted: 'kein ausstehender benutzer Anfrage wurde verweigert'

Solution:
- Create separate matrix-invitation flow
- Use for invitation links only
- Prevents conflicts and allows proper field capture (email is mandatory)

This guide provides:
1. Step-by-step flow creation (5 stages)
2. Field configuration for Prompt Stage
3. Binding setup for each stage
4. Testing procedure with invitation link
5. Troubleshooting checklist

Related issues: Klaus enrollment failure, Boje enrollment failure

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-18 12:52:35 +02:00
Scrublord MacBadandClaude Haiku 4.5 f70e77127e docs: Add Authentik Invitation Flow repair guide
The Invitation Flow is not properly configured:
- Only username is captured during invitations
- Email field is missing (required for OIDC token generation)
- 'Fehler fehlende Rechte' error indicates incomplete user data

This guide walks through:
1. Diagnosing the current Invitation Flow configuration
2. Creating/repairing the Prompt Stage with email field
3. Adding the Prompt Stage to the Invitation Flow
4. Testing the complete enrollment process

Related to: User Boje enrollment failure via invitation link

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-18 12:36:56 +02:00
Scrublord MacBadandClaude Haiku 4.5 f658ce2980 docs: Add Authentik enrollment flow diagnostics and repair guides
- DIAGNOSTIK-AUTHENTIK-FLOW.md: Comprehensive troubleshooting guide
- AUTHENTIK-FIX-TEMPLATE.md: Repair instructions for common issues

These guides help debug why Boje user was created in Authentik but not synchronized to Matrix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-18 12:24:49 +02:00
18 changed files with 2107 additions and 68 deletions
+228
View File
@@ -0,0 +1,228 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: authentik-blueprints
namespace: authentik
data:
matrix-invitation-flow.yaml: |
# yaml-language-server: $schema=https://goauthentik.io/blueprints/schema.json
version: 1
metadata:
name: matrix-invitation-flow
labels:
blueprints.goauthentik.io/instantiate: "true"
entries:
# Reaffirm the flow itself (already created manually; matched by slug)
- model: authentik_flows.flow
state: present
identifiers:
slug: matrix-invitation
id: matrix_invitation_flow
attrs:
name: matrix-invitation
title: matrix-invitation
designation: enrollment
# The prompt stage had accumulated 16 unrelated system validation_policies
# (e.g. default-user-settings-authorization, default-oobe-password-usable)
# from manual UI setup, likely a "select all" slip in the policy picker.
# These crash on an anonymous enrollment context ('AnonymousUser' object
# has no attribute 'group_attributes', etc). A prompt stage needs none here.
- model: authentik_stages_prompt.promptstage
state: present
identifiers:
name: matrix-invitation-prompt
attrs:
validation_policies: []
# Correct stage chain, mirroring the working matrix-enrollment flow:
# Invite -> Prompt (username/email/password) -> Write -> Password -> Login
# Root cause of the original bug: only Invite+Prompt were bound, both at
# order=0, so the flow never wrote the user to the DB or logged them in.
- model: authentik_flows.flowstagebinding
state: present
identifiers:
target: !KeyOf matrix_invitation_flow
order: 0
attrs:
stage: !Find [authentik_stages_invitation.invitationstage, [name, matrix-enrollment-invitation]]
- model: authentik_flows.flowstagebinding
state: present
identifiers:
target: !KeyOf matrix_invitation_flow
order: 1
attrs:
stage: !Find [authentik_stages_prompt.promptstage, [name, matrix-invitation-prompt]]
- model: authentik_flows.flowstagebinding
state: present
identifiers:
target: !KeyOf matrix_invitation_flow
order: 2
attrs:
stage: !Find [authentik_stages_user_write.userwritestage, [name, default-source-enrollment-write]]
- model: authentik_flows.flowstagebinding
state: present
identifiers:
target: !KeyOf matrix_invitation_flow
order: 3
attrs:
stage: !Find [authentik_stages_password.passwordstage, [name, default-authentication-password]]
- model: authentik_flows.flowstagebinding
state: present
identifiers:
target: !KeyOf matrix_invitation_flow
order: 4
attrs:
stage: !Find [authentik_stages_user_login.userloginstage, [name, default-source-enrollment-login]]
# Without an explicit destination, the flow falls back to Authentik's own
# /if/user/ dashboard, which refuses type=external users ("Die Oberflaeche
# kann nur von internen Nutzern geoeffnet werden") - exactly the user type
# these Matrix-only accounts correctly have. Send them to Element instead.
- model: authentik_stages_redirect.redirectstage
state: present
identifiers:
name: matrix-invitation-redirect
id: matrix_invitation_redirect_stage
attrs:
mode: static
target_static: https://axion1337.chat
- model: authentik_flows.flowstagebinding
state: present
identifiers:
target: !KeyOf matrix_invitation_flow
order: 5
attrs:
stage: !KeyOf matrix_invitation_redirect_stage
matrix-recovery-flow.yaml: |
# yaml-language-server: $schema=https://goauthentik.io/blueprints/schema.json
version: 1
metadata:
name: matrix-recovery-flow
labels:
blueprints.goauthentik.io/instantiate: "true"
entries:
# matrix-recovery existed but had zero stage bindings (dead flow), and the
# real login flow (default-authentication-flow, used by the MAS OAuth2
# provider's authentication_flow) didn't link to it at all - no "Forgot
# password?" link was ever shown. Reuses the same default-recovery-*
# stages the built-in default-recovery-flow already uses successfully,
# plus our own redirect stage instead of falling back to the authentik
# dashboard (blocked for type=external Matrix users).
- model: authentik_flows.flow
state: present
identifiers:
slug: matrix-recovery
id: matrix_recovery_flow
attrs:
designation: recovery
- model: authentik_flows.flowstagebinding
state: present
identifiers:
target: !KeyOf matrix_recovery_flow
order: 10
attrs:
stage: !Find [authentik_stages_identification.identificationstage, [name, default-recovery-identification]]
- model: authentik_flows.flowstagebinding
state: present
identifiers:
target: !KeyOf matrix_recovery_flow
order: 20
attrs:
stage: !Find [authentik_stages_email.emailstage, [name, default-recovery-email]]
- model: authentik_flows.flowstagebinding
state: present
identifiers:
target: !KeyOf matrix_recovery_flow
order: 30
attrs:
stage: !Find [authentik_stages_prompt.promptstage, [name, "Change your password"]]
- model: authentik_flows.flowstagebinding
state: present
identifiers:
target: !KeyOf matrix_recovery_flow
order: 40
attrs:
stage: !Find [authentik_stages_user_write.userwritestage, [name, default-recovery-user-write]]
- model: authentik_flows.flowstagebinding
state: present
identifiers:
target: !KeyOf matrix_recovery_flow
order: 100
attrs:
stage: !Find [authentik_stages_user_login.userloginstage, [name, default-recovery-user-login]]
- model: authentik_flows.flowstagebinding
state: present
identifiers:
target: !KeyOf matrix_recovery_flow
order: 110
attrs:
stage: !Find [authentik_stages_redirect.redirectstage, [name, matrix-invitation-redirect]]
# Wire the "Forgot password?" link on the real login flow used by MAS
- model: authentik_stages_identification.identificationstage
state: present
identifiers:
name: default-authentication-identification
attrs:
recovery_flow: !KeyOf matrix_recovery_flow
matrix-mfa-setup-redirect.yaml: |
# yaml-language-server: $schema=https://goauthentik.io/blueprints/schema.json
version: 1
metadata:
name: matrix-mfa-setup-redirect
labels:
blueprints.goauthentik.io/instantiate: "true"
entries:
# 2FA is optional (default-authentication-mfa-validation has
# not_configured_action=skip - login never blocks on missing MFA).
# Users who want to opt in use these built-in single-stage setup flows
# directly (unreachable via /if/user/, which is blocked for type=external
# Matrix accounts). Without a stage after the setup itself, completion
# fell back to the same blocked /if/user/ dashboard - append our redirect.
- model: authentik_flows.flowstagebinding
state: present
identifiers:
target: !Find [authentik_flows.flow, [slug, default-authenticator-totp-setup]]
order: 10
attrs:
stage: !Find [authentik_stages_redirect.redirectstage, [name, matrix-invitation-redirect]]
- model: authentik_flows.flowstagebinding
state: present
identifiers:
target: !Find [authentik_flows.flow, [slug, default-authenticator-webauthn-setup]]
order: 10
attrs:
stage: !Find [authentik_stages_redirect.redirectstage, [name, matrix-invitation-redirect]]
matrix-brand-default-app.yaml: |
# yaml-language-server: $schema=https://goauthentik.io/blueprints/schema.json
version: 1
metadata:
name: matrix-brand-default-app
labels:
blueprints.goauthentik.io/instantiate: "true"
entries:
# Root cause behind several dead ends: an authenticated user hitting "/"
# with no other destination (e.g. after logging in mid-way through the
# TOTP/WebAuthn setup flows) falls back to Brand.default_application: if
# unset, that's /if/user/, which type=external Matrix accounts can't
# open. Only affects the bare "/" fallback - explicit URLs like
# /if/admin/ are unaffected, so internal/staff access is unchanged.
- model: authentik_brands.brand
state: present
identifiers:
domain: authentik-default
attrs:
default_application: !Find [authentik_core.application, [slug, matrix]]
Regular → Executable
+4
View File
@@ -52,6 +52,10 @@ spec:
use_tls: true
from: "Authentik <gamemaster@axion1337.chat>"
blueprints:
configMaps:
- authentik-blueprints
server:
ingress:
enabled: false
+2
View File
@@ -4,6 +4,8 @@ resources:
- namespace.yaml
- helm-repo.yaml
- authentik-secret.yaml
- authentik-blueprints.yaml
- certificate.yaml
- authentik.yaml
- ingress.yaml
- networkpolicy.yaml
+66
View File
@@ -0,0 +1,66 @@
# Default-deny ingress for the authentik namespace, with explicit allow rules for the
# traffic paths that actually need to reach in: Traefik (kube-system) for the public
# auth.axion1337.chat endpoint and ACME HTTP-01 challenges, and MAS (matrix namespace)
# for upstream OIDC calls. Egress is intentionally untouched (federation-equivalent
# outbound calls like SMTP aren't restricted here).
#
# Note: authentik-postgresql already has its own NetworkPolicy from the Bitnami
# postgresql subchart (port 5432, no source restriction) - left alone, not duplicated,
# since it would get reset on the next Helm upgrade anyway.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: authentik
spec:
podSelector: {}
policyTypes:
- Ingress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-authentik-server
namespace: authentik
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: authentik
app.kubernetes.io/component: server
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: matrix
ports:
# NetworkPolicy matches the pod's actual container port, not the Service's
# external port - the authentik-server Service maps 80->9000, 443->9443.
- protocol: TCP
port: 9000
- protocol: TCP
port: 9443
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-acme-solver
namespace: authentik
spec:
podSelector:
matchLabels:
acme.cert-manager.io/http01-solver: "true"
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: TCP
port: 8089
Regular → Executable
+2 -5
View File
@@ -130,11 +130,8 @@ spec:
cpu: 100m
memory: 128Mi
livenessProbe:
exec:
command:
- /bin/sh
- -c
- "netstat -uln | grep 3478 || exit 1"
tcpSocket:
port: 3478
initialDelaySeconds: 30
periodSeconds: 10
volumes:
+9 -1
View File
@@ -59,6 +59,14 @@ spec:
enabled: true
ingress:
host: mrtc.axion1337.chat
# Chart default (20Mi request+limit) OOM-killed the authorisation service after
# ~74 days of uptime (2026-07-28) - too tight for a long-running Go service.
resources:
requests:
memory: 64Mi
cpu: 50m
limits:
memory: 128Mi
# Element Web
elementWeb:
@@ -66,7 +74,7 @@ spec:
image:
registry: rohana.axion1337.de
repository: sorb/threadnet-web
tag: v0.1.0
tag: v0.2.3-elementcall-h264
ingress:
host: axion1337.chat
+112
View File
@@ -193,6 +193,7 @@ data:
<div class="section">
<h2>❓ Support</h2>
<p>Für weitere Hilfe besuche: <a href="https://element.io/help" target="_blank">element.io/help</a></p>
<p>🔐 <a href="security.html">Konto-Sicherheit (Passkey/2FA einrichten)</a></p>
</div>
<div class="support">
@@ -203,6 +204,117 @@ data:
</body>
</html>
# Security / 2FA setup page
"security.html": |
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Konto-Sicherheit - aXion1337.Chat</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 40px 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
padding: 40px;
}
h1 { color: #333; margin-bottom: 10px; font-size: 2.5em; }
.subtitle { color: #666; margin-bottom: 40px; font-size: 1.1em; }
.section { margin-bottom: 40px; }
.section h2 {
color: #667eea;
font-size: 1.5em;
margin-bottom: 20px;
border-bottom: 3px solid #667eea;
padding-bottom: 10px;
}
.download-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.download-card {
background: #f8f9fa;
border: 2px solid #e9ecef;
border-radius: 8px;
padding: 20px;
text-align: center;
transition: all 0.3s ease;
text-decoration: none;
color: #333;
}
.download-card:hover {
border-color: #667eea;
background: #f0f3ff;
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.2);
}
.download-card .icon { font-size: 2.5em; margin-bottom: 10px; }
.download-card .name { font-weight: 600; font-size: 1.1em; margin-bottom: 5px; }
.download-card .desc { font-size: 0.9em; color: #666; }
.instructions {
background: #e7f3ff;
border-left: 4px solid #0066cc;
padding: 15px;
border-radius: 4px;
margin: 15px 0;
line-height: 1.6;
}
.support {
text-align: center;
color: #666;
margin-top: 40px;
padding-top: 20px;
border-top: 1px solid #e9ecef;
}
.support a { color: #667eea; text-decoration: none; font-weight: 500; }
.support a:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="container">
<h1>🔐 Konto-Sicherheit</h1>
<p class="subtitle">Zwei-Faktor-Authentifizierung ist optional - richte sie nur ein, wenn du sie nutzen möchtest.</p>
<div class="section">
<h2>🔑 Einrichten</h2>
<div class="download-grid">
<a href="https://auth.axion1337.chat/if/flow/default-authenticator-webauthn-setup/" class="download-card" target="_blank">
<div class="icon">🔑</div>
<div class="name">Passkey</div>
<div class="desc">WebAuthn / Sicherheitsschlüssel</div>
</a>
<a href="https://auth.axion1337.chat/if/flow/default-authenticator-totp-setup/" class="download-card" target="_blank">
<div class="icon">📱</div>
<div class="name">TOTP</div>
<div class="desc">Authenticator-App</div>
</a>
</div>
<div class="instructions">
<strong>Hinweis:</strong> Du musst bei <code>auth.axion1337.chat</code> eingeloggt sein, damit die
Einrichtung funktioniert. Ohne konfiguriertes Gerät wird beim Login einfach kein zweiter Faktor abgefragt -
2FA ist nie Voraussetzung zum Anmelden.
</div>
</div>
<div class="support">
<p><a href="index.html">← Zurück zum Setup</a></p>
</div>
</div>
</body>
</html>
# README
"README-Element-Setup.md": |
# Element Desktop Setup Scripts
+1
View File
@@ -21,6 +21,7 @@ spec:
- |
mkdir -p /html/docs/setup
cp /config/index.html /html/docs/setup/
cp /config/security.html /html/docs/setup/
cp /config/README-Element-Setup.md /html/docs/setup/
cp /config/element-setup-windows.cmd /html/docs/setup/
cp /config/element-setup-macos.command /html/docs/setup/
+2 -1
View File
@@ -29,4 +29,5 @@ resources:
# HelmRelease (muss ganz unten stehen, damit die ConfigMaps vorher da sind!)
- element-server-suite.yaml
# Custom Apex Ingress für Element Web + Well-Known auf axion1337.chat
- apex-ingress.yaml # Custom Apex Ingress für Element Web + Well-Known auf axion1337.chat
- apex-ingress.yaml # Custom Apex Ingress für Element Web + Well-Known auf axion1337.chat
- networkpolicy.yaml
+293
View File
@@ -0,0 +1,293 @@
# Default-deny ingress for the matrix namespace, with explicit allow rules per component.
# Egress is intentionally untouched (federation to arbitrary Matrix servers, ACME, SMTP,
# DNS all stay unrestricted).
#
# Lesson learned deploying the authentik namespace's equivalent policy: NetworkPolicy
# filters on the pod's actual container port, not the Service's external port (e.g.
# authentik-server's Service maps 80->9000). Wherever a Service here uses a *named*
# targetPort, this file references that name directly instead of guessing a number -
# Kubernetes resolves it from the pod spec, which is safer than a hardcoded port.
#
# matrix-stack-postgres already effectively has no dedicated chart NetworkPolicy of its
# own (unlike authentik-postgresql's Bitnami one) - the rules below are the only gate.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: matrix
spec:
podSelector: {}
policyTypes:
- Ingress
---
# axion1337.chat (root) -> Element Web
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-element-web
namespace: matrix
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: element-web
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: TCP
port: element
---
# admin.axion1337.chat -> Element Admin
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-element-admin
namespace: matrix
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: element-admin
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: TCP
port: http
---
# axion1337.chat/docs/setup -> Element desktop setup docs (our own nginx)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-element-web-docs
namespace: matrix
spec:
podSelector:
matchLabels:
app: element-web-docs
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: TCP
port: 8080
---
# matrix.axion1337.chat AND the well-known delegation both front through haproxy
# (matrix-stack-synapse and matrix-stack-well-known Services both target haproxy's
# named ports, not synapse-main directly).
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-haproxy
namespace: matrix
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: haproxy
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: TCP
port: haproxy-synapse
- protocol: TCP
port: haproxy-403
- protocol: TCP
port: haproxy-wkd
---
# account.axion1337.chat (Traefik) + matrix.axion1337.chat (also routes to MAS for some
# paths) + synapse-main calling MAS's internal port for session/token introspection.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-mas
namespace: matrix
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: matrix-authentication-service
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
- podSelector:
matchLabels:
app.kubernetes.io/name: synapse-main
ports:
- protocol: TCP
port: 8080
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: synapse-main
ports:
- protocol: TCP
port: 8081
---
# Synapse itself: reached via haproxy (same namespace), calls from MAS (provisioning),
# metrics scraped by Alloy (monitoring namespace).
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-synapse
namespace: matrix
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: synapse-main
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: haproxy
- podSelector:
matchLabels:
app.kubernetes.io/name: matrix-authentication-service
ports:
- protocol: TCP
port: synapse-http
- protocol: TCP
port: synapse-health
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
ports:
- protocol: TCP
port: synapse-metrics
---
# mrtc.axion1337.chat (Traefik) for the auth handshake, plus Alloy scraping metrics.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-rtc-authorisation-service
namespace: matrix
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: matrix-rtc-authorisation-service
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
- podSelector:
matchLabels:
app.kubernetes.io/name: matrix-rtc-sfu
ports:
- protocol: TCP
port: http
---
# The SFU: mrtc.axion1337.chat (Traefik) for signalling, Alloy for metrics, and the
# NodePort-exposed WebRTC media ports need to stay open to the internet by design -
# that's the actual point of a TURN/SFU media relay, not a mistake.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-rtc-sfu
namespace: matrix
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: matrix-rtc-sfu
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: TCP
port: http
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
ports:
- protocol: TCP
port: metrics
- from:
- ipBlock:
cidr: 0.0.0.0/0
ports:
- protocol: TCP
port: 30001
- protocol: UDP
port: 30002
---
# Postgres: only Synapse and MAS need data access; Alloy scrapes the exporter.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-postgres
namespace: matrix
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: postgres
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: synapse-main
- podSelector:
matchLabels:
app.kubernetes.io/name: matrix-authentication-service
ports:
- protocol: TCP
port: 5432
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
ports:
- protocol: TCP
port: 9187
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-acme-solver
namespace: matrix
spec:
podSelector:
matchLabels:
acme.cert-manager.io/http01-solver: "true"
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: TCP
port: 8089
# Note: coturn runs with hostNetwork: true, so NetworkPolicy does not apply to it at all -
# it's already gated by the Hetzner Cloud Firewall instead. Nothing to write here.
Regular → Executable
+139 -60
View File
@@ -1,7 +1,7 @@
# aXion1337.Chat Task List & Meilensteine
**Last Updated**: 2026-05-15
**Statusübersicht**: [✅ 9 Abgeschlossen] [🔄 0 In Progress] [📋 11+ Pending] [🔒 10 Security]
**Last Updated**: 2026-07-28
**Statusübersicht**: [✅ 13 Abgeschlossen] [🔄 0 In Progress] [📋 8+ Pending] [🔒 10 Security]
---
@@ -9,9 +9,9 @@
| Kategorie | Count | Status | Details |
|-----------|-------|--------|---------|
| **Completed** | 9 | ✅ Done | K3S, Flux, ESS, Themes, Desktop, Monitoring, TURN, Authentik, Firewall, SSH |
| **Completed** | 13 | ✅ Done | K3S, Flux, ESS, Themes, Desktop, Monitoring, TURN, Authentik (Deploy+Enrollment/Recovery/2FA), Firewall, SSH, coturn Fix, Element Call Fork, NetworkPolicies |
| **In Progress** | 0 | 🔄 — | — |
| **Backlog** | 11+ | 📋 Pending | DB Backups, E2E Test, Element Call Fork, PostgreSQL Migration, NetworkPolicies |
| **Backlog** | 8+ | 📋 Pending | DB Backups, PostgreSQL Migration, MAS-Template-Link, VP9-Retry |
| **Security Tasks** | 5 | 🔒 Pending | auditd, Kernel hardening, CrowdSec, Falco, WAF |
### Priority Distribution
@@ -25,6 +25,63 @@
---
## 🗓️ Session-Zusammenfassung 2026-07-27/28 (fortlaufend aktualisiert)
Nach längerer Pause wiederaufgenommen — Mac war neu aufgesetzt, Zugriff (SSH, Kubeconfig,
age-Key, Homebrew/flux/helm/sops/age) komplett wiederhergestellt und dauerhaft in `~/.zshrc`
verankert. Was in dieser Session erledigt wurde:
1. **Authentik Enrollment/Recovery/2FA** (Issue #7 ✅ geschlossen) — siehe Phase 8 unten und
`docs/troubleshooting/README.md`. `matrix-invitation`- und `matrix-recovery`-Flows waren
kaputt bzw. leer, jetzt als Authentik Blueprint (`apps/authentik/authentik-blueprints.yaml`)
deklarativ repariert. E2E mit echten Test-Usern (`clark`, `lucky`) verifiziert.
2. **coturn-Crash behoben** — Liveness-Probe nutzte `netstat` (existiert nicht im Image),
Server killte einen gesunden Prozess seit 88 Tagen, 36.000+ Restarts. Auf `tcpSocket`-Probe
umgestellt, läuft seitdem stabil.
3. **Element Call Fork** (Issue #8 ✅ geschlossen, Release `m6-element-call-fork-complete`) —
1440p/60fps-Defaults, siehe Kapitel 4 in `docs/deployment-guides/04-element-customization.md`.
**Wichtig**: erzwungenes `video_codec: vp9` hat Calls kurzzeitig live komplett kaputt gemacht
(kein Bild/Ton) — sofort zurückgerollt, ohne Codec-Zwang läuft's. Root Cause dafür nicht
abschließend isoliert, nur umgangen.
4. **Identitäts-Aufräumarbeiten**: `sorB`'s Authentik-E-Mail korrigiert (`thorec@hotmail.de`),
MAS OIDC-Link (`upstream_oauth_links`) von `sorB` zeigte fest auf den alten MAS-User
`akadmin`/`@akadmin:axion1337.chat` (Sub-Hash ist stabil über Username-Renames, daher blieb
die Verknüpfung nach dem Rename "akadmin"→"sorB" bestehen) — umgehängt auf `sorb`/
`@sorb:axion1337.chat`. Neue Identität `elbojoloco` angelegt (E-Mail `cfx@riot.8shield.net`),
verknüpft mit dem alten `akadmin`-MAS-User. **Übrig**: ein leeres, unverknüpftes
`@bojeledoggo:axion1337.chat`-Konto (Tippfehler-Artefakt) — User räumt das selbst auf.
5. **NetworkPolicies** (Issue #10 ✅ geschlossen) — siehe "Network Security" Abschnitt unten.
Zwei Live-Incidents beim Rollout (Port-Verwechslungen), beide binnen Minuten live gepatcht
und danach committed. Nebenbei: `matrixRTC`-Authorisation-Service OOM-Fix (20Mi→128Mi).
6. **Element Call Qualität nachgeschärft** — 720p-Zwischen-Simulcast-Layer ergänzt (sonst
harter Sprung von 1440p auf blockiges 360p bei kleinsten Netzwerkschwankungen), und
`video_codec: h264` statt VP8 (klassisches Simulcast wie VP8, kein SVC-Risiko wie bei
VP9, oft hardwarebeschleunigt v.a. auf iOS). Live verifiziert: 7/8 Tracks nativ H.264,
1 sauberer VP8-Fallback. Deployed als `v0.2.3-elementcall-h264`.
7. **Backlog nach Gitea migriert** — restlicher offener Backlog (VP9-Retry, ThreadNet-Web-Bug,
MAS-Template-Link, WAF und 17 weitere Security-/Infra-Punkte) als Issues #11#31 angelegt,
veraltete erledigte Punkte (Authentik Stage 2/E2E-Test/Invite-Links, Hetzner-Firewall,
SSH-Hardening) aus dieser Datei entfernt bzw. als done markiert.
### Offene Punkte
- **VP9-Retry**: vermutete Ursache jetzt bekannt (LiveKit nutzt SVC für vp9/av1, Fork-Code
setzt aber immer Simulcast-Layer) — braucht einen Code-Fix in `buildPublishOptions()`
(`src/livekit/options.ts`) bevor erneut versucht wird. Stattdessen H.264 probiert (siehe
unten) — läuft gut, kein SVC-Risiko, hardwarebeschleunigt auf mehr Geräten.
- **`ThreadNet-Web` Build-Bug**: `scripts/docker-link-repos.sh`/`docker-package.sh` nicht
ausführbar committet + veralteter `matrix-js-sdk#develop`-Pin im Lockfile blockiert
vollständigen Neu-Build des Web-Forks. Noch nicht gefixt, User hat noch nicht final
entschieden ob gewünscht.
- **Verwaistes `@bojeledoggo:axion1337.chat`**: leeres Matrix-Konto ohne OIDC-Link, User räumt
das selbst auf (braucht dafür seinen eigenen Access-Token für die Admin-API).
- **MAS-Template-Link**: 2FA/Passkey-Setup-Links direkt auf `account.axion1337.chat/account/`
statt nur über `docs/setup/security.html` — braucht MAS Custom-Template-Override
(`templates.path`), größerer separater Task.
- Nächste Kandidaten aus den offenen Issues: #6 (DB-Backup, CRITICAL), #9 (PostgreSQL-Migration),
#10 (NetworkPolicies).
---
## 🎯 Next Steps (Priorisiert)
### 🔴 **THIS WEEK CRITICAL**
@@ -206,38 +263,65 @@
**None** Alle CRITICAL Tasks erledigt! Nächster Focus: Database Backups
### Phase 8: Authentik Enrollment/Recovery/MFA Fix (2026-07-27)
- [x] **matrix-invitation Flow repariert** fehlende Write/Password/Login-Stages ergänzt, Reihenfolge korrigiert, als Authentik Blueprint (`apps/authentik/authentik-blueprints.yaml`) reproduzierbar gemacht
- [x] **matrix-invitation-prompt** 16 fehlerhafte `validation_policies` entfernt (crashten mit `AnonymousUser`/`NoneType`-Fehlern)
- [x] **Redirect-Stage** Flow endet jetzt auf `axion1337.chat` statt in der `/if/user/`-Sackgasse (blockiert für `type=external`)
- [x] **matrix-recovery Flow gebaut** war komplett leer (0 Stages); Passwort-Reset funktioniert jetzt, verlinkt von der echten Login-Seite
- [x] **Brand.default_application gesetzt** behebt mehrere Dead-Ends, wenn eingeloggte User `/` ohne Ziel aufrufen
- [x] **2FA/Passkey Selbst-Einrichtung** Links zu `default-authenticator-totp-setup`/`-webauthn-setup` (2FA bleibt optional, `not_configured_action=skip`), dokumentiert unter `axion1337.chat/docs/setup/security.html`
- [ ] **Backlog**: → **Issue #13** (MAS Custom-Template-Override für 2FA/Passkey-Link auf `account.axion1337.chat/account/`)
---
## 📋 Backlog (Weitere Aufgaben)
### Authentik Completion
- [ ] **Finish Authentik Stage 2 MAS Integration**
- Prerequisites: Authentik OIDC Provider vollständig konfiguriert
- Task: Update `mas-secret.yaml`, enable password login disable
- Commit: `enable-authentik-oidc-integration-in-mas`
- Est. Effort: 30 min (manual + scripted)
- [ ] **Test End-to-End Login Flow**
- Element Web login → MAS → Authentik → Matrix User Creation
- Create test users via Authentik
- Verify password reset flow
- Commit: (implicit in Stage 2)
- Est. Effort: 20 min
- [ ] **Create Invite Links für neue User**
- Authentik Admin UI → Invitations → Create
- Set expiry dates (7d) + use limits
- Document procedure
- Est. Effort: 15 min
**Ab 2026-07-28 in Gitea-Issues gepflegt statt hier** (eine Quelle der Wahrheit) — offene Issues:
[#6](https://rohana.axion1337.de/sorb/axion1337.chat-gitops/issues/6) DB-Backup-Strategie,
[#9](https://rohana.axion1337.de/sorb/axion1337.chat-gitops/issues/9) Externe PostgreSQL-Migration,
[#11](https://rohana.axion1337.de/sorb/axion1337.chat-gitops/issues/11)[#31](https://rohana.axion1337.de/sorb/axion1337.chat-gitops/issues/31)
(VP9-Retry, ThreadNet-Web-Build-Bug, MAS-Template-Link, WAF, Media-PVC-Backups, Pod Security
Admission, Federation-Allowlist, Mjolnir/Draupnir, Content-Scanner, External-Secrets,
Renovate/Trivy, Security-Advisory-Monitoring, automountServiceAccountToken,
unattended-upgrades, K3s-API-Security, auditd, Kernel-Hardening, Lynis, CrowdSec, Falco).
Die detaillierten Beschreibungen unten sind das historische Original, aus dem die Issues
entstanden sind — nicht mehr getrennt pflegen, stattdessen die Issues aktuell halten.
### Element Call Enhancement
- [ ] **Element Call Fork für Custom Constraints**
- Repository: Fork `element-hq/element-call`
- Feature: Video/Audio constraints parameter im config
- Include: Bandwidth limiting, resolution limits, frame rate control
- Integration mit Synapse well-known
- Est. Effort: 23 days (fork + feature + test)
- Priority: **HIGH** (user feature)
- [x] **Element Call Fork für Custom Constraints** (2026-07-28, Closes #8)
- Fork: `rohana.axion1337.de/sorb/threadnet-call` (basiert auf `emmick4/element-call:livekit`,
das den noch nicht gemergten Upstream-PR element-hq/element-call#3736 enthält —
config-driven `media_quality`, keine Custom-Logik nötig)
- Defaults angehoben: Video bis 1440p/60fps (~8 Mbps), Screen-Share 1440p/30fps (~6 Mbps).
Das sind Startwerte, keine harten Limits — Nutzer können in den Settings weiter hochdrehen.
- **Incident (2026-07-28)**: Erster Deploy (`v0.2.0`, mit `video_codec: vp9` erzwungen) hat
Calls komplett kaputt gemacht (kein Bild/Ton), obwohl LiveKit-Server-Logs den
Codec-Regression-Fallback auf VP8 als erfolgreich zeigten — Root Cause nicht abschließend
isoliert. Sofort auf `v0.1.0` zurückgerollt, dann `v0.2.1` ohne erzwungenen Codec (Standard
VP8) mit denselben 1440p/60fps-Werten deployed und vom Nutzer live bestätigt: funktioniert.
VP9-Präferenz vorerst fallengelassen, siehe Backlog.
- Rauschunterdrückung: nur clientseitige WebRTC-Standardtoggles (echoCancellation/
noiseSuppression/autoGainControl), kommt kostenlos mit derselben PR. **Bewusst kein**
server-seitiges ML-Noise-Cancellation (LiveKit Agents + DTLN/RNNoise) — laut LiveKits
eigener Doku ist das für Mensch-zu-Mensch-Calls der falsche Ansatz (nur für AI-Voice-Agents
gedacht, kein Standard-Pfad um bereinigtes Audio an andere Teilnehmer zurückzugeben).
- Well-Known/`org.matrix.msc4143.rtc_foci`-Delegation war schon vom ESS-Chart korrekt
automatisch konfiguriert — kein Handlungsbedarf trotz anderslautendem Issue-Text.
- **Deployment-Ansatz geändert**: `sorb/ThreadNet-Web` (der Element-Web-Fork) hat einen
vorbestehenden, unabhängigen Build-Bug (siehe unten) und ließ sich nicht komplett neu
bauen. Stattdessen: nur der `/app/widgets/element-call/`-Ordner im bereits laufenden
`threadnet-web:v0.1.0`-Image ausgetauscht → neues Image
`rohana.axion1337.de/sorb/threadnet-web:v0.2.0-elementcall-mediaquality`.
- Verifiziert: `media_quality` live auf `axion1337.chat/widgets/element-call/config.json`.
- **Gefunden, nicht gefixt**: `ThreadNet-Web` lässt sich aktuell nicht komplett neu bauen
`scripts/docker-link-repos.sh`/`docker-package.sh` sind im Repo nicht ausführbar
committet (Mode 644 statt 755), UND der gepinnte `matrix-js-sdk#develop`-Commit im
Lockfile ist zu alt (fehlt `src/oidc/authorize.ts`, das `apps/web` importiert). Beides
unabhängig von diesem Fix, blockiert aber jeden zukünftigen vollständigen Rebuild.
- Backlog: MAL-basierte Noise-Cancellation (LiveKit Agents + self-hosted DTLN/RNNoise) als
experimentelle Idee, falls später gewünscht — kein etablierter Pfad für Conferencing.
- Backlog: VP9-Codec-Präferenz erneut versuchen, sobald PR #3736 upstream gemerged/gereift
ist oder Root Cause des Ausfalls isoliert wurde (Browser-Konsolen-Repro nötig).
### Database Hardening
- [ ] **External/Dedicated PostgreSQL Deployment**
@@ -264,18 +348,29 @@
- Priority: **HIGH** (data preservation)
### Network Security
- [ ] **NetworkPolicies K8s-Layer Segmentation**
- Default-Deny Ingress für `matrix` namespace
- Allow rules:
- Ingress → MAS:443
- Ingress → ElementWeb:443
- MAS ↔ Synapse:8008
- Synapse ↔ Postgres:5432
- Authentik → Postgres:5432
- Authentik → Loki:3100 (monitoring)
- Egress: Matrix-specific (federation, etc.)
- Est. Effort: 1 day
- Priority: **MEDIUM** (compliance, least-privilege)
- [x] **NetworkPolicies K8s-Layer Segmentation** (2026-07-28, Closes #10)
- Default-Deny Ingress (egress left untouched) für `matrix` UND `authentik` namespaces,
per-Komponente Allow-Regeln in `apps/authentik/networkpolicy.yaml` und
`apps/production/networkpolicy.yaml`. Rollout: authentik zuerst als Pilot, dann matrix.
- Empirisch verifiziert, dass K3s' eingebauter NetworkPolicy-Controller tatsächlich
durchsetzt (Testnamespace, Timeout- statt Refused-Verhalten unter Deny-Policy).
- **Zwei Live-Incidents beim Rollout, beide binnen Minuten behoben**:
1. `authentik-server`: Regel erlaubte Service-Port 80/443, aber NetworkPolicy filtert
auf dem tatsächlichen Container-Port (9000/9443 nach kube-proxy-DNAT) — 502 auf
`auth.axion1337.chat`, sofort korrigiert.
2. `matrix-authentication-service`: Regel erlaubte Synapse nur auf Port 8081, aber
Synapse ruft `/oauth2/introspect` tatsächlich auf **Port 8080** — jede
authentifizierte Anfrage (inkl. `/sync`) scheiterte mit 503, alle Clients zeigten
"Verbindung unterbrochen". Live gepatcht, dann committed.
- Lehre für zukünftige NetworkPolicies in diesem Repo: wo immer ein Service benannte
Ports (`targetPort: <name>`) nutzt, diese direkt in der Policy referenzieren statt
Portnummern zu raten — schließt genau diese Fehlerklasse aus.
- Nebenbefund (unabhängig von NetworkPolicies): `matrixRTC`-Authorisation-Service hatte
ein 20Mi-Memory-Limit (Chart-Default), OOM-gekillt nach ~74 Tagen Uptime während der
Verifikations-Calls — auf 64Mi/128Mi angehoben.
- `coturn` (hostNetwork) bewusst ausgenommen — NetworkPolicy greift dort nicht.
- `authentik-postgresql`'s Bitnami-Chart-Policy (Port 5432, quelloffen) bewusst nicht
angefasst/dupliziert, da Helm-verwaltet.
- [ ] **Pod Security Admission (Restricted)**
- Apply to `matrix` & `authentik` namespaces
@@ -355,24 +450,8 @@
## 🔒 Security Hardening (Host & Cluster Level)
### Host OS Layer (Ubuntu/Debian)
- [ ] **Hetzner Cloud Firewall**
- Default-Deny inbound
- Allow: 80/443 (HTTP/HTTPS)
- Allow: 22 (SSH) from your IP only (or via WireGuard/Tailscale)
- Status: ✅ Can be done in Hetzner UI
- Est. Effort: 30 min
- Priority: **CRITICAL** (immediate, zero config cost)
- [ ] **SSH Hardening**
- Disable password auth (key-only)
- Disable root login
- PermitRootLogin: no
- PasswordAuthentication: no
- MaxAuthTries: 3
- Optional: Change SSH port (cosmetic, reduces log noise)
- Optional: SSH hinter WireGuard/Tailscale (eliminates fail2ban für SSH)
- Est. Effort: 2 hours
- Priority: **HIGH** (immediate)
- [x] **Hetzner Cloud Firewall** Default-Deny inbound, siehe "Phase 7" oben. **Done.**
- [x] **SSH Hardening** Key-only, Root-Login disabled, Port 2248, siehe "Phase 7" oben. **Done.**
- [ ] **unattended-upgrades**
- Enable automatic security updates
+33
View File
@@ -44,6 +44,39 @@
**Konfiguration**: `apps/production/element-server-suite.yaml` (ESS Chart)
## 4. Element Call Fork (Video/Audio-Qualität)
**Status**: ✅ Deployed (2026-07-28, Closes Issue #8)
- Fork: `rohana.axion1337.de/sorb/threadnet-call` (basiert auf `emmick4/element-call:livekit`,
enthält den noch nicht gemergten Upstream-PR element-hq/element-call#3736 mit
config-driven `media_quality` — kein Custom-Code nötig)
- Defaults angehoben: Kamera bis **1440p/60fps** (~8 Mbps), Screen-Share **1440p/30fps**
(~6 Mbps). Startwerte, keine harten Limits — Nutzer können in Settings weiter hochdrehen.
- Rauschunterdrückung: clientseitige WebRTC-Standardtoggles (Echo/Noise/Gain), passend zu
LiveKits eigener Empfehlung für Mensch-zu-Mensch-Calls. Bewusst **kein** server-seitiges
ML-Noise-Cancellation (siehe `docs/TASKS.md` Backlog).
- **Incident (2026-07-28)**: Erster Versuch mit erzwungenem `video_codec: vp9` hat Calls
komplett kaputt gemacht (kein Bild/Ton). Sofort zurückgerollt. Vermutete Ursache: LiveKit
nutzt für vp9/av1 SVC statt klassischem Simulcast, `buildPublishOptions()` im Fork setzt
aber immer Simulcast-Layer — Code-Fix nötig, bevor vp9 erneut versucht wird (Backlog).
- **720p-Zwischen-Layer ergänzt** (`simulcast_layers`) — ohne eigene Definition fiel die
Übertragung bei kleinsten Netzwerkschwankungen direkt von 1440p auf blockiges 360p, jetzt
sanftere Abstufung über 720p.
- **H.264 statt VP8** (2026-07-28) — nutzt wie VP8 klassisches Simulcast (kein SVC-Risiko wie
bei VP9), zusätzlich auf vielen Geräten (v.a. iOS/Safari) hardwarebeschleunigt. Live
verifiziert: 7 von 8 Video-Tracks liefen über H.264, 1 fiel sauber auf den VP8-Backup-Codec
zurück (kein Ausfall). Deployed als `v0.2.3-elementcall-h264`.
- Deployt als `rohana.axion1337.de/sorb/threadnet-web:v0.2.1-elementcall-noquotavp9` — nur
der `/app/widgets/element-call/`-Ordner im bestehenden `v0.1.0`-Image ausgetauscht, da
`ThreadNet-Web` einen vorbestehenden Build-Bug hat (siehe unten).
- Config live prüfbar: `https://axion1337.chat/widgets/element-call/config.json`
**Bekannter, nicht behobener Bug in `ThreadNet-Web`**: `scripts/docker-link-repos.sh` /
`docker-package.sh` sind nicht ausführbar committet (Mode 644), und der gepinnte
`matrix-js-sdk#develop`-Commit im Lockfile ist zu alt (fehlt `src/oidc/authorize.ts`) —
blockiert einen kompletten Neu-Build des Forks von Grund auf.
## Dateien
| Datei | Ort |
Regular → Executable
+29 -1
View File
@@ -75,4 +75,32 @@ flux get helmreleases -n matrix --watch
# Zeigt, wie die Pods hochfahren:
kubectl get pods -n matrix -w
```
Sobald alle Pods auf `Running` stehen und die Zertifikate über Let's Encrypt validiert wurden (`kubectl get certificate -n matrix`), ist dein Matrix-Stack unter `https://axion1337.chat` erreichbar.
Sobald alle Pods auf `Running` stehen und die Zertifikate über Let's Encrypt validiert wurden (`kubectl get certificate -n matrix`), ist dein Matrix-Stack unter `https://axion1337.chat` erreichbar.
---
## 🔁 Recovery: lokalen age-Key wiederherstellen (Server läuft bereits)
Anders als Schritt 2 oben (neuen Key **erzeugen**) — falls der Server bereits läuft und nur der
lokale Rechner den age-Key verloren hat (z.B. nach einer Neuinstallation), lässt sich der
**bestehende** Private Key direkt aus dem Cluster zurückholen, ohne einen neuen zu generieren
(das würde `.sops.yaml` und alle bereits verschlüsselten Secrets ungültig machen):
```bash
mkdir -p ~/.age
kubectl get secret sops-age -n flux-system -o jsonpath='{.data.age\.agekey}' | base64 -d > ~/.age/keys.txt
chmod 600 ~/.age/keys.txt
# Public Key zur Kontrolle gegen .sops.yaml abgleichen:
grep 'public key:' ~/.age/keys.txt
grep 'age:' .sops.yaml
```
Voraussetzung: laufender Kubeconfig-Zugriff auf den Cluster (siehe Schritt 1 oben — auch das
ist reines Zurückkopieren, kein Neu-Erzeugen).
**Bekannte Schwachstelle**: Dieser Key existiert aktuell nur an zwei Orten — im
`sops-age`-Secret selbst (auf demselben Server) und lokal bei wem auch immer ihn zuletzt
zurückgeholt hat. Es gibt kein separates, offsite Backup. Fällt der Server komplett aus
(nicht nur der lokale Rechner), sind alle SOPS-verschlüsselten Secrets im Repo unlesbar.
Siehe Issue-Backlog für die Entscheidung, ob/wie das abgesichert wird.
@@ -0,0 +1,256 @@
# 🆕 Authentik: Neuen Invitation Flow erstellen
**Problem**:
- Nur ein `matrix-enrollment` Flow existiert
- Wird für Standard-Signup + Invitations verwendet → Konflikt
- Fehler: "Found existing plan for other flow, deleting plan"
**Lösung**: Separaten `matrix-invitation` Flow für Einladungslinks erstellen.
---
## Schritt 1: Authentik Admin UI öffnen
```bash
kubectl port-forward -n authentik svc/authentik 9000:9000
# Browser: http://localhost:9000/
# Admin: akadmin / (password)
```
---
## Schritt 2: Neuen Flow erstellen
**Navigation**: Admin → Flows & Stages → Flows
1. Klick **"Create"** (oben rechts)
2. Fülle folgendes aus:
```
Name: matrix-invitation
Slug: matrix-invitation
Title: Matrix Enrollment via Invitation
Description: Enrollment flow for users created via invitation links
Designation: enrollment
```
3. **Speichern** (Save)
---
## Schritt 3: Stages zur Invitation Flow hinzufügen
Nach dem Erstellen wirst du auf die Flow-Edit-Seite weitergeleitet.
**Navigation**: Admin → Flows & Stages → Flows → `matrix-invitation` → Edit
Klick auf "Add Stage" und folge dieser Reihenfolge:
### Stage 1: Invite Stage (Invitation verarbeiten)
1. Klick **"Add Stage"**
2. Wähle: **"Invite Stage"**
3. Konfiguriere:
```
Name: Invite
Order: 1
```
4. **Save**
Dann musst du das Binding setzen:
- Klick auf die Stage in der Flow
- Binding: **"Invite"** (oder "Invitation")
- Required: **Yes**
- **Save**
### Stage 2: Identification Stage (Username überprüfen)
1. Klick **"Add Stage"**
2. Wähle: **"Identification Stage"** (nicht "Authenticate Stage")
3. Konfiguriere:
```
Name: Identification
Order: 2
User Fields: username (oder email)
Create Users as Inactive: NO
```
4. **Save**
Binding setzen:
- Binding: **"Identify"**
- Required: **No**
- **Save**
### Stage 3: Prompt Stage (Daten abfragen: Username, Email, Name)
1. Klick **"Add Stage"**
2. Wähle: **"Prompt Stage"**
3. Konfiguriere:
```
Name: User Data
Order: 3
```
4. **Speichern (Save)**
Dann **Fields hinzufügen**:
- Klick auf die Stage
- Klick **"Add Field"** für jedes Feld:
#### Field 1: Username
```
Field Name: username
Label: Username
Type: text
Required: Yes
Placeholder: Choose a username
```
#### Field 2: Email
```
Field Name: email
Label: Email Address
Type: email
Required: Yes
Placeholder: your@email.com
```
#### Field 3: Name (Optional)
```
Field Name: name
Label: Full Name
Type: text
Required: No
Placeholder: Your Name
```
Alle Fields **Save**.
Dann **Stage-Binding setzen**:
- Binding: **"Prompt for data"** (oder "User Data")
- Required: **Yes**
- **Save**
### Stage 4: Write Stage (User in DB erstellen)
1. Klick **"Add Stage"**
2. Wähle: **"Write Stage"** (oder "User Write Stage")
3. Konfiguriere:
```
Name: Create User
Order: 4
```
4. **Speichern (Save)**
Dann **Field Bindings setzen**:
- Klick auf die Stage
- Unter "Field Bindings" oder "User Creation":
- `username` ← mapped von username Feld
- `email` ← mapped von email Feld
- `name` ← mapped von name Feld
- **Save**
Stage-Binding setzen:
- Binding: **"Create or update user"**
- Required: **Yes**
- **Save**
### Stage 5: Finish Stage (Abschluss)
1. Klick **"Add Stage"**
2. Wähle: **"Finish Stage"** (oder "User Login")
3. Konfiguriere:
```
Name: Finish
Order: 5
```
4. **Speichern (Save)**
Stage-Binding:
- Binding: **"Finish"** (oder "Complete enrollment")
- Required: **Yes**
- **Save**
---
## Schritt 4: Flow als Standard-Invitation setzen
**Navigation**: Admin → System → Settings
Suche nach "Invitation Flow" oder "Default Flows":
1. Setze **"Invitation Flow"** auf `matrix-invitation`
2. **Save**
Alternativ:
- Admin → Flows & Stages → Flows
- Für jede Invitation/Group:
- Klick auf Group/Invitation
- Setze "Enrollment Flow" auf `matrix-invitation`
---
## Schritt 5: Test mit neuem Einladungslink
1. **Neuen Einladungslink erstellen**:
- Admin → Users & Groups → Invitations
- Klick **"Create"**
- Expiry: 7 days
- **Create & Copy Link**
2. **Link öffnen** (neuer Browser/Inkognito):
- Link in Browser öffnen
- Sollte jetzt alle Felder zeigen:
- [ ] Username eingeben
- [ ] Email eingeben ← sollte jetzt da sein!
- [ ] Name eingeben (optional)
- [ ] "Weiter" oder "Sign in with Authentik"
3. **Authentik Login** (falls Binding korrekt):
- Mit Authentik anmelden
- Enrollment abgeschlossen
- User sollte in Synapse erstellt sein
---
## Troubleshooting
### Fehler: "Stage not found"
- Stelle sicher, dass alle Stages ein **Binding** haben
- Alle Bindings müssen **eindeutig** sein (nicht doppelt)
- **Save** nach jeder Änderung
### Felder werden nicht angezeigt
- Prompt Stage überprüfen
- Alle Fields müssen **Save** sein
- Ggfs. Browser-Cache löschen
### Fehler nach Enrollment
- MAS Logs: `kubectl logs -f matrix-stack-matrix-authentication-service-6b994b9fcf-qqcxz -n matrix`
- Authentik Logs: `kubectl logs -f -n authentik -l app.kubernetes.io/name=authentik`
---
## Erwartetes Ergebnis
Nach dem Fix:
1. Einladungslink öffnen → `matrix-invitation` Flow
2. Username, Email, Name eingeben
3. "Mit Authentik anmelden"
4. Nach Login: User in Synapse erstellt
5. Login zu ElementWeb möglich
---
## Checkliste
- [ ] `matrix-invitation` Flow erstellt
- [ ] 5 Stages in korrekter Reihenfolge (Invite → Identify → Prompt → Write → Finish)
- [ ] Prompt Stage hat username, email, name Felder
- [ ] Alle Stages haben korrektes Binding
- [ ] `matrix-invitation` als Standard-Invitation-Flow gesetzt
- [ ] Neuen Einladungslink erstellt und getestet
- [ ] Test-User kann Email eingeben
- [ ] Test-User in Synapse DB nach Login
---
**Sollte ca. 10-15 Minuten dauern!** 🚀
@@ -0,0 +1,314 @@
# ✅ Authentik Enrollment Flow Reparatur-Template
Basierend auf häufigen Problemen: Hier sind die wahrscheinlichsten Fixes.
---
## Problem 1: MAS kennt Authentik-OIDC nicht
### Symptom
- MAS zeigt "Sign in with Authentik" Button nicht
- Logs: "upstream provider not configured"
### Lösung
**MAS Secret muss diesen Block enthalten:**
```yaml
# apps/production/custom-configs/mas-secret.yaml (decrypted)
---
matrixAuthenticationService:
upstream_oauth2_config:
issuer: "https://auth.axion1337.chat/application/o/matrix/"
client_id: "{{ CLIENT_ID_FROM_AUTHENTIK }}"
client_secret: "{{ CLIENT_SECRET_FROM_AUTHENTIK }}"
authorization_endpoint: "https://auth.axion1337.chat/application/o/authorize/"
token_endpoint: "https://auth.axion1337.chat/application/o/token/"
userinfo_endpoint: "https://auth.axion1337.chat/application/o/userinfo/"
scopes:
- "openid"
- "profile"
- "email"
user_mapping_provider:
type: "oidc"
config:
localpart_template: "{{ user.preferred_username }}"
display_name_template: "{{ user.name }}"
email_template: "{{ user.email }}"
# Wichtig: Password-Login deaktivieren (da wir nur OIDC verwenden)
passwords:
enabled: false
```
**Wie ausfüllen:**
1. Authentik Admin UI öffnen: `https://auth.axion1337.chat`
2. Admin → Applications → Providers → "matrix-provider" (oder ähnlich)
3. Folgende Werte kopieren:
- **Client ID**: Im Provider-Details zu sehen
- **Client Secret**: Im Provider-Details zu sehen (unter "Credentials")
4. Local entschlüsseln (mit age-key):
```bash
cd gitops/
sops -d -i apps/production/custom-configs/mas-secret.yaml
```
5. Datei öffnen und die Werte einfügen
6. Wieder verschlüsseln:
```bash
sops -e -i apps/production/custom-configs/mas-secret.yaml
```
7. Commiten:
```bash
git add apps/production/custom-configs/mas-secret.yaml
git commit -m "Fix: Configure MAS upstream OIDC for Authentik"
git push
```
8. Flux triggern:
```bash
flux reconcile kustomization production-apps --with-source
```
9. Warten, dass MAS Pod neu startet:
```bash
kubectl get pods -n matrix -l app=matrix-authentication-service -w
```
---
## Problem 2: Authentik Enrollment Flow ist kaputt
### Symptom
- User kommt zu Authentik-Login
- Nach Login: "Error: Enrollment stage not found" oder ähnlich
- Oder: Flow bricht ohne Fehlermeldung ab
### Lösung
**Authentik UI → Flows & Stages → Enrollment Flow überprüfen:**
```
Flows → Enrollment
├── Stage 1: "Identify" (if not exists)
│ └── Binding: "Identification (if not exists)"
├── Stage 2: "Write" (wichtig!)
│ └── Binding: "Create or update user"
│ └── User Creation Policies: ???
├── Stage 3: (optional) Weitere Datenerfassung
└── Stage 4: "Finish"
```
**Häufiger Fehler**: "Write" Stage ist nicht korrekt mit den benötigten Feldern konfiguriert.
**Fix:**
1. Authentik Admin UI: `https://auth.axion1337.chat`
2. **Flows & Stages****Stages**
3. Nach "Write" Stage suchen (Filter: "write")
4. Klick auf "Write Stage"
5. **Field Bindings** überprüfen:
- [ ] `username` ← MUSS mit Authentik Username bindbar sein
- [ ] `email` ← MUSS vorhanden sein
- [ ] `name` ← Optional, aber empfohlen
6. Alle sollten "required" sein (nicht optional)
7. **Save**
Dann zurück zu **Flows****Enrollment**:
1. Stages überprüfen (oben)
2. Bindings überprüfen:
- Each Stage hat "Binding" Feld
- "Write" Stage sollte mit "Create or update user" gebunden sein
3. **Save**
---
## Problem 3: OIDC Token werden nicht korrekt zu Synapse weitergeleitet
### Symptom
- User kommt bis zu ElementWeb
- Nach Login zu Authentik: "User not found in Synapse" oder Loop
- Oder: User wird in Authentik angelegt, aber nicht in Synapse
### Lösung
Das ist komplexer und erfordert MAS-Konfiguration + Synapse-Konfiguration.
**MAS Config (upstream_oauth2_config)** muss korrekt sein (siehe Problem 1).
**Zusätzlich in MAS Secret:**
```yaml
# apps/production/custom-configs/mas-secret.yaml
matrixAuthenticationService:
# ... upstream_oauth2_config ...
# User-Provisioning Konfiguration
access:
# Benutzer aus OIDC Provider automatisch erzeugen
auto_provision: true
# Synapse URL
home_server: "https://matrix.axion1337.chat"
```
---
## Problem 4: ElementWeb zeigt nicht automatisch OIDC-Button
### Symptom
- MAS läuft und hat OIDC konfiguriert
- ElementWeb zeigt nur "Sign in with username" oder "SAML login"
- Kein "Sign in with Authentik" Button
### Lösung
ElementWeb muss die MAS-Konfiguration kennen. Das geschieht über `.well-known/matrix/client`:
**Test:**
```bash
curl https://axion1337.chat/.well-known/matrix/client | jq '.authentication'
```
Sollte zurückgeben:
```json
{
"flows": [
{
"stages": ["m.login.sso"]
}
],
"identity_providers": [
{
"id": "authentik",
"name": "Authentik",
"icon": "...",
"brand": "custom"
}
]
}
```
Falls nicht: **ElementWeb Config in Element-Stack aktualisieren:**
```yaml
# apps/production/custom-configs/element-values.yaml
elementWeb:
config:
auth:
sso_redirect_options:
immediate: false
on_welcome_page: true
```
**Dann:**
```bash
git add apps/production/custom-configs/element-values.yaml
git commit -m "Fix: Enable SSO redirect in ElementWeb"
git push
flux reconcile kustomization production-apps --with-source
```
---
## Problem 5: User kann sich anmelden, aber Boje ist nicht in Matrix
### Symptom
- Authentik-Login funktioniert
- MAS zeigt User erfolgreich
- ElementWeb Login funktioniert
- **ABER**: User ist nicht in Synapse (check mit `kubectl exec -n matrix synapse ...`)
### Lösung
Das bedeutet: User wird nicht automatisch in Synapse provisioniert.
**MAS muss User zu Synapse erstellen:**
In MAS Secret, `access` Sektion:
```yaml
matrixAuthenticationService:
access:
# Synapse erlaubt neue User-Erstellung
homeserver: "https://matrix.axion1337.chat"
# Optional: User automatisch erstellen
registration_enabled: true
```
**Synapse-seitig** muss auch User-Erstellung erlaubt sein:
```yaml
# apps/production/custom-configs/synapse-values.yaml
synapse:
additional:
registration:
config: |
enable_registration: true
enable_registration_without_token: false
# Token wird von MAS bereitgestellt
```
---
## Komplettes Test-Szenario
Nach allen Fixes:
1. **Browser 1**: `https://axion1337.chat` öffnen
2. Auf ElementWeb "Sign in" klicken
3. "Sign in with Authentik" klicken (sollte sichtbar sein)
4. Authentik-Login durchführen (akadmin)
5. Nach Login: Enrollment-Flow (nur falls neuer User)
6. Zurück zu ElementWeb
7. **Synapse prüfen**:
```bash
kubectl exec -it -n matrix deployment/synapse -- \
/usr/local/bin/psql -U synapse -d synapse -c "SELECT name, admin FROM users LIMIT 10;"
```
8. Neuer User sollte auftauchen
---
## Debugging-Commands (während Test)
```bash
# Live MAS logs (folgen)
kubectl logs -n matrix -l app=matrix-authentication-service -f
# Authentik logs
kubectl logs -n authentik -l app.kubernetes.io/name=authentik -f
# Port-Forward für manuelles Testen
kubectl port-forward -n matrix svc/matrix-authentication-service 8765:8080
# MAS Secret auslesen (im Cluster)
kubectl get secret ess-mas-values-secret -n matrix -o jsonpath='{.data.values\.yaml}' | base64 -d
# Synapse User-Liste
kubectl exec -it -n matrix deployment/synapse -- \
/usr/local/bin/psql -U synapse -d synapse -c "SELECT name, admin, is_guest FROM users;"
```
---
## Checkliste für erfolgreichen Fix
- [ ] MAS Secret decrypted, upstream_oauth2_config eingetragen
- [ ] Client ID + Secret von Authentik kopiert
- [ ] MAS Secret re-encrypted und commited
- [ ] Authentik Enrollment Flow überprüft
- [ ] OIDC Provider in Authentik aktiv
- [ ] OIDC Application "matrix" in Authentik existiert
- [ ] MAS Pod neu gestartet (nach Secret-Change)
- [ ] ElementWeb zeigt OIDC-Button
- [ ] Test-Login durchgeführt
- [ ] Neuer User in Synapse DB vorhanden
---
**Fragen?** → Siehe `DIAGNOSTIK-AUTHENTIK-FLOW.md` für tiefergehende Diagnose.
@@ -0,0 +1,244 @@
# 🔧 Authentik Invitation Flow Fix Für Einladungslinks
**Problem**:
- Standard Enrollment (akadmin): ✅ funktioniert
- Invitation Flow (Boje über Einladungslink): ❌ Nur Username gefragt, keine Email
- Nach Enrollment: "Fehler fehlende Rechte"
**Root Cause**: Invitation Flow erfasst nicht alle erforderlichen Felder (Email) für OIDC-Token-Generation.
---
## Phase 1: Diagnose im Authentik Admin UI
```bash
# Authentik Admin UI öffnen
kubectl port-forward -n authentik svc/authentik 9000:9000
# Browser: http://localhost:9000/
# Admin credentials: akadmin / (password)
```
### 1.1 Überprüfe: Welche Flows existieren?
**Navigation**: Admin → Flows & Stages → Flows
Suche nach diesen Flows:
- [ ] `enrollment` Standard Enrollment (für akadmin)
- [ ] `invitation` Invitation Flow (für Einladungslinks)
- [ ] `default-authentication-flow` Standard Login
### 1.2 Überprüfe: Standard Enrollment Flow (funktioniert)
**Navigation**: Flows → `enrollment` öffnen
**Stages sollten sein:**
```
1. Identify (if not exists)
└─ Binding: "Identification (if not exists)"
2. Write
└─ Binding: "Create or update user"
└─ Field bindings MUST include:
├─ username
├─ email ← WICHTIG
└─ name (optional)
3. (optional) Weitere Stages
4. Finish
```
**Wichtig**: Alle Felder müssen "required" sein (nicht optional).
### 1.3 Überprüfe: Invitation Flow (wahrscheinlich kaputt)
**Navigation**: Flows → `invitation` öffnen
**Problem**: Wahrscheinlich fehlt die "Email" Stage hier!
**Sollte sein:**
```
1. Invite Stage
└─ Binding: "Invite user"
2. Identification (if not exists)
└─ Binding: "Identify"
3. Prompt Stage (für zusätzliche Daten!)
└─ Binding: "Prompt for data"
└─ Fields: username, email, name, password
4. Write
└─ Binding: "Create or update user"
5. Finish
```
---
## Phase 2: Reparatur der Invitation Flow
### Schritt 1: Neue "Prompt Stage" erstellen (falls nicht existiert)
**Navigation**: Admin → Flows & Stages → Stages
1. Klick "Create"
2. Name: `invitation-prompt` oder ähnlich
3. Type: **"Prompt Stage"**
4. Configure:
- [ ] **Fields to Prompt**:
- Username (required)
- Email (required) ← WICHTIG
- Name (optional)
- Password (optional, da OIDC)
5. Save
### Schritt 2: Invitation Flow reparieren
**Navigation**: Admin → Flows & Stages → Flows → `invitation`
**Stages in dieser Reihenfolge:**
```
Stage 1: Invite Stage
├─ Binding: "Invite"
├─ Required: Yes
Stage 2: Identification Stage
├─ Binding: "Identify" (oder "Identification (if not exists)")
├─ Required: No
Stage 3: [NEUE STAGE] Prompt für Email/Username
├─ Type: "Prompt Stage"
├─ Binding: "Prompt for data"
├─ Fields:
│ ├─ username (required)
│ ├─ email (required) ← ENTSCHEIDEND
│ └─ name (optional)
├─ Required: Yes
Stage 4: Write
├─ Binding: "Create or update user"
├─ User Creation Policies: (standard)
├─ Required: Yes
Stage 5: Finish
├─ Binding: "Finish"
├─ Required: Yes
```
**Speichern** und Testen!
---
## Phase 3: Teste Invitation Flow
### Test 1: Neuen Einladungslink erstellen
**Navigation**: Admin → Users & Groups → Invitations
1. Klick "Create"
2. Expiry: 7 days
3. Create & Copy Link
### Test 2: Einladungslink öffnen (in neuem Browser/Inkognito)
1. Link öffnen
2. Enrollment Flow sollte jetzt:
- [ ] Username eingeben
- [ ] **Email eingeben** ← Das sollte jetzt da sein!
- [ ] Name eingeben (optional)
- [ ] "Sign in with Authentik" klicken (falls Authentik-Binding korrekt)
3. Nach Authentik-Login: User in Synapse erstellt?
```bash
kubectl exec -it -n matrix matrix-stack-postgres-0 -- \
psql -U synapse -d synapse -c "SELECT name FROM users WHERE created_ts > now() - interval '5 minutes';"
```
### Test 3: Prüfe MAS Logs auf Fehler
```bash
kubectl logs -f matrix-stack-matrix-authentication-service-6b994b9fcf-qqcxz -n matrix | grep -i "error\|fail\|boje"
```
---
## Phase 4: Häufige Fehler & Lösungen
### Fehler 1: "Fehlende Rechte" nach Enrollment
**Symptom**: Enrollment abgeschlossen, aber Fehler auf Berechtigungsseite
**Ursachen**:
- [ ] Email-Feld wurde nicht erfasst
- [ ] OIDC-Token hat unvollständige Daten
- [ ] Synapse User konnte nicht erstellt werden (Duplikat?)
**Lösung**:
1. Authentik Logs prüfen: `kubectl logs -n authentik -l app.kubernetes.io/name=authentik -f | grep -i "error\|invitation"`
2. MAS Logs prüfen: `kubectl logs -f matrix-stack-matrix-authentication-service-6b994b9fcf-qqcxz -n matrix | grep -i "boje\|error"`
3. Synapse Logs prüfen: `kubectl logs -f -n matrix matrix-stack-synapse-0 | grep -i "boje\|register"`
### Fehler 2: "Stage not found" oder "Flow invalid"
**Ursache**: Invitation Flow hat Binding-Fehler
**Lösung**:
1. Admin UI → Flows → Invitation Flow öffnen
2. Alle Stages überprüfen, dass sie korrekt gebunden sind
3. Keine leeren/ungültigen Bindings
4. Save & Retry
### Fehler 3: Email-Feld wird nicht angezeigt
**Ursache**: Prompt Stage hat email nicht in Fields
**Lösung**:
1. Admin UI → Flows → Stages → Prompt Stage öffnen
2. Edit → Fields überprüfen
3. Email hinzufügen if missing:
- Field name: `email`
- Type: `email`
- Required: Yes
4. Save
---
## Erwarteter Ablauf nach Fix
1. Browser öffnet Einladungslink → Enrollment Flow
2. "Username eingeben" → z.B. "boje"
3. **"Email eingeben"** ← Sollte jetzt da sein
4. "Name eingeben" (optional)
5. "Weiter" oder "Mit Authentik anmelden"
6. Authentik Login
7. Enrollment abgeschlossen
8. User "boje" in Synapse DB angelegt
9. Login zu ElementWeb möglich
---
## Checkliste
- [ ] Authentik Admin UI geöffnet (port-forward 9000)
- [ ] Standard Enrollment Flow überprüft (funktioniert mit akadmin)
- [ ] Invitation Flow überprüft
- [ ] Prompt Stage existiert mit email field
- [ ] Invitation Flow hat alle 5 Stages in korrekter Reihenfolge
- [ ] Neuen Einladungslink erstellt und getestet
- [ ] Test-User hat Email eingeben können
- [ ] Test-User in Synapse DB nach Login
- [ ] MAS Logs zeigen keine Fehler
---
**Frage**: Stimmt das mit deiner Beobachtung überein - dass bei der Einladung **nur Username** gefragt wurde, aber **nicht die Email**?
Wenn ja, dann ist der Fix:
1. Prompt Stage erstellen/reparieren (mit email field)
2. Zur Invitation Flow hinzufügen
3. Testen
Soll ich dir noch mehr Detailschritte geben?
@@ -0,0 +1,261 @@
# 🔍 Authentik Enrollment Flow Diagnostik & Reparaturplan
**Symptom**:
- `akadmin` wurde manuell in Matrix-DB angelegt (funktioniert)
- `Boje` wurde nur in Authentik erstellt, nicht in Matrix-DB (kaputt)
- Beide sollen denselben Enrollment Flow verwenden
**Vermutung**: Die Authentik → MAS → Synapse Kette ist unterbrochen
---
## Phase 1: Diagnose (Bestandsaufnahme)
### 1.1 Authentik Logs prüfen
```bash
# Authentik Server logs
kubectl logs -n authentik -l app.kubernetes.io/name=authentik -f | grep -i "oauth\|oidc\|enroll\|flow"
# Worker logs
kubectl logs -n authentik -l app.kubernetes.io/component=worker -f | grep -i "enroll"
```
**Worauf achten**:
- Fehler bei "Enrollment create"?
- OIDC Token-Probleme?
- Flow-Validierungsfehler?
### 1.2 MAS (Matrix Authentication Service) Logs prüfen
```bash
# MAS logs
kubectl logs -n matrix -l app=matrix-authentication-service -f | grep -i "oauth\|upstream\|user\|oidc"
```
**Worauf achten**:
- Verbindung zu Authentik erfolgreich?
- Token-Validierung fehlgeschlagen?
- User-Provisioning-Fehler?
### 1.3 Synapse Logs prüfen
```bash
# Synapse logs (für User-Erstellung)
kubectl logs -n matrix -l app=synapse -f | grep -i "user\|provision\|register\|auth"
```
**Worauf achten**:
- User-Registrierungs-Fehler?
- Provisioning-Fehler?
- Authentifizierungsprobleme?
---
## Phase 2: Authentik UI Überprüfung
### 2.1 Enrollment Flow inspizieren
```bash
# Authentik UI öffnen
kubectl port-forward -n authentik svc/authentik 9000:9000
# → Browser: http://localhost:9000
# → Admin UI → Flows & Stages → "Enrollment" Flow suchen
```
**Checklist**:
- [ ] Flow existiert und heißt "Enrollment"
- [ ] Reihenfolge der Stages:
1. Identify (optional)
2. Write (User-Erstellung)
3. Enrollment (if-condition für neuen User)
4. Verification (optional)
- [ ] "Write Stage" bindet sich an:
- [ ] Username
- [ ] Email
- [ ] Name
- [ ] Alle Bindings sind "required" (nicht optional)
### 2.2 OIDC Provider in Authentik prüfen
```bash
# Über Authentik UI:
# Admin → Applications → Providers → "matrix-provider" (oder ähnlich)
```
**Checklist**:
- [ ] Provider existiert
- [ ] Name: z.B. "matrix-provider"
- [ ] Client Type: "Confidential"
- [ ] Redirect URIs enthalten:
- [ ] `https://account.axion1337.chat/upstream/callback/*`
- [ ] `https://account.axion1337.chat/upstream/callback/01KQDJTR1ZVTG8JQ220F5BNBFZ` (exact)
- [ ] Scopes: `openid profile email`
- [ ] Client ID + Secret kopiert?
### 2.3 OIDC Application in Authentik
```bash
# Admin → Applications → Applications → "matrix"
```
**Checklist**:
- [ ] Application existiert mit Slug "matrix"
- [ ] Provider ist zugewiesen
- [ ] Enrollment Flow ist zugewiesen (nicht "deny")
- [ ] Enrollment Flow ist die richtige (die von oben)
### 2.4 Test-User "Boje" inspizieren
```bash
# Admin → Directory → Users → "Boje"
```
**Checklist**:
- [ ] Username: `boje`
- [ ] Email: `boje@...` (vorhanden?)
- [ ] Groups: Falls erforderlich, die richtigen Groups zugewiesen?
- [ ] Status: Active oder Disabled?
- [ ] Sessions: Aktive Logins?
---
## Phase 3: MAS Konfiguration überprüfen
### 3.1 Current MAS Secret auslesen
Da der age-key nicht lokal verfügbar ist, müssen wir die Konfiguration im Cluster prüfen:
```bash
# MAS Config im Cluster auslesen (nicht verschlüsselt)
kubectl get secret ess-mas-values-secret -n matrix -o jsonpath='{.data.values\.yaml}' | base64 -d | yq . | head -100
```
**Worauf achten**:
- [ ] `upstream_oauth2_config` Block existiert
- [ ] `upstream_oauth2_config.issuer`: `https://auth.axion1337.chat/application/o/matrix/`
- [ ] `upstream_oauth2_config.client_id`: Authentik Client ID
- [ ] `upstream_oauth2_config.client_secret`: Authentik Client Secret (*)
- [ ] `upstream_oauth2_config.scopes`: `["openid", "profile", "email"]`
- [ ] `upstream_oauth2_config.user_mapping_provider`:
- `type`: "oidc"
- `config.localpart_template`: `{{ user.preferred_username }}`
- `config.display_name_template`: `{{ user.name }}`
- `config.email_template`: `{{ user.email }}`
### 3.2 MAS Pod exec Config live prüfen
```bash
# MAS Config im laufenden Pod inspizieren
kubectl exec -it -n matrix deployment/matrix-authentication-service -- cat /etc/mas/config.yaml | grep -A50 upstream_oauth2_config
```
**Worauf achten**:
- Config ist syntaktisch korrekt (YAML)?
- Indentierung ist richtig?
- Werte sind vorhanden?
### 3.3 MAS OIDC Discovery prüfen
```bash
# Authentik OIDC Discovery Endpoint
curl -s https://auth.axion1337.chat/application/o/matrix/.well-known/openid-configuration | jq .
# Sollte zurückgeben:
# {
# "issuer": "https://auth.axion1337.chat/application/o/matrix/",
# "token_endpoint": "https://auth.axion1337.chat/application/o/token/",
# "authorization_endpoint": "...",
# ...
# }
```
---
## Phase 4: Login-Flow Testen
### 4.1 MAS Login UI öffnen
```bash
# Port-Forward zu MAS
kubectl port-forward -n matrix svc/matrix-authentication-service 8765:8080
# → Browser: http://localhost:8765
```
**Test**:
1. Auf MAS-Seite: "Sign in with Authentik" klicken
2. Authentik-Login durchführen
3. Auf Enrollment Flow warten
4. Neuen User erstellen (Test-Username, Email, Password)
5. Nach erfolgreicher Registrierung: Matrix home_server erhalten?
### 4.2 Fehlerberichte
Falls Fehler auftritt:
- [ ] Screenshot des Fehlers
- [ ] MAS logs auslesen: `kubectl logs -n matrix -l app=matrix-authentication-service --tail=50`
- [ ] Authentik logs auslesen: `kubectl logs -n authentik -l app.kubernetes.io/name=authentik --tail=50`
---
## Phase 5: Reparaturschritte (nachdem Diagnose klar ist)
### Falls MAS-Config fehlerhaft:
```bash
# 1. Secrets entschlüsseln (lokale Umgebung mit age-key erforderlich)
sops -d apps/production/custom-configs/mas-secret.yaml > /tmp/mas-secret-decrypted.yaml
# 2. Editor öffnen und Konfiguration reparieren
vim /tmp/mas-secret-decrypted.yaml
# → upstream_oauth2_config überprüfen und korrigieren
# 3. Wieder verschlüsseln
sops -e /tmp/mas-secret-decrypted.yaml > apps/production/custom-configs/mas-secret.yaml
# 4. Commiten
git add apps/production/custom-configs/mas-secret.yaml
git commit -m "Fix: Correct MAS upstream_oauth2_config for Authentik integration"
# 5. Flux triggern
flux reconcile kustomization production-apps --with-source
```
### Falls Authentik Enrollment Flow fehlerhaft:
1. Admin UI öffnen: `kubectl port-forward -n authentik svc/authentik 9000:9000`
2. Flows → Enrollment Flow öffnen
3. Stages überprüfen und in richtige Reihenfolge bringen:
- **Identify**: Benutzer identifizieren
- **Write**: Benutzer in DB speichern
- **Enrollment**: Weitere Felder (optional)
- **Finish**: Abschluss
4. Speichern
5. Neuen Test-User erstellen und Enrollment durchlaufen
---
## Erwartete Endergebnisse
Nach erfolgreichem Fix:
1. Benutzer klickt "Sign in with Authentik" auf MAS
2. Authentik-Login-Seite wird angezeigt
3. Nach Login: Enrollment Flow wird angezeigt
4. User füllt Formular aus
5. Nach "Finish": Authentik erstellt User UND verbindet zu Matrix
6. User wird in Matrix-DB angelegt (`_matrix_auth` prefix)
7. User kan sich bei ElementWeb anmelden
---
## 🎯 Nächster Schritt
Bitte folgende Diagnostik durchlaufen und mir die Output berichte:
1. **MAS Logs** (letzten 30 Zeilen)
2. **Authentik Logs** (letzten 30 Zeilen)
3. **MAS Secret (entschlüsselt)** upstream_oauth2_config Block
4. **Authentik OIDC Discovery** Output
5. **Screenshots** der Authentik UI (Enrollment Flow, OIDC Provider, Application)
Damit kann ich dann genau sehen, wo der Bruch in der Kette ist! 🔗
+112
View File
@@ -0,0 +1,112 @@
# 🔧 Troubleshooting Guides
Dieser Ordner enthält detaillierte Troubleshooting- und Reparaturanleitungen für häufige Probleme bei der Authentik/MAS/Matrix Integration.
---
## 📖 Guides
### 1. **DIAGNOSTIK-AUTHENTIK-FLOW.md**
**Für**: Vollständige Diagnose des Authentik Enrollment Flows
**Wann**: Wenn Sie systematisch überprüfen möchten, ob die gesamte OIDC-Kette (Authentik → MAS → Synapse) funktioniert
**Umfasst**:
- Authentik Logs analysieren
- MAS Konfiguration überprüfen
- OIDC Discovery testen
- Enrollment Flow inspizieren
- Fehlersuche mit Debugging-Commands
**Status**: Nutzer Boje - Nur in Authentik erstellt, nicht in Synapse
---
### 2. **AUTHENTIK-FIX-TEMPLATE.md**
**Für**: Konkrete Reparaturen bei MAS/Authentik Integration
**Wann**: Wenn Sie wissen, welches Problem Sie haben und schnelle Lösungen suchen
**Behandelt**:
- Problem 1: MAS kennt Authentik-OIDC nicht
- Problem 2: Authentik Enrollment Flow kaputt
- Problem 3: OIDC Token werden nicht weitergeleitet
- Problem 4: ElementWeb zeigt keinen OIDC-Button
- Problem 5: User in Authentik aber nicht in Synapse
**Status**: Best Practices für häufige Probleme
---
### 3. **AUTHENTIK-INVITATION-FLOW-FIX.md**
**Für**: Reparatur des Invitation Flows bei Einladungslinks
**Wann**: Wenn Nutzer via Einladungslink nicht korrekt erstellt werden
**Problem**: Invitation Flow erfasst nur Username, nicht Email/Name
**Lösung**:
- Prompt Stage mit Email-Feld erstellen/reparieren
- Zur Invitation Flow hinzufügen
- Testen mit neuem Einladungslink
**Status**: Nutzer Klaus - Fehler "kein ausstehender benutzer Anfrage wurde verweigert"
---
### 4. **AUTHENTIK-CREATE-INVITATION-FLOW.md** ⭐ **WICHTIGSTE ANLEITUNG**
**Für**: Neuen separaten Invitation Flow erstellen
**Wann**: Wenn nur ein `matrix-enrollment` Flow existiert (Standard + Invitations gemeinsam)
**Root Cause**: Flow-Konflikt durch gemeinsamen Flow
**Lösung** (Schritt-für-Schritt):
1. Neuen Flow `matrix-invitation` erstellen
2. 5 Stages konfigurieren (Invite → Identify → Prompt → Write → Finish)
3. Email-Feld in Prompt Stage hinzufügen
4. Invitations auf neuen Flow setzen
5. Testen
**Zeitaufwand**: ~20 Minuten
**Status**: Aktuell für Klaus/Boje notwendig
---
## 🎯 Schneller Einstieg
### Szenario 1: "Enrollment funktioniert nicht, ich weiß nicht warum"
**Start**: `DIAGNOSTIK-AUTHENTIK-FLOW.md`
### Szenario 2: "Einladungslink funktioniert nicht"
**Start**: `AUTHENTIK-CREATE-INVITATION-FLOW.md` (wenn nur ein Flow existiert)
→ oder `AUTHENTIK-INVITATION-FLOW-FIX.md` (wenn zwei Flows existieren)
### Szenario 3: "Ich kenne das Problem und brauche Lösungen"
**Start**: `AUTHENTIK-FIX-TEMPLATE.md`
### Szenario 4: "Alles ist kaputt, ich brauche alles Schritt-für-Schritt"
**Start**: `AUTHENTIK-CREATE-INVITATION-FLOW.md``AUTHENTIK-FIX-TEMPLATE.md``DIAGNOSTIK-AUTHENTIK-FLOW.md`
---
## 📋 Bekannte Probleme
| Problem | Nutzer | Guide | Status |
|---------|--------|-------|--------|
| Nur Standard Enrollment funktioniert | akadmin ✅ | - | Resolved |
| User nur in Authentik, nicht in Synapse | Boje | `DIAGNOSTIK-AUTHENTIK-FLOW.md` | In Progress |
| Einladungslink-Fehler: "kein ausstehender benutzer" | Klaus | `AUTHENTIK-CREATE-INVITATION-FLOW.md` | **Fixed (2026-07-27)**`matrix-invitation` Flow hatte nur Invite+Prompt Stage-Bindings, beide auf `order=0`. Write/Password/Login-Stages fehlten komplett. Live gefixt + als Blueprint (`apps/authentik/authentik-blueprints.yaml`) reproduzierbar gemacht. |
| OIDC-Integration unklar | General | `AUTHENTIK-FIX-TEMPLATE.md` | Reference |
---
## 🔗 Verwandte Dokumentation
- `../README.md` Hauptdokumentation & Architektur
- `../TASKS.md` Aufgabenliste & Meilensteine
- `../deployment-guides/` Deployment-Anleitungen für andere Components
---
## 💡 Tipps
- **Immer Logs überprüfen** bevor man herumrät: `kubectl logs -f -n <namespace> <pod>`
- **Browser-Cache löschen** nach Authentik-Änderungen
- **Port-Forward nutzen** für lokales Testen: `kubectl port-forward -n authentik svc/authentik 9000:9000`
- **Kleine Tests machen** (einen User mit Invitation testen, nicht 10 auf einmal)
---
**Zuletzt aktualisiert**: 2026-05-18
**Verfasser**: Claude Code + Thore