Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 809600@bnt-lap001 Assistant-Session: 40ba228f-c8a0-4ec9-bb29-3129951afdc4
403 lines
22 KiB
Markdown
403 lines
22 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-21"
|
|
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.
|
|
|
|
|
|
2026-10-16 bounded stopped-chart source preparation (Custodian
|
|
`access-native-admission/stopped-chart-r2`): both outbox charts admit numeric
|
|
replicaCount exactly 0 (maintenance stop) or 1 (single writer), with Recreate,
|
|
process flock and bounded persistence unchanged. Fractions, strings, booleans,
|
|
negative values and 2+ refuse; Helm decodes YAML/JSON integral 0.0/1.0 as the same
|
|
numeric values as 0/1, so lexical float spelling is not distinguished. Actual
|
|
Helm stopped/ready renders differ only in replicas and retain the same Platform
|
|
source-rendered deployment projection. Owner preparation and independent
|
|
coordinator checks approve this bounded source change only; 86 Helm renders and
|
|
the existing outbox/delivery/receiver-port suite pass. No native stop, bootstrap,
|
|
rollout, custody, public trust, resource or protected-effect approval follows.
|
|
The proposed immutable image remains source revision
|
|
`84c78e072c8f3daf2bc3b90f8efc8fbeb16c3cf0`, ordinary CI402 verified digest
|
|
`sha256:09a83b273740133518cc7e7915fb211381e06545d347d2dd1974042525ac981f`.
|
|
This chart-only successor requires its own exact source/chart pin and reviewed
|
|
runtime scope; it can explicitly select the unchanged verified 84 image without
|
|
rebuilding it or claiming that its revision label names the new chart commit.
|
|
These chart/test/workplan-only paths do not trigger the existing image workflow.
|
|
Frozen rollout R1, Platform P2 and the package extraction2b/current05a remain
|
|
unchanged. T06 stays `progress`; actual stopped-fence verification and all native
|
|
admission gates remain open under the same existing task.
|
|
|
|
|
|
2026-10-16 Access owner exact native plan review (Custodian
|
|
`access-native-plan-review-next/`): approved bounded Access workload/Helm/outbox/
|
|
stopped-only rollback plan for frozen rollout R2 manifest5be7a7f and Platform P4
|
|
manifest9eec861, chartdc0420/image84c78e OCI09a83b/projection8ce375f. Offline
|
|
verification checks every R2 manifest/source digest and reproduces both exact
|
|
Helm render bytes. The mandatory execution supplement names actual repo/Helm/CI
|
|
paths and excludes unrelated legacy rollback recipes, records the owner/founder
|
|
manual-writer acknowledgement, supplies a verified stopped-revision rollback
|
|
shape, and separates120-second stop/readiness gates from atomic rollback and
|
|
the600-second recovery decision. No production source change was needed.
|
|
Existing read-only preflight admits all7exact resources but still observes
|
|
unsigned05a/revision4running, no installed stopped fence or held window. T06
|
|
stays progress: actual founder entity/expiry/window/executor and five final
|
|
external scope dispositions, custody/trust/caller, actual stopped Helm revision,
|
|
PVC/PV Retain and native producer/consumer/restart/failure receipts remain open.
|
|
No live mutation, credential access, new key, authority or protected effect follows.
|
|
|
|
|
|
2026-10-18 source implementation under CUST-WP-0080 / NK-WP-0048-T43:
|
|
the producer-owned normal Check client now authenticates exact policy/registry/
|
|
caller/request/lifetime bindings with a nonce and the optional lossless JSON
|
|
profile. The default legacy decoder and unchanged signature/API contract remain
|
|
intact. Root pkg/decisionpep is the source of truth; its Go1.24 consumer slice is
|
|
generated by tools/export_decisionpep_adapter.py and checked by ordinary tests.
|
|
The Forgejo example uses authoritative registry tenant/issuer facts and verified
|
|
runtime identity in the admitted caller's request context. Actual producer
|
|
regressions reproduce/refuse object-shaped group inputs; policy fixtures and
|
|
Rego tests validate the original three-group contract. Full isolated root race
|
|
suite: 494 test/subtest passes, three existing optional/fixture skips. Export
|
|
parity and final group-shape regressions pass. Detailed source pins and retained
|
|
failures are in Custodian apps-access-engine-integration-r2.
|
|
|
|
This is source delivery, not owner assent or native admission. Synthetic keys,
|
|
registry and local callback proof establish no live signer/caller/policy grant.
|
|
Existing rollout R2/image84 and their approvals remain frozen; they do not admit
|
|
this source or a new image. T06 and all native custody/trust/rollout/recovery gates
|
|
remain open. Forgejo's complete resource/operation/inverse and session-revocation
|
|
coverage remains NK48-T43 / RAPPS16-T09 work.
|
|
|
|
|
|
2026-10-21 Access owner review of CCR-2026-0050 (railiance-platform fd2f808; CCR
|
|
sha256 66b3ea12d3f3bb870fa0aa0e7598e32abdb9bf39efe1adcfac286407abeb98e4, policy
|
|
b9f816da…85ef, native plan 22306d50…06e5): **approved for Access scope** and sent to
|
|
`railiance-platform` via State Hub. Secret name/key/mount/0440/kid in the CCR
|
|
projection match frozen rollout R2 activation values byte for byte; candidate
|
|
84c78e0 carries `--envelope-signing-key-file`. Public receipt prepared:
|
|
`docs/native-signer-public-pin.json` (pin derived by running `check` at 84c78e0:
|
|
`flex-auth/local`, `standalone`, `secrets-engine.catalog-lane.lifecycle`, `v2`,
|
|
`sha256:bd11c5fe…b8b4`) and `tools/prepare_signer_public_receipt.py`, which projects
|
|
the Platform `signer-cas-receipt.v1` into the exact `secrets-engine.pdp-trust.v1`
|
|
bytes plus `access-engine.signer-public-receipt.v1` (kid, public key, pin,
|
|
provenance hashes only). Rollout proposal stays R2; no parallel one. **No key exists
|
|
yet**; no rollout, trust or admission happened. Founder go is T07.
|
|
|
|
## 7. Founder go for the attended signer rollout
|
|
|
|
```task
|
|
id: FLEX-WP-0024-T07
|
|
status: wait
|
|
priority: high
|
|
needs_human: true
|
|
decision_id: "4fab263d-686b-4a06-9cea-8df619c87c03"
|
|
blocking_reason: "Founder go/no-go (entity, window, expiry, executor) for the attended R2 signer rollout, after Platform custody exists under CCR-2026-0050"
|
|
state_hub_task_id: "f3ac7eb7-0196-5853-b699-7a7f10138bf7"
|
|
```
|
|
|
|
Founder go/no-go for one attended window, after Platform custody exists under
|
|
CCR-2026-0050 (Platform-owned approval, not this task). Executes frozen R2 exactly:
|
|
stop to 0, Platform CAS0 under fence, activation values (image `sha256:09a83b27…`,
|
|
kid `flex-auth-lifecycle-20261016-r1`). Rollback to the stopped revision, then the
|
|
reviewed unsigned `05a03a87` restore; never re-CAS or restore a seed. Afterwards:
|
|
`prepare_signer_public_receipt.py` → commit receipt under `docs/evidence/` → founder
|
|
signs `pdp-trust.json` (SECRETS-WP-0014-T04, decision e4eee965) →
|
|
`verify_native_signer_receipt.py` on one native allow and deny. Then T06 closes.
|