From 5497e2c1006f4046f10ddd1dd2ea5d3abb867629 Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 28 Sep 2026 23:41:12 +0200 Subject: [PATCH] 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 Assistant: claude-code Assistant-Model: sonnet Assistant-Process: 295952@bnt-lap001 Assistant-Session: e93f64ad-516c-46eb-9666-aad8d300c477 --- ...anded-mode-keycloak-federation-topology.md | 66 +++++++++++++++---- ...platform-identity-security-architecture.md | 4 +- .../NK-WP-0011-enterprise-federation-saml.md | 19 +++++- 3 files changed, 73 insertions(+), 16 deletions(-) diff --git a/docs/adr/ADR-0009-expanded-mode-keycloak-federation-topology.md b/docs/adr/ADR-0009-expanded-mode-keycloak-federation-topology.md index 0389eb7..e14b4bd 100644 --- a/docs/adr/ADR-0009-expanded-mode-keycloak-federation-topology.md +++ b/docs/adr/ADR-0009-expanded-mode-keycloak-federation-topology.md @@ -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/`) 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. diff --git a/docs/platform-identity-security-architecture.md b/docs/platform-identity-security-architecture.md index 5410708..455355d 100644 --- a/docs/platform-identity-security-architecture.md +++ b/docs/platform-identity-security-architecture.md @@ -137,7 +137,9 @@ required that the lightweight stack does not provide — chiefly inbound enterprise federation and SAML brokering (Entra ID, Active Directory, generic SAML IdPs), complex multi-realm topologies, or delegated admin. 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 trigger for it. See **Capability Progression** below. diff --git a/workplans/NK-WP-0011-enterprise-federation-saml.md b/workplans/NK-WP-0011-enterprise-federation-saml.md index a65267d..cc8034b 100644 --- a/workplans/NK-WP-0011-enterprise-federation-saml.md +++ b/workplans/NK-WP-0011-enterprise-federation-saml.md @@ -117,7 +117,7 @@ Out of scope: ```task id: NK-WP-0011-T01 state_hub_task_id: "a5807808-fb34-50de-8f67-9128011833d4" -status: progress +status: done 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 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 +- A rehearsed shared-to-dedicated isolation upgrade preserves issuer and subjects. - An ADR records the expanded-mode trigger, federation topology, selected realm/tenant isolation model, and KeyCape/Keycloak issuer coexistence. - A federated user from at least one enterprise IdP (Entra ID) can log in