diff --git a/docker-compose.tmpl.yml b/docker-compose.tmpl.yml index e496dfb4..c882975c 100644 --- a/docker-compose.tmpl.yml +++ b/docker-compose.tmpl.yml @@ -140,6 +140,10 @@ services: - "10.7.7.1:48082:48082" - "10.7.7.1:48081:48081" - "10.7.7.1:48087:48087" + {{ if .Env.NGINX_BIND_IP }} + # Optional access for a reverse proxy running on another host. + - "${NGINX_BIND_IP}:48087:48087" + {{ end }} networks: bbb-net: ipv4_address: 10.7.7.34 @@ -244,9 +248,9 @@ services: environment: ESL_PASSWORD: ${FSESL_PASSWORD:-ClueCon} {{ if .Env.EXTERNAL_IPv6 }} - MS_WEBRTC_LISTEN_IPS: '[{"ip":"::", "announcedIp":"${EXTERNAL_IPv6}"}, {"ip":"${EXTERNAL_IPv4}", "announcedIp":"${EXTERNAL_IPv4}"}]' + MS_WEBRTC_LISTEN_IPS: '[{"ip":"::", "announcedIp":"${EXTERNAL_IPv6}"}, {"ip":"{{ .Env.INTERNAL_IPv4 }}", "announcedIp":"${EXTERNAL_IPv4}"}]' {{else}} - MS_WEBRTC_LISTEN_IPS: '[{"ip":"${EXTERNAL_IPv4}", "announcedIp":"${EXTERNAL_IPv4}"}]' + MS_WEBRTC_LISTEN_IPS: '[{"ip":"{{ .Env.INTERNAL_IPv4 }}", "announcedIp":"${EXTERNAL_IPv4}"}]' {{end}} volumes: - ./data/mediasoup:/var/mediasoup @@ -520,11 +524,11 @@ services: image: coturn/coturn:4.9.0-alpine restart: unless-stopped command: - - "--external-ip=${EXTERNAL_IPv4}/${EXTERNAL_IPv4}" + - "--external-ip=${EXTERNAL_IPv4}/{{ .Env.INTERNAL_IPv4 }}" - "--external-ip=${EXTERNAL_IPv6:-::1}/${EXTERNAL_IPv6:-::1}" - "--static-auth-secret=${TURN_SECRET}" - "--allowed-peer-ip=${EXTERNAL_IPv4}" - - "--relay-ip=${EXTERNAL_IPv4}" + - "--relay-ip={{ .Env.INTERNAL_IPv4 }}" - "--relay-ip=${EXTERNAL_IPv6:-::1}" volumes: - ./mod/coturn/turnserver.conf:/etc/coturn/turnserver.conf diff --git a/docs/behind-nat.md b/docs/behind-nat.md index 3a8d1e67..98b2d055 100644 --- a/docs/behind-nat.md +++ b/docs/behind-nat.md @@ -1,14 +1,46 @@ -# Note if you use a Firewall / NAT -Kurento binds somehow always to the external IP instead of the local one or `0.0.0.0`. For that reason you need to add your external IP to your interface. +# Running behind a firewall or NAT -#### Temporary way (until next reboot) +BigBlueButton's media services must listen on an address assigned to the host +while advertising the public address that browsers use. The setup script +detects both addresses and writes them to `.env`: + +```dotenv +EXTERNAL_IPv4=203.0.113.10 +INTERNAL_IPv4=192.168.50.10 ``` -$ ip addr add 144.76.97.34/32 dev ens3 + +`EXTERNAL_IPv4` is the public address configured on the router. +`INTERNAL_IPv4` is the address assigned to the BigBlueButton host. On a server +with a directly assigned public address, both values are normally identical. + +Verify these values after running `./scripts/setup`. If either address changes, +update `.env`, regenerate Compose, and recreate the affected services: + +```bash +./scripts/generate-compose +docker compose up -d --no-build webrtc-sfu coturn ``` -#### Permanent way -Specific to your linux distribution. Use a search engine of your choice. ;) +The generated configuration makes the WebRTC SFU listen on `INTERNAL_IPv4` +and announce `EXTERNAL_IPv4`. Coturn uses the equivalent public/private +external-address mapping and relays through `INTERNAL_IPv4`. This avoids +binding media processes to a public address that is not assigned to the host. + +## Router and firewall rules + +Forward the following UDP traffic to `INTERNAL_IPv4` and allow it through the +host firewall: + +- `16384-32768` for WebRTC media +- `32769-65535` for TURN relay traffic +- `3478` when using the bundled coturn service -## Ports -Also don't forget to forward all necassary ports listed in https://docs.bigbluebutton.org/admin/configure-firewall.html +Also allow the Docker network (`10.7.7.0/24` by default) through the host +firewall. See the +[BigBlueButton firewall documentation](https://docs.bigbluebutton.org/administration/firewall-configuration/) +for the complete port list. +If HTTPS terminates on a reverse proxy running on another host, forward TCP +`443` to that proxy and route media UDP ports directly to the BigBlueButton +host. See [existing-web-server.md](existing-web-server.md) for the proxy bind +configuration. diff --git a/docs/existing-web-server.md b/docs/existing-web-server.md index 4ca3f09a..6c0307ed 100644 --- a/docs/existing-web-server.md +++ b/docs/existing-web-server.md @@ -8,11 +8,26 @@ You could dedicate a virtual host to BigBlueButton, allowing external access to ## Installation 1. Install BigBlueButton Docker [as explained above](#install). While running the setup script, please choose `n` when you're asked the following question: `Should an automatic HTTPS Proxy be included? (y/n)`. -2. Now all the required Docker containers should be running. BigBlueButton listens to port 48087 (among others, but 48087 is intended for external reverse proxies). By default, the port is only opened on the internal bbb-net network created by docker-compose, so either your reverse proxy should run within the same docker-compose file or otherwise have access to the network, or the port should be made available on the host system by adding something like: +2. Now all the required Docker containers should be running. BigBlueButton listens to port 48087 (among others, but 48087 is intended for external reverse proxies). By default, the port is only exposed on loopback and the local Docker bridge. + + If the reverse proxy runs on the same host, use `127.0.0.1:48087`. If it runs on another host, set `NGINX_BIND_IP` in `.env` to an address assigned to the BigBlueButton host and regenerate Compose: + + ```dotenv + NGINX_BIND_IP=192.168.50.10 + ``` + + ```bash + ./scripts/generate-compose + docker compose up -d --no-build nginx + ``` + + Restrict TCP port `48087` to the reverse proxy's source address with the host or network firewall. Alternatively, attach a containerized reverse proxy to the `bbb-net` network and route it directly to the nginx container. + + For a manual Compose override, publish only the host address that the reverse proxy can reach: + ``` ports: - - "127.0.0.1:48087:48087 - - "[::1]:48087:48087 + - "192.168.50.10:48087:48087" ``` In the `nginx` container config in `docker-compose.yml`. diff --git a/mod/nginx/bigbluebutton b/mod/nginx/bigbluebutton index a7ff2f02..ff6401e3 100644 --- a/mod/nginx/bigbluebutton +++ b/mod/nginx/bigbluebutton @@ -15,6 +15,9 @@ server { # This variable is used instead of $scheme by bigbluebutton nginx include # files, so $scheme can be overridden in reverse-proxy configurations. set $real_scheme $scheme; + if ($http_x_forwarded_proto = "https") { + set $real_scheme https; + } # opt-out of google's floc tracking # https://www.eff.org/deeplinks/2021/03/googles-floc-terrible-idea @@ -38,8 +41,7 @@ server { proxy_pass http://greenlight:3000; proxy_set_header Host $host; proxy_set_header X-Forwarded-For "127.0.0.1"; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Ssl on; + proxy_set_header X-Forwarded-Proto $real_scheme; proxy_http_version 1.1; client_max_body_size 1000m; } @@ -48,8 +50,7 @@ server { proxy_pass http://greenlight:3000; proxy_set_header Host $host; proxy_set_header X-Forwarded-For "127.0.0.1"; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Ssl on; + proxy_set_header X-Forwarded-Proto $real_scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; proxy_http_version 1.1; @@ -82,4 +83,4 @@ server { proxy_pass http://hasura; } -} \ No newline at end of file +} diff --git a/sample.env b/sample.env index f9c43bce..8548d5c5 100644 --- a/sample.env +++ b/sample.env @@ -68,6 +68,14 @@ DOMAIN=bbb.example.com EXTERNAL_IPv4=144.76.97.10 EXTERNAL_IPv6= +# IPv4 address assigned to the BBB host. This is usually the same as +# EXTERNAL_IPv4 on a directly connected host and a private address behind NAT. +INTERNAL_IPv4=144.76.97.10 + +# Optional host address for a reverse proxy running on another machine. +# Leave empty to keep the nginx ports restricted to the local host. +NGINX_BIND_IP= + # STUN SERVER # stun.freeswitch.org STUN_IP=216.93.246.18 @@ -168,4 +176,3 @@ ENABLE_LEARNING_DASHBOARD=true # Define the default locale language code (i.e. 'en' for English) from the fallowing list: # [en, ar, fr, es] #DEFAULT_LOCALE=en - diff --git a/scripts/generate-compose b/scripts/generate-compose index 24fafb63..e60ff35b 100755 --- a/scripts/generate-compose +++ b/scripts/generate-compose @@ -53,6 +53,8 @@ docker run \ -e BBB_BUILD_TAG=${BBB_BUILD_TAG} \ -e DEV_MODE=${DEV_MODE:-false} \ -e IGNORE_TLS_CERT_ERRORS=${IGNORE_TLS_CERT_ERRORS:-} \ + -e INTERNAL_IPv4=${INTERNAL_IPv4:-$EXTERNAL_IPv4} \ + -e NGINX_BIND_IP=${NGINX_BIND_IP:-} \ -e EXTERNAL_IPv6=${EXTERNAL_IPv6:-} \ -e SIP_IP_ALLOWLIST=${SIP_IP_ALLOWLIST:-} \ -e ENABLE_RECORDING=${ENABLE_RECORDING:-false} \ diff --git a/scripts/setup b/scripts/setup index 94927a66..f20a50c0 100755 --- a/scripts/setup +++ b/scripts/setup @@ -23,6 +23,13 @@ ensure_bbbhtml5yml EXTERNAL_IPv4=$(curl -4 -s https://icanhazip.com) EXTERNAL_IPv6=$(curl -6 -s -m 10 https://icanhazip.com || true) +INTERNAL_IPv4="$EXTERNAL_IPv4" +if command -v ip >/dev/null 2>&1; then + detected_ipv4=$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{for (i = 1; i <= NF; i++) if ($i == "src") {print $(i + 1); exit}}') + if [ -n "$detected_ipv4" ]; then + INTERNAL_IPv4="$detected_ipv4" + fi +fi greenlight="" while [[ ! $greenlight =~ ^(y|n)$ ]]; do @@ -126,6 +133,7 @@ fi cp sample.env .env sed -i "s/EXTERNAL_IPv4=.*/EXTERNAL_IPv4=$EXTERNAL_IPv4/" .env sed -i "s/EXTERNAL_IPv6=.*/EXTERNAL_IPv6=$EXTERNAL_IPv6/" .env +sed -i "s/INTERNAL_IPv4=.*/INTERNAL_IPv4=$INTERNAL_IPv4/" .env sed -i "s/DOMAIN=.*/DOMAIN=$DOMAIN/" .env sed -i "s/.*STUN_IP=.*/STUN_IP=$EXTERNAL_IPv4/" .env