Assistant: codex Assistant-Model: gpt-5.6-luna Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
183 lines
10 KiB
Markdown
183 lines
10 KiB
Markdown
---
|
|
id: SECRETS-WP-0009
|
|
type: workplan
|
|
title: "Activate native Claude credential delivery for Glas"
|
|
domain: infotech
|
|
repo: secrets-engine
|
|
status: blocked
|
|
owner: codex
|
|
created: "2026-09-05"
|
|
updated: "2026-09-09"
|
|
state_hub_workstream_id: "40ccc3b4-d046-5a58-8649-e7935f45c974"
|
|
---
|
|
|
|
Demand: GLAS-WP-0012-T02 / SAND-WP-0015-T04, custody CCR-2026-0016.
|
|
The user authorized continuing credential delivery after storing the key in Bao.
|
|
This record tracks native read-lane adoption, not a new provider key or rotation.
|
|
|
|
## Define the exact native read lane
|
|
|
|
```task
|
|
id: SECRETS-WP-0009-T01
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "4f1cf242-3aec-56a8-b958-5d53823875e0"
|
|
```
|
|
|
|
Added catalog glas-claude-agent-dev-anthropic; plan checks existing platform
|
|
mount and proposes one data-only read policy/AppRole. Token TTL5m/max15m,
|
|
single-use SecretID5m, token use budget8. See docs/glas-claude-delivery.md.
|
|
No metadata, sibling, listing or write capability is proposed.
|
|
|
|
## Support data-only delivery policies
|
|
|
|
```task
|
|
id: SECRETS-WP-0009-T02
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "fa4a3e87-0854-5e2b-af44-facd5b25f443"
|
|
```
|
|
|
|
Added boolean delivery_auth.metadata_read with compatible default true and
|
|
explicit false for this lane. Validation refuses non-booleans; generated plan
|
|
omits metadata permissions when disabled. Full owner suite passes. Sand-boxer
|
|
synthetic exec-env transport proof passed; no real secret was read.
|
|
|
|
## Activate the approved native lane and verify real owner delivery
|
|
|
|
```task
|
|
id: SECRETS-WP-0009-T03
|
|
status: wait
|
|
priority: high
|
|
state_hub_task_id: "f8069c8a-ad6b-5d0b-9a36-c2326699437d"
|
|
```
|
|
|
|
Inbound 2026-09-06 (glas-harness `GLAS-WP-0015` production dependency handoff).
|
|
Ownership acknowledged. Answering their tenant question rather than assuming the
|
|
values lined up found a blocking defect in this repo, so the handoff earned its
|
|
keep before any activation work started.
|
|
|
|
- **Our CheckRequest carried no `tenant` at all.** The deployed
|
|
`secrets-engine.catalog-lane.lifecycle` **v2** package reads
|
|
`request_tenant := object.get(input, "tenant", "")` against
|
|
`known_tenant := "tenant:platform"`, so an absent tenant is a `wrong_tenant`
|
|
denial rather than an ignored field. Every gated action this engine sent would
|
|
have been denied, and the omission also produced a `request_digest` matching no
|
|
correctly issued decision, because `tenant` is hashed material. Fixed in
|
|
`80eafaf`; `REQUEST_TENANT` is pinned against the vendored allow envelopes.
|
|
- **The package is v2, not the v1 glas reported.** v1 had no tenant rule and
|
|
failed open — a `rotate` under `tenant:coulomb` returned allow against the
|
|
deployed package (`decision:066e629bbf0c0924`). flex-auth superseded rather
|
|
than amended it, because a fail-open correction must be visible as a version
|
|
change. Our accepted version moved to v2 and a test refuses a v1 decision.
|
|
- **Wrong-tenant denial evidence delivered**, from flex-auth's real deny envelope
|
|
rather than asserted: `decision:7d56b7fc274ddfd6`, `matched_rule wrong_tenant`,
|
|
`binding.tenant tenant:coulomb`. Vendored with tests that we refuse it on
|
|
`effect` first and that a deny legally carries no `lifetime`.
|
|
- **Tenant mapping NOT resolved, deliberately.** `service_auth.TENANT` is
|
|
`tenant:coulomb`, which is exactly the value the package denies. Either those
|
|
are two layers sharing a namespace format or one constant is wrong. Choosing
|
|
without an owner ruling is the fail-open shape `GH-DEC-2026-008` rejected for
|
|
action vocabularies. Both constants stay as they are, not unified behind one
|
|
symbol. Recorded in `docs/tenant-alignment.md` for `KEY-WP-0013-T02` /
|
|
`APPROVAL-WP-0002-T01`.
|
|
- **Hazard found while probing the caller access path.** Every
|
|
`*.svc.cluster.local` name resolves on this workstation to one unrelated public
|
|
address via the `ad.binect.de` search suffix, including names of services that
|
|
do not exist. Pointing `SECRETS_ENGINE_PDP_URL` at the Service name would ship
|
|
CheckRequest metadata and the Bearer token to that host, and decision
|
|
envelopes are structurally validated but not signed, so a knowing responder
|
|
could return a well-formed allow. Reported to flex-auth for `FLEX-WP-0021-T05`;
|
|
not mitigated here because the transport control is the owners' call. The pin
|
|
stays unset, so the hazard is theoretical today.
|
|
|
|
CCR-2026-0016 is explicitly treated as custody provenance, not a per-action
|
|
authorization. No unsafe-demo switch and no human runtime token will be used to
|
|
reach a prod lane. This task stays `wait`: activation is blocked on the owner
|
|
access path, not on engine work or on approval shape.
|
|
|
|
Digest join corrected 2026-09-07. The live proof from 2026-09-06 (commit
|
|
`03c0569`) showed `validate_decision_envelope` rejecting every real allow. The
|
|
cause and the fix are both now settled, and the fix follows a published rule
|
|
rather than an inferred one.
|
|
|
|
- **Cause.** The evaluator normalizes before hashing: the request tenant is
|
|
copied onto `subject` and `resource`, and a registry hit copies `type`,
|
|
`tenant` and selected `attributes` onto the refs. `binding.request_digest` is
|
|
therefore over material we never sent and cannot reproduce, so byte-equality
|
|
against our unenriched request is unsatisfiable, not merely mismatched.
|
|
- **The rule was already published.** flex-auth's `canonical-request-digest.md`
|
|
section "Normalization" states it, and instructs consumers to compare
|
|
structured `binding` fields to the proposed action and treat `request_digest`
|
|
as the evaluator's statement of what it hashed, recomputing independently over
|
|
the tuple the binding carries. Correction to the previous note: this repo
|
|
raised it with flex-auth as an unpublished gap and asked them to choose
|
|
between three shapes. It was already in their contract, and the answer was the
|
|
first of the three. Nothing was blocked on them.
|
|
- **Implemented.** `_require_binding_corresponds` enforces that everything we
|
|
proposed survives unchanged — tenant, action, context, `subject.id`/`type`,
|
|
`resource.id`/`type`/`system`, and every attribute we sent. Enrichment may add
|
|
only `type`, `tenant`, `attributes`; any other added field is refused, and an
|
|
enriched tenant must be the request tenant so a cross-tenant binding cannot
|
|
arrive wearing our request's clothes. `request_digest` is still checked, now
|
|
against `binding_tuple(binding)` for self-consistency.
|
|
- **Proved against the artifact.** `tests/test_live_decision_enrichment.py`
|
|
validates the real `decision:0f9c98f14545c42d` under the corrected rule, and
|
|
separately asserts the unrefreshed envelope is refused on lifetime — reaching
|
|
the lifetime check at all is the evidence every binding check now passes on a
|
|
real decision. Negative tests cover a restated `resource.attributes.stage`, a
|
|
foreign `subject.tenant`, and an unexpected enrichment field.
|
|
- **Why the fixtures could not catch it.** `_request_from()` rebuilds the
|
|
request out of the binding, i.e. the already-enriched form, so every digest
|
|
assertion hashed the evaluator's output and compared it to the evaluator's
|
|
output. The defect survived the excluded-fields fix, the
|
|
`approval_binding_digest` fix and the tenant fix because all three were tested
|
|
that way. Only a real request through the owner access path exposed it.
|
|
|
|
Remaining before activation is now one external dependency, not two:
|
|
approval-engine must serve the claim endpoint so protocol step 1 can run and
|
|
`GH-DEC-2026-003` consume-before-OpenBao can be satisfied. The decision path
|
|
itself is proved end to end against the deployed pin.
|
|
|
|
Depends on SECRETS-WP-0007-T04 and SECRETS-WP-0008-T02/T06: canonical production
|
|
authorization, successful consume and scoped service authority must exist.
|
|
Current production exec refuses before OpenBao because durable access-engine
|
|
decision records are not served. Do not bypass this with unsafe-demo or a human
|
|
operator runtime token. Then apply the exact read policy/AppRole, prove positive
|
|
and negative access, register delivery-ready evidence, bind owner exec with
|
|
service authentication, and return verified pins/evidence to SAND-WP-0015 and
|
|
GLAS-WP-0012. Provider scope/budget and expiry remain explicit acceptance inputs.
|
|
Keep the catalog route inactive until real verification passes.
|
|
|
|
|
|
### 2026-09-09 consumer identity implementation and component proof
|
|
|
|
The "one external dependency" statement above concealed another local seam:
|
|
claim and consume could only read a manually supplied bearer-token file. The
|
|
OpenBao service-JWT provider cannot be reused with approval credentials because
|
|
its audience, tenant and scope are different.
|
|
|
|
Implemented the separate secrets-engine-approval provider and wired it into
|
|
both actual claim/consume call sites. It requests only read or consume for the
|
|
current request, keeps the token in memory, rejects mixed providers, and never
|
|
falls back on exchange failure. The exact service:secrets-engine / approval-engine /
|
|
tenant:platform identity and scope/time bounds are preflighted. Credential-bearing
|
|
redirects and unsupported approval endpoint shapes are refused.
|
|
|
|
The pinned real KeyCape image exposed a second defect: the shared consumer
|
|
expected assurance.aal/method but the issuer emits assurance.level/methods.
|
|
Corrected to the issuer's actual source contract, with regression coverage.
|
|
The real Approval Engine JWT verifier and SQLite store then accepted the actual
|
|
consumer's claim/check/consume chain in a disposable component exercise. Wrong
|
|
action/secret, spent claim, different digest and operator consume scope are
|
|
refused; same-digest consume retries are idempotent. Only the PDP was a test
|
|
double. The suite passed 350 tests; no production credential was read.
|
|
|
|
Contract and repeatable exercise: docs/approval-service-auth.md; receipt:
|
|
docs/evidence/2026-09-09-approval-identity-exercise.json.
|
|
|
|
T03 remains wait for RPF-WP-0035-T06's separately admitted client-side reader,
|
|
AUDIT-WP-0009-T09 audit custody, APPROVAL-WP-0002 live endpoint/claim/consume,
|
|
and the existing exact native OpenBao authority/delivery returns. The synthetic
|
|
proof does not grant these. KEY-WP-0013-T02 and verifier CCR-2026-0017/0018
|
|
are complete and must not be requested again. No new workplan duplicates T03.
|