# QA-04 – Prüfprotokoll: Sicherheits- & Penetrationsprüfung Core Welle 6. Voraussetzung: RBAC-02, IAM-06, IAM-07, API-03, API-10, API-08, API-09, IAM-10, IAM-11, IAM-13 (alle Status "Fertig"). Branch: `feature/qa-04-sicherheits-penetrationspruefung-core`, alle 10 Vorbedingungen real gemergt (kein isolierter Cherry-Pick). ## 1. Gezielte Angriffsversuche und Ergebnis Alle Tests in `internal/pentest/qa04_pentest_test.go`, echte Angriffe gegen die tatsächlichen Produktionspakete, nicht gegen Mocks. ### 1.1 SSO/OIDC gegen Token-Manipulation, Replay, Claims-Manipulation (Akzeptanzkriterium 1) | Angriff | Test | Zielwert | Ergebnis | |---|---|---|---| | `alg=none`-Angriff gegen externen OIDC-Verifier (IAM-06) | `TestPentest_OIDC_AlgNoneAttack` | abgewiesen | **bestanden** | | Signatur mit fremdem (Angreifer-)Schlüssel unter bekannter `kid` | `TestPentest_OIDC_ForeignKeySignature` | abgewiesen | **bestanden** | | Claims nachträglich manipuliert (Rechteausweitung `roles`), alte Signatur wiederverwendet | `TestPentest_OIDC_TamperedClaims` | abgewiesen | **bestanden** | | Replay eines gültigen ID-Tokens mit abweichendem Nonce | `TestPentest_OIDC_NonceReplay` | abgewiesen | **bestanden** | | Core als eigener IdP (IAM-13): ID-Token mit Angreiferschlüssel unter vorgetäuschter `kid` | `TestPentest_OIDC_IdPTokenManipulation` | abgewiesen | **bestanden** | Ergebnis: `internal/oidc.Verifier` erzwingt Algorithmus (`RS256` bzw. `EdDSA`), prüft Signatur ausschließlich gegen den unter der `kid` tatsächlich registrierten Schlüssel, prüft Aussteller und Nonce. Kein Umgehungsweg gefunden. ### 1.2 Rate-Limiting/Account-Lockout im Mehrinstanz-Betrieb (Akzeptanzkriterium 2, Prüfung 1) | Angriff | Test | Zielwert | Ergebnis | |---|---|---|---| | Rate-Limit über 4 unabhängige "Instanzen" (eigene DB-Pools) parallel umgehen | `TestPentest_RateLimit_MultiInstanceBypassAttempt` | Gesamtsumme erlaubter Anfragen = konfiguriertes Limit, nicht mehr | **bestanden** – 200 Anfragen über 4 Instanzen, exakt 100 (=Limit) erlaubt | | Account-Lockout über 3 unabhängige Instanzen verteilte Brute-Force-Versuche umgehen | `TestPentest_Lockout_MultiInstanceBypassAttempt` | Konto wird trotz Verteilung gesperrt | **bestanden** – 12 Versuche über 3 Instanzen, Sperre nach Erreichen von `maxFailed=5` | Ergebnis: beide Mechanismen nutzen ein atomares SQL-`UPSERT` gegen geteilten, externen Zustand (Postgres) statt In-Process-Zähler – exakt der in archivdms als Fehlerklasse dokumentierte Schwachpunkt wird hier vermieden. ### 1.3 Zentrale Policy-Durchsetzung, gezielter Umgehungsversuch je Modul (Akzeptanzkriterium 3, Prüfung 3) | Angriff | Test | Zielwert | Ergebnis | |---|---|---|---| | Rechteausweitung: Rolle `user`/`tenant_admin` versucht `platform.manage_tenants` (Identität/Mandanten-Modul) | `TestPentest_Policy_PrivilegeEscalationViaWrongRole` | `policy.ErrDenied` | **bestanden** | | Strukturelle Umgehung von `policy.Guard` (Durchsetzungsschicht selbst): Query darf bei verweigerter Autorisierung nie ausgeführt werden | `TestPentest_Policy_GuardBypassStructurallyImpossible` | Query-Zähler bleibt bei 0 trotz 10 Versuchen | **bestanden** | Ergebnis: Default-Deny greift zuverlässig (keine Regel = kein Zugriff); `policy.Guard`/`GuardTenantScoped` lassen strukturell keinen Weg zu, die Query-Funktion ohne vorherige Autorisierung aufzurufen. ### 1.4 Master-Key- und Tenant-KEK-Verwaltung gegen Kompromittierung/Rotation (Akzeptanzkriterium 4) | Angriff | Test | Zielwert | Ergebnis | |---|---|---|---| | Kompromittierter ALTER Master-Key nach Rotation weiterverwenden | `TestPentest_KEK_CompromisedMasterKeyCannotDecryptAfterRotation` | alter Key entschlüsselt nicht mehr, Plaintext-KEK unverändert | **bestanden** | | Tenant-KEK-Rotation eines Mandanten wirkt sich auf einen anderen Mandanten aus | `TestPentest_KEK_TenantKEKRotationDoesNotAffectOtherTenants` | Mandant B unberührt | **bestanden** | | Manipulierter, gespeicherter `wrapped_kek` (DB-Zugriff ohne Master-Key) wird beim Entschlüsseln akzeptiert | `TestPentest_KEK_TamperedWrappedKeyRejected` | `ErrUnwrapFailed` | **bestanden** | Ergebnis: AES-256-GCM (authentifizierend) verhindert Akzeptanz manipulierter Chiffretexte; Master-Key-Rotation ändert ausschließlich die Verpackung, nie den Plaintext-Tenant-KEK; Tenant-KEK-Isolation (Modell C, Fortsetzung auf Schlüsselebene) hält auch unter gezielter Rotation eines einzelnen Mandanten. ## 2. Beim Merge/Pentest gefundene Fehler (nicht vorab bekannt) ### 2.1 `scripts/reset-test-env.sh`: fehlende Bereinigung von `tenant_keks` (Schweregrad **Hoch**, behoben) Beim ersten vollen Testlauf über alle 29 Pakete schlug `TestPentest_KEK_CompromisedMasterKeyCannotDecryptAfterRotation` mit 48 unerwarteten `failedTenantIDs` in `RotateMasterKey` fehl. Ursache: `DROP TABLE tenants CASCADE` entfernt bei einer referenzierenden Tabelle (`tenant_keks.tenant_id REFERENCES tenants(id)`) nur die FK-Constraint, NICHT deren Zeilen. Da `reset-test-env.sh` `tenant_keks` (API-10-Migration `0006_tenant_keks.up.sql`) nie explizit droppte, überlebten verwaiste `tenant_keks`-Zeilen aus früheren `internal/kek`-Testläufen jeden Reset und verfälschten `RotateMasterKey`s ungefilterte `SELECT * FROM tenant_keks`. Behoben: `reset-test-env.sh` droppt jetzt explizit alle Registry-Tabellen aus `migrations/*.up.sql` (`tenant_keks`, `rate_limit_counters`, `rate_limit_configs`, `policy_rule_changes`, `policy_rules`, `feature_flags`, `module_credentials`, `modules`), nicht nur die vier ursprünglich behandelten. Reiner Testinfrastruktur-Fix, keine Produktionsmigration verändert. ### 2.2 `internal/e2e`-Tests: `defer adminPool.Close()` lief vor `t.Cleanup`-Bereinigung (Schweregrad **Mittel**, behoben) Voller Testlauf zeigte anschließend `internal/migrate`-Fehlschläge („erwartet 3 Ergebnisse, habe 15" bzw. 12, mit Zeilen wie `e2e_tenant_a`). Ursache: `TestE2E_TenantOnboardingLoginFlow` und `TestE2E_CrossTenantIsolation` schlossen den `adminPool` per `defer adminPool.Close()`, ihre `t.Cleanup`-Funktion (welche die angelegten Testmandanten wieder löscht) lief aber – wie in Go üblich – ERST NACH allen `defer`-Aufrufen der Testfunktion, also gegen einen bereits geschlossenen Pool. Der Fehler wurde durch `_, _ = adminPool.Exec(...)` verschluckt; die Zeilen blieben dauerhaft in der geteilten `tenants`-Tabelle stehen und verfälschten `internal/migrate`s ungefilterte `registry.List()`-Abfrage im selben Prozess/derselben Datenbank. Behoben: `adminPool.Close()` in beiden Tests über `t.Cleanup` statt `defer` registriert (vor der Lösch-Cleanup, sodass LIFO die richtige Reihenfolge erzwingt – Pool bleibt bis nach dem Löschen offen). ### 2.3 `internal/kek/kek_test.go`: derselbe defer/`t.Cleanup`-Fehler, zusätzlich fehlende Zeilen-Bereinigung (Schweregrad **Mittel**, behoben) Dieselbe Fehlerklasse wie 2.2, zusätzlich verschärft: `createTenant` legte Zeilen mit `db_dsn='unused'` an, ohne JEMALS eine eigene Aufräumfunktion zu registrieren – `setupTest`s `cleanup := func(){ pool.Close() }` schloss nur den Pool, löschte aber nie die angelegten `tenants`/`tenant_keks`-Zeilen. Behoben: `createTenant` registriert jetzt eine eigene `t.Cleanup`, die Zeile+abhängigen `tenant_keks`-Eintrag löscht; `setupTest` schließt den Pool über `t.Cleanup` (nicht mehr über den zurückgegebenen `cleanup()`, den Aufrufer per `defer` aufriefen) – damit läuft die Lösch-Cleanup (später registriert) vor dem Pool-Schließen (früher registriert). ### 2.4 `internal/migrate/migrate_test.go`: hartkodiertes Testschema ohne Lifecycle-Spalten (Schweregrad **Mittel**, behoben) `setupOrchestratorTest` legte `tenants` über ein eigenes, hartkodiertes `CREATE TABLE IF NOT EXISTS` ohne die Spalten `previous_status`/`deletion_scheduled_at` an, die TEN-04 (`internal/tenant/registry.go`, `GetBySlug`/`List`) inzwischen mitliest. Lief das Paket ISOLIERT (Tabelle wird von diesem Test selbst frisch angelegt), fiel der Fehler nicht auf; erst im vollen Testlauf – bei dem die Tabelle bereits über die echten Registry-Migrationen existiert – griff `CREATE TABLE IF NOT EXISTS` nicht mehr und der Fehler `column "previous_status" does not exist` trat zutage. Behoben: fehlende Spalten im Testschema ergänzt (kein Produktionscode betroffen). ## 3. Build/Test-Ergebnis auf 192.168.1.131 (`/root/nexarch-code-qa04`) ``` go mod tidy -> clean go build ./... -> clean go vet ./... -> clean go test ./... -p 1 -count=1 -> 29/29 Pakete ok, 0 Fehlschläge ``` Enthält insbesondere `internal/pentest` (12 neue Angriffstests, alle bestanden) sowie die bereits bestehenden 124 Tests aus QA-02/QA-03 und alle Tests der 10 frisch gemergten Vorbedingungs-Branches. ## 4. Gesamtergebnis **Bestanden.** Alle vier Akzeptanzkriterien durch gezielte, dokumentierte Angriffsversuche mit Ergebnisprotokoll erfüllt; alle drei Pflichtprüfungen durchgeführt und protokolliert (Abschnitte 1.2–1.4). Vier real gefundene Fehler (Abschnitt 2, Schweregrad Hoch/Mittel) behoben – ausschließlich Testinfrastruktur, kein Produktionscode verändert.