I have an application behind haproxy with two different clients:
client 1 uses TLS 1.2/1.3 to connect to backend /photo-app/
client 2 can only use TLS 1.0 to connect to backend /photo-app/scan/
In order to support both clients, the application is forced to allow TLSv1 connections. This shows up as a vulnerability from standard website scans (using tools such as testssl.sh or SSL Labs). I am stuck with client 2 for the time being and I cannot change the URL or protocols/ciphers they use to connect. This is a legacy integration I am working to retire.
My current haproxy frontend implementation does properly reject client 1 non-secure requests, but that only happens after SSL termination. The goal is to also show that /photo-app/
is not vulnerable to TLS 1.1 and below.
I found a post that solves a related problem here. Unfortunately, I am unable to distinguish the two clients based on SNI, since they are the same.
This is what I have so far, but I have been unable to make client 2 requests work when trying to route on TLS version.
global
log /dev/log local0 debug
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options no-sslv3 no-tls-tickets
# https://datatracker.ietf.org/doc/html/rfc7919
ssl-dh-param-file /usr/local/etc/haproxy/ffdhe2048.dhe
defaults
log global
mode http
balance roundrobin
option dontlognull
timeout http-keep-alive 5s
timeout http-request 10s
timeout client 10s
timeout connect 5s
timeout server 10m
timeout check 5s
default-server inter 30s
default-server rise 2
default-server fall 2
compression algo gzip
compression type text/html text/css text/javascript application/javascript
frontend http
bind *:80
http-request redirect scheme https if !{ ssl_fc }
frontend https
bind *:443
mode tcp
option tcplog
tcp-request inspect-delay 5s
tcp-request content accept if { req.ssl_hello_type 1 }
use_backend recirc_client2 if { WHAT-GOES-HERE? }
default_backend recirc_default
backend recirc_client2
mode tcp
server loopback-for-tls abns@haproxy-client2 send-proxy-v2
backend recirc_default
mode tcp
server loopback-for-tls abns@haproxy-default send-proxy-v2
frontend client2
option httpslog
option forwardfor
bind abns@haproxy-client2 accept-proxy ssl crt /usr/local/etc/haproxy/photo-app.net.pem ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES128-SHA
acl url-photoapp-scan path_beg /photo-app/scan
http-request deny if !url-photoapp-scan
http-request add-header X-Forwarded-Proto https
default_backend photoapp
frontend default
option httpslog
option forwardfor
bind abns@haproxy-default accept-proxy ssl crt /usr/local/etc/haproxy/photo-app.net.pem ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305
acl is-supported-tls ssl_fc_protocol TLSv1.2 TLSv1.3
acl url-photoapp-scan path_beg /photo-app/scan
acl url-photoapp path_beg /photo-app
http-request deny if !is-supported-tls !url-photoapp-scan
http-request add-header X-Forwarded-Proto https
http-response set-header Strict-Transport-Security "max-age=63072000"
default_backend photoapp
backend photoapp
option httpchk GET /photo-app/health-check/
server 1 10.72.100.1:8100 check cookie 1
server 2 10.72.100.2:8100 check cookie 2
cookie JSESSIONID prefix nocache
HAProxy version 2.9.12-1086254 2024/11/08 - https://haproxy.org/
Status: stable branch - will stop receiving fixes around Q1 2025.
Known bugs: http://www.haproxy.org/bugs/bugs-2.9.12.html
Running on: Linux 5.15.0-124-generic #134-Ubuntu SMP Fri Sep 27 20:20:17 UTC 2024 x86_64
Build options :
TARGET = linux-glibc
CPU = generic
CC = cc
CFLAGS = -O2 -g -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_GETADDRINFO=1 USE_OPENSSL=1 USE_LUA=1 USE_PROMEX=1 USE_PCRE2=1 USE_PCRE2_JIT=1
DEBUG = -DDEBUG_STRICT -DDEBUG_MEMORY_POOLS
Feature list : -51DEGREES +ACCEPT4 +BACKTRACE -CLOSEFROM +CPU_AFFINITY +CRYPT_H -DEVICEATLAS +DL -ENGINE +EPOLL -EVPORTS +GETADDRINFO -KQUEUE -LIBATOMIC +LIBCRYPT +LINUX_CAP +LINUX_SPLICE +LINUX_TPROXY +LUA +MATH -MEMORY_PROFILING +NETFILTER +NS -OBSOLETE_LINKER +OPENSSL -OPENSSL_AWSLC -OPENSSL_WOLFSSL -OT -PCRE +PCRE2 +PCRE2_JIT -PCRE_JIT +POLL +PRCTL -PROCCTL +PROMEX -PTHREAD_EMULATION -QUIC -QUIC_OPENSSL_COMPAT +RT +SHM_OPEN +SLZ +SSL -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_TGROUPS=16, MAX_THREADS=256, default=2).
Built with OpenSSL version : OpenSSL 3.0.15 3 Sep 2024
Running on OpenSSL version : OpenSSL 3.0.15 3 Sep 2024
OpenSSL library supports TLS extensions : yes
OpenSSL library supports SNI : yes
OpenSSL library supports : TLSv1.0 TLSv1.1 TLSv1.2 TLSv1.3
OpenSSL providers loaded : default
Built with Lua version : Lua 5.4.4
Built with the Prometheus exporter as a service
Built with network namespace support.
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.42 2022-12-11
PCRE2 library supports JIT : yes
Encrypted password support via crypt(3): yes
Built with gcc compiler version 12.2.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 :
[BWLIM] bwlim-in
[BWLIM] bwlim-out
[CACHE] cache
[COMP] compression
[FCGI] fcgi-app
[SPOE] spoe
[TRACE] trace
If you route a TLSv1.0 only client to a TLSv1.0 compatible SSL termination point, this means that tools like testssl.sh and SSL Labs will show that you have enabled TLSv1.0, because that is how they test TLSv1.0 support: by trying to connect with TLSv1.0.
If you want keep TLSv1.0 enabled for a specific client and you cannot do it via hostname/SNI, then you need to do it via hardcoded IP address.
You cannot access encrypted data like URI or paths before decrypting the SSL part.
1 Like
Thank you Lukas. Given a list of client 2 IP addresses, how would I test for that in the TCP frontend?
You can make an ACL matching IP addresses and then refer to that ACL when making routing decisions:
acl client1 src 192.0.2.35
acl client1 src 198.51.100.99
use_backend client1backend if client1
1 Like
Oh crud, that was easy. I really do appreciate the help as my mind is a bit blown from looking at this for so long.
1 Like