Repository: freedomofpress/securedrop

Issue:
Allow optional local management of SD servers without tor
# Feature request

## Description

Currently a production securedrop install configures the servers so that they are only manageable remotely over tor. There are particularly use-cases where this could be overkill -- such as when an admin only needs to "remotely" manage the servers on the local network.

We should allow the setting of a boolean flag (disabled by default to leave the behavior as-is) that will disable ssh over tor and restrict ssh by a local admin workstation IP address (we'll throw warnings if users try to set non-RFC1918 addresses).

I realize that there is value to ssh over tor -- but I also see the perspective of: 
> If i don't need to remotely manage SD outside of the local building why am I opening up a remote network service for anyone on tor to access it? Yes I realize it's an authenticated service but its still a possible attack vector that isn't present if I just restrict SSH to the local network

## User Stories

As an admin, I'd like to manage my securedrop servers from my local network and do not require access remotely.



Discussion:
I would also add that backup and restore over tor can be BRUTAL if the site is large (has over 1GB+) in the `store` folder. It also seems inefficient to send data out through an external network just to get data between two systems in very close physical proximity. Being able to toggle straight SSH here quickly via the admin workstation would be super helpful (or if they just had traffic restricted to local network ssh the entire time).

I don't know enough about a storage case where someone would have a folder that large and you could easily argue that in that scenario its imperative that they clean up old documents. In spite of that, I still think this is another valid use-case for having this feature available easily for admins.

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
@@ -338,6 +338,10 @@ def __init__(self, args):
              u'SASL password for sending OSSEC alerts',
              SiteConfig.ValidateOSSECPassword(),
              None],
+            ['enable_ssh_over_tor', True, bool,
+             u'Enable SSH over Tor',
+             SiteConfig.ValidateYesNo(),
+             lambda x: x.lower() == 'yes'],
             ['securedrop_supported_locales', [], types.ListType,
              u'Space separated list of additional locales to support '
              '(' + translations + ')',


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
@@ -580,14 +580,7 @@ def verify_desc_consistency(self, site_config, desc):
         with pytest.raises(ValidationError):
             site_config.user_prompt_config_one(desc, '')
 
-    verify_prompt_ssh_users = verify_desc_consistency
-    verify_prompt_app_ip = verify_desc_consistency
-    verify_prompt_monitor_ip = verify_desc_consistency
-    verify_prompt_app_hostname = verify_desc_consistency
-    verify_prompt_monitor_hostname = verify_desc_consistency
-    verify_prompt_dns_server = verify_desc_consistency
-
-    def verify_prompt_securedrop_app_https_on_source_interface(
+    def verify_prompt_boolean(
             self, site_config, desc):
         self.verify_desc_consistency(site_config, desc)
         (var, default, etype, prompt, validator, transform) = desc
@@ -596,6 +589,17 @@ def verify_prompt_securedrop_app_https_on_source_interface(
         assert site_config.user_prompt_config_one(desc, 'YES') is True
         assert site_config.user_prompt_config_one(desc, 'NO') is False
 
+    verify_prompt_ssh_users = verify_desc_consistency
+    verify_prompt_app_ip = verify_desc_consistency
+    verify_prompt_monitor_ip = verify_desc_consistency
+    verify_prompt_app_hostname = verify_desc_consistency
+    verify_prompt_monitor_hostname = verify_desc_consistency
+    verify_prompt_dns_server = verify_desc_consistency
+
+    verify_prompt_securedrop_app_https_on_source_interface = \
+        verify_prompt_boolean
+    verify_prompt_enable_ssh_over_tor = verify_prompt_boolean
+
     verify_prompt_securedrop_app_gpg_public_key = verify_desc_consistency
 
     def verify_prompt_not_empty(self, site_config, desc):
diff --git a/docs/test_the_installation.rst b/docs/test_the_installation.rst
--- a/docs/test_the_installation.rst
+++ b/docs/test_the_installation.rst
@@ -7,6 +7,9 @@ Test connectivity
 SSH to both servers over Tor
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+Assuming you haven't disabled ssh over tor, SSH access will be
+restricted to the tor network.
+
 On the *Admin Workstation*, you should be able to SSH to the *Application Server* and the *Monitor Server*. ::
 
    ssh app
diff --git a/testinfra/common/test_tor_config.py b/testinfra/app/test_tor_config.py
similarity index 100%
rename from testinfra/common/test_tor_config.py
rename to testinfra/app/test_tor_config.py
diff --git a/testinfra/common/test_tor_hidden_services.py b/testinfra/app/test_tor_hidden_services.py
similarity index 100%
rename from testinfra/common/test_tor_hidden_services.py
rename to testinfra/app/test_tor_hidden_services.py