aboutsummaryrefslogtreecommitdiff
path: root/content/blog
diff options
context:
space:
mode:
authorJulien Dessaux2024-10-28 13:10:03 +0100
committerJulien Dessaux2024-10-28 13:10:03 +0100
commit382e09e4f80604db00944410f59733864388d4f0 (patch)
tree7f1bdd88f7d3bcea634309d7a63b6d35b6a3abbc /content/blog
parentchore(deps): updated dependencies (diff)
downloadwww-382e09e4f80604db00944410f59733864388d4f0.tar.gz
www-382e09e4f80604db00944410f59733864388d4f0.tar.bz2
www-382e09e4f80604db00944410f59733864388d4f0.zip
Add nginx ansible role blog article
Diffstat (limited to '')
-rw-r--r--content/blog/ansible/nginx-ansible-role.md336
-rw-r--r--content/blog/ansible/postgresql-ansible-role.md2
2 files changed, 337 insertions, 1 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!
diff --git a/content/blog/ansible/postgresql-ansible-role.md b/content/blog/ansible/postgresql-ansible-role.md
index 848e206..02614c0 100644
--- a/content/blog/ansible/postgresql-ansible-role.md
+++ b/content/blog/ansible/postgresql-ansible-role.md
@@ -224,7 +224,7 @@ I do not call the role from a playbook, I prefer running the setup from an appli
``` yaml
---
dependencies:
- - role: 'borg
+ - role: 'borg'
- role: 'postgresql'
```