Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a140c4-354a-7823-8f0d-8f717397f786
304 lines
15 KiB
Markdown
304 lines
15 KiB
Markdown
---
|
|
id: FLEX-WP-0024
|
|
type: workplan
|
|
title: "Sign the decision envelope: the response channel is unauthenticated"
|
|
domain: infotech
|
|
repo: flex-auth
|
|
status: active
|
|
owner: claude
|
|
topic_slug: netkingdom
|
|
planning_priority: P1
|
|
planning_order: 240
|
|
depends_on_workplans:
|
|
- FLEX-WP-0021
|
|
related_workplans:
|
|
- FLEX-WP-0019
|
|
- FLEX-WP-0023
|
|
created: "2026-09-06"
|
|
updated: "2026-10-16"
|
|
state_hub_workstream_id: "90577acd-6910-548d-a13e-1dbfdfb8ed27"
|
|
---
|
|
|
|
# FLEX-WP-0024 — Sign the decision envelope: the response channel is unauthenticated
|
|
|
|
Opened by `FLEX-DEC-2026-010`, from a question `secrets-engine` asked rather
|
|
than answering locally: **how is the response channel authenticated?** They
|
|
declined to choose a transport control for flex-auth's service on the grounds
|
|
that it is not a consumer's call. Correct, and it is why this is a flex-auth
|
|
workplan and not a mitigation in their repo.
|
|
|
|
The stance today: pins serve plain HTTP, `flex-auth.decision-record.v1` carries
|
|
no signature, and a responder that knows the package id and version — both
|
|
published — can return a well-formed `effect: allow` that passes every check a
|
|
consumer performs.
|
|
|
|
## The two things not to get backwards
|
|
|
|
**The digests do not authenticate the responder and cannot.** Every input to
|
|
them is either sent by the caller or published, so a forger reproduces
|
|
`request_digest`, `policy_package_digest`, and `registry_snapshot_digest`
|
|
exactly. Publishing more digests makes a forged envelope look *more*
|
|
authenticated, not less.
|
|
|
|
**Fail-closed does not cover this.** It protects against a PDP that is absent,
|
|
not against one that lies. An unreachable PDP denies; a lying PDP allows.
|
|
|
|
## 1. Publish the stance and stop publishing bare Service names
|
|
|
|
```task
|
|
id: FLEX-WP-0024-T01
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "b8fad80d-5c44-5e26-a632-5096e0c0f3c5"
|
|
```
|
|
|
|
Owner: `flex-auth`.
|
|
|
|
**Done 2026-09-06.** `docs/decision-record-contract.md` states that digest
|
|
recomputation is not responder verification, because the contract is the only
|
|
document a consumer is guaranteed to read.
|
|
`docs/operator-caller-access-path.md` carries the reproduced DNS probe and the
|
|
channel analysis. The trailing-dot FQDN replaced the bare Service name in
|
|
`examples/secrets-engine/README.md`, `SCOPE.md`, and the `FLEX-WP-0021-T05`
|
|
handover note — a bare `*.svc.cluster.local` name resolves through
|
|
`search ad.binect.de` to one unrelated public host, and a name for a service
|
|
that does not exist resolves to the same address.
|
|
|
|
## 2. Choose the signature shape and key custody
|
|
|
|
```task
|
|
id: FLEX-WP-0024-T02
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "5482f3cd-36cb-55e0-8c52-0ca2340ed4d7"
|
|
```
|
|
|
|
Owner: `flex-auth`; key custody is **not** ours to invent.
|
|
|
|
- Detached signature over the canonical envelope, preferred over transport-only
|
|
mTLS for two reasons: it survives the transport, so a decision audited later
|
|
is still attributable, where mTLS authenticates a connection that no longer
|
|
exists; and it composes with `FLEX-DEC-2026-009`, since a record that names
|
|
its authenticated caller *and* carries flex-auth's signature is an audit
|
|
artifact where either alone is a claim.
|
|
- Decide the canonical form to sign. The existing canonical-JSON rules from
|
|
`docs/canonical-request-digest.md` are the obvious basis, but the signed
|
|
material is the **envelope**, not the request, and it must exclude the
|
|
signature field itself. Write it down before implementing: an implicit
|
|
canonicalisation rule is what `FLEX-DEC-2026-007` had to correct.
|
|
- **Do not mint a key in this repo.** Route custody via
|
|
`warden route find "decision envelope signing key"` and OpenBao, per the
|
|
estate credential-routing rule. flex-auth decides the shape; it does not own
|
|
key material.
|
|
- State key rotation and how a consumer discovers the current public key, since
|
|
a consumer that cannot rotate trust has a pinned key that will one day be
|
|
wrong.
|
|
|
|
Gate: the canonical form, the algorithm, and the custody path are written down
|
|
and reviewed before any signing code exists.
|
|
|
|
**Done 2026-09-14.** Written in `docs/decision-envelope-signature.md`: detached
|
|
Ed25519 over `json.Marshal` of the envelope with `signature` omitted; unsigned
|
|
is `{mode: unsigned}`; production key lives in OpenBao, not this repo
|
|
(proposed catalog id `flex-auth-decision-envelope-signing-key`). Test-only seed
|
|
is documented and is not the custody path.
|
|
|
|
## 3. Implement signing and verification
|
|
|
|
```task
|
|
id: FLEX-WP-0024-T03
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "041612ea-1be4-5997-8c32-49f8bcc50855"
|
|
```
|
|
|
|
Owner: `flex-auth`.
|
|
|
|
- Add the signature to `schemas/decision_envelope.schema.json` as additive, and
|
|
state in the contract that `request_digest` is unaffected so no consumer
|
|
re-pins — the same note that was needed for v2 and for
|
|
`approval_binding_digest`.
|
|
- Emit unsigned when no key is configured, and say so in the envelope rather
|
|
than omitting the field silently. Absence stated, not implied — the
|
|
`pdp_digest` precedent.
|
|
- Ship a verification example and a fixture pair in
|
|
`examples/secrets-engine/replay/`: a genuine signed envelope and one whose
|
|
payload was altered after signing, so a consumer's verifier is tested against
|
|
a real failure rather than only a real success. **A verifier that has never
|
|
seen an invalid signature is untested**, which is the lesson from every
|
|
fixture suite in `FLEX-DEC-2026-008`.
|
|
|
|
Gate: an altered envelope fails verification, an unaltered one passes, and a
|
|
consumer can perform both from the fixtures alone.
|
|
|
|
**Done 2026-09-14.** Additive `signature` on the schema and envelope. Engine
|
|
emits `{mode: unsigned}` when no key is configured. Fixture pair:
|
|
`examples/secrets-engine/replay/decision_rotate_signed.json` verifies,
|
|
`decision_rotate_signed_tampered.json` does not, same `request_digest`. Public
|
|
key in `keys.json`. Tests in `internal/sign`.
|
|
|
|
## 4. Report the gap to gate-house
|
|
|
|
```task
|
|
id: FLEX-WP-0024-T04
|
|
status: done
|
|
priority: medium
|
|
state_hub_task_id: "82d6b75e-e2c6-5e3d-a897-fbdfb7c9094e"
|
|
```
|
|
|
|
Owner: `flex-auth`.
|
|
|
|
§6.4 obligates the PEP to act on a decision and §9.3 splits degraded mode by
|
|
owner, but **nothing in the standard requires a PDP's response to be
|
|
authenticable**. So a fully conformant PEP can trust a forged allow while
|
|
meeting every published obligation. That is a gap in the standard, not only in
|
|
this implementation, and it belongs in the outstanding v0.8 assent round
|
|
alongside the `FLEX-DEC-2026-009` finding.
|
|
|
|
## T04 closed — 2026-09-07
|
|
|
|
**Done.** Reported as `FLEX-DEC-2026-011` F1, ranked blocking for
|
|
calling the §6.4 PEP obligations complete and explicitly not blocking anyone's
|
|
adoption of v0.8 — the condition already exists under v0.7 and is not created by
|
|
this version. §16 already carries the same observation one layer up, about the
|
|
standard's own publication path having no digest or freeze discipline; F1 is
|
|
that observation applied to the artifact the standard regulates.
|
|
|
|
## 5. Wire owner signing into the decision pin and retained deployment charts
|
|
|
|
```task
|
|
id: FLEX-WP-0024-T05
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "cfe4d275-9f02-5090-8391-06eea59d45c5"
|
|
```
|
|
|
|
2026-10-16 source follow-through: NetKingdom target Secrets Engine R2 owner review
|
|
found that the completed T03 signature package was used only by fixtures. Add
|
|
explicit mounted seed/kid configuration for `serve`, sign the final envelope before
|
|
recording, and expose inert existing-Secret mounts in both retained/new charts.
|
|
The API/sign verifier sources remain byte-identical. Local source validation and
|
|
review are in the Custodian `docs/changes/netkingdom-target-2026-10-16/access-verifier-review/`.
|
|
This is source preparation only, not signer admission or deployed acceptance.
|
|
|
|
## 6. Admit custody, consumer trust, exact packages and native signed decisions
|
|
|
|
```task
|
|
id: FLEX-WP-0024-T06
|
|
status: progress
|
|
priority: high
|
|
state_hub_task_id: "7f785b52-bf62-5884-8d34-670d70bc9023"
|
|
```
|
|
|
|
Wait on Platform/OpenBao key custody, independent public-key and issuer/policy-pin
|
|
admission, exact image and existing-Secret values rollout, owner-bound caller/tunnel,
|
|
and Secrets Engine verifier installation. Follow `docs/decision-envelope-signature.md`
|
|
for overlap/removal semantics. Retain signed producer record/consumer acceptance,
|
|
forged/altered/unsigned/wrong-pin/expired denial and rotation-removal receipts;
|
|
then update actual deployed status. No signer or runtime trust is admitted by the
|
|
source review. This task owns the producer-side native gate; SECRETS-WP-0014 owns
|
|
consumer admission/enforcement. The old fixture completion never established this
|
|
gate, so the workplan is reopened rather than claiming it already passed.
|
|
|
|
|
|
2026-10-16 owner native preparation: T06 is `progress` because Access still owns
|
|
signer workload declaration, artifact/provenance and source/provider-join preparation;
|
|
no unqualified cross-owner wait is claimed. Read-only actual release metadata and
|
|
bounded proposal are in the Custodian
|
|
`docs/changes/netkingdom-target-2026-10-16/access-native-preparation/`.
|
|
The real Platform custody route `flex-auth-decision-envelope-signing-key` exists but
|
|
is draft/nonresolvable; no production kid/public fingerprint, Secret projection or
|
|
custody receipt is admitted. Coordinate that named commitment through CUST-WP-0080
|
|
instead of inventing a Platform task/approval. SECRETS-WP-0014-T03 owns installed
|
|
consumer native verification and must not form a circular blocking dependency.
|
|
|
|
The first proposed native trial only signs the unchanged lifecycle package on the
|
|
existing retained release; `execute-ccr` remains denied. Current service-pilot
|
|
`runtime_admission := false` remains mandatory: the source ServicePilotInputs is
|
|
not yet wired into HTTP, and public service_actor_binding never authenticates the
|
|
caller. Later activation needs a complete reviewed provider join, exact policy and
|
|
operation admission, artifact digests, owner execution/rollback proposal and native
|
|
receipts. Preparation/local synthetic success grants none of those permissions.
|
|
|
|
|
|
2026-10-16 source-only native preparation adds the existing Secrets PDP ServiceAccount
|
|
to `tenancy.yaml` with evidence in `docs/native-signer-workload.json`, makes the
|
|
retained `flex-auth` audience explicit, pins CI archive fetch to the full source
|
|
revision, and adds offline native signer receipt verification using the existing
|
|
Secrets verifier. No key, trust, image publication or live activation is admitted.
|
|
The managed workload's missing rapp declaration remains explicit migration debt;
|
|
this source binding does not fabricate a package declaration or raise its grade.
|
|
|
|
|
|
2026-10-16 separate B1 source follow-through wires ServicePilotInputs through actual
|
|
HTTP/TokenReview into a signed-DENY-only engine entry. Generic `execute-ccr` calls
|
|
refuse across every policy, including the old separately named positive fixture;
|
|
that proof stays pinned to ec21518e. Original context/submitted digest stay unchanged.
|
|
Actual disposable serve/TLS TokenReview/HTTP signed-DENY and bypass/refusal tests are
|
|
source evidence only. B2 depends on independently reviewed Approval fact contract
|
|
and canonical vectors; B3 additionally needs exact source/normalization numeric
|
|
representation and full trusted provider joins. No current policy false gate,
|
|
production trust, native deployment or protected effect is enabled by B1.
|
|
|
|
2026-10-16 B2/B3 source successor preparation (separate from frozen B1): the
|
|
isolated v2 source path now joins the fixed authenticated Approval reader,
|
|
unchanged Approval-owner canonical/fact helper, original/fresh correspondence,
|
|
raw numeric request/registry source retention, same policy evaluator and signed
|
|
source provenance. Disposable actual HTTP and production serve subprocess prove
|
|
synthetic signed ALLOW and fail-closed negative joins with exact response/record
|
|
parity. See `docs/trusted-service-task-source.md`. T06 remains `progress`: this
|
|
source proposal requires independent cross-owner review and full consumer/Platform
|
|
successor integration, then separately admitted runtime custody, source trust,
|
|
image provenance and rollout. No source-only proof establishes those runtime gates.
|
|
|
|
|
|
2026-10-16 B2/B3 R2 temporal correction (R1 retained): whole-stack review exposed
|
|
that R1's 15s ALLOW and default Approval freshness30 cannot fit the declared
|
|
minimum backendTTL30 plus existing cleanupreserve5. The v2 successor now derives
|
|
exact ALLOW expiry from the immutable operationdeadline, requiring all original/
|
|
fresh approval/freshness/caller bounds, a conservative source maximum120 and
|
|
remaining declaredTTL+5 after source I/O/evaluation. No consumer/native deadline
|
|
or cleanup guard is relaxed. Max structural budgets that cannot fit refuse.
|
|
The fixture declares backendTTL30/deadline60 and explicit source freshness120;
|
|
owner runtime source freshness configuration and full native integration remain
|
|
separate reviewed gates. See docs/trusted-service-task-source.md.
|
|
|
|
2026-10-16 B2/B3 R3 follows actual retained-registry integration failure: subject
|
|
enrichment changed evaluated approval-free digest from the original proposal.
|
|
The existing signed claim-free DENY now explicitly carries v2 approvalfree digest
|
|
(equal evaluated requestdigest), enabling producer-owned preapproval facts under
|
|
the already published enrichment contract. The final original proposal adds only
|
|
the issued claim; registry drift/refreshed facts cannot silently rebind it.
|
|
Retained-registry HTTP and production serve proofs and negative joins pass.
|
|
R3 preserves precise notbefore pending separate Approval-owner timestamp fidelity
|
|
repair; no rounding waiver, copied registry algorithm or empty-registry workaround.
|
|
|
|
2026-10-16 native admission prerequisite: fresh metadata confirms no namespace
|
|
PVC exists, and current outbox had neither OS writer exclusion nor a byte/startup
|
|
bound. Prepared existing-outbox hardening on exact canonical R3 base2b064ee:
|
|
nonblocking Linux flock held through Close/process lifetime before recovery,
|
|
64MiB source journal ceiling,2MiB record/atomic-batch bounds, streaming startup,
|
|
4KiB cursor, all-effect commit refusal and fenced unconfirmed rollback. Actual
|
|
process contention/crash release, capacity/prefix/restart and ENOSPC/fsync failure
|
|
proofs are part of the owner package. No new service, live mount, deletion or
|
|
custody grant; exact published successor, new PVC/PV retention/managed-package
|
|
owner disposition and independent consumer admission remain prerequisites.
|
|
|
|
2026-10-16 owner package extraction record: canonical local package
|
|
`rapp-flex-auth-secrets-engine` now exists at commit
|
|
`d52f811ed5ec0be27c9c367e71d476125b3228be`, tree
|
|
`d4fbad71bdc8ef870603526ccd1f53195d0322af`; authoritative declaration
|
|
`declarations/rapp.yaml` SHA256 `0b722431bc5162dd86f72b15f12424e149aca8f2a8790adf2c57e8511ea73b4c`.
|
|
Repo Manager exact local resolution under `/home/worsch` returns
|
|
`rapp-flex-auth-secrets-engine` / workload `flex-auth-secrets-engine` /
|
|
deployable `flex-auth-secrets-engine`, ownership `access-engine`, readiness
|
|
`draft`. The retained lifecycle PDP is the only rollback boundary claimed;
|
|
tenant/user/canary and retired `rapp-secrets-engine` remain excluded. Normative
|
|
focused family validation passes; existing unrelated global rapp-forgejo error
|
|
and reef projection warning remain visible. Package extraction keeps source
|
|
`2b064eee510640224274ccdc7cefceb1ae13162e` and current image
|
|
`sha256:05a03a8790c2210c48ea92391441c77ddf640d0cd32f5ec09838f5393171fcbd`
|
|
unchanged. Exact local source resolution is no central/private read-placement,
|
|
reef binding, custody, native rollout or consumer trust admission. Future signer
|
|
image/config adoption remains separate. T06 remains `progress`; no second work
|
|
item duplicates this bounded dependency.
|