IPStrategy for selecting IP in whitelist
This commit is contained in:
parent
1ec4e03738
commit
00728e711c
65 changed files with 2444 additions and 1837 deletions
|
@ -80,7 +80,11 @@ func TestDo_globalConfiguration(t *testing.T) {
|
|||
TrustForwardHeader: true,
|
||||
},
|
||||
},
|
||||
WhitelistSourceRange: []string{"foo WhitelistSourceRange 1", "foo WhitelistSourceRange 2", "foo WhitelistSourceRange 3"},
|
||||
WhiteList: &types.WhiteList{
|
||||
SourceRange: []string{
|
||||
"127.0.0.1/32",
|
||||
},
|
||||
},
|
||||
Compress: &configuration.Compress{},
|
||||
ProxyProtocol: &configuration.ProxyProtocol{
|
||||
TrustedIPs: []string{"127.0.0.1/32", "192.168.0.1"},
|
||||
|
@ -125,7 +129,11 @@ func TestDo_globalConfiguration(t *testing.T) {
|
|||
TrustForwardHeader: true,
|
||||
},
|
||||
},
|
||||
WhitelistSourceRange: []string{"fii WhitelistSourceRange 1", "fii WhitelistSourceRange 2", "fii WhitelistSourceRange 3"},
|
||||
WhiteList: &types.WhiteList{
|
||||
SourceRange: []string{
|
||||
"127.0.0.1/32",
|
||||
},
|
||||
},
|
||||
Compress: &configuration.Compress{},
|
||||
ProxyProtocol: &configuration.ProxyProtocol{
|
||||
TrustedIPs: []string{"127.0.0.1/32", "192.168.0.1"},
|
||||
|
|
|
@ -179,7 +179,13 @@ var _templatesConsul_catalogTmpl = []byte(`[backends]
|
|||
sourceRange = [{{range $whitelist.SourceRange }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
||||
{{if $whitelist.IPStrategy }}
|
||||
[frontends."frontend-{{ $service.ServiceName }}".whiteList.IPStrategy]
|
||||
depth = {{ $whitelist.IPStrategy.Depth }}
|
||||
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{ $redirect := getRedirect $service.TraefikLabels }}
|
||||
|
@ -418,7 +424,13 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
|
|||
sourceRange = [{{range $whitelist.SourceRange }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
||||
{{if $whitelist.IPStrategy }}
|
||||
[frontends."frontend-{{ $frontendName }}".whiteList.IPStrategy]
|
||||
depth = {{ $whitelist.IPStrategy.Depth }}
|
||||
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{ $redirect := getRedirect $container.SegmentLabels }}
|
||||
|
@ -657,7 +669,13 @@ var _templatesEcsTmpl = []byte(`[backends]
|
|||
sourceRange = [{{range $whitelist.SourceRange }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
||||
{{if $whitelist.IPStrategy }}
|
||||
[frontends."frontend-{{ $serviceName }}".whiteList.IPStrategy]
|
||||
depth = {{ $whitelist.IPStrategy.Depth }}
|
||||
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{ $redirect := getRedirect $instance.TraefikLabels }}
|
||||
|
@ -904,10 +922,16 @@ var _templatesKubernetesTmpl = []byte(`[backends]
|
|||
|
||||
{{if $frontend.WhiteList }}
|
||||
[frontends."{{ $frontendName }}".whiteList]
|
||||
sourceRange = [{{range $frontend.WhiteList.SourceRange }}
|
||||
sourceRange = [{{range $frontend.Whitelist.SourceRange }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
useXForwardedFor = {{ $frontend.WhiteList.UseXForwardedFor }}
|
||||
{{if $frontend.Whitelist.IPStrategy }}
|
||||
[frontends."{{ $frontendName }}".whiteList.IPStrategy]
|
||||
depth = {{ $frontend.Whitelist.IPStrategy.Depth }}
|
||||
excludedIPs = [{{range $frontend.Whitelist.IPStrategy.ExcludedIPs }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{if $frontend.Redirect }}
|
||||
|
@ -1148,7 +1172,13 @@ var _templatesKvTmpl = []byte(`[backends]
|
|||
sourceRange = [{{range $whitelist.SourceRange }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
||||
{{if $whitelist.IPStrategy }}
|
||||
[frontends."{{ $frontendName }}".whiteList.IPStrategy]
|
||||
depth = {{ $whitelist.IPStrategy.Depth }}
|
||||
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{ $redirect := getRedirect $frontend }}
|
||||
|
@ -1404,7 +1434,13 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
|
|||
sourceRange = [{{range $whitelist.SourceRange }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
||||
{{if $whitelist.IPStrategy }}
|
||||
[frontends."{{ $frontendName }}".whiteList.IPStrategy]
|
||||
depth = {{ $whitelist.IPStrategy.Depth }}
|
||||
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{ $redirect := getRedirect $app.SegmentLabels }}
|
||||
|
@ -1645,7 +1681,13 @@ var _templatesMesosTmpl = []byte(`[backends]
|
|||
sourceRange = [{{range $whitelist.SourceRange }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
||||
{{if $whitelist.IPStrategy }}
|
||||
[frontends."frontend-{{ $frontendName }}".whiteList.IPStrategy]
|
||||
depth = {{ $whitelist.IPStrategy.Depth }}
|
||||
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{ $redirect := getRedirect $app.TraefikLabels }}
|
||||
|
@ -1908,7 +1950,13 @@ var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
|
|||
sourceRange = [{{range $whitelist.SourceRange }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
||||
{{if $whitelist.IPStrategy }}
|
||||
[frontends."frontend-{{ $frontendName }}".whiteList.IPStrategy]
|
||||
depth = {{ $whitelist.IPStrategy.Depth }}
|
||||
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{ $redirect := getRedirect $service.SegmentLabels }}
|
||||
|
|
|
@ -108,7 +108,7 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
|
|||
if len(gc.EntryPoints) == 0 {
|
||||
gc.EntryPoints = map[string]*EntryPoint{"http": {
|
||||
Address: ":80",
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
ForwardedHeaders: &ForwardedHeaders{},
|
||||
}}
|
||||
gc.DefaultEntryPoints = []string{"http"}
|
||||
}
|
||||
|
@ -126,18 +126,7 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
|
|||
entryPoint := gc.EntryPoints[entryPointName]
|
||||
// ForwardedHeaders must be remove in the next breaking version
|
||||
if entryPoint.ForwardedHeaders == nil {
|
||||
entryPoint.ForwardedHeaders = &ForwardedHeaders{Insecure: true}
|
||||
}
|
||||
|
||||
if len(entryPoint.WhitelistSourceRange) > 0 {
|
||||
log.Warnf("Deprecated configuration found: %s. Please use %s.", "whiteListSourceRange", "whiteList.sourceRange")
|
||||
|
||||
if entryPoint.WhiteList == nil {
|
||||
entryPoint.WhiteList = &types.WhiteList{
|
||||
SourceRange: entryPoint.WhitelistSourceRange,
|
||||
}
|
||||
entryPoint.WhitelistSourceRange = nil
|
||||
}
|
||||
entryPoint.ForwardedHeaders = &ForwardedHeaders{}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package configuration
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
|
@ -15,16 +16,15 @@ type EntryPoint struct {
|
|||
TLS *tls.TLS `export:"true"`
|
||||
Redirect *types.Redirect `export:"true"`
|
||||
Auth *types.Auth `export:"true"`
|
||||
WhitelistSourceRange []string // Deprecated
|
||||
WhiteList *types.WhiteList `export:"true"`
|
||||
Compress *Compress `export:"true"`
|
||||
ProxyProtocol *ProxyProtocol `export:"true"`
|
||||
ForwardedHeaders *ForwardedHeaders `export:"true"`
|
||||
ClientIPStrategy *types.IPStrategy `export:"true"`
|
||||
}
|
||||
|
||||
// Compress contains compress configuration
|
||||
type Compress struct {
|
||||
}
|
||||
type Compress struct{}
|
||||
|
||||
// ProxyProtocol contains Proxy-Protocol configuration
|
||||
type ProxyProtocol struct {
|
||||
|
@ -68,11 +68,6 @@ func (ep *EntryPoints) Type() string {
|
|||
func (ep *EntryPoints) Set(value string) error {
|
||||
result := parseEntryPointsConfiguration(value)
|
||||
|
||||
var whiteListSourceRange []string
|
||||
if len(result["whitelistsourcerange"]) > 0 {
|
||||
whiteListSourceRange = strings.Split(result["whitelistsourcerange"], ",")
|
||||
}
|
||||
|
||||
var compress *Compress
|
||||
if len(result["compress"]) > 0 {
|
||||
compress = &Compress{}
|
||||
|
@ -89,24 +84,37 @@ func (ep *EntryPoints) Set(value string) error {
|
|||
Auth: makeEntryPointAuth(result),
|
||||
Redirect: makeEntryPointRedirect(result),
|
||||
Compress: compress,
|
||||
WhitelistSourceRange: whiteListSourceRange,
|
||||
WhiteList: makeWhiteList(result),
|
||||
ProxyProtocol: makeEntryPointProxyProtocol(result),
|
||||
ForwardedHeaders: makeEntryPointForwardedHeaders(result),
|
||||
ClientIPStrategy: makeIPStrategy("clientipstrategy", result),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeWhiteList(result map[string]string) *types.WhiteList {
|
||||
var wl *types.WhiteList
|
||||
if rawRange, ok := result["whitelist_sourcerange"]; ok {
|
||||
wl = &types.WhiteList{
|
||||
return &types.WhiteList{
|
||||
SourceRange: strings.Split(rawRange, ","),
|
||||
UseXForwardedFor: toBool(result, "whitelist_usexforwardedfor"),
|
||||
IPStrategy: makeIPStrategy("whitelist_ipstrategy", result),
|
||||
}
|
||||
}
|
||||
return wl
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeIPStrategy(prefix string, result map[string]string) *types.IPStrategy {
|
||||
depth := toInt(result, prefix+"_depth")
|
||||
excludedIPs := result[prefix+"_excludedips"]
|
||||
|
||||
if depth == 0 && len(excludedIPs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &types.IPStrategy{
|
||||
Depth: depth,
|
||||
ExcludedIPs: strings.Split(excludedIPs, ","),
|
||||
}
|
||||
}
|
||||
|
||||
func makeEntryPointAuth(result map[string]string) *types.Auth {
|
||||
|
@ -184,15 +192,14 @@ func makeEntryPointProxyProtocol(result map[string]string) *ProxyProtocol {
|
|||
}
|
||||
|
||||
if proxyProtocol != nil && proxyProtocol.Insecure {
|
||||
log.Warn("ProxyProtocol.Insecure:true is dangerous. Please use 'ProxyProtocol.TrustedIPs:IPs' and remove 'ProxyProtocol.Insecure:true'")
|
||||
log.Warn("ProxyProtocol.insecure:true is dangerous. Please use 'ProxyProtocol.TrustedIPs:IPs' and remove 'ProxyProtocol.insecure:true'")
|
||||
}
|
||||
|
||||
return proxyProtocol
|
||||
}
|
||||
|
||||
func makeEntryPointForwardedHeaders(result map[string]string) *ForwardedHeaders {
|
||||
// TODO must be changed to false by default in the next breaking version.
|
||||
forwardedHeaders := &ForwardedHeaders{Insecure: true}
|
||||
forwardedHeaders := &ForwardedHeaders{}
|
||||
if _, ok := result["forwardedheaders_insecure"]; ok {
|
||||
forwardedHeaders.Insecure = toBool(result, "forwardedheaders_insecure")
|
||||
}
|
||||
|
@ -300,3 +307,14 @@ func toBool(conf map[string]string, key string) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func toInt(conf map[string]string, key string) int {
|
||||
if val, ok := conf[key]; ok {
|
||||
intVal, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return intVal
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
|
|
@ -45,9 +45,11 @@ func Test_parseEntryPointsConfiguration(t *testing.T) {
|
|||
"Auth.Forward.TLS.Cert:path/to/foo.cert " +
|
||||
"Auth.Forward.TLS.Key:path/to/foo.key " +
|
||||
"Auth.Forward.TLS.InsecureSkipVerify:true " +
|
||||
"WhiteListSourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " +
|
||||
"whiteList.sourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " +
|
||||
"whiteList.useXForwardedFor:true ",
|
||||
"WhiteList.SourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " +
|
||||
"WhiteList.IPStrategy.depth:3 " +
|
||||
"WhiteList.IPStrategy.ExcludedIPs:10.0.0.3/24,20.0.0.3/24 " +
|
||||
"ClientIPStrategy.depth:3 " +
|
||||
"ClientIPStrategy.ExcludedIPs:10.0.0.3/24,20.0.0.3/24 ",
|
||||
expectedResult: map[string]string{
|
||||
"address": ":8000",
|
||||
"auth_basic_users": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
|
@ -77,9 +79,11 @@ func Test_parseEntryPointsConfiguration(t *testing.T) {
|
|||
"tls_acme": "TLS",
|
||||
"tls_ciphersuites": "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
|
||||
"tls_minversion": "VersionTLS11",
|
||||
"whitelistsourcerange": "10.42.0.0/16,152.89.1.33/32,afed:be44::/16",
|
||||
"whitelist_sourcerange": "10.42.0.0/16,152.89.1.33/32,afed:be44::/16",
|
||||
"whitelist_usexforwardedfor": "true",
|
||||
"whitelist_ipstrategy_depth": "3",
|
||||
"whitelist_ipstrategy_excludedips": "10.0.0.3/24,20.0.0.3/24",
|
||||
"clientipstrategy_depth": "3",
|
||||
"clientipstrategy_excludedips": "10.0.0.3/24,20.0.0.3/24",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -206,9 +210,11 @@ func TestEntryPoints_Set(t *testing.T) {
|
|||
"Auth.Forward.TLS.Cert:path/to/foo.cert " +
|
||||
"Auth.Forward.TLS.Key:path/to/foo.key " +
|
||||
"Auth.Forward.TLS.InsecureSkipVerify:true " +
|
||||
"WhiteListSourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " +
|
||||
"whiteList.sourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " +
|
||||
"whiteList.useXForwardedFor:true ",
|
||||
"WhiteList.SourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " +
|
||||
"WhiteList.IPStrategy.depth:3 " +
|
||||
"WhiteList.IPStrategy.ExcludedIPs:10.0.0.3/24,20.0.0.3/24 " +
|
||||
"ClientIPStrategy.depth:3 " +
|
||||
"ClientIPStrategy.ExcludedIPs:10.0.0.3/24,20.0.0.3/24 ",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
Address: ":8000",
|
||||
|
@ -265,18 +271,19 @@ func TestEntryPoints_Set(t *testing.T) {
|
|||
},
|
||||
HeaderField: "X-WebAuth-User",
|
||||
},
|
||||
WhitelistSourceRange: []string{
|
||||
"10.42.0.0/16",
|
||||
"152.89.1.33/32",
|
||||
"afed:be44::/16",
|
||||
},
|
||||
WhiteList: &types.WhiteList{
|
||||
SourceRange: []string{
|
||||
"10.42.0.0/16",
|
||||
"152.89.1.33/32",
|
||||
"afed:be44::/16",
|
||||
},
|
||||
UseXForwardedFor: true,
|
||||
IPStrategy: &types.IPStrategy{
|
||||
Depth: 3,
|
||||
ExcludedIPs: []string{
|
||||
"10.0.0.3/24",
|
||||
"20.0.0.3/24",
|
||||
},
|
||||
},
|
||||
},
|
||||
Compress: &Compress{},
|
||||
ProxyProtocol: &ProxyProtocol{
|
||||
|
@ -290,6 +297,13 @@ func TestEntryPoints_Set(t *testing.T) {
|
|||
"20.0.0.3/24",
|
||||
},
|
||||
},
|
||||
ClientIPStrategy: &types.IPStrategy{
|
||||
Depth: 3,
|
||||
ExcludedIPs: []string{
|
||||
"10.0.0.3/24",
|
||||
"20.0.0.3/24",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -307,7 +321,7 @@ func TestEntryPoints_Set(t *testing.T) {
|
|||
"redirect.replacement:http://mydomain/$1 " +
|
||||
"redirect.permanent:true " +
|
||||
"compress:true " +
|
||||
"whiteListSourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " +
|
||||
"whiteList.sourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " +
|
||||
"proxyProtocol.TrustedIPs:192.168.0.1 " +
|
||||
"forwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 " +
|
||||
"auth.basic.users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 " +
|
||||
|
@ -375,11 +389,13 @@ func TestEntryPoints_Set(t *testing.T) {
|
|||
},
|
||||
HeaderField: "X-WebAuth-User",
|
||||
},
|
||||
WhitelistSourceRange: []string{
|
||||
WhiteList: &types.WhiteList{
|
||||
SourceRange: []string{
|
||||
"10.42.0.0/16",
|
||||
"152.89.1.33/32",
|
||||
"afed:be44::/16",
|
||||
},
|
||||
},
|
||||
Compress: &Compress{},
|
||||
ProxyProtocol: &ProxyProtocol{
|
||||
Insecure: false,
|
||||
|
@ -399,12 +415,12 @@ func TestEntryPoints_Set(t *testing.T) {
|
|||
expression: "Name:foo",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ForwardedHeaders insecure true",
|
||||
expression: "Name:foo ForwardedHeaders.Insecure:true",
|
||||
expression: "Name:foo ForwardedHeaders.insecure:true",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
|
@ -412,7 +428,7 @@ func TestEntryPoints_Set(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "ForwardedHeaders insecure false",
|
||||
expression: "Name:foo ForwardedHeaders.Insecure:false",
|
||||
expression: "Name:foo ForwardedHeaders.insecure:false",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: false},
|
||||
|
@ -430,19 +446,19 @@ func TestEntryPoints_Set(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "ProxyProtocol insecure true",
|
||||
expression: "Name:foo ProxyProtocol.Insecure:true",
|
||||
expression: "Name:foo ProxyProtocol.insecure:true",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
ForwardedHeaders: &ForwardedHeaders{},
|
||||
ProxyProtocol: &ProxyProtocol{Insecure: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ProxyProtocol insecure false",
|
||||
expression: "Name:foo ProxyProtocol.Insecure:false",
|
||||
expression: "Name:foo ProxyProtocol.insecure:false",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
ForwardedHeaders: &ForwardedHeaders{},
|
||||
ProxyProtocol: &ProxyProtocol{},
|
||||
},
|
||||
},
|
||||
|
@ -451,7 +467,7 @@ func TestEntryPoints_Set(t *testing.T) {
|
|||
expression: "Name:foo ProxyProtocol.TrustedIPs:10.0.0.3/24,20.0.0.3/24",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
ForwardedHeaders: &ForwardedHeaders{},
|
||||
ProxyProtocol: &ProxyProtocol{
|
||||
TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"},
|
||||
},
|
||||
|
@ -463,7 +479,7 @@ func TestEntryPoints_Set(t *testing.T) {
|
|||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
Compress: &Compress{},
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
ForwardedHeaders: &ForwardedHeaders{},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -472,7 +488,7 @@ func TestEntryPoints_Set(t *testing.T) {
|
|||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
Compress: &Compress{},
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
ForwardedHeaders: &ForwardedHeaders{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -16,9 +16,17 @@ func NewInternalRouterAggregator(globalConfiguration configuration.GlobalConfigu
|
|||
var serverMiddlewares []negroni.Handler
|
||||
|
||||
if globalConfiguration.EntryPoints[entryPointName].WhiteList != nil {
|
||||
ipWhitelistMiddleware, err := middlewares.NewIPWhiteLister(
|
||||
globalConfiguration.EntryPoints[entryPointName].WhiteList.SourceRange,
|
||||
globalConfiguration.EntryPoints[entryPointName].WhiteList.UseXForwardedFor)
|
||||
ipStrategy := globalConfiguration.EntryPoints[entryPointName].ClientIPStrategy
|
||||
if globalConfiguration.EntryPoints[entryPointName].WhiteList.IPStrategy != nil {
|
||||
ipStrategy = globalConfiguration.EntryPoints[entryPointName].WhiteList.IPStrategy
|
||||
}
|
||||
|
||||
strategy, err := ipStrategy.Get()
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating whitelist middleware: %s", err)
|
||||
}
|
||||
|
||||
ipWhitelistMiddleware, err := middlewares.NewIPWhiteLister(globalConfiguration.EntryPoints[entryPointName].WhiteList.SourceRange, strategy)
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating whitelist middleware: %s", err)
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ Additional settings can be defined using Consul Catalog tags.
|
|||
The default prefix is `traefik`.
|
||||
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|--------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `<prefix>.enable=false` | Disables this container in Træfik. |
|
||||
| `<prefix>.protocol=https` | Overrides the default `http` protocol. |
|
||||
| `<prefix>.weight=10` | Assigns this weight to the container. |
|
||||
|
@ -148,7 +148,9 @@ Additional settings can be defined using Consul Catalog tags.
|
|||
| `<prefix>.frontend.redirect.permanent=true` | Returns 301 instead of 302. |
|
||||
| `<prefix>.frontend.rule=EXPR` | Overrides the default frontend rule. Default: `Host:{{.ServiceName}}.{{.Domain}}`. |
|
||||
| `<prefix>.frontend.whiteList.sourceRange=RANGE` | Sets a list of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `<prefix>.frontend.whiteList.useXForwardedFor=true` | Uses `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
| `<prefix>.frontend.whiteList.ipStrategy=true` | Uses the default IPStrategy.<br>Can be used when there is an existing `clientIPStrategy` but you want the remote address for whitelisting. |
|
||||
| `<prefix>.frontend.whiteList.ipStrategy.depth=5` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| `<prefix>.frontend.whiteList.ipStrategy.excludedIPs=127.0.0.1` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
|
||||
### Custom Headers
|
||||
|
||||
|
|
|
@ -208,7 +208,7 @@ services:
|
|||
Labels can be used on containers to override default behavior.
|
||||
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|-------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.docker.network` | Overrides the default docker network to use for connections to the container. [1] |
|
||||
| `traefik.domain` | Sets the default domain for the frontend rules. |
|
||||
| `traefik.enable=false` | Disables this container in Træfik. |
|
||||
|
@ -266,7 +266,9 @@ Labels can be used on containers to override default behavior.
|
|||
| `traefik.frontend.redirect.permanent=true` | Returns 301 instead of 302. |
|
||||
| `traefik.frontend.rule=EXPR` | Overrides the default frontend rule. Default: `Host:{containerName}.{domain}` or `Host:{service}.{project_name}.{domain}` if you are using `docker-compose`. |
|
||||
| `traefik.frontend.whiteList.sourceRange=RANGE` | Sets a list of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access.<br>If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.frontend.whiteList.useXForwardedFor=true` | Uses `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
| `traefik.frontend.whiteList.ipStrategy=true` | Uses the default IPStrategy.<br>Can be used when there is an existing `clientIPStrategy` but you want the remote address for whitelisting. |
|
||||
| `traefik.frontend.whiteList.ipStrategy.depth=5` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| `traefik.frontend.whiteList.ipStrategy.excludedIPs=127.0.0.1` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
|
||||
[1] `traefik.docker.network`:
|
||||
If a container is linked to several networks, be sure to set the proper network name (you can check with `docker inspect <container_id>`) otherwise it will randomly pick one (depending on how docker is returning them).
|
||||
|
@ -278,7 +280,6 @@ To create `user:password` pair, it's possible to use this command:
|
|||
`echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g`.
|
||||
The result will be `user:$$apr1$$9Cv/OMGj$$ZomWQzuQbL.3TRCS81A1g/`, note additional symbol `$` makes escaping.
|
||||
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
| Label | Description |
|
||||
|
@ -320,7 +321,7 @@ You can define as many segments as ports exposed in a container.
|
|||
Segment labels override the default behavior.
|
||||
|
||||
| Label | Description |
|
||||
|---------------------------------------------------------------------------|---------------------------------------------------------------|
|
||||
|------------------------------------------------------------------------------|----------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
|
||||
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
|
||||
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
|
||||
|
@ -358,7 +359,9 @@ Segment labels override the default behavior.
|
|||
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Same as `traefik.frontend.redirect.permanent` |
|
||||
| `traefik.<segment_name>.frontend.rule=EXP` | Same as `traefik.frontend.rule` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Same as `traefik.frontend.whiteList.sourceRange` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Same as `traefik.frontend.whiteList.useXForwardedFor` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy=true` | Same as `traefik.frontend.whiteList.ipStrategy` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy.depth=5` | Same as `traefik.frontend.whiteList.ipStrategy.depth` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy.excludedIPs=127.0.0.1` | Same as `traefik.frontend.whiteList.ipStrategy.excludedIPs` |
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
|
|
|
@ -131,7 +131,7 @@ Træfik needs the following policy to read ECS information:
|
|||
Labels can be used on task containers to override default behavior:
|
||||
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|-------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.domain` | Sets the default domain for frontend rules. |
|
||||
| `traefik.enable=false` | Disables this container in Træfik. |
|
||||
| `traefik.port=80` | Overrides the default `port` value. Overrides `NetworkBindings` from Docker Container |
|
||||
|
@ -188,7 +188,9 @@ Labels can be used on task containers to override default behavior:
|
|||
| `traefik.frontend.redirect.permanent=true` | Returns 301 instead of 302. |
|
||||
| `traefik.frontend.rule=EXPR` | Overrides the default frontend rule. Default: `Host:{instance_name}.{domain}`. |
|
||||
| `traefik.frontend.whiteList.sourceRange=RANGE` | Sets a list of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.frontend.whiteList.useXForwardedFor=true` | Uses `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
| `traefik.frontend.whiteList.ipStrategy=true` | Uses the default IPStrategy.<br>Can be used when there is an existing `clientIPStrategy` but you want the remote address for whitelisting. |
|
||||
| `traefik.frontend.whiteList.ipStrategy.depth=5` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| `traefik.frontend.whiteList.ipStrategy.excludedIPs=127.0.0.1 ` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
|
||||
### Custom Headers
|
||||
|
||||
|
|
|
@ -85,7 +85,9 @@ Træfik can be configured with a file.
|
|||
|
||||
[frontends.frontend1.whiteList]
|
||||
sourceRange = ["10.42.0.0/16", "152.89.1.33/32", "afed:be44::/16"]
|
||||
useXForwardedFor = true
|
||||
[frontends.frontend1.whiteList.IPStrategy]
|
||||
depth = 6
|
||||
excludedIPs = ["152.89.1.33/32", "afed:be44::/16"]
|
||||
|
||||
[frontends.frontend1.routes]
|
||||
[frontends.frontend1.routes.route0]
|
||||
|
|
|
@ -141,7 +141,7 @@ If the service port defined in the ingress spec is 443, then the backend communi
|
|||
The following general annotations are applicable on the Ingress object:
|
||||
|
||||
| Annotation | Description |
|
||||
|---------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------|
|
||||
|---------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.ingress.kubernetes.io/buffering: <YML>` | (3) See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.ingress.kubernetes.io/error-pages: <YML>` | (1) See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.ingress.kubernetes.io/frontend-entry-points: http,https` | Override the default frontend endpoints. |
|
||||
|
@ -157,10 +157,12 @@ The following general annotations are applicable on the Ingress object:
|
|||
| `traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip` | Override the default frontend rule type. Only path related matchers can be used [(`Path`, `PathPrefix`, `PathStrip`, `PathPrefixStrip`)](/basics/#path-matcher-usage-guidelines). Note: ReplacePath is deprecated in this annotation, use the `traefik.ingress.kubernetes.io/request-modifier` annotation instead. Default: `PathPrefix`. |
|
||||
| `traefik.ingress.kubernetes.io/request-modifier: AddPrefix: /users` | Add a [request modifier](/basics/#modifiers) to the backend request. |
|
||||
| `traefik.ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"` | A comma-separated list of IP ranges permitted for access (6). |
|
||||
| `ingress.kubernetes.io/whitelist-x-forwarded-for: "true"` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
| `traefik.ingress.kubernetes.io/whiteList-ipstrategy=true` | Uses the default IPStrategy.<br>Can be used when there is an existing `clientIPStrategy` but you want the remote address for whitelisting. |
|
||||
| `traefik.ingress.kubernetes.io/whiteList-ipstrategy-depth=5` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| `traefik.ingress.kubernetes.io/whiteList-ipstrategy-excludedIPs=127.0.0. 1` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| `traefik.ingress.kubernetes.io/app-root: "/index.html"` | Redirects all requests for `/` to the defined path. (4) |
|
||||
| `traefik.ingress.kubernetes.io/service-weights: <YML>` | Set ingress backend weights specified as percentage or decimal numbers in YAML. (5)
|
||||
| `ingress.kubernetes.io/protocol: <NAME>` | Set the protocol Traefik will use to communicate with pods.
|
||||
| `traefik.ingress.kubernetes.io/service-weights: <YML>` | Set ingress backend weights specified as percentage or decimal numbers in YAML. (5) |
|
||||
| `ingress.kubernetes.io/protocol: <NAME>` | Set the protocol Traefik will use to communicate with pods. |
|
||||
|
||||
|
||||
<1> `traefik.ingress.kubernetes.io/error-pages` example:
|
||||
|
|
|
@ -194,7 +194,7 @@ They may be specified on one of two levels: Application or service.
|
|||
The following labels can be defined on Marathon applications. They adjust the behavior for the entire application.
|
||||
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|-----------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.domain` | Sets the default domain used for the frontend rules. |
|
||||
| `traefik.enable=false` | Disables this container in Træfik. |
|
||||
| `traefik.port=80` | Registers this port. Useful when the container exposes multiples ports. |
|
||||
|
@ -252,7 +252,9 @@ The following labels can be defined on Marathon applications. They adjust the be
|
|||
| `traefik.frontend.redirect.permanent=true` | Returns 301 instead of 302. |
|
||||
| `traefik.frontend.rule=EXPR` | Overrides the default frontend rule. Default: `Host:{sub_domain}.{domain}`. |
|
||||
| `traefik.frontend.whiteList.sourceRange=RANGE` | Sets a list of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.frontend.whiteList.useXForwardedFor=true` | Uses `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
| `traefik.frontend.whiteList.ipStrategy=true` | Uses the default IPStrategy.<br>Can be used when there is an existing `clientIPStrategy` but you want the remote address for whitelisting. |
|
||||
| `traefik.frontend.whiteList.ipStrategy.depth=5` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| `traefik.frontend.whiteList.ipStrategy.excludedIPs=127.0.0.1` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
|
@ -296,7 +298,7 @@ You can define as many segments as ports exposed in an application.
|
|||
Segment labels override the default behavior.
|
||||
|
||||
| Label | Description |
|
||||
|---------------------------------------------------------------------------|----------------------------------------------------------------|
|
||||
|------------------------------------------------------------------------------ |----------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
|
||||
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
|
||||
| `traefik.<segment_name>.portIndex=1` | Same as `traefik.portIndex` |
|
||||
|
@ -336,7 +338,9 @@ Segment labels override the default behavior.
|
|||
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Same as `traefik.frontend.redirect.permanent` |
|
||||
| `traefik.<segment_name>.frontend.rule=EXP` | Same as `traefik.frontend.rule` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Same as `traefik.frontend.whiteList.sourceRange` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Same as `traefik.frontend.whiteList.useXForwardedFor` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy=true` | Same as `traefik.frontend.whiteList.ipStrategy` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy.depth=5` | Same as `traefik.frontend.whiteList.ipStrategy.depth` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy.excludedIPs=127.0.0.1` | Same as `traefik.frontend.whiteList.ipStrategy.excludedIPs` |
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ domain = "mesos.localhost"
|
|||
The following labels can be defined on Mesos tasks. They adjust the behavior for the entire application.
|
||||
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|-----------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.domain` | Sets the default domain for the frontend rules. |
|
||||
| `traefik.enable=false` | Disables this container in Træfik. |
|
||||
| `traefik.port=80` | Registers this port. Useful when the application exposes multiple ports. |
|
||||
|
@ -166,7 +166,9 @@ The following labels can be defined on Mesos tasks. They adjust the behavior for
|
|||
| `traefik.frontend.redirect.permanent=true` | Returns 301 instead of 302. |
|
||||
| `traefik.frontend.rule=EXPR` | Overrides the default frontend rule. Default: `Host:{discovery_name}.{domain}`. |
|
||||
| `traefik.frontend.whiteList.sourceRange=RANGE` | Sets a list of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.frontend.whiteList.useXForwardedFor=true` | Uses `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
| `traefik.frontend.whiteList.ipStrategy=true` | Uses the default IPStrategy.<br>Can be used when there is an existing `clientIPStrategy` but you want the remote address for whitelisting. |
|
||||
| `traefik.frontend.whiteList.ipStrategy.depth=5` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| `traefik.frontend.whiteList.ipStrategy.excludedIPs=127.0.0.1` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
|
||||
### Custom Headers
|
||||
|
||||
|
@ -211,7 +213,7 @@ Additionally, if a segment name matches a named port, that port will be used unl
|
|||
Segment labels override the default behavior.
|
||||
|
||||
| Label | Description |
|
||||
|---------------------------------------------------------------------------|----------------------------------------------------------------|
|
||||
|------------------------------------------------------------------------------|----------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
|
||||
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
|
||||
| `traefik.<segment_name>.portIndex=1` | Same as `traefik.portIndex` |
|
||||
|
@ -252,7 +254,9 @@ Segment labels override the default behavior.
|
|||
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Same as `traefik.frontend.redirect.permanent` |
|
||||
| `traefik.<segment_name>.frontend.rule=EXP` | Same as `traefik.frontend.rule` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Same as `traefik.frontend.whiteList.sourceRange` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Same as `traefik.frontend.whiteList.useXForwardedFor` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy=true` | Same as `traefik.frontend.whiteList.ipStrategy` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy.depth=5` | Same as `traefik.frontend.whiteList.ipStrategy.depth` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy.excludedIPs=127.0.0.1` | Same as `traefik.frontend.whiteList.ipStrategy.excludedIPs` |
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
|
|
|
@ -139,7 +139,7 @@ secretKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|||
Labels can be used on task containers to override default behavior:
|
||||
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|-----------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.domain` | Sets the default domain for the frontend rules. |
|
||||
| `traefik.enable=false` | Disables this container in Træfik. |
|
||||
| `traefik.port=80` | Registers this port. Useful when the container exposes multiple ports. |
|
||||
|
@ -195,7 +195,9 @@ Labels can be used on task containers to override default behavior:
|
|||
| `traefik.frontend.redirect.permanent=true` | Returns 301 instead of 302. |
|
||||
| `traefik.frontend.rule=EXPR` | Overrides the default frontend rule. Default: `Host:{containerName}.{domain}` or `Host:{service}.{project_name}.{domain}` if you are using `docker-compose`. |
|
||||
| `traefik.frontend.whiteList.sourceRange=RANGE` | Sets a list of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access.<br>If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.frontend.whiteList.useXForwardedFor=true` | Uses `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
| `traefik.frontend.whiteList.ipStrategy=true` | Uses the default IPStrategy.<br>Can be used when there is an existing `clientIPStrategy` but you want the remote address for whitelisting. |
|
||||
| `traefik.frontend.whiteList.ipStrategy.depth=5` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| `traefik.frontend.whiteList.ipStrategy.excludedIPs=127.0.0.1` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
|
@ -238,7 +240,7 @@ You can define as many segments as ports exposed in a container.
|
|||
Segment labels override the default behavior.
|
||||
|
||||
| Label | Description |
|
||||
|---------------------------------------------------------------------------|---------------------------------------------------------------|
|
||||
|------------------------------------------------------------------------------|----------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
|
||||
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
|
||||
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
|
||||
|
@ -276,7 +278,9 @@ Segment labels override the default behavior.
|
|||
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Same as `traefik.frontend.redirect.permanent` |
|
||||
| `traefik.<segment_name>.frontend.rule=EXP` | Same as `traefik.frontend.rule` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Same as `traefik.frontend.whiteList.sourceRange` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Same as `traefik.frontend.whiteList.useXForwardedFor` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy=true` | Same as `traefik.frontend.whiteList.ipStrategy` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy.depth=5` | Same as `traefik.frontend.whiteList.ipStrategy.depth` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy.excludedIPs=127.0.0.1` | Same as `traefik.frontend.whiteList.ipStrategy.excludedIPs` |
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ curl -X PUT \
|
|||
Labels, set through extensions or the property manager, can be used on services to override default behavior.
|
||||
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|-----------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.enable=false` | Disable this container in Træfik |
|
||||
| `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||
| `traefik.servicefabric.groupname` | Group all services with the same name into a single backend in Træfik |
|
||||
|
@ -123,7 +123,9 @@ Labels, set through extensions or the property manager, can be used on services
|
|||
| `traefik.frontend.redirect.permanent=true` | Return 301 instead of 302. |
|
||||
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Defaults to SF address. |
|
||||
| `traefik.frontend.whiteList.sourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access.<br>If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.frontend.whiteList.useXForwardedFor=true` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
| `traefik.frontend.whiteList.ipStrategy=true` | Uses the default IPStrategy.<br>Can be used when there is an existing `clientIPStrategy` but you want the remote address for whitelisting. |
|
||||
| `traefik.frontend.whiteList.ipStrategy.depth=5` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| `traefik.frontend.whiteList.ipStrategy.excludedIPs=127.0.0.1` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
|
||||
### Custom Headers
|
||||
|
||||
|
|
|
@ -15,9 +15,15 @@ defaultEntryPoints = ["http", "https"]
|
|||
address = ":80"
|
||||
[entryPoints.http.compress]
|
||||
|
||||
[entryPoints.http.clientIPStrategy]
|
||||
depth = 5
|
||||
excludedIPs = ["127.0.0.1/32", "192.168.1.7"]
|
||||
|
||||
[entryPoints.http.whitelist]
|
||||
sourceRange = ["10.42.0.0/16", "152.89.1.33/32", "afed:be44::/16"]
|
||||
useXForwardedFor = true
|
||||
[entryPoints.http.whitelist.IPStrategy]
|
||||
depth = 5
|
||||
excludedIPs = ["127.0.0.1/32", "192.168.1.7"]
|
||||
|
||||
[entryPoints.http.tls]
|
||||
minVersion = "VersionTLS12"
|
||||
|
@ -75,6 +81,7 @@ defaultEntryPoints = ["http", "https"]
|
|||
|
||||
[entryPoints.http.forwardedHeaders]
|
||||
trustedIPs = ["10.10.10.1", "10.10.10.2"]
|
||||
insecure = false
|
||||
|
||||
[entryPoints.https]
|
||||
# ...
|
||||
|
@ -129,7 +136,8 @@ Redirect.Replacement:http://mydomain/$1
|
|||
Redirect.Permanent:true
|
||||
Compress:true
|
||||
WhiteList.SourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16
|
||||
WhiteList.UseXForwardedFor:true
|
||||
WhiteList.IPStrategy.depth:3
|
||||
WhiteList.IPStrategy.ExcludedIPs:10.0.0.3/24,20.0.0.3/24
|
||||
ProxyProtocol.TrustedIPs:192.168.0.1
|
||||
ProxyProtocol.Insecure:true
|
||||
ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24
|
||||
|
@ -464,7 +472,9 @@ Responses are compressed when:
|
|||
|
||||
## White Listing
|
||||
|
||||
To enable IP white listing at the entry point level.
|
||||
Træfik supports whitelisting to accept or refuse requests based on the client IP.
|
||||
|
||||
The following example enables IP white listing and accepts requests from client IPs defined in `sourceRange`.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
|
@ -473,9 +483,97 @@ To enable IP white listing at the entry point level.
|
|||
|
||||
[entryPoints.http.whiteList]
|
||||
sourceRange = ["127.0.0.1/32", "192.168.1.7"]
|
||||
# useXForwardedFor = true
|
||||
# [entryPoints.http.whiteList.IPStrategy]
|
||||
# Override the clientIPStrategy
|
||||
```
|
||||
|
||||
By default, Træfik uses the client IP (see [ClientIPStrategy](/configuration/entrypoints/#clientipstrategy)) for the whitelisting.
|
||||
|
||||
If you want to use another IP than the one determined by `ClientIPStrategy` for the whitelisting, you can define the `IPStrategy` option:
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http.clientIPStrategy]
|
||||
depth = 4
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
[entryPoints.http.whiteList]
|
||||
sourceRange = ["127.0.0.1/32", "192.168.1.7"]
|
||||
[entryPoints.http.whiteList.IPStrategy]
|
||||
depth = 2
|
||||
```
|
||||
|
||||
In the above example, if the value of the `X-Forwarded-For` header was `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` then the client IP would be `"10.0.0.1"` (`clientIPStrategy.depth=4`) but the IP used for the whitelisting would be `"12.0.0.1"` (`whitelist.IPStrategy.depth=2`).
|
||||
|
||||
## ClientIPStrategy
|
||||
|
||||
The `clientIPStrategy` defines how you want Træfik to determine the client IP (used for whitelisting for example).
|
||||
|
||||
There are several option available:
|
||||
|
||||
### Depth
|
||||
|
||||
This option uses the `X-Forwarded-For` header and takes the IP located at the `depth` position (starting from the right).
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
[entryPoints.http.clientIPStrategy]
|
||||
```
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
[entryPoints.http.clientIPStrategy]
|
||||
depth = 5
|
||||
```
|
||||
|
||||
!!! note
|
||||
- If `depth` is greater than the total number of IPs in `X-Forwarded-For`, then clientIP will be empty.
|
||||
- If `depth` is lesser than or equal to 0, then the option is ignored.
|
||||
|
||||
Examples:
|
||||
|
||||
| `X-Forwarded-For` | `depth` | clientIP |
|
||||
|-----------------------------------------|---------|--------------|
|
||||
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `1` | `"13.0.0.1"` |
|
||||
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3` | `"11.0.0.1"` |
|
||||
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5` | `""` |
|
||||
|
||||
### Excluded IPs
|
||||
|
||||
Træfik will scan the `X-Forwarded-For` header (from the right) and pick the first IP not in the `excludedIPs` list.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
[entryPoints.http.clientIPStrategy]
|
||||
excludedIPs = ["127.0.0.1/32", "192.168.1.7"]
|
||||
```
|
||||
|
||||
!!! note
|
||||
If `depth` is specified, `excludedIPs` is ignored.
|
||||
|
||||
Examples:
|
||||
|
||||
| `X-Forwarded-For` | `excludedIPs` | clientIP |
|
||||
|-----------------------------------------|-----------------------|--------------|
|
||||
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"12.0.0.1,13.0.0.1"` | `"11.0.0.1"` |
|
||||
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,13.0.0.1"` | `"12.0.0.1"` |
|
||||
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"10.0.0.1,13.0.0.1"` | `"12.0.0.1"` |
|
||||
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,16.0.0.1"` | `"13.0.0.1"` |
|
||||
| `"10.0.0.1,11.0.0.1"` | `"10.0.0.1,11.0.0.1"` | `""` |
|
||||
|
||||
### Default
|
||||
|
||||
If there are no `depth` or `excludedIPs`, then the client IP is the IP of the computer that initiated the connection with the Træfik server (the remote address).
|
||||
|
||||
## ProxyProtocol
|
||||
|
||||
To enable [ProxyProtocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) support.
|
||||
|
@ -524,4 +622,12 @@ Only IPs in `trustedIPs` will be authorized to trust the client forwarded header
|
|||
# Default: []
|
||||
#
|
||||
trustedIPs = ["127.0.0.1/32", "192.168.1.7"]
|
||||
|
||||
# Insecure mode
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# insecure = true
|
||||
|
||||
```
|
||||
|
|
|
@ -3,6 +3,7 @@ package integration
|
|||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
@ -253,7 +254,6 @@ func (s *SimpleSuite) TestNoAuthOnPing(c *check.C) {
|
|||
}
|
||||
|
||||
func (s *SimpleSuite) TestDefaultEntrypointHTTP(c *check.C) {
|
||||
|
||||
s.createComposeProject(c, "base")
|
||||
s.composeProject.Start(c)
|
||||
|
||||
|
@ -272,7 +272,6 @@ func (s *SimpleSuite) TestDefaultEntrypointHTTP(c *check.C) {
|
|||
}
|
||||
|
||||
func (s *SimpleSuite) TestWithUnexistingEntrypoint(c *check.C) {
|
||||
|
||||
s.createComposeProject(c, "base")
|
||||
s.composeProject.Start(c)
|
||||
|
||||
|
@ -291,7 +290,6 @@ func (s *SimpleSuite) TestWithUnexistingEntrypoint(c *check.C) {
|
|||
}
|
||||
|
||||
func (s *SimpleSuite) TestMetricsPrometheusDefaultEntrypoint(c *check.C) {
|
||||
|
||||
s.createComposeProject(c, "base")
|
||||
s.composeProject.Start(c)
|
||||
|
||||
|
@ -313,15 +311,16 @@ func (s *SimpleSuite) TestMetricsPrometheusDefaultEntrypoint(c *check.C) {
|
|||
}
|
||||
|
||||
func (s *SimpleSuite) TestMultipleProviderSameBackendName(c *check.C) {
|
||||
|
||||
s.createComposeProject(c, "base")
|
||||
s.composeProject.Start(c)
|
||||
|
||||
ipWhoami01 := s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress
|
||||
ipWhoami02 := s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress
|
||||
file := s.adaptFile(c, "fixtures/multiple_provider.toml", struct{ IP string }{
|
||||
IP: ipWhoami02,
|
||||
})
|
||||
defer os.Remove(file)
|
||||
|
||||
cmd, output := s.traefikCmd(withConfigFile(file))
|
||||
defer output(c)
|
||||
|
||||
|
@ -339,3 +338,80 @@ func (s *SimpleSuite) TestMultipleProviderSameBackendName(c *check.C) {
|
|||
c.Assert(err, checker.IsNil)
|
||||
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestIPStrategyWhitelist(c *check.C) {
|
||||
s.createComposeProject(c, "whitelist")
|
||||
s.composeProject.Start(c)
|
||||
|
||||
cmd, output := s.traefikCmd(withConfigFile("fixtures/simple_whitelist.toml"))
|
||||
defer output(c)
|
||||
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.BodyContains("override"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
xForwardedFor string
|
||||
host string
|
||||
expectedStatusCode int
|
||||
}{
|
||||
{
|
||||
desc: "default client ip strategy accept",
|
||||
xForwardedFor: "8.8.8.8,127.0.0.1",
|
||||
host: "no.override.whitelist.docker.local",
|
||||
expectedStatusCode: 200,
|
||||
},
|
||||
{
|
||||
desc: "default client ip strategy reject",
|
||||
xForwardedFor: "8.8.8.10,127.0.0.1",
|
||||
host: "no.override.whitelist.docker.local",
|
||||
expectedStatusCode: 403,
|
||||
},
|
||||
{
|
||||
desc: "override remote addr reject",
|
||||
xForwardedFor: "8.8.8.8,8.8.8.8",
|
||||
host: "override.remoteaddr.whitelist.docker.local",
|
||||
expectedStatusCode: 403,
|
||||
},
|
||||
{
|
||||
desc: "override depth accept",
|
||||
xForwardedFor: "8.8.8.8,10.0.0.1,127.0.0.1",
|
||||
host: "override.depth.whitelist.docker.local",
|
||||
expectedStatusCode: 200,
|
||||
},
|
||||
{
|
||||
desc: "override depth reject",
|
||||
xForwardedFor: "10.0.0.1,8.8.8.8,127.0.0.1",
|
||||
host: "override.depth.whitelist.docker.local",
|
||||
expectedStatusCode: 403,
|
||||
},
|
||||
{
|
||||
desc: "override excludedIPs reject",
|
||||
xForwardedFor: "10.0.0.3,10.0.0.1,10.0.0.2",
|
||||
host: "override.excludedips.whitelist.docker.local",
|
||||
expectedStatusCode: 403,
|
||||
},
|
||||
{
|
||||
desc: "override excludedIPs accept",
|
||||
xForwardedFor: "8.8.8.8,10.0.0.1,10.0.0.2",
|
||||
host: "override.excludedips.whitelist.docker.local",
|
||||
expectedStatusCode: 200,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
req := httptest.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil)
|
||||
req.Header.Set("X-Forwarded-For", test.xForwardedFor)
|
||||
req.Host = test.host
|
||||
req.RequestURI = ""
|
||||
|
||||
err = try.Request(req, 1*time.Second, try.StatusCodeIs(test.expectedStatusCode))
|
||||
if err != nil {
|
||||
c.Fatalf("Error while %s: %v", test.desc, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,8 @@ checkNewVersion = false
|
|||
entryPoint = "http"
|
||||
[entryPoints.httpWhitelistReject]
|
||||
address = ":8002"
|
||||
whiteListSourceRange = ["8.8.8.8/32"]
|
||||
[entryPoints.httpWhitelistReject.whiteList]
|
||||
sourceRange = ["8.8.8.8/32"]
|
||||
[entryPoints.httpAuth]
|
||||
address = ":8004"
|
||||
[entryPoints.httpAuth.auth.basic]
|
||||
|
|
13
integration/fixtures/simple_whitelist.toml
Normal file
13
integration/fixtures/simple_whitelist.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
logLevel = "DEBUG"
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":8000"
|
||||
[entryPoints.http.ForwardedHeaders]
|
||||
insecure=true
|
||||
[entryPoints.http.ClientIPStrategy]
|
||||
depth=2
|
||||
|
||||
[api]
|
||||
[docker]
|
|
@ -102,6 +102,6 @@ frontendWhitelist:
|
|||
- traefik.enable=true
|
||||
- traefik.port=80
|
||||
- traefik.backend=backend3
|
||||
- traefik.frontend.whitelistSourceRange=8.8.8.8/32
|
||||
- traefik.frontend.whiteList.sourceRange=8.8.8.8/32
|
||||
- traefik.frontend.entryPoints=http
|
||||
- traefik.frontend.rule=Host:frontend.whitelist.docker.local
|
||||
|
|
34
integration/resources/compose/whitelist.yml
Normal file
34
integration/resources/compose/whitelist.yml
Normal file
|
@ -0,0 +1,34 @@
|
|||
noOverrideWhitelist:
|
||||
image: containous/whoami
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.port=80
|
||||
- traefik.frontend.rule=Host:no.override.whitelist.docker.local
|
||||
- traefik.frontend.whiteList.sourceRange=8.8.8.8
|
||||
|
||||
overrideIPStrategyRemoteAddrWhitelist:
|
||||
image: containous/whoami
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.port=80
|
||||
- traefik.frontend.rule=Host:override.remoteaddr.whitelist.docker.local
|
||||
- traefik.frontend.whiteList.sourceRange=8.8.8.8
|
||||
- traefik.frontend.whiteList.ipStrategy=true
|
||||
|
||||
overrideIPStrategyDepthWhitelist:
|
||||
image: containous/whoami
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.port=80
|
||||
- traefik.frontend.rule=Host:override.depth.whitelist.docker.local
|
||||
- traefik.frontend.whiteList.sourceRange=8.8.8.8
|
||||
- traefik.frontend.whiteList.ipStrategy.depth=3
|
||||
|
||||
overrideIPStrategyExcludedIPsWhitelist:
|
||||
image: containous/whoami
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.port=80
|
||||
- traefik.frontend.rule=Host:override.excludedips.whitelist.docker.local
|
||||
- traefik.frontend.whiteList.sourceRange=8.8.8.8
|
||||
- traefik.frontend.whiteList.ipStrategy.excludedIPs=10.0.0.1,10.0.0.2
|
99
ip/checker.go
Normal file
99
ip/checker.go
Normal file
|
@ -0,0 +1,99 @@
|
|||
package ip
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Checker allows to check that addresses are in a trusted IPs
|
||||
type Checker struct {
|
||||
authorizedIPs []*net.IP
|
||||
authorizedIPsNet []*net.IPNet
|
||||
}
|
||||
|
||||
// NewChecker builds a new Checker given a list of CIDR-Strings to trusted IPs
|
||||
func NewChecker(trustedIPs []string) (*Checker, error) {
|
||||
if len(trustedIPs) == 0 {
|
||||
return nil, errors.New("no trusted IPs provided")
|
||||
}
|
||||
|
||||
checker := &Checker{}
|
||||
|
||||
for _, ipMask := range trustedIPs {
|
||||
if ipAddr := net.ParseIP(ipMask); ipAddr != nil {
|
||||
checker.authorizedIPs = append(checker.authorizedIPs, &ipAddr)
|
||||
} else {
|
||||
_, ipAddr, err := net.ParseCIDR(ipMask)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing CIDR trusted IPs %s: %v", ipAddr, err)
|
||||
}
|
||||
checker.authorizedIPsNet = append(checker.authorizedIPsNet, ipAddr)
|
||||
}
|
||||
}
|
||||
|
||||
return checker, nil
|
||||
}
|
||||
|
||||
// IsAuthorized checks if provided request is authorized by the trusted IPs
|
||||
func (ip *Checker) IsAuthorized(addr string) error {
|
||||
var invalidMatches []string
|
||||
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
host = addr
|
||||
}
|
||||
|
||||
ok, err := ip.Contains(host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
invalidMatches = append(invalidMatches, addr)
|
||||
return fmt.Errorf("%q matched none of the trusted IPs", strings.Join(invalidMatches, ", "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Contains checks if provided address is in the trusted IPs
|
||||
func (ip *Checker) Contains(addr string) (bool, error) {
|
||||
if len(addr) <= 0 {
|
||||
return false, errors.New("empty IP address")
|
||||
}
|
||||
|
||||
ipAddr, err := parseIP(addr)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("unable to parse address: %s: %s", addr, err)
|
||||
}
|
||||
|
||||
return ip.ContainsIP(ipAddr), nil
|
||||
}
|
||||
|
||||
// ContainsIP checks if provided address is in the trusted IPs
|
||||
func (ip *Checker) ContainsIP(addr net.IP) bool {
|
||||
for _, authorizedIP := range ip.authorizedIPs {
|
||||
if authorizedIP.Equal(addr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, authorizedNet := range ip.authorizedIPsNet {
|
||||
if authorizedNet.Contains(addr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func parseIP(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
|
||||
}
|
326
ip/checker_test.go
Normal file
326
ip/checker_test.go
Normal file
|
@ -0,0 +1,326 @@
|
|||
package ip
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIsAuthorized(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
whiteList []string
|
||||
remoteAddr string
|
||||
authorized bool
|
||||
}{
|
||||
{
|
||||
desc: "remoteAddr not in range",
|
||||
whiteList: []string{"1.2.3.4/24"},
|
||||
remoteAddr: "10.2.3.1:123",
|
||||
authorized: false,
|
||||
},
|
||||
{
|
||||
desc: "remoteAddr in range",
|
||||
whiteList: []string{"1.2.3.4/24"},
|
||||
remoteAddr: "1.2.3.1:123",
|
||||
authorized: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ipChecker, err := NewChecker(test.whiteList)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ipChecker.IsAuthorized(test.remoteAddr)
|
||||
if test.authorized {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
trustedIPs []string
|
||||
expectedAuthorizedIPs []*net.IPNet
|
||||
errMessage string
|
||||
}{
|
||||
{
|
||||
desc: "nil trusted IPs",
|
||||
trustedIPs: nil,
|
||||
expectedAuthorizedIPs: nil,
|
||||
errMessage: "no trusted IPs provided",
|
||||
}, {
|
||||
desc: "empty trusted IPs",
|
||||
trustedIPs: []string{},
|
||||
expectedAuthorizedIPs: nil,
|
||||
errMessage: "no trusted IPs provided",
|
||||
}, {
|
||||
desc: "trusted IPs containing empty string",
|
||||
trustedIPs: []string{
|
||||
"1.2.3.4/24",
|
||||
"",
|
||||
"fe80::/16",
|
||||
},
|
||||
expectedAuthorizedIPs: nil,
|
||||
errMessage: "parsing CIDR trusted IPs <nil>: invalid CIDR address: ",
|
||||
}, {
|
||||
desc: "trusted IPs containing only an empty string",
|
||||
trustedIPs: []string{
|
||||
"",
|
||||
},
|
||||
expectedAuthorizedIPs: nil,
|
||||
errMessage: "parsing CIDR trusted IPs <nil>: invalid CIDR address: ",
|
||||
}, {
|
||||
desc: "trusted IPs containing an invalid string",
|
||||
trustedIPs: []string{
|
||||
"foo",
|
||||
},
|
||||
expectedAuthorizedIPs: nil,
|
||||
errMessage: "parsing CIDR trusted IPs <nil>: invalid CIDR address: foo",
|
||||
}, {
|
||||
desc: "IPv4 & IPv6 trusted IPs",
|
||||
trustedIPs: []string{
|
||||
"1.2.3.4/24",
|
||||
"fe80::/16",
|
||||
},
|
||||
expectedAuthorizedIPs: []*net.IPNet{
|
||||
{IP: net.IPv4(1, 2, 3, 0).To4(), Mask: net.IPv4Mask(255, 255, 255, 0)},
|
||||
{IP: net.ParseIP("fe80::"), Mask: net.IPMask(net.ParseIP("ffff::"))},
|
||||
},
|
||||
errMessage: "",
|
||||
}, {
|
||||
desc: "IPv4 only",
|
||||
trustedIPs: []string{
|
||||
"127.0.0.1/8",
|
||||
},
|
||||
expectedAuthorizedIPs: []*net.IPNet{
|
||||
{IP: net.IPv4(127, 0, 0, 0).To4(), Mask: net.IPv4Mask(255, 0, 0, 0)},
|
||||
},
|
||||
errMessage: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ipChecker, err := NewChecker(test.trustedIPs)
|
||||
if test.errMessage != "" {
|
||||
require.EqualError(t, err, test.errMessage)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
for index, actual := range ipChecker.authorizedIPsNet {
|
||||
expected := test.expectedAuthorizedIPs[index]
|
||||
assert.Equal(t, expected.IP, actual.IP)
|
||||
assert.Equal(t, expected.Mask.String(), actual.Mask.String())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsIsAllowed(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
trustedIPs []string
|
||||
passIPs []string
|
||||
rejectIPs []string
|
||||
}{
|
||||
{
|
||||
desc: "IPv4",
|
||||
trustedIPs: []string{"1.2.3.4/24"},
|
||||
passIPs: []string{
|
||||
"1.2.3.1",
|
||||
"1.2.3.32",
|
||||
"1.2.3.156",
|
||||
"1.2.3.255",
|
||||
},
|
||||
rejectIPs: []string{
|
||||
"1.2.16.1",
|
||||
"1.2.32.1",
|
||||
"127.0.0.1",
|
||||
"8.8.8.8",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "IPv4 single IP",
|
||||
trustedIPs: []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",
|
||||
trustedIPs: []string{"8.8.8.8/32"},
|
||||
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: "multiple IPv4",
|
||||
trustedIPs: []string{"1.2.3.4/24", "8.8.8.8/8"},
|
||||
passIPs: []string{
|
||||
"1.2.3.1",
|
||||
"1.2.3.32",
|
||||
"1.2.3.156",
|
||||
"1.2.3.255",
|
||||
"8.8.4.4",
|
||||
"8.0.0.1",
|
||||
"8.32.42.128",
|
||||
"8.255.255.255",
|
||||
},
|
||||
rejectIPs: []string{
|
||||
"1.2.16.1",
|
||||
"1.2.32.1",
|
||||
"127.0.0.1",
|
||||
"4.4.4.4",
|
||||
"4.8.8.8",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "IPv6",
|
||||
trustedIPs: []string{"2a03:4000:6:d080::/64"},
|
||||
passIPs: []string{
|
||||
"2a03:4000:6:d080::",
|
||||
"2a03:4000:6:d080::1",
|
||||
"2a03:4000:6:d080:dead:beef:ffff:ffff",
|
||||
"2a03:4000:6:d080::42",
|
||||
},
|
||||
rejectIPs: []string{
|
||||
"2a03:4000:7:d080::",
|
||||
"2a03:4000:7:d080::1",
|
||||
"fe80::",
|
||||
"4242::1",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "IPv6 single IP",
|
||||
trustedIPs: []string{"2a03:4000:6:d080::42/128"},
|
||||
passIPs: []string{"2a03:4000:6:d080::42"},
|
||||
rejectIPs: []string{
|
||||
"2a03:4000:6:d080::1",
|
||||
"2a03:4000:6:d080:dead:beef:ffff:ffff",
|
||||
"2a03:4000:6:d080::43",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "multiple IPv6",
|
||||
trustedIPs: []string{"2a03:4000:6:d080::/64", "fe80::/16"},
|
||||
passIPs: []string{
|
||||
"2a03:4000:6:d080::",
|
||||
"2a03:4000:6:d080::1",
|
||||
"2a03:4000:6:d080:dead:beef:ffff:ffff",
|
||||
"2a03:4000:6:d080::42",
|
||||
"fe80::1",
|
||||
"fe80:aa00:00bb:4232:ff00:eeee:00ff:1111",
|
||||
"fe80::fe80",
|
||||
},
|
||||
rejectIPs: []string{
|
||||
"2a03:4000:7:d080::",
|
||||
"2a03:4000:7:d080::1",
|
||||
"4242::1",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "multiple IPv6 & IPv4",
|
||||
trustedIPs: []string{"2a03:4000:6:d080::/64", "fe80::/16", "1.2.3.4/24", "8.8.8.8/8"},
|
||||
passIPs: []string{
|
||||
"2a03:4000:6:d080::",
|
||||
"2a03:4000:6:d080::1",
|
||||
"2a03:4000:6:d080:dead:beef:ffff:ffff",
|
||||
"2a03:4000:6:d080::42",
|
||||
"fe80::1",
|
||||
"fe80:aa00:00bb:4232:ff00:eeee:00ff:1111",
|
||||
"fe80::fe80",
|
||||
"1.2.3.1",
|
||||
"1.2.3.32",
|
||||
"1.2.3.156",
|
||||
"1.2.3.255",
|
||||
"8.8.4.4",
|
||||
"8.0.0.1",
|
||||
"8.32.42.128",
|
||||
"8.255.255.255",
|
||||
},
|
||||
rejectIPs: []string{
|
||||
"2a03:4000:7:d080::",
|
||||
"2a03:4000:7:d080::1",
|
||||
"4242::1",
|
||||
"1.2.16.1",
|
||||
"1.2.32.1",
|
||||
"127.0.0.1",
|
||||
"4.4.4.4",
|
||||
"4.8.8.8",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "broken IP-addresses",
|
||||
trustedIPs: []string{"127.0.0.1/32"},
|
||||
passIPs: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ipChecker, err := NewChecker(test.trustedIPs)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ipChecker)
|
||||
|
||||
for _, testIP := range test.passIPs {
|
||||
allowed, err := ipChecker.Contains(testIP)
|
||||
require.NoError(t, err)
|
||||
assert.Truef(t, allowed, "%s should have passed.", testIP)
|
||||
}
|
||||
|
||||
for _, testIP := range test.rejectIPs {
|
||||
allowed, err := ipChecker.Contains(testIP)
|
||||
require.NoError(t, err)
|
||||
assert.Falsef(t, allowed, "%s should not have passed.", testIP)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsBrokenIPs(t *testing.T) {
|
||||
brokenIPs := []string{
|
||||
"foo",
|
||||
"10.0.0.350",
|
||||
"fe:::80",
|
||||
"",
|
||||
"\\&$§&/(",
|
||||
}
|
||||
|
||||
ipChecker, err := NewChecker([]string{"1.2.3.4/24"})
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, testIP := range brokenIPs {
|
||||
_, err := ipChecker.Contains(testIP)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
}
|
62
ip/strategy.go
Normal file
62
ip/strategy.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package ip
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
xForwardedFor = "X-Forwarded-For"
|
||||
)
|
||||
|
||||
// Strategy a strategy for IP selection
|
||||
type Strategy interface {
|
||||
GetIP(req *http.Request) string
|
||||
}
|
||||
|
||||
// RemoteAddrStrategy a strategy that always return the remote address
|
||||
type RemoteAddrStrategy struct{}
|
||||
|
||||
// GetIP return the selected IP
|
||||
func (s *RemoteAddrStrategy) GetIP(req *http.Request) string {
|
||||
return req.RemoteAddr
|
||||
}
|
||||
|
||||
// DepthStrategy a strategy based on the depth inside the X-Forwarded-For from right to left
|
||||
type DepthStrategy struct {
|
||||
Depth int
|
||||
}
|
||||
|
||||
// GetIP return the selected IP
|
||||
func (s *DepthStrategy) GetIP(req *http.Request) string {
|
||||
xff := req.Header.Get(xForwardedFor)
|
||||
xffs := strings.Split(xff, ",")
|
||||
|
||||
if len(xffs) < s.Depth {
|
||||
return ""
|
||||
}
|
||||
return xffs[len(xffs)-s.Depth]
|
||||
}
|
||||
|
||||
// CheckerStrategy a strategy based on an IP Checker
|
||||
// allows to check that addresses are in a trusted IPs
|
||||
type CheckerStrategy struct {
|
||||
Checker *Checker
|
||||
}
|
||||
|
||||
// GetIP return the selected IP
|
||||
func (s *CheckerStrategy) GetIP(req *http.Request) string {
|
||||
if s.Checker == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
xff := req.Header.Get(xForwardedFor)
|
||||
xffs := strings.Split(xff, ",")
|
||||
|
||||
for i := len(xffs) - 1; i >= 0; i-- {
|
||||
if contain, _ := s.Checker.Contains(xffs[i]); !contain {
|
||||
return xffs[i]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
125
ip/strategy_test.go
Normal file
125
ip/strategy_test.go
Normal file
|
@ -0,0 +1,125 @@
|
|||
package ip
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRemoteAddrStrategy_GetIP(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "Use RemoteAddr",
|
||||
expected: "192.0.2.1:1234",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
strategy := RemoteAddrStrategy{}
|
||||
req := httptest.NewRequest(http.MethodGet, "http://127.0.0.1", nil)
|
||||
actual := strategy.GetIP(req)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDepthStrategy_GetIP(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
depth int
|
||||
xForwardedFor string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "Use depth",
|
||||
depth: 3,
|
||||
xForwardedFor: "10.0.0.4,10.0.0.3,10.0.0.2,10.0.0.1",
|
||||
expected: "10.0.0.3",
|
||||
},
|
||||
{
|
||||
desc: "Use non existing depth in XForwardedFor",
|
||||
depth: 2,
|
||||
xForwardedFor: "",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
desc: "Use depth that match the first IP in XForwardedFor",
|
||||
depth: 2,
|
||||
xForwardedFor: "10.0.0.2,10.0.0.1",
|
||||
expected: "10.0.0.2",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
strategy := DepthStrategy{Depth: test.depth}
|
||||
req := httptest.NewRequest(http.MethodGet, "http://127.0.0.1", nil)
|
||||
req.Header.Set(xForwardedFor, test.xForwardedFor)
|
||||
actual := strategy.GetIP(req)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExcludedIPsStrategy_GetIP(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
excludedIPs []string
|
||||
xForwardedFor string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "Use excluded all IPs",
|
||||
excludedIPs: []string{"10.0.0.4", "10.0.0.3", "10.0.0.2", "10.0.0.1"},
|
||||
xForwardedFor: "10.0.0.4,10.0.0.3,10.0.0.2,10.0.0.1",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
desc: "Use excluded IPs",
|
||||
excludedIPs: []string{"10.0.0.2", "10.0.0.1"},
|
||||
xForwardedFor: "10.0.0.4,10.0.0.3,10.0.0.2,10.0.0.1",
|
||||
expected: "10.0.0.3",
|
||||
},
|
||||
{
|
||||
desc: "Use excluded IPs CIDR",
|
||||
excludedIPs: []string{"10.0.0.1/24"},
|
||||
xForwardedFor: "127.0.0.1,10.0.0.4,10.0.0.3,10.0.0.2,10.0.0.1",
|
||||
expected: "127.0.0.1",
|
||||
},
|
||||
{
|
||||
desc: "Use excluded all IPs CIDR",
|
||||
excludedIPs: []string{"10.0.0.1/24"},
|
||||
xForwardedFor: "10.0.0.4,10.0.0.3,10.0.0.2,10.0.0.1",
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checker, err := NewChecker(test.excludedIPs)
|
||||
require.NoError(t, err)
|
||||
|
||||
strategy := CheckerStrategy{Checker: checker}
|
||||
req := httptest.NewRequest(http.MethodGet, "http://127.0.0.1", nil)
|
||||
req.Header.Set(xForwardedFor, test.xForwardedFor)
|
||||
actual := strategy.GetIP(req)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
52
middlewares/forwardedheaders/forwarded_header.go
Normal file
52
middlewares/forwardedheaders/forwarded_header.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
package forwardedheaders
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/containous/traefik/ip"
|
||||
"github.com/vulcand/oxy/forward"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
)
|
||||
|
||||
// XForwarded filter for XForwarded headers
|
||||
type XForwarded struct {
|
||||
insecure bool
|
||||
trustedIps []string
|
||||
ipChecker *ip.Checker
|
||||
}
|
||||
|
||||
// NewXforwarded creates a new XForwarded
|
||||
func NewXforwarded(insecure bool, trustedIps []string) (*XForwarded, error) {
|
||||
var ipChecker *ip.Checker
|
||||
if len(trustedIps) > 0 {
|
||||
var err error
|
||||
ipChecker, err = ip.NewChecker(trustedIps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &XForwarded{
|
||||
insecure: insecure,
|
||||
trustedIps: trustedIps,
|
||||
ipChecker: ipChecker,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (x *XForwarded) isTrustedIP(ip string) bool {
|
||||
if x.ipChecker == nil {
|
||||
return false
|
||||
}
|
||||
return x.ipChecker.IsAuthorized(ip) == nil
|
||||
}
|
||||
|
||||
func (x *XForwarded) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
if !x.insecure && !x.isTrustedIP(r.RemoteAddr) {
|
||||
utils.RemoveHeaders(r.Header, forward.XHeaders...)
|
||||
}
|
||||
|
||||
// If there is a next, call it.
|
||||
if next != nil {
|
||||
next(w, r)
|
||||
}
|
||||
}
|
128
middlewares/forwardedheaders/forwarded_header_test.go
Normal file
128
middlewares/forwardedheaders/forwarded_header_test.go
Normal file
|
@ -0,0 +1,128 @@
|
|||
package forwardedheaders
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestServeHTTP(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
insecure bool
|
||||
trustedIps []string
|
||||
incomingHeaders map[string]string
|
||||
remoteAddr string
|
||||
expectedHeaders map[string]string
|
||||
}{
|
||||
{
|
||||
desc: "all Empty",
|
||||
insecure: true,
|
||||
trustedIps: nil,
|
||||
remoteAddr: "",
|
||||
incomingHeaders: map[string]string{},
|
||||
expectedHeaders: map[string]string{
|
||||
"X-Forwarded-for": "",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "insecure true with incoming X-Forwarded-For",
|
||||
insecure: true,
|
||||
trustedIps: nil,
|
||||
remoteAddr: "",
|
||||
incomingHeaders: map[string]string{
|
||||
"X-Forwarded-for": "10.0.1.0, 10.0.1.12",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
"X-Forwarded-for": "10.0.1.0, 10.0.1.12",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "insecure false with incoming X-Forwarded-For",
|
||||
insecure: false,
|
||||
trustedIps: nil,
|
||||
remoteAddr: "",
|
||||
incomingHeaders: map[string]string{
|
||||
"X-Forwarded-for": "10.0.1.0, 10.0.1.12",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
"X-Forwarded-for": "",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "insecure false with incoming X-Forwarded-For and valid Trusted Ips",
|
||||
insecure: false,
|
||||
trustedIps: []string{"10.0.1.100"},
|
||||
remoteAddr: "10.0.1.100:80",
|
||||
incomingHeaders: map[string]string{
|
||||
"X-Forwarded-for": "10.0.1.0, 10.0.1.12",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
"X-Forwarded-for": "10.0.1.0, 10.0.1.12",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "insecure false with incoming X-Forwarded-For and invalid Trusted Ips",
|
||||
insecure: false,
|
||||
trustedIps: []string{"10.0.1.100"},
|
||||
remoteAddr: "10.0.1.101:80",
|
||||
incomingHeaders: map[string]string{
|
||||
"X-Forwarded-for": "10.0.1.0, 10.0.1.12",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
"X-Forwarded-for": "",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "insecure false with incoming X-Forwarded-For and valid Trusted Ips CIDR",
|
||||
insecure: false,
|
||||
trustedIps: []string{"1.2.3.4/24"},
|
||||
remoteAddr: "1.2.3.156:80",
|
||||
incomingHeaders: map[string]string{
|
||||
"X-Forwarded-for": "10.0.1.0, 10.0.1.12",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
"X-Forwarded-for": "10.0.1.0, 10.0.1.12",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "insecure false with incoming X-Forwarded-For and invalid Trusted Ips CIDR",
|
||||
insecure: false,
|
||||
trustedIps: []string{"1.2.3.4/24"},
|
||||
remoteAddr: "10.0.1.101:80",
|
||||
incomingHeaders: map[string]string{
|
||||
"X-Forwarded-for": "10.0.1.0, 10.0.1.12",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
"X-Forwarded-for": "",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
req.RemoteAddr = test.remoteAddr
|
||||
|
||||
for k, v := range test.incomingHeaders {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
m, err := NewXforwarded(test.insecure, test.trustedIps)
|
||||
require.NoError(t, err)
|
||||
|
||||
m.ServeHTTP(nil, req, nil)
|
||||
|
||||
for k, v := range test.expectedHeaders {
|
||||
assert.Equal(t, v, req.Header.Get(k))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -4,9 +4,9 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/containous/traefik/ip"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/middlewares/tracing"
|
||||
"github.com/containous/traefik/whitelist"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/negroni"
|
||||
)
|
||||
|
@ -14,22 +14,25 @@ import (
|
|||
// IPWhiteLister is a middleware that provides Checks of the Requesting IP against a set of Whitelists
|
||||
type IPWhiteLister struct {
|
||||
handler negroni.Handler
|
||||
whiteLister *whitelist.IP
|
||||
whiteLister *ip.Checker
|
||||
strategy ip.Strategy
|
||||
}
|
||||
|
||||
// NewIPWhiteLister builds a new IPWhiteLister given a list of CIDR-Strings to whitelist
|
||||
func NewIPWhiteLister(whiteList []string, useXForwardedFor bool) (*IPWhiteLister, error) {
|
||||
func NewIPWhiteLister(whiteList []string, strategy ip.Strategy) (*IPWhiteLister, error) {
|
||||
if len(whiteList) == 0 {
|
||||
return nil, errors.New("no white list provided")
|
||||
}
|
||||
|
||||
whiteLister := IPWhiteLister{}
|
||||
|
||||
ip, err := whitelist.NewIP(whiteList, false, useXForwardedFor)
|
||||
checker, err := ip.NewChecker(whiteList)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing CIDR whitelist %s: %v", whiteList, err)
|
||||
}
|
||||
whiteLister.whiteLister = ip
|
||||
|
||||
whiteLister := IPWhiteLister{
|
||||
strategy: strategy,
|
||||
whiteLister: checker,
|
||||
}
|
||||
|
||||
whiteLister.handler = negroni.HandlerFunc(whiteLister.handle)
|
||||
log.Debugf("configured IP white list: %s", whiteList)
|
||||
|
@ -38,13 +41,13 @@ func NewIPWhiteLister(whiteList []string, useXForwardedFor bool) (*IPWhiteLister
|
|||
}
|
||||
|
||||
func (wl *IPWhiteLister) handle(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
err := wl.whiteLister.IsAuthorized(r)
|
||||
err := wl.whiteLister.IsAuthorized(wl.strategy.GetIP(r))
|
||||
if err != nil {
|
||||
tracing.SetErrorAndDebugLog(r, "request %+v - rejecting: %v", r, err)
|
||||
reject(w)
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("Accept %s: %+v", wl.strategy.GetIP(r), r)
|
||||
tracing.SetErrorAndDebugLog(r, "request %+v matched white list %s - passing", r, wl.whiteLister)
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/whitelist"
|
||||
"github.com/containous/traefik/ip"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -14,19 +14,16 @@ func TestNewIPWhiteLister(t *testing.T) {
|
|||
testCases := []struct {
|
||||
desc string
|
||||
whiteList []string
|
||||
useXForwardedFor bool
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
desc: "invalid IP",
|
||||
whiteList: []string{"foo"},
|
||||
useXForwardedFor: false,
|
||||
expectedError: "parsing CIDR whitelist [foo]: parsing CIDR white list <nil>: invalid CIDR address: foo",
|
||||
expectedError: "parsing CIDR whitelist [foo]: parsing CIDR trusted IPs <nil>: invalid CIDR address: foo",
|
||||
},
|
||||
{
|
||||
desc: "valid IP",
|
||||
whiteList: []string{"10.10.10.10"},
|
||||
useXForwardedFor: false,
|
||||
expectedError: "",
|
||||
},
|
||||
}
|
||||
|
@ -36,7 +33,7 @@ func TestNewIPWhiteLister(t *testing.T) {
|
|||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
whiteLister, err := NewIPWhiteLister(test.whiteList, test.useXForwardedFor)
|
||||
whiteLister, err := NewIPWhiteLister(test.whiteList, &ip.RemoteAddrStrategy{})
|
||||
|
||||
if len(test.expectedError) > 0 {
|
||||
assert.EqualError(t, err, test.expectedError)
|
||||
|
@ -52,54 +49,19 @@ func TestIPWhiteLister_ServeHTTP(t *testing.T) {
|
|||
testCases := []struct {
|
||||
desc string
|
||||
whiteList []string
|
||||
useXForwardedFor bool
|
||||
remoteAddr string
|
||||
xForwardedFor []string
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
desc: "authorized with remote address",
|
||||
whiteList: []string{"20.20.20.20"},
|
||||
useXForwardedFor: false,
|
||||
remoteAddr: "20.20.20.20:1234",
|
||||
xForwardedFor: nil,
|
||||
expected: 200,
|
||||
},
|
||||
{
|
||||
desc: "non authorized with remote address",
|
||||
whiteList: []string{"20.20.20.20"},
|
||||
useXForwardedFor: false,
|
||||
remoteAddr: "20.20.20.21:1234",
|
||||
xForwardedFor: nil,
|
||||
expected: 403,
|
||||
},
|
||||
{
|
||||
desc: "non authorized with remote address (X-Forwarded-For possible)",
|
||||
whiteList: []string{"20.20.20.20"},
|
||||
useXForwardedFor: false,
|
||||
remoteAddr: "20.20.20.21:1234",
|
||||
xForwardedFor: []string{"20.20.20.20", "40.40.40.40"},
|
||||
expected: 403,
|
||||
},
|
||||
{
|
||||
desc: "authorized with X-Forwarded-For",
|
||||
whiteList: []string{"30.30.30.30"},
|
||||
useXForwardedFor: true,
|
||||
xForwardedFor: []string{"30.30.30.30", "40.40.40.40"},
|
||||
expected: 200,
|
||||
},
|
||||
{
|
||||
desc: "authorized with only one X-Forwarded-For",
|
||||
whiteList: []string{"30.30.30.30"},
|
||||
useXForwardedFor: true,
|
||||
xForwardedFor: []string{"30.30.30.30"},
|
||||
expected: 200,
|
||||
},
|
||||
{
|
||||
desc: "non authorized with X-Forwarded-For",
|
||||
whiteList: []string{"30.30.30.30"},
|
||||
useXForwardedFor: true,
|
||||
xForwardedFor: []string{"30.30.30.31", "40.40.40.40"},
|
||||
expected: 403,
|
||||
},
|
||||
}
|
||||
|
@ -109,7 +71,7 @@ func TestIPWhiteLister_ServeHTTP(t *testing.T) {
|
|||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
whiteLister, err := NewIPWhiteLister(test.whiteList, test.useXForwardedFor)
|
||||
whiteLister, err := NewIPWhiteLister(test.whiteList, &ip.RemoteAddrStrategy{})
|
||||
require.NoError(t, err)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
|
@ -120,12 +82,6 @@ func TestIPWhiteLister_ServeHTTP(t *testing.T) {
|
|||
req.RemoteAddr = test.remoteAddr
|
||||
}
|
||||
|
||||
if len(test.xForwardedFor) > 0 {
|
||||
for _, xff := range test.xForwardedFor {
|
||||
req.Header.Add(whitelist.XForwardedFor, xff)
|
||||
}
|
||||
}
|
||||
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
whiteLister.ServeHTTP(recorder, req, next)
|
||||
|
|
|
@ -375,7 +375,8 @@ func TestProviderBuildConfiguration(t *testing.T) {
|
|||
label.TraefikFrontendRedirectPermanent + "=true",
|
||||
label.TraefikFrontendRule + "=Host:traefik.io",
|
||||
label.TraefikFrontendWhiteListSourceRange + "=10.10.10.10",
|
||||
label.TraefikFrontendWhiteListUseXForwardedFor + "=true",
|
||||
label.TraefikFrontendWhiteListIPStrategyExcludedIPS + "=10.10.10.10,10.10.10.11",
|
||||
label.TraefikFrontendWhiteListIPStrategyDepth + "=5",
|
||||
|
||||
label.TraefikFrontendRequestHeaders + "=Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
|
||||
label.TraefikFrontendResponseHeaders + "=Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
|
||||
|
@ -478,7 +479,10 @@ func TestProviderBuildConfiguration(t *testing.T) {
|
|||
SourceRange: []string{
|
||||
"10.10.10.10",
|
||||
},
|
||||
UseXForwardedFor: true,
|
||||
IPStrategy: &types.IPStrategy{
|
||||
Depth: 5,
|
||||
ExcludedIPs: []string{"10.10.10.10", "10.10.10.11"},
|
||||
},
|
||||
},
|
||||
Headers: &types.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
|
|
|
@ -413,7 +413,8 @@ func TestDockerBuildConfiguration(t *testing.T) {
|
|||
label.TraefikFrontendRedirectPermanent: "true",
|
||||
label.TraefikFrontendRule: "Host:traefik.io",
|
||||
label.TraefikFrontendWhiteListSourceRange: "10.10.10.10",
|
||||
label.TraefikFrontendWhiteListUseXForwardedFor: "true",
|
||||
label.TraefikFrontendWhiteListIPStrategyExcludedIPS: "10.10.10.10,10.10.10.11",
|
||||
label.TraefikFrontendWhiteListIPStrategyDepth: "5",
|
||||
|
||||
label.TraefikFrontendRequestHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
|
||||
label.TraefikFrontendResponseHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
|
||||
|
@ -485,7 +486,10 @@ func TestDockerBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
WhiteList: &types.WhiteList{
|
||||
SourceRange: []string{"10.10.10.10"},
|
||||
UseXForwardedFor: true,
|
||||
IPStrategy: &types.IPStrategy{
|
||||
Depth: 5,
|
||||
ExcludedIPs: []string{"10.10.10.10", "10.10.10.11"},
|
||||
},
|
||||
},
|
||||
Headers: &types.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
|
|
|
@ -357,7 +357,8 @@ func TestSwarmBuildConfiguration(t *testing.T) {
|
|||
label.TraefikFrontendRedirectReplacement: "nope",
|
||||
label.TraefikFrontendRule: "Host:traefik.io",
|
||||
label.TraefikFrontendWhiteListSourceRange: "10.10.10.10",
|
||||
label.TraefikFrontendWhiteListUseXForwardedFor: "true",
|
||||
label.TraefikFrontendWhiteListIPStrategyExcludedIPS: "10.10.10.10,10.10.10.11",
|
||||
label.TraefikFrontendWhiteListIPStrategyDepth: "5",
|
||||
|
||||
label.TraefikFrontendRequestHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
|
||||
label.TraefikFrontendResponseHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
|
||||
|
@ -427,7 +428,10 @@ func TestSwarmBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
WhiteList: &types.WhiteList{
|
||||
SourceRange: []string{"10.10.10.10"},
|
||||
UseXForwardedFor: true,
|
||||
IPStrategy: &types.IPStrategy{
|
||||
Depth: 5,
|
||||
ExcludedIPs: []string{"10.10.10.10", "10.10.10.11"},
|
||||
},
|
||||
},
|
||||
Headers: &types.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
|
|
|
@ -311,7 +311,8 @@ func TestSegmentBuildConfiguration(t *testing.T) {
|
|||
label.Prefix + "sauternes." + label.SuffixFrontendRedirectReplacement: "nope",
|
||||
label.Prefix + "sauternes." + label.SuffixFrontendRedirectPermanent: "true",
|
||||
label.Prefix + "sauternes." + label.SuffixFrontendWhiteListSourceRange: "10.10.10.10",
|
||||
label.Prefix + "sauternes." + label.SuffixFrontendWhiteListUseXForwardedFor: "true",
|
||||
label.Prefix + "sauternes." + label.SuffixFrontendWhiteListIPStrategyExcludedIPS: "10.10.10.10,10.10.10.11",
|
||||
label.Prefix + "sauternes." + label.SuffixFrontendWhiteListIPStrategyDepth: "5",
|
||||
|
||||
label.Prefix + "sauternes." + label.SuffixFrontendRequestHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
|
||||
label.Prefix + "sauternes." + label.SuffixFrontendResponseHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
|
||||
|
@ -378,7 +379,10 @@ func TestSegmentBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
WhiteList: &types.WhiteList{
|
||||
SourceRange: []string{"10.10.10.10"},
|
||||
UseXForwardedFor: true,
|
||||
IPStrategy: &types.IPStrategy{
|
||||
Depth: 5,
|
||||
ExcludedIPs: []string{"10.10.10.10", "10.10.10.11"},
|
||||
},
|
||||
},
|
||||
Headers: &types.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
|
|
|
@ -378,7 +378,8 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
label.TraefikFrontendRedirectPermanent: aws.String("true"),
|
||||
label.TraefikFrontendRule: aws.String("Host:traefik.io"),
|
||||
label.TraefikFrontendWhiteListSourceRange: aws.String("10.10.10.10"),
|
||||
label.TraefikFrontendWhiteListUseXForwardedFor: aws.String("true"),
|
||||
label.TraefikFrontendWhiteListIPStrategyExcludedIPS: aws.String("10.10.10.10,10.10.10.11"),
|
||||
label.TraefikFrontendWhiteListIPStrategyDepth: aws.String("5"),
|
||||
|
||||
label.TraefikFrontendRequestHeaders: aws.String("Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8"),
|
||||
label.TraefikFrontendResponseHeaders: aws.String("Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8"),
|
||||
|
@ -493,7 +494,10 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
WhiteList: &types.WhiteList{
|
||||
SourceRange: []string{"10.10.10.10"},
|
||||
UseXForwardedFor: true,
|
||||
IPStrategy: &types.IPStrategy{
|
||||
Depth: 5,
|
||||
ExcludedIPs: []string{"10.10.10.10", "10.10.10.11"},
|
||||
},
|
||||
},
|
||||
Headers: &types.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
|
@ -621,7 +625,6 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
label.TraefikFrontendRedirectPermanent: aws.String("true"),
|
||||
label.TraefikFrontendRule: aws.String("Host:traefik.io"),
|
||||
label.TraefikFrontendWhiteListSourceRange: aws.String("10.10.10.10"),
|
||||
label.TraefikFrontendWhiteListUseXForwardedFor: aws.String("true"),
|
||||
|
||||
label.TraefikFrontendRequestHeaders: aws.String("Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8"),
|
||||
label.TraefikFrontendResponseHeaders: aws.String("Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8"),
|
||||
|
@ -707,7 +710,6 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
label.TraefikFrontendRedirectPermanent: aws.String("true"),
|
||||
label.TraefikFrontendRule: aws.String("Host:traefik.io"),
|
||||
label.TraefikFrontendWhiteListSourceRange: aws.String("10.10.10.10"),
|
||||
label.TraefikFrontendWhiteListUseXForwardedFor: aws.String("true"),
|
||||
|
||||
label.TraefikFrontendRequestHeaders: aws.String("Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8"),
|
||||
label.TraefikFrontendResponseHeaders: aws.String("Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8"),
|
||||
|
@ -823,7 +825,6 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
WhiteList: &types.WhiteList{
|
||||
SourceRange: []string{"10.10.10.10"},
|
||||
UseXForwardedFor: true,
|
||||
},
|
||||
Headers: &types.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
|
|
|
@ -18,7 +18,9 @@ const (
|
|||
annotationKubernetesAuthForwardTLSInsecure = "ingress.kubernetes.io/auth-tls-insecure"
|
||||
annotationKubernetesRewriteTarget = "ingress.kubernetes.io/rewrite-target"
|
||||
annotationKubernetesWhiteListSourceRange = "ingress.kubernetes.io/whitelist-source-range"
|
||||
annotationKubernetesWhiteListUseXForwardedFor = "ingress.kubernetes.io/whitelist-x-forwarded-for"
|
||||
annotationKubernetesWhiteListIPStrategy = "ingress.kubernetes.io/whitelist-ipstrategy"
|
||||
annotationKubernetesWhiteListIPStrategyDepth = "ingress.kubernetes.io/whitelist-ipstrategy-depth"
|
||||
annotationKubernetesWhiteListIPStrategyExcludedIPs = "ingress.kubernetes.io/whitelist-ipstrategy-excluded-ips"
|
||||
annotationKubernetesPreserveHost = "ingress.kubernetes.io/preserve-host"
|
||||
annotationKubernetesPassTLSCert = "ingress.kubernetes.io/pass-tls-cert"
|
||||
annotationKubernetesFrontendEntryPoints = "ingress.kubernetes.io/frontend-entry-points"
|
||||
|
|
|
@ -257,13 +257,29 @@ func fwdAuthTLS(cert, key string, insecure bool) func(*types.Forward) {
|
|||
}
|
||||
}
|
||||
|
||||
func whiteList(useXFF bool, ranges ...string) func(*types.Frontend) {
|
||||
func whiteListRange(ranges ...string) func(*types.WhiteList) {
|
||||
return func(wl *types.WhiteList) {
|
||||
wl.SourceRange = ranges
|
||||
}
|
||||
}
|
||||
|
||||
func whiteListIPStrategy(depth int, excludedIPs ...string) func(*types.WhiteList) {
|
||||
return func(wl *types.WhiteList) {
|
||||
wl.IPStrategy = &types.IPStrategy{
|
||||
Depth: depth,
|
||||
ExcludedIPs: excludedIPs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func whiteList(opts ...func(*types.WhiteList)) func(*types.Frontend) {
|
||||
return func(f *types.Frontend) {
|
||||
if f.WhiteList == nil {
|
||||
f.WhiteList = &types.WhiteList{}
|
||||
}
|
||||
f.WhiteList.UseXForwardedFor = useXFF
|
||||
f.WhiteList.SourceRange = ranges
|
||||
for _, opt := range opts {
|
||||
opt(f.WhiteList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -903,7 +903,22 @@ func getWhiteList(i *extensionsv1beta1.Ingress) *types.WhiteList {
|
|||
|
||||
return &types.WhiteList{
|
||||
SourceRange: ranges,
|
||||
UseXForwardedFor: getBoolValue(i.Annotations, annotationKubernetesWhiteListUseXForwardedFor, false),
|
||||
IPStrategy: getIPStrategy(i.Annotations),
|
||||
}
|
||||
}
|
||||
|
||||
func getIPStrategy(annotations map[string]string) *types.IPStrategy {
|
||||
ipStrategy := getBoolValue(annotations, annotationKubernetesWhiteListIPStrategy, false)
|
||||
depth := getIntValue(annotations, annotationKubernetesWhiteListIPStrategyDepth, 0)
|
||||
excludedIPs := getSliceStringValue(annotations, annotationKubernetesWhiteListIPStrategyExcludedIPs)
|
||||
|
||||
if depth == 0 && len(excludedIPs) == 0 && !ipStrategy {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &types.IPStrategy{
|
||||
Depth: depth,
|
||||
ExcludedIPs: excludedIPs,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1080,13 +1080,24 @@ func TestIngressAnnotations(t *testing.T) {
|
|||
buildIngress(
|
||||
iNamespace("testing"),
|
||||
iAnnotation(annotationKubernetesWhiteListSourceRange, "1.1.1.1/24, 1234:abcd::42/32"),
|
||||
iAnnotation(annotationKubernetesWhiteListUseXForwardedFor, "true"),
|
||||
iAnnotation(annotationKubernetesWhiteListIPStrategyExcludedIPs, "1.1.1.1/24, 1234:abcd::42/32"),
|
||||
iAnnotation(annotationKubernetesWhiteListIPStrategyDepth, "5"),
|
||||
iRules(
|
||||
iRule(
|
||||
iHost("test"),
|
||||
iPaths(onePath(iPath("/whitelist-source-range"), iBackend("service1", intstr.FromInt(80))))),
|
||||
),
|
||||
),
|
||||
buildIngress(
|
||||
iNamespace("testing"),
|
||||
iAnnotation(annotationKubernetesWhiteListSourceRange, "1.1.1.1/24, 1234:abcd::42/32"),
|
||||
iAnnotation(annotationKubernetesWhiteListIPStrategy, "true"),
|
||||
iRules(
|
||||
iRule(
|
||||
iHost("test"),
|
||||
iPaths(onePath(iPath("/whitelist-remote-addr"), iBackend("service1", intstr.FromInt(80))))),
|
||||
),
|
||||
),
|
||||
buildIngress(
|
||||
iNamespace("testing"),
|
||||
iAnnotation(annotationKubernetesRewriteTarget, "/"),
|
||||
|
@ -1357,6 +1368,12 @@ rateset:
|
|||
server("http://example.com", weight(1))),
|
||||
lbMethod("wrr"),
|
||||
),
|
||||
backend("test/whitelist-remote-addr",
|
||||
servers(
|
||||
server("http://example.com", weight(1)),
|
||||
server("http://example.com", weight(1))),
|
||||
lbMethod("wrr"),
|
||||
),
|
||||
backend("rewrite/api",
|
||||
servers(
|
||||
server("http://example.com", weight(1)),
|
||||
|
@ -1467,11 +1484,22 @@ rateset:
|
|||
),
|
||||
frontend("test/whitelist-source-range",
|
||||
passHostHeader(),
|
||||
whiteList(true, "1.1.1.1/24", "1234:abcd::42/32"),
|
||||
whiteList(
|
||||
whiteListRange("1.1.1.1/24", "1234:abcd::42/32"),
|
||||
whiteListIPStrategy(5, "1.1.1.1/24", "1234:abcd::42/32")),
|
||||
routes(
|
||||
route("/whitelist-source-range", "PathPrefix:/whitelist-source-range"),
|
||||
route("test", "Host:test")),
|
||||
),
|
||||
frontend("test/whitelist-remote-addr",
|
||||
passHostHeader(),
|
||||
whiteList(
|
||||
whiteListRange("1.1.1.1/24", "1234:abcd::42/32"),
|
||||
whiteListIPStrategy(0)),
|
||||
routes(
|
||||
route("/whitelist-remote-addr", "PathPrefix:/whitelist-remote-addr"),
|
||||
route("test", "Host:test")),
|
||||
),
|
||||
frontend("rewrite/api",
|
||||
passHostHeader(),
|
||||
routes(
|
||||
|
|
|
@ -30,7 +30,9 @@ const (
|
|||
pathFrontendPassHostHeader = "/passhostheader"
|
||||
pathFrontendPassTLSCert = "/passtlscert"
|
||||
pathFrontendWhiteListSourceRange = "/whitelist/sourcerange"
|
||||
pathFrontendWhiteListUseXForwardedFor = "/whitelist/usexforwardedfor"
|
||||
pathFrontendWhiteListIPStrategy = "/whitelist/ipstrategy"
|
||||
pathFrontendWhiteListIPStrategyDepth = pathFrontendWhiteListIPStrategy + "/depth"
|
||||
pathFrontendWhiteListIPStrategyExcludedIPs = pathFrontendWhiteListIPStrategy + "/excludedips"
|
||||
|
||||
pathFrontendAuth = "/auth/"
|
||||
pathFrontendAuthBasic = pathFrontendAuth + "basic/"
|
||||
|
|
|
@ -80,16 +80,31 @@ func (p *Provider) buildConfiguration() *types.Configuration {
|
|||
func (p *Provider) getWhiteList(rootPath string) *types.WhiteList {
|
||||
ranges := p.getList(rootPath, pathFrontendWhiteListSourceRange)
|
||||
|
||||
if len(ranges) > 0 {
|
||||
if len(ranges) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &types.WhiteList{
|
||||
SourceRange: ranges,
|
||||
UseXForwardedFor: p.getBool(false, rootPath, pathFrontendWhiteListUseXForwardedFor),
|
||||
IPStrategy: p.getIPStrategy(rootPath),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) getIPStrategy(rootPath string) *types.IPStrategy {
|
||||
ipStrategy := p.getBool(false, rootPath, pathFrontendWhiteListIPStrategy)
|
||||
depth := p.getInt(0, rootPath, pathFrontendWhiteListIPStrategyDepth)
|
||||
excludedIPs := p.getList(rootPath, pathFrontendWhiteListIPStrategyExcludedIPs)
|
||||
|
||||
if depth == 0 && len(excludedIPs) == 0 && !ipStrategy {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &types.IPStrategy{
|
||||
Depth: depth,
|
||||
ExcludedIPs: excludedIPs,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) getRedirect(rootPath string) *types.Redirect {
|
||||
permanent := p.getBool(false, rootPath, pathFrontendRedirectPermanent)
|
||||
|
||||
|
|
|
@ -215,6 +215,39 @@ func TestProviderBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "forward auth",
|
||||
kvPairs: filler("traefik",
|
||||
frontend("frontend",
|
||||
withPair(pathFrontendBackend, "backend"),
|
||||
withList(pathFrontendWhiteListSourceRange, "1.1.1.1/24", "1234:abcd::42/32"),
|
||||
withPair(pathFrontendWhiteListIPStrategy, "true"),
|
||||
),
|
||||
backend("backend"),
|
||||
),
|
||||
expected: &types.Configuration{
|
||||
Backends: map[string]*types.Backend{
|
||||
"backend": {
|
||||
LoadBalancer: &types.LoadBalancer{
|
||||
Method: "wrr",
|
||||
},
|
||||
},
|
||||
},
|
||||
Frontends: map[string]*types.Frontend{
|
||||
"frontend": {
|
||||
Backend: "backend",
|
||||
PassHostHeader: true,
|
||||
EntryPoints: []string{},
|
||||
WhiteList: &types.WhiteList{
|
||||
SourceRange: []string{"1.1.1.1/24", "1234:abcd::42/32"},
|
||||
IPStrategy: &types.IPStrategy{
|
||||
ExcludedIPs: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all parameters",
|
||||
kvPairs: filler("traefik",
|
||||
|
@ -247,7 +280,8 @@ func TestProviderBuildConfiguration(t *testing.T) {
|
|||
withPair(pathFrontendPassTLSCert, "true"),
|
||||
withList(pathFrontendEntryPoints, "http", "https"),
|
||||
withList(pathFrontendWhiteListSourceRange, "1.1.1.1/24", "1234:abcd::42/32"),
|
||||
withPair(pathFrontendWhiteListUseXForwardedFor, "true"),
|
||||
withPair(pathFrontendWhiteListIPStrategyDepth, "5"),
|
||||
withList(pathFrontendWhiteListIPStrategyExcludedIPs, "1.1.1.1/24", "1234:abcd::42/32"),
|
||||
|
||||
withPair(pathFrontendAuthBasicRemoveHeader, "true"),
|
||||
withList(pathFrontendAuthBasicUsers, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"),
|
||||
|
@ -364,7 +398,10 @@ func TestProviderBuildConfiguration(t *testing.T) {
|
|||
PassTLSCert: true,
|
||||
WhiteList: &types.WhiteList{
|
||||
SourceRange: []string{"1.1.1.1/24", "1234:abcd::42/32"},
|
||||
UseXForwardedFor: true,
|
||||
IPStrategy: &types.IPStrategy{
|
||||
Depth: 5,
|
||||
ExcludedIPs: []string{"1.1.1.1/24", "1234:abcd::42/32"},
|
||||
},
|
||||
},
|
||||
Auth: &types.Auth{
|
||||
HeaderField: "X-WebAuth-User",
|
||||
|
@ -1240,31 +1277,8 @@ func TestWhiteList(t *testing.T) {
|
|||
SourceRange: []string{
|
||||
"10.10.10.10",
|
||||
},
|
||||
UseXForwardedFor: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "should return a struct when range and UseXForwardedFor",
|
||||
rootPath: "traefik/frontends/foo",
|
||||
kvPairs: filler("traefik",
|
||||
frontend("foo",
|
||||
withPair(pathFrontendWhiteListSourceRange, "10.10.10.10"),
|
||||
withPair(pathFrontendWhiteListUseXForwardedFor, "true"))),
|
||||
expected: &types.WhiteList{
|
||||
SourceRange: []string{
|
||||
"10.10.10.10",
|
||||
},
|
||||
UseXForwardedFor: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "should return nil when only UseXForwardedFor",
|
||||
rootPath: "traefik/frontends/foo",
|
||||
kvPairs: filler("traefik",
|
||||
frontend("foo",
|
||||
withPair(pathFrontendWhiteListUseXForwardedFor, "true"))),
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
|
|
@ -86,10 +86,11 @@ const (
|
|||
SuffixFrontendRedirectReplacement = "frontend.redirect.replacement"
|
||||
SuffixFrontendRedirectPermanent = "frontend.redirect.permanent"
|
||||
SuffixFrontendRule = "frontend.rule"
|
||||
SuffixFrontendWhitelistSourceRange = "frontend.whitelistSourceRange" // Deprecated
|
||||
SuffixFrontendWhiteList = "frontend.whiteList."
|
||||
SuffixFrontendWhiteListSourceRange = SuffixFrontendWhiteList + "sourceRange"
|
||||
SuffixFrontendWhiteListUseXForwardedFor = SuffixFrontendWhiteList + "useXForwardedFor"
|
||||
SuffixFrontendWhiteListIPStrategy = SuffixFrontendWhiteList + "ipStrategy"
|
||||
SuffixFrontendWhiteListIPStrategyDepth = SuffixFrontendWhiteListIPStrategy + ".depth"
|
||||
SuffixFrontendWhiteListIPStrategyExcludedIPS = SuffixFrontendWhiteListIPStrategy + ".excludedIPs"
|
||||
TraefikDomain = Prefix + SuffixDomain
|
||||
TraefikEnable = Prefix + SuffixEnable
|
||||
TraefikPort = Prefix + SuffixPort
|
||||
|
@ -150,9 +151,10 @@ const (
|
|||
TraefikFrontendRedirectReplacement = Prefix + SuffixFrontendRedirectReplacement
|
||||
TraefikFrontendRedirectPermanent = Prefix + SuffixFrontendRedirectPermanent
|
||||
TraefikFrontendRule = Prefix + SuffixFrontendRule
|
||||
TraefikFrontendWhitelistSourceRange = Prefix + SuffixFrontendWhitelistSourceRange // Deprecated
|
||||
TraefikFrontendWhiteListSourceRange = Prefix + SuffixFrontendWhiteListSourceRange
|
||||
TraefikFrontendWhiteListUseXForwardedFor = Prefix + SuffixFrontendWhiteListUseXForwardedFor
|
||||
TraefikFrontendWhiteListIPStrategy = Prefix + SuffixFrontendWhiteListIPStrategy
|
||||
TraefikFrontendWhiteListIPStrategyDepth = Prefix + SuffixFrontendWhiteListIPStrategyDepth
|
||||
TraefikFrontendWhiteListIPStrategyExcludedIPS = Prefix + SuffixFrontendWhiteListIPStrategyExcludedIPS
|
||||
TraefikFrontendRequestHeaders = Prefix + SuffixFrontendRequestHeaders
|
||||
TraefikFrontendResponseHeaders = Prefix + SuffixFrontendResponseHeaders
|
||||
TraefikFrontendAllowedHosts = Prefix + SuffixFrontendHeadersAllowedHosts
|
||||
|
|
|
@ -13,30 +13,32 @@ import (
|
|||
|
||||
// GetWhiteList Create white list from labels
|
||||
func GetWhiteList(labels map[string]string) *types.WhiteList {
|
||||
if Has(labels, TraefikFrontendWhitelistSourceRange) {
|
||||
log.Warnf("Deprecated configuration found: %s. Please use %s.", TraefikFrontendWhitelistSourceRange, TraefikFrontendWhiteListSourceRange)
|
||||
ranges := GetSliceStringValue(labels, TraefikFrontendWhiteListSourceRange)
|
||||
if len(ranges) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ranges := GetSliceStringValue(labels, TraefikFrontendWhiteListSourceRange)
|
||||
if len(ranges) > 0 {
|
||||
return &types.WhiteList{
|
||||
SourceRange: ranges,
|
||||
UseXForwardedFor: GetBoolValue(labels, TraefikFrontendWhiteListUseXForwardedFor, false),
|
||||
IPStrategy: getIPStrategy(labels),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Deprecated
|
||||
values := GetSliceStringValue(labels, TraefikFrontendWhitelistSourceRange)
|
||||
if len(values) > 0 {
|
||||
return &types.WhiteList{
|
||||
SourceRange: values,
|
||||
UseXForwardedFor: false,
|
||||
}
|
||||
}
|
||||
func getIPStrategy(labels map[string]string) *types.IPStrategy {
|
||||
ipStrategy := GetBoolValue(labels, TraefikFrontendWhiteListIPStrategy, false)
|
||||
depth := GetIntValue(labels, TraefikFrontendWhiteListIPStrategyDepth, 0)
|
||||
excludedIPs := GetSliceStringValue(labels, TraefikFrontendWhiteListIPStrategyExcludedIPS)
|
||||
|
||||
if depth == 0 && len(excludedIPs) == 0 && !ipStrategy {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &types.IPStrategy{
|
||||
Depth: depth,
|
||||
ExcludedIPs: excludedIPs,
|
||||
}
|
||||
}
|
||||
|
||||
// GetRedirect Create redirect from labels
|
||||
func GetRedirect(labels map[string]string) *types.Redirect {
|
||||
permanent := GetBoolValue(labels, TraefikFrontendRedirectPermanent, false)
|
||||
|
|
|
@ -134,18 +134,6 @@ func TestWhiteList(t *testing.T) {
|
|||
labels: map[string]string{},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
desc: "should return a struct when deprecated label",
|
||||
labels: map[string]string{
|
||||
TraefikFrontendWhitelistSourceRange: "10.10.10.10",
|
||||
},
|
||||
expected: &types.WhiteList{
|
||||
SourceRange: []string{
|
||||
"10.10.10.10",
|
||||
},
|
||||
UseXForwardedFor: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "should return a struct when only range",
|
||||
labels: map[string]string{
|
||||
|
@ -155,42 +143,75 @@ func TestWhiteList(t *testing.T) {
|
|||
SourceRange: []string{
|
||||
"10.10.10.10",
|
||||
},
|
||||
UseXForwardedFor: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "should return a struct when range and UseXForwardedFor",
|
||||
desc: "should return a struct with ip strategy depth",
|
||||
labels: map[string]string{
|
||||
TraefikFrontendWhiteListSourceRange: "10.10.10.10",
|
||||
TraefikFrontendWhiteListUseXForwardedFor: "true",
|
||||
TraefikFrontendWhiteListIPStrategyDepth: "5",
|
||||
},
|
||||
expected: &types.WhiteList{
|
||||
SourceRange: []string{
|
||||
"10.10.10.10",
|
||||
},
|
||||
UseXForwardedFor: true,
|
||||
IPStrategy: &types.IPStrategy{
|
||||
Depth: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "should return a struct when mix deprecated label and new labels",
|
||||
desc: "should return a struct with ip strategy depth and excluded ips",
|
||||
labels: map[string]string{
|
||||
TraefikFrontendWhitelistSourceRange: "20.20.20.20",
|
||||
TraefikFrontendWhiteListSourceRange: "10.10.10.10",
|
||||
TraefikFrontendWhiteListUseXForwardedFor: "true",
|
||||
TraefikFrontendWhiteListIPStrategyDepth: "5",
|
||||
TraefikFrontendWhiteListIPStrategyExcludedIPS: "10.10.10.10,10.10.10.11",
|
||||
},
|
||||
expected: &types.WhiteList{
|
||||
SourceRange: []string{
|
||||
"10.10.10.10",
|
||||
},
|
||||
UseXForwardedFor: true,
|
||||
IPStrategy: &types.IPStrategy{
|
||||
Depth: 5,
|
||||
ExcludedIPs: []string{
|
||||
"10.10.10.10",
|
||||
"10.10.10.11",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "should return nil when only UseXForwardedFor",
|
||||
desc: "should return a struct with ip strategy (remoteAddr) with no depth and no excludedIPs",
|
||||
labels: map[string]string{
|
||||
TraefikFrontendWhiteListUseXForwardedFor: "true",
|
||||
TraefikFrontendWhiteListSourceRange: "10.10.10.10",
|
||||
TraefikFrontendWhiteListIPStrategy: "true",
|
||||
},
|
||||
expected: &types.WhiteList{
|
||||
SourceRange: []string{
|
||||
"10.10.10.10",
|
||||
},
|
||||
IPStrategy: &types.IPStrategy{
|
||||
Depth: 0,
|
||||
ExcludedIPs: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "should return a struct with ip strategy with depth",
|
||||
labels: map[string]string{
|
||||
TraefikFrontendWhiteListSourceRange: "10.10.10.10",
|
||||
TraefikFrontendWhiteListIPStrategy: "true",
|
||||
TraefikFrontendWhiteListIPStrategyDepth: "5",
|
||||
},
|
||||
expected: &types.WhiteList{
|
||||
SourceRange: []string{
|
||||
"10.10.10.10",
|
||||
},
|
||||
IPStrategy: &types.IPStrategy{
|
||||
Depth: 5,
|
||||
ExcludedIPs: nil,
|
||||
},
|
||||
},
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -399,7 +399,8 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
withLabel(label.TraefikFrontendRedirectPermanent, "true"),
|
||||
withLabel(label.TraefikFrontendRule, "Host:traefik.io"),
|
||||
withLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10"),
|
||||
withLabel(label.TraefikFrontendWhiteListUseXForwardedFor, "true"),
|
||||
withLabel(label.TraefikFrontendWhiteListIPStrategyExcludedIPS, "10.10.10.10,10.10.10.11"),
|
||||
withLabel(label.TraefikFrontendWhiteListIPStrategyDepth, "5"),
|
||||
|
||||
withLabel(label.TraefikFrontendRequestHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8"),
|
||||
withLabel(label.TraefikFrontendResponseHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8"),
|
||||
|
@ -465,7 +466,10 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
WhiteList: &types.WhiteList{
|
||||
SourceRange: []string{"10.10.10.10"},
|
||||
UseXForwardedFor: true,
|
||||
IPStrategy: &types.IPStrategy{
|
||||
Depth: 5,
|
||||
ExcludedIPs: []string{"10.10.10.10", "10.10.10.11"},
|
||||
},
|
||||
},
|
||||
Headers: &types.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
|
@ -789,7 +793,6 @@ func TestBuildConfigurationSegments(t *testing.T) {
|
|||
withSegmentLabel(label.TraefikFrontendRedirectPermanent, "true", "containous"),
|
||||
withSegmentLabel(label.TraefikFrontendRule, "Host:traefik.io", "containous"),
|
||||
withSegmentLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10", "containous"),
|
||||
withSegmentLabel(label.TraefikFrontendWhiteListUseXForwardedFor, "true", "containous"),
|
||||
|
||||
withSegmentLabel(label.TraefikFrontendRequestHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", "containous"),
|
||||
withSegmentLabel(label.TraefikFrontendResponseHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", "containous"),
|
||||
|
@ -855,7 +858,6 @@ func TestBuildConfigurationSegments(t *testing.T) {
|
|||
},
|
||||
WhiteList: &types.WhiteList{
|
||||
SourceRange: []string{"10.10.10.10"},
|
||||
UseXForwardedFor: true,
|
||||
},
|
||||
Headers: &types.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
|
|
|
@ -356,7 +356,8 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
withLabel(label.TraefikFrontendRedirectPermanent, "true"),
|
||||
withLabel(label.TraefikFrontendRule, "Host:traefik.io"),
|
||||
withLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10"),
|
||||
withLabel(label.TraefikFrontendWhiteListUseXForwardedFor, "true"),
|
||||
withLabel(label.TraefikFrontendWhiteListIPStrategyExcludedIPS, "10.10.10.10,10.10.10.11"),
|
||||
withLabel(label.TraefikFrontendWhiteListIPStrategyDepth, "5"),
|
||||
|
||||
withLabel(label.TraefikFrontendRequestHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type:application/json; charset=utf-8"),
|
||||
withLabel(label.TraefikFrontendResponseHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type:application/json; charset=utf-8"),
|
||||
|
@ -428,7 +429,10 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
WhiteList: &types.WhiteList{
|
||||
SourceRange: []string{"10.10.10.10"},
|
||||
UseXForwardedFor: true,
|
||||
IPStrategy: &types.IPStrategy{
|
||||
Depth: 5,
|
||||
ExcludedIPs: []string{"10.10.10.10", "10.10.10.11"},
|
||||
},
|
||||
},
|
||||
Headers: &types.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
|
@ -709,7 +713,6 @@ func TestBuildConfigurationSegments(t *testing.T) {
|
|||
withSegmentLabel(label.TraefikFrontendRedirectPermanent, "true", "containous"),
|
||||
withSegmentLabel(label.TraefikFrontendRule, "Host:traefik.io", "containous"),
|
||||
withSegmentLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10", "containous"),
|
||||
withSegmentLabel(label.TraefikFrontendWhiteListUseXForwardedFor, "true", "containous"),
|
||||
|
||||
withSegmentLabel(label.TraefikFrontendRequestHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", "containous"),
|
||||
withSegmentLabel(label.TraefikFrontendResponseHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", "containous"),
|
||||
|
@ -777,7 +780,6 @@ func TestBuildConfigurationSegments(t *testing.T) {
|
|||
|
||||
WhiteList: &types.WhiteList{
|
||||
SourceRange: []string{"10.10.10.10"},
|
||||
UseXForwardedFor: true,
|
||||
},
|
||||
Headers: &types.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
|
|
|
@ -84,7 +84,8 @@ func TestProviderBuildConfiguration(t *testing.T) {
|
|||
label.TraefikFrontendRedirectPermanent: "true",
|
||||
label.TraefikFrontendRule: "Host:traefik.io",
|
||||
label.TraefikFrontendWhiteListSourceRange: "10.10.10.10",
|
||||
label.TraefikFrontendWhiteListUseXForwardedFor: "true",
|
||||
label.TraefikFrontendWhiteListIPStrategyExcludedIPS: "10.10.10.10,10.10.10.11",
|
||||
label.TraefikFrontendWhiteListIPStrategyDepth: "5",
|
||||
|
||||
label.TraefikFrontendRequestHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
|
||||
label.TraefikFrontendResponseHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
|
||||
|
@ -156,7 +157,10 @@ func TestProviderBuildConfiguration(t *testing.T) {
|
|||
SourceRange: []string{
|
||||
"10.10.10.10",
|
||||
},
|
||||
UseXForwardedFor: true,
|
||||
IPStrategy: &types.IPStrategy{
|
||||
Depth: 5,
|
||||
ExcludedIPs: []string{"10.10.10.10", "10.10.10.11"},
|
||||
},
|
||||
},
|
||||
Headers: &types.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
|
@ -314,7 +318,6 @@ func TestProviderBuildConfiguration(t *testing.T) {
|
|||
label.Prefix + "sauternes." + label.SuffixFrontendRedirectReplacement: "nope",
|
||||
label.Prefix + "sauternes." + label.SuffixFrontendRedirectPermanent: "true",
|
||||
label.Prefix + "sauternes." + label.SuffixFrontendWhiteListSourceRange: "10.10.10.10",
|
||||
label.Prefix + "sauternes." + label.SuffixFrontendWhiteListUseXForwardedFor: "true",
|
||||
|
||||
label.Prefix + "sauternes." + label.SuffixFrontendRequestHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
|
||||
label.Prefix + "sauternes." + label.SuffixFrontendResponseHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
|
||||
|
@ -383,7 +386,6 @@ func TestProviderBuildConfiguration(t *testing.T) {
|
|||
SourceRange: []string{
|
||||
"10.10.10.10",
|
||||
},
|
||||
UseXForwardedFor: true,
|
||||
},
|
||||
Headers: &types.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/whitelist"
|
||||
"github.com/vulcand/oxy/forward"
|
||||
)
|
||||
|
||||
// NewHeaderRewriter Create a header rewriter
|
||||
func NewHeaderRewriter(trustedIPs []string, insecure bool) (forward.ReqRewriter, error) {
|
||||
ips, err := whitelist.NewIP(trustedIPs, insecure, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
hostname = "localhost"
|
||||
}
|
||||
|
||||
return &headerRewriter{
|
||||
secureRewriter: &forward.HeaderRewriter{TrustForwardHeader: false, Hostname: hostname},
|
||||
insecureRewriter: &forward.HeaderRewriter{TrustForwardHeader: true, Hostname: hostname},
|
||||
ips: ips,
|
||||
insecure: insecure,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type headerRewriter struct {
|
||||
secureRewriter forward.ReqRewriter
|
||||
insecureRewriter forward.ReqRewriter
|
||||
insecure bool
|
||||
ips *whitelist.IP
|
||||
}
|
||||
|
||||
func (h *headerRewriter) Rewrite(req *http.Request) {
|
||||
if h.insecure {
|
||||
h.insecureRewriter.Rewrite(req)
|
||||
return
|
||||
}
|
||||
|
||||
err := h.ips.IsAuthorized(req)
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
h.secureRewriter.Rewrite(req)
|
||||
return
|
||||
}
|
||||
|
||||
h.insecureRewriter.Rewrite(req)
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHeaderRewriter_Rewrite(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
remoteAddr string
|
||||
trustedIPs []string
|
||||
insecure bool
|
||||
expected map[string]string
|
||||
}{
|
||||
{
|
||||
desc: "Secure & authorized",
|
||||
remoteAddr: "10.10.10.10:80",
|
||||
trustedIPs: []string{"10.10.10.10"},
|
||||
insecure: false,
|
||||
expected: map[string]string{
|
||||
"X-Foo": "bar",
|
||||
"X-Forwarded-For": "30.30.30.30",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Secure & unauthorized",
|
||||
remoteAddr: "50.50.50.50:80",
|
||||
trustedIPs: []string{"10.10.10.10"},
|
||||
insecure: false,
|
||||
expected: map[string]string{
|
||||
"X-Foo": "bar",
|
||||
"X-Forwarded-For": "",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Secure & authorized error",
|
||||
remoteAddr: "10.10.10.10",
|
||||
trustedIPs: []string{"10.10.10.10"},
|
||||
insecure: false,
|
||||
expected: map[string]string{
|
||||
"X-Foo": "bar",
|
||||
"X-Forwarded-For": "",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "insecure & authorized",
|
||||
remoteAddr: "10.10.10.10:80",
|
||||
trustedIPs: []string{"10.10.10.10"},
|
||||
insecure: true,
|
||||
expected: map[string]string{
|
||||
"X-Foo": "bar",
|
||||
"X-Forwarded-For": "30.30.30.30",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "insecure & unauthorized",
|
||||
remoteAddr: "50.50.50.50:80",
|
||||
trustedIPs: []string{"10.10.10.10"},
|
||||
insecure: true,
|
||||
expected: map[string]string{
|
||||
"X-Foo": "bar",
|
||||
"X-Forwarded-For": "30.30.30.30",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "insecure & authorized error",
|
||||
remoteAddr: "10.10.10.10",
|
||||
trustedIPs: []string{"10.10.10.10"},
|
||||
insecure: true,
|
||||
expected: map[string]string{
|
||||
"X-Foo": "bar",
|
||||
"X-Forwarded-For": "30.30.30.30",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rewriter, err := NewHeaderRewriter(test.trustedIPs, test.insecure)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "http://20.20.20.20/foo", nil)
|
||||
require.NoError(t, err)
|
||||
req.RemoteAddr = test.remoteAddr
|
||||
|
||||
req.Header.Set("X-Foo", "bar")
|
||||
req.Header.Set("X-Forwarded-For", "30.30.30.30")
|
||||
|
||||
rewriter.Rewrite(req)
|
||||
|
||||
for key, value := range test.expected {
|
||||
assert.Equal(t, value, req.Header.Get(key))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/configuration"
|
||||
"github.com/containous/traefik/h2c"
|
||||
"github.com/containous/traefik/ip"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/metrics"
|
||||
"github.com/containous/traefik/middlewares"
|
||||
|
@ -31,7 +32,6 @@ import (
|
|||
"github.com/containous/traefik/safe"
|
||||
traefiktls "github.com/containous/traefik/tls"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/containous/traefik/whitelist"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/negroni"
|
||||
"github.com/xenolf/lego/acme"
|
||||
|
@ -552,7 +552,7 @@ func (s *Server) prepareServer(entryPointName string, entryPoint *configuration.
|
|||
if entryPoint.ProxyProtocol != nil {
|
||||
listener, err = buildProxyProtocolListener(entryPoint, listener)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, fmt.Errorf("error creating proxy protocol listener: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -572,23 +572,32 @@ func (s *Server) prepareServer(entryPointName string, entryPoint *configuration.
|
|||
}
|
||||
|
||||
func buildProxyProtocolListener(entryPoint *configuration.EntryPoint, listener net.Listener) (net.Listener, error) {
|
||||
IPs, err := whitelist.NewIP(entryPoint.ProxyProtocol.TrustedIPs, entryPoint.ProxyProtocol.Insecure, false)
|
||||
var sourceCheck func(addr net.Addr) (bool, error)
|
||||
if entryPoint.ProxyProtocol.Insecure {
|
||||
sourceCheck = func(_ net.Addr) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
} else {
|
||||
checker, err := ip.NewChecker(entryPoint.ProxyProtocol.TrustedIPs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating whitelist: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sourceCheck = func(addr net.Addr) (bool, error) {
|
||||
ipAddr, ok := addr.(*net.TCPAddr)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("type error %v", addr)
|
||||
}
|
||||
|
||||
return checker.ContainsIP(ipAddr.IP), nil
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("Enabling ProxyProtocol for trusted IPs %v", entryPoint.ProxyProtocol.TrustedIPs)
|
||||
|
||||
return &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), nil
|
||||
},
|
||||
SourceCheck: sourceCheck,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -233,17 +233,11 @@ func (s *Server) buildForwarder(entryPointName string, entryPoint *configuration
|
|||
return nil, fmt.Errorf("failed to create RoundTripper for frontend %s: %v", frontendName, err)
|
||||
}
|
||||
|
||||
rewriter, err := NewHeaderRewriter(entryPoint.ForwardedHeaders.TrustedIPs, entryPoint.ForwardedHeaders.Insecure)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating rewriter for frontend %s: %v", frontendName, err)
|
||||
}
|
||||
|
||||
var fwd http.Handler
|
||||
fwd, err = forward.New(
|
||||
forward.Stream(true),
|
||||
forward.PassHostHeader(frontend.PassHostHeader),
|
||||
forward.RoundTripper(roundTripper),
|
||||
forward.Rewriter(rewriter),
|
||||
forward.ResponseModifier(responseModifier),
|
||||
forward.BufferPool(s.bufferPool),
|
||||
forward.WebsocketConnectionClosedHook(func(req *http.Request, conn net.Conn) {
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/containous/traefik/middlewares/accesslog"
|
||||
mauth "github.com/containous/traefik/middlewares/auth"
|
||||
"github.com/containous/traefik/middlewares/errorpages"
|
||||
"github.com/containous/traefik/middlewares/forwardedheaders"
|
||||
"github.com/containous/traefik/middlewares/redirect"
|
||||
"github.com/containous/traefik/types"
|
||||
thoas_stats "github.com/thoas/stats"
|
||||
|
@ -47,7 +48,7 @@ func (s *Server) buildMiddlewares(frontendName string, frontend *types.Frontend,
|
|||
}
|
||||
|
||||
// Whitelist
|
||||
ipWhitelistMiddleware, err := buildIPWhiteLister(frontend.WhiteList, frontend.WhitelistSourceRange)
|
||||
ipWhitelistMiddleware, err := buildIPWhiteLister(frontend.WhiteList, s.entryPoints[entryPointName].Configuration.ClientIPStrategy)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("error creating IP Whitelister: %s", err)
|
||||
}
|
||||
|
@ -146,9 +147,18 @@ func (s *Server) buildServerEntryPointMiddlewares(serverEntryPointName string) (
|
|||
serverMiddlewares = append(serverMiddlewares, &middlewares.Compress{})
|
||||
}
|
||||
|
||||
ipWhitelistMiddleware, err := buildIPWhiteLister(
|
||||
s.entryPoints[serverEntryPointName].Configuration.WhiteList,
|
||||
s.entryPoints[serverEntryPointName].Configuration.WhitelistSourceRange)
|
||||
if s.entryPoints[serverEntryPointName].Configuration.ForwardedHeaders != nil {
|
||||
xForwardedMiddleware, err := forwardedheaders.NewXforwarded(
|
||||
s.entryPoints[serverEntryPointName].Configuration.ForwardedHeaders.Insecure,
|
||||
s.entryPoints[serverEntryPointName].Configuration.ForwardedHeaders.TrustedIPs,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create xforwarded headers middleware: %v", err)
|
||||
}
|
||||
serverMiddlewares = append(serverMiddlewares, xForwardedMiddleware)
|
||||
}
|
||||
|
||||
ipWhitelistMiddleware, err := buildIPWhiteLister(s.entryPoints[serverEntryPointName].Configuration.WhiteList, s.entryPoints[serverEntryPointName].Configuration.ClientIPStrategy)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create ip whitelist middleware: %v", err)
|
||||
}
|
||||
|
@ -268,16 +278,23 @@ func (s *Server) buildRedirectHandler(srcEntryPointName string, opt *types.Redir
|
|||
return redirection, nil
|
||||
}
|
||||
|
||||
func buildIPWhiteLister(whiteList *types.WhiteList, wlRange []string) (*middlewares.IPWhiteLister, error) {
|
||||
if whiteList != nil &&
|
||||
len(whiteList.SourceRange) > 0 {
|
||||
return middlewares.NewIPWhiteLister(whiteList.SourceRange, whiteList.UseXForwardedFor)
|
||||
} else if len(wlRange) > 0 {
|
||||
return middlewares.NewIPWhiteLister(wlRange, false)
|
||||
}
|
||||
func buildIPWhiteLister(whiteList *types.WhiteList, ipStrategy *types.IPStrategy) (*middlewares.IPWhiteLister, error) {
|
||||
if whiteList == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if whiteList.IPStrategy != nil {
|
||||
ipStrategy = whiteList.IPStrategy
|
||||
}
|
||||
|
||||
strategy, err := ipStrategy.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return middlewares.NewIPWhiteLister(whiteList.SourceRange, strategy)
|
||||
}
|
||||
|
||||
func (s *Server) wrapNegroniHandlerWithAccessLog(handler negroni.Handler, frontendName string) negroni.Handler {
|
||||
if s.accessLoggerMiddleware != nil {
|
||||
saveBackend := accesslog.NewSaveNegroniBackend(handler, "Træfik")
|
||||
|
|
|
@ -36,9 +36,11 @@ func TestServerEntryPointWhitelistConfig(t *testing.T) {
|
|||
desc: "whitelist middleware should be added if configured on entrypoint",
|
||||
entrypoint: &configuration.EntryPoint{
|
||||
Address: ":0",
|
||||
WhitelistSourceRange: []string{
|
||||
WhiteList: &types.WhiteList{
|
||||
SourceRange: []string{
|
||||
"127.0.0.1/32",
|
||||
},
|
||||
},
|
||||
ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true},
|
||||
},
|
||||
expectMiddleware: true,
|
||||
|
@ -96,23 +98,6 @@ func TestBuildIPWhiteLister(t *testing.T) {
|
|||
middlewareConfigured: false,
|
||||
errMessage: "",
|
||||
},
|
||||
{
|
||||
desc: "whitelists configured (deprecated)",
|
||||
whitelistSourceRange: []string{
|
||||
"1.2.3.4/24",
|
||||
"fe80::/16",
|
||||
},
|
||||
middlewareConfigured: true,
|
||||
errMessage: "",
|
||||
},
|
||||
{
|
||||
desc: "invalid whitelists configured (deprecated)",
|
||||
whitelistSourceRange: []string{
|
||||
"foo",
|
||||
},
|
||||
middlewareConfigured: false,
|
||||
errMessage: "parsing CIDR whitelist [foo]: parsing CIDR white list <nil>: invalid CIDR address: foo",
|
||||
},
|
||||
{
|
||||
desc: "whitelists configured",
|
||||
whiteList: &types.WhiteList{
|
||||
|
@ -120,22 +105,10 @@ func TestBuildIPWhiteLister(t *testing.T) {
|
|||
"1.2.3.4/24",
|
||||
"fe80::/16",
|
||||
},
|
||||
UseXForwardedFor: false,
|
||||
},
|
||||
middlewareConfigured: true,
|
||||
errMessage: "",
|
||||
},
|
||||
{
|
||||
desc: "invalid whitelists configured (deprecated)",
|
||||
whiteList: &types.WhiteList{
|
||||
SourceRange: []string{
|
||||
"foo",
|
||||
},
|
||||
UseXForwardedFor: false,
|
||||
},
|
||||
middlewareConfigured: false,
|
||||
errMessage: "parsing CIDR whitelist [foo]: parsing CIDR white list <nil>: invalid CIDR address: foo",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
@ -143,7 +116,7 @@ func TestBuildIPWhiteLister(t *testing.T) {
|
|||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
middleware, err := buildIPWhiteLister(test.whiteList, test.whitelistSourceRange)
|
||||
middleware, err := buildIPWhiteLister(test.whiteList, nil)
|
||||
|
||||
if test.errMessage != "" {
|
||||
require.EqualError(t, err, test.errMessage)
|
||||
|
@ -258,6 +231,11 @@ func TestServerGenericFrontendAuthFail(t *testing.T) {
|
|||
"http": &configuration.EntryPoint{ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true}},
|
||||
},
|
||||
}
|
||||
entryPoints := map[string]EntryPoint{
|
||||
"http": {
|
||||
Configuration: globalConfig.EntryPoints["http"],
|
||||
},
|
||||
}
|
||||
|
||||
dynamicConfigs := types.Configurations{
|
||||
"config": &types.Configuration{
|
||||
|
@ -286,7 +264,7 @@ func TestServerGenericFrontendAuthFail(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
srv := NewServer(globalConfig, nil, nil)
|
||||
srv := NewServer(globalConfig, nil, entryPoints)
|
||||
|
||||
_, err := srv.loadConfig(dynamicConfigs, globalConfig)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -123,7 +123,13 @@
|
|||
sourceRange = [{{range $whitelist.SourceRange }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
||||
{{if $whitelist.IPStrategy }}
|
||||
[frontends."frontend-{{ $service.ServiceName }}".whiteList.IPStrategy]
|
||||
depth = {{ $whitelist.IPStrategy.Depth }}
|
||||
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{ $redirect := getRedirect $service.TraefikLabels }}
|
||||
|
|
|
@ -123,7 +123,13 @@
|
|||
sourceRange = [{{range $whitelist.SourceRange }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
||||
{{if $whitelist.IPStrategy }}
|
||||
[frontends."frontend-{{ $frontendName }}".whiteList.IPStrategy]
|
||||
depth = {{ $whitelist.IPStrategy.Depth }}
|
||||
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{ $redirect := getRedirect $container.SegmentLabels }}
|
||||
|
|
|
@ -122,7 +122,13 @@
|
|||
sourceRange = [{{range $whitelist.SourceRange }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
||||
{{if $whitelist.IPStrategy }}
|
||||
[frontends."frontend-{{ $serviceName }}".whiteList.IPStrategy]
|
||||
depth = {{ $whitelist.IPStrategy.Depth }}
|
||||
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{ $redirect := getRedirect $instance.TraefikLabels }}
|
||||
|
|
|
@ -90,10 +90,16 @@
|
|||
|
||||
{{if $frontend.WhiteList }}
|
||||
[frontends."{{ $frontendName }}".whiteList]
|
||||
sourceRange = [{{range $frontend.WhiteList.SourceRange }}
|
||||
sourceRange = [{{range $frontend.Whitelist.SourceRange }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
useXForwardedFor = {{ $frontend.WhiteList.UseXForwardedFor }}
|
||||
{{if $frontend.Whitelist.IPStrategy }}
|
||||
[frontends."{{ $frontendName }}".whiteList.IPStrategy]
|
||||
depth = {{ $frontend.Whitelist.IPStrategy.Depth }}
|
||||
excludedIPs = [{{range $frontend.Whitelist.IPStrategy.ExcludedIPs }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{if $frontend.Redirect }}
|
||||
|
|
|
@ -122,7 +122,13 @@
|
|||
sourceRange = [{{range $whitelist.SourceRange }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
||||
{{if $whitelist.IPStrategy }}
|
||||
[frontends."{{ $frontendName }}".whiteList.IPStrategy]
|
||||
depth = {{ $whitelist.IPStrategy.Depth }}
|
||||
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{ $redirect := getRedirect $frontend }}
|
||||
|
|
|
@ -125,7 +125,13 @@
|
|||
sourceRange = [{{range $whitelist.SourceRange }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
||||
{{if $whitelist.IPStrategy }}
|
||||
[frontends."{{ $frontendName }}".whiteList.IPStrategy]
|
||||
depth = {{ $whitelist.IPStrategy.Depth }}
|
||||
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{ $redirect := getRedirect $app.SegmentLabels }}
|
||||
|
|
|
@ -125,7 +125,13 @@
|
|||
sourceRange = [{{range $whitelist.SourceRange }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
||||
{{if $whitelist.IPStrategy }}
|
||||
[frontends."frontend-{{ $frontendName }}".whiteList.IPStrategy]
|
||||
depth = {{ $whitelist.IPStrategy.Depth }}
|
||||
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{ $redirect := getRedirect $app.TraefikLabels }}
|
||||
|
|
|
@ -123,7 +123,13 @@
|
|||
sourceRange = [{{range $whitelist.SourceRange }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
||||
{{if $whitelist.IPStrategy }}
|
||||
[frontends."frontend-{{ $frontendName }}".whiteList.IPStrategy]
|
||||
depth = {{ $whitelist.IPStrategy.Depth }}
|
||||
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{ $redirect := getRedirect $service.SegmentLabels }}
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/abronan/valkeyrie/store"
|
||||
"github.com/containous/flaeg/parse"
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/ip"
|
||||
"github.com/containous/traefik/log"
|
||||
traefiktls "github.com/containous/traefik/tls"
|
||||
"github.com/mitchellh/hashstructure"
|
||||
|
@ -64,7 +65,7 @@ type Buffering struct {
|
|||
// WhiteList contains white list configuration.
|
||||
type WhiteList struct {
|
||||
SourceRange []string `json:"sourceRange,omitempty"`
|
||||
UseXForwardedFor bool `json:"useXForwardedFor,omitempty" export:"true"`
|
||||
IPStrategy *IPStrategy `json:"ipStrategy,omitempty"`
|
||||
}
|
||||
|
||||
// HealthCheck holds HealthCheck configuration
|
||||
|
@ -183,7 +184,6 @@ type Frontend struct {
|
|||
PassHostHeader bool `json:"passHostHeader,omitempty"`
|
||||
PassTLSCert bool `json:"passTLSCert,omitempty"`
|
||||
Priority int `json:"priority"`
|
||||
WhitelistSourceRange []string `json:"whitelistSourceRange,omitempty"` // Deprecated
|
||||
WhiteList *WhiteList `json:"whiteList,omitempty"`
|
||||
Headers *Headers `json:"headers,omitempty"`
|
||||
Errors map[string]*ErrorPage `json:"errors,omitempty"`
|
||||
|
@ -611,3 +611,37 @@ func (h HTTPCodeRanges) Contains(statusCode int) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IPStrategy Configuration to choose the IP selection strategy.
|
||||
type IPStrategy struct {
|
||||
Depth int `json:"depth,omitempty" export:"true"`
|
||||
ExcludedIPs []string `json:"excludedIPs,omitempty"`
|
||||
}
|
||||
|
||||
// Get an IP selection strategy
|
||||
// if nil return the RemoteAddr strategy
|
||||
// else return a strategy base on the configuration using the X-Forwarded-For Header.
|
||||
// Depth override the ExcludedIPs
|
||||
func (s *IPStrategy) Get() (ip.Strategy, error) {
|
||||
if s == nil {
|
||||
return &ip.RemoteAddrStrategy{}, nil
|
||||
}
|
||||
|
||||
if s.Depth > 0 {
|
||||
return &ip.DepthStrategy{
|
||||
Depth: s.Depth,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if len(s.ExcludedIPs) > 0 {
|
||||
checker, err := ip.NewChecker(s.ExcludedIPs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ip.CheckerStrategy{
|
||||
Checker: checker,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &ip.RemoteAddrStrategy{}, nil
|
||||
}
|
||||
|
|
|
@ -4,7 +4,9 @@ import (
|
|||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/ip"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHeaders_ShouldReturnFalseWhenNotHasCustomHeadersDefined(t *testing.T) {
|
||||
|
@ -136,3 +138,49 @@ func TestHTTPCodeRanges_Contains(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPStrategy_Get(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
ipStrategy *IPStrategy
|
||||
expected ip.Strategy
|
||||
}{
|
||||
{
|
||||
desc: "IPStrategy is nil",
|
||||
expected: &ip.RemoteAddrStrategy{},
|
||||
},
|
||||
{
|
||||
desc: "IPStrategy is not nil but with no values",
|
||||
ipStrategy: &IPStrategy{},
|
||||
expected: &ip.RemoteAddrStrategy{},
|
||||
},
|
||||
{
|
||||
desc: "IPStrategy with Depth",
|
||||
ipStrategy: &IPStrategy{Depth: 3},
|
||||
expected: &ip.DepthStrategy{},
|
||||
},
|
||||
{
|
||||
desc: "IPStrategy with ExcludedIPs",
|
||||
ipStrategy: &IPStrategy{ExcludedIPs: []string{"10.0.0.1"}},
|
||||
expected: &ip.CheckerStrategy{},
|
||||
},
|
||||
{
|
||||
desc: "IPStrategy with ExcludedIPs and Depth",
|
||||
ipStrategy: &IPStrategy{Depth: 4, ExcludedIPs: []string{"10.0.0.1"}},
|
||||
expected: &ip.DepthStrategy{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
strategy, err := test.ipStrategy.Get()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.IsType(t, test.expected, strategy)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
145
whitelist/ip.go
145
whitelist/ip.go
|
@ -1,145 +0,0 @@
|
|||
package whitelist
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// XForwardedFor Header name
|
||||
XForwardedFor = "X-Forwarded-For"
|
||||
)
|
||||
|
||||
// IP allows to check that addresses are in a white list
|
||||
type IP struct {
|
||||
whiteListsIPs []*net.IP
|
||||
whiteListsNet []*net.IPNet
|
||||
insecure bool
|
||||
useXForwardedFor bool
|
||||
}
|
||||
|
||||
// NewIP builds a new IP given a list of CIDR-Strings to white list
|
||||
func NewIP(whiteList []string, insecure bool, useXForwardedFor bool) (*IP, error) {
|
||||
if len(whiteList) == 0 && !insecure {
|
||||
return nil, errors.New("no white list provided")
|
||||
}
|
||||
|
||||
ip := IP{
|
||||
insecure: insecure,
|
||||
useXForwardedFor: useXForwardedFor,
|
||||
}
|
||||
|
||||
if !insecure {
|
||||
for _, ipMask := range whiteList {
|
||||
if ipAddr := net.ParseIP(ipMask); ipAddr != nil {
|
||||
ip.whiteListsIPs = append(ip.whiteListsIPs, &ipAddr)
|
||||
} else {
|
||||
_, ipAddr, err := net.ParseCIDR(ipMask)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing CIDR white list %s: %v", ipAddr, err)
|
||||
}
|
||||
ip.whiteListsNet = append(ip.whiteListsNet, ipAddr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &ip, nil
|
||||
}
|
||||
|
||||
// IsAuthorized checks if provided request is authorized by the white list
|
||||
func (ip *IP) IsAuthorized(req *http.Request) error {
|
||||
if ip.insecure {
|
||||
return nil
|
||||
}
|
||||
|
||||
var invalidMatches []string
|
||||
|
||||
if ip.useXForwardedFor {
|
||||
xFFs := req.Header[XForwardedFor]
|
||||
if len(xFFs) > 0 {
|
||||
for _, xFF := range xFFs {
|
||||
xffs := strings.Split(xFF, ",")
|
||||
for _, xff := range xffs {
|
||||
ok, err := ip.contains(parseHost(xff))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
invalidMatches = append(invalidMatches, xff)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ok, err := ip.contains(host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
invalidMatches = append(invalidMatches, req.RemoteAddr)
|
||||
return fmt.Errorf("%q matched none of the white list", strings.Join(invalidMatches, ", "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// contains checks if provided address is in the white list
|
||||
func (ip *IP) contains(addr string) (bool, error) {
|
||||
ipAddr, err := parseIP(addr)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("unable to parse address: %s: %s", addr, err)
|
||||
}
|
||||
|
||||
return ip.ContainsIP(ipAddr), nil
|
||||
}
|
||||
|
||||
// ContainsIP checks if provided address is in the white list
|
||||
func (ip *IP) ContainsIP(addr net.IP) bool {
|
||||
if ip.insecure {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, whiteListIP := range ip.whiteListsIPs {
|
||||
if whiteListIP.Equal(addr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, whiteListNet := range ip.whiteListsNet {
|
||||
if whiteListNet.Contains(addr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func parseIP(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
|
||||
}
|
||||
|
||||
func parseHost(addr string) string {
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return addr
|
||||
}
|
||||
return host
|
||||
}
|
|
@ -1,452 +0,0 @@
|
|||
package whitelist
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIsAuthorized(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
whiteList []string
|
||||
allowXForwardedFor bool
|
||||
remoteAddr string
|
||||
xForwardedForValues []string
|
||||
authorized bool
|
||||
}{
|
||||
{
|
||||
desc: "allow UseXForwardedFor, remoteAddr not in range, UseXForwardedFor in range",
|
||||
whiteList: []string{"1.2.3.4/24"},
|
||||
allowXForwardedFor: true,
|
||||
remoteAddr: "10.2.3.1:123",
|
||||
xForwardedForValues: []string{"1.2.3.1", "10.2.3.1"},
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
desc: "allow UseXForwardedFor, remoteAddr not in range, UseXForwardedFor in range (compact XFF)",
|
||||
whiteList: []string{"1.2.3.4/24"},
|
||||
allowXForwardedFor: true,
|
||||
remoteAddr: "10.2.3.1:123",
|
||||
xForwardedForValues: []string{"1.2.3.1, 10.2.3.1"},
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
desc: "allow UseXForwardedFor, remoteAddr in range, UseXForwardedFor in range",
|
||||
whiteList: []string{"1.2.3.4/24"},
|
||||
allowXForwardedFor: true,
|
||||
remoteAddr: "1.2.3.1:123",
|
||||
xForwardedForValues: []string{"1.2.3.1", "10.2.3.1"},
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
desc: "allow UseXForwardedFor, remoteAddr in range, UseXForwardedFor not in range",
|
||||
whiteList: []string{"1.2.3.4/24"},
|
||||
allowXForwardedFor: true,
|
||||
remoteAddr: "1.2.3.1:123",
|
||||
xForwardedForValues: []string{"10.2.3.1", "10.2.3.1"},
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
desc: "allow UseXForwardedFor, remoteAddr not in range, UseXForwardedFor not in range",
|
||||
whiteList: []string{"1.2.3.4/24"},
|
||||
allowXForwardedFor: true,
|
||||
remoteAddr: "10.2.3.1:123",
|
||||
xForwardedForValues: []string{"10.2.3.1", "10.2.3.1"},
|
||||
authorized: false,
|
||||
},
|
||||
{
|
||||
desc: "don't allow UseXForwardedFor, remoteAddr not in range, UseXForwardedFor in range",
|
||||
whiteList: []string{"1.2.3.4/24"},
|
||||
allowXForwardedFor: false,
|
||||
remoteAddr: "10.2.3.1:123",
|
||||
xForwardedForValues: []string{"1.2.3.1", "10.2.3.1"},
|
||||
authorized: false,
|
||||
},
|
||||
{
|
||||
desc: "don't allow UseXForwardedFor, remoteAddr in range, UseXForwardedFor in range",
|
||||
whiteList: []string{"1.2.3.4/24"},
|
||||
allowXForwardedFor: false,
|
||||
remoteAddr: "1.2.3.1:123",
|
||||
xForwardedForValues: []string{"1.2.3.1", "10.2.3.1"},
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
desc: "don't allow UseXForwardedFor, remoteAddr in range, UseXForwardedFor not in range",
|
||||
whiteList: []string{"1.2.3.4/24"},
|
||||
allowXForwardedFor: false,
|
||||
remoteAddr: "1.2.3.1:123",
|
||||
xForwardedForValues: []string{"10.2.3.1", "10.2.3.1"},
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
desc: "don't allow UseXForwardedFor, remoteAddr not in range, UseXForwardedFor not in range",
|
||||
whiteList: []string{"1.2.3.4/24"},
|
||||
allowXForwardedFor: false,
|
||||
remoteAddr: "10.2.3.1:123",
|
||||
xForwardedForValues: []string{"10.2.3.1", "10.2.3.1"},
|
||||
authorized: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req := NewRequest(test.remoteAddr, test.xForwardedForValues)
|
||||
|
||||
whiteLister, err := NewIP(test.whiteList, false, test.allowXForwardedFor)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = whiteLister.IsAuthorized(req)
|
||||
if test.authorized {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
whiteList []string
|
||||
expectedWhitelists []*net.IPNet
|
||||
errMessage string
|
||||
}{
|
||||
{
|
||||
desc: "nil whitelist",
|
||||
whiteList: nil,
|
||||
expectedWhitelists: nil,
|
||||
errMessage: "no white list provided",
|
||||
}, {
|
||||
desc: "empty whitelist",
|
||||
whiteList: []string{},
|
||||
expectedWhitelists: nil,
|
||||
errMessage: "no white list provided",
|
||||
}, {
|
||||
desc: "whitelist containing empty string",
|
||||
whiteList: []string{
|
||||
"1.2.3.4/24",
|
||||
"",
|
||||
"fe80::/16",
|
||||
},
|
||||
expectedWhitelists: nil,
|
||||
errMessage: "parsing CIDR white list <nil>: invalid CIDR address: ",
|
||||
}, {
|
||||
desc: "whitelist containing only an empty string",
|
||||
whiteList: []string{
|
||||
"",
|
||||
},
|
||||
expectedWhitelists: nil,
|
||||
errMessage: "parsing CIDR white list <nil>: invalid CIDR address: ",
|
||||
}, {
|
||||
desc: "whitelist containing an invalid string",
|
||||
whiteList: []string{
|
||||
"foo",
|
||||
},
|
||||
expectedWhitelists: nil,
|
||||
errMessage: "parsing CIDR white list <nil>: invalid CIDR address: foo",
|
||||
}, {
|
||||
desc: "IPv4 & IPv6 whitelist",
|
||||
whiteList: []string{
|
||||
"1.2.3.4/24",
|
||||
"fe80::/16",
|
||||
},
|
||||
expectedWhitelists: []*net.IPNet{
|
||||
{IP: net.IPv4(1, 2, 3, 0).To4(), Mask: net.IPv4Mask(255, 255, 255, 0)},
|
||||
{IP: net.ParseIP("fe80::"), Mask: net.IPMask(net.ParseIP("ffff::"))},
|
||||
},
|
||||
errMessage: "",
|
||||
}, {
|
||||
desc: "IPv4 only",
|
||||
whiteList: []string{
|
||||
"127.0.0.1/8",
|
||||
},
|
||||
expectedWhitelists: []*net.IPNet{
|
||||
{IP: net.IPv4(127, 0, 0, 0).To4(), Mask: net.IPv4Mask(255, 0, 0, 0)},
|
||||
},
|
||||
errMessage: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
whiteLister, err := NewIP(test.whiteList, false, false)
|
||||
if test.errMessage != "" {
|
||||
require.EqualError(t, err, test.errMessage)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
for index, actual := range whiteLister.whiteListsNet {
|
||||
expected := test.expectedWhitelists[index]
|
||||
assert.Equal(t, expected.IP, actual.IP)
|
||||
assert.Equal(t, expected.Mask.String(), actual.Mask.String())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsIsAllowed(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
whitelistStrings []string
|
||||
passIPs []string
|
||||
rejectIPs []string
|
||||
}{
|
||||
{
|
||||
desc: "IPv4",
|
||||
whitelistStrings: []string{"1.2.3.4/24"},
|
||||
passIPs: []string{
|
||||
"1.2.3.1",
|
||||
"1.2.3.32",
|
||||
"1.2.3.156",
|
||||
"1.2.3.255",
|
||||
},
|
||||
rejectIPs: []string{
|
||||
"1.2.16.1",
|
||||
"1.2.32.1",
|
||||
"127.0.0.1",
|
||||
"8.8.8.8",
|
||||
},
|
||||
},
|
||||
{
|
||||
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{"8.8.8.8/32"},
|
||||
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: "multiple IPv4",
|
||||
whitelistStrings: []string{"1.2.3.4/24", "8.8.8.8/8"},
|
||||
passIPs: []string{
|
||||
"1.2.3.1",
|
||||
"1.2.3.32",
|
||||
"1.2.3.156",
|
||||
"1.2.3.255",
|
||||
"8.8.4.4",
|
||||
"8.0.0.1",
|
||||
"8.32.42.128",
|
||||
"8.255.255.255",
|
||||
},
|
||||
rejectIPs: []string{
|
||||
"1.2.16.1",
|
||||
"1.2.32.1",
|
||||
"127.0.0.1",
|
||||
"4.4.4.4",
|
||||
"4.8.8.8",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "IPv6",
|
||||
whitelistStrings: []string{"2a03:4000:6:d080::/64"},
|
||||
passIPs: []string{
|
||||
"2a03:4000:6:d080::",
|
||||
"2a03:4000:6:d080::1",
|
||||
"2a03:4000:6:d080:dead:beef:ffff:ffff",
|
||||
"2a03:4000:6:d080::42",
|
||||
},
|
||||
rejectIPs: []string{
|
||||
"2a03:4000:7:d080::",
|
||||
"2a03:4000:7:d080::1",
|
||||
"fe80::",
|
||||
"4242::1",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "IPv6 single IP",
|
||||
whitelistStrings: []string{"2a03:4000:6:d080::42/128"},
|
||||
passIPs: []string{"2a03:4000:6:d080::42"},
|
||||
rejectIPs: []string{
|
||||
"2a03:4000:6:d080::1",
|
||||
"2a03:4000:6:d080:dead:beef:ffff:ffff",
|
||||
"2a03:4000:6:d080::43",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "multiple IPv6",
|
||||
whitelistStrings: []string{"2a03:4000:6:d080::/64", "fe80::/16"},
|
||||
passIPs: []string{
|
||||
"2a03:4000:6:d080::",
|
||||
"2a03:4000:6:d080::1",
|
||||
"2a03:4000:6:d080:dead:beef:ffff:ffff",
|
||||
"2a03:4000:6:d080::42",
|
||||
"fe80::1",
|
||||
"fe80:aa00:00bb:4232:ff00:eeee:00ff:1111",
|
||||
"fe80::fe80",
|
||||
},
|
||||
rejectIPs: []string{
|
||||
"2a03:4000:7:d080::",
|
||||
"2a03:4000:7:d080::1",
|
||||
"4242::1",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "multiple IPv6 & IPv4",
|
||||
whitelistStrings: []string{"2a03:4000:6:d080::/64", "fe80::/16", "1.2.3.4/24", "8.8.8.8/8"},
|
||||
passIPs: []string{
|
||||
"2a03:4000:6:d080::",
|
||||
"2a03:4000:6:d080::1",
|
||||
"2a03:4000:6:d080:dead:beef:ffff:ffff",
|
||||
"2a03:4000:6:d080::42",
|
||||
"fe80::1",
|
||||
"fe80:aa00:00bb:4232:ff00:eeee:00ff:1111",
|
||||
"fe80::fe80",
|
||||
"1.2.3.1",
|
||||
"1.2.3.32",
|
||||
"1.2.3.156",
|
||||
"1.2.3.255",
|
||||
"8.8.4.4",
|
||||
"8.0.0.1",
|
||||
"8.32.42.128",
|
||||
"8.255.255.255",
|
||||
},
|
||||
rejectIPs: []string{
|
||||
"2a03:4000:7:d080::",
|
||||
"2a03:4000:7:d080::1",
|
||||
"4242::1",
|
||||
"1.2.16.1",
|
||||
"1.2.32.1",
|
||||
"127.0.0.1",
|
||||
"4.4.4.4",
|
||||
"4.8.8.8",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "broken IP-addresses",
|
||||
whitelistStrings: []string{"127.0.0.1/32"},
|
||||
passIPs: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
whiteLister, err := NewIP(test.whitelistStrings, false, false)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, whiteLister)
|
||||
|
||||
for _, testIP := range test.passIPs {
|
||||
allowed, err := whiteLister.contains(testIP)
|
||||
require.NoError(t, err)
|
||||
assert.Truef(t, allowed, "%s should have passed.", testIP)
|
||||
}
|
||||
|
||||
for _, testIP := range test.rejectIPs {
|
||||
allowed, err := whiteLister.contains(testIP)
|
||||
require.NoError(t, err)
|
||||
assert.Falsef(t, allowed, "%s should not have passed.", testIP)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsInsecure(t *testing.T) {
|
||||
mustNewIP := func(whitelistStrings []string, insecure bool) *IP {
|
||||
ip, err := NewIP(whitelistStrings, insecure, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
whiteLister *IP
|
||||
ip string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
desc: "valid ip and insecure",
|
||||
whiteLister: mustNewIP([]string{"1.2.3.4/24"}, true),
|
||||
ip: "1.2.3.1",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
desc: "invalid ip and insecure",
|
||||
whiteLister: mustNewIP([]string{"1.2.3.4/24"}, true),
|
||||
ip: "10.2.3.1",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
desc: "invalid ip and secure",
|
||||
whiteLister: mustNewIP([]string{"1.2.3.4/24"}, false),
|
||||
ip: "10.2.3.1",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ok, err := test.whiteLister.contains(test.ip)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected, ok)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsBrokenIPs(t *testing.T) {
|
||||
brokenIPs := []string{
|
||||
"foo",
|
||||
"10.0.0.350",
|
||||
"fe:::80",
|
||||
"",
|
||||
"\\&$§&/(",
|
||||
}
|
||||
|
||||
whiteLister, err := NewIP([]string{"1.2.3.4/24"}, false, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, testIP := range brokenIPs {
|
||||
_, err := whiteLister.contains(testIP)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func NewRequest(remoteAddr string, xForwardedFor []string) *http.Request {
|
||||
req := httptest.NewRequest(http.MethodGet, "http://example.com/foo", nil)
|
||||
if len(remoteAddr) > 0 {
|
||||
req.RemoteAddr = remoteAddr
|
||||
}
|
||||
if len(xForwardedFor) > 0 {
|
||||
for _, xff := range xForwardedFor {
|
||||
req.Header.Add(XForwardedFor, xff)
|
||||
}
|
||||
}
|
||||
return req
|
||||
}
|
Loading…
Reference in a new issue