feat: admit existing OpenBao catalog lanes
This commit is contained in:
parent
9d383442c8
commit
784be978bf
29 changed files with 1490 additions and 79 deletions
|
|
@ -63,6 +63,19 @@ def _writer(cfg: Config) -> EvidenceWriter:
|
|||
return EvidenceWriter(evidence_dir=cfg.evidence_dir, hub_url=cfg.hub_url, topic_id=cfg.topic_id)
|
||||
|
||||
|
||||
def _require_lane_approval(cfg: Config, entry):
|
||||
"""Resolve and enforce the lane approval for a privileged live action."""
|
||||
if not entry.approval_required():
|
||||
return None
|
||||
decision = resolve_decision(
|
||||
hub_url=cfg.hub_url,
|
||||
repo_root=repo_root(),
|
||||
decision_ref=entry.approval.get("decision_ref", entry.id),
|
||||
)
|
||||
require_approved(entry, decision)
|
||||
return decision
|
||||
|
||||
|
||||
# -- command handlers ------------------------------------------------------
|
||||
|
||||
|
||||
|
|
@ -89,13 +102,23 @@ def cmd_catalog_show(cfg: Config, args) -> int:
|
|||
print(f"openbao: mount={e.mount} allowed={sorted(e.auth_allowed_paths)}")
|
||||
print(f"approle: {e.role_name} policy={e.policy_name}")
|
||||
else:
|
||||
print(f"openbao: {e.mount}/{e.path} fields={e.fields}")
|
||||
print(
|
||||
f"openbao: {e.mount}/{e.path} fields={e.fields} "
|
||||
f"mount_management={e.mount_management}"
|
||||
)
|
||||
print(
|
||||
f"delivery auth: {e.delivery_auth_method}/"
|
||||
f"{e.delivery_auth_management} role={e.role_name if e.has_delivery_auth else '-'}"
|
||||
)
|
||||
print(f"workload: {e.workload_delivery}")
|
||||
print(f"consumers: {[c['name'] for c in e.consumers]}")
|
||||
print(f"delivery: {e.delivery_modes}")
|
||||
print(f"approval: {e.approval.get('model')} ref={e.approval.get('decision_ref','')}")
|
||||
print(f"verification: {e.verification}")
|
||||
print(f"rotation: {e.rotation}")
|
||||
print(f"deactivation: {e.deactivation}")
|
||||
if e.risk:
|
||||
print(f"risk: {e.risk}")
|
||||
print(f"description: {e.description.strip()}")
|
||||
return 0
|
||||
|
||||
|
|
@ -172,6 +195,7 @@ def cmd_provision(cfg: Config, args) -> int:
|
|||
if args.stage != entry.stage:
|
||||
from secrets_engine.errors import ProvisioningError
|
||||
raise ProvisioningError(f"lane '{entry.id}' is stage '{entry.stage}', not '{args.stage}'")
|
||||
decision = _require_lane_approval(cfg, entry)
|
||||
client = OpenBaoClient.resolve(cfg.bao_addr, bootstrap_token_file=args.bootstrap_token_file)
|
||||
field = args.field or (entry.fields[0] if entry.fields else "")
|
||||
if args.generate:
|
||||
|
|
@ -182,12 +206,13 @@ def cmd_provision(cfg: Config, args) -> int:
|
|||
mode = "from-file"
|
||||
print(f"provisioned lane '{entry.id}' field '{f}' ({mode}) — value not displayed")
|
||||
_writer(cfg).record("provision", result=mode, catalog_id=entry.id, stage=entry.stage,
|
||||
detail={"field": f})
|
||||
decision_id=decision.id if decision else "", detail={"field": f})
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_verify(cfg: Config, args) -> int:
|
||||
entry = get_entry(cfg.catalog_dir, args.catalog_id)
|
||||
decision = _require_lane_approval(cfg, entry)
|
||||
client = OpenBaoClient.resolve(cfg.bao_addr, bootstrap_token_file=args.bootstrap_token_file)
|
||||
field = args.field or (entry.fields[0] if entry.fields else "")
|
||||
if entry.stores_kv_value() and not field:
|
||||
|
|
@ -202,7 +227,8 @@ def cmd_verify(cfg: Config, args) -> int:
|
|||
if not r.passed:
|
||||
rc = 7
|
||||
_writer(cfg).record("verify", result=f"{r.check}:{'pass' if r.passed else 'fail'}",
|
||||
catalog_id=entry.id, stage=entry.stage, detail=r.detail)
|
||||
catalog_id=entry.id, stage=entry.stage,
|
||||
decision_id=decision.id if decision else "", detail=r.detail)
|
||||
return rc
|
||||
|
||||
|
||||
|
|
@ -215,13 +241,7 @@ def cmd_handoff(cfg: Config, args) -> int:
|
|||
raise ProvisioningError(f"lane '{entry.id}' is stage '{entry.stage}', not '{args.stage}'")
|
||||
if entry.kind != "auth-capability":
|
||||
raise ProvisioningError(f"lane '{entry.id}' is {entry.kind}; handoff needs auth-capability")
|
||||
decision = None
|
||||
if entry.approval_required():
|
||||
decision = resolve_decision(
|
||||
hub_url=cfg.hub_url, repo_root=repo_root(),
|
||||
decision_ref=entry.approval.get("decision_ref", entry.id),
|
||||
)
|
||||
require_approved(entry, decision)
|
||||
decision = _require_lane_approval(cfg, entry)
|
||||
client = OpenBaoClient.resolve(cfg.bao_addr, bootstrap_token_file=args.bootstrap_token_file)
|
||||
result = write_approle_handoff(
|
||||
client,
|
||||
|
|
@ -257,12 +277,7 @@ def cmd_exec(cfg: Config, args) -> int:
|
|||
from secrets_engine.exec_delivery import exec_with_secret
|
||||
entry = get_entry(cfg.catalog_dir, args.catalog)
|
||||
# require approval + readiness before running.
|
||||
if entry.approval_required():
|
||||
decision = resolve_decision(
|
||||
hub_url=cfg.hub_url, repo_root=repo_root(),
|
||||
decision_ref=entry.approval.get("decision_ref", entry.id),
|
||||
)
|
||||
require_approved(entry, decision)
|
||||
decision = _require_lane_approval(cfg, entry)
|
||||
if not args.command:
|
||||
from secrets_engine.errors import DeliveryError
|
||||
raise DeliveryError("no command after '--'")
|
||||
|
|
@ -270,9 +285,11 @@ def cmd_exec(cfg: Config, args) -> int:
|
|||
field = args.field or (entry.fields[0] if entry.fields else "")
|
||||
w = _writer(cfg)
|
||||
w.record("exec", result="attempt", catalog_id=entry.id, stage=entry.stage,
|
||||
decision_id=decision.id if decision else "",
|
||||
detail={"command": args.command[0], "mode": args.mode})
|
||||
rc = exec_with_secret(client, entry, field, args.command, mode=args.mode)
|
||||
w.record("exec", result=f"exit-{rc}", catalog_id=entry.id, stage=entry.stage,
|
||||
decision_id=decision.id if decision else "",
|
||||
detail={"command": args.command[0]})
|
||||
return rc
|
||||
|
||||
|
|
@ -322,6 +339,7 @@ def cmd_route(cfg: Config, args) -> int:
|
|||
|
||||
def cmd_revoke(cfg: Config, args) -> int:
|
||||
entry = get_entry(cfg.catalog_dir, args.catalog_id)
|
||||
decision = None if args.dry_run else _require_lane_approval(cfg, entry)
|
||||
client = OpenBaoClient.resolve(cfg.bao_addr, bootstrap_token_file=args.bootstrap_token_file)
|
||||
if entry.kind == "auth-capability":
|
||||
if args.dry_run:
|
||||
|
|
@ -336,7 +354,10 @@ def cmd_revoke(cfg: Config, args) -> int:
|
|||
f"revoked lane '{entry.id}': deleted approle {entry.role_name} "
|
||||
f"and policy {entry.policy_name}"
|
||||
)
|
||||
_writer(cfg).record("revoke", result="auth-capability-deactivated", catalog_id=entry.id, stage=entry.stage)
|
||||
_writer(cfg).record(
|
||||
"revoke", result="auth-capability-deactivated", catalog_id=entry.id,
|
||||
stage=entry.stage, decision_id=decision.id if decision else ""
|
||||
)
|
||||
return 0
|
||||
if args.dry_run:
|
||||
print(f"(dry-run) would delete KV metadata {entry.mount}/{entry.path} "
|
||||
|
|
@ -344,7 +365,10 @@ def cmd_revoke(cfg: Config, args) -> int:
|
|||
return 0
|
||||
client.kv_delete_metadata(entry.mount, entry.path)
|
||||
print(f"revoked lane '{entry.id}': KV metadata deleted at {entry.mount}/{entry.path}")
|
||||
_writer(cfg).record("revoke", result="deactivated", catalog_id=entry.id, stage=entry.stage)
|
||||
_writer(cfg).record(
|
||||
"revoke", result="deactivated", catalog_id=entry.id, stage=entry.stage,
|
||||
decision_id=decision.id if decision else ""
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue