From 5b6d00b0beabc59657ebb9a1f713d5e240c41d8b 2020-11-16 17:00:23 From: Branko Majic Date: 2020-11-16 17:00:23 Subject: [PATCH] MAR-170: Always enforce use of HTTPS in the wsgi_server role: - Dropped the enforce_https parameter. - Updated tests. - Updated release notes. --- diff --git a/docs/releasenotes.rst b/docs/releasenotes.rst index 03c53fb9caa2d0af8ce2fd5fceea24df7ea8d545..2aa625e33357952bb885b0f8c7e7d52c936a3b4b 100644 --- a/docs/releasenotes.rst +++ b/docs/releasenotes.rst @@ -86,6 +86,9 @@ Breaking changes: deprecated and removed. Existing roles should be updated to utilise the ``wsgi_requirements`` parameter instead. + * Parameter ``enforce_https`` has been deprecated and + removed. HTTPS is now mandatory in all cases. + * Added parameter ``wsgi_requirements_in`` for listing top-level packages for performing pip requirements upgrade checks for Gunicorn requirements (listed via existing ``wsgi_requirements`` diff --git a/docs/rolereference.rst b/docs/rolereference.rst index 50f97dfcc9ef5ece39314803fa21f9575a3b69a4..39efdcb270333faf9c32eccbbe886e271da00461 100644 --- a/docs/rolereference.rst +++ b/docs/rolereference.rst @@ -1717,6 +1717,14 @@ The role implements the following: The role is implemented with the following layout/logic in mind: +* No plaintext HTTP is allowed, HTTPS is mandatory. Clients connecting + via plaintext HTTP are redirected to HTTPS. +* Clients are served with ``Strict-Transport-Security`` header with + value of ``max-age=31536000; includeSubDomains``. This forces + compliant clients to always connect using HTTPS to the web server + when accessing its domain, as well as any subdomains served + by this web server or any other. The (client-side) cached header + value expires after one year. * Website users are named after the ``FQDN`` (fully qualified domain name) of website, in format of ``web-ESCAPEDFQDN``, where ``ESCAPEDFQDN`` is equal to ``FQDN`` where dots have been replaced by underscores (for example, @@ -1779,12 +1787,6 @@ Parameters UID of the dedicated website administrator user. The user will be member of website group. -**enforce_https** (boolean, optional, ``True``) - Specify if HTTPS should be enforced for the website or not. If enforced, - clients connecting via plaintext will be redirected to HTTPS, and clients will - be served with ``Strict-Transport-Security`` header with value of - ``max-age=31536000; includeSubDomains``. - **environment_indicator** (dictionary, optional, ``null``) Specify configuration for including environment indicator on all HTML pages. Indicator is a simple strip at bottom of a page with custom background diff --git a/roles/wsgi_website/defaults/main.yml b/roles/wsgi_website/defaults/main.yml index 19d7e8b9451889dbc95043ded02470486a51591b..5a0ff237a7941907a64cb7777e1cdd680e17205d 100644 --- a/roles/wsgi_website/defaults/main.yml +++ b/roles/wsgi_website/defaults/main.yml @@ -1,7 +1,6 @@ --- additional_nginx_config: {} -enforce_https: true packages: [] rewrites: [] static_locations: [] diff --git a/roles/wsgi_website/molecule/default/playbook.yml b/roles/wsgi_website/molecule/default/playbook.yml index fcac8e8344822bc0ba653e96eda332e9f33bb424..1a765e1c3f03d599a6d779fbb4554b592dd9f1dd 100644 --- a/roles/wsgi_website/molecule/default/playbook.yml +++ b/roles/wsgi_website/molecule/default/playbook.yml @@ -26,7 +26,6 @@ - comment: Custom missing page. value: error_page 404 /my/own/error/page; admin_uid: 5000 - enforce_https: false environment_indicator: background_colour: "#ff0000" text_colour: "#00ff00" diff --git a/roles/wsgi_website/molecule/default/tests/test_default.py b/roles/wsgi_website/molecule/default/tests/test_default.py index 2a8b840860f0b4298dc798273fb83f2d20e140ff..ae9fe0c457afa5549767cfc16513c3e25d1f5021 100644 --- a/roles/wsgi_website/molecule/default/tests/test_default.py +++ b/roles/wsgi_website/molecule/default/tests/test_default.py @@ -585,3 +585,25 @@ def test_wsgi_requirements(host, fqdn, pip_check_requirements_upgrades_directory install_requirements = requirements.content_string.strip().split("\n") assert install_requirements == expected_requirements + + +@pytest.mark.parametrize('fqdn', [ + 'parameters-mandatory', + 'parameters-optional.local', + 'parameters-paste-req' +]) +def test_https_enforcement(host, fqdn): + """ + Tests if HTTPS is being enforced. + """ + + https_enforcement = host.run('curl -I http://%s/', fqdn) + + assert https_enforcement.rc == 0 + assert 'HTTP/1.1 301 Moved Permanently' in https_enforcement.stdout + assert 'Location: https://%s/' % fqdn in https_enforcement.stdout + + https_enforcement = host.run('curl -I https://%s/', fqdn) + + assert https_enforcement.rc == 0 + assert 'Strict-Transport-Security: max-age=31536000; includeSubDomains' in https_enforcement.stdout diff --git a/roles/wsgi_website/molecule/default/tests/test_parameters_mandatory.py b/roles/wsgi_website/molecule/default/tests/test_parameters_mandatory.py index 12ba9ea69901141d2a08a23bfc4b30a33443eb32..7bd0897532812014a3c3f64e6e9d00d74b198f84 100644 --- a/roles/wsgi_website/molecule/default/tests/test_parameters_mandatory.py +++ b/roles/wsgi_website/molecule/default/tests/test_parameters_mandatory.py @@ -7,23 +7,6 @@ testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('parameters-mandatory') -def test_https_enforcement(host): - """ - Tests if HTTPS is being enforced. - """ - - https_enforcement = host.run('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 = host.run('curl -I https://parameters-mandatory/') - - assert https_enforcement.rc == 0 - assert 'Strict-Transport-Security: max-age=31536000; includeSubDomains' in https_enforcement.stdout - - def test_index_page(host): """ Tests if index page is served correctly. This covers: diff --git a/roles/wsgi_website/molecule/default/tests/test_parameters_optional.py b/roles/wsgi_website/molecule/default/tests/test_parameters_optional.py index be962d30b839ba40650ece44ba722e89f1187027..a9c707214221136d638865375f5eef231ae76ecd 100644 --- a/roles/wsgi_website/molecule/default/tests/test_parameters_optional.py +++ b/roles/wsgi_website/molecule/default/tests/test_parameters_optional.py @@ -16,24 +16,6 @@ def test_installed_packages(host): assert host.package('global').is_installed -def test_https_enforcement(host): - """ - Tests if HTTPS is (not) being enforced. - """ - - https_enforcement = host.run('curl -I http://parameters-optional.local/') - - 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 = host.run('curl -I https://parameters-optional.local/') - - assert https_enforcement.rc == 0 - assert 'Strict-Transport-Security' not in https_enforcement.stdout - - def test_index_page(host): """ Tests if index page is served correctly. This covers: diff --git a/roles/wsgi_website/templates/nginx_site.j2 b/roles/wsgi_website/templates/nginx_site.j2 index 361af0e1a948200036a994b49f3f290c9622134f..e47a06cf35d45d6621f3052d92e5a2e5f087acb2 100644 --- a/roles/wsgi_website/templates/nginx_site.j2 +++ b/roles/wsgi_website/templates/nginx_site.j2 @@ -1,4 +1,3 @@ -{% if enforce_https -%} server { # HTTP (plaintext) configuration. listen 80; @@ -8,28 +7,20 @@ server { return 301 https://$host$request_uri; } -{% endif -%} server { # Base settings. root {{ home }}/htdocs/; server_name {{ fqdn }}; -{% if not enforce_https %} - # HTTP (plaintext) configuration. - listen 80; - -{% endif %} # HTTPS (TLS) configuration. listen 443 ssl; listen [::]:443 ssl; ssl_certificate_key /etc/ssl/private/{{ fqdn }}_https.key; ssl_certificate /etc/ssl/certs/{{ fqdn }}_https.pem; -{% if enforce_https -%} # Set-up HSTS header for preventing downgrades for users that visited the # site via HTTPS at least once. add_header Strict-Transport-Security "max-age=31536000; includeSubDomains"; -{% endif -%} {% for config in additional_nginx_config -%} # {{ config.comment }}