From 8321e14b4658a4ae55d7d4ebc721a1f8508f060e Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 1 Jul 2026 23:10:38 +0200 Subject: [PATCH] Unblock credential broker warden-sign pilot --- Makefile | 2 + docs/credential-broker.md | 2 + scripts/credential.py | 2 +- scripts/openbao-apply-token-grants.py | 2 +- scripts/openbao-verify-token-grants.py | 66 ++++++++++++------- ...005-credential-request-and-lease-broker.md | 46 ++++++++++--- 6 files changed, 87 insertions(+), 33 deletions(-) diff --git a/Makefile b/Makefile index 17fd440..276a8a6 100644 --- a/Makefile +++ b/Makefile @@ -35,6 +35,7 @@ OPENBAO_CREDENTIAL_CHANGE_APPLIER_ARGS ?= OPENBAO_WORKLOAD_KV_ARGS ?= CREDENTIAL_HELPER_GLOBAL_ARGS ?= CREDENTIAL_HELPER_ARGS ?= +CREDENTIAL_HELPER_CHILD_ENV ?= CREDENTIAL_HELPER_PURPOSE ?= flex-auth-openbao-smoke ##@ 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 \ --grant ops-warden/warden-sign --purpose ops-warden-production-sign-smoke \ $(CREDENTIAL_HELPER_ARGS) -- \ + $(CREDENTIAL_HELPER_CHILD_ENV) \ SMOKE_VAULT=1 /home/worsch/ops-warden/scripts/policy_gate_production_smoke.sh ##@ ArgoCD GitOps bootstrap diff --git a/docs/credential-broker.md b/docs/credential-broker.md index 21c2d96..4819bb5 100644 --- a/docs/credential-broker.md +++ b/docs/credential-broker.md @@ -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 ``` +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: ```bash diff --git a/scripts/credential.py b/scripts/credential.py index 8ae9e00..e69999e 100755 --- a/scripts/credential.py +++ b/scripts/credential.py @@ -451,7 +451,7 @@ class BaoRunner: "--", "sh", "-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", ] + args diff --git a/scripts/openbao-apply-token-grants.py b/scripts/openbao-apply-token-grants.py index d30d315..29c3cba 100755 --- a/scripts/openbao-apply-token-grants.py +++ b/scripts/openbao-apply-token-grants.py @@ -63,7 +63,7 @@ class BaoRunner: "--", "sh", "-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", ] + args diff --git a/scripts/openbao-verify-token-grants.py b/scripts/openbao-verify-token-grants.py index b0626fb..ae30b29 100755 --- a/scripts/openbao-verify-token-grants.py +++ b/scripts/openbao-verify-token-grants.py @@ -7,6 +7,7 @@ import json import shlex import subprocess import sys +import tempfile from pathlib import Path from typing import Any @@ -68,7 +69,7 @@ class BaoRunner: "--", "sh", "-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", ] + args @@ -110,7 +111,7 @@ def run_with_token( "--", "sh", "-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", ] + args @@ -126,7 +127,7 @@ def read_token( if dry_run or use_token_helper: return None if token_file: - path = Path(token_file) + path = Path(token_file).expanduser() if not path.exists(): raise SystemExit(f"ERROR: OPENBAO_TOKEN_FILE does not exist: {path}") lines = path.read_text(encoding="utf-8").splitlines() @@ -180,18 +181,21 @@ def issue_smoke_token( ) -> None: openbao = grant["openbao"] ttl = grant["ttl"]["default"] - policies = openbao["policies"] - result = runner.run( - [ - "token", - "create", - f"-role={openbao['token_role']}", - f"-policy={policies[0]}", - f"-ttl={ttl}", - "-format=json", - ], - quiet=True, - ) + args = [ + "token", + "create", + f"-role={openbao['token_role']}", + f"-ttl={ttl}", + "-format=json", + ] + for policy in openbao["policies"]: + args.append(f"-policy={policy}") + result = runner.run(args, quiet=True, check=False) + if result.returncode != 0: + raise SystemExit( + f"ERROR: token create failed (rc={result.returncode}): " + f"{(result.stderr or result.stdout or '').strip()}" + ) try: payload = json.loads(result.stdout) auth = payload.get("auth") or payload.get("data") or {} @@ -203,33 +207,49 @@ def issue_smoke_token( ) from exc 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( kubectl=kubectl, namespace=namespace, release=release, token=child_token, - args=["list", "ssh/roles"], + args=["write", "-field=signed_key", "ssh/sign/agt-role", f"public_key={public_key}"], check=False, ) - if positive.returncode != 0: + if positive.returncode != 0 or not positive.stdout.strip(): 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( kubectl=kubectl, namespace=namespace, release=release, token=child_token, - args=["secrets", "list"], + args=["policy", "read", "warden-sign"], check=False, ) if negative.returncode == 0: - raise SystemExit("ERROR: child token unexpectedly listed secret engines") - print("OK: child token cannot list secret engines") + raise SystemExit("ERROR: child token unexpectedly read policy metadata") + print("OK: child token cannot read policy metadata") 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") diff --git a/workplans/RAILIANCE-WP-0005-credential-request-and-lease-broker.md b/workplans/RAILIANCE-WP-0005-credential-request-and-lease-broker.md index e1ef36c..41b904d 100644 --- a/workplans/RAILIANCE-WP-0005-credential-request-and-lease-broker.md +++ b/workplans/RAILIANCE-WP-0005-credential-request-and-lease-broker.md @@ -4,13 +4,13 @@ type: workplan title: "Credential Request and Lease Broker" domain: financials repo: railiance-platform -status: blocked +status: active owner: codex topic_slug: railiance planning_priority: high planning_order: 5 created: "2026-06-24" -updated: "2026-06-27" +updated: "2026-07-01" depends_on_workplans: - RAIL-PL-WP-0002 state_hub_workstream_id: "2731fece-6c49-45b8-ab8a-4ea6c04ac603" @@ -152,7 +152,7 @@ Acceptance: ```task id: RAILIANCE-WP-0005-T03 -status: wait +status: done priority: high 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 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 ```task id: RAILIANCE-WP-0005-T04 -status: wait +status: done priority: high 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 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 ```task id: RAILIANCE-WP-0005-T05 -status: wait +status: progress priority: high 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 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 ```task @@ -305,7 +323,7 @@ cleared. ```task id: RAILIANCE-WP-0005-T08 -status: wait +status: done priority: high 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 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 ```task id: RAILIANCE-WP-0005-T09 -status: wait +status: progress priority: high 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 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 ```task id: RAILIANCE-WP-0005-T10 -status: wait +status: progress priority: medium 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 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 - A policy-approved actor can request or exec with a short-lived OpenBao token without seeing or pasting the raw token.