SNI-Based Transparent TLS Proxy Without Enumerating Backends

haproxyhttpsnginxreverse-proxysni

I'm in a situation where I have to provide a transparent reverse proxy in front of a set of thousands of backend https webservers, with the list changing (relatively) frequently.

I know I can tell haproxy to select a backend to connect to based on the SNI string the client sends along with the Client Hello (see e.g. Can a Reverse Proxy use SNI with SSL pass through?), but it seems I would need to enumerate all backends and refer to them individually in the configuration; i.e. "if SNI is so-and-so, talk to backend this-and-that".

I'd instead like to just take the SNI string from the Client Hello, look it up in DNS, connect to the IP DNS provides (on tcp port 443), relay the client hello to the server, and then keep relaying between the client and the server.

I don't want to inspect the traffic and don't want to install a new certificate on the clients.

Can haproxy do this? If not, what other program can?

Best Answer

I ended up using nginx with the stream SSL preread module.

The configuration is dead simple:

stream {
    server {
        resolver 127.0.0.1;
        listen 443;
        ssl_preread on;
        proxy_pass $ssl_preread_server_name:443;
    }
}

No http { } block would even be needed, but nginx segfaulted for me if I omitted it, so I have an empty http { } block in the config. Only the stream module is loaded. The resolver is needed for nginx to be able to look up the backend names in DNS; I have a caching recursive resolver on 127.0.0.1.

I make it so all clients that need to connect to any of the backends connect to my nginx instead (using a combination of split horizon DNS and DNAT), and nginx connects to the actual backends on their behalf. It's completely transparent to the clients.