Skip to content

Instantly share code, notes, and snippets.

@ifraixedes
Last active June 30, 2022 17:25
Show Gist options
  • Save ifraixedes/547e3e8f96e93293fb86b3d4319cc839 to your computer and use it in GitHub Desktop.
Save ifraixedes/547e3e8f96e93293fb86b3d4319cc839 to your computer and use it in GitHub Desktop.

Trusted Client IP

This implementation was developed for Storj Labs and is part of some of their systems, however, it isn't at this time used, so they will likely remove it.

I've kept it in this gist for future reference.

Storj labs licensed this code under the GNU Affero General Public License v3.0.

This code was copied on 2022-06-30 from the repository hosted on https://github.com/storj/gateway-mt

What did it do?

It was extracting client IPs from HTTP requests if the proxy that forwards them are in a trusted list.

My main interest is to keep the function that extract the client IP form the HTTP request headers for future reference.

module trustedip
go 1.18
require (
github.com/stretchr/testify v1.8.0
storj.io/gateway-mt v1.32.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
storj.io/gateway-mt v1.32.0 h1:gNhiw6/rZyKQEHr/Rf1bE4utJstNKbAt+DKbd3pjSXQ=
storj.io/gateway-mt v1.32.0/go.mod h1:EiKyWRxWTwmc/Qpx0C3yhFPVDmdJbXoRQ2c98Q2e3Hc=
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
package trustedip
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestStripPort(t *testing.T) {
testCases := []struct {
desc string
addr string
exp string
}{
{
desc: "hostname no port",
addr: "storj.test",
exp: "storj.test",
},
{
desc: "hostname port",
addr: "storj.test:1234",
exp: "storj.test",
},
{
desc: "hostname invalid",
addr: "storj:test:123:",
exp: "storj:test:123:",
},
{
desc: "IPv4 no port",
addr: "192.168.1.78",
exp: "192.168.1.78",
},
{
desc: "IPv4 port",
addr: "192.168.7.69:7888",
exp: "192.168.7.69",
},
{
desc: "IPv4 invalid",
addr: "1985:5849.15.15:8080:",
exp: "1985:5849.15.15:8080:",
},
{
desc: "IPv6 no port",
addr: "6934:9e20:e075:a5f6:c8d2:21d1:124d:94b7",
exp: "6934:9e20:e075:a5f6:c8d2:21d1:124d:94b7",
},
{
desc: "IPv6 port",
addr: "[6934:9e20:e075:a5f6:c8d2:21d1:124d:94b7]:9898",
exp: "6934:9e20:e075:a5f6:c8d2:21d1:124d:94b7",
},
{
desc: "IPv6 invalid not closing bracket",
addr: "[6934:9e20:e075:a5f6:c8d2:21d1:124d:94b7:9898",
exp: "6934:9e20:e075:a5f6:c8d2:21d1:124d:94b",
},
{
desc: "IPv6 invalid port without brackets",
addr: "6934:9e20:e075:a5f6:c8d2:21d1:124d:94b7:9898",
exp: "6934:9e20:e075:a5f6:c8d2:21d1:124d:94b7:9898",
},
{
desc: "IPv6 invalid brackets no port",
addr: "[6934:9e20:e075:a5f6:c8d2:21d1:124d:94b7]",
exp: "6934:9e20:e075:a5f6:c8d2:21d1:124",
},
{
desc: "empty address",
addr: "",
exp: "",
},
{
desc: "invalid address bracket",
addr: "[",
exp: "[",
},
{
desc: "invalid address bracket-colon",
addr: "[:",
exp: "[:",
},
{
desc: "invalid address brackets",
addr: "[]",
exp: "[]",
},
{
desc: "invalid address colon",
addr: ":",
exp: "",
},
}
for _, tC := range testCases {
t.Run(tC.desc, func(t *testing.T) {
host := stripPort(tC.addr)
require.Equal(t, tC.exp, host)
})
}
}
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
package trustedip
import (
"net/http"
"regexp"
"strings"
)
// List is a list of trusted IPs for conveniently verifying if an IP is trusted.
type List struct {
// ips is the list of trusted IPs. It's used when untrustAll is false. When
// empty it trusts any IP.
ips map[string]struct{}
untrustAll bool
}
// NewListUntrustAll creates a new List which doesn't trust in any IP.
func NewListUntrustAll() List {
return List{untrustAll: true}
}
// NewListTrustAll creates a new List which trusts any IP.
func NewListTrustAll() List {
return List{}
}
// NewList creates a new List which trusts the passed ips.
//
// NOTE: ips are not checked to be well formatted and their values are what they
// kept in the list.
func NewList(ips ...string) List {
l := List{ips: make(map[string]struct{}, len(ips))}
for _, ip := range ips {
l.ips[ip] = struct{}{}
}
return l
}
// IsTrusted returns true if ip is trusted, otherwise false.
func (l List) IsTrusted(ip string) bool {
if l.untrustAll {
return false
}
if len(l.ips) == 0 {
return true
}
_, ok := l.ips[ip]
return ok
}
// GetClientIP gets the IP of the client from the 'Forwarded',
// 'X-Forwarded-For', or 'X-Real-Ip' headers if r.RemoteAddr is a trusted IP and
// returning it from the first header which are checked in that specific order.
// If the IP isn't trusted then it returns r.RemoteAddr.
// It panics if r is nil.
//
// If an address contains the port, it's stripped. Addresses with ports are
// accepted in r.RemoteAddr and 'Forwarded' header.
//
// NOTE: it doesn't check that the IP value get from wherever source is a well
// formatted IP v4 nor v6, because it's expected that it's. It also expect that
// request has been created from a trusted source (e.g. http.Server).
func GetClientIP(l List, r *http.Request) string {
addr := stripPort(r.RemoteAddr)
if l.IsTrusted(addr) {
ip, ok := GetIPFromHeaders(r.Header)
if ok {
return ip
}
}
return addr
}
var forwardForClientIPRegExp = regexp.MustCompile(`(?i:(?:^|;)for=([^,; ]+))`)
// GetIPFromHeaders gets the IP of the client from the first exiting header in
// this order: 'Forwarded', 'X-Forwarded-For', or 'X-Real-Ip'.
// It returns the IP and true if the any of the headers exists, otherwise false.
//
// The 'for' field of the 'Forwarded' may contain the IP with a port, as defined
// in the RFC 7239. When the header contains the IP with a port, the port is
// striped, so only the IP is returned.
//
// NOTE: it doesn't check that the IP value get from wherever source is a well
// formatted IP v4 nor v6; an invalid formatted IP will return an undefined
// result.
func GetIPFromHeaders(headers http.Header) (string, bool) {
h := headers.Get("Forwarded")
if h != "" {
// Get the first value of the 'for' identifier present in the header because
// its the one that contains the client IP.
// see: https://datatracker.ietf.org/doc/html/rfc7230
matches := forwardForClientIPRegExp.FindStringSubmatch(h)
if len(matches) > 1 {
ip := strings.Trim(matches[1], `"`)
ip = stripPort(ip)
if ip[0] == '[' {
ip = ip[1 : len(ip)-1]
}
return ip, true
}
}
h = headers.Get("X-Forwarded-For")
if h != "" {
// Get the first the value IP because it's the client IP.
// Header sysntax: X-Forwarded-For: <client>, <proxy1>, <proxy2>
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For
ips := strings.SplitN(h, ",", 2)
if len(ips) > 0 {
return ips[0], true
}
}
h = headers.Get("X-Real-Ip")
if h != "" {
// Get the value of the header because its value is just the client IP.
// This header is mostly sent by NGINX.
// See https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/
return h, true
}
return "", false
}
// stripPort strips the port from addr when it has it and return the host
// part. A host can be a hostname or an IP v4 or an IP v6.
//
// NOTE: this function expects a well-formatted address. When it's hostname or
// IP v4, the port at the end and separated with a colon, nor hostname or IP can
// have colons; when it's a IP v6 with port the IP part is enclosed with square
// brackets (.i.e []) and the port separated with a colon, otherwise the IP
// isn't enclosed by square brackets.
// An invalid addr produce an unspecified value.
func stripPort(addr string) string {
// Ensure to strip the port out if r.RemoteAddr has it.
// We don't use net.SplitHostPort because the function returns an error if the
// address doesn't contain the port and the returned host is an empty string,
// besides it doesn't return an error that can be distinguished from others
// unless that the error message is compared, which is discouraging.
if addr == "" {
return ""
}
// It's an IP v6 with port.
if addr[0] == '[' {
idx := strings.LastIndex(addr, ":")
if idx <= 1 {
return addr
}
return addr[1 : idx-1]
}
// It's a IP v4 with port.
if strings.Count(addr, ":") == 1 {
idx := strings.LastIndex(addr, ":")
if idx < 0 {
return addr
}
return addr[0:idx]
}
// It's a IP v4 or v6 without port.
return addr
}
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
package trustedip_test
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"storj.io/gateway-mt/pkg/trustedip"
)
func TestGetClientIP(t *testing.T) {
testCases := []struct {
desc string
l trustedip.List
r *http.Request
ip string
}{
{
desc: "Trusted IP (v4) 'Forwarded' single 'for'",
l: trustedip.NewList("192.168.5.2", "10.5.2.23"),
r: &http.Request{
RemoteAddr: "10.5.2.23",
Header: map[string][]string{"Forwarded": {"for=172.17.5.10"}},
},
ip: "172.17.5.10",
},
{
desc: "Trusted IP (v6) 'Forwarded' single 'for'",
l: trustedip.NewList("192.168.5.2", "8428:f6d:9d3d:82cf:7190:3c31:3326:8484"),
r: &http.Request{
RemoteAddr: "8428:f6d:9d3d:82cf:7190:3c31:3326:8484",
Header: map[string][]string{"Forwarded": {"for=49d4:4f54:d2fa:4f5f:fe12:d3bf:d523:192c"}},
},
ip: "49d4:4f54:d2fa:4f5f:fe12:d3bf:d523:192c",
},
{
desc: "Trusted IP (v4) 'Forwarded' multiple 'for'",
l: trustedip.NewList("192.168.5.2", "10.5.2.23"),
r: &http.Request{
RemoteAddr: "192.168.5.2",
Header: map[string][]string{
"Forwarded": {"for=172.31.254.250,for=172.17.5.10"},
},
},
ip: "172.31.254.250",
},
{
desc: "Trusted IP (v6) 'Forwarded' multiple 'for'",
l: trustedip.NewList("8428:f6d:9d3d:82cf:7190:3c31:3326:8484", "10.5.2.23"),
r: &http.Request{
RemoteAddr: "8428:f6d:9d3d:82cf:7190:3c31:3326:8484",
Header: map[string][]string{
"Forwarded": {"for=49d4:4f54:d2fa:4f5f:fe12:d3bf:d523:192c,for=172.17.5.10"},
},
},
ip: "49d4:4f54:d2fa:4f5f:fe12:d3bf:d523:192c",
},
{
desc: "Trusted IP (v4) 'Forwarded' multiple 'for' with space after comma",
l: trustedip.NewList("10.5.2.23"),
r: &http.Request{
RemoteAddr: "10.5.2.23",
Header: map[string][]string{
"Forwarded": {"for=192.168.5.250, for=172.17.5.10"},
},
},
ip: "192.168.5.250",
},
{
desc: "Trusted IP (v6) 'Forwarded' multiple 'for' with space after comma",
l: trustedip.NewList("49d4:4f54:d2fa:4f5f:fe12:d3bf:d523:192c"),
r: &http.Request{
RemoteAddr: "[49d4:4f54:d2fa:4f5f:fe12:d3bf:d523:192c]:8089",
Header: map[string][]string{
"Forwarded": {"for=8428:f6d:9d3d:82cf:7190:3c31:3326:8484, for=172.17.5.10"},
},
},
ip: "8428:f6d:9d3d:82cf:7190:3c31:3326:8484",
},
{
desc: "Trusted IP (v4) 'Forwarded' multiple 'for' with other pairs",
l: trustedip.NewList("192.168.5.2", "10.5.2.23", "172.20.20.20"),
r: &http.Request{
RemoteAddr: "172.20.20.20",
Header: map[string][]string{
"Forwarded": {
"by=storj;for=172.31.254.15,for=172.17.5.10;host=example.test;proto=https",
"for=172.28.15.15",
},
},
},
ip: "172.31.254.15",
},
{
desc: "Trusted IP (v6) 'Forwarded' multiple 'for' with other pairs",
l: trustedip.NewList("192.168.5.2", "8428:f6d:9d3d:82cf:7190:3c31:3326:8484", "172.20.20.20"),
r: &http.Request{
RemoteAddr: "8428:f6d:9d3d:82cf:7190:3c31:3326:8484",
Header: map[string][]string{
"Forwarded": {
"by=storj;for=49d4:4f54:d2fa:4f5f:fe12:d3bf:d523:192c,for=172.17.5.10;host=example.test;proto=https",
"for=172.28.15.15",
},
},
},
ip: "49d4:4f54:d2fa:4f5f:fe12:d3bf:d523:192c",
},
{
desc: "Trusted IP (v4) 'X-Forwarded-For' single IP",
l: trustedip.NewList("192.168.50.2", "10.5.2.23"),
r: &http.Request{
RemoteAddr: "192.168.50.2",
Header: map[string][]string{"X-Forwarded-For": {"172.31.254.80"}},
},
ip: "172.31.254.80",
},
{
desc: "Trusted IP (v6) 'X-Forwarded-For' single IP",
l: trustedip.NewList("8428:f6d:9d3d:82cf:7190:3c31:3326:8484", "10.5.2.23"),
r: &http.Request{
RemoteAddr: "[8428:f6d:9d3d:82cf:7190:3c31:3326:8484]:123",
Header: map[string][]string{"X-Forwarded-For": {"49d4:4f54:d2fa:4f5f:fe12:d3bf:d523:192c"}},
},
ip: "49d4:4f54:d2fa:4f5f:fe12:d3bf:d523:192c",
},
{
desc: "Trusted IP (v4) 'X-Forwarded-For' multiple IPs",
l: trustedip.NewList("10.5.2.23", "192.168.50.2"),
r: &http.Request{
RemoteAddr: "192.168.50.2",
Header: map[string][]string{
"X-Forwarded-For": {"172.28.254.80, 192.168.80.25"},
},
},
ip: "172.28.254.80",
},
{
desc: "Trusted IP (v6) 'X-Forwarded-For' multiple IPs",
l: trustedip.NewList("49d4:4f54:d2fa:4f5f:fe12:d3bf:d523:192c", "192.168.50.2"),
r: &http.Request{
RemoteAddr: "49d4:4f54:d2fa:4f5f:fe12:d3bf:d523:192c",
Header: map[string][]string{
"X-Forwarded-For": {"6e11:d5a8:b04d:9416:1f51:5262:15bc:4be6, 8428:f6d:9d3d:82cf:7190:3c31:3326:8484"},
},
},
ip: "6e11:d5a8:b04d:9416:1f51:5262:15bc:4be6",
},
{
desc: "Trusted IP (v4) 'X-Real-Ip'",
l: trustedip.NewList("192.168.50.2"),
r: &http.Request{
RemoteAddr: "192.168.50.2",
Header: map[string][]string{"X-Real-Ip": {"172.31.254.85"}},
},
ip: "172.31.254.85",
},
{
desc: "Trusted IP (v6) 'X-Real-Ip'",
l: trustedip.NewList("8428:f6d:9d3d:82cf:7190:3c31:3326:8484"),
r: &http.Request{
RemoteAddr: "8428:f6d:9d3d:82cf:7190:3c31:3326:8484",
Header: map[string][]string{"X-Real-Ip": {"49d4:4f54:d2fa:4f5f:fe12:d3bf:d523:192c"}},
},
ip: "49d4:4f54:d2fa:4f5f:fe12:d3bf:d523:192c",
},
{
desc: "Trusted IP (v4) no headers",
l: trustedip.NewList("192.168.50.60", "10.5.2.23"),
r: &http.Request{
RemoteAddr: "192.168.50.60",
},
ip: "192.168.50.60",
},
{
desc: "Trusted IP (v6) no headers",
l: trustedip.NewList("192.168.50.60", "8428:f6d:9d3d:82cf:7190:3c31:3326:8484"),
r: &http.Request{
RemoteAddr: "[8428:f6d:9d3d:82cf:7190:3c31:3326:8484]:7894",
},
ip: "8428:f6d:9d3d:82cf:7190:3c31:3326:8484",
},
{
desc: "Trusted IP (v4) multiple headers",
l: trustedip.NewList("10.5.2.23"),
r: &http.Request{
RemoteAddr: "10.5.2.23",
Header: map[string][]string{
"X-Forwarded-For": {"172.28.254.80, 192.168.80.25"},
"Forwarded": {"for=192.168.5.250, for=172.17.5.10"},
},
},
ip: "192.168.5.250",
},
{
desc: "Trusted IP (v6) multiple headers",
l: trustedip.NewList("8428:f6d:9d3d:82cf:7190:3c31:3326:8484"),
r: &http.Request{
RemoteAddr: "8428:f6d:9d3d:82cf:7190:3c31:3326:8484",
Header: map[string][]string{
"X-Forwarded-For": {"172.28.254.80, 192.168.80.25"},
"Forwarded": {"for=49d4:4f54:d2fa:4f5f:fe12:d3bf:d523:192c, for=172.17.5.10"},
},
},
ip: "49d4:4f54:d2fa:4f5f:fe12:d3bf:d523:192c",
},
{
desc: "Untrusted IP (v4)",
l: trustedip.NewList("192.168.50.2", "10.5.2.23"),
r: &http.Request{
RemoteAddr: "192.168.100.15",
Header: map[string][]string{"X-Forwarded-For": {"172.31.254.80"}},
},
ip: "192.168.100.15",
},
{
desc: "Untrusted IP (v6)",
l: trustedip.NewList("49d4:4f54:d2fa:4f5f:fe12:d3bf:d523:192c", "10.5.2.23"),
r: &http.Request{
RemoteAddr: "8428:f6d:9d3d:82cf:7190:3c31:3326:8484",
Header: map[string][]string{"X-Forwarded-For": {"172.31.254.80"}},
},
ip: "8428:f6d:9d3d:82cf:7190:3c31:3326:8484",
},
{
desc: "Untrusted any IP (v4)",
l: trustedip.NewListUntrustAll(),
r: &http.Request{
RemoteAddr: "192.168.135.80:6968",
Header: map[string][]string{
"X-Forwarded-For": {"172.28.254.80, 192.168.80.25"},
"Forwarded": {"for=192.168.5.250, for=172.17.5.10"},
},
},
ip: "192.168.135.80",
},
{
desc: "Untrusted any IP (v6)",
l: trustedip.NewListUntrustAll(),
r: &http.Request{
RemoteAddr: "[6e11:d5a8:b04d:9416:1f51:5262:15bc:4be6]:5458",
Header: map[string][]string{
"X-Forwarded-For": {"172.28.254.80, 192.168.80.25"},
"Forwarded": {"for=192.168.5.250, for=172.17.5.10"},
},
},
ip: "6e11:d5a8:b04d:9416:1f51:5262:15bc:4be6",
},
}
for _, tC := range testCases {
tC := tC
t.Run(tC.desc, func(t *testing.T) {
ip := trustedip.GetClientIP(tC.l, tC.r)
assert.Equal(t, tC.ip, ip)
})
}
}
func TestGetClientIPFromHeaders(t *testing.T) {
testCases := []struct {
desc string
r *http.Request
ip string
ok bool
}{
{
desc: "'Forwarded' single 'for'",
r: &http.Request{
RemoteAddr: "10.5.2.23",
Header: map[string][]string{"Forwarded": {"for=172.17.5.10"}},
},
ip: "172.17.5.10",
ok: true,
},
{
desc: "'Forwarded' multiple 'for'",
r: &http.Request{
RemoteAddr: "192.168.5.2",
Header: map[string][]string{
"Forwarded": {"for=172.31.254.250,for=172.17.5.10"},
},
},
ip: "172.31.254.250",
ok: true,
},
{
desc: "'Forwarded' multiple 'for' with space after comma",
r: &http.Request{
RemoteAddr: "10.5.2.23",
Header: map[string][]string{
"Forwarded": {"for=192.168.5.250, for=172.17.5.10"},
},
},
ip: "192.168.5.250",
ok: true,
},
{
desc: "'Forwarded' multiple 'for' with other pairs",
r: &http.Request{
RemoteAddr: "172.20.20.20",
Header: map[string][]string{
"Forwarded": {
"by=storj;for=172.31.254.15,for=172.17.5.10;host=example.test;proto=https",
"for=172.28.15.15",
},
},
},
ip: "172.31.254.15",
ok: true,
},
{
desc: "'Forwarded' single capitalized 'For'",
r: &http.Request{
RemoteAddr: "10.5.2.23",
Header: map[string][]string{"Forwarded": {"For=172.17.5.10"}},
},
ip: "172.17.5.10",
ok: true,
},
{
desc: "'Forwarded' multiple capitalized 'For'",
r: &http.Request{
RemoteAddr: "192.168.5.2",
Header: map[string][]string{
"Forwarded": {"For=172.31.254.250,For=172.17.5.10"},
},
},
ip: "172.31.254.250",
ok: true,
},
{
desc: "'Forwarded' multiple uppercase 'For' with space after comma",
r: &http.Request{
RemoteAddr: "10.5.2.23",
Header: map[string][]string{
"Forwarded": {"FOR=192.168.5.250, For=172.17.5.10"},
},
},
ip: "192.168.5.250",
ok: true,
},
{
desc: "'Forwarded' multiple capitalized 'For' with other pairs",
r: &http.Request{
RemoteAddr: "172.20.20.20",
Header: map[string][]string{
"Forwarded": {
"by=storj;For=172.31.254.15,for=172.17.5.10;host=example.test;proto=https",
"For=172.28.15.15",
},
},
},
ip: "172.31.254.15",
ok: true,
},
{
desc: "'Forwarded' 'for' IPv4 with port",
r: &http.Request{
RemoteAddr: "172.20.20.20",
Header: map[string][]string{
"Forwarded": {
`by=storj;for="172.31.254.15:9089",for=172.17.5.10;host=example.test;proto=https`,
"for=172.28.15.15",
},
},
},
ip: "172.31.254.15",
ok: true,
},
{
desc: "'Forwarded' 'for' IPv6",
r: &http.Request{
RemoteAddr: "172.20.20.20",
Header: map[string][]string{
"Forwarded": {
`by=storj;for="6934:9e20:e075:a5f6:c8d2:21d1:124d:94b7",for=172.17.5.10;host=example.test;proto=https`,
"for=172.28.15.15",
},
},
},
ip: "6934:9e20:e075:a5f6:c8d2:21d1:124d:94b7",
ok: true,
},
{
desc: "'Forwarded' 'for' IPv6 with port",
r: &http.Request{
RemoteAddr: "172.20.20.20",
Header: map[string][]string{
"Forwarded": {
`by=storj;for="[6934:9e20:e075:a5f6:c8d2:21d1:124d:94b7]:7896",for=172.17.5.10;host=example.test;proto=https`,
"for=172.28.15.15",
},
},
},
ip: "6934:9e20:e075:a5f6:c8d2:21d1:124d:94b7",
ok: true,
},
{
desc: "'Forwarded' with a extension whose field name has 'for' postfix",
r: &http.Request{
RemoteAddr: "172.20.20.20",
Header: map[string][]string{
"Forwarded": {
"by=storj;xfor=172.31.254.15;for=172.17.5.10,for=172.17.5.12;host=example.test;proto=https",
"For=172.28.15.15",
},
},
},
ip: "172.17.5.10",
ok: true,
},
{
desc: "'X-Forwarded-For' single IP",
r: &http.Request{
RemoteAddr: "192.168.50.2",
Header: map[string][]string{"X-Forwarded-For": {"172.31.254.80"}},
},
ip: "172.31.254.80",
ok: true,
},
{
desc: "'X-Forwarded-For' multiple IPs",
r: &http.Request{
RemoteAddr: "192.168.50.2",
Header: map[string][]string{
"X-Forwarded-For": {"172.28.254.80, 192.168.80.25"},
},
},
ip: "172.28.254.80",
ok: true,
},
{
desc: "'X-Real-Ip'",
r: &http.Request{
RemoteAddr: "192.168.50.2",
Header: map[string][]string{"X-Real-Ip": {"172.31.254.85"}},
},
ip: "172.31.254.85",
ok: true,
},
{
desc: "multiple headers",
r: &http.Request{
RemoteAddr: "10.5.2.23",
Header: map[string][]string{
"X-Forwarded-For": {"172.28.254.80, 192.168.80.25"},
"Forwarded": {"for=192.168.5.250, for=172.17.5.10"},
"X-Real-Ip": {"172.31.254.85"},
},
},
ip: "192.168.5.250",
ok: true,
},
{
desc: "no headers",
r: &http.Request{
RemoteAddr: "192.168.50.60",
},
ok: false,
},
}
for _, tC := range testCases {
tC := tC
t.Run(tC.desc, func(t *testing.T) {
ip, ok := trustedip.GetIPFromHeaders(tC.r.Header)
if !tC.ok {
assert.Equal(t, tC.ok, ok, "OK")
return
}
assert.Equal(t, tC.ip, ip)
assert.Equal(t, tC.ok, ok)
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment