#!/usr/bin/env python3 """Guarded public-only Argo transport prerequisite; no Secret or credential calls.""" import argparse import copy from datetime import datetime,timezone import hashlib import json import os from pathlib import Path import re import shlex import subprocess URL='ssh://git@forgejo.coulomb.social:30022/coulomb/rapp-issue-core.git' D2='89f1058f-33bc-47a8-8a49-0b44d426c67e' TARGETS=[('configmap','argocd-ssh-known-hosts-cm','/data/ssh_known_hosts','railiance-enablement'),('appproject','railiance-tenants','/spec/sourceRepos','railiance-platform')] class Refused(Exception):pass def _kube_route(): """Local-only import; this module's source may also run remotely without it.""" import sys here = str(Path(__file__).resolve().parent) if here not in sys.path: sys.path.insert(0, here) import kube_route return kube_route def require(ok,reason): if not ok:raise Refused(reason) def digest(value):return hashlib.sha256(json.dumps(value,sort_keys=True).encode()).hexdigest() def run(args,body=None): try: result=subprocess.run(_kube_route().ssh_argv(['kubectl','--request-timeout=15s',*args]),input=body,text=True,capture_output=True,timeout=60) except Exception:raise Refused('command_outcome_unverified') from None require(result.returncode==0,'command_outcome_unverified') return result.stdout def value(obj,path): for key in path.strip('/').split('/'):obj=obj[key] return obj def replace(obj,path,wanted): keys=path.strip('/').split('/');parent=obj for key in keys[:-1]:parent=parent[key] parent[keys[-1]]=wanted def normalized(obj): obj=copy.deepcopy(obj) for field in ('managedFields','resourceVersion','generation'):obj.get('metadata',{}).pop(field,None) return obj def validate(plan): require(plan.get('schema')=='argocd-public-transport/v1' and plan.get('task')=='RPF-WP-0044-T04' and plan.get('approval_ref')==D2,'plan_authority') require(plan.get('target')=='railiance01' and plan.get('namespace')=='argocd' and plan.get('repo_server_tcp_30022_verified') is True,'plan_target') require(all(isinstance(plan.get(k),str) and plan[k] for k in ('diff_ref','rollback','abort_owner','baseline_sha256','public_authority_observation_sha256')),'plan_receipt_contract') require(len(plan.get('resources',[]))==2,'target_count') for item,fixed in zip(plan['resources'],TARGETS): require(tuple(item.get(k) for k in ('kind','name','path','owner'))==fixed,'fixed_target') require(bool(re.fullmatch(r'[a-f0-9-]{36}',item.get('uid',''))),'target_uid') cm,project=plan['resources'] require(isinstance(cm['before'],str) and cm['before'].endswith('\n') and isinstance(cm['after'],str),'host_field_type') added=cm['after'][len(cm['before']):] require(cm['after'].startswith(cm['before']) and bool(re.fullmatch(r'\[forgejo\.coulomb\.social\]:30022 (ssh-rsa|ssh-ed25519|ecdsa-sha2-nistp256) [A-Za-z0-9+/=]+\n',added)),'host_addition_scope') require('[forgejo.coulomb.social]:30022 ' not in cm['before'],'host_preimage_already_admitted') require(isinstance(project['before'],list) and all(isinstance(v,str) for v in project['before']) and URL not in project['before'] and project['after']==project['before']+[URL],'repository_addition_scope') def patch(item,live,rollback=False): before,after=(item['after'],item['before']) if rollback else (item['before'],item['after']) require(live['metadata']['uid']==item['uid'],'target_uid_changed') require(value(live,item['path'])==before,'target_preimage_changed') require(isinstance(live['metadata'].get('resourceVersion'),str),'resource_version_required') return [{'op':'test','path':'/metadata/uid','value':item['uid']},{'op':'test','path':'/metadata/resourceVersion','value':live['metadata']['resourceVersion']},{'op':'test','path':item['path'],'value':before},{'op':'replace','path':item['path'],'value':after}] def perform(plan,action,receipt,checkpoint=lambda:None): validate(plan) nodes=json.loads(run(['get','nodes','-o','json']))['items'] require(len(nodes)==1 and any(a.get('address')=='92.205.62.239' for a in nodes[0]['status']['addresses']),'wrong_estate') read=lambda item:json.loads(run(['-n','argocd','get',item['kind'],item['name'],'-o','json'])) rollback=action=='rollback' # Validate both targets before the first write, avoiding an avoidable partial change. live=[read(item) for item in plan['resources']] for item,obj in zip(plan['resources'],live): require(obj['metadata']['uid']==item['uid'],'target_uid_changed') if action=='verify':require(value(obj,item['path'])==item['after'],'verification_field_changed') elif rollback and value(obj,item['path'])==item['before']:continue else:patch(item,obj,rollback) for item,obj in zip(plan['resources'],live): record={'kind':item['kind'],'name':item['name'],'uid':item['uid'],'field':item['path'],'before_sha256':digest(item['before']),'after_sha256':digest(item['after'])};receipt['resources'].append(record) if action=='verify':record['verified']=True;continue if rollback and value(obj,item['path'])==item['before']:record['already_restored']=True;continue body=json.dumps(patch(item,obj,rollback));args=['-n','argocd','patch',item['kind'],item['name'],'--type=json','--patch-file=/dev/stdin'] admitted=json.loads(run(args+['--dry-run=server','-o','json'],body)) wanted=copy.deepcopy(obj);replace(wanted,item['path'],item['before'] if rollback else item['after']) require(normalized(admitted)==normalized(wanted),'dry_run_modified_other_fields');record['server_dry_run']=True if action=='dry-run':continue receipt['pending_target']=item['kind']+'/'+item['name'];receipt['mutation_attempted']=True;checkpoint() run(args+['-o','name'],body) checked=read(item) require(checked['metadata']['uid']==item['uid'] and normalized(checked)==normalized(wanted),'postchange_projection_unverified') record['changed_verified']=True;receipt['pending_target']=None;checkpoint() receipt['passed']=True;return receipt def main(): parser=argparse.ArgumentParser(description=__doc__) parser.add_argument('--plan',type=Path,required=True);parser.add_argument('--plan-sha256',required=True) parser.add_argument('--action',choices=('dry-run','execute','verify','rollback'),default='dry-run') parser.add_argument('--receipt',type=Path,required=True) args=parser.parse_args() receipt={'schema':'argocd-public-transport-receipt/v1','task':'RPF-WP-0044-T04','approval_ref':D2,'action':args.action,'passed':False,'mutation_attempted':False,'resources':[],'secret_or_credential_calls':False,'captured_at':datetime.now(timezone.utc).isoformat()} # Exclusive receipt is a replay guard. Never overwrite another attempt. fd=None try: require(not args.receipt.exists(),'receipt_exists_do_not_replay') fd=os.open(args.receipt,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600) raw=args.plan.read_bytes();require(hashlib.sha256(raw).hexdigest()==args.plan_sha256,'plan_changed') def checkpoint(): os.lseek(fd,0,os.SEEK_SET);os.ftruncate(fd,0);os.write(fd,(json.dumps(receipt,sort_keys=True)+'\n').encode());os.fsync(fd) receipt['plan_sha256']=args.plan_sha256;checkpoint();perform(json.loads(raw),args.action,receipt,checkpoint) except Refused as error:receipt['failure']=str(error) except Exception:receipt['failure']='internal_outcome_unverified' finally: if fd is not None: os.lseek(fd,0,os.SEEK_SET);os.ftruncate(fd,0);os.write(fd,(json.dumps(receipt,sort_keys=True)+'\n').encode());os.fsync(fd);os.close(fd) # Only whitelist receipt fields are emitted. Every raw child error stays captured. print(json.dumps(receipt,sort_keys=True));return 0 if receipt['passed'] else 1 if __name__=='__main__':raise SystemExit(main())