HAProxy ALPN Pass through to TLS protocol farms


#1

Hi,

I need help to better understand alpn routing capabilities of haproxy…
I have tried something which finaly did not work but I had not understand where I missed.

In my mind, I would like to implement an SSL Pass-Through TLS protocol router which by default detect alpn and send request to a nginx with alpn + h2 farm or a nginx + spdy one (switch user alpn protocol supported) and fallback to a npn + spdy farm if alpn is not supported by client.

Focusing only on h2 and a fallback to npn + spdy, I tried:

backend nginx-alpn-h2-lb-webfullhttps
mode tcp
server 10.4.156.232:8026_nginx-alpn-h2-lb1 10.20.135.232:8026 check inter 2s rise 3 fall 2 send-proxy

backend nginx-npn-spdy-lb-web
mode tcp
server 10.4.156.234:8025_nginx-npn-spdy-lb1 10.20.135.234:8025 check inter 2s rise 3 fall 2 send-proxy

backend nginx-npn-spdy-lb-webfullhttps
mode tcp
server 10.20.135.234:8026_nginx-npn-spdy-lb1 10.20.135.234:8026 check inter 2s rise 3 fall 2 send-proxy

frontend shared-frontend
bind 10.4.156.235:80
bind 10.4.156.235:443 alpn h2
acl p443 dst_port 443
acl speak_alpn_h2 ssl_fc_alpn -i h2
use_backend nginx-alpn-h2-lb-webfullhttps if p443 speak_alpn_h2
acl p80 dst_port 80
use_backend nginx-npn-spdy-lb-web if p80
acl speak_alpn ssl_fc_alpn -m found
use_backend nginx-npn-spdy-lb-webfullhttps if p443 !speak_alpn

It does not work, it only pass request to the npn + spdy farm even with proper client (curl with h2 support and openssl 1.0.2).

I have tried the same thing and I put ssl on bind directive with a valid cert and i set ssl as server backend options and it seems to work…

Reading HaProxy doc, I see:

ssl_fc_alpn : string
This extracts the Application Layer Protocol Negotiation field from an
incoming connection made via a TLS transport layer and locally deciphered by
haproxy. The result is a string containing the protocol name advertised by
the client. The SSL library must have been built with support for TLS
extensions enabled (check haproxy -vv). Note that the TLS ALPN extension is
not advertised unless the “alpn” keyword on the “bind” line specifies a
protocol list. Also, nothing forces the client to pick a protocol from this
list, any other one may be requested. The TLS ALPN extension is meant to
replace the TLS NPN extension. See also “ssl_fc_npn”.

Why is it mandatory to offload SSL to be able to read ssl_fc_alpn? It would be more performant to be able to read it without offloading it and let it be offloaded by the proxified backend… I though that alpn field was a clear one, is it not the case?

Thanks for your help.


#2

Well your analysis is correct, it doesn’t work because all those ssl_fc_* variables are based on OpenSSL feedback that is there only when we actually do TLS/SSL. If we just forward TCP between the frontend and backend, we don’t have that insight.

We can parse SNI (req.ssl_sni) even when we just forward TCP, because req.ssl_sni parses the payload “manually”.

Its is impossible to do the same with NPN, because its actually encrypted, so we will never be able to take decisions from NPN unless we terminate TLS.

We could theoretically extract ALPN information (it is in fact plaintext, like you said) from the client hello, but that’s not supported currently - someone would have to actually write that parsing code.

Also ALPN is a list of supported protocols (each protocol is a string, so we are talking about a list of strings), not just a single value. So either we just provide the first protocol the client lists in ALPN (which would brake your setup once clients no longer announce h2 as the first protocol in ALPN), or we would have to handle that list properly, which I’m not sure the sample framework in haproxy actually supports.

Or we could just return binary and leave it to completely up to the user to parse it.

A more generic approach would be to make an fetcher that returns arbitrary TLS extensions as binary.


#3

Hi,

OK, I am pleased to know why it did not work :slight_smile:
Great thanks for your smart answer.

I think Chrome is about to switch to ALPN (I do not know if it has been released or not), that is why I would like to manage the transition properly since we have android/ios clients which are always npn compliant only and it would have been a better way to manage it than creating different application endpoints, there is so much amazing and smart things in haproxy that I was thinking that it will be able to do this kind of TLS pass through.

I know that in the state of the art (with haproxy) I can manage a routing for spdy or h2 over ALPN or NPN but not both and at the cost of 2 times SSL offload…

A question for you, do you know if it would be possible to fetch the field (alpn protocol list) using payload() function? Using which offsets(I did not find the info where it starts)?
Doing this, I could write an acl which assert the h2 binary value presence in client hello and maybe use a kind of affinity using stick (in a fashion inspired of http://blog.haproxy.com/2011/07/04/maintain-affinity-based-on-ssl-session-id/).

Many thanks.


#4

Major browsers already use ALPN in their stable version, that even includes IE11 on Win10 and Chrome on Android 6.

The problem with matching ALPN is that the offset is not fixed, it could change from one browser, one OS, one SSL library to another, so when you find out howto statically match your browser (with payload()), you will only match that exact combination (its like fingerprinting).

Thats why a fetcher returing only the ALPN binary content would be required in your case (it would have to go through each TLS extension until ALPN is found and then return it).