- Implemented PolicyPackSelectorComponent for selecting policy packs. - Added unit tests for component behavior, including API success and error handling. - Introduced monaco-workers type declarations for editor workers. - Created acceptance tests for guardrails with stubs for AT1–AT10. - Established SCA Failure Catalogue Fixtures for regression testing. - Developed plugin determinism harness with stubs for PL1–PL10. - Added scripts for evidence upload and verification processes.
181 lines
7.5 KiB
Python
181 lines
7.5 KiB
Python
#!/usr/bin/env python3
|
|
import json
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
import runpy
|
|
|
|
_VERIFIER_PATH = Path(__file__).parent / "verify_offline_bundle.py"
|
|
_mod = runpy.run_path(_VERIFIER_PATH.as_posix(), run_name="verify_offline_bundle")
|
|
|
|
BundleReader = _mod["BundleReader"]
|
|
validate_manifest = _mod["validate_manifest"]
|
|
verify_files = _mod["verify_files"]
|
|
verify_hashes = _mod["verify_hashes"]
|
|
sha256_digest = _mod["sha256_digest"]
|
|
|
|
|
|
def _write(path: Path, content: str) -> str:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(content, encoding="utf-8")
|
|
return sha256_digest(content.encode("utf-8"))
|
|
|
|
|
|
class VerifyOfflineBundleTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.tmp = tempfile.TemporaryDirectory()
|
|
self.root = Path(self.tmp.name)
|
|
|
|
def tearDown(self) -> None:
|
|
self.tmp.cleanup()
|
|
|
|
def _build_manifest(self) -> Path:
|
|
plan_hash = _write(self.root / "canonical-plan.json", '{"steps":[]}')
|
|
inputs_lock_hash = _write(self.root / "inputs.lock", '{"inputs":{}}')
|
|
sbom_hash = _write(self.root / "sbom.json", '{"bom":"demo"}')
|
|
attest_hash = _write(self.root / "attestation.dsse", "attestation")
|
|
approvals_ledger = json.dumps(
|
|
{
|
|
"schemaVersion": "stellaops.pack.approval-ledger.v1",
|
|
"runId": "run-1",
|
|
"gateId": "security-review",
|
|
"planHash": plan_hash,
|
|
"decision": "approved",
|
|
"decidedAt": "2025-12-05T00:00:00Z",
|
|
"tenantId": "demo-tenant",
|
|
"approver": {"id": "approver@example.com", "summary": "LGTM"},
|
|
}
|
|
)
|
|
approvals_hash = _write(self.root / "approvals-ledger.dsse", approvals_ledger)
|
|
revocations_hash = _write(self.root / "revocations.json", '{"revoked":false}')
|
|
bundle_dsse_hash = _write(self.root / "bundle.dsse", "bundle-dsse")
|
|
att_dsse_hash = _write(self.root / "attestation.dsse.sig", "att-dsse")
|
|
redaction_hash = _write(self.root / "redaction-policy.json", '{"mode":"hash"}')
|
|
pack_blob_hash = _write(self.root / "packs/my-pack.tgz", "dummy pack")
|
|
|
|
manifest = {
|
|
"schemaVersion": "stellaops.pack.offline-bundle.v1",
|
|
"pack": {
|
|
"name": "demo-pack",
|
|
"version": "1.0.0",
|
|
"bundle": "packs/my-pack.tgz",
|
|
"digest": pack_blob_hash,
|
|
"registry": "demo.local/pack/demo:1.0.0",
|
|
"sbom": "sbom.json",
|
|
},
|
|
"plan": {
|
|
"hashAlgorithm": "sha256",
|
|
"hash": plan_hash,
|
|
"canonicalPlanPath": "canonical-plan.json",
|
|
"inputsLock": "inputs.lock",
|
|
"rngSeed": "rng-demo",
|
|
"timestampSource": "utc-iso8601",
|
|
},
|
|
"evidence": {
|
|
"attestation": "attestation.dsse",
|
|
"approvalsLedger": "approvals-ledger.dsse",
|
|
"timeline": "timeline.ndjson",
|
|
},
|
|
"security": {
|
|
"sandbox": {
|
|
"mode": "sealed",
|
|
"egressAllowlist": [],
|
|
"cpuLimitMillicores": 250,
|
|
"memoryLimitMiB": 256,
|
|
"quotaSeconds": 120,
|
|
},
|
|
"revocations": "revocations.json",
|
|
"signatures": {
|
|
"bundleDsse": "bundle.dsse",
|
|
"attestationDsse": "attestation.dsse.sig",
|
|
"registryCertChain": "certs.pem",
|
|
},
|
|
"secretsRedactionPolicy": "redaction-policy.json",
|
|
},
|
|
"hashes": [
|
|
{"path": "canonical-plan.json", "algorithm": "sha256", "digest": plan_hash},
|
|
{"path": "inputs.lock", "algorithm": "sha256", "digest": inputs_lock_hash},
|
|
{"path": "sbom.json", "algorithm": "sha256", "digest": sbom_hash},
|
|
{"path": "attestation.dsse", "algorithm": "sha256", "digest": attest_hash},
|
|
{"path": "approvals-ledger.dsse", "algorithm": "sha256", "digest": approvals_hash},
|
|
{"path": "revocations.json", "algorithm": "sha256", "digest": revocations_hash},
|
|
{"path": "bundle.dsse", "algorithm": "sha256", "digest": bundle_dsse_hash},
|
|
{"path": "attestation.dsse.sig", "algorithm": "sha256", "digest": att_dsse_hash},
|
|
{"path": "redaction-policy.json", "algorithm": "sha256", "digest": redaction_hash},
|
|
{"path": "packs/my-pack.tgz", "algorithm": "sha256", "digest": pack_blob_hash},
|
|
],
|
|
"slo": {
|
|
"runP95Seconds": 300,
|
|
"approvalP95Seconds": 900,
|
|
"maxQueueDepth": 1000,
|
|
"alertRules": "alerts.yaml",
|
|
},
|
|
"tenant": "demo-tenant",
|
|
"environment": "dev",
|
|
"created": "2025-12-05T00:00:00Z",
|
|
"expires": "2026-01-05T00:00:00Z",
|
|
"verifyScriptVersion": "local-test",
|
|
}
|
|
manifest_path = self.root / "bundle.json"
|
|
manifest_path.write_text(json.dumps(manifest, indent=2), encoding="utf-8")
|
|
return manifest_path
|
|
|
|
def test_good_bundle_passes(self):
|
|
manifest_path = self._build_manifest()
|
|
reader = BundleReader(self.root.as_posix())
|
|
manifest = json.loads(manifest_path.read_text())
|
|
|
|
errors = []
|
|
errors.extend(validate_manifest(manifest))
|
|
errors.extend(verify_files(reader, manifest, require_dsse=True))
|
|
errors.extend(verify_hashes(reader, manifest))
|
|
self.assertFalse(errors, f"Expected no validation errors, got: {errors}")
|
|
|
|
def test_missing_hash_fails(self):
|
|
manifest_path = self._build_manifest()
|
|
manifest = json.loads(manifest_path.read_text())
|
|
# Corrupt a hash to force a failure.
|
|
manifest["hashes"][0]["digest"] = "sha256:" + "0" * 64
|
|
reader = BundleReader(self.root.as_posix())
|
|
errors = verify_hashes(reader, manifest)
|
|
self.assertTrue(errors, "Expected hash verification to fail when hash entry is missing")
|
|
|
|
def test_missing_quota_fails(self):
|
|
manifest_path = self._build_manifest()
|
|
manifest = json.loads(manifest_path.read_text())
|
|
del manifest["security"]["sandbox"]["quotaSeconds"]
|
|
reader = BundleReader(self.root.as_posix())
|
|
|
|
errors = []
|
|
errors.extend(validate_manifest(manifest))
|
|
errors.extend(verify_files(reader, manifest, require_dsse=True))
|
|
errors.extend(verify_hashes(reader, manifest))
|
|
|
|
self.assertTrue(
|
|
any(err.path == "security.sandbox.quotaSeconds" for err in errors),
|
|
"Expected quotaSeconds validation failure"
|
|
)
|
|
|
|
def test_invalid_approval_ledger_plan_hash_fails(self):
|
|
manifest_path = self._build_manifest()
|
|
manifest = json.loads(manifest_path.read_text())
|
|
ledger_path = self.root / "approvals-ledger.dsse"
|
|
ledger = json.loads(ledger_path.read_text())
|
|
ledger["planHash"] = "not-a-digest"
|
|
ledger_path.write_text(json.dumps(ledger), encoding="utf-8")
|
|
|
|
reader = BundleReader(self.root.as_posix())
|
|
errors = []
|
|
errors.extend(validate_manifest(manifest))
|
|
errors.extend(verify_files(reader, manifest, require_dsse=True))
|
|
errors.extend(verify_hashes(reader, manifest))
|
|
|
|
self.assertTrue(
|
|
any(err.path.startswith("approvalsLedger.planHash") for err in errors),
|
|
"Expected approval ledger plan hash validation failure"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|