# Add a protected system to flex-auth Exercise status: unexercised Workplan task: NK-WP-0009-T05 Pattern(s): authorization boundary (`docs/platform-identity-security-architecture.md`, ADR-0006); IAM Profile v0.3 consumption Exercise notes: the offline commands in steps 4 and 5 were run once by Claude on 2026-09-28 against the `secrets-engine` example in a local flex-auth checkout and produced the outcomes listed under Verification. The live caller-authentication steps (6 and 7) have not been run. Nobody has yet completed the whole tutorial, so it stays `unexercised`. ## Outcome Your system publishes its resources and actions to flex-auth, sends check requests carrying a projected caller identity, enforces the decision it gets back, and is refused when the caller is wrong. ## Prerequisites - **[owner: flex-auth]** A local checkout of `flex-auth` with Go, to run `go run ./cmd/flex-auth`. Read `flex-auth/docs/decision-record-contract.md`. - **[owner: your system's repo]** A system id and a list of the things it protects and the actions it performs on them. - **[owner: railiance-platform / package owner, ADR-0015]** A Kubernetes ServiceAccount that will represent your system. Package and runtime declarations belong to the owner, not to this repo. - **[owner: net-kingdom]** Accepted IAM Profile v0.3 for the identity you present (`canon/standards/iam-profile_v0.3.md`). ## Architecture context flex-auth is the policy decision point; your system is the enforcement point. flex-auth never sees secret material, only coordinates (ids, types, attributes). The worked example throughout is `secrets-engine` (`flex-auth/examples/secrets-engine/`). Two gates protect a consumer: caller authentication (who is asking) and the policy decision (may they). A consumer running with caller auth in `warn` mode is not protected. ## Steps 1. **[owner: your system's repo]** Define the resource types and the action vocabulary. Actions are your system's own strings; do not infer them. Copy the shape of `flex-auth/examples/secrets-engine/protected_system_manifest.yaml`: `resource_types` (name, scope level, planes) and `actions` (name, capabilities, planes, exposure modes, required context). Keep a verb that is not a real gate out of the list (`revoke` is deliberately not an action in the example; the CLI verb gates as `deactivate`). 2. **[owner: your system's repo]** Publish resource manifests and, for callers, a subject manifest: one service identity per protected system. Send empty lists rather than omitting or guessing attributes. 3. **[owner: flex-auth]** Write the policy package (Markdown with Rego) and fixtures covering every allow and every denial branch, including wrong tenant, unknown subject and unknown action. 4. **[owner: flex-auth]** Validate and load offline: `go run ./cmd/flex-auth validate -kind policy -file ` then `go run ./cmd/flex-auth load-registry -file `. 5. **[owner: flex-auth]** Evaluate a check request locally: `go run ./cmd/flex-auth check -registry -policy -request `. A request is `tenant`, `subject`, `action`, `resource` and `context`. The result is a decision envelope (`flex-auth.decision-record.v1`) with `effect`, `reason`, and a deterministic decision `id`. 6. **[owner: package owner]** Give the caller a projected identity: a short-lived, audience-scoped ServiceAccount token, and register the exact binding `=system:serviceaccount::` on the flex-auth pin with audience `flex-auth`. Send the token as `Authorization: Bearer`. Use the in-cluster trailing-dot FQDN of the pin, never a bare service name. 7. **[owner: flex-auth]** Set caller authentication to `enforce` and run the four negative tests from `flex-auth/docs/operator-caller-access-path.md` (no header, wrong ServiceAccount, wrong audience, expired token). 8. **[owner: your system's repo]** Enforce the envelope: `allow` proceeds; `deny` blocks; `redact` and `audit_only` apply their obligations; any error or missing decision fails closed. Persist the decision id with every deny, redaction, export and privileged action. ## Verification Done when: - `validate` and `load-registry` exit 0 on your package and snapshot. - Offline `check` returns `allow` for a permitted request, and `deny` with reason `wrong_tenant`, `unknown_subject` and `unknown_action` for the matching negative requests. (Observed on the `secrets-engine` example: `allow` / `catalog_lane_policy_matched`; `deny` / `wrong_tenant`; `deny` / `unknown_action` for `revoke`; `deny` / `unknown_subject`.) - Against the live pin under `enforce`: a valid token returns 200 and an `allow`; no header returns 401; a token for the wrong ServiceAccount returns 403; a wrong-audience token returns 401; an expired token returns 401. All four negatives must be refused before policy is evaluated. - A pin whose logs still show `warn` is reported as not enforcing. That is a failed check, not a pass. ## Rollback - Offline artifacts are files; revert them in git. - On the live pin, set caller authentication back only as a recorded, time-boxed exception owned by the package owner, and never leave a consumer in `warn` as the resting state. - Removing a protected system means removing its binding and manifests; consumers must then fail closed. ## Threat checks - Never send a permanent token; mint short-lived tokens per use. - Never point a consumer at a bare in-cluster service name; workstation resolvers can answer it with an unrelated host. Use the trailing-dot FQDN. - A port-forward bypasses NetworkPolicy, so caller authentication is the only gate on an operator path. - Never put secret values in a resource attribute or a check request. - Do not give raw upstream group names platform meaning; map them explicitly per tenant. - Do not apply stale tenant-engine reference YAML; take manifests from the current owner package. ## Ownership notes | Concern | Owner | | --- | --- | | Action vocabulary, resource manifests, enforcement of decisions | the protected system's repo | | Policy packages, decision envelope, caller-auth verification | flex-auth | | ServiceAccount, package and runtime declaration | package owner under ADR-0015 | | Identity contract (IAM Profile v0.3) | net-kingdom | | Credential routing for the token | ops-warden (pointer only) |