#!/usr/bin/env python3 """Read-only bounded S3 backup metadata through existing scoped runtime delivery. No bootstrap API key, new grant, object content read, age key, provider write or retention action. The caller must already have admitted cluster read authority. """ import argparse import base64 from datetime import datetime, timezone import hashlib import json import os from pathlib import Path import re import subprocess BUCKET='railiance-platform-pg-backup' ENDPOINT='https://s3.nl-ams.scw.cloud' PREFIX='platform-pg/' CLUSTER='a553c742-0115-43d4-99a4-a5ca56fe0786' class Refused(Exception):pass def kube(args): p=subprocess.run(['kubectl',*args,'-o','json'],stdout=subprocess.PIPE,stderr=subprocess.DEVNULL,timeout=30) if p.returncode:raise Refused() return json.loads(p.stdout) def list_prefix(client,prefix): if not prefix.startswith(PREFIX) or not re.fullmatch(r'[A-Za-z0-9._/-]+',prefix) or '..' in prefix.split('/'):raise Refused() directories=[];files=[];continuation=None for _ in range(20): args={'Bucket':BUCKET,'Prefix':prefix,'Delimiter':'/','MaxKeys':1000} if continuation:args['ContinuationToken']=continuation page=client.list_objects_v2(**args) for row in page.get('CommonPrefixes',[]): value=row['Prefix'] if not value.startswith(prefix) or not value.endswith('/') or not re.fullmatch(r'[A-Za-z0-9._/-]+',value) or '..' in value.split('/'):raise Refused() directories.append(value) for row in page.get('Contents',[]): name=row['Key'] if not name.startswith(prefix) or not re.fullmatch(r'[A-Za-z0-9._/-]+',name) or '..' in name.split('/'):raise Refused() if name.endswith(('.age','.json','.yaml')): size=row['Size'] if type(size) is not int or size<0:raise Refused() files.append({'key':name,'bytes':size}) if not page.get('IsTruncated'):break continuation=page.get('NextContinuationToken') if not continuation:raise Refused() else:raise Refused() return {'directories':sorted(set(directories)),'files':sorted(files,key=lambda x:x['key'])} def inventory(client): pending=[(PREFIX,0)];result={} while pending: prefix,depth=pending.pop(0) if prefix in result:raise Refused() if len(result)>=64:raise Refused() row=list_prefix(client,prefix);result[prefix]=row if depth<3: for child in row['directories']: # Native database WAL/base trees are not application/key-file # archives. Record their presence without enumerating every WAL. if any(x in child.split('/') for x in ['wals','base']):continue pending.append((child,depth+1)) return result def main(): p=argparse.ArgumentParser(description=__doc__);p.add_argument('--receipt',required=True) a=p.parse_args();fd=os.open(a.receipt,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600) r={'schema':'netkingdom.scaleway-backup-inventory.v1','status':'failed', 'started_at':datetime.now(timezone.utc).isoformat(),'read_only':True, 'provider':'Scaleway','bucket':BUCKET,'prefix':PREFIX,'source_sha256':hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), 'bootstrap_api_key_used':False,'age_keys_read':False,'object_contents_downloaded':False, 'secret_values_exported':False,'scope':'Bounded prefix metadata, at most three child levels; WAL/base trees not enumerated; other buckets/prefixes unassessed'} try: if kube(['get','namespace','kube-system'])['metadata']['uid']!=CLUSTER:raise Refused() delivered=kube(['-n','databases','get','secret','platform-pg-backup-s3']) if delivered['metadata']['namespace']!='databases' or delivered['metadata']['name']!='platform-pg-backup-s3':raise Refused() values=delivered['data'] # Same governed runtime delivery used by Platform's archive verifier; # explicit credentials avoid SDK ambient/profile/metadata discovery. import boto3 from botocore.config import Config client=boto3.client('s3',endpoint_url=ENDPOINT,region_name='nl-ams', aws_access_key_id=base64.b64decode(values['ACCESS_KEY_ID'],validate=True).decode(), aws_secret_access_key=base64.b64decode(values['ACCESS_SECRET_KEY'],validate=True).decode(), config=Config(signature_version='s3v4',proxies={},connect_timeout=15,read_timeout=30, retries={'max_attempts':1},s3={'addressing_style':'path'})) r['prefixes']=inventory(client);r['status']='metadata_inventory_complete' r['runtime_delivery_uid']=delivered['metadata']['uid'] except Exception:r['error']='inventory_failed_diagnostics_suppressed' finally: r['finished_at']=datetime.now(timezone.utc).isoformat() with os.fdopen(fd,'w') as out:json.dump(r,out,indent=2);out.write('\n') return int(r['status']=='failed') if __name__=='__main__':raise SystemExit(main())