ansible-taler-exchange

Ansible playbook to deploy a production Taler Exchange
Log | Files | Refs | Submodules | README | LICENSE

commit b22bf069faa3982c602752b82e747ab3343bd126
parent 82a4c089a0f8d886b8392b06b25a5a2390eef9cb
Author: Christian Grothoff <christian@grothoff.org>
Date:   Mon, 16 Dec 2024 01:41:51 +0100

improve nginx deployment, letsencrypt setups

Diffstat:
MREADME | 15++++++++++++++-
MTODO | 9+++++----
Mplaybooks/setup.yml | 1+
Mplaybooks/test-secrets.yml | 3+++
Mroles/auditor/tasks/main.yml | 26+++++++++++++++++++++++++-
Aroles/auditor/templates/etc/nginx/sites-available/auditor-http.conf.j2 | 19+++++++++++++++++++
Mroles/auditor/templates/etc/nginx/sites-available/auditor-nginx.conf.j2 | 28+++-------------------------
Mroles/challenger/tasks/main.yml | 64+++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Aroles/challenger/templates/etc/nginx/sites-available/email-challenger-http.conf.j2 | 20++++++++++++++++++++
Mroles/challenger/templates/etc/nginx/sites-available/email-challenger-nginx.conf.j2 | 29+++--------------------------
Aroles/challenger/templates/etc/nginx/sites-available/postal-challenger-http.conf.j2 | 19+++++++++++++++++++
Droles/challenger/templates/etc/nginx/sites-available/postal-challenger-nginx.conf | 58----------------------------------------------------------
Aroles/challenger/templates/etc/nginx/sites-available/postal-challenger-nginx.conf.j2 | 32++++++++++++++++++++++++++++++++
Aroles/challenger/templates/etc/nginx/sites-available/sms-challenger-http.conf.j2 | 20++++++++++++++++++++
Mroles/challenger/templates/etc/nginx/sites-available/sms-challenger-nginx.conf.j2 | 30++----------------------------
Mroles/common_packages/tasks/main.yml | 7+++++++
Mroles/exchange/tasks/main.yml | 23+++++++++++++++++++----
Aroles/exchange/templates/etc/nginx/sites-available/exchange-http.conf.j2 | 19+++++++++++++++++++
Mroles/exchange/templates/etc/nginx/sites-available/exchange-nginx.conf.j2 | 29+++--------------------------
Aroles/monitoring/tasks/main.yml | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aroles/monitoring/templates/etc/nginx/sites-available/monitoring-http.conf.j2 | 19+++++++++++++++++++
Aroles/monitoring/templates/etc/nginx/sites-available/monitoring-nginx.conf.j2 | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aroles/webserver/files/etc/nginx/conf.d/log-format-apm.conf | 9+++++++++
Mroles/webserver/tasks/main.yml | 8++++++++
24 files changed, 437 insertions(+), 184 deletions(-)

diff --git a/README b/README @@ -1,11 +1,24 @@ # Ansible Taler Playbooks +## Installing dependencies + +First, install the prometheus collection using: + +$ ansible-galaxy collection install prometheus.prometheus + + ## Running the Playbook To run the main playbook (playbooks/setup.yml): ``` -$ ansible-playbook --verbose --inventory <host>, --user root playbooks/setup.yml --extra-vars "@playbooks/test-secrets.yml" +$ ansible-playbook --verbose --inventory <host> --user root playbooks/setup.yml --extra-vars "@playbooks/test-secrets.yml" +``` + +For example, if you are root@taler-ops.ch, you may be able to: + +``` +$ ansible-playbook --verbose --inventory inventories/tops --user root playbooks/setup.yml --extra-vars "@playbooks/test-secrets.yml" ``` Instead of specifying the host(s) as arguments (note the trailing comma!) diff --git a/TODO b/TODO @@ -2,11 +2,12 @@ - setup postfix role (needed for email-challenger) => https://github.com/FoxyRoles/ansible-dkim seems about right! -@TBD (#9352) -- setup prometheus-nginx-exporter, postgres-exporter, node-exporter, - systemd_exporter, possibly alertmanager +@TBD (#9352)2 +- postgres prometheus exporter setup is incomplete: postgres part (access!) is missing - setup loki log aggregator -- setup HTTPS reverse proxy for the above, limit access using basic auth! +- setup HTTPS reverse proxy loki +- check limit access using basic auth to prometheus exporters + => right now uses Bearer token. Is that OK? ---- @LATER: diff --git a/playbooks/setup.yml b/playbooks/setup.yml @@ -9,6 +9,7 @@ - libeufin-nexus - exchange - auditor + - monitoring # Note that we ONLY define those variables here that are NOT # secrets. For secrets, test-secrets.yml contains a template. vars: diff --git a/playbooks/test-secrets.yml b/playbooks/test-secrets.yml @@ -21,3 +21,6 @@ EXCHANGE_KYCAID_ACCESS_TOKEN: FIXME # Bearer access token for the auditor AUDITOR_ACCESS_TOKEN: secret-token:FIXME + +# Bearer access token for monitoring +PROMETHEUS_ACCESS_TOKEN: secret-token:FIXME diff --git a/roles/auditor/tasks/main.yml b/roles/auditor/tasks/main.yml @@ -18,7 +18,15 @@ path: "/etc/taler-auditor" state: directory -- name: Ensure Taler exchange virtualhost configuration file exists +- name: Ensure Taler letsencrypt auditor dir exists from installation + file: + path: "/var/www/letsencrypt/auditor.{{ DOMAIN_NAME }}/.well-known/acme-challenge/" + state: directory + owner: www-data + group: www-data + mode: 0755 + +- name: Ensure Taler auditor virtualhost configuration file exists template: src: templates/etc/nginx/sites-available/auditor-nginx.conf.j2 dest: "/etc/nginx/sites-available/auditor-nginx.conf" @@ -27,6 +35,22 @@ mode: 0644 notify: restart nginx +- name: Ensure Taler auditor HTTP virtualhost configuration file exists + template: + src: templates/etc/nginx/sites-available/auditor-http.conf.j2 + dest: "/etc/nginx/sites-available/auditor-http.conf" + owner: root + group: root + mode: 0644 + notify: restart nginx + +- name: Enable Taler HTTP auditor reverse proxy configuration + file: + src: /etc/nginx/sites-available/auditor-http.conf + dest: /etc/nginx/sites-enabled/auditor-http.conf + state: link + notify: restart nginx + - name: Secure the auditor site with Letsencrypt ansible.builtin.include_role: name: geerlingguy.certbot diff --git a/roles/auditor/templates/etc/nginx/sites-available/auditor-http.conf.j2 b/roles/auditor/templates/etc/nginx/sites-available/auditor-http.conf.j2 @@ -0,0 +1,19 @@ +server { + + listen 80; + listen [::]:80; + + server_name auditor.{{ DOMAIN_NAME }}; + + error_log /var/log/nginx/auditor.{{ DOMAIN_NAME }}-http.err; + access_log /var/log/nginx/auditor.{{ DOMAIN_NAME }}-http.log; + + location ^~ /.well-known/acme-challenge/ { + default_type "text/plain"; + root /var/www/letsencrypt/auditor.{{ DOMAIN_NAME }}; + } + + location / { + return 301 https://$host$request_uri; + } +} diff --git a/roles/auditor/templates/etc/nginx/sites-available/auditor-nginx.conf.j2 b/roles/auditor/templates/etc/nginx/sites-available/auditor-nginx.conf.j2 @@ -1,22 +1,5 @@ server { - listen 80; - listen [::]:80; - - server_name auditor.{{ DOMAIN_NAME }}; - - location ^~ /.well-known/acme-challenge/ { - default_type "text/plain"; - root /var/www/letsencrypt/auditor.{{ DOMAIN_NAME }}; - } - - location / { - return 301 https://$host$request_uri; - } -} - -server { - listen 443; listen [::]:443; @@ -41,15 +24,10 @@ server { keepalive_requests 1000000; keepalive_timeout 6500s; - # Redirect non-https traffic to https - if ($scheme != "https") { - return 301 https://$host$request_uri; - } + if ($http_user_agent ~* "Bytedance|bytespider|Amazonbot|Claude|Anthropic|AI|GPT|acebook") { return 451 ; } - location ^~ /.well-known/acme-challenge/ { - default_type "text/plain"; - root /var/www/letsencrypt/email.challenger.{{ DOMAIN_NAME }}; - } + error_log /var/log/nginx/auditor.{{ DOMAIN_NAME }}.err; + access_log /var/log/nginx/auditor.{{ DOMAIN_NAME }}.log apm; location / { # Most of the API we will put behind simple access control for now. diff --git a/roles/challenger/tasks/main.yml b/roles/challenger/tasks/main.yml @@ -225,10 +225,18 @@ group: root mode: 0644 -- name: Enable SMS challenger reverse proxy configuration +- name: Place SMS challenger HTTP Nginx configuration + ansible.builtin.template: + src: templates/etc/nginx/sites-available/sms-challenger-http.conf.j2 + dest: /etc/nginx/sites-available/sms-challenger-http.conf + owner: root + group: root + mode: 0644 + +- name: Enable SMS challenger HTTP reverse proxy configuration file: - src: /etc/nginx/sites-available/sms-challenger-nginx.conf - dest: /etc/nginx/sites-enabled/sms-challenger-nginx.conf + src: /etc/nginx/sites-available/sms-challenger-http.conf + dest: /etc/nginx/sites-enabled/sms-challenger-http.conf state: link notify: restart nginx @@ -240,10 +248,18 @@ group: root mode: 0644 -- name: Enable email challenger reverse proxy configuration +- name: Place email challenger HTTP Nginx configuration + ansible.builtin.template: + src: templates/etc/nginx/sites-available/email-challenger-http.conf.j2 + dest: /etc/nginx/sites-available/email-challenger-http.conf + owner: root + group: root + mode: 0644 + +- name: Enable email challenger HTTP reverse proxy configuration file: - src: /etc/nginx/sites-available/email-challenger-nginx.conf - dest: /etc/nginx/sites-enabled/email-challenger-nginx.conf + src: /etc/nginx/sites-available/email-challenger-http.conf + dest: /etc/nginx/sites-enabled/email-challenger-http.conf state: link notify: restart nginx @@ -255,14 +271,19 @@ group: root mode: 0644 -- name: Enable postal challenger reverse proxy configuration - file: - src: /etc/nginx/sites-available/postal-challenger-nginx.conf - dest: /etc/nginx/sites-enabled/postal-challenger-nginx.conf - state: link +- name: Place postal challenger HTTP Nginx configuration + ansible.builtin.template: + src: templates/etc/nginx/sites-available/postal-challenger-http.conf.j2 + dest: /etc/nginx/sites-available/postal-challenger-http.conf owner: root group: root mode: 0644 + +- name: Enable postal challenger HTTP reverse proxy configuration + file: + src: /etc/nginx/sites-available/postal-challenger-http.conf + dest: /etc/nginx/sites-enabled/postal-challenger-http.conf + state: link notify: restart nginx - name: Secure the SMS challenger site with Letsencrypt @@ -335,3 +356,24 @@ - webroot: "/var/www/letsencrypt/postal.challenger.{{ DOMAIN_NAME }}" domains: - "postal.challenger.{{ DOMAIN_NAME }}" + +- name: Enable SMS challenger reverse proxy configuration + file: + src: /etc/nginx/sites-available/sms-challenger-nginx.conf + dest: /etc/nginx/sites-enabled/sms-challenger-nginx.conf + state: link + notify: restart nginx + +- name: Enable email challenger reverse proxy configuration + file: + src: /etc/nginx/sites-available/email-challenger-nginx.conf + dest: /etc/nginx/sites-enabled/email-challenger-nginx.conf + state: link + notify: restart nginx + +- name: Enable postal challenger reverse proxy configuration + file: + src: /etc/nginx/sites-available/postal-challenger-nginx.conf + dest: /etc/nginx/sites-enabled/postal-challenger-nginx.conf + state: link + notify: restart nginx diff --git a/roles/challenger/templates/etc/nginx/sites-available/email-challenger-http.conf.j2 b/roles/challenger/templates/etc/nginx/sites-available/email-challenger-http.conf.j2 @@ -0,0 +1,20 @@ +server { + + listen 80; + listen [::]:80; + + server_name email.challenger.{{ DOMAIN_NAME }}; + + error_log /var/log/nginx/email.challenger.{{ DOMAIN_NAME }}-http.err; + access_log /var/log/nginx/email.challenger.{{ DOMAIN_NAME }}-http.log; + + location ^~ /.well-known/acme-challenge/ { + default_type "text/plain"; + root /var/www/letsencrypt/email.challenger.{{ DOMAIN_NAME }}; + } + + location / { + return 301 https://$host$request_uri; + } + +} diff --git a/roles/challenger/templates/etc/nginx/sites-available/email-challenger-nginx.conf.j2 b/roles/challenger/templates/etc/nginx/sites-available/email-challenger-nginx.conf.j2 @@ -1,23 +1,5 @@ server { - listen 80; - listen [::]:80; - - server_name email.challenger.{{ DOMAIN_NAME }}; - - location ^~ /.well-known/acme-challenge/ { - default_type "text/plain"; - root /var/www/letsencrypt/email.challenger.{{ DOMAIN_NAME }}; - } - - location / { - return 301 https://$host$request_uri; - } - -} - -server { - listen 443; listen [::]:443; @@ -41,15 +23,10 @@ server { keepalive_requests 10000; keepalive_timeout 650s; - # Redirect non-https traffic to https - if ($scheme != "https") { - return 301 https://$host$request_uri; - } + if ($http_user_agent ~* "Bytedance|bytespider|Amazonbot|Claude|Anthropic|AI|GPT|acebook") { return 451 ; } - location ^~ /.well-known/acme-challenge/ { - default_type "text/plain"; - root /var/www/letsencrypt/email.challenger.{{ DOMAIN_NAME }}; - } + error_log /var/log/nginx/email.challenger.{{ DOMAIN_NAME }}.err; + access_log /var/log/nginx/email.challenger.{{ DOMAIN_NAME }}.log apm; location / { proxy_pass http://unix:/var/run/challenger-email/challenger-http.sock; diff --git a/roles/challenger/templates/etc/nginx/sites-available/postal-challenger-http.conf.j2 b/roles/challenger/templates/etc/nginx/sites-available/postal-challenger-http.conf.j2 @@ -0,0 +1,19 @@ +server { + + listen 80; + listen [::]:80; + + server_name postal.challenger.{{ DOMAIN_NAME }}; + + error_log /var/log/nginx/postal.challenger.{{ DOMAIN_NAME }}-http.err; + access_log /var/log/nginx/postal.challenger.{{ DOMAIN_NAME }}-http.log; + + location ^~ /.well-known/acme-challenge/ { + default_type "text/plain"; + root /var/www/letsencrypt/postal.challenger.{{ DOMAIN_NAME }}; + } + + location / { + return 301 https://$host$request_uri; + } +} diff --git a/roles/challenger/templates/etc/nginx/sites-available/postal-challenger-nginx.conf b/roles/challenger/templates/etc/nginx/sites-available/postal-challenger-nginx.conf @@ -1,58 +0,0 @@ -server { - - listen 80; - listen [::]:80; - - server_name postal.challenger.{{ DOMAIN_NAME }}; - - location ^~ /.well-known/acme-challenge/ { - default_type "text/plain"; - root /var/www/letsencrypt/postal.challenger.{{ DOMAIN_NAME }}; - } - - location / { - return 301 https://$host$request_uri; - } -} - - -server { - - listen 443; - listen [::]:443; - - # Do not identify as nginx - server_tokens off; - server_name postal.challenger.{{ DOMAIN_NAME }}; - - ssl_certificate /etc/letsencrypt/live/postal.challenger.{{ DOMAIN_NAME }}/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/postal.challenger.{{ DOMAIN_NAME }}/privkey.pem; - ssl_trusted_certificate /etc/letsencrypt/live/postal.challenger.{{ DOMAIN_NAME }}/chain.pem; - ssl_prefer_server_ciphers on; - ssl_session_cache shared:SSL:10m; - ssl_dhparam /etc/ssl/private/dhparam.pem; - ssl_protocols TLSv1.3 TLSv1.2; - ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH'; - - add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; - - # Bigger than default timeout to support long polling - proxy_read_timeout 650s; - keepalive_requests 10000; - keepalive_timeout 650s; - - - # Redirect non-https traffic to https - if ($scheme != "https") { - return 301 https://$host$request_uri; - } - - location ^~ /.well-known/acme-challenge/ { - default_type "text/plain"; - root /var/www/letsencrypt/postal.challenger.{{ DOMAIN_NAME }}; - } - - location / { - proxy_pass http://unix:/var/run/challenger-postal/challenger-http.sock; - } -} diff --git a/roles/challenger/templates/etc/nginx/sites-available/postal-challenger-nginx.conf.j2 b/roles/challenger/templates/etc/nginx/sites-available/postal-challenger-nginx.conf.j2 @@ -0,0 +1,32 @@ +server { + + listen 443; + listen [::]:443; + + # Do not identify as nginx + server_tokens off; + server_name postal.challenger.{{ DOMAIN_NAME }}; + + ssl_certificate /etc/letsencrypt/live/postal.challenger.{{ DOMAIN_NAME }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/postal.challenger.{{ DOMAIN_NAME }}/privkey.pem; + ssl_trusted_certificate /etc/letsencrypt/live/postal.challenger.{{ DOMAIN_NAME }}/chain.pem; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_dhparam /etc/ssl/private/dhparam.pem; + ssl_protocols TLSv1.3 TLSv1.2; + ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH'; + + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; + + # Bigger than default timeout to support long polling + proxy_read_timeout 650s; + keepalive_requests 10000; + keepalive_timeout 650s; + + error_log /var/log/nginx/postal.challenger.{{ DOMAIN_NAME }}.err; + access_log /var/log/nginx/postal.challenger.{{ DOMAIN_NAME }}.log apm; + + location / { + proxy_pass http://unix:/var/run/challenger-postal/challenger-http.sock; + } +} diff --git a/roles/challenger/templates/etc/nginx/sites-available/sms-challenger-http.conf.j2 b/roles/challenger/templates/etc/nginx/sites-available/sms-challenger-http.conf.j2 @@ -0,0 +1,20 @@ +server { + + listen 80; + listen [::]:80; + + server_name sms.challenger.{{ DOMAIN_NAME }}; + + error_log /var/log/nginx/sms.challenger.{{ DOMAIN_NAME }}-http.err; + access_log /var/log/nginx/sms.challenger.{{ DOMAIN_NAME }}-http.log; + + location ^~ /.well-known/acme-challenge/ { + default_type "text/plain"; + root /var/www/letsencrypt/sms.challenger.{{ DOMAIN_NAME }}; + } + + location / { + return 301 https://$host$request_uri; + } + +} diff --git a/roles/challenger/templates/etc/nginx/sites-available/sms-challenger-nginx.conf.j2 b/roles/challenger/templates/etc/nginx/sites-available/sms-challenger-nginx.conf.j2 @@ -1,24 +1,5 @@ server { - listen 80; - listen [::]:80; - - server_name sms.challenger.{{ DOMAIN_NAME }}; - - location ^~ /.well-known/acme-challenge/ { - default_type "text/plain"; - root /var/www/letsencrypt/sms.challenger.{{ DOMAIN_NAME }}; - } - - location / { - return 301 https://$host$request_uri; - } - -} - - -server { - listen 443; listen [::]:443; @@ -42,15 +23,8 @@ server { keepalive_requests 10000; keepalive_timeout 650s; - # Redirect non-https traffic to https - if ($scheme != "https") { - return 301 https://$host$request_uri; - } - - location ^~ /.well-known/acme-challenge/ { - default_type "text/plain"; - root /var/www/letsencrypt/sms.challenger.{{ DOMAIN_NAME }}; - } + error_log /var/log/nginx/sms.challenger.{{ DOMAIN_NAME }}.err; + access_log /var/log/nginx/sms.challenger.{{ DOMAIN_NAME }}.log apm; location / { proxy_pass http://unix:/var/run/challenger-sms/challenger-http.sock; diff --git a/roles/common_packages/tasks/main.yml b/roles/common_packages/tasks/main.yml @@ -73,5 +73,12 @@ - sudo - uuid-runtime - wget + - openssl state: latest when: ansible_os_family == 'Debian' + +- name: create dhparam.pem + command: openssl dhparam -out dhparam.pem 4096 + args: + chdir: /etc/ssl/private/ + creates: /etc/ssl/private/dhparam.pem diff --git a/roles/exchange/tasks/main.yml b/roles/exchange/tasks/main.yml @@ -20,10 +20,18 @@ group: root mode: 0644 -- name: Enable Taler exchange reverse proxy configuration +- name: Ensure Taler exchange HTTP virtualhost configuration file exists + template: + src: templates/etc/nginx/sites-available/exchange-http.conf.j2 + dest: /etc/nginx/sites-available/exchange-http.conf + owner: root + group: root + mode: 0644 + +- name: Enable Taler exchange HTTP reverse proxy configuration file: - src: /etc/nginx/sites-available/exchange-nginx.conf - dest: /etc/nginx/sites-enabled/exchange-nginx.conf + src: /etc/nginx/sites-available/exchange-http.conf + dest: /etc/nginx/sites-enabled/exchange-http.conf state: link notify: restart nginx @@ -50,6 +58,13 @@ domains: - "exchange.{{ DOMAIN_NAME }}" +- name: Enable Taler exchange reverse proxy configuration + file: + src: /etc/nginx/sites-available/exchange-nginx.conf + dest: /etc/nginx/sites-enabled/exchange-nginx.conf + state: link + notify: restart nginx + - name: Ensure /etc/taler-exchange/config.d/ directory exists file: path: "/etc/taler-exchange/conf.d/" @@ -117,7 +132,7 @@ - name: Ensure taler-exchange service is enabled and started service: - deamon_reload: true + daemon_reload: true name: taler-exchange.target state: started enabled: true diff --git a/roles/exchange/templates/etc/nginx/sites-available/exchange-http.conf.j2 b/roles/exchange/templates/etc/nginx/sites-available/exchange-http.conf.j2 @@ -0,0 +1,19 @@ +server { + + listen 80; + listen [::]:80; + + server_name exchange.{{ DOMAIN_NAME }}; + + error_log /var/log/nginx/exchange.{{ DOMAIN_NAME }}-http.err; + access_log /var/log/nginx/exchange.{{ DOMAIN_NAME }}-http.log; + + location ^~ /.well-known/acme-challenge/ { + default_type "text/plain"; + root /var/www/letsencrypt/exchange.{{ DOMAIN_NAME }}; + } + + location / { + return 301 https://$host$request_uri; + } +} diff --git a/roles/exchange/templates/etc/nginx/sites-available/exchange-nginx.conf.j2 b/roles/exchange/templates/etc/nginx/sites-available/exchange-nginx.conf.j2 @@ -1,23 +1,5 @@ server { - listen 80; - listen [::]:80; - - server_name exchange.{{ DOMAIN_NAME }}; - - location ^~ /.well-known/acme-challenge/ { - default_type "text/plain"; - root /var/www/letsencrypt/exchange.{{ DOMAIN_NAME }}; - } - - location / { - return 301 https://$host$request_uri; - } -} - - -server { - listen 443; listen [::]:443; @@ -41,15 +23,10 @@ server { keepalive_requests 1000000; keepalive_timeout 6500s; - # Redirect non-https traffic to https - if ($scheme != "https") { - return 301 https://$host$request_uri; - } + if ($http_user_agent ~* "Bytedance|bytespider|Amazonbot|Claude|Anthropic|AI|GPT|acebook") { return 451 ; } - location ^~ /.well-known/acme-challenge/ { - default_type "text/plain"; - root /var/www/letsencrypt/exchange.{{ DOMAIN_NAME }}; - } + error_log /var/log/nginx/exchange.{{ DOMAIN_NAME }}.err; + access_log /var/log/nginx/exchange.{{ DOMAIN_NAME }}.log apm; location / { proxy_pass http://unix:/var/run/taler/exchange-httpd/exchange-http.sock; diff --git a/roles/monitoring/tasks/main.yml b/roles/monitoring/tasks/main.yml @@ -0,0 +1,79 @@ +--- +- name: Deploy prometheus-nginx exporter + include_role: + name: prometheus.prometheus.nginx_exporter + vars: + nginx_exporter_web_listen_address: 127.0.0.1:9113 + +- name: Deploy prometheus-node exporter + include_role: + name: prometheus.prometheus.node_exporter + vars: + node_exporter_web_listen_address: 127.0.0.1:9114 + +- name: Deploy prometheus-postgres exporter + include_role: + name: prometheus.prometheus.postgres_exporter + vars: + postgres_exporter_web_listen_address: 127.0.0.1:9115 + postgres_exporter_username: exporter + postgres_exporter_password: secret + +- name: Deploy prometheus-systemd exporter + include_role: + name: prometheus.prometheus.systemd_exporter + vars: + systemd_exporter_web_listen_address: 127.0.0.1:9116 + +- name: Ensure Taler monitoring virtualhost configuration file exists + template: + src: templates/etc/nginx/sites-available/monitoring-nginx.conf.j2 + dest: /etc/nginx/sites-available/monitoring-nginx.conf + owner: root + group: root + mode: 0644 + +- name: Ensure Taler monitoring HTTP virtualhost configuration file exists + template: + src: templates/etc/nginx/sites-available/monitoring-http.conf.j2 + dest: /etc/nginx/sites-available/monitoring-http.conf + owner: root + group: root + mode: 0644 + +- name: Enable Taler monitoring HTTP reverse proxy configuration + file: + src: /etc/nginx/sites-available/monitoring-http.conf + dest: /etc/nginx/sites-enabled/monitoring-http.conf + state: link + notify: restart nginx + +- name: Secure the monitoring site with Letsencrypt + ansible.builtin.include_role: + name: geerlingguy.certbot + vars: + certbot_install_method: package + certbot_auto_renew: true + certbot_auto_renew_user: "{{ ansible_user | default(lookup('env', 'USER')) }}" + certbot_auto_renew_hour: "11" + certbot_auto_renew_minute: "11" + certbot_auto_renew_options: "--quiet" + certbot_create_method: webroot + certbot_create_if_missing: true + certbot_create_extra_args: + certbot_hsts: false + certbot_testmode: true + certbot_admin_email: "admin@{{ DOMAIN_NAME }}" + certbot_keep_updated: true + certbot_script: letsencrypt + certbot_certs: + - webroot: "/var/www/letsencrypt/monitoring.{{ DOMAIN_NAME }}" + domains: + - "exchange.{{ DOMAIN_NAME }}" + +- name: Enable Taler monitoring reverse proxy configuration + file: + src: /etc/nginx/sites-available/monitoring-nginx.conf + dest: /etc/nginx/sites-enabled/monitoring-nginx.conf + state: link + notify: restart nginx diff --git a/roles/monitoring/templates/etc/nginx/sites-available/monitoring-http.conf.j2 b/roles/monitoring/templates/etc/nginx/sites-available/monitoring-http.conf.j2 @@ -0,0 +1,19 @@ +server { + + listen 80; + listen [::]:80; + + server_name monitoring.{{ DOMAIN_NAME }}; + + error_log /var/log/nginx/monitoring.{{ DOMAIN_NAME }}-http.err; + access_log /var/log/nginx/monitoring.{{ DOMAIN_NAME }}-http.log; + + location ^~ /.well-known/acme-challenge/ { + default_type "text/plain"; + root /var/www/letsencrypt/monitoring.{{ DOMAIN_NAME }}; + } + + location / { + return 301 https://$host$request_uri; + } +} diff --git a/roles/monitoring/templates/etc/nginx/sites-available/monitoring-nginx.conf.j2 b/roles/monitoring/templates/etc/nginx/sites-available/monitoring-nginx.conf.j2 @@ -0,0 +1,55 @@ +server { + + listen 443; + listen [::]:443; + + # Do not identify as nginx + server_tokens off; + server_name monitoring.{{ DOMAIN_NAME }}; + + ssl_certificate /etc/letsencrypt/live/monitoring.{{ DOMAIN_NAME }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/monitoring.{{ DOMAIN_NAME }}/privkey.pem; + ssl_trusted_certificate /etc/letsencrypt/live/monitoring.{{ DOMAIN_NAME }}/chain.pem; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_dhparam /etc/ssl/private/dhparam.pem; + ssl_protocols TLSv1.3 TLSv1.2; + ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH'; + + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; + + error_log /var/log/nginx/monitoring.{{ DOMAIN_NAME }}.err; + access_log /var/log/nginx/monitoring.{{ DOMAIN_NAME }}.log; + + location = /nginx/ { + # Put API behind simple access control. TODO: check Prometheus can do this! + if ($http_authorization != "Bearer {{ PROMETHEUS_ACCESS_TOKEN }}") { + return 401; + } + proxy_pass http://127.0.0.1:9113/; + } + + location = /node/ { + # Put API behind simple access control. TODO: check Prometheus can do this! + if ($http_authorization != "Bearer {{ PROMETHEUS_ACCESS_TOKEN }}") { + return 401; + } + proxy_pass http://127.0.0.1:9114/; + } + + location = /postgres/ { + # Put API behind simple access control. TODO: check Prometheus can do this! + if ($http_authorization != "Bearer {{ PROMETHEUS_ACCESS_TOKEN }}") { + return 401; + } + proxy_pass http://127.0.0.1:9115/; + } + + location = /systemd/ { + # Put API behind simple access control. TODO: check Prometheus can do this! + if ($http_authorization != "Bearer {{ PROMETHEUS_ACCESS_TOKEN }}") { + return 401; + } + proxy_pass http://127.0.0.1:9116/; + } +} diff --git a/roles/webserver/files/etc/nginx/conf.d/log-format-apm.conf b/roles/webserver/files/etc/nginx/conf.d/log-format-apm.conf @@ -0,0 +1,9 @@ +# Define log format where we also log latencies. +log_format apm '”$time_local” client=$remote_addr ' + 'method=$request_method request=”$request” ' + 'request_length=$request_length ' + 'status=$status bytes_sent=$bytes_sent ' + 'body_bytes_sent=$body_bytes_sent ' + 'upstream_addr=$upstream_addr ' + 'upstream_status=$upstream_status ' + 'request_time=$request_time'; diff --git a/roles/webserver/tasks/main.yml b/roles/webserver/tasks/main.yml @@ -24,6 +24,14 @@ path: /etc/nginx/sites-enabled/default state: absent +- name: Setup extended log format + copy: + src: etc/nginx/conf.d/log-format-apm.conf + dest: /etc/nginx/conf.d/log-format-apm.conf + owner: root + group: root + mode: 0644 + - name: Ensure Nginx service is enabled and started service: name: nginx