diff options
author | Julien Dessaux | 2024-10-28 13:10:03 +0100 |
---|---|---|
committer | Julien Dessaux | 2024-10-28 13:10:03 +0100 |
commit | 382e09e4f80604db00944410f59733864388d4f0 (patch) | |
tree | 7f1bdd88f7d3bcea634309d7a63b6d35b6a3abbc /content/blog/ansible/nginx-ansible-role.md | |
parent | chore(deps): updated dependencies (diff) | |
download | www-master.tar.gz www-master.tar.bz2 www-master.zip |
Diffstat (limited to 'content/blog/ansible/nginx-ansible-role.md')
-rw-r--r-- | content/blog/ansible/nginx-ansible-role.md | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/content/blog/ansible/nginx-ansible-role.md b/content/blog/ansible/nginx-ansible-role.md new file mode 100644 index 0000000..0c465a9 --- /dev/null +++ b/content/blog/ansible/nginx-ansible-role.md @@ -0,0 +1,336 @@ +--- +title: 'Nginx ansible role' +description: 'The ansible role I use to manage my nginx web servers' +date: '2024-10-28' +tags: +- ansible +- nginx +--- + +## Introduction + +Before succumbing to nixos, I had been using an ansible role to manage my nginx web servers. Now that I am in need of it again I refined it a bit: here is the result. + +## The role + +### Vars + +The role has OS specific vars in files named after the operating system. For example in `vars/Debian.yaml` I have: + +``` yaml +--- +nginx: + etc_dir: '/etc/nginx' + pid_file: '/run/nginx.pid' + www_user: 'www-data' +``` + +While in `vars/FreeBSD.yaml` I have: + +``` yaml +--- +nginx: + etc_dir: '/usr/local/etc/nginx' + pid_file: '/var/run/nginx.pid' + www_user: 'www' +``` + +### Tasks + +The main tasks file setups nginx and the global configuration common to all virtual hosts: + +``` yaml +--- +- include_vars: '{{ ansible_distribution }}.yaml' + +- name: 'Install nginx' + package: + name: + - 'nginx' + +- name: 'Make nginx vhost directory' + file: + path: '{{ nginx.etc_dir }}/vhost.d' + mode: '0755' + owner: 'root' + state: 'directory' + +- name: 'Deploy nginx configuration files' + copy: + src: '{{ item }}' + dest: '{{ nginx.etc_dir }}/{{ item }}' + notify: 'reload nginx' + loop: + - 'headers_base.conf' + - 'headers_secure.conf' + - 'headers_static.conf' + - 'headers_unsafe_inline_csp.conf' + +- name: 'Deploy nginx configuration template' + template: + src: 'nginx.conf' + dest: '{{ nginx.etc_dir }}/' + notify: 'reload nginx' + +- name: 'Deploy nginx certificates' + copy: + src: '{{ item }}' + dest: '{{ nginx.etc_dir }}/' + notify: 'reload nginx' + loop: + - 'adyxax.org.fullchain' + - 'adyxax.org.key' + - 'dh4096.pem' + +- name: 'Start nginx and activate it on boot' + service: + name: 'nginx' + enabled: true + state: 'started' +``` + +I have a `vhost.yaml` task file which currently simply deploys a file and reload nginx: + +``` yaml +- name: 'Deploy {{ vhost.name }} vhost {{ vhost.path }}' + template: + src: '{{ vhost.path }}' + dest: '{{ nginx.etc_dir }}/vhost.d/{{ vhost.name }}.conf' + notify: 'reload nginx' +``` + +### Handlers + +There is a single `main.yaml` handler: + +``` yaml +--- +- name: 'reload nginx' + service: + name: 'nginx' + state: 'reloaded' +``` + +### Files + +I deploy four configuration files in this role. These are all variants of the same theme and their purpose is just to prevent duplicating statements in the virtual hosts configuration files. + +`headers_base.conf`: + +``` nginx +############################################################################### +# \_o< WARNING : This file is being managed by ansible! >o_/ # +# ~~~~ ~~~~ # +############################################################################### + +add_header X-Frame-Options deny; +add_header X-XSS-Protection "1; mode=block"; +add_header X-Content-Type-Options nosniff; +add_header Referrer-Policy strict-origin; +add_header Cache-Control no-transform; +add_header Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"; +# 6 months HSTS pinning +add_header Strict-Transport-Security max-age=16000000; +``` + +`headers_secure.conf`: + +``` nginx +############################################################################### +# \_o< WARNING : This file is being managed by ansible! >o_/ # +# ~~~~ ~~~~ # +############################################################################### + +include headers_base.conf; +add_header Content-Security-Policy "script-src 'self'"; +``` + +`headers_static.conf`: + +``` nginx +############################################################################### +# \_o< WARNING : This file is being managed by ansible! >o_/ # +# ~~~~ ~~~~ # +############################################################################### + +include headers_secure.conf; +# Infinite caching +add_header Cache-Control "public, max-age=31536000, immutable"; +``` + +`headers_unsafe_inline_csp.conf`: + +``` nginx +############################################################################### +# \_o< WARNING : This file is being managed by ansible! >o_/ # +# ~~~~ ~~~~ # +############################################################################### + +include headers_base.conf; +add_header Content-Security-Policy "script-src 'self' 'unsafe-inline'"; +``` + +### Templates + +I have a single template for `nginx.conf`: + +``` nginx +############################################################################### +# \_o< WARNING : This file is being managed by ansible! >o_/ # +# ~~~~ ~~~~ # +############################################################################### + +user {{ nginx.www_user }}; +worker_processes auto; +pid {{ nginx.pid_file }}; +error_log /var/log/nginx/error.log; + +events { + worker_connections 1024; +} + +http { + include mime.types; + types_hash_max_size 4096; + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + + ssl_protocols TLSv1.2 TLSv1.3; + 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; + ssl_prefer_server_ciphers on; + + gzip on; + gzip_static on; + gzip_vary on; + gzip_comp_level 5; + gzip_min_length 256; + gzip_proxied expired no-cache no-store private auth; + gzip_types application/atom+xml application/geo+json application/javascript application/json application/ld+json application/manifest+json application/rdf+xml application/vnd.ms-fontobject application/wasm application/x-rss+xml application/x-web-app-manifest+json application/xhtml+xml application/xliff+xml application/xml font/collection font/otf font/ttf image/bmp image/svg+xml image/vnd.microsoft.icon text/cache-manifest text/calendar text/css text/csv text/javascript text/markdown text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/xml; + + proxy_redirect off; + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + proxy_http_version 1.1; + proxy_set_header "Connection" ""; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Server $host; + + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + + client_max_body_size 40M; + server_tokens off; + default_type application/octet-stream; + access_log /var/log/nginx/access.log; + + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param QUERY_STRING $query_string; + fastcgi_param REQUEST_METHOD $request_method; + fastcgi_param CONTENT_TYPE $content_type; + fastcgi_param CONTENT_LENGTH $content_length; + + fastcgi_param SCRIPT_NAME $fastcgi_script_name; + fastcgi_param REQUEST_URI $request_uri; + fastcgi_param DOCUMENT_URI $document_uri; + fastcgi_param DOCUMENT_ROOT $document_root; + fastcgi_param SERVER_PROTOCOL $server_protocol; + fastcgi_param REQUEST_SCHEME $scheme; + fastcgi_param HTTPS $https if_not_empty; + + fastcgi_param GATEWAY_INTERFACE CGI/1.1; + fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; + + fastcgi_param REMOTE_ADDR $remote_addr; + fastcgi_param REMOTE_PORT $remote_port; + fastcgi_param REMOTE_USER $remote_user; + fastcgi_param SERVER_ADDR $server_addr; + fastcgi_param SERVER_PORT $server_port; + fastcgi_param SERVER_NAME $server_name; + + # PHP only, required if PHP was built with --enable-force-cgi-redirect + fastcgi_param REDIRECT_STATUS 200; + + uwsgi_param QUERY_STRING $query_string; + uwsgi_param REQUEST_METHOD $request_method; + uwsgi_param CONTENT_TYPE $content_type; + uwsgi_param CONTENT_LENGTH $content_length; + + uwsgi_param REQUEST_URI $request_uri; + uwsgi_param PATH_INFO $document_uri; + uwsgi_param DOCUMENT_ROOT $document_root; + uwsgi_param SERVER_PROTOCOL $server_protocol; + uwsgi_param REQUEST_SCHEME $scheme; + uwsgi_param HTTPS $https if_not_empty; + + uwsgi_param REMOTE_ADDR $remote_addr; + uwsgi_param REMOTE_PORT $remote_port; + uwsgi_param SERVER_PORT $server_port; + uwsgi_param SERVER_NAME $server_name; + + ssl_dhparam dh4096.pem; + ssl_session_cache shared:SSL:2m; + ssl_session_timeout 1h; + ssl_session_tickets off; + + server { + listen 80 default_server; + listen [::]:80 default_server; + server_name _; + access_log off; + server_name_in_redirect off; + return 444; + } + + server { + listen 443 ssl; + listen [::]:443 ssl; + server_name _; + access_log off; + server_name_in_redirect off; + return 444; + ssl_certificate adyxax.org.fullchain; + ssl_certificate_key adyxax.org.key; + } + + include vhost.d/*.conf; +} +``` + +## Usage example + +I do not call the role from a playbook, I prefer running the setup from an application's role that relies on nginx using a `meta/main.yaml` containing something like: + +``` yaml +--- +dependencies: + - role: 'borg' + - role: 'nginx' + - role: 'postgresql' +``` + +Then from a tasks file: + +``` yaml +- include_role: + name: 'nginx' + tasks_from: 'vhost' + vars: + vhost: + name: 'www' + path: 'roles/www.adyxax.org/files/nginx-vhost.conf' +``` + +I did not find an elegant way to pass a file path local to one role to another. Because of that, here I just specify the full vhost file path complete with the `roles/` prefix. + +### Conclusion + +I you have an elegant idea for passing the local file path from one role to another do not hesitate to ping me! |