diff --git a/SCOPE.md b/SCOPE.md index 9a125fa..9ffecc4 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -21,7 +21,7 @@ NetKingdom is a self-optimizing security platform for Kubernetes-based IT infras ## In Scope - NetKingdom IAM Profile specification (versioned OIDC/PKCE contract; - canonical spec: `canon/standards/iam-profile_v0.2.md`) + canonical spec: `canon/standards/iam-profile_v0.3.md`) - SSO/MFA Platform: Keycloak with LDAP/Entra federation, enterprise identity (NK-WP-0001, finished) - Local Identity: file-based user store + minimal OIDC server for bootstrap phase (NK-WP-0002, finished) - User Engine Boundary Contract: source-of-truth, membership, diff --git a/canon/standards/iam-profile_v0.3.md b/canon/standards/iam-profile_v0.3.md new file mode 100644 index 0000000..546bf49 --- /dev/null +++ b/canon/standards/iam-profile_v0.3.md @@ -0,0 +1,473 @@ +--- +id: netkingdom-iam-profile +type: standard +title: "NetKingdom IAM Profile v0.3" +domain: netkingdom +status: accepted +version: "0.3" +created: "2026-07-23" +updated: "2026-07-23" +scope: core-platform +supersedes: + - canon/standards/iam-profile_v0.2.md +adr: + - docs/adr/ADR-0011-iam-profile-ownership-and-version-governance.md + - docs/adr/ADR-0013-tenant-onboarding-grouping-taxonomy.md + - docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md +--- + +# NetKingdom IAM Profile v0.3 + +> Minor version. Per ADR-0011's versioning rule, this adds an optional claim +> and clarifies non-normative guidance — no required claim, validation rule, +> or previously-issued token is invalidated. Existing v0.2 implementations +> remain conformant; `tenant_roles` and the revised Tenant Claim guidance are +> additive. + +## Purpose + +The NetKingdom IAM Profile is the provider-neutral OIDC contract that +identity implementations issue and applications consume. + +It defines: + +- OIDC discovery and endpoint requirements; +- Authorization Code + PKCE for human login; +- service-account and workload identity token requirements; +- human, service, and agent principal representation; +- tenant, tenant-grouping, and platform-boundary claims; +- tenant capability roles and their carrying mechanism; +- explicit assurance evidence; +- the identity-to-authorization claim contract consumed by flex-auth; +- local-development and emergency-access behavior; +- executable conformance expectations. + +Applications target this profile, not a concrete identity provider. +key-cape is the lightweight implementation. Keycloak is the expanded-mode +implementation. Both are interchangeable at the application and +authorization boundary when they conform to this document. + +## Ownership + +NetKingdom owns the core/platform profile. See ADR-0011. + +Downstream systems may define extension scopes, roles, resource names, +and tenant policy vocabularies. Those extensions are not part of the core +profile unless a future version explicitly adopts them. Extension +vocabularies must map back to the core claims in this document before +flex-auth or applications consume them. + +## Design Principles + +- Consumers trust signed OIDC tokens, not provider-specific sessions. +- Identity providers assert identity and authentication evidence; they do + not make final resource authorization decisions. +- The same profile works in lightweight key-cape mode and expanded + Keycloak mode. +- Tenancy is explicit. `tenant:platform` is distinct from tenant planes + such as `tenant:coulomb` and from later tenants grouped per ADR-0013. +- A tenant's onboarding **grouping** (ADR-0013) and its **capability + roles** (ADR-0014) are independent axes. Neither is encoded in the other; + a tenant's roles may change without renaming its identifier. +- Human, service, and agent principals are distinguishable. +- Assurance evidence is explicit enough for flex-auth policy. +- Local-development issuers are useful but never accepted by production. +- Emergency access is auditable, time-bounded, and reviewable. + +## Discovery Contract + +Every IAM Profile implementation MUST expose OIDC discovery at: + +```text +GET /.well-known/openid-configuration +``` + +The discovery response MUST include: + +| Field | Requirement | +| --- | --- | +| `issuer` | Exact issuer identifier used in tokens | +| `authorization_endpoint` | Required for human Authorization Code + PKCE | +| `token_endpoint` | Required for token exchange and service accounts | +| `jwks_uri` | Required for signature validation | +| `userinfo_endpoint` | Required when userinfo is supported by the flow | +| `scopes_supported` | MUST include `openid`; SHOULD include `profile` and `email` | +| `response_types_supported` | MUST include `code` | +| `grant_types_supported` | MUST include `authorization_code`; MUST include `client_credentials` or a documented workload-token exchange for service identities | +| `id_token_signing_alg_values_supported` | MUST include the implementation signing algorithm; RS256 is required for v0.2+ conformance | +| `code_challenge_methods_supported` | MUST include `S256` | + +The response SHOULD include `end_session_endpoint` where logout is +supported and `claims_supported` listing the core claims below. + +Consumers MUST discover endpoints and key material from the issuer +metadata instead of hardcoding provider-specific paths. + +## Required Flows + +### Human Interactive Flow + +Human users authenticate with Authorization Code + PKCE. + +Required properties: + +- PKCE with `S256` is mandatory for browser and CLI clients. +- Implicit flow is not part of the profile. +- MFA or equivalent strong assurance is mandatory for privileged, + destructive, platform-root, and emergency access in production. +- Access tokens are short-lived. +- Refresh tokens are allowed only for trusted clients with explicit + rotation and revocation. + +### Service Account Flow + +Service-to-service traffic uses client credentials or a deployment's +documented workload identity token-exchange equivalent. + +Required properties: + +- Service subjects are stable and named for service plus environment. +- Secrets or workload credentials are delivered through the + credential-management standard, not plaintext configuration. +- Tokens include an audience that identifies the target service. +- Tokens carry `principal_type: service`. +- Service accounts receive only required scopes and roles. +- Credentials are rotated and never shared between environments. + +### Agent Principal Flow + +Agents are automation principals that may act autonomously or under +delegated authority. + +Required properties: + +- Tokens carry `principal_type: agent`. +- Tokens include an `agent` object with `id` and `mode`. +- `agent.mode` is `autonomous` or `delegated`. +- Delegated agents MUST identify the delegating actor using `actor_sub` + or an equivalent `act.sub` claim. +- Agent tokens MUST carry the tenant they operate within. +- Agent tokens MUST include assurance evidence for both the agent + credential and any delegated human authority when policy needs it. + +## Core Claims + +Access tokens accepted by production consumers MUST provide the following +claims after provider mapping or normalization: + +| Claim | Type | Meaning | +| --- | --- | --- | +| `iss` | string | OIDC issuer URL or issuer identifier | +| `sub` | string | Stable subject identifier unique within `iss` | +| `aud` | string or array | Intended audience; MUST include the receiving service | +| `exp` | number | Expiry timestamp | +| `iat` | number | Issued-at timestamp | +| `nbf` | number | Not-before timestamp, recommended for production tokens | +| `jti` | string | Token identifier, recommended for audit and replay controls | +| `tenant` | string | Tenant identifier such as `tenant:platform` or `tenant:friendly:binky` | +| `principal_type` | string | `human`, `service`, or `agent` | +| `groups` | array | Group memberships, possibly empty | +| `roles` | array | Coarse **identity** roles for the subject, possibly empty | +| `scope` or `scp` | string or array | Granted OAuth scopes | +| `assurance` | object | Authentication and credential assurance evidence | + +Recommended human claims: + +| Claim | Meaning | +| --- | --- | +| `preferred_username` | Human-readable username | +| `email` | Contact identity | +| `name` | Display name | + +Recommended service claims: + +| Claim | Meaning | +| --- | --- | +| `azp` or `client_id` | Authorized client/service identifier | +| `service` | Object naming the service and environment | + +Recommended agent claims: + +| Claim | Meaning | +| --- | --- | +| `agent.id` | Stable agent identifier | +| `agent.mode` | `autonomous` or `delegated` | +| `actor_sub` or `act.sub` | Delegating subject for delegated agents | + +Optional claims (new in v0.3): + +| Claim | Type | Meaning | +| --- | --- | --- | +| `tenant_roles` | array | Cached tenant **capability** roles (`PLTF`/`IAM`/`VEN`/`CUS`), possibly empty. See "Tenant Roles" below — this is a point-in-time cache, not the authoritative source. | + +### Role Claim + +The canonical **subject**-level role claim is `roles`, an array of strings. +This is distinct from `tenant_roles` (below) — a subject's own coarse +identity roles are not the same fact as which capability roles the +subject's tenant holds. + +Expanded-mode Keycloak deployments may also expose provider-native roles +such as `realm_access.roles`, but conforming tokens consumed by flex-auth +or applications MUST either emit `roles` directly or pass through a +normalizing adapter that produces `roles`. + +### Scope Vocabulary + +The core profile defines only OAuth/OIDC base scopes: + +| Scope | Meaning | +| --- | --- | +| `openid` | Required for OIDC login | +| `profile` | Basic profile claims | +| `email` | Email claim where appropriate | +| `offline_access` | Refresh-token capable access where explicitly allowed | + +Hub-, application-, and resource-specific scopes such as `hub:*`, +`ops:*`, `fin:*`, or storage actions are downstream extensions. They are +valid only when the consuming system defines them and maps them to +flex-auth resource/action semantics. + +## Tenant Claim + +`tenant` is required for every token accepted by profile consumers. + +Tenant identifiers follow `tenant::`, where `` is +one of the taxonomy ratified by ADR-0013: + +```text +trial - test/trial/showcase tenants only +friendly - known, easily reached, tolerant of experimentation/instability +single - one-person business entities (freelance consultants) +small - up to 10 employees at time of onboarding (attoo) +medium - up to 100 employees (attoo) +large - up to 1000 employees (attoo) +enterprise - 1001+ employees (attoo) +consumer - private individuals +family - a legal family +community - a non-legal group of people +association - a legal association of people +agentic - financially enabled AI entities +``` + +`tenant:platform` and `tenant:coulomb` remain **reserved, ungrouped** +identifiers outside this taxonomy: `tenant:platform` is the platform +control-plane tenant, not a business entity being onboarded; `tenant:coulomb` +is the first internal/reference tenant established by ADR-0006, predating +this taxonomy. Tenant administration for `tenant:coulomb` or any grouped +tenant must never imply platform-root authority. + +Subjects may have access to multiple tenants, but a token used for a +request MUST identify the tenant context for that request. If a client +needs to switch tenant context, it obtains a new token or uses an +approved token-exchange flow that records the target tenant. + +The grouping segment is **onboarding-risk / entity-shape** classification +only. It does not gate which capability roles (below) a tenant may hold — +see Tenant Roles. + +## Tenant Roles + +Tenant **capability roles** are a separate fact from the grouping above and +from the subject-level `roles` claim: `PLTF`, `IAM`, `VEN`, `CUS` +(ratified by ADR-0014), non-exclusive — a tenant may hold several +simultaneously, and holding one does not require or restrict any grouping. + +**Source of truth:** `tenant-engine` +(`canon/standards/tenant-engine-boundary-contract_v0.1.md`), not this +profile and not any token. `tenant-engine` records role grants/revocations, +their link (if any) to a plan/subscription, and emits domain events on +change. + +**Carrying mechanism — hybrid, not claim-only:** + +- `key-cape` (or Keycloak) MAY stamp a cached `tenant_roles` claim onto an + issued token at issuance time, sourced from `tenant-engine`. +- Consumers MAY trust the cached claim for ordinary, non-privileged + decisions. +- Consumers **MUST NOT** trust the cached claim for privileged, destructive, + platform-root, secret, credential-vending, or otherwise `assurance.level + >= aal2`-class decisions. Those decisions **MUST** query `tenant-engine` + live for current role state before authorizing the action. +- This bounds staleness for ordinary actions to the issuing token's + lifetime (5-30 minutes for service/agent tokens, see Token Lifecycle) + while guaranteeing freshness exactly where a stale grant (e.g. `VEN` + surviving a plan cancellation) would matter most. + +`trial`-grouped tenants may hold any capability role without restriction — +the grouping exists to showcase, test, and explore every role. Safety for +`trial` tenants is enforced through `tenant-engine`-owned resource +guardrails (spend limits, entity/action counts — reserved, not yet +specified), not through role gating. + +## Assurance Evidence + +The canonical assurance claim is `assurance`. + +It is an object with these fields: + +| Field | Type | Meaning | +| --- | --- | --- | +| `level` | string | `aal0`, `aal1`, `aal2`, `aal3`, or `break_glass` | +| `methods` | array | Authentication methods, e.g. `pwd`, `otp`, `webauthn`, `client_secret`, `workload_identity`, `upstream_mfa` | +| `mfa` | boolean | Whether the authentication included multiple factors or equivalent upstream evidence | +| `source` | string | Provider of the evidence, e.g. `key-cape`, `keycloak`, `privacyidea`, `entra`, `local-identity` | +| `at` | number | Authentication time, recommended | + +Level meanings: + +| Level | Meaning | +| --- | --- | +| `aal0` | Local/dev or unauthenticated bootstrap evidence; never production privileged | +| `aal1` | Single-factor or service credential evidence | +| `aal2` | MFA or equivalent strong upstream assurance | +| `aal3` | Phishing-resistant or hardware-backed assurance | +| `break_glass` | Time-bounded emergency access with post-event review | + +Privileged, destructive, platform-root, secret, credential-vending, and +emergency flows require `aal2` or stronger unless a policy explicitly +permits a narrower service or workload identity path. Emergency access +MUST use `break_glass` and short token lifetimes. This is the same +threshold class that gates when `tenant_roles` MUST be re-validated live +against `tenant-engine` rather than trusted from the token — see Tenant +Roles. + +Provider-native claims such as `acr` and `amr` may be present, but +consumers use `assurance` as the normalized profile claim. + +## Identity To Authorization Contract + +flex-auth consumes IAM Profile tokens as normative identity input. +flex-auth MUST NOT re-derive identity, tenant, group, role, or assurance +facts from provider-specific session state. + +The profile guarantees these inputs for authorization decision envelopes: + +| Decision input | Source claim | +| --- | --- | +| Subject | `sub` | +| Issuer | `iss` | +| Audience | `aud` | +| Tenant | `tenant` | +| Principal type | `principal_type` | +| Groups | `groups` | +| Subject roles | `roles` | +| Tenant capability roles | `tenant_roles` (cached); `tenant-engine` live lookup required for high-stakes decisions | +| Scopes | `scope` or `scp` | +| Assurance | `assurance` | +| Authorized client | `azp` or `client_id`, where present | +| Agent/delegation context | `agent`, `actor_sub`, or `act`, where present | +| Token lifetime/audit ids | `iat`, `nbf`, `exp`, `jti`, where present | + +Authorization decisions are made by flex-auth and its delegated PDP +adapters. Identity providers may assert roles or scopes, but those claims +are inputs to policy, not final permission to act on a resource. + +## Token Lifecycle + +Recommended production defaults: + +| Token | Lifetime | Notes | +| --- | --- | --- | +| Human access token | 5-15 minutes | Short-lived bearer token | +| Refresh token | 8-12 hours | Rotated and revoked on logout or suspicion | +| Service token | 5-30 minutes | Reissued by client credentials or workload identity | +| Agent token | 5-30 minutes | Shorter when delegated or platform-scoped | +| Emergency token | 5-15 minutes | Requires incident/review record | + +Consumers MUST reject expired tokens and tokens with invalid issuer, +audience, signature, `nbf`, or algorithm. Clock skew tolerance SHOULD be +small, normally no more than 60 seconds. + +JWKS material may be cached, but consumers MUST tolerate key rotation by +refreshing JWKS when a token uses an unknown `kid`. + +## Local Development Profile + +A local file-backed provider may be used for development, tests, and +bootstrap contexts where the full platform is unavailable. + +It MUST: + +- expose OIDC discovery; +- issue signed JWTs; +- support deterministic test users and service accounts; +- use local-only issuer URLs or a clearly local issuer identifier; +- mark tokens as local/development through issuer, audience, or + assurance evidence; +- be rejected by production consumers. + +Production consumers MUST reject: + +- issuer `local-identity`; +- `http://` issuers; +- loopback issuers such as `localhost` or `127.0.0.1`; +- tokens with `assurance.level: aal0`; +- tokens where the environment marks the issuer as local/dev. + +## Emergency And Break-Glass Access + +Emergency access is allowed only as a break-glass path. + +Requirements: + +- Emergency identities are disabled by default. +- Activation requires an incident, decision, or human-recorded review + reference. +- Tokens are short-lived and carry the `emergency` role. +- Tokens carry `assurance.level: break_glass`. +- Every emergency action emits an audit/progress/incident event. +- Emergency access is reviewed after use and then disabled again. + +Emergency access MUST NOT bypass audit logging or flex-auth policy. + +## Conformance + +An implementation conforms to IAM Profile v0.3 when it passes the +executable conformance suite in: + +```text +tools/iam-profile-conformance/ +``` + +The suite validates: + +- discovery document completeness; +- PKCE `S256` advertisement and rejection of authorization requests that + omit a code challenge; +- JWKS structure and key ids; +- token issuer, audience, expiry, `nbf`, `iat`, and RS256 signature; +- tenant, principal type, groups, roles, scopes, and assurance claim + shape; +- `tenant_roles` claim shape when present (array of ratified role strings); +- agent and delegated-agent claim shape; +- local-development issuer rejection in production mode. + +Conformance must be runnable against both key-cape lightweight issuers +and Keycloak expanded-mode issuers. Implementations may add provider +adapters, but the token consumed by applications and flex-auth must match +the core claim contract above. `tenant_roles` conformance does not require +an implementation to emit the claim (it is optional); when emitted, it +must match the ratified vocabulary. + +## Validation Checklist + +A service or implementation is profile-ready when: + +- it reads OIDC discovery rather than hardcoding endpoints; +- it validates issuer, audience, expiry, `nbf`, algorithm, and + signature; +- it refreshes JWKS on unknown `kid`; +- it supports Authorization Code + PKCE for human login; +- it supports service-account or workload identity tokens; +- it emits `tenant`, `principal_type`, `groups`, `roles`, + `scope`/`scp`, and `assurance`; +- it uses the ADR-0013 grouping vocabulary for new tenant identifiers; +- if it consumes `tenant_roles`, it treats the claim as a cache and + re-validates live against `tenant-engine` before any `aal2`-class + decision; +- it maps provider-native claims into the canonical core claims; +- it rejects local-development issuers in production; +- it logs emergency access with a durable audit trail; +- flex-auth receives identity facts from the profile, not from + provider-specific sessions. diff --git a/canon/standards/tenant-engine-boundary-contract_v0.1.md b/canon/standards/tenant-engine-boundary-contract_v0.1.md new file mode 100644 index 0000000..51bedaf --- /dev/null +++ b/canon/standards/tenant-engine-boundary-contract_v0.1.md @@ -0,0 +1,163 @@ +--- +id: netkingdom-tenant-engine-boundary-contract +type: standard +title: "NetKingdom Tenant Engine Boundary Contract v0.1" +domain: netkingdom +status: accepted +version: "0.1" +created: "2026-07-23" +updated: "2026-07-23" +scope: tenant-domain-boundaries +adr: + - docs/adr/ADR-0006-recursive-multi-tenant-identity-authorization.md + - docs/adr/ADR-0013-tenant-onboarding-grouping-taxonomy.md + - docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md +related: + - canon/standards/iam-profile_v0.3.md + - canon/standards/user-engine-boundary-contract_v0.1.md + - canon/standards/credential-management_v0.2.md + - docs/platform-identity-security-architecture.md +--- + +# NetKingdom Tenant Engine Boundary Contract v0.1 + +## Purpose + +This contract defines how `tenant-engine` integrates into the NetKingdom +landscape without duplicating identity, authorization, user/membership, +pricing, deployment, or secret-custody responsibilities that other services +already own. + +`tenant-engine` owns **tenant-as-an-entity** facts: existence, identifier +and grouping (ADR-0013), capability roles (ADR-0014), plan/subscription +assignment, and — reserved, not yet implemented — guardrail/quota policy. +It is not an identity provider, not a policy decision point, not a billing +processor, not a pricing-model engine, and not the user/membership domain. +NetKingdom owns this cross-repo boundary contract; `tenant-engine` and its +adapters must preserve the ownership rules below even in local/standalone +deployments. + +## Ownership Model + +| Concern | Owner | tenant-engine responsibility | +| --- | --- | --- | +| OIDC discovery, login, token issuance | `key-cape`, Keycloak | Supply `tenant_roles` via a read API at issuance time; never issues tokens itself | +| Tenant identifier format and grouping vocabulary | NetKingdom (`iam-profile_v0.3.md`, ADR-0013) | Store and validate tenant records against the ratified vocabulary; does not redefine it | +| Tenant capability-role vocabulary | NetKingdom (ADR-0014) | Store role grants against the ratified vocabulary (`PLTF`/`IAM`/`VEN`/`CUS`); does not redefine it | +| Users, profiles, memberships | `user-engine` | References `tenant_id` as an external key only; never stores user or membership records | +| Fine-grained authorization decisions | `flex-auth` | Serves as an authoritative data source `flex-auth` queries live for high-stakes checks; makes no authorization decisions itself | +| Pricing-model / plan definitions | `adaptive-pricing` | References plan definitions by id; never forks or duplicates plan terms locally | +| Runtime secrets | OpenBao / `secrets-engine` | Tenant-scoped OpenBao paths (e.g. `tenants//...`) reference `tenant-engine`'s tenant registry as the naming authority; `tenant-engine` never stores secret material | +| Deployment mechanics | Railiance | Publishes readiness signals only | +| Credential routing/discovery | `ops-warden` | `tenant-engine`'s own service credentials (if any) are routed the same way as any other lane; `tenant-engine` is not a routing authority | + +## Source-Of-Truth Matrix + +| Resource kind | Source of truth | tenant-engine relation | Boundary rule | +| --- | --- | --- | --- | +| Tenant record (existence, grouping, name/slug) | `tenant-engine` | Canonical owner | Grouping value must be one of ADR-0013's taxonomy, or the reserved ungrouped `platform`/`coulomb` identifiers | +| Tenant capability roles | `tenant-engine` | Canonical owner | Non-exclusive; grant/revoke are audited mutations (see Grant Contract below), never free-form field updates | +| Plan/subscription assignment | `tenant-engine` | Canonical owner | References an `adaptive-pricing` plan id; does not define plan terms | +| Pricing-model / plan definitions | `adaptive-pricing` | Consume by reference only | `tenant-engine` must not cache plan terms beyond what's needed to resolve which roles a plan currently grants | +| Guardrail/quota policy (spend limits, entity/action counts) | `tenant-engine` (reserved) | Canonical owner once designed | Not implemented by this contract version — reserved namespace only, see Guardrail Policy section | +| `tenant_roles` token claim | `tenant-engine` (live) / `key-cape` (cached copy) | `tenant-engine` is authoritative; the token claim is a point-in-time cache | Privileged/high-stakes decisions MUST re-query `tenant-engine` live — see IAM Profile v0.3, "Tenant Roles" section — never trust the cached claim alone | +| Tenant identifier claim shape (`tenant`) | NetKingdom / IAM Profile contract | `tenant-engine` validates against it; does not mint the claim format | The wire format stays owned by the profile; `tenant-engine` owns which concrete tenant values currently exist | +| User/membership records scoped by a tenant | `user-engine` | No relation | `tenant_id` is the only key shared between the two services; `tenant-engine` never stores or reads user data | +| Authorization decisions | `flex-auth` | Data source only | `tenant-engine` never enforces access itself; it answers queries `flex-auth`'s policy packages issue | + +## Tenant Role & Plan Grant Contract + +Every role grant or revocation is an audited mutation, not a direct field +write, and must carry: + +| Field | Requirement | +| --- | --- | +| `tenant_id` | Stable tenant identifier | +| `role` | One of `PLTF`, `IAM`, `VEN`, `CUS` | +| `grant_reason` | `plan_assignment`, `manual_grant`, or `platform_default` | +| `plan_id` | `adaptive-pricing` plan id; required when `grant_reason: plan_assignment`, otherwise null | +| `granted_by` | Subject or system that performed the grant | +| `granted_at` | Timestamp | +| `revoked_at` | Nullable timestamp | +| `correlation_id` | Request/import correlation id | + +Rules: + +- Plan-linked roles (starting with `IAM`) are granted and revoked + automatically as a consequence of plan-assignment changes in + `tenant-engine`; manual grants are the exception path and must always + record `granted_by`. +- `trial`-grouped tenants may hold any role without a plan requirement — + `grant_reason: platform_default` is valid for trial-eligible roles with no + `plan_id` (ADR-0014). +- Every grant or revocation emits a domain event. After a revocation, + `key-cape`'s previously-cached `tenant_roles` claim is considered stale + until the token naturally expires — v0.1 relies on the profile's short + token lifetimes for bounded staleness, not push invalidation. +- Cross-tenant grants are rejected; a grant always targets exactly one + `tenant_id`. + +## Authorization Contract + +`tenant-engine` is a data source, not a policy enforcement point. +`flex-auth` remains the policy decision point for authorization outcomes; +`key-cape` remains the token issuer. `tenant-engine` exposes: + +- a **read API** `key-cape` calls at token-issuance time to source the + cached `tenant_roles` claim; +- a **live-lookup API** `flex-auth` calls for high-stakes decisions + (`assurance.level >= aal2`-class actions, per IAM Profile v0.3), returning + current roles, grouping, and plan state; +- a **write API**, authorization-gated by `flex-auth` itself, for grant/ + revoke/plan-assignment mutations — `tenant-engine` does not self-authorize + its own writes. + +Performance model: + +- the live-lookup API must be fast and highly available, since `flex-auth` + calls it synchronously on the request path for privileged actions; slow + or unavailable `tenant-engine` lookups fail closed (deny), never fail open; +- the cached-claim read API used by `key-cape` may be slower, since it only + runs at token issuance, not per-request; +- request-scoped memoization within a single `flex-auth` decision is + allowed; cross-request caching of live-lookup results is not, since it + would recreate the staleness risk the live path exists to avoid. + +## Guardrail Policy (Reserved, Not Implemented) + +Named here so the shape exists before it's designed, matching ADR-0014: + +- Spend limits — `trial`-grouped tenants default to a **zero** spend budget. +- Entity count limits — reserved, not yet specified. +- Action count limits — reserved, not yet specified. +- Enforcement point, override process, and how guardrails interact with + capability roles (a `trial` tenant holding `VEN` is still bound by `trial` + guardrails) are all open design questions for `tenant-engine`'s own + workplan, not resolved by this contract. + +## Audit Correlation Contract + +Every `tenant-engine` mutation (tenant creation, role grant/revoke, plan +assignment) must create or reference a correlation bundle: + +| Field | Requirement | +| --- | --- | +| `correlation_id` | Stable id propagated across request, decision, audit, and event records | +| `actor` | IAM Profile actor envelope (who performed the mutation) | +| `tenant_id` | Tenant affected | +| `resource` and `action` | e.g. `tenant-engine:role`, `grant` / `revoke` | +| `authorization_decision_id` | `flex-auth` decision id backing the write | +| `outbox_event_id` | Event id emitted for the mutation | +| `change_summary` | Redacted mutation summary | + +## Non-Goals + +- `tenant-engine` does not process payments or hold financial instruments — + it records which plan a tenant is assigned to, sourced from whatever + system actually processes payment (not yet identified; out of scope for + this contract). +- `tenant-engine` does not define pricing models, tiers, or terms — + `adaptive-pricing` does. +- `tenant-engine` does not store user accounts, profiles, or memberships — + `user-engine` does. +- `tenant-engine` does not make authorization decisions — `flex-auth` does. diff --git a/docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md b/docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md new file mode 100644 index 0000000..81d03c9 --- /dev/null +++ b/docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md @@ -0,0 +1,171 @@ +# ADR-0014 - Tenant Capability Roles, Carrying Mechanism, and Tenant-Engine Ownership + +**Status:** Accepted +**Date:** 2026-07-23 +**Deciders:** Bernd Worsch, Codex + +## Context + +ADR-0013 introduced the tenant onboarding grouping taxonomy +(`trial`/`friendly`/`single`/.../`agentic`), deliberately orthogonal to a +separate, unratified **capability-role** model sketched in +`docs/princedom-isolation-exploration.md`: `PLTF` (operates the platform), +`IAM` (organizes its own users/auth/secrets), `VEN` (provides apps/services +to others), `CUS` (consumes apps/services from `PLTF` or `VEN` tenants) — +non-exclusive, a tenant may hold several at once. + +That exploration left open where capability roles actually live (a per-token +claim vs. a registry), who owns them, how they're granted or revoked, and +how this interacts with the IAM Profile's existing `roles` claim — which is +a *per-subject* claim ("coarse identity roles" for the human/service/agent +holding the token), a different concept from a *per-tenant* capability fact. +Conflating the two would be a category error: `roles: ["VEN"]` on a token +would ambiguously mean "this subject has vendor-role" vs. "this subject's +tenant is a vendor." + +No existing service owns tenant-as-an-entity facts (existence, grouping, +capability roles, plan/subscription state) as a queryable resource. +`user-engine`'s own boundary contract +(`canon/standards/user-engine-boundary-contract_v0.1.md`) explicitly scopes +`user-engine` to *consuming* tenant identifiers and *storing tenant-scoped +records*, not owning tenant identity or capability facts. + +Bernd's direction (2026-07-23): + +- Implement the previously-discussed hybrid carrying approach: cache a + tenant's capability roles on the token for ordinary decisions, but + require a live check for critical/high-stakes actions. +- Role grants are usually tied to a payment plan — most concretely, `IAM` + means the tenant has its own dedicated key-cape/Keycloak instance for + isolation, scale, and performance, which is itself a paid capability. +- `trial`-grouped tenants may hold **any** capability role without + restriction, specifically so the platform can showcase, test, and explore + every role. Safety for trial tenants comes from resource guardrails + (spend limits defaulting to zero budget, entity/action count limits), not + from role gating — guardrail design itself is future work, not this ADR. +- A new service, `tenant-engine`, will be built (Bernd) as the owner of this + domain, organized beside `user-engine` rather than inside it — smaller, + single-purpose services are easier to reason about and drift less, matching + the fleet's existing convention (`activity-core`, `audit-core`, `user-engine`, + and others). + +## Decision + +1. **Capability-role vocabulary ratified as core NetKingdom vocabulary**: + `PLTF`, `IAM`, `VEN`, `CUS`, non-exclusive. `IAM` specifically means: the + tenant operates its own dedicated IAM implementation instance (lightweight + key-cape or expanded Keycloak) rather than sharing the platform's, for + isolation/scale/performance — not "any tenant that happens to have users." + +2. **`tenant-engine` is the canonical owner** of tenant-domain facts: tenant + existence, grouping (ADR-0013), capability roles (this ADR), plan/ + subscription assignment, and — reserved for future design, not built now — + guardrail/quota policy. It is a new, separate service, not a module inside + `user-engine`. Its ownership boundary is defined in the companion contract, + `canon/standards/tenant-engine-boundary-contract_v0.1.md`. + +3. **Carrying mechanism: hybrid cache + live re-validation.** `tenant-engine` + is the single source of truth. `key-cape` stamps a cached, optional + `tenant_roles` claim onto issued tokens at issuance time, sourced from + `tenant-engine` (added to the IAM Profile as a new optional claim — + `canon/standards/iam-profile_v0.3.md`). Consumers may trust the cached + claim for ordinary decisions. `flex-auth` **MUST** re-validate live + against `tenant-engine` — never trust the cached claim alone — before + authorizing privileged or high-stakes actions, using the same threshold + class the profile already defines for `assurance.level >= aal2` + (privileged, destructive, platform-root, secret, credential-vending + flows). This bounds staleness risk for ordinary actions to a token's + short lifetime (5–30 minutes for service/agent tokens, per the profile's + Token Lifecycle table) while guaranteeing freshness exactly where it + matters most. + +4. **Role governance is plan-linked.** Granting a role is normally a + consequence of a tenant's payment-plan state in `tenant-engine`, not a + separate manual workflow — starting with `IAM`. `tenant-engine` records + which plan grants which role(s); `adaptive-pricing` remains the source of + plan/pricing-model *definitions*, `tenant-engine` owns the tenant's + *current* plan/subscription assignment, referenced by id, never + duplicated locally. Whether `VEN` needs an approval gate beyond payment + (reselling access carries legal/compliance exposure a payment alone + doesn't cover) is **not resolved by this ADR** — left to `tenant-engine`'s + own workplan. + +5. **Trial tenants may hold any capability role, unrestricted.** The `trial` + grouping's purpose (showcase, test, explore) requires demonstrating every + role. Safety is enforced through resource guardrails instead: trial + tenants default to a **spend budget of zero**, with entity and action + count limits to follow. Guardrail policy design (exact limits, + enforcement point, override process) is real, near-term future work, + reserved as `tenant-engine`'s to own once designed — not specified by + this ADR. + +6. **Grouping and capability role are independent axes** recorded on the + same tenant record in `tenant-engine`. Neither constrains the other + except where a future guardrail policy explicitly says so. + +## Consequences + +- `canon/standards/iam-profile_v0.3.md` adds the optional `tenant_roles` + claim and folds in ADR-0013's tenant-identifier vocabulary update (both + non-breaking per ADR-0011's own minor-version rule — no existing + implementation is invalidated by either change). Supersedes + `iam-profile_v0.2.md`. +- `tenant-engine` becomes a new repository with its own workplans (Bernd). + `canon/standards/tenant-engine-boundary-contract_v0.1.md` defines its + ownership boundary now, before code exists — the same sequencing + `user-engine`'s contract followed. +- `flex-auth` policy packages gating high-stakes actions must add a + `tenant-engine` live-lookup step; they cannot trust `tenant_roles` alone + for those decisions. +- `key-cape` needs a `tenant-engine` integration at token-issuance time to + source the cached claim — tracked in `key-cape`'s own workplans, not here. +- `docs/platform-identity-security-architecture.md`'s Tenant Model section + is updated to reflect the grouping + role split and `tenant-engine`'s role + (companion change alongside this ADR). +- Guardrail/quota policy is named as required near-term work and given an + owner (`tenant-engine`), but is explicitly not designed by this ADR. + +## Alternatives Considered + +### Token-claim-only, no live re-validation + +Rejected: staleness would be unbounded within a token's lifetime for +genuinely high-stakes actions. A stale `VEN` grant surviving a plan +downgrade or cancellation is not an acceptable risk for money-movement or +credential-vending flows — exactly the class the profile already treats as +requiring the strongest assurance. + +### Registry-only, no cached claim + +Rejected: every ordinary request would pay a `tenant-engine` round-trip even +for non-critical checks, adding latency and a hard runtime dependency for +every consumer, not just the ones handling privileged actions. + +### Restrict role eligibility by tenant grouping (e.g., `trial` cannot hold `VEN`) + +Rejected per Bernd's direction: `trial` tenants exist specifically to +showcase every role. Resource guardrails are the intended safety mechanism +instead, keeping the two axes (grouping, role) independent. + +### Put tenant-role/plan storage inside `user-engine` + +Rejected. `user-engine`'s own boundary contract scopes it to consuming +tenant identifiers and storing tenant-*scoped* user records, not owning +tenant-as-an-entity facts. A dedicated service avoids coupling a +security-critical, high-frequency lookup (used by `flex-auth` on every +privileged decision, and by `key-cape` on every token issuance) to +`user-engine`'s much larger surface (registration flows, factor models, +family dataspace onboarding) that has nothing to do with tenant capability +state. + +## Follow-Up + +- `tenant-engine` repository creation and its own workplan (Bernd). +- `key-cape` integration: source `tenant_roles` from `tenant-engine` at + token issuance. +- `flex-auth` policy package updates: live `tenant-engine` re-validation + gate for privileged actions. +- Guardrail/quota policy design for `trial` (and eventually all) tenants: + spend limits, entity/action count limits, enforcement point, override + process. +- Resolve whether `VEN` needs an approval gate beyond payment-plan state. diff --git a/docs/platform-identity-security-architecture.md b/docs/platform-identity-security-architecture.md index 161c4e7..03f18d4 100644 --- a/docs/platform-identity-security-architecture.md +++ b/docs/platform-identity-security-architecture.md @@ -1,7 +1,7 @@ # Platform Identity and Security Architecture Status: implemented architecture baseline for NetKingdom/Railiance/Coulomb -Date: 2026-05-24 +Date: 2026-05-24 (Tenant Model updated 2026-07-23, ADR-0013/ADR-0014) ## Purpose @@ -296,15 +296,39 @@ Examples of platform-root actions: Every protected resource should belong to a tenant or to the platform control plane. -Suggested identifiers: +Tenant identifiers are `tenant::`, where `` is an +onboarding-risk / entity-shape classification ratified by ADR-0013 +(`trial`, `friendly`, `single`, `small`, `medium`, `large`, `enterprise`, +`consumer`, `family`, `community`, `association`, `agentic`): ```text -tenant:platform # platform control plane resources -tenant:coulomb # first internal/reference tenant -tenant:sandbox: # sandbox tenants -tenant:customer: # future customer tenants +tenant:platform # platform control plane — reserved, ungrouped +tenant:coulomb # first internal/reference tenant — reserved, ungrouped +tenant:friendly:binky # first tenant onboarded under this taxonomy +tenant:: # general shape for tenants onboarded going forward ``` +`tenant:platform` and `tenant:coulomb` predate the taxonomy and stay +reserved outside it — see `canon/standards/iam-profile_v0.3.md`'s Tenant +Claim section for the full rationale. + +A tenant separately holds **capability roles** — `PLTF`, `IAM`, `VEN`, `CUS` +(ADR-0014), non-exclusive — describing what it *does* on the platform, +independent of its grouping (what kind of entity it *is*). A `trial`-grouped +tenant may hold any capability role, since `trial` exists to showcase every +role; safety instead comes from tenant-level resource guardrails (spend +limits, entity/action counts — reserved, not yet designed). + +`tenant-engine` (new service, `canon/standards/tenant-engine-boundary-contract_v0.1.md`) +is the canonical owner of both tenant grouping and capability-role facts, +plus plan/subscription assignment. `key-cape` caches a tenant's roles on +issued tokens (`tenant_roles` claim) for ordinary decisions; `flex-auth` +re-validates live against `tenant-engine` before authorizing privileged, +destructive, platform-root, or otherwise `aal2`-class actions — the same +threshold the assurance model below already uses. `user-engine` continues +to own users and memberships *scoped by* a tenant, not the tenant record +itself. + Tenant membership and platform membership are distinct. A subject may be an administrator in `tenant:coulomb` without being a platform operator.