Nginx with multiple client certificates on one host

5/28/2019

I want to use nginx 1.15.12 as a proxy for tls termination and authentication. If a valid client certificate is shown, the nginx server will forward to the respective backend system (localhost:8080 in this case) The current configuration does that for every request.

Unfortunately it is not possible to configure one certificate per location{} block. Multiple server blocks could be created, which each check for another certificate, but I have also the requirement to just receive requests via one port.

nginx.conf: |
    events {
      worker_connections  1024;  ## Default: 1024
    }

    http{
      # password file to be moved to seperate folder?
      ssl_password_file /etc/nginx/certs/global.pass;
      server {
        listen 8443;
        ssl on;
        server_name *.blabla.domain.com;
        error_log stderr debug;

        # server certificate
        ssl_certificate     /etc/nginx/certs/server.crt;
        ssl_certificate_key /etc/nginx/certs/server.key;

        # CA certificate for mutual TLS
        ssl_client_certificate /etc/nginx/certs/ca.crt;
        proxy_ssl_trusted_certificate /etc/nginx/certs/ca.crt;

        # need to validate client certificate(if this flag optional it won't
        # validate client certificates)
        ssl_verify_client on;

        location / {
          # remote ip and forwarding ip
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

          # certificate verification information
          # if the client certificate verified against CA, the header VERIFIED
          # will have the value of 'SUCCESS' and 'NONE' otherwise
          proxy_set_header VERIFIED $ssl_client_verify;

          # client certificate information(DN)
          proxy_set_header DN $ssl_client_s_dn;
          proxy_pass http://localhost:8080/;
        }
      }
    }

Ideally I would like to achieve something like that: (requests to any path "/" except "/blabla" should be checked with cert1, if "/blabla" is matching, another key should be used to check the client certificate.

nginx.conf: |
    events {
      worker_connections  1024;  ## Default: 1024
    }

    http{
      # password file to be moved to seperate folder?
      ssl_password_file /etc/nginx/certs/global.pass;
      server {
        listen 8443;
        ssl on;
        server_name *.blabla.domain.com;
        error_log stderr debug;

        # server certificate
        ssl_certificate     /etc/nginx/certs/server.crt;
        ssl_certificate_key /etc/nginx/certs/server.key;

        # CA certificate for mutual TLS
        ssl_client_certificate /etc/nginx/certs/ca.crt;
        proxy_ssl_trusted_certificate /etc/nginx/certs/ca.crt;

        # need to validate client certificate(if this flag optional it won't
        # validate client certificates)
        ssl_verify_client on;

        location / {
          # remote ip and forwarding ip
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

          # certificate verification information
          # if the client certificate verified against CA, the header VERIFIED
          # will have the value of 'SUCCESS' and 'NONE' otherwise
          proxy_set_header VERIFIED $ssl_client_verify;

          # client certificate information(DN)
          proxy_set_header DN $ssl_client_s_dn;
          proxy_pass http://localhost:8080/;
        }

        location /blabla {
          # Basically do the same like above, but use another ca.crt for checking the client cert.
        }
      }
    }

Im on a kubernetes cluster but using ingress auth mechanisms is no option here for reasons. Ideal result would be a way to configure different path, with different certificates for the same server block in nginx.

Thank you!

Edit: The following nginx.conf can be used to check different certificates within nginx. Therefore 2 independent server{} blocks are needed with a different server_name. The URI /blabla can now only be accessed via blabla-api.blabla.domain.com.

events {
      worker_connections  1024;  ## Default: 1024
    }
    http{
      server_names_hash_bucket_size 128;
      server {
        listen 8443;
        ssl on;
        server_name *.blabla.domain.com;
        error_log stderr debug;
        # password file (passphrase) for secret keys
        ssl_password_file /etc/nginx/certs/global.pass;
        # server certificate
        ssl_certificate     /etc/nginx/certs/server.crt;
        ssl_certificate_key /etc/nginx/certs/server.key;
        # CA certificate for mutual TLS
        ssl_client_certificate /etc/nginx/certs/ca.crt;
        proxy_ssl_trusted_certificate /etc/nginx/certs/ca.crt;
        # need to validate client certificate(if this flag optional it won't
        # validate client certificates)
        ssl_verify_client on;

        location / {
          # remote ip and forwarding ip
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          # certificate verification information
          # if the client certificate verified against CA, the header VERIFIED
          # will have the value of 'SUCCESS' and 'NONE' otherwise
          proxy_set_header VERIFIED $ssl_client_verify;
          # client certificate information(DN)
          proxy_set_header DN $ssl_client_s_dn;
          proxy_pass http://localhost:8080/;
        }
        location /blabla {
          return 403 "authorized user is not allowed to access /blabla";
        }

      }
      server {
        listen 8443;
        ssl on;
        server_name blabla-api.blabla.domain.com;
        error_log stderr debug;
        # password file (passphrase) for secret keys
        ssl_password_file /etc/nginx/certs/global-support.pass;
        # server certificate
        ssl_certificate     /etc/nginx/certs/server-support.crt;
        ssl_certificate_key /etc/nginx/certs/server-support.key;
        # CA certificate for mutual TLS
        ssl_client_certificate /etc/nginx/certs/ca-support.crt;
        proxy_ssl_trusted_certificate /etc/nginx/certs/ca-support.crt;
        # need to validate client certificate(if this flag optional it won't
        # validate client certificates)
        ssl_verify_client on;

        location /blabla {
          # remote ip and forwarding ip
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          # certificate verification information
          # if the client certificate verified against CA, the header VERIFIED
          # will have the value of 'SUCCESS' and 'NONE' otherwise
          proxy_set_header VERIFIED $ssl_client_verify;
          # client certificate information(DN)
          proxy_set_header DN $ssl_client_s_dn;
          proxy_pass http://localhost:8080/blabla;
        }
      }
    }
-- LeonG
authentication
kubernetes
nginx

1 Answer

6/20/2019

I guess SNI is the answer. With that in the ssl handshake a server with one IP and one port can provide multiple certificates

But in my understanding server_name attribute has to be different for the two servers. Not sure if this is needed for top and second level domain, or if you can do it simply with the path.

SNI extends the handshake protocol of TLS. This way before the connection is established during the ssl handshake the server can know what certificate to use.

Newer nginx versions should have SNI enabled by default. Can be checked: nginx -V

Look at this how to structure the nginx.conf

-- Matthias Rich
Source: StackOverflow