diff --git a/.salt-lint b/.salt-lint new file mode 100644 index 0000000..ed7c553 --- /dev/null +++ b/.salt-lint @@ -0,0 +1,5 @@ +--- +skip_list: + # Allows 3-digit unquoted codes to still be used, such as `644` and `755` + - 207 # File modes should always be encapsulated in quotation marks + - 208 # File modes should always contain a leading zero diff --git a/README.md b/README.md index f5c8d7a..0b8359a 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ Install and configure nginx. Install the `nginx` package from the official PPA. +Set default ssl settings in the `http` context. This settings can be overwritten for `server {}` block if needed. + To deploy sites, you need to put your configuration files in `/etc/nginx/conf.d` in another formula. diff --git a/init.sls b/init.sls index f593c76..af14b6a 100644 --- a/init.sls +++ b/init.sls @@ -1,3 +1,16 @@ +# The cipher suite comes from the Mozilla TLS guide, with the following modifications: +# - Prefer Chacha-Poly over AES +# - Remove less secure DHE based ciphers +# - Prefer SHA384 over SHA256 +# See: https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28recommended.29 +{% set ssl_protocols = salt['pillar.get']('nginx:ssl_protocols', 'TLSv1.2 TLSv1.3') %} +{% set ssl_ciphers = salt['pillar.get']('nginx:ssl_ciphers', 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256' ) %} # noqa: 204 +{% set ssl_session_cache = salt['pillar.get']('nginx:ssl_session_cache', 'shared:SSL:10m' ) %} +{% set ssl_prefer_server_ciphers = salt['pillar.get']('nginx:ssl_prefer_server_ciphers', 'on') %} +{% set ssl_stapling = salt['pillar.get']('nginx:ssl_stapling', 'off') %} +{% set ssl_stapling_verify = salt['pillar.get']('nginx:ssl_stapling_verify', 'off') %} +{% set ssl_session_tickets = salt['pillar.get']('nginx:ssl_session_tickets', 'off') %} + nginx-repo: pkgrepo.managed: - name: deb http://nginx.org/packages/mainline/ubuntu {{ grains['oscodename'] }} nginx @@ -33,6 +46,15 @@ validate-nginx-config: - user: root - group: root - mode: 644 + - defaults: + ssl_protocols: {{ ssl_protocols }} + ssl_ciphers: {{ ssl_ciphers }} + ssl_session_cache: {{ ssl_session_cache }} + # Quotes are needed, as YAML will interpret on/off as boolean + ssl_prefer_server_ciphers: '{{ ssl_prefer_server_ciphers }}' + ssl_stapling: '{{ ssl_stapling }}' + ssl_stapling_verify: '{{ ssl_stapling_verify }}' + ssl_session_tickets: '{{ ssl_session_tickets }}' - require: - pkg: nginx diff --git a/letsencrypt/default_server.nginx.jinja b/letsencrypt/default_server.nginx.jinja index 9573cd4..47651da 100644 --- a/letsencrypt/default_server.nginx.jinja +++ b/letsencrypt/default_server.nginx.jinja @@ -1,15 +1,10 @@ # To prevent host header injection, this file sets the default_server directive and returns # HTTP 400 for requests with an unknown hostname in Host header server { - listen *:80 default_server; - listen [::]:80 default_server; - - return 400 "Unknown Host $host\n"; -} - -server { - listen *:443 default_server; - listen [::]:443 default_server; + listen *:80 default_server; + listen [::]:80 default_server; + listen *:443 default_server ssl; + listen [::]:443 default_server ssl; # SSL certificate ssl_dhparam {{ acme_certificate_dir }}/dhparam.pem; diff --git a/letsencrypt/init.sls b/letsencrypt/init.sls index 731ccb7..a9b6d8b 100644 --- a/letsencrypt/init.sls +++ b/letsencrypt/init.sls @@ -30,7 +30,7 @@ nginx-generate-dhparam: generate-dummy-cert: cmd.run: - names: - - openssl req -x509 -nodes -batch -newkey rsa:2048 -keyout {{ acme_certificate_dir }}/{{ domain }}/dummy.key -out {{ acme_certificate_dir }}/{{ domain }}/dummy.crt -days 1 + - openssl req -x509 -nodes -batch -newkey rsa:2048 -keyout {{ acme_certificate_dir }}/{{ domain }}/dummy.key -out {{ acme_certificate_dir }}/{{ domain }}/dummy.crt -days 1 # noqa: 204 - ln -s {{ acme_certificate_dir }}/{{ domain }}/dummy.key {{ acme_certificate_dir }}/{{ domain }}/privkey.pem - ln -s {{ acme_certificate_dir }}/{{ domain }}/dummy.crt {{ acme_certificate_dir }}/{{ domain }}/fullchain.pem - creates: {{ acme_certificate_dir }}/{{ domain }}/fullchain.pem @@ -49,8 +49,8 @@ generate-dummy-cert: - source: salt://{{ tpldir }}/letsencrypt.nginx.jinja - template: jinja - defaults: - fqdn: {{ domain }} {{ altnames }} - acme_challenge_dir: {{ acme_challenge_dir }} + fqdn: {{ domain }} {{ altnames }} + acme_challenge_dir: {{ acme_challenge_dir }} - require: - file: /etc/nginx/conf.d @@ -63,8 +63,8 @@ generate-dummy-cert: - source: salt://{{ tpldir }}/default_server.nginx.jinja - template: jinja - defaults: - fqdn: {{ domain }} - acme_certificate_dir: {{ acme_certificate_dir }} + fqdn: {{ domain }} + acme_certificate_dir: {{ acme_certificate_dir }} - require: - file: /etc/nginx/conf.d @@ -79,11 +79,11 @@ dehydrated: - source: salt://{{ tpldir }}/dehydrated.conf.jinja - template: jinja - defaults: - acme_certificate_dir: {{ acme_certificate_dir }} - acme_challenge_dir: {{ acme_challenge_dir }} - contact_email: {{ contact_email }} - hook: /etc/dehydrated/hook.sh - ca: {{ ca }} + acme_certificate_dir: {{ acme_certificate_dir }} + acme_challenge_dir: {{ acme_challenge_dir }} + contact_email: {{ contact_email }} + hook: /etc/dehydrated/hook.sh + ca: {{ ca }} cmd.run: - name: /usr/bin/dehydrated --register --accept-terms - onchanges: @@ -109,7 +109,7 @@ dehydrated: - source: salt://{{ tpldir }}/hook.sh.jinja - template: jinja - defaults: - services: {{ salt['pillar.get']('letsencrypt:services', ['nginx']) }} + services: {{ salt['pillar.get']('letsencrypt:services', ['nginx']) }} - require: - pkg: dehydrated diff --git a/nginx.conf.jinja b/nginx.conf.jinja index 4dfe485..4492424 100644 --- a/nginx.conf.jinja +++ b/nginx.conf.jinja @@ -24,5 +24,14 @@ http { server_tokens off; + # Global TLS settings + ssl_protocols {{ ssl_protocols }}; + ssl_ciphers {{ ssl_ciphers }}; + ssl_session_cache {{ ssl_session_cache }}; + ssl_prefer_server_ciphers {{ ssl_prefer_server_ciphers }}; + ssl_stapling {{ ssl_stapling }}; + ssl_stapling_verify {{ ssl_stapling_verify }}; + ssl_session_tickets {{ ssl_session_tickets }}; + include /etc/nginx/conf.d/*.conf; } diff --git a/pillar.example b/pillar.example index 6978c6f..9a8d1b0 100644 --- a/pillar.example +++ b/pillar.example @@ -1,5 +1,20 @@ # vim: ft=sls +nginx: + + # Default SSL values which will be written to the http context. + # This values can be overwritten in each server context individually. + ssl_protocols: 'TLSv1.2 TLSv1.3' + ssl_ciphers: 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256' # noqa: 204 + + ssl_session_cache: 'shared:SSL:10m' + ssl_prefer_server_ciphers: 'on' + + ssl_stapling: 'off' + ssl_stapling_verify: 'off' + ssl_session_tickets: 'off' + + letsencrypt: domain: example.com altnames: www.example.com alt.example.com diff --git a/tests/integration/nginx/nginx_spec.rb b/tests/integration/nginx/nginx_spec.rb index f97883b..1a2169e 100644 --- a/tests/integration/nginx/nginx_spec.rb +++ b/tests/integration/nginx/nginx_spec.rb @@ -13,6 +13,7 @@ describe file('/etc/nginx/nginx.conf') do its('mode') { should cmp '0644' } its('content') { should match /^\s*worker_processes/ } + its('content') { should match /^\s*ssl_prefer_server_ciphers\s*on;$/ } end describe file('/etc/nginx/conf.d/default.conf') do