Ssl_fc_sni redirect changed from 2.2.5 to 2.2.6

Hello,

up to now I’d configured

http-request redirect location https://www.example.com/ code 301 \
    if { ssl_fc_sni -i example.com }

Since updating to 2.2.6 this produces an endless redirect loop because it matches also for host www.example.com?!

I’ve modified this now to

http-request redirect location https://www.example.com/ code 301 \
    if { ssl_fc_sni -i example.com } { hdr(host) -i example.com }

My cert is for www.example.com with an alias for example.com.

I’m using haproxy since version 2.2.3 only, so can you explain me if this is a bug introduced in 2.2.6 or why I need to check for host header in addition to SNI now?

Thanks

If this behavior changes between 2.2.5 and to 2.2.6, then it is indeed a bug. I will check this later.

However the configuration is wrong nonetheless: if your certificate has SAN’s for both example.org and www.example.org, or a wildcard *.example.org and example.org on the apex, this configuration will never work reliably.

What you need to do is check only the host header.

http-request redirect location https://www.example.com/ code 301 \
if { hdr(host) -i example.com }

Otherwise the browser can reuse the SSL session and you will make wrong assumptions because instead of looking what HTTP request actually is, you are looking at the SNI value which not only doesn’t matter, it will also deceive you in many cases (like when the certificate covers both and the browser reuses the SSL session).

When you are using host header matching only, than you are not affected by the bug anymore.

Nonetheless, if this is indeed a bug, this needs fixing of course.

Ok. Maybe I’ve been misled by some samples using TCP mode with ssl at the beginning.
I’ve been already surprised why there was not used hdr(host) in ssl-frontends.

So it might be the case that the SNI is still example.com although it has been requested www.example.com by redirect from example.com, because the browser knows by cert it can reuse the old ssl connection and so SNI never changes and stays example.com?
Can you confirm this?

Yes, if the browser setups a SSL connection for example.org, the SNI value will have example.org. But if the certificate also covers www.example.org the browser can request that in the same SSL session. But SNI is only exchanged at the beginning, in the client_hello, there is no SNI afterwards.

So you can end up with a HTTP transaction where the SNI is example.org and the Host header is www.example.org - in this case, only the Host header is relevant and correct for what you need to know (you want to know what the browser intends to request, not what the initial SNI value was).

Which is why you should always use HTTP headers. Only use SNI if you cannot actually get to the HTTP header (like when you are passing TLS through without decryption).

Does this explain what you are seeing or do you think there is still a change in the behavior of haproxy between those releases that needs troubleshooting?

I can not say for sure at the moment.
The last weeks I’ve never had a problem with the redirect looping,
and yesterday I’ve updated to 2.2.6 and it came to the scene.
But I’ve also seen that the browsers or better the web clients handle the redirecting with ssl all a little bit different.
Curl seems not to reuse the ssl connection on redirect at all. With desktop firefox I also had no problem. Only the stripped down mobile version and with chrome(ium) on desktop and mobile.
So maybe I just didn’t see it before, because I mostly use desktop firefox and in the rare situations using a mobile, it maybe just worked at this moment or there has been some info in the cache.
I’ll go temporarily back to version 2.2.5 with the old config in the next days and check this out.

@lukastribus

Hi Lukas,
there is definitively something changed between 2.2.5 and 2.2.6.
Up to 2.2.5 it is no problem to use the ssl_fc_sni in chromium.
Going to 2.2.6 it “fails” immediately with loop-redirect. I’ve repeated 3 times now.

br Markus

Can you provide the output of haproxy -vv please, then I will try to reproduce.

# haproxy -vv
HA-Proxy version 2.2.6-3709bd4 2020/11/30 - https://haproxy.org/
Status: long-term supported branch - will stop receiving fixes around Q2 2025.
Known bugs: http://www.haproxy.org/bugs/bugs-2.2.6.html
Running on: Linux 5.4.0-56-generic #62-Ubuntu SMP Mon Nov 23 19:20:19 UTC 2020 x86_64
Build options :
  TARGET  = linux-musl
  CPU     = generic
  CC      = gcc
  CFLAGS  = -Os -fomit-frame-pointer
  OPTIONS = USE_PCRE=1 USE_OPENSSL=1 USE_LUA=1 USE_ZLIB=1 USE_NS=1
  DEBUG   = 

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

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.1g  21 Apr 2020
Running on OpenSSL version : OpenSSL 1.1.1g  21 Apr 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.5
Built with network namespace support.
Built with zlib version : 1.2.11
Running on zlib version : 1.2.11
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 PCRE version : 8.44 2020-02-12
Running on PCRE version : 8.44 2020-02-12
PCRE library supports JIT : no (USE_PCRE_JIT not set)
Encrypted password support via crypt(3): yes
Built with gcc compiler version 9.3.0
Built with the Prometheus exporter as a service

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)
            fcgi : mode=HTTP       side=BE        mux=FCGI
       <default> : mode=HTTP       side=FE|BE     mux=H1
              h2 : mode=HTTP       side=FE|BE     mux=H2
       <default> : mode=TCP        side=FE|BE     mux=PASS

Available services :
	prometheus-exporter

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

I’m unable to reproduce anything here.

{ ssl_fc_sni -i example.com } only matches example.com, not www.example.com, whether it comes from curl or chrome. I can’t seem to trigger any misbehavior in 2.2.6 regarding this ACL.

Yes, you have explained me the condition yourself before :wink:
It is no problem of acl. But the tls connection handling has changed.

My guess is, that haproxy 2.2.5 closes the tls connections always on redirect to different url domain or it only reuses tls connection with equal sni domain.
In 2.2.6 the tls connection is now reused and so sni doesn’t match host header like in the older version if a redirect to different host url occurs.
If this is the case you could say that 2.2.5 has been buggy and it works now as expected.
Although I don’t know if this change is intended in a minor-release.

Changes in connection handling or TLS are assumption, we don’t know what actually changed that causes the difference in behavior. That’s what I was trying to find out, but I was unable to reproduce it, even with Chrome.