key-cape/docs/approval-engine-provisioning-request.yaml
tegwick 329e48f64a
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 46s
Let a human token carry the zone it is issued into, without relabelling anyone
KEY-WP-0013-T05's tenant blocker did not need the decision it was waiting on. The
two proposed resolutions differ in where a human's tenant comes from -- the
directory record, or the client registration -- and an implementation exists that
is correct under either, so the choice can be made later without another
migration.

A client registration may now declare a tenant. humanTenant() resolves it by four
rules: no declaration keeps the directory answer unchanged; a declared zone
applies where the directory has placed the user nowhere; agreement passes; and a
declared zone conflicting with a directory assignment refuses issuance rather
than relabelling the user.

The refusal is the design, not an edge case. A registration can bind a zone for
unplaced users and can never move a placed one, so this gets the approval chain
its tenant:platform without writing a general cross-tenant override into the
issuer. It fails closed rather than picking a winner, because either answer would
be a silent cross-tenant assertion, and it reports 403 with
error_type: tenant_binding so an operator can tell a misconfigured registration
from a rejected login. If the owners later populate directory tenants, the same
code stops supplying the zone and starts enforcing agreement with it.

Safe only because client registrations are static and deployment-owned. The
tenant contract records that this rule must be revisited if dynamic client
registration is ever admitted.

Tests cover all four rules; neutering the conflict check fails the relabel test
rather than passing silently.

T05 now waits on one thing only: the client_id and callback URI from
informed-decision once it has a deployed origin.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uV8zoCKpA1WRAxsKRYbdH

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1182213@bnt-lap001
Assistant-Session: 966597b9-ae61-46a4-8b9e-1594ab3ec4ad
2026-09-09 14:40:36 +02:00

100 lines
5.5 KiB
YAML

# Proposed non-secret admission packet. This is not executable authorization.
# Tenant for both requests is tenant:platform per decision
# 5ed3fb35-eca9-413a-82b9-95171ba85bf6 (landlord zone). Exact spelling required
# across the approval store, these JWT claims and the lifecycle CheckRequest;
# no alias to platform or tenant:coulomb.
status: awaiting-custody-admission
owner: key-cape
resource_audience: approval-engine
issuer: https://kc.coulomb.social
registration_source: config/service-clients.example.yaml
requests:
- client_id: secrets-engine-approval
subject: service:secrets-engine
tenant: tenant:platform
scopes: [approval:read, approval:consume]
lifetime: 15m
proposed_openbao_path: platform/workloads/secrets-engine/approval-client
field: CLIENT_SECRET
proposed_kubernetes_secret: sso/keycape-secrets-engine-approval-client
kubernetes_key: client-secret
keycape_environment: KEYCAPE_SECRETS_ENGINE_APPROVAL_CLIENT_SECRET
custody_owner: railiance-platform
consumer: secrets-engine
- client_id: approval-engine-operator
subject: service:approval-engine-operator
tenant: tenant:platform
scopes: [approval:create, approval:read, approval:approve, approval:revoke, approval:supersede, approval:observe, approval:emit]
lifetime: 15m
proposed_openbao_path: platform/workloads/approval-engine/operator-client
field: CLIENT_SECRET
proposed_kubernetes_secret: sso/keycape-approval-engine-operator-client
kubernetes_key: client-secret
keycape_environment: KEYCAPE_APPROVAL_ENGINE_OPERATOR_CLIENT_SECRET
custody_owner: railiance-platform
consumer: approval-engine-operator
human_registration:
status: awaiting-exact-callback
blocks_service_client_rollout: false
# Owner identified 2026-09-09: informed-decision (hub repo cf4c7da8). It
# supplies client_id and callback URI from INFD-WP-0001-T07 once it has a
# deployed origin. approval-engine is a bearer-only resource server and never
# owned those strings.
owner: informed-decision
# approval:read added 2026-09-09 on approval-engine's finding: the surface
# cannot render a decision without GET /v1/approvals/{id} and /claim, so the
# earlier scope set let an approver submit what they could not display.
scopes: [openid, approval:read, approval:approve]
mfa_required: true
client_type: public
grant: authorization_code + S256 PKCE
never: [approval:consume]
# Required. Without it the token carries tenant:coulomb from the directory
# default and approval-engine refuses it. See docs/tenant-claim-contract.md.
tenant: tenant:platform
keycape_side_resolved: |
2026-09-09: a human token can now carry tenant:platform. A client
registration may declare a tenant; humanTenant() supplies it when the
directory has not placed the user, requires agreement when it has, and
refuses issuance on conflict rather than relabelling. Safe only because
registrations are static and deployment-owned. Still outstanding from
elsewhere: the exact client_id and callback URI from informed-decision
(INFD-WP-0001-T07), and whether the owners prefer the directory-sourced
resolution instead, which this implementation degrades into cleanly.
verification:
# The first two lines are now one runnable command per client; see
# docs/native-authentication.md, "Verifying a live registration". It writes
# nothing and prints no value, so it is safe to run against production.
- command: |
keycape verify-client -issuer https://kc.coulomb.social
-client-id secrets-engine-approval -audience approval-engine
-scope "approval:read approval:consume"
-secret-env KEYCAPE_SECRETS_ENGINE_APPROVAL_CLIENT_SECRET
-expect-subject service:secrets-engine -expect-tenant tenant:platform
-expect-roles secrets-engine
-deny-scope "approval:approve approval:revoke approval:supersede"
- command: |
keycape verify-client -issuer https://kc.coulomb.social
-client-id approval-engine-operator -audience approval-engine
-scope "approval:create approval:read approval:approve approval:revoke approval:supersede approval:observe approval:emit"
-secret-env KEYCAPE_APPROVAL_ENGINE_OPERATOR_CLIENT_SECRET
-expect-subject service:approval-engine-operator -expect-tenant tenant:platform
-expect-roles approval-operator
-deny-scope "approval:consume"
- Check KeyCape and consumer readiness without emitting secrets or tokens.
- Preserve existing registrations and signing key; record versions and image digest.
- Human consume denial is approval-engine's to verify at its resource; KeyCape
proves only that the human client is never issued a consume grant.
blockers:
- Admit exact custody paths, field delivery, consumer identities and lifecycle authority.
- Resolve attended first-provision authority through the custody owner.
- Verify the actual upstream ID-token issuer before production image rollout.
custody_return:
owner_record: railiance-platform/workplans/RPF-WP-0035-credential-lane-implementation.md#Admit-KeyCape-approval-engine-client-custody-and-delivery
requests: [CCR-2026-0017, CCR-2026-0018]
status: proposed-awaiting-owner-approval
field_correction: CLIENT_SECRET
scope: KeyCape verifier-side copies only; client-side retrieval is not admitted.
human_registration_gate:
- The approver UI owner must supply its real client ID and exact callback.
- This gate blocks human approval entry; it does not block the two independent client_credentials registrations or service startup.