Provision scoped T03 requester custody and native requests
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a09cbb-87c6-7900-a145-4ce53ba9f1a6
This commit is contained in:
parent
525664efa3
commit
703d552ee7
10 changed files with 650 additions and 0 deletions
|
|
@ -0,0 +1,44 @@
|
||||||
|
apiVersion: external-secrets.io/v1
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
metadata:
|
||||||
|
name: openbao-keycape-secrets-engine-requester
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/part-of: railiance-gitops
|
||||||
|
railiance-platform/component: external-secrets
|
||||||
|
app.kubernetes.io/name: keycape
|
||||||
|
spec:
|
||||||
|
provider:
|
||||||
|
vault:
|
||||||
|
server: http://openbao.openbao.svc:8200
|
||||||
|
path: platform
|
||||||
|
version: v2
|
||||||
|
auth:
|
||||||
|
kubernetes:
|
||||||
|
mountPath: kubernetes
|
||||||
|
role: external-secrets-keycape-secrets-engine-requester
|
||||||
|
serviceAccountRef:
|
||||||
|
name: external-secrets
|
||||||
|
namespace: external-secrets
|
||||||
|
conditions:
|
||||||
|
- namespaces:
|
||||||
|
- sso
|
||||||
|
---
|
||||||
|
apiVersion: external-secrets.io/v1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: keycape-secrets-engine-requester-client
|
||||||
|
namespace: sso
|
||||||
|
spec:
|
||||||
|
refreshInterval: 5m
|
||||||
|
secretStoreRef:
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
name: openbao-keycape-secrets-engine-requester
|
||||||
|
target:
|
||||||
|
name: keycape-secrets-engine-requester-client
|
||||||
|
creationPolicy: Owner
|
||||||
|
deletionPolicy: Retain
|
||||||
|
data:
|
||||||
|
- secretKey: client-secret
|
||||||
|
remoteRef:
|
||||||
|
key: workloads/secrets-engine/approval-requester
|
||||||
|
property: CLIENT_SECRET
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
id: CCR-2026-0024
|
||||||
|
kind: credential-change-request
|
||||||
|
schema_version: 1
|
||||||
|
request_type: workload-kv-read
|
||||||
|
title: T03 create-only requester KeyCape verifier custody
|
||||||
|
status: applied
|
||||||
|
created: '2026-09-14'
|
||||||
|
updated: '2026-09-14'
|
||||||
|
requester:
|
||||||
|
agent: codex
|
||||||
|
reason: User instructed completion of SECRETS-WP-0010-T03 and explicitly confirmed
|
||||||
|
its scoped human review mandate. A separate create-only requester is required;
|
||||||
|
the withdrawn combined operator client remains unused.
|
||||||
|
review:
|
||||||
|
required: true
|
||||||
|
required_approvers:
|
||||||
|
- platform-operator
|
||||||
|
- key-cape-owner
|
||||||
|
comments:
|
||||||
|
- at: '2026-09-14'
|
||||||
|
reviewer: operator instruction in Codex session
|
||||||
|
decision: approved
|
||||||
|
comment: Necessary implementation of the user-authorized T03 completion. Exact
|
||||||
|
requester has only approval:create, subject secrets-engine matching the existing
|
||||||
|
evaluator actor, audience approval-engine, tenant:platform, lifetime 15m. No
|
||||||
|
approval, consume, provider-key access or model-spending scope. Separate reader
|
||||||
|
and verifier remain exact to this new path; no old credential or registration
|
||||||
|
is widened.
|
||||||
|
target:
|
||||||
|
domain: financials
|
||||||
|
tenant: platform
|
||||||
|
workload: secrets-engine
|
||||||
|
environment: production
|
||||||
|
purpose: T03 create-only requester KeyCape verifier custody; execute only the three
|
||||||
|
fixed T03 request records.
|
||||||
|
openbao:
|
||||||
|
mount: platform
|
||||||
|
kv_path: platform/workloads/secrets-engine/approval-requester
|
||||||
|
fields:
|
||||||
|
- CLIENT_SECRET
|
||||||
|
policy_name: workload-kv-read-keycape-secrets-engine-requester
|
||||||
|
policy_file: openbao/policies/workload-kv-read-keycape-secrets-engine-requester.hcl
|
||||||
|
auth:
|
||||||
|
method: kubernetes
|
||||||
|
mount: kubernetes
|
||||||
|
role: external-secrets-keycape-secrets-engine-requester
|
||||||
|
bound_claims:
|
||||||
|
service_account_names:
|
||||||
|
- external-secrets
|
||||||
|
service_account_namespaces:
|
||||||
|
- external-secrets
|
||||||
|
bound_claims_confirmed: true
|
||||||
|
policies:
|
||||||
|
- workload-kv-read-keycape-secrets-engine-requester
|
||||||
|
ttl: 15m
|
||||||
|
access_frontdoor:
|
||||||
|
type: ops-warden
|
||||||
|
catalog_id: secrets-engine-requester-login
|
||||||
|
selector: T03 create-only approval requester
|
||||||
|
command: warden access secrets-engine-requester-login --exec -- <reviewed-requester-command>
|
||||||
|
resolvable: false
|
||||||
|
readiness: pending-review
|
||||||
|
delivery:
|
||||||
|
surface: external-secrets
|
||||||
|
target: sso/keycape-secrets-engine-requester-client via ESO; env KEYCAPE_SECRETS_ENGINE_REQUESTER_CLIENT_SECRET
|
||||||
|
risk:
|
||||||
|
classification: high
|
||||||
|
notes:
|
||||||
|
- Credential authenticates only the separate approval:create requester. Human disposition
|
||||||
|
remains mandatory and uses a different public PKCE client.
|
||||||
|
- The stable subject secrets-engine matches the existing evaluator and Approval
|
||||||
|
Engine binding.actor check; it is not an alias for consumer subject service:secrets-engine.
|
||||||
|
verification:
|
||||||
|
positive:
|
||||||
|
- Exact scope and 900-second token lifetime; native create of only the reviewed
|
||||||
|
three records.
|
||||||
|
negative:
|
||||||
|
- Approval and consume scopes refused; sibling KV paths and parent listing denied.
|
||||||
|
activation_conditions:
|
||||||
|
- Attended platform authority, CAS=0 custody, exact policy/auth readback and synchronized
|
||||||
|
verifier delivery.
|
||||||
|
- Separate reader verification and no human entry synthesized.
|
||||||
|
evidence:
|
||||||
|
- at: '2026-09-14T00:23:31+00:00'
|
||||||
|
actor: operator via attended T03 requester custody session
|
||||||
|
kind: delegated_metadata_apply
|
||||||
|
result: passed
|
||||||
|
details:
|
||||||
|
- Delegated metadata applier ran as operator via attended T03 requester custody
|
||||||
|
session using local bao CLI ambient authority.
|
||||||
|
- 'Policy metadata write: sys/policies/acl/workload-kv-read-keycape-secrets-engine-requester'
|
||||||
|
- 'Auth role metadata write: auth/kubernetes/role/external-secrets-keycape-secrets-engine-requester'
|
||||||
|
- No secret values were read, written, printed, or accepted in argv.
|
||||||
|
lifecycle:
|
||||||
|
deactivate: Disable the secrets-engine-requester KeyCape registration and detach
|
||||||
|
only the two requester reader roles. Preserve approvals and existing consumer/verifier
|
||||||
|
lanes.
|
||||||
|
rotate: Rotate through KeyCape and platform using a new version with predecessor
|
||||||
|
refusal proof.
|
||||||
|
compromised: Disable requester issuance first; revoke sessions and rotate under
|
||||||
|
attended owner authority.
|
||||||
|
state_hub:
|
||||||
|
workplan_id: RPF-WP-0035
|
||||||
|
task_id: RPF-WP-0035-T06
|
||||||
|
related_request: CCR-2026-0025
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
id: CCR-2026-0025
|
||||||
|
kind: credential-change-request
|
||||||
|
schema_version: 1
|
||||||
|
request_type: workload-kv-read
|
||||||
|
title: T03 create-only requester attended operator reader
|
||||||
|
status: applied
|
||||||
|
created: '2026-09-14'
|
||||||
|
updated: '2026-09-14'
|
||||||
|
requester:
|
||||||
|
agent: codex
|
||||||
|
reason: User instructed completion of SECRETS-WP-0010-T03 and explicitly confirmed
|
||||||
|
its scoped human review mandate. A separate create-only requester is required;
|
||||||
|
the withdrawn combined operator client remains unused.
|
||||||
|
review:
|
||||||
|
required: true
|
||||||
|
required_approvers:
|
||||||
|
- platform-operator
|
||||||
|
- secrets-engine-owner
|
||||||
|
comments:
|
||||||
|
- at: '2026-09-14'
|
||||||
|
reviewer: operator instruction in Codex session
|
||||||
|
decision: approved
|
||||||
|
comment: Necessary implementation of the user-authorized T03 completion. Exact
|
||||||
|
requester has only approval:create, subject secrets-engine matching the existing
|
||||||
|
evaluator actor, audience approval-engine, tenant:platform, lifetime 15m. No
|
||||||
|
approval, consume, provider-key access or model-spending scope. Separate reader
|
||||||
|
and verifier remain exact to this new path; no old credential or registration
|
||||||
|
is widened.
|
||||||
|
target:
|
||||||
|
domain: financials
|
||||||
|
tenant: platform
|
||||||
|
workload: secrets-engine
|
||||||
|
environment: production
|
||||||
|
purpose: T03 create-only requester attended operator reader; execute only the three
|
||||||
|
fixed T03 request records.
|
||||||
|
openbao:
|
||||||
|
mount: platform
|
||||||
|
kv_path: platform/workloads/secrets-engine/approval-requester
|
||||||
|
fields:
|
||||||
|
- CLIENT_SECRET
|
||||||
|
policy_name: workload-kv-read-secrets-engine-requester-client
|
||||||
|
policy_file: openbao/policies/workload-kv-read-secrets-engine-requester-client.hcl
|
||||||
|
auth:
|
||||||
|
method: oidc
|
||||||
|
mount: netkingdom
|
||||||
|
role: secrets-engine-requester-workload-kv-read
|
||||||
|
allowed_redirect_uris:
|
||||||
|
- https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback
|
||||||
|
- http://localhost:8250/oidc/callback
|
||||||
|
- http://127.0.0.1:8250/oidc/callback
|
||||||
|
oidc_scopes:
|
||||||
|
- openid
|
||||||
|
- profile
|
||||||
|
- email
|
||||||
|
- groups
|
||||||
|
user_claim: sub
|
||||||
|
groups_claim: groups
|
||||||
|
bound_claims:
|
||||||
|
groups:
|
||||||
|
- net-kingdom-admins
|
||||||
|
bound_claims_confirmed: true
|
||||||
|
policies:
|
||||||
|
- workload-kv-read-secrets-engine-requester-client
|
||||||
|
ttl: 15m
|
||||||
|
access_frontdoor:
|
||||||
|
type: ops-warden
|
||||||
|
catalog_id: secrets-engine-requester-login
|
||||||
|
selector: T03 create-only approval requester
|
||||||
|
command: warden access secrets-engine-requester-login --exec -- <reviewed-requester-command>
|
||||||
|
resolvable: false
|
||||||
|
readiness: pending-review
|
||||||
|
delivery:
|
||||||
|
surface: operator-workstation
|
||||||
|
target: Contained attended reader session; secret stays in memory for native requester
|
||||||
|
exchange; no retained file or raw output.
|
||||||
|
risk:
|
||||||
|
classification: high
|
||||||
|
notes:
|
||||||
|
- Credential authenticates only the separate approval:create requester. Human disposition
|
||||||
|
remains mandatory and uses a different public PKCE client.
|
||||||
|
- The stable subject secrets-engine matches the existing evaluator and Approval
|
||||||
|
Engine binding.actor check; it is not an alias for consumer subject service:secrets-engine.
|
||||||
|
verification:
|
||||||
|
positive:
|
||||||
|
- Exact scope and 900-second token lifetime; native create of only the reviewed
|
||||||
|
three records.
|
||||||
|
negative:
|
||||||
|
- Approval and consume scopes refused; sibling KV paths and parent listing denied.
|
||||||
|
activation_conditions:
|
||||||
|
- Attended platform authority, CAS=0 custody, exact policy/auth readback and synchronized
|
||||||
|
verifier delivery.
|
||||||
|
- Separate reader verification and no human entry synthesized.
|
||||||
|
evidence:
|
||||||
|
- at: '2026-09-14T00:23:32+00:00'
|
||||||
|
actor: operator via attended T03 requester custody session
|
||||||
|
kind: delegated_metadata_apply
|
||||||
|
result: passed
|
||||||
|
details:
|
||||||
|
- Delegated metadata applier ran as operator via attended T03 requester custody
|
||||||
|
session using local bao CLI ambient authority.
|
||||||
|
- 'Policy metadata write: sys/policies/acl/workload-kv-read-secrets-engine-requester-client'
|
||||||
|
- 'Auth role metadata write: auth/netkingdom/role/secrets-engine-requester-workload-kv-read'
|
||||||
|
- No secret values were read, written, printed, or accepted in argv.
|
||||||
|
lifecycle:
|
||||||
|
deactivate: Disable the secrets-engine-requester KeyCape registration and detach
|
||||||
|
only the two requester reader roles. Preserve approvals and existing consumer/verifier
|
||||||
|
lanes.
|
||||||
|
rotate: Rotate through KeyCape and platform using a new version with predecessor
|
||||||
|
refusal proof.
|
||||||
|
compromised: Disable requester issuance first; revoke sessions and rotate under
|
||||||
|
attended owner authority.
|
||||||
|
state_hub:
|
||||||
|
workplan_id: RPF-WP-0035
|
||||||
|
task_id: RPF-WP-0035-T06
|
||||||
|
related_request: CCR-2026-0024
|
||||||
145
docs/evidence/2026-09-14-t03-native-approval-requests.json
Normal file
145
docs/evidence/2026-09-14-t03-native-approval-requests.json
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
{
|
||||||
|
"observed_at": "2026-09-14T00:33:04.552946+00:00",
|
||||||
|
"status": "created",
|
||||||
|
"phase": "three_unapproved_requests_created",
|
||||||
|
"requests": [
|
||||||
|
{
|
||||||
|
"memo_id": "SECRETS-WP-0010-T03-apply",
|
||||||
|
"action": "apply",
|
||||||
|
"approval": {
|
||||||
|
"binding": {
|
||||||
|
"action": "apply",
|
||||||
|
"actor": "secrets-engine",
|
||||||
|
"digest": "sha256:03cc5b37437f14e86b686ce4054f976556334eff3fa260b03e8014ed3d9217e5",
|
||||||
|
"human_control": true,
|
||||||
|
"pdp_digest": "sha256:933427642c58c54f65dd4781b8e4c8e2f358eb96497ae35870fff4bd593d2b84",
|
||||||
|
"pdp_path": true,
|
||||||
|
"principal": "secrets-engine",
|
||||||
|
"purpose": "IR-WP-0004 read-only OpenRouter key authentication check; no inference",
|
||||||
|
"target": {
|
||||||
|
"attributes": {
|
||||||
|
"auth_targets": [
|
||||||
|
"se-prod-openrouter-llm-connect"
|
||||||
|
],
|
||||||
|
"fields": [],
|
||||||
|
"policy_targets": [
|
||||||
|
"se-prod-openrouter-llm-connect"
|
||||||
|
],
|
||||||
|
"stage": "prod"
|
||||||
|
},
|
||||||
|
"id": "catalog:openrouter-llm-connect",
|
||||||
|
"system": "secrets-engine",
|
||||||
|
"type": "secret-catalog-lane"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"created_at": "2026-09-14T00:33:07+00:00",
|
||||||
|
"entries": [],
|
||||||
|
"id": "09592588-ab15-53e7-89b8-c4e9f29aaacf",
|
||||||
|
"required_count": 1,
|
||||||
|
"status": "requested",
|
||||||
|
"superseded_by": null,
|
||||||
|
"updated_at": "2026-09-14T00:33:07+00:00",
|
||||||
|
"validity": {
|
||||||
|
"expires_at": "2026-09-15T00:33:06.559689+00:00",
|
||||||
|
"not_before": "2026-09-14T00:33:06.559689+00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"memo_id": "SECRETS-WP-0010-T03-verify",
|
||||||
|
"action": "verify",
|
||||||
|
"approval": {
|
||||||
|
"binding": {
|
||||||
|
"action": "verify",
|
||||||
|
"actor": "secrets-engine",
|
||||||
|
"digest": "sha256:72d9267d038c17107c880ffc9009793ce9c70defbddfe2219628854b811a04b2",
|
||||||
|
"human_control": true,
|
||||||
|
"pdp_digest": "sha256:9e36cdbf3890cc5a7e550fd57191f9c0f2a6b2180909aac302aa3b999e7e6b25",
|
||||||
|
"pdp_path": true,
|
||||||
|
"principal": "secrets-engine",
|
||||||
|
"purpose": "IR-WP-0004 read-only OpenRouter key authentication check; no inference",
|
||||||
|
"target": {
|
||||||
|
"attributes": {
|
||||||
|
"auth_targets": [
|
||||||
|
"se-prod-openrouter-llm-connect"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
"OPENROUTER_API_KEY"
|
||||||
|
],
|
||||||
|
"policy_targets": [
|
||||||
|
"se-prod-openrouter-llm-connect"
|
||||||
|
],
|
||||||
|
"stage": "prod"
|
||||||
|
},
|
||||||
|
"id": "catalog:openrouter-llm-connect",
|
||||||
|
"system": "secrets-engine",
|
||||||
|
"type": "secret-catalog-lane"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"created_at": "2026-09-14T00:33:07+00:00",
|
||||||
|
"entries": [],
|
||||||
|
"id": "9416fa31-fa9e-5603-8289-f35bc9625409",
|
||||||
|
"required_count": 1,
|
||||||
|
"status": "requested",
|
||||||
|
"superseded_by": null,
|
||||||
|
"updated_at": "2026-09-14T00:33:07+00:00",
|
||||||
|
"validity": {
|
||||||
|
"expires_at": "2026-09-15T00:33:06.722887+00:00",
|
||||||
|
"not_before": "2026-09-14T00:33:06.722887+00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"memo_id": "SECRETS-WP-0010-T03-exec",
|
||||||
|
"action": "exec",
|
||||||
|
"approval": {
|
||||||
|
"binding": {
|
||||||
|
"action": "exec",
|
||||||
|
"actor": "secrets-engine",
|
||||||
|
"digest": "sha256:f2cf0fb53b740900756ccde5587c3578fcff44beef2f1edab17b9a198a4033d8",
|
||||||
|
"human_control": true,
|
||||||
|
"pdp_digest": "sha256:9f17812750fc8962b0fc58fc09df136850c9935a864fafa998a1c030dfb75bd9",
|
||||||
|
"pdp_path": true,
|
||||||
|
"principal": "secrets-engine",
|
||||||
|
"purpose": "IR-WP-0004 read-only OpenRouter key authentication check; no inference",
|
||||||
|
"target": {
|
||||||
|
"attributes": {
|
||||||
|
"auth_targets": [
|
||||||
|
"se-prod-openrouter-llm-connect"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
"OPENROUTER_API_KEY"
|
||||||
|
],
|
||||||
|
"policy_targets": [
|
||||||
|
"se-prod-openrouter-llm-connect"
|
||||||
|
],
|
||||||
|
"stage": "prod"
|
||||||
|
},
|
||||||
|
"id": "catalog:openrouter-llm-connect",
|
||||||
|
"system": "secrets-engine",
|
||||||
|
"type": "secret-catalog-lane"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"created_at": "2026-09-14T00:33:07+00:00",
|
||||||
|
"entries": [],
|
||||||
|
"id": "da678b61-35be-598e-8d95-a7aefa2fdc73",
|
||||||
|
"required_count": 1,
|
||||||
|
"status": "requested",
|
||||||
|
"superseded_by": null,
|
||||||
|
"updated_at": "2026-09-14T00:33:07+00:00",
|
||||||
|
"validity": {
|
||||||
|
"expires_at": "2026-09-15T00:33:06.955168+00:00",
|
||||||
|
"not_before": "2026-09-14T00:33:06.955168+00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"credential_values_emitted": false,
|
||||||
|
"signature_verified": true,
|
||||||
|
"excess_scopes_refused": true,
|
||||||
|
"wrong_secret_refused": true,
|
||||||
|
"reader_scope_verified": true,
|
||||||
|
"human_entries_created": false,
|
||||||
|
"approvals_consumed": false,
|
||||||
|
"warden_session": "exited_0_after_self_revocation_and_helper_cleanup"
|
||||||
|
}
|
||||||
24
docs/evidence/2026-09-14-t03-requester-provision.json
Normal file
24
docs/evidence/2026-09-14-t03-requester-provision.json
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"observed_at": "2026-09-14T00:23:24.931083+00:00",
|
||||||
|
"status": "applied",
|
||||||
|
"phase": "keycape_ready",
|
||||||
|
"ccrs": [
|
||||||
|
"CCR-2026-0024",
|
||||||
|
"CCR-2026-0025"
|
||||||
|
],
|
||||||
|
"credential_values_emitted": false,
|
||||||
|
"approval_mutations": false,
|
||||||
|
"kv_version": 1,
|
||||||
|
"request_id": "2f85087b-68af-028a-a9a7-3c0d89c06c03",
|
||||||
|
"keycape": {
|
||||||
|
"deployment_uid": "99ddd83c-cb3f-4847-bcf8-35f1aa87627f",
|
||||||
|
"generation": 51,
|
||||||
|
"pod_uid": "c4b4b63c-fd5b-45eb-bfa5-59197afe1412",
|
||||||
|
"single_ready_replica": true
|
||||||
|
},
|
||||||
|
"image_unchanged": "forgejo.coulomb.social/coulomb/key-cape@sha256:db2c5a13a47839049349e881c8d19bc39f720ee69d8518f9f2eba2b1f98af9d5",
|
||||||
|
"unrelated_config_preserved": true,
|
||||||
|
"verifier_secret_exported": false,
|
||||||
|
"requester_credential_presented": false,
|
||||||
|
"warden_session": "exited_0_after_self_revocation_and_helper_cleanup"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
path "platform/data/workloads/secrets-engine/approval-requester" {
|
||||||
|
capabilities = ["read"]
|
||||||
|
}
|
||||||
|
|
||||||
|
path "platform/metadata/workloads/secrets-engine/approval-requester" {
|
||||||
|
capabilities = ["read"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
path "platform/data/workloads/secrets-engine/approval-requester" {
|
||||||
|
capabilities = ["read"]
|
||||||
|
}
|
||||||
|
|
||||||
|
path "platform/metadata/workloads/secrets-engine/approval-requester" {
|
||||||
|
capabilities = ["read"]
|
||||||
|
}
|
||||||
93
scripts/create-t03-approval-requests.py
Normal file
93
scripts/create-t03-approval-requests.py
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
"""Silent, single-attempt creation of the three operator-reviewed T03 requests."""
|
||||||
|
import base64,json,os,stat,subprocess,time
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime,timezone,timedelta
|
||||||
|
from urllib.request import Request,build_opener,ProxyHandler,HTTPRedirectHandler
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
from urllib.error import HTTPError
|
||||||
|
import jwt
|
||||||
|
ROOT=Path('/home/worsch/railiance-platform')
|
||||||
|
RECEIPT=ROOT/'docs/evidence/2026-09-14-t03-native-approval-requests.json'
|
||||||
|
POLICY='workload-kv-read-secrets-engine-requester-client'
|
||||||
|
KV='platform/data/workloads/secrets-engine/approval-requester'
|
||||||
|
ISSUER='https://kc.coulomb.social'
|
||||||
|
class NoRedirect(HTTPRedirectHandler):
|
||||||
|
def redirect_request(self,*args,**kwargs): return None
|
||||||
|
|
||||||
|
def http(url,*,body=None,headers=None):
|
||||||
|
req=Request(url,data=body,headers=headers or {})
|
||||||
|
try:
|
||||||
|
with build_opener(ProxyHandler({}),NoRedirect()).open(req,timeout=20) as response:
|
||||||
|
content=response.read(1048577);status=response.status
|
||||||
|
except HTTPError as error:
|
||||||
|
status=error.code;content=error.read(1048577);error.close()
|
||||||
|
if len(content)>1048576:raise ValueError('response_too_large')
|
||||||
|
return status,json.loads(content)
|
||||||
|
|
||||||
|
def bao(*args):
|
||||||
|
p=subprocess.run(['bao',*args],capture_output=True,text=True,timeout=20)
|
||||||
|
if p.returncode:raise ValueError('metadata_failed')
|
||||||
|
return json.loads(p.stdout)
|
||||||
|
|
||||||
|
def check_identity(d):
|
||||||
|
policies=set(d.get('policies',[]))|set(d.get('identity_policies',[]))
|
||||||
|
if POLICY not in policies or policies-{POLICY,'default'} or not d.get('entity_id') or not 0<d.get('ttl',0)<=900:
|
||||||
|
raise ValueError('reader_identity_failed')
|
||||||
|
|
||||||
|
def requests():
|
||||||
|
records=json.loads(Path('/home/worsch/flex-auth/examples/informed-decision-t03/records.json').read_text())
|
||||||
|
evaluations=json.loads(Path('/home/worsch/secrets-engine/docs/evidence/2026-09-14-t03-native-pdp-inputs.json').read_text())
|
||||||
|
if {r['action'] for r in records.values()}!={'apply','verify','exec'} or len(records)!=3:raise ValueError('exact_three_records_required')
|
||||||
|
result=[]
|
||||||
|
for memo,record in records.items():
|
||||||
|
d=evaluations[record['action']]
|
||||||
|
if d['provenance']['policy_package_digest']!='sha256:bd11c5fe77ce6439c65fea225ad6b71d2110efc5e7b5bc9b499c59cd0a53b8b4' or d['binding']['subject']['id']!='secrets-engine' or record['binding']['actor']!='secrets-engine':raise ValueError('native_evaluator_binding_drift')
|
||||||
|
digest='sha256:'+__import__('hashlib').sha256(json.dumps(record['binding'],sort_keys=True,separators=(',',':'),ensure_ascii=False).encode()).hexdigest()
|
||||||
|
if digest!=record['binding_digest']:raise ValueError('native_binding_drift')
|
||||||
|
result.append((memo,record,d['binding']['request_digest']))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def main(receipt):
|
||||||
|
prepared=requests()
|
||||||
|
if Path.home().parent.name!='.warden-attended-login' or os.getenv('BAO_TOKEN') or os.getenv('VAULT_TOKEN'):raise ValueError('attended_reader_required')
|
||||||
|
check_identity(bao('token','lookup','-format=json')['data'])
|
||||||
|
for path,expected in [(KV,['read']),('platform/data/workloads/secrets-engine/approval-client',['deny']),('platform/metadata/workloads/secrets-engine',['deny'])]:
|
||||||
|
if bao('token','capabilities','-format=json',path)!=expected:raise ValueError('reader_scope_failed')
|
||||||
|
helper=Path.home()/'.vault-token';info=helper.lstat()
|
||||||
|
if not stat.S_ISREG(info.st_mode) or stat.S_IMODE(info.st_mode)!=0o600 or info.st_uid!=os.getuid():raise ValueError('private_helper_required')
|
||||||
|
status,data=http('https://bao.coulomb.social/v1/'+KV+'?version=1',headers={'X-Vault-Token':helper.read_text().strip()})
|
||||||
|
if status!=200 or data['data']['metadata']['version']!=1:raise ValueError('requester_delivery_failed')
|
||||||
|
secret=data['data']['data']['CLIENT_SECRET'];del data
|
||||||
|
def exchange(scope,credential=secret):
|
||||||
|
auth=base64.b64encode(('secrets-engine-requester:'+credential).encode()).decode()
|
||||||
|
return http(ISSUER+'/token',body=urlencode({'grant_type':'client_credentials','scope':scope}).encode(),headers={'Authorization':'Basic '+auth,'Content-Type':'application/x-www-form-urlencoded'})
|
||||||
|
status,tokens=exchange('approval:create')
|
||||||
|
if status!=200:raise ValueError('requester_exchange_failed')
|
||||||
|
token=tokens['access_token'];status,jwks=http(ISSUER+'/jwks')
|
||||||
|
if status!=200:raise ValueError('jwks_failed')
|
||||||
|
header=jwt.get_unverified_header(token)
|
||||||
|
keys=[key for key in jwks['keys'] if key['kid']==header.get('kid')]
|
||||||
|
if header.get('alg')!='RS256' or len(keys)!=1:raise ValueError('signing_key_failed')
|
||||||
|
claims=jwt.decode(token,jwt.PyJWK.from_dict(keys[0]).key,algorithms=['RS256'],issuer=ISSUER,audience='approval-engine',options={'strict_aud':True,'require':['sub','iat','exp','iss','aud']})
|
||||||
|
expected={'sub':'secrets-engine','tenant':'tenant:platform','principal_type':'service','scope':'approval:create','roles':['secrets-engine-requester'],'groups':[]}
|
||||||
|
if any(claims.get(k)!=v for k,v in expected.items()) or claims['exp']-claims['iat']!=900:raise ValueError('requester_claims_failed')
|
||||||
|
for scope in ('approval:approve','approval:consume','approval:read'):
|
||||||
|
if exchange(scope)[0]!=400:raise ValueError('excess_scope_not_refused')
|
||||||
|
if exchange('approval:create','invalid-synthetic-credential')[0]!=401:raise ValueError('wrong_secret_not_refused')
|
||||||
|
receipt.update(phase='requester_verified',signature_verified=True,excess_scopes_refused=True,wrong_secret_refused=True,reader_scope_verified=True)
|
||||||
|
for memo,record,pdp_digest in prepared:
|
||||||
|
now=datetime.now(timezone.utc)
|
||||||
|
body={'id':record['approval_id'],'binding':record['binding'],'validity':{'not_before':now.isoformat(),'expires_at':(now+timedelta(hours=24)).isoformat()},'required_count':1,'human_control':True,'pdp_path':True,'pdp_digest':pdp_digest}
|
||||||
|
receipt['phase']='create_attempt:'+record['action'];RECEIPT.write_text(json.dumps(receipt,indent=2)+'\n')
|
||||||
|
status,result=http('http://127.0.0.1:18281/v1/approvals',body=json.dumps(body).encode(),headers={'Authorization':'Bearer '+token,'Content-Type':'application/json'})
|
||||||
|
if status!=201 or result['status']!='requested' or result['entries'] or result['binding']['digest']!=record['binding_digest'] or result['binding']['human_control'] is not True or result['binding']['pdp_digest']!=pdp_digest:raise ValueError('request_creation_requires_reconciliation')
|
||||||
|
receipt['requests'].append({'memo_id':memo.removeprefix('memo:'),'action':record['action'],'approval':result})
|
||||||
|
RECEIPT.write_text(json.dumps(receipt,indent=2)+'\n')
|
||||||
|
receipt.update(status='created',phase='three_unapproved_requests_created',human_entries_created=False,approvals_consumed=False)
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
if RECEIPT.exists():raise SystemExit(1)
|
||||||
|
receipt={'observed_at':datetime.now(timezone.utc).isoformat(),'status':'failed','phase':'preflight','requests':[],'credential_values_emitted':False}
|
||||||
|
try:main(receipt)
|
||||||
|
except Exception:raise SystemExit(1) from None
|
||||||
|
finally:RECEIPT.write_text(json.dumps(receipt,indent=2)+'\n')
|
||||||
75
scripts/provision-t03-requester.py
Normal file
75
scripts/provision-t03-requester.py
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
"""Silent attended provisioning of the separate create-only T03 requester."""
|
||||||
|
import base64,copy,hashlib,importlib.util,json,os,secrets,sys,time
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime,timezone
|
||||||
|
import yaml
|
||||||
|
ROOT=Path('/home/worsch/railiance-platform')
|
||||||
|
sys.path.insert(0,str(ROOT/'scripts'))
|
||||||
|
from state_hub_preflight_lane import command,bao,data,assert_cluster,LaneError
|
||||||
|
from keycape_approval_custody import require,read_optional,BOUNDARY
|
||||||
|
KUBE=['kubectl','--kubeconfig','/home/worsch/.kube/config-railiance01']
|
||||||
|
KV='platform/data/workloads/secrets-engine/approval-requester'
|
||||||
|
META='platform/metadata/workloads/secrets-engine/approval-requester'
|
||||||
|
CLIENT=json.loads(Path('/home/worsch/key-cape/docs/t03-requester-registration.json').read_text())
|
||||||
|
RECEIPT=ROOT/'docs/evidence/2026-09-14-t03-requester-provision.json'
|
||||||
|
def load(name,path):
|
||||||
|
spec=importlib.util.spec_from_file_location(name,path); m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m);return m
|
||||||
|
rollout=load('requester_rollout','/home/worsch/net-kingdom/sso-mfa/k8s/keycape/approval-clients-rollout.py')
|
||||||
|
rollout.IDS=('secrets-engine-requester',)
|
||||||
|
|
||||||
|
def run(receipt):
|
||||||
|
require(Path.home().parent.name=='.warden-attended-login' and not os.getenv('BAO_TOKEN') and not os.getenv('VAULT_TOKEN'),'attended_envelope_required')
|
||||||
|
assert_cluster(KUBE)
|
||||||
|
identity=data(bao(['token','lookup','-format=json']))['data']
|
||||||
|
require('platform-admin' in identity['policies'] and 'root' not in identity['policies'],'attended_operator_required')
|
||||||
|
require(CLIENT['clientId']=='secrets-engine-requester' and CLIENT['allowedScopes']==['approval:create'] and CLIENT['serviceSubject']=='secrets-engine' and CLIENT['audience']=='approval-engine' and CLIENT['tenant']=='tenant:platform','requester_contract_drift')
|
||||||
|
require(read_optional(META) is None,'existing_requester_custody_requires_reconciliation')
|
||||||
|
for ref in ('CCR-2026-0024','CCR-2026-0025'):
|
||||||
|
command(['python3',str(ROOT/'scripts/credential-change.py'),'applier-dry-run',ref,'--json'])
|
||||||
|
secret=rollout.get(KUBE,'secret','keycape-config')
|
||||||
|
deployment=rollout.get(KUBE,'deployment','keycape')
|
||||||
|
config=rollout.replacement(secret,[CLIENT])
|
||||||
|
spec=copy.deepcopy(deployment['spec'])
|
||||||
|
require(spec.get('replicas',1)==1 and spec['strategy']['type']=='Recreate','single_recreate_keycape_required')
|
||||||
|
container=next(c for c in spec['template']['spec']['containers'] if c['name']=='keycape')
|
||||||
|
image=container['image']; require('@sha256:' in image,'pinned_keycape_image_required')
|
||||||
|
env_name='KEYCAPE_SECRETS_ENGINE_REQUESTER_CLIENT_SECRET'
|
||||||
|
require(not any(e['name']==env_name for e in container.setdefault('env',[])),'existing_requester_env')
|
||||||
|
container['env'].append({'name':env_name,'valueFrom':{'secretKeyRef':{'name':'keycape-secrets-engine-requester-client','key':'client-secret'}}})
|
||||||
|
rollout.patch_object(KUBE,'secret',secret,'/data/config.yaml',config,dry=True)
|
||||||
|
rollout.patch_object(KUBE,'deployment',deployment,'/spec',spec,dry=True)
|
||||||
|
receipt['phase']='metadata_preflight_passed'
|
||||||
|
boundary=read_optional(BOUNDARY); require(boundary is not None,'agent_boundary_required')
|
||||||
|
additions=''.join('path "'+p+'" { capabilities = ["deny"] }\n' for p in (KV,META))
|
||||||
|
require(all('"'+p+'"' not in boundary['policy'] for p in (KV,META)),'boundary_path_requires_reconciliation')
|
||||||
|
bao(['write',BOUNDARY,'-'],payload={'policy':boundary['policy']+'\n'+additions})
|
||||||
|
require(read_optional(BOUNDARY)['policy']==boundary['policy']+'\n'+additions,'boundary_readback_failed')
|
||||||
|
for ref in ('CCR-2026-0024','CCR-2026-0025'):
|
||||||
|
command(['python3',str(ROOT/'scripts/credential-change.py'),'applier-apply',ref,'--actor','operator via attended T03 requester custody session','--confirm','DELEGATED APPLY '+ref,'--quiet'])
|
||||||
|
receipt['phase']='reader_roles_applied'
|
||||||
|
result=data(bao(['write','-format=json',KV,'-'],payload={'options':{'cas':0},'data':{'CLIENT_SECRET':secrets.token_urlsafe(48)}}))
|
||||||
|
require(result['data']['version']==1,'initial_version_mismatch')
|
||||||
|
receipt.update(phase='custody_seeded',kv_version=1,request_id=result.get('request_id'))
|
||||||
|
RECEIPT.write_text(json.dumps(receipt,indent=2)+'\n')
|
||||||
|
command(KUBE+['apply','-f',str(ROOT/'argocd/platform-addons/openbao-secretstore/t03-requester.yaml')])
|
||||||
|
deadline=time.monotonic()+90
|
||||||
|
while time.monotonic()<deadline:
|
||||||
|
obj=rollout.get(KUBE,'externalsecret','keycape-secrets-engine-requester-client')
|
||||||
|
if any(c.get('type')=='Ready' and c.get('status')=='True' for c in obj.get('status',{}).get('conditions',[])):
|
||||||
|
break
|
||||||
|
time.sleep(2)
|
||||||
|
else:raise LaneError('verifier_sync_failed')
|
||||||
|
receipt['phase']='verifier_synced'
|
||||||
|
# UID/resourceVersion preconditions forbid overwriting concurrent owner changes.
|
||||||
|
rollout.patch_object(KUBE,'secret',secret,'/data/config.yaml',config)
|
||||||
|
after=rollout.get(KUBE,'secret','keycape-config')
|
||||||
|
require(after['data']==dict(secret['data'],**{'config.yaml':config}),'unrelated_config_or_key_changed')
|
||||||
|
rollout.patch_object(KUBE,'deployment',deployment,'/spec',spec)
|
||||||
|
receipt['keycape']=rollout.ready(KUBE,image)
|
||||||
|
receipt.update(status='applied',phase='keycape_ready',image_unchanged=image,unrelated_config_preserved=True,verifier_secret_exported=False,requester_credential_presented=False)
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
receipt={'observed_at':datetime.now(timezone.utc).isoformat(),'status':'failed','phase':'preflight','ccrs':['CCR-2026-0024','CCR-2026-0025'],'credential_values_emitted':False,'approval_mutations':False}
|
||||||
|
try:run(receipt)
|
||||||
|
except Exception:raise SystemExit(1) from None
|
||||||
|
finally:RECEIPT.write_text(json.dumps(receipt,indent=2)+'\n')
|
||||||
|
|
@ -633,3 +633,38 @@ Remaining: an actual nonmember login refusal, real approval claim/consume,
|
||||||
separate narrow requester admission and deployed Informed Decision review with
|
separate narrow requester admission and deployed Informed Decision review with
|
||||||
an explicitly admitted human mandate. The reader group alone grants no review
|
an explicitly admitted human mandate. The reader group alone grants no review
|
||||||
mandate. T03 and CCR delivery activation remain open; no OpenRouter key was read.
|
mandate. T03 and CCR delivery activation remain open; no OpenRouter key was read.
|
||||||
|
|
||||||
|
|
||||||
|
### T03 scoped review deployment — 2026-09-14
|
||||||
|
|
||||||
|
The operator explicitly admitted `net-kingdom-admins` as the human review group
|
||||||
|
for only the T03 apply, verify and read-only key-check records, separately from
|
||||||
|
its credential-reader membership. The review service is live and ready at
|
||||||
|
https://decisions.coulomb.social with verified KeyCape groups, fresh MFA and a
|
||||||
|
dedicated caller-bound Flex Auth policy. Its mandate does not grant consumption.
|
||||||
|
|
||||||
|
The new `secrets-engine-requester` client has subject `secrets-engine`, tenant
|
||||||
|
`tenant:platform`, role `secrets-engine-requester`, and only `approval:create`.
|
||||||
|
CCR-2026-0024 and CCR-2026-0025 provide distinct verifier and attended reader
|
||||||
|
custody. Native signature, subject, scope and TTL checks passed; excess scopes
|
||||||
|
and a wrong secret were refused. Existing consumer identity is unchanged.
|
||||||
|
Three real requested approvals were created with human control, required count
|
||||||
|
one, and zero entries. Platform evidence is
|
||||||
|
`docs/evidence/2026-09-14-t03-native-approval-requests.json`.
|
||||||
|
|
||||||
|
Review image: sha256:8f55bcecf37a8d65f96e073510b1ffb4636c0a91d75e1ee7d582ad4bce8b953a.
|
||||||
|
Policy image: sha256:c9f028b49dfcede930a9cc48757ec8371ecc71d20b1bfee2733e55298dffcc7c.
|
||||||
|
Review tests: 339 passed, 39 optional integration tests skipped; 11 container
|
||||||
|
checks and HIGH/CRITICAL image scan passed. Policy checks: 57 local and 6 native
|
||||||
|
caller checks passed, using synthetic subjects, not human binding evidence.
|
||||||
|
Approval Engine CPU request was reduced from 25m to 10m after observing 1m use;
|
||||||
|
review requests 20m and its PDP 5m. Limits are unchanged. Native services ready.
|
||||||
|
|
||||||
|
Remaining T03 gate: the operator's exact signed-in account is needed to address
|
||||||
|
three prepared immutable memos, followed by real acknowledgements and acceptance
|
||||||
|
in Informed Decision. No human entry or consume has been generated by an agent.
|
||||||
|
Then execute claim -> validated PDP Check -> CAS consume separately for apply,
|
||||||
|
verify and exec using scoped attended authority, and capture native denial,
|
||||||
|
revocation, workload health and key-check evidence. No OpenRouter credential has
|
||||||
|
been read and no inference or spend was performed. T03 remains waiting; this
|
||||||
|
entry supersedes earlier statements that requester or group admission is missing.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue