3

I'm having an issue while trying to use Nginx as a reverse proxy where it doesn't appear to be sending the client certificate to the backend resource.

Here is the server block config:

server {
listen 443;
server_name my.server.tld;

location / {
    proxy_pass https://my.realserver.tld;

    proxy_redirect off;

    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;

    proxy_pass_request_body on;
    proxy_pass_request_headers on;
}

include ssl_params;
ssl_client_certificate /etc/ssl/client-ca.pem;
ssl_verify_client optional;

}

ssl_params:

ssl on;
ssl_certificate /etc/ssl/private/fullchain.pem;
ssl_certificate_key /etc/ssl/private/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;

ssl_protocols TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
ssl_prefer_server_ciphers on;

add_header Strict-Transport-Security max-age=15768000;

The nginx logs show a 502 from the back-end resource and the back-end shows an error: exception reported by IO thread: null cert chain Caused by javax.net.ssl.SSLHandshakeException: null cert chain

The back-end is a Tomcat server.

EDIT:

I've built nginx from source to try to use the ssl_preread stuff, but it's failing.

Here's the build info:

./nginx -V
nginx version: nginx/1.11.10
built by gcc 4.9.2 (Debian 4.9.2-10) 
built with OpenSSL 1.0.1t  3 May 2016
TLS SNI support enabled
configure arguments: --with-stream_ssl_preread_module --with-cc-opt='-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -D_FORTIFY_SOURCE=2' --with-ld-opt=-Wl,-z,relro --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-ipv6 --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_addition_module --with-http_dav_module --with-http_geoip_module --with-http_gzip_static_module --with-http_image_filter_module --with-http_sub_module --with-http_xslt_module --with-mail --with-mail_ssl_module

I'm using Debian Jessie 8.7.

Here's the failure when trying to use the example by Alexey:

./nginx -t
nginx: [emerg] unknown directive "stream" in /etc/nginx/nginx.conf:11
nginx: configuration file /etc/nginx/nginx.conf test failed
1
  • 2
    Nginx terminates the connection, then makes a new connection through to the back end server. You may need a TCP load balancer rather than an HTTP load balancer. Nginx or HAProxy are suitable, if configured correctly. nginx.com/resources/admin-guide/tcp-load-balancing
    – Tim
    Mar 2, 2017 at 22:41

2 Answers 2

4

This configuration works, but it requires nginx to be build with ngx_stream_ssl_preread_module.

stream {
    upstream yandex {
        server 93.158.134.3:443;
    }

    upstream google {
        server 64.233.164.113:443;
    }

    map $ssl_preread_server_name $upstream {
        hostnames;
        default yandex;
        .google.com google;
    }

    server {
        listen 12345;

        ssl_preread on;
        proxy_pass $upstream;
    }
}

Testing it:

$ openssl s_client -quiet -connect localhost:12345 -servername yandex.ru
depth=3 C = PL, O = Unizeto Sp. z o.o., CN = Certum CA
verify return:1
depth=2 C = PL, O = Unizeto Technologies S.A., OU = Certum Certification Authority, CN = Certum Trusted Network CA
verify return:1
depth=1 C = RU, O = Yandex LLC, OU = Yandex Certification Authority, CN = Yandex CA
verify return:1
depth=0 C = RU, O = Yandex LLC, OU = ITO, L = Moscow, ST = Russian Federation, CN = *.wfarm.yandex.net
verify return:1
^C

$ openssl s_client -quiet -connect localhost:12345 -servername google.com
depth=3 C = US, O = Equifax, OU = Equifax Secure Certificate Authority
verify return:1
depth=2 C = US, O = GeoTrust Inc., CN = GeoTrust Global CA
verify return:1
depth=1 C = US, O = Google Inc, CN = Google Internet Authority G2
verify return:1
depth=0 C = US, ST = California, L = Mountain View, O = Google Inc, CN = *.google.com
verify return:1
^C
6
  • I'm getting errors trying to use this configuration. I've built nginx from source using version 1.11.10 with the -with-stream_ssl_preread_module flag, but it can't valide the stream directive.
    – Andrew
    Mar 8, 2017 at 19:41
  • Where do you put it? stream must be top level directive
    – Alexey Ten
    Mar 8, 2017 at 20:48
  • I had it in the nginx.conf file. It was outside of the http block, at the top-level.
    – Andrew
    Mar 8, 2017 at 22:25
  • It wasn't complied with --with-stream apparently, event though I had the stream pre-read. It's now validating the config. I'm testing the viability of this configuration for my needs now.
    – Andrew
    Mar 8, 2017 at 22:31
  • Do you know where I could find more info on how to do more with this? What are it's limitations? What is it actually reading at the packet level to determine the destination? would this work for servers that don't have HTTPS?
    – Andrew
    Mar 8, 2017 at 22:53
2

Right now Nginx you've configured Nginx to act as a layer 7 load balancer. Nginx will terminate the HTTPS connections, then create another connection out to your back end server. Nginx has access to the client certificate, but there's no reason Nginx would choose to pass a client certificate on unless it's told to, assuming it has that capability.

What you need is a layer 4 load balancer, so the TCP connection is passed through to the back end server. This can be done in Nginx, HAProxy, or no doubt others. Nginx will look something like this (taken from this documentation)

stream {
  server {
    listen     127.0.0.1:12345;
    proxy_pass backend.example.com:12345;
  }
}

The linked article shows you how to load balance among multiple back end servers.

Update

You added a new requirement to load balance based on domain name. You can't use SNI because that's an HTTP (layer 7) feature. In this case one option is to assign multiple IPs to the server and have the client make the request to the correct IP.

stream {
  server {
    listen     127.0.0.1:12345;
    proxy_pass backend.example.com:12345;
  }
  server {
    listen     127.0.0.2:12345;
    proxy_pass backend2.example.com:12345;
  }
}

Alternate solution

An answer to this question suggests the following may work, in your proxy block. It's worth a shot.

proxy_set_header X-SSL-CERT $ssl_client_cert;
8
  • The reason I can't use a tcp load balancer is because I have to be able to resolve the hostname before sending the certificate onward; I don't know where to send the traffic otherwise. It seems like it gets the certificate, why can't it be configured to send it onward?
    – Andrew
    Mar 3, 2017 at 0:40
  • Answer updated. Based on 2 minutes of research on Google I think it might be possibly for Nginx to pass the client cert through.
    – Tim
    Mar 3, 2017 at 0:50
  • I have had no luck so far but this is my first Nginx deployment.
    – Andrew
    Mar 3, 2017 at 1:02
  • This doesn't seem like a simple problem to solve. Even my "alternate solution" is only a partial solution. Multiple IPs and Nginx listening on multiple ports as I describe in my first update is probably your better option. Maybe someone else can provide you with a better option.
    – Tim
    Mar 3, 2017 at 1:09
  • I'm hopeful. it seems like it should be something doable, but I'm not sure. By the way, proxy_set_header X-SSL-CERT $ssl_client_cert; didn't work. Still getting javax.net.ssl.SSLHandshakeException: null cert chain; in the App-Server log. I did try this before.
    – Andrew
    Mar 3, 2017 at 1:22

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .