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

Reply via email to