Changeset - 5b6d00b0beab
[Not reviewed]
0 8 0
Branko Majic (branko) - 4 years ago 2020-11-16 17:00:23
branko@majic.rs
MAR-170: Always enforce use of HTTPS in the wsgi_server role:

- Dropped the enforce_https parameter.
- Updated tests.
- Updated release notes.
8 files changed with 33 insertions and 52 deletions:
0 comments (0 inline, 0 general)
docs/releasenotes.rst
Show inline comments
 
@@ -83,12 +83,15 @@ Breaking changes:
 
* ``wsgi_website`` role
 

	
 
  * Parameters ``gunicorn_version`` and ``futures_version`` have been
 
    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``
 
    parameter).
 

	
 
* ``xmpp_server`` role
docs/rolereference.rst
Show inline comments
 
@@ -1714,12 +1714,20 @@ The role implements the following:
 
* Deploys the HTTPS TLS private key and certificate (for website vhost).
 
* Configures nginx to serve the website (static files served directly, requests
 
  passed on to Gunicorn).
 

	
 
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,
 
  ``web-wiki_example_com``).
 
* Website users are set-up via GECOS field to have their umask set to ``0007``
 
  (in combination with ``pam_umask``).
 
@@ -1776,18 +1784,12 @@ Parameters
 
    Configuration option.
 

	
 
**admin_uid** (integer, optional, ``whatever OS picks``)
 
  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
 
  colour, text colour, and text.
 

	
 
  Specifying environment indicator is useful for avoiding mistakes when testing
roles/wsgi_website/defaults/main.yml
Show inline comments
 
---
 

	
 
additional_nginx_config: {}
 
enforce_https: true
 
packages: []
 
rewrites: []
 
static_locations: []
 
use_paste: false
 
virtualenv_packages: []
 
environment_variables: {}
roles/wsgi_website/molecule/default/playbook.yml
Show inline comments
 
@@ -23,13 +23,12 @@
 
      https_tls_certificate: "{{ lookup('file', 'tests/data/x509/server/parameters-optional.local_https.cert.pem') }}"
 
      https_tls_key: "{{ lookup('file', 'tests/data/x509/server/parameters-optional.local_https.key.pem') }}"
 
      additional_nginx_config:
 
        - 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"
 
        text: "parameters-optional"
 
      environment_variables:
 
        MY_ENV_VAR: "My environment variable"
roles/wsgi_website/molecule/default/tests/test_default.py
Show inline comments
 
@@ -582,6 +582,28 @@ def test_wsgi_requirements(host, fqdn, pip_check_requirements_upgrades_directory
 
        assert requirements.user == 'admin-%s' % fqdn.replace(".", "_")
 
        assert requirements.group == 'web-%s' % fqdn.replace(".", "_")
 
        assert requirements.mode == 0o640
 

	
 
        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
roles/wsgi_website/molecule/default/tests/test_parameters_mandatory.py
Show inline comments
 
@@ -4,29 +4,12 @@ import testinfra.utils.ansible_runner
 

	
 

	
 
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:
 

	
 
    - Basic WSGI application operation.
 
    - Handling of environment variables.
roles/wsgi_website/molecule/default/tests/test_parameters_optional.py
Show inline comments
 
@@ -13,30 +13,12 @@ def test_installed_packages(host):
 
    """
 

	
 
    assert host.package('libmariadbclient-dev-compat').is_installed
 
    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:
 

	
 
    - Basic WSGI application operation.
 
    - Handling of environment variables.
roles/wsgi_website/templates/nginx_site.j2
Show inline comments
 
{% if enforce_https -%}
 
server {
 
    # HTTP (plaintext) configuration.
 
    listen 80;
 
    server_name {{ fqdn }};
 

	
 
    # Redirect plaintext connections to HTTPS
 
    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 }}
 
    {{ config.value }}
 
    {% endfor -%}
 

	
0 comments (0 inline, 0 general)