Files
mission-control-v2/backend/kern/schwaerzen.py
T
HitonabiandClaude Opus 5.5 75c6037431 Später · 20 Konfig-Sicherung ins private Gitea-Repo, Geheimnisse geschwärzt
- kern/schwaerzen.py + dieselben Regeln im Ausführer (Test vergleicht beide): private Schlüssel, Werte hinter
  password/secret/token/key…, Passwort-Hashes, htpasswd, Zugangsdaten in Adressen, Authorization-Kopfzeilen,
  Token-Formate, lange Hex-Ketten
- Ausführer: sammelt Proxmox (Gäste, Speicher, Aufträge, Netz), AdGuard, NPMplus (ohne tls/), schwärzt auf dem Host,
  sendet alle 6 h und auf Abruf (Aktion konfig_senden) an POST /api/homelab/ausfuehrer/konfig
- Homelab-Teil: GET /api/homelab/konfig (frisch=1), dazu Compose und .env der Arcane-Projekte, geschwärzt
- KI-Box: services/konfig_sicherung.py legt das Repo beim ersten Lauf privat an, Arbeitskopie je Lauf auf Gitea-Stand,
  Commit nur bei Änderungen; Timer täglich 02:30, Knopf „Jetzt sichern“ unter Einstellungen → Homelab
- Totmannschalter und Wächter kennen den Timer; Doku

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 14:23:19 +02:00

44 lines
2.4 KiB
Python

"""Geheimnisse schwärzen (Ausbauplan „Später“, Punkt 20, seit 26.09.2026) — für die Konfig-Sicherung in Gitea.
User-Entscheid 25.09.2026: privates Repo, Geheimnisse geschwärzt. Geschwärzt wird, was nach Zugangsdaten aussieht:
• private Schlüssel (PEM-Blöcke)
• Werte hinter Schlüsseln wie password, secret, token, api_key, key, salt, cookie, credential (YAML, INI, .env,
„- NAME=wert“ in Compose)
• Passwort-Hashes (bcrypt, argon2, sha-crypt, apr1) und htpasswd-Zeilen
• Zugangsdaten in Adressen (scheme://benutzer:passwort@…), Authorization-/API-Kopfzeilen in nginx
• typische Token-Formate (sk-…, hf_…, gh?_…) und lange Hex-Ketten (≥ 32 Zeichen, etwa API-Schlüssel)
Lieber zu viel als zu wenig: Beim Zurückholen trägt der User Geheimnisse ohnehin selbst wieder ein.
Dieselben Regeln stehen im Ausführer (deploy/homelab/ausfuehrer.py, SCHWAERZEN), damit Geheimnisse den Proxmox-Host gar
nicht erst verlassen; ein Test prüft, dass beide gleich schwärzen.
"""
import re
ERSATZ = "«geschwärzt»"
_WORT = (r"(?:pass(?:word|wd|phrase)?|secret|token|api[_-]?key|apikey|private[_-]?key|credential|salt|cookie|"
r"auth[_-]?key|access[_-]?key|client[_-]?secret|jwt|key(?![a-z]))")
REGELN: tuple[tuple[re.Pattern, str], ...] = (
(re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----.*?-----END [A-Z ]*PRIVATE KEY-----", re.DOTALL),
f"-----BEGIN PRIVATE KEY-----\n{ERSATZ}\n-----END PRIVATE KEY-----"),
(re.compile(rf"(?im)^([ \t]*(?:-[ \t]*)?[\"']?[\w.-]*{_WORT}[\w.-]*[\"']?[ \t]*[:=][ \t]*)(?![ \t]*$)(?!{ERSATZ})(.+)$"),
rf"\g<1>{ERSATZ}"),
(re.compile(r"(?im)^(\s*proxy_set_header\s+(?:authorization|x-api-key|x-auth-token|cookie)\s+)(\"[^\"]*\"|\S+?)(;?)$"),
rf'\g<1>"{ERSATZ}"\g<3>'),
(re.compile(r"\$(?:2[abxy]|argon2(?:id|i|d)|apr1|[156])\$[^\s\"',;]+"), ERSATZ),
(re.compile(r"(?m)^([\w.@-]+):(?:\{SHA\}|\$)\S+$"), rf"\g<1>:{ERSATZ}"),
(re.compile(r"(\b[a-z][a-z0-9+.-]*://[^\s:/@]+:)[^\s@/]+@"), rf"\g<1>{ERSATZ}@"),
(re.compile(r"(?i)\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]{8,}"), rf"\g<1> {ERSATZ}"),
(re.compile(r"\b(?:sk-|hf_|gh[pousr]_)[A-Za-z0-9_-]{16,}"), ERSATZ),
(re.compile(r"\b[0-9a-fA-F]{32,}\b"), ERSATZ),
)
def schwaerzen(text: str) -> tuple[str, int]:
"""(geschwärzter Text, Zahl der Stellen)."""
zahl = 0
for muster, ersatz in REGELN:
text, n = muster.subn(ersatz, text)
zahl += n
return text, zahl