generated from coulomb/repo-seed
IAM Profile consumption doc + claim fixtures; close FLEX-WP-0005
Completes FLEX-WP-0005 T05 and closes the Foundations and Topaz Alignment workstream. docs/iam-profile-consumption.md captures flex-auth's input surface against NetKingdom IAM Profile v0.1: - boundary (flex-auth consumes verified claims; upstream layer validates signatures and audiences) - normalized input envelope (matches Markitect's EnterpriseIdentity) - required, recommended, and tolerated claim variations - role-claim location union (top-level / realm_access / resource_access) - scope encoding (string vs array) - principal-type detection (human / service / emergency) - group-overage and freshness expectations - production vs local-development handling examples/claims/ ships five contract fixtures: - key-cape-lightweight.yaml (profile minimum) - keycloak-heavy.yaml (full variation set + MFA) - service-account.yaml (svc-* hub-to-hub) - emergency.yaml (break-glass with incident metadata) - keycloak-group-overage.yaml (Entra-style hasgroups: true) All fixtures parse as valid YAML. They become contract tests for the standalone evaluator (FLEX-WP-0002 P2.4) and the Topaz adapter (FLEX-WP-0004 T01); both code paths must produce identical normalized envelopes for the same fixture. FLEX-WP-0005 workstream marked status=done in this file and completed in the State Hub. FLEX-WP-0002 is now fully unblocked. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
164
docs/iam-profile-consumption.md
Normal file
164
docs/iam-profile-consumption.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# NetKingdom IAM Profile — flex-auth Consumption Surface
|
||||
|
||||
Date: 2026-05-16
|
||||
Status: Draft for FLEX-WP-0005 P5.5; binds the input contract for the
|
||||
standalone evaluator (FLEX-WP-0002) and every PDP adapter (FLEX-WP-0004).
|
||||
Upstream: `~/the-custodian/canon/standards/iam-profile_v0.1.md`.
|
||||
|
||||
## Boundary
|
||||
|
||||
The NetKingdom IAM Profile defines the OIDC contract shared across hubs
|
||||
and services. flex-auth **consumes verified claims**; it does not
|
||||
verify token signatures, fetch JWKS, or terminate OIDC sessions. Those
|
||||
responsibilities belong upstream:
|
||||
|
||||
- **key-cape (lightweight mode)** validates tokens against its local
|
||||
OIDC provider and emits claims that conform to the profile.
|
||||
- **Keycloak (heavy mode)** signs tokens; integration code (e.g.
|
||||
Markitect's `NetKingdomIdentityClaimsAdapter`) validates issuer,
|
||||
audience, signature, expiry, and clock skew before handing claims to
|
||||
flex-auth.
|
||||
|
||||
A flex-auth deployment that exposes a network endpoint MUST be fronted
|
||||
by an identity layer that does the verification. The flex-auth core
|
||||
accepts a normalized claim envelope and is responsible for everything
|
||||
*after* "this caller is authenticated".
|
||||
|
||||
## Input Envelope
|
||||
|
||||
flex-auth's standalone evaluator and adapters consume a normalized
|
||||
envelope identical to Markitect's `EnterpriseIdentity` shape:
|
||||
|
||||
```yaml
|
||||
issuer: <oidc issuer URL> # required
|
||||
subject: <stable subject id> # required
|
||||
principal_type: human | service | emergency
|
||||
audience: [<aud>, ...] # required, non-empty
|
||||
authorized_party: <azp or client_id, optional>
|
||||
preferred_username: <string> # required for humans
|
||||
roles: [<role>, ...] # required, non-empty
|
||||
scopes: [<scope>, ...] # required, non-empty
|
||||
groups: [<group>, ...] # optional; resolved by directory layer
|
||||
assurance:
|
||||
acr: <oidc acr value, optional>
|
||||
amr: [<oidc amr value>, ...] # e.g. pwd, otp, mfa, hwk
|
||||
mfa: <bool — derived from amr>
|
||||
directory:
|
||||
groups_claim_present: <bool>
|
||||
group_overage: <bool> # Microsoft Entra-style group overage
|
||||
claims: { ... } # full original claim map (minus 'groups')
|
||||
provenance:
|
||||
source: claims | jwt | jwt-fixture
|
||||
verified_signature: <bool>
|
||||
```
|
||||
|
||||
This is the envelope every check API call receives, regardless of
|
||||
which upstream identity provider produced the token.
|
||||
|
||||
## Required Claims (per IAM Profile §"Required Claims")
|
||||
|
||||
flex-auth treats the following as hard requirements. Missing any
|
||||
produces a `validation_error` before the request reaches a policy
|
||||
package.
|
||||
|
||||
| Claim | flex-auth field | Notes |
|
||||
| --- | --- | --- |
|
||||
| `iss` | `issuer` | Must match the deployment's expected issuer; production rejects local-dev issuers (`localhost`, `127.0.0.1`, `.local`, `dev.local`). |
|
||||
| `sub` | `subject` | Stable identifier; not a username. |
|
||||
| `aud` | `audience` | Must include the flex-auth instance or the protected system. |
|
||||
| `exp` | (validated upstream) | flex-auth tolerates ≤60s clock skew per profile §"Token Lifecycle". |
|
||||
| `iat` | (validated upstream) | Same. |
|
||||
| `scope` or `scp` | `scopes` | At least one scope required. Empty scope is a hard fail. |
|
||||
| `preferred_username` | `preferred_username` | Required for `principal_type=human`. Optional for service accounts. |
|
||||
| `roles` or `realm_access.roles` or `resource_access.<client>.roles` | `roles` | Union of all three sources. At least one role required. |
|
||||
|
||||
## Recommended Claims
|
||||
|
||||
| Claim | flex-auth field | Use |
|
||||
| --- | --- | --- |
|
||||
| `email` | `claims.email` | Contact identity; **never** used for authorization decisions. |
|
||||
| `name` | `claims.name` | Display only. |
|
||||
| `groups` | `groups` (after resolution) | Authorization input; subject to freshness/overage. |
|
||||
| `azp` | `authorized_party` | Distinguishes service-account client from impersonating client. |
|
||||
| `acr` | `assurance.acr` | Authentication context class; gates high-trust scopes. |
|
||||
| `amr` | `assurance.amr` | Authentication methods; `otp`/`mfa`/`hwk` lift `assurance.mfa` to true. |
|
||||
|
||||
## Tolerated Variations
|
||||
|
||||
flex-auth normalizes — protected systems never see the variation.
|
||||
|
||||
- **Role claim location.** Three OIDC providers ship roles in three
|
||||
places: top-level `roles`, Keycloak's `realm_access.roles`, and
|
||||
Keycloak's per-client `resource_access.<client>.roles`. flex-auth
|
||||
unions all three.
|
||||
- **Scope encoding.** `scope` (space-separated string) and `scp`
|
||||
(array) both accepted; both produce the same `scopes` array.
|
||||
- **Audience encoding.** `aud` as a single string or as an array;
|
||||
flex-auth always normalizes to an array.
|
||||
- **MFA signal.** Either an explicit `mfa: true` claim or any of
|
||||
`otp`/`mfa`/`hwk` in `amr` produces `assurance.mfa = true`.
|
||||
|
||||
## Principal-Type Detection
|
||||
|
||||
flex-auth classifies the principal by:
|
||||
|
||||
1. If `client_id` is set and `service` is in `roles` → `service`.
|
||||
2. If `azp` starts with `svc-` or `service` is in `roles` → `service`.
|
||||
3. If `emergency` is in `roles` → `emergency`.
|
||||
4. Otherwise → `human`.
|
||||
|
||||
This matches Markitect's `NetKingdomIdentityClaimsAdapter._principal_type`
|
||||
and follows IAM Profile §"Hub-to-Hub Service Account Pattern" (service
|
||||
accounts named `svc-*` and carrying the `service` role).
|
||||
|
||||
## Group Overage and Freshness
|
||||
|
||||
Microsoft Entra and Keycloak both clip the `groups` claim once a
|
||||
threshold is reached; the token then carries `hasgroups: true` (Entra)
|
||||
or `_claim_names.groups` (also Entra). flex-auth's directory layer is
|
||||
responsible for resolving the full set via Graph/SCIM/Keycloak admin
|
||||
API; the claim envelope carries `directory.group_overage = true` so
|
||||
policy packages can decide whether to fail-closed or accept the
|
||||
partial set with an `audit_only` outcome.
|
||||
|
||||
Group freshness is tracked at the directory-resolver layer (out of
|
||||
scope for this document; see FLEX-WP-0004 T05).
|
||||
|
||||
## Production vs Local Development
|
||||
|
||||
Per IAM Profile §"Local Development Profile":
|
||||
|
||||
- Local-development issuers (`localhost`, `127.0.0.1`, hostnames
|
||||
ending in `.local`, `dev.local`) are rejected when
|
||||
`environment=production` is set in the request context.
|
||||
- A development token marked clearly through issuer/audience is
|
||||
accepted in non-production environments.
|
||||
- The local-development path exists to keep flex-auth useful before
|
||||
Keycloak is wired in; it never weakens production rules.
|
||||
|
||||
## Emergency Principals
|
||||
|
||||
Per IAM Profile §"Human Override and Emergency Access":
|
||||
|
||||
- `emergency` is a first-class `principal_type`.
|
||||
- Every decision involving an emergency principal MUST record a
|
||||
`record_emergency` obligation in the decision envelope.
|
||||
- Policy packages MAY allow emergency principals; flex-auth's audit
|
||||
layer ensures the action is durable regardless.
|
||||
|
||||
## Reference Implementation
|
||||
|
||||
Markitect's `NetKingdomIdentityClaimsAdapter` (at
|
||||
`markitect-tool/src/markitect_tool/policy/enterprise.py`) implements
|
||||
the validation steps above in Python. flex-auth's Go implementation
|
||||
(FLEX-WP-0002 P2.4) mirrors its behavior and stays in sync via
|
||||
contract tests against the fixtures in `examples/claims/`.
|
||||
|
||||
## Open Items
|
||||
|
||||
- Whether `roles` becomes canonical and `realm_access.roles` becomes
|
||||
legacy is still listed as an open question in IAM Profile v0.1. As
|
||||
of 2026-05-16 flex-auth normalizes both with no preference.
|
||||
- `Workload identity` (Kubernetes service-account tokens, GCP/AWS
|
||||
workload-identity federation) is not yet in the IAM Profile.
|
||||
flex-auth's service-account handling is currently OIDC-only.
|
||||
23
examples/claims/README.md
Normal file
23
examples/claims/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# examples/claims/
|
||||
|
||||
Contract fixtures for the NetKingdom IAM Profile v0.1 claim shapes
|
||||
flex-auth must accept. Each file is the *raw verified claim map* as
|
||||
flex-auth receives it from the upstream identity layer (key-cape or
|
||||
Keycloak); flex-auth's normalization produces the same
|
||||
`EnterpriseIdentity`-shaped envelope for all of them.
|
||||
|
||||
See `docs/iam-profile-consumption.md` for the full consumption
|
||||
surface.
|
||||
|
||||
| Fixture | Provider | Demonstrates |
|
||||
| --- | --- | --- |
|
||||
| `key-cape-lightweight.yaml` | key-cape lightweight mode | Profile-conformant minimum: single audience, top-level `roles` array, single-factor `amr=pwd`. |
|
||||
| `keycloak-heavy.yaml` | Keycloak production | Full variation set: `realm_access.roles` + `resource_access.<client>.roles`, scope as space-separated string, MFA via `amr=otp`, multiple audiences. |
|
||||
| `service-account.yaml` | Either provider | Hub-to-hub service account; `service` + `operator` roles, no `preferred_username`, narrow scope. |
|
||||
| `emergency.yaml` | Either provider | Break-glass human identity; `emergency` role, short expiry, hardware MFA, audit-trail metadata in an `emergency` claim. |
|
||||
| `keycloak-group-overage.yaml` | Entra/Keycloak | Group-claim overage signal (`hasgroups: true`); flex-auth's directory resolver fetches the full set. |
|
||||
|
||||
These fixtures are loaded by the standalone evaluator's contract tests
|
||||
(`FLEX-WP-0002 P2.4`) and by the Topaz adapter's contract tests
|
||||
(`FLEX-WP-0004 T01`). Both code paths MUST produce identical
|
||||
normalized envelopes for the same fixture.
|
||||
31
examples/claims/emergency.yaml
Normal file
31
examples/claims/emergency.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
# Claim envelope for an emergency (break-glass) human principal. Short
|
||||
# expiry, emergency role, requires MFA per the profile, and triggers
|
||||
# durable audit recording on every flex-auth decision that involves it.
|
||||
#
|
||||
# Reference: NetKingdom IAM Profile v0.1 §"Human Override and Emergency
|
||||
# Access". flex-auth maps this to principal_type=emergency and emits a
|
||||
# `record_emergency` obligation on every decision.
|
||||
|
||||
iss: https://sso.netkingdom.example/realms/netkingdom
|
||||
sub: f1c4f64e-2c0c-4cda-8c9f-9f3f8f3a2b0e
|
||||
aud:
|
||||
- flex-auth
|
||||
exp: 1767226200 # iat + 10 minutes; emergency tokens are short-lived
|
||||
iat: 1767225600
|
||||
auth_time: 1767225595
|
||||
azp: ops-console
|
||||
preferred_username: ada
|
||||
email: ada@netkingdom.example
|
||||
scope: openid profile hub:admin
|
||||
roles:
|
||||
- emergency
|
||||
- admin
|
||||
amr:
|
||||
- pwd
|
||||
- otp
|
||||
- hwk
|
||||
acr: "3"
|
||||
emergency:
|
||||
incident_id: INC-2026-0042
|
||||
authorized_by: "team:platform-stewards"
|
||||
reason: "credential rotation playbook step 4"
|
||||
24
examples/claims/key-cape-lightweight.yaml
Normal file
24
examples/claims/key-cape-lightweight.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
# Claim envelope a key-cape (lightweight mode) deployment emits for an
|
||||
# authenticated human user. Profile-conformant minimum: required claims
|
||||
# only, single audience, simple roles list, OIDC standard amr values.
|
||||
#
|
||||
# Reference: docs/iam-profile-consumption.md, NetKingdom IAM Profile v0.1
|
||||
# §"Required Claims" and §"Local Development Profile".
|
||||
|
||||
iss: https://idp.netkingdom.local/keycape
|
||||
sub: user-7f9e2b
|
||||
aud:
|
||||
- flex-auth
|
||||
exp: 4102444800 # 2100-01-01, kept far-future for stable fixtures
|
||||
iat: 1767225600 # 2026-01-01
|
||||
preferred_username: ada
|
||||
email: ada@netkingdom.local
|
||||
name: Ada Lovelace
|
||||
scope: openid profile hub:read
|
||||
roles:
|
||||
- viewer
|
||||
amr:
|
||||
- pwd
|
||||
acr: "1"
|
||||
groups:
|
||||
- /markitect/readers
|
||||
26
examples/claims/keycloak-group-overage.yaml
Normal file
26
examples/claims/keycloak-group-overage.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
# Claim envelope when the token-side `groups` list has been clipped by
|
||||
# the IdP. Both Microsoft Entra and Keycloak signal this differently;
|
||||
# this fixture shows the Entra-style `hasgroups: true` flag. flex-auth
|
||||
# sets directory.group_overage = true and depends on the directory
|
||||
# resolver (FLEX-WP-0004 T05) to fetch the full set.
|
||||
#
|
||||
# Reference: docs/iam-profile-consumption.md §"Group Overage and
|
||||
# Freshness".
|
||||
|
||||
iss: https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0
|
||||
sub: f1c4f64e-2c0c-4cda-8c9f-9f3f8f3a2b0e
|
||||
aud:
|
||||
- flex-auth
|
||||
exp: 4102444800
|
||||
iat: 1767225600
|
||||
preferred_username: ada
|
||||
name: Ada Lovelace
|
||||
scope: openid profile hub:read
|
||||
roles:
|
||||
- viewer
|
||||
hasgroups: true
|
||||
_claim_names:
|
||||
groups: src1
|
||||
_claim_sources:
|
||||
src1:
|
||||
endpoint: https://graph.microsoft.com/v1.0/users/f1c4f64e/getMemberObjects
|
||||
43
examples/claims/keycloak-heavy.yaml
Normal file
43
examples/claims/keycloak-heavy.yaml
Normal file
@@ -0,0 +1,43 @@
|
||||
# Claim envelope a Keycloak (heavy mode) deployment emits for an
|
||||
# authenticated human user with MFA. Demonstrates the full set of
|
||||
# variations flex-auth must normalize: roles in realm_access AND
|
||||
# resource_access, scope as space-separated string, multiple audiences,
|
||||
# enriched assurance via amr=otp.
|
||||
#
|
||||
# Reference: docs/iam-profile-consumption.md §"Tolerated Variations".
|
||||
|
||||
iss: https://sso.netkingdom.example/realms/netkingdom
|
||||
sub: f1c4f64e-2c0c-4cda-8c9f-9f3f8f3a2b0e
|
||||
aud:
|
||||
- flex-auth
|
||||
- markitect-tool
|
||||
exp: 4102444800
|
||||
iat: 1767225600
|
||||
auth_time: 1767225590
|
||||
azp: markitect-cli
|
||||
preferred_username: ada
|
||||
email: ada@netkingdom.example
|
||||
email_verified: true
|
||||
name: Ada Lovelace
|
||||
given_name: Ada
|
||||
family_name: Lovelace
|
||||
scope: openid profile email hub:read hub:write hub:capability
|
||||
realm_access:
|
||||
roles:
|
||||
- default-roles-netkingdom
|
||||
- operator
|
||||
resource_access:
|
||||
flex-auth:
|
||||
roles:
|
||||
- reader
|
||||
markitect-tool:
|
||||
roles:
|
||||
- editor
|
||||
groups:
|
||||
- /platform/architecture
|
||||
- /markitect/readers
|
||||
amr:
|
||||
- pwd
|
||||
- otp
|
||||
acr: "2"
|
||||
sid: 4c0a3a8a-3a47-4f2f-8e89-9e5f9b0a0a0a
|
||||
20
examples/claims/service-account.yaml
Normal file
20
examples/claims/service-account.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
# Claim envelope for a hub-to-hub service account (client_credentials
|
||||
# grant). Profile-required `service` role, scoped tightly to the
|
||||
# operation it performs. No preferred_username (service identities are
|
||||
# named after the service and environment per the profile).
|
||||
#
|
||||
# Reference: NetKingdom IAM Profile v0.1 §"Service Account Flow" and
|
||||
# §"Hub-to-Hub Service Account Pattern".
|
||||
|
||||
iss: https://sso.netkingdom.example/realms/netkingdom
|
||||
sub: svc-markitect-tool-prod
|
||||
aud:
|
||||
- flex-auth
|
||||
exp: 4102444800
|
||||
iat: 1767225600
|
||||
azp: svc-markitect-tool-prod
|
||||
client_id: svc-markitect-tool-prod
|
||||
scope: hub:read hub:capability
|
||||
roles:
|
||||
- service
|
||||
- operator
|
||||
@@ -3,7 +3,7 @@ id: FLEX-WP-0005
|
||||
type: workplan
|
||||
title: "Foundations and Topaz Alignment"
|
||||
domain: netkingdom
|
||||
status: todo
|
||||
status: done
|
||||
owner: flex-auth
|
||||
topic_slug: flex-auth
|
||||
planning_priority: P0
|
||||
@@ -154,7 +154,7 @@ both `FLEX-WP-0002 P2.1` and `FLEX-WP-0004 T001`.
|
||||
|
||||
```task
|
||||
id: FLEX-WP-0005-T005
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "b31dab7b-e72c-4abe-b6d5-f5875fd0c25a"
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user