Add flex-auth protected-system tutorial (NK-WP-0009-T05)
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:
parent
2a004a70ea
commit
923e211365
3 changed files with 127 additions and 3 deletions
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
124
docs/tutorials/protected-system-flex-auth.md
Normal file
124
docs/tutorials/protected-system-flex-auth.md
Normal 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) |
|
||||||
|
|
@ -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"
|
||||||
```
|
```
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue