Ssl-min-ver in crt-list used to work with haproxy 2.2, does not seem to work with haproxy 2.6

I have a crt-list.txt which looks like so:

old.example.com.pem [alpn h2,http/1.1,http/1.0 ssl-min-ver TLSv1.0]
modern.example.com.pem [alpn h2,http/1.1,http/1.0 ssl-min-ver TLSv1.2]

And it’s used in haproxy.cfg like so:

ssl-default-bind-options ssl-min-ver TLSv1.0 no-tls-tickets

frontend www
    bind *:443 ssl crt-list /etc/haproxy/crt-list.txt
    bind :::443 ssl crt-list /etc/haproxy/crt-list.txt

The idea is that both domains get served from the same port, one supports TLSv1.0 and up, and the other only supports TLSv1.2 and up.

This used to work with haproxy 2.2, but I cannot get it to work with haproxy 2.6 or 2.7. It appears as if ssl-min-ver in crt-list.txt gets ignored, and both domains use the min version I specify in ssl-default-bind-options (or the default TLSv1.2 if I remove ssl-default-bind-options).

I’m probably missing something, but not sure what… Any tips?

haproxy -vv

haproxy -vv
HAProxy version 2.6.9-1ppa1~focal 2023/02/16 - https://haproxy.org/
Status: long-term supported branch - will stop receiving fixes around Q2 2027.
Known bugs: http://www.haproxy.org/bugs/bugs-2.6.9.html
Running on: Linux 5.4.0-139-generic #156-Ubuntu SMP Fri Jan 20 17:27:18 UTC 2023 x86_64
Build options :
  TARGET  = linux-glibc
  CPU     = generic
  CC      = cc
  CFLAGS  = -O2 -g -O2 -fdebug-prefix-map=/build/haproxy-N4tzqY/haproxy-2.6.9=. -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -Wall -Wextra -Wundef -Wdeclaration-after-statement -Wfatal-errors -Wtype-limits -Wshift-negative-value -Wshift-overflow=2 -Wduplicated-cond -Wnull-dereference -fwrapv -Wno-address-of-packed-member -Wno-unused-label -Wno-sign-compare -Wno-unused-parameter -Wno-clobbered -Wno-missing-field-initializers -Wno-cast-function-type -Wno-string-plus-int -Wno-atomic-alignment
  OPTIONS = USE_PCRE2=1 USE_PCRE2_JIT=1 USE_OPENSSL=1 USE_LUA=1 USE_SLZ=1 USE_SYSTEMD=1 USE_PROMEX=1
  DEBUG   = -DDEBUG_STRICT -DDEBUG_MEMORY_POOLS

Feature list : -51DEGREES +ACCEPT4 +BACKTRACE -CLOSEFROM +CPU_AFFINITY +CRYPT_H -DEVICEATLAS +DL -ENGINE +EPOLL -EVPORTS +GETADDRINFO -KQUEUE +LIBCRYPT +LINUX_SPLICE +LINUX_TPROXY +LUA -MEMORY_PROFILING +NETFILTER +NS -OBSOLETE_LINKER +OPENSSL -OT -PCRE +PCRE2 +PCRE2_JIT -PCRE_JIT +POLL +PRCTL -PROCCTL +PROMEX -QUIC +RT +SLZ -STATIC_PCRE -STATIC_PCRE2 +SYSTEMD +TFO +THREAD +THREAD_DUMP +TPROXY -WURFL -ZLIB

Default settings :
  bufsize = 16384, maxrewrite = 1024, maxpollevents = 200

Built with multi-threading support (MAX_THREADS=64, default=1).
Built with OpenSSL version : OpenSSL 1.1.1f  31 Mar 2020
Running on OpenSSL version : OpenSSL 1.1.1f  31 Mar 2020
OpenSSL library supports TLS extensions : yes
OpenSSL library supports SNI : yes
OpenSSL library supports : TLSv1.0 TLSv1.1 TLSv1.2 TLSv1.3
Built with Lua version : Lua 5.3.3
Built with the Prometheus exporter as a service
Built with network namespace support.
Support for malloc_trim() is enabled.
Built with libslz for stateless compression.
Compression algorithms supported : identity("identity"), deflate("deflate"), raw-deflate("deflate"), gzip("gzip")
Built with transparent proxy support using: IP_TRANSPARENT IPV6_TRANSPARENT IP_FREEBIND
Built with PCRE2 version : 10.34 2019-11-21
PCRE2 library supports JIT : yes
Encrypted password support via crypt(3): yes
Built with gcc compiler version 9.4.0

Available polling systems :
      epoll : pref=300,  test result OK
       poll : pref=200,  test result OK
     select : pref=150,  test result OK
Total: 3 (3 usable), will use epoll.

Available multiplexer protocols :
(protocols marked as <default> cannot be specified using 'proto' keyword)
         h2 : mode=HTTP  side=FE|BE  mux=H2    flags=HTX|HOL_RISK|NO_UPG
       fcgi : mode=HTTP  side=BE     mux=FCGI  flags=HTX|HOL_RISK|NO_UPG
  <default> : mode=HTTP  side=FE|BE  mux=H1    flags=HTX
         h1 : mode=HTTP  side=FE|BE  mux=H1    flags=HTX|NO_UPG
  <default> : mode=TCP   side=FE|BE  mux=PASS  flags=
       none : mode=TCP   side=FE|BE  mux=PASS  flags=NO_UPG

Available services : prometheus-exporter
Available filters :
	[CACHE] cache
	[COMP] compression
	[FCGI] fcgi-app
	[SPOE] spoe
	[TRACE] trace

I test TLSv1.0 & TLSv1.2 support from a Ubuntu Xenial VM using curl:

curl --tlsv1.0 -vvv  https://old.example.org
curl --tlsv1.2 -vvv  https://old.example.org

I experimented with multiple crt directives as an alternative to crt-list:

ssl-default-bind-options ssl-min-ver TLSv1.0 no-tls-tickets

frontend www
    bind *:443 ssl crt old.example.com.pem ssl-min-ver TLSv1.0 crt modern.example.com.pem ssl-min-ver TLSv1.2
    bind :::443 ssl crt old.example.com.pem ssl-min-ver TLSv1.0 crt modern.example.com.pem ssl-min-ver TLSv1.2

Haproxy seemed to use the same ssl-min-ver for both both domains. In the above example, both domains worked with TLSv1.2+ only.

If I switched the crt directives around, both domains worked with TLSv1.0+:

ssl-default-bind-options ssl-min-ver TLSv1.0 no-tls-tickets

frontend www
    bind *:443 ssl crt modern.example.com.pem ssl-min-ver TLSv1.2 crt old.example.com.pem ssl-min-ver TLSv1.0 
    bind :::443 ssl crt modern.example.com.pem ssl-min-ver TLSv1.2 crt old.example.com.pem ssl-min-ver TLSv1.0

After some more experimenting, it seems like ssl-min-ver in crt-list is simply ignored.

If ssl-min-ver is defined on the bind line, haproxy uses that.

Otherwise, if ssl-min-ver is defined in ssl-default-bind-options, haproxy uses that.

Otherwise, it uses the default TLSv1.2 value.

Haproxy does parse and read ssl-min-ver from the crt-list file. If I specify a bad value in the crt-list file, haproxy does complain about it:

[ALERT]    (78775) : config : parsing [/etc/haproxy/haproxy.cfg:38] : 'bind *:443' in section 'frontend' : 'crt-list' : 'TLSv1.123' : unknown ssl/tls version

But for some reason it does not use it…

Are the names on dedicated non overlapping certificates? Do other option for the specific domain name work that are configured in crt-list?

The question is, does that particular line in crt-list actually match, or does it actually not match at all.

The CN values for the certificates do not overlap.

I did an experiment with different ALPN values:

old.example.com.pem [alpn h2,http/1.1,http/1.0 ssl-min-ver TLSv1.0]
modern.example.com.pem [alpn h2 ssl-min-ver TLSv1.2]    

I get different output in curl when requesting them:

curl --tlsv1.2 -vvv  https://old.example.com

* ALPN, offering http/1.1
[...]
* ALPN, server accepted to use http/1.1

and

curl --tlsv1.2 -vvv  https://modern.example.com

* ALPN, offering http/1.1
[...]
* ALPN, server did not agree to a protocol

So it seems the per-domain alpn directive did have an effect.

One more note: I’m using both ECDSA and RSA certificates. In the certificates directory I have:

  • old.example.com.pem.rsa
  • old.example.com.pem.ecdsa
  • modern.example.com.pem.rsa
  • modern.example.com.pem.ecdsa

Ok, I think at this point there is enough information to file a bug report on Github, please make sure you mention all the informations from the last post too:

OK, filed a bug report: Ssl-min-ver in crt-list used to work with haproxy 2.2, does not seem to work with haproxy 2.6 · Issue #2069 · haproxy/haproxy · GitHub

1 Like