Prepare independent outage recovery and Storage Share documentation

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
This commit is contained in:
tegwick 2026-10-16 17:59:18 +02:00
parent 2a3800e7f1
commit 1764f40b43
12 changed files with 1050 additions and 22 deletions

View file

@ -0,0 +1,155 @@
"""Outage download boundaries, failure cleanup and independently usable kit."""
import hashlib
import importlib.util
import io
import json
from pathlib import Path
import subprocess
import sys
import urllib.error
import zipfile
import pytest
ROOT = Path(__file__).resolve().parents[1]
def load(name, root=ROOT):
spec = importlib.util.spec_from_file_location(name, root / 'tools/bootstrap-recovery' / (name + '.py'))
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
m = load('retrieve-identity-offline')
k = load('recovery-kit')
class Response(io.BytesIO):
def __init__(self, url, raw, etag='"original"', status=200):
super().__init__(raw)
self.url = url
self.status = status
self.headers = {'Content-Length': str(len(raw)), 'ETag': etag}
class Provider:
def __init__(self, body):
self.body = body
self.calls = []
self.tamper = False
self.etag = '"original"'
self.objects = {}
def open(self, request, timeout):
method = request.get_method()
self.calls.append((method, request.full_url, dict(request.headers)))
assert request.full_url.startswith((m.ACCOUNT_BASE, m.offsite.BASE))
if self.objects or method == 'PUT':
if method == 'PUT':
assert request.get_header('If-none-match') == '*'
assert request.full_url not in self.objects
self.objects[request.full_url] = request.data
return Response(request.full_url, b'', status=201)
if request.full_url not in self.objects:
raise urllib.error.HTTPError(request.full_url, 404, 'missing', {}, None)
return Response(request.full_url, self.objects[request.full_url])
raw = self.body if not self.tamper or method == 'HEAD' else b'X' * len(self.body)
return Response(request.full_url, raw, self.etag)
@pytest.fixture
def record():
return dict(schema='netkingdom.identity-offsite.v1', status='readback_verified',
provider='Storage Share', folder='Backup/railiance-backups/net-kingdom-identity',
artifact='net-kingdom-identity-20261016T063159Z-5882771aa2ce.tar.age',
ciphertext_bytes=32, ciphertext_sha256=hashlib.sha256(b'A' * 32).hexdigest(),
etag='"original"')
@pytest.mark.parametrize('mode', ['account', 'folder-share'])
def test_offline_success_uses_only_fixed_provider_reads(record, mode):
provider = Provider(b'A' * 32)
record['untrusted_extra'] = 'must-not-export'
result = m.retrieve(record, 'fixtureSecret1234', mode, provider)
try:
assert Path(result['fetched_path']).read_bytes() == b'A' * 32
assert not result['openbao_contacted'] and not result['sso_contacted']
assert {c[0] for c in provider.calls} == {'HEAD', 'GET'}
assert 'fixtureSecret1234' not in json.dumps(result)
assert 'must-not-export' not in json.dumps(result)
assert provider.calls[1][2]['If-match'] == record['etag']
finally:
path = Path(result['fetched_path'])
path.unlink()
path.parent.rmdir()
@pytest.mark.parametrize('field,value', [
('artifact', '../another.tar.age'), ('folder', 'other'),
('ciphertext_sha256', 'x' * 64), ('ciphertext_bytes', True),
('provider', 'other'), ('etag', 'W/"weak"'),
])
def test_unreviewed_scope_refuses_before_contact(record, field, value):
record[field] = value
provider = Provider(b'A' * 32)
with pytest.raises(m.offsite.Refused):
m.retrieve(record, 'fixtureSecret1234', 'account', provider)
assert not provider.calls
def test_digest_failure_removes_incomplete_ciphertext(record, monkeypatch, tmp_path):
directory = tmp_path / 'download'
directory.mkdir()
monkeypatch.setattr(m.tempfile, 'mkdtemp', lambda **kwargs: str(directory))
provider = Provider(b'A' * 32)
provider.tamper = True
with pytest.raises(m.offsite.Refused):
m.retrieve(record, 'fixtureSecret1234', 'account', provider)
assert not directory.exists()
def test_changed_etag_refuses_before_download(record):
provider = Provider(b'A' * 32)
provider.etag = '"changed"'
with pytest.raises(m.offsite.Refused):
m.retrieve(record, 'fixtureSecret1234', 'account', provider)
assert len(provider.calls) == 1
def test_kit_contains_all_offline_imports_and_checksums(tmp_path):
files = k.build()
archive = next(value for name, value in files.items() if name.endswith('.zip'))
with zipfile.ZipFile(io.BytesIO(archive)) as kit:
kit.extractall(tmp_path)
for line in kit.read('SHA256SUMS').decode().splitlines():
digest, name = line.split(' ', 1)
assert hashlib.sha256((tmp_path / name).read_bytes()).hexdigest() == digest
for script in ('verify-identity.py', 'retrieve-identity-offline.py'):
result = subprocess.run([sys.executable, str(tmp_path / 'tools/bootstrap-recovery' / script), '--help'],
capture_output=True)
assert result.returncode == 0, result.stderr
assert k.build() == files
def test_kit_publication_conditional_and_exact_readback():
provider = Provider(b'')
# Ensure initial HEAD returns 404 instead of the download-fixture response.
provider.objects['unrelated'] = b'preserved'
files = k.build()
rows = k.publish(files, {'Authorization': 'fixture'}, provider)
assert len(rows) == 3 and all(row['readback_verified'] for row in rows)
assert len([c for c in provider.calls if c[0] == 'PUT']) == 3
k.publish(files, {'Authorization': 'fixture'}, provider)
assert len([c for c in provider.calls if c[0] == 'PUT']) == 3
assert provider.objects['unrelated'] == b'preserved'
def test_kit_conflicting_existing_object_never_overwritten():
provider = Provider(b'')
files = k.build()
first = next(iter(files))
provider.objects[m.ACCOUNT_BASE + first] = b'X' * len(files[first])
with pytest.raises(k.offsite.Refused):
k.publish(files, {'Authorization': 'fixture'}, provider)
assert not any(c[0] == 'PUT' for c in provider.calls)