aws / amazon-chime-sdk-js

A JavaScript client library for integrating multi-party communications powered by the Amazon Chime service.
Apache License 2.0
699 stars 473 forks source link

How can we proxy all Chime client communication through our servers? #2902

Open BLineBrian opened 3 weeks ago

BLineBrian commented 3 weeks ago

What are you trying to do?

We'd like to route all web meeting networking between the browser SDK and chime through a proxy. There are a handful of reasons one could want to do something like this, but in our case we are looking to limit the number of domains and IPs required by our application. Our users are often on sensitive networks that require an IT team to explicitly allow every outbound IP, hostname, and port. Chime requires over a hundred IP ranges to be opened, or open the wildcard *.chime.aws (which is equally problematic in this case).

How can the documentation be improved to help your use case?

The documentation very briefly mentions proxying and suggests using url rewriting to do it. As written, it says clearly that media can be proxied (over TCP), and implies that everything can be proxied. It's not clear to me if that's actually possible. Forwarding all of the necessary data is not trivial and there are no examples.

In our minimal case, which is to have a web conference and record it - without any filters or other magic - we would want the following to be proxied:

  1. TURN listeners, e.g. turns:ice.m1.ue1.app.chime.aws?transport=tcp. These URLs are passed to the pluggable urlRewriter which implies this should be possible. We tried to proxy these a handful of different ways (generally inspired by work others have done proxying CoTURN, which I think is analogous) and were not able to get it working.
  2. Signaling endpoints, e.g. signal.m1.ue1.app.chime.aws. We were able to get this working using the urlRewriter, with an approach similar to the one here: https://github.com/aws/amazon-chime-sdk-js/issues/951
  3. Static assets at https://static.sdkassets.chime.aws. These URLs are not passed to the pluggable urlRewriter so I don't see any obvious way to do this without editing source code.
  4. WebRTC media. I'm unclear if we need to do anything additional here. From testing, it appears the Chime peer is at a private 10.3.X.X IP, which I assume is hole punching behavior that I don't completely understand. Maybe this works itself out if we proxy the turn listener, I'm not sure.

More documentation on exactly what is possible with proxying, and more information on how to do it, would be very valuable.

Has anyone had success with any of the above?

What documentation have you looked at so far?

Github FAQs, API docs, and source code. See above.

hensmi-amazon commented 3 weeks ago
  1. and 4. here are the same. Media is connected through TURN to the backend peer (the private IP is reachable from the TURN instance).

Granted I haven't set up a proxy for media before, but are you trying to proxy UDP or TCP traffic? I'd assume TCP would be easier to set up (though be warned that media over TCP is a major downgrade, it's the fallback for a reason).

hundred IP ranges

Where is this coming from? It should just be 99.77.128.0/18. Also, if it's helpful, you can get away with just allowlisting one regions URLs, e.g.

turns:ice.m1.ue1.app.chime.aws

for us-east-1, if you explicitly place your meetings there.

BLineBrian commented 3 weeks ago

Ideally we'd like to proxy UDP for the reasons you mentioned, but TCP isn't in the end of the world. If clients want the best performance then they should open the required port ranges for Chime.

Where is this coming from? It should just be 99.77.128.0/18

The documentation indicates that all IPs for route53, cloudfront, and ec2 must be whitelisted. It does look like the media-specific endpoints (ice...chime.aws for TURN, signal...chime.aws for signaling) are in that 99.77.128.0/18 subnet.

But the UI sdk also uses a static assets endpoint (static.sdkassets.chime.aws) and an event data endpoint (data.svc...chime.aws), which are both cloudfront IPs. That's a pretty big list of subnets to whitelist. I does not look like the UI requires EC2 IPs, which was the most egregious one.

Also, if it's helpful, you can get away with just allowlisting one regions URLs, e.g.

Any chance it's possible to get a complete list of these URLs? IT teams don't like wildcards, but are more amenable to lists of addresses.

I did actually end up getting the ice/TURN proxying working for video conferencing. The only real problem left is that the urlRewriter function doesn't catch all addresses. The static.sdkassets.chime.aws in particular does not seem to have a built-in way to run through a proxy. We will fork the sdk and make a change to support that. If it works well, we will open a PR back to this repo. Because TCP and UDP don't really support hostnames, there's no magic way to make this proxy work. The plan is to use the URL rewriter to map Chime ice URIs to ports on our proxy server. We'll have to continue to update that mapping when we discover new URLs (it'd be great to get a list somehow!).

For example, we would URL rewrite:

ice.m1.ue1.app.chime.aws:3478 -> our-proxy.example.com:8000
ice.m3.ue1.app.chime.aws:3478 -> our-proxy.example.com:8001

Then those endpoints would forward to the original URLs based on a hardcoded list.

I was able to get UDP forwarding working with openresty, with a config like this:

stream {
  upstream turn_backend1 {
          server ice.m1.ue1.app.chime.aws:3478;
      }

    server {
        listen 8000 udp;
        proxy_pass turn_backend1;
        ssl_preread on;
  }

     upstream turn_backend2 {
        server ice.m3.ue1.app.chime.aws:3478;
    }

    server {
        listen 8001 udp;
        proxy_pass turn_backend2;
        ssl_preread on;
  }
}

I'm pretty certain it's possible to get TCP forwarding working with openresty but I haven't had a chance yet. I got that working in haproxy though, with a config like this:

listen turn1
    mode tcp
    bind 0.0.0.0:8000
    server turn ice.m1.ue1.app.chime.aws:443

listen turn2
    mode tcp
    bind 0.0.0.0:8001
    server turn2 ice.m3.ue1.app.chime.aws:443

And the URL rewriter is setup on the client side like this:

const meetingSessionConfiguration = new MeetingSessionConfiguration(JoinInfo.Meeting, JoinInfo.Attendee);
      meetingSessionConfiguration.urls.urlRewriter = (uri) => {
        if (uri?.match(/\.*signal.*?aws/gi)) {
          const wsUri = uri.replace('wss', 'https');
          return (
            'wss://signal-proxy.example.com/' + // replace with your domain, use wss if you have ssl
            '?url=' +
            encodeURIComponent(wsUri)
          );
        } else if (uri?.match(/turn:*/)) {
          if (uri === 'turn:ice.m1.ue1.app.chime.aws:3478?transport=udp') {
            return 'turn:ice-proxy.example.com:8000?transport=udp';
          } else if (uri === 'turn:ice.m3.ue1.app.chime.aws:3478?transport=udp') {
            return 'turn:ice-proxy.example.com:8001?transport=udp';
          } else if (uri === 'turns:ice.m3.ue1.app.chime.aws:443?transport=tcp') {
            return 'turn:ice-proxy.example.com:8000?transport=tcp';
          } else if (uri === 'turns:ice.m1.ue1.app.chime.aws:443?transport=tcp') {
            return 'turn:ice-proxy.example.com:8001?transport=tcp';
          } else {
           throw new Error(`Unknown ice server: ${uri}`);
          }
        }
        return uri;
      };

That code is junk but you get the picture. Hope that helps somebody.

I'm going to leave this issue open since the documentation good be better, and it's possible there's a better, built-in way to handle things. If I open a PR for url rewrite updates I'll link it here.

hensmi-amazon commented 2 weeks ago

Thanks for the response, i think this would probably help others in the future. May be a bit of time until this can make it into documentation since we'd probably need to set up our own working example :P.

As for the URLs it is just

signal.m1.ue1.app.chime.aws
ice.m1.ue1.app.chime.aws

Where ue1 can be replaced by the Meeting regions short hand, and m1 replaced by m1, m2, m3.