-
Notifications
You must be signed in to change notification settings - Fork 48
Expand file tree
/
Copy pathtest_vms_platform.py
More file actions
259 lines (229 loc) · 10.5 KB
/
test_vms_platform.py
File metadata and controls
259 lines (229 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
import json
import unittest
import subprocess
from qubesadmin import Qubes
from base import WANTED_VMS, CURRENT_FEDORA_TEMPLATE
SUPPORTED_PLATFORMS = [
"Debian GNU/Linux 10 (buster)",
]
apt_url = ""
FPF_APT_SOURCES_STRETCH_DEV = "deb [arch=amd64] https://apt-test.freedom.press stretch main"
FPF_APT_SOURCES_BUSTER_DEV = "deb [arch=amd64] https://apt-test.freedom.press buster main"
FPF_APT_SOURCES_STRETCH = "deb [arch=amd64] https://apt.freedom.press stretch main"
FPF_APT_SOURCES_BUSTER = "deb [arch=amd64] https://apt.freedom.press buster main"
APT_SOURCES_FILE = "/etc/apt/sources.list.d/securedrop_workstation.list"
class SD_VM_Platform_Tests(unittest.TestCase):
def setUp(self):
self.app = Qubes()
with open("config.json") as c:
config = json.load(c)
if "environment" not in config:
config["environment"] = "dev"
if config["environment"] == "prod":
self.apt_url = FPF_APT_SOURCES_BUSTER
else:
self.apt_url = FPF_APT_SOURCES_BUSTER_DEV
def tearDown(self):
pass
def _get_platform_info(self, vm):
"""
Retrieve base OS for running AppVM. Executes command on AppVM
and returns the PRETTY_NAME field from /etc/os-release.
"""
# Not using `lsb_release` for platform info, because it is not present
# on Qubes AppVMs. Rather than install it simply for testing purposes,
# let's maintain the default config and retrieve the value elsewise.
cmd = "perl -nE '/^PRETTY_NAME=\"(.*)\"$/ and say $1' /etc/os-release"
stdout, stderr = vm.run(cmd)
platform = stdout.decode("utf-8").rstrip("\n")
return platform
def _validate_vm_platform(self, vm):
"""
Asserts that the given AppVM is based on an OS listed in the
SUPPORTED_PLATFORMS list, as specified in tests.
sd-whonix and sd-proxy are based on whonix-15 templates.
All other workstation-provisioned VMs
should be buster based.
"""
platform = self._get_platform_info(vm)
self.assertIn(platform, SUPPORTED_PLATFORMS)
def _validate_apt_sources(self, vm):
"""
Asserts that the given AppVM has the proper apt sources list in
/etc/apt/sources.list.d/securedrop_workstation.list
"""
# sd-whonix does not use the fpf-apt-test-repo
if vm.name in ["sd-whonix"]:
pass
else:
cmd = "cat {}".format(APT_SOURCES_FILE)
stdout, stderr = vm.run(cmd)
contents = stdout.decode("utf-8").rstrip("\n")
self.assertTrue(self.apt_url in contents)
self.assertFalse(FPF_APT_SOURCES_STRETCH in contents)
# Old alpha URL for apt repo should be absent
self.assertFalse("apt-test-qubes.freedom.press" in contents)
def _ensure_packages_up_to_date(self, vm, fedora=False):
"""
Asserts that all available packages are installed; no pending
updates. Assumes VM is Debian-based, so uses apt, but supports
`fedora=True` to use dnf instead.
"""
# Create custom error message, so failing test cases display
# which VM caused the looped check to fail.
fail_msg = "Unapplied updates for VM '{}'".format(vm)
if not fedora:
cmd = "apt list --upgradable"
stdout, stderr = vm.run(cmd)
results = stdout.rstrip().decode("utf-8")
# `apt list` will always print "Listing..." to stdout,
# so expect only that string.
self.assertEqual(results, "Listing...", fail_msg)
else:
cmd = "sudo dnf check-update"
# Will raise CalledProcessError if updates available
stdout, stderr = vm.run(cmd)
# 'stdout' will contain timestamped progress info; ignore it
results = stderr.rstrip().decode("utf-8")
self.assertEqual(results, "", fail_msg)
def _ensure_jessie_backports_disabled(self, vm):
"""
Ensures that there are no Debian Jessie repos configured in
apt source lists. This is only relevant for Debian Stretch,
on which misconfigured apt sources containing Jessie references
will cause apt commands to fail.
"""
# Use a fileglob to account for /etc/apt/sources.list.d/, as well.
# Add `|| true` to ensure dom0 receives a zero exit code.
cmd = "grep -i jessie /etc/apt/sources.list* || true"
# Will raise CalledProcessError if no hits found
stdout, stderr = vm.run(cmd)
results = stdout.rstrip().decode("utf-8")
# We expect zero hits, so confirm output is empty string.
self.assertEqual(results, "")
def _ensure_keyring_package_exists_and_has_correct_key(self, vm):
"""
Inspect the securedrop-keyring used by apt to ensure the correct key
and only the correct key is installed in that location.
"""
keyring_path = "/etc/apt/trusted.gpg.d/securedrop-keyring.gpg"
# apt-key finger doesnt work here due to stdout/terminal
# We also set the homedir to bypass whonix-specific gnupg.conf
cmd = "gpg --homedir /tmp --no-default-keyring --keyring {} -k".format(keyring_path)
stdout, stderr = vm.run(cmd)
results = stdout.rstrip().decode("utf-8")
fpf_gpg_pub_key_info = ["{}".format(keyring_path)]
fpf_gpg_pub_key_info += [
"---------------------------------------------",
"pub rsa4096 2016-10-20 [SC] [expires: 2021-06-30]",
" 22245C81E3BAEB4138B36061310F561200F4AD77",
"uid [ unknown] SecureDrop Release Signing Key",
"uid [ unknown] SecureDrop Release Signing Key <securedrop-release-key@freedom.press>", # noqa: E501
]
self.assertEqual(fpf_gpg_pub_key_info, results.split("\n"))
def _ensure_trusted_keyring_securedrop_key_removed(self, vm):
"""
Ensures the production key is no longer found in the default apt keyring
In testeing dev/staging, that keyring will be used for the test apt key,
the goal is to reduce of the production key
"""
# apt-key finger doesnt work here due to stdout/terminal
cmd = "gpg --no-default-keyring --keyring /etc/apt/trusted.gpg -k"
stdout, stderr = vm.run(cmd)
results = stdout.rstrip().decode("utf-8")
fpf_gpg_pub_key_fp = "22245C81E3BAEB4138B36061310F561200F4AD77"
self.assertFalse(fpf_gpg_pub_key_fp in results)
def test_all_jessie_backports_disabled(self):
"""
Asserts that all VMs lack references to Jessie in apt config.
"""
for vm_name in WANTED_VMS:
vm = self.app.domains[vm_name]
self._ensure_jessie_backports_disabled(vm)
def test_all_sd_vms_uptodate(self):
"""
Asserts that all VMs have all available apt packages at the latest
versions, with no updates pending.
"""
for vm_name in WANTED_VMS:
vm = self.app.domains[vm_name]
self._ensure_packages_up_to_date(vm)
def test_all_fedora_vms_uptodate(self):
"""
Asserts that all Fedora-based templates, such as sys-net, have all
available packages at the latest versions, with no updates pending.
"""
# Technically we want to know whether the sys-firewall, sys-net, and
# sys-usb VMs have their updates installed. This test assumes those
# AppVMs are based on the most recent Fedora version.
vm_name = CURRENT_FEDORA_TEMPLATE
vm = self.app.domains[vm_name]
self._ensure_packages_up_to_date(vm, fedora=True)
vm.shutdown()
def test_sd_proxy_template(self):
"""
Asserts that the 'sd-proxy' VM is using a supported base OS.
"""
# This test is a single example of the method for testing: it would
# be ideal to use a loop construct (such as pytest.mark.parametrize),
# but doing so would introduce additional dependencies to dom0.
vm = self.app.domains["sd-proxy"]
self._validate_vm_platform(vm)
def test_all_sd_vm_platforms(self):
"""
Test all VM platforms iteratively.
Due to for-loop implementation, the first failure will stop the test.
Therefore, even if multiple VMs are NOT running a supported platform,
only a single failure will be reported.
"""
# Would prefer to use a feature like pytest.mark.parametrize
# for better error output here, but not available in dom0.
for vm_name in WANTED_VMS:
vm = self.app.domains[vm_name]
self._validate_vm_platform(vm)
def test_dispvm_default_platform(self):
"""
Query dom0 Qubes preferences and confirm that new DispVMs
will be created under a supported OS. Requires a separate
test because DispVMs may not be running at present.
"""
cmd = ["qubes-prefs", "default_dispvm"]
result = subprocess.check_output(cmd).decode("utf-8").rstrip("\n")
self.assertEqual(result, "sd-viewer")
def test_sys_vms_use_supported_fedora(self):
"""
The 'sys-*' VMs must be updated to use the latest version of Fedora,
to ensure critical components such as 'sys-firewall' receive security
updates.
"""
sys_vms = [
"sys-firewall",
"sys-net",
"sys-usb",
]
for vm in sys_vms:
wanted_template = CURRENT_FEDORA_TEMPLATE
found_template = self.app.domains[vm].template.name
self.assertEqual(wanted_template, found_template)
def test_all_sd_vm_apt_sources(self):
"""
Test all VMs fpf apt source list iteratively.
Due to for-loop implementation, the first failure will stop the test.
Therefore, even if multiple VMs are NOT running a supported platform,
only a single failure will be reported.
"""
for vm_name in WANTED_VMS:
vm = self.app.domains[vm_name]
self._validate_apt_sources(vm)
def test_debian_keyring_config(self):
"""
Ensure the securedrop keyring package is properly installed and the
key it contains is up-to-date.
"""
for vm_name in WANTED_VMS:
vm = self.app.domains[vm_name]
self._ensure_keyring_package_exists_and_has_correct_key(vm)
self._ensure_trusted_keyring_securedrop_key_removed(vm)
def load_tests(loader, tests, pattern):
suite = unittest.TestLoader().loadTestsFromTestCase(SD_VM_Platform_Tests)
return suite