diff --git a/autogen/gentemplates/gen.go b/autogen/gentemplates/gen.go index e7e392d8f..935ed5c2f 100644 --- a/autogen/gentemplates/gen.go +++ b/autogen/gentemplates/gen.go @@ -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] diff --git a/configuration/configuration.go b/configuration/configuration.go index 3f2c9651e..083085f86 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -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 { diff --git a/configuration/entrypoints.go b/configuration/entrypoints.go index a73ef6100..9b8bc3f9c 100644 --- a/configuration/entrypoints.go +++ b/configuration/entrypoints.go @@ -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 { diff --git a/configuration/entrypoints_test.go b/configuration/entrypoints_test.go index 795a6e8c1..a528c04b0 100644 --- a/configuration/entrypoints_test.go +++ b/configuration/entrypoints_test.go @@ -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, diff --git a/docs/configuration/backends/consulcatalog.md b/docs/configuration/backends/consulcatalog.md index ca6938b82..94b56450a 100644 --- a/docs/configuration/backends/consulcatalog.md +++ b/docs/configuration/backends/consulcatalog.md @@ -109,7 +109,8 @@ Additional settings can be defined using Consul Catalog tags. | `.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.
Must be set with `traefik.frontend.redirect.regex`. | | `.frontend.redirect.permanent=true` | Return 301 instead of 302. | | `.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{{.ServiceName}}.{{.Domain}}`. | -| `.frontend.whitelistSourceRange=RANGE` | List of IP-Ranges which are allowed to access.
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. | +| `.frontend.whiteList.sourceRange=RANGE` | List of IP-Ranges which are allowed to access.
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. | +| `.frontend.whiteList.useXForwardedFor=true` | Use `X-Forwarded-For` header as valid source of IP for the white list. | ### Custom Headers diff --git a/docs/configuration/backends/docker.md b/docs/configuration/backends/docker.md index 8a84f1a0a..25f332396 100644 --- a/docs/configuration/backends/docker.md +++ b/docs/configuration/backends/docker.md @@ -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.
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.
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`.
Overrides `defaultEntryPoints` | -| `traefik.frontend.errors..backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. | -| `traefik.frontend.errors..query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. | -| `traefik.frontend.errors..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..period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. | -| `traefik.frontend.rateLimit.rateSet..average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. | -| `traefik.frontend.rateLimit.rateSet..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.
Must be set with `traefik.frontend.redirect.replacement`. | -| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.
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.
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.
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.
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`.
Overrides `defaultEntryPoints` | +| `traefik.frontend.errors..backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. | +| `traefik.frontend.errors..query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. | +| `traefik.frontend.errors..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..period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. | +| `traefik.frontend.rateLimit.rateSet..average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. | +| `traefik.frontend.rateLimit.rateSet..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.
Must be set with `traefik.frontend.redirect.replacement`. | +| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.
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.
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. | [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 `) otherwise it will randomly pick one (depending on how docker is returning them). @@ -303,7 +304,8 @@ Segment labels override the default behavior. | `traefik..frontend.redirect.replacement=http://mydomain/$1` | Overrides `traefik.frontend.redirect.replacement`. | | `traefik..frontend.redirect.permanent=true` | Return 301 instead of 302. | | `traefik..frontend.rule` | Overrides `traefik.frontend.rule`. | -| `traefik..frontend.whitelistSourceRange=RANGE` | Overrides `traefik.frontend.whitelistSourceRange`. | +| `traefik..frontend.whiteList.sourceRange=RANGE` | Overrides `traefik.frontend.whiteList.sourceRange`. | +| `traefik..frontend.whiteList.useXForwardedFor=true` | Overrides `traefik.frontend.whiteList.useXForwardedFor`. | #### Custom Headers diff --git a/docs/configuration/backends/ecs.md b/docs/configuration/backends/ecs.md index 780d8dc0f..ef2278f29 100644 --- a/docs/configuration/backends/ecs.md +++ b/docs/configuration/backends/ecs.md @@ -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.
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.
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.
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 diff --git a/docs/configuration/backends/file.md b/docs/configuration/backends/file.md index 5e6ff0c99..785155d17 100644 --- a/docs/configuration/backends/file.md +++ b/docs/configuration/backends/file.md @@ -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] diff --git a/docs/configuration/backends/marathon.md b/docs/configuration/backends/marathon.md index 3100b8926..e1f9c432c 100644 --- a/docs/configuration/backends/marathon.md +++ b/docs/configuration/backends/marathon.md @@ -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..period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. | -| `traefik.frontend.rateLimit.rateSet..average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. | -| `traefik.frontend.rateLimit.rateSet..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..period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. | +| `traefik.frontend.rateLimit.rateSet..average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. | +| `traefik.frontend.rateLimit.rateSet..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.
Must be set with `traefik.frontend.redirect.replacement`. | | `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.
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.
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.
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..frontend.redirect.permanent=true` | Return 301 instead of 302. | | `traefik..frontend.rule=EXP` | Overrides `traefik.frontend.rule`. Default: `{service_name}.{sub_domain}.{domain}` | | `traefik..frontend.whitelistSourceRange=RANGE` | Overrides `traefik.frontend.whitelistSourceRange`. | +| `traefik..frontend.whiteList.sourceRange=RANGE` | Overrides `traefik.frontend.whiteList.sourceRange`. | +| `traefik..frontend.whiteList.useXForwardedFor=true` | Use `X-Forwarded-For` header as valid source of IP for the white list. | #### Custom Headers diff --git a/docs/configuration/backends/mesos.md b/docs/configuration/backends/mesos.md index 923d4155d..229418729 100644 --- a/docs/configuration/backends/mesos.md +++ b/docs/configuration/backends/mesos.md @@ -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.
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.
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.
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 diff --git a/docs/configuration/backends/rancher.md b/docs/configuration/backends/rancher.md index 167b3e530..4743e7180 100644 --- a/docs/configuration/backends/rancher.md +++ b/docs/configuration/backends/rancher.md @@ -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.
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.
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.
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 diff --git a/docs/configuration/entrypoints.md b/docs/configuration/entrypoints.md index e4e75c793..10208916e 100644 --- a/docs/configuration/entrypoints.md +++ b/docs/configuration/entrypoints.md @@ -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 diff --git a/middlewares/ip_whitelister.go b/middlewares/ip_whitelister.go index 061c94e29..082c12348 100644 --- a/middlewares/ip_whitelister.go +++ b/middlewares/ip_whitelister.go @@ -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) } diff --git a/provider/consulcatalog/consul_catalog_config.go b/provider/consulcatalog/consul_catalog_config.go index 71e309c81..14ab512b6 100644 --- a/provider/consulcatalog/consul_catalog_config.go +++ b/provider/consulcatalog/consul_catalog_config.go @@ -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) diff --git a/provider/consulcatalog/consul_catalog_config_test.go b/provider/consulcatalog/consul_catalog_config_test.go index b51b41d99..8f8b475d0 100644 --- a/provider/consulcatalog/consul_catalog_config_test.go +++ b/provider/consulcatalog/consul_catalog_config_test.go @@ -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", diff --git a/provider/docker/config.go b/provider/docker/config.go index c584f07bb..9c2ea6e38 100644 --- a/provider/docker/config.go +++ b/provider/docker/config.go @@ -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) diff --git a/provider/docker/config_container_docker_test.go b/provider/docker/config_container_docker_test.go index ebe35f0b0..c088f653b 100644 --- a/provider/docker/config_container_docker_test.go +++ b/provider/docker/config_container_docker_test.go @@ -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) + }) + } +} diff --git a/provider/docker/config_container_swarm_test.go b/provider/docker/config_container_swarm_test.go index cae3468a4..4623dafd9 100644 --- a/provider/docker/config_container_swarm_test.go +++ b/provider/docker/config_container_swarm_test.go @@ -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{ diff --git a/provider/docker/config_segment_test.go b/provider/docker/config_segment_test.go index e1c10fe7b..babdc04d5 100644 --- a/provider/docker/config_segment_test.go +++ b/provider/docker/config_segment_test.go @@ -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{ diff --git a/provider/docker/deprecated_config.go b/provider/docker/deprecated_config.go index 0b0ca3890..9cbd29f52 100644 --- a/provider/docker/deprecated_config.go +++ b/provider/docker/deprecated_config.go @@ -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), diff --git a/provider/ecs/config.go b/provider/ecs/config.go index a802b2bd3..a61add0b1 100644 --- a/provider/ecs/config.go +++ b/provider/ecs/config.go @@ -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) diff --git a/provider/ecs/config_test.go b/provider/ecs/config_test.go index f1d981607..9860869b9 100644 --- a/provider/ecs/config_test.go +++ b/provider/ecs/config_test.go @@ -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 diff --git a/provider/kubernetes/annotations.go b/provider/kubernetes/annotations.go index ca3d0b899..9b2e6ab89 100644 --- a/provider/kubernetes/annotations.go +++ b/provider/kubernetes/annotations.go @@ -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" diff --git a/provider/kubernetes/builder_configuration_test.go b/provider/kubernetes/builder_configuration_test.go index 7427be3a5..b9b32f512 100644 --- a/provider/kubernetes/builder_configuration_test.go +++ b/provider/kubernetes/builder_configuration_test.go @@ -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 } } diff --git a/provider/kubernetes/kubernetes.go b/provider/kubernetes/kubernetes.go index 4b4cbd6cc..3c92bdfc0 100644 --- a/provider/kubernetes/kubernetes.go +++ b/provider/kubernetes/kubernetes.go @@ -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 diff --git a/provider/kubernetes/kubernetes_test.go b/provider/kubernetes/kubernetes_test.go index 164799804..74b11e20a 100644 --- a/provider/kubernetes/kubernetes_test.go +++ b/provider/kubernetes/kubernetes_test.go @@ -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")), diff --git a/provider/kv/keynames.go b/provider/kv/keynames.go index 964841d91..5d9268c46 100644 --- a/provider/kv/keynames.go +++ b/provider/kv/keynames.go @@ -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/" diff --git a/provider/kv/kv_config.go b/provider/kv/kv_config.go index d2c6fd8e9..2a629ffa6 100644 --- a/provider/kv/kv_config.go +++ b/provider/kv/kv_config.go @@ -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) diff --git a/provider/kv/kv_config_test.go b/provider/kv/kv_config_test.go index 7788937b1..ff3b35986 100644 --- a/provider/kv/kv_config_test.go +++ b/provider/kv/kv_config_test.go @@ -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 diff --git a/provider/label/names.go b/provider/label/names.go index ef13b4538..ae0aaaede 100644 --- a/provider/label/names.go +++ b/provider/label/names.go @@ -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 diff --git a/provider/marathon/config.go b/provider/marathon/config.go index 50938491c..a19128700 100644 --- a/provider/marathon/config.go +++ b/provider/marathon/config.go @@ -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) diff --git a/provider/marathon/config_test.go b/provider/marathon/config_test.go index fc696f51d..328f7ce02 100644 --- a/provider/marathon/config_test.go +++ b/provider/marathon/config_test.go @@ -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 diff --git a/provider/mesos/config.go b/provider/mesos/config.go index 6fd881017..43795edbd 100644 --- a/provider/mesos/config.go +++ b/provider/mesos/config.go @@ -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) diff --git a/provider/mesos/config_test.go b/provider/mesos/config_test.go index 3bcf3df5f..730f336b3 100644 --- a/provider/mesos/config_test.go +++ b/provider/mesos/config_test.go @@ -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 diff --git a/provider/rancher/config.go b/provider/rancher/config.go index 9d8ff5b6e..af1a1c06a 100644 --- a/provider/rancher/config.go +++ b/provider/rancher/config.go @@ -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) diff --git a/provider/rancher/config_test.go b/provider/rancher/config_test.go index 48c04413c..0a1d746f0 100644 --- a/provider/rancher/config_test.go +++ b/provider/rancher/config_test.go @@ -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 diff --git a/server/header_rewriter.go b/server/header_rewriter.go index 17c5a4257..e4531bc64 100644 --- a/server/header_rewriter.go +++ b/server/header_rewriter.go @@ -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) diff --git a/server/server.go b/server/server.go index 92cff0a25..34b128bd9 100644 --- a/server/server.go +++ b/server/server.go @@ -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 } diff --git a/server/server_test.go b/server/server_test.go index 27adf118e..b0d326d57 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -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 : invalid CIDR address: foo", + errMessage: "parsing CIDR whitelist [foo]: parsing CIDR white list : 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 : 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") diff --git a/templates/consul_catalog.tmpl b/templates/consul_catalog.tmpl index 8334f06a2..7638965f7 100644 --- a/templates/consul_catalog.tmpl +++ b/templates/consul_catalog.tmpl @@ -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] diff --git a/templates/docker.tmpl b/templates/docker.tmpl index 3592e9e94..06a47c663 100644 --- a/templates/docker.tmpl +++ b/templates/docker.tmpl @@ -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] diff --git a/templates/ecs.tmpl b/templates/ecs.tmpl index ee06e6a7b..0030e83c4 100644 --- a/templates/ecs.tmpl +++ b/templates/ecs.tmpl @@ -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] diff --git a/templates/kubernetes.tmpl b/templates/kubernetes.tmpl index 4d230dc58..e6af7b43a 100644 --- a/templates/kubernetes.tmpl +++ b/templates/kubernetes.tmpl @@ -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] diff --git a/templates/kv.tmpl b/templates/kv.tmpl index 4c2df182a..81e278266 100644 --- a/templates/kv.tmpl +++ b/templates/kv.tmpl @@ -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] diff --git a/templates/marathon.tmpl b/templates/marathon.tmpl index 74e882fa0..d8673515b 100644 --- a/templates/marathon.tmpl +++ b/templates/marathon.tmpl @@ -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] diff --git a/templates/mesos.tmpl b/templates/mesos.tmpl index e58b330b5..e71cc238e 100644 --- a/templates/mesos.tmpl +++ b/templates/mesos.tmpl @@ -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] diff --git a/templates/rancher.tmpl b/templates/rancher.tmpl index b8d610698..77384c7d4 100644 --- a/templates/rancher.tmpl +++ b/templates/rancher.tmpl @@ -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] diff --git a/types/types.go b/types/types.go index 24ccb6c70..518d5a365 100644 --- a/types/types.go +++ b/types/types.go @@ -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]) } diff --git a/whitelist/ip.go b/whitelist/ip.go index 200e7ae7c..3b1c4a0b7 100644 --- a/whitelist/ip.go +++ b/whitelist/ip.go @@ -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 +} diff --git a/whitelist/ip_test.go b/whitelist/ip_test.go index a80fe98d0..f4dc0f022 100644 --- a/whitelist/ip_test.go +++ b/whitelist/ip_test.go @@ -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 : invalid CIDR address: ", + errMessage: "parsing CIDR white list : invalid CIDR address: ", }, { desc: "whitelist containing only an empty string", - whitelistStrings: []string{ + whiteList: []string{ "", }, expectedWhitelists: nil, - errMessage: "parsing CIDR whitelist : invalid CIDR address: ", + errMessage: "parsing CIDR white list : invalid CIDR address: ", }, { desc: "whitelist containing an invalid string", - whitelistStrings: []string{ + whiteList: []string{ "foo", }, expectedWhitelists: nil, - errMessage: "parsing CIDR whitelist : invalid CIDR address: foo", + errMessage: "parsing CIDR white list : 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 }