flex-auth-user-engine is enforce on sha256:138aa347. Probe from user-engine-8569d7cb87-fp7px: 200 with the projected token, 401 without, 403 when representing tenant-engine. Record fsGroup 10001 so uid 10001 can read the projected caller token.
5.8 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.
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.