Skip to content

Commit

Permalink
Merge pull request #172 from freedomofpress/24-automatically-upgrade-vms
Browse files Browse the repository at this point in the history
Automatically update dom0 and VM configs over time
  • Loading branch information
emkll authored Oct 31, 2018
2 parents df92da6 + 4c75e27 commit 121a01e
Show file tree
Hide file tree
Showing 11 changed files with 172 additions and 30 deletions.
2 changes: 2 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[flake8]
ignore: W605
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@ endif

## Builds and provisions all VMs required for testing workstation
all: assert-dom0 validate clean update-fedora-templates \
update-whonix-templates prep-whonix sd-workstation-template \
update-whonix-templates prep-whonix prep-dom0 sd-workstation-template \
sd-whonix sd-svs sd-gpg \
sd-journalist sd-svs-disp

clone: assert-dom0 ## Pulls the latest repo from work VM to dom0
@./scripts/clone-to-dom0


sd-workstation-template: prep-salt ## Provisions base template for SDW AppVMs
sudo qubesctl top.enable sd-workstation-template
sudo qubesctl top.enable sd-workstation-template-files
Expand Down Expand Up @@ -128,6 +127,11 @@ prep-whonix: ## enables apparmor on whonix-ws-14 and whonix-gw-14
qvm-prefs -s whonix-gw-14 kernelopts "nopat apparmor=1 security=apparmor"
qvm-prefs -s whonix-ws-14 kernelopts "nopat apparmor=1 security=apparmor"

prep-dom0: prep-salt # Copies dom0 config files for VM updates
sudo qubesctl top.enable sd-vm-updates
sudo qubesctl top.enable sd-dom0-files
sudo qubesctl --targets dom0 state.highstate

list-vms: ## Prints all Qubes VMs managed by Workstation salt config
@./scripts/list-vms

Expand Down
48 changes: 48 additions & 0 deletions dom0/sd-dom0-files.sls
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
# vim: set syntax=yaml ts=2 sw=2 sts=2 et :

##
# Installs dom0 config scripts specific to tracking updates
# over time. These scripts should be ported to an RPM package.
##


# Copy script to system location so admins can run ad-hoc
dom0-update-securedrop-script:
file.managed:
- name: /usr/bin/securedrop-update
- source: salt://securedrop-update
- user: root
- group: root
- mode: 755

# Symlink update script into cron, for single point of update
dom0-update-securedrop-script-cron:
file.symlink:
- name: /etc/cron.daily/securedrop-update-cron
- target: /usr/bin/securedrop-update

# Create directory for storing SecureDrop-specific icons
dom0-securedrop-icons-directory:
file.directory:
- name: /usr/share/securedrop/icons
- user: root
- group: root
- mode: 755
- makedirs: True

# Copy SecureDrop icon for use in GUI feedback. It's also present in
# the Salt directory, but the permissions on that dir don't permit
# normal user reads.
dom0-securedrop-icon:
file.managed:
- name: /usr/share/securedrop/icons/sd-logo.png
- source: salt://sd/sd-journalist/logo-small.png
- user: root
- group: root
- mode: 644
# Dependency on parent dir should be explicitly declared,
# but the require syntax below was throwing an error that the
# referenced task was "not available".
# require:
# - dom0-securedrop-icons-directory
6 changes: 6 additions & 0 deletions dom0/sd-dom0-files.top
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# vim: set syntax=yaml ts=2 sw=2 sts=2 et :

base:
dom0:
- sd-dom0-files
9 changes: 9 additions & 0 deletions dom0/sd-vm-updates.top
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# vim: set syntax=yaml ts=2 sw=2 sts=2 et :

# "Placeholder" config to trigger TemplateVM boots,
# so upgrades can be applied automatically via cron.
base:
qubes:type:template:
- match: pillar
- topd
52 changes: 52 additions & 0 deletions dom0/securedrop-update
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/bin/bash
# Utility for dom- to ensure all updates are regularly installed
set -e
set -u

# Number of VMs to update in parallel. Default is 4,
# which can be memory-intensive.
SECUREDROP_MAX_CONCURRENCY=2


# Ensure elevated privileges
if [[ "$EUID" -ne 0 ]]; then
echo "Script must be run as root! Exiting..."
exit 1
fi

# Display GUI feedback about update process
function securedrop-update-feedback() {
# Unpack msg as arg1
local msg="$1"
shift

# Running `notify-send` as root doesn't work, must be normal user.
# Setting 60s expire time (in ms) since it's a long-running cmd.
local qubes_user
qubes_user="$(id -nu 1000)"
su "$qubes_user" -c "notify-send \
--app-name 'SecureDrop Workstation' \
--icon /usr/share/securedrop/icons/sd-logo.png \
--expire-time 60000 \
'SecureDrop: $msg'"
}

# `qubesctl pkg.upgrade` will automatically update dom0 packages, as well,
# but we *first* want the freshest RPMs from dom0, *then* we'll want to
# update the VMs themselves.
securedrop-update-feedback "Updating dom0 configuration..."
sudo qubes-dom0-update -y

securedrop-update-feedback "Updating application..."
qubesctl --skip-dom0 --templates \
--max-concurrency "$SECUREDROP_MAX_CONCURRENCY" \
pkg.upgrade refresh=true dist_upgrade=true

securedrop-update-feedback "Updating VM configuration..."
qubesctl \
--max-concurrency "$SECUREDROP_MAX_CONCURRENCY" \
state.highstate

securedrop-update-feedback \
"Updates installed. Please reboot the workstation \
to ensure the latest security fixes are applied."
2 changes: 1 addition & 1 deletion sd-journalist/sd-process-display
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import sys
import PyQt4.QtCore as QtCore
from PyQt4.QtGui import QDialog, QDialogButtonBox, QApplication, \
QLabel, QVBoxLayout, QImage, QPixmap
QLabel, QVBoxLayout, QImage, QPixmap
from PyQt4.QtCore import Qt
import os
import threading
Expand Down
4 changes: 2 additions & 2 deletions sd-svs/decrypt-sd-submission
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ try:
# given a malicious tarball
tar.extractall(tmpdir)

except Exception as e:
except Exception:
send_progress("DECRYPTION_BUNDLE_OPEN_FAILURE")
# although we're exiting with failure, we return a
# 0 exit code so xdg does not try to re-open this file with
Expand Down Expand Up @@ -139,7 +139,7 @@ try:
print(os.path.join(tmpdir, "extracted"))
distutils.dir_util.copy_tree(os.path.join(tmpdir, "extracted"),
"/home/user/Sources/", update=1)
except Exception as e:
except Exception:
send_progress("DECRYPTED_BUNDLE_COPY_FAILURE")
shutil.rmtree(tmpdir)
sys.exit(0)
Expand Down
30 changes: 15 additions & 15 deletions tests/integration/test_integration
Original file line number Diff line number Diff line change
Expand Up @@ -142,35 +142,35 @@ def run_q(n):

if run_q("full-success"):
receiver.expected_states = [
"DECRYPTION_PROCESS_START",
"SUBMISSION_BUNDLE_UNBUNDLED",
"SUBMISSION_FILES_EXTRACTED",
"SUBMISSION_FILE_DECRYPTION_SUCCEEDED",
"SUBMISSION_FILE_DECRYPTION_SUCCEEDED",
"DECRYPTED_BUNDLE_ON_SVS",
"DECRYPTED_FILES_AVAILABLE",
"DECRYPTION_PROCESS_START",
"SUBMISSION_BUNDLE_UNBUNDLED",
"SUBMISSION_FILES_EXTRACTED",
"SUBMISSION_FILE_DECRYPTION_SUCCEEDED",
"SUBMISSION_FILE_DECRYPTION_SUCCEEDED",
"DECRYPTED_BUNDLE_ON_SVS",
"DECRYPTED_FILES_AVAILABLE",
]
receiver.cmd = ["qvm-open-in-vm", "sd-svs", "./all.sd-xfer"]
receiver.test_name = "full successful open"
receiver.test()

if run_q("empty-download"):
receiver.expected_states = [
"DECRYPTION_PROCESS_START",
"DECRYPTION_BUNDLE_OPEN_FAILURE",
"DECRYPTION_PROCESS_START",
"DECRYPTION_BUNDLE_OPEN_FAILURE",
]
receiver.cmd = ["qvm-open-in-vm", "sd-svs", "./empty.sd-xfer"]
receiver.test_name = "empty sd-xfer"
receiver.test()

if run_q("bad-gpg-key"):
receiver.expected_states = [
"DECRYPTION_PROCESS_START",
"SUBMISSION_BUNDLE_UNBUNDLED",
"SUBMISSION_FILES_EXTRACTED",
"SUBMISSION_FILE_DECRYPTION_FAILED",
"SUBMISSION_FILE_DECRYPTION_FAILED",
"SUBMISSION_FILE_NO_FILES_FOUND",
"DECRYPTION_PROCESS_START",
"SUBMISSION_BUNDLE_UNBUNDLED",
"SUBMISSION_FILES_EXTRACTED",
"SUBMISSION_FILE_DECRYPTION_FAILED",
"SUBMISSION_FILE_DECRYPTION_FAILED",
"SUBMISSION_FILE_NO_FILES_FOUND",
]
receiver.cmd = ["qvm-open-in-vm", "sd-svs", "./bad-key.sd-xfer"]
receiver.test_name = "bad encryption key"
Expand Down
20 changes: 10 additions & 10 deletions tests/test_svs.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,28 @@ def setUp(self):

def test_decrypt_sd_submission(self):
self.assertFilesMatch(
"/usr/bin/decrypt-sd-submission",
"sd-svs/decrypt-sd-submission")
"/usr/bin/decrypt-sd-submission",
"sd-svs/decrypt-sd-submission")

def test_decrypt_sd_submission_desktop(self):
self.assertFilesMatch(
"/usr/share/applications/decrypt-sd-submission.desktop",
"sd-svs/decrypt-sd-submission.desktop")
"/usr/share/applications/decrypt-sd-submission.desktop",
"sd-svs/decrypt-sd-submission.desktop")

def test_decrypt_sd_user_profile(self):
self.assertFilesMatch(
"/etc/profile.d/sd-svs-qubes-gpg-domain.sh",
"sd-svs/dot-profile")
"/etc/profile.d/sd-svs-qubes-gpg-domain.sh",
"sd-svs/dot-profile")

def test_open_in_dvm_desktop(self):
self.assertFilesMatch(
"/usr/share/applications/open-in-dvm.desktop",
"sd-svs/open-in-dvm.desktop")
"/usr/share/applications/open-in-dvm.desktop",
"sd-svs/open-in-dvm.desktop")

def test_mimeapps(self):
self.assertFilesMatch(
"/usr/share/applications/mimeapps.list",
"sd-svs/mimeapps.list")
"/usr/share/applications/mimeapps.list",
"sd-svs/mimeapps.list")


def load_tests(loader, tests, pattern):
Expand Down
21 changes: 21 additions & 0 deletions tests/test_vms_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,27 @@ def _validate_vm_platform(self, vm):
platform = self._get_platform_info(vm)
self.assertIn(platform, SUPPORTED_PLATFORMS)

def _ensure_packages_up_to_date(self, vm):
"""
Asserts that all available apt packages are installed; no pending
updates.
"""
cmd = "apt list --upgradable"
stdout, stderr = vm.run(cmd)
results = stdout.rstrip()
# `apt list` will always print "Listing..." to stdout,
# so expect only that string.
self.assertEqual(results, "Listing...")

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_sd_journalist_template(self):
"""
Asserts that the 'sd-journalist' VM is using a supported base OS.
Expand Down

0 comments on commit 121a01e

Please sign in to comment.