From 51c92f71fa0abe3b948cf7252f50faebd4f34276 2020-11-16 16:32:14 From: Branko Majic Date: 2020-11-16 16:32:14 Subject: [PATCH] MAR-170: Always enforce use of HTTPS in the web_server role: - Dropped the default_enforce_https parameter. - Updated tests. - Updated release notes. --- diff --git a/docs/releasenotes.rst b/docs/releasenotes.rst index 30c2bd11c9bb21001e0e974e70fbc0a9053cf756..03c53fb9caa2d0af8ce2fd5fceea24df7ea8d545 100644 --- a/docs/releasenotes.rst +++ b/docs/releasenotes.rst @@ -77,6 +77,9 @@ Breaking changes: dropped. This could introduce incompatibility with older clients trying to connect to the server. + * Parameter ``default_enforce_https`` has been deprecated and + removed. HTTPS is now mandatory in all cases. + * ``wsgi_website`` role * Parameters ``gunicorn_version`` and ``futures_version`` have been diff --git a/docs/rolereference.rst b/docs/rolereference.rst index 2048589d2dfae556de58c8737bcd3c01a930eb99..50f97dfcc9ef5ece39314803fa21f9575a3b69a4 100644 --- a/docs/rolereference.rst +++ b/docs/rolereference.rst @@ -1374,6 +1374,17 @@ The role implements the following: Python apps. * Installs and configures PHP FPM as a common base for PHP apps. +The web server is configured as follows: + +* 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 default domain, as well as any subdomains served + by this web server or any other. The (client-side) cached header + value expires after one year. + Role dependencies ~~~~~~~~~~~~~~~~~ @@ -1386,12 +1397,6 @@ Depends on the following roles: Parameters ~~~~~~~~~~ -**default_enforce_https** (boolean, optional, ``True``) - Specify if HTTPS should be enforced for the default virtual host 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``. - **default_https_tls_certificate** (string, mandatory) X.509 certificate used for TLS for HTTPS service. The file will be stored in directory ``/etc/ssl/certs/`` under name ``{{ ansible_fqdn }}_https.pem``. diff --git a/roles/web_server/defaults/main.yml b/roles/web_server/defaults/main.yml index e7bd8e9e2cfc6bb09cabf7f5d0ed0cd1c58e7c62..bb9b8f239b90623115e58dbd9b4f811f38756b5e 100644 --- a/roles/web_server/defaults/main.yml +++ b/roles/web_server/defaults/main.yml @@ -1,6 +1,5 @@ --- -default_enforce_https: true web_default_title: "Welcome" web_default_message: "You are attempting to access the web server using a wrong name or an IP address. Please check your URL." web_server_tls_protocols: diff --git a/roles/web_server/molecule/default/group_vars/parameters-optional.yml b/roles/web_server/molecule/default/group_vars/parameters-optional.yml index b74bd6c86dba1c1d5de733b946614e0e20049f6a..ff2c5e0f3891f2503fd3f34a5e82ff59f0af7778 100644 --- a/roles/web_server/molecule/default/group_vars/parameters-optional.yml +++ b/roles/web_server/molecule/default/group_vars/parameters-optional.yml @@ -1,6 +1,5 @@ --- -default_enforce_https: false default_https_tls_certificate: "{{ lookup('file', 'tests/data/x509/server/{{ inventory_hostname }}_https.cert.pem') }}" default_https_tls_key: "{{ lookup('file', 'tests/data/x509/server/{{ inventory_hostname }}_https.key.pem') }}" web_default_title: "Optional Welcome" diff --git a/roles/web_server/molecule/default/tests/test_default.py b/roles/web_server/molecule/default/tests/test_default.py index a5c3a1815560821c28915c3ab2e87aaa41b7acdd..fbfffa675c9628b3e5467ac1e428f2ad990e7bcc 100644 --- a/roles/web_server/molecule/default/tests/test_default.py +++ b/roles/web_server/molecule/default/tests/test_default.py @@ -328,3 +328,20 @@ def test_tls_enabled(host): tls = host.run('wget -q -O - https://%s/', fqdn) assert tls.rc == 0 + + +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 diff --git a/roles/web_server/molecule/default/tests/test_mandatory.py b/roles/web_server/molecule/default/tests/test_mandatory.py index 3ac60b2ca2205933ed54e95b692fe3a072059512..0d53df3ee72a7823d9992fd16a61c9eb04db566d 100644 --- a/roles/web_server/molecule/default/tests/test_mandatory.py +++ b/roles/web_server/molecule/default/tests/test_mandatory.py @@ -48,23 +48,6 @@ def test_tls_version_and_ciphers(host): assert tls_ciphers == expected_tls_ciphers -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_default_vhost_index_page(host): """ Tests content of default vhost index page. diff --git a/roles/web_server/molecule/default/tests/test_optional.py b/roles/web_server/molecule/default/tests/test_optional.py index 624bd021caab3796b544a83e11da3613bc98f890..d2bfd77e5b73c878f15b04a9a2c707dbfb19e9e6 100644 --- a/roles/web_server/molecule/default/tests/test_optional.py +++ b/roles/web_server/molecule/default/tests/test_optional.py @@ -51,24 +51,6 @@ def test_tls_version_and_ciphers(host): assert tls_ciphers == expected_tls_ciphers -def test_https_enforcement(host): - """ - Tests if HTTPS is (not) being enforced. - """ - - https_enforcement = host.run('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 = host.run('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(host): """ Tests content of default vhost index page. diff --git a/roles/web_server/templates/nginx-default.j2 b/roles/web_server/templates/nginx-default.j2 index 1ab3a553317a49276aa36913500d01508c30e882..89711a3b278fc3d6ecea3fb6134e0105969d2b44 100644 --- a/roles/web_server/templates/nginx-default.j2 +++ b/roles/web_server/templates/nginx-default.j2 @@ -1,7 +1,6 @@ # # Default server (vhost) configuration. # -{% if default_enforce_https -%} server { # HTTP (plaintext) configuration. listen 80 default_server; @@ -14,25 +13,16 @@ server { return 301 https://$host$request_uri; } -{% endif -%} server { -{% if not default_enforce_https %} - # HTTP (plaintext) configuration. - listen 80 default_server; - listen [::]:80 default_server; - -{% endif %} # HTTPS (TLS) configuration. listen 443 ssl default_server; listen [::]:443 ssl default_server; ssl_certificate_key /etc/ssl/private/{{ ansible_fqdn }}_https.key; ssl_certificate /etc/ssl/certs/{{ ansible_fqdn }}_https.pem; -{% if default_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 %} # Set-up the serving of default page. root /var/www/default/;