KEY-WP-0005-T02-T03: cached tenant_roles claim, close workplan
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 1m21s
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 1m21s
New internal/adapters/tenantengine package, mirroring
internal/adapters/{lldap,privacyidea,authelia}'s shape: Client.Roles()
calls tenant-engine's cache-read endpoint. Fails open by construction --
unreachable, non-200, malformed body, or a nil *Client all return
(nil, false), never an error to specially handle. Wired into
TokenHandler.TenantEngine (nil by default, existing tests unaffected);
token.go stamps tenant_roles only when ok.
7 adapter tests plus 3 TokenHandler-level tests proving the actual
required behavior end-to-end: present when reachable, token issuance still
200 with every other core claim intact when unreachable (tenant_roles
simply absent -- the literal done-criteria), absent when not configured.
Real bug found and fixed at the source, not worked around: the first live
cross-process check (real flex-auth, real tenant-engine, this adapter)
returned tenant_not_found for a tenant that existed -- tenant-engine's read
endpoint was keyed by its internal tenant_id, but key-cape only ever has
the tenant's profile identifier. Fixed in tenant-engine
(ADHOC-2026-07-24), re-verified with the same live three-process chain --
roles=[IAM] ok=true.
Workplan closed: T01-T03 done. Explicitly still open: client_credentials /
service-token issuance -- no such flow exists in token.go at all, a
materially larger separate piece of work than either task here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
fb888579dc
commit
44da5f5f99
5 changed files with 375 additions and 4 deletions
|
|
@ -11,6 +11,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"keycape/internal/adapters/tenantengine"
|
||||
"keycape/internal/domain"
|
||||
profileerrors "keycape/internal/errors"
|
||||
"keycape/internal/server/telemetry"
|
||||
|
|
@ -25,6 +26,9 @@ type TokenHandler struct {
|
|||
Issuer string
|
||||
TokenLifetime time.Duration
|
||||
Emitter telemetry.Emitter
|
||||
// TenantEngine sources the optional tenant_roles claim (KEY-WP-0005-T02).
|
||||
// Nil disables it entirely -- token issuance never depends on it.
|
||||
TenantEngine *tenantengine.Client
|
||||
}
|
||||
|
||||
// tokenResponse is the JSON body returned on a successful token exchange.
|
||||
|
|
@ -130,12 +134,20 @@ func (h *TokenHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
// Core claims required by net-kingdom/canon/standards/iam-profile_v0.3.md
|
||||
// for every production token -- not scope-gated, unlike the recommended
|
||||
// human claims above (KEY-WP-0005-T01).
|
||||
claims["tenant"] = effectiveTenant(user)
|
||||
tenant := effectiveTenant(user)
|
||||
claims["tenant"] = tenant
|
||||
claims["principal_type"] = "human"
|
||||
claims["groups"] = nonNilStrings(user.Groups)
|
||||
claims["roles"] = nonNilStrings(user.Roles)
|
||||
claims["assurance"] = assuranceClaim(sess.MFAVerified, now)
|
||||
|
||||
// Optional cached tenant_roles claim (KEY-WP-0005-T02). Fails open --
|
||||
// see internal/adapters/tenantengine's package doc for why this is the
|
||||
// one place in the whole tenant_roles design where that's correct.
|
||||
if roles, ok := h.TenantEngine.Roles(ctx, tenant); ok {
|
||||
claims["tenant_roles"] = roles
|
||||
}
|
||||
|
||||
// 7. Sign JWT with RSA-SHA256.
|
||||
kid := "key-1" // static kid for v0.1
|
||||
jwtToken, err := buildJWT(claims, kid, h.SigningKey)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue