HAProxy 1.6 with SNI and different SSL Settings per hostname

Dear Readers,
I’m trying to set up haproxy with SNI.
I’m getting a GREEN A on SSLlabs for news.rathaus.potsdam.de
But Browsers or haproxy are ignoring my setup :expressionless:

with
openssl s_client -servername news.rathaus.potsdam.de -connect news.rathaus.potsdam.de:443

I see that haproxy is sending the right certificate for news.potsdam… AND asks for a Client certificate for test.potsdam…

What’s wrong with that configuration or is it simply not implemented in the way I think ?

8<------ SNIP
haproxy -vv

HA-Proxy version 1.6.9 2016/08/30
Copyright 2000-2016 Willy Tarreau <willy@haproxy.org>

Build options :
  TARGET  = linux2628
  CPU     = generic
  CC      = gcc
  CFLAGS  = -O2 -g -fno-strict-aliasing -Wdeclaration-after-statement
  OPTIONS = USE_OPENSSL=1 USE_STATIC_PCRE=1

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

Encrypted password support via crypt(3): yes
Built without compression support (neither USE_ZLIB nor USE_SLZ are set)
Compression algorithms supported : identity("identity")
Built with OpenSSL version : OpenSSL 1.0.2j  26 Sep 2016
Running on OpenSSL version : OpenSSL 1.0.2j  26 Sep 2016
OpenSSL library supports TLS extensions : yes
OpenSSL library supports SNI : yes
OpenSSL library supports prefer-server-ciphers : yes
Built with PCRE version : 8.38 2015-11-23
PCRE library supports JIT : no (USE_PCRE_JIT not set)
Built without Lua support
Built with transparent proxy support using: IP_TRANSPARENT IPV6_TRANSPARENT IP_FREEBIND

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.

8< ---- SNIP

haproxy.cfg

global
        user    haproxy
        group   haproxy
        stats socket /var/run/haproxy.sock level admin
        nbproc  1
        log 127.0.0.1:514 len 4096 local2
        pidfile /var/run/haproxy.pid
      # SSL DEFAULTS
        tune.ssl.default-dh-param 4096
        ssl-default-bind-options no-sslv3 no-tls-tickets no-tlsv11
        ssl-default-bind-ciphers ECDHE+aRSA+AES256+GCM+SHA384:ECDHE+aRSA+AES128+GCM+SHA256:ECDHE+aRSA+AES256+SHA384:ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:!aNULL:!MD5:!DSS

defaults
        option http-server-close
        log global
        option httplog
        mode http
        timeout http-request 5s
        timeout connect 4s
        timeout server 10s
        timeout client 30s

    option forwardfor    # set the client's IP in X-Forwarded-For.
    option dontlognull
    option splice-response
    option http-keep-alive
    option tcp-smart-accept
    option tcp-smart-connect
    timeout http-request    5s
    timeout http-keep-alive 5s
    timeout connect         5s

frontend www-https

# news 443 ssl crt /etc/haproxy/certs.d/news.rathaus.potsdam.de.pem
# test crt /etc/haproxy/certs.d/test.rathaus.potsdam.de.pem ca-file /etc/haproxy/certs-own/ca-svp-root-crt.pem verify required
# TWO Certs news and test whetre test is self signed an reqires an client certificate which is downloadable
# with https:/news.rathaus.potsdam.de/certs/client1.pfx pass:Potsdam2016!

bind *:443 ssl  crt /etc/haproxy/certs.d/news.rathaus.potsdam.de.pem strict-sni  crt /etc/haproxy/certs.d/test.rathaus.potsdam.de.pem strict-sni ca-file /etc/haproxy/certs-own/ca-svp-root-crt.pem verify required


 # split up incoming URLs
        acl APP_CERTS path_beg -i /certs
        acl APP_RIS path_beg -i /ai
        acl APP_RIS path_beg -i /bi
        acl APP_PWA path_beg -i /pwa
        
        # PWA und robots favicon
        acl APP_PWA path_end -i robots.txt
        acl APP_PWA path_end -i favicon.ico

  # Backend Routing
        use_backend server_RIS if APP_RIS-AI # --> news
        use_backend server_CERTS if APP_CERTS # --> test
        use_backend server_EMA if APP_EMA 
        use_backend server_PWA if APP_PWA
        
  # Backend definition
    backend server_CERTS
      mode http
      option httpclose
       server ZertiSchleuder 127.0.0.1:8080 maxconn 120 check
    
    backend server_RIS
          mode http
          option httpclose
          server ris 10.234.205.157:80 maxconn 200 check
    
    backend server_PWA
            mode http
            option httpclose
            http-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains; preload;"
            server pwa 10.234.205.156:80  check

Its not supported the way you use it. They sequence of keywords in the bind configuration line is irrelevant.

For example, specifying strict-sni twice doesn’t make any difference, its not a per certificate configuration.

And that is also valid for the client certificate authentication, so if you specify it, you are enabling it for everything.

Workarounds:

  • use a different IP or port for each cert (so it is a different bind line and you can therefor apply the configuration you need on a per certificate/“bind” basis)
  • if you need everything on a single IP:port combination, the only way to achieve this currently with haproxy is to configure the frontend as TCP mode without any SSL and switching to different TCP backends based on SNI values and then configure those TCP backends to point to dedicated SSL frontends, where you have a different SSL configuration (with and without client certificates). You can use unix sockets or linux abstrace namespace sockets, instead of using the loopback TCP stack.

Thats a complicated setup, but it is the only way if you need full per certificate flexibility on a single IP:port combination.
For details see those 3 links:


http://cbonte.github.io/haproxy-dconv/1.6/configuration.html#4.2-bind
http://www.serverphorums.com/read.php?10,967239

There is a proposal to fix this properly, however it is in development and it could take some time until this is implemented and made its way to a stable haproxy release:

https://www.mail-archive.com/haproxy@formilux.org/msg23747.html