Files
aios/tests/test_trusted_builder_signer_provisioning.py
T
AIOS Governed Continuation 91f104ef62
autonomy-smoke-tests focused receipt/replay and Task lifecycle smoke: PASS
autonomy-unit-tests full Linux regression: 834 passed, 3 skipped, 154 subtests passed
campaign-approval-gate standing Owner V15 exact Repair Task authorization and formal Task verification: PASS
git-diff-check exact two-file governed diff and whitespace check: PASS
py-compile active-runtime and tests compileall: PASS
reconciliation-contract WriterLease completed replay and governed branch convergence: PASS
release-state-machine full Linux regression includes release state-machine contract: PASS; no release mutation
secret-scan exact changed-file high-signal credential/private-key scan: PASS
signer-validator-contract receipt framing/replay/adoption contract: 18 focused tests PASS; private key non-exportable
fix(gitops): repair trusted-builder receipt replay framing
2026-08-30 00:05:00 +08:00

292 lines
14 KiB
Python

from __future__ import annotations
import json
import os
from pathlib import Path
import sys
import pytest
APP = Path(__file__).resolve().parents[1] / "active-runtime" / "app"
sys.path.insert(0, str(APP))
import trusted_builder_signer_provisioning as provisioning # noqa: E402
def _source(tmp_path: Path) -> tuple[Path, list[str]]:
root = tmp_path / "source"
for relative in provisioning.SOURCE_EVIDENCE_PATHS:
path = root / relative
path.parent.mkdir(parents=True, exist_ok=True)
if relative != provisioning.PACKAGE_MANIFEST_PATH:
path.write_text("source:" + relative + "\n", encoding="utf-8")
os.chmod(path, 0o644)
manifest = root / provisioning.PACKAGE_MANIFEST_PATH
manifest.write_bytes(provisioning.pretty_json(provisioning.package_contract_document()))
os.chmod(manifest, 0o644)
return root, list(provisioning.SOURCE_EVIDENCE_PATHS)
def _payload(tmp_path: Path):
source, paths = _source(tmp_path)
output = tmp_path / "payload.json"
value = provisioning.build_payload(
source, output, main_oid="c" * 40, tree_oid="d" * 40,
pr_number=70, pr_head_oid="e" * 40,
merge_parent_oids=["a" * 40, "e" * 40], source_files=paths,
nonce="test-provisioning-nonce-001",
)
return source, output, value
def test_fresh_payload_is_bound_to_final_common_main_and_ordered_pr_parent(tmp_path: Path):
_source_root, output, payload = _payload(tmp_path)
assert output.read_bytes().startswith(b"{")
provisioning._validate_payload(payload)
assert payload["source"]["gitea_main_oid"] == payload["source"]["governed_main_oid"]
assert payload["source"]["merge_parent_oids"][1] == payload["source"]["pr_head_oid"]
assert set(payload["source"]["source_evidence_sha256"]) == set(provisioning.SOURCE_EVIDENCE_PATHS)
assert set(payload["allowed_capabilities"]) == provisioning.ALLOWED_CAPABILITIES
assert set(payload["denied_capabilities"]) == provisioning.DENIED_CAPABILITIES
@pytest.mark.parametrize("field", ["allowed_capabilities", "denied_capabilities"])
def test_payload_rejects_capability_scope_tampering(tmp_path: Path, field: str):
_source_root, _output, payload = _payload(tmp_path)
payload[field] = ["owner_release_approval"]
with pytest.raises(provisioning.ProvisioningError, match="CAPABILITY_SCOPE_INVALID"):
provisioning._validate_payload(payload)
def test_payload_rejects_main_divergence_and_wrong_second_parent(tmp_path: Path):
_source_root, _output, payload = _payload(tmp_path)
payload["source"]["governed_main_oid"] = "f" * 40
with pytest.raises(provisioning.ProvisioningError, match="SOURCE_MAIN_NOT_CONVERGED"):
provisioning._validate_payload(payload)
_source_root, _output, payload = _payload(tmp_path / "second")
payload["source"]["merge_parent_oids"][1] = "a" * 40
with pytest.raises(provisioning.ProvisioningError, match="SOURCE_PR_HEAD_SECOND_PARENT_INVALID"):
provisioning._validate_payload(payload)
def test_payload_rejects_missing_or_extra_source_evidence(tmp_path: Path):
_source_root, _output, payload = _payload(tmp_path)
payload["source"]["source_evidence_sha256"].pop(next(iter(payload["source"]["source_evidence_sha256"])))
with pytest.raises(provisioning.ProvisioningError, match="SOURCE_EVIDENCE_SET_INVALID"):
provisioning._validate_payload(payload)
_source_root, _output, payload = _payload(tmp_path / "second")
payload["source"]["source_evidence_sha256"]["extra.txt"] = "0" * 64
with pytest.raises(provisioning.ProvisioningError, match="SOURCE_EVIDENCE_SET_INVALID"):
provisioning._validate_payload(payload)
def test_package_contract_rejects_incompatible_source_mode_and_tampering(tmp_path: Path):
source, _paths = _source(tmp_path)
service = source / "deploy/systemd/aios-trusted-candidate-builder.service"
os.chmod(service, 0o601)
with pytest.raises(provisioning.ProvisioningError, match="SOURCE_EVIDENCE_MODE_INVALID"):
provisioning.verify_package_contract(source)
os.chmod(service, 0o644)
manifest = source / provisioning.PACKAGE_MANIFEST_PATH
value = json.loads(manifest.read_text(encoding="utf-8"))
value["activation"]["credential_installation"] = True
manifest.write_text(json.dumps(value), encoding="utf-8")
with pytest.raises(provisioning.ProvisioningError, match="PACKAGE_CONTRACT_INVALID"):
provisioning.verify_package_contract(source)
def test_package_contract_declares_socket_activation_no_credentials_and_0755_launcher(tmp_path: Path):
source, _paths = _source(tmp_path)
provisioning.verify_package_contract(source)
value = json.loads((source / provisioning.PACKAGE_MANIFEST_PATH).read_text(encoding="utf-8"))
launcher = next(item for item in value["files"] if item["source_path"] == "deploy/libexec/aios-trusted-builder-signer-provisioner")
assert launcher["install_mode"] == "0755"
assert value["activation"] == {
"daemon_reload": True,
"enable_units": ["aios-trusted-candidate-builder.socket"],
"service_start": "socket-activation-only",
"credential_installation": False,
"signer_invocation": False,
}
def test_provisioner_self_test_has_no_mutation(capsys):
assert provisioning.main(["--self-test"]) == 0
assert capsys.readouterr().out.strip() == "PASS"
def _replay_fixture(tmp_path: Path):
_source_root, payload_path, payload = _payload(tmp_path)
raw = payload_path.read_bytes()
signature = tmp_path / "owner.sig"
signature.write_bytes(b"owner-signature-test")
os.chmod(signature, 0o600)
config = provisioning.ProvisionerConfig()
config.credential_store = tmp_path / "credstore"
config.public_keyring = tmp_path / "state" / "public-keyring.json"
config.binding_file = tmp_path / "state" / "service-principal-binding.json"
config.receipt_log = tmp_path / "state" / "provisioning-receipts.jsonl"
config.credential_store.mkdir(mode=0o700)
os.chmod(config.credential_store, 0o700)
config.public_keyring.parent.mkdir(mode=0o700)
public = b"p" * 32
now = provisioning.utc_now()
keyring = provisioning._keyring(payload["signing_identity"], public, now)
config.public_keyring.write_bytes(provisioning.pretty_json(keyring))
os.chmod(config.public_keyring, 0o600)
binding = {
"schema": "aios.trusted-builder.service-principal-binding.v1",
"purpose": provisioning.PURPOSE,
"service_principal": payload["signing_identity"]["service_principal"],
"key_id": payload["signing_identity"]["key_id"],
"generation": payload["signing_identity"]["key_generation"],
"algorithm": "Ed25519",
"private_key_export": False,
"public_key_sha256": provisioning.sha256_bytes(public),
"source_commit": payload["source"]["gitea_main_oid"],
"source_tree": payload["source"]["gitea_main_tree_oid"],
"payload_sha256": provisioning.sha256_bytes(raw),
}
config.binding_file.write_bytes(provisioning.pretty_json(binding))
os.chmod(config.binding_file, 0o600)
for name in ("signing_private_key", "builder_hmac"):
target = config.credential_store / name
target.write_bytes(("encrypted-" + name).encode())
os.chmod(target, 0o600)
signature_bytes = signature.read_bytes()
receipt = {
"schema": "aios.trusted-builder-signer-provisioning-receipt.v1",
"status": "COMPLETED",
"created_at": provisioning.iso(now),
"operation_key": "payload:" + provisioning.sha256_bytes(raw) + ":signature:" + provisioning.sha256_bytes(signature_bytes),
"payload_sha256": provisioning.sha256_bytes(raw),
"signature_sha256": provisioning.sha256_bytes(signature_bytes),
"source_commit": payload["source"]["gitea_main_oid"],
"source_tree": payload["source"]["gitea_main_tree_oid"],
"key_id": payload["signing_identity"]["key_id"],
"generation": payload["signing_identity"]["key_generation"],
"algorithm": "Ed25519",
"purpose": provisioning.PURPOSE,
"service_principal": payload["signing_identity"]["service_principal"],
"service_principal_bound": True,
"public_key_sha256": provisioning.sha256_bytes(public),
"private_key_exportable": False,
"credential_projection": "systemd-encrypted-host-credential",
"negative_capability_tests": {name: True for name in sorted(provisioning.DENIED_CAPABILITIES)},
"production_mutation": False,
"protected_ref_mutation": False,
"tag_mutation": False,
"baseline_mutation": False,
}
return config, payload_path, payload, signature, receipt
def test_legacy_multiline_receipt_parse(tmp_path: Path):
path = tmp_path / "receipts.jsonl"
receipt = {"schema": "legacy", "status": "COMPLETED", "nested": {"value": True}}
path.write_bytes(provisioning.pretty_json(receipt))
assert provisioning._receipts(path) == [receipt]
def test_new_canonical_jsonl_receipt_parse(tmp_path: Path):
path = tmp_path / "receipts.jsonl"
rows = [{"id": 1}, {"id": 2}]
for row in rows:
provisioning._append(path, row)
assert path.read_bytes() == b"".join(provisioning.canonical_json(row) + b"\n" for row in rows)
assert provisioning._receipts(path) == rows
@pytest.mark.parametrize(
"content",
[b'{"status":"COMPLETED"}\nnot-json\n', b'{"status":"COMPLETED"'],
ids=["malformed", "truncated"],
)
def test_malformed_or_truncated_receipt_fails_closed(tmp_path: Path, content: bytes):
path = tmp_path / "receipts.jsonl"
path.write_bytes(content)
with pytest.raises(provisioning.ProvisioningError, match="RECEIPT_LOG_INVALID"):
provisioning._receipts(path)
def test_preexisting_valid_state_is_adopted_without_mutation(tmp_path: Path, monkeypatch):
config, payload_path, _payload_value, signature, receipt = _replay_fixture(tmp_path)
config.receipt_log.write_bytes(provisioning.pretty_json(receipt))
os.chmod(config.receipt_log, 0o600)
before = {
path: path.read_bytes()
for path in (
config.receipt_log, config.public_keyring, config.binding_file,
config.credential_store / "signing_private_key",
config.credential_store / "builder_hmac",
)
}
monkeypatch.setattr(provisioning, "_run_signature_verify", lambda *_args: None)
monkeypatch.setattr(provisioning, "_verify_replay_source", lambda *_args: None)
monkeypatch.setattr(provisioning.os, "geteuid", lambda: 0)
result = provisioning.provision(config, payload_path, signature)
assert result["status"] == "IDEMPOTENT_REPLAY_PASS"
assert result["mutation_executed"] is False
assert result["existing_signer_state_adopted"] is True
assert result["private_key_exportable"] is False
assert before == {path: path.read_bytes() for path in before}
def test_duplicate_replay_is_idempotent(tmp_path: Path, monkeypatch):
config, payload_path, _payload_value, signature, receipt = _replay_fixture(tmp_path)
config.receipt_log.write_bytes(provisioning.pretty_json(receipt))
os.chmod(config.receipt_log, 0o600)
monkeypatch.setattr(provisioning, "_run_signature_verify", lambda *_args: None)
monkeypatch.setattr(provisioning, "_verify_replay_source", lambda *_args: None)
monkeypatch.setattr(provisioning.os, "geteuid", lambda: 0)
first = provisioning.provision(config, payload_path, signature)
receipt_bytes = config.receipt_log.read_bytes()
second = provisioning.provision(config, payload_path, signature)
assert first == second
assert config.receipt_log.read_bytes() == receipt_bytes
def test_wrong_payload_is_rejected_against_preexisting_state(tmp_path: Path, monkeypatch):
config, payload_path, payload, signature, receipt = _replay_fixture(tmp_path)
config.receipt_log.write_bytes(provisioning.pretty_json(receipt))
os.chmod(config.receipt_log, 0o600)
payload["nonce"] = "wrong-payload-nonce-0002"
payload_path.write_bytes(provisioning.pretty_json(payload))
monkeypatch.setattr(provisioning, "_run_signature_verify", lambda *_args: None)
monkeypatch.setattr(provisioning, "_verify_source", lambda *_args: None)
monkeypatch.setattr(provisioning.os, "geteuid", lambda: 0)
with pytest.raises(provisioning.ProvisioningError, match="PREEXISTING_STATE_CONFLICT"):
provisioning.provision(config, payload_path, signature)
def test_wrong_signature_is_rejected(tmp_path: Path, monkeypatch):
config, payload_path, _payload_value, signature, receipt = _replay_fixture(tmp_path)
config.receipt_log.write_bytes(provisioning.pretty_json(receipt))
monkeypatch.setattr(
provisioning,
"_run_signature_verify",
lambda *_args: (_ for _ in ()).throw(provisioning.ProvisioningError("OWNER_SIGNATURE_VERIFY_FAILED")),
)
monkeypatch.setattr(provisioning.os, "geteuid", lambda: 0)
with pytest.raises(provisioning.ProvisioningError, match="OWNER_SIGNATURE_VERIFY_FAILED"):
provisioning.provision(config, payload_path, signature)
def test_wrong_purpose_is_rejected(tmp_path: Path):
_source_root, _payload_path, payload = _payload(tmp_path)
payload["signing_identity"]["purpose"] = "WRONG_PURPOSE"
with pytest.raises(provisioning.ProvisioningError, match="SIGNING_IDENTITY_INVALID"):
provisioning._validate_payload(payload)
def test_replay_rejects_state_binding_tampering(tmp_path: Path, monkeypatch):
config, payload_path, _payload_value, signature, receipt = _replay_fixture(tmp_path)
receipt["service_principal"] = "wrong.service"
config.receipt_log.write_bytes(provisioning.pretty_json(receipt))
monkeypatch.setattr(provisioning, "_run_signature_verify", lambda *_args: None)
monkeypatch.setattr(provisioning, "_verify_replay_source", lambda *_args: None)
monkeypatch.setattr(provisioning.os, "geteuid", lambda: 0)
with pytest.raises(provisioning.ProvisioningError, match="RECEIPT_CONFLICT"):
provisioning.provision(config, payload_path, signature)