ci: mount audit workspace safely
This commit is contained in:
248
tests/test_security_audit_mount_resolver.py
Normal file
248
tests/test_security_audit_mount_resolver.py
Normal file
@@ -0,0 +1,248 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
META_ROOT = Path(__file__).resolve().parents[1]
|
||||
RESOLVER = (
|
||||
META_ROOT / "tools" / "checks" / "security-audit" / "resolve_workspace_mount.py"
|
||||
)
|
||||
|
||||
|
||||
def load_resolver_module():
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"security_audit_mount_resolver",
|
||||
RESOLVER,
|
||||
)
|
||||
if spec is None or spec.loader is None:
|
||||
raise RuntimeError(f"Could not load {RESOLVER}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
class SecurityAuditMountResolverTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.resolver = load_resolver_module()
|
||||
self.root = "/workspace/add-ideas/govoplan/govoplan"
|
||||
self.workspace = "/workspace/add-ideas/govoplan"
|
||||
|
||||
def test_resolves_only_the_named_workspace_volume_for_both_scopes(self) -> None:
|
||||
mounts = [
|
||||
{
|
||||
"Type": "bind",
|
||||
"Source": "/var/run/docker.sock",
|
||||
"Destination": "/var/run/docker.sock",
|
||||
"RW": True,
|
||||
},
|
||||
{
|
||||
"Type": "volume",
|
||||
"Name": "actions-workspace",
|
||||
"Source": "/var/lib/docker/volumes/actions-workspace/_data",
|
||||
"Destination": self.workspace,
|
||||
"RW": True,
|
||||
},
|
||||
{
|
||||
"Type": "volume",
|
||||
"Name": "actions-environment",
|
||||
"Source": "/var/lib/docker/volumes/actions-environment/_data",
|
||||
"Destination": "/var/run/act",
|
||||
"RW": True,
|
||||
},
|
||||
]
|
||||
|
||||
for scope in ("current", "govoplan"):
|
||||
with self.subTest(scope=scope):
|
||||
self.assertEqual(
|
||||
(f"type=volume,source=actions-workspace,target={self.workspace}"),
|
||||
self.resolver.resolve_workspace_mount(
|
||||
mounts,
|
||||
root=self.root,
|
||||
scope=scope,
|
||||
reports_dir="audit-reports",
|
||||
),
|
||||
)
|
||||
|
||||
def test_resolves_a_workspace_bind_without_other_mounts(self) -> None:
|
||||
mounts = [
|
||||
{
|
||||
"Type": "bind",
|
||||
"Source": "/srv/gitea/actions/task-123",
|
||||
"Destination": self.workspace,
|
||||
"RW": True,
|
||||
},
|
||||
{
|
||||
"Type": "bind",
|
||||
"Source": "/var/run/docker.sock",
|
||||
"Destination": "/var/run/docker.sock",
|
||||
"RW": True,
|
||||
},
|
||||
]
|
||||
|
||||
self.assertEqual(
|
||||
(f"type=bind,source=/srv/gitea/actions/task-123,target={self.workspace}"),
|
||||
self.resolver.resolve_workspace_mount(
|
||||
mounts,
|
||||
root=self.root,
|
||||
scope="govoplan",
|
||||
reports_dir="audit-reports",
|
||||
),
|
||||
)
|
||||
|
||||
def test_nested_repository_mount_is_valid_only_for_current_scope(self) -> None:
|
||||
mounts = [
|
||||
{
|
||||
"Type": "volume",
|
||||
"Name": "all-repositories",
|
||||
"Destination": self.workspace,
|
||||
"RW": True,
|
||||
},
|
||||
{
|
||||
"Type": "volume",
|
||||
"Name": "current-repository",
|
||||
"Destination": self.root,
|
||||
"RW": True,
|
||||
},
|
||||
]
|
||||
|
||||
self.assertIn(
|
||||
"source=current-repository",
|
||||
self.resolver.resolve_workspace_mount(
|
||||
mounts,
|
||||
root=self.root,
|
||||
scope="current",
|
||||
reports_dir="audit-reports",
|
||||
),
|
||||
)
|
||||
with self.assertRaisesRegex(
|
||||
self.resolver.MountResolutionError,
|
||||
"nested job-container mounts",
|
||||
):
|
||||
self.resolver.resolve_workspace_mount(
|
||||
mounts,
|
||||
root=self.root,
|
||||
scope="govoplan",
|
||||
reports_dir="audit-reports",
|
||||
)
|
||||
|
||||
def test_rejects_unsafe_or_unusable_mount_layouts(self) -> None:
|
||||
cases = {
|
||||
"missing": [],
|
||||
"path-boundary": [
|
||||
{
|
||||
"Type": "volume",
|
||||
"Name": "wrong-workspace",
|
||||
"Destination": "/workspace/add-ideas/govoplan-other",
|
||||
"RW": True,
|
||||
}
|
||||
],
|
||||
"read-only": [
|
||||
{
|
||||
"Type": "volume",
|
||||
"Name": "actions-workspace",
|
||||
"Destination": self.workspace,
|
||||
"RW": False,
|
||||
}
|
||||
],
|
||||
"ambiguous": [
|
||||
{
|
||||
"Type": "volume",
|
||||
"Name": name,
|
||||
"Destination": self.workspace,
|
||||
"RW": True,
|
||||
}
|
||||
for name in ("workspace-one", "workspace-two")
|
||||
],
|
||||
"scope-too-narrow": [
|
||||
{
|
||||
"Type": "volume",
|
||||
"Name": "repository-only",
|
||||
"Destination": self.root,
|
||||
"RW": True,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
for name, mounts in cases.items():
|
||||
with (
|
||||
self.subTest(name=name),
|
||||
self.assertRaises(self.resolver.MountResolutionError),
|
||||
):
|
||||
self.resolver.resolve_workspace_mount(
|
||||
mounts,
|
||||
root=self.root,
|
||||
scope="govoplan",
|
||||
reports_dir="audit-reports",
|
||||
)
|
||||
|
||||
def test_rejects_reports_outside_the_selected_workspace_mount(self) -> None:
|
||||
mounts = [
|
||||
{
|
||||
"Type": "volume",
|
||||
"Name": "actions-workspace",
|
||||
"Destination": self.workspace,
|
||||
"RW": True,
|
||||
}
|
||||
]
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
self.resolver.MountResolutionError,
|
||||
"reports path .* outside",
|
||||
):
|
||||
self.resolver.resolve_workspace_mount(
|
||||
mounts,
|
||||
root=self.root,
|
||||
scope="current",
|
||||
reports_dir="/tmp/audit-reports",
|
||||
)
|
||||
|
||||
def test_rejects_broad_or_sensitive_workspace_bind_sources(self) -> None:
|
||||
for source in (
|
||||
"/",
|
||||
"/home",
|
||||
"/var/run/docker.sock",
|
||||
"/srv/../etc/shadow",
|
||||
):
|
||||
with (
|
||||
self.subTest(source=source),
|
||||
self.assertRaisesRegex(
|
||||
self.resolver.MountResolutionError,
|
||||
"too broad or sensitive",
|
||||
),
|
||||
):
|
||||
self.resolver.resolve_workspace_mount(
|
||||
[
|
||||
{
|
||||
"Type": "bind",
|
||||
"Source": source,
|
||||
"Destination": self.workspace,
|
||||
"RW": True,
|
||||
}
|
||||
],
|
||||
root=self.root,
|
||||
scope="govoplan",
|
||||
reports_dir="audit-reports",
|
||||
)
|
||||
|
||||
with self.assertRaises(self.resolver.MountResolutionError):
|
||||
self.resolver.resolve_workspace_mount(
|
||||
[
|
||||
{
|
||||
"Type": "bind",
|
||||
"Source": "//var/lib/workspace",
|
||||
"Destination": self.workspace,
|
||||
"RW": True,
|
||||
}
|
||||
],
|
||||
root=self.root,
|
||||
scope="govoplan",
|
||||
reports_dir="audit-reports",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user