"""Owner-derived metadata pilot compiler. Source-only; never authorizes or dispatches. OwnerConfig is installed by the trusted owner, never deserialized from a request. Six fixed calls on two structure objects. Recompile immediately before each call. Canonical PDP request digest is opaque: the authenticated mediator verifies it. """ from __future__ import annotations import hashlib import json import os from pathlib import Path import re import stat import types from dataclasses import dataclass from uuid import UUID import yaml class Refused(Exception): pass def need(value, reason): if not value: raise Refused(reason) def encoded(value): return json.dumps(value,sort_keys=True,separators=(',', ':'),ensure_ascii=True,allow_nan=False).encode() def content_hash(raw): return hashlib.sha256(raw).hexdigest() def tagged(raw): return 'sha256:'+content_hash(raw) def digest_ok(value): return type(value) is str and re.fullmatch(r'sha256:[0-9a-f]{64}',value) is not None class UniqueLoader(yaml.SafeLoader): pass def unique(loader,node): result={} for key,val in node.value: k=loader.construct_object(key) need(k not in result,'duplicate_owner_key') result[k]=loader.construct_object(val) return result UniqueLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,unique) TARGET_KEYS=('kind','org','repo','mount','path','fields','mount_management','consumers', 'delivery_modes','delivery_auth','delivery_config','auth_capability','workload_delivery') REQUEST_KEYS={'action','kind','catalog_id','operation_id','request_digest','ccr_sha256','catalog_sha256','operations'} @dataclass(frozen=True) class OwnerConfig: platform_repo: Path secrets_repo: Path ccr_filename: str catalog_filename: str catalog_directory: Path # Exact path/hash pairs, configured independently by the owner. No request paths. source_pins: tuple[tuple[str,str], ...] @dataclass(frozen=True) class Plan: serialized: bytes @property def content_digest(self): return content_hash(self.serialized) # Journal uses bare content hash. def metadata(self): try: return json.loads(self.serialized) except Exception: raise Refused('invalid_plan') from None @dataclass(frozen=True) class Call: ordinal: int kind: str method: str path: str payload: bytes @property def payload_sha256(self): return tagged(self.payload) @property def call_digest(self): return content_hash(encoded({'ordinal':self.ordinal,'kind':self.kind,'method':self.method, 'path':self.path,'payload_sha256':self.payload_sha256})) class Compiler: def __init__(self, config: OwnerConfig): need(type(config) is OwnerConfig,'owner_configuration_required') self.config=config for root in (config.platform_repo,config.secrets_repo,config.catalog_directory): need(type(root) is Path or isinstance(root,Path),'owner_root_invalid') need(root.is_absolute() and root.resolve()==root,'owner_root_invalid') for name in (config.ccr_filename,config.catalog_filename): need(type(name) is str and re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9_.-]*\.yaml',name),'owner_filename_invalid') self.ccr=config.platform_repo/'credential-change-requests'/config.ccr_filename self.catalog=config.catalog_directory/config.catalog_filename self.validator=config.platform_repo/'scripts/credential-change.py' self.catalog_code=config.secrets_repo/'src/secrets_engine/catalog.py' self.dependencies=[config.secrets_repo/'src/secrets_engine'/name for name in ('__init__.py','errors.py','redact.py')] pins=dict(config.source_pins) self.pins=types.MappingProxyType(pins) need(len(self.pins)==len(config.source_pins) and all(type(k) is str and digest_ok(v) for k,v in self.pins.items()),'owner_pins_invalid') # Compiler source is part of the owner plan, not just generated body hashes. required={str(p) for p in [Path(__file__).resolve(),self.ccr,self.catalog,self.validator,self.catalog_code,*self.dependencies]} need(required <= set(self.pins),'owner_source_pin_missing') extra=set(self.pins)-required need(len(extra)==1,'closed_owner_source_set_required') artifact=Path(next(iter(extra))) need(artifact.parent==config.platform_repo/'openbao/policies' and re.fullmatch(r'workload-kv-read-[a-z0-9-]+\.hcl',artifact.name), 'closed_owner_policy_source_required') def read(self,path): need(str(path) in self.pins and path.is_absolute() and path.resolve()==path,'owner_source_path_invalid') fd=None try: fd=os.open(path,os.O_RDONLY|os.O_NOFOLLOW|os.O_NONBLOCK) before=os.fstat(fd) need(stat.S_ISREG(before.st_mode) and before.st_nlink==1 and not before.st_mode & 0o022 and 0') need(entry.id==ccr['access_frontdoor']['catalog_id'] and entry.stage=='prod' and ccr['target']['environment']=='production' and entry.kind=='kv' and entry.mount_management=='existing' and entry.delivery_auth_management in {'none','existing'},'catalogue_not_metadata_pilot') need(entry.mount==ccr['openbao']['mount'] and entry.path==ccr['openbao']['kv_path'][len(entry.mount)+1:] and sorted(entry.fields)==sorted(ccr['openbao']['fields']) and entry.approval.get('decision_ref')==ccr['id'],'catalogue_ccr_mismatch') target={k:getattr(entry,k) for k in TARGET_KEYS} auth=ccr['openbao']['auth'];role_path=f"auth/{auth['mount']}/role/{auth['role']}" policy_path=f'sys/policies/acl/{policy_name}' bodies=[b'',encoded({'policy':generated.rstrip()}),b'',b'',encoded(owner.auth_payload(ccr)),b''] paths=[policy_path,policy_path,policy_path,role_path,role_path,role_path] methods=['GET','PUT','GET','GET','PUT','GET'] kinds=['policy_baseline','policy_write','policy_readback','role_baseline','auth_role_write','role_readback'] calls=tuple(Call(i,kinds[i],methods[i],paths[i],bodies[i]) for i in range(6)) need(all(re.fullmatch(r'(sys/policies/acl/workload-kv-read-[a-z0-9-]+|auth/(netkingdom|kubernetes)/role/[a-z0-9-]+)',c.path) for c in calls),'unsafe_structure_target') # Detect source changes during existing validator/generator reads and catalog validation. for p,raw in snapshot.items(): need(self.read(Path(p))==raw,'owner_source_changed') return ccr,target,calls except Refused: raise except BaseException: raise Refused('owner_compilation_refused') from None def proposal(self): ccr,target,calls=self._derive() return {'action':'execute-ccr','kind':'workload-kv-read','catalog_id':ccr['access_frontdoor']['catalog_id'], 'ccr_sha256':self.pins[str(self.ccr)],'catalog_sha256':tagged(json.dumps(target,sort_keys=True,separators=(',', ':'),ensure_ascii=False,allow_nan=False).encode('utf-8')), 'catalog_target':json.loads(encoded(target)), 'operations':[{'method':c.method,'path':c.path,'payload_sha256':c.payload_sha256} for c in calls]} def compile(self,request): need(type(request) is dict and set(request)==REQUEST_KEYS,'closed_plan_request_required') need(digest_ok(request['request_digest']),'canonical_request_digest_required') try: need(str(UUID(request['operation_id']))==request['operation_id'],'operation_id_invalid') except (ValueError,AttributeError,TypeError): raise Refused('operation_id_invalid') from None expected=self.proposal() need(all(request[k]==expected[k] for k in REQUEST_KEYS-{'operation_id','request_digest'}),'owner_plan_binding_mismatch') return Plan(encoded({'schema':'platform-owner-typed-plan/v1','action':'execute-ccr','operation':'metadata-apply-ccr', 'request':request,'catalog_target':expected['catalog_target'], 'source_pins':dict(self.pins),'task_token_max_ttl':120,'task_token_max_uses':12, 'cleanup_uses_reserved':1})) def before_call(self,plan,ordinal): need(type(plan) is Plan and type(ordinal) is int and 0<=ordinal<6,'fixed_plan_ordinal_required') try: meta=plan.metadata();fresh=self.compile(meta['request']) except Refused: raise except BaseException: raise Refused('invalid_plan') from None need(fresh.serialized==plan.serialized,'immutable_plan_changed') # Immediately re-derive owner payload bytes, and reconfirm immutable snapshot after derivation. _,_,calls=self._derive();need(self.compile(meta['request']).serialized==plan.serialized,'immutable_plan_changed') return calls[ordinal]