0

I'm having a bunch of virtual hosts served behind a nginx reverse proxy. At the end of each there is a server that has valid certificates for the given virtual domain.

E.g.

api.example.com -> proxy_pass https://api.example.com; # which resolves locally to a docker instance that has the certificates for api.example.com

Now, my problem is that, the proxy server itself seems to be needing its own certificates and I don't understand why. Since domain names and subdomains don't get encrypted over https, why can't I simply forward the certificate of each proxied server? Or can I? How?

This is what I have so far:

server {
    listen      80;
    listen [::]:80;
    server_name *.example.com;

    location / {
        proxy_pass http://$http_host$uri$is_args$args;
    }
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name *.example.com;

    location / {
        proxy_pass https://$http_host$uri$is_args$args;
    }
}

But the second directive requires certificates.

Rad'Val
  • 267
  • 2
  • 9
  • That is expected behavior. It’s a “proxy.” In other words, the HTTPS connection is terminated at nginx and a second connection is established between the backend server and nginx. In fact, the proxy_pass address doesn’t even need to use https unless you want the backend connection encrypted as well. – Appleoddity Nov 27 '18 at 17:57
  • @Appleoddity That makes sense. I think I wasn't fully understanding proxy servers, now I'm a step closer. – Rad'Val Nov 27 '18 at 18:03

2 Answers2

2

Think about what happens with a reverse proxy:

  • The client connects to the proxy and asks for a resource.
  • The proxy takes this URL and sends a different request to the backend server, takes the returned result and sends it out to the client.
  • Specifically, what does not happen is that the network connection is just forwarded to the backend server with an IP level (layer 4) forwarding.

So the HTTPS connection from the client is terminated at the proxy and thus the proxy needs to have a valid certificate for this domain.

Why can't you just deploy the certificate currently in the docker container to the proxy? This is the normal approach. The connection to the backend from the proxy doesn't need to be encrypted in a secure network (like in your case where the docker container is running on the same host).

Sven
  • 98,649
  • 14
  • 180
  • 226
  • But maybe then proxying is not what I'm looking for? Is there a way to achieve this host/container paring under the same ip? As fo reason, it's all about making it easier to deploy and maintain in the future (it's something nice to have, sort of separation of concerns I guess) – Rad'Val Nov 27 '18 at 18:01
  • On the contrary, proxying should be what you are after. The usual approach for your situation (as far as I understand it) where you have multiple docker containers and want to export each to the public is to have each docker container listening on a different port to HTTP (usually *not* HTTPS) requests and then have `nginx` or similar tools connect to these containers for incoming requests for the respective domain. – Sven Nov 27 '18 at 18:07
  • Yeah, but that would be slightly less contained and secure than having them over ssl. In any case, your answer answers the question – Rad'Val Nov 27 '18 at 18:08
  • nginx can do it with some configuration (for which tutorials are scarce, unfortunately) as can haproxy. – Michael Hampton Nov 27 '18 at 18:09
  • For this added layer of security, you can either try to get layer4 proxying working (as per @MichaelHampton's comment), or use HTTPS internally as well with a set of different host names, maybe even just self-signed (so you proxy to https://api-internal.example.com and just make sure nginx accept the cert offered by the container). – Sven Nov 27 '18 at 18:13
1

Nginx is actually capable to do this. Not at http level, but stream level. The configuration itself is not that difficult. Here is a full tutorial on how to do it

Also another example on how to achieve this can be found here. I'll copy the example here for completeness.

stream {

    map $ssl_preread_server_name $name {
        vpn1.app.com vpn1_backend;
        vpn2.app.com vpn2_backend;
        https.app.com https_backend;
        default https_default_backend;
    }

    upstream vpn1_backend {
        server 10.0.0.3:443;
    }

    upstream vpn2_backend {
        server 10.0.0.4:443;
    }

    upstream https_backend {
        server 10.0.0.5:443;
    }

    upstream https_default_backend {
        server 127.0.0.1:443;
    }

    server {
        listen 10.0.0.1:443;
        proxy_pass $name;
        ssl_preread on;
    }
}

For those downvoting: I might've not explained this well enough in the question, but this is exactly what I wanted.

Rad'Val
  • 267
  • 2
  • 9