Skip to content

feat(identity): production realm, admin mfa and auth event logging (#121) - #139

Merged
mpcs2013 merged 1 commit into
mainfrom
issue/121-production-identity
Oct 8, 2026
Merged

mpcs2013 merged 1 commit into
mainfrom
issue/121-production-identity

Conversation

@mpcs2013

@mpcs2013 mpcs2013 commented Oct 8, 2026

Copy link
Copy Markdown
Owner

Summary

The production identity for the deployable stack (#120). Phase 0 has synthetic users only (ADR-0016).

  • Production realm (deploy/keycloak/production/realm-decisya.json, C-03):
    • no seeded users; sslRequired: all;
    • plain ${NAME} placeholders filled by the hardened entrypoint-stack.sh. It validates the host, port 8443 and the client secret, refuses pre-set values, and runs kc.sh start --import-realm itself.
    • The stack mounts exactly this one realm file and the password list, read-only. The 0.17b Deployable stack: Compose from aspire publish, Caddy, production config and secrets #120 "no realm mount" guard becomes "exactly this file".
    • A parity test against the dev realm allows only differences P1 to P8.
    • The dev realm and the dev AppHost are unchanged.
  • Admin MFA (0.13 Modules.Admin API #25 C-5):
    • a step-up flow gives platform-admin holders acr "2" through TOTP; the BFF always sends acr_values=2;
    • /api/admin requires exactly one acr "2" claim (policy Api.AdminMfa, a coverage test on every admin route);
    • Authentication:RequireAdminMfa is true by default and fails closed outside Development. The dev AppHost turns it off in run mode only.
  • C-02 (Marco's decisions Q1 to Q8):
    • admin MFA and Keycloak's native password checks are enforced now, including the common-password list;
    • tenant-user MFA is configured but off;
    • breached and context-specific checks are designed only.
    • The trigger is the first non-synthetic user. It is tracked by docs/runbooks/go-live-gate.md and by synthetic=true on every user, checked by stackctl verify through the read-only identity-check.sql. That check also fails on any master-realm user or service account without OTP once the bootstrap secret is retired.
    • stackctl realm rebuild --confirm exists only while every user is synthetic.
  • Authentication events (C-10):
    • Keycloak login and admin events are stored for 90 days, admin events without details, with no stdout listener;
    • the BFF logs auth.signin.succeeded, auth.signin.failed and auth.signout;
    • the Api logs auth.token.rejected, including the previously silent bad_type, and auth.admin.mfa_required;
    • every event has fixed text and closed reason lists, and the hashed user id comes only from log enrichment.
  • Fixed along the way:
    • The OIDC handler logged the provider's error_description and exception text at Error level. That category is now set to None in the BFF, with a test.
    • The BFF principal dropped acr.
    • The RealmGuard working-tree scan now skips Python bytecode caches (G6 ruling).
  • Also: a one-line wording fix in docs/ai/pipeline/135.md (the ruleset change shipped in the same commit, 354eb8b).

Rollback

Revert this PR. On a deployed stack (none exists yet; #132), roll back by redeploying the previous release and running stackctl realm rebuild --confirm, which is allowed only while every user is synthetic. The dev environment needs no step.

Verification (local | CI)

What Local (host, 2026-10-07/08) CI
dotnet build -warnaserror 0 warnings, 0 errors build-test
Unit lane 1654 passed build-test
Integration, Identity in one command 147 passed, 0 failed (9m55s) build-test (same loop)
Integration, the other 7 projects Bff 89, Api 66, Persistence 14, Tenancy 15, Entitlements 97, Migrator 28, Audit 15, 0 failed build-test
Category=AppHost 38 passed, 1 skipped (#70) host-only
deploy/tests (Docker required) OK, 2 skipped (POSIX modes on Windows) deploy-guards
.claude/tests, lint, roster --check OK claude-config
Compose drift regenerated; only the 2 Keycloak env lines changed deploy-guards
Real-Keycloak spike D7 (26.7.5) D7-1 to D7-6 pass (folded into the integration tests)
First stackctl up/verify on Linux or the NAS not run: manual, #132 —

Data and packages

  • New data file: deploy/keycloak/production/common-passwords.txt.
    • Source: SecLists Passwords/Common-Credentials/100k-most-used-passwords-NCSC.txt at commit 1a7bb9127eca9e6ff2fc0301c597fe6e16a0cb56, MIT © 2018 Daniel Miessler (attribution in common-passwords.SOURCE.md); the data comes from the UK NCSC's list, built on HIBP frequencies.
    • Built with hex-decode, lowercase, 12 to 128 characters, de-duplicate: 1,195 lines, 17,894 bytes, SHA-256 ed0469a2ad1fd4afa94b53d0878eaaa80018a843d6de3fbfaa94a7c68740c976. Approved by Marco 2026-10-07; a test pins the hash.
  • No NuGet or npm package added.

Security

  • G3 PASS-WITH-NOTES (MUSTs G4-121-01 to 05); G6 PASS-WITH-NOTES with F-01 to F-03 fixed on re-check.
  • Accepted residuals: R-1 (usernames and IPs in Keycloak's own event store), R-2 (the client secret in Keycloak's environment), and the ASVS V6.3.3 tenant-user MFA deviation in Phase 0, compensated by the go-live trip-wire.
  • B-01 to B-03 go to Phase 0 backlog: deferred review findings and discoveries #83.

After merge (Marco)

Closes #121

Docs: docs/architecture/production-identity.md, docs/requirements/phase-0/production-identity.md, docs/requirements/nfr.md, docs/runbooks/deployable-stack.md, docs/runbooks/go-live-gate.md, docs/security/threat-models/production-identity.md, docs/security/threat-models/realm-guard-scope.md, docs/security/reviews/121.md, docs/ai/pipeline/135.md

🤖 Generated with Claude Code

)

Closes #121

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mpcs2013
mpcs2013 merged commit f48c7b1 into main Oct 8, 2026
8 checks passed
@mpcs2013
mpcs2013 deleted the issue/121-production-identity branch October 8, 2026 07:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0.17c Production identity: production realm, admin MFA, auth event logging

1 participant