Add versioned ephemeral custody lifecycle
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02669-87ee-7a31-b111-edc95a16e0fa
This commit is contained in:
codex 2026-08-22 21:56:42 +02:00
parent 985cef2572
commit 30e6edc236
17 changed files with 2855 additions and 6 deletions

948
scripts/custody-projection.py Executable file
View file

@ -0,0 +1,948 @@
#!/usr/bin/env python3
"""Data-driven, attended ephemeral credential custody lifecycle.
The command never prints or persists bearer values. Live projection requires a
current Whitehat broker-readiness receipt in State Hub and an exact attended
confirmation. Cleanup is scoped only by the validated projection contract and
receipt.
"""
from __future__ import annotations
import argparse
import json
import os
import secrets
import stat
import subprocess
import sys
import tempfile
import time
from datetime import UTC, datetime
from pathlib import Path
from typing import Any, Callable
SCRIPT_DIR = Path(__file__).resolve().parent
if str(SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPT_DIR))
from custody_contract import ( # noqa: E402
CLEANUP_INTERFACE,
PROJECTION_INTERFACE,
WORKPLAN_ID,
ContractError,
canonical_json,
contract_digest,
current_broker_receipt,
digest,
load_json,
parse_time,
resource_names,
validate_cleanup_receipt,
validate_projection_contract,
validate_projection_receipt,
)
from remote_exec import RemoteExecutionError, run_remote # noqa: E402
DEFAULT_API_BASE = os.environ.get("STATE_HUB_URL", "http://127.0.0.1:8000")
class ProcedureError(RuntimeError):
pass
def utc_now() -> datetime:
return datetime.now(UTC)
def rfc3339(value: datetime) -> str:
return value.astimezone(UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z")
def safe_run(
command: list[str],
*,
label: str,
env: dict[str, str] | None = None,
input_text: str | None = None,
allow_missing: bool = False,
runner: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run,
) -> subprocess.CompletedProcess[str]:
completed = runner(
command,
text=True,
input=input_text,
capture_output=True,
env=env,
check=False,
)
if completed.returncode and not allow_missing:
raise ProcedureError(f"{label} failed (exit {completed.returncode})")
return completed
def secure_unlink(path: Path) -> None:
if not path.exists():
return
size = path.stat().st_size
with path.open("r+b", buffering=0) as handle:
handle.write(b"\0" * size)
handle.flush()
os.fsync(handle.fileno())
path.unlink()
def write_receipt(path: Path, value: dict[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
descriptor, temporary_name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
temporary = Path(temporary_name)
try:
os.fchmod(descriptor, 0o600)
with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
handle.write(json.dumps(value, indent=2, sort_keys=True) + "\n")
handle.flush()
os.fsync(handle.fileno())
os.replace(temporary, path)
os.chmod(path, 0o600)
finally:
if temporary.exists():
temporary.unlink()
class Operator:
def __init__(self, contract: dict[str, Any], token_file: Path) -> None:
mode = stat.S_IMODE(token_file.stat().st_mode) if token_file.exists() else None
if mode != 0o600:
raise ProcedureError(f"OpenBao token file must exist with mode 0600: {token_file}")
token = token_file.read_text(encoding="utf-8").splitlines()[0].strip()
if not token:
raise ProcedureError("OpenBao token file is empty")
self.contract = contract
self.remote = contract["authority"]["remote"]
self.bao_env = dict(
os.environ,
BAO_ADDR=os.environ.get("BAO_ADDR", "https://bao.coulomb.social"),
BAO_TOKEN=token,
)
def bao(
self,
args: list[str],
*,
label: str,
input_text: str | None = None,
allow_missing: bool = False,
) -> subprocess.CompletedProcess[str]:
return safe_run(
["bao", *args],
label=label,
env=self.bao_env,
input_text=input_text,
allow_missing=allow_missing,
)
def kubectl(
self,
args: list[str],
*,
label: str,
input_text: str | None = None,
allow_missing: bool = False,
) -> subprocess.CompletedProcess[str]:
try:
return run_remote(
self.remote,
["kubectl", *args],
label=label,
input_text=input_text,
allow_missing=allow_missing,
)
except RemoteExecutionError as exc:
raise ProcedureError(str(exc)) from exc
def bao_exists(self, args: list[str], *, label: str) -> bool:
result = self.bao(args, label=label, allow_missing=True)
if result.returncode == 0:
return True
error = result.stderr.lower()
if "404" in error or "no value found" in error or "no policy named" in error:
return False
raise ProcedureError(f"{label} failed without a confirmed not-found response")
def kubectl_exists(self, args: list[str], *, label: str) -> bool:
result = self.kubectl(args, label=label, allow_missing=True)
return result.returncode == 0 and bool(result.stdout.strip())
def registry(self) -> list[dict[str, Any]]:
authority = self.contract["authority"]
result = self.bao(
["kv", "get", f"-field={authority['registry_field']}", authority["registry_path"]],
label="read sender registry",
)
try:
value = json.loads(result.stdout)
except json.JSONDecodeError as exc:
raise ProcedureError("sender registry is not valid JSON") from exc
if not isinstance(value, list):
raise ProcedureError("sender registry must be a list")
return value
def write_registry(self, registry: list[dict[str, Any]]) -> None:
authority = self.contract["authority"]
with tempfile.TemporaryDirectory(prefix="railiance-custody-registry-") as directory:
root = Path(directory)
os.chmod(root, 0o700)
document = root / "registry.json"
document.write_text(canonical_json(registry), encoding="utf-8")
os.chmod(document, 0o600)
try:
self.bao(
[
"kv",
"patch",
authority["registry_path"],
f"{authority['registry_field']}=@{document}",
],
label="write sender registry",
)
finally:
secure_unlink(document)
def identity_names(contract: dict[str, Any]) -> set[str]:
return {item["sender_name"] for item in contract["identities"]}
def token_paths(contract: dict[str, Any]) -> dict[str, str]:
authority = contract["authority"]
prefix = authority["kv_prefix"].strip("/")
return {
item["handle"]: f"{authority['kv_mount']}/{prefix}/{item['handle']}"
for item in contract["identities"]
}
def add_temporary_identities(
registry: list[dict[str, Any]], contract: dict[str, Any], values: dict[str, str]
) -> list[dict[str, Any]]:
names = identity_names(contract)
present = {str(item.get("name")) for item in registry if isinstance(item, dict)}
if present & names:
raise ProcedureError(f"temporary identities already exist: {sorted(present & names)}")
updated = list(registry)
expires = contract["window"]["expires_at"]
for identity in contract["identities"]:
updated.append(
{
"name": identity["sender_name"],
"tokens": [values[identity["handle"]]],
"sources": ["whitehat-security"],
"tenants": [identity["tenant"]],
"may_write": True,
"may_read": True,
"expires_at": expires,
}
)
return updated
def remove_temporary_identities(
registry: list[dict[str, Any]], contract: dict[str, Any]
) -> tuple[list[dict[str, Any]], list[str]]:
names = identity_names(contract)
removed = sorted(
str(item.get("name"))
for item in registry
if isinstance(item, dict) and item.get("name") in names
)
updated = [
item
for item in registry
if not isinstance(item, dict) or item.get("name") not in names
]
return updated, removed
def build_policy(contract: dict[str, Any]) -> str:
paths = token_paths(contract)
blocks: list[str] = []
for path in paths.values():
mount, relative = path.split("/", 1)
blocks.extend(
[
f'path "{mount}/data/{relative}" {{',
' capabilities = ["read"]',
"}",
"",
f'path "{mount}/metadata/{relative}" {{',
' capabilities = ["read"]',
"}",
"",
]
)
return "\n".join(blocks).rstrip() + "\n"
def build_manifest(contract: dict[str, Any], lease_id: str) -> str:
names = resource_names(contract)
authority = contract["authority"]
runner = contract["runner"]
engagement = contract["engagement_id"]
annotations = {
"custody.railiance/engagement": engagement,
"custody.railiance/lease-id": lease_id,
"custody.railiance/contract-sha256": contract_digest(contract),
}
document = {
"apiVersion": "v1",
"kind": "List",
"items": [
{
"apiVersion": "external-secrets.io/v1",
"kind": "ClusterSecretStore",
"metadata": {"name": names["store"], "annotations": annotations},
"spec": {
"provider": {
"vault": {
"server": "http://openbao.openbao.svc:8200",
"path": authority["kv_mount"],
"version": "v2",
"auth": {
"kubernetes": {
"mountPath": "kubernetes",
"role": names["role"],
"serviceAccountRef": {
"name": authority["eso_service_account"],
"namespace": authority["eso_namespace"],
},
}
},
}
},
"conditions": [{"namespaces": [runner["namespace"]]}],
},
},
{
"apiVersion": "external-secrets.io/v1",
"kind": "ExternalSecret",
"metadata": {
"name": names["external_secret"],
"namespace": runner["namespace"],
"annotations": annotations,
},
"spec": {
"refreshInterval": "1m",
"secretStoreRef": {"kind": "ClusterSecretStore", "name": names["store"]},
"target": {
"name": runner["secret_name"],
"creationPolicy": "Owner",
"deletionPolicy": "Delete",
},
"data": [
{
"secretKey": identity["handle"],
"remoteRef": {
"key": f"{authority['kv_prefix'].strip('/')}/{identity['handle']}",
"property": "token",
},
}
for identity in sorted(contract["identities"], key=lambda item: item["handle"])
],
},
},
],
}
return canonical_json(document)
def time_state(contract: dict[str, Any], now: datetime) -> str:
starts = parse_time(contract["window"]["starts_at"], "window.starts_at")
cutoff = parse_time(contract["window"]["projection_cutoff"], "window.projection_cutoff")
if now < starts:
return "before-window"
if now > cutoff:
return "projection-cutoff-passed"
return "projection-window-open"
def target_ready(operator: Operator, contract: dict[str, Any]) -> str:
target = contract["target"]
image = operator.kubectl(
[
"-n",
target["namespace"],
"get",
"deploy",
target["deployment"],
"-o",
f"jsonpath={{.spec.template.spec.containers[?(@.name==\"{target['container']}\")].image}}",
],
label="read target image",
).stdout.strip()
if not image.endswith("@" + target["image_digest"]):
raise ProcedureError("target is not on the contract-approved image digest")
ready = operator.kubectl(
[
"-n",
target["namespace"],
"get",
"deploy",
target["deployment"],
"-o",
"jsonpath={.status.readyReplicas}/{.status.replicas}",
],
label="read target readiness",
).stdout.strip()
if ready != "1/1":
raise ProcedureError(f"target is not 1/1 Ready (observed {ready or 'unknown'})")
return ready
def resource_presence(operator: Operator, contract: dict[str, Any]) -> dict[str, Any]:
names = resource_names(contract)
runner = contract["runner"]
paths = token_paths(contract)
identities = sorted(
identity_names(contract)
& {
str(item.get("name"))
for item in operator.registry()
if isinstance(item, dict)
}
)
exact_paths = sorted(
handle
for handle, path in paths.items()
if operator.bao_exists(
["kv", "metadata", "get", "-format=json", path],
label=f"check exact {handle} KV metadata",
)
)
resources: list[str] = []
checks = (
("store", ["get", "clustersecretstore", names["store"], "-o", "name"]),
(
"external_secret",
["-n", runner["namespace"], "get", "externalsecret", names["external_secret"], "-o", "name"],
),
(
"mounted_secret",
["-n", runner["namespace"], "get", "secret", runner["secret_name"], "-o", "name"],
),
)
for label, args in checks:
if operator.kubectl_exists(args, label=f"check exact {label}"):
resources.append(label)
return {
"temporary_identities_present": identities,
"exact_token_paths_present": exact_paths,
"projection_resources_present": resources,
}
def live_preflight(
operator: Operator,
contract: dict[str, Any],
*,
api_base: str = DEFAULT_API_BASE,
now: datetime | None = None,
) -> dict[str, Any]:
current = (now or utc_now()).astimezone(UTC)
operator.bao(["status", "-format=json"], label="read OpenBao status")
target = target_ready(operator, contract)
runner = contract["runner"]
authority = contract["authority"]
operator.kubectl(["get", "ns", runner["namespace"], "-o", "name"], label="verify runner namespace")
operator.kubectl(
["-n", authority["eso_namespace"], "get", "sa", authority["eso_service_account"], "-o", "name"],
label="verify ESO service account",
)
presence = resource_presence(operator, contract)
broker_ready = True
broker_reason = None
try:
current_broker_receipt(contract, api_base, now=current)
except ContractError as exc:
broker_ready = False
broker_reason = str(exc)
return {
"interface": "railiance.custody-preflight",
"version": 1,
"engagement_id": contract["engagement_id"],
"projection_contract_digest": contract_digest(contract),
"time_state": time_state(contract, current),
"broker_ready": broker_ready,
"broker_gate": "pass" if broker_ready else "blocked",
"broker_reason": broker_reason,
"target_image_matches": True,
"target_ready": target,
"openbao_initialized": True,
"openbao_sealed": False,
**presence,
"secret_values_observed": False,
}
def write_exact_values(operator: Operator, contract: dict[str, Any], values: dict[str, str]) -> None:
with tempfile.TemporaryDirectory(prefix="railiance-custody-values-") as directory:
root = Path(directory)
os.chmod(root, 0o700)
files: list[Path] = []
try:
for handle, path in token_paths(contract).items():
document = root / handle
document.write_text(values[handle], encoding="utf-8")
os.chmod(document, 0o600)
files.append(document)
operator.bao(
[
"kv",
"put",
path,
f"token=@{document}",
f"engagement_id={contract['engagement_id']}",
f"expires_at={contract['window']['expires_at']}",
],
label=f"write exact {handle} KV path",
)
finally:
for document in files:
secure_unlink(document)
def configure_projection(operator: Operator, contract: dict[str, Any], lease_id: str) -> None:
names = resource_names(contract)
authority = contract["authority"]
runner = contract["runner"]
operator.bao(
["policy", "write", names["policy"], "-"],
label="write exact ESO policy",
input_text=build_policy(contract),
)
operator.bao(
[
"write",
f"auth/kubernetes/role/{names['role']}",
f"bound_service_account_names={authority['eso_service_account']}",
f"bound_service_account_namespaces={authority['eso_namespace']}",
f"policies={names['policy']}",
"ttl=5m",
"max_ttl=15m",
],
label="write exact ESO Kubernetes role",
)
operator.kubectl(
["apply", "-f", "-"],
label="apply exact projection manifest",
input_text=build_manifest(contract, lease_id),
)
operator.kubectl(
["wait", "--for=condition=Ready", f"clustersecretstore/{names['store']}", "--timeout=90s"],
label="wait for exact store",
)
operator.kubectl(
[
"-n",
runner["namespace"],
"wait",
"--for=condition=Ready",
f"externalsecret/{names['external_secret']}",
"--timeout=90s",
],
label="wait for exact ExternalSecret",
)
keys = operator.kubectl(
[
"-n",
runner["namespace"],
"get",
"secret",
runner["secret_name"],
"-o",
'go-template={{range $k, $_ := .data}}{{$k}}{{"\\n"}}{{end}}',
],
label="verify projected key names",
).stdout.splitlines()
expected = sorted(identity["handle"] for identity in contract["identities"])
if sorted(keys) != expected:
raise ProcedureError(f"projected Secret has unexpected key names: {sorted(keys)}")
def force_sender_sync_and_restart(operator: Operator, contract: dict[str, Any]) -> None:
target = contract["target"]
namespace = target["namespace"]
external_secret = target["sender_external_secret"]
before = operator.kubectl(
["-n", namespace, "get", "secret", external_secret, "-o", "jsonpath={.metadata.resourceVersion}"],
label="read sender Secret resource version",
).stdout.strip()
operator.kubectl(
[
"-n",
namespace,
"annotate",
"externalsecret",
external_secret,
f"force-sync={int(time.time())}",
"--overwrite",
],
label="force sender registry sync",
)
deadline = time.monotonic() + 120
while time.monotonic() < deadline:
current = operator.kubectl(
["-n", namespace, "get", "secret", external_secret, "-o", "jsonpath={.metadata.resourceVersion}"],
label="poll sender Secret resource version",
).stdout.strip()
if current and current != before:
break
time.sleep(2)
else:
raise ProcedureError("sender Secret did not refresh within 120 seconds")
operator.kubectl(
["-n", namespace, "rollout", "restart", f"deploy/{target['deployment']}"],
label="restart sender registry reader",
)
operator.kubectl(
["-n", namespace, "rollout", "status", f"deploy/{target['deployment']}", "--timeout=120s"],
label="wait for target rollout",
)
def delete_projection(operator: Operator, contract: dict[str, Any]) -> None:
names = resource_names(contract)
runner = contract["runner"]
operator.kubectl(
["-n", runner["namespace"], "delete", "externalsecret", names["external_secret"], "--ignore-not-found"],
label="delete exact ExternalSecret",
)
operator.kubectl(
["-n", runner["namespace"], "delete", "secret", runner["secret_name"], "--ignore-not-found"],
label="delete exact mounted Secret",
)
operator.kubectl(
["delete", "clustersecretstore", names["store"], "--ignore-not-found"],
label="delete exact store",
)
for handle, path in token_paths(contract).items():
if operator.bao_exists(
["kv", "metadata", "get", "-format=json", path],
label=f"check exact {handle} metadata before delete",
):
operator.bao(["kv", "metadata", "delete", path], label=f"delete exact {handle} KV path")
if operator.bao_exists(["read", f"auth/kubernetes/role/{names['role']}"] , label="check exact role"):
operator.bao(["delete", f"auth/kubernetes/role/{names['role']}"] , label="delete exact role")
if operator.bao_exists(["policy", "read", names["policy"]], label="check exact policy"):
operator.bao(["policy", "delete", names["policy"]], label="delete exact policy")
def verify_cleanup_absent(operator: Operator, contract: dict[str, Any]) -> None:
operator.bao(["status", "-format=json"], label="verify OpenBao after cleanup")
operator.kubectl(["get", "ns", contract["runner"]["namespace"], "-o", "name"], label="verify Kubernetes after cleanup")
presence = resource_presence(operator, contract)
if any(presence.values()):
raise ProcedureError(f"engagement resources remain after cleanup: {presence}")
def cleanup_scope(operator: Operator, contract: dict[str, Any]) -> dict[str, Any]:
registry = operator.registry()
updated, removed = remove_temporary_identities(registry, contract)
if removed:
operator.write_registry(updated)
force_sender_sync_and_restart(operator, contract)
delete_projection(operator, contract)
verify_cleanup_absent(operator, contract)
target_ready(operator, contract)
return {
"removed_identities": removed,
"removed_resources": sorted(
[
*token_paths(contract).values(),
*resource_names(contract).values(),
]
),
}
def transactional(
steps: list[Callable[[], None]], rollback: Callable[[], Any]
) -> None:
try:
for step in steps:
step()
except Exception as original:
try:
rollback()
except Exception as cleanup_error:
raise ProcedureError(
"projection failed and exact cleanup could not be proven"
) from cleanup_error
raise original
def resource_uids(
operator: Operator, contract: dict[str, Any], *, require_all: bool = True
) -> dict[str, str]:
names = resource_names(contract)
runner = contract["runner"]
requests = {
"store": [
"get", "clustersecretstore", names["store"], "--ignore-not-found",
"-o", "jsonpath={.metadata.uid}",
],
"external_secret": [
"-n", runner["namespace"], "get", "externalsecret", names["external_secret"],
"--ignore-not-found",
"-o", "jsonpath={.metadata.uid}",
],
"mounted_secret": [
"-n", runner["namespace"], "get", "secret", runner["secret_name"],
"--ignore-not-found",
"-o", "jsonpath={.metadata.uid}",
],
}
result = {
key: operator.kubectl(args, label=f"read exact {key} UID").stdout.strip()
for key, args in requests.items()
}
if require_all and any(not value for value in result.values()):
raise ProcedureError("projection resource UID evidence is incomplete")
return {key: value for key, value in result.items() if value}
def verify_receipt_resource_scope(
operator: Operator, contract: dict[str, Any], receipt: dict[str, Any]
) -> dict[str, str]:
present = resource_uids(operator, contract, require_all=False)
expected = receipt["resources"]["uids"]
mismatched = {
key: value
for key, value in present.items()
if expected.get(key) != value
}
if mismatched:
raise ProcedureError(
"live projection resource UID differs from the cleanup receipt; refusing deletion"
)
return present
def project(
operator: Operator,
contract: dict[str, Any],
*,
api_base: str = DEFAULT_API_BASE,
now: datetime | None = None,
lease_id: str | None = None,
) -> dict[str, Any]:
current = (now or utc_now()).astimezone(UTC)
if time_state(contract, current) != "projection-window-open":
raise ProcedureError("projection is outside the approved projection window")
# This is intentionally before token generation and every mutation.
broker_receipt = current_broker_receipt(contract, api_base, now=current)
preflight = live_preflight(operator, contract, api_base=api_base, now=current)
if not preflight["broker_ready"]:
raise ProcedureError("consumer broker is not ready")
if any(
preflight[key]
for key in (
"temporary_identities_present",
"exact_token_paths_present",
"projection_resources_present",
)
):
raise ProcedureError("cleanup is required before a new projection")
active_lease = lease_id or f"custody:{secrets.token_hex(16)}"
values = {identity["handle"]: secrets.token_urlsafe(48) for identity in contract["identities"]}
if len(set(values.values())) != len(values):
raise ProcedureError("credential generator returned duplicate values")
try:
transactional(
[
lambda: write_exact_values(operator, contract, values),
lambda: operator.write_registry(
add_temporary_identities(operator.registry(), contract, values)
),
lambda: configure_projection(operator, contract, active_lease),
lambda: force_sender_sync_and_restart(operator, contract),
],
lambda: cleanup_scope(operator, contract),
)
finally:
values.clear()
projected_at = rfc3339(utc_now())
base = {
"interface": PROJECTION_INTERFACE,
"version": 1,
"workplan_id": WORKPLAN_ID,
"state": "projected",
"lease_id": active_lease,
"engagement_id": contract["engagement_id"],
"target": {
"id": contract["target"]["id"],
"revision": contract["target"]["revision"],
"image_digest": contract["target"]["image_digest"],
},
"projection_contract_digest": contract_digest(contract),
"broker_receipt_digest": digest(broker_receipt),
"projected_at": projected_at,
"expires_at": contract["window"]["expires_at"],
"identities": sorted(
(
{
"handle": item["handle"],
"role": item["role"],
"sender_name": item["sender_name"],
"mount_path": item["mount_path"],
}
for item in contract["identities"]
),
key=lambda item: item["handle"],
),
"resources": {
"names": resource_names(contract),
"uids": resource_uids(operator, contract),
},
"cleanup_authority": "railiance-platform",
"secret_values_observed": False,
}
receipt = {**base, "receipt_id": f"sha256:{digest(base)}"}
validate_projection_receipt(receipt, contract)
return receipt
def projection_status(
operator: Operator, contract: dict[str, Any], receipt: dict[str, Any]
) -> dict[str, Any]:
validate_projection_receipt(receipt, contract)
presence = resource_presence(operator, contract)
counts = [
len(presence["temporary_identities_present"]),
len(presence["exact_token_paths_present"]),
len(presence["projection_resources_present"]),
]
state = "active" if counts == [2, 2, 3] else "absent" if counts == [0, 0, 0] else "partial"
uid_match = False
present_uids = resource_uids(operator, contract, require_all=False)
uid_match = bool(present_uids) and all(
receipt["resources"]["uids"].get(key) == value
for key, value in present_uids.items()
)
if present_uids and not uid_match:
state = "mismatched"
return {
"interface": "railiance.custody-projection-status",
"version": 1,
"engagement_id": contract["engagement_id"],
"lease_id": receipt["lease_id"],
"state": state,
"resource_uids_match": uid_match,
**presence,
"target_ready": target_ready(operator, contract),
"secret_values_observed": False,
}
def cleanup(
operator: Operator, contract: dict[str, Any], projection: dict[str, Any]
) -> dict[str, Any]:
validate_projection_receipt(projection, contract)
verify_receipt_resource_scope(operator, contract, projection)
result = cleanup_scope(operator, contract)
receipt = {
"interface": CLEANUP_INTERFACE,
"version": 1,
"workplan_id": WORKPLAN_ID,
"state": "cleaned",
"lease_id": projection["lease_id"],
"engagement_id": contract["engagement_id"],
"projection_receipt_id": projection["receipt_id"],
"cleaned_at": rfc3339(utc_now()),
"removed_resources": result["removed_resources"],
"target_ready": True,
"secret_values_observed": False,
}
validate_cleanup_receipt(receipt, projection, contract)
return receipt
def assert_confirm(value: str | None, expected: str) -> None:
if value != expected:
raise ProcedureError(f"live command requires --confirm {expected}")
def assert_expired_cleanup(contract: dict[str, Any], now: datetime) -> None:
expires = parse_time(contract["window"]["expires_at"], "window.expires_at")
if now.astimezone(UTC) <= expires:
raise ProcedureError("cleanup-expired refuses before receipt expiry")
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
"command",
choices=["validate", "render", "preflight", "project", "status", "cleanup", "cleanup-expired"],
)
parser.add_argument("--contract", type=Path, required=True)
parser.add_argument("--receipt", type=Path)
parser.add_argument("--receipt-out", type=Path)
parser.add_argument("--token-file", type=Path, default=Path.home() / ".local/openbao/platform-admin.token")
parser.add_argument("--state-hub", default=DEFAULT_API_BASE)
parser.add_argument("--confirm")
args = parser.parse_args()
try:
contract = validate_projection_contract(load_json(args.contract))
if args.command == "validate":
result = {
"valid": True,
"projection_contract_digest": contract_digest(contract),
"resource_names": resource_names(contract),
"secret_values_observed": False,
}
elif args.command == "render":
result = {
"policy": build_policy(contract),
"manifest": json.loads(build_manifest(contract, "custody:" + "0" * 32)),
"secret_values_observed": False,
}
else:
projection = None
if args.command in {"status", "cleanup", "cleanup-expired"}:
if not args.receipt:
raise ProcedureError(f"{args.command} requires --receipt")
projection = validate_projection_receipt(load_json(args.receipt), contract)
if args.command == "cleanup-expired":
assert_expired_cleanup(contract, utc_now())
if args.command == "project":
# Reject an unready consumer before reading platform-admin
# custody material. project() repeats this immutable gate.
current_broker_receipt(contract, args.state_hub)
operator = Operator(contract, args.token_file)
if args.command == "preflight":
result = live_preflight(operator, contract, api_base=args.state_hub)
elif args.command == "project":
assert_confirm(args.confirm, f"{contract['engagement_id']}:attended")
result = project(operator, contract, api_base=args.state_hub)
else:
assert projection is not None
if args.command == "status":
result = projection_status(operator, contract, projection)
else:
if args.command == "cleanup-expired":
assert_confirm(args.confirm, f"{contract['engagement_id']}:expired")
else:
assert_confirm(args.confirm, f"{contract['engagement_id']}:cleanup")
result = cleanup(operator, contract, projection)
if args.receipt_out:
write_receipt(args.receipt_out, result)
print(json.dumps(result, indent=2, sort_keys=True))
return 0
except (ContractError, OSError, ProcedureError, RemoteExecutionError) as exc:
print(f"custody projection failed: {exc}", file=sys.stderr)
return 1
if __name__ == "__main__":
raise SystemExit(main())

444
scripts/custody_contract.py Executable file
View file

@ -0,0 +1,444 @@
#!/usr/bin/env python3
"""Canonical, value-safe contracts for ephemeral credential projection."""
from __future__ import annotations
import hashlib
import json
import re
import urllib.error
import urllib.parse
import urllib.request
from datetime import UTC, datetime
from pathlib import Path
from typing import Any, Callable
CONTRACT_INTERFACE = "railiance.custody-projection-contract"
BROKER_INTERFACE = "railiance.custody-broker-readiness"
PROJECTION_INTERFACE = "railiance.custody-projection-receipt"
CLEANUP_INTERFACE = "railiance.custody-cleanup-receipt"
WORKPLAN_ID = "RAILIANCE-WP-0025"
BROKER_OWNER = "whitehat-security"
BROKER_SUBJECT_PREFIX = "WP0025-BROKER-READINESS"
REPO_ROOT = Path(__file__).resolve().parents[1]
INTERFACE_ARTIFACTS = (
"schemas/custody-projection-contract.schema.json",
"schemas/custody-broker-readiness.schema.json",
"schemas/custody-projection-receipt.schema.json",
"schemas/custody-cleanup-receipt.schema.json",
)
SHA256 = re.compile(r"(?:sha256:)?[0-9a-f]{64}\Z")
GIT_REVISION = re.compile(r"[0-9a-f]{40}\Z")
DNS_LABEL = re.compile(r"[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?\Z")
class ContractError(RuntimeError):
pass
def canonical_json(value: Any) -> str:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
def digest(value: Any) -> str:
return hashlib.sha256(canonical_json(value).encode("utf-8")).hexdigest()
def file_digest(path: Path) -> str:
result = hashlib.sha256()
with path.open("rb") as handle:
for block in iter(lambda: handle.read(1024 * 1024), b""):
result.update(block)
return result.hexdigest()
def parse_time(value: Any, field: str) -> datetime:
if not isinstance(value, str):
raise ContractError(f"{field} must be an RFC3339 timestamp")
try:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
except ValueError as exc:
raise ContractError(f"{field} must be an RFC3339 timestamp") from exc
if parsed.tzinfo is None:
raise ContractError(f"{field} must include a timezone")
return parsed.astimezone(UTC)
def load_json(path: Path) -> dict[str, Any]:
try:
value = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise ContractError(f"JSON document is unavailable or invalid: {path}") from exc
if not isinstance(value, dict):
raise ContractError("JSON document must be an object")
return value
def _require_string(value: dict[str, Any], key: str) -> str:
observed = value.get(key)
if not isinstance(observed, str) or not observed:
raise ContractError(f"{key} must be a non-empty string")
return observed
def _require_sha(value: dict[str, Any], key: str) -> str:
observed = _require_string(value, key)
if not SHA256.fullmatch(observed):
raise ContractError(f"{key} must be a SHA-256 digest")
return observed.removeprefix("sha256:")
def resource_suffix(engagement_id: str) -> str:
return hashlib.sha256(engagement_id.encode("utf-8")).hexdigest()[:12]
def interface_artifacts(root: Path = REPO_ROOT) -> dict[str, str]:
return {path: file_digest(root / path) for path in INTERFACE_ARTIFACTS}
def resource_names(contract: dict[str, Any]) -> dict[str, str]:
suffix = resource_suffix(contract["engagement_id"])
return {
"policy": f"custody-{suffix}",
"role": f"custody-{suffix}",
"store": f"custody-{suffix}",
"external_secret": contract["runner"]["secret_name"],
}
def validate_projection_contract(value: dict[str, Any]) -> dict[str, Any]:
if value.get("interface") != CONTRACT_INTERFACE or value.get("version") != 1:
raise ContractError("unsupported projection contract interface/version")
if value.get("workplan_id") != WORKPLAN_ID:
raise ContractError("projection contract has the wrong workplan")
engagement_id = _require_string(value, "engagement_id")
if not re.fullmatch(r"[A-Z0-9][A-Z0-9._-]{7,127}", engagement_id):
raise ContractError("engagement_id has an invalid shape")
if value.get("status") != "approved":
raise ContractError("engagement must be approved")
target = value.get("target")
runner = value.get("runner")
window = value.get("window")
authority = value.get("authority")
identities = value.get("identities")
if not all(isinstance(item, dict) for item in (target, runner, window, authority)):
raise ContractError("target, runner, window and authority must be objects")
if not isinstance(identities, list) or len(identities) != 2:
raise ContractError("exactly two identity handles are required")
target_id = _require_string(target, "id")
target_namespace = _require_string(target, "namespace")
target_deployment = _require_string(target, "deployment")
_require_string(target, "container")
_require_string(target, "sender_external_secret")
if not DNS_LABEL.fullmatch(target_namespace) or not DNS_LABEL.fullmatch(target_deployment):
raise ContractError("target namespace and deployment must be DNS labels")
revision = _require_string(target, "revision")
if not GIT_REVISION.fullmatch(revision):
raise ContractError("target.revision must be a full Git revision")
image = _require_string(target, "image_digest")
if not re.fullmatch(r"sha256:[0-9a-f]{64}", image):
raise ContractError("target.image_digest must be a SHA-256 image digest")
_require_sha(target, "contract_sha256")
_require_sha(runner, "manifest_sha256")
_require_sha(value, "engagement_contract_sha256")
namespace = _require_string(runner, "namespace")
secret_name = _require_string(runner, "secret_name")
_require_string(runner, "service_account")
mount_root = _require_string(runner, "mount_root").rstrip("/")
if not DNS_LABEL.fullmatch(namespace) or not DNS_LABEL.fullmatch(secret_name):
raise ContractError("runner namespace and secret_name must be DNS labels")
if not mount_root.startswith("/") or ".." in Path(mount_root).parts:
raise ContractError("runner.mount_root must be an absolute safe path")
starts = parse_time(window.get("starts_at"), "window.starts_at")
cutoff = parse_time(window.get("projection_cutoff"), "window.projection_cutoff")
expires = parse_time(window.get("expires_at"), "window.expires_at")
if not starts < cutoff < expires:
raise ContractError("window must satisfy starts_at < projection_cutoff < expires_at")
if (expires - starts).total_seconds() > 900:
raise ContractError("ephemeral custody window may not exceed 900 seconds")
remote = _require_string(authority, "remote")
if any(character.isspace() for character in remote):
raise ContractError("authority.remote must be one SSH destination")
registry_path = _require_string(authority, "registry_path")
registry_field = _require_string(authority, "registry_field")
kv_mount = _require_string(authority, "kv_mount")
kv_prefix = _require_string(authority, "kv_prefix").strip("/")
_require_string(authority, "eso_service_account")
_require_string(authority, "eso_namespace")
if engagement_id not in kv_prefix or target_id not in kv_prefix:
raise ContractError("authority.kv_prefix must bind engagement_id and target id")
if "/" in kv_mount or not registry_path or "/" in registry_field:
raise ContractError("invalid KV mount or registry field")
expected_handles = {"token-a", "token-b"}
handles: set[str] = set()
roles: set[str] = set()
senders: set[str] = set()
tenants: set[str] = set()
for identity in identities:
if not isinstance(identity, dict):
raise ContractError("identity entries must be objects")
handle = _require_string(identity, "handle")
role = _require_string(identity, "role")
sender = _require_string(identity, "sender_name")
tenant = _require_string(identity, "tenant")
mount_path = _require_string(identity, "mount_path")
if identity.get("may_read") is not True or identity.get("may_write") is not True:
raise ContractError("temporary identities require may_read=true and may_write=true")
if mount_path != f"{mount_root}/{handle}":
raise ContractError("identity mount_path must be mount_root plus its handle")
handles.add(handle)
roles.add(role)
senders.add(sender)
tenants.add(tenant)
if handles != expected_handles or roles != {"attacker", "owner"}:
raise ContractError("identities must provide token-a/token-b and attacker/owner roles")
if len(senders) != 2 or len(tenants) != 2:
raise ContractError("sender names and tenants must be distinct")
names = resource_names(value)
if any(not DNS_LABEL.fullmatch(name) for name in names.values()):
raise ContractError("derived resource name is not a DNS label")
return value
def contract_digest(contract: dict[str, Any]) -> str:
validate_projection_contract(contract)
return digest(contract)
def assert_value_safe(value: Any) -> None:
forbidden_keys = {
"token",
"tokens",
"password",
"secret_value",
"secret_values",
"bearer",
"private_key",
}
if isinstance(value, dict):
for key, item in value.items():
if str(key).lower() in forbidden_keys:
raise ContractError(f"value-bearing field is forbidden: {key}")
assert_value_safe(item)
elif isinstance(value, list):
for item in value:
assert_value_safe(item)
def broker_subject(receipt: dict[str, Any]) -> str:
return "/".join(
(
BROKER_SUBJECT_PREFIX,
"v1",
receipt["engagement_id"],
receipt["decision"],
receipt["projection_contract_digest"],
)
)
def validate_broker_receipt(
receipt: dict[str, Any], contract: dict[str, Any], *, now: datetime | None = None
) -> dict[str, Any]:
validate_projection_contract(contract)
assert_value_safe(receipt)
if receipt.get("interface") != BROKER_INTERFACE or receipt.get("version") != 1:
raise ContractError("unsupported broker receipt interface/version")
if receipt.get("workplan_id") != WORKPLAN_ID or receipt.get("owner") != BROKER_OWNER:
raise ContractError("broker receipt has the wrong workplan or owner")
if receipt.get("decision") != "approve":
raise ContractError("broker receipt is not approved")
if receipt.get("engagement_id") != contract["engagement_id"]:
raise ContractError("broker receipt engagement does not match")
if receipt.get("target_id") != contract["target"]["id"]:
raise ContractError("broker receipt target does not match")
if receipt.get("projection_contract_digest") != contract_digest(contract):
raise ContractError("broker receipt projection contract is stale")
if receipt.get("projection_receipt_interface") != PROJECTION_INTERFACE:
raise ContractError("broker receipt cannot consume projection receipt v1")
if receipt.get("interface_artifacts") != interface_artifacts():
raise ContractError("broker receipt interface artifacts are stale")
if receipt.get("required_roles") != ["attacker", "owner"]:
raise ContractError("broker receipt has the wrong identity roles")
expected_mounts = sorted(identity["mount_path"] for identity in contract["identities"])
if receipt.get("mount_paths") != expected_mounts:
raise ContractError("broker receipt mount paths do not match")
adapter = receipt.get("adapter")
if not isinstance(adapter, dict):
raise ContractError("broker receipt requires adapter evidence")
adapter_path = adapter.get("path")
if (
adapter.get("repo") != "whitehat-security"
or not isinstance(adapter_path, str)
or not adapter_path
or Path(adapter_path).is_absolute()
or ".." in Path(adapter_path).parts
):
raise ContractError("broker adapter repo/path is invalid")
if not GIT_REVISION.fullmatch(str(adapter.get("revision", ""))):
raise ContractError("broker adapter revision must be a full Git revision")
if not SHA256.fullmatch(str(adapter.get("sha256", ""))):
raise ContractError("broker adapter requires a SHA-256 digest")
if adapter.get("tests_passed") is not True or receipt.get("cleanup_request_supported") is not True:
raise ContractError("broker adapter tests and cleanup support are required")
if receipt.get("secret_values_observed") is not False:
raise ContractError("broker receipt is not value-safe")
created = parse_time(receipt.get("created_at"), "broker.created_at")
current = (now or datetime.now(UTC)).astimezone(UTC)
starts = parse_time(contract["window"]["starts_at"], "window.starts_at")
expires = parse_time(contract["window"]["expires_at"], "window.expires_at")
if created > current or created > expires or created < starts.replace(hour=0, minute=0, second=0, microsecond=0):
raise ContractError("broker receipt timestamp is outside the engagement day")
return receipt
def parse_broker_message(
message: dict[str, Any], contract: dict[str, Any], *, now: datetime | None = None
) -> dict[str, Any] | None:
if message.get("from_agent") != BROKER_OWNER or message.get("to_agent") != "railiance-platform":
return None
body = message.get("body")
if not isinstance(body, str):
return None
try:
receipt = json.loads(body)
except json.JSONDecodeError:
return None
if not isinstance(receipt, dict):
return None
try:
if message.get("subject") != broker_subject(receipt):
return None
return validate_broker_receipt(receipt, contract, now=now)
except (ContractError, KeyError, TypeError):
return None
def http_json(
method: str,
url: str,
payload: dict[str, Any] | None = None,
*,
opener: Callable[..., Any] = urllib.request.urlopen,
) -> Any:
data = canonical_json(payload).encode("utf-8") if payload is not None else None
request = urllib.request.Request(
url,
data=data,
method=method,
headers={"Content-Type": "application/json"} if data else {},
)
try:
with opener(request, timeout=10) as response:
return json.loads(response.read().decode("utf-8"))
except (OSError, urllib.error.URLError, json.JSONDecodeError) as exc:
raise ContractError(f"State Hub request failed: {method} {url}") from exc
def current_broker_receipt(
contract: dict[str, Any], api_base: str, *, now: datetime | None = None
) -> dict[str, Any]:
query = urllib.parse.urlencode(
{"to_agent": "railiance-platform", "unread_only": "false"}
)
messages = http_json("GET", f"{api_base.rstrip('/')}/messages/?{query}")
if not isinstance(messages, list):
raise ContractError("State Hub returned an invalid message list")
receipts = [
parsed
for message in messages
if isinstance(message, dict)
and (parsed := parse_broker_message(message, contract, now=now)) is not None
]
if not receipts:
raise ContractError("no current Whitehat broker-readiness receipt exists")
return max(receipts, key=lambda item: item["created_at"])
def validate_projection_receipt(
receipt: dict[str, Any], contract: dict[str, Any]
) -> dict[str, Any]:
validate_projection_contract(contract)
assert_value_safe(receipt)
if receipt.get("interface") != PROJECTION_INTERFACE or receipt.get("version") != 1:
raise ContractError("unsupported projection receipt interface/version")
if receipt.get("workplan_id") != WORKPLAN_ID or receipt.get("state") != "projected":
raise ContractError("projection receipt has the wrong workplan or state")
if receipt.get("engagement_id") != contract["engagement_id"]:
raise ContractError("projection receipt engagement does not match")
if receipt.get("projection_contract_digest") != contract_digest(contract):
raise ContractError("projection receipt contract is stale")
expected_target = {
"id": contract["target"]["id"],
"revision": contract["target"]["revision"],
"image_digest": contract["target"]["image_digest"],
}
if receipt.get("target") != expected_target:
raise ContractError("projection receipt target does not match")
lease_id = receipt.get("lease_id")
if not isinstance(lease_id, str) or not re.fullmatch(r"custody:[0-9a-f]{32}", lease_id):
raise ContractError("projection receipt lease_id is invalid")
if receipt.get("secret_values_observed") is not False:
raise ContractError("projection receipt is not value-safe")
projected = parse_time(receipt.get("projected_at"), "projection.projected_at")
expires = parse_time(receipt.get("expires_at"), "projection.expires_at")
contract_expires = parse_time(contract["window"]["expires_at"], "window.expires_at")
if expires != contract_expires or projected >= expires:
raise ContractError("projection receipt has invalid projection/expiry times")
identities = receipt.get("identities")
expected_identities = sorted(
(
{
"handle": item["handle"],
"role": item["role"],
"sender_name": item["sender_name"],
"mount_path": item["mount_path"],
}
for item in contract["identities"]
),
key=lambda item: item["handle"],
)
if identities != expected_identities:
raise ContractError("projection receipt identity handles do not match")
resources = receipt.get("resources")
if not isinstance(resources, dict) or resources.get("names") != resource_names(contract):
raise ContractError("projection receipt resources do not match")
uids = resources.get("uids")
if not isinstance(uids, dict) or set(uids) != {"store", "external_secret", "mounted_secret"}:
raise ContractError("projection receipt requires exact Kubernetes UIDs")
if any(not isinstance(uid, str) or not uid for uid in uids.values()):
raise ContractError("projection receipt contains an invalid Kubernetes UID")
base = dict(receipt)
observed_id = base.pop("receipt_id", None)
if observed_id != f"sha256:{digest(base)}":
raise ContractError("projection receipt id does not match its canonical content")
return receipt
def validate_cleanup_receipt(
receipt: dict[str, Any], projection: dict[str, Any], contract: dict[str, Any]
) -> dict[str, Any]:
validate_projection_receipt(projection, contract)
assert_value_safe(receipt)
if receipt.get("interface") != CLEANUP_INTERFACE or receipt.get("version") != 1:
raise ContractError("unsupported cleanup receipt interface/version")
if receipt.get("workplan_id") != WORKPLAN_ID or receipt.get("state") != "cleaned":
raise ContractError("cleanup receipt has the wrong workplan or state")
if receipt.get("engagement_id") != contract["engagement_id"]:
raise ContractError("cleanup receipt engagement does not match")
if receipt.get("lease_id") != projection["lease_id"]:
raise ContractError("cleanup receipt lease does not match")
if receipt.get("projection_receipt_id") != projection["receipt_id"]:
raise ContractError("cleanup receipt projection id does not match")
parse_time(receipt.get("cleaned_at"), "cleanup.cleaned_at")
if receipt.get("target_ready") is not True or receipt.get("secret_values_observed") is not False:
raise ContractError("cleanup receipt does not prove safe target recovery")
return receipt

43
scripts/remote_exec.py Executable file
View file

@ -0,0 +1,43 @@
#!/usr/bin/env python3
"""Small, value-safe SSH argv transport shared by attended procedures."""
from __future__ import annotations
import shlex
import subprocess
from typing import Callable
class RemoteExecutionError(RuntimeError):
pass
def remote_command(argv: list[str]) -> str:
if not argv or any(not isinstance(part, str) or "\0" in part for part in argv):
raise RemoteExecutionError("remote argv must contain non-NUL strings")
return shlex.join(argv)
def run_remote(
host: str,
argv: list[str],
*,
label: str,
input_text: str | None = None,
allow_missing: bool = False,
runner: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run,
) -> subprocess.CompletedProcess[str]:
if not host or any(character.isspace() for character in host):
raise RemoteExecutionError("remote host must be a single SSH destination")
completed = runner(
["ssh", "-o", "BatchMode=yes", host, remote_command(argv)],
text=True,
capture_output=True,
input=input_text,
check=False,
)
if completed.returncode and not (allow_missing and completed.returncode == 1):
# Remote output may contain provider or application material. Keep the
# durable error value-safe and let an attended operator inspect locally.
raise RemoteExecutionError(f"{label} failed (exit {completed.returncode})")
return completed

View file

@ -0,0 +1,257 @@
#!/usr/bin/env python3
"""Direct Whitehat owner interface for WP-0025 broker readiness."""
from __future__ import annotations
import argparse
import json
import re
import subprocess
import sys
from datetime import UTC, datetime
from pathlib import Path
from typing import Any, Callable
SCRIPT_DIR = Path(__file__).resolve().parent
if str(SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPT_DIR))
from custody_contract import ( # noqa: E402
BROKER_INTERFACE,
BROKER_OWNER,
PROJECTION_INTERFACE,
WORKPLAN_ID,
ContractError,
broker_subject,
canonical_json,
contract_digest,
current_broker_receipt,
file_digest,
http_json,
interface_artifacts,
load_json,
validate_broker_receipt,
validate_projection_contract,
)
DEFAULT_ADAPTER = Path("src/whitehat_security/platform_custody.py")
DEFAULT_TEST = Path("tests/test_platform_custody_adapter.py")
class ReadinessError(RuntimeError):
pass
def validate_reviewer(value: str) -> str:
if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._:@/+\-]{1,127}", value):
raise ReadinessError("reviewer must be a stable 2-128 character identifier")
return value
def validate_note(value: str) -> str:
note = value.strip()
if not note or len(note) > 2000:
raise ReadinessError("request-changes requires a note of 1-2000 characters")
forbidden = ("BEGIN PRIVATE KEY", "BEGIN OPENSSH PRIVATE KEY", "AGE-SECRET-KEY-1", "hvs.")
if any(marker in note for marker in forbidden):
raise ReadinessError("note appears to contain credential material")
return note
def safe_child(root: Path, relative: Path) -> Path:
if relative.is_absolute():
raise ReadinessError("adapter and test paths must be repository-relative")
target = (root / relative).resolve()
try:
target.relative_to(root.resolve())
except ValueError as exc:
raise ReadinessError("consumer artifact escapes repository root") from exc
return target
def verify_adapter(
consumer_root: Path,
*,
adapter_path: Path = DEFAULT_ADAPTER,
test_path: Path = DEFAULT_TEST,
runner: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run,
) -> dict[str, Any]:
root = consumer_root.resolve()
adapter = safe_child(root, adapter_path)
test = safe_child(root, test_path)
if not adapter.is_file() or not test.is_file():
return {
"passed": False,
"adapter_present": adapter.is_file(),
"test_present": test.is_file(),
"secret_values_observed": False,
}
revision_result = runner(
["git", "rev-parse", "HEAD"],
cwd=root,
text=True,
capture_output=True,
check=False,
)
revision = revision_result.stdout.strip() if revision_result.returncode == 0 else ""
tests = runner(
[sys.executable, "-m", "pytest", "-q", str(test_path)],
cwd=root,
text=True,
capture_output=True,
check=False,
)
passed = bool(re.fullmatch(r"[0-9a-f]{40}", revision)) and tests.returncode == 0
return {
"passed": passed,
"adapter_present": True,
"test_present": True,
"adapter": {
"repo": "whitehat-security",
"revision": revision,
"path": str(adapter_path),
"sha256": file_digest(adapter),
"tests_passed": tests.returncode == 0,
},
"test_exit_code": tests.returncode,
"secret_values_observed": False,
}
def receipt_base(contract: dict[str, Any], reviewer: str, decision: str) -> dict[str, Any]:
return {
"interface": BROKER_INTERFACE,
"version": 1,
"workplan_id": WORKPLAN_ID,
"owner": BROKER_OWNER,
"reviewer": validate_reviewer(reviewer),
"decision": decision,
"created_at": datetime.now(UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z"),
"engagement_id": contract["engagement_id"],
"target_id": contract["target"]["id"],
"projection_contract_digest": contract_digest(contract),
"projection_receipt_interface": PROJECTION_INTERFACE,
"interface_artifacts": interface_artifacts(),
"required_roles": ["attacker", "owner"],
"mount_paths": sorted(item["mount_path"] for item in contract["identities"]),
"secret_values_observed": False,
}
def build_approval(
contract: dict[str, Any], reviewer: str, verification: dict[str, Any]
) -> dict[str, Any]:
if verification.get("passed") is not True or verification.get("secret_values_observed") is not False:
raise ReadinessError("consumer adapter verification did not pass")
receipt = {
**receipt_base(contract, reviewer, "approve"),
"adapter": verification["adapter"],
"cleanup_request_supported": True,
}
validate_broker_receipt(receipt, contract)
return receipt
def build_change_request(contract: dict[str, Any], reviewer: str, note: str) -> dict[str, Any]:
return {
**receipt_base(contract, reviewer, "request-changes"),
"note": validate_note(note),
}
def post_receipt(receipt: dict[str, Any], api_base: str) -> dict[str, Any]:
response = http_json(
"POST",
f"{api_base.rstrip('/')}/messages/",
{
"from_agent": BROKER_OWNER,
"to_agent": "railiance-platform",
"subject": broker_subject(receipt),
"body": canonical_json(receipt),
},
)
if not isinstance(response, dict) or not response.get("id"):
raise ReadinessError("State Hub returned an invalid message receipt")
return response
def show(contract: dict[str, Any]) -> dict[str, Any]:
return {
"interface": BROKER_INTERFACE,
"version": 1,
"owner": BROKER_OWNER,
"engagement_id": contract["engagement_id"],
"target_id": contract["target"]["id"],
"projection_contract_digest": contract_digest(contract),
"projection_receipt_interface": PROJECTION_INTERFACE,
"required_roles": ["attacker", "owner"],
"mount_paths": sorted(item["mount_path"] for item in contract["identities"]),
"adapter_path": str(DEFAULT_ADAPTER),
"test_path": str(DEFAULT_TEST),
"approve_command": (
"python3 scripts/wp0025-broker-readiness.py approve --contract <contract.json> "
"--consumer-root <whitehat-security> --reviewer <stable-id>"
),
"live_mutation_authorized": False,
"secret_values_observed": False,
}
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("command", choices=["show", "verify", "approve", "request-changes", "status"])
parser.add_argument("--contract", type=Path, required=True)
parser.add_argument("--consumer-root", type=Path)
parser.add_argument("--reviewer")
parser.add_argument("--note")
parser.add_argument("--state-hub", default="http://127.0.0.1:8000")
args = parser.parse_args()
try:
contract = validate_projection_contract(load_json(args.contract))
if args.command == "show":
result = show(contract)
elif args.command == "status":
try:
receipt = current_broker_receipt(contract, args.state_hub)
except ContractError as exc:
result = {
"ready": False,
"engagement_id": contract["engagement_id"],
"reason": str(exc),
"secret_values_observed": False,
}
else:
result = {
"ready": True,
"engagement_id": contract["engagement_id"],
"receipt": receipt,
"secret_values_observed": False,
}
elif args.command == "verify":
if not args.consumer_root:
raise ReadinessError("verify requires --consumer-root")
result = verify_adapter(args.consumer_root)
elif args.command == "approve":
if not args.consumer_root or not args.reviewer:
raise ReadinessError("approve requires --consumer-root and --reviewer")
verification = verify_adapter(args.consumer_root)
receipt = build_approval(contract, args.reviewer, verification)
posted = post_receipt(receipt, args.state_hub)
result = {"submitted": True, "message_id": posted["id"], "receipt": receipt}
else:
if not args.reviewer or not args.note:
raise ReadinessError("request-changes requires --reviewer and --note")
receipt = build_change_request(contract, args.reviewer, args.note)
posted = post_receipt(receipt, args.state_hub)
result = {"submitted": True, "message_id": posted["id"], "receipt": receipt}
print(json.dumps(result, indent=2, sort_keys=True))
return 0
except (ContractError, OSError, ReadinessError) as exc:
print(f"broker readiness failed: {exc}", file=sys.stderr)
return 1
if __name__ == "__main__":
raise SystemExit(main())