railiance-platform/scripts/flex_auth_signer_custody.py
codex a3e8eb64ba
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Add attended signer adapter with phase-aware read-only recovery
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a140c4-354a-7823-8f0d-8f717397f786
2026-10-16 18:44:07 +02:00

153 lines
8.8 KiB
Python

#!/usr/bin/env python3
"""Protected signer CAS primitive. Disposable owner fixture only; no live entry.
This module deliberately cannot admit production custody. A reviewed native CCR,
protected executor, fencing and founder scope are prerequisites for that adapter.
"""
from __future__ import annotations
from dataclasses import dataclass,field
from datetime import datetime,timezone
import base64
import hashlib
import hmac
import http.client
import json
import os
from pathlib import Path
import re
import secrets
import subprocess
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from task_execution_journal import Journal,Refused
from task_execution_native_fixture import _absolute_budget
from task_execution_recovery import _json_unique,_invalid_constant
PATH='platform/data/workloads/flex-auth/envelope-signing'
FIELDS={'ED25519_PRIVATE_KEY','ED25519_PUBLIC_KEY','kid'}
BINDINGS={'proposal_sha256','executed_revision_sha256','deployment_sha256','image_sha256','policy_sha256','trust_sha256'}
def encoded(value):return json.dumps(value,sort_keys=True,separators=(',',':'),allow_nan=False).encode()
def b64(value):return base64.urlsafe_b64encode(value).decode().rstrip('=')
@dataclass(frozen=True)
class FixtureWriterAdmission:
port:int
owner_token:str=field(repr=False)
server_process:subprocess.Popen=field(repr=False)
fenced:bool=False
class SignerWriter:
"""Single attempted operation; failed/unknown writes never retry or remint.
File permissions are accidental-disclosure guards, not same-UID isolation.
The receipt directory must be independent owner custody in a native adapter.
Python cannot promise seed zeroization; process lifetime is the boundary.
"""
def __init__(self,admission:FixtureWriterAdmission,directory:Path,operation_id:str,*,kid:str,expected_version:int,bindings:dict):
if (type(admission) is not FixtureWriterAdmission or type(admission.port) is not int
or not 1<=admission.port<=65535 or type(admission.owner_token) is not str
or not admission.owner_token or type(admission.fenced) is not bool or not admission.fenced):
raise Refused('exact fenced fixture custody required')
process=admission.server_process
if (type(process) is not subprocess.Popen or process.poll() is not None or type(process.args) is not list
or '-dev' not in process.args or '-dev-no-store-token' not in process.args
or '-dev-listen-address=127.0.0.1:'+str(admission.port) not in process.args):
raise Refused('live owner-spawned disposable dev process required')
self.admission=admission
self._initialize_receipt(directory,operation_id,kid=kid,expected_version=expected_version,bindings=bindings)
def _initialize_receipt(self,directory,operation_id,*,kid,expected_version,bindings):
if (type(operation_id) is not str or not re.fullmatch(r'[a-zA-Z0-9_-]{1,80}',operation_id)
or type(kid) is not str or not re.fullmatch(r'[a-zA-Z0-9_-]{1,80}',kid)
or type(expected_version) is not int or not 0<=expected_version<2**31
or type(bindings) is not dict or set(bindings)!=BINDINGS
or any(type(v) is not str or not re.fullmatch(r'sha256:[0-9a-f]{64}',v) for v in bindings.values())):
raise Refused('exact signer operation contract required')
if directory.absolute()!=directory.resolve() or directory.is_symlink():raise Refused('indirect receipt custody refused')
Journal._private(directory.stat(),directory=True)
self.directory=directory;self.path=directory/(operation_id+'.json')
self.expected_version=expected_version;self.kid=kid;self.attempted=False
self.receipt={'schema':'railiance-platform.signer-cas-receipt.v1','operation_id':operation_id,
'bindings':dict(bindings),'writer_sha256':'sha256:'+hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
'actor':'synthetic-owner-fixture','created_at':datetime.now(timezone.utc).isoformat(),'path':PATH,'expected_version':expected_version,'kid':kid,
'status':'prepared','production_admission':False,'rollback':'forward-recovery-only',
'public_key':None,'public_fingerprint':None,'kv_version':None,'exact_readback':False}
self._save(initial=True)
def _save(self,*,initial=False):
raw=encoded(self.receipt)
if len(raw)>65536:raise Refused('receipt bound exceeded')
if initial:
target=self.path
else:
fd=os.open(self.path,os.O_RDONLY|os.O_NOFOLLOW|os.O_NONBLOCK)
try:Journal._private(os.fstat(fd))
finally:os.close(fd)
target=self.directory/('.pending-'+secrets.token_hex(16))
fd=os.open(target,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600)
try:
with os.fdopen(fd,'wb',closefd=False) as stream:stream.write(raw);stream.flush();os.fsync(fd)
finally:os.close(fd)
if not initial:os.replace(target,self.path)
fd=os.open(self.directory,os.O_RDONLY|os.O_DIRECTORY|os.O_NOFOLLOW)
try:os.fsync(fd)
finally:os.close(fd)
def _http(self,method,path,body=None):
# Fixed loopback owner process and fixed data path; never arbitrary URL.
if self.admission.server_process.poll() is not None:raise Refused('fixture custody backend unavailable')
with _absolute_budget(2.0):
conn=http.client.HTTPConnection('127.0.0.1',self.admission.port,timeout=2)
try:
conn.request(method,'/v1/'+path,body=None if body is None else encoded(body),headers={
'X-Vault-Token':self.admission.owner_token,'Content-Type':'application/json'})
response=conn.getresponse();raw=response.read(1048577)
if len(raw)>1048576:raise Refused('backend response bound exceeded')
value=json.loads(raw,object_pairs_hook=_json_unique,parse_constant=_invalid_constant) if raw else {}
return response.status,value
finally:conn.close()
def write(self):
if self.attempted:raise Refused('signer operation already attempted')
self.attempted=True
# All source/receipt checks precede generation; no seed import, argv,
# environment, temporary file or public output interface exists.
private=Ed25519PrivateKey.generate()
seed=b64(private.private_bytes(serialization.Encoding.Raw,serialization.PrivateFormat.Raw,serialization.NoEncryption()))
public=b64(private.public_key().public_bytes(serialization.Encoding.Raw,serialization.PublicFormat.Raw))
document={'ED25519_PRIVATE_KEY':seed,'ED25519_PUBLIC_KEY':public,'kid':self.kid}
self.receipt.update(status='write-intent-unknown',public_key=public,
public_fingerprint='sha256:'+hashlib.sha256(public.encode()).hexdigest())
self._save() # A durable intent must exist before any possible effect.
try:
status,result=self._http('POST',PATH,{'options':{'cas':self.expected_version},'data':document})
if status!=200 or type(result) is not dict:raise Refused('signer write unconfirmed')
version=result.get('data',{}).get('version')
if type(version) is not int or version!=self.expected_version+1:raise Refused('signer write version unconfirmed')
self.receipt['kv_version']=version
status,result=self._http('GET',PATH+'?version='+str(version))
if status!=200 or type(result) is not dict:raise Refused('signer readback unconfirmed')
data=result.get('data',{});stored=data.get('data') if type(data) is dict else None
metadata=data.get('metadata') if type(data) is dict else None
if (type(stored) is not dict or set(stored)!=FIELDS or any(type(v) is not str for v in stored.values())
or type(metadata) is not dict or type(metadata.get('version')) is not int or metadata['version']!=version
or any(not hmac.compare_digest(stored[name],document[name]) for name in FIELDS)):
raise Refused('signer readback mismatch')
self.receipt.update(status='custody-verified-pending-independent-public-admission',exact_readback=True)
self._save()
return dict(self.receipt)
except BaseException:
# Persist only closed metadata; unknown never implies absence or retry.
# If receipt storage fails, the durable earlier intent remains unknown.
self.receipt.update(status='write-intent-unknown',exact_readback=False)
self._save()
raise Refused('signer custody outcome unconfirmed') from None
if __name__=='__main__':
raise SystemExit('production signer custody unadmitted; no live entry point')