Forward Connections if SSL Cert or IP in Whitelist

I am working on an HAProxy server configuration for a proof of concept. We want to forward any incoming connections which either

  1. Have a successful 2-way TLS handshake or
  2. Are coming from an IP address in a whitelist

I was looking at the documentation on ACLs, and thought maybe I could configure one to check for certs and one to check the whitelist, but I’m not sure if I’m barking up the right tree here. Currently, I have a server accepting valid certs by binding a port with an SSL certificate like so: bind *:2000 ssl crt cert.pem ca-file myCA.pem verify required

Another idea i’ve had is redirecting to a second port in case of failure on the first one. So, for instance, if the handshake fails, we redirect to another port checking a whitelist, or vice versa.

Which of these seems like a better approach? Are either of them impossible to implement in HAProxy? Thank you for any assistance.

In a perfect world, you would just set “verify” to optional, and handle everything else with ACL’s.

However, that means that a browser in the whitelisted IP range may ask the customer as well which client certificate to send (if the browser has a client certificate). You probably want haproxy to act normal for the whitelisted IP’s, and only signal the intend to authenticate a client certificate when the customer is not whitelisted.

To do that, you need a little trick, basically a TCP frontend that checks the source IP and reoutes the TCP traffic to a client cert frontend or a “normal” frontend.

It would probably look like this:

frontend port443
    mode tcp
    bind :443
    acl goodguys src 10.0.0.0/24
    use_backend recir_goodguys if goodguys
    default_backend recir_clientcert

backend recir_clientcert
    mode tcp
    server loopback-for-tls abns@haproxy-clientcert send-proxy-v2
backend recir_goodguys
    mode tcp
    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

Hey, thanks for the reply. This is similar to the idea I had in my mind for the second option, which I’ve been playing around with. I have a few questions about it.

First, what are bind abns@haproxy-clientcert... and bind abns@haproxy-default... doing? I only know how to bind to an IP:port.

Second, are you using fe-ssl-default to handle regular SSL connections (as in, only the client is verifying the server, not 2-way). If so, what exactly is in test2.pem, and what does the client need to access the service that way?

Finally, this proxy is (eventually) going to be used for forwarding to another https site. Will it be a problem to have two different SSL connections in the chain? Can HAProxy handle that?

My idea had been to do something like this, but it seems a but hacky:

frontend intranet
    mode http
    bind *:2000 ssl crt both.pem ca-file myCA.pem verify optional
    use_backend ssl-error unless { ssl_c_verify 0 }
    default_backend demo

backend ssl-error
    mode http
    server wl 127.0.0.1:2001

frontend http-in
    bind *:2001
    mode http
    reqadd X-Forwarded-Proto:\ https
    acl whitelist src -f whitelist.txt
    acl all src 0.0.0.0

    use_backend demo if whitelist

backend demo
    mode http
    server locahost 127.0.0.1:9000 check

There is a regular http server running on :9000

Those are abstract namespaces, think of it like sockets, but neither with an IP:port, nor with a filesystem dependency (as it is for unix domain sockets).

Sounds complicated, but it isn’t. This configuration just connects the backend with the lower layer frontend.

You can easily replace this with 2 TCP ports on 127.0.0.1.

I just copy and pasted the base config from another example where we actually had different certificates.

In your case both would be “crt both.pem”.

You can do that, you just have to re-encrypt on the backend (use the ssl keyword on the backend and configure ca validation with the “verify” and the ca-file keyword (this is different than your frontend client certificate authentication).

So I’ve adapted this to my situation. I have:

frontend port2000
    mode tcp
    bind *:2000
    acl goodguys src -f whitelist.txt
    use_backend recir_goodguys if goodguys
    default_backend recir_clientcert

backend recir_clientcert
    mode tcp
    server loopback-for-tls abns@haproxy-clientcert send-proxy-v2

backend recir_goodguys
    mode tcp
    server loopback-for-tls abns@haproxy-default send-proxy-v2

frontend fe-ssl-clientcert
    mode http
    bind abns@haproxy-clientcert accept-proxy ssl crt root1.crt.pem ca-file root1.ca.pem verify required
    default_backend app

frontend fe-ssl-default
    mode http
    bind abns@haproxy-default accept-proxy ssl crt root2.crt.pem
    default_backend app

backend app
    mode http
    server localhost 127.0.0.1:9000

But, when I run this, I get [ALERT] 284/153320 (57243) : Starting frontend fe-ssl-clientcert: cannot bind UNIX socket [] and [ALERT] 284/153320 (57243) : Starting frontend fe-ssl-default: cannot bind UNIX socket [] I’m running this on a mac, so UNIX. Is there something else I need to configure to use the abstract namespaces you mentioned?

It works by binding on 127.0.0.1, but I would like to use the abns if possible so the app isn’t exposed on these other ports.

Ah no, abstract namespaces is a Linux-only feature.
You can still bind to unix sockets though, just put in a filesystem path.

You can read more about those address families in:
https://cbonte.github.io/haproxy-dconv/1.7/configuration.html#4.2-bind