116 lines
5.3 KiB
Python
116 lines
5.3 KiB
Python
|
|
"""Proposed task-execution-grant CCR immutable projection; no native admission.
|
||
|
|
|
||
|
|
The only excluded paths are typed append-only review.comments and evidence.events.
|
||
|
|
Status, updated, review requirements, effectful bytes and source revisions remain
|
||
|
|
immutable. A digest is provenance, never served approval or execution authority.
|
||
|
|
"""
|
||
|
|
from __future__ import annotations
|
||
|
|
import copy
|
||
|
|
import hashlib
|
||
|
|
import json
|
||
|
|
import re
|
||
|
|
from datetime import datetime
|
||
|
|
|
||
|
|
|
||
|
|
class Refused(Exception): pass
|
||
|
|
|
||
|
|
ROOT_KEYS={'id','kind','schema_version','request_type','title','status','created','updated',
|
||
|
|
'requester','review','target','execution','openbao','evidence'}
|
||
|
|
APPEND_ONLY_PATHS=(('review','comments'),('evidence','events'))
|
||
|
|
HASH=re.compile(r'sha256:[0-9a-f]{64}')
|
||
|
|
|
||
|
|
|
||
|
|
def need(value):
|
||
|
|
if not value:raise Refused('invalid task execution grant')
|
||
|
|
|
||
|
|
|
||
|
|
def encoded(value):
|
||
|
|
def json_value(item):
|
||
|
|
if type(item) is dict:
|
||
|
|
need(all(type(key) is str for key in item))
|
||
|
|
for child in item.values():json_value(child)
|
||
|
|
elif type(item) is list:
|
||
|
|
for child in item:json_value(child)
|
||
|
|
else:need(item is None or type(item) in {str,int,float,bool})
|
||
|
|
try:
|
||
|
|
json_value(value)
|
||
|
|
return json.dumps(value,sort_keys=True,separators=(',',':'),ensure_ascii=True,allow_nan=False).encode()
|
||
|
|
except (TypeError,ValueError,RecursionError):raise Refused('invalid task execution grant') from None
|
||
|
|
|
||
|
|
|
||
|
|
def closed(value,keys):
|
||
|
|
need(type(value) is dict and set(value)==set(keys));return value
|
||
|
|
|
||
|
|
|
||
|
|
def text(value):
|
||
|
|
need(type(value) is str and 0<len(value)<=4096 and '\x00' not in value);return value
|
||
|
|
|
||
|
|
|
||
|
|
def timestamp(value):
|
||
|
|
try:need(datetime.fromisoformat(text(value).replace('Z','+00:00')).tzinfo is not None)
|
||
|
|
except (ValueError,TypeError):raise Refused('invalid task execution grant') from None
|
||
|
|
|
||
|
|
|
||
|
|
def projection(record):
|
||
|
|
"""Validate and capture every field except the two explicit append-only lists.
|
||
|
|
|
||
|
|
Unknown keys never become implicitly excluded evidence. The independently
|
||
|
|
derived exact execution/openbao comparison belongs to bind(), below.
|
||
|
|
"""
|
||
|
|
closed(record,ROOT_KEYS)
|
||
|
|
need(record['kind']=='credential-change-request' and type(record['schema_version']) is int
|
||
|
|
and record['schema_version']==1 and record['request_type']=='task-execution-grant')
|
||
|
|
need(type(record['id']) is str and re.fullmatch(r'[A-Z0-9][A-Z0-9_.-]*',record['id']))
|
||
|
|
need(type(record['status']) is str and record['status'] in {'proposed','approved','denied','cancelled','superseded'})
|
||
|
|
for field in ('title','created','updated'):text(record[field])
|
||
|
|
requester=closed(record['requester'],{'agent','reason'})
|
||
|
|
for value in requester.values():text(value)
|
||
|
|
review=closed(record['review'],{'required','required_approvers','comments'})
|
||
|
|
need(review['required'] is True and type(review['required_approvers']) is list
|
||
|
|
and review['required_approvers']==['railiance-platform-owner','secrets-engine-owner','access-engine-owner','approval-engine-owner'])
|
||
|
|
need(type(review['comments']) is list and len(review['comments'])<=1024)
|
||
|
|
for comment in review['comments']:
|
||
|
|
closed(comment,{'at','reviewer','decision','comment'})
|
||
|
|
timestamp(comment['at']);text(comment['reviewer']);text(comment['comment'])
|
||
|
|
need(type(comment['decision']) is str and comment['decision'] in {'approved','changes_requested','declined'})
|
||
|
|
target=closed(record['target'],{'domain','tenant','workload','environment','purpose'})
|
||
|
|
need(target['tenant']=='platform' and target['workload']=='secrets-engine' and target['environment']=='production')
|
||
|
|
for value in target.values():text(value)
|
||
|
|
need(type(record['execution']) is dict and type(record['openbao']) is dict)
|
||
|
|
evidence=closed(record['evidence'],{'events'})
|
||
|
|
need(type(evidence['events']) is list and len(evidence['events'])<=1024)
|
||
|
|
for event in evidence['events']:
|
||
|
|
closed(event,{'at','kind','result','reference_sha256'})
|
||
|
|
timestamp(event['at']);text(event['kind'])
|
||
|
|
need(type(event['result']) is str and event['result'] in {'pending','passed','failed','unknown'})
|
||
|
|
need(type(event['reference_sha256']) is str and HASH.fullmatch(event['reference_sha256']))
|
||
|
|
# Deep capture refuses NaN/non-JSON structures rather than silently coercing.
|
||
|
|
value=json.loads(encoded(record))
|
||
|
|
for parent,leaf in APPEND_ONLY_PATHS:value[parent][leaf]=[]
|
||
|
|
return value
|
||
|
|
|
||
|
|
|
||
|
|
def immutable_digest(record):
|
||
|
|
return 'sha256:'+hashlib.sha256(encoded(projection(record))).hexdigest()
|
||
|
|
|
||
|
|
|
||
|
|
def require_append_only(previous,current):
|
||
|
|
need(immutable_digest(previous)==immutable_digest(current))
|
||
|
|
for parent,leaf in APPEND_ONLY_PATHS:
|
||
|
|
old=previous[parent][leaf];new=current[parent][leaf]
|
||
|
|
need(len(new)>=len(old) and new[:len(old)]==old)
|
||
|
|
|
||
|
|
|
||
|
|
def bind(record,*,grant_id,expected_execution,expected_openbao,expected_digest):
|
||
|
|
"""Compare an owner source record with independently reconstructed effects.
|
||
|
|
|
||
|
|
Does not trust status/comments as approval, read source files, contact engines,
|
||
|
|
register a delegated applier or issue any backend request.
|
||
|
|
"""
|
||
|
|
value=projection(record)
|
||
|
|
need(record['id']==grant_id and type(expected_digest) is str and HASH.fullmatch(expected_digest))
|
||
|
|
need(encoded(record['execution'])==encoded(expected_execution))
|
||
|
|
need(encoded(record['openbao'])==encoded(expected_openbao))
|
||
|
|
need(immutable_digest(record)==expected_digest)
|
||
|
|
return value
|