Linux – How to config nginx reverse proxy to accept HTTPS client with private key connection

linuxnginxnode.jsreverse-proxy

I got problem in Nginx reverse proxy configuration, the problem is complex, please read the story.

Our company have a internal infrastructure, in the old architecture the client (In fact, they are services was writen in NodeJS) is connecting the servers with IP directly, the client are using pfx options of https module to attend a private key for identification.

Just like following image.

Orignal architecture

But we have a new structure, all of servers will close all of the opening ports, and makes the servers be interal accessable only, a domain name based nginx reverse proxy will be bring into the connection between client and server, the proxy will be opened in future.

New architecture

The problem is the old clients connect server with pfx(private key), the reverse proxy to server connection broken, it always didn't reponse correctly, it must be something got mistake, so how to config nginx reverse proxy the https <-> https connection by taking pfx?

Our client accessing code just like

var fs    = require('fs);
var https = require('https');

// Read the private key file
var cert = fs.readFileSync('./client-certification.pfx');

var httpSettings = {
  host: options.host,
  port: options.port,
  path: options.path,
  pfx: cert,                           /* Important */
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  passphrase: "",
  rejectUnauthorized: false,
  agent: false
};

var request = https.request(httpSettings, function(res){
  var str = '';
  res.on('data',function(chunk){
    str += chunk;
  }).on("end", function() {
    try {
      var rt = JSON.parse(str);
      return callback(null, rt);
    }catch (e) {
      return callback(e, null);
    };
  });
});

Current nginx config content is, but doesn't work yet.

map $http_upgrade $connection_upgrade {
  default upgrade;
  '' close; 
}

upstream server1-upstream {
  server 8.8.8.8:443;
  keepalive 16;
}

server {
  listen 443;
  server_name server1.example.com;

  ssl_certificate     server1.example.com.crt;
  ssl_certificate_key server1.example.com.key;

  location / {
    proxy_pass https://server1-upstream;
    proxy_http_version 1.1;
    proxy_read_timeout 60m;
    proxy_pass_request_headers on;
    proxy_set_header Host $http_host;
    proxy_pass_header Date;
    proxy_pass_header Server;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Authorization $http_authorization;
    proxy_set_header Accept-Encoding "";
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Upgrade $http_upgrade;
  }
}

Does anyone have experience about the situation.

Best Answer

First of all, I'm afraid I have some news not so great. You cannot just insert an nginx in the HTTPS chain with client authenticated by their certificates.

I should mention that this pfx notice really confused me at first, but as it turns out, node.js slang is using this term for a **pkcs12* certificate.

A brief explanation on how the client certificate authentication works: during the SSL handshake, a client presents it's certificate to a server. If the client certificate CA is recognized, is trusted, it's not expired or revoked, then it's accepted. So, the client certifocate authentication is done by the SSL-enabled server (in your case- web-server), so on the scheme that describes the current setup it should be done by the nginx. The connection between nginx and backends may be done via plain HTTP from this point (if the transport is trusted, of course), because there's no point in HTTPS between both of your endpoints. For the same reason you cannot authenticate client with their certificates on backend node.js server: the nginx withh simply present them it's own certificate, and only if configured with.

So how do you assemble this chain ? The solution is rather simple: you rely entirely on the authentication sequence that happen inside nginx, and then make nginx to insert the headers, indicating whether or not it was successful. Something like that:

ssl_stapling on;
ssl_client_certificate /etc/nginx/certs/trusted.pem;
ssl_verify_client optional;
ssl_verify_depth 2;

proxy_set_header X-SSL-Verified $ssl_client_verify;
proxy_set_header X-SSL-Certificate $ssl_client_cert;
proxy_set_header X-SSL-IDN $ssl_client_i_dn;
proxy_set_header X-SSL-SDN $ssl_client_s_dn;

And on the backends you rely on these headers.