diff --git a/architecture/credentials-and-auth/auth-implementation.md b/architecture/credentials-and-auth/auth-implementation.md new file mode 100644 index 0000000..f72a675 --- /dev/null +++ b/architecture/credentials-and-auth/auth-implementation.md @@ -0,0 +1,333 @@ +--- +Document Status: ✅ Stable — DCM implementation +Document Type: Architecture Reference — Authentication Implementation +Established: 2026-05-26 +Maps to: udlm/governance/auth-providers.md +--- + +# Authentication Implementation + +> **Implements contracts defined in UDLM**: +> [udlm/governance/auth-providers.md](https://github.com/croadfeldt/udlm/blob/main/governance/auth-providers.md). +> UDLM defines the Auth Provider taxonomy (built-in, static API key, local +> users, GitHub/GitLab OAuth, LDAP, FreeIPA, AD, OIDC, SAML, mTLS, SCIM 2.0), +> the multi-provider authentication contract, and the credential +> types/issuance taxonomy. DCM operationalizes the implementation: library +> choices, integration mechanics, routing logic, session management, and +> token lifecycle. + +--- + +## 1. Authentication implementation within DCM + +The DCM API Gateway is the single ingress for all authenticated requests. It +performs the following on every request: + +1. Extract the authentication signal (mTLS cert, Bearer token, Basic auth, + HMAC signature) +2. Route to the appropriate registered Auth Provider per the resolution order +3. Validate the credential through the provider +4. Resolve the actor's roles, groups, and tenant scope +5. Inject `X-DCM-Tenant`, `X-DCM-Actor-Uuid`, `X-Request-ID` headers for + downstream services +6. Apply rate limiting per the authenticated actor + +The Built-in Auth Provider ships with DCM and is always registered. It +provides static API key, local user/password, and optional GitHub/GitLab +OAuth (opt-in via configuration). + +### 1.1 Library and protocol choices + +| Mechanism | DCM library / protocol | +|---|---| +| OIDC / OAuth 2.0 | Standard OIDC discovery + JWKS endpoint validation; ID token format per RFC 7519 | +| SAML 2.0 | OASIS SAML 2.0 assertion parsing; optional Auth Provider type | +| LDAP / FreeIPA / AD | RFC 4511 LDAP v3; bind operation for authentication; group membership via filter (LDAP_MATCHING_RULE_IN_CHAIN for AD nested groups) | +| Kerberos (FreeIPA SSO) | GSSAPI; keytab-based service principal | +| mTLS | RFC 5280 X.509 chain validation; CN → actor mapping | +| SCIM 2.0 | RFC 7643/7644 endpoints at `/scim/v2/Users` and `/scim/v2/Groups` | +| Local users | argon2id password hashing; SQLite (minimal/dev) or PostgreSQL (standard+) backend | +| Sessions | RFC 7519 JWT for stateless session tokens; refresh tokens stored in DB | + +### 1.2 Internal vs External mode + +DCM follows the same Internal/External pattern as policy evaluation and +secrets management: + +- **Internal mode (default):** local user accounts in the `actors` table; + passwords as argon2id hashes; DCM-issued JWT session tokens with + configurable expiry. Zero external dependencies — bootstrap with a local + admin account and start. +- **External mode (optional):** register one or more `auth_provider` + instances; DCM validates their tokens, extracts claims, maps groups to + DCM roles. Multiple providers enable tenant-routed authentication + (Tenant A through AD, Tenant B through Okta). + +External Auth Providers register through the standard provider registration +contract with `provider_type: auth_provider` and capability declaration. + +--- + +## 2. Credential Management Service integration + +DCM never stores credentials directly. The Built-in Auth Provider's secret +storage uses the same envelope encryption mechanism as DCM internal secrets: + +| KEK source | Profile | +|---|---| +| Environment variable | minimal, dev (homelab) | +| Kubernetes Secret | standard, prod | +| HSM via PKCS#11 | fsi, sovereign | + +For external Auth Providers, DCM resolves bind passwords, OAuth client +secrets, SAML signing certificates, and all other secrets via the registered +Credential Management Service (see +[`credentials.md`](credentials.md)). + +### 2.1 Secret references + +Every Auth Provider configuration references secrets, never embeds them: + +```yaml +auth_provider: + provider_type: freeipa + config: + bind_password_ref: + service_provider_uuid: + secret_path: "dcm/auth/freeipa/bind-password" +``` + +DCM resolves the reference at runtime via the Credential Management Service. +Plaintext credentials in registration payloads are rejected (`AUTH-007`). + +--- + +## 3. Provider authentication routing logic + +The API Gateway routes incoming requests to the appropriate Auth Provider +based on the authentication signal: + +```yaml +auth_provider_resolution: + resolution_order: + - signal: mtls_client_cert + provider_uuid: + - signal: bearer_token_oidc + provider_uuid: + - signal: bearer_token_apikey + provider_uuid: + - signal: basic_auth + provider_uuid: + - signal: hmac_signature + provider_uuid: + - signal: none + action: reject # always — no anonymous access +``` + +The resolution order is declared in DCM configuration; the API Gateway walks +it on every request. The first match wins. + +### 3.1 Auth Provider chain (enrichment + augmentation) + +A request can authenticate with one provider and enrich claims via another: + +```yaml +auth_provider_chain: + authentication: + provider_uuid: # fast LDAP bind + enrichment: + provider_uuid: # LDAP group membership + augmentation: + provider_uuid: # OIDC userinfo for rich claims (dept, cost_center, project codes) +``` + +The API Gateway invokes each stage; failure at enrichment or augmentation is +logged but does not block the request unless the active profile requires +all stages to succeed (`fsi`/`sovereign`). + +### 3.2 Failover behavior (AUTH-013) + +When an Auth Provider becomes unhealthy: + +- **In-flight requests** authenticated before the failure continue using + cached session tokens +- **New requests** follow the declared failover chain +- **Session expiry during outage** requires re-authentication via available + failover provider; if all providers unavailable → reject with clear error + +```yaml +auth_failover_config: + primary_provider_uuid: + failover_chain: + - provider_uuid: + promotion_delay: PT30S + - provider_uuid: + session_cache: + enabled: true + ttl: PT8H +``` + +--- + +## 4. Session management and token lifecycle + +DCM issues its own session tokens (JWT) for actors authenticated through any +Auth Provider. Session tokens carry: `actor_uuid`, `roles`, `tenant_scope`, +`auth_provider_uuid`, `exp`, `iat`. + +### 4.1 Session configuration + +```yaml +session: + token_ttl: PT8H # per-profile default + refresh_enabled: true + refresh_ttl: P7D + concurrent_sessions: 3 # max per actor; enforced via session_store +``` + +The `sessions` table stores active session metadata; revocation is a status +update. Token introspection per RFC 7662 is exposed at +`POST /api/v1/auth:introspect`. + +### 4.2 MFA enforcement (AUTH-014) + +DCM implements two-tier MFA: + +- **Per-session MFA:** validated at login; captured in the JWT `mfa_verified` claim +- **Step-up MFA:** additional challenge at sensitive operations within an + already-authenticated session; results in a short-lived (PT10M) step-up + token + +```yaml +step_up_mfa_config: + step_up_required_for: + - platform_policy_activate + - provider_decommission + - tenant_decommission + - sovereignty_zone_change + - auth_provider_update + - manual_rehydration + step_up_method: totp | push_notification | hardware_token | sms + step_up_token_ttl: PT10M + step_up_challenge_max_age: PT5M +``` + +Profile defaults govern which operations require step-up: + +| Profile | Per-Session MFA | Step-Up Required | +|---|---|---| +| minimal | No | No | +| dev | No | No | +| standard | Recommended | Optional | +| prod | Required | Destructive operations | +| fsi | Required | All policy changes | +| sovereign | Required (hardware token) | All administrative operations | + +### 4.3 Session revocation + +Session revocation follows the SES-001 model (in +[`../control-plane/session-revocation.md`](../control-plane/session-revocation.md)). +Triggers include actor deprovisioning, manual admin revocation, password +change, and security event. + +When a session is revoked, the Auth Implementation: + +1. Marks the session row status: `revoked` +2. Publishes `session.revoked` event to `pipeline_events` +3. All Policy Manager and API Gateway instances invalidate their permission + cache entries for the actor + +### 4.4 SCIM 2.0 deprovisioning + +When SCIM signals an actor deprovision: + +1. Actor's session(s) revoked immediately +2. All credentials issued to actor revoked (per `CPX-006`) +3. In-flight requests complete on cached tokens; new requests rejected +4. Audit record written with `source: scim_deprovision` + +The SCIM endpoint at `/scim/v2/Users/{id}` (DELETE) triggers +`AUTH-016`/`SES-001`/`CPX-006` in parallel. + +--- + +## 5. Git PR actor identity resolution + +When DCM processes Git PR ingress, the Git server's authenticated user must +resolve to the same DCM actor as if they had logged into the web UI: + +``` +Git server authenticates user → PR merge webhook → DCM Auth Provider resolution → DCM actor +``` + +DCM trusts the Git server's verified identity assertion — not user-declared +Git config. Resolution methods: + +| Method | When | +|---|---| +| `oidc_subject_lookup` | Git server uses same OIDC/OAuth IdP as DCM | +| `ldap_username_lookup` | Git server authenticates via LDAP/AD | +| `ssh_key_fingerprint` | SSH key-authenticated Git workflows | +| `webhook_service_account` | Automated CI/CD Git workflows | + +The resolved actor carries identical role, group, and tenant scope to the +same user authenticating via web UI (`AUTH-011`). Git PR ingress does not +grant different permissions than any other ingress surface. + +--- + +## 6. Authentication ladder (DCM realization) + +Every rung is authenticated. The ladder is about setup effort — not whether +authentication exists. + +| Profile | Modes available | Setup effort | +|---|---|---| +| `minimal` | Static API key, Local user/password | 30 seconds – 2 minutes | +| `dev` | + GitHub/GitLab OAuth, FreeIPA/AD direct bind | 5–15 minutes | +| `standard` | + OIDC via broker (Dex/Keycloak), AD/FreeIPA direct | 30–60 minutes | +| `prod` | + OIDC direct, MFA | 1–2 hours | +| `fsi` | + mTLS required, MFA required | 4–8 hours | +| `sovereign` | + Air-gapped OIDC/mTLS | 1–2 days | + +### 6.1 Built-in Auth Provider storage backend (AUTH-015) + +```yaml +builtin_auth_provider_config: + user_store: + profile_defaults: + minimal: sqlite # zero infrastructure; single-file + dev: sqlite + standard: postgresql + prod: postgresql + fsi: postgresql # encrypted (TDE required) + sovereign: postgresql # HSM-backed encryption required + encryption_at_rest: + required_profiles: [fsi, sovereign] + key_ref: + service_provider_uuid: + path: "dcm/auth/builtin/encryption-key" +``` + +The local user store should only contain: bootstrap users, service accounts, +and API key holders. Enterprise users belong in external Auth Providers +(LDAP, OIDC, SCIM). + +--- + +## 7. Policy IDs (DCM realization) + +| Policy | Rule | +|---|---| +| `AUTH-001-DCM` | All DCM authentication is handled through a registered Auth Provider; the built-in is always available | +| `AUTH-002-DCM` | DCM routes to the appropriate Auth Provider based on the authentication signal in the request | +| `AUTH-005-DCM` | When an Auth Provider becomes unhealthy, existing sessions remain valid until TTL expiry; new auth follows failover chain or is rejected | +| `AUTH-006-DCM` | DCM records the Auth Provider used in the ingress block and carries it into the audit record | +| `AUTH-007-DCM` | DCM rejects Auth Provider configurations containing plaintext credentials; secret references required | +| `AUTH-008-DCM` | DCM permits no anonymous access in any profile; minimal/dev support lightweight authenticated modes | +| `AUTH-009-DCM` | DCM always requires authentication on webhook and message bus inbound surfaces regardless of profile | +| `AUTH-010-DCM` | DCM enforces rate limiting per authenticated actor; limits declared on Auth Provider or webhook registration | +| `AUTH-011-DCM` | DCM resolves Git PR actor identity through the registered Auth Provider; resolved actor carries same role/group/tenant scope as web UI authentication | +| `AUTH-013-DCM` | In-flight requests continue on cached tokens during Auth Provider outage; new auth follows failover chain | +| `AUTH-014-DCM` | DCM enforces two-tier MFA: per-session (mfa_verified claim) and step-up (short-lived token) for sensitive operations per policy | +| `AUTH-015-DCM` | DCM's built-in Auth Provider uses SQLite for minimal/dev, PostgreSQL for standard+; encryption-at-rest required in fsi/sovereign | diff --git a/architecture/credentials-and-auth/authority-enforcement.md b/architecture/credentials-and-auth/authority-enforcement.md new file mode 100644 index 0000000..3c2ac1b --- /dev/null +++ b/architecture/credentials-and-auth/authority-enforcement.md @@ -0,0 +1,376 @@ +--- +Document Status: ✅ Stable — DCM implementation +Document Type: Architecture Reference — Authority Tier Enforcement +Established: 2026-05-26 +Maps to: udlm/governance/authority-tier-model.md +--- + +# Authority Enforcement + +> **Implements contracts defined in UDLM**: +> [udlm/governance/authority-tier-model.md](https://github.com/croadfeldt/udlm/blob/main/governance/authority-tier-model.md). +> UDLM defines the core authority tier model (auto / reviewed / verified / +> authorized), the decision_gravity vocabulary, the custom tier definition +> contract, the tier registry change impact detection contract, and the +> degradation review gate contract. DCM operationalizes the tier evaluation +> algorithm, approval authority mapping, profile threshold configuration, +> DCMGroup assignment, tier enforcement at decision points, and the +> degradation review orchestration. + +--- + +## 1. Tier evaluation algorithm + +DCM evaluates required tier at every approval-gated decision point. The +algorithm: + +``` +At decision evaluation time: + ▼ 1. Compute the request risk score (0–100) via the Scoring Model + ▼ 2. Load the active profile's threshold list + ▼ 3. Walk the list in order; the first tier whose max_score ≥ risk_score + is the required tier + ▼ 4. Resolve the numeric weight of the required tier from the ordered + tier registry + ▼ 5. Create an approval record with the required tier name and weight +``` + +The tier **name** (not the weight) is what's stored in the approval record +and shown to reviewers. The weight is used for comparison operations +(e.g., "is this action at least as significant as `verified`?"). + +### 1.1 Approval record + +```yaml +approval_record: + approval_uuid: + subject_uuid: + subject_type: request | policy_contribution | provider_registration | federation_contribution + required_tier: verified # tier name — stable reference + required_tier_weight: 3 # resolved at creation; point-in-time audit + required_tier_gravity: elevated + dcmgroup_uuid: # non-null only for dcmgroup_required: true tiers + quorum_threshold: + status: pending_reviewed | pending_verified | pending_authorized | pending_ + created_at: + window_expires_at: + decisions: [] +``` + +The `required_tier_weight` is **stored at creation** (`ATM-008`). If the +tier registry changes later, the stored weight reflects the state at +creation — point-in-time audit. + +--- + +## 2. Approval authority mapping + +DCM enforces each tier per the contract: + +| Tier | DCM gate | +|---|---| +| `auto` | All structural and governance validation checks pass; automatic activation | +| `reviewed` | One actor with reviewer role records a decision via `POST /api/v1/admin/approvals/{uuid}:vote` | +| `verified` | Two distinct actors with reviewer role each record a decision (DCM enforces actor distinctness — same actor cannot satisfy both) | +| `authorized` | N members of a declared DCMGroup record decisions; DCM tracks quorum (N of M); pipeline advances when N reached | + +### 2.1 Admin API as integration point + +DCM's vote-recording endpoint is designed for external system integration, +not only humans-in-UI: + +``` +POST /api/v1/admin/approvals/{approval_uuid}:vote +Authorization: Bearer # any actor who is a member of the required DCMGroup +{ + "decision": "approve | reject", + "reason": "", + "recorded_via": "dcm_admin_ui | servicenow | jira | slack_bot | api_direct | other", + "external_reference": "" +} +→ { + "approval_uuid": "", + "voter_uuid": "", + "decision": "approve", + "votes_recorded": 2, + "quorum_required": 3, + "quorum_reached": false, + "pipeline_status": "pending_authorized" + } +``` + +The `recorded_via` field provides audit provenance — informational, not +enforced. DCM does not care whether the vote came from a Slack bot, ServiceNow +integration, Jira plugin, or direct API call — only that an authorized actor +recorded it. + +### 2.2 Deadline and escalation + +```yaml +approval_window: + reviewed: PT72H + verified: PT72H + authorized: P7D + on_expiry: + reviewed: escalate + verified: escalate + authorized: reject +``` + +When the window expires without a decision, DCM fires an escalation +notification. For `reviewed` and `verified`, escalates to platform admin or +the next tier. For `authorized`, rejects (cannot lower the authority gate +silently). + +--- + +## 3. Profile threshold configuration + +DCM ships per-profile threshold defaults: + +```yaml +profile_approval_thresholds: + minimal: + - { tier: auto, max_score: 44 } + - { tier: reviewed, max_score: 100 } + + dev: + - { tier: auto, max_score: 39 } + - { tier: reviewed, max_score: 69 } + - { tier: verified, max_score: 100 } + + standard: + - { tier: auto, max_score: 24 } + - { tier: reviewed, max_score: 59 } + - { tier: verified, max_score: 79 } + - { tier: authorized, max_score: 100 } + + prod: + - { tier: auto, max_score: 14 } + - { tier: reviewed, max_score: 49 } + - { tier: verified, max_score: 74 } + - { tier: authorized, max_score: 100 } + + fsi: + - { tier: auto, max_score: 9 } + - { tier: reviewed, max_score: 39 } + - { tier: verified, max_score: 69 } + - { tier: authorized, max_score: 100 } + + sovereign: + - { tier: auto, max_score: 4 } + - { tier: reviewed, max_score: 29 } + - { tier: verified, max_score: 59 } + - { tier: authorized, max_score: 100 } +``` + +**SMX-008 in the dynamic model:** `auto.max_score` may never exceed 50 in any +profile, regardless of custom tier additions. DCM enforces this at profile +contribution time (`ATM-002`). + +### 3.1 Custom tier insertion + +When an organization adds a custom tier (e.g., `compliance_reviewed` between +`verified` and `authorized`), DCM: + +1. Validates `decision_gravity` is consistent with position (`ATM-003`) +2. Requires `verified` tier approval to add the tier (`ATM-004`) +3. Re-resolves numeric weights from list position (`ATM-001`) +4. Triggers the Tier Registry Change Impact Detection pipeline (Section 5) + +Existing references to `authorized` continue to work — the name is stable; +the weight is updated. + +--- + +## 4. DCMGroup assignment + +When a decision requires the `authorized` tier (or any custom tier with +`dcmgroup_required: true`), DCM resolves the required DCMGroup and quorum +threshold from the profile or per-action-type config: + +```yaml +authorized_tier_configuration: + default_dcmgroup_handle: platform/security-council + quorum_threshold: "2 of 5" + + # Per-action-type overrides + action_type_overrides: + - subject_type: provider_registration + provider_type: service_provider + dcmgroup_handle: platform/credential-governance + quorum_threshold: "3 of 5" + - subject_type: federation_contribution + dcmgroup_handle: platform/federation-council + quorum_threshold: "2 of 3" + - subject_type: policy_contribution + policy_domain: system + dcmgroup_handle: platform/policy-governance + quorum_threshold: "3 of 5" +``` + +DCM enforces: +- Required DCMGroup must be declared before the tier can be used as a + routing target (`ATM-006`) +- Each decision is attributed to the specific DCMGroup member who recorded it +- The audit record links to the DCMGroup at decision time (point-in-time + membership) + +--- + +## 5. Tier enforcement at decision points + +DCM applies the tier evaluation algorithm at every decision point: + +| Decision point | Trigger | +|---|---| +| Resource request | Risk score computed; threshold resolved; approval record created (if non-auto) | +| Policy contribution | Per `contribution_policy` in active profile; tier resolved based on contributor + artifact type + profile | +| Provider registration | Per `provider_type_registry.default_approval_method`; tier resolved at registration submission | +| Federation contribution | Per peer trust posture × profile contribution_policy | +| Sovereignty zone change | Tier resolved at change submission; typically `authorized` | +| Auth Provider update | Tier resolved at update; typically `verified` in standard+ | +| Federation policy update | Tier resolved per policy domain | + +In every case, the approval record drives the pipeline. The pipeline holds +in `pending_` state until quorum is reached or the deadline expires. + +--- + +## 6. Degradation review orchestration + +UDLM defines the tier registry change impact detection contract. DCM +operationalizes the detection pipeline. + +### 6.1 Tier impact diff computation + +When a tier registry change is proposed, DCM computes the diff before +activation: + +```yaml +tier_impact_diff: + registry_change_uuid: + proposed_at: + proposed_by: + + tier_changes: + - tier_name: verified + change_type: POSITION_CHANGED # NEW | REMOVED | POSITION_CHANGED | GRAVITY_CHANGED | UNCHANGED + old_position: 3 + new_position: 4 + old_gravity: elevated + new_gravity: elevated + net_effect: UPGRADED # UPGRADED | DEGRADED | NEW | REMOVED | UNCHANGED + + security_degradations: [] + profile_gaps: [] + broken_references: [] +``` + +### 6.2 Affected item query + +For each changed tier, DCM queries for affected items: + +| Category | Query | +|---|---| +| Pending approval records | WHERE required_tier IN (changed_tier_names) AND status LIKE 'pending_%' | +| Profile threshold configs | WHERE tier_registry_version < new_registry_version | +| Provider registration requirements | WHERE default_approval_method IN (changed_tier_names) | +| FCM contribution policy requirements | WHERE any tier reference IN (changed_tier_names) | +| Active policy sets | WHERE policy_content CONTAINS tier_name_reference | + +### 6.3 Impact classification + +Each affected item receives one or more classifications: + +| Classification | Condition | Required action | +|---|---|---| +| `SECURITY_DEGRADATION` | Item references a tier whose gravity decreased OR position decreased | **Blocks activation** — must be reviewed and accepted | +| `SECURITY_UPGRADE` | Item references a tier whose gravity or position increased | Informational | +| `BROKEN_REFERENCE` | Item references a tier name that no longer exists | **Blocks activation** — must be resolved | +| `PROFILE_GAP` | Profile threshold list incomplete after new tier insertion | **Warning** — does not block | +| `STALE_WEIGHT` | Pending approval's stored_tier_weight differs from current | Informational | + +### 6.4 Degradation review gate + +Security degradations block activation. The gate requires: + +1. Each `SECURITY_DEGRADATION` item presented to a reviewer at `verified` or above +2. The reviewer records an explicit acceptance via Admin API +3. The acceptance includes a reason; written to audit trail +4. Only after ALL degradations accepted does the tier registry change activate + +``` +POST /api/v1/admin/tier-registry/{change_uuid}:accept-degradation +{ + "affected_item_uuid": "", + "affected_item_type": "provider_registration_requirement", + "degradation_classification": "SECURITY_DEGRADATION", + "acceptance_reason": "", + "accepted_by": "" +} +``` + +Broken references **cannot be accepted** — they must be resolved (tier +restored, item updated, or item cancelled). DCM will not activate a +registry change that leaves unresolvable references (`ATM-010`). + +### 6.5 Impact report + +DCM generates a tier registry impact report at proposed time and again at +activation time: + +```yaml +tier_registry_impact_report: + registry_change_uuid: + report_generated_at: + stage: proposed | accepted | activated + + summary: + degradations: 0 + upgrades: 3 + new_tiers: 1 + broken_references: 0 + profile_gaps: 2 + stale_weight_records: 4 + + degradations: [] + upgrades: [...] + profile_gaps: [...] + + notification_targets: + - platform_admin + - provider_owners + - affected_actor_groups +``` + +The report is stored in the Audit Store and linked to the tier registry +version (`ATM-011`). + +### 6.6 Audit trail + +Every tier registry change produces: + +- Registry change proposal record +- Tier impact diff record (all changes, all affected items, all classifications) +- Per-degradation acceptance records (if any) +- Registry activation record (actual effective timestamp) +- Per-affected-item notification records + +--- + +## 7. Policy IDs (DCM realization) + +| Policy | Rule | +|---|---| +| `ATM-001-DCM` | DCM identifies tiers by name; numeric weight resolved from list position at evaluation time | +| `ATM-002-DCM` | DCM enforces auto.max_score ≤ 50 in any profile regardless of custom tier additions | +| `ATM-003-DCM` | DCM validates custom tier decision_gravity is consistent with position | +| `ATM-004-DCM` | DCM requires verified tier approval for custom tier contributions | +| `ATM-005-DCM` | DCM rejects custom tier definitions that alter dcm_gate semantics of system tiers | +| `ATM-006-DCM` | For dcmgroup_required tiers, DCMGroup and quorum threshold must be declared before tier becomes a routing target | +| `ATM-008-DCM` | DCM stores tier name and resolved weight in approval records at creation — point-in-time audit | +| `ATM-009-DCM` | DCM blocks tier registry activation on SECURITY_DEGRADATION until each is explicitly accepted by verified-tier reviewer | +| `ATM-010-DCM` | DCM blocks tier registry activation on BROKEN_REFERENCE; must be resolved | +| `ATM-011-DCM` | DCM produces tier impact report stored in Audit Store linked to registry version | +| `ATM-012-DCM` | DCM generates warning notification for PROFILE_GAP; change may activate; admins update or acknowledge within approval window | diff --git a/architecture/credentials-and-auth/credentials.md b/architecture/credentials-and-auth/credentials.md new file mode 100644 index 0000000..2383755 --- /dev/null +++ b/architecture/credentials-and-auth/credentials.md @@ -0,0 +1,460 @@ +--- +Document Status: ✅ Stable — DCM implementation +Document Type: Architecture Reference — Credential Management +Established: 2026-05-26 +Maps to: udlm/governance/credentials.md +--- + +# Credentials + +> **Implements contracts defined in UDLM**: +> [udlm/governance/credentials.md](https://github.com/croadfeldt/udlm/blob/main/governance/credentials.md). +> UDLM defines the credential scope (internal vs consumer-facing), the +> credential type taxonomy (api_key, JWT, mTLS cert, SSH key, secret, +> signing key, HSM-backed, dcm_interaction), the credential lifecycle +> (issuance / active / rotation / revocation / expired), the rotation +> protocol with parallel validity windows, the revocation propagation +> contract, consumer credential delivery, provider API contract, and +> cryptographic requirements (deferred to standards catalog). DCM +> operationalizes the storage, generation, issuance flow, rotation +> execution, revocation enforcement, delivery mechanics, validation, and +> profile-governed configuration. + +--- + +## 1. Credential storage and access control + +DCM operates at two levels of credential management: + +### 1.1 Internal — DCM operational secrets + +DCM's own operational secrets use envelope encryption in the PostgreSQL +`secrets` table. Each value is encrypted with AES-256-GCM using a per-secret +data encryption key (DEK); DEKs are encrypted with a master key (KEK) +sourced from the deployment environment. + +| KEK source | Profile | Security level | +|---|---|---| +| Environment variable | minimal, dev | Basic — protects against database theft | +| Kubernetes Secret | standard, prod | Good — K8s RBAC + etcd encryption | +| HSM via PKCS#11 | fsi, sovereign | Strong — KEK never leaves the HSM | + +The `secrets` table has the same RLS, append-only audit, and tenant isolation +as every other DCM table. Used for: provider authentication credentials +(values referenced from PCA records), encryption keys for sensitive JSONB +fields (PHI, PCI), audit signing keys, internal service credentials. + +### 1.2 Consumer-facing — Credential Provider + +Consumer-facing credentials (kubeconfigs, database passwords, API keys, SSH +keys, service account tokens) flow through a registered Credential Provider +(a service_provider with `Credential.*` in supported_resource_types). + +**Credential values are never stored in DCM** (`CPX-001` — non-negotiable in +all profiles). DCM stores only credential metadata: UUID, type, scope, +expiry, status. The actual value is held exclusively by the Credential +Provider, retrieved by the authorized consumer via the provider's +`value_retrieval_endpoint`. + +External Credential Providers are registered via the standard provider +registration contract: + +```yaml +provider: + provider_type: service_provider + supported_resource_types: + - "Credential.Secret" + - "Credential.Certificate" + - "Credential.SSHKey" + - "Credential.APIKey" + capability_extension: + hsm_support: true + rotation_protocol: automatic + max_secret_size_bytes: 65536 + supported_algorithms: [rsa-2048, rsa-4096, ecdsa-p256, ecdsa-p384, ed25519] +``` + +--- + +## 2. Credential generation implementation + +DCM does not generate credential values itself (except bootstrap tokens during +initial registration). Generation is delegated to the registered Credential +Provider per the contract in +[udlm/governance/credentials.md](https://github.com/croadfeldt/udlm/blob/main/governance/credentials.md). + +DCM's role at generation time: + +1. Validate the issuance request matches an authorized DCM operation (e.g., + resource realization triggered the request) +2. Compute the `expires_at` based on the active profile's max_lifetime +3. Build the scope: `issued_to`, `operations`, `resource_types`, `tenant_uuid` +4. Apply profile-governed bindings: `bound_to_ip` if required, hardware + attestation flag if sovereign +5. Submit to the Credential Provider's `issue_endpoint` +6. Persist the returned credential_record metadata in `credentials` table +7. Return credential metadata to the consumer (never the value) + +--- + +## 3. Issuance flow orchestration + +### 3.1 Resource credential issuance (consumer-facing) + +Credentials issued as part of resource realization flow through the standard +provider dispatch pipeline: + +``` +Consumer requests resource (e.g., Compute.VirtualMachine) + ▼ Layer assembly + policy evaluation + │ Transformation policy may inject credential_requirements: + │ - credential_type: ssh_key + │ issued_to: requesting_actor + │ scope: [ssh_access] + ▼ Placement selects Service Provider for the VM + ▼ After VM realization: Credential Provider dispatched + │ Sub-request issued to Credential Provider with: + │ entity_uuid, credential_type, issued_to.actor_uuid, + │ scope.operations, scope.resource_types, expires_at + ▼ Credential Provider issues credential; returns credential_record + ▼ DCM writes credential_record to Realized State; links credential_uuid to entity_uuid + ▼ Consumer receives realized entity + credential metadata + │ Consumer calls value_retrieval_endpoint to get actual credential + │ (step-up MFA may be required per profile) +``` + +### 3.2 DCM interaction credential issuance + +DCM interaction credentials are issued automatically before each provider +interaction. They implement the Zero Trust scoped credential model: + +``` +DCM prepares to dispatch to a provider + ▼ API Gateway requests interaction credential from Credential Provider: + │ credential_type: dcm_interaction + │ issued_to.component_uuid: + │ issued_to.provider_uuid: + │ scope.operations: [dispatch] + │ scope.resource_types: [Compute.VirtualMachine] + │ entity_uuid: + │ expires_at: (max; profile-governed) + ▼ DCM includes credential in provider dispatch + ▼ Provider validates credential scope before executing + ▼ Credential expires after PT15M regardless of use + │ (no renewal; new credential issued for next interaction) +``` + +### 3.3 Bootstrap credential issuance + +During bootstrap (before Credential Provider is registered), DCM uses a +bootstrap credential mechanism. After bootstrap, all credentials are issued +through a registered Credential Provider. + +--- + +## 4. Rotation job scheduling and execution + +DCM operationalizes the UDLM rotation contract through scheduled rotation +jobs (no transition window for emergency rotations). + +### 4.1 Rotation triggers + +| Trigger | DCM mechanism | +|---|---| +| `scheduled` | Cron-based rotation per credential type interval | +| `pre_expiry` | Time-based: rotation initiated `pre_expiry_window` before expires_at; PT5M for dcm_interaction, P14D for x509, P7D for ssh_key | +| `provider_initiated` | Provider's update notification (PCA model) triggers rotation | +| `security_event` | Emergency revocation triggers — no transition window | +| `actor_request` | Consumer requests via API; rate-limited per policy | + +### 4.2 Rotation protocol execution + +``` +Rotation initiated (any trigger): + ▼ DCM requests new credential from Credential Provider + │ rotation_of: + │ same scope; new expires_at + ▼ Credential Provider issues new credential + │ Returns new credential_record; old NOT yet revoked + ▼ New credential delivered to authorized consumer/component + ▼ Transition window: both credentials valid + │ Duration: P1D for consumer credentials (default) + │ PT5M for dcm_interaction + │ P7D for x509_certificate + ▼ Old credential revoked at end of transition window + │ Revocation propagated to all registered consumers + ▼ Rotation record written to audit trail +``` + +### 4.3 Pre-expiry rotation scheduler + +A background worker per Credential Provider scans for credentials approaching +expiry: + +```sql +SELECT credential_uuid, expires_at +FROM credentials +WHERE status = 'active' + AND expires_at - pre_expiry_window <= now() + AND rotation_in_progress = false +``` + +For each match, the rotation pipeline kicks off automatically. The worker +runs on the cadence of the shortest pre_expiry_window across active +credential types (typically PT1M for dcm_interaction credentials). + +### 4.4 Emergency rotation (security event) + +``` +Triggers: security.credential_compromised, security.anomalous_usage_detected, + actor.deprovisioned, provider.deregistered, accreditation.revoked + ▼ No transition window + ▼ Old credential revoked immediately + ▼ New credential issued and delivered via fastest available channel + ▼ Security event record written with full context + ▼ Compliance-class Gating Policy firing audited against the event + ▼ Platform admin notified regardless of profile +``` + +--- + +## 5. Revocation enforcement across providers + +### 5.1 Revocation triggers + +| Trigger | DCM Behavior | +|---|---| +| `actor_deprovisioned` | All credentials for actor revoked; propagated via SCIM (per `CPX-006`) | +| `entity_decommissioned` | All credentials scoped to entity revoked before decommission confirmed (per `CPX-007`) | +| `security_event` | Immediate; no transition window | +| `provider_deregistered` | All interaction credentials for provider revoked | +| `actor_request` | Consumer may revoke their own credentials | +| `ttl_expired` | Lifecycle Constraint Enforcer triggers revocation | + +### 5.2 Revocation propagation + +DCM maintains a Credential Revocation Registry — fast-queryable store of +revoked credential UUIDs. All components that receive interaction credentials +must check this registry on each use, not just at issuance. + +``` +Credential revoked + ▼ credential record status: active → revoked + ▼ revoked_at, revocation_reason persisted + ▼ credential.revoked event published to pipeline_events + ▼ All subscribed components update local revocation cache + │ Cache TTL: PT1M standard, PT30S fsi/sovereign + ▼ Credential Provider notified to invalidate stored value + │ Provider must honor within revocation_sla + │ standard/prod: PT5M + │ fsi/sovereign: PT1M + ▼ Audit record: credential_uuid, revocation_trigger, revoked_by_actor +``` + +### 5.3 Revocation check at use + +Providers receiving DCM interaction credentials must validate at use time, +not only at receipt: + +1. Verify credential signature (if signed) +2. Check credential UUID against local revocation cache +3. Verify credential has not expired (`expires_at`) +4. Verify operation is within credential scope +5. Verify IP binding if `bound_to_ip` is set + +Failure → return `403 Forbidden` with `credential_revoked` or +`credential_expired` error code. + +--- + +## 6. Consumer delivery mechanics + +After resource realization with associated credential, the consumer receives +`credential_record` metadata in the realized entity response. The actual +value is retrieved separately: + +``` +GET /api/v1/resources/{entity_uuid}/credentials +→ { + "credentials": [ + { + "credential_uuid": "", + "credential_type": "ssh_key", + "status": "active", + "issued_at": "", + "expires_at": "", + "scope": {...}, + "retrieval": { + "endpoint": "/api/v1/credentials//value", + "auth_required": "step_up_mfa", + "retrieval_count": 1, + "last_retrieved_at": "" + }, + "rotation_schedule": {...} + } + ] + } +``` + +### 6.1 Value retrieval + +``` +GET /api/v1/credentials/{credential_uuid}/value +Authorization: Bearer +X-DCM-StepUp-Token: # if auth_required: step_up_mfa +→ { + "credential_uuid": "", + "credential_type": "ssh_key", + "value": { "private_key": "...", "public_key": "...", "username": "..." }, + "expires_at": "", + "retrieval_uuid": "" # idempotency key; audited + } +``` + +Every retrieval is audited: credential_uuid, actor_uuid, retrieved_at, +retrieval_uuid (`CPX-005`). + +--- + +## 7. Provider authentication validation + +The Credential Provider must validate at use time (see Section 5.3 above): +- Signature verification +- Revocation cache check +- Expiry check +- Scope check +- IP binding (if bound) +- `key_usage` enforcement — a credential issued for `authentication` cannot + be used for `signing` (`CPX-009`) + +--- + +## 8. Profile-governed constraints (enforcement) + +DCM enforces the credential profile configuration at issuance and use. + +| Setting | minimal/dev | standard/prod | fsi/sovereign | +|---|---|---|---| +| Default TTL | P365D | P90D | P30D | +| Max TTL | unlimited | P365D | P90D | +| Rotation grace period | P7D | P3D | P1D | +| HSM required | No | No | Yes (signing keys) | +| Idle credential detection | Disabled | P90D warning | P30D auto-revoke | +| IP binding | Optional | Optional | Required | +| FIPS level | None | Level 1 | Level 2 (fsi) / Level 3 (sovereign) | + +Full per-profile configuration matrix is in +[udlm/governance/credentials.md Section 12.1](https://github.com/croadfeldt/udlm/blob/main/governance/credentials.md). +DCM applies these constants at issuance — Credential Provider may issue +shorter than max_lifetime; never longer. + +### 8.1 Algorithm enforcement + +DCM rejects credentials with forbidden algorithms (MD5, SHA-1, DES, 3DES, +RC4, RSA < 2048, ECDSA < P-256) at issuance regardless of profile. Approved +algorithms vary per profile: + +- `minimal/dev`: negative list (forbidden_algorithms enforced; everything + else permitted) +- `standard+`: positive list per credential type +- `fsi`: FIPS-approved subset (Ed25519 excluded from FIPS 140-2 in `fsi`; + permitted in standard) +- `sovereign`: hsm_backed_only across all types + +See [`../reference/implementation-standards.md`](../../reference/implementation-standards.md) +for the algorithm and FIPS-level decisions DCM makes. + +--- + +## 9. Integration with external services + +| External system | Integration | +|---|---| +| HashiCorp Vault PKI | Register as Credential Provider with `secret_engine: vault`; supports x509_certificate, secrets, dynamic secrets | +| AWS Secrets Manager | Register as Credential Provider with `secret_engine: aws_secrets_manager` | +| Azure Key Vault | Register as Credential Provider with `secret_engine: azure_key_vault` | +| GCP Secret Manager | Register as Credential Provider with `secret_engine: gcp_secret_manager` | +| Local HSM (sovereign) | Register as Credential Provider with `secret_engine: local_hsm`; FIPS 140-2 Level 3 | +| Enterprise CA (cert-manager / Venafi / EJBCA) | Register as Credential Provider with `external_ca_config` supporting ACME / EST / SCEP / CMP | + +### 9.1 External CA integration + +DCM's Credential Provider model natively supports external CAs as backends +for x509_certificate. When configured as the trust anchor for internal +component auth, DCM's component certificate requests flow through the +Credential Provider interface instead of the built-in Internal CA. This +makes DCM's internal mTLS fully auditable through existing enterprise PKI +infrastructure — a key requirement for fsi/sovereign profiles. + +```yaml +external_ca_config: + ca_protocol: acme | est | scep | cmp | vault_pki | aws_acm_pca | azure_key_vault + ca_endpoint: + issued_cert_lifetime: P90D + subject_template: "CN={{component_type}}-{{component_uuid}},O=dcm-internal" +``` + +--- + +## 10. Idle credential detection + +A credential issued but never retrieved within the declared threshold +triggers an idle alert: + +```yaml +idle_credential_record: + credential_uuid: + issued_at: + threshold_hours: 48 + last_checked_at: + retrieval_count: 0 + status: idle_alert_pending +``` + +Idle threshold by profile: P30D (minimal) → PT12H (sovereign). The credential +is NOT automatically revoked at the threshold — alert only. Auto-revocation +after 2× threshold is profile-configurable (`CPX-010`). + +--- + +## 11. Lifecycle state machine (DCM realization) + +``` + ┌──────────────┐ + issuance │ │ expiry / explicit + ─────────────────►│ ACTIVE │────revocation──────────► REVOKED / EXPIRED + │ │ + └──────┬───────┘ + │ rotation initiated + ▼ + ┌──────────────┐ + │ ROTATING │ both old and new valid + │ │ during transition window + └──────┬───────┘ + │ transition window ends + │ or emergency revocation + ▼ + REVOKED +``` + +Every transition writes an audit record with `credential_uuid`, transition +type, and trigger metadata. + +--- + +## 12. Policy IDs (DCM realization) + +| Policy | Rule | +|---|---| +| `CPX-001-DCM` | DCM stores only credential metadata; credential values never in DCM data model, GitOps stores, Realized State Store, or Audit Store | +| `CPX-002-DCM` | Every DCM provider interaction presents a scoped, short-lived `dcm_interaction` credential; providers reject calls without one (403) | +| `CPX-003-DCM` | DCM propagates revocation within profile-governed cache TTL (PT1M standard; PT30S fsi/sovereign) | +| `CPX-004-DCM` | DCM emergency rotation has no transition window; old revoked immediately; new delivered via fastest channel | +| `CPX-005-DCM` | DCM audits first credential value retrieval in all profiles; subsequent retrievals in standard+ | +| `CPX-006-DCM` | Actor deprovisioning triggers immediate revocation of all credentials issued to the actor (parallel with session revocation) | +| `CPX-007-DCM` | Entity decommissioning triggers revocation of all credentials scoped to entity before decommission confirmed | +| `CPX-008-DCM` | DCM rejects unbound credentials in fsi/sovereign; IP-bound or HSM-backed required | +| `CPX-009-DCM` | DCM declares algorithm + key_usage on every credential at issuance (standard+); enforces key_usage at validation | +| `CPX-010-DCM` | DCM fires idle detection at profile threshold; alert-only; auto-revocation after 2× threshold profile-configurable | +| `CPX-011-DCM` | DCM compliance overlays always tighten (never relax) base profile credential requirements | +| `CPX-012-DCM` | CPX-001-DCM applies in ALL profiles including minimal; no profile permits credential values in DCM stores | diff --git a/architecture/credentials-and-auth/provider-callback.md b/architecture/credentials-and-auth/provider-callback.md new file mode 100644 index 0000000..8ad8ab0 --- /dev/null +++ b/architecture/credentials-and-auth/provider-callback.md @@ -0,0 +1,417 @@ +--- +Document Status: ✅ Stable — DCM implementation +Document Type: Architecture Reference — Provider Callback Authentication +Established: 2026-05-26 +Maps to: udlm/contracts/provider-callback-auth.md +--- + +# Provider Callback Authentication — mTLS + Interaction Credential + +> **Implements contracts defined in UDLM**: +> [udlm/contracts/provider-callback-auth.md](https://github.com/croadfeldt/udlm/blob/main/contracts/provider-callback-auth.md). +> UDLM defines the mechanism-neutral two-layer authentication contract: any +> callback MUST be validated via two independent identity factors. DCM +> picks **mTLS as Layer 1** and **interaction credential as Layer 2** as +> its specific realization. A peer DCM realization could pick different +> layers (JWT + signed assertion, hardware-backed tokens, etc.) and remain +> UDLM-conformant — provided it declares its chosen mechanism via the +> schema-sharing protocol +> ([udlm/contracts/schema-sharing.md](https://github.com/croadfeldt/udlm/blob/main/contracts/schema-sharing.md)). + +--- + +## 1. The mechanism + +DCM realizes UDLM's two-layer auth contract with: + +| Layer | UDLM contract | DCM mechanism | +|---|---|---| +| 1 (transport identity) | "any peer MUST attest provider identity at registration via a verifiable mechanism" | **mTLS** — the provider presents its registered X.509 certificate; DCM validates the chain against the registered CA | +| 2 (operation authorization) | "every callback MUST present an independently-verifiable credential scoped to the operation" | **Interaction credential** — a `dcm_interaction` typed credential issued by the Credential Provider, presented as `Authorization: Bearer `, scoped to provider_uuid + allowed_operations | + +Both layers are required on every callback. mTLS alone proves identity but +not authorization; credential alone proves authorization but not identity. + +This mechanism is **DCM-specific**. A federation peer's DCM realization must +declare its chosen mechanism via the schema-sharing bundle so federated peers +can verify each other. + +--- + +## 2. Provider certificate storage and validation + +### 2.1 Certificate registration + +At provider registration: + +```yaml +provider_registration: + certificate: + pem: + ca_chain: + rotation_interval: P90D +``` + +DCM validates at registration: +- Certificate chain valid and trusted +- Certificate not in DCM's Credential Revocation Registry +- Certificate `CN` or `SAN` matches the declared `handle` +- Certificate `expires_at` not in the past + +DCM stores the certificate fingerprint. On every subsequent inbound +connection, DCM validates the presented certificate against the stored +fingerprint for this provider. + +### 2.2 mTLS enforcement at TLS handshake + +``` +Provider → DCM: + TLS ClientHello → ServerHello + DCM certificate + Provider verifies DCM certificate (DCM identity) + Provider sends its certificate + DCM validates: + 1. Certificate chain → registered CA for this provider + 2. Certificate fingerprint → matches stored fingerprint for provider_uuid + 3. Certificate not in Credential Revocation Registry + 4. Certificate expires_at not expired + Any failure → TLS handshake rejected; connection refused +``` + +### 2.3 Certificate rotation + +Providers rotate certificates on the declared `rotation_interval`. DCM fires +a `P14D` warning event when approaching expiry. During the rotation transition +window (P7D), DCM accepts both the current and new certificate +simultaneously. After the window, only the new certificate is accepted. + +--- + +## 3. Interaction credential issuance and management + +### 3.1 Provider callback credential + +At provider activation, DCM issues a `dcm_interaction` credential through +the Credential Provider: + +```yaml +provider_callback_credential: + credential_uuid: + credential_type: dcm_interaction + issued_to: + provider_uuid: + provider_handle: + issued_at: + expires_at: # profile-governed + operation_scope: + allowed_operations: + - realized_state_push + - capacity_report + - interim_status + - update_notification + - lifecycle_event + - notification_poll + non_transferable: true + bound_to_ip: # fsi/sovereign: required + revocation_check_url: +``` + +Presented as `Authorization: Bearer ` on all callback API calls. + +**Key property:** the credential is scoped to the `provider_uuid` — not to +specific entities or operations within that provider. Entity-level scope is +enforced separately (Section 6). + +### 3.2 Credential issuance lifecycle + +``` +Registration approved (provider status → ACTIVE) + ▼ API Gateway requests credential from Credential Provider: + │ credential_type: dcm_interaction + │ issued_to.provider_uuid: + │ allowed_operations: [] + │ expires_at: + ▼ Credential Provider issues credential + │ Stores credential_record in Credential Store + │ Returns credential_value (the bearer token) + ▼ DCM delivers credential to provider via activation response + │ POST /api/v1/admin/providers/{uuid}:approve + │ Response includes: credential_ref (UUID for retrieval) + ▼ Provider retrieves credential value via Credential Provider endpoint + │ GET {service_provider_endpoint}/credentials/{credential_ref}/value + │ (Requires the registration token used at initial registration — one-time bootstrap) + ▼ Provider stores credential securely and uses for all callback calls +``` + +### 3.3 Credential lifetime by profile + +| Profile | Lifetime | Rotation trigger | IP binding | +|---|---|---|---| +| minimal | PT8H | Pre-expiry P1H | No | +| dev | PT4H | Pre-expiry P30M | No | +| standard | PT1H | Pre-expiry PT10M | No | +| prod | PT30M | Pre-expiry PT5M | Optional | +| fsi | PT15M | Pre-expiry PT3M | Required | +| sovereign | PT15M + hardware attestation | Pre-expiry PT3M | Required; HSM-bound | + +### 3.4 Rotation protocol + +``` +PT{rotation_trigger} before credential expiry: + ▼ DCM initiates rotation + │ Requests new credential from Credential Provider + │ rotation_of: + │ same allowed_operations scope; new expires_at + ▼ Credential Provider issues new; old NOT yet revoked + ▼ DCM pushes rotation notification to provider + │ POST {provider_health_endpoint}/credential-rotation (if supported) + │ OR: credential.rotating event published to Message Bus + ▼ Transition window: both credentials valid + │ Duration: 50% of credential lifetime + ▼ Transition window closes; old credential revoked + │ Revocation event → all components update revocation cache +``` + +If the provider fails to pick up the new credential before the window closes, +the old credential is revoked and subsequent callbacks return `403 Forbidden` +with code `CREDENTIAL_EXPIRED`. The provider must re-register to recover. + +--- + +## 4. mTLS enforcement at callback endpoint + +The Provider Callback API endpoints all require Layer 1 (mTLS) at the TLS +termination point. If mTLS fails, the TLS handshake is rejected before +Layer 2 evaluation: + +| Endpoint | mTLS required | Credential required | +|---|---|---| +| `POST /api/v1/providers` (registration) | Yes | Bootstrap registration token | +| `POST /api/v1/providers/{provider_uuid}/capacity` | Yes | dcm_interaction credential | +| `PUT /api/v1/instances/{resource_id}/status` | Yes | dcm_interaction credential | +| `POST /api/v1/provider/entities/{entity_uuid}/status` | Yes | dcm_interaction credential | +| `POST /api/v1/provider/entities/{entity_uuid}/update-notification` | Yes | dcm_interaction credential | +| `GET /api/v1/provider/notifications/{notification_uuid}` | Yes | dcm_interaction credential | +| `POST /api/v1/instances/{resource_id}/events` | Yes | dcm_interaction credential | + +--- + +## 5. Validation logic at callback time + +Layer 2 validation runs after the TLS handshake completes: + +``` +1. Extract credential_value from Authorization: Bearer header + → Missing or malformed: 401 Unauthorized; MISSING_CREDENTIAL audit record + +2. Look up credential_record by credential_value hash + → Not found: 401 Unauthorized; CREDENTIAL_NOT_FOUND audit record + +3. Check credential_record.status is 'active' + → Revoked: 403 Forbidden; code: CREDENTIAL_REVOKED + → Expired: 403 Forbidden; code: CREDENTIAL_EXPIRED + +4. Check credential_record.expires_at > now + → Expired: 403 Forbidden; code: CREDENTIAL_EXPIRED + +5. Check credential_record.issued_to.provider_uuid matches: + a. The provider_uuid in the URL path (where applicable) + b. The mTLS certificate's registered provider (Layer 1 binding) + → Mismatch: 403 Forbidden; code: CREDENTIAL_SCOPE_VIOLATION + +6. Check operation_type for this endpoint is in allowed_operations + → Not in scope: 403 Forbidden; code: OPERATION_NOT_IN_SCOPE + +7. If bound_to_ip is set: verify client IP matches + → Mismatch: 403 Forbidden; code: IP_BINDING_VIOLATION +``` + +All failures write an audit record with credential_uuid, provider_uuid, +endpoint, and failure reason. + +After 5 consecutive `CREDENTIAL_SCOPE_VIOLATION` or `IP_BINDING_VIOLATION` +failures from the same provider within PT1H, DCM fires +`security.unsanctioned_provider_write` and notifies the platform admin +(urgency: critical). + +--- + +## 6. Entity authorization checks + +A valid credential proves the caller is the registered provider. It does NOT +prove the provider is authorized to act on a specific entity. Entity-level +authorization runs on each call. + +### 6.1 Resource ownership binding (realized_state_push, interim_status) + +``` +PUT /api/v1/instances/{resource_id}/status + +DCM checks: + 1. Look up Requested State record for resource_id + 2. Verify credential's provider_uuid matches provider_uuid in Requested State + 3. Verify entity is in a lifecycle state that permits this push + (PROVISIONING, UPDATING, or DECOMMISSIONING — not OPERATIONAL, not DECOMMISSIONED) + + → Mismatch on provider_uuid: 403; code: ENTITY_NOT_OWNED_BY_PROVIDER + → Wrong lifecycle state: 409; code: INVALID_LIFECYCLE_STATE_FOR_PUSH +``` + +A provider receiving a resource_id (e.g., by observing traffic) cannot push +realized state for an entity it was not dispatched to. + +### 6.2 Update notification binding + +``` +POST /api/v1/provider/entities/{entity_uuid}/update-notification + +DCM checks: + 1. Look up Realized State record for entity_uuid + 2. Verify credential's provider_uuid matches the provider_uuid in the most + recent Realized State + 3. Verify the provider's registration includes the update_capability + declared in the notification_type field + + → Provider not current owner: 403; code: ENTITY_NOT_OWNED_BY_PROVIDER + → Update type not declared: 403; code: UPDATE_TYPE_NOT_DECLARED +``` + +### 6.3 Lifecycle event binding + +``` +POST /api/v1/instances/{resource_id}/events + +DCM checks: + 1. Verify credential's provider_uuid matches provider on record for resource_id + 2. Verify resource is in an operational state (not DECOMMISSIONED) + 3. Verify event_type is in the standard event catalog + + → Provider not current owner: 403; code: ENTITY_NOT_OWNED_BY_PROVIDER + → Entity decommissioned: 409; code: ENTITY_DECOMMISSIONED + → Unknown event_type: 400; code: UNKNOWN_EVENT_TYPE +``` + +--- + +## 7. Registration token generation and validation + +The initial `POST /api/v1/providers` registration call cannot use a callback +credential (none exists yet). DCM uses a single-use registration token: + +```yaml +registration_token: + token_uuid: + token_value: + issued_at: + expires_at: # typically PT72H + scope: + provider_type_id: service_provider + provider_handle_pattern: "eu-west-*" + grants_auto_approval: true | false + used: false # single-use; set true after first successful use +``` + +Passed as `Authorization: Bearer ` on the initial registration +call. After first successful registration, marked `used: true`. Re-registration +requires a new token (per `PCA-006`). + +**mTLS still required for registration** — the provider must present the +certificate declared in the payload, proving private-key possession. + +### 7.1 Re-registration + +For re-registration (same `name`, updating version or capabilities), the +provider uses its active callback credential. Re-registration that changes +sovereignty declaration requires a new registration token (treated as a new +registration requiring new approval; `PCA-007`). + +--- + +## 8. Revocation enforcement + +### 8.1 Triggers + +| Trigger | What happens | +|---|---| +| Provider deregistered | All callback credentials for provider revoked immediately | +| 5+ scope violations in PT1H | Provider suspended; credential revoked; platform admin notified | +| Provider certificate expiry without rotation | Credential revoked at certificate expiry | +| Platform admin explicit revocation | Immediate; provider must re-register | +| Provider compromise suspected | Emergency revocation; Recovery Policy evaluates affected entities | + +### 8.2 Revocation cache + +DCM components maintain a local Credential Revocation Cache populated from +the Message Bus `credential.revoked` event stream: + +- Cache TTL matches the maximum credential lifetime for the active profile +- On cache miss: remote check against Credential Store (prevents stale cache + from accepting revoked credentials) +- Cache invalidation is immediate on `credential.revoked` event receipt + (not TTL-based) + +The revocation cache ensures revocation propagates within PT30S even without +a cache miss triggering a remote lookup. + +--- + +## 9. Emergency revocation + +``` +Platform admin triggers emergency revocation: + POST /api/v1/admin/providers/{provider_uuid}/revoke-credential + { reason: , suspend_provider: true | false } + + ▼ DCM revokes credential immediately + │ credential_record.status → revoked + │ Revocation event → Message Bus + │ All DCM components update revocation cache (within PT30S) + ▼ If suspend_provider: true + │ Provider status → SUSPENDED + │ New requests not routed to this provider + │ Active realizations enter PENDING_REVIEW state + ▼ Recovery Policy evaluates affected entities: + Entities currently hosted at provider: notify Tenant owners + In-progress operations: depends on Recovery Policy profile +``` + +--- + +## 10. Schema sharing declaration + +Per the UDLM compatibility model, DCM declares its chosen mechanism in its +schema bundle so federated peers can verify and interoperate: + +```yaml +# Excerpt from DCM's schema bundle published per udlm/contracts/schema-sharing.md +provider_callback_auth: + contract_version: 1.0 + layer_1_mechanism: mtls + layer_2_mechanism: interaction_credential + layer_1_protocol_refs: + - rfc_5280 # X.509 PKI + - rfc_8446 # TLS 1.3 + layer_2_credential_type: dcm_interaction + layer_2_format: bearer_token + layer_2_transport: Authorization HTTP header +``` + +A federated peer that picks a different mechanism (e.g., JWT + signed +assertion) declares its mechanism similarly; cross-peer federation +negotiation includes mechanism compatibility checks. + +--- + +## 11. Policy IDs (DCM realization) + +| Policy | Rule | +|---|---| +| `PCA-001-DCM` | All DCM provider calls present both valid mTLS (Layer 1) and valid interaction credential (Layer 2); neither alone is sufficient | +| `PCA-002-DCM` | Interaction credentials are scoped to provider_uuid; cannot act on entities at other providers | +| `PCA-003-DCM` | Entity-level authorization checked on every realized_state_push, update_notification, and lifecycle_event call independent of credential validity | +| `PCA-004-DCM` | Five consecutive scope or IP binding violations within PT1H triggers automatic provider suspension and admin notification | +| `PCA-005-DCM` | Interaction credentials issued by the Credential Management Service — the authoritative source for all issuance, rotation, and revocation | +| `PCA-006-DCM` | Registration tokens are single-use; a token used once is permanently invalidated regardless of expires_at | +| `PCA-007-DCM` | Re-registration changing sovereignty declaration requires a new registration token and new approval pipeline; version/capability updates do not | +| `PCA-008-DCM` | Interaction credentials must be rotated before expiry; expired without rotation → CREDENTIAL_EXPIRED; provider must obtain new via platform admin | +| `PCA-009-DCM` | For fsi/sovereign, interaction credentials are IP-bound; mismatched IP → rejected regardless of validity | +| `PCA-010-DCM` | All inbound provider calls — including rejected — produce an audit record; no silent failures |