use new config structs for passing config down

This commit is contained in:
crapStone 2023-11-17 22:09:28 +01:00 committed by crapStone
parent fdbbc17cca
commit 81e980ce13
8 changed files with 110 additions and 145 deletions

View File

@ -1,20 +1,15 @@
package cli package cli
import ( import (
"errors"
"fmt" "fmt"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/urfave/cli/v2" "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/database"
"codeberg.org/codeberg/pages/server/version" "codeberg.org/codeberg/pages/server/version"
) )
var ErrAcmeMissConfig = errors.New("ACME client has wrong config")
func CreatePagesApp() *cli.App { func CreatePagesApp() *cli.App {
app := cli.NewApp() app := cli.NewApp()
app.Name = "pages-server" app.Name = "pages-server"
@ -42,37 +37,3 @@ func OpenCertDB(ctx *cli.Context) (certDB database.CertDB, closeFn func(), err e
return certDB, closeFn, nil 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,
)
}

View File

@ -15,15 +15,18 @@ type ServerConfig struct {
HttpServerEnabled bool HttpServerEnabled bool
MainDomain string MainDomain string
RawDomain string RawDomain string
DefaultBranches []string
AllowedCorsDomains []string AllowedCorsDomains []string
BlacklistedPaths []string BlacklistedPaths []string
} }
type GiteaConfig struct { type GiteaConfig struct {
Root string Root string
Token string Token string
LFSEnabled bool LFSEnabled bool
FollowSymlinks bool FollowSymlinks bool
DefaultMimeType string
ForbiddenMimeTypes []string
} }
type DatabaseConfig struct { type DatabaseConfig struct {

View File

@ -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
}

53
main.go
View File

@ -17,6 +17,7 @@ import (
cmd "codeberg.org/codeberg/pages/cli" cmd "codeberg.org/codeberg/pages/cli"
"codeberg.org/codeberg/pages/config" "codeberg.org/codeberg/pages/config"
"codeberg.org/codeberg/pages/server/acme"
"codeberg.org/codeberg/pages/server/cache" "codeberg.org/codeberg/pages/server/cache"
"codeberg.org/codeberg/pages/server/certificates" "codeberg.org/codeberg/pages/server/certificates"
"codeberg.org/codeberg/pages/server/gitea" "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) log.Logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).With().Timestamp().Logger().Level(logLevel)
giteaRoot := ctx.String("gitea-root") listeningSSLAddress := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port)
giteaAPIToken := ctx.String("gitea-api-token") listeningHTTPAddress := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.HttpPort)
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")
allowedCorsDomains := cfg.Server.AllowedCorsDomains if cfg.Server.RawDomain != "" {
if rawDomain != "" { cfg.Server.AllowedCorsDomains = append(cfg.Server.AllowedCorsDomains, cfg.Server.RawDomain)
allowedCorsDomains = append(allowedCorsDomains, rawDomain)
} }
// Make sure MainDomain has a trailing dot // Make sure MainDomain has a leading dot
if !strings.HasPrefix(mainDomainSuffix, ".") { if !strings.HasPrefix(cfg.Server.MainDomain, ".") {
mainDomainSuffix = "." + mainDomainSuffix // 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)") 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 stores responses from the Gitea server
clientResponseCache := cache.NewInMemoryCache() 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 { if err != nil {
return fmt.Errorf("could not create new gitea client: %v", err) 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 { if err != nil {
return err return err
} }
if err := certificates.SetupMainDomainCertificates(mainDomainSuffix, acmeClient, certDB); err != nil { if err := certificates.SetupMainDomainCertificates(cfg.Server.MainDomain, acmeClient, certDB); err != nil {
return err return err
} }
@ -116,21 +109,23 @@ func Serve(ctx *cli.Context) error {
} }
// Setup listener for SSL connections // Setup listener for SSL connections
listener = tls.NewListener(listener, certificates.TLSConfig(mainDomainSuffix, listener = tls.NewListener(listener, certificates.TLSConfig(
cfg.Server.MainDomain,
giteaClient, giteaClient,
acmeClient, acmeClient,
defaultBranches[0], cfg.Server.DefaultBranches[0],
keyCache, challengeCache, dnsLookupCache, canonicalDomainCache, keyCache, challengeCache, dnsLookupCache, canonicalDomainCache,
certDB)) certDB,
))
interval := 12 * time.Hour interval := 12 * time.Hour
certMaintainCtx, cancelCertMaintain := context.WithCancel(context.Background()) certMaintainCtx, cancelCertMaintain := context.WithCancel(context.Background())
defer cancelCertMaintain() 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 // 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 // Create listener for http and start listening
go func() { go func() {
@ -143,11 +138,7 @@ func Serve(ctx *cli.Context) error {
} }
// Create ssl handler based on settings // Create ssl handler based on settings
sslHandler := handler.Handler(mainDomainSuffix, rawDomain, sslHandler := handler.Handler(cfg.Server, giteaClient, dnsLookupCache, canonicalDomainCache, redirectsCache)
giteaClient,
cfg.Server.BlacklistedPaths, allowedCorsDomains,
defaultBranches,
dnsLookupCache, canonicalDomainCache, redirectsCache)
// Start the ssl listener // Start the ssl listener
log.Info().Msgf("Start SSL server using TCP listener on %s", listener.Addr()) log.Info().Msgf("Start SSL server using TCP listener on %s", listener.Addr())

37
server/acme/client.go Normal file
View File

@ -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,
)
}

View File

@ -16,6 +16,7 @@ import (
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"codeberg.org/codeberg/pages/config"
"codeberg.org/codeberg/pages/server/cache" "codeberg.org/codeberg/pages/server/cache"
"codeberg.org/codeberg/pages/server/version" "codeberg.org/codeberg/pages/server/version"
) )
@ -55,24 +56,21 @@ type Client struct {
defaultMimeType string defaultMimeType string
} }
func NewClient(giteaRoot, giteaAPIToken string, respCache cache.ICache, followSymlinks, supportLFS bool) (*Client, error) { func NewClient(cfg config.GiteaConfig, respCache cache.ICache) (*Client, error) {
rootURL, err := url.Parse(giteaRoot) rootURL, err := url.Parse(cfg.Root)
if err != nil { if err != nil {
return nil, err return nil, err
} }
giteaRoot = strings.Trim(rootURL.String(), "/") giteaRoot := strings.Trim(rootURL.String(), "/")
stdClient := http.Client{Timeout: 10 * time.Second} stdClient := http.Client{Timeout: 10 * time.Second}
// TODO: pass down forbiddenMimeTypes := make(map[string]bool, len(cfg.ForbiddenMimeTypes))
var ( for _, mimeType := range cfg.ForbiddenMimeTypes {
forbiddenMimeTypes map[string]bool forbiddenMimeTypes[mimeType] = true
defaultMimeType string
)
if forbiddenMimeTypes == nil {
forbiddenMimeTypes = make(map[string]bool)
} }
defaultMimeType := cfg.DefaultMimeType
if defaultMimeType == "" { if defaultMimeType == "" {
defaultMimeType = "application/octet-stream" defaultMimeType = "application/octet-stream"
} }
@ -80,7 +78,7 @@ func NewClient(giteaRoot, giteaAPIToken string, respCache cache.ICache, followSy
sdk, err := gitea.NewClient( sdk, err := gitea.NewClient(
giteaRoot, giteaRoot,
gitea.SetHTTPClient(&stdClient), gitea.SetHTTPClient(&stdClient),
gitea.SetToken(giteaAPIToken), gitea.SetToken(cfg.Token),
gitea.SetUserAgent("pages-server/"+version.Version), gitea.SetUserAgent("pages-server/"+version.Version),
) )
@ -90,8 +88,8 @@ func NewClient(giteaRoot, giteaAPIToken string, respCache cache.ICache, followSy
giteaRoot: giteaRoot, giteaRoot: giteaRoot,
followSymlinks: followSymlinks, followSymlinks: cfg.FollowSymlinks,
supportLFS: supportLFS, supportLFS: cfg.LFSEnabled,
forbiddenMimeTypes: forbiddenMimeTypes, forbiddenMimeTypes: forbiddenMimeTypes,
defaultMimeType: defaultMimeType, defaultMimeType: defaultMimeType,

View File

@ -6,6 +6,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"codeberg.org/codeberg/pages/config"
"codeberg.org/codeberg/pages/html" "codeberg.org/codeberg/pages/html"
"codeberg.org/codeberg/pages/server/cache" "codeberg.org/codeberg/pages/server/cache"
"codeberg.org/codeberg/pages/server/context" "codeberg.org/codeberg/pages/server/context"
@ -19,10 +20,9 @@ const (
) )
// Handler handles a single HTTP request to the web server. // Handler handles a single HTTP request to the web server.
func Handler(mainDomainSuffix, rawDomain string, func Handler(
cfg config.ServerConfig,
giteaClient *gitea.Client, giteaClient *gitea.Client,
blacklistedPaths, allowedCorsDomains []string,
defaultPagesBranches []string,
dnsLookupCache, canonicalDomainCache, redirectsCache cache.ICache, dnsLookupCache, canonicalDomainCache, redirectsCache cache.ICache,
) http.HandlerFunc { ) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) { return func(w http.ResponseWriter, req *http.Request) {
@ -39,8 +39,8 @@ func Handler(mainDomainSuffix, rawDomain string,
trimmedHost := ctx.TrimHostPort() trimmedHost := ctx.TrimHostPort()
// Add HSTS for RawDomain and MainDomainSuffix // Add HSTS for RawDomain and MainDomain
if hsts := getHSTSHeader(trimmedHost, mainDomainSuffix, rawDomain); hsts != "" { if hsts := getHSTSHeader(trimmedHost, cfg.MainDomain, cfg.RawDomain); hsts != "" {
ctx.RespWriter.Header().Set("Strict-Transport-Security", hsts) ctx.RespWriter.Header().Set("Strict-Transport-Security", hsts)
} }
@ -62,7 +62,7 @@ func Handler(mainDomainSuffix, rawDomain string,
} }
// Block blacklisted paths (like ACME challenges) // Block blacklisted paths (like ACME challenges)
for _, blacklistedPath := range blacklistedPaths { for _, blacklistedPath := range cfg.BlacklistedPaths {
if strings.HasPrefix(ctx.Path(), blacklistedPath) { if strings.HasPrefix(ctx.Path(), blacklistedPath) {
html.ReturnErrorPage(ctx, "requested path is blacklisted", http.StatusForbidden) html.ReturnErrorPage(ctx, "requested path is blacklisted", http.StatusForbidden)
return return
@ -71,7 +71,7 @@ func Handler(mainDomainSuffix, rawDomain string,
// Allow CORS for specified domains // Allow CORS for specified domains
allowCors := false allowCors := false
for _, allowedCorsDomain := range allowedCorsDomains { for _, allowedCorsDomain := range cfg.AllowedCorsDomains {
if strings.EqualFold(trimmedHost, allowedCorsDomain) { if strings.EqualFold(trimmedHost, allowedCorsDomain) {
allowCors = true allowCors = true
break break
@ -85,28 +85,28 @@ func Handler(mainDomainSuffix, rawDomain string,
// Prepare request information to Gitea // Prepare request information to Gitea
pathElements := strings.Split(strings.Trim(ctx.Path(), "/"), "/") 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") log.Debug().Msg("raw domain request detected")
handleRaw(log, ctx, giteaClient, handleRaw(log, ctx, giteaClient,
mainDomainSuffix, cfg.MainDomain,
trimmedHost, trimmedHost,
pathElements, pathElements,
canonicalDomainCache, redirectsCache) canonicalDomainCache, redirectsCache)
} else if strings.HasSuffix(trimmedHost, mainDomainSuffix) { } else if strings.HasSuffix(trimmedHost, cfg.MainDomain) {
log.Debug().Msg("subdomain request detected") log.Debug().Msg("subdomain request detected")
handleSubDomain(log, ctx, giteaClient, handleSubDomain(log, ctx, giteaClient,
mainDomainSuffix, cfg.MainDomain,
defaultPagesBranches, cfg.DefaultBranches,
trimmedHost, trimmedHost,
pathElements, pathElements,
canonicalDomainCache, redirectsCache) canonicalDomainCache, redirectsCache)
} else { } else {
log.Debug().Msg("custom domain request detected") log.Debug().Msg("custom domain request detected")
handleCustomDomain(log, ctx, giteaClient, handleCustomDomain(log, ctx, giteaClient,
mainDomainSuffix, cfg.MainDomain,
trimmedHost, trimmedHost,
pathElements, pathElements,
defaultPagesBranches[0], cfg.DefaultBranches[0],
dnsLookupCache, canonicalDomainCache, redirectsCache) dnsLookupCache, canonicalDomainCache, redirectsCache)
} }
} }

View File

@ -6,23 +6,30 @@ import (
"testing" "testing"
"time" "time"
"codeberg.org/codeberg/pages/config"
"codeberg.org/codeberg/pages/server/cache" "codeberg.org/codeberg/pages/server/cache"
"codeberg.org/codeberg/pages/server/gitea" "codeberg.org/codeberg/pages/server/gitea"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
func TestHandlerPerformance(t *testing.T) { func TestHandlerPerformance(t *testing.T) {
giteaClient, _ := gitea.NewClient("https://codeberg.org", "", cache.NewInMemoryCache(), false, false) cfg := config.GiteaConfig{
testHandler := Handler( Root: "https://codeberg.org",
"codeberg.page", "raw.codeberg.org", Token: "",
giteaClient, LFSEnabled: false,
[]string{"/.well-known/acme-challenge/"}, FollowSymlinks: false,
[]string{"raw.codeberg.org", "fonts.codeberg.org", "design.codeberg.org"}, }
[]string{"pages"}, giteaClient, _ := gitea.NewClient(cfg, cache.NewInMemoryCache())
cache.NewInMemoryCache(), serverCfg := config.ServerConfig{
cache.NewInMemoryCache(), MainDomain: "codeberg.page",
cache.NewInMemoryCache(), 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) { testCase := func(uri string, status int) {
t.Run(uri, func(t *testing.T) { t.Run(uri, func(t *testing.T) {