Align approval registrations to the tenant:platform decision
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 33s
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 33s
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
This commit is contained in:
parent
519e4d8ef1
commit
7a6666d1f9
8 changed files with 199 additions and 28 deletions
|
|
@ -104,3 +104,72 @@ func TestHumanTenantClaimUsesDirectoryValueThenPlatformDefault(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Issuance evidence for the aligned approval chain: a token minted for the
|
||||
// approval registrations carries tenant:platform exactly, and never the
|
||||
// rejected alias spellings, even when the caller asks for them
|
||||
// (decision 5ed3fb35-eca9-413a-82b9-95171ba85bf6).
|
||||
func TestApprovalClientIssuesExactPlatformTenantAndRejectsAliases(t *testing.T) {
|
||||
h := serviceTokenHandler(t)
|
||||
h.ClientConfig["secrets-engine-approval"] = &domain.Client{
|
||||
ClientID: "secrets-engine-approval",
|
||||
Audience: "approval-engine",
|
||||
AllowedScopes: []string{"approval:read", "approval:consume"},
|
||||
GrantTypes: []string{"client_credentials"},
|
||||
ClientType: "confidential",
|
||||
ClientSecret: "approval-test-secret",
|
||||
ServiceSubject: "service:secrets-engine",
|
||||
Tenant: "tenant:platform",
|
||||
Roles: []string{"secrets-engine"},
|
||||
}
|
||||
for _, requested := range []string{"", "platform", "tenant:coulomb", "TENANT:PLATFORM"} {
|
||||
values := url.Values{"grant_type": {"client_credentials"}, "scope": {"approval:read"}}
|
||||
if requested != "" {
|
||||
values.Set("tenant", requested)
|
||||
values.Set("tenant_hint", requested)
|
||||
}
|
||||
req := tokenRequest(values)
|
||||
req.SetBasicAuth("secrets-engine-approval", "approval-test-secret")
|
||||
w := httptest.NewRecorder()
|
||||
h.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("requested %q: status %d", requested, w.Code)
|
||||
}
|
||||
token := decodeTokenResponse(t, w.Body.String())["access_token"].(string)
|
||||
claims := parseJWTPayload(t, token)
|
||||
if claims["tenant"] != "tenant:platform" {
|
||||
t.Fatalf("requested %q: tenant %v, want tenant:platform", requested, claims["tenant"])
|
||||
}
|
||||
if claims["aud"] != "approval-engine" {
|
||||
t.Fatalf("requested %q: audience %v", requested, claims["aud"])
|
||||
}
|
||||
verifyWithJWKS(t, h, token)
|
||||
}
|
||||
}
|
||||
|
||||
// The aligned decision grants no cross-tenant reach: the unrelated OpenBao
|
||||
// login client keeps tenant:coulomb.
|
||||
func TestUnrelatedServiceClientKeepsCoulombTenant(t *testing.T) {
|
||||
h := serviceTokenHandler(t)
|
||||
h.ClientConfig["secrets-engine-openbao"] = &domain.Client{
|
||||
ClientID: "secrets-engine-openbao",
|
||||
AllowedScopes: []string{"openbao:login"},
|
||||
GrantTypes: []string{"client_credentials"},
|
||||
ClientType: "confidential",
|
||||
ClientSecret: "openbao-test-secret",
|
||||
ServiceSubject: "service:secrets-engine",
|
||||
Tenant: "tenant:coulomb",
|
||||
Roles: []string{"secrets-engine"},
|
||||
}
|
||||
req := tokenRequest(url.Values{"grant_type": {"client_credentials"}, "scope": {"openbao:login"}, "tenant": {"tenant:platform"}})
|
||||
req.SetBasicAuth("secrets-engine-openbao", "openbao-test-secret")
|
||||
w := httptest.NewRecorder()
|
||||
h.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status %d", w.Code)
|
||||
}
|
||||
token := decodeTokenResponse(t, w.Body.String())["access_token"].(string)
|
||||
if got := parseJWTPayload(t, token)["tenant"]; got != "tenant:coulomb" {
|
||||
t.Fatalf("tenant %v, want tenant:coulomb", got)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue