Operator decision 5ed3fb35-eca9-413a-82b9-95171ba85bf6 accepts tenant:platform as the platform management tenant for the Glas approval chain, requiring exact spelling across the approval store, the service-client JWT claim and the lifecycle CheckRequest. Changes the tenant field on secrets-engine-approval and approval-engine-operator only, in the registration fixture and the provisioning packet. Unrelated clients and the human directory default keep tenant:coulomb, and no audience, scope, subject, role, lifetime or MFA grant changes. Adds issuance evidence that the approval shape emits tenant:platform exactly and never an alias the caller requests, that the OpenBao client gains no cross-tenant reach, and a fixture guard pinning every reviewed client's tenant. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P Assistant: claude-code Assistant-Model: opus Assistant-Process: 713576@bnt-lap001 Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
4.7 KiB
Tenant claim contract
States what KeyCape owns and emits for the tenant claim. Raised by glas-harness
message 356f6977-d361-4e3b-83ab-b2c7f4759286 (GLAS-WP-0015) and resolved by
operator decision 5ed3fb35-eca9-413a-82b9-95171ba85bf6 — "Use tenant:platform
for the Glas approval dependency chain", recorded in
glas-harness/docs/platform-tenant-decision.md.
tenant:platform is the platform management, administration and services tenant
(the landlord zone). KeyCape emits it verbatim for the two approval clients and
still creates no mapping between tenant vocabularies: no alias to platform or
tenant:coulomb, and no implicit cross-tenant grant.
What KeyCape emits
The tenant claim is bound at registration or directory-resolution time and is
never influenced by request parameters.
| Principal | Source of tenant |
Value in the reviewed registrations |
|---|---|---|
Service (client_credentials) |
The client's tenant field. Required by config validation — a client_credentials client without serviceSubject and tenant fails startup validation. |
In config/service-clients.example.yaml: tenant:platform for secrets-engine-approval and approval-engine-operator; tenant:coulomb for codex-railiance-platform and secrets-engine-openbao. |
Human (authorization_code) |
The directory user's tenant, falling back to the platform default when unset. | Directory value, else tenant:coulomb. |
tenant, tenant_hint, audience and resource request parameters cannot
change the claim. tenant_hint on /authorize reaches registration/enrollment
handoffs only; it is not a claim input.
The approval chain, after the decision
All three layers now use one exact spelling, tenant:platform:
| Layer | Value | Owner |
|---|---|---|
| Approval store tenant | tenant:platform |
approval-engine |
Service-client JWT tenant claim |
tenant:platform |
KeyCape (this repo) |
| Lifecycle CheckRequest tenant | tenant:platform |
flex-auth |
KeyCape performs no normalization, prefix-stripping or aliasing, so a
resource server compares tenant as an exact string. The previously observed
platform and tenant:coulomb spellings are not accepted aliases for
tenant:platform; a token carrying either must be denied by the resource server.
Scope of the alignment
The decision changed the tenant field on exactly two registrations,
secrets-engine-approval and approval-engine-operator, plus the matching
entries in docs/approval-engine-provisioning-request.yaml. Deliberately
unchanged:
codex-railiance-platformandsecrets-engine-openbaokeeptenant:coulomb.- Human directory resolution keeps its
tenant:coulombdefault. - No cross-tenant grant is implied: a
tenant:platformtoken conveys no reach intotenant:coulombresources, and audiences, scopes, subjects, roles, lifetimes and MFA requirements are untouched.
This resolves the choice of tenant only. Live provisioning of the two clients remains gated on deployment-owned custody and the separate verification gates in KEY-WP-0013-T02; KeyCape changed no live registration or policy subject.
Evidence
src/internal/server/oidc/tenant_test.go:
TestServiceTenantIsBoundToRegistrationAndIgnoresRequestParameters— a request supplyingtenant=tenant:platformandtenant_hint=platformstill yields the registeredtenant, signature-verified against/jwks.TestServiceTenantsAreDistinctPerRegistration— two registrations with different tenants yield their own values and never each other's; this is the wrong-tenant denial basis for an exact-comparison resource server.TestHumanTenantClaimUsesDirectoryValueThenPlatformDefault— human tokens carry the directory tenant, defaulting totenant:coulombrather than an empty claim.TestApprovalClientIssuesExactPlatformTenantAndRejectsAliases— the approval-client shape issuestenant:platformexactly, withaud=approval-engine, even when the caller asks forplatform,tenant:coulomborTENANT:PLATFORM.TestUnrelatedServiceClientKeepsCoulombTenant— the OpenBao login client keepstenant:coulombeven when the request asks fortenant:platform, so the alignment grants no cross-tenant reach.
src/cmd/keycape/clients_test.go:
TestServiceRegistrationTenantsAreExactPerDecision— loads the real registration fixture and pins the exact tenant of every reviewed client, so drift or an alias reintroduced intoconfig/service-clients.example.yamlfails the build.
These are local issuance proofs. They are not live-rollout evidence; see
docs/approval-engine-auth-contract.md and KEY-WP-0013-T02 for the deployment
boundary. No token or secret values appear in this document or in test output.