From f630d5135e907fa7562b77bd0b474f57b40c45be Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 28 Jun 2026 02:33:42 +0200 Subject: [PATCH] Fix OpenBao role payload handoff --- docs/whynot-design-npm-publish-handoff.md | 29 ++++++++++++++++------- scripts/credential-change.py | 24 ++++++------------- tests/test_credential_change.py | 4 +++- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/docs/whynot-design-npm-publish-handoff.md b/docs/whynot-design-npm-publish-handoff.md index 7068a1e..6cfaaf1 100644 --- a/docs/whynot-design-npm-publish-handoff.md +++ b/docs/whynot-design-npm-publish-handoff.md @@ -104,18 +104,31 @@ Role payload: } ``` -Equivalent CLI command from an approved OpenBao operator context: +Equivalent CLI command from an approved OpenBao operator shell: ```bash -bao write auth/netkingdom/role/whynot-design-workload-kv-read \ - 'bound_claims={"groups":["whynot-design"]}' \ - groups_claim=groups \ - policies=workload-kv-read-whynot-design-npm-publish \ - role_type=oidc \ - ttl=15m \ - user_claim=sub +role_payload_file="$(mktemp)" +trap 'rm -f "$role_payload_file"' EXIT +cat >"$role_payload_file" <<'JSON' +{ + "bound_claims": { + "groups": [ + "whynot-design" + ] + }, + "groups_claim": "groups", + "policies": "workload-kv-read-whynot-design-npm-publish", + "role_type": "oidc", + "ttl": "15m", + "user_claim": "sub" +} +JSON +bao write auth/netkingdom/role/whynot-design-workload-kv-read @"$role_payload_file" ``` +The OpenBao Web UI console may treat `bound_claims={...}` as a string. Use a +raw JSON/API role editor when staying in the UI, or use the shell form above. + ## Non-Secret Reads These commands should succeed from an operator-capable identity and do not print diff --git a/scripts/credential-change.py b/scripts/credential-change.py index 10b9251..5e4a231 100755 --- a/scripts/credential-change.py +++ b/scripts/credential-change.py @@ -375,26 +375,12 @@ def render_plan(ccr: dict[str, Any]) -> str: return "\n".join(lines) -def shell_kv_arg(key: str, value: Any) -> str: - if isinstance(value, (dict, list)): - encoded = json.dumps(value, sort_keys=True, separators=(",", ":")) - elif isinstance(value, bool): - encoded = "true" if value else "false" - elif value is None: - encoded = "" - else: - encoded = str(value) - return shlex.quote(f"{key}={encoded}") - - def render_operator_commands(ccr: dict[str, Any]) -> str: openbao = ccr["openbao"] auth = openbao["auth"] auth_path = f"auth/{auth['mount']}/role/{auth['role']}" payload = auth_payload(ccr) - role_args = " ".join( - shell_kv_arg(key, payload[key]) for key in sorted(payload) - ) + role_payload = json.dumps(payload, indent=2, sort_keys=True) secret_args = " ".join( shlex.quote(f"{field}=") for field in openbao["fields"] @@ -404,7 +390,12 @@ def render_operator_commands(ccr: dict[str, Any]) -> str: "# Run from the railiance-platform repo with an approved OpenBao operator token.", "set -euo pipefail", f"bao policy write {shlex.quote(openbao['policy_name'])} {shlex.quote(openbao['policy_file'])}", - f"bao write {shlex.quote(auth_path)} {role_args}", + 'role_payload_file="$(mktemp)"', + 'trap \'rm -f "$role_payload_file"\' EXIT', + 'cat >"$role_payload_file" <<\'JSON\'', + role_payload, + "JSON", + f"bao write {shlex.quote(auth_path)} @\"$role_payload_file\"", "", "# Secret provisioning remains under approved OpenBao/operator custody.", "# Do not paste secret values into Git, State Hub, workplans, logs, or chat.", @@ -414,7 +405,6 @@ def render_operator_commands(ccr: dict[str, Any]) -> str: ] return "\n".join(lines) - def validate_or_exit(path: Path) -> tuple[dict[str, Any], list[str]]: ccr, errors, warnings = validate_ccr(path) for warning in warnings: diff --git a/tests/test_credential_change.py b/tests/test_credential_change.py index 5e0554a..3120203 100644 --- a/tests/test_credential_change.py +++ b/tests/test_credential_change.py @@ -144,8 +144,10 @@ class CredentialChangeTests(unittest.TestCase): "bao write auth/netkingdom/role/whynot-design-workload-kv-read", rendered, ) + self.assertIn('role_payload_file="$(mktemp)"', rendered) + self.assertIn('"bound_claims": {', rendered) self.assertIn( - 'bound_claims={"groups":["whynot-design"]}', + 'bao write auth/netkingdom/role/whynot-design-workload-kv-read @"$role_payload_file"', rendered, ) self.assertIn(