diff --git a/admin/securedrop_admin/__init__.py b/admin/securedrop_admin/__init__.py index 96e07db423..be35b16a66 100755 --- a/admin/securedrop_admin/__init__.py +++ b/admin/securedrop_admin/__init__.py @@ -375,11 +375,21 @@ def __init__(self, args: argparse.Namespace) -> None: None, lambda config: True, ), + ( + "securedrop_app_pow_on_source_interface", + True, + bool, + "Enable Tor's proof-of-work defense against denial-of-service attacks for the " + "Source Interface?", + SiteConfig.ValidateYesNo(), + lambda x: x.lower() == "yes", + lambda config: True, + ), ( "securedrop_app_https_on_source_interface", False, bool, - "Whether HTTPS should be enabled on " + "Source Interface (requires EV cert)", + "Enable HTTPS for the Source Interface (requires EV certificate)?", SiteConfig.ValidateYesNo(), lambda x: x.lower() == "yes", lambda config: True, diff --git a/admin/tests/test_integration.py b/admin/tests/test_integration.py index 8df0322e58..618b547659 100644 --- a/admin/tests/test_integration.py +++ b/admin/tests/test_integration.py @@ -41,6 +41,7 @@ securedrop_app_https_certificate_chain_src: '' securedrop_app_https_certificate_key_src: '' securedrop_app_https_on_source_interface: false +securedrop_app_pow_on_source_interface: true securedrop_supported_locales: - de_DE - es_ES @@ -73,6 +74,7 @@ securedrop_app_https_certificate_chain_src: '' securedrop_app_https_certificate_key_src: '' securedrop_app_https_on_source_interface: false +securedrop_app_pow_on_source_interface: true securedrop_supported_locales: - de_DE - es_ES @@ -81,7 +83,7 @@ ssh_users: sd """ -HTTPS_OUTPUT = """app_hostname: app +HTTPS_OUTPUT_NO_POW = """app_hostname: app app_ip: 10.20.2.2 daily_reboot_time: 5 dns_server: @@ -105,6 +107,7 @@ securedrop_app_https_certificate_chain_src: ca.crt securedrop_app_https_certificate_key_src: key.asc securedrop_app_https_on_source_interface: true +securedrop_app_pow_on_source_interface: false securedrop_supported_locales: - de_DE - es_ES @@ -188,9 +191,16 @@ def verify_app_gpg_key_prompt(child): ) +def verify_tor_pow_prompt(child): + # We don't need child.expect()'s regex matching, but the prompt is too long + # to match on the whole thing. + child.expect_exact("Enable Tor's proof-of-work defense", timeout=2) + + def verify_https_prompt(child): - child.expect( - rb"Whether HTTPS should be enabled on Source Interface \(requires EV cert\)\:", timeout=2 + # We don't need child.expect()'s regex matching. + child.expect_exact( + "Enable HTTPS for the Source Interface (requires EV certificate)?:", timeout=2 ) @@ -312,6 +322,9 @@ def test_sdconfig_on_first_run(): child.sendline("") verify_app_gpg_key_prompt(child) child.sendline("\b" * 14 + "sd_admin_test.pub") + verify_tor_pow_prompt(child) + # Default answer is yes + child.sendline("") verify_https_prompt(child) # Default answer is no child.sendline("") @@ -375,7 +388,11 @@ def test_sdconfig_enable_journalist_alerts(): child.sendline("") verify_app_gpg_key_prompt(child) child.sendline("\b" * 14 + "sd_admin_test.pub") + verify_tor_pow_prompt(child) + # Default answer is yes + child.sendline("") verify_https_prompt(child) + child.sendline("") # Default answer is no child.sendline("") verify_app_gpg_fingerprint_prompt(child) @@ -422,7 +439,7 @@ def test_sdconfig_enable_journalist_alerts(): verify_install_has_valid_config() -def test_sdconfig_enable_https_on_source_interface(): +def test_sdconfig_enable_https_disable_pow_on_source_interface(): cmd = os.path.join(os.path.dirname(CURRENT_DIR), "securedrop_admin/__init__.py") child = pexpect.spawn(f"python {cmd} --force --root {SD_DIR} sdconfig") verify_username_prompt(child) @@ -441,6 +458,10 @@ def test_sdconfig_enable_https_on_source_interface(): child.sendline("") verify_app_gpg_key_prompt(child) child.sendline("\b" * 14 + "sd_admin_test.pub") + verify_tor_pow_prompt(child) + # Default answer is yes + # We will press backspace thrice and type no + child.sendline("\b\b\bno") verify_https_prompt(child) # Default answer is no # We will press backspace twice and type yes @@ -490,7 +511,7 @@ def test_sdconfig_enable_https_on_source_interface(): os.path.join(SD_DIR, "install_files/ansible-base/group_vars/all/site-specific") ) as fobj: data = fobj.read() - assert data == HTTPS_OUTPUT + assert data == HTTPS_OUTPUT_NO_POW verify_install_has_valid_config() diff --git a/admin/tests/test_securedrop-admin.py b/admin/tests/test_securedrop-admin.py index 2689832c7f..5b55807c40 100644 --- a/admin/tests/test_securedrop-admin.py +++ b/admin/tests/test_securedrop-admin.py @@ -916,6 +916,7 @@ def auto_prompt(prompt, default, **kwargs): verify_prompt_monitor_hostname = verify_desc_consistency verify_prompt_dns_server = verify_desc_consistency + verify_prompt_securedrop_app_pow_on_source_interface = verify_prompt_boolean verify_prompt_securedrop_app_https_on_source_interface = verify_prompt_boolean verify_prompt_enable_ssh_over_tor = verify_prompt_boolean diff --git a/install_files/ansible-base/roles/tor-hidden-services/templates/torrc b/install_files/ansible-base/roles/tor-hidden-services/templates/torrc index cf382df041..dfc89e33f6 100644 --- a/install_files/ansible-base/roles/tor-hidden-services/templates/torrc +++ b/install_files/ansible-base/roles/tor-hidden-services/templates/torrc @@ -6,6 +6,10 @@ RunAsDaemon 1 HiddenServiceDir /var/lib/tor/services/sourcev3 HiddenServicePort 80 127.0.0.1:80 +{% if securedrop_app_pow_on_source_interface|default(False) %} +HiddenServicePoWDefensesEnabled 1 +{% endif %} + {% if securedrop_app_https_on_source_interface|default(False) %} HiddenServicePort 443 127.0.0.1:443 {% endif %}