diff --git a/conf/config.example.toml b/conf/config.example.toml index 135fa2a..550ce0d 100644 --- a/conf/config.example.toml +++ b/conf/config.example.toml @@ -16,7 +16,10 @@ clone-url = "https://codeberg.org//.git" index-repos = [".codeberg.page", "pages"] index-repo-branch = "main" authorization = "forgejo" -fallback-proxy-to = "https://codeberg.page" + +[fallback] # non-default section +proxy-to = "https://codeberg.page" +insecure = false [storage] type = "fs" @@ -24,7 +27,7 @@ type = "fs" [storage.fs] root = "./data" -[storage.s3] # non-default bucket configuration +[storage.s3] # non-default section endpoint = "play.min.io" access-key-id = "Q3AM3UQ867SPQQA43P2F" secret-access-key = "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG" diff --git a/src/caddy.go b/src/caddy.go index 3c49f0c..74761c5 100644 --- a/src/caddy.go +++ b/src/caddy.go @@ -1,6 +1,7 @@ package git_pages import ( + "context" "crypto/tls" "fmt" "net" @@ -34,31 +35,9 @@ func ServeCaddy(w http.ResponseWriter, r *http.Request) { // 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.) - for _, wildcardConfig := range config.Wildcard { - if wildcardConfig.FallbackProxyTo == "" { - continue - } - fallbackURL, err := url.Parse(wildcardConfig.FallbackProxyTo) - if err != nil { - continue - } - if fallbackURL.Scheme != "https" { - continue - } - connectHost := fallbackURL.Host - if fallbackURL.Port() != "" { - connectHost += ":" + fallbackURL.Port() - } else { - connectHost += ":443" - } - logc.Printf(r.Context(), "caddy: check TLS %s", fallbackURL) - connection, err := tls.Dial("tcp", connectHost, &tls.Config{ServerName: domain}) - if err != nil { - continue - } - connection.Close() - found = true - break + found, err = tryDialWithSNI(r.Context(), domain) + if err != nil { + logc.Printf(r.Context(), "caddy err: check SNI: %s\n", err) } } @@ -74,3 +53,32 @@ func ServeCaddy(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, err) } } + +func tryDialWithSNI(ctx context.Context, domain string) (bool, error) { + if config.Fallback.ProxyTo == "" { + return false, nil + } + + fallbackURL, err := url.Parse(config.Fallback.ProxyTo) + if err != nil { + return false, err + } + 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 +} diff --git a/src/config.go b/src/config.go index 0a7b9f9..b4db8da 100644 --- a/src/config.go +++ b/src/config.go @@ -41,6 +41,7 @@ type Config struct { LogLevel string `toml:"log-level" default:"info"` Server ServerConfig `toml:"server"` Wildcard []WildcardConfig `toml:"wildcard"` + Fallback FallbackConfig `toml:"fallback"` Storage StorageConfig `toml:"storage"` Limits LimitsConfig `toml:"limits"` Observability ObservabilityConfig `toml:"observability"` @@ -53,13 +54,16 @@ type ServerConfig struct { } type WildcardConfig struct { - Domain string `toml:"domain"` - CloneURL string `toml:"clone-url"` - IndexRepos []string `toml:"index-repos" default:"[]"` - IndexRepoBranch string `toml:"index-repo-branch" default:"pages"` - Authorization string `toml:"authorization"` - FallbackProxyTo string `toml:"fallback-proxy-to"` - FallbackInsecure bool `toml:"fallback-insecure"` + Domain string `toml:"domain"` + CloneURL string `toml:"clone-url"` + IndexRepos []string `toml:"index-repos" default:"[]"` + IndexRepoBranch string `toml:"index-repo-branch" default:"pages"` + Authorization string `toml:"authorization"` +} + +type FallbackConfig struct { + ProxyTo string `toml:"proxy-to"` + Insecure bool `toml:"insecure"` } type CacheConfig struct { diff --git a/src/main.go b/src/main.go index 843cf1f..5a26d45 100644 --- a/src/main.go +++ b/src/main.go @@ -2,6 +2,7 @@ package git_pages import ( "context" + "crypto/tls" "errors" "flag" "fmt" @@ -10,6 +11,7 @@ import ( "log/slog" "net" "net/http" + "net/http/httputil" "net/url" "os" "runtime/debug" @@ -22,6 +24,7 @@ import ( var config *Config var wildcards []*WildcardPattern +var fallback http.Handler var backend Backend func configureFeatures() (err error) { @@ -63,6 +66,31 @@ func configureWildcards() (err error) { } } +func configureFallback() (err error) { + if config.Fallback.ProxyTo != "" { + var fallbackURL *url.URL + fallbackURL, err = url.Parse(config.Fallback.ProxyTo) + if err != nil { + err = fmt.Errorf("fallback: %w", err) + return + } + + fallback = &httputil.ReverseProxy{ + Rewrite: func(r *httputil.ProxyRequest) { + r.SetURL(fallbackURL) + r.Out.Host = r.In.Host + r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"] + }, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: config.Fallback.Insecure, + }, + }, + } + } + return +} + func listen(name string, listen string) net.Listener { if listen == "-" { return nil @@ -230,6 +258,7 @@ func Main() { configureFeatures(), configureMemLimit(), configureWildcards(), + configureFallback(), ); err != nil { log.Fatalln(err) } @@ -392,6 +421,7 @@ func Main() { configureFeatures(), configureMemLimit(), configureWildcards(), + configureFallback(), ); err != nil { // At this point the configuration is in an in-between, corrupted state, so // the only reasonable choice is to crash. diff --git a/src/pages.go b/src/pages.go index 6fa5b7e..04a2762 100644 --- a/src/pages.go +++ b/src/pages.go @@ -136,8 +136,10 @@ func getPage(w http.ResponseWriter, r *http.Request) error { result := <-indexManifestCh manifest, manifestMtime, err = result.manifest, result.manifestMtime, result.err if manifest == nil && errors.Is(err, ErrObjectNotFound) { - if found, fallbackErr := HandleWildcardFallback(w, r); found { - return fallbackErr + if fallback != nil { + logc.Printf(r.Context(), "fallback: %s via %s", host, config.Fallback.ProxyTo) + fallback.ServeHTTP(w, r) + return nil } else { w.WriteHeader(http.StatusNotFound) fmt.Fprintf(w, "site not found\n") diff --git a/src/wildcard.go b/src/wildcard.go index f23ca0b..66feb92 100644 --- a/src/wildcard.go +++ b/src/wildcard.go @@ -1,11 +1,7 @@ package git_pages import ( - "crypto/tls" "fmt" - "net/http" - "net/http/httputil" - "net/url" "slices" "strings" @@ -18,8 +14,6 @@ type WildcardPattern struct { IndexRepos []*fasttemplate.Template IndexBranch string Authorization bool - FallbackURL *url.URL - Fallback http.Handler } func (pattern *WildcardPattern) GetHost() string { @@ -78,30 +72,6 @@ func (pattern *WildcardPattern) ApplyTemplate(userName string, projectName strin return repoURLs, branch } -func (pattern *WildcardPattern) IsFallbackFor(host string) bool { - if pattern.Fallback == nil { - return false - } - _, found := pattern.Matches(host) - return found -} - -func HandleWildcardFallback(w http.ResponseWriter, r *http.Request) (bool, error) { - host, err := GetHost(r) - if err != nil { - return false, err - } - - for _, pattern := range wildcards { - if pattern.IsFallbackFor(host) { - logc.Printf(r.Context(), "proxy: %s via %s", pattern.GetHost(), pattern.FallbackURL) - pattern.Fallback.ServeHTTP(w, r) - return true, nil - } - } - return false, nil -} - func TranslateWildcards(configs []WildcardConfig) ([]*WildcardPattern, error) { var wildcardPatterns []*WildcardPattern for _, config := range configs { @@ -134,36 +104,12 @@ func TranslateWildcards(configs []WildcardConfig) ([]*WildcardPattern, error) { } } - var fallbackURL *url.URL - var fallback http.Handler - if config.FallbackProxyTo != "" { - fallbackURL, err = url.Parse(config.FallbackProxyTo) - if err != nil { - return nil, fmt.Errorf("wildcard pattern: fallback URL: %w", err) - } - - fallback = &httputil.ReverseProxy{ - Rewrite: func(r *httputil.ProxyRequest) { - r.SetURL(fallbackURL) - r.Out.Host = r.In.Host - r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"] - }, - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: config.FallbackInsecure, - }, - }, - } - } - wildcardPatterns = append(wildcardPatterns, &WildcardPattern{ Domain: strings.Split(config.Domain, "."), CloneURL: cloneURLTemplate, IndexRepos: indexRepoTemplates, IndexBranch: indexRepoBranch, Authorization: authorization, - FallbackURL: fallbackURL, - Fallback: fallback, }) } return wildcardPatterns, nil