Prepare immutable caller rollout and independent Scaleway recovery custody
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a11fd9-caf6-7a63-b578-9daaaa134ff2
This commit is contained in:
parent
3e18f11afe
commit
9a2cf213d1
19 changed files with 1543 additions and 4 deletions
183
tests/test_closure_custody.py
Normal file
183
tests/test_closure_custody.py
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
"""Synthetic exact-custody and offline-image guards; no operator/provider access."""
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def module(name, monkeypatch):
|
||||
scripts = Path(__file__).resolve().parents[1] / "scripts"
|
||||
monkeypatch.syspath_prepend(str(scripts))
|
||||
spec = importlib.util.spec_from_file_location(name.replace("-", "_"), scripts / (name + ".py"))
|
||||
result = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(result)
|
||||
return result
|
||||
|
||||
|
||||
@pytest.mark.parametrize("failure", [None, "write", "readback"])
|
||||
def test_caller_writer_uses_cas0_and_revokes_on_every_outcome(monkeypatch, failure):
|
||||
caller = module("admit-scoped-caller", monkeypatch)
|
||||
value = "synthetic-caller-credential"
|
||||
monkeypatch.setattr(caller.secrets, "token_urlsafe", lambda *args: value)
|
||||
calls = []
|
||||
def transport(args, payload=None, token=None, missing=False):
|
||||
calls.append((args, payload, token))
|
||||
if args[0] == "token":
|
||||
return json.dumps({"auth": {"client_token": "synthetic-writer",
|
||||
"policies": [caller.WRITER], "lease_duration": 300}}).encode()
|
||||
if "auth/token/revoke-self" in args:
|
||||
return b"{}"
|
||||
if "platform/metadata/workloads/email-connect/callers/user-engine" in args:
|
||||
return None
|
||||
if args[0] == "write":
|
||||
assert payload == {"options": {"cas": 0}, "data": {"TOKEN": value}}
|
||||
if failure == "write":
|
||||
raise caller.Refused("synthetic_failure")
|
||||
return b'{"data":{"version":1}}'
|
||||
if failure == "readback":
|
||||
raise caller.Refused("synthetic_failure")
|
||||
return json.dumps({"data": {"data": {"TOKEN": value}, "metadata": {"version": 1}}}).encode()
|
||||
if failure:
|
||||
with pytest.raises(caller.Refused):
|
||||
caller.provision(transport)
|
||||
else:
|
||||
receipt = caller.provision(transport)
|
||||
assert receipt["kv_version"] == 1 and receipt["readback_equal"]
|
||||
assert value not in json.dumps(receipt)
|
||||
assert calls[-1][0] == ["write", "auth/token/revoke-self"]
|
||||
assert calls[-1][2] == "synthetic-writer"
|
||||
|
||||
|
||||
def test_caller_writer_rejects_inherited_authority_before_any_secret_access(monkeypatch):
|
||||
caller = module("admit-scoped-caller", monkeypatch)
|
||||
seen = []
|
||||
def transport(args, *rest, **kwargs):
|
||||
seen.append(args)
|
||||
if args[0] == "token":
|
||||
return json.dumps({"auth": {"client_token": "synthetic-writer",
|
||||
"policies": [caller.WRITER], "identity_policies": ["platform-admin"],
|
||||
"lease_duration": 300}}).encode()
|
||||
return b"{}"
|
||||
with pytest.raises(caller.Refused, match="bounded_writer_required"):
|
||||
caller.provision(transport)
|
||||
assert len(seen) == 2 and seen[-1] == ["write", "auth/token/revoke-self"]
|
||||
|
||||
|
||||
def test_unreviewed_plan_refuses_before_decision_network_call(monkeypatch):
|
||||
caller = module("admit-scoped-caller", monkeypatch)
|
||||
monkeypatch.setattr(caller, "urlopen", lambda *a, **kw: pytest.fail("network forbidden"))
|
||||
with pytest.raises(caller.Refused, match="reviewed_plan_hash_required"):
|
||||
caller.approved_plan({}, "unreviewed", None, "http://synthetic.invalid")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("protected,ephemeral,corrupt", [
|
||||
(True, None, False), (False, None, False), (True, {}, False), (True, None, True),
|
||||
])
|
||||
def test_scaleway_key_escrow_requires_exact_protected_nonexpiring_readback(monkeypatch, protected, ephemeral, corrupt):
|
||||
escrow = module("scaleway-recovery-custody", monkeypatch)
|
||||
identity = b"synthetic-only-private-identity"
|
||||
secret_id = "00000000-0000-0000-0000-000000000001"
|
||||
project_id = "00000000-0000-0000-0000-000000000002"
|
||||
monkeypatch.setattr(escrow, "recipient", lambda value: escrow.RECIPIENT)
|
||||
seen = []
|
||||
def transport(secret, suffix="", payload=None):
|
||||
seen.append((secret, suffix, payload))
|
||||
if suffix == "":
|
||||
return {"name": escrow.NAME, "project_id": project_id, "region": escrow.REGION,
|
||||
"type": "opaque", "protected": protected, "ephemeral_policy": ephemeral}
|
||||
if "?" in suffix:
|
||||
return {"total_count": 0, "versions": []}
|
||||
if payload is not None:
|
||||
assert base64.b64decode(payload["data"]) == identity
|
||||
assert payload["disable_previous"] is False
|
||||
return {"revision": 1}
|
||||
return {"secret_id": secret_id, "revision": 1,
|
||||
"data": base64.b64encode(b"synthetic-corrupt" if corrupt else identity).decode()}
|
||||
if protected and ephemeral is None and not corrupt:
|
||||
result = escrow.escrow(secret_id, project_id, identity, transport)
|
||||
assert result["readback_equal"] and not result["worker_access_denial_verified"]
|
||||
assert identity.decode() not in json.dumps(result)
|
||||
else:
|
||||
with pytest.raises(escrow.Refused):
|
||||
escrow.escrow(secret_id, project_id, identity, transport)
|
||||
if not protected or ephemeral is not None:
|
||||
assert len(seen) == 1
|
||||
|
||||
|
||||
def test_offline_recovery_image_is_bound_to_export_and_capture_image(monkeypatch, tmp_path):
|
||||
rehearsal = module("rehearse-ledger", monkeypatch)
|
||||
image = tmp_path / "receiver-image.tar"
|
||||
image.write_bytes(b"synthetic-image-export")
|
||||
image.chmod(0o600)
|
||||
manifest = tmp_path / "manifest.json"
|
||||
manifest.write_text(json.dumps({"schema": "email-connect.ledger-recovery-kit.v1",
|
||||
"image": rehearsal.IMAGE, "image_id": "sha256:" + "a" * 64,
|
||||
"files": {"receiver-image.tar": {"sha256": hashlib.sha256(image.read_bytes()).hexdigest(),
|
||||
"bytes": image.stat().st_size}}}))
|
||||
manifest.chmod(0o600)
|
||||
assert rehearsal.recovery_image(manifest) == "sha256:" + "a" * 64
|
||||
image.write_bytes(b"synthetic-changed-export")
|
||||
with pytest.raises(rehearsal.Refused, match="offline_image_archive_changed"):
|
||||
rehearsal.recovery_image(manifest)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("lifecycle,corrupt", [(False, False), (True, False), (False, True)])
|
||||
def test_kit_requires_no_expiry_and_separate_version_hash_readback(monkeypatch, tmp_path, lifecycle, corrupt):
|
||||
import io
|
||||
publisher = module("publish-ledger-recovery-kit", monkeypatch)
|
||||
kit = tmp_path / "kit.tar.gz"
|
||||
content = b"synthetic-kit-with-no-private-identity"
|
||||
kit.write_bytes(content)
|
||||
expected = {"status": "recovery_kit_prepared", "private_identity_included": False,
|
||||
"kit_sha256": hashlib.sha256(content).hexdigest(), "kit_bytes": len(content)}
|
||||
monkeypatch.setattr(publisher, "private_directory", lambda path: None)
|
||||
calls = []
|
||||
class Owner:
|
||||
def get_bucket_versioning(self, **kwargs): return {"Status": "Enabled"}
|
||||
def get_bucket_lifecycle_configuration(self, **kwargs):
|
||||
return {"Rules": [{"Expiration": {"Days": 30}}]} if lifecycle else {"Rules": []}
|
||||
def put_object(self, **kwargs):
|
||||
calls.append(kwargs)
|
||||
assert kwargs["IfNoneMatch"] == "*"
|
||||
return {"VersionId": "synthetic-kit-version"}
|
||||
class Reader:
|
||||
def get_object(self, **kwargs):
|
||||
assert kwargs["VersionId"] == "synthetic-kit-version"
|
||||
return {"VersionId": "synthetic-kit-version", "ContentLength": len(content),
|
||||
"Body": io.BytesIO(b"synthetic-corrupt" if corrupt else content)}
|
||||
if lifecycle or corrupt:
|
||||
with pytest.raises(publisher.Refused):
|
||||
publisher.publish(Owner(), Reader(), kit, expected, tmp_path / "readback")
|
||||
if lifecycle:
|
||||
assert calls == []
|
||||
else:
|
||||
result = publisher.publish(Owner(), Reader(), kit, expected, tmp_path / "readback")
|
||||
assert result["separate_reader_readback_verified"]
|
||||
assert not result["reader_application_binding_verified"]
|
||||
assert not result["private_identity_included"]
|
||||
|
||||
|
||||
def test_low_disk_headroom_refuses_before_capture(monkeypatch, tmp_path):
|
||||
from types import SimpleNamespace
|
||||
backup = module("backup-ledger", monkeypatch)
|
||||
monkeypatch.setattr(backup.shutil, "disk_usage", lambda *args: SimpleNamespace(free=1023))
|
||||
with pytest.raises(backup.Refused, match="backup_disk_headroom_required"):
|
||||
backup.capacity(tmp_path / "not-created", minimum_free=1024)
|
||||
assert not (tmp_path / "not-created").exists()
|
||||
|
||||
|
||||
def test_retention_budget_refuses_without_deleting_ciphertext(monkeypatch, tmp_path):
|
||||
from types import SimpleNamespace
|
||||
backup = module("backup-ledger", monkeypatch)
|
||||
state = tmp_path / "state"
|
||||
state.mkdir(mode=0o700)
|
||||
archive = state / "synthetic.age"
|
||||
archive.write_bytes(b"synthetic-protected-ciphertext")
|
||||
monkeypatch.setattr(backup.shutil, "disk_usage", lambda *args: SimpleNamespace(free=10**10))
|
||||
with pytest.raises(backup.Refused, match="backup_local_retention_review_required"):
|
||||
backup.capacity(state, budget=512 * 1024**2)
|
||||
assert archive.read_bytes() == b"synthetic-protected-ciphertext"
|
||||
Loading…
Add table
Add a link
Reference in a new issue