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
8.1 KiB
| id | type | title | status | owner | revision | decided | last_reviewed | review_interval |
|---|---|---|---|---|---|---|---|---|
| NK-ADR-0009 | architecture-decision-record | Expanded-Mode Keycloak: Adoption Trigger and Federation Topology | accepted | net-kingdom | 1 | 2026-09-28 | 2026-09-28 | 12m |
ADR-0009 - Expanded-Mode Keycloak: Adoption Trigger and Federation Topology
Status: Accepted 2026-09-28, with the isolation-upgrade amendment in decision 3 (NK-WP-0011-T01) Date: 2026-09-28 Deciders: Bernd Worsch, Claude
Context
The live IAM Profile issuer is KeyCape (Authelia + LLDAP + privacyIDEA) at
https://kc.coulomb.social. No Keycloak Deployment exists. The architecture
doc says expanded mode is capability-driven, chiefly inbound enterprise
federation (Entra ID, AD, SAML), and leaves the per-tenant trigger and
dual-issuer rule open. ADR-0014 defines the IAM capability role as a tenant
operating its own dedicated IAM instance. ADR-0006 keeps flex-auth as the PDP.
ADR-0016 makes MFA a user preference with workload opt-in step-up. The user-engine
extension-point doc requires immutable provider subjects, tenant-scoped group
mappings, and no privilege from unmapped upstream claims. The estate is one
node with single-instance databases, so no option here buys HA.
NK-WP-0001 decision D2 (Keycloak as primary internal user store) predates all of this.
Decision
-
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 requirement the lightweight stack cannot meet. User count, or the wish to have Keycloak, is not a trigger. Until then NK-WP-0011 implementation tasks stay unstarted.
-
Role: OIDC identity broker only. Keycloak brokers upstream Entra ID (OIDC), AD (LDAP federation) and generic SAML 2.0 IdPs (SAML as an upstream, via Keycloak's identity-provider brokering). Downstream it issues only IAM Profile OIDC/PKCE tokens. Emitting SAML assertions to downstream applications is out of scope. Keycloak is not the primary user store (supersedes D2), not the PDP, and not a secret store.
-
Isolation tiers, with a defined upgrade path. The realm is the tenant boundary and
tenant:platformis a separate, reserved realm. Isolation is a tenant-selectable tier, expected to be a paid upgrade:Tier Name Boundary Selected by shared-realmDefault Own realm in the shared broker: realm-level isolation only (shared process, database and admin plane) Default for any federated tenant dedicated-instanceUpgrade Own broker instance and database: process-level isolation, own scale and performance Tenant acquires the IAMcapability role (ADR-0014)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
IAMrole, never stored as a second independent fact.Reference points that make the upgrade non-breaking. These are binding on implementation and documentation:
- 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. - 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 anysubfails the drill and is not permitted. - 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.
- 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
subpreservation 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. - 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.
- Billing is out of scope here. Plan and pricing are tenant-engine
facts; this ADR only fixes the technical contract that the
IAMrole selects. Downgrade is not defined and needs its own decision.
- Issuer invariance. The tenant's issuer URL (host plus
-
Coexistence. Each tenant has exactly one active issuer at a time, recorded by tenant-engine and published as issuer configuration. KeyCape stays unchanged and keeps serving lightweight tenants. Applications target the IAM Profile and discover the issuer; they do not embed provider choice. Account bindings remain keyed on issuer plus immutable subject, so moving a tenant between issuers is a planned migration with an explicit subject-mapping step, never a silent dual login.
-
Assurance. Federated login is AAL1 by default. Upstream MFA (for example Entra Conditional Access) counts as higher assurance only through a reviewed, per-IdP trust mapping written down before use. Privileged step-up continues to use privacyIDEA under ADR-0016 and the proposed IAM v0.4 step-up. The privacyIDEA provider JAR is added only if a tenant needs Keycloak-side MFA.
-
Provisioning and mapping. Follow the user-engine extension-point rules: JIT creates a pending projection; group mappings are tenant-scoped, versioned and deny ambiguous envelopes; no platform role from raw upstream group names.
-
Hostname and issuer. Not chosen here. It is assigned by the package owner under ADR-0015 with the first tenant, as a distinct host from
kc.coulomb.social; the issuer URL is then treated as immutable. -
Ownership. net-kingdom owns this contract and conformance checks. Packaging and runtime belong to the ADR-0015 owner; the database consumer to railiance-platform; OpenBao roles to railiance-platform.
Alternatives considered
- Keycloak replaces KeyCape. Rejected: unnecessary migration risk for working tenants; contradicts capability-driven adoption.
- Dedicated instance for every federated tenant. Strongest isolation, but on
one node the cost is multiplied database, memory and patching for tenants
that do not need it. Offered as the paid
dedicated-instanceupgrade. - Realm-per-tenant only, including
IAMtenants. Contradicts ADR-0014's meaning ofIAM. - Keycloak as SAML IdP downstream too. No consumer requires it; widens the contract surface. Revisit on a named need.
- Trust upstream MFA claims by default. Rejected: assurance would depend on each customer's tenant configuration.
- Build ahead of demand. Rejected: adds an issuer, a database and a break-glass path with no owner or user.
Consequences
- NK-WP-0011 T02 onward stay gated on the trigger in decision 1.
- T06 conformance uses
tools/iam-profile-conformance/against IAM v0.3. - tenant-engine needs an issuer-per-tenant field; this is a dependency to raise with its owner, not something built here.
- 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 and an isolated restore proof.
Open questions
- Which named tenant and IdP owner will trigger the first deployment?
- Does key-cape require changes for issuer-per-tenant discovery (EP-NK-001)?
- Which claim carries the federated-assurance mapping so flex-auth can consume it?