diff --git a/cli/setup.go b/cli/setup.go index 878e3de..6bbff40 100644 --- a/cli/setup.go +++ b/cli/setup.go @@ -1,20 +1,15 @@ package cli import ( - "errors" "fmt" "github.com/rs/zerolog/log" "github.com/urfave/cli/v2" - "codeberg.org/codeberg/pages/server/cache" - "codeberg.org/codeberg/pages/server/certificates" "codeberg.org/codeberg/pages/server/database" "codeberg.org/codeberg/pages/server/version" ) -var ErrAcmeMissConfig = errors.New("ACME client has wrong config") - func CreatePagesApp() *cli.App { app := cli.NewApp() app.Name = "pages-server" @@ -42,37 +37,3 @@ func OpenCertDB(ctx *cli.Context) (certDB database.CertDB, closeFn func(), err e return certDB, closeFn, nil } - -func CreateAcmeClient(ctx *cli.Context, enableHTTPServer bool, challengeCache cache.ICache) (*certificates.AcmeClient, error) { - acmeAPI := ctx.String("acme-api-endpoint") - acmeMail := ctx.String("acme-email") - acmeEabHmac := ctx.String("acme-eab-hmac") - acmeEabKID := ctx.String("acme-eab-kid") - acmeAcceptTerms := ctx.Bool("acme-accept-terms") - dnsProvider := ctx.String("dns-provider") - acmeUseRateLimits := ctx.Bool("acme-use-rate-limits") - acmeAccountConf := ctx.String("acme-account-config") - - // check config - if (!acmeAcceptTerms || dnsProvider == "") && acmeAPI != "https://acme.mock.directory" { - return nil, fmt.Errorf("%w: you must set $ACME_ACCEPT_TERMS and $DNS_PROVIDER, unless $ACME_API is set to https://acme.mock.directory", ErrAcmeMissConfig) - } - if acmeEabHmac != "" && acmeEabKID == "" { - return nil, fmt.Errorf("%w: ACME_EAB_HMAC also needs ACME_EAB_KID to be set", ErrAcmeMissConfig) - } else if acmeEabHmac == "" && acmeEabKID != "" { - return nil, fmt.Errorf("%w: ACME_EAB_KID also needs ACME_EAB_HMAC to be set", ErrAcmeMissConfig) - } - - return certificates.NewAcmeClient( - acmeAccountConf, - acmeAPI, - acmeMail, - acmeEabHmac, - acmeEabKID, - dnsProvider, - acmeAcceptTerms, - enableHTTPServer, - acmeUseRateLimits, - challengeCache, - ) -} diff --git a/config/config.go b/config/config.go index 48080f6..0647869 100644 --- a/config/config.go +++ b/config/config.go @@ -15,15 +15,18 @@ type ServerConfig struct { HttpServerEnabled bool MainDomain string RawDomain string + DefaultBranches []string AllowedCorsDomains []string BlacklistedPaths []string } type GiteaConfig struct { - Root string - Token string - LFSEnabled bool - FollowSymlinks bool + Root string + Token string + LFSEnabled bool + FollowSymlinks bool + DefaultMimeType string + ForbiddenMimeTypes []string } type DatabaseConfig struct { diff --git a/config/program.go b/config/program.go deleted file mode 100644 index 34bf633..0000000 --- a/config/program.go +++ /dev/null @@ -1,32 +0,0 @@ -package config - -import ( - "codeberg.org/codeberg/pages/server/cache" - "codeberg.org/codeberg/pages/server/certificates" - "codeberg.org/codeberg/pages/server/database" - "codeberg.org/codeberg/pages/server/gitea" -) - -type HandlerConfig struct { - mainDomainSuffix string - rawDomain string - giteaClient *gitea.Client - blacklistedPaths []string - allowedCorsDomains []string - defaultBranches []string - dnsLookupCache cache.ICache - canonicalDomainCache cache.ICache - redirectsCache cache.ICache -} - -type TLSConfig struct { - mainDomainSuffix string - firstDefaultBranch string - giteaClient *gitea.Client - acmeClient *certificates.AcmeClient - keyCache cache.ICache - challengeCache cache.ICache - dnsLookupCache cache.ICache - canonicalDomainCache cache.ICache - certDB database.CertDB -} diff --git a/main.go b/main.go index c360053..2ceb645 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ import ( cmd "codeberg.org/codeberg/pages/cli" "codeberg.org/codeberg/pages/config" + "codeberg.org/codeberg/pages/server/acme" "codeberg.org/codeberg/pages/server/cache" "codeberg.org/codeberg/pages/server/certificates" "codeberg.org/codeberg/pages/server/gitea" @@ -51,28 +52,20 @@ func Serve(ctx *cli.Context) error { } log.Logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).With().Timestamp().Logger().Level(logLevel) - giteaRoot := ctx.String("gitea-root") - giteaAPIToken := ctx.String("gitea-api-token") - rawDomain := ctx.String("raw-domain") - defaultBranches := ctx.StringSlice("pages-branch") - mainDomainSuffix := ctx.String("pages-domain") - listeningHost := ctx.String("host") - 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") + listeningSSLAddress := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port) + listeningHTTPAddress := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.HttpPort) - allowedCorsDomains := cfg.Server.AllowedCorsDomains - if rawDomain != "" { - allowedCorsDomains = append(allowedCorsDomains, rawDomain) + if cfg.Server.RawDomain != "" { + cfg.Server.AllowedCorsDomains = append(cfg.Server.AllowedCorsDomains, cfg.Server.RawDomain) } - // Make sure MainDomain has a trailing dot - if !strings.HasPrefix(mainDomainSuffix, ".") { - mainDomainSuffix = "." + mainDomainSuffix + // Make sure MainDomain has a leading dot + if !strings.HasPrefix(cfg.Server.MainDomain, ".") { + // TODO make this better + cfg.Server.MainDomain = "." + cfg.Server.MainDomain } - if len(defaultBranches) == 0 { + if len(cfg.Server.DefaultBranches) == 0 { return fmt.Errorf("no default branches set (PAGES_BRANCHES)") } @@ -94,17 +87,17 @@ func Serve(ctx *cli.Context) error { // clientResponseCache stores responses from the Gitea server clientResponseCache := cache.NewInMemoryCache() - giteaClient, err := gitea.NewClient(giteaRoot, giteaAPIToken, clientResponseCache, ctx.Bool("enable-symlink-support"), ctx.Bool("enable-lfs-support")) + giteaClient, err := gitea.NewClient(cfg.Gitea, clientResponseCache) if err != nil { return fmt.Errorf("could not create new gitea client: %v", err) } - acmeClient, err := cmd.CreateAcmeClient(ctx, enableHTTPServer, challengeCache) + acmeClient, err := acme.CreateAcmeClient(cfg.ACME, cfg.Server.HttpServerEnabled, challengeCache) if err != nil { return err } - if err := certificates.SetupMainDomainCertificates(mainDomainSuffix, acmeClient, certDB); err != nil { + if err := certificates.SetupMainDomainCertificates(cfg.Server.MainDomain, acmeClient, certDB); err != nil { return err } @@ -116,21 +109,23 @@ func Serve(ctx *cli.Context) error { } // Setup listener for SSL connections - listener = tls.NewListener(listener, certificates.TLSConfig(mainDomainSuffix, + listener = tls.NewListener(listener, certificates.TLSConfig( + cfg.Server.MainDomain, giteaClient, acmeClient, - defaultBranches[0], + cfg.Server.DefaultBranches[0], keyCache, challengeCache, dnsLookupCache, canonicalDomainCache, - certDB)) + certDB, + )) interval := 12 * time.Hour certMaintainCtx, cancelCertMaintain := context.WithCancel(context.Background()) defer cancelCertMaintain() - go certificates.MaintainCertDB(certMaintainCtx, interval, acmeClient, mainDomainSuffix, certDB) + go certificates.MaintainCertDB(certMaintainCtx, interval, acmeClient, cfg.Server.MainDomain, certDB) - if enableHTTPServer { + if cfg.Server.HttpServerEnabled { // Create handler for http->https redirect and http acme challenges - httpHandler := certificates.SetupHTTPACMEChallengeServer(challengeCache, listeningSSLPort) + httpHandler := certificates.SetupHTTPACMEChallengeServer(challengeCache, uint(cfg.Server.Port)) // Create listener for http and start listening go func() { @@ -143,11 +138,7 @@ func Serve(ctx *cli.Context) error { } // Create ssl handler based on settings - sslHandler := handler.Handler(mainDomainSuffix, rawDomain, - giteaClient, - cfg.Server.BlacklistedPaths, allowedCorsDomains, - defaultBranches, - dnsLookupCache, canonicalDomainCache, redirectsCache) + sslHandler := handler.Handler(cfg.Server, giteaClient, dnsLookupCache, canonicalDomainCache, redirectsCache) // Start the ssl listener log.Info().Msgf("Start SSL server using TCP listener on %s", listener.Addr()) diff --git a/server/acme/client.go b/server/acme/client.go new file mode 100644 index 0000000..e9684c7 --- /dev/null +++ b/server/acme/client.go @@ -0,0 +1,37 @@ +package acme + +import ( + "errors" + "fmt" + + "codeberg.org/codeberg/pages/config" + "codeberg.org/codeberg/pages/server/cache" + "codeberg.org/codeberg/pages/server/certificates" +) + +var ErrAcmeMissConfig = errors.New("ACME client has wrong config") + +func CreateAcmeClient(cfg config.ACMEConfig, enableHTTPServer bool, challengeCache cache.ICache) (*certificates.AcmeClient, error) { + // check config + if (!cfg.AcceptTerms || cfg.DNSProvider == "") && cfg.APIEndpoint != "https://acme.mock.directory" { + return nil, fmt.Errorf("%w: you must set $ACME_ACCEPT_TERMS and $DNS_PROVIDER, unless $ACME_API is set to https://acme.mock.directory", ErrAcmeMissConfig) + } + if cfg.EAB_HMAC != "" && cfg.EAB_KID == "" { + return nil, fmt.Errorf("%w: ACME_EAB_HMAC also needs ACME_EAB_KID to be set", ErrAcmeMissConfig) + } else if cfg.EAB_HMAC == "" && cfg.EAB_KID != "" { + return nil, fmt.Errorf("%w: ACME_EAB_KID also needs ACME_EAB_HMAC to be set", ErrAcmeMissConfig) + } + + return certificates.NewAcmeClient( + cfg.AccountConfigFile, + cfg.APIEndpoint, + cfg.Email, + cfg.EAB_HMAC, + cfg.EAB_KID, + cfg.DNSProvider, + cfg.AcceptTerms, + enableHTTPServer, + cfg.UseRateLimits, + challengeCache, + ) +} diff --git a/server/gitea/client.go b/server/gitea/client.go index 87e46eb..42cf065 100644 --- a/server/gitea/client.go +++ b/server/gitea/client.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/sdk/gitea" "github.com/rs/zerolog/log" + "codeberg.org/codeberg/pages/config" "codeberg.org/codeberg/pages/server/cache" "codeberg.org/codeberg/pages/server/version" ) @@ -55,24 +56,21 @@ type Client struct { defaultMimeType string } -func NewClient(giteaRoot, giteaAPIToken string, respCache cache.ICache, followSymlinks, supportLFS bool) (*Client, error) { - rootURL, err := url.Parse(giteaRoot) +func NewClient(cfg config.GiteaConfig, respCache cache.ICache) (*Client, error) { + rootURL, err := url.Parse(cfg.Root) if err != nil { return nil, err } - giteaRoot = strings.Trim(rootURL.String(), "/") + giteaRoot := strings.Trim(rootURL.String(), "/") stdClient := http.Client{Timeout: 10 * time.Second} - // TODO: pass down - var ( - forbiddenMimeTypes map[string]bool - defaultMimeType string - ) - - if forbiddenMimeTypes == nil { - forbiddenMimeTypes = make(map[string]bool) + forbiddenMimeTypes := make(map[string]bool, len(cfg.ForbiddenMimeTypes)) + for _, mimeType := range cfg.ForbiddenMimeTypes { + forbiddenMimeTypes[mimeType] = true } + + defaultMimeType := cfg.DefaultMimeType if defaultMimeType == "" { defaultMimeType = "application/octet-stream" } @@ -80,7 +78,7 @@ func NewClient(giteaRoot, giteaAPIToken string, respCache cache.ICache, followSy sdk, err := gitea.NewClient( giteaRoot, gitea.SetHTTPClient(&stdClient), - gitea.SetToken(giteaAPIToken), + gitea.SetToken(cfg.Token), gitea.SetUserAgent("pages-server/"+version.Version), ) @@ -90,8 +88,8 @@ func NewClient(giteaRoot, giteaAPIToken string, respCache cache.ICache, followSy giteaRoot: giteaRoot, - followSymlinks: followSymlinks, - supportLFS: supportLFS, + followSymlinks: cfg.FollowSymlinks, + supportLFS: cfg.LFSEnabled, forbiddenMimeTypes: forbiddenMimeTypes, defaultMimeType: defaultMimeType, diff --git a/server/handler/handler.go b/server/handler/handler.go index a5b47ba..6bbed5d 100644 --- a/server/handler/handler.go +++ b/server/handler/handler.go @@ -6,6 +6,7 @@ import ( "github.com/rs/zerolog/log" + "codeberg.org/codeberg/pages/config" "codeberg.org/codeberg/pages/html" "codeberg.org/codeberg/pages/server/cache" "codeberg.org/codeberg/pages/server/context" @@ -19,10 +20,9 @@ const ( ) // Handler handles a single HTTP request to the web server. -func Handler(mainDomainSuffix, rawDomain string, +func Handler( + cfg config.ServerConfig, giteaClient *gitea.Client, - blacklistedPaths, allowedCorsDomains []string, - defaultPagesBranches []string, dnsLookupCache, canonicalDomainCache, redirectsCache cache.ICache, ) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { @@ -39,8 +39,8 @@ func Handler(mainDomainSuffix, rawDomain string, trimmedHost := ctx.TrimHostPort() - // Add HSTS for RawDomain and MainDomainSuffix - if hsts := getHSTSHeader(trimmedHost, mainDomainSuffix, rawDomain); hsts != "" { + // Add HSTS for RawDomain and MainDomain + if hsts := getHSTSHeader(trimmedHost, cfg.MainDomain, cfg.RawDomain); hsts != "" { ctx.RespWriter.Header().Set("Strict-Transport-Security", hsts) } @@ -62,7 +62,7 @@ func Handler(mainDomainSuffix, rawDomain string, } // Block blacklisted paths (like ACME challenges) - for _, blacklistedPath := range blacklistedPaths { + for _, blacklistedPath := range cfg.BlacklistedPaths { if strings.HasPrefix(ctx.Path(), blacklistedPath) { html.ReturnErrorPage(ctx, "requested path is blacklisted", http.StatusForbidden) return @@ -71,7 +71,7 @@ func Handler(mainDomainSuffix, rawDomain string, // Allow CORS for specified domains allowCors := false - for _, allowedCorsDomain := range allowedCorsDomains { + for _, allowedCorsDomain := range cfg.AllowedCorsDomains { if strings.EqualFold(trimmedHost, allowedCorsDomain) { allowCors = true break @@ -85,28 +85,28 @@ func Handler(mainDomainSuffix, rawDomain string, // Prepare request information to Gitea pathElements := strings.Split(strings.Trim(ctx.Path(), "/"), "/") - if rawDomain != "" && strings.EqualFold(trimmedHost, rawDomain) { + if cfg.RawDomain != "" && strings.EqualFold(trimmedHost, cfg.RawDomain) { log.Debug().Msg("raw domain request detected") handleRaw(log, ctx, giteaClient, - mainDomainSuffix, + cfg.MainDomain, trimmedHost, pathElements, canonicalDomainCache, redirectsCache) - } else if strings.HasSuffix(trimmedHost, mainDomainSuffix) { + } else if strings.HasSuffix(trimmedHost, cfg.MainDomain) { log.Debug().Msg("subdomain request detected") handleSubDomain(log, ctx, giteaClient, - mainDomainSuffix, - defaultPagesBranches, + cfg.MainDomain, + cfg.DefaultBranches, trimmedHost, pathElements, canonicalDomainCache, redirectsCache) } else { log.Debug().Msg("custom domain request detected") handleCustomDomain(log, ctx, giteaClient, - mainDomainSuffix, + cfg.MainDomain, trimmedHost, pathElements, - defaultPagesBranches[0], + cfg.DefaultBranches[0], dnsLookupCache, canonicalDomainCache, redirectsCache) } } diff --git a/server/handler/handler_test.go b/server/handler/handler_test.go index 0079000..daa799a 100644 --- a/server/handler/handler_test.go +++ b/server/handler/handler_test.go @@ -6,23 +6,30 @@ import ( "testing" "time" + "codeberg.org/codeberg/pages/config" "codeberg.org/codeberg/pages/server/cache" "codeberg.org/codeberg/pages/server/gitea" "github.com/rs/zerolog/log" ) func TestHandlerPerformance(t *testing.T) { - giteaClient, _ := gitea.NewClient("https://codeberg.org", "", cache.NewInMemoryCache(), false, false) - testHandler := Handler( - "codeberg.page", "raw.codeberg.org", - giteaClient, - []string{"/.well-known/acme-challenge/"}, - []string{"raw.codeberg.org", "fonts.codeberg.org", "design.codeberg.org"}, - []string{"pages"}, - cache.NewInMemoryCache(), - cache.NewInMemoryCache(), - cache.NewInMemoryCache(), - ) + cfg := config.GiteaConfig{ + Root: "https://codeberg.org", + Token: "", + LFSEnabled: false, + FollowSymlinks: false, + } + giteaClient, _ := gitea.NewClient(cfg, cache.NewInMemoryCache()) + serverCfg := config.ServerConfig{ + MainDomain: "codeberg.page", + RawDomain: "raw.codeberg.page", + BlacklistedPaths: []string{ + "/.well-known/acme-challenge/", + }, + AllowedCorsDomains: []string{"raw.codeberg.org", "fonts.codeberg.org", "design.codeberg.org"}, + DefaultBranches: []string{"pages"}, + } + testHandler := Handler(serverCfg, giteaClient, cache.NewInMemoryCache(), cache.NewInMemoryCache(), cache.NewInMemoryCache()) testCase := func(uri string, status int) { t.Run(uri, func(t *testing.T) {