3

Standard practice for RTMP is still to have a plain text stream key out on the wires.

I want to acccept RTMPS streams from encoders to NGINX however the RTMP module does not yet have RTMPS.

I'm not interested in all the relay solutions to allow taking an RTMP stream and sending to a place like facebook over RTMPS because the same security flaw is still there because at some point you are passing the keys over plain text.

My question is where can I find the reference specs on RTMPS? I'd like to know what keys are needed to make a proper handshake between an RTMPS source such as OBS and NGINX and then I will use the connection with the RTMP module. Can normal keys and an authority like Let's Encrypt be used on a server so that it can make the handshake with a RTMPS encoder?

I've seen stunnel used to wrap RTMP in TLS. Is it possible to do the reverse -- use stunnel to receive RTMPS and convert back to RTMP for the RTMP module?

3

UPDATE: This is my original answer that describes pretty well the issues one may face while implementing RTMPS with Nginx. However, I've added an improved version for more fine-tuned access control, and I recommend using the configuration from it, instead.


Yes, this is possible with stunnel, as RTMPS is just a RTMP session wrapped inside a standard TLS session. The examples on the Internet are mostly RTMP→RTMPS i.e. the stunnel is working as a plain text server and TLS client, which is configured with client = yes. Without that, the client defaults to no, which is the server mode.

The stunnel configuration could look like this:

[rtmps]
accept = 1935
connect = 127.0.0.1:1936
cert=/etc/letsencrypt/live/rtmp.example.com/fullchain.pem
key=/etc/letsencrypt/live/rtmp.example.com/privkey.pem

With this:

  • The Nginx should be listening for RTMP on local loopback, port 1936/tcp.
  • As you can't renew the Let's Encrypt ertificate using RTMP, you might need a HTTP server block for the HTTP-01 challenge, too.
  • As the connection to Nginx always comes from the stunnel i.e. from the 127.0.0.1, you can't use the allow/deny directives to limit connection based on IP addresses anymore. This means your access control would be limited to the key, alone, but at the same time it's less of a problem, as it's transmitted encrypted.

    However, this still causes problems, as you'd push the stream from the same IP than the clients that are using it, but you can't allow them to publish to your stream. Luckily, you don't have to push the stream from the application with the key, but you can also pull it from the public application (/live).

The following Nginx example configuration takes these considerations into account:

rtmp {
    server {
        listen 127.0.0.1:1936;
        chunk_size 4096;

        application app-secret-stream-key {
            live on;
            record off;
            allow publish 127.0.0.1;  # for streaming through stunnel
            allow play 127.0.0.1;     # for the pull from /live
        }

        application live {
            live on;
            record off;
            deny publish all;         # no need to publish on /live
            allow play all;           # playing allowed

            pull rtmp://127.0.0.1:1936/app-secret-stream-key;
        }
    }
}

http {
    server {
        listen 80;
        server_name rtmp.example.com;

        location ^~ /.well-known/acme-challenge/ {
            root /var/www/letsencrypt;
        }
        location / {
            return 404;
        }
    }
}

However, this is just an example, so you can and should modify it to fit your exact needs. (Also, I haven't tested this configuration but written it solely based on the documentation, so please feel free to correct, if I got something wrong.)

| improve this answer | |
  • 1
    I believe all the same can be done with NGINX alone (stream { } with TLS config). – Danila Vershinin May 30 at 14:52
  • 1
    If you have some time, please provide this alternative solution as an answer. I'd upvote it for sure! – Esa Jokinen May 30 at 15:02
  • 1
    Don't know if it's worthwhile a separate answer, but I've added it. – Danila Vershinin May 30 at 15:55
  • I've added another answer, an improved version using @DanilaVershinin's idea of using stream{}, but with an ability for IP based access control preventing publishing from anywhere. I hope @johnsonjp34 could choose it as the accepted answer, as it wraps everything up. – Esa Jokinen Jun 6 at 12:52
6

Since NGINX is capable of terminating TLS for upstream TCP servers, this should take care of it, using only NGINX (simply added stream section to config from @Esa Jokinen):

stream {
    upstream backend {
        server 127.0.0.1:1936;
    }
    server {
        listen 1935 ssl;
        proxy_pass backend;
        ssl_certificate /etc/letsencrypt/live/rtmp.example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/rtmp.example.com/privkey.pem;
    }
}

rtmp {
    server {
        listen 127.0.0.1:1936;
        chunk_size 4096;

        application app-secret-stream-key {
            live on;
            record off;
            allow publish 127.0.0.1;  # for streaming through stunnel
            allow play 127.0.0.1;     # for the pull from /live
        }

        application live {
            live on;
            record off;
            deny publish all;         # no need to publish on /live
            allow play all;           # playing allowed

            pull rtmp://127.0.0.1:1936/app-secret-stream-key;
        }
    }
}

http {
    server {
        listen 80;
        server_name rtmp.example.com;

        location ^~ /.well-known/acme-challenge/ {
            root /var/www/letsencrypt;
        }
        location / {
            return 404;
        }
    }
}
| improve this answer | |
  • I just fixed some details to make the configuration more coherent. It now uses the Let's Encrypt certificates that the HTTP part of the configuration is for, and the backend name was different on the upstream{} than on the server{}. Elegent solution without stunnel, indeed! – Esa Jokinen May 30 at 16:46
0

Nginx RTMPS + secret publishing key + IP address based access control

I decided to post this as another answer, as my first answer is still a good explanatory answer to keep, and I also wanted to give credits to Danila Vershinin for pointing out using Nginx's stream{}. However, while both these answers increases security by encrypting the contents including the key, they also remove the ability of access control using allow/deny [play|publish] address|subnet|all of the rtmp{} module.

The stream{} i.e. proxied TCP has own access control, but (unlike rtmp{}) it can't distinguish publishing from playing: with a single stream{} proxy everyone can both publish & play – or is denied from doing neither one. Therefore, an access control using both keys and IP restrictions requires a structure with separate proxies for both publishing and streaming: a separete TCP port for proxying the publishing with the key. The following diagram demonstrates this design:

enter image description here

Here, I use the standard port 1935/tcp for the RTMPS-play and an additional 1936/tcp for the RTMPS-publish. For the internal unencrypted RTMP connections I use similar ports 19351 and 19361. The red color represents unencrypted connections & untrusted networks, whereas the green color represents encrypted connections & trusted networks.

The proxied TCP now has two (RTMPS) configurations, but both can still use the same certificate:

stream {
    upstream publish {
        server 127.0.0.1:19361;
    }
    server {
        listen 1936 ssl;        # additional port for publishing
        proxy_pass backend;
        ssl_certificate /etc/letsencrypt/live/rtmp.example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/rtmp.example.com/privkey.pem;

        allow 192.0.2.1;        # allow publish from this IP
        allow 192.0.2.0/24;     # -- also supports CIDR notation!
        deny all;               # deny publish from the rest
    }

    upstream live {
        server 127.0.0.1:19351;
    }
    server {
        listen 1935 ssl;        # standard RTMP(S) port
        proxy_pass live;
        ssl_certificate /etc/letsencrypt/live/rtmp.example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/rtmp.example.com/privkey.pem;

        allow all;              # this is public (this is also the default)
    }
}

Likewise, we now need two separate (local loopback) RTMP servers for each application:

rtmp {
    server {
        listen 127.0.0.1:19361;
        chunk_size 4096;

        application secret-key {
            live on;
            record off;
            allow publish 127.0.0.1;  # publishing through rtmps://rtmp.example.com:1936
            allow play 127.0.0.1;     # for the pull from rtmp://localhost:19351/live
        }
    }

    server {
        listen 127.0.0.1:19351;
        chunk_size 4096;

        application live {
            live on;
            record off;
            deny publish all;         # no need to publish on /live -- IMPORTANT!!!
            allow play 127.0.0.1;     # playing through rtmps://rtmp.example.com:1935/live

            pull rtmp://127.0.0.1:19361/secret-key;
        }
    }
}

The actual IP based access control is done on the stream{} section, so only the deny publish all; is mandatory for preventing direct publishing using the /live application. I've added the allow directives to the rtmp{} section just to clarify (and comment on) the default behaviour of the RTMP access control.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.