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

Automatically update dom0 and VM configs over time #172

Merged
merged 9 commits into from
Oct 31, 2018
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: 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding the --clean option here will refresh dnf cache, which might be useful in some cases.

I just had an issue where qubes-dom0-update was complaining of an unsigned package, due to me attempting to download an older whonix template in an effort to reproduce #122 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, probably worth adding here, lest we forget to circle back—feel free to append, @emkll.


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

emkll marked this conversation as resolved.
Show resolved Hide resolved
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