railiance-platform/scripts/provision-policy-nexus-forgejo-source.py
codex 1b85a3ef3d
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Add attended Policy Nexus source bootstrap
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a058f3-8ba0-7692-a042-9a870fc3d663
2026-08-31 23:24:05 +02:00

375 lines
13 KiB
Python
Executable file

#!/usr/bin/env python3
"""Attended first bootstrap for CCR-2026-0014 (silent on success)."""
from __future__ import annotations
import argparse
import datetime as dt
import json
import os
from pathlib import Path
import secrets
import subprocess
import sys
import urllib.error
import urllib.parse
import urllib.request
API_ROOT = "https://forgejo.coulomb.social/api/v1"
BAO_PATH = "platform/workloads/policy-nexus/forgejo-source-read"
ORG = "coulomb"
REPO = "policy-nexus"
USER = "policy-nexus-source"
TEAM = "policy-nexus-source-readers"
SECRET = "FORGEJO_SOURCE_TOKEN"
TOKEN_PREFIX = "policy-nexus-source-read-"
SCOPES = ["read:repository"]
REPO_DIR = Path(__file__).resolve().parent.parent
NON_CODE_UNITS = (
"repo.actions",
"repo.packages",
"repo.issues",
"repo.ext_issues",
"repo.wiki",
"repo.pulls",
"repo.releases",
"repo.projects",
"repo.ext_wiki",
)
class ProvisionError(RuntimeError):
pass
def api_request(
token: str,
method: str,
path: str,
*,
payload: dict[str, object] | None = None,
expected: tuple[int, ...] = (200,),
read_body: bool = True,
) -> tuple[int, object | None]:
data = None if payload is None else json.dumps(payload).encode()
request = urllib.request.Request(
API_ROOT + path,
data=data,
method=method,
headers={
"Authorization": f"token {token}",
"Accept": "application/json",
**({"Content-Type": "application/json"} if data is not None else {}),
},
)
try:
with urllib.request.urlopen(request, timeout=30) as response:
status = response.status
body = response.read() if read_body else response.read(1)
except urllib.error.HTTPError as error:
status = error.code
body = b""
if status not in expected:
raise ProvisionError(f"{method} {path} returned HTTP {status}")
if not read_body or not body:
return status, None
try:
return status, json.loads(body)
except json.JSONDecodeError as error:
raise ProvisionError(f"{method} {path} returned invalid JSON") from error
def bao(args: list[str], *, stdin: bytes | None = None) -> subprocess.CompletedProcess[bytes]:
return subprocess.run(
["bao", *args],
cwd=REPO_DIR,
input=stdin,
capture_output=True,
check=False,
timeout=60,
)
def require_empty_destinations(admin_token: str, existing_tokens: list[dict[str, object]]) -> None:
if any(str(item.get("name", "")).startswith(TOKEN_PREFIX) for item in existing_tokens):
raise ProvisionError("a Policy Nexus source PAT already exists; use the rotation procedure")
_, action_secrets = api_request(
admin_token,
"GET",
f"/repos/{ORG}/{REPO}/actions/secrets?limit=100",
)
if not isinstance(action_secrets, list):
raise ProvisionError("Forgejo returned an invalid Actions-secret list")
if any(item.get("name") == SECRET for item in action_secrets):
raise ProvisionError("FORGEJO_SOURCE_TOKEN already exists; use the rotation procedure")
if bao(["kv", "get", "-field=FORGEJO_SOURCE_TOKEN", BAO_PATH]).returncode == 0:
raise ProvisionError("the OpenBao destination already contains a source token")
def ensure_user(admin_token: str) -> None:
status, user = api_request(admin_token, "GET", f"/users/{USER}", expected=(200, 404))
if status == 404:
_, user = api_request(
admin_token,
"POST",
"/admin/users",
payload={
"username": USER,
"email": "policy-nexus-source@coulomb.social",
"full_name": "Policy Nexus source reader",
"password": secrets.token_urlsafe(48),
"restricted": True,
"must_change_password": False,
"send_notify": False,
"visibility": "private",
},
expected=(201,),
)
if not isinstance(user, dict):
raise ProvisionError("Forgejo returned an invalid service user")
if user.get("login") != USER or user.get("restricted") is not True or user.get("is_admin") is True:
raise ProvisionError("service user does not satisfy the restricted non-admin contract")
def ensure_team(admin_token: str) -> int:
_, teams = api_request(admin_token, "GET", f"/orgs/{ORG}/teams?limit=100")
if not isinstance(teams, list):
raise ProvisionError("Forgejo returned an invalid team list")
team = next((item for item in teams if item.get("name") == TEAM), None)
if team is None:
units_map = {unit: "none" for unit in NON_CODE_UNITS}
units_map["repo.code"] = "read"
_, team = api_request(
admin_token,
"POST",
f"/orgs/{ORG}/teams",
payload={
"name": TEAM,
"description": "Read-only source acquisition for Policy Nexus publication",
"permission": "read",
"includes_all_repositories": True,
"can_create_org_repo": False,
"units": ["repo.code"],
"units_map": units_map,
},
expected=(201,),
)
if not isinstance(team, dict) or not isinstance(team.get("id"), int):
raise ProvisionError("Forgejo returned an invalid source-reader team")
units_map = team.get("units_map") or {}
if (
team.get("permission") != "read"
or team.get("includes_all_repositories") is not True
or team.get("can_create_org_repo") is not False
or units_map.get("repo.code") != "read"
or any(units_map.get(unit, "none") != "none" for unit in NON_CODE_UNITS)
):
raise ProvisionError("source-reader team does not satisfy the least-privilege contract")
team_id = int(team["id"])
api_request(
admin_token,
"PUT",
f"/teams/{team_id}/members/{USER}",
expected=(204,),
)
return team_id
def verify_source_token(source_token: str, scopes: object) -> None:
if scopes != SCOPES:
raise ProvisionError("Forgejo returned scopes broader than read:repository")
_, branch = api_request(source_token, "GET", f"/repos/{ORG}/{REPO}/branches/main")
if not isinstance(branch, dict) or not isinstance(branch.get("commit"), dict):
raise ProvisionError("source token could not resolve the main branch")
revision = branch["commit"].get("id")
if not isinstance(revision, str) or len(revision) != 40:
raise ProvisionError("source token returned an invalid main revision")
api_request(
source_token,
"GET",
f"/repos/{ORG}/{REPO}/archive/{urllib.parse.quote(revision)}.tar.gz",
expected=(200,),
read_body=False,
)
api_request(source_token, "GET", "/admin/users", expected=(403, 404))
api_request(
source_token,
"GET",
f"/repos/{ORG}/{REPO}/actions/secrets?limit=1",
expected=(403, 404),
)
def verify_effective_permission(admin_token: str) -> None:
_, permission = api_request(
admin_token,
"GET",
f"/repos/{ORG}/{REPO}/collaborators/{USER}/permission",
)
if not isinstance(permission, dict) or permission.get("permission") != "read":
raise ProvisionError("service user effective repository permission is not read-only")
def record_evidence(run_id: int | None, token_name: str) -> None:
detail = (
f"Restricted user {USER} and all-repository repo.code-read team {TEAM} verified; "
f"PAT {token_name} reports exactly read:repository; archive read passed; effective repository "
f"permission is read-only; Actions-secret read and instance-admin probes were denied; OpenBao and repository "
f"Actions secret were populated without value output; workflow dispatch run_id={run_id}."
)
result = subprocess.run(
[
sys.executable,
"scripts/credential-change.py",
"record-evidence",
"CCR-2026-0014",
"--actor",
"attended operator via governed platform-admin lane",
"--kind",
"forgejo_source_bootstrap",
"--result",
"passed",
"--detail",
detail,
"--status",
"applied",
"--record-state-hub",
],
cwd=REPO_DIR,
capture_output=True,
check=False,
timeout=120,
)
if result.returncode != 0:
raise ProvisionError("credential evidence recording failed")
def provision(admin_token: str) -> None:
ensure_user(admin_token)
ensure_team(admin_token)
verify_effective_permission(admin_token)
_, existing_tokens = api_request(admin_token, "GET", f"/users/{USER}/tokens?limit=100")
if not isinstance(existing_tokens, list):
raise ProvisionError("Forgejo returned an invalid token list")
require_empty_destinations(admin_token, existing_tokens)
timestamp = dt.datetime.now(dt.timezone.utc).strftime("%Y%m%dT%H%M%SZ")
token_name = TOKEN_PREFIX + timestamp
token_id: int | None = None
source_token = ""
bao_written = False
activated = False
try:
_, created = api_request(
admin_token,
"POST",
f"/users/{USER}/tokens",
payload={"name": token_name, "scopes": SCOPES},
expected=(201,),
)
if not isinstance(created, dict) or not isinstance(created.get("id"), int):
raise ProvisionError("Forgejo returned an invalid access-token record")
token_id = int(created["id"])
source_token = str(created.get("sha1") or "")
if not source_token:
raise ProvisionError("Forgejo did not return the new access token")
verify_source_token(source_token, created.get("scopes"))
stored = bao(
[
"kv",
"put",
BAO_PATH,
"FORGEJO_SOURCE_TOKEN=-",
f"API_USER={USER}",
"API_BASE_URL=https://forgejo.coulomb.social",
"TOKEN_SCOPES=read:repository",
f"GENERATED_AT={timestamp}",
],
stdin=source_token.encode(),
)
if stored.returncode != 0:
raise ProvisionError("OpenBao source-token write failed")
bao_written = True
readback = bao(["kv", "get", "-field=FORGEJO_SOURCE_TOKEN", BAO_PATH])
if readback.returncode != 0 or readback.stdout.strip() != source_token.encode():
raise ProvisionError("OpenBao source-token readback mismatch")
api_request(
admin_token,
"PUT",
f"/repos/{ORG}/{REPO}/actions/secrets/{SECRET}",
payload={"data": source_token},
expected=(201, 204),
)
activated = True
_, dispatch = api_request(
admin_token,
"POST",
f"/repos/{ORG}/{REPO}/actions/workflows/publish-image.yaml/dispatches",
payload={"ref": "main", "return_run_info": True},
expected=(201, 204),
)
run_id = dispatch.get("id") if isinstance(dispatch, dict) else None
record_evidence(run_id if isinstance(run_id, int) else None, token_name)
except Exception:
if not activated and token_id is not None:
try:
api_request(
admin_token,
"DELETE",
f"/users/{USER}/tokens/{token_id}",
expected=(204, 404),
)
except Exception:
pass
if bao_written and not activated:
bao(["kv", "delete", BAO_PATH])
raise
finally:
source_token = ""
def outer() -> int:
result = subprocess.run(
[
"warden",
"access",
"forgejo-admin-api-token",
"--field",
"API_TOKEN",
"--exec",
"--",
sys.executable,
str(Path(__file__).resolve()),
"--inner",
],
cwd=REPO_DIR,
capture_output=True,
check=False,
timeout=900,
)
return result.returncode
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--inner", action="store_true", help=argparse.SUPPRESS)
args = parser.parse_args()
if not args.inner:
return outer()
admin_token = os.environ.get("API_TOKEN", "")
if not admin_token:
raise ProvisionError("Warden did not provide API_TOKEN")
provision(admin_token)
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except Exception:
# The surrounding Warden lane records only the failure and never receives
# provider response bodies or credential material.
raise SystemExit(1) from None