How to create a catch-all for HTTPS connections?

I wanted to create a catch-all configuration that would answer to any http:// or https:// call if the URL is not known at that time (i.e. if there was no match before)

I thought that this would just be

http://, https:// {
    respond 410 {
        close
    }
}

It works for HTTP calls:

$ curl -v http://sjhkjsdks.swtk.eu
*   Trying 88.120.31.79:80...
* Connected to sjhkjsdks.swtk.eu (88.120.31.79) port 80
> GET / HTTP/1.1
> Host: sjhkjsdks.swtk.eu
> User-Agent: curl/8.4.0
> Accept: */*
>
< HTTP/1.1 410 Gone
< Connection: close
< Server: Caddy
< Date: Tue, 14 May 2024 12:48:13 GMT
< Content-Length: 0
<
* Closing connection

But it does not for HTTPS. The Windows version of the error:

$ curl -v https://sjhkjsdks.swtk.eu
*   Trying 88.120.31.79:443...
* Connected to sjhkjsdks.swtk.eu (88.120.31.79) port 443
* schannel: disabled automatic use of client certificate
* ALPN: curl offers http/1.1
* schannel: next InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE (0x80090326) - This error usually occurs when a fatal SSL/TLS alert is received (e.g. handshake failed). More detail may be available in the Windows System event log.
* Closing connection
* schannel: shutting down SSL/TLS connection with sjhkjsdks.swtk.eu port 443
curl: (35) schannel: next InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE (0x80090326) - This error usually occurs when a fatal SSL/TLS alert is received (e.g. handshake failed). More detail may be available in the Windows System event log.

The Linux one:

root@srv ~# curl -v https://sjhkjsdks.swtk.eu
* Host sjhkjsdks.swtk.eu:443 was resolved.
* IPv6: (none)
* IPv4: 88.120.31.79
*   Trying 88.120.31.79:443...
* Connected to sjhkjsdks.swtk.eu (88.120.31.79) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: none
* TLSv1.3 (IN), TLS alert, internal error (592):
* OpenSSL/3.3.0: error:0A000438:SSL routines::tlsv1 alert internal error
* Closing connection
curl: (35) OpenSSL/3.3.0: error:0A000438:SSL routines::tlsv1 alert internal error

There are no errors in the Caddy logs.

I suspect that this is because there is no certificate to present for the HTTPS connection (wildly guessing). Is there a solution to that?

Yes, and in fact only Caddy has this feature. You’re looking for “on-demand TLS”.

Scroll down and find the “Experience it” box to do the demo here:

Here’s the docs about how to use it:

1 Like

If there’s really nothing in the Caddy logs, then the connection isn’t even reaching Caddy. You should enable debug mode in your config to be sure.

1 Like

OK, I get it now. I never knew about on_demand :slight_smile:

I think I will not have a simple solution then because either I have a correct request and it is handled correctly, or I have an incorrect one (= the app does not exist) and I would need to issue an on_demand TLS request which is exactly what must not be done (because of rate limitations).

I will live with that.

Thanks for teaching me a new thing!

I’m not sure if I understand your conclusion. Why are you worried about rate limits exactly?

If Caddy doesn’t have a TLS cert for a domain, then it can’t complete the TLS handshake. On-Demand TLS allows Caddy to issue a certificate right away on the first TLS handshake for a domain, if allowed. If not allowed, it will cause the TLS handshake to fail as normal (the behaviour you’re already seeing).

Yes, that’s right. I am going to try to clarify.

I wanted to send back a 410 when there is no match in caddy. This means that if someone sent me 10000 requests to https://req1.swtk.eu, https://req2.swtk.eu, … https://req10000.swtk.eu, Let’s Encrypt would have been queried 10000 times and I would have hit the quota somewhere in the meantime.

This is a danger that is clearly documented and the solution is to have a database that keeps of the names that have been configured. This is fine, but not relevant to my case because I do not know which domain will be requested.

Hope this is better now.


As for the “why” part: I do not care about forged requests but I have monitoring set up for my services (applications behind caddy). I can have two cases of replies (in addition to a correct one):

  • if an application really fails (crashes, does not respond, …) I will get a 50x reply and this is for me the signal that something is wrong with my app.
  • if an application that existed has been removed (deleted), it would be the case of the “forged” URL and I would like to respond with the 410. This will be a signal for me that I have to fix the monitoring

This is in reality a nice to have - I am not Google and have, what, 30 services or so. I add or remove one maybe once a month or even less so whatever I get in the monitoring will mean “something needs to be fixed”. It would be just aesthetically pleasing to differentiate the two cases.

Another thing is that I have a generic domain attacked through typical means - notably though the public certificate log, so for known (and at some point registered) FQDNs. I do not expect to be DDoSed with the 10000 forged URLs I mentioned earlier so I could go for it, use on-demand TLS and hope for the best (worst case I will need to wait until the quota is reset)

If all your domains are subdomains like this, and are for domains under your control, you should just get a wildcard certificate using the ACME DNS challenge. That way you have one certificate that covers those infinite amount of subdomains.

If these are actually customer domains, then you should have a registry in your database of those customer domains. You should reject any unknown domains.

But yeah, it’s impossible to respond with any kind of HTTP status when Caddy doesn’t have a TLS cert, because HTTP doesn’t happen if the TLS handshake fails (TLS wraps HTTP, and therefore is handled before HTTP is decrypted/parsed/executed).

2 Likes

To block clients before a TLS handshake, you won’t be able to use HTTP as Francis mentioned. You’ll need to implement some TCP-layer or IP-layer blocking.

1 Like

This is also the conclusion I had last night when thinking more about this.

My installation is purely a home self-hosting one (though my sanity and family relationship depend on it because when smart lights do not switch on, well this is not good), I can implement whatever I need.

I’ve been using Caddy for so many years and the TLS part is so seamless that I simply forgot about wildcard certs (though they would neatly map with my wildcard DNS, aesthetically speaking).

I will do that, thanks a lot for the hint.

That was not my intent - as I mentioned in the “why” part of How to create a catch-all for HTTPS connections? - #7 by WoJ, it is really for eye candy (ok, eye candy++ because it makes my life easier).