Nginx reverse proxy pass through client certificate
I'm trying to set up a load balancer via an Nginx reverse proxy. My application uses client certificates to authenticate clients. I would like my reverse proxy to forward the client certificate to my back-end servers. I have added this line to my reverse proxy's configuration to store the client certificate information in a custom HTTP header:
proxy_set_header X-SSL-CERT $ssl_client_cert
However, $ssl_client_cert
uses multiple lines to store the certificate, and my back-end nginx server does not recognize this properly as one HTTP header. What is the best way to achieve forward my client certificates?
This question has been asked in 2013 on this forum, but so far no real solution: https://forum.nginx.org/read.php?2,236546,236546
Thanks!
So I have found a solution. I remove all newlines in the certificate and send them as a single HTTP Header from the proxy to the back-end, similar as explained here:
https://forum.nginx.org/read.php?2,236546,236546
In my back-end I reconstruct the certificate by adding a newline every 64 characters. The updated code for the reverse proxy is the following and works up to 26 lines:
map $ssl_client_raw_cert $a {
"~^(-.*-\n)(?<st>[^\n]+)\n((?<b>[^\n]+)\n)?((?<c>[^\n]+)\n)?((?<d>[^\n]+)\n)?((?<e>[^\n]+)\n)?((?<f>[^\n]+)\n)?((?<g>[^\n]+)\n)?((?<h>[^\n]+)\n)?((?<i>[^\n]+)\n)?((?<j>[^\n]+)\n)?((?<k>[^\n]+)\n)?((?<l>[^\n]+)\n)?((?<m>[^\n]+)\n)?((?<n>[^\n]+)\n)?((?<o>[^\n]+)\n)?((?<p>[^\n]+)\n)?((?<q>[^\n]+)\n)?((?<r>[^\n]+)\n)?((?<s>[^\n]+)\n)?((?<t>[^\n]+)\n)?((?<v>[^\n]+)\n)?((?<u>[^\n]+)\n)?((?<w>[^\n]+)\n)?((?<x>[^\n]+)\n)?((?<y>[^\n]+)\n)?((?<z>[^\n]+)\n)?(-.*-)$" $st;
}
server {
location / {
proxy_set_header X-cert $a$b$c$d$e$f$g$h$i$j$k$l$m$n$o$p$q$r$s$t$v$u$w$x$y$z;
proxy_pass http://localhost:8000;
}
}
(note that I have removed the variable starting with a number) While this solution is not ideal, it works for me at this moment. Another solution would be to only send the certificate's DN info, which is a single line. This does not work for me at this time, as I have not stored every DN in my database.
It looks like newer versions of nginx change all \n
to \t
for you in the variable $ssl_client_cert
. The variable $ssl_client_raw_cert
is available if you don't want that transformation applied.
See: http://nginx.org/en/docs/http/ngx_http_ssl_module.html#variables
I see 2 solutions here:
- Enable LUA scripting and reformat the string in a no-end-of-line format.
- Use the Set Misc plugin and use base64_encode on that variable.
Here is the LUA solution that worked for me:
set_by_lua $client_cert "return ngx.var.ssl_client_raw_cert and ngx.var.ssl_client_raw_cert:gsub('\\n',' ') or nil";
proxy_set_header X-SSL-CERT $client_cert;
Also, if you use Ubuntu, nginx-extra has LUA plugin by default so there's no need for additional configuration: https://stackoverflow.com/questions/22193852/running-lua-in-nginx-config