Record off-host database recovery and prepare identity backup coverage
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
This commit is contained in:
tegwick 2026-10-16 00:32:50 +02:00
parent a1939a7d06
commit dca999222f
13 changed files with 596 additions and 29 deletions

View file

@ -0,0 +1,54 @@
"""Preparation must never promote stale metadata or expand its data-read scope."""
import importlib.util
import json
from pathlib import Path
import pytest
ROOT=Path(__file__).resolve().parents[1]
spec=importlib.util.spec_from_file_location('prepare',ROOT/'tools/bootstrap-recovery/prepare-identity-coverage.py')
m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m)
def fixture():
profile=json.loads(m.PROFILE.read_text());objects={}
objects[(None,'namespace','kube-system')]={'metadata':{'uid':m.CLUSTER_UID}}
for c in profile['components']:
ns=c['namespace'];name=c['deployment']
objects[(ns,'deployment',name)]={'metadata':{'uid':c['deployment_uid'],'generation':9},
'status':{'observedGeneration':9,'readyReplicas':1},'spec':{'template':{'spec':{
'containers':[{'name':'service','env':[{'name':'DO_NOT_EXPORT','value':'private-value-canary'}],
'envFrom':[{'secretRef':{'name':n}} for n in c['secret_refs']]}],
'volumes':[{'configMap':{'name':n}} for n in c['configmap_refs']]+
[{'persistentVolumeClaim':{'claimName':p['name']}} for p in c['pvc_refs']]}}}}
for pvc in c['pvc_refs']:objects[(ns,'pvc',pvc['name'])]={'metadata':{'uid':pvc['uid']},'status':{'phase':'Bound'}}
calls=[]
def get(ns,kind,name):calls.append((ns,kind,name));return objects[(ns,kind,name)]
return profile,objects,calls,get
def test_only_safe_metadata_leaves_preparation_and_no_secret_api_is_called():
p,objects,calls,get=fixture();r=m.prepare(p,get)
assert r['status']=='prepared_not_admitted'
assert not any(r[k] for k in ['secret_values_read','backup_captured','uploaded','runtime_changed','credential_grant_created'])
assert 'private-value-canary' not in json.dumps(r)
assert all(kind in {'namespace','deployment','pvc'} for ns,kind,name in calls)
@pytest.mark.parametrize('change',['cluster','pvc','deployment','not-ready','unobserved','terminating','new-secret','projected-secret','new-pvc'])
def test_stale_or_expanded_live_state_requires_fresh_owner_review(change):
p,o,calls,get=fixture();d=o[('sso','deployment','lldap')];s=d['spec']['template']['spec']
if change=='cluster':o[(None,'namespace','kube-system')]['metadata']['uid']='other-cluster'
if change=='pvc':o[('sso','pvc','lldap-data')]['metadata']['uid']='replacement'
if change=='deployment':d['metadata']['uid']='replacement'
if change=='not-ready':d['status']['readyReplicas']=0
if change=='unobserved':d['status']['observedGeneration']=8
if change=='terminating':d['metadata']['deletionTimestamp']='2026-10-16'
if change=='new-secret':s['containers'][0]['envFrom'].append({'secretRef':{'name':'unreviewed-secret'}})
if change=='projected-secret':s['volumes'].append({'projected':{'sources':[{'secret':{'name':'unreviewed-secret'}}]}})
if change=='new-pvc':s['volumes'].append({'persistentVolumeClaim':{'claimName':'unreviewed-pvc'}})
with pytest.raises(m.Refused):m.prepare(p,get)
@pytest.mark.parametrize('kind',['secret','configmap','pods/exec','clusterrolebinding'])
def test_preparation_transport_has_no_private_data_or_mutation_route(kind):
with pytest.raises(m.Refused):m.query('sso',kind,'anything')