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
type: architecture-decision-record
title: "Expanded-Mode Keycloak: Adoption Trigger and Federation Topology"
status: proposed
status: accepted
owner: net-kingdom
revision: "1"
proposed: "2026-09-28"
decided: "2026-09-28"
last_reviewed: "2026-09-28"
review_interval: 12m
---
# 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
**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
of this.
## Decision (proposed)
## Decision
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
@ -45,15 +45,51 @@ of this.
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.
3. **Isolation.** Realms are the tenant boundary. The `tenant:platform` realm
is separate and reserved. A tenant without the `IAM` role gets a realm in
the shared broker; a tenant with the `IAM` role gets a dedicated broker
instance (ADR-0014). A shared broker gives realm-level, not process-level,
isolation, so it is acceptable only for tenants whose federation trust and
data classification allow that. Realm admins may not alter IAM Profile
semantics, the platform realm, federation trust settings, or audit
retention. tenant-engine, not Keycloak, is the source of truth for tenant
existence and capability roles.
3. **Isolation tiers, with a defined upgrade path.** The realm is the tenant
boundary and `tenant:platform` is a separate, reserved realm. Isolation is
a tenant-selectable tier, expected to be a paid upgrade:
| Tier | Name | Boundary | Selected by |
| --- | --- | --- | --- |
| `shared-realm` | Default | Own realm in the shared broker: realm-level isolation only (shared process, database and admin plane) | Default for any federated tenant |
| `dedicated-instance` | Upgrade | Own broker instance and database: process-level isolation, own scale and performance | Tenant acquires the `IAM` capability 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 `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,
recorded by tenant-engine and published as issuer configuration. KeyCape
stays unchanged and keeps serving lightweight tenants. Applications target
@ -84,7 +120,7 @@ of this.
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. 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
meaning of `IAM`.
- **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
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.