autonomy-smoke-tests phase2-pr76 PASS evidence_sha256=c16c68f86ee0c4e8cfd0af3f76078be45441ee151395fbde5800145c08e189b5
autonomy-unit-tests phase2-pr76 PASS evidence_sha256=15bc76da47bdf8218f18fc8a7da65fafd841677d5f754b71a0c9f56e07d84c1b
campaign-approval-gate phase2-pr76 PASS evidence_sha256=ea7ed0516efe6736a8eaee426685e40a2393fbf3ae84104a743f394204c2ec33
git-diff-check phase2-pr76 PASS evidence_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
py-compile phase2-pr76 PASS evidence_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
reconciliation-contract phase2-pr76 PASS evidence_sha256=211444cb0c146f5d4a57e13b780cfe7ffb406e8a8bc2e11c02a3a4728f66b0b2
release-state-machine phase2-pr76 PASS evidence_sha256=6cea88752b3e58ae5cee56ff3b1b13eab34117918d6bad9661194c031e2dc157
secret-scan phase2-pr76 PASS evidence_sha256=7cb68dd604dc6cd4904246e16333072d512dba14c096301e08c5a2b78a45bd55
signer-validator-contract phase2-pr76 PASS evidence_sha256=775b7aa27d0c17f48b3feff20c836f14b6ed8ebcb90ec7e0b76d2a13c579268a
aios.owner-final-merge-authorization-v2 real v2 contract tests passed
177 lines
6.1 KiB
Python
177 lines
6.1 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from cryptography.hazmat.primitives import serialization
|
|
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
|
|
|
from aios_owner_merge_authorization import (
|
|
OwnerMergeApprovalError,
|
|
OwnerMergeApprovalPolicy,
|
|
OwnerMergeApprovalVerifier,
|
|
)
|
|
from aios_owner_merge_authorization_server import OwnerMergeApprovalService
|
|
from aios_single_host_authority import (
|
|
RegistryOwnerApprovalVerifier,
|
|
ssh_fingerprint,
|
|
ssh_public_line,
|
|
)
|
|
|
|
|
|
NOW = 2_000_000_000
|
|
|
|
|
|
def _write_key(path: Path, key: Ed25519PrivateKey) -> None:
|
|
path.write_bytes(
|
|
key.private_bytes(
|
|
serialization.Encoding.PEM,
|
|
serialization.PrivateFormat.PKCS8,
|
|
serialization.NoEncryption(),
|
|
)
|
|
)
|
|
os.chmod(path, 0o600)
|
|
|
|
|
|
@pytest.fixture
|
|
def service_env(tmp_path: Path):
|
|
owner_key = Ed25519PrivateKey.generate()
|
|
owner_path = tmp_path / "owner.key"
|
|
_write_key(owner_path, owner_key)
|
|
registry_path = tmp_path / "registry.json"
|
|
registry_path.write_text(
|
|
json.dumps(
|
|
{
|
|
"schema": "aios.single-host-signer-registry.v1",
|
|
"authority_generation": 1,
|
|
"signers": [
|
|
{
|
|
"signer_id": "local-owner-authority",
|
|
"role": "OWNER_AUTHORITY",
|
|
"provider": "local_unix_service",
|
|
"fingerprint": ssh_fingerprint(owner_key.public_key()),
|
|
"public_key": ssh_public_line(owner_key.public_key(), "owner"),
|
|
"socket": "/run/aios/owner-authority.sock",
|
|
"secret_ref": "aios-secret://owner",
|
|
"status": "ACTIVE",
|
|
"generation": 1,
|
|
"parent_signer_id": "",
|
|
"parent_fingerprint": "",
|
|
}
|
|
],
|
|
},
|
|
sort_keys=True,
|
|
)
|
|
+ "\n"
|
|
)
|
|
os.chmod(registry_path, 0o644)
|
|
policy = OwnerMergeApprovalPolicy(
|
|
repository="aios/aios",
|
|
pr_number=75,
|
|
pr_head_oid="a" * 40,
|
|
expected_main_oid="b" * 40,
|
|
operation="merge.pull-request",
|
|
required_check_contract_digest="c" * 64,
|
|
required_checks=("contract", "security"),
|
|
owner_identity="owner:aios-single-host",
|
|
audit_lineage_digest="d" * 64,
|
|
maximum_ttl_seconds=300,
|
|
)
|
|
policy_path = tmp_path / "policy.json"
|
|
policy_path.write_text(json.dumps(policy.as_dict(), sort_keys=True) + "\n")
|
|
os.chmod(policy_path, 0o600)
|
|
return {
|
|
"policy": policy,
|
|
"policy_path": policy_path,
|
|
"registry_path": registry_path,
|
|
"owner_path": owner_path,
|
|
"ledger_path": tmp_path / "issuance.sqlite3",
|
|
}
|
|
|
|
|
|
def _service(env):
|
|
return OwnerMergeApprovalService(
|
|
policy_path=env["policy_path"],
|
|
registry_path=env["registry_path"],
|
|
owner_key_path=env["owner_path"],
|
|
issuance_ledger_path=env["ledger_path"],
|
|
allowed_uids=(1000,),
|
|
require_root_owner=False,
|
|
clock=lambda: NOW,
|
|
token_urlsafe=lambda _size: "server-generated-nonce",
|
|
)
|
|
|
|
|
|
def test_service_issues_exact_owner_approval_and_exact_replay_is_audit_idempotent(service_env):
|
|
service = _service(service_env)
|
|
request = {"operation": "issue_owner_merge_approval", "payload": {}}
|
|
|
|
first = service.handle(request, peer_uid=1000)
|
|
second = _service(service_env).handle(request, peer_uid=1000)
|
|
|
|
assert first["status"] == "PASS"
|
|
assert first["idempotent"] is False
|
|
assert second["idempotent"] is True
|
|
assert second["artifact"] == first["artifact"]
|
|
verifier = OwnerMergeApprovalVerifier(
|
|
primary=RegistryOwnerApprovalVerifier(
|
|
service_env["registry_path"], require_root_owner=False
|
|
),
|
|
clock=lambda: NOW,
|
|
)
|
|
payload, proof_path = verifier.verify(first["artifact"], service_env["policy"])
|
|
assert proof_path == "primary"
|
|
assert payload["repository"] == "aios/aios"
|
|
assert payload["pr_number"] == 75
|
|
|
|
|
|
def test_expired_approval_is_retained_for_audit_but_reissued_with_a_new_nonce(service_env):
|
|
clock = [NOW]
|
|
token_values = iter(("approval-one", "nonce-one", "approval-two", "nonce-two"))
|
|
service = OwnerMergeApprovalService(
|
|
policy_path=service_env["policy_path"],
|
|
registry_path=service_env["registry_path"],
|
|
owner_key_path=service_env["owner_path"],
|
|
issuance_ledger_path=service_env["ledger_path"],
|
|
allowed_uids=(1000,),
|
|
require_root_owner=False,
|
|
clock=lambda: clock[0],
|
|
token_urlsafe=lambda _size: next(token_values),
|
|
)
|
|
request = {"operation": "issue_owner_merge_approval", "payload": {}}
|
|
|
|
first = service.handle(request, peer_uid=1000)
|
|
clock[0] += service_env["policy"].maximum_ttl_seconds
|
|
second = service.handle(request, peer_uid=1000)
|
|
|
|
assert first["idempotent"] is False
|
|
assert second["idempotent"] is False
|
|
assert second["artifact"]["payload"]["nonce"] != first["artifact"]["payload"]["nonce"]
|
|
assert second["artifact"]["payload"]["issued_at"] == clock[0]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"raw_request",
|
|
[
|
|
{"operation": "issue_owner_merge_approval", "payload": {"nonce": "caller-nonce"}},
|
|
{"operation": "issue_owner_merge_approval", "payload": {"pr_head_oid": "f" * 40}},
|
|
{"operation": "sign", "payload": {}},
|
|
{"operation": "sign_bytes", "payload": {}},
|
|
],
|
|
)
|
|
def test_service_rejects_caller_selected_scope_and_generic_signing(service_env, raw_request):
|
|
service = _service(service_env)
|
|
|
|
with pytest.raises(OwnerMergeApprovalError, match="OWNER_MERGE_ISSUER_"):
|
|
service.handle(raw_request, peer_uid=1000)
|
|
|
|
|
|
def test_service_rejects_untrusted_peer_before_issuing_anything(service_env):
|
|
with pytest.raises(OwnerMergeApprovalError, match="OWNER_MERGE_ISSUER_PEER_UID_FORBIDDEN"):
|
|
_service(service_env).handle(
|
|
{"operation": "issue_owner_merge_approval", "payload": {}},
|
|
peer_uid=2000,
|
|
)
|