Accept ADR-0009 with tenant-selectable isolation upgrade path
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:
parent
7d11ce58ce
commit
5497e2c100
3 changed files with 73 additions and 16 deletions
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue