NK-WP-0056-T10: one OpenBao login per lane; renew before the 15m lease ends
The executor logs in separately for the bind write (CCR-2026-0058) and the journey read (CCR-2026-0059) and refuses one role for both; every login is revoked at exit, also when setup fails. Clients log in again (re-reading the rotating projected JWT) before the lease ends, since a run's budget (20m) outlasts a 15m token. Synthetic pilot with per-lane tokens: completed and reverted, all_met. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 5502@bnt-lap001 Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
This commit is contained in:
parent
74599a583d
commit
1cdc18f75f
6 changed files with 409 additions and 25 deletions
|
|
@ -9,10 +9,10 @@ The config (JSON, non-secret) names endpoints, token files and the pilot's targe
|
|||
trust_satisfied[...] trust states the operator attests for this run (recorded)
|
||||
approval_engine{url, token_file, ca_file?}
|
||||
audit_core{url, token_file, ca_file?}
|
||||
openbao{addr, k8s_role, jwt_file, ca_file?}
|
||||
openbao{addr, k8s_role, jwt_file, ca_file?} bind write role (CCR-2026-0058); jwt_file: audience-openbao token
|
||||
lldap{url, ca_file?, set_password: "/app/lldap_set_password"}
|
||||
kubernetes{namespace, external_secret, deployment}
|
||||
bind{kv, user}, journey{kv, config{...}}
|
||||
bind{kv, user}, journey{kv, k8s_role, config{...}} journey read role (CCR-2026-0059): one login per lane
|
||||
|
||||
Exit codes: 0 completed, 3 reverted, 4 refused before any effect, 5 stopped / unknown / revert failed / budget,
|
||||
6 consumed but not in custody. The result printed is value-free.
|
||||
|
|
@ -43,15 +43,26 @@ C = _load("clients")
|
|||
EXIT = {"completed": 0, "reverted": 3, "refused": 4}
|
||||
|
||||
|
||||
def build(cfg: dict):
|
||||
bao = OPS.OpenBao(cfg["openbao"]["addr"], k8s_role=cfg["openbao"]["k8s_role"],
|
||||
jwt_file=cfg["openbao"]["jwt_file"], ca_file=cfg["openbao"].get("ca_file"))
|
||||
def build(cfg: dict, logins: list):
|
||||
"""`logins` collects every OpenBao client as soon as it exists, so the caller revokes them all, even when a
|
||||
later step of setup fails."""
|
||||
o = cfg["openbao"]
|
||||
if cfg["journey"]["k8s_role"] == o["k8s_role"]:
|
||||
raise ValueError("the journey read and the bind write must use separate roles")
|
||||
|
||||
def login(role: str):
|
||||
client = OPS.OpenBao(o["addr"], k8s_role=role, jwt_file=o["jwt_file"], ca_file=o.get("ca_file"))
|
||||
logins.append(client)
|
||||
return client
|
||||
|
||||
bao = login(o["k8s_role"])
|
||||
journey_bao = login(cfg["journey"]["k8s_role"])
|
||||
directory = OPS.Directory(cfg["lldap"]["url"], [cfg["lldap"].get("set_password", "/app/lldap_set_password")],
|
||||
ca_file=cfg["lldap"].get("ca_file"))
|
||||
kube = OPS.Kube()
|
||||
k = cfg["kubernetes"]
|
||||
operations, checks = OPS.pilot_registry(
|
||||
bao=bao, directory=directory, delivery=OPS.KubeDelivery(kube, k["namespace"], k["external_secret"]),
|
||||
bao=bao, journey_bao=journey_bao, directory=directory, delivery=OPS.KubeDelivery(kube, k["namespace"], k["external_secret"]),
|
||||
restart=OPS.KubeRestart(kube, k["namespace"], k["deployment"]), bind_kv=cfg["bind"]["kv"],
|
||||
bind_user=cfg["bind"]["user"], journey_kv=cfg["journey"]["kv"], journey_config=cfg["journey"]["config"])
|
||||
trusted = set(cfg.get("trust_satisfied", []))
|
||||
|
|
@ -68,7 +79,7 @@ def build(cfg: dict):
|
|||
config = X.Config(executor_identity=cfg["executor_identity"], runtime_principal=cfg["runtime_principal"],
|
||||
tenant=cfg["tenant"], environment=cfg["environment"],
|
||||
approver_subjects=dict(cfg["approver_subjects"]))
|
||||
return config, deps, bao
|
||||
return config, deps
|
||||
|
||||
|
||||
def exit_code(result: dict) -> int:
|
||||
|
|
@ -86,15 +97,15 @@ def main(argv=None) -> int:
|
|||
args = parser.parse_args(argv)
|
||||
cfg = json.loads(pathlib.Path(args.config).read_text())
|
||||
admission = X.bea.load(pathlib.Path(args.admission))
|
||||
bao = None
|
||||
logins: list = []
|
||||
try:
|
||||
config, deps, bao = build(cfg)
|
||||
config, deps = build(cfg, logins)
|
||||
result = X.Run(admission, args.approval_id, config, deps).execute()
|
||||
except Exception as error: # setup failed before preflight: nothing was consumed
|
||||
result = {"outcome": "refused", "reason": "setup:" + type(error).__name__, "run_id": None, "effects": False}
|
||||
finally:
|
||||
if bao is not None:
|
||||
bao.revoke_self()
|
||||
for client in logins:
|
||||
client.revoke_self()
|
||||
text = json.dumps(result, indent=2, sort_keys=True)
|
||||
if args.result:
|
||||
pathlib.Path(args.result).write_text(text + "\n")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue