Compare commits

...
11 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
8 changed files with 476 additions and 63 deletions
+1
View File
@@ -8,3 +8,4 @@ resources:
- 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
+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.2.1-elementcall-noquotavp9
tag: v0.2.3-elementcall-h264
ingress:
host: axion1337.chat
+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.
+66 -57
View File
@@ -1,7 +1,7 @@
# aXion1337.Chat Task List & Meilensteine
**Last Updated**: 2026-07-28
**Statusübersicht**: [✅ 12 Abgeschlossen] [🔄 0 In Progress] [📋 9+ Pending] [🔒 10 Security]
**Statusübersicht**: [✅ 13 Abgeschlossen] [🔄 0 In Progress] [📋 8+ Pending] [🔒 10 Security]
---
@@ -9,9 +9,9 @@
| Kategorie | Count | Status | Details |
|-----------|-------|--------|---------|
| **Completed** | 12 | ✅ Done | K3S, Flux, ESS, Themes, Desktop, Monitoring, TURN, Authentik (Deploy+Enrollment/Recovery/2FA), Firewall, SSH, coturn Fix, Element Call Fork |
| **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** | 9+ | 📋 Pending | DB Backups, PostgreSQL Migration, NetworkPolicies, MAS-Template-Link, VP9-Retry |
| **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,7 +25,7 @@
---
## 🗓️ Session-Zusammenfassung 2026-07-27/28 (weiter morgen)
## 🗓️ 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`
@@ -43,14 +43,37 @@ verankert. Was in dieser Session erledigt wurde:
**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 für morgen
- **VP9-Retry**: Root Cause des Call-Ausfalls isolieren (Browser-Konsolen-Repro nötig), bevor
wieder versucht wird — siehe Backlog "Element Call Enhancement".
### 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.
@@ -247,31 +270,22 @@ verankert. Was in dieser Session erledigt wurde:
- [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**: MAS Custom-Template-Override, um den 2FA/Passkey-Link direkt auf `account.axion1337.chat/account/` anzuzeigen (statt nur über die Doku-Seite) erfordert Forken der MAS Tera/Askama-Templates via `templates.path`, höherer Aufwand/Risiko, bei MAS-Updates zu pflegen
- [ ] **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
- [x] **Element Call Fork für Custom Constraints** (2026-07-28, Closes #8)
@@ -334,18 +348,29 @@ verankert. Was in dieser Session erledigt wurde:
- 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
@@ -425,24 +450,8 @@ verankert. Was in dieser Session erledigt wurde:
## 🔒 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
@@ -57,9 +57,16 @@
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, dann ohne erzwungenen Codec
(Standard VP8) mit denselben Auflösungswerten neu deployed — funktioniert, live bestätigt.
VP9 vorerst zurückgestellt (Backlog).
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).
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.