NK-WP-0056-T13 (part): executor CLI, clients, Containerfile, image workflow
All checks were successful
All checks were successful
CLI entry point and approval-engine/audit-core clients; events carry occurred_at (audit-core envelope); hash-locked image with pinned lldap stage builds locally; image workflow and executor tests in CI; pilot example pins the executed files. rapp reshaping still open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 994874@bnt-lap001 Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
This commit is contained in:
parent
fe18096aa3
commit
ba9d266b7e
18 changed files with 694 additions and 33 deletions
106
bounded-executor/cli.py
Normal file
106
bounded-executor/cli.py
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Run one admission (NK-WP-0056-T13): the entry point of the rapp-bounded-executor Job.
|
||||
|
||||
python3 bounded-executor/cli.py --admission FILE --approval-id ID --config FILE [--result FILE]
|
||||
|
||||
The config (JSON, non-secret) names endpoints, token files and the pilot's targets:
|
||||
|
||||
executor_identity, runtime_principal, tenant, environment, approver_subjects{name: subject_id},
|
||||
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?}
|
||||
lldap{url, ca_file?, set_password: "/app/lldap_set_password"}
|
||||
kubernetes{namespace, external_secret, deployment}
|
||||
bind{kv, user}, journey{kv, config{...}}
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
import json
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
HERE = pathlib.Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def _load(name):
|
||||
spec = importlib.util.spec_from_file_location(name, HERE / f"{name}.py")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
X = _load("executor")
|
||||
OPS = _load("operations")
|
||||
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"))
|
||||
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"]),
|
||||
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", []))
|
||||
|
||||
def alert(kind: str, info: dict) -> None:
|
||||
print(json.dumps({"alert": kind, **info}), file=sys.stderr, flush=True)
|
||||
|
||||
deps = X.Deps(carrier=C.ApprovalEngine(cfg["approval_engine"]["url"], cfg["approval_engine"]["token_file"],
|
||||
cfg["approval_engine"].get("ca_file")),
|
||||
operations=operations, checks=checks, trust=lambda state: state in trusted,
|
||||
audit=C.AuditCore(cfg["audit_core"]["url"], cfg["audit_core"]["token_file"],
|
||||
cfg["audit_core"].get("ca_file")),
|
||||
alert=alert)
|
||||
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
|
||||
|
||||
|
||||
def exit_code(result: dict) -> int:
|
||||
if result.get("reason") in ("receipt_not_in_custody", "audit_unavailable") and result["outcome"] != "refused":
|
||||
return 6
|
||||
return EXIT.get(result["outcome"], 5)
|
||||
|
||||
|
||||
def main(argv=None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__.split("\n", 1)[0])
|
||||
parser.add_argument("--admission", required=True)
|
||||
parser.add_argument("--approval-id", required=True)
|
||||
parser.add_argument("--config", required=True)
|
||||
parser.add_argument("--result", help="also write the result here")
|
||||
args = parser.parse_args(argv)
|
||||
cfg = json.loads(pathlib.Path(args.config).read_text())
|
||||
admission = X.bea.load(pathlib.Path(args.admission))
|
||||
bao = None
|
||||
try:
|
||||
config, deps, bao = build(cfg)
|
||||
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()
|
||||
text = json.dumps(result, indent=2, sort_keys=True)
|
||||
if args.result:
|
||||
pathlib.Path(args.result).write_text(text + "\n")
|
||||
print(text)
|
||||
return exit_code(result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue