From b8b5312dc818d511c6e3c63a4056a255f69cfa62 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 21 Jul 2026 18:07:15 +0000 Subject: [PATCH] Grade Landlock network egress and microVM readiness The `doctor --grade` / conformance score covered eight guarantees but not the two boundary features added recently, so a host running them got no credit in the score CI and admission checks read. Add two grade components: - `landlock_net_egress` -- active when the kernel's Landlock ABI is >= 4, so the process backend's TCP-egress rules can confine connect() to the policy's ports. - `microvm_ready` -- active when a supported VMM and an accessible /dev/kvm are present, so the microVM hardware boundary can be launched; evidence carries the full readiness probe (vmm kind/path, kvm, blocking reasons). The grade is now a 10-point score. README and the grade test are updated, and each new probe has a focused test covering the active and inactive paths. Full suite: 493 passed, 6 skipped. black/isort/flake8/mypy clean. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01PbbJc7Ntj159D9LNGevwC2 --- README.md | 9 +++--- pyisolate/conformance.py | 49 ++++++++++++++++++++++++++++++ tests/test_conformance.py | 64 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 116 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d002d7d..3cc01c8 100644 --- a/README.md +++ b/README.md @@ -156,11 +156,12 @@ python -m pyisolate.conformance --grade pyisolate-doctor --grade ``` -The `--grade` output replaces a vague secure/insecure claim with an 8-point +The `--grade` output replaces a vague secure/insecure claim with a 10-point score over the guarantees that are actually active on the host: free-threading, -eBPF-LSM, cgroup v2, Landlock fallback, no-GIL extension safety, broker crypto, -quota enforcement, and crash isolation. Use it in CI or admission checks to -attach evidence to each guarantee rather than relying on a single pass/fail bit. +eBPF-LSM, cgroup v2, Landlock fallback, Landlock network egress, no-GIL extension +safety, broker crypto, quota enforcement, crash isolation, and microVM readiness. +Use it in CI or admission checks to attach evidence to each guarantee rather than +relying on a single pass/fail bit. ### Policy editor diff --git a/pyisolate/conformance.py b/pyisolate/conformance.py index 12bc65c..2a264dc 100644 --- a/pyisolate/conformance.py +++ b/pyisolate/conformance.py @@ -112,11 +112,13 @@ def grade(self) -> GradeReport: quota_enforcement = self._probe_timeout_and_kill_behavior() ebpf_lsm = self._probe_ebpf_lsm(bpf_availability) landlock_fallback = self._probe_landlock_fallback(ebpf_lsm.passed) + landlock_net_egress = self._probe_landlock_net_egress() no_gil_extension_safety = self._probe_no_gil_extension_safety( python_build, policy_enforcement ) broker_crypto = self._probe_broker_crypto() crash_isolation = self._probe_crash_isolation() + microvm_ready = self._probe_microvm_readiness() probe_components = [ ( @@ -134,6 +136,12 @@ def grade(self) -> GradeReport: landlock_fallback, landlock_fallback.passed, ), + ( + "landlock_net_egress", + "Landlock network egress", + landlock_net_egress, + landlock_net_egress.passed, + ), ( "no_gil_extension_safety", "no-GIL extension safety", @@ -153,6 +161,12 @@ def grade(self) -> GradeReport: crash_isolation, crash_isolation.passed, ), + ( + "microvm_ready", + "microVM readiness", + microvm_ready, + microvm_ready.passed, + ), ] components = [ GradeComponent( @@ -336,6 +350,41 @@ def _probe_landlock_fallback(self, ebpf_lsm_active: bool = False) -> ProbeResult }, ) + def _probe_landlock_net_egress(self) -> ProbeResult: + from pyisolate.runtime import landlock + + abi = landlock.abi_version() + supported = landlock.net_supported() + return ProbeResult( + name="landlock_net_egress", + passed=supported, + required=False, + details=( + "Landlock TCP-egress rules confine process-backend connect() to " + "the policy's ports (Landlock ABI >= 4)" + ), + evidence={ + "landlock_abi": abi, + "net_supported": supported, + "min_abi": 4, + }, + ) + + def _probe_microvm_readiness(self) -> ProbeResult: + from pyisolate.runtime import microvm + + support = microvm.detect_microvm_support() + return ProbeResult( + name="microvm_ready", + passed=support.ready, + required=False, + details=( + "A supported VMM and an accessible /dev/kvm are present, so the " + "microVM hardware boundary can be launched" + ), + evidence=support.as_dict(), + ) + def _probe_cgroup_behavior(self) -> ProbeResult: from pyisolate import cgroup diff --git a/tests/test_conformance.py b/tests/test_conformance.py index e4bb594..4c26f4e 100644 --- a/tests/test_conformance.py +++ b/tests/test_conformance.py @@ -125,13 +125,25 @@ def test_grade_report_scores_named_guarantees(monkeypatch): "_probe_crash_isolation", lambda self: ProbeResult("crash_isolation", True, True, "crash ok", {}), ) + monkeypatch.setattr( + ConformanceSuite, + "_probe_landlock_net_egress", + lambda self: ProbeResult( + "landlock_net_egress", False, False, "no net egress", {} + ), + ) + monkeypatch.setattr( + ConformanceSuite, + "_probe_microvm_readiness", + lambda self: ProbeResult("microvm_ready", False, False, "no kvm", {}), + ) monkeypatch.setattr("pyisolate.conformance.sys.version_info", (3, 13, 0)) report = ConformanceSuite().grade() payload = report.to_dict() assert report.score == 7 - assert report.max_score == 8 + assert report.max_score == 10 assert payload["active_guarantees"] == [ "free_threading", "ebpf_lsm", @@ -141,19 +153,67 @@ def test_grade_report_scores_named_guarantees(monkeypatch): "quota_enforcement", "crash_isolation", ] - assert payload["inactive_guarantees"] == ["landlock_fallback"] + assert payload["inactive_guarantees"] == [ + "landlock_fallback", + "landlock_net_egress", + "microvm_ready", + ] assert [component["label"] for component in payload["components"]] == [ "free-threading", "eBPF-LSM", "cgroup v2", "Landlock fallback", + "Landlock network egress", "no-GIL extension safety", "broker crypto", "quota enforcement", "crash isolation", + "microVM readiness", ] +def test_probe_landlock_net_egress_reflects_support(monkeypatch): + from pyisolate.runtime import landlock + + suite = ConformanceSuite() + monkeypatch.setattr(landlock, "abi_version", lambda: 4) + monkeypatch.setattr(landlock, "net_supported", lambda: True) + result = suite._probe_landlock_net_egress() + assert result.name == "landlock_net_egress" + assert result.passed is True + assert result.evidence["landlock_abi"] == 4 + assert result.evidence["min_abi"] == 4 + + monkeypatch.setattr(landlock, "net_supported", lambda: False) + assert suite._probe_landlock_net_egress().passed is False + + +def test_probe_microvm_readiness_reflects_support(monkeypatch): + from pyisolate.runtime import microvm + + suite = ConformanceSuite() + monkeypatch.setattr( + microvm, + "detect_microvm_support", + lambda: microvm.MicroVMSupport( + vmm_kind="firecracker", vmm_path="/usr/bin/firecracker", kvm=True + ), + ) + result = suite._probe_microvm_readiness() + assert result.name == "microvm_ready" + assert result.passed is True + assert result.evidence["vmm_kind"] == "firecracker" + + monkeypatch.setattr( + microvm, + "detect_microvm_support", + lambda: microvm.MicroVMSupport( + vmm_kind=None, vmm_path=None, kvm=False, reasons=("no vmm",) + ), + ) + assert suite._probe_microvm_readiness().passed is False + + def test_conformance_cli_grade(monkeypatch, capsys): monkeypatch.setattr( ConformanceSuite,