Unblock credential broker warden-sign pilot

This commit is contained in:
tegwick 2026-07-01 23:10:38 +02:00
parent a95236d2e5
commit 8321e14b46
6 changed files with 87 additions and 33 deletions

View file

@ -35,6 +35,7 @@ OPENBAO_CREDENTIAL_CHANGE_APPLIER_ARGS ?=
OPENBAO_WORKLOAD_KV_ARGS ?= OPENBAO_WORKLOAD_KV_ARGS ?=
CREDENTIAL_HELPER_GLOBAL_ARGS ?= CREDENTIAL_HELPER_GLOBAL_ARGS ?=
CREDENTIAL_HELPER_ARGS ?= CREDENTIAL_HELPER_ARGS ?=
CREDENTIAL_HELPER_CHILD_ENV ?=
CREDENTIAL_HELPER_PURPOSE ?= flex-auth-openbao-smoke CREDENTIAL_HELPER_PURPOSE ?= flex-auth-openbao-smoke
##@ CloudNative PG (cnpg) — primary database operator ##@ CloudNative PG (cnpg) — primary database operator
@ -305,6 +306,7 @@ credential-exec-ops-warden-smoke: ## Run ops-warden smoke with an exec-injected
scripts/credential.py $(CREDENTIAL_HELPER_GLOBAL_ARGS) exec \ scripts/credential.py $(CREDENTIAL_HELPER_GLOBAL_ARGS) exec \
--grant ops-warden/warden-sign --purpose ops-warden-production-sign-smoke \ --grant ops-warden/warden-sign --purpose ops-warden-production-sign-smoke \
$(CREDENTIAL_HELPER_ARGS) -- \ $(CREDENTIAL_HELPER_ARGS) -- \
$(CREDENTIAL_HELPER_CHILD_ENV) \
SMOKE_VAULT=1 /home/worsch/ops-warden/scripts/policy_gate_production_smoke.sh SMOKE_VAULT=1 /home/worsch/ops-warden/scripts/policy_gate_production_smoke.sh
##@ ArgoCD GitOps bootstrap ##@ ArgoCD GitOps bootstrap

View file

@ -255,6 +255,8 @@ OPENBAO_TOKEN_FILE=~/.local/openbao/platform-admin.token make openbao-verify-tok
OPENBAO_TOKEN_FILE=~/.local/openbao/platform-admin.token make credential-exec-ops-warden-smoke OPENBAO_TOKEN_FILE=~/.local/openbao/platform-admin.token make credential-exec-ops-warden-smoke
``` ```
Use CREDENTIAL_HELPER_CHILD_ENV for child-only environment assignments needed by the smoke command, for example a Linux-only PATH that exposes ops-warden tooling. These assignments are passed after the credential helper separator and are not used for token handoff.
Emergency revocation by accessor: Emergency revocation by accessor:
```bash ```bash

View file

@ -451,7 +451,7 @@ class BaoRunner:
"--", "--",
"sh", "sh",
"-c", "-c",
'read -r BAO_TOKEN; export BAO_TOKEN; exec bao "$@"', 'read -r BAO_TOKEN; export BAO_TOKEN; export VAULT_TOKEN="$BAO_TOKEN"; exec bao "$@"',
"sh", "sh",
] ]
+ args + args

View file

@ -63,7 +63,7 @@ class BaoRunner:
"--", "--",
"sh", "sh",
"-c", "-c",
'read -r BAO_TOKEN; export BAO_TOKEN; exec bao "$@"', 'read -r BAO_TOKEN; export BAO_TOKEN; export VAULT_TOKEN="$BAO_TOKEN"; exec bao "$@"',
"sh", "sh",
] ]
+ args + args

View file

@ -7,6 +7,7 @@ import json
import shlex import shlex
import subprocess import subprocess
import sys import sys
import tempfile
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@ -68,7 +69,7 @@ class BaoRunner:
"--", "--",
"sh", "sh",
"-c", "-c",
'read -r BAO_TOKEN; export BAO_TOKEN; exec bao "$@"', 'read -r BAO_TOKEN; export BAO_TOKEN; export VAULT_TOKEN="$BAO_TOKEN"; exec bao "$@"',
"sh", "sh",
] ]
+ args + args
@ -110,7 +111,7 @@ def run_with_token(
"--", "--",
"sh", "sh",
"-c", "-c",
'read -r BAO_TOKEN; export BAO_TOKEN; exec bao "$@"', 'read -r BAO_TOKEN; export BAO_TOKEN; export VAULT_TOKEN="$BAO_TOKEN"; exec bao "$@"',
"sh", "sh",
] ]
+ args + args
@ -126,7 +127,7 @@ def read_token(
if dry_run or use_token_helper: if dry_run or use_token_helper:
return None return None
if token_file: if token_file:
path = Path(token_file) path = Path(token_file).expanduser()
if not path.exists(): if not path.exists():
raise SystemExit(f"ERROR: OPENBAO_TOKEN_FILE does not exist: {path}") raise SystemExit(f"ERROR: OPENBAO_TOKEN_FILE does not exist: {path}")
lines = path.read_text(encoding="utf-8").splitlines() lines = path.read_text(encoding="utf-8").splitlines()
@ -180,18 +181,21 @@ def issue_smoke_token(
) -> None: ) -> None:
openbao = grant["openbao"] openbao = grant["openbao"]
ttl = grant["ttl"]["default"] ttl = grant["ttl"]["default"]
policies = openbao["policies"] args = [
result = runner.run( "token",
[ "create",
"token", f"-role={openbao['token_role']}",
"create", f"-ttl={ttl}",
f"-role={openbao['token_role']}", "-format=json",
f"-policy={policies[0]}", ]
f"-ttl={ttl}", for policy in openbao["policies"]:
"-format=json", args.append(f"-policy={policy}")
], result = runner.run(args, quiet=True, check=False)
quiet=True, if result.returncode != 0:
) raise SystemExit(
f"ERROR: token create failed (rc={result.returncode}): "
f"{(result.stderr or result.stdout or '').strip()}"
)
try: try:
payload = json.loads(result.stdout) payload = json.loads(result.stdout)
auth = payload.get("auth") or payload.get("data") or {} auth = payload.get("auth") or payload.get("data") or {}
@ -203,33 +207,49 @@ def issue_smoke_token(
) from exc ) from exc
try: try:
with tempfile.TemporaryDirectory() as tmpdir:
key_path = Path(tmpdir) / "warden-sign-smoke_ed25519"
keygen = subprocess.run(
["ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-f", str(key_path)],
capture_output=True,
text=True,
check=False,
)
if keygen.returncode != 0:
raise SystemExit(
"ERROR: could not generate smoke SSH key: "
f"{(keygen.stderr or keygen.stdout).strip()}"
)
public_key = key_path.with_suffix(key_path.suffix + ".pub").read_text(encoding="utf-8").strip()
positive = run_with_token( positive = run_with_token(
kubectl=kubectl, kubectl=kubectl,
namespace=namespace, namespace=namespace,
release=release, release=release,
token=child_token, token=child_token,
args=["list", "ssh/roles"], args=["write", "-field=signed_key", "ssh/sign/agt-role", f"public_key={public_key}"],
check=False, check=False,
) )
if positive.returncode != 0: if positive.returncode != 0 or not positive.stdout.strip():
raise SystemExit( raise SystemExit(
"ERROR: child token could not list ssh/roles with warden-sign policy" "ERROR: child token could not sign with ssh/sign/agt-role: "
f"{(positive.stderr or positive.stdout).strip()}"
) )
print("OK: child token can list ssh/roles") print("OK: child token can sign with ssh/sign/agt-role")
negative = run_with_token( negative = run_with_token(
kubectl=kubectl, kubectl=kubectl,
namespace=namespace, namespace=namespace,
release=release, release=release,
token=child_token, token=child_token,
args=["secrets", "list"], args=["policy", "read", "warden-sign"],
check=False, check=False,
) )
if negative.returncode == 0: if negative.returncode == 0:
raise SystemExit("ERROR: child token unexpectedly listed secret engines") raise SystemExit("ERROR: child token unexpectedly read policy metadata")
print("OK: child token cannot list secret engines") print("OK: child token cannot read policy metadata")
finally: finally:
runner.run(["token", "revoke-accessor", accessor], quiet=True) runner.run(["write", "auth/token/revoke-accessor", f"accessor={accessor}"], quiet=True)
print("OK: smoke child token revoked by accessor") print("OK: smoke child token revoked by accessor")

View file

@ -4,13 +4,13 @@ type: workplan
title: "Credential Request and Lease Broker" title: "Credential Request and Lease Broker"
domain: financials domain: financials
repo: railiance-platform repo: railiance-platform
status: blocked status: active
owner: codex owner: codex
topic_slug: railiance topic_slug: railiance
planning_priority: high planning_priority: high
planning_order: 5 planning_order: 5
created: "2026-06-24" created: "2026-06-24"
updated: "2026-06-27" updated: "2026-07-01"
depends_on_workplans: depends_on_workplans:
- RAIL-PL-WP-0002 - RAIL-PL-WP-0002
state_hub_workstream_id: "2731fece-6c49-45b8-ab8a-4ea6c04ac603" state_hub_workstream_id: "2731fece-6c49-45b8-ab8a-4ea6c04ac603"
@ -152,7 +152,7 @@ Acceptance:
```task ```task
id: RAILIANCE-WP-0005-T03 id: RAILIANCE-WP-0005-T03
status: wait status: done
priority: high priority: high
state_hub_task_id: "d8498e3b-b2fb-47b7-ab88-cd6592c1807e" state_hub_task_id: "d8498e3b-b2fb-47b7-ab88-cd6592c1807e"
``` ```
@ -184,11 +184,18 @@ OpenBao was reachable and unsealed, but the pod token helper received
until an approved OpenBao issuer/platform-admin path applies the policy and until an approved OpenBao issuer/platform-admin path applies the policy and
role, or the pod token helper is granted that narrow capability. role, or the pod token helper is granted that narrow capability.
**2026-07-01:** Operator unsealed OpenBao. Live apply succeeded with
`OPENBAO_TOKEN_FILE=~/.local/openbao/platform-admin.token make openbao-configure-token-grants`:
`credential-broker-warden-sign-issuer` policy and `warden-sign` token role are
configured. T03 is `done`.
**2026-07-01 follow-up:** Live smoke succeeded with openbao-verify-token-grants-smoke: a child token minted from role warden-sign signed a throwaway SSH public key through ssh/sign/agt-role, was denied policy metadata read, and was revoked by accessor.
## T04 - Build credential helper MVP ## T04 - Build credential helper MVP
```task ```task
id: RAILIANCE-WP-0005-T04 id: RAILIANCE-WP-0005-T04
status: wait status: done
priority: high priority: high
state_hub_task_id: "0c543cb3-36cb-4b25-9a58-de8efc1216c9" state_hub_task_id: "0c543cb3-36cb-4b25-9a58-de8efc1216c9"
``` ```
@ -220,11 +227,18 @@ surface so global helper flags such as `--use-token-helper` are passed before
the subcommand. T04 is now `wait` on the same OpenBao live gate as T03 before the subcommand. T04 is now `wait` on the same OpenBao live gate as T03 before
ops-warden smoke can be proven end to end. ops-warden smoke can be proven end to end.
**2026-07-01:** `make credential-exec-ops-warden-smoke` passed end to end:
`credential exec --grant ops-warden/warden-sign` minted a bounded child token,
injected `VAULT_TOKEN` only into the ops-warden production policy-gate smoke,
and completed without manual token paste. T04 is `done`.
**2026-07-01 follow-up:** The Make smoke target passed with CREDENTIAL_HELPER_CHILD_ENV providing a child-only PATH for the temporary uv shim. credential exec minted a bounded child token, injected VAULT_TOKEN only into the ops-warden production policy-gate smoke, and completed without manual token paste. The smoke recorded policy decision decision:032b096c433ad80c for both the local allow path and the vault-backed allow path.
## T05 - Implement secure delivery modes ## T05 - Implement secure delivery modes
```task ```task
id: RAILIANCE-WP-0005-T05 id: RAILIANCE-WP-0005-T05
status: wait status: progress
priority: high priority: high
state_hub_task_id: "66f3cd6d-7520-4584-90b8-672866ef3490" state_hub_task_id: "66f3cd6d-7520-4584-90b8-672866ef3490"
``` ```
@ -252,6 +266,10 @@ account auth metadata for Kubernetes-auth. T05 is `wait` until live response-wra
single-use behavior and the OpenBao-backed exec path are verified with an single-use behavior and the OpenBao-backed exec path are verified with an
approved issuer token. approved issuer token.
**2026-07-01:** `exec-env` is live-verified via `credential-exec-ops-warden-smoke`.
`response-wrap`, `local-token-file`, and `kubernetes-auth` still need live
evidence. T05 is `progress`.
## T06 - Integrate KeyCape identity and agent subject binding ## T06 - Integrate KeyCape identity and agent subject binding
```task ```task
@ -305,7 +323,7 @@ cleared.
```task ```task
id: RAILIANCE-WP-0005-T08 id: RAILIANCE-WP-0005-T08
status: wait status: done
priority: high priority: high
state_hub_task_id: "4571d4c9-d4de-4ee9-97e0-ff03e49e65ec" state_hub_task_id: "4571d4c9-d4de-4ee9-97e0-ff03e49e65ec"
``` ```
@ -327,11 +345,19 @@ needs belong to `railiance-platform`; ops-warden executes SSH cert issuance
only. T08 is `wait` because this workspace cannot update the external only. T08 is `wait` because this workspace cannot update the external
ops-warden routing catalog and the live OpenBao grant apply is still denied. ops-warden routing catalog and the live OpenBao grant apply is still denied.
**2026-07-01:** ops-warden T08 closed: added catalog id
`ops-warden-warden-sign-token`, playbook
`wiki/playbooks/ops-warden-warden-sign-token.md`, and updated
`operator-openbao-token-hygiene.md`, `PolicyGatedSigning.md`, and
`CredentialRouting.md`. `warden route find "VAULT_TOKEN ops-warden warden sign"`
now ranks the broker lane first. Live smoke already proven via
`make credential-exec-ops-warden-smoke`. T08 is `done`.
## T09 - Verification, audit, and red-team checks ## T09 - Verification, audit, and red-team checks
```task ```task
id: RAILIANCE-WP-0005-T09 id: RAILIANCE-WP-0005-T09
status: wait status: progress
priority: high priority: high
state_hub_task_id: "78d1db83-12fb-4ac2-95eb-54c91ac125b5" state_hub_task_id: "78d1db83-12fb-4ac2-95eb-54c91ac125b5"
``` ```
@ -353,11 +379,13 @@ coverage for local lease files. Offline validation is passing. T09 is `wait`
until live OpenBao audit evidence, response-wrap unwrap-once evidence, and until live OpenBao audit evidence, response-wrap unwrap-once evidence, and
negative live mint checks can be collected. negative live mint checks can be collected.
**2026-07-01:** Live verification moved forward. make credential-tests passed 50 tests. make openbao-verify-token-grants-smoke minted a child token with policy warden-sign, proved it can sign via ssh/sign/agt-role, proved it cannot read policy metadata, and revoked it by accessor. make credential-exec-ops-warden-smoke passed with the child-only PATH hook, proving the flex-auth allow/deny smoke and vault-backed ops-warden signing path without manual VAULT_TOKEN paste. T09 is progress; remaining evidence is OpenBao audit-log reference collection plus response-wrap unwrap-once verification.
## T10 - Rollout and migration ## T10 - Rollout and migration
```task ```task
id: RAILIANCE-WP-0005-T10 id: RAILIANCE-WP-0005-T10
status: wait status: progress
priority: medium priority: medium
state_hub_task_id: "44ce4082-fa8f-44d0-8f86-172d14ecfb0e" state_hub_task_id: "44ce4082-fa8f-44d0-8f86-172d14ecfb0e"
``` ```
@ -382,6 +410,8 @@ identity binding, flex-auth preflight, State Hub metadata, and routing ownership
in `docs/credential-broker.md`. T10 is `wait` on the live warden-sign pilot and in `docs/credential-broker.md`. T10 is `wait` on the live warden-sign pilot and
external routing-doc/catalog updates. external routing-doc/catalog updates.
**2026-07-01:** Phase 1 rollout is live: the warden-sign VAULT_TOKEN pilot passed through credential exec, and ops-warden routing now ranks the broker lane first for the warden-sign token need. T10 is progress; platform-readonly diagnostics, additional workload grants, and final cross-repo doc consistency remain follow-up rollout phases.
## Exit Criteria ## Exit Criteria
- A policy-approved actor can request or exec with a short-lived OpenBao token without seeing or pasting the raw token. - A policy-approved actor can request or exec with a short-lived OpenBao token without seeing or pasting the raw token.