Accept ADR-0009 with tenant-selectable isolation upgrade path
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run

Add shared-realm and dedicated-instance tiers selected by the IAM capability
role, binding reference points (issuer invariance, subject preservation,
tier-neutral declaration, drill), and NK-WP-0011-T09.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 295952@bnt-lap001
Assistant-Session: e93f64ad-516c-46eb-9666-aad8d300c477
This commit is contained in:
tegwick 2026-09-28 23:41:12 +02:00
parent 7d11ce58ce
commit 5497e2c100
3 changed files with 73 additions and 16 deletions

View file

@ -2,17 +2,17 @@
id: NK-ADR-0009 id: NK-ADR-0009
type: architecture-decision-record type: architecture-decision-record
title: "Expanded-Mode Keycloak: Adoption Trigger and Federation Topology" title: "Expanded-Mode Keycloak: Adoption Trigger and Federation Topology"
status: proposed status: accepted
owner: net-kingdom owner: net-kingdom
revision: "1" revision: "1"
proposed: "2026-09-28" decided: "2026-09-28"
last_reviewed: "2026-09-28" last_reviewed: "2026-09-28"
review_interval: 12m review_interval: 12m
--- ---
# ADR-0009 - Expanded-Mode Keycloak: Adoption Trigger and Federation Topology # ADR-0009 - Expanded-Mode Keycloak: Adoption Trigger and Federation Topology
**Status:** Proposed (awaiting Bernd's decision; NK-WP-0011-T01) **Status:** Accepted 2026-09-28, with the isolation-upgrade amendment in decision 3 (NK-WP-0011-T01)
**Date:** 2026-09-28 **Date:** 2026-09-28
**Deciders:** Bernd Worsch, Claude **Deciders:** Bernd Worsch, Claude
@ -32,7 +32,7 @@ node with single-instance databases, so no option here buys HA.
NK-WP-0001 decision D2 (Keycloak as primary internal user store) predates all NK-WP-0001 decision D2 (Keycloak as primary internal user store) predates all
of this. of this.
## Decision (proposed) ## Decision
1. **Trigger.** A tenant moves to expanded mode only when all three hold: a 1. **Trigger.** A tenant moves to expanded mode only when all three hold: a
named tenant, a named owner of the upstream IdP, and a concrete federation named tenant, a named owner of the upstream IdP, and a concrete federation
@ -45,15 +45,51 @@ of this.
only IAM Profile OIDC/PKCE tokens. Emitting SAML assertions to downstream only IAM Profile OIDC/PKCE tokens. Emitting SAML assertions to downstream
applications is out of scope. Keycloak is not the primary user store applications is out of scope. Keycloak is not the primary user store
(**supersedes D2**), not the PDP, and not a secret store. (**supersedes D2**), not the PDP, and not a secret store.
3. **Isolation.** Realms are the tenant boundary. The `tenant:platform` realm 3. **Isolation tiers, with a defined upgrade path.** The realm is the tenant
is separate and reserved. A tenant without the `IAM` role gets a realm in boundary and `tenant:platform` is a separate, reserved realm. Isolation is
the shared broker; a tenant with the `IAM` role gets a dedicated broker a tenant-selectable tier, expected to be a paid upgrade:
instance (ADR-0014). A shared broker gives realm-level, not process-level,
isolation, so it is acceptable only for tenants whose federation trust and | Tier | Name | Boundary | Selected by |
data classification allow that. Realm admins may not alter IAM Profile | --- | --- | --- | --- |
semantics, the platform realm, federation trust settings, or audit | `shared-realm` | Default | Own realm in the shared broker: realm-level isolation only (shared process, database and admin plane) | Default for any federated tenant |
retention. tenant-engine, not Keycloak, is the source of truth for tenant | `dedicated-instance` | Upgrade | Own broker instance and database: process-level isolation, own scale and performance | Tenant acquires the `IAM` capability role (ADR-0014) |
existence and capability roles.
Realm admins in either tier may not alter IAM Profile semantics, the
platform realm, federation trust settings, or audit retention. Shared-realm
tenants must not be described as strongly isolated. tenant-engine owns
tenant existence, capability roles and plan; the tier is *derived* from
the `IAM` role, never stored as a second independent fact.
**Reference points that make the upgrade non-breaking.** These are
binding on implementation and documentation:
1. **Issuer invariance.** The tenant's issuer URL (host plus
`/realms/<tenant>`) is the same in both tiers. An upgrade moves the
routing behind the hostname (Traefik), not the issuer, so applications,
audiences and account bindings do not change. The realm name is
therefore chosen once, is immutable, and is derived from the tenant id.
2. **Subject preservation.** Keycloak-local user ids are the `sub`. Upgrade
migrates the realm with users and their original ids preserved, and
federated-identity links intact. A migration that changes any `sub`
fails the drill and is not permitted.
3. **Tier-neutral declaration.** Each tenant realm is declared in git as
sanitized realm configuration plus a small tenant manifest naming the
tenant id, realm name and issuer. The manifest carries no tier field; the
tier is derived from tenant-engine, and the same realm declaration
deploys to either tier.
4. **Owner-executed runbook and drill.** A documented
shared-to-dedicated procedure exists with a rehearsed drill before
any paid tier is offered: export, import into an isolated instance,
verify `sub` preservation and login, cut routing, keep the source
realm read-only for a retention window, then remove it. Rollback is
re-routing to the still-intact source realm until that window ends.
5. **Per-tier evidence.** Backup ownership, off-host custody and an
isolated restore proof are recorded per tier; the dedicated tier
has its own database consumer and OpenBao role.
6. **Billing is out of scope here.** Plan and pricing are tenant-engine
facts; this ADR only fixes the technical contract that the `IAM` role
selects. Downgrade is not defined and needs its own decision.
4. **Coexistence.** Each tenant has exactly one active issuer at a time, 4. **Coexistence.** Each tenant has exactly one active issuer at a time,
recorded by tenant-engine and published as issuer configuration. KeyCape recorded by tenant-engine and published as issuer configuration. KeyCape
stays unchanged and keeps serving lightweight tenants. Applications target stays unchanged and keeps serving lightweight tenants. Applications target
@ -84,7 +120,7 @@ of this.
working tenants; contradicts capability-driven adoption. working tenants; contradicts capability-driven adoption.
- **Dedicated instance for every federated tenant.** Strongest isolation, but on - **Dedicated instance for every federated tenant.** Strongest isolation, but on
one node the cost is multiplied database, memory and patching for tenants one node the cost is multiplied database, memory and patching for tenants
that do not need it. Kept for `IAM`-role tenants. that do not need it. Offered as the paid `dedicated-instance` upgrade.
- **Realm-per-tenant only, including `IAM` tenants.** Contradicts ADR-0014's - **Realm-per-tenant only, including `IAM` tenants.** Contradicts ADR-0014's
meaning of `IAM`. meaning of `IAM`.
- **Keycloak as SAML IdP downstream too.** No consumer requires it; widens the - **Keycloak as SAML IdP downstream too.** No consumer requires it; widens the
@ -101,6 +137,8 @@ of this.
- tenant-engine needs an issuer-per-tenant field; this is a dependency to - tenant-engine needs an issuer-per-tenant field; this is a dependency to
raise with its owner, not something built here. raise with its owner, not something built here.
- Shared-broker realms cannot be described as strongly isolated tenants. - Shared-broker realms cannot be described as strongly isolated tenants.
- The upgrade constrains the design now: immutable realm names, issuer
hostnames that can be re-routed, and `sub`-preserving realm migration.
- No HA is implied; T02 and T08 must name backup ownership, off-host custody - No HA is implied; T02 and T08 must name backup ownership, off-host custody
and an isolated restore proof. and an isolated restore proof.

View file

@ -137,7 +137,9 @@ required that the lightweight stack does not provide — chiefly inbound
enterprise federation and SAML brokering (Entra ID, Active Directory, enterprise federation and SAML brokering (Entra ID, Active Directory,
generic SAML IdPs), complex multi-realm topologies, or delegated admin. generic SAML IdPs), complex multi-realm topologies, or delegated admin.
A deployment climbs to expanded mode because it needs that capability, A deployment climbs to expanded mode because it needs that capability,
not because it has more users. The lower resource and operational not because it has more users. Federated tenants start in a shared realm and
can upgrade to a dedicated instance via the `IAM` capability role without
changing issuer or subjects (ADR-0009, decision 3). The lower resource and operational
footprint of the lightweight stack is a consequence of this rule, not the footprint of the lightweight stack is a consequence of this rule, not the
trigger for it. See **Capability Progression** below. trigger for it. See **Capability Progression** below.

View file

@ -117,7 +117,7 @@ Out of scope:
```task ```task
id: NK-WP-0011-T01 id: NK-WP-0011-T01
state_hub_task_id: "a5807808-fb34-50de-8f67-9128011833d4" state_hub_task_id: "a5807808-fb34-50de-8f67-9128011833d4"
status: progress status: done
priority: high priority: high
``` ```
@ -251,8 +251,25 @@ audit sink alongside flex-auth/Topaz/OpenBao records, with correlation
ids — satisfying the "Audit sink" and "Break-glass" rows of the ids — satisfying the "Audit sink" and "Break-glass" rows of the
production-readiness checklist. production-readiness checklist.
## Prove the isolation upgrade path
```task
id: NK-WP-0011-T09
status: todo
priority: medium
```
**Shared-realm to dedicated-instance upgrade (ADR-0009, decision 3).** Write
the owner-executed upgrade runbook and rehearse it in an isolated environment
before any paid tier is offered. The drill must show issuer URL unchanged,
every user `sub` and federated link preserved, login working after cutover,
rollback by re-routing during the retention window, and per-tier backup and
restore evidence. Coordinate with tenant-engine on deriving the tier from the
`IAM` capability role; do not add an independent tier field.
## Acceptance Criteria ## Acceptance Criteria
- A rehearsed shared-to-dedicated isolation upgrade preserves issuer and subjects.
- An ADR records the expanded-mode trigger, federation topology, - An ADR records the expanded-mode trigger, federation topology,
selected realm/tenant isolation model, and KeyCape/Keycloak issuer coexistence. selected realm/tenant isolation model, and KeyCape/Keycloak issuer coexistence.
- A federated user from at least one enterprise IdP (Entra ID) can log in - A federated user from at least one enterprise IdP (Entra ID) can log in