diff --git a/modules/caddyhttp/autohttps.go b/modules/caddyhttp/autohttps.go new file mode 100644 index 00000000..6cb04922 --- /dev/null +++ b/modules/caddyhttp/autohttps.go @@ -0,0 +1,344 @@ +package caddyhttp + +import ( + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddytls" + "github.com/mholt/certmagic" + "go.uber.org/zap" +) + +// AutoHTTPSConfig is used to disable automatic HTTPS +// or certain aspects of it for a specific server. +// HTTPS is enabled automatically and by default when +// qualifying hostnames are available from the config. +type AutoHTTPSConfig struct { + // If true, automatic HTTPS will be entirely disabled. + Disabled bool `json:"disable,omitempty"` + + // If true, only automatic HTTP->HTTPS redirects will + // be disabled. + DisableRedir bool `json:"disable_redirects,omitempty"` + + // Hosts/domain names listed here will not be included + // in automatic HTTPS (they will not have certificates + // loaded nor redirects applied). + Skip []string `json:"skip,omitempty"` + + // Hosts/domain names listed here will still be enabled + // for automatic HTTPS (unless in the Skip list), except + // that certificates will not be provisioned and managed + // for these names. + SkipCerts []string `json:"skip_certificates,omitempty"` + + // By default, automatic HTTPS will obtain and renew + // certificates for qualifying hostnames. However, if + // a certificate with a matching SAN is already loaded + // into the cache, certificate management will not be + // enabled. To force automated certificate management + // regardless of loaded certificates, set this to true. + IgnoreLoadedCerts bool `json:"ignore_loaded_certificates,omitempty"` + + domainSet map[string]struct{} +} + +// Skipped returns true if name is in skipSlice, which +// should be one of the Skip* fields on ahc. +func (ahc AutoHTTPSConfig) Skipped(name string, skipSlice []string) bool { + for _, n := range skipSlice { + if name == n { + return true + } + } + return false +} + +// automaticHTTPSPhase1 provisions all route matchers, determines +// which domain names found in the routes qualify for automatic +// HTTPS, and sets up HTTP->HTTPS redirects. This phase must occur +// at the beginning of provisioning, because it may add routes and +// even servers to the app, which still need to be set up with the +// rest of them. +func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) error { + // this map will store associations of HTTP listener + // addresses to the routes that do HTTP->HTTPS redirects + lnAddrRedirRoutes := make(map[string]Route) + + for srvName, srv := range app.Servers { + // as a prerequisite, provision route matchers; this is + // required for all routes on all servers, and must be + // done before we attempt to do phase 1 of auto HTTPS, + // since we have to access the decoded host matchers the + // handlers will be provisioned later + if srv.Routes != nil { + err := srv.Routes.ProvisionMatchers(ctx) + if err != nil { + return fmt.Errorf("server %s: setting up route matchers: %v", srvName, err) + } + } + + // prepare for automatic HTTPS + if srv.AutoHTTPS == nil { + srv.AutoHTTPS = new(AutoHTTPSConfig) + } + if srv.AutoHTTPS.Disabled { + continue + } + + // skip if all listeners use the HTTP port + if !srv.listenersUseAnyPortOtherThan(app.httpPort()) { + app.logger.Info("server is listening only 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()), + ) + srv.AutoHTTPS.Disabled = true + continue + } + + defaultConnPolicies := caddytls.ConnectionPolicies{ + &caddytls.ConnectionPolicy{ALPN: defaultALPN}, + } + + // 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.TLSConnPolicies == nil && + !srv.listenersUseAnyPortOtherThan(app.httpsPort()) { + app.logger.Info("server is listening only 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 = defaultConnPolicies + } + + // find all qualifying domain names in this server + srv.AutoHTTPS.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 { + var err error + 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) { + srv.AutoHTTPS.domainSet[d] = struct{}{} + } + } + } + } + } + } + + // nothing more to do here if there are no + // domains that qualify for automatic HTTPS + if len(srv.AutoHTTPS.domainSet) == 0 { + continue + } + + // tell the server to use TLS if it is not already doing so + if srv.TLSConnPolicies == nil { + srv.TLSConnPolicies = defaultConnPolicies + } + + // nothing left to do if auto redirects are disabled + if srv.AutoHTTPS.DisableRedir { + continue + } + + app.logger.Info("enabling automatic HTTP->HTTPS redirects", + zap.String("server_name", srvName), + ) + + // 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())) + + // build the matcher set for this redirect route + // (note that we happen to bypass Provision and + // Validate steps for these matcher modules) + matcherSet := MatcherSet{MatchProtocol("http")} + if len(srv.AutoHTTPS.Skip) > 0 { + matcherSet = append(matcherSet, MatchNegate{ + Matchers: MatcherSet{MatchHost(srv.AutoHTTPS.Skip)}, + }) + } + + // create the route that does the redirect and associate + // it with the listener address it will be served from + // (note that we happen to bypass any Provision or Validate + // steps on the handler modules created here) + lnAddrRedirRoutes[httpRedirLnAddr] = Route{ + MatcherSets: []MatcherSet{matcherSet}, + 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 { + return nil + } + + 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, + } + } + + return nil +} + +// automaticHTTPSPhase2 attaches a TLS app pointer to each +// server and begins certificate management for all names +// in the qualifying domain set for each server. This phase +// must occur after provisioning, and at the beginning of +// the app start, before starting each of the servers. +func (app *App) automaticHTTPSPhase2() error { + tlsAppIface, err := app.ctx.App("tls") + if err != nil { + return fmt.Errorf("getting tls app: %v", err) + } + tlsApp := tlsAppIface.(*caddytls.TLS) + + // set the tlsApp pointer before starting any + // challenges, since it is required to solve + // the ACME HTTP challenge + for _, srv := range app.Servers { + srv.tlsApp = tlsApp + } + + // begin managing certificates for enabled servers + for srvName, srv := range app.Servers { + if srv.AutoHTTPS == nil || + srv.AutoHTTPS.Disabled || + len(srv.AutoHTTPS.domainSet) == 0 { + continue + } + + // marshal the domains into a slice + var domains, domainsForCerts []string + for d := range srv.AutoHTTPS.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) + } + } + + return nil +} diff --git a/modules/caddyhttp/caddyhttp.go b/modules/caddyhttp/caddyhttp.go index 73c48635..37f96706 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 diff --git a/modules/caddyhttp/routes.go b/modules/caddyhttp/routes.go index 431d1a5e..d4ff02a2 100644 --- a/modules/caddyhttp/routes.go +++ b/modules/caddyhttp/routes.go @@ -113,23 +113,43 @@ func (r Route) Empty() bool { // create a middleware chain. type RouteList []Route -// Provision sets up all the routes by loading the modules. +// Provision sets up both the matchers and handlers in the route. func (routes RouteList) Provision(ctx caddy.Context) error { + err := routes.ProvisionMatchers(ctx) + if err != nil { + return err + } + return routes.ProvisionHandlers(ctx) +} + +// ProvisionMatchers sets up all the matchers by loading the +// matcher modules. Only call this method directly if you need +// to set up matchers and handlers separately without having +// to provision a second time; otherwise use Provision instead. +func (routes RouteList) ProvisionMatchers(ctx caddy.Context) error { for i := range routes { // matchers matchersIface, err := ctx.LoadModule(&routes[i], "MatcherSetsRaw") if err != nil { - return fmt.Errorf("loading matchers in route %d: %v", i, err) + return fmt.Errorf("route %d: loading matcher modules: %v", i, err) } err = routes[i].MatcherSets.FromInterface(matchersIface) if err != nil { return fmt.Errorf("route %d: %v", i, err) } + } + return nil +} - // handlers +// ProvisionHandlers sets up all the handlers by loading the +// handler modules. Only call this method directly if you need +// to set up matchers and handlers separately without having +// to provision a second time; otherwise use Provision instead. +func (routes RouteList) ProvisionHandlers(ctx caddy.Context) error { + for i := range routes { handlersIface, err := ctx.LoadModule(&routes[i], "HandlersRaw") if err != nil { - return fmt.Errorf("loading handler modules in route %d: %v", i, err) + return fmt.Errorf("route %d: loading handler modules: %v", i, err) } for _, handler := range handlersIface.([]interface{}) { routes[i].Handlers = append(routes[i].Handlers, handler.(MiddlewareHandler)) @@ -140,7 +160,6 @@ func (routes RouteList) Provision(ctx caddy.Context) error { routes[i].middleware = append(routes[i].middleware, wrapMiddleware(midhandler)) } } - return nil } diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go index ce61b13b..1c896a44 100644 --- a/modules/caddyhttp/server.go +++ b/modules/caddyhttp/server.go @@ -173,7 +173,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } log("handled request", - zap.String("common_log", repl.ReplaceAll(CommonLogFormat, "-")), + zap.String("common_log", repl.ReplaceAll(commonLogFormat, "-")), zap.Duration("latency", latency), zap.Int("size", wrec.Size()), zap.Int("status", wrec.Status()), @@ -317,49 +317,6 @@ func (s *Server) hasTLSClientAuth() bool { return false } -// AutoHTTPSConfig is used to disable automatic HTTPS -// or certain aspects of it for a specific server. -// HTTPS is enabled automatically and by default when -// qualifying hostnames are available from the config. -type AutoHTTPSConfig struct { - // If true, automatic HTTPS will be entirely disabled. - Disabled bool `json:"disable,omitempty"` - - // If true, only automatic HTTP->HTTPS redirects will - // be disabled. - DisableRedir bool `json:"disable_redirects,omitempty"` - - // Hosts/domain names listed here will not be included - // in automatic HTTPS (they will not have certificates - // loaded nor redirects applied). - Skip []string `json:"skip,omitempty"` - - // Hosts/domain names listed here will still be enabled - // for automatic HTTPS (unless in the Skip list), except - // that certificates will not be provisioned and managed - // for these names. - SkipCerts []string `json:"skip_certificates,omitempty"` - - // By default, automatic HTTPS will obtain and renew - // certificates for qualifying hostnames. However, if - // a certificate with a matching SAN is already loaded - // into the cache, certificate management will not be - // enabled. To force automated certificate management - // regardless of loaded certificates, set this to true. - IgnoreLoadedCerts bool `json:"ignore_loaded_certificates,omitempty"` -} - -// Skipped returns true if name is in skipSlice, which -// should be one of the Skip* fields on ahc. -func (ahc AutoHTTPSConfig) Skipped(name string, skipSlice []string) bool { - for _, n := range skipSlice { - if name == n { - return true - } - } - return false -} - // HTTPErrorConfig determines how to handle errors // from the HTTP handlers. type HTTPErrorConfig struct { @@ -466,11 +423,11 @@ func cloneURL(from, to *url.URL) { } const ( - // CommonLogFormat is the common log format. https://en.wikipedia.org/wiki/Common_Log_Format - CommonLogFormat = `{http.request.remote.host} ` + CommonLogEmptyValue + ` {http.authentication.user.id} [{time.now.common_log}] "{http.request.orig_method} {http.request.orig_uri} {http.request.proto}" {http.response.status} {http.response.size}` + // commonLogFormat is the common log format. https://en.wikipedia.org/wiki/Common_Log_Format + commonLogFormat = `{http.request.remote.host} ` + commonLogEmptyValue + ` {http.authentication.user.id} [{time.now.common_log}] "{http.request.orig_method} {http.request.orig_uri} {http.request.proto}" {http.response.status} {http.response.size}` - // CommonLogEmptyValue is the common empty log value. - CommonLogEmptyValue = "-" + // commonLogEmptyValue is the common empty log value. + commonLogEmptyValue = "-" ) // Context keys for HTTP request context values.