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" <[email protected]> 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