From ccb3688a8af1843f86b7ca780ead5ebc4aa510b9 Mon Sep 17 00:00:00 2001 From: L3D Date: Sat, 27 Feb 2021 00:46:37 +0100 Subject: [PATCH] try to merge with r3l --- .gitignore | 1 + defaults/main.yml | 50 +++---- .../sites-available/default_http.conf.j2 | 13 ++ .../nginx/sites-available/default_tls.conf.j2 | 16 +++ .../vhost_http_redirect.conf.j2 | 14 ++ files/nginx/sites-available/vhost_tls.conf.j2 | 14 ++ ...et.conf => _certificate_site.snippet.conf} | 0 .../nginx/snippets/_logging_site.snippet.conf | 4 + ...ppet.conf => acmetool_global.snippet.conf} | 0 .../header-csp-static_global.snippet.conf | 2 + .../snippets/header-hsts_global.snippet.conf | 2 + ...onf => tls-parameters_global.snippet.conf} | 0 readme.md | 123 ++++++++++++++++++ tasks/default_site.yml | 19 ++- tasks/installation.yml | 6 +- tasks/main.yml | 5 +- tasks/nginx.yml | 15 ++- tasks/single_site.yml | 39 ++++-- .../nginx/sites-available/default_http.j2 | 2 +- .../nginx/sites-available/default_tls.j2 | 2 +- .../http_plain_redirect.conf.j2 | 4 +- 21 files changed, 275 insertions(+), 56 deletions(-) create mode 100644 .gitignore create mode 100644 files/nginx/sites-available/default_http.conf.j2 create mode 100644 files/nginx/sites-available/default_tls.conf.j2 create mode 100644 files/nginx/sites-available/vhost_http_redirect.conf.j2 create mode 100644 files/nginx/sites-available/vhost_tls.conf.j2 rename files/nginx/snippets/{tls_certificate.snippet.conf => _certificate_site.snippet.conf} (100%) create mode 100644 files/nginx/snippets/_logging_site.snippet.conf rename files/nginx/snippets/{acmetool.snippet.conf => acmetool_global.snippet.conf} (100%) create mode 100644 files/nginx/snippets/header-csp-static_global.snippet.conf create mode 100644 files/nginx/snippets/header-hsts_global.snippet.conf rename files/nginx/snippets/{tls_parameters.snippet.conf => tls-parameters_global.snippet.conf} (100%) create mode 100644 readme.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b25c15b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*~ diff --git a/defaults/main.yml b/defaults/main.yml index 891e522..b38ca39 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -1,40 +1,30 @@ --- # enable version check for this role? (true is recomended) submodules_versioncheck: false +nginx__packages: + - 'nginx' + +# Length of DH parameters +nginx__dhparam_size: 2048 + + +# Configuration of virtual hosts nginx_sites: {} -# nginx_sites: -# - name: 'example.org' # required -# altnames: # Optional alternative names +#nginx_sites: +# - name: 'example.org' +# altnames: Optional, for acmetool # - 'www.example.org' # - 'ftp.example.org' -# logging: false # Optional enable nginx logging -# robots: 'robots_allow_all.txt' # Optional, unimplemented -# htaccess: 'htpasswd.example.org' # Optional, unimplemented -# webroot: # Optional, for use with 'webhost' role -# path # Optional, for use with 'webhost' role -# user # Optional, for use with 'webhost' role -# group # Optional, for use with 'webhost' role -# mode # Optional, for use with 'webhost' role - -nginx__snippet_path: 'files/nginx/snippets/' -nginx__snippet_files: - - 'acmetool.snippet.conf' - - 'tls_parameters.snippet.conf' +# robots: 'robots_allow_all.txt' Optional, unimplemented +# htaccess: 'htpasswd.example.org' Optional, unimplemented +# webroot: Optional, for use with 'webhost' role +# path Optional, for use with 'webhost' role +# user Optional, for use with 'webhost' role +# group Optional, for use with 'webhost' role +# mode Optional, for use with 'webhost' role -# default_robots_file: 'robots_disallow_all.txt' - -# nginx logging default for all sites -nginx__default_enable_logging: false - -nginx__dhparam_size: 4096 - -nxinx__state: 'present' - -# disable it if you do not want a autogenerated infrastructure domain config -nginx__infrastructure_domain__enabled: true - -# disable this variable if you don't want to use our acmetool role to manage tls certificates -nginx__acmetool_enabled: true +# Optionally disable acme support within this role +nginx__disable_acmetool: false diff --git a/files/nginx/sites-available/default_http.conf.j2 b/files/nginx/sites-available/default_http.conf.j2 new file mode 100644 index 0000000..e5c2129 --- /dev/null +++ b/files/nginx/sites-available/default_http.conf.j2 @@ -0,0 +1,13 @@ +server { + listen 80 default_server; + listen [::]:80 default_server; + + access_log /var/log/nginx/log_{{ inventory_hostname }}.access.log; + error_log /var/log/nginx/log_{{ inventory_hostname }}.error.log; + + include snippets/acmetool_global.snippet.conf; + + location ^~ / { + return 403; + } +} diff --git a/files/nginx/sites-available/default_tls.conf.j2 b/files/nginx/sites-available/default_tls.conf.j2 new file mode 100644 index 0000000..00ed105 --- /dev/null +++ b/files/nginx/sites-available/default_tls.conf.j2 @@ -0,0 +1,16 @@ +server { + listen 443 ssl http2 default_server; + listen [::]:443 ssl http2 default_server; + + include snippets/tls-parameters_global.snippet.conf; + + ssl_certificate /var/lib/acme/live/{{ inventory_hostname }}/fullchain; + ssl_certificate_key /var/lib/acme/live/{{ inventory_hostname }}/privkey; + + access_log /var/log/nginx/log_{{ inventory_hostname }}.access.log; + error_log /var/log/nginx/log_{{ inventory_hostname }}.error.log; + + location ^~ / { + return 403; + } +} diff --git a/files/nginx/sites-available/vhost_http_redirect.conf.j2 b/files/nginx/sites-available/vhost_http_redirect.conf.j2 new file mode 100644 index 0000000..a8b7fc1 --- /dev/null +++ b/files/nginx/sites-available/vhost_http_redirect.conf.j2 @@ -0,0 +1,14 @@ +server { + listen 80; + listen [::]:80; + + server_name {{ site.name }}; + + include snippets/{{ site.name }}_logging_site.snippet.conf; + + include snippets/acmetool_global.snippet.conf; + + location ^~ / { + return 308 https://{{ site.name }}$request_uri; + } +} diff --git a/files/nginx/sites-available/vhost_tls.conf.j2 b/files/nginx/sites-available/vhost_tls.conf.j2 new file mode 100644 index 0000000..cb5725b --- /dev/null +++ b/files/nginx/sites-available/vhost_tls.conf.j2 @@ -0,0 +1,14 @@ +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + + server_name {{ site.name }}; + + include snippets/tls-parameters_global.snippet.conf; + include snippets/{{ site.name }}_certificate_site.snippet.conf; + include snippets/{{ site.name }}_logging_site.snippet.conf; + + location ^~ / { + return 403; + } +} diff --git a/files/nginx/snippets/tls_certificate.snippet.conf b/files/nginx/snippets/_certificate_site.snippet.conf similarity index 100% rename from files/nginx/snippets/tls_certificate.snippet.conf rename to files/nginx/snippets/_certificate_site.snippet.conf diff --git a/files/nginx/snippets/_logging_site.snippet.conf b/files/nginx/snippets/_logging_site.snippet.conf new file mode 100644 index 0000000..18af9a6 --- /dev/null +++ b/files/nginx/snippets/_logging_site.snippet.conf @@ -0,0 +1,4 @@ +error_log /var/log/nginx/log_{{ site.name }}.error.log; + +#access_log /var/log/nginx/log_{{ site.name }}.access.log; +access_log off; diff --git a/files/nginx/snippets/acmetool.snippet.conf b/files/nginx/snippets/acmetool_global.snippet.conf similarity index 100% rename from files/nginx/snippets/acmetool.snippet.conf rename to files/nginx/snippets/acmetool_global.snippet.conf diff --git a/files/nginx/snippets/header-csp-static_global.snippet.conf b/files/nginx/snippets/header-csp-static_global.snippet.conf new file mode 100644 index 0000000..0ed857e --- /dev/null +++ b/files/nginx/snippets/header-csp-static_global.snippet.conf @@ -0,0 +1,2 @@ +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy +add_header Content-Security-Policy "default-src 'self'; object-src 'none'"; diff --git a/files/nginx/snippets/header-hsts_global.snippet.conf b/files/nginx/snippets/header-hsts_global.snippet.conf new file mode 100644 index 0000000..dbfbc5a --- /dev/null +++ b/files/nginx/snippets/header-hsts_global.snippet.conf @@ -0,0 +1,2 @@ +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security +add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; diff --git a/files/nginx/snippets/tls_parameters.snippet.conf b/files/nginx/snippets/tls-parameters_global.snippet.conf similarity index 100% rename from files/nginx/snippets/tls_parameters.snippet.conf rename to files/nginx/snippets/tls-parameters_global.snippet.conf diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..3a6930d --- /dev/null +++ b/readme.md @@ -0,0 +1,123 @@ +Nginx Webserver +=============== + +Ansible role to configure the `nginx` webserver and manage TLS certificates +by the help of the `acmetool` LE client. This role is designed to work together +with the `acmetool` role. + + +Variables +--------- + +* `nginx__dhparam_size` (Default 2048): + The DH parameters bit length. + +* `nginx_sites` (Default `{}`): + The virtual hosts configurations for this webserver. + +* `nginx__disable_acmetool` (Default `False`): + Optionally disable acme support within this role. + + +Note: If you do not intent to use `acmetool` or LE at all, it is possible to disable + support for it. However, in that case *all* TLS certificates and private keys + loaded by the nginx configuration must be present on the destination host *before* + running this role. Additionally you need to provide own replacement templates + for `files/nginx/sites-available/*j2` (see point 3 in the next section). + + +Files +----- + +Note: Path segments `` when resolved by the `hf`/`hfg` lookup plugins always + include full `hf`/`hfg` functionality, for example also search the `group_files` + directory as apropriate. (See also note on dependencies below.) + + +* Main `nginx` configuration file template `nginx/nginx.conf` + Lookup path: + - ` / files / / / nginx / nginx.conf` [via `hf` lookup] + - ` / files / nginx / nginx.conf` [via `hf` lookup] + - ` / files / nginx / nginx.conf` [default role fallback] + + +* Global (default and vhost independent) configuration snippets + Lookup path: + - ` / files / / / nginx / snippets / _global.snippet.conf` [via `hfg` lookup] + - ` / files / nginx / snippets / _global.snippet.conf` [via `hfg` lookup] + - ` / files / nginx / snippets / _global.snippet.conf` [default role fallback] + Note: The `` may not contain a `_`. + + +* Main configuration file for each virtual host (usually contains corresponding nginx `server` block) + Lookup path (tls): + - ` / files / nginx / sites / _tls.conf` + - ` / files / nginx / sites-available / vhost_tls.conf.j2` [default role fallback] + Lookup path (http): + - ` / files / nginx / sites / _http.conf` [via `first_found` lookup] + - ` / files / nginx / sites-available / vhost_http_redirect.conf.j2` [default role fallback] + + +* Per virtual host templated snippets + Lookup path: + - ` / files / / / nginx / snippets / __site.snippet.conf` [via `hfg` lookup] + - ` / files / nginx / snippets / __site.snippet.conf` [via `hfg` lookup] + - ` / files / nginx / snippets / __site.snippet.conf` [default role fallback] + Note 1: The file name is expanded on the server per each virtual host to `__site.snippet.conf`. + Note 2: The `` may not contain a `_`. + + +* Per virtual host custom individual snippet files + Lookup path: + - ` / files / / / nginx / snippets / __site.snippet.conf` [via `hfg` lookup] + - ` / files / nginx / snippets / __site.snippet.conf` [via `hfg` lookup] + - ` / files / nginx / snippets / __site.snippet.conf` [default role fallback] + Note 1: In general, content of such snippets could be merged with main vhost configuration file. + Note 2: The `` may not contain a `_`. + + +* Per virtual host basic auth file + Lookup path: + - unimplemented + +* Per virtual host robots file + Lookup path: + - unimplemented + + +Example +------- + +Configuration of the virtual hosts in the `host_vars` of the webserver: + +``` +nginx_sites: + - name: 'example.org' + altnames: Optional, for acmetool + - 'www.example.org' + - 'ftp.example.org' + robots: 'robots_allow_all.txt' Optional, unimplemented + htaccess: 'htpasswd.example.org' Optional, unimplemented + webroot: Optional, for use with 'webhost' role + path Optional, for use with 'webhost' role + user Optional, for use with 'webhost' role + group Optional, for use with 'webhost' role + mode Optional, for use with 'webhost' role +``` + +Alternatively, put this data into a suitable `group_vars` file. + + +Dependencies +------------ + +This role depends on the `host_file` (`hf`) and `host_files_glob` (`hfg`) lookup plugins. + + +References +---------- + +* [Nginx documentation](https://nginx.org/en/docs/) + +* [acmetool](https://github.com/hlandau/acmetool) +* [acmetool user's guide](https://hlandau.github.io/acmetool/userguide) diff --git a/tasks/default_site.yml b/tasks/default_site.yml index e0ef4f7..eb3b25d 100644 --- a/tasks/default_site.yml +++ b/tasks/default_site.yml @@ -2,22 +2,31 @@ - name: Create default site plain http configuration become: true ansible.builtin.template: - src: 'templates/nginx/sites-available/default_http.j2' + src: '{{ item }}' dest: '/etc/nginx/sites-available/{{ inventory_hostname }}_http' owner: root group: root mode: 'u=rw,g=r,o=r' + with_first_found: + - files: + - 'files/nginx/sites/default_http.conf' + - 'files/nginx/sites-available/default_http.conf.j2' notify: - systemctl reload nginx - name: Create default site tls https configuration become: true ansible.builtin.template: - src: 'templates/nginx/sites-available/default_tls.j2' + template: + src: '{{ item }}' dest: '/etc/nginx/sites-available/{{ inventory_hostname }}_tls' owner: root group: root mode: 'u=rw,g=r,o=r' + with_first_found: + - files: + - 'files/nginx/sites/default_tls.conf' + - 'files/nginx/sites-available/default_tls.conf.j2' notify: - systemctl reload nginx @@ -39,4 +48,8 @@ state: link notify: - systemctl reload nginx - when: not nginx__acmetool_enabled + when: nginx__disable_acmetool + tags: + - configuration + - nginx + - sites diff --git a/tasks/installation.yml b/tasks/installation.yml index c29dae8..898fe04 100644 --- a/tasks/installation.yml +++ b/tasks/installation.yml @@ -11,5 +11,7 @@ become: true ansible.builtin.package: name: - - 'nginx' - state: "{{ nxinx__state }}" + package: '{{ nginx__packages }}' + state: present + tags: + - installation diff --git a/tasks/main.yml b/tasks/main.yml index 5cc1960..111b782 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -31,4 +31,7 @@ - name: Configure acmetool and obtain certificates ansible.builtin.include_tasks: acme.yml - when: nginx__acmetool_enabled + when: not nginx__disable_acmetool + tags: + - configuration + - acme diff --git a/tasks/nginx.yml b/tasks/nginx.yml index 6fec803..d3c8a65 100644 --- a/tasks/nginx.yml +++ b/tasks/nginx.yml @@ -2,7 +2,9 @@ - name: Copy main nginx configuration file become: true ansible.builtin.copy: - src: 'nginx/nginx.conf' + copy: + #src: 'files/nginx/nginx.conf' + src: '{{ lookup("hf", "nginx/nginx.conf") }}' dest: '/etc/nginx/' owner: root group: root @@ -64,14 +66,13 @@ group: root mode: 'u=rwx,g=rx,o=rx' -- name: Copy nginx snippet files - become: true - ansible.builtin.copy: - src: '{{ nginx__snippet_path }}{{ item }}' - dest: '/etc/nginx/snippets/{{ item }}' +- name: Copy nginx global configuration snippet files + copy: + src: '{{ item }}' + dest: '/etc/nginx/snippets/{{ item | basename }}' owner: root group: root mode: 'u=rw,g=r,o=r' - with_items: '{{ nginx__snippet_files }}' + with_items: "{{ lookup('hfg', 'nginx/snippets/[!_]*_global.snippet.conf', wantlist=True) }}" notify: - systemctl reload nginx diff --git a/tasks/single_site.yml b/tasks/single_site.yml index 25fa406..d44043b 100644 --- a/tasks/single_site.yml +++ b/tasks/single_site.yml @@ -2,44 +2,56 @@ - name: Create '{{ site.name }}' site plain http configuration become: true ansible.builtin.template: - src: 'templates/nginx/sites-available/http_plain_redirect.conf.j2' + src: '{{ item }}' dest: '/etc/nginx/sites-available/{{ site.name }}_http' owner: root group: root mode: 'u=rw,g=r,o=r' + with_first_found: + - files: + - 'files/nginx/sites/{{ site.name }}_http.conf' + - 'files/nginx/sites-available/vhost_http_redirect.conf.j2' notify: - systemctl reload nginx - name: Create '{{ site.name }}' site tls https configuration become: true ansible.builtin.template: - src: 'files/nginx/sites/{{ site.name }}_tls.conf' + template: + src: '{{ item }}' dest: '/etc/nginx/sites-available/{{ site.name }}_tls' owner: root group: root mode: 'u=rw,g=r,o=r' + with_first_found: + - files: + - 'files/nginx/sites/{{ site.name }}_tls.conf' + - 'files/nginx/sites-available/vhost_tls.conf.j2' notify: - systemctl reload nginx - name: Create '{{ site.name }}' site tls parameter configuration become: true ansible.builtin.template: - src: 'files/nginx/snippets/tls_parameters.snippet.conf' - dest: '/etc/nginx/snippets/tls_parameters_{{ site.name }}.snippet.conf' + src: '{{ item }}' + dest: '/etc/nginx/snippets/{{ site.name }}{{ item | basename }}' owner: root group: root mode: 'u=rw,g=r,o=r' + with_items: "{{ lookup('hfg', 'nginx/snippets/_*_site.snippet.conf', wantlist=True) }}" notify: - systemctl reload nginx - name: Create '{{ site.name }}' site tls certificate configuration become: true ansible.builtin.template: - src: 'files/nginx/snippets/tls_certificate.snippet.conf' - dest: '/etc/nginx/snippets/tls_certificate_{{ site.name }}.snippet.conf' + template: + src: '{{ item }}' + dest: '/etc/nginx/snippets/{{ item | basename }}' owner: root group: root mode: 'u=rw,g=r,o=r' + with_items: "{{ lookup('hfg', 'nginx/snippets/' + site.name + '_*_site.snippet.conf', wantlist=True) }}" notify: - systemctl reload nginx @@ -57,14 +69,19 @@ - name: Enable '{{ site.name }}' site plain http configuration become: true ansible.builtin.file: + file: src: '/etc/nginx/sites-available/{{ site.name }}_http' dest: '/etc/nginx/sites-enabled/{{ site.name }}_http' state: link - when: site.http_plain_template | default(True) notify: - systemctl reload nginx + tags: + - configuration + - nginx + - sites -# Note: done by acmetool after sucessfully obtaining a suitable certificate + +# Note: Normally done by acmetool after sucessfully obtaining a suitable certificate - name: Enable '{{ site.name }}' site tls configuration become: true ansible.builtin.file: @@ -73,4 +90,8 @@ state: link notify: - systemctl reload nginx - when: not nginx__acmetool_enabled + when: nginx__disable_acmetool + tags: + - configuration + - nginx + - sites diff --git a/templates/nginx/sites-available/default_http.j2 b/templates/nginx/sites-available/default_http.j2 index 5509087..e5c2129 100644 --- a/templates/nginx/sites-available/default_http.j2 +++ b/templates/nginx/sites-available/default_http.j2 @@ -5,7 +5,7 @@ server { access_log /var/log/nginx/log_{{ inventory_hostname }}.access.log; error_log /var/log/nginx/log_{{ inventory_hostname }}.error.log; - include snippets/acmetool.snippet.conf; + include snippets/acmetool_global.snippet.conf; location ^~ / { return 403; diff --git a/templates/nginx/sites-available/default_tls.j2 b/templates/nginx/sites-available/default_tls.j2 index 6f80d83..00ed105 100644 --- a/templates/nginx/sites-available/default_tls.j2 +++ b/templates/nginx/sites-available/default_tls.j2 @@ -2,7 +2,7 @@ server { listen 443 ssl http2 default_server; listen [::]:443 ssl http2 default_server; - include snippets/tls_parameters.snippet.conf; + include snippets/tls-parameters_global.snippet.conf; ssl_certificate /var/lib/acme/live/{{ inventory_hostname }}/fullchain; ssl_certificate_key /var/lib/acme/live/{{ inventory_hostname }}/privkey; diff --git a/templates/nginx/sites-available/http_plain_redirect.conf.j2 b/templates/nginx/sites-available/http_plain_redirect.conf.j2 index 9093f43..a8b7fc1 100644 --- a/templates/nginx/sites-available/http_plain_redirect.conf.j2 +++ b/templates/nginx/sites-available/http_plain_redirect.conf.j2 @@ -4,9 +4,9 @@ server { server_name {{ site.name }}; - include snippets/logging_{{ site.name }}.snippet.conf; + include snippets/{{ site.name }}_logging_site.snippet.conf; - include snippets/acmetool.snippet.conf; + include snippets/acmetool_global.snippet.conf; location ^~ / { return 308 https://{{ site.name }}$request_uri;