2017-09-18 15:48:07 +00:00
|
|
|
package auth
|
|
|
|
|
|
|
|
import (
|
2018-11-14 09:18:03 +00:00
|
|
|
"context"
|
2017-09-18 15:48:07 +00:00
|
|
|
"fmt"
|
2020-01-07 14:48:07 +00:00
|
|
|
"io"
|
2024-03-11 10:50:04 +00:00
|
|
|
"net"
|
2017-09-18 15:48:07 +00:00
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
2024-03-11 10:50:04 +00:00
|
|
|
"net/url"
|
|
|
|
"strconv"
|
2017-09-18 15:48:07 +00:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
2018-04-23 13:28:04 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2023-02-03 14:24:05 +00:00
|
|
|
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
|
|
|
"github.com/traefik/traefik/v3/pkg/testhelpers"
|
|
|
|
"github.com/traefik/traefik/v3/pkg/tracing"
|
2022-11-21 17:36:05 +00:00
|
|
|
"github.com/vulcand/oxy/v2/forward"
|
2024-03-11 10:50:04 +00:00
|
|
|
"go.opentelemetry.io/contrib/propagators/autoprop"
|
2024-01-08 08:10:06 +00:00
|
|
|
"go.opentelemetry.io/otel"
|
2024-03-11 10:50:04 +00:00
|
|
|
"go.opentelemetry.io/otel/attribute"
|
|
|
|
"go.opentelemetry.io/otel/codes"
|
|
|
|
"go.opentelemetry.io/otel/trace"
|
|
|
|
"go.opentelemetry.io/otel/trace/embedded"
|
2017-09-18 15:48:07 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestForwardAuthFail(t *testing.T) {
|
2018-11-14 09:18:03 +00:00
|
|
|
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
fmt.Fprintln(w, "traefik")
|
|
|
|
})
|
|
|
|
|
2017-09-18 15:48:07 +00:00
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2021-01-21 17:34:04 +00:00
|
|
|
w.Header().Set(forward.ProxyAuthenticate, "test")
|
2017-09-18 15:48:07 +00:00
|
|
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
|
|
|
}))
|
2020-08-17 10:02:03 +00:00
|
|
|
t.Cleanup(server.Close)
|
2017-09-18 15:48:07 +00:00
|
|
|
|
2019-07-10 07:26:04 +00:00
|
|
|
middleware, err := NewForward(context.Background(), next, dynamic.ForwardAuth{
|
2018-11-14 09:18:03 +00:00
|
|
|
Address: server.URL,
|
|
|
|
}, "authTest")
|
|
|
|
require.NoError(t, err)
|
2017-09-18 15:48:07 +00:00
|
|
|
|
2018-11-14 09:18:03 +00:00
|
|
|
ts := httptest.NewServer(middleware)
|
2020-08-17 10:02:03 +00:00
|
|
|
t.Cleanup(ts.Close)
|
2017-09-18 15:48:07 +00:00
|
|
|
|
|
|
|
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
|
2018-06-30 05:54:03 +00:00
|
|
|
res, err := http.DefaultClient.Do(req)
|
2018-11-14 09:18:03 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, http.StatusForbidden, res.StatusCode)
|
2017-09-18 15:48:07 +00:00
|
|
|
|
2021-03-04 19:08:03 +00:00
|
|
|
body, err := io.ReadAll(res.Body)
|
2018-11-14 09:18:03 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
err = res.Body.Close()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2021-01-21 17:34:04 +00:00
|
|
|
assert.Equal(t, "test", res.Header.Get(forward.ProxyAuthenticate))
|
2018-11-14 09:18:03 +00:00
|
|
|
assert.Equal(t, "Forbidden\n", string(body))
|
2017-09-18 15:48:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestForwardAuthSuccess(t *testing.T) {
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2018-06-30 05:54:03 +00:00
|
|
|
w.Header().Set("X-Auth-User", "user@example.com")
|
|
|
|
w.Header().Set("X-Auth-Secret", "secret")
|
2019-04-10 15:18:06 +00:00
|
|
|
w.Header().Add("X-Auth-Group", "group1")
|
|
|
|
w.Header().Add("X-Auth-Group", "group2")
|
2020-10-29 14:10:04 +00:00
|
|
|
w.Header().Add("Foo-Bar", "auth-value")
|
2024-01-15 15:14:05 +00:00
|
|
|
w.Header().Add("Set-Cookie", "authCookie=Auth")
|
|
|
|
w.Header().Add("Set-Cookie", "authCookieNotAdded=Auth")
|
2017-09-18 15:48:07 +00:00
|
|
|
fmt.Fprintln(w, "Success")
|
|
|
|
}))
|
2020-08-17 10:02:03 +00:00
|
|
|
t.Cleanup(server.Close)
|
2017-09-18 15:48:07 +00:00
|
|
|
|
2018-11-14 09:18:03 +00:00
|
|
|
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2018-06-30 05:54:03 +00:00
|
|
|
assert.Equal(t, "user@example.com", r.Header.Get("X-Auth-User"))
|
|
|
|
assert.Empty(t, r.Header.Get("X-Auth-Secret"))
|
2019-04-10 15:18:06 +00:00
|
|
|
assert.Equal(t, []string{"group1", "group2"}, r.Header["X-Auth-Group"])
|
2020-10-29 14:10:04 +00:00
|
|
|
assert.Equal(t, "auth-value", r.Header.Get("Foo-Bar"))
|
|
|
|
assert.Empty(t, r.Header.Get("Foo-Baz"))
|
2024-01-15 15:14:05 +00:00
|
|
|
w.Header().Add("Set-Cookie", "authCookie=Backend")
|
|
|
|
w.Header().Add("Set-Cookie", "backendCookie=Backend")
|
|
|
|
w.Header().Add("Other-Header", "BackendHeaderValue")
|
2017-09-18 15:48:07 +00:00
|
|
|
fmt.Fprintln(w, "traefik")
|
|
|
|
})
|
2018-11-14 09:18:03 +00:00
|
|
|
|
2019-07-10 07:26:04 +00:00
|
|
|
auth := dynamic.ForwardAuth{
|
2020-10-29 14:10:04 +00:00
|
|
|
Address: server.URL,
|
|
|
|
AuthResponseHeaders: []string{"X-Auth-User", "X-Auth-Group"},
|
|
|
|
AuthResponseHeadersRegex: "^Foo-",
|
2024-01-15 15:14:05 +00:00
|
|
|
AddAuthCookiesToResponse: []string{"authCookie"},
|
2018-11-14 09:18:03 +00:00
|
|
|
}
|
|
|
|
middleware, err := NewForward(context.Background(), next, auth, "authTest")
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
ts := httptest.NewServer(middleware)
|
2020-08-17 10:02:03 +00:00
|
|
|
t.Cleanup(ts.Close)
|
2017-09-18 15:48:07 +00:00
|
|
|
|
|
|
|
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
|
2019-04-10 15:18:06 +00:00
|
|
|
req.Header.Set("X-Auth-Group", "admin_group")
|
2020-10-29 14:10:04 +00:00
|
|
|
req.Header.Set("Foo-Bar", "client-value")
|
|
|
|
req.Header.Set("Foo-Baz", "client-value")
|
2018-06-30 05:54:03 +00:00
|
|
|
res, err := http.DefaultClient.Do(req)
|
2018-11-14 09:18:03 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, http.StatusOK, res.StatusCode)
|
2024-01-15 15:14:05 +00:00
|
|
|
assert.Equal(t, []string{"backendCookie=Backend", "authCookie=Auth"}, res.Header["Set-Cookie"])
|
|
|
|
assert.Equal(t, []string{"BackendHeaderValue"}, res.Header["Other-Header"])
|
2017-09-18 15:48:07 +00:00
|
|
|
|
2021-03-04 19:08:03 +00:00
|
|
|
body, err := io.ReadAll(res.Body)
|
2018-11-14 09:18:03 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
err = res.Body.Close()
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "traefik\n", string(body))
|
2017-09-18 15:48:07 +00:00
|
|
|
}
|
|
|
|
|
2017-11-02 10:06:03 +00:00
|
|
|
func TestForwardAuthRedirect(t *testing.T) {
|
|
|
|
authTs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
http.Redirect(w, r, "http://example.com/redirect-test", http.StatusFound)
|
|
|
|
}))
|
2020-08-17 10:02:03 +00:00
|
|
|
t.Cleanup(authTs.Close)
|
2017-11-02 10:06:03 +00:00
|
|
|
|
2018-11-14 09:18:03 +00:00
|
|
|
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2017-11-02 10:06:03 +00:00
|
|
|
fmt.Fprintln(w, "traefik")
|
|
|
|
})
|
2018-11-14 09:18:03 +00:00
|
|
|
|
2020-08-17 10:02:03 +00:00
|
|
|
auth := dynamic.ForwardAuth{Address: authTs.URL}
|
|
|
|
|
2018-11-14 09:18:03 +00:00
|
|
|
authMiddleware, err := NewForward(context.Background(), next, auth, "authTest")
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
ts := httptest.NewServer(authMiddleware)
|
2020-08-17 10:02:03 +00:00
|
|
|
t.Cleanup(ts.Close)
|
2017-11-02 10:06:03 +00:00
|
|
|
|
|
|
|
client := &http.Client{
|
|
|
|
CheckRedirect: func(r *http.Request, via []*http.Request) error {
|
|
|
|
return http.ErrUseLastResponse
|
|
|
|
},
|
|
|
|
}
|
2018-11-14 09:18:03 +00:00
|
|
|
|
2017-11-02 10:06:03 +00:00
|
|
|
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
|
2018-11-14 09:18:03 +00:00
|
|
|
|
2017-11-02 10:06:03 +00:00
|
|
|
res, err := client.Do(req)
|
2018-11-14 09:18:03 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Equal(t, http.StatusFound, res.StatusCode)
|
2017-11-02 10:06:03 +00:00
|
|
|
|
|
|
|
location, err := res.Location()
|
2018-11-14 09:18:03 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "http://example.com/redirect-test", location.String())
|
2017-11-02 10:06:03 +00:00
|
|
|
|
2021-03-04 19:08:03 +00:00
|
|
|
body, err := io.ReadAll(res.Body)
|
2018-11-14 09:18:03 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
err = res.Body.Close()
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.NotEmpty(t, string(body))
|
2017-11-02 10:06:03 +00:00
|
|
|
}
|
|
|
|
|
2018-09-18 12:22:03 +00:00
|
|
|
func TestForwardAuthRemoveHopByHopHeaders(t *testing.T) {
|
|
|
|
authTs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
headers := w.Header()
|
2021-01-21 17:34:04 +00:00
|
|
|
for _, header := range hopHeaders {
|
2018-09-18 12:22:03 +00:00
|
|
|
if header == forward.TransferEncoding {
|
2020-08-17 10:02:03 +00:00
|
|
|
headers.Set(header, "chunked")
|
2018-09-18 12:22:03 +00:00
|
|
|
} else {
|
|
|
|
headers.Add(header, "test")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
http.Redirect(w, r, "http://example.com/redirect-test", http.StatusFound)
|
|
|
|
}))
|
2020-08-17 10:02:03 +00:00
|
|
|
t.Cleanup(authTs.Close)
|
2018-09-18 12:22:03 +00:00
|
|
|
|
2018-11-14 09:18:03 +00:00
|
|
|
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2018-09-18 12:22:03 +00:00
|
|
|
fmt.Fprintln(w, "traefik")
|
|
|
|
})
|
2018-11-14 09:18:03 +00:00
|
|
|
|
2020-08-17 10:02:03 +00:00
|
|
|
auth := dynamic.ForwardAuth{Address: authTs.URL}
|
|
|
|
|
|
|
|
authMiddleware, err := NewForward(context.Background(), next, auth, "authTest")
|
|
|
|
require.NoError(t, err)
|
2018-11-14 09:18:03 +00:00
|
|
|
|
|
|
|
ts := httptest.NewServer(authMiddleware)
|
2020-08-17 10:02:03 +00:00
|
|
|
t.Cleanup(ts.Close)
|
2018-09-18 12:22:03 +00:00
|
|
|
|
|
|
|
client := &http.Client{
|
|
|
|
CheckRedirect: func(r *http.Request, via []*http.Request) error {
|
|
|
|
return http.ErrUseLastResponse
|
|
|
|
},
|
|
|
|
}
|
2020-08-17 10:02:03 +00:00
|
|
|
|
2018-09-18 12:22:03 +00:00
|
|
|
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
|
|
|
|
res, err := client.Do(req)
|
2020-08-17 10:02:03 +00:00
|
|
|
require.NoError(t, err)
|
2018-09-18 12:22:03 +00:00
|
|
|
assert.Equal(t, http.StatusFound, res.StatusCode, "they should be equal")
|
|
|
|
|
|
|
|
for _, header := range forward.HopHeaders {
|
|
|
|
assert.Equal(t, "", res.Header.Get(header), "hop-by-hop header '%s' mustn't be set", header)
|
|
|
|
}
|
|
|
|
|
|
|
|
location, err := res.Location()
|
2020-08-17 10:02:03 +00:00
|
|
|
require.NoError(t, err)
|
2018-09-18 12:22:03 +00:00
|
|
|
assert.Equal(t, "http://example.com/redirect-test", location.String(), "they should be equal")
|
|
|
|
|
2021-03-04 19:08:03 +00:00
|
|
|
body, err := io.ReadAll(res.Body)
|
2020-08-17 10:02:03 +00:00
|
|
|
require.NoError(t, err)
|
2018-09-18 12:22:03 +00:00
|
|
|
assert.NotEmpty(t, string(body), "there should be something in the body")
|
|
|
|
}
|
|
|
|
|
2018-04-23 13:28:04 +00:00
|
|
|
func TestForwardAuthFailResponseHeaders(t *testing.T) {
|
2017-11-02 10:06:03 +00:00
|
|
|
authTs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
cookie := &http.Cookie{Name: "example", Value: "testing", Path: "/"}
|
|
|
|
http.SetCookie(w, cookie)
|
2018-04-23 13:28:04 +00:00
|
|
|
w.Header().Add("X-Foo", "bar")
|
2017-11-02 10:06:03 +00:00
|
|
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
|
|
|
}))
|
2020-08-17 10:02:03 +00:00
|
|
|
t.Cleanup(authTs.Close)
|
2017-11-02 10:06:03 +00:00
|
|
|
|
2018-11-14 09:18:03 +00:00
|
|
|
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2017-11-02 10:06:03 +00:00
|
|
|
fmt.Fprintln(w, "traefik")
|
|
|
|
})
|
2018-11-14 09:18:03 +00:00
|
|
|
|
2019-07-10 07:26:04 +00:00
|
|
|
auth := dynamic.ForwardAuth{
|
2018-11-14 09:18:03 +00:00
|
|
|
Address: authTs.URL,
|
|
|
|
}
|
|
|
|
authMiddleware, err := NewForward(context.Background(), next, auth, "authTest")
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
ts := httptest.NewServer(authMiddleware)
|
2020-08-17 10:02:03 +00:00
|
|
|
t.Cleanup(ts.Close)
|
2017-11-02 10:06:03 +00:00
|
|
|
|
|
|
|
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
|
2018-11-14 09:18:03 +00:00
|
|
|
|
|
|
|
res, err := http.DefaultClient.Do(req)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, http.StatusForbidden, res.StatusCode)
|
2017-11-02 10:06:03 +00:00
|
|
|
|
2018-04-23 13:28:04 +00:00
|
|
|
require.Len(t, res.Cookies(), 1)
|
2017-11-02 10:06:03 +00:00
|
|
|
for _, cookie := range res.Cookies() {
|
2018-11-14 09:18:03 +00:00
|
|
|
assert.Equal(t, "testing", cookie.Value)
|
2017-11-02 10:06:03 +00:00
|
|
|
}
|
|
|
|
|
2018-04-23 13:28:04 +00:00
|
|
|
expectedHeaders := http.Header{
|
|
|
|
"Content-Length": []string{"10"},
|
|
|
|
"Content-Type": []string{"text/plain; charset=utf-8"},
|
|
|
|
"X-Foo": []string{"bar"},
|
|
|
|
"Set-Cookie": []string{"example=testing; Path=/"},
|
|
|
|
"X-Content-Type-Options": []string{"nosniff"},
|
|
|
|
}
|
|
|
|
|
|
|
|
assert.Len(t, res.Header, 6)
|
|
|
|
for key, value := range expectedHeaders {
|
|
|
|
assert.Equal(t, value, res.Header[key])
|
|
|
|
}
|
|
|
|
|
2021-03-04 19:08:03 +00:00
|
|
|
body, err := io.ReadAll(res.Body)
|
2018-11-14 09:18:03 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
err = res.Body.Close()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Equal(t, "Forbidden\n", string(body))
|
2017-11-02 10:06:03 +00:00
|
|
|
}
|
|
|
|
|
2017-09-18 15:48:07 +00:00
|
|
|
func Test_writeHeader(t *testing.T) {
|
|
|
|
testCases := []struct {
|
2018-09-25 13:06:03 +00:00
|
|
|
name string
|
|
|
|
headers map[string]string
|
2020-10-07 14:36:04 +00:00
|
|
|
authRequestHeaders []string
|
2018-09-25 13:06:03 +00:00
|
|
|
trustForwardHeader bool
|
|
|
|
emptyHost bool
|
|
|
|
expectedHeaders map[string]string
|
|
|
|
checkForUnexpectedHeaders bool
|
2017-09-18 15:48:07 +00:00
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "trust Forward Header",
|
|
|
|
headers: map[string]string{
|
|
|
|
"Accept": "application/json",
|
|
|
|
"X-Forwarded-Host": "fii.bir",
|
|
|
|
},
|
|
|
|
trustForwardHeader: true,
|
|
|
|
expectedHeaders: map[string]string{
|
|
|
|
"Accept": "application/json",
|
|
|
|
"X-Forwarded-Host": "fii.bir",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "not trust Forward Header",
|
|
|
|
headers: map[string]string{
|
|
|
|
"Accept": "application/json",
|
|
|
|
"X-Forwarded-Host": "fii.bir",
|
|
|
|
},
|
|
|
|
trustForwardHeader: false,
|
|
|
|
expectedHeaders: map[string]string{
|
|
|
|
"Accept": "application/json",
|
|
|
|
"X-Forwarded-Host": "foo.bar",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "trust Forward Header with empty Host",
|
|
|
|
headers: map[string]string{
|
|
|
|
"Accept": "application/json",
|
|
|
|
"X-Forwarded-Host": "fii.bir",
|
|
|
|
},
|
|
|
|
trustForwardHeader: true,
|
|
|
|
emptyHost: true,
|
|
|
|
expectedHeaders: map[string]string{
|
|
|
|
"Accept": "application/json",
|
|
|
|
"X-Forwarded-Host": "fii.bir",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "not trust Forward Header with empty Host",
|
|
|
|
headers: map[string]string{
|
|
|
|
"Accept": "application/json",
|
|
|
|
"X-Forwarded-Host": "fii.bir",
|
|
|
|
},
|
|
|
|
trustForwardHeader: false,
|
|
|
|
emptyHost: true,
|
|
|
|
expectedHeaders: map[string]string{
|
|
|
|
"Accept": "application/json",
|
|
|
|
"X-Forwarded-Host": "",
|
|
|
|
},
|
|
|
|
},
|
2017-12-09 23:58:21 +00:00
|
|
|
{
|
|
|
|
name: "trust Forward Header with forwarded URI",
|
|
|
|
headers: map[string]string{
|
|
|
|
"Accept": "application/json",
|
|
|
|
"X-Forwarded-Host": "fii.bir",
|
|
|
|
"X-Forwarded-Uri": "/forward?q=1",
|
|
|
|
},
|
|
|
|
trustForwardHeader: true,
|
|
|
|
expectedHeaders: map[string]string{
|
|
|
|
"Accept": "application/json",
|
|
|
|
"X-Forwarded-Host": "fii.bir",
|
|
|
|
"X-Forwarded-Uri": "/forward?q=1",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "not trust Forward Header with forward requested URI",
|
|
|
|
headers: map[string]string{
|
|
|
|
"Accept": "application/json",
|
|
|
|
"X-Forwarded-Host": "fii.bir",
|
|
|
|
"X-Forwarded-Uri": "/forward?q=1",
|
|
|
|
},
|
|
|
|
trustForwardHeader: false,
|
|
|
|
expectedHeaders: map[string]string{
|
|
|
|
"Accept": "application/json",
|
|
|
|
"X-Forwarded-Host": "foo.bar",
|
|
|
|
"X-Forwarded-Uri": "/path?q=1",
|
|
|
|
},
|
2020-07-07 12:42:03 +00:00
|
|
|
},
|
|
|
|
{
|
2018-06-13 13:14:03 +00:00
|
|
|
name: "trust Forward Header with forwarded request Method",
|
|
|
|
headers: map[string]string{
|
|
|
|
"X-Forwarded-Method": "OPTIONS",
|
|
|
|
},
|
|
|
|
trustForwardHeader: true,
|
|
|
|
expectedHeaders: map[string]string{
|
|
|
|
"X-Forwarded-Method": "OPTIONS",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "not trust Forward Header with forward request Method",
|
|
|
|
headers: map[string]string{
|
|
|
|
"X-Forwarded-Method": "OPTIONS",
|
|
|
|
},
|
|
|
|
trustForwardHeader: false,
|
|
|
|
expectedHeaders: map[string]string{
|
|
|
|
"X-Forwarded-Method": "GET",
|
|
|
|
},
|
2017-12-09 23:58:21 +00:00
|
|
|
},
|
2018-09-25 13:06:03 +00:00
|
|
|
{
|
|
|
|
name: "remove hop-by-hop headers",
|
|
|
|
headers: map[string]string{
|
|
|
|
forward.Connection: "Connection",
|
|
|
|
forward.KeepAlive: "KeepAlive",
|
|
|
|
forward.ProxyAuthenticate: "ProxyAuthenticate",
|
|
|
|
forward.ProxyAuthorization: "ProxyAuthorization",
|
|
|
|
forward.Te: "Te",
|
|
|
|
forward.Trailers: "Trailers",
|
|
|
|
forward.TransferEncoding: "TransferEncoding",
|
|
|
|
forward.Upgrade: "Upgrade",
|
|
|
|
"X-CustomHeader": "CustomHeader",
|
|
|
|
},
|
|
|
|
trustForwardHeader: false,
|
|
|
|
expectedHeaders: map[string]string{
|
2021-01-21 17:34:04 +00:00
|
|
|
"X-CustomHeader": "CustomHeader",
|
|
|
|
"X-Forwarded-Proto": "http",
|
|
|
|
"X-Forwarded-Host": "foo.bar",
|
|
|
|
"X-Forwarded-Uri": "/path?q=1",
|
|
|
|
"X-Forwarded-Method": "GET",
|
|
|
|
forward.ProxyAuthenticate: "ProxyAuthenticate",
|
|
|
|
forward.ProxyAuthorization: "ProxyAuthorization",
|
2018-09-25 13:06:03 +00:00
|
|
|
},
|
|
|
|
checkForUnexpectedHeaders: true,
|
|
|
|
},
|
2020-10-07 14:36:04 +00:00
|
|
|
{
|
|
|
|
name: "filter forward request headers",
|
|
|
|
headers: map[string]string{
|
|
|
|
"X-CustomHeader": "CustomHeader",
|
|
|
|
"Content-Type": "multipart/form-data; boundary=---123456",
|
|
|
|
},
|
|
|
|
authRequestHeaders: []string{
|
|
|
|
"X-CustomHeader",
|
|
|
|
},
|
|
|
|
trustForwardHeader: false,
|
|
|
|
expectedHeaders: map[string]string{
|
|
|
|
"x-customHeader": "CustomHeader",
|
|
|
|
"X-Forwarded-Proto": "http",
|
|
|
|
"X-Forwarded-Host": "foo.bar",
|
|
|
|
"X-Forwarded-Uri": "/path?q=1",
|
|
|
|
"X-Forwarded-Method": "GET",
|
|
|
|
},
|
|
|
|
checkForUnexpectedHeaders: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "filter forward request headers doesn't add new headers",
|
|
|
|
headers: map[string]string{
|
|
|
|
"X-CustomHeader": "CustomHeader",
|
|
|
|
"Content-Type": "multipart/form-data; boundary=---123456",
|
|
|
|
},
|
|
|
|
authRequestHeaders: []string{
|
|
|
|
"X-CustomHeader",
|
|
|
|
"X-Non-Exists-Header",
|
|
|
|
},
|
|
|
|
trustForwardHeader: false,
|
|
|
|
expectedHeaders: map[string]string{
|
|
|
|
"X-CustomHeader": "CustomHeader",
|
|
|
|
"X-Forwarded-Proto": "http",
|
|
|
|
"X-Forwarded-Host": "foo.bar",
|
|
|
|
"X-Forwarded-Uri": "/path?q=1",
|
|
|
|
"X-Forwarded-Method": "GET",
|
|
|
|
},
|
|
|
|
checkForUnexpectedHeaders: true,
|
|
|
|
},
|
2017-09-18 15:48:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range testCases {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
2017-12-09 23:58:21 +00:00
|
|
|
req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/path?q=1", nil)
|
2017-09-18 15:48:07 +00:00
|
|
|
for key, value := range test.headers {
|
|
|
|
req.Header.Set(key, value)
|
|
|
|
}
|
|
|
|
|
|
|
|
if test.emptyHost {
|
|
|
|
req.Host = ""
|
|
|
|
}
|
|
|
|
|
2017-12-09 23:58:21 +00:00
|
|
|
forwardReq := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/path?q=1", nil)
|
2017-09-18 15:48:07 +00:00
|
|
|
|
2020-10-07 14:36:04 +00:00
|
|
|
writeHeader(req, forwardReq, test.trustForwardHeader, test.authRequestHeaders)
|
2017-09-18 15:48:07 +00:00
|
|
|
|
2018-09-25 13:06:03 +00:00
|
|
|
actualHeaders := forwardReq.Header
|
2020-10-07 14:36:04 +00:00
|
|
|
|
2018-09-25 13:06:03 +00:00
|
|
|
expectedHeaders := test.expectedHeaders
|
|
|
|
for key, value := range expectedHeaders {
|
|
|
|
assert.Equal(t, value, actualHeaders.Get(key))
|
|
|
|
actualHeaders.Del(key)
|
|
|
|
}
|
|
|
|
if test.checkForUnexpectedHeaders {
|
|
|
|
for key := range actualHeaders {
|
|
|
|
assert.Fail(t, "Unexpected header found", key)
|
|
|
|
}
|
2017-09-18 15:48:07 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2020-01-07 14:48:07 +00:00
|
|
|
|
2024-03-11 10:50:04 +00:00
|
|
|
func TestForwardAuthTracing(t *testing.T) {
|
|
|
|
type expected struct {
|
|
|
|
name string
|
|
|
|
attributes []attribute.KeyValue
|
|
|
|
}
|
|
|
|
|
2020-01-07 14:48:07 +00:00
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2024-01-08 08:10:06 +00:00
|
|
|
if r.Header.Get("Traceparent") == "" {
|
|
|
|
t.Errorf("expected Traceparent header to be present in request")
|
2020-01-07 14:48:07 +00:00
|
|
|
}
|
2024-03-11 10:50:04 +00:00
|
|
|
|
|
|
|
w.Header().Set("X-Bar", "foo")
|
|
|
|
w.Header().Add("X-Bar", "bar")
|
|
|
|
w.WriteHeader(http.StatusNotFound)
|
2020-01-07 14:48:07 +00:00
|
|
|
}))
|
2020-08-17 10:02:03 +00:00
|
|
|
t.Cleanup(server.Close)
|
2020-01-07 14:48:07 +00:00
|
|
|
|
2024-03-11 10:50:04 +00:00
|
|
|
parse, err := url.Parse(server.URL)
|
|
|
|
require.NoError(t, err)
|
2020-01-07 14:48:07 +00:00
|
|
|
|
2024-03-11 10:50:04 +00:00
|
|
|
_, serverPort, err := net.SplitHostPort(parse.Host)
|
|
|
|
require.NoError(t, err)
|
2020-01-07 14:48:07 +00:00
|
|
|
|
2024-03-11 10:50:04 +00:00
|
|
|
serverPortInt, err := strconv.Atoi(serverPort)
|
2024-01-08 08:10:06 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2024-03-11 10:50:04 +00:00
|
|
|
testCases := []struct {
|
|
|
|
desc string
|
|
|
|
expected []expected
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
desc: "basic test",
|
|
|
|
expected: []expected{
|
|
|
|
{
|
|
|
|
name: "initial",
|
|
|
|
attributes: []attribute.KeyValue{
|
|
|
|
attribute.String("span.kind", "unspecified"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "AuthRequest",
|
|
|
|
attributes: []attribute.KeyValue{
|
|
|
|
attribute.String("span.kind", "client"),
|
|
|
|
attribute.String("http.request.method", "GET"),
|
|
|
|
attribute.String("network.protocol.version", "1.1"),
|
|
|
|
attribute.String("url.full", server.URL),
|
|
|
|
attribute.String("url.scheme", "http"),
|
|
|
|
attribute.String("user_agent.original", ""),
|
|
|
|
attribute.String("network.peer.address", "127.0.0.1"),
|
|
|
|
attribute.String("network.peer.port", serverPort),
|
|
|
|
attribute.String("server.address", "127.0.0.1"),
|
|
|
|
attribute.Int64("server.port", int64(serverPortInt)),
|
|
|
|
attribute.StringSlice("http.request.header.x-foo", []string{"foo", "bar"}),
|
|
|
|
attribute.Int64("http.response.status_code", int64(404)),
|
|
|
|
attribute.StringSlice("http.response.header.x-bar", []string{"foo", "bar"}),
|
|
|
|
},
|
|
|
|
},
|
2024-01-08 08:10:06 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2020-01-07 14:48:07 +00:00
|
|
|
|
2024-03-11 10:50:04 +00:00
|
|
|
for _, test := range testCases {
|
|
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
|
|
next := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
2020-01-07 14:48:07 +00:00
|
|
|
|
2024-03-11 10:50:04 +00:00
|
|
|
auth := dynamic.ForwardAuth{
|
|
|
|
Address: server.URL,
|
|
|
|
AuthRequestHeaders: []string{"X-Foo"},
|
|
|
|
}
|
|
|
|
next, err := NewForward(context.Background(), next, auth, "authTest")
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "http://www.test.com/search?q=Opentelemetry", nil)
|
|
|
|
req.RemoteAddr = "10.0.0.1:1234"
|
|
|
|
req.Header.Set("User-Agent", "forward-test")
|
|
|
|
req.Header.Set("X-Forwarded-Proto", "http")
|
|
|
|
req.Header.Set("X-Foo", "foo")
|
|
|
|
req.Header.Add("X-Foo", "bar")
|
|
|
|
|
|
|
|
otel.SetTextMapPropagator(autoprop.NewTextMapPropagator())
|
|
|
|
|
|
|
|
mockTracer := &mockTracer{}
|
|
|
|
tracer := tracing.NewTracer(mockTracer, []string{"X-Foo"}, []string{"X-Bar"})
|
|
|
|
initialCtx, initialSpan := tracer.Start(req.Context(), "initial")
|
|
|
|
defer initialSpan.End()
|
|
|
|
req = req.WithContext(initialCtx)
|
|
|
|
|
|
|
|
recorder := httptest.NewRecorder()
|
|
|
|
next.ServeHTTP(recorder, req)
|
|
|
|
|
|
|
|
for i, span := range mockTracer.spans {
|
|
|
|
assert.Equal(t, test.expected[i].name, span.name)
|
|
|
|
assert.Equal(t, test.expected[i].attributes, span.attributes)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2020-01-07 14:48:07 +00:00
|
|
|
|
2024-03-11 10:50:04 +00:00
|
|
|
type mockTracer struct {
|
|
|
|
embedded.Tracer
|
2020-01-07 14:48:07 +00:00
|
|
|
|
2024-03-11 10:50:04 +00:00
|
|
|
spans []*mockSpan
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ trace.Tracer = &mockTracer{}
|
|
|
|
|
|
|
|
func (t *mockTracer) Start(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
|
|
|
|
config := trace.NewSpanStartConfig(opts...)
|
|
|
|
span := &mockSpan{}
|
|
|
|
span.SetName(name)
|
|
|
|
span.SetAttributes(attribute.String("span.kind", config.SpanKind().String()))
|
|
|
|
span.SetAttributes(config.Attributes()...)
|
|
|
|
t.spans = append(t.spans, span)
|
|
|
|
return trace.ContextWithSpan(ctx, span), span
|
|
|
|
}
|
|
|
|
|
|
|
|
// mockSpan is an implementation of Span that preforms no operations.
|
|
|
|
type mockSpan struct {
|
|
|
|
embedded.Span
|
|
|
|
|
|
|
|
name string
|
|
|
|
attributes []attribute.KeyValue
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ trace.Span = &mockSpan{}
|
|
|
|
|
|
|
|
func (*mockSpan) SpanContext() trace.SpanContext {
|
|
|
|
return trace.NewSpanContext(trace.SpanContextConfig{TraceID: trace.TraceID{1}, SpanID: trace.SpanID{1}})
|
|
|
|
}
|
|
|
|
func (*mockSpan) IsRecording() bool { return false }
|
|
|
|
func (s *mockSpan) SetStatus(_ codes.Code, _ string) {}
|
|
|
|
func (s *mockSpan) SetAttributes(kv ...attribute.KeyValue) {
|
|
|
|
s.attributes = append(s.attributes, kv...)
|
|
|
|
}
|
|
|
|
func (s *mockSpan) End(...trace.SpanEndOption) {}
|
|
|
|
func (s *mockSpan) RecordError(_ error, _ ...trace.EventOption) {}
|
|
|
|
func (s *mockSpan) AddEvent(_ string, _ ...trace.EventOption) {}
|
|
|
|
|
|
|
|
func (s *mockSpan) SetName(name string) { s.name = name }
|
|
|
|
|
|
|
|
func (s *mockSpan) TracerProvider() trace.TracerProvider {
|
|
|
|
return nil
|
2020-01-07 14:48:07 +00:00
|
|
|
}
|