feat(talk/turn/signaling/hpb): add role for Talk with backend services
This commit is contained in:
parent
2c2dbbc648
commit
05fb62c75d
25 changed files with 930 additions and 0 deletions
69
roles/coturn/README.md
Normal file
69
roles/coturn/README.md
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# coturn
|
||||
|
||||
Deploys a [coturn](https://github.com/coturn/coturn) TURN/STUN server with `network_mode: host`,
|
||||
optionally accompanied by an `acme.sh` sidecar that obtains and renews a public TLS certificate
|
||||
via RFC2136 (`nsupdate`) and restarts coturn on renewal.
|
||||
|
||||
This is the recommended pairing for `digitalboard.core.talk` (Nextcloud Talk HPB).
|
||||
|
||||
## What it does
|
||||
|
||||
- Renders `/etc/docker/compose/coturn/docker-compose.yml`
|
||||
- (acme mode) Deploys the TSIG key from `playbooks/secrets/{{ inventory_hostname }}/nsupdate.key`
|
||||
- (selfsigned mode) Generates an ECC keypair + selfsigned cert in `{{ coturn_cert_dir }}`
|
||||
- Starts the stack via `community.docker.docker_compose_v2`
|
||||
|
||||
## Required variables
|
||||
|
||||
| Variable | Description |
|
||||
|---|---|
|
||||
| `coturn_realm` | Public DNS name used as realm + cert CN (e.g. `stun.digitalboard.ch`) |
|
||||
| `coturn_external_ip` | Mapping for `--external-ip`, format `PUBLIC[/PRIVATE]` |
|
||||
| `coturn_static_auth_secret` | Shared secret for HMAC-based credentials; **must match** `talk_turn_secret` on the HPB host |
|
||||
|
||||
## Important variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `coturn_cert_mode` | `file` | One of `acme`, `file`, `selfsigned` |
|
||||
| `coturn_listening_port` | `443` | TCP/UDP non-TLS port |
|
||||
| `coturn_tls_listening_port` | `443` | TLS port (shared with non-TLS via STUN mux) |
|
||||
| `coturn_min_relay_port` / `coturn_max_relay_port` | `49160` / `49200` | UDP relay range |
|
||||
| `coturn_internal_realm` | `""` | Optional second SAN for split-horizon DNS |
|
||||
| `coturn_image` | `coturn/coturn:4.6.2-r5-alpine` | Pinned by default; override as needed |
|
||||
|
||||
## ACME / nsupdate mode
|
||||
|
||||
When `coturn_cert_mode: acme` is set, also configure:
|
||||
|
||||
```yaml
|
||||
coturn_acme_email: "admin@digitalboard.ch"
|
||||
coturn_acme_nsupdate_server: "ns1.digitalboard.ch"
|
||||
coturn_acme_nsupdate_server_ip: "172.16.9.169" # optional pin
|
||||
coturn_acme_nsupdate_zone: "digitalboard._acme.digitalboard.ch"
|
||||
# optional: override the auto-built challenge alias mapping
|
||||
coturn_acme_challenge_aliases:
|
||||
- name: stun.digitalboard.ch
|
||||
alias: stun.digitalboard._acme.digitalboard.ch
|
||||
- name: stun.int.digitalboard.ch
|
||||
alias: stun.int.digitalboard._acme.digitalboard.ch
|
||||
```
|
||||
|
||||
Place your TSIG key at `playbooks/secrets/{{ inventory_hostname }}/nsupdate.key` (mode 0600).
|
||||
|
||||
## Secrets
|
||||
|
||||
Place the static auth secret at:
|
||||
|
||||
```
|
||||
playbooks/secrets/{{ inventory_hostname }}/coturn_static_auth_secret
|
||||
```
|
||||
|
||||
Mode 0600. The same value must be deployed to the HPB host as `talk_turn_secret`.
|
||||
|
||||
## Firewall
|
||||
|
||||
The role does not manage firewall rules. Ensure the host has:
|
||||
|
||||
- `443/tcp` and `443/udp` reachable from the internet
|
||||
- UDP `{{ coturn_min_relay_port }}-{{ coturn_max_relay_port }}` reachable from the internet
|
||||
77
roles/coturn/defaults/main.yml
Normal file
77
roles/coturn/defaults/main.yml
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# defaults file for coturn
|
||||
|
||||
# Base directories (inherited from base role)
|
||||
docker_compose_base_dir: /etc/docker/compose
|
||||
docker_volume_base_dir: /srv/data
|
||||
|
||||
# Service-specific paths
|
||||
coturn_service_name: coturn
|
||||
coturn_docker_compose_dir: "{{ docker_compose_base_dir }}/{{ coturn_service_name }}"
|
||||
coturn_docker_volume_dir: "{{ docker_volume_base_dir }}/{{ coturn_service_name }}"
|
||||
|
||||
# Container images (pin per host_vars in production)
|
||||
coturn_image: "coturn/coturn:4.6.2-r5-alpine"
|
||||
coturn_acme_image: "neilpang/acme.sh:3.1.0"
|
||||
|
||||
# Public DNS name used for the realm and the public certificate
|
||||
coturn_realm: "stun.example.test"
|
||||
# Optional second DNS name issued on the same certificate (for split-horizon "internal" name)
|
||||
coturn_internal_realm: "" # e.g. "stun.int.example.test"
|
||||
|
||||
# Ports
|
||||
# Defaults follow IANA standards (3478/TURN, 5349/TURNS) so coturn can
|
||||
# co-exist with a Traefik instance on the same host. Override to 443/443
|
||||
# in restrictive-network environments where punching through firewalls matters.
|
||||
coturn_listening_port: 3478 # TURN / STUN (TCP+UDP)
|
||||
coturn_tls_listening_port: 5349 # TURNS (TCP+UDP)
|
||||
coturn_min_relay_port: 49160
|
||||
coturn_max_relay_port: 49200
|
||||
|
||||
# IP advertisement: must be set in host_vars for production
|
||||
# Format follows coturn's --external-ip: "PUBLIC_IP" or "PUBLIC_IP/PRIVATE_IP"
|
||||
coturn_external_ip: "" # e.g. "203.0.113.10/172.18.0.2"
|
||||
coturn_listening_ip: "0.0.0.0"
|
||||
|
||||
# Shared secret used by HPB to mint short-lived TURN credentials.
|
||||
# Loaded by default from a plain file in playbooks/secrets/{host}/coturn_static_auth_secret
|
||||
# Override per host_vars if you want to use a vault or different lookup.
|
||||
coturn_static_auth_secret: "{{ lookup('file', playbook_dir ~ '/secrets/' ~ inventory_hostname ~ '/coturn_static_auth_secret') }}"
|
||||
|
||||
# Additional CLI flags (list of strings, appended verbatim to command:)
|
||||
coturn_extra_args: []
|
||||
|
||||
# --- TLS certificate ---
|
||||
# 'acme' : run an acme.sh sidecar that issues + renews via RFC2136 / nsupdate, restarts coturn
|
||||
# 'file' : assume a certificate already lives at {{ coturn_cert_dir }}/{{ coturn_cert_file }} on the host (you manage it)
|
||||
# 'selfsigned' : generate a selfsigned cert on first run (for vagrant/dev only)
|
||||
coturn_cert_mode: "file"
|
||||
|
||||
coturn_cert_dir: "{{ docker_volume_base_dir }}/acme/certs"
|
||||
coturn_cert_file: "fullchain.cer"
|
||||
coturn_key_file: "{{ coturn_realm }}.key"
|
||||
|
||||
# --- acme.sh sidecar (only used when coturn_cert_mode == 'acme') ---
|
||||
coturn_acme_email: "admin@example.test"
|
||||
coturn_acme_directory: "https://acme-v02.api.letsencrypt.org/directory"
|
||||
# Stage URL for testing: "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
coturn_acme_keylength: "ec-256"
|
||||
coturn_acme_dnssleep: 60
|
||||
coturn_acme_data_dir: "{{ docker_volume_base_dir }}/acme/acme"
|
||||
|
||||
# DNS-01 RFC2136 / nsupdate configuration
|
||||
coturn_acme_nsupdate_server: "" # e.g. "ns1.example.test"
|
||||
coturn_acme_nsupdate_server_ip: "" # optional extra_hosts pin (string IP) for the server
|
||||
coturn_acme_nsupdate_zone: "" # e.g. "example._acme.example.test"
|
||||
# Per-name challenge alias zones (one entry per SAN)
|
||||
# When empty (default), built automatically as "{{ realm }}._acme.{{ zone-tail }}"
|
||||
coturn_acme_challenge_aliases: []
|
||||
# Example:
|
||||
# - name: stun.example.test
|
||||
# alias: stun.example._acme.example.test
|
||||
# - name: stun.int.example.test
|
||||
# alias: stun.int.example._acme.example.test
|
||||
|
||||
# Path of the TSIG key file inside the container (mounted from secrets)
|
||||
coturn_acme_nsupdate_key_src: "{{ playbook_dir }}/secrets/{{ inventory_hostname }}/nsupdate.key"
|
||||
10
roles/coturn/handlers/main.yml
Normal file
10
roles/coturn/handlers/main.yml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# handlers file for coturn
|
||||
|
||||
- name: Restart coturn container
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ coturn_docker_compose_dir }}"
|
||||
state: restarted
|
||||
services:
|
||||
- coturn
|
||||
15
roles/coturn/meta/main.yml
Normal file
15
roles/coturn/meta/main.yml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#SPDX-License-Identifier: MIT-0
|
||||
galaxy_info:
|
||||
author: Digital Board Team
|
||||
description: Deploy a coturn TURN/STUN server with optional acme.sh sidecar (RFC2136/nsupdate)
|
||||
company: digitalboard.ch
|
||||
license: GPL-2.0-or-later
|
||||
min_ansible_version: "2.14"
|
||||
galaxy_tags:
|
||||
- turn
|
||||
- stun
|
||||
- coturn
|
||||
- webrtc
|
||||
- nextcloud
|
||||
- talk
|
||||
dependencies: []
|
||||
110
roles/coturn/tasks/main.yml
Normal file
110
roles/coturn/tasks/main.yml
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
#SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# tasks file for coturn
|
||||
|
||||
- name: Assert minimum configuration
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- coturn_realm | length > 0
|
||||
- coturn_external_ip | length > 0
|
||||
- coturn_static_auth_secret | length > 0
|
||||
fail_msg: >
|
||||
coturn_realm, coturn_external_ip and coturn_static_auth_secret must be set.
|
||||
Provide them in host_vars or via a secrets file.
|
||||
|
||||
- name: Create coturn compose directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ coturn_docker_compose_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Create coturn data directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ coturn_docker_volume_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Create certificate directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ coturn_cert_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
# --- TLS certificate provisioning -------------------------------------------------
|
||||
|
||||
- name: Configure acme.sh sidecar (TSIG key + acme data dir)
|
||||
when: coturn_cert_mode == 'acme'
|
||||
block:
|
||||
- name: Create acme.sh data directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ coturn_acme_data_dir }}"
|
||||
state: directory
|
||||
mode: "0700"
|
||||
|
||||
- name: Deploy nsupdate TSIG key
|
||||
ansible.builtin.copy:
|
||||
src: "{{ coturn_acme_nsupdate_key_src }}"
|
||||
dest: "{{ coturn_docker_compose_dir }}/nsupdate.key"
|
||||
mode: "0600"
|
||||
no_log: true
|
||||
notify: Restart coturn container
|
||||
|
||||
- name: Build effective challenge alias list (default if not provided)
|
||||
ansible.builtin.set_fact:
|
||||
_coturn_challenge_aliases: >-
|
||||
{{ coturn_acme_challenge_aliases
|
||||
if coturn_acme_challenge_aliases | length > 0
|
||||
else (
|
||||
[{'name': coturn_realm,
|
||||
'alias': (coturn_realm.split('.')[:-2] | join('.')) ~ '.' ~ coturn_acme_nsupdate_zone }]
|
||||
+ ([{'name': coturn_internal_realm,
|
||||
'alias': (coturn_internal_realm.split('.')[:-2] | join('.')) ~ '.' ~ coturn_acme_nsupdate_zone }]
|
||||
if coturn_internal_realm | length > 0 else [])
|
||||
)
|
||||
}}
|
||||
|
||||
- name: Generate selfsigned certificate (vagrant / dev only)
|
||||
when: coturn_cert_mode == 'selfsigned'
|
||||
block:
|
||||
- name: Ensure openssl is available
|
||||
ansible.builtin.package:
|
||||
name: openssl
|
||||
state: present
|
||||
|
||||
- name: Generate selfsigned private key
|
||||
community.crypto.openssl_privatekey:
|
||||
path: "{{ coturn_cert_dir }}/{{ coturn_key_file }}"
|
||||
type: ECC
|
||||
curve: secp256r1
|
||||
mode: "0600"
|
||||
|
||||
- name: Generate selfsigned CSR
|
||||
community.crypto.openssl_csr:
|
||||
path: "{{ coturn_cert_dir }}/{{ coturn_realm }}.csr"
|
||||
privatekey_path: "{{ coturn_cert_dir }}/{{ coturn_key_file }}"
|
||||
common_name: "{{ coturn_realm }}"
|
||||
subject_alt_name:
|
||||
- "DNS:{{ coturn_realm }}"
|
||||
mode: "0644"
|
||||
|
||||
- name: Issue selfsigned certificate
|
||||
community.crypto.x509_certificate:
|
||||
path: "{{ coturn_cert_dir }}/{{ coturn_cert_file }}"
|
||||
privatekey_path: "{{ coturn_cert_dir }}/{{ coturn_key_file }}"
|
||||
csr_path: "{{ coturn_cert_dir }}/{{ coturn_realm }}.csr"
|
||||
provider: selfsigned
|
||||
mode: "0644"
|
||||
|
||||
# --- Compose + start --------------------------------------------------------------
|
||||
|
||||
- name: Generate docker-compose.yml for coturn
|
||||
ansible.builtin.template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: "{{ coturn_docker_compose_dir }}/docker-compose.yml"
|
||||
mode: "0644"
|
||||
notify: Restart coturn container
|
||||
|
||||
- name: Start coturn stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ coturn_docker_compose_dir }}"
|
||||
state: present
|
||||
78
roles/coturn/templates/docker-compose.yml.j2
Normal file
78
roles/coturn/templates/docker-compose.yml.j2
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
services:
|
||||
coturn:
|
||||
image: {{ coturn_image }}
|
||||
container_name: {{ coturn_service_name }}
|
||||
restart: always
|
||||
network_mode: host
|
||||
volumes:
|
||||
- {{ coturn_cert_dir }}:/certs:ro
|
||||
command:
|
||||
- --use-auth-secret
|
||||
- --static-auth-secret={{ coturn_static_auth_secret }}
|
||||
- --realm={{ coturn_realm }}
|
||||
- --fingerprint
|
||||
- --no-multicast-peers
|
||||
- --no-cli
|
||||
- --listening-ip={{ coturn_listening_ip }}
|
||||
- --listening-port={{ coturn_listening_port }}
|
||||
- --tls-listening-port={{ coturn_tls_listening_port }}
|
||||
- --min-port={{ coturn_min_relay_port }}
|
||||
- --max-port={{ coturn_max_relay_port }}
|
||||
- --cert=/certs/{{ coturn_cert_file }}
|
||||
- --pkey=/certs/{{ coturn_key_file }}
|
||||
- --external-ip={{ coturn_external_ip }}
|
||||
{% for arg in coturn_extra_args %}
|
||||
- {{ arg }}
|
||||
{% endfor %}
|
||||
|
||||
{% if coturn_cert_mode == 'acme' %}
|
||||
acme:
|
||||
image: {{ coturn_acme_image }}
|
||||
container_name: acme-{{ coturn_service_name }}
|
||||
restart: always
|
||||
environment:
|
||||
NSUPDATE_SERVER: "{{ coturn_acme_nsupdate_server }}"
|
||||
NSUPDATE_KEY: "/acme.sh/nsupdate.key"
|
||||
ACME_DIRECTORY: "{{ coturn_acme_directory }}"
|
||||
NSUPDATE_ZONE: "{{ coturn_acme_nsupdate_zone }}"
|
||||
{% if coturn_acme_nsupdate_server_ip | length > 0 %}
|
||||
extra_hosts:
|
||||
- "{{ coturn_acme_nsupdate_server }}:{{ coturn_acme_nsupdate_server_ip }}"
|
||||
{% endif %}
|
||||
volumes:
|
||||
- {{ coturn_cert_dir }}:/certs
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- {{ coturn_docker_compose_dir }}/nsupdate.key:/acme.sh/nsupdate.key:ro
|
||||
- {{ coturn_acme_data_dir }}:/acme.sh
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
set -eu
|
||||
acme.sh --set-default-ca --server "$$ACME_DIRECTORY"
|
||||
acme.sh --register-account -m {{ coturn_acme_email }} || true
|
||||
set +e
|
||||
acme.sh --issue \
|
||||
{% for san in _coturn_challenge_aliases %}
|
||||
-d {{ san.name }} \
|
||||
--challenge-alias {{ san.alias }} \
|
||||
{% endfor %}
|
||||
--dns dns_nsupdate \
|
||||
--keylength {{ coturn_acme_keylength }} \
|
||||
--dnssleep {{ coturn_acme_dnssleep }}
|
||||
rc=$$?
|
||||
set -e
|
||||
if [ "$$rc" -eq 0 ]; then
|
||||
echo "Issue: success"
|
||||
elif [ "$$rc" -eq 2 ]; then
|
||||
echo "Issue: not due, continuing"
|
||||
else
|
||||
echo "Issue: failed with rc=$$rc"
|
||||
exit "$$rc"
|
||||
fi
|
||||
acme.sh --install-cert -d {{ coturn_realm }} --ecc \
|
||||
--fullchain-file /certs/{{ coturn_cert_file }} \
|
||||
--key-file /certs/{{ coturn_key_file }} \
|
||||
--reloadcmd 'curl --fail --silent --show-error --unix-socket /var/run/docker.sock -X POST http://localhost/v1.41/containers/{{ coturn_service_name }}/restart' || true
|
||||
exec crond -f
|
||||
{% endif %}
|
||||
2
roles/coturn/tests/inventory
Normal file
2
roles/coturn/tests/inventory
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
#SPDX-License-Identifier: MIT-0
|
||||
localhost
|
||||
6
roles/coturn/tests/tests.yml
Normal file
6
roles/coturn/tests/tests.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
- hosts: localhost
|
||||
remote_user: root
|
||||
roles:
|
||||
- coturn
|
||||
3
roles/coturn/vars/main.yml
Normal file
3
roles/coturn/vars/main.yml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# vars file for httpbin
|
||||
Loading…
Add table
Add a link
Reference in a new issue