net-kingdom/workplans/NK-WP-0011-enterprise-federation-saml.md
tegwick 294b43a7d7
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
Sync consistency output after ADR-0009 acceptance
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
2026-09-28 23:42:07 +02:00

318 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
id: NK-WP-0011
type: workplan
title: "Enterprise Federation & SAML — Expanded-Mode Keycloak Identity Broker"
domain: infotech
repo: net-kingdom
status: active
flavor: implementation
owner: worsch
topic_slug: netkingdom
created: "2026-05-20"
updated: "2026-09-28"
state_hub_workstream_id: "1075448f-d533-5f9e-94b7-c3adfe151a07"
depends_on:
- NK-WP-0003
- NK-WP-0004
- NK-WP-0006
supersedes_tasks:
- NK-WP-0001-T05
- NK-WP-0001-T06
- NK-WP-0001-T07
- NK-WP-0001-T08
---
# NK-WP-0011 — Enterprise Federation & SAML (Expanded-Mode Keycloak)
> Extracted from NK-WP-0001 (T05–T08, the deferred Keycloak path) and
> refined against where net-kingdom actually stands today: a deployed
> KeyCape lightweight stack, an OpenBao runtime-secret authority, and a
> recursive platform/tenant authorization model. This is **expanded
> identity mode** in the architecture (`docs/platform-identity-security-architecture.md`).
## Goal
Stand up **Keycloak as an identity broker** that federates upstream
enterprise identity providers (Microsoft Entra ID / Azure AD via OIDC,
on-prem Active Directory via LDAP, and generic SAML 2.0 IdPs) and issues
**NetKingdom IAM Profile-conformant** tokens downstream — without
displacing flex-auth as the authorization decision point or breaking the
recursive platform/tenant boundary.
This is the answer to the long-standing open question
*"when does the platform switch from key-cape lightweight mode to Keycloak
expanded mode?"* — expanded mode exists **specifically** to onboard
identities that originate in an external enterprise IdP, which the
lightweight Authelia + LLDAP stack cannot broker.
## Why this is not just "resume NK-WP-0001"
NK-WP-0001 assumed a greenfield: bootstrap Vault, build PostgreSQL, treat
Keycloak as the internal user store. None of those assumptions hold now:
| NK-WP-0001 assumption | Current reality | Effect on this plan |
|---|---|---|
| HashiCorp Vault, bootstrapped from KeePassXC | **OpenBao** is the runtime secret authority (NK-WP-0006); SOPS/age + agent bootstrap exist (NK-WP-0004/0005) | Keycloak DB + admin secrets come from OpenBao via ESO; no new vault bootstrap |
| PostgreSQL built from scratch | CloudNativePG running on RAILIANCE01 (NK-WP-0003) | Admit a database consumer through current platform owners; prove backup/restore |
| Keycloak is the internal source of truth (D2 hybrid) | KeyCape lightweight stack is the *deployed* IAM Profile issuer | Keycloak is a **broker/federation front-end**, not the primary user store |
| Authorization via Keycloak Authorization Services | flex-auth + Topaz is the canonical PDP (ADR-0006) | Keycloak AuthZ Services is at most an optional adapter, never canonical |
| Single-tenant Coulomb deployment | Recursive `tenant:platform` vs `tenant:coulomb` model (NK-WP-0006) | Evaluate realm-per-tenant; tenant admins must not receive platform-root |
| MFA solely via privacyIDEA provider JAR | privacyIDEA deployed *and* upstream IdPs carry their own MFA | MFA assurance source becomes a decision, not a default |
## Architecture
```text
Enterprise IdPs (upstream)
Entra ID (OIDC) AD (LDAP) SAML 2.0 IdP
│ │ │
└──────────────┼──────────────┘
▼
[ Keycloak ] expanded-mode broker
│ realm-per-tenant candidate; IAM Profile issuer
│ secrets ← OpenBao (ESO)
│ MFA ← privacyIDEA *or* upstream assurance
▼
NetKingdom IAM Profile token (OIDC/PKCE)
│
├──► applications (depend on the Profile, not the provider)
└──► flex-auth / Topaz ── authorization decision (PDP)
coexists with: KeyCape lightweight issuer (kc.coulomb.social)
```
Keycloak answers identity (who, how authenticated, coarse claims,
assurance). It does **not** answer resource authorization — that stays in
flex-auth (ADR-0006). It does not store runtime secrets — those stay in
OpenBao.
## Scope
In scope:
- decision record for expanded-mode adoption: trigger, federation
topology (broker vs SAML SP), realm isolation model, and coexistence
with the KeyCape lightweight issuer
- owner-packaged Keycloak image (privacyIDEA provider JAR only if selected
and verified compatible) and managed deployment on railiance01
- upstream federation: Entra ID (OIDC), AD (LDAP), generic SAML 2.0 IdP
- claim mapping to the NetKingdom IAM Profile (issuer, audience, subject,
groups, tenant, assurance evidence) and IAM Profile conformance checks
- MFA / assurance source decision and enforcement of step-up for
privileged actions
- recursive tenancy: realm-per-tenant, platform-root guardrails, and the
flex-auth/Topaz authorization boundary
- backups, DR, break-glass, monitoring, and audit shipping for the broker
Out of scope:
- replacing flex-auth/Topaz with Keycloak Authorization Services
- migrating the deployed lightweight stack off KeyCape (coexistence only)
- application-side OIDC client code (apps target the IAM Profile spec)
- deploying OpenBao itself (Railiance platform) — consumed, not built
- tenant-specific federation policy for tenants beyond `tenant:platform`
and `tenant:coulomb`
## Decide federation adoption and topology
```task
id: NK-WP-0011-T01
state_hub_task_id: "a5807808-fb34-50de-8f67-9128011833d4"
status: done
priority: high
```
**Decision record — expanded-mode adoption & federation topology.** Write
an ADR (ADR-0009) capturing: the concrete trigger for switching a tenant
from lightweight to expanded mode; whether Keycloak acts as an OIDC
identity broker, a SAML service provider, or both; the realm-per-tenant
candidate and its alternatives mapped onto `tenant:platform` /
`tenant:coulomb`; how the Keycloak issuer
coexists with the KeyCape issuer (`kc.coulomb.social`) so applications
still target one IAM Profile contract; and the canonical hostname/issuer
for the broker. Resolve or supersede D2 from NK-WP-0001.
## Admit the database consumer
```task
id: NK-WP-0011-T02
state_hub_task_id: "2fe6f100-3a5d-563e-b033-7d7b846ae487"
status: todo
priority: high
```
**PostgreSQL `keycloak_db` on the existing operator.** Have railiance-platform
and rapp-postgres admit a named Keycloak database consumer against the current
database catalog and isolation needs. Do not assume the historical NK-WP-0003
instance is the correct placement. Source credentials from OpenBao via ESO
into a K8s Secret. Confirm the existing backup schedule covers the new database and
run a restore drill for `keycloak_db` specifically.
## Package the broker deployment
```task
id: NK-WP-0011-T03
state_hub_task_id: "32d1411b-10a4-5a8b-8f43-99ac46d83908"
status: todo
priority: high
```
**Deploy expanded-mode Keycloak.** Build a custom image
(`kc.sh build`, privacyIDEA provider JAR included only if T5 delegates MFA
to privacyIDEA). Assign the package/runtime owner under ADR-0015 and deploy
through its managed declaration on railiance01 behind Traefik +
cert-manager at the issuer hostname from T1. Admin bootstrap secret and DB
secret come from OpenBao/ESO — never typed, never in git. Hostname
strictness + proxy headers configured for Traefik. Realm import is
GitOps-friendly (realm JSON/CR in git).
## Integrate an upstream identity provider
```task
id: NK-WP-0011-T04
state_hub_task_id: "6697a994-7343-5ea8-8b37-bc3b921e5a9b"
status: todo
priority: high
```
**Upstream federation.** Configure identity brokering for Entra ID (OIDC),
on-prem AD (LDAP user federation), and a generic SAML 2.0 IdP. Map each
source's claims/attributes into the NetKingdom IAM Profile shape: issuer,
audience, subject, groups, **tenant**, and assurance evidence. Define the
attribute/claim mappers and group→role mapping. Verify a federated login
end-to-end for at least the Entra ID path.
## Define federated assurance
```task
id: NK-WP-0011-T05
state_hub_task_id: "d845380d-3dbc-5e59-8f2b-5a4d1b32d89d"
status: todo
priority: medium
```
**MFA / assurance source.** Decide and implement the assurance model: MFA
enforced by privacyIDEA (via the Keycloak provider JAR + a "privacyIDEA
Browser" flow, carried over from NK-WP-0001) versus trusting upstream IdP
MFA (e.g. Entra Conditional Access) and reflecting it as assurance
evidence in the token. Require step-up for admin console and
platform-root-sensitive clients. Ensure assurance evidence is carried in
the IAM Profile token so flex-auth can gate privileged actions on it.
## Verify IAM conformance and coexistence
```task
id: NK-WP-0011-T06
state_hub_task_id: "9cb7fd93-c16e-5102-83ce-195b3aa87446"
status: todo
priority: high
```
**IAM Profile conformance & downstream coexistence.** Run IAM Profile
conformance checks against the Keycloak issuer (discovery document, PKCE,
token/claim shape, JWKS, userinfo). Verify an application configured for
the IAM Profile can authenticate against either the KeyCape or the
Keycloak issuer per the T1 selection rule. Use the canonical
`canon/standards/iam-profile_v0.3.md` contract and the executable suite in
`tools/iam-profile-conformance/`. Document per-tenant issuer selection.
## Enforce tenant and platform boundaries
```task
id: NK-WP-0011-T07
state_hub_task_id: "38998c68-29bf-50d2-8c8d-0c75184d5833"
status: todo
priority: high
```
**Recursive tenancy & authorization boundary.** Implement T1's reviewed
realm/tenant topology with platform-root guardrails. If realm-per-tenant is
selected, tenant admins manage only their realm; in every topology they
must not be able to alter IAM Profile semantics, the platform realm,
federation trust, OpenBao platform mounts, or audit retention (per the
flex-auth/Topaz implications in the architecture doc). Confirm flex-auth +
Topaz remains the PDP; if a Keycloak Authorization Services adapter is
used at all, document it as a delegated, non-canonical adapter.
## Prove recovery and audit delivery
```task
id: NK-WP-0011-T08
state_hub_task_id: "e5f44ddc-9645-5f61-b84c-da5ab9cccb6d"
status: todo
priority: medium
```
**Backups, DR, break-glass, monitoring, audit.** Only sanitized declarative
realm configuration belongs in git; keep credential-bearing exports in
protected backup custody; DB backup + restore drill (T2); break-glass admin path disabled-by-default
with alerting on use; Prometheus/Grafana for auth success/failure, MFA
latency, federation errors. Ship Keycloak events to the durable platform
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
state_hub_task_id: "d9dc4292-6b66-5797-9ff9-a1a4a556278e"
```
**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
and receive an IAM Profile-conformant token with tenant + assurance
claims.
- Keycloak secrets originate from OpenBao; none are bootstrapped from
KeePassXC or committed to git.
- flex-auth + Topaz remains the authorization decision point; Keycloak is
not the canonical policy engine.
- Tenant admins cannot cross the platform-root boundary.
- Keycloak audit events land in the durable platform audit sink with
correlation ids, and a DR/break-glass drill has passed.
## Open Questions / Dependencies on Other Repos
- **key-cape**: does coexistence require KeyCape changes, or can both
issuers serve the same IAM Profile unchanged? (EP-NK-001 federation
extension point.)
- **flex-auth**: confirmed claim/decision-envelope contract for tenant +
assurance evidence sourced from a federated token.
- **railiance-platform**: OpenBao must expose a Keycloak auth role / ESO
path before T3; unseal/break-glass story must be ready.
- **IAM Profile spec**: resolved by NK-WP-0012. T6 consumes
`canon/standards/iam-profile_v0.3.md` and
`tools/iam-profile-conformance/`.
## Infrastructure review — 2026-09-28
Keep in backlog until a named enterprise tenant, upstream IdP owner and
concrete federation need justify operating another issuer. No Keycloak
Deployment appears in today's cluster inventory. Realm-per-tenant remains a
proposal to evaluate in T01, not a requirement derived from current topology;
prove its mapping to tenant-engine's canonical identity/lifecycle contract.
The live issuer is `https://kc.coulomb.social`; IAM v0.3 is accepted and v0.4
step-up is proposed. T01/T05/T06 must coordinate with NK-WP-0042 and preserve
existing issuer/subject account bindings, audiences and session/revocation
behavior. Do not infer equivalent assurance from an upstream MFA claim without
a reviewed trust mapping. The single-node cluster and single-instance database
providers offer no automatic HA guarantee. T02/T08 must name backup ownership,
off-host custody and an isolated restore proof. OpenBao access is private via
the platform operator path. Resource placement, packaging and runtime
execution belong to their owners, not this canon repository.
Evidence and cross-plan priorities: [estate review](../history/2026-09-28-open-workplan-infrastructure-review.md).