ssl, nginx: flexibility and modularity improvements

This commit is contained in:
dogeystamp 2022-06-05 13:20:53 -04:00
parent 495216318c
commit 11b11194cb
Signed by: dogeystamp
GPG Key ID: 7225FE3592EFFA38
26 changed files with 343 additions and 164 deletions

View File

@ -9,9 +9,6 @@
### Misc settings
# Email address for Let's Encrypt and DNS
email: dogeystamp@disroot.org
# Could be sudo instead
escalation_method: doas
@ -64,7 +61,12 @@ util_pack:
### Network settings (nameserver, address, etc.)
domain: d.nerdpol.ovh
# Domain to send dynamic DNS updates to
dyndns_domain: d.nerdpol.ovh
# Domain actually used for the site (this will be set up to forward to dyndns_domain)
# Set this to the same as dyndns_domain if your registrar supports dynamic DNS natively
domain: dogeystamp.com
# Forward DNS queries to
dns_forward: 1.1.1.1
@ -86,23 +88,94 @@ gateway: 192.168.0.1
# Connection interface for static IP
interface: eth0
# Email to send renewal notices to
acme_email: "{{ email }}"
# ACME directory to use
# acme_dir: "https://acme-v02.api.letsencrypt.org/directory"
acme_dir: "https://acme.zerossl.com/v2/DV90"
# Algorithm for ACME External Account Binding
acme_eab_alg: HS256
# Settings for templates in templates/srv_conf/ and service configs
nginx_services:
wiki:
path: "/"
navidrome:
path: "/"
gitea:
path: "/"
synapse:
# Synapse does not support a prefix path.
max_body_size: 50M
website:
path: "/"
# List of nginx server blocks
# domain is the domain this server block listens to
# ssl_cert is the name of the SSL certificate and key (see roles/networking/ssl)
# listens sets the ports the block will listen to
# services is a list of services (templates/srv_conf/) to place in this domain
# Ensure no two services have the same location (domain and path)
default_listens:
- "443 ssl http2"
- "[::]:443 ssl http2"
server_blocks:
wiki:
domain: "wiki.{{ domain }}"
ssl_cert: "{{ domain }}"
listens: "{{ default_listens }}"
services:
- wiki
navidrome:
domain: "mus.{{ domain }}"
ssl_cert: "{{ domain }}"
listens: "{{ default_listens }}"
services:
- navidrome
gitea:
domain: "git.{{ domain }}"
ssl_cert: "{{ domain }}"
listens: "{{ default_listens }}"
services:
- gitea
synapse:
domain: "m.{{ domain }}"
ssl_cert: "{{ domain }}"
listens:
- "443 ssl http2"
- "[::]:443 ssl http2"
- "8448 ssl http2 default_server"
- "[::]:8448 ssl http2 default_server"
services:
- synapse
website:
domain: "www.{{ domain }}"
ssl_cert: "{{ domain }}"
listens: "{{ default_listens }}"
services:
- website
redirect:
domain: "{{ dyndns_domain }}"
ssl_cert: "{{ dyndns_domain }}"
listens: "{{ default_listens }}"
services:
- redirect
# Settings for other services not handled by nginx
service_info:
coturn:
domain: "stun.{{ domain }}"
ssl_cert: "stun.{{ domain }}"
### Mediawiki farm variables
# Internal names for the wikis, used for filenames and URLs
wiki_names:
- wiki
- bepp
- rw
@ -131,8 +204,15 @@ form_secret: "secret"
coturn_secret_key: "secret"
# SSL ACME External Account Binding secrets (optional: required for some CAs)
acme_eab_kid: "secret"
acme_eab_key: "secret"
# ZeroSSL
zerossl_acme_eab_kid: "secret"
zerossl_acme_eab_key: "secret"
# If you have a different email for ZeroSSL
zerossl_email: "you@example.com"
# Email address for ACME and DNS
email: me@example.com

View File

@ -7,6 +7,6 @@ protocol=dyndns2
use=web, web=https://ip.me
ssl=yes # yes = use https for updates
server=ipv4.nsupdate.info
login={{ domain }}
login={{ dyndns_domain }}
password='{{ ddclient_pass }}'
{{ domain }}
{{ dyndns_domain }}

View File

@ -1,5 +1,5 @@
$TTL 604800
@ IN SOA {{ domain }}. {{ email }}. (
@ IN SOA {{ dyndns_domain }}. {{ email }}. (
3 ; Serial
604800 ; Refresh
86400 ; Retry
@ -10,4 +10,4 @@ ns IN A {{ local_ip }}
@ IN NS localhost.
@ IN A {{ local_ip }}
@ IN AAAA ::1
{{ domain }} IN A {{ local_ip }}
{{ dyndns_domain }} IN A {{ local_ip }}

View File

@ -27,9 +27,9 @@ zone "0.0.127.in-addr.arpa" IN {
file "127.0.0.zone";
};
zone "{{ domain }}" {
zone "{{ dyndns_domain }}" {
type master;
file "/var/named/{{ domain }}";
file "/var/named/{{ dyndns_domain }}";
};

View File

@ -0,0 +1,30 @@
# List of domains to get an SSL cert for
# There will be a *single* certificate covering all of these
# Ensure that your CA supports multi-domain certs
ssl_domains:
- "site.dogeystamp.com"
- "d.nerdpol.ovh"
- "{{ wiki_domain }}"
# File name for the certificate
cert_name: "{{ ssl_domains[0] }}"
# File name for the private key
key_name: "{{ cert_name }}"
# Account name for the account key
account_name: "account"
# Email to send renewal notices to
acme_email: "{{ email }}"
# ACME directory to use
# (Staging directoy by default.)
acme_dir: "https://acme-staging-v02.api.letsencrypt.org/directory"
# SSL ACME External Account Binding settings (optional: required for some CAs)
acme_eab: {
alg: HS256,
key: "",
kid: "",
}

View File

@ -12,19 +12,13 @@
name: sslr
state: present
- name: Add turnserver to SSL read group
user:
name: "turnserver"
append: yes
groups: sslr
- name: Create directories for ACME
file:
path: "/etc/ssl-acme/{{ item }}"
state: directory
owner: root
group: root
mode: 0711
mode: 0755
with_items:
- account
- certs
@ -33,52 +27,52 @@
- name: Generate ACME account key
community.crypto.openssl_privatekey:
path: "/etc/ssl-acme/account/account.key"
path: "/etc/ssl-acme/account/{{ account_name }}.key"
owner: root
group: sslr
mode: 0640
- name: Generate ACME private key
community.crypto.openssl_privatekey:
path: "/etc/ssl-acme/keys/{{ domain }}.key"
path: "/etc/ssl-acme/keys/{{ key_name }}.key"
owner: root
group: sslr
mode: 0640
- name: Check if certificate exists
- name: Check if certificate file exists
stat:
path: "/etc/ssl-acme/certs/{{ domain }}.crt"
path: "/etc/ssl-acme/certs/{{ cert_name }}.crt"
register: cert_file
- name: Check if certificate is expired
community.crypto.x509_certificate_info:
path: "/etc/ssl-acme/certs/{{ domain }}.crt"
path: "{{ cert_file.stat.path }}"
valid_at:
now: "+3w"
register: result
when: cert_file.stat.exists
register: expired_cert
- name: Determine whether the certificate should be regenerated
set_fact:
cert_regen: yes
when: not cert_file.stat.exists or result.expired | bool
to_regen: "{{ ssl_domains }}"
when: not cert_file.stat.exists or not expired_cert.valid_at.now
- name: Configure nginx for ACME
template:
src: nginx_bare.conf.j2
dest: /etc/nginx/nginx.conf
when: cert_regen is defined
when: to_regen is defined
- name: Restart nginx service
service:
name: nginx
state: restarted
enabled: yes
when: cert_regen is defined
when: to_regen is defined
- name: Create ACME account
community.crypto.acme_account:
account_key_src: /etc/ssl-acme/account/account.key
account_key_src: "/etc/ssl-acme/account/{{ account_name }}.key"
state: present
allow_creation: yes
contact:
@ -86,66 +80,65 @@
acme_directory: "{{ acme_dir }}"
terms_agreed: 1
acme_version: 2
external_account_binding: {alg: "{{ acme_eab_alg }}", key: "{{ acme_eab_key }}", kid: "{{ acme_eab_kid }}"}
external_account_binding: "{{ (acme_eab.key != '') | ternary(acme_eab, omit) }}"
register: account
when: cert_regen is defined
when: to_regen is defined
- name: Generate ACME CSR
community.crypto.openssl_csr:
path: "/etc/ssl-acme/csrs/{{ domain }}.csr"
common_name: "{{ domain }}"
subject_alt_name: "DNS:{{ domain }}"
privatekey_path: "/etc/ssl-acme/keys/{{ domain }}.key"
when: cert_regen is defined
path: "/etc/ssl-acme/csrs/{{ cert_name }}.csr"
subject_alt_name: "{{ to_regen | map('regex_replace', '^', 'DNS:') | join(',') }}"
privatekey_path: "/etc/ssl-acme/keys/{{ key_name }}.key"
when: to_regen is defined
- name: Retrieve ACME challenge
community.crypto.acme_certificate:
acme_directory: "{{ acme_dir }}"
acme_version: 2
account_key_src: /etc/ssl-acme/account/account.key
account_key_src: "/etc/ssl-acme/account/{{ account_name }}.key"
account_uri: "{{ account.account_uri }}"
account_email: "{{ acme_email }}"
terms_agreed: 1
challenge: http-01
csr: "/etc/ssl-acme/csrs/{{ domain }}.csr"
dest: "/etc/ssl-acme/certs/{{ domain }}.crt"
fullchain_dest: "/etc/ssl-acme/certs/fullchain_{{ domain }}.crt"
csr: "/etc/ssl-acme/csrs/{{ cert_name }}.csr"
dest: "/etc/ssl-acme/certs/{{ cert_name }}.crt"
fullchain_dest: "/etc/ssl-acme/certs/fullchain_{{ cert_name }}.crt"
remaining_days: 91
register: acme_challenge
when: cert_regen is defined
when: to_regen is defined
- name: Create ACME challenge directory
file:
path: "{{ webroot }}/.well-known/acme-challenge"
path: "{{ webroot }}/{{ item }}/.well-known/acme-challenge"
state: directory
owner: root
group: root
mode: 0755
when: cert_regen is defined
with_items: "{{ to_regen }}"
when: to_regen is defined
- name: Add ACME challenge files
copy:
content: "{{ acme_challenge['challenge_data'][item]['http-01']['resource_value'] }}"
dest: "{{ webroot }}/{{ acme_challenge['challenge_data'][item]['http-01']['resource'] }}"
content: "{{ item.value['http-01']['resource_value'] }}"
dest: "{{ webroot }}/{{ item.key }}/{{ item.value['http-01']['resource'] }}"
owner: root
group: root
mode: 644
with_items:
- "{{ domain }}"
when: cert_regen is defined
with_items: "{{ acme_challenge['challenge_data'] | dict2items }}"
when: to_regen is defined
- name: Complete ACME challenge
community.crypto.acme_certificate:
acme_directory: "{{ acme_dir }}"
acme_version: 2
account_key_src: /etc/ssl-acme/account/account.key
account_key_src: "/etc/ssl-acme/account/{{ account_name }}.key"
account_email: "{{ acme_email }}"
account_uri: "{{ account.account_uri }}"
challenge: http-01
terms_agreed: 1
csr: "/etc/ssl-acme/csrs/{{ domain }}.csr"
dest: "/etc/ssl-acme/certs/{{ domain }}.crt"
fullchain_dest: "/etc/ssl-acme/certs/fullchain_{{ domain }}.crt"
chain_dest: "/etc/ssl-acme/certs/chain_{{ domain }}.crt"
csr: "/etc/ssl-acme/csrs/{{ cert_name }}.csr"
dest: "/etc/ssl-acme/certs/{{ cert_name }}.crt"
fullchain_dest: "/etc/ssl-acme/certs/fullchain_{{ cert_name }}.crt"
chain_dest: "/etc/ssl-acme/certs/chain_{{ cert_name }}.crt"
data: "{{ acme_challenge }}"
when: cert_regen is defined
when: to_regen is defined

View File

@ -6,12 +6,14 @@ events {
}
http {
{% for item in to_regen %}
server {
listen 80;
server_name {{ domain }};
root {{ webroot }}/;
server_name {{ item }};
root {{ webroot }}/{{ item }};
location / {
}
}
{% endfor %}
}

View File

@ -3,6 +3,12 @@
name: coturn
state: present
- name: Add turnserver to SSL read group
user:
name: "turnserver"
append: yes
groups: sslr
- name: Get public IP address
community.general.ipify_facts:

View File

@ -1,7 +1,7 @@
use-auth-secret
static-auth-secret={{ coturn_secret_key }}
realm={{ domain }}
realm={{ service_info.coturn.domain }}
server-name=turnserver
syslog
@ -34,8 +34,8 @@ user-quota=12
total-quota=1200
# TLS
cert=/etc/ssl-acme/certs/fullchain_{{ domain }}.crt
pkey=/etc/ssl-acme/keys/{{ domain }}.key
cert=/etc/ssl-acme/certs/fullchain_{{ service_info.coturn.ssl_cert }}.crt
pkey=/etc/ssl-acme/keys/{{ service_info.coturn.ssl_cert }}.key
# External IP address (automatically managed: do not edit!)
external-ip={{ ansible_facts.ipify_public_ip }}

View File

@ -267,8 +267,8 @@ FILE_EXTENSIONS = .md,.markdown,.mdown,.mkd
[server]
; The protocol the server listens on. One of 'http', 'https', 'unix' or 'fcgi'.
PROTOCOL = http
DOMAIN = {{ domain }}
ROOT_URL = http://{{ domain }}/git/
DOMAIN = {{ server_blocks.gitea.domain }}
ROOT_URL = http://{{ server_blocks.gitea.domain }}{{ nginx_services.gitea.path }}
; when STATIC_URL_PREFIX is empty it will follow ROOT_URL
STATIC_URL_PREFIX =
; The address to listen on. Either a IPv4/IPv6 address or the path to a unix socket.

View File

@ -1,2 +1,2 @@
MusicFolder = "{{ dataroot }}/navidrome/mus/"
BaseUrl = "/mus"
BaseUrl = "{{ nginx_services.navidrome.path }}"

View File

@ -42,7 +42,7 @@ modules:
# lowercase and may contain an explicit port.
# Examples: matrix.org, localhost:8080
#
server_name: "{{ domain }}"
server_name: "{{ server_blocks.synapse.domain }}"
# When running as a daemon, the file to store the pid in
#
@ -1059,7 +1059,7 @@ url_preview_accept_language:
# The public URIs of the TURN server to give to clients
#
turn_uris: [ "turn:{{ domain }}?transport=udp", "turn:{{ domain }}?transport=tcp" ]
turn_uris: [ "turn:{{ service_info.coturn.domain }}?transport=udp", "turn:{{ service_info.coturn.domain }}?transport=tcp" ]
# The shared secret used to compute passwords for the TURN server
#

View File

@ -3,12 +3,30 @@
name: nginx
state: present
- name: Create nginx conf.d directory
file:
path: /etc/nginx/conf.d
state: directory
- name: Configure nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: Restart webserver
- name: Create nginx SSL configuration file
template:
src: ssl.conf.j2
dest: /etc/nginx/ssl.conf
notify: Restart webserver
- name: Create nginx server blocks
template:
src: server_block.j2
dest: "/etc/nginx/conf.d/{{ item }}.conf"
with_items: "{{ server_blocks }}"
notify: Restart webserver
- name: Enable nginx service
service:
name: nginx

View File

@ -1,95 +1,33 @@
worker_processes 1;
user http;
worker_processes 1;
events {
worker_connections 1024;
}
http { include mime.types;
disable_symlinks off;
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nodelay on;
tcp_nopush on;
keepalive_timeout 65;
types_hash_max_size 4096;
root {{ webroot }}/;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
gzip on;
gzip_disable "msie6";
include /etc/nginx/conf.d/*.conf;
server {
if ($host = {{ domain }}) {
return 301 https://$host$request_uri;
}
listen 80 default_server;
server_name _;
return 301 https://$host$request_uri;
}
server {
ssl_certificate /etc/ssl-acme/certs/fullchain_{{ domain }}.crt;
ssl_certificate_key /etc/ssl-acme/keys/{{ domain }}.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
listen 443 ssl http2;
listen [::]:443 ssl http2;
listen 8448 ssl http2 default_server;
listen [::]:8448 ssl http2 default_server;
server_name {{ domain }};
location ~* ^(\/_matrix|\/_synapse\/client) {
proxy_pass http://localhost:8008;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
client_max_body_size 50M;
}
location = / {
return 301 https://{{ domain }}/site/index.html;
}
location /site {
index index.html;
}
location /wiki {
index index.php;
}
location /rw {
index index.php;
}
location /git/ {
proxy_pass http://localhost:3000/ ;
}
location /mus/ {
proxy_pass http://localhost:4533/mus/ ;
}
location ~ \.php$ {
# 404
try_files $fastcgi_script_name =404;
# default fastcgi_params
include fastcgi_params;
# fastcgi settings
fastcgi_pass unix:/run/php-fpm/php-fpm.sock;
fastcgi_index index.php;
fastcgi_buffers 8 16k;
fastcgi_buffer_size 32k;
# fastcgi params
fastcgi_param DOCUMENT_ROOT $realpath_root;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
#fastcgi_param PHP_ADMIN_VALUE "open_basedir=$base/:/usr/lib/php/:/tmp/";
}
}
}

View File

@ -0,0 +1,16 @@
server {
ssl_certificate /etc/ssl-acme/certs/fullchain_{{ server_blocks[item].ssl_cert }}.crt;
ssl_certificate_key /etc/ssl-acme/keys/{{ server_blocks[item].ssl_cert }}.key;
include ssl.conf;
server_name {{ server_blocks[item].domain }};
{% for listener in server_blocks[item].listens %}
listen {{ listener }};
{% endfor %}
{% for srv in server_blocks[item].services %}
{% include "srv_conf/" + srv + ".conf.j2" %}
{% endfor %}
}

View File

@ -0,0 +1,3 @@
location {{ nginx_services[srv].path }} {
proxy_pass http://localhost:3000{{ nginx_services[srv].path }} ;
}

View File

@ -0,0 +1,3 @@
location {{ nginx_services[srv].path }} {
proxy_pass http://localhost:4533{{ nginx_services[srv].path }} ;
}

View File

@ -0,0 +1 @@
return 301 https://www.{{ domain }}$request_uri;

View File

@ -0,0 +1,8 @@
location ~* ^(\/_matrix|\/_synapse\/client) {
proxy_pass http://localhost:8008;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
client_max_body_size {{ nginx_services[srv].max_body_size }};
}

View File

@ -0,0 +1,4 @@
location {{ nginx_services[srv].path }} {
root {{ webroot }}/{{ server_blocks.website.domain }}{{ nginx_services[srv].path }};
index index.html;
}

View File

@ -0,0 +1,17 @@
location {{ nginx_services[srv].path }} {
index index.php;
}
location ~ \.php$ {
try_files $fastcgi_script_name = 404;
include fastcgi_params;
fastcgi_pass unix:/run/php-fpm/php-fpm.sock;
fastcgi_index index.php;
fastcgi_buffers 8 16k;
fastcgi_buffer_size 32k;
fastcgi_param DOCUMENT_ROOT $realpath_root;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
}

View File

@ -0,0 +1,7 @@
# General SSL settings
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;

View File

@ -1,3 +1,11 @@
- name: Set site path
set_fact:
path: "{{ server_blocks.website.domain }}{{ nginx_services.website.path }}"
- name: Set full root
set_fact:
fullroot: "{{ webroot }}/{{ path }}"
- name: Install required packages
community.general.pacman:
name:
@ -25,13 +33,13 @@
file:
group: http
owner: http
path: "{{ webroot }}/site/"
path: "{{ fullroot }}"
state: directory
register: site_folder
- name: Deploy source to web root
shell:
cmd: "./ssg6 src {{ webroot }}/site/ 'dogeystamp' 'https://{{ domain }}/site'"
cmd: "./ssg6 src {{ fullroot }} '{{ web_name }}' 'https://{{ path }}'"
chdir: /srv/web_source
when: site_source.changed or site_folder.changed or site_perm.changed
become_user: http
@ -42,4 +50,4 @@
name: "Update and deploy website source"
minute: 0
hour: "*/12"
job: "git -C /srv/web_source/ pull && /srv/web_source/ssg6 /srv/web_source/src {{ webroot }}/site/ '{{ web_name }}' 'https://{{ domain }}/site'"
job: "git -C /srv/web_source/ pull && /srv/web_source/ssg6 /srv/web_source/src {{ fullroot }} '{{ web_name }}' 'https://{{ path }}'"

View File

@ -9,23 +9,32 @@
state: present
- name: Symlink wikis into web root
- name: Set wiki root
set_fact:
wiki_root: "{{ webroot }}/{{ server_blocks.wiki.domain }}{{ nginx_services.wiki.path }}"
- name: Create wiki web root
file:
path: "{{ wiki_root }}"
state: directory
- name: Symlink wikis into wiki web root
file:
src: /usr/share/webapps/mediawiki
dest: "{{ webroot }}/{{ item }}"
dest: "{{ wiki_root }}/{{ item }}"
state: link
with_items: "{{ wiki_names }}"
- name: Deploy wiki-farm main configuration file
template:
src: LocalSettings.php.j2
dest: "{{ webroot }}/{{ wiki_names[0] }}/LocalSettings.php"
dest: "{{ wiki_root }}/{{ wiki_names[0] }}/LocalSettings.php"
mode: u=rwx,g=r,o=r
- name: Deploy configuration files for individual wikis
template:
src: "LocalSettings_{{ item.1 }}.php.j2"
dest: "{{ webroot }}/{{ wiki_names[0] }}/LocalSettings_{{ item.1 }}.php"
dest: "{{ wiki_root }}/{{ wiki_names[0] }}/LocalSettings_{{ item.1 }}.php"
with_indexed_items: "{{ wiki_names }}"
- name: Ensure correct permissions on data
@ -37,7 +46,7 @@
- name: Copy wiki logo files
copy:
src: "{{ item }}"
dest: "{{ webroot }}/{{ wiki_names[0] }}/resources/assets/"
dest: "{{ wiki_root }}/{{ wiki_names[0] }}/resources/assets/"
with_fileglob:
- logos/*.png

36
run.yml
View File

@ -65,11 +65,47 @@
- mail
when: enable_mail
# Main SSL certificate (with Let's Encrypt)
- role: networking/ssl
tags:
- ssl
acme_dir: "https://acme-v02.api.letsencrypt.org/directory"
cert_name: "{{ domain }}"
ssl_domains:
- "{{ server_blocks.wiki.domain }}"
- "{{ server_blocks.synapse.domain }}"
- "{{ server_blocks.website.domain }}"
- "{{ server_blocks.gitea.domain }}"
- "{{ server_blocks.navidrome.domain }}"
account_name: "letsencrypt"
when: enable_ssl
# SSL cert for dyndns_domain
- role: networking/ssl
tags:
- ssl
acme_dir: "https://acme-v02.api.letsencrypt.org/directory"
ssl_domains:
- "{{ dyndns_domain }}"
account_name: "letsencrypt"
when: enable_ssl and dyndns_domain != domain
# ZeroSSL certificate for Coturn (see https://bugs.chromium.org/p/webrtc/issues/detail?id=11710)
- role: networking/ssl
tags:
- ssl
acme_dir: "https://acme.zerossl.com/v2/DV90"
ssl_domains:
- "{{ service_info.coturn.domain }}"
acme_eab: {
alg: HS256,
key: "{{ zerossl_acme_eab_key }}",
kid: "{{ zerossl_acme_eab_kid }}"
}
acme_email: "{{ zerossl_email }}"
account_name: "zerossl"
when: enable_ssl and enable_coturn
- role: services/webserver
tags:
- webserver