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
149 lines
8.1 KiB
Markdown
149 lines
8.1 KiB
Markdown
---
|
|
id: NK-ADR-0009
|
|
type: architecture-decision-record
|
|
title: "Expanded-Mode Keycloak: Adoption Trigger and Federation Topology"
|
|
status: accepted
|
|
owner: net-kingdom
|
|
revision: "1"
|
|
decided: "2026-09-28"
|
|
last_reviewed: "2026-09-28"
|
|
review_interval: 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
|
|
|
|
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
|
|
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.
|
|
2. **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.
|
|
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
|
|
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.
|
|
5. **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.
|
|
6. **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.
|
|
7. **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.
|
|
8. **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-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
|
|
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?
|