--- id: FLEX-WP-0021 type: workplan title: "secrets-engine consumer policy package and cluster-local pin" domain: infotech repo: flex-auth status: finished owner: claude topic_slug: netkingdom planning_priority: P1 planning_order: 210 depends_on_workplans: - FLEX-WP-0012 - FLEX-WP-0016 related_workplans: - SECRETS-WP-0009 created: "2026-09-06" updated: "2026-09-06" state_hub_workstream_id: "b01f655e-f71a-50ae-b110-178557f07c63" --- # FLEX-WP-0021 — secrets-engine consumer policy package and cluster-local pin Opened by `FLEX-DEC-2026-005`, answering secrets-engine's two questions of 2026-09-05. Both resolve to one piece of work in one order: **publish the package, then pin it.** ## Why this exists secrets-engine has already built its side. Commit `627810b` applies flex-auth's PDP answers — `resource.type: secret-catalog-lane`, `resource.system: secrets-engine`, catalog id as `request.resource.id` — and validates `ActionAuthorization.decision.binding.request_digest` against `flex-auth.decision-record.v1` with `status: approved` inside validity and distinct approvers. Two things it cannot supply itself: 1. `SECRETS_ENGINE_AUTHORIZATION_POLICY_PACKAGE` / `_VERSION` are required configuration with **no fallback**, deliberately. `secrets-engine.lifecycle/v1` is example vocabulary, not a published package. Until flex-auth publishes a real one, that configuration cannot be set to anything true. 2. There is no address for secrets-engine to call. flex-auth runs as per-consumer cluster-local pins with default-deny ingress admitting one approved workload each (`deploy/README.md`); no pin exists for secrets-engine. Its 2026-09-06 probe finding no reachable PDP found the design working, not an outage. Glas real-key execution (`SECRETS-WP-0009-T03`) is the workload waiting. ## Boundary flex-auth authors the policy package; secrets-engine owns the action vocabulary it encodes and the enforcement around the verdict. flex-auth never mutates the approval object — that is `approval-engine`'s (`security-layer-model_v0.7` §9.4) — and validates approvals only as input claims. This plan does not default, weaken, or supply a fallback for the consumer's package pin. ## 1. Obtain the real action vocabulary from secrets-engine ```task id: FLEX-WP-0021-T01 status: done priority: high state_hub_task_id: "3c191808-6fe5-5a9b-9a68-a4ce4a672dbd" ``` Owner: `flex-auth` to request and document; `secrets-engine` owns the answer. - Ask secrets-engine for the authoritative action list over `secret-catalog-lane` — the operations it will actually gate, not the `secrets-engine.lifecycle/v1` example set. - Record it in `docs/secrets-engine-action-vocabulary.md`, following `docs/tenant-engine-action-vocabulary.md`. - Record the subject classes that may appear, and which of them are service accounts versus humans behind an approval. Gate: every action in the vocabulary is named by secrets-engine, not inferred here. An inferred action is a blocker, not a default. **Done 2026-09-06.** secrets-engine delivered twelve actions in their `docs/gated-actions.md`, read out of `cli.py`: `apply`, `provision`, `rotate`, `verify`, `handoff`, `wrap`, `exec`, `deactivate`, `suspend`, `destroy`, `compromise`, `reactivate`. Recorded in `docs/secrets-engine-action-vocabulary.md`. The gate earned its keep: four would have been inferred wrongly, and `revoke` — the obvious thirteenth — does not exist as an action at all. ## 2. Publish `secrets-engine.catalog-lane.lifecycle` v1 ```task id: FLEX-WP-0021-T02 status: done priority: high state_hub_task_id: "38770813-dfcc-5711-bba3-0db50d3af135" ``` Owner: `flex-auth`. - Write `examples/secrets-engine/policy_package.md` with `id: secrets-engine.catalog-lane.lifecycle`, `version: v1`, and an explicit `allow_ttl`. An omitted TTL takes the 15m engine default; `none`/`0s` denies with `allow_lifetime_unstated` rather than granting standing access. - Add `protected_system_manifest.yaml`, `subject_manifest.yaml`, `resource_manifest.yaml`, and `registry_snapshot.json` for the namespace. - Add allow and deny fixtures in `policy_fixtures.yaml` plus `check_request_*.json`, covering at minimum: an allow per vocabulary action, wrong-tenant deny, unknown-subject deny, and an approval-claim-absent deny. - Add `examples/secrets-engine/README.md`. Gate: `flex-auth validate` passes, the fixture tests pass, and the package digest is stable across two runs. **Done 2026-09-06.** `examples/secrets-engine/` carries the package (`allow_ttl: 15m` stated explicitly), both manifests, a loadable `registry_snapshot.json`, 26 fixtures, five standalone check requests, and a README. `validate -kind policy` reports valid with 22/22 Rego tests and 26/26 fixtures passing. `destroy` is the dual-control case, gated on a `context.approval` claim marked approved with two distinct approvers; flex-auth checks what the claim says and does not re-derive its validity, signature, or supersession (`FLEX-DEC-2026-006`). Per the `FLEX-WP-0010-T02` precedent the denial ladder has no `action_not_granted` branch, because one subject holding all twelve actions could never reach it. **Reopened and re-closed 2026-09-06 at v2 — the stated gate had not been met.** This task's gate named "wrong-tenant deny" among the required fixtures and the package shipped without one, because it had no tenant rule at all: a `rotate` under `tenant: tenant:coulomb` returned `allow` against the deployed v1 (`decision:066e629bbf0c0924`). Found while answering `glas-harness`'s tenant-alignment request, which had asked for exactly that evidence. v2 adds `wrong_tenant` above `wrong_system`, three Rego tests and three fixtures (28/28 and 32/32 passing), and supersedes v1 rather than amending it because the defect failed **open** — see `FLEX-DEC-2026-008`. The `T03` replay digests are unchanged at v2; only `policy_version` and `policy_package_digest` moved. The sweep this prompted found the same shape in `tenant-engine` (`FLEX-WP-0022`). ## 3. Confirm the digest join against a real decision record ```task id: FLEX-WP-0021-T03 status: done priority: high state_hub_task_id: "8f7e5cdd-777e-5f54-9b92-c72b79f65672" ``` Owner: `flex-auth` to emit; `secrets-engine` to verify. - Emit a real `DecisionEnvelope` from the package above and hand it to secrets-engine as the replay fixture for the join it built in `627810b`. - Verify `binding.request_digest` reproduces as SHA-256 over canonical JSON of tenant/subject/action/resource/context per `docs/canonical-request-digest.md`. - Verify `provenance.registry_snapshot_digest` is present — §9.7.2 promotes it to a conformance prerequisite and `FLEX-WP-0019` closed that gap. Gate: secrets-engine confirms its validator accepts the real record unchanged. A validator change on their side is their work-record, not closed from here. **Done 2026-09-06.** `secrets-engine` confirmed both envelopes reproduce, and running them found a defect in *their* digest join: they were hashing `id`, `policy_version`, and `caring_context`, which the canonical digest excludes. Their previously pinned constant had been computed with `id` in the material, so it was wrong and its passing proved nothing — two real envelopes were what caught it. That is the value of handing over a real record rather than a described one. Re-verification then surfaced a structural finding resolved in `FLEX-DEC-2026-007`: a `pdp_digest` recorded at issue cannot equal the `request_digest` of a request carrying the claim in its hashed context. `binding.approval_binding_digest` is now published for that comparison. Original emission note follows. `examples/secrets-engine/replay/` carries two real envelopes — a plain allow (`rotate`, empty context) and the dual-control allow (`destroy` with a valid approval-claim). All three digests plus `input_claim_digests.context` verified identical across two runs; `id`, `decision_time`, and the `lifetime` bounds move with the clock and are documented as unpinnable. Both fixtures are included because `input_claim_digests.context` appears only for a non-empty context, so a consumer asserting it is always present would pass on one and fail on the other. ## 4. Stand up the `flex-auth-secrets-engine` pin ```task id: FLEX-WP-0021-T04 status: done priority: high state_hub_task_id: "f4e8709a-65dd-5172-97ae-e7c3432afb22" ``` Owner: `flex-auth`; deployment approval required. - Add `deploy/flex-auth-secrets-engine.yaml` as a three-document manifest — `Deployment`, `Service`, default-deny `NetworkPolicy` admitting ingress from the secrets-engine workload only — following the two existing pins. - Promote through the Railiance overlay (`railiance/app.toml`, `charts/flex-auth`, `values/`), not by editing the flattened emergency files. - Build in CI and deploy **by immutable digest**, never by tag. Do not roll the other pins: they are pinned to their own digests precisely so one policy change does not move another consumer. - Start `callerAuth.mode` in `warn`. Flip to `enforce` only after secrets-engine adopts a calling identity and its warn logs are clean, as `FLEX-WP-0016` did for ops-warden. Gate: the pin answers `POST /v1/check` for the secrets-engine workload and default-denies every other source; the other two pins are unchanged. **Blocked 2026-09-06 — the consumer is not a workload.** This task was written from the `ops-warden` / `tenant-engine` / `user-engine` pattern, where the pin's default-deny `NetworkPolicy` admits ingress from exactly one approved consumer *workload*. `secrets-engine` has no Kubernetes deployment at all: it is a CLI (`src/`, `cli.py`, `pyproject.toml`) with no manifests, no namespace, and no pod labels. There is no selector to write, and inventing one would be the same error corrected in `FLEX-WP-0021-T02` — authoring against a shape that does not exist. Three shapes are possible and the choice is not flex-auth's alone: 1. **Operator-run CLI reaching an in-cluster pin.** Needs a decided ingress path (tunnel or port-forward), and `callerAuth` becomes the real boundary rather than the `NetworkPolicy`, because the source address is an operator's machine rather than a pod. 2. **A `secrets-engine` workload that does not exist yet.** Then this task is correct as written but waits on `secrets-engine` to have one. 3. **No pin at all**, if the gate is only ever consulted from an operator context that already holds a decision. Raised with `secrets-engine`; T04 does not proceed until the shape is decided. Note this also changes what `callerAuth.mode: warn` is warning about, so the `FLEX-WP-0016` precedent does not transfer unexamined. ## 5. Hand the pin coordinates back and close ```task id: FLEX-WP-0021-T05 status: done priority: medium state_hub_task_id: "f0828871-fd65-5d8c-adfd-28b13fedd2b0" ``` Owner: `flex-auth`. - Reply to secrets-engine with the Service DNS, the package id and version to set in `SECRETS_ENGINE_AUTHORIZATION_POLICY_PACKAGE` / `_VERSION`, and the `callerAuth` mode currently in force. - Update `SCOPE.md` to list secrets-engine among the shipped consumers, and log a progress event. Gate: secrets-engine can set its required configuration to published values and reach a pin; nothing about the fallback-free shape of that configuration changed. **Done 2026-09-06, with one coordinate that is not flex-auth's to supply.** Handed back to `secrets-engine` and `glas-harness`: ```text Service: http://flex-auth-secrets-engine.flex-auth.svc.cluster.local.:8080 Package: secrets-engine.catalog-lane.lifecycle Version: v2 <- not v1; v1 is deployed and superseded callerAuth.mode: warn (not enforced caller authentication) ``` **Corrected 2026-09-06: the address originally handed over was a bare Service name, which is a live misdirection from a workstation.** `secrets-engine` probed it and flex-auth reproduced: `search ad.binect.de` expands any `*.svc.cluster.local` name to one unrelated public host, including names for services that do not exist. The trailing dot is required and the address is in-cluster only. `FLEX-WP-0024` carries the consequence — a published address a consumer can be misdirected from, reaching an unsigned decision envelope over plain HTTP. `SCOPE.md` lists secrets-engine among the shipped consumers and `examples/secrets-engine/README.md` carries the coordinates at source. Two things are stated rather than closed, because closing them is not ours: 1. **The pin serves v1 until a redeploy lands.** The over-permissive package is live now. Deployment approval is the user's; `FLEX-DEC-2026-008` records the defect and does not grant it. 2. **There is still no verified access path for a workstation CLI.** Ingress admits namespace `secrets-engine` with pod label `app.kubernetes.io/name=secrets-engine`; a CLI on an operator's machine is not that, and Service DNS is not workstation connectivity. `T04`'s three shapes remain undecided, and `glas-harness` named the same gap independently. `secrets-engine` cannot complete adoption on coordinates alone. The fallback-free, fail-closed shape of `SECRETS_ENGINE_AUTHORIZATION_POLICY_PACKAGE` / `_VERSION` is unchanged. ## Production deployment — 2026-09-06 User authorized production deployment in the Glas session. Installed dedicated Helm release `flex-auth-secrets-engine`, namespace `flex-auth`, revision 1, using values/secrets-engine.yaml. CI tag main-dd3ce4c resolves to immutable digest sha256:89086c02c74a931068423e70937d03df7850fa0db9c63e70be56b3558f1756af. Chart lint and server dry-run passed; deployment available 1/1. Existing three consumer deployment specifications were compared before/after and unchanged. Five published requests returned two allows and three expected denies via local port-forward. Network probes: namespace secrets-engine with pod label app.kubernetes.io/name=secrets-engine succeeded after initial propagation retry; wrong pod label and wrong namespace remained denied across retries. All six temporary probes and the port-forward were removed. Created the secrets-engine namespace for the intended consumer identity; no workload or credential was installed there. Service: http://flex-auth-secrets-engine.flex-auth.svc.cluster.local:8080. Policy: secrets-engine.catalog-lane.lifecycle / v1. Caller authentication is warn, per this task's rollout plan; this is not enforced caller authentication. Network ingress admits only the specified namespace/pod selector. T05 remains waiting on consumer configuration/adoption and the owner handoff. Approval service, KeyCape clients and real credential-lane activation are not supplied by this deployment. First-install rollback is removal of this dedicated Helm release, leaving the three existing consumers untouched. ## v2 production correction applied — 2026-09-06 Glas applied the existing operator production authorization to correct the reported v1 tenant fail-open. Helm revision 2 uses CI main-d98323b digest sha256:db1c4f7e621c7ea119489a321d7db0e05da09afc17be5f69d873b2b3c7f60cfc. Lint/server dry-run/rollout passed. Six published Check requests on the live service returned expected two allows/four denies, including wrong_tenant; all matched_policy_version values are v2. Other consumer Deployment specs are unchanged. Values pin updated. Caller authentication remains warn; workstation consumer access/adoption remains outstanding despite T05 closure. Glas has sent a follow-up requesting an explicit live owner work record for that gate. Do not roll back to known over-permissive v1; stop this dedicated release if v2 cannot be served.