#!/usr/bin/python3 """Non-admitting structural preflight. Never opens namespaces or binds sockets.""" import argparse import hashlib import ipaddress import json from pathlib import Path import re import sys from c01_endpoints import validate_route, source_addresses, source_binding_conflict from c01_uplink import validate as validate_uplink import c01_network_mdns import c01_retained_producer def exact(pairs): result={} for key,value in pairs: if key in result:raise ValueError('duplicate JSON field') result[key]=value return result def validate(policy,runtime): if type(policy) is not dict or set(policy) not in ({'version','namespaces','manifests','routes','uplink'},{'version','namespaces','manifests','routes','uplink','mdns'}) or type(policy['version']) is not int or policy['version']!=4: raise ValueError('version 4 endpoint policy required') ns=policy['namespaces'] if type(ns) is not dict or set(ns)!={'host','managed','runtime'}:raise ValueError('namespace fields') for pair in ns.values(): if type(pair) is not list or len(pair)!=2 or any(type(v) is not int or v<=0 for v in pair):raise ValueError('measured namespace identity required') if len({tuple(pair) for pair in ns.values()})!=3:raise ValueError('namespaces must be distinct') validate_uplink(policy['uplink']) if 'mdns' in policy:c01_network_mdns.validate(policy['mdns'],policy['uplink']) if type(policy['manifests']) is not list or len(policy['manifests'])>128:raise ValueError('projection bound') manifests={} base={'app_id','manifest_sha256','loopback_ports','target_cgroup'} for doc in policy['manifests']: if type(doc) is not dict or set(doc) not in (base,base|{'source_bindings','target_addresses','producer'}):raise ValueError('projection exact fields') if not re.fullmatch('[a-z0-9][a-z0-9-]{0,63}',doc['app_id']) or not re.fullmatch('[0-9a-f]{64}',doc['manifest_sha256']):raise ValueError('projection identity') if type(doc['loopback_ports']) is not list or not 1<=len(doc['loopback_ports'])<=128:raise ValueError('projection ports bound') for port in doc['loopback_ports']: if type(port) is not list or len(port)!=2 or port[0] not in ('tcp','udp') or type(port[1]) is not int or not 1<=port[1]<=65535:raise ValueError('projection port') if 'producer' in doc: producer=doc['producer'] if type(producer) is dict and producer.get('kind')=='retained-container-v1': c01_retained_producer.validate(producer,doc['target_cgroup']) elif producer!={'kind':'exact'}: fields={'kind','container_name','image_id'} if type(producer) is not dict or set(producer) not in (fields,fields|{'payload_principal'}) or producer['kind']!='container-payload' or not re.fullmatch('[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}',producer['container_name']) or not re.fullmatch('[0-9a-f]{64}',producer['image_id']):raise ValueError('runtime producer selector') if 'payload_principal' in producer: principal=producer['payload_principal'] if type(principal) is not list or len(principal)!=2 or any(type(n) is not int or not 0512:raise ValueError('listener capability bound') gated_ports={r['target_port'] for r in policy['routes'] if r['name'].startswith('gate/')} for route in policy['routes']: if route['target']=='runtime' and route['source']=='host' and route['protocol']=='tcp' and route['target_port'] in gated_ports and any(not ipaddress.ip_address(address).is_loopback for address in route['source_addresses']): raise ValueError('public runtime route would bypass an appointed authentication gate') mdns_names={name for name in names if name.startswith('native/mdns')} if mdns_names != ({'native/mdns4/udp/5353/host','native/mdns6/udp/5353/host'} if 'mdns' in policy else set()):raise ValueError('complete mDNS scope/routes required') return {'structural_validation':True,'routes':len(names),'listeners':len(bindings),'live_admission':False} if __name__=='__main__': parser=argparse.ArgumentParser(description=__doc__) parser.add_argument('--policy',required=True);parser.add_argument('--runtime',required=True) args=parser.parse_args() def load(path): with open(path,'rb') as stream:raw=stream.read(65537) if len(raw)>65536:raise ValueError('plan exceeds size bound') return json.loads(raw,object_pairs_hook=exact) print(json.dumps(validate(load(args.policy),load(args.runtime))))