fix: bind approval consumption to actual Flex Auth submissions
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
tegwick 2026-09-09 08:55:46 +02:00
parent 89bc31460f
commit ee4e901611
23 changed files with 612 additions and 1023 deletions

View file

@ -1,52 +1,22 @@
# flex-auth T03 replay fixtures
# Flex Auth replay contract fixtures
Copied verbatim from `flex-auth/examples/secrets-engine/replay/` (commit
`9e10d1c`, `FLEX-WP-0021-T03`). Real `DecisionEnvelope`s emitted by the
published `secrets-engine.catalog-lane.lifecycle` v1 package via
`go run ./cmd/flex-auth check`, from `flex-auth/local` in `standalone` mode.
Copied byte-for-byte on 2026-09-09 from flex-auth revision
`88b354377c8e26b162f1234e673072f1c06dcd89`, examples/secrets-engine/ and its
replay/ directory. Contract: FLEX-DEC-2026-012. These are standalone evaluator
outputs, not deployment evidence.
Vendored so the digest contract test is hermetic. Regenerate upstream and
re-copy if the contract version changes.
Each output is paired with its independently supplied check_request input.
Tests compute submitted_request_digest from that input; they do not reconstruct
it from the enriched response. Existing request_digest, approval_binding_digest,
policy-package and registry-snapshot pins are unchanged. Policy remains v2.
Approval correspondence compares claim.binding.pdp_digest with the evaluator's
approval_binding_digest. Neither is computed from the unenriched request.
Re-copied 2026-09-06 (twice, both upstream regenerations):
Tests inject the actual decision time instead of modifying captured lifetimes.
The older flex-auth-live fixture remains untouched and must fail the new
contract because it has no submitted_request_digest.
1. commit `9f3e7e3` completed the approval-claim on `context.approval`. Because
`context` is hashed material, completing the claim moved the request digest.
2. commit `dd3ce4c` (`FLEX-DEC-2026-007`) published
`binding.approval_binding_digest` and set the embedded claim's
`binding.pdp_digest` to it with `binding.pdp_path` true. The request digest
moved once more with the claim's contents; the approval-binding digest did
**not**, which is the property the fixture now demonstrates rather than
asserts.
3. commit `d98323b` published **v2**, which adds the `input.tenant` rule v1
never had, and a third fixture: `decision_wrong_tenant_deny.json`. The
request digests did **not** move — every allow fixture already carried
`tenant: tenant:platform` — but `provenance.policy_version` is now `v2` and
`policy_package_digest` moved to `sha256:bd11c5fe…`.
`decision_rotate.json` carries no `approval_binding_digest` — the field is
omitted on claim-free decisions rather than duplicated onto them, and a test
pins that omission.
`decision_wrong_tenant_deny.json` is an `effect: deny` envelope and carries no
`lifetime`, which is legal: the schema requires `lifetime` only for an allow.
It is the wrong-tenant denial evidence `GLAS-WP-0015` asked for. Do not
lifetime-refresh it in tests.
**v1 must not be pinned.** It had no tenant rule and failed open; flex-auth
superseded rather than amended it so the change is visible in the version
string. See `docs/tenant-alignment.md`.
**Pinned here** (stable across runs, per the upstream README):
`binding.request_digest`, `binding.approval_binding_digest`,
`provenance.policy_package_digest`, `provenance.registry_snapshot_digest`, and
the presence/absence of `provenance.input_claim_digests.context`.
`approval_binding_digest` is not only pinned but **rederived** by
`test_pdp_digest_equals_the_published_approval_binding_digest`: our canonical
implementation must reproduce it from the fixture's own request. A pin asserts
the constant; rederiving it proves we hash the same material flex-auth does.
**Never pin:** `id`, `provenance.decision_time`, `lifetime.not_before`,
`lifetime.expires_at` — all move with the clock.
`tools/exercise_approval_identity.py` additionally compiles the owner evaluator
and runs real KeyCape/Approval Engine/Flex Auth joins with synthetic credentials,
including registry override and destructive-action dual control. Its receipt
pins source, binary and producer inputs; it makes no live admission claim.

View file

@ -0,0 +1,54 @@
{
"id": "check:secrets-engine-destroy",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "destroy",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [],
"policy_targets": [],
"auth_targets": []
}
},
"context": {
"approval": {
"schema_version": "0.1",
"kind": "approval-claim",
"issuer": "approval-engine",
"approval_id": "3d1c0a8e-6b7f-4c21-9a0e-1f2b3c4d5e6f",
"state": "valid",
"valid_now": true,
"consumed": false,
"binding": {
"action": "secrets.kv.destroy",
"target": {
"id": "lane-openbao-root",
"stage": "prod"
},
"actor": "agt-secrets-engine",
"principal": "bernd",
"purpose": "rotate-exposed-key",
"digest": "sha256:3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f",
"pdp_digest": "sha256:fa07becfaa471394d06aee5fa3cd66352bf0cc69ef24900240489684cda8cd56",
"pdp_path": true
},
"freshness": {
"observed_at": "2026-09-06T12:00:00+00:00",
"ttl_seconds": 30,
"not_after": "2026-09-06T12:00:30+00:00"
},
"validity": {
"not_before": "2026-09-06T11:00:00+00:00",
"expires_at": "2026-09-06T15:00:00+00:00"
},
"reason_code": "ok"
}
}
}

View file

@ -0,0 +1,23 @@
{
"id": "check:secrets-engine-rotate",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "rotate",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [
"password"
],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
}

View file

@ -0,0 +1,23 @@
{
"id": "check:secrets-engine-wrong-tenant",
"tenant": "tenant:coulomb",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "rotate",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [
"password"
],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
}

View file

@ -100,19 +100,21 @@
}
},
"request_digest": "sha256:c749ee2dc3cdf927a70a3e5b27cff4d97a438d3264153b4b2e3bcacbaf82091a",
"approval_binding_digest": "sha256:fa07becfaa471394d06aee5fa3cd66352bf0cc69ef24900240489684cda8cd56"
"approval_binding_digest": "sha256:fa07becfaa471394d06aee5fa3cd66352bf0cc69ef24900240489684cda8cd56",
"submitted_request_digest": "sha256:c605a9ecd5711d0a1d59e7b29f3a16b53fd09d104dd077766f098e7bc5895435"
},
"lifetime": {
"kind": "ttl",
"ttl": "15m",
"not_before": "2026-09-06T18:35:19Z",
"expires_at": "2026-09-06T18:50:19Z"
"not_before": "2026-09-07T07:10:23Z",
"expires_at": "2026-09-07T07:25:23Z"
},
"diagnostics": {
"action": "destroy",
"matched_relationship": "",
"policy_package": "secrets-engine.catalog-lane.lifecycle",
"policy_status": "ready",
"registry_overrode": [],
"registry_resource": false,
"registry_subject": true
},
@ -126,7 +128,7 @@
"input_claim_digests": {
"context": "sha256:8b73d29ecef286d42e03d2420531d6c45219f325a7ae004c1ecfc781203a2800"
},
"decision_time": "2026-09-06T18:35:19Z"
"decision_time": "2026-09-07T07:10:23Z"
},
"caring": {
"profile": "caring-0.4.0-rc2",

View file

@ -69,19 +69,21 @@
"stage": "prod"
}
},
"request_digest": "sha256:de67324f54187055307a833235f83ced9fcd3a20952a27b3d19493ed39734345"
"request_digest": "sha256:de67324f54187055307a833235f83ced9fcd3a20952a27b3d19493ed39734345",
"submitted_request_digest": "sha256:41c8fc084e58c46554ccb6afe9943a99906e5986668c923811721f66d9b30a6a"
},
"lifetime": {
"kind": "ttl",
"ttl": "15m",
"not_before": "2026-09-06T18:35:18Z",
"expires_at": "2026-09-06T18:50:18Z"
"not_before": "2026-09-07T07:10:23Z",
"expires_at": "2026-09-07T07:25:23Z"
},
"diagnostics": {
"action": "rotate",
"matched_relationship": "",
"policy_package": "secrets-engine.catalog-lane.lifecycle",
"policy_status": "ready",
"registry_overrode": [],
"registry_resource": false,
"registry_subject": true
},
@ -92,7 +94,7 @@
"policy_version": "v2",
"policy_package_digest": "sha256:bd11c5fe77ce6439c65fea225ad6b71d2110efc5e7b5bc9b499c59cd0a53b8b4",
"registry_snapshot_digest": "sha256:f5a309bc0b36721fd6d9ad7f53eb21222162bc2eac62a0ab0802a9a1d51340bb",
"decision_time": "2026-09-06T18:35:18Z"
"decision_time": "2026-09-07T07:10:23Z"
},
"caring": {
"profile": "caring-0.4.0-rc2",

View file

@ -69,13 +69,15 @@
"stage": "prod"
}
},
"request_digest": "sha256:c9c6e6f8713266e9e95ae1443a395a3a1f965ba95469dea747645f0437bb0d20"
"request_digest": "sha256:c9c6e6f8713266e9e95ae1443a395a3a1f965ba95469dea747645f0437bb0d20",
"submitted_request_digest": "sha256:9aab6de9069e1e811a52835ca00bc5e9cb38166444df068eb60a26923b1c9175"
},
"diagnostics": {
"action": "rotate",
"matched_relationship": "",
"policy_package": "secrets-engine.catalog-lane.lifecycle",
"policy_status": "ready",
"registry_overrode": [],
"registry_resource": false,
"registry_subject": true
},
@ -86,7 +88,7 @@
"policy_version": "v2",
"policy_package_digest": "sha256:bd11c5fe77ce6439c65fea225ad6b71d2110efc5e7b5bc9b499c59cd0a53b8b4",
"registry_snapshot_digest": "sha256:f5a309bc0b36721fd6d9ad7f53eb21222162bc2eac62a0ab0802a9a1d51340bb",
"decision_time": "2026-09-06T18:35:20Z"
"decision_time": "2026-09-07T07:10:24Z"
},
"caring": {
"profile": "caring-0.4.0-rc2",