AUD-08: REST-Ingest-Endpunkt für Audit-Ereignisse externer Module #2
Closed
patrick
wants to merge 0 commits from
feature/aud-08-audit-ingest-endpoint-fuer-externe-module into main
pull from: feature/aud-08-audit-ingest-endpoint-fuer-externe-module
merge into: scripte:main
scripte:main
scripte:fix/ai-app-write-timeout
scripte:chore/migration-kanban-board
scripte:chore/connect-kanban-board
scripte:chore/workflow-kanban-board
scripte:chore/archive-kanban-board
scripte:chore/mail-kanban-board
scripte:chore/dms-kanban-board
scripte:chore/core-kanban-board
scripte:chore/ai-kanban-board
scripte:chore/ai-kanban-security-folgetickets
scripte:feat/ai-app-deployment
scripte:feat/ai-app-vorschlaege
scripte:feat/ai-cls09-herkunftsmarkierung
scripte:feat/ai-eng09-gemini
scripte:feat/ai-cls08-mehrsprachig
scripte:feat/ai-titel-korrespondent
scripte:feat/dms-backup-s3
scripte:feat/dms-s3-verdrahten
scripte:docs/alfresco-vergleich
scripte:refactor/common-batch-2
scripte:refactor/common-modul-pilot
scripte:fix/dms-httpharden-normalize
scripte:feature/shl-02-pagecontainer-dedup
scripte:test/dedup-fakekek-testutil
scripte:feature/nav-01-mail-postfach-registry-eintrag
scripte:feature/sec-08-09-rbac-cmis-audit
scripte:feature/shl-04-globale-modul-navigation
scripte:chore/mail-kanban-security-folgetickets
scripte:chore/dms-kanban-security-folgetickets
scripte:chore/core-kanban-security-folgetickets
scripte:chore/archive-kanban-security-folgetickets
scripte:chore/workflow-kanban-security-folgetickets
scripte:chore/connect-kanban-security-folgetickets
scripte:feature/qa-08-barrierefreiheits-audit-workflow
scripte:feature/tsk-04-aufgaben-oberflaeche-workflow
scripte:feature/qa-08-barrierefreiheits-audit-connect
scripte:feature/int-18-smtp-gateway-weiterleitung-an-feste-adresse-dms
scripte:docs/rollout-131-vorbereitung
scripte:feature/met-08-facturx-zugferd-szenarien-validator-dms
scripte:feature/qa-15-sicherheitspruefung-gesamtprojekt
scripte:fix/testrun-2026-09-21-dms
scripte:feature/cmp-07-dsgvo-loeschantrag-recht-auf-loeschung
scripte:feature/cmp-06-vier-augen-freigabe-fuer-loeschungen
scripte:feature/cmp-02-dsgvo-datenschutz-berichte
scripte:feature/ret-03-loeschworkflow-und-aufbewahrungssperre-legal-hold
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Kontext
Core exponierte bislang nur GET /audit/export (AUD-03). Physisch getrennte Module (Archive, DMS, Mail) hatten keinen Weg, eigene sicherheitsrelevante Vorgänge im zentralen Audit-Log zu vermerken. Notwendige Grundlage für Archive CMP-04.
Prüfungen
Alle Tests real gegen Postgres auf 192.168.1.131 verifiziert. go vet sauber.
Registry-DB (nur Tenant-Metadaten), Provisioning-Routine legt pro Mandant eine physisch isolierte Postgres-DB an und registriert sie transaktional (Rollback der DB bei fehlgeschlagener Registrierung). Schlanker HTTP-Handler als Schnittstellen-Vorbereitung fuer API-01/TEN-02, kein eigenes REST-Grundgerüst. Pruefungen: 1. Migration up/down geschrieben (0001_tenant_registry.{up,down}.sql) — nicht gegen echte DB ausgefuehrt, da auf dieser Maschine kein Go/Postgres-Test- Setup verfuegbar ist. Offen zur Ausfuehrung. 2. Integrationstest TestProvision_CreatesIsolatedDatabases geschrieben (zwei Mandanten, prueft unterschiedliche db_name und current_database()) — ebenfalls nicht ausgefuehrt, guarded per TEST_ADMIN_DSN env var. Offen. 3. Slug-Validierung (unit test TestValidateSlug) deckt SQL-Injection-Versuch im Datenbanknamen ab — ebenfalls nicht lokal ausgefuehrt, da kein Go Compiler auf dieser Maschine vorhanden ist. Offen. Alle drei Pruefungen sind vorbereitet, aber NICHT durchgefuehrt worden — zaehlen laut Vorgabe als offen bis auf einer Maschine mit Go+Postgres verifiziert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>internal/apiserver: Server.Handle(version, pattern, h) registriert Routen unter /api/{version}/... (Akzeptanzkriterium 1) — verschiedene Versionen sind unabhaengige Pfade im ServeMux, eine neue Version beeintraechtigt bestehende nicht. HandleV1 ist die Kurzform fuer die aktuelle Hauptversion. Einheitliches Fehlerschema {"error":{"code","message"}} ueber WriteError (Akzeptanzkriterium 2) — bewusst NICHT auth.RequireAuth aus IAM-02 wiederverwendet, da dessen Klartext-Fehlerantworten nicht zum einheitlichen JSON-Schema passen wuerden; stattdessen authAndTenantContext nutzt auth.TokenIssuer.Verify direkt (dieselbe Kryptographie, keine Duplikation) und antwortet im API-01-Schema, auch bei 401. authAndTenantContext ist die Middleware, die JEDEM ueber Handle registrierten Endpunkt Tenant-/Benutzerkontext bereitstellt (Akzeptanzkriterium 3, ueber apiserver.FromContext abrufbar) — kein Handler prueft Auth selbst. loggingMiddleware protokolliert jede Anfrage strukturiert. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Fehlerhafte Anfrage liefert ueber mehrere Endpunkte hinweg dasselbe Fehlerschema (Stichprobe) — TestErrorFormat_ConsistentAcrossEndpoints: zwei unabhaengige Endpunkte, beide liefern 401 im identischen {"error":{"code","message"}}-Schema. PASS. 2. Middleware-Kette nachweislich von jedem Endpunkt durchlaufen — TestMiddleware_SetsRequestContextForEveryEndpoint: zwei Endpunkte lesen RequestContext, beide erhalten korrekten UserID/TenantSlug aus dem Token. PASS. 3. Versionswechsel (fiktive v2-Route) ohne v1 zu beeintraechtigen — TestVersioning_V2DoesNotAffectV1: v1 vor und nach Anlage von v2 liefert unveraendert dieselbe Antwort. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>internal/ldapsync: Client ist eine Schnittstelle (Search() liefert Entry- Liste) — in dieser Umgebung steht kein echter LDAP/AD-Server zur Verfuegung (analog AUD-05/Archive), daher ist die Sync-/Rollenzuordnungs-Logik vollstaendig gegen einen Fake getestet, eine echte Verbindungspruefung gegen LDAP/AD steht noch aus. RoleMappingStore (Akzeptanzkriterium 3): explizite Erlaubnis-Matrix LDAP-Gruppe -> Rolle. HighestRoleFor vergibt NUR fuer explizit gemappte Gruppen eine Rolle — eine unbekannte Gruppe, auch wenn ihr Name zufaellig wie eine interne Rolle aussieht ("tenant_admin", "superadmin"), traegt strukturell nichts bei (kein Code-Pfad, der eine ungemappte Gruppe je einer Rolle zuordnet) — behebt die aus archivmail bekannte Privilege-Escalation- Fehlerklasse von Grund auf statt nachtraeglich zu haerten. Syncer.Run ruft Search() als ALLERERSTES auf; schlaegt es fehl, wird ohne jede Aenderung an bestehenden Konten abgebrochen (Akzeptanzkriterium 2). Pro Eintrag isolierte Fehler landen in SyncResult.Failed, ohne andere Eintraege zu beeintraechtigen. Deaktivierung in LDAP wird als users.Deactivate uebernommen (Akzeptanzkriterium 3). ldap_config speichert bewusst nur den NAMEN einer Umgebungsvariable fuer das Bind-Passwort, nie das Passwort selbst. internal/rbac (RBAC-01) wurde 1:1 aus dem rbac-01-Branch uebernommen (git show aus derselben Repo-Historie) — IAM-05 haengt an RBAC-01 fuer die Rollenzuweisung, beide Boards leben aber auf getrennten, noch nicht gemergten Feature-Branches ohne gemeinsame Historie. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Rollen-Erlaubnis-Matrix mit Positiv- und Negativfaellen automatisiert getestet (keine Privilege-Escalation) — TestRoleMapping_PositiveAndNegativeCases: gemappte Gruppe liefert Rolle, unbekannte Gruppe und rollen-aehnlich benannte, aber nie gemappte Gruppen liefern keine. PASS. 2. Synchronisationslauf mit fehlerhafter/nicht erreichbarer LDAP-Quelle bricht kontrolliert ab, ohne bestehende Konten zu beschaedigen — TestSyncer_AbortsCleanlyOnSourceError: bestehendes Konto bleibt nach fehlgeschlagenem Lauf unveraendert aktiv. PASS. 3. Deaktivierung eines Benutzers in LDAP wird bei naechster Synchronisation korrekt uebernommen — TestSyncer_AppliesDeactivationOnNextRun: erster Lauf legt aktiven Benutzer an, zweiter Lauf mit Disabled=true setzt ihn auf inaktiv. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>internal/channels: konkrete Zustellkanaele fuer CFG-02s Dispatcher. TemplateStore.Resolve loest Vorlagen pro Tenant auf und faellt auf GlobalTemplateScope zurueck, wenn ein Tenant keine eigene gesetzt hat (Akzeptanzkriterium 3). Render nutzt text/template mit Option("missingkey=error") — ein fehlender Platzhalter bricht das Rendering MIT FEHLER ab, statt eine unvollstaendige Nachricht zu erzeugen (Akzeptanzkriterium 1). EmailSender implementiert notify.Sender: rendert ZUERST die Vorlage, bevor ueberhaupt eine SMTP-Verbindung aufgebaut wird — schlaegt das Rendering fehl, wird nie ein Netzwerkzugriff versucht. Ein anschliessend fehl- schlagender SMTP-Versand liefert einen Fehler, den CFG-02s bereits getestete Wiederholungslogik verarbeitet (kein zweiter Retry-Mechanismus hier). InAppSender persistiert In-App-Nachrichten ueber InAppStore (Akzeptanzkriterium 2, ueber API abrufbar/als gelesen markierbar). Router waehlt den Kanal anhand Notification.Channel — ein neuer Kanal wird per Register() ergaenzt, ohne Dispatcher oder Router umzubauen. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Vorlagenrendering mit fehlenden Platzhaltern bricht kontrolliert ab — TestRender_MissingPlaceholderAborts und TestEmailSender_AbortsBeforeSMTPWhenTemplateMissing (Fehler kommt von der Vorlagenaufloesung, kein SMTP-Verbindungsversuch). PASS. 2. In-App-Benachrichtigung nach Markierung als gelesen korrekt gefuehrt — TestInAppStore_MarkReadIsReflectedCorrectly. PASS. 3. E-Mail-Versand bei nicht erreichbarem SMTP-Server loest dokumentiertes Retry-Verhalten ueber CFG-02 aus — TestEmailSender_TriggersDispatcherRetryOnUnreachableSMTP: echter EmailSender gegen unerreichbaren Host, ueber notify.Dispatcher eingereiht, nach ausgeschoepften Wiederholungen status=failed mit korrekter Versuchszahl. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>Zwei kleine Nacharbeiten aus dem Testlauf auf 131: - ConfigStore.Save nutzte einen Multi-Statement-Exec (DELETE; INSERT), den pgx im extended protocol ablehnt ("cannot insert multiple commands into a prepared statement") -- jetzt als Transaktion mit zwei Exec-Aufrufen. - Das Test-Setup fuer die tenants-Tabelle in config_store_test.go war schlanker als das in internal/kek/kek_test.go verwendete Vollschema (fehlende Spalten previous_status/deletion_scheduled_at/ retention_block_reason/retention_checked_at). Da beide Pakete im Gesamttestlauf dieselbe Test-Datenbank per CREATE TABLE IF NOT EXISTS teilen, hat das schlankere Schema (wenn zuerst angelegt) den kek-Test TestTenantKEKHandler_AllowsActiveModuleForTenant zum Fehlschlagen gebracht. Jetzt identisches Vollschema wie kek_test.go. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPsyieCYwvbUBsnN598SbPAUD-02 hat bereits einen Trigger, der UPDATE/DELETE auf audit_events per RAISE EXCEPTION ablehnt (Akzeptanzkriterium 3 aus AUD-07 war damit schon erfuellt). Zwei Luecken blieben: (1) die Anwendungsrolle hatte weiterhin UPDATE/DELETE-Rechte auf DB-Ebene, der Schutz war nur ein Code-Pfad (Trigger), keine fehlenden Rechte; (2) ein abgelehnter Aenderungsversuch wurde nirgends selbst protokolliert. - migrations/0010_audit_events_role_enforcement.{up,down}.sql: Gruppenrolle nexarch_app ohne LOGIN/Passwort, REVOKE ALL + GRANT INSERT, SELECT auf audit_events fuer PUBLIC/nexarch_app; Trigger-Funktion um RAISE WARNING mit Akteur/Session/Client-Kontext ergaenzt, bevor der Versuch weiterhin per RAISE EXCEPTION abgelehnt wird (RAISE WARNING ist nicht transaktional, landet also auch bei Rollback im Postgres-Server-Log). - internal/audit/append_only_test.go: Tests je Akzeptanzkriterium - TestAppendOnly_AppRoleHasNoUpdateOrDeletePrivilege (AC1/Pruefung1, erwartet "permission denied" statt Trigger-Fehler), TestAppendOnly_TamperAttemptIsLogged (AC2/Pruefung2, faengt die RAISE-WARNING-Notice ueber pgx OnNotice ab), TestAppendOnly_NormalWriteAndReadStillWork (AC3/Pruefung3, Regression). Pruefungen auf 192.168.1.131 durchgefuehrt: 1. UPDATE/DELETE mit Anwendungsrolle (Mitglied nexarch_app) -> "permission denied for table audit_events" statt Trigger-Fehler: PASS. 2. Ablehnungsversuch selbst protokolliert (RAISE WARNING mit Akteur/Session/Zeitpunkt, unabhaengig von Transaktions-Rollback) via pgx-Notice-Capture nachgewiesen: PASS. 3. Regression Schreiben/Lesen (Record + CountByTenant, mehrere Eintraege): PASS. Gesamtlauf go test ./... -p 1 -count=1: internal/audit vollstaendig gruen (alle 22 Tests inkl. der 3 neuen). Bestehende, dokumentierte Altlasten unveraendert beobachtet (nicht Bestandteil dieser Kachel): internal/migrate (TestRolloutAll_* reihenfolgeabhaengig), internal/notify (TestDispatcher_EnqueueAndProcess flakt), internal/pentest (TestPentest_KEK_CompromisedMasterKeyCannotDecryptAfterRotation), internal/user (verwaiste test_iam01_users/test_iam01_superadmins-DBs). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPsyieCYwvbUBsnN598SbPPull request closed