NK-WP-0055-T13: kingpath-offsite helper seals break-glass material to Storage Share
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 4515@bnt-lap001 Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
This commit is contained in:
parent
e9bc86160f
commit
d7fde1f2ba
4 changed files with 309 additions and 0 deletions
127
tests/test_kingpath_offsite.py
Normal file
127
tests/test_kingpath_offsite.py
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
"""Break-glass bundle sealing and Storage Share publication on contained fixtures."""
|
||||
from datetime import datetime,timezone
|
||||
import importlib.util
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tarfile
|
||||
import urllib.error
|
||||
import pytest
|
||||
|
||||
ROOT=Path(__file__).resolve().parents[1]
|
||||
spec=importlib.util.spec_from_file_location('kingpath_offsite',ROOT/'tools/bootstrap-recovery/kingpath-offsite.py')
|
||||
k=importlib.util.module_from_spec(spec);spec.loader.exec_module(k)
|
||||
NOW=datetime(2026,10,27,22,0,tzinfo=timezone.utc)
|
||||
|
||||
|
||||
class Response(io.BytesIO):
|
||||
def __init__(self,url,data=b'',status=200,etag='"v1"'):
|
||||
super().__init__(data);self.url=url;self.status=status
|
||||
self.headers={'ETag':etag,'Content-Length':str(len(data))}
|
||||
def __enter__(self):return self
|
||||
def __exit__(self,*a):self.close()
|
||||
|
||||
|
||||
class Share:
|
||||
def __init__(self,folder_exists=False):
|
||||
self.objects={};self.folder=folder_exists;self.calls=[];self.tamper=False;self.collide=False
|
||||
def open(self,request,timeout=None):
|
||||
method=request.get_method();url=request.full_url;self.calls.append((method,url))
|
||||
assert request.headers['Authorization'].startswith('Basic ')
|
||||
if method=='MKCOL':
|
||||
if self.folder:raise urllib.error.HTTPError(url,405,'exists',{},None)
|
||||
self.folder=True;return Response(url,status=201)
|
||||
if method=='PUT':
|
||||
assert request.headers['If-none-match']=='*'
|
||||
self.objects[url]=request.data.read();return Response(url,status=201)
|
||||
if self.collide and method=='HEAD':return _head(url,b'other')
|
||||
if url not in self.objects:raise urllib.error.HTTPError(url,404,'missing',{},None)
|
||||
data=self.objects[url]
|
||||
if method=='HEAD':return _head(url,data)
|
||||
if self.tamper:data=data[:-1]+bytes([data[-1]^1])
|
||||
return Response(url,data)
|
||||
|
||||
|
||||
def _head(url,data):
|
||||
r=Response(url,b'');r.headers['Content-Length']=str(len(data));return r
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def key(tmp_path):
|
||||
identity=tmp_path/'fixture.key'
|
||||
subprocess.run(['age-keygen','-o',str(identity)],check=True,capture_output=True)
|
||||
recipient=subprocess.run(['age-keygen','-y',str(identity)],check=True,capture_output=True,text=True).stdout.strip()
|
||||
return identity,recipient
|
||||
|
||||
|
||||
def private(tmp_path,name,data,mode=0o600):
|
||||
path=tmp_path/name;path.write_bytes(data);path.chmod(mode);return path
|
||||
|
||||
|
||||
def test_seals_uploads_and_reads_back(tmp_path,key):
|
||||
identity,recipient=key;share=Share()
|
||||
a=private(tmp_path,'kc',b'synthetic-kubeconfig');b=private(tmp_path,'ssh',b'synthetic-ssh-key')
|
||||
receipt=tmp_path/'receipt.json'
|
||||
record=k.run([f'kubeconfig={a}',f'tunnel-ssh-key={b}'],'synthetic-app-password',receipt,share,recipient,NOW)
|
||||
assert record['status']=='readback_verified' and record['items']=={'kubeconfig':20,'tunnel-ssh-key':17}
|
||||
assert record['artifact'].startswith('net-kingdom-kingpath-20261027T220000Z-')
|
||||
assert oct(receipt.stat().st_mode&0o777)=='0o600'
|
||||
text=receipt.read_text();assert 'synthetic' not in text
|
||||
(stored,)=share.objects.values();assert stored.startswith(b'age-encryption.org/v1\n') and b'synthetic' not in stored
|
||||
plain=subprocess.run(['age','-d','-i',str(identity)],input=stored,capture_output=True,check=True).stdout
|
||||
with tarfile.open(fileobj=io.BytesIO(plain)) as archive:
|
||||
assert {m.name:(archive.extractfile(m).read(),m.mtime,m.mode) for m in archive}=={
|
||||
'kubeconfig':(b'synthetic-kubeconfig',0,0o600),'tunnel-ssh-key':(b'synthetic-ssh-key',0,0o600)}
|
||||
assert [c[0] for c in share.calls]==['MKCOL','HEAD','PUT','HEAD','GET']
|
||||
|
||||
|
||||
def test_existing_folder_is_accepted(tmp_path,key):
|
||||
share=Share(folder_exists=True)
|
||||
k.run([f'kubeconfig={private(tmp_path,"kc",b"x")}'],'pw',tmp_path/'r.json',share,key[1],NOW)
|
||||
assert len(share.objects)==1
|
||||
|
||||
|
||||
@pytest.mark.parametrize('mode',[0o640,0o604,0o644])
|
||||
def test_refuses_group_or_world_readable_input(tmp_path,key,mode):
|
||||
with pytest.raises(k.Refused):
|
||||
k.run([f'kubeconfig={private(tmp_path,"kc",b"x",mode)}'],'pw',tmp_path/'r.json',Share(),key[1],NOW)
|
||||
|
||||
|
||||
def test_refuses_symlink_bad_name_duplicate_and_empty(tmp_path,key):
|
||||
real=private(tmp_path,'kc',b'x');link=tmp_path/'link';link.symlink_to(real)
|
||||
for specs in ([f'kubeconfig={link}'],[f'Bad_Name={real}'],[f'a={real}',f'a={real}'],
|
||||
[f'a={private(tmp_path,"empty",b"")}'],[],[f'a={tmp_path/"missing"}']):
|
||||
with pytest.raises(k.Refused):k.run(specs,'pw',tmp_path/'r.json',Share(),key[1],NOW)
|
||||
|
||||
|
||||
def test_refuses_existing_receipt_before_any_provider_call(tmp_path,key):
|
||||
share=Share();receipt=private(tmp_path,'r.json',b'{}')
|
||||
with pytest.raises(k.Refused):k.run([f'a={private(tmp_path,"kc",b"x")}'],'pw',receipt,share,key[1],NOW)
|
||||
assert share.calls==[]
|
||||
|
||||
|
||||
def test_refuses_tampered_readback_and_writes_no_receipt(tmp_path,key):
|
||||
share=Share();share.tamper=True;receipt=tmp_path/'r.json'
|
||||
with pytest.raises(k.Refused):k.run([f'a={private(tmp_path,"kc",b"x")}'],'pw',receipt,share,key[1],NOW)
|
||||
assert not receipt.exists()
|
||||
|
||||
|
||||
def test_refuses_overwriting_an_existing_object(tmp_path,key):
|
||||
share=Share();share.collide=True
|
||||
with pytest.raises(k.Refused):k.run([f'a={private(tmp_path,"kc",b"x")}'],'pw',tmp_path/'r.json',share,key[1],NOW)
|
||||
assert 'PUT' not in [c[0] for c in share.calls]
|
||||
|
||||
|
||||
def test_refuses_bad_credential_and_recipient(tmp_path,key):
|
||||
item=private(tmp_path,'kc',b'x')
|
||||
for pw in ('','a\nb'):
|
||||
with pytest.raises(k.Refused):k.run([f'a={item}'],pw,tmp_path/'r.json',Share(),key[1],NOW)
|
||||
with pytest.raises(k.Refused):k.run([f'a={item}'],'pw',tmp_path/'r.json',Share(),'not-a-recipient',NOW)
|
||||
|
||||
|
||||
def test_production_recipient_is_the_registered_backup_recipient():
|
||||
profile=json.loads((ROOT/'tools/bootstrap-recovery/identity-backup-profile.json').read_text())
|
||||
assert k.RECIPIENT==profile['public_recipient']
|
||||
assert k.ACCOUNT_BASE=='https://nx4069.your-storageshare.de/remote.php/dav/files/Backup/railiance-backups/net-kingdom-kingpath/'
|
||||
Loading…
Add table
Add a link
Reference in a new issue