the-custodian/docs/changes/CUST-WP-0080/test_receive_activation.py
codex eb93eeedfd
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Reject generated-only commits / generated-commit-guard (push) Successful in 7s
security: preserve verified Forgejo scanner activation and reconcile recovery
2026-10-13 11:31:06 +02:00

81 lines
4.7 KiB
Python

import copy
import importlib.util
import io
import json
from pathlib import Path
import subprocess
import sys
import tarfile
import tempfile
import unittest
from unittest.mock import patch
SOURCE=Path(__file__).with_name('activate_receive_hooks.py').resolve()
spec=importlib.util.spec_from_file_location('activation',SOURCE)
a=importlib.util.module_from_spec(spec);spec.loader.exec_module(a)
BASE=json.loads(SOURCE.with_name('receive-hook-proposal.json').read_text())
class Native:
def __init__(self, root, proposal, fail=False): self.root=root;self.p=proposal;self.fail=fail
def preflight(self,p):
for x in p['artifacts']:
if not Path(x['source']).is_file(): raise ValueError('missing-artifact')
return {'rehearsal':True}
def archive(self,p):
out=io.BytesIO();checks=[]
with tarfile.open(fileobj=out,mode='w') as t:
for item in p['artifacts']:
content=Path(item['source']).read_bytes();i=tarfile.TarInfo(item['name']);i.size=len(content);i.mode=item['mode'];t.addfile(i,io.BytesIO(content));checks.append(item['sha256']+' '+item['name']+'\n')
content=''.join(checks).encode();i=tarfile.TarInfo('SHA256SUMS');i.size=len(content);t.addfile(i,io.BytesIO(content))
return out.getvalue()
def execute(self,p,*args,payload=None,timeout=45):
if args[0]=='wget': return b''
mapped=[x.replace('/data/',str(self.root/'data')+'/').replace('/tmp/'+p['directory_name'],str(self.root/'stage')) for x in args]
if self.fail and args[0]=='bash' and 'mktemp' in args[-1]:
self.fail=False
path=self.root/'data/git/gitea-repositories/coulomb'/ (p['repositories'][0]+'.git/hooks/pre-receive.d')
for field,source in [('hook_name','receive-profile-hook'),('veto_name','receive-probe-veto')]:
dest=path/p[field];dest.write_bytes(SOURCE.with_name(source).read_bytes());dest.chmod(0o755)
raise ValueError('injected-partial-install-failure')
r=subprocess.run(mapped,input=payload,capture_output=True,timeout=timeout)
if r.returncode: raise ValueError('local-command-refused')
return r.stdout.replace((str(self.root/'data')+'/').encode(),b'/data/')
class Tests(unittest.TestCase):
def setUp(self):
self.tmp=tempfile.TemporaryDirectory();self.root=Path(self.tmp.name);self.p=copy.deepcopy(BASE)
# Small inert tools are sufficient for installer/rollback protocol testing.
binary=self.root/'tool';binary.write_text('#!/bin/sh\nexit 0\n')
for item in self.p['artifacts'][:2]: item.update(source=str(binary),sha256=a.sha(binary))
self.p['existing_hook_hashes']={k:a.hashlib.sha256(b'#!/bin/sh\nexit 0\n').hexdigest() for k in self.p['existing_hook_hashes']}
for name in self.p['repositories']:
repo=self.root/'data/git/gitea-repositories/coulomb'/(name+'.git')
subprocess.run(['git','init','--bare','-q',str(repo)],check=True)
for rel in self.p['existing_hook_hashes']:
f=repo/'hooks'/rel;f.parent.mkdir(exist_ok=True);f.write_bytes(binary.read_bytes());f.chmod(0o755)
self.native=Native(self.root,self.p);self.proposal=self.root/'proposal.json';self.receipt=self.root/'receipt.json'
def tearDown(self):self.tmp.cleanup()
def runmain(self,*args):
self.proposal.write_text(json.dumps(self.p))
with patch.object(a,'helper',return_value=self.native),patch.object(a,'verify_pushes',return_value=[{'rehearsal_only':True}]),patch.object(sys,'argv',['activate','--proposal',str(self.proposal),'--receipt',str(self.receipt),*args]):a.main()
def test_activation_and_rollback_without_build_files(self):
self.runmain('--apply');a.inspect(self.native,self.p,applied=True)
self.assertEqual(json.loads(self.receipt.read_text())['result'],'verified')
for x in self.p['artifacts']:x['source']='/missing/build/artifact'
self.runmain('--rollback');a.inspect(self.native,self.p)
def test_partial_install_rolls_back_all_added_hooks(self):
self.native.fail=True
with self.assertRaises(ValueError):self.runmain('--apply')
a.inspect(self.native,self.p)
r=json.loads(self.receipt.read_text());self.assertEqual(r['rollback'],'prior hook inventory restored');self.assertTrue(r['temporary_directory_removed']);self.assertEqual(r['result'],'unverified')
def test_non_executable_hook_refused(self):
self.runmain('--apply')
f=self.root/'data/git/gitea-repositories/coulomb'/(self.p['repositories'][0]+'.git/hooks/pre-receive.d')/self.p['hook_name'];f.chmod(0o644)
with self.assertRaises(ValueError):a.inspect(self.native,self.p,applied=True)
def test_rollback_preserves_unknown_modification(self):
self.runmain('--apply')
f=self.root/'data/git/gitea-repositories/coulomb'/(self.p['repositories'][0]+'.git/hooks/pre-receive.d')/self.p['hook_name'];f.write_text('unknown change\n')
with self.assertRaises(ValueError):a.rollback(self.native,self.p)
self.assertEqual(f.read_text(),'unknown change\n')
if __name__=='__main__':unittest.main(verbosity=2)