diff options
Diffstat (limited to 'roles')
69 files changed, 1095 insertions, 525 deletions
diff --git a/roles/apps/bluespice/tasks/main.yml b/roles/apps/bluespice/tasks/main.yml index 899d1e1d..49ef2418 100644 --- a/roles/apps/bluespice/tasks/main.yml +++ b/roles/apps/bluespice/tasks/main.yml @@ -49,7 +49,8 @@ nginx_vhost: name: "bluespice-{{ item.key }}" template: generic - acme: true + tls: + certificate_provider: "{{ acme_client }}" hostnames: - "{{ item.value.hostname }}" locations: diff --git a/roles/apps/collabora/code/tasks/main.yml b/roles/apps/collabora/code/tasks/main.yml index db28bb65..84efec5c 100644 --- a/roles/apps/collabora/code/tasks/main.yml +++ b/roles/apps/collabora/code/tasks/main.yml @@ -45,16 +45,8 @@ include_role: name: kubernetes/standalone/pod -- name: configure nginx vhost +- name: install nginx vhost config loop: "{{ collabora_code_instances | dict2items }}" loop_control: label: "{{ item.key }}" - vars: - nginx_vhost: - name: "collabora-code-{{ item.key }}" - content: "{{ lookup('template', 'nginx-vhost.conf.j2') }}" - acme: true - hostnames: - - "{{ item.value.hostname }}" - include_role: - name: nginx/vhost + include_tasks: nginx-vhost.yml diff --git a/roles/apps/collabora/code/tasks/nginx-vhost.yml b/roles/apps/collabora/code/tasks/nginx-vhost.yml new file mode 100644 index 00000000..afd8f1e0 --- /dev/null +++ b/roles/apps/collabora/code/tasks/nginx-vhost.yml @@ -0,0 +1,17 @@ +--- +- name: render nginx-vhost custom config + set_fact: + collabora_code_nginx_vhost_custom: "{{ lookup('template', 'nginx-vhost.conf.j2') }}" + +- name: configure nginx vhost + vars: + nginx_vhost: + name: "collabora-code-{{ item.key }}" + template: generic + tls: + certificate_provider: "{{ acme_client }}" + hostnames: + - "{{ item.value.hostname }}" + custom: "{{ collabora_code_nginx_vhost_custom }}" + include_role: + name: nginx/vhost diff --git a/roles/apps/collabora/code/templates/nginx-vhost.conf.j2 b/roles/apps/collabora/code/templates/nginx-vhost.conf.j2 index d56d77a0..67502e20 100644 --- a/roles/apps/collabora/code/templates/nginx-vhost.conf.j2 +++ b/roles/apps/collabora/code/templates/nginx-vhost.conf.j2 @@ -1,124 +1,99 @@ -server { - listen 80; - listen [::]:80; - server_name {{ item.value.hostname }}; +client_max_body_size 128M; - include snippets/acmetool.conf; - - location / { - return 301 https://$host$request_uri; - } -} - -server { - listen 443 ssl http2; - listen [::]:443 ssl http2; - server_name {{ item.value.hostname }}; - - include snippets/acmetool.conf; - include snippets/tls.conf; - ssl_certificate /var/lib/acme/live/{{ item.value.hostname }}/fullchain; - ssl_certificate_key /var/lib/acme/live/{{ item.value.hostname }}/privkey; - include snippets/hsts.conf; - - - client_max_body_size 128M; - - # static files +# static files {% if item.value.version | collabora_code_loolvcool %} - location ^~ /loleaflet { +location ^~ /loleaflet { {% else %} - location ^~ /browser { +location ^~ /browser { {% endif %} - include snippets/proxy-nobuff.conf; - include snippets/proxy-forward-headers.conf; + include snippets/proxy-nobuff.conf; + include snippets/proxy-forward-headers.conf; - proxy_set_header Host $http_host; - proxy_pass http://127.0.0.1:{{ item.value.port }}; + proxy_set_header Host $http_host; + proxy_pass http://127.0.0.1:{{ item.value.port }}; - proxy_redirect http://$host/ https://$host/; - proxy_redirect http://$host:9980/ https://$host/; - } + proxy_redirect http://$host/ https://$host/; + proxy_redirect http://$host:9980/ https://$host/; +} - # WOPI discovery URL - location ^~ /hosting/discovery { - include snippets/proxy-nobuff.conf; - include snippets/proxy-forward-headers.conf; +# WOPI discovery URL +location ^~ /hosting/discovery { + include snippets/proxy-nobuff.conf; + include snippets/proxy-forward-headers.conf; - proxy_set_header Host $http_host; - proxy_pass http://127.0.0.1:{{ item.value.port }}; + proxy_set_header Host $http_host; + proxy_pass http://127.0.0.1:{{ item.value.port }}; - proxy_redirect http://$host/ https://$host/; - proxy_redirect http://$host:9980/ https://$host/; - } + proxy_redirect http://$host/ https://$host/; + proxy_redirect http://$host:9980/ https://$host/; +} - # Capabilities - location ^~ /hosting/capabilities { - include snippets/proxy-nobuff.conf; - include snippets/proxy-forward-headers.conf; +# Capabilities +location ^~ /hosting/capabilities { + include snippets/proxy-nobuff.conf; + include snippets/proxy-forward-headers.conf; - proxy_set_header Host $http_host; - proxy_pass http://127.0.0.1:{{ item.value.port }}; + proxy_set_header Host $http_host; + proxy_pass http://127.0.0.1:{{ item.value.port }}; - proxy_redirect http://$host/ https://$host/; - proxy_redirect http://$host:9980/ https://$host/; - } + proxy_redirect http://$host/ https://$host/; + proxy_redirect http://$host:9980/ https://$host/; +} - # main websocket +# main websocket {% if item.value.version | collabora_code_loolvcool %} - location ~ ^/lool/(.*)/ws$ { +location ~ ^/lool/(.*)/ws$ { {% else %} - location ~ ^/cool/(.*)/ws$ { +location ~ ^/cool/(.*)/ws$ { {% endif %} - include snippets/proxy-nobuff.conf; - include snippets/proxy-forward-headers.conf; + include snippets/proxy-nobuff.conf; + include snippets/proxy-forward-headers.conf; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; - proxy_read_timeout 36000s; + proxy_read_timeout 36000s; - proxy_set_header Host $http_host; - proxy_pass http://127.0.0.1:{{ item.value.port }}; + proxy_set_header Host $http_host; + proxy_pass http://127.0.0.1:{{ item.value.port }}; - proxy_redirect http://$host/ https://$host/; - proxy_redirect http://$host:9980/ https://$host/; - } + proxy_redirect http://$host/ https://$host/; + proxy_redirect http://$host:9980/ https://$host/; +} - # download, presentation and image upload +# download, presentation and image upload {% if item.value.version | collabora_code_loolvcool %} - location ~ ^/lool { +location ~ ^/lool { {% else %} - location ~ ^/(c|l)ool { +location ~ ^/(c|l)ool { {% endif %} - include snippets/proxy-nobuff.conf; - include snippets/proxy-forward-headers.conf; + include snippets/proxy-nobuff.conf; + include snippets/proxy-forward-headers.conf; - proxy_set_header Host $http_host; - proxy_pass http://127.0.0.1:{{ item.value.port }}; + proxy_set_header Host $http_host; + proxy_pass http://127.0.0.1:{{ item.value.port }}; - proxy_redirect http://$host/ https://$host/; - proxy_redirect http://$host:9980/ https://$host/; - } + proxy_redirect http://$host/ https://$host/; + proxy_redirect http://$host:9980/ https://$host/; +} - # Admin Console websocket +# Admin Console websocket {% if item.value.version | collabora_code_loolvcool %} - location ^~ /lool/adminws { +location ^~ /lool/adminws { {% else %} - location ^~ /cool/adminws { +location ^~ /cool/adminws { {% endif %} - include snippets/proxy-nobuff.conf; - include snippets/proxy-forward-headers.conf; + include snippets/proxy-nobuff.conf; + include snippets/proxy-forward-headers.conf; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; - proxy_read_timeout 36000s; + proxy_read_timeout 36000s; - proxy_set_header Host $http_host; - proxy_pass http://127.0.0.1:{{ item.value.port }}; + proxy_set_header Host $http_host; + proxy_pass http://127.0.0.1:{{ item.value.port }}; - proxy_redirect http://$host/ https://$host/; - proxy_redirect http://$host:9980/ https://$host/; - } + proxy_redirect http://$host/ https://$host/; + proxy_redirect http://$host:9980/ https://$host/; } diff --git a/roles/apps/coturn/defaults/main.yml b/roles/apps/coturn/defaults/main.yml index 842e7f05..760a6499 100644 --- a/roles/apps/coturn/defaults/main.yml +++ b/roles/apps/coturn/defaults/main.yml @@ -3,7 +3,7 @@ coturn_uid: 930 coturn_gid: 930 coturn_base_path: /srv/coturn -# coturn_version: 4.5.2-r2 +# coturn_version: 4.6.2-r4 # coturn_realm: example.com # coturn_hostnames: # - stun.example.com @@ -17,6 +17,9 @@ coturn_threads: 0 coturn_dhparam_size: 2048 +# coturn_tls: +# certificate_provider: ... + coturn_listening_port: 3478 coturn_tls_listening_port: 5349 diff --git a/roles/apps/coturn/tasks/main.yml b/roles/apps/coturn/tasks/main.yml index 42ccd2b3..4e5adbd5 100644 --- a/roles/apps/coturn/tasks/main.yml +++ b/roles/apps/coturn/tasks/main.yml @@ -39,43 +39,52 @@ group: coturn mode: 0644 -- name: install acmetool hook script - template: - src: acmetool-reload.sh.j2 - dest: "/etc/acme/hooks/coturn-{{ coturn_realm }}" - mode: 0755 - -- name: install acmetool systemd unit snippet - copy: - dest: "/etc/systemd/system/acmetool.service.d/coturn-{{ coturn_realm }}.conf" - content: | - [Service] - ReadWritePaths={{ coturn_base_path }}/{{ coturn_realm }}/config/ssl - register: coturn_acmetool_snippet - -- name: reload systemd - when: coturn_acmetool_snippet is changed - systemd: - daemon_reload: yes +- name: compute certificate renewal config + set_fact: + coturn_certificate_renewal: + install: + - dest: "{{ coturn_base_path }}/{{ coturn_realm }}/config/ssl/cert.pem" + src: + - fullchain + owner: root + group: coturn + mode: "0644" + - dest: "{{ coturn_base_path }}/{{ coturn_realm }}/config/ssl/privkey.pem" + src: + - key + owner: root + group: coturn + mode: "0640" + reload: | + pod_id=$(crictl pods -q --state ready --name "^coturn-{{ coturn_realm }}-{{ ansible_nodename }}$") + [ -n "$pod_id" ] || exit 42 + container_id=$(crictl ps -q --name '^coturn$' -p "$pod_id") + [ -n "$container_id" ] || exit 42 + crictl stop "$container_id" - name: configure nginx vhost when: coturn_install_nginx_vhost vars: nginx_vhost: name: "coturn-{{ coturn_realm }}" - content: "{{ lookup('template', 'nginx-vhost.conf.j2') }}" - acme: true + template: generic + tls: "{{ coturn_tls }}" hostnames: "{{ coturn_hostnames }}" + locations: + '/': + return: "404" + x509_certificate_renewal: "{{ coturn_certificate_renewal }}" include_role: name: nginx/vhost -- name: get certificate using acmetool +- name: generate/install/fetch TLS certificate when: not coturn_install_nginx_vhost - import_role: - name: x509/acmetool/cert vars: - acmetool_cert_name: "coturn-{{ coturn_realm }}" - acmetool_cert_hostnames: "{{ coturn_hostnames }}" + x509_certificate_name: "coturn-{{ coturn_realm }}" + x509_certificate_hostnames: "{{ coturn_hostnames }}" + x509_certificate_renewal: "{{ coturn_certificate_renewal }}" + include_role: + name: "x509/{{ coturn_tls.certificate_provider }}/cert" - name: install pod manifest vars: diff --git a/roles/apps/coturn/templates/acmetool-reload.sh.j2 b/roles/apps/coturn/templates/acmetool-reload.sh.j2 deleted file mode 100644 index 08530583..00000000 --- a/roles/apps/coturn/templates/acmetool-reload.sh.j2 +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh -set -e -EVENT_NAME="$1" -[ "$EVENT_NAME" = "live-updated" ] || exit 42 - -MAIN_HOSTNAME="{{ coturn_hostnames[0] }}" -SSL_D="{{ coturn_base_path }}/{{ coturn_realm }}/config/ssl" - -while read name; do - certdir="$ACME_STATE_DIR/live/$name" - if [ -z "$name" -o ! -e "$certdir" ]; then - continue - fi - if [ "$name" != "$MAIN_HOSTNAME" ]; then - continue - fi - - install -m 0644 -o root -g coturn "$certdir/fullchain" "$SSL_D/cert.pem" - install -m 0640 -o root -g coturn "$certdir/privkey" "$SSL_D/privkey.pem" - - pod_id=$(crictl pods -q --state ready --name "^coturn-{{ coturn_realm }}-{{ ansible_nodename }}$") - [ -n "$pod_id" ] || exit 42 - container_id=$(crictl ps -q --name '^coturn$' -p "$pod_id") - [ -n "$container_id" ] || exit 42 - crictl stop "$container_id" - - break -done diff --git a/roles/apps/coturn/templates/nginx-vhost.conf.j2 b/roles/apps/coturn/templates/nginx-vhost.conf.j2 deleted file mode 100644 index 0639fbe1..00000000 --- a/roles/apps/coturn/templates/nginx-vhost.conf.j2 +++ /dev/null @@ -1,27 +0,0 @@ -server { - listen 80; - listen [::]:80; - server_name {{ coturn_hostnames | join(' ') }}; - - include snippets/acmetool.conf; - - location / { - return 301 https://$host$request_uri; - } -} - -server { - listen 443 ssl http2; - listen [::]:443 ssl http2; - server_name {{ coturn_hostnames | join(' ') }}; - - include snippets/acmetool.conf; - include snippets/tls.conf; - ssl_certificate /var/lib/acme/live/{{ coturn_hostnames[0] }}/fullchain; - ssl_certificate_key /var/lib/acme/live/{{ coturn_hostnames[0] }}/privkey; - include snippets/hsts.conf; - - location / { - return 404; - } -} diff --git a/roles/apps/etherpad-lite/tasks/main.yml b/roles/apps/etherpad-lite/tasks/main.yml index 072a6c09..a3b167a8 100644 --- a/roles/apps/etherpad-lite/tasks/main.yml +++ b/roles/apps/etherpad-lite/tasks/main.yml @@ -108,13 +108,8 @@ include_role: name: kubernetes/standalone/pod -- name: configure nginx vhost +- name: install nginx vhost config loop: "{{ etherpad_lite_instances | dict2items }}" - vars: - nginx_vhost: - name: "etherpad-lite-{{ item.key }}" - content: "{{ lookup('template', 'nginx-vhost.conf.j2') }}" - acme: true - hostnames: "{{ item.value.hostnames }}" - include_role: - name: nginx/vhost + loop_control: + label: "{{ item.key }}" + include_tasks: nginx-vhost.yml diff --git a/roles/apps/etherpad-lite/tasks/nginx-vhost.yml b/roles/apps/etherpad-lite/tasks/nginx-vhost.yml new file mode 100644 index 00000000..91c8b54d --- /dev/null +++ b/roles/apps/etherpad-lite/tasks/nginx-vhost.yml @@ -0,0 +1,16 @@ +--- +- name: render nginx-vhost custom config + set_fact: + etherpad_lite_nginx_vhost_custom: "{{ lookup('template', 'nginx-vhost.conf.j2') }}" + +- name: configure nginx vhost + vars: + nginx_vhost: + name: "etherpad-lite-{{ item.key }}" + template: generic + tls: + certificate_provider: "{{ acme_client }}" + hostnames: "{{ item.value.hostnames }}" + custom: "{{ etherpad_lite_nginx_vhost_custom }}" + include_role: + name: nginx/vhost diff --git a/roles/apps/etherpad-lite/templates/nginx-vhost.conf.j2 b/roles/apps/etherpad-lite/templates/nginx-vhost.conf.j2 index b59701fc..209a81c0 100644 --- a/roles/apps/etherpad-lite/templates/nginx-vhost.conf.j2 +++ b/roles/apps/etherpad-lite/templates/nginx-vhost.conf.j2 @@ -1,57 +1,33 @@ -server { - listen 80; - listen [::]:80; - server_name {{ item.value.hostnames | join(' ') }}; - - include snippets/acmetool.conf; - - location / { - return 301 https://$host$request_uri; - } -} - -server { - listen 443 ssl http2; - listen [::]:443 ssl http2; - server_name {{ item.value.hostnames | join(' ') }}; - - include snippets/acmetool.conf; - include snippets/tls.conf; - ssl_certificate /var/lib/acme/live/{{ item.value.hostnames[0] }}/fullchain; - ssl_certificate_key /var/lib/acme/live/{{ item.value.hostnames[0] }}/privkey; - include snippets/hsts.conf; - - location / { - rewrite ^/$ / break; - rewrite ^/locales/(.*) /locales/$1 break; - rewrite ^/locales.json /locales.json break; - rewrite ^/admin(.*) /admin$1 break; - rewrite ^/p/(.*) /p/$1 break; - rewrite ^/static/(.*) /static/$1 break; - rewrite ^/pluginfw/(.*) /pluginfw/$1 break; - rewrite ^/javascripts/(.*) /javascripts/$1 break; - rewrite ^/socket.io/(.*) /socket.io/$1 break; - rewrite ^/ep/(.*) /ep/$1 break; - rewrite ^/minified/(.*) /minified/$1 break; - rewrite ^/api/(.*) /api/$1 break; - rewrite ^/ro/(.*) /ro/$1 break; - rewrite ^/error/(.*) /error/$1 break; - rewrite ^/jserror(.*) /jserror$1 break; - rewrite ^/redirect(.*) /redirect$1 break; - rewrite /favicon.ico /favicon.ico break; - rewrite /robots.txt /robots.txt break; - rewrite /(.*) /p/$1; - - include snippets/proxy-nobuff.conf; - - proxy_set_header Host $host; - include snippets/proxy-forward-headers.conf; - proxy_pass_header Server; - - # for websockets - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - - proxy_pass http://127.0.0.1:{{ item.value.port }}; - } +location / { + rewrite ^/$ / break; + rewrite ^/locales/(.*) /locales/$1 break; + rewrite ^/locales.json /locales.json break; + rewrite ^/admin(.*) /admin$1 break; + rewrite ^/p/(.*) /p/$1 break; + rewrite ^/static/(.*) /static/$1 break; + rewrite ^/pluginfw/(.*) /pluginfw/$1 break; + rewrite ^/javascripts/(.*) /javascripts/$1 break; + rewrite ^/socket.io/(.*) /socket.io/$1 break; + rewrite ^/ep/(.*) /ep/$1 break; + rewrite ^/minified/(.*) /minified/$1 break; + rewrite ^/api/(.*) /api/$1 break; + rewrite ^/ro/(.*) /ro/$1 break; + rewrite ^/error/(.*) /error/$1 break; + rewrite ^/jserror(.*) /jserror$1 break; + rewrite ^/redirect(.*) /redirect$1 break; + rewrite /favicon.ico /favicon.ico break; + rewrite /robots.txt /robots.txt break; + rewrite /(.*) /p/$1; + + include snippets/proxy-nobuff.conf; + + proxy_set_header Host $host; + include snippets/proxy-forward-headers.conf; + proxy_pass_header Server; + + # for websockets + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + proxy_pass http://127.0.0.1:{{ item.value.port }}; } diff --git a/roles/apps/jitsi/meet/tasks/main.yml b/roles/apps/jitsi/meet/tasks/main.yml index eff8232b..1d55fc78 100644 --- a/roles/apps/jitsi/meet/tasks/main.yml +++ b/roles/apps/jitsi/meet/tasks/main.yml @@ -151,7 +151,8 @@ nginx_vhost: name: "jitsi-meet-{{ jitsi_meet_inst_name }}" template: generic - acme: true + tls: + certificate_provider: "{{ acme_client }}" hostnames: - "{{ jitsi_meet_hostname }}" locations: "{{ nginx_vhost_locations_base | combine(nginx_vhost_locations_streamui) }}" diff --git a/roles/apps/keycloak/tasks/main.yml b/roles/apps/keycloak/tasks/main.yml index 68806458..c3e93666 100644 --- a/roles/apps/keycloak/tasks/main.yml +++ b/roles/apps/keycloak/tasks/main.yml @@ -96,7 +96,8 @@ nginx_vhost: name: "keycloak-{{ item.key }}" template: generic - acme: true + tls: + certificate_provider: "{{ acme_client }}" hostnames: - "{{ item.value.hostname }}" locations: diff --git a/roles/apps/mumble/defaults/main.yml b/roles/apps/mumble/defaults/main.yml index 627af125..c9cd9db3 100644 --- a/roles/apps/mumble/defaults/main.yml +++ b/roles/apps/mumble/defaults/main.yml @@ -14,6 +14,9 @@ mumble_dhparam_size: 2048 mumble_timezone: "Europe/Vienna" +# mumble_tls: +# certificate_provider: ... + mumble_config_options: bonjour: false sslCiphers: "ECDHE+AESGCM:DHE+AESGCM:ECDHE+AES256:DHE+AES256:ECDHE+AES128:DHE+AES128:!RSA:!ADH:!AECDH:!MD5" diff --git a/roles/apps/mumble/tasks/main.yml b/roles/apps/mumble/tasks/main.yml index 33331dca..5b380725 100644 --- a/roles/apps/mumble/tasks/main.yml +++ b/roles/apps/mumble/tasks/main.yml @@ -27,31 +27,32 @@ group: mumble mode: 0644 -- name: install acmetool hook script - template: - src: acmetool-reload.sh.j2 - dest: "/etc/acme/hooks/mumble-{{ mumble_instance }}" - mode: 0755 - -- name: install acmetool systemd unit snippet - copy: - dest: "/etc/systemd/system/acmetool.service.d/mumble-{{ mumble_instance }}.conf" - content: | - [Service] - ReadWritePaths={{ mumble_base_path }}/{{ mumble_instance }}/ssl - register: mumble_acmetool_snippet - -- name: reload systemd - when: mumble_acmetool_snippet is changed - systemd: - daemon_reload: yes - -- name: get certificate using acmetool - import_role: - name: x509/acmetool/cert +- name: generate/install/fetch TLS certificate vars: - acmetool_cert_name: "mumble-{{ mumble_instance }}" - acmetool_cert_hostnames: "{{ mumble_hostnames }}" + x509_certificate_name: "mumble-{{ mumble_instance }}" + x509_certificate_hostnames: "{{ mumble_hostnames }}" + x509_certificate_renewal: + install: + - dest: "{{ mumble_base_path }}/{{ mumble_instance }}/ssl/cert.pem" + src: + - fullchain + owner: root + group: mumble + mode: "0644" + - dest: "{{ mumble_base_path }}/{{ mumble_instance }}/ssl/privkey.pem" + src: + - key + owner: root + group: mumble + mode: "0640" + reload: | + pod_id=$(crictl pods -q --state ready --name "^mumble-{{ mumble_instance }}-{{ ansible_nodename }}$") + [ -n "$pod_id" ] || exit 42 + container_id=$(crictl ps -q --name '^mumble$' -p "$pod_id") + [ -n "$container_id" ] || exit 42 + crictl exec "$container_id" kill -USR1 1 + include_role: + name: "x509/{{ mumble_tls.certificate_provider }}/cert" - name: create mumble data directory file: diff --git a/roles/apps/mumble/templates/acmetool-reload.sh.j2 b/roles/apps/mumble/templates/acmetool-reload.sh.j2 deleted file mode 100644 index fd9f01ba..00000000 --- a/roles/apps/mumble/templates/acmetool-reload.sh.j2 +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh -set -e -EVENT_NAME="$1" -[ "$EVENT_NAME" = "live-updated" ] || exit 42 - -MAIN_HOSTNAME="{{ mumble_hostnames[0] }}" -SSL_D="{{ mumble_base_path }}/{{ mumble_instance }}/ssl" - -while read name; do - certdir="$ACME_STATE_DIR/live/$name" - if [ -z "$name" -o ! -e "$certdir" ]; then - continue - fi - if [ "$name" != "$MAIN_HOSTNAME" ]; then - continue - fi - - install -m 0644 -o root -g mumble "$certdir/fullchain" "$SSL_D/cert.pem" - install -m 0640 -o root -g mumble "$certdir/privkey" "$SSL_D/privkey.pem" - - pod_id=$(crictl pods -q --state ready --name "^mumble-{{ mumble_instance }}-{{ ansible_nodename }}$") - [ -n "$pod_id" ] || exit 42 - container_id=$(crictl ps -q --name '^mumble$' -p "$pod_id") - [ -n "$container_id" ] || exit 42 - crictl exec "$container_id" kill -USR1 1 - - break -done diff --git a/roles/apps/nextcloud/tasks/main.yml b/roles/apps/nextcloud/tasks/main.yml index 29ab9c39..c9a9061c 100644 --- a/roles/apps/nextcloud/tasks/main.yml +++ b/roles/apps/nextcloud/tasks/main.yml @@ -160,7 +160,8 @@ nginx_vhost: name: "nextcloud-{{ item.key }}" template: generic - acme: true + tls: + certificate_provider: "{{ acme_client }}" hostnames: "{{ item.value.hostnames }}" locations: '/': diff --git a/roles/apps/onlyoffice/tasks/main.yml b/roles/apps/onlyoffice/tasks/main.yml index 957d8afe..960e811b 100644 --- a/roles/apps/onlyoffice/tasks/main.yml +++ b/roles/apps/onlyoffice/tasks/main.yml @@ -140,7 +140,8 @@ nginx_vhost: name: "onlyoffice-{{ item.key }}" template: generic - acme: true + tls: + certificate_provider: "{{ acme_client }}" hostnames: - "{{ item.value.hostname }}" locations: diff --git a/roles/apps/pigallery2/tasks/main.yml b/roles/apps/pigallery2/tasks/main.yml index b8b0166d..2a758da1 100644 --- a/roles/apps/pigallery2/tasks/main.yml +++ b/roles/apps/pigallery2/tasks/main.yml @@ -67,7 +67,8 @@ nginx_vhost: name: "pigallery2-{{ item.key }}" template: generic - acme: true + tls: + certificate_provider: "{{ acme_client }}" hostnames: - "{{ item.value.hostname }}" locations: diff --git a/roles/apps/wikijs/tasks/main.yml b/roles/apps/wikijs/tasks/main.yml index e2b03d24..10b0aa54 100644 --- a/roles/apps/wikijs/tasks/main.yml +++ b/roles/apps/wikijs/tasks/main.yml @@ -73,7 +73,8 @@ nginx_vhost: name: "wikijs-{{ item.key }}" template: generic - acme: true + tls: + certificate_provider: "{{ acme_client }}" hostnames: - "{{ item.value.hostname }}" locations: diff --git a/roles/elevate/liquidtruth/tasks/main.yml b/roles/elevate/liquidtruth/tasks/main.yml index 837d2fd0..9f1ef3ad 100644 --- a/roles/elevate/liquidtruth/tasks/main.yml +++ b/roles/elevate/liquidtruth/tasks/main.yml @@ -12,17 +12,18 @@ import_tasks: nodejs.yml - name: configure nginx vhost - import_role: - name: nginx/vhost vars: nginx_vhost: name: liquidtruth template: generic - acme: true + tls: + certificate_provider: "{{ acme_client }}" hostnames: "{{ liquidtruth_hostnames }}" locations: '/': proxy_pass: "http://127.0.0.1:8080" + include_role: + name: nginx/vhost - name: create app user user: diff --git a/roles/elevate/media/tasks/nextcloud-app.yml b/roles/elevate/media/tasks/nextcloud-app.yml index 2e533ec6..42a351e4 100644 --- a/roles/elevate/media/tasks/nextcloud-app.yml +++ b/roles/elevate/media/tasks/nextcloud-app.yml @@ -102,7 +102,8 @@ nginx_vhost: name: "nextcloud-{{ elevate_media_nextcloud_instance_name }}" template: generic - acme: true + tls: + certificate_provider: "{{ acme_client }}" hostnames: "{{ elevate_media_nextcloud_instance.hostnames }}" locations: '/': diff --git a/roles/gitolite/base/defaults/main.yml b/roles/gitolite/base/defaults/main.yml index 1c5962cc..3c2e8fa3 100644 --- a/roles/gitolite/base/defaults/main.yml +++ b/roles/gitolite/base/defaults/main.yml @@ -15,3 +15,5 @@ gitolite_base_path: /srv/git # title: cgit root title # description: this will be shown by cgit below the title # logo: path/to/logo/file/on/ansible/controller.png +# tls: +# certificate_provider: "{{ acme_client }}" diff --git a/roles/gitolite/http/tasks/main.yml b/roles/gitolite/http/tasks/main.yml index a3055902..ee5b226c 100644 --- a/roles/gitolite/http/tasks/main.yml +++ b/roles/gitolite/http/tasks/main.yml @@ -50,12 +50,59 @@ src: "{{ gitolite_instances[gitolite_instance].http.logo }}" dest: "/usr/local/share/cgit/{{ gitolite_instance }}.png" + - name: compute nginx location directive for logo + set_fact: + nginx_locations_logo: + '= /logo.png': + alias: "/usr/local/share/cgit/{{ gitolite_instance }}.png" + +- name: compute nginx locations directives + set_fact: + nginx_locations_base: + '= /': + return: "303 /cgit/" + '/cgit-css/': + alias: "/usr/share/cgit/" + nginx_locations_main: + '/cgit/': + custom: |- + include fastcgi_params; + fastcgi_split_path_info ^(/cgit)(.*)$; + + fastcgi_param SCRIPT_FILENAME /usr/lib/cgit/cgit.cgi; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param QUERY_STRING $args; + fastcgi_param HTTP_HOST $server_name; + fastcgi_param CGIT_CONFIG {{ gitolite_base_path }}/{{ gitolite_instance }}/cgitrc; + + fastcgi_pass unix:/run/fcgiwrap/gitolite-{{ gitolite_instance }}.sock; + +- name: compute nginx location directive for git_backend + when: "'enable_git_backend' in gitolite_instances[gitolite_instance].http and gitolite_instances[gitolite_instance].http.enable_git_backend" + set_fact: + nginx_locations_git_backend: + '~ ^.*/git-receive-pack$': + return: "403" + '~ ^.*/(HEAD|info/refs|objects/(info/.*|[0-9a-f]+/[0-9a-f]+|pack/pack-[0-9a-f]+.(pack|idx))|git-upload-pack)$': + custom: |- + include fastcgi_params; + + fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend; + fastcgi_param PATH_INFO $uri; + fastcgi_param GIT_PROJECT_ROOT {{ gitolite_base_path }}/{{ gitolite_instance }}/repositories; + + fastcgi_pass unix:/run/fcgiwrap/gitolite-{{ gitolite_instance }}.sock; + - name: install nginx vhost vars: nginx_vhost: name: "gitolite-{{ gitolite_instance }}" - acme: true + template: generic + tls: "{{ gitolite_instances[gitolite_instance].http.tls }}" hostnames: "{{ gitolite_instances[gitolite_instance].http.hostnames }}" - content: "{{ lookup('template', 'nginx-vhost.conf.j2') }}" + logs: + access: "/var/log/nginx/git-{{ gitolite_instance }}_access.log" + error: "/var/log/nginx/git-{{ gitolite_instance }}_error.log" + locations: "{{ nginx_locations_base | combine(nginx_locations_logo | default({})) | combine(nginx_locations_main) | combine(nginx_locations_git_backend | default({})) }}" include_role: name: nginx/vhost diff --git a/roles/gitolite/http/templates/nginx-vhost.conf.j2 b/roles/gitolite/http/templates/nginx-vhost.conf.j2 deleted file mode 100644 index add7a719..00000000 --- a/roles/gitolite/http/templates/nginx-vhost.conf.j2 +++ /dev/null @@ -1,72 +0,0 @@ - server { - listen 80; - listen [::]:80; - server_name {{ gitolite_instances[gitolite_instance].http.hostnames | join(' ') }}; - - access_log /var/log/nginx/git-{{ gitolite_instance }}_access.log; - error_log /var/log/nginx/git-{{ gitolite_instance }}_error.log; - - include snippets/acmetool.conf; - - location / { - return 301 https://$host$request_uri; - } -} - -server { - listen 443 ssl http2; - listen [::]:443 ssl http2; - server_name {{ gitolite_instances[gitolite_instance].http.hostnames | join(' ') }}; - - access_log /var/log/nginx/git-{{ gitolite_instance }}_access.log; - error_log /var/log/nginx/git-{{ gitolite_instance }}_error.log; - - include snippets/acmetool.conf; - include snippets/tls.conf; - ssl_certificate /var/lib/acme/live/{{ gitolite_instances[gitolite_instance].http.hostnames[0] }}/fullchain; - ssl_certificate_key /var/lib/acme/live/{{ gitolite_instances[gitolite_instance].http.hostnames[0] }}/privkey; - include snippets/hsts.conf; - - location = / { - return 303 /cgit/; - } - - location /cgit-css/ { - alias /usr/share/cgit/; - } -{% if 'logo' in gitolite_instances[gitolite_instance].http %} - - location = /logo.png { - alias /usr/local/share/cgit/{{ gitolite_instance }}.png; - } -{% endif %} - - location /cgit/ { - include fastcgi_params; - fastcgi_split_path_info ^(/cgit)(.*)$; - - fastcgi_param SCRIPT_FILENAME /usr/lib/cgit/cgit.cgi; - fastcgi_param PATH_INFO $fastcgi_path_info; - fastcgi_param QUERY_STRING $args; - fastcgi_param HTTP_HOST $server_name; - fastcgi_param CGIT_CONFIG {{ gitolite_base_path }}/{{ gitolite_instance }}/cgitrc; - - fastcgi_pass unix:/run/fcgiwrap/gitolite-{{ gitolite_instance }}.sock; - } -{% if 'enable_git_backend' in gitolite_instances[gitolite_instance].http and gitolite_instances[gitolite_instance].http.enable_git_backend %} - - location ~ ^.*/git-receive-pack$ { - return 403; - } - - location ~ ^.*/(HEAD|info/refs|objects/(info/.*|[0-9a-f]+/[0-9a-f]+|pack/pack-[0-9a-f]+.(pack|idx))|git-upload-pack)$ { - include fastcgi_params; - - fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend; - fastcgi_param PATH_INFO $uri; - fastcgi_param GIT_PROJECT_ROOT {{ gitolite_base_path }}/{{ gitolite_instance }}/repositories; - - fastcgi_pass unix:/run/fcgiwrap/gitolite-{{ gitolite_instance }}.sock; - } -{% endif %} -} diff --git a/roles/monitoring/graphite/web/tasks/main.yml b/roles/monitoring/graphite/web/tasks/main.yml index 7c796722..e450fac4 100644 --- a/roles/monitoring/graphite/web/tasks/main.yml +++ b/roles/monitoring/graphite/web/tasks/main.yml @@ -41,10 +41,14 @@ include_role: name: uwsgi/app +- name: render nginx-vhost config template + set_fact: + graphite_web_nginx_vhost_content: "{{ lookup('template', 'nginx-vhost.conf.j2') }}" + - name: install nginx vhost vars: nginx_vhost: name: graphite - content: "{{ lookup('template', 'nginx-vhost.conf.j2') }}" + content: "{{ graphite_web_nginx_vhost_content }}" include_role: name: nginx/vhost diff --git a/roles/monitoring/landingpage/defaults/main.yml b/roles/monitoring/landingpage/defaults/main.yml index ad2a3895..8cdaba86 100644 --- a/roles/monitoring/landingpage/defaults/main.yml +++ b/roles/monitoring/landingpage/defaults/main.yml @@ -2,6 +2,12 @@ # monitoring_landingpage_hostnames: # - "mon.example.com" -monitoring_landingpage_acme: no +# monitoring_landingpage_tls: +# certificate_provider: "{{ acme_client }}" #monitoring_landingpage_title: "Example Monitoring Host" + +monitoring_landingpage_services: + - prometheus + - alertmanager + - grafana diff --git a/roles/monitoring/landingpage/tasks/main.yml b/roles/monitoring/landingpage/tasks/main.yml index 3158770b..85543294 100644 --- a/roles/monitoring/landingpage/tasks/main.yml +++ b/roles/monitoring/landingpage/tasks/main.yml @@ -9,21 +9,37 @@ src: index.html.j2 dest: "/var/www/landingpage/index.html" -- name: configure nginx vhost +- name: compute nginx vhost config vars: - nginx_vhost: + monitoring_landingpage_vhost_base: name: landingpage template: generic hostnames: "{{ monitoring_landingpage_hostnames }}" - acme: "{{ monitoring_landingpage_acme }}" locations: '/': root: /var/www/landingpage + monitoring_landingpage_vhost_override__yaml: | + {% if monitoring_landingpage_tls is defined %} + tls: "{{ monitoring_landingpage_tls }}" + {% endif %} + locations: + {% if 'prometheus' in monitoring_landingpage_services %} '/prometheus/': proxy_pass: "http://{{ prometheus_server_web_listen_address | default('127.0.0.1:9090') }}" + {% endif %} + {% if 'alertmanager' in monitoring_landingpage_services %} '/alertmanager/': proxy_pass: "http://{{ prometheus_alertmanager_web_listen_address | default('127.0.0.1:9093') }}" + {% endif %} + {% if 'grafana' in monitoring_landingpage_services %} '/grafana/': proxy_pass: "http://{{ grafana_config_server.http_addr | default('localhost') }}:{{ grafana_config_server.http_port | default(3000) }}" + {% endif %} + set_fact: + monitoring_landingpage_vhost: "{{ monitoring_landingpage_vhost_base | combine(monitoring_landingpage_vhost_override__yaml | from_yaml, recursive=True) }}" + +- name: configure nginx vhost + vars: + nginx_vhost: "{{ monitoring_landingpage_vhost }}" include_role: name: nginx/vhost diff --git a/roles/monitoring/landingpage/templates/index.html.j2 b/roles/monitoring/landingpage/templates/index.html.j2 index a340f1db..a9bb8aca 100644 --- a/roles/monitoring/landingpage/templates/index.html.j2 +++ b/roles/monitoring/landingpage/templates/index.html.j2 @@ -8,9 +8,15 @@ <div style="text-align:center; margin-left:auto; margin-right:auto;"> <h1>{{ monitoring_landingpage_title }}</h1> <ul style="list-style-type: none;"> +{% if 'prometheus' in monitoring_landingpage_services %} <li><a target='_blank' href='/prometheus/'>Prometheus</a></li> +{% endif %} +{% if 'alertmanager' in monitoring_landingpage_services %} <li><a target='_blank' href='/alertmanager/'>Prometheus Alertmanager</a></li> +{% endif %} +{% if 'grafana' in monitoring_landingpage_services %} <li><a target='_blank' href='/grafana/'>Grafana</a></li> +{% endif %} </ul> </div> </body> diff --git a/roles/monitoring/prometheus/exporter/base/tasks/main.yml b/roles/monitoring/prometheus/exporter/base/tasks/main.yml index c69c6e05..235b7241 100644 --- a/roles/monitoring/prometheus/exporter/base/tasks/main.yml +++ b/roles/monitoring/prometheus/exporter/base/tasks/main.yml @@ -21,10 +21,14 @@ - name: create TLS certificate and key import_tasks: tls.yml +- name: render nginx-vhost config template + set_fact: + prometheus_exporter_nginx_vhost_content: "{{ lookup('template', 'nginx-vhost.j2') }}" + - name: configure nginx vhost - import_role: - name: nginx/vhost vars: nginx_vhost: name: prometheus-exporter - content: "{{ lookup('template', 'nginx-vhost.j2') }}" + content: "{{ prometheus_exporter_nginx_vhost_content }}" + include_role: + name: nginx/vhost diff --git a/roles/nginx/vhost/defaults/main.yml b/roles/nginx/vhost/defaults/main.yml index b80a5442..0eb67b42 100644 --- a/roles/nginx/vhost/defaults/main.yml +++ b/roles/nginx/vhost/defaults/main.yml @@ -3,10 +3,14 @@ # default: yes # name: example # template: generic -# acme: yes +# tls: +# certificate_provider: acmetool # hostnames: # - example.com # - www.example.com +# logs: +# access: /var/log/nginx/example_access.log +# error: /var/log/nginx/example_error.log # extra_directives: |- # add_header X-Example-Header "foo"; # locations: @@ -26,7 +30,10 @@ # nginx_vhost: # name: mixed-static-and-proxy # template: generic -# acme: yes +# tls: +# variant: legacy +# hsts: false +# certificate_provider: acmetool # hostnames: # - static.example.com # extra_directives: |- @@ -41,8 +48,30 @@ # add_header X-Example-Header "foo"; # '/subdir/': # alias: /srv/www/foo +# '/private/': +# return: "403" # '/foo/': # proxy_pass: http://127.0.0.1:1234 +# '/custom/': +# custom: |- +# include fastcgi_params; +# fastcgi_param SCRIPT_FILENAME /usr/lib/cgi/foo +# fastcgi_param PATH_INFO $uri; +# fastcgi_pass unix:/run/fcgiwrap/foo.sock; + +# nginx_vhost: +# name: example-custom +# template: generic +# tls: +# variant: legacy +# hsts: false +# certificate_provider: acmetool +# hostnames: +# - static.example.com +# custom: |- +# location / { +# foo "bar"; +# } # nginx_vhost: # name: other-example diff --git a/roles/nginx/vhost/tasks/acme.yml b/roles/nginx/vhost/tasks/acme.yml deleted file mode 100644 index 8a6cddb7..00000000 --- a/roles/nginx/vhost/tasks/acme.yml +++ /dev/null @@ -1,44 +0,0 @@ ---- -- name: check if acme certs already exist - loop: "{{ nginx_vhost.hostnames }}" - loop_control: - loop_var: acme_hostname - stat: - path: "/var/lib/acme/live/{{ acme_hostname }}" - register: acme_cert_stat - -- name: set acmecert_missing_hostnames variable - set_fact: - acmecert_missing_hostnames: "{{ acme_cert_stat.results | acme_cert_nonexistent(nginx_vhost.hostnames) }}" - -- name: link nonexistent hostnames to self-signed interim cert - when: acmecert_missing_hostnames | length > 0 - block: - - name: get id of existing selfsigned interim certificate - command: cat /var/lib/acme/.selfsigned-interim-cert - changed_when: false - check_mode: false - register: selfsigned_interim_cert_id - - - name: set selfsigned_interim_cert_id variable - set_fact: - selfsigned_interim_cert_id: "{{ selfsigned_interim_cert_id.stdout }}" - - - name: link to snakeoil cert for nonexistent hostnames - loop: "{{ acmecert_missing_hostnames }}" - loop_control: - loop_var: acme_missing_hostname - file: - src: "../certs/{{ selfsigned_interim_cert_id }}" - dest: "/var/lib/acme/live/{{ acme_missing_hostname }}" - state: link - -- name: make sure nginx config has been (re)loaded - meta: flush_handlers - -- name: get certificate using acmetool - import_role: - name: x509/acmetool/cert - vars: - acmetool_cert_name: "{{ nginx_vhost.name }}" - acmetool_cert_hostnames: "{{ nginx_vhost.hostnames }}" diff --git a/roles/nginx/vhost/tasks/main.yml b/roles/nginx/vhost/tasks/main.yml index 1b5e3392..2c1f0f29 100644 --- a/roles/nginx/vhost/tasks/main.yml +++ b/roles/nginx/vhost/tasks/main.yml @@ -1,4 +1,15 @@ --- +- name: ensure certificate exists (fake it, until you make it) + when: "'tls' in nginx_vhost" + vars: + x509_certificate_name: "{{ nginx_vhost.name }}" + x509_certificate_hostnames: "{{ nginx_vhost.hostnames }}" + x509_certificate_reload_services: + - nginx + include_role: + name: "x509/{{ nginx_vhost.tls.certificate_provider }}/cert/prepare" + public: true + - name: install nginx configs from template when: "'template' in nginx_vhost" template: @@ -23,5 +34,16 @@ notify: reload nginx - name: generate acme certificate - when: "'acme' in nginx_vhost and nginx_vhost.acme" - include_tasks: acme.yml + when: "'tls' in nginx_vhost" + block: + - name: make sure nginx config has been (re)loaded + meta: flush_handlers + + - name: actually request the certificate + vars: + x509_certificate_name: "{{ nginx_vhost.name }}" + x509_certificate_hostnames: "{{ nginx_vhost.hostnames }}" + x509_certificate_reload_services: + - nginx + include_role: + name: "x509/{{ nginx_vhost.tls.certificate_provider }}/cert/finalize" diff --git a/roles/nginx/vhost/templates/generic.conf.j2 b/roles/nginx/vhost/templates/generic.conf.j2 index 08bf7a60..dae84a2f 100644 --- a/roles/nginx/vhost/templates/generic.conf.j2 +++ b/roles/nginx/vhost/templates/generic.conf.j2 @@ -3,9 +3,20 @@ server { listen [::]:80{% if 'default' in nginx_vhost and nginx_vhost.default %} default_server{% endif %}; server_name {{ nginx_vhost.hostnames | join(' ') }}; -{% if 'acme' in nginx_vhost and nginx_vhost.acme %} - include snippets/acmetool.conf; +{% if 'logs' in nginx_vhost %} +{% if 'access' in nginx_vhost.logs %} + access_log {{ nginx_vhost.logs.access }}; +{% endif %} +{% if 'error' in nginx_vhost.logs %} + error_log {{ nginx_vhost.logs.error }}; +{% endif %} + +{% endif %} +{% if 'tls' in nginx_vhost %} +{% if nginx_vhost.tls.certificate_provider == 'acmetool' or nginx_vhost.tls.certificate_provider == 'uacme' %} + include snippets/{{ nginx_vhost.tls.certificate_provider }}.conf; +{% endif %} location / { return 301 https://$host$request_uri; } @@ -16,20 +27,36 @@ server { listen [::]:443 ssl http2{% if 'default' in nginx_vhost and nginx_vhost.default %} default_server{% endif %}; server_name {{ nginx_vhost.hostnames | join(' ') }}; - include snippets/acmetool.conf; - include snippets/tls{% if 'tls_variant' in nginx_vhost %}-{{ nginx_vhost.tls_variant }}{% endif %}.conf; - ssl_certificate /var/lib/acme/live/{{ nginx_vhost.hostnames[0] }}/fullchain; - ssl_certificate_key /var/lib/acme/live/{{ nginx_vhost.hostnames[0] }}/privkey; +{% if 'logs' in nginx_vhost %} +{% if 'access' in nginx_vhost.logs %} + access_log {{ nginx_vhost.logs.access }}; +{% endif %} +{% if 'error' in nginx_vhost.logs %} + error_log {{ nginx_vhost.logs.error }}; +{% endif %} + +{% endif %} +{% if nginx_vhost.tls.certificate_provider == 'acmetool' or nginx_vhost.tls.certificate_provider == 'uacme' %} + include snippets/{{ nginx_vhost.tls.certificate_provider }}.conf; +{% endif %} + include snippets/tls{% if 'variant' in nginx_vhost.tls %}-{{ nginx_vhost.tls.variant }}{% endif %}.conf; + ssl_certificate {{ x509_certificate_path_fullchain }}; + ssl_certificate_key {{ x509_certificate_path_key }}; +{% if 'hsts' not in nginx_vhost.tls or nginx_vhost.tls.hsts %} include snippets/hsts.conf; +{% endif %} {% endif %} -{% if 'extra_directives' in nginx_vhost %} - {{ nginx_vhost.extra_directives | indent(4) }} +{% if 'custom' in nginx_vhost %} + {{ nginx_vhost.custom | trim | indent(4) }} +{% else %} +{% if 'extra_directives' in nginx_vhost %} + {{ nginx_vhost.extra_directives | trim | indent(4) }} -{% endif %} -{% for path, location in nginx_vhost.locations.items() %} +{% endif %} +{% for path, location in nginx_vhost.locations.items() %} location {{ path }} { -{% if 'proxy_pass' in location %} +{% if 'proxy_pass' in location %} include snippets/proxy-nobuff.conf; proxy_set_header Host $host; include snippets/proxy-forward-headers.conf; @@ -39,36 +66,41 @@ server { proxy_set_header Connection $connection_upgrade; proxy_pass {{ location.proxy_pass }}; -{% if 'proxy_redirect' in location %} -{% for entry in location.proxy_redirect %} +{% if 'proxy_redirect' in location %} +{% for entry in location.proxy_redirect %} proxy_redirect {{ entry.redirect }} {{ entry.replacement }}; -{% endfor %} -{% endif %} -{% if 'proxy_ssl' in location %} -{% for prop in (location.proxy_ssl | list | sort) %} +{% endfor %} +{% endif %} +{% if 'proxy_ssl' in location %} +{% for prop in (location.proxy_ssl | list | sort) %} proxy_ssl_{{ prop }} {{ location.proxy_ssl[prop] }}; -{% endfor %} -{% endif %} -{% else %} -{% if 'root' in location %} +{% endfor %} +{% endif %} +{% elif 'return' in location %} + return {{ location.return }}; +{% elif 'custom' in location %} + {{ location.custom | trim | indent(8) }} +{% else %} +{% if 'root' in location %} root {{ location.root }}; -{% elif 'alias' in location %} +{% elif 'alias' in location %} alias {{ location.alias }}; -{% endif %} -{% if 'index' in location %} +{% endif %} +{% if 'index' in location %} index {{ location.index }}; -{% endif %} -{% if 'autoindex' in location %} +{% endif %} +{% if 'autoindex' in location %} autoindex on; -{% if 'format' in location.autoindex %} +{% if 'format' in location.autoindex %} autoindex_format {{ nginx_vhost.autoindex.format }}; -{% endif %} +{% endif %} +{% endif %} {% endif %} -{% endif %} -{% if 'extra_directives' in location %} +{% if 'extra_directives' in location %} - {{ location.extra_directives | indent(8) }} -{% endif %} + {{ location.extra_directives | trim | indent(8) }} +{% endif %} } -{% endfor %} +{% endfor %} +{% endif %} } diff --git a/roles/x509/acmetool/cert/defaults/main.yml b/roles/x509/acmetool/cert/defaults/main.yml deleted file mode 100644 index ab0afaa3..00000000 --- a/roles/x509/acmetool/cert/defaults/main.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -acmetool_reconcile_disabled: false diff --git a/roles/x509/acmetool/cert/finalize/defaults/main.yml b/roles/x509/acmetool/cert/finalize/defaults/main.yml new file mode 100644 index 00000000..b9a80136 --- /dev/null +++ b/roles/x509/acmetool/cert/finalize/defaults/main.yml @@ -0,0 +1,5 @@ +--- +acmetool_cert_hostnames: "{{ x509_certificate_hostnames }}" +acmetool_cert_name: "{{ x509_certificate_name | default(acmetool_cert_hostnames[0]) }}" + +acmetool_reconcile_disabled: false diff --git a/roles/x509/acmetool/cert/handlers/main.yml b/roles/x509/acmetool/cert/finalize/handlers/main.yml index a7fc43ed..02ffa598 100644 --- a/roles/x509/acmetool/cert/handlers/main.yml +++ b/roles/x509/acmetool/cert/finalize/handlers/main.yml @@ -2,5 +2,6 @@ - name: reconcile acmetool when: not acmetool_reconcile_disabled systemd: + daemon_reload: yes name: acmetool.service state: started diff --git a/roles/x509/acmetool/cert/tasks/main.yml b/roles/x509/acmetool/cert/finalize/tasks/main.yml index 09980dad..abb2d4cb 100644 --- a/roles/x509/acmetool/cert/tasks/main.yml +++ b/roles/x509/acmetool/cert/finalize/tasks/main.yml @@ -3,7 +3,7 @@ vars: acmetool_cert_satisfy: satisfy: - names: "{{ acmetool_cert_hostnames | default([acmetool_cert_name]) }}" + names: "{{ acmetool_cert_hostnames }}" copy: content: "{{ acmetool_cert_config | default({}) | combine(acmetool_cert_satisfy) | to_nice_yaml }}" dest: "/var/lib/acme/desired/{{ acmetool_cert_name }}" diff --git a/roles/x509/acmetool/cert/meta/main.yml b/roles/x509/acmetool/cert/meta/main.yml new file mode 100644 index 00000000..472f5a8c --- /dev/null +++ b/roles/x509/acmetool/cert/meta/main.yml @@ -0,0 +1,4 @@ +--- +dependencies: + - role: x509/acmetool/cert/prepare + - role: x509/acmetool/cert/finalize diff --git a/roles/x509/acmetool/cert/prepare/defaults/main.yml b/roles/x509/acmetool/cert/prepare/defaults/main.yml new file mode 100644 index 00000000..d4eb7c86 --- /dev/null +++ b/roles/x509/acmetool/cert/prepare/defaults/main.yml @@ -0,0 +1,2 @@ +--- +acmetool_cert_hostnames: "{{ x509_certificate_hostnames }}" diff --git a/roles/x509/acmetool/cert/filter_plugins/acme_certs.py b/roles/x509/acmetool/cert/prepare/filter_plugins/acme_certs.py index 179f71e9..179f71e9 100644 --- a/roles/x509/acmetool/cert/filter_plugins/acme_certs.py +++ b/roles/x509/acmetool/cert/prepare/filter_plugins/acme_certs.py diff --git a/roles/x509/acmetool/cert/prepare/handlers/main.yml b/roles/x509/acmetool/cert/prepare/handlers/main.yml new file mode 100644 index 00000000..330bcd11 --- /dev/null +++ b/roles/x509/acmetool/cert/prepare/handlers/main.yml @@ -0,0 +1,10 @@ +--- +- name: reload systemd + systemd: + daemon_reload: yes + +- name: reload services for x509 certificates + loop: "{{ x509_certificate_reload_services | default([]) }}" + service: + name: "{{ item }}" + state: reloaded diff --git a/roles/x509/acmetool/cert/prepare/tasks/main.yml b/roles/x509/acmetool/cert/prepare/tasks/main.yml new file mode 100644 index 00000000..2db332b8 --- /dev/null +++ b/roles/x509/acmetool/cert/prepare/tasks/main.yml @@ -0,0 +1,79 @@ +--- +- name: check if acme certs already exist + loop: "{{ acmetool_cert_hostnames }}" + loop_control: + loop_var: acme_hostname + stat: + path: "/var/lib/acme/live/{{ acme_hostname }}" + register: acme_cert_stat + +- name: set acmecert_missing_hostnames variable + set_fact: + acmecert_missing_hostnames: "{{ acme_cert_stat.results | acme_cert_nonexistent(acmetool_cert_hostnames) }}" + +- name: link nonexistent hostnames to self-signed interim cert + when: acmecert_missing_hostnames | length > 0 + block: + - name: get id of existing selfsigned interim certificate + command: cat /var/lib/acme/.selfsigned-interim-cert + changed_when: false + check_mode: false + register: selfsigned_interim_cert_id + + - name: set selfsigned_interim_cert_id variable + set_fact: + selfsigned_interim_cert_id: "{{ selfsigned_interim_cert_id.stdout }}" + + - name: link to snakeoil cert for nonexistent hostnames + loop: "{{ acmecert_missing_hostnames }}" + loop_control: + loop_var: acme_missing_hostname + file: + src: "../certs/{{ selfsigned_interim_cert_id }}" + dest: "/var/lib/acme/live/{{ acme_missing_hostname }}" + state: link + notify: reload services for x509 certificates + +- name: export paths to certificate files + set_fact: + x509_certificate_path_key: "/var/lib/acme/live/{{ acmetool_cert_hostnames[0] }}/privkey" + x509_certificate_path_cert: "/var/lib/acme/live/{{ acmetool_cert_hostnames[0] }}/cert" + x509_certificate_path_chain: "/var/lib/acme/live/{{ acmetool_cert_hostnames[0] }}/chain" + x509_certificate_path_fullchain: "/var/lib/acme/live/{{ acmetool_cert_hostnames[0] }}/fullchain" + +- name: setup custom renewal script + when: x509_certificate_renewal is defined + block: + - name: install custom hook script + template: + src: reload.sh.j2 + dest: "/etc/acme/hooks/{{ x509_certificate_name }}" + mode: 0755 + + - name: install acmetool systemd unit snippet + when: "'install' in x509_certificate_renewal" + copy: + dest: "/etc/systemd/system/acmetool.service.d/{{ x509_certificate_name }}.conf" + content: | + [Service] + {% for path in (x509_certificate_renewal.install | map(attribute='dest') | map('dirname') | unique | list) %} + ReadWritePaths={{ path }} + {% endfor %} + notify: reload systemd + + - name: remove acmetool systemd unit snippet + when: "'install' not in x509_certificate_renewal" + file: + path: "/etc/systemd/system/acmetool.service.d/{{ x509_certificate_name }}.conf" + state: absent + notify: reload systemd + +- name: remove custom renewal script + when: x509_certificate_renewal is not defined + loop: + - "/etc/systemd/system/acmetool.service.d/{{ x509_certificate_name }}.conf" + - "/etc/acme/hooks/{{ x509_certificate_name }}" + file: + path: "{{ item }}" + state: absent + notify: reload systemd diff --git a/roles/x509/acmetool/cert/prepare/templates/reload.sh.j2 b/roles/x509/acmetool/cert/prepare/templates/reload.sh.j2 new file mode 100644 index 00000000..f4b8259e --- /dev/null +++ b/roles/x509/acmetool/cert/prepare/templates/reload.sh.j2 @@ -0,0 +1,31 @@ +#!/bin/sh +set -e +EVENT_NAME="$1" +[ "$EVENT_NAME" = "live-updated" ] || exit 42 + +MAIN_HOSTNAME="{{ acmetool_cert_hostnames[0] }}" + +while read name; do + certdir="$ACME_STATE_DIR/live/$name" + if [ -z "$name" -o ! -e "$certdir" ]; then + continue + fi + if [ "$name" != "$MAIN_HOSTNAME" ]; then + continue + fi +{% if 'install' in x509_certificate_renewal %} + +{% for file in x509_certificate_renewal.install %} + install{% if 'mode' in file %} -m {{ file.mode }}{% endif %}{% if 'owner' in file %} -o {{ file.owner }}{% endif %}{% if 'owner' in file %} -g {{ file.group }}{% endif %} /dev/null "{{ file.dest }}.new" +{% for src in file.src %} + cat "{{ hostvars[inventory_hostname]['x509_certificate_path_' + src] }}" >> "{{ file.dest }}.new" + mv "{{ file.dest }}.new" "{{ file.dest }}" +{% endfor %} +{% endfor %} +{% endif %} +{% if 'reload' in x509_certificate_renewal %} + + {{ x509_certificate_renewal.reload | trim | indent(2) }} +{% endif %} + break +done diff --git a/roles/x509/selfsigned/base/tasks/main.yml b/roles/x509/selfsigned/base/tasks/main.yml new file mode 100644 index 00000000..51397d67 --- /dev/null +++ b/roles/x509/selfsigned/base/tasks/main.yml @@ -0,0 +1,5 @@ +--- +- name: install needed packages + apt: + name: "{{ python_basename }}-openssl" + state: present diff --git a/roles/x509/selfsigned/cert/finalize/tasks/main.yml b/roles/x509/selfsigned/cert/finalize/tasks/main.yml new file mode 100644 index 00000000..c5b6cafe --- /dev/null +++ b/roles/x509/selfsigned/cert/finalize/tasks/main.yml @@ -0,0 +1,2 @@ +--- +# nothing to do here diff --git a/roles/x509/selfsigned/cert/meta/main.yml b/roles/x509/selfsigned/cert/meta/main.yml new file mode 100644 index 00000000..c7a30d00 --- /dev/null +++ b/roles/x509/selfsigned/cert/meta/main.yml @@ -0,0 +1,4 @@ +--- +dependencies: + - role: x509/selfsigned/cert/prepare + - role: x509/selfsigned/cert/finalize diff --git a/roles/x509/selfsigned/cert/prepare/defaults/main.yml b/roles/x509/selfsigned/cert/prepare/defaults/main.yml new file mode 100644 index 00000000..53dc3b06 --- /dev/null +++ b/roles/x509/selfsigned/cert/prepare/defaults/main.yml @@ -0,0 +1,41 @@ +--- +selfsigned_cert_hostnames: "{{ x509_certificate_hostnames }}" +selfsigned_cert_name: "{{ x509_certificate_name | default(selfsigned_cert_hostnames[0]) }}" + +selfsigned_cert_base_dir: "/etc/ssl" + +# selfsigned_cert_config: +# path: "{{ selfsigned_cert_base_dir }}/{{ selfsigned_cert_name }}" +# mode: "0750" +# owner: root +# group: www-data +# key: +# mode: "0640" +# owner: root +# group: www-data +# type: RSA +# size: 4096 +# cert: +# mode: "0644" +# owner: root +# group: www-data +# country_name: "AT" +# locality_name: "Graz" +# organization_name: "spreadspace" +# organizational_unit_name: "ansible" +# state_or_province_name: "Styria" +# basic_constraints: +# - "CA:TRUE" +# - "pathLenConstraint:0" +# basic_constraints_critical: no +# key_usage: +# - digitalSignature +# - keyAgreement +# key_usage_critical: yes +# extended_key_usage: +# - serverAuth +# extended_key_usage_critical: yes +# create_subject_key_identifier: yes +# digest: SHA256 +# not_before: +0h +# not_after: +520w diff --git a/roles/x509/selfsigned/cert/prepare/handlers/main.yml b/roles/x509/selfsigned/cert/prepare/handlers/main.yml new file mode 100644 index 00000000..b169d6ca --- /dev/null +++ b/roles/x509/selfsigned/cert/prepare/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: reload services for x509 certificates + loop: "{{ x509_certificate_reload_services | default([]) }}" + service: + name: "{{ item }}" + state: reloaded diff --git a/roles/x509/selfsigned/cert/prepare/tasks/main.yml b/roles/x509/selfsigned/cert/prepare/tasks/main.yml new file mode 100644 index 00000000..e7a47742 --- /dev/null +++ b/roles/x509/selfsigned/cert/prepare/tasks/main.yml @@ -0,0 +1,69 @@ +--- +- name: compute path to selfsigned certificate directory + set_fact: + selfsigned_cert_path: "{{ selfsigned_cert_config.path | default([selfsigned_cert_base_dir, selfsigned_cert_name] | path_join) }}" + +- name: create directory for selfsigned certificate + file: + path: "{{ selfsigned_cert_path }}" + state: directory + mode: "{{ selfsigned_cert_config.mode | default('0700') }}" + owner: "{{ selfsigned_cert_config.owner | default(omit) }}" + group: "{{ selfsigned_cert_config.group | default(omit) }}" + notify: reload services for x509 certificates + +- name: generate key for selfsigned certificate + openssl_privatekey: + path: "{{ selfsigned_cert_path }}/{{ selfsigned_cert_name }}-key.pem" + mode: "{{ selfsigned_cert_config.key.mode | default('0600') }}" + owner: "{{ selfsigned_cert_config.key.owner | default(omit) }}" + group: "{{ selfsigned_cert_config.key.group | default(omit) }}" + type: "{{ selfsigned_cert_config.key.type | default(omit) }}" + size: "{{ selfsigned_cert_config.key.size | default(omit) }}" + notify: reload services for x509 certificates + +- name: generate csr for selfsigned certificate + community.crypto.openssl_csr: + path: "{{ selfsigned_cert_path }}/{{ selfsigned_cert_name }}-csr.pem" + mode: "{{ selfsigned_cert_config.cert.mode | default('0644') }}" + owner: "{{ selfsigned_cert_config.cert.owner | default(omit) }}" + group: "{{ selfsigned_cert_config.cert.group | default(omit) }}" + privatekey_path: "{{ selfsigned_cert_path }}/{{ selfsigned_cert_name }}-key.pem" + create_subject_key_identifier: "{{ selfsigned_cert_config.cert.create_subject_key_identifier | default(omit) }}" + digest: "{{ selfsigned_cert_config.cert.digest | default(omit) }}" + common_name: "{{ selfsigned_cert_name }}" + subject_alt_name: "{{ ['DNS:'] | product(selfsigned_cert_hostnames) | map('join') | list }}" + subject_alt_name_critical: yes + use_common_name_for_san: no + country_name: "{{ selfsigned_cert_config.cert.country_name | default(omit) }}" + locality_name: "{{ selfsigned_cert_config.cert.locality_name | default(omit) }}" + organization_name: "{{ selfsigned_cert_config.cert.organization_name | default(omit) }}" + organizational_unit_name: "{{ selfsigned_cert_config.cert.organizational_unit_name | default(omit) }}" + state_or_province_name: "{{ selfsigned_cert_config.cert.state_or_province_name | default(omit) }}" + basic_constraints: "{{ selfsigned_cert_config.cert.basic_constraints | default(omit) }}" + basic_constraints_critical: "{{ selfsigned_cert_config.cert.basic_constraints_critical | default(omit) }}" + key_usage: "{{ selfsigned_cert_config.cert.key_usage | default(omit) }}" + key_usage_critical: "{{ selfsigned_cert_config.cert.key_usage_critical | default(omit) }}" + extended_key_usage: "{{ selfsigned_cert_config.cert.extended_key_usage | default(omit) }}" + extended_key_usage_critical: "{{ selfsigned_cert_config.cert.extended_key_usage_critical | default(omit) }}" + +- name: generate selfsigned certificate + community.crypto.x509_certificate: + path: "{{ selfsigned_cert_path }}/{{ selfsigned_cert_name }}-crt.pem" + mode: "{{ selfsigned_cert_config.cert.mode | default('0644') }}" + owner: "{{ selfsigned_cert_config.cert.owner | default(omit) }}" + group: "{{ selfsigned_cert_config.cert.group | default(omit) }}" + privatekey_path: "{{ selfsigned_cert_path }}/{{ selfsigned_cert_name }}-key.pem" + csr_path: "{{ selfsigned_cert_path }}/{{ selfsigned_cert_name }}-csr.pem" + provider: selfsigned + selfsigned_digest: "{{ selfsigned_cert_config.cert.digest | default(omit) }}" + selfsigned_not_before: "{{ selfsigned_cert_config.cert.not_before | default(omit) }}" + selfsigned_not_after: "{{ selfsigned_cert_config.cert.not_after | default(omit) }}" + notify: reload services for x509 certificates + +- name: export paths to certificate files + set_fact: + x509_certificate_path_key: "{{ selfsigned_cert_path }}/{{ selfsigned_cert_name }}-key.pem" + x509_certificate_path_cert: "{{ selfsigned_cert_path }}/{{ selfsigned_cert_name }}-crt.pem" + x509_certificate_path_chain: "" + x509_certificate_path_fullchain: "{{ selfsigned_cert_path }}/{{ selfsigned_cert_name }}-crt.pem" diff --git a/roles/x509/static/base/tasks/main.yml b/roles/x509/static/base/tasks/main.yml new file mode 100644 index 00000000..c5b6cafe --- /dev/null +++ b/roles/x509/static/base/tasks/main.yml @@ -0,0 +1,2 @@ +--- +# nothing to do here diff --git a/roles/x509/static/cert/finalize/tasks/main.yml b/roles/x509/static/cert/finalize/tasks/main.yml new file mode 100644 index 00000000..c5b6cafe --- /dev/null +++ b/roles/x509/static/cert/finalize/tasks/main.yml @@ -0,0 +1,2 @@ +--- +# nothing to do here diff --git a/roles/x509/static/cert/meta/main.yml b/roles/x509/static/cert/meta/main.yml new file mode 100644 index 00000000..c619208c --- /dev/null +++ b/roles/x509/static/cert/meta/main.yml @@ -0,0 +1,4 @@ +--- +dependencies: + - role: x509/static/cert/prepare + - role: x509/static/cert/finalize diff --git a/roles/x509/static/cert/prepare/defaults/main.yml b/roles/x509/static/cert/prepare/defaults/main.yml new file mode 100644 index 00000000..d632a5de --- /dev/null +++ b/roles/x509/static/cert/prepare/defaults/main.yml @@ -0,0 +1,35 @@ +--- +static_cert_hostnames: "{{ x509_certificate_hostnames }}" +static_cert_name: "{{ x509_certificate_name | default(static_cert_hostnames[0]) }}" + +static_cert_base_dir: "/etc/ssl" + +# static_cert_config: +# path: "{{ static_cert_base_dir }}/{{ static_cert_name }}" +# mode: "0750" +# owner: root +# group: www-data +# key: +# mode: "0640" +# owner: root +# group: www-data +# content: | +# -----BEGIN RSA PRIVATE KEY----- +# ... +# -----END RSA PRIVATE KEY----- +# cert: +# mode: "0644" +# owner: root +# group: www-data +# content: | +# -----BEGIN CERTIFICATE----- +# ... +# -----END CERTIFICATE----- +# chain: +# mode: "0644" +# owner: root +# group: www-data +# content: | +# -----BEGIN CERTIFICATE----- +# ... +# -----END CERTIFICATE----- diff --git a/roles/x509/static/cert/prepare/handlers/main.yml b/roles/x509/static/cert/prepare/handlers/main.yml new file mode 100644 index 00000000..b169d6ca --- /dev/null +++ b/roles/x509/static/cert/prepare/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: reload services for x509 certificates + loop: "{{ x509_certificate_reload_services | default([]) }}" + service: + name: "{{ item }}" + state: reloaded diff --git a/roles/x509/static/cert/prepare/tasks/main.yml b/roles/x509/static/cert/prepare/tasks/main.yml new file mode 100644 index 00000000..03df7542 --- /dev/null +++ b/roles/x509/static/cert/prepare/tasks/main.yml @@ -0,0 +1,81 @@ +--- +- name: compute path to static certificate directory + set_fact: + static_cert_path: "{{ static_cert_config.path | default([static_cert_base_dir, static_cert_name] | path_join) }}" + +- name: create directory for static certificate + file: + path: "{{ static_cert_path }}" + state: directory + mode: "{{ static_cert_config.mode | default('0700') }}" + owner: "{{ static_cert_config.owner | default(omit) }}" + group: "{{ static_cert_config.group | default(omit) }}" + notify: reload services for x509 certificates + +- name: install key for static certificate + copy: + content: "{{ static_cert_config.key.content }}" + dest: "{{ static_cert_path }}/{{ static_cert_name }}-key.pem" + mode: "{{ static_cert_config.key.mode | default('0600') }}" + owner: "{{ static_cert_config.key.owner | default(omit) }}" + group: "{{ static_cert_config.key.group | default(omit) }}" + notify: reload services for x509 certificates + +- name: install static certificate + copy: + content: "{{ static_cert_config.cert.content }}" + dest: "{{ static_cert_path }}/{{ static_cert_name }}-crt.pem" + mode: "{{ static_cert_config.cert.mode | default('0644') }}" + owner: "{{ static_cert_config.cert.owner | default(omit) }}" + group: "{{ static_cert_config.cert.group | default(omit) }}" + notify: reload services for x509 certificates + +- name: export paths to basic certificate files + set_fact: + x509_certificate_path_key: "{{ static_cert_path }}/{{ static_cert_name }}-key.pem" + x509_certificate_path_fullchain: "{{ static_cert_path }}/{{ static_cert_name }}-crt.pem" + x509_certificate_path_cert: "{{ static_cert_path }}/{{ static_cert_name }}-crt.pem" + +- name: install chain and fullchain for static certificate + when: "'chain' in static_cert_config" + block: + - name: install chain for static certificate + copy: + content: "{{ static_cert_config.chain.content }}" + dest: "{{ static_cert_path }}/{{ static_cert_name }}-chain.pem" + mode: "{{ static_cert_config.chain.mode | default('0644') }}" + owner: "{{ static_cert_config.chain.owner | default(omit) }}" + group: "{{ static_cert_config.chain.group | default(omit) }}" + notify: reload services for x509 certificates + + - name: install fullchain for static certificate + copy: + content: | + {{ static_cert_config.cert.content | trim }} + {{ static_cert_config.chain.content }} + dest: "{{ static_cert_path }}/{{ static_cert_name }}-fullchain.pem" + mode: "{{ static_cert_config.cert.mode | default('0644') }}" + owner: "{{ static_cert_config.cert.owner | default(omit) }}" + group: "{{ static_cert_config.cert.group | default(omit) }}" + notify: reload services for x509 certificates + + - name: export paths to additional certificate files + set_fact: + x509_certificate_path_chain: "{{ static_cert_path }}/{{ static_cert_name }}-chain.pem" + x509_certificate_path_fullchain: "{{ static_cert_path }}/{{ static_cert_name }}-fullchain.pem" + +- name: make sure chain and fullchain files are removed + when: "'chain' not in static_cert_config" + block: + - name: remove chain/fullchain files + loop: + - chain + - fullchain + file: + path: "{{ static_cert_path }}/{{ static_cert_name }}-{{ item }}.pem" + state: absent + notify: reload services for x509 certificates + + - name: make sure variable that points to the chain certificate file is unset + set_fact: + x509_certificate_path_chain: "" diff --git a/roles/x509/uacme/base/defaults/main.yml b/roles/x509/uacme/base/defaults/main.yml index 50ac8019..264bc2d9 100644 --- a/roles/x509/uacme/base/defaults/main.yml +++ b/roles/x509/uacme/base/defaults/main.yml @@ -4,3 +4,5 @@ uacme_directory_server: "{{ acme_directory_server }}" ### this defaults to '/var/run/acme/acme-challenge' # uacme_challenge_webroot_path: "/path/to/acme-challenge" + +# uacme_eab: <keyid>:base64(<key>) diff --git a/roles/x509/uacme/base/tasks/main.yml b/roles/x509/uacme/base/tasks/main.yml index 3d1c8404..3473d541 100644 --- a/roles/x509/uacme/base/tasks/main.yml +++ b/roles/x509/uacme/base/tasks/main.yml @@ -7,7 +7,7 @@ state: present - name: create acme account key - command: "uacme -c /var/lib/uacme.d -a '{{ uacme_directory_server }}' -y new '{{ uacme_account_email }}'" + command: "uacme -c /var/lib/uacme.d -a '{{ uacme_directory_server }}' -y{% if uacme_eab is defined %} -e {{ uacme_eab }}{% endif %} new '{{ uacme_account_email }}'" args: creates: /var/lib/uacme.d/private/key.pem @@ -44,7 +44,28 @@ alias {{ uacme_challenge_webroot_path | default('/var/run/acme/acme-challenge') }}/; } -- name: generate selfsigned interim certificate - include_tasks: selfsigned.yml +- name: install reconcile script + template: + src: uacme-reconcile.sh.j2 + dest: /usr/local/bin/uacme-reconcile.sh + mode: 0755 -## TODO: add global automatic refresher? +- name: install systemd unit for automatic refresh + loop: + - service + - timer + template: + src: "uacme-reconcile.{{ item }}.j2" + dest: "/etc/systemd/system/uacme-reconcile.{{ item }}" + +- name: create system unit snippet directory + file: + path: /etc/systemd/system/uacme-reconcile.service.d/ + state: directory + +- name: make sure systemd timer for automatic refresh is enabled and started + systemd: + daemon_reload: yes + name: uacme-reconcile.timer + state: started + enabled: yes diff --git a/roles/x509/uacme/base/tasks/selfsigned.yml b/roles/x509/uacme/base/tasks/selfsigned.yml deleted file mode 100644 index fff77d42..00000000 --- a/roles/x509/uacme/base/tasks/selfsigned.yml +++ /dev/null @@ -1,47 +0,0 @@ ---- -- name: create directories for selfsigned interim certificate - loop: - - path: private/.self-signed - mode: "0700" - - path: .self-signed - mode: "0755" - loop_control: - label: "{{ item.path }}" - file: - path: "/var/lib/uacme.d/{{ item.path }}" - state: directory - mode: "{{ item.mode }}" - -- name: generate private key for selfsigned interim certificate - openssl_privatekey: - path: /var/lib/uacme.d/private/.self-signed/key.pem - mode: 0600 - -- name: generate csr for selfsigned interim certificate - community.crypto.openssl_csr_pipe: - privatekey_path: /var/lib/uacme.d/private/.self-signed/key.pem - common_name: "{{ ansible_fqdn }}" - register: selfsigned_interim_cert_req - changed_when: false - -### this is needed because strftime filter in ansible is exceptionally stupid -### see: https://github.com/ansible/ansible/issues/39835 -- name: get remote date-time 10s ago - command: date -d '10 seconds ago' -u '+%Y%m%d%H%M%SZ' - register: remote_datetime_10sago - changed_when: false - -- name: get remote date-time now - command: date -u '+%Y%m%d%H%M%SZ' - register: remote_datetime_now - changed_when: false - -- name: generate selfsigned interim certificate - community.crypto.x509_certificate: - path: /var/lib/uacme.d/.self-signed/cert.pem - privatekey_path: /var/lib/uacme.d/private/.self-signed/key.pem - csr_content: "{{ selfsigned_interim_cert_req.csr }}" - provider: selfsigned - ## make sure the certificate is not valid anymore to force uacme to create a new cert - selfsigned_not_before: "{{ remote_datetime_10sago.stdout }}" - selfsigned_not_after: "{{ remote_datetime_now.stdout }}" diff --git a/roles/x509/uacme/base/templates/uacme-reconcile.service.j2 b/roles/x509/uacme/base/templates/uacme-reconcile.service.j2 new file mode 100644 index 00000000..c2fe917a --- /dev/null +++ b/roles/x509/uacme/base/templates/uacme-reconcile.service.j2 @@ -0,0 +1,18 @@ +[Unit] +Description=Reconcile Let's Encrypt certificates using uacme + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/uacme-reconcile.sh +TimeoutStartSec=5min +CapabilityBoundingSet=CAP_CHOWN CAP_NET_BIND_SERVICE +NoNewPrivileges=yes +PrivateTmp=yes +PrivateDevices=yes +ProtectSystem=strict +ReadWritePaths=/var/lib/uacme.d {{ uacme_challenge_webroot_path | default('/var/run/acme/acme-challenge') }} +ProtectHome=yes +ProtectKernelTunables=yes +ProtectControlGroups=yes +RestrictRealtime=yes +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 diff --git a/roles/x509/uacme/base/templates/uacme-reconcile.sh.j2 b/roles/x509/uacme/base/templates/uacme-reconcile.sh.j2 new file mode 100644 index 00000000..ea02841d --- /dev/null +++ b/roles/x509/uacme/base/templates/uacme-reconcile.sh.j2 @@ -0,0 +1,32 @@ +#!/bin/bash + +declare -a csr_files +if [ -n "$1" ]; then + csr_files+=("/var/lib/uacme.d/$1/$1.csr") +else + readarray -d '' csr_files < <(find /var/lib/uacme.d -name "*.csr" -print0) +fi + +export UACME_CHALLENGE_PATH="{{ uacme_challenge_webroot_path | default('/var/run/acme/acme-challenge') }}" + +failed=0 +for csr_file in "${csr_files[@]}"; do + id=$(basename -s .csr "$csr_file") + uacme -c /var/lib/uacme.d -a "{{ uacme_directory_server }}" -h /usr/share/uacme/uacme.sh -n issue "$csr_file" + case $? in + 0) + echo "$id successfully (re)issued." + if [ -x "/var/lib/uacme.d/$id/updated.sh" ]; then + /var/lib/uacme.d/$id/updated.sh + fi + ;; + 1) + echo "$id not updated." + ;; + *) + failed=1 + ;; + esac +done + +exit $failed diff --git a/roles/x509/uacme/base/templates/uacme-reconcile.timer.j2 b/roles/x509/uacme/base/templates/uacme-reconcile.timer.j2 new file mode 100644 index 00000000..6d37a162 --- /dev/null +++ b/roles/x509/uacme/base/templates/uacme-reconcile.timer.j2 @@ -0,0 +1,10 @@ +[Unit] +Description=Reconcile Let's Encrypt certificates using uacme + +[Timer] +OnCalendar=*-*-* 00,12:00:00 +RandomizedDelaySec=1h +Persistent=yes + +[Install] +WantedBy=timers.target diff --git a/roles/x509/uacme/cert/finalize/defaults/main.yml b/roles/x509/uacme/cert/finalize/defaults/main.yml new file mode 100644 index 00000000..611dc6fc --- /dev/null +++ b/roles/x509/uacme/cert/finalize/defaults/main.yml @@ -0,0 +1,3 @@ +--- +uacme_cert_hostnames: "{{ x509_certificate_hostnames }}" +uacme_cert_name: "{{ x509_certificate_name | default(uacme_cert_hostnames[0]) }}" diff --git a/roles/x509/uacme/cert/finalize/tasks/main.yml b/roles/x509/uacme/cert/finalize/tasks/main.yml new file mode 100644 index 00000000..6578c418 --- /dev/null +++ b/roles/x509/uacme/cert/finalize/tasks/main.yml @@ -0,0 +1,5 @@ +--- +- name: running uacme issue command + command: "/usr/local/bin/uacme-reconcile.sh '{{ uacme_cert_name }}'" + register: uacme_reconcile + changed_when: "'not updated.' not in uacme_reconcile.stdout" diff --git a/roles/x509/uacme/cert/meta/main.yml b/roles/x509/uacme/cert/meta/main.yml new file mode 100644 index 00000000..5106342c --- /dev/null +++ b/roles/x509/uacme/cert/meta/main.yml @@ -0,0 +1,4 @@ +--- +dependencies: + - role: x509/uacme/cert/prepare + - role: x509/uacme/cert/finalize diff --git a/roles/x509/uacme/cert/prepare/defaults/main.yml b/roles/x509/uacme/cert/prepare/defaults/main.yml new file mode 100644 index 00000000..b15c1e44 --- /dev/null +++ b/roles/x509/uacme/cert/prepare/defaults/main.yml @@ -0,0 +1,15 @@ +--- +uacme_cert_hostnames: "{{ x509_certificate_hostnames }}" +uacme_cert_name: "{{ x509_certificate_name | default(uacme_cert_hostnames[0]) }}" + +# uacme_cert_config: +# key: +# mode: "0640" +# owner: root +# group: www-data +# type: RSA +# size: 4096 +# cert: +# mode: "0644" +# owner: root +# group: www-data diff --git a/roles/x509/uacme/cert/prepare/handlers/main.yml b/roles/x509/uacme/cert/prepare/handlers/main.yml new file mode 100644 index 00000000..330bcd11 --- /dev/null +++ b/roles/x509/uacme/cert/prepare/handlers/main.yml @@ -0,0 +1,10 @@ +--- +- name: reload systemd + systemd: + daemon_reload: yes + +- name: reload services for x509 certificates + loop: "{{ x509_certificate_reload_services | default([]) }}" + service: + name: "{{ item }}" + state: reloaded diff --git a/roles/x509/uacme/cert/prepare/tasks/main.yml b/roles/x509/uacme/cert/prepare/tasks/main.yml new file mode 100644 index 00000000..a83651b3 --- /dev/null +++ b/roles/x509/uacme/cert/prepare/tasks/main.yml @@ -0,0 +1,112 @@ +--- +- name: create directory for uacme-controlled certificate + file: + path: "/var/lib/uacme.d/{{ uacme_cert_name }}" + state: directory + +- name: generate key for uacme-controlled certificate + openssl_privatekey: + path: "/var/lib/uacme.d/{{ uacme_cert_name }}/key.pem" + mode: "{{ uacme_cert_config.key.mode | default('0600') }}" + owner: "{{ uacme_cert_config.key.owner | default(omit) }}" + group: "{{ uacme_cert_config.key.group | default(omit) }}" + type: "{{ uacme_cert_config.key.type | default(omit) }}" + size: "{{ uacme_cert_config.key.size | default(omit) }}" + notify: reload services for x509 certificates + +- name: generate csr for uacme-controlled certificate + community.crypto.openssl_csr: + path: "/var/lib/uacme.d/{{ uacme_cert_name }}/{{ uacme_cert_name }}.csr" + mode: "{{ uacme_cert_config.cert.mode | default('0644') }}" + owner: "{{ uacme_cert_config.cert.owner | default(omit) }}" + group: "{{ uacme_cert_config.cert.group | default(omit) }}" + privatekey_path: "/var/lib/uacme.d/{{ uacme_cert_name }}/key.pem" + common_name: "{{ uacme_cert_hostnames[0] }}" + subject_alt_name: "{{ ['DNS:'] | product(uacme_cert_hostnames) | map('join') | list }}" + subject_alt_name_critical: yes + use_common_name_for_san: no + +- name: test if uacme-controlled certificate already exists + stat: + path: "/var/lib/uacme.d/{{ uacme_cert_name }}/{{ uacme_cert_name }}-cert.pem" + register: uacme_cert_file + +- name: generate selfsigned interim certificate + when: not uacme_cert_file.stat.exists + block: + ### this is needed because strftime filter in ansible is exceptionally stupid + ### see: https://github.com/ansible/ansible/issues/39835 + - name: get remote date-time 10s ago + command: date -d '10 seconds ago' -u '+%Y%m%d%H%M%SZ' + register: remote_datetime_10sago + changed_when: false + + - name: get remote date-time now + command: date -u '+%Y%m%d%H%M%SZ' + register: remote_datetime_now + changed_when: false + + - name: generate selfsigned interim certificate + community.crypto.x509_certificate: + path: "/var/lib/uacme.d/{{ uacme_cert_name }}/{{ uacme_cert_name }}-cert.pem" + mode: "{{ uacme_cert_config.cert.mode | default('0644') }}" + owner: "{{ uacme_cert_config.cert.owner | default(omit) }}" + group: "{{ uacme_cert_config.cert.group | default(omit) }}" + privatekey_path: "/var/lib/uacme.d/{{ uacme_cert_name }}/key.pem" + csr_path: "/var/lib/uacme.d/{{ uacme_cert_name }}/{{ uacme_cert_name }}.csr" + provider: selfsigned + ## make sure the certificate is not valid anymore to force uacme to create a new cert + selfsigned_not_before: "{{ remote_datetime_10sago.stdout }}" + selfsigned_not_after: "{{ remote_datetime_now.stdout }}" + return_content: yes + register: uacme_cert_selfsigned + notify: reload services for x509 certificates + + - name: make sure cert-only file exists + copy: + content: "{{ uacme_cert_selfsigned.certificate }}" + dest: "/var/lib/uacme.d/{{ uacme_cert_name }}/crt.pem" + mode: "{{ uacme_cert_config.cert.mode | default('0644') }}" + owner: "{{ uacme_cert_config.cert.owner | default(omit) }}" + group: "{{ uacme_cert_config.cert.group | default(omit) }}" + notify: reload services for x509 certificates + + - name: make sure the chain file exists + copy: + content: "" + dest: "/var/lib/uacme.d/{{ uacme_cert_name }}/chain.pem" + mode: "{{ uacme_cert_config.cert.mode | default('0644') }}" + owner: "{{ uacme_cert_config.cert.owner | default(omit) }}" + group: "{{ uacme_cert_config.cert.group | default(omit) }}" + notify: reload services for x509 certificates + +- name: export paths to certificate files + set_fact: + x509_certificate_path_key: "/var/lib/uacme.d/{{ uacme_cert_name }}/key.pem" + x509_certificate_path_cert: "/var/lib/uacme.d/{{ uacme_cert_name }}/crt.pem" + x509_certificate_path_chain: "/var/lib/uacme.d/{{ uacme_cert_name }}/chain.pem" + x509_certificate_path_fullchain: "/var/lib/uacme.d/{{ uacme_cert_name }}/{{ uacme_cert_name }}-cert.pem" + +- name: install script to be called when new certificate is generated + template: + src: updated.sh.j2 + dest: "/var/lib/uacme.d/{{ uacme_cert_name }}/updated.sh" + mode: 0755 + +- name: install systemd unit snippet + when: "x509_certificate_renewal is defined and 'install' in x509_certificate_renewal" + copy: + dest: "/etc/systemd/system/uacme-reconcile.service.d/{{ x509_certificate_name }}.conf" + content: | + [Service] + {% for path in (x509_certificate_renewal.install | map(attribute='dest') | map('dirname') | unique | list) %} + ReadWritePaths={{ path }} + {% endfor %} + notify: reload systemd + +- name: remove systemd unit snippet + when: "x509_certificate_renewal is undefined or 'install' not in x509_certificate_renewal" + file: + path: "/etc/systemd/system/uacme-reconcile.service.d/{{ x509_certificate_name }}.conf" + state: absent + notify: reload systemd diff --git a/roles/x509/uacme/cert/prepare/templates/updated.sh.j2 b/roles/x509/uacme/cert/prepare/templates/updated.sh.j2 new file mode 100644 index 00000000..275ca189 --- /dev/null +++ b/roles/x509/uacme/cert/prepare/templates/updated.sh.j2 @@ -0,0 +1,33 @@ +#!/bin/sh + +BASE_D="/var/lib/uacme.d/{{ uacme_cert_name }}" + +# split fullchain and fix permissions +awk '{if(length($0) > 0) print} /-----END CERTIFICATE-----/ { exit }' "$BASE_D/{{ uacme_cert_name }}-cert.pem" > "$BASE_D/crt.pem" +awk '(show==1) {if(length($0) > 0) print} /-----END CERTIFICATE-----/ { show=1 }' "$BASE_D/{{ uacme_cert_name }}-cert.pem" > "$BASE_D/chain.pem" +chmod "{{ uacme_cert_config.cert.mode | default('0644') }}" $BASE_D/{{ uacme_cert_name }}-cert.pem $BASE_D/crt.pem $BASE_D/chain.pem +{% if uacme_cert_config.cert.owner is defined %} +chown "{{ uacme_cert_config.cert.owner }}" $BASE_D/{{ uacme_cert_name }}-cert.pem $BASE_D/crt.pem $BASE_D/chain.pem +{% endif %} +{% if uacme_cert_config.cert.group is defined %} +chgrp "{{ uacme_cert_config.cert.group }}" $BASE_D/{{ uacme_cert_name }}-cert.pem $BASE_D/crt.pem $BASE_D/chain.pem +{% endif %} +{% if x509_certificate_renewal is defined and 'install' in x509_certificate_renewal %} +{% for file in x509_certificate_renewal.install %} + +install{% if 'mode' in file %} -m {{ file.mode }}{% endif %}{% if 'owner' in file %} -o {{ file.owner }}{% endif %}{% if 'owner' in file %} -g {{ file.group }}{% endif %} /dev/null "{{ file.dest }}.new" +{% for src in file.src %} +cat "{{ hostvars[inventory_hostname]['x509_certificate_path_' + src] }}" >> "{{ file.dest }}.new" +mv "{{ file.dest }}.new" "{{ file.dest }}" +{% endfor %} +{% endfor %} +{% endif %} + +## reload services +{% for service in (x509_certificate_reload_services | default([])) %} +systemctl reload "{{ service }}.service" +{% endfor %} +{% if x509_certificate_renewal is defined and 'reload' in x509_certificate_renewal %} + +{{ x509_certificate_renewal.reload | trim }} +{% endif %} |