diff --git a/Makefile b/Makefile index 08ec3a9d41..1cccd104dc 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ FORCE_REBUILD ?= 0 JITSI_RELEASE ?= stable JITSI_BUILD ?= latest JITSI_REPO ?= jitsi -JITSI_SERVICES ?= base base-java web prosody jicofo jvb jigasi jibri +JITSI_SERVICES ?= base base-java web prosody jicofo jvb jigasi jibri turn BUILD_ARGS := --build-arg JITSI_REPO=$(JITSI_REPO) --build-arg JITSI_RELEASE=$(JITSI_RELEASE) ifeq ($(FORCE_REBUILD), 1) diff --git a/README.md b/README.md index 7c07d2c25a..6c905dc4a7 100644 --- a/README.md +++ b/README.md @@ -15,5 +15,4 @@ The installation manual is available [here](https://jitsi.github.io/handbook/doc ## TODO * Support container replicas (where applicable). -* TURN server. diff --git a/docker-compose.yml b/docker-compose.yml index 17ca6eca66..6a5bb3ef69 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -165,6 +165,11 @@ services: - LOG_LEVEL - PUBLIC_URL - TZ + - ENABLE_TURN + - TURN_SECRET + - TURN_HOST + - TURN_PORT + - TURN_TRANSPORT networks: meet.jitsi: aliases: diff --git a/env.example b/env.example index 5b62e08d8b..2ad04a7830 100644 --- a/env.example +++ b/env.example @@ -379,3 +379,43 @@ RESTART_POLICY=unless-stopped # Authenticate using external service or just focus external auth window if there is one already. # TOKEN_AUTH_URL=https://auth.meet.example.com/{room} +# +## Use TURN for P2P connections +##TURN_ENABLE_P2P=0 +# +## Use TURN for JVB (bridge mode) connections +##ENABLE_TURN=0 +# +## Realm to be used for the users with long-term credentials mechanism or with TURN REST API +##TURN_REALM=realm +# +## Secret for connect to TURN server +##TURN_SECRET=keepthissecret +# +## Username for admin panel +##TURN_ADMIN_USER=admin +# +## Password for admin panel +##TURN_ADMIN_SECRET=changeme +# +## HTTP(s) port for acess to admin panel +##TURN_ADMIN_PORT=8443 +# +## Type of TURN(s)/STUN. Can be turn or turns. +##TURN_TYPE=turns +# +## Annonce FQDN or IP address of turn server +##TURN_HOST=8.8.8.8 +# +## TLS/TCP/UDP turn port for connection +##TURN_PORT=5349 +# +## Transport for stun/turn connection. Can be tcp or udp. +##TURN_TRANSPORT=tcp +# +## RTP start port for turn/turns connections +##TURN_RTP_MIN=10000 +# +## RTP end port for turn/turns connections +##TURN_RTP_MAX=11000 +# diff --git a/prosody/rootfs/defaults/conf.d/jitsi-meet.cfg.lua b/prosody/rootfs/defaults/conf.d/jitsi-meet.cfg.lua index 86c52021c4..cd3aa860d3 100644 --- a/prosody/rootfs/defaults/conf.d/jitsi-meet.cfg.lua +++ b/prosody/rootfs/defaults/conf.d/jitsi-meet.cfg.lua @@ -95,6 +95,9 @@ VirtualHost "{{ .Env.XMPP_DOMAIN }}" {{ if and $ENABLE_AUTH (eq $AUTH_TYPE "ldap") }} "auth_cyrus"; {{end}} + {{ if .Env.ENABLE_TURN | default "0" | toBool }} + "turncredentials"; + {{end}} } {{ if and $ENABLE_LOBBY (not $ENABLE_GUEST_DOMAIN) }} diff --git a/prosody/rootfs/defaults/prosody.cfg.lua b/prosody/rootfs/defaults/prosody.cfg.lua index 467d4222b8..9a4ed549f0 100644 --- a/prosody/rootfs/defaults/prosody.cfg.lua +++ b/prosody/rootfs/defaults/prosody.cfg.lua @@ -172,3 +172,18 @@ smacks_max_hibernated_sessions = 1; smacks_max_old_sessions = 1; Include "conf.d/*.cfg.lua" + + +{{ if .Env.ENABLE_TURN | default "0" | toBool }} +turncredentials_secret = "{{ .Env.TURN_SECRET | default "keepthissecret" }}"; +turncredentials_port = {{ .Env.TURN_PORT | default "3478" }}; +turncredentials_ttl = {{ .Env.TURN_TTL | default "86400" }}; +turncredentials = { +{{ if .Env.TURN_HOST }} + { type = "{{ .Env.TURN_PROTO | default "turns" }}", + host = "{{ .Env.TURN_HOST }}", + port = {{ .Env.TURN_PORT | default "3478" }}, + transport = "{{ .Env.TURN_TRANSPORT | default "tcp" }}" + } +{{ end }} +{{ end }} diff --git a/prosody/rootfs/etc/cont-init.d/10-config b/prosody/rootfs/etc/cont-init.d/10-config index 82f02eb72d..c4699a8587 100644 --- a/prosody/rootfs/etc/cont-init.d/10-config +++ b/prosody/rootfs/etc/cont-init.d/10-config @@ -26,6 +26,11 @@ if [[ "$(stat -c %U /prosody-plugins-custom)" != "prosody" ]]; then fi cp -r /defaults/* /config +if [[ "${ENABLE_TURN}" == "1" || "${ENABLE_TURN}" == "true" ]]; then + [ -z "${GLOBAL_MODULES}" ] && export GLOBAL_MODULES="turncredentials" \ + || export GLOBAL_MODULES="${GLOBAL_MODULES},turncredentials" +fi + tpl /defaults/prosody.cfg.lua > $PROSODY_CFG tpl /defaults/conf.d/jitsi-meet.cfg.lua > /config/conf.d/jitsi-meet.cfg.lua diff --git a/turn.yml b/turn.yml new file mode 100644 index 0000000000..1df2f910e1 --- /dev/null +++ b/turn.yml @@ -0,0 +1,36 @@ +version: '3' + +services: + # coturn TURN server project + turn: + image: jitsi/turn + restart: always + volumes: + - ${CONFIG}/turn:/config + ports: + - '${TURN_PORT}:${TURN_PORT}/tcp' + - '${TURN_PORT}:${TURN_PORT}/udp' + - '${TURN_RTP_MIN}-${TURN_RTP_MAX}:${TURN_RTP_MIN}-${TURN_RTP_MAX}/udp' + - '${TURN_ADMIN_PORT}:${TURN_ADMIN_PORT}/tcp' + - '80:80' + environment: + - DOCKER_HOST_ADDRESS + - TURN_SECRET + - TURN_REALM + - TURN_ADMIN_ENABLE + - TURN_ADMIN_USER + - TURN_ADMIN_SECRET + - TURN_ADMIN_PORT + - TURN_TYPE + - TURN_HOST + - TURN_PORT + - TURN_TRANSPORT + - TURN_RTP_MIN + - TURN_RTP_MAX + - DISABLE_HTTPS + - ENABLE_LETSENCRYPT + - LETSENCRYPT_DOMAIN + - LETSENCRYPT_EMAIL + networks: + meet.jitsi: + diff --git a/turn/Dockerfile b/turn/Dockerfile new file mode 100644 index 0000000000..9772a1142b --- /dev/null +++ b/turn/Dockerfile @@ -0,0 +1,15 @@ +ARG VERSION +FROM coturn/coturn:${VERSION:-latest} + +RUN apk add --no-cache openssl +RUN apk add --no-cache certbot +RUN apk add --no-cache curl +RUN apk add --no-cache bash + +ADD ./rootfs/defaults/docker-entrypoint.sh /docker-entrypoint.sh + +ENTRYPOINT ["/docker-entrypoint.sh"] + +VOLUME ["/config"] + +EXPOSE 80 5349 8443 10000:11000/udp diff --git a/turn/Makefile b/turn/Makefile new file mode 100644 index 0000000000..7317d86e50 --- /dev/null +++ b/turn/Makefile @@ -0,0 +1,5 @@ +build: + docker build $(BUILD_ARGS) -t $(JITSI_REPO)/turn . + +.PHONY: build + diff --git a/turn/rootfs/defaults/docker-entrypoint.sh b/turn/rootfs/defaults/docker-entrypoint.sh new file mode 100755 index 0000000000..cc54a71871 --- /dev/null +++ b/turn/rootfs/defaults/docker-entrypoint.sh @@ -0,0 +1,38 @@ +#!/bin/ash +# make certs if not exist +if [[ ! -f /etc/ssl/cert.crt || ! -f /etc/ssl/cert.key ]]; then + openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 3650 -out certificate.pem -subj "/C=US/ST=NY/L=NY/O=IT/CN=${TURN_HOST}" +fi + +# set coturn admin user +turnadmin -A -u ${TURN_ADMIN_USER:-admin} -p ${TURN_ADMIN_SECRET:-changeme} + +# run coturn server with API auth method enabled. +turnserver -n \ +--verbose \ +--prod \ +--no-tlsv1 \ +--no-tlsv1_1 \ +--log-file=stdout \ +--listening-port=${TURN_PORT:-5349} \ +--tls-listening-port=${TURN_PORT:-5349} \ +--alt-listening-port=${TURN_PORT:-5349} \ +--alt-tls-listening-port=${TURN_PORT:-5349} \ +--cert=/etc/ssl/cert.crt \ +--pkey=/etc/ssl/cert.key \ +--min-port=${TURN_RTP_MIN:-10000} \ +--max-port=${TURN_RTP_MAX:-11000} \ +--no-stun \ +--use-auth-secret \ +--static-auth-secret=${TURN_SECRET:-keepthissecret} \ +--no-multicast-peers \ +--realm=${TURN_REALM:-realm} \ +--external-ip=$(curl -4k https://icanhazip.com 2>/dev/null) \ +--relay-ip=$(hostname -i) \ +--listening-ip=$(hostname -i) \ +--web-admin \ +--web-admin-ip=$(hostname -i) \ +--web-admin-port=${TURN_ADMIN_PORT:-8443} \ +--no-cli \ +--cli-password=${TURN_ADMIN_SECRET:-changeme} + diff --git a/turn/rootfs/defaults/letsencrypt-renew b/turn/rootfs/defaults/letsencrypt-renew new file mode 100644 index 0000000000..c64f6bea24 --- /dev/null +++ b/turn/rootfs/defaults/letsencrypt-renew @@ -0,0 +1,7 @@ +#!/bin/bash + +certbot --no-self-upgrade -n renew >> /config/le-renew.log + +# Not sur it reload the service ... +/bin/kill -HUP `cat /var/run/turnserver.pid 2>/dev/null` 2> /dev/null || true +exit 0 diff --git a/turn/rootfs/docker-entrypoint.sh b/turn/rootfs/docker-entrypoint.sh new file mode 100755 index 0000000000..ad303a2696 --- /dev/null +++ b/turn/rootfs/docker-entrypoint.sh @@ -0,0 +1,84 @@ +#!/bin/bash + +mkdir -p /config/keys +# make certs if not exist +# generate keys (maybe) +if [[ $DISABLE_HTTPS -ne 1 ]]; then + if [[ $ENABLE_LETSENCRYPT -eq 1 ]]; then + if [[ ! -f /etc/letsencrypt/live/$LETSENCRYPT_DOMAIN/fullchain.pem ]]; then + if ! certbot \ + certonly \ + --no-self-upgrade \ + --noninteractive \ + --standalone \ + --preferred-challenges http \ + -d $LETSENCRYPT_DOMAIN \ + --agree-tos \ + --email $LETSENCRYPT_EMAIL; then + + echo "Failed to obtain a certificate from the Let's Encrypt CA." + # this tries to get the user's attention and to spare the + # authority's rate limit: + sleep 15 + echo "Exiting." + exit 1 + else + echo "Let's Encrypt certificate generated." + cp -f /etc/letsencrypt/live/$LETSENCRYPT_DOMAIN/fullchain.pem /config/keys/cert.crt + cp -f /etc/letsencrypt/live/$LETSENCRYPT_DOMAIN/privkey.pem /config/keys/cert.key + fi + fi + + # setup certbot renewal script + if [[ ! -f /etc/periodic/weekly/letencrypt-renew ]]; then + cp /defaults/letsencrypt-renew /etc/periodic/weekly/ + fi + else + # use self-signed certs + if [[ -f /config/keys/cert.key && -f /config/keys/cert.crt ]]; then + echo "using keys found in /config/keys" + else + echo "generating self-signed keys in /config/keys, you can replace these with your own keys if required" + SUBJECT="/C=US/ST=TX/L=Austin/O=jitsi.org/OU=Jitsi Server/CN=*" + openssl req -new -x509 -days 3650 -nodes -out /config/keys/cert.crt -keyout /config/keys/cert.key -subj "$SUBJECT" + fi + fi +fi + +# use non empty TURN_PUBLIC_IP variable, othervise set it dynamically. +[ -z "${TURN_PUBLIC_IP}" ] && export TURN_PUBLIC_IP=$(curl -4ks https://icanhazip.com) +[ -z "${TURN_PUBLIC_IP}" ] && echo "ERROR: variable TURN_PUBLIC_IP is not set and can not be set dynamically!" && kill 1 + +# set coturn web-admin access +if [[ "${TURN_ADMIN_ENABLE}" == "1" || "${TURN_ADMIN_ENABLE}" == "true" ]]; then + turnadmin -A -u ${TURN_ADMIN_USER:-admin} -p ${TURN_ADMIN_SECRET:-changeme} + export TURN_ADMIN_OPTIONS="--web-admin --web-admin-ip=$(hostname -i) --web-admin-port=${TURN_ADMIN_PORT:-8443}" +fi + +#run cron +crond + +# run coturn server with API auth method enabled. +turnserver -n ${TURN_ADMIN_OPTIONS} \ + --verbose \ + --prod \ + --no-tlsv1 \ + --no-tlsv1_1 \ + --log-file=stdout \ + --listening-port=${TURN_PORT:-5349} \ + --tls-listening-port=${TURN_PORT:-5349} \ + --alt-listening-port=${TURN_PORT:-5349} \ + --alt-tls-listening-port=${TURN_PORT:-5349} \ + --cert=/config/keys/cert.crt \ + --pkey=/config/keys/cert.key \ + --min-port=${TURN_RTP_MIN:-10000} \ + --max-port=${TURN_RTP_MAX:-11000} \ + --no-stun \ + --use-auth-secret \ + --static-auth-secret=${TURN_SECRET:-keepthissecret} \ + --no-multicast-peers \ + --realm=${TURN_REALM:-realm} \ + --listening-ip=$(hostname -i) \ + --external-ip=${TURN_PUBLIC_IP} \ + --cli-password=NotReallyCliUs3d \ + --no-cli