mirror of
https://codeberg.org/git-pages/git-pages.git
synced 2026-05-21 14:41:34 +00:00
In commit bbdaae7280, a domain cache was
introduced to deal with misbehaving crawlers that forge `Host:` header
and may cause thousands of expensive S3 requests to be submitted.
This domain cache is implemented using a Bloom filter (which can
produce false positives but not false negatives) for S3 backend, and
using a function always returning true (which will be a false positive
in most cases) for the FS backend.
Both of these behaviors are unacceptable for the Caddy endpoint, but
the FS backend case much more so. If you use git-pages with Caddy you
should upgrade to a build that includes this commit as soon as possible
or Let's Encrypt may rate-limit or restrict your account when you get
unlucky with a crawler.
91 lines
2.7 KiB
Go
91 lines
2.7 KiB
Go
package git_pages
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
func ServeCaddy(w http.ResponseWriter, r *http.Request) {
|
|
domain := r.URL.Query().Get("domain")
|
|
if domain == "" {
|
|
http.Error(w, "domain parameter required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Save the backend some effort from queries that are essentially guaranteed to fail.
|
|
// While TLS certificates may be provisionsed for IP addresses under special circumstances[^1],
|
|
// this isn't really what git-pages is designed for, and object store accesses can cost money.
|
|
// [^1]: https://letsencrypt.org/2025/07/01/issuing-our-first-ip-address-certificate
|
|
if ip := net.ParseIP(domain); ip != nil {
|
|
logc.Println(r.Context(), "caddy:", domain, 404, "(bare IP)")
|
|
w.WriteHeader(http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
var err error
|
|
domain = strings.ToLower(domain)
|
|
|
|
// Run a cheap check as to whether we might be serving the domain.
|
|
var found = domainCache.CheckDomain(r.Context(), domain)
|
|
|
|
if found {
|
|
// Run an expensive check as to whether we are actually serving the domain.
|
|
found, err = backend.CheckDomain(r.Context(), domain)
|
|
}
|
|
|
|
if !found {
|
|
// If we don't serve the domain, but a fallback server does, then we should let our
|
|
// Caddy instance request a TLS certificate. Otherwise, we'll never have an opportunity
|
|
// to proxy the request further. (This functionality was originally added for Codeberg
|
|
// Pages v2, which would under some circumstances return certificates with subjectAltName
|
|
// not valid for the SNI. Go's TLS stack makes `tls.Dial` return an error for these,
|
|
// thankfully making it unnecessary to examine X.509 certificates manually here.)
|
|
found, err = tryDialWithSNI(r.Context(), domain)
|
|
if err != nil {
|
|
logc.Printf(r.Context(), "caddy err: check SNI: %s\n", err)
|
|
}
|
|
}
|
|
|
|
if found {
|
|
logc.Println(r.Context(), "caddy:", domain, 200)
|
|
w.WriteHeader(http.StatusOK)
|
|
} else if err == nil {
|
|
logc.Println(r.Context(), "caddy:", domain, 404)
|
|
w.WriteHeader(http.StatusNotFound)
|
|
} else {
|
|
logc.Println(r.Context(), "caddy:", domain, 500)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
fmt.Fprintln(w, err)
|
|
}
|
|
}
|
|
|
|
func tryDialWithSNI(ctx context.Context, domain string) (bool, error) {
|
|
if config.Fallback.ProxyTo == nil {
|
|
return false, nil
|
|
}
|
|
|
|
fallbackURL := config.Fallback.ProxyTo
|
|
if fallbackURL.Scheme != "https" {
|
|
return false, nil
|
|
}
|
|
|
|
connectHost := fallbackURL.Host
|
|
if fallbackURL.Port() != "" {
|
|
connectHost += ":" + fallbackURL.Port()
|
|
} else {
|
|
connectHost += ":443"
|
|
}
|
|
|
|
logc.Printf(ctx, "caddy: check TLS %s", fallbackURL)
|
|
connection, err := tls.Dial("tcp", connectHost, &tls.Config{ServerName: domain})
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
connection.Close()
|
|
return true, nil
|
|
}
|