Configure Nginx to forward client certificate to backend - ssl

I have a spring boot service configured for two way ssl to verify clients using certificates. It is behind nginx proxy server. So requirements are to configure nginx to provide transparent https connection from the client and forward client certificate to the webservice(backend) to be verified. Also to configure one way ssl for other services that don't require client authentication.
Something like:
|Client| -->httpS + Client Cert--->|NGINX|--->httpS + Client Cert--->|Service 1|
|Client| ------------>httpS----------->|NGINX| ------------>http------------>|Service 2|
My nginx config:
server {
listen 443;
server_name xx.xx.xx.xxx;
ssl on;
ssl_certificate /path/to/server/cert.crt;
ssl_certificate_key /path/to/server/key.key;
ssl_client_certificate /path/to/ca.crt;
ssl_verify_client optional;
location /service1/ {
proxy_pass https://service1:80/;
#Config to forward client certificate or to forward ssl connection(handshake) to service1
}
location /service2/ {
proxy_pass http://service2:80/;
#http connection
}
}
Also, is there a way to get the common name from the certificate to verify the client and take decisions in nginx? as using the CA is not enough.
Thanks..

This is not possible. What you are attempting to do is make the nginx proxy into a "man in the middle" and this will not be allowed by TLS by design.

Related

How to fix cert-manager responses to Let's Encrypt ACME challenges when using client certificate authentication on Kubernetes with nginx ingress?

We've set-up a new ingress route that requires TLS certificate authentication, and we have placed it on its own subdomain, but we are finding that cert-manager is unable to issue a certificate for it.
Using the examples provided here, we generated the CA cert and CA key, and then configured the client certificate:
https://github.com/kubernetes/ingress-nginx/tree/master/docs/examples/auth/client-certs
In the logs, I can see that all of the .acme-challenge requests are returning a 403. I am guessing that nginx is rejecting the requests because Let's Encrypt can't present a client certificate for the challenge request. What do I need to do in order to bypass the client cert requirement for ACME?
The issue here was that we actually had misconfigured the nginx.ingress.kubernetes.io/auth-tls-secret annotation. It must be in namespace/name format -- where namespace is the namespace that contains the secret containing the client CA certificate, and name is the name of that secret -- but we were only providing the name since the secret is in the same namespace with the ingress.
I was able to diagnose the issue by dumping the nginx ingress controller config to nginx.conf.txt with:
kubectl exec <NAME OF INGRESS CONTROLLER POD> -n <INGRESS NAMESPACE> -- nginx -T | tee nginx.conf.txt
(Adapted from https://docs.nginx.com/nginx-ingress-controller/troubleshooting/#checking-the-generated-config).
This included the following snippet:
## start server the.hostname.com
server {
server_name the.hostname.com ;
listen 80;
set $proxy_upstream_name "-";
set $pass_access_scheme $scheme;
set $pass_server_port $server_port;
set $best_http_host $http_host;
set $pass_port $pass_server_port;
listen 443 ssl http2;
# PEM sha: 66c07c44ba9637a23cd3d7b6ebce958e08a52ccb
ssl_certificate /etc/ingress-controller/ssl/default-fake-certificate.pem;
ssl_certificate_key /etc/ingress-controller/ssl/default-fake-certificate.pem;
ssl_certificate_by_lua_block {
certificate.call()
}
# Location denied, reason: invalid format (namespace/name) found in 'the-secret-name'
return 403;
}
## end server the.hostname.com
The key is these two lines:
# Location denied, reason: invalid format (namespace/name) found in 'the-secret-name'
return 403;
This pointed me to the annotation for the secret name. Once I fixed that, ACME worked properly.

Cloudflare SSL cert throws a Security Issue

I have configured my nginx to use the certificate and private_key that I downloaded from cloudflare crypto.
This is my nginx.conf file-
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name autocaptions.app *.autocaptions.app;
location / {
proxy_pass http://127.0.0.1:7887;
}
ssl on;
ssl_certificate /home/ubuntu/sslcerts/autocaptions.pem;
ssl_certificate_key /home/ubuntu/sslcerts/private-key.pem;
# ssl_client_certificate /home/ubuntu/sslcerts/cloudflare.crt;
# ssl_verify_client on;
}
# Redirect http to https
server {
listen 80;
listen [::]:80;
return 301 https://$host$request_uri;
}
I am not sure what the issue is. I have added the certificate and the private_key.
I see the following error in the browser when I try to access https://autocaptions.app -
Error in text-
autocaptions.app has a security policy called HTTP Strict Transport Security (HSTS), which means that Firefox can only connect to it securely. You can’t add an exception to visit this site.
In Cloudflare configuration, I have disabled HSTS, not sure why this error is showing up.
I have followed digitalocean tutorial to configure SSL.
You are using a certificate signed by the "Cloudflare Origin CA". Certificates issued by this CA are intended to be installed on your origin server so that the communication between the Cloudflare CDN and your origin server can be protected by a certificate.
These kind of certificates are not intended on systems facing end users (i.e. browsers). They are only intended to secure the communication between your origin server and Cloudflare. Typical end users will not have the "Cloudflare Origin CA" as a trusted CA in their browser and thus they will get a TLS error when connecting to your origin server - and this is thus what you get. But typical end users should not connect to the origin server in the first place - they should connect to the Cloudflare instance instead. Only Cloudflare itself should connect to the origin server and they will acknowledge their own CA as trusted.
Check your site's SSL Setting under Crypto tab. Change it to 'Full' or 'Flexible' if its on 'Full (Strict)'.
TL;DR #SteffenUllrich is absolutely correct and should be the accepted answer.
This is your current setup.
You are gray-clouding the DNS record, essentially exposing your origin server's IP address to the whole world.
The first problem here is that, it is similar to posting your home's address on Twitter/Facebook, saying the front door is unlocked so feel free to come in and take what you want!
Cloudflare Origin CA Certificate
|
client <---------------------HTTPS-----------------> your origin (AWS)
The second problem here is that CloudFlare Origin CA Certificate is not meant to be used for client-server connection. It's purpose is to encrypt connection between Cloudflare edge and your origin only. You can think of it as a self-signed certificate. This is the reason for the error you're seeing.
One very simple solution is to replace this origin certificate with other free or paid SSL certificate such as Let's Encrypt/Certbot. If you decide to go this way, you can then skip the rest of below explanation if you want.
If you wish to keep using Cloudflare Origin CA Certificate however, keep on reading.
The next step is to proxy your connection to Cloudflare by orange-clouding the DNS record. Connection between client and Cloudflare edge will be encrypted using Cloudflare's free (shared) Universal SSL Certificate. It will partially solve the problem, but only half of the client-server connection is encrypted, because you are using Flexible mode. Connection between Cloudflare edge and your origin will not be encrypted.
Universal SSL Certificate
|
client <---HTTPS---> Cloudflare edge <----HTTP----> your origin (AWS)
The final step if to change the SSL mode from Flexible to Full or Full (Strict). Now you will get end-to-end encryption.
Universal SSL Certificate Cloudflare Origin CA Certificate
| |
client <---HTTPS---> Cloudflare edge <----HTTPS----> your origin (AWS)
Any questions?

Setup Nginx server with SSL configured

I have configured Nginx as reverse proxy and each client calls are validated using the certificates. but when I browse in the client machine I get "400 Bad Request No required SSL certificate was sent"
I enabled error log and it says "client sent no required SSL certificate while reading client request headers, client: x.x.x.x, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "y.y.y.y", referrer: "https://y.y.y.y/"
I am not able to make out what is the problem it is trying to say.
my Nginx config changes
server {
error_log "C:/Error/error.log" debug;
listen 443 ssl;
server_name localhost;
#ssl_protocols TLSv1 TLSv1.1;
ssl_certificate "C:/Test/server.crt";
ssl_certificate_key "C:/Test/server.key";
ssl_client_certificate "C:/Test/ca.crt";
ssl_verify_client on;
#ssl_session_cache off;
#proxy_ssl_server_name on;
#proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
#proxy_ssl_session_reuse off;
location / {
root html;
index index.html index.htm;
proxy_pass https://10.10.10.10/webservice;
}
Thanks,
Vinod G
Your configuration tries to authenticate a client using it's certificate and it looks like the client is not sending it.
** ssl_client_certificate** is to indicate you want to validate client certificate against the trusted CAs you're pointing to. The server would then ask the client to send a certificate and must be failing when it doesn't receive it.
A pictorial guide of the process can be read here for a better understanding:
https://comodosslstore.com/blog/what-is-ssl-tls-client-authentication-how-does-it-work.html
To debug further:
Tools like wireshark can be used to examine if client is sending a cert
https://www.linuxbabe.com/security/ssltls-handshake-process-explained-with-wireshark-screenshot
Use a tool like Postman to set the client certificate and check if the server responds as expected
https://blog.getpostman.com/2017/12/05/set-and-view-ssl-certificates-with-postman/
common issues in this area and how to resolve them
https://www.thesslstore.com/blog/tls-handshake-failed/

Client-side SSL not working with AWS API Gateway

I generated a client-side SSL Certificate on API Gateway and added it to my nginx configuration as below:
listen *:443;
ssl on;
server_name api.xxxx.com;
ssl_certificate /etc/letsencrypt/live/api.xxxx.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.xxxx.com/privkey.pem;
ssl_verify_client on;
ssl_client_certificate /etc/nginx/ssl/awsapigateway.crt;
location /home/ubuntu/api {
# if ($ssl_client_verify != SUCCESS) { return 403; }
# proxy_pass http://my.http.public.endpoint.com;
# proxy_set_header X-Client-Verify $ssl_client_verify;
}
The client certificate doesn't work after testing via the AWS API gateway test console. It ends up with Error 400 - No required SSL certificate was sent. API Gateway should be sending its client cert to my server with each request, so that I can validate that requests are genuinely coming from API Gateway.
I believe the reason it is not working is I am adding the PEM-encoded public key from the AWS API gateway console directly to awsapigateway.crt. Is that correct?
Additionally, does nginx support self-signed client SSL certificates, which is what AWS is providing us?
Api Gateway team here.
It looks like the nginx configuration is correct. And for our simple test case we use a node server and simply write the PEM certificate from the console directly to the crt file that is set as the ca, or in this case the ssl_client_certificate.
I'd also test using the actual deployed API if for some reason the test function in the console has an issue. Make sure to use the Stage settings to specify the cert.

How to pass a client certificate through two server nginx?

I have some reason to use two nginx servers before the application server.
Both nginx servers using an SSL connection.
Nginx1 (SSL 443 and ssl_verify_client on) -> Nginx2 (SSL 443) -> App (9000).
On the first Nginx1 server I use the option: proxy_set_header client_cert $ssl_client_cert;
On the second server Nginx2 I use the option: underscores_in_headers on;
The problem is that the second Nginx2 server is sent only the first line of the certificate - "----- BEGIN CERTIFICATE -----".
How to pass a client certificate to the application server?
Nginx terminates SSL with no exception, so if you want this config anyway - you will need to have SSL config again and keep certificates on the server (here is relevant SO answer) or based on Nginx support discussion to use HAProxy in TCP mode. Here is the sample configuration article.
I found a Workaround for proxy client certificate
# NGINX1
...
map $ssl_client_raw_cert $a {
"~^(-.*-\n)(?<1st>[^\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)?((?<ab>[^\n]+)\n)?((?<ac>[^\n]+)\n)?((?<ad>[^\n]+)\n)?((?<ae>[^\n]+)\n)?((?<af>[^\n]+)\n)?((?<ag>[^\n]+)\n)?((?<ah>[^\n]+)\n)?((?<ai>[^\n]+)\n)?((?<aj>[^\n]+)\n)?((?<ak>[^\n]+)\n)?((?<al>[^\n]+)\n)?((?<am>[^\n]+)\n)?((?<an>[^\n]+)\n)?((?<ao>[^\n]+)\n)?((?<ap>[^\n]+)\n)?((?<aq>[^\n]+)\n)?((?<ar>[^\n]+)\n)?((?<as>[^\n]+)\n)?((?<at>[^\n]+)\n)?((?<av>[^\n]+)\n)?((?<au>[^\n]+)\n)?((?<aw>[^\n]+)\n)?((?<ax>[^\n]+)\n)?((?<ay>[^\n]+)\n)?((?<az>[^\n]+)\n)*(-.*-)$"
$1st;
}
server {
...
location / {
...
proxy_set_header client_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$ab$ac$ad$ae$af$ag$ah$ai$aj$ak$al$am$an$ao$ap$aq$ar$as$at$av$au$aw$ax$ay$az;
...
}
...
}
# NGINX 2
server {
...
underscores_in_headers on;
...
location / {
proxy_pass_request_headers on;
proxy_pass http://app:9000/;
}
...
}