diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a006d26b35f3..2c7a67c5bff4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,18 +23,18 @@ jobs: - mac - windows go: - - '1.21' - '1.22' + - '1.23' include: # Set the minimum Go patch version for the given Go minor # Usable via ${{ matrix.GO_SEMVER }} - - go: '1.21' - GO_SEMVER: '~1.21.0' - - go: '1.22' GO_SEMVER: '~1.22.3' + - go: '1.23' + GO_SEMVER: '~1.23.0' + # Set some variables per OS, usable via ${{ matrix.VAR }} # OS_LABEL: the VM label from GitHub Actions (see https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories) # CADDY_BIN_PATH: the path to the compiled Caddy binary, for artifact publishing @@ -150,6 +150,7 @@ jobs: uses: actions/checkout@v4 - name: Run Tests run: | + set +e mkdir -p ~/.ssh && echo -e "${SSH_KEY//_/\\n}" > ~/.ssh/id_ecdsa && chmod og-rwx ~/.ssh/id_ecdsa # short sha is enough? diff --git a/.github/workflows/cross-build.yml b/.github/workflows/cross-build.yml index c54a9a80dbfb..e77e4e992d03 100644 --- a/.github/workflows/cross-build.yml +++ b/.github/workflows/cross-build.yml @@ -28,6 +28,7 @@ jobs: - 'netbsd' go: - '1.22' + - '1.23' include: # Set the minimum Go patch version for the given Go minor @@ -35,6 +36,9 @@ jobs: - go: '1.22' GO_SEMVER: '~1.22.3' + - go: '1.23' + GO_SEMVER: '~1.23.0' + runs-on: ubuntu-latest continue-on-error: true steps: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1ebdb0afea1f..94250f88f35e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -43,13 +43,13 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version: '~1.22.3' + go-version: '~1.23' check-latest: true - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: - version: v1.55 + version: v1.60 # Windows times out frequently after about 5m50s if we don't set a longer timeout. args: --timeout 10m @@ -63,5 +63,5 @@ jobs: - name: govulncheck uses: golang/govulncheck-action@v1 with: - go-version-input: '~1.22.3' + go-version-input: '~1.23.0' check-latest: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cb5d750dcb6b..1eb59e9d02d6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,13 +13,13 @@ jobs: os: - ubuntu-latest go: - - '1.22' + - '1.23' include: # Set the minimum Go patch version for the given Go minor # Usable via ${{ matrix.GO_SEMVER }} - - go: '1.22' - GO_SEMVER: '~1.22.3' + - go: '1.23' + GO_SEMVER: '~1.23.0' runs-on: ${{ matrix.os }} # https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233 diff --git a/README.md b/README.md index 57463180fa33..2185eccd8cf8 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ See [our online documentation](https://caddyserver.com/docs/install) for other i Requirements: -- [Go 1.21 or newer](https://golang.org/dl/) +- [Go 1.22.3 or newer](https://golang.org/dl/) ### For development diff --git a/caddyconfig/caddyfile/dispenser.go b/caddyconfig/caddyfile/dispenser.go index e36275a1c1bf..325bb54d3f3a 100644 --- a/caddyconfig/caddyfile/dispenser.go +++ b/caddyconfig/caddyfile/dispenser.go @@ -415,7 +415,7 @@ func (d *Dispenser) EOFErr() error { // Err generates a custom parse-time error with a message of msg. func (d *Dispenser) Err(msg string) error { - return d.Errf(msg) + return d.WrapErr(errors.New(msg)) } // Errf is like Err, but for formatted error messages diff --git a/caddytest/caddytest_test.go b/caddytest/caddytest_test.go index 937537faa7c2..a9d5da9365b6 100644 --- a/caddytest/caddytest_test.go +++ b/caddytest/caddytest_test.go @@ -84,7 +84,6 @@ func TestLoadUnorderedJSON(t *testing.T) { "servers": { "s_server": { "listen": [ - ":9443", ":9080" ], "routes": [ diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_health_reqbody.caddyfiletest b/caddytest/integration/caddyfile_adapt/reverse_proxy_health_reqbody.caddyfiletest new file mode 100644 index 000000000000..ae5a6791ef7f --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_health_reqbody.caddyfiletest @@ -0,0 +1,40 @@ +:8884 + +reverse_proxy 127.0.0.1:65535 { + health_uri /health + health_request_body "test body" +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "health_checks": { + "active": { + "body": "test body", + "uri": "/health" + } + }, + "upstreams": [ + { + "dial": "127.0.0.1:65535" + } + ] + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_localaddr.caddyfiletest b/caddytest/integration/caddyfile_adapt/reverse_proxy_localaddr.caddyfiletest new file mode 100644 index 000000000000..d734c9ce09a0 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_localaddr.caddyfiletest @@ -0,0 +1,57 @@ +https://example.com { + reverse_proxy http://localhost:54321 { + transport http { + local_address 192.168.0.1 + } + } +} + +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "transport": { + "local_address": "192.168.0.1", + "protocol": "http" + }, + "upstreams": [ + { + "dial": "localhost:54321" + } + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} diff --git a/cmd/cobra.go b/cmd/cobra.go index 1a2509206a81..9ecb389e2aac 100644 --- a/cmd/cobra.go +++ b/cmd/cobra.go @@ -8,9 +8,10 @@ import ( "github.com/caddyserver/caddy/v2" ) -var rootCmd = &cobra.Command{ - Use: "caddy", - Long: `Caddy is an extensible server platform written in Go. +var defaultFactory = newRootCommandFactory(func() *cobra.Command { + return &cobra.Command{ + Use: "caddy", + Long: `Caddy is an extensible server platform written in Go. At its core, Caddy merely manages configuration. Modules are plugged in statically at compile-time to provide useful functionality. Caddy's @@ -91,23 +92,26 @@ package installers: https://caddyserver.com/docs/install Instructions for running Caddy in production are also available: https://caddyserver.com/docs/running `, - Example: ` $ caddy run + Example: ` $ caddy run $ caddy run --config caddy.json $ caddy reload --config caddy.json $ caddy stop`, - // kind of annoying to have all the help text printed out if - // caddy has an error provisioning its modules, for instance... - SilenceUsage: true, - Version: onlyVersionText(), -} + // kind of annoying to have all the help text printed out if + // caddy has an error provisioning its modules, for instance... + SilenceUsage: true, + Version: onlyVersionText(), + } +}) const fullDocsFooter = `Full documentation is available at: https://caddyserver.com/docs/command-line` func init() { - rootCmd.SetVersionTemplate("{{.Version}}\n") - rootCmd.SetHelpTemplate(rootCmd.HelpTemplate() + "\n" + fullDocsFooter + "\n") + defaultFactory.Use(func(rootCmd *cobra.Command) { + rootCmd.SetVersionTemplate("{{.Version}}\n") + rootCmd.SetHelpTemplate(rootCmd.HelpTemplate() + "\n" + fullDocsFooter + "\n") + }) } func onlyVersionText() string { diff --git a/cmd/commandfactory.go b/cmd/commandfactory.go new file mode 100644 index 000000000000..ac571a21ae1d --- /dev/null +++ b/cmd/commandfactory.go @@ -0,0 +1,28 @@ +package caddycmd + +import ( + "github.com/spf13/cobra" +) + +type rootCommandFactory struct { + constructor func() *cobra.Command + options []func(*cobra.Command) +} + +func newRootCommandFactory(fn func() *cobra.Command) *rootCommandFactory { + return &rootCommandFactory{ + constructor: fn, + } +} + +func (f *rootCommandFactory) Use(fn func(cmd *cobra.Command)) { + f.options = append(f.options, fn) +} + +func (f *rootCommandFactory) Build() *cobra.Command { + o := f.constructor() + for _, v := range f.options { + v(o) + } + return o +} diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go index 50048970f9ba..c7fddd6be5c9 100644 --- a/cmd/commandfuncs.go +++ b/cmd/commandfuncs.go @@ -74,6 +74,10 @@ func cmdStart(fl Flags) (int, error) { // sure by giving it some random bytes and having it echo // them back to us) cmd := exec.Command(os.Args[0], "run", "--pingback", ln.Addr().String()) + // we should be able to run caddy in relative paths + if errors.Is(cmd.Err, exec.ErrDot) { + cmd.Err = nil + } if configFlag != "" { cmd.Args = append(cmd.Args, "--config", configFlag) } diff --git a/cmd/commands.go b/cmd/commands.go index e5e1265e441d..0853ebf83590 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -438,43 +438,44 @@ EXPERIMENTAL: May be changed or removed. }, }) - RegisterCommand(Command{ - Name: "manpage", - Usage: "--directory ", - Short: "Generates the manual pages for Caddy commands", - Long: ` + defaultFactory.Use(func(rootCmd *cobra.Command) { + RegisterCommand(Command{ + Name: "manpage", + Usage: "--directory ", + Short: "Generates the manual pages for Caddy commands", + Long: ` Generates the manual pages for Caddy commands into the designated directory tagged into section 8 (System Administration). The manual page files are generated into the directory specified by the argument of --directory. If the directory does not exist, it will be created. `, - CobraFunc: func(cmd *cobra.Command) { - cmd.Flags().StringP("directory", "o", "", "The output directory where the manpages are generated") - cmd.RunE = WrapCommandFuncForCobra(func(fl Flags) (int, error) { - dir := strings.TrimSpace(fl.String("directory")) - if dir == "" { - return caddy.ExitCodeFailedQuit, fmt.Errorf("designated output directory and specified section are required") - } - if err := os.MkdirAll(dir, 0o755); err != nil { - return caddy.ExitCodeFailedQuit, err - } - if err := doc.GenManTree(rootCmd, &doc.GenManHeader{ - Title: "Caddy", - Section: "8", // https://en.wikipedia.org/wiki/Man_page#Manual_sections - }, dir); err != nil { - return caddy.ExitCodeFailedQuit, err - } - return caddy.ExitCodeSuccess, nil - }) - }, - }) - - // source: https://github.com/spf13/cobra/blob/main/shell_completions.md - rootCmd.AddCommand(&cobra.Command{ - Use: "completion [bash|zsh|fish|powershell]", - Short: "Generate completion script", - Long: fmt.Sprintf(`To load completions: + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("directory", "o", "", "The output directory where the manpages are generated") + cmd.RunE = WrapCommandFuncForCobra(func(fl Flags) (int, error) { + dir := strings.TrimSpace(fl.String("directory")) + if dir == "" { + return caddy.ExitCodeFailedQuit, fmt.Errorf("designated output directory and specified section are required") + } + if err := os.MkdirAll(dir, 0o755); err != nil { + return caddy.ExitCodeFailedQuit, err + } + if err := doc.GenManTree(rootCmd, &doc.GenManHeader{ + Title: "Caddy", + Section: "8", // https://en.wikipedia.org/wiki/Man_page#Manual_sections + }, dir); err != nil { + return caddy.ExitCodeFailedQuit, err + } + return caddy.ExitCodeSuccess, nil + }) + }, + }) + + // source: https://github.com/spf13/cobra/blob/main/shell_completions.md + rootCmd.AddCommand(&cobra.Command{ + Use: "completion [bash|zsh|fish|powershell]", + Short: "Generate completion script", + Long: fmt.Sprintf(`To load completions: Bash: @@ -513,23 +514,24 @@ argument of --directory. If the directory does not exist, it will be created. PS> %[1]s completion powershell > %[1]s.ps1 # and source this file from your PowerShell profile. `, rootCmd.Root().Name()), - DisableFlagsInUseLine: true, - ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, - Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), - RunE: func(cmd *cobra.Command, args []string) error { - switch args[0] { - case "bash": - return cmd.Root().GenBashCompletion(os.Stdout) - case "zsh": - return cmd.Root().GenZshCompletion(os.Stdout) - case "fish": - return cmd.Root().GenFishCompletion(os.Stdout, true) - case "powershell": - return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) - default: - return fmt.Errorf("unrecognized shell: %s", args[0]) - } - }, + DisableFlagsInUseLine: true, + ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, + Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), + RunE: func(cmd *cobra.Command, args []string) error { + switch args[0] { + case "bash": + return cmd.Root().GenBashCompletion(os.Stdout) + case "zsh": + return cmd.Root().GenZshCompletion(os.Stdout) + case "fish": + return cmd.Root().GenFishCompletion(os.Stdout, true) + case "powershell": + return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) + default: + return fmt.Errorf("unrecognized shell: %s", args[0]) + } + }, + }) }) } @@ -563,7 +565,9 @@ func RegisterCommand(cmd Command) { if !commandNameRegex.MatchString(cmd.Name) { panic("invalid command name") } - rootCmd.AddCommand(caddyCmdToCobra(cmd)) + defaultFactory.Use(func(rootCmd *cobra.Command) { + rootCmd.AddCommand(caddyCmdToCobra(cmd)) + }) } var commandNameRegex = regexp.MustCompile(`^[a-z0-9]$|^([a-z0-9]+-?[a-z0-9]*)+[a-z0-9]$`) diff --git a/cmd/main.go b/cmd/main.go index 3c3ae6270875..655c0084bea5 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -72,7 +72,7 @@ func Main() { caddy.Log().Warn("failed to set GOMAXPROCS", zap.Error(err)) } - if err := rootCmd.Execute(); err != nil { + if err := defaultFactory.Build().Execute(); err != nil { var exitError *exitError if errors.As(err, &exitError) { os.Exit(exitError.ExitCode) diff --git a/go.mod b/go.mod index 09b7b6e0f7a4..47027f5f16ff 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/caddyserver/caddy/v2 -go 1.21.0 +go 1.22.3 -toolchain go1.22.2 +toolchain go1.23.0 require ( github.com/BurntSushi/toml v1.3.2 diff --git a/modules/caddyhttp/celmatcher.go b/modules/caddyhttp/celmatcher.go index d4016478ec25..a5565eb9840d 100644 --- a/modules/caddyhttp/celmatcher.go +++ b/modules/caddyhttp/celmatcher.go @@ -340,7 +340,7 @@ func (celTypeAdapter) NativeToValue(value any) ref.Val { case time.Time: return types.Timestamp{Time: v} case error: - types.NewErr(v.Error()) + return types.WrapErr(v) } return types.DefaultTypeAdapter.NativeToValue(value) } @@ -499,7 +499,7 @@ func CELMatcherRuntimeFunction(funcName string, fac CELMatcherFactory) functions return func(celReq, matcherData ref.Val) ref.Val { matcher, err := fac(matcherData) if err != nil { - return types.NewErr(err.Error()) + return types.WrapErr(err) } httpReq := celReq.Value().(celHTTPRequest) return types.Bool(matcher.Match(httpReq.Request)) diff --git a/modules/caddyhttp/ip_range.go b/modules/caddyhttp/ip_range.go index 7632e460c28b..bfd76c14c3d4 100644 --- a/modules/caddyhttp/ip_range.go +++ b/modules/caddyhttp/ip_range.go @@ -128,3 +128,10 @@ var ( _ caddyfile.Unmarshaler = (*StaticIPRange)(nil) _ IPRangeSource = (*StaticIPRange)(nil) ) + +// PrivateRangesCIDR returns a list of private CIDR range +// strings, which can be used as a configuration shortcut. +// Note: this function is used at least by mholt/caddy-l4. +func PrivateRangesCIDR() []string { + return internal.PrivateRangesCIDR() +} diff --git a/modules/caddyhttp/proxyprotocol/listenerwrapper.go b/modules/caddyhttp/proxyprotocol/listenerwrapper.go index e25fe02a6e53..440e7071010a 100644 --- a/modules/caddyhttp/proxyprotocol/listenerwrapper.go +++ b/modules/caddyhttp/proxyprotocol/listenerwrapper.go @@ -40,7 +40,7 @@ type ListenerWrapper struct { Allow []string `json:"allow,omitempty"` allow []netip.Prefix - // Denby is an optional list of CIDR ranges to + // Deny is an optional list of CIDR ranges to // deny PROXY headers from. Deny []string `json:"deny,omitempty"` deny []netip.Prefix diff --git a/modules/caddyhttp/reverseproxy/caddyfile.go b/modules/caddyhttp/reverseproxy/caddyfile.go index 4ca5d0e0dafc..12e2b9b97a40 100644 --- a/modules/caddyhttp/reverseproxy/caddyfile.go +++ b/modules/caddyhttp/reverseproxy/caddyfile.go @@ -69,19 +69,20 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) // lb_retry_match // // # active health checking -// health_uri -// health_port -// health_interval -// health_passes -// health_fails -// health_timeout -// health_status -// health_body +// health_uri +// health_port +// health_interval +// health_passes +// health_fails +// health_timeout +// health_status +// health_body +// health_method +// health_request_body // health_follow_redirects // health_headers { // [] // } -// health_method // // # passive health checking // fail_duration @@ -425,6 +426,18 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { } h.HealthChecks.Active.Method = d.Val() + case "health_request_body": + if !d.NextArg() { + return d.ArgErr() + } + if h.HealthChecks == nil { + h.HealthChecks = new(HealthChecks) + } + if h.HealthChecks.Active == nil { + h.HealthChecks.Active = new(ActiveHealthChecks) + } + h.HealthChecks.Active.Body = d.Val() + case "health_interval": if !d.NextArg() { return d.ArgErr() @@ -1313,7 +1326,11 @@ func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { return d.Err("cannot specify \"tls_trust_pool\" twice in caddyfile") } h.TLS.CARaw = caddyconfig.JSONModuleObject(ca, "provider", modStem, nil) - + case "local_address": + if !d.NextArg() { + return d.ArgErr() + } + h.LocalAddress = d.Val() default: return d.Errf("unrecognized subdirective %s", d.Val()) } diff --git a/modules/caddyhttp/reverseproxy/healthchecks.go b/modules/caddyhttp/reverseproxy/healthchecks.go index 4d65bd27e337..6f8fe7fa5d0a 100644 --- a/modules/caddyhttp/reverseproxy/healthchecks.go +++ b/modules/caddyhttp/reverseproxy/healthchecks.go @@ -25,6 +25,7 @@ import ( "regexp" "runtime/debug" "strconv" + "strings" "time" "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" @@ -97,6 +98,9 @@ type ActiveHealthChecks struct { // The HTTP method to use for health checks (default "GET"). Method string `json:"method,omitempty"` + // The body to send with the health check request. + Body string `json:"body,omitempty"` + // Whether to follow HTTP redirects in response to active health checks (default off). FollowRedirects bool `json:"follow_redirects,omitempty"` @@ -405,6 +409,16 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, networ u.Path = h.HealthChecks.Active.Path } + // replacer used for both body and headers. Only globals (env vars, system info, etc.) are available + repl := caddy.NewReplacer() + + // if body is provided, create a reader for it, otherwise nil + var requestBody io.Reader + if h.HealthChecks.Active.Body != "" { + // set body, using replacer + requestBody = strings.NewReader(repl.ReplaceAll(h.HealthChecks.Active.Body, "")) + } + // attach dialing information to this request, as well as context values that // may be expected by handlers of this request ctx := h.ctx.Context @@ -412,6 +426,7 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, networ ctx = context.WithValue(ctx, caddyhttp.VarsCtxKey, map[string]any{ dialInfoVarKey: dialInfo, }) +<<<<<<< HEAD tr := otel.Tracer("reverseproxy") @@ -419,14 +434,16 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, networ defer span.End() req, err := http.NewRequestWithContext(ctx, h.HealthChecks.Active.Method, u.String(), nil) +======= + req, err := http.NewRequestWithContext(ctx, h.HealthChecks.Active.Method, u.String(), requestBody) +>>>>>>> upstream/master if err != nil { return fmt.Errorf("making request: %v", err) } ctx = context.WithValue(ctx, caddyhttp.OriginalRequestCtxKey, *req) req = req.WithContext(ctx) - // set headers, using a replacer with only globals (env vars, system info, etc.) - repl := caddy.NewReplacer() + // set headers, using replacer repl.Set("http.reverse_proxy.active.target_upstream", networkAddr) for key, vals := range h.HealthChecks.Active.Headers { key = repl.ReplaceAll(key, "") diff --git a/modules/caddyhttp/reverseproxy/httptransport.go b/modules/caddyhttp/reverseproxy/httptransport.go index 9a82341d070e..9929ae5d17ed 100644 --- a/modules/caddyhttp/reverseproxy/httptransport.go +++ b/modules/caddyhttp/reverseproxy/httptransport.go @@ -132,6 +132,10 @@ type HTTPTransport struct { // to change or removal while experimental. Versions []string `json:"versions,omitempty"` + // Specify the address to bind to when connecting to an upstream. In other words, + // it is the address the upstream sees as the remote address. + LocalAddress string `json:"local_address,omitempty"` + // The pre-configured underlying HTTP transport. Transport *http.Transport `json:"-"` @@ -185,6 +189,31 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e FallbackDelay: time.Duration(h.FallbackDelay), } + if h.LocalAddress != "" { + netaddr, err := caddy.ParseNetworkAddressWithDefaults(h.LocalAddress, "tcp", 0) + if err != nil { + return nil, err + } + if netaddr.PortRangeSize() > 1 { + return nil, fmt.Errorf("local_address must be a single address, not a port range") + } + switch netaddr.Network { + case "tcp", "tcp4", "tcp6": + dialer.LocalAddr, err = net.ResolveTCPAddr(netaddr.Network, netaddr.JoinHostPort(0)) + if err != nil { + return nil, err + } + case "unix", "unixgram", "unixpacket": + dialer.LocalAddr, err = net.ResolveUnixAddr(netaddr.Network, netaddr.JoinHostPort(0)) + if err != nil { + return nil, err + } + case "udp", "udp4", "udp6": + return nil, fmt.Errorf("local_address must be a TCP address, not a UDP address") + default: + return nil, fmt.Errorf("unsupported network") + } + } if h.Resolver != nil { err := h.Resolver.ParseAddresses() if err != nil { diff --git a/modules/caddyhttp/reverseproxy/reverseproxy.go b/modules/caddyhttp/reverseproxy/reverseproxy.go index d6c4422a2b49..3688fdb41b62 100644 --- a/modules/caddyhttp/reverseproxy/reverseproxy.go +++ b/modules/caddyhttp/reverseproxy/reverseproxy.go @@ -981,7 +981,7 @@ 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.Error("aborting with incomplete response", zap.Error(err)) + logger.Warn("aborting with incomplete response", zap.Error(err)) // no extra logging from stdlib panic(http.ErrAbortHandler) } diff --git a/modules/caddyhttp/staticerror.go b/modules/caddyhttp/staticerror.go index b6e10ff328c3..aeb31140667a 100644 --- a/modules/caddyhttp/staticerror.go +++ b/modules/caddyhttp/staticerror.go @@ -105,8 +105,7 @@ func (e StaticError) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler } statusCode = intVal } - - return Error(statusCode, fmt.Errorf("%s", e.Error)) + return Error(statusCode, fmt.Errorf("%s", repl.ReplaceKnown(e.Error, ""))) } // Interface guard diff --git a/modules/caddytls/matchers.go b/modules/caddytls/matchers.go index 83a464713819..f946223741b5 100644 --- a/modules/caddytls/matchers.go +++ b/modules/caddytls/matchers.go @@ -50,12 +50,13 @@ func (MatchServerName) CaddyModule() caddy.ModuleInfo { // Match matches hello based on SNI. func (m MatchServerName) Match(hello *tls.ClientHelloInfo) bool { + repl := caddy.NewReplacer() // caddytls.TestServerNameMatcher calls this function without any context - var repl *caddy.Replacer if ctx := hello.Context(); ctx != nil { - repl = ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer) - } else { - repl = caddy.NewReplacer() + // In some situations the existing context may have no replacer + if replAny := ctx.Value(caddy.ReplacerCtxKey); replAny != nil { + repl = replAny.(*caddy.Replacer) + } } for _, name := range m {