diff options
Diffstat (limited to 'modules/caddyhttp')
-rw-r--r-- | modules/caddyhttp/caddyhttp.go | 30 | ||||
-rw-r--r-- | modules/caddyhttp/fileserver/caddyfile.go | 2 | ||||
-rw-r--r-- | modules/caddyhttp/fileserver/staticfiles.go | 44 | ||||
-rw-r--r-- | modules/caddyhttp/replacer.go | 6 | ||||
-rw-r--r-- | modules/caddyhttp/server.go | 28 |
5 files changed, 93 insertions, 17 deletions
diff --git a/modules/caddyhttp/caddyhttp.go b/modules/caddyhttp/caddyhttp.go index 174e316..9dfdf36 100644 --- a/modules/caddyhttp/caddyhttp.go +++ b/modules/caddyhttp/caddyhttp.go @@ -75,6 +75,15 @@ func (app *App) Provision(ctx caddy.Context) error { srv.AutoHTTPS = new(AutoHTTPSConfig) } + // disallow TLS client auth bypass which could + // otherwise be exploited by sending an unprotected + // SNI value during TLS handshake, then a protected + // Host header during HTTP request later on that + // connection + if srv.hasTLSClientAuth() { + srv.StrictSNIHost = true + } + // TODO: Test this function to ensure these replacements are performed for i := range srv.Listen { srv.Listen[i] = repl.ReplaceAll(srv.Listen[i], "") @@ -159,8 +168,7 @@ func (app *App) Start() error { return fmt.Errorf("%s: listening on %s: %v", network, addr, err) } - // enable HTTP/2 (and support for solving the - // TLS-ALPN ACME challenge) by default + // enable HTTP/2 by default for _, pol := range srv.TLSConnPolicies { if len(pol.ALPN) == 0 { pol.ALPN = append(pol.ALPN, defaultALPN...) @@ -226,6 +234,8 @@ func (app *App) automaticHTTPS() error { // skip if all listeners use the HTTP port if !srv.listenersUseAnyPortOtherThan(app.HTTPPort) { + log.Printf("[INFO] Server %v is only listening on the HTTP port %d, so no automatic HTTPS will be applied to this server", + srv.Listen, app.HTTPPort) continue } @@ -294,11 +304,11 @@ func (app *App) automaticHTTPS() error { return fmt.Errorf("%s: managing certificate for %s: %s", srvName, domains, err) } - // tell the server to use TLS by specifying a TLS - // connection policy (which supports HTTP/2 and the - // TLS-ALPN ACME challenge as well) - srv.TLSConnPolicies = caddytls.ConnectionPolicies{ - {ALPN: defaultALPN}, + // 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 { @@ -307,6 +317,12 @@ func (app *App) automaticHTTPS() error { log.Printf("[INFO] Enabling automatic HTTP->HTTPS redirects for %v", domains) + // notify user if their config might override the HTTP->HTTPS redirects + if srv.listenersIncludePort(app.HTTPPort) { + log.Printf("[WARNING] Server %v is listening on HTTP port %d, so automatic HTTP->HTTPS redirects may be overridden by your own configuration", + srv.Listen, app.HTTPPort) + } + // create HTTP->HTTPS redirects for _, addr := range srv.Listen { netw, host, port, err := caddy.SplitNetworkAddress(addr) diff --git a/modules/caddyhttp/fileserver/caddyfile.go b/modules/caddyhttp/fileserver/caddyfile.go index 4622af2..b7cb311 100644 --- a/modules/caddyhttp/fileserver/caddyfile.go +++ b/modules/caddyhttp/fileserver/caddyfile.go @@ -94,7 +94,7 @@ func parseTryFiles(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) } handler := rewrite.Rewrite{ - URI: "{http.matchers.file.relative}{http.request.uri.query}", + URI: "{http.matchers.file.relative}{http.request.uri.query_string}", } matcherSet := map[string]json.RawMessage{ diff --git a/modules/caddyhttp/fileserver/staticfiles.go b/modules/caddyhttp/fileserver/staticfiles.go index cfb79f8..3e4cccc 100644 --- a/modules/caddyhttp/fileserver/staticfiles.go +++ b/modules/caddyhttp/fileserver/staticfiles.go @@ -41,10 +41,11 @@ func init() { // FileServer implements a static file server responder for Caddy. type FileServer struct { - Root string `json:"root,omitempty"` // default is current directory - Hide []string `json:"hide,omitempty"` - IndexNames []string `json:"index_names,omitempty"` - Browse *Browse `json:"browse,omitempty"` + Root string `json:"root,omitempty"` // default is current directory + Hide []string `json:"hide,omitempty"` + IndexNames []string `json:"index_names,omitempty"` + Browse *Browse `json:"browse,omitempty"` + CanonicalURIs *bool `json:"canonical_uris,omitempty"` } // CaddyModule returns the Caddy module information. @@ -109,6 +110,7 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, _ cadd // if the request mapped to a directory, see if // there is an index file we can serve + var implicitIndexFile bool if info.IsDir() && len(fsrv.IndexNames) > 0 { for _, indexPage := range fsrv.IndexNames { indexPath := sanitizedPathJoin(filename, indexPage) @@ -122,12 +124,17 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, _ cadd continue } - // we found an index file that might work, - // so rewrite the request path - r.URL.Path = path.Join(r.URL.Path, indexPage) + // don't rewrite the request path to append + // the index file, because we might need to + // do a canonical-URL redirect below based + // on the URL as-is + // we've chosen to use this index file, + // so replace the last file info and path + // with that of the index file info = indexInfo filename = indexPath + implicitIndexFile = true break } } @@ -149,10 +156,22 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, _ cadd return caddyhttp.Error(http.StatusNotFound, nil) } + // if URL canonicalization is enabled, we need to enforce trailing + // slash convention: if a directory, trailing slash; if a file, no + // trailing slash - not enforcing this can break relative hrefs + // in HTML (see https://github.com/caddyserver/caddy/issues/2741) + if fsrv.CanonicalURIs == nil || *fsrv.CanonicalURIs { + if implicitIndexFile && !strings.HasSuffix(r.URL.Path, "/") { + return redirect(w, r, r.URL.Path+"/") + } else if !implicitIndexFile && strings.HasSuffix(r.URL.Path, "/") { + return redirect(w, r, r.URL.Path[:len(r.URL.Path)-1]) + } + } + // open the file file, err := fsrv.openFile(filename, w) if err != nil { - return err + return err // error is already structured } defer file.Close() @@ -309,6 +328,15 @@ func calculateEtag(d os.FileInfo) string { return `"` + t + s + `"` } +func redirect(w http.ResponseWriter, r *http.Request, to string) error { + for strings.HasPrefix(to, "//") { + // prevent path-based open redirects + to = strings.TrimPrefix(to, "/") + } + http.Redirect(w, r, to, http.StatusPermanentRedirect) + return nil +} + var defaultIndexNames = []string{"index.html", "index.txt"} var bufPool = sync.Pool{ diff --git a/modules/caddyhttp/replacer.go b/modules/caddyhttp/replacer.go index f7f69a4..e003259 100644 --- a/modules/caddyhttp/replacer.go +++ b/modules/caddyhttp/replacer.go @@ -89,6 +89,12 @@ func addHTTPVarsToReplacer(repl caddy.Replacer, req *http.Request, w http.Respon return dir, true case "http.request.uri.query": return req.URL.RawQuery, true + case "http.request.uri.query_string": + qs := req.URL.Query().Encode() + if qs != "" { + qs = "?" + qs + } + return qs, true } // hostname labels diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go index 04935e6..42f7a5a 100644 --- a/modules/caddyhttp/server.go +++ b/modules/caddyhttp/server.go @@ -41,7 +41,7 @@ type Server struct { TLSConnPolicies caddytls.ConnectionPolicies `json:"tls_connection_policies,omitempty"` AutoHTTPS *AutoHTTPSConfig `json:"automatic_https,omitempty"` MaxRehandles *int `json:"max_rehandles,omitempty"` - StrictSNIHost bool `json:"strict_sni_host,omitempty"` // TODO: see if we can turn this on by default when clientauth is configured + StrictSNIHost bool `json:"strict_sni_host,omitempty"` tlsApp *caddytls.TLS } @@ -183,6 +183,32 @@ func (s *Server) listenersUseAnyPortOtherThan(otherPort int) bool { return false } +// listenersIncludePort returns true if there are any +// listeners in s that use otherPort. +func (s *Server) listenersIncludePort(otherPort int) bool { + for _, lnAddr := range s.Listen { + _, addrs, err := caddy.ParseListenAddr(lnAddr) + if err == nil { + for _, a := range addrs { + _, port, err := net.SplitHostPort(a) + if err == nil && port == strconv.Itoa(otherPort) { + return true + } + } + } + } + return false +} + +func (s *Server) hasTLSClientAuth() bool { + for _, cp := range s.TLSConnPolicies { + if cp.ClientAuthentication != nil && cp.ClientAuthentication.Active() { + return true + } + } + return false +} + // AutoHTTPSConfig is used to disable automatic HTTPS // or certain aspects of it for a specific server. type AutoHTTPSConfig struct { |