Unblock credential broker warden-sign pilot
This commit is contained in:
parent
a95236d2e5
commit
8321e14b46
6 changed files with 87 additions and 33 deletions
2
Makefile
2
Makefile
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue