Repository: freedomofpress/securedrop

Issue:
Add "« Back to submission page" link to NoScript docs
The `why-journalist-key` page in the Source Interface has a helpful "« Back to submission page" link at the bottom, the `disable-noscript-xss` page does not. We should have the link on both pages (or make other consistent UX changes to both pages), especially given that otherwise there's only a prominent "LOG OUT" link, and users may be disinclined to click the "Back" button in connection with forms.

# User Story

As a source, I want to know how I get back to the submission screen after learning about fun technical workarounds, so that I can successfully continue my submission.
SecureDrop GUI updater fails due to keyserver timeout
## Description

The SecureDrop GUI updater is failing with the "Update failed. Please contact your SecureDrop administrator" message. Checking the detailed log, the failure is due to the call to retrieve the release signing key from the keyserver at hkps://hkps.pool.sks-keyservers.net timing out.

## Steps to Reproduce

On a Tails 3.12 Admin or Journalist stick:
 - set the code version to an earlier tag than the current release:
```
cd ~/Persistent/securedrop
git checkout 0.11.0
```
- Restart the Tor network connection (eg. by disconnecting and reconnecting the network connection)
- Wait for the GUI Updater to appear and click *Update Now*

## Expected Behavior

Update completes without error, user prompted for local admin password, tailsconfig phase completes without error.

## Actual Behavior

Update fails as described above

Please provide screenshots where appropriate.

## Comments

The `./securedrop-admin update` command works fine. Maybe pull the key in the same way in the GUI updater?



Discussion:
Rats, good catch!
For 0.12.0, we will need to
- verify whether the graphical updater works _at all_ (i.e. intermittently or after repeated retries). If the graphical updater doesn't work at all but the CLI updater does, then that's unexpected and requires further investigation.
- at minimum, document the behavior in the release notes. 

Adding rotation of keyservers or other mitigation is a stretch goal for the release.
Too late to make changes like this for 0.12.0, moving into 0.12.1 milestone

Resolving patch:
diff --git a/admin/securedrop_admin/__init__.py b/admin/securedrop_admin/__init__.py
--- a/admin/securedrop_admin/__init__.py
+++ b/admin/securedrop_admin/__init__.py
@@ -642,7 +642,8 @@ def check_for_updates(args):
 
 
 def get_release_key_from_keyserver(args, keyserver=None, timeout=45):
-    gpg_recv = ['timeout', str(timeout), 'gpg', '--recv-key']
+    gpg_recv = ['timeout', str(timeout), 'gpg', '--batch', '--no-tty',
+                '--recv-key']
     release_key = [RELEASE_KEY]
 
     # We construct the gpg --recv-key command based on optional keyserver arg.
diff --git a/docs/conf.py b/docs/conf.py
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -57,9 +57,9 @@
 # built documents.
 #
 # The short X.Y version.
-version = '0.12.0'
+version = '0.12.1~rc1'
 # The full version, including alpha/beta/rc tags.
-release = '0.12.0'
+release = '0.12.1~rc1'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
diff --git a/securedrop/version.py b/securedrop/version.py
--- a/securedrop/version.py
+++ b/securedrop/version.py
@@ -1 +1 @@
-__version__ = '0.12.0'
+__version__ = '0.12.1~rc1'


Test patch:
diff --git a/molecule/ansible-config/tests/test_max_fail_percentage.py b/molecule/ansible-config/tests/test_play_configuration.py
similarity index 87%
rename from molecule/ansible-config/tests/test_max_fail_percentage.py
rename to molecule/ansible-config/tests/test_play_configuration.py
--- a/molecule/ansible-config/tests/test_max_fail_percentage.py
+++ b/molecule/ansible-config/tests/test_play_configuration.py
@@ -78,3 +78,16 @@ def test_any_errors_fatal(host, playbook):
             assert 'any_errors_fatal' in play
             # Ansible coerces booleans, so bare assert is sufficient
             assert play['any_errors_fatal']
+
+
+@pytest.mark.parametrize('playbook', find_ansible_playbooks())
+def test_locale(host, playbook):
+    """
+    The securedrop-prod and securedrop-staging playbooks should
+    control the locale in the host environment by setting LC_ALL=C.
+    """
+    with io.open(os.path.join(ANSIBLE_BASE, playbook), 'r') as f:
+        playbook_yaml = yaml.safe_load(f)
+        for play in playbook_yaml:
+            assert 'environment' in play
+            assert play['environment']['LC_ALL'] == 'C'
diff --git a/molecule/builder-trusty/tests/test_securedrop_deb_package.py b/molecule/builder-trusty/tests/test_securedrop_deb_package.py
--- a/molecule/builder-trusty/tests/test_securedrop_deb_package.py
+++ b/molecule/builder-trusty/tests/test_securedrop_deb_package.py
@@ -241,7 +241,7 @@ def test_deb_package_contains_expected_conffiles(host, deb):
     deb_package = host.file(deb.format(
         securedrop_test_vars.securedrop_version))
 
-    # Only relevant for the securedrop-app-code package:
+    # For the securedrop-app-code package:
     if "securedrop-app-code" in deb_package.path:
         tmpdir = tempfile.mkdtemp()
         # The `--raw-extract` flag includes `DEBIAN/` dir with control files
@@ -255,6 +255,12 @@ def test_deb_package_contains_expected_conffiles(host, deb):
         conffiles = f.content_string.rstrip()
         assert conffiles == "/var/www/securedrop/static/i/logo.png"
 
+    # For the securedrop-config package, we want to ensure there are no
+    # conffiles so securedrop_additions.sh is squashed every time
+    if "securedrop-config" in deb_package.path:
+        c = host.run("dpkg-deb -I {}".format(deb))
+        assert "conffiles" not in c.stdout
+
 
 @pytest.mark.parametrize("deb", deb_packages)
 def test_deb_package_contains_css(host, deb):
@@ -449,3 +455,21 @@ def test_ossec_binaries_are_present_server(host, deb):
         c = host.run("dpkg-deb --contents {}".format(deb_package.path))
         for wanted_file in wanted_files:
             assert wanted_file in c.stdout
+
+
+@pytest.mark.parametrize("deb", deb_packages)
+def test_config_package_contains_expected_files(host, deb):
+    """
+    Inspect the package contents to ensure all config files are included in
+    the package.
+    """
+    deb_package = host.file(deb.format(
+        securedrop_test_vars.securedrop_version))
+    if "securedrop-config" in deb_package.path:
+        wanted_files = [
+            "/etc/cron-apt/action.d/9-remove",
+            "/etc/profile.d/securedrop_additions.sh",
+        ]
+        c = host.run("dpkg-deb --contents {}".format(deb_package.path))
+        for wanted_file in wanted_files:
+            assert wanted_file in c.stdout
diff --git a/molecule/builder-trusty/tests/vars.yml b/molecule/builder-trusty/tests/vars.yml
--- a/molecule/builder-trusty/tests/vars.yml
+++ b/molecule/builder-trusty/tests/vars.yml
@@ -1,8 +1,8 @@
 ---
-securedrop_version: "0.12.0"
+securedrop_version: "0.12.1~rc1"
 ossec_version: "3.0.0"
 keyring_version: "0.1.2"
-config_version: "0.1.2"
+config_version: "0.1.3"
 grsec_version: "4.4.167"
 
 # These values will be interpolated with values populated above
diff --git a/molecule/testinfra/staging/common/test_system_hardening.py b/molecule/testinfra/staging/common/test_system_hardening.py
--- a/molecule/testinfra/staging/common/test_system_hardening.py
+++ b/molecule/testinfra/staging/common/test_system_hardening.py
@@ -1,6 +1,8 @@
 import pytest
 import re
 
+testinfra_hosts = ["app", "app-staging", "mon", "mon-staging"]
+
 
 @pytest.mark.parametrize('sysctl_opt', [
   ('net.ipv4.conf.all.accept_redirects', 0),
@@ -133,3 +135,13 @@ def test_no_ecrypt_messages_in_logs(host, logfile):
         # string to make it into syslog as a side-effect of the testinfra
         # invocation, causing subsequent test runs to report failure.
         assert error_message not in f.content_string
+
+
+@pytest.mark.parametrize('package', [
+    'libiw30',
+    'wpasupplicant',
+    'wireless-tools',
+])
+def test_unused_packages_are_removed(host, package):
+    """ Check if unused package is present """
+    assert host.package(package).is_installed is False
diff --git a/molecule/testinfra/staging/common/test_user_config.py b/molecule/testinfra/staging/common/test_user_config.py
--- a/molecule/testinfra/staging/common/test_user_config.py
+++ b/molecule/testinfra/staging/common/test_user_config.py
@@ -1,4 +1,5 @@
 import re
+import textwrap
 
 
 def test_sudoers_config(host):
@@ -38,16 +39,33 @@ def test_sudoers_tmux_env(host):
     the corresponding settings there.
     """
 
-    f = host.file('/etc/profile.d/securedrop_additions.sh')
-    non_interactive_str = re.escape('[[ $- != *i* ]] && return')
-    tmux_check = re.escape('test -z "$TMUX" && (tmux attach ||'
-                           ' tmux new-session)')
+    host_file = host.file('/etc/profile.d/securedrop_additions.sh')
+    expected_content = textwrap.dedent(
+        """\
+        [[ $- != *i* ]] && return
 
-    assert f.contains("^{}$".format(non_interactive_str))
-    assert f.contains("^if which tmux >\/dev\/null 2>&1; then$")
+        which tmux >/dev/null 2>&1 || return
 
-    assert 'test -z "$TMUX" && (tmux attach || tmux new-session)' in f.content
-    assert f.contains(tmux_check)
+        tmux_attach_via_proc() {
+            # If the tmux package is upgraded during the lifetime of a
+            # session, attaching with the new binary can fail due to different
+            # protocol versions. This function attaches using the reference to
+            # the old executable found in the /proc tree of an existing
+            # session.
+            pid=$(pgrep --newest tmux)
+            if test -n "$pid"
+            then
+                /proc/$pid/exe attach
+            fi
+            return 1
+        }
+
+        if test -z "$TMUX"
+        then
+            (tmux attach || tmux_attach_via_proc || tmux new-session)
+        fi"""
+    )
+    assert host_file.content_string == expected_content
 
 
 def test_tmux_installed(host):