diff --git a/roles/web_server/playbook.yml b/roles/web_server/playbook.yml index 7406fb82c0c76b1e1ca869ab52632b84ebcf560d..40f3f1d5a1f9d14138dd7cece368d0bb82415451 100644 --- a/roles/web_server/playbook.yml +++ b/roles/web_server/playbook.yml @@ -75,3 +75,11 @@ # common ca_certificates: testca: "{{ lookup('file', 'tests/data/x509/ca.cert.pem') }}" + + +- hosts: all + tasks: + - name: Install curl for testing redirects and webpage content + apt: + name: curl + state: installed diff --git a/roles/web_server/tasks/main.yml b/roles/web_server/tasks/main.yml index bf4007f07e4f4a3b3319589d697eb4dfa1d52448..8d4edc70cd6c00f29bac2238c969383c64813d2c 100644 --- a/roles/web_server/tasks/main.yml +++ b/roles/web_server/tasks/main.yml @@ -9,14 +9,22 @@ - Restart nginx - name: Deploy nginx TLS private key - copy: dest="/etc/ssl/private/{{ ansible_fqdn }}_https.key" content="{{ default_https_tls_key }}" - mode=0640 owner=root group=root + copy: + dest: "/etc/ssl/private/{{ ansible_fqdn }}_https.key" + content: "{{ default_https_tls_key }}" + mode: 0640 + owner: root + group: root notify: - Restart nginx - name: Deploy nginx TLS certificate - copy: dest="/etc/ssl/certs/{{ ansible_fqdn }}_https.pem" content="{{ default_https_tls_certificate }}" - mode=0644 owner=root group=root + copy: + dest: "/etc/ssl/certs/{{ ansible_fqdn }}_https.pem" + content: "{{ default_https_tls_certificate }}" + mode: 0644 + owner: root + group: root notify: - Restart nginx diff --git a/roles/web_server/tests/test_client.py b/roles/web_server/tests/test_client.py new file mode 100644 index 0000000000000000000000000000000000000000..f2f84caf9bd7053bee4aaf36249d1d5e787dae8b --- /dev/null +++ b/roles/web_server/tests/test_client.py @@ -0,0 +1,21 @@ +import testinfra.utils.ansible_runner + + +testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( + '.molecule/ansible_inventory').get_hosts('client1') + + +def test_connectivity(Command, Sudo): + """ + Tests connectivity to the web server (ports that should be reachable). + """ + + with Sudo(): + + for server in ["parameters-mandatory", + "parameters-optional"]: + # HTTP, HTTPS. + for port in [80, 443]: + + ping = Command('hping3 -S -p %d -c 1 %s' % (port, server)) + assert ping.rc == 0 diff --git a/roles/web_server/tests/test_default.py b/roles/web_server/tests/test_default.py index e7601e24b0cb5d4182c4c10d1920b89d94851cb3..0c4501d1051b252353cd75481d4603803db2b713 100644 --- a/roles/web_server/tests/test_default.py +++ b/roles/web_server/tests/test_default.py @@ -2,12 +2,235 @@ import testinfra.utils.ansible_runner testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( - '.molecule/ansible_inventory').get_hosts('all') + '.molecule/ansible_inventory').get_hosts(['parameters-mandatory', 'parameters-optional']) -def test_hosts_file(File): - f = File('/etc/hosts') +def test_installed_packages(Package): + """ + Tests if the required packages have been installed. + """ - assert f.exists - assert f.user == 'root' - assert f.group == 'root' + assert Package('nginx').is_installed + assert Package('virtualenv').is_installed + assert Package('virtualenvwrapper').is_installed + assert Package('php5-fpm').is_installed + + +def test_nginx_user(User): + """ + Tests if Nginx user has been set-up correctly to traverse TLS directories. + """ + + assert 'ssl-cert' in User('www-data').groups + + +def test_default_tls_configuration_removed(File): + """ + Tests if TLS configuration has been removed from the main (default) + configuration file. + """ + + assert 'ssl_protocols' not in File('/etc/nginx/nginx.conf').content + + +def test_nginx_configuration_verification_script(File): + """ + Tests if script used for verifying Nginx configuration is deployed + correctly. + """ + + script = File('/usr/local/bin/nginx_verify_site.sh') + + assert script.is_file + assert script.user == 'root' + assert script.group == 'root' + assert script.mode == 0o755 + + +def test_tls_configuration_file(File): + """ + Tests permissions of TLS configuration file. + """ + + config = File('/etc/nginx/conf.d/tls.conf') + + assert config.is_file + assert config.user == 'root' + assert config.group == 'root' + assert config.mode == 0o644 + + +def test_default_vhost_file(File): + """ + Tests permissions of default vhost configuration file. + """ + + config = File('/etc/nginx/sites-available/default') + + assert config.is_file + assert config.user == 'root' + assert config.group == 'root' + assert config.mode == 0o640 + + +def test_default_website_enabled(File): + """ + Tests if default website has been enabled. + """ + + config = File('/etc/nginx/sites-enabled/default') + + assert config.is_symlink + assert config.linked_to == '/etc/nginx/sites-available/default' + + +def test_firewall_configuration_file(File, Sudo): + """ + Tests if firewall configuration file has been deployed correctly. + """ + + with Sudo(): + + config = File('/etc/ferm/conf.d/30-web.conf') + + assert config.is_file + assert config.user == 'root' + assert config.group == 'root' + assert config.mode == 0o640 + + +def test_default_debian_index_removed(File, Sudo): + """ + Tests if default HTML pages provided by debian are removed. + """ + + with Sudo(): + assert not File('/var/www/html').exists + + +def test_default_vhost_root_directory(File, Sudo): + """ + Tests if the default vhost root directory exists. + """ + + directory = File('/var/www/default') + + assert directory.is_directory + assert directory.user == 'root' + assert directory.group == 'www-data' + assert directory.mode == 0o750 + + +def test_default_vhost_index_page_file(File, Sudo): + """ + Tests permissions of default vhost index page. + """ + + with Sudo(): + + page = File('/var/www/default/index.html') + + assert page.is_file + assert page.user == 'root' + assert page.group == 'www-data' + assert page.mode == 0o640 + + +def test_services(Service): + """ + Tests if services are enabled at boot and running. + """ + + service = Service('nginx') + assert service.is_enabled + assert service.is_running + + service = Service('php5-fpm') + assert service.is_enabled + assert service.is_running + + +def test_sockets(Socket): + """ + Tests if web server is listening on correct ports. + """ + + assert Socket("tcp://80").is_listening + assert Socket("tcp://443").is_listening + + +def test_socket_directories(File, Sudo): + """ + Tests if directories containing sockets for WSGI and PHP apps are created + correctly. + """ + + directory = File('/run/wsgi') + assert directory.is_directory + assert directory.user == 'root' + assert directory.group == 'www-data' + assert directory.mode == 0o750 + + directory = File('/run/php5-fpm') + assert directory.is_directory + assert directory.user == 'root' + assert directory.group == 'www-data' + assert directory.mode == 0o750 + + config = File('/etc/tmpfiles.d/wsgi.conf') + assert config.is_file + assert config.user == 'root' + assert config.group == 'root' + assert config.mode == 0o644 + assert 'd /run/wsgi/ 0750 root www-data - -' in config.content + + config = File('/etc/tmpfiles.d/php5-fpm.conf') + assert config.is_file + assert config.user == 'root' + assert config.group == 'root' + assert config.mode == 0o644 + assert 'd /run/php5-fpm/ 0750 root www-data - -' in config.content + + +def test_php5_fpm_service_overrides(File): + """ + Tests if overrides for php5-fpm service are deployed correctly. + """ + + directory = File('/etc/systemd/system/php5-fpm.service.d') + assert directory.is_directory + assert directory.user == 'root' + assert directory.group == 'root' + assert directory.mode == 0o755 + + config = File('/etc/systemd/system/php5-fpm.service.d/umask.conf') + assert config.is_file + assert config.user == 'root' + assert config.group == 'root' + assert config.mode == 0o644 + + +def test_php_timezone_configuration(Command, File): + """ + Tests if PHP timezone configuration has been set correctly. + """ + + config = File('/etc/php5/cli/conf.d/30-timezone.ini') + assert config.is_file + assert config.user == 'root' + assert config.group == 'root' + assert config.mode == 0o644 + + config = File('/etc/php5/fpm/conf.d/30-timezone.ini') + assert config.is_file + assert config.user == 'root' + assert config.group == 'root' + assert config.mode == 0o644 + + timezone = Command("php --php-ini /etc/php5/cli/php.ini -r 'echo ini_get(\"date.timezone\");'") + assert timezone.rc == 0 + assert timezone.stdout == "GMT+0" + + timezone = Command("php --php-ini /etc/php5/fpm/php.ini -r 'echo ini_get(\"date.timezone\");'") + assert timezone.rc == 0 + assert timezone.stdout == "GMT+0" diff --git a/roles/web_server/tests/test_mandatory.py b/roles/web_server/tests/test_mandatory.py new file mode 100644 index 0000000000000000000000000000000000000000..2718254ed4094a58da568f2883994d1691c734b7 --- /dev/null +++ b/roles/web_server/tests/test_mandatory.py @@ -0,0 +1,92 @@ +import testinfra.utils.ansible_runner + + +testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( + '.molecule/ansible_inventory').get_hosts('parameters-mandatory') + + +def test_nginx_tls_files(File, Sudo): + """ + Tests if TLS private key and certificate have been deployed correctly. + """ + + with Sudo(): + + tls_file = File('/etc/ssl/private/parameters-mandatory_https.key') + assert tls_file.is_file + assert tls_file.user == 'root' + assert tls_file.group == 'root' + assert tls_file.mode == 0o640 + assert tls_file.content == open("tests/data/x509/parameters-mandatory_https.key", "r").read().rstrip() + + tls_file = File('/etc/ssl/certs/parameters-mandatory_https.pem') + assert tls_file.is_file + assert tls_file.user == 'root' + assert tls_file.group == 'root' + assert tls_file.mode == 0o644 + assert tls_file.content == open("tests/data/x509/parameters-mandatory_https.pem", "r").read().rstrip() + + +def test_certificate_validity_check_configuration(File): + """ + Tests if certificate validity check configuration file has been deployed + correctly. + """ + + config = File('/etc/check_certificate/parameters-mandatory_https.conf') + assert config.is_file + assert config.user == 'root' + assert config.group == 'root' + assert config.mode == 0o644 + assert config.content == "/etc/ssl/certs/parameters-mandatory_https.pem" + + +def test_tls_configuration(Command): + """ + Tests if the TLS has been configured correctly and works. + """ + + tls = Command('wget -q -O - https://parameters-mandatory/') + assert tls.rc == 0 + + old_tls_versions_disabled = Command("echo 'Q' | openssl s_client -no_tls1_2 -connect parameters-mandatory:443") + assert old_tls_versions_disabled.rc != 0 + assert "CONNECTED" in old_tls_versions_disabled.stdout + + cipher = Command("echo 'Q' | openssl s_client -cipher ECDHE-RSA-AES128-SHA256 -connect parameters-mandatory:443") + assert cipher.rc == 0 + assert "ECDHE-RSA-AES128-SHA256" in cipher.stdout + + cipher = Command("echo 'Q' | openssl s_client -cipher ECDHE-RSA-AES128-SHA -connect parameters-mandatory:443") + assert cipher.rc != 0 + assert "ECDHE-RSA-AES128-SHA" not in cipher.stdout + + +def test_https_enforcement(Command): + """ + Tests if HTTPS is being enforced. + """ + + https_enforcement = Command('curl -I http://parameters-mandatory/') + + assert https_enforcement.rc == 0 + assert 'HTTP/1.1 301 Moved Permanently' in https_enforcement.stdout + assert 'Location: https://parameters-mandatory/' in https_enforcement.stdout + + https_enforcement = Command('curl -I https://parameters-mandatory/') + + assert https_enforcement.rc == 0 + assert 'Strict-Transport-Security: max-age=31536000; includeSubDomains' in https_enforcement.stdout + + +def test_default_vhost_index_page(Command): + """ + Tests content of default vhost index page. + """ + + page = Command('curl https://parameters-mandatory/') + + assert page.rc == 0 + assert "
You are attempting to access the web server using a wrong name or an IP address. Please check your URL.
" in page.stdout diff --git a/roles/web_server/tests/test_optional.py b/roles/web_server/tests/test_optional.py new file mode 100644 index 0000000000000000000000000000000000000000..c231051b672bc3cbcc461f3212b5abc325bfc9ee --- /dev/null +++ b/roles/web_server/tests/test_optional.py @@ -0,0 +1,97 @@ +import testinfra.utils.ansible_runner + + +testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( + '.molecule/ansible_inventory').get_hosts('parameters-optional') + + +def test_nginx_tls_files(File, Sudo): + """ + Tests if TLS private key and certificate have been deployed correctly. + """ + + with Sudo(): + + tls_file = File('/etc/ssl/private/parameters-optional_https.key') + assert tls_file.is_file + assert tls_file.user == 'root' + assert tls_file.group == 'root' + assert tls_file.mode == 0o640 + assert tls_file.content == open("tests/data/x509/parameters-optional_https.key.pem", "r").read().rstrip() + + tls_file = File('/etc/ssl/certs/parameters-optional_https.pem') + assert tls_file.is_file + assert tls_file.user == 'root' + assert tls_file.group == 'root' + assert tls_file.mode == 0o644 + assert tls_file.content == open("tests/data/x509/parameters-optional_https.cert.pem", "r").read().rstrip() + + +def test_certificate_validity_check_configuration(File): + """ + Tests if certificate validity check configuration file has been deployed + correctly. + """ + + config = File('/etc/check_certificate/parameters-optional_https.conf') + assert config.is_file + assert config.user == 'root' + assert config.group == 'root' + assert config.mode == 0o644 + assert config.content == "/etc/ssl/certs/parameters-optional_https.pem" + + +def test_tls_configuration(Command): + """ + Tests if the TLS has been configured correctly and works. + """ + + tls = Command('wget -q -O - https://parameters-optional/') + assert tls.rc == 0 + + old_tls_versions_disabled = Command("echo 'Q' | openssl s_client -no_tls1_1 -no_tls1_2 -connect parameters-optional:443") + assert old_tls_versions_disabled.rc != 0 + assert "CONNECTED" in old_tls_versions_disabled.stdout + + newer_tls_versions_enabled = Command("echo 'Q' | openssl s_client -no_tls1_2 -connect parameters-optional:443") + assert newer_tls_versions_enabled.rc == 0 + assert "CONNECTED" in newer_tls_versions_enabled.stdout + + cipher = Command("echo 'Q' | openssl s_client -cipher ECDHE-RSA-AES128-SHA256 -connect parameters-optional:443") + assert cipher.rc == 0 + assert "ECDHE-RSA-AES128-SHA256" in cipher.stdout + + cipher = Command("echo 'Q' | openssl s_client -cipher ECDHE-RSA-AES128-SHA -connect parameters-optional:443") + assert cipher.rc == 0 + assert "ECDHE-RSA-AES128-SHA" in cipher.stdout + + +def test_https_enforcement(Command): + """ + Tests if HTTPS is (not) being enforced. + """ + + https_enforcement = Command('curl -I http://parameters-optional/') + + assert https_enforcement.rc == 0 + assert 'HTTP/1.1 200 OK' in https_enforcement.stdout + assert 'HTTP/1.1 301 Moved Permanently' not in https_enforcement.stdout + assert 'Location: https://parameters-optional/' not in https_enforcement.stdout + + https_enforcement = Command('curl -I https://parameters-optional/') + + assert https_enforcement.rc == 0 + assert 'Strict-Transport-Security' not in https_enforcement.stdout + + +def test_default_vhost_index_page(Command): + """ + Tests content of default vhost index page. + """ + + page = Command('curl https://parameters-optional/') + + assert page.rc == 0 + assert "Welcome to parameters-optional, default virtual host.
" in page.stdout