feat(execution): bind immutable task grants before native effects
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a140c4-354a-7823-8f0d-8f717397f786
This commit is contained in:
parent
4021ca5e79
commit
d042d468df
7 changed files with 460 additions and 7 deletions
|
|
@ -53,6 +53,7 @@ class OwnerConfig:
|
|||
catalog_directory: Path
|
||||
# Exact path/hash pairs, configured independently by the owner. No request paths.
|
||||
source_pins: tuple[tuple[str,str], ...]
|
||||
grant_filename: str | None = None
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Plan:
|
||||
|
|
@ -111,11 +112,18 @@ class Compiler:
|
|||
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')]
|
||||
self.grant_record=None;self.grant_validator=None
|
||||
if config.grant_filename is not None:
|
||||
need(type(config.grant_filename) is str and re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9_.-]*\.yaml',config.grant_filename)
|
||||
and config.grant_filename!=config.ccr_filename,'owner_grant_filename_invalid')
|
||||
self.grant_record=config.platform_repo/'credential-change-requests'/config.grant_filename
|
||||
self.grant_validator=Path(__file__).resolve().parent/'task_execution_grant.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]}
|
||||
if self.grant_record is not None:required.update({str(self.grant_record),str(self.grant_validator)})
|
||||
need(required <= set(self.pins),'owner_source_pin_missing')
|
||||
extra=set(self.pins)-required
|
||||
need(len(extra)==1,'closed_owner_source_set_required')
|
||||
|
|
@ -254,6 +262,51 @@ class Compiler:
|
|||
'source_pins':dict(self.pins),'task_token_max_ttl':120,'task_token_max_uses':12,
|
||||
'cleanup_uses_reserved':1,'cleanup':cleanup_specs(request['operation_id'])}))
|
||||
|
||||
def grant_effects(self,plan,descriptor):
|
||||
"""Independently derive the one proposed grant's effectful immutable body."""
|
||||
need(type(plan) is Plan and type(descriptor) is dict,'typed_grant_inputs_required')
|
||||
meta=plan.metadata();req=meta['request']
|
||||
need(self.compile(req).serialized==plan.serialized,'immutable_plan_changed')
|
||||
_,_,calls=self._derive()
|
||||
budget=descriptor['backend_budget']
|
||||
need(type(budget) is dict and set(budget)=={'ttl_seconds','uses'}
|
||||
and type(budget['ttl_seconds']) is int and 30<=budget['ttl_seconds']<=120
|
||||
and type(budget['uses']) is int and budget['uses']==12,'fixed_grant_budget_required')
|
||||
operation_id=req['operation_id'];policy_name='task-exec-'+operation_id
|
||||
paths=sorted({call.path for call in calls})
|
||||
hcl=''.join('path '+json.dumps(path)+' {\n capabilities = ["read", "create", "update"]\n}\n' for path in paths)
|
||||
hcl+='path "auth/token/revoke-self" {\n capabilities = ["update"]\n}\n'
|
||||
token_body={'policies':[policy_name],'no_default_policy':True,'renewable':False,
|
||||
'ttl':str(budget['ttl_seconds'])+'s','explicit_max_ttl':str(budget['ttl_seconds'])+'s',
|
||||
'num_uses':12,'type':'service','display_name':policy_name,'no_parent':False}
|
||||
# A grant cannot include its own immutable hash or raw file SHA. The
|
||||
# existing descriptor joins it by ID+immutable SHA after this projection.
|
||||
descriptor_binding={k:v for k,v in descriptor.items() if k!='grant_immutable_sha256'}
|
||||
pins={path:digest for path,digest in self.pins.items() if self.grant_record is None or path!=str(self.grant_record)}
|
||||
execution={'action':'execute-ccr','catalog_id':req['catalog_id'],
|
||||
'descriptor_binding':descriptor_binding,'source_pins':pins,
|
||||
'cleanup':cleanup_specs(operation_id),'cleanup_uses_reserved':1,
|
||||
'cleanup_after_business_deadline':True}
|
||||
openbao={'policy':{'name':policy_name,'hcl':hcl},'token_create_body':token_body,
|
||||
'owner_parent_custody':'platform','identity_policies':[]}
|
||||
return execution,openbao
|
||||
|
||||
def _bind_grant(self,plan,descriptor):
|
||||
need(self.grant_record is not None,'owner_grant_source_required')
|
||||
# Execute exactly the pinned pure projection source, never a cached module.
|
||||
module=types.ModuleType('_owner_task_execution_grant')
|
||||
raw=self.read(self.grant_validator)
|
||||
exec(compile(raw,str(self.grant_validator),'exec'),module.__dict__)
|
||||
record=yaml.load(self.read(self.grant_record),Loader=UniqueLoader)
|
||||
execution,openbao=self.grant_effects(plan,descriptor)
|
||||
try:
|
||||
projection=module.bind(record,grant_id=descriptor['execution_grant_ccr_id'],
|
||||
expected_execution=execution,expected_openbao=openbao,
|
||||
expected_digest=descriptor['grant_immutable_sha256'])
|
||||
except Exception:raise Refused('owner_grant_binding_mismatch') from None
|
||||
need(self.read(self.grant_validator)==raw,'owner_grant_source_changed')
|
||||
return projection
|
||||
|
||||
def bind_execution(self, plan, descriptor, owner_descriptor):
|
||||
"""Bind exact engine descriptor to owner plan; no authorization is created.
|
||||
|
||||
|
|
@ -275,9 +328,11 @@ class Compiler:
|
|||
need(all(descriptor.get(k)==v for k,v in expected.items()),'execution_descriptor_plan_mismatch')
|
||||
bound = dict(meta, execution_descriptor=descriptor,
|
||||
execution_descriptor_sha256=tagged(owner_descriptor))
|
||||
# The full descriptor's independently admitted grant immutable digest must
|
||||
# cover these cleanup commitments. No grant schema exists here yet, so this
|
||||
# source binding cannot itself admit a grant/native production execution.
|
||||
if self.grant_record is not None:
|
||||
bound['execution_grant']=self._bind_grant(plan,descriptor)
|
||||
bound['grant_immutable_sha256']=descriptor['grant_immutable_sha256']
|
||||
# Without an independently pinned grant this remains preparation only.
|
||||
# The native fixture adapter refuses to materialize such an unbound plan.
|
||||
return Plan(encoded(bound))
|
||||
|
||||
def before_call(self,plan,ordinal):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue