flex-auth/docs/iam-profile-consumption.md
tegwick 39a0034fcc Close B2 and finish FLEX-WP-0030; record tenant-engine's and key-cape's answers.
B2: key-cape (Tooling), net-kingdom (Taxonomy) and ops-mason (Staff) now declare
in their own files. Verified by the survey rather than taken on report: 14 of 14
counterparts declared, 0 undeclared. ops-mason sent no reply and needed none —
under §11 the file is the declaration. Every T04 finding is answered, so
FLEX-WP-0030 is finished. B5's residual ping belongs to FLEX-WP-0020.

key-cape stated the identity boundary from its side for the first time and
cautioned that principal_type must not be read as authentication-derived until
GH-DEC-2026-013/-016 §5 is answered. Checked against every published package:
only the two informed-decision packages gate on a human subject, and both also
require principal_type_source == "authentication-derived", which informed-decision
derives from the verified code-flow MFA event rather than from key-cape's claim.
No change needed; the assessment and a warning about the legacy "otherwise ->
human" fallback are in docs/iam-profile-consumption.md.

tenant-engine confirmed FLEX-DEC-2026-016's reading of commitment (b): the
fixed-record exclusion is consistent with it. Recorded under point 3; v3 stands.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 28468@bnt-lap001
Assistant-Session: c76569b2-6056-4dad-aea4-49cd7a018f5d
2026-09-21 22:57:56 +02:00

9.2 KiB

NetKingdom IAM Profile — flex-auth Consumption Surface

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 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:

  • 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:

issuer: <oidc issuer URL>          # required
subject: <stable subject id>       # required
tenant: tenant:platform | tenant:<id> # required
principal_type: human | service | agent
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>, ...]             # required, may be empty
assurance:
  level: aal0 | aal1 | aal2 | aal3 | break_glass
  methods: [<method>, ...]
  mfa: <bool>
  source: <identity or MFA evidence source>
  at: <unix timestamp, optional>
  acr: <oidc acr value, optional>
  amr: [<oidc amr value>, ...]     # tolerated provider-native input
agent:
  id: <agent id, optional>
  mode: autonomous | delegated
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 v0.2 "Core 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.
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.
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.
Claim flex-auth field Use
email claims.email Contact identity; never used for authorization decisions.
name claims.name Display only.
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. IAM Profile v0.2 makes top-level roles canonical. During migration, flex-auth may also accept Keycloak's realm_access.roles and resource_access.<client>.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. 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

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 rolesservice.
  2. If azp starts with svc- or service is in rolesservice.
  3. If agent metadata is present → agent.
  4. Otherwise → human.

This matches Markitect's NetKingdomIdentityClaimsAdapter._principal_type as a compatibility path. New claim envelopes should not force flex-auth to infer principal type.

principal_type carries no route (key-cape, 2026-09-21). Whether key-cape distinguishes a registration-asserted principal_type from an authentication-derived one is open (GH-DEC-2026-013 / -016 §5). Until it is answered, key-cape's own instruction is: do not read principal_type as authentication-derived. flex-auth may classify with it; no rule may discharge a human-in-the-loop control on it alone.

Checked against every published package on that date. Only informed-decision-t03 and informed-decision-sitting gate on a human subject, and both also require principal_type_source == "authentication-derived". That value is not key-cape's principal_type: informed-decision derives it from the verified code-flow MFA event itself (informed_decision/oidc.py, refused unless aal2, pwd+otp, source key-cape, fresh at). So neither package rests on the claim key-cape cautioned against, and no change was needed.

The legacy fallback above ends in "Otherwise → human", which defaults to the strongest reading. It is a compatibility path for classification only; a rule that needs humanness must require a routed source, as the two packages above do, and never this default.

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 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 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/.

Compatibility Notes

  • roles is canonical in IAM Profile v0.2. realm_access.roles and resource_access.<client>.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.