Repository: freedomofpress/securedrop

Issue:
securedrop-admin update sporadically fails on gpg recv-key
# Bug

## Description

This an [old issue](https://github.com/freedomofpress/securedrop/issues/1797) that has reared its head again in the latest release inside our automated scripts.

## Steps to Reproduce

* Log into Tails
* Hop into the securedrop directory under persistence and checkout a random branch
* Run `./securedrop-admin update`

## Expected Behavior

The branch (at the time of this writing) should be changed to the tag for `0.6` release.

## Actual Behavior

Depending on when you run this, it might bomb out during the `gpg recv-key` portion. Might get an error that looks like this:
![image](https://user-images.githubusercontent.com/1727935/38442332-840da5f0-39d7-11e8-9229-768d8f032a14.png)

## Comments

Lets implement the manual documented fixes that we recommended in #1842 in an automated fashion within the `securedrop-admin` script. Check out this [comment](https://github.com/freedomofpress/securedrop/pull/1842#issuecomment-309579980) specifically.



Discussion:
Thanks for filing this @msheiny - since this produces a frustrating experience for end users, we should definitely fix in the next release
Yup, this is what I am getting while testing #3246 

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
@@ -574,6 +574,19 @@ def check_for_updates(args):
     return False, latest_tag
 
 
+def get_release_key_from_keyserver(args, keyserver=None, timeout=45):
+    gpg_recv = ['timeout', str(timeout), 'gpg', '--recv-key']
+    release_key = ['22245C81E3BAEB4138B36061310F561200F4AD77']
+
+    # We construct the gpg --recv-key command based on optional keyserver arg.
+    if keyserver:
+        get_key_cmd = gpg_recv + ['--keyserver', keyserver] + release_key
+    else:
+        get_key_cmd = gpg_recv + release_key
+
+    subprocess.check_call(get_key_cmd, cwd=args.root)
+
+
 def update(args):
     """Verify, and apply latest SecureDrop workstation update"""
     sdlog.info("Applying SecureDrop updates...")
@@ -588,9 +601,15 @@ def update(args):
     subprocess.check_call(git_checkout_cmd, cwd=args.root)
 
     sdlog.info("Verifying signature on latest update...")
-    get_release_key = ['gpg', '--recv-key',
-                       '22245C81E3BAEB4138B36061310F561200F4AD77']
-    subprocess.check_call(get_release_key, cwd=args.root)
+
+    try:
+        # First try to get the release key using Tails default keyserver
+        get_release_key_from_keyserver(args)
+    except subprocess.CalledProcessError:
+        # Now try to get the key from a secondary keyserver.
+        secondary_keyserver = 'hkps://hkps.pool.sks-keyservers.net'
+        get_release_key_from_keyserver(args,
+                                       keyserver=secondary_keyserver)
 
     git_verify_tag_cmd = ['git', 'tag', '-v', latest_tag]
     sig_result = subprocess.check_output(git_verify_tag_cmd,


Test patch:
diff --git a/admin/tests/test_securedrop-admin.py b/admin/tests/test_securedrop-admin.py
--- a/admin/tests/test_securedrop-admin.py
+++ b/admin/tests/test_securedrop-admin.py
@@ -106,6 +106,50 @@ def test_update_exits_if_not_needed(self, tmpdir, caplog):
             assert "Applying SecureDrop updates..." in caplog.text
             assert "Updated to SecureDrop" not in caplog.text
 
+    def test_update_gpg_recv_primary_key_failure(self, tmpdir, caplog):
+        """We should try a secondary keyserver if for some reason the primary
+        keyserver is not available."""
+
+        git_repo_path = str(tmpdir)
+        args = argparse.Namespace(root=git_repo_path)
+
+        git_output = 'Good signature from "SecureDrop Release Signing Key"'
+
+        patchers = [
+            mock.patch('securedrop_admin.check_for_updates',
+                       return_value=(True, "0.6.1")),
+            mock.patch('subprocess.check_call'),
+            mock.patch('subprocess.check_output',
+                       return_value=git_output),
+            mock.patch('securedrop_admin.get_release_key_from_keyserver',
+                       side_effect=[
+                           subprocess.CalledProcessError(1, 'cmd', 'BANG'),
+                           None])
+            ]
+
+        for patcher in patchers:
+            patcher.start()
+
+        try:
+            securedrop_admin.update(args)
+            assert "Applying SecureDrop updates..." in caplog.text
+            assert "Signature verification successful." in caplog.text
+            assert "Updated to SecureDrop" in caplog.text
+        finally:
+            for patcher in patchers:
+                patcher.stop()
+
+    def test_get_release_key_from_valid_keyserver(self, tmpdir, caplog):
+        git_repo_path = str(tmpdir)
+        args = argparse.Namespace(root=git_repo_path)
+        with mock.patch('subprocess.check_call'):
+            # Check that no exception is raised when the process is fast
+            securedrop_admin.get_release_key_from_keyserver(args)
+
+            # Check that use of the keyword arg also raises no exception
+            securedrop_admin.get_release_key_from_keyserver(
+                args, keyserver='test.com')
+
     def test_update_signature_verifies(self, tmpdir, caplog):
         git_repo_path = str(tmpdir)
         args = argparse.Namespace(root=git_repo_path)