diff --git a/Justfile b/Justfile index 9ee7eb3..0b8f814 100644 --- a/Justfile +++ b/Justfile @@ -9,6 +9,8 @@ dev: export PAGES_DOMAIN=localhost.mock.directory export RAW_DOMAIN=raw.localhost.mock.directory export PORT=4430 + export HTTP_PORT=8880 + export ENABLE_HTTP_SERVER=true export LOG_LEVEL=trace go run -tags '{{TAGS}}' . diff --git a/cmd/main.go b/cmd/main.go index 8a65d43..aa00f54 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -48,7 +48,8 @@ func Serve(ctx *cli.Context) error { mainDomainSuffix := ctx.String("pages-domain") rawInfoPage := ctx.String("raw-info-page") listeningHost := ctx.String("host") - listeningSSLAddress := fmt.Sprintf("%s:%d", listeningHost, ctx.Uint("port")) + listeningSSLPort := ctx.Uint("port") + listeningSSLAddress := fmt.Sprintf("%s:%d", listeningHost, listeningSSLPort) listeningHTTPAddress := fmt.Sprintf("%s:%d", listeningHost, ctx.Uint("http-port")) enableHTTPServer := ctx.Bool("enable-http-server") @@ -93,7 +94,7 @@ func Serve(ctx *cli.Context) error { } // Create listener for SSL connections - log.Info().Msgf("Listening on https://%s", listeningSSLAddress) + log.Info().Msgf("Create TCP listener for SSL on %s", listeningSSLAddress) listener, err := net.Listen("tcp", listeningSSLAddress) if err != nil { return fmt.Errorf("couldn't create listener: %v", err) @@ -113,7 +114,7 @@ func Serve(ctx *cli.Context) error { if enableHTTPServer { // Create handler for http->https redirect and http acme challenges - httpHandler := certificates.SetupHTTPACMEChallengeServer(challengeCache) + httpHandler := certificates.SetupHTTPACMEChallengeServer(challengeCache, listeningSSLPort) // Create listener for http and start listening go func() { @@ -133,7 +134,7 @@ func Serve(ctx *cli.Context) error { dnsLookupCache, canonicalDomainCache) // Start the ssl listener - log.Info().Msgf("Start listening on %s", listener.Addr()) + log.Info().Msgf("Start SSL server using TCP listener on %s", listener.Addr()) if err := http.Serve(listener, sslHandler); err != nil { log.Panic().Err(err).Msg("Couldn't start fastServer") } diff --git a/integration/get_test.go b/integration/get_test.go index b70eeed..3a7190a 100644 --- a/integration/get_test.go +++ b/integration/get_test.go @@ -193,6 +193,18 @@ func TestGetOptions(t *testing.T) { assert.EqualValues(t, "GET, HEAD, OPTIONS", resp.Header.Get("Allow")) } +func TestHttpRedirect(t *testing.T) { + log.Println("=== TestHttpRedirect ===") + resp, err := getTestHTTPSClient().Get("http://mock-pages.codeberg-test.org:8880/README.md") + assert.NoError(t, err) + if !assert.NotNil(t, resp) { + t.FailNow() + } + assert.EqualValues(t, http.StatusMovedPermanently, resp.StatusCode) + assert.EqualValues(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type")) + assert.EqualValues(t, "https://mock-pages.codeberg-test.org:4430/README.md", resp.Header.Get("Location")) +} + func getTestHTTPSClient() *http.Client { cookieJar, _ := cookiejar.New(nil) return &http.Client{ diff --git a/integration/main_test.go b/integration/main_test.go index 3e0e187..a6579fd 100644 --- a/integration/main_test.go +++ b/integration/main_test.go @@ -40,6 +40,8 @@ func startServer(ctx context.Context) error { setEnvIfNotSet("PAGES_DOMAIN", "localhost.mock.directory") setEnvIfNotSet("RAW_DOMAIN", "raw.localhost.mock.directory") setEnvIfNotSet("PORT", "4430") + setEnvIfNotSet("HTTP_PORT", "8880") + setEnvIfNotSet("ENABLE_HTTP_SERVER", "true") setEnvIfNotSet("DB_TYPE", "sqlite3") app := cli.NewApp() diff --git a/server/certificates/cached_challengers.go b/server/certificates/cached_challengers.go index 02474b3..bc9ea67 100644 --- a/server/certificates/cached_challengers.go +++ b/server/certificates/cached_challengers.go @@ -1,15 +1,17 @@ package certificates import ( + "fmt" "net/http" + "net/url" "strings" "time" "github.com/go-acme/lego/v4/challenge" + "github.com/rs/zerolog/log" "codeberg.org/codeberg/pages/server/cache" "codeberg.org/codeberg/pages/server/context" - "codeberg.org/codeberg/pages/server/utils" ) type AcmeTLSChallengeProvider struct { @@ -44,17 +46,38 @@ func (a AcmeHTTPChallengeProvider) CleanUp(domain, token, _ string) error { return nil } -func SetupHTTPACMEChallengeServer(challengeCache cache.SetGetKey) http.HandlerFunc { +func SetupHTTPACMEChallengeServer(challengeCache cache.SetGetKey, sslPort uint) http.HandlerFunc { + // handle custom-ssl-ports to be added on https redirects + portPart := "" + if sslPort != 443 { + portPart = fmt.Sprintf(":%d", sslPort) + } + return func(w http.ResponseWriter, req *http.Request) { ctx := context.New(w, req) + domain := ctx.TrimHostPort() + + // it's an acme request if strings.HasPrefix(ctx.Path(), challengePath) { - challenge, ok := challengeCache.Get(utils.TrimHostPort(ctx.Host()) + "/" + strings.TrimPrefix(ctx.Path(), challengePath)) + challenge, ok := challengeCache.Get(domain + "/" + strings.TrimPrefix(ctx.Path(), challengePath)) if !ok || challenge == nil { + log.Info().Msgf("HTTP-ACME challenge for '%s' failed: token not found", domain) ctx.String("no challenge for this token", http.StatusNotFound) } + log.Info().Msgf("HTTP-ACME challenge for '%s' succeeded", domain) ctx.String(challenge.(string)) - } else { - ctx.Redirect("https://"+ctx.Host()+ctx.Path(), http.StatusMovedPermanently) + return } + + // it's a normal http request that needs to be redirected + u, err := url.Parse(fmt.Sprintf("https://%s%s%s", domain, portPart, ctx.Path())) + if err != nil { + log.Error().Err(err).Msg("could not craft http to https redirect") + ctx.String("", http.StatusInternalServerError) + } + + newURL := u.String() + log.Debug().Msgf("redirect http to https: %s", newURL) + ctx.Redirect(newURL, http.StatusMovedPermanently) } } diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go index 707672c..ce1420d 100644 --- a/server/certificates/certificates.go +++ b/server/certificates/certificates.go @@ -47,6 +47,7 @@ func TLSConfig(mainDomainSuffix string, if proto != tlsalpn01.ACMETLS1Protocol { continue } + log.Info().Msgf("Detect ACME-TLS1 challenge for '%s'", domain) challenge, ok := challengeCache.Get(domain) if !ok {