ops-warden/workplans/WARDEN-WP-0032-security-zones.md
tegwick b1070be644
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
WARDEN-WP-0032: flex-auth amendments to T02 and T03
flex-auth reviewed ZONE-WP-0001 as the consuming PDP and two findings
land on the ops-warden consumer side:

T02 - fail_closed is not expressible by a PDP. Fail-open describes what
warden sign does when flex-auth is unreachable, so no decision exists to
carry it. The replacement for policy.enabled is two things: stance,
which arrives in the decision, and failure mode, which stays local and
is declared per zone.

T02 - build_flex_auth_registry.py:77 already emits a hardcoded
trust_zone: platform that no policy reads. Resolve that before
compiling zone membership rather than adding security_zone beside it.

T03 - the declaration must say whether a zone rides the actor (subject)
or the lane (resource); in the compiled registry an actor is both.

No flex-auth registry schema change is needed - metadata, labels and
attributes already flatten into the rego input.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 22:00:13 +02:00

147 lines
6.5 KiB
Markdown

---
id: WARDEN-WP-0032
type: workplan
title: "Adopt security zones as a consumer — retire the global policy.enabled"
domain: infotech
repo: ops-warden
status: proposed
owner: ops-warden
topic_slug: netkingdom
planning_priority: P1
depends_on_workplans:
- WARDEN-WP-0031
created: "2026-08-19"
updated: "2026-08-19"
state_hub_workstream_id: "f1b6cdcb-5e16-48ab-a72b-339e909e92f0"
---
# WARDEN-WP-0032 — Adopt security zones as a consumer
`ADR-0006` defers `policy.enabled: true` until enforcement can be scoped to a
zone. **The zone model itself is no longer ops-warden's work.** It moved to
`zone-engine` as `ZONE-WP-0001` on 2026-08-19, where it belongs under `ADR-0005`
— ops-warden implements one lane narrowly and routes the rest, and an
estate-wide enforcement model is not a lane it should absorb.
What stays here is the consumer side: ops-warden was the repo whose deferred
flip exposed the gap, it holds the controls the model must be able to express,
and it is the first consumer the model has to satisfy.
## What ops-warden owes zone-engine
Inputs, not designs. `ZONE-WP-0001-T02` derives the model from the real estate,
and most of that estate is ops-warden's:
- **27 routing catalog lanes** (`registry/routing/catalog.yaml`) with `risk`,
`status`, and `delegation` already on each — the likeliest membership inputs.
- **The actor inventory** (`adm` / `agt` / `atm`) and its TTL policy.
- **The three posture axes already shipped** — environment and maturity
(WP-0015), `organization_posture` (WP-0029) — including the honest answer to
whether `organization_posture` should be folded into zones rather than run
beside them.
- **Three controls the model must be able to express**: the flex-auth pre-sign
gate (`policy.enabled` + `fail_closed`), the agent read-boundary on
`risk: high` lanes (`ADR-0004`), and the `warden plan` escalation verdicts.
- **The compiled-registry path** (`scripts/build_flex_auth_registry.py`) — how
membership can reach flex-auth without a lookup in a latency-critical decision
path.
## Tasks
```task
id: WARDEN-WP-0032-T01
status: todo
priority: high
state_hub_task_id: "b6dac514-4b05-47b8-8745-6a1c67b6100e"
```
**Hand the estate inputs to `ZONE-WP-0001-T02`.** Not a design proposal — the
lanes, actors, axes, and controls above, with the exceptions ops-warden already
knows do not fit cleanly (the interim delegation lanes from WP-0030 are the
obvious candidates: a lane covered on someone else's behalf may not sit in the
same zone as one ops-warden owns outright).
Include the `organization_posture` fold-in question honestly, including the case
against keeping it.
```task
id: WARDEN-WP-0032-T02
status: wait
priority: high
state_hub_task_id: "08735f17-fe7e-4a6a-9b71-f350f98c30b1"
```
**Replace `policy.enabled` with a zone-aware control.** Waits on
`ZONE-WP-0001-T03` and `T05`. Reads the zone of the actor being signed for and
that zone's failure mode. Ship deprecation and migration in the same change —
leaving both is the second-source-of-truth failure `ADR-0001` exists to prevent.
Re-run `scripts/check_policy_caller_identity.py` before enabling anything: the
WP-0031 evidence (`decision:f3f7c88f9585582a`, 2026-08-19) will be stale, and
re-establishing it is cheap precisely so this is a re-check rather than a re-do.
This closes `WARDEN-WP-0031-T05`.
**Amended by flex-auth 2026-08-19, reviewing `ZONE-WP-0001` as the PDP.** Two
constraints that change what this task can build, both argued in full at
`ZONE-WP-0001-T03` and `T04`:
1. **The zone-aware control splits across the boundary, it does not move.**
flex-auth will read compiled zone *membership* from the registry and apply the
per-zone *stance* from its policy package, returning an effect plus an
advisory annotation. What stays on the ops-warden side is the axis flex-auth
structurally cannot carry: **`fail_closed` is not expressible by a PDP.**
Fail-open describes what `warden sign` does when flex-auth is *unreachable* —
no decision is rendered, so no compiled data and no policy rule can reach it.
So the replacement for `policy.enabled` is not one zone-derived boolean
either; it is (a) stance, which arrives *in the decision*, and (b) failure
mode, which stays a local ops-warden setting declared per zone. Plan for two,
or T02 will be built expecting flex-auth to answer something it cannot.
2. **The membership compiler is ops-warden's to change, and it has a name
collision to resolve first.** `scripts/build_flex_auth_registry.py:77` already
emits `"trust_zone": "platform"` as a hardcoded constant on every
ssh-certificate resource. It is a first-class field on flex-auth's `Resource`,
it is surfaced into rego input, and **no policy package reads it**. Before
compiling zone membership, either retire that constant or deliberately
repurpose it — do not add `security_zone` beside a dormant `trust_zone` and
leave the next reader to guess which is real. That is the `ADR-0001`
second-source-of-truth failure in miniature, inside a generated artifact.
Good news for T03's cost: **no flex-auth registry schema change is required.**
`metadata`, `labels` and `attributes` already flatten into the rego input, so the
compiler can emit membership today.
```task
id: WARDEN-WP-0032-T03
status: wait
priority: medium
state_hub_task_id: "fd642377-8d64-473c-b684-f8f1a842c223"
```
**Declare ops-warden's zones** in whatever format `ZONE-WP-0001-T05` settles,
alongside the existing posture descriptors. Per flex-auth's T05 amendment, this
declaration must also say whether a zone rides the **actor** (a flex-auth
subject) or the **lane** (a flex-auth per-actor resource) — in the compiled
registry an actor is both, and the compiler needs to be told which record
carries membership. Carry the conformance rule:
*accuracy, not altitude*. Declaring a stricter zone than can be evidenced is the
failure mode that looks like progress.
```task
id: WARDEN-WP-0032-T04
status: wait
priority: medium
state_hub_task_id: "1af3d26e-ce21-46ca-a56c-486d52b76dcb"
```
**Amend `ADR-0006`.** Once zones exist and enforcement scoping is owned by
`zone-engine`, ADR-0006 must say that ops-warden *follows* the model rather than
owning it — a superseding record, never an in-place edit. Update `SCOPE.md`,
`wiki/WorkloadSecurityPosture.md`, and `wiki/PolicyGatedSigning.md` with it.
## Related
- `zone-engine` `ZONE-WP-0001` — the model, and where this work is led from
- `ADR-0006` — enforcement is zone-scoped, never a global flag
- `WARDEN-WP-0031` — the deferred flip and its readiness evidence