Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a06ecb-456a-71c2-b41e-0755d336e883
67 lines
3.6 KiB
Python
67 lines
3.6 KiB
Python
"""Hash-pinned native recovery receipts, with original completion timestamps."""
|
|
import hashlib
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
from service_assurance import timestamp
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def recovery_signals(now, root=ROOT):
|
|
index = json.loads((root / 'assurance/recovery-evidence.json').read_text())
|
|
if index['schema'] != 'railiance-platform.recovery-evidence.v1':
|
|
raise ValueError('unknown recovery index')
|
|
signals = {}
|
|
for entry in index['receipts']:
|
|
signal = entry['signal']
|
|
if signal in signals or signal not in ('apps-pg.restore', 'forgejo-db.restore', 'openbao.snapshot'):
|
|
raise ValueError('unexpected recovery signal')
|
|
sample = {'result': 'unavailable', 'observed_at': now.isoformat()}
|
|
try:
|
|
path = (root / entry['path']).resolve()
|
|
allowed = [root / 'docs/evidence']
|
|
if signal == 'openbao.snapshot':
|
|
allowed.append(root / 'reviews')
|
|
if not any(path.is_relative_to(directory.resolve()) for directory in allowed):
|
|
raise ValueError('receipt outside evidence directory')
|
|
raw = path.read_bytes()
|
|
if hashlib.sha256(raw).hexdigest() != entry['sha256']:
|
|
raise ValueError('receipt drift')
|
|
receipt = json.loads(raw)
|
|
if signal == 'openbao.snapshot':
|
|
required = ('snapshot_created', 'source_initialized', 'source_unsealed',
|
|
'snapshot_encrypted', 'encrypted_copy_off_host',
|
|
'encryption_verified', 'hash_verified', 'no_secret_material_recorded')
|
|
if (receipt.get('receipt_version') != 1
|
|
or receipt.get('source_cluster') != 'railiance01'
|
|
or receipt.get('source_namespace') != 'openbao'
|
|
or receipt.get('cluster_id') != 'fd28df5d-98ec-57dd-42ec-9b3e4f4e53bf'
|
|
or not all(receipt.get(key) is True for key in required)
|
|
or not receipt.get('encrypted_location_ref', '').startswith('offhost-custody:')):
|
|
raise ValueError('snapshot not accepted')
|
|
for key in ('snapshot_sha256', 'encrypted_snapshot_sha256'):
|
|
value = receipt.get(key, '')
|
|
if not re.fullmatch(r'sha256:[0-9a-f]{64}', value):
|
|
raise ValueError('snapshot hash missing')
|
|
if timestamp(receipt['created_at']) > now:
|
|
raise ValueError('future snapshot')
|
|
signals[signal] = {'result': 'pass', 'observed_at': receipt['created_at']}
|
|
continue
|
|
cell = signal.removesuffix('.restore')
|
|
if (receipt['schema'] != 'platform.scaleway-primary-restore.v1'
|
|
or receipt['primary_destination'] != f's3://railiance-platform-pg-backup/platform-pg/{cell}/'
|
|
or receipt['source'] != 'Scaleway Barman base backup and WAL'
|
|
or receipt['stage'] != 'database_acceptance'
|
|
or receipt['status'] != 'verified'
|
|
or receipt['cleanup'] is not True
|
|
or receipt['production_ready'] is not True):
|
|
raise ValueError('receipt not accepted')
|
|
completed = timestamp(receipt['finished_at'])
|
|
if not timestamp(receipt['started_at']) <= completed <= now:
|
|
raise ValueError('invalid receipt chronology')
|
|
sample = {'result': 'pass', 'observed_at': receipt['finished_at']}
|
|
except (OSError, ValueError, KeyError, TypeError):
|
|
pass
|
|
signals[signal] = sample
|
|
return signals
|