mirror of
https://github.com/caddyserver/caddy.git
synced 2025-04-19 10:49:17 +08:00
Merge branch 'master' into active-health-transport
This commit is contained in:
commit
2c8c0286be
79
admin.go
79
admin.go
@ -221,7 +221,8 @@ func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool, _ Co
|
||||
if remote {
|
||||
muxWrap.remoteControl = admin.Remote
|
||||
} else {
|
||||
muxWrap.enforceHost = !addr.isWildcardInterface()
|
||||
// see comment in allowedOrigins() as to why we disable the host check for unix/fd networks
|
||||
muxWrap.enforceHost = !addr.isWildcardInterface() && !addr.IsUnixNetwork() && !addr.IsFdNetwork()
|
||||
muxWrap.allowedOrigins = admin.allowedOrigins(addr)
|
||||
muxWrap.enforceOrigin = admin.EnforceOrigin
|
||||
}
|
||||
@ -310,47 +311,43 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
|
||||
for _, o := range admin.Origins {
|
||||
uniqueOrigins[o] = struct{}{}
|
||||
}
|
||||
if admin.Origins == nil {
|
||||
// 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
|
||||
// name for the service being requested, then the Host header field MUST
|
||||
// be given with an empty value."
|
||||
//
|
||||
// UPDATE July 2023: Go broke this by patching a minor security bug in 1.20.6.
|
||||
// Understandable, but frustrating. See:
|
||||
// https://github.com/golang/go/issues/60374
|
||||
// See also the discussion here:
|
||||
// https://github.com/golang/go/issues/61431
|
||||
//
|
||||
// We can no longer conform to RFC 2616 Section 14.26 from either Go or curl
|
||||
// in purity. (Curl allowed no host between 7.40 and 7.50, but now requires a
|
||||
// bogus host; see https://superuser.com/a/925610.) If we disable Host/Origin
|
||||
// security checks, the infosec community assures me that it is secure to do
|
||||
// so, because:
|
||||
//
|
||||
// 1) Browsers do not allow access to unix sockets
|
||||
// 2) DNS is irrelevant to unix sockets
|
||||
//
|
||||
// If either of those two statements ever fail to hold true, it is not the
|
||||
// fault of Caddy.
|
||||
//
|
||||
// Thus, we do not fill out allowed origins and do not enforce Host
|
||||
// requirements for unix sockets. Enforcing it leads to confusion and
|
||||
// frustration, when UDS have their own permissions from the OS.
|
||||
// Enforcing host requirements here is effectively security theater,
|
||||
// and a false sense of security.
|
||||
//
|
||||
// See also the discussion in #6832.
|
||||
if admin.Origins == nil && !addr.IsUnixNetwork() && !addr.IsFdNetwork() {
|
||||
if addr.isLoopback() {
|
||||
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
|
||||
// name for the service being requested, then the Host header field MUST
|
||||
// be given with an empty value."
|
||||
//
|
||||
// UPDATE July 2023: Go broke this by patching a minor security bug in 1.20.6.
|
||||
// Understandable, but frustrating. See:
|
||||
// https://github.com/golang/go/issues/60374
|
||||
// See also the discussion here:
|
||||
// https://github.com/golang/go/issues/61431
|
||||
//
|
||||
// We can no longer conform to RFC 2616 Section 14.26 from either Go or curl
|
||||
// in purity. (Curl allowed no host between 7.40 and 7.50, but now requires a
|
||||
// bogus host; see https://superuser.com/a/925610.) If we disable Host/Origin
|
||||
// security checks, the infosec community assures me that it is secure to do
|
||||
// so, because:
|
||||
// 1) Browsers do not allow access to unix sockets
|
||||
// 2) DNS is irrelevant to unix sockets
|
||||
//
|
||||
// I am not quite ready to trust either of those external factors, so instead
|
||||
// of disabling Host/Origin checks, we now allow specific Host values when
|
||||
// accessing the admin endpoint over unix sockets. I definitely don't trust
|
||||
// DNS (e.g. I don't trust 'localhost' to always resolve to the local host),
|
||||
// and IP shouldn't even be used, but if it is for some reason, I think we can
|
||||
// at least be reasonably assured that 127.0.0.1 and ::1 route to the local
|
||||
// machine, meaning that a hypothetical browser origin would have to be on the
|
||||
// local machine as well.
|
||||
uniqueOrigins[""] = struct{}{}
|
||||
uniqueOrigins["127.0.0.1"] = struct{}{}
|
||||
uniqueOrigins["::1"] = struct{}{}
|
||||
} else {
|
||||
uniqueOrigins[net.JoinHostPort("localhost", addr.port())] = struct{}{}
|
||||
uniqueOrigins[net.JoinHostPort("::1", addr.port())] = struct{}{}
|
||||
uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{}
|
||||
}
|
||||
}
|
||||
if !addr.IsUnixNetwork() && !addr.IsFdNetwork() {
|
||||
uniqueOrigins[net.JoinHostPort("localhost", addr.port())] = struct{}{}
|
||||
uniqueOrigins[net.JoinHostPort("::1", addr.port())] = struct{}{}
|
||||
uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{}
|
||||
} else {
|
||||
uniqueOrigins[addr.JoinHostPort(0)] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
@ -531,6 +531,7 @@ func TestAdminRouterProvisioning(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAllowedOriginsUnixSocket(t *testing.T) {
|
||||
// see comment in allowedOrigins() as to why we do not fill out allowed origins for UDS
|
||||
tests := []struct {
|
||||
name string
|
||||
addr NetworkAddress
|
||||
@ -543,12 +544,8 @@ func TestAllowedOriginsUnixSocket(t *testing.T) {
|
||||
Network: "unix",
|
||||
Host: "/tmp/caddy.sock",
|
||||
},
|
||||
origins: nil, // default origins
|
||||
expectOrigins: []string{
|
||||
"", // empty host as per RFC 2616
|
||||
"127.0.0.1",
|
||||
"::1",
|
||||
},
|
||||
origins: nil, // default origins
|
||||
expectOrigins: []string{},
|
||||
},
|
||||
{
|
||||
name: "unix socket with custom origins",
|
||||
@ -578,7 +575,7 @@ func TestAllowedOriginsUnixSocket(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
for i, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
admin := AdminConfig{
|
||||
Origins: test.origins,
|
||||
@ -592,7 +589,7 @@ func TestAllowedOriginsUnixSocket(t *testing.T) {
|
||||
}
|
||||
|
||||
if len(gotOrigins) != len(test.expectOrigins) {
|
||||
t.Errorf("Expected %d origins but got %d", len(test.expectOrigins), len(gotOrigins))
|
||||
t.Errorf("%d: Expected %d origins but got %d", i, len(test.expectOrigins), len(gotOrigins))
|
||||
return
|
||||
}
|
||||
|
||||
@ -607,7 +604,7 @@ func TestAllowedOriginsUnixSocket(t *testing.T) {
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expectMap, gotMap) {
|
||||
t.Errorf("Origins mismatch.\nExpected: %v\nGot: %v", test.expectOrigins, gotOrigins)
|
||||
t.Errorf("%d: Origins mismatch.\nExpected: %v\nGot: %v", i, test.expectOrigins, gotOrigins)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
4
go.mod
4
go.mod
@ -8,7 +8,7 @@ require (
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/alecthomas/chroma/v2 v2.15.0
|
||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
|
||||
github.com/caddyserver/certmagic v0.22.3-0.20250407182622-b9399eadfbe7
|
||||
github.com/caddyserver/certmagic v0.23.0
|
||||
github.com/caddyserver/zerossl v0.1.3
|
||||
github.com/cloudflare/circl v1.6.0
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
@ -17,7 +17,7 @@ require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/klauspost/compress v1.18.0
|
||||
github.com/klauspost/cpuid/v2 v2.2.10
|
||||
github.com/mholt/acmez/v3 v3.1.1
|
||||
github.com/mholt/acmez/v3 v3.1.2
|
||||
github.com/prometheus/client_golang v1.19.1
|
||||
github.com/quic-go/quic-go v0.50.1
|
||||
github.com/smallstep/certificates v0.26.1
|
||||
|
8
go.sum
8
go.sum
@ -93,8 +93,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.22.3-0.20250407182622-b9399eadfbe7 h1:dZCbkHTYh2ulXpmUK4ZrYWMCTa7x4/F3w52RGQfYoIY=
|
||||
github.com/caddyserver/certmagic v0.22.3-0.20250407182622-b9399eadfbe7/go.mod h1:r8ZK7Y8zaUU3j5g+oTiluLtQX8fwWI6MamcRINlSsJY=
|
||||
github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU=
|
||||
github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4=
|
||||
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.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
@ -347,8 +347,8 @@ 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/v3 v3.1.1 h1:Jh+9uKHkPxUJdxM16q5mOr+G2V0aqkuFtNA28ihCxhQ=
|
||||
github.com/mholt/acmez/v3 v3.1.1/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
|
||||
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
|
||||
|
@ -210,7 +210,7 @@ func (na NetworkAddress) IsUnixNetwork() bool {
|
||||
return IsUnixNetwork(na.Network)
|
||||
}
|
||||
|
||||
// IsUnixNetwork returns true if na.Network is
|
||||
// IsFdNetwork returns true if na.Network is
|
||||
// fd or fdgram.
|
||||
func (na NetworkAddress) IsFdNetwork() bool {
|
||||
return IsFdNetwork(na.Network)
|
||||
@ -641,7 +641,7 @@ 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(network, "ip:") || strings.HasPrefix(network, "ip4:") || strings.HasPrefix(network, "ip6:") ||
|
||||
network == "fd" || network == "fdgram" {
|
||||
panic("network type " + network + " is reserved")
|
||||
}
|
||||
|
@ -326,7 +326,9 @@ func (h *Handler) doActiveHealthCheckForAllHosts() {
|
||||
}
|
||||
}()
|
||||
|
||||
networkAddr, err := caddy.NewReplacer().ReplaceOrErr(upstream.Dial, true, true)
|
||||
repl := caddy.NewReplacer()
|
||||
|
||||
networkAddr, err := repl.ReplaceOrErr(upstream.Dial, true, true)
|
||||
if err != nil {
|
||||
if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "invalid use of placeholders in dial address for active health checks"); c != nil {
|
||||
c.Write(
|
||||
@ -361,14 +363,24 @@ func (h *Handler) doActiveHealthCheckForAllHosts() {
|
||||
return
|
||||
}
|
||||
hostAddr := addr.JoinHostPort(0)
|
||||
dialAddr := hostAddr
|
||||
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
|
||||
hostAddr = "localhost"
|
||||
}
|
||||
err = h.doActiveHealthCheck(DialInfo{Network: addr.Network, Address: dialAddr}, hostAddr, networkAddr, upstream)
|
||||
|
||||
// Fill in the dial info for the upstream
|
||||
// If the upstream is set, use that instead
|
||||
dialInfoUpstream := upstream
|
||||
if h.HealthChecks.Active.Upstream != "" {
|
||||
dialInfoUpstream = &Upstream{
|
||||
Dial: h.HealthChecks.Active.Upstream,
|
||||
}
|
||||
}
|
||||
dialInfo, _ := dialInfoUpstream.fillDialInfo(repl)
|
||||
|
||||
err = h.doActiveHealthCheck(dialInfo, hostAddr, networkAddr, upstream)
|
||||
if err != nil {
|
||||
if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "active health check failed"); c != nil {
|
||||
c.Write(
|
||||
|
@ -17,7 +17,6 @@ package reverseproxy
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
@ -100,8 +99,7 @@ func (u *Upstream) Full() bool {
|
||||
|
||||
// fillDialInfo returns a filled DialInfo for upstream u, using the request
|
||||
// context. Note that the returned value is not a pointer.
|
||||
func (u *Upstream) fillDialInfo(r *http.Request) (DialInfo, error) {
|
||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
func (u *Upstream) fillDialInfo(repl *caddy.Replacer) (DialInfo, error) {
|
||||
var addr caddy.NetworkAddress
|
||||
|
||||
// use provided dial address
|
||||
|
@ -532,7 +532,7 @@ func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w h
|
||||
// the dial address may vary per-request if placeholders are
|
||||
// used, so perform those replacements here; the resulting
|
||||
// DialInfo struct should have valid network address syntax
|
||||
dialInfo, err := upstream.fillDialInfo(r)
|
||||
dialInfo, err := upstream.fillDialInfo(repl)
|
||||
if err != nil {
|
||||
return true, fmt.Errorf("making dial info: %v", err)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user