All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 46s
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
100 lines
5.5 KiB
YAML
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.
|