diff --git a/SCOPE.md b/SCOPE.md index 623d84b..c12180b 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -126,7 +126,7 @@ local diagnostics. - key-cape / NetKingdom SSO: identity source and coarse claims provider; flex-auth consumes the **NetKingdom IAM Profile** - (`~/the-custodian/canon/standards/iam-profile_v0.1.md`). + (`~/net-kingdom/canon/standards/iam-profile_v0.2.md`). - Markitect: first protected-system consumer and policy enforcement point. - Topaz: aligned evaluator. Per ADR-003 the standalone core is shaped to match Topaz's Rego + directory model from day one; the Topaz diff --git a/docs/iam-profile-consumption.md b/docs/iam-profile-consumption.md index 4b1c9b2..f1e81fe 100644 --- a/docs/iam-profile-consumption.md +++ b/docs/iam-profile-consumption.md @@ -1,14 +1,16 @@ # 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`. +Date: 2026-05-22 +Status: Aligned with NetKingdom IAM Profile v0.2; binds the input contract +for the standalone evaluator (FLEX-WP-0002) and every PDP adapter +(FLEX-WP-0004). +Upstream: `~/net-kingdom/canon/standards/iam-profile_v0.2.md`. ## Boundary -The NetKingdom IAM Profile defines the OIDC contract shared across hubs -and services. flex-auth **consumes verified claims**; it does not +The NetKingdom IAM Profile defines the OIDC contract shared across +platform, tenant, service, and agent principals. flex-auth **consumes +verified claims**; it does not verify token signatures, fetch JWKS, or terminate OIDC sessions. Those responsibilities belong upstream: @@ -32,17 +34,25 @@ envelope identical to Markitect's `EnterpriseIdentity` shape: ```yaml issuer: # required subject: # required -principal_type: human | service | emergency +tenant: tenant:platform | tenant: # required +principal_type: human | service | agent audience: [, ...] # required, non-empty authorized_party: preferred_username: # required for humans roles: [, ...] # required, non-empty scopes: [, ...] # required, non-empty -groups: [, ...] # optional; resolved by directory layer +groups: [, ...] # required, may be empty assurance: + level: aal0 | aal1 | aal2 | aal3 | break_glass + methods: [, ...] + mfa: + source: + at: acr: - amr: [, ...] # e.g. pwd, otp, mfa, hwk - mfa: + amr: [, ...] # tolerated provider-native input +agent: + id: + mode: autonomous | delegated directory: groups_claim_present: group_overage: # Microsoft Entra-style group overage @@ -55,7 +65,7 @@ provenance: 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") +## Required Claims (per IAM Profile v0.2 "Core Claims") flex-auth treats the following as hard requirements. Missing any produces a `validation_error` before the request reaches a policy @@ -68,9 +78,13 @@ package. | `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. | +| `tenant` | `tenant` | Required for platform/tenant boundary decisions. | +| `principal_type` | `principal_type` | `human`, `service`, or `agent`; emergency is a role plus `assurance.level=break_glass`. | +| `groups` | `groups` | Required, possibly empty; overage is handled by directory resolvers. | | `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..roles` | `roles` | Union of all three sources. At least one role required. | +| `roles` | `roles` | Canonical role source. At least one role required by current flex-auth policy fixtures. | +| `assurance` | `assurance` | Required normalized evidence object with level, methods, mfa, and source. | +| `preferred_username` | `preferred_username` | Required for `principal_type=human`. Optional for service and agent principals. | ## Recommended Claims @@ -78,7 +92,6 @@ package. | --- | --- | --- | | `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. | @@ -87,29 +100,31 @@ package. 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..roles`. flex-auth - unions all three. +- **Role claim location.** IAM Profile v0.2 makes top-level `roles` + canonical. During migration, flex-auth may also accept Keycloak's + `realm_access.roles` and `resource_access..roles`, but those + are provider-native compatibility inputs. - **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`. +- **MFA signal.** IAM Profile v0.2 uses `assurance.mfa` and + `assurance.level`. Legacy/provider-native `amr` and `acr` are + tolerated as inputs to the normalized assurance object. ## Principal-Type Detection -flex-auth classifies the principal by: +IAM Profile v0.2 supplies `principal_type` directly. flex-auth uses that +claim as normative input. Legacy fixtures may be classified 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`. +3. If agent metadata is present → `agent`. 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). +as a compatibility path. New claim envelopes should not force flex-auth +to infer principal type. ## Group Overage and Freshness @@ -140,7 +155,9 @@ Per IAM Profile §"Local Development Profile": Per IAM Profile §"Human Override and Emergency Access": -- `emergency` is a first-class `principal_type`. +- Emergency access is represented as a human, service, or agent + principal with an `emergency`/`break-glass` role and + `assurance.level: break_glass`. - 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 @@ -154,11 +171,11 @@ 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 +## Compatibility Notes -- 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. +- `roles` is canonical in IAM Profile v0.2. `realm_access.roles` and + `resource_access..roles` remain tolerated provider-native inputs + while Keycloak mappings are updated. +- Workload identity may enter through a documented token-exchange path, + but the normalized envelope still carries `principal_type: service` or + `principal_type: agent`, `tenant`, and `assurance`. diff --git a/examples/claims/README.md b/examples/claims/README.md index c0b020a..8657f7a 100644 --- a/examples/claims/README.md +++ b/examples/claims/README.md @@ -1,6 +1,6 @@ # examples/claims/ -Contract fixtures for the NetKingdom IAM Profile v0.1 claim shapes +Contract fixtures for the NetKingdom IAM Profile v0.2 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 @@ -11,10 +11,10 @@ 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..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. | +| `key-cape-lightweight.yaml` | key-cape lightweight mode | Profile-conformant minimum: single audience, top-level `roles` array, explicit tenant/principal/assurance. | +| `keycloak-heavy.yaml` | Keycloak production | Full variation set: canonical `roles`, provider-native role sources, scope as space-separated string, MFA assurance, multiple audiences. | +| `service-account.yaml` | Either provider | Service account; `principal_type: service`, `service` + `operator` roles, no `preferred_username`, narrow scope. | +| `emergency.yaml` | Either provider | Break-glass human identity; `emergency` role, `assurance.level: break_glass`, short expiry, 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 diff --git a/examples/claims/emergency.yaml b/examples/claims/emergency.yaml index 24280f6..e0de150 100644 --- a/examples/claims/emergency.yaml +++ b/examples/claims/emergency.yaml @@ -2,9 +2,9 @@ # 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. +# Reference: NetKingdom IAM Profile v0.2 "Emergency And Break-Glass +# Access". flex-auth maps the emergency role plus break_glass assurance to +# a `record_emergency` obligation on every decision. iss: https://sso.netkingdom.example/realms/netkingdom sub: f1c4f64e-2c0c-4cda-8c9f-9f3f8f3a2b0e @@ -13,6 +13,8 @@ aud: exp: 1767226200 # iat + 10 minutes; emergency tokens are short-lived iat: 1767225600 auth_time: 1767225595 +tenant: tenant:platform +principal_type: human azp: ops-console preferred_username: ada email: ada@netkingdom.example @@ -20,11 +22,22 @@ scope: openid profile hub:admin roles: - emergency - admin +groups: + - /platform/stewards amr: - pwd - otp - hwk acr: "3" +assurance: + level: break_glass + methods: + - pwd + - otp + - hwk + mfa: true + source: keycloak + at: 1767225595 emergency: incident_id: INC-2026-0042 authorized_by: "team:platform-stewards" diff --git a/examples/claims/key-cape-lightweight.yaml b/examples/claims/key-cape-lightweight.yaml index 4abf963..e145b50 100644 --- a/examples/claims/key-cape-lightweight.yaml +++ b/examples/claims/key-cape-lightweight.yaml @@ -2,8 +2,8 @@ # 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". +# Reference: docs/iam-profile-consumption.md, NetKingdom IAM Profile v0.2 +# "Core Claims" and "Local Development Profile". iss: https://idp.netkingdom.local/keycape sub: user-7f9e2b @@ -11,6 +11,8 @@ aud: - flex-auth exp: 4102444800 # 2100-01-01, kept far-future for stable fixtures iat: 1767225600 # 2026-01-01 +tenant: tenant:platform +principal_type: human preferred_username: ada email: ada@netkingdom.local name: Ada Lovelace @@ -22,3 +24,10 @@ amr: acr: "1" groups: - /markitect/readers +assurance: + level: aal1 + methods: + - pwd + mfa: false + source: key-cape + at: 1767225600 diff --git a/examples/claims/keycloak-heavy.yaml b/examples/claims/keycloak-heavy.yaml index 1e3cdeb..6ba05ee 100644 --- a/examples/claims/keycloak-heavy.yaml +++ b/examples/claims/keycloak-heavy.yaml @@ -14,6 +14,8 @@ aud: exp: 4102444800 iat: 1767225600 auth_time: 1767225590 +tenant: tenant:platform +principal_type: human azp: markitect-cli preferred_username: ada email: ada@netkingdom.example @@ -22,6 +24,8 @@ name: Ada Lovelace given_name: Ada family_name: Lovelace scope: openid profile email hub:read hub:write hub:capability +roles: + - operator realm_access: roles: - default-roles-netkingdom @@ -40,4 +44,12 @@ amr: - pwd - otp acr: "2" +assurance: + level: aal2 + methods: + - pwd + - otp + mfa: true + source: keycloak + at: 1767225590 sid: 4c0a3a8a-3a47-4f2f-8e89-9e5f9b0a0a0a diff --git a/examples/claims/service-account.yaml b/examples/claims/service-account.yaml index 6abb0e1..fa64a3a 100644 --- a/examples/claims/service-account.yaml +++ b/examples/claims/service-account.yaml @@ -3,8 +3,7 @@ # 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". +# Reference: NetKingdom IAM Profile v0.2 "Service Account Flow". iss: https://sso.netkingdom.example/realms/netkingdom sub: svc-markitect-tool-prod @@ -12,9 +11,19 @@ aud: - flex-auth exp: 4102444800 iat: 1767225600 +tenant: tenant:platform +principal_type: service azp: svc-markitect-tool-prod client_id: svc-markitect-tool-prod scope: hub:read hub:capability roles: - service - operator +groups: [] +assurance: + level: aal1 + methods: + - client_secret + mfa: false + source: keycloak + at: 1767225600