Record off-host database recovery and prepare identity backup coverage
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
This commit is contained in:
parent
a1939a7d06
commit
dca999222f
13 changed files with 596 additions and 29 deletions
54
tests/test_identity_backup_preparation.py
Normal file
54
tests/test_identity_backup_preparation.py
Normal 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')
|
||||
Loading…
Add table
Add a link
Reference in a new issue