Match server based on correct certificate?


I am trying to write a config that allows me to work with this setup:

I currently have one client connecting to two different services (borth port 443) on two different servers (different IPs). I would like that client to connect to the same server.

Service 1 is a mix of http and tcp, while Service 2 is pure tcp (protobuf).

The SNI is empty so I can’t use that to write my ACL route.

Is it possible to route via matched certificate?

Something like:

frontend in
        bind *:443 ssl crt /link/to/service1.pem  crt /link/to/service2.pem
        mode tcp
        acl backend1 cert matches service1.pem
        acl backend2 cert matches service2.pem
        use_backend tcp_backend1 if backend1
        use_backend tcp_backend2 if backend2

Any help is appreciated!

How does it select the correct certificate without SNI?

At the moment I am using the same .pem for both services so that’s probably why that works at all.

I am using something akin to this to see the SNI

frontend in
  mode tcp
  tcp-request inspect-delay 3s
  tcp-request content capture req.ssl_sni len 10
  log-format "capture0: %[capture.req.hdr(0)]"

The resulting loglines are empty except for “capture0:”.

I am happy to try other ways of separating the traffic. Unfortunately I can’t change anything about the client.

First of all you are accessing req.ssl_sni which will always be empty because you don’t have a SSL client hello in the buffer, but you are terminating(deciphering) SSL, so you need to be using ssl_fc_sni instead.

Then what you are logging is the first captured HTTP header, which is completely unrelated of what you are trying to do; you don’t work with HTTP headers here at all.

Try this instead:

frontend in
    bind *:443 ssl crt /link/to/service1.pem  crt /link/to/service2.pem
    mode tcp
    log-format "ssl variables: %[ssl_fc_sni]/%sslv/%sslc"

To find out whether or not the client send SNI you can also take a packet capture and check the server name value in the client hello.

Afterwards you can simply match SNI with acls like:

    use_backend tcp_backend1 if { ssl_fc_sni -i }
    use_backend tcp_backend2 if { ssl_fc_sni -i }

Or you can match one and default_backend the other one, if you only have 2 scenarios you need to cover and you want to match only a single scenario via SNI.

I really appreciate your help, thank you!

It seems like I am out of luck though:

<134>Apr 25 11:44:47 haproxy[30]: ssl variables: -/TLSv1.2/ECDHE-RSA-AES128-SHA256 <-Service A
<134>Apr 25 11:44:50 haproxy[30]: ssl variables: -/TLSv1.2/ECDHE-RSA-AES128-SHA256 <-Service A
<134>Apr 25 11:44:57 haproxy[30]: ssl variables: -/TLSv1.2/ECDHE-RSA-AES128-SHA256 <-Service B

It seems like it can’t be done, which is too bad. Unless you have another idea?

Did you try both clients? The one using protobuf and the one using HTTP and other TCP protocols?
You only need SNI capable clients for one of the two scenarios.

If none of the clients support SNI, you only choice is getting a single certificate that matches both hostnames (via SAN or wildcard), and then try to match something in the first client packet to make a routing decision.

For example, HTTP can be matched. I’m not familiar with protobuf though, I don’t know if the first packet comes from the client and if it contains something useful to make a routing decision.

Yes, I tried both. It’s hard to see in my last message, but I marked which request(s) where made to which service. Both empty unfortunately.

I have a single certificate, unfortunately I can’t find anything to match by. What makes this worse is that Service A sometimes gets http-requests and sometimes tcp requests (If I only route http-requests to Service A the client doesn’t work as intended).

Looks like you out of luck then. This is going to require separate ports or IP addresses then.