From 2d12fb7ac6c7dfcbb8abeafbfb64af5ad1175bb3 Mon Sep 17 00:00:00 2001 From: vnxme <46669194+vnxme@users.noreply.github.com> Date: Thu, 12 Sep 2024 05:51:59 +0300 Subject: [PATCH 01/39] caddytls: Add sni_regexp matcher (#6569) --- modules/caddytls/matchers.go | 155 +++++++++++++++++++++++++++++- modules/caddytls/matchers_test.go | 46 +++++++++ 2 files changed, 199 insertions(+), 2 deletions(-) diff --git a/modules/caddytls/matchers.go b/modules/caddytls/matchers.go index f946223741b..65bbfa311e1 100644 --- a/modules/caddytls/matchers.go +++ b/modules/caddytls/matchers.go @@ -19,6 +19,8 @@ import ( "fmt" "net" "net/netip" + "regexp" + "strconv" "strings" "github.com/caddyserver/certmagic" @@ -31,6 +33,7 @@ import ( func init() { caddy.RegisterModule(MatchServerName{}) + caddy.RegisterModule(MatchServerNameRE{}) caddy.RegisterModule(MatchRemoteIP{}) caddy.RegisterModule(MatchLocalIP{}) } @@ -91,6 +94,146 @@ func (m *MatchServerName) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { return nil } +// MatchRegexp is an embeddable type for matching +// using regular expressions. It adds placeholders +// to the request's replacer. In fact, it is a copy of +// caddyhttp.MatchRegexp with a local replacer prefix +// and placeholders support in a regular expression pattern. +type MatchRegexp struct { + // A unique name for this regular expression. Optional, + // but useful to prevent overwriting captures from other + // regexp matchers. + Name string `json:"name,omitempty"` + + // The regular expression to evaluate, in RE2 syntax, + // which is the same general syntax used by Go, Perl, + // and Python. For details, see + // [Go's regexp package](https://golang.org/pkg/regexp/). + // Captures are accessible via placeholders. Unnamed + // capture groups are exposed as their numeric, 1-based + // index, while named capture groups are available by + // the capture group name. + Pattern string `json:"pattern"` + + compiled *regexp.Regexp +} + +// Provision compiles the regular expression which may include placeholders. +func (mre *MatchRegexp) Provision(caddy.Context) error { + repl := caddy.NewReplacer() + re, err := regexp.Compile(repl.ReplaceAll(mre.Pattern, "")) + if err != nil { + return fmt.Errorf("compiling matcher regexp %s: %v", mre.Pattern, err) + } + mre.compiled = re + return nil +} + +// Validate ensures mre is set up correctly. +func (mre *MatchRegexp) Validate() error { + if mre.Name != "" && !wordRE.MatchString(mre.Name) { + return fmt.Errorf("invalid regexp name (must contain only word characters): %s", mre.Name) + } + return nil +} + +// Match returns true if input matches the compiled regular +// expression in m. It sets values on the replacer repl +// associated with capture groups, using the given scope +// (namespace). +func (mre *MatchRegexp) Match(input string, repl *caddy.Replacer) bool { + matches := mre.compiled.FindStringSubmatch(input) + if matches == nil { + return false + } + + // save all capture groups, first by index + for i, match := range matches { + keySuffix := "." + strconv.Itoa(i) + if mre.Name != "" { + repl.Set(regexpPlaceholderPrefix+"."+mre.Name+keySuffix, match) + } + repl.Set(regexpPlaceholderPrefix+keySuffix, match) + } + + // then by name + for i, name := range mre.compiled.SubexpNames() { + // skip the first element (the full match), and empty names + if i == 0 || name == "" { + continue + } + + keySuffix := "." + name + if mre.Name != "" { + repl.Set(regexpPlaceholderPrefix+"."+mre.Name+keySuffix, matches[i]) + } + repl.Set(regexpPlaceholderPrefix+keySuffix, matches[i]) + } + + return true +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. +func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // iterate to merge multiple matchers into one + for d.Next() { + // If this is the second iteration of the loop + // then there's more than one *_regexp matcher, + // and we would end up overwriting the old one + if mre.Pattern != "" { + return d.Err("regular expression can only be used once per named matcher") + } + + args := d.RemainingArgs() + switch len(args) { + case 1: + mre.Pattern = args[0] + case 2: + mre.Name = args[0] + mre.Pattern = args[1] + default: + return d.ArgErr() + } + + // Default to the named matcher's name, if no regexp name is provided. + // Note: it requires d.SetContext(caddyfile.MatcherNameCtxKey, value) + // called before this unmarshalling, otherwise it wouldn't work. + if mre.Name == "" { + mre.Name = d.GetContextString(caddyfile.MatcherNameCtxKey) + } + + if d.NextBlock(0) { + return d.Err("malformed regexp matcher: blocks are not supported") + } + } + return nil +} + +// MatchServerNameRE matches based on SNI using a regular expression. +type MatchServerNameRE struct{ MatchRegexp } + +// CaddyModule returns the Caddy module information. +func (MatchServerNameRE) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.handshake_match.sni_regexp", + New: func() caddy.Module { return new(MatchServerNameRE) }, + } +} + +// Match matches hello based on SNI using a regular expression. +func (m MatchServerNameRE) Match(hello *tls.ClientHelloInfo) bool { + repl := caddy.NewReplacer() + // caddytls.TestServerNameMatcher calls this function without any context + if ctx := hello.Context(); ctx != nil { + // In some situations the existing context may have no replacer + if replAny := ctx.Value(caddy.ReplacerCtxKey); replAny != nil { + repl = replAny.(*caddy.Replacer) + } + } + + return m.MatchRegexp.Match(hello.ServerName, repl) +} + // MatchRemoteIP matches based on the remote IP of the // connection. Specific IPs or CIDR ranges can be specified. // @@ -331,13 +474,21 @@ func (m *MatchLocalIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { // Interface guards var ( - _ ConnectionMatcher = (*MatchServerName)(nil) + _ ConnectionMatcher = (*MatchLocalIP)(nil) _ ConnectionMatcher = (*MatchRemoteIP)(nil) + _ ConnectionMatcher = (*MatchServerName)(nil) + _ ConnectionMatcher = (*MatchServerNameRE)(nil) _ caddy.Provisioner = (*MatchLocalIP)(nil) - _ ConnectionMatcher = (*MatchLocalIP)(nil) + _ caddy.Provisioner = (*MatchRemoteIP)(nil) + _ caddy.Provisioner = (*MatchServerNameRE)(nil) _ caddyfile.Unmarshaler = (*MatchLocalIP)(nil) _ caddyfile.Unmarshaler = (*MatchRemoteIP)(nil) _ caddyfile.Unmarshaler = (*MatchServerName)(nil) + _ caddyfile.Unmarshaler = (*MatchServerNameRE)(nil) ) + +var wordRE = regexp.MustCompile(`\w+`) + +const regexpPlaceholderPrefix = "tls.regexp" diff --git a/modules/caddytls/matchers_test.go b/modules/caddytls/matchers_test.go index 54dfdb9c45c..824f7207098 100644 --- a/modules/caddytls/matchers_test.go +++ b/modules/caddytls/matchers_test.go @@ -89,6 +89,52 @@ func TestServerNameMatcher(t *testing.T) { } } +func TestServerNameREMatcher(t *testing.T) { + for i, tc := range []struct { + pattern string + input string + expect bool + }{ + { + pattern: "^example\\.(com|net)$", + input: "example.com", + expect: true, + }, + { + pattern: "^example\\.(com|net)$", + input: "foo.com", + expect: false, + }, + { + pattern: "^example\\.(com|net)$", + input: "", + expect: false, + }, + { + pattern: "", + input: "", + expect: true, + }, + { + pattern: "^example\\.(com|net)$", + input: "foo.example.com", + expect: false, + }, + } { + chi := &tls.ClientHelloInfo{ServerName: tc.input} + mre := MatchServerNameRE{MatchRegexp{Pattern: tc.pattern}} + ctx, _ := caddy.NewContext(caddy.Context{Context: context.Background()}) + if mre.Provision(ctx) != nil { + t.Errorf("Test %d: Failed to provision a regexp matcher (pattern=%v)", i, tc.pattern) + } + actual := mre.Match(chi) + if actual != tc.expect { + t.Errorf("Test %d: Expected %t but got %t (input=%s match=%v)", + i, tc.expect, actual, tc.input, tc.pattern) + } + } +} + func TestRemoteIPMatcher(t *testing.T) { ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) defer cancel() From 21f9c20a04ec5c2ac430daa8e4ba8fbdef67f773 Mon Sep 17 00:00:00 2001 From: mister-turtle <50653342+mister-turtle@users.noreply.github.com> Date: Fri, 13 Sep 2024 08:22:03 +0100 Subject: [PATCH 02/39] rewrite: Avoid panic on bad arg count for `uri` (#6571) --- modules/caddyhttp/rewrite/caddyfile.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/caddyhttp/rewrite/caddyfile.go b/modules/caddyhttp/rewrite/caddyfile.go index 0ce5c41d217..89f44c79bf3 100644 --- a/modules/caddyhttp/rewrite/caddyfile.go +++ b/modules/caddyhttp/rewrite/caddyfile.go @@ -106,7 +106,7 @@ func parseCaddyfileURI(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, err switch args[0] { case "strip_prefix": - if len(args) > 2 { + if len(args) != 2 { return nil, h.ArgErr() } rewr.StripPathPrefix = args[1] @@ -115,7 +115,7 @@ func parseCaddyfileURI(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, err } case "strip_suffix": - if len(args) > 2 { + if len(args) != 2 { return nil, h.ArgErr() } rewr.StripPathSuffix = args[1] From f4bf4e0097853438eb69c573bbaa0581e9b9c02d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 13 Sep 2024 19:16:37 +0200 Subject: [PATCH 03/39] perf: use zap's Check() to prevent useless allocs (#6560) * perf: use zap's Check() to prevent useless allocs * fix * fix * fix * fix * restore previous replacer behavior * fix linter --- modules/caddyhttp/caddyauth/caddyauth.go | 7 +- modules/caddyhttp/fileserver/browse.go | 11 +- .../caddyhttp/fileserver/browsetplcontext.go | 7 +- modules/caddyhttp/fileserver/matcher.go | 14 +- modules/caddyhttp/fileserver/staticfiles.go | 91 +++++++---- modules/caddyhttp/intercept/intercept.go | 5 +- modules/caddyhttp/ip_matchers.go | 10 +- modules/caddyhttp/logging.go | 12 +- modules/caddyhttp/push/handler.go | 24 ++- modules/caddyhttp/requestbody/requestbody.go | 9 +- .../caddyhttp/reverseproxy/fastcgi/client.go | 10 +- .../caddyhttp/reverseproxy/fastcgi/fastcgi.go | 11 +- .../caddyhttp/reverseproxy/healthchecks.go | 154 +++++++++++------- .../caddyhttp/reverseproxy/httptransport.go | 9 +- modules/caddyhttp/reverseproxy/metrics.go | 10 +- .../caddyhttp/reverseproxy/reverseproxy.go | 54 ++++-- modules/caddyhttp/reverseproxy/streaming.go | 84 +++++++--- modules/caddyhttp/reverseproxy/upstreams.go | 62 ++++--- modules/caddyhttp/rewrite/rewrite.go | 10 +- modules/caddyhttp/server.go | 141 ++++++++++------ modules/caddyhttp/server_test.go | 23 +++ modules/caddyhttp/tracing/tracerprovider.go | 5 +- modules/caddypki/acmeserver/acmeserver.go | 13 +- modules/caddytls/acmeissuer.go | 5 +- modules/caddytls/automation.go | 28 +++- modules/caddytls/certmanagers.go | 5 +- modules/caddytls/connpolicy.go | 6 +- modules/caddytls/matchers.go | 9 +- modules/caddytls/ondemand.go | 25 ++- modules/caddytls/tls.go | 27 ++- 30 files changed, 599 insertions(+), 282 deletions(-) diff --git a/modules/caddyhttp/caddyauth/caddyauth.go b/modules/caddyhttp/caddyauth/caddyauth.go index c60de880b8b..f799d7a0cb0 100644 --- a/modules/caddyhttp/caddyauth/caddyauth.go +++ b/modules/caddyhttp/caddyauth/caddyauth.go @@ -19,6 +19,7 @@ import ( "net/http" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/modules/caddyhttp" @@ -76,9 +77,9 @@ func (a Authentication) ServeHTTP(w http.ResponseWriter, r *http.Request, next c for provName, prov := range a.Providers { user, authed, err = prov.Authenticate(w, r) if err != nil { - a.logger.Error("auth provider returned error", - zap.String("provider", provName), - zap.Error(err)) + if c := a.logger.Check(zapcore.ErrorLevel, "auth provider returned error"); c != nil { + c.Write(zap.String("provider", provName), zap.Error(err)) + } continue } if authed { diff --git a/modules/caddyhttp/fileserver/browse.go b/modules/caddyhttp/fileserver/browse.go index f5cc16b42ab..a19b4e17a85 100644 --- a/modules/caddyhttp/fileserver/browse.go +++ b/modules/caddyhttp/fileserver/browse.go @@ -33,6 +33,7 @@ import ( "time" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/modules/caddyhttp" @@ -68,9 +69,9 @@ type Browse struct { } func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { - fsrv.logger.Debug("browse enabled; listing directory contents", - zap.String("path", dirPath), - zap.String("root", root)) + if c := fsrv.logger.Check(zapcore.DebugLevel, "browse enabled; listing directory contents"); c != nil { + c.Write(zap.String("path", dirPath), zap.String("root", root)) + } // Navigation on the client-side gets messed up if the // URL doesn't end in a trailing slash because hrefs to @@ -92,7 +93,9 @@ func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w ht origReq := r.Context().Value(caddyhttp.OriginalRequestCtxKey).(http.Request) if r.URL.Path == "" || path.Base(origReq.URL.Path) == path.Base(r.URL.Path) { if !strings.HasSuffix(origReq.URL.Path, "/") { - fsrv.logger.Debug("redirecting to trailing slash to preserve hrefs", zap.String("request_path", r.URL.Path)) + if c := fsrv.logger.Check(zapcore.DebugLevel, "redirecting to trailing slash to preserve hrefs"); c != nil { + c.Write(zap.String("request_path", r.URL.Path)) + } return redirect(w, r, origReq.URL.Path+"/") } } diff --git a/modules/caddyhttp/fileserver/browsetplcontext.go b/modules/caddyhttp/fileserver/browsetplcontext.go index 0251bc581f6..37e1cc3dda8 100644 --- a/modules/caddyhttp/fileserver/browsetplcontext.go +++ b/modules/caddyhttp/fileserver/browsetplcontext.go @@ -28,6 +28,7 @@ import ( "github.com/dustin/go-humanize" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/modules/caddyhttp" @@ -57,9 +58,9 @@ func (fsrv *FileServer) directoryListing(ctx context.Context, fileSystem fs.FS, info, err := entry.Info() if err != nil { - fsrv.logger.Error("could not get info about directory entry", - zap.String("name", entry.Name()), - zap.String("root", root)) + if c := fsrv.logger.Check(zapcore.ErrorLevel, "could not get info about directory entry"); c != nil { + c.Write(zap.String("name", entry.Name()), zap.String("root", root)) + } continue } diff --git a/modules/caddyhttp/fileserver/matcher.go b/modules/caddyhttp/fileserver/matcher.go index c315b8e38d7..6ab2180ad77 100644 --- a/modules/caddyhttp/fileserver/matcher.go +++ b/modules/caddyhttp/fileserver/matcher.go @@ -33,6 +33,7 @@ import ( "github.com/google/cel-go/common/types/ref" "github.com/google/cel-go/parser" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" @@ -326,7 +327,9 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) { fileSystem, ok := m.fsmap.Get(fsName) if !ok { - m.logger.Error("use of unregistered filesystem", zap.String("fs", fsName)) + if c := m.logger.Check(zapcore.ErrorLevel, "use of unregistered filesystem"); c != nil { + c.Write(zap.String("fs", fsName)) + } return false } type matchCandidate struct { @@ -356,7 +359,10 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) { return val, nil }) if err != nil { - m.logger.Error("evaluating placeholders", zap.Error(err)) + if c := m.logger.Check(zapcore.ErrorLevel, "evaluating placeholders"); c != nil { + c.Write(zap.Error(err)) + } + expandedFile = file // "oh well," I guess? } @@ -379,7 +385,9 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) { } else { globResults, err = fs.Glob(fileSystem, fullPattern) if err != nil { - m.logger.Error("expanding glob", zap.Error(err)) + if c := m.logger.Check(zapcore.ErrorLevel, "expanding glob"); c != nil { + c.Write(zap.Error(err)) + } } } diff --git a/modules/caddyhttp/fileserver/staticfiles.go b/modules/caddyhttp/fileserver/staticfiles.go index 2dd237ae7dc..4ae69b64761 100644 --- a/modules/caddyhttp/fileserver/staticfiles.go +++ b/modules/caddyhttp/fileserver/staticfiles.go @@ -31,6 +31,7 @@ import ( "strings" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/modules/caddyhttp" @@ -286,11 +287,14 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c // remove any trailing `/` as it breaks fs.ValidPath() in the stdlib filename := strings.TrimSuffix(caddyhttp.SanitizedPathJoin(root, r.URL.Path), "/") - fsrv.logger.Debug("sanitized path join", - zap.String("site_root", root), - zap.String("fs", fsName), - zap.String("request_path", r.URL.Path), - zap.String("result", filename)) + if c := fsrv.logger.Check(zapcore.DebugLevel, "sanitized path join"); c != nil { + c.Write( + zap.String("site_root", root), + zap.String("fs", fsName), + zap.String("request_path", r.URL.Path), + zap.String("result", filename), + ) + } // get information about the file info, err := fs.Stat(fileSystem, filename) @@ -313,9 +317,12 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c indexPath := caddyhttp.SanitizedPathJoin(filename, indexPage) if fileHidden(indexPath, filesToHide) { // pretend this file doesn't exist - fsrv.logger.Debug("hiding index file", - zap.String("filename", indexPath), - zap.Strings("files_to_hide", filesToHide)) + if c := fsrv.logger.Check(zapcore.DebugLevel, "hiding index file"); c != nil { + c.Write( + zap.String("filename", indexPath), + zap.Strings("files_to_hide", filesToHide), + ) + } continue } @@ -335,7 +342,9 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c info = indexInfo filename = indexPath implicitIndexFile = true - fsrv.logger.Debug("located index file", zap.String("filename", filename)) + if c := fsrv.logger.Check(zapcore.DebugLevel, "located index file"); c != nil { + c.Write(zap.String("filename", filename)) + } break } } @@ -343,9 +352,12 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c // if still referencing a directory, delegate // to browse or return an error if info.IsDir() { - fsrv.logger.Debug("no index file in directory", - zap.String("path", filename), - zap.Strings("index_filenames", fsrv.IndexNames)) + if c := fsrv.logger.Check(zapcore.DebugLevel, "no index file in directory"); c != nil { + c.Write( + zap.String("path", filename), + zap.Strings("index_filenames", fsrv.IndexNames), + ) + } if fsrv.Browse != nil && !fileHidden(filename, filesToHide) { return fsrv.serveBrowse(fileSystem, root, filename, w, r, next) } @@ -355,9 +367,12 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c // one last check to ensure the file isn't hidden (we might // have changed the filename from when we last checked) if fileHidden(filename, filesToHide) { - fsrv.logger.Debug("hiding file", - zap.String("filename", filename), - zap.Strings("files_to_hide", filesToHide)) + if c := fsrv.logger.Check(zapcore.DebugLevel, "hiding file"); c != nil { + c.Write( + zap.String("filename", filename), + zap.Strings("files_to_hide", filesToHide), + ) + } return fsrv.notFound(w, r, next) } @@ -375,15 +390,21 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c if path.Base(origReq.URL.Path) == path.Base(r.URL.Path) { if implicitIndexFile && !strings.HasSuffix(origReq.URL.Path, "/") { to := origReq.URL.Path + "/" - fsrv.logger.Debug("redirecting to canonical URI (adding trailing slash for directory)", - zap.String("from_path", origReq.URL.Path), - zap.String("to_path", to)) + if c := fsrv.logger.Check(zapcore.DebugLevel, "redirecting to canonical URI (adding trailing slash for directory"); c != nil { + c.Write( + zap.String("from_path", origReq.URL.Path), + zap.String("to_path", to), + ) + } return redirect(w, r, to) } else if !implicitIndexFile && strings.HasSuffix(origReq.URL.Path, "/") { to := origReq.URL.Path[:len(origReq.URL.Path)-1] - fsrv.logger.Debug("redirecting to canonical URI (removing trailing slash for file)", - zap.String("from_path", origReq.URL.Path), - zap.String("to_path", to)) + if c := fsrv.logger.Check(zapcore.DebugLevel, "redirecting to canonical URI (removing trailing slash for file"); c != nil { + c.Write( + zap.String("from_path", origReq.URL.Path), + zap.String("to_path", to), + ) + } return redirect(w, r, to) } } @@ -411,13 +432,19 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c compressedFilename := filename + precompress.Suffix() compressedInfo, err := fs.Stat(fileSystem, compressedFilename) if err != nil || compressedInfo.IsDir() { - fsrv.logger.Debug("precompressed file not accessible", zap.String("filename", compressedFilename), zap.Error(err)) + if c := fsrv.logger.Check(zapcore.DebugLevel, "precompressed file not accessible"); c != nil { + c.Write(zap.String("filename", compressedFilename), zap.Error(err)) + } continue } - fsrv.logger.Debug("opening compressed sidecar file", zap.String("filename", compressedFilename), zap.Error(err)) + if c := fsrv.logger.Check(zapcore.DebugLevel, "opening compressed sidecar file"); c != nil { + c.Write(zap.String("filename", compressedFilename), zap.Error(err)) + } file, err = fsrv.openFile(fileSystem, compressedFilename, w) if err != nil { - fsrv.logger.Warn("opening precompressed file failed", zap.String("filename", compressedFilename), zap.Error(err)) + if c := fsrv.logger.Check(zapcore.WarnLevel, "opening precompressed file failed"); c != nil { + c.Write(zap.String("filename", compressedFilename), zap.Error(err)) + } if caddyErr, ok := err.(caddyhttp.HandlerError); ok && caddyErr.StatusCode == http.StatusServiceUnavailable { return err } @@ -448,7 +475,9 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c // no precompressed file found, use the actual file if file == nil { - fsrv.logger.Debug("opening file", zap.String("filename", filename)) + if c := fsrv.logger.Check(zapcore.DebugLevel, "opening file"); c != nil { + c.Write(zap.String("filename", filename)) + } // open the file file, err = fsrv.openFile(fileSystem, filename, w) @@ -548,10 +577,14 @@ func (fsrv *FileServer) openFile(fileSystem fs.FS, filename string, w http.Respo if err != nil { err = fsrv.mapDirOpenError(fileSystem, err, filename) if errors.Is(err, fs.ErrNotExist) { - fsrv.logger.Debug("file not found", zap.String("filename", filename), zap.Error(err)) + if c := fsrv.logger.Check(zapcore.DebugLevel, "file not found"); c != nil { + c.Write(zap.String("filename", filename), zap.Error(err)) + } return nil, caddyhttp.Error(http.StatusNotFound, err) } else if errors.Is(err, fs.ErrPermission) { - fsrv.logger.Debug("permission denied", zap.String("filename", filename), zap.Error(err)) + if c := fsrv.logger.Check(zapcore.DebugLevel, "permission denied"); c != nil { + c.Write(zap.String("filename", filename), zap.Error(err)) + } return nil, caddyhttp.Error(http.StatusForbidden, err) } // maybe the server is under load and ran out of file descriptors? @@ -559,7 +592,9 @@ func (fsrv *FileServer) openFile(fileSystem fs.FS, filename string, w http.Respo //nolint:gosec backoff := weakrand.Intn(maxBackoff-minBackoff) + minBackoff w.Header().Set("Retry-After", strconv.Itoa(backoff)) - fsrv.logger.Debug("retry after backoff", zap.String("filename", filename), zap.Int("backoff", backoff), zap.Error(err)) + if c := fsrv.logger.Check(zapcore.DebugLevel, "retry after backoff"); c != nil { + c.Write(zap.String("filename", filename), zap.Int("backoff", backoff), zap.Error(err)) + } return nil, caddyhttp.Error(http.StatusServiceUnavailable, err) } return file, nil diff --git a/modules/caddyhttp/intercept/intercept.go b/modules/caddyhttp/intercept/intercept.go index 720a09333aa..29889dcc0ed 100644 --- a/modules/caddyhttp/intercept/intercept.go +++ b/modules/caddyhttp/intercept/intercept.go @@ -23,6 +23,7 @@ import ( "sync" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" @@ -165,7 +166,9 @@ func (ir Intercept) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddy } repl.Set("http.intercept.status_code", rec.Status()) - ir.logger.Debug("handling response", zap.Int("handler", rec.handlerIndex)) + if c := ir.logger.Check(zapcore.DebugLevel, "handling response"); c != nil { + c.Write(zap.Int("handler", rec.handlerIndex)) + } // pass the request through the response handler routes return rec.handler.Routes.Compile(next).ServeHTTP(w, r) diff --git a/modules/caddyhttp/ip_matchers.go b/modules/caddyhttp/ip_matchers.go index 2e735cb6903..99eb39dff78 100644 --- a/modules/caddyhttp/ip_matchers.go +++ b/modules/caddyhttp/ip_matchers.go @@ -26,6 +26,7 @@ import ( "github.com/google/cel-go/cel" "github.com/google/cel-go/common/types/ref" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" @@ -150,12 +151,17 @@ func (m MatchRemoteIP) Match(r *http.Request) bool { address := r.RemoteAddr clientIP, zoneID, err := parseIPZoneFromString(address) if err != nil { - m.logger.Error("getting remote IP", zap.Error(err)) + if c := m.logger.Check(zapcore.ErrorLevel, "getting remote "); c != nil { + c.Write(zap.Error(err)) + } + return false } matches, zoneFilter := matchIPByCidrZones(clientIP, zoneID, m.cidrs, m.zones) if !matches && !zoneFilter { - m.logger.Debug("zone ID from remote IP did not match", zap.String("zone", zoneID)) + if c := m.logger.Check(zapcore.DebugLevel, "zone ID from remote IP did not match"); c != nil { + c.Write(zap.String("zone", zoneID)) + } } return matches } diff --git a/modules/caddyhttp/logging.go b/modules/caddyhttp/logging.go index 823763e911e..0a389fe16a1 100644 --- a/modules/caddyhttp/logging.go +++ b/modules/caddyhttp/logging.go @@ -193,7 +193,7 @@ func (sa *StringArray) UnmarshalJSON(b []byte) error { // to use, the error log message, and any extra fields. // If err is a HandlerError, the returned values will // have richer information. -func errLogValues(err error) (status int, msg string, fields []zapcore.Field) { +func errLogValues(err error) (status int, msg string, fields func() []zapcore.Field) { var handlerErr HandlerError if errors.As(err, &handlerErr) { status = handlerErr.StatusCode @@ -202,10 +202,12 @@ func errLogValues(err error) (status int, msg string, fields []zapcore.Field) { } else { msg = handlerErr.Err.Error() } - fields = []zapcore.Field{ - zap.Int("status", handlerErr.StatusCode), - zap.String("err_id", handlerErr.ID), - zap.String("err_trace", handlerErr.Trace), + fields = func() []zapcore.Field { + return []zapcore.Field{ + zap.Int("status", handlerErr.StatusCode), + zap.String("err_id", handlerErr.ID), + zap.String("err_trace", handlerErr.Trace), + } } return } diff --git a/modules/caddyhttp/push/handler.go b/modules/caddyhttp/push/handler.go index 031a8991fb5..1fbe53d8366 100644 --- a/modules/caddyhttp/push/handler.go +++ b/modules/caddyhttp/push/handler.go @@ -20,6 +20,7 @@ import ( "strings" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/modules/caddyhttp" @@ -92,14 +93,17 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhtt // push first! for _, resource := range h.Resources { - h.logger.Debug("pushing resource", - zap.String("uri", r.RequestURI), - zap.String("push_method", resource.Method), - zap.String("push_target", resource.Target), - zap.Object("push_headers", caddyhttp.LoggableHTTPHeader{ - Header: hdr, - ShouldLogCredentials: shouldLogCredentials, - })) + if c := h.logger.Check(zapcore.DebugLevel, "pushing resource"); c != nil { + c.Write( + zap.String("uri", r.RequestURI), + zap.String("push_method", resource.Method), + zap.String("push_target", resource.Target), + zap.Object("push_headers", caddyhttp.LoggableHTTPHeader{ + Header: hdr, + ShouldLogCredentials: shouldLogCredentials, + }), + ) + } err := pusher.Push(repl.ReplaceAll(resource.Target, "."), &http.PushOptions{ Method: resource.Method, Header: hdr, @@ -209,7 +213,9 @@ func (lp linkPusher) WriteHeader(statusCode int) { if links, ok := lp.ResponseWriter.Header()["Link"]; ok { // only initiate these pushes if it hasn't been done yet if val := caddyhttp.GetVar(lp.request.Context(), pushedLink); val == nil { - lp.handler.logger.Debug("pushing Link resources", zap.Strings("linked", links)) + if c := lp.handler.logger.Check(zapcore.DebugLevel, "pushing Link resources"); c != nil { + c.Write(zap.Strings("linked", links)) + } caddyhttp.SetVar(lp.request.Context(), pushedLink, true) lp.handler.servePreloadLinks(lp.pusher, lp.header, links) } diff --git a/modules/caddyhttp/requestbody/requestbody.go b/modules/caddyhttp/requestbody/requestbody.go index d00455a8c97..1c804aa134a 100644 --- a/modules/caddyhttp/requestbody/requestbody.go +++ b/modules/caddyhttp/requestbody/requestbody.go @@ -20,6 +20,7 @@ import ( "time" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/modules/caddyhttp" @@ -69,12 +70,16 @@ func (rb RequestBody) ServeHTTP(w http.ResponseWriter, r *http.Request, next cad rc := http.NewResponseController(w) if rb.ReadTimeout > 0 { if err := rc.SetReadDeadline(time.Now().Add(rb.ReadTimeout)); err != nil { - rb.logger.Error("could not set read deadline", zap.Error(err)) + if c := rb.logger.Check(zapcore.ErrorLevel, "could not set read deadline"); c != nil { + c.Write(zap.Error(err)) + } } } if rb.WriteTimeout > 0 { if err := rc.SetWriteDeadline(time.Now().Add(rb.WriteTimeout)); err != nil { - rb.logger.Error("could not set write deadline", zap.Error(err)) + if c := rb.logger.Check(zapcore.ErrorLevel, "could not set write deadline"); c != nil { + c.Write(zap.Error(err)) + } } } } diff --git a/modules/caddyhttp/reverseproxy/fastcgi/client.go b/modules/caddyhttp/reverseproxy/fastcgi/client.go index d944c5778c1..7284fe67285 100644 --- a/modules/caddyhttp/reverseproxy/fastcgi/client.go +++ b/modules/caddyhttp/reverseproxy/fastcgi/client.go @@ -40,6 +40,7 @@ import ( "time" "go.uber.org/zap" + "go.uber.org/zap/zapcore" ) // FCGIListenSockFileno describes listen socket file number. @@ -184,10 +185,13 @@ func (f clientCloser) Close() error { return f.rwc.Close() } + logLevel := zapcore.WarnLevel if f.status >= 400 { - f.logger.Error("stderr", zap.ByteString("body", stderr)) - } else { - f.logger.Warn("stderr", zap.ByteString("body", stderr)) + logLevel = zapcore.ErrorLevel + } + + if c := f.logger.Check(logLevel, "stderr"); c != nil { + c.Write(zap.ByteString("body", stderr)) } return f.rwc.Close() diff --git a/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go b/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go index 31febdd4efe..3985465ba1a 100644 --- a/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go +++ b/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go @@ -148,10 +148,13 @@ func (t Transport) RoundTrip(r *http.Request) (*http.Response, error) { zap.Object("request", loggableReq), zap.Object("env", loggableEnv), ) - logger.Debug("roundtrip", - zap.String("dial", address), - zap.Object("env", loggableEnv), - zap.Object("request", loggableReq)) + if c := t.logger.Check(zapcore.DebugLevel, "roundtrip"); c != nil { + c.Write( + zap.String("dial", address), + zap.Object("env", loggableEnv), + zap.Object("request", loggableReq), + ) + } // connect to the backend dialer := net.Dialer{Timeout: time.Duration(t.DialTimeout)} diff --git a/modules/caddyhttp/reverseproxy/healthchecks.go b/modules/caddyhttp/reverseproxy/healthchecks.go index efa1dbf09b7..179805f2028 100644 --- a/modules/caddyhttp/reverseproxy/healthchecks.go +++ b/modules/caddyhttp/reverseproxy/healthchecks.go @@ -28,6 +28,7 @@ import ( "time" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/modules/caddyhttp" @@ -270,9 +271,12 @@ type CircuitBreaker interface { func (h *Handler) activeHealthChecker() { defer func() { if err := recover(); err != nil { - h.HealthChecks.Active.logger.Error("active health checker panicked", - zap.Any("error", err), - zap.ByteString("stack", debug.Stack())) + if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "active health checker panicked"); c != nil { + c.Write( + zap.Any("error", err), + zap.ByteString("stack", debug.Stack()), + ) + } } }() ticker := time.NewTicker(time.Duration(h.HealthChecks.Active.Interval)) @@ -295,26 +299,33 @@ func (h *Handler) doActiveHealthCheckForAllHosts() { go func(upstream *Upstream) { defer func() { if err := recover(); err != nil { - h.HealthChecks.Active.logger.Error("active health check panicked", - zap.Any("error", err), - zap.ByteString("stack", debug.Stack())) + if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "active health checker panicked"); c != nil { + c.Write( + zap.Any("error", err), + zap.ByteString("stack", debug.Stack()), + ) + } } }() networkAddr, err := caddy.NewReplacer().ReplaceOrErr(upstream.Dial, true, true) if err != nil { - h.HealthChecks.Active.logger.Error("invalid use of placeholders in dial address for active health checks", - zap.String("address", networkAddr), - zap.Error(err), - ) + if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "invalid use of placeholders in dial address for active health checks"); c != nil { + c.Write( + zap.String("address", networkAddr), + zap.Error(err), + ) + } return } addr, err := caddy.ParseNetworkAddress(networkAddr) if err != nil { - h.HealthChecks.Active.logger.Error("bad network address", - zap.String("address", networkAddr), - zap.Error(err), - ) + if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "bad network address"); c != nil { + c.Write( + zap.String("address", networkAddr), + zap.Error(err), + ) + } return } if hcp := uint(upstream.activeHealthCheckPort); hcp != 0 { @@ -324,9 +335,11 @@ func (h *Handler) doActiveHealthCheckForAllHosts() { addr.StartPort, addr.EndPort = hcp, hcp } if addr.PortRangeSize() != 1 { - h.HealthChecks.Active.logger.Error("multiple addresses (upstream must map to only one address)", - zap.String("address", networkAddr), - ) + if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "multiple addresses (upstream must map to only one address)"); c != nil { + c.Write( + zap.String("address", networkAddr), + ) + } return } hostAddr := addr.JoinHostPort(0) @@ -339,10 +352,12 @@ func (h *Handler) doActiveHealthCheckForAllHosts() { } err = h.doActiveHealthCheck(DialInfo{Network: addr.Network, Address: dialAddr}, hostAddr, networkAddr, upstream) if err != nil { - h.HealthChecks.Active.logger.Error("active health check failed", - zap.String("address", hostAddr), - zap.Error(err), - ) + if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "active health check failed"); c != nil { + c.Write( + zap.String("address", hostAddr), + zap.Error(err), + ) + } } }(upstream) } @@ -441,9 +456,12 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, networ // increment failures and then check if it has reached the threshold to mark unhealthy err := upstream.Host.countHealthFail(1) if err != nil { - h.HealthChecks.Active.logger.Error("could not count active health failure", - zap.String("host", upstream.Dial), - zap.Error(err)) + if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "could not count active health failure"); c != nil { + c.Write( + zap.String("host", upstream.Dial), + zap.Error(err), + ) + } return } if upstream.Host.activeHealthFails() >= h.HealthChecks.Active.Fails { @@ -459,14 +477,19 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, networ // increment passes and then check if it has reached the threshold to be healthy err := upstream.Host.countHealthPass(1) if err != nil { - h.HealthChecks.Active.logger.Error("could not count active health pass", - zap.String("host", upstream.Dial), - zap.Error(err)) + if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "could not count active health pass"); c != nil { + c.Write( + zap.String("host", upstream.Dial), + zap.Error(err), + ) + } return } if upstream.Host.activeHealthPasses() >= h.HealthChecks.Active.Passes { if upstream.setHealthy(true) { - h.HealthChecks.Active.logger.Info("host is up", zap.String("host", hostAddr)) + if c := h.HealthChecks.Active.logger.Check(zapcore.InfoLevel, "host is up"); c != nil { + c.Write(zap.String("host", hostAddr)) + } h.events.Emit(h.ctx, "healthy", map[string]any{"host": hostAddr}) upstream.Host.resetHealth() } @@ -476,10 +499,12 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, networ // do the request, being careful to tame the response body resp, err := h.HealthChecks.Active.httpClient.Do(req) if err != nil { - h.HealthChecks.Active.logger.Info("HTTP request failed", - zap.String("host", hostAddr), - zap.Error(err), - ) + if c := h.HealthChecks.Active.logger.Check(zapcore.InfoLevel, "HTTP request failed"); c != nil { + c.Write( + zap.String("host", hostAddr), + zap.Error(err), + ) + } markUnhealthy() return nil } @@ -496,18 +521,22 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, networ // if status code is outside criteria, mark down if h.HealthChecks.Active.ExpectStatus > 0 { if !caddyhttp.StatusCodeMatches(resp.StatusCode, h.HealthChecks.Active.ExpectStatus) { - h.HealthChecks.Active.logger.Info("unexpected status code", - zap.Int("status_code", resp.StatusCode), - zap.String("host", hostAddr), - ) + if c := h.HealthChecks.Active.logger.Check(zapcore.InfoLevel, "unexpected status code"); c != nil { + c.Write( + zap.Int("status_code", resp.StatusCode), + zap.String("host", hostAddr), + ) + } markUnhealthy() return nil } } else if resp.StatusCode < 200 || resp.StatusCode >= 300 { - h.HealthChecks.Active.logger.Info("status code out of tolerances", - zap.Int("status_code", resp.StatusCode), - zap.String("host", hostAddr), - ) + if c := h.HealthChecks.Active.logger.Check(zapcore.InfoLevel, "status code out of tolerances"); c != nil { + c.Write( + zap.Int("status_code", resp.StatusCode), + zap.String("host", hostAddr), + ) + } markUnhealthy() return nil } @@ -516,17 +545,21 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, networ if h.HealthChecks.Active.bodyRegexp != nil { bodyBytes, err := io.ReadAll(body) if err != nil { - h.HealthChecks.Active.logger.Info("failed to read response body", - zap.String("host", hostAddr), - zap.Error(err), - ) + if c := h.HealthChecks.Active.logger.Check(zapcore.InfoLevel, "failed to read response body"); c != nil { + c.Write( + zap.String("host", hostAddr), + zap.Error(err), + ) + } markUnhealthy() return nil } if !h.HealthChecks.Active.bodyRegexp.Match(bodyBytes) { - h.HealthChecks.Active.logger.Info("response body failed expectations", - zap.String("host", hostAddr), - ) + if c := h.HealthChecks.Active.logger.Check(zapcore.InfoLevel, "response body failed expectations"); c != nil { + c.Write( + zap.String("host", hostAddr), + ) + } markUnhealthy() return nil } @@ -556,9 +589,12 @@ func (h *Handler) countFailure(upstream *Upstream) { // count failure immediately err := upstream.Host.countFail(1) if err != nil { - h.HealthChecks.Passive.logger.Error("could not count failure", - zap.String("host", upstream.Dial), - zap.Error(err)) + if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "could not count failure"); c != nil { + c.Write( + zap.String("host", upstream.Dial), + zap.Error(err), + ) + } return } @@ -566,9 +602,12 @@ func (h *Handler) countFailure(upstream *Upstream) { go func(host *Host, failDuration time.Duration) { defer func() { if err := recover(); err != nil { - h.HealthChecks.Passive.logger.Error("passive health check failure forgetter panicked", - zap.Any("error", err), - zap.ByteString("stack", debug.Stack())) + if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "passive health check failure forgetter panicked"); c != nil { + c.Write( + zap.Any("error", err), + zap.ByteString("stack", debug.Stack()), + ) + } } }() timer := time.NewTimer(failDuration) @@ -581,9 +620,12 @@ func (h *Handler) countFailure(upstream *Upstream) { } err := host.countFail(-1) if err != nil { - h.HealthChecks.Passive.logger.Error("could not forget failure", - zap.String("host", upstream.Dial), - zap.Error(err)) + if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "could not forget failure"); c != nil { + c.Write( + zap.String("host", upstream.Dial), + zap.Error(err), + ) + } } }(upstream.Host, failDuration) } diff --git a/modules/caddyhttp/reverseproxy/httptransport.go b/modules/caddyhttp/reverseproxy/httptransport.go index 9929ae5d17e..144960cf920 100644 --- a/modules/caddyhttp/reverseproxy/httptransport.go +++ b/modules/caddyhttp/reverseproxy/httptransport.go @@ -33,6 +33,7 @@ import ( "github.com/pires/go-proxyproto" "github.com/quic-go/quic-go/http3" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "golang.org/x/net/http2" "github.com/caddyserver/caddy/v2" @@ -750,7 +751,9 @@ func (c *tcpRWTimeoutConn) Read(b []byte) (int, error) { if c.readTimeout > 0 { err := c.TCPConn.SetReadDeadline(time.Now().Add(c.readTimeout)) if err != nil { - c.logger.Error("failed to set read deadline", zap.Error(err)) + if ce := c.logger.Check(zapcore.ErrorLevel, "failed to set read deadline"); ce != nil { + ce.Write(zap.Error(err)) + } } } return c.TCPConn.Read(b) @@ -760,7 +763,9 @@ func (c *tcpRWTimeoutConn) Write(b []byte) (int, error) { if c.writeTimeout > 0 { err := c.TCPConn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) if err != nil { - c.logger.Error("failed to set write deadline", zap.Error(err)) + if ce := c.logger.Check(zapcore.ErrorLevel, "failed to set write deadline"); ce != nil { + ce.Write(zap.Error(err)) + } } } return c.TCPConn.Write(b) diff --git a/modules/caddyhttp/reverseproxy/metrics.go b/modules/caddyhttp/reverseproxy/metrics.go index d3c8ee03ee2..f744756d3f9 100644 --- a/modules/caddyhttp/reverseproxy/metrics.go +++ b/modules/caddyhttp/reverseproxy/metrics.go @@ -8,6 +8,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "go.uber.org/zap" + "go.uber.org/zap/zapcore" ) var reverseProxyMetrics = struct { @@ -48,9 +49,12 @@ func (m *metricsUpstreamsHealthyUpdater) Init() { go func() { defer func() { if err := recover(); err != nil { - reverseProxyMetrics.logger.Error("upstreams healthy metrics updater panicked", - zap.Any("error", err), - zap.ByteString("stack", debug.Stack())) + if c := reverseProxyMetrics.logger.Check(zapcore.ErrorLevel, "upstreams healthy metrics updater panicked"); c != nil { + c.Write( + zap.Any("error", err), + zap.ByteString("stack", debug.Stack()), + ) + } } }() diff --git a/modules/caddyhttp/reverseproxy/reverseproxy.go b/modules/caddyhttp/reverseproxy/reverseproxy.go index 44cc2f9d9a6..bcbc1ff4691 100644 --- a/modules/caddyhttp/reverseproxy/reverseproxy.go +++ b/modules/caddyhttp/reverseproxy/reverseproxy.go @@ -33,6 +33,7 @@ import ( "time" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "golang.org/x/net/http/httpguts" "github.com/caddyserver/caddy/v2" @@ -439,7 +440,9 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht if h.LoadBalancing != nil { lbWait = time.Duration(h.LoadBalancing.TryInterval) } - h.logger.Debug("retrying", zap.Error(proxyErr), zap.Duration("after", lbWait)) + if c := h.logger.Check(zapcore.DebugLevel, "retrying"); c != nil { + c.Write(zap.Error(proxyErr), zap.Duration("after", lbWait)) + } } retries++ } @@ -466,13 +469,17 @@ func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w h if h.DynamicUpstreams != nil { dUpstreams, err := h.DynamicUpstreams.GetUpstreams(r) if err != nil { - h.logger.Error("failed getting dynamic upstreams; falling back to static upstreams", zap.Error(err)) + if c := h.logger.Check(zapcore.ErrorLevel, "failed getting dynamic upstreams; falling back to static upstreams"); c != nil { + c.Write(zap.Error(err)) + } } else { upstreams = dUpstreams for _, dUp := range dUpstreams { h.provisionUpstream(dUp) } - h.logger.Debug("provisioned dynamic upstreams", zap.Int("count", len(dUpstreams))) + if c := h.logger.Check(zapcore.DebugLevel, "provisioned dynamic upstreams"); c != nil { + c.Write(zap.Int("count", len(dUpstreams))) + } defer func() { // these upstreams are dynamic, so they are only used for this iteration // of the proxy loop; be sure to let them go away when we're done with them @@ -503,9 +510,12 @@ func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w h return true, fmt.Errorf("making dial info: %v", err) } - h.logger.Debug("selected upstream", - zap.String("dial", dialInfo.Address), - zap.Int("total_upstreams", len(upstreams))) + if c := h.logger.Check(zapcore.DebugLevel, "selected upstream"); c != nil { + c.Write( + zap.String("dial", dialInfo.Address), + zap.Int("total_upstreams", len(upstreams)), + ) + } // attach to the request information about how to dial the upstream; // this is necessary because the information cannot be sufficiently @@ -811,16 +821,22 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origRe ShouldLogCredentials: shouldLogCredentials, }), ) + if err != nil { - logger.Debug("upstream roundtrip", zap.Error(err)) + if c := logger.Check(zapcore.DebugLevel, "upstream roundtrip"); c != nil { + c.Write(zap.Error(err)) + } return err } - logger.Debug("upstream roundtrip", - zap.Object("headers", caddyhttp.LoggableHTTPHeader{ - Header: res.Header, - ShouldLogCredentials: shouldLogCredentials, - }), - zap.Int("status", res.StatusCode)) + if c := logger.Check(zapcore.DebugLevel, "upstream roundtrip"); c != nil { + c.Write( + zap.Object("headers", caddyhttp.LoggableHTTPHeader{ + Header: res.Header, + ShouldLogCredentials: shouldLogCredentials, + }), + zap.Int("status", res.StatusCode), + ) + } // duration until upstream wrote response headers (roundtrip duration) repl.Set("http.reverse_proxy.upstream.latency", duration) @@ -879,7 +895,9 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origRe repl.Set("http.reverse_proxy.status_code", res.StatusCode) repl.Set("http.reverse_proxy.status_text", res.Status) - logger.Debug("handling response", zap.Int("handler", i)) + if c := logger.Check(zapcore.DebugLevel, "handling response"); c != nil { + c.Write(zap.Int("handler", i)) + } // we make some data available via request context to child routes // so that they may inherit some options and functions from the @@ -975,7 +993,9 @@ func (h *Handler) finalizeResponse( err := h.copyResponse(rw, res.Body, h.flushInterval(req, res), logger) errClose := res.Body.Close() // close now, instead of defer, to populate res.Trailer if h.VerboseLogs || errClose != nil { - logger.Debug("closed response body from upstream", zap.Error(errClose)) + if c := logger.Check(zapcore.DebugLevel, "closed response body from upstream"); c != nil { + c.Write(zap.Error(errClose)) + } } if err != nil { // we're streaming the response and we've already written headers, so @@ -983,7 +1003,9 @@ func (h *Handler) finalizeResponse( // we'll just log the error and abort the stream here and panic just as // the standard lib's proxy to propagate the stream error. // see issue https://github.com/caddyserver/caddy/issues/5951 - logger.Warn("aborting with incomplete response", zap.Error(err)) + if c := logger.Check(zapcore.WarnLevel, "aborting with incomplete response"); c != nil { + c.Write(zap.Error(err)) + } // no extra logging from stdlib panic(http.ErrAbortHandler) } diff --git a/modules/caddyhttp/reverseproxy/streaming.go b/modules/caddyhttp/reverseproxy/streaming.go index a696ac7fbed..c871a3fa12b 100644 --- a/modules/caddyhttp/reverseproxy/streaming.go +++ b/modules/caddyhttp/reverseproxy/streaming.go @@ -31,6 +31,7 @@ import ( "unsafe" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "golang.org/x/net/http/httpguts" ) @@ -41,14 +42,18 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup, // Taken from https://github.com/golang/go/commit/5c489514bc5e61ad9b5b07bd7d8ec65d66a0512a // We know reqUpType is ASCII, it's checked by the caller. if !asciiIsPrint(resUpType) { - logger.Debug("backend tried to switch to invalid protocol", - zap.String("backend_upgrade", resUpType)) + if c := logger.Check(zapcore.DebugLevel, "backend tried to switch to invalid protocol"); c != nil { + c.Write(zap.String("backend_upgrade", resUpType)) + } return } if !asciiEqualFold(reqUpType, resUpType) { - logger.Debug("backend tried to switch to unexpected protocol via Upgrade header", - zap.String("backend_upgrade", resUpType), - zap.String("requested_upgrade", reqUpType)) + if c := logger.Check(zapcore.DebugLevel, "backend tried to switch to unexpected protocol via Upgrade header"); c != nil { + c.Write( + zap.String("backend_upgrade", resUpType), + zap.String("requested_upgrade", reqUpType), + ) + } return } @@ -68,12 +73,16 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup, //nolint:bodyclose conn, brw, hijackErr := http.NewResponseController(rw).Hijack() if errors.Is(hijackErr, http.ErrNotSupported) { - h.logger.Error("can't switch protocols using non-Hijacker ResponseWriter", zap.String("type", fmt.Sprintf("%T", rw))) + if c := logger.Check(zapcore.ErrorLevel, "can't switch protocols using non-Hijacker ResponseWriter"); c != nil { + c.Write(zap.String("type", fmt.Sprintf("%T", rw))) + } return } if hijackErr != nil { - h.logger.Error("hijack failed on protocol switch", zap.Error(hijackErr)) + if c := logger.Check(zapcore.ErrorLevel, "hijack failed on protocol switch"); c != nil { + c.Write(zap.Error(hijackErr)) + } return } @@ -93,11 +102,15 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup, start := time.Now() defer func() { conn.Close() - logger.Debug("connection closed", zap.Duration("duration", time.Since(start))) + if c := logger.Check(zapcore.DebugLevel, "hijack failed on protocol switch"); c != nil { + c.Write(zap.Duration("duration", time.Since(start))) + } }() if err := brw.Flush(); err != nil { - logger.Debug("response flush", zap.Error(err)) + if c := logger.Check(zapcore.DebugLevel, "response flush"); c != nil { + c.Write(zap.Error(err)) + } return } @@ -107,7 +120,9 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup, data, _ := brw.Peek(buffered) _, err := backConn.Write(data) if err != nil { - logger.Debug("backConn write failed", zap.Error(err)) + if c := logger.Check(zapcore.DebugLevel, "backConn write failed"); c != nil { + c.Write(zap.Error(err)) + } return } } @@ -148,9 +163,13 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup, go spc.copyFromBackend(errc) select { case err := <-errc: - logger.Debug("streaming error", zap.Error(err)) + if c := logger.Check(zapcore.DebugLevel, "streaming error"); c != nil { + c.Write(zap.Error(err)) + } case time := <-timeoutc: - logger.Debug("stream timed out", zap.Time("timeout", time)) + if c := logger.Check(zapcore.DebugLevel, "stream timed out"); c != nil { + c.Write(zap.Time("timeout", time)) + } } } @@ -247,7 +266,9 @@ func (h Handler) copyBuffer(dst io.Writer, src io.Reader, buf []byte, logger *za logger.Debug("waiting to read from upstream") nr, rerr := src.Read(buf) logger := logger.With(zap.Int("read", nr)) - logger.Debug("read from upstream", zap.Error(rerr)) + if c := logger.Check(zapcore.DebugLevel, "read from upstream"); c != nil { + c.Write(zap.Error(rerr)) + } if rerr != nil && rerr != io.EOF && rerr != context.Canceled { // TODO: this could be useful to know (indeed, it revealed an error in our // fastcgi PoC earlier; but it's this single error report here that necessitates @@ -256,7 +277,9 @@ func (h Handler) copyBuffer(dst io.Writer, src io.Reader, buf []byte, logger *za // something we need to report to the client, but read errors are a problem on our // end for sure. so we need to decide what we want.) // p.logf("copyBuffer: ReverseProxy read error during body copy: %v", rerr) - h.logger.Error("reading from backend", zap.Error(rerr)) + if c := logger.Check(zapcore.ErrorLevel, "reading from backend"); c != nil { + c.Write(zap.Error(rerr)) + } } if nr > 0 { logger.Debug("writing to downstream") @@ -264,10 +287,13 @@ func (h Handler) copyBuffer(dst io.Writer, src io.Reader, buf []byte, logger *za if nw > 0 { written += int64(nw) } - logger.Debug("wrote to downstream", - zap.Int("written", nw), - zap.Int64("written_total", written), - zap.Error(werr)) + if c := logger.Check(zapcore.DebugLevel, "wrote to downstream"); c != nil { + c.Write( + zap.Int("written", nw), + zap.Int64("written_total", written), + zap.Error(werr), + ) + } if werr != nil { return written, fmt.Errorf("writing: %w", werr) } @@ -347,13 +373,17 @@ func (h *Handler) cleanupConnections() error { if len(h.connections) > 0 { delay := time.Duration(h.StreamCloseDelay) h.connectionsCloseTimer = time.AfterFunc(delay, func() { - h.logger.Debug("closing streaming connections after delay", - zap.Duration("delay", delay)) + if c := h.logger.Check(zapcore.DebugLevel, "closing streaming connections after delay"); c != nil { + c.Write(zap.Duration("delay", delay)) + } err := h.closeConnections() if err != nil { - h.logger.Error("failed to closed connections after delay", - zap.Error(err), - zap.Duration("delay", delay)) + if c := h.logger.Check(zapcore.ErrorLevel, "failed to closed connections after delay"); c != nil { + c.Write( + zap.Error(err), + zap.Duration("delay", delay), + ) + } } }) } @@ -494,7 +524,9 @@ func (m *maxLatencyWriter) Write(p []byte) (n int, err error) { m.mu.Lock() defer m.mu.Unlock() n, err = m.dst.Write(p) - m.logger.Debug("wrote bytes", zap.Int("n", n), zap.Error(err)) + if c := m.logger.Check(zapcore.DebugLevel, "wrote bytes"); c != nil { + c.Write(zap.Int("n", n), zap.Error(err)) + } if m.latency < 0 { m.logger.Debug("flushing immediately") //nolint:errcheck @@ -510,7 +542,9 @@ func (m *maxLatencyWriter) Write(p []byte) (n int, err error) { } else { m.t.Reset(m.latency) } - m.logger.Debug("timer set for delayed flush", zap.Duration("duration", m.latency)) + if c := m.logger.Check(zapcore.DebugLevel, "timer set for delayed flush"); c != nil { + c.Write(zap.Duration("duration", m.latency)) + } m.flushPending = true return } diff --git a/modules/caddyhttp/reverseproxy/upstreams.go b/modules/caddyhttp/reverseproxy/upstreams.go index c8ba930d203..aa59dc41b2f 100644 --- a/modules/caddyhttp/reverseproxy/upstreams.go +++ b/modules/caddyhttp/reverseproxy/upstreams.go @@ -12,6 +12,7 @@ import ( "time" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" ) @@ -136,10 +137,13 @@ func (su SRVUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) { return allNew(cached.upstreams), nil } - su.logger.Debug("refreshing SRV upstreams", - zap.String("service", service), - zap.String("proto", proto), - zap.String("name", name)) + if c := su.logger.Check(zapcore.DebugLevel, "refreshing SRV upstreams"); c != nil { + c.Write( + zap.String("service", service), + zap.String("proto", proto), + zap.String("name", name), + ) + } _, records, err := su.resolver.LookupSRV(r.Context(), service, proto, name) if err != nil { @@ -148,23 +152,30 @@ func (su SRVUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) { // only return an error if no records were also returned. if len(records) == 0 { if su.GracePeriod > 0 { - su.logger.Error("SRV lookup failed; using previously cached", zap.Error(err)) + if c := su.logger.Check(zapcore.ErrorLevel, "SRV lookup failed; using previously cached"); c != nil { + c.Write(zap.Error(err)) + } cached.freshness = time.Now().Add(time.Duration(su.GracePeriod) - time.Duration(su.Refresh)) srvs[suAddr] = cached return allNew(cached.upstreams), nil } return nil, err } - su.logger.Warn("SRV records filtered", zap.Error(err)) + if c := su.logger.Check(zapcore.WarnLevel, "SRV records filtered"); c != nil { + c.Write(zap.Error(err)) + } } upstreams := make([]Upstream, len(records)) for i, rec := range records { - su.logger.Debug("discovered SRV record", - zap.String("target", rec.Target), - zap.Uint16("port", rec.Port), - zap.Uint16("priority", rec.Priority), - zap.Uint16("weight", rec.Weight)) + if c := su.logger.Check(zapcore.DebugLevel, "discovered SRV record"); c != nil { + c.Write( + zap.String("target", rec.Target), + zap.Uint16("port", rec.Port), + zap.Uint16("priority", rec.Priority), + zap.Uint16("weight", rec.Weight), + ) + } addr := net.JoinHostPort(rec.Target, strconv.Itoa(int(rec.Port))) upstreams[i] = Upstream{Dial: addr} } @@ -361,10 +372,13 @@ func (au AUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) { name := repl.ReplaceAll(au.Name, "") port := repl.ReplaceAll(au.Port, "") - au.logger.Debug("refreshing A upstreams", - zap.String("version", ipVersion), - zap.String("name", name), - zap.String("port", port)) + if c := au.logger.Check(zapcore.DebugLevel, "refreshing A upstreams"); c != nil { + c.Write( + zap.String("version", ipVersion), + zap.String("name", name), + zap.String("port", port), + ) + } ips, err := au.resolver.LookupIP(r.Context(), ipVersion, name) if err != nil { @@ -373,8 +387,9 @@ func (au AUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) { upstreams := make([]Upstream, len(ips)) for i, ip := range ips { - au.logger.Debug("discovered A record", - zap.String("ip", ip.String())) + if c := au.logger.Check(zapcore.DebugLevel, "discovered A record"); c != nil { + c.Write(zap.String("ip", ip.String())) + } upstreams[i] = Upstream{ Dial: net.JoinHostPort(ip.String(), port), } @@ -467,11 +482,16 @@ func (mu MultiUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) { up, err := src.GetUpstreams(r) if err != nil { - mu.logger.Error("upstream source returned error", - zap.Int("source_idx", i), - zap.Error(err)) + if c := mu.logger.Check(zapcore.ErrorLevel, "upstream source returned error"); c != nil { + c.Write( + zap.Int("source_idx", i), + zap.Error(err), + ) + } } else if len(up) == 0 { - mu.logger.Warn("upstream source returned 0 upstreams", zap.Int("source_idx", i)) + if c := mu.logger.Check(zapcore.WarnLevel, "upstream source returned 0 upstreams"); c != nil { + c.Write(zap.Int("source_idx", i)) + } } else { upstreams = append(upstreams, up...) } diff --git a/modules/caddyhttp/rewrite/rewrite.go b/modules/caddyhttp/rewrite/rewrite.go index c45ad98a20a..e766827298b 100644 --- a/modules/caddyhttp/rewrite/rewrite.go +++ b/modules/caddyhttp/rewrite/rewrite.go @@ -133,19 +133,17 @@ func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddy repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) const message = "rewrote request" - if rewr.logger.Check(zap.DebugLevel, message) == nil { + c := rewr.logger.Check(zap.DebugLevel, message) + if c == nil { rewr.Rewrite(r, repl) return next.ServeHTTP(w, r) } - logger := rewr.logger.With( - zap.Object("request", caddyhttp.LoggableHTTPRequest{Request: r}), - ) - changed := rewr.Rewrite(r, repl) if changed { - logger.Debug(message, + c.Write( + zap.Object("request", caddyhttp.LoggableHTTPRequest{Request: r}), zap.String("method", r.Method), zap.String("uri", r.RequestURI), ) diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go index 6caaabcdaf0..9dfa1bb6d3b 100644 --- a/modules/caddyhttp/server.go +++ b/modules/caddyhttp/server.go @@ -275,7 +275,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.ProtoMajor < 3 { err := s.h3server.SetQUICHeaders(w.Header()) if err != nil { - s.logger.Error("setting HTTP/3 Alt-Svc header", zap.Error(err)) + if c := s.logger.Check(zapcore.ErrorLevel, "setting HTTP/3 Alt-Svc header"); c != nil { + c.Write(zap.Error(err)) + } } } } @@ -283,9 +285,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // reject very long methods; probably a mistake or an attack if len(r.Method) > 32 { if s.shouldLogRequest(r) { - s.accessLogger.Debug("rejecting request with long method", - zap.String("method_trunc", r.Method[:32]), - zap.String("remote_addr", r.RemoteAddr)) + if c := s.accessLogger.Check(zapcore.DebugLevel, "rejecting request with long method"); c != nil { + c.Write( + zap.String("method_trunc", r.Method[:32]), + zap.String("remote_addr", r.RemoteAddr), + ) + } } w.WriteHeader(http.StatusMethodNotAllowed) return @@ -300,7 +305,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { //nolint:bodyclose err := http.NewResponseController(w).EnableFullDuplex() if err != nil { - s.logger.Warn("failed to enable full duplex", zap.Error(err)) + if c := s.logger.Check(zapcore.WarnLevel, "failed to enable full duplex"); c != nil { + c.Write(zap.Error(err)) + } } } @@ -379,6 +386,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // add HTTP error information to request context r = s.Errors.WithError(r, err) + var fields []zapcore.Field if s.Errors != nil && len(s.Errors.Routes) > 0 { // execute user-defined error handling route err2 := s.errorHandlerChain.ServeHTTP(w, r) @@ -386,17 +394,28 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // user's error route handled the error response // successfully, so now just log the error for _, logger := range errLoggers { - logger.Debug(errMsg, errFields...) + if c := logger.Check(zapcore.DebugLevel, errMsg); c != nil { + if fields == nil { + fields = errFields() + } + + c.Write(fields...) + } } } else { // well... this is awkward - errFields = append([]zapcore.Field{ - zap.String("error", err2.Error()), - zap.Namespace("first_error"), - zap.String("msg", errMsg), - }, errFields...) for _, logger := range errLoggers { - logger.Error("error handling handler error", errFields...) + if c := logger.Check(zapcore.ErrorLevel, "error handling handler error"); c != nil { + if fields == nil { + fields = errFields() + fields = append([]zapcore.Field{ + zap.String("error", err2.Error()), + zap.Namespace("first_error"), + zap.String("msg", errMsg), + }, fields...) + } + c.Write(fields...) + } } if handlerErr, ok := err.(HandlerError); ok { w.WriteHeader(handlerErr.StatusCode) @@ -405,11 +424,17 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } } else { + logLevel := zapcore.DebugLevel + if errStatus >= 500 { + logLevel = zapcore.ErrorLevel + } + for _, logger := range errLoggers { - if errStatus >= 500 { - logger.Error(errMsg, errFields...) - } else { - logger.Debug(errMsg, errFields...) + if c := logger.Check(logLevel, errMsg); c != nil { + if fields == nil { + fields = errFields() + } + c.Write(fields...) } } w.WriteHeader(errStatus) @@ -746,7 +771,9 @@ func (s *Server) logTrace(mh MiddlewareHandler) { if s.Logs == nil || !s.Logs.Trace { return } - s.traceLogger.Debug(caddy.GetModuleName(mh), zap.Any("module", mh)) + if c := s.traceLogger.Check(zapcore.DebugLevel, caddy.GetModuleName(mh)); c != nil { + c.Write(zap.Any("module", mh)) + } } // logRequest logs the request to access logs, unless skipped. @@ -759,50 +786,64 @@ func (s *Server) logRequest( return } - repl.Set("http.response.status", wrec.Status()) // will be 0 if no response is written by us (Go will write 200 to client) - repl.Set("http.response.size", wrec.Size()) + status := wrec.Status() + size := wrec.Size() + + repl.Set("http.response.status", status) // will be 0 if no response is written by us (Go will write 200 to client) + repl.Set("http.response.size", size) repl.Set("http.response.duration", duration) repl.Set("http.response.duration_ms", duration.Seconds()*1e3) // multiply seconds to preserve decimal (see #4666) - userID, _ := repl.GetString("http.auth.user.id") - - reqBodyLength := 0 - if bodyReader != nil { - reqBodyLength = bodyReader.Length - } - - extra := r.Context().Value(ExtraLogFieldsCtxKey).(*ExtraLogFields) - - fieldCount := 6 - fields := make([]zapcore.Field, 0, fieldCount+len(extra.fields)) - fields = append(fields, - zap.Int("bytes_read", reqBodyLength), - zap.String("user_id", userID), - zap.Duration("duration", *duration), - zap.Int("size", wrec.Size()), - zap.Int("status", wrec.Status()), - zap.Object("resp_headers", LoggableHTTPHeader{ - Header: wrec.Header(), - ShouldLogCredentials: shouldLogCredentials, - })) - fields = append(fields, extra.fields...) - loggers := []*zap.Logger{accLog} if s.Logs != nil { loggers = s.Logs.wrapLogger(accLog, r) } - // wrapping may return multiple loggers, so we log to all of them + message := "handled request" + if nop, ok := GetVar(r.Context(), "unhandled").(bool); ok && nop { + message = "NOP" + } + + logLevel := zapcore.InfoLevel + if status >= 500 { + logLevel = zapcore.ErrorLevel + } + + var fields []zapcore.Field for _, logger := range loggers { - logAtLevel := logger.Info - if wrec.Status() >= 500 { - logAtLevel = logger.Error + c := logger.Check(logLevel, message) + if c == nil { + continue } - message := "handled request" - if nop, ok := GetVar(r.Context(), "unhandled").(bool); ok && nop { - message = "NOP" + + if fields == nil { + + userID, _ := repl.GetString("http.auth.user.id") + + reqBodyLength := 0 + if bodyReader != nil { + reqBodyLength = bodyReader.Length + } + + extra := r.Context().Value(ExtraLogFieldsCtxKey).(*ExtraLogFields) + + fieldCount := 6 + fields = make([]zapcore.Field, 0, fieldCount+len(extra.fields)) + fields = append(fields, + zap.Int("bytes_read", reqBodyLength), + zap.String("user_id", userID), + zap.Duration("duration", *duration), + zap.Int("size", size), + zap.Int("status", status), + zap.Object("resp_headers", LoggableHTTPHeader{ + Header: wrec.Header(), + ShouldLogCredentials: shouldLogCredentials, + }), + ) + fields = append(fields, extra.fields...) } - logAtLevel(message, fields...) + + c.Write(fields...) } } diff --git a/modules/caddyhttp/server_test.go b/modules/caddyhttp/server_test.go index d382a6963de..2c8033d45d2 100644 --- a/modules/caddyhttp/server_test.go +++ b/modules/caddyhttp/server_test.go @@ -121,6 +121,29 @@ func BenchmarkServer_LogRequest(b *testing.B) { } } +func BenchmarkServer_LogRequest_NopLogger(b *testing.B) { + s := &Server{} + + extra := new(ExtraLogFields) + ctx := context.WithValue(context.Background(), ExtraLogFieldsCtxKey, extra) + + req := httptest.NewRequest(http.MethodGet, "/", nil).WithContext(ctx) + rec := httptest.NewRecorder() + wrec := NewResponseRecorder(rec, nil, nil) + + duration := 50 * time.Millisecond + repl := NewTestReplacer(req) + bodyReader := &lengthReader{Source: req.Body} + + accLog := zap.NewNop() + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + s.logRequest(accLog, req, wrec, &duration, repl, bodyReader, false) + } +} + func BenchmarkServer_LogRequest_WithTraceID(b *testing.B) { s := &Server{} diff --git a/modules/caddyhttp/tracing/tracerprovider.go b/modules/caddyhttp/tracing/tracerprovider.go index 035425ed646..0a376697471 100644 --- a/modules/caddyhttp/tracing/tracerprovider.go +++ b/modules/caddyhttp/tracing/tracerprovider.go @@ -7,6 +7,7 @@ import ( sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.uber.org/zap" + "go.uber.org/zap/zapcore" ) // globalTracerProvider stores global tracer provider and is responsible for graceful shutdown when nobody is using it. @@ -47,7 +48,9 @@ func (t *tracerProvider) cleanupTracerProvider(logger *zap.Logger) error { if t.tracerProvider != nil { // tracerProvider.ForceFlush SHOULD be invoked according to https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#forceflush if err := t.tracerProvider.ForceFlush(context.Background()); err != nil { - logger.Error("forcing flush", zap.Error(err)) + if c := logger.Check(zapcore.ErrorLevel, "forcing flush"); c != nil { + c.Write(zap.Error(err)) + } } // tracerProvider.Shutdown MUST be invoked according to https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#shutdown diff --git a/modules/caddypki/acmeserver/acmeserver.go b/modules/caddypki/acmeserver/acmeserver.go index eaf6e930ab2..aeb4eab8ec3 100644 --- a/modules/caddypki/acmeserver/acmeserver.go +++ b/modules/caddypki/acmeserver/acmeserver.go @@ -35,6 +35,7 @@ import ( "github.com/smallstep/certificates/db" "github.com/smallstep/nosql" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/modules/caddyhttp" @@ -243,10 +244,14 @@ func (ash Handler) Cleanup() error { key := ash.getDatabaseKey() deleted, err := databasePool.Delete(key) if deleted { - ash.logger.Debug("unloading unused CA database", zap.String("db_key", key)) + if c := ash.logger.Check(zapcore.DebugLevel, "unloading unused CA database"); c != nil { + c.Write(zap.String("db_key", key)) + } } if err != nil { - ash.logger.Error("closing CA database", zap.String("db_key", key), zap.Error(err)) + if c := ash.logger.Check(zapcore.ErrorLevel, "closing CA database"); c != nil { + c.Write(zap.String("db_key", key), zap.Error(err)) + } } return err } @@ -271,7 +276,9 @@ func (ash Handler) openDatabase() (*db.AuthDB, error) { }) if loaded { - ash.logger.Debug("loaded preexisting CA database", zap.String("db_key", key)) + if c := ash.logger.Check(zapcore.DebugLevel, "loaded preexisting CA database"); c != nil { + c.Write(zap.String("db_key", key)) + } } return database.(databaseCloser).DB, err diff --git a/modules/caddytls/acmeissuer.go b/modules/caddytls/acmeissuer.go index f46e296e3ea..9dfeff724a1 100644 --- a/modules/caddytls/acmeissuer.go +++ b/modules/caddytls/acmeissuer.go @@ -30,6 +30,7 @@ import ( "github.com/caddyserver/zerossl" "github.com/mholt/acmez/v2/acme" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig" @@ -321,7 +322,9 @@ func (iss *ACMEIssuer) generateZeroSSLEABCredentials(ctx context.Context, acct a return nil, acct, fmt.Errorf("failed getting EAB credentials: HTTP %d", resp.StatusCode) } - iss.logger.Info("generated EAB credentials", zap.String("key_id", result.EABKID)) + if c := iss.logger.Check(zapcore.InfoLevel, "generated EAB credentials"); c != nil { + c.Write(zap.String("key_id", result.EABKID)) + } return &acme.EAB{ KeyID: result.EABKID, diff --git a/modules/caddytls/automation.go b/modules/caddytls/automation.go index 781818ed139..e2e02221df3 100644 --- a/modules/caddytls/automation.go +++ b/modules/caddytls/automation.go @@ -26,6 +26,7 @@ import ( "github.com/caddyserver/certmagic" "github.com/mholt/acmez/v2" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" ) @@ -292,21 +293,30 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error { remoteIP, _, _ = net.SplitHostPort(remote.String()) } } - tlsApp.logger.Debug("asking for permission for on-demand certificate", - zap.String("remote_ip", remoteIP), - zap.String("domain", name)) + if c := tlsApp.logger.Check(zapcore.DebugLevel, "asking for permission for on-demand certificate"); c != nil { + c.Write( + zap.String("remote_ip", remoteIP), + zap.String("domain", name), + ) + } // ask the permission module if this cert is allowed if err := tlsApp.Automation.OnDemand.permission.CertificateAllowed(ctx, name); err != nil { // distinguish true errors from denials, because it's important to elevate actual errors if errors.Is(err, ErrPermissionDenied) { - tlsApp.logger.Debug("on-demand certificate issuance denied", - zap.String("domain", name), - zap.Error(err)) + if c := tlsApp.logger.Check(zapcore.DebugLevel, "on-demand certificate issuance denied"); c != nil { + c.Write( + zap.String("domain", name), + zap.Error(err), + ) + } } else { - tlsApp.logger.Error("failed to get permission for on-demand certificate", - zap.String("domain", name), - zap.Error(err)) + if c := tlsApp.logger.Check(zapcore.ErrorLevel, "failed to get permission for on-demand certificate"); c != nil { + c.Write( + zap.String("domain", name), + zap.Error(err), + ) + } } return err } diff --git a/modules/caddytls/certmanagers.go b/modules/caddytls/certmanagers.go index f65a78b09ad..56950bc84df 100644 --- a/modules/caddytls/certmanagers.go +++ b/modules/caddytls/certmanagers.go @@ -12,6 +12,7 @@ import ( "github.com/caddyserver/certmagic" "github.com/tailscale/tscert" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" @@ -46,7 +47,9 @@ func (ts Tailscale) GetCertificate(ctx context.Context, hello *tls.ClientHelloIn return nil, nil // pass-thru: Tailscale can't offer a cert for this name } if err != nil { - ts.logger.Warn("could not get status; will try to get certificate anyway", zap.Error(err)) + if c := ts.logger.Check(zapcore.WarnLevel, "could not get status; will try to get certificate anyway"); c != nil { + c.Write(zap.Error(err)) + } } return tscert.GetCertificateWithContext(ctx, hello) } diff --git a/modules/caddytls/connpolicy.go b/modules/caddytls/connpolicy.go index e2890c848ab..2ff41f7b221 100644 --- a/modules/caddytls/connpolicy.go +++ b/modules/caddytls/connpolicy.go @@ -28,6 +28,7 @@ import ( "github.com/mholt/acmez/v2" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig" @@ -338,8 +339,9 @@ func (p *ConnectionPolicy) buildStandardTLSConfig(ctx caddy.Context) error { cfg.KeyLogWriter = logFile.(io.Writer) - tlsApp.logger.Warn("TLS SECURITY COMPROMISED: secrets logging is enabled!", - zap.String("log_filename", filename)) + if c := tlsApp.logger.Check(zapcore.WarnLevel, "TLS SECURITY COMPROMISED: secrets logging is enabled!"); c != nil { + c.Write(zap.String("log_filename", filename)) + } } setDefaultTLSParams(cfg) diff --git a/modules/caddytls/matchers.go b/modules/caddytls/matchers.go index 65bbfa311e1..f44b4c02a5b 100644 --- a/modules/caddytls/matchers.go +++ b/modules/caddytls/matchers.go @@ -25,6 +25,7 @@ import ( "github.com/caddyserver/certmagic" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" @@ -291,7 +292,9 @@ func (m MatchRemoteIP) Match(hello *tls.ClientHelloInfo) bool { } ipAddr, err := netip.ParseAddr(ipStr) if err != nil { - m.logger.Error("invalid client IP address", zap.String("ip", ipStr)) + if c := m.logger.Check(zapcore.ErrorLevel, "invalid client IP address"); c != nil { + c.Write(zap.String("ip", ipStr)) + } return false } return (len(m.cidrs) == 0 || m.matches(ipAddr, m.cidrs)) && @@ -408,7 +411,9 @@ func (m MatchLocalIP) Match(hello *tls.ClientHelloInfo) bool { } ipAddr, err := netip.ParseAddr(ipStr) if err != nil { - m.logger.Error("invalid local IP address", zap.String("ip", ipStr)) + if c := m.logger.Check(zapcore.ErrorLevel, "invalid local IP address"); c != nil { + c.Write(zap.String("ip", ipStr)) + } return false } return (len(m.cidrs) == 0 || m.matches(ipAddr, m.cidrs)) diff --git a/modules/caddytls/ondemand.go b/modules/caddytls/ondemand.go index 060a3ac6a74..89abfe03f41 100644 --- a/modules/caddytls/ondemand.go +++ b/modules/caddytls/ondemand.go @@ -26,6 +26,7 @@ import ( "github.com/caddyserver/certmagic" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" @@ -156,10 +157,13 @@ func (p PermissionByHTTP) CertificateAllowed(ctx context.Context, name string) e remote = chi.Conn.RemoteAddr().String() } - p.logger.Debug("asking permission endpoint", - zap.String("remote", remote), - zap.String("domain", name), - zap.String("url", askURLString)) + if c := p.logger.Check(zapcore.DebugLevel, "asking permission endpoint"); c != nil { + c.Write( + zap.String("remote", remote), + zap.String("domain", name), + zap.String("url", askURLString), + ) + } resp, err := onDemandAskClient.Get(askURLString) if err != nil { @@ -168,11 +172,14 @@ func (p PermissionByHTTP) CertificateAllowed(ctx context.Context, name string) e } resp.Body.Close() - p.logger.Debug("response from permission endpoint", - zap.String("remote", remote), - zap.String("domain", name), - zap.String("url", askURLString), - zap.Int("status", resp.StatusCode)) + if c := p.logger.Check(zapcore.DebugLevel, "response from permission endpoint"); c != nil { + c.Write( + zap.String("remote", remote), + zap.String("domain", name), + zap.String("url", askURLString), + zap.Int("status", resp.StatusCode), + ) + } if resp.StatusCode < 200 || resp.StatusCode > 299 { return fmt.Errorf("%s: %w %s - non-2xx status code %d", name, ErrPermissionDenied, askEndpoint, resp.StatusCode) diff --git a/modules/caddytls/tls.go b/modules/caddytls/tls.go index b30b10c24ad..5f3d0eaeba1 100644 --- a/modules/caddytls/tls.go +++ b/modules/caddytls/tls.go @@ -27,6 +27,7 @@ import ( "github.com/caddyserver/certmagic" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/modules/caddyevents" @@ -323,8 +324,9 @@ func (t *TLS) Start() error { if t.Automation.OnDemand == nil || (t.Automation.OnDemand.Ask == "" && t.Automation.OnDemand.permission == nil) { for _, ap := range t.Automation.Policies { if ap.OnDemand && ap.isWildcardOrDefault() { - t.logger.Warn("YOUR SERVER MAY BE VULNERABLE TO ABUSE: on-demand TLS is enabled, but no protections are in place", - zap.String("docs", "https://caddyserver.com/docs/automatic-https#on-demand-tls")) + if c := t.logger.Check(zapcore.WarnLevel, "YOUR SERVER MAY BE VULNERABLE TO ABUSE: on-demand TLS is enabled, but no protections are in place"); c != nil { + c.Write(zap.String("docs", "https://caddyserver.com/docs/automatic-https#on-demand-tls")) + } break } } @@ -408,9 +410,12 @@ func (t *TLS) Cleanup() error { // give the new TLS app a "kick" to manage certs that it is configured for // with its own configuration instead of the one we just evicted if err := nextTLSApp.Manage(reManage); err != nil { - t.logger.Error("re-managing unloaded certificates with new config", - zap.Strings("subjects", reManage), - zap.Error(err)) + if c := t.logger.Check(zapcore.ErrorLevel, "re-managing unloaded certificates with new config"); c != nil { + c.Write( + zap.Strings("subjects", reManage), + zap.Error(err), + ) + } } } else { // no more TLS app running, so delete in-memory cert cache @@ -653,7 +658,9 @@ func (t *TLS) cleanStorageUnits() { id, err := caddy.InstanceID() if err != nil { - t.logger.Warn("unable to get instance ID; storage clean stamps will be incomplete", zap.Error(err)) + if c := t.logger.Check(zapcore.WarnLevel, "unable to get instance ID; storage clean stamps will be incomplete"); c != nil { + c.Write(zap.Error(err)) + } } options := certmagic.CleanStorageOptions{ Logger: t.logger, @@ -669,7 +676,9 @@ func (t *TLS) cleanStorageUnits() { if err != nil { // probably don't want to return early, since we should still // see if any other storages can get cleaned up - t.logger.Error("could not clean default/global storage", zap.Error(err)) + if c := t.logger.Check(zapcore.ErrorLevel, "could not clean default/global storage"); c != nil { + c.Write(zap.Error(err)) + } } // then clean each storage defined in ACME automation policies @@ -679,7 +688,9 @@ func (t *TLS) cleanStorageUnits() { continue } if err := certmagic.CleanStorage(t.ctx, ap.storage, options); err != nil { - t.logger.Error("could not clean storage configured in automation policy", zap.Error(err)) + if c := t.logger.Check(zapcore.ErrorLevel, "could not clean storage configured in automation policy"); c != nil { + c.Write(zap.Error(err)) + } } } } From 6ab9fb6f7424026ed67ceb112d99a4c483ef468b Mon Sep 17 00:00:00 2001 From: Mohammed Al Sahaf Date: Mon, 16 Sep 2024 16:50:26 +0300 Subject: [PATCH 04/39] ci: update the linter action version (#6575) * ci: update the linter action version Signed-off-by: Mohammed Al Sahaf * exclude rule `G115`; disable deprecated linter Signed-off-by: Mohammed Al Sahaf --------- Signed-off-by: Mohammed Al Sahaf --- .github/workflows/lint.yml | 2 +- .golangci.yml | 4 +++- modules/caddyhttp/server.go | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 94250f88f35..22e13973fe0 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -49,7 +49,7 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: - version: v1.60 + version: latest # Windows times out frequently after about 5m50s if we don't set a longer timeout. args: --timeout 10m diff --git a/.golangci.yml b/.golangci.yml index 74e3503c484..e8b2a55d3f5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -35,7 +35,6 @@ linters: - errcheck - errname - exhaustive - - exportloopref - gci - gofmt - goimports @@ -145,6 +144,9 @@ output: issues: exclude-rules: + - text: 'G115' # TODO: Either we should fix the issues or nuke the linter if it's bad + linters: + - gosec # we aren't calling unknown URL - text: 'G107' # G107: Url provided to HTTP request as taint input linters: diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go index 9dfa1bb6d3b..e301bdc68a4 100644 --- a/modules/caddyhttp/server.go +++ b/modules/caddyhttp/server.go @@ -817,7 +817,6 @@ func (s *Server) logRequest( } if fields == nil { - userID, _ := repl.GetString("http.auth.user.id") reqBodyLength := 0 From 5b44d6cea87e91723e166eb30041d0ec79c29e66 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 21 Sep 2024 05:00:13 +0800 Subject: [PATCH 05/39] update quic-go to v0.47.0 (#6582) --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 6ae4a4a8ea7..051058f4eaa 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/klauspost/cpuid/v2 v2.2.7 github.com/mholt/acmez/v2 v2.0.1 github.com/prometheus/client_golang v1.19.1 - github.com/quic-go/quic-go v0.46.0 + github.com/quic-go/quic-go v0.47.0 github.com/smallstep/certificates v0.26.1 github.com/smallstep/nosql v0.6.1 github.com/smallstep/truststore v0.13.0 @@ -62,7 +62,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect github.com/onsi/ginkgo/v2 v2.13.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/quic-go/qpack v0.4.0 // indirect + github.com/quic-go/qpack v0.5.1 // indirect github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 // indirect github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect diff --git a/go.sum b/go.sum index 5bf39b424bf..e65ac8b431f 100644 --- a/go.sum +++ b/go.sum @@ -337,10 +337,10 @@ github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSz github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= -github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/quic-go v0.46.0 h1:uuwLClEEyk1DNvchH8uCByQVjo3yKL9opKulExNDs7Y= -github.com/quic-go/quic-go v0.46.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.47.0 h1:yXs3v7r2bm1wmPTYNLKAAJTHMYkPEsfYJmTazXrCZ7Y= +github.com/quic-go/quic-go v0.47.0/go.mod h1:3bCapYsJvXGZcipOHuu7plYtaV6tnF+z7wIFsU0WK9E= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= From ff67b971267abb24774d18f323b0d6d43bfcdb3b Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 21 Sep 2024 11:47:18 +0800 Subject: [PATCH 06/39] caddyhttp: enable qlog, controlled by QLOGDIR env (#6581) --- go.mod | 1 + go.sum | 144 ++++++++++++++++++++++++++++++++++++ listeners.go | 9 ++- modules/caddyhttp/server.go | 3 +- 4 files changed, 155 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 051058f4eaa..0c4f1edd749 100644 --- a/go.mod +++ b/go.mod @@ -51,6 +51,7 @@ require ( github.com/Microsoft/go-winio v0.6.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/francoispqt/gojay v1.2.13 // indirect github.com/fxamacker/cbor/v2 v2.6.0 // indirect github.com/go-jose/go-jose/v3 v3.0.3 // indirect github.com/go-kit/log v0.2.1 // indirect diff --git a/go.sum b/go.sum index e65ac8b431f..6bf0bd3e2b7 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,7 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg= cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro= @@ -12,8 +16,13 @@ cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY= cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc= cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= +dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= +dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= +dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= +dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -38,6 +47,7 @@ github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -71,8 +81,11 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.28.7 h1:et3Ta53gotFR4ERLXXHIHl/Uuk1q github.com/aws/aws-sdk-go-v2/service/sts v1.28.7/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw= github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/caddyserver/certmagic v0.21.3 h1:pqRRry3yuB4CWBVq9+cUqu+Y6E2z8TswbhNx1AZeYm0= github.com/caddyserver/certmagic v0.21.3/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI= github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= @@ -93,11 +106,13 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= @@ -128,11 +143,17 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= +github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -158,17 +179,23 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= @@ -176,15 +203,20 @@ github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 h1:heyoXNxkRT155x4jTAiSv5BVSVkueifPUm+Q8LUXMRo= github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98= github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY= github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus= github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20231212022811-ec68065c825e h1:bwOy7hAFd0C91URzMIEBfr6BAz29yk7Qj0cy6S7DJlU= github.com/google/pprof v0.0.0-20231212022811-ec68065c825e/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -195,8 +227,14 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU= +github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= @@ -257,7 +295,10 @@ github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= @@ -272,6 +313,7 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -284,7 +326,9 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= @@ -297,10 +341,12 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k= github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= @@ -313,10 +359,15 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= +github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU= github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o= @@ -329,12 +380,16 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= @@ -355,11 +410,34 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E= github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= +github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= +github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= +github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= +github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= +github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= +github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= +github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= +github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= +github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= +github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= +github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= +github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= +github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= +github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= +github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -381,6 +459,8 @@ github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d h1:06LUHn4Ia2X6syjI github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d/go.mod h1:4d0ub42ut1mMtvGyMensjuHYEUpRrASvkzLEJvoRQcU= github.com/smallstep/truststore v0.13.0 h1:90if9htAOblavbMeWlqNLnO9bsjjgVv2hQeQJCi/py4= github.com/smallstep/truststore v0.13.0/go.mod h1:3tmMp2aLKZ/OA/jnFUB0cYPcho402UG2knuJoPh4j7A= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -417,9 +497,12 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 h1:uxMgm0C+EjytfAqyfBG55ZONKQ7mvd7x4YYCWsf8QHQ= github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= +github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= +github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= @@ -438,6 +521,7 @@ github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= +go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= @@ -497,8 +581,12 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs= go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ= +go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -514,8 +602,12 @@ golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 h1:TgSqweA595vD0Zt86JzLv3Pb/syKg8gd5KMGGbJPYFw= golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= @@ -523,7 +615,15 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -534,18 +634,31 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -579,6 +692,7 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -590,9 +704,15 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -610,14 +730,30 @@ golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4= google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw= google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw= google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae h1:AH34z6WAGVNkllnKs5raNq3yRq93VnjBG6rpfub/jYk= google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y= google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 h1:DujSIu+2tC9Ht0aPNA7jgj23Iq8Ewi5sgkQ++wdvonE= google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= @@ -629,9 +765,11 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -639,6 +777,12 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= +sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/listeners.go b/listeners.go index fa5ac1f56c0..0d9dd75381d 100644 --- a/listeners.go +++ b/listeners.go @@ -31,6 +31,7 @@ import ( "github.com/quic-go/quic-go" "github.com/quic-go/quic-go/http3" + "github.com/quic-go/quic-go/qlog" "go.uber.org/zap" "golang.org/x/time/rate" @@ -437,7 +438,13 @@ func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config Conn: h3ln, VerifySourceAddress: func(addr net.Addr) bool { return !limiter.Allow() }, } - earlyLn, err := tr.ListenEarly(http3.ConfigureTLSConfig(quicTlsConfig), &quic.Config{Allow0RTT: true}) + earlyLn, err := tr.ListenEarly( + http3.ConfigureTLSConfig(quicTlsConfig), + &quic.Config{ + Allow0RTT: true, + Tracer: qlog.DefaultConnectionTracer, + }, + ) if err != nil { return nil, err } diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go index e301bdc68a4..54b1c3b34ae 100644 --- a/modules/caddyhttp/server.go +++ b/modules/caddyhttp/server.go @@ -32,6 +32,7 @@ import ( "github.com/caddyserver/certmagic" "github.com/quic-go/quic-go" "github.com/quic-go/quic-go/http3" + "github.com/quic-go/quic-go/qlog" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -623,9 +624,9 @@ func (s *Server) serveHTTP3(addr caddy.NetworkAddress, tlsCfg *tls.Config) error }), TLSConfig: tlsCfg, MaxHeaderBytes: s.MaxHeaderBytes, - // TODO: remove this config when draft versions are no longer supported (we have no need to support drafts) QUICConfig: &quic.Config{ Versions: []quic.Version{quic.Version1, quic.Version2}, + Tracer: qlog.DefaultConnectionTracer, }, IdleTimeout: time.Duration(s.IdleTimeout), ConnContext: func(ctx context.Context, c quic.Connection) context.Context { From 9dda8fbf846db052243e6ce5ab707650da8c030e Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Wed, 25 Sep 2024 08:00:48 -0400 Subject: [PATCH 07/39] caddytls: Give a better error message when given encrypted private keys (#6591) --- modules/caddytls/fileloader.go | 9 +++++++++ modules/caddytls/folderloader.go | 6 ++++++ modules/caddytls/storageloader.go | 9 +++++++++ 3 files changed, 24 insertions(+) diff --git a/modules/caddytls/fileloader.go b/modules/caddytls/fileloader.go index 8603bbe652b..7d2927e2ae9 100644 --- a/modules/caddytls/fileloader.go +++ b/modules/caddytls/fileloader.go @@ -18,6 +18,7 @@ import ( "crypto/tls" "fmt" "os" + "strings" "github.com/caddyserver/caddy/v2" ) @@ -92,8 +93,16 @@ func (fl FileLoader) LoadCertificates() ([]Certificate, error) { switch pair.Format { case "": fallthrough + case "pem": + // if the start of the key file looks like an encrypted private key, + // reject it with a helpful error message + if strings.Contains(string(keyData[:40]), "ENCRYPTED") { + return nil, fmt.Errorf("encrypted private keys are not supported; please decrypt the key first") + } + cert, err = tls.X509KeyPair(certData, keyData) + default: return nil, fmt.Errorf("unrecognized certificate/key encoding format: %s", pair.Format) } diff --git a/modules/caddytls/folderloader.go b/modules/caddytls/folderloader.go index 89e978df631..2df6f4cee41 100644 --- a/modules/caddytls/folderloader.go +++ b/modules/caddytls/folderloader.go @@ -150,6 +150,12 @@ func tlsCertFromCertAndKeyPEMBundle(bundle []byte) (tls.Certificate, error) { return tls.Certificate{}, fmt.Errorf("no private key block found") } + // if the start of the key file looks like an encrypted private key, + // reject it with a helpful error message + if strings.HasPrefix(string(keyPEMBytes[:40]), "ENCRYPTED") { + return tls.Certificate{}, fmt.Errorf("encrypted private keys are not supported; please decrypt the key first") + } + cert, err := tls.X509KeyPair(certPEMBytes, keyPEMBytes) if err != nil { return tls.Certificate{}, fmt.Errorf("making X509 key pair: %v", err) diff --git a/modules/caddytls/storageloader.go b/modules/caddytls/storageloader.go index f9f0e7e680f..c9487e89252 100644 --- a/modules/caddytls/storageloader.go +++ b/modules/caddytls/storageloader.go @@ -17,6 +17,7 @@ package caddytls import ( "crypto/tls" "fmt" + "strings" "github.com/caddyserver/certmagic" @@ -88,8 +89,16 @@ func (sl StorageLoader) LoadCertificates() ([]Certificate, error) { switch pair.Format { case "": fallthrough + case "pem": + // if the start of the key file looks like an encrypted private key, + // reject it with a helpful error message + if strings.Contains(string(keyData[:40]), "ENCRYPTED") { + return nil, fmt.Errorf("encrypted private keys are not supported; please decrypt the key first") + } + cert, err = tls.X509KeyPair(certData, keyData) + default: return nil, fmt.Errorf("unrecognized certificate/key encoding format: %s", pair.Format) } From 2faeac0a104c72e9396717406ecb7c353bf9aa64 Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Wed, 25 Sep 2024 16:30:56 -0400 Subject: [PATCH 08/39] chore: Use slices package where possible (#6585) * chore: Use slices package where possible * More, mostly using ContainsFunc * Even more slice operations --- admin.go | 19 ++----- caddyconfig/caddyfile/importgraph.go | 8 +-- caddyconfig/httpcaddyfile/directives.go | 39 +++---------- caddyconfig/httpcaddyfile/httptype.go | 33 +++-------- caddyconfig/httpcaddyfile/options.go | 40 +++++++------- caddyconfig/httpcaddyfile/serveroptions.go | 24 +++----- caddyconfig/httpcaddyfile/tlsapp.go | 55 +++++++++---------- modules/caddyevents/app.go | 25 +++++---- modules/caddyhttp/autohttps.go | 16 +----- modules/caddyhttp/encode/encode.go | 10 ++-- .../caddyhttp/fileserver/browsetplcontext.go | 10 ++-- modules/caddyhttp/headers/headers.go | 13 +++-- modules/caddyhttp/map/map.go | 14 +---- modules/caddyhttp/matchers.go | 7 +-- .../caddyhttp/reverseproxy/healthchecks.go | 9 +-- .../caddyhttp/reverseproxy/httptransport.go | 17 ++---- modules/caddyhttp/server.go | 26 +++------ modules/caddyhttp/templates/frontmatter.go | 1 + modules/caddytls/automation.go | 10 ++-- modules/caddytls/certselection.go | 13 ++--- modules/caddytls/matchers.go | 19 +++---- 21 files changed, 141 insertions(+), 267 deletions(-) diff --git a/admin.go b/admin.go index ef78254ee1c..e78adeec98c 100644 --- a/admin.go +++ b/admin.go @@ -34,6 +34,7 @@ import ( "os" "path" "regexp" + "slices" "strconv" "strings" "sync" @@ -675,13 +676,7 @@ func (remote RemoteAdmin) enforceAccessControls(r *http.Request) error { // key recognized; make sure its HTTP request is permitted for _, accessPerm := range adminAccess.Permissions { // verify method - methodFound := accessPerm.Methods == nil - for _, method := range accessPerm.Methods { - if method == r.Method { - methodFound = true - break - } - } + methodFound := accessPerm.Methods == nil || slices.Contains(accessPerm.Methods, r.Method) if !methodFound { return APIError{ HTTPStatus: http.StatusForbidden, @@ -877,13 +872,9 @@ func (h adminHandler) handleError(w http.ResponseWriter, r *http.Request, err er // a trustworthy/expected value. This helps to mitigate DNS // rebinding attacks. func (h adminHandler) checkHost(r *http.Request) error { - var allowed bool - for _, allowedOrigin := range h.allowedOrigins { - if r.Host == allowedOrigin.Host { - allowed = true - break - } - } + allowed := slices.ContainsFunc(h.allowedOrigins, func(u *url.URL) bool { + return r.Host == u.Host + }) if !allowed { return APIError{ HTTPStatus: http.StatusForbidden, diff --git a/caddyconfig/caddyfile/importgraph.go b/caddyconfig/caddyfile/importgraph.go index d5037fe6232..ca859299dce 100644 --- a/caddyconfig/caddyfile/importgraph.go +++ b/caddyconfig/caddyfile/importgraph.go @@ -16,6 +16,7 @@ package caddyfile import ( "fmt" + "slices" ) type adjacency map[string][]string @@ -91,12 +92,7 @@ func (i *importGraph) areConnected(from, to string) bool { if !ok { return false } - for _, v := range al { - if v == to { - return true - } - } - return false + return slices.Contains(al, to) } func (i *importGraph) willCycle(from, to string) bool { diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go index 6972bb674cb..19ef4bc0e1a 100644 --- a/caddyconfig/httpcaddyfile/directives.go +++ b/caddyconfig/httpcaddyfile/directives.go @@ -17,6 +17,7 @@ package httpcaddyfile import ( "encoding/json" "net" + "slices" "sort" "strconv" "strings" @@ -100,17 +101,6 @@ var defaultDirectiveOrder = []string{ // plugins or by the user via the "order" global option. var directiveOrder = defaultDirectiveOrder -// directiveIsOrdered returns true if dir is -// a known, ordered (sorted) directive. -func directiveIsOrdered(dir string) bool { - for _, d := range directiveOrder { - if d == dir { - return true - } - } - return false -} - // RegisterDirective registers a unique directive dir with an // associated unmarshaling (setup) function. When directive dir // is encountered in a Caddyfile, setupFunc will be called to @@ -161,7 +151,7 @@ func RegisterHandlerDirective(dir string, setupFunc UnmarshalHandlerFunc) { // EXPERIMENTAL: This API may change or be removed. func RegisterDirectiveOrder(dir string, position Positional, standardDir string) { // check if directive was already ordered - if directiveIsOrdered(dir) { + if slices.Contains(directiveOrder, dir) { panic("directive '" + dir + "' already ordered") } @@ -172,12 +162,7 @@ func RegisterDirectiveOrder(dir string, position Positional, standardDir string) // check if directive exists in standard distribution, since // we can't allow plugins to depend on one another; we can't // guarantee the order that plugins are loaded in. - foundStandardDir := false - for _, d := range defaultDirectiveOrder { - if d == standardDir { - foundStandardDir = true - } - } + foundStandardDir := slices.Contains(defaultDirectiveOrder, standardDir) if !foundStandardDir { panic("the 3rd argument '" + standardDir + "' must be a directive that exists in the standard distribution of Caddy") } @@ -603,23 +588,17 @@ func (sb serverBlock) hostsFromKeysNotHTTP(httpPort string) []string { // hasHostCatchAllKey returns true if sb has a key that // omits a host portion, i.e. it "catches all" hosts. func (sb serverBlock) hasHostCatchAllKey() bool { - for _, addr := range sb.keys { - if addr.Host == "" { - return true - } - } - return false + return slices.ContainsFunc(sb.keys, func(addr Address) bool { + return addr.Host == "" + }) } // isAllHTTP returns true if all sb keys explicitly specify // the http:// scheme func (sb serverBlock) isAllHTTP() bool { - for _, addr := range sb.keys { - if addr.Scheme != "http" { - return false - } - } - return true + return !slices.ContainsFunc(sb.keys, func(addr Address) bool { + return addr.Scheme != "http" + }) } // Positional are the supported modes for ordering directives. diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go index a8a2ae5b3c5..c858ee56497 100644 --- a/caddyconfig/httpcaddyfile/httptype.go +++ b/caddyconfig/httpcaddyfile/httptype.go @@ -536,7 +536,7 @@ func (st *ServerType) serversFromPairings( if k == j { continue } - if sliceContains(sblock2.block.GetKeysText(), key) { + if slices.Contains(sblock2.block.GetKeysText(), key) { return nil, fmt.Errorf("ambiguous site definition: %s", key) } } @@ -720,7 +720,7 @@ func (st *ServerType) serversFromPairings( if srv.AutoHTTPS == nil { srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig) } - if !sliceContains(srv.AutoHTTPS.Skip, addr.Host) { + if !slices.Contains(srv.AutoHTTPS.Skip, addr.Host) { srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, addr.Host) } } @@ -734,7 +734,7 @@ func (st *ServerType) serversFromPairings( // https://caddy.community/t/making-sense-of-auto-https-and-why-disabling-it-still-serves-https-instead-of-http/9761 createdTLSConnPolicies, ok := sblock.pile["tls.connection_policy"] hasTLSEnabled := (ok && len(createdTLSConnPolicies) > 0) || - (addr.Host != "" && srv.AutoHTTPS != nil && !sliceContains(srv.AutoHTTPS.Skip, addr.Host)) + (addr.Host != "" && srv.AutoHTTPS != nil && !slices.Contains(srv.AutoHTTPS.Skip, addr.Host)) // we'll need to remember if the address qualifies for auto-HTTPS, so we // can add a TLS conn policy if necessary @@ -1061,7 +1061,7 @@ func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.Connecti } else if cps[i].CertSelection != nil && cps[j].CertSelection != nil { // if both have one, then combine AnyTag for _, tag := range cps[j].CertSelection.AnyTag { - if !sliceContains(cps[i].CertSelection.AnyTag, tag) { + if !slices.Contains(cps[i].CertSelection.AnyTag, tag) { cps[i].CertSelection.AnyTag = append(cps[i].CertSelection.AnyTag, tag) } } @@ -1144,7 +1144,7 @@ func appendSubrouteToRouteList(routeList caddyhttp.RouteList, func buildSubroute(routes []ConfigValue, groupCounter counter, needsSorting bool) (*caddyhttp.Subroute, error) { if needsSorting { for _, val := range routes { - if !directiveIsOrdered(val.directive) { + if !slices.Contains(directiveOrder, val.directive) { return nil, fmt.Errorf("directive '%s' is not an ordered HTTP handler, so it cannot be used here - try placing within a route block or using the order global option", val.directive) } } @@ -1354,17 +1354,8 @@ func (st *ServerType) compileEncodedMatcherSets(sblock serverBlock) ([]caddy.Mod // add this server block's keys to the matcher // pair if it doesn't already exist - if addr.Host != "" { - var found bool - for _, h := range chosenMatcherPair.hostm { - if h == addr.Host { - found = true - break - } - } - if !found { - chosenMatcherPair.hostm = append(chosenMatcherPair.hostm, addr.Host) - } + if addr.Host != "" && !slices.Contains(chosenMatcherPair.hostm, addr.Host) { + chosenMatcherPair.hostm = append(chosenMatcherPair.hostm, addr.Host) } } @@ -1540,16 +1531,6 @@ func tryDuration(val any, warnings *[]caddyconfig.Warning) caddy.Duration { return durationVal } -// sliceContains returns true if needle is in haystack. -func sliceContains(haystack []string, needle string) bool { - for _, s := range haystack { - if s == needle { - return true - } - } - return false -} - // listenersUseAnyPortOtherThan returns true if there are any // listeners in addresses that use a port which is not otherPort. // Mostly borrowed from unexported method in caddyhttp package. diff --git a/caddyconfig/httpcaddyfile/options.go b/caddyconfig/httpcaddyfile/options.go index db9be52ca99..53687d32a56 100644 --- a/caddyconfig/httpcaddyfile/options.go +++ b/caddyconfig/httpcaddyfile/options.go @@ -15,6 +15,7 @@ package httpcaddyfile import ( + "slices" "strconv" "github.com/caddyserver/certmagic" @@ -110,17 +111,12 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) { } pos := Positional(d.Val()) - newOrder := directiveOrder + // if directive already had an order, drop it + newOrder := slices.DeleteFunc(directiveOrder, func(d string) bool { + return d == dirName + }) - // if directive exists, first remove it - for i, d := range newOrder { - if d == dirName { - newOrder = append(newOrder[:i], newOrder[i+1:]...) - break - } - } - - // act on the positional + // act on the positional; if it's First or Last, we're done right away switch pos { case First: newOrder = append([]string{dirName}, newOrder...) @@ -129,6 +125,7 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) { } directiveOrder = newOrder return newOrder, nil + case Last: newOrder = append(newOrder, dirName) if d.NextArg() { @@ -136,8 +133,11 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) { } directiveOrder = newOrder return newOrder, nil + + // if it's Before or After, continue case Before: case After: + default: return nil, d.Errf("unknown positional '%s'", pos) } @@ -151,17 +151,17 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) { return nil, d.ArgErr() } - // insert directive into proper position - for i, d := range newOrder { - if d == otherDir { - if pos == Before { - newOrder = append(newOrder[:i], append([]string{dirName}, newOrder[i:]...)...) - } else if pos == After { - newOrder = append(newOrder[:i+1], append([]string{dirName}, newOrder[i+1:]...)...) - } - break - } + // get the position of the target directive + targetIndex := slices.Index(newOrder, otherDir) + if targetIndex == -1 { + return nil, d.Errf("directive '%s' not found", otherDir) + } + // if we're inserting after, we need to increment the index to go after + if pos == After { + targetIndex++ } + // insert the directive into the new order + newOrder = slices.Insert(newOrder, targetIndex, dirName) directiveOrder = newOrder diff --git a/caddyconfig/httpcaddyfile/serveroptions.go b/caddyconfig/httpcaddyfile/serveroptions.go index 4246cd7dce8..7087cdba59c 100644 --- a/caddyconfig/httpcaddyfile/serveroptions.go +++ b/caddyconfig/httpcaddyfile/serveroptions.go @@ -17,6 +17,7 @@ package httpcaddyfile import ( "encoding/json" "fmt" + "slices" "github.com/dustin/go-humanize" @@ -180,7 +181,7 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) { if proto != "h1" && proto != "h2" && proto != "h2c" && proto != "h3" { return nil, d.Errf("unknown protocol '%s': expected h1, h2, h2c, or h3", proto) } - if sliceContains(serverOpts.Protocols, proto) { + if slices.Contains(serverOpts.Protocols, proto) { return nil, d.Errf("protocol %s specified more than once", proto) } serverOpts.Protocols = append(serverOpts.Protocols, proto) @@ -229,7 +230,7 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) { case "client_ip_headers": headers := d.RemainingArgs() for _, header := range headers { - if sliceContains(serverOpts.ClientIPHeaders, header) { + if slices.Contains(serverOpts.ClientIPHeaders, header) { return nil, d.Errf("client IP header %s specified more than once", header) } serverOpts.ClientIPHeaders = append(serverOpts.ClientIPHeaders, header) @@ -288,24 +289,15 @@ func applyServerOptions( for key, server := range servers { // find the options that apply to this server - opts := func() *serverOptions { - for _, entry := range serverOpts { - if entry.ListenerAddress == "" { - return &entry - } - for _, listener := range server.Listen { - if entry.ListenerAddress == listener { - return &entry - } - } - } - return nil - }() + optsIndex := slices.IndexFunc(serverOpts, func(s serverOptions) bool { + return s.ListenerAddress == "" || slices.Contains(server.Listen, s.ListenerAddress) + }) // if none apply, then move to the next server - if opts == nil { + if optsIndex == -1 { continue } + opts := serverOpts[optsIndex] // set all the options server.ListenerWrappersRaw = opts.ListenerWrappersRaw diff --git a/caddyconfig/httpcaddyfile/tlsapp.go b/caddyconfig/httpcaddyfile/tlsapp.go index f69e2c54a2e..157a3113ea2 100644 --- a/caddyconfig/httpcaddyfile/tlsapp.go +++ b/caddyconfig/httpcaddyfile/tlsapp.go @@ -19,6 +19,7 @@ import ( "encoding/json" "fmt" "reflect" + "slices" "sort" "strconv" "strings" @@ -57,19 +58,20 @@ func (st ServerType) buildTLSApp( for _, pair := range pairings { for _, sb := range pair.serverBlocks { for _, addr := range sb.keys { - if addr.Host == "" { - // this server block has a hostless key, now - // go through and add all the hosts to the set - for _, otherAddr := range sb.keys { - if otherAddr.Original == addr.Original { - continue - } - if otherAddr.Host != "" && otherAddr.Scheme != "http" && otherAddr.Port != httpPort { - httpsHostsSharedWithHostlessKey[otherAddr.Host] = struct{}{} - } + if addr.Host != "" { + continue + } + // this server block has a hostless key, now + // go through and add all the hosts to the set + for _, otherAddr := range sb.keys { + if otherAddr.Original == addr.Original { + continue + } + if otherAddr.Host != "" && otherAddr.Scheme != "http" && otherAddr.Port != httpPort { + httpsHostsSharedWithHostlessKey[otherAddr.Host] = struct{}{} } - break } + break } } } @@ -465,7 +467,7 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e if globalACMECA != nil && acmeIssuer.CA == "" { acmeIssuer.CA = globalACMECA.(string) } - if globalACMECARoot != nil && !sliceContains(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string)) { + if globalACMECARoot != nil && !slices.Contains(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string)) { acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string)) } if globalACMEDNS != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil) { @@ -580,7 +582,7 @@ func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls if !automationPolicyHasAllPublicNames(aps[i]) { // if this automation policy has internal names, we might as well remove it // so auto-https can implicitly use the internal issuer - aps = append(aps[:i], aps[i+1:]...) + aps = slices.Delete(aps, i, i+1) i-- } } @@ -597,7 +599,7 @@ outer: for j := i + 1; j < len(aps); j++ { // if they're exactly equal in every way, just keep one of them if reflect.DeepEqual(aps[i], aps[j]) { - aps = append(aps[:j], aps[j+1:]...) + aps = slices.Delete(aps, j, j+1) // must re-evaluate current i against next j; can't skip it! // even if i decrements to -1, will be incremented to 0 immediately i-- @@ -627,18 +629,18 @@ outer: // cause example.com to be served by the less specific policy for // '*.com', which might be different (yes we've seen this happen) if automationPolicyShadows(i, aps) >= j { - aps = append(aps[:i], aps[i+1:]...) + aps = slices.Delete(aps, i, i+1) i-- continue outer } } else { // avoid repeated subjects for _, subj := range aps[j].SubjectsRaw { - if !sliceContains(aps[i].SubjectsRaw, subj) { + if !slices.Contains(aps[i].SubjectsRaw, subj) { aps[i].SubjectsRaw = append(aps[i].SubjectsRaw, subj) } } - aps = append(aps[:j], aps[j+1:]...) + aps = slices.Delete(aps, j, j+1) j-- } } @@ -658,13 +660,9 @@ func automationPolicyIsSubset(a, b *caddytls.AutomationPolicy) bool { return false } for _, aSubj := range a.SubjectsRaw { - var inSuperset bool - for _, bSubj := range b.SubjectsRaw { - if certmagic.MatchWildcard(aSubj, bSubj) { - inSuperset = true - break - } - } + inSuperset := slices.ContainsFunc(b.SubjectsRaw, func(bSubj string) bool { + return certmagic.MatchWildcard(aSubj, bSubj) + }) if !inSuperset { return false } @@ -709,12 +707,9 @@ func subjectQualifiesForPublicCert(ap *caddytls.AutomationPolicy, subj string) b // automationPolicyHasAllPublicNames returns true if all the names on the policy // do NOT qualify for public certs OR are tailscale domains. func automationPolicyHasAllPublicNames(ap *caddytls.AutomationPolicy) bool { - for _, subj := range ap.SubjectsRaw { - if !subjectQualifiesForPublicCert(ap, subj) || isTailscaleDomain(subj) { - return false - } - } - return true + return !slices.ContainsFunc(ap.SubjectsRaw, func(i string) bool { + return !subjectQualifiesForPublicCert(ap, i) || isTailscaleDomain(i) + }) } func isTailscaleDomain(name string) bool { diff --git a/modules/caddyevents/app.go b/modules/caddyevents/app.go index fe76a675756..2e4a6f2c0ce 100644 --- a/modules/caddyevents/app.go +++ b/modules/caddyevents/app.go @@ -124,18 +124,19 @@ func (app *App) Provision(ctx caddy.Context) error { app.subscriptions = make(map[string]map[caddy.ModuleID][]Handler) for _, sub := range app.Subscriptions { - if sub.HandlersRaw != nil { - handlersIface, err := ctx.LoadModule(sub, "HandlersRaw") - if err != nil { - return fmt.Errorf("loading event subscriber modules: %v", err) - } - for _, h := range handlersIface.([]any) { - sub.Handlers = append(sub.Handlers, h.(Handler)) - } - if len(sub.Handlers) == 0 { - // pointless to bind without any handlers - return fmt.Errorf("no handlers defined") - } + if sub.HandlersRaw == nil { + continue + } + handlersIface, err := ctx.LoadModule(sub, "HandlersRaw") + if err != nil { + return fmt.Errorf("loading event subscriber modules: %v", err) + } + for _, h := range handlersIface.([]any) { + sub.Handlers = append(sub.Handlers, h.(Handler)) + } + if len(sub.Handlers) == 0 { + // pointless to bind without any handlers + return fmt.Errorf("no handlers defined") } } diff --git a/modules/caddyhttp/autohttps.go b/modules/caddyhttp/autohttps.go index 263cd14c731..ea5a07b3c05 100644 --- a/modules/caddyhttp/autohttps.go +++ b/modules/caddyhttp/autohttps.go @@ -17,6 +17,7 @@ package caddyhttp import ( "fmt" "net/http" + "slices" "strconv" "strings" @@ -66,17 +67,6 @@ type AutoHTTPSConfig struct { IgnoreLoadedCerts bool `json:"ignore_loaded_certificates,omitempty"` } -// Skipped returns true if name is in skipSlice, which -// should be either the Skip or SkipCerts field 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 @@ -158,7 +148,7 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er return fmt.Errorf("%s: route %d, matcher set %d, matcher %d, host matcher %d: %v", srvName, routeIdx, matcherSetIdx, matcherIdx, hostMatcherIdx, err) } - if !srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.Skip) { + if !slices.Contains(srv.AutoHTTPS.Skip, d) { serverDomainSet[d] = struct{}{} } } @@ -193,7 +183,7 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er } else { for d := range serverDomainSet { if certmagic.SubjectQualifiesForCert(d) && - !srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.SkipCerts) { + !slices.Contains(srv.AutoHTTPS.SkipCerts, d) { // if a certificate for this name is already loaded, // don't obtain another one for it, unless we are // supposed to ignore loaded certificates diff --git a/modules/caddyhttp/encode/encode.go b/modules/caddyhttp/encode/encode.go index 00e5072772a..f0d56a90d1e 100644 --- a/modules/caddyhttp/encode/encode.go +++ b/modules/caddyhttp/encode/encode.go @@ -24,6 +24,7 @@ import ( "io" "math" "net/http" + "slices" "sort" "strconv" "strings" @@ -441,12 +442,9 @@ func AcceptedEncodings(r *http.Request, preferredOrder []string) []string { } // set server preference - prefOrder := -1 - for i, p := range preferredOrder { - if encName == p { - prefOrder = len(preferredOrder) - i - break - } + prefOrder := slices.Index(preferredOrder, encName) + if prefOrder > -1 { + prefOrder = len(preferredOrder) - prefOrder } prefs = append(prefs, encodingPreference{ diff --git a/modules/caddyhttp/fileserver/browsetplcontext.go b/modules/caddyhttp/fileserver/browsetplcontext.go index 37e1cc3dda8..8e5d138f1d5 100644 --- a/modules/caddyhttp/fileserver/browsetplcontext.go +++ b/modules/caddyhttp/fileserver/browsetplcontext.go @@ -21,6 +21,7 @@ import ( "os" "path" "path/filepath" + "slices" "sort" "strconv" "strings" @@ -281,12 +282,9 @@ type fileInfo struct { // HasExt returns true if the filename has any of the given suffixes, case-insensitive. func (fi fileInfo) HasExt(exts ...string) bool { - for _, ext := range exts { - if strings.HasSuffix(strings.ToLower(fi.Name), strings.ToLower(ext)) { - return true - } - } - return false + return slices.ContainsFunc(exts, func(ext string) bool { + return strings.HasSuffix(strings.ToLower(fi.Name), strings.ToLower(ext)) + }) } // HumanSize returns the size of the file as a diff --git a/modules/caddyhttp/headers/headers.go b/modules/caddyhttp/headers/headers.go index bdb185f46e7..a3279d913e1 100644 --- a/modules/caddyhttp/headers/headers.go +++ b/modules/caddyhttp/headers/headers.go @@ -135,13 +135,14 @@ type HeaderOps struct { func (ops *HeaderOps) Provision(_ caddy.Context) error { for fieldName, replacements := range ops.Replace { for i, r := range replacements { - if r.SearchRegexp != "" { - re, err := regexp.Compile(r.SearchRegexp) - if err != nil { - return fmt.Errorf("replacement %d for header field '%s': %v", i, fieldName, err) - } - replacements[i].re = re + if r.SearchRegexp == "" { + continue + } + re, err := regexp.Compile(r.SearchRegexp) + if err != nil { + return fmt.Errorf("replacement %d for header field '%s': %v", i, fieldName, err) } + replacements[i].re = re } } return nil diff --git a/modules/caddyhttp/map/map.go b/modules/caddyhttp/map/map.go index 336f2572319..d02085e7633 100644 --- a/modules/caddyhttp/map/map.go +++ b/modules/caddyhttp/map/map.go @@ -18,6 +18,7 @@ import ( "fmt" "net/http" "regexp" + "slices" "strings" "github.com/caddyserver/caddy/v2" @@ -126,7 +127,7 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhtt // defer work until a variable is actually evaluated by using replacer's Map callback repl.Map(func(key string) (any, bool) { // return early if the variable is not even a configured destination - destIdx := h.destinationIndex(key) + destIdx := slices.Index(h.Destinations, key) if destIdx < 0 { return nil, false } @@ -170,17 +171,6 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhtt return next.ServeHTTP(w, r) } -// destinationIndex returns the positional index of the destination -// is name is a known destination; otherwise it returns -1. -func (h Handler) destinationIndex(name string) int { - for i, dest := range h.Destinations { - if dest == name { - return i - } - } - return -1 -} - // Mapping describes a mapping from input to outputs. type Mapping struct { // The input value to match. Must be distinct from other mappings. diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go index b7952ab6951..a0bc6d63a61 100644 --- a/modules/caddyhttp/matchers.go +++ b/modules/caddyhttp/matchers.go @@ -764,12 +764,7 @@ func (m *MatchMethod) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { // Match returns true if r matches m. func (m MatchMethod) Match(r *http.Request) bool { - for _, method := range m { - if r.Method == method { - return true - } - } - return false + return slices.Contains(m, r.Method) } // CELLibrary produces options that expose this matcher for use in CEL diff --git a/modules/caddyhttp/reverseproxy/healthchecks.go b/modules/caddyhttp/reverseproxy/healthchecks.go index 179805f2028..319cc924818 100644 --- a/modules/caddyhttp/reverseproxy/healthchecks.go +++ b/modules/caddyhttp/reverseproxy/healthchecks.go @@ -23,6 +23,7 @@ import ( "net/url" "regexp" "runtime/debug" + "slices" "strconv" "strings" "time" @@ -397,12 +398,8 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, networ u.Scheme = "https" // if the port is in the except list, flip back to HTTP - if ht, ok := h.Transport.(*HTTPTransport); ok { - for _, exceptPort := range ht.TLS.ExceptPorts { - if exceptPort == port { - u.Scheme = "http" - } - } + if ht, ok := h.Transport.(*HTTPTransport); ok && slices.Contains(ht.TLS.ExceptPorts, port) { + u.Scheme = "http" } } diff --git a/modules/caddyhttp/reverseproxy/httptransport.go b/modules/caddyhttp/reverseproxy/httptransport.go index 144960cf920..bc82337880c 100644 --- a/modules/caddyhttp/reverseproxy/httptransport.go +++ b/modules/caddyhttp/reverseproxy/httptransport.go @@ -27,6 +27,7 @@ import ( "net/url" "os" "reflect" + "slices" "strings" "time" @@ -381,7 +382,7 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e rt.DisableCompression = !*h.Compression } - if sliceContains(h.Versions, "2") { + if slices.Contains(h.Versions, "2") { if err := http2.ConfigureTransport(rt); err != nil { return nil, err } @@ -400,13 +401,13 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e return nil, fmt.Errorf("making TLS client config for HTTP/3 transport: %v", err) } } - } else if len(h.Versions) > 1 && sliceContains(h.Versions, "3") { + } else if len(h.Versions) > 1 && slices.Contains(h.Versions, "3") { return nil, fmt.Errorf("if HTTP/3 is enabled to the upstream, no other HTTP versions are supported") } // if h2c is enabled, configure its transport (std lib http.Transport // does not "HTTP/2 over cleartext TCP") - if sliceContains(h.Versions, "h2c") { + if slices.Contains(h.Versions, "h2c") { // crafting our own http2.Transport doesn't allow us to utilize // most of the customizations/preferences on the http.Transport, // because, for some reason, only http2.ConfigureTransport() @@ -783,16 +784,6 @@ func decodeBase64DERCert(certStr string) (*x509.Certificate, error) { return x509.ParseCertificate(derBytes) } -// sliceContains returns true if needle is in haystack. -func sliceContains(haystack []string, needle string) bool { - for _, s := range haystack { - if s == needle { - return true - } - } - return false -} - // Interface guards var ( _ caddy.Provisioner = (*HTTPTransport)(nil) diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go index 54b1c3b34ae..ece3f1cd28e 100644 --- a/modules/caddyhttp/server.go +++ b/modules/caddyhttp/server.go @@ -25,6 +25,7 @@ import ( "net/netip" "net/url" "runtime" + "slices" "strings" "sync" "time" @@ -543,12 +544,9 @@ func (s *Server) hasListenerAddress(fullAddr string) bool { } func (s *Server) hasTLSClientAuth() bool { - for _, cp := range s.TLSConnPolicies { - if cp.ClientAuthentication != nil && cp.ClientAuthentication.Active() { - return true - } - } - return false + return slices.ContainsFunc(s.TLSConnPolicies, func(cp *caddytls.ConnectionPolicy) bool { + return cp.ClientAuthentication != nil && cp.ClientAuthentication.Active() + }) } // findLastRouteWithHostMatcher returns the index of the last route @@ -849,12 +847,7 @@ func (s *Server) logRequest( // protocol returns true if the protocol proto is configured/enabled. func (s *Server) protocol(proto string) bool { - for _, p := range s.Protocols { - if p == proto { - return true - } - } - return false + return slices.Contains(s.Protocols, proto) } // Listeners returns the server's listeners. These are active listeners, @@ -959,12 +952,9 @@ func determineTrustedProxy(r *http.Request, s *Server) (bool, string) { // isTrustedClientIP returns true if the given IP address is // in the list of trusted IP ranges. func isTrustedClientIP(ipAddr netip.Addr, trusted []netip.Prefix) bool { - for _, ipRange := range trusted { - if ipRange.Contains(ipAddr) { - return true - } - } - return false + return slices.ContainsFunc(trusted, func(prefix netip.Prefix) bool { + return prefix.Contains(ipAddr) + }) } // trustedRealClientIP finds the client IP from the request assuming it is diff --git a/modules/caddyhttp/templates/frontmatter.go b/modules/caddyhttp/templates/frontmatter.go index 3f7bd0cc4cf..fb62a41847f 100644 --- a/modules/caddyhttp/templates/frontmatter.go +++ b/modules/caddyhttp/templates/frontmatter.go @@ -40,6 +40,7 @@ func extractFrontMatter(input string) (map[string]any, string, error) { if firstLine == fmType.FenceOpen { closingFence = fmType.FenceClose fmParser = fmType.ParseFunc + break } } diff --git a/modules/caddytls/automation.go b/modules/caddytls/automation.go index e2e02221df3..1f1042ba0da 100644 --- a/modules/caddytls/automation.go +++ b/modules/caddytls/automation.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "net" + "slices" "strings" "github.com/caddyserver/certmagic" @@ -373,12 +374,9 @@ func (ap *AutomationPolicy) Subjects() []string { // AllInternalSubjects returns true if all the subjects on this policy are internal. func (ap *AutomationPolicy) AllInternalSubjects() bool { - for _, subj := range ap.subjects { - if !certmagic.SubjectIsInternal(subj) { - return false - } - } - return true + return !slices.ContainsFunc(ap.subjects, func(s string) bool { + return !certmagic.SubjectIsInternal(s) + }) } func (ap *AutomationPolicy) onlyInternalIssuer() bool { diff --git a/modules/caddytls/certselection.go b/modules/caddytls/certselection.go index 84ca2e118c8..a561e3a1d00 100644 --- a/modules/caddytls/certselection.go +++ b/modules/caddytls/certselection.go @@ -20,6 +20,7 @@ import ( "encoding/json" "fmt" "math/big" + "slices" "github.com/caddyserver/certmagic" @@ -72,15 +73,9 @@ nextChoice: } if len(p.SubjectOrganization) > 0 { - var found bool - for _, subjOrg := range p.SubjectOrganization { - for _, org := range cert.Leaf.Subject.Organization { - if subjOrg == org { - found = true - break - } - } - } + found := slices.ContainsFunc(p.SubjectOrganization, func(s string) bool { + return slices.Contains(cert.Leaf.Subject.Organization, s) + }) if !found { continue } diff --git a/modules/caddytls/matchers.go b/modules/caddytls/matchers.go index f44b4c02a5b..a74d6ea80e0 100644 --- a/modules/caddytls/matchers.go +++ b/modules/caddytls/matchers.go @@ -20,6 +20,7 @@ import ( "net" "net/netip" "regexp" + "slices" "strconv" "strings" @@ -321,12 +322,9 @@ func (MatchRemoteIP) parseIPRange(str string) ([]netip.Prefix, error) { } func (MatchRemoteIP) matches(ip netip.Addr, ranges []netip.Prefix) bool { - for _, ipRange := range ranges { - if ipRange.Contains(ip) { - return true - } - } - return false + return slices.ContainsFunc(ranges, func(prefix netip.Prefix) bool { + return prefix.Contains(ip) + }) } // UnmarshalCaddyfile sets up the MatchRemoteIP from Caddyfile tokens. Syntax: @@ -439,12 +437,9 @@ func (MatchLocalIP) parseIPRange(str string) ([]netip.Prefix, error) { } func (MatchLocalIP) matches(ip netip.Addr, ranges []netip.Prefix) bool { - for _, ipRange := range ranges { - if ipRange.Contains(ip) { - return true - } - } - return false + return slices.ContainsFunc(ranges, func(prefix netip.Prefix) bool { + return prefix.Contains(ip) + }) } // UnmarshalCaddyfile sets up the MatchLocalIP from Caddyfile tokens. Syntax: From 22c98ea165bdfbca33fbc77ce3b2bd22d3ee4626 Mon Sep 17 00:00:00 2001 From: Alexander Stecher <45872305+AlliBalliBaba@users.noreply.github.com> Date: Thu, 26 Sep 2024 20:23:12 +0200 Subject: [PATCH 09/39] caddyhttp: Optimize logs using zap's WithLazy() (#6590) * uses zap's .WithLazy with a cloned request * fixes the cloning * adds comment explaining why cloning is faster --- modules/caddyhttp/server.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go index ece3f1cd28e..f5478cb37a4 100644 --- a/modules/caddyhttp/server.go +++ b/modules/caddyhttp/server.go @@ -313,16 +313,18 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } - // encode the request for logging purposes before + // clone the request for logging purposes before // it enters any handler chain; this is necessary // to capture the original request in case it gets // modified during handling + // cloning the request and using .WithLazy is considerably faster + // than using .With, which will JSON encode the request immediately shouldLogCredentials := s.Logs != nil && s.Logs.ShouldLogCredentials loggableReq := zap.Object("request", LoggableHTTPRequest{ - Request: r, + Request: r.Clone(r.Context()), ShouldLogCredentials: shouldLogCredentials, }) - errLog := s.errorLogger.With(loggableReq) + errLog := s.errorLogger.WithLazy(loggableReq) var duration time.Duration From 1a345b4fa620dfb0909a3b086bd76e35dfdbefa5 Mon Sep 17 00:00:00 2001 From: Mohammed Al Sahaf Date: Sun, 29 Sep 2024 12:12:52 +0300 Subject: [PATCH 10/39] doc: remove docs of deprecated directives (#6566) Co-authored-by: Francis Lavoie --- modules/caddytls/connpolicy.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/modules/caddytls/connpolicy.go b/modules/caddytls/connpolicy.go index 2ff41f7b221..2e2d4f748cd 100644 --- a/modules/caddytls/connpolicy.go +++ b/modules/caddytls/connpolicy.go @@ -555,16 +555,10 @@ type ClientAuthentication struct { // trust_pool { // ... // } -// trusted_leaf_cert -// trusted_leaf_cert_file // verifier // } // -// If `mode` is not provided, it defaults to `require_and_verify` if any of the following are provided: -// - `trusted_leaf_certs` -// - `trusted_leaf_cert_file` -// - `trust_pool` -// +// If `mode` is not provided, it defaults to `require_and_verify` if `trust_pool` is provided. // Otherwise, it defaults to `require`. func (ca *ClientAuthentication) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { for d.NextArg() { @@ -768,7 +762,7 @@ func (clientauth *ClientAuthentication) ConfigureTLSConfig(cfg *tls.Config) erro if len(clientauth.TrustedCACerts) > 0 || len(clientauth.TrustedCACertPEMFiles) > 0 || len(clientauth.TrustedLeafCerts) > 0 || - clientauth.CARaw != nil { + clientauth.CARaw != nil || clientauth.ca != nil { cfg.ClientAuth = tls.RequireAndVerifyClientCert } else { cfg.ClientAuth = tls.RequireAnyClientCert From 4b1a9b6cc1aa521e21289afa276d29952a97d8f3 Mon Sep 17 00:00:00 2001 From: Aaron Paterson <9441877+MayCXC@users.noreply.github.com> Date: Mon, 30 Sep 2024 12:55:03 -0400 Subject: [PATCH 11/39] core: Implement socket activation listeners (#6573) * caddy adapt for listen_protocols * adapt listen_socket * allow multiple listen sockets for port ranges and readd socket fd listen logic * readd logic to start servers according to listener protocols * gofmt * adapt caddytest * gosec * fmt and rename listen to listenWithSocket * fmt and rename listen to listenWithSocket * more consistent error msg * non unix listenReusableWithSocketFile * remove unused func * doc comment typo * nonosec * commit * doc comments * more doc comments * comment was misleading, cardinality did not change * addressesWithProtocols * update test * fd/ and fdgram/ * rm addr * actually write... * i guess we doin' "skip": now * wrong var in placeholder * wrong var in placeholder II * update param name in comment * dont save nil file pointers * windows * key -> parsedKey * osx * multiple default_bind with protocols * check for h1 and h2 listener netw --- admin.go | 4 +- caddyconfig/httpcaddyfile/addresses.go | 282 +++++++++++------- caddyconfig/httpcaddyfile/builtins.go | 24 +- caddyconfig/httpcaddyfile/directives.go | 14 +- caddyconfig/httpcaddyfile/directives_test.go | 2 +- caddyconfig/httpcaddyfile/httptype.go | 80 ++++- caddyconfig/httpcaddyfile/options.go | 31 +- caddyconfig/httpcaddyfile/tlsapp.go | 14 +- .../bind_fd_fdgram_h123.caddyfiletest | 142 +++++++++ cmd/commandfuncs.go | 12 +- cmd/main_test.go | 6 +- listen.go | 80 ++++- listen_unix.go | 172 +++++++---- listeners.go | 113 ++++--- modules/caddyhttp/app.go | 232 +++++++++----- .../proxyprotocol/listenerwrapper.go | 2 +- modules/caddyhttp/reverseproxy/addresses.go | 2 +- .../caddyhttp/reverseproxy/healthchecks.go | 4 +- modules/caddyhttp/server.go | 44 ++- modules/caddyhttp/staticresp.go | 2 +- replacer.go | 6 +- 21 files changed, 925 insertions(+), 343 deletions(-) create mode 100644 caddytest/integration/caddyfile_adapt/bind_fd_fdgram_h123.caddyfiletest diff --git a/admin.go b/admin.go index e78adeec98c..24c583235be 100644 --- a/admin.go +++ b/admin.go @@ -313,7 +313,7 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL { } if admin.Origins == nil { if addr.isLoopback() { - if addr.IsUnixNetwork() { + if addr.IsUnixNetwork() || addr.IsFdNetwork() { // RFC 2616, Section 14.26: // "A client MUST include a Host header field in all HTTP/1.1 request // messages. If the requested URI does not include an Internet host @@ -351,7 +351,7 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL { uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{} } } - if !addr.IsUnixNetwork() { + if !addr.IsUnixNetwork() && !addr.IsFdNetwork() { uniqueOrigins[addr.JoinHostPort(0)] = struct{}{} } } diff --git a/caddyconfig/httpcaddyfile/addresses.go b/caddyconfig/httpcaddyfile/addresses.go index da51fe9b003..1c331eadc6a 100644 --- a/caddyconfig/httpcaddyfile/addresses.go +++ b/caddyconfig/httpcaddyfile/addresses.go @@ -77,10 +77,15 @@ import ( // repetition may be undesirable, so call consolidateAddrMappings() to map // multiple addresses to the same lists of server blocks (a many:many mapping). // (Doing this is essentially a map-reduce technique.) -func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBlock, +func (st *ServerType) mapAddressToProtocolToServerBlocks(originalServerBlocks []serverBlock, options map[string]any, -) (map[string][]serverBlock, error) { - sbmap := make(map[string][]serverBlock) +) (map[string]map[string][]serverBlock, error) { + addrToProtocolToServerBlocks := map[string]map[string][]serverBlock{} + + type keyWithParsedKey struct { + key caddyfile.Token + parsedKey Address + } for i, sblock := range originalServerBlocks { // within a server block, we need to map all the listener addresses @@ -88,27 +93,48 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBloc // will be served by them; this has the effect of treating each // key of a server block as its own, but without having to repeat its // contents in cases where multiple keys really can be served together - addrToKeys := make(map[string][]caddyfile.Token) + addrToProtocolToKeyWithParsedKeys := map[string]map[string][]keyWithParsedKey{} for j, key := range sblock.block.Keys { + parsedKey, err := ParseAddress(key.Text) + if err != nil { + return nil, fmt.Errorf("parsing key: %v", err) + } + parsedKey = parsedKey.Normalize() + // a key can have multiple listener addresses if there are multiple // arguments to the 'bind' directive (although they will all have // the same port, since the port is defined by the key or is implicit // through automatic HTTPS) - addrs, err := st.listenerAddrsForServerBlockKey(sblock, key.Text, options) + listeners, err := st.listenersForServerBlockAddress(sblock, parsedKey, options) if err != nil { return nil, fmt.Errorf("server block %d, key %d (%s): determining listener address: %v", i, j, key.Text, err) } - // associate this key with each listener address it is served on - for _, addr := range addrs { - addrToKeys[addr] = append(addrToKeys[addr], key) + // associate this key with its protocols and each listener address served with them + kwpk := keyWithParsedKey{key, parsedKey} + for addr, protocols := range listeners { + protocolToKeyWithParsedKeys, ok := addrToProtocolToKeyWithParsedKeys[addr] + if !ok { + protocolToKeyWithParsedKeys = map[string][]keyWithParsedKey{} + addrToProtocolToKeyWithParsedKeys[addr] = protocolToKeyWithParsedKeys + } + + // an empty protocol indicates the default, a nil or empty value in the ListenProtocols array + if len(protocols) == 0 { + protocols[""] = struct{}{} + } + for prot := range protocols { + protocolToKeyWithParsedKeys[prot] = append( + protocolToKeyWithParsedKeys[prot], + kwpk) + } } } // make a slice of the map keys so we can iterate in sorted order - addrs := make([]string, 0, len(addrToKeys)) - for k := range addrToKeys { - addrs = append(addrs, k) + addrs := make([]string, 0, len(addrToProtocolToKeyWithParsedKeys)) + for addr := range addrToProtocolToKeyWithParsedKeys { + addrs = append(addrs, addr) } sort.Strings(addrs) @@ -118,85 +144,132 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBloc // server block are only the ones which use the address; but // the contents (tokens) are of course the same for _, addr := range addrs { - keys := addrToKeys[addr] - // parse keys so that we only have to do it once - parsedKeys := make([]Address, 0, len(keys)) - for _, key := range keys { - addr, err := ParseAddress(key.Text) - if err != nil { - return nil, fmt.Errorf("parsing key '%s': %v", key.Text, err) + protocolToKeyWithParsedKeys := addrToProtocolToKeyWithParsedKeys[addr] + + prots := make([]string, 0, len(protocolToKeyWithParsedKeys)) + for prot := range protocolToKeyWithParsedKeys { + prots = append(prots, prot) + } + sort.Strings(prots) + + protocolToServerBlocks, ok := addrToProtocolToServerBlocks[addr] + if !ok { + protocolToServerBlocks = map[string][]serverBlock{} + addrToProtocolToServerBlocks[addr] = protocolToServerBlocks + } + + for _, prot := range prots { + keyWithParsedKeys := protocolToKeyWithParsedKeys[prot] + + keys := make([]caddyfile.Token, len(keyWithParsedKeys)) + parsedKeys := make([]Address, len(keyWithParsedKeys)) + + for k, keyWithParsedKey := range keyWithParsedKeys { + keys[k] = keyWithParsedKey.key + parsedKeys[k] = keyWithParsedKey.parsedKey } - parsedKeys = append(parsedKeys, addr.Normalize()) + + protocolToServerBlocks[prot] = append(protocolToServerBlocks[prot], serverBlock{ + block: caddyfile.ServerBlock{ + Keys: keys, + Segments: sblock.block.Segments, + }, + pile: sblock.pile, + parsedKeys: parsedKeys, + }) } - sbmap[addr] = append(sbmap[addr], serverBlock{ - block: caddyfile.ServerBlock{ - Keys: keys, - Segments: sblock.block.Segments, - }, - pile: sblock.pile, - keys: parsedKeys, - }) } } - return sbmap, nil + return addrToProtocolToServerBlocks, nil } // consolidateAddrMappings eliminates repetition of identical server blocks in a mapping of -// single listener addresses to lists of server blocks. Since multiple addresses may serve -// identical sites (server block contents), this function turns a 1:many mapping into a -// many:many mapping. Server block contents (tokens) must be exactly identical so that -// reflect.DeepEqual returns true in order for the addresses to be combined. Identical -// entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each +// single listener addresses to protocols to lists of server blocks. Since multiple addresses +// may serve multiple protocols to identical sites (server block contents), this function turns +// a 1:many mapping into a many:many mapping. Server block contents (tokens) must be +// exactly identical so that reflect.DeepEqual returns true in order for the addresses to be combined. +// Identical entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each // association from multiple addresses to multiple server blocks; i.e. each element of // the returned slice) becomes a server definition in the output JSON. -func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]serverBlock) []sbAddrAssociation { - sbaddrs := make([]sbAddrAssociation, 0, len(addrToServerBlocks)) - for addr, sblocks := range addrToServerBlocks { - // we start with knowing that at least this address - // maps to these server blocks - a := sbAddrAssociation{ - addresses: []string{addr}, - serverBlocks: sblocks, +func (st *ServerType) consolidateAddrMappings(addrToProtocolToServerBlocks map[string]map[string][]serverBlock) []sbAddrAssociation { + sbaddrs := make([]sbAddrAssociation, 0, len(addrToProtocolToServerBlocks)) + + addrs := make([]string, 0, len(addrToProtocolToServerBlocks)) + for addr := range addrToProtocolToServerBlocks { + addrs = append(addrs, addr) + } + sort.Strings(addrs) + + for _, addr := range addrs { + protocolToServerBlocks := addrToProtocolToServerBlocks[addr] + + prots := make([]string, 0, len(protocolToServerBlocks)) + for prot := range protocolToServerBlocks { + prots = append(prots, prot) } + sort.Strings(prots) + + for _, prot := range prots { + serverBlocks := protocolToServerBlocks[prot] + + // now find other addresses that map to identical + // server blocks and add them to our map of listener + // addresses and protocols, while removing them from + // the original map + listeners := map[string]map[string]struct{}{} + + for otherAddr, otherProtocolToServerBlocks := range addrToProtocolToServerBlocks { + for otherProt, otherServerBlocks := range otherProtocolToServerBlocks { + if addr == otherAddr && prot == otherProt || reflect.DeepEqual(serverBlocks, otherServerBlocks) { + listener, ok := listeners[otherAddr] + if !ok { + listener = map[string]struct{}{} + listeners[otherAddr] = listener + } + listener[otherProt] = struct{}{} + delete(otherProtocolToServerBlocks, otherProt) + } + } + } - // now find other addresses that map to identical - // server blocks and add them to our list of - // addresses, while removing them from the map - for otherAddr, otherSblocks := range addrToServerBlocks { - if addr == otherAddr { - continue + addresses := make([]string, 0, len(listeners)) + for lnAddr := range listeners { + addresses = append(addresses, lnAddr) } - if reflect.DeepEqual(sblocks, otherSblocks) { - a.addresses = append(a.addresses, otherAddr) - delete(addrToServerBlocks, otherAddr) + sort.Strings(addresses) + + addressesWithProtocols := make([]addressWithProtocols, 0, len(listeners)) + + for _, lnAddr := range addresses { + lnProts := listeners[lnAddr] + prots := make([]string, 0, len(lnProts)) + for prot := range lnProts { + prots = append(prots, prot) + } + sort.Strings(prots) + + addressesWithProtocols = append(addressesWithProtocols, addressWithProtocols{ + address: lnAddr, + protocols: prots, + }) } - } - sort.Strings(a.addresses) - sbaddrs = append(sbaddrs, a) + sbaddrs = append(sbaddrs, sbAddrAssociation{ + addressesWithProtocols: addressesWithProtocols, + serverBlocks: serverBlocks, + }) + } } - // sort them by their first address (we know there will always be at least one) - // to avoid problems with non-deterministic ordering (makes tests flaky) - sort.Slice(sbaddrs, func(i, j int) bool { - return sbaddrs[i].addresses[0] < sbaddrs[j].addresses[0] - }) - return sbaddrs } -// listenerAddrsForServerBlockKey essentially converts the Caddyfile -// site addresses to Caddy listener addresses for each server block. -func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key string, +// listenersForServerBlockAddress essentially converts the Caddyfile site addresses to a map from +// Caddy listener addresses and the protocols to serve them with to the parsed address for each server block. +func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock, addr Address, options map[string]any, -) ([]string, error) { - addr, err := ParseAddress(key) - if err != nil { - return nil, fmt.Errorf("parsing key: %v", err) - } - addr = addr.Normalize() - +) (map[string]map[string]struct{}, error) { switch addr.Scheme { case "wss": return nil, fmt.Errorf("the scheme wss:// is only supported in browsers; use https:// instead") @@ -230,55 +303,54 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key str // error if scheme and port combination violate convention if (addr.Scheme == "http" && lnPort == httpsPort) || (addr.Scheme == "https" && lnPort == httpPort) { - return nil, fmt.Errorf("[%s] scheme and port violate convention", key) + return nil, fmt.Errorf("[%s] scheme and port violate convention", addr.String()) } - // the bind directive specifies hosts (and potentially network), but is optional - lnHosts := make([]string, 0, len(sblock.pile["bind"])) + // the bind directive specifies hosts (and potentially network), and the protocols to serve them with, but is optional + lnCfgVals := make([]addressesWithProtocols, 0, len(sblock.pile["bind"])) for _, cfgVal := range sblock.pile["bind"] { - lnHosts = append(lnHosts, cfgVal.Value.([]string)...) + if val, ok := cfgVal.Value.(addressesWithProtocols); ok { + lnCfgVals = append(lnCfgVals, val) + } } - if len(lnHosts) == 0 { - if defaultBind, ok := options["default_bind"].([]string); ok { - lnHosts = defaultBind + if len(lnCfgVals) == 0 { + if defaultBindValues, ok := options["default_bind"].([]ConfigValue); ok { + for _, defaultBindValue := range defaultBindValues { + lnCfgVals = append(lnCfgVals, defaultBindValue.Value.(addressesWithProtocols)) + } } else { - lnHosts = []string{""} + lnCfgVals = []addressesWithProtocols{{ + addresses: []string{""}, + protocols: nil, + }} } } // use a map to prevent duplication - listeners := make(map[string]struct{}) - for _, lnHost := range lnHosts { - // normally we would simply append the port, - // but if lnHost is IPv6, we need to ensure it - // is enclosed in [ ]; net.JoinHostPort does - // this for us, but lnHost might also have a - // network type in front (e.g. "tcp/") leading - // to "[tcp/::1]" which causes parsing failures - // later; what we need is "tcp/[::1]", so we have - // to split the network and host, then re-combine - network, host, ok := strings.Cut(lnHost, "/") - if !ok { - host = network - network = "" - } - host = strings.Trim(host, "[]") // IPv6 - networkAddr := caddy.JoinNetworkAddress(network, host, lnPort) - addr, err := caddy.ParseNetworkAddress(networkAddr) - if err != nil { - return nil, fmt.Errorf("parsing network address: %v", err) + listeners := map[string]map[string]struct{}{} + for _, lnCfgVal := range lnCfgVals { + for _, lnHost := range lnCfgVal.addresses { + networkAddr, err := caddy.ParseNetworkAddressFromHostPort(lnHost, lnPort) + if err != nil { + return nil, fmt.Errorf("parsing network address: %v", err) + } + if _, ok := listeners[addr.String()]; !ok { + listeners[networkAddr.String()] = map[string]struct{}{} + } + for _, protocol := range lnCfgVal.protocols { + listeners[networkAddr.String()][protocol] = struct{}{} + } } - listeners[addr.String()] = struct{}{} } - // now turn map into list - listenersList := make([]string, 0, len(listeners)) - for lnStr := range listeners { - listenersList = append(listenersList, lnStr) - } - sort.Strings(listenersList) + return listeners, nil +} - return listenersList, nil +// addressesWithProtocols associates a list of listen addresses +// with a list of protocols to serve them with +type addressesWithProtocols struct { + addresses []string + protocols []string } // Address represents a site address. It contains diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go index e1e95e00a37..165c66b25bc 100644 --- a/caddyconfig/httpcaddyfile/builtins.go +++ b/caddyconfig/httpcaddyfile/builtins.go @@ -56,10 +56,30 @@ func init() { // parseBind parses the bind directive. Syntax: // -// bind +// bind [{ +// protocols [h1|h2|h2c|h3] [...] +// }] func parseBind(h Helper) ([]ConfigValue, error) { h.Next() // consume directive name - return []ConfigValue{{Class: "bind", Value: h.RemainingArgs()}}, nil + var addresses, protocols []string + addresses = h.RemainingArgs() + + for h.NextBlock(0) { + switch h.Val() { + case "protocols": + protocols = h.RemainingArgs() + if len(protocols) == 0 { + return nil, h.Errf("protocols requires one or more arguments") + } + default: + return nil, h.Errf("unknown subdirective: %s", h.Val()) + } + } + + return []ConfigValue{{Class: "bind", Value: addressesWithProtocols{ + addresses: addresses, + protocols: protocols, + }}}, nil } // parseTLS parses the tls directive. Syntax: diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go index 19ef4bc0e1a..f0687a7e937 100644 --- a/caddyconfig/httpcaddyfile/directives.go +++ b/caddyconfig/httpcaddyfile/directives.go @@ -516,9 +516,9 @@ func sortRoutes(routes []ConfigValue) { // a "pile" of config values, keyed by class name, // as well as its parsed keys for convenience. type serverBlock struct { - block caddyfile.ServerBlock - pile map[string][]ConfigValue // config values obtained from directives - keys []Address + block caddyfile.ServerBlock + pile map[string][]ConfigValue // config values obtained from directives + parsedKeys []Address } // hostsFromKeys returns a list of all the non-empty hostnames found in @@ -535,7 +535,7 @@ type serverBlock struct { func (sb serverBlock) hostsFromKeys(loggerMode bool) []string { // ensure each entry in our list is unique hostMap := make(map[string]struct{}) - for _, addr := range sb.keys { + for _, addr := range sb.parsedKeys { if addr.Host == "" { if !loggerMode { // server block contains a key like ":443", i.e. the host portion @@ -567,7 +567,7 @@ func (sb serverBlock) hostsFromKeys(loggerMode bool) []string { func (sb serverBlock) hostsFromKeysNotHTTP(httpPort string) []string { // ensure each entry in our list is unique hostMap := make(map[string]struct{}) - for _, addr := range sb.keys { + for _, addr := range sb.parsedKeys { if addr.Host == "" { continue } @@ -588,7 +588,7 @@ func (sb serverBlock) hostsFromKeysNotHTTP(httpPort string) []string { // hasHostCatchAllKey returns true if sb has a key that // omits a host portion, i.e. it "catches all" hosts. func (sb serverBlock) hasHostCatchAllKey() bool { - return slices.ContainsFunc(sb.keys, func(addr Address) bool { + return slices.ContainsFunc(sb.parsedKeys, func(addr Address) bool { return addr.Host == "" }) } @@ -596,7 +596,7 @@ func (sb serverBlock) hasHostCatchAllKey() bool { // isAllHTTP returns true if all sb keys explicitly specify // the http:// scheme func (sb serverBlock) isAllHTTP() bool { - return !slices.ContainsFunc(sb.keys, func(addr Address) bool { + return !slices.ContainsFunc(sb.parsedKeys, func(addr Address) bool { return addr.Scheme != "http" }) } diff --git a/caddyconfig/httpcaddyfile/directives_test.go b/caddyconfig/httpcaddyfile/directives_test.go index db028229950..2b4d3e6c370 100644 --- a/caddyconfig/httpcaddyfile/directives_test.go +++ b/caddyconfig/httpcaddyfile/directives_test.go @@ -78,7 +78,7 @@ func TestHostsFromKeys(t *testing.T) { []string{"example.com:2015"}, }, } { - sb := serverBlock{keys: tc.keys} + sb := serverBlock{parsedKeys: tc.keys} // test in normal mode actual := sb.hostsFromKeys(false) diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go index c858ee56497..6745969eefc 100644 --- a/caddyconfig/httpcaddyfile/httptype.go +++ b/caddyconfig/httpcaddyfile/httptype.go @@ -171,7 +171,7 @@ func (st ServerType) Setup( } // map - sbmap, err := st.mapAddressToServerBlocks(originalServerBlocks, options) + sbmap, err := st.mapAddressToProtocolToServerBlocks(originalServerBlocks, options) if err != nil { return nil, warnings, err } @@ -402,6 +402,20 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options options[opt] = append(existingOpts, logOpts...) continue } + // Also fold multiple "default_bind" options together into an + // array so that server blocks can have multiple binds by default. + if opt == "default_bind" { + existingOpts, ok := options[opt].([]ConfigValue) + if !ok { + existingOpts = []ConfigValue{} + } + defaultBindOpts, ok := val.([]ConfigValue) + if !ok { + return nil, fmt.Errorf("unexpected type from 'default_bind' global options: %T", val) + } + options[opt] = append(existingOpts, defaultBindOpts...) + continue + } options[opt] = val } @@ -543,8 +557,40 @@ func (st *ServerType) serversFromPairings( } } + var ( + addresses []string + protocols [][]string + ) + + for _, addressWithProtocols := range p.addressesWithProtocols { + addresses = append(addresses, addressWithProtocols.address) + protocols = append(protocols, addressWithProtocols.protocols) + } + srv := &caddyhttp.Server{ - Listen: p.addresses, + Listen: addresses, + ListenProtocols: protocols, + } + + // remove srv.ListenProtocols[j] if it only contains the default protocols + for j, lnProtocols := range srv.ListenProtocols { + srv.ListenProtocols[j] = nil + for _, lnProtocol := range lnProtocols { + if lnProtocol != "" { + srv.ListenProtocols[j] = lnProtocols + break + } + } + } + + // remove srv.ListenProtocols if it only contains the default protocols for all listen addresses + listenProtocols := srv.ListenProtocols + srv.ListenProtocols = nil + for _, lnProtocols := range listenProtocols { + if lnProtocols != nil { + srv.ListenProtocols = listenProtocols + break + } } // handle the auto_https global option @@ -566,7 +612,7 @@ func (st *ServerType) serversFromPairings( // See ParseAddress() where parsing should later reject paths // See https://github.com/caddyserver/caddy/pull/4728 for a full explanation for _, sblock := range p.serverBlocks { - for _, addr := range sblock.keys { + for _, addr := range sblock.parsedKeys { if addr.Path != "" { caddy.Log().Named("caddyfile").Warn("Using a path in a site address is deprecated; please use the 'handle' directive instead", zap.String("address", addr.String())) } @@ -584,7 +630,7 @@ func (st *ServerType) serversFromPairings( var iLongestPath, jLongestPath string var iLongestHost, jLongestHost string var iWildcardHost, jWildcardHost bool - for _, addr := range p.serverBlocks[i].keys { + for _, addr := range p.serverBlocks[i].parsedKeys { if strings.Contains(addr.Host, "*") || addr.Host == "" { iWildcardHost = true } @@ -595,7 +641,7 @@ func (st *ServerType) serversFromPairings( iLongestPath = addr.Path } } - for _, addr := range p.serverBlocks[j].keys { + for _, addr := range p.serverBlocks[j].parsedKeys { if strings.Contains(addr.Host, "*") || addr.Host == "" { jWildcardHost = true } @@ -711,7 +757,7 @@ func (st *ServerType) serversFromPairings( } } - for _, addr := range sblock.keys { + for _, addr := range sblock.parsedKeys { // if server only uses HTTP port, auto-HTTPS will not apply if listenersUseAnyPortOtherThan(srv.Listen, httpPort) { // exclude any hosts that were defined explicitly with "http://" @@ -886,8 +932,7 @@ func (st *ServerType) serversFromPairings( servers[fmt.Sprintf("srv%d", i)] = srv } - err := applyServerOptions(servers, options, warnings) - if err != nil { + if err := applyServerOptions(servers, options, warnings); err != nil { return nil, fmt.Errorf("applying global server options: %v", err) } @@ -932,7 +977,7 @@ func detectConflictingSchemes(srv *caddyhttp.Server, serverBlocks []serverBlock, } for _, sblock := range serverBlocks { - for _, addr := range sblock.keys { + for _, addr := range sblock.parsedKeys { if addr.Scheme == "http" || addr.Port == httpPort { if err := checkAndSetHTTP(addr); err != nil { return err @@ -1322,7 +1367,7 @@ func (st *ServerType) compileEncodedMatcherSets(sblock serverBlock) ([]caddy.Mod var matcherPairs []*hostPathPair var catchAllHosts bool - for _, addr := range sblock.keys { + for _, addr := range sblock.parsedKeys { // choose a matcher pair that should be shared by this // server block; if none exists yet, create one var chosenMatcherPair *hostPathPair @@ -1594,12 +1639,19 @@ type namedCustomLog struct { noHostname bool } +// addressWithProtocols associates a listen address with +// the protocols to serve it with +type addressWithProtocols struct { + address string + protocols []string +} + // sbAddrAssociation is a mapping from a list of -// addresses to a list of server blocks that are -// served on those addresses. +// addresses with protocols, and a list of server +// blocks that are served on those addresses. type sbAddrAssociation struct { - addresses []string - serverBlocks []serverBlock + addressesWithProtocols []addressWithProtocols + serverBlocks []serverBlock } const ( diff --git a/caddyconfig/httpcaddyfile/options.go b/caddyconfig/httpcaddyfile/options.go index 53687d32a56..c14208b614f 100644 --- a/caddyconfig/httpcaddyfile/options.go +++ b/caddyconfig/httpcaddyfile/options.go @@ -31,7 +31,7 @@ func init() { RegisterGlobalOption("debug", parseOptTrue) RegisterGlobalOption("http_port", parseOptHTTPPort) RegisterGlobalOption("https_port", parseOptHTTPSPort) - RegisterGlobalOption("default_bind", parseOptStringList) + RegisterGlobalOption("default_bind", parseOptDefaultBind) RegisterGlobalOption("grace_period", parseOptDuration) RegisterGlobalOption("shutdown_delay", parseOptDuration) RegisterGlobalOption("default_sni", parseOptSingleString) @@ -284,13 +284,32 @@ func parseOptSingleString(d *caddyfile.Dispenser, _ any) (any, error) { return val, nil } -func parseOptStringList(d *caddyfile.Dispenser, _ any) (any, error) { +func parseOptDefaultBind(d *caddyfile.Dispenser, _ any) (any, error) { d.Next() // consume option name - val := d.RemainingArgs() - if len(val) == 0 { - return "", d.ArgErr() + + var addresses, protocols []string + addresses = d.RemainingArgs() + + if len(addresses) == 0 { + addresses = append(addresses, "") } - return val, nil + + for d.NextBlock(0) { + switch d.Val() { + case "protocols": + protocols = d.RemainingArgs() + if len(protocols) == 0 { + return nil, d.Errf("protocols requires one or more arguments") + } + default: + return nil, d.Errf("unknown subdirective: %s", d.Val()) + } + } + + return []ConfigValue{{Class: "bind", Value: addressesWithProtocols{ + addresses: addresses, + protocols: protocols, + }}}, nil } func parseOptAdmin(d *caddyfile.Dispenser, _ any) (any, error) { diff --git a/caddyconfig/httpcaddyfile/tlsapp.go b/caddyconfig/httpcaddyfile/tlsapp.go index 157a3113ea2..c6ff81b2bf2 100644 --- a/caddyconfig/httpcaddyfile/tlsapp.go +++ b/caddyconfig/httpcaddyfile/tlsapp.go @@ -57,13 +57,13 @@ func (st ServerType) buildTLSApp( if autoHTTPS != "off" { for _, pair := range pairings { for _, sb := range pair.serverBlocks { - for _, addr := range sb.keys { + for _, addr := range sb.parsedKeys { if addr.Host != "" { continue } // this server block has a hostless key, now // go through and add all the hosts to the set - for _, otherAddr := range sb.keys { + for _, otherAddr := range sb.parsedKeys { if otherAddr.Original == addr.Original { continue } @@ -93,7 +93,11 @@ func (st ServerType) buildTLSApp( for _, p := range pairings { // avoid setting up TLS automation policies for a server that is HTTP-only - if !listenersUseAnyPortOtherThan(p.addresses, httpPort) { + var addresses []string + for _, addressWithProtocols := range p.addressesWithProtocols { + addresses = append(addresses, addressWithProtocols.address) + } + if !listenersUseAnyPortOtherThan(addresses, httpPort) { continue } @@ -183,8 +187,8 @@ func (st ServerType) buildTLSApp( if acmeIssuer.Challenges.BindHost == "" { // only binding to one host is supported var bindHost string - if bindHosts, ok := cfgVal.Value.([]string); ok && len(bindHosts) > 0 { - bindHost = bindHosts[0] + if asserted, ok := cfgVal.Value.(addressesWithProtocols); ok && len(asserted.addresses) > 0 { + bindHost = asserted.addresses[0] } acmeIssuer.Challenges.BindHost = bindHost } diff --git a/caddytest/integration/caddyfile_adapt/bind_fd_fdgram_h123.caddyfiletest b/caddytest/integration/caddyfile_adapt/bind_fd_fdgram_h123.caddyfiletest new file mode 100644 index 00000000000..08f30d18a5f --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/bind_fd_fdgram_h123.caddyfiletest @@ -0,0 +1,142 @@ +{ + auto_https disable_redirects + admin off +} + +http://localhost { + bind fd/{env.CADDY_HTTP_FD} { + protocols h1 + } + log + respond "Hello, HTTP!" +} + +https://localhost { + bind fd/{env.CADDY_HTTPS_FD} { + protocols h1 h2 + } + bind fdgram/{env.CADDY_HTTP3_FD} { + protocols h3 + } + log + respond "Hello, HTTPS!" +} +---------- +{ + "admin": { + "disabled": true + }, + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + "fd/{env.CADDY_HTTPS_FD}", + "fdgram/{env.CADDY_HTTP3_FD}" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Hello, HTTPS!", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "automatic_https": { + "disable_redirects": true + }, + "logs": { + "logger_names": { + "localhost": [ + "" + ] + } + }, + "listen_protocols": [ + [ + "h1", + "h2" + ], + [ + "h3" + ] + ] + }, + "srv1": { + "automatic_https": { + "disable_redirects": true + } + }, + "srv2": { + "listen": [ + "fd/{env.CADDY_HTTP_FD}" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Hello, HTTP!", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "automatic_https": { + "disable_redirects": true, + "skip": [ + "localhost" + ] + }, + "logs": { + "logger_names": { + "localhost": [ + "" + ] + } + }, + "listen_protocols": [ + [ + "h1" + ] + ] + } + } + } + } +} diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go index 49d0321efae..50e9b110da5 100644 --- a/cmd/commandfuncs.go +++ b/cmd/commandfuncs.go @@ -660,6 +660,8 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io return nil, err } parsedAddr.Host = addr + } else if parsedAddr.IsFdNetwork() { + origin = "http://127.0.0.1" } // form the request @@ -667,13 +669,13 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io if err != nil { return nil, fmt.Errorf("making request: %v", err) } - if parsedAddr.IsUnixNetwork() { + if parsedAddr.IsUnixNetwork() || parsedAddr.IsFdNetwork() { // We used to conform to RFC 2616 Section 14.26 which requires // an empty host header when there is no host, as is the case - // with unix sockets. However, Go required a Host value so we - // used a hack of a space character as the host (it would see - // the Host was non-empty, then trim the space later). As of - // Go 1.20.6 (July 2023), this hack no longer works. See: + // with unix sockets and socket fds. However, Go required a + // Host value so we used a hack of a space character as the host + // (it would see the Host was non-empty, then trim the space later). + // As of Go 1.20.6 (July 2023), this hack no longer works. See: // https://github.com/golang/go/issues/60374 // See also the discussion here: // https://github.com/golang/go/issues/61431 diff --git a/cmd/main_test.go b/cmd/main_test.go index 757a58ce6db..3b2412c57e9 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -235,7 +235,7 @@ func Test_isCaddyfile(t *testing.T) { wantErr: false, }, { - + name: "json is not caddyfile but not error", args: args{ configFile: "./Caddyfile.json", @@ -245,7 +245,7 @@ func Test_isCaddyfile(t *testing.T) { wantErr: false, }, { - + name: "prefix of Caddyfile and ./ with any extension is Caddyfile", args: args{ configFile: "./Caddyfile.prd", @@ -255,7 +255,7 @@ func Test_isCaddyfile(t *testing.T) { wantErr: false, }, { - + name: "prefix of Caddyfile without ./ with any extension is Caddyfile", args: args{ configFile: "Caddyfile.prd", diff --git a/listen.go b/listen.go index 34812b54f1a..f5c2086a656 100644 --- a/listen.go +++ b/listen.go @@ -18,7 +18,11 @@ package caddy import ( "context" + "fmt" "net" + "os" + "slices" + "strconv" "sync" "sync/atomic" "time" @@ -31,10 +35,49 @@ func reuseUnixSocket(network, addr string) (any, error) { } func listenReusable(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (any, error) { - switch network { - case "udp", "udp4", "udp6", "unixgram": + var socketFile *os.File + + fd := slices.Contains([]string{"fd", "fdgram"}, network) + if fd { + socketFd, err := strconv.ParseUint(address, 0, strconv.IntSize) + if err != nil { + return nil, fmt.Errorf("invalid file descriptor: %v", err) + } + + func() { + socketFilesMu.Lock() + defer socketFilesMu.Unlock() + + socketFdWide := uintptr(socketFd) + var ok bool + + socketFile, ok = socketFiles[socketFdWide] + + if !ok { + socketFile = os.NewFile(socketFdWide, lnKey) + if socketFile != nil { + socketFiles[socketFdWide] = socketFile + } + } + }() + + if socketFile == nil { + return nil, fmt.Errorf("invalid socket file descriptor: %d", socketFd) + } + } + + datagram := slices.Contains([]string{"udp", "udp4", "udp6", "unixgram", "fdgram"}, network) + if datagram { sharedPc, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) { - pc, err := config.ListenPacket(ctx, network, address) + var ( + pc net.PacketConn + err error + ) + if fd { + pc, err = net.FilePacketConn(socketFile) + } else { + pc, err = config.ListenPacket(ctx, network, address) + } if err != nil { return nil, err } @@ -44,20 +87,27 @@ func listenReusable(ctx context.Context, lnKey string, network, address string, return nil, err } return &fakeClosePacketConn{sharedPacketConn: sharedPc.(*sharedPacketConn)}, nil + } - default: - sharedLn, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) { - ln, err := config.Listen(ctx, network, address) - if err != nil { - return nil, err - } - return &sharedListener{Listener: ln, key: lnKey}, nil - }) + sharedLn, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) { + var ( + ln net.Listener + err error + ) + if fd { + ln, err = net.FileListener(socketFile) + } else { + ln, err = config.Listen(ctx, network, address) + } if err != nil { return nil, err } - return &fakeCloseListener{sharedListener: sharedLn.(*sharedListener), keepAlivePeriod: config.KeepAlive}, nil + return &sharedListener{Listener: ln, key: lnKey}, nil + }) + if err != nil { + return nil, err } + return &fakeCloseListener{sharedListener: sharedLn.(*sharedListener), keepAlivePeriod: config.KeepAlive}, nil } // fakeCloseListener is a private wrapper over a listener that @@ -260,3 +310,9 @@ var ( Unwrap() net.PacketConn }) = (*fakeClosePacketConn)(nil) ) + +// socketFiles is a fd -> *os.File map used to make a FileListener/FilePacketConn from a socket file descriptor. +var socketFiles = map[uintptr]*os.File{} + +// socketFilesMu synchronizes socketFiles insertions +var socketFilesMu sync.Mutex diff --git a/listen_unix.go b/listen_unix.go index 9ec65c39960..d6ae0cb8ebb 100644 --- a/listen_unix.go +++ b/listen_unix.go @@ -22,10 +22,14 @@ package caddy import ( "context" "errors" + "fmt" "io" "io/fs" "net" "os" + "slices" + "strconv" + "sync" "sync/atomic" "syscall" @@ -34,12 +38,9 @@ import ( ) // reuseUnixSocket copies and reuses the unix domain socket (UDS) if we already -// have it open; if not, unlink it so we can have it. No-op if not a unix network. +// have it open; if not, unlink it so we can have it. +// No-op if not a unix network. func reuseUnixSocket(network, addr string) (any, error) { - if !IsUnixNetwork(network) { - return nil, nil - } - socketKey := listenerKey(network, addr) socket, exists := unixSockets[socketKey] @@ -71,7 +72,7 @@ func reuseUnixSocket(network, addr string) (any, error) { return nil, err } atomic.AddInt32(unixSocket.count, 1) - unixSockets[socketKey] = &unixConn{pc.(*net.UnixConn), addr, socketKey, unixSocket.count} + unixSockets[socketKey] = &unixConn{pc.(*net.UnixConn), socketKey, unixSocket.count} } return unixSockets[socketKey], nil @@ -89,56 +90,107 @@ func reuseUnixSocket(network, addr string) (any, error) { return nil, nil } +// listenReusable creates a new listener for the given network and address, and adds it to listenerPool. func listenReusable(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (any, error) { - // wrap any Control function set by the user so we can also add our reusePort control without clobbering theirs - oldControl := config.Control - config.Control = func(network, address string, c syscall.RawConn) error { - if oldControl != nil { - if err := oldControl(network, address, c); err != nil { - return err - } - } - return reusePort(network, address, c) - } - // even though SO_REUSEPORT lets us bind the socket multiple times, // we still put it in the listenerPool so we can count how many // configs are using this socket; necessary to ensure we can know // whether to enforce shutdown delays, for example (see #5393). - var ln io.Closer - var err error - switch network { - case "udp", "udp4", "udp6", "unixgram": - ln, err = config.ListenPacket(ctx, network, address) - default: - ln, err = config.Listen(ctx, network, address) - } - if err == nil { - listenerPool.LoadOrStore(lnKey, nil) + var ( + ln io.Closer + err error + socketFile *os.File + ) + + fd := slices.Contains([]string{"fd", "fdgram"}, network) + if fd { + socketFd, err := strconv.ParseUint(address, 0, strconv.IntSize) + if err != nil { + return nil, fmt.Errorf("invalid file descriptor: %v", err) + } + + func() { + socketFilesMu.Lock() + defer socketFilesMu.Unlock() + + socketFdWide := uintptr(socketFd) + var ok bool + + socketFile, ok = socketFiles[socketFdWide] + + if !ok { + socketFile = os.NewFile(socketFdWide, lnKey) + if socketFile != nil { + socketFiles[socketFdWide] = socketFile + } + } + }() + + if socketFile == nil { + return nil, fmt.Errorf("invalid socket file descriptor: %d", socketFd) + } + } else { + // wrap any Control function set by the user so we can also add our reusePort control without clobbering theirs + oldControl := config.Control + config.Control = func(network, address string, c syscall.RawConn) error { + if oldControl != nil { + if err := oldControl(network, address, c); err != nil { + return err + } + } + return reusePort(network, address, c) + } } - // if new listener is a unix socket, make sure we can reuse it later - // (we do our own "unlink on close" -- not required, but more tidy) - one := int32(1) - if unix, ok := ln.(*net.UnixListener); ok { - unix.SetUnlinkOnClose(false) - ln = &unixListener{unix, lnKey, &one} - unixSockets[lnKey] = ln.(*unixListener) + datagram := slices.Contains([]string{"udp", "udp4", "udp6", "unixgram", "fdgram"}, network) + if datagram { + if fd { + ln, err = net.FilePacketConn(socketFile) + } else { + ln, err = config.ListenPacket(ctx, network, address) + } + } else { + if fd { + ln, err = net.FileListener(socketFile) + } else { + ln, err = config.Listen(ctx, network, address) + } } - // TODO: Not 100% sure this is necessary, but we do this for net.UnixListener in listen_unix.go, so... - if unix, ok := ln.(*net.UnixConn); ok { - ln = &unixConn{unix, address, lnKey, &one} - unixSockets[lnKey] = ln.(*unixConn) + if err == nil { + listenerPool.LoadOrStore(lnKey, nil) } - // lightly wrap the listener so that when it is closed, - // we can decrement the usage pool counter - switch specificLn := ln.(type) { - case net.Listener: - return deleteListener{specificLn, lnKey}, err - case net.PacketConn: - return deletePacketConn{specificLn, lnKey}, err + if datagram { + if !fd { + // TODO: Not 100% sure this is necessary, but we do this for net.UnixListener, so... + if unix, ok := ln.(*net.UnixConn); ok { + one := int32(1) + ln = &unixConn{unix, lnKey, &one} + unixSockets[lnKey] = ln.(*unixConn) + } + } + // lightly wrap the connection so that when it is closed, + // we can decrement the usage pool counter + if specificLn, ok := ln.(net.PacketConn); ok { + ln = deletePacketConn{specificLn, lnKey} + } + } else { + if !fd { + // if new listener is a unix socket, make sure we can reuse it later + // (we do our own "unlink on close" -- not required, but more tidy) + if unix, ok := ln.(*net.UnixListener); ok { + unix.SetUnlinkOnClose(false) + one := int32(1) + ln = &unixListener{unix, lnKey, &one} + unixSockets[lnKey] = ln.(*unixListener) + } + } + // lightly wrap the listener so that when it is closed, + // we can decrement the usage pool counter + if specificLn, ok := ln.(net.Listener); ok { + ln = deleteListener{specificLn, lnKey} + } } // other types, I guess we just return them directly @@ -170,12 +222,18 @@ type unixListener struct { func (uln *unixListener) Close() error { newCount := atomic.AddInt32(uln.count, -1) if newCount == 0 { + file, err := uln.File() + var name string + if err == nil { + name = file.Name() + } defer func() { - addr := uln.Addr().String() unixSocketsMu.Lock() delete(unixSockets, uln.mapKey) unixSocketsMu.Unlock() - _ = syscall.Unlink(addr) + if err == nil { + _ = syscall.Unlink(name) + } }() } return uln.UnixListener.Close() @@ -183,19 +241,25 @@ func (uln *unixListener) Close() error { type unixConn struct { *net.UnixConn - filename string - mapKey string - count *int32 // accessed atomically + mapKey string + count *int32 // accessed atomically } func (uc *unixConn) Close() error { newCount := atomic.AddInt32(uc.count, -1) if newCount == 0 { + file, err := uc.File() + var name string + if err == nil { + name = file.Name() + } defer func() { unixSocketsMu.Lock() delete(unixSockets, uc.mapKey) unixSocketsMu.Unlock() - _ = syscall.Unlink(uc.filename) + if err == nil { + _ = syscall.Unlink(name) + } }() } return uc.UnixConn.Close() @@ -211,6 +275,12 @@ var unixSockets = make(map[string]interface { File() (*os.File, error) }) +// socketFiles is a fd -> *os.File map used to make a FileListener/FilePacketConn from a socket file descriptor. +var socketFiles = map[uintptr]*os.File{} + +// socketFilesMu synchronizes socketFiles insertions +var socketFilesMu sync.Mutex + // deleteListener is a type that simply deletes itself // from the listenerPool when it closes. It is used // solely for the purpose of reference counting (i.e. diff --git a/listeners.go b/listeners.go index 0d9dd75381d..cf7b52018fc 100644 --- a/listeners.go +++ b/listeners.go @@ -58,7 +58,7 @@ type NetworkAddress struct { EndPort uint } -// ListenAll calls Listen() for all addresses represented by this struct, i.e. all ports in the range. +// ListenAll calls Listen for all addresses represented by this struct, i.e. all ports in the range. // (If the address doesn't use ports or has 1 port only, then only 1 listener will be created.) // It returns an error if any listener failed to bind, and closes any listeners opened up to that point. func (na NetworkAddress) ListenAll(ctx context.Context, config net.ListenConfig) ([]any, error) { @@ -106,7 +106,8 @@ func (na NetworkAddress) ListenAll(ctx context.Context, config net.ListenConfig) // portOffset to the start port. (For network types that do not use ports, the // portOffset is ignored.) // -// The provided ListenConfig is used to create the listener. Its Control function, +// First Listen checks if a plugin can provide a listener from this address. Otherwise, +// the provided ListenConfig is used to create the listener. Its Control function, // if set, may be wrapped by an internally-used Control function. The provided // context may be used to cancel long operations early. The context is not used // to close the listener after it has been created. @@ -129,6 +130,8 @@ func (na NetworkAddress) ListenAll(ctx context.Context, config net.ListenConfig) // Unix sockets will be unlinked before being created, to ensure we can bind to // it even if the previous program using it exited uncleanly; it will also be // unlinked upon a graceful exit (or when a new config does not use that socket). +// Listen synchronizes binds to unix domain sockets to avoid race conditions +// while an existing socket is unlinked. func (na NetworkAddress) Listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) { if na.IsUnixNetwork() { unixSocketsMu.Lock() @@ -146,54 +149,53 @@ func (na NetworkAddress) Listen(ctx context.Context, portOffset uint, config net func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) { var ( - ln any - err error - address string - unixFileMode fs.FileMode - isAbstractUnixSocket bool + ln any + err error + address string + unixFileMode fs.FileMode ) // split unix socket addr early so lnKey // is independent of permissions bits if na.IsUnixNetwork() { - var err error address, unixFileMode, err = internal.SplitUnixSocketPermissionsBits(na.Host) if err != nil { return nil, err } - isAbstractUnixSocket = strings.HasPrefix(address, "@") + } else if na.IsFdNetwork() { + address = na.Host } else { address = na.JoinHostPort(portOffset) } - // if this is a unix socket, see if we already have it open, - // force socket permissions on it and return early - if socket, err := reuseUnixSocket(na.Network, address); socket != nil || err != nil { - if !isAbstractUnixSocket { - if err := os.Chmod(address, unixFileMode); err != nil { - return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err) - } - } - return socket, err - } - - lnKey := listenerKey(na.Network, address) - if strings.HasPrefix(na.Network, "ip") { ln, err = config.ListenPacket(ctx, na.Network, address) } else { - ln, err = listenReusable(ctx, lnKey, na.Network, address, config) - } - if err != nil { - return nil, err + if na.IsUnixNetwork() { + // if this is a unix socket, see if we already have it open + ln, err = reuseUnixSocket(na.Network, address) + } + + if ln == nil && err == nil { + // otherwise, create a new listener + lnKey := listenerKey(na.Network, address) + ln, err = listenReusable(ctx, lnKey, na.Network, address, config) + } } + if ln == nil { return nil, fmt.Errorf("unsupported network type: %s", na.Network) } + if err != nil { + return nil, err + } + if IsUnixNetwork(na.Network) { + isAbstractUnixSocket := strings.HasPrefix(address, "@") if !isAbstractUnixSocket { - if err := os.Chmod(address, unixFileMode); err != nil { + err = os.Chmod(address, unixFileMode) + if err != nil { return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err) } } @@ -208,13 +210,19 @@ func (na NetworkAddress) IsUnixNetwork() bool { return IsUnixNetwork(na.Network) } +// IsUnixNetwork returns true if na.Network is +// fd or fdgram. +func (na NetworkAddress) IsFdNetwork() bool { + return IsFdNetwork(na.Network) +} + // JoinHostPort is like net.JoinHostPort, but where the port // is StartPort + offset. func (na NetworkAddress) JoinHostPort(offset uint) string { - if na.IsUnixNetwork() { + if na.IsUnixNetwork() || na.IsFdNetwork() { return na.Host } - return net.JoinHostPort(na.Host, strconv.Itoa(int(na.StartPort+offset))) + return net.JoinHostPort(na.Host, strconv.FormatUint(uint64(na.StartPort+offset), 10)) } // Expand returns one NetworkAddress for each port in the port range. @@ -248,7 +256,7 @@ func (na NetworkAddress) PortRangeSize() uint { } func (na NetworkAddress) isLoopback() bool { - if na.IsUnixNetwork() { + if na.IsUnixNetwork() || na.IsFdNetwork() { return true } if na.Host == "localhost" { @@ -292,6 +300,30 @@ func IsUnixNetwork(netw string) bool { return strings.HasPrefix(netw, "unix") } +// IsFdNetwork returns true if the netw is a fd network. +func IsFdNetwork(netw string) bool { + return strings.HasPrefix(netw, "fd") +} + +// normally we would simply append the port, +// but if host is IPv6, we need to ensure it +// is enclosed in [ ]; net.JoinHostPort does +// this for us, but host might also have a +// network type in front (e.g. "tcp/") leading +// to "[tcp/::1]" which causes parsing failures +// later; what we need is "tcp/[::1]", so we have +// to split the network and host, then re-combine +func ParseNetworkAddressFromHostPort(host, port string) (NetworkAddress, error) { + network, addr, ok := strings.Cut(host, "/") + if !ok { + addr = network + network = "" + } + addr = strings.Trim(addr, "[]") // IPv6 + networkAddr := JoinNetworkAddress(network, addr, port) + return ParseNetworkAddress(networkAddr) +} + // ParseNetworkAddress parses addr into its individual // components. The input string is expected to be of // the form "network/host:port-range" where any part is @@ -322,6 +354,12 @@ func ParseNetworkAddressWithDefaults(addr, defaultNetwork string, defaultPort ui Host: host, }, err } + if IsFdNetwork(network) { + return NetworkAddress{ + Network: network, + Host: host, + }, nil + } var start, end uint64 if port == "" { start = uint64(defaultPort) @@ -362,7 +400,7 @@ func SplitNetworkAddress(a string) (network, host, port string, err error) { network = strings.ToLower(strings.TrimSpace(beforeSlash)) a = afterSlash } - if IsUnixNetwork(network) { + if IsUnixNetwork(network) || IsFdNetwork(network) { host = a return } @@ -393,7 +431,7 @@ func JoinNetworkAddress(network, host, port string) string { if network != "" { a = network + "/" } - if (host != "" && port == "") || IsUnixNetwork(network) { + if (host != "" && port == "") || IsUnixNetwork(network) || IsFdNetwork(network) { a += host } else if port != "" { a += net.JoinHostPort(host, port) @@ -401,9 +439,11 @@ func JoinNetworkAddress(network, host, port string) string { return a } -// ListenQUIC returns a quic.EarlyListener suitable for use in a Caddy module. -// The network will be transformed into a QUIC-compatible type (if unix, then -// unixgram will be used; otherwise, udp will be used). +// ListenQUIC returns a http3.QUICEarlyListener suitable for use in a Caddy module. +// +// The network will be transformed into a QUIC-compatible type if the same address can be used with +// different networks. Currently this just means that for tcp, udp will be used with the same +// address instead. // // NOTE: This API is EXPERIMENTAL and may be changed or removed. func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config net.ListenConfig, tlsConf *tls.Config) (http3.QUICEarlyListener, error) { @@ -617,7 +657,8 @@ func RegisterNetwork(network string, getListener ListenerFunc) { if network == "tcp" || network == "tcp4" || network == "tcp6" || network == "udp" || network == "udp4" || network == "udp6" || network == "unix" || network == "unixpacket" || network == "unixgram" || - strings.HasPrefix("ip:", network) || strings.HasPrefix("ip4:", network) || strings.HasPrefix("ip6:", network) { + strings.HasPrefix("ip:", network) || strings.HasPrefix("ip4:", network) || strings.HasPrefix("ip6:", network) || + network == "fd" || network == "fdgram" { panic("network type " + network + " is reserved") } diff --git a/modules/caddyhttp/app.go b/modules/caddyhttp/app.go index 5dbecf9b263..673ebcb8e6b 100644 --- a/modules/caddyhttp/app.go +++ b/modules/caddyhttp/app.go @@ -18,6 +18,7 @@ import ( "context" "crypto/tls" "fmt" + "maps" "net" "net/http" "strconv" @@ -203,15 +204,73 @@ func (app *App) Provision(ctx caddy.Context) error { } } + // if no protocols configured explicitly, enable all except h2c + if len(srv.Protocols) == 0 { + srv.Protocols = []string{"h1", "h2", "h3"} + } + + srvProtocolsUnique := map[string]struct{}{} + for _, srvProtocol := range srv.Protocols { + srvProtocolsUnique[srvProtocol] = struct{}{} + } + _, h1ok := srvProtocolsUnique["h1"] + _, h2ok := srvProtocolsUnique["h2"] + _, h2cok := srvProtocolsUnique["h2c"] + // the Go standard library does not let us serve only HTTP/2 using // http.Server; we would probably need to write our own server - if !srv.protocol("h1") && (srv.protocol("h2") || srv.protocol("h2c")) { + if !h1ok && (h2ok || h2cok) { return fmt.Errorf("server %s: cannot enable HTTP/2 or H2C without enabling HTTP/1.1; add h1 to protocols or remove h2/h2c", srvName) } - // if no protocols configured explicitly, enable all except h2c - if len(srv.Protocols) == 0 { - srv.Protocols = []string{"h1", "h2", "h3"} + if srv.ListenProtocols != nil { + if len(srv.ListenProtocols) != len(srv.Listen) { + return fmt.Errorf("server %s: listener protocols count does not match address count: %d != %d", + srvName, len(srv.ListenProtocols), len(srv.Listen)) + } + + for i, lnProtocols := range srv.ListenProtocols { + if lnProtocols != nil { + // populate empty listen protocols with server protocols + lnProtocolsDefault := false + var lnProtocolsInclude []string + srvProtocolsInclude := maps.Clone(srvProtocolsUnique) + + // keep existing listener protocols unless they are empty + for _, lnProtocol := range lnProtocols { + if lnProtocol == "" { + lnProtocolsDefault = true + } else { + lnProtocolsInclude = append(lnProtocolsInclude, lnProtocol) + delete(srvProtocolsInclude, lnProtocol) + } + } + + // append server protocols to listener protocols if any listener protocols were empty + if lnProtocolsDefault { + for _, srvProtocol := range srv.Protocols { + if _, ok := srvProtocolsInclude[srvProtocol]; ok { + lnProtocolsInclude = append(lnProtocolsInclude, srvProtocol) + } + } + } + + lnProtocolsIncludeUnique := map[string]struct{}{} + for _, lnProtocol := range lnProtocolsInclude { + lnProtocolsIncludeUnique[lnProtocol] = struct{}{} + } + _, h1ok := lnProtocolsIncludeUnique["h1"] + _, h2ok := lnProtocolsIncludeUnique["h2"] + _, h2cok := lnProtocolsIncludeUnique["h2c"] + + // check if any listener protocols contain h2 or h2c without h1 + if !h1ok && (h2ok || h2cok) { + return fmt.Errorf("server %s, listener %d: cannot enable HTTP/2 or H2C without enabling HTTP/1.1; add h1 to protocols or remove h2/h2c", srvName, i) + } + + srv.ListenProtocols[i] = lnProtocolsInclude + } + } } // if not explicitly configured by the user, disallow TLS @@ -344,7 +403,7 @@ func (app *App) Validate() error { // check that every address in the port range is unique to this server; // we do not use <= here because PortRangeSize() adds 1 to EndPort for us for i := uint(0); i < listenAddr.PortRangeSize(); i++ { - addr := caddy.JoinNetworkAddress(listenAddr.Network, listenAddr.Host, strconv.Itoa(int(listenAddr.StartPort+i))) + addr := caddy.JoinNetworkAddress(listenAddr.Network, listenAddr.Host, strconv.FormatUint(uint64(listenAddr.StartPort+i), 10)) if sn, ok := lnAddrs[addr]; ok { return fmt.Errorf("server %s: listener address repeated: %s (already claimed by server '%s')", srvName, addr, sn) } @@ -422,99 +481,118 @@ func (app *App) Start() error { srv.server.Handler = h2c.NewHandler(srv, h2server) } - for _, lnAddr := range srv.Listen { + for lnIndex, lnAddr := range srv.Listen { listenAddr, err := caddy.ParseNetworkAddress(lnAddr) if err != nil { return fmt.Errorf("%s: parsing listen address '%s': %v", srvName, lnAddr, err) } + srv.addresses = append(srv.addresses, listenAddr) + protocols := srv.Protocols + if srv.ListenProtocols != nil && srv.ListenProtocols[lnIndex] != nil { + protocols = srv.ListenProtocols[lnIndex] + } + + protocolsUnique := map[string]struct{}{} + for _, protocol := range protocols { + protocolsUnique[protocol] = struct{}{} + } + _, h1ok := protocolsUnique["h1"] + _, h2ok := protocolsUnique["h2"] + _, h2cok := protocolsUnique["h2c"] + _, h3ok := protocolsUnique["h3"] + for portOffset := uint(0); portOffset < listenAddr.PortRangeSize(); portOffset++ { - // create the listener for this socket hostport := listenAddr.JoinHostPort(portOffset) - lnAny, err := listenAddr.Listen(app.ctx, portOffset, net.ListenConfig{KeepAlive: time.Duration(srv.KeepAliveInterval)}) - if err != nil { - return fmt.Errorf("listening on %s: %v", listenAddr.At(portOffset), err) - } - ln := lnAny.(net.Listener) - - // wrap listener before TLS (up to the TLS placeholder wrapper) - var lnWrapperIdx int - for i, lnWrapper := range srv.listenerWrappers { - if _, ok := lnWrapper.(*tlsPlaceholderWrapper); ok { - lnWrapperIdx = i + 1 // mark the next wrapper's spot - break - } - ln = lnWrapper.WrapListener(ln) - } // enable TLS if there is a policy and if this is not the HTTP port useTLS := len(srv.TLSConnPolicies) > 0 && int(listenAddr.StartPort+portOffset) != app.httpPort() - if useTLS { - // create TLS listener - this enables and terminates TLS - ln = tls.NewListener(ln, tlsCfg) - - // enable HTTP/3 if configured - if srv.protocol("h3") { - // Can't serve HTTP/3 on the same socket as HTTP/1 and 2 because it uses - // a different transport mechanism... which is fine, but the OS doesn't - // differentiate between a SOCK_STREAM file and a SOCK_DGRAM file; they - // are still one file on the system. So even though "unixpacket" and - // "unixgram" are different network types just as "tcp" and "udp" are, - // the OS will not let us use the same file as both STREAM and DGRAM. - if len(srv.Protocols) > 1 && listenAddr.IsUnixNetwork() { - app.logger.Warn("HTTP/3 disabled because Unix can't multiplex STREAM and DGRAM on same socket", - zap.String("file", hostport)) - for i := range srv.Protocols { - if srv.Protocols[i] == "h3" { - srv.Protocols = append(srv.Protocols[:i], srv.Protocols[i+1:]...) - break - } - } - } else { - app.logger.Info("enabling HTTP/3 listener", zap.String("addr", hostport)) - if err := srv.serveHTTP3(listenAddr.At(portOffset), tlsCfg); err != nil { - return err - } - } + + // enable HTTP/3 if configured + if h3ok && useTLS { + app.logger.Info("enabling HTTP/3 listener", zap.String("addr", hostport)) + if err := srv.serveHTTP3(listenAddr.At(portOffset), tlsCfg); err != nil { + return err } } - // finish wrapping listener where we left off before TLS - for i := lnWrapperIdx; i < len(srv.listenerWrappers); i++ { - ln = srv.listenerWrappers[i].WrapListener(ln) + if h3ok && !useTLS { + // Can only serve h3 with TLS enabled + app.logger.Warn("HTTP/3 skipped because it requires TLS", + zap.String("network", listenAddr.Network), + zap.String("addr", hostport)) } - // handle http2 if use tls listener wrapper - if useTLS { - http2lnWrapper := &http2Listener{ - Listener: ln, - server: srv.server, - h2server: h2server, + if h1ok || h2ok && useTLS || h2cok { + // create the listener for this socket + lnAny, err := listenAddr.Listen(app.ctx, portOffset, net.ListenConfig{KeepAlive: time.Duration(srv.KeepAliveInterval)}) + if err != nil { + return fmt.Errorf("listening on %s: %v", listenAddr.At(portOffset), err) + } + ln, ok := lnAny.(net.Listener) + if !ok { + return fmt.Errorf("network '%s' cannot handle HTTP/1 or HTTP/2 connections", listenAddr.Network) } - srv.h2listeners = append(srv.h2listeners, http2lnWrapper) - ln = http2lnWrapper - } - // if binding to port 0, the OS chooses a port for us; - // but the user won't know the port unless we print it - if !listenAddr.IsUnixNetwork() && listenAddr.StartPort == 0 && listenAddr.EndPort == 0 { - app.logger.Info("port 0 listener", - zap.String("input_address", lnAddr), - zap.String("actual_address", ln.Addr().String())) - } + if useTLS { + // create TLS listener - this enables and terminates TLS + ln = tls.NewListener(ln, tlsCfg) + } + + // wrap listener before TLS (up to the TLS placeholder wrapper) + var lnWrapperIdx int + for i, lnWrapper := range srv.listenerWrappers { + if _, ok := lnWrapper.(*tlsPlaceholderWrapper); ok { + lnWrapperIdx = i + 1 // mark the next wrapper's spot + break + } + ln = lnWrapper.WrapListener(ln) + } + + // finish wrapping listener where we left off before TLS + for i := lnWrapperIdx; i < len(srv.listenerWrappers); i++ { + ln = srv.listenerWrappers[i].WrapListener(ln) + } + + // handle http2 if use tls listener wrapper + if h2ok { + http2lnWrapper := &http2Listener{ + Listener: ln, + server: srv.server, + h2server: h2server, + } + srv.h2listeners = append(srv.h2listeners, http2lnWrapper) + ln = http2lnWrapper + } + + // if binding to port 0, the OS chooses a port for us; + // but the user won't know the port unless we print it + if !listenAddr.IsUnixNetwork() && !listenAddr.IsFdNetwork() && listenAddr.StartPort == 0 && listenAddr.EndPort == 0 { + app.logger.Info("port 0 listener", + zap.String("input_address", lnAddr), + zap.String("actual_address", ln.Addr().String())) + } - app.logger.Debug("starting server loop", - zap.String("address", ln.Addr().String()), - zap.Bool("tls", useTLS), - zap.Bool("http3", srv.h3server != nil)) + app.logger.Debug("starting server loop", + zap.String("address", ln.Addr().String()), + zap.Bool("tls", useTLS), + zap.Bool("http3", srv.h3server != nil)) - srv.listeners = append(srv.listeners, ln) + srv.listeners = append(srv.listeners, ln) + + // enable HTTP/1 if configured + if h1ok { + //nolint:errcheck + go srv.server.Serve(ln) + } + } - // enable HTTP/1 if configured - if srv.protocol("h1") { - //nolint:errcheck - go srv.server.Serve(ln) + if h2ok && !useTLS { + // Can only serve h2 with TLS enabled + app.logger.Warn("HTTP/2 skipped because it requires TLS", + zap.String("network", listenAddr.Network), + zap.String("addr", hostport)) } } } diff --git a/modules/caddyhttp/proxyprotocol/listenerwrapper.go b/modules/caddyhttp/proxyprotocol/listenerwrapper.go index 440e7071010..f5f2099ce42 100644 --- a/modules/caddyhttp/proxyprotocol/listenerwrapper.go +++ b/modules/caddyhttp/proxyprotocol/listenerwrapper.go @@ -72,7 +72,7 @@ func (pp *ListenerWrapper) Provision(ctx caddy.Context) error { pp.policy = func(options goproxy.ConnPolicyOptions) (goproxy.Policy, error) { // trust unix sockets - if network := options.Upstream.Network(); caddy.IsUnixNetwork(network) { + if network := options.Upstream.Network(); caddy.IsUnixNetwork(network) || caddy.IsFdNetwork(network) { return goproxy.USE, nil } ret := pp.FallbackPolicy diff --git a/modules/caddyhttp/reverseproxy/addresses.go b/modules/caddyhttp/reverseproxy/addresses.go index 82c1c799461..31f4aeb3502 100644 --- a/modules/caddyhttp/reverseproxy/addresses.go +++ b/modules/caddyhttp/reverseproxy/addresses.go @@ -137,7 +137,7 @@ func parseUpstreamDialAddress(upstreamAddr string) (parsedAddr, error) { } // we can assume a port if only a hostname is specified, but use of a // placeholder without a port likely means a port will be filled in - if port == "" && !strings.Contains(host, "{") && !caddy.IsUnixNetwork(network) { + if port == "" && !strings.Contains(host, "{") && !caddy.IsUnixNetwork(network) && !caddy.IsFdNetwork(network) { port = "80" } } diff --git a/modules/caddyhttp/reverseproxy/healthchecks.go b/modules/caddyhttp/reverseproxy/healthchecks.go index 319cc924818..1735e45a451 100644 --- a/modules/caddyhttp/reverseproxy/healthchecks.go +++ b/modules/caddyhttp/reverseproxy/healthchecks.go @@ -330,7 +330,7 @@ func (h *Handler) doActiveHealthCheckForAllHosts() { return } if hcp := uint(upstream.activeHealthCheckPort); hcp != 0 { - if addr.IsUnixNetwork() { + if addr.IsUnixNetwork() || addr.IsFdNetwork() { addr.Network = "tcp" // I guess we just assume TCP since we are using a port?? } addr.StartPort, addr.EndPort = hcp, hcp @@ -345,7 +345,7 @@ func (h *Handler) doActiveHealthCheckForAllHosts() { } hostAddr := addr.JoinHostPort(0) dialAddr := hostAddr - if addr.IsUnixNetwork() { + if addr.IsUnixNetwork() || addr.IsFdNetwork() { // this will be used as the Host portion of a http.Request URL, and // paths to socket files would produce an error when creating URL, // so use a fake Host value instead; unix sockets are usually local diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go index f5478cb37a4..5aa7e0f6351 100644 --- a/modules/caddyhttp/server.go +++ b/modules/caddyhttp/server.go @@ -220,6 +220,10 @@ type Server struct { // Default: `[h1 h2 h3]` Protocols []string `json:"protocols,omitempty"` + // ListenProtocols overrides Protocols for each parallel address in Listen. + // A nil value or element indicates that Protocols will be used instead. + ListenProtocols [][]string `json:"listen_protocols,omitempty"` + // If set, metrics observations will be enabled. // This setting is EXPERIMENTAL and subject to change. Metrics *Metrics `json:"metrics,omitempty"` @@ -597,7 +601,11 @@ func (s *Server) findLastRouteWithHostMatcher() int { // not already done, and then uses that server to serve HTTP/3 over // the listener, with Server s as the handler. func (s *Server) serveHTTP3(addr caddy.NetworkAddress, tlsCfg *tls.Config) error { - addr.Network = getHTTP3Network(addr.Network) + h3net, err := getHTTP3Network(addr.Network) + if err != nil { + return fmt.Errorf("starting HTTP/3 QUIC listener: %v", err) + } + addr.Network = h3net h3ln, err := addr.ListenQUIC(s.ctx, 0, net.ListenConfig{}, tlsCfg) if err != nil { return fmt.Errorf("starting HTTP/3 QUIC listener: %v", err) @@ -849,7 +857,21 @@ func (s *Server) logRequest( // protocol returns true if the protocol proto is configured/enabled. func (s *Server) protocol(proto string) bool { - return slices.Contains(s.Protocols, proto) + if s.ListenProtocols == nil { + if slices.Contains(s.Protocols, proto) { + return true + } + } else { + for _, lnProtocols := range s.ListenProtocols { + for _, lnProtocol := range lnProtocols { + if lnProtocol == "" && slices.Contains(s.Protocols, proto) || lnProtocol == proto { + return true + } + } + } + } + + return false } // Listeners returns the server's listeners. These are active listeners, @@ -1089,9 +1111,14 @@ const ( ) var networkTypesHTTP3 = map[string]string{ - "unix": "unixgram", - "tcp4": "udp4", - "tcp6": "udp6", + "unixgram": "unixgram", + "udp": "udp", + "udp4": "udp4", + "udp6": "udp6", + "tcp": "udp", + "tcp4": "udp4", + "tcp6": "udp6", + "fdgram": "fdgram", } // RegisterNetworkHTTP3 registers a mapping from non-HTTP/3 network to HTTP/3 @@ -1106,11 +1133,10 @@ func RegisterNetworkHTTP3(originalNetwork, h3Network string) { networkTypesHTTP3[originalNetwork] = h3Network } -func getHTTP3Network(originalNetwork string) string { +func getHTTP3Network(originalNetwork string) (string, error) { h3Network, ok := networkTypesHTTP3[strings.ToLower(originalNetwork)] if !ok { - // TODO: Maybe a better default is to not enable HTTP/3 if we do not know the network? - return "udp" + return "", fmt.Errorf("network '%s' cannot handle HTTP/3 connections", originalNetwork) } - return h3Network + return h3Network, nil } diff --git a/modules/caddyhttp/staticresp.go b/modules/caddyhttp/staticresp.go index 1fea6978f95..1b93ede4b65 100644 --- a/modules/caddyhttp/staticresp.go +++ b/modules/caddyhttp/staticresp.go @@ -387,7 +387,7 @@ func cmdRespond(fl caddycmd.Flags) (int, error) { return caddy.ExitCodeFailedStartup, err } - if !listenAddr.IsUnixNetwork() { + if !listenAddr.IsUnixNetwork() && !listenAddr.IsFdNetwork() { listenAddrs := make([]string, 0, listenAddr.PortRangeSize()) for offset := uint(0); offset < listenAddr.PortRangeSize(); offset++ { listenAddrs = append(listenAddrs, listenAddr.JoinHostPort(offset)) diff --git a/replacer.go b/replacer.go index 65815c92aec..297dd935c69 100644 --- a/replacer.go +++ b/replacer.go @@ -299,11 +299,11 @@ func ToString(val any) string { case int64: return strconv.Itoa(int(v)) case uint: - return strconv.Itoa(int(v)) + return strconv.FormatUint(uint64(v), 10) case uint32: - return strconv.Itoa(int(v)) + return strconv.FormatUint(uint64(v), 10) case uint64: - return strconv.Itoa(int(v)) + return strconv.FormatUint(v, 10) case float32: return strconv.FormatFloat(float64(v), 'f', -1, 32) case float64: From 0e829bc4187e265d1e4ccb1bec24996088acf0a5 Mon Sep 17 00:00:00 2001 From: Aaron Paterson <9441877+MayCXC@users.noreply.github.com> Date: Tue, 1 Oct 2024 01:47:21 -0400 Subject: [PATCH 12/39] caddyhttp: Fix listener wrapper regression from #6573 (#6599) --- listeners.go | 8 ++++---- modules/caddyhttp/app.go | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/listeners.go b/listeners.go index cf7b52018fc..3a2a5180f6f 100644 --- a/listeners.go +++ b/listeners.go @@ -183,14 +183,14 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net } } - if ln == nil { - return nil, fmt.Errorf("unsupported network type: %s", na.Network) - } - if err != nil { return nil, err } + if ln == nil { + return nil, fmt.Errorf("unsupported network type: %s", na.Network) + } + if IsUnixNetwork(na.Network) { isAbstractUnixSocket := strings.HasPrefix(address, "@") if !isAbstractUnixSocket { diff --git a/modules/caddyhttp/app.go b/modules/caddyhttp/app.go index 673ebcb8e6b..7a5c1062342 100644 --- a/modules/caddyhttp/app.go +++ b/modules/caddyhttp/app.go @@ -535,11 +535,6 @@ func (app *App) Start() error { return fmt.Errorf("network '%s' cannot handle HTTP/1 or HTTP/2 connections", listenAddr.Network) } - if useTLS { - // create TLS listener - this enables and terminates TLS - ln = tls.NewListener(ln, tlsCfg) - } - // wrap listener before TLS (up to the TLS placeholder wrapper) var lnWrapperIdx int for i, lnWrapper := range srv.listenerWrappers { @@ -550,6 +545,11 @@ func (app *App) Start() error { ln = lnWrapper.WrapListener(ln) } + if useTLS { + // create TLS listener - this enables and terminates TLS + ln = tls.NewListener(ln, tlsCfg) + } + // finish wrapping listener where we left off before TLS for i := lnWrapperIdx; i < len(srv.listenerWrappers); i++ { ln = srv.listenerWrappers[i].WrapListener(ln) From 571f88d86f244d1d0211379c5aaad1ddada87fb1 Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Tue, 1 Oct 2024 12:56:30 -0400 Subject: [PATCH 13/39] chore: Adjust incorrect `reverse_proxy` Caddyfile comment (#6598) --- modules/caddyhttp/reverseproxy/caddyfile.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/caddyhttp/reverseproxy/caddyfile.go b/modules/caddyhttp/reverseproxy/caddyfile.go index 12e2b9b97a4..ab1dcdd029c 100644 --- a/modules/caddyhttp/reverseproxy/caddyfile.go +++ b/modules/caddyhttp/reverseproxy/caddyfile.go @@ -93,12 +93,11 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) // // # streaming // flush_interval -// buffer_requests -// buffer_responses -// max_buffer_size +// request_buffers +// response_buffers // stream_timeout // stream_close_delay -// trace_logs +// verbose_logs // // # request manipulation // trusted_proxies [private_ranges] From f3aead0e4df57716d525f3a25fb11f70bc72b20a Mon Sep 17 00:00:00 2001 From: WeidiDeng Date: Wed, 2 Oct 2024 01:19:03 +0800 Subject: [PATCH 14/39] http: ReponseWriter prefer ReadFrom if available (#6565) Co-authored-by: Matt Holt --- modules/caddyhttp/responsewriter.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/caddyhttp/responsewriter.go b/modules/caddyhttp/responsewriter.go index 808d2de3aa3..3c0f89d0b33 100644 --- a/modules/caddyhttp/responsewriter.go +++ b/modules/caddyhttp/responsewriter.go @@ -42,9 +42,13 @@ func (rww *ResponseWriterWrapper) Push(target string, opts *http.PushOptions) er return ErrNotImplemented } -// ReadFrom implements io.ReaderFrom. It simply calls io.Copy, -// which uses io.ReaderFrom if available. +// ReadFrom implements io.ReaderFrom. It retries to use io.ReaderFrom if available, +// then fallback to io.Copy. +// see: https://github.com/caddyserver/caddy/issues/6546 func (rww *ResponseWriterWrapper) ReadFrom(r io.Reader) (n int64, err error) { + if rf, ok := rww.ResponseWriter.(io.ReaderFrom); ok { + return rf.ReadFrom(r) + } return io.Copy(rww.ResponseWriter, r) } From 9b4acc2449b10ff7c32eff4ddf1838cd2d93c9cf Mon Sep 17 00:00:00 2001 From: Matt Holt Date: Tue, 1 Oct 2024 17:18:17 -0600 Subject: [PATCH 15/39] caddytls: Support new tls.context module (#6369) * caddytls: Support new tls.context module This allows modules to manipulate the context passed into CertMagic's GetCertificate function, which can be useful for tracing/metrics, or other custom logic. This is experimental and may resolve the request of a sponsor, so we'll see how it goes! * Derpy derp --- modules/caddytls/connpolicy.go | 38 ++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/modules/caddytls/connpolicy.go b/modules/caddytls/connpolicy.go index 2e2d4f748cd..f415fffa014 100644 --- a/modules/caddytls/connpolicy.go +++ b/modules/caddytls/connpolicy.go @@ -15,6 +15,7 @@ package caddytls import ( + "context" "crypto/tls" "crypto/x509" "encoding/base64" @@ -78,6 +79,14 @@ func (cp ConnectionPolicies) Provision(ctx caddy.Context) error { cp[i].ClientAuthentication.verifiers = append(cp[i].ClientAuthentication.verifiers, validator.(ClientCertificateVerifier)) } } + + if len(pol.HandshakeContextRaw) > 0 { + modIface, err := ctx.LoadModule(pol, "HandshakeContextRaw") + if err != nil { + return fmt.Errorf("loading handshake context module: %v", err) + } + cp[i].handshakeContext = modIface.(HandshakeContext) + } } return nil @@ -137,6 +146,7 @@ type ConnectionPolicy struct { // How to match this policy with a TLS ClientHello. If // this policy is the first to match, it will be used. MatchersRaw caddy.ModuleMap `json:"match,omitempty" caddy:"namespace=tls.handshake_match"` + matchers []ConnectionMatcher // How to choose a certificate if more than one matched // the given ServerName (SNI) value. @@ -192,6 +202,12 @@ type ConnectionPolicy struct { // This feature is EXPERIMENTAL and subject to change or removal. InsecureSecretsLog string `json:"insecure_secrets_log,omitempty"` + // A module that can manipulate the context passed into CertMagic's + // certificate management functions during TLS handshakes. + // EXPERIMENTAL - subject to change or removal. + HandshakeContextRaw json.RawMessage `json:"handshake_context,omitempty" caddy:"namespace=tls.context inline_key=module"` + handshakeContext HandshakeContext + // TLSConfig is the fully-formed, standard lib TLS config // used to serve TLS connections. Provision all // ConnectionPolicies to populate this. It is exported only @@ -199,8 +215,15 @@ type ConnectionPolicy struct { // if necessary (like to adjust NextProtos to disable HTTP/2), // and may be unexported in the future. TLSConfig *tls.Config `json:"-"` +} - matchers []ConnectionMatcher +type HandshakeContext interface { + // HandshakeContext returns a context to pass into CertMagic's + // GetCertificate function used to serve, load, and manage certs + // during TLS handshakes. Generally you'll start with the context + // from the ClientHelloInfo, but you may use other information + // from it as well. Return an error to abort the handshake. + HandshakeContext(*tls.ClientHelloInfo) (context.Context, error) } func (p *ConnectionPolicy) buildStandardTLSConfig(ctx caddy.Context) error { @@ -240,7 +263,18 @@ func (p *ConnectionPolicy) buildStandardTLSConfig(ctx caddy.Context) error { } cfg.DefaultServerName = p.DefaultSNI cfg.FallbackServerName = p.FallbackSNI - return cfg.GetCertificate(hello) + + // TODO: experimental: if a handshake context module is configured, allow it + // to modify the context before passing it into CertMagic's GetCertificate + ctx := hello.Context() + if p.handshakeContext != nil { + ctx, err = p.handshakeContext.HandshakeContext(hello) + if err != nil { + return nil, fmt.Errorf("handshake context: %v", err) + } + } + + return cfg.GetCertificateWithContext(ctx, hello) }, MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS13, From c8adb1b553412253d5f166065635ab809d3eef33 Mon Sep 17 00:00:00 2001 From: Matt Holt Date: Tue, 1 Oct 2024 20:31:30 -0600 Subject: [PATCH 16/39] cmd: Better error handling when reloading (#6601) * caddyhttp: Limit auto-HTTPS error logs to 100 domains * Improve error message and increase error size limit --- cmd/commandfuncs.go | 2 +- modules/caddyhttp/autohttps.go | 2 +- modules/caddytls/tls.go | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go index 50e9b110da5..a5c357cd7c9 100644 --- a/cmd/commandfuncs.go +++ b/cmd/commandfuncs.go @@ -716,7 +716,7 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io // if it didn't work, let the user know if resp.StatusCode >= 400 { - respBody, err := io.ReadAll(io.LimitReader(resp.Body, 1024*10)) + respBody, err := io.ReadAll(io.LimitReader(resp.Body, 1024*1024*2)) if err != nil { return nil, fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err) } diff --git a/modules/caddyhttp/autohttps.go b/modules/caddyhttp/autohttps.go index ea5a07b3c05..79fdfd6ec1f 100644 --- a/modules/caddyhttp/autohttps.go +++ b/modules/caddyhttp/autohttps.go @@ -732,7 +732,7 @@ func (app *App) automaticHTTPSPhase2() error { ) err := app.tlsApp.Manage(app.allCertDomains) if err != nil { - return fmt.Errorf("managing certificates for %v: %s", app.allCertDomains, err) + return fmt.Errorf("managing certificates for %d domains: %s", len(app.allCertDomains), err) } app.allCertDomains = nil // no longer needed; allow GC to deallocate return nil diff --git a/modules/caddytls/tls.go b/modules/caddytls/tls.go index 5f3d0eaeba1..f04beb2ee47 100644 --- a/modules/caddytls/tls.go +++ b/modules/caddytls/tls.go @@ -447,6 +447,10 @@ func (t *TLS) Manage(names []string) error { for ap, names := range policyToNames { err := ap.magic.ManageAsync(t.ctx.Context, names) if err != nil { + const maxNamesToDisplay = 100 + if len(names) > maxNamesToDisplay { + names = append(names[:maxNamesToDisplay], fmt.Sprintf("(%d more...)", len(names)-maxNamesToDisplay)) + } return fmt.Errorf("automate: manage %v: %v", names, err) } for _, name := range names { From 792f1c7ed759b97ee6dc80246cf2de054c09a12f Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Wed, 2 Oct 2024 08:34:04 -0400 Subject: [PATCH 17/39] caddyhttp: Escaping placeholders in CEL, add `vars` and `vars_regexp` (#6594) * caddyhttp: Escaping placeholders in CEL * Simplify some of the test cases * Implement vars and vars_regexp in CEL * dupl lint is dumb * Better consts for the placeholder CEL shortcut * Bump CEL version, register a few extensions * Refactor s390x test script for readability * Add retries for s390x to smooth over flakiness * Switch to `ph` for the CEL shortcut (match it in templates cause why not) --- .github/workflows/ci.yml | 28 ++++- .golangci.yml | 6 + go.mod | 2 +- go.sum | 4 +- modules/caddyhttp/celmatcher.go | 51 ++++++--- modules/caddyhttp/celmatcher_test.go | 133 +++++++++++++++++----- modules/caddyhttp/fileserver/matcher.go | 10 +- modules/caddyhttp/matchers.go | 4 +- modules/caddyhttp/templates/templates.go | 6 + modules/caddyhttp/templates/tplcontext.go | 1 + modules/caddyhttp/vars.go | 89 +++++++++++++++ 11 files changed, 276 insertions(+), 58 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c7a67c5bff..3a74d8cc69b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -156,13 +156,35 @@ jobs: # short sha is enough? short_sha=$(git rev-parse --short HEAD) + # To shorten the following lines + ssh_opts="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" + ssh_host="$CI_USER@ci-s390x.caddyserver.com" + # The environment is fresh, so there's no point in keeping accepting and adding the key. - rsync -arz -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" --progress --delete --exclude '.git' . "$CI_USER"@ci-s390x.caddyserver.com:/var/tmp/"$short_sha" - ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -t "$CI_USER"@ci-s390x.caddyserver.com "cd /var/tmp/$short_sha; go version; go env; printf "\n\n";CGO_ENABLED=0 go test -p 1 -tags nobadger -v ./..." + rsync -arz -e "ssh $ssh_opts" --progress --delete --exclude '.git' . "$ssh_host":/var/tmp/"$short_sha" + ssh $ssh_opts -t "$ssh_host" bash < 0)); do + CGO_ENABLED=0 go test -p 1 -tags nobadger -v ./... + exit_code=$? + if ((exit_code == 0)); then + break + fi + echo "\n\nTest failed: \$exit_code, retrying..." + ((retries--)) + done + echo "Remote exit code: \$exit_code" + exit \$exit_code + EOF test_result=$? # There's no need leaving the files around - ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "$CI_USER"@ci-s390x.caddyserver.com "rm -rf /var/tmp/'$short_sha'" + ssh $ssh_opts "$ssh_host" "rm -rf /var/tmp/'$short_sha'" echo "Test exit code: $test_result" exit $test_result diff --git a/.golangci.yml b/.golangci.yml index e8b2a55d3f5..aecff563eed 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -171,6 +171,12 @@ issues: - path: modules/logging/filters.go linters: - dupl + - path: modules/caddyhttp/matchers.go + linters: + - dupl + - path: modules/caddyhttp/vars.go + linters: + - dupl - path: _test\.go linters: - errcheck diff --git a/go.mod b/go.mod index 0c4f1edd749..62fe30dee56 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/caddyserver/zerossl v0.1.3 github.com/dustin/go-humanize v1.0.1 github.com/go-chi/chi/v5 v5.0.12 - github.com/google/cel-go v0.20.1 + github.com/google/cel-go v0.21.0 github.com/google/uuid v1.6.0 github.com/klauspost/compress v1.17.8 github.com/klauspost/cpuid/v2 v2.2.7 diff --git a/go.sum b/go.sum index 6bf0bd3e2b7..1f741c104bc 100644 --- a/go.sum +++ b/go.sum @@ -198,8 +198,8 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= -github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= +github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= +github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 h1:heyoXNxkRT155x4jTAiSv5BVSVkueifPUm+Q8LUXMRo= github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k= diff --git a/modules/caddyhttp/celmatcher.go b/modules/caddyhttp/celmatcher.go index a5565eb9840..2a03ebba723 100644 --- a/modules/caddyhttp/celmatcher.go +++ b/modules/caddyhttp/celmatcher.go @@ -126,6 +126,10 @@ func (m *MatchExpression) Provision(ctx caddy.Context) error { // light (and possibly naïve) syntactic sugar m.expandedExpr = placeholderRegexp.ReplaceAllString(m.Expr, placeholderExpansion) + // as a second pass, we'll strip the escape character from an escaped + // placeholder, so that it can be used as an input to other CEL functions + m.expandedExpr = escapedPlaceholderRegexp.ReplaceAllString(m.expandedExpr, escapedPlaceholderExpansion) + // our type adapter expands CEL's standard type support m.ta = celTypeAdapter{} @@ -159,14 +163,17 @@ func (m *MatchExpression) Provision(ctx caddy.Context) error { // create the CEL environment env, err := cel.NewEnv( - cel.Function(placeholderFuncName, cel.SingletonBinaryBinding(m.caddyPlaceholderFunc), cel.Overload( - placeholderFuncName+"_httpRequest_string", + cel.Function(CELPlaceholderFuncName, cel.SingletonBinaryBinding(m.caddyPlaceholderFunc), cel.Overload( + CELPlaceholderFuncName+"_httpRequest_string", []*cel.Type{httpRequestObjectType, cel.StringType}, cel.AnyType, )), - cel.Variable("request", httpRequestObjectType), + cel.Variable(CELRequestVarName, httpRequestObjectType), cel.CustomTypeAdapter(m.ta), ext.Strings(), + ext.Bindings(), + ext.Lists(), + ext.Math(), matcherLib, ) if err != nil { @@ -247,7 +254,7 @@ func (m MatchExpression) caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val { return types.NewErr( "invalid request of type '%v' to %s(request, placeholderVarName)", lhs.Type(), - placeholderFuncName, + CELPlaceholderFuncName, ) } phStr, ok := rhs.(types.String) @@ -255,7 +262,7 @@ func (m MatchExpression) caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val { return types.NewErr( "invalid placeholder variable name of type '%v' to %s(request, placeholderVarName)", rhs.Type(), - placeholderFuncName, + CELPlaceholderFuncName, ) } @@ -275,7 +282,7 @@ var httpRequestCELType = cel.ObjectType("http.Request", traits.ReceiverType) type celHTTPRequest struct{ *http.Request } func (cr celHTTPRequest) ResolveName(name string) (any, bool) { - if name == "request" { + if name == CELRequestVarName { return cr, true } return nil, false @@ -457,15 +464,15 @@ func CELMatcherDecorator(funcName string, fac CELMatcherFactory) interpreter.Int callArgs := call.Args() reqAttr, ok := callArgs[0].(interpreter.InterpretableAttribute) if !ok { - return nil, errors.New("missing 'request' argument") + return nil, errors.New("missing 'req' argument") } nsAttr, ok := reqAttr.Attr().(interpreter.NamespacedAttribute) if !ok { - return nil, errors.New("missing 'request' argument") + return nil, errors.New("missing 'req' argument") } varNames := nsAttr.CandidateVariableNames() - if len(varNames) != 1 || len(varNames) == 1 && varNames[0] != "request" { - return nil, errors.New("missing 'request' argument") + if len(varNames) != 1 || len(varNames) == 1 && varNames[0] != CELRequestVarName { + return nil, errors.New("missing 'req' argument") } matcherData, ok := callArgs[1].(interpreter.InterpretableConst) if !ok { @@ -524,7 +531,7 @@ func celMatcherStringListMacroExpander(funcName string) cel.MacroFactory { return nil, eh.NewError(arg.ID(), "matcher arguments must be string constants") } } - return eh.NewCall(funcName, eh.NewIdent("request"), eh.NewList(matchArgs...)), nil + return eh.NewCall(funcName, eh.NewIdent(CELRequestVarName), eh.NewList(matchArgs...)), nil } } @@ -538,7 +545,7 @@ func celMatcherStringMacroExpander(funcName string) parser.MacroExpander { return nil, eh.NewError(0, "matcher requires one argument") } if isCELStringExpr(args[0]) { - return eh.NewCall(funcName, eh.NewIdent("request"), args[0]), nil + return eh.NewCall(funcName, eh.NewIdent(CELRequestVarName), args[0]), nil } return nil, eh.NewError(args[0].ID(), "matcher argument must be a string literal") } @@ -572,7 +579,7 @@ func celMatcherJSONMacroExpander(funcName string) parser.MacroExpander { return nil, eh.NewError(entry.AsMapEntry().Value().ID(), "matcher map values must be string or list literals") } } - return eh.NewCall(funcName, eh.NewIdent("request"), arg), nil + return eh.NewCall(funcName, eh.NewIdent(CELRequestVarName), arg), nil case ast.UnspecifiedExprKind, ast.CallKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.SelectKind: // appeasing the linter :) } @@ -646,7 +653,7 @@ func isCELCaddyPlaceholderCall(e ast.Expr) bool { switch e.Kind() { case ast.CallKind: call := e.AsCall() - if call.FunctionName() == "caddyPlaceholder" { + if call.FunctionName() == CELPlaceholderFuncName { return true } case ast.UnspecifiedExprKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind: @@ -701,8 +708,15 @@ func isCELStringListLiteral(e ast.Expr) bool { // expressions with a proper CEL function call; this is // just for syntactic sugar. var ( - placeholderRegexp = regexp.MustCompile(`{([a-zA-Z][\w.-]+)}`) - placeholderExpansion = `caddyPlaceholder(request, "${1}")` + // The placeholder may not be preceded by a backslash; the expansion + // will include the preceding character if it is not a backslash. + placeholderRegexp = regexp.MustCompile(`([^\\]|^){([a-zA-Z][\w.-]+)}`) + placeholderExpansion = `${1}ph(req, "${2}")` + + // As a second pass, we need to strip the escape character in front of + // the placeholder, if it exists. + escapedPlaceholderRegexp = regexp.MustCompile(`\\{([a-zA-Z][\w.-]+)}`) + escapedPlaceholderExpansion = `{${1}}` CELTypeJSON = cel.MapType(cel.StringType, cel.DynType) ) @@ -710,7 +724,10 @@ var ( var httpRequestObjectType = cel.ObjectType("http.Request") // The name of the CEL function which accesses Replacer values. -const placeholderFuncName = "caddyPlaceholder" +const CELPlaceholderFuncName = "ph" + +// The name of the CEL request variable. +const CELRequestVarName = "req" const MatcherNameCtxKey = "matcher_name" diff --git a/modules/caddyhttp/celmatcher_test.go b/modules/caddyhttp/celmatcher_test.go index 25fdcf45e66..26491b7ca3f 100644 --- a/modules/caddyhttp/celmatcher_test.go +++ b/modules/caddyhttp/celmatcher_test.go @@ -70,13 +70,36 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV wantResult: true, }, { - name: "header error (MatchHeader)", + name: "header matches an escaped placeholder value (MatchHeader)", expression: &MatchExpression{ - Expr: `header('foo')`, + Expr: `header({'Field': '\\\{foobar}'})`, }, urlTarget: "https://example.com/foo", - httpHeader: &http.Header{"Field": []string{"foo", "bar"}}, - wantErr: true, + httpHeader: &http.Header{"Field": []string{"{foobar}"}}, + wantResult: true, + }, + { + name: "header matches an placeholder replaced during the header matcher (MatchHeader)", + expression: &MatchExpression{ + Expr: `header({'Field': '\{http.request.uri.path}'})`, + }, + urlTarget: "https://example.com/foo", + httpHeader: &http.Header{"Field": []string{"/foo"}}, + wantResult: true, + }, + { + name: "header error, invalid escape sequence (MatchHeader)", + expression: &MatchExpression{ + Expr: `header({'Field': '\\{foobar}'})`, + }, + wantErr: true, + }, + { + name: "header error, needs to be JSON syntax with field as key (MatchHeader)", + expression: &MatchExpression{ + Expr: `header('foo')`, + }, + wantErr: true, }, { name: "header_regexp matches (MatchHeaderRE)", @@ -110,9 +133,7 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV expression: &MatchExpression{ Expr: `header_regexp('foo')`, }, - urlTarget: "https://example.com/foo", - httpHeader: &http.Header{"Field": []string{"foo", "bar"}}, - wantErr: true, + wantErr: true, }, { name: "host matches localhost (MatchHost)", @@ -143,8 +164,7 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV expression: &MatchExpression{ Expr: `host(80)`, }, - urlTarget: "http://localhost:80", - wantErr: true, + wantErr: true, }, { name: "method does not match (MatchMethod)", @@ -169,9 +189,7 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV expression: &MatchExpression{ Expr: `method()`, }, - urlTarget: "https://foo.example.com", - httpMethod: "PUT", - wantErr: true, + wantErr: true, }, { name: "path matches substring (MatchPath)", @@ -266,24 +284,21 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV expression: &MatchExpression{ Expr: `protocol()`, }, - urlTarget: "https://example.com", - wantErr: true, + wantErr: true, }, { name: "protocol invocation error too many args (MatchProtocol)", expression: &MatchExpression{ Expr: `protocol('grpc', 'https')`, }, - urlTarget: "https://example.com", - wantErr: true, + wantErr: true, }, { name: "protocol invocation error wrong arg type (MatchProtocol)", expression: &MatchExpression{ Expr: `protocol(true)`, }, - urlTarget: "https://example.com", - wantErr: true, + wantErr: true, }, { name: "query does not match against a specific value (MatchQuery)", @@ -330,40 +345,35 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV expression: &MatchExpression{ Expr: `query({1: "1"})`, }, - urlTarget: "https://example.com/foo", - wantErr: true, + wantErr: true, }, { name: "query error typed struct instead of map (MatchQuery)", expression: &MatchExpression{ Expr: `query(Message{field: "1"})`, }, - urlTarget: "https://example.com/foo", - wantErr: true, + wantErr: true, }, { name: "query error bad map value type (MatchQuery)", expression: &MatchExpression{ Expr: `query({"debug": 1})`, }, - urlTarget: "https://example.com/foo/?debug=1", - wantErr: true, + wantErr: true, }, { name: "query error no args (MatchQuery)", expression: &MatchExpression{ Expr: `query()`, }, - urlTarget: "https://example.com/foo/?debug=1", - wantErr: true, + wantErr: true, }, { name: "remote_ip error no args (MatchRemoteIP)", expression: &MatchExpression{ Expr: `remote_ip()`, }, - urlTarget: "https://example.com/foo", - wantErr: true, + wantErr: true, }, { name: "remote_ip single IP match (MatchRemoteIP)", @@ -373,6 +383,67 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV urlTarget: "https://example.com/foo", wantResult: true, }, + { + name: "vars value (VarsMatcher)", + expression: &MatchExpression{ + Expr: `vars({'foo': 'bar'})`, + }, + urlTarget: "https://example.com/foo", + wantResult: true, + }, + { + name: "vars matches placeholder, needs escape (VarsMatcher)", + expression: &MatchExpression{ + Expr: `vars({'\{http.request.uri.path}': '/foo'})`, + }, + urlTarget: "https://example.com/foo", + wantResult: true, + }, + { + name: "vars error wrong syntax (VarsMatcher)", + expression: &MatchExpression{ + Expr: `vars('foo', 'bar')`, + }, + wantErr: true, + }, + { + name: "vars error no args (VarsMatcher)", + expression: &MatchExpression{ + Expr: `vars()`, + }, + wantErr: true, + }, + { + name: "vars_regexp value (MatchVarsRE)", + expression: &MatchExpression{ + Expr: `vars_regexp('foo', 'ba?r')`, + }, + urlTarget: "https://example.com/foo", + wantResult: true, + }, + { + name: "vars_regexp value with name (MatchVarsRE)", + expression: &MatchExpression{ + Expr: `vars_regexp('name', 'foo', 'ba?r')`, + }, + urlTarget: "https://example.com/foo", + wantResult: true, + }, + { + name: "vars_regexp matches placeholder, needs escape (MatchVarsRE)", + expression: &MatchExpression{ + Expr: `vars_regexp('\{http.request.uri.path}', '/fo?o')`, + }, + urlTarget: "https://example.com/foo", + wantResult: true, + }, + { + name: "vars_regexp error no args (MatchVarsRE)", + expression: &MatchExpression{ + Expr: `vars_regexp()`, + }, + wantErr: true, + }, } ) @@ -396,6 +467,9 @@ func TestMatchExpressionMatch(t *testing.T) { } repl := caddy.NewReplacer() ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + ctx = context.WithValue(ctx, VarsCtxKey, map[string]any{ + "foo": "bar", + }) req = req.WithContext(ctx) addHTTPVarsToReplacer(repl, req, httptest.NewRecorder()) @@ -436,6 +510,9 @@ func BenchmarkMatchExpressionMatch(b *testing.B) { } repl := caddy.NewReplacer() ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + ctx = context.WithValue(ctx, VarsCtxKey, map[string]any{ + "foo": "bar", + }) req = req.WithContext(ctx) addHTTPVarsToReplacer(repl, req, httptest.NewRecorder()) if tc.clientCertificate != nil { diff --git a/modules/caddyhttp/fileserver/matcher.go b/modules/caddyhttp/fileserver/matcher.go index 6ab2180ad77..71de1db297c 100644 --- a/modules/caddyhttp/fileserver/matcher.go +++ b/modules/caddyhttp/fileserver/matcher.go @@ -225,7 +225,7 @@ func celFileMatcherMacroExpander() parser.MacroExpander { return func(eh parser.ExprHelper, target ast.Expr, args []ast.Expr) (ast.Expr, *common.Error) { if len(args) == 0 { return eh.NewCall("file", - eh.NewIdent("request"), + eh.NewIdent(caddyhttp.CELRequestVarName), eh.NewMap(), ), nil } @@ -233,7 +233,7 @@ func celFileMatcherMacroExpander() parser.MacroExpander { arg := args[0] if isCELStringLiteral(arg) || isCELCaddyPlaceholderCall(arg) { return eh.NewCall("file", - eh.NewIdent("request"), + eh.NewIdent(caddyhttp.CELRequestVarName), eh.NewMap(eh.NewMapEntry( eh.NewLiteral(types.String("try_files")), eh.NewList(arg), @@ -242,7 +242,7 @@ func celFileMatcherMacroExpander() parser.MacroExpander { ), nil } if isCELTryFilesLiteral(arg) { - return eh.NewCall("file", eh.NewIdent("request"), arg), nil + return eh.NewCall("file", eh.NewIdent(caddyhttp.CELRequestVarName), arg), nil } return nil, &common.Error{ Location: eh.OffsetLocation(arg.ID()), @@ -259,7 +259,7 @@ func celFileMatcherMacroExpander() parser.MacroExpander { } } return eh.NewCall("file", - eh.NewIdent("request"), + eh.NewIdent(caddyhttp.CELRequestVarName), eh.NewMap(eh.NewMapEntry( eh.NewLiteral(types.String("try_files")), eh.NewList(args...), @@ -634,7 +634,7 @@ func isCELCaddyPlaceholderCall(e ast.Expr) bool { switch e.Kind() { case ast.CallKind: call := e.AsCall() - if call.FunctionName() == "caddyPlaceholder" { + if call.FunctionName() == caddyhttp.CELPlaceholderFuncName { return true } case ast.UnspecifiedExprKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind: diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go index a0bc6d63a61..93a36237bd1 100644 --- a/modules/caddyhttp/matchers.go +++ b/modules/caddyhttp/matchers.go @@ -1562,8 +1562,8 @@ var ( _ CELLibraryProducer = (*MatchHeader)(nil) _ CELLibraryProducer = (*MatchHeaderRE)(nil) _ CELLibraryProducer = (*MatchProtocol)(nil) - // _ CELLibraryProducer = (*VarsMatcher)(nil) - // _ CELLibraryProducer = (*MatchVarsRE)(nil) + _ CELLibraryProducer = (*VarsMatcher)(nil) + _ CELLibraryProducer = (*MatchVarsRE)(nil) _ json.Marshaler = (*MatchNot)(nil) _ json.Unmarshaler = (*MatchNot)(nil) diff --git a/modules/caddyhttp/templates/templates.go b/modules/caddyhttp/templates/templates.go index 0eba4870f99..eb648865983 100644 --- a/modules/caddyhttp/templates/templates.go +++ b/modules/caddyhttp/templates/templates.go @@ -81,6 +81,12 @@ func init() { // {{placeholder "http.error.status_code"}} // ``` // +// As a shortcut, `ph` is an alias for `placeholder`. +// +// ``` +// {{ph "http.request.method"}} +// ``` +// // ##### `.Host` // // Returns the hostname portion (no port) of the Host header of the HTTP request. diff --git a/modules/caddyhttp/templates/tplcontext.go b/modules/caddyhttp/templates/tplcontext.go index 2324586be1e..1b1020f1b2c 100644 --- a/modules/caddyhttp/templates/tplcontext.go +++ b/modules/caddyhttp/templates/tplcontext.go @@ -88,6 +88,7 @@ func (c *TemplateContext) NewTemplate(tplName string) *template.Template { "fileStat": c.funcFileStat, "env": c.funcEnv, "placeholder": c.funcPlaceholder, + "ph": c.funcPlaceholder, // shortcut "fileExists": c.funcFileExists, "httpError": c.funcHTTPError, "humanize": c.funcHumanize, diff --git a/modules/caddyhttp/vars.go b/modules/caddyhttp/vars.go index 9e86dd7164a..77e06e3cbed 100644 --- a/modules/caddyhttp/vars.go +++ b/modules/caddyhttp/vars.go @@ -18,8 +18,12 @@ import ( "context" "fmt" "net/http" + "reflect" "strings" + "github.com/google/cel-go/cel" + "github.com/google/cel-go/common/types/ref" + "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" ) @@ -203,6 +207,28 @@ func (m VarsMatcher) Match(r *http.Request) bool { return false } +// CELLibrary produces options that expose this matcher for use in CEL +// expression matchers. +// +// Example: +// +// expression vars({'{magic_number}': ['3', '5']}) +// expression vars({'{foo}': 'single_value'}) +func (VarsMatcher) CELLibrary(_ caddy.Context) (cel.Library, error) { + return CELMatcherImpl( + "vars", + "vars_matcher_request_map", + []*cel.Type{CELTypeJSON}, + func(data ref.Val) (RequestMatcher, error) { + mapStrListStr, err := CELValueToMapStrList(data) + if err != nil { + return nil, err + } + return VarsMatcher(mapStrListStr), nil + }, + ) +} + // MatchVarsRE matches the value of the context variables by a given regular expression. // // Upon a match, it adds placeholders to the request: `{http.regexp.name.capture_group}` @@ -302,6 +328,69 @@ func (m MatchVarsRE) Match(r *http.Request) bool { return false } +// CELLibrary produces options that expose this matcher for use in CEL +// expression matchers. +// +// Example: +// +// expression vars_regexp('foo', '{magic_number}', '[0-9]+') +// expression vars_regexp('{magic_number}', '[0-9]+') +func (MatchVarsRE) CELLibrary(ctx caddy.Context) (cel.Library, error) { + unnamedPattern, err := CELMatcherImpl( + "vars_regexp", + "vars_regexp_request_string_string", + []*cel.Type{cel.StringType, cel.StringType}, + func(data ref.Val) (RequestMatcher, error) { + refStringList := reflect.TypeOf([]string{}) + params, err := data.ConvertToNative(refStringList) + if err != nil { + return nil, err + } + strParams := params.([]string) + matcher := MatchVarsRE{} + matcher[strParams[0]] = &MatchRegexp{ + Pattern: strParams[1], + Name: ctx.Value(MatcherNameCtxKey).(string), + } + err = matcher.Provision(ctx) + return matcher, err + }, + ) + if err != nil { + return nil, err + } + namedPattern, err := CELMatcherImpl( + "vars_regexp", + "vars_regexp_request_string_string_string", + []*cel.Type{cel.StringType, cel.StringType, cel.StringType}, + func(data ref.Val) (RequestMatcher, error) { + refStringList := reflect.TypeOf([]string{}) + params, err := data.ConvertToNative(refStringList) + if err != nil { + return nil, err + } + strParams := params.([]string) + name := strParams[0] + if name == "" { + name = ctx.Value(MatcherNameCtxKey).(string) + } + matcher := MatchVarsRE{} + matcher[strParams[1]] = &MatchRegexp{ + Pattern: strParams[2], + Name: name, + } + err = matcher.Provision(ctx) + return matcher, err + }, + ) + if err != nil { + return nil, err + } + envOpts := append(unnamedPattern.CompileOptions(), namedPattern.CompileOptions()...) + prgOpts := append(unnamedPattern.ProgramOptions(), namedPattern.ProgramOptions()...) + return NewMatcherCELLibrary(envOpts, prgOpts), nil +} + // Validate validates m's regular expressions. func (m MatchVarsRE) Validate() error { for _, rm := range m { From 16724842d9b9096b800326d0b7667a4361552552 Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Wed, 2 Oct 2024 09:31:58 -0400 Subject: [PATCH 18/39] caddyhttp: Implement `auto_https prefer_wildcard` option (#6146) * Allow specifying multiple `auto_https` options * Implement `auto_https prefer_wildcard` option * Adapt tests, add mock DNS module for config testing * Rebase fix --- caddyconfig/httpcaddyfile/httptype.go | 56 ++++++- caddyconfig/httpcaddyfile/options.go | 21 ++- caddyconfig/httpcaddyfile/tlsapp.go | 41 ++++- .../auto_https_prefer_wildcard.caddyfiletest | 106 ++++++++++++ .../wildcard_pattern.caddyfiletest | 157 ++++++++++++++++++ caddytest/integration/mockdns_test.go | 61 +++++++ modules/caddyhttp/autohttps.go | 27 +++ 7 files changed, 449 insertions(+), 20 deletions(-) create mode 100644 caddytest/integration/caddyfile_adapt/auto_https_prefer_wildcard.caddyfiletest create mode 100644 caddytest/integration/caddyfile_adapt/wildcard_pattern.caddyfiletest create mode 100644 caddytest/integration/mockdns_test.go diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go index 6745969eefc..4dacd9058d0 100644 --- a/caddyconfig/httpcaddyfile/httptype.go +++ b/caddyconfig/httpcaddyfile/httptype.go @@ -534,8 +534,8 @@ func (st *ServerType) serversFromPairings( if hsp, ok := options["https_port"].(int); ok { httpsPort = strconv.Itoa(hsp) } - autoHTTPS := "on" - if ah, ok := options["auto_https"].(string); ok { + autoHTTPS := []string{} + if ah, ok := options["auto_https"].([]string); ok { autoHTTPS = ah } @@ -594,17 +594,37 @@ func (st *ServerType) serversFromPairings( } // handle the auto_https global option - if autoHTTPS != "on" { - srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig) - switch autoHTTPS { + for _, val := range autoHTTPS { + switch val { case "off": + if srv.AutoHTTPS == nil { + srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig) + } srv.AutoHTTPS.Disabled = true + case "disable_redirects": + if srv.AutoHTTPS == nil { + srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig) + } srv.AutoHTTPS.DisableRedir = true + case "disable_certs": + if srv.AutoHTTPS == nil { + srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig) + } srv.AutoHTTPS.DisableCerts = true + case "ignore_loaded_certs": + if srv.AutoHTTPS == nil { + srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig) + } srv.AutoHTTPS.IgnoreLoadedCerts = true + + case "prefer_wildcard": + if srv.AutoHTTPS == nil { + srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig) + } + srv.AutoHTTPS.PreferWildcard = true } } @@ -673,7 +693,7 @@ func (st *ServerType) serversFromPairings( }) var hasCatchAllTLSConnPolicy, addressQualifiesForTLS bool - autoHTTPSWillAddConnPolicy := autoHTTPS != "off" + autoHTTPSWillAddConnPolicy := srv.AutoHTTPS == nil || !srv.AutoHTTPS.Disabled // if needed, the ServerLogConfig is initialized beforehand so // that all server blocks can populate it with data, even when not @@ -757,6 +777,13 @@ func (st *ServerType) serversFromPairings( } } + wildcardHosts := []string{} + for _, addr := range sblock.parsedKeys { + if strings.HasPrefix(addr.Host, "*.") { + wildcardHosts = append(wildcardHosts, addr.Host[2:]) + } + } + for _, addr := range sblock.parsedKeys { // if server only uses HTTP port, auto-HTTPS will not apply if listenersUseAnyPortOtherThan(srv.Listen, httpPort) { @@ -772,6 +799,18 @@ func (st *ServerType) serversFromPairings( } } + // If prefer wildcard is enabled, then we add hosts that are + // already covered by the wildcard to the skip list + if srv.AutoHTTPS != nil && srv.AutoHTTPS.PreferWildcard && addr.Scheme == "https" { + baseDomain := addr.Host + if idx := strings.Index(baseDomain, "."); idx != -1 { + baseDomain = baseDomain[idx+1:] + } + if !strings.HasPrefix(addr.Host, "*.") && slices.Contains(wildcardHosts, baseDomain) { + srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, addr.Host) + } + } + // If TLS is specified as directive, it will also result in 1 or more connection policy being created // Thus, catch-all address with non-standard port, e.g. :8443, can have TLS enabled without // specifying prefix "https://" @@ -919,7 +958,10 @@ func (st *ServerType) serversFromPairings( if addressQualifiesForTLS && !hasCatchAllTLSConnPolicy && (len(srv.TLSConnPolicies) > 0 || !autoHTTPSWillAddConnPolicy || defaultSNI != "" || fallbackSNI != "") { - srv.TLSConnPolicies = append(srv.TLSConnPolicies, &caddytls.ConnectionPolicy{DefaultSNI: defaultSNI, FallbackSNI: fallbackSNI}) + srv.TLSConnPolicies = append(srv.TLSConnPolicies, &caddytls.ConnectionPolicy{ + DefaultSNI: defaultSNI, + FallbackSNI: fallbackSNI, + }) } // tidy things up a bit diff --git a/caddyconfig/httpcaddyfile/options.go b/caddyconfig/httpcaddyfile/options.go index c14208b614f..abbe8f4185e 100644 --- a/caddyconfig/httpcaddyfile/options.go +++ b/caddyconfig/httpcaddyfile/options.go @@ -452,15 +452,22 @@ func parseOptPersistConfig(d *caddyfile.Dispenser, _ any) (any, error) { func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) { d.Next() // consume option name - if !d.Next() { - return "", d.ArgErr() - } - val := d.Val() - if d.Next() { + val := d.RemainingArgs() + if len(val) == 0 { return "", d.ArgErr() } - if val != "off" && val != "disable_redirects" && val != "disable_certs" && val != "ignore_loaded_certs" { - return "", d.Errf("auto_https must be one of 'off', 'disable_redirects', 'disable_certs', or 'ignore_loaded_certs'") + for _, v := range val { + switch v { + case "off": + case "disable_redirects": + case "disable_certs": + case "ignore_loaded_certs": + case "prefer_wildcard": + break + + default: + return "", d.Errf("auto_https must be one of 'off', 'disable_redirects', 'disable_certs', 'ignore_loaded_certs', or 'prefer_wildcard'") + } } return val, nil } diff --git a/caddyconfig/httpcaddyfile/tlsapp.go b/caddyconfig/httpcaddyfile/tlsapp.go index c6ff81b2bf2..ed708524d68 100644 --- a/caddyconfig/httpcaddyfile/tlsapp.go +++ b/caddyconfig/httpcaddyfile/tlsapp.go @@ -45,8 +45,8 @@ func (st ServerType) buildTLSApp( if hp, ok := options["http_port"].(int); ok { httpPort = strconv.Itoa(hp) } - autoHTTPS := "on" - if ah, ok := options["auto_https"].(string); ok { + autoHTTPS := []string{} + if ah, ok := options["auto_https"].([]string); ok { autoHTTPS = ah } @@ -54,13 +54,14 @@ func (st ServerType) buildTLSApp( // key, so that they don't get forgotten/omitted by auto-HTTPS // (since they won't appear in route matchers) httpsHostsSharedWithHostlessKey := make(map[string]struct{}) - if autoHTTPS != "off" { + if !slices.Contains(autoHTTPS, "off") { for _, pair := range pairings { for _, sb := range pair.serverBlocks { for _, addr := range sb.parsedKeys { if addr.Host != "" { continue } + // this server block has a hostless key, now // go through and add all the hosts to the set for _, otherAddr := range sb.parsedKeys { @@ -350,7 +351,7 @@ func (st ServerType) buildTLSApp( internalAP := &caddytls.AutomationPolicy{ IssuersRaw: []json.RawMessage{json.RawMessage(`{"module":"internal"}`)}, } - if autoHTTPS != "off" && autoHTTPS != "disable_certs" { + if !slices.Contains(autoHTTPS, "off") && !slices.Contains(autoHTTPS, "disable_certs") { for h := range httpsHostsSharedWithHostlessKey { al = append(al, h) if !certmagic.SubjectQualifiesForPublicCert(h) { @@ -417,7 +418,10 @@ func (st ServerType) buildTLSApp( } // consolidate automation policies that are the exact same - tlsApp.Automation.Policies = consolidateAutomationPolicies(tlsApp.Automation.Policies) + tlsApp.Automation.Policies = consolidateAutomationPolicies( + tlsApp.Automation.Policies, + slices.Contains(autoHTTPS, "prefer_wildcard"), + ) // ensure automation policies don't overlap subjects (this should be // an error at provision-time as well, but catch it in the adapt phase @@ -563,7 +567,7 @@ func newBaseAutomationPolicy( // consolidateAutomationPolicies combines automation policies that are the same, // for a cleaner overall output. -func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls.AutomationPolicy { +func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy, preferWildcard bool) []*caddytls.AutomationPolicy { // sort from most specific to least specific; we depend on this ordering sort.SliceStable(aps, func(i, j int) bool { if automationPolicyIsSubset(aps[i], aps[j]) { @@ -648,6 +652,31 @@ outer: j-- } } + + if preferWildcard { + // remove subjects from i if they're covered by a wildcard in j + iSubjs := aps[i].SubjectsRaw + for iSubj := 0; iSubj < len(iSubjs); iSubj++ { + for jSubj := range aps[j].SubjectsRaw { + if !strings.HasPrefix(aps[j].SubjectsRaw[jSubj], "*.") { + continue + } + if certmagic.MatchWildcard(aps[i].SubjectsRaw[iSubj], aps[j].SubjectsRaw[jSubj]) { + iSubjs = slices.Delete(iSubjs, iSubj, iSubj+1) + iSubj-- + break + } + } + } + aps[i].SubjectsRaw = iSubjs + + // remove i if it has no subjects left + if len(aps[i].SubjectsRaw) == 0 { + aps = slices.Delete(aps, i, i+1) + i-- + continue outer + } + } } } diff --git a/caddytest/integration/caddyfile_adapt/auto_https_prefer_wildcard.caddyfiletest b/caddytest/integration/caddyfile_adapt/auto_https_prefer_wildcard.caddyfiletest new file mode 100644 index 00000000000..8880d71aede --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/auto_https_prefer_wildcard.caddyfiletest @@ -0,0 +1,106 @@ +{ + auto_https prefer_wildcard +} + +*.example.com { + tls { + dns mock + } + respond "fallback" +} + +foo.example.com { + respond "foo" +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "foo.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "foo", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "*.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "fallback", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "automatic_https": { + "prefer_wildcard": true + } + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "*.example.com" + ], + "issuers": [ + { + "challenges": { + "dns": { + "provider": { + "name": "mock" + } + } + }, + "module": "acme" + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/wildcard_pattern.caddyfiletest b/caddytest/integration/caddyfile_adapt/wildcard_pattern.caddyfiletest new file mode 100644 index 00000000000..1a9ccea741f --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/wildcard_pattern.caddyfiletest @@ -0,0 +1,157 @@ +*.example.com { + tls foo@example.com { + dns mock + } + + @foo host foo.example.com + handle @foo { + respond "Foo!" + } + + @bar host bar.example.com + handle @bar { + respond "Bar!" + } + + # Fallback for otherwise unhandled domains + handle { + abort + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "*.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "group": "group3", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Foo!", + "handler": "static_response" + } + ] + } + ] + } + ], + "match": [ + { + "host": [ + "foo.example.com" + ] + } + ] + }, + { + "group": "group3", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Bar!", + "handler": "static_response" + } + ] + } + ] + } + ], + "match": [ + { + "host": [ + "bar.example.com" + ] + } + ] + }, + { + "group": "group3", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "abort": true, + "handler": "static_response" + } + ] + } + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "*.example.com" + ], + "issuers": [ + { + "challenges": { + "dns": { + "provider": { + "name": "mock" + } + } + }, + "email": "foo@example.com", + "module": "acme" + }, + { + "ca": "https://acme.zerossl.com/v2/DV90", + "challenges": { + "dns": { + "provider": { + "name": "mock" + } + } + }, + "email": "foo@example.com", + "module": "acme" + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/mockdns_test.go b/caddytest/integration/mockdns_test.go new file mode 100644 index 00000000000..1b2efb65328 --- /dev/null +++ b/caddytest/integration/mockdns_test.go @@ -0,0 +1,61 @@ +package integration + +import ( + "context" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/certmagic" + "github.com/libdns/libdns" +) + +func init() { + caddy.RegisterModule(MockDNSProvider{}) +} + +// MockDNSProvider is a mock DNS provider, for testing config with DNS modules. +type MockDNSProvider struct{} + +// CaddyModule returns the Caddy module information. +func (MockDNSProvider) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "dns.providers.mock", + New: func() caddy.Module { return new(MockDNSProvider) }, + } +} + +// Provision sets up the module. +func (MockDNSProvider) Provision(ctx caddy.Context) error { + return nil +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. +func (MockDNSProvider) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + return nil +} + +// AppendsRecords appends DNS records to the zone. +func (MockDNSProvider) AppendRecords(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) { + return nil, nil +} + +// DeleteRecords deletes DNS records from the zone. +func (MockDNSProvider) DeleteRecords(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) { + return nil, nil +} + +// GetRecords gets DNS records from the zone. +func (MockDNSProvider) GetRecords(ctx context.Context, zone string) ([]libdns.Record, error) { + return nil, nil +} + +// SetRecords sets DNS records in the zone. +func (MockDNSProvider) SetRecords(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) { + return nil, nil +} + +// Interface guard +var _ caddyfile.Unmarshaler = (*MockDNSProvider)(nil) +var _ certmagic.DNSProvider = (*MockDNSProvider)(nil) +var _ caddy.Provisioner = (*MockDNSProvider)(nil) +var _ caddy.Module = (*MockDNSProvider)(nil) diff --git a/modules/caddyhttp/autohttps.go b/modules/caddyhttp/autohttps.go index 79fdfd6ec1f..ccb610327ac 100644 --- a/modules/caddyhttp/autohttps.go +++ b/modules/caddyhttp/autohttps.go @@ -65,6 +65,12 @@ type AutoHTTPSConfig struct { // enabled. To force automated certificate management // regardless of loaded certificates, set this to true. IgnoreLoadedCerts bool `json:"ignore_loaded_certificates,omitempty"` + + // If true, automatic HTTPS will prefer wildcard names + // and ignore non-wildcard names if both are available. + // This allows for writing a config with top-level host + // matchers without having those names produce certificates. + PreferWildcard bool `json:"prefer_wildcard,omitempty"` } // automaticHTTPSPhase1 provisions all route matchers, determines @@ -157,6 +163,27 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er } } + if srv.AutoHTTPS.PreferWildcard { + wildcards := make(map[string]struct{}) + for d := range serverDomainSet { + if strings.HasPrefix(d, "*.") { + wildcards[d[2:]] = struct{}{} + } + } + for d := range serverDomainSet { + if strings.HasPrefix(d, "*.") { + continue + } + base := d + if idx := strings.Index(d, "."); idx != -1 { + base = d[idx+1:] + } + if _, ok := wildcards[base]; ok { + delete(serverDomainSet, d) + } + } + } + // nothing more to do here if there are no domains that qualify for // automatic HTTPS and there are no explicit TLS connection policies: // if there is at least one domain but no TLS conn policy (F&&T), we'll From 41f5dd56e1b93ec815daa98dd1f1caa7f2087312 Mon Sep 17 00:00:00 2001 From: Mohammed Al Sahaf Date: Wed, 2 Oct 2024 17:23:26 +0300 Subject: [PATCH 19/39] metrics: scope metrics to active config, add optional per-host metrics (#6531) * Add per host config * Pass host label when option is enabled * Test per host enabled * metrics: scope metrics per loaded config * doc and linter Signed-off-by: Mohammed Al Sahaf * inject the custom registry into the admin handler Co-Authored-By: Dave Henderson * remove `TODO` comment * fixes Signed-off-by: Mohammed Al Sahaf * refactor to delay metrics admin handler provision Signed-off-by: Mohammed Al Sahaf --------- Signed-off-by: Mohammed Al Sahaf Co-authored-by: Hussam Almarzooq Co-authored-by: Dave Henderson --- admin.go | 11 +- caddy.go | 9 +- caddyconfig/httpcaddyfile/serveroptions.go | 12 +- .../metrics_perhost.caddyfiletest | 37 ++++ context.go | 22 +- metrics.go | 23 +- modules/caddyhttp/app.go | 4 + modules/caddyhttp/metrics.go | 68 +++--- modules/caddyhttp/metrics_test.go | 196 +++++++++++++++++- modules/caddyhttp/routes.go | 4 +- modules/metrics/adminmetrics.go | 32 ++- modules/metrics/metrics.go | 13 +- 12 files changed, 365 insertions(+), 66 deletions(-) create mode 100644 caddytest/integration/caddyfile_adapt/metrics_perhost.caddyfiletest diff --git a/admin.go b/admin.go index 24c583235be..0d5ecc92c21 100644 --- a/admin.go +++ b/admin.go @@ -214,7 +214,7 @@ type AdminPermissions struct { // newAdminHandler reads admin's config and returns an http.Handler suitable // for use in an admin endpoint server, which will be listening on listenAddr. -func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool) adminHandler { +func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool, ctx Context) adminHandler { muxWrap := adminHandler{mux: http.NewServeMux()} // secure the local or remote endpoint respectively @@ -270,7 +270,6 @@ func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool) admi // register third-party module endpoints for _, m := range GetModules("admin.api") { router := m.New().(AdminRouter) - handlerLabel := m.ID.Name() for _, route := range router.Routes() { addRoute(route.Pattern, handlerLabel, route.Handler) } @@ -382,7 +381,9 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL { // for the admin endpoint exists in cfg, a default one is used, so // that there is always an admin server (unless it is explicitly // configured to be disabled). -func replaceLocalAdminServer(cfg *Config) error { +// Critically note that some elements and functionality of the context +// may not be ready, e.g. storage. Tread carefully. +func replaceLocalAdminServer(cfg *Config, ctx Context) error { // always* be sure to close down the old admin endpoint // as gracefully as possible, even if the new one is // disabled -- careful to use reference to the current @@ -424,7 +425,7 @@ func replaceLocalAdminServer(cfg *Config) error { return err } - handler := cfg.Admin.newAdminHandler(addr, false) + handler := cfg.Admin.newAdminHandler(addr, false, ctx) ln, err := addr.Listen(context.TODO(), 0, net.ListenConfig{}) if err != nil { @@ -545,7 +546,7 @@ func replaceRemoteAdminServer(ctx Context, cfg *Config) error { // make the HTTP handler but disable Host/Origin enforcement // because we are using TLS authentication instead - handler := cfg.Admin.newAdminHandler(addr, true) + handler := cfg.Admin.newAdminHandler(addr, true, ctx) // create client certificate pool for TLS mutual auth, and extract public keys // so that we can enforce access controls at the application layer diff --git a/caddy.go b/caddy.go index 7dd989c9e1e..b3e8889fa44 100644 --- a/caddy.go +++ b/caddy.go @@ -399,6 +399,7 @@ func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error { func run(newCfg *Config, start bool) (Context, error) { ctx, err := provisionContext(newCfg, start) if err != nil { + globalMetrics.configSuccess.Set(0) return ctx, err } @@ -410,6 +411,7 @@ func run(newCfg *Config, start bool) (Context, error) { // some of the other apps at runtime err = ctx.cfg.Admin.provisionAdminRouters(ctx) if err != nil { + globalMetrics.configSuccess.Set(0) return ctx, err } @@ -435,9 +437,11 @@ func run(newCfg *Config, start bool) (Context, error) { return nil }() if err != nil { + globalMetrics.configSuccess.Set(0) return ctx, err } - + globalMetrics.configSuccess.Set(1) + globalMetrics.configSuccessTime.SetToCurrentTime() // now that the user's config is running, finish setting up anything else, // such as remote admin endpoint, config loader, etc. return ctx, finishSettingUp(ctx, ctx.cfg) @@ -471,6 +475,7 @@ func provisionContext(newCfg *Config, replaceAdminServer bool) (Context, error) ctx, cancel := NewContext(Context{Context: context.Background(), cfg: newCfg}) defer func() { if err != nil { + globalMetrics.configSuccess.Set(0) // if there were any errors during startup, // we should cancel the new context we created // since the associated config won't be used; @@ -497,7 +502,7 @@ func provisionContext(newCfg *Config, replaceAdminServer bool) (Context, error) // start the admin endpoint (and stop any prior one) if replaceAdminServer { - err = replaceLocalAdminServer(newCfg) + err = replaceLocalAdminServer(newCfg, ctx) if err != nil { return ctx, fmt.Errorf("starting caddy administration endpoint: %v", err) } diff --git a/caddyconfig/httpcaddyfile/serveroptions.go b/caddyconfig/httpcaddyfile/serveroptions.go index 7087cdba59c..b05af47f547 100644 --- a/caddyconfig/httpcaddyfile/serveroptions.go +++ b/caddyconfig/httpcaddyfile/serveroptions.go @@ -240,13 +240,13 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) { } case "metrics": - if d.NextArg() { - return nil, d.ArgErr() - } - if nesting := d.Nesting(); d.NextBlock(nesting) { - return nil, d.ArgErr() - } serverOpts.Metrics = new(caddyhttp.Metrics) + for nesting := d.Nesting(); d.NextBlock(nesting); { + switch d.Val() { + case "per_host": + serverOpts.Metrics.PerHost = true + } + } case "trace": if d.NextArg() { diff --git a/caddytest/integration/caddyfile_adapt/metrics_perhost.caddyfiletest b/caddytest/integration/caddyfile_adapt/metrics_perhost.caddyfiletest new file mode 100644 index 00000000000..49921551573 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/metrics_perhost.caddyfiletest @@ -0,0 +1,37 @@ +{ + servers :80 { + metrics { + per_host + } + } +} +:80 { + respond "Hello" +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "handle": [ + { + "body": "Hello", + "handler": "static_response" + } + ] + } + ], + "metrics": { + "per_host": true + } + } + } + } + } +} diff --git a/context.go b/context.go index 5b8c1070333..17a8aa4f8c3 100644 --- a/context.go +++ b/context.go @@ -23,6 +23,8 @@ import ( "reflect" "github.com/caddyserver/certmagic" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/collectors" "go.uber.org/zap" "go.uber.org/zap/exp/zapslog" @@ -47,6 +49,7 @@ type Context struct { ancestry []Module cleanupFuncs []func() // invoked at every config unload exitFuncs []func(context.Context) // invoked at config unload ONLY IF the process is exiting (EXPERIMENTAL) + metricsRegistry *prometheus.Registry } // NewContext provides a new context derived from the given @@ -58,7 +61,7 @@ type Context struct { // modules which are loaded will be properly unloaded. // See standard library context package's documentation. func NewContext(ctx Context) (Context, context.CancelFunc) { - newCtx := Context{moduleInstances: make(map[string][]Module), cfg: ctx.cfg} + newCtx := Context{moduleInstances: make(map[string][]Module), cfg: ctx.cfg, metricsRegistry: prometheus.NewPedanticRegistry()} c, cancel := context.WithCancel(ctx.Context) wrappedCancel := func() { cancel() @@ -79,6 +82,7 @@ func NewContext(ctx Context) (Context, context.CancelFunc) { } } newCtx.Context = c + newCtx.initMetrics() return newCtx, wrappedCancel } @@ -97,6 +101,22 @@ func (ctx *Context) Filesystems() FileSystems { return ctx.cfg.filesystems } +// Returns the active metrics registry for the context +// EXPERIMENTAL: This API is subject to change. +func (ctx *Context) GetMetricsRegistry() *prometheus.Registry { + return ctx.metricsRegistry +} + +func (ctx *Context) initMetrics() { + ctx.metricsRegistry.MustRegister( + collectors.NewBuildInfoCollector(), + adminMetrics.requestCount, + adminMetrics.requestErrors, + globalMetrics.configSuccess, + globalMetrics.configSuccessTime, + ) +} + // OnExit executes f when the process exits gracefully. // The function is only executed if the process is gracefully // shut down while this context is active. diff --git a/metrics.go b/metrics.go index 0f8ea03cbd6..0ee3853eb85 100644 --- a/metrics.go +++ b/metrics.go @@ -4,30 +4,33 @@ import ( "net/http" "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/collectors" - "github.com/prometheus/client_golang/prometheus/promauto" "github.com/caddyserver/caddy/v2/internal/metrics" ) // define and register the metrics used in this package. func init() { - prometheus.MustRegister(collectors.NewBuildInfoCollector()) - const ns, sub = "caddy", "admin" - - adminMetrics.requestCount = promauto.NewCounterVec(prometheus.CounterOpts{ + adminMetrics.requestCount = prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: ns, Subsystem: sub, Name: "http_requests_total", Help: "Counter of requests made to the Admin API's HTTP endpoints.", }, []string{"handler", "path", "code", "method"}) - adminMetrics.requestErrors = promauto.NewCounterVec(prometheus.CounterOpts{ + adminMetrics.requestErrors = prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: ns, Subsystem: sub, Name: "http_request_errors_total", Help: "Number of requests resulting in middleware errors.", }, []string{"handler", "path", "method"}) + globalMetrics.configSuccess = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "caddy_config_last_reload_successful", + Help: "Whether the last configuration reload attempt was successful.", + }) + globalMetrics.configSuccessTime = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "caddy_config_last_reload_success_timestamp_seconds", + Help: "Timestamp of the last successful configuration reload.", + }) } // adminMetrics is a collection of metrics that can be tracked for the admin API. @@ -36,6 +39,12 @@ var adminMetrics = struct { requestErrors *prometheus.CounterVec }{} +// globalMetrics is a collection of metrics that can be tracked for Caddy global state +var globalMetrics = struct { + configSuccess prometheus.Gauge + configSuccessTime prometheus.Gauge +}{} + // Similar to promhttp.InstrumentHandlerCounter, but upper-cases method names // instead of lower-casing them. // diff --git a/modules/caddyhttp/app.go b/modules/caddyhttp/app.go index 7a5c1062342..7dc2bee72fa 100644 --- a/modules/caddyhttp/app.go +++ b/modules/caddyhttp/app.go @@ -347,6 +347,10 @@ func (app *App) Provision(ctx caddy.Context) error { // route handler so that important security checks are done, etc. primaryRoute := emptyHandler if srv.Routes != nil { + if srv.Metrics != nil { + srv.Metrics.init = sync.Once{} + srv.Metrics.httpMetrics = &httpMetrics{} + } err := srv.Routes.ProvisionHandlers(ctx, srv.Metrics) if err != nil { return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err) diff --git a/modules/caddyhttp/metrics.go b/modules/caddyhttp/metrics.go index 11138921811..947721429f4 100644 --- a/modules/caddyhttp/metrics.go +++ b/modules/caddyhttp/metrics.go @@ -10,15 +10,23 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/internal/metrics" ) // Metrics configures metrics observations. // EXPERIMENTAL and subject to change or removal. -type Metrics struct{} +type Metrics struct { + // Enable per-host metrics. Enabling this option may + // incur high-memory consumption, depending on the number of hosts + // managed by Caddy. + PerHost bool `json:"per_host,omitempty"` + + init sync.Once + httpMetrics *httpMetrics `json:"-"` +} -var httpMetrics = struct { - init sync.Once +type httpMetrics struct { requestInFlight *prometheus.GaugeVec requestCount *prometheus.CounterVec requestErrors *prometheus.CounterVec @@ -26,27 +34,28 @@ var httpMetrics = struct { requestSize *prometheus.HistogramVec responseSize *prometheus.HistogramVec responseDuration *prometheus.HistogramVec -}{ - init: sync.Once{}, } -func initHTTPMetrics() { +func initHTTPMetrics(ctx caddy.Context, metrics *Metrics) { const ns, sub = "caddy", "http" - + registry := ctx.GetMetricsRegistry() basicLabels := []string{"server", "handler"} - httpMetrics.requestInFlight = promauto.NewGaugeVec(prometheus.GaugeOpts{ + if metrics.PerHost { + basicLabels = append(basicLabels, "host") + } + metrics.httpMetrics.requestInFlight = promauto.With(registry).NewGaugeVec(prometheus.GaugeOpts{ Namespace: ns, Subsystem: sub, Name: "requests_in_flight", Help: "Number of requests currently handled by this server.", }, basicLabels) - httpMetrics.requestErrors = promauto.NewCounterVec(prometheus.CounterOpts{ + metrics.httpMetrics.requestErrors = promauto.With(registry).NewCounterVec(prometheus.CounterOpts{ Namespace: ns, Subsystem: sub, Name: "request_errors_total", Help: "Number of requests resulting in middleware errors.", }, basicLabels) - httpMetrics.requestCount = promauto.NewCounterVec(prometheus.CounterOpts{ + metrics.httpMetrics.requestCount = promauto.With(registry).NewCounterVec(prometheus.CounterOpts{ Namespace: ns, Subsystem: sub, Name: "requests_total", @@ -58,28 +67,31 @@ func initHTTPMetrics() { sizeBuckets := prometheus.ExponentialBuckets(256, 4, 8) httpLabels := []string{"server", "handler", "code", "method"} - httpMetrics.requestDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{ + if metrics.PerHost { + httpLabels = append(httpLabels, "host") + } + metrics.httpMetrics.requestDuration = promauto.With(registry).NewHistogramVec(prometheus.HistogramOpts{ Namespace: ns, Subsystem: sub, Name: "request_duration_seconds", Help: "Histogram of round-trip request durations.", Buckets: durationBuckets, }, httpLabels) - httpMetrics.requestSize = promauto.NewHistogramVec(prometheus.HistogramOpts{ + metrics.httpMetrics.requestSize = promauto.With(registry).NewHistogramVec(prometheus.HistogramOpts{ Namespace: ns, Subsystem: sub, Name: "request_size_bytes", Help: "Total size of the request. Includes body", Buckets: sizeBuckets, }, httpLabels) - httpMetrics.responseSize = promauto.NewHistogramVec(prometheus.HistogramOpts{ + metrics.httpMetrics.responseSize = promauto.With(registry).NewHistogramVec(prometheus.HistogramOpts{ Namespace: ns, Subsystem: sub, Name: "response_size_bytes", Help: "Size of the returned response.", Buckets: sizeBuckets, }, httpLabels) - httpMetrics.responseDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{ + metrics.httpMetrics.responseDuration = promauto.With(registry).NewHistogramVec(prometheus.HistogramOpts{ Namespace: ns, Subsystem: sub, Name: "response_duration_seconds", @@ -101,14 +113,15 @@ func serverNameFromContext(ctx context.Context) string { type metricsInstrumentedHandler struct { handler string mh MiddlewareHandler + metrics *Metrics } -func newMetricsInstrumentedHandler(handler string, mh MiddlewareHandler) *metricsInstrumentedHandler { - httpMetrics.init.Do(func() { - initHTTPMetrics() +func newMetricsInstrumentedHandler(ctx caddy.Context, handler string, mh MiddlewareHandler, metrics *Metrics) *metricsInstrumentedHandler { + metrics.init.Do(func() { + initHTTPMetrics(ctx, metrics) }) - return &metricsInstrumentedHandler{handler, mh} + return &metricsInstrumentedHandler{handler, mh, metrics} } func (h *metricsInstrumentedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error { @@ -119,7 +132,12 @@ func (h *metricsInstrumentedHandler) ServeHTTP(w http.ResponseWriter, r *http.Re // of a panic statusLabels := prometheus.Labels{"server": server, "handler": h.handler, "method": method, "code": ""} - inFlight := httpMetrics.requestInFlight.With(labels) + if h.metrics.PerHost { + labels["host"] = r.Host + statusLabels["host"] = r.Host + } + + inFlight := h.metrics.httpMetrics.requestInFlight.With(labels) inFlight.Inc() defer inFlight.Dec() @@ -131,13 +149,13 @@ func (h *metricsInstrumentedHandler) ServeHTTP(w http.ResponseWriter, r *http.Re writeHeaderRecorder := ShouldBufferFunc(func(status int, header http.Header) bool { statusLabels["code"] = metrics.SanitizeCode(status) ttfb := time.Since(start).Seconds() - httpMetrics.responseDuration.With(statusLabels).Observe(ttfb) + h.metrics.httpMetrics.responseDuration.With(statusLabels).Observe(ttfb) return false }) wrec := NewResponseRecorder(w, nil, writeHeaderRecorder) err := h.mh.ServeHTTP(wrec, r, next) dur := time.Since(start).Seconds() - httpMetrics.requestCount.With(labels).Inc() + h.metrics.httpMetrics.requestCount.With(labels).Inc() observeRequest := func(status int) { // If the code hasn't been set yet, and we didn't encounter an error, we're @@ -148,9 +166,9 @@ func (h *metricsInstrumentedHandler) ServeHTTP(w http.ResponseWriter, r *http.Re statusLabels["code"] = metrics.SanitizeCode(status) } - httpMetrics.requestDuration.With(statusLabels).Observe(dur) - httpMetrics.requestSize.With(statusLabels).Observe(float64(computeApproximateRequestSize(r))) - httpMetrics.responseSize.With(statusLabels).Observe(float64(wrec.Size())) + h.metrics.httpMetrics.requestDuration.With(statusLabels).Observe(dur) + h.metrics.httpMetrics.requestSize.With(statusLabels).Observe(float64(computeApproximateRequestSize(r))) + h.metrics.httpMetrics.responseSize.With(statusLabels).Observe(float64(wrec.Size())) } if err != nil { @@ -159,7 +177,7 @@ func (h *metricsInstrumentedHandler) ServeHTTP(w http.ResponseWriter, r *http.Re observeRequest(handlerErr.StatusCode) } - httpMetrics.requestErrors.With(labels).Inc() + h.metrics.httpMetrics.requestErrors.With(labels).Inc() return err } diff --git a/modules/caddyhttp/metrics_test.go b/modules/caddyhttp/metrics_test.go index 8f88549d5a2..4a0519b8769 100644 --- a/modules/caddyhttp/metrics_test.go +++ b/modules/caddyhttp/metrics_test.go @@ -6,9 +6,10 @@ import ( "net/http" "net/http/httptest" "strings" + "sync" "testing" - "github.com/prometheus/client_golang/prometheus" + "github.com/caddyserver/caddy/v2" "github.com/prometheus/client_golang/prometheus/testutil" ) @@ -27,10 +28,15 @@ func TestServerNameFromContext(t *testing.T) { } func TestMetricsInstrumentedHandler(t *testing.T) { + ctx, _ := caddy.NewContext(caddy.Context{Context: context.Background()}) + metrics := &Metrics{ + init: sync.Once{}, + httpMetrics: &httpMetrics{}, + } handlerErr := errors.New("oh noes") response := []byte("hello world!") h := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { - if actual := testutil.ToFloat64(httpMetrics.requestInFlight); actual != 1.0 { + if actual := testutil.ToFloat64(metrics.httpMetrics.requestInFlight); actual != 1.0 { t.Errorf("Not same: expected %#v, but got %#v", 1.0, actual) } if handlerErr == nil { @@ -43,7 +49,7 @@ func TestMetricsInstrumentedHandler(t *testing.T) { return h.ServeHTTP(w, r) }) - ih := newMetricsInstrumentedHandler("bar", mh) + ih := newMetricsInstrumentedHandler(ctx, "bar", mh, metrics) r := httptest.NewRequest("GET", "/", nil) w := httptest.NewRecorder() @@ -51,7 +57,7 @@ func TestMetricsInstrumentedHandler(t *testing.T) { if actual := ih.ServeHTTP(w, r, h); actual != handlerErr { t.Errorf("Not same: expected %#v, but got %#v", handlerErr, actual) } - if actual := testutil.ToFloat64(httpMetrics.requestInFlight); actual != 0.0 { + if actual := testutil.ToFloat64(metrics.httpMetrics.requestInFlight); actual != 0.0 { t.Errorf("Not same: expected %#v, but got %#v", 0.0, actual) } @@ -64,7 +70,7 @@ func TestMetricsInstrumentedHandler(t *testing.T) { mh = middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error { return nil }) - ih = newMetricsInstrumentedHandler("empty", mh) + ih = newMetricsInstrumentedHandler(ctx, "empty", mh, metrics) r = httptest.NewRequest("GET", "/", nil) w = httptest.NewRecorder() @@ -83,7 +89,7 @@ func TestMetricsInstrumentedHandler(t *testing.T) { return Error(http.StatusTooManyRequests, nil) }) - ih = newMetricsInstrumentedHandler("foo", mh) + ih = newMetricsInstrumentedHandler(ctx, "foo", mh, metrics) r = httptest.NewRequest("GET", "/", nil) w = httptest.NewRecorder() @@ -183,7 +189,183 @@ func TestMetricsInstrumentedHandler(t *testing.T) { caddy_http_request_errors_total{handler="bar",server="UNKNOWN"} 1 caddy_http_request_errors_total{handler="foo",server="UNKNOWN"} 1 ` - if err := testutil.GatherAndCompare(prometheus.DefaultGatherer, strings.NewReader(expected), + if err := testutil.GatherAndCompare(ctx.GetMetricsRegistry(), strings.NewReader(expected), + "caddy_http_request_size_bytes", + "caddy_http_response_size_bytes", + // caddy_http_request_duration_seconds_sum will vary based on how long the test took to run, + // so we check just the _bucket and _count metrics + "caddy_http_request_duration_seconds_bucket", + "caddy_http_request_duration_seconds_count", + "caddy_http_request_errors_total", + ); err != nil { + t.Errorf("received unexpected error: %s", err) + } +} + +func TestMetricsInstrumentedHandlerPerHost(t *testing.T) { + ctx, _ := caddy.NewContext(caddy.Context{Context: context.Background()}) + metrics := &Metrics{ + PerHost: true, + init: sync.Once{}, + httpMetrics: &httpMetrics{}, + } + handlerErr := errors.New("oh noes") + response := []byte("hello world!") + h := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { + if actual := testutil.ToFloat64(metrics.httpMetrics.requestInFlight); actual != 1.0 { + t.Errorf("Not same: expected %#v, but got %#v", 1.0, actual) + } + if handlerErr == nil { + w.Write(response) + } + return handlerErr + }) + + mh := middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error { + return h.ServeHTTP(w, r) + }) + + ih := newMetricsInstrumentedHandler(ctx, "bar", mh, metrics) + + r := httptest.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + + if actual := ih.ServeHTTP(w, r, h); actual != handlerErr { + t.Errorf("Not same: expected %#v, but got %#v", handlerErr, actual) + } + if actual := testutil.ToFloat64(metrics.httpMetrics.requestInFlight); actual != 0.0 { + t.Errorf("Not same: expected %#v, but got %#v", 0.0, actual) + } + + handlerErr = nil + if err := ih.ServeHTTP(w, r, h); err != nil { + t.Errorf("Received unexpected error: %v", err) + } + + // an empty handler - no errors, no header written + mh = middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error { + return nil + }) + ih = newMetricsInstrumentedHandler(ctx, "empty", mh, metrics) + r = httptest.NewRequest("GET", "/", nil) + w = httptest.NewRecorder() + + if err := ih.ServeHTTP(w, r, h); err != nil { + t.Errorf("Received unexpected error: %v", err) + } + if actual := w.Result().StatusCode; actual != 200 { + t.Errorf("Not same: expected status code %#v, but got %#v", 200, actual) + } + if actual := w.Result().Header; len(actual) != 0 { + t.Errorf("Not empty: expected headers to be empty, but got %#v", actual) + } + + // handler returning an error with an HTTP status + mh = middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error { + return Error(http.StatusTooManyRequests, nil) + }) + + ih = newMetricsInstrumentedHandler(ctx, "foo", mh, metrics) + + r = httptest.NewRequest("GET", "/", nil) + w = httptest.NewRecorder() + + if err := ih.ServeHTTP(w, r, nil); err == nil { + t.Errorf("expected error to be propagated") + } + + expected := ` + # HELP caddy_http_request_duration_seconds Histogram of round-trip request durations. + # TYPE caddy_http_request_duration_seconds histogram + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="0.005"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="0.01"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="0.025"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="0.05"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="0.1"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="0.25"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="0.5"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="1"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="2.5"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="5"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="10"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="+Inf"} 1 + caddy_http_request_duration_seconds_count{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN"} 1 + # HELP caddy_http_request_size_bytes Total size of the request. Includes body + # TYPE caddy_http_request_size_bytes histogram + caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="256"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="1024"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="4096"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="16384"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="65536"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="262144"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="1.048576e+06"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="4.194304e+06"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="+Inf"} 1 + caddy_http_request_size_bytes_sum{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN"} 23 + caddy_http_request_size_bytes_count{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="256"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="1024"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="4096"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="16384"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="65536"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="262144"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="1.048576e+06"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="4.194304e+06"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="+Inf"} 1 + caddy_http_request_size_bytes_sum{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN"} 23 + caddy_http_request_size_bytes_count{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="256"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="1024"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="4096"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="16384"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="65536"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="262144"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="1.048576e+06"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="4.194304e+06"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="+Inf"} 1 + caddy_http_request_size_bytes_sum{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN"} 23 + caddy_http_request_size_bytes_count{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN"} 1 + # HELP caddy_http_response_size_bytes Size of the returned response. + # TYPE caddy_http_response_size_bytes histogram + caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="256"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="1024"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="4096"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="16384"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="65536"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="262144"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="1.048576e+06"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="4.194304e+06"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="+Inf"} 1 + caddy_http_response_size_bytes_sum{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN"} 12 + caddy_http_response_size_bytes_count{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="256"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="1024"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="4096"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="16384"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="65536"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="262144"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="1.048576e+06"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="4.194304e+06"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="+Inf"} 1 + caddy_http_response_size_bytes_sum{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN"} 0 + caddy_http_response_size_bytes_count{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="256"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="1024"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="4096"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="16384"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="65536"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="262144"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="1.048576e+06"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="4.194304e+06"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="+Inf"} 1 + caddy_http_response_size_bytes_sum{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN"} 0 + caddy_http_response_size_bytes_count{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN"} 1 + # HELP caddy_http_request_errors_total Number of requests resulting in middleware errors. + # TYPE caddy_http_request_errors_total counter + caddy_http_request_errors_total{handler="bar",host="example.com",server="UNKNOWN"} 1 + caddy_http_request_errors_total{handler="foo",host="example.com",server="UNKNOWN"} 1 + ` + if err := testutil.GatherAndCompare(ctx.GetMetricsRegistry(), strings.NewReader(expected), "caddy_http_request_size_bytes", "caddy_http_response_size_bytes", // caddy_http_request_duration_seconds_sum will vary based on how long the test took to run, diff --git a/modules/caddyhttp/routes.go b/modules/caddyhttp/routes.go index 54a3f38e62a..939d01e5588 100644 --- a/modules/caddyhttp/routes.go +++ b/modules/caddyhttp/routes.go @@ -314,11 +314,11 @@ func wrapRoute(route Route) Middleware { // we need to pull this particular MiddlewareHandler // pointer into its own stack frame to preserve it so it // won't be overwritten in future loop iterations. -func wrapMiddleware(_ caddy.Context, mh MiddlewareHandler, metrics *Metrics) Middleware { +func wrapMiddleware(ctx caddy.Context, mh MiddlewareHandler, metrics *Metrics) Middleware { handlerToUse := mh if metrics != nil { // wrap the middleware with metrics instrumentation - handlerToUse = newMetricsInstrumentedHandler(caddy.GetModuleName(mh), mh) + handlerToUse = newMetricsInstrumentedHandler(ctx, caddy.GetModuleName(mh), mh, metrics) } return func(next Handler) Handler { diff --git a/modules/metrics/adminmetrics.go b/modules/metrics/adminmetrics.go index 1cf398e4cb6..1e3a841ddb2 100644 --- a/modules/metrics/adminmetrics.go +++ b/modules/metrics/adminmetrics.go @@ -15,8 +15,11 @@ package metrics import ( + "errors" "net/http" + "github.com/prometheus/client_golang/prometheus" + "github.com/caddyserver/caddy/v2" ) @@ -29,7 +32,11 @@ func init() { // is permanently mounted to the admin API endpoint at "/metrics". // See the Metrics module for a configurable endpoint that is usable if the // Admin API is disabled. -type AdminMetrics struct{} +type AdminMetrics struct { + registry *prometheus.Registry + + metricsHandler http.Handler +} // CaddyModule returns the Caddy module information. func (AdminMetrics) CaddyModule() caddy.ModuleInfo { @@ -39,17 +46,28 @@ func (AdminMetrics) CaddyModule() caddy.ModuleInfo { } } +// Provision - +func (m *AdminMetrics) Provision(ctx caddy.Context) error { + m.registry = ctx.GetMetricsRegistry() + if m.registry == nil { + return errors.New("no metrics registry found") + } + m.metricsHandler = createMetricsHandler(nil, false, m.registry) + return nil +} + // Routes returns a route for the /metrics endpoint. func (m *AdminMetrics) Routes() []caddy.AdminRoute { - metricsHandler := createMetricsHandler(nil, false) - h := caddy.AdminHandlerFunc(func(w http.ResponseWriter, r *http.Request) error { - metricsHandler.ServeHTTP(w, r) - return nil - }) - return []caddy.AdminRoute{{Pattern: "/metrics", Handler: h}} + return []caddy.AdminRoute{{Pattern: "/metrics", Handler: caddy.AdminHandlerFunc(m.serveHTTP)}} +} + +func (m *AdminMetrics) serveHTTP(w http.ResponseWriter, r *http.Request) error { + m.metricsHandler.ServeHTTP(w, r) + return nil } // Interface guards var ( + _ caddy.Provisioner = (*AdminMetrics)(nil) _ caddy.AdminRouter = (*AdminMetrics)(nil) ) diff --git a/modules/metrics/metrics.go b/modules/metrics/metrics.go index dc6196a15c8..42b30d88dd7 100644 --- a/modules/metrics/metrics.go +++ b/modules/metrics/metrics.go @@ -15,6 +15,7 @@ package metrics import ( + "errors" "net/http" "github.com/prometheus/client_golang/prometheus" @@ -62,7 +63,11 @@ func (l *zapLogger) Println(v ...any) { // Provision sets up m. func (m *Metrics) Provision(ctx caddy.Context) error { log := ctx.Logger() - m.metricsHandler = createMetricsHandler(&zapLogger{log}, !m.DisableOpenMetrics) + registry := ctx.GetMetricsRegistry() + if registry == nil { + return errors.New("no metrics registry found") + } + m.metricsHandler = createMetricsHandler(&zapLogger{log}, !m.DisableOpenMetrics, registry) return nil } @@ -107,9 +112,9 @@ var ( _ caddyfile.Unmarshaler = (*Metrics)(nil) ) -func createMetricsHandler(logger promhttp.Logger, enableOpenMetrics bool) http.Handler { - return promhttp.InstrumentMetricHandler(prometheus.DefaultRegisterer, - promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{ +func createMetricsHandler(logger promhttp.Logger, enableOpenMetrics bool, registry *prometheus.Registry) http.Handler { + return promhttp.InstrumentMetricHandler(registry, + promhttp.HandlerFor(registry, promhttp.HandlerOpts{ // will only log errors if logger is non-nil ErrorLog: logger, From 01be1b54a8c9b7e6ea52af8165c583d4d2ebe7e3 Mon Sep 17 00:00:00 2001 From: Mohammed Al Sahaf Date: Wed, 2 Oct 2024 19:12:29 +0300 Subject: [PATCH 20/39] ci: install xcaddy to fix release flow (#6602) --- .github/workflows/ci.yml | 15 +++++++++++++++ .github/workflows/release.yml | 4 ++++ .goreleaser.yml | 3 +-- cmd/commands.go | 4 ++-- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a74d8cc69b..a88bd17a418 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -202,3 +202,18 @@ jobs: with: version: latest args: check + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: "~1.23" + check-latest: true + - name: Install xcaddy + run: | + go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest + xcaddy version + - uses: goreleaser/goreleaser-action@v6 + with: + version: latest + args: build --single-target --snapshot + env: + TAG: "master" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1eb59e9d02d..d788ca361ff 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -104,6 +104,10 @@ jobs: uses: anchore/sbom-action/download-syft@main - name: Syft version run: syft version + - name: Install xcaddy + run: | + go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest + xcaddy version # GoReleaser will take care of publishing those artifacts into the release - name: Run GoReleaser uses: goreleaser/goreleaser-action@v6 diff --git a/.goreleaser.yml b/.goreleaser.yml index c7d01571ee4..5c1f7df407f 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -13,8 +13,7 @@ before: - cp cmd/caddy/main.go caddy-build/main.go - /bin/sh -c 'cd ./caddy-build && go mod init caddy' # prepare syso files for windows embedding - - go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest - - /bin/sh -c 'for a in amd64 arm arm64; do XCADDY_SKIP_BUILD=1 GOOS=windows GOARCH=$a $GOPATH/bin/xcaddy build {{.Env.TAG}}; done' + - /bin/sh -c 'for a in amd64 arm arm64; do XCADDY_SKIP_BUILD=1 GOOS=windows GOARCH=$a xcaddy build {{.Env.TAG}}; done' - /bin/sh -c 'mv /tmp/buildenv_*/*.syso caddy-build' # GoReleaser doesn't seem to offer {{.Tag}} at this stage, so we have to embed it into the env # so we run: TAG=$(git describe --abbrev=0) goreleaser release --rm-dist --skip-publish --skip-validate diff --git a/cmd/commands.go b/cmd/commands.go index 0853ebf8359..ab4b66d7771 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -439,7 +439,7 @@ EXPERIMENTAL: May be changed or removed. }) defaultFactory.Use(func(rootCmd *cobra.Command) { - RegisterCommand(Command{ + rootCmd.AddCommand(caddyCmdToCobra(Command{ Name: "manpage", Usage: "--directory ", Short: "Generates the manual pages for Caddy commands", @@ -469,7 +469,7 @@ argument of --directory. If the directory does not exist, it will be created. return caddy.ExitCodeSuccess, nil }) }, - }) + })) // source: https://github.com/spf13/cobra/blob/main/shell_completions.md rootCmd.AddCommand(&cobra.Command{ From 2ae58ac13e1757bccc935818879bc12a2320aea3 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Wed, 2 Oct 2024 16:00:48 -0600 Subject: [PATCH 21/39] go.mod: Upgrade some dependencies --- go.mod | 56 +++++++++++++------------- go.sum | 125 ++++++++++++++++++++++++++------------------------------- 2 files changed, 86 insertions(+), 95 deletions(-) diff --git a/go.mod b/go.mod index 62fe30dee56..dd83ed9cfa2 100644 --- a/go.mod +++ b/go.mod @@ -5,49 +5,50 @@ go 1.22.3 toolchain go1.23.0 require ( - github.com/BurntSushi/toml v1.3.2 - github.com/Masterminds/sprig/v3 v3.2.3 - github.com/alecthomas/chroma/v2 v2.13.0 + github.com/BurntSushi/toml v1.4.0 + github.com/Masterminds/sprig/v3 v3.3.0 + github.com/alecthomas/chroma/v2 v2.14.0 github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b - github.com/caddyserver/certmagic v0.21.3 + github.com/caddyserver/certmagic v0.21.4 github.com/caddyserver/zerossl v0.1.3 github.com/dustin/go-humanize v1.0.1 github.com/go-chi/chi/v5 v5.0.12 github.com/google/cel-go v0.21.0 github.com/google/uuid v1.6.0 - github.com/klauspost/compress v1.17.8 - github.com/klauspost/cpuid/v2 v2.2.7 - github.com/mholt/acmez/v2 v2.0.1 + github.com/klauspost/compress v1.17.10 + github.com/klauspost/cpuid/v2 v2.2.8 + github.com/mholt/acmez/v2 v2.0.3 github.com/prometheus/client_golang v1.19.1 github.com/quic-go/quic-go v0.47.0 github.com/smallstep/certificates v0.26.1 github.com/smallstep/nosql v0.6.1 github.com/smallstep/truststore v0.13.0 - github.com/spf13/cobra v1.8.0 + github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.9.0 github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 - github.com/yuin/goldmark v1.7.1 + github.com/yuin/goldmark v1.7.4 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 go.opentelemetry.io/contrib/propagators/autoprop v0.42.0 go.opentelemetry.io/otel v1.24.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 go.opentelemetry.io/otel/sdk v1.21.0 - go.uber.org/automaxprocs v1.5.3 + go.uber.org/automaxprocs v1.6.0 go.uber.org/zap v1.27.0 go.uber.org/zap/exp v0.2.0 - golang.org/x/crypto v0.26.0 - golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 - golang.org/x/net v0.28.0 + golang.org/x/crypto v0.27.0 + golang.org/x/crypto/x509roots/fallback v0.0.0-20240930154113-a0819fbb0244 + golang.org/x/net v0.29.0 golang.org/x/sync v0.8.0 - golang.org/x/term v0.23.0 - golang.org/x/time v0.5.0 + golang.org/x/term v0.24.0 + golang.org/x/time v0.6.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 ) require ( + dario.cat/mergo v1.0.1 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -68,7 +69,7 @@ require ( github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect github.com/x448/float16 v0.8.4 // indirect - github.com/zeebo/blake3 v0.2.3 // indirect + github.com/zeebo/blake3 v0.2.4 // indirect go.opentelemetry.io/contrib/propagators/aws v1.17.0 // indirect go.opentelemetry.io/contrib/propagators/b3 v1.17.0 // indirect go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 // indirect @@ -83,13 +84,13 @@ require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 github.com/chzyer/readline v1.5.1 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/dgraph-io/badger v1.6.2 // indirect github.com/dgraph-io/badger/v2 v2.2007.4 // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect @@ -104,8 +105,7 @@ require ( github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/huandu/xstrings v1.3.3 // indirect - github.com/imdario/mergo v0.3.12 // indirect + github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgconn v1.14.3 // indirect @@ -115,12 +115,12 @@ require ( github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgtype v1.14.0 // indirect github.com/jackc/pgx/v4 v4.18.3 // indirect - github.com/libdns/libdns v0.2.2 // indirect + github.com/libdns/libdns v0.2.2 github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect - github.com/miekg/dns v1.1.59 // indirect + github.com/miekg/dns v1.1.62 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect @@ -131,11 +131,11 @@ require ( github.com/prometheus/procfs v0.12.0 // indirect github.com/rs/xid v1.5.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/shopspring/decimal v1.2.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/slackhq/nebula v1.6.1 // indirect - github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/cast v1.7.0 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect github.com/urfave/cli v1.22.14 // indirect go.etcd.io/bbolt v1.3.9 // indirect @@ -147,10 +147,10 @@ require ( go.step.sm/crypto v0.45.0 go.step.sm/linkedca v0.20.1 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/sys v0.23.0 - golang.org/x/text v0.17.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + golang.org/x/mod v0.18.0 // indirect + golang.org/x/sys v0.25.0 + golang.org/x/text v0.18.0 // indirect + golang.org/x/tools v0.22.0 // indirect google.golang.org/grpc v1.63.2 // indirect google.golang.org/protobuf v1.34.1 // indirect howett.net/plist v1.0.0 // indirect diff --git a/go.sum b/go.sum index 1f741c104bc..f12b4b3c1c1 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY= cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc= cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= @@ -26,24 +28,25 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= -github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= -github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU= -github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= +github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= -github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI= -github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk= +github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= +github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= @@ -86,8 +89,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/caddyserver/certmagic v0.21.3 h1:pqRRry3yuB4CWBVq9+cUqu+Y6E2z8TswbhNx1AZeYm0= -github.com/caddyserver/certmagic v0.21.3/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI= +github.com/caddyserver/certmagic v0.21.4 h1:e7VobB8rffHv8ZZpSiZtEwnLDHUwLVYLWzWSa1FfKI0= +github.com/caddyserver/certmagic v0.21.4/go.mod h1:swUXjQ1T9ZtMv95qj7/InJvWLXURU85r+CfG0T+ZbDE= github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= @@ -117,8 +120,8 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -146,6 +149,8 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= @@ -222,7 +227,6 @@ github.com/google/pprof v0.0.0-20231212022811-ec68065c825e/go.mod h1:czg5+yv1E0Z github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= @@ -240,11 +244,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp4 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= -github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -301,11 +302,10 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= -github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/compress v1.17.10 h1:oXAz+Vh0PMUvJczoi+flxpnBEPxoER1IaAnU/NMPtT0= +github.com/klauspost/compress v1.17.10/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -344,19 +344,17 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k= -github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U= +github.com/mholt/acmez/v2 v2.0.3 h1:CgDBlEwg3QBp6s45tPQmFIBrkRIkBT4rW4orMM6p4sw= +github.com/mholt/acmez/v2 v2.0.3/go.mod h1:pQ1ysaDeGrIMvJ9dfJMk5kJNkn7L2sb3UhyrX6Q91cw= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= -github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= -github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= +github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -412,8 +410,9 @@ github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= @@ -466,12 +465,11 @@ github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0b github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= -github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -508,14 +506,14 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U= -github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= +github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= -github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= +github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= +github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= @@ -562,8 +560,8 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= -go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= @@ -596,12 +594,11 @@ golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= -golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 h1:TgSqweA595vD0Zt86JzLv3Pb/syKg8gd5KMGGbJPYFw= -golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto/x509roots/fallback v0.0.0-20240930154113-a0819fbb0244 h1:3uziZWNwkTfxhMOxJB13NpTR+svHLMMVDhTrEyZOd3k= +golang.org/x/crypto/x509roots/fallback v0.0.0-20240930154113-a0819fbb0244/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -613,8 +610,8 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -629,11 +626,10 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -675,22 +671,20 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -698,16 +692,15 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -723,8 +716,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -771,8 +764,6 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYs gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= From 88fd5f3491ab888f69f0be02cea68a49164298eb Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Fri, 4 Oct 2024 10:23:30 -0600 Subject: [PATCH 22/39] caddyhttp: Use internal issuer for IPs when no APs configured This fixes a regression in 2.8 where IP addresses would be considered qualifying for public certs by auto-HTTPS. The default issuers do not issue IP certs at this time, so if no APs are explicitly configured, we assign them to the internal issuer. We have to add a couple lines of code because CertMagic can no longer consider IPs as not qualifying for public certs, since there are public CAs that issue IP certs. This edge case is specific to Caddy's auto-HTTPS. Without this patch, Caddy will try using Let's Encrypt or ZeroSSL's ACME endpoint to get IP certs, neither of which support that. --- modules/caddyhttp/autohttps.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/modules/caddyhttp/autohttps.go b/modules/caddyhttp/autohttps.go index ccb610327ac..4449e1f4db6 100644 --- a/modules/caddyhttp/autohttps.go +++ b/modules/caddyhttp/autohttps.go @@ -320,11 +320,21 @@ uniqueDomainsLoop: } } - // if no automation policy exists for the name yet, we - // will associate it with an implicit one + // if no automation policy exists for the name yet, we will associate it with an implicit one; + // we handle tailscale domains specially, and we also separate out identifiers that need the + // internal issuer (self-signed certs); certmagic does not consider public IP addresses to be + // disqualified for public certs, because there are public CAs that will issue certs for IPs. + // However, with auto-HTTPS, many times there is no issuer explicitly defined, and the default + // issuers do not (currently, as of 2024) issue IP certificates; so assign all IP subjects to + // the internal issuer when there are no explicit automation policies + shouldUseInternal := func(ident string) bool { + usingDefaultIssuersAndIsIP := certmagic.SubjectIsIP(ident) && + (app.tlsApp == nil || app.tlsApp.Automation == nil || len(app.tlsApp.Automation.Policies) == 0) + return !certmagic.SubjectQualifiesForPublicCert(d) || usingDefaultIssuersAndIsIP + } if isTailscaleDomain(d) { tailscale = append(tailscale, d) - } else if !certmagic.SubjectQualifiesForPublicCert(d) { + } else if shouldUseInternal(d) { internal = append(internal, d) } } From d7564d632fbed209e81978c5c2c529a7bf1836f7 Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Mon, 7 Oct 2024 17:39:47 -0400 Subject: [PATCH 23/39] caddytls: Drop `rate_limit` and `burst`, has been deprecated (#6611) --- caddyconfig/httpcaddyfile/options.go | 30 ++---------------- .../global_options.caddyfiletest | 6 ---- .../global_options_acme.caddyfiletest | 6 ---- .../global_options_admin.caddyfiletest | 6 ---- modules/caddytls/automation.go | 6 ---- modules/caddytls/ondemand.go | 31 +++---------------- modules/caddytls/tls.go | 11 ------- 7 files changed, 7 insertions(+), 89 deletions(-) diff --git a/caddyconfig/httpcaddyfile/options.go b/caddyconfig/httpcaddyfile/options.go index abbe8f4185e..9bae760c01a 100644 --- a/caddyconfig/httpcaddyfile/options.go +++ b/caddyconfig/httpcaddyfile/options.go @@ -394,36 +394,10 @@ func parseOptOnDemand(d *caddyfile.Dispenser, _ any) (any, error) { ond.PermissionRaw = caddyconfig.JSONModuleObject(perm, "module", modName, nil) case "interval": - if !d.NextArg() { - return nil, d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return nil, err - } - if ond == nil { - ond = new(caddytls.OnDemandConfig) - } - if ond.RateLimit == nil { - ond.RateLimit = new(caddytls.RateLimit) - } - ond.RateLimit.Interval = caddy.Duration(dur) + return nil, d.Errf("the on_demand_tls 'interval' option is no longer supported, remove it from your config") case "burst": - if !d.NextArg() { - return nil, d.ArgErr() - } - burst, err := strconv.Atoi(d.Val()) - if err != nil { - return nil, err - } - if ond == nil { - ond = new(caddytls.OnDemandConfig) - } - if ond.RateLimit == nil { - ond.RateLimit = new(caddytls.RateLimit) - } - ond.RateLimit.Burst = burst + return nil, d.Errf("the on_demand_tls 'burst' option is no longer supported, remove it from your config") default: return nil, d.Errf("unrecognized parameter '%s'", d.Val()) diff --git a/caddytest/integration/caddyfile_adapt/global_options.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options.caddyfiletest index 88729c51287..af301615b41 100644 --- a/caddytest/integration/caddyfile_adapt/global_options.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/global_options.caddyfiletest @@ -17,8 +17,6 @@ admin off on_demand_tls { ask https://example.com - interval 30s - burst 20 } local_certs key_type ed25519 @@ -72,10 +70,6 @@ "permission": { "endpoint": "https://example.com", "module": "http" - }, - "rate_limit": { - "interval": 30000000000, - "burst": 20 } } }, diff --git a/caddytest/integration/caddyfile_adapt/global_options_acme.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_acme.caddyfiletest index bc4b6dcaf91..004a3a32ece 100644 --- a/caddytest/integration/caddyfile_adapt/global_options_acme.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/global_options_acme.caddyfiletest @@ -17,8 +17,6 @@ admin off on_demand_tls { ask https://example.com - interval 30s - burst 20 } storage_clean_interval 7d renew_interval 1d @@ -89,10 +87,6 @@ "permission": { "endpoint": "https://example.com", "module": "http" - }, - "rate_limit": { - "interval": 30000000000, - "burst": 20 } }, "ocsp_interval": 172800000000000, diff --git a/caddytest/integration/caddyfile_adapt/global_options_admin.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_admin.caddyfiletest index cfc5788267f..be309eaa304 100644 --- a/caddytest/integration/caddyfile_adapt/global_options_admin.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/global_options_admin.caddyfiletest @@ -16,8 +16,6 @@ } on_demand_tls { ask https://example.com - interval 30s - burst 20 } local_certs key_type ed25519 @@ -74,10 +72,6 @@ "permission": { "endpoint": "https://example.com", "module": "http" - }, - "rate_limit": { - "interval": 30000000000, - "burst": 20 } } } diff --git a/modules/caddytls/automation.go b/modules/caddytls/automation.go index 1f1042ba0da..f6a5350778d 100644 --- a/modules/caddytls/automation.go +++ b/modules/caddytls/automation.go @@ -322,12 +322,6 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error { return err } - // check the rate limiter last because - // doing so makes a reservation - if !onDemandRateLimiter.Allow() { - return fmt.Errorf("on-demand rate limit exceeded") - } - return nil }, Managers: ap.Managers, diff --git a/modules/caddytls/ondemand.go b/modules/caddytls/ondemand.go index 89abfe03f41..066473cd939 100644 --- a/modules/caddytls/ondemand.go +++ b/modules/caddytls/ondemand.go @@ -38,12 +38,11 @@ func init() { // OnDemandConfig configures on-demand TLS, for obtaining // needed certificates at handshake-time. Because this -// feature can easily be abused, you should use this to -// establish rate limits and/or an internal endpoint that -// Caddy can "ask" if it should be allowed to manage -// certificates for a given hostname. +// feature can easily be abused, Caddy must ask permission +// to your application whether a particular domain is allowed +// to have a certificate issued for it. type OnDemandConfig struct { - // DEPRECATED. WILL BE REMOVED SOON. Use 'permission' instead. + // DEPRECATED. WILL BE REMOVED SOON. Use 'permission' instead with the `http` module. Ask string `json:"ask,omitempty"` // REQUIRED. A module that will determine whether a @@ -51,25 +50,6 @@ type OnDemandConfig struct { // or obtained from an issuer on demand. PermissionRaw json.RawMessage `json:"permission,omitempty" caddy:"namespace=tls.permission inline_key=module"` permission OnDemandPermission - - // DEPRECATED. An optional rate limit to throttle - // the checking of storage and the issuance of - // certificates from handshakes if not already in - // storage. WILL BE REMOVED IN A FUTURE RELEASE. - RateLimit *RateLimit `json:"rate_limit,omitempty"` -} - -// DEPRECATED. WILL LIKELY BE REMOVED SOON. -// Instead of using this rate limiter, use a proper tool such as a -// level 3 or 4 firewall and/or a permission module to apply rate limits. -type RateLimit struct { - // A duration value. Storage may be checked and a certificate may be - // obtained 'burst' times during this interval. - Interval caddy.Duration `json:"interval,omitempty"` - - // How many times during an interval storage can be checked or a - // certificate can be obtained. - Burst int `json:"burst,omitempty"` } // OnDemandPermission is a type that can give permission for @@ -195,8 +175,7 @@ var ErrPermissionDenied = errors.New("certificate not allowed by permission modu // These perpetual values are used for on-demand TLS. var ( - onDemandRateLimiter = certmagic.NewRateLimiter(0, 0) - onDemandAskClient = &http.Client{ + onDemandAskClient = &http.Client{ Timeout: 10 * time.Second, CheckRedirect: func(req *http.Request, via []*http.Request) error { return fmt.Errorf("following http redirects is not allowed") diff --git a/modules/caddytls/tls.go b/modules/caddytls/tls.go index f04beb2ee47..6e660dea851 100644 --- a/modules/caddytls/tls.go +++ b/modules/caddytls/tls.go @@ -188,17 +188,6 @@ func (t *TLS) Provision(ctx caddy.Context) error { t.Automation.OnDemand.permission = val.(OnDemandPermission) } - // on-demand rate limiting (TODO: deprecated, and should be removed later; rate limiting is ineffective now that permission modules are required) - if t.Automation != nil && t.Automation.OnDemand != nil && t.Automation.OnDemand.RateLimit != nil { - t.logger.Warn("DEPRECATED: on_demand.rate_limit will be removed in a future release; use permission modules or external certificate managers instead") - onDemandRateLimiter.SetMaxEvents(t.Automation.OnDemand.RateLimit.Burst) - onDemandRateLimiter.SetWindow(time.Duration(t.Automation.OnDemand.RateLimit.Interval)) - } else { - // remove any existing rate limiter - onDemandRateLimiter.SetWindow(0) - onDemandRateLimiter.SetMaxEvents(0) - } - // run replacer on ask URL (for environment variables) -- return errors to prevent surprises (#5036) if t.Automation != nil && t.Automation.OnDemand != nil && t.Automation.OnDemand.Ask != "" { t.Automation.OnDemand.Ask, err = repl.ReplaceOrErr(t.Automation.OnDemand.Ask, true, true) From dd5decabe7379a069c3b198e0930ec94d75c7bd4 Mon Sep 17 00:00:00 2001 From: Mohammed Al Sahaf Date: Thu, 10 Oct 2024 22:38:49 +0300 Subject: [PATCH 24/39] tests: fix caddyfile adapt warnings (#6619) Signed-off-by: Mohammed Al Sahaf --- .../acme_server_sign_with_root.caddyfiletest | 4 ++-- .../caddyfile_adapt/heredoc.caddyfiletest | 3 ++- .../map_and_vars_with_raw_types.caddyfiletest | 15 ++++++++------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/caddytest/integration/caddyfile_adapt/acme_server_sign_with_root.caddyfiletest b/caddytest/integration/caddyfile_adapt/acme_server_sign_with_root.caddyfiletest index 9880f282150..5b50401077c 100644 --- a/caddytest/integration/caddyfile_adapt/acme_server_sign_with_root.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/acme_server_sign_with_root.caddyfiletest @@ -5,15 +5,15 @@ root_cn "Internal Root Cert" intermediate_cn "Internal Intermediate Cert" } - } + } } - acme.example.com { acme_server { ca internal sign_with_root } } + ---------- { "apps": { diff --git a/caddytest/integration/caddyfile_adapt/heredoc.caddyfiletest b/caddytest/integration/caddyfile_adapt/heredoc.caddyfiletest index cc1174d6d3b..f50d2b7f952 100644 --- a/caddytest/integration/caddyfile_adapt/heredoc.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/heredoc.caddyfiletest @@ -1,11 +1,12 @@ example.com { - respond < Foo Foo EOF 200 } + ---------- { "apps": { diff --git a/caddytest/integration/caddyfile_adapt/map_and_vars_with_raw_types.caddyfiletest b/caddytest/integration/caddyfile_adapt/map_and_vars_with_raw_types.caddyfiletest index cc75630151c..8b872635df4 100644 --- a/caddytest/integration/caddyfile_adapt/map_and_vars_with_raw_types.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/map_and_vars_with_raw_types.caddyfiletest @@ -1,23 +1,23 @@ example.com -map {host} {my_placeholder} {magic_number} { +map {host} {my_placeholder} {magic_number} { # Should output boolean "true" and an integer - example.com true 3 + example.com true 3 # Should output a string and null - foo.example.com "string value" + foo.example.com "string value" # Should output two strings (quoted int) - (.*)\.example.com "${1} subdomain" "5" + (.*)\.example.com "${1} subdomain" "5" # Should output null and a string (quoted int) - ~.*\.net$ - `7` + ~.*\.net$ - `7` # Should output a float and the string "false" - ~.*\.xyz$ 123.456 "false" + ~.*\.xyz$ 123.456 "false" # Should output two strings, second being escaped quote - default "unknown domain" \""" + default "unknown domain" \""" } vars foo bar @@ -27,6 +27,7 @@ vars { ghi 2.3 jkl "mn op" } + ---------- { "apps": { From c8a76d003f700b448291845b7879f27c2f19d1dc Mon Sep 17 00:00:00 2001 From: Mohammed Al Sahaf Date: Thu, 10 Oct 2024 23:21:26 +0300 Subject: [PATCH 25/39] docs: expand proxy protocol docs (#6620) --- .../proxyprotocol/listenerwrapper.go | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/modules/caddyhttp/proxyprotocol/listenerwrapper.go b/modules/caddyhttp/proxyprotocol/listenerwrapper.go index f5f2099ce42..f1d170c38ca 100644 --- a/modules/caddyhttp/proxyprotocol/listenerwrapper.go +++ b/modules/caddyhttp/proxyprotocol/listenerwrapper.go @@ -25,7 +25,12 @@ import ( ) // ListenerWrapper provides PROXY protocol support to Caddy by implementing -// the caddy.ListenerWrapper interface. It must be loaded before the `tls` listener. +// the caddy.ListenerWrapper interface. If a connection is received via Unix +// socket, it's trusted. Otherwise, it's checked against the Allow/Deny lists, +// then it's handled by the FallbackPolicy. +// +// It must be loaded before the `tls` listener because the PROXY protocol +// encapsulates the TLS data. // // Credit goes to https://github.com/mastercactapus/caddy2-proxyprotocol for having // initially implemented this as a plugin. @@ -45,8 +50,35 @@ type ListenerWrapper struct { Deny []string `json:"deny,omitempty"` deny []netip.Prefix - // Accepted values are: ignore, use, reject, require, skip - // default: ignore + // FallbackPolicy specifies the policy to use if the downstream + // IP address is not in the Allow list nor is in the Deny list. + // + // NOTE: The generated docs which describe the value of this + // field is wrong because of how this type unmarshals JSON in a + // custom way. The field expects a string, not a number. + // + // Accepted values are: IGNORE, USE, REJECT, REQUIRE, SKIP + // + // - IGNORE: address from PROXY header, but accept connection + // + // - USE: address from PROXY header + // + // - REJECT: connection when PROXY header is sent + // Note: even though the first read on the connection returns an error if + // a PROXY header is present, subsequent reads do not. It is the task of + // the code using the connection to handle that case properly. + // + // - REQUIRE: connection to send PROXY header, reject if not present + // Note: even though the first read on the connection returns an error if + // a PROXY header is not present, subsequent reads do not. It is the task + // of the code using the connection to handle that case properly. + // + // - SKIP: accepts a connection without requiring the PROXY header. + // Note: an example usage can be found in the SkipProxyHeaderForCIDR + // function. + // + // Default: IGNORE + // // Policy definitions are here: https://pkg.go.dev/github.com/pires/go-proxyproto@v0.7.0#Policy FallbackPolicy Policy `json:"fallback_policy,omitempty"` From ef4e0224a8495fc29847d865087febdee8736e3b Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Thu, 10 Oct 2024 16:26:59 -0400 Subject: [PATCH 26/39] caddyfile: Fix comma edgecase in address parsing (#6616) --- caddyconfig/caddyfile/parse.go | 9 +++++++-- caddyconfig/caddyfile/parse_test.go | 8 ++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/caddyconfig/caddyfile/parse.go b/caddyconfig/caddyfile/parse.go index 17d824efd5d..e19b3b97d1e 100644 --- a/caddyconfig/caddyfile/parse.go +++ b/caddyconfig/caddyfile/parse.go @@ -264,8 +264,13 @@ func (p *parser) addresses() error { return p.Errf("Site addresses cannot contain a comma ',': '%s' - put a space after the comma to separate site addresses", value) } - token.Text = value - p.block.Keys = append(p.block.Keys, token) + // After the above, a comma surrounded by spaces would result + // in an empty token which we should ignore + if value != "" { + // Add the token as a site address + token.Text = value + p.block.Keys = append(p.block.Keys, token) + } } // Advance token and possibly break out of loop or return error diff --git a/caddyconfig/caddyfile/parse_test.go b/caddyconfig/caddyfile/parse_test.go index 7157b2b5f10..d3fada4e026 100644 --- a/caddyconfig/caddyfile/parse_test.go +++ b/caddyconfig/caddyfile/parse_test.go @@ -555,6 +555,10 @@ func TestParseAll(t *testing.T) { {"localhost:1234", "http://host2"}, }}, + {`foo.example.com , example.com`, false, [][]string{ + {"foo.example.com", "example.com"}, + }}, + {`localhost:1234, http://host2,`, true, [][]string{}}, {`http://host1.com, http://host2.com { @@ -614,8 +618,8 @@ func TestParseAll(t *testing.T) { } for j, block := range blocks { if len(block.Keys) != len(test.keys[j]) { - t.Errorf("Test %d: Expected %d keys in block %d, got %d", - i, len(test.keys[j]), j, len(block.Keys)) + t.Errorf("Test %d: Expected %d keys in block %d, got %d: %v", + i, len(test.keys[j]), j, len(block.Keys), block.Keys) continue } for k, addr := range block.GetKeysText() { From 48ce47f1d44da485fbbf6be536a0e3822763f313 Mon Sep 17 00:00:00 2001 From: WeidiDeng Date: Fri, 11 Oct 2024 17:02:23 +0800 Subject: [PATCH 27/39] reverseproxy: Use correct cases for websocket related headers (#6621) Co-authored-by: Francis Lavoie --- .../caddyhttp/reverseproxy/reverseproxy.go | 25 +++++++++++++++++++ modules/caddyhttp/reverseproxy/streaming.go | 1 + 2 files changed, 26 insertions(+) diff --git a/modules/caddyhttp/reverseproxy/reverseproxy.go b/modules/caddyhttp/reverseproxy/reverseproxy.go index bcbc1ff4691..123bf774b33 100644 --- a/modules/caddyhttp/reverseproxy/reverseproxy.go +++ b/modules/caddyhttp/reverseproxy/reverseproxy.go @@ -569,6 +569,30 @@ func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w h return false, proxyErr } +// Mapping of the canonical form of the headers, to the RFC 6455 form, +// i.e. `WebSocket` with uppercase 'S'. +var websocketHeaderMapping = map[string]string{ + "Sec-Websocket-Accept": "Sec-WebSocket-Accept", + "Sec-Websocket-Extensions": "Sec-WebSocket-Extensions", + "Sec-Websocket-Key": "Sec-WebSocket-Key", + "Sec-Websocket-Protocol": "Sec-WebSocket-Protocol", + "Sec-Websocket-Version": "Sec-WebSocket-Version", +} + +// normalizeWebsocketHeaders ensures we use the standard casing as per +// RFC 6455, i.e. `WebSocket` with uppercase 'S'. Most servers don't +// care about this difference (read headers case insensitively), but +// some do, so this maximizes compatibility with upstreams. +// See https://github.com/caddyserver/caddy/pull/6621 +func normalizeWebsocketHeaders(header http.Header) { + for k, rk := range websocketHeaderMapping { + if v, ok := header[k]; ok { + delete(header, k) + header[rk] = v + } + } +} + // prepareRequest clones req so that it can be safely modified without // changing the original request or introducing data races. It then // modifies it so that it is ready to be proxied, except for directing @@ -655,6 +679,7 @@ func (h Handler) prepareRequest(req *http.Request, repl *caddy.Replacer) (*http. if reqUpType != "" { req.Header.Set("Connection", "Upgrade") req.Header.Set("Upgrade", reqUpType) + normalizeWebsocketHeaders(req.Header) } // Set up the PROXY protocol info diff --git a/modules/caddyhttp/reverseproxy/streaming.go b/modules/caddyhttp/reverseproxy/streaming.go index c871a3fa12b..3fde10b35f2 100644 --- a/modules/caddyhttp/reverseproxy/streaming.go +++ b/modules/caddyhttp/reverseproxy/streaming.go @@ -66,6 +66,7 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup, // write header first, response headers should not be counted in size // like the rest of handler chain. copyHeader(rw.Header(), res.Header) + normalizeWebsocketHeaders(rw.Header()) rw.WriteHeader(res.StatusCode) logger.Debug("upgrading connection") From a211c656f12bcab73df0de114f2b6100ee5a0fe4 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 15 Oct 2024 08:38:10 -0500 Subject: [PATCH 28/39] chore: update quic-go to v0.48.0 (#6627) --- go.mod | 2 +- go.sum | 4 ++-- modules/caddyhttp/reverseproxy/httptransport.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index dd83ed9cfa2..67fe8e59c89 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/klauspost/cpuid/v2 v2.2.8 github.com/mholt/acmez/v2 v2.0.3 github.com/prometheus/client_golang v1.19.1 - github.com/quic-go/quic-go v0.47.0 + github.com/quic-go/quic-go v0.48.0 github.com/smallstep/certificates v0.26.1 github.com/smallstep/nosql v0.6.1 github.com/smallstep/truststore v0.13.0 diff --git a/go.sum b/go.sum index f12b4b3c1c1..0956dffd2f0 100644 --- a/go.sum +++ b/go.sum @@ -392,8 +392,8 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.47.0 h1:yXs3v7r2bm1wmPTYNLKAAJTHMYkPEsfYJmTazXrCZ7Y= -github.com/quic-go/quic-go v0.47.0/go.mod h1:3bCapYsJvXGZcipOHuu7plYtaV6tnF+z7wIFsU0WK9E= +github.com/quic-go/quic-go v0.48.0 h1:2TCyvBrMu1Z25rvIAlnp2dPT4lgh/uTqLqiXVpp5AeU= +github.com/quic-go/quic-go v0.48.0/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= diff --git a/modules/caddyhttp/reverseproxy/httptransport.go b/modules/caddyhttp/reverseproxy/httptransport.go index bc82337880c..2b4c3f094b0 100644 --- a/modules/caddyhttp/reverseproxy/httptransport.go +++ b/modules/caddyhttp/reverseproxy/httptransport.go @@ -142,7 +142,7 @@ type HTTPTransport struct { Transport *http.Transport `json:"-"` h2cTransport *http2.Transport - h3Transport *http3.RoundTripper // TODO: EXPERIMENTAL (May 2024) + h3Transport *http3.Transport // TODO: EXPERIMENTAL (May 2024) } // CaddyModule returns the Caddy module information. @@ -393,7 +393,7 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e // do (that'd add latency and complexity, besides, we expect that // site owners control the backends), so it must be exclusive if len(h.Versions) == 1 && h.Versions[0] == "3" { - h.h3Transport = new(http3.RoundTripper) + h.h3Transport = new(http3.Transport) if h.TLS != nil { var err error h.h3Transport.TLSClientConfig, err = h.TLS.MakeTLSClientConfig(caddyCtx) From c6f2979986d87d7236b132c687c8887c92248dd8 Mon Sep 17 00:00:00 2001 From: WeidiDeng Date: Wed, 16 Oct 2024 09:28:20 +0800 Subject: [PATCH 29/39] caddyhttp: Close http3 server gracefully (#6213) * close http3 server gracefully * update server field * update from upstream --------- Co-authored-by: Matt Holt --- modules/caddyhttp/app.go | 11 +---------- modules/caddyhttp/server.go | 24 +----------------------- 2 files changed, 2 insertions(+), 33 deletions(-) diff --git a/modules/caddyhttp/app.go b/modules/caddyhttp/app.go index 7dc2bee72fa..cd32e72d762 100644 --- a/modules/caddyhttp/app.go +++ b/modules/caddyhttp/app.go @@ -689,16 +689,7 @@ func (app *App) Stop() error { return } - // First close h3server then close listeners unlike stdlib for several reasons: - // 1, udp has only a single socket, once closed, no more data can be read and - // written. In contrast, closing tcp listeners won't affect established connections. - // This have something to do with graceful shutdown when upstream implements it. - // 2, h3server will only close listeners it's registered (quic listeners). Closing - // listener first and these listeners maybe unregistered thus won't be closed. caddy - // distinguishes quic-listener and underlying datagram sockets. - - // TODO: CloseGracefully, once implemented upstream (see https://github.com/quic-go/quic-go/issues/2103) - if err := server.h3server.Close(); err != nil { + if err := server.h3server.Shutdown(ctx); err != nil { app.logger.Error("HTTP/3 server shutdown", zap.Error(err), zap.Strings("addresses", server.Listen)) diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go index 5aa7e0f6351..96001c6f9c7 100644 --- a/modules/caddyhttp/server.go +++ b/modules/caddyhttp/server.go @@ -614,22 +614,7 @@ func (s *Server) serveHTTP3(addr caddy.NetworkAddress, tlsCfg *tls.Config) error // create HTTP/3 server if not done already if s.h3server == nil { s.h3server = &http3.Server{ - // Currently when closing a http3.Server, only listeners are closed. But caddy reuses these listeners - // if possible, requests are still read and handled by the old handler. Close these connections manually. - // see issue: https://github.com/caddyserver/caddy/issues/6195 - // Will interrupt ongoing requests. - // TODO: remove the handler wrap after http3.Server.CloseGracefully is implemented, see App.Stop - Handler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { - select { - case <-s.ctx.Done(): - if quicConn, ok := request.Context().Value(quicConnCtxKey).(quic.Connection); ok { - //nolint:errcheck - quicConn.CloseWithError(quic.ApplicationErrorCode(http3.ErrCodeRequestRejected), "") - } - default: - s.ServeHTTP(writer, request) - } - }), + Handler: s, TLSConfig: tlsCfg, MaxHeaderBytes: s.MaxHeaderBytes, QUICConfig: &quic.Config{ @@ -637,9 +622,6 @@ func (s *Server) serveHTTP3(addr caddy.NetworkAddress, tlsCfg *tls.Config) error Tracer: qlog.DefaultConnectionTracer, }, IdleTimeout: time.Duration(s.IdleTimeout), - ConnContext: func(ctx context.Context, c quic.Connection) context.Context { - return context.WithValue(ctx, quicConnCtxKey, c) - }, } } @@ -1099,10 +1081,6 @@ const ( // For referencing underlying net.Conn ConnCtxKey caddy.CtxKey = "conn" - // For referencing underlying quic.Connection - // TODO: export if needed later - quicConnCtxKey caddy.CtxKey = "quic_conn" - // For tracking whether the client is a trusted proxy TrustedProxyVarKey string = "trusted_proxy" From 388c7e898c6cbcddd2c59e8a902238a0b4f06857 Mon Sep 17 00:00:00 2001 From: Mohammed Al Sahaf Date: Fri, 18 Oct 2024 18:54:21 +0300 Subject: [PATCH 30/39] metrics: move `metrics` up, outside `servers` (#6606) * metrics: move `metrics` up, outside `servers` This change moves the metrics configuration from per-server level to a single config knob within the `http` app. Enabling `metrics` in any of the configured servers inside `http` enables metrics for all servers. Fix #6604 Signed-off-by: Mohammed Al Sahaf * normalize domain name --------- Signed-off-by: Mohammed Al Sahaf --- caddyconfig/httpcaddyfile/httptype.go | 14 +++++++ caddyconfig/httpcaddyfile/options.go | 20 ++++++++++ caddyconfig/httpcaddyfile/serveroptions.go | 1 + .../metrics_merge_options.caddyfiletest | 39 +++++++++++++++++++ .../metrics_perhost.caddyfiletest | 8 ++-- modules/caddyhttp/app.go | 27 +++++++++---- modules/caddyhttp/metrics.go | 5 ++- modules/caddyhttp/server.go | 1 + 8 files changed, 102 insertions(+), 13 deletions(-) create mode 100644 caddytest/integration/caddyfile_adapt/metrics_merge_options.caddyfiletest diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go index 4dacd9058d0..a238a33b42f 100644 --- a/caddyconfig/httpcaddyfile/httptype.go +++ b/caddyconfig/httpcaddyfile/httptype.go @@ -15,6 +15,7 @@ package httpcaddyfile import ( + "cmp" "encoding/json" "fmt" "net" @@ -186,12 +187,25 @@ func (st ServerType) Setup( return nil, warnings, err } + // hoist the metrics config from per-server to global + metrics, _ := options["metrics"].(*caddyhttp.Metrics) + for _, s := range servers { + if s.Metrics != nil { + metrics = cmp.Or[*caddyhttp.Metrics](metrics, &caddyhttp.Metrics{}) + metrics = &caddyhttp.Metrics{ + PerHost: metrics.PerHost || s.Metrics.PerHost, + } + s.Metrics = nil // we don't need it anymore + } + } + // now that each server is configured, make the HTTP app httpApp := caddyhttp.App{ HTTPPort: tryInt(options["http_port"], &warnings), HTTPSPort: tryInt(options["https_port"], &warnings), GracePeriod: tryDuration(options["grace_period"], &warnings), ShutdownDelay: tryDuration(options["shutdown_delay"], &warnings), + Metrics: metrics, Servers: servers, } diff --git a/caddyconfig/httpcaddyfile/options.go b/caddyconfig/httpcaddyfile/options.go index 9bae760c01a..03b9ba230a6 100644 --- a/caddyconfig/httpcaddyfile/options.go +++ b/caddyconfig/httpcaddyfile/options.go @@ -24,6 +24,7 @@ import ( "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" "github.com/caddyserver/caddy/v2/modules/caddytls" ) @@ -53,6 +54,7 @@ func init() { RegisterGlobalOption("local_certs", parseOptTrue) RegisterGlobalOption("key_type", parseOptSingleString) RegisterGlobalOption("auto_https", parseOptAutoHTTPS) + RegisterGlobalOption("metrics", parseMetricsOptions) RegisterGlobalOption("servers", parseServerOptions) RegisterGlobalOption("ocsp_stapling", parseOCSPStaplingOptions) RegisterGlobalOption("cert_lifetime", parseOptDuration) @@ -446,6 +448,24 @@ func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) { return val, nil } +func unmarshalCaddyfileMetricsOptions(d *caddyfile.Dispenser) (any, error) { + d.Next() // consume option name + metrics := new(caddyhttp.Metrics) + for d.NextBlock(0) { + switch d.Val() { + case "per_host": + metrics.PerHost = true + default: + return nil, d.Errf("unrecognized servers option '%s'", d.Val()) + } + } + return metrics, nil +} + +func parseMetricsOptions(d *caddyfile.Dispenser, _ any) (any, error) { + return unmarshalCaddyfileMetricsOptions(d) +} + func parseServerOptions(d *caddyfile.Dispenser, _ any) (any, error) { return unmarshalCaddyfileServerOptions(d) } diff --git a/caddyconfig/httpcaddyfile/serveroptions.go b/caddyconfig/httpcaddyfile/serveroptions.go index b05af47f547..40a8af20962 100644 --- a/caddyconfig/httpcaddyfile/serveroptions.go +++ b/caddyconfig/httpcaddyfile/serveroptions.go @@ -240,6 +240,7 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) { } case "metrics": + caddy.Log().Warn("The nested 'metrics' option inside `servers` is deprecated and will be removed in the next major version. Use the global 'metrics' option instead.") serverOpts.Metrics = new(caddyhttp.Metrics) for nesting := d.Nesting(); d.NextBlock(nesting); { switch d.Val() { diff --git a/caddytest/integration/caddyfile_adapt/metrics_merge_options.caddyfiletest b/caddytest/integration/caddyfile_adapt/metrics_merge_options.caddyfiletest new file mode 100644 index 00000000000..946b3d0c83a --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/metrics_merge_options.caddyfiletest @@ -0,0 +1,39 @@ +{ + metrics + servers :80 { + metrics { + per_host + } + } +} +:80 { + respond "Hello" +} + +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "handle": [ + { + "body": "Hello", + "handler": "static_response" + } + ] + } + ] + } + }, + "metrics": { + "per_host": true + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/metrics_perhost.caddyfiletest b/caddytest/integration/caddyfile_adapt/metrics_perhost.caddyfiletest index 49921551573..e362cecc93e 100644 --- a/caddytest/integration/caddyfile_adapt/metrics_perhost.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/metrics_perhost.caddyfiletest @@ -26,11 +26,11 @@ } ] } - ], - "metrics": { - "per_host": true - } + ] } + }, + "metrics": { + "per_host": true } } } diff --git a/modules/caddyhttp/app.go b/modules/caddyhttp/app.go index cd32e72d762..2d221265fa9 100644 --- a/modules/caddyhttp/app.go +++ b/modules/caddyhttp/app.go @@ -15,6 +15,7 @@ package caddyhttp import ( + "cmp" "context" "crypto/tls" "fmt" @@ -142,6 +143,10 @@ type App struct { // affect functionality. Servers map[string]*Server `json:"servers,omitempty"` + // If set, metrics observations will be enabled. + // This setting is EXPERIMENTAL and subject to change. + Metrics *Metrics `json:"metrics,omitempty"` + ctx caddy.Context logger *zap.Logger tlsApp *caddytls.TLS @@ -184,6 +189,10 @@ func (app *App) Provision(ctx caddy.Context) error { return err } + if app.Metrics != nil { + app.Metrics.init = sync.Once{} + app.Metrics.httpMetrics = &httpMetrics{} + } // prepare each server oldContext := ctx.Context for srvName, srv := range app.Servers { @@ -196,6 +205,15 @@ func (app *App) Provision(ctx caddy.Context) error { srv.errorLogger = app.logger.Named("log.error") srv.shutdownAtMu = new(sync.RWMutex) + if srv.Metrics != nil { + srv.logger.Warn("per-server 'metrics' is deprecated; use 'metrics' in the root 'http' app instead") + app.Metrics = cmp.Or[*Metrics](app.Metrics, &Metrics{ + init: sync.Once{}, + httpMetrics: &httpMetrics{}, + }) + app.Metrics.PerHost = app.Metrics.PerHost || srv.Metrics.PerHost + } + // only enable access logs if configured if srv.Logs != nil { srv.accessLogger = app.logger.Named("log.access") @@ -342,16 +360,11 @@ func (app *App) Provision(ctx caddy.Context) error { srv.listenerWrappers = append([]caddy.ListenerWrapper{new(tlsPlaceholderWrapper)}, srv.listenerWrappers...) } } - // 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 { - if srv.Metrics != nil { - srv.Metrics.init = sync.Once{} - srv.Metrics.httpMetrics = &httpMetrics{} - } - err := srv.Routes.ProvisionHandlers(ctx, srv.Metrics) + err := srv.Routes.ProvisionHandlers(ctx, app.Metrics) if err != nil { return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err) } @@ -370,7 +383,7 @@ func (app *App) Provision(ctx caddy.Context) error { // provision the named routes (they get compiled at runtime) for name, route := range srv.NamedRoutes { - err := route.Provision(ctx, srv.Metrics) + err := route.Provision(ctx, app.Metrics) if err != nil { return fmt.Errorf("server %s: setting up named route '%s' handlers: %v", name, srvName, err) } diff --git a/modules/caddyhttp/metrics.go b/modules/caddyhttp/metrics.go index 947721429f4..9bb97e0b47b 100644 --- a/modules/caddyhttp/metrics.go +++ b/modules/caddyhttp/metrics.go @@ -4,6 +4,7 @@ import ( "context" "errors" "net/http" + "strings" "sync" "time" @@ -133,8 +134,8 @@ func (h *metricsInstrumentedHandler) ServeHTTP(w http.ResponseWriter, r *http.Re statusLabels := prometheus.Labels{"server": server, "handler": h.handler, "method": method, "code": ""} if h.metrics.PerHost { - labels["host"] = r.Host - statusLabels["host"] = r.Host + labels["host"] = strings.ToLower(r.Host) + statusLabels["host"] = strings.ToLower(r.Host) } inFlight := h.metrics.httpMetrics.requestInFlight.With(labels) diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go index 96001c6f9c7..24fecfd887e 100644 --- a/modules/caddyhttp/server.go +++ b/modules/caddyhttp/server.go @@ -226,6 +226,7 @@ type Server struct { // If set, metrics observations will be enabled. // This setting is EXPERIMENTAL and subject to change. + // DEPRECATED: Use the app-level `metrics` field. Metrics *Metrics `json:"metrics,omitempty"` name string From b443190b66055089d1e112abc992512ca4f6bfba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sat, 19 Oct 2024 15:33:50 +0200 Subject: [PATCH 31/39] sigtrap: always ignore SIGPIPE (#6645) --- sigtrap_posix.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sigtrap_posix.go b/sigtrap_posix.go index 7033f16357e..2c630612114 100644 --- a/sigtrap_posix.go +++ b/sigtrap_posix.go @@ -28,6 +28,10 @@ import ( // trapSignalsPosix captures POSIX-only signals. func trapSignalsPosix() { + // Ignore all SIGPIPE signals to prevent weird issues with systemd: https://github.com/dunglas/frankenphp/issues/1020 + // Docker/Moby has a similar hack: https://github.com/moby/moby/blob/d828b032a87606ae34267e349bf7f7ccb1f6495a/cmd/dockerd/docker.go#L87-L90 + signal.Ignore(syscall.SIGPIPE) + go func() { sigchan := make(chan os.Signal, 1) signal.Notify(sigchan, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2) From 9753c4451077d4459ec10cb3df27ab9dc4456290 Mon Sep 17 00:00:00 2001 From: Logan Fleur Date: Sun, 20 Oct 2024 16:08:30 +0200 Subject: [PATCH 32/39] fileserver: fix try_policy when instantiating file matcher from CEL (#6624) Co-authored-by: Francis Lavoie --- modules/caddyhttp/fileserver/matcher.go | 2 +- modules/caddyhttp/fileserver/matcher_test.go | 20 +++++++++++++++++++ .../caddyhttp/fileserver/testdata/large.txt | 3 +++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 modules/caddyhttp/fileserver/testdata/large.txt diff --git a/modules/caddyhttp/fileserver/matcher.go b/modules/caddyhttp/fileserver/matcher.go index 71de1db297c..28f7b89be5f 100644 --- a/modules/caddyhttp/fileserver/matcher.go +++ b/modules/caddyhttp/fileserver/matcher.go @@ -191,7 +191,7 @@ func (MatchFile) CELLibrary(ctx caddy.Context) (cel.Library, error) { var try_policy string if len(values["try_policy"]) > 0 { - root = values["try_policy"][0] + try_policy = values["try_policy"][0] } m := MatchFile{ diff --git a/modules/caddyhttp/fileserver/matcher_test.go b/modules/caddyhttp/fileserver/matcher_test.go index 527c16bd1de..95eeb821663 100644 --- a/modules/caddyhttp/fileserver/matcher_test.go +++ b/modules/caddyhttp/fileserver/matcher_test.go @@ -289,6 +289,7 @@ var expressionTests = []struct { wantErr bool wantResult bool clientCertificate []byte + expectedPath string }{ { name: "file error no args (MatchFile)", @@ -354,6 +355,15 @@ var expressionTests = []struct { urlTarget: "https://example.com/nopenope.txt", wantResult: false, }, + { + name: "file match long pattern foo.txt with try_policy (MatchFile)", + expression: &caddyhttp.MatchExpression{ + Expr: `file({"root": "./testdata", "try_policy": "largest_size", "try_files": ["foo.txt", "large.txt"]})`, + }, + urlTarget: "https://example.com/", + wantResult: true, + expectedPath: "/large.txt", + }, } func TestMatchExpressionMatch(t *testing.T) { @@ -382,6 +392,16 @@ func TestMatchExpressionMatch(t *testing.T) { if tc.expression.Match(req) != tc.wantResult { t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tc.wantResult, tc.expression.Expr) } + + if tc.expectedPath != "" { + path, ok := repl.Get("http.matchers.file.relative") + if !ok { + t.Errorf("MatchExpression.Match() expected to return path '%s', but got none", tc.expectedPath) + } + if path != tc.expectedPath { + t.Errorf("MatchExpression.Match() expected to return path '%s', but got '%s'", tc.expectedPath, path) + } + } }) } } diff --git a/modules/caddyhttp/fileserver/testdata/large.txt b/modules/caddyhttp/fileserver/testdata/large.txt new file mode 100644 index 00000000000..c3662374432 --- /dev/null +++ b/modules/caddyhttp/fileserver/testdata/large.txt @@ -0,0 +1,3 @@ +This is a file with more content than the other files in this directory +such that tests using the largest_size policy pick this file, or the +smallest_size policy avoids this file. \ No newline at end of file From 1391e8ed9ae16e233c904b31fe4da2d687149820 Mon Sep 17 00:00:00 2001 From: David Wood Date: Mon, 21 Oct 2024 15:39:58 +0800 Subject: [PATCH 33/39] chore: fix some function names in comment (#6650) --- caddyconfig/httpcaddyfile/addresses.go | 2 +- caddytest/integration/mockdns_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/caddyconfig/httpcaddyfile/addresses.go b/caddyconfig/httpcaddyfile/addresses.go index 1c331eadc6a..c9057ab4e6a 100644 --- a/caddyconfig/httpcaddyfile/addresses.go +++ b/caddyconfig/httpcaddyfile/addresses.go @@ -31,7 +31,7 @@ import ( "github.com/caddyserver/caddy/v2/modules/caddyhttp" ) -// mapAddressToServerBlocks returns a map of listener address to list of server +// mapAddressToProtocolToServerBlocks returns a map of listener address to list of server // blocks that will be served on that address. To do this, each server block is // expanded so that each one is considered individually, although keys of a // server block that share the same address stay grouped together so the config diff --git a/caddytest/integration/mockdns_test.go b/caddytest/integration/mockdns_test.go index 1b2efb65328..615116a3aec 100644 --- a/caddytest/integration/mockdns_test.go +++ b/caddytest/integration/mockdns_test.go @@ -34,7 +34,7 @@ func (MockDNSProvider) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { return nil } -// AppendsRecords appends DNS records to the zone. +// AppendRecords appends DNS records to the zone. func (MockDNSProvider) AppendRecords(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) { return nil, nil } From 0182fb87fa7276463086c2360431a1c0dc797edf Mon Sep 17 00:00:00 2001 From: Aaron Paterson Date: Mon, 21 Oct 2024 10:02:29 -0400 Subject: [PATCH 34/39] core: addresses.go funcs renames (#6622) * right side in tls ln * remove ParseNetworkAddressFromHostPort * ignore placeholder port * remove println * update test cases (!!!) * [] * comment * Trim * Update addresses.go --- caddyconfig/httpcaddyfile/addresses.go | 8 +++- listeners.go | 52 +++++++++----------------- listeners_test.go | 12 +++--- 3 files changed, 31 insertions(+), 41 deletions(-) diff --git a/caddyconfig/httpcaddyfile/addresses.go b/caddyconfig/httpcaddyfile/addresses.go index c9057ab4e6a..1121776d98f 100644 --- a/caddyconfig/httpcaddyfile/addresses.go +++ b/caddyconfig/httpcaddyfile/addresses.go @@ -329,8 +329,12 @@ func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock, addr Ad // use a map to prevent duplication listeners := map[string]map[string]struct{}{} for _, lnCfgVal := range lnCfgVals { - for _, lnHost := range lnCfgVal.addresses { - networkAddr, err := caddy.ParseNetworkAddressFromHostPort(lnHost, lnPort) + for _, lnAddr := range lnCfgVal.addresses { + lnNetw, lnHost, _, err := caddy.SplitNetworkAddress(lnAddr) + if err != nil { + return nil, fmt.Errorf("splitting listener address: %v", err) + } + networkAddr, err := caddy.ParseNetworkAddress(caddy.JoinNetworkAddress(lnNetw, lnHost, lnPort)) if err != nil { return nil, fmt.Errorf("parsing network address: %v", err) } diff --git a/listeners.go b/listeners.go index 3a2a5180f6f..8a40d46efdf 100644 --- a/listeners.go +++ b/listeners.go @@ -305,25 +305,6 @@ func IsFdNetwork(netw string) bool { return strings.HasPrefix(netw, "fd") } -// normally we would simply append the port, -// but if host is IPv6, we need to ensure it -// is enclosed in [ ]; net.JoinHostPort does -// this for us, but host might also have a -// network type in front (e.g. "tcp/") leading -// to "[tcp/::1]" which causes parsing failures -// later; what we need is "tcp/[::1]", so we have -// to split the network and host, then re-combine -func ParseNetworkAddressFromHostPort(host, port string) (NetworkAddress, error) { - network, addr, ok := strings.Cut(host, "/") - if !ok { - addr = network - network = "" - } - addr = strings.Trim(addr, "[]") // IPv6 - networkAddr := JoinNetworkAddress(network, addr, port) - return ParseNetworkAddress(networkAddr) -} - // ParseNetworkAddress parses addr into its individual // components. The input string is expected to be of // the form "network/host:port-range" where any part is @@ -399,25 +380,28 @@ func SplitNetworkAddress(a string) (network, host, port string, err error) { if slashFound { network = strings.ToLower(strings.TrimSpace(beforeSlash)) a = afterSlash + if IsUnixNetwork(network) || IsFdNetwork(network) { + host = a + return + } } - if IsUnixNetwork(network) || IsFdNetwork(network) { - host = a - return - } + host, port, err = net.SplitHostPort(a) - if err == nil || a == "" { - return - } - // in general, if there was an error, it was likely "missing port", - // so try adding a bogus port to take advantage of standard library's - // robust parser, then strip the artificial port before returning - // (don't overwrite original error though; might still be relevant) - var err2 error - host, port, err2 = net.SplitHostPort(a + ":0") - if err2 == nil { - err = nil + firstErr := err + + if err != nil { + // in general, if there was an error, it was likely "missing port", + // so try removing square brackets around an IPv6 host, adding a bogus + // port to take advantage of standard library's robust parser, then + // strip the artificial port. + host, _, err = net.SplitHostPort(net.JoinHostPort(strings.Trim(a, "[]"), "0")) port = "" } + + if err != nil { + err = errors.Join(firstErr, err) + } + return } diff --git a/listeners_test.go b/listeners_test.go index f8a13cafc0c..03945308e40 100644 --- a/listeners_test.go +++ b/listeners_test.go @@ -31,7 +31,7 @@ func TestSplitNetworkAddress(t *testing.T) { }{ { input: "", - expectErr: true, + expectHost: "", }, { input: "foo", @@ -42,7 +42,7 @@ func TestSplitNetworkAddress(t *testing.T) { }, { input: "::", - expectErr: true, + expectHost: "::", }, { input: "[::]", @@ -77,7 +77,7 @@ func TestSplitNetworkAddress(t *testing.T) { { input: "udp/", expectNetwork: "udp", - expectErr: true, + expectHost: "", }, { input: "unix//foo/bar", @@ -185,7 +185,8 @@ func TestParseNetworkAddress(t *testing.T) { }{ { input: "", - expectErr: true, + expectAddr: NetworkAddress{ + }, }, { input: ":", @@ -311,7 +312,8 @@ func TestParseNetworkAddressWithDefaults(t *testing.T) { }{ { input: "", - expectErr: true, + expectAddr: NetworkAddress{ + }, }, { input: ":", From 669fc41e6321115df554d5f7d8eb55fdfbdaab18 Mon Sep 17 00:00:00 2001 From: Yifan Yang Date: Tue, 22 Oct 2024 01:06:55 +0800 Subject: [PATCH 35/39] tracing: Add `spanID` field to access logs and `http.vars.span_id` placeholder (#6646) * logging: Add spanID field to access logs when tracing is enabled Signed-off-by: YifanYang6 * tracing: add `http.vars.span_id` placeholder when tracing is enabled Signed-off-by: YifanYang6 --------- Signed-off-by: YifanYang6 --- modules/caddyhttp/server_test.go | 9 ++++++--- modules/caddyhttp/tracing/tracer.go | 6 +++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/modules/caddyhttp/server_test.go b/modules/caddyhttp/server_test.go index 2c8033d45d2..53f35368f93 100644 --- a/modules/caddyhttp/server_test.go +++ b/modules/caddyhttp/server_test.go @@ -69,12 +69,13 @@ func TestServer_LogRequest(t *testing.T) { }`, buf.String()) } -func TestServer_LogRequest_WithTraceID(t *testing.T) { +func TestServer_LogRequest_WithTrace(t *testing.T) { s := &Server{} extra := new(ExtraLogFields) ctx := context.WithValue(context.Background(), ExtraLogFieldsCtxKey, extra) extra.Add(zap.String("traceID", "1234567890abcdef")) + extra.Add(zap.String("spanID", "12345678")) req := httptest.NewRequest(http.MethodGet, "/", nil).WithContext(ctx) rec := httptest.NewRecorder() @@ -93,7 +94,8 @@ func TestServer_LogRequest_WithTraceID(t *testing.T) { "msg":"handled request", "level":"info", "bytes_read":0, "duration":"50ms", "resp_headers": {}, "size":0, "status":0, "user_id":"", - "traceID":"1234567890abcdef" + "traceID":"1234567890abcdef", + "spanID":"12345678" }`, buf.String()) } @@ -144,12 +146,13 @@ func BenchmarkServer_LogRequest_NopLogger(b *testing.B) { } } -func BenchmarkServer_LogRequest_WithTraceID(b *testing.B) { +func BenchmarkServer_LogRequest_WithTrace(b *testing.B) { s := &Server{} extra := new(ExtraLogFields) ctx := context.WithValue(context.Background(), ExtraLogFieldsCtxKey, extra) extra.Add(zap.String("traceID", "1234567890abcdef")) + extra.Add(zap.String("spanID", "12345678")) req := httptest.NewRequest(http.MethodGet, "/", nil).WithContext(ctx) rec := httptest.NewRecorder() diff --git a/modules/caddyhttp/tracing/tracer.go b/modules/caddyhttp/tracing/tracer.go index 89c617bf4f0..261952aa65e 100644 --- a/modules/caddyhttp/tracing/tracer.go +++ b/modules/caddyhttp/tracing/tracer.go @@ -88,11 +88,15 @@ func (ot *openTelemetryWrapper) serveHTTP(w http.ResponseWriter, r *http.Request spanCtx := trace.SpanContextFromContext(ctx) if spanCtx.IsValid() { traceID := spanCtx.TraceID().String() + spanID := spanCtx.SpanID().String() // Add a trace_id placeholder, accessible via `{http.vars.trace_id}`. caddyhttp.SetVar(ctx, "trace_id", traceID) - // Add the trace id to the log fields for the request. + // Add a span_id placeholder, accessible via `{http.vars.span_id}`. + caddyhttp.SetVar(ctx, "span_id", spanID) + // Add the traceID and spanID to the log fields for the request. if extra, ok := ctx.Value(caddyhttp.ExtraLogFieldsCtxKey).(*caddyhttp.ExtraLogFields); ok { extra.Add(zap.String("traceID", traceID)) + extra.Add(zap.String("spanID", spanID)) } } next := ctx.Value(nextCallCtxKey).(*nextCall) From 5e6024c48da68492761837af3806be1951fa4c24 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Mon, 21 Oct 2024 12:19:04 -0600 Subject: [PATCH 36/39] reverseproxy: Fix log message Fixes regression from #6560 --- modules/caddyhttp/reverseproxy/streaming.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/caddyhttp/reverseproxy/streaming.go b/modules/caddyhttp/reverseproxy/streaming.go index 3fde10b35f2..91af7c263bb 100644 --- a/modules/caddyhttp/reverseproxy/streaming.go +++ b/modules/caddyhttp/reverseproxy/streaming.go @@ -103,7 +103,7 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup, start := time.Now() defer func() { conn.Close() - if c := logger.Check(zapcore.DebugLevel, "hijack failed on protocol switch"); c != nil { + if c := logger.Check(zapcore.DebugLevel, "connection closed"); c != nil { c.Write(zap.Duration("duration", time.Since(start))) } }() From fbf0f4c425b23c39dbddd975d36df506cca5f45b Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Tue, 22 Oct 2024 14:10:46 -0400 Subject: [PATCH 37/39] reverseproxy: Sync changes from stdlib for 1xx handling (#6656) * reverseproxy: Sync changes from stdlib for 1xx handling Sourced from https://github.com/golang/go/commit/960654be0c4ad7918376e2e1d47491c9bc7520e0 * Use clear() https://github.com/golang/go/commit/3bc28402fae2a1646e4d2756344b5eb34994d25f --- modules/caddyhttp/headers/headers.go | 4 +-- .../caddyhttp/reverseproxy/reverseproxy.go | 26 +++++++++++++++---- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/modules/caddyhttp/headers/headers.go b/modules/caddyhttp/headers/headers.go index a3279d913e1..c66bd414497 100644 --- a/modules/caddyhttp/headers/headers.go +++ b/modules/caddyhttp/headers/headers.go @@ -200,9 +200,7 @@ func (ops HeaderOps) ApplyTo(hdr http.Header, repl *caddy.Replacer) { for _, fieldName := range ops.Delete { fieldName = repl.ReplaceKnown(fieldName, "") if fieldName == "*" { - for existingField := range hdr { - delete(hdr, existingField) - } + clear(hdr) } } diff --git a/modules/caddyhttp/reverseproxy/reverseproxy.go b/modules/caddyhttp/reverseproxy/reverseproxy.go index 123bf774b33..1250eae6c13 100644 --- a/modules/caddyhttp/reverseproxy/reverseproxy.go +++ b/modules/caddyhttp/reverseproxy/reverseproxy.go @@ -807,17 +807,26 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origRe shouldLogCredentials := server.Logs != nil && server.Logs.ShouldLogCredentials // Forward 1xx status codes, backported from https://github.com/golang/go/pull/53164 + var ( + roundTripMutex sync.Mutex + roundTripDone bool + ) trace := &httptrace.ClientTrace{ Got1xxResponse: func(code int, header textproto.MIMEHeader) error { + roundTripMutex.Lock() + defer roundTripMutex.Unlock() + if roundTripDone { + // If RoundTrip has returned, don't try to further modify + // the ResponseWriter's header map. + return nil + } h := rw.Header() copyHeader(h, http.Header(header)) rw.WriteHeader(code) // Clear headers coming from the backend // (it's not automatically done by ResponseWriter.WriteHeader() for 1xx responses) - for k := range header { - delete(h, k) - } + clear(h) return nil }, @@ -833,11 +842,18 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origRe req = req.WithContext(context.WithoutCancel(req.Context())) } - // do the round-trip; emit debug log with values we know are - // safe, or if there is no error, emit fuller log entry + // do the round-trip start := time.Now() res, err := h.Transport.RoundTrip(req) duration := time.Since(start) + + // record that the round trip is done for the 1xx response handler + roundTripMutex.Lock() + roundTripDone = true + roundTripMutex.Unlock() + + // emit debug log with values we know are safe, + // or if there is no error, emit fuller log entry logger := h.logger.With( zap.String("upstream", di.Upstream.String()), zap.Duration("duration", duration), From 4457afc170cb2f42d8f361de8b5d2668d80232ac Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Tue, 22 Oct 2024 15:29:46 -0400 Subject: [PATCH 38/39] chore: Bump quic-go to 0.48.1, fixing a panic (#6654) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 67fe8e59c89..d847c7bfbef 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/klauspost/cpuid/v2 v2.2.8 github.com/mholt/acmez/v2 v2.0.3 github.com/prometheus/client_golang v1.19.1 - github.com/quic-go/quic-go v0.48.0 + github.com/quic-go/quic-go v0.48.1 github.com/smallstep/certificates v0.26.1 github.com/smallstep/nosql v0.6.1 github.com/smallstep/truststore v0.13.0 diff --git a/go.sum b/go.sum index 0956dffd2f0..794ad6fc80b 100644 --- a/go.sum +++ b/go.sum @@ -392,8 +392,8 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.48.0 h1:2TCyvBrMu1Z25rvIAlnp2dPT4lgh/uTqLqiXVpp5AeU= -github.com/quic-go/quic-go v0.48.0/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= +github.com/quic-go/quic-go v0.48.1 h1:y/8xmfWI9qmGTc+lBr4jKRUWLGSlSigv847ULJ4hYXA= +github.com/quic-go/quic-go v0.48.1/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= From eaaa2e5872ef9e845a50c6aade36676c0ecfe2e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 22 Oct 2024 22:53:14 +0200 Subject: [PATCH 39/39] chore: compile without nosql's support for Postgres and MySQL (#6655) * chore: compile without nosql's support for Postgres and MySQL * Update cross-build.yml * Update cross-build.yml * Update README.md --- .github/workflows/cross-build.yml | 2 +- .goreleaser.yml | 2 ++ README.md | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cross-build.yml b/.github/workflows/cross-build.yml index e77e4e992d0..af0394603ec 100644 --- a/.github/workflows/cross-build.yml +++ b/.github/workflows/cross-build.yml @@ -70,4 +70,4 @@ jobs: continue-on-error: true working-directory: ./cmd/caddy run: | - GOOS=$GOOS GOARCH=$GOARCH go build -tags nobadger -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null + GOOS=$GOOS GOARCH=$GOARCH go build -tags=nobadger,nomysql,nopgx -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null diff --git a/.goreleaser.yml b/.goreleaser.yml index 5c1f7df407f..37614e75f4a 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -83,6 +83,8 @@ builds: - -s -w tags: - nobadger + - nomysql + - nopgx signs: - cmd: cosign diff --git a/README.md b/README.md index 2185eccd8cf..abc136f68e8 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ $ xcaddy build 4. Initialize a Go module: `go mod init caddy` 5. (Optional) Pin Caddy version: `go get github.com/caddyserver/caddy/v2@version` replacing `version` with a git tag, commit, or branch name. 6. (Optional) Add plugins by adding their import: `_ "import/path/here"` -7. Compile: `go build` +7. Compile: `go build -tags=nobadger,nomysql,nopgx`