Repository: caddyserver/caddy
File: modules/caddyhttp/reverseproxy/selectionpolicies.go (Go)

Code before:
		return fmt.Errorf("loading fallback selection policy: %s", err)
	}
	s.fallback = mod.(Selector)
	return nil
}

// Select returns an available host, if any.
func (s CookieHashSelection) Select(pool UpstreamPool, req *http.Request, w http.ResponseWriter) *Upstream {
	// selects a new Host using the fallback policy (typically random)
	// and write a sticky session cookie to the response.
	selectNewHost := func() *Upstream {
		upstream := s.fallback.Select(pool, req, w)
		if upstream == nil {
			return nil
		}
		sha, err := hashCookie(s.Secret, upstream.Dial)
		if err != nil {
			return upstream
		}
		cookie := &http.Cookie{
			Name:   s.Name,
			Value:  sha,
			Path:   "/",
			Secure: false,
		}
		if req.TLS != nil {
			cookie.Secure = true
			cookie.SameSite = http.SameSiteNoneMode
		}
		http.SetCookie(w, cookie)
		return upstream
	}

	cookie, err := req.Cookie(s.Name)
	// If there's no cookie, select a host using the fallback policy
	if err != nil || cookie == nil {
		return selectNewHost()
	}
	// If the cookie is present, loop over the available upstreams until we find a match
	cookieValue := cookie.Value
	for _, upstream := range pool {
		if !upstream.Available() {
			continue
		}
		sha, err := hashCookie(s.Secret, upstream.Dial)
		if err == nil && sha == cookieValue {
			return upstream
		}
	}
	// If there is no matching host, select a host using the fallback policy
	return selectNewHost()

Review comment (suggestion):
Can probably also add this check:

```go
trusted := caddyhttp.GetVar(req.Context(), caddyhttp.TrustedProxyVarKey).(bool)
xfp, ok, _ := lastHeaderValue(req.Header, "X-Forwarded-Proto")
if req.TLS != nil || (trusted && ok && xfp == "https") {
```

This way it still works as expected if Caddy is behind another proxy which adds the XFP header and Caddy has `trusted_proxies` configured to trust the remote IP.

Code after:
		return fmt.Errorf("loading fallback selection policy: %s", err)
	}
	s.fallback = mod.(Selector)
	return nil
}

// Select returns an available host, if any.
func (s CookieHashSelection) Select(pool UpstreamPool, req *http.Request, w http.ResponseWriter) *Upstream {
	// selects a new Host using the fallback policy (typically random)
	// and write a sticky session cookie to the response.
	selectNewHost := func() *Upstream {
		upstream := s.fallback.Select(pool, req, w)
		if upstream == nil {
			return nil
		}
		sha, err := hashCookie(s.Secret, upstream.Dial)
		if err != nil {
			return upstream
		}
		cookie := &http.Cookie{
			Name:   s.Name,
			Value:  sha,
			Path:   "/",
			Secure: false,
		}
		isProxyHttps := false
		if trusted, ok := caddyhttp.GetVar(req.Context(), caddyhttp.TrustedProxyVarKey).(bool); ok && trusted {
			xfp, xfpOk, _ := lastHeaderValue(req.Header, "X-Forwarded-Proto")
			isProxyHttps = xfpOk && xfp == "https"
		}
		if req.TLS != nil || isProxyHttps {
			cookie.Secure = true
			cookie.SameSite = http.SameSiteNoneMode
		}
		http.SetCookie(w, cookie)
		return upstream
	}

	cookie, err := req.Cookie(s.Name)
	// If there's no cookie, select a host using the fallback policy
	if err != nil || cookie == nil {
		return selectNewHost()
	}
	// If the cookie is present, loop over the available upstreams until we find a match
	cookieValue := cookie.Value
	for _, upstream := range pool {
		if !upstream.Available() {
			continue
		}
		sha, err := hashCookie(s.Secret, upstream.Dial)
		if err == nil && sha == cookieValue {