Hi,
I’m using haproxy as an SSL terminator and SNI based service selector for my family server. Some of the subdomains use client side certificate, some of them not. Some of them are TCP, others are HTTP. For me haproxy is a convenient solution for SSL termination, authentication and even HTTP/2 support for my dummy embedded servers, alarm system, openvpn (over tls) and nextcloud instance. (A lot of things, except load balancing )
My problem with switching subdomain in a browser from an unauthenticated section to a restricted section. I use Let’s encrypt wildcard certificate for my domain, so the browser would like to use the same ssl session with the same SNI for all the subdomains. Only the HTTPS services are affected, so I can check the HOST field in the HTTP header. Now I know, that the browser want to reach a restricted subdomain and drop and error message. The only way to reach the restricted subdomain is to restart the browser.
Is it possible to close the SSL session (see close-session-backend in my config) to force the browser for a new SSL negotiation (with client certificate)? I don’t want to use the verify optional
solution at bind, as I don’t want the browser to popup a window for client side certificate where I don’t need it. After all when I don’t give a certificate there, I don’t think it will prompt again when I switch subdomains.
I was thinking about using SNIs in the crt-list.txt, but I don’t think it would work, as the same wildcard certificate will be used for every line and the browser will know nothing about this trick. Will haproxy handle these lines in the crt-list.txt as different ssl sessions?
I don’t want to use two or more certificates if possible, wildcard is a very flexible solution for me.
Thank you for your help!
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
daemon
# Default ciphers to use on SSL-enabled listening sockets.
ssl-default-bind-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
ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
tune.ssl.default-dh-param 2048
maxconn 2048
defaults
log global
mode tcp
option tcplog
option logasap
timeout connect 10000
timeout client 300000
timeout server 300000
source 0.0.0.0 usesrc clientip
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
##############################################
# http -> https redirection
##############################################
frontend http-frontend
mode http
option httplog
bind 192.168.10.52:80
redirect scheme https code 301 if !{ ssl_fc }
##############################################
# Switch between subdomains by sni
##############################################
frontend mode-switch
# option tcplog
bind 192.168.10.52:443
tcp-request inspect-delay 5s
tcp-request content accept if { req.ssl_hello_type 1 }
use_backend verif-none-tcp if { req.ssl_sni -i ovpn.mydomain.com }
use_backend verif-required-tcp if { req.ssl_sni -i tcpservice1.mydomain.com }
use_backend verif-none-error if !{ req.ssl_sni -m dom mydomain.com }
use_backend verif-none if { req.ssl_sni -i mydomain.com } || { req.ssl_sni -i www.mydomain.com } || { req.ssl_sni -i other.mydomain.com }
default_backend verif-required
backend verif-none
server recir abns@haproxy-normal send-proxy-v2
backend verif-required
server recir abns@haproxy-clientcert send-proxy-v2
backend verif-none-tcp
server recir abns@haproxy-tcp-normal send-proxy-v2
backend verif-required-tcp
server recir abns@haproxy-tcp-clientcert send-proxy-v2
backend verif-none-error
server recir abns@haproxy-error send-proxy-v2
##############################################
# HTTPS subdomains without client cert.
##############################################
# https://mydomain.com
# https://www.mydomain.com
# https://other.mydomain.com
##############################################
frontend fe-ssl-normal
mode http
bind abns@haproxy-normal accept-proxy ssl crt-list /etc/haproxy/certs/crt-list.txt alpn h2,http/1.1 no-tls-tickets strict-sni
use_backend https-backend if { hdr(host) -i mydomain.com } || { hdr(host) -i www.mydomain.com } || { hdr(host) -i other.mydomain.com }
# Wants to reach a restricted subdomain with wrong SNI
default_backend close-session-backend
backend https-backend
mode http
option forwardfor
option http-server-close
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https
http-response set-header Strict-Transport-Security "max-age=15552000; includeSubDomains"
server apache2-https 192.168.10.52:81
##############################################
# HTTPS subdomains with client cert.
##############################################
# https://*.mydomain.com
##############################################
frontend fe-ssl-clientcert
mode http
bind abns@haproxy-clientcert accept-proxy ssl crt-list /etc/haproxy/certs/crt-list.txt ca-file /etc/haproxy/certs/https_client_certs/ca.crt verify required crl-file /etc/haproxy/certs/https_client_certs/root_crl.pem alpn h2,http/1.1 no-tls-tickets strict-sni
use_backend alarm-backend if { ssl_fc_sni -i alarm.mydomain.com } { ssl_c_s_dn(CN) -m reg ^(user1|user2)$ }
use_backend gitlist-backend if { ssl_fc_sni -i gitlist.mydomain.com } { ssl_c_s_dn(CN) -m reg ^(user1)$ }
default_backend access-denied-backend
backend alarm-backend
mode http
source 192.168.10.52
server alarm-http 192.168.10.51:8000
backend gitlist-backend
mode http
option forwardfor
option http-server-close
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https
http-response set-header Strict-Transport-Security "max-age=15552000; includeSubDomains"
server apache2-https 192.168.10.52:81
##############################################
# TLS / TCP subdomains without client cert.
##############################################
# https://ovpn.mydomain.com -- OpenVPN
##############################################
frontend fe-ssl-tcp-normal
mode tcp
bind abns@haproxy-tcp-normal accept-proxy ssl crt-list /etc/haproxy/certs/crt-list.txt
use_backend ovpn-backend if { ssl_fc_sni -i ovpn.mydomain.com }
backend ovpn-backend
mode tcp
server ovpn 192.168.10.52:1122
##############################################
# TLS / TCP subdomains with client cert.
##############################################
# https://tcpservice1.mydomain.com
##############################################
frontend fe-ssl-tcp-clientcert
mode tcp
bind abns@haproxy-tcp-clientcert accept-proxy ssl crt-list /etc/haproxy/certs/crt-list.txt ca-file /etc/haproxy/certs/https_client_certs/ca.crt verify required crl-file /etc/haproxy/certs/https_client_certs/root_crl.pem
use_backend tcpservice1-backend if { ssl_fc_sni -i tcpservice1.mydomain.com } { ssl_c_s_dn(CN) -m reg ^(user1|user2|user3|user4)$ }
backend tcpservice1-backend
mode tcp
server tcpservice1 192.168.10.52:54321
##############################################
# Error backends
##############################################
frontend fe-ssl-error
mode http
option httplog
bind abns@haproxy-error accept-proxy ssl crt-list /etc/haproxy/certs/crt-list.txt alpn h2,http/1.1
use_backend error-backend
backend access-denied-backend
mode http
errorfile 503 /etc/haproxy/err_access_denied.http
backend error-backend
mode http
errorfile 503 /etc/haproxy/err_no_sni.http
backend close-session-backend
mode http
# option forceclose
errorfile 503 /etc/haproxy/err_access_denied.http
# http-request redirect code 301 location https://%[hdr(host)]%[capture.req.uri]