I use haproxy in a SSL termination config, where depending on the URL the traffic is directed to different backends.
I auto generate a SSL certificate using Let’s Encrypt. Clients are just Web browsers and I currently authenticate using usernames and passwords for each backend. I can either enable or disable the authentication. I cannot modify the backends to accept client certificates.
I would like to use client certificates for authentication on the front end and therefore remove the need for username and passwords on the backend. According to this https://arcweb.co/securing-websites-nginx-and-client-side-certificate-authentication-linux/ for nginx some additional lines need to be added to enable client authentication, and once authenticated, the rest of the traffic is encrypted.
How can I achieve the same thing with haproxy?
I’m aware that in some instances certificates can be combined (eg TLS with Client Authentication) but I’m not sure if this is required for haproxy nor how to do it.
On the front end I have the following line related to ssl:
A quick follow up I have is that I use ACLs based on path_beg to determine which backend to direct them to. How do check if a client certificate is valid and path_beg is met?
In your example you have the opposite, all paths allowed except secure. I want to (for the sake of simplicity) state each path individually. For example I have the following:
acl url_gui path_beg /gui
use_backend www-sync if url_gui
acl url_sick path_beg /sick
use_backend www-sick if url_sick
I want to modify it so that backend www-sick is only used of the path begins with /sick and client certificate is valid.
What I found in my testing is that when using “verify optional” to allow both those with and those without the client cert access I had to verify two things to be sure it was valid.
Was the client cert used? ssl_c_used 1
Did it verify? ssl_c_verify 0
I couldn’t just use “Did it verify” as I found “ssl_c_verify” returned “0” even when a client certificate wasn’t present… However, if it was used so “ssl_c_used” “1” then it will have been checked against the CA so you can now trust “ssl_c_verify”.
There may be a better way to do this that I missed but something like this should do it:
Sorry, I haven’t actually tested it but AND is implicit so that should result in, send to backend “www-sick” if “url_sick” and “cert_present” and “cert_verified”.
I’ve been trying this out and I suspect the overall configuration is correct (with regards path etc), however it’s not working and I wonder if it’s something trivial I’m doing.
I left the hostname-dh.pem from above alone. For the ca.crt I concatenated the root, intermediate and device certificates (and called it bundle.crt), is that correct?
The CA file, “ca.crt” in your example should only be the root and intermediate certs I think… No need to include the device cert as this will have already been signed by the CA or any intermediate CA.
Can you see the output of ssl_c_verify in your testing? The number presented in this fetch represents the status code of the client SSL handshake so acts as a good troubleshooting method.
Common codes I ran into in my testing are:
10 = Client Cert Expired.
23 = Client Cert Revoked
21 = Cert not authenticated, not correctly signed by this CA.
26 = Wrong Cert usage, invalid purpose.
Thanks guys. It’s working now. Here is what I think went wrong:
1st attempt, I didn’t follow the instructions carefully when generating the device certificate from the guide I was following, specifically the line regarding server_cert instead of usr_cert. I also tried changing the ca-file from having just root and intermediate certs to root, intermediate and device.
2nd attempt I followed the instructions carefully but was tying to be too clever by using ECDSA to generate the private keys. As I was testing at the time with my Android (7.1.2) phone, I couldn’t get the pkcs12 to install.
3rd attempt, I followed the cert instructions and used RSA. I’m not sure if it made a difference but I didn’t put a password on the device private key (which might be removed during pfx export?), but otherwise did the same thing as above and it all works on my phone and laptop.