secrets-engine/src/secrets_engine/routing.py

100 lines
3.2 KiB
Python
Raw Normal View History

"""ops-warden routing contract.
ops-warden routes non-SSH credential needs here. It must NOT vend secret values.
A route result is a pointer: catalog id, readiness, decision status, and the safe
next command. This module computes that pointer for a lane. No value is read.
"""
from __future__ import annotations
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Any
from secrets_engine.catalog import CatalogEntry
from secrets_engine.decisions import Decision, resolve_decision
from secrets_engine.errors import DecisionError
from secrets_engine.openbao import OpenBaoClient
@dataclass
class RouteResult:
catalog_id: str
owner: str
stage: str
decision_status: str
decision_ref: str
review_url: str
metadata_applied: bool
value_present: bool
ready: bool
next_command: str
missing: str
def to_json(self) -> dict[str, Any]:
return asdict(self)
def route_lane(
entry: CatalogEntry,
*,
hub_url: str,
repo_root: Path,
client: OpenBaoClient | None = None,
) -> RouteResult:
"""Build the front-door routing pointer for a lane. Never reads the value."""
decision_status = "n/a (bootstrap-only)"
decision_ref = entry.approval.get("decision_ref", "")
review_url = ""
decision: Decision | None = None
if entry.approval_required():
try:
decision = resolve_decision(
hub_url=hub_url, repo_root=repo_root, decision_ref=decision_ref
)
decision_status = decision.status
review_url = decision.review_url
except DecisionError:
decision_status = "missing"
metadata_applied = False
value_present = False
if client is not None and client.is_reachable():
metadata_applied = client.read_policy(entry.policy_name) is not None
# Presence check uses the engine's own token; reports boolean only.
field = entry.fields[0] if entry.fields else ""
if field:
value_present = client.kv_field_present(entry.mount, entry.path, field)
approved = decision is None or decision.is_approved()
ready = approved and metadata_applied and value_present
if not approved:
missing = f"approved decision for '{decision_ref}'"
next_command = f"secrets-engine decision inspect {decision_ref or entry.id}"
elif not metadata_applied:
missing = "OpenBao policy/role apply"
next_command = f"secrets-engine apply {decision_ref or entry.id} --stage {entry.stage}"
elif not value_present:
missing = "provisioned secret value"
next_command = (
f"secrets-engine provision {entry.id} --stage {entry.stage} "
f"--field {entry.fields[0]} --from-file <path>"
)
else:
missing = ""
next_command = f"secrets-engine exec --catalog {entry.id} -- <command...>"
return RouteResult(
catalog_id=entry.id,
owner=entry.owner,
stage=entry.stage,
decision_status=decision_status,
decision_ref=decision_ref,
review_url=review_url,
metadata_applied=metadata_applied,
value_present=value_present,
ready=ready,
next_command=next_command,
missing=missing,
)