CUST-WP-0075: dedicated core-hub Forgejo read identity CCR draft
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 3775@bnt-lap001
Assistant-Session: cfacb6a5-ad5b-4f79-ac67-053e3b0d34a3
This commit is contained in:
codex 2026-10-03 08:27:56 +02:00
parent 6bb2f54b5f
commit 401765f184
7 changed files with 846 additions and 0 deletions

View file

@ -0,0 +1,143 @@
id: CCR-2026-0031
kind: credential-change-request
schema_version: 1
request_type: workload-kv-read
title: Core Hub repo-manager publisher Forgejo private-source read token lane
status: proposed
created: '2026-10-03'
updated: '2026-10-03'
requester:
agent: claude (rapp-core-hub repo agent, dispatched by the-custodian under CUST-WP-0075)
reason: RAPPCOREHUB-WP-0003-T04. The repo-manager publisher in namespace core-hub
reads the 123-entry repository registry from Forgejo anonymously; nine private
sources return HTTP 404, so admission cannot reach 123 accepted / 0 errors.
The Forgejo admin PAT, any personal SSH key or PAT, and the policy-nexus-source
token (CCR-2026-0014) are prohibited for this workload. A dedicated identity
with read:repository only is required.
review:
required: true
required_approvers:
- platform-operator
- core-hub-owner
comments: []
target:
domain: infotech
tenant: core-hub
workload: core-hub-repo-manager-publisher
rapp: rapp-core-hub
environment: production
purpose: Hold a dedicated Forgejo PAT scoped exactly read:repository and project
it through External Secrets into namespace core-hub as the Secret that the
rapp-core-hub publisher mounts read-only at /var/run/secrets/repo-manager/forgejo/token.
openbao:
mount: platform
kv_path: platform/workloads/core-hub/forgejo-source-read
fields:
- FORGEJO_SOURCE_TOKEN
- API_USER
- API_BASE_URL
- TOKEN_SCOPES
- GENERATED_AT
policy_name: workload-kv-read-core-hub-forgejo-source
policy_file: openbao/policies/workload-kv-read-core-hub-forgejo-source.hcl
auth:
method: kubernetes
mount: kubernetes
role: external-secrets-core-hub-forgejo-source
bound_claims:
service_account_names:
- external-secrets
service_account_namespaces:
- external-secrets
bound_claims_confirmed: false
policies:
- workload-kv-read-core-hub-forgejo-source
ttl: 15m
access_frontdoor:
type: ops-warden
catalog_id: core-hub-forgejo-source-read
selector: core hub repo-manager publisher Forgejo private source repository read token
readiness: pending-review
resolvable: false
delivery:
surface: external-secrets
bootstrap_command: warden access openbao-platform-admin-login --exec -- scripts/openbao-bootstrap-core-hub-source.sh
target: ClusterSecretStore openbao-core-hub-forgejo-source (conditions limited to
namespace core-hub) feeding ExternalSecret core-hub/repo-manager-forgejo-source,
which materializes Secret repo-manager-forgejo-source with the single key token
(remoteRef workloads/core-hub/forgejo-source-read property FORGEJO_SOURCE_TOKEN),
explicit empty target metadata template, creationPolicy Owner, deletionPolicy
Retain. rapp-core-hub then sets publisher.existingSecret to repo-manager-forgejo-source
with tokenKey token. Both manifests are drafted in this CCR only and land in
argocd/platform-addons/openbao-secretstore after approval and metadata apply.
forgejo_identity: core-hub-source
forgejo_team: core-hub-source-readers
forgejo_team_contract: Restricted, non-admin, private
service user (random unrecorded password) in org coulomb; team permission read, includes_all_repositories
true, can_create_org_repo false, repo.code read, every non-code unit none. PAT
name prefix core-hub-source-read-, scope exactly read:repository.
repositories: All repositories of Forgejo org coulomb (the registry's 123 entries
are all coulomb/*). Per-repository team membership was rejected because the
registry changes weekly and each addition would need a new attended session.
naming_basis: Follows the CCR-2026-0014 precedent (<workload>-source user, <workload>-source-readers
team). repo-manager publishes no service-user naming canon; its outstanding
identity-canon correction is a registry/Forgejo identity mismatch for one repository
and does not constrain this name.
provisioning_script: scripts/provision-core-hub-forgejo-source.py (silent, idempotent;
admin token reaches it only through warden access forgejo-admin-api-token --exec)
risk:
classification: high
notes:
- Read-only at Forgejo, but disclosure grants estate-wide private source read
for org coulomb. No package, repository-write, organization-admin, user-write,
Actions, cluster, or deployment authority.
- Distinct from CCR-2026-0014; the policy-nexus-source identity and its token are
never reused, so revoking either lane leaves the other intact.
- The publisher calls the in-cluster Forgejo service over plain HTTP; the token
stays on the cluster network, restricted by the core-hub NetworkPolicy egress
to the forgejo namespace. TLS to the in-cluster service is out of scope here.
- Any ExternalSecret in namespace core-hub can reference the store; the namespace
is single-tenant (rapp-core-hub) and the store is not shared with other namespaces.
- Forgejo PATs carry no server-side expiry; the expiry is enforced by the rotation
schedule below and by GENERATED_AT metadata.
verification:
positive:
- The minted token metadata reports exactly read:repository; it resolves a branch
and fetches one archive of a private coulomb repository, checked without printing
the value.
- ExternalSecret core-hub/repo-manager-forgejo-source reports Ready and the
Secret exists (checked only with the safe secret_annotation_maintenance helper,
never by reading values).
- make verify-publisher in rapp-core-hub reports 123 accepted / 0 errors once
the separate identity-canon registry correction has shipped.
negative:
- The PAT cannot create, update, or delete repository content, write packages,
or read Actions secrets; /admin/users is denied.
- The effective permission of core-hub-source on a probe repository is read.
- The external-secrets role cannot read platform/workloads/policy-nexus/forgejo-source-read
or any sibling path; a default OpenBao identity cannot read this path.
- Removing the Secret makes publisher private-source reads fail with 404 again
(fail closed), not fall back to another credential.
activation_conditions:
- Platform operator and Core Hub owner approve this CCR.
- Kubernetes auth role binding for the external-secrets service account is confirmed
(bound_claims_confirmed set true by confirm-binding).
- Delegated metadata apply writes the policy and the auth role.
- Attended provisioning mints the PAT and writes it to OpenBao from stdin, without
logs, chat, Git, State Hub, argv, or persistent temp files.
- The ClusterSecretStore and ExternalSecret are committed and synced; rapp-core-hub
mounts the Secret and passes the positive and negative checks above.
- ops-warden catalog entry core-hub-forgejo-source-read is added and reviewed.
evidence: []
lifecycle:
deactivate: Delete ExternalSecret and Secret in core-hub, unset publisher.existingSecret,
revoke the PAT, disable the auth role, and leave private sources failing closed.
rotate: Every 180 days or on owner change. Mint a replacement read:repository PAT
for core-hub-source, write it to OpenBao from stdin, let ESO refresh (5m), pass
make verify-publisher, then revoke the predecessor PAT and record evidence.
compromised: Revoke the PAT immediately, delete the Secret, inspect Forgejo access
logs for core-hub-source reads, rotate through this lane, record bounded incident
evidence.
state_hub:
workplan_id: RAPPCOREHUB-WP-0003
task_id: RAPPCOREHUB-WP-0003-T04

View file

@ -13,6 +13,7 @@ KeyCape approval-client admission below, which carries executable CCRs
| [State Hub preflight signing](state-hub-preflight-signing.md) | RPF-WP-0034 (finished) / RPF-WP-0035-T04 | FLEX-WP-0020-T05; STATE-WP-0085-T09 delivered | Deployment binding, owner-approved custody and rotation window |
| [KeyCape approval-engine clients](keycape-approval-clients.md) | RPF-WP-0035-T05 | KEY-WP-0013-T02; GLAS-WP-0015 | Owner approval of CCR-2026-0017/0018, attended founder window, KeyCape reading build |
| [Approval client-side readers](keycape-approval-client-side-readers.md) | RPF-WP-0035-T06 | SECRETS-WP-0009-T03; APPROVAL-WP-0002 | Reader 1: exact operator group claim; Reader 2: the presenting actor is unnamed by any owner source |
| [Core Hub Forgejo source read](core-hub-forgejo-source-read.md) (CCR-2026-0031, `proposed`) | CUST-WP-0075 / RAPPCOREHUB-WP-0003-T04 | rapp-core-hub publisher private sources | Approval + K8s binding confirmation; repo-manager identity-canon correction for 123/0 |
The completed design workplans are archived. RPF-WP-0035 is the canonical
queue for owner review, implementation, and live acceptance still required.

View file

@ -0,0 +1,115 @@
# Core Hub Forgejo source-read lane (CCR-2026-0031)
Status: **proposed**, drafted 2026-10-03 under CUST-WP-0075 for
`RAPPCOREHUB-WP-0003-T04`. Nothing here is approved, applied or provisioned.
No Forgejo, OpenBao or cluster object was created and no owner message other
than the CUST-WP-0075 reply was sent.
## Why
The repo-manager publisher in namespace `core-hub` reads the 123-entry
repository registry (all `coulomb/*`) anonymously. Nine private sources return
HTTP 404. T04 forbids the Forgejo admin PAT, personal keys or PATs, and the
`policy-nexus-source` token from CCR-2026-0014. This lane gives the publisher
its own read-only identity.
## Shape
| Item | Value |
| --- | --- |
| Forgejo user | `core-hub-source` (restricted, non-admin, private; random unrecorded password) |
| Forgejo team | `coulomb/core-hub-source-readers`: permission read, all repositories, `repo.code` read, every other unit none, cannot create repos |
| PAT | `core-hub-source-read-<UTC timestamp>`, scope exactly `read:repository` |
| OpenBao | `platform/workloads/core-hub/forgejo-source-read` fields `FORGEJO_SOURCE_TOKEN`, `API_USER`, `API_BASE_URL`, `TOKEN_SCOPES`, `GENERATED_AT` |
| Policy | `workload-kv-read-core-hub-forgejo-source` (`openbao/policies/…hcl`, read on data + metadata of that one path) |
| Auth | Kubernetes role `external-secrets-core-hub-forgejo-source`, bound to SA `external-secrets/external-secrets`, TTL 15m |
| Delivery | ESO store + ExternalSecret below → Secret `core-hub/repo-manager-forgejo-source` key `token` |
| Consumer | rapp-core-hub `publisher.existingSecret: repo-manager-forgejo-source` (already supported by the chart; mounts read-only at `/var/run/secrets/repo-manager/forgejo/token`) |
| Rotation | every 180 days or owner change; Forgejo PATs have no server-side expiry |
| Front door | ops-warden `core-hub-forgejo-source-read`, follow-up after approval (see below) |
Naming follows the CCR-2026-0014 precedent (`<workload>-source`,
`<workload>-source-readers`). repo-manager has no service-user naming canon.
Its outstanding `identity-canon` correction is a registry/Forgejo identity
mismatch for one repository. It blocks the 123/0 acceptance, not this
credential or its name.
## Draft manifests (land in `argocd/platform-addons/openbao-secretstore/` only after metadata apply)
```yaml
# CCR-2026-0031. Do not apply until approval and confirm-binding.
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: openbao-core-hub-forgejo-source
labels:
app.kubernetes.io/part-of: railiance-gitops
railiance-platform/component: external-secrets
app.kubernetes.io/name: core-hub
spec:
provider:
vault:
server: http://openbao.openbao.svc:8200
path: platform
version: v2
auth:
kubernetes:
mountPath: kubernetes
role: external-secrets-core-hub-forgejo-source
serviceAccountRef:
name: external-secrets
namespace: external-secrets
conditions:
- namespaces:
- core-hub
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: repo-manager-forgejo-source
namespace: core-hub
spec:
refreshInterval: 5m
secretStoreRef:
kind: ClusterSecretStore
name: openbao-core-hub-forgejo-source
target:
# Explicit target metadata prevents ESO copying source last-applied annotations.
template:
metadata: {}
name: repo-manager-forgejo-source
creationPolicy: Owner
deletionPolicy: Retain
data:
- secretKey: token
remoteRef:
key: workloads/core-hub/forgejo-source-read
property: FORGEJO_SOURCE_TOKEN
```
## Attended sequence (after approval)
1. Reviewers approve and confirm the binding (human acts, recorded by the
CCR tooling): `credential-change.py approve CCR-2026-0031 …` and
`credential-change.py confirm-binding CCR-2026-0031 …`.
2. Founder, in their own terminal (child is silent; read the diagnostic file
on failure, never the output):
`warden access openbao-platform-admin-login --exec -- scripts/openbao-bootstrap-core-hub-source.sh`.
This runs the delegated metadata apply (policy + auth role), then
`scripts/provision-core-hub-forgejo-source.py`, which gets the Forgejo admin
token only through `warden access forgejo-admin-api-token --exec`, mints the
PAT, verifies scope, private-repo read and denials, writes the value to
OpenBao from stdin, reads it back for equality, and records non-secret
evidence. Re-running when the PAT and the OpenBao value both exist is a no-op;
a half state fails closed and points to rotation. Failure diagnostics:
`/tmp/core-hub-source-bootstrap-diagnostic.json` (stage + bounded reason only).
3. Commit the two manifests above and let Argo sync; check presence only with
`scripts/secret_annotation_maintenance.py` through the authorized admin path.
4. rapp-core-hub sets `publisher.existingSecret`, deploys, runs
`make verify-publisher` (expects 123/0 once repo-manager ships the
identity-canon correction), then enables `connectCandidate`.
5. Add the ops-warden catalog entry `core-hub-forgejo-source-read`
(risk high, owner railiance-platform, path above, rotation steps from the
CCR) and move the CCR to `verified`/`active` with evidence.
Offline tests: `python3 -m unittest tests/test_core_hub_forgejo_source_provision.py`.

View file

@ -0,0 +1,9 @@
# CCR-2026-0031 (proposed). External Secrets reader for the dedicated Core Hub
# Forgejo read:repository token. Not applied until the CCR is approved.
path "platform/data/workloads/core-hub/forgejo-source-read" {
capabilities = ["read"]
}
path "platform/metadata/workloads/core-hub/forgejo-source-read" {
capabilities = ["read"]
}

View file

@ -0,0 +1,16 @@
#!/usr/bin/env bash
# Exact silent owner command for the CCR-2026-0031 attended bootstrap.
# Run only after CCR-2026-0031 is approved and its binding confirmed:
# warden access openbao-platform-admin-login --exec -- scripts/openbao-bootstrap-core-hub-source.sh
# Both steps are idempotent; on failure read /tmp/core-hub-source-bootstrap-diagnostic.json.
set -euo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "${REPO_DIR}"
python3 scripts/credential-change.py applier-apply CCR-2026-0031 \
--actor "attended operator via governed platform-admin lane" \
--confirm "DELEGATED APPLY CCR-2026-0031" \
--record-state-hub \
>/dev/null 2>&1
python3 scripts/provision-core-hub-forgejo-source.py >/dev/null 2>&1

View file

@ -0,0 +1,384 @@
#!/usr/bin/env python3
"""Attended, idempotent bootstrap for CCR-2026-0031 (silent on success).
Creates or verifies the restricted Forgejo service user ``core-hub-source`` and
its all-repository ``repo.code`` read team in org ``coulomb``, mints one PAT with
scope exactly ``read:repository``, verifies it, and writes it to OpenBao from
stdin. The value never reaches argv, stdout, stderr, Git, State Hub or files.
Idempotency: if a ``core-hub-source-read-*`` PAT and the OpenBao field both
exist, the run is a verified no-op. A PAT without the OpenBao value (or the
reverse) is drift and fails closed; use the CCR rotation procedure.
The Forgejo admin token reaches the inner process only through
``warden access forgejo-admin-api-token --exec``; OpenBao authority is the
ambient platform-admin session of the surrounding governed lane.
"""
from __future__ import annotations
import argparse
import base64
import datetime as dt
import json
import os
from pathlib import Path
import re
import secrets
import subprocess
import sys
import urllib.error
import urllib.parse
import urllib.request
CCR_ID = "CCR-2026-0031"
API_BASE_URL = "https://forgejo.coulomb.social"
API_ROOT = API_BASE_URL + "/api/v1"
BAO_PATH = "platform/workloads/core-hub/forgejo-source-read"
BAO_FIELD = "FORGEJO_SOURCE_TOKEN"
ORG = "coulomb"
USER = "core-hub-source"
TEAM = "core-hub-source-readers"
TOKEN_PREFIX = "core-hub-source-read-"
SCOPES = ["read:repository"]
ACTOR = "attended operator via governed platform-admin lane"
REPO_DIR = Path(__file__).resolve().parent.parent
DIAGNOSTIC_PATH = Path("/tmp/core-hub-source-bootstrap-diagnostic.json")
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 write_diagnostic(stage: str, error: Exception | None = None) -> None:
detail = str(error) if isinstance(error, ProvisionError) else "unexpected internal error"
DIAGNOSTIC_PATH.write_text(json.dumps({"stage": stage, "detail": detail}, sort_keys=True) + "\n")
DIAGNOSTIC_PATH.chmod(0o600)
def sanitize_warden_failure(result: subprocess.CompletedProcess[bytes]) -> str:
text = ((result.stdout or b"") + b"\n" + (result.stderr or b"")).decode("utf-8", errors="replace")
text = re.sub(r"[A-Za-z0-9_=.:-]{20,}", "<redacted>", text)
lines = [line.strip() for line in text.splitlines() if line.strip()]
return " | ".join(lines[-8:])[:600] or f"nested Warden exit {result.returncode}"
def api_request(
token: str,
method: str,
path: str,
*,
payload: dict[str, object] | None = None,
expected: tuple[int, ...] = (200,),
read_body: bool = True,
authorization: str | None = None,
) -> 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": authorization or 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.split('?')[0]} 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.split('?')[0]} 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 bao_has_token() -> bool:
return bao(["kv", "get", f"-field={BAO_FIELD}", BAO_PATH]).returncode == 0
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": f"{USER}@coulomb.social",
"full_name": "Core Hub repo-manager 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 team_satisfies_contract(team: dict[str, object]) -> bool:
units_map = team.get("units_map") or {}
if not isinstance(units_map, dict):
return False
return (
team.get("permission") == "read"
and team.get("includes_all_repositories") is True
and team.get("can_create_org_repo") is False
and units_map.get("repo.code") == "read"
and all(units_map.get(unit, "none") == "none" for unit in NON_CODE_UNITS)
)
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": f"Read-only source acquisition for the Core Hub repo-manager publisher ({CCR_ID})",
"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")
if not team_satisfies_contract(team):
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 find_private_probe(source_token: str) -> str:
for page in range(1, 6):
_, repos = api_request(source_token, "GET", f"/orgs/{ORG}/repos?limit=50&page={page}")
if not isinstance(repos, list):
raise ProvisionError("Forgejo returned an invalid repository list")
for repo in repos:
if isinstance(repo, dict) and repo.get("private") is True and isinstance(repo.get("name"), str):
return str(repo["name"])
if len(repos) < 50:
break
raise ProvisionError("source token sees no private repository in the org")
def verify_source_token(source_token: str, scopes: object) -> str:
if scopes != SCOPES:
raise ProvisionError("Forgejo returned scopes other than exactly read:repository")
probe = find_private_probe(source_token)
_, repo = api_request(source_token, "GET", f"/repos/{ORG}/{probe}")
branch_name = repo.get("default_branch") if isinstance(repo, dict) else None
if not isinstance(branch_name, str) or not branch_name:
raise ProvisionError("source token could not resolve the probe default branch")
_, branch = api_request(
source_token, "GET", f"/repos/{ORG}/{probe}/branches/{urllib.parse.quote(branch_name)}"
)
commit = branch.get("commit") if isinstance(branch, dict) else None
revision = commit.get("id") if isinstance(commit, dict) else None
if not isinstance(revision, str) or len(revision) != 40:
raise ProvisionError("source token returned an invalid probe revision")
api_request(
source_token,
"GET",
f"/repos/{ORG}/{probe}/archive/{urllib.parse.quote(revision)}.tar.gz",
read_body=False,
)
api_request(source_token, "GET", "/admin/users", expected=(403, 404))
api_request(source_token, "GET", f"/repos/{ORG}/{probe}/actions/secrets?limit=1", expected=(403, 404))
return probe
def verify_effective_permission(admin_token: str, repo: 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(token_name: str, probe: str) -> None:
detail = (
f"Restricted user {USER} and all-repository repo.code-read team {TEAM} verified; PAT {token_name} "
f"reports exactly read:repository; private probe {ORG}/{probe} branch and archive read passed; "
"effective permission read-only; Actions-secret and instance-admin probes denied; OpenBao field "
"written from stdin and read back without value output."
)
result = subprocess.run(
[
sys.executable, "scripts/credential-change.py", "record-evidence", CCR_ID,
"--actor", ACTOR, "--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) -> str:
"""Return "noop" when already provisioned, "minted" after a fresh mint."""
ensure_user(admin_token)
ensure_team(admin_token)
_, existing = api_request(admin_token, "GET", f"/users/{USER}/tokens?limit=100")
if not isinstance(existing, list):
raise ProvisionError("Forgejo returned an invalid token list")
has_pat = any(str(item.get("name", "")).startswith(TOKEN_PREFIX) for item in existing)
has_bao = bao_has_token()
if has_pat and has_bao:
return "noop"
if has_pat or has_bao:
raise ProvisionError("PAT and OpenBao value disagree; use the CCR rotation procedure")
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 = ""
service_password = ""
basic_authorization = ""
bao_written = False
committed = False
try:
service_password = secrets.token_urlsafe(48)
api_request(
admin_token,
"PATCH",
f"/admin/users/{USER}",
payload={
"password": service_password, "must_change_password": False, "restricted": True,
"admin": False, "allow_create_organization": False, "allow_git_hook": False,
"allow_import_local": False, "max_repo_creation": 0, "prohibit_login": False,
"active": True, "visibility": "private",
},
)
basic_authorization = "Basic " + base64.b64encode(f"{USER}:{service_password}".encode()).decode()
_, created = api_request(
"", "POST", f"/users/{USER}/tokens",
payload={"name": token_name, "scopes": SCOPES},
expected=(201,), authorization=basic_authorization,
)
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")
probe = verify_source_token(source_token, created.get("scopes"))
verify_effective_permission(admin_token, probe)
stored = bao(
[
"kv", "put", BAO_PATH, f"{BAO_FIELD}=-", f"API_USER={USER}",
f"API_BASE_URL={API_BASE_URL}", "TOKEN_SCOPES=read:repository",
f"GENERATED_AT={timestamp}",
],
stdin=source_token.encode(),
)
bao_written = True
if stored.returncode != 0:
raise ProvisionError("OpenBao source-token write failed")
readback = bao(["kv", "get", f"-field={BAO_FIELD}", BAO_PATH])
if readback.returncode != 0 or readback.stdout.strip() != source_token.encode():
raise ProvisionError("OpenBao source-token readback mismatch")
committed = True
record_evidence(token_name, probe)
return "minted"
except Exception:
if not committed:
if token_id is not None:
try:
api_request(
"", "DELETE", f"/users/{USER}/tokens/{token_id}",
expected=(204, 404), authorization=basic_authorization,
)
except Exception:
pass
if bao_written:
bao(["kv", "delete", BAO_PATH])
raise
finally:
source_token = ""
service_password = ""
basic_authorization = ""
def outer() -> int:
DIAGNOSTIC_PATH.unlink(missing_ok=True)
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,
)
if result.returncode != 0 and not DIAGNOSTIC_PATH.exists():
write_diagnostic("forgejo-admin-route", ProvisionError(sanitize_warden_failure(result)))
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:
write_diagnostic("inner", ProvisionError("Warden did not provide API_TOKEN"))
return 1
try:
provision(admin_token)
except Exception as error:
write_diagnostic("provision", error)
raise
DIAGNOSTIC_PATH.unlink(missing_ok=True)
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except Exception:
# Never surface provider bodies or credential material to the Warden lane.
raise SystemExit(1) from None

View file

@ -0,0 +1,178 @@
"""Offline tests for the CCR-2026-0031 attended bootstrap. No network, no bao."""
from __future__ import annotations
import importlib.util
from pathlib import Path
import subprocess
import unittest
from unittest import mock
SCRIPT = Path(__file__).resolve().parents[1] / "scripts" / "provision-core-hub-forgejo-source.py"
SPEC = importlib.util.spec_from_file_location("core_hub_source_provision", SCRIPT)
assert SPEC is not None and SPEC.loader is not None
MODULE = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(MODULE)
SENTINEL = "NONPRODUCTION_SOURCE_TOKEN_SENTINEL"
USER = MODULE.USER
def completed(args, rc=0, out=b""):
return subprocess.CompletedProcess(args, rc, out, b"")
class Harness:
"""Fake Forgejo + OpenBao recording every call."""
def __init__(self, *, pat_exists=False, bao_has=False, put_rc=0, readback=None):
self.calls: list[tuple[str, str, dict]] = []
self.bao_calls: list[tuple[list[str], bytes | None]] = []
self.pat_exists = pat_exists
self.bao_has = bao_has
self.put_rc = put_rc
self.readback = SENTINEL.encode() if readback is None else readback
def api(self, token, method, path, **kwargs):
self.calls.append((method, path, kwargs))
if method == "GET" and path == f"/users/{USER}/tokens?limit=100":
return 200, ([{"name": MODULE.TOKEN_PREFIX + "20260101T000000Z"}] if self.pat_exists else [])
if method == "PATCH" and path == f"/admin/users/{USER}":
return 200, {"login": USER}
if method == "POST" and path == f"/users/{USER}/tokens":
return 201, {"id": 73, "sha1": SENTINEL, "scopes": ["read:repository"]}
if method == "DELETE" and path == f"/users/{USER}/tokens/73":
return 204, None
raise AssertionError((method, path))
def bao(self, args, *, stdin=None):
self.bao_calls.append((args, stdin))
if args[:2] == ["kv", "get"]:
if not any(a[:2] == ["kv", "put"] for a, _ in self.bao_calls):
return completed(args, 0 if self.bao_has else 2)
return completed(args, 0, self.readback + b"\n")
if args[:2] == ["kv", "put"]:
return completed(args, self.put_rc)
return completed(args)
def run(self):
with (
mock.patch.object(MODULE, "ensure_user"),
mock.patch.object(MODULE, "ensure_team", return_value=7),
mock.patch.object(MODULE, "verify_source_token", return_value="private-probe"),
mock.patch.object(MODULE, "verify_effective_permission"),
mock.patch.object(MODULE, "record_evidence") as evidence,
mock.patch.object(MODULE, "api_request", side_effect=self.api),
mock.patch.object(MODULE, "bao", side_effect=self.bao),
):
self.evidence = evidence
return MODULE.provision("admin-token")
def revoked(self):
return any(m == "DELETE" for m, _, _ in self.calls)
class ContractTests(unittest.TestCase):
def test_identity_is_dedicated_and_not_policy_nexus(self) -> None:
self.assertEqual(MODULE.SCOPES, ["read:repository"])
self.assertEqual(MODULE.USER, "core-hub-source")
self.assertNotIn("policy-nexus", MODULE.USER + MODULE.TEAM + MODULE.BAO_PATH + MODULE.TOKEN_PREFIX)
self.assertEqual(MODULE.BAO_PATH, "platform/workloads/core-hub/forgejo-source-read")
self.assertNotIn("repo.code", MODULE.NON_CODE_UNITS)
def test_team_contract_rejects_any_write_or_extra_unit(self) -> None:
good = {
"permission": "read",
"includes_all_repositories": True,
"can_create_org_repo": False,
"units_map": {"repo.code": "read", **{u: "none" for u in MODULE.NON_CODE_UNITS}},
}
self.assertTrue(MODULE.team_satisfies_contract(good))
for change in (
{"permission": "write"},
{"can_create_org_repo": True},
{"includes_all_repositories": False},
{"units_map": {**good["units_map"], "repo.packages": "read"}},
):
with self.subTest(change=change):
self.assertFalse(MODULE.team_satisfies_contract({**good, **change}))
def test_broader_scope_rejected_before_network(self) -> None:
with mock.patch.object(MODULE, "api_request") as request:
with self.assertRaises(MODULE.ProvisionError):
MODULE.verify_source_token(SENTINEL, ["read:repository", "write:repository"])
request.assert_not_called()
def test_errors_never_carry_query_strings_or_unexpected_detail(self) -> None:
with mock.patch.object(MODULE.urllib.request, "urlopen") as urlopen:
urlopen.side_effect = MODULE.urllib.error.HTTPError("u", 500, "x", {}, None)
with self.assertRaises(MODULE.ProvisionError) as ctx:
MODULE.api_request("t", "GET", "/orgs/coulomb/repos?limit=50&page=1")
self.assertEqual(str(ctx.exception), "GET /orgs/coulomb/repos returned HTTP 500")
with mock.patch.object(MODULE, "DIAGNOSTIC_PATH") as path:
MODULE.write_diagnostic("provision", RuntimeError(SENTINEL))
self.assertNotIn(SENTINEL, path.write_text.call_args.args[0])
def test_private_probe_requires_a_private_repository(self) -> None:
with mock.patch.object(MODULE, "api_request", return_value=(200, [{"name": "pub", "private": False}])):
with self.assertRaises(MODULE.ProvisionError):
MODULE.find_private_probe(SENTINEL)
repos = [{"name": "pub", "private": False}, {"name": "priv", "private": True}]
with mock.patch.object(MODULE, "api_request", return_value=(200, repos)):
self.assertEqual(MODULE.find_private_probe(SENTINEL), "priv")
class ProvisionFlowTests(unittest.TestCase):
def test_fresh_mint_writes_value_only_via_stdin(self) -> None:
h = Harness()
self.assertEqual(h.run(), "minted")
put_args, put_stdin = next((a, s) for a, s in h.bao_calls if a[:2] == ["kv", "put"])
self.assertEqual(put_stdin, SENTINEL.encode())
self.assertIn(f"{MODULE.BAO_FIELD}=-", put_args)
self.assertNotIn(SENTINEL, " ".join(put_args))
self.assertFalse(h.revoked())
h.evidence.assert_called_once()
self.assertNotIn(SENTINEL, repr(h.evidence.call_args))
def test_rerun_when_fully_provisioned_is_noop(self) -> None:
h = Harness(pat_exists=True, bao_has=True)
self.assertEqual(h.run(), "noop")
self.assertFalse(any(m in ("POST", "PATCH", "DELETE") for m, _, _ in h.calls))
self.assertFalse(any(a[:2] == ["kv", "put"] for a, _ in h.bao_calls))
def test_drift_fails_closed_without_mutation(self) -> None:
for pat, has in ((True, False), (False, True)):
with self.subTest(pat=pat, bao=has):
h = Harness(pat_exists=pat, bao_has=has)
with self.assertRaises(MODULE.ProvisionError):
h.run()
self.assertFalse(any(m in ("POST", "PATCH") for m, _, _ in h.calls))
def test_bao_write_failure_revokes_pat_and_cleans_kv(self) -> None:
h = Harness(put_rc=2)
with self.assertRaises(MODULE.ProvisionError):
h.run()
self.assertTrue(h.revoked())
self.assertIn((["kv", "delete", MODULE.BAO_PATH], None), h.bao_calls)
def test_readback_mismatch_revokes_pat(self) -> None:
h = Harness(readback=b"other")
with self.assertRaises(MODULE.ProvisionError):
h.run()
self.assertTrue(h.revoked())
def test_inner_without_admin_token_fails_without_network(self) -> None:
with (
mock.patch.object(MODULE.sys, "argv", ["x", "--inner"]),
mock.patch.dict(MODULE.os.environ, {}, clear=True),
mock.patch.object(MODULE, "write_diagnostic") as diag,
mock.patch.object(MODULE, "provision") as provision,
):
self.assertEqual(MODULE.main(), 1)
provision.assert_not_called()
diag.assert_called_once()
if __name__ == "__main__":
unittest.main()