You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/daemon/common/client_ip_test.go

195 lines
5.7 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package common
import (
"context"
"net/http"
"testing"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/go-secure-stdlib/listenerutil"
"github.com/hashicorp/go-sockaddr"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_ClientIpFromRequest(t *testing.T) {
// Cannot run in parallels since it relies on the pkg var common.privateNets
testCtx := context.Background()
testReq := func(remote, realIp string, forwardedFor ...string) *http.Request {
h := http.Header{}
h.Set("X-Real-IP", realIp)
for _, address := range forwardedFor {
h.Set("X-Forwarded-For", address)
}
return &http.Request{
RemoteAddr: remote,
Header: h,
}
}
pub1 := "147.12.56.100"
pub2 := "119.14.55.11"
localAddr := "127.0.0.1:101"
goodAddr, err := sockaddr.NewIPAddr(localAddr)
require.NoError(t, err)
tests := []struct {
name string
request *http.Request
listenerCfg *listenerutil.ListenerConfig
wantAddr string
wantErrMatch *errors.Template
wantErrContains string
}{
{
name: "missing-request",
listenerCfg: func() *listenerutil.ListenerConfig {
listenerConfig := cfgListener(goodAddr)
listenerConfig.XForwardedForRejectNotPresent = false
return listenerConfig
}(),
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing http request",
},
{
name: "missing-listener-config",
request: testReq(pub1, ""),
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing listener config",
},
{
name: "no-header-remote-addr",
request: testReq(pub1, ""),
listenerCfg: func() *listenerutil.ListenerConfig {
listenerConfig := cfgListener(goodAddr)
listenerConfig.XForwardedForRejectNotPresent = false
return listenerConfig
}(),
wantAddr: pub1,
},
{
name: "no-header-remote-addr-with-port",
request: testReq(pub1+":49152", ""),
listenerCfg: func() *listenerutil.ListenerConfig {
listenerConfig := cfgListener(goodAddr)
listenerConfig.XForwardedForRejectNotPresent = false
return listenerConfig
}(),
wantAddr: pub1,
},
{
name: "one-x-forwarded",
request: testReq(localAddr, "", pub1),
listenerCfg: func() *listenerutil.ListenerConfig {
listenerConfig := cfgListener(goodAddr)
listenerConfig.XForwardedForRejectNotPresent = false
return listenerConfig
}(),
wantAddr: pub1,
},
{
name: "no-trusted-x-forwarded-with-remote-addr",
request: testReq(localAddr, "", ""),
listenerCfg: func() *listenerutil.ListenerConfig {
listenerConfig := cfgListener(goodAddr)
listenerConfig.XForwardedForRejectNotPresent = false
return listenerConfig
}(),
wantAddr: "127.0.0.1",
},
{
name: "no-trusted-x-forwarded-with-bad-remote-addr",
request: testReq(localAddr+":22", "", ""),
listenerCfg: func() *listenerutil.ListenerConfig {
listenerConfig := cfgListener(goodAddr)
listenerConfig.XForwardedForRejectNotPresent = false
return listenerConfig
}(),
wantErrMatch: errors.T(errors.Unknown),
wantErrContains: "too many colons in address",
},
{
name: "many-x-forwarded",
request: testReq(localAddr, "", localAddr, pub1, pub2),
listenerCfg: func() *listenerutil.ListenerConfig {
listenerConfig := cfgListener(goodAddr)
listenerConfig.XForwardedForRejectNotPresent = false
return listenerConfig
}(),
wantAddr: pub2,
},
{
name: "real-ip", // will fallback to remote addr, since real-ip isn't enabled for boundary
request: testReq(localAddr, pub1),
listenerCfg: func() *listenerutil.ListenerConfig {
listenerConfig := cfgListener(goodAddr)
listenerConfig.XForwardedForRejectNotPresent = false
return listenerConfig
}(),
wantAddr: "127.0.0.1",
},
{
name: "local-forwaredfor-no-real-ip-use-remote-addr",
request: testReq(pub1, "", localAddr),
listenerCfg: func() *listenerutil.ListenerConfig {
listenerConfig := cfgListener(goodAddr)
listenerConfig.XForwardedForRejectNotPresent = false
return listenerConfig
}(),
wantAddr: pub1,
},
{
name: "trusted-x-forwarded-for-success",
request: testReq(localAddr, "", pub1),
listenerCfg: func() *listenerutil.ListenerConfig {
listenerConfig := cfgListener(goodAddr)
listenerConfig.XForwardedForRejectNotPresent = true
listenerConfig.XForwardedForRejectNotAuthorized = true
return listenerConfig
}(),
wantAddr: pub1,
},
{
name: "trusted-x-forwarded-for-failed",
request: testReq("", "", localAddr),
listenerCfg: func() *listenerutil.ListenerConfig {
listenerConfig := cfgListener(goodAddr)
listenerConfig.XForwardedForRejectNotPresent = true
listenerConfig.XForwardedForRejectNotAuthorized = true
return listenerConfig
}(),
wantErrMatch: errors.T(errors.Unknown),
wantErrContains: "failed to determine trusted X-Forwarded-For",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
gotAddr, err := ClientIpFromRequest(testCtx, tt.listenerCfg, tt.request)
if tt.wantErrMatch != nil {
require.Error(err)
assert.Truef(errors.Match(tt.wantErrMatch, err), "wanted %q and got %q", tt.wantErrMatch.Code, err.Error())
if tt.wantErrContains != "" {
assert.Contains(err.Error(), tt.wantErrContains)
}
return
}
require.NoError(err)
assert.Equal(tt.wantAddr, gotAddr)
})
}
}
func cfgListener(addr sockaddr.IPAddr) *listenerutil.ListenerConfig {
return &listenerutil.ListenerConfig{
XForwardedForAuthorizedAddrs: []*sockaddr.SockAddrMarshaler{
{
SockAddr: addr,
},
},
}
}