Frontend TLS v1.3 to multiple backends TLS v1.0

Hello all,

I’m an old Unix admin with no experience in web proxying, and I need to configure haproxy for this intended setup:

I tried to configure haproxy using tips from this other topic:

Here is my haproxy.cfg so far:

global
    log         127.0.0.1  local2
    chroot      /var/lib/haproxy
    pidfile     /var/run/haproxy.pid
    maxconn     4000
    user        haproxy
    group       haproxy
    daemon
    stats socket /var/lib/haproxy/stats
    ssl-default-bind-ciphers PROFILE=SYSTEM
    ssl-default-server-ciphers PROFILE=SYSTEM
    tune.ssl.default-dh-param 2048

defaults
    log      global
    maxconn  3000
    retries  2

    option   redispatch
    option   dontlognull
    option   forwardfor  except 127.0.0.0/8
    timeout  queue              1m
    timeout  check              5s
    timeout  client             1m
    timeout  server             1m
    timeout  connect            5s
    timeout  http-request       5s
    timeout  http-keep-alive    5s



frontend http_front
    mode   http
    option httplog
    option http-server-close
    bind  10.10.10.10:443  ssl  crt  /etc/haproxy/certs/certificate.pem  force-tlsv13
    default_backend backend_oldblackbox1

backend backend_oldblackbox1
    mode     http
    balance  roundrobin
    server   server_oldblackbox1  10.10.10.11 ssl verify none

backend backend_oldblackbox2
    mode     http
    balance  roundrobin
    server   server_oldblackbox2  10.10.10.12 ssl verify none

From the Windows box, when I try to connect to the linux box either by its DNS name or it’s IP address, I get the default backend’s web page OK (which is oldblackbox1 in my current config)

But if i try to connect with oldblackbox1’s IP address or DNS name, I get this error message in the logs: http_front/1: SSL handshake failure

I know that I have to configure ACLs to route traffic to the right ‘oldblackbox’ in the backend, but I can’t figure out how to do this in my context. I read the haproxy documentation for a many hours until now, and I miss the web proxy concepts and literacy to understand all the implications of the config parameters (and there are a lot).

Any help would be very much apreciated.

It’s possible your backend servers require you to send a SNI value (hostname) in the SSL hello, and reject the handshake otherwise.

To send a specific SNI value, you can add something like this to the server line sni str(oldblackbox1.mydomain.com) :

server server_oldblackbox1  10.10.10.11 ssl verify none sni str(oldblackbox1.mydomain.com)

However more likely your haproxy has a recent operating system, probably with openssl version 3 which raises tls and cipher configuration defaults significantly causing issues with older version and ciphers.

Try adding to your server line:

ssl-min-ver TLSv1.0 ciphers AES128-SHA@SECLEVEL=0

Modifying the SECLEVEL to 0 in /etc/ssl/openssl.cnf or if in haproxy >= 3.0 use the ssl-security-level directive to drop the level to 0.

@lukastribus , thank you so much for your help.

My Linux server is a RHEL 8.9, with haproxy 1.8.27

As for the openssl version, here is the info I have:

# rpm -qa | grep -i openssl
openssl-pkcs11-0.4.10-3.el8.x86_64
openssl-libs-1.1.1k-12.el8_9.x86_64
openssl-libs-1.1.1k-12.el8_9.i686
openssl-1.1.1k-12.el8_9.x86_64
python3-pyOpenSSL-19.0.0-1.el8.noarch
openssl-pkcs11-0.4.10-3.el8.i686
xmlsec1-openssl-1.2.25-4.el8.x86_64

I edited my haproxy.cnf as you suggested, here are the changes:

    server  server_oldblackbox1  10.10.10.11  ssl verify none  sni str(oldblackbox1.mydomain.com) ssl-min-ver TLSv1.0 ciphers AES128-SHA@SECLEVEL=0

    server  server_oldblackbox2  10.10.10.12  ssl verify none  sni str(oldblackbox2.mydomain.com) ssl-min-ver TLSv1.0 ciphers AES128-SHA@SECLEVEL=0

The only openssl.cnf file present on the RHEL 8 server is in /etc/pki/tls/openssl.cnf
I added this line at the beginning of the file:
CipherString = DEFAULT@SECLEVEL=0

This file has 2 ‘.include’ directives, so I modified the 2 included files also:

/etc/crypto-policies/back-ends/openssl.config:
@SECLEVEL=0:kEECDH:kRSA:kEDH:kPSK:kDHEPSK:kECDHEPSK:-aDSS:-3DES:!DES:!RC4:!RC2:!IDEA:-SEED:!eNULL:!aNULL:!MD5:-SHA384:-CAMELLIA:-ARIA:-AESCCM8

/etc/crypto-policies/back-ends/opensslcnf.config:

CipherString = @SECLEVEL=0:kEECDH:kRSA:kEDH:kPSK:kDHEPSK:kECDHEPSK:-aDSS:-3DES:!DES:!RC4:!RC2:!IDEA:-SEED:!eNULL:!aNULL:!MD5:-SHA384:-CAMELLIA:-ARIA:-AESCCM8
Ciphersuites = TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_SHA256
TLS.MinProtocol = TLSv1.2
TLS.MaxProtocol = TLSv1.3
DTLS.MinProtocol = DTLSv1.2
DTLS.MaxProtocol = DTLSv1.2
SignatureAlgorithms = ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA512:ed25519:ed448:rsa_pss_pss_sha256:rsa_pss_rsae_sha256:rsa_pss_pss_sha384:rsa_pss_rsae_sha384:rsa_pss_pss_sha512:rsa_pss_rsae_sha512:RSA+SHA256:RSA+SHA384:RSA+SHA512:ECDSA+SHA224:RSA+SHA224:ECDSA+SHA1:RSA+SHA1

Both files had @SECLEVEL=2 before I changed it to 0.

Still, after stop-start of haproxy, I have the exact same behaviour as before:
https://linuxserver.mydomain.com → WORKS OK
https://oldblackbox1.mydomain.com → ‘ERR_EMPTY_RESPONSE’ in the Chrome browser, and this error message in haproxy.log:

<150>2024-12-04T15:45:45-05:00 localhost haproxy[549796]: 10.10.10.9:52350 [04/Dec/2024:15:45:45.891] http_front/1: SSL handshake failure

The fact that the message originates from the frontend ‘http_front’ confuses me, this suggests to me that the TLS handshake failure is between the browser and the frontend… Am I wrong ?

Thanks again for the help.

You are right this is most likely not about the backend.

Taking a look at your diagram I can see that you are pointing to haproxy by wpad config, but really you are mixing forward proxy concepts with reverse proxy concepts. The wpad configuration indicates to the browser that you are talking to is a forward proxy, while haproxy is a reverse proxy.

You should drop the wdap configuration in its entirety, and instead make sure DNS records of oldblackbox1 and 2 point to 10.10.10.10 without any WPAD involved.

With a forward proxy configuration, and a proxy server that supports forward proxying, you do not terminate TLSv1.0 on haproxy, but you ware passing TLSv1.0 in a HTTP CONNECT through a TLSv1.3 tunnel to the browser, which I do not think is what you want.

You want haproxy to terminate TLSv1.3 on haproxy so that the browser is unaware that the backend only speaks TLSv1.0 is my assumption.

The reason the ssl handshake fails is because you indicate in the WPAD file that the browser should connect to 10.10.10.10 port 443 in plaintext without SSL, which is why the browser will send a clear text HTTP CONNECT request to haproxy on port 443. But fixing this by replacing PROXY with HTTPS and 10.10.10.10 with linuxbox.mydomain.com does not fix the problem because the configuration is still fundamentally wrong; you need to point the DNS records to haproxy instead.

Thant you for the clarification @lukastribus

I forgot to mention in my original post that we need haproxy to be a ‘man in the middle’ because the Chrome browser doesn’t accept TLS V1.0 anymore, and the users on the Windows box need access to those ‘olbblackboxes’ until we replace them, and that will not be done soon.

The reason we setup WPAD is because our users are engineers, and they can point the browser to the ‘oldblackboxes’ with either their DNS names or IP addresses.

I did as you suggested: Drop the WPAD config, and use the DNS to redirect the oldblackboxes entries to linuxserver. I’ll tell the engineers not to use the IP addresses and I’ll put shortcuts on their Windows desktops to point to the oldblackboxes with the DNS names.

One last thing: I read the doc about ACLs, and I added these lines in the frontend section to redirect the requests the right backend:

   use_backend backend_oldblackbox1 if { req.hdr(host) -i oldblackbox1.mydomain.com }
   use_backend backend_oldblackbox2 if { req.hdr(host) -i oldblackbox2.mydomain.com }

Everything works now, case closed, thank you again @lukastribus !

1 Like