RPF-WP-0061-T01/T02: standard lane profile and self-service CCR submit/review-standard
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 306568@bnt-lap001 Assistant-Session: 8bdf8150-ea56-41e7-9132-13f04f6b0b91
This commit is contained in:
parent
4398f89e02
commit
b6148da443
5 changed files with 619 additions and 10 deletions
310
scripts/ccr_intake.py
Normal file
310
scripts/ccr_intake.py
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
"""Self-service CCR intake: submit, standard-lane review and id checks.
|
||||
|
||||
RPF-WP-0061-T02. Lets a client file a CCR and obtain the platform-owner review
|
||||
for in-profile lanes without a railiance-platform worker session. The rules
|
||||
live in registry/ccr-standard-profile.yaml; this module only evaluates them.
|
||||
Nothing here touches OpenBao or grants approval: the CCR stays `proposed`
|
||||
until the founder decision is synced.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import urllib.request
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
|
||||
REPO_DIR = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_PROFILE = REPO_DIR / "registry/ccr-standard-profile.yaml"
|
||||
CCR_ID_RE = re.compile(r"^CCR-(\d{4})-(\d{4})$")
|
||||
CCR_FILE_RE = re.compile(r"^(CCR-\d{4}-\d{4})(-[a-z0-9-]+)?\.ya?ml$")
|
||||
SLUG_RE = re.compile(r"^[a-z0-9][a-z0-9-]*$")
|
||||
TTL_UNITS = {"s": 1, "m": 60, "h": 3600, "d": 86400}
|
||||
PLATFORM_OWNER = "railiance-platform-owner"
|
||||
|
||||
|
||||
def load_profile(path: Path = DEFAULT_PROFILE) -> tuple[dict[str, Any], str]:
|
||||
raw = path.read_bytes()
|
||||
return yaml.safe_load(raw), "sha256:" + hashlib.sha256(raw).hexdigest()
|
||||
|
||||
|
||||
def ttl_seconds(value: Any) -> int | None:
|
||||
if not isinstance(value, str) or not re.match(r"^[1-9][0-9]*[smhd]$", value):
|
||||
return None
|
||||
return int(value[:-1]) * TTL_UNITS[value[-1]]
|
||||
|
||||
|
||||
def bound_list(auth: dict[str, Any], key: str) -> list[Any]:
|
||||
claims = auth.get("bound_claims") or {}
|
||||
return list(claims.get(key) or auth.get(f"bound_{key}") or [])
|
||||
|
||||
|
||||
def ccr_files(directory: Path) -> list[Path]:
|
||||
return sorted(p for p in directory.glob("CCR-*.y*ml") if CCR_FILE_RE.match(p.name))
|
||||
|
||||
|
||||
def reader_conflicts(
|
||||
ccr: dict[str, Any], directory: Path, profile: dict[str, Any], cc: Any
|
||||
) -> list[str]:
|
||||
kv_path = (ccr.get("openbao") or {}).get("kv_path")
|
||||
statuses = set(profile["reader_conflict_statuses"])
|
||||
conflicts = []
|
||||
for path in ccr_files(directory):
|
||||
other = cc.load_yaml(path)
|
||||
if other.get("id") == ccr.get("id"):
|
||||
continue
|
||||
if (other.get("openbao") or {}).get("kv_path") == kv_path and other.get("status") in statuses:
|
||||
conflicts.append(f"{other.get('id')} ({other.get('status')})")
|
||||
return conflicts
|
||||
|
||||
|
||||
def standard_violations(
|
||||
ccr: dict[str, Any], profile: dict[str, Any], directory: Path, cc: Any
|
||||
) -> list[str]:
|
||||
"""Return every reason the CCR falls outside the standard lane profile."""
|
||||
v: list[str] = []
|
||||
if ccr.get("request_type") != profile["request_type"]:
|
||||
return [f"request_type must be {profile['request_type']}"]
|
||||
openbao = ccr.get("openbao") or {}
|
||||
auth = openbao.get("auth") or {}
|
||||
want = profile["auth"]
|
||||
kv_path = openbao.get("kv_path") or ""
|
||||
if openbao.get("mount") != profile["mount"]:
|
||||
v.append(f"mount must be {profile['mount']}")
|
||||
if not any(re.match(p, kv_path) for p in profile["kv_path_patterns"]):
|
||||
v.append(f"kv_path {kv_path!r} is outside the standard workload/supplier prefixes")
|
||||
if openbao.get("metadata_read", True) is not profile["metadata_read"]:
|
||||
v.append("metadata_read must be false")
|
||||
if bool(openbao.get("token_capabilities_self", False)) is not profile["token_capabilities_self"]:
|
||||
v.append("token_capabilities_self is not standard")
|
||||
policy_name = openbao.get("policy_name") or ""
|
||||
if not policy_name.startswith(profile["policy_name_prefix"]):
|
||||
v.append(f"policy_name must start with {profile['policy_name_prefix']}")
|
||||
policy_file = openbao.get("policy_file")
|
||||
if not policy_file:
|
||||
v.append("policy_file is required")
|
||||
else:
|
||||
policy_path = REPO_DIR / policy_file
|
||||
if not policy_path.exists():
|
||||
v.append(f"policy_file {policy_file} does not exist")
|
||||
elif cc.normalized_policy_body(policy_path.read_text()) != cc.normalized_policy_body(
|
||||
cc.generated_policy_hcl(ccr)
|
||||
):
|
||||
v.append("policy_file differs from the generated policy")
|
||||
if auth.get("method") != want["method"] or auth.get("mount") != want["mount"]:
|
||||
v.append(f"auth must be {want['method']} on mount {want['mount']}")
|
||||
if not str(auth.get("role") or "").startswith(want["role_prefix"]):
|
||||
v.append(f"auth role must start with {want['role_prefix']}")
|
||||
if auth.get("audience") != want["audience"]:
|
||||
v.append(f"audience must be {want['audience']}")
|
||||
for key in ("ttl", "token_max_ttl", "token_explicit_max_ttl"):
|
||||
seconds = ttl_seconds(auth.get(key))
|
||||
if seconds is None or seconds > want["max_ttl_seconds"]:
|
||||
v.append(f"{key} must be set and at most {want['max_ttl_seconds']}s")
|
||||
if auth.get("token_no_default_policy") is not want["token_no_default_policy"]:
|
||||
v.append("token_no_default_policy must be true")
|
||||
if auth.get("policies") != [policy_name]:
|
||||
v.append("auth policies must be exactly [policy_name]")
|
||||
accounts = bound_list(auth, "service_account_names")
|
||||
namespaces = bound_list(auth, "service_account_namespaces")
|
||||
if len(accounts) != want["service_accounts"] or "*" in accounts:
|
||||
v.append(f"exactly {want['service_accounts']} exact service account required")
|
||||
if len(namespaces) != want["namespaces"] or "*" in namespaces:
|
||||
v.append(f"exactly {want['namespaces']} exact namespace required")
|
||||
forbidden = sorted(set(namespaces) & set(want["forbidden_namespaces"]))
|
||||
if forbidden:
|
||||
v.append(f"namespace {forbidden} is reserved for platform components")
|
||||
risk = (ccr.get("risk") or {}).get("classification")
|
||||
if risk not in profile["allowed_risk_classifications"]:
|
||||
v.append(f"risk classification {risk!r} needs a platform review")
|
||||
conflicts = reader_conflicts(ccr, directory, profile, cc)
|
||||
if conflicts:
|
||||
v.append(f"kv_path already has a reader: {', '.join(conflicts)}")
|
||||
return v
|
||||
|
||||
|
||||
def duplicate_ids(directory: Path, cc: Any) -> dict[str, list[str]]:
|
||||
seen: dict[str, list[str]] = defaultdict(list)
|
||||
for path in ccr_files(directory):
|
||||
seen[str(cc.load_yaml(path).get("id"))].append(path.name)
|
||||
prefix = CCR_FILE_RE.match(path.name).group(1)
|
||||
if prefix != cc.load_yaml(path).get("id"):
|
||||
seen[f"{prefix} (file name vs id)"].append(path.name)
|
||||
return {k: v for k, v in seen.items() if len(v) > 1 or "file name" in k}
|
||||
|
||||
|
||||
def next_ccr_id(directory: Path, year: int) -> str:
|
||||
numbers = [0]
|
||||
for path in ccr_files(directory):
|
||||
match = CCR_ID_RE.match(CCR_FILE_RE.match(path.name).group(1))
|
||||
if match and int(match.group(1)) == year:
|
||||
numbers.append(int(match.group(2)))
|
||||
return f"CCR-{year}-{max(numbers) + 1:04d}"
|
||||
|
||||
|
||||
def record_review(path: Path, ccr: dict[str, Any], violations: list[str], profile: dict[str, Any], digest: str, cc: Any) -> None:
|
||||
label = f"{profile['profile']} v{profile['version']} {digest}"
|
||||
if violations:
|
||||
decision = "needs_platform_review"
|
||||
comment = f"Outside {label}: " + "; ".join(violations)
|
||||
else:
|
||||
decision = "approved"
|
||||
comment = (f"Standard lane: passes {label}. Platform-owner source review only; "
|
||||
"founder approval and confirm-binding still required.")
|
||||
comments = ccr.setdefault("review", {}).setdefault("comments", [])
|
||||
comments.append({
|
||||
"at": cc.utc_now(),
|
||||
"reviewer": f"{PLATFORM_OWNER} (policy-as-code {profile['profile']} v{profile['version']})",
|
||||
"decision": decision,
|
||||
"comment": comment,
|
||||
})
|
||||
ccr["updated"] = datetime.now(timezone.utc).date().isoformat()
|
||||
cc.dump_yaml(path, ccr)
|
||||
|
||||
|
||||
def notify_platform(ccr: dict[str, Any], violations: list[str], base_url: str) -> None:
|
||||
payload = {
|
||||
"from_agent": (ccr.get("requester") or {}).get("agent", "ccr-intake"),
|
||||
"to_agent": "railiance-platform",
|
||||
"subject": f"{ccr['id']} needs platform review (outside standard lane profile)",
|
||||
"body": f"{ccr['id']} {ccr.get('title')}: " + "; ".join(violations),
|
||||
}
|
||||
request = urllib.request.Request(
|
||||
f"{base_url.rstrip('/')}/messages/", data=json.dumps(payload).encode(),
|
||||
headers={"Content-Type": "application/json"}, method="POST")
|
||||
with urllib.request.urlopen(request, timeout=15):
|
||||
pass
|
||||
|
||||
|
||||
def review_standard(path: Path, cc: Any, profile_path: Path = DEFAULT_PROFILE,
|
||||
write: bool = True) -> list[str]:
|
||||
ccr, _warnings = cc.validate_or_exit(path)
|
||||
if ccr.get("status") != "proposed":
|
||||
cc.fail(f"review-standard requires status proposed, got {ccr.get('status')}")
|
||||
profile, digest = load_profile(profile_path)
|
||||
violations = standard_violations(ccr, profile, path.parent, cc)
|
||||
if write:
|
||||
record_review(path, ccr, violations, profile, digest, cc)
|
||||
return violations
|
||||
|
||||
|
||||
def command_review_standard(args: argparse.Namespace, cc: Any) -> int:
|
||||
path = cc.resolve_ccr(args.ref)
|
||||
violations = review_standard(path, cc, Path(args.profile), write=not args.check)
|
||||
if not violations:
|
||||
print(f"[OK] {path.name}: standard lane; platform-owner review recorded"
|
||||
if not args.check else f"[OK] {path.name}: standard lane")
|
||||
return 0
|
||||
for item in violations:
|
||||
print(f"[NEEDS-PLATFORM] {path.name}: {item}")
|
||||
if args.notify and not args.check:
|
||||
notify_platform(cc.load_yaml(path), violations, args.state_hub_url)
|
||||
print("[OK] railiance-platform notified")
|
||||
return 2
|
||||
|
||||
|
||||
def command_check_ids(args: argparse.Namespace, cc: Any) -> int:
|
||||
duplicates = duplicate_ids(cc.ccr_dir(), cc)
|
||||
for key, names in duplicates.items():
|
||||
print(f"[ERROR] {key}: {names}")
|
||||
if not duplicates:
|
||||
print("[OK] CCR ids unique and matching file names")
|
||||
return 1 if duplicates else 0
|
||||
|
||||
|
||||
def git(*argv: str) -> None:
|
||||
subprocess.run(["git", "-C", str(REPO_DIR), *argv], check=True)
|
||||
|
||||
|
||||
def submit(args: argparse.Namespace, cc: Any) -> tuple[Path, list[str]]:
|
||||
profile, _digest = load_profile(Path(args.profile))
|
||||
if args.requester not in profile["submitters"]:
|
||||
cc.fail(f"requester {args.requester!r} may not self-submit; allowed: {profile['submitters']}")
|
||||
if not SLUG_RE.match(args.slug):
|
||||
cc.fail("slug must be lowercase letters, digits and dashes")
|
||||
source = cc.resolve_repo_path(args.ccr)
|
||||
errors: list[str] = []
|
||||
cc.scan_for_secrets(source, errors)
|
||||
if errors:
|
||||
cc.fail("; ".join(errors))
|
||||
ccr = cc.load_yaml(source)
|
||||
directory = cc.ccr_dir()
|
||||
today = datetime.now(timezone.utc).date()
|
||||
ccr_id = next_ccr_id(directory, today.year)
|
||||
ccr.update(id=ccr_id, status="proposed", created=today.isoformat(), updated=today.isoformat())
|
||||
ccr.setdefault("requester", {})["agent"] = args.requester
|
||||
ccr.setdefault("review", {})["comments"] = []
|
||||
openbao = ccr.get("openbao") or {}
|
||||
if args.policy:
|
||||
policy_name = openbao.get("policy_name") or cc.fail("openbao.policy_name is required")
|
||||
target = REPO_DIR / "openbao/policies" / f"{policy_name}.hcl"
|
||||
policy_text = cc.resolve_repo_path(args.policy).read_text()
|
||||
cc.reject_secret_text(policy_text, "policy file")
|
||||
if target.exists() and target.read_text() != policy_text:
|
||||
cc.fail(f"{target.relative_to(REPO_DIR)} exists with different content")
|
||||
target.write_text(policy_text)
|
||||
openbao["policy_file"] = str(target.relative_to(REPO_DIR))
|
||||
destination = directory / f"{ccr_id}-{args.slug}.yaml"
|
||||
cc.dump_yaml(destination, ccr)
|
||||
violations = review_standard(destination, cc, Path(args.profile))
|
||||
return destination, violations
|
||||
|
||||
|
||||
def command_submit(args: argparse.Namespace, cc: Any) -> int:
|
||||
destination, violations = submit(args, cc)
|
||||
ccr = cc.load_yaml(destination)
|
||||
print(f"[OK] filed {ccr['id']} -> {destination.relative_to(REPO_DIR)}")
|
||||
for item in violations:
|
||||
print(f"[NEEDS-PLATFORM] {item}")
|
||||
if args.commit or args.push:
|
||||
branch = f"ccr/{destination.stem}"
|
||||
git("switch", "-c", branch)
|
||||
paths = [str(destination.relative_to(REPO_DIR))]
|
||||
if ccr["openbao"].get("policy_file"):
|
||||
paths.append(ccr["openbao"]["policy_file"])
|
||||
git("add", *paths)
|
||||
git("commit", "-m", f"File {ccr['id']}: {ccr.get('title')}", "--", *paths)
|
||||
if args.push:
|
||||
git("push", "-u", "origin", branch)
|
||||
if violations and args.notify:
|
||||
notify_platform(ccr, violations, args.state_hub_url)
|
||||
return 0
|
||||
|
||||
|
||||
def add_parsers(sub: Any, cc: Any, state_hub_url: str) -> None:
|
||||
review = sub.add_parser(
|
||||
"review-standard",
|
||||
help="Check a proposed CCR against the standard lane profile; record the platform-owner review",
|
||||
)
|
||||
review.add_argument("ref")
|
||||
review.add_argument("--profile", default=str(DEFAULT_PROFILE))
|
||||
review.add_argument("--check", action="store_true", help="report only; do not write the review")
|
||||
review.add_argument("--notify", action="store_true", help="message railiance-platform when out of profile")
|
||||
review.add_argument("--state-hub-url", default=state_hub_url)
|
||||
review.set_defaults(func=lambda args: command_review_standard(args, cc))
|
||||
|
||||
check_ids = sub.add_parser("check-ids", help="Fail on duplicate CCR ids or id/file name mismatch")
|
||||
check_ids.set_defaults(func=lambda args: command_check_ids(args, cc))
|
||||
|
||||
submit_p = sub.add_parser(
|
||||
"submit",
|
||||
help="File a client CCR draft under the next CCR id and run the standard review",
|
||||
)
|
||||
submit_p.add_argument("--ccr", required=True, help="client CCR draft (non-secret YAML)")
|
||||
submit_p.add_argument("--policy", help="client policy HCL; copied to openbao/policies/<policy_name>.hcl")
|
||||
submit_p.add_argument("--slug", required=True)
|
||||
submit_p.add_argument("--requester", required=True)
|
||||
submit_p.add_argument("--profile", default=str(DEFAULT_PROFILE))
|
||||
submit_p.add_argument("--commit", action="store_true", help="commit on a new ccr/<id>-<slug> branch")
|
||||
submit_p.add_argument("--push", action="store_true", help="commit and push the ccr/* branch")
|
||||
submit_p.add_argument("--notify", action="store_true")
|
||||
submit_p.add_argument("--state-hub-url", default=state_hub_url)
|
||||
submit_p.set_defaults(func=lambda args: command_submit(args, cc))
|
||||
|
|
@ -2384,6 +2384,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
scaffold.add_argument("--catalog-manifest")
|
||||
scaffold.set_defaults(func=lambda args: command_scaffold(args, generated_policy_hcl, auth_payload))
|
||||
|
||||
from ccr_intake import add_parsers as add_intake_parsers
|
||||
add_intake_parsers(sub, sys.modules[__name__], os.environ.get("STATE_HUB_URL", "http://127.0.0.1:8000"))
|
||||
|
||||
from provisioning_window import command_window
|
||||
window = sub.add_parser("window-checklist", help="Render an offline attended-window preparation checklist; never execute")
|
||||
window.add_argument("--window-file", required=True)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue