fix: bind approval consumption to actual Flex Auth submissions
Assistant: codex Assistant-Model: gpt-5.6-luna Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
parent
89bc31460f
commit
ee4e901611
23 changed files with 612 additions and 1023 deletions
66
tests/fixtures/flex-auth-replay/PROVENANCE.md
vendored
66
tests/fixtures/flex-auth-replay/PROVENANCE.md
vendored
|
|
@ -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.
|
||||
|
|
|
|||
54
tests/fixtures/flex-auth-replay/check_request_allow_destroy_dual_control.json
vendored
Normal file
54
tests/fixtures/flex-auth-replay/check_request_allow_destroy_dual_control.json
vendored
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
23
tests/fixtures/flex-auth-replay/check_request_allow_rotate.json
vendored
Normal file
23
tests/fixtures/flex-auth-replay/check_request_allow_rotate.json
vendored
Normal 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": {}
|
||||
}
|
||||
23
tests/fixtures/flex-auth-replay/check_request_deny_wrong_tenant.json
vendored
Normal file
23
tests/fixtures/flex-auth-replay/check_request_deny_wrong_tenant.json
vendored
Normal 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": {}
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue