Ability to use "X-Forwarded-For" as a source of IP for white list.

This commit is contained in:
Ludovic Fernandez 2018-03-23 17:40:04 +01:00 committed by Traefiker Bot
parent 4802484729
commit d2766b1b4f
50 changed files with 1496 additions and 599 deletions

View file

@ -123,17 +123,19 @@ var _templatesConsul_catalogTmpl = []byte(`[backends]
"{{.}}",
{{end}}]
{{ $whitelistSourceRange := getWhitelistSourceRange $service.Attributes }}
{{if $whitelistSourceRange }}
whitelistSourceRange = [{{range $whitelistSourceRange }}
"{{.}}",
{{end}}]
{{end}}
basicAuth = [{{range getBasicAuth $service.Attributes }}
"{{.}}",
{{end}}]
{{ $whitelist := getWhiteList $service.Attributes }}
{{if $whitelist }}
[frontends."frontend-{{ $service.ServiceName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}",
{{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
{{ $redirect := getRedirect $service.Attributes }}
{{if $redirect }}
[frontends."frontend-{{ $service.ServiceName }}".redirect]
@ -523,17 +525,19 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
"{{.}}",
{{end}}]
{{ $whitelistSourceRange := getWhitelistSourceRange $container.SegmentLabels }}
{{if $whitelistSourceRange }}
whitelistSourceRange = [{{range $whitelistSourceRange }}
"{{.}}",
{{end}}]
{{end}}
basicAuth = [{{range getBasicAuth $container.SegmentLabels }}
"{{.}}",
{{end}}]
{{ $whitelist := getWhiteList $container.SegmentLabels }}
{{if $whitelist }}
[frontends."frontend-{{ $frontendName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}",
{{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
{{ $redirect := getRedirect $container.SegmentLabels }}
{{if $redirect }}
[frontends."frontend-{{ $frontendName }}".redirect]
@ -713,18 +717,19 @@ var _templatesEcsTmpl = []byte(`[backends]
"{{.}}",
{{end}}]
{{ $whitelistSourceRange := getWhitelistSourceRange $instance }}
{{if $whitelistSourceRange }}
whitelistSourceRange = [{{range $whitelistSourceRange }}
"{{.}}",
{{end}}]
{{end}}
basicAuth = [{{range getBasicAuth $instance }}
"{{.}}",
{{end}}]
{{ $whitelist := getWhiteList $instance }}
{{if $whitelist }}
[frontends."frontend-{{ $serviceName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}",
{{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
{{ $redirect := getRedirect $instance }}
{{if $redirect }}
[frontends."frontend-{{ $serviceName }}".redirect]
@ -934,9 +939,13 @@ var _templatesKubernetesTmpl = []byte(`[backends]
"{{.}}",
{{end}}]
whitelistSourceRange = [{{range $frontend.WhitelistSourceRange }}
"{{.}}",
{{end}}]
{{if $frontend.WhiteList }}
[frontends."{{ $frontendName }}".whiteList]
sourceRange = [{{range $frontend.WhiteList.SourceRange }}
"{{.}}",
{{end}}]
useXForwardedFor = {{ $frontend.WhiteList.UseXForwardedFor }}
{{end}}
{{if $frontend.Redirect }}
[frontends."{{ $frontendName }}".redirect]
@ -1118,17 +1127,19 @@ var _templatesKvTmpl = []byte(`[backends]
"{{.}}",
{{end}}]
{{ $whitelistSourceRange := getWhitelistSourceRange $frontend }}
{{if $whitelistSourceRange }}
whitelistSourceRange = [{{range $whitelistSourceRange }}
"{{.}}",
{{end}}]
{{end}}
basicAuth = [{{range getBasicAuth $frontend }}
"{{.}}",
{{end}}]
{{ $whitelist := getWhiteList $frontend }}
{{if $whitelist }}
[frontends."{{ $frontendName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}",
{{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
{{ $redirect := getRedirect $frontend }}
{{if $redirect }}
[frontends."{{ $frontendName }}".redirect]
@ -1329,17 +1340,19 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
"{{.}}",
{{end}}]
{{ $whitelistSourceRange := getWhitelistSourceRange $app $serviceName }}
{{if $whitelistSourceRange }}
whitelistSourceRange = [{{range $whitelistSourceRange }}
"{{.}}",
{{end}}]
{{end}}
basicAuth = [{{range getBasicAuth $app $serviceName }}
"{{.}}",
{{end}}]
{{ $whitelist := getWhiteList $app $serviceName }}
{{if $whitelist }}
[frontends."{{ $frontendName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}",
{{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
{{ $redirect := getRedirect $app $serviceName }}
{{if $redirect }}
[frontends."{{ $frontendName }}".redirect]
@ -1522,17 +1535,19 @@ var _templatesMesosTmpl = []byte(`[backends]
"{{.}}",
{{end}}]
{{ $whitelistSourceRange := getWhitelistSourceRange $app }}
{{if $whitelistSourceRange }}
whitelistSourceRange = [{{range $whitelistSourceRange }}
"{{.}}",
{{end}}]
{{end}}
basicAuth = [{{range getBasicAuth $app }}
"{{.}}",
{{end}}]
{{ $whitelist := getWhiteList $app }}
{{if $whitelist }}
[frontends."frontend-{{ $frontendName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}",
{{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
{{ $redirect := getRedirect $app }}
{{if $redirect }}
[frontends."frontend-{{ $frontendName }}".redirect]
@ -1736,17 +1751,19 @@ var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
"{{.}}",
{{end}}]
{{ $whitelistSourceRange := getWhitelistSourceRange $service }}
{{if $whitelistSourceRange }}
whitelistSourceRange = [{{range $whitelistSourceRange }}
"{{.}}",
{{end}}]
{{end}}
basicAuth = [{{range getBasicAuth $service }}
"{{.}}",
{{end}}]
{{ $whitelist := getWhiteList $service }}
{{if $whitelist }}
[frontends."frontend-{{ $frontendName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}",
{{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
{{ $redirect := getRedirect $service }}
{{if $redirect }}
[frontends."frontend-{{ $frontendName }}".redirect]

View file

@ -182,12 +182,23 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
}
}
// ForwardedHeaders must be remove in the next breaking version
for entryPointName := range gc.EntryPoints {
entryPoint := gc.EntryPoints[entryPointName]
// ForwardedHeaders must be remove in the next breaking version
if entryPoint.ForwardedHeaders == nil {
entryPoint.ForwardedHeaders = &ForwardedHeaders{Insecure: true}
}
if len(entryPoint.WhitelistSourceRange) > 0 {
log.Warnf("Deprecated configuration found: %s. Please use %s.", "whiteListSourceRange", "whiteList.sourceRange")
if entryPoint.WhiteList == nil {
entryPoint.WhiteList = &types.WhiteList{
SourceRange: entryPoint.WhitelistSourceRange,
}
entryPoint.WhitelistSourceRange = nil
}
}
}
// Make sure LifeCycle isn't nil to spare nil checks elsewhere.
@ -378,18 +389,6 @@ type ForwardingTimeouts struct {
ResponseHeaderTimeout flaeg.Duration `description:"The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists" export:"true"`
}
// ProxyProtocol contains Proxy-Protocol configuration
type ProxyProtocol struct {
Insecure bool
TrustedIPs []string
}
// ForwardedHeaders Trust client forwarding headers
type ForwardedHeaders struct {
Insecure bool
TrustedIPs []string
}
// LifeCycle contains configurations relevant to the lifecycle (such as the
// shutdown phase) of Traefik.
type LifeCycle struct {

View file

@ -12,15 +12,28 @@ import (
// EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...)
type EntryPoint struct {
Address string
TLS *tls.TLS `export:"true"`
Redirect *types.Redirect `export:"true"`
Auth *types.Auth `export:"true"`
WhitelistSourceRange []string
TLS *tls.TLS `export:"true"`
Redirect *types.Redirect `export:"true"`
Auth *types.Auth `export:"true"`
WhitelistSourceRange []string // Deprecated
WhiteList *types.WhiteList `export:"true"`
Compress bool `export:"true"`
ProxyProtocol *ProxyProtocol `export:"true"`
ForwardedHeaders *ForwardedHeaders `export:"true"`
}
// ProxyProtocol contains Proxy-Protocol configuration
type ProxyProtocol struct {
Insecure bool `export:"true"`
TrustedIPs []string
}
// ForwardedHeaders Trust client forwarding headers
type ForwardedHeaders struct {
Insecure bool `export:"true"`
TrustedIPs []string
}
// EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...)
type EntryPoints map[string]*EntryPoint
@ -70,6 +83,7 @@ func (ep *EntryPoints) Set(value string) error {
Redirect: makeEntryPointRedirect(result),
Compress: compress,
WhitelistSourceRange: whiteListSourceRange,
WhiteList: makeWhiteList(result),
ProxyProtocol: makeEntryPointProxyProtocol(result),
ForwardedHeaders: makeEntryPointForwardedHeaders(result),
}
@ -77,6 +91,17 @@ func (ep *EntryPoints) Set(value string) error {
return nil
}
func makeWhiteList(result map[string]string) *types.WhiteList {
var wl *types.WhiteList
if rawRange, ok := result["whitelist_sourcerange"]; ok {
wl = &types.WhiteList{
SourceRange: strings.Split(rawRange, ","),
UseXForwardedFor: toBool(result, "whitelist_usexforwardedfor"),
}
}
return wl
}
func makeEntryPointAuth(result map[string]string) *types.Auth {
var basic *types.Basic
if v, ok := result["auth_basic_users"]; ok {

View file

@ -28,7 +28,6 @@ func Test_parseEntryPointsConfiguration(t *testing.T) {
"Redirect.Replacement:http://mydomain/$1 " +
"Redirect.Permanent:true " +
"Compress:true " +
"WhiteListSourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " +
"ProxyProtocol.TrustedIPs:192.168.0.1 " +
"ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 " +
"Auth.Basic.Users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 " +
@ -40,7 +39,10 @@ func Test_parseEntryPointsConfiguration(t *testing.T) {
"Auth.Forward.TLS.CAOptional:true " +
"Auth.Forward.TLS.Cert:path/to/foo.cert " +
"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.useXForwardedFor:true ",
expectedResult: map[string]string{
"address": ":8000",
"auth_basic_users": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
@ -63,9 +65,11 @@ func Test_parseEntryPointsConfiguration(t *testing.T) {
"redirect_permanent": "true",
"redirect_regex": "http://localhost/(.*)",
"redirect_replacement": "http://mydomain/$1",
"tls": "goo,gii",
"tls_acme": "TLS",
"whitelistsourcerange": "10.42.0.0/16,152.89.1.33/32,afed:be44::/16",
"tls": "goo,gii",
"tls_acme": "TLS",
"whitelistsourcerange": "10.42.0.0/16,152.89.1.33/32,afed:be44::/16",
"whitelist_sourcerange": "10.42.0.0/16,152.89.1.33/32,afed:be44::/16",
"whitelist_usexforwardedfor": "true",
},
},
{
@ -175,7 +179,6 @@ func TestEntryPoints_Set(t *testing.T) {
"Redirect.Replacement:http://mydomain/$1 " +
"Redirect.Permanent:true " +
"Compress:true " +
"WhiteListSourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " +
"ProxyProtocol.TrustedIPs:192.168.0.1 " +
"ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 " +
"Auth.Basic.Users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 " +
@ -187,7 +190,10 @@ func TestEntryPoints_Set(t *testing.T) {
"Auth.Forward.TLS.CAOptional:true " +
"Auth.Forward.TLS.Cert:path/to/foo.cert " +
"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.useXForwardedFor:true ",
expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{
Address: ":8000",
@ -240,6 +246,14 @@ func TestEntryPoints_Set(t *testing.T) {
"152.89.1.33/32",
"afed:be44::/16",
},
WhiteList: &types.WhiteList{
SourceRange: []string{
"10.42.0.0/16",
"152.89.1.33/32",
"afed:be44::/16",
},
UseXForwardedFor: true,
},
Compress: true,
ProxyProtocol: &ProxyProtocol{
Insecure: false,

View file

@ -109,7 +109,8 @@ Additional settings can be defined using Consul Catalog tags.
| `<prefix>.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
| `<prefix>.frontend.redirect.permanent=true` | Return 301 instead of 302. |
| `<prefix>.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{{.ServiceName}}.{{.Domain}}`. |
| `<prefix>.frontend.whitelistSourceRange=RANGE` | 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` | 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` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
### Custom Headers

View file

@ -193,48 +193,49 @@ services:
Labels can be used on containers to override default behavior.
| Label | Description |
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.docker.network` | Set the docker network to use for connections to this container. [1] |
| `traefik.enable=false` | Disable this container in Træfik |
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. |
| `traefik.protocol=https` | Override the default `http` protocol |
| `traefik.weight=10` | Assign this weight to the container |
| `traefik.backend=foo` | Give the name `foo` to the generated backend for this container. |
| `traefik.backend.buffering.maxRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
| `traefik.backend.buffering.maxResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
| `traefik.backend.buffering.memRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
| `traefik.backend.buffering.memResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
| `traefik.backend.buffering.retryExpression=EXPR` | See [buffering](/configuration/commons/#buffering) section. |
| `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend |
| `traefik.backend.healthcheck.path=/health` | Enable health check for the backend, hitting the container at `path`. |
| `traefik.backend.healthcheck.port=8080` | Allow to use a different port for the health check. |
| `traefik.backend.healthcheck.interval=1s` | Define the health check interval. |
| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm |
| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions |
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
| `traefik.backend.loadbalancer.sticky=true` | Enable backend sticky sessions (DEPRECATED) |
| `traefik.backend.loadbalancer.swarm=true` | Use Swarm's inbuilt load balancer (only relevant under Swarm Mode). |
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
| `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`.<br>Overrides `defaultEntryPoints` |
| `traefik.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
| `traefik.frontend.errors.<name>.query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
| `traefik.frontend.errors.<name>.status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
| `traefik.frontend.passTLSCert=true` | Forward TLS Client certificates to the backend. |
| `traefik.frontend.priority=10` | Override default frontend priority |
| `traefik.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
| `traefik.frontend.rateLimit.rateSet.<name>.period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
| `traefik.frontend.rateLimit.rateSet.<name>.average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
| `traefik.frontend.rateLimit.rateSet.<name>.burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
| `traefik.frontend.redirect.permanent=true` | Return 301 instead of 302. |
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{containerName}.{domain}` or `Host:{service}.{project_name}.{domain}` if you are using `docker-compose`. |
| `traefik.frontend.whitelistSourceRange=RANGE` | 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. |
| Label | Description |
|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.docker.network` | Set the docker network to use for connections to this container. [1] |
| `traefik.enable=false` | Disable this container in Træfik |
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. |
| `traefik.protocol=https` | Override the default `http` protocol |
| `traefik.weight=10` | Assign this weight to the container |
| `traefik.backend=foo` | Give the name `foo` to the generated backend for this container. |
| `traefik.backend.buffering.maxRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
| `traefik.backend.buffering.maxResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
| `traefik.backend.buffering.memRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
| `traefik.backend.buffering.memResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
| `traefik.backend.buffering.retryExpression=EXPR` | See [buffering](/configuration/commons/#buffering) section. |
| `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend |
| `traefik.backend.healthcheck.path=/health` | Enable health check for the backend, hitting the container at `path`. |
| `traefik.backend.healthcheck.port=8080` | Allow to use a different port for the health check. |
| `traefik.backend.healthcheck.interval=1s` | Define the health check interval. |
| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm |
| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions |
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
| `traefik.backend.loadbalancer.sticky=true` | Enable backend sticky sessions (DEPRECATED) |
| `traefik.backend.loadbalancer.swarm=true` | Use Swarm's inbuilt load balancer (only relevant under Swarm Mode). |
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
| `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`.<br>Overrides `defaultEntryPoints` |
| `traefik.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
| `traefik.frontend.errors.<name>.query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
| `traefik.frontend.errors.<name>.status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
| `traefik.frontend.passTLSCert=true` | Forward TLS Client certificates to the backend. |
| `traefik.frontend.priority=10` | Override default frontend priority |
| `traefik.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
| `traefik.frontend.rateLimit.rateSet.<name>.period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
| `traefik.frontend.rateLimit.rateSet.<name>.average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
| `traefik.frontend.rateLimit.rateSet.<name>.burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
| `traefik.frontend.redirect.permanent=true` | Return 301 instead of 302. |
| `traefik.frontend.rule=EXPR` | Override 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` | 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. |
[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).
@ -303,7 +304,8 @@ Segment labels override the default behavior.
| `traefik.<segment_name>.frontend.redirect.replacement=http://mydomain/$1` | Overrides `traefik.frontend.redirect.replacement`. |
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Return 301 instead of 302. |
| `traefik.<segment_name>.frontend.rule` | Overrides `traefik.frontend.rule`. |
| `traefik.<segment_name>.frontend.whitelistSourceRange=RANGE` | Overrides `traefik.frontend.whitelistSourceRange`. |
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Overrides `traefik.frontend.whiteList.sourceRange`. |
| `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Overrides `traefik.frontend.whiteList.useXForwardedFor`. |
#### Custom Headers

View file

@ -163,7 +163,8 @@ Labels can be used on task containers to override default behaviour:
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
| `traefik.frontend.redirect.permanent=true` | Return 301 instead of 302. |
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{instance_name}.{domain}`. |
| `traefik.frontend.whitelistSourceRange=RANGE` | 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` | 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` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
### Custom Headers

View file

@ -54,7 +54,10 @@ Træfik can be configured with a file.
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
]
whitelistSourceRange = ["10.42.0.0/16", "152.89.1.33/32", "afed:be44::/16"]
[frontends.frontend1.whiteList]
sourceRange = ["10.42.0.0/16", "152.89.1.33/32", "afed:be44::/16"]
useXForwardedFor = true
[frontends.frontend1.routes]
[frontends.frontend1.routes.route0]

View file

@ -191,16 +191,17 @@ The following labels can be defined on Marathon applications. They adjust the be
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
| `traefik.frontend.passTLSCert=true` | Forward TLS Client certificates to the backend. |
| `traefik.frontend.priority=10` | Override default frontend priority |
| `traefik.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
| `traefik.frontend.rateLimit.rateSet.<name>.period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
| `traefik.frontend.rateLimit.rateSet.<name>.average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
| `traefik.frontend.rateLimit.rateSet.<name>.burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
| `traefik.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
| `traefik.frontend.rateLimit.rateSet.<name>.period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
| `traefik.frontend.rateLimit.rateSet.<name>.average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
| `traefik.frontend.rateLimit.rateSet.<name>.burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
| `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. Default: `Host:{sub_domain}.{domain}`. |
| `traefik.frontend.whitelistSourceRange=RANGE` | 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` | 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` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
#### Custom Headers
@ -265,6 +266,8 @@ You can define as many segments as ports exposed in an application.
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Return 301 instead of 302. |
| `traefik.<segment_name>.frontend.rule=EXP` | Overrides `traefik.frontend.rule`. Default: `{service_name}.{sub_domain}.{domain}` |
| `traefik.<segment_name>.frontend.whitelistSourceRange=RANGE` | Overrides `traefik.frontend.whitelistSourceRange`. |
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Overrides `traefik.frontend.whiteList.sourceRange`. |
| `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
#### Custom Headers

View file

@ -135,7 +135,8 @@ The following labels can be defined on Mesos tasks. They adjust the behaviour fo
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
| `traefik.frontend.redirect.permanent=true` | Return 301 instead of 302. |
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{discovery_name}.{domain}`. |
| `traefik.frontend.whitelistSourceRange=RANGE` | 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` | 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` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
### Custom Headers

View file

@ -159,7 +159,8 @@ Labels can be used on task containers to override default behaviour:
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
| `traefik.frontend.redirect.permanent=true` | Return 301 instead of 302. |
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{service_name}.{stack_name}.{domain}`. |
| `traefik.frontend.whitelistSourceRange=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. |
### Custom Headers

View file

@ -8,9 +8,12 @@
[entryPoints]
[entryPoints.http]
address = ":80"
whitelistSourceRange = ["10.42.0.0/16", "152.89.1.33/32", "afed:be44::/16"]
compress = true
[entryPoints.http.whitelist]
sourceRange = ["10.42.0.0/16", "152.89.1.33/32", "afed:be44::/16"]
useXForwardedFor = true
[entryPoints.http.tls]
minVersion = "VersionTLS12"
cipherSuites = [
@ -112,7 +115,8 @@ Redirect.Regex:http://localhost/(.*)
Redirect.Replacement:http://mydomain/$1
Redirect.Permanent:true
Compress:true
WhiteListSourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16
WhiteList.SourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16
WhiteList.UseXForwardedFor:true
ProxyProtocol.TrustedIPs:192.168.0.1
ProxyProtocol.Insecure:tue
ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24
@ -352,15 +356,18 @@ Responses are compressed when:
* And the `Accept-Encoding` request header contains `gzip`
* And the response is not already compressed, i.e. the `Content-Encoding` response header is not already set.
## Whitelisting
## White Listing
To enable IP whitelisting at the entrypoint level.
To enable IP white listing at the entry point level.
```toml
[entryPoints]
[entryPoints.http]
address = ":80"
whiteListSourceRange = ["127.0.0.1/32", "192.168.1.7"]
address = ":80"
[entryPoints.http]
sourceRange = ["127.0.0.1/32", "192.168.1.7"]
# useXForwardedFor = true
```
## ProxyProtocol

View file

@ -2,7 +2,6 @@ package middlewares
import (
"fmt"
"net"
"net/http"
"github.com/containous/traefik/log"
@ -18,49 +17,41 @@ type IPWhiteLister struct {
whiteLister *whitelist.IP
}
// NewIPWhitelister builds a new IPWhiteLister given a list of CIDR-Strings to whitelist
func NewIPWhitelister(whitelistStrings []string) (*IPWhiteLister, error) {
if len(whitelistStrings) == 0 {
return nil, errors.New("no whitelists provided")
// NewIPWhiteLister builds a new IPWhiteLister given a list of CIDR-Strings to whitelist
func NewIPWhiteLister(whiteList []string, useXForwardedFor bool) (*IPWhiteLister, error) {
if len(whiteList) == 0 {
return nil, errors.New("no white list provided")
}
whiteLister := IPWhiteLister{}
ip, err := whitelist.NewIP(whitelistStrings, false)
ip, err := whitelist.NewIP(whiteList, false, useXForwardedFor)
if err != nil {
return nil, fmt.Errorf("parsing CIDR whitelist %s: %v", whitelistStrings, err)
return nil, fmt.Errorf("parsing CIDR whitelist %s: %v", whiteList, err)
}
whiteLister.whiteLister = ip
whiteLister.handler = negroni.HandlerFunc(whiteLister.handle)
log.Debugf("configured %u IP whitelists: %s", len(whitelistStrings), whitelistStrings)
log.Debugf("configured %u IP white list: %s", len(whiteList), whiteList)
return &whiteLister, nil
}
func (wl *IPWhiteLister) handle(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
ipAddress, _, err := net.SplitHostPort(r.RemoteAddr)
allowed, ip, err := wl.whiteLister.IsAuthorized(r)
if err != nil {
tracing.SetErrorAndWarnLog(r, "unable to parse remote-address from header: %s - rejecting", r.RemoteAddr)
reject(w)
return
}
allowed, ip, err := wl.whiteLister.Contains(ipAddress)
if err != nil {
tracing.SetErrorAndDebugLog(r, "source-IP %s matched none of the whitelists - rejecting", ipAddress)
tracing.SetErrorAndDebugLog(r, "request %+v matched none of the white list - rejecting", r)
reject(w)
return
}
if allowed {
tracing.SetErrorAndDebugLog(r, "source-IP %s matched whitelist %s - passing", ipAddress, wl.whiteLister)
tracing.SetErrorAndDebugLog(r, "request %+v matched white list %s - passing", r, wl.whiteLister)
next.ServeHTTP(w, r)
return
}
tracing.SetErrorAndDebugLog(r, "source-IP %s matched none of the whitelists - rejecting", ip)
tracing.SetErrorAndDebugLog(r, "source-IP %s matched none of the white list - rejecting", ip)
reject(w)
}

View file

@ -43,20 +43,20 @@ func (p *Provider) buildConfiguration(catalog []catalogUpdate) *types.Configurat
"getBuffering": p.getBuffering,
// Frontend functions
"getFrontendRule": p.getFrontendRule,
"getBasicAuth": p.getFuncSliceAttribute(label.SuffixFrontendAuthBasic),
"getEntryPoints": getEntryPoints, // TODO Deprecated [breaking]
"getFrontEndEntryPoints": p.getFuncSliceAttribute(label.SuffixFrontendEntryPoints), // TODO [breaking] rename to getEntryPoints when getEntryPoints will be removed
"getPriority": p.getFuncIntAttribute(label.SuffixFrontendPriority, label.DefaultFrontendPriorityInt),
"getPassHostHeader": p.getFuncBoolAttribute(label.SuffixFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": p.getFuncBoolAttribute(label.SuffixFrontendPassTLSCert, label.DefaultPassTLSCert),
"getWhitelistSourceRange": p.getFuncSliceAttribute(label.SuffixFrontendWhitelistSourceRange),
"getRedirect": p.getRedirect,
"hasErrorPages": p.getFuncHasAttributePrefix(label.BaseFrontendErrorPage),
"getErrorPages": p.getErrorPages,
"hasRateLimit": p.getFuncHasAttributePrefix(label.BaseFrontendRateLimit),
"getRateLimit": p.getRateLimit,
"getHeaders": p.getHeaders,
"getFrontendRule": p.getFrontendRule,
"getBasicAuth": p.getFuncSliceAttribute(label.SuffixFrontendAuthBasic),
"getEntryPoints": getEntryPoints, // TODO Deprecated [breaking]
"getFrontEndEntryPoints": p.getFuncSliceAttribute(label.SuffixFrontendEntryPoints), // TODO [breaking] rename to getEntryPoints when getEntryPoints will be removed
"getPriority": p.getFuncIntAttribute(label.SuffixFrontendPriority, label.DefaultFrontendPriorityInt),
"getPassHostHeader": p.getFuncBoolAttribute(label.SuffixFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": p.getFuncBoolAttribute(label.SuffixFrontendPassTLSCert, label.DefaultPassTLSCert),
"getWhiteList": p.getWhiteList,
"getRedirect": p.getRedirect,
"hasErrorPages": p.getFuncHasAttributePrefix(label.BaseFrontendErrorPage),
"getErrorPages": p.getErrorPages,
"hasRateLimit": p.getFuncHasAttributePrefix(label.BaseFrontendRateLimit),
"getRateLimit": p.getRateLimit,
"getHeaders": p.getHeaders,
}
var allNodes []*api.ServiceEntry
@ -311,6 +311,19 @@ func (p *Provider) getBuffering(tags []string) *types.Buffering {
}
}
func (p *Provider) getWhiteList(tags []string) *types.WhiteList {
ranges := p.getSliceAttribute(label.SuffixFrontendWhiteListSourceRange, tags)
if len(ranges) > 0 {
return &types.WhiteList{
SourceRange: ranges,
UseXForwardedFor: p.getBoolAttribute(label.SuffixFrontendWhiteListUseXForwardedFor, tags, false),
}
}
return nil
}
func (p *Provider) getRedirect(tags []string) *types.Redirect {
permanent := p.getBoolAttribute(label.SuffixFrontendRedirectPermanent, tags, false)

View file

@ -1048,6 +1048,65 @@ func TestProviderGetBuffering(t *testing.T) {
}
}
func TestProviderWhiteList(t *testing.T) {
p := &Provider{
Prefix: "traefik",
}
testCases := []struct {
desc string
tags []string
expected *types.WhiteList
}{
{
desc: "should return nil when no white list labels",
expected: nil,
},
{
desc: "should return a struct when only range",
tags: []string{
label.TraefikFrontendWhiteListSourceRange + "=10.10.10.10",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: false,
},
},
{
desc: "should return a struct when range and UseXForwardedFor",
tags: []string{
label.TraefikFrontendWhiteListSourceRange + "=10.10.10.10",
label.TraefikFrontendWhiteListUseXForwardedFor + "=true",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: true,
},
},
{
desc: "should return nil when only UseXForwardedFor",
tags: []string{
label.TraefikFrontendWhiteListUseXForwardedFor + "=true",
},
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := p.getWhiteList(test.tags)
assert.Equal(t, test.expected, actual)
})
}
}
func TestProviderGetRedirect(t *testing.T) {
p := &Provider{
Prefix: "traefik",

View file

@ -40,18 +40,18 @@ func (p *Provider) buildConfigurationV2(containersInspected []dockerData) *types
"getLoadBalancer": getLoadBalancer,
// Frontend functions
"getBackendName": getBackendName,
"getPriority": getFuncIntLabel(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
"getPassHostHeader": getFuncBoolLabel(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": getFuncBoolLabel(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getEntryPoints": getFuncSliceStringLabel(label.TraefikFrontendEntryPoints),
"getBasicAuth": getFuncSliceStringLabel(label.TraefikFrontendAuthBasic),
"getWhitelistSourceRange": getFuncSliceStringLabel(label.TraefikFrontendWhitelistSourceRange),
"getFrontendRule": p.getFrontendRule,
"getRedirect": getRedirect,
"getErrorPages": getErrorPages,
"getRateLimit": getRateLimit,
"getHeaders": getHeaders,
"getBackendName": getBackendName,
"getPriority": getFuncIntLabel(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
"getPassHostHeader": getFuncBoolLabel(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": getFuncBoolLabel(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getEntryPoints": getFuncSliceStringLabel(label.TraefikFrontendEntryPoints),
"getBasicAuth": getFuncSliceStringLabel(label.TraefikFrontendAuthBasic),
"getFrontendRule": p.getFrontendRule,
"getRedirect": getRedirect,
"getErrorPages": getErrorPages,
"getRateLimit": getRateLimit,
"getHeaders": getHeaders,
"getWhiteList": getWhiteList,
}
// filter containers
@ -276,6 +276,31 @@ func getBackendName(container dockerData) string {
return getDefaultBackendName(container)
}
func getWhiteList(labels map[string]string) *types.WhiteList {
if label.Has(labels, label.TraefikFrontendWhitelistSourceRange) {
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikFrontendWhitelistSourceRange, label.TraefikFrontendWhiteListSourceRange)
}
ranges := label.GetSliceStringValue(labels, label.TraefikFrontendWhiteListSourceRange)
if len(ranges) > 0 {
return &types.WhiteList{
SourceRange: ranges,
UseXForwardedFor: label.GetBoolValue(labels, label.TraefikFrontendWhiteListUseXForwardedFor, false),
}
}
// TODO: Deprecated
values := label.GetSliceStringValue(labels, label.TraefikFrontendWhitelistSourceRange)
if len(values) > 0 {
return &types.WhiteList{
SourceRange: values,
UseXForwardedFor: false,
}
}
return nil
}
func getRedirect(labels map[string]string) *types.Redirect {
permanent := label.GetBoolValue(labels, label.TraefikFrontendRedirectPermanent, false)

View file

@ -113,17 +113,18 @@ func TestDockerBuildConfiguration(t *testing.T) {
label.TraefikBackendBufferingMemRequestBodyBytes: "2097152",
label.TraefikBackendBufferingRetryExpression: "IsNetworkError() && Attempts() <= 2",
label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
label.TraefikFrontendEntryPoints: "http,https",
label.TraefikFrontendPassHostHeader: "true",
label.TraefikFrontendPassTLSCert: "true",
label.TraefikFrontendPriority: "666",
label.TraefikFrontendRedirectEntryPoint: "https",
label.TraefikFrontendRedirectRegex: "nope",
label.TraefikFrontendRedirectReplacement: "nope",
label.TraefikFrontendRedirectPermanent: "true",
label.TraefikFrontendRule: "Host:traefik.io",
label.TraefikFrontendWhitelistSourceRange: "10.10.10.10",
label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
label.TraefikFrontendEntryPoints: "http,https",
label.TraefikFrontendPassHostHeader: "true",
label.TraefikFrontendPassTLSCert: "true",
label.TraefikFrontendPriority: "666",
label.TraefikFrontendRedirectEntryPoint: "https",
label.TraefikFrontendRedirectRegex: "nope",
label.TraefikFrontendRedirectReplacement: "nope",
label.TraefikFrontendRedirectPermanent: "true",
label.TraefikFrontendRule: "Host:traefik.io",
label.TraefikFrontendWhiteListSourceRange: "10.10.10.10",
label.TraefikFrontendWhiteListUseXForwardedFor: "true",
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",
@ -187,8 +188,9 @@ func TestDockerBuildConfiguration(t *testing.T) {
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
},
WhitelistSourceRange: []string{
"10.10.10.10",
WhiteList: &types.WhiteList{
SourceRange: []string{"10.10.10.10"},
UseXForwardedFor: true,
},
Headers: &types.Headers{
CustomRequestHeaders: map[string]string{
@ -692,17 +694,17 @@ func TestDockerGetSliceStringLabel(t *testing.T) {
{
desc: "whitelist-label with empty string",
labels: map[string]string{
label.TraefikFrontendWhitelistSourceRange: "",
label.TraefikFrontendWhiteListSourceRange: "",
},
labelName: label.TraefikFrontendWhitelistSourceRange,
labelName: label.TraefikFrontendWhiteListSourceRange,
expected: nil,
},
{
desc: "whitelist-label with IPv4 mask",
labels: map[string]string{
label.TraefikFrontendWhitelistSourceRange: "1.2.3.4/16",
label.TraefikFrontendWhiteListSourceRange: "1.2.3.4/16",
},
labelName: label.TraefikFrontendWhitelistSourceRange,
labelName: label.TraefikFrontendWhiteListSourceRange,
expected: []string{
"1.2.3.4/16",
},
@ -710,9 +712,9 @@ func TestDockerGetSliceStringLabel(t *testing.T) {
{
desc: "whitelist-label with IPv6 mask",
labels: map[string]string{
label.TraefikFrontendWhitelistSourceRange: "fe80::/16",
label.TraefikFrontendWhiteListSourceRange: "fe80::/16",
},
labelName: label.TraefikFrontendWhitelistSourceRange,
labelName: label.TraefikFrontendWhiteListSourceRange,
expected: []string{
"fe80::/16",
},
@ -720,9 +722,9 @@ func TestDockerGetSliceStringLabel(t *testing.T) {
{
desc: "whitelist-label with multiple masks",
labels: map[string]string{
label.TraefikFrontendWhitelistSourceRange: "1.1.1.1/24, 1234:abcd::42/32",
label.TraefikFrontendWhiteListSourceRange: "1.1.1.1/24, 1234:abcd::42/32",
},
labelName: label.TraefikFrontendWhitelistSourceRange,
labelName: label.TraefikFrontendWhiteListSourceRange,
expected: []string{
"1.1.1.1/24",
"1234:abcd::42/32",
@ -1025,3 +1027,85 @@ func TestDockerGetPort(t *testing.T) {
})
}
}
func TestWhiteList(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected *types.WhiteList
}{
{
desc: "should return nil when no white list labels",
labels: map[string]string{},
expected: nil,
},
{
desc: "should return a struct when deprecated label",
labels: map[string]string{
label.TraefikFrontendWhitelistSourceRange: "10.10.10.10",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: false,
},
},
{
desc: "should return a struct when only range",
labels: map[string]string{
label.TraefikFrontendWhiteListSourceRange: "10.10.10.10",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: false,
},
},
{
desc: "should return a struct when range and UseXForwardedFor",
labels: map[string]string{
label.TraefikFrontendWhiteListSourceRange: "10.10.10.10",
label.TraefikFrontendWhiteListUseXForwardedFor: "true",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: true,
},
},
{
desc: "should return a struct when mix deprecated label and new labels",
labels: map[string]string{
label.TraefikFrontendWhitelistSourceRange: "20.20.20.20",
label.TraefikFrontendWhiteListSourceRange: "10.10.10.10",
label.TraefikFrontendWhiteListUseXForwardedFor: "true",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: true,
},
},
{
desc: "should return nil when only UseXForwardedFor",
labels: map[string]string{
label.TraefikFrontendWhiteListUseXForwardedFor: "true",
},
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getWhiteList(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}

View file

@ -122,16 +122,17 @@ func TestSwarmBuildConfiguration(t *testing.T) {
label.TraefikBackendBufferingMemRequestBodyBytes: "2097152",
label.TraefikBackendBufferingRetryExpression: "IsNetworkError() && Attempts() <= 2",
label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
label.TraefikFrontendEntryPoints: "http,https",
label.TraefikFrontendPassHostHeader: "true",
label.TraefikFrontendPassTLSCert: "true",
label.TraefikFrontendPriority: "666",
label.TraefikFrontendRedirectEntryPoint: "https",
label.TraefikFrontendRedirectRegex: "nope",
label.TraefikFrontendRedirectReplacement: "nope",
label.TraefikFrontendRule: "Host:traefik.io",
label.TraefikFrontendWhitelistSourceRange: "10.10.10.10",
label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
label.TraefikFrontendEntryPoints: "http,https",
label.TraefikFrontendPassHostHeader: "true",
label.TraefikFrontendPassTLSCert: "true",
label.TraefikFrontendPriority: "666",
label.TraefikFrontendRedirectEntryPoint: "https",
label.TraefikFrontendRedirectRegex: "nope",
label.TraefikFrontendRedirectReplacement: "nope",
label.TraefikFrontendRule: "Host:traefik.io",
label.TraefikFrontendWhiteListSourceRange: "10.10.10.10",
label.TraefikFrontendWhiteListUseXForwardedFor: "true",
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",
@ -193,8 +194,9 @@ func TestSwarmBuildConfiguration(t *testing.T) {
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
},
WhitelistSourceRange: []string{
"10.10.10.10",
WhiteList: &types.WhiteList{
SourceRange: []string{"10.10.10.10"},
UseXForwardedFor: true,
},
Headers: &types.Headers{
CustomRequestHeaders: map[string]string{

View file

@ -76,16 +76,17 @@ func TestSegmentBuildConfiguration(t *testing.T) {
label.Prefix + "sauternes." + label.SuffixProtocol: "https",
label.Prefix + "sauternes." + label.SuffixWeight: "12",
label.Prefix + "sauternes." + label.SuffixFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
label.Prefix + "sauternes." + label.SuffixFrontendEntryPoints: "http,https",
label.Prefix + "sauternes." + label.SuffixFrontendPassHostHeader: "true",
label.Prefix + "sauternes." + label.SuffixFrontendPassTLSCert: "true",
label.Prefix + "sauternes." + label.SuffixFrontendPriority: "666",
label.Prefix + "sauternes." + label.SuffixFrontendRedirectEntryPoint: "https",
label.Prefix + "sauternes." + label.SuffixFrontendRedirectRegex: "nope",
label.Prefix + "sauternes." + label.SuffixFrontendRedirectReplacement: "nope",
label.Prefix + "sauternes." + label.SuffixFrontendRedirectPermanent: "true",
label.Prefix + "sauternes." + label.SuffixFrontendWhitelistSourceRange: "10.10.10.10",
label.Prefix + "sauternes." + label.SuffixFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
label.Prefix + "sauternes." + label.SuffixFrontendEntryPoints: "http,https",
label.Prefix + "sauternes." + label.SuffixFrontendPassHostHeader: "true",
label.Prefix + "sauternes." + label.SuffixFrontendPassTLSCert: "true",
label.Prefix + "sauternes." + label.SuffixFrontendPriority: "666",
label.Prefix + "sauternes." + label.SuffixFrontendRedirectEntryPoint: "https",
label.Prefix + "sauternes." + label.SuffixFrontendRedirectRegex: "nope",
label.Prefix + "sauternes." + label.SuffixFrontendRedirectReplacement: "nope",
label.Prefix + "sauternes." + label.SuffixFrontendRedirectPermanent: "true",
label.Prefix + "sauternes." + label.SuffixFrontendWhiteListSourceRange: "10.10.10.10",
label.Prefix + "sauternes." + label.SuffixFrontendWhiteListUseXForwardedFor: "true",
label.Prefix + "sauternes." + label.SuffixFrontendRequestHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
label.Prefix + "sauternes." + label.SuffixFrontendResponseHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
@ -144,8 +145,9 @@ func TestSegmentBuildConfiguration(t *testing.T) {
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
},
WhitelistSourceRange: []string{
"10.10.10.10",
WhiteList: &types.WhiteList{
SourceRange: []string{"10.10.10.10"},
UseXForwardedFor: true,
},
Headers: &types.Headers{
CustomRequestHeaders: map[string]string{

View file

@ -103,7 +103,7 @@ func (p *Provider) buildConfigurationV1(containersInspected []dockerData) *types
"getServiceWeight": getFuncServiceStringLabelV1(label.SuffixWeight, label.DefaultWeight),
// Services - Frontend functions
"getServiceEntryPoints": getFuncServiceSliceStringLabelV1(label.SuffixFrontendEntryPoints),
"getServiceWhitelistSourceRange": getFuncServiceSliceStringLabelV1(label.SuffixFrontendWhitelistSourceRange),
"getServiceWhitelistSourceRange": getFuncServiceSliceStringLabelV1(label.SuffixFrontendWhiteListSourceRange),
"getServiceBasicAuth": getFuncServiceSliceStringLabelV1(label.SuffixFrontendAuthBasic),
"getServiceFrontendRule": p.getServiceFrontendRuleV1,
"getServicePassHostHeader": getFuncServiceBoolLabelV1(label.SuffixFrontendPassHostHeader, label.DefaultPassHostHeaderBool),

View file

@ -48,18 +48,18 @@ func (p *Provider) buildConfiguration(services map[string][]ecsInstance) (*types
"getHealthCheckInterval": getFuncFirstStringValue(label.TraefikBackendHealthCheckInterval, ""),
// Frontend functions
"filterFrontends": filterFrontends,
"getFrontendRule": p.getFrontendRule,
"getPassHostHeader": getFuncBoolValue(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": getFuncBoolValue(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getPriority": getFuncIntValue(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
"getBasicAuth": getFuncSliceString(label.TraefikFrontendAuthBasic),
"getEntryPoints": getFuncSliceString(label.TraefikFrontendEntryPoints),
"getWhitelistSourceRange": getFuncSliceString(label.TraefikFrontendWhitelistSourceRange),
"getRedirect": getRedirect,
"getErrorPages": getErrorPages,
"getRateLimit": getRateLimit,
"getHeaders": getHeaders,
"filterFrontends": filterFrontends,
"getFrontendRule": p.getFrontendRule,
"getPassHostHeader": getFuncBoolValue(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": getFuncBoolValue(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getPriority": getFuncIntValue(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
"getBasicAuth": getFuncSliceString(label.TraefikFrontendAuthBasic),
"getEntryPoints": getFuncSliceString(label.TraefikFrontendEntryPoints),
"getRedirect": getRedirect,
"getErrorPages": getErrorPages,
"getRateLimit": getRateLimit,
"getHeaders": getHeaders,
"getWhiteList": getWhiteList,
}
return p.GetConfiguration("templates/ecs.tmpl", ecsFuncMap, struct {
Services map[string][]ecsInstance
@ -211,6 +211,18 @@ func getServers(instances []ecsInstance) map[string]types.Server {
return servers
}
func getWhiteList(instance ecsInstance) *types.WhiteList {
ranges := getSliceString(instance, label.TraefikFrontendWhiteListSourceRange)
if len(ranges) > 0 {
return &types.WhiteList{
SourceRange: ranges,
UseXForwardedFor: getBoolValue(instance, label.TraefikFrontendWhiteListUseXForwardedFor, false),
}
}
return nil
}
func getRedirect(instance ecsInstance) *types.Redirect {
permanent := getBoolValue(instance, label.TraefikFrontendRedirectPermanent, false)

View file

@ -142,17 +142,18 @@ func TestBuildConfiguration(t *testing.T) {
label.TraefikBackendBufferingMemRequestBodyBytes: aws.String("2097152"),
label.TraefikBackendBufferingRetryExpression: aws.String("IsNetworkError() && Attempts() <= 2"),
label.TraefikFrontendAuthBasic: aws.String("test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"),
label.TraefikFrontendEntryPoints: aws.String("http,https"),
label.TraefikFrontendPassHostHeader: aws.String("true"),
label.TraefikFrontendPassTLSCert: aws.String("true"),
label.TraefikFrontendPriority: aws.String("666"),
label.TraefikFrontendRedirectEntryPoint: aws.String("https"),
label.TraefikFrontendRedirectRegex: aws.String("nope"),
label.TraefikFrontendRedirectReplacement: aws.String("nope"),
label.TraefikFrontendRedirectPermanent: aws.String("true"),
label.TraefikFrontendRule: aws.String("Host:traefik.io"),
label.TraefikFrontendWhitelistSourceRange: aws.String("10.10.10.10"),
label.TraefikFrontendAuthBasic: aws.String("test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"),
label.TraefikFrontendEntryPoints: aws.String("http,https"),
label.TraefikFrontendPassHostHeader: aws.String("true"),
label.TraefikFrontendPassTLSCert: aws.String("true"),
label.TraefikFrontendPriority: aws.String("666"),
label.TraefikFrontendRedirectEntryPoint: aws.String("https"),
label.TraefikFrontendRedirectRegex: aws.String("nope"),
label.TraefikFrontendRedirectReplacement: aws.String("nope"),
label.TraefikFrontendRedirectPermanent: aws.String("true"),
label.TraefikFrontendRule: aws.String("Host:traefik.io"),
label.TraefikFrontendWhiteListSourceRange: aws.String("10.10.10.10"),
label.TraefikFrontendWhiteListUseXForwardedFor: aws.String("true"),
label.TraefikFrontendRequestHeaders: aws.String("Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8"),
label.TraefikFrontendResponseHeaders: aws.String("Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8"),
@ -257,8 +258,9 @@ func TestBuildConfiguration(t *testing.T) {
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
},
WhitelistSourceRange: []string{
"10.10.10.10",
WhiteList: &types.WhiteList{
SourceRange: []string{"10.10.10.10"},
UseXForwardedFor: true,
},
Headers: &types.Headers{
CustomRequestHeaders: map[string]string{
@ -1115,6 +1117,74 @@ func TestGetServers(t *testing.T) {
}
}
func TestWhiteList(t *testing.T) {
testCases := []struct {
desc string
instance ecsInstance
expected *types.WhiteList
}{
{
desc: "should return nil when no white list labels",
instance: ecsInstance{
containerDefinition: &ecs.ContainerDefinition{
DockerLabels: map[string]*string{},
},
},
expected: nil,
},
{
desc: "should return a struct when only range",
instance: ecsInstance{
containerDefinition: &ecs.ContainerDefinition{
DockerLabels: map[string]*string{
label.TraefikFrontendWhiteListSourceRange: aws.String("10.10.10.10"),
}},
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: false,
},
},
{
desc: "should return a struct when range and UseXForwardedFor",
instance: ecsInstance{
containerDefinition: &ecs.ContainerDefinition{
DockerLabels: map[string]*string{
label.TraefikFrontendWhiteListSourceRange: aws.String("10.10.10.10"),
label.TraefikFrontendWhiteListUseXForwardedFor: aws.String("true"),
}},
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: true,
},
},
{
desc: "should return nil when only UseXForwardedFor",
instance: ecsInstance{
containerDefinition: &ecs.ContainerDefinition{
DockerLabels: map[string]*string{
label.TraefikFrontendWhiteListUseXForwardedFor: aws.String("true"),
}},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getWhiteList(test.instance)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetRedirect(t *testing.T) {
testCases := []struct {
desc string

View file

@ -5,31 +5,32 @@ import (
)
const (
annotationKubernetesIngressClass = "kubernetes.io/ingress.class"
annotationKubernetesAuthRealm = "ingress.kubernetes.io/auth-realm"
annotationKubernetesAuthType = "ingress.kubernetes.io/auth-type"
annotationKubernetesAuthSecret = "ingress.kubernetes.io/auth-secret"
annotationKubernetesRewriteTarget = "ingress.kubernetes.io/rewrite-target"
annotationKubernetesWhitelistSourceRange = "ingress.kubernetes.io/whitelist-source-range"
annotationKubernetesPreserveHost = "ingress.kubernetes.io/preserve-host"
annotationKubernetesPassTLSCert = "ingress.kubernetes.io/pass-tls-cert"
annotationKubernetesFrontendEntryPoints = "ingress.kubernetes.io/frontend-entry-points"
annotationKubernetesPriority = "ingress.kubernetes.io/priority"
annotationKubernetesCircuitBreakerExpression = "ingress.kubernetes.io/circuit-breaker-expression"
annotationKubernetesLoadBalancerMethod = "ingress.kubernetes.io/load-balancer-method"
annotationKubernetesAffinity = "ingress.kubernetes.io/affinity"
annotationKubernetesSessionCookieName = "ingress.kubernetes.io/session-cookie-name"
annotationKubernetesRuleType = "ingress.kubernetes.io/rule-type"
annotationKubernetesRedirectEntryPoint = "ingress.kubernetes.io/redirect-entry-point"
annotationKubernetesRedirectPermanent = "ingress.kubernetes.io/redirect-permanent"
annotationKubernetesRedirectRegex = "ingress.kubernetes.io/redirect-regex"
annotationKubernetesRedirectReplacement = "ingress.kubernetes.io/redirect-replacement"
annotationKubernetesMaxConnAmount = "ingress.kubernetes.io/max-conn-amount"
annotationKubernetesMaxConnExtractorFunc = "ingress.kubernetes.io/max-conn-extractor-func"
annotationKubernetesRateLimit = "ingress.kubernetes.io/rate-limit"
annotationKubernetesErrorPages = "ingress.kubernetes.io/error-pages"
annotationKubernetesBuffering = "ingress.kubernetes.io/buffering"
annotationKubernetesAppRoot = "ingress.kubernetes.io/app-root"
annotationKubernetesIngressClass = "kubernetes.io/ingress.class"
annotationKubernetesAuthRealm = "ingress.kubernetes.io/auth-realm"
annotationKubernetesAuthType = "ingress.kubernetes.io/auth-type"
annotationKubernetesAuthSecret = "ingress.kubernetes.io/auth-secret"
annotationKubernetesRewriteTarget = "ingress.kubernetes.io/rewrite-target"
annotationKubernetesWhiteListSourceRange = "ingress.kubernetes.io/whitelist-source-range"
annotationKubernetesWhiteListUseXForwardedFor = "ingress.kubernetes.io/whitelist-x-forwarded-for"
annotationKubernetesPreserveHost = "ingress.kubernetes.io/preserve-host"
annotationKubernetesPassTLSCert = "ingress.kubernetes.io/pass-tls-cert"
annotationKubernetesFrontendEntryPoints = "ingress.kubernetes.io/frontend-entry-points"
annotationKubernetesPriority = "ingress.kubernetes.io/priority"
annotationKubernetesCircuitBreakerExpression = "ingress.kubernetes.io/circuit-breaker-expression"
annotationKubernetesLoadBalancerMethod = "ingress.kubernetes.io/load-balancer-method"
annotationKubernetesAffinity = "ingress.kubernetes.io/affinity"
annotationKubernetesSessionCookieName = "ingress.kubernetes.io/session-cookie-name"
annotationKubernetesRuleType = "ingress.kubernetes.io/rule-type"
annotationKubernetesRedirectEntryPoint = "ingress.kubernetes.io/redirect-entry-point"
annotationKubernetesRedirectPermanent = "ingress.kubernetes.io/redirect-permanent"
annotationKubernetesRedirectRegex = "ingress.kubernetes.io/redirect-regex"
annotationKubernetesRedirectReplacement = "ingress.kubernetes.io/redirect-replacement"
annotationKubernetesMaxConnAmount = "ingress.kubernetes.io/max-conn-amount"
annotationKubernetesMaxConnExtractorFunc = "ingress.kubernetes.io/max-conn-extractor-func"
annotationKubernetesRateLimit = "ingress.kubernetes.io/rate-limit"
annotationKubernetesErrorPages = "ingress.kubernetes.io/error-pages"
annotationKubernetesBuffering = "ingress.kubernetes.io/buffering"
annotationKubernetesAppRoot = "ingress.kubernetes.io/app-root"
annotationKubernetesSSLRedirect = "ingress.kubernetes.io/ssl-redirect"
annotationKubernetesHSTSMaxAge = "ingress.kubernetes.io/hsts-max-age"

View file

@ -202,9 +202,13 @@ func basicAuth(auth ...string) func(*types.Frontend) {
}
}
func whitelistSourceRange(ranges ...string) func(*types.Frontend) {
func whiteList(useXFF bool, ranges ...string) func(*types.Frontend) {
return func(f *types.Frontend) {
f.WhitelistSourceRange = ranges
if f.WhiteList == nil {
f.WhiteList = &types.WhiteList{}
}
f.WhiteList.UseXForwardedFor = useXFF
f.WhiteList.SourceRange = ranges
}
}

View file

@ -200,21 +200,20 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
passTLSCert := getBoolValue(i.Annotations, annotationKubernetesPassTLSCert, p.EnablePassTLSCert)
priority := getIntValue(i.Annotations, annotationKubernetesPriority, 0)
entryPoints := getSliceStringValue(i.Annotations, annotationKubernetesFrontendEntryPoints)
whitelistSourceRange := getSliceStringValue(i.Annotations, annotationKubernetesWhitelistSourceRange)
templateObjects.Frontends[baseName] = &types.Frontend{
Backend: baseName,
PassHostHeader: passHostHeader,
PassTLSCert: passTLSCert,
Routes: make(map[string]types.Route),
Priority: priority,
BasicAuth: basicAuthCreds,
WhitelistSourceRange: whitelistSourceRange,
Redirect: getFrontendRedirect(i),
EntryPoints: entryPoints,
Headers: getHeader(i),
Errors: getErrorPages(i),
RateLimit: getRateLimit(i),
Backend: baseName,
PassHostHeader: passHostHeader,
PassTLSCert: passTLSCert,
Routes: make(map[string]types.Route),
Priority: priority,
BasicAuth: basicAuthCreds,
WhiteList: getWhiteList(i),
Redirect: getFrontendRedirect(i),
EntryPoints: entryPoints,
Headers: getHeader(i),
Errors: getErrorPages(i),
RateLimit: getRateLimit(i),
}
}
@ -457,7 +456,7 @@ func getTLS(ingress *extensionsv1beta1.Ingress, k8sClient Client) ([]*tls.Config
func endpointPortNumber(servicePort corev1.ServicePort, endpointPorts []corev1.EndpointPort) int {
if len(endpointPorts) > 0 {
//name is optional if there is only one port
// name is optional if there is only one port
port := endpointPorts[0]
for _, endpointPort := range endpointPorts {
if servicePort.Name == endpointPort.Name {
@ -510,6 +509,18 @@ func getFrontendRedirect(i *extensionsv1beta1.Ingress) *types.Redirect {
return nil
}
func getWhiteList(i *extensionsv1beta1.Ingress) *types.WhiteList {
ranges := getSliceStringValue(i.Annotations, annotationKubernetesWhiteListSourceRange)
if len(ranges) <= 0 {
return nil
}
return &types.WhiteList{
SourceRange: ranges,
UseXForwardedFor: getBoolValue(i.Annotations, annotationKubernetesWhiteListUseXForwardedFor, false),
}
}
func getBuffering(service *corev1.Service) *types.Buffering {
var buffering *types.Buffering

View file

@ -665,7 +665,8 @@ func TestIngressAnnotations(t *testing.T) {
),
buildIngress(
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"),
iRules(
iRule(
iHost("test"),
@ -984,7 +985,7 @@ rateset:
),
frontend("test/whitelist-source-range",
passHostHeader(),
whitelistSourceRange("1.1.1.1/24", "1234:abcd::42/32"),
whiteList(true, "1.1.1.1/24", "1234:abcd::42/32"),
routes(
route("/whitelist-source-range", "PathPrefix:/whitelist-source-range"),
route("test", "Host:test")),

View file

@ -22,29 +22,30 @@ const (
pathBackendBufferingMemRequestBodyBytes = pathBackendBuffering + "memrequestbodybytes"
pathBackendBufferingRetryExpression = pathBackendBuffering + "retryexpression"
pathFrontends = "/frontends/"
pathFrontendBackend = "/backend"
pathFrontendPriority = "/priority"
pathFrontendPassHostHeaderDeprecated = "/passHostHeader" // Deprecated
pathFrontendPassHostHeader = "/passhostheader"
pathFrontendPassTLSCert = "/passtlscert"
pathFrontendWhiteListSourceRange = "/whitelistsourcerange"
pathFrontendBasicAuth = "/basicauth"
pathFrontendEntryPoints = "/entrypoints"
pathFrontendRedirectEntryPoint = "/redirect/entrypoint"
pathFrontendRedirectRegex = "/redirect/regex"
pathFrontendRedirectReplacement = "/redirect/replacement"
pathFrontendRedirectPermanent = "/redirect/permanent"
pathFrontendErrorPages = "/errors/"
pathFrontendErrorPagesBackend = "/backend"
pathFrontendErrorPagesQuery = "/query"
pathFrontendErrorPagesStatus = "/status"
pathFrontendRateLimit = "/ratelimit/"
pathFrontendRateLimitRateSet = pathFrontendRateLimit + "rateset/"
pathFrontendRateLimitExtractorFunc = pathFrontendRateLimit + "extractorfunc"
pathFrontendRateLimitPeriod = "/period"
pathFrontendRateLimitAverage = "/average"
pathFrontendRateLimitBurst = "/burst"
pathFrontends = "/frontends/"
pathFrontendBackend = "/backend"
pathFrontendPriority = "/priority"
pathFrontendPassHostHeaderDeprecated = "/passHostHeader" // Deprecated
pathFrontendPassHostHeader = "/passhostheader"
pathFrontendPassTLSCert = "/passtlscert"
pathFrontendWhiteListSourceRange = "/whitelist/sourcerange"
pathFrontendWhiteListUseXForwardedFor = "/whitelist/usexforwardedfor"
pathFrontendBasicAuth = "/basicauth"
pathFrontendEntryPoints = "/entrypoints"
pathFrontendRedirectEntryPoint = "/redirect/entrypoint"
pathFrontendRedirectRegex = "/redirect/regex"
pathFrontendRedirectReplacement = "/redirect/replacement"
pathFrontendRedirectPermanent = "/redirect/permanent"
pathFrontendErrorPages = "/errors/"
pathFrontendErrorPagesBackend = "/backend"
pathFrontendErrorPagesQuery = "/query"
pathFrontendErrorPagesStatus = "/status"
pathFrontendRateLimit = "/ratelimit/"
pathFrontendRateLimitRateSet = pathFrontendRateLimit + "rateset/"
pathFrontendRateLimitExtractorFunc = pathFrontendRateLimit + "extractorfunc"
pathFrontendRateLimitPeriod = "/period"
pathFrontendRateLimitAverage = "/average"
pathFrontendRateLimitBurst = "/burst"
pathFrontendCustomRequestHeaders = "/headers/customrequestheaders/"
pathFrontendCustomResponseHeaders = "/headers/customresponseheaders/"

View file

@ -41,18 +41,18 @@ func (p *Provider) buildConfiguration() *types.Configuration {
"getTLSSection": p.getTLSSection,
// Frontend functions
"getBackendName": p.getFuncString(pathFrontendBackend, ""),
"getPriority": p.getFuncInt(pathFrontendPriority, label.DefaultFrontendPriorityInt),
"getPassHostHeader": p.getPassHostHeader(),
"getPassTLSCert": p.getFuncBool(pathFrontendPassTLSCert, label.DefaultPassTLSCert),
"getEntryPoints": p.getFuncList(pathFrontendEntryPoints),
"getWhitelistSourceRange": p.getFuncList(pathFrontendWhiteListSourceRange),
"getBasicAuth": p.getFuncList(pathFrontendBasicAuth),
"getRoutes": p.getRoutes,
"getRedirect": p.getRedirect,
"getErrorPages": p.getErrorPages,
"getRateLimit": p.getRateLimit,
"getHeaders": p.getHeaders,
"getBackendName": p.getFuncString(pathFrontendBackend, ""),
"getPriority": p.getFuncInt(pathFrontendPriority, label.DefaultFrontendPriorityInt),
"getPassHostHeader": p.getPassHostHeader(),
"getPassTLSCert": p.getFuncBool(pathFrontendPassTLSCert, label.DefaultPassTLSCert),
"getEntryPoints": p.getFuncList(pathFrontendEntryPoints),
"getBasicAuth": p.getFuncList(pathFrontendBasicAuth),
"getRoutes": p.getRoutes,
"getRedirect": p.getRedirect,
"getErrorPages": p.getErrorPages,
"getRateLimit": p.getRateLimit,
"getHeaders": p.getHeaders,
"getWhiteList": p.getWhiteList,
// Backend functions
"getServers": p.getServers,
@ -125,6 +125,19 @@ func (p *Provider) getStickinessCookieName(rootPath string) string {
return p.get("", rootPath, pathBackendLoadBalancerStickinessCookieName)
}
func (p *Provider) getWhiteList(rootPath string) *types.WhiteList {
ranges := p.getList(rootPath, pathFrontendWhiteListSourceRange)
if len(ranges) > 0 {
return &types.WhiteList{
SourceRange: ranges,
UseXForwardedFor: p.getBool(false, rootPath, pathFrontendWhiteListUseXForwardedFor),
}
}
return nil
}
func (p *Provider) getRedirect(rootPath string) *types.Redirect {
permanent := p.getBool(false, rootPath, pathFrontendRedirectPermanent)

View file

@ -91,6 +91,7 @@ func TestProviderBuildConfiguration(t *testing.T) {
withPair(pathFrontendPassTLSCert, "true"),
withPair(pathFrontendEntryPoints, "http,https"),
withPair(pathFrontendWhiteListSourceRange, "1.1.1.1/24, 1234:abcd::42/32"),
withPair(pathFrontendWhiteListUseXForwardedFor, "true"),
withPair(pathFrontendBasicAuth, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/, test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"),
withPair(pathFrontendRedirectEntryPoint, "https"),
withPair(pathFrontendRedirectRegex, "nope"),
@ -180,12 +181,15 @@ func TestProviderBuildConfiguration(t *testing.T) {
},
Frontends: map[string]*types.Frontend{
"frontend1": {
Priority: 6,
EntryPoints: []string{"http", "https"},
Backend: "backend1",
PassTLSCert: true,
WhitelistSourceRange: []string{"1.1.1.1/24", "1234:abcd::42/32"},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
Priority: 6,
EntryPoints: []string{"http", "https"},
Backend: "backend1",
PassTLSCert: true,
WhiteList: &types.WhiteList{
SourceRange: []string{"1.1.1.1/24", "1234:abcd::42/32"},
UseXForwardedFor: true,
},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
Redirect: &types.Redirect{
EntryPoint: "https",
Permanent: true,
@ -1031,6 +1035,68 @@ func TestProviderHasStickinessLabel(t *testing.T) {
}
}
func TestWhiteList(t *testing.T) {
testCases := []struct {
desc string
rootPath string
kvPairs []*store.KVPair
expected *types.WhiteList
}{
{
desc: "should return nil when no white list labels",
rootPath: "traefik/frontends/foo",
expected: nil,
},
{
desc: "should return a struct when only range",
rootPath: "traefik/frontends/foo",
kvPairs: filler("traefik",
frontend("foo",
withPair(pathFrontendWhiteListSourceRange, "10.10.10.10"))),
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: false,
},
},
{
desc: "should return a struct when range and UseXForwardedFor",
rootPath: "traefik/frontends/foo",
kvPairs: filler("traefik",
frontend("foo",
withPair(pathFrontendWhiteListSourceRange, "10.10.10.10"),
withPair(pathFrontendWhiteListUseXForwardedFor, "true"))),
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: true,
},
},
{
desc: "should return nil when only UseXForwardedFor",
rootPath: "traefik/frontends/foo",
kvPairs: filler("traefik",
frontend("foo",
withPair(pathFrontendWhiteListUseXForwardedFor, "true"))),
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
p := newProviderMock(test.kvPairs)
actual := p.getWhiteList(test.rootPath)
assert.Equal(t, test.expected, actual)
})
}
}
func TestProviderGetRedirect(t *testing.T) {
testCases := []struct {
desc string

View file

@ -65,7 +65,10 @@ const (
SuffixFrontendRedirectReplacement = "frontend.redirect.replacement"
SuffixFrontendRedirectPermanent = "frontend.redirect.permanent"
SuffixFrontendRule = "frontend.rule"
SuffixFrontendWhitelistSourceRange = "frontend.whitelistSourceRange"
SuffixFrontendWhitelistSourceRange = "frontend.whitelistSourceRange" // Deprecated
SuffixFrontendWhiteList = "frontend.whiteList."
SuffixFrontendWhiteListSourceRange = SuffixFrontendWhiteList + "sourceRange"
SuffixFrontendWhiteListUseXForwardedFor = SuffixFrontendWhiteList + "useXForwardedFor"
TraefikDomain = Prefix + SuffixDomain
TraefikEnable = Prefix + SuffixEnable
TraefikPort = Prefix + SuffixPort
@ -106,7 +109,9 @@ const (
TraefikFrontendRedirectReplacement = Prefix + SuffixFrontendRedirectReplacement
TraefikFrontendRedirectPermanent = Prefix + SuffixFrontendRedirectPermanent
TraefikFrontendRule = Prefix + SuffixFrontendRule
TraefikFrontendWhitelistSourceRange = Prefix + SuffixFrontendWhitelistSourceRange
TraefikFrontendWhitelistSourceRange = Prefix + SuffixFrontendWhitelistSourceRange // Deprecated
TraefikFrontendWhiteListSourceRange = Prefix + SuffixFrontendWhiteListSourceRange
TraefikFrontendWhiteListUseXForwardedFor = Prefix + SuffixFrontendWhiteListUseXForwardedFor
TraefikFrontendRequestHeaders = Prefix + SuffixFrontendRequestHeaders
TraefikFrontendResponseHeaders = Prefix + SuffixFrontendResponseHeaders
TraefikFrontendAllowedHosts = Prefix + SuffixFrontendHeadersAllowedHosts

View file

@ -67,20 +67,23 @@ func (p *Provider) buildConfiguration() *types.Configuration {
"getHealthCheckInterval": getFuncString(label.TraefikBackendHealthCheckInterval, ""),
// Frontend functions
"getServiceNames": getServiceNames,
"getServiceNameSuffix": getServiceNameSuffix,
"getPassHostHeader": getFuncBoolService(label.SuffixFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": getFuncBoolService(label.SuffixFrontendPassTLSCert, label.DefaultPassTLSCert),
"getPriority": getFuncIntService(label.SuffixFrontendPriority, label.DefaultFrontendPriorityInt),
"getEntryPoints": getFuncSliceStringService(label.SuffixFrontendEntryPoints),
"getFrontendRule": p.getFrontendRule,
"getFrontendName": p.getFrontendName,
"getBasicAuth": getFuncSliceStringService(label.SuffixFrontendAuthBasic),
"getServiceNames": getServiceNames,
"getServiceNameSuffix": getServiceNameSuffix,
"getPassHostHeader": getFuncBoolService(label.SuffixFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": getFuncBoolService(label.SuffixFrontendPassTLSCert, label.DefaultPassTLSCert),
"getPriority": getFuncIntService(label.SuffixFrontendPriority, label.DefaultFrontendPriorityInt),
"getEntryPoints": getFuncSliceStringService(label.SuffixFrontendEntryPoints),
"getFrontendRule": p.getFrontendRule,
"getFrontendName": p.getFrontendName,
"getBasicAuth": getFuncSliceStringService(label.SuffixFrontendAuthBasic),
"getRedirect": getRedirect,
"getErrorPages": getErrorPages,
"getRateLimit": getRateLimit,
"getHeaders": getHeaders,
"getWhiteList": getWhiteList,
// TODO Deprecated [breaking]
"getWhitelistSourceRange": getFuncSliceStringService(label.SuffixFrontendWhitelistSourceRange),
"getRedirect": getRedirect,
"getErrorPages": getErrorPages,
"getRateLimit": getRateLimit,
"getHeaders": getHeaders,
}
v := url.Values{}
@ -486,6 +489,20 @@ func (p *Provider) getServers(application marathon.Application, serviceName stri
return servers
}
func getWhiteList(application marathon.Application, serviceName string) *types.WhiteList {
labels := getLabels(application, serviceName)
ranges := label.GetSliceStringValue(labels, getLabelName(serviceName, label.SuffixFrontendWhiteListSourceRange))
if len(ranges) > 0 {
return &types.WhiteList{
SourceRange: ranges,
UseXForwardedFor: label.GetBoolValue(labels, getLabelName(serviceName, label.SuffixFrontendWhiteListUseXForwardedFor), false),
}
}
return nil
}
func getRedirect(application marathon.Application, serviceName string) *types.Redirect {
labels := getLabels(application, serviceName)

View file

@ -206,7 +206,8 @@ func TestBuildConfigurationNonAPIErrors(t *testing.T) {
withLabel(label.TraefikFrontendRedirectReplacement, "nope"),
withLabel(label.TraefikFrontendRedirectPermanent, "true"),
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.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"),
@ -268,8 +269,9 @@ func TestBuildConfigurationNonAPIErrors(t *testing.T) {
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
},
WhitelistSourceRange: []string{
"10.10.10.10",
WhiteList: &types.WhiteList{
SourceRange: []string{"10.10.10.10"},
UseXForwardedFor: true,
},
Headers: &types.Headers{
CustomRequestHeaders: map[string]string{
@ -498,7 +500,7 @@ func TestBuildConfigurationServicesNonAPIErrors(t *testing.T) {
application: application(
appPorts(80, 81),
//withLabel(label.TraefikBackend, "foobar"),
// withLabel(label.TraefikBackend, "foobar"),
withLabel(label.TraefikBackendCircuitBreakerExpression, "NetworkErrorRatio() > 0.5"),
withLabel(label.TraefikBackendHealthCheckPath, "/health"),
@ -530,7 +532,8 @@ func TestBuildConfigurationServicesNonAPIErrors(t *testing.T) {
withServiceLabel(label.TraefikFrontendRedirectReplacement, "nope", "containous"),
withServiceLabel(label.TraefikFrontendRedirectPermanent, "true", "containous"),
withServiceLabel(label.TraefikFrontendRule, "Host:traefik.io", "containous"),
withServiceLabel(label.TraefikFrontendWhitelistSourceRange, "10.10.10.10", "containous"),
withServiceLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10", "containous"),
withServiceLabel(label.TraefikFrontendWhiteListUseXForwardedFor, "true", "containous"),
withServiceLabel(label.TraefikFrontendRequestHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", "containous"),
withServiceLabel(label.TraefikFrontendResponseHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", "containous"),
@ -591,8 +594,9 @@ func TestBuildConfigurationServicesNonAPIErrors(t *testing.T) {
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
},
WhitelistSourceRange: []string{
"10.10.10.10",
WhiteList: &types.WhiteList{
SourceRange: []string{"10.10.10.10"},
UseXForwardedFor: true,
},
Headers: &types.Headers{
CustomRequestHeaders: map[string]string{
@ -1627,6 +1631,107 @@ func TestGetServers(t *testing.T) {
}
}
func TestWhiteList(t *testing.T) {
testCases := []struct {
desc string
application marathon.Application
serviceName string
expected *types.WhiteList
}{
{
desc: "should return nil when no white list labels",
application: application(
appPorts(80),
),
expected: nil,
},
{
desc: "should return a struct when only range",
application: application(
appPorts(80),
withLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10"),
),
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: false,
},
},
{
desc: "should return a struct when range and UseXForwardedFor",
application: application(
appPorts(80),
withLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10"),
withLabel(label.TraefikFrontendWhiteListUseXForwardedFor, "true"),
),
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: true,
},
},
{
desc: "should return nil when only UseXForwardedFor",
application: application(
appPorts(80),
withLabel(label.TraefikFrontendWhiteListUseXForwardedFor, "true"),
),
expected: nil,
},
// Service
{
desc: "should return a struct when only range on service",
application: application(
appPorts(80),
withLabel(label.Prefix+"containous."+label.SuffixFrontendWhiteListSourceRange, "10.10.10.10"),
),
serviceName: "containous",
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: false,
},
},
{
desc: "should return a struct when range and UseXForwardedFor on service",
application: application(
appPorts(80),
withLabel(label.Prefix+"containous."+label.SuffixFrontendWhiteListSourceRange, "10.10.10.10"),
withLabel(label.Prefix+"containous."+label.SuffixFrontendWhiteListUseXForwardedFor, "true"),
),
serviceName: "containous",
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: true,
},
},
{
desc: "should return nil when only UseXForwardedFor on service",
application: application(
appPorts(80),
withLabel(label.Prefix+"containous."+label.SuffixFrontendWhiteListUseXForwardedFor, "true"),
),
serviceName: "containous",
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getWhiteList(test.application, test.serviceName)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetRedirect(t *testing.T) {
testCases := []struct {
desc string

View file

@ -41,18 +41,18 @@ func (p *Provider) buildConfiguration(tasks []state.Task) *types.Configuration {
"getPort": p.getPort,
// Frontend functions
"getFrontEndName": getFrontendName,
"getEntryPoints": getFuncSliceStringValue(label.TraefikFrontendEntryPoints),
"getBasicAuth": getFuncSliceStringValue(label.TraefikFrontendAuthBasic),
"getWhitelistSourceRange": getFuncSliceStringValue(label.TraefikFrontendWhitelistSourceRange),
"getPriority": getFuncStringValue(label.TraefikFrontendPriority, label.DefaultFrontendPriority),
"getPassHostHeader": getFuncBoolValue(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": getFuncBoolValue(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getFrontendRule": p.getFrontendRule,
"getRedirect": getRedirect,
"getErrorPages": getErrorPages,
"getRateLimit": getRateLimit,
"getHeaders": getHeaders,
"getFrontEndName": getFrontendName,
"getEntryPoints": getFuncSliceStringValue(label.TraefikFrontendEntryPoints),
"getBasicAuth": getFuncSliceStringValue(label.TraefikFrontendAuthBasic),
"getPriority": getFuncStringValue(label.TraefikFrontendPriority, label.DefaultFrontendPriority),
"getPassHostHeader": getFuncBoolValue(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": getFuncBoolValue(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getFrontendRule": p.getFrontendRule,
"getRedirect": getRedirect,
"getErrorPages": getErrorPages,
"getRateLimit": getRateLimit,
"getHeaders": getHeaders,
"getWhiteList": getWhiteList,
// TODO Deprecated [breaking]
"getFrontendBackend": getBackendName,
@ -337,6 +337,18 @@ func (p *Provider) getServers(tasks []state.Task) map[string]types.Server {
return servers
}
func getWhiteList(task state.Task) *types.WhiteList {
ranges := getSliceStringValue(task, label.TraefikFrontendWhiteListSourceRange)
if len(ranges) > 0 {
return &types.WhiteList{
SourceRange: ranges,
UseXForwardedFor: getBoolValue(task, label.TraefikFrontendWhiteListUseXForwardedFor, false),
}
}
return nil
}
func getRedirect(task state.Task) *types.Redirect {
permanent := getBoolValue(task, label.TraefikFrontendRedirectPermanent, false)

View file

@ -148,7 +148,8 @@ func TestBuildConfiguration(t *testing.T) {
withLabel(label.TraefikFrontendRedirectReplacement, "nope"),
withLabel(label.TraefikFrontendRedirectPermanent, "true"),
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.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"),
@ -212,8 +213,9 @@ func TestBuildConfiguration(t *testing.T) {
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
},
WhitelistSourceRange: []string{
"10.10.10.10",
WhiteList: &types.WhiteList{
SourceRange: []string{"10.10.10.10"},
UseXForwardedFor: true,
},
Headers: &types.Headers{
CustomRequestHeaders: map[string]string{
@ -953,6 +955,75 @@ func TestGetServers(t *testing.T) {
}
}
func TestWhiteList(t *testing.T) {
testCases := []struct {
desc string
task state.Task
expected *types.WhiteList
}{
{
desc: "should return nil when no white list labels",
task: aTask("ID1",
withIP("10.10.10.10"),
withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
withDefaultStatus(),
),
expected: nil,
},
{
desc: "should return a struct when only range",
task: aTask("ID1",
withLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10"),
withIP("10.10.10.10"),
withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
withDefaultStatus(),
),
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: false,
},
},
{
desc: "should return a struct when range and UseXForwardedFor",
task: aTask("ID1",
withLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10"),
withLabel(label.TraefikFrontendWhiteListUseXForwardedFor, "true"),
withIP("10.10.10.10"),
withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
withDefaultStatus(),
),
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: true,
},
},
{
desc: "should return nil when only UseXForwardedFor",
task: aTask("ID1",
withLabel(label.TraefikFrontendWhiteListUseXForwardedFor, "true"),
withIP("10.10.10.10"),
withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
withDefaultStatus(),
),
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getWhiteList(test.task)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetRedirect(t *testing.T) {
testCases := []struct {
desc string

View file

@ -55,20 +55,22 @@ func (p *Provider) buildConfiguration(services []rancherData) *types.Configurati
"getStickinessCookieName": getFuncString(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName),
// Frontend functions
"getBackend": getBackendName, // TODO Deprecated [breaking] replaced by getBackendName
"getBackendName": getBackendName,
"getFrontendRule": p.getFrontendRule,
"getPriority": getFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
"getPassHostHeader": getFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": getFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getEntryPoints": getFuncSliceString(label.TraefikFrontendEntryPoints),
"getBasicAuth": getFuncSliceString(label.TraefikFrontendAuthBasic),
"getWhitelistSourceRange": getFuncSliceString(label.TraefikFrontendWhitelistSourceRange),
"getBackend": getBackendName, // TODO Deprecated [breaking] replaced by getBackendName
"getBackendName": getBackendName,
"getFrontendRule": p.getFrontendRule,
"getPriority": getFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
"getPassHostHeader": getFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": getFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getEntryPoints": getFuncSliceString(label.TraefikFrontendEntryPoints),
"getBasicAuth": getFuncSliceString(label.TraefikFrontendAuthBasic),
"getErrorPages": getErrorPages,
"getRateLimit": getRateLimit,
"getRedirect": getRedirect,
"getHeaders": getHeaders,
"getWhiteList": getWhiteList,
"getErrorPages": getErrorPages,
"getRateLimit": getRateLimit,
"getRedirect": getRedirect,
"getHeaders": getHeaders,
// TODO Deprecated [breaking]
"getWhitelistSourceRange": getFuncSliceString(label.TraefikFrontendWhitelistSourceRange),
}
// filter services
@ -271,6 +273,19 @@ func getServers(service rancherData) map[string]types.Server {
return servers
}
func getWhiteList(service rancherData) *types.WhiteList {
ranges := label.GetSliceStringValue(service.Labels, label.TraefikFrontendWhiteListSourceRange)
if len(ranges) > 0 {
return &types.WhiteList{
SourceRange: ranges,
UseXForwardedFor: label.GetBoolValue(service.Labels, label.TraefikFrontendWhiteListUseXForwardedFor, false),
}
}
return nil
}
func getRedirect(service rancherData) *types.Redirect {
permanent := label.GetBoolValue(service.Labels, label.TraefikFrontendRedirectPermanent, false)

View file

@ -56,17 +56,18 @@ func TestProviderBuildConfiguration(t *testing.T) {
label.TraefikBackendBufferingMemRequestBodyBytes: "2097152",
label.TraefikBackendBufferingRetryExpression: "IsNetworkError() && Attempts() <= 2",
label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
label.TraefikFrontendEntryPoints: "http,https",
label.TraefikFrontendPassHostHeader: "true",
label.TraefikFrontendPassTLSCert: "true",
label.TraefikFrontendPriority: "666",
label.TraefikFrontendRedirectEntryPoint: "https",
label.TraefikFrontendRedirectRegex: "nope",
label.TraefikFrontendRedirectReplacement: "nope",
label.TraefikFrontendRedirectPermanent: "true",
label.TraefikFrontendRule: "Host:traefik.io",
label.TraefikFrontendWhitelistSourceRange: "10.10.10.10",
label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
label.TraefikFrontendEntryPoints: "http,https",
label.TraefikFrontendPassHostHeader: "true",
label.TraefikFrontendPassTLSCert: "true",
label.TraefikFrontendPriority: "666",
label.TraefikFrontendRedirectEntryPoint: "https",
label.TraefikFrontendRedirectRegex: "nope",
label.TraefikFrontendRedirectReplacement: "nope",
label.TraefikFrontendRedirectPermanent: "true",
label.TraefikFrontendRule: "Host:traefik.io",
label.TraefikFrontendWhiteListSourceRange: "10.10.10.10",
label.TraefikFrontendWhiteListUseXForwardedFor: "true",
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",
@ -128,8 +129,11 @@ func TestProviderBuildConfiguration(t *testing.T) {
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
},
WhitelistSourceRange: []string{
"10.10.10.10",
WhiteList: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: true,
},
Headers: &types.Headers{
CustomRequestHeaders: map[string]string{
@ -1001,6 +1005,78 @@ func TestGetServers(t *testing.T) {
}
}
func TestWhiteList(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected *types.WhiteList
}{
{
desc: "should return nil when no white list labels",
service: rancherData{
Labels: map[string]string{},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should return a struct when only range",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendWhiteListSourceRange: "10.10.10.10",
},
Health: "healthy",
State: "active",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: false,
},
},
{
desc: "should return a struct when range and UseXForwardedFor",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendWhiteListSourceRange: "10.10.10.10",
label.TraefikFrontendWhiteListUseXForwardedFor: "true",
},
Health: "healthy",
State: "active",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: true,
},
},
{
desc: "should return nil when only UseXForwardedFor",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendWhiteListUseXForwardedFor: "true",
},
Health: "healthy",
State: "active",
},
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getWhiteList(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetRedirect(t *testing.T) {
testCases := []struct {
desc string

View file

@ -1,7 +1,6 @@
package server
import (
"net"
"net/http"
"os"
@ -12,7 +11,7 @@ import (
// NewHeaderRewriter Create a header rewriter
func NewHeaderRewriter(trustedIPs []string, insecure bool) (forward.ReqRewriter, error) {
IPs, err := whitelist.NewIP(trustedIPs, insecure)
IPs, err := whitelist.NewIP(trustedIPs, insecure, true)
if err != nil {
return nil, err
}
@ -38,14 +37,7 @@ type headerRewriter struct {
}
func (h *headerRewriter) Rewrite(req *http.Request) {
clientIP, _, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
log.Error(err)
h.secureRewriter.Rewrite(req)
return
}
authorized, _, err := h.ips.Contains(clientIP)
authorized, _, err := h.ips.IsAuthorized(req)
if err != nil {
log.Error(err)
h.secureRewriter.Rewrite(req)

View file

@ -326,8 +326,8 @@ func (s *Server) setupServerEntryPoint(newServerEntryPointName string, newServer
}
serverMiddlewares = append(serverMiddlewares, s.globalConfiguration.API.StatsRecorder)
}
}
if s.globalConfiguration.EntryPoints[newServerEntryPointName].Auth != nil {
authMiddleware, err := mauth.NewAuthenticator(s.globalConfiguration.EntryPoints[newServerEntryPointName].Auth, s.tracingMiddleware)
if err != nil {
@ -336,17 +336,22 @@ func (s *Server) setupServerEntryPoint(newServerEntryPointName string, newServer
serverMiddlewares = append(serverMiddlewares, s.wrapNegroniHandlerWithAccessLog(authMiddleware, fmt.Sprintf("Auth for entrypoint %s", newServerEntryPointName)))
serverInternalMiddlewares = append(serverInternalMiddlewares, authMiddleware)
}
if s.globalConfiguration.EntryPoints[newServerEntryPointName].Compress {
serverMiddlewares = append(serverMiddlewares, &middlewares.Compress{})
}
if len(s.globalConfiguration.EntryPoints[newServerEntryPointName].WhitelistSourceRange) > 0 {
ipWhitelistMiddleware, err := middlewares.NewIPWhitelister(s.globalConfiguration.EntryPoints[newServerEntryPointName].WhitelistSourceRange)
if err != nil {
log.Fatal("Error starting server: ", err)
}
ipWhitelistMiddleware, err := buildIPWhiteLister(
s.globalConfiguration.EntryPoints[newServerEntryPointName].WhiteList,
s.globalConfiguration.EntryPoints[newServerEntryPointName].WhitelistSourceRange)
if err != nil {
log.Fatal("Error starting server: ", err)
}
if ipWhitelistMiddleware != nil {
serverMiddlewares = append(serverMiddlewares, s.wrapNegroniHandlerWithAccessLog(ipWhitelistMiddleware, fmt.Sprintf("ipwhitelister for entrypoint %s", newServerEntryPointName)))
serverInternalMiddlewares = append(serverInternalMiddlewares, ipWhitelistMiddleware)
}
newSrv, listener, err := s.prepareServer(newServerEntryPointName, s.globalConfiguration.EntryPoints[newServerEntryPointName], newServerEntryPoint.httpRouter, serverMiddlewares, serverInternalMiddlewares)
if err != nil {
log.Fatal("Error preparing server: ", err)
@ -794,7 +799,7 @@ func (s *Server) prepareServer(entryPointName string, entryPoint *configuration.
}
if entryPoint.ProxyProtocol != nil {
IPs, err := whitelist.NewIP(entryPoint.ProxyProtocol.TrustedIPs, entryPoint.ProxyProtocol.Insecure)
IPs, err := whitelist.NewIP(entryPoint.ProxyProtocol.TrustedIPs, entryPoint.ProxyProtocol.Insecure, false)
if err != nil {
return nil, nil, fmt.Errorf("error creating whitelist: %s", err)
}
@ -1137,13 +1142,16 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura
n.Use(middlewares.NewBackendMetricsMiddleware(s.metricsRegistry, frontend.Backend))
}
ipWhitelistMiddleware, err := configureIPWhitelistMiddleware(frontend.WhitelistSourceRange)
ipWhitelistMiddleware, err := buildIPWhiteLister(frontend.WhiteList, frontend.WhitelistSourceRange)
if err != nil {
log.Errorf("Error creating IP Whitelister: %s", err)
} else if ipWhitelistMiddleware != nil {
ipWhitelistMiddleware = s.wrapNegroniHandlerWithAccessLog(ipWhitelistMiddleware, fmt.Sprintf("ipwhitelister for %s", frontendName))
n.Use(s.tracingMiddleware.NewNegroniHandlerWrapper("IP whitelist", ipWhitelistMiddleware, false))
log.Infof("Configured IP Whitelists: %s", frontend.WhitelistSourceRange)
n.Use(
s.tracingMiddleware.NewNegroniHandlerWrapper(
"IP whitelist",
s.wrapNegroniHandlerWithAccessLog(ipWhitelistMiddleware, fmt.Sprintf("ipwhitelister for %s", frontendName)),
false))
log.Debugf("Configured IP Whitelists: %s", frontend.WhitelistSourceRange)
}
if frontend.Redirect != nil && entryPointName != frontend.Redirect.EntryPoint {
@ -1256,18 +1264,13 @@ func (s *Server) configureLBServers(lb healthcheck.LoadBalancer, config *types.C
return nil
}
func configureIPWhitelistMiddleware(whitelistSourceRanges []string) (negroni.Handler, error) {
if len(whitelistSourceRanges) > 0 {
ipSourceRanges := whitelistSourceRanges
ipWhitelistMiddleware, err := middlewares.NewIPWhitelister(ipSourceRanges)
if err != nil {
return nil, err
}
return ipWhitelistMiddleware, nil
func buildIPWhiteLister(whiteList *types.WhiteList, wlRange []string) (*middlewares.IPWhiteLister, error) {
if whiteList != nil &&
len(whiteList.SourceRange) > 0 {
return middlewares.NewIPWhiteLister(whiteList.SourceRange, whiteList.UseXForwardedFor)
} else if len(wlRange) > 0 {
return middlewares.NewIPWhiteLister(wlRange, false)
}
return nil, nil
}

View file

@ -571,48 +571,75 @@ func TestServerParseHealthCheckOptions(t *testing.T) {
}
}
func TestNewServerWithWhitelistSourceRange(t *testing.T) {
cases := []struct {
func TestBuildIPWhiteLister(t *testing.T) {
testCases := []struct {
desc string
whitelistStrings []string
whitelistSourceRange []string
whiteList *types.WhiteList
middlewareConfigured bool
errMessage string
}{
{
desc: "no whitelists configued",
whitelistStrings: nil,
desc: "no whitelists configured",
whitelistSourceRange: nil,
middlewareConfigured: false,
errMessage: "",
}, {
desc: "whitelists configued",
whitelistStrings: []string{
},
{
desc: "whitelists configured (deprecated)",
whitelistSourceRange: []string{
"1.2.3.4/24",
"fe80::/16",
},
middlewareConfigured: true,
errMessage: "",
}, {
desc: "invalid whitelists configued",
whitelistStrings: []string{
},
{
desc: "invalid whitelists configured (deprecated)",
whitelistSourceRange: []string{
"foo",
},
middlewareConfigured: false,
errMessage: "parsing CIDR whitelist [foo]: parsing CIDR whitelist <nil>: invalid CIDR address: foo",
errMessage: "parsing CIDR whitelist [foo]: parsing CIDR white list <nil>: invalid CIDR address: foo",
},
{
desc: "whitelists configured",
whiteList: &types.WhiteList{
SourceRange: []string{
"1.2.3.4/24",
"fe80::/16",
},
UseXForwardedFor: false,
},
middlewareConfigured: true,
errMessage: "",
},
{
desc: "invalid whitelists configured (deprecated)",
whiteList: &types.WhiteList{
SourceRange: []string{
"foo",
},
UseXForwardedFor: false,
},
middlewareConfigured: false,
errMessage: "parsing CIDR whitelist [foo]: parsing CIDR white list <nil>: invalid CIDR address: foo",
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.desc, func(t *testing.T) {
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
middleware, err := configureIPWhitelistMiddleware(tc.whitelistStrings)
if tc.errMessage != "" {
require.EqualError(t, err, tc.errMessage)
middleware, err := buildIPWhiteLister(test.whiteList, test.whitelistSourceRange)
if test.errMessage != "" {
require.EqualError(t, err, test.errMessage)
} else {
assert.NoError(t, err)
if tc.middlewareConfigured {
if test.middlewareConfigured {
require.NotNil(t, middleware, "not expected middleware to be configured")
} else {
require.Nil(t, middleware, "expected middleware to be configured")

View file

@ -66,17 +66,19 @@
"{{.}}",
{{end}}]
{{ $whitelistSourceRange := getWhitelistSourceRange $service.Attributes }}
{{if $whitelistSourceRange }}
whitelistSourceRange = [{{range $whitelistSourceRange }}
"{{.}}",
{{end}}]
{{end}}
basicAuth = [{{range getBasicAuth $service.Attributes }}
"{{.}}",
{{end}}]
{{ $whitelist := getWhiteList $service.Attributes }}
{{if $whitelist }}
[frontends."frontend-{{ $service.ServiceName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}",
{{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
{{ $redirect := getRedirect $service.Attributes }}
{{if $redirect }}
[frontends."frontend-{{ $service.ServiceName }}".redirect]

View file

@ -67,17 +67,19 @@
"{{.}}",
{{end}}]
{{ $whitelistSourceRange := getWhitelistSourceRange $container.SegmentLabels }}
{{if $whitelistSourceRange }}
whitelistSourceRange = [{{range $whitelistSourceRange }}
"{{.}}",
{{end}}]
{{end}}
basicAuth = [{{range getBasicAuth $container.SegmentLabels }}
"{{.}}",
{{end}}]
{{ $whitelist := getWhiteList $container.SegmentLabels }}
{{if $whitelist }}
[frontends."frontend-{{ $frontendName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}",
{{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
{{ $redirect := getRedirect $container.SegmentLabels }}
{{if $redirect }}
[frontends."frontend-{{ $frontendName }}".redirect]

View file

@ -66,18 +66,19 @@
"{{.}}",
{{end}}]
{{ $whitelistSourceRange := getWhitelistSourceRange $instance }}
{{if $whitelistSourceRange }}
whitelistSourceRange = [{{range $whitelistSourceRange }}
"{{.}}",
{{end}}]
{{end}}
basicAuth = [{{range getBasicAuth $instance }}
"{{.}}",
{{end}}]
{{ $whitelist := getWhiteList $instance }}
{{if $whitelist }}
[frontends."frontend-{{ $serviceName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}",
{{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
{{ $redirect := getRedirect $instance }}
{{if $redirect }}
[frontends."frontend-{{ $serviceName }}".redirect]

View file

@ -56,9 +56,13 @@
"{{.}}",
{{end}}]
whitelistSourceRange = [{{range $frontend.WhitelistSourceRange }}
"{{.}}",
{{end}}]
{{if $frontend.WhiteList }}
[frontends."{{ $frontendName }}".whiteList]
sourceRange = [{{range $frontend.WhiteList.SourceRange }}
"{{.}}",
{{end}}]
useXForwardedFor = {{ $frontend.WhiteList.UseXForwardedFor }}
{{end}}
{{if $frontend.Redirect }}
[frontends."{{ $frontendName }}".redirect]

View file

@ -66,17 +66,19 @@
"{{.}}",
{{end}}]
{{ $whitelistSourceRange := getWhitelistSourceRange $frontend }}
{{if $whitelistSourceRange }}
whitelistSourceRange = [{{range $whitelistSourceRange }}
"{{.}}",
{{end}}]
{{end}}
basicAuth = [{{range getBasicAuth $frontend }}
"{{.}}",
{{end}}]
{{ $whitelist := getWhiteList $frontend }}
{{if $whitelist }}
[frontends."{{ $frontendName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}",
{{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
{{ $redirect := getRedirect $frontend }}
{{if $redirect }}
[frontends."{{ $frontendName }}".redirect]

View file

@ -73,17 +73,19 @@
"{{.}}",
{{end}}]
{{ $whitelistSourceRange := getWhitelistSourceRange $app $serviceName }}
{{if $whitelistSourceRange }}
whitelistSourceRange = [{{range $whitelistSourceRange }}
"{{.}}",
{{end}}]
{{end}}
basicAuth = [{{range getBasicAuth $app $serviceName }}
"{{.}}",
{{end}}]
{{ $whitelist := getWhiteList $app $serviceName }}
{{if $whitelist }}
[frontends."{{ $frontendName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}",
{{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
{{ $redirect := getRedirect $app $serviceName }}
{{if $redirect }}
[frontends."{{ $frontendName }}".redirect]

View file

@ -69,17 +69,19 @@
"{{.}}",
{{end}}]
{{ $whitelistSourceRange := getWhitelistSourceRange $app }}
{{if $whitelistSourceRange }}
whitelistSourceRange = [{{range $whitelistSourceRange }}
"{{.}}",
{{end}}]
{{end}}
basicAuth = [{{range getBasicAuth $app }}
"{{.}}",
{{end}}]
{{ $whitelist := getWhiteList $app }}
{{if $whitelist }}
[frontends."frontend-{{ $frontendName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}",
{{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
{{ $redirect := getRedirect $app }}
{{if $redirect }}
[frontends."frontend-{{ $frontendName }}".redirect]

View file

@ -67,17 +67,19 @@
"{{.}}",
{{end}}]
{{ $whitelistSourceRange := getWhitelistSourceRange $service }}
{{if $whitelistSourceRange }}
whitelistSourceRange = [{{range $whitelistSourceRange }}
"{{.}}",
{{end}}]
{{end}}
basicAuth = [{{range getBasicAuth $service }}
"{{.}}",
{{end}}]
{{ $whitelist := getWhiteList $service }}
{{if $whitelist }}
[frontends."frontend-{{ $frontendName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}",
{{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
{{ $redirect := getRedirect $service }}
{{if $redirect }}
[frontends."frontend-{{ $frontendName }}".redirect]

View file

@ -61,6 +61,12 @@ type Buffering struct {
RetryExpression string `json:"retryExpression,omitempty"`
}
// WhiteList contains white list configuration.
type WhiteList struct {
SourceRange []string `json:"sourceRange,omitempty"`
UseXForwardedFor bool `json:"useXForwardedFor,omitempty" export:"true"`
}
// HealthCheck holds HealthCheck configuration
type HealthCheck struct {
Path string `json:"path,omitempty"`
@ -89,7 +95,7 @@ type ServerRoute struct {
ReplacePathRegex string
}
//ErrorPage holds custom error page configuration
// ErrorPage holds custom error page configuration
type ErrorPage struct {
Status []string `json:"status,omitempty"`
Backend string `json:"backend,omitempty"`
@ -172,7 +178,8 @@ type Frontend struct {
PassTLSCert bool `json:"passTLSCert,omitempty"`
Priority int `json:"priority"`
BasicAuth []string `json:"basicAuth"`
WhitelistSourceRange []string `json:"whitelistSourceRange,omitempty"`
WhitelistSourceRange []string `json:"whitelistSourceRange,omitempty"` // Deprecated
WhiteList *WhiteList `json:"whiteList,omitempty"`
Headers *Headers `json:"headers,omitempty"`
Errors map[string]*ErrorPage `json:"errors,omitempty"`
RateLimit *RateLimit `json:"ratelimit,omitempty"`
@ -547,7 +554,7 @@ func NewHTTPCodeRanges(strBlocks []string) (HTTPCodeRanges, error) {
var blocks HTTPCodeRanges
for _, block := range strBlocks {
codes := strings.Split(block, "-")
//if only a single HTTP code was configured, assume the best and create the correct configuration on the user's behalf
// if only a single HTTP code was configured, assume the best and create the correct configuration on the user's behalf
if len(codes) == 1 {
codes = append(codes, codes[0])
}

View file

@ -3,36 +3,45 @@ package whitelist
import (
"fmt"
"net"
"net/http"
"github.com/pkg/errors"
)
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
whiteListsIPs []*net.IP
whiteListsNet []*net.IPNet
insecure bool
useXForwardedFor bool
}
// NewIP builds a new IP given a list of CIDR-Strings to whitelist
func NewIP(whitelistStrings []string, insecure bool) (*IP, error) {
if len(whitelistStrings) == 0 && !insecure {
// 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}
ip := IP{
insecure: insecure,
useXForwardedFor: useXForwardedFor,
}
if !insecure {
for _, whitelistString := range whitelistStrings {
ipAddr := net.ParseIP(whitelistString)
if ipAddr != nil {
for _, ipMask := range whiteList {
if ipAddr := net.ParseIP(ipMask); ipAddr != nil {
ip.whiteListsIPs = append(ip.whiteListsIPs, &ipAddr)
} else {
_, whitelist, err := net.ParseCIDR(whitelistString)
_, ipAddr, err := net.ParseCIDR(ipMask)
if err != nil {
return nil, fmt.Errorf("parsing CIDR whitelist %s: %v", whitelist, err)
return nil, fmt.Errorf("parsing CIDR white list %s: %v", ipAddr, err)
}
ip.whiteListsNet = append(ip.whiteListsNet, whitelist)
ip.whiteListsNet = append(ip.whiteListsNet, ipAddr)
}
}
}
@ -40,13 +49,38 @@ func NewIP(whitelistStrings []string, insecure bool) (*IP, error) {
return &ip, nil
}
// Contains checks if provided address is in the white list
func (ip *IP) Contains(addr string) (bool, net.IP, error) {
// IsAuthorized checks if provided request is authorized by the white list
func (ip *IP) IsAuthorized(req *http.Request) (bool, net.IP, error) {
if ip.insecure {
return true, nil, nil
}
ipAddr, err := ipFromRemoteAddr(addr)
if ip.useXForwardedFor {
xFFs := req.Header[XForwardedFor]
if len(xFFs) > 1 {
for _, xFF := range xFFs {
ok, i, err := ip.contains(parseHost(xFF))
if err != nil {
return false, nil, err
}
if ok {
return ok, i, nil
}
}
}
}
host, _, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
return false, nil, err
}
return ip.contains(host)
}
// contains checks if provided address is in the white list
func (ip *IP) contains(addr string) (bool, net.IP, error) {
ipAddr, err := parseIP(addr)
if err != nil {
return false, nil, fmt.Errorf("unable to parse address: %s: %s", addr, err)
}
@ -76,7 +110,7 @@ func (ip *IP) ContainsIP(addr net.IP) (bool, error) {
return false, nil
}
func ipFromRemoteAddr(addr string) (net.IP, error) {
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)
@ -84,3 +118,11 @@ func ipFromRemoteAddr(addr string) (net.IP, error) {
return userIP, nil
}
func parseHost(addr string) string {
host, _, err := net.SplitHostPort(addr)
if err != nil {
return addr
}
return host
}

View file

@ -2,55 +2,151 @@ 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
expected 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"},
expected: 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"},
expected: 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"},
expected: 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"},
expected: 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"},
expected: 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"},
expected: 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"},
expected: 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"},
expected: 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)
authorized, ips, err := whiteLister.IsAuthorized(req)
require.NoError(t, err)
assert.NotNil(t, ips)
assert.Equal(t, test.expected, authorized)
})
}
}
func TestNew(t *testing.T) {
cases := []struct {
desc string
whitelistStrings []string
whiteList []string
expectedWhitelists []*net.IPNet
errMessage string
}{
{
desc: "nil whitelist",
whitelistStrings: nil,
whiteList: nil,
expectedWhitelists: nil,
errMessage: "no white list provided",
}, {
desc: "empty whitelist",
whitelistStrings: []string{},
whiteList: []string{},
expectedWhitelists: nil,
errMessage: "no white list provided",
}, {
desc: "whitelist containing empty string",
whitelistStrings: []string{
whiteList: []string{
"1.2.3.4/24",
"",
"fe80::/16",
},
expectedWhitelists: nil,
errMessage: "parsing CIDR whitelist <nil>: invalid CIDR address: ",
errMessage: "parsing CIDR white list <nil>: invalid CIDR address: ",
}, {
desc: "whitelist containing only an empty string",
whitelistStrings: []string{
whiteList: []string{
"",
},
expectedWhitelists: nil,
errMessage: "parsing CIDR whitelist <nil>: invalid CIDR address: ",
errMessage: "parsing CIDR white list <nil>: invalid CIDR address: ",
}, {
desc: "whitelist containing an invalid string",
whitelistStrings: []string{
whiteList: []string{
"foo",
},
expectedWhitelists: nil,
errMessage: "parsing CIDR whitelist <nil>: invalid CIDR address: foo",
errMessage: "parsing CIDR white list <nil>: invalid CIDR address: foo",
}, {
desc: "IPv4 & IPv6 whitelist",
whitelistStrings: []string{
whiteList: []string{
"1.2.3.4/24",
"fe80::/16",
},
@ -61,7 +157,7 @@ func TestNew(t *testing.T) {
errMessage: "",
}, {
desc: "IPv4 only",
whitelistStrings: []string{
whiteList: []string{
"127.0.0.1/8",
},
expectedWhitelists: []*net.IPNet{
@ -75,12 +171,12 @@ func TestNew(t *testing.T) {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
whitelister, err := NewIP(test.whitelistStrings, false)
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 {
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())
@ -98,10 +194,8 @@ func TestContainsIsAllowed(t *testing.T) {
rejectIPs []string
}{
{
desc: "IPv4",
whitelistStrings: []string{
"1.2.3.4/24",
},
desc: "IPv4",
whitelistStrings: []string{"1.2.3.4/24"},
passIPs: []string{
"1.2.3.1",
"1.2.3.32",
@ -116,13 +210,9 @@ func TestContainsIsAllowed(t *testing.T) {
},
},
{
desc: "IPv4 single IP",
whitelistStrings: []string{
"8.8.8.8",
},
passIPs: []string{
"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",
@ -133,13 +223,9 @@ func TestContainsIsAllowed(t *testing.T) {
},
},
{
desc: "IPv4 Net single IP",
whitelistStrings: []string{
"8.8.8.8/32",
},
passIPs: []string{
"8.8.8.8",
},
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",
@ -150,11 +236,8 @@ func TestContainsIsAllowed(t *testing.T) {
},
},
{
desc: "multiple IPv4",
whitelistStrings: []string{
"1.2.3.4/24",
"8.8.8.8/8",
},
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",
@ -174,10 +257,8 @@ func TestContainsIsAllowed(t *testing.T) {
},
},
{
desc: "IPv6",
whitelistStrings: []string{
"2a03:4000:6:d080::/64",
},
desc: "IPv6",
whitelistStrings: []string{"2a03:4000:6:d080::/64"},
passIPs: []string{
"2a03:4000:6:d080::",
"2a03:4000:6:d080::1",
@ -192,13 +273,9 @@ func TestContainsIsAllowed(t *testing.T) {
},
},
{
desc: "IPv6 single IP",
whitelistStrings: []string{
"2a03:4000:6:d080::42/128",
},
passIPs: []string{
"2a03:4000:6:d080::42",
},
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",
@ -206,11 +283,8 @@ func TestContainsIsAllowed(t *testing.T) {
},
},
{
desc: "multiple IPv6",
whitelistStrings: []string{
"2a03:4000:6:d080::/64",
"fe80::/16",
},
desc: "multiple IPv6",
whitelistStrings: []string{"2a03:4000:6:d080::/64", "fe80::/16"},
passIPs: []string{
"2a03:4000:6:d080::",
"2a03:4000:6:d080::1",
@ -227,13 +301,8 @@ func TestContainsIsAllowed(t *testing.T) {
},
},
{
desc: "multiple IPv6 & IPv4",
whitelistStrings: []string{
"2a03:4000:6:d080::/64",
"fe80::/16",
"1.2.3.4/24",
"8.8.8.8/8",
},
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",
@ -263,11 +332,9 @@ func TestContainsIsAllowed(t *testing.T) {
},
},
{
desc: "broken IP-addresses",
whitelistStrings: []string{
"127.0.0.1/32",
},
passIPs: nil,
desc: "broken IP-addresses",
whitelistStrings: []string{"127.0.0.1/32"},
passIPs: nil,
},
}
@ -276,23 +343,23 @@ func TestContainsIsAllowed(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
whiteLister, err := NewIP(test.whitelistStrings, false)
whiteLister, err := NewIP(test.whitelistStrings, false, false)
require.NoError(t, err)
require.NotNil(t, whiteLister)
for _, testIP := range test.passIPs {
allowed, ip, err := whiteLister.Contains(testIP)
allowed, ip, err := whiteLister.contains(testIP)
require.NoError(t, err)
require.NotNil(t, ip, err)
assert.True(t, allowed, testIP+" should have passed "+test.desc)
assert.Truef(t, allowed, "%s should have passed.", testIP)
}
for _, testIP := range test.rejectIPs {
allowed, ip, err := whiteLister.Contains(testIP)
allowed, ip, err := whiteLister.contains(testIP)
require.NoError(t, err)
require.NotNil(t, ip, err)
assert.False(t, allowed, testIP+" should not have passed "+test.desc)
assert.Falsef(t, allowed, "%s should not have passed.", testIP)
}
})
}
@ -300,7 +367,7 @@ func TestContainsIsAllowed(t *testing.T) {
func TestContainsInsecure(t *testing.T) {
mustNewIP := func(whitelistStrings []string, insecure bool) *IP {
ip, err := NewIP(whitelistStrings, insecure)
ip, err := NewIP(whitelistStrings, insecure, false)
if err != nil {
t.Fatal(err)
}
@ -338,7 +405,7 @@ func TestContainsInsecure(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
ok, _, err := test.whiteLister.Contains(test.ip)
ok, _, err := test.whiteLister.contains(test.ip)
require.NoError(t, err)
assert.Equal(t, test.expected, ok)
@ -355,13 +422,25 @@ func TestContainsBrokenIPs(t *testing.T) {
"\\&$§&/(",
}
whiteLister, err := NewIP([]string{"1.2.3.4/24"}, false)
whiteLister, err := NewIP([]string{"1.2.3.4/24"}, false, false)
require.NoError(t, err)
for _, testIP := range brokenIPs {
_, ip, err := whiteLister.Contains(testIP)
_, ip, err := whiteLister.contains(testIP)
assert.Error(t, err)
require.Nil(t, ip, 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
}