reverseproxy: save dial info in a context key instead of a variable key to avoid race conditions when forward_auth is used (#7859)

pull/7872/head
WeidiDeng 2026-07-10 23:37:07 +08:00 committed by GitHub
parent 945d199724
commit 1830809afe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 13 additions and 14 deletions

View File

@ -438,9 +438,7 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, networ
// may be expected by handlers of this request
ctx := h.ctx.Context
ctx = context.WithValue(ctx, caddy.ReplacerCtxKey, caddy.NewReplacer())
ctx = context.WithValue(ctx, caddyhttp.VarsCtxKey, map[string]any{
dialInfoVarKey: dialInfo,
})
ctx = context.WithValue(ctx, dialInfoCtxKey, dialInfo)
req, err := http.NewRequestWithContext(ctx, h.HealthChecks.Active.Method, u.String(), requestBody)
if err != nil {
return fmt.Errorf("making request: %v", err)

View File

@ -24,7 +24,6 @@ import (
"time"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
// UpstreamPool is a collection of upstreams.
@ -297,7 +296,7 @@ func (di DialInfo) String() string {
// GetDialInfo gets the upstream dialing info out of the context,
// and returns true if there was a valid value; false otherwise.
func GetDialInfo(ctx context.Context) (DialInfo, bool) {
dialInfo, ok := caddyhttp.GetVar(ctx, dialInfoVarKey).(DialInfo)
dialInfo, ok := ctx.Value(dialInfoCtxKey).(DialInfo)
return dialInfo, ok
}
@ -329,9 +328,9 @@ type dynamicHostEntry struct {
lastSeen time.Time
}
// dialInfoVarKey is the key used for the variable that holds
// dialInfoCtxKey is the context key used for the variable that holds
// the dial info for the upstream connection.
const dialInfoVarKey = "reverse_proxy.dial_info"
const dialInfoCtxKey caddy.CtxKey = "reverse_proxy.dial_info"
// proxyProtocolInfoVarKey is the key used for the variable that holds
// the proxy protocol info for the upstream connection.

View File

@ -260,7 +260,7 @@ func TestHTTPTransport_DialContext_DialInfoOverride(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dialCtx := context.WithValue(context.Background(), caddyhttp.VarsCtxKey, make(map[string]any))
caddyhttp.SetVar(dialCtx, dialInfoVarKey, DialInfo{
dialCtx = context.WithValue(dialCtx, dialInfoCtxKey, DialInfo{
Network: "tcp4",
Address: tt.dialInfo,
})

View File

@ -660,11 +660,6 @@ func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w h
)
}
// attach to the request information about how to dial the upstream;
// this is necessary because the information cannot be sufficiently
// or satisfactorily represented in a URL
caddyhttp.SetVar(r.Context(), dialInfoVarKey, dialInfo)
// set placeholders with information about this upstream
repl.Set("http.reverse_proxy.upstream.address", dialInfo.String())
repl.Set("http.reverse_proxy.upstream.hostport", dialInfo.Address)
@ -1037,7 +1032,14 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origRe
return nil
},
}
req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
// attach to the request information about how to dial the upstream;
// this is necessary because the information cannot be sufficiently
// or satisfactorily represented in a URL
// it's set before request is roundtripped to avoid a race condition when
// http.Transport reads a newer value to dial a new connection when that new
// value is updated by another reverse proxy handler, typically forward_auth.
ctx := context.WithValue(req.Context(), dialInfoCtxKey, di)
req = req.WithContext(httptrace.WithClientTrace(ctx, trace))
// do the round-trip
start := time.Now()