Commit Graph
10 Commits
Author SHA1 Message Date
sysops 53af28d928 Merge branch 'feature/ops-05-alerting-bei-schwellwert-ueberschreitung' into feature/qa-05-abnahme-compliance-pruefung-core
# Conflicts:
#	DEVLOG.md
#	go.mod
#	go.sum
#	scripts/reset-test-env.sh
#	scripts/run-checks.sh
2026-08-29 17:06:55 +02:00
sysops d1977e6c2e fix: verbleibende go.sum-Merge-Marker aus vorherigen Merges bereinigt 2026-08-29 10:03:00 +02:00
sysops 93a49dd72c Merge branch 'feature/aud-04-audit-log-ansicht' into feature/qa-09-barrierefreiheits-audit
# Conflicts:
#	DEVLOG.md
#	go.mod
#	go.sum
#	scripts/reset-test-env.sh
2026-08-29 10:00:28 +02:00
sysops 8bd5bb5a52 Merge branch 'feature/lic-04-lizenz-modul-verwaltungsoberflaeche' into feature/qa-09-barrierefreiheits-audit
# Conflicts:
#	DEVLOG.md
#	go.mod
#	go.sum
#	internal/flag/flag.go
#	scripts/reset-test-env.sh
2026-08-29 09:59:50 +02:00
sysops 5fcae51aac OPS-05: fix — go.mod auf go 1.25.0 (prometheus/client_golang benoetigt es), sql-typfehler in shouldFire (interval-multiplikation statt string-konkatenation) 2026-08-29 00:02:11 +02:00
sysopsandClaude Sonnet 5 b12d53f469 AUD-01: zentrales-audit-log-modell
internal/audit: eigenes, strukturiertes Audit-Datenmodell (Akteur, Aktion,
Zielobjekt, Zeitpunkt, Tenant) in der Registry-DB, getrennt von jedem
allgemeinen Anwendungs-Log (eigenes Paket, eigene Tabelle audit_events,
kein Logging-Framework). Log.Record ist der EINE zentrale Schreibpfad —
es gibt keine zweite Schreibmoeglichkeit, ueber die ein Handler die
Validierung umgehen koennte.

Fehlender Tenant-Bezug wird zweifach verhindert (Akzeptanzkriterium 2):
Log.Record weist leeren TenantSlug direkt ab (ErrMissingTenant), zusaetzlich
erzwingt eine CHECK-Constraint in der Migration dasselbe auf Datenbankebene,
selbst wenn Log.Record umgangen wuerde. Mandantenuebergreifende Ereignisse
(z.B. Superadmin-Aktionen) nutzen den reservierten Wert audit.SystemTenant
statt NULL oder leerem String — es gibt keinen Weg, ganz ohne Tenant-Bezug
zu schreiben.

Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. Automatisierter Test belegt genau einen Audit-Eintrag pro
   sicherheitsrelevantem Vorgang — TestRecord_PersistsExactlyOneEventPerSecurityIncident
   (simulierter fehlgeschlagener Login), Feldinhalte verifiziert. PASS.
2. Fehlender Tenant-Bezug durch Constraint/Test verhindert —
   TestRecord_RejectsMissingTenant (App-Ebene) UND
   TestConstraint_RejectsMissingTenantAtDatabaseLevel (direkter INSERT unter
   Umgehung von Log.Record, durch CHECK-Constraint abgewiesen). PASS.
3. Datenmodell von zweiter Person gegen Dokumentation geprueft — NICHT
   durchgefuehrt (keine zweite Person in dieser Session verfuegbar). Offen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 19:22:50 +02:00
sysopsandClaude Sonnet 5 4a30345e07 LIC-02: feature-flag-service-je-tenant
internal/flag: Store (Verwaltung) + Service (Auswertung mit TTL-Cache,
Default 5s) — Unleash-Prinzip Flag-Verwaltung vs. Flag-Auswertung getrennt,
als Kernfunktion des Core-Dienstes selbst statt separater Infrastruktur.

evaluate() wendet drei Strategien in fester Reihenfolge an: global an/aus,
Tenant-Zielgruppe, deterministischer Prozentsatz-Rollout (FNV-Hash aus
Tenant+Key, stabil pro Tenant). IsEnabled liefert IMMER nur bool (kein
Fehlerwert) — ein nicht erreichbarer Flag-Dienst kann damit keinen
Aufrufer zum Absturz bringen: bei DB-Fehler wird der zuletzt bekannte
Cache-Stand verwendet, ohne jeglichen Stand faellt der Dienst sicher auf
false zurueck. Service.Invalidate erzwingt sofortiges Neuladen fuer den
Schreiber selbst, andere Instanzen sehen Aenderungen spaetestens nach der
TTL (Akzeptanzkriterium 3, kein Neustart noetig).

Bugfix waehrend Tests: Store.Set uebergab ein nil-TargetTenantSlugs-Slice
als SQL NULL statt leerem Array (NOT-NULL-Verletzung) — auf leeres Slice
normalisiert.

Akzeptanzkriterium 4 (Deaktivierung loescht keine Daten): dieses Paket
besitzt ausschliesslich die eigene feature_flags-Zeile, hat keinerlei
Code-Pfad, der Modul-Geschaeftsdaten anfassen koennte — Loeschung bleibt
strukturell der Archive-Retention-Engine vorbehalten.

Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. Cache-Invalidierungszeit automatisiert gemessen —
   TestService_CacheInvalidationTiming: Aenderung wirksam nach 153ms bei
   TTL=150ms (innerhalb Ziel+Toleranz), vorher nachweislich noch alter Stand. PASS.
2. Zielgruppen-Strategie liefert erwartete Auswertung —
   TestService_TargetTenantStrategy / TestEvaluate_TargetTenantStrategy. PASS.
3. Ausfall des Flag-Dienstes fuehrt zu dokumentiertem Fallback, kein Absturz —
   TestService_FallsBackOnStoreFailure (mit recover()-Absicherung): Fallback
   auf Cache-Stand bzw. sicheres false bei komplett unerreichbarer DB, geloggt. PASS.
4. Modul-Deaktivierung/Reaktivierung ohne Datenverlust — architektonisch durch
   fehlenden Code-Pfad sichergestellt (siehe oben), zusaetzlich durch
   TestService_InvalidateForcesImmediateRefresh (Toggle aus/an bleibt
   konsistent nachvollziehbar) mitabgedeckt. PASS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 19:19:59 +02:00
sysopsandClaude Sonnet 5 d63db93a4c LIC-01: lizenzmodell-lizenzschluessel-pruefung
internal/license: Ed25519-signierte Lizenzschluessel (stdlib crypto/ed25519,
keine neue Abhaengigkeit). Issuer haelt den privaten Schluessel (lebt beim
Lizenzgeber), Validator nur den oeffentlichen (lebt im Core-Prozess) — klare
Trennung Ausstellung/Pruefung nach Unleash-Vorbild (Flag-Verwaltung vs.
Flag-Auswertung).

Store.Install prueft NUR die Signatur und persistiert den Lizenzumfang
(Plan, Modul-Liste, Laufzeit) in tenant_licenses (Registry-DB, 1:1 zu
tenants). Eine bereits abgelaufene, aber korrekt signierte Lizenz laesst
sich trotzdem einspielen — der Ablauf wird erst bei Store.RequireActive
bewertet (liefert ErrLicenseExpired statt Panic/Absturz), waehrend
Store.Status den Umfang unabhaengig vom Ablauf weiterhin liefert.

Neu: scripts/run-checks.sh buendelt reset-test-env.sh + go build/vet/test
(-p 1) zu einem Ein-Kommando-Check fuer den Testhost.

Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. Manipulierter Lizenzschluessel zuverlaessig erkannt —
   TestParse_RejectsTamperedKey, TestParse_RejectsWrongKeyPair,
   TestStore_InstallRejectsInvalidSignature. PASS.
2. Ablauf loest definierten eingeschraenkten Zustand aus, kein harter
   Systemausfall — TestStore_RequireActive_DetectsExpiry (inkl. recover()-
   Absicherung im Test, dass kein Panic auftritt), ErrLicenseExpired statt
   Absturz; Status bleibt trotzdem abfragbar. PASS.
3. Signaturpruefung von zweiter Person gegen Dokumentation nachvollzogen —
   NICHT durchgefuehrt (keine zweite Person in dieser Session verfuegbar).
   Offen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 19:12:13 +02:00
sysopsandClaude Sonnet 5 3d20d86a4f IAM-02: login-session-jwt-grundgeruest
internal/auth: Login/Logout ueber httpOnly/Secure/SameSite=Strict-Cookie mit
HS256-JWT (30min TTL), bcrypt-Passwort-Hashing (Cost 12, explizit begruendet
und benchmarkt statt DefaultCost uebernommen), RequireAuth-Middleware fuer
geschuetzte Routen. LoginService ist strukturell auf einen Tenant gescopt
(nutzt user.TenantUserStore, dessen Pool = eine Tenant-DB — derselbe
Mechanismus wie in TEN-01/TEN-02), liefert bei falscher E-Mail und falschem
Passwort denselben Fehler (User-Enumeration-Schutz) inkl. Dummy-bcrypt-
Vergleich gegen Timing-Seitenkanal bei unbekannter E-Mail.

user.TenantUserStore erweitert um SetPasswordHash/GetByEmailForAuth
(password_hash bleibt ausserhalb des regulaeren User-Typs/JSON-Pfads).
Migration 0002 fuegt password_hash-Spalte hinzu (Default '', da IAM-01
User ohne Passwort anlegt).

Login-Handler ist wie IAM-01/TEN-02 aus denselben Gruenden (Tenant-
Connection-Routing = TEN-06, noch nicht gebaut) nicht in cmd/core/main.go
verdrahtet — Package ist eigenstaendig nutzbar/getestet.

Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. Login-Query tenant-gescopt — TestLoginService_NoCrossTenantLogin: gleiche
   E-Mail in zwei Tenant-DBs mit unterschiedlichem Passwort, Login gegen
   Tenant A mit Tenant-B-Passwort schlaegt fehl. PASS.
2. Session-Fixation/Token-Manipulation — TestTokenVerify_RejectsManipulatedPayload
   und TestTokenVerify_RejectsWrongSecret: manipuliertes/falsch signiertes
   Token wird abgelehnt. PASS.
3. Abgelaufenes Token erzwingt Neuanmeldung — TestTokenVerify_RejectsExpiredToken
   und TestRequireAuth_BlocksWithoutValidCookie. PASS.
4. Login-Latenz mit Kostenfaktor 12 gemessen: 294ms (Ziel < 400ms) —
   TestBcryptCostAgainstLatencyTarget. PASS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 18:26:00 +02:00
sysopsandClaude Sonnet 5 e4793303fc IAM-01: benutzer-datenmodell-crud
Benutzer-Datenmodell + CRUD fuer Tenant-User (tenant-scoped, keine
tenant_id-Spalte noetig, Tenant ergibt sich aus der DB-Verbindung, Modell C)
und getrennt dafuer SuperadminStore fuer mandantenuebergreifende Konten in
der Registry-DB — First-Class-Typ statt tenant_id-NULL-Sonderfall im
Tenant-User-Code (bekannter archivdms-Fehler vermieden).

E-Mail-Eindeutigkeit: tenant-scoped fuer normale Benutzer (UNIQUE-Constraint
gilt nur innerhalb der jeweiligen Tenant-DB), global fuer Superadmins
(eine Registry-DB, ein UNIQUE-Constraint).

Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. CRUD automatisiert getestet inkl. Negativfaellen — TestTenantUserStore_CRUD
   deckt doppelte E-Mail (ErrEmailTaken) und unbekannte ID ab. PASS.
2. Superadmin-Anlage ohne Tenant-Kontext — TestSuperadminStore_CreateWithoutTenantContext:
   SuperadminStore.Create hat syntaktisch keinen Tenant-Parameter, kein
   if-Zweig fuer "kein Tenant" im Code. PASS.
3. Datenmodell von zweiter Person gegen Dokumentation geprueft — NICHT
   durchgefuehrt (keine zweite Person in dieser Session verfuegbar). Offen.

Tenant-User-Handler ist im Code vorhanden, aber in cmd/core/main.go noch
nicht geroutet — braucht Connection-Routing pro Mandant (TEN-06), das nicht
Teil dieser Kachel ist. Nur der Superadmin-Endpunkt ist verdrahtet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 17:57:49 +02:00