diff options
Diffstat (limited to 'modules/caddyhttp/caddyhttp.go')
-rw-r--r-- | modules/caddyhttp/caddyhttp.go | 275 |
1 files changed, 31 insertions, 244 deletions
diff --git a/modules/caddyhttp/caddyhttp.go b/modules/caddyhttp/caddyhttp.go index 73c4863..37f9670 100644 --- a/modules/caddyhttp/caddyhttp.go +++ b/modules/caddyhttp/caddyhttp.go @@ -25,13 +25,10 @@ import ( "net" "net/http" "strconv" - "strings" "time" "github.com/caddyserver/caddy/v2" - "github.com/caddyserver/caddy/v2/modules/caddytls" "github.com/lucas-clemente/quic-go/http3" - "github.com/mholt/certmagic" "go.uber.org/zap" ) @@ -52,7 +49,7 @@ func init() { // only on the HTTPS port but which do not have any TLS connection policies // defined by adding a good, default TLS connection policy. // -// In HTTP routes, additional placeholders are available: +// In HTTP routes, additional placeholders are available (replace any `*`): // // Placeholder | Description // ------------|--------------- @@ -127,6 +124,14 @@ func (app *App) Provision(ctx caddy.Context) error { repl := caddy.NewReplacer() + // this provisions the matchers for each route, + // and prepares auto HTTP->HTTP redirects, and + // is required before we provision each server + err := app.automaticHTTPSPhase1(ctx, repl) + if err != nil { + return err + } + for srvName, srv := range app.Servers { srv.logger = app.logger.Named("log") srv.errorLogger = app.logger.Named("log.error") @@ -136,11 +141,6 @@ func (app *App) Provision(ctx caddy.Context) error { srv.accessLogger = app.logger.Named("log.access") } - if srv.AutoHTTPS == nil { - // avoid nil pointer dereferences - srv.AutoHTTPS = new(AutoHTTPSConfig) - } - // if not explicitly configured by the user, disallow TLS // client auth bypass (domain fronting) which could // otherwise be exploited by sending an unprotected SNI @@ -151,6 +151,9 @@ func (app *App) Provision(ctx caddy.Context) error { // domain fronting is desired and access is not restricted // based on hostname if srv.StrictSNIHost == nil && srv.hasTLSClientAuth() { + app.logger.Info("enabling strict SNI-Host matching because TLS client auth is configured", + zap.String("server_name", srvName), + ) trueBool := true srv.StrictSNIHost = &trueBool } @@ -164,18 +167,19 @@ func (app *App) Provision(ctx caddy.Context) error { srv.Listen[i] = lnOut } + // pre-compile the primary handler chain, and be sure to wrap it in our + // route handler so that important security checks are done, etc. primaryRoute := emptyHandler if srv.Routes != nil { - err := srv.Routes.Provision(ctx) + err := srv.Routes.ProvisionHandlers(ctx) if err != nil { - return fmt.Errorf("server %s: setting up server routes: %v", srvName, err) + return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err) } - // pre-compile the handler chain, and be sure to wrap it in our - // route handler so that important security checks are done, etc. primaryRoute = srv.Routes.Compile(emptyHandler) } srv.primaryHandlerChain = srv.wrapPrimaryRoute(primaryRoute) + // pre-compile the error handler chain if srv.Errors != nil { err := srv.Errors.Routes.Provision(ctx) if err != nil { @@ -213,9 +217,12 @@ func (app *App) Validate() error { return nil } -// Start runs the app. It sets up automatic HTTPS if enabled. +// Start runs the app. It finishes automatic HTTPS if enabled, +// including management of certificates. func (app *App) Start() error { - err := app.automaticHTTPS() + // finish setting up automatic HTTPS and manage certs; + // this must happen before each server is started + err := app.automaticHTTPSPhase2() if err != nil { return fmt.Errorf("enabling automatic HTTPS: %v", err) } @@ -235,8 +242,8 @@ func (app *App) Start() error { if err != nil { return fmt.Errorf("%s: parsing listen address '%s': %v", srvName, lnAddr, err) } - for i := uint(0); i < listenAddr.PortRangeSize(); i++ { - hostport := listenAddr.JoinHostPort(i) + for portOffset := uint(0); portOffset < listenAddr.PortRangeSize(); portOffset++ { + hostport := listenAddr.JoinHostPort(portOffset) ln, err := caddy.Listen(listenAddr.Network, hostport) if err != nil { return fmt.Errorf("%s: listening on %s: %v", listenAddr.Network, hostport, err) @@ -249,8 +256,10 @@ func (app *App) Start() error { } } - // enable TLS - if len(srv.TLSConnPolicies) > 0 && int(i) != app.httpPort() { + // enable TLS if there is a policy and if this is not the HTTP port + if len(srv.TLSConnPolicies) > 0 && + int(listenAddr.StartPort+portOffset) != app.httpPort() { + // create TLS listener tlsCfg, err := srv.TLSConnPolicies.TLSConfig(app.ctx) if err != nil { return fmt.Errorf("%s/%s: making TLS configuration: %v", listenAddr.Network, hostport, err) @@ -330,230 +339,6 @@ func (app *App) Stop() error { return nil } -func (app *App) automaticHTTPS() error { - tlsAppIface, err := app.ctx.App("tls") - if err != nil { - return fmt.Errorf("getting tls app: %v", err) - } - tlsApp := tlsAppIface.(*caddytls.TLS) - - // this map will store associations of HTTP listener - // addresses to the routes that do HTTP->HTTPS redirects - lnAddrRedirRoutes := make(map[string]Route) - - repl := caddy.NewReplacer() - - for srvName, srv := range app.Servers { - srv.tlsApp = tlsApp - - if srv.AutoHTTPS.Disabled { - continue - } - - // skip if all listeners use the HTTP port - if !srv.listenersUseAnyPortOtherThan(app.httpPort()) { - app.logger.Info("server is only listening on the HTTP port, so no automatic HTTPS will be applied to this server", - zap.String("server_name", srvName), - zap.Int("http_port", app.httpPort()), - ) - continue - } - - // if all listeners are on the HTTPS port, make sure - // there is at least one TLS connection policy; it - // should be obvious that they want to use TLS without - // needing to specify one empty policy to enable it - if !srv.listenersUseAnyPortOtherThan(app.httpsPort()) && len(srv.TLSConnPolicies) == 0 { - app.logger.Info("server is only listening on the HTTPS port but has no TLS connection policies; adding one to enable TLS", - zap.String("server_name", srvName), - zap.Int("https_port", app.httpsPort()), - ) - srv.TLSConnPolicies = append(srv.TLSConnPolicies, new(caddytls.ConnectionPolicy)) - } - - // find all qualifying domain names, de-duplicated - domainSet := make(map[string]struct{}) - for routeIdx, route := range srv.Routes { - for matcherSetIdx, matcherSet := range route.MatcherSets { - for matcherIdx, m := range matcherSet { - if hm, ok := m.(*MatchHost); ok { - for hostMatcherIdx, d := range *hm { - d, err = repl.ReplaceOrErr(d, true, false) - if err != nil { - return fmt.Errorf("%s: route %d, matcher set %d, matcher %d, host matcher %d: %v", - srvName, routeIdx, matcherSetIdx, matcherIdx, hostMatcherIdx, err) - } - if certmagic.HostQualifies(d) && - !srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.Skip) { - domainSet[d] = struct{}{} - } - } - } - } - } - } - - if len(domainSet) > 0 { - // marshal the domains into a slice - var domains, domainsForCerts []string - for d := range domainSet { - domains = append(domains, d) - if !srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.SkipCerts) { - // if a certificate for this name is already loaded, - // don't obtain another one for it, unless we are - // supposed to ignore loaded certificates - if !srv.AutoHTTPS.IgnoreLoadedCerts && - len(tlsApp.AllMatchingCertificates(d)) > 0 { - app.logger.Info("skipping automatic certificate management because one or more matching certificates are already loaded", - zap.String("domain", d), - zap.String("server_name", srvName), - ) - continue - } - domainsForCerts = append(domainsForCerts, d) - } - } - - // ensure that these certificates are managed properly; - // for example, it's implied that the HTTPPort should also - // be the port the HTTP challenge is solved on, and so - // for HTTPS port and TLS-ALPN challenge also - we need - // to tell the TLS app to manage these certs by honoring - // those port configurations - acmeManager := &caddytls.ACMEManagerMaker{ - Challenges: &caddytls.ChallengesConfig{ - HTTP: &caddytls.HTTPChallengeConfig{ - AlternatePort: app.HTTPPort, // we specifically want the user-configured port, if any - }, - TLSALPN: &caddytls.TLSALPNChallengeConfig{ - AlternatePort: app.HTTPSPort, // we specifically want the user-configured port, if any - }, - }, - } - if tlsApp.Automation == nil { - tlsApp.Automation = new(caddytls.AutomationConfig) - } - tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, - caddytls.AutomationPolicy{ - Hosts: domainsForCerts, - Management: acmeManager, - }) - - // manage their certificates - app.logger.Info("enabling automatic TLS certificate management", - zap.Strings("domains", domainsForCerts), - ) - err := tlsApp.Manage(domainsForCerts) - if err != nil { - return fmt.Errorf("%s: managing certificate for %s: %s", srvName, domains, err) - } - - // tell the server to use TLS if it is not already doing so - if srv.TLSConnPolicies == nil { - srv.TLSConnPolicies = caddytls.ConnectionPolicies{ - &caddytls.ConnectionPolicy{ALPN: defaultALPN}, - } - } - - if srv.AutoHTTPS.DisableRedir { - continue - } - - app.logger.Info("enabling automatic HTTP->HTTPS redirects", - zap.Strings("domains", domains), - ) - - // create HTTP->HTTPS redirects - for _, addr := range srv.Listen { - netw, host, port, err := caddy.SplitNetworkAddress(addr) - if err != nil { - return fmt.Errorf("%s: invalid listener address: %v", srvName, addr) - } - - if parts := strings.SplitN(port, "-", 2); len(parts) == 2 { - port = parts[0] - } - redirTo := "https://{http.request.host}" - - if port != strconv.Itoa(app.httpsPort()) { - redirTo += ":" + port - } - redirTo += "{http.request.uri}" - - // build the plaintext HTTP variant of this address - httpRedirLnAddr := caddy.JoinNetworkAddress(netw, host, strconv.Itoa(app.httpPort())) - - // create the route that does the redirect and associate - // it with the listener address it will be served from - lnAddrRedirRoutes[httpRedirLnAddr] = Route{ - MatcherSets: []MatcherSet{{MatchProtocol("http")}}, - Handlers: []MiddlewareHandler{ - StaticResponse{ - StatusCode: WeakString(strconv.Itoa(http.StatusPermanentRedirect)), - Headers: http.Header{ - "Location": []string{redirTo}, - "Connection": []string{"close"}, - }, - Close: true, - }, - }, - } - - } - } - } - - // if there are HTTP->HTTPS redirects to add, do so now - if len(lnAddrRedirRoutes) > 0 { - var redirServerAddrs []string - var redirRoutes RouteList - - // for each redirect listener, see if there's already a - // server configured to listen on that exact address; if so, - // simply add the redirect route to the end of its route - // list; otherwise, we'll create a new server for all the - // listener addresses that are unused and serve the - // remaining redirects from it - redirRoutesLoop: - for addr, redirRoute := range lnAddrRedirRoutes { - for srvName, srv := range app.Servers { - if srv.hasListenerAddress(addr) { - // user has configured a server for the same address - // that the redirect runs from; simply append our - // redirect route to the existing routes, with a - // caveat that their config might override ours - app.logger.Warn("server is listening on same interface as redirects, so automatic HTTP->HTTPS redirects might be overridden by your own configuration", - zap.String("server_name", srvName), - zap.String("interface", addr), - ) - srv.Routes = append(srv.Routes, redirRoute) - continue redirRoutesLoop - } - } - // no server with this listener address exists; - // save this address and route for custom server - redirServerAddrs = append(redirServerAddrs, addr) - redirRoutes = append(redirRoutes, redirRoute) - } - - // if there are routes remaining which do not belong - // in any existing server, make our own to serve the - // rest of the redirects - if len(redirServerAddrs) > 0 { - app.Servers["remaining_auto_https_redirects"] = &Server{ - Listen: redirServerAddrs, - Routes: redirRoutes, - tlsApp: tlsApp, // required to solve HTTP challenge - logger: app.logger.Named("log"), - errorLogger: app.logger.Named("log.error"), - primaryHandlerChain: redirRoutes.Compile(emptyHandler), - } - } - } - - return nil -} - func (app *App) httpPort() int { if app.HTTPPort == 0 { return DefaultHTTPPort @@ -709,7 +494,9 @@ func StatusCodeMatches(actual, configured int) bool { if actual == configured { return true } - if configured < 100 && actual >= configured*100 && actual < (configured+1)*100 { + if configured < 100 && + actual >= configured*100 && + actual < (configured+1)*100 { return true } return false |