diff --git a/modules/caddyhttp/reverseproxy/selectionpolicies.go b/modules/caddyhttp/reverseproxy/selectionpolicies.go index cc42b7a3b..86a3d0d7c 100644 --- a/modules/caddyhttp/reverseproxy/selectionpolicies.go +++ b/modules/caddyhttp/reverseproxy/selectionpolicies.go @@ -712,7 +712,7 @@ func (s CookieHashSelection) Select(pool UpstreamPool, req *http.Request, w http continue } sha, err := hashCookie(s.Secret, upstream.Dial) - if err == nil && sha == cookieValue { + if err == nil && hmac.Equal([]byte(sha), []byte(cookieValue)) { return upstream } } diff --git a/modules/caddyhttp/reverseproxy/selectionpolicies_test.go b/modules/caddyhttp/reverseproxy/selectionpolicies_test.go index ce1d502df..7c912ce01 100644 --- a/modules/caddyhttp/reverseproxy/selectionpolicies_test.go +++ b/modules/caddyhttp/reverseproxy/selectionpolicies_test.go @@ -890,3 +890,46 @@ func TestCookieHashPolicyWithFirstFallback(t *testing.T) { t.Error("Expected cookieHashPolicy to set a new cookie.") } } + +func TestCookieHashPolicyWithSecret(t *testing.T) { + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + cookieHashPolicy := CookieHashSelection{Secret: "hunter2"} + if err := cookieHashPolicy.Provision(ctx); err != nil { + t.Errorf("Provision error: %v", err) + t.FailNow() + } + + pool := testPool() + pool[0].Dial = "localhost:8080" + pool[1].Dial = "localhost:8081" + pool[2].Dial = "localhost:8082" + pool[0].setHealthy(true) + pool[1].setHealthy(true) + pool[2].setHealthy(true) + + request := httptest.NewRequest(http.MethodGet, "/test", nil) + w := httptest.NewRecorder() + h := cookieHashPolicy.Select(pool, request, w) + cookie := w.Result().Cookies()[0] + + // a matching cookie sticks to the same host + request = httptest.NewRequest(http.MethodGet, "/test", nil) + w = httptest.NewRecorder() + request.AddCookie(cookie) + if got := cookieHashPolicy.Select(pool, request, w); got != h { + t.Errorf("Expected to stick to host %s, got %s", h, got) + } + if len(w.Result().Cookies()) != 0 { + t.Error("Expected no new cookie for a matching value") + } + + // a tampered cookie value must not match any host and gets a fresh cookie + request = httptest.NewRequest(http.MethodGet, "/test", nil) + w = httptest.NewRecorder() + request.AddCookie(&http.Cookie{Name: cookie.Name, Value: cookie.Value[:len(cookie.Value)-1] + "0"}) + cookieHashPolicy.Select(pool, request, w) + if len(w.Result().Cookies()) == 0 { + t.Error("Expected a new cookie to be set for a non-matching value") + } +}