From 73a38e0b2bf4dab30bc6c2188ce57b5c5802410f Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Fri, 26 Sep 2025 22:11:55 +0200 Subject: [PATCH] Refactor TURN/STUN handling: - Split internal/external Coturn for BBB and Nextcloud - Added dedicated relay port ranges per app - Updated env and compose overrides for coturn - Ensure coturn role is loaded conditionally - Standardize credential/env passing for coturn @See https://chatgpt.com/share/68d6f376-4878-800f-b4f7-62822caa49ea --- group_vars/all/10_ports.yml | 13 ++++++----- roles/web-app-bigbluebutton/config/main.yml | 2 +- roles/web-app-bigbluebutton/tasks/main.yml | 10 +++++++++ .../templates/docker-compose.override.yml.j2 | 22 +++++++++++++++++++ roles/web-app-bigbluebutton/templates/env.j2 | 4 ++-- roles/web-app-bigbluebutton/vars/main.yml | 11 ++++++---- roles/web-app-nextcloud/config/main.yml | 1 + .../tasks/plugins/spreed.yml | 7 ++++++ .../templates/docker-compose.yml.j2 | 7 +++--- roles/web-app-nextcloud/templates/env.j2 | 4 ++++ roles/web-app-nextcloud/vars/main.yml | 15 +++++++++---- .../config/{main.yml.j2 => main.yml} | 3 ++- roles/web-svc-coturn/tasks/01_core.yml | 3 +++ roles/web-svc-coturn/tasks/main.yml | 8 ++++--- .../templates/docker-compose.yml.j2 | 8 ++++--- roles/web-svc-coturn/templates/env.j2 | 4 ++++ roles/web-svc-coturn/vars/main.yml | 2 +- 17 files changed, 97 insertions(+), 27 deletions(-) create mode 100644 roles/web-app-nextcloud/tasks/plugins/spreed.yml rename roles/web-svc-coturn/config/{main.yml.j2 => main.yml} (81%) create mode 100644 roles/web-svc-coturn/tasks/01_core.yml create mode 100644 roles/web-svc-coturn/templates/env.j2 diff --git a/group_vars/all/10_ports.yml b/group_vars/all/10_ports.yml index 8c4c5200..bf39ddc1 100644 --- a/group_vars/all/10_ports.yml +++ b/group_vars/all/10_ports.yml @@ -92,14 +92,17 @@ ports: web-app-bigbluebutton: 3478 # Not sure if it's right placed here or if it should be moved to localhost section # Occupied by BBB: 3479 web-app-nextcloud: 3480 - web-app-coturn: 3481 + web-svc-coturn: 3481 turn: web-app-bigbluebutton: 5349 # Not sure if it's right placed here or if it should be moved to localhost section web-app-nextcloud: 5350 # Not used yet - web-app-coturn: 5351 + web-svc-coturn: 5351 federation: web-app-matrix_synapse: 8448 relay_port_ranges: - web-app-coturn_start: 49152 - web-app-coturn_end: 65535 - + web-app-bigbluebutton_start: 40000 + web-app-bigbluebutton_end: 59999 + web-app-nextcloud_start: 60000 + web-app-nextcloud_end: 79999 + web-svc-coturn_start: 80000 + web-svc-coturn_end: 99999 diff --git a/roles/web-app-bigbluebutton/config/main.yml b/roles/web-app-bigbluebutton/config/main.yml index 1139fab2..89dcf3ca 100644 --- a/roles/web-app-bigbluebutton/config/main.yml +++ b/roles/web-app-bigbluebutton/config/main.yml @@ -32,4 +32,4 @@ docker: greenlight: enabled: true coturn: - enabled: true \ No newline at end of file + internal: "{{ not 'web-svc-coturn' in group_names | lower }}" diff --git a/roles/web-app-bigbluebutton/tasks/main.yml b/roles/web-app-bigbluebutton/tasks/main.yml index 32b7671d..292ec9cd 100644 --- a/roles/web-app-bigbluebutton/tasks/main.yml +++ b/roles/web-app-bigbluebutton/tasks/main.yml @@ -68,3 +68,13 @@ include_tasks: "02_administrator.yml" +- name: "Load Coturn Role for '{{ application_id }}'" + include_role: + name: web-svc-coturn + vars: + flush_handlers: true + when: + - run_once_web_svc_coturn is not defined + - not BBB_INTERNAL_COTURN_ENABLED + + diff --git a/roles/web-app-bigbluebutton/templates/docker-compose.override.yml.j2 b/roles/web-app-bigbluebutton/templates/docker-compose.override.yml.j2 index c6ca0d5a..746aecbc 100644 --- a/roles/web-app-bigbluebutton/templates/docker-compose.override.yml.j2 +++ b/roles/web-app-bigbluebutton/templates/docker-compose.override.yml.j2 @@ -5,3 +5,25 @@ services: MS_ENABLE_IPV6: "false" MS_WEBRTC_LISTEN_IPS: >- [{"ip":"0.0.0.0","announcedIp":"${EXTERNAL_IPv4}"}] + coturn: + ports: + - "{{ BBB_TURN_PORT }}:{{ BBB_TURN_PORT }}/udp" + - "{{ BBB_TURN_PORT }}:{{ BBB_TURN_PORT }}/tcp" + - "{{ BBB_STUN_PORT }}:{{ BBB_STUN_PORT }}/udp" + - "{{ BBB_STUN_PORT }}:{{ BBB_STUN_PORT }}/tcp" + - "{{ BBB_RELAY_PORT_RANGE }}/udp" + command: >- + --use-auth-secret + --static-auth-secret=${TURN_SECRET} + --lt-cred-mech + --realm=${DOMAIN} + --fingerprint + --no-multicast-peers + --no-cli + --no-tcp-relay + --min-port={{ BBB_RELAY_PORT_START }} + --max-port={{ BBB_RELAY_PORT_END }} + --external-ip=${EXTERNAL_IPv4} + {% if BBB_IP6_ENABLED %}--external-ip=${EXTERNAL_IPv6}{% endif %} + --cert=${COTURN_TLS_CERT_PATH} + --pkey=${COTURN_TLS_KEY_PATH} diff --git a/roles/web-app-bigbluebutton/templates/env.j2 b/roles/web-app-bigbluebutton/templates/env.j2 index d1d3e998..01b01ccd 100644 --- a/roles/web-app-bigbluebutton/templates/env.j2 +++ b/roles/web-app-bigbluebutton/templates/env.j2 @@ -1,7 +1,7 @@ # Coturn -ENABLE_COTURN={{ BBB_COTURN_ENABLED }} +ENABLE_COTURN={{ BBB_INTERNAL_COTURN_ENABLED }} -# Credentials +## Credentials COTURN_TLS_CERT_PATH={{ BBB_COTURN_TLS_CERT_PATH }} COTURN_TLS_KEY_PATH={{ BBB_COTURN_TLS_KEY_PATH }} diff --git a/roles/web-app-bigbluebutton/vars/main.yml b/roles/web-app-bigbluebutton/vars/main.yml index 23f646ea..1edfbaa1 100644 --- a/roles/web-app-bigbluebutton/vars/main.yml +++ b/roles/web-app-bigbluebutton/vars/main.yml @@ -34,9 +34,12 @@ BBB_COTURN_TLS_CERT_PATH: "{{ [ LETSENCRYPT_LIVE_PATH, ssl_cert_fold BBB_COTURN_TLS_KEY_PATH: "{{ [ LETSENCRYPT_LIVE_PATH, ssl_cert_folder, 'privkey.pem'] | path_join }}" ## Turn -BBB_TURN_DOMAIN: "{{ networks.internet.ip4 if BBB_COTURN_ENABLED else domains | get_domain('web-svc-coturn') }}" -BBB_TURN_PORT: "{{ ports.public.turn[application_id] if BBB_COTURN_ENABLED else ports.public.turn['web-svc-coturn'] }}" -BBB_STUN_PORT: "{{ ports.public.turn[application_id] if BBB_COTURN_ENABLED else ports.public.stun['web-svc-coturn'] }}" +BBB_TURN_DOMAIN: "{{ networks.internet.ip4 if BBB_INTERNAL_COTURN_ENABLED else domains | get_domain('web-svc-coturn') }}" +BBB_TURN_PORT: "{{ ports.public.turn[application_id] if BBB_INTERNAL_COTURN_ENABLED else ports.public.turn['web-svc-coturn'] }}" +BBB_STUN_PORT: "{{ ports.public.turn[application_id] if BBB_INTERNAL_COTURN_ENABLED else ports.public.stun['web-svc-coturn'] }}" +BBB_RELAY_PORT_START: "{{ ports.public.relay_port_ranges[application_id ~ '_start'] }}" +BBB_RELAY_PORT_END: "{{ ports.public.relay_port_ranges[application_id ~ '_end'] }}" +BBB_RELAY_PORT_RANGE: "{{ BBB_RELAY_PORT_START }}-{{ BBB_RELAY_PORT_END }}" ## Switchs @@ -45,7 +48,7 @@ BBB_IP6_ENABLED: "{{ applications | get_app_conf(applicatio ### Container BBB_GREENLIGHT_ENABLED: "{{ applications | get_app_conf(application_id, 'docker.services.greenlight.enabled') }}" -BBB_COTURN_ENABLED: "{{ applications | get_app_conf(application_id, 'docker.services.coturn.enabled') }}" +BBB_INTERNAL_COTURN_ENABLED: "{{ applications | get_app_conf(application_id, 'docker.services.coturn.internal') }}" ### SSO BBB_LDAP_ENABLED: "{{ applications | get_app_conf(application_id, 'features.ldap') }}" diff --git a/roles/web-app-nextcloud/config/main.yml b/roles/web-app-nextcloud/config/main.yml index 69300f1b..68bc048c 100644 --- a/roles/web-app-nextcloud/config/main.yml +++ b/roles/web-app-nextcloud/config/main.yml @@ -51,6 +51,7 @@ docker: version: "latest" backup: no_stop_required: false + internal: "{{ not 'web-svc-coturn' in group_names | lower }}" whiteboard: name: "nextcloud-whiteboard" image: "ghcr.io/nextcloud-releases/whiteboard" diff --git a/roles/web-app-nextcloud/tasks/plugins/spreed.yml b/roles/web-app-nextcloud/tasks/plugins/spreed.yml new file mode 100644 index 00000000..ad05ff89 --- /dev/null +++ b/roles/web-app-nextcloud/tasks/plugins/spreed.yml @@ -0,0 +1,7 @@ +- name: "Load Coturn Role for '{{ application_id }}'" + include_role: + name: web-svc-coturn + vars: + flush_handlers: true + when: + - run_once_web_svc_coturn is not defined diff --git a/roles/web-app-nextcloud/templates/docker-compose.yml.j2 b/roles/web-app-nextcloud/templates/docker-compose.yml.j2 index 1464a50d..3fd49530 100644 --- a/roles/web-app-nextcloud/templates/docker-compose.yml.j2 +++ b/roles/web-app-nextcloud/templates/docker-compose.yml.j2 @@ -34,7 +34,7 @@ {% include 'roles/docker-container/templates/networks.yml.j2' %} ipv4_address: 192.168.102.69 -{% if NEXTCLOUD_TALK_ENABLED %} +{% if NEXTCLOUD_TALK_INTERNAL_ENABLED %} talk: {% set container_port = NEXTCLOUD_TALK_PORT_INTERNAL %} {% include 'roles/docker-container/templates/base.yml.j2' %} @@ -43,8 +43,9 @@ container_name: {{ NEXTCLOUD_TALK_CONTAINER }} init: true ports: - - {{ networks.internet.ip4 }}:{{ NEXTCLOUD_TALK_STUN_PORT }}:{{ NEXTCLOUD_TALK_INT_TURN_PORT }}/tcp #TURN TCP - - {{ networks.internet.ip4 }}:{{ NEXTCLOUD_TALK_STUN_PORT }}:{{ NEXTCLOUD_TALK_INT_TURN_PORT }}/udp #TURN UDP + - {{ networks.internet.ip4 }}:{{ NEXTCLOUD_TALK_STUN_PORT }}:{{ NEXTCLOUD_TALK_INT_TURN_PORT }}/tcp + - {{ networks.internet.ip4 }}:{{ NEXTCLOUD_TALK_STUN_PORT }}:{{ NEXTCLOUD_TALK_INT_TURN_PORT }}/udp + - {{ NEXTCLOUD_TALK_RELAY_PORT_RANGE }}:{{ NEXTCLOUD_TALK_RELAY_PORT_RANGE }}/udp expose: - "{{ container_port }}" networks: diff --git a/roles/web-app-nextcloud/templates/env.j2 b/roles/web-app-nextcloud/templates/env.j2 index b4fb3aa6..c8a1467f 100644 --- a/roles/web-app-nextcloud/templates/env.j2 +++ b/roles/web-app-nextcloud/templates/env.j2 @@ -49,6 +49,10 @@ SIGNALING_SECRET={{ applications | get_app_conf(application_id, 'credentials.tal INTERNAL_SECRET={{ applications | get_app_conf(application_id, 'credentials.talk_internal_secret') }} TZ={{ HOST_TIMEZONE }} TALK_PORT={{ NEXTCLOUD_TALK_INT_TURN_PORT }} +TURN_MIN_PORT={{ NEXTCLOUD_TALK_RELAY_PORT_START }} +TURN_MAX_PORT={{ NEXTCLOUD_TALK_RELAY_PORT_END }} +COTURN_MIN_PORT={{ NEXTCLOUD_TALK_RELAY_PORT_START }} +COTURN_MAX_PORT={{ NEXTCLOUD_TALK_RELAY_PORT_END }} {% endif %} {% if NEXTCLOUD_WHITEBOARD_ENABLED %} diff --git a/roles/web-app-nextcloud/vars/main.yml b/roles/web-app-nextcloud/vars/main.yml index 88374a35..12acc406 100644 --- a/roles/web-app-nextcloud/vars/main.yml +++ b/roles/web-app-nextcloud/vars/main.yml @@ -58,16 +58,23 @@ NEXTCLOUD_PROXY_VERSION: "{{ applications | get_app_conf(application_ NEXTCLOUD_CRON_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.cron.name') }}" ### Talk +#### Service NEXTCLOUD_TALK_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.talk.name') }}" NEXTCLOUD_TALK_IMAGE: "{{ applications | get_app_conf(application_id, 'docker.services.talk.image') }}" NEXTCLOUD_TALK_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.talk.version') }}" -NEXTCLOUD_TALK_ENABLED: "{{ applications | get_app_conf(application_id, 'plugins.spreed.enabled') }}" -NEXTCLOUD_TALK_STUN_PORT: "{{ ports.public.stun[application_id] }}" -NEXTCLOUD_TALK_DOMAIN: "{{ NEXTCLOUD_DOMAIN }}" +NEXTCLOUD_TALK_ENABLED: "{{ applications | get_app_conf(application_id, 'docker.services.talk.internal') }}" +NEXTCLOUD_TALK_INTERNAL_ENABLED: "{{ applications | get_app_conf(application_id, 'plugins.spreed.enabled') }}" NEXTCLOUD_TALK_LOCATION: "/standalone-signaling/" -NEXTCLOUD_TALK_URL: "{{ [ NEXTCLOUD_URL, NEXTCLOUD_TALK_LOCATION ] | url_join }}" NEXTCLOUD_TALK_PORT_INTERNAL: "8081" NEXTCLOUD_TALK_INT_TURN_PORT: "3478" +NEXTCLOUD_TALK_RELAY_PORT_START: "{{ ports.public.relay_port_ranges[application_id ~ '_start'] }}" +NEXTCLOUD_TALK_RELAY_PORT_END: "{{ ports.public.relay_port_ranges[application_id ~ '_end' ] }}" +NEXTCLOUD_TALK_RELAY_PORT_RANGE: "{{ NEXTCLOUD_TALK_RELAY_PORT_START }}-{{ NEXTCLOUD_TALK_RELAY_PORT_END }}" + +# Connection +NEXTCLOUD_TALK_STUN_PORT: "{{ ports.public.stun[application_id] }}" +NEXTCLOUD_TALK_DOMAIN: "{{ NEXTCLOUD_DOMAIN }}" +NEXTCLOUD_TALK_URL: "{{ [ NEXTCLOUD_URL, NEXTCLOUD_TALK_LOCATION ] | url_join }}" ### Whiteboard NEXTCLOUD_WHITEBOARD_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.whiteboard.name') }}" diff --git a/roles/web-svc-coturn/config/main.yml.j2 b/roles/web-svc-coturn/config/main.yml similarity index 81% rename from roles/web-svc-coturn/config/main.yml.j2 rename to roles/web-svc-coturn/config/main.yml index ba03ed68..1ac5f080 100644 --- a/roles/web-svc-coturn/config/main.yml.j2 +++ b/roles/web-svc-coturn/config/main.yml @@ -1,3 +1,4 @@ +username: coturnconsumer server: domains: canonical: @@ -10,4 +11,4 @@ docker: redis: enabled: false database: - enabled: false \ No newline at end of file + enabled: false diff --git a/roles/web-svc-coturn/tasks/01_core.yml b/roles/web-svc-coturn/tasks/01_core.yml new file mode 100644 index 00000000..55c5eacc --- /dev/null +++ b/roles/web-svc-coturn/tasks/01_core.yml @@ -0,0 +1,3 @@ +- name: "Load 'sys-stk-semi-stateless' for '{{ application_id }}'" + include_role: + name: sys-stk-semi-stateless \ No newline at end of file diff --git a/roles/web-svc-coturn/tasks/main.yml b/roles/web-svc-coturn/tasks/main.yml index ad4af46a..4abd9dc3 100644 --- a/roles/web-svc-coturn/tasks/main.yml +++ b/roles/web-svc-coturn/tasks/main.yml @@ -1,4 +1,6 @@ --- -- name: "Load 'sys-stk-semi-stateless' for '{{ application_id }}'" - include_role: - name: sys-stk-semi-stateless +- block: + - name: "Load core functions for '{{ application_id }}'" + include_tasks: 01_core.yml + - include_tasks: utils/run_once.yml + when: run_once_web_svc_coturn is not defined \ No newline at end of file diff --git a/roles/web-svc-coturn/templates/docker-compose.yml.j2 b/roles/web-svc-coturn/templates/docker-compose.yml.j2 index a1b9df6d..d85fd055 100644 --- a/roles/web-svc-coturn/templates/docker-compose.yml.j2 +++ b/roles/web-svc-coturn/templates/docker-compose.yml.j2 @@ -11,17 +11,19 @@ - "{{ COTURN_STUN_PORT }}:{{ COTURN_STUN_PORT }}/tcp" - "{{ COTURN_STUN_PORT }}:{{ COTURN_STUN_PORT }}/udp" - "{{ COTURN_RELAY_PORT_RANGE }}/udp" + - "{{ COTURN_TLS_CERT_PATH }}:{{ COTURN_TLS_CERT_PATH }}:ro" + - "{{ COTURN_TLS_KEY_PATH }}:{{ COTURN_TLS_KEY_PATH }}:ro" command: > --use-auth-secret - --static-auth-secret={{ COTURN_STATIC_AUTH_SECRET }} + --static-auth-secret=${ COTURN_STATIC_AUTH_SECRET } --lt-cred-mech - --user={{ COTURN_USER_NAME }}:{{ COTURN_USER_PASSWORD }} + --user=${ COTURN_USER_NAME }:${ COTURN_USER_PASSWORD } --log-file=stdout --external-ip={{ networks.internet.ip4 }} {% if networks.internet.ip6|default('') %} --external-ip={{ networks.internet.ip6 }} {% endif %} - --realm={{ COTURN_REALM }} + --realm=${ COTURN_REALM } --fingerprint --total-quota=100 --stale-nonce diff --git a/roles/web-svc-coturn/templates/env.j2 b/roles/web-svc-coturn/templates/env.j2 new file mode 100644 index 00000000..37ce09db --- /dev/null +++ b/roles/web-svc-coturn/templates/env.j2 @@ -0,0 +1,4 @@ +COTURN_STATIC_AUTH_SECRET={{ COTURN_STATIC_AUTH_SECRET }} +COTURN_USER_NAME={{ COTURN_USER_NAME }} +COTURN_USER_PASSWORD={{ COTURN_USER_PASSWORD }} +COTURN_REALM={{ COTURN_REALM }} \ No newline at end of file diff --git a/roles/web-svc-coturn/vars/main.yml b/roles/web-svc-coturn/vars/main.yml index 84f119cf..c9e60594 100644 --- a/roles/web-svc-coturn/vars/main.yml +++ b/roles/web-svc-coturn/vars/main.yml @@ -19,7 +19,7 @@ COTURN_RELAY_PORT_END: "{{ ports.public.relay_port_ranges[application_id ~ COTURN_RELAY_PORT_RANGE: "{{ COTURN_RELAY_PORT_START }}-{{ COTURN_RELAY_PORT_END }}" ## Credentials -COTURN_USER_NAME: "{{ applications | get_app_conf(application_id, 'credentials.user_name') }}" +COTURN_USER_NAME: "{{ applications | get_app_conf(application_id, 'username') }}" COTURN_USER_PASSWORD: "{{ applications | get_app_conf(application_id, 'credentials.user_password') }}" COTURN_STATIC_AUTH_SECRET: "{{ applications | get_app_conf(application_id, 'credentials.auth_secret') }}"