Add flex-auth protected-system tutorial (NK-WP-0009-T05)
Some checks are pending
CI Smoke / container-smoke (push) Waiting to run
CI Smoke / host-smoke (push) Waiting to run

Offline steps exercised against the secrets-engine example; live caller-auth
steps remain unexercised.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 295952@bnt-lap001
Assistant-Session: e93f64ad-516c-46eb-9666-aad8d300c477
This commit is contained in:
tegwick 2026-09-28 23:44:44 +02:00
parent 2a004a70ea
commit 923e211365
3 changed files with 127 additions and 3 deletions

View file

@ -24,10 +24,10 @@ Hands-on paths for operating the canonical NetKingdom security patterns
| --- | --- | --- | --- | | --- | --- | --- | --- |
| [OpenBao: consume, attend, recover](openbao-operating-path.md) | T03 | railiance-platform, net-kingdom | unexercised | | [OpenBao: consume, attend, recover](openbao-operating-path.md) | T03 | railiance-platform, net-kingdom | unexercised |
| [Short-lived SSH credentials](ssh-certificates-and-tunnels.md) | T04 | ops-warden, ops-bridge | unexercised | | [Short-lived SSH credentials](ssh-certificates-and-tunnels.md) | T04 | ops-warden, ops-bridge | unexercised |
| [Add a protected system to flex-auth](protected-system-flex-auth.md) | T05 | flex-auth, package owner | unexercised (offline part run) |
Deferred (see NK-WP-0009): T02 object-storage STS (needs an owner-backed Deferred (see NK-WP-0009): T02 object-storage STS (needs an owner-backed
issuer and refusal/lease proof — ADR-0008 is architecture, not evidence) and issuer and refusal/lease proof — ADR-0008 is architecture, not evidence).
T05 flex-auth protected consumer.
## Pattern mapping ## Pattern mapping

View file

@ -0,0 +1,124 @@
# 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 <policy_package.md>`
then `go run ./cmd/flex-auth load-registry -file <registry_snapshot.json>`.
5. **[owner: flex-auth]** Evaluate a check request locally:
`go run ./cmd/flex-auth check -registry <registry_snapshot.json> -policy <policy_package.md> -request <request.json>`.
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>=system:serviceaccount:<namespace>:<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) |

View file

@ -122,7 +122,7 @@ implementation.
```task ```task
id: NK-WP-0009-T05 id: NK-WP-0009-T05
status: todo status: progress
priority: medium priority: medium
state_hub_task_id: "c380ef19-4bc2-5ade-b127-baf18c64bf35" state_hub_task_id: "c380ef19-4bc2-5ade-b127-baf18c64bf35"
``` ```