Add trusted whitelist proxy protocol

This commit is contained in:
Emile Vauge 2017-10-10 14:50:03 +02:00 committed by Traefiker
parent 93a1db77c5
commit 6287a3dd53
9 changed files with 249 additions and 143 deletions

View file

@ -84,7 +84,9 @@ func TestDo_globalConfiguration(t *testing.T) {
}, },
WhitelistSourceRange: []string{"foo WhitelistSourceRange 1", "foo WhitelistSourceRange 2", "foo WhitelistSourceRange 3"}, WhitelistSourceRange: []string{"foo WhitelistSourceRange 1", "foo WhitelistSourceRange 2", "foo WhitelistSourceRange 3"},
Compress: true, Compress: true,
ProxyProtocol: true, ProxyProtocol: &configuration.ProxyProtocol{
TrustedIPs: []string{"127.0.0.1/32", "192.168.0.1"},
},
}, },
"fii": { "fii": {
Network: "fii Network", Network: "fii Network",
@ -125,7 +127,9 @@ func TestDo_globalConfiguration(t *testing.T) {
}, },
WhitelistSourceRange: []string{"fii WhitelistSourceRange 1", "fii WhitelistSourceRange 2", "fii WhitelistSourceRange 3"}, WhitelistSourceRange: []string{"fii WhitelistSourceRange 1", "fii WhitelistSourceRange 2", "fii WhitelistSourceRange 3"},
Compress: true, Compress: true,
ProxyProtocol: true, ProxyProtocol: &configuration.ProxyProtocol{
TrustedIPs: []string{"127.0.0.1/32", "192.168.0.1"},
},
}, },
} }
config.Cluster = &types.Cluster{ config.Cluster = &types.Cluster{

View file

@ -231,7 +231,14 @@ func (ep *EntryPoints) Set(value string) error {
} }
compress := toBool(result, "Compress") compress := toBool(result, "Compress")
proxyProtocol := toBool(result, "ProxyProtocol")
var proxyProtocol *ProxyProtocol
if len(result["ProxyProtocol"]) > 0 {
trustedIPs := strings.Split(result["ProxyProtocol"], ",")
proxyProtocol = &ProxyProtocol{
TrustedIPs: trustedIPs,
}
}
(*ep)[result["Name"]] = &EntryPoint{ (*ep)[result["Name"]] = &EntryPoint{
Address: result["Address"], Address: result["Address"],
@ -246,7 +253,7 @@ func (ep *EntryPoints) Set(value string) error {
} }
func parseEntryPointsConfiguration(value string) (map[string]string, error) { func parseEntryPointsConfiguration(value string) (map[string]string, error) {
regex := regexp.MustCompile(`(?:Name:(?P<Name>\S*))\s*(?:Address:(?P<Address>\S*))?\s*(?:TLS:(?P<TLS>\S*))?\s*(?P<TLSACME>TLS)?\s*(?:CA:(?P<CA>\S*))?\s*(?:Redirect\.EntryPoint:(?P<RedirectEntryPoint>\S*))?\s*(?:Redirect\.Regex:(?P<RedirectRegex>\S*))?\s*(?:Redirect\.Replacement:(?P<RedirectReplacement>\S*))?\s*(?:Compress:(?P<Compress>\S*))?\s*(?:WhiteListSourceRange:(?P<WhiteListSourceRange>\S*))?\s*(?:ProxyProtocol:(?P<ProxyProtocol>\S*))?`) regex := regexp.MustCompile(`(?:Name:(?P<Name>\S*))\s*(?:Address:(?P<Address>\S*))?\s*(?:TLS:(?P<TLS>\S*))?\s*(?P<TLSACME>TLS)?\s*(?:CA:(?P<CA>\S*))?\s*(?:Redirect\.EntryPoint:(?P<RedirectEntryPoint>\S*))?\s*(?:Redirect\.Regex:(?P<RedirectRegex>\S*))?\s*(?:Redirect\.Replacement:(?P<RedirectReplacement>\S*))?\s*(?:Compress:(?P<Compress>\S*))?\s*(?:WhiteListSourceRange:(?P<WhiteListSourceRange>\S*))?\s*(?:ProxyProtocol\.TrustedIPs:(?P<ProxyProtocol>\S*))?`)
match := regex.FindAllStringSubmatch(value, -1) match := regex.FindAllStringSubmatch(value, -1)
if match == nil { if match == nil {
return nil, fmt.Errorf("bad EntryPoints format: %s", value) return nil, fmt.Errorf("bad EntryPoints format: %s", value)
@ -293,8 +300,8 @@ type EntryPoint struct {
Redirect *Redirect `export:"true"` Redirect *Redirect `export:"true"`
Auth *types.Auth `export:"true"` Auth *types.Auth `export:"true"`
WhitelistSourceRange []string WhitelistSourceRange []string
Compress bool `export:"true"` Compress bool `export:"true"`
ProxyProtocol bool `export:"true"` ProxyProtocol *ProxyProtocol `export:"true"`
} }
// Redirect configures a redirection of an entry point to another, or to an URL // Redirect configures a redirection of an entry point to another, or to an URL
@ -443,3 +450,8 @@ type ForwardingTimeouts struct {
DialTimeout flaeg.Duration `description:"The amount of time to wait until a connection to a backend server can be established. Defaults to 30 seconds. If zero, no timeout exists" export:"true"` DialTimeout flaeg.Duration `description:"The amount of time to wait until a connection to a backend server can be established. Defaults to 30 seconds. If zero, no timeout exists" export:"true"`
ResponseHeaderTimeout flaeg.Duration `description:"The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists" export:"true"` ResponseHeaderTimeout flaeg.Duration `description:"The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists" export:"true"`
} }
// ProxyProtocol contains Proxy-Protocol configuration
type ProxyProtocol struct {
TrustedIPs []string
}

View file

@ -16,7 +16,7 @@ func Test_parseEntryPointsConfiguration(t *testing.T) {
}{ }{
{ {
name: "all parameters", name: "all parameters",
value: "Name:foo Address:bar TLS:goo TLS CA:car Redirect.EntryPoint:RedirectEntryPoint Redirect.Regex:RedirectRegex Redirect.Replacement:RedirectReplacement Compress:true WhiteListSourceRange:WhiteListSourceRange ProxyProtocol:true", value: "Name:foo Address:bar TLS:goo TLS CA:car Redirect.EntryPoint:RedirectEntryPoint Redirect.Regex:RedirectRegex Redirect.Replacement:RedirectReplacement Compress:true WhiteListSourceRange:WhiteListSourceRange ProxyProtocol.TrustedIPs:192.168.0.1",
expectedResult: map[string]string{ expectedResult: map[string]string{
"Name": "foo", "Name": "foo",
"Address": "bar", "Address": "bar",
@ -27,18 +27,10 @@ func Test_parseEntryPointsConfiguration(t *testing.T) {
"RedirectRegex": "RedirectRegex", "RedirectRegex": "RedirectRegex",
"RedirectReplacement": "RedirectReplacement", "RedirectReplacement": "RedirectReplacement",
"WhiteListSourceRange": "WhiteListSourceRange", "WhiteListSourceRange": "WhiteListSourceRange",
"ProxyProtocol": "true", "ProxyProtocol": "192.168.0.1",
"Compress": "true", "Compress": "true",
}, },
}, },
{
name: "proxy protocol on",
value: "Name:foo ProxyProtocol:on",
expectedResult: map[string]string{
"Name": "foo",
"ProxyProtocol": "on",
},
},
{ {
name: "compress on", name: "compress on",
value: "Name:foo Compress:on", value: "Name:foo Compress:on",
@ -142,7 +134,7 @@ func TestEntryPoints_Set(t *testing.T) {
}{ }{
{ {
name: "all parameters", name: "all parameters",
expression: "Name:foo Address:bar TLS:goo,gii TLS CA:car Redirect.EntryPoint:RedirectEntryPoint Redirect.Regex:RedirectRegex Redirect.Replacement:RedirectReplacement Compress:true WhiteListSourceRange:Range ProxyProtocol:true", expression: "Name:foo Address:bar TLS:goo,gii TLS CA:car Redirect.EntryPoint:RedirectEntryPoint Redirect.Regex:RedirectRegex Redirect.Replacement:RedirectReplacement Compress:true WhiteListSourceRange:Range ProxyProtocol.TrustedIPs:192.168.0.1",
expectedEntryPointName: "foo", expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{ expectedEntryPoint: &EntryPoint{
Address: "bar", Address: "bar",
@ -151,8 +143,10 @@ func TestEntryPoints_Set(t *testing.T) {
Regex: "RedirectRegex", Regex: "RedirectRegex",
Replacement: "RedirectReplacement", Replacement: "RedirectReplacement",
}, },
Compress: true, Compress: true,
ProxyProtocol: true, ProxyProtocol: &ProxyProtocol{
TrustedIPs: []string{"192.168.0.1"},
},
WhitelistSourceRange: []string{"Range"}, WhitelistSourceRange: []string{"Range"},
TLS: &TLS{ TLS: &TLS{
ClientCAFiles: []string{"car"}, ClientCAFiles: []string{"car"},

View file

@ -185,16 +185,20 @@ To enable IP whitelisting at the entrypoint level.
[entryPoints] [entryPoints]
[entryPoints.http] [entryPoints.http]
address = ":80" address = ":80"
whiteListSourceRange = ["127.0.0.1/32"] whiteListSourceRange = ["127.0.0.1/32", "192.168.1.7"]
``` ```
## ProxyProtocol Support ## ProxyProtocol Support
To enable [ProxyProtocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) support. To enable [ProxyProtocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) support.
Only IPs in `trustedIPs` will lead to remote client address replacement: you should declare your load-balancer IP or CIDR range here.
```toml ```toml
[entryPoints] [entryPoints]
[entryPoints.http] [entryPoints.http]
address = ":80" address = ":80"
proxyprotocol = true [entryPoints.http.proxyProtocol]
trustedIPs = ["127.0.0.1/32", "192.168.1.7"]
``` ```
²

View file

@ -6,80 +6,70 @@ import (
"net/http" "net/http"
"github.com/containous/traefik/log" "github.com/containous/traefik/log"
"github.com/containous/traefik/whitelist"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/urfave/negroni" "github.com/urfave/negroni"
) )
// IPWhitelister is a middleware that provides Checks of the Requesting IP against a set of Whitelists // IPWhiteLister is a middleware that provides Checks of the Requesting IP against a set of Whitelists
type IPWhitelister struct { type IPWhiteLister struct {
handler negroni.Handler handler negroni.Handler
whitelists []*net.IPNet whiteLister *whitelist.IP
} }
// NewIPWhitelister builds a new IPWhitelister given a list of CIDR-Strings to whitelist // NewIPWhitelister builds a new IPWhiteLister given a list of CIDR-Strings to whitelist
func NewIPWhitelister(whitelistStrings []string) (*IPWhitelister, error) { func NewIPWhitelister(whitelistStrings []string) (*IPWhiteLister, error) {
if len(whitelistStrings) == 0 { if len(whitelistStrings) == 0 {
return nil, errors.New("no whitelists provided") return nil, errors.New("no whitelists provided")
} }
whitelister := IPWhitelister{} whiteLister := IPWhiteLister{}
for _, whitelistString := range whitelistStrings { ip, err := whitelist.NewIP(whitelistStrings)
_, whitelist, err := net.ParseCIDR(whitelistString) if err != nil {
if err != nil { return nil, fmt.Errorf("parsing CIDR whitelist %s: %v", whitelistStrings, err)
return nil, fmt.Errorf("parsing CIDR whitelist %s: %v", whitelist, err)
}
whitelister.whitelists = append(whitelister.whitelists, whitelist)
} }
whiteLister.whiteLister = ip
whitelister.handler = negroni.HandlerFunc(whitelister.handle) whiteLister.handler = negroni.HandlerFunc(whiteLister.handle)
log.Debugf("configured %u IP whitelists: %s", len(whitelister.whitelists), whitelister.whitelists) log.Debugf("configured %u IP whitelists: %s", len(whitelistStrings), whitelistStrings)
return &whitelister, nil return &whiteLister, nil
} }
func (whitelister *IPWhitelister) handle(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { func (wl *IPWhiteLister) handle(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
remoteIP, err := ipFromRemoteAddr(r.RemoteAddr) ipAddress, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil { if err != nil {
log.Warnf("unable to parse remote-address from header: %s - rejecting", r.RemoteAddr) log.Warnf("unable to parse remote-address from header: %s - rejecting", r.RemoteAddr)
reject(w) reject(w)
return return
} }
for _, whitelist := range whitelister.whitelists { allowed, ip, err := wl.whiteLister.Contains(ipAddress)
if whitelist.Contains(*remoteIP) { if err != nil {
log.Debugf("source-IP %s matched whitelist %s - passing", remoteIP, whitelist) log.Debugf("source-IP %s matched none of the whitelists - rejecting", ipAddress)
next.ServeHTTP(w, r) reject(w)
return return
}
} }
log.Debugf("source-IP %s matched none of the whitelists - rejecting", remoteIP) if allowed {
log.Debugf("source-IP %s matched whitelist %s - passing", ipAddress, wl.whiteLister)
next.ServeHTTP(w, r)
return
}
log.Debugf("source-IP %s matched none of the whitelists - rejecting", ip)
reject(w) reject(w)
} }
func (wl *IPWhiteLister) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
wl.handler.ServeHTTP(rw, r, next)
}
func reject(w http.ResponseWriter) { func reject(w http.ResponseWriter) {
statusCode := http.StatusForbidden statusCode := http.StatusForbidden
w.WriteHeader(statusCode) w.WriteHeader(statusCode)
w.Write([]byte(http.StatusText(statusCode))) w.Write([]byte(http.StatusText(statusCode)))
} }
func ipFromRemoteAddr(addr string) (*net.IP, error) {
ip, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, fmt.Errorf("can't extract IP/Port from address %s: %s", addr, err)
}
userIP := net.ParseIP(ip)
if userIP == nil {
return nil, fmt.Errorf("can't parse IP from address %s", ip)
}
return &userIP, nil
}
func (whitelister *IPWhitelister) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
whitelister.handler.ServeHTTP(rw, r, next)
}

View file

@ -6,6 +6,7 @@ import (
"crypto/x509" "crypto/x509"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"io/ioutil" "io/ioutil"
"net" "net"
"net/http" "net/http"
@ -32,6 +33,7 @@ import (
"github.com/containous/traefik/safe" "github.com/containous/traefik/safe"
"github.com/containous/traefik/server/cookie" "github.com/containous/traefik/server/cookie"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"github.com/containous/traefik/whitelist"
"github.com/streamrail/concurrent-map" "github.com/streamrail/concurrent-map"
thoas_stats "github.com/thoas/stats" thoas_stats "github.com/thoas/stats"
"github.com/urfave/negroni" "github.com/urfave/negroni"
@ -652,8 +654,22 @@ func (server *Server) prepareServer(entryPointName string, entryPoint *configura
return nil, nil, err return nil, nil, err
} }
if entryPoint.ProxyProtocol { if entryPoint.ProxyProtocol != nil {
listener = &proxyproto.Listener{Listener: listener} IPs, err := whitelist.NewIP(entryPoint.ProxyProtocol.TrustedIPs)
if err != nil {
return nil, nil, fmt.Errorf("Error creating whitelist: %s", err)
}
log.Infof("Enabling ProxyProtocol for trusted IPs %v", entryPoint.ProxyProtocol.TrustedIPs)
listener = &proxyproto.Listener{
Listener: listener,
SourceCheck: func(addr net.Addr) (bool, error) {
ip, ok := addr.(*net.TCPAddr)
if !ok {
return false, fmt.Errorf("Type error %v", addr)
}
return IPs.ContainsIP(ip.IP)
},
}
} }
return &http.Server{ return &http.Server{

View file

@ -355,7 +355,7 @@ func TestNewServerWithWhitelistSourceRange(t *testing.T) {
"foo", "foo",
}, },
middlewareConfigured: false, middlewareConfigured: false,
errMessage: "parsing CIDR whitelist <nil>: invalid CIDR address: foo", errMessage: "parsing CIDR whitelist [foo]: parsing CIDR whitelist <nil>: invalid CIDR address: foo",
}, },
} }
@ -536,7 +536,7 @@ func TestServerEntrypointWhitelistConfig(t *testing.T) {
handler := srvEntryPoint.httpServer.Handler.(*negroni.Negroni) handler := srvEntryPoint.httpServer.Handler.(*negroni.Negroni)
found := false found := false
for _, handler := range handler.Handlers() { for _, handler := range handler.Handlers() {
if reflect.TypeOf(handler) == reflect.TypeOf((*middlewares.IPWhitelister)(nil)) { if reflect.TypeOf(handler) == reflect.TypeOf((*middlewares.IPWhiteLister)(nil)) {
found = true found = true
} }
} }

75
whitelist/ip.go Normal file
View file

@ -0,0 +1,75 @@
package whitelist
import (
"fmt"
"net"
"github.com/pkg/errors"
)
// IP allows to check that addresses are in a white list
type IP struct {
whiteListsIPs []*net.IP
whiteListsNet []*net.IPNet
}
// NewIP builds a new IP given a list of CIDR-Strings to whitelist
func NewIP(whitelistStrings []string) (*IP, error) {
if len(whitelistStrings) == 0 {
return nil, errors.New("no whiteListsNet provided")
}
ip := IP{}
for _, whitelistString := range whitelistStrings {
ipAddr := net.ParseIP(whitelistString)
if ipAddr != nil {
ip.whiteListsIPs = append(ip.whiteListsIPs, &ipAddr)
} else {
_, whitelist, err := net.ParseCIDR(whitelistString)
if err != nil {
return nil, fmt.Errorf("parsing CIDR whitelist %s: %v", whitelist, err)
}
ip.whiteListsNet = append(ip.whiteListsNet, whitelist)
}
}
return &ip, nil
}
// Contains checks if provided address is in the white list
func (ip *IP) Contains(addr string) (bool, net.IP, error) {
ipAddr, err := ipFromRemoteAddr(addr)
if err != nil {
return false, nil, fmt.Errorf("unable to parse address: %s: %s", addr, err)
}
contains, err := ip.ContainsIP(ipAddr)
return contains, ipAddr, err
}
// ContainsIP checks if provided address is in the white list
func (ip *IP) ContainsIP(addr net.IP) (bool, error) {
for _, whiteListIP := range ip.whiteListsIPs {
if whiteListIP.Equal(addr) {
return true, nil
}
}
for _, whiteListNet := range ip.whiteListsNet {
if whiteListNet.Contains(addr) {
return true, nil
}
}
return false, nil
}
func ipFromRemoteAddr(addr string) (net.IP, error) {
userIP := net.ParseIP(addr)
if userIP == nil {
return nil, fmt.Errorf("can't parse IP from address %s", addr)
}
return userIP, nil
}

View file

@ -1,19 +1,14 @@
package middlewares package whitelist
import ( import (
"fmt"
"net" "net"
"net/http"
"net/http/httptest"
"testing" "testing"
"github.com/containous/traefik/testhelpers"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/urfave/negroni"
) )
func TestNewIPWhitelister(t *testing.T) { func TestNew(t *testing.T) {
cases := []struct { cases := []struct {
desc string desc string
whitelistStrings []string whitelistStrings []string
@ -24,12 +19,12 @@ func TestNewIPWhitelister(t *testing.T) {
desc: "nil whitelist", desc: "nil whitelist",
whitelistStrings: nil, whitelistStrings: nil,
expectedWhitelists: nil, expectedWhitelists: nil,
errMessage: "no whitelists provided", errMessage: "no whiteListsNet provided",
}, { }, {
desc: "empty whitelist", desc: "empty whitelist",
whitelistStrings: []string{}, whitelistStrings: []string{},
expectedWhitelists: nil, expectedWhitelists: nil,
errMessage: "no whitelists provided", errMessage: "no whiteListsNet provided",
}, { }, {
desc: "whitelist containing empty string", desc: "whitelist containing empty string",
whitelistStrings: []string{ whitelistStrings: []string{
@ -80,12 +75,12 @@ func TestNewIPWhitelister(t *testing.T) {
test := test test := test
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
whitelister, err := NewIPWhitelister(test.whitelistStrings) whitelister, err := NewIP(test.whitelistStrings)
if test.errMessage != "" { if test.errMessage != "" {
require.EqualError(t, err, test.errMessage) require.EqualError(t, err, test.errMessage)
} else { } else {
require.NoError(t, err) require.NoError(t, err)
for index, actual := range whitelister.whitelists { for index, actual := range whitelister.whiteListsNet {
expected := test.expectedWhitelists[index] expected := test.expectedWhitelists[index]
assert.Equal(t, expected.IP, actual.IP) assert.Equal(t, expected.IP, actual.IP)
assert.Equal(t, expected.Mask.String(), actual.Mask.String()) assert.Equal(t, expected.Mask.String(), actual.Mask.String())
@ -95,7 +90,7 @@ func TestNewIPWhitelister(t *testing.T) {
} }
} }
func TestIPWhitelisterHandle(t *testing.T) { func TestIsAllowed(t *testing.T) {
cases := []struct { cases := []struct {
desc string desc string
whitelistStrings []string whitelistStrings []string
@ -122,6 +117,23 @@ func TestIPWhitelisterHandle(t *testing.T) {
}, },
{ {
desc: "IPv4 single IP", desc: "IPv4 single IP",
whitelistStrings: []string{
"8.8.8.8",
},
passIPs: []string{
"8.8.8.8",
},
rejectIPs: []string{
"8.8.8.7",
"8.8.8.9",
"8.8.8.0",
"8.8.8.255",
"4.4.4.4",
"127.0.0.1",
},
},
{
desc: "IPv4 Net single IP",
whitelistStrings: []string{ whitelistStrings: []string{
"8.8.8.8/32", "8.8.8.8/32",
}, },
@ -167,16 +179,16 @@ func TestIPWhitelisterHandle(t *testing.T) {
"2a03:4000:6:d080::/64", "2a03:4000:6:d080::/64",
}, },
passIPs: []string{ passIPs: []string{
"[2a03:4000:6:d080::]", "2a03:4000:6:d080::",
"[2a03:4000:6:d080::1]", "2a03:4000:6:d080::1",
"[2a03:4000:6:d080:dead:beef:ffff:ffff]", "2a03:4000:6:d080:dead:beef:ffff:ffff",
"[2a03:4000:6:d080::42]", "2a03:4000:6:d080::42",
}, },
rejectIPs: []string{ rejectIPs: []string{
"[2a03:4000:7:d080::]", "2a03:4000:7:d080::",
"[2a03:4000:7:d080::1]", "2a03:4000:7:d080::1",
"[fe80::]", "fe80::",
"[4242::1]", "4242::1",
}, },
}, },
{ {
@ -185,12 +197,12 @@ func TestIPWhitelisterHandle(t *testing.T) {
"2a03:4000:6:d080::42/128", "2a03:4000:6:d080::42/128",
}, },
passIPs: []string{ passIPs: []string{
"[2a03:4000:6:d080::42]", "2a03:4000:6:d080::42",
}, },
rejectIPs: []string{ rejectIPs: []string{
"[2a03:4000:6:d080::1]", "2a03:4000:6:d080::1",
"[2a03:4000:6:d080:dead:beef:ffff:ffff]", "2a03:4000:6:d080:dead:beef:ffff:ffff",
"[2a03:4000:6:d080::43]", "2a03:4000:6:d080::43",
}, },
}, },
{ {
@ -200,18 +212,18 @@ func TestIPWhitelisterHandle(t *testing.T) {
"fe80::/16", "fe80::/16",
}, },
passIPs: []string{ passIPs: []string{
"[2a03:4000:6:d080::]", "2a03:4000:6:d080::",
"[2a03:4000:6:d080::1]", "2a03:4000:6:d080::1",
"[2a03:4000:6:d080:dead:beef:ffff:ffff]", "2a03:4000:6:d080:dead:beef:ffff:ffff",
"[2a03:4000:6:d080::42]", "2a03:4000:6:d080::42",
"[fe80::1]", "fe80::1",
"[fe80:aa00:00bb:4232:ff00:eeee:00ff:1111]", "fe80:aa00:00bb:4232:ff00:eeee:00ff:1111",
"[fe80::fe80]", "fe80::fe80",
}, },
rejectIPs: []string{ rejectIPs: []string{
"[2a03:4000:7:d080::]", "2a03:4000:7:d080::",
"[2a03:4000:7:d080::1]", "2a03:4000:7:d080::1",
"[4242::1]", "4242::1",
}, },
}, },
{ {
@ -223,13 +235,13 @@ func TestIPWhitelisterHandle(t *testing.T) {
"8.8.8.8/8", "8.8.8.8/8",
}, },
passIPs: []string{ passIPs: []string{
"[2a03:4000:6:d080::]", "2a03:4000:6:d080::",
"[2a03:4000:6:d080::1]", "2a03:4000:6:d080::1",
"[2a03:4000:6:d080:dead:beef:ffff:ffff]", "2a03:4000:6:d080:dead:beef:ffff:ffff",
"[2a03:4000:6:d080::42]", "2a03:4000:6:d080::42",
"[fe80::1]", "fe80::1",
"[fe80:aa00:00bb:4232:ff00:eeee:00ff:1111]", "fe80:aa00:00bb:4232:ff00:eeee:00ff:1111",
"[fe80::fe80]", "fe80::fe80",
"1.2.3.1", "1.2.3.1",
"1.2.3.32", "1.2.3.32",
"1.2.3.156", "1.2.3.156",
@ -240,9 +252,9 @@ func TestIPWhitelisterHandle(t *testing.T) {
"8.255.255.255", "8.255.255.255",
}, },
rejectIPs: []string{ rejectIPs: []string{
"[2a03:4000:7:d080::]", "2a03:4000:7:d080::",
"[2a03:4000:7:d080::1]", "2a03:4000:7:d080::1",
"[4242::1]", "4242::1",
"1.2.16.1", "1.2.16.1",
"1.2.32.1", "1.2.32.1",
"127.0.0.1", "127.0.0.1",
@ -256,13 +268,6 @@ func TestIPWhitelisterHandle(t *testing.T) {
"127.0.0.1/32", "127.0.0.1/32",
}, },
passIPs: nil, passIPs: nil,
rejectIPs: []string{
"foo",
"10.0.0.350",
"fe:::80",
"",
"\\&$§&/(",
},
}, },
} }
@ -270,38 +275,44 @@ func TestIPWhitelisterHandle(t *testing.T) {
test := test test := test
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
whitelister, err := NewIPWhitelister(test.whitelistStrings) whiteLister, err := NewIP(test.whitelistStrings)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, whitelister) require.NotNil(t, whiteLister)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "traefik")
})
n := negroni.New(whitelister)
n.UseHandler(handler)
for _, testIP := range test.passIPs { for _, testIP := range test.passIPs {
req := testhelpers.MustNewRequest(http.MethodGet, "/", nil) allowed, ip, err := whiteLister.Contains(testIP)
require.NoError(t, err)
req.RemoteAddr = testIP + ":2342" require.NotNil(t, ip, err)
recorder := httptest.NewRecorder() assert.True(t, allowed, testIP+" should have passed "+test.desc)
n.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusOK, recorder.Code, testIP+" should have passed "+test.desc)
assert.Contains(t, recorder.Body.String(), "traefik")
} }
for _, testIP := range test.rejectIPs { for _, testIP := range test.rejectIPs {
req := testhelpers.MustNewRequest(http.MethodGet, "/", nil) allowed, ip, err := whiteLister.Contains(testIP)
require.NoError(t, err)
req.RemoteAddr = testIP + ":2342" require.NotNil(t, ip, err)
recorder := httptest.NewRecorder() assert.False(t, allowed, testIP+" should not have passed "+test.desc)
n.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusForbidden, recorder.Code, testIP+" should not have passed "+test.desc)
assert.NotContains(t, recorder.Body.String(), "traefik")
} }
}) })
} }
} }
func TestBrokenIPs(t *testing.T) {
brokenIPs := []string{
"foo",
"10.0.0.350",
"fe:::80",
"",
"\\&$§&/(",
}
whiteLister, err := NewIP([]string{"1.2.3.4/24"})
require.NoError(t, err)
for _, testIP := range brokenIPs {
_, ip, err := whiteLister.Contains(testIP)
assert.Error(t, err)
require.Nil(t, ip, err)
}
}