From 571f9d1a8271dd01af542b3c633a72556512d336 Mon Sep 17 00:00:00 2001 From: "Jasper N. Brouwer" Date: Thu, 20 Jul 2017 16:10:33 +0200 Subject: [PATCH] Optimize SSL settings & headers Best security while supporting older browsers --- README.md | 75 +++++++++++++++++++++++------------------ defaults/main.yml | 24 +++++++------ templates/nginx.conf.j2 | 12 +++---- 3 files changed, 61 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 82e4d73..a067d29 100644 --- a/README.md +++ b/README.md @@ -32,46 +32,55 @@ This role sets a couple of settings that are viewed as best practices. The result is the same as if configured in the following way: nginx_http_params: - server_names_hash_bucket_size: 64 - server_tokens: off - - sendfile: on - tcp_nopush: on - tcp_nodelay: on - - gzip: on - gzip_disable: "msie6" - gzip_min_length: 256 - gzip_types: application/json application/vnd.ms-fontobject application/x-font-ttf application/x-javascript application/xml application/xml+rss font/opentype image/svg+xml image/x-icon text/css text/javascript text/plain text/xml - - ssl_ciphers: '"EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"' - ssl_dhparam: "/etc/nginx/dh{{ nginx_dhparam_bits }}.pem" - ssl_ecdh_curve: secp384r1 - ssl_prefer_server_ciphers: on - ssl_protocols: TLSv1.2 - ssl_session_cache: shared:SSL:10m - ssl_session_tickets: off - ssl_stapling: on - ssl_stapling_verify: on - resolver: "{{ ansible_dns.nameservers|join(' ') }} valid=300s" - resolver_timeout: 5s - add_header: "{{ nginx_add_headers }}" + server_names_hash_bucket_size: 64 + server_tokens: off + + sendfile: on + tcp_nopush: on + tcp_nodelay: on + + gzip: on + gzip_disable: "msie6" + gzip_min_length: 256 + gzip_types: application/json application/vnd.ms-fontobject application/x-font-ttf application/x-javascript application/xml application/xml+rss font/opentype image/svg+xml image/x-icon text/css text/javascript text/plain text/xml + + ssl_ciphers: "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS" + ssl_dhparam: "/etc/nginx/dh{{ nginx_dhparam_bits }}.pem" + ssl_prefer_server_ciphers: on + ssl_protocols: TLSv1 TLSv1.1 TLSv1.2 + ssl_session_cache: shared:SSL:50m + ssl_session_tickets: off + ssl_session_timeout: 1d + ssl_stapling: on + ssl_stapling_verify: on + resolver: "{{ ansible_dns.nameservers|join(' ') }} valid=300s" If you want to change one of these settings, add it to `nginx_http_params`. No need to copy the entire dictionary. -Some SSL related headers are included by default: +Add or change headers added in the `http` context: - nginx_add_headers: - - 'Strict-Transport-Security "max-age=63072000 includeSubDomains preload" always' - - X-Content-Type-Options nosniff always - - X-Frame-Options DENY always + nginx_http_headers: {} -Note that when you use `add_header` in a `server` or `location` block, that will completely override the headers that are placed in the `http` block. -To work around this, you can use a union of the "global" headers and your custom headers: +This role adds a couple of headers that are viewed as best practices. +The result is the same as if configured in the following way: + + nginx_http_headers: + Content-Security-Policy: "default-src 'self'; form-action 'self'; frame-ancestors 'none'" + Referrer-Policy: "no-referrer, strict-origin-when-cross-origin" + Strict-Transport-Security: max-age=15768000 + X-Content-Type-Options: nosniff + X-Frame-Options: DENY + X-Xss-Protection: "1; mode=block" + +If you want to change one of these headers, add it to `nginx_http_headers`. +Again, no need to copy the entire dictionary. + +Note that headers added to the `http` context will be ignored when you add headers in a `server` or `location` context. +So don't forget to combine all headers: - {% for header in nginx_add_headers | union(custom_headers) %} - add_header {{ header }}; + {% for key, value in (nginx_http_headers_default | combine(nginx_http_headers) | combine(specialized_headers)).iteritems() %} + add_header {{ key }} "{{ value }}" always; {% endfor %} We generate Diffie-Hellman parameters to enable Perfect Forward Secrecy. diff --git a/defaults/main.yml b/defaults/main.yml index 636e86a..7cc8f8d 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -11,11 +11,6 @@ nginx_use_realpath_root: no nginx_php_force_cgi_redirect: no nginx_set_default_server: yes -nginx_add_headers: - - 'Strict-Transport-Security "max-age=63072000 includeSubDomains preload" always' - - X-Content-Type-Options nosniff always - - X-Frame-Options DENY always - nginx_dhparam_bits: 4096 nginx_http_params_default: @@ -31,19 +26,26 @@ nginx_http_params_default: gzip_min_length: 256 gzip_types: application/json application/vnd.ms-fontobject application/x-font-ttf application/x-javascript application/xml application/xml+rss font/opentype image/svg+xml image/x-icon text/css text/javascript text/plain text/xml - ssl_ciphers: '"EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"' + ssl_ciphers: "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS" ssl_dhparam: "/etc/nginx/dh{{ nginx_dhparam_bits }}.pem" - ssl_ecdh_curve: secp384r1 ssl_prefer_server_ciphers: on - ssl_protocols: TLSv1.2 - ssl_session_cache: shared:SSL:10m + ssl_protocols: TLSv1 TLSv1.1 TLSv1.2 + ssl_session_cache: shared:SSL:50m ssl_session_tickets: off + ssl_session_timeout: 1d ssl_stapling: on ssl_stapling_verify: on resolver: "{{ ansible_dns.nameservers|join(' ') }} valid=300s" - resolver_timeout: 5s - add_header: "{{ nginx_add_headers }}" + +nginx_http_headers_default: + Content-Security-Policy: "default-src 'self'; form-action 'self'; frame-ancestors 'none'" + Referrer-Policy: "no-referrer, strict-origin-when-cross-origin" + Strict-Transport-Security: max-age=15768000 + X-Content-Type-Options: nosniff + X-Frame-Options: DENY + X-Xss-Protection: "1; mode=block" nginx_http_params: {} +nginx_http_headers: {} nginx_server_templates: [] diff --git a/templates/nginx.conf.j2 b/templates/nginx.conf.j2 index 6e84a5d..d37616d 100644 --- a/templates/nginx.conf.j2 +++ b/templates/nginx.conf.j2 @@ -18,12 +18,8 @@ http { access_log /var/log/nginx/access.log main; error_log /var/log/nginx/error.log warn; -{% for key, value in (nginx_http_params_default|combine(nginx_http_params)).iteritems() %} -{% if key == 'add_header' %} -{% for header in value %} - add_header {{ header }}; -{% endfor %} -{% elif value is sameas True %} +{% for key, value in (nginx_http_params_default | combine(nginx_http_params)).iteritems() %} +{% if value is sameas True %} {{ key }} on; {% elif value is sameas False %} {{ key }} off; @@ -32,6 +28,10 @@ http { {% endif %} {% endfor %} +{% for key, value in (nginx_http_headers_default | combine(nginx_http_headers)).iteritems() %} + add_header {{ key }} "{{ value }}" always; +{% endfor %} + include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*.conf; }