Answer ops-warden and secrets-engine; open FLEX-WP-0021
Two cross-repo questions arrived in the flex-auth inbox and both are answered as decision records rather than as prose in a message. FLEX-DEC-2026-004 answers ops-warden WARDEN-WP-0034-T05. A decision lifetime shorter than the SSH certificate TTL is meaningful, but only as authority to issue, never as authority to use an already-issued certificate. The pre-sign gate is the only consumer of the shorter lifetime: no replay past expires_at, fresh Check per sign. The lever that shortens effective access is the requested TTL as a policy input, which is already deployed as the ttl_out_of_bounds deny. ops-warden's section 9.7.2 window through certificate TTL is correct as written and correctly owned by the PEP; flex-auth does not want that residue moved to the PDP. docs/decision-input-freshness.md gains the same boundary as published contract text, so the ruling is not only in the decision log. FLEX-DEC-2026-005 answers secrets-engine. A real policy package is expected and flex-auth authors it here as it does for every consumer; the reserved coordinate is secrets-engine.catalog-lane.lifecycle v1 and it does not exist yet. Their choice not to default the pin was correct and is endorsed explicitly. POST /v1/check is deployed but has no estate-wide address by design -- per-consumer cluster-local pins with default-deny ingress -- so their 2026-09-06 probe found the design working, not an outage. FLEX-WP-0021 carries that work: obtain the real action vocabulary from secrets-engine, publish the package with fixtures, confirm the digest join against a real decision record, then stand up a flex-auth-secrets-engine pin in warn without moving the other two pins. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JTbVXpEiXA7mNJVpDnEPcB Assistant: claude-code Assistant-Model: opus Assistant-Process: 412054@bnt-lap001 Assistant-Session: 3968fae1-8d59-4209-9bd6-c22594b8ab19
This commit is contained in:
parent
56940727bf
commit
6a6464fcc9
4 changed files with 659 additions and 15 deletions
|
|
@ -2,9 +2,9 @@
|
|||
"schema": "repo_manager.index.v1",
|
||||
"slug": "flex-auth",
|
||||
"repo_root": "/home/worsch/flex-auth",
|
||||
"head_sha": "5753b47ccb72f96689df9ec5eeb7be100f6817cd",
|
||||
"observed_at": "2026-08-29T08:19:42.754823Z",
|
||||
"source_fingerprint": "2d3773fc38408c64d1a9a4efc7335ed792f361cdf6464f04389615c9b443fa53",
|
||||
"head_sha": "56940727bfb36aec705318b0128599f367256c23",
|
||||
"observed_at": "2026-09-05T23:08:53.160637Z",
|
||||
"source_fingerprint": "727c449ea8eb418a198b97d34244e377936060c84024b65b2af950ea71714259",
|
||||
"source_files": [
|
||||
".repo-classification.yaml",
|
||||
"INTENT.md",
|
||||
|
|
@ -27,7 +27,9 @@
|
|||
"workplans/FLEX-WP-0015-tenancy-posture-conformance.md",
|
||||
"workplans/FLEX-WP-0016-ops-warden-incluster-policy-pin.md",
|
||||
"workplans/FLEX-WP-0017-action-bound-authorization-contract.md",
|
||||
"workplans/FLEX-WP-0018-inbound-auth-corrections.md"
|
||||
"workplans/FLEX-WP-0018-inbound-auth-corrections.md",
|
||||
"workplans/FLEX-WP-0019-layer-model-conformance.md",
|
||||
"workplans/FLEX-WP-0020-repository-identity-migration.md"
|
||||
],
|
||||
"work_records": [
|
||||
{
|
||||
|
|
@ -937,7 +939,7 @@
|
|||
{
|
||||
"kind": "workplan",
|
||||
"id": "FLEX-WP-0017",
|
||||
"status": "active",
|
||||
"status": "finished",
|
||||
"title": "Action-bound authorization and durable approval contract",
|
||||
"source_path": "workplans/FLEX-WP-0017-action-bound-authorization-contract.md",
|
||||
"uuid": "d75b7256-8b3d-5797-911c-96c3199b8baa",
|
||||
|
|
@ -967,7 +969,7 @@
|
|||
{
|
||||
"kind": "task",
|
||||
"id": "FLEX-WP-0017-T03",
|
||||
"status": "wait",
|
||||
"status": "cancel",
|
||||
"title": "Add durable storage and authenticated approval evidence",
|
||||
"source_path": "workplans/FLEX-WP-0017-action-bound-authorization-contract.md",
|
||||
"uuid": "82d39961-8140-5a7f-9bd8-5164dd1742e5",
|
||||
|
|
@ -987,7 +989,7 @@
|
|||
{
|
||||
"kind": "task",
|
||||
"id": "FLEX-WP-0017-T05",
|
||||
"status": "wait",
|
||||
"status": "cancel",
|
||||
"title": "Consumer handoff and live destructive-action proof",
|
||||
"source_path": "workplans/FLEX-WP-0017-action-bound-authorization-contract.md",
|
||||
"uuid": "8c3fc0a2-0855-5ac9-afa5-d03b8b1f0bf9",
|
||||
|
|
@ -1024,6 +1026,196 @@
|
|||
"parent_id": "FLEX-WP-0018",
|
||||
"extra": {}
|
||||
},
|
||||
{
|
||||
"kind": "workplan",
|
||||
"id": "FLEX-WP-0019",
|
||||
"status": "finished",
|
||||
"title": "Layer model v0.7 conformance: provenance, lifetimes, deadlines, and the decision contract",
|
||||
"source_path": "workplans/FLEX-WP-0019-layer-model-conformance.md",
|
||||
"uuid": "84b9dc5a-71f2-5c70-ace6-78242b13d0f1",
|
||||
"parent_id": null,
|
||||
"extra": {}
|
||||
},
|
||||
{
|
||||
"kind": "task",
|
||||
"id": "FLEX-WP-0019-T01",
|
||||
"status": "done",
|
||||
"title": "Assert the layer declaration mechanically",
|
||||
"source_path": "workplans/FLEX-WP-0019-layer-model-conformance.md",
|
||||
"uuid": "f72b1305-114e-5ba2-b84c-f6cc0b524178",
|
||||
"parent_id": "FLEX-WP-0019",
|
||||
"extra": {}
|
||||
},
|
||||
{
|
||||
"kind": "task",
|
||||
"id": "FLEX-WP-0019-T02",
|
||||
"status": "done",
|
||||
"title": "Add the registry-snapshot digest to decision provenance",
|
||||
"source_path": "workplans/FLEX-WP-0019-layer-model-conformance.md",
|
||||
"uuid": "7a980074-8488-5ab1-9202-60878adb261d",
|
||||
"parent_id": "FLEX-WP-0019",
|
||||
"extra": {}
|
||||
},
|
||||
{
|
||||
"kind": "task",
|
||||
"id": "FLEX-WP-0019-T03",
|
||||
"status": "done",
|
||||
"title": "Give every allow an explicit lifetime",
|
||||
"source_path": "workplans/FLEX-WP-0019-layer-model-conformance.md",
|
||||
"uuid": "9d9c0e7a-56e2-5c71-9110-cea973243c22",
|
||||
"parent_id": "FLEX-WP-0019",
|
||||
"extra": {}
|
||||
},
|
||||
{
|
||||
"kind": "task",
|
||||
"id": "FLEX-WP-0019-T04",
|
||||
"status": "done",
|
||||
"title": "State revocation visibility deadlines per input class",
|
||||
"source_path": "workplans/FLEX-WP-0019-layer-model-conformance.md",
|
||||
"uuid": "d5917b24-2efb-503e-9a9d-837702cafc1c",
|
||||
"parent_id": "FLEX-WP-0019",
|
||||
"extra": {}
|
||||
},
|
||||
{
|
||||
"kind": "task",
|
||||
"id": "FLEX-WP-0019-T05",
|
||||
"status": "done",
|
||||
"title": "Publish the decision-record schema as flex-auth's contract",
|
||||
"source_path": "workplans/FLEX-WP-0019-layer-model-conformance.md",
|
||||
"uuid": "f7f501d7-6862-5542-88f7-78b704524706",
|
||||
"parent_id": "FLEX-WP-0019",
|
||||
"extra": {}
|
||||
},
|
||||
{
|
||||
"kind": "task",
|
||||
"id": "FLEX-WP-0019-T06",
|
||||
"status": "done",
|
||||
"title": "Publish the request digest as the replay test",
|
||||
"source_path": "workplans/FLEX-WP-0019-layer-model-conformance.md",
|
||||
"uuid": "bc109ee3-14b0-5603-a655-0d7376c2a41c",
|
||||
"parent_id": "FLEX-WP-0019",
|
||||
"extra": {}
|
||||
},
|
||||
{
|
||||
"kind": "workplan",
|
||||
"id": "FLEX-WP-0020",
|
||||
"status": "proposed",
|
||||
"title": "Repository identity migration from flex-auth to access-engine",
|
||||
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
|
||||
"uuid": "99a661a8-b36c-5c1c-b78b-1e8930bcd0a9",
|
||||
"parent_id": null,
|
||||
"extra": {}
|
||||
},
|
||||
{
|
||||
"kind": "task",
|
||||
"id": "FLEX-WP-0020-T01",
|
||||
"status": "todo",
|
||||
"title": "1. Capture immutable cleanliness and identity baseline",
|
||||
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
|
||||
"uuid": "bb989019-50a7-5273-ba09-b2df2e2602a4",
|
||||
"parent_id": "FLEX-WP-0020",
|
||||
"extra": {}
|
||||
},
|
||||
{
|
||||
"kind": "task",
|
||||
"id": "FLEX-WP-0020-T02",
|
||||
"status": "todo",
|
||||
"title": "2. Prepare repository metadata and work-record frontmatter",
|
||||
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
|
||||
"uuid": "102e9dc7-4724-5e80-84c4-092e6ecfbb2b",
|
||||
"parent_id": "FLEX-WP-0020",
|
||||
"extra": {}
|
||||
},
|
||||
{
|
||||
"kind": "task",
|
||||
"id": "FLEX-WP-0020-T03",
|
||||
"status": "todo",
|
||||
"title": "3. Decide product and runtime naming separately",
|
||||
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
|
||||
"uuid": "5095ddbc-b69c-5a52-b97f-08fca9b610c3",
|
||||
"parent_id": "FLEX-WP-0020",
|
||||
"extra": {}
|
||||
},
|
||||
{
|
||||
"kind": "task",
|
||||
"id": "FLEX-WP-0020-T04",
|
||||
"status": "todo",
|
||||
"title": "4. Inventory consumers and create owned handoffs",
|
||||
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
|
||||
"uuid": "9bb138e1-5edb-5714-8d89-3b1b7039a7ce",
|
||||
"parent_id": "FLEX-WP-0020",
|
||||
"extra": {}
|
||||
},
|
||||
{
|
||||
"kind": "task",
|
||||
"id": "FLEX-WP-0020-T05",
|
||||
"status": "todo",
|
||||
"title": "5. Renew State Hub preflight and record approval",
|
||||
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
|
||||
"uuid": "86fecbb6-b008-5354-8d70-0f4ba5077a58",
|
||||
"parent_id": "FLEX-WP-0020",
|
||||
"extra": {}
|
||||
},
|
||||
{
|
||||
"kind": "task",
|
||||
"id": "FLEX-WP-0020-T06",
|
||||
"status": "wait",
|
||||
"title": "6. Execute the Forgejo repository rename",
|
||||
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
|
||||
"uuid": "aa7ccd59-ddd3-5af0-a866-30e88552fc09",
|
||||
"parent_id": "FLEX-WP-0020",
|
||||
"extra": {}
|
||||
},
|
||||
{
|
||||
"kind": "task",
|
||||
"id": "FLEX-WP-0020-T07",
|
||||
"status": "wait",
|
||||
"title": "7. Record the State Hub identity rebind",
|
||||
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
|
||||
"uuid": "155041ed-8cba-5e5e-ac59-fc03ef6dd0ce",
|
||||
"parent_id": "FLEX-WP-0020",
|
||||
"extra": {}
|
||||
},
|
||||
{
|
||||
"kind": "task",
|
||||
"id": "FLEX-WP-0020-T08",
|
||||
"status": "wait",
|
||||
"title": "8. Establish and register a fresh canonical clone",
|
||||
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
|
||||
"uuid": "c8afe078-41cf-5d8c-84d1-a49e4b618d34",
|
||||
"parent_id": "FLEX-WP-0020",
|
||||
"extra": {}
|
||||
},
|
||||
{
|
||||
"kind": "task",
|
||||
"id": "FLEX-WP-0020-T09",
|
||||
"status": "wait",
|
||||
"title": "9. Verify identity, history, routes, builds, and deployments",
|
||||
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
|
||||
"uuid": "fdb50a96-f7fa-5d4a-9efa-231750dcb2f1",
|
||||
"parent_id": "FLEX-WP-0020",
|
||||
"extra": {}
|
||||
},
|
||||
{
|
||||
"kind": "task",
|
||||
"id": "FLEX-WP-0020-T10",
|
||||
"status": "wait",
|
||||
"title": "10. Exercise rollback decision points",
|
||||
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
|
||||
"uuid": "3dc49eb4-1f39-5250-87c9-716a9a97790d",
|
||||
"parent_id": "FLEX-WP-0020",
|
||||
"extra": {}
|
||||
},
|
||||
{
|
||||
"kind": "task",
|
||||
"id": "FLEX-WP-0020-T11",
|
||||
"status": "wait",
|
||||
"title": "11. Soak, hand off residuals, and clean up the old checkout",
|
||||
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
|
||||
"uuid": "46d8318b-d1fb-52e3-b632-52230ed1438f",
|
||||
"parent_id": "FLEX-WP-0020",
|
||||
"extra": {}
|
||||
},
|
||||
{
|
||||
"kind": "decision",
|
||||
"id": "FLEX-DEC-2026-001",
|
||||
|
|
@ -1112,7 +1304,7 @@
|
|||
"status": "resolved",
|
||||
"title": "Review of security layer model v0.6 and companion v0.1: assent, two answers, five findings",
|
||||
"source_path": "decisions/decisions.md",
|
||||
"uuid": null,
|
||||
"uuid": "def6a31b-4cd7-4474-8cfa-339b0fc9fcc6",
|
||||
"parent_id": null,
|
||||
"extra": {
|
||||
"record": {
|
||||
|
|
@ -1141,7 +1333,72 @@
|
|||
"updated": "2026-08-29T08:19:41.832549Z",
|
||||
"rationale": "Assent to v0.6 and companion v0.1, with two answers and five findings, none blocking. Q1: section 6.4.2 is right but collides with 9.7.1's session-bound allow, needs the request digest as its mechanical replay test, and should rule explicitly on deny-caching. Q2: the visibility deadline does land on a PDP and harder than at a PEP, but must be per input class rather than one number, and it makes flex-auth's registry-provenance gap load-bearing rather than untidy. Findings: 6.4's stance-map register does not exist in section 13 and neither document says where a map is published; the companion omits it too, which is the sufficiency gap gate-house asked for; section 17 puts the decision-record schema in Taxonomy when it is the PDP's output artifact, inverting the section 2 rule flex-auth used to decline authentication evidence; sections 17-19 are H1 outside the hierarchy; section 19 grades the document it lives in and will age.",
|
||||
"decided_by": "flex-auth (reviewing side)",
|
||||
"decided_at": "2026-08-29T08:19:41.832549Z"
|
||||
"decided_at": "2026-08-29T08:19:41.832549Z",
|
||||
"state_hub_decision_id": "def6a31b-4cd7-4474-8cfa-339b0fc9fcc6"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "decision",
|
||||
"id": "FLEX-DEC-2026-004",
|
||||
"status": "resolved",
|
||||
"title": "Decision lifetime does not reach past issuance: answer to ops-warden WARDEN-WP-0034-T05",
|
||||
"source_path": "decisions/decisions.md",
|
||||
"uuid": null,
|
||||
"parent_id": null,
|
||||
"extra": {
|
||||
"record": {
|
||||
"id": "FLEX-DEC-2026-004",
|
||||
"kind": "decision",
|
||||
"title": "Decision lifetime does not reach past issuance: answer to ops-warden WARDEN-WP-0034-T05",
|
||||
"status": "resolved",
|
||||
"origin": "cross-repo",
|
||||
"origin_ref": "WARDEN-WP-0034-T05",
|
||||
"owner": "flex-auth",
|
||||
"affects": [
|
||||
"flex-auth",
|
||||
"ops-warden"
|
||||
],
|
||||
"requested_dispositions": [
|
||||
"answer",
|
||||
"decline"
|
||||
],
|
||||
"decided_by": "flex-auth (access-engine / PDP)",
|
||||
"rationale": "Answered, not declined. A decision lifetime shorter than the SSH certificate TTL is meaningful, but only as an authority-to-issue window, never as an authority-to-use window over an already-issued certificate. flex-auth lifetime.expires_at bounds how long that one allow may be relied on to authorise a sign; it cannot bound an artifact ops-warden issued under it, and flex-auth does not claim it does. The downstream contract that consumes the shorter lifetime is the pre-sign gate itself: no replay of an allow past expires_at, and a fresh Check per sign. The lever that actually shortens effective access is the requested TTL as a policy input, which is already deployed as the ttl_out_of_bounds deny; ops-warden section 9.7.2 window through cert TTL is correctly stated and correctly owned by the PEP.",
|
||||
"created": "2026-09-05T23:08:38.877787Z",
|
||||
"updated": "2026-09-05T23:08:38.877787Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "decision",
|
||||
"id": "FLEX-DEC-2026-005",
|
||||
"status": "resolved",
|
||||
"title": "secrets-engine policy package is expected but unpublished; /v1/check has no estate-wide endpoint by design",
|
||||
"source_path": "decisions/decisions.md",
|
||||
"uuid": null,
|
||||
"parent_id": null,
|
||||
"extra": {
|
||||
"record": {
|
||||
"id": "FLEX-DEC-2026-005",
|
||||
"kind": "decision",
|
||||
"title": "secrets-engine policy package is expected but unpublished; /v1/check has no estate-wide endpoint by design",
|
||||
"status": "resolved",
|
||||
"origin": "cross-repo",
|
||||
"origin_ref": "secrets-engine SECRETS-WP-0009-T03",
|
||||
"owner": "flex-auth",
|
||||
"affects": [
|
||||
"flex-auth",
|
||||
"secrets-engine",
|
||||
"approval-engine"
|
||||
],
|
||||
"requested_dispositions": [
|
||||
"answer"
|
||||
],
|
||||
"decided_by": "flex-auth (access-engine / PDP)",
|
||||
"rationale": "Two answers. (1) Yes, a real package is expected, and flex-auth authors it in this repo as it did for every other consumer; the reserved coordinate is secrets-engine.catalog-lane.lifecycle at version v1, and it does not exist yet. Until it is published and pinned, secrets-engine keeping SECRETS_ENGINE_AUTHORIZATION_POLICY_PACKAGE and _VERSION as required configuration with no fallback is the correct shape and flex-auth endorses it. (2) POST /v1/check is deployed, but there is no estate-wide PDP address and there is not meant to be one: each consumer gets its own cluster-local pin whose NetworkPolicy default-denies ingress except from that one approved workload. The 2026-09-06 probe finding no reachable PDP is the design working, not an outage. A reachable endpoint for secrets-engine is a per-consumer pin that follows its policy package.",
|
||||
"created": "2026-09-05T23:08:51.144287Z",
|
||||
"updated": "2026-09-05T23:08:51.144287Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -1220,17 +1477,17 @@
|
|||
"events": [
|
||||
{
|
||||
"type": "repo.command.applied",
|
||||
"command": "repo.work.resolve_decision",
|
||||
"operation": "resolve",
|
||||
"correlation_id": "fe430c1e-83bb-4dba-b6fc-337baa1a0059",
|
||||
"command": "repo.work.create_decision",
|
||||
"operation": "create",
|
||||
"correlation_id": "ff360be5-e7bf-4914-95c3-1ea6df9f6957",
|
||||
"kind": "decision",
|
||||
"id": "FLEX-DEC-2026-003",
|
||||
"git_sha": "5753b47ccb72f96689df9ec5eeb7be100f6817cd",
|
||||
"id": "FLEX-DEC-2026-005",
|
||||
"git_sha": null,
|
||||
"files_touched": [
|
||||
"decisions/decisions.md"
|
||||
],
|
||||
"source": "repo-manager",
|
||||
"emitted_at": "2026-08-29T08:19:42.754925Z"
|
||||
"emitted_at": "2026-09-05T23:08:53.160770Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -537,3 +537,208 @@ questions already carry the same content as questions rather than as a grade.
|
|||
conformance prerequisite under §9.7.2, and will be planned as one.
|
||||
- flex-auth offers the canonical request digest as the §6.4.2 replay test and
|
||||
offers to publish the decision-record schema as its own contract.
|
||||
|
||||
## FLEX-DEC-2026-004 — Decision lifetime does not reach past issuance: answer to ops-warden WARDEN-WP-0034-T05
|
||||
|
||||
```yaml
|
||||
id: FLEX-DEC-2026-004
|
||||
kind: decision
|
||||
title: 'Decision lifetime does not reach past issuance: answer to ops-warden WARDEN-WP-0034-T05'
|
||||
status: resolved
|
||||
origin: cross-repo
|
||||
origin_ref: WARDEN-WP-0034-T05
|
||||
owner: flex-auth
|
||||
affects:
|
||||
- flex-auth
|
||||
- ops-warden
|
||||
requested_dispositions:
|
||||
- answer
|
||||
- decline
|
||||
decided_by: flex-auth (access-engine / PDP)
|
||||
rationale: 'Answered, not declined. A decision lifetime shorter than the SSH certificate
|
||||
TTL is meaningful, but only as an authority-to-issue window, never as an authority-to-use
|
||||
window over an already-issued certificate. flex-auth lifetime.expires_at bounds
|
||||
how long that one allow may be relied on to authorise a sign; it cannot bound an
|
||||
artifact ops-warden issued under it, and flex-auth does not claim it does. The downstream
|
||||
contract that consumes the shorter lifetime is the pre-sign gate itself: no replay
|
||||
of an allow past expires_at, and a fresh Check per sign. The lever that actually
|
||||
shortens effective access is the requested TTL as a policy input, which is already
|
||||
deployed as the ttl_out_of_bounds deny; ops-warden section 9.7.2 window through
|
||||
cert TTL is correctly stated and correctly owned by the PEP.'
|
||||
created: '2026-09-05T23:08:38.877787Z'
|
||||
updated: '2026-09-06T00:00:00.000000Z'
|
||||
decided_at: '2026-09-06T00:00:00.000000Z'
|
||||
```
|
||||
|
||||
## Context
|
||||
|
||||
ops-warden now states its §9.7.2 revocation window explicitly: an allow can
|
||||
remain effective through an already-issued SSH certificate until TTL expiry
|
||||
(`adm` 48h, `agt` 24h, `atm` 8h), because there is no CRL/KRL recall channel.
|
||||
It asked flex-auth, as the access engine, whether a decision lifetime shorter
|
||||
than the certificate TTL is meaningful under that PEP behaviour, and if so what
|
||||
downstream contract should consume the shorter lifetime.
|
||||
|
||||
## Disposition
|
||||
|
||||
**Answered, not declined.** The lifetime is meaningful, and the mismatch
|
||||
ops-warden noticed is not a defect on either side. It is two different objects
|
||||
with two different revocation stories.
|
||||
|
||||
### A decision lifetime is authority to issue, not authority to use
|
||||
|
||||
`lifetime.expires_at` (`docs/decision-record-contract.md`, default TTL 15m)
|
||||
bounds one thing: how long that particular allow may be relied on as the
|
||||
authority for the action it decided. For ops-warden the action is `sign`, and
|
||||
the action completes at issuance. Once the certificate exists, the decision has
|
||||
been consumed; nothing in the decision record reaches the artifact.
|
||||
|
||||
flex-auth does not claim otherwise, and a PDP that did would be lying. The
|
||||
estate already ruled that fail-open is not expressible by a PDP because there
|
||||
is no evaluator in that path; recall of an issued credential is the same shape
|
||||
of claim. There is no flex-auth in the path of an SSH session already
|
||||
authenticated by a valid certificate.
|
||||
|
||||
So a 15m decision lifetime against a 48h `adm` certificate is not an
|
||||
inconsistency to reconcile. It says the gate's answer goes stale in 15 minutes;
|
||||
it says nothing about the certificate, and was never asked to.
|
||||
|
||||
### The downstream contract is the pre-sign gate itself
|
||||
|
||||
That is the only consumer of the shorter lifetime, and it consumes it two ways:
|
||||
|
||||
1. **No replay past `expires_at`.** An allow may not be reused to authorise a
|
||||
second `warden sign`, and a cached verdict must not outlive its TTL. This is
|
||||
already the published rule (`docs/decision-input-freshness.md`, "How to read
|
||||
this as a consumer") and it is the whole of what the lifetime binds.
|
||||
2. **Fresh Check per sign.** Because the lifetime is short and claims are not
|
||||
cached PDP-side, revocation of a subject, actor, or approval claim is visible
|
||||
to the *next* sign immediately — deadline 0 for the claim class. That is the
|
||||
real value of the short lifetime: it bounds how long a revoked principal can
|
||||
keep obtaining *new* certificates, which is the part flex-auth can bound.
|
||||
|
||||
### The lever that shortens effective access is already deployed
|
||||
|
||||
If the goal is to shrink the window in which a revoked principal retains live
|
||||
access, the mechanism is not a shorter decision lifetime — it is a shorter
|
||||
**certificate** TTL, and the requested TTL is already a policy input. The
|
||||
shipped `ops-warden.ssh-certificate.sign` package denies `ttl_out_of_bounds`
|
||||
before OpenBao is reached (verified 2026-06-29). Capping the requested TTL per
|
||||
actor class or per zone in the policy package is a policy change flex-auth can
|
||||
make and explain; asking the decision lifetime to reach past issuance is not.
|
||||
|
||||
flex-auth is not proposing that change here. Certificate TTLs are ops-warden's
|
||||
to set, and this record does not reopen them.
|
||||
|
||||
## Consequences
|
||||
|
||||
- ops-warden's §9.7.2 statement — allow effective through certificate TTL,
|
||||
bounded by 48h/24h/8h, no recall channel — is **correct as written and
|
||||
correctly owned by the PEP**. flex-auth does not want that residue moved to
|
||||
the PDP's side of the line.
|
||||
- No change to `flex-auth.decision-record.v1`. `lifetime` keeps its published
|
||||
meaning; this record states the boundary it already had rather than adding
|
||||
one.
|
||||
- If ops-warden ever wants a certificate TTL bound to policy rather than to
|
||||
actor class, that is a policy-package change under a new workplan, not a
|
||||
lifetime semantics change.
|
||||
|
||||
## FLEX-DEC-2026-005 — secrets-engine policy package is expected but unpublished; /v1/check has no estate-wide endpoint by design
|
||||
|
||||
```yaml
|
||||
id: FLEX-DEC-2026-005
|
||||
kind: decision
|
||||
title: secrets-engine policy package is expected but unpublished; /v1/check has no
|
||||
estate-wide endpoint by design
|
||||
status: resolved
|
||||
origin: cross-repo
|
||||
origin_ref: secrets-engine SECRETS-WP-0009-T03
|
||||
owner: flex-auth
|
||||
affects:
|
||||
- flex-auth
|
||||
- secrets-engine
|
||||
- approval-engine
|
||||
requested_dispositions:
|
||||
- answer
|
||||
decided_by: flex-auth (access-engine / PDP)
|
||||
rationale: 'Two answers. (1) Yes, a real package is expected, and flex-auth authors
|
||||
it in this repo as it did for every other consumer; the reserved coordinate is secrets-engine.catalog-lane.lifecycle
|
||||
at version v1, and it does not exist yet. Until it is published and pinned, secrets-engine
|
||||
keeping SECRETS_ENGINE_AUTHORIZATION_POLICY_PACKAGE and _VERSION as required configuration
|
||||
with no fallback is the correct shape and flex-auth endorses it. (2) POST /v1/check
|
||||
is deployed, but there is no estate-wide PDP address and there is not meant to be
|
||||
one: each consumer gets its own cluster-local pin whose NetworkPolicy default-denies
|
||||
ingress except from that one approved workload. The 2026-09-06 probe finding no
|
||||
reachable PDP is the design working, not an outage. A reachable endpoint for secrets-engine
|
||||
is a per-consumer pin that follows its policy package.'
|
||||
created: '2026-09-05T23:08:51.144287Z'
|
||||
updated: '2026-09-06T00:00:00.000000Z'
|
||||
decided_at: '2026-09-06T00:00:00.000000Z'
|
||||
```
|
||||
|
||||
## Context
|
||||
|
||||
secrets-engine applied flex-auth's earlier PDP answers in commit `627810b`
|
||||
(`resource.type: secret-catalog-lane`, `resource.system: secrets-engine`,
|
||||
catalog id as `request.resource.id`, and a join that validates
|
||||
`ActionAuthorization.decision.binding.request_digest` against
|
||||
`flex-auth.decision-record.v1`). It then asked two questions, with Glas
|
||||
real-key execution (`SECRETS-WP-0009-T03`) waiting on the answers.
|
||||
|
||||
## Disposition
|
||||
|
||||
### Answer 1 — yes, a package is expected; it does not exist yet
|
||||
|
||||
Every shipped consumer's policy package is authored **in this repository**, not
|
||||
in the consumer's, and baked into the image: `ops-warden.ssh-certificate.sign`
|
||||
(v2), `tenant-engine.write-api.mutate`, `user-engine.portal.authorize`,
|
||||
`railiance-platform.credential-grant.issue`, `qonto-assistant.finance-read`.
|
||||
secrets-engine follows the same path. The reserved coordinate is:
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Package | `secrets-engine.catalog-lane.lifecycle` |
|
||||
| Version | `v1` |
|
||||
|
||||
This is a **reservation, not a publication.** No such package exists in
|
||||
`examples/` today, and until one is published and pinned, a Check naming it
|
||||
evaluates nothing. `FLEX-WP-0021` carries the work.
|
||||
|
||||
secrets-engine's decision not to default the pin was the right call and
|
||||
flex-auth endorses it explicitly: keeping
|
||||
`SECRETS_ENGINE_AUTHORIZATION_POLICY_PACKAGE` and `_VERSION` as required
|
||||
configuration with no fallback is exactly correct. Defaulting them would have
|
||||
pinned production to example vocabulary nobody publishes, and the failure would
|
||||
have been a silent evaluation against a package that is not there rather than a
|
||||
loud missing-configuration error.
|
||||
|
||||
### Answer 2 — /v1/check is deployed; there is no estate-wide address, by design
|
||||
|
||||
The 2026-09-06 probe finding no reachable PDP found the design working.
|
||||
|
||||
flex-auth runs as **per-consumer cluster-local pins**, not as one shared
|
||||
service. Three are live (`flex-auth-ops-warden`, `flex-auth-tenant-engine`,
|
||||
`flex-auth-user-engine`). Each is a three-document manifest whose third document
|
||||
is a default-deny `NetworkPolicy` admitting ingress from exactly one approved
|
||||
consumer workload, and each is pinned to its own image digest so one consumer's
|
||||
policy can roll without moving another's (`deploy/README.md`). Caller
|
||||
authentication is per-pin: on the ops-warden pin it is `enforce`, so an
|
||||
anonymous `/v1/check` is 401 and a token bound to another protected system is
|
||||
403 (`FLEX-WP-0016`).
|
||||
|
||||
There is therefore no address for secrets-engine to reach *because none has been
|
||||
created for it*. A reachable endpoint is a per-consumer pin, and a pin follows
|
||||
its policy package — so both questions resolve to the same piece of work, in
|
||||
that order: publish the package, then pin it.
|
||||
|
||||
## Consequences
|
||||
|
||||
- `FLEX-WP-0021` is opened in this repo to publish
|
||||
`secrets-engine.catalog-lane.lifecycle` v1 and stand up a
|
||||
`flex-auth-secrets-engine` pin. It needs secrets-engine's real action
|
||||
vocabulary as input; the example `secrets-engine.lifecycle/v1` vocabulary is
|
||||
explicitly not it.
|
||||
- secrets-engine should keep the pin required and unset until the package is
|
||||
published. Nothing in this record authorises a fallback value.
|
||||
- No change to `flex-auth.decision-record.v1`. The digest join secrets-engine
|
||||
built against it is correct as described.
|
||||
|
|
|
|||
|
|
@ -62,6 +62,21 @@ read a different snapshot.
|
|||
|
||||
A stale-directory signal from Topaz fails closed (`topaz_directory_stale`).
|
||||
|
||||
## Lifetime versus an artifact issued under the decision
|
||||
|
||||
`lifetime.expires_at` bounds the **decision**, not anything a consumer creates
|
||||
while acting on it. It is authority to issue, not authority to use.
|
||||
|
||||
Where a PEP's enforcement produces a durable credential — ops-warden's SSH
|
||||
certificate is the shipped case — the credential's own TTL governs it, and no
|
||||
flex-auth field reaches it. A short decision lifetime bounds how long a revoked
|
||||
principal can keep obtaining *new* credentials, which is the part a PDP can
|
||||
bound; it does not recall one already issued. The residue between the two is the
|
||||
consumer's §9.7.2 window to state, and it is correctly stated at the PEP.
|
||||
|
||||
The lever that shortens the artifact's own life is the requested TTL as a policy
|
||||
input, not the decision lifetime. See `FLEX-DEC-2026-004`.
|
||||
|
||||
## How to read this as a consumer
|
||||
|
||||
1. Do not cache a verdict past `lifetime.expires_at`.
|
||||
|
|
@ -70,3 +85,5 @@ A stale-directory signal from Topaz fails closed (`topaz_directory_stale`).
|
|||
to a fresh Check.
|
||||
3. Approval revocation is a claim-class problem: stop presenting the claim,
|
||||
and do not replay a digest that included it.
|
||||
4. Do not read `lifetime` as a bound on a credential you issued under the
|
||||
allow. Bound that credential with its own TTL.
|
||||
|
|
|
|||
165
workplans/FLEX-WP-0021-secrets-engine-consumer-policy-gate.md
Normal file
165
workplans/FLEX-WP-0021-secrets-engine-consumer-policy-gate.md
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
---
|
||||
id: FLEX-WP-0021
|
||||
type: workplan
|
||||
title: "secrets-engine consumer policy package and cluster-local pin"
|
||||
domain: infotech
|
||||
repo: flex-auth
|
||||
status: proposed
|
||||
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"
|
||||
---
|
||||
|
||||
# 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: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## 2. Publish `secrets-engine.catalog-lane.lifecycle` v1
|
||||
|
||||
```task
|
||||
id: FLEX-WP-0021-T02
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## 3. Confirm the digest join against a real decision record
|
||||
|
||||
```task
|
||||
id: FLEX-WP-0021-T03
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## 4. Stand up the `flex-auth-secrets-engine` pin
|
||||
|
||||
```task
|
||||
id: FLEX-WP-0021-T04
|
||||
status: wait
|
||||
priority: high
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## 5. Hand the pin coordinates back and close
|
||||
|
||||
```task
|
||||
id: FLEX-WP-0021-T05
|
||||
status: wait
|
||||
priority: medium
|
||||
```
|
||||
|
||||
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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue