the-custodian/docs/changes/CUST-WP-0080/expand_forge_profiles.py
codex 9af8689327
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Reject generated-only commits / generated-commit-guard (push) Successful in 10s
security: close sync deadline fix and checkpoint scoped forge expansion
2026-10-13 16:55:37 +02:00

169 lines
9.4 KiB
Python

#!/usr/bin/env python3
"""Admit an exact private inventory, add hooks, and verify vetoed SSH pushes."""
import argparse
import datetime
import hashlib
import importlib.util
import json
from pathlib import Path
import re
import shlex
HERE=Path(__file__).parent
s=importlib.util.spec_from_file_location('template',HERE/'activate_forge_template.py')
t=importlib.util.module_from_spec(s)
s.loader.exec_module(t)
def repo_path(row):
if row['owner']!='coulomb' or not re.fullmatch(r'[a-z0-9-]+',row['name']):
raise ValueError('scope-identity-refused')
return '/data/git/gitea-repositories/coulomb/'+row['name']+'.git'
def inspect(pod,rows,expected):
commands=['set -euo pipefail']
for row in rows:
commands += ['repo='+shlex.quote(repo_path(row)),
'printf "REPO '+str(row['id'])+'\\n"',
'test -d "$repo" && test ! -L "$repo" && test ! -L "$repo/hooks" && test ! -L "$repo/hooks/pre-receive.d"',
'test -z "$(find "$repo/hooks" -maxdepth 2 -type l -print -quit)"',
'find "$repo/hooks" -maxdepth 2 -type f ! -name "*.sample" -exec sha256sum {} \\;',
'test "$(git --git-dir="$repo" rev-parse --is-shallow-repository)" = false',
'test "$(git --git-dir="$repo" rev-parse --show-object-format)" = sha1',
'config=$(git --git-dir="$repo" config --local --includes --name-only --list)',
'if printf "%s\\n" "$config" | grep -Eiq "(^extensions.partialclone$|^remote\\..*\\.(promisor|partialclonefilter)$)"; then exit 21; fi',
'if git --git-dir="$repo" rev-parse --verify HEAD >/dev/null 2>&1; then git --git-dir="$repo" ls-tree -r --format="%(objectmode)" HEAD | awk \'$0=="160000" {bad=1} END {exit bad}\'; fi']
raw=t.native.execute(pod,'bash','-s',payload=('\n'.join(commands)+'\n').encode(),timeout=180).decode()
found={}; current=None; by_id={r['id']:r for r in rows}
for line in raw.splitlines():
if line.startswith('REPO '): current=int(line[5:]);found[current]={}
else:
digest,path=line.split(' ',1);prefix=repo_path(by_id[current])+'/hooks/'
if not path.startswith(prefix) or not re.fullmatch('[a-f0-9]{64}',digest):raise ValueError('unexpected-hook-output')
found[current][path[len(prefix):]]=digest
if set(found)!=set(by_id) or any(hooks!=expected for hooks in found.values()):
raise ValueError('hook-inventory-changed')
def install(pod,path,content,digest):
# Temporary executable is outside the dispatch directory. Hard-link creation
# atomically exposes a complete file and refuses any existing destination.
script='''set -eu
umask 077
target=$1
temporary=$2
test ! -e "$target" && test ! -L "$target"
test ! -e "$temporary" && test ! -L "$temporary"
set -C
cat > "$temporary"
test "$(sha256sum "$temporary" | cut -d ' ' -f 1)" = "$3"
chmod 755 "$temporary"
ln "$temporary" "$target"
'''
temporary=path.rsplit('/hooks/pre-receive.d/',1)[0]+'/.custodian-expand-'+Path(path).name
t.native.execute(pod,'sh','-c',script,'sh',path,temporary,digest,payload=content)
def remove_owned(pod,path,digest,require_marker=False):
marker=path.rsplit('/hooks/pre-receive.d/',1)[0]+'/.custodian-expand-'+Path(path).name
t.native.execute(pod,'sh','-c',
'set -eu; if test -e "$1" || test -L "$1"; then test -f "$1" && test ! -L "$1"; '
'test "$(sha256sum "$1" | cut -d " " -f 1)" = "$2"; '
'if test "$4" = yes; then test "$1" -ef "$3"; fi; rm -- "$1"; fi; '
'if test -e "$3" || test -L "$3"; then test -f "$3" && test ! -L "$3"; '
'test "$(sha256sum "$3" | cut -d " " -f 1)" = "$2"; rm -- "$3"; fi',
'sh',path,digest,marker,'yes' if require_marker else 'no')
def rollback(pod,rows,original,proposal,require_marker=True):
for row in rows:
for name,digest in [(original['hook_name'],original['hook_sha256']),
(proposal['veto_name'],proposal['veto_sha256'])]:
remove_owned(pod,repo_path(row)+'/hooks/pre-receive.d/'+name,digest,require_marker)
inspect(pod,rows,original['existing_hook_hashes'])
def main():
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('--proposal',type=Path,required=True)
parser.add_argument('--inventory',type=Path,required=True)
parser.add_argument('--receipt',type=Path,required=True)
mode=parser.add_mutually_exclusive_group()
mode.add_argument('--apply',action='store_true')
mode.add_argument('--rollback',action='store_true')
args=parser.parse_args()
p=json.loads(args.proposal.read_text())
for file,digest in p['executor_hashes'].items():
if hashlib.sha256((HERE/file).read_bytes()).hexdigest()!=digest:raise ValueError('executor-changed')
if hashlib.sha256(args.inventory.read_bytes()).hexdigest()!=p['inventory_sha256']:raise ValueError('scope-changed')
inventory=json.loads(args.inventory.read_text());by_id={r['id']:r for r in inventory['repositories']}
rows=[by_id[i] for i in p['target_ids']]
if len(rows)!=p['target_count'] or len(set(p['target_ids']))!=len(rows):raise ValueError('scope-count-changed')
if any(r['profile']!='builtin-only' or not r['head_compatible'] for r in rows):raise ValueError('inadmissible-scope')
original=json.loads((HERE/'receive-hook-proposal.json').read_text())
pod=t.target_pod(t.deployment(),original)
if pod['pod_uid']!=p['pod_uid']:raise ValueError('pod-changed')
t.check_artifacts(pod)
for a in original['artifacts'][:2]:
if t.native.execute(pod,'stat','-c','%a %u %g',original['version_directory']+'/'+a['name']).decode().strip()!='755 1000 1000':
raise ValueError('artifact-custody-changed')
if args.rollback:
receipt=json.loads(args.receipt.read_text())
if receipt['proposal_sha256']!=hashlib.sha256(args.proposal.read_bytes()).hexdigest():raise ValueError('receipt-changed')
rollback(pod,[by_id[i] for i in receipt['attempted_ids']],original,p,
require_marker=receipt['result']!='verified')
receipt['rollback']='verified-original-profiles-restored'
args.receipt.write_text(json.dumps(receipt,indent=2)+'\n');return
inspect(pod,rows,original['existing_hook_hashes'])
fixture=Path(p['fixture_key'])
if hashlib.sha256(fixture.read_bytes()).hexdigest()!=p['fixture_sha256']:raise ValueError('fixture-changed')
hook=(HERE/'receive-profile-hook').read_bytes();veto=(HERE/'receive-probe-veto-expansion').read_bytes()
assert hashlib.sha256(hook).hexdigest()==original['hook_sha256']
assert hashlib.sha256(veto).hexdigest()==p['veto_sha256']
receipt={'proposal_id':p['proposal_id'],'proposal_sha256':hashlib.sha256(args.proposal.read_bytes()).hexdigest(),
'pod_uid':pod['pod_uid'],'result':'preflight-verified','target_count':len(rows),'attempted_ids':[],
'verified_ids':[],'at':datetime.datetime.now(datetime.timezone.utc).isoformat()}
def save():args.receipt.write_text(json.dumps(receipt,indent=2)+'\n')
save()
if not args.apply:print(json.dumps(receipt));return
try:
receipt['result']='applying';save()
for row in rows:
receipt['attempted_ids'].append(row['id']);save()
path=repo_path(row)+'/hooks/pre-receive.d/'
# No sample ref can be accepted during its probe, even if scanning fails.
install(pod,path+p['veto_name'],veto,p['veto_sha256'])
install(pod,path+original['hook_name'],hook,original['hook_sha256'])
proof=dict(pod,repositories=[row['name']],fixture_key=str(fixture),fixture_sha256=p['fixture_sha256'],
probe_ref=p['probe_ref'],proposal_id=p['proposal_id'])
t.activation.verify_pushes(t.native,proof)
remove_owned(pod,path+p['veto_name'],p['veto_sha256'],require_marker=True)
expected=dict(original['existing_hook_hashes']);expected['pre-receive.d/'+original['hook_name']]=original['hook_sha256']
inspect(pod,[row],expected)
receipt['verified_ids'].append(row['id']);save()
if len(receipt['verified_ids'])%5==0:print('Verified scanner profiles:',len(receipt['verified_ids']),'of',len(rows),flush=True)
inspect(pod,rows,expected)
receipt.update(result='verified',clean_scanner_and_synthetic_refusal_under_veto=True,
no_probe_refs_or_quarantined_commits=True,vetoes_removed=True,
original_dispatchers_preserved=True)
save()
except Exception as error:
receipt.update(result='failed',failure=type(error).__name__);save()
try:
rollback(pod,[by_id[i] for i in receipt['attempted_ids']],original,p)
receipt['rollback']='verified-original-profiles-restored'
except Exception as recovery:
receipt['rollback']='unverified:'+type(recovery).__name__
save();raise SystemExit('Expansion failed; inspect value-free receipt and rollback status.')
# After successful acceptance, receipt-bound rollback can use exact hashes.
# Cleanup failure leaves only inert ownership links outside the hook directory.
for row in rows:
target=repo_path(row)+'/hooks/pre-receive.d/'+original['hook_name']
marker=repo_path(row)+'/.custodian-expand-'+original['hook_name']
t.native.execute(pod,'sh','-c','set -eu; test "$1" -ef "$2"; rm -- "$2"','sh',target,marker)
receipt['ownership_markers_removed']=True;save()
print('All exact target profiles verified.',flush=True)
if __name__=='__main__':main()