feat: add PolicyPackSelectorComponent with tests and integration
- 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.
This commit is contained in:
180
scripts/packs/test_verify_offline_bundle.py
Normal file
180
scripts/packs/test_verify_offline_bundle.py
Normal file
@@ -0,0 +1,180 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user