125 lines
6.4 KiB
Markdown
125 lines
6.4 KiB
Markdown
|
|
# 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) |
|