#!/usr/bin/env python3 """Prepare exact identity backup coverage from read-only workload/PVC metadata. This produces a proposal, never reads Secrets, executes in pods, changes cluster resources, captures private files, uploads, schedules or grants credentials. """ import argparse from datetime import datetime,timezone import hashlib import json import os from pathlib import Path import subprocess ROOT=Path(__file__).resolve().parents[2] CLUSTER_UID='a553c742-0115-43d4-99a4-a5ca56fe0786' PROFILE=ROOT/'tools/bootstrap-recovery/identity-backup-profile.json' class Refused(Exception):pass def query(namespace,kind,name): # Closed read-only types; Secret/ConfigMap data and arbitrary API paths have # no transport in this preparation command. if kind not in {'namespace','deployment','pvc'}:raise Refused() args=['kubectl','--request-timeout=20s'] if namespace:args+=['-n',namespace] r=subprocess.run(args+['get',kind,name,'-o','json'],capture_output=True,timeout=25) if r.returncode:raise Refused() return json.loads(r.stdout) def references(spec): secrets=set();configmaps=set();pvcs=set() for c in spec.get('containers',[])+spec.get('initContainers',[]): for e in c.get('env',[]): v=e.get('valueFrom',{}) if 'secretKeyRef' in v:secrets.add(v['secretKeyRef']['name']) if 'configMapKeyRef' in v:configmaps.add(v['configMapKeyRef']['name']) for e in c.get('envFrom',[]): if 'secretRef' in e:secrets.add(e['secretRef']['name']) if 'configMapRef' in e:configmaps.add(e['configMapRef']['name']) for v in spec.get('volumes',[]): if 'secret' in v:secrets.add(v['secret']['secretName']) if 'configMap' in v:configmaps.add(v['configMap']['name']) if 'persistentVolumeClaim' in v:pvcs.add(v['persistentVolumeClaim']['claimName']) for p in v.get('projected',{}).get('sources',[]): if 'secret' in p:secrets.add(p['secret']['name']) if 'configMap' in p:configmaps.add(p['configMap']['name']) return secrets,configmaps,pvcs def prepare(profile,get=query): if get(None,'namespace','kube-system')['metadata']['uid']!=CLUSTER_UID:raise Refused() bindings=[] for component in profile['components']: ns=component['namespace'];name=component['deployment'] d=get(ns,'deployment',name);m=d['metadata'] if (m['uid']!=component['deployment_uid'] or m.get('deletionTimestamp') or d['status'].get('observedGeneration')!=m['generation'] or d['status'].get('readyReplicas')!=1):raise Refused() refs=references(d['spec']['template']['spec']) expected_pvcs={p['name']:p['uid'] for p in component['pvc_refs']} if refs[0]!=set(component['secret_refs']) or refs[1]!=set(component['configmap_refs']) or refs[2]!=set(expected_pvcs):raise Refused() pvcs=[] for pvc,expected in expected_pvcs.items(): obj=get(ns,'pvc',pvc) if obj['metadata']['uid']!=expected or obj['metadata'].get('deletionTimestamp') or obj['status'].get('phase')!='Bound':raise Refused() pvcs.append({'name':pvc,'uid':expected}) bindings.append({'namespace':ns,'deployment':name,'uid':m['uid'],'generation':m['generation'], 'pvc_bindings':pvcs,'secret_names_only':sorted(refs[0]),'configmap_names_only':sorted(refs[1])}) if get(None,'namespace','kube-system')['metadata']['uid']!=CLUSTER_UID:raise Refused() return {'schema':'netkingdom.identity-backup-preparation.v1','status':'prepared_not_admitted', 'observed_at':datetime.now(timezone.utc).isoformat(),'cluster_uid':CLUSTER_UID, 'profile_sha256':hashlib.sha256(PROFILE.read_bytes()).hexdigest(), 'source_sha256':hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), 'bindings':bindings,'secret_values_read':False,'backup_captured':False,'uploaded':False, 'runtime_changed':False,'credential_grant_created':False, 'owner_admission_required':profile['admission_gates']} def main(): p=argparse.ArgumentParser(description=__doc__);p.add_argument('--receipt',required=True) a=p.parse_args() try: r=prepare(json.loads(PROFILE.read_text())) fd=os.open(a.receipt,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600) with os.fdopen(fd,'w') as out:json.dump(r,out,indent=2);out.write('\n') except Exception: print('ERROR: coverage preparation refused; private diagnostics suppressed.');return 1 print('OK: exact coverage proposal prepared; capture, delivery and owner admission remain pending.');return 0 if __name__=='__main__':raise SystemExit(main())