Jitsi Meet Docker - Use 443 TCP Only in case of corporate firewalls

Hi all,

for some of our clients which have strict firewall policies, blocking uncommon ports like 4443 and 10000, we would like to setup Jitsi meet (manual install or docker, whichever is the easiest way to configure in this case) to use ONLY TCP 443, both for communications and media stream, even though that would affect performances.
Could someone point me out on some docs / posts / guides or other material to properly do so? I haven’t been able to find a proper guide or documentation.

Thanks very much

Do a default jitsi-meet install and then followh this https://jitsi.github.io/handbook/docs/devops-guide/turn#use-turn-server-on-port-443
You will need to dns entries.

Thanks very much! I’ll configure and test it by blocking ports on the Jitsi VM’s firewall (except from 443) and seeing connection and media streams work.
Of course I will put, on fron of Jitsi, an apache reverse proxy to forward all the requests.
Do Jitsi and Turn Server have to be on different machines or I can install them both on the same machine?
Since I will be doing only tests for the moment, would a local DNS + resolution on users’s host file sufficient for it to work?

Hi, we are currently in the exact same situation and try to follow the handbook. Unfortunatley, there are some Inconsistencies in the guides regarding the required ports… (eg 4443)
We now have one specific question regarding tue linked guide (turn server on port 443). If the server sits behind a NAT, do we really need to specifiy our public IP as turn_backend, or the DMZ IP address?

Thanks a lot in advance

The instructions had been tested with turnserver on the same machine with using nginx. Not sure whether apache has something like $ssl_preread_server_name and usestream …

You better do it with nginx as this is tested and it works, see it working then try replacing it with apache …

It can be the internal IP, but make sure that from that internal IP, coturn can access jvb public address on port 10000 UDP.

1 Like

Does coturn really connect to the public IP address itself? If yes, that would mean, the traffic would go from the server to the firewall and back to itself again, which doesn’t make sense to me. But I think I misunderstood, what you meant.

Thanks

That is correct.

You can avoid that but you will open your internal network to the world. Let’s say jvb is in 192.168… you can control this with this: https://github.com/jitsi/jitsi-meet/blob/master/doc/debian/jitsi-meet-turn/turnserver.conf#L30
Removing this deny or adding allow rules just for your jvb IP addresses you can achieve direct in network communication.

OK, I’m going to try that. But how do I expose my networkt to the public? I only see advantages when not routing my traffic via our firewall again and if everything happens in host (we are using single host setup).

Your coturn can be used to relay traffic to the internal network, basically that way you don’t have a firewall and internal network is accessible for the public internet

But as soon as the coturn sits behind my firewall, it is accessible either way, no matter if the traffic is routed via the firewall again, isn’t it?

And besides that, I think the guide (https://jitsi.github.io/handbook/docs/devops-guide/turn) is missing the required change in the turnserver.conf after creating turn-specific Let’s Encrypt certificates, correct?

I don’t understand that. I think this is an explanation of the problem https://hackerone.com/reports/333419

It is handled by the script. https://github.com/jitsi/jitsi-meet/blob/master/resources/install-letsencrypt-cert.sh#L68

@damencho Thanks very much, I will try it. Is there a way to force P2P in the docker installation of release 4857 when there are only 2 partecipants?

Hi @damencho, I’m following the guide, I’ve skipped the certificate part since I’ve manually modified the turnserver conf with my own certificates.
Premise: my Jitsi server is behind a NAT and thus behind an Apache reverse proxy that has a virtualhost for proxying ports 80 and 443 (actually) and also port forwards on the same apache VM for TCP 4443 and UDP 10000 to nginx. With the docker installation, this was working fine. Now I’m trying to follow the guide and switch back to a standard installation.

So I have:
jitsi-meet-turnserver configurations:
turnserver.conf:

use-auth-secret
keep-address-family
static-auth-secret=__turnSecret__
realm=my.domain.it
cert=/etc/jitsi/meet/my.domain.it.crt
pkey=/etc/jitsi/meet/my.domain.it.key
no-multicast-peers
no-cli
no-loopback-peers
no-tcp-relay
no-tcp
listening-port=3478
tls-listening-port=5349
no-tlsv1
no-tlsv1_1
# https://ssl-config.mozilla.org/#server=haproxy&version=2.1&config=intermediate&openssl=1.1.0g&guideline=5.4
cipher-list=ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
# jitsi-meet coturn relay disable config. Do not modify this line
denied-peer-ip=0.0.0.0-0.255.255.255
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=100.64.0.0-100.127.255.255
denied-peer-ip=127.0.0.0-127.255.255.255
denied-peer-ip=169.254.0.0-169.254.255.255
denied-peer-ip=127.0.0.0-127.255.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.0.0.0-192.0.0.255
denied-peer-ip=192.0.2.0-192.0.2.255
denied-peer-ip=192.88.99.0-192.88.99.255
denied-peer-ip=192.168.0.0-192.168.255.255
denied-peer-ip=198.18.0.0-198.19.255.255
denied-peer-ip=198.51.100.0-198.51.100.255
denied-peer-ip=203.0.113.0-203.0.113.255
denied-peer-ip=240.0.0.0-255.255.255.255
syslog

jitsi-meet.conf:
stream {
upstream web {
server 127.0.0.1:4444;
}
upstream turn {
server 127.0.0.1:5349;
}
# since 1.13.10
map $ssl_preread_alpn_protocols $upstream {
~\bh2\b web;
~\bhttp/1. web;
default turn;
}

    server {
        listen 443;
        listen [::]:443;

        # since 1.11.5
        ssl_preread on;
        proxy_pass $upstream;

        # Increase buffer to serve video
        proxy_buffer_size 10m;
    }
}

For nginx, i have:

my.domain.it.conf

.....
.....
.....
server {
listen 4444 ssl;
listen [::]:4444 ssl;
server_name my.domain.it
.....
.....

under modules-enabled, I have created turn.conf:

stream {
    map $ssl_preread_server_name $name {
        video.opensquare.it web_backend;
        turn-video.opensquare.it turn_backend;
    }

    upstream web_backend {
        server 127.0.0.1:4444;
    }

    upstream turn_backend {
        server public_ip_of_apache:5349;
    }

    server {
        listen 443;
        listen [::]:443;

        # since 1.11.5
        ssl_preread on;

        proxy_pass $name;

        # Increase buffer to serve video
        proxy_buffer_size 10m;
    }
}

Question: the upstream turn_backend that should have the public IP, which one should have? the LAN address of the VM or the public IP of the Apache that is exposed to the internet and proxies to Jitsi’s Nginx? In the second case, I should add another port forward, right?

I’ve also modified the TURNS line in /etc/prosody/conf.d/my.domain.it.cfg.lua:

turncredentials = {
  { type = "stun", host = "video.opensquare.it", port = "3478" },
  { type = "turn", host = "video.opensquare.it", port = "3478", transport = "udp" },
  { type = "turns", host = "video.opensquare.it", port = "443", transport = "tcp" }
};

Am I missing something?

That should be the ip address of the turn server, if nginx and coturn are on the same machine this will be 127.0.0.1, if there are in the same network the private address …

1 Like

Hi @damencho,

I’ve followed the guide, skipping only the part of the letsencrypt certiticates, since I’m using the same valid certificate I already own for coturn and jitsi’s nginx. Here’s the network configuration:


Am I missing something? Is it correct to setup both virtualhosts for turn-jitsi.mydomain.it (proxypass to jitsi server on port 443) and jitsi.mydomain.it (proxypass to jitsi server on port 4444)?

With the configurations I’ve posted before, blocking outgoing 10000 port on client, audio and video won’t work. With it opened, everything works. As I said my goal is to simulate a corporate firewall which blocks every uncommon ports such as 10000, 3478 etc…

What am I missing? Could you help me?

Can Apache handle tcp streams, if you are doing http proxy forward that will not work as this is not http traffic. Check the example config for nginx, it used stream to forward traffic which is not http proxy, you need to find how to do that incase of Apache, I’m not familiar with it.

Ok, neither do I know about tcp streams in apache… So in this case, I should forward everything from the IBM firewall/networks directly to Jitsi’s Nginx, instead of reverse proxying it with apache, right?

I don’t know Apache but I have done it with haproxy.

1 Like

If you don’t have any other web servers and domains in this network, yes…
Otherwise, you need a reverse proxy which can handle TCP streams too

1 Like

hi @emrah and @damencho,

I’ve managed to “rebuild” the infrastructure, removing the Apache component from the equation.

Now I’ve got a public IP and two dns, say my.domain.com and turn-my.domain.com that resolves that IP and a firewall that forwards the ports:

80/tcp
443/tcp
10000/udp
22/tcp
3478/udp
5349/tcp

directly to the internal Jitsi VM.
I’ve followed the guides Damencho provided me for setting up the TURN server on the same machine, skipping the letsencrypt part since I already have valid wildcard certificates. It works with multiple partecipants, but not the way it was intended: Seems that the fallback on the 443 port only doesn’t work.
I tried opening 3 incognito windows on my browser and opening a conference. It works fine, but if I try it again with port 10000 UDP blocked for outgoing connection (simulating a corporate firewall) on windows defender firewall, only 2 partecipants calls (P2P) work, when the third join, audio and video stop working for everyone.

Could you help me sort this out?

Here are my configs file (with sensitive data changed/removed):

prosody my.domain.it.cfg.lua:

plugin_paths = { "/usr/share/jitsi-meet/prosody-plugins/" }

-- domain mapper options, must at least have domain base set to use the mapper
muc_mapper_domain_base = "my.domain.it";

turncredentials_secret = "*******";

turncredentials = {
  { type = "stun", host = "my.domain.it", port = "3478" },
  { type = "turn", host = "my.domain.it", port = "3478", transport = "udp" },
  { type = "turns", host = "turn-my.domain.it", port = "443", transport = "tcp" }
};

cross_domain_bosh = false;
consider_bosh_secure = true;
-- https_ports = { }; -- Remove this line to prevent listening on port 5284

-- https://ssl-config.mozilla.org/#server=haproxy&version=2.1&config=intermediate&openssl=1.1.0g&guideline=5.4
ssl = {
  protocol = "tlsv1_2+";
  ciphers = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"
}

VirtualHost "my.domain.it"
        -- enabled = false -- Remove this line to enable this host
        authentication = "anonymous"
        -- Properties below are modified by jitsi-meet-tokens package config
        -- and authentication above is switched to "token"
        --app_id="example_app_id"
        --app_secret="example_app_secret"
        -- Assign this host a certificate for TLS, otherwise it would use the one
        -- set in the global section (if any).
        -- Note that old-style SSL on port 5223 only supports one certificate, and will always
        -- use the global one.
        ssl = {
                key = "/etc/prosody/certs/my.domain.it.key";
                certificate = "/etc/prosody/certs/my.domain.it.crt";
        }
        speakerstats_component = "speakerstats.my.domain.it"
        conference_duration_component = "conferenceduration.my.domain.it"
        -- we need bosh
        modules_enabled = {
            "bosh";
            "pubsub";
            "ping"; -- Enable mod_ping
            "speakerstats";
            "turncredentials";
            "conference_duration";
            "muc_lobby_rooms";
        }
        c2s_require_encryption = false
        lobby_muc = "lobby.my.domain.it"
        main_muc = "conference.my.domain.it"
        muc_lobby_whitelist = { "recorder.my.domain.it" } -- Here we can whitelist jibri to enter lobby enabled rooms

Component "conference.my.domain.it" "muc"
    storage = "memory"
    modules_enabled = {
        "muc_meeting_id";
        "muc_domain_mapper";
        -- "token_verification";
    }
    admins = { "focus@auth.my.domain.it" }
    muc_room_locking = false
    muc_room_default_public_jids = true

-- internal muc component
Component "internal.auth.my.domain.it" "muc"
    storage = "memory"
    modules_enabled = {
      "ping";
    }
    admins = { "focus@auth.my.domain.it", "jvb@auth.my.domain.it" }
    muc_room_locking = false
    muc_room_default_public_jids = true

VirtualHost "auth.my.domain.it"
    ssl = {
        key = "/etc/prosody/certs/auth.my.domain.it.key";
        certificate = "/etc/prosody/certs/auth.my.domain.it.crt";
    }
    authentication = "internal_plain"

VirtualHost "recorder.my.domain.it"
    modules_enabled = {
      "ping";
    }
    authentication = "internal_plain"

Component "focus.my.domain.it"
    component_secret = "******"

Component "speakerstats.my.domain.it" "speakerstats_component"
    muc_component = "conference.my.domain.it"

Component "conferenceduration.my.domain.it" "conference_duration_component"
    muc_component = "conference.my.domain.it"

Component "lobby.my.domain.it" "muc"
    storage = "memory"
    restrict_room_creation = true
    muc_room_locking = false
    muc_room_default_public_jids = true

nginx my.domain.it.conf:

server_names_hash_bucket_size 64;

server {
    listen 80;
    listen [::]:80;
    server_name my.domain.it;

    location ^~ /.well-known/acme-challenge/ {
       default_type "text/plain";
       root         /usr/share/jitsi-meet;
    }
    location = /.well-known/acme-challenge/ {
       return 404;
    }
    location / {
       return 301 https://$host$request_uri;
    }
}
server {
    listen 4444 ssl;
    listen [::]:4444 ssl;
    server_name my.domain.it;

# Mozilla Guideline v5.4, nginx 1.17.7, OpenSSL 1.1.1d, intermediate configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;  # about 40000 sessions
    ssl_session_tickets off;

    add_header Strict-Transport-Security "max-age=63072000" always;

    ssl_certificate /etc/certs/wildcard_domain_it.crt;
    ssl_certificate_key /etc/certs/wildcard_domain_it.key;

    root /usr/share/jitsi-meet;

    # ssi on with javascript for multidomain variables in config.js
    ssi on;
    ssi_types application/x-javascript application/javascript;

    index index.html index.htm;
    error_page 404 /static/404.html;

    gzip on;
    gzip_types text/plain text/css application/javascript application/json image/x-icon application/octet-stream application/wasm;
    gzip_vary on;
    gzip_proxied no-cache no-store private expired auth;
    gzip_min_length 512;

    location = /config.js {
        alias /etc/jitsi/meet/my.domain.it-config.js;
    }

    location = /external_api.js {
        alias /usr/share/jitsi-meet/libs/external_api.min.js;
    }

    #ensure all static content can always be found first
    location ~ ^/(libs|css|static|images|fonts|lang|sounds|connection_optimization|.well-known)/(.*)$
    {
        add_header 'Access-Control-Allow-Origin' '*';
        alias /usr/share/jitsi-meet/$1/$2;

        # cache all versioned files
        if ($arg_v) {
          expires 1y;
        }
    }

    # BOSH
    location = /http-bind {
        proxy_pass      http://localhost:5280/http-bind;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header Host $http_host;
    }

    # xmpp websockets
    location = /xmpp-websocket {
        proxy_pass http://127.0.0.1:5280/xmpp-websocket?prefix=$prefix&$args;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $http_host;
        tcp_nodelay on;
    }

    # colibri (JVB) websockets for jvb1
    location ~ ^/colibri-ws/default-id/(.*) {
       proxy_pass http://127.0.0.1:9090/colibri-ws/default-id/$1$is_args$args;
       proxy_http_version 1.1;
       proxy_set_header Upgrade $http_upgrade;
       proxy_set_header Connection "upgrade";
       tcp_nodelay on;
    }

    location ~ ^/([^/?&:'"]+)$ {
        try_files $uri @root_path;
    }

    location @root_path {
        rewrite ^/(.*)$ / break;
    }

    location ~ ^/([^/?&:'"]+)/config.js$
    {
       set $subdomain "$1.";
       set $subdir "$1/";

       alias /etc/jitsi/meet/my.domain.it-config.js;
    }

    #Anything that didn't match above, and isn't a real file, assume it's a room name and redirect to /
    location ~ ^/([^/?&:'"]+)/(.*)$ {
        set $subdomain "$1.";
        set $subdir "$1/";
        rewrite ^/([^/?&:'"]+)/(.*)$ /$2;
    }

    # BOSH for subdomains
    location ~ ^/([^/?&:'"]+)/http-bind {
        set $subdomain "$1.";
        set $subdir "$1/";
        set $prefix "$1";

        rewrite ^/(.*)$ /http-bind;
    }

    # websockets for subdomains
    location ~ ^/([^/?&:'"]+)/xmpp-websocket {
        set $subdomain "$1.";
        set $subdir "$1/";
        set $prefix "$1";

        rewrite ^/(.*)$ /xmpp-websocket;
    }
}

Nginx coturn module configuration (turn.conf):

stream {
    map $ssl_preread_server_name $name {
        my.domain.it web_backend;
        turn-my.domain.it turn_backend;
    }

    upstream web_backend {
        server 127.0.0.1:4444;
    }

    upstream turn_backend {
        server 127.0.0.1:5349;
    }

    server {
        listen 443;
        listen [::]:443;

        # since 1.11.5
        ssl_preread on;

        proxy_pass $name;

        # Increase buffer to serve video
        proxy_buffer_size 10m;
    }
}

turnserver config ( /etc/turnserver.conf ):

# jitsi-meet coturn config. Do not modify this line
use-auth-secret
keep-address-family
static-auth-secret=Tlr7DCSJznqF7eZt
realm=my.domain.it
cert=/etc/certs/wildcard_domain_it.crt
pkey=/etc/certs/wildcard_domain_it.key
no-multicast-peers
no-cli
no-loopback-peers
no-tcp-relay
no-tcp
listening-port=3478
tls-listening-port=5349
no-tlsv1
no-tlsv1_1
# https://ssl-config.mozilla.org/#server=haproxy&version=2.1&config=intermediate&openssl=1.1.0g&guideline=5.4
cipher-list=ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
# jitsi-meet coturn relay disable config. Do not modify this line
denied-peer-ip=0.0.0.0-0.255.255.255
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=100.64.0.0-100.127.255.255
denied-peer-ip=127.0.0.0-127.255.255.255
denied-peer-ip=169.254.0.0-169.254.255.255
denied-peer-ip=127.0.0.0-127.255.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.0.0.0-192.0.0.255
denied-peer-ip=192.0.2.0-192.0.2.255
denied-peer-ip=192.88.99.0-192.88.99.255
denied-peer-ip=192.168.0.0-192.168.255.255
denied-peer-ip=198.18.0.0-198.19.255.255
denied-peer-ip=198.51.100.0-198.51.100.255
denied-peer-ip=203.0.113.0-203.0.113.255
denied-peer-ip=240.0.0.0-255.255.255.255
syslog