From 06e6a3493a6d0796962d5796dcd3662e3b39126f Mon Sep 17 00:00:00 2001 From: s3lph Date: Sat, 26 Feb 2022 04:19:44 +0100 Subject: [PATCH] Add experimental nginx role --- galaxy.yml | 15 ++--- roles/nginx/defaults/main.yml | 23 +++++++ roles/nginx/handlers/main.yml | 11 +++ roles/nginx/tasks/config.yml | 62 +++++++++++++++++ roles/nginx/tasks/install.yml | 22 ++++++ roles/nginx/tasks/main.yml | 11 +++ roles/nginx/templates/etc/nginx/dh_param | 8 +++ roles/nginx/templates/etc/nginx/nginx.conf.j2 | 65 ++++++++++++++++++ .../etc/nginx/sites-available/site.conf.j2 | 67 +++++++++++++++++++ 9 files changed, 276 insertions(+), 8 deletions(-) create mode 100644 roles/nginx/defaults/main.yml create mode 100644 roles/nginx/handlers/main.yml create mode 100644 roles/nginx/tasks/config.yml create mode 100644 roles/nginx/tasks/install.yml create mode 100644 roles/nginx/tasks/main.yml create mode 100644 roles/nginx/templates/etc/nginx/dh_param create mode 100644 roles/nginx/templates/etc/nginx/nginx.conf.j2 create mode 100644 roles/nginx/templates/etc/nginx/sites-available/site.conf.j2 diff --git a/galaxy.yml b/galaxy.yml index 2c7811a..454a483 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -8,7 +8,7 @@ namespace: s3lph name: webserver # The version of the collection. Must be compatible with semantic versioning -version: 0.1.0 +version: 0.2.0 # The path to the Markdown (.md) readme file. This path is relative to the root of the collection readme: README.md @@ -21,7 +21,7 @@ authors: ### OPTIONAL but strongly recommended # A short summary description of the collection -description: Webserver, Apache, Lets Encrypt, ACME, Certbot +description: Webserver, Apache, Nginx, Lets Encrypt, ACME, Certbot # Either a single license or a list of licenses for content inside of a collection. Ansible Galaxy currently only # accepts L(SPDX,https://spdx.org/licenses/) licenses. This key is mutually exclusive with 'license_file' @@ -31,12 +31,11 @@ license: # A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character # requirements as 'namespace' and 'name' tags: - - email - - mailserver - - postfix - - dovecot - - postfixadmin - - postsrsd + - webserver + - apache2 + - nginx + - letsencrypt + - certbot # Collections that this collection requires to be installed for it to be usable. The key of the dict is the # collection label 'namespace.name'. The value is a version range diff --git a/roles/nginx/defaults/main.yml b/roles/nginx/defaults/main.yml new file mode 100644 index 0000000..f10430f --- /dev/null +++ b/roles/nginx/defaults/main.yml @@ -0,0 +1,23 @@ +--- + +nginx_vhost_serveraliases: [] +nginx_vhost_documentroot: /var/www/html + +nginx_vhost_http_enabled: true +nginx_vhost_https_enabled: true +nginx_vhost_http_redirect_to_https: true + +nginx_tls_certfile: "/etc/letsencrypt/live/{{ inventory_hostname }}/fullchain.pem" +nginx_tls_keyfile: "/etc/letsencrypt/live/{{ inventory_hostname }}/privkey.pem" +nginx_tls_noacme_fallback_certfile: /etc/ssl/certs/ssl-cert-snakeoil.pem +nginx_tls_noacme_fallback_keyfile: /etc/ssl/private/ssl-cert-snakeoil.key + +# generated 2022-02-26, Mozilla Guideline v5.6, nginx 1.18.0, OpenSSL 1.1.1k, intermediate configuration, no OCSP +# https://ssl-config.mozilla.org/#server=nginx&version=1.18.0&config=intermediate&openssl=1.1.1k&ocsp=false&guideline=5.6 +nginx_ssl_protocols: "TLSv1.2 TLSv1.3" +nginx_ssl_ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384" +nginx_ssl_prefer_server_ciphers: false +nginx_ssl_session_tickets: false + +nginx_modules: [] +nginx_sites: {} diff --git a/roles/nginx/handlers/main.yml b/roles/nginx/handlers/main.yml new file mode 100644 index 0000000..8130a20 --- /dev/null +++ b/roles/nginx/handlers/main.yml @@ -0,0 +1,11 @@ +--- + +- name: restart nginx + service: + name: nginx + state: restarted + +- name: reload nginx + service: + name: nginx + state: reloaded diff --git a/roles/nginx/tasks/config.yml b/roles/nginx/tasks/config.yml new file mode 100644 index 0000000..06866fe --- /dev/null +++ b/roles/nginx/tasks/config.yml @@ -0,0 +1,62 @@ +--- + +- name: enable nginx modules + ansible.builtin.file: + path: "/etc/nginx/modules-enabled/50-mod-{{ item }}.conf" + state: link + src: "/usr/share/nginx/modules-available/mod-{{ item }}.conf" + owner: root + group: root + loop: "{{ nginx_modules }}" + notify: restart nginx + +- ansible.builtin.meta: flush_handlers + +- name: check for tls keypair existence + ansible.builtin.stat: + path: "{{ item }}" + follow: yes + loop: | + {%- set files = [] -%} + {%- for name, site in nginx_sites.items() -%} + {%- if site.https_enabled | default(nginx_vhost_https_enabled) -%} + {%- set _x = files.append(site.tls_certfile | default(nginx_tls_certfile)) -%} + {%- set _x = files.append(site.tls_keyfile | default(nginx_tls_keyfile)) -%} + {%- endif -%} + {%- endfor -%} + {{- files | unique | list -}} + register: nginx_register_stat_tls_keypairs + +- name: create nginx document roots + ansible.builtin.file: + path: "{{ item.documentroot | default(nginx_vhost_documentroot) }}" + state: directory + owner: www-data + group: www-data + mode: 0755 + loop: "{{ nginx_sites.values() }}" + +- name: render nginx site configs + ansible.builtin.template: + src: etc/nginx/sites-available/site.conf.j2 + dest: "/etc/nginx/sites-available/{{ item.key }}.conf" + owner: root + group: root + mode: 0644 + vars: + name: "{{ item.key }}" + site: "{{ item.value }}" + certfile_exists: "{{ (nginx_register_stat_tls_keypairs.results | selectattr('item', 'equalto', (item.value.tls_certfile | default(nginx_tls_certfile)) ))[0].stat.exists }}" + keyfile_exists: "{{ (nginx_register_stat_tls_keypairs.results | selectattr('item', 'equalto', (item.value.tls_certfile | default(nginx_tls_keyfile)) ))[0].stat.exists }}" + loop: "{{ nginx_sites | dict2items }}" + notify: reload nginx + +- name: enable nginx sites + ansible.builtin.file: + path: "/etc/nginx/sites-enabled/{{ item }}.conf" + state: link + src: "/etc/nginx/sites-available/{{ item }}.conf" + owner: root + group: root + loop: "{{ nginx_sites.keys() }}" + notify: reload nginx diff --git a/roles/nginx/tasks/install.yml b/roles/nginx/tasks/install.yml new file mode 100644 index 0000000..45de3b0 --- /dev/null +++ b/roles/nginx/tasks/install.yml @@ -0,0 +1,22 @@ +--- + +- name: install nginx and related packages + ansible.builtin.apt: + name: + - nginx + - ssl-cert # snakeoil cert used for optional tls bootstrapping + +- name: deploy ffdhe2048 dhparams + ansible.builtin.template: + src: etc/nginx/dh_param + dest: /etc/nginx/dh_param + owner: root + group: root + moe: 0644 + notify: reload nginx + +- name: start and enable nginx + ansible.builtin.service: + name: nginx + state: started + enabled: yes diff --git a/roles/nginx/tasks/main.yml b/roles/nginx/tasks/main.yml new file mode 100644 index 0000000..b317c95 --- /dev/null +++ b/roles/nginx/tasks/main.yml @@ -0,0 +1,11 @@ +--- + +- ansible.builtin.import_tasks: install.yml + tags: + - "role::nginx" + - "role::nginx:install" + +- ansible.builtin.import_tasks: config.yml + tags: + - "role::nginx" + - "role::nginx:config" diff --git a/roles/nginx/templates/etc/nginx/dh_param b/roles/nginx/templates/etc/nginx/dh_param new file mode 100644 index 0000000..088f967 --- /dev/null +++ b/roles/nginx/templates/etc/nginx/dh_param @@ -0,0 +1,8 @@ +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz ++8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a +87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 +YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi +7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD +ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== +-----END DH PARAMETERS----- \ No newline at end of file diff --git a/roles/nginx/templates/etc/nginx/nginx.conf.j2 b/roles/nginx/templates/etc/nginx/nginx.conf.j2 new file mode 100644 index 0000000..e27a83f --- /dev/null +++ b/roles/nginx/templates/etc/nginx/nginx.conf.j2 @@ -0,0 +1,65 @@ +{{ ansible_managed | comment }} + +user www-data; +worker_processes auto; +pid /run/nginx.pid; +include /etc/nginx/modules-enabled/*.conf; + +events { + worker_connections 768; + # multi_accept on; +} + +http { + + ## + # Basic Settings + ## + + sendfile on; + tcp_nopush on; + types_hash_max_size 2048; + # server_tokens off; + + # server_names_hash_bucket_size 64; + # server_name_in_redirect off; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + ## + # SSL Settings + ## + + ssl_dhparam /etc/nginx/dh_param; + ssl_protocols {{ nginx_ssl_protocols }}; + ssl_ciphers {{ nginx_ssl_ciphers }}; + ssl_prefer_server_ciphers {{ nginx_ssl_prefer_server_ciphers }}; + + ## + # Logging Settings + ## + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + ## + # Gzip Settings + ## + + gzip on; + + # gzip_vary on; + # gzip_proxied any; + # gzip_comp_level 6; + # gzip_buffers 16 8k; + # gzip_http_version 1.1; + # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + ## + # Virtual Host Configs + ## + + include /etc/nginx/conf.d/*.conf; + include /etc/nginx/sites-enabled/*; +} diff --git a/roles/nginx/templates/etc/nginx/sites-available/site.conf.j2 b/roles/nginx/templates/etc/nginx/sites-available/site.conf.j2 new file mode 100644 index 0000000..071b219 --- /dev/null +++ b/roles/nginx/templates/etc/nginx/sites-available/site.conf.j2 @@ -0,0 +1,67 @@ +{{ ansible_managed | comment }} + +{% if site.http_enabled | default(nginx_vhost_http_enabled) %} +server { + listen 80{% if site.default_server | default(false) %} default_server{% endif %}; + listen [::]:80{% if site.default_server | default(false) %} default_server{% endif %}; + + server_name {{ name }}{% for alias in site.aliases | default(nginx_vhost_serveraliases) %} {{ alias }}{% endfor %}; + +{% if site.http_redirect_to_https | default(nginx_vhost_http_redirect_to_https) %} + location / { + return 301 https://$host$request_uri; + } + +{% else %} + root {{ site.documentroot | default(nginx_vhost_documentroot) }}; + +{% if site.php_proxy is defined %} + location ~ \.php$ { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass {{ site.php_proxy }}; + fastcgi_index index.php; + include fastcgi_params; + } +{% endif %} + + {{ site.additional_config | indent(4) }} + +{% endif %} + +} +{% endif %} + + +{% if site.https_enabled | default(nginx_vhost_https_enabled) %} +server { + listen 443 ssl http2{% if site.default_server | default(false) %} default_server{% endif %}; + listen [::]:443 ssl http2{% if site.default_server | default(false) %} default_server{% endif %}; + + server_name {{ name }}{% for alias in site.aliases | default(nginx_vhost_serveraliases) %} {{ alias }}{% endfor %}; + +{% if certfile_exists and keyfile_exists %} + ssl_certificate {{ site.tls_certfile | default(nginx_tls_certfile) }}; + ssl_certificate_key {{ site.tls_keyfile | default(nginx_tls_keyfile) }}; +{% else %} + ssl_certificate {{ nginx_tls_noacme_fallback_certfile }}; + ssl_certificate_key {{ nginx_tls_noacme_fallback_keyfile }}; +{% endif %} + ssl_session_timeout 1d; + ssl_session_cache shared:MozSSL:10m; # about 40000 sessions + ssl_session_tickets off; + + root {{ site.documentroot | default(nginx_vhost_documentroot) }}; + +{% if site.php_proxy is defined %} + location ~ \.php$ { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass {{ site.php_proxy }}; + fastcgi_index index.php; + include fastcgi_params; + } +{% endif %} + + {{ site.additional_config | indent(4) }} + +} +{% endif %}