How to set ssl verify client for specific domain name


#1

Hi, all

I have two domain name test1 and test2
test1 needs to verify client certificate,
test2 is a normal https website

here’s the config for test1, but I don’t know how to merge test2 to it becase test2 does not need to verify client certificate, seems ‘verify required’ is a global option, how can I just let test1 to verify client certificate? Thanks for the help (I’m new to HAProxy, please correct me if anything wrong in my config, thanks a lot.).

frontend http_in
        bind *:80
        bind *:443 ssl crt /etc/ssl/certsforhaproxy/test1.pem crt /etc/ssl/certsforhaproxy/test2.pem ca-file /etc/ssl/certsforhaproxy/ca.pem verify required
        redirect scheme https if !{ ssl_fc }
        acs host_test1 hdr_beg(host) test1.demo.com
        acs host_test2 hdr_beg(host) test2.demo.com
        use_backend test1_back if host_test1
        use_backend test2_back if host_test2

backend test1_back
        mode http
        default-server inter 2s fall 2 rise 2
        server node1 10.10.0.1:1234 check port 1234
        server node2 10.10.0.2:1234 check port 1234
        server node3 10.10.0.3:1234 check port 1234

backend test2_back
        mode http
        default-server inter 2s fall 2 rise 2
        server node1 10.10.0.1:2345 check port 2345
        server node2 10.10.0.2:2345 check port 2345
        server node3 10.10.0.3:2345 check port 2345


#2

Only thing I can think of right now is to set the verify to optional so clients can connect with or without the client certificate and then restrict access using an ACL like so:

acl restricted hdr(host) -i somedomain.com
http-request deny if restricted ! { ssl_c_used }


#3

There is no simple way to do this, unfortunately.

Use a TCP frontend withouth SSL termination, SNI route to different backends that recirculate to traffic to dedicated SSL frontends with different configurations.

Something like:

frontend port443
    bind :443
    tcp-request inspect-delay 5s
    tcp-request content accept if { req_ssl_hello_type 1 }
    use_backend recir_clientcertenabled if { req_ssl_sni -i test1.demo.com }
    default_backend recir_default

backend recir_clientcertenabled
    server loopback-for-tls abns@haproxy-clientcert send-proxy-v2
backend recir_default
    server loopback-for-tls abns@haproxy-default send-proxy-v2

frontend fe-ssl-clientcert
    mode http
    bind abns@haproxy-clientcert accept-proxy ssl crt /etc/ssl/certsforhaproxy/test1.pem crt ca-file /etc/ssl/certsforhaproxy/ca.pem verify required
frontend fe-ssl-default
    mode http
    bind abns@haproxy-default accept-proxy ssl crt /etc/ssl/certsforhaproxy/test2.pem crt

Use haproxy with http2 backends and non http2 backends
Different SSL Protocols for each backend server. Is it possible?
Restrict access to some part of the site with certificate
How to auth with client certificates and not depending on URI
#4

Improvement to my answer…

Set the verify option on the bind line to “optional” and use the following ACL:

acl restricted hdr(host) -i somedomain.com
http-request deny if restricted !{ ssl_c_used 1 } || restricted !{ ssl_c_verify 0 }

The above simply says if the header host matches a specific domain deny request unless the client has provided a certificate and that certificate was verified.

You can improve on this by adding specific error pages, example:

redirect location /certmisiing.html if restricted !{ ssl_c_used 1 }
redirect location /certexpired.html if restricted { ssl_c_verify 10 }
redirect location /certrevoked.html if restricted { ssl_c_verify 23 }
redirect location /othererrors.html if restricted !{ ssl_c_verify 0 } 

I even wrote a Blog on the subject, hope that helps: https://www.loadbalancer.org/blog/client-certificate-authentication-with-haproxy/


#5

“verify optional” has a rather important disadvantage: it changes the SSL handshake, leading to different browser behavior for all of the websites.

For example, if you have a browser with a client certificate installed for a unrelated domain, and haproxy handles test1.demo.com and test2.demo.com, the browser will ask you for both test1.demo.com and test2.demo.com if and which client certificate you wanna send.

While this is expected behavior for test1.demo.com it isn’t for test2.demo.com.

By SNI routing to different SSL termination endpoints like suggest above we avoid this problem. We also have a “proper” SSL handshake failure with “verify required”, instead of failure on the HTTP layer.


#6

Thanks Lukas, I found this also in my testing, I was very much playing and reverse engineering how it works learning lots of new stuff for the first time. Yesterday I referenced your better solution with SNI in my Blog, I hope that’s okay, I gave you credit and linked it back here.


#7

Hi,

I tried to get lukas’ solution running, but with no success

frontend https-switch
    bind *:444
    tcp-request inspect-delay 5s
    tcp-request content accept if { req_ssl_hello_type 1 }
    #use_backend recir_clientcertenabled if { req_ssl_sni -i www.mydomain.de }
    default_backend recir_default

backend recir_clientcertenabled
    server loopback-for-tls abns@haproxy-clientcert send-proxy-v2
backend recir_default
    server loopback-for-tls abns@haproxy-default send-proxy-v2

frontend https-cert-required
    bind abns@haproxy-clientcert accept-proxy ssl crt /etc/ssl/private/mydomain.de.pem ca-file /etc/ssl/private/client-authentication.pem verify required
    # HSTS (15768000 seconds = 6 months)
    http-response set-header Strict-Transport-Security max-age=15768000
    mode http
    default_backend nodes-cert-required

frontend https-default
    bind abns@haproxy-default ssl accept-proxy crt /etc/ssl/private/mydomain.de.pem crt /etc/ssl/private/mydomain2.de.pem
    # HSTS (15768000 seconds = 6 months)
    http-response set-header Strict-Transport-Security max-age=15768000
    mode http
    default_backend nodes-https

But everytime I try to open https://www.mydomain.de:444 I receive an ssl error in the browser:
SSL_ERROR_RX_RECORD_TOO_LONG

In the log file I could not find any hint too:

Mar 29 17:00:32 sql2 haproxy[13206]: 95.88.243.138:50938 [29/Mar/2018:17:00:32.101] https-switch https-switch/<NOSRV> -1/-1/-1/-1/0 400 188 - - PR-- 22/0/0/0/0 0/0 "<BADREQ>"

Has someone made a running configuration?
@AaronWest: can you give us your blog reference?

Thanks!

Marc


#8

You need “mode tcp” in the default section, or in the first 3 sections in my proposal. TCP mode is required for SNI switching.


#9

Thank you!

That was the missing part. In my default section was mode http.