Hi Tim, I've played around with your solution a bit and I think I may have found two issues with it:
- It doesn't check if the client uses SNI at all and it will deny the request if no SNI is used - It fails if the client adds a port to the host header So to my understanding, it is perfectly fine for a client to not use SNI if there is only one certificate to be used. HAproxy should deny requests only if SNI and host header don't match AND there is a value in SNI set. As for the second part, I've come across some clients that set the port number in the host header, even if it is a well-known port. HAproxy should accept that and compare SNI and host header based on name only, since SNI will never contain a port. Here is my iteration of your solution: http-request set-var(txn.host) hdr(host),field(1,:) acl ssl_sni_http_host_match ssl_fc_sni,strcmp(txn.host) eq 0 http-request deny deny_status 421 if !ssl_sni_http_host_match { ssl_fc_has_sni } - I am using the field converter to strip away any ports from the host header - I only deny requests that actually use SNI What are your thoughts on this? I know that technically this would still allow clients to do this: curl https://myhost.com:443 -H "host: myhost.com:1234" this would then pass and not be denied. But I don't see any other choice since SNI will never contain a port, I must ignore it in the comparison. Best regards, Dominik On 25.06.21, 11:07, "Tim Düsterhus" <t...@bastelstu.be> wrote: Dominik, On 6/25/21 10:42 AM, Froehlich, Dominik wrote: > Your code sends a 421 if the SNI and host header don't match. > Is this the recommended behavior? The RFC is pretty thin here: > > " Since it is possible for a client to present a different server_name > in the application protocol, application server implementations that > rely upon these names being the same MUST check to make sure the > client did not present a different name in the application protocol." > (from https://datatracker.ietf.org/doc/html/rfc6066#section-11.1 ) Indeed it is. See the HTTP/2 RFC: https://datatracker.ietf.org/doc/html/rfc7540#section-9.1.1. Additionally I would recommend using single-domain certificates (if possible), then a regular browser will never see a 421, because it will not perform connection reuse. > My initial idea was to simply pave over any incoming headers with the SNI: > >> http-request set-header Host %[ssl_fc_sni] if { ssl_fc_has_sni } > > This wouldn't abort the request with a 421 but I am not sure if I MUST abort it to be compliant. This is equivalent to routing based off the SNI directly and it's as unsafe. If a browser reuses a TCP connection for an unrelated host you will send the request to the wrong backend. > Regarding domain fronting, I thought I might be able to have the cake and eat it, too if I still allowed it but only prevented it for mTLS > Requests. Maybe like this: > >> http-request set-header Host %[ssl_fc_sni] if { ssl_c_used } > > Only performing checks when `ssl_c_used` is true should be safe. But I *strongly* recommend sending a 421 instead of attempting to fiddle around with the 'host' header. It's just too easy to accidentally perform incorrect routing. It would then look like this: > http-request set-var(txn.host) hdr(host) > http-request deny deny_status 400 unless { req.hdr_cnt(host) eq 1 } > # Verify that SNI and Host header match if a client certificate is sent > http-request deny deny_status 421 if { ssl_c_used } ! { ssl_fc_sni,strcmp(txn.host) eq 0 } Best regards Tim Düsterhus