Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a137ca-5c77-7a71-b9ae-23ad9b0b5684
178 lines
7.5 KiB
Python
178 lines
7.5 KiB
Python
"""Owner-prepared, receipt-only native acquisition check for CUST81 revision 1.
|
|
|
|
Run inside the hub environment after a separately approved credential rollout:
|
|
python -m reuse_surface.private_source_verify --proposal FILE --disclosure FILE
|
|
--consent FILE
|
|
No credentials are accepted as arguments. No bodies or exception text are emitted.
|
|
This command does not register sources or use the federation cache.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import re
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Any
|
|
from urllib.parse import quote
|
|
|
|
import yaml
|
|
from jsonschema import Draft202012Validator
|
|
|
|
from reuse_surface import federation
|
|
from reuse_surface.registry import load_schema
|
|
|
|
PROPOSAL_SHA256 = "7819eb51d249f5c7aff4df9fb520afcfb76603b05a4104439499102b6c20af99"
|
|
DISCLOSURE_SHA256 = "46ac4fe49a08054cc215aade2fbaf3b36ee334520997857ed84f530b52da9d35"
|
|
CONSENT_SHA256 = "4a4602ad926e3af855d6ddba4731430d33e4a7432c507ba0a5193b9f43056b52"
|
|
AUTH_ENV = "REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN"
|
|
REPOSITORIES = frozenset({
|
|
"rapp-openbao", "rapp-policy-nexus", "rapp-postgres", "rapp-sbom-nexus",
|
|
"rapp-telemetry", "rapp-tenant-engine", "rapp-user-engine",
|
|
})
|
|
|
|
|
|
def _sha(content: bytes) -> str:
|
|
return hashlib.sha256(content).hexdigest()
|
|
|
|
|
|
def _load(path: Path, expected: str) -> dict[str, Any]:
|
|
data = path.read_bytes()
|
|
if _sha(data) != expected:
|
|
raise ValueError("packet_digest_mismatch")
|
|
return json.loads(data)
|
|
|
|
|
|
def _raw_url(repo: str, commit: str, path: str) -> str:
|
|
if not re.fullmatch(r"[0-9a-f]{40}", commit):
|
|
raise ValueError("invalid_commit")
|
|
if not re.fullmatch(r"registry/(indexes|capabilities)/[a-zA-Z0-9._-]+\.(yaml|md)", path):
|
|
raise ValueError("invalid_metadata_path")
|
|
return (
|
|
f"https://forgejo.coulomb.social/api/v1/repos/coulomb/{repo}/raw/"
|
|
f"{quote(path, safe='/')}?ref={commit}"
|
|
)
|
|
|
|
|
|
def _prepared_sources(proposal_path: Path, disclosure_path: Path, consent_path: Path):
|
|
proposal = _load(proposal_path, PROPOSAL_SHA256)
|
|
disclosure = _load(disclosure_path, DISCLOSURE_SHA256)
|
|
consent = _load(consent_path, CONSENT_SHA256)
|
|
if (
|
|
consent["consent_status"] != "approved"
|
|
or consent["proposal_sha256"] != DISCLOSURE_SHA256
|
|
or consent["proposal_id"] != disclosure["id"]
|
|
or consent["proposal_revision"] != disclosure["revision"]
|
|
or set(consent["repositories"]) != REPOSITORIES
|
|
):
|
|
raise ValueError("invalid_consent")
|
|
sources = proposal["sources"]
|
|
approved = {source["repo"]: source for source in disclosure["sources"]}
|
|
if len(sources) != 7 or {source["repo"] for source in sources} != REPOSITORIES:
|
|
raise ValueError("invalid_source_scope")
|
|
for source in sources:
|
|
owner = approved[source["repo"]]
|
|
files = {item["path"]: item["sha256"] for item in owner["files"]}
|
|
if (
|
|
source["consented_commit"] != owner["commit"]
|
|
or source["capability_ids"] != owner["capability_ids"]
|
|
or source["index_sha256"] != files["registry/indexes/capabilities.yaml"]
|
|
or source["auth_env"] != AUTH_ENV
|
|
or source["native_raw_url"] != _raw_url(
|
|
source["repo"], owner["commit"], "registry/indexes/capabilities.yaml"
|
|
)
|
|
):
|
|
raise ValueError("source_not_consented")
|
|
yield source, files
|
|
|
|
|
|
def verify(proposal_path: Path, disclosure_path: Path, consent_path: Path) -> dict[str, Any]:
|
|
receipt: dict[str, Any] = {
|
|
"id": "CUST81-PRIVATE-NATIVE-FETCH-RECEIPT-R1",
|
|
"checked_at": datetime.now(timezone.utc).isoformat(),
|
|
"proposal_sha256": PROPOSAL_SHA256,
|
|
"disclosure_sha256": DISCLOSURE_SHA256,
|
|
"consent_sha256": CONSENT_SHA256,
|
|
"cache_used": False,
|
|
"all_verified": False,
|
|
"sources": [],
|
|
}
|
|
try:
|
|
# Exhaust all packet checks before any native network request.
|
|
sources = list(_prepared_sources(proposal_path, disclosure_path, consent_path))
|
|
expected_urls = {
|
|
_raw_url(source["repo"], source["consented_commit"], path)
|
|
for source, files in sources for path in files
|
|
}
|
|
allowlist = json.loads(os.environ.get("REUSE_SURFACE_AUTH_SOURCE_ALLOWLIST", "{}"))
|
|
actual_urls = allowlist.get(AUTH_ENV)
|
|
if (
|
|
not isinstance(actual_urls, list)
|
|
or len(actual_urls) != 14
|
|
or set(actual_urls) != expected_urls
|
|
):
|
|
raise ValueError("runtime_scope_not_exact")
|
|
schema = Draft202012Validator(load_schema())
|
|
except Exception:
|
|
receipt["error"] = "packet_scope_or_schema_validation_failed"
|
|
return receipt
|
|
for source, files in sources:
|
|
row = {
|
|
"repo": source["repo"],
|
|
"commit": source["consented_commit"],
|
|
"index_sha256": source["index_sha256"],
|
|
"verified": False,
|
|
"entry_sha256": [],
|
|
}
|
|
receipt["sources"].append(row)
|
|
try:
|
|
index_text = federation.fetch_remote_index_text(source["native_raw_url"], source)
|
|
if _sha(index_text.encode("utf-8")) != source["index_sha256"]:
|
|
raise ValueError("index_hash_mismatch")
|
|
index = yaml.safe_load(index_text)
|
|
entries = index["capabilities"]
|
|
if not isinstance(entries, list) or [e["id"] for e in entries] != source["capability_ids"]:
|
|
raise ValueError("unexpected_capability_ids")
|
|
for entry in entries:
|
|
if not {"id", "name", "summary", "vector", "domain", "status", "owner", "path"} <= entry.keys():
|
|
raise ValueError("invalid_index_row")
|
|
if entry["owner"] != source["repo"] or entry["path"] not in files:
|
|
raise ValueError("unconsented_entry")
|
|
entry_url = _raw_url(source["repo"], source["consented_commit"], entry["path"])
|
|
entry_text = federation.fetch_remote_index_text(entry_url, source)
|
|
digest = _sha(entry_text.encode("utf-8"))
|
|
if digest != files[entry["path"]]:
|
|
raise ValueError("entry_hash_mismatch")
|
|
front = re.match(r"^---\n(.*?)\n---", entry_text, re.DOTALL)
|
|
if front is None:
|
|
raise ValueError("missing_front_matter")
|
|
metadata = yaml.safe_load(front.group(1))
|
|
schema.validate(metadata)
|
|
if metadata["id"] != entry["id"] or metadata["owner"] != source["repo"]:
|
|
raise ValueError("entry_identity_mismatch")
|
|
row["entry_sha256"].append(digest)
|
|
row["verified"] = True
|
|
except Exception:
|
|
# Never stringify errors: HTTP handlers and parsers can embed bodies,
|
|
# headers, credentials, or private source text in their messages.
|
|
row["error"] = "native_fetch_or_metadata_validation_failed"
|
|
receipt["all_verified"] = len(receipt["sources"]) == 7 and all(
|
|
row["verified"] for row in receipt["sources"]
|
|
)
|
|
return receipt
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
for name in ("proposal", "disclosure", "consent"):
|
|
parser.add_argument(f"--{name}", type=Path, required=True)
|
|
args = parser.parse_args()
|
|
receipt = verify(args.proposal, args.disclosure, args.consent)
|
|
print(json.dumps(receipt, sort_keys=True))
|
|
return 0 if receipt["all_verified"] else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|