IPStrategy for selecting IP in whitelist

This commit is contained in:
SALLEYRON Julien 2018-08-24 16:20:03 +02:00 committed by Traefiker Bot
parent 1ec4e03738
commit 00728e711c
65 changed files with 2444 additions and 1837 deletions

View file

@ -80,7 +80,11 @@ func TestDo_globalConfiguration(t *testing.T) {
TrustForwardHeader: true, 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{}, Compress: &configuration.Compress{},
ProxyProtocol: &configuration.ProxyProtocol{ ProxyProtocol: &configuration.ProxyProtocol{
TrustedIPs: []string{"127.0.0.1/32", "192.168.0.1"}, TrustedIPs: []string{"127.0.0.1/32", "192.168.0.1"},
@ -125,7 +129,11 @@ func TestDo_globalConfiguration(t *testing.T) {
TrustForwardHeader: true, 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{}, Compress: &configuration.Compress{},
ProxyProtocol: &configuration.ProxyProtocol{ ProxyProtocol: &configuration.ProxyProtocol{
TrustedIPs: []string{"127.0.0.1/32", "192.168.0.1"}, TrustedIPs: []string{"127.0.0.1/32", "192.168.0.1"},

View file

@ -179,7 +179,13 @@ var _templatesConsul_catalogTmpl = []byte(`[backends]
sourceRange = [{{range $whitelist.SourceRange }} sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}", "{{.}}",
{{end}}] {{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}} {{end}}
{{ $redirect := getRedirect $service.TraefikLabels }} {{ $redirect := getRedirect $service.TraefikLabels }}
@ -418,7 +424,13 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
sourceRange = [{{range $whitelist.SourceRange }} sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }} {{if $whitelist.IPStrategy }}
[frontends."frontend-{{ $frontendName }}".whiteList.IPStrategy]
depth = {{ $whitelist.IPStrategy.Depth }}
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
"{{.}}",
{{end}}]
{{end}}
{{end}} {{end}}
{{ $redirect := getRedirect $container.SegmentLabels }} {{ $redirect := getRedirect $container.SegmentLabels }}
@ -657,7 +669,13 @@ var _templatesEcsTmpl = []byte(`[backends]
sourceRange = [{{range $whitelist.SourceRange }} sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }} {{if $whitelist.IPStrategy }}
[frontends."frontend-{{ $serviceName }}".whiteList.IPStrategy]
depth = {{ $whitelist.IPStrategy.Depth }}
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
"{{.}}",
{{end}}]
{{end}}
{{end}} {{end}}
{{ $redirect := getRedirect $instance.TraefikLabels }} {{ $redirect := getRedirect $instance.TraefikLabels }}
@ -904,10 +922,16 @@ var _templatesKubernetesTmpl = []byte(`[backends]
{{if $frontend.WhiteList }} {{if $frontend.WhiteList }}
[frontends."{{ $frontendName }}".whiteList] [frontends."{{ $frontendName }}".whiteList]
sourceRange = [{{range $frontend.WhiteList.SourceRange }} sourceRange = [{{range $frontend.Whitelist.SourceRange }}
"{{.}}", "{{.}}",
{{end}}] {{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}} {{end}}
{{if $frontend.Redirect }} {{if $frontend.Redirect }}
@ -1148,7 +1172,13 @@ var _templatesKvTmpl = []byte(`[backends]
sourceRange = [{{range $whitelist.SourceRange }} sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }} {{if $whitelist.IPStrategy }}
[frontends."{{ $frontendName }}".whiteList.IPStrategy]
depth = {{ $whitelist.IPStrategy.Depth }}
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
"{{.}}",
{{end}}]
{{end}}
{{end}} {{end}}
{{ $redirect := getRedirect $frontend }} {{ $redirect := getRedirect $frontend }}
@ -1404,7 +1434,13 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
sourceRange = [{{range $whitelist.SourceRange }} sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }} {{if $whitelist.IPStrategy }}
[frontends."{{ $frontendName }}".whiteList.IPStrategy]
depth = {{ $whitelist.IPStrategy.Depth }}
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
"{{.}}",
{{end}}]
{{end}}
{{end}} {{end}}
{{ $redirect := getRedirect $app.SegmentLabels }} {{ $redirect := getRedirect $app.SegmentLabels }}
@ -1645,7 +1681,13 @@ var _templatesMesosTmpl = []byte(`[backends]
sourceRange = [{{range $whitelist.SourceRange }} sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }} {{if $whitelist.IPStrategy }}
[frontends."frontend-{{ $frontendName }}".whiteList.IPStrategy]
depth = {{ $whitelist.IPStrategy.Depth }}
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
"{{.}}",
{{end}}]
{{end}}
{{end}} {{end}}
{{ $redirect := getRedirect $app.TraefikLabels }} {{ $redirect := getRedirect $app.TraefikLabels }}
@ -1908,7 +1950,13 @@ var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
sourceRange = [{{range $whitelist.SourceRange }} sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }} {{if $whitelist.IPStrategy }}
[frontends."frontend-{{ $frontendName }}".whiteList.IPStrategy]
depth = {{ $whitelist.IPStrategy.Depth }}
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
"{{.}}",
{{end}}]
{{end}}
{{end}} {{end}}
{{ $redirect := getRedirect $service.SegmentLabels }} {{ $redirect := getRedirect $service.SegmentLabels }}

View file

@ -108,7 +108,7 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
if len(gc.EntryPoints) == 0 { if len(gc.EntryPoints) == 0 {
gc.EntryPoints = map[string]*EntryPoint{"http": { gc.EntryPoints = map[string]*EntryPoint{"http": {
Address: ":80", Address: ":80",
ForwardedHeaders: &ForwardedHeaders{Insecure: true}, ForwardedHeaders: &ForwardedHeaders{},
}} }}
gc.DefaultEntryPoints = []string{"http"} gc.DefaultEntryPoints = []string{"http"}
} }
@ -126,18 +126,7 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
entryPoint := gc.EntryPoints[entryPointName] entryPoint := gc.EntryPoints[entryPointName]
// ForwardedHeaders must be remove in the next breaking version // ForwardedHeaders must be remove in the next breaking version
if entryPoint.ForwardedHeaders == nil { if entryPoint.ForwardedHeaders == nil {
entryPoint.ForwardedHeaders = &ForwardedHeaders{Insecure: true} entryPoint.ForwardedHeaders = &ForwardedHeaders{}
}
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
}
} }
} }

View file

@ -2,6 +2,7 @@ package configuration
import ( import (
"fmt" "fmt"
"strconv"
"strings" "strings"
"github.com/containous/traefik/log" "github.com/containous/traefik/log"
@ -15,16 +16,15 @@ type EntryPoint struct {
TLS *tls.TLS `export:"true"` TLS *tls.TLS `export:"true"`
Redirect *types.Redirect `export:"true"` Redirect *types.Redirect `export:"true"`
Auth *types.Auth `export:"true"` Auth *types.Auth `export:"true"`
WhitelistSourceRange []string // Deprecated
WhiteList *types.WhiteList `export:"true"` WhiteList *types.WhiteList `export:"true"`
Compress *Compress `export:"true"` Compress *Compress `export:"true"`
ProxyProtocol *ProxyProtocol `export:"true"` ProxyProtocol *ProxyProtocol `export:"true"`
ForwardedHeaders *ForwardedHeaders `export:"true"` ForwardedHeaders *ForwardedHeaders `export:"true"`
ClientIPStrategy *types.IPStrategy `export:"true"`
} }
// Compress contains compress configuration // Compress contains compress configuration
type Compress struct { type Compress struct{}
}
// ProxyProtocol contains Proxy-Protocol configuration // ProxyProtocol contains Proxy-Protocol configuration
type ProxyProtocol struct { type ProxyProtocol struct {
@ -68,11 +68,6 @@ func (ep *EntryPoints) Type() string {
func (ep *EntryPoints) Set(value string) error { func (ep *EntryPoints) Set(value string) error {
result := parseEntryPointsConfiguration(value) result := parseEntryPointsConfiguration(value)
var whiteListSourceRange []string
if len(result["whitelistsourcerange"]) > 0 {
whiteListSourceRange = strings.Split(result["whitelistsourcerange"], ",")
}
var compress *Compress var compress *Compress
if len(result["compress"]) > 0 { if len(result["compress"]) > 0 {
compress = &Compress{} compress = &Compress{}
@ -89,24 +84,37 @@ func (ep *EntryPoints) Set(value string) error {
Auth: makeEntryPointAuth(result), Auth: makeEntryPointAuth(result),
Redirect: makeEntryPointRedirect(result), Redirect: makeEntryPointRedirect(result),
Compress: compress, Compress: compress,
WhitelistSourceRange: whiteListSourceRange,
WhiteList: makeWhiteList(result), WhiteList: makeWhiteList(result),
ProxyProtocol: makeEntryPointProxyProtocol(result), ProxyProtocol: makeEntryPointProxyProtocol(result),
ForwardedHeaders: makeEntryPointForwardedHeaders(result), ForwardedHeaders: makeEntryPointForwardedHeaders(result),
ClientIPStrategy: makeIPStrategy("clientipstrategy", result),
} }
return nil return nil
} }
func makeWhiteList(result map[string]string) *types.WhiteList { func makeWhiteList(result map[string]string) *types.WhiteList {
var wl *types.WhiteList
if rawRange, ok := result["whitelist_sourcerange"]; ok { if rawRange, ok := result["whitelist_sourcerange"]; ok {
wl = &types.WhiteList{ return &types.WhiteList{
SourceRange: strings.Split(rawRange, ","), 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 { func makeEntryPointAuth(result map[string]string) *types.Auth {
@ -184,15 +192,14 @@ func makeEntryPointProxyProtocol(result map[string]string) *ProxyProtocol {
} }
if proxyProtocol != nil && proxyProtocol.Insecure { 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 return proxyProtocol
} }
func makeEntryPointForwardedHeaders(result map[string]string) *ForwardedHeaders { func makeEntryPointForwardedHeaders(result map[string]string) *ForwardedHeaders {
// TODO must be changed to false by default in the next breaking version. forwardedHeaders := &ForwardedHeaders{}
forwardedHeaders := &ForwardedHeaders{Insecure: true}
if _, ok := result["forwardedheaders_insecure"]; ok { if _, ok := result["forwardedheaders_insecure"]; ok {
forwardedHeaders.Insecure = toBool(result, "forwardedheaders_insecure") forwardedHeaders.Insecure = toBool(result, "forwardedheaders_insecure")
} }
@ -300,3 +307,14 @@ func toBool(conf map[string]string, key string) bool {
} }
return false 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
}

View file

@ -45,9 +45,11 @@ func Test_parseEntryPointsConfiguration(t *testing.T) {
"Auth.Forward.TLS.Cert:path/to/foo.cert " + "Auth.Forward.TLS.Cert:path/to/foo.cert " +
"Auth.Forward.TLS.Key:path/to/foo.key " + "Auth.Forward.TLS.Key:path/to/foo.key " +
"Auth.Forward.TLS.InsecureSkipVerify:true " + "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.sourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " + "WhiteList.IPStrategy.depth:3 " +
"whiteList.useXForwardedFor:true ", "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{ expectedResult: map[string]string{
"address": ":8000", "address": ":8000",
"auth_basic_users": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", "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_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_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", "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_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.Cert:path/to/foo.cert " +
"Auth.Forward.TLS.Key:path/to/foo.key " + "Auth.Forward.TLS.Key:path/to/foo.key " +
"Auth.Forward.TLS.InsecureSkipVerify:true " + "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.sourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " + "WhiteList.IPStrategy.depth:3 " +
"whiteList.useXForwardedFor:true ", "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", expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{ expectedEntryPoint: &EntryPoint{
Address: ":8000", Address: ":8000",
@ -265,18 +271,19 @@ func TestEntryPoints_Set(t *testing.T) {
}, },
HeaderField: "X-WebAuth-User", HeaderField: "X-WebAuth-User",
}, },
WhitelistSourceRange: []string{
"10.42.0.0/16",
"152.89.1.33/32",
"afed:be44::/16",
},
WhiteList: &types.WhiteList{ WhiteList: &types.WhiteList{
SourceRange: []string{ SourceRange: []string{
"10.42.0.0/16", "10.42.0.0/16",
"152.89.1.33/32", "152.89.1.33/32",
"afed:be44::/16", "afed:be44::/16",
}, },
UseXForwardedFor: true, IPStrategy: &types.IPStrategy{
Depth: 3,
ExcludedIPs: []string{
"10.0.0.3/24",
"20.0.0.3/24",
},
},
}, },
Compress: &Compress{}, Compress: &Compress{},
ProxyProtocol: &ProxyProtocol{ ProxyProtocol: &ProxyProtocol{
@ -290,6 +297,13 @@ func TestEntryPoints_Set(t *testing.T) {
"20.0.0.3/24", "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.replacement:http://mydomain/$1 " +
"redirect.permanent:true " + "redirect.permanent:true " +
"compress: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 " + "proxyProtocol.TrustedIPs:192.168.0.1 " +
"forwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 " + "forwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 " +
"auth.basic.users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 " + "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", HeaderField: "X-WebAuth-User",
}, },
WhitelistSourceRange: []string{ WhiteList: &types.WhiteList{
SourceRange: []string{
"10.42.0.0/16", "10.42.0.0/16",
"152.89.1.33/32", "152.89.1.33/32",
"afed:be44::/16", "afed:be44::/16",
}, },
},
Compress: &Compress{}, Compress: &Compress{},
ProxyProtocol: &ProxyProtocol{ ProxyProtocol: &ProxyProtocol{
Insecure: false, Insecure: false,
@ -399,12 +415,12 @@ func TestEntryPoints_Set(t *testing.T) {
expression: "Name:foo", expression: "Name:foo",
expectedEntryPointName: "foo", expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{ expectedEntryPoint: &EntryPoint{
ForwardedHeaders: &ForwardedHeaders{Insecure: true}, ForwardedHeaders: &ForwardedHeaders{Insecure: false},
}, },
}, },
{ {
name: "ForwardedHeaders insecure true", name: "ForwardedHeaders insecure true",
expression: "Name:foo ForwardedHeaders.Insecure:true", expression: "Name:foo ForwardedHeaders.insecure:true",
expectedEntryPointName: "foo", expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{ expectedEntryPoint: &EntryPoint{
ForwardedHeaders: &ForwardedHeaders{Insecure: true}, ForwardedHeaders: &ForwardedHeaders{Insecure: true},
@ -412,7 +428,7 @@ func TestEntryPoints_Set(t *testing.T) {
}, },
{ {
name: "ForwardedHeaders insecure false", name: "ForwardedHeaders insecure false",
expression: "Name:foo ForwardedHeaders.Insecure:false", expression: "Name:foo ForwardedHeaders.insecure:false",
expectedEntryPointName: "foo", expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{ expectedEntryPoint: &EntryPoint{
ForwardedHeaders: &ForwardedHeaders{Insecure: false}, ForwardedHeaders: &ForwardedHeaders{Insecure: false},
@ -430,19 +446,19 @@ func TestEntryPoints_Set(t *testing.T) {
}, },
{ {
name: "ProxyProtocol insecure true", name: "ProxyProtocol insecure true",
expression: "Name:foo ProxyProtocol.Insecure:true", expression: "Name:foo ProxyProtocol.insecure:true",
expectedEntryPointName: "foo", expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{ expectedEntryPoint: &EntryPoint{
ForwardedHeaders: &ForwardedHeaders{Insecure: true}, ForwardedHeaders: &ForwardedHeaders{},
ProxyProtocol: &ProxyProtocol{Insecure: true}, ProxyProtocol: &ProxyProtocol{Insecure: true},
}, },
}, },
{ {
name: "ProxyProtocol insecure false", name: "ProxyProtocol insecure false",
expression: "Name:foo ProxyProtocol.Insecure:false", expression: "Name:foo ProxyProtocol.insecure:false",
expectedEntryPointName: "foo", expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{ expectedEntryPoint: &EntryPoint{
ForwardedHeaders: &ForwardedHeaders{Insecure: true}, ForwardedHeaders: &ForwardedHeaders{},
ProxyProtocol: &ProxyProtocol{}, 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", expression: "Name:foo ProxyProtocol.TrustedIPs:10.0.0.3/24,20.0.0.3/24",
expectedEntryPointName: "foo", expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{ expectedEntryPoint: &EntryPoint{
ForwardedHeaders: &ForwardedHeaders{Insecure: true}, ForwardedHeaders: &ForwardedHeaders{},
ProxyProtocol: &ProxyProtocol{ ProxyProtocol: &ProxyProtocol{
TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"}, TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"},
}, },
@ -463,7 +479,7 @@ func TestEntryPoints_Set(t *testing.T) {
expectedEntryPointName: "foo", expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{ expectedEntryPoint: &EntryPoint{
Compress: &Compress{}, Compress: &Compress{},
ForwardedHeaders: &ForwardedHeaders{Insecure: true}, ForwardedHeaders: &ForwardedHeaders{},
}, },
}, },
{ {
@ -472,7 +488,7 @@ func TestEntryPoints_Set(t *testing.T) {
expectedEntryPointName: "foo", expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{ expectedEntryPoint: &EntryPoint{
Compress: &Compress{}, Compress: &Compress{},
ForwardedHeaders: &ForwardedHeaders{Insecure: true}, ForwardedHeaders: &ForwardedHeaders{},
}, },
}, },
} }

View file

@ -16,9 +16,17 @@ func NewInternalRouterAggregator(globalConfiguration configuration.GlobalConfigu
var serverMiddlewares []negroni.Handler var serverMiddlewares []negroni.Handler
if globalConfiguration.EntryPoints[entryPointName].WhiteList != nil { if globalConfiguration.EntryPoints[entryPointName].WhiteList != nil {
ipWhitelistMiddleware, err := middlewares.NewIPWhiteLister( ipStrategy := globalConfiguration.EntryPoints[entryPointName].ClientIPStrategy
globalConfiguration.EntryPoints[entryPointName].WhiteList.SourceRange, if globalConfiguration.EntryPoints[entryPointName].WhiteList.IPStrategy != nil {
globalConfiguration.EntryPoints[entryPointName].WhiteList.UseXForwardedFor) 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 { if err != nil {
log.Fatalf("Error creating whitelist middleware: %s", err) log.Fatalf("Error creating whitelist middleware: %s", err)
} }

View file

@ -95,7 +95,7 @@ Additional settings can be defined using Consul Catalog tags.
The default prefix is `traefik`. The default prefix is `traefik`.
| Label | Description | | Label | Description |
|-------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |--------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `<prefix>.enable=false` | Disables this container in Træfik. | | `<prefix>.enable=false` | Disables this container in Træfik. |
| `<prefix>.protocol=https` | Overrides the default `http` protocol. | | `<prefix>.protocol=https` | Overrides the default `http` protocol. |
| `<prefix>.weight=10` | Assigns this weight to the container. | | `<prefix>.weight=10` | Assigns this weight to the container. |
@ -123,11 +123,11 @@ Additional settings can be defined using Consul Catalog tags.
| `<prefix>.frontend.auth.digest.removeHeader=true` | If set to `true`, removes the `Authorization` header. | | `<prefix>.frontend.auth.digest.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
| `<prefix>.frontend.auth.digest.users=EXPR` | Sets digest authentication to this frontend in CSV format: `User:Realm:Hash,User:Realm:Hash`. | | `<prefix>.frontend.auth.digest.users=EXPR` | Sets digest authentication to this frontend in CSV format: `User:Realm:Hash,User:Realm:Hash`. |
| `<prefix>.frontend.auth.digest.usersfile=/path/.htdigest` | Sets digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. | | `<prefix>.frontend.auth.digest.usersfile=/path/.htdigest` | Sets digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
| `<prefix>.frontend.auth.forward.address=https://example.com`| Sets the URL of the authentication server. | | `<prefix>.frontend.auth.forward.address=https://example.com` | Sets the URL of the authentication server. |
| `<prefix>.frontend.auth.forward.tls.ca=/path/ca.pem` | Sets the Certificate Authority (CA) for the TLS connection with the authentication server. | | `<prefix>.frontend.auth.forward.tls.ca=/path/ca.pem` | Sets the Certificate Authority (CA) for the TLS connection with the authentication server. |
| `<prefix>.frontend.auth.forward.tls.caOptional=true` | Checks the certificates if present but do not force to be signed by a specified Certificate Authority (CA). | | `<prefix>.frontend.auth.forward.tls.caOptional=true` | Checks the certificates if present but do not force to be signed by a specified Certificate Authority (CA). |
| `<prefix>.frontend.auth.forward.tls.cert=/path/server.pem` | Sets the Certificate for the TLS connection with the authentication server. | | `<prefix>.frontend.auth.forward.tls.cert=/path/server.pem` | Sets the Certificate for the TLS connection with the authentication server. |
| `<prefix>.frontend.auth.forward.tls.insecureSkipVerify=true`| If set to true invalid SSL certificates are accepted. | | `<prefix>.frontend.auth.forward.tls.insecureSkipVerify=true` | If set to true invalid SSL certificates are accepted. |
| `<prefix>.frontend.auth.forward.tls.key=/path/server.key` | Sets the Certificate for the TLS connection with the authentication server. | | `<prefix>.frontend.auth.forward.tls.key=/path/server.key` | Sets the Certificate for the TLS connection with the authentication server. |
| `<prefix>.frontend.auth.forward.trustForwardHeader=true` | Trusts X-Forwarded-* headers. | | `<prefix>.frontend.auth.forward.trustForwardHeader=true` | Trusts X-Forwarded-* headers. |
| `<prefix>.frontend.auth.headerField=X-WebAuth-User` | Sets the header used to pass the authenticated user to the application. | | `<prefix>.frontend.auth.headerField=X-WebAuth-User` | Sets the header used to pass the authenticated user to the application. |
@ -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.redirect.permanent=true` | Returns 301 instead of 302. |
| `<prefix>.frontend.rule=EXPR` | Overrides the default frontend rule. Default: `Host:{{.ServiceName}}.{{.Domain}}`. | | `<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.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 ### Custom Headers

View file

@ -208,7 +208,7 @@ services:
Labels can be used on containers to override default behavior. Labels can be used on containers to override default behavior.
| Label | Description | | Label | Description |
|------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |-------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.docker.network` | Overrides the default docker network to use for connections to the container. [1] | | `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.domain` | Sets the default domain for the frontend rules. |
| `traefik.enable=false` | Disables this container in Træfik. | | `traefik.enable=false` | Disables this container in Træfik. |
@ -241,11 +241,11 @@ Labels can be used on containers to override default behavior.
| `traefik.frontend.auth.digest.removeHeader=true` | If set to `true`, removes the `Authorization` header. | | `traefik.frontend.auth.digest.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
| `traefik.frontend.auth.digest.users=EXPR` | Sets the digest authentication to this frontend in CSV format: `User:Realm:Hash,User:Realm:Hash`. | | `traefik.frontend.auth.digest.users=EXPR` | Sets the digest authentication to this frontend in CSV format: `User:Realm:Hash,User:Realm:Hash`. |
| `traefik.frontend.auth.digest.usersfile=/path/.htdigest` | Sets the digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. | | `traefik.frontend.auth.digest.usersfile=/path/.htdigest` | Sets the digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
| `traefik.frontend.auth.forward.address=https://example.com`| Sets the URL of the authentication server. | | `traefik.frontend.auth.forward.address=https://example.com` | Sets the URL of the authentication server. |
| `traefik.frontend.auth.forward.tls.ca=/path/ca.pem` | Sets the Certificate Authority (CA) for the TLS connection with the authentication server. | | `traefik.frontend.auth.forward.tls.ca=/path/ca.pem` | Sets the Certificate Authority (CA) for the TLS connection with the authentication server. |
| `traefik.frontend.auth.forward.tls.caOptional=true` | Checks the certificates if present but do not force to be signed by a specified Certificate Authority (CA). | | `traefik.frontend.auth.forward.tls.caOptional=true` | Checks the certificates if present but do not force to be signed by a specified Certificate Authority (CA). |
| `traefik.frontend.auth.forward.tls.cert=/path/server.pem` | Sets the Certificate for the TLS connection with the authentication server. | | `traefik.frontend.auth.forward.tls.cert=/path/server.pem` | Sets the Certificate for the TLS connection with the authentication server. |
| `traefik.frontend.auth.forward.tls.insecureSkipVerify=true`| If set to true invalid SSL certificates are accepted. | | `traefik.frontend.auth.forward.tls.insecureSkipVerify=true` | If set to true invalid SSL certificates are accepted. |
| `traefik.frontend.auth.forward.tls.key=/path/server.key` | Sets the Certificate for the TLS connection with the authentication server. | | `traefik.frontend.auth.forward.tls.key=/path/server.key` | Sets the Certificate for the TLS connection with the authentication server. |
| `traefik.frontend.auth.forward.trustForwardHeader=true` | Trusts X-Forwarded-* headers. | | `traefik.frontend.auth.forward.trustForwardHeader=true` | Trusts X-Forwarded-* headers. |
| `traefik.frontend.auth.headerField=X-WebAuth-User` | Sets the header user to pass the authenticated user to the application. | | `traefik.frontend.auth.headerField=X-WebAuth-User` | Sets the header user to pass the authenticated user to the application. |
@ -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.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.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.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`: [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). 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`. `echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g`.
The result will be `user:$$apr1$$9Cv/OMGj$$ZomWQzuQbL.3TRCS81A1g/`, note additional symbol `$` makes escaping. The result will be `user:$$apr1$$9Cv/OMGj$$ZomWQzuQbL.3TRCS81A1g/`, note additional symbol `$` makes escaping.
#### Custom Headers #### Custom Headers
| Label | Description | | Label | Description |
@ -320,7 +321,7 @@ You can define as many segments as ports exposed in a container.
Segment labels override the default behavior. Segment labels override the default behavior.
| Label | Description | | Label | Description |
|---------------------------------------------------------------------------|---------------------------------------------------------------| |------------------------------------------------------------------------------|----------------------------------------------------------------|
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` | | `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` | | `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` | | `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
@ -333,11 +334,11 @@ Segment labels override the default behavior.
| `traefik.<segment_name>.frontend.auth.digest.removeHeader=true` | Same as `traefik.frontend.auth.digest.removeHeader` | | `traefik.<segment_name>.frontend.auth.digest.removeHeader=true` | Same as `traefik.frontend.auth.digest.removeHeader` |
| `traefik.<segment_name>.frontend.auth.digest.users=EXPR` | Same as `traefik.frontend.auth.digest.users` | | `traefik.<segment_name>.frontend.auth.digest.users=EXPR` | Same as `traefik.frontend.auth.digest.users` |
| `traefik.<segment_name>.frontend.auth.digest.usersfile=/path/.htdigest` | Same as `traefik.frontend.auth.digest.usersfile` | | `traefik.<segment_name>.frontend.auth.digest.usersfile=/path/.htdigest` | Same as `traefik.frontend.auth.digest.usersfile` |
| `traefik.<segment_name>.frontend.auth.forward.address=https://example.com`| Same as `traefik.frontend.auth.forward.address` | | `traefik.<segment_name>.frontend.auth.forward.address=https://example.com` | Same as `traefik.frontend.auth.forward.address` |
| `traefik.<segment_name>.frontend.auth.forward.tls.ca=/path/ca.pem` | Same as `traefik.frontend.auth.forward.tls.ca` | | `traefik.<segment_name>.frontend.auth.forward.tls.ca=/path/ca.pem` | Same as `traefik.frontend.auth.forward.tls.ca` |
| `traefik.<segment_name>.frontend.auth.forward.tls.caOptional=true` | Same as `traefik.frontend.auth.forward.tls.caOptional` | | `traefik.<segment_name>.frontend.auth.forward.tls.caOptional=true` | Same as `traefik.frontend.auth.forward.tls.caOptional` |
| `traefik.<segment_name>.frontend.auth.forward.tls.cert=/path/server.pem` | Same as `traefik.frontend.auth.forward.tls.cert` | | `traefik.<segment_name>.frontend.auth.forward.tls.cert=/path/server.pem` | Same as `traefik.frontend.auth.forward.tls.cert` |
| `traefik.<segment_name>.frontend.auth.forward.tls.insecureSkipVerify=true`| Same as `traefik.frontend.auth.forward.tls.insecureSkipVerify`| | `traefik.<segment_name>.frontend.auth.forward.tls.insecureSkipVerify=true` | Same as `traefik.frontend.auth.forward.tls.insecureSkipVerify` |
| `traefik.<segment_name>.frontend.auth.forward.tls.key=/path/server.key` | Same as `traefik.frontend.auth.forward.tls.key` | | `traefik.<segment_name>.frontend.auth.forward.tls.key=/path/server.key` | Same as `traefik.frontend.auth.forward.tls.key` |
| `traefik.<segment_name>.frontend.auth.forward.trustForwardHeader=true` | Same as `traefik.frontend.auth.forward.trustForwardHeader` | | `traefik.<segment_name>.frontend.auth.forward.trustForwardHeader=true` | Same as `traefik.frontend.auth.forward.trustForwardHeader` |
| `traefik.<segment_name>.frontend.auth.headerField=X-WebAuth-User` | Same as `traefik.frontend.auth.headerField` | | `traefik.<segment_name>.frontend.auth.headerField=X-WebAuth-User` | Same as `traefik.frontend.auth.headerField` |
@ -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.redirect.permanent=true` | Same as `traefik.frontend.redirect.permanent` |
| `traefik.<segment_name>.frontend.rule=EXP` | Same as `traefik.frontend.rule` | | `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.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 #### Custom Headers

View file

@ -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: Labels can be used on task containers to override default behavior:
| Label | Description | | Label | Description |
|------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |-------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.domain` | Sets the default domain for frontend rules. | | `traefik.domain` | Sets the default domain for frontend rules. |
| `traefik.enable=false` | Disables this container in Træfik. | | `traefik.enable=false` | Disables this container in Træfik. |
| `traefik.port=80` | Overrides the default `port` value. Overrides `NetworkBindings` from Docker Container | | `traefik.port=80` | Overrides the default `port` value. Overrides `NetworkBindings` from Docker Container |
@ -162,11 +162,11 @@ Labels can be used on task containers to override default behavior:
| `traefik.frontend.auth.digest.removeHeader=true` | If set to `true`, removes the `Authorization` header. | | `traefik.frontend.auth.digest.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
| `traefik.frontend.auth.digest.users=EXPR` | Sets digest authentication to this frontend in CSV format: `User:Realm:Hash,User:Realm:Hash`. | | `traefik.frontend.auth.digest.users=EXPR` | Sets digest authentication to this frontend in CSV format: `User:Realm:Hash,User:Realm:Hash`. |
| `traefik.frontend.auth.digest.usersfile=/path/.htdigest` | Sets digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. | | `traefik.frontend.auth.digest.usersfile=/path/.htdigest` | Sets digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
| `traefik.frontend.auth.forward.address=https://example.com`| Sets the URL of the authentication server. | | `traefik.frontend.auth.forward.address=https://example.com` | Sets the URL of the authentication server. |
| `traefik.frontend.auth.forward.tls.ca=/path/ca.pem` | Sets the Certificate Authority (CA) for the TLS connection with the authentication server. | | `traefik.frontend.auth.forward.tls.ca=/path/ca.pem` | Sets the Certificate Authority (CA) for the TLS connection with the authentication server. |
| `traefik.frontend.auth.forward.tls.caOptional=true` | Checks the certificates if present but do not force to be signed by a specified Certificate Authority (CA). | | `traefik.frontend.auth.forward.tls.caOptional=true` | Checks the certificates if present but do not force to be signed by a specified Certificate Authority (CA). |
| `traefik.frontend.auth.forward.tls.cert=/path/server.pem` | Sets the Certificate for the TLS connection with the authentication server. | | `traefik.frontend.auth.forward.tls.cert=/path/server.pem` | Sets the Certificate for the TLS connection with the authentication server. |
| `traefik.frontend.auth.forward.tls.insecureSkipVerify=true`| If set to true invalid SSL certificates are accepted. | | `traefik.frontend.auth.forward.tls.insecureSkipVerify=true` | If set to true invalid SSL certificates are accepted. |
| `traefik.frontend.auth.forward.tls.key=/path/server.key` | Sets the Certificate for the TLS connection with the authentication server. | | `traefik.frontend.auth.forward.tls.key=/path/server.key` | Sets the Certificate for the TLS connection with the authentication server. |
| `traefik.frontend.auth.forward.trustForwardHeader=true` | Trusts X-Forwarded-* headers. | | `traefik.frontend.auth.forward.trustForwardHeader=true` | Trusts X-Forwarded-* headers. |
| `traefik.frontend.auth.headerField=X-WebAuth-User` | Sets the header used to pass the authenticated user to the application. | | `traefik.frontend.auth.headerField=X-WebAuth-User` | Sets the header used to pass the authenticated user to the application. |
@ -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.redirect.permanent=true` | Returns 301 instead of 302. |
| `traefik.frontend.rule=EXPR` | Overrides the default frontend rule. Default: `Host:{instance_name}.{domain}`. | | `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.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 ### Custom Headers

View file

@ -85,7 +85,9 @@ Træfik can be configured with a file.
[frontends.frontend1.whiteList] [frontends.frontend1.whiteList]
sourceRange = ["10.42.0.0/16", "152.89.1.33/32", "afed:be44::/16"] 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]
[frontends.frontend1.routes.route0] [frontends.frontend1.routes.route0]

View file

@ -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: The following general annotations are applicable on the Ingress object:
| Annotation | Description | | Annotation | Description |
|---------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------| |---------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.ingress.kubernetes.io/buffering: <YML>` | (3) See [buffering](/configuration/commons/#buffering) section. | | `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/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. | | `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/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/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). | | `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/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) | `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. | `ingress.kubernetes.io/protocol: <NAME>` | Set the protocol Traefik will use to communicate with pods. |
<1> `traefik.ingress.kubernetes.io/error-pages` example: <1> `traefik.ingress.kubernetes.io/error-pages` example:

View file

@ -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. The following labels can be defined on Marathon applications. They adjust the behavior for the entire application.
| Label | Description | | Label | Description |
|------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |-----------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.domain` | Sets the default domain used for the frontend rules. | | `traefik.domain` | Sets the default domain used for the frontend rules. |
| `traefik.enable=false` | Disables this container in Træfik. | | `traefik.enable=false` | Disables this container in Træfik. |
| `traefik.port=80` | Registers this port. Useful when the container exposes multiples ports. | | `traefik.port=80` | Registers this port. Useful when the container exposes multiples ports. |
@ -226,11 +226,11 @@ The following labels can be defined on Marathon applications. They adjust the be
| `traefik.frontend.auth.digest.removeHeader=true` | If set to `true`, removes the `Authorization` header. | | `traefik.frontend.auth.digest.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
| `traefik.frontend.auth.digest.users=EXPR` | Sets digest authentication to this frontend in CSV format: `User:Realm:Hash,User:Realm:Hash`. | | `traefik.frontend.auth.digest.users=EXPR` | Sets digest authentication to this frontend in CSV format: `User:Realm:Hash,User:Realm:Hash`. |
| `traefik.frontend.auth.digest.usersFile=/path/.htdigest` | Sets digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. | | `traefik.frontend.auth.digest.usersFile=/path/.htdigest` | Sets digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
| `traefik.frontend.auth.forward.address=https://example.com`| Sets the URL of the authentication server. | | `traefik.frontend.auth.forward.address=https://example.com` | Sets the URL of the authentication server. |
| `traefik.frontend.auth.forward.tls.ca=/path/ca.pem` | Sets the Certificate Authority (CA) for the TLS connection with the authentication server. | | `traefik.frontend.auth.forward.tls.ca=/path/ca.pem` | Sets the Certificate Authority (CA) for the TLS connection with the authentication server. |
| `traefik.frontend.auth.forward.tls.caOptional=true` | Checks the certificates if present but do not force to be signed by a specified Certificate Authority (CA). | | `traefik.frontend.auth.forward.tls.caOptional=true` | Checks the certificates if present but do not force to be signed by a specified Certificate Authority (CA). |
| `traefik.frontend.auth.forward.tls.cert=/path/server.pem` | Sets the Certificate for the TLS connection with the authentication server. | | `traefik.frontend.auth.forward.tls.cert=/path/server.pem` | Sets the Certificate for the TLS connection with the authentication server. |
| `traefik.frontend.auth.forward.tls.insecureSkipVerify=true`| If set to true invalid SSL certificates are accepted. | | `traefik.frontend.auth.forward.tls.insecureSkipVerify=true` | If set to true invalid SSL certificates are accepted. |
| `traefik.frontend.auth.forward.tls.key=/path/server.key` | Sets the Certificate for the TLS connection with the authentication server. | | `traefik.frontend.auth.forward.tls.key=/path/server.key` | Sets the Certificate for the TLS connection with the authentication server. |
| `traefik.frontend.auth.forward.trustForwardHeader=true` | Trusts X-Forwarded-* headers. | | `traefik.frontend.auth.forward.trustForwardHeader=true` | Trusts X-Forwarded-* headers. |
| `traefik.frontend.auth.headerField=X-WebAuth-User` | Sets the header used to pass the authenticated user to the application. | | `traefik.frontend.auth.headerField=X-WebAuth-User` | Sets the header used to pass the authenticated user to the application. |
@ -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.redirect.permanent=true` | Returns 301 instead of 302. |
| `traefik.frontend.rule=EXPR` | Overrides the default frontend rule. Default: `Host:{sub_domain}.{domain}`. | | `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.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 #### Custom Headers
@ -296,7 +298,7 @@ You can define as many segments as ports exposed in an application.
Segment labels override the default behavior. Segment labels override the default behavior.
| Label | Description | | Label | Description |
|---------------------------------------------------------------------------|----------------------------------------------------------------| |------------------------------------------------------------------------------ |----------------------------------------------------------------|
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` | | `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` | | `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
| `traefik.<segment_name>.portIndex=1` | Same as `traefik.portIndex` | | `traefik.<segment_name>.portIndex=1` | Same as `traefik.portIndex` |
@ -310,11 +312,11 @@ Segment labels override the default behavior.
| `traefik.<segment_name>.frontend.auth.digest.removeHeader=true` | Same as `traefik.frontend.auth.digest.removeHeader` | | `traefik.<segment_name>.frontend.auth.digest.removeHeader=true` | Same as `traefik.frontend.auth.digest.removeHeader` |
| `traefik.<segment_name>.frontend.auth.digest.users=EXPR` | Same as `traefik.frontend.auth.digest.users` | | `traefik.<segment_name>.frontend.auth.digest.users=EXPR` | Same as `traefik.frontend.auth.digest.users` |
| `traefik.<segment_name>.frontend.auth.digest.usersFile=/path/.htdigest` | Same as `traefik.frontend.auth.digest.usersFile` | | `traefik.<segment_name>.frontend.auth.digest.usersFile=/path/.htdigest` | Same as `traefik.frontend.auth.digest.usersFile` |
| `traefik.<segment_name>.frontend.auth.forward.address=https://example.com`| Same as `traefik.frontend.auth.forward.address` | | `traefik.<segment_name>.frontend.auth.forward.address=https://example.com` | Same as `traefik.frontend.auth.forward.address` |
| `traefik.<segment_name>.frontend.auth.forward.tls.ca=/path/ca.pem` | Same as `traefik.frontend.auth.forward.tls.ca` | | `traefik.<segment_name>.frontend.auth.forward.tls.ca=/path/ca.pem` | Same as `traefik.frontend.auth.forward.tls.ca` |
| `traefik.<segment_name>.frontend.auth.forward.tls.caOptional=true` | Same as `traefik.frontend.auth.forward.tls.caOptional` | | `traefik.<segment_name>.frontend.auth.forward.tls.caOptional=true` | Same as `traefik.frontend.auth.forward.tls.caOptional` |
| `traefik.<segment_name>.frontend.auth.forward.tls.cert=/path/server.pem` | Same as `traefik.frontend.auth.forward.tls.cert` | | `traefik.<segment_name>.frontend.auth.forward.tls.cert=/path/server.pem` | Same as `traefik.frontend.auth.forward.tls.cert` |
| `traefik.<segment_name>.frontend.auth.forward.tls.insecureSkipVerify=true`| Same as `traefik.frontend.auth.forward.tls.insecureSkipVerify` | | `traefik.<segment_name>.frontend.auth.forward.tls.insecureSkipVerify=true` | Same as `traefik.frontend.auth.forward.tls.insecureSkipVerify` |
| `traefik.<segment_name>.frontend.auth.forward.tls.key=/path/server.key` | Same as `traefik.frontend.auth.forward.tls.key` | | `traefik.<segment_name>.frontend.auth.forward.tls.key=/path/server.key` | Same as `traefik.frontend.auth.forward.tls.key` |
| `traefik.<segment_name>.frontend.auth.forward.trustForwardHeader=true` | Same as `traefik.frontend.auth.forward.trustForwardHeader` | | `traefik.<segment_name>.frontend.auth.forward.trustForwardHeader=true` | Same as `traefik.frontend.auth.forward.trustForwardHeader` |
| `traefik.<segment_name>.frontend.auth.headerField=X-WebAuth-User` | Same as `traefik.frontend.auth.headerField` | | `traefik.<segment_name>.frontend.auth.headerField=X-WebAuth-User` | Same as `traefik.frontend.auth.headerField` |
@ -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.redirect.permanent=true` | Same as `traefik.frontend.redirect.permanent` |
| `traefik.<segment_name>.frontend.rule=EXP` | Same as `traefik.frontend.rule` | | `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.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 #### Custom Headers

View file

@ -107,7 +107,7 @@ domain = "mesos.localhost"
The following labels can be defined on Mesos tasks. They adjust the behavior for the entire application. The following labels can be defined on Mesos tasks. They adjust the behavior for the entire application.
| Label | Description | | Label | Description |
|------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |-----------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.domain` | Sets the default domain for the frontend rules. | | `traefik.domain` | Sets the default domain for the frontend rules. |
| `traefik.enable=false` | Disables this container in Træfik. | | `traefik.enable=false` | Disables this container in Træfik. |
| `traefik.port=80` | Registers this port. Useful when the application exposes multiple ports. | | `traefik.port=80` | Registers this port. Useful when the application exposes multiple ports. |
@ -140,11 +140,11 @@ The following labels can be defined on Mesos tasks. They adjust the behavior for
| `traefik.frontend.auth.digest.removeHeader=true` | If set to `true`, removes the `Authorization` header. | | `traefik.frontend.auth.digest.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
| `traefik.frontend.auth.digest.users=EXPR` | Sets digest authentication to this frontend in CSV format: `User:Realm:Hash,User:Realm:Hash`. | | `traefik.frontend.auth.digest.users=EXPR` | Sets digest authentication to this frontend in CSV format: `User:Realm:Hash,User:Realm:Hash`. |
| `traefik.frontend.auth.digest.usersFile=/path/.htdigest` | Sets digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. | | `traefik.frontend.auth.digest.usersFile=/path/.htdigest` | Sets digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
| `traefik.frontend.auth.forward.address=https://example.com`| Sets the URL of the authentication server. | | `traefik.frontend.auth.forward.address=https://example.com` | Sets the URL of the authentication server. |
| `traefik.frontend.auth.forward.tls.ca=/path/ca.pem` | Sets the Certificate Authority (CA) for the TLS connection with the authentication server. | | `traefik.frontend.auth.forward.tls.ca=/path/ca.pem` | Sets the Certificate Authority (CA) for the TLS connection with the authentication server. |
| `traefik.frontend.auth.forward.tls.caOptional=true` | Checks the certificates if present but do not force to be signed by a specified Certificate Authority (CA). | | `traefik.frontend.auth.forward.tls.caOptional=true` | Checks the certificates if present but do not force to be signed by a specified Certificate Authority (CA). |
| `traefik.frontend.auth.forward.tls.cert=/path/server.pem` | Sets the Certificate for the TLS connection with the authentication server. | | `traefik.frontend.auth.forward.tls.cert=/path/server.pem` | Sets the Certificate for the TLS connection with the authentication server. |
| `traefik.frontend.auth.forward.tls.insecureSkipVerify=true`| If set to true invalid SSL certificates are accepted. | | `traefik.frontend.auth.forward.tls.insecureSkipVerify=true` | If set to true invalid SSL certificates are accepted. |
| `traefik.frontend.auth.forward.tls.key=/path/server.key` | Sets the Certificate for the TLS connection with the authentication server. | | `traefik.frontend.auth.forward.tls.key=/path/server.key` | Sets the Certificate for the TLS connection with the authentication server. |
| `traefik.frontend.auth.forward.trustForwardHeader=true` | Trusts X-Forwarded-* headers. | | `traefik.frontend.auth.forward.trustForwardHeader=true` | Trusts X-Forwarded-* headers. |
| `traefik.frontend.auth.headerField=X-WebAuth-User` | Sets the header used to pass the authenticated user to the application. | | `traefik.frontend.auth.headerField=X-WebAuth-User` | Sets the header used to pass the authenticated user to the application. |
@ -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.redirect.permanent=true` | Returns 301 instead of 302. |
| `traefik.frontend.rule=EXPR` | Overrides the default frontend rule. Default: `Host:{discovery_name}.{domain}`. | | `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.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 ### 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. Segment labels override the default behavior.
| Label | Description | | Label | Description |
|---------------------------------------------------------------------------|----------------------------------------------------------------| |------------------------------------------------------------------------------|----------------------------------------------------------------|
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` | | `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` | | `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
| `traefik.<segment_name>.portIndex=1` | Same as `traefik.portIndex` | | `traefik.<segment_name>.portIndex=1` | Same as `traefik.portIndex` |
@ -226,11 +228,11 @@ Segment labels override the default behavior.
| `traefik.<segment_name>.frontend.auth.digest.removeHeader=true` | Same as `traefik.frontend.auth.digest.removeHeader` | | `traefik.<segment_name>.frontend.auth.digest.removeHeader=true` | Same as `traefik.frontend.auth.digest.removeHeader` |
| `traefik.<segment_name>.frontend.auth.digest.users=EXPR` | Same as `traefik.frontend.auth.digest.users` | | `traefik.<segment_name>.frontend.auth.digest.users=EXPR` | Same as `traefik.frontend.auth.digest.users` |
| `traefik.<segment_name>.frontend.auth.digest.usersFile=/path/.htdigest` | Same as `traefik.frontend.auth.digest.usersFile` | | `traefik.<segment_name>.frontend.auth.digest.usersFile=/path/.htdigest` | Same as `traefik.frontend.auth.digest.usersFile` |
| `traefik.<segment_name>.frontend.auth.forward.address=https://example.com`| Same as `traefik.frontend.auth.forward.address` | | `traefik.<segment_name>.frontend.auth.forward.address=https://example.com` | Same as `traefik.frontend.auth.forward.address` |
| `traefik.<segment_name>.frontend.auth.forward.tls.ca=/path/ca.pem` | Same as `traefik.frontend.auth.forward.tls.ca` | | `traefik.<segment_name>.frontend.auth.forward.tls.ca=/path/ca.pem` | Same as `traefik.frontend.auth.forward.tls.ca` |
| `traefik.<segment_name>.frontend.auth.forward.tls.caOptional=true` | Same as `traefik.frontend.auth.forward.tls.caOptional` | | `traefik.<segment_name>.frontend.auth.forward.tls.caOptional=true` | Same as `traefik.frontend.auth.forward.tls.caOptional` |
| `traefik.<segment_name>.frontend.auth.forward.tls.cert=/path/server.pem` | Same as `traefik.frontend.auth.forward.tls.cert` | | `traefik.<segment_name>.frontend.auth.forward.tls.cert=/path/server.pem` | Same as `traefik.frontend.auth.forward.tls.cert` |
| `traefik.<segment_name>.frontend.auth.forward.tls.insecureSkipVerify=true`| Same as `traefik.frontend.auth.forward.tls.insecureSkipVerify` | | `traefik.<segment_name>.frontend.auth.forward.tls.insecureSkipVerify=true` | Same as `traefik.frontend.auth.forward.tls.insecureSkipVerify` |
| `traefik.<segment_name>.frontend.auth.forward.tls.key=/path/server.key` | Same as `traefik.frontend.auth.forward.tls.key` | | `traefik.<segment_name>.frontend.auth.forward.tls.key=/path/server.key` | Same as `traefik.frontend.auth.forward.tls.key` |
| `traefik.<segment_name>.frontend.auth.forward.trustForwardHeader=true` | Same as `traefik.frontend.auth.forward.trustForwardHeader` | | `traefik.<segment_name>.frontend.auth.forward.trustForwardHeader=true` | Same as `traefik.frontend.auth.forward.trustForwardHeader` |
| `traefik.<segment_name>.frontend.auth.headerField=X-WebAuth-User` | Same as `traefik.frontend.auth.headerField` | | `traefik.<segment_name>.frontend.auth.headerField=X-WebAuth-User` | Same as `traefik.frontend.auth.headerField` |
@ -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.redirect.permanent=true` | Same as `traefik.frontend.redirect.permanent` |
| `traefik.<segment_name>.frontend.rule=EXP` | Same as `traefik.frontend.rule` | | `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.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 #### Custom Headers

View file

@ -139,7 +139,7 @@ secretKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Labels can be used on task containers to override default behavior: Labels can be used on task containers to override default behavior:
| Label | Description | | Label | Description |
|------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |-----------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.domain` | Sets the default domain for the frontend rules. | | `traefik.domain` | Sets the default domain for the frontend rules. |
| `traefik.enable=false` | Disables this container in Træfik. | | `traefik.enable=false` | Disables this container in Træfik. |
| `traefik.port=80` | Registers this port. Useful when the container exposes multiple ports. | | `traefik.port=80` | Registers this port. Useful when the container exposes multiple ports. |
@ -170,11 +170,11 @@ Labels can be used on task containers to override default behavior:
| `traefik.frontend.auth.digest.removeHeader=true` | If set to `true`, removes the `Authorization` header. | | `traefik.frontend.auth.digest.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
| `traefik.frontend.auth.digest.users=EXPR` | Sets the digest authentication to this frontend in CSV format: `User:Realm:Hash,User:Realm:Hash`. | | `traefik.frontend.auth.digest.users=EXPR` | Sets the digest authentication to this frontend in CSV format: `User:Realm:Hash,User:Realm:Hash`. |
| `traefik.frontend.auth.digest.usersfile=/path/.htdigest` | Sets the digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. | | `traefik.frontend.auth.digest.usersfile=/path/.htdigest` | Sets the digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
| `traefik.frontend.auth.forward.address=https://example.com`| Sets the URL of the authentication server. | | `traefik.frontend.auth.forward.address=https://example.com` | Sets the URL of the authentication server. |
| `traefik.frontend.auth.forward.tls.ca=/path/ca.pem` | Sets the Certificate Authority (CA) for the TLS connection with the authentication server. | | `traefik.frontend.auth.forward.tls.ca=/path/ca.pem` | Sets the Certificate Authority (CA) for the TLS connection with the authentication server. |
| `traefik.frontend.auth.forward.tls.caOptional=true` | Checks the certificates if present but do not force to be signed by a specified Certificate Authority (CA). | | `traefik.frontend.auth.forward.tls.caOptional=true` | Checks the certificates if present but do not force to be signed by a specified Certificate Authority (CA). |
| `traefik.frontend.auth.forward.tls.cert=/path/server.pem` | Sets the Certificate for the TLS connection with the authentication server. | | `traefik.frontend.auth.forward.tls.cert=/path/server.pem` | Sets the Certificate for the TLS connection with the authentication server. |
| `traefik.frontend.auth.forward.tls.insecureSkipVerify=true`| If set to true invalid SSL certificates are accepted. | | `traefik.frontend.auth.forward.tls.insecureSkipVerify=true` | If set to true invalid SSL certificates are accepted. |
| `traefik.frontend.auth.forward.tls.key=/path/server.key` | Sets the Certificate for the TLS connection with the authentication server. | | `traefik.frontend.auth.forward.tls.key=/path/server.key` | Sets the Certificate for the TLS connection with the authentication server. |
| `traefik.frontend.auth.forward.trustForwardHeader=true` | Trusts X-Forwarded-* headers. | | `traefik.frontend.auth.forward.trustForwardHeader=true` | Trusts X-Forwarded-* headers. |
| `traefik.frontend.auth.headerField=X-WebAuth-User` | Sets the header used to pass the authenticated user to the application. | | `traefik.frontend.auth.headerField=X-WebAuth-User` | Sets the header used to pass the authenticated user to the application. |
@ -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.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.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.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 #### Custom Headers
@ -238,7 +240,7 @@ You can define as many segments as ports exposed in a container.
Segment labels override the default behavior. Segment labels override the default behavior.
| Label | Description | | Label | Description |
|---------------------------------------------------------------------------|---------------------------------------------------------------| |------------------------------------------------------------------------------|----------------------------------------------------------------|
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` | | `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` | | `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` | | `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
@ -251,11 +253,11 @@ Segment labels override the default behavior.
| `traefik.<segment_name>.frontend.auth.digest.removeHeader=true` | Same as `traefik.frontend.auth.digest.removeHeader` | | `traefik.<segment_name>.frontend.auth.digest.removeHeader=true` | Same as `traefik.frontend.auth.digest.removeHeader` |
| `traefik.<segment_name>.frontend.auth.digest.users=EXPR` | Same as `traefik.frontend.auth.digest.users` | | `traefik.<segment_name>.frontend.auth.digest.users=EXPR` | Same as `traefik.frontend.auth.digest.users` |
| `traefik.<segment_name>.frontend.auth.digest.usersfile=/path/.htdigest` | Same as `traefik.frontend.auth.digest.usersfile` | | `traefik.<segment_name>.frontend.auth.digest.usersfile=/path/.htdigest` | Same as `traefik.frontend.auth.digest.usersfile` |
| `traefik.<segment_name>.frontend.auth.forward.address=https://example.com`| Same as `traefik.frontend.auth.forward.address` | | `traefik.<segment_name>.frontend.auth.forward.address=https://example.com` | Same as `traefik.frontend.auth.forward.address` |
| `traefik.<segment_name>.frontend.auth.forward.tls.ca=/path/ca.pem` | Same as `traefik.frontend.auth.forward.tls.ca` | | `traefik.<segment_name>.frontend.auth.forward.tls.ca=/path/ca.pem` | Same as `traefik.frontend.auth.forward.tls.ca` |
| `traefik.<segment_name>.frontend.auth.forward.tls.caOptional=true` | Same as `traefik.frontend.auth.forward.tls.caOptional` | | `traefik.<segment_name>.frontend.auth.forward.tls.caOptional=true` | Same as `traefik.frontend.auth.forward.tls.caOptional` |
| `traefik.<segment_name>.frontend.auth.forward.tls.cert=/path/server.pem` | Same as `traefik.frontend.auth.forward.tls.cert` | | `traefik.<segment_name>.frontend.auth.forward.tls.cert=/path/server.pem` | Same as `traefik.frontend.auth.forward.tls.cert` |
| `traefik.<segment_name>.frontend.auth.forward.tls.insecureSkipVerify=true`| Same as `traefik.frontend.auth.forward.tls.insecureSkipVerify`| | `traefik.<segment_name>.frontend.auth.forward.tls.insecureSkipVerify=true` | Same as `traefik.frontend.auth.forward.tls.insecureSkipVerify` |
| `traefik.<segment_name>.frontend.auth.forward.tls.key=/path/server.key` | Same as `traefik.frontend.auth.forward.tls.key` | | `traefik.<segment_name>.frontend.auth.forward.tls.key=/path/server.key` | Same as `traefik.frontend.auth.forward.tls.key` |
| `traefik.<segment_name>.frontend.auth.forward.trustForwardHeader=true` | Same as `traefik.frontend.auth.forward.trustForwardHeader` | | `traefik.<segment_name>.frontend.auth.forward.trustForwardHeader=true` | Same as `traefik.frontend.auth.forward.trustForwardHeader` |
| `traefik.<segment_name>.frontend.auth.headerField=X-WebAuth-User` | Same as `traefik.frontend.auth.headerField` | | `traefik.<segment_name>.frontend.auth.headerField=X-WebAuth-User` | Same as `traefik.frontend.auth.headerField` |
@ -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.redirect.permanent=true` | Same as `traefik.frontend.redirect.permanent` |
| `traefik.<segment_name>.frontend.rule=EXP` | Same as `traefik.frontend.rule` | | `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.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 #### Custom Headers

View file

@ -95,7 +95,7 @@ curl -X PUT \
Labels, set through extensions or the property manager, can be used on services to override default behavior. Labels, set through extensions or the property manager, can be used on services to override default behavior.
| Label | Description | | Label | Description |
|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |-----------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.enable=false` | Disable this container in Træfik | | `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.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 | | `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.redirect.permanent=true` | Return 301 instead of 302. |
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Defaults to SF address. | | `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.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 ### Custom Headers

View file

@ -15,9 +15,15 @@ defaultEntryPoints = ["http", "https"]
address = ":80" address = ":80"
[entryPoints.http.compress] [entryPoints.http.compress]
[entryPoints.http.clientIPStrategy]
depth = 5
excludedIPs = ["127.0.0.1/32", "192.168.1.7"]
[entryPoints.http.whitelist] [entryPoints.http.whitelist]
sourceRange = ["10.42.0.0/16", "152.89.1.33/32", "afed:be44::/16"] 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] [entryPoints.http.tls]
minVersion = "VersionTLS12" minVersion = "VersionTLS12"
@ -75,6 +81,7 @@ defaultEntryPoints = ["http", "https"]
[entryPoints.http.forwardedHeaders] [entryPoints.http.forwardedHeaders]
trustedIPs = ["10.10.10.1", "10.10.10.2"] trustedIPs = ["10.10.10.1", "10.10.10.2"]
insecure = false
[entryPoints.https] [entryPoints.https]
# ... # ...
@ -129,7 +136,8 @@ Redirect.Replacement:http://mydomain/$1
Redirect.Permanent:true Redirect.Permanent:true
Compress:true Compress:true
WhiteList.SourceRange: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
ProxyProtocol.TrustedIPs:192.168.0.1 ProxyProtocol.TrustedIPs:192.168.0.1
ProxyProtocol.Insecure:true ProxyProtocol.Insecure:true
ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24
@ -464,7 +472,9 @@ Responses are compressed when:
## White Listing ## 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 ```toml
[entryPoints] [entryPoints]
@ -473,9 +483,97 @@ To enable IP white listing at the entry point level.
[entryPoints.http.whiteList] [entryPoints.http.whiteList]
sourceRange = ["127.0.0.1/32", "192.168.1.7"] 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 ## ProxyProtocol
To enable [ProxyProtocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) support. To enable [ProxyProtocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) support.
@ -524,4 +622,12 @@ Only IPs in `trustedIPs` will be authorized to trust the client forwarded header
# Default: [] # Default: []
# #
trustedIPs = ["127.0.0.1/32", "192.168.1.7"] trustedIPs = ["127.0.0.1/32", "192.168.1.7"]
# Insecure mode
#
# Optional
# Default: false
#
# insecure = true
``` ```

View file

@ -3,6 +3,7 @@ package integration
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest"
"os" "os"
"strings" "strings"
"syscall" "syscall"
@ -253,7 +254,6 @@ func (s *SimpleSuite) TestNoAuthOnPing(c *check.C) {
} }
func (s *SimpleSuite) TestDefaultEntrypointHTTP(c *check.C) { func (s *SimpleSuite) TestDefaultEntrypointHTTP(c *check.C) {
s.createComposeProject(c, "base") s.createComposeProject(c, "base")
s.composeProject.Start(c) s.composeProject.Start(c)
@ -272,7 +272,6 @@ func (s *SimpleSuite) TestDefaultEntrypointHTTP(c *check.C) {
} }
func (s *SimpleSuite) TestWithUnexistingEntrypoint(c *check.C) { func (s *SimpleSuite) TestWithUnexistingEntrypoint(c *check.C) {
s.createComposeProject(c, "base") s.createComposeProject(c, "base")
s.composeProject.Start(c) s.composeProject.Start(c)
@ -291,7 +290,6 @@ func (s *SimpleSuite) TestWithUnexistingEntrypoint(c *check.C) {
} }
func (s *SimpleSuite) TestMetricsPrometheusDefaultEntrypoint(c *check.C) { func (s *SimpleSuite) TestMetricsPrometheusDefaultEntrypoint(c *check.C) {
s.createComposeProject(c, "base") s.createComposeProject(c, "base")
s.composeProject.Start(c) s.composeProject.Start(c)
@ -313,15 +311,16 @@ func (s *SimpleSuite) TestMetricsPrometheusDefaultEntrypoint(c *check.C) {
} }
func (s *SimpleSuite) TestMultipleProviderSameBackendName(c *check.C) { func (s *SimpleSuite) TestMultipleProviderSameBackendName(c *check.C) {
s.createComposeProject(c, "base") s.createComposeProject(c, "base")
s.composeProject.Start(c) s.composeProject.Start(c)
ipWhoami01 := s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress ipWhoami01 := s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress
ipWhoami02 := s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress ipWhoami02 := s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress
file := s.adaptFile(c, "fixtures/multiple_provider.toml", struct{ IP string }{ file := s.adaptFile(c, "fixtures/multiple_provider.toml", struct{ IP string }{
IP: ipWhoami02, IP: ipWhoami02,
}) })
defer os.Remove(file) defer os.Remove(file)
cmd, output := s.traefikCmd(withConfigFile(file)) cmd, output := s.traefikCmd(withConfigFile(file))
defer output(c) defer output(c)
@ -339,3 +338,80 @@ func (s *SimpleSuite) TestMultipleProviderSameBackendName(c *check.C) {
c.Assert(err, checker.IsNil) 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)
}
}
}

View file

@ -17,7 +17,8 @@ checkNewVersion = false
entryPoint = "http" entryPoint = "http"
[entryPoints.httpWhitelistReject] [entryPoints.httpWhitelistReject]
address = ":8002" address = ":8002"
whiteListSourceRange = ["8.8.8.8/32"] [entryPoints.httpWhitelistReject.whiteList]
sourceRange = ["8.8.8.8/32"]
[entryPoints.httpAuth] [entryPoints.httpAuth]
address = ":8004" address = ":8004"
[entryPoints.httpAuth.auth.basic] [entryPoints.httpAuth.auth.basic]

View 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]

View file

@ -102,6 +102,6 @@ frontendWhitelist:
- traefik.enable=true - traefik.enable=true
- traefik.port=80 - traefik.port=80
- traefik.backend=backend3 - 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.entryPoints=http
- traefik.frontend.rule=Host:frontend.whitelist.docker.local - traefik.frontend.rule=Host:frontend.whitelist.docker.local

View 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
View 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
View 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
View 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
View 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)
})
}
}

View 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)
}
}

View 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))
}
})
}
}

View file

@ -4,9 +4,9 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/containous/traefik/ip"
"github.com/containous/traefik/log" "github.com/containous/traefik/log"
"github.com/containous/traefik/middlewares/tracing" "github.com/containous/traefik/middlewares/tracing"
"github.com/containous/traefik/whitelist"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/urfave/negroni" "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 // IPWhiteLister is a middleware that provides Checks of the Requesting IP against a set of Whitelists
type IPWhiteLister struct { type IPWhiteLister struct {
handler negroni.Handler handler negroni.Handler
whiteLister *whitelist.IP whiteLister *ip.Checker
strategy ip.Strategy
} }
// NewIPWhiteLister builds a new IPWhiteLister given a list of CIDR-Strings to whitelist // NewIPWhiteLister builds a new IPWhiteLister given a list of CIDR-Strings to whitelist
func NewIPWhiteLister(whiteList []string, useXForwardedFor bool) (*IPWhiteLister, error) { func NewIPWhiteLister(whiteList []string, strategy ip.Strategy) (*IPWhiteLister, error) {
if len(whiteList) == 0 { if len(whiteList) == 0 {
return nil, errors.New("no white list provided") return nil, errors.New("no white list provided")
} }
whiteLister := IPWhiteLister{} checker, err := ip.NewChecker(whiteList)
ip, err := whitelist.NewIP(whiteList, false, useXForwardedFor)
if err != nil { if err != nil {
return nil, fmt.Errorf("parsing CIDR whitelist %s: %v", whiteList, err) 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) whiteLister.handler = negroni.HandlerFunc(whiteLister.handle)
log.Debugf("configured IP white list: %s", whiteList) 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) { 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 { if err != nil {
tracing.SetErrorAndDebugLog(r, "request %+v - rejecting: %v", r, err) tracing.SetErrorAndDebugLog(r, "request %+v - rejecting: %v", r, err)
reject(w) reject(w)
return return
} }
log.Debugf("Accept %s: %+v", wl.strategy.GetIP(r), r)
tracing.SetErrorAndDebugLog(r, "request %+v matched white list %s - passing", r, wl.whiteLister) tracing.SetErrorAndDebugLog(r, "request %+v matched white list %s - passing", r, wl.whiteLister)
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
} }

View file

@ -5,7 +5,7 @@ import (
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"github.com/containous/traefik/whitelist" "github.com/containous/traefik/ip"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -14,19 +14,16 @@ func TestNewIPWhiteLister(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
whiteList []string whiteList []string
useXForwardedFor bool
expectedError string expectedError string
}{ }{
{ {
desc: "invalid IP", desc: "invalid IP",
whiteList: []string{"foo"}, whiteList: []string{"foo"},
useXForwardedFor: false, expectedError: "parsing CIDR whitelist [foo]: parsing CIDR trusted IPs <nil>: invalid CIDR address: foo",
expectedError: "parsing CIDR whitelist [foo]: parsing CIDR white list <nil>: invalid CIDR address: foo",
}, },
{ {
desc: "valid IP", desc: "valid IP",
whiteList: []string{"10.10.10.10"}, whiteList: []string{"10.10.10.10"},
useXForwardedFor: false,
expectedError: "", expectedError: "",
}, },
} }
@ -36,7 +33,7 @@ func TestNewIPWhiteLister(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
whiteLister, err := NewIPWhiteLister(test.whiteList, test.useXForwardedFor) whiteLister, err := NewIPWhiteLister(test.whiteList, &ip.RemoteAddrStrategy{})
if len(test.expectedError) > 0 { if len(test.expectedError) > 0 {
assert.EqualError(t, err, test.expectedError) assert.EqualError(t, err, test.expectedError)
@ -52,54 +49,19 @@ func TestIPWhiteLister_ServeHTTP(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
whiteList []string whiteList []string
useXForwardedFor bool
remoteAddr string remoteAddr string
xForwardedFor []string
expected int expected int
}{ }{
{ {
desc: "authorized with remote address", desc: "authorized with remote address",
whiteList: []string{"20.20.20.20"}, whiteList: []string{"20.20.20.20"},
useXForwardedFor: false,
remoteAddr: "20.20.20.20:1234", remoteAddr: "20.20.20.20:1234",
xForwardedFor: nil,
expected: 200, expected: 200,
}, },
{ {
desc: "non authorized with remote address", desc: "non authorized with remote address",
whiteList: []string{"20.20.20.20"}, whiteList: []string{"20.20.20.20"},
useXForwardedFor: false,
remoteAddr: "20.20.20.21:1234", 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, expected: 403,
}, },
} }
@ -109,7 +71,7 @@ func TestIPWhiteLister_ServeHTTP(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
whiteLister, err := NewIPWhiteLister(test.whiteList, test.useXForwardedFor) whiteLister, err := NewIPWhiteLister(test.whiteList, &ip.RemoteAddrStrategy{})
require.NoError(t, err) require.NoError(t, err)
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
@ -120,12 +82,6 @@ func TestIPWhiteLister_ServeHTTP(t *testing.T) {
req.RemoteAddr = test.remoteAddr 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) {}) next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
whiteLister.ServeHTTP(recorder, req, next) whiteLister.ServeHTTP(recorder, req, next)

View file

@ -375,7 +375,8 @@ func TestProviderBuildConfiguration(t *testing.T) {
label.TraefikFrontendRedirectPermanent + "=true", label.TraefikFrontendRedirectPermanent + "=true",
label.TraefikFrontendRule + "=Host:traefik.io", label.TraefikFrontendRule + "=Host:traefik.io",
label.TraefikFrontendWhiteListSourceRange + "=10.10.10.10", 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.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", 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{ SourceRange: []string{
"10.10.10.10", "10.10.10.10",
}, },
UseXForwardedFor: true, IPStrategy: &types.IPStrategy{
Depth: 5,
ExcludedIPs: []string{"10.10.10.10", "10.10.10.11"},
},
}, },
Headers: &types.Headers{ Headers: &types.Headers{
CustomRequestHeaders: map[string]string{ CustomRequestHeaders: map[string]string{

View file

@ -413,7 +413,8 @@ func TestDockerBuildConfiguration(t *testing.T) {
label.TraefikFrontendRedirectPermanent: "true", label.TraefikFrontendRedirectPermanent: "true",
label.TraefikFrontendRule: "Host:traefik.io", label.TraefikFrontendRule: "Host:traefik.io",
label.TraefikFrontendWhiteListSourceRange: "10.10.10.10", 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.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", 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{ WhiteList: &types.WhiteList{
SourceRange: []string{"10.10.10.10"}, 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{ Headers: &types.Headers{
CustomRequestHeaders: map[string]string{ CustomRequestHeaders: map[string]string{

View file

@ -357,7 +357,8 @@ func TestSwarmBuildConfiguration(t *testing.T) {
label.TraefikFrontendRedirectReplacement: "nope", label.TraefikFrontendRedirectReplacement: "nope",
label.TraefikFrontendRule: "Host:traefik.io", label.TraefikFrontendRule: "Host:traefik.io",
label.TraefikFrontendWhiteListSourceRange: "10.10.10.10", 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.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", 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{ WhiteList: &types.WhiteList{
SourceRange: []string{"10.10.10.10"}, 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{ Headers: &types.Headers{
CustomRequestHeaders: map[string]string{ CustomRequestHeaders: map[string]string{

View file

@ -311,7 +311,8 @@ func TestSegmentBuildConfiguration(t *testing.T) {
label.Prefix + "sauternes." + label.SuffixFrontendRedirectReplacement: "nope", label.Prefix + "sauternes." + label.SuffixFrontendRedirectReplacement: "nope",
label.Prefix + "sauternes." + label.SuffixFrontendRedirectPermanent: "true", label.Prefix + "sauternes." + label.SuffixFrontendRedirectPermanent: "true",
label.Prefix + "sauternes." + label.SuffixFrontendWhiteListSourceRange: "10.10.10.10", 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.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", 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{ WhiteList: &types.WhiteList{
SourceRange: []string{"10.10.10.10"}, 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{ Headers: &types.Headers{
CustomRequestHeaders: map[string]string{ CustomRequestHeaders: map[string]string{

View file

@ -378,7 +378,8 @@ func TestBuildConfiguration(t *testing.T) {
label.TraefikFrontendRedirectPermanent: aws.String("true"), label.TraefikFrontendRedirectPermanent: aws.String("true"),
label.TraefikFrontendRule: aws.String("Host:traefik.io"), label.TraefikFrontendRule: aws.String("Host:traefik.io"),
label.TraefikFrontendWhiteListSourceRange: aws.String("10.10.10.10"), 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.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"), 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{ WhiteList: &types.WhiteList{
SourceRange: []string{"10.10.10.10"}, 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{ Headers: &types.Headers{
CustomRequestHeaders: map[string]string{ CustomRequestHeaders: map[string]string{
@ -621,7 +625,6 @@ func TestBuildConfiguration(t *testing.T) {
label.TraefikFrontendRedirectPermanent: aws.String("true"), label.TraefikFrontendRedirectPermanent: aws.String("true"),
label.TraefikFrontendRule: aws.String("Host:traefik.io"), label.TraefikFrontendRule: aws.String("Host:traefik.io"),
label.TraefikFrontendWhiteListSourceRange: aws.String("10.10.10.10"), 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.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"), 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.TraefikFrontendRedirectPermanent: aws.String("true"),
label.TraefikFrontendRule: aws.String("Host:traefik.io"), label.TraefikFrontendRule: aws.String("Host:traefik.io"),
label.TraefikFrontendWhiteListSourceRange: aws.String("10.10.10.10"), 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.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"), 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{ WhiteList: &types.WhiteList{
SourceRange: []string{"10.10.10.10"}, SourceRange: []string{"10.10.10.10"},
UseXForwardedFor: true,
}, },
Headers: &types.Headers{ Headers: &types.Headers{
CustomRequestHeaders: map[string]string{ CustomRequestHeaders: map[string]string{

View file

@ -18,7 +18,9 @@ const (
annotationKubernetesAuthForwardTLSInsecure = "ingress.kubernetes.io/auth-tls-insecure" annotationKubernetesAuthForwardTLSInsecure = "ingress.kubernetes.io/auth-tls-insecure"
annotationKubernetesRewriteTarget = "ingress.kubernetes.io/rewrite-target" annotationKubernetesRewriteTarget = "ingress.kubernetes.io/rewrite-target"
annotationKubernetesWhiteListSourceRange = "ingress.kubernetes.io/whitelist-source-range" 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" annotationKubernetesPreserveHost = "ingress.kubernetes.io/preserve-host"
annotationKubernetesPassTLSCert = "ingress.kubernetes.io/pass-tls-cert" annotationKubernetesPassTLSCert = "ingress.kubernetes.io/pass-tls-cert"
annotationKubernetesFrontendEntryPoints = "ingress.kubernetes.io/frontend-entry-points" annotationKubernetesFrontendEntryPoints = "ingress.kubernetes.io/frontend-entry-points"

View file

@ -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) { return func(f *types.Frontend) {
if f.WhiteList == nil { if f.WhiteList == nil {
f.WhiteList = &types.WhiteList{} f.WhiteList = &types.WhiteList{}
} }
f.WhiteList.UseXForwardedFor = useXFF for _, opt := range opts {
f.WhiteList.SourceRange = ranges opt(f.WhiteList)
}
} }
} }

View file

@ -903,7 +903,22 @@ func getWhiteList(i *extensionsv1beta1.Ingress) *types.WhiteList {
return &types.WhiteList{ return &types.WhiteList{
SourceRange: ranges, 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,
} }
} }

View file

@ -1080,13 +1080,24 @@ func TestIngressAnnotations(t *testing.T) {
buildIngress( buildIngress(
iNamespace("testing"), iNamespace("testing"),
iAnnotation(annotationKubernetesWhiteListSourceRange, "1.1.1.1/24, 1234:abcd::42/32"), 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( iRules(
iRule( iRule(
iHost("test"), iHost("test"),
iPaths(onePath(iPath("/whitelist-source-range"), iBackend("service1", intstr.FromInt(80))))), 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( buildIngress(
iNamespace("testing"), iNamespace("testing"),
iAnnotation(annotationKubernetesRewriteTarget, "/"), iAnnotation(annotationKubernetesRewriteTarget, "/"),
@ -1357,6 +1368,12 @@ rateset:
server("http://example.com", weight(1))), server("http://example.com", weight(1))),
lbMethod("wrr"), 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", backend("rewrite/api",
servers( servers(
server("http://example.com", weight(1)), server("http://example.com", weight(1)),
@ -1467,11 +1484,22 @@ rateset:
), ),
frontend("test/whitelist-source-range", frontend("test/whitelist-source-range",
passHostHeader(), 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( routes(
route("/whitelist-source-range", "PathPrefix:/whitelist-source-range"), route("/whitelist-source-range", "PathPrefix:/whitelist-source-range"),
route("test", "Host:test")), 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", frontend("rewrite/api",
passHostHeader(), passHostHeader(),
routes( routes(

View file

@ -30,7 +30,9 @@ const (
pathFrontendPassHostHeader = "/passhostheader" pathFrontendPassHostHeader = "/passhostheader"
pathFrontendPassTLSCert = "/passtlscert" pathFrontendPassTLSCert = "/passtlscert"
pathFrontendWhiteListSourceRange = "/whitelist/sourcerange" pathFrontendWhiteListSourceRange = "/whitelist/sourcerange"
pathFrontendWhiteListUseXForwardedFor = "/whitelist/usexforwardedfor" pathFrontendWhiteListIPStrategy = "/whitelist/ipstrategy"
pathFrontendWhiteListIPStrategyDepth = pathFrontendWhiteListIPStrategy + "/depth"
pathFrontendWhiteListIPStrategyExcludedIPs = pathFrontendWhiteListIPStrategy + "/excludedips"
pathFrontendAuth = "/auth/" pathFrontendAuth = "/auth/"
pathFrontendAuthBasic = pathFrontendAuth + "basic/" pathFrontendAuthBasic = pathFrontendAuth + "basic/"

View file

@ -80,14 +80,29 @@ func (p *Provider) buildConfiguration() *types.Configuration {
func (p *Provider) getWhiteList(rootPath string) *types.WhiteList { func (p *Provider) getWhiteList(rootPath string) *types.WhiteList {
ranges := p.getList(rootPath, pathFrontendWhiteListSourceRange) ranges := p.getList(rootPath, pathFrontendWhiteListSourceRange)
if len(ranges) > 0 { if len(ranges) == 0 {
return &types.WhiteList{ return nil
SourceRange: ranges,
UseXForwardedFor: p.getBool(false, rootPath, pathFrontendWhiteListUseXForwardedFor),
}
} }
return &types.WhiteList{
SourceRange: ranges,
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 nil
}
return &types.IPStrategy{
Depth: depth,
ExcludedIPs: excludedIPs,
}
} }
func (p *Provider) getRedirect(rootPath string) *types.Redirect { func (p *Provider) getRedirect(rootPath string) *types.Redirect {

View file

@ -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", desc: "all parameters",
kvPairs: filler("traefik", kvPairs: filler("traefik",
@ -247,7 +280,8 @@ func TestProviderBuildConfiguration(t *testing.T) {
withPair(pathFrontendPassTLSCert, "true"), withPair(pathFrontendPassTLSCert, "true"),
withList(pathFrontendEntryPoints, "http", "https"), withList(pathFrontendEntryPoints, "http", "https"),
withList(pathFrontendWhiteListSourceRange, "1.1.1.1/24", "1234:abcd::42/32"), 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"), withPair(pathFrontendAuthBasicRemoveHeader, "true"),
withList(pathFrontendAuthBasicUsers, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"), withList(pathFrontendAuthBasicUsers, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"),
@ -364,7 +398,10 @@ func TestProviderBuildConfiguration(t *testing.T) {
PassTLSCert: true, PassTLSCert: true,
WhiteList: &types.WhiteList{ WhiteList: &types.WhiteList{
SourceRange: []string{"1.1.1.1/24", "1234:abcd::42/32"}, 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{ Auth: &types.Auth{
HeaderField: "X-WebAuth-User", HeaderField: "X-WebAuth-User",
@ -1240,31 +1277,8 @@ func TestWhiteList(t *testing.T) {
SourceRange: []string{ SourceRange: []string{
"10.10.10.10", "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 { for _, test := range testCases {

View file

@ -86,10 +86,11 @@ const (
SuffixFrontendRedirectReplacement = "frontend.redirect.replacement" SuffixFrontendRedirectReplacement = "frontend.redirect.replacement"
SuffixFrontendRedirectPermanent = "frontend.redirect.permanent" SuffixFrontendRedirectPermanent = "frontend.redirect.permanent"
SuffixFrontendRule = "frontend.rule" SuffixFrontendRule = "frontend.rule"
SuffixFrontendWhitelistSourceRange = "frontend.whitelistSourceRange" // Deprecated
SuffixFrontendWhiteList = "frontend.whiteList." SuffixFrontendWhiteList = "frontend.whiteList."
SuffixFrontendWhiteListSourceRange = SuffixFrontendWhiteList + "sourceRange" SuffixFrontendWhiteListSourceRange = SuffixFrontendWhiteList + "sourceRange"
SuffixFrontendWhiteListUseXForwardedFor = SuffixFrontendWhiteList + "useXForwardedFor" SuffixFrontendWhiteListIPStrategy = SuffixFrontendWhiteList + "ipStrategy"
SuffixFrontendWhiteListIPStrategyDepth = SuffixFrontendWhiteListIPStrategy + ".depth"
SuffixFrontendWhiteListIPStrategyExcludedIPS = SuffixFrontendWhiteListIPStrategy + ".excludedIPs"
TraefikDomain = Prefix + SuffixDomain TraefikDomain = Prefix + SuffixDomain
TraefikEnable = Prefix + SuffixEnable TraefikEnable = Prefix + SuffixEnable
TraefikPort = Prefix + SuffixPort TraefikPort = Prefix + SuffixPort
@ -150,9 +151,10 @@ const (
TraefikFrontendRedirectReplacement = Prefix + SuffixFrontendRedirectReplacement TraefikFrontendRedirectReplacement = Prefix + SuffixFrontendRedirectReplacement
TraefikFrontendRedirectPermanent = Prefix + SuffixFrontendRedirectPermanent TraefikFrontendRedirectPermanent = Prefix + SuffixFrontendRedirectPermanent
TraefikFrontendRule = Prefix + SuffixFrontendRule TraefikFrontendRule = Prefix + SuffixFrontendRule
TraefikFrontendWhitelistSourceRange = Prefix + SuffixFrontendWhitelistSourceRange // Deprecated
TraefikFrontendWhiteListSourceRange = Prefix + SuffixFrontendWhiteListSourceRange TraefikFrontendWhiteListSourceRange = Prefix + SuffixFrontendWhiteListSourceRange
TraefikFrontendWhiteListUseXForwardedFor = Prefix + SuffixFrontendWhiteListUseXForwardedFor TraefikFrontendWhiteListIPStrategy = Prefix + SuffixFrontendWhiteListIPStrategy
TraefikFrontendWhiteListIPStrategyDepth = Prefix + SuffixFrontendWhiteListIPStrategyDepth
TraefikFrontendWhiteListIPStrategyExcludedIPS = Prefix + SuffixFrontendWhiteListIPStrategyExcludedIPS
TraefikFrontendRequestHeaders = Prefix + SuffixFrontendRequestHeaders TraefikFrontendRequestHeaders = Prefix + SuffixFrontendRequestHeaders
TraefikFrontendResponseHeaders = Prefix + SuffixFrontendResponseHeaders TraefikFrontendResponseHeaders = Prefix + SuffixFrontendResponseHeaders
TraefikFrontendAllowedHosts = Prefix + SuffixFrontendHeadersAllowedHosts TraefikFrontendAllowedHosts = Prefix + SuffixFrontendHeadersAllowedHosts

View file

@ -13,28 +13,30 @@ import (
// GetWhiteList Create white list from labels // GetWhiteList Create white list from labels
func GetWhiteList(labels map[string]string) *types.WhiteList { func GetWhiteList(labels map[string]string) *types.WhiteList {
if Has(labels, TraefikFrontendWhitelistSourceRange) { ranges := GetSliceStringValue(labels, TraefikFrontendWhiteListSourceRange)
log.Warnf("Deprecated configuration found: %s. Please use %s.", TraefikFrontendWhitelistSourceRange, TraefikFrontendWhiteListSourceRange) if len(ranges) == 0 {
return nil
} }
ranges := GetSliceStringValue(labels, TraefikFrontendWhiteListSourceRange)
if len(ranges) > 0 {
return &types.WhiteList{ return &types.WhiteList{
SourceRange: ranges, SourceRange: ranges,
UseXForwardedFor: GetBoolValue(labels, TraefikFrontendWhiteListUseXForwardedFor, false), IPStrategy: getIPStrategy(labels),
}
} }
}
// TODO: Deprecated func getIPStrategy(labels map[string]string) *types.IPStrategy {
values := GetSliceStringValue(labels, TraefikFrontendWhitelistSourceRange) ipStrategy := GetBoolValue(labels, TraefikFrontendWhiteListIPStrategy, false)
if len(values) > 0 { depth := GetIntValue(labels, TraefikFrontendWhiteListIPStrategyDepth, 0)
return &types.WhiteList{ excludedIPs := GetSliceStringValue(labels, TraefikFrontendWhiteListIPStrategyExcludedIPS)
SourceRange: values,
UseXForwardedFor: false,
}
}
if depth == 0 && len(excludedIPs) == 0 && !ipStrategy {
return nil return nil
}
return &types.IPStrategy{
Depth: depth,
ExcludedIPs: excludedIPs,
}
} }
// GetRedirect Create redirect from labels // GetRedirect Create redirect from labels

View file

@ -134,18 +134,6 @@ func TestWhiteList(t *testing.T) {
labels: map[string]string{}, labels: map[string]string{},
expected: nil, 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", desc: "should return a struct when only range",
labels: map[string]string{ labels: map[string]string{
@ -155,42 +143,75 @@ func TestWhiteList(t *testing.T) {
SourceRange: []string{ SourceRange: []string{
"10.10.10.10", "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{ labels: map[string]string{
TraefikFrontendWhiteListSourceRange: "10.10.10.10", TraefikFrontendWhiteListSourceRange: "10.10.10.10",
TraefikFrontendWhiteListUseXForwardedFor: "true", TraefikFrontendWhiteListIPStrategyDepth: "5",
}, },
expected: &types.WhiteList{ expected: &types.WhiteList{
SourceRange: []string{ SourceRange: []string{
"10.10.10.10", "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{ labels: map[string]string{
TraefikFrontendWhitelistSourceRange: "20.20.20.20",
TraefikFrontendWhiteListSourceRange: "10.10.10.10", TraefikFrontendWhiteListSourceRange: "10.10.10.10",
TraefikFrontendWhiteListUseXForwardedFor: "true", TraefikFrontendWhiteListIPStrategyDepth: "5",
TraefikFrontendWhiteListIPStrategyExcludedIPS: "10.10.10.10,10.10.10.11",
}, },
expected: &types.WhiteList{ expected: &types.WhiteList{
SourceRange: []string{ SourceRange: []string{
"10.10.10.10", "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{ 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,
}, },
} }

View file

@ -399,7 +399,8 @@ func TestBuildConfiguration(t *testing.T) {
withLabel(label.TraefikFrontendRedirectPermanent, "true"), withLabel(label.TraefikFrontendRedirectPermanent, "true"),
withLabel(label.TraefikFrontendRule, "Host:traefik.io"), withLabel(label.TraefikFrontendRule, "Host:traefik.io"),
withLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10"), 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.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"), 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{ WhiteList: &types.WhiteList{
SourceRange: []string{"10.10.10.10"}, 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{ Headers: &types.Headers{
CustomRequestHeaders: map[string]string{ CustomRequestHeaders: map[string]string{
@ -789,7 +793,6 @@ func TestBuildConfigurationSegments(t *testing.T) {
withSegmentLabel(label.TraefikFrontendRedirectPermanent, "true", "containous"), withSegmentLabel(label.TraefikFrontendRedirectPermanent, "true", "containous"),
withSegmentLabel(label.TraefikFrontendRule, "Host:traefik.io", "containous"), withSegmentLabel(label.TraefikFrontendRule, "Host:traefik.io", "containous"),
withSegmentLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10", "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.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"), 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{ WhiteList: &types.WhiteList{
SourceRange: []string{"10.10.10.10"}, SourceRange: []string{"10.10.10.10"},
UseXForwardedFor: true,
}, },
Headers: &types.Headers{ Headers: &types.Headers{
CustomRequestHeaders: map[string]string{ CustomRequestHeaders: map[string]string{

View file

@ -356,7 +356,8 @@ func TestBuildConfiguration(t *testing.T) {
withLabel(label.TraefikFrontendRedirectPermanent, "true"), withLabel(label.TraefikFrontendRedirectPermanent, "true"),
withLabel(label.TraefikFrontendRule, "Host:traefik.io"), withLabel(label.TraefikFrontendRule, "Host:traefik.io"),
withLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10"), 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.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"), 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{ WhiteList: &types.WhiteList{
SourceRange: []string{"10.10.10.10"}, 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{ Headers: &types.Headers{
CustomRequestHeaders: map[string]string{ CustomRequestHeaders: map[string]string{
@ -709,7 +713,6 @@ func TestBuildConfigurationSegments(t *testing.T) {
withSegmentLabel(label.TraefikFrontendRedirectPermanent, "true", "containous"), withSegmentLabel(label.TraefikFrontendRedirectPermanent, "true", "containous"),
withSegmentLabel(label.TraefikFrontendRule, "Host:traefik.io", "containous"), withSegmentLabel(label.TraefikFrontendRule, "Host:traefik.io", "containous"),
withSegmentLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10", "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.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"), 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{ WhiteList: &types.WhiteList{
SourceRange: []string{"10.10.10.10"}, SourceRange: []string{"10.10.10.10"},
UseXForwardedFor: true,
}, },
Headers: &types.Headers{ Headers: &types.Headers{
CustomRequestHeaders: map[string]string{ CustomRequestHeaders: map[string]string{

View file

@ -84,7 +84,8 @@ func TestProviderBuildConfiguration(t *testing.T) {
label.TraefikFrontendRedirectPermanent: "true", label.TraefikFrontendRedirectPermanent: "true",
label.TraefikFrontendRule: "Host:traefik.io", label.TraefikFrontendRule: "Host:traefik.io",
label.TraefikFrontendWhiteListSourceRange: "10.10.10.10", 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.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", 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{ SourceRange: []string{
"10.10.10.10", "10.10.10.10",
}, },
UseXForwardedFor: true, IPStrategy: &types.IPStrategy{
Depth: 5,
ExcludedIPs: []string{"10.10.10.10", "10.10.10.11"},
},
}, },
Headers: &types.Headers{ Headers: &types.Headers{
CustomRequestHeaders: map[string]string{ CustomRequestHeaders: map[string]string{
@ -314,7 +318,6 @@ func TestProviderBuildConfiguration(t *testing.T) {
label.Prefix + "sauternes." + label.SuffixFrontendRedirectReplacement: "nope", label.Prefix + "sauternes." + label.SuffixFrontendRedirectReplacement: "nope",
label.Prefix + "sauternes." + label.SuffixFrontendRedirectPermanent: "true", label.Prefix + "sauternes." + label.SuffixFrontendRedirectPermanent: "true",
label.Prefix + "sauternes." + label.SuffixFrontendWhiteListSourceRange: "10.10.10.10", 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.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", 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{ SourceRange: []string{
"10.10.10.10", "10.10.10.10",
}, },
UseXForwardedFor: true,
}, },
Headers: &types.Headers{ Headers: &types.Headers{
CustomRequestHeaders: map[string]string{ CustomRequestHeaders: map[string]string{

View file

@ -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)
}

View file

@ -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))
}
})
}
}

View file

@ -22,6 +22,7 @@ import (
"github.com/containous/traefik/cluster" "github.com/containous/traefik/cluster"
"github.com/containous/traefik/configuration" "github.com/containous/traefik/configuration"
"github.com/containous/traefik/h2c" "github.com/containous/traefik/h2c"
"github.com/containous/traefik/ip"
"github.com/containous/traefik/log" "github.com/containous/traefik/log"
"github.com/containous/traefik/metrics" "github.com/containous/traefik/metrics"
"github.com/containous/traefik/middlewares" "github.com/containous/traefik/middlewares"
@ -31,7 +32,6 @@ import (
"github.com/containous/traefik/safe" "github.com/containous/traefik/safe"
traefiktls "github.com/containous/traefik/tls" traefiktls "github.com/containous/traefik/tls"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"github.com/containous/traefik/whitelist"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/negroni" "github.com/urfave/negroni"
"github.com/xenolf/lego/acme" "github.com/xenolf/lego/acme"
@ -552,7 +552,7 @@ func (s *Server) prepareServer(entryPointName string, entryPoint *configuration.
if entryPoint.ProxyProtocol != nil { if entryPoint.ProxyProtocol != nil {
listener, err = buildProxyProtocolListener(entryPoint, listener) listener, err = buildProxyProtocolListener(entryPoint, listener)
if err != nil { 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) { 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 { 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) log.Infof("Enabling ProxyProtocol for trusted IPs %v", entryPoint.ProxyProtocol.TrustedIPs)
return &proxyproto.Listener{ return &proxyproto.Listener{
Listener: listener, Listener: listener,
SourceCheck: func(addr net.Addr) (bool, error) { SourceCheck: sourceCheck,
ip, ok := addr.(*net.TCPAddr)
if !ok {
return false, fmt.Errorf("type error %v", addr)
}
return IPs.ContainsIP(ip.IP), nil
},
}, nil }, nil
} }

View file

@ -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) 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 var fwd http.Handler
fwd, err = forward.New( fwd, err = forward.New(
forward.Stream(true), forward.Stream(true),
forward.PassHostHeader(frontend.PassHostHeader), forward.PassHostHeader(frontend.PassHostHeader),
forward.RoundTripper(roundTripper), forward.RoundTripper(roundTripper),
forward.Rewriter(rewriter),
forward.ResponseModifier(responseModifier), forward.ResponseModifier(responseModifier),
forward.BufferPool(s.bufferPool), forward.BufferPool(s.bufferPool),
forward.WebsocketConnectionClosedHook(func(req *http.Request, conn net.Conn) { forward.WebsocketConnectionClosedHook(func(req *http.Request, conn net.Conn) {

View file

@ -9,6 +9,7 @@ import (
"github.com/containous/traefik/middlewares/accesslog" "github.com/containous/traefik/middlewares/accesslog"
mauth "github.com/containous/traefik/middlewares/auth" mauth "github.com/containous/traefik/middlewares/auth"
"github.com/containous/traefik/middlewares/errorpages" "github.com/containous/traefik/middlewares/errorpages"
"github.com/containous/traefik/middlewares/forwardedheaders"
"github.com/containous/traefik/middlewares/redirect" "github.com/containous/traefik/middlewares/redirect"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
thoas_stats "github.com/thoas/stats" thoas_stats "github.com/thoas/stats"
@ -47,7 +48,7 @@ func (s *Server) buildMiddlewares(frontendName string, frontend *types.Frontend,
} }
// Whitelist // Whitelist
ipWhitelistMiddleware, err := buildIPWhiteLister(frontend.WhiteList, frontend.WhitelistSourceRange) ipWhitelistMiddleware, err := buildIPWhiteLister(frontend.WhiteList, s.entryPoints[entryPointName].Configuration.ClientIPStrategy)
if err != nil { if err != nil {
return nil, nil, nil, fmt.Errorf("error creating IP Whitelister: %s", err) 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{}) serverMiddlewares = append(serverMiddlewares, &middlewares.Compress{})
} }
ipWhitelistMiddleware, err := buildIPWhiteLister( if s.entryPoints[serverEntryPointName].Configuration.ForwardedHeaders != nil {
s.entryPoints[serverEntryPointName].Configuration.WhiteList, xForwardedMiddleware, err := forwardedheaders.NewXforwarded(
s.entryPoints[serverEntryPointName].Configuration.WhitelistSourceRange) 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 { if err != nil {
return nil, fmt.Errorf("failed to create ip whitelist middleware: %v", err) return nil, fmt.Errorf("failed to create ip whitelist middleware: %v", err)
} }
@ -268,14 +278,21 @@ func (s *Server) buildRedirectHandler(srcEntryPointName string, opt *types.Redir
return redirection, nil return redirection, nil
} }
func buildIPWhiteLister(whiteList *types.WhiteList, wlRange []string) (*middlewares.IPWhiteLister, error) { func buildIPWhiteLister(whiteList *types.WhiteList, ipStrategy *types.IPStrategy) (*middlewares.IPWhiteLister, error) {
if whiteList != nil && if whiteList == nil {
len(whiteList.SourceRange) > 0 {
return middlewares.NewIPWhiteLister(whiteList.SourceRange, whiteList.UseXForwardedFor)
} else if len(wlRange) > 0 {
return middlewares.NewIPWhiteLister(wlRange, false)
}
return nil, 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 { func (s *Server) wrapNegroniHandlerWithAccessLog(handler negroni.Handler, frontendName string) negroni.Handler {

View file

@ -36,9 +36,11 @@ func TestServerEntryPointWhitelistConfig(t *testing.T) {
desc: "whitelist middleware should be added if configured on entrypoint", desc: "whitelist middleware should be added if configured on entrypoint",
entrypoint: &configuration.EntryPoint{ entrypoint: &configuration.EntryPoint{
Address: ":0", Address: ":0",
WhitelistSourceRange: []string{ WhiteList: &types.WhiteList{
SourceRange: []string{
"127.0.0.1/32", "127.0.0.1/32",
}, },
},
ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true}, ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true},
}, },
expectMiddleware: true, expectMiddleware: true,
@ -96,23 +98,6 @@ func TestBuildIPWhiteLister(t *testing.T) {
middlewareConfigured: false, middlewareConfigured: false,
errMessage: "", 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", desc: "whitelists configured",
whiteList: &types.WhiteList{ whiteList: &types.WhiteList{
@ -120,22 +105,10 @@ func TestBuildIPWhiteLister(t *testing.T) {
"1.2.3.4/24", "1.2.3.4/24",
"fe80::/16", "fe80::/16",
}, },
UseXForwardedFor: false,
}, },
middlewareConfigured: true, middlewareConfigured: true,
errMessage: "", 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 { for _, test := range testCases {
@ -143,7 +116,7 @@ func TestBuildIPWhiteLister(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
middleware, err := buildIPWhiteLister(test.whiteList, test.whitelistSourceRange) middleware, err := buildIPWhiteLister(test.whiteList, nil)
if test.errMessage != "" { if test.errMessage != "" {
require.EqualError(t, err, 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}}, "http": &configuration.EntryPoint{ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true}},
}, },
} }
entryPoints := map[string]EntryPoint{
"http": {
Configuration: globalConfig.EntryPoints["http"],
},
}
dynamicConfigs := types.Configurations{ dynamicConfigs := types.Configurations{
"config": &types.Configuration{ "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) _, err := srv.loadConfig(dynamicConfigs, globalConfig)
require.NoError(t, err) require.NoError(t, err)

View file

@ -123,7 +123,13 @@
sourceRange = [{{range $whitelist.SourceRange }} sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}", "{{.}}",
{{end}}] {{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}} {{end}}
{{ $redirect := getRedirect $service.TraefikLabels }} {{ $redirect := getRedirect $service.TraefikLabels }}

View file

@ -123,7 +123,13 @@
sourceRange = [{{range $whitelist.SourceRange }} sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }} {{if $whitelist.IPStrategy }}
[frontends."frontend-{{ $frontendName }}".whiteList.IPStrategy]
depth = {{ $whitelist.IPStrategy.Depth }}
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
"{{.}}",
{{end}}]
{{end}}
{{end}} {{end}}
{{ $redirect := getRedirect $container.SegmentLabels }} {{ $redirect := getRedirect $container.SegmentLabels }}

View file

@ -122,7 +122,13 @@
sourceRange = [{{range $whitelist.SourceRange }} sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }} {{if $whitelist.IPStrategy }}
[frontends."frontend-{{ $serviceName }}".whiteList.IPStrategy]
depth = {{ $whitelist.IPStrategy.Depth }}
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
"{{.}}",
{{end}}]
{{end}}
{{end}} {{end}}
{{ $redirect := getRedirect $instance.TraefikLabels }} {{ $redirect := getRedirect $instance.TraefikLabels }}

View file

@ -90,10 +90,16 @@
{{if $frontend.WhiteList }} {{if $frontend.WhiteList }}
[frontends."{{ $frontendName }}".whiteList] [frontends."{{ $frontendName }}".whiteList]
sourceRange = [{{range $frontend.WhiteList.SourceRange }} sourceRange = [{{range $frontend.Whitelist.SourceRange }}
"{{.}}", "{{.}}",
{{end}}] {{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}} {{end}}
{{if $frontend.Redirect }} {{if $frontend.Redirect }}

View file

@ -122,7 +122,13 @@
sourceRange = [{{range $whitelist.SourceRange }} sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }} {{if $whitelist.IPStrategy }}
[frontends."{{ $frontendName }}".whiteList.IPStrategy]
depth = {{ $whitelist.IPStrategy.Depth }}
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
"{{.}}",
{{end}}]
{{end}}
{{end}} {{end}}
{{ $redirect := getRedirect $frontend }} {{ $redirect := getRedirect $frontend }}

View file

@ -125,7 +125,13 @@
sourceRange = [{{range $whitelist.SourceRange }} sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }} {{if $whitelist.IPStrategy }}
[frontends."{{ $frontendName }}".whiteList.IPStrategy]
depth = {{ $whitelist.IPStrategy.Depth }}
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
"{{.}}",
{{end}}]
{{end}}
{{end}} {{end}}
{{ $redirect := getRedirect $app.SegmentLabels }} {{ $redirect := getRedirect $app.SegmentLabels }}

View file

@ -125,7 +125,13 @@
sourceRange = [{{range $whitelist.SourceRange }} sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }} {{if $whitelist.IPStrategy }}
[frontends."frontend-{{ $frontendName }}".whiteList.IPStrategy]
depth = {{ $whitelist.IPStrategy.Depth }}
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
"{{.}}",
{{end}}]
{{end}}
{{end}} {{end}}
{{ $redirect := getRedirect $app.TraefikLabels }} {{ $redirect := getRedirect $app.TraefikLabels }}

View file

@ -123,7 +123,13 @@
sourceRange = [{{range $whitelist.SourceRange }} sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }} {{if $whitelist.IPStrategy }}
[frontends."frontend-{{ $frontendName }}".whiteList.IPStrategy]
depth = {{ $whitelist.IPStrategy.Depth }}
excludedIPs = [{{range $whitelist.IPStrategy.ExcludedIPs }}
"{{.}}",
{{end}}]
{{end}}
{{end}} {{end}}
{{ $redirect := getRedirect $service.SegmentLabels }} {{ $redirect := getRedirect $service.SegmentLabels }}

View file

@ -14,6 +14,7 @@ import (
"github.com/abronan/valkeyrie/store" "github.com/abronan/valkeyrie/store"
"github.com/containous/flaeg/parse" "github.com/containous/flaeg/parse"
"github.com/containous/mux" "github.com/containous/mux"
"github.com/containous/traefik/ip"
"github.com/containous/traefik/log" "github.com/containous/traefik/log"
traefiktls "github.com/containous/traefik/tls" traefiktls "github.com/containous/traefik/tls"
"github.com/mitchellh/hashstructure" "github.com/mitchellh/hashstructure"
@ -64,7 +65,7 @@ type Buffering struct {
// WhiteList contains white list configuration. // WhiteList contains white list configuration.
type WhiteList struct { type WhiteList struct {
SourceRange []string `json:"sourceRange,omitempty"` SourceRange []string `json:"sourceRange,omitempty"`
UseXForwardedFor bool `json:"useXForwardedFor,omitempty" export:"true"` IPStrategy *IPStrategy `json:"ipStrategy,omitempty"`
} }
// HealthCheck holds HealthCheck configuration // HealthCheck holds HealthCheck configuration
@ -183,7 +184,6 @@ type Frontend struct {
PassHostHeader bool `json:"passHostHeader,omitempty"` PassHostHeader bool `json:"passHostHeader,omitempty"`
PassTLSCert bool `json:"passTLSCert,omitempty"` PassTLSCert bool `json:"passTLSCert,omitempty"`
Priority int `json:"priority"` Priority int `json:"priority"`
WhitelistSourceRange []string `json:"whitelistSourceRange,omitempty"` // Deprecated
WhiteList *WhiteList `json:"whiteList,omitempty"` WhiteList *WhiteList `json:"whiteList,omitempty"`
Headers *Headers `json:"headers,omitempty"` Headers *Headers `json:"headers,omitempty"`
Errors map[string]*ErrorPage `json:"errors,omitempty"` Errors map[string]*ErrorPage `json:"errors,omitempty"`
@ -611,3 +611,37 @@ func (h HTTPCodeRanges) Contains(statusCode int) bool {
} }
return false 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
}

View file

@ -4,7 +4,9 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/containous/traefik/ip"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestHeaders_ShouldReturnFalseWhenNotHasCustomHeadersDefined(t *testing.T) { 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)
})
}
}

View file

@ -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
}

View file

@ -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
}