Compare commits
44
Commits
e055ec511d
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c53b0fe830 | ||
|
|
fc577ba89b | ||
|
|
6cd6ee618e | ||
|
|
533fcdeac1 | ||
|
|
07ffd0e86e | ||
|
|
fcf3169aa3 | ||
|
|
8114b9ec1e | ||
|
|
44a884062d | ||
|
|
e394cf1a58 | ||
|
|
fff5f54213 | ||
|
|
45ce4417e9 | ||
|
|
5507f13cae | ||
|
|
d90861e112 | ||
|
|
93155c2f37 | ||
|
|
c9a3ae60f5 | ||
|
|
92add9b9a2 | ||
|
|
a252ac0f09 | ||
|
|
df6d291d81 | ||
|
|
90a89f338a | ||
|
|
c7fe0488e2 | ||
|
|
d5b59eb5e9 | ||
|
|
0a7b8f4bf1 | ||
|
|
69c9ef13a1 | ||
|
|
3fbc268e84 | ||
|
|
80cf715e3c | ||
|
|
d9ef54dc99 | ||
|
|
59b7d288dc | ||
|
|
73f56c3414 | ||
|
|
a6eaea6074 | ||
|
|
a3d7e03d90 | ||
|
|
0f24fe8a54 | ||
|
|
803c444885 | ||
|
|
d87c432f89 | ||
|
|
de347343b3 | ||
|
|
b319f21133 | ||
|
|
1c3df5badb | ||
|
|
0f45266426 | ||
|
|
d809b3f695 | ||
|
|
1e65f5b3dd | ||
|
|
3935f359f9 | ||
|
|
e7469a36b8 | ||
|
|
8473a1f0e5 | ||
|
|
c3e42be0d1 | ||
|
|
e816d76f60 |
+22
-34
@@ -3,6 +3,28 @@
|
||||
# weiterhin selbststaendig aus dem Gitea-Mirror. Repo-Topologie: git.lab ist
|
||||
# kanonisch, rohana/Gitea ist Push-Mirror und Flux-Quelle.
|
||||
|
||||
# Ohne workflow-Block legt GitLab auch dann eine Pipeline an, wenn KEIN Job auf sie
|
||||
# passt - und fuehrt sie als "failed". Das ist rot ohne Fehler, und rot ohne Fehler
|
||||
# gewoehnt einem das Hinsehen ab (management #0104). Real passiert am 2026-08-19,
|
||||
# Pipeline 518. Diese Regeln verhindern die leere Pipeline, statt sie hinterher zu
|
||||
# quittieren.
|
||||
#
|
||||
# Reihenfolge zaehlt: schedule zuerst, sonst faellt die taegliche Kanonisierung mit
|
||||
# heraus - sie aendert keine Pfade und wuerde von der changes-Regel nie erfasst.
|
||||
.pfade_mit_pipeline: &pfade_mit_pipeline
|
||||
- apps/**/*
|
||||
- clusters/**/*
|
||||
- .gitlab-ci.yml
|
||||
|
||||
workflow:
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "schedule"
|
||||
- if: $CI_PIPELINE_SOURCE == "web"
|
||||
- if: $CI_PIPELINE_SOURCE == "api"
|
||||
- if: $CI_COMMIT_BRANCH == "main"
|
||||
changes: *pfade_mit_pipeline
|
||||
- when: never
|
||||
|
||||
verify:
|
||||
image: alpine:3.20
|
||||
rules:
|
||||
@@ -200,37 +222,3 @@ canonize_wiki:
|
||||
- git clone --bare --quiet https://rohana.axion1337.de/sorb/ThreadNetWiki.git /tmp/wiki.git
|
||||
- GIT_SSL_CAINFO="$LABCA" git -C /tmp/wiki.git push "https://oauth2:${WIKI_CANONIZE_TOKEN}@${CI_SERVER_HOST}/axion1337.chat/threadnet-wiki.git" 'refs/heads/main:refs/heads/main'
|
||||
- echo "git.lab threadnet-wiki spiegelt jetzt Gitea sorb/ThreadNetWiki (main)."
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TEMPORAER (2026-08-19): beweist, dass CANONIZE_TOKEN in der Pipeline ankommt
|
||||
# und schreiben darf - OHNE main anzufassen. Laeuft nur, wenn eine Web-Pipeline
|
||||
# ausdruecklich mit TEST_CANONIZE=1 gestartet wird. Wird nach dem Test entfernt.
|
||||
# ---------------------------------------------------------------------------
|
||||
canonize_token_test:
|
||||
stage: pruefen
|
||||
image: alpine:3.20
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "web" && $TEST_CANONIZE == "1"
|
||||
script:
|
||||
- apk add --no-cache git ca-certificates >/dev/null
|
||||
- export GIT_SSL_CAINFO="$CI_PROJECT_DIR/ci/lab-ca-chain.crt"
|
||||
- |
|
||||
if [ -z "$CANONIZE_TOKEN" ]; then
|
||||
echo "FEHLT: CANONIZE_TOKEN ist im Job nicht sichtbar."
|
||||
echo "Ursache meist: Variable 'protected', Pipeline laeuft aber auf einem"
|
||||
echo "ungeschuetzten Branch. Genau dann bliebe die Rotation liegen."
|
||||
exit 1
|
||||
fi
|
||||
echo "Sichtbar: CANONIZE_TOKEN ist im Job gesetzt."
|
||||
URL="https://oauth2:${CANONIZE_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"
|
||||
REF="canonize-token-probe-${CI_PIPELINE_ID}"
|
||||
# Ausgabe wird gefiltert: git schreibt die URL bei Fehlern mit ins Log.
|
||||
if git push "$URL" "HEAD:refs/heads/$REF" 2>&1 | sed 's#oauth2:[^@]*@#oauth2:***@#g'; then
|
||||
echo "SCHREIBEN OK: Zweig $REF angelegt."
|
||||
else
|
||||
echo "SCHREIBEN FEHLGESCHLAGEN - Rolle oder Scope reichen nicht."
|
||||
exit 1
|
||||
fi
|
||||
git push "$URL" --delete "refs/heads/$REF" 2>&1 | sed 's#oauth2:[^@]*@#oauth2:***@#g' \
|
||||
&& echo "Aufgeraeumt: $REF wieder entfernt." \
|
||||
|| echo "WARNUNG: $REF blieb liegen, bitte von Hand loeschen."
|
||||
|
||||
@@ -8,7 +8,16 @@ spec:
|
||||
chart:
|
||||
spec:
|
||||
chart: authentik
|
||||
version: "2026.2.3"
|
||||
# Zweite und letzte Stufe des Sprungs (management #0051, Gate 2).
|
||||
# Der Weg war 2026.2.3 -> 2026.5.6 -> 2026.8.0: Authentik verlangt die
|
||||
# letzte Minor-Fassung jeder Reihe und verbietet das Ueberspringen von
|
||||
# Fassungsreihen.
|
||||
#
|
||||
# An den Images gemessen, nicht geschaetzt (CRITICAL / HIGH):
|
||||
# 2026.2.3 27 / 477 2026.5.6 5 / 92 2026.8.0 5 / 21
|
||||
# Die Zwischenstufe trug den gesamten CRITICAL-Gewinn, diese Stufe die
|
||||
# restlichen 71 HIGH. Was bleibt, sind perl-base und libxml2 ohne Fix.
|
||||
version: "2026.8.0"
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: goauthentik
|
||||
@@ -18,6 +27,11 @@ spec:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
# Waehrend des Sprungs 2026.2.3 -> 2026.5.6 -> 2026.8.0 stand hier 0, damit
|
||||
# Flux ein misslingendes Upgrade nicht selbsttaetig auf die alte Fassung
|
||||
# zurueckrollt - der Rueckweg war dort eine Datenbank-Wiederherstellung,
|
||||
# keine Fassungsruecknahme (management #0051). Sprung ist durch, also
|
||||
# wieder scharf: im Normalbetrieb ist die Behebung richtig.
|
||||
retries: 3
|
||||
valuesFrom:
|
||||
- kind: Secret
|
||||
|
||||
@@ -94,3 +94,177 @@ spec:
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8089
|
||||
---
|
||||
# Egress: alles bleibt erlaubt AUSSER dem Metadaten-Dienst (management #0088).
|
||||
#
|
||||
# Warum so schmal? Der Lehrbuch-Schnitt (0.0.0.0/0 except RFC1918) haette hier zwei
|
||||
# Dinge zerrissen, beide ueber 10.0.0.3 (CFGMON im privaten Hetzner-Netz): Alloy
|
||||
# schreibt Metriken und Logs dorthin, und der TURN-Rotations-CronJob erreicht Gitea
|
||||
# ueber einen hostAlias auf dieselbe Adresse. Private Netze bleiben deshalb offen.
|
||||
#
|
||||
# 169.254.0.0/16 ist Link-Local; darin liegt bei Hetzner unter 169.254.169.254 der
|
||||
# Metadaten-Dienst, aus jedem Pod unauthentifiziert abfragbar (nachgemessen 2026-08-19:
|
||||
# HTTP 200 mit instance-id, hostname, region, MAC, Netzkonfiguration). userdata und
|
||||
# public-keys waren LEER - es liegen hier also keine Zugangsdaten offen. Der Gewinn ist
|
||||
# entsprechend bescheiden; der Schnitt kostet aber nichts und schliesst die Klasse.
|
||||
#
|
||||
# ACHTUNG beim Erweitern: Sobald eine Egress-Regel fuer einen Pod existiert, gilt fuer
|
||||
# ihn Default-Deny fuer alles NICHT Aufgefuehrte. Die drei Bloecke unten sind daher
|
||||
# Pflicht, nicht Bequemlichkeit - insbesondere DNS: fehlt es, steht alles, und der
|
||||
# Fehler sieht wie ein Anwendungsproblem aus, nicht wie eine Firewall.
|
||||
#
|
||||
# Rollback: diese eine Policy im Namespace loeschen.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: egress-block-metadata
|
||||
namespace: authentik
|
||||
spec:
|
||||
# Waehlt alle Pods AUSSER denen mit eigener, engerer Regel (#0088).
|
||||
# Bleibt als Auffangnetz: Eine kuenftige Arbeitslast ohne eigene Policy
|
||||
# landet hier - Metadaten gesperrt, Rest offen - statt voellig ungeregelt.
|
||||
# ⚠️ Der Ausschluss MUSS hier stehen: NetworkPolicies sind additiv.
|
||||
podSelector:
|
||||
matchExpressions:
|
||||
- key: app.kubernetes.io/name
|
||||
operator: NotIn
|
||||
values:
|
||||
- authentik
|
||||
- postgresql
|
||||
- authentik-backup
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
# 1. DNS - zuerst, weil ohne DNS nichts geht.
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
ports:
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
- protocol: TCP
|
||||
port: 53
|
||||
# 2. Cluster-intern: Pods und Services (inkl. API-Server 10.43.0.1).
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 10.42.0.0/16
|
||||
- ipBlock:
|
||||
cidr: 10.43.0.0/16
|
||||
# 3. Alles uebrige - Foederation, ACME, SMTP, Registries, privates Netz -
|
||||
# ausser Link-Local.
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 0.0.0.0/0
|
||||
except:
|
||||
- 169.254.0.0/16
|
||||
---
|
||||
# Welle 3 (#0088): Authentik-Namespace, enge Regeln je Arbeitslast.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: egress-nur-intern
|
||||
namespace: authentik
|
||||
spec:
|
||||
# Die Datenbank spricht mit niemandem ausserhalb des Clusters.
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: postgresql
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
ports:
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
- protocol: TCP
|
||||
port: 53
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 10.42.0.0/16
|
||||
- ipBlock:
|
||||
cidr: 10.43.0.0/16
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: egress-storage-box
|
||||
namespace: authentik
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: authentik-backup
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
ports:
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
- protocol: TCP
|
||||
port: 53
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 10.42.0.0/16
|
||||
- ipBlock:
|
||||
cidr: 10.43.0.0/16
|
||||
# Hetzner Storage Box, borg ueber SSH - wie die Backups in `matrix`.
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 91.98.246.178/32
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 23
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: egress-authentik-smtp
|
||||
namespace: authentik
|
||||
spec:
|
||||
# Server und Worker tragen beide app.kubernetes.io/name=authentik.
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: authentik
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
ports:
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
- protocol: TCP
|
||||
port: 53
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 10.42.0.0/16
|
||||
- ipBlock:
|
||||
cidr: 10.43.0.0/16
|
||||
# E-Mail-Versand ueber smtp.ionos.de:587. TRAGEND, nicht optional: die
|
||||
# Blueprints nutzen Passwort-Wiederherstellung und Einladungen per Mail.
|
||||
#
|
||||
# /27 statt zwei /32: Der Name loest heute auf 213.165.67.97 und .113 auf,
|
||||
# beide im selben Block des Anbieters. Eine dritte Adresse wuerde bei /32
|
||||
# den Mailversand brechen, ohne dass jemand eine Regel im Blick haette -
|
||||
# 32 Adressen der Mail-Infrastruktur eines Anbieters sind der kleinere
|
||||
# Preis. (Anders als bei ClamAV: das ist kein CDN vor dem halben Internet.)
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 213.165.67.96/27
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 587
|
||||
#
|
||||
# BEWUSST NICHT erlaubt: version.goauthentik.io (Versionspruefung). Liegt
|
||||
# hinter Cloudflare mit wechselnden Adressen und ist nicht tragend - der
|
||||
# Aufruf scheitert und Authentik protokolliert es. Wer den Fehler spaeter
|
||||
# findet: das ist Absicht, kein Versehen.
|
||||
|
||||
@@ -9,7 +9,13 @@ spec:
|
||||
chart:
|
||||
spec:
|
||||
chart: cert-manager
|
||||
version: "v1.14.0" # Oder aktuellste stabile Version
|
||||
# Schrittweise auf v1.21.1 (management #0051). cert-manager verlangt
|
||||
# "one minor version at a time, always the latest patch" - Ueberspringen
|
||||
# ist nur ueber Deinstallation+Neuinstallation vorgesehen.
|
||||
# Weg: 1.15.5 -> 1.16.5 -> 1.17.4 -> 1.18.6 -> 1.19.6 -> 1.20.3 -> 1.21.1
|
||||
# Am Zielimage gemessen: v1.14.0 traegt 8 CRITICAL und 128 HIGH ueber die
|
||||
# drei Images, v1.21.1 traegt 0 und 24.
|
||||
version: "v1.21.1"
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: jetstack
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# Interner Zeiger auf rohana (#0088).
|
||||
#
|
||||
# rohana.axion1337.de loest oeffentlich auf 188.245.193.243 auf. Der Host ist
|
||||
# aber ueber das private Netz unter 10.0.0.3 erreichbar UND traegt dort ein
|
||||
# gueltiges Let's-Encrypt-Zertifikat fuer genau diesen Namen - am 2026-08-21
|
||||
# mit voller Zertifikatspruefung nachgemessen. Ohne diesen Zeiger verlaesst
|
||||
# jeder Zugriff aus dem Cluster (Gitea-Push der TURN-Rotation, git-storage von
|
||||
# Wiki.js) unnoetig den Cluster und braucht eine Egress-Ausnahme nach draussen.
|
||||
#
|
||||
# Warum eine eigene Zone und kein zweiter `hosts`-Block: Die Corefile fuehrt
|
||||
# bereits `hosts /etc/coredns/NodeHosts` im Hauptblock. Ein zweiter dort waere
|
||||
# ein Konflikt, deshalb ein `.server`-Import mit eigener Zone (Corefile,
|
||||
# letzte Zeile). `reload` im Hauptblock zieht Aenderungen selbsttaetig nach -
|
||||
# ein Neustart von CoreDNS ist nicht noetig.
|
||||
#
|
||||
# ⚠️ PREIS: Aus zwei Wegen wird einer. Faellt 10.0.0.3 aus, ist rohana aus dem
|
||||
# Cluster nicht mehr erreichbar, obwohl der oeffentliche Weg funktionieren
|
||||
# wuerde - und der Fehler sieht aus wie "Gitea ist weg", nicht wie "der private
|
||||
# Pfad ist weg". Wer hier sucht: erst `nslookup rohana.axion1337.de` aus einem
|
||||
# Pod, dann diese ConfigMap.
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: coredns-custom
|
||||
namespace: kube-system
|
||||
data:
|
||||
# Dateiname MUSS auf .server enden - nur die werden am Ende der Corefile
|
||||
# importiert. `.override` liefe im Hauptblock und damit in den Konflikt oben.
|
||||
rohana.server: |
|
||||
rohana.axion1337.de:53 {
|
||||
hosts {
|
||||
10.0.0.3 rohana.axion1337.de
|
||||
fallthrough
|
||||
}
|
||||
forward . /etc/resolv.conf
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- coredns-custom.yaml
|
||||
- ns-cert-manager.yaml
|
||||
- ns-matrix.yaml
|
||||
- cert-manager-repo.yaml
|
||||
|
||||
@@ -8,7 +8,13 @@ spec:
|
||||
chart:
|
||||
spec:
|
||||
chart: alloy
|
||||
version: "0.x"
|
||||
# ⚠️ Bis 2026-08-21 stand hier "0.x". Das sah nach "bleibt aktuell" aus
|
||||
# und war eine DECKE: 0.12.6 ist die letzte 0er-Fassung des Charts, seither
|
||||
# erschienen 1.x-Fassungen, die der Bereich nie erreichen konnte. Alloy blieb
|
||||
# dadurch auf v1.7.5 stehen (2 CRITICAL) waehrend v1.16.3 keine traegt.
|
||||
# Jetzt fest wie jeder andere Release in diesem Repo - eine feste Nummer ist
|
||||
# sichtbar veraltet, ein gedeckelter Bereich nicht.
|
||||
version: "1.9.0"
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: grafana
|
||||
|
||||
@@ -8,7 +8,12 @@ spec:
|
||||
chart:
|
||||
spec:
|
||||
chart: kube-state-metrics
|
||||
version: "5.x"
|
||||
# ⚠️ Bis 2026-08-21 "5.x" - dieselbe Decke wie bei alloy: 5.37.0 ist die
|
||||
# letzte 5er-Fassung, aktuell ist 8.4.0. Stand deshalb auf App v2.15.0
|
||||
# (1 CRITICAL); v2.20.0 traegt keine. Beide Renderings verglichen:
|
||||
# gleiche Objekte, gleiche Selektoren, targetPort 8080 -> benannt "http"
|
||||
# (derselbe Port), RBAC endpoints -> endpointslices.
|
||||
version: "8.4.0"
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: prometheus-community
|
||||
|
||||
@@ -6,3 +6,4 @@ resources:
|
||||
- kube-state-metrics.yaml
|
||||
- alloy-config.yaml
|
||||
- alloy.yaml
|
||||
- networkpolicy.yaml
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
---
|
||||
# Egress: alles bleibt erlaubt AUSSER dem Metadaten-Dienst (management #0088).
|
||||
#
|
||||
# Warum so schmal? Der Lehrbuch-Schnitt (0.0.0.0/0 except RFC1918) haette hier zwei
|
||||
# Dinge zerrissen, beide ueber 10.0.0.3 (CFGMON im privaten Hetzner-Netz): Alloy
|
||||
# schreibt Metriken und Logs dorthin, und der TURN-Rotations-CronJob erreicht Gitea
|
||||
# ueber einen hostAlias auf dieselbe Adresse. Private Netze bleiben deshalb offen.
|
||||
#
|
||||
# 169.254.0.0/16 ist Link-Local; darin liegt bei Hetzner unter 169.254.169.254 der
|
||||
# Metadaten-Dienst, aus jedem Pod unauthentifiziert abfragbar (nachgemessen 2026-08-19:
|
||||
# HTTP 200 mit instance-id, hostname, region, MAC, Netzkonfiguration). userdata und
|
||||
# public-keys waren LEER - es liegen hier also keine Zugangsdaten offen. Der Gewinn ist
|
||||
# entsprechend bescheiden; der Schnitt kostet aber nichts und schliesst die Klasse.
|
||||
#
|
||||
# ACHTUNG beim Erweitern: Sobald eine Egress-Regel fuer einen Pod existiert, gilt fuer
|
||||
# ihn Default-Deny fuer alles NICHT Aufgefuehrte. Die drei Bloecke unten sind daher
|
||||
# Pflicht, nicht Bequemlichkeit - insbesondere DNS: fehlt es, steht alles, und der
|
||||
# Fehler sieht wie ein Anwendungsproblem aus, nicht wie eine Firewall.
|
||||
#
|
||||
# Rollback: diese eine Policy im Namespace loeschen.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: egress-block-metadata
|
||||
namespace: monitoring
|
||||
spec:
|
||||
# Waehlt alle Pods AUSSER denen mit eigener, engerer Regel (#0088).
|
||||
# Bleibt als Auffangnetz: Eine kuenftige Arbeitslast ohne eigene Policy
|
||||
# landet hier - Metadaten gesperrt, Rest offen - statt voellig ungeregelt.
|
||||
# ⚠️ Der Ausschluss MUSS hier stehen: NetworkPolicies sind additiv.
|
||||
podSelector:
|
||||
matchExpressions:
|
||||
- key: app.kubernetes.io/name
|
||||
operator: NotIn
|
||||
values:
|
||||
- alloy
|
||||
- kube-state-metrics
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
# 1. DNS - zuerst, weil ohne DNS nichts geht.
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
ports:
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
- protocol: TCP
|
||||
port: 53
|
||||
# 2. Cluster-intern: Pods und Services (inkl. API-Server 10.43.0.1).
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 10.42.0.0/16
|
||||
- ipBlock:
|
||||
cidr: 10.43.0.0/16
|
||||
# 3. Alles uebrige - Foederation, ACME, SMTP, Registries, privates Netz -
|
||||
# ausser Link-Local.
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 0.0.0.0/0
|
||||
except:
|
||||
- 169.254.0.0/16
|
||||
---
|
||||
# Welle 3 (#0088): Monitoring-Namespace, enge Regeln je Arbeitslast.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: egress-nur-intern
|
||||
namespace: monitoring
|
||||
spec:
|
||||
# Liefert Metriken, holt selbst nichts von aussen.
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: kube-state-metrics
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
ports:
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
- protocol: TCP
|
||||
port: 53
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 10.42.0.0/16
|
||||
- ipBlock:
|
||||
cidr: 10.43.0.0/16
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: egress-ops-host
|
||||
namespace: monitoring
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: alloy
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
ports:
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
- protocol: TCP
|
||||
port: 53
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 10.42.0.0/16
|
||||
- ipBlock:
|
||||
cidr: 10.43.0.0/16
|
||||
# Der operating-Host im PRIVATEN Netz: Logs nach Loki (3100), Metriken per
|
||||
# remote_write nach Prometheus (9090). Beide Ziele aus der laufenden
|
||||
# Alloy-Konfiguration gelesen, nicht angenommen.
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 10.0.0.3/32
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 3100
|
||||
- protocol: TCP
|
||||
port: 9090
|
||||
@@ -4,7 +4,7 @@ metadata:
|
||||
name: coturn-secret
|
||||
namespace: matrix
|
||||
stringData:
|
||||
TURN_SECRET: ENC[AES256_GCM,data:Cbu5SoxQp0L9WFZFbEiyK8j0IJuSgoJE9OpRKZVXyF+PcTM+ewD+3TI8xq+g0C34XDWeaJdxaj3B4yyKsqQLIA==,iv:lW8lD3D75Z0b0EfBj5myUrv4GWMvRTBELjhCVm+QJjc=,tag:nC5L/PcYPoLJP1whSSfSTA==,type:str]
|
||||
TURN_SECRET: ENC[AES256_GCM,data:ATip8K3DKFyyInoJ9/e6SGJobGNruDgNn8fGopkct2cbTHKXduIZWjMEaZ4y47XS5d52qjbHSGSNm/uATsnN3A==,iv:vcGxyduSlqQPZv7fVmWgUypaSSMCDxF2m8WnDiiRaq0=,tag:P+lZAzQZYK5NY7KIr8igug==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
@@ -26,6 +26,6 @@ sops:
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1x4jjwc8nuttwr8us924pvdc6dll5npkc6c8f4zf2hx5d2qu75dtqx0fm0d
|
||||
encrypted_regex: ^(data|stringData)$
|
||||
lastmodified: "2026-08-01T02:00:01Z"
|
||||
mac: ENC[AES256_GCM,data:1Tx2/4O/fcv9BPYLXainmsILC2HbBJtJkgVnbkCe9oVSKca/hVf6zAa8Pd6n/tvHHAuU1Ghm2mglk/IxbNKgCDHH+xaEeK5sfhAK0Ot2ffIMLK5chBNMH0DpvTjqP8ttMidgPJ4XYzizB850gq7pSRrZBJCC2oG66F+WPnfKkxQ=,iv:NIDfNbIyWSgvjpXtQFVjnZFjtKx8sV3a45ssiebkHf0=,tag:hNDmHPTu4RyNMvjyfJRCmA==,type:str]
|
||||
lastmodified: "2026-09-01T02:00:01Z"
|
||||
mac: ENC[AES256_GCM,data:SNMa4VdFzNY5cC3Rv53XhH7J9ptePn5LQR6038m09zOaGa6M9iXrK6f3SgSh8+53u40IewyFf/5ZjwH7d7O08r3uZp+VPcEovubElqbQ6in/YPkWGZ/M1rDJ4y1JM5FlfcYHRqzbTsDwJco7SoYtUthSrmQDIX+Bdb04ZgwzTuQ=,iv:D9ZVhMk71lGaFFrdzeosRB4IJMxzic96QqUAkaCLPrk=,tag:feWHR1RQaroIvJlT80vluQ==,type:str]
|
||||
version: 3.12.2
|
||||
|
||||
@@ -88,7 +88,7 @@ spec:
|
||||
# Bumped on every TURN_SECRET rotation (Issue #38) to force a new pod, since
|
||||
# Kubernetes doesn't restart running pods when a referenced Secret's content
|
||||
# changes and the initContainer that reads it only runs once at pod start.
|
||||
rotated-at: "2026-08-01T02:00:01Z"
|
||||
rotated-at: "2026-09-01T02:00:01Z"
|
||||
spec:
|
||||
hostNetwork: true
|
||||
dnsPolicy: ClusterFirstWithHostNet
|
||||
|
||||
@@ -15,6 +15,8 @@ data:
|
||||
config.json: |
|
||||
{
|
||||
"brand": "aXion1337.Chat",
|
||||
"_kommentar_custom_urls": "Entscheidung sorb 2026-08-19 (management #0099). Blendet den 'Bearbeiten'-Knopf am Servernamen aus (ServerPicker.tsx) - der Homeserver ist damit ueber die Oberflaeche nicht mehr wechselbar, und die Login-Fehlermeldung benennt bei 401/403 den Server statt generisch zu bleiben (ErrorUtils.tsx). Es ist eine OBERFLAECHEN-Sperre: der hs_url-Query-Parameter wird in MatrixChat.tsx (mobile Registrierung, Registrierungs-Bestaetigung) weiterhin ohne diese Pruefung uebernommen. Gegen das offene Advisory GHSA-wrcp-5v3v-3j6v (betrifft < 1.12.22, wir laufen 1.12.17) verengt das die Flaeche, ersetzt aber NICHT das Upstream-Update aus #0099. Dieselbe Zeile muss im Desktop-Client stehen - der hat seine eigene config.json.",
|
||||
"disable_custom_urls": true,
|
||||
"_kommentar_bug_report": "local = der Knopf erscheint als 'Protokolle herunterladen'; die Logs bleiben auf dem Geraet und gehen an KEINEN Server. Ohne diesen Wert ist die ganze Sektion unsichtbar und Nutzer kommen gar nicht an ihre Logs (ThreadNet-Web#9). Der element.io-Endpunkt wurde am 2026-08-01 bewusst entfernt - dieser Wert holt die Faehigkeit zurueck, ohne den Abfluss.",
|
||||
"bug_report_endpoint_url": "local",
|
||||
"_kommentar_report_event": "Weg B (Entscheidung sorb 2026-08-15): Meldungen bleiben im event_reports-Speicher des Servers und werden ueber Element Admin gesichtet - Draupnir bekommt dafuer BEWUSST keine Server-Admin-Rechte. Ohne diesen Text sieht der Melder nach dem Absenden gar nichts und weiss nicht, ob und an wen die Meldung geht (#0045).",
|
||||
|
||||
@@ -35,6 +35,13 @@ data:
|
||||
- '10.0.0.0/8'
|
||||
- '172.16.0.0/12'
|
||||
- '192.168.0.0/16'
|
||||
# Link-local, enthaelt den Cloud-Metadaten-Dienst 169.254.169.254.
|
||||
# Bis 2026-08-20 nur durch die NetworkPolicy gedeckt (#0088) - die
|
||||
# Vorschau holt Adressen, die Nutzer bestimmen, also gehoert die Sperre
|
||||
# auch an diese Stelle und nicht nur eine Ebene tiefer.
|
||||
- '169.254.0.0/16'
|
||||
# CGNAT - dahinter koennen fremde Netze liegen.
|
||||
- '100.64.0.0/10'
|
||||
- '::1/128'
|
||||
- 'fe80::/10'
|
||||
- 'fc00::/7'
|
||||
@@ -69,6 +76,26 @@ data:
|
||||
auto_join_rooms:
|
||||
- "#onboarding:axion1337.chat"
|
||||
auto_join_rooms_for_guests: false
|
||||
# Foederation geschlossen (Entscheidung sorb 2026-08-19, management #0060,
|
||||
# ADR-0021). Leere Liste = mit KEINEM Server foederieren; ein Eintrag hier
|
||||
# oeffnet gezielt fuer diese eine Domain.
|
||||
#
|
||||
# Grundlage war eine Messung, keine Meinung: in vier Monaten Betrieb null
|
||||
# Eintraege in `destinations`, null fremde Nutzer, null Raeume mit fremder
|
||||
# Beteiligung - bei gleichzeitig oeffentlich erreichbarer Foederations-API
|
||||
# (die Delegation fuehrt sie ueber 443, 8448 ist ohnehin zu).
|
||||
#
|
||||
# ⚠️ NICHT betroffen und darf es auch nicht sein:
|
||||
# /_matrix/federation/v1/openid/userinfo. Ueber diesen Endpunkt prueft der
|
||||
# MatrixRTC-Authorisation-Service (lk-jwt-service) die OpenID-Tokens, und er
|
||||
# ruft ihn ueber den OEFFENTLICHEN Namen auf - das Deployment hat keine
|
||||
# hostAliases und ClusterFirst-DNS. Synapse bedient ihn ohne
|
||||
# X-Matrix-Signatur (REQUIRE_AUTH=False), die Whitelist greift dort also
|
||||
# nicht. Wer hier auf Pfad-Ebene sperrt statt ueber diese Liste, legt die
|
||||
# Gruppen-Calls lahm - genau der mrtc-Ausfall, nur mit anderer Ursache.
|
||||
federation:
|
||||
config: |
|
||||
federation_domain_whitelist: []
|
||||
room_publish:
|
||||
config: |
|
||||
room_list_publication_rules:
|
||||
|
||||
@@ -12,7 +12,23 @@ spec:
|
||||
chart:
|
||||
spec:
|
||||
chart: matrix-stack
|
||||
version: "26.4.0"
|
||||
# 26.8.0 (management #0051). Bringt Synapse v1.158.0, MAS 1.22.0,
|
||||
# element-admin 0.1.12, lk-jwt-service 0.4.4 und LiveKit-SFU v1.12.0.
|
||||
#
|
||||
# NICHT 26.8.1: die erschien am 2026-08-21, also am Tag dieses Sprungs.
|
||||
#
|
||||
# Der Grund steht nicht in der CVE-Spalte (17 -> 6 CRITICAL, 217 -> 120 HIGH),
|
||||
# sondern in Synapse v1.157.0: 'Fix a bug introduced in Synapse v1.150.0 where
|
||||
# reactivating a deactivated and erased user did not restore their profile,
|
||||
# breaking login, name changes, and invitations.' Wir fuhren v1.151.0 - das war
|
||||
# die Ursache des Vorfalls vom 2026-08-21 (AAR clark-calls-reset).
|
||||
#
|
||||
# Alle Bruchstellen der Spanne gegen unseren Stand geprueft und gegenstandslos:
|
||||
# keine Synapse-Worker (v1.152, ESS 26.5.0), stabile MAS-Integration statt
|
||||
# MSC3861 bereits in Gebrauch (v1.157), kein livekitAuth.keysYaml (26.8.1).
|
||||
# Unsere elementWeb-Ueberschreibung auf threadnet-web ueberlebt - beide
|
||||
# Chart-Fassungen wurden mit unseren echten Werten gerendert und verglichen.
|
||||
version: "26.8.0"
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: element-ess-oci
|
||||
@@ -74,6 +90,27 @@ spec:
|
||||
cpu: 50m
|
||||
limits:
|
||||
memory: 128Mi
|
||||
# Client-IP erhalten statt sie wegzu-SNATten.
|
||||
#
|
||||
# Mit dem Chart-Default `Cluster` verteilt kube-proxy eingehende NodePort-Pakete
|
||||
# selbst weiter und ersetzt dabei die Absenderadresse durch die des Knotens. Der
|
||||
# SFU sieht den Client deshalb nie unter seiner echten Adresse: Am 2026-08-19 war
|
||||
# das ausgewaehlte ICE-Paar `prflx 10.42.0.1` - eine peer-reflexive Adresse aus
|
||||
# dem Cluster-Netz. Das macht die Kandidatenwahl unnoetig instabil und ist die
|
||||
# dokumentierte LiveKit-Empfehlung fuer Kubernetes.
|
||||
#
|
||||
# `Local` ist hier ohne Nachteil, weil der Cluster aus EINEM Knoten besteht: Der
|
||||
# uebliche Preis - nur Knoten mit laufendem Pod nehmen Verkehr an - kann nicht
|
||||
# greifen. Bei einem zweiten Knoten waere das neu zu bewerten.
|
||||
#
|
||||
# Was es NICHT behebt: das schubweise ICE-Flappen (16.08. = 119 Wechsel, 18.08.
|
||||
# = 0). Das ist aelter und stammt aus der Zahl der Netzwerkpfade auf Client-Seite.
|
||||
sfu:
|
||||
exposedServices:
|
||||
rtcMuxedUdp:
|
||||
externalTrafficPolicy: Local
|
||||
rtcTcp:
|
||||
externalTrafficPolicy: Local
|
||||
|
||||
# Element Web
|
||||
elementWeb:
|
||||
@@ -81,6 +118,35 @@ spec:
|
||||
image:
|
||||
registry: rohana.axion1337.de
|
||||
repository: sorb/threadnet-web
|
||||
# v0.6.0-rc.2 = Anschluss an Element Web v1.12.26 (ADR-0022). Ein echter
|
||||
# Merge-Commit statt Cherry-Picks; die Fork-Patches mussten umziehen,
|
||||
# weil Upstream MImageBody.tsx geloescht und den Raumlisten-Inhalt nach
|
||||
# RoomListItemContent ausgelagert hat. Betroffen sind genau die zwei
|
||||
# Stellen, die die Abnahme pruefen muss: die ClamAV-Fehlermeldung im
|
||||
# Bild-Pfad (ImageBodyViewModel.computeErrorLabel) und die
|
||||
# Call-Teilnehmerliste in der Raumliste.
|
||||
# Am 19.08. ausgerollt und nach wenigen Minuten zurueckgenommen: die
|
||||
# Raumliste stuerzte bei jedem Eintrag ab (react-soft-crash), weil in
|
||||
# RoomListItemViewModel.ts eine getValue-Zeile auf den von Upstream
|
||||
# entfernten Labs-Schalter feature_room_list_sections stehenblieb -
|
||||
# Sektionen laufen dort inzwischen ueber RoomList.showSections. Eine
|
||||
# Leiche aus der Merge-Aufloesung, die kein Build fangen konnte:
|
||||
# getValue nimmt einen String und wirft erst zur Laufzeit.
|
||||
# rc.3 = derselbe Merge ohne den Rest, plus ein typecheck-Job, den
|
||||
# docker_web als needs fuehrt: kein Image mehr ohne Typpruefung. Der
|
||||
# web-Job baut nur, webpack wirft Typen weg - tsc hatte den Fehler die
|
||||
# ganze Zeit gemeldet, gefragt hatte ihn niemand. Massstab ist "kein
|
||||
# Fehler ausserhalb von node_modules", weil Upstream v1.12.26 selbst
|
||||
# nicht typrein ist (matrix-js-sdk 42.2.0, in einem sauberen Checkout
|
||||
# gegengeprueft).
|
||||
# v0.6.0 = Abnahme auf rc.3 bestanden (19.08.), derselbe Quellstand
|
||||
# 8ca03fe unter Release-Nummer. Geprueft am laufenden System, nicht nur
|
||||
# am Build: Raumliste laedt, ClamAV blockt beim Senden (ein Scan-Aufruf,
|
||||
# kein Upload), abgewiesene Datei zeigt die Meldung, und das .png wurde
|
||||
# zugestellt, beim Herunterladen abgewiesen und die Meldung gerendert -
|
||||
# damit ist der portierte Bild-Pfad belegt, nicht nur vermutet. Auch die
|
||||
# Call-Teilnehmerliste, der zweite umgezogene Patch, steht richtig drin.
|
||||
# Rueckhebel bleibt der Tag-Revert auf v0.5.4.
|
||||
# v0.5.4 = Sender-Verifikation (threadnet-call fee9866): auf Safari
|
||||
# uebersprang LiveKit den Track-Tausch stumm (sender?.replaceTrack),
|
||||
# das rohe Mikro blieb auf der Leitung. Der Fork prueft und erzwingt
|
||||
@@ -116,7 +182,7 @@ spec:
|
||||
# Die Linie beginnt bei v0.3.0, dem ersten kanonischen CI-Build aus
|
||||
# apps/web/Dockerfile - er loeste die Derivat-Images ab, deren
|
||||
# Entrypoint ohne Exec-Bit /config.json still brach (ThreadNet-Web#8).
|
||||
tag: v0.5.4
|
||||
tag: v0.6.0
|
||||
ingress:
|
||||
host: axion1337.chat
|
||||
|
||||
|
||||
@@ -34,7 +34,8 @@ spec:
|
||||
mountPath: /html
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.26-alpine
|
||||
# 1.26-alpine trug 2 CRITICAL; 1.31.4-alpine gemessen: 0 CRITICAL, 0 HIGH.
|
||||
image: nginx:1.31.4-alpine
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
volumeMounts:
|
||||
|
||||
@@ -16,7 +16,7 @@ patches:
|
||||
value: "401f8a87d0ef5d91d2e5032d4aede42c"
|
||||
- op: add
|
||||
path: /metadata/annotations/turn-secret-checksum
|
||||
value: "05aad8b742fb02c42f4c1a5629ae31e1"
|
||||
value: "fd34214736091bf3ced17afe7bb4d61f"
|
||||
|
||||
resources:
|
||||
- matrix-postgres-auth.yaml
|
||||
@@ -42,6 +42,7 @@ resources:
|
||||
- synapse-backup.yaml
|
||||
# Monatliche Restore-Probe: spielt die Sicherungen isoliert zurueck (#0030)
|
||||
- restore-drill.yaml
|
||||
- restore-drill-media.yaml
|
||||
# Automatisierte TURN-Secret-Rotation (Issue #38)
|
||||
- turn-secret-rotation-secret.yaml
|
||||
- turn-secret-rotation.yaml
|
||||
@@ -65,6 +66,7 @@ resources:
|
||||
- wiki-ingress.yaml
|
||||
- wikijs-config.yaml # Konfig-Job (headless Setup + OIDC + Rollen)
|
||||
- wikijs-postgres-backup.yaml # Nächtliches Borg-Backup der Wiki-DB (#0048)
|
||||
- wikijs-gruppenpruefung.yaml # Tägliche Prüfung auf Nutzer ohne Gruppe (#0103)
|
||||
|
||||
# Synapse-Modul als eigene Datei gepflegt (lintbar/testbar), aber als ConfigMap gemounted -
|
||||
# disableNameSuffixHash, da der Name in synapse-values.yaml's eingebettetem values.yaml
|
||||
|
||||
@@ -109,6 +109,23 @@ spec:
|
||||
port: haproxy-403
|
||||
- protocol: TCP
|
||||
port: haproxy-wkd
|
||||
# Matrix Authentication Service ruft Synapse ueber haproxy.
|
||||
#
|
||||
# ⚠️ Bis ESS 26.4.0 zeigte MAS DIREKT auf matrix-stack-synapse-main; dorthin
|
||||
# erlaubt allow-ingress-synapse den Zugriff, hier war nichts noetig. ESS 26.8.0
|
||||
# stellt matrix.endpoint auf den haproxy-Dienst matrix-stack-synapse um - und
|
||||
# damit lief MAS in die default-deny-Regel. Folge: /oauth2/token antwortete mit
|
||||
# 500 'failed to provision device', die Anmeldung brach ab (2026-08-21, #0051).
|
||||
#
|
||||
# Die Lehre steht im AAR: Eine Regel, die auf einen Dienstnamen zeigt, haengt an
|
||||
# einer Entscheidung des Charts, nicht an unserer.
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: matrix-authentication-service
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: haproxy-synapse
|
||||
# Draupnir (Issue #18) calls Synapse's client-server API directly, in-namespace -
|
||||
# without this it would be silently blocked by the default-deny policy.
|
||||
- from:
|
||||
@@ -405,6 +422,311 @@ spec:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: wikijs-backup
|
||||
# Tägliche Gruppenprüfung (wikijs-gruppenpruefung.yaml, #0103) liest nur.
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: wikijs-gruppenpruefung
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: postgres
|
||||
---
|
||||
# Egress: alles bleibt erlaubt AUSSER dem Metadaten-Dienst (management #0088).
|
||||
#
|
||||
# Warum so schmal? Der Lehrbuch-Schnitt (0.0.0.0/0 except RFC1918) haette hier zwei
|
||||
# Dinge zerrissen, beide ueber 10.0.0.3 (CFGMON im privaten Hetzner-Netz): Alloy
|
||||
# schreibt Metriken und Logs dorthin, und der TURN-Rotations-CronJob erreicht Gitea
|
||||
# ueber einen hostAlias auf dieselbe Adresse. Private Netze bleiben deshalb offen.
|
||||
#
|
||||
# 169.254.0.0/16 ist Link-Local; darin liegt bei Hetzner unter 169.254.169.254 der
|
||||
# Metadaten-Dienst, aus jedem Pod unauthentifiziert abfragbar (nachgemessen 2026-08-19:
|
||||
# HTTP 200 mit instance-id, hostname, region, MAC, Netzkonfiguration). userdata und
|
||||
# public-keys waren LEER - es liegen hier also keine Zugangsdaten offen. Der Gewinn ist
|
||||
# entsprechend bescheiden; der Schnitt kostet aber nichts und schliesst die Klasse.
|
||||
#
|
||||
# ACHTUNG beim Erweitern: Sobald eine Egress-Regel fuer einen Pod existiert, gilt fuer
|
||||
# ihn Default-Deny fuer alles NICHT Aufgefuehrte. Die drei Bloecke unten sind daher
|
||||
# Pflicht, nicht Bequemlichkeit - insbesondere DNS: fehlt es, steht alles, und der
|
||||
# Fehler sieht wie ein Anwendungsproblem aus, nicht wie eine Firewall.
|
||||
#
|
||||
# Rollback: diese eine Policy im Namespace loeschen.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: egress-block-metadata
|
||||
namespace: matrix
|
||||
spec:
|
||||
# Waehlt alle Pods AUSSER denen mit eigener, engerer Regel (#0088).
|
||||
#
|
||||
# WER HIER NOCH DRIN STEHT, UND WARUM (Stand 2026-08-21, Welle 4):
|
||||
#
|
||||
# synapse-main Foederation zu beliebigen Matrix-Servern und URL-Vorschau
|
||||
# zu Adressen, die Nutzer bestimmen. Ein Ziel laesst sich
|
||||
# nicht angeben. Die richtige Kontrolle sitzt eine Ebene
|
||||
# hoeher: url_preview_ip_range_blacklist in synapse-values,
|
||||
# seit 2026-08-20 inkl. 169.254.0.0/16 und 100.64.0.0/10.
|
||||
#
|
||||
# coturn Relais fuer Medien zu beliebigen Clients - das IST der
|
||||
# matrix-rtc-sfu Dienst. Eine Zielliste gaebe es nur, wenn man wuesste,
|
||||
# wer anruft.
|
||||
#
|
||||
# clamav Signaturen von database.clamav.net. Loest auf Cloudflare
|
||||
# auf, Adressen wechseln. Pinnen braeche den Abgleich STILL
|
||||
# - Signaturen veralten, der Dienst laeuft weiter, niemand
|
||||
# merkt es. Cloudflare-Bereiche zu erlauben waere keine
|
||||
# Einschraenkung. Besserer Weg waere ein interner Spiegel;
|
||||
# das ist eigene Infrastruktur und bewusst nicht Teil von
|
||||
# #0088. Bis dahin: Ausgang offen, Grund hier.
|
||||
#
|
||||
# ⚠️ Der Ausschluss muss HIER stehen, nicht in einer zusaetzlichen engen Policy:
|
||||
# NetworkPolicies sind additiv. Solange diese Policy 0.0.0.0/0 fuer einen Pod
|
||||
# erlaubt, aendert eine zweite, strengere Regel fuer denselben Pod nichts.
|
||||
#
|
||||
# `NotIn` waehlt auch Pods, die das Label gar nicht tragen - am 2026-08-20 im
|
||||
# Cluster nachgemessen (29 Pods, 1 Treffer, notin liefert 28). Drei Pods im
|
||||
# Namespace fuehren kein app.kubernetes.io/name, deshalb der zweite Ausdruck
|
||||
# auf `app`. Beide Ausdruecke sind UND-verknuepft.
|
||||
podSelector:
|
||||
matchExpressions:
|
||||
- key: app.kubernetes.io/name
|
||||
operator: NotIn
|
||||
values:
|
||||
- element-web
|
||||
- element-admin
|
||||
- postgres
|
||||
- wikijs-postgres
|
||||
- clamav-http-scanner
|
||||
- wikijs-gruppenpruefung
|
||||
- haproxy
|
||||
- draupnir
|
||||
# Welle 4 (#0088): Upstream ist Authentik ueber den oeffentlichen
|
||||
# Namen, der auf die Knoten-Adresse zeigt - die unterliegt der
|
||||
# Policy nicht. Homeserver ueber den internen Service-Namen.
|
||||
- matrix-authentication-service
|
||||
# Welle 2 (#0088): je genau ein Ziel, eigene Policy unten.
|
||||
- synapse-backup
|
||||
- wikijs-backup
|
||||
- turn-secret-rotation
|
||||
- wikijs
|
||||
# Welle 1 (#0088): kein externer Bedarf nachweisbar.
|
||||
- matrix-rtc-authorisation-service
|
||||
- wikijs-config
|
||||
- init-secrets
|
||||
- synapse-check-config
|
||||
- deployment-markers-pre
|
||||
- deployment-markers-post
|
||||
- key: app
|
||||
operator: NotIn
|
||||
values:
|
||||
- element-web-docs
|
||||
- concierge-bot
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
# 1. DNS - zuerst, weil ohne DNS nichts geht.
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
ports:
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
- protocol: TCP
|
||||
port: 53
|
||||
# 2. Cluster-intern: Pods und Services (inkl. API-Server 10.43.0.1).
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 10.42.0.0/16
|
||||
- ipBlock:
|
||||
cidr: 10.43.0.0/16
|
||||
# 3. Alles uebrige - Foederation, ACME, SMTP, Registries, privates Netz -
|
||||
# ausser Link-Local.
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 0.0.0.0/0
|
||||
except:
|
||||
- 169.254.0.0/16
|
||||
|
||||
---
|
||||
# Pods ohne jeden Bedarf an Ausgang nach draussen (#0088).
|
||||
#
|
||||
# Zehn Arbeitslasten sprechen ausschliesslich cluster-intern: die drei nginx-Dienste,
|
||||
# beide Postgres, der ClamAV-Vorschalter, haproxy, die Wiki-Gruppenpruefung und die
|
||||
# beiden Matrix-Bots (Foederation ist geschlossen, Draupnir holt keine fremden
|
||||
# Policy-Listen). Fuer sie faellt der pauschale 0.0.0.0/0-Satz weg; erlaubt bleibt
|
||||
# DNS und das Cluster-Netz.
|
||||
#
|
||||
# Zwei Policies, weil ein podSelector nicht ueber zwei Label-Schluessel odern kann -
|
||||
# die Vereinigung mehrerer Policies leistet genau das.
|
||||
#
|
||||
# Rollback: beide Policies loeschen und den Ausschluss in egress-block-metadata
|
||||
# zuruecknehmen.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: egress-nur-intern
|
||||
namespace: matrix
|
||||
spec:
|
||||
podSelector:
|
||||
matchExpressions:
|
||||
- key: app.kubernetes.io/name
|
||||
operator: In
|
||||
values:
|
||||
- element-web
|
||||
- element-admin
|
||||
- postgres
|
||||
- wikijs-postgres
|
||||
- clamav-http-scanner
|
||||
- wikijs-gruppenpruefung
|
||||
- haproxy
|
||||
- draupnir
|
||||
# Welle 4 (#0088): Upstream ist Authentik ueber den oeffentlichen
|
||||
# Namen, der auf die Knoten-Adresse zeigt - die unterliegt der
|
||||
# Policy nicht. Homeserver ueber den internen Service-Namen.
|
||||
- matrix-authentication-service
|
||||
# Welle 1 (#0088): Deploy- und Hilfs-Arbeitslasten.
|
||||
- matrix-rtc-authorisation-service
|
||||
- wikijs-config
|
||||
- init-secrets
|
||||
- synapse-check-config
|
||||
- deployment-markers-pre
|
||||
- deployment-markers-post
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
ports:
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
- protocol: TCP
|
||||
port: 53
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 10.42.0.0/16
|
||||
- ipBlock:
|
||||
cidr: 10.43.0.0/16
|
||||
---
|
||||
# Dieselben Regeln fuer die zwei Pods, die kein app.kubernetes.io/name fuehren.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: egress-nur-intern-app-label
|
||||
namespace: matrix
|
||||
spec:
|
||||
podSelector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- element-web-docs
|
||||
- concierge-bot
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
ports:
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
- protocol: TCP
|
||||
port: 53
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 10.42.0.0/16
|
||||
- ipBlock:
|
||||
cidr: 10.43.0.0/16
|
||||
---
|
||||
# Welle 2 (#0088): Arbeitslasten mit genau EINEM externen Ziel.
|
||||
#
|
||||
# Eigene Policies statt Aufnahme in egress-nur-intern: Die Egress-Regeln einer
|
||||
# Policy gelten fuer ALLE von ihr gewaehlten Pods. Haenge man die Storage Box an
|
||||
# egress-nur-intern, bekaemen sie auch die vierzehn, die sie nicht brauchen.
|
||||
#
|
||||
# ⚠️ Die Namen stehen zusaetzlich in der NotIn-Liste von egress-block-metadata.
|
||||
# Ohne das bliebe die breite Erlaubnis bestehen und diese Regeln waeren
|
||||
# wirkungslos - NetworkPolicies sind additiv.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: egress-storage-box
|
||||
namespace: matrix
|
||||
spec:
|
||||
podSelector:
|
||||
matchExpressions:
|
||||
- key: app.kubernetes.io/name
|
||||
operator: In
|
||||
values:
|
||||
- synapse-backup
|
||||
- wikijs-backup
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
ports:
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
- protocol: TCP
|
||||
port: 53
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 10.42.0.0/16
|
||||
- ipBlock:
|
||||
cidr: 10.43.0.0/16
|
||||
# Hetzner Storage Box, borg ueber SSH. Einziges Ziel ausserhalb des
|
||||
# Clusters - kein privater Weg dorthin bekannt (Stand 2026-08-21).
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 91.98.246.178/32
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 23
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: egress-rohana
|
||||
namespace: matrix
|
||||
spec:
|
||||
podSelector:
|
||||
matchExpressions:
|
||||
- key: app.kubernetes.io/name
|
||||
operator: In
|
||||
values:
|
||||
# Schreibt den rotierten TURN-Schluessel nach Gitea - die einzige
|
||||
# bewusste Gitea-Ausnahme der Gruppe (AGENTS.md).
|
||||
- turn-secret-rotation
|
||||
# git-storage von Wiki.js: https://rohana.axion1337.de/sorb/ThreadNetWiki.git
|
||||
# (aus der laufenden Konfiguration gelesen, nicht angenommen).
|
||||
- wikijs
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
ports:
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
- protocol: TCP
|
||||
port: 53
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 10.42.0.0/16
|
||||
- ipBlock:
|
||||
cidr: 10.43.0.0/16
|
||||
# rohana ueber das PRIVATE Netz. Der oeffentliche Name zeigt seit
|
||||
# coredns-custom.yaml hierher; das Zertifikat gilt auch auf diesem Weg.
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 10.0.0.3/32
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 443
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
# Monatliche Medien-Restore-Probe (#0030). Gegenstueck zu restore-drill.yaml: dort
|
||||
# Datenbanken, hier der Medienspeicher von Synapse.
|
||||
#
|
||||
# Warum eigener Job und nicht angehaengt: Die Datenbank-Probe braucht eine Wegwerf-Postgres,
|
||||
# diese hier das Produktions-PVC (lesend). Zwei verschiedene Rechte- und Fehlerbilder in
|
||||
# einem Job zu buendeln macht die Fehlersuche im Ernstfall schwerer, nicht leichter.
|
||||
#
|
||||
# Der NAME ist bewusst `restore-drill-media`: Die bestehende Regel BackupJobFailed matcht
|
||||
# `restore-drill.*`, also ist der Fehlschlag ohne neue Alarmregel abgedeckt und landet
|
||||
# ueber die einzige Alertmanager-Route im wartung-Raum.
|
||||
#
|
||||
# ⚠️ Das Produktions-PVC wird `readOnly: true` gemountet. Geschrieben wird ausschliesslich
|
||||
# nach emptyDir. Das PVC ist ReadWriteOnce; im heutigen Ein-Knoten-Cluster ist ein zweiter
|
||||
# lesender Mount unproblematisch. Kaeme je ein zweiter Knoten dazu, koennte der Pod dort
|
||||
# landen und der Mount scheitern - der Job schluege fehl und alarmierte, ohne dass an den
|
||||
# Daten etwas waere. Dann gehoert hier eine Node-Affinitaet zu Synapse hin.
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: restore-drill-media
|
||||
namespace: matrix
|
||||
spec:
|
||||
# 4. des Monats, 05:20 - eine Stunde nach der Datenbank-Probe, damit beide denselben
|
||||
# frischen Sicherungsstand pruefen, sich aber nicht um Storage Box und CPU streiten.
|
||||
schedule: "20 5 4 * *"
|
||||
concurrencyPolicy: Forbid
|
||||
successfulJobsHistoryLimit: 3
|
||||
failedJobsHistoryLimit: 3
|
||||
jobTemplate:
|
||||
spec:
|
||||
backoffLimit: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: synapse-backup # NetworkPolicy/Egress wie die Backups
|
||||
app.kubernetes.io/component: restore-drill-media
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: drill
|
||||
image: rohana.axion1337.de/sorb/axion-backup:v2
|
||||
env:
|
||||
- name: BORG_PASSPHRASE
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: synapse-backup-credentials
|
||||
key: borg-passphrase
|
||||
- name: SSH_PRIVATE_KEY_FILE
|
||||
value: /secrets/ssh/ssh-private-key
|
||||
- name: SSH_KNOWN_HOSTS_FILE
|
||||
value: /secrets/known-hosts/known_hosts
|
||||
command: ["sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
set -eu
|
||||
export BORG_RSH="ssh -i $SSH_PRIVATE_KEY_FILE -o UserKnownHostsFile=$SSH_KNOWN_HOSTS_FILE -o StrictHostKeyChecking=yes"
|
||||
export BORG_REPO='ssh://u641795@u641795.your-storagebox.de:23/./synapse-backup'
|
||||
cd /scratch
|
||||
latest=$(borg list --last 1 --format '{archive}' "$BORG_REPO")
|
||||
echo "[drill] Archiv: $latest"
|
||||
borg extract "$BORG_REPO::$latest" media/media_store
|
||||
anz=$(find media/media_store -type f | wc -l)
|
||||
echo "[drill] zurueckgespielt: ${anz} Dateien, $(du -sk media/media_store | cut -f1) KB"
|
||||
echo "[drill] Produktion jetzt: $(find /live/media_store -type f | wc -l) Dateien, $(du -sk /live/media_store | cut -f1) KB"
|
||||
if [ "$anz" -le 0 ]; then
|
||||
echo "[drill] FEHLER: nichts extrahiert - Sicherung unbrauchbar!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
( cd /scratch/media/media_store && find . -type f -print0 | xargs -0 sha256sum ) | sort -k2 > /scratch/restore.sha
|
||||
( cd /live/media_store && find . -type f -print0 | xargs -0 sha256sum ) | sort -k2 > /scratch/live.sha
|
||||
awk '{print $2}' /scratch/restore.sha | sort > /scratch/r.names
|
||||
awk '{print $2}' /scratch/live.sha | sort > /scratch/l.names
|
||||
comm -12 /scratch/r.names /scratch/l.names > /scratch/beide.names
|
||||
|
||||
# Die Sicherung ist eine Momentaufnahme, die Produktion laeuft weiter.
|
||||
# Beweiskraeftig ist deshalb die Schnittmenge - die Gesamtzahlen weichen
|
||||
# regulaer ab, weil Synapse seine Vorschau-Caches selbst aufraeumt.
|
||||
abw() { awk 'NR==FNR{h[$2]=$1;next} ($2 in h) && (h[$2]!=$1){print $2}' /scratch/live.sha "$1"; }
|
||||
n_abw=$(abw /scratch/restore.sha | wc -l)
|
||||
echo "[drill] gemeinsam: $(wc -l < /scratch/beide.names), davon abweichend: ${n_abw}"
|
||||
if [ "$n_abw" -ne 0 ]; then
|
||||
echo "[drill] FEHLER: gemeinsame Dateien sind NICHT byte-gleich:"
|
||||
abw /scratch/restore.sha | head -10
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Nur url_cache und url_cache_thumbnails duerfen einseitig sein - alles
|
||||
# andere waere ein echter Fehlbetrag und kein Cache-Effekt.
|
||||
fremd=$(comm -23 /scratch/r.names /scratch/l.names | sed 's|^\./||' | cut -d/ -f1 \
|
||||
| grep -vE '^url_cache(_thumbnails)?$' | sort -u || true)
|
||||
if [ -n "$fremd" ]; then
|
||||
echo "[drill] FEHLER: einseitige Dateien ausserhalb der Vorschau-Caches:"
|
||||
echo "$fremd"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Positivkontrolle bei JEDEM Lauf: eine gemeinsame Datei um ein Byte
|
||||
# veraendern und pruefen, dass der Vergleich es merkt. Ein Abgleich, der
|
||||
# nur je "gleich" gesagt hat, ist eine Vermutung - auch als CronJob.
|
||||
opfer=$(head -1 /scratch/beide.names)
|
||||
printf 'x' >> "/scratch/media/media_store/${opfer}"
|
||||
( cd /scratch/media/media_store && find . -type f -print0 | xargs -0 sha256sum ) | sort -k2 > /scratch/kaputt.sha
|
||||
if [ "$(abw /scratch/kaputt.sha | wc -l)" -eq 0 ]; then
|
||||
echo "[drill] FEHLER: der Vergleich erkennt eine absichtliche Aenderung NICHT - diese Probe beweist nichts!"
|
||||
exit 1
|
||||
fi
|
||||
echo "[drill] OK - ${anz} Dateien zurueckgespielt, alle gemeinsamen byte-gleich, Vergleich gegengeprueft"
|
||||
volumeMounts:
|
||||
- name: ssh-key
|
||||
mountPath: /secrets/ssh
|
||||
readOnly: true
|
||||
- name: known-hosts
|
||||
mountPath: /secrets/known-hosts
|
||||
readOnly: true
|
||||
- name: scratch
|
||||
mountPath: /scratch
|
||||
- name: live
|
||||
mountPath: /live
|
||||
readOnly: true
|
||||
resources:
|
||||
requests:
|
||||
memory: 256Mi
|
||||
cpu: 100m
|
||||
limits:
|
||||
memory: 1Gi
|
||||
volumes:
|
||||
- name: ssh-key
|
||||
secret:
|
||||
secretName: synapse-backup-credentials
|
||||
defaultMode: 0400
|
||||
items:
|
||||
- key: ssh-private-key
|
||||
path: ssh-private-key
|
||||
- name: known-hosts
|
||||
configMap:
|
||||
name: synapse-backup-known-hosts
|
||||
- name: scratch
|
||||
emptyDir:
|
||||
sizeLimit: 4Gi
|
||||
- name: live
|
||||
persistentVolumeClaim:
|
||||
claimName: matrix-stack-synapse-media
|
||||
readOnly: true
|
||||
@@ -4,7 +4,7 @@ metadata:
|
||||
name: synapse-turn-secret
|
||||
namespace: matrix
|
||||
stringData:
|
||||
values.yaml: ENC[AES256_GCM,data:jGoBDvjGymp4qEVEB6hzgRJk1ZUVB+jNxfhqpl7tG7UQmZGskix43rMJIOdDZihBTOUHP4emhlOzTxqx3SCAFCb8Ie78xA+DfjuLnQ6PQ2jqN48+yc7QE2xbNr5eWlYYKzl1u/z0kCQT723ptA+tQzYCzUq5U624HHxki0firgoSJmaF9S/vbwXHLPqtnviYX48Eim0suwMnavWdWGd1X3yvqUoZnWKz3eDQrH3cp4fQAdnRosveIcWwz+1na67Zshczq7hhlNp/WZ95guAOiqlLxGMPwJQJou24UYynwQTtDle5IzCxOww+sCNuQtPp29NE1gZ5pxIM7Ys6Ul7UxtPAww6OCVbkYYGmQxIq0cY4CrFrgLDJYQ2JJPIexc1ooeIDcYiNYts/2TCAj9ck+Vd+6xN1NEYwm0ZTLL9naLJJ1PEzRkfy3bCOTfKWCRNwLzn/3gsRgr+hrf5dJ6Av6VbM0Ae9xhTEQ4d64C8YRjb4G9BnDRGuaHu+5DYAqiFUEMm6cM4IV3E=,iv:ZRGQREujdrDCNj1OcgV7HAjZREArdnodnD2J5BxkGQo=,tag:2FOO9/rv4IteyulLnsR/YA==,type:str]
|
||||
values.yaml: ENC[AES256_GCM,data:xc/pUhIbmGizSCS/P5EI+5zgxyoM7SiHOo9loGmUfQ0iiL+EntJlK4WiaHwpvAwCDA/N+4IrUMp5kBjAnCz/Y4qD/geXz932ccG4mIH1ncD5F/2/mFVN+gqo4q+xwy27wzR1RS2p3hJN7z11HrXazqMIgDvz3EXqeYYD6YnM8Z1IfFdGA+daSSoRDiiqh9XFL7g2aVKomWImxkP4kXU4vJCoXXmd3DF8guZv7zQ1uhJtQOzGHF1cST+pxBV4/+zkpdY7Du7L3cNc7qk/oJNbMh3VEyB6Yi3krcLLbXuTSXZ9qBHhXLrsWngxoXRSo++PIfbqAuJ1iSzQ8Zz0NUbqOS+tILLo/Twe9DjwT5hunIPX1vIN/4SOqPgS0ZQm+VISs5eixxYzAA8xvIbc/0twbWA+33HEo5G6mXe6uzoNqsWE1PKYw7gl7vbVqnTYkyMQXdAPIbfKaW4JQ0zkpeFnJpGdKnYse2xwfofEtV6aiV3eNo1IyUjsp2kN7F4RBd1e1SqrI9nyUcM=,iv:MFc+1fDPK5bg607mMN2sDAufmFzE7hHqEmnejMjmiCI=,tag:pgFMtvcm3a9I+coXMO0xXA==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
@@ -26,6 +26,6 @@ sops:
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1x4jjwc8nuttwr8us924pvdc6dll5npkc6c8f4zf2hx5d2qu75dtqx0fm0d
|
||||
encrypted_regex: ^(data|stringData)$
|
||||
lastmodified: "2026-08-01T02:00:01Z"
|
||||
mac: ENC[AES256_GCM,data:zk0ivb9asZLHUg819tR8GV5R5ViSiJId0T1o2GqYWjc/AcNK1pBKwIJ+S3TuB0fLz3qaFXUBfzcc+CfztuTLcRzAfu3mF8Hv0boZTe0lGl5XugmozQWjoLjSw+roWZHlXd5CfYbDZsHSO70231NyPlpuUkVr2fqAFZgTEev2A9Y=,iv:IkRYK6PLE74i+Kq5fop4ddS+Q9KW8uNVfABK81uFyIw=,tag:kanZfzBXEYodnQtwWdZ85g==,type:str]
|
||||
lastmodified: "2026-09-01T02:00:01Z"
|
||||
mac: ENC[AES256_GCM,data:VdjLTJSa4ZfWLnola8F4u5q50a9Z6+h+Dbb9MKgGz3v+F2II+u29XrmXDS0Xpqx7QMjpzPdDp4gvyBecXxk5p+nzoC2lHSENvrZsloTQi18wEX04C53IXteoB6ijJ8VR6xC0GyKhXgIxo297dqfndSqgsYmQB5w2WKvzmcb+Ctg=,iv:9AWwKsGiJJMe8qpo534/cZT/2bW4DXeiRhzneSPF24s=,tag:n6bSrwV+9WVMzBsh8ou7OA==,type:str]
|
||||
version: 3.13.3
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
# Taegliche Pruefung: Gibt es Wiki-Nutzer ohne Gruppe? (management #0103)
|
||||
#
|
||||
# Der Fall, den dieses Issue ausgeloest hat: @apo meldete sich erfolgreich an, landete in
|
||||
# keiner Gruppe und sah deshalb nichts - ohne Fehlermeldung, ohne Logzeile, ohne Zaehler.
|
||||
# Aufgefallen ist es erst, weil er sich beschwerte. Das Auto-Enrollment verhindert den Fall
|
||||
# jetzt; diese Pruefung stellt sicher, dass ein erneutes Versagen NICHT wieder auf eine
|
||||
# Beschwerde angewiesen ist (Abnahmekriterium des Issues).
|
||||
#
|
||||
# Bewusst eine taegliche Stichprobe auf der Datenbank statt einer Instrumentierung des
|
||||
# Logins: Wiki.js meldet den Zustand "angemeldet, aber ohne Gruppe" nirgends, und ein
|
||||
# Patch am Anmeldeweg waere fuer diesen Zweck unverhaeltnismaessig. Ein Tag Verzug ist
|
||||
# hinnehmbar - der Fall lag vorher Tage unbemerkt.
|
||||
#
|
||||
# Die Ausgabe geht nach stdout und damit ueber Alloy nach Loki. Suchbegriff fuer eine
|
||||
# Abfrage oder ein Panel: "Wiki-Nutzer ohne Gruppe".
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: wikijs-gruppenpruefung
|
||||
namespace: matrix
|
||||
spec:
|
||||
schedule: "15 6 * * *"
|
||||
concurrencyPolicy: Forbid
|
||||
successfulJobsHistoryLimit: 3
|
||||
failedJobsHistoryLimit: 3
|
||||
jobTemplate:
|
||||
spec:
|
||||
backoffLimit: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
# Diese Marke steht auch in networkpolicy.yaml - ohne sie kaeme der Pod nicht
|
||||
# an die Datenbank, und zwar lautlos.
|
||||
app.kubernetes.io/name: wikijs-gruppenpruefung
|
||||
app.kubernetes.io/component: pruefung
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: pruefung
|
||||
image: postgres:15-alpine
|
||||
env:
|
||||
- name: PGHOST
|
||||
value: wikijs-postgres
|
||||
- name: PGDATABASE
|
||||
value: wiki
|
||||
- name: PGUSER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: wikijs-postgres-secret
|
||||
key: username
|
||||
- name: PGPASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: wikijs-postgres-secret
|
||||
key: password
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
set -eu
|
||||
|
||||
# Warten, bis die Datenbank erreichbar ist - und zwar wirklich warten:
|
||||
# Die NetworkPolicy-Regeln fuer einen NEU erzeugten Pod sind beim Start
|
||||
# des Containers noch nicht programmiert. In diesem Fenster wird die
|
||||
# Verbindung abgewiesen ("Connection refused"), obwohl Ziel, Marken und
|
||||
# Regel korrekt sind. Gemessen am 2026-08-20: sofort nach dem Start
|
||||
# abgewiesen, nach 20 Sekunden erfolgreich.
|
||||
#
|
||||
# Ohne diese Schleife scheitert der Job jede Nacht am Wettlauf und meldet
|
||||
# einen Fehler, den niemand nachvollziehen kann - oder schlimmer: Man
|
||||
# gewoehnt sich an den roten Job.
|
||||
bereit=""
|
||||
for versuch in 1 2 3 4 5 6 7 8 9 10 11 12; do
|
||||
if psql -Atc 'select 1;' >/dev/null 2>&1; then bereit="ja"; break; fi
|
||||
sleep 5
|
||||
done
|
||||
if [ -z "$bereit" ]; then
|
||||
echo "FEHLER: Datenbank nach 60 s nicht erreichbar - Pruefung nicht gelaufen."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Gegenprobe: Ohne sie sieht "0 Nutzer ohne Gruppe" bei einer leeren oder
|
||||
# falschen Datenbank genauso aus wie ein sauberes Ergebnis - stiller
|
||||
# Erfolg ist hier das groessere Risiko als ein Fund.
|
||||
gesamt="$(psql -Atc 'select count(*) from users;')"
|
||||
if [ -z "$gesamt" ] || [ "$gesamt" -eq 0 ]; then
|
||||
echo "FEHLER: keine Nutzer gelesen - Datenbank leer oder falsche Datenbank."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ohne="$(psql -Atc '
|
||||
select count(*) from users u
|
||||
left join "userGroups" ug on ug."userId" = u.id
|
||||
where ug."userId" is null;')"
|
||||
|
||||
if [ "$ohne" -gt 0 ]; then
|
||||
echo "WARNUNG: $ohne von $gesamt Wiki-Nutzer ohne Gruppe - sie sehen nach der Anmeldung nichts (management #0103)"
|
||||
# -At mit Vorgabe-Trenner "|" statt eines eingebetteten E'\t':
|
||||
# Die Anfuehrungszeichen-Verschachtelung waere die wahrscheinlichste
|
||||
# Fehlerquelle in dieser Datei und traegt nichts zur Aussage bei.
|
||||
psql -Atc '
|
||||
select u.id, u.email from users u
|
||||
left join "userGroups" ug on ug."userId" = u.id
|
||||
where ug."userId" is null order by u.id;' \
|
||||
| while IFS= read -r z; do echo " betroffen: $z"; done
|
||||
echo "Behebung: Auto-Enrollment der OIDC-Strategy pruefen (wikijs-config.py, Gruppe wiki-anwender)."
|
||||
else
|
||||
echo "OK: kein Wiki-Nutzer ohne Gruppe ($gesamt geprueft)"
|
||||
fi
|
||||
resources:
|
||||
requests:
|
||||
memory: 32Mi
|
||||
cpu: 10m
|
||||
limits:
|
||||
memory: 96Mi
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user