This is an unusual requirement which is far from the correct way to do this, however, this is a system which I’ve just taken on and we need to get this working in this way until we can do it better next year.
There’s an API endpoint, secured with TLS, but also secured by the presentation of a client certificate. The unusual part here is the certificate must have a given string for the organisation name and common name. There is no other purpose for this certificate; not for encryption or any other purpose.
The frontend bind line has verify none as we don’t (apparently) care where the cert has come from.
bind *:443 ssl crt-list /etc/haproxy/certs/certlist.txt ca-file /etc/haproxy/certs/internal/RootAuthority.pem verify none
Just trying to log either the Common Name or Organisation Name doesn’t work using this line in the frontend: (Edit: it just logs “”)
log-format "%ci %{+Q}[ssl_c_s_dn(c)]"
I’ve looked at Lua to try and get this information, but I’ve no experience with Lua and feel this should be possible through the existing configuration language.
If anyone can get both of these certificate fields into variables that would really help.
verify none
means the client certificate is not even requested, which is not possible if you want to access client certificate data.
You need verify optional
so that the client is actually able to use its client certificate. Then you should be able to access those fields.
Doing that results in this entry in the log: SSL client certificate not trusted
How would you overcome the untrusted nature of the certificate to be able to read the certificate fields?
You will need ca-ignore-err all
on the bind line. Or crt-ignore-err all
, not sure, one of the two.
That’s cleared the SSL trust problem, however I’m still not able to pickup the certificate information. Additional fields have been added to the log entry in case there was some anomaly reading the subject line.
log-format "%ci CERTIFICATE INFORMATION: %{+Q}[ssl_c_s_dn] / %{+Q}[ssl_c_notbefore] / %{+Q}[ssl_c_notafter] / %{+Q}[ssl_c_serial]"
This results in the log file entry:
Dec 13 09:45:48 localhost haproxy[255615]: 192.168.1.1 CERTIFICATE INFORMATION: "" / "" / "" / ""
Any thoughts why the certificate information isn’t being picked up?
If you want additional help you need to post the entire configuration and the haproxy version.
HAProxy version 2.8.4-a4ebf9d, released 2023/11/17
#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
log 127.0.0.1:514 local2
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 4000
user haproxy
group haproxy
daemon
crt-base /etc/haproxy/certs
# turn on stats unix socket
stats socket /var/lib/haproxy/stats
# utilize system-wide crypto-policies
ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
ssl-default-bind-ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256
#---------------------------------------------------------------------
# common defaults that all the 'listen' and 'backend' sections will
# use if not designated in their block
#---------------------------------------------------------------------
defaults
mode http
log global
option httplog
option dontlognull
option http-server-close
option forwardfor except 127.0.0.0/8
option redispatch
retries 3
timeout http-request 10s
timeout queue 1m
timeout connect 10s
timeout client 1m
timeout server 1m
timeout http-keep-alive 10s
timeout check 10s
maxconn 3000
#---------------------------------------------------------------------
# Frontend all-port-80
#---------------------------------------------------------------------
frontend all-port-80
bind *:80
option http-server-close
http-request redirect scheme https code 301
#---------------------------------------------------------------------
# Frontend all-ip-443
#---------------------------------------------------------------------
frontend all-ip-443
bind *:443 ssl crt-list /etc/haproxy/certs/certlist.txt ca-file /etc/haproxy/certs/internal/RootAuthority.pem verify none ca-ignore-err all crt-ignore-err all
option http-keep-alive
# Log Client Certificate Inormation
log-format "%ci CERTIFICATE INFORMATION: %{+Q}[ssl_c_s_dn] / %{+Q}[ssl_c_notbefore] / %{+Q}[ssl_c_notafter] / %{+Q}[ssl_c_serial]"
# Host based ACLs
acl acl_haproxyhost hdr(host) -i haproxyhost.dif.domain.com
acl acl_password.domain.com hdr(host) -i password.domain.com
acl acl_uat-im1.ct1.domain.com hdr(host) -i uat-im1.ct1.domain.com
acl acl_uat-im2.ct1.domain.com hdr(host) -i uat-im2.ct1.domain.com
acl acl_uat-api.ct1.domain.com hdr(host) -i uat-api.ct1.domain.com
# Backend Selection
use_backend haproxyhost if acl_haproxyhost
use_backend password.domain.com if acl_password.domain.com
use_backend uat-im1.ct1.domain.com if acl_uat-im1.ct1.domain.com
use_backend uat-im2.ct1.domain.com if acl_uat-im2.ct1.domain.com
use_backend uat-api.ct1.domain.com if acl_uat-api.ct1.domain.com
#---------------------------------------------------------------------
# Backend haproxyhost
#---------------------------------------------------------------------
backend haproxyhost
server haproxyhost 127.0.0.1:81
#---------------------------------------------------------------------
# Backend password.domain.com
#---------------------------------------------------------------------
backend password.domain.com
option httpchk
http-check send hdr host pwdserver.dif.domain.com
http-check expect status 400
http-request set-header host pwdserver.dif.domain.com
server pwdserver.dif 172.20.32.16:443 check ssl verify none
#---------------------------------------------------------------------
# Backend uat-im1.ct1.domain.com
#---------------------------------------------------------------------
backend uat-im1.ct1.domain.com
option httpchk
http-check send hdr host uat-im1.ct1.domain.com
http-request set-header Host uat-im1.ct1.domain.com
server ct1-web-server.ct1 172.22.43.1:443 check ssl verify none
#---------------------------------------------------------------------
# Backend uat-im2.ct1.domain.com
#---------------------------------------------------------------------
backend uat-im2.ct1.domain.com
option httpchk
http-check send hdr host uat-im2.ct1.domain.com
http-request set-header Host uat-im2.ct1.domain.com
server ct1-web-server.ct1 172.22.43.1:443 check ssl verify none
#---------------------------------------------------------------------
# Backend uat-api.ct1.domain.com
#---------------------------------------------------------------------
backend uat-api.ct1.domain.com
option httpchk GET /swagger
http-check send hdr host im2_uat-webapi.local
http-request set-header Host im2_uat-webapi.local
server ct1.api-server.ct1 172.22.43.32:80 check
#---------------------------------------------------------------------
# Stastics Page Config
#---------------------------------------------------------------------
listen stats
bind *:81 # Bind stats to port 81
log global # Enable Logging
stats enable # enable statistics reports
# stats hide-version # Hide the version of HAProxy
stats refresh 30s # HAProxy refresh time
stats show-node # Shows the hostname of the node
stats uri /stats # Statistics URL
Like I said, verify needs to be optional instead of none.
Your configuration works, but you cannot refer to ssl_c_serial
, this returns a binary, not a string, so you need to add a conversation to string (adding ,hex
):
log-format "%ci CERTIFICATE INFORMATION: %{+Q}[ssl_c_s_dn] / %{+Q}[ssl_c_notbefore] / %{+Q}[ssl_c_notafter] / %{+Q}[ssl_c_serial,hex]"
Logging looks like:
127.0.0.1 CERTIFICATE INFORMATION: "/CN=ubuntuvm.local/O=LT-Org SpA/C=IT/ST=MI/L=City" / "150102161510Z" / "160102161510Z" / "97B6A242E673100B"
Apologies, you did say to make verify optional.
It is now partially working, if certificate A is sent then the log outputs the certificate information:
Dec 13 16:15:47 localhost haproxy[267501]: 10.149.208.254 CERTIFICATE INFORMATION: "/O=Saturday Sports/CN=Dicky Davis" / "241211095145Z" / "251211101145Z"
Any other certificate just has blank values again for the certificate fields.
Several certificates have been created and none of them are logged, except the one above. The certificates have been created in the same way and I’m at a loss to understand if there’s a problem with the certificate or HAProxy.
The certificates have been created using the below ini file and certreq.exe on Windows :
[Version]
Signature = "$Windows NT$"
[NewRequest]
Subject = "CN=Bob Wilson,O=Saturday Sports"
KeySpec = 1
KeyLength = 2048
HashAlgorithm = sha256
Exportable = TRUE
KeyUsage = 0xa0
MachineKeySet = FALSE
ProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider"
RequestType = Cert
[Extensions]
2.5.29.19 = "{text}"
Added %{+Q}[ssl_c_used]
to the log-format line and it returns “1” with Certificate A and “0” for all other certificates. It looks like for some reason the other certificates are not being sent with the Invoke-WebRequest PS comandlet.
To try and come at this form another angle, another certificate was created using openssl but this hasn’t worked.
openssl req -x509 -newkey rsa:2048 -keyout hugo_raddon.key -out hugo_raddon.crt -days 365 -nodes -subj "/CN=Hugo Raddon/O=Some Company" -addext "keyUsage=digitalSignature" -addext "extendedKeyUsage=clientAuth"
openssl pkcs12 -export -out hugo_raddon.pfx -inkey hugo_raddon.key -in hugo_raddon.crt -certfile hugo_raddon.crt -passout pass:yourpassword
OpenSSL has also been used to dump the details of the working and non-working certificates to text files so a diff could be done. Aside from the expected differences nothing stood out or would explain why the non-working certificate was different.
I’m thinking this is more of a certificate/request issue rather than HAProxy as this stage but haven’t enough evidence to rule anything out just yet.
You can try to enforce client certificates by turning verify to required
instead of optional
, this may make client cert issues more clear.
The only question I can ask is whether both working and non working certificates are issued from the same exact private CA referenced in /etc/haproxy/certs/internal/RootAuthority.pem
, but haproxy should not verify them either way.
It comes as no surprise that Windows was doing something screwy with creating the certificates. Ended up recreating some certificate on Linux and used curl to send them to HAProxy. Everything worked as expected.
Many thanks for your help with this.
1 Like