From fecd59603863bb1047a79a4d0dd74e18941794c1 Mon Sep 17 00:00:00 2001 From: mickael e Date: Thu, 5 Mar 2020 16:45:54 -0500 Subject: [PATCH 1/7] Add securedrop-admin uninstall Will remove all files and packages in a staging or prod setting config.json (containing instance configuration and Journalist Interface ATHS) and sd-journalist.sec (submission private key) will *not* be deleted, they should be found in two locations (unless they have been also copied elsewhere): - /usr/share/securedrop-workstation/dom0-config/ - /srv/salt/sd/ --- scripts/securedrop-admin.py | 56 +++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/scripts/securedrop-admin.py b/scripts/securedrop-admin.py index 557bf9f6..7d920b7e 100644 --- a/scripts/securedrop-admin.py +++ b/scripts/securedrop-admin.py @@ -32,6 +32,13 @@ def parse_args(): action="store_true", help="Validate the configuration", ) + parser.add_argument( + "--uninstall", + default=False, + required=False, + action="store_true", + help="Completely Uninstalls the SecureDrop Workstation", + ) args = parser.parse_args() return args @@ -72,6 +79,45 @@ def validate_config(path): raise SDAdminException("Error while validating configuration") +def perform_uninstall(): + + try: + subprocess.check_call( + ["sudo", "qubesctl", "state.sls", "sd-clean-default-dispvm"] + ) + print("Destroying all VMs") + subprocess.check_call( + [os.path.join(SCRIPTS_PATH, "scripts/destroy-vm"), "--all"] + ) + subprocess.check_call( + [ + "sudo", "qubesctl", "--skip-dom0", "--targets", + "whonix-gw-15", "state.sls", "sd-clean-whonix" + ] + ) + print("Reverting dom0 configuration") + + subprocess.check_call( + ["sudo", "qubesctl", "state.sls", "sd-clean-all"] + ) + print("Uninstalling Template") + subprocess.check_call( + ["sudo", "dnf", "-y", "-q", "remove", "qubes-template-securedrop-workstation-buster"] + ) + print("Uninstalling dom0 config package") + subprocess.check_call( + ["sudo", "dnf", "-y", "-q", "remove", "securedrop-workstation-dom0-config"] + ) + except subprocess.CalledProcessError: + raise SDAdminException("Error during uninstall") + + print( + "Instance secrets (Journalist Interface token and Submission private key) are still" + "present on disk. You can delete them in /usr/share/securedrop-workstation-dom0-config" + "/srv/salt/sd/" + ) + + def main(): args = parse_args() if args.validate: @@ -82,6 +128,16 @@ def main(): validate_config(SCRIPTS_PATH) copy_config() provision_all() + elif args.uninstall: + print( + "Uninstalling SecureDrop workstation will uninstall all packages and destroy all VMs" + ) + response = input("Are you sure you would want to uninstall (y/N)? ") + if response.lower() != 'y': + print("Exiting.") + sys.exit(0) + else: + perform_uninstall() else: sys.exit(0) From bc9b0a7d0c65e158f8fbc175a730e77810a5a597 Mon Sep 17 00:00:00 2001 From: mickael e Date: Thu, 5 Mar 2020 16:55:59 -0500 Subject: [PATCH 2/7] Separately delete dom0-rpm provisioned files for dev This will ensure idempotency of delete operations when invoking `sd-clean-all` in staging and production scenarios. --- dom0/sd-clean-all.sls | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/dom0/sd-clean-all.sls b/dom0/sd-clean-all.sls index 567702a7..1d5b5647 100644 --- a/dom0/sd-clean-all.sls +++ b/dom0/sd-clean-all.sls @@ -28,17 +28,16 @@ dom0-reset-power-management-xfce: - runas: {{ gui_user }} {% endif %} +# Removes all salt-provisioned files (if these files are also provisioned via +# RPM, they should be removed as part of remove-dom0-sdw-config-files-dev) remove-dom0-sdw-config-files: file.absent: - names: - - /opt/securedrop - /etc/yum.repos.d/securedrop-workstation-dom0.repo - /usr/bin/securedrop-update - /etc/pki/rpm-gpg/RPM-GPG-KEY-securedrop-workstation - /etc/pki/rpm-gpg/RPM-GPG-KEY-securedrop-workstation-test - /etc/cron.daily/securedrop-update-cron - - /srv/salt/securedrop-update - - /srv/salt/update-xfce-settings - /usr/share/securedrop/icons - /home/{{ gui_user }}/.config/autostart/SDWLogin.desktop - /usr/bin/securedrop-login @@ -47,6 +46,17 @@ remove-dom0-sdw-config-files: - /home/{{ gui_user }}/Desktop/securedrop-launcher.desktop - /home/{{ gui_user }}/.securedrop_launcher +# Removes files that are provisioned by the dom0 RPM, only for the development +# environment, since dnf takes care of those provisioned in the RPM +{% if d.environment == "dev" %} +remove-dom0-sdw-config-files-dev: + file.absent: + - names: + - /opt/securedrop + - /srv/salt/securedrop-update + - /srv/salt/update-xfce-settings +{% endif %} + sd-cleanup-etc-changes: file.replace: - names: @@ -94,4 +104,4 @@ sd-cleanup-rpc-policy-grants: - DOTALL - repl: '' - backup: no -{% endif %} \ No newline at end of file +{% endif %} From 28e0424322e9df71dd00b515be1ef35dea6cbddd Mon Sep 17 00:00:00 2001 From: mickael e Date: Tue, 10 Mar 2020 08:28:45 -0400 Subject: [PATCH 3/7] Remove instance config from /srv/salt/sd Since it's automatically copied from /usr/share, we can delete the duplicate in /srv/salt/sd --- scripts/securedrop-admin.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/securedrop-admin.py b/scripts/securedrop-admin.py index 7d920b7e..d247bd42 100644 --- a/scripts/securedrop-admin.py +++ b/scripts/securedrop-admin.py @@ -96,7 +96,12 @@ def perform_uninstall(): ] ) print("Reverting dom0 configuration") - + subprocess.check_call( + ["sudo", "rm", "/srv/salt/sd/sd-journalist.sec"] + ) + subprocess.check_call( + ["sudo", "rm", "/srv/salt/sd/config.json"] + ) subprocess.check_call( ["sudo", "qubesctl", "state.sls", "sd-clean-all"] ) @@ -114,7 +119,6 @@ def perform_uninstall(): print( "Instance secrets (Journalist Interface token and Submission private key) are still" "present on disk. You can delete them in /usr/share/securedrop-workstation-dom0-config" - "/srv/salt/sd/" ) From b6b721af5c27ab614647a35578fd7017ace610cc Mon Sep 17 00:00:00 2001 From: mickael e Date: Tue, 10 Mar 2020 10:00:02 -0400 Subject: [PATCH 4/7] Use blockreplace for sd-whonix tor config This ensures idempotency --- dom0/sd-whonix-hidserv-key.sls | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/dom0/sd-whonix-hidserv-key.sls b/dom0/sd-whonix-hidserv-key.sls index 3d50b54a..2456278d 100644 --- a/dom0/sd-whonix-hidserv-key.sls +++ b/dom0/sd-whonix-hidserv-key.sls @@ -6,14 +6,20 @@ # add hidden service auth key to torrc {% if d.hidserv.hostname|length == 22 %} sd-whonix-hidserv-key: - file.append: + file.blockreplace: - name: /usr/local/etc/torrc.d/50_user.conf - - text: HidServAuth {{ d.hidserv.hostname }} {{ d.hidserv.key }} + - append_if_not_found: True + - marker_start: "### BEGIN securedrop-workstation ###" + - marker_end: "### END securedrop-workstation ###" + - content: HidServAuth {{ d.hidserv.hostname }} {{ d.hidserv.key }} {% else %} sd-whonix-hidservv3-directory-path: - file.append: + file.blockreplace: - name: /usr/local/etc/torrc.d/50_user.conf - - text: ClientOnionAuthDir /var/lib/tor/keys + - append_if_not_found: True + - marker_start: "### BEGIN securedrop-workstation ###" + - marker_end: "### END securedrop-workstation ###" + - content: ClientOnionAuthDir /var/lib/tor/keys {% set hostname_without_onion = d.hidserv.hostname.split('.')[0] %} install-sd-whonix-tor-private-key: From 617b490c95098b4e83f3a8fe28c9bbe35ce09e79 Mon Sep 17 00:00:00 2001 From: Conor Schaefer Date: Tue, 10 Mar 2020 15:59:26 -0700 Subject: [PATCH 5/7] Config test sd-whonix: verify tor config If the torrc end state is broken on sd-whonix, we're sure to encounter it in various failures, but having an explicit test is well worth it. It's especially valuable to ask tor to perform the verification, given the include-heavy config used by Whonix: there's no single torrc file to inspect and reason about, but rather a chain of includes. Let's ask tor to assemble and assert it's valid. --- tests/test_sd_whonix.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_sd_whonix.py b/tests/test_sd_whonix.py index 3edd0c74..e74b6d86 100644 --- a/tests/test_sd_whonix.py +++ b/tests/test_sd_whonix.py @@ -61,6 +61,8 @@ def test_sd_whonix_repo_enabled(self): def test_logging_configured(self): self.logging_configured() + def test_sd_whonix_verify_tor_config(self): + self._run("tor --verify-config") def load_tests(loader, tests, pattern): suite = unittest.TestLoader().loadTestsFromTestCase(SD_Whonix_Tests) From d5c9839debe62af466867131b463b38cff3c2585 Mon Sep 17 00:00:00 2001 From: Conor Schaefer Date: Tue, 10 Mar 2020 18:06:32 -0700 Subject: [PATCH 6/7] Config test sd-whonix: check for multiple includes The specific problem of multiple includes causing recursion in imports for the tor config broke v2 onion services for the workstation (and whonix-gw overall). Let's monitor for regressions on that specific config state. --- tests/test_sd_whonix.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_sd_whonix.py b/tests/test_sd_whonix.py index e74b6d86..e1cf662d 100644 --- a/tests/test_sd_whonix.py +++ b/tests/test_sd_whonix.py @@ -64,6 +64,17 @@ def test_logging_configured(self): def test_sd_whonix_verify_tor_config(self): self._run("tor --verify-config") + def test_whonix_torrc(self): + """ + Ensure Whonix-maintained torrc files don't contain duplicate entries. + """ + torrc_contents = self._get_file_contents("/etc/tor/torrc") + duplicate_includes = """%include /etc/torrc.d/ +%include /etc/torrc.d/95_whonix.conf""" + self.assertFalse(duplicate_includes in torrc_contents, + "Whonix GW torrc contains duplicate %include lines") + + def load_tests(loader, tests, pattern): suite = unittest.TestLoader().loadTestsFromTestCase(SD_Whonix_Tests) return suite From 50f3cd625cb5f9bd661dacf46daa3e49a1eef6dd Mon Sep 17 00:00:00 2001 From: mickael e Date: Wed, 11 Mar 2020 12:29:24 -0400 Subject: [PATCH 7/7] Create clean-salt script Run clean-salt as part of securedrop-admin uninstall action --- Makefile | 8 +------- scripts/clean-salt | 27 +++++++++++++++++++++++++++ scripts/securedrop-admin.py | 7 ++----- 3 files changed, 30 insertions(+), 12 deletions(-) create mode 100755 scripts/clean-salt diff --git a/Makefile b/Makefile index 28675f51..bb9c20ac 100644 --- a/Makefile +++ b/Makefile @@ -77,13 +77,7 @@ sd-log: prep-salt ## Provisions SD logging VM sudo qubesctl --show-output --skip-dom0 --targets sd-log-buster-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 - @sudo rm -rf /srv/salt/launcher - @sudo find /srv/salt -maxdepth 1 -type f -iname 'fpf*' -delete - @sudo find /srv/salt -maxdepth 1 -type f -iname 'sd*' -delete - @sudo find /srv/salt -maxdepth 1 -type f -iname 'securedrop*' -delete - @sudo find /srv/salt/_tops -lname '/srv/salt/sd-*' -delete + @./scripts/clean-salt prep-salt: assert-dom0 ## Configures Salt layout for SD workstation VMs @./scripts/prep-salt diff --git a/scripts/clean-salt b/scripts/clean-salt new file mode 100755 index 00000000..5d5a8dc6 --- /dev/null +++ b/scripts/clean-salt @@ -0,0 +1,27 @@ +#!/bin/bash +# Utility script to clean Saltstack config +# files for the SecureDrop Workstation. +set -e +set -u +set -o pipefail + + +# Hardcoded location of SecureDrop Workstation salt config files +SDW_SALT_DIR="/srv/salt/sd" +SALT_DIR="/srv/salt" + +echo "Purging Salt config..." + +# If SDW Salt config dir already exists, delete all SecureDrop Workstation +# related Salt files. In production scenarios, most of these will be provisioned +# by the RPM package, but the top files and configs will not, so we should use a +# common script to ensure all config is removed. + +if [[ ! -d "$SDW_SALT_DIR" ]]; then + sudo rm -rf ${SDW_SALT_DIR} + sudo rm -rf ${SALT_DIR}/launcher + sudo find ${SALT_DIR} -maxdepth 1 -type f -iname 'fpf*' -delete + sudo find ${SALT_DIR} -maxdepth 1 -type f -iname 'sd*' -delete + sudo find ${SALT_DIR} -maxdepth 1 -type f -iname 'securedrop*' -delete + sudo find ${SALT_DIR}/_tops -lname '/srv/salt/sd-*' -delete +fi diff --git a/scripts/securedrop-admin.py b/scripts/securedrop-admin.py index d247bd42..9ced4552 100644 --- a/scripts/securedrop-admin.py +++ b/scripts/securedrop-admin.py @@ -97,13 +97,10 @@ def perform_uninstall(): ) print("Reverting dom0 configuration") subprocess.check_call( - ["sudo", "rm", "/srv/salt/sd/sd-journalist.sec"] - ) - subprocess.check_call( - ["sudo", "rm", "/srv/salt/sd/config.json"] + ["sudo", "qubesctl", "state.sls", "sd-clean-all"] ) subprocess.check_call( - ["sudo", "qubesctl", "state.sls", "sd-clean-all"] + [os.path.join(SCRIPTS_PATH, "scripts/clean-salt")] ) print("Uninstalling Template") subprocess.check_call(