QA-04: sicherheits-penetrationspruefung-core

Voller Merge von RBAC-02/IAM-06/IAM-07/API-03/API-10/API-08/API-09/IAM-10/
IAM-11/IAM-13 plus echte Angriffstests (internal/pentest) gegen SSO/OIDC
(alg=none, Fremdschluessel, Claims-Manipulation, Nonce-Replay), Rate-
Limiting/Lockout im simulierten Mehrinstanz-Betrieb, zentrale Policy-
Durchsetzung (Rechteausweitung, struktureller Guard-Bypass) und Master-
Key-/Tenant-KEK-Rotation. 29/29 Pakete gruen auf 192.168.1.131.

Vier real gefundene Testinfrastruktur-Fehler behoben: reset-test-env.sh
liess tenant_keks (und weitere neuere Registry-Tabellen) beim Reset stehen
(FK-CASCADE loescht nur die Constraint, keine Zeilen); zwei E2E-Tests und
kek_test.go schlossen ihren adminPool per defer VOR ihrer t.Cleanup-
Bereinigung (t.Cleanup laeuft immer nach allen defers); migrate_test.go
hatte ein Testschema ohne die TEN-04-Lifecycle-Spalten. Alle vier Fixes
betreffen ausschliesslich Testcode, kein Produktionscode geaendert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
This commit is contained in:
sysops
2026-08-29 09:57:36 +02:00
co-authored by Claude Sonnet 5
parent 5e6cb4e2db
commit 9f641fec76
7 changed files with 926 additions and 4 deletions
+149
View File
@@ -0,0 +1,149 @@
# 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.21.4). Vier real gefundene
Fehler (Abschnitt 2, Schweregrad Hoch/Mittel) behoben ausschließlich
Testinfrastruktur, kein Produktionscode verändert.