1

I have system of IoT devices behind a NAT, so they are not accessible from the public internet (although it's desired). To overcome this I tied them into a VPN, with one member exposed to the public internet to act as a gateway. The VPN has an internal domain set up, and each member of the network has a subdomain based on a unique ID (let's go with MAC address), like so: 12a4f81ead4e.vpn.example.com

I wish to create a reverse proxy on the Gatway to proxy requests, running nginx.

The plan is to create a DNS record for the gateway, *.gateway.com, and route (ahem, proxy) traffic going to/from 12a4f81ead4e.gateway.com to 12a4f81ead4e.vpn.example.com. And so the end-user would just need to type 12a4f81ead4e.gateway.com into their browser to access their device. I'd like to use nginx, as the gateway is already running nginx for other purposes.

I expect HTTP requests to be easy, and can be done with a carefully crafted nginx proxy_pass directive.

But what about HTTPS requests? As far as I understand, TLS passthrough based on SNI is now implemented by nginx, but all the examples I've seen so far create a static map for ... well mapping the incoming SNI to a target upstream:

stream {
  map $ssl_preread_server_name $selected_upstream {
    example.org upstream_1;
    example.net upstream_2;
    example.com upstream_3;
    default upstream_4;
  }
  upstream upstream_1 { server 10.0.0.1:443; }
  upstream upstream_2 { server 10.0.0.2:443; }
  upstream upstream_3 { server 10.0.0.3:443; }
  upstream upstream_4 { server 10.0.0.4:443; }
  server {
    listen 10.0.0.5:443;
    proxy_pass $selected_upstream;
    ssl_preread on;
  }
}

Problem is devices are added/removed dynamically from the VPN, and I don't want to rewrite the nginx config files all the time. If reading the map from a file is possible, that's a step in the right direction, although I think nginx would need to be reloaded every time that changes, which raises permissions issues, that could be circumvented with sudo rules of course, but not the best solution.

Also I only want to proxy requests coming in to *.gateway.com, and server other https requests normally to the existing vhosts. If at all possile, I would like to avoid terminating the SSL connection. Not really a hard requirement, but would like to implement it that way if techinically doable. Also just for the kicks.

I'm fine internally listening on an alternate port for the other vhosts, I did something similar for HTTP when I wanted to set a "global" location, and moved all HTTP vhosts to port 81, and implemented a catch-all vhost on port 80 that served the "global" location, and proxied everything else to port 81. :)

So... What I would need it something like this (obviously not working):

stream {
  map $ssl_preread_server_name $selected_upstream {
    (.*).gateway.com $1.vpn.example.com;
    default normal_serve;
  }

  upstream normal_serve { server 127.0.0.1:8443; }

  server {
    listen 0.0.0.0:443;
    proxy_pass $selected_upstream;
    ssl_preread on;
  }

  server {
    listen 127.0.0.1:8443;
    server_name other.website.com;

    (...)
  }
}
  • SNI isn't relevant here. SNI is a solution for having multiple SSL certs attached to a single IP address. That isn't a requirement for you. All you need is a wildcard certificate (*.gateway.com). Once TLS handshake has taken place, Nginx knows what the host header is. Beyond that, I'm not really sure what your question is. What exactly is not working? The regex in the map directive looks fine. – Garreth McDaid Jan 21 '20 at 01:55
  • 1
    SNI *is* relevant, as I do not want to decrypt the tls traffic. Since the sni header comes in plain text, it is possible to forward traffic based on that without needing to decrypt and basically become mitm. See this for more info: https://serverfault.com/questions/625362/can-a-reverse-proxy-use-sni-with-ssl-pass-through – Ákos Vandra-Meyer Jan 21 '20 at 09:57
  • You can have regexes like that in the map? I need to try that, didn't think it was possible – Ákos Vandra-Meyer Jan 21 '20 at 09:58
  • https://serverfault.com/a/952499/385030 for the answer regarding nginx – Ákos Vandra-Meyer Jan 21 '20 at 10:04

1 Answers1

1

This does the trick:

stream {
  resolver 8.8.8.8;

  map $ssl_preread_server_name $selected_upstream {
    ~(.*).gateway.example.com $1.vpn.example.com:443;
    default 127.0.0.1:8443;
  }

  server {
    listen 0.0.0.0;
    proxy_pass $selected_upstream;
    ssl_preread on;
  }
}

http {
  resolver 8.8.8.8;
  server {
    listen 127.0.0.1:8443 ssl;
    (...)
  }
}