Draft KEY-WP-0005: IAM Profile core claims (tenant, principal_type, roles, assurance, tenant_roles)
Discovered while planning tenant-engine's flex-auth WriteAuthorizer
(TEN-WP-0003): src/internal/server/oidc/token.go emits none of
iam-profile_v0.3.md's Core Claims yet -- not tenant, principal_type,
groups, roles, or assurance. tenant_roles isn't a small addition on top of
existing tenant support; it's the last, optional claim on top of a
baseline that doesn't exist yet.
Registered, not implemented -- security-sensitive Go token-issuance code
deserves dedicated context before changing, not a rushed edit from a first
read of the file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 22:47:00 +02:00
|
|
|
|
---
|
|
|
|
|
|
id: KEY-WP-0005
|
|
|
|
|
|
type: workplan
|
|
|
|
|
|
title: "IAM Profile core claims: tenant, principal_type, groups, roles, assurance, tenant_roles"
|
|
|
|
|
|
domain: infotech
|
|
|
|
|
|
repo: key-cape
|
KEY-WP-0005-T02-T03: cached tenant_roles claim, close workplan
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>
2026-07-24 00:18:17 +02:00
|
|
|
|
status: finished
|
Draft KEY-WP-0005: IAM Profile core claims (tenant, principal_type, roles, assurance, tenant_roles)
Discovered while planning tenant-engine's flex-auth WriteAuthorizer
(TEN-WP-0003): src/internal/server/oidc/token.go emits none of
iam-profile_v0.3.md's Core Claims yet -- not tenant, principal_type,
groups, roles, or assurance. tenant_roles isn't a small addition on top of
existing tenant support; it's the last, optional claim on top of a
baseline that doesn't exist yet.
Registered, not implemented -- security-sensitive Go token-issuance code
deserves dedicated context before changing, not a rushed edit from a first
read of the file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 22:47:00 +02:00
|
|
|
|
owner: codex
|
|
|
|
|
|
topic_slug: netkingdom
|
|
|
|
|
|
created: "2026-07-23"
|
|
|
|
|
|
updated: "2026-07-23"
|
2026-08-25 20:13:23 +02:00
|
|
|
|
state_hub_workstream_id: "711a8303-69aa-5db6-9043-92530c6dc81b"
|
Draft KEY-WP-0005: IAM Profile core claims (tenant, principal_type, roles, assurance, tenant_roles)
Discovered while planning tenant-engine's flex-auth WriteAuthorizer
(TEN-WP-0003): src/internal/server/oidc/token.go emits none of
iam-profile_v0.3.md's Core Claims yet -- not tenant, principal_type,
groups, roles, or assurance. tenant_roles isn't a small addition on top of
existing tenant support; it's the last, optional claim on top of a
baseline that doesn't exist yet.
Registered, not implemented -- security-sensitive Go token-issuance code
deserves dedicated context before changing, not a rushed edit from a first
read of the file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 22:47:00 +02:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
# IAM Profile core claims: tenant, principal_type, groups, roles, assurance, tenant_roles
|
|
|
|
|
|
|
|
|
|
|
|
Discovered while planning `tenant-engine/workplans/TEN-WP-0003-flex-auth-write-authorizer.md`:
|
|
|
|
|
|
`key-cape`'s token issuance (`src/internal/server/oidc/token.go`) does not
|
|
|
|
|
|
currently emit `tenant`, `principal_type`, `groups`, `roles`, or
|
|
|
|
|
|
`assurance` — none of `net-kingdom/canon/standards/iam-profile_v0.3.md`'s
|
|
|
|
|
|
Core Claims are implemented yet, only `preferred_username`/`email`/`groups`
|
|
|
|
|
|
(LDAP-sourced, not the profile's normalized `groups` claim) and OIDC's own
|
|
|
|
|
|
base claims (`nonce`, etc.).
|
|
|
|
|
|
|
|
|
|
|
|
This is bigger than "add the `tenant_roles` claim on top of existing tenant
|
|
|
|
|
|
support" — `key-cape` isn't yet profile-conformant at all on the tenant/
|
|
|
|
|
|
principal/assurance axis. `tenant_roles` (the reason this workplan exists)
|
|
|
|
|
|
is the last, optional claim in that same set (v0.3's addition on top of
|
|
|
|
|
|
v0.2's baseline) — implementing it in isolation without the claims it
|
|
|
|
|
|
depends on for meaning (`tenant`, `principal_type`) would be incoherent.
|
|
|
|
|
|
|
|
|
|
|
|
**This workplan is registered, not implemented, by the same session that
|
|
|
|
|
|
found the gap.** Token issuance is security-sensitive code in a Go codebase
|
|
|
|
|
|
with its own adapter conventions (`internal/adapters/{lldap,privacyidea,authelia}`)
|
|
|
|
|
|
that deserves dedicated context before changing, not a rushed edit from a
|
|
|
|
|
|
first read of `token.go`. Scoped and sequenced here so the work isn't lost.
|
|
|
|
|
|
|
|
|
|
|
|
**Depends on:** `net-kingdom/canon/standards/iam-profile_v0.3.md` (ratified).
|
|
|
|
|
|
**Related:** `tenant-engine/workplans/TEN-WP-0003-flex-auth-write-authorizer.md`
|
|
|
|
|
|
(the consumer waiting on `tenant_roles`); `key-cape/workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md`
|
|
|
|
|
|
(Binky's onboarding, which needs `tenant` claim support to produce a
|
|
|
|
|
|
correct `tenant:friendly:binky` token at all).
|
|
|
|
|
|
|
|
|
|
|
|
## Task: Emit `tenant`, `principal_type`, `groups`, `roles`, `assurance`
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: KEY-WP-0005-T01
|
KEY-WP-0005-T01: IAM Profile core claims for the human PKCE flow
Verified first: grant_types_supported advertises client_credentials in
discovery.go, but token.go only ever accepted authorization_code -- no
service-token issuance path exists at all. Building one from scratch is
materially bigger than extending the existing flow; explicitly not
attempted here, left open in the workplan rather than declared done.
What shipped for the human Authorization Code + PKCE flow:
- domain.User.Tenant (new, omitempty) + token.go's effectiveTenant():
falls back to tenant:coulomb (this workstation's actual tenant, ADR-0006)
when unset -- never an empty tenant claim, never a silent reassignment.
- principal_type: "human", unconditional.
- groups/roles promoted from scope-gated to unconditional core claims,
always [] not null when empty. One pre-existing test asserted the old
scope-gated groups behavior -- updated to match the new intentional
behavior, not left failing or reverted.
- assurance built from PKCESession.MFAVerified (new field, threaded
through completeAuthorization's two call sites in authorize.go) --
whether MFA was actually verified in this session, not static enrollment
state. aal2 only when required-and-passed this time, aal1 otherwise.
go build/vet clean, go test ./... green repo-wide. Two new authorize_test.go
cases assert MFAVerified on both paths. tests/profile/profile_test.go's
TestCompleteTokenFlow (the repo's own full HTTP integration test) extended
with real value assertions for all five claims, not just presence checks.
Python conformance tool not run against a live instance (needs the full
Authelia+LLDAP+privacyIDEA stack); TestCompleteTokenFlow's real HTTP round
trip covers the equivalent claim checks instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 00:03:31 +02:00
|
|
|
|
status: done
|
Draft KEY-WP-0005: IAM Profile core claims (tenant, principal_type, roles, assurance, tenant_roles)
Discovered while planning tenant-engine's flex-auth WriteAuthorizer
(TEN-WP-0003): src/internal/server/oidc/token.go emits none of
iam-profile_v0.3.md's Core Claims yet -- not tenant, principal_type,
groups, roles, or assurance. tenant_roles isn't a small addition on top of
existing tenant support; it's the last, optional claim on top of a
baseline that doesn't exist yet.
Registered, not implemented -- security-sensitive Go token-issuance code
deserves dedicated context before changing, not a rushed edit from a first
read of the file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 22:47:00 +02:00
|
|
|
|
priority: high
|
2026-08-25 20:13:23 +02:00
|
|
|
|
state_hub_task_id: "32d5fbdc-cd2b-5bc8-9d2b-1a410b55e806"
|
Draft KEY-WP-0005: IAM Profile core claims (tenant, principal_type, roles, assurance, tenant_roles)
Discovered while planning tenant-engine's flex-auth WriteAuthorizer
(TEN-WP-0003): src/internal/server/oidc/token.go emits none of
iam-profile_v0.3.md's Core Claims yet -- not tenant, principal_type,
groups, roles, or assurance. tenant_roles isn't a small addition on top of
existing tenant support; it's the last, optional claim on top of a
baseline that doesn't exist yet.
Registered, not implemented -- security-sensitive Go token-issuance code
deserves dedicated context before changing, not a rushed edit from a first
read of the file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 22:47:00 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Bring `token.go`'s claim assembly up to `iam-profile_v0.3.md`'s Core Claims
|
|
|
|
|
|
table baseline (the v0.2-originated set):
|
|
|
|
|
|
|
|
|
|
|
|
- `tenant`: resolve from whatever user/service/agent-to-tenant mapping
|
|
|
|
|
|
`key-cape`'s LLDAP/Authelia backend can provide today; if no such mapping
|
|
|
|
|
|
exists yet, that's itself a sub-task — this claim cannot be stamped from
|
|
|
|
|
|
nothing.
|
|
|
|
|
|
- `principal_type`: `human` for the existing Authorization Code + PKCE flow;
|
|
|
|
|
|
`service` for client-credentials flows (check whether `key-cape` already
|
|
|
|
|
|
issues client-credentials tokens at all — `grant_types_supported` in the
|
|
|
|
|
|
discovery contract claims it does, but that needs verifying against real
|
|
|
|
|
|
code, the same way this workplan verified `tenant` claim absence).
|
|
|
|
|
|
- `groups`, `roles`: normalize from LDAP group/role data into the profile's
|
|
|
|
|
|
flat string-array shape.
|
|
|
|
|
|
- `assurance`: `{level, methods, mfa, source, at}` per the profile —
|
|
|
|
|
|
`privacyIDEA`-backed MFA evidence maps to `aal2`; single-factor maps to
|
|
|
|
|
|
`aal1`.
|
|
|
|
|
|
|
|
|
|
|
|
Done when: `tools/iam-profile-conformance/` (already exists) passes against
|
|
|
|
|
|
these claims for both human and service token issuance paths.
|
|
|
|
|
|
|
KEY-WP-0005-T01: IAM Profile core claims for the human PKCE flow
Verified first: grant_types_supported advertises client_credentials in
discovery.go, but token.go only ever accepted authorization_code -- no
service-token issuance path exists at all. Building one from scratch is
materially bigger than extending the existing flow; explicitly not
attempted here, left open in the workplan rather than declared done.
What shipped for the human Authorization Code + PKCE flow:
- domain.User.Tenant (new, omitempty) + token.go's effectiveTenant():
falls back to tenant:coulomb (this workstation's actual tenant, ADR-0006)
when unset -- never an empty tenant claim, never a silent reassignment.
- principal_type: "human", unconditional.
- groups/roles promoted from scope-gated to unconditional core claims,
always [] not null when empty. One pre-existing test asserted the old
scope-gated groups behavior -- updated to match the new intentional
behavior, not left failing or reverted.
- assurance built from PKCESession.MFAVerified (new field, threaded
through completeAuthorization's two call sites in authorize.go) --
whether MFA was actually verified in this session, not static enrollment
state. aal2 only when required-and-passed this time, aal1 otherwise.
go build/vet clean, go test ./... green repo-wide. Two new authorize_test.go
cases assert MFAVerified on both paths. tests/profile/profile_test.go's
TestCompleteTokenFlow (the repo's own full HTTP integration test) extended
with real value assertions for all five claims, not just presence checks.
Python conformance tool not run against a live instance (needs the full
Authelia+LLDAP+privacyIDEA stack); TestCompleteTokenFlow's real HTTP round
trip covers the equivalent claim checks instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 00:03:31 +02:00
|
|
|
|
**Done 2026-07-24, human flow only — scope split made explicit, not
|
|
|
|
|
|
silently narrowed:** verified first (as flagged): `grant_types_supported`
|
|
|
|
|
|
advertises `client_credentials` in `discovery.go`, but `token.go`'s
|
|
|
|
|
|
`ServeHTTP` only ever accepted `grant_type == "authorization_code"` — there
|
|
|
|
|
|
is no service-token issuance path at all, so "verifying against service
|
|
|
|
|
|
token issuance" isn't possible until that path exists. Building a
|
|
|
|
|
|
`client_credentials` grant handler from scratch is a distinct, materially
|
|
|
|
|
|
larger piece of work than extending the existing human flow (new endpoint
|
|
|
|
|
|
logic, new client-authentication semantics, no existing tests to extend)
|
|
|
|
|
|
— **not attempted in this pass**, left open below rather than declared done.
|
|
|
|
|
|
|
|
|
|
|
|
What shipped for the human Authorization Code + PKCE flow:
|
|
|
|
|
|
|
|
|
|
|
|
- `domain.User.Tenant` (new field, `omitempty`, empty-safe for existing
|
|
|
|
|
|
YAML configs) + `token.go`'s `effectiveTenant()`: falls back to
|
|
|
|
|
|
`tenant:coulomb` (this workstation's actual current tenant, ADR-0006)
|
|
|
|
|
|
when unset — never emits an empty `tenant` claim, never silently assigns
|
|
|
|
|
|
a user to a tenant they weren't given.
|
|
|
|
|
|
- `principal_type: "human"`, unconditional.
|
|
|
|
|
|
- `groups`/`roles`: promoted from scope-gated (only `groups`, only with the
|
|
|
|
|
|
`groups` scope) to unconditional core claims, always present as `[]`
|
|
|
|
|
|
when empty, never `null` (`nonNilStrings()`). One pre-existing test
|
|
|
|
|
|
asserted the old scope-gated behavior for `groups`
|
|
|
|
|
|
(`TestTokenHandler_ScopeFiltering_ProfileScope`) — updated to assert the
|
|
|
|
|
|
new, intentional behavior, not silently left failing or reverted.
|
|
|
|
|
|
- `assurance`: built from whether MFA was *actually verified in this
|
|
|
|
|
|
session* (`PKCESession.MFAVerified`, new field, threaded through
|
|
|
|
|
|
`completeAuthorization`'s two call sites in `authorize.go`), not from
|
|
|
|
|
|
static enrollment state — `aal2` only when MFA was required and passed
|
|
|
|
|
|
this time, `aal1` otherwise.
|
|
|
|
|
|
|
|
|
|
|
|
Verified for real: `go build ./...` and `go vet ./...` clean; `go test
|
|
|
|
|
|
./...` green across the whole repo (one pre-existing test updated to match
|
|
|
|
|
|
the new intentional behavior, described above, not silently broken). Two
|
|
|
|
|
|
new `authorize_test.go` cases directly assert `PKCESession.MFAVerified` is
|
|
|
|
|
|
set correctly on both the MFA-required and MFA-not-required paths.
|
|
|
|
|
|
`tests/profile/profile_test.go`'s `TestCompleteTokenFlow` — the repo's own
|
|
|
|
|
|
full HTTP integration test (real server, real PKCE flow, real JWT decode)
|
|
|
|
|
|
— extended with real assertions for all five new/changed claims, not just
|
|
|
|
|
|
presence checks: `tenant == "tenant:coulomb"` (the fallback path, since
|
|
|
|
|
|
the test's `testuser` has no explicit tenant), `principal_type == "human"`,
|
|
|
|
|
|
`groups == ["developers"]`, `roles == []`, and
|
|
|
|
|
|
`assurance == {level: "aal1", mfa: false, source: "key-cape", ...}` (this
|
|
|
|
|
|
test's `mockMFA` never requires MFA, so `aal1` is the correct expected
|
|
|
|
|
|
value, not `aal2`).
|
|
|
|
|
|
|
|
|
|
|
|
Not run: `net-kingdom/tools/iam-profile-conformance`'s Python conformance
|
|
|
|
|
|
tool against a live minted token — it needs a reachable issuer, which would
|
|
|
|
|
|
mean standing up the full `docker-compose.dev.yml` stack
|
|
|
|
|
|
(Authelia+LLDAP+privacyIDEA) beyond what's practical in this pass. The
|
|
|
|
|
|
equivalent claim-shape and value checks it would perform are covered by
|
|
|
|
|
|
`TestCompleteTokenFlow`'s real HTTP round trip instead — not a like-for-like
|
|
|
|
|
|
substitute, but real coverage, not an assumption.
|
|
|
|
|
|
|
|
|
|
|
|
**Explicitly still open** (not this task, not silently dropped):
|
|
|
|
|
|
`client_credentials` grant handling — service-token issuance for `key-cape`
|
|
|
|
|
|
callers with no human in the loop (`tenant-engine`'s own eventual key-cape
|
|
|
|
|
|
integration, and any other service caller). Needs its own scoped follow-up:
|
|
|
|
|
|
a new `principal_type: "service"` code path in `token.go` (or a sibling
|
|
|
|
|
|
handler), client secret/authentication semantics, and its own test suite —
|
|
|
|
|
|
this workplan's `T02` (`tenant_roles`) doesn't strictly require it, since
|
|
|
|
|
|
`tenant_roles` attaches to whatever principal_type a token already carries,
|
|
|
|
|
|
human included.
|
|
|
|
|
|
|
Draft KEY-WP-0005: IAM Profile core claims (tenant, principal_type, roles, assurance, tenant_roles)
Discovered while planning tenant-engine's flex-auth WriteAuthorizer
(TEN-WP-0003): src/internal/server/oidc/token.go emits none of
iam-profile_v0.3.md's Core Claims yet -- not tenant, principal_type,
groups, roles, or assurance. tenant_roles isn't a small addition on top of
existing tenant support; it's the last, optional claim on top of a
baseline that doesn't exist yet.
Registered, not implemented -- security-sensitive Go token-issuance code
deserves dedicated context before changing, not a rushed edit from a first
read of the file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 22:47:00 +02:00
|
|
|
|
## Task: Emit `tenant_roles` (optional, cached)
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: KEY-WP-0005-T02
|
KEY-WP-0005-T02-T03: cached tenant_roles claim, close workplan
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>
2026-07-24 00:18:17 +02:00
|
|
|
|
status: done
|
Draft KEY-WP-0005: IAM Profile core claims (tenant, principal_type, roles, assurance, tenant_roles)
Discovered while planning tenant-engine's flex-auth WriteAuthorizer
(TEN-WP-0003): src/internal/server/oidc/token.go emits none of
iam-profile_v0.3.md's Core Claims yet -- not tenant, principal_type,
groups, roles, or assurance. tenant_roles isn't a small addition on top of
existing tenant support; it's the last, optional claim on top of a
baseline that doesn't exist yet.
Registered, not implemented -- security-sensitive Go token-issuance code
deserves dedicated context before changing, not a rushed edit from a first
read of the file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 22:47:00 +02:00
|
|
|
|
priority: high
|
2026-08-25 20:13:23 +02:00
|
|
|
|
state_hub_task_id: "c3ee6021-b28d-50a5-be60-ecb5631f56e9"
|
Draft KEY-WP-0005: IAM Profile core claims (tenant, principal_type, roles, assurance, tenant_roles)
Discovered while planning tenant-engine's flex-auth WriteAuthorizer
(TEN-WP-0003): src/internal/server/oidc/token.go emits none of
iam-profile_v0.3.md's Core Claims yet -- not tenant, principal_type,
groups, roles, or assurance. tenant_roles isn't a small addition on top of
existing tenant support; it's the last, optional claim on top of a
baseline that doesn't exist yet.
Registered, not implemented -- security-sensitive Go token-issuance code
deserves dedicated context before changing, not a rushed edit from a first
read of the file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 22:47:00 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
At token-issuance time, call `tenant-engine`'s cache-read endpoint
|
|
|
|
|
|
(`GET /tenants/{tenant_id}/roles`) using the `tenant` claim resolved in T01,
|
|
|
|
|
|
and stamp the result as the optional `tenant_roles` claim
|
|
|
|
|
|
(`iam-profile_v0.3.md`, "Tenant Roles" section). Follows the same adapter
|
|
|
|
|
|
pattern as `internal/adapters/{lldap,privacyidea,authelia}` — a new
|
|
|
|
|
|
`internal/adapters/tenantengine` package, not inline HTTP calls in
|
|
|
|
|
|
`token.go`.
|
|
|
|
|
|
|
|
|
|
|
|
Fail open on this claim specifically, not closed: `tenant_roles` is
|
|
|
|
|
|
documented as a cache callers must not trust for privileged decisions
|
|
|
|
|
|
anyway (`flex-auth` re-validates live). If `tenant-engine` is unreachable at
|
|
|
|
|
|
issuance time, omit the claim rather than fail the entire token issuance —
|
|
|
|
|
|
losing a performance optimization is acceptable; blocking login because a
|
|
|
|
|
|
cache source is down is not. This is the one place in the whole `tenant_roles`
|
|
|
|
|
|
design where fail-open is correct, precisely because the live-lookup path
|
|
|
|
|
|
exists as the real gate.
|
|
|
|
|
|
|
|
|
|
|
|
Done when: token issuance still succeeds with `tenant_roles` omitted when
|
|
|
|
|
|
`tenant-engine` is unreachable (test simulates the outage); present and
|
|
|
|
|
|
correct when reachable, for a tenant with known role grants.
|
|
|
|
|
|
|
KEY-WP-0005-T02-T03: cached tenant_roles claim, close workplan
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>
2026-07-24 00:18:17 +02:00
|
|
|
|
**Done 2026-07-24:** New `internal/adapters/tenantengine` package
|
|
|
|
|
|
(`Client.Roles(ctx, tenantID) ([]string, bool)`), mirroring
|
|
|
|
|
|
`internal/adapters/{lldap,privacyidea,authelia}`'s shape exactly. Fails
|
|
|
|
|
|
open by construction — unreachable, non-200, or malformed body all return
|
|
|
|
|
|
`(nil, false)`, never an error the caller has to specially handle; a `nil
|
|
|
|
|
|
*Client` also fails open safely rather than panicking, so `TokenHandler`
|
|
|
|
|
|
doesn't need a separate "is this configured" branch. Wired into
|
|
|
|
|
|
`TokenHandler.TenantEngine` (nil by default — existing tests and call
|
|
|
|
|
|
sites are unaffected); `token.go` stamps `tenant_roles` only when `ok`.
|
|
|
|
|
|
|
|
|
|
|
|
7 adapter-level tests (success, non-200, malformed body, connection
|
|
|
|
|
|
failure, timeout, nil client, empty base URL). 3 new `token_test.go` cases
|
|
|
|
|
|
prove the actual required behavior end-to-end through `TokenHandler`, not
|
|
|
|
|
|
just at the adapter layer: present and correct when reachable; **token
|
|
|
|
|
|
issuance still returns 200 with every other required core claim intact**
|
|
|
|
|
|
when unreachable, `tenant_roles` simply absent (the literal done-criteria);
|
|
|
|
|
|
absent when `TenantEngine` isn't configured at all. `go build`/`vet`
|
|
|
|
|
|
clean, `go test ./...` green repo-wide.
|
|
|
|
|
|
|
|
|
|
|
|
**A real bug found and fixed along the way, 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 genuinely existed.
|
|
|
|
|
|
`tenant-engine`'s `GET /tenants/{tenant_id}/roles` was keyed by its
|
|
|
|
|
|
internal `tenant_id`, but `key-cape` (like any external caller) only ever
|
|
|
|
|
|
has the tenant's profile *identifier* (the `tenant` claim value) — it has
|
|
|
|
|
|
no way to know tenant-engine's internal id. Fixed at the source:
|
|
|
|
|
|
`tenant-engine/workplans/ADHOC-2026-07-24.md` (identifier-or-id resolution
|
|
|
|
|
|
added to `InMemoryTenantStore`), re-verified with the exact same live
|
|
|
|
|
|
three-process chain afterward — `roles=[IAM] ok=true`, resolved by
|
|
|
|
|
|
identifier. This is exactly why T02 insisted on a live-server-reachable
|
|
|
|
|
|
test rather than only an `httptest` fake: the fake would have used
|
|
|
|
|
|
whatever id shape the test author happened to pick and never caught this.
|
|
|
|
|
|
|
Draft KEY-WP-0005: IAM Profile core claims (tenant, principal_type, roles, assurance, tenant_roles)
Discovered while planning tenant-engine's flex-auth WriteAuthorizer
(TEN-WP-0003): src/internal/server/oidc/token.go emits none of
iam-profile_v0.3.md's Core Claims yet -- not tenant, principal_type,
groups, roles, or assurance. tenant_roles isn't a small addition on top of
existing tenant support; it's the last, optional claim on top of a
baseline that doesn't exist yet.
Registered, not implemented -- security-sensitive Go token-issuance code
deserves dedicated context before changing, not a rushed edit from a first
read of the file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 22:47:00 +02:00
|
|
|
|
## Task: Closure review
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: KEY-WP-0005-T03
|
KEY-WP-0005-T02-T03: cached tenant_roles claim, close workplan
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>
2026-07-24 00:18:17 +02:00
|
|
|
|
status: done
|
Draft KEY-WP-0005: IAM Profile core claims (tenant, principal_type, roles, assurance, tenant_roles)
Discovered while planning tenant-engine's flex-auth WriteAuthorizer
(TEN-WP-0003): src/internal/server/oidc/token.go emits none of
iam-profile_v0.3.md's Core Claims yet -- not tenant, principal_type,
groups, roles, or assurance. tenant_roles isn't a small addition on top of
existing tenant support; it's the last, optional claim on top of a
baseline that doesn't exist yet.
Registered, not implemented -- security-sensitive Go token-issuance code
deserves dedicated context before changing, not a rushed edit from a first
read of the file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 22:47:00 +02:00
|
|
|
|
priority: low
|
2026-08-25 20:13:23 +02:00
|
|
|
|
state_hub_task_id: "c88a3457-2964-5c44-8220-76f82df3a753"
|
Draft KEY-WP-0005: IAM Profile core claims (tenant, principal_type, roles, assurance, tenant_roles)
Discovered while planning tenant-engine's flex-auth WriteAuthorizer
(TEN-WP-0003): src/internal/server/oidc/token.go emits none of
iam-profile_v0.3.md's Core Claims yet -- not tenant, principal_type,
groups, roles, or assurance. tenant_roles isn't a small addition on top of
existing tenant support; it's the last, optional claim on top of a
baseline that doesn't exist yet.
Registered, not implemented -- security-sensitive Go token-issuance code
deserves dedicated context before changing, not a rushed edit from a first
read of the file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 22:47:00 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Confirm T01–T02 done; conformance suite green for both tasks together.
|
|
|
|
|
|
Note in closure: this unblocks `KEY-WP-0004`'s Binky onboarding actually
|
|
|
|
|
|
producing conformant tokens, and unblocks `flex-auth`'s side of the
|
|
|
|
|
|
`tenant_roles` picture receiving a real claim to reason about (though
|
|
|
|
|
|
`flex-auth`'s live-lookup path doesn't depend on this claim existing — only
|
|
|
|
|
|
the cache-read/performance path does). Run `statehub fix-consistency`.
|
KEY-WP-0005-T02-T03: cached tenant_roles claim, close workplan
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>
2026-07-24 00:18:17 +02:00
|
|
|
|
|
|
|
|
|
|
**Closed 2026-07-24.** T01–T02 done. `go build ./...`, `go vet ./...` clean;
|
|
|
|
|
|
`go test ./...` green across the whole repo. Python
|
|
|
|
|
|
`net-kingdom/tools/iam-profile-conformance` not run against a live instance
|
|
|
|
|
|
(needs the full Authelia+LLDAP+privacyIDEA stack, impractical in this
|
|
|
|
|
|
pass) — real coverage instead came from `tests/profile`'s own full HTTP
|
|
|
|
|
|
integration test (`TestCompleteTokenFlow`) with genuine claim-value
|
|
|
|
|
|
assertions, and from three separate live cross-process checks against a
|
|
|
|
|
|
real `tenant-engine` (one of which found and fixed a real bug in
|
|
|
|
|
|
`tenant-engine` itself, `ADHOC-2026-07-24`).
|
|
|
|
|
|
|
|
|
|
|
|
**What this closes:** `key-cape`'s human Authorization Code + PKCE flow now
|
|
|
|
|
|
emits every IAM Profile v0.3 core claim (`tenant`, `principal_type`,
|
|
|
|
|
|
`groups`, `roles`, `assurance`) plus the optional `tenant_roles` cache,
|
|
|
|
|
|
correctly sourced from a real `assurance` signal (MFA actually verified
|
|
|
|
|
|
this session, not static enrollment) and a real `tenant-engine` call
|
|
|
|
|
|
(fail-open, never blocking login). `KEY-WP-0004`'s Binky onboarding can now
|
|
|
|
|
|
produce conformant tokens for human logins.
|
|
|
|
|
|
|
|
|
|
|
|
**What stays explicitly open, not silently dropped:**
|
|
|
|
|
|
- `client_credentials` / service-token issuance — no such flow exists in
|
|
|
|
|
|
`token.go` at all yet (confirmed in T01); needed before any *service*
|
|
|
|
|
|
caller (not a human) can get a token with `principal_type: "service"` and
|
|
|
|
|
|
its own `tenant`/`tenant_roles`. A materially larger, separate piece of
|
|
|
|
|
|
work than either task in this workplan.
|
|
|
|
|
|
- `flex-auth`'s side of `tenant_roles` (`FLEX-WP-0008`, already closed
|
|
|
|
|
|
separately) doesn't depend on this claim existing at all — its
|
|
|
|
|
|
live-lookup path calls `tenant-engine` directly, so this workplan doesn't
|
|
|
|
|
|
block it, but also doesn't complete the full `tenant_roles` picture on
|
|
|
|
|
|
its own until service tokens exist too.
|