Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 58902@bnt-lap001 Assistant-Session: 7ac7f865-2dc5-4aa7-8eb7-27a342109c2f
6.1 KiB
flex-auth caller identity contract
Status: live A2 proven 2026-08-19 on flex-auth-user-engine (enforce).
Governing published policy (2026-08-19):
- IAM Profile v0.3 Service Account Flow — audience-scoped, rotating
workload identity:
https://policy.coulomb.social/standards/iam-profile/v0.3/ - Tenancy Posture v0.1 A2 — inbound caller choke point:
https://policy.coulomb.social/standards/tenancy-posture/v0.1/
user-engine calls flex-auth-user-engine with a projected Kubernetes
ServiceAccount token whose audience is exactly flex-auth. The adapter reads
the token file for every decision so hourly projection rotation requires no
restart. A missing, empty or unreadable file fails closed as an authorization
denial; the token value is never logged.
flex-auth binds protected system user-engine to principal
system:serviceaccount:user-engine:user-engine. The token authenticates the
calling workload only. It does not replace the IAM actor/tenant/assurance facts
inside the authorization request and grants no Kubernetes API permission to
user-engine.
Runtime configuration requires USER_ENGINE_FLEX_AUTH_TOKEN_FILE. Local
construction keeps the adapter argument optional so unit tests and explicit
non-production adapters remain usable.
The tenant authority seam is distinct: user-engine identifies itself as actor
user-engine on tenant lifecycle reads and writes; tenant-engine performs its
own flex-auth decision before store access. Tenant ids remain opaque and are
URL-encoded. No client may infer existence from an unauthorized read.
As of 2026-08-19 flex-auth-user-engine is live enforce on digest
sha256:138aa347… (Helm release rev 4). user-engine sha256:c501aeb2…
sends the projected token. The pod security context must set
runAsUser / runAsGroup / fsGroup 10001 (image uid; see
Containerfile). Without fsGroup, the projected token is 0440 root:root
and the adapter never sends Authorization. That is persisted in
rapp-user-engine manifests/runtime.yaml e599b4f. The portal image has
no curl; live probes use Python.
Live promotion probe (USER-WP-0023-T03)
The caller side is deployed: image sha256:c501aeb2… (user-engine 7604d31)
runs in namespace user-engine as ServiceAccount user-engine, with the
projected token mounted at /var/run/secrets/flex-auth-caller/token under
audience flex-auth and USER_ENGINE_FLEX_AUTH_TOKEN_FILE pointing at it.
Closed 2026-08-19 after flex-auth-user-engine flipped to enforce.
Do not re-run these against warn: step 2 would return a decision.
POD is any ready user-engine pod. The portal image has no curl; the
recorded run used Python. The bash shape below is the contract, not the
exact live invocation.
The target cluster is railiance01 (node 92.205.62.239). Run the commands
on the node (ssh railiance01, then the commands below). If you run them
from the workstation, first confirm that kubectl get nodes -o wide shows
that IP. A kubeconfig pointing at the retired coulombcore cluster answers
with the wrong cluster (see
the-custodian/docs/agent-environment-orientation.md §1).
POD=$(kubectl -n user-engine get pod -l app.kubernetes.io/name=user-engine \
-o jsonpath='{.items[0].metadata.name}')
CHECK=http://flex-auth-user-engine.flex-auth.svc.cluster.local:8080/v1/check
BODY='{"actor":{"issuer":"https://kc.coulomb.social","subject":"probe",
"tenant":"tenant:friendly:binky","principal_type":"human",
"audience":["user-engine"],"roles":["tenant-admin"]},
"resource":{"system":"user-engine","type":"user-engine:user","id":"probe"},
"action":"user.update","tenant":"tenant:friendly:binky",
"correlation_id":"probe-t03","context":{}}'
- A valid caller succeeds. Expect
200and a decision id.kubectl -n user-engine exec "$POD" -c portal -- sh -c \ "curl -sS -o /dev/null -w '%{http_code}\n' -X POST $CHECK \ -H 'Content-Type: application/json' \ -H \"Authorization: Bearer \$(cat /var/run/secrets/flex-auth-caller/token)\" \ -d '$BODY'" - No token is refused. Expect
401, not a decision.kubectl -n user-engine exec "$POD" -c portal -- sh -c \ "curl -sS -o /dev/null -w '%{http_code}\n' -X POST $CHECK \ -H 'Content-Type: application/json' -d '$BODY'" - user-engine cannot represent another protected system. Re-run step 1
with
resource.systemset totenant-engine. Expect a refusal from the binding, not an allow: the token bindsuser-enginetosystem:serviceaccount:user-engine:user-engineonly. That binding is the A2 choke point named by Tenancy Posture v0.1; publication does not change the remaining operator sequence.
Recorded 2026-08-19 from pod user-engine-8569d7cb87-fp7px (flex-auth
message 45852487):
| Step | Result |
|---|---|
| 1. Bearer projected token | 200 decision:350b46fb42989606 allow tenant_admin |
| 2. No Authorization | 401 unauthenticated |
3. resource.system=tenant-engine |
403 forbidden |
Digests: flex-auth sha256:138aa3471c46bca6e814691fa1e6520aedda3dffd743e6b09141ab433afdb64b,
user-engine sha256:c501aeb204154d85017565a87fd34cff12e5c48f600d124d3f8c82f11bb9d59c.
tenant-engine and policy.enabled were not moved. A pass on steps 1 and 3
without a 401 on step 2 would have meant enforcement was still off.
Live cross-service check (USER-WP-0021)
Owed separately to tenant-engine after production served 404 on the lifecycle routes from 2026-08-13 to 2026-08-16 while our contract-level suite passed throughout. Contract evidence structurally cannot see a pin rollback, so this must be exercised live, from a pod, against the real authority.
Use a disposable tenant — never a real one, since retire is a lifecycle
mutation. Through the portal's own platform routes as a platform operator:
GET the record, PATCH its display name, retire, then reactivate,
echoing each returned version as the next If-Match. Expect 200 throughout
and no 404, which is what the rollback window produced. Capture the
tenant-engine digest alongside the results; both silent rollbacks this month
were digest-level and neither raised an alert.
This remains owed. It was not run in the 2026-08-19 A2 session.