Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Harden sd-viewer configuration and provision mailcap default rules; bump version #661

Merged
merged 4 commits into from
Mar 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.5.2
0.5.3
9 changes: 9 additions & 0 deletions dom0/sd-dom0-qvm-rpc.sls
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ dom0-rpc-qubes.Filecopy:
sd-proxy @tag:sd-client allow
@anyvm @tag:sd-workstation deny
@tag:sd-workstation @anyvm deny
dom0-rpc-qubes.GetImageRGBA:
file.blockreplace:
- name: /etc/qubes-rpc/policy/qubes.GetImageRGBA
- prepend_if_not_found: True
- marker_start: "### BEGIN securedrop-workstation ###"
- marker_end: "### END securedrop-workstation ###"
- content: |
@anyvm @tag:sd-workstation deny
@tag:sd-workstation @anyvm deny
dom0-rpc-qubes.OpenInVM:
file.blockreplace:
- name: /etc/qubes-rpc/policy/qubes.OpenInVM
Expand Down
7 changes: 7 additions & 0 deletions dom0/sd-mime-handling.sls
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,10 @@ sd-private-volume-mimeapps-handling:
- file: sd-private-volume-mimeapps-config-dir

{% endif %}

sd-private-volume-mailcap-handling:
file.symlink:
- name: /home/user/.mailcap
- target: /opt/sdw/mailcap.default
- user: user
- group: user
1 change: 1 addition & 0 deletions dom0/sd-viewer.sls
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ sd-viewer:
- template: sd-large-buster-template
- netvm: ""
- template_for_dispvms: True
- default_dispvm: ""
- tags:
- add:
- sd-workstation
Expand Down
3 changes: 0 additions & 3 deletions dom0/securedrop-check-migration
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,4 @@ if [[ -n "$(qvm-ls --tags sd-workstation --raw-list | perl -nE '/sd-(?!small|lar
reason="template-consolidation"
echo "Migration required for ${reason}, will re-run Salt states."
request_migration "$reason"
else
echo "No migration required, skipping full state run against all VMs."
rm -rf "${flag_dir}"
fi
12 changes: 9 additions & 3 deletions rpm-build/SPECS/securedrop-workstation-dom0-config.spec
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
Name: securedrop-workstation-dom0-config
Version: 0.5.2
Version: 0.5.3
Release: 1%{?dist}
Summary: SecureDrop Workstation

Group: Library
License: GPLv3+
URL: https://github.com/freedomofpress/securedrop-workstation
Source0: securedrop-workstation-dom0-config-0.5.2.tar.gz
Source0: securedrop-workstation-dom0-config-0.5.3.tar.gz

BuildArch: noarch
BuildRequires: python3-setuptools
Expand All @@ -28,7 +28,7 @@ configuration over time.
%undefine py_auto_byte_compile

%prep
%setup -n securedrop-workstation-dom0-config-0.5.2
%setup -n securedrop-workstation-dom0-config-0.5.3

%build
%{__python3} setup.py build
Expand Down Expand Up @@ -105,8 +105,14 @@ find /srv/salt -maxdepth 1 -type f -iname '*.top' \
| xargs -n1 basename \
| sed -e 's/\.top$$//g' \
| xargs qubesctl top.enable > /dev/null
mkdir -p /tmp/sdw-migrations
touch /tmp/sdw-migrations/mailcap-hardening

%changelog
* Wed Mar 10 2021 SecureDrop Team <securedrop@freedom.press> - 0.5.3
- Prevents sd-viewer from launching disposable VMs
- Provisions default mailcap rules to enforce Fail Closed behavior

* Fri Nov 20 2020 SecureDrop Team <securedrop@freedom.press> - 0.5.2
- Fixes updater, ensuring dom0 packages are updated

Expand Down
29 changes: 29 additions & 0 deletions tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,35 @@ def logging_configured(self):
# cmd_output = self._run("sudo grep -F \"action 'action-0-omprog' suspended (module 'omprog')\" /var/log/syslog | wc -l").strip() # noqa
# self.assertTrue(cmd_output == "0")

def mailcap_hardened(self):
"""
Ensure that mailcap rules are not used as a fallback when looking up
appropriate viewer applications for files of a given MIME type.
"""

# Ensure that mailcap configuration files are present in the expected
# locations and contain the expected contents. Rules in `~/.mailcap`
# take precedence over those in `/etc/mailcap`.
self.assertTrue(self._fileExists("/home/user/.mailcap"))
self.assertTrue(self._fileExists("/opt/sdw/mailcap.default"))
self.assertFileHasLine("/home/user/.mailcap", '*/*; logger "Mailcap is disabled."')

# Because we target an AppVM, we cannot easily use the Pyhton tempfile
# module here without relying on a helper script. Instead, we use the
# mktemp utility to create a test file with the OpenDocument extension.
tmpfile_name = self._run("mktemp -t XXXXXX.odt")

# The --norun argument ensures that we do not launch any application,
# regardless of the result of this invocation.
mailcap_result = self._run("run-mailcap --norun {}".format(tmpfile_name))

# For simplicity, we remove the tempfile here instead of in a separate
# teardown method.
self._run("rm {}".format(tmpfile_name))

# Ensure that the wildcard rule worked as expected.
self.assertEqual(mailcap_result, 'logger "Mailcap is disabled." <{}'.format(tmpfile_name))

def qubes_gpg_domain_configured(self, vmname=False):
"""
Ensure the QUBES_GPG_DOMAIN is properly set for a given AppVM. This
Expand Down
5 changes: 4 additions & 1 deletion tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def test_open_in_dvm_desktop(self):
contents = self._get_file_contents("/usr/share/applications/open-in-dvm.desktop")
expected_contents = [
"TryExec=/usr/bin/qvm-open-in-vm",
"Exec=/usr/bin/qvm-open-in-vm --view-only '@dispvm:sd-viewer' %f",
"Exec=/usr/bin/qvm-open-in-vm --view-only @dispvm:sd-viewer %f",
]
for line in expected_contents:
self.assertTrue(line in contents)
Expand All @@ -35,6 +35,9 @@ def test_mimeapps_functional(self):
actual_app = self._run("xdg-mime query default {}".format(line))
self.assertEqual(actual_app, "open-in-dvm.desktop")

def test_mailcap_hardened(self):
self.mailcap_hardened()

def test_sd_client_package_installed(self):
self.assertTrue(self._package_is_installed("securedrop-client"))

Expand Down
3 changes: 3 additions & 0 deletions tests/test_proxy_vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ def test_mime_types(self):
actual_app = self._run("xdg-mime query default {}".format(line))
self.assertEqual(actual_app, "open-in-dvm.desktop")

def test_mailcap_hardened(self):
self.mailcap_hardened()

def test_gpg_domain_configured(self):
self.qubes_gpg_domain_configured(self.vm_name)

Expand Down
5 changes: 4 additions & 1 deletion tests/test_sd_devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,14 @@ def test_mime_types(self):
actual_app = self._run("xdg-mime query default {}".format(mime_type))
self.assertEqual(actual_app, expected_app)

def test_mailcap_hardened(self):
self.mailcap_hardened()

def test_open_in_dvm_desktop(self):
contents = self._get_file_contents("/usr/share/applications/open-in-dvm.desktop")
expected_contents = [
"TryExec=/usr/bin/qvm-open-in-vm",
"Exec=/usr/bin/qvm-open-in-vm --view-only '@dispvm:sd-viewer' %f",
"Exec=/usr/bin/qvm-open-in-vm --view-only @dispvm:sd-viewer %f",
]
for line in expected_contents:
self.assertTrue(line in contents)
Expand Down
3 changes: 3 additions & 0 deletions tests/test_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ def test_mime_types(self):
actual_app = self._run("xdg-mime query default {}".format(mime_type))
self.assertEqual(actual_app, expected_app)

def test_mailcap_hardened(self):
self.mailcap_hardened()

def test_gpg_domain_configured(self):
self.qubes_gpg_domain_configured(self.vm_name)

Expand Down
2 changes: 2 additions & 0 deletions tests/test_vms_exist.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ def test_sd_viewer_config(self):
self.assertTrue(vm.template == "sd-large-buster-template")
self.assertFalse(vm.provides_network)
self.assertTrue(vm.template_for_dispvms)
# sd-viewer should not be able to create other disposable VMs
self.assertIsNone(vm.default_dispvm)
self._check_kernel(vm)
self._check_service_running(vm, "paxctld")
self.assertTrue("sd-workstation" in vm.tags)
Expand Down
17 changes: 7 additions & 10 deletions tests/vars/qubes-rpc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,21 @@
$tag:anon-vm $anyvm deny
$anyvm $anyvm allow,target=dom0

- policy: qubes.GetRandomizedTime
- policy: qubes.GetImageRGBA
starts_with: |-
## Note that policy parsing stops at the first match,
## so adding anything below "$anyvm $anyvm action" line will have no effect

## Please use a single # to start your custom comments

$anyvm dom0 allow
### BEGIN securedrop-workstation ###
@anyvm @tag:sd-workstation deny
@tag:sd-workstation @anyvm deny
### END securedrop-workstation ###

- policy: qubes.GetImageRGBA
- policy: qubes.GetRandomizedTime
starts_with: |-
## Note that policy parsing stops at the first match,
## so adding anything below "$anyvm $anyvm action" line will have no effect

## Please use a single # to start your custom comments

$anyvm $dispvm allow
$anyvm $anyvm ask
$anyvm dom0 allow

- policy: qubes.Gpg
starts_with: |-
Expand Down