| // Copyright 2024 The Prometheus Authors |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // This package no longer handles safe yaml parsing. In order to |
| // ensure correct yaml unmarshalling, use "yaml.UnmarshalStrict()". |
| |
| package config |
| |
| import ( |
| "fmt" |
| "io" |
| "net" |
| "net/http" |
| "net/http/httptest" |
| "strings" |
| "testing" |
| |
| "github.com/stretchr/testify/require" |
| ) |
| |
| func TestReservedHeaders(t *testing.T) { |
| for k := range ReservedHeaders { |
| l := http.CanonicalHeaderKey(k) |
| if k != l { |
| t.Errorf("ReservedHeaders keys should be lowercase: got %q, expected %q", k, http.CanonicalHeaderKey(k)) |
| } |
| } |
| } |
| |
| func TestHeadersRoundTripperSameHost(t *testing.T) { |
| // All headers, including sensitive ones, must be forwarded on same-host requests. |
| for _, header := range []string{"Cookie", "X-Custom-Header"} { |
| t.Run(header, func(t *testing.T) { |
| received := "" |
| server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| received = r.Header.Get(header) |
| fmt.Fprint(w, "ok") |
| })) |
| t.Cleanup(server.Close) |
| |
| headers := &Headers{ |
| Headers: map[string]Header{ |
| header: {Values: []string{"testvalue"}}, |
| }, |
| } |
| rt := NewHeadersRoundTripper(headers, http.DefaultTransport) |
| |
| req, err := http.NewRequest(http.MethodGet, server.URL, nil) |
| require.NoError(t, err) |
| |
| resp, err := rt.RoundTrip(req) |
| require.NoError(t, err) |
| defer resp.Body.Close() |
| body, err := io.ReadAll(resp.Body) |
| require.NoError(t, err) |
| require.Equal(t, "ok", strings.TrimSpace(string(body))) |
| require.Equalf(t, "testvalue", received, "header %q must be forwarded on same-host request", header) |
| }) |
| } |
| } |
| |
| func TestHeadersRoundTripperCrossHostRedirect(t *testing.T) { |
| // Cookie must be set on the initial request but stripped on cross-host redirects. |
| cookieOnRedirect := "" |
| target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| cookieOnRedirect = r.Header.Get("Cookie") |
| fmt.Fprint(w, "ok") |
| })) |
| t.Cleanup(target.Close) |
| |
| // Use "localhost" as the redirect target hostname so that it differs from |
| // "127.0.0.1" used by the origin server, making it a cross-host redirect. |
| targetPort := target.Listener.Addr().(*net.TCPAddr).Port |
| targetURL := fmt.Sprintf("http://localhost:%d", targetPort) |
| |
| cookieOnOrigin := "" |
| origin := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| cookieOnOrigin = r.Header.Get("Cookie") |
| http.Redirect(w, r, targetURL, http.StatusFound) |
| })) |
| t.Cleanup(origin.Close) |
| |
| cfg := HTTPClientConfig{ |
| FollowRedirects: true, |
| HTTPHeaders: &Headers{ |
| Headers: map[string]Header{ |
| "Cookie": {Values: []string{"session=abc"}}, |
| }, |
| }, |
| } |
| client, err := NewClientFromConfig(cfg, "test") |
| require.NoError(t, err) |
| |
| resp, err := client.Get(origin.URL) |
| require.NoError(t, err) |
| defer resp.Body.Close() |
| _, err = io.ReadAll(resp.Body) |
| require.NoError(t, err) |
| |
| require.Equalf(t, "session=abc", cookieOnOrigin, "Cookie must be set on the initial request.") |
| require.Emptyf(t, cookieOnRedirect, "Cookie must not be forwarded on a cross-host redirect.") |
| } |
| |
| func TestHeadersRoundTripperSameHostRedirect(t *testing.T) { |
| // Cookie must be forwarded on same-host redirects. |
| mux := http.NewServeMux() |
| cookieOnRedirect := "" |
| mux.HandleFunc("/start", func(w http.ResponseWriter, r *http.Request) { |
| http.Redirect(w, r, "/end", http.StatusFound) |
| }) |
| mux.HandleFunc("/end", func(w http.ResponseWriter, r *http.Request) { |
| cookieOnRedirect = r.Header.Get("Cookie") |
| fmt.Fprint(w, "ok") |
| }) |
| server := httptest.NewServer(mux) |
| t.Cleanup(server.Close) |
| |
| cfg := HTTPClientConfig{ |
| FollowRedirects: true, |
| HTTPHeaders: &Headers{ |
| Headers: map[string]Header{ |
| "Cookie": {Values: []string{"session=abc"}}, |
| }, |
| }, |
| } |
| client, err := NewClientFromConfig(cfg, "test") |
| require.NoError(t, err) |
| |
| resp, err := client.Get(server.URL + "/start") |
| require.NoError(t, err) |
| defer resp.Body.Close() |
| _, err = io.ReadAll(resp.Body) |
| require.NoError(t, err) |
| |
| require.Equalf(t, "session=abc", cookieOnRedirect, "Cookie must be forwarded on a same-host redirect.") |
| } |