Skip to content

Commit

Permalink
Updated testinfra tests to optionally run against a prod instance
Browse files Browse the repository at this point in the history
  • Loading branch information
zenmonkeykstop committed Jun 16, 2020
1 parent a98dbad commit eb703f0
Show file tree
Hide file tree
Showing 49 changed files with 379 additions and 31 deletions.
29 changes: 29 additions & 0 deletions devops/scripts/run_prod_testinfra
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/bash
#
# Script to run testinfra tests against a production SecureDrop instance.
# Must be run on an Admin Workstation after './securedrop-admin tailsconfig'
# has completed successfully.
#

set -e
set -o pipefail

# Are we in Tails?
if [ -f "/home/amnesia/Persistent/.securedrop/securedrop_init.py" ]
then
echo "Tails workstation detected, continuing..."
else
echo "This script should be run on a SecureDrop Admin Workstation!"
exit 1
fi

cd ~/Persistent/securedrop
source admin/.venv3/bin/activate
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"

deactivate
echo "--------"
echo "Testinfra run complete - restore your workstation virtualenv by removing ~/Persistent/securedrop/admin/.venv3 and running 'cd ~/Persistent/securedrop && ./securedrop-admin setup'"
2 changes: 1 addition & 1 deletion molecule/libvirt-staging-xenial/molecule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ verifier:
name: testinfra
lint:
name: flake8
directory: ../testinfra/staging/
directory: ../testinfra
options:
n: auto
v: 2
Expand Down
2 changes: 1 addition & 1 deletion molecule/qubes-staging/molecule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ verifier:
name: testinfra
lint:
name: flake8
directory: ../testinfra/staging/
directory: ../testinfra
options:
n: auto
v: 2
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
testinfra_hosts = ["app-staging"]
import pytest

sdvars = pytest.securedrop_test_vars
testinfra_hosts = [sdvars.app_hostname]


@pytest.mark.run_in_prod
def test_haveged_config(host):
"""
Ensure haveged's low entrop watermark is sufficiently high.
Expand All @@ -13,6 +17,7 @@ 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
Expand All @@ -25,6 +30,7 @@ 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.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import pytest


testinfra_hosts = ["app-staging"]
securedrop_test_vars = pytest.securedrop_test_vars
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
Expand All @@ -14,6 +15,7 @@ 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',
Expand Down Expand Up @@ -83,6 +85,7 @@ 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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import pytest


testinfra_hosts = ["app-staging"]
sdvars = pytest.securedrop_test_vars
testinfra_hosts = [sdvars.app_hostname]


@pytest.mark.run_in_prod
def test_securedrop_rqrequeue_service(host):
"""
Verify configuration of securedrop_rqrequeue systemd service.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import pytest


testinfra_hosts = ["app-staging"]
sdvars = pytest.securedrop_test_vars
testinfra_hosts = [sdvars.app_hostname]


@pytest.mark.run_in_prod
def test_securedrop_rqworker_service(host):
"""
Verify configuration of securedrop_rqworker systemd service.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import pytest


testinfra_hosts = ["app-staging"]
sdvars = pytest.securedrop_test_vars
testinfra_hosts = [sdvars.app_hostname]


@pytest.mark.run_in_prod
def test_securedrop_shredder_service(host):
"""
Verify configuration of securedrop_shredder systemd service.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import pytest


testinfra_hosts = ["app-staging"]
sdvars = pytest.securedrop_test_vars
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import re


testinfra_hosts = ["app-staging"]
securedrop_test_vars = pytest.securedrop_test_vars
testinfra_hosts = [securedrop_test_vars.app_hostname]

# Setting once so it can be reused in multiple tests.
wanted_apache_headers = [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import pytest


testinfra_hosts = ["app-staging"]
securedrop_test_vars = pytest.securedrop_test_vars
testinfra_hosts = [securedrop_test_vars.app_hostname]


@pytest.mark.parametrize("apache_site", [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import pytest
import re

testinfra_hosts = ["app-staging"]
securedrop_test_vars = pytest.securedrop_test_vars
testinfra_hosts = [securedrop_test_vars.app_hostname]


@pytest.mark.parametrize("header", securedrop_test_vars.wanted_apache_headers)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import pytest
import re

testinfra_hosts = ["app-staging"]
securedrop_test_vars = pytest.securedrop_test_vars
testinfra_hosts = [securedrop_test_vars.app_hostname]


@pytest.mark.parametrize("package", [
Expand Down
File renamed without changes.
44 changes: 44 additions & 0 deletions molecule/testinfra/app/iptables-app-staging.j2
Original file line number Diff line number Diff line change
@@ -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 -i eth0 -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 eth0 -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 eth0 -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
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
from jinja2 import Template


testinfra_hosts = ["app-staging"]
securedrop_test_vars = pytest.securedrop_test_vars
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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import pytest


testinfra_hosts = ["app-staging"]
sdvars = pytest.securedrop_test_vars
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():
Expand All @@ -25,6 +27,7 @@ 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 """
Expand All @@ -34,6 +37,7 @@ 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")
Expand All @@ -43,20 +47,23 @@ 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 """
c = host.run(r"perl -nE '/^\s+capability\s+(\w+),$/ && say $1' /etc/apparmor.d/usr.sbin.tor")
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 "
"/etc/apparmor.d/usr.sbin.tor")
assert str(len(tor_capabilities)) == c


@pytest.mark.run_in_prod
@pytest.mark.parametrize('profile', [
'ntpd',
'apache2',
Expand All @@ -74,6 +81,7 @@ 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 """
Expand All @@ -84,13 +92,15 @@ 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():
c = host.check_output("aa-status --complaining")
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./"
Expand All @@ -100,6 +110,7 @@ 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 """
Expand All @@ -111,6 +122,7 @@ 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 """
Expand All @@ -125,6 +137,7 @@ 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():
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import os.path
import pytest

testinfra_hosts = ["app-staging"]
sdvars = pytest.securedrop_test_vars
testinfra_hosts = [sdvars.app_hostname]

sdbin = "/opt/venvs/securedrop-app-code/bin"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import pytest

sdvars = pytest.securedrop_test_vars
testinfra_hosts = ["app", "app-staging"]
testinfra_hosts = [sdvars.app_hostname]


def test_hosts_files(host):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import re


testinfra_hosts = ["app-staging"]
securedrop_test_vars = pytest.securedrop_test_vars
testinfra_hosts = [securedrop_test_vars.app_hostname]


def test_paxctld_installed(host):
Expand Down
Loading

0 comments on commit eb703f0

Please sign in to comment.