From add8e6e9b42ca174986e17213be4052a86afd99e Mon Sep 17 00:00:00 2001 From: Kevin O Gorman Date: Tue, 7 Jul 2020 17:27:41 -0400 Subject: [PATCH] added additional tests to prod run, added prod VM vars --- devops/scripts/run_prod_testinfra | 3 +- molecule/testinfra/app-code/test_haveged.py | 3 - .../app-code/test_securedrop_app_code.py | 5 +- .../app-code/test_securedrop_rqrequeue.py | 1 - .../app-code/test_securedrop_rqworker.py | 1 - .../test_securedrop_shredder_configuration.py | 1 - ...securedrop_source_deleter_configuration.py | 1 - .../test_apache_journalist_interface.py | 1 + molecule/testinfra/app/iptables-app-prodVM.j2 | 44 ++++ molecule/testinfra/app/test_app_network.py | 1 - molecule/testinfra/app/test_apparmor.py | 13 -- molecule/testinfra/app/test_appenv.py | 2 + molecule/testinfra/app/test_tor_config.py | 2 + .../testinfra/app/test_tor_hidden_services.py | 6 +- molecule/testinfra/common/test_cron_apt.py | 9 - .../testinfra/common/test_fpf_apt_repo.py | 3 - molecule/testinfra/common/test_grsecurity.py | 10 +- molecule/testinfra/common/test_ip6tables.py | 6 + molecule/testinfra/common/test_platform.py | 5 + .../testinfra/common/test_release_upgrades.py | 7 + .../testinfra/common/test_system_hardening.py | 8 - molecule/testinfra/common/test_tor_mirror.py | 7 +- molecule/testinfra/common/test_user_config.py | 4 - molecule/testinfra/mon/iptables-mon-prod.j2 | 17 +- molecule/testinfra/mon/iptables-mon-prodVM.j2 | 47 +++++ molecule/testinfra/mon/test_mon_network.py | 2 - molecule/testinfra/vars/prod.yml | 10 +- molecule/testinfra/vars/prodVM.yml | 199 ++++++++++++++++++ 28 files changed, 341 insertions(+), 77 deletions(-) create mode 100644 molecule/testinfra/app/iptables-app-prodVM.j2 create mode 100644 molecule/testinfra/mon/iptables-mon-prodVM.j2 create mode 100644 molecule/testinfra/vars/prodVM.yml diff --git a/devops/scripts/run_prod_testinfra b/devops/scripts/run_prod_testinfra index 49045363d9e..a8011886be6 100755 --- a/devops/scripts/run_prod_testinfra +++ b/devops/scripts/run_prod_testinfra @@ -19,10 +19,11 @@ fi cd ~/Persistent/securedrop source admin/.venv3/bin/activate +echo "Installing test dependencies, this may take a while..." torify python3 -m pip install --quiet --no-deps --require-hashes -r securedrop/requirements/python3/develop-requirements.txt cd molecule/testinfra -CI_SD_ENV=prod SECUREDROP_TESTINFRA_TARGET_HOST=prod py.test -n 2 --disable-warnings -m "run_in_prod" +CI_SD_ENV=${TEST_ENV:-prod} SECUREDROP_TESTINFRA_TARGET_HOST=${TEST_ENV:-prod} py.test -v -n 4 --disable-warnings -m "not skip_in_prod" deactivate echo "--------" diff --git a/molecule/testinfra/app-code/test_haveged.py b/molecule/testinfra/app-code/test_haveged.py index 209f0150c21..b693820528f 100644 --- a/molecule/testinfra/app-code/test_haveged.py +++ b/molecule/testinfra/app-code/test_haveged.py @@ -4,7 +4,6 @@ testinfra_hosts = [sdvars.app_hostname] -@pytest.mark.run_in_prod def test_haveged_config(host): """ Ensure haveged's low entrop watermark is sufficiently high. @@ -17,7 +16,6 @@ def test_haveged_config(host): assert f.contains('^DAEMON_ARGS="-w 2400"$') -@pytest.mark.run_in_prod def test_haveged_no_duplicate_lines(host): """ Regression test to check for duplicate entries. Earlier playbooks @@ -30,7 +28,6 @@ def test_haveged_no_duplicate_lines(host): assert c.stdout == "" -@pytest.mark.run_in_prod def test_haveged_is_running(host): """ Ensure haveged service is running, to provide additional entropy. diff --git a/molecule/testinfra/app-code/test_securedrop_app_code.py b/molecule/testinfra/app-code/test_securedrop_app_code.py index d0b0730f08e..8f33bed2869 100644 --- a/molecule/testinfra/app-code/test_securedrop_app_code.py +++ b/molecule/testinfra/app-code/test_securedrop_app_code.py @@ -5,7 +5,6 @@ testinfra_hosts = [securedrop_test_vars.app_hostname] -@pytest.mark.run_in_prod def test_apache_default_docroot_is_absent(host): """ Ensure that the default docroot for Apache, containing static HTML @@ -15,7 +14,6 @@ def test_apache_default_docroot_is_absent(host): assert not host.file('/var/www/html').exists -@pytest.mark.run_in_prod @pytest.mark.parametrize('package', [ 'apache2', 'apparmor-utils', @@ -40,6 +38,7 @@ def test_securedrop_application_apt_dependencies(host, package): assert host.package(package).is_installed +@pytest.mark.skip_in_prod def test_securedrop_application_test_locale(host): """ Ensure both SecureDrop DEFAULT_LOCALE and SUPPORTED_LOCALES are present. @@ -54,6 +53,7 @@ def test_securedrop_application_test_locale(host): assert "\nSUPPORTED_LOCALES = ['el', 'ar', 'en_US']\n" in securedrop_config.content_string +@pytest.mark.skip_in_prod def test_securedrop_application_test_journalist_key(host): """ Ensure the SecureDrop Application GPG public key file is present. @@ -85,7 +85,6 @@ def test_securedrop_application_test_journalist_key(host): "^JOURNALIST_KEY = '65A1B5FF195B56353CC63DFFCC40EF1228271441'$") -@pytest.mark.run_in_prod def test_securedrop_application_sqlite_db(host): """ Ensure sqlite database exists for application. The database file should be diff --git a/molecule/testinfra/app-code/test_securedrop_rqrequeue.py b/molecule/testinfra/app-code/test_securedrop_rqrequeue.py index 867602eaf6f..dbbb36fad56 100644 --- a/molecule/testinfra/app-code/test_securedrop_rqrequeue.py +++ b/molecule/testinfra/app-code/test_securedrop_rqrequeue.py @@ -4,7 +4,6 @@ testinfra_hosts = [sdvars.app_hostname] -@pytest.mark.run_in_prod def test_securedrop_rqrequeue_service(host): """ Verify configuration of securedrop_rqrequeue systemd service. diff --git a/molecule/testinfra/app-code/test_securedrop_rqworker.py b/molecule/testinfra/app-code/test_securedrop_rqworker.py index 3a67e3f70f1..01ff07d3980 100644 --- a/molecule/testinfra/app-code/test_securedrop_rqworker.py +++ b/molecule/testinfra/app-code/test_securedrop_rqworker.py @@ -4,7 +4,6 @@ testinfra_hosts = [sdvars.app_hostname] -@pytest.mark.run_in_prod def test_securedrop_rqworker_service(host): """ Verify configuration of securedrop_rqworker systemd service. diff --git a/molecule/testinfra/app-code/test_securedrop_shredder_configuration.py b/molecule/testinfra/app-code/test_securedrop_shredder_configuration.py index ed397ed3c5c..ed709227be3 100644 --- a/molecule/testinfra/app-code/test_securedrop_shredder_configuration.py +++ b/molecule/testinfra/app-code/test_securedrop_shredder_configuration.py @@ -4,7 +4,6 @@ testinfra_hosts = [sdvars.app_hostname] -@pytest.mark.run_in_prod def test_securedrop_shredder_service(host): """ Verify configuration of securedrop_shredder systemd service. diff --git a/molecule/testinfra/app-code/test_securedrop_source_deleter_configuration.py b/molecule/testinfra/app-code/test_securedrop_source_deleter_configuration.py index 3a4b3796dc7..82f10308922 100644 --- a/molecule/testinfra/app-code/test_securedrop_source_deleter_configuration.py +++ b/molecule/testinfra/app-code/test_securedrop_source_deleter_configuration.py @@ -4,7 +4,6 @@ testinfra_hosts = [sdvars.app_hostname] -@pytest.mark.run_in_prod def test_securedrop_source_deleter_service(host): """ Verify configuration of securedrop_source_deleter systemd service. diff --git a/molecule/testinfra/app/apache/test_apache_journalist_interface.py b/molecule/testinfra/app/apache/test_apache_journalist_interface.py index 5a83db1e7f2..f0bc536ed9f 100644 --- a/molecule/testinfra/app/apache/test_apache_journalist_interface.py +++ b/molecule/testinfra/app/apache/test_apache_journalist_interface.py @@ -28,6 +28,7 @@ 'Header set Referrer-Policy "no-referrer"', ] + # Test is not DRY; haven't figured out how to parametrize on # multiple inputs, so explicitly redeclaring test logic. @pytest.mark.parametrize("header", wanted_apache_headers) diff --git a/molecule/testinfra/app/iptables-app-prodVM.j2 b/molecule/testinfra/app/iptables-app-prodVM.j2 new file mode 100644 index 00000000000..fdb5c14e8a1 --- /dev/null +++ b/molecule/testinfra/app/iptables-app-prodVM.j2 @@ -0,0 +1,44 @@ +*filter +:INPUT DROP +:FORWARD DROP +:OUTPUT DROP +:LOGNDROP - +-A INPUT -p tcp -m state --state RELATED,ESTABLISHED -m comment --comment "Allow traffic back for tor" -j ACCEPT +-A INPUT -i lo -p tcp -m tcp --dport 80 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "Allow tor connection from local loopback to connect to source int" -j ACCEPT +-A INPUT -i lo -p tcp -m tcp --dport 8080 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "Allow tor connection from local loopback to connect to journalist int" -j ACCEPT +-A INPUT -s 127.0.0.1/32 -d 127.0.0.1/32 -i lo -p tcp -m state --state RELATED,ESTABLISHED -m comment --comment "for redis worker all application user local loopback user" -j ACCEPT +{% for address in dns_server -%} +-A INPUT -s {{ address }}/32 -p tcp -m tcp --sport 53 -m state --state RELATED,ESTABLISHED -m comment --comment "tcp/udp dns" -j ACCEPT +-A INPUT -s {{ address }}/32 -p udp -m udp --sport 53 -m state --state RELATED,ESTABLISHED -m comment --comment "tcp/udp dns" -j ACCEPT +{% endfor -%} +-A INPUT -p udp -m udp --sport 123 --dport 123 -m state --state RELATED,ESTABLISHED -m comment --comment ntp -j ACCEPT +-A INPUT -p tcp -m multiport --sports 80,8080,443 -m state --state RELATED,ESTABLISHED -m comment --comment "apt updates" -j ACCEPT +-A INPUT -s {{ mon_ip }}/32 -p udp -m udp --sport 1514 -m state --state RELATED,ESTABLISHED -m comment --comment "OSSEC server agent" -j ACCEPT +-A INPUT -s {{ mon_ip }}/32 -p tcp -m tcp --dport 22 -m comment --comment "Block explicitly SSH from the adjacent SD component" -j DROP +-A INPUT -s 10.0.1.0/24 -i eth1 -p tcp -m tcp --dport 22 -m state --state NEW -m limit --limit 3/min --limit-burst 3 -m comment --comment "Rate limit incoming ssh traffic" -j ACCEPT +-A INPUT -s 10.0.1.0/24 -i eth1 -p tcp -m tcp --dport 22 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT +-A INPUT -i lo -m comment --comment "Allow lo to lo traffic all protocols" -j ACCEPT +-A INPUT -p tcp -m state --state INVALID -m comment --comment "drop but do not log inbound invalid state packets" -j DROP +-A INPUT -m comment --comment "Drop and log all other incoming traffic" -j LOGNDROP +-A OUTPUT -p tcp -m owner --uid-owner {{ tor_user_id }} -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "Allow tor outbound" -j ACCEPT +-A OUTPUT -m owner --uid-owner {{ tor_user_id }} -m comment --comment "Drop all other traffic for tor" -j LOGNDROP +-A OUTPUT -o lo -p tcp -m tcp --sport 80 -m owner --uid-owner {{ securedrop_user_id }} -m state --state RELATED,ESTABLISHED -m comment --comment "Restrict the apache user outbound connections" -j ACCEPT +-A OUTPUT -o lo -p tcp -m tcp --sport 8080 -m owner --uid-owner {{ securedrop_user_id }} -m state --state RELATED,ESTABLISHED -m comment --comment "Restrict the apache user outbound connections" -j ACCEPT +-A OUTPUT -s 127.0.0.1/32 -d 127.0.0.1/32 -o lo -p tcp -m owner --uid-owner {{ securedrop_user_id }} -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "for redis worker all application user local loopback user" -j ACCEPT +-A OUTPUT -m owner --uid-owner {{ securedrop_user_id }} -m comment --comment "Drop all other traffic by the securedrop user" -j LOGNDROP +-A OUTPUT -m owner --gid-owner {{ ssh_group_gid }} -m comment --comment "Drop all other outbound traffic for ssh user" -j LOGNDROP +{% for address in dns_server -%} +-A OUTPUT -d {{ address }}/32 -p tcp -m tcp --dport 53 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "tcp/udp dns" -j ACCEPT +-A OUTPUT -d {{ address }}/32 -p udp -m udp --dport 53 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "tcp/udp dns" -j ACCEPT +{% endfor -%} +-A OUTPUT -p udp -m udp --sport 123 --dport 123 -m owner --uid-owner 0 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment ntp -j ACCEPT +-A OUTPUT -p tcp -m multiport --dports 80,8080,443 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "apt updates" -j ACCEPT +-A OUTPUT -d {{ mon_ip }}/32 -p udp -m udp --dport 1514 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "OSSEC server agent" -j ACCEPT +-A OUTPUT -o eth1 -p tcp -m owner --uid-owner 0 -m tcp --sport 22 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT +-A OUTPUT -o lo -m comment --comment "Allow lo to lo traffic all protocols" -j ACCEPT +-A OUTPUT -m comment --comment "Drop all other outgoing traffic" -j DROP +-A LOGNDROP -p tcp -m limit --limit 5/min -j LOG --log-tcp-options --log-ip-options --log-uid +-A LOGNDROP -p udp -m limit --limit 5/min -j LOG --log-ip-options --log-uid +-A LOGNDROP -p icmp -m limit --limit 5/min -j LOG --log-ip-options --log-uid +-A LOGNDROP -j DROP +COMMIT diff --git a/molecule/testinfra/app/test_app_network.py b/molecule/testinfra/app/test_app_network.py index 10509d0ed69..e84a7098c5f 100644 --- a/molecule/testinfra/app/test_app_network.py +++ b/molecule/testinfra/app/test_app_network.py @@ -9,7 +9,6 @@ testinfra_hosts = [securedrop_test_vars.app_hostname] -@pytest.mark.run_in_prod def test_app_iptables_rules(host): # Build a dict of variables to pass to jinja for iptables comparison diff --git a/molecule/testinfra/app/test_apparmor.py b/molecule/testinfra/app/test_apparmor.py index f931e49f535..e965722289f 100644 --- a/molecule/testinfra/app/test_apparmor.py +++ b/molecule/testinfra/app/test_apparmor.py @@ -5,14 +5,12 @@ testinfra_hosts = [sdvars.app_hostname] -@pytest.mark.run_in_prod @pytest.mark.parametrize('pkg', ['apparmor', 'apparmor-utils']) def test_apparmor_pkg(host, pkg): """ Apparmor package dependencies """ assert host.package(pkg).is_installed -@pytest.mark.run_in_prod def test_apparmor_enabled(host): """ Check that apparmor is enabled """ with host.sudo(): @@ -27,7 +25,6 @@ def test_apparmor_enabled(host): ] -@pytest.mark.run_in_prod @pytest.mark.parametrize('cap', apache2_capabilities) def test_apparmor_apache_capabilities(host, cap): """ check for exact list of expected app-armor capabilities for apache2 """ @@ -37,7 +34,6 @@ def test_apparmor_apache_capabilities(host, cap): assert cap in c.stdout -@pytest.mark.run_in_prod def test_apparmor_apache_exact_capabilities(host): """ ensure no extra capabilities are defined for apache2 """ c = host.check_output("grep -ic capability /etc/apparmor.d/usr.sbin.apache2") @@ -47,7 +43,6 @@ def test_apparmor_apache_exact_capabilities(host): tor_capabilities = ['setgid'] -@pytest.mark.run_in_prod @pytest.mark.parametrize('cap', tor_capabilities) def test_apparmor_tor_capabilities(host, cap): """ check for exact list of expected app-armor capabilities for tor """ @@ -55,7 +50,6 @@ def test_apparmor_tor_capabilities(host, cap): assert cap in c.stdout -@pytest.mark.run_in_prod def test_apparmor_tor_exact_capabilities(host): """ ensure no extra capabilities are defined for tor """ c = host.check_output("grep -ic capability " @@ -63,7 +57,6 @@ def test_apparmor_tor_exact_capabilities(host): assert str(len(tor_capabilities)) == c -@pytest.mark.run_in_prod @pytest.mark.parametrize('profile', [ 'ntpd', 'apache2', @@ -81,7 +74,6 @@ def test_apparmor_ensure_not_disabled(host, profile): assert not f.exists -@pytest.mark.run_in_prod @pytest.mark.parametrize('complain_pkg', sdvars.apparmor_complain) def test_app_apparmor_complain(host, complain_pkg): """ Ensure app-armor profiles are in complain mode for staging """ @@ -92,7 +84,6 @@ def test_app_apparmor_complain(host, complain_pkg): assert complain_pkg in c -@pytest.mark.run_in_prod def test_app_apparmor_complain_count(host): """ Ensure right number of app-armor profiles are in complain mode """ with host.sudo(): @@ -100,7 +91,6 @@ def test_app_apparmor_complain_count(host): assert c == str(len(sdvars.apparmor_complain)) -@pytest.mark.run_in_prod @pytest.mark.parametrize('aa_enforced', sdvars.apparmor_enforce) def test_apparmor_enforced(host, aa_enforced): awk = ("awk '/[0-9]+ profiles.*enforce./" @@ -110,7 +100,6 @@ def test_apparmor_enforced(host, aa_enforced): assert aa_enforced in c -@pytest.mark.run_in_prod def test_apparmor_total_profiles(host): """ Ensure number of total profiles is sum of enforced and complaining profiles """ @@ -122,7 +111,6 @@ def test_apparmor_total_profiles(host): assert host.check_output("aa-status --profiled") >= total_expected -@pytest.mark.run_in_prod def test_aastatus_unconfined(host): """ Ensure that there are no processes that are unconfined but have a profile """ @@ -137,7 +125,6 @@ def test_aastatus_unconfined(host): assert unconfined_chk in aa_status_output -@pytest.mark.run_in_prod def test_aa_no_denies_in_syslog(host): """ Ensure that there are no apparmor denials in syslog """ with host.sudo(): diff --git a/molecule/testinfra/app/test_appenv.py b/molecule/testinfra/app/test_appenv.py index 1a512f2a520..efb7bb8bb30 100644 --- a/molecule/testinfra/app/test_appenv.py +++ b/molecule/testinfra/app/test_appenv.py @@ -14,6 +14,7 @@ def test_app_pip_deps(host, exp_pip_pkg): assert pip[exp_pip_pkg['name']]['version'] == exp_pip_pkg['version'] +@pytest.mark.skip_in_prod def test_app_wsgi(host): """ ensure logging is enabled for source interface in staging """ f = host.file("/var/www/source.wsgi") @@ -53,6 +54,7 @@ def test_supervisor_not_installed(host): assert host.package("supervisor").is_installed is False +@pytest.mark.skip_in_prod def test_gpg_key_in_keyring(host): """ ensure test gpg key is present in app keyring """ with host.sudo(sdvars.securedrop_user): diff --git a/molecule/testinfra/app/test_tor_config.py b/molecule/testinfra/app/test_tor_config.py index a6b60b8865b..e0c5f25ae93 100644 --- a/molecule/testinfra/app/test_tor_config.py +++ b/molecule/testinfra/app/test_tor_config.py @@ -61,6 +61,7 @@ def test_tor_torrc_sandbox(host): assert not f.contains("^.*Sandbox.*$") +@pytest.mark.skip_in_prod def test_tor_v2_onion_url_readable_by_app(host): v2_url_filepath = "/var/lib/securedrop/source_v2_url" with host.sudo(): @@ -71,6 +72,7 @@ def test_tor_v2_onion_url_readable_by_app(host): assert re.search(r"^[a-z0-9]{16}\.onion$", f.content_string) +@pytest.mark.skip_in_prod def test_tor_v3_onion_url_readable_by_app(host): v3_url_filepath = "/var/lib/securedrop/source_v3_url" with host.sudo(): diff --git a/molecule/testinfra/app/test_tor_hidden_services.py b/molecule/testinfra/app/test_tor_hidden_services.py index ca13a5989d5..d60a634bfdc 100644 --- a/molecule/testinfra/app/test_tor_hidden_services.py +++ b/molecule/testinfra/app/test_tor_hidden_services.py @@ -5,7 +5,9 @@ sdvars = pytest.securedrop_test_vars testinfra_hosts = [sdvars.app_hostname] - +# Prod Tor services may have unexpected configs +# TODO: read from admin workstation site-specific file if available +@pytest.mark.skip_in_prod @pytest.mark.parametrize('tor_service', sdvars.tor_services) def test_tor_service_directories(host, tor_service): """ @@ -19,6 +21,7 @@ def test_tor_service_directories(host, tor_service): assert f.group == "debian-tor" +@pytest.mark.skip_in_prod @pytest.mark.parametrize('tor_service', sdvars.tor_services) def test_tor_service_hostnames(host, tor_service): """ @@ -62,6 +65,7 @@ def test_tor_service_hostnames(host, tor_service): assert re.search("^{}$".format(ths_hostname_regex_v3), f.content_string) +@pytest.mark.skip_in_prod @pytest.mark.parametrize('tor_service', sdvars.tor_services) def test_tor_services_config(host, tor_service): """ diff --git a/molecule/testinfra/common/test_cron_apt.py b/molecule/testinfra/common/test_cron_apt.py index ab32ae5bc5b..6b767edaa9c 100644 --- a/molecule/testinfra/common/test_cron_apt.py +++ b/molecule/testinfra/common/test_cron_apt.py @@ -6,7 +6,6 @@ testinfra_hosts = [test_vars.app_hostname, test_vars.monitor_hostname] -@pytest.mark.run_in_prod @pytest.mark.parametrize('dependency', [ 'cron-apt', 'ntp' @@ -25,7 +24,6 @@ def test_cron_apt_dependencies(host, dependency): assert host.package(dependency).is_installed -@pytest.mark.run_in_prod def test_cron_apt_config(host): """ Ensure custom cron-apt config file is present. @@ -38,7 +36,6 @@ def test_cron_apt_config(host): assert f.contains('^EXITON=error$') -@pytest.mark.run_in_prod @pytest.mark.parametrize('repo', [ 'deb http://security.ubuntu.com/ubuntu {securedrop_target_platform}-security main', 'deb-src http://security.ubuntu.com/ubuntu {securedrop_target_platform}-security main', @@ -63,7 +60,6 @@ def test_cron_apt_repo_list(host, repo): assert f.contains(repo_regex) -@pytest.mark.run_in_prod def test_cron_apt_repo_config_update(host): """ Ensure cron-apt updates repos from the security.list config. @@ -79,7 +75,6 @@ def test_cron_apt_repo_config_update(host): assert f.contains('^{}$'.format(repo_config)) -@pytest.mark.run_in_prod def test_cron_apt_delete_vanilla_kernels(host): """ Ensure cron-apt removes generic linux image packages when installed. @@ -95,7 +90,6 @@ def test_cron_apt_delete_vanilla_kernels(host): assert f.contains('^{}$'.format(command)) -@pytest.mark.run_in_prod def test_cron_apt_repo_config_upgrade(host): """ Ensure cron-apt upgrades packages from the security.list config. @@ -112,7 +106,6 @@ def test_cron_apt_repo_config_upgrade(host): assert f.contains(re.escape(repo_config)) -@pytest.mark.run_in_prod def test_cron_apt_config_deprecated(host): """ Ensure default cron-apt file to download all updates does not exist. @@ -121,7 +114,6 @@ def test_cron_apt_config_deprecated(host): assert not f.exists -@pytest.mark.run_in_prod @pytest.mark.parametrize('cron_job', [ {'job': '0 4 * * * root /usr/bin/test -x /usr/sbin/cron-apt && /usr/sbin/cron-apt && /sbin/reboot', # noqa 'state': 'present'}, @@ -148,7 +140,6 @@ def test_cron_apt_cron_jobs(host, cron_job): assert not f.contains(regex_job) -@pytest.mark.run_in_prod def test_cron_apt_all_packages_updated(host): """ Ensure a safe-upgrade has already been run, by checking that no diff --git a/molecule/testinfra/common/test_fpf_apt_repo.py b/molecule/testinfra/common/test_fpf_apt_repo.py index eeddce8b91d..cbf7447cea3 100644 --- a/molecule/testinfra/common/test_fpf_apt_repo.py +++ b/molecule/testinfra/common/test_fpf_apt_repo.py @@ -6,7 +6,6 @@ testinfra_hosts = [test_vars.app_hostname, test_vars.monitor_hostname] -@pytest.mark.run_in_prod def test_fpf_apt_repo_present(host): """ Ensure the FPF apt repo, apt.freedom.press, is configured. @@ -34,7 +33,6 @@ def test_fpf_apt_repo_present(host): assert f.contains(repo_regex) -@pytest.mark.run_in_prod def test_fpf_apt_repo_fingerprint(host): """ Ensure the FPF apt repo has the correct fingerprint on the associated @@ -55,7 +53,6 @@ def test_fpf_apt_repo_fingerprint(host): assert fpf_gpg_pub_key_info in c.stdout -@pytest.mark.run_in_prod @pytest.mark.parametrize('old_pubkey', [ 'pub 4096R/FC9F6818 2014-10-26 [expired: 2016-10-27]', 'pub 4096R/00F4AD77 2016-10-20 [expires: 2017-10-20]', diff --git a/molecule/testinfra/common/test_grsecurity.py b/molecule/testinfra/common/test_grsecurity.py index 394b91e4dbd..3ac7544eb42 100644 --- a/molecule/testinfra/common/test_grsecurity.py +++ b/molecule/testinfra/common/test_grsecurity.py @@ -7,7 +7,6 @@ testinfra_hosts = [sdvars.app_hostname, sdvars.monitor_hostname] -@pytest.mark.run_in_prod def test_ssh_motd_disabled(host): """ Ensure the SSH MOTD (Message of the Day) is disabled. @@ -18,7 +17,6 @@ def test_ssh_motd_disabled(host): assert not f.contains(r"pam\.motd") -@pytest.mark.run_in_prod @pytest.mark.parametrize("package", [ 'linux-image-{}-grsec-securedrop'.format(KERNEL_VERSION), 'paxctl', @@ -33,7 +31,6 @@ def test_grsecurity_apt_packages(host, package): assert host.package(package).is_installed -@pytest.mark.run_in_prod @pytest.mark.parametrize("package", [ 'linux-signed-image-generic-lts-utopic', 'linux-signed-image-generic', @@ -59,7 +56,6 @@ def test_generic_kernels_absent(host, package): assert error_text in c.stderr.strip() -@pytest.mark.run_in_prod def test_grsecurity_lock_file(host): """ Ensure system is rerunning a grsecurity kernel by testing for the @@ -71,7 +67,6 @@ def test_grsecurity_lock_file(host): assert f.size == 0 -@pytest.mark.run_in_prod def test_grsecurity_kernel_is_running(host): """ Make sure the currently running kernel is specific grsec kernel. @@ -81,7 +76,6 @@ def test_grsecurity_kernel_is_running(host): assert c.stdout.strip() == '{}-grsec-securedrop'.format(KERNEL_VERSION) -@pytest.mark.run_in_prod @pytest.mark.parametrize('sysctl_opt', [ ('kernel.grsecurity.grsec_lock', 1), ('kernel.grsecurity.rwxmap_logging', 0), @@ -96,6 +90,7 @@ def test_grsecurity_sysctl_options(host, sysctl_opt): assert host.sysctl(sysctl_opt[0]) == sysctl_opt[1] +@pytest.mark.skip_in_prod @pytest.mark.parametrize('paxtest_check', [ "Executable anonymous mapping", "Executable bss", @@ -130,6 +125,7 @@ def test_grsecurity_paxtest(host, paxtest_check): assert re.search(regex, c.stdout) +@pytest.mark.skip_in_prod def test_grub_pc_marked_manual(host): """ Ensure the `grub-pc` packaged is marked as manually installed. @@ -140,7 +136,6 @@ def test_grub_pc_marked_manual(host): assert c.stdout.strip() == "grub-pc" -@pytest.mark.run_in_prod def test_apt_autoremove(host): """ Ensure old packages have been autoremoved. @@ -220,7 +215,6 @@ def test_kernel_options_enabled_config(host, kernel_opts): assert line in kernel_config -@pytest.mark.run_in_prod def test_mds_mitigations_and_smt_disabled(host): """ Ensure that full mitigations are in place for MDS diff --git a/molecule/testinfra/common/test_ip6tables.py b/molecule/testinfra/common/test_ip6tables.py index c40c9d24672..29e1267270a 100644 --- a/molecule/testinfra/common/test_ip6tables.py +++ b/molecule/testinfra/common/test_ip6tables.py @@ -1,3 +1,9 @@ +import pytest + +test_vars = pytest.securedrop_test_vars +testinfra_hosts = [test_vars.app_hostname, test_vars.monitor_hostname] + + def test_ip6tables_drop_everything(host): """ Ensure that all IPv6 packets are dropped by default. diff --git a/molecule/testinfra/common/test_platform.py b/molecule/testinfra/common/test_platform.py index bd770e6c2e6..ec2fcaf1ac4 100644 --- a/molecule/testinfra/common/test_platform.py +++ b/molecule/testinfra/common/test_platform.py @@ -1,3 +1,8 @@ +import pytest + +test_vars = pytest.securedrop_test_vars +testinfra_hosts = [test_vars.app_hostname, test_vars.monitor_hostname] + # We expect Ubuntu Xenial SUPPORTED_CODENAMES = ('xenial') SUPPORTED_RELEASES = ('16.04') diff --git a/molecule/testinfra/common/test_release_upgrades.py b/molecule/testinfra/common/test_release_upgrades.py index 76f57577d84..d0b5e146648 100644 --- a/molecule/testinfra/common/test_release_upgrades.py +++ b/molecule/testinfra/common/test_release_upgrades.py @@ -1,3 +1,10 @@ +import pytest + + +test_vars = pytest.securedrop_test_vars +testinfra_hosts = [test_vars.app_hostname, test_vars.monitor_hostname] + + def test_release_manager_upgrade_channel(host): """ Ensures that the `do-release-upgrade` command will not diff --git a/molecule/testinfra/common/test_system_hardening.py b/molecule/testinfra/common/test_system_hardening.py index 4e7cd85cc0f..a3410399a6e 100644 --- a/molecule/testinfra/common/test_system_hardening.py +++ b/molecule/testinfra/common/test_system_hardening.py @@ -5,7 +5,6 @@ testinfra_hosts = [sdvars.app_hostname, sdvars.monitor_hostname] -@pytest.mark.run_in_prod @pytest.mark.parametrize('sysctl_opt', [ ('net.ipv4.conf.all.accept_redirects', 0), ('net.ipv4.conf.all.accept_source_route', 0), @@ -35,7 +34,6 @@ def test_sysctl_options(host, sysctl_opt): assert host.sysctl(sysctl_opt[0]) == sysctl_opt[1] -@pytest.mark.run_in_prod def test_dns_setting(host): """ Ensure DNS service is hard-coded in resolv.conf config. @@ -48,7 +46,6 @@ def test_dns_setting(host): assert f.contains(r'^nameserver 8\.8\.8\.8$') -@pytest.mark.run_in_prod @pytest.mark.parametrize('kernel_module', [ 'bluetooth', 'iwlwifi', @@ -67,7 +64,6 @@ def test_blacklisted_kernel_modules(host, kernel_module): assert f.contains("^blacklist {}$".format(kernel_module)) -@pytest.mark.run_in_prod def test_swap_disabled(host): """ Ensure swap space is disabled. Prohibit writing memory to swapfiles @@ -90,7 +86,6 @@ def test_swap_disabled(host): assert re.search(rgx, c) -@pytest.mark.run_in_prod def test_twofactor_disabled_on_tty(host): """ Having 2FA on TTY logins is cumbersome on systems without encrypted drives. @@ -103,7 +98,6 @@ def test_twofactor_disabled_on_tty(host): assert "pam_ecryptfs.so unwrap" not in pam_auth_file -@pytest.mark.run_in_prod @pytest.mark.parametrize('sshd_opts', [ ('UsePAM', 'no'), ('ChallengeResponseAuthentication', 'no'), @@ -122,7 +116,6 @@ def test_sshd_config(host, sshd_opts): assert line in sshd_config_file -@pytest.mark.run_in_prod @pytest.mark.parametrize('logfile', [ '/var/log/auth.log', '/var/log/syslog', @@ -141,7 +134,6 @@ def test_no_ecrypt_messages_in_logs(host, logfile): assert error_message not in f.content_string -@pytest.mark.run_in_prod @pytest.mark.parametrize('package', [ 'libiw30', 'wpasupplicant', diff --git a/molecule/testinfra/common/test_tor_mirror.py b/molecule/testinfra/common/test_tor_mirror.py index b2300f5ed67..ccc7340e981 100644 --- a/molecule/testinfra/common/test_tor_mirror.py +++ b/molecule/testinfra/common/test_tor_mirror.py @@ -1,7 +1,9 @@ import pytest +test_vars = pytest.securedrop_test_vars +testinfra_hosts = [test_vars.app_hostname, test_vars.monitor_hostname] + -@pytest.mark.run_in_prod @pytest.mark.parametrize('repo_file', [ "/etc/apt/sources.list.d/deb_torproject_org_torproject_org.list", ]) @@ -15,7 +17,6 @@ def test_tor_mirror_absent(host, repo_file): assert not f.exists -@pytest.mark.run_in_prod def test_tor_keyring_absent(host): """ Tor packages are installed via the FPF apt mirror, and signed with the @@ -33,7 +34,6 @@ def test_tor_keyring_absent(host): assert error_text in c.stderr.strip() -@pytest.mark.run_in_prod @pytest.mark.parametrize('tor_key_info', [ "pub 2048R/886DDD89 2009-09-04 [expires: 2020-08-29]", "Key fingerprint = A3C4 F0F9 79CA A22C DBA8 F512 EE8C BC9E 886D DD89", @@ -54,7 +54,6 @@ def test_tor_mirror_fingerprint(host, tor_key_info): assert tor_key_info not in c.stdout -@pytest.mark.run_in_prod @pytest.mark.parametrize('repo_pattern', [ 'deb.torproject.org', 'tor-apt.freedom.press', diff --git a/molecule/testinfra/common/test_user_config.py b/molecule/testinfra/common/test_user_config.py index 35f939fed72..f687ea64cec 100644 --- a/molecule/testinfra/common/test_user_config.py +++ b/molecule/testinfra/common/test_user_config.py @@ -7,7 +7,6 @@ testinfra_hosts = [sdvars.app_hostname, sdvars.monitor_hostname] -@pytest.mark.run_in_prod def test_sudoers_config(host): """ Check sudoers config for passwordless sudo via group membership, @@ -37,7 +36,6 @@ def test_sudoers_config(host): assert re.search(r'Defaults:%sudo\s+!requiretty', sudoers_config, re.M) -@pytest.mark.run_in_prod def test_sudoers_tmux_env(host): """ Ensure SecureDrop-specific bashrc additions are present. @@ -75,7 +73,6 @@ def test_sudoers_tmux_env(host): assert host_file.content_string.strip() == expected_content -@pytest.mark.run_in_prod def test_tmux_installed(host): """ Ensure the `tmux` package is present, since it's required for the user env. @@ -87,7 +84,6 @@ def test_tmux_installed(host): assert host.package("tmux").is_installed -@pytest.mark.run_in_prod def test_sudoers_tmux_env_deprecated(host): """ Previous version of the Ansible config set the tmux config diff --git a/molecule/testinfra/mon/iptables-mon-prod.j2 b/molecule/testinfra/mon/iptables-mon-prod.j2 index 8a20b277107..9a1064080e6 100644 --- a/molecule/testinfra/mon/iptables-mon-prod.j2 +++ b/molecule/testinfra/mon/iptables-mon-prod.j2 @@ -16,27 +16,28 @@ -A INPUT -s {{ address }}/32 -p udp -m udp --sport 53 -m state --state RELATED,ESTABLISHED -m comment --comment "tcp/udp dns" -j ACCEPT {% endfor -%} -A INPUT -p tcp -m tcp --sport 587 -m state --state RELATED,ESTABLISHED -m comment --comment "Allow ossec email alerts out" -j ACCEPT +-A INPUT -s {{ app_ip }}/32 -p tcp -m tcp --dport 22 -m comment --comment "Block explicitly SSH from the adjacent SD component" -j DROP +-A INPUT -i {{ default_interface }} -p tcp -m tcp --dport 22 -m state --state NEW -m limit --limit 3/min --limit-burst 3 -m comment --comment "Rate limit incoming ssh traffic" -j ACCEPT +-A INPUT -i {{ default_interface }} -p tcp -m tcp --dport 22 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT -A INPUT -i lo -m comment --comment "Allow lo to lo traffic all protocols" -j ACCEPT -A INPUT -p tcp -m state --state INVALID -m comment --comment "drop but do not log inbound invalid state packets" -j DROP -A INPUT -m comment --comment "Drop and log all other incoming traffic" -j LOGNDROP --A OUTPUT -o lo -p tcp -m tcp --dport 22 -m owner --uid-owner {{ tor_user_id }} -m state --state NEW -m limit --limit 3/min --limit-burst 3 -m comment --comment "Rate limit traffic from tor to the ssh dameon" -j ACCEPT --A OUTPUT -o lo -p tcp -m tcp --dport 22 -m owner --uid-owner {{ tor_user_id }} -m state --state NEW -m comment --comment "Drop all other new connections from tor to the ssh dameon" -j LOGNDROP --A OUTPUT -o lo -p tcp -m tcp --dport 22 -m owner --uid-owner {{ tor_user_id }} -m state --state RELATED,ESTABLISHED -m comment --comment "Allow the established traffic from tor to the ssh dameon" -j ACCEPT --A OUTPUT -p tcp -m owner --uid-owner {{ tor_user_id }} -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "tor instance that provides ssh access" -j ACCEPT --A OUTPUT -m owner --uid-owner {{ tor_user_id }} -m comment --comment "Drop all other traffic for the tor instance used for ssh" -j LOGNDROP +-A OUTPUT -p tcp -m owner --uid-owner {{ tor_user_id }} -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "Allow tor outbound" -j ACCEPT +-A OUTPUT -m owner --uid-owner {{ tor_user_id }} -m comment --comment "Drop all other traffic for tor" -j LOGNDROP -A OUTPUT -m owner --gid-owner {{ ssh_group_gid }} -m comment --comment "Drop all other outbound traffic for ssh user" -j LOGNDROP {% for address in dns_server -%} --A OUTPUT -d {{ address }}/32 -p tcp -m tcp --dport 53 -m owner --uid-owner 0 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "tcp/udp dns" -j ACCEPT --A OUTPUT -d {{ address }}/32 -p udp -m udp --dport 53 -m owner --uid-owner 0 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "tcp/udp dns" -j ACCEPT +-A OUTPUT -d {{ address }}/32 -p tcp -m tcp --dport 53 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "tcp/udp dns" -j ACCEPT +-A OUTPUT -d {{ address }}/32 -p udp -m udp --dport 53 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "tcp/udp dns" -j ACCEPT {% endfor -%} -A OUTPUT -p udp -m udp --sport 123 --dport 123 -m owner --uid-owner 0 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment ntp -j ACCEPT --A OUTPUT -p tcp -m multiport --dports 80,8080,443 -m owner --uid-owner 0 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "apt updates" -j ACCEPT +-A OUTPUT -p tcp -m multiport --dports 80,8080,443 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "apt updates" -j ACCEPT -A OUTPUT -d {{ app_ip }}/32 -p udp -m udp --sport 1514 -m state --state RELATED,ESTABLISHED -m comment --comment "Allow OSSEC agent to monitor" -j ACCEPT {% for address in dns_server -%} -A OUTPUT -d {{ address }}/32 -p tcp -m tcp --dport 53 -m owner --uid-owner {{ postfix_user_id }} -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "postfix dns rule" -j ACCEPT -A OUTPUT -d {{ address }}/32 -p udp -m udp --dport 53 -m owner --uid-owner {{ postfix_user_id }} -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "postfix dns rule" -j ACCEPT {% endfor -%} -A OUTPUT -p tcp -m tcp --dport 587 -m owner --uid-owner {{ postfix_user_id }} -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "Allow ossec email alerts out" -j ACCEPT +-A OUTPUT -o {{ default_interface }} -p tcp -m owner --uid-owner 0 -m tcp --sport 22 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT -A OUTPUT -o lo -m comment --comment "Allow lo to lo traffic all protocols" -j ACCEPT -A OUTPUT -m comment --comment "Drop all other outgoing traffic" -j DROP -A LOGNDROP -p tcp -m limit --limit 5/min -j LOG --log-tcp-options --log-ip-options --log-uid diff --git a/molecule/testinfra/mon/iptables-mon-prodVM.j2 b/molecule/testinfra/mon/iptables-mon-prodVM.j2 new file mode 100644 index 00000000000..bc9b0676a47 --- /dev/null +++ b/molecule/testinfra/mon/iptables-mon-prodVM.j2 @@ -0,0 +1,47 @@ +*filter +:INPUT DROP +:FORWARD DROP +:OUTPUT DROP +:LOGNDROP - +-A INPUT -p tcp -m state --state RELATED,ESTABLISHED -m comment --comment "Allow traffic back for tor" -j ACCEPT +{% for address in dns_server -%} +-A INPUT -s {{ address }}/32 -p tcp -m tcp --sport 53 -m state --state RELATED,ESTABLISHED -m comment --comment "tcp/udp dns" -j ACCEPT +-A INPUT -s {{ address }}/32 -p udp -m udp --sport 53 -m state --state RELATED,ESTABLISHED -m comment --comment "tcp/udp dns" -j ACCEPT +{% endfor -%} +-A INPUT -p udp -m udp --sport 123 --dport 123 -m state --state RELATED,ESTABLISHED -m comment --comment ntp -j ACCEPT +-A INPUT -p tcp -m multiport --sports 80,8080,443 -m state --state RELATED,ESTABLISHED -m comment --comment "apt updates" -j ACCEPT +-A INPUT -s {{ app_ip }}/32 -p udp -m udp --dport 1514 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "Allow OSSEC agent to monitor" -j ACCEPT +{% for address in dns_server -%} +-A INPUT -s {{ address }}/32 -p tcp -m tcp --sport 53 -m state --state RELATED,ESTABLISHED -m comment --comment "tcp/udp dns" -j ACCEPT +-A INPUT -s {{ address }}/32 -p udp -m udp --sport 53 -m state --state RELATED,ESTABLISHED -m comment --comment "tcp/udp dns" -j ACCEPT +{% endfor -%} +-A INPUT -p tcp -m tcp --sport 587 -m state --state RELATED,ESTABLISHED -m comment --comment "Allow ossec email alerts out" -j ACCEPT +-A INPUT -s {{ app_ip }}/32 -p tcp -m tcp --dport 22 -m comment --comment "Block explicitly SSH from the adjacent SD component" -j DROP +-A INPUT -s 10.0.1.0/24 -i eth1 -p tcp -m tcp --dport 22 -m state --state NEW -m limit --limit 3/min --limit-burst 3 -m comment --comment "Rate limit incoming ssh traffic" -j ACCEPT +-A INPUT -s 10.0.1.0/24 -i eth1 -p tcp -m tcp --dport 22 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT +-A INPUT -i lo -m comment --comment "Allow lo to lo traffic all protocols" -j ACCEPT +-A INPUT -p tcp -m state --state INVALID -m comment --comment "drop but do not log inbound invalid state packets" -j DROP +-A INPUT -m comment --comment "Drop and log all other incoming traffic" -j LOGNDROP +-A OUTPUT -p tcp -m owner --uid-owner {{ tor_user_id }} -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "Allow tor outbound" -j ACCEPT +-A OUTPUT -m owner --uid-owner {{ tor_user_id }} -m comment --comment "Drop all other traffic for tor" -j LOGNDROP +-A OUTPUT -m owner --gid-owner {{ ssh_group_gid }} -m comment --comment "Drop all other outbound traffic for ssh user" -j LOGNDROP +{% for address in dns_server -%} +-A OUTPUT -d {{ address }}/32 -p tcp -m tcp --dport 53 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "tcp/udp dns" -j ACCEPT +-A OUTPUT -d {{ address }}/32 -p udp -m udp --dport 53 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "tcp/udp dns" -j ACCEPT +{% endfor -%} +-A OUTPUT -p udp -m udp --sport 123 --dport 123 -m owner --uid-owner 0 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment ntp -j ACCEPT +-A OUTPUT -p tcp -m multiport --dports 80,8080,443 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "apt updates" -j ACCEPT +-A OUTPUT -d {{ app_ip }}/32 -p udp -m udp --sport 1514 -m state --state RELATED,ESTABLISHED -m comment --comment "Allow OSSEC agent to monitor" -j ACCEPT +{% for address in dns_server -%} +-A OUTPUT -d {{ address }}/32 -p tcp -m tcp --dport 53 -m owner --uid-owner {{ postfix_user_id }} -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "postfix dns rule" -j ACCEPT +-A OUTPUT -d {{ address }}/32 -p udp -m udp --dport 53 -m owner --uid-owner {{ postfix_user_id }} -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "postfix dns rule" -j ACCEPT +{% endfor -%} +-A OUTPUT -p tcp -m tcp --dport 587 -m owner --uid-owner {{ postfix_user_id }} -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "Allow ossec email alerts out" -j ACCEPT +-A OUTPUT -o eth1 -p tcp -m owner --uid-owner 0 -m tcp --sport 22 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT +-A OUTPUT -o lo -m comment --comment "Allow lo to lo traffic all protocols" -j ACCEPT +-A OUTPUT -m comment --comment "Drop all other outgoing traffic" -j DROP +-A LOGNDROP -p tcp -m limit --limit 5/min -j LOG --log-tcp-options --log-ip-options --log-uid +-A LOGNDROP -p udp -m limit --limit 5/min -j LOG --log-ip-options --log-uid +-A LOGNDROP -p icmp -m limit --limit 5/min -j LOG --log-ip-options --log-uid +-A LOGNDROP -j DROP +COMMIT diff --git a/molecule/testinfra/mon/test_mon_network.py b/molecule/testinfra/mon/test_mon_network.py index 71116ef1b42..da3714f8307 100644 --- a/molecule/testinfra/mon/test_mon_network.py +++ b/molecule/testinfra/mon/test_mon_network.py @@ -9,7 +9,6 @@ testinfra_hosts = [securedrop_test_vars.monitor_hostname] -@pytest.mark.run_in_prod def test_mon_iptables_rules(host): # Build a dict of variables to pass to jinja for iptables comparison @@ -46,7 +45,6 @@ def test_mon_iptables_rules(host): assert iptables_expected == iptables -@pytest.mark.run_in_prod @pytest.mark.parametrize('ossec_service', [ dict(host="0.0.0.0", proto="tcp", port=22, listening=True), dict(host="0.0.0.0", proto="udp", port=1514, listening=True), diff --git a/molecule/testinfra/vars/prod.yml b/molecule/testinfra/vars/prod.yml index 01c1bd93e2d..c28774f7acd 100644 --- a/molecule/testinfra/vars/prod.yml +++ b/molecule/testinfra/vars/prod.yml @@ -22,14 +22,14 @@ app_hostname: app monitor_hostname: mon apache_listening_address: 127.0.0.1 -apache_source_log: /var/log/apache2/source-error.log +apache_source_log: /dev/null apache_allow_from: 127.0.0.1 dns_server: - 8.8.8.8 - 8.8.4.4 -mon_ip: 10.0.1.5 -app_ip: 10.0.1.4 +mon_ip: 10.20.3.2 +app_ip: 10.20.2.2 pip_deps: - name: 'Flask' @@ -93,7 +93,7 @@ allowed_apache_logfiles: # - /var/log/apache2/source-error.log # Disable Postfix in staging, since we don't have valid credentials. -postfix_enabled: False +postfix_enabled: True # Log events for OSSEC alerts we suppress log_events_without_ossec_alerts: @@ -196,5 +196,5 @@ log_events_with_ossec_alerts: level: "7" rule_id: "400700" -fpf_apt_repo_url: "https://apt-test.freedom.press" +fpf_apt_repo_url: "https://apt.freedom.press" grsec_version: "4.14.175" diff --git a/molecule/testinfra/vars/prodVM.yml b/molecule/testinfra/vars/prodVM.yml new file mode 100644 index 00000000000..9b716c7431f --- /dev/null +++ b/molecule/testinfra/vars/prodVM.yml @@ -0,0 +1,199 @@ +--- +# Testinfra vars file for app-staigng. +wanted_apache_headers: + - 'Header edit Set-Cookie ^(.*)$ $1;HttpOnly' + - 'Header always append X-Frame-Options: DENY' + - 'Header set Referrer-Policy "same-origin"' + - 'Header set X-XSS-Protection: "1; mode=block"' + - 'Header set X-Content-Type-Options: nosniff' + - 'Header set X-Download-Options: noopen' + - "Header set X-Content-Security-Policy: \"default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self';\"" + - "Header set Content-Security-Policy: \"default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self';\"" + - 'Header unset Etag' + +securedrop_venv: /opt/venvs/securedrop-app-code +securedrop_venv_bin: "/opt/venvs/securedrop-app-code/bin" +securedrop_venv_site_packages: "/opt/venvs/securedrop-app-code/lib/python3.5/site-packages" +securedrop_code: /var/www/securedrop +securedrop_data: /var/lib/securedrop +securedrop_user: www-data + +app_hostname: app +monitor_hostname: mon + +apache_listening_address: 127.0.0.1 +apache_source_log: /dev/null +apache_allow_from: 127.0.0.1 + +dns_server: + - 8.8.8.8 +mon_ip: 10.0.1.5 +app_ip: 10.0.1.4 + +pip_deps: + - name: 'Flask' + version: '1.0.2' + +apparmor_complain: [] + +apparmor_enforce: + - "/sbin/dhclient" + - "/usr/lib/NetworkManager/nm-dhcp-client.action" + - "/usr/lib/connman/scripts/dhclient-script" + - "/usr/sbin/ntpd" + - "/usr/sbin/tcpdump" + - "system_tor" + - "/usr/sbin/apache2" + - "/usr/sbin/apache2//DEFAULT_URI" + - "/usr/sbin/apache2//HANDLING_UNTRUSTED_INPUT" + - "/usr/sbin/tor" + +app_directories: + - /var/www/securedrop + - /var/lib/securedrop + - /var/lib/securedrop/store + - /var/lib/securedrop/keys + - /var/lib/securedrop/tmp + +tor_services: + - name: source + ports: + - "80" + authenticated: no + version: 2 + + - name: journalist + ports: + - "80" + - "8080" + authenticated: yes + client: journalist + version: 2 + + - name: journalistv3 + ports: + - "80" + authenticated: yes + version: 3 + + - name: sourcev3 + ports: + - "80" + authenticated: no + version: 3 + +# source-error.log not enabled by default in prod +allowed_apache_logfiles: + - /var/log/apache2/access.log + - /var/log/apache2/error.log + - /var/log/apache2/journalist-access.log + - /var/log/apache2/journalist-error.log + - /var/log/apache2/other_vhosts_access.log +# - /var/log/apache2/source-error.log + +# Disable Postfix in staging, since we don't have valid credentials. +postfix_enabled: True + +# Log events for OSSEC alerts we suppress +log_events_without_ossec_alerts: + # Check that using an overloaded guard does not produce an OSSEC alert + - name: test_overloaded_tor_guard_does_not_produce_alert + alert: > + Aug 16 21:54:44 app-staging Tor[26695]: [warn] Your Guard + () is failing a very large amount of + circuits. Most likely this means the Tor network is + overloaded, but it could also mean an attack against you + or potentially the guard itself. + + # Check that OSSEC keep alive messages sent to the OSSEC manager + # do not produce OSSEC alerts. + # + # For more information see: + # https://github.com/ossec/ossec-hids/issues/466 + # http://ossec-docs.readthedocs.io/en/latest/faq/alerts.html + # + # Example alert is from: + # https://groups.google.com/forum/#!msg/ossec-list/dE3klm84JMU/kGZkRdSl3ZkJ + - name: test_ossec_keep_alive_mark_does_not_produce_alert + alert: > + Dec 02 09:48:40 app-staging ossec-keepalive: --MARK--: + &pQSW__BPa5S?%tyDTJ3-iCG2lz2dU))r(F%6tjp8wqpf=]IKFT%ND2k + P]ua/W)3-6'eHduX$;$Axqq7Vr.dVZ1SUDSaH)4xTXCIieaEKv47LD-b + U)SXMnXO/jPGKn3.!NGBR_5]jD2UoSV9)h%z8G%7.xhI;s)267.rV214 + O@t2#w)Z(k'UQp9]MyDERrOrG[-,e?iS@B3Rg/kGiR[g6mc0K)/]S]0' + +?+'/.[r$fqBR^7iAjoPv4j6SWjeRsLGr%$3#p+buf&u_RC3i/mE3vS3* + jp&B1qSJM431TmEg,YJ][ge;6-dJI69?-TB?!BI4?Uza63V3vMY3ake6a + hj-%A-m_5lgab!OVR,!pR+;L]eLgilU + + +# Log events we expect an OSSEC alert to occur for +log_events_with_ossec_alerts: + # Check that a denied RWX mmaping would produce an OSSEC alert + - name: test_grsec_denied_rwx_mapping_produces_alert + alert: > + Feb 10 23:34:40 app kernel: [ 124.188641] grsec: denied + RWX mmap of by /usr/sbin/apache2 + [apache2:1328] uid/euid:33/33 gid/egid:33/33, parent + /usr/sbin/apache2[apache2:1309] uid/euid:0/0 gid/egid:0/0 + level: "7" # Level 7 alert should be triggered by rule 100101 + rule_id: "100101" + + # When Ansible playbooks are run, an informative alert should be triggered + - name: test_ansible_playbook_triggers_alert + alert: > + Jul 22 17:06:41 app ansible-apt_key: Invoked with file=None + keyserver=None url=None data=-----BEGIN PGP PUBLIC KEY BLOCK + -----#012Version: GnuPG + v1#012#012mQENBEqg7GsBCACsef8koRT8UyZxiv1Irke5nVpte54TDtTl1 + za1tOKfthmHbs2I#0124DHWG3qrwGayw+6yb5mMFe0h9Ap9IbilA5a1IdRs + dDgViyQQ3kvdfoavFHRxvGON#012tknIyk5Goa36GMBl84gQceRs/4Zx3kx + qCV+JYXE9CmdkpkVrh2K3j5+ysDWfD/kO#012dTzwu3WHaAwL8d5MJAGQn2 + i6bTw4UHytrYemS1DdG/0EThCCyAnPmmb8iBkZlSW8#0126MzVqTrN37yvY + WTXk6MwKH50twaX5hzZAlSh9eqRjZLq51DDomO7EumXP90rS5mT#012QrS+ + wiYfGQttoZfbh3wl5ZjejgEjx+qrnOH7ABEBAAG0JmRlYi50b3Jwcm9qZWN + 0#012Lm9yZyBhcmNoaXZlIHNpZ25pbmcga2V5iEYEEBECAAYFAkqqojIACg + kQ61qJaiiY#012i/WmOgCfTyf3NJ7wHTBckwAeE4MSt5ZtXVsAn0XDq8PWW + nk4nK6TlevqK/VoWItF#012iEYEEBECAAYFAkqsYDUACgkQO50JPzGwl0vo + JwCcCSokiJSNY+yIr3nBPN/LJldb#012xekAmwfU60GeaWFwz7hqwVFL23x + eTpyniEYEEBECAAYFAkt9ndgACgkQYhWWT1sX#012KrI5TACfcBPbsaPA1A + UVVXXPv0KeWFYgVaIAoMr3jwd1NYVD6Te3D+yJhGzzCD6P#012iEYEEBECA + AYFAkt+li8ACgkQTlMAGaGhvAU4FwCfX3H4Ggm/x0yIAvmt4CW8AP9F#012 + 5D8AoKapuwbjsGncT3UdNFiHminAaq1tiEYEEBECAAYFAky6mjsACgkQhfc + mMSeh#012yJpL+gCggxs4C5o+Oznk7WmFrPQ3lbnfDKIAni4p20aRuwx6QW + GH8holjzTSmm5F#012iEYEEBECAAYFAlMI0FEACgkQhEMxewZV94DLagCcD + G5SR00+00VHzBVE6fDg027e#012N2sAnjNLOYbRSBxBnELUDKC7Vjaz/sAM + iEwEExECAAwFAkqg7nQFgwll/3cACgkQ#0123nqvbpTAnH+GJA + level: "13" + rule_id: "400001" + + # Override and Log (as to not send email of messages pertaining to pssec + # server and agent starting up after each reboot. + - name: test_ossec_server_started_does_not_produce_email + alert: > + ossec: Ossec started. + level: "1" + rule_id: "400502" + + - name: test_ossec_agent_started_does_not_produce_email + alert: > + ossec: Agent started + level: "1" + rule_id: "400503" + + - name: test_ossec_server_apache_error_log_alert + alert: > + [Fri Apr 12 14:39:25.596318 2019] [wsgi:error] + [pid 1480:tid 4201987876608] ERROR:flask.app:Login for 'user' failed: + invalid username 'user' + level: "7" + rule_id: "400700" + + - name: test_ossec_server_test_notification_alert + alert: > + [Fri Apr 12 15:45:05.310796 2019] [wsgi:error] + [pid 1479:tid 4201988093696] ERROR:flask.app:This is a test OSSEC alert + level: "7" + rule_id: "400700" + +fpf_apt_repo_url: "https://apt.freedom.press" +grsec_version: "4.14.175"