Files
nexarch/docs/IAM-18-PRUEFPROTOKOLL.md
rootandClaude Sonnet 5 5361addc55
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
Archive-Schnittstellen-Vertragstests / contract-tests (push) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
IAM-18: Praefprotokoll zur strikten Claim-Validierung bei OIDC-SSO
Implementierung war bereits in main vorhanden (Commit 9a41f3d). Dieser
Commit dokumentiert die reale Verifikation aller drei Akzeptanzkriterien
(Issuer/Audience-Bindung, JWKS-only-Signaturpruefung, Sperre gegen
automatische Superadmin-Vergabe aus externen Claims) durch Testlauf auf
Host 204.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-15 20:30:04 +00:00

5.6 KiB

Prüfprotokoll IAM-18 — Strikte Claim-Validierung bei OIDC-SSO

Datum: 2026-10-15 Geprüft auf: Host 204 (/root/iam-18-work-1792096064/repo), Branch feature/iam-18-strikte-claim-validierung-bei-oidc-sso

Befund: Ticket bereits implementiert

Bei der Analyse zeigte sich, dass IAM-18 bereits vollständig im main-Branch enthalten ist (Commit 9a41f3d, "IAM-18: strikte-claim-validierung-bei-oidc-sso", nach QA-02 gemergt). Es war keine neue Implementierung nötig; dieses Protokoll dokumentiert die reale Verifikation der drei Akzeptanzkriterien auf Host 204.

Relevante Dateien:

  • internal/oidc/verify.go — Verifier/NewVerifier/Verify/VerifyWithNonce
  • internal/oidc/login.go — CompleteOIDCLogin, capToTenantScopedRole
  • internal/oidc/jwks.go — ParseJWKS (JWKS-Parsing des konfigurierten Issuers)
  • internal/oidc/verify_test.go, internal/oidc/login_test.go — zugehörige Tests

Akzeptanzkriterium 1 — Issuer/Audience exakt gegen Tenant-Konfiguration

oidc.NewVerifier(keys, issuer, audience) bindet Issuer und Audience fest an Werte, die der Aufrufer aus der je-Tenant hinterlegten Konfiguration übergibt — niemals aus dem Token selbst gelesen. Verify() vergleicht claims.Issuer und claims.Audience exakt (keine Präfix-/Teilstring-Logik) gegen diese Werte und lehnt bei Abweichung mit ErrInvalidToken ab.

Negativtest ausgeführt:

  • TestVerify_RejectsWrongIssuer — PASS
  • TestVerify_RejectsWrongAudience — PASS

Akzeptanzkriterium 2 — Signatur ausschließlich gegen JWKS des konfigurierten Issuers

Die Schlüsselmenge (map[string]*rsa.PublicKey) wird beim Aufrufer ausschließlich aus ParseJWKS() der JWKS-URL des konfigurierten Issuers befüllt und dem Verifier übergeben. Der kid-Header des Tokens wählt nur innerhalb dieser vorab geladenen Menge aus; ein Token kann weder einen eigenen Signaturschlüssel noch einen eigenen Issuer erzwingen.

Negativtest ausgeführt:

  • TestVerify_RejectsWrongSigningKey (Token mit fremdem, nicht in der konfigurierten JWKS-Menge enthaltenem Schlüssel signiert) — PASS
  • TestVerify_RejectsExpiredToken — PASS (Ablaufprüfung als Teil derselben Verify-Pfad-Härtung)

Akzeptanzkriterium 3 — Keine automatische Superadmin-Vergabe aus externen Claims

Zweistufige Sperre:

  1. ldapsync.RoleMappingStore (gemeinsame Mapping-Tabelle mit IAM-05/LDAP) lässt über SetMapping nur user/tenant_admin als Zielrolle zu (CHECK-Constraint ldap_group_role_mapping_role_check).
  2. capToTenantScopedRole() im OIDC-Login-Pfad selbst kappt zusätzlich und unabhängig von der Mapping-Tabelle jede gelieferte rbac.RoleSuperadmin auf rbac.RoleTenantAdmin — selbst wenn die Mapping-Tabelle unter Umgehung der Anwendungsschicht (z. B. fehlerhafter DB-Zugriff) auf superadmin gesetzt würde.

Negativtests ausgeführt:

  • TestCompleteOIDCLogin_UnmappedRoleGrantsNothing — Claim behauptet ["tenant_admin", "superadmin"] über nie gemappte Gruppen → keine Rollenzuweisung. PASS
  • TestCompleteOIDCLogin_CapsSuperadminClaimToTenantAdmin — CHECK-Constraint der Mapping-Tabelle wird testweise entfernt und die Tabelle direkt auf superadmin gesetzt (Simulation einer kompromittierten Zuordnung); CompleteOIDCLogin vergibt trotzdem nur tenant_admin, niemals superadmin. PASS
  • TestCapToTenantScopedRole_NeverGrantsSuperadmin (reine Unit-Ebene ohne DB) — PASS

Testlauf (real ausgeführt auf Host 204)

Befehl:

set -a && source /etc/nexarch-dev/core-dev.env && set +a
go test ./internal/oidc/... ./internal/rbac/... ./internal/ldapsync/... -v -count=1

Ergebnis: alle Tests PASS.

  • internal/oidc: 17 Tests PASS (TestCompleteOIDCLogin_EndToEnd, TestCompleteOIDCLogin_UnmappedRoleGrantsNothing, TestCapToTenantScopedRole_NeverGrantsSuperadmin, TestCompleteOIDCLogin_CapsSuperadminClaimToTenantAdmin, TestFullAuthorizationCodeFlow, TestGrantedScopes_NeverExceedsAllowed, TestValidateRedirectURI_RejectsUnregistered, TestAuthCodeStore_CannotBeConsumedTwice, TestStateStore_ConsumeIsSingleUse, TestStateStore_RejectsUnknownState, TestVerify_AcceptsValidToken, TestVerify_RejectsExpiredToken, TestVerify_RejectsWrongSigningKey, TestVerify_RejectsWrongIssuer, TestVerify_RejectsWrongAudience, TestVerifyWithNonce_RejectsMismatch, TestParseJWKS_RoundTrip) — Laufzeit 2,1s
  • internal/rbac: 19 Tests PASS (u. a. TestStore_RejectsSuperadminOutsideAllowedMatrix, TestValidateNoTenantToSuperadminEscalation_*, TestAssignRole_RejectsSelfEscalation) — Laufzeit 34,7s
  • internal/ldapsync: 9 Tests PASS — Laufzeit 1,4s

Keine Fehlschläge, keine Skips (TEST_ADMIN_DSN war gesetzt).

Bewertung

Alle drei Akzeptanzkriterien sind durch Code und durch reale, auf Host 204 ausgeführte automatisierte Tests abgedeckt. Es waren keine Codeänderungen nötig; dieser Durchlauf dient der Abnahme-Verifikation und Dokumentation.

Offene Anmerkung (keine Ticketerweiterung, nur Beobachtung)

In cmd/ wurde kein HTTP-Server gefunden, der oidc.NewVerifier mit einer aus einer Tenant-Konfigurationstabelle geladenen Issuer/Audience/JWKS-URL instanziiert (nur in Tests verwendet). Die Bindung "Issuer/Audience kommen ausschließlich aus der Tenant-Konfiguration" ist auf Code-Ebene (API von NewVerifier) bereits erzwungen; die konkrete Tenant-Konfigurationsablage und das Verdrahten in einen produktiven Login-Endpunkt scheinen außerhalb des Scopes von IAM-06/IAM-18 zu liegen (kein echter externer OIDC-Provider in dieser Umgebung verfügbar, siehe Kommentar in internal/oidc/jwks.go). Kein Handlungsbedarf für dieses Ticket, ggf. als Folgeticket für die produktive Tenant-OIDC-Konfigurationsverwaltung vormerken.