Skip to content

Commit 81e2835

Browse files
authored
Merge pull request #1487 from freedomofpress/1338-pytest-conversion
Partial pytest conversion to centralize list of present VMs
2 parents ce506eb + 5c48033 commit 81e2835

File tree

7 files changed

+525
-528
lines changed

7 files changed

+525
-528
lines changed

tests/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
SD_TEMPLATE_LARGE = f"sd-large-{DEBIAN_VERSION}-template"
1111
SD_TEMPLATE_SMALL = f"sd-small-{DEBIAN_VERSION}-template"
1212

13+
SD_TAG = "sd-workstation" # Tag identifying SecureDrop Workstation-managed VMs
14+
15+
# Expectations regarding VMs' existence and versions
1316
SD_VMS = ["sd-gpg", "sd-log", "sd-proxy", "sd-app", "sd-viewer", "sd-devices"]
1417
SD_DVM_TEMPLATES = ["sd-devices-dvm", "sd-proxy-dvm"]
1518
SD_TEMPLATES = [SD_TEMPLATE_BASE, SD_TEMPLATE_LARGE, SD_TEMPLATE_SMALL]

tests/conftest.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
from pathlib import Path
33

44
import pytest
5+
from qubesadmin import Qubes
6+
7+
from tests.base import SD_TAG
58

69
PROJ_ROOT = Path(__file__).parent.parent
710

@@ -11,3 +14,24 @@ def dom0_config():
1114
"""Make the dom0 "config.json" available to tests."""
1215
with open(PROJ_ROOT / "config.json") as config_file:
1316
return json.load(config_file)
17+
18+
19+
@pytest.fixture
20+
def all_vms():
21+
"""Obtain all qubes present in the system"""
22+
return Qubes().domains
23+
24+
25+
@pytest.fixture
26+
def sdw_tagged_vms(all_vms):
27+
"""Obtain all SecureDrop Workstation-exclusive qubes"""
28+
return [vm for vm in all_vms if SD_TAG in vm.tags]
29+
30+
31+
@pytest.fixture
32+
def config():
33+
with open("config.json") as c:
34+
config = json.load(c)
35+
if "environment" not in config:
36+
config["environment"] = "dev"
37+
return config

tests/test_log_vm.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,14 @@ def test_redis_service_running(qube):
4444
assert qube.service_is_active("redis")
4545

4646

47-
def test_logs_are_flowing(qube):
47+
def test_logs_are_flowing(qube, sdw_tagged_vms):
4848
"""
4949
To test that logs work, we run a unique command in each VM we care
5050
about that gets logged, and then check for that string in the logs.
5151
"""
5252
# Random string, to avoid collisions with other test runs
5353
token = "".join(secrets.choice(string.ascii_uppercase) for _ in range(10))
5454

55-
# All @tag:sd-workstation VMs
56-
all_vms = [vm.name for vm in qube.app.domains if "sd-workstation" in vm.tags]
5755
# base template doesn't have sd-log configured
5856
# TODO: test a sd-viewer based dispVM
5957
skip = [f"sd-base-{CURRENT_DEBIAN_VERSION}-template", "sd-viewer"]
@@ -63,17 +61,17 @@ def test_logs_are_flowing(qube):
6361
# We first run the command in each VM, and then do a second loop to
6462
# look for the token in the log entry, so there's enough time for the
6563
# log entry to get written.
66-
for vm_name in all_vms:
67-
if vm_name in skip:
64+
for vm in sdw_tagged_vms:
65+
if vm.name in skip:
6866
continue
6967
# The sudo call will make it into syslog
70-
subprocess.check_call(["qvm-run", vm_name, f"sudo echo {token}"])
68+
subprocess.check_call(["qvm-run", vm.name, f"sudo echo {token}"])
7169

72-
for vm_name in all_vms:
73-
if vm_name in skip:
70+
for vm in sdw_tagged_vms:
71+
if vm.name in skip:
7472
continue
75-
syslog = f"/home/user/QubesIncomingLogs/{vm_name}/syslog.log"
76-
if vm_name in no_log_vms:
73+
syslog = f"/home/user/QubesIncomingLogs/{vm.name}/syslog.log"
74+
if vm.name in no_log_vms:
7775
assert not qube.fileExists(syslog)
7876
else:
7977
assert token in qube.get_file_contents(syslog)

tests/test_qubes_rpc.py

Lines changed: 55 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,58 @@
11
import functools
22
import os
33
import subprocess
4-
import unittest
5-
6-
from qubesadmin import Qubes
7-
8-
9-
class SD_Qubes_Rpc_Tests(unittest.TestCase):
10-
@classmethod
11-
def setUpClass(cls):
12-
cls.all_vms = set()
13-
cls.vms_by_tag = {}
14-
15-
app = Qubes()
16-
cls.all_vms = {vm for vm in app.domains if vm.name != "dom0"}
17-
cls.sdw_tagged_vms = {vm for vm in app.domains if "sd-workstation" in vm.tags}
18-
19-
@functools.cache
20-
def _qrexec_policy_graph(self, service):
21-
cmd = ["qrexec-policy-graph", "--service", service]
22-
p = subprocess.run(cmd, capture_output=True, text=True, check=False)
23-
return p.stdout
24-
25-
def _policy_exists(self, source, target, service):
26-
service_policy_graph = self._qrexec_policy_graph(service)
27-
policy_str = f'"{source}" -> "{target}" [label="{service}"'
28-
return policy_str in service_policy_graph
29-
30-
def test_policy_files_exist(self):
31-
"""verify the policies are installed"""
32-
assert os.path.exists("/etc/qubes/policy.d/31-securedrop-workstation.policy")
33-
assert os.path.exists("/etc/qubes/policy.d/32-securedrop-workstation.policy")
34-
35-
# securedrop.Log from @tag:sd-workstation to sd-log should be allowed
36-
def test_sdlog_from_sdw_to_sdlog_allowed(self):
37-
for vm in self.sdw_tagged_vms:
38-
if vm != "sd-log":
39-
assert self._policy_exists(vm, "sd-log", "securedrop.Log")
40-
41-
# securedrop.Log from anything else to sd-log should be denied
42-
def test_sdlog_from_other_to_sdlog_denied(self):
43-
non_sd_workstation_vms = self.all_vms.difference(self.sdw_tagged_vms)
44-
for vm in non_sd_workstation_vms:
45-
if vm != "sd-log":
46-
assert not self._policy_exists(vm, "sd-log", "securedrop.Log")
47-
48-
# securedrop.Proxy from sd-app to sd-proxy should be allowed
49-
def test_sdproxy_from_sdapp_to_sdproxy_allowed(self):
50-
assert self._policy_exists("sd-app", "sd-proxy", "securedrop.Proxy")
51-
52-
# securedrop.Proxy from anything else to sd-proxy should be denied
53-
def test_sdproxy_from_other_to_sdproxy_denied(self):
54-
assert not self._policy_exists("sys-net", "sd-proxy", "securedrop.Proxy")
55-
assert not self._policy_exists("sys-firewall", "sd-proxy", "securedrop.Proxy")
56-
57-
# qubes.Gpg, qubes.GpgImportKey, and qubes.Gpg2 from anything else to sd-gpg should be denied
58-
def test_qubesgpg_from_other_to_sdgpg_denied(self):
59-
assert not self._policy_exists("sys-net", "sd-gpg", "qubes.Gpg")
60-
assert not self._policy_exists("sys-firewall", "sd-gpg", "qubes.Gpg")
61-
assert not self._policy_exists("sys-net", "sd-gpg", "qubes.GpgImportKey")
62-
assert not self._policy_exists("sys-firewall", "sd-gpg", "qubes.GpgImportKey")
63-
assert not self._policy_exists("sys-net", "sd-gpg", "qubes.Gpg2")
64-
assert not self._policy_exists("sys-firewall", "sd-gpg", "qubes.Gpg2")
65-
66-
67-
def load_tests(loader, tests, pattern):
68-
return unittest.TestLoader().loadTestsFromTestCase(SD_Qubes_Rpc_Tests)
4+
5+
6+
@functools.cache
7+
def qrexec_policy_graph(service):
8+
cmd = ["qrexec-policy-graph", "--service", service]
9+
p = subprocess.run(cmd, capture_output=True, text=True, check=False)
10+
return p.stdout
11+
12+
13+
def policy_exists(source, target, service):
14+
service_policy_graph = qrexec_policy_graph(service)
15+
policy_str = f'"{source}" -> "{target}" [label="{service}"'
16+
return policy_str in service_policy_graph
17+
18+
19+
def test_policy_files_exist():
20+
"""verify the policies are installed"""
21+
assert os.path.exists("/etc/qubes/policy.d/31-securedrop-workstation.policy")
22+
assert os.path.exists("/etc/qubes/policy.d/32-securedrop-workstation.policy")
23+
24+
25+
# securedrop.Log from @tag:sd-workstation to sd-log should be allowed
26+
def test_sdlog_from_sdw_to_sdlog_allowed(sdw_tagged_vms):
27+
for vm in sdw_tagged_vms:
28+
if vm.name != "sd-log":
29+
assert policy_exists(vm, "sd-log", "securedrop.Log")
30+
31+
32+
# securedrop.Log from anything else to sd-log should be denied
33+
def test_sdlog_from_other_to_sdlog_denied(all_vms, sdw_tagged_vms):
34+
non_sd_workstation_vms = set(all_vms).difference(set(sdw_tagged_vms))
35+
for vm in non_sd_workstation_vms:
36+
if vm.name != "sd-log":
37+
assert not policy_exists(vm, "sd-log", "securedrop.Log")
38+
39+
40+
# securedrop.Proxy from sd-app to sd-proxy should be allowed
41+
def test_sdproxy_from_sdapp_to_sdproxy_allowed():
42+
assert policy_exists("sd-app", "sd-proxy", "securedrop.Proxy")
43+
44+
45+
# securedrop.Proxy from anything else to sd-proxy should be denied
46+
def test_sdproxy_from_other_to_sdproxy_denied():
47+
assert not policy_exists("sys-net", "sd-proxy", "securedrop.Proxy")
48+
assert not policy_exists("sys-firewall", "sd-proxy", "securedrop.Proxy")
49+
50+
51+
# qubes.Gpg, qubes.GpgImportKey, and qubes.Gpg2 from anything else to sd-gpg should be denied
52+
def test_qubesgpg_from_other_to_sdgpg_denied():
53+
assert not policy_exists("sys-net", "sd-gpg", "qubes.Gpg")
54+
assert not policy_exists("sys-firewall", "sd-gpg", "qubes.Gpg")
55+
assert not policy_exists("sys-net", "sd-gpg", "qubes.GpgImportKey")
56+
assert not policy_exists("sys-firewall", "sd-gpg", "qubes.GpgImportKey")
57+
assert not policy_exists("sys-net", "sd-gpg", "qubes.Gpg2")
58+
assert not policy_exists("sys-firewall", "sd-gpg", "qubes.Gpg2")

tests/test_qubes_vms.py

Lines changed: 26 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,31 @@
1-
import unittest
2-
3-
from qubesadmin import Qubes
4-
51
from tests.base import CURRENT_FEDORA_DVM, CURRENT_FEDORA_TEMPLATE
62

3+
"""
4+
Ensures that the upstream, Qubes-maintained VMs are
5+
sufficiently up to date.
6+
"""
77

8-
class SD_Qubes_VM_Tests(unittest.TestCase):
8+
9+
def test_current_fedora_for_sys_vms(all_vms):
910
"""
10-
Ensures that the upstream, Qubes-maintained VMs are
11-
sufficiently up to date.
11+
Checks that all sys-* VMs are configured to use
12+
an up-to-date version of Fedora.
1213
"""
13-
14-
def setUp(self):
15-
self.app = Qubes()
16-
17-
def tearDown(self):
18-
pass
19-
20-
def test_current_fedora_for_sys_vms(self):
21-
"""
22-
Checks that all sys-* VMs are configured to use
23-
an up-to-date version of Fedora.
24-
"""
25-
sys_vms = ["sys-firewall", "sys-net", "sys-usb", "default-mgmt-dvm"]
26-
sys_vms_maybe_disp = ["sys-firewall", "sys-usb"]
27-
sys_vms_custom_disp = ["sys-usb"]
28-
29-
for sys_vm in sys_vms:
30-
vm = self.app.domains[sys_vm]
31-
wanted_templates = [CURRENT_FEDORA_TEMPLATE]
32-
if sys_vm in sys_vms_maybe_disp:
33-
if sys_vm in sys_vms_custom_disp:
34-
wanted_templates.append(f"sd-{CURRENT_FEDORA_DVM}")
35-
else:
36-
wanted_templates.append(CURRENT_FEDORA_DVM)
37-
38-
assert vm.template.name in wanted_templates, (
39-
f"Unexpected template for {sys_vm}\n"
40-
+ f"Current: {vm.template.name}\n"
41-
+ "Expected: {}".format(", ".join(wanted_templates))
42-
)
14+
sys_vms = ["sys-firewall", "sys-net", "sys-usb", "default-mgmt-dvm"]
15+
sys_vms_maybe_disp = ["sys-firewall", "sys-usb"]
16+
sys_vms_custom_disp = ["sys-usb"]
17+
18+
for sys_vm in sys_vms:
19+
vm = all_vms[sys_vm]
20+
wanted_templates = [CURRENT_FEDORA_TEMPLATE]
21+
if sys_vm in sys_vms_maybe_disp:
22+
if sys_vm in sys_vms_custom_disp:
23+
wanted_templates.append(f"sd-{CURRENT_FEDORA_DVM}")
24+
else:
25+
wanted_templates.append(CURRENT_FEDORA_DVM)
26+
27+
assert vm.template.name in wanted_templates, (
28+
f"Unexpected template for {sys_vm}\n"
29+
+ f"Current: {vm.template.name}\n"
30+
+ "Expected: {}".format(", ".join(wanted_templates))
31+
)

0 commit comments

Comments
 (0)