diff --git a/Makefile b/Makefile index bb1823aa6..c1ff8974d 100644 --- a/Makefile +++ b/Makefile @@ -47,6 +47,10 @@ sd-export: prep-salt ## Provisions SD Export VM sudo qubesctl --show-output state.sls sd-export sudo qubesctl --show-output --skip-dom0 --targets sd-export-template,sd-export-usb,sd-export-usb-dvm state.highstate +sd-log: prep-salt ## Provisions SD logging VM + sudo qubesctl --show-output state.sls sd-log + sudo qubesctl --show-output --skip-dom0 --targets sd-log-template,sd-log state.highstate + clean-salt: assert-dom0 ## Purges SD Salt configuration from dom0 @echo "Purging Salt config..." @sudo rm -rf /srv/salt/sd @@ -77,6 +81,9 @@ remove-sd-export: assert-dom0 ## Destroys SD EXPORT VMs @./scripts/destroy-vm sd-export-usb @./scripts/destroy-vm sd-export-usb-dvm +remove-sd-log: assert-dom0 ## Destroys SD logging VM + @./scripts/destroy-vm sd-log + clean: assert-dom0 prep-salt destroy-all ## Destroys all SD VMs sudo qubesctl --show-output state.sls sd-clean-all sudo dnf -y -q remove securedrop-workstation-dom0-config 2>/dev/null || true diff --git a/README.md b/README.md index 4fcc8db08..99389fb57 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ Currently, the following VMs are provisioned: - `sd-whonix` is the Tor gateway used to contact the journalist Tor hidden service. It's configured with the auth key for the hidden service. The default Qubes Whonix workstation uses the non-SecureDrop Whonix gateway, and thus won't be able to access the *Journalist Interface*. - `sd-gpg` is a Qubes split-gpg AppVM, used to hold submission decryption keys and do the actual submission crypto. - `sd-dispvm` is an AppVM used as the template for the disposable VMs used for processing and opening files. +- `sd-log` is an AppVM used for centralized logging - logs will appear in `~/QubesIncomingLogs` from each AppVM using the centralized logging service. Submissions are processed in the following steps: @@ -601,6 +602,14 @@ The *GPG VM* does not have network access, and the Qubes split-gpg mechanism res * An adversary can store and view any message that is being decrypted by the *SecureDrop Workstation*. * An adversary can attempt to elevate their privileges and escape the VM. +#### What Compromise of the *Log VM* (`sd-log`) Can Achieve + +The *Log VM* does not have network access nor does it contain any other secrets. + +* An adversary can read log messages from any VM using the centralized logging service. +* An adversary can tamper with log messages from any VM using the centralized logging service. +* An adversary can attempt to elevate their privileges and escape the VM. + #### What Compromise of `dom0` Can Achieve `dom0` can do all of the above: spawn arbitrary virtual machines, access all data, modify all *SecureDrop Workstation* provisioning code, as well as introduce mechanisms to establish persistence and exfiltrate data. By design, Qubes' `dom0` does not have network access, files cannot be copied to `dom0`, and clipboard sharing is disabled. diff --git a/dom0/sd-log-template-files.sls b/dom0/sd-log-template-files.sls new file mode 100644 index 000000000..b909e9fd0 --- /dev/null +++ b/dom0/sd-log-template-files.sls @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# vim: set syntax=yaml ts=2 sw=2 sts=2 et : +include: + - fpf-apt-test-repo + +install-securedrop-log-package: + pkg.installed: + - pkgs: + - securedrop-log + - require: + - sls: fpf-apt-test-repo diff --git a/dom0/sd-log.sls b/dom0/sd-log.sls new file mode 100644 index 000000000..2461958e9 --- /dev/null +++ b/dom0/sd-log.sls @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# vim: set syntax=yaml ts=2 sw=2 sts=2 et : + +# +# Installs 'sd-log' AppVM for collecting and storing logs +# from all SecureDrop related VMs. +# This VM has no network configured. +## +include: + - sd-workstation-template + +sd-log-template: + qvm.vm: + - name: sd-log-buster-template + - clone: + - source: securedrop-workstation-buster + - label: red + - tags: + - add: + - sd-workstation + - require: + - sls: sd-workstation-template + +sd-log: + qvm.vm: + - name: sd-log + - present: + - template: sd-log-buster-template + - label: red + - prefs: + - netvm: "" + - autostart: true + - tags: + - add: + - sd-workstation + - features: + - enable: + - service.paxctld + - require: + - qvm: sd-log-buster-template + +# Allow any SecureDrop VM to log to the centralized log VM +sd-log-dom0-oqubes.Logging: + file.prepend: + - name: /etc/qubes-rpc/policy/oqubes.Logging + - text: | + @tag:sd-workstation sd-log allow + @anyvm @anyvm deny diff --git a/dom0/sd-workstation.top b/dom0/sd-workstation.top index 3aff35ada..b765a0649 100644 --- a/dom0/sd-workstation.top +++ b/dom0/sd-workstation.top @@ -15,6 +15,7 @@ base: - sd-svs - sd-whonix - sd-remove-unused-templates + - sd-log sd-export-buster-template: - sd-export-files sd-gpg: @@ -31,6 +32,8 @@ base: - sd-sys-firewall-files sd-whonix: - sd-whonix-hidserv-key + sd-log-buster-template: + - sd-log-template-files securedrop-workstation-buster: - sd-workstation-template-files diff --git a/tests/base.py b/tests/base.py index 4f69cb255..bd8d3a104 100644 --- a/tests/base.py +++ b/tests/base.py @@ -8,6 +8,7 @@ # Reusable constant for DRY import across tests WANTED_VMS = [ "sd-gpg", + "sd-log", "sd-proxy", "sd-svs", "sd-svs-disp", diff --git a/tests/test_log_vm.py b/tests/test_log_vm.py new file mode 100644 index 000000000..bfe6a6f91 --- /dev/null +++ b/tests/test_log_vm.py @@ -0,0 +1,21 @@ +import unittest + +from base import SD_VM_Local_Test + + +class SD_Log_Tests(SD_VM_Local_Test): + def setUp(self): + self.vm_name = "sd-log" + super(SD_Log_Tests, self).setUp() + + def test_sd_log_package_installed(self): + self.assertTrue(self._package_is_installed("securedrop-log")) + + def test_log_utility_installed(self): + self.assertTrue(self._fileExists("/usr/sbin/securedrop-log")) + self.assertTrue(self._fileExists("/etc/qubes-rpc/securedrop.Logging")) + + +def load_tests(loader, tests, pattern): + suite = unittest.TestLoader().loadTestsFromTestCase(SD_Log_Tests) + return suite diff --git a/tests/test_vms_exist.py b/tests/test_vms_exist.py index f3708ebd4..3c7f167e7 100644 --- a/tests/test_vms_exist.py +++ b/tests/test_vms_exist.py @@ -102,6 +102,19 @@ def test_sd_gpg_config(self): self._check_kernel(vm) self.assertTrue('sd-workstation' in vm.tags) + def test_sd_log_config(self): + vm = self.app.domains["sd-log"] + nvm = vm.netvm + self.assertTrue(nvm is None) + self.assertTrue(vm.template == "sd-log-buster-template") + self.assertTrue(vm.autostart is True) + self.assertFalse(vm.provides_network) + self.assertFalse(vm.template_for_dispvms) + self._check_kernel(vm) + self._check_service_running(vm, "paxctld") + self.assertFalse(vm.template_for_dispvms) + self.assertTrue('sd-workstation' in vm.tags) + def test_sd_workstation_template(self): vm = self.app.domains["securedrop-workstation-buster"] nvm = vm.netvm @@ -156,6 +169,14 @@ def sd_export(self): self.assertTrue('sd-workstation' in vm.tags) self._check_kernel(vm) + def sd_log_template(self): + vm = self.app.domains["sd-log-buster-template"] + nvm = vm.netvm + self.assertTrue(nvm is None) + self.assertTrue('sd-workstation' in vm.tags) + self.assertFalse(vm.template_for_dispvms) + self._check_kernel(vm) + def load_tests(loader, tests, pattern): suite = unittest.TestLoader().loadTestsFromTestCase(SD_VM_Tests)