Prepare source-bound one-use catalogue maintenance exception
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
This commit is contained in:
parent
1eefbfc44a
commit
c89614ec81
12 changed files with 886 additions and 0 deletions
241
tools/catalogue_bootstrap.py
Normal file
241
tools/catalogue_bootstrap.py
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
"""One-time supervised catalogue maintenance; no installed runtime endpoint.
|
||||
|
||||
A private, exact-hash supervisor artifact is an out-of-band maintenance grant,
|
||||
not authentication of its JSON author. The operator must independently confirm
|
||||
the founder's native disposition before supplying it. No JWT role is invented.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import argparse
|
||||
from dataclasses import replace
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import stat
|
||||
|
||||
from user_engine.adapters.postgres import PostgresUserEngineStore
|
||||
from user_engine.domain import (Actor, Application, ApplicationBinding, AuditRecord,
|
||||
AuthorizationDecision, AuthorizationEffect, PrincipalType)
|
||||
from user_engine.service import UserEngineService, AuthorizationDenied
|
||||
from user_engine.store_records import store_record_for
|
||||
|
||||
APP = Application('informed-decision', 'Informed Decision', 'informed-decision')
|
||||
BINDING = ApplicationBinding('informed-decision', oidc_client_id='informed-decision-approver', protected_system_id='informed-decision')
|
||||
PROPOSAL = 'USER-CATALOGUE-INFDEC-20261011-R1'
|
||||
SCHEMA = 'user-engine.supervised-catalogue-maintenance.v1'
|
||||
KEYS = {'schema','proposal_id','approval_id','memo_id','memo_version','approval_receipt_sha256','executor_sha256','records_sha256','expires_at','operator_id','authority'}
|
||||
|
||||
def digest(value):
|
||||
return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(',', ':')).encode()).hexdigest()
|
||||
|
||||
def records():
|
||||
return [store_record_for(x).__dict__ for x in (APP, BINDING)]
|
||||
|
||||
class Refused(ValueError):
|
||||
pass
|
||||
|
||||
def validate_grant(grant, *, expected_grant_sha256, actual_executor_sha256, now):
|
||||
if (type(grant) is not dict or set(grant) != KEYS
|
||||
or grant['schema'] != SCHEMA or grant['proposal_id'] != PROPOSAL
|
||||
or grant['authority'] != 'founder-approved-one-time-administrative-exception'
|
||||
or grant['memo_id'] != 'infd-20261011-user-catalogue-bootstrap-r1'
|
||||
or type(grant['memo_version']) is not int or grant['memo_version'] != 1
|
||||
or not re.fullmatch(r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}', grant['approval_id'])
|
||||
or not re.fullmatch(r'[a-zA-Z0-9:._-]{3,80}', grant['operator_id'])):
|
||||
raise Refused('closed grant required')
|
||||
for key in ('approval_receipt_sha256', 'executor_sha256', 'records_sha256'):
|
||||
if not re.fullmatch(r'[0-9a-f]{64}', grant[key]):
|
||||
raise Refused('exact evidence digests required')
|
||||
expires = datetime.fromisoformat(grant['expires_at'].replace('Z', '+00:00'))
|
||||
if (expires.tzinfo is None or now.tzinfo is None or now >= expires
|
||||
or expires > datetime(2026, 10, 12, 23, 59, 59, tzinfo=timezone.utc)):
|
||||
raise Refused('expired grant')
|
||||
if (digest(grant) != expected_grant_sha256 or grant['executor_sha256'] != actual_executor_sha256
|
||||
or grant['records_sha256'] != digest(records())):
|
||||
raise Refused('approved source differs')
|
||||
return expires
|
||||
|
||||
def _summary(cursor, audit_id):
|
||||
cursor.execute('SELECT summary FROM user_engine_audit_records WHERE audit_id = %s', (audit_id,))
|
||||
row = cursor.fetchone()
|
||||
return None if row is None else json.loads(row[0])
|
||||
|
||||
def _event_ids(cursor, correlation):
|
||||
cursor.execute('SELECT event_id FROM user_engine_outbox_events WHERE correlation_id = %s ORDER BY event_id', (correlation,))
|
||||
return [r[0] for r in cursor.fetchall()]
|
||||
|
||||
def _actor(grant):
|
||||
return Actor(issuer='urn:supervised-maintenance', subject=grant['operator_id'], tenant='tenant:platform',
|
||||
principal_type=PrincipalType.SERVICE, audience=(), claims={'maintenance_proposal': PROPOSAL})
|
||||
|
||||
class _Capture:
|
||||
def __init__(self, ordinary):
|
||||
self.ordinary = ordinary
|
||||
self.result = None
|
||||
def check(self, request):
|
||||
self.result = self.ordinary.check(request)
|
||||
return self.result
|
||||
|
||||
class _ExceptionAuthorization:
|
||||
def __init__(self, actor, correlation, expires):
|
||||
self.actor, self.correlation, self.expires = actor, correlation, expires
|
||||
self.used = False
|
||||
def check(self, request):
|
||||
now = datetime.now(timezone.utc)
|
||||
if (self.used or now >= self.expires or request.actor != self.actor
|
||||
or request.action != 'application.register' or request.tenant != 'tenant:platform'
|
||||
or request.resource_type != 'user-engine:application'
|
||||
or request.resource_id != APP.application_id or request.application_id != APP.application_id
|
||||
or request.correlation_id != self.correlation):
|
||||
raise Refused('exception outside fixed registration')
|
||||
self.used = True
|
||||
return AuthorizationDecision(effect=AuthorizationEffect.ALLOW, reason='explicit supervised maintenance exception; not a PDP allow',
|
||||
decision_id=None, binding='supervised-maintenance', issued_at=now,
|
||||
lifetime=min(timedelta(seconds=30), self.expires-now))
|
||||
|
||||
def execute(connection, ordinary_authorization, grant, *, expected_grant_sha256,
|
||||
actual_executor_sha256, mode='dry-run', now=None):
|
||||
now = now or datetime.now(timezone.utc)
|
||||
expires = validate_grant(grant, expected_grant_sha256=expected_grant_sha256,
|
||||
actual_executor_sha256=actual_executor_sha256, now=now)
|
||||
if mode not in ('dry-run', 'apply', 'rollback'):
|
||||
raise Refused('unknown operation')
|
||||
store = PostgresUserEngineStore(connection)
|
||||
if not store.ready:
|
||||
raise Refused('existing schema required; no migration')
|
||||
capture = _Capture(ordinary_authorization)
|
||||
service = UserEngineService(store=store, identity_adapter=object(), authorization=capture)
|
||||
actor = _actor(grant)
|
||||
ledger = 'aud_catalogue_bootstrap_' + grant['approval_id']
|
||||
correlation = 'maintenance:' + grant['approval_id']
|
||||
# Construct both services before BEGIN: the owner readiness probe rolls back
|
||||
# its connection. Constructing a service in the transaction would discard
|
||||
# the genuine denial audit and release the native serialization lock.
|
||||
exception_service = UserEngineService(store=store, identity_adapter=object(),
|
||||
authorization=_ExceptionAuthorization(actor, correlation, expires))
|
||||
outcome = None
|
||||
try:
|
||||
with store.transaction():
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE')
|
||||
cursor.execute('SELECT pg_advisory_xact_lock(hashtextextended(%s, 0))', ('user-engine:one-app-catalogue:informed-decision',))
|
||||
cursor.execute('SELECT clock_timestamp()')
|
||||
if cursor.fetchone()[0] >= expires:
|
||||
raise Refused('native transaction grant expired')
|
||||
previous = _summary(cursor, ledger)
|
||||
rollback_done = _summary(cursor, ledger + '_rollback')
|
||||
if mode == 'rollback':
|
||||
if previous is None or rollback_done is not None or not previous['created']:
|
||||
raise Refused('no unused created-only recovery authority')
|
||||
if previous['grant_sha256'] != expected_grant_sha256:
|
||||
raise Refused('recovery grant differs from consumed authority')
|
||||
if store.application(APP.application_id) != APP or store.binding(APP.application_id) != BINDING:
|
||||
raise Refused('created metadata changed')
|
||||
cursor.execute("SELECT COUNT(*) FROM user_engine_records WHERE application_id = %s AND record_type NOT IN ('applications', 'application_bindings')", (APP.application_id,))
|
||||
if cursor.fetchone()[0]:
|
||||
raise Refused('later dependent records exist')
|
||||
# Match the existing delivery worker's session advisory lock namespace.
|
||||
for event_id in previous['event_ids']:
|
||||
cursor.execute('SELECT pg_advisory_xact_lock(hashtextextended(%s, 0))', ('user-engine:tenant-lifecycle:outbox-event:' + event_id,))
|
||||
for event_id in previous['event_ids']:
|
||||
cursor.execute('SELECT event_id FROM user_engine_outbox_events WHERE event_id = %s FOR UPDATE', (event_id,))
|
||||
if cursor.fetchone() is None:
|
||||
raise Refused('created event missing')
|
||||
event = store.outbox_event(event_id)
|
||||
if (event is None or event.correlation_id != correlation or event.claimed_by is not None
|
||||
or event.claimed_at is not None or event.delivered_at is not None):
|
||||
raise Refused('claimed/delivered event needs owner reconciliation')
|
||||
store.save_outbox(replace(event, dead_lettered_at=now,
|
||||
failure_reason='supervised bootstrap rolled back before delivery'))
|
||||
for row in records():
|
||||
cursor.execute('DELETE FROM user_engine_records WHERE record_type = %s AND record_key = %s AND payload = %s::jsonb',
|
||||
(row['record_type'], row['record_key'], json.dumps(row['payload'])))
|
||||
if cursor.rowcount != 1:
|
||||
raise Refused('created-only compare-and-delete failed')
|
||||
store.append_audit(AuditRecord(ledger + '_rollback', actor, 'application.metadata.bootstrap.rollback', APP.application_id,
|
||||
actor.tenant, correlation, summary=json.dumps({'maintenance_authority': grant['approval_id'], 'grant_sha256': expected_grant_sha256,
|
||||
'removed': ['applications', 'application_bindings'], 'events_retained_dead_lettered': previous['event_ids']})))
|
||||
outcome = {'result': 'rolled-back', 'ordinary_pdp_allow_claimed': False, 'created_entities_removed': 2, 'audit_and_event_history_retained': True}
|
||||
else:
|
||||
if previous is not None:
|
||||
raise Refused('native single-use grant already consumed')
|
||||
a, b = store.application(APP.application_id), store.binding(APP.application_id)
|
||||
if (a is None) != (b is None) or (a is not None and (a != APP or b != BINDING)):
|
||||
raise Refused('partial or conflicting metadata')
|
||||
created = a is None
|
||||
if created:
|
||||
try:
|
||||
service.register_application(actor, APP, binding=BINDING, correlation_id=correlation)
|
||||
except AuthorizationDenied:
|
||||
refusal = capture.result
|
||||
if (refusal is None or refusal.allowed or refusal.decision_id is None
|
||||
or refusal.reason == 'authorization service unavailable'):
|
||||
raise Refused('real ordinary PDP refusal required')
|
||||
else:
|
||||
raise Refused('ordinary authorization unexpectedly allowed; use normal owner path')
|
||||
exception_service.register_application(actor, APP, binding=BINDING, correlation_id=correlation)
|
||||
if store.application(APP.application_id) != APP or store.binding(APP.application_id) != BINDING:
|
||||
raise Refused('post-write readback differs')
|
||||
event_ids = _event_ids(cursor, correlation)
|
||||
for event_id in event_ids:
|
||||
event = store.outbox_event(event_id)
|
||||
store.save_outbox(replace(event, payload={**event.payload, 'maintenance_exception': True,
|
||||
'proposal_id': PROPOSAL, 'approval_id': grant['approval_id'], 'operator_id': actor.subject}))
|
||||
summary = {'created': created, 'grant_sha256': expected_grant_sha256, 'records_sha256': grant['records_sha256'],
|
||||
'executor_sha256': actual_executor_sha256, 'approval_receipt_sha256': grant['approval_receipt_sha256'],
|
||||
'ordinary_pdp_denial_id': capture.result.decision_id if created else None, 'event_ids': event_ids,
|
||||
'authority': grant['authority'], 'not_human_authentication': True}
|
||||
store.append_audit(AuditRecord(ledger, actor, 'application.metadata.bootstrap', APP.application_id,
|
||||
actor.tenant, correlation, summary=json.dumps(summary)))
|
||||
outcome = {'result': 'created' if created else 'no-change', 'ordinary_pdp_refusal_retained': created,
|
||||
'ordinary_pdp_allow_claimed': False, 'created_entities': 2 if created else 0,
|
||||
'created_event_ids': event_ids, 'grant_native_single_use': True}
|
||||
cursor.execute('SELECT clock_timestamp()')
|
||||
if cursor.fetchone()[0] >= expires:
|
||||
raise Refused('grant expired before transaction commit')
|
||||
if mode == 'dry-run':
|
||||
raise _DryRun(outcome)
|
||||
except _DryRun as dry:
|
||||
return {**dry.outcome, 'transaction_committed': False, 'dry_run_rolled_back': True}
|
||||
return {**outcome, 'transaction_committed': True}
|
||||
|
||||
class _DryRun(Exception):
|
||||
def __init__(self, outcome):
|
||||
self.outcome = outcome
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--grant-file', required=True, type=Path)
|
||||
parser.add_argument('--expected-grant-sha256', required=True)
|
||||
parser.add_argument('--mode', choices=['dry-run','apply','rollback'], default='dry-run')
|
||||
args = parser.parse_args()
|
||||
# Whole stdout/stderr, including dependency/driver errors, stays contained.
|
||||
saved = os.dup(1)
|
||||
null = os.open(os.devnull, os.O_WRONLY)
|
||||
os.dup2(null, 1); os.dup2(null, 2)
|
||||
try:
|
||||
info = args.grant_file.lstat()
|
||||
if (not stat.S_ISREG(info.st_mode) or info.st_uid != os.getuid()
|
||||
or stat.S_IMODE(info.st_mode) != 0o600 or info.st_size > 8192):
|
||||
raise Refused('private reviewed grant artifact required')
|
||||
grant = json.loads(args.grant_file.read_text())
|
||||
source_hash = hashlib.sha256(Path(__file__).read_bytes()).hexdigest()
|
||||
import psycopg
|
||||
from user_engine.adapters.flex_auth import FlexAuthHTTPAdapter
|
||||
ordinary = FlexAuthHTTPAdapter(base_url=os.environ['USER_ENGINE_FLEX_AUTH_URL'],
|
||||
bearer_token_file=os.environ['USER_ENGINE_FLEX_AUTH_TOKEN_FILE'], timeout_seconds=3)
|
||||
with psycopg.connect(os.environ['USER_ENGINE_DATABASE_URL']) as connection:
|
||||
result = execute(connection, ordinary, grant, expected_grant_sha256=args.expected_grant_sha256,
|
||||
actual_executor_sha256=source_hash, mode=args.mode)
|
||||
status = 0
|
||||
except Exception:
|
||||
result, status = {'result': 'refused-or-rolled-back', 'raw_output_suppressed': True}, 2
|
||||
os.dup2(saved, 1); os.close(saved); os.close(null)
|
||||
print(json.dumps(result, sort_keys=True))
|
||||
return status
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
62
tools/run_approved_catalogue_bootstrap.py
Normal file
62
tools/run_approved_catalogue_bootstrap.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
"""Prepared supervisor-only transport. NEVER run before founder exception approval.
|
||||
No credentials leave the workload. Source/grant are delivered only on stdin.
|
||||
"""
|
||||
import argparse,base64,hashlib,json,os,stat,subprocess,sys
|
||||
from pathlib import Path
|
||||
|
||||
def main():
|
||||
p=argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument('--executor',type=Path,required=True)
|
||||
p.add_argument('--executor-sha256',required=True)
|
||||
p.add_argument('--grant-file',type=Path,required=True)
|
||||
p.add_argument('--grant-sha256',required=True)
|
||||
p.add_argument('--expected-pod-uid',required=True)
|
||||
p.add_argument('--mode',choices=['dry-run','apply','rollback'],default='dry-run')
|
||||
a=p.parse_args()
|
||||
try:
|
||||
st=a.grant_file.lstat()
|
||||
if not stat.S_ISREG(st.st_mode) or st.st_uid!=os.getuid() or stat.S_IMODE(st.st_mode)!=0o600 or st.st_size>8192:raise ValueError('private grant required')
|
||||
source=a.executor.read_bytes();grant=json.loads(a.grant_file.read_text())
|
||||
sha=lambda b:hashlib.sha256(b).hexdigest()
|
||||
canonical=lambda x:json.dumps(x,sort_keys=True,separators=(',',':')).encode()
|
||||
if sha(source)!=a.executor_sha256 or sha(canonical(grant))!=a.grant_sha256:raise ValueError('source/grant mismatch')
|
||||
packet={'source':base64.b64encode(source).decode(),'executor_sha256':a.executor_sha256,'grant':grant,'grant_sha256':a.grant_sha256,'mode':a.mode}
|
||||
native="""
|
||||
import base64,hashlib,json,os,sys
|
||||
payload=json.loads(base64.b64decode('__PACKET__'))
|
||||
saved=os.dup(1);null=os.open(os.devnull,os.O_WRONLY);os.dup2(null,1);os.dup2(null,2)
|
||||
try:
|
||||
source=base64.b64decode(payload['source'])
|
||||
if hashlib.sha256(source).hexdigest()!=payload['executor_sha256']:raise ValueError('source differs')
|
||||
ns={'__name__':'supervised_catalogue_maintenance'};exec(compile(source,'<reviewed-maintenance-executor>','exec'),ns)
|
||||
import psycopg
|
||||
from user_engine.adapters.flex_auth import FlexAuthHTTPAdapter
|
||||
ordinary=FlexAuthHTTPAdapter(base_url=os.environ['USER_ENGINE_FLEX_AUTH_URL'],bearer_token_file=os.environ['USER_ENGINE_FLEX_AUTH_TOKEN_FILE'],timeout_seconds=3)
|
||||
with psycopg.connect(os.environ['USER_ENGINE_DATABASE_URL']) as conn:
|
||||
result=ns['execute'](conn,ordinary,payload['grant'],expected_grant_sha256=payload['grant_sha256'],actual_executor_sha256=payload['executor_sha256'],mode=payload['mode'])
|
||||
except Exception:result={'result':'refused-or-rolled-back','raw_output_suppressed':True}
|
||||
os.dup2(saved,1);os.close(saved);os.close(null);print(json.dumps(result,sort_keys=True))
|
||||
""".replace('__PACKET__',base64.b64encode(canonical(packet)).decode())
|
||||
remote="""
|
||||
import base64,json,subprocess,sys
|
||||
r=subprocess.run(['kubectl','get','pods','-n','user-engine','-l','app.kubernetes.io/name=user-engine','-o','json'],capture_output=True,text=True)
|
||||
try:
|
||||
pods=[p for p in json.loads(r.stdout)['items'] if p['metadata']['uid']=='__UID__' and p.get('status',{}).get('phase')=='Running' and any(c['type']=='Ready' and c['status']=='True' for c in p.get('status',{}).get('conditions',[]))]
|
||||
if r.returncode or len(pods)!=1:raise ValueError('fixed pod not ready')
|
||||
pod=pods[0];portal=[c for c in pod['spec']['containers'] if c['name']=='portal']
|
||||
if len(portal)!=1 or portal[0]['image']!='forgejo.coulomb.social/coulomb/user-engine@sha256:f239bbf332b8eb87320c86390fddca1dcb4f7c628a7af75e79dee8058d446a7b':raise ValueError('source runtime differs')
|
||||
r=subprocess.run(['kubectl','exec','-i','-n','user-engine',pod['metadata']['name'],'-c','portal','--','python3','-'],input=base64.b64decode('__NATIVE__'),capture_output=True)
|
||||
out=json.loads(r.stdout)
|
||||
if r.returncode:raise ValueError('exec failure')
|
||||
except Exception:out={'result':'refused-or-rolled-back','raw_output_suppressed':True}
|
||||
print(json.dumps(out,sort_keys=True))
|
||||
""".replace('__UID__',a.expected_pod_uid).replace('__NATIVE__',base64.b64encode(native.encode()).decode())
|
||||
import uuid
|
||||
uuid.UUID(a.expected_pod_uid) # only UUID reaches the fixed remote literal
|
||||
r=subprocess.run(['ssh','railiance01','python3 -'],input=remote,text=True,capture_output=True)
|
||||
out=json.loads(r.stdout)
|
||||
if r.returncode:raise ValueError('transport failure')
|
||||
except Exception:out={'result':'refused-or-rolled-back','raw_output_suppressed':True}
|
||||
print(json.dumps(out,sort_keys=True))
|
||||
return 0 if out.get('result') in ('created','no-change','rolled-back') else 2
|
||||
if __name__=='__main__':raise SystemExit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue