Merge 'v1.4.0' into master

This commit is contained in:
Fernandez Ludovic 2017-10-16 23:10:44 +02:00
commit 7192aa86b5
38 changed files with 1165 additions and 327 deletions

View file

@ -1,5 +1,225 @@
# Change Log # Change Log
## [v1.4.0](https://github.com/containous/traefik/tree/v1.4.0) (2017-10-16)
[All Commits](https://github.com/containous/traefik/compare/v1.3.0-rc1...v1.4.0)
**Enhancements:**
- **[acme]** Display Traefik logs in integration tests ([#2114](https://github.com/containous/traefik/pull/2114) by [ldez](https://github.com/ldez))
- **[acme]** Make the ACME developments testing easier ([#1769](https://github.com/containous/traefik/pull/1769) by [nmengin](https://github.com/nmengin))
- **[acme]** contrib: Dump keys/certs from acme.json to files ([#1484](https://github.com/containous/traefik/pull/1484) by [brianredbeard](https://github.com/brianredbeard))
- **[api]** Add HTTP HEAD handling to /ping endpoint ([#1768](https://github.com/containous/traefik/pull/1768) by [martinbaillie](https://github.com/martinbaillie))
- **[authentication,consulcatalog]** Add Basic auth for consul catalog ([#2027](https://github.com/containous/traefik/pull/2027) by [mmatur](https://github.com/mmatur))
- **[authentication,marathon]** Add marathon label to configure basic auth ([#1799](https://github.com/containous/traefik/pull/1799) by [nikore](https://github.com/nikore))
- **[authentication,ecs]** Add basic auth for ecs ([#2026](https://github.com/containous/traefik/pull/2026) by [mmatur](https://github.com/mmatur))
- **[authentication,middleware]** Add forward authentication option ([#1972](https://github.com/containous/traefik/pull/1972) by [drampelt](https://github.com/drampelt))
- **[authentication]** Manage Headers for the Authentication forwarding. ([#2132](https://github.com/containous/traefik/pull/2132) by [ldez](https://github.com/ldez))
- **[consulcatalog,sticky-session]** Enable loadbalancer.sticky for Consul Catalog ([#1917](https://github.com/containous/traefik/pull/1917) by [nbonneval](https://github.com/nbonneval))
- **[consulcatalog]** Exposed by default feature in Consul Catalog ([#2006](https://github.com/containous/traefik/pull/2006) by [mmatur](https://github.com/mmatur))
- **[consulcatalog]** Speeding up consul catalog health change detection ([#1694](https://github.com/containous/traefik/pull/1694) by [vholovko](https://github.com/vholovko))
- **[consulcatalog]** Enhanced flexibility in Consul Catalog configuration ([#1565](https://github.com/containous/traefik/pull/1565) by [aantono](https://github.com/aantono))
- **[docker,k8s]** IP Whitelists for Frontend (with Docker- & Kubernetes-Provider Support) ([#1332](https://github.com/containous/traefik/pull/1332) by [MaZderMind](https://github.com/MaZderMind))
- **[ecs,sticky-session]** Enable loadbalancer.sticky for ECS ([#1925](https://github.com/containous/traefik/pull/1925) by [mmatur](https://github.com/mmatur))
- **[ecs]** Add support for several ECS backends ([#1913](https://github.com/containous/traefik/pull/1913) by [mmatur](https://github.com/mmatur))
- **[file]** Allow file provider to load service config from files in a directory. ([#1672](https://github.com/containous/traefik/pull/1672) by [rjshep](https://github.com/rjshep))
- **[healthcheck]** Add healthcheck command ([#1982](https://github.com/containous/traefik/pull/1982) by [emilevauge](https://github.com/emilevauge))
- **[healthcheck]** Allow overriding the port used for healthchecks ([#1567](https://github.com/containous/traefik/pull/1567) by [bakins](https://github.com/bakins))
- **[k8s,rules]** kubernetes ingress rewrite-target implementation ([#1723](https://github.com/containous/traefik/pull/1723) by [mlaccetti](https://github.com/mlaccetti))
- **[k8s]** Added ability to override frontend priority for k8s ingress router ([#1874](https://github.com/containous/traefik/pull/1874) by [DiverOfDark](https://github.com/DiverOfDark))
- **[kv]** Adds definitions to backend kv template for health checking ([#1644](https://github.com/containous/traefik/pull/1644) by [zachomedia](https://github.com/zachomedia))
- **[logs,dynamodb,ecs,marathon]** Link some providers logs to Traefik ([#1746](https://github.com/containous/traefik/pull/1746) by [ldez](https://github.com/ldez))
- **[logs,marathon]** remove confusing go-marathon log message ([#1810](https://github.com/containous/traefik/pull/1810) by [marco-jantke](https://github.com/marco-jantke))
- **[logs]** Send traefik logs to stdout instead stderr ([#2054](https://github.com/containous/traefik/pull/2054) by [marco-jantke](https://github.com/marco-jantke))
- **[logs]** enable logging to stdout for access logs ([#1683](https://github.com/containous/traefik/pull/1683) by [marco-jantke](https://github.com/marco-jantke))
- **[logs]** Logs & errors review ([#1673](https://github.com/containous/traefik/pull/1673) by [ldez](https://github.com/ldez))
- **[logs]** Switch access logging to logrus ([#1647](https://github.com/containous/traefik/pull/1647) by [rjshep](https://github.com/rjshep))
- **[logs]** log X-Forwarded-For as ClientHost if present ([#1946](https://github.com/containous/traefik/pull/1946) by [mildis](https://github.com/mildis))
- **[logs]** Restore: First stage of access logging middleware. ([#1571](https://github.com/containous/traefik/pull/1571) by [ldez](https://github.com/ldez))
- **[logs]** Add log file close and reopen on receipt of SIGUSR1 ([#1761](https://github.com/containous/traefik/pull/1761) by [rjshep](https://github.com/rjshep))
- **[logs]** add RetryAttempts to AccessLog in JSON format ([#1793](https://github.com/containous/traefik/pull/1793) by [marco-jantke](https://github.com/marco-jantke))
- **[logs]** Add JSON as access logging format ([#1669](https://github.com/containous/traefik/pull/1669) by [rjshep](https://github.com/rjshep))
- **[marathon]** Support multi-port service routing for containers running on Marathon ([#1742](https://github.com/containous/traefik/pull/1742) by [aantono](https://github.com/aantono))
- **[marathon]** Improve Marathon integration tests. ([#1406](https://github.com/containous/traefik/pull/1406) by [timoreimann](https://github.com/timoreimann))
- **[marathon]** Exported getSubDomain function from Marathon provider ([#1693](https://github.com/containous/traefik/pull/1693) by [aantono](https://github.com/aantono))
- **[marathon]** Use test builder. ([#1871](https://github.com/containous/traefik/pull/1871) by [timoreimann](https://github.com/timoreimann))
- **[marathon]** Add support for readiness checks. ([#1883](https://github.com/containous/traefik/pull/1883) by [timoreimann](https://github.com/timoreimann))
- **[marathon]** Move marathon mock ([#1732](https://github.com/containous/traefik/pull/1732) by [ldez](https://github.com/ldez))
- **[marathon]** Use single API call to fetch Marathon resources. ([#1815](https://github.com/containous/traefik/pull/1815) by [timoreimann](https://github.com/timoreimann))
- **[metrics]** Added RetryMetrics to DataDog and StatsD providers ([#1884](https://github.com/containous/traefik/pull/1884) by [aantono](https://github.com/aantono))
- **[metrics]** Extract metrics to own package and refactor implementations ([#1968](https://github.com/containous/traefik/pull/1968) by [marco-jantke](https://github.com/marco-jantke))
- **[metrics]** Add metrics for backend_retries_total ([#1504](https://github.com/containous/traefik/pull/1504) by [marco-jantke](https://github.com/marco-jantke))
- **[metrics]** Add status code to request duration metric ([#1755](https://github.com/containous/traefik/pull/1755) by [marco-jantke](https://github.com/marco-jantke))
- **[middleware]** Add trusted whitelist proxy protocol ([#2234](https://github.com/containous/traefik/pull/2234) by [emilevauge](https://github.com/emilevauge)))
- **[metrics]** DataDog and StatsD Metrics Support ([#1701](https://github.com/containous/traefik/pull/1701) by [aantono](https://github.com/aantono))
- **[middleware]** Create Header Middleware ([#1236](https://github.com/containous/traefik/pull/1236) by [dtomcej](https://github.com/dtomcej))
- **[middleware]** Add configurable timeouts and curate default timeout settings ([#1873](https://github.com/containous/traefik/pull/1873) by [marco-jantke](https://github.com/marco-jantke))
- **[middleware]** Fix command bug content. ([#2002](https://github.com/containous/traefik/pull/2002) by [ldez](https://github.com/ldez))
- **[middleware]** Retry only on real network errors ([#1549](https://github.com/containous/traefik/pull/1549) by [marco-jantke](https://github.com/marco-jantke))
- **[middleware]** Return 503 on empty backend ([#1748](https://github.com/containous/traefik/pull/1748) by [marco-jantke](https://github.com/marco-jantke))
- **[middleware]** Custom Error Pages ([#1675](https://github.com/containous/traefik/pull/1675) by [bparli](https://github.com/bparli))
- **[oxy]** Support X-Forwarded-Port. ([#1960](https://github.com/containous/traefik/pull/1960) by [ldez](https://github.com/ldez))
- **[provider,tls]** Added a check to ensure clientTLS configuration contains either a cert or a key ([#1932](https://github.com/containous/traefik/pull/1932) by [aantono](https://github.com/aantono))
- **[provider]** Deflake integration tests ([#1599](https://github.com/containous/traefik/pull/1599) by [ldez](https://github.com/ldez))
- **[provider]** Factorize labels ([#1843](https://github.com/containous/traefik/pull/1843) by [ldez](https://github.com/ldez))
- **[provider]** Replace go routine by Safe.Go ([#1879](https://github.com/containous/traefik/pull/1879) by [ldez](https://github.com/ldez))
- **[rancher]** Refactor into dual Rancher API/Metadata providers ([#1563](https://github.com/containous/traefik/pull/1563) by [martinbaillie](https://github.com/martinbaillie))
- **[rules]** Add support for Query String filtering ([#1934](https://github.com/containous/traefik/pull/1934) by [driverpt](https://github.com/driverpt))
- **[rules]** Simplify stripPrefix and stripPrefixRegex tests ([#1699](https://github.com/containous/traefik/pull/1699) by [ldez](https://github.com/ldez))
- **[rules]** Enhance rules tests. ([#1679](https://github.com/containous/traefik/pull/1679) by [ldez](https://github.com/ldez))
- **[sticky-session]** make the cookie name unique to the backend being served ([#1716](https://github.com/containous/traefik/pull/1716) by [richardjq](https://github.com/richardjq))
- **[tls]** Handle RootCAs certificate ([#1789](https://github.com/containous/traefik/pull/1789) by [Juliens](https://github.com/Juliens))
- **[tls]** enable TLS client forwarding ([#1446](https://github.com/containous/traefik/pull/1446) by [drewwells](https://github.com/drewwells))
- **[websocket]** Add tests for urlencoded part in url ([#2199](https://github.com/containous/traefik/pull/2199) by [Juliens](https://github.com/Juliens))
- **[websocket]** Add test for SSL TERMINATION in Websocket IT ([#2063](https://github.com/containous/traefik/pull/2063) by [Juliens](https://github.com/Juliens)
- **[webui]** Proxy in dev mode ([#1544](https://github.com/containous/traefik/pull/1544) by [maxwo](https://github.com/maxwo))
- **[webui]** Minor Health UI fixes ([#1651](https://github.com/containous/traefik/pull/1651) by [mihaitodor](https://github.com/mihaitodor))
- Fail fast in IT and fix some flaky tests ([#2126](https://github.com/containous/traefik/pull/2126) by [ldez](https://github.com/ldez))
- extract lb configuration steps into method ([#1841](https://github.com/containous/traefik/pull/1841) by [marco-jantke](https://github.com/marco-jantke))
- Add whitelist configuration option for entrypoints ([#1702](https://github.com/containous/traefik/pull/1702) by [christopherobin](https://github.com/christopherobin))
- Enhance integration tests ([#1842](https://github.com/containous/traefik/pull/1842) by [ldez](https://github.com/ldez))
- Add helloworld tests with gRPC ([#1845](https://github.com/containous/traefik/pull/1845) by [Juliens](https://github.com/Juliens))
- Add the sprig functions in the template engine ([#1891](https://github.com/containous/traefik/pull/1891) by [thomasbach76](https://github.com/thomasbach76))
- Refactor globalConfiguration / WebProvider ([#1938](https://github.com/containous/traefik/pull/1938) by [Juliens](https://github.com/Juliens))
- Code cleaning. ([#1956](https://github.com/containous/traefik/pull/1956) by [ldez](https://github.com/ldez))
- Add proxy protocol ([#2004](https://github.com/containous/traefik/pull/2004) by [emilevauge](https://github.com/emilevauge))
- Bump gorilla/mux version. ([#1954](https://github.com/containous/traefik/pull/1954) by [ldez](https://github.com/ldez))
**Bug fixes:**
- **[cluster,kv]** Be certain to clear our marshalled representation before reloading it ([#2165](https://github.com/containous/traefik/pull/2165) by [gozer](https://github.com/gozer))
- **[consulcatalog,docker,ecs,k8s,kv,marathon,rancher,sticky-session]** Backward compatibility for sticky ([#2266](https://github.com/containous/traefik/pull/2266) by [ldez](https://github.com/ldez))
- **[consulcatalog,docker,ecs,k8s,marathon,rancher,sticky-session]** Stickiness cookie name ([#2232](https://github.com/containous/traefik/pull/2232) by [ldez](https://github.com/ldez))
- **[consulcatalog,docker,ecs,k8s,marathon,rancher,sticky-session]** Stickiness cookie name. ([#2251](https://github.com/containous/traefik/pull/2251) by [ldez](https://github.com/ldez))
- **[consulcatalog]** Fix consul catalog retry ([#2263](https://github.com/containous/traefik/pull/2263) by [mmatur](https://github.com/mmatur))
- **[consulcatalog]** Flaky tests and refresh problem in consul catalog ([#2148](https://github.com/containous/traefik/pull/2148) by [Juliens](https://github.com/Juliens))
- **[consulcatalog]** Consul catalog failed to remove service ([#2157](https://github.com/containous/traefik/pull/2157) by [Juliens](https://github.com/Juliens))
- **[consulcatalog]** Fix Consul Catalog refresh ([#2089](https://github.com/containous/traefik/pull/2089) by [Juliens](https://github.com/Juliens))
- **[docker]** Changed Docker network filter to allow any swarm network ([#2244](https://github.com/containous/traefik/pull/2244) by [pistolero](https://github.com/pistolero))
- **[docker]** Error handling for docker swarm mode ([#1533](https://github.com/containous/traefik/pull/1533) by [tanyadegurechaff](https://github.com/tanyadegurechaff))
- **[ecs]** Handle empty ECS Clusters properly ([#2170](https://github.com/containous/traefik/pull/2170) by [jeffreykoetsier](https://github.com/jeffreykoetsier))
- **[healthcheck]** Fix healthcheck port ([#2131](https://github.com/containous/traefik/pull/2131) by [fredix](https://github.com/fredix))
- **[healthcheck]** Bind healthcheck to backend by entryPointName ([#1868](https://github.com/containous/traefik/pull/1868) by [chrigl](https://github.com/chrigl))
- **[k8s]** Continue processing on invalid auth-realm annotation. ([#2252](https://github.com/containous/traefik/pull/2252) by [timoreimann](https://github.com/timoreimann))
- **[k8s]** Use default frontend priority of zero. ([#1906](https://github.com/containous/traefik/pull/1906) by [timoreimann](https://github.com/timoreimann))
- **[kv]** add retry backoff to staert config loading ([#2268](https://github.com/containous/traefik/pull/2268) by [emilevauge](https://github.com/emilevauge))
- **[logs,middleware]** Enable loss less rotation of log files ([#2062](https://github.com/containous/traefik/pull/2062) by [marco-jantke](https://github.com/marco-jantke))
- **[logs,middleware]** Access log default values ([#2061](https://github.com/containous/traefik/pull/2061) by [ldez](https://github.com/ldez))
- **[logs]** Fix flakiness in log rotation test ([#2213](https://github.com/containous/traefik/pull/2213) by [marco-jantke](https://github.com/marco-jantke))
- **[marathon]** Assign filtered tasks to apps contained in slice. ([#1881](https://github.com/containous/traefik/pull/1881) by [timoreimann](https://github.com/timoreimann))
- **[marathon]** Fix fallback to other nodes for Marathon ([#1740](https://github.com/containous/traefik/pull/1740) by [marco-jantke](https://github.com/marco-jantke))
- **[metrics]** prometheus, HTTP method and utf8 ([#2081](https://github.com/containous/traefik/pull/2081) by [ldez](https://github.com/ldez))
- **[middleware]** Enable prefix matching within slash boundaries ([#2214](https://github.com/containous/traefik/pull/2214) by [marco-jantke](https://github.com/marco-jantke))
- **[middleware]** Fix SSE subscriptions when retries are enabled ([#2145](https://github.com/containous/traefik/pull/2145) by [marco-jantke](https://github.com/marco-jantke))
- **[middleware]** compress: preserve status code ([#1948](https://github.com/containous/traefik/pull/1948) by [ldez](https://github.com/ldez))
- **[rancher]** Add stack name to backend name generation to fix rancher metadata backend ([#2107](https://github.com/containous/traefik/pull/2107) by [SantoDE](https://github.com/SantoDE))
- **[rancher]** Rancher host IP address ([#2101](https://github.com/containous/traefik/pull/2101) by [matq007](https://github.com/matq007))
- **[rancher]** fix seconds to really be seconds ([#2259](https://github.com/containous/traefik/pull/2259) by [SantoDE](https://github.com/SantoDE))
- **[rancher]** fix rancher api environment get ([#2053](https://github.com/containous/traefik/pull/2053) by [SantoDE](https://github.com/SantoDE))
- **[sticky-session]** Sanitize cookie names. ([#2216](https://github.com/containous/traefik/pull/2216) by [timoreimann](https://github.com/timoreimann))
- **[sticky-session]** Setting the Cookie Path explicitly to root ([#1950](https://github.com/containous/traefik/pull/1950) by [marcopaga](https://github.com/marcopaga))
- **[websocket]** Forward upgrade error from backend ([#2187](https://github.com/containous/traefik/pull/2187) by [Juliens](https://github.com/Juliens))
- **[websocket]** RawPath and Transfer TLSConfig in websocket ([#2088](https://github.com/containous/traefik/pull/2088) by [Juliens](https://github.com/Juliens))
- Nil body retries ([#2258](https://github.com/containous/traefik/pull/2258) by [Juliens](https://github.com/Juliens))
- Fix deprecated IdleTimeout config ([#2143](https://github.com/containous/traefik/pull/2143) by [marco-jantke](https://github.com/marco-jantke))
- Fixes entry points configuration. ([#2120](https://github.com/containous/traefik/pull/2120) by [ldez](https://github.com/ldez))
- Delay first version check ([#2215](https://github.com/containous/traefik/pull/2215) by [emilevauge](https://github.com/emilevauge))
- Move http2 configure transport ([#2231](https://github.com/containous/traefik/pull/2231) by [Juliens](https://github.com/Juliens))
- Fix error in prepareServer ([#2076](https://github.com/containous/traefik/pull/2076) by [emilevauge](https://github.com/emilevauge))
- New entry point parser. ([#2248](https://github.com/containous/traefik/pull/2248) by [ldez](https://github.com/ldez))
- Add TrustForwardHeader options. ([#2262](https://github.com/containous/traefik/pull/2262) by [ldez](https://github.com/ldez))
- `bug` command. ([#2178](https://github.com/containous/traefik/pull/2178) by [ldez](https://github.com/ldez))
**Documentation:**
- **[acme,provider]** Enhance documentation readability. ([#2095](https://github.com/containous/traefik/pull/2095) by [ldez](https://github.com/ldez))
- **[acme,provider]** Fix whitespaces ([#2075](https://github.com/containous/traefik/pull/2075) by [chulkilee](https://github.com/chulkilee))
- **[acme,provider]** Re-organize documentation ([#2012](https://github.com/containous/traefik/pull/2012) by [jmaitrehenry](https://github.com/jmaitrehenry))
- **[acme]** Fix grammar ([#2208](https://github.com/containous/traefik/pull/2208) by [mvasin](https://github.com/mvasin))
- **[acme]** Add guide for Docker, Traefik & Letsencrypt ([#1923](https://github.com/containous/traefik/pull/1923) by [mvdstam](https://github.com/mvdstam))
- **[acme]** Improve Let's Encrypt documentation ([#1885](https://github.com/containous/traefik/pull/1885) by [nmengin](https://github.com/nmengin))
- **[acme]** Update docs for dnsimple env vars. ([#1872](https://github.com/containous/traefik/pull/1872) by [untalpierre](https://github.com/untalpierre))
- **[api]** Add examples of proxying ping ([#2102](https://github.com/containous/traefik/pull/2102) by [deitch](https://github.com/deitch))
- **[authentication,k8s]** traefik controller access to secrets ([#1707](https://github.com/containous/traefik/pull/1707) by [spinto](https://github.com/spinto))
- **[consul,tls]** doc change regarding consul SSL ([#1774](https://github.com/containous/traefik/pull/1774) by [bitsofinfo](https://github.com/bitsofinfo))
- **[consulcatalog,docker,ecs,k8s,marathon,rancher,sticky-session]** Stickiness documentation ([#2238](https://github.com/containous/traefik/pull/2238) by [ldez](https://github.com/ldez))
- **[consul]** added consul acl token note ([#1720](https://github.com/containous/traefik/pull/1720) by [bitsofinfo](https://github.com/bitsofinfo))
- **[docker]** Updating Docker output and curl for sticky sessions ([#2150](https://github.com/containous/traefik/pull/2150) by [jtyr](https://github.com/jtyr))
- **[docker]** Add more visibility to docker stack deploy label issue ([#1984](https://github.com/containous/traefik/pull/1984) by [jmaitrehenry](https://github.com/jmaitrehenry))
- **[ecs]** Fix IAM policy sid. ([#2066](https://github.com/containous/traefik/pull/2066) by [charlieoleary](https://github.com/charlieoleary))
- **[k8s,marathon]** Mark Marathon and Kubernetes as constraint-supporting. ([#1964](https://github.com/containous/traefik/pull/1964) by [timoreimann](https://github.com/timoreimann))
- **[k8s]** Add guide section on production advice, esp. CPU. ([#2113](https://github.com/containous/traefik/pull/2113) by [timoreimann](https://github.com/timoreimann))
- **[k8s]** Document ways to partition Ingresses in the k8s guide. ([#2223](https://github.com/containous/traefik/pull/2223) by [timoreimann](https://github.com/timoreimann))
- **[k8s]** Remove pod from RBAC rules. ([#2229](https://github.com/containous/traefik/pull/2229) by [timoreimann](https://github.com/timoreimann))
- **[k8s]** Quote priority values in annotation examples. ([#2230](https://github.com/containous/traefik/pull/2230) by [timoreimann](https://github.com/timoreimann))
- **[k8s]** Fix invalid service yaml example ([#2059](https://github.com/containous/traefik/pull/2059) by [kairen](https://github.com/kairen))
- **[k8s]** Update usage of `.local` with `.minikube` in k8s docs ([#1551](https://github.com/containous/traefik/pull/1551) by [errm](https://github.com/errm))
- **[k8s]** Update the documentation to use DaemonSet or Deployment ([#1735](https://github.com/containous/traefik/pull/1735) by [saschagrunert](https://github.com/saschagrunert))
- **[k8s]** Fix docs about default namespaces. ([#1961](https://github.com/containous/traefik/pull/1961) by [timoreimann](https://github.com/timoreimann))
- **[k8s]** Moved namespace to correct place ([#1911](https://github.com/containous/traefik/pull/1911) by [markround](https://github.com/markround))
- **[k8s]** examples/k8s: fix ui ingress port out of sync with deployment ([#1943](https://github.com/containous/traefik/pull/1943) by [borancar](https://github.com/borancar))
- **[k8s]** Add secrets resource to in-line RBAC spec. ([#1890](https://github.com/containous/traefik/pull/1890) by [timoreimann](https://github.com/timoreimann))
- **[k8s]** Improve documentation. ([#1831](https://github.com/containous/traefik/pull/1831) by [timoreimann](https://github.com/timoreimann))
- **[marathon]** Fix documentation glitches. ([#1996](https://github.com/containous/traefik/pull/1996) by [timoreimann](https://github.com/timoreimann))
- **[metrics]** Enhance web backend documentation ([#2122](https://github.com/containous/traefik/pull/2122) by [ldez](https://github.com/ldez))
- **[mesos]** fix: documentation Mesos. ([#2029](https://github.com/containous/traefik/pull/2029) by [ldez](https://github.com/ldez))
- **[middleware]** Improve compression documentation ([#2184](https://github.com/containous/traefik/pull/2184) by [errm](https://github.com/errm))
- **[provider]** Clarify that provider-enabling argument parameters set all defaults. ([#1830](https://github.com/containous/traefik/pull/1830) by [timoreimann](https://github.com/timoreimann))
- **[rancher]** Update Rancher documentation. ([#1776](https://github.com/containous/traefik/pull/1776) by [ldez](https://github.com/ldez))
- **[webui]** Document yarnpkg. ([#1558](https://github.com/containous/traefik/pull/1558) by [Stibbons](https://github.com/Stibbons))
- Add forward auth documentation. ([#2110](https://github.com/containous/traefik/pull/2110) by [ldez](https://github.com/ldez))
- User guide gRPC ([#2108](https://github.com/containous/traefik/pull/2108) by [Juliens](https://github.com/Juliens))
- Document custom error page restrictions. ([#2104](https://github.com/containous/traefik/pull/2104) by [timoreimann](https://github.com/timoreimann))
- Prepare release v1.4.0-rc3 ([#2135](https://github.com/containous/traefik/pull/2135) by [Juliens](https://github.com/Juliens))
- Update gRPC example ([#2191](https://github.com/containous/traefik/pull/2191) by [jsenon](https://github.com/jsenon))
- Prepare release v1.4.0-rc2 ([#2091](https://github.com/containous/traefik/pull/2091) by [ldez](https://github.com/ldez))
- Fix grammar mistake in the kv-config docs ([#2197](https://github.com/containous/traefik/pull/2197) by [chr4](https://github.com/chr4))
- Update cluster.md ([#2073](https://github.com/containous/traefik/pull/2073) by [kmbremner](https://github.com/kmbremner))
- Prepare release v1.4.0-rc4 ([#2201](https://github.com/containous/traefik/pull/2201) by [nmengin](https://github.com/nmengin))
- Prepare release v1.4.0-rc5 ([#2241](https://github.com/containous/traefik/pull/2241) by [ldez](https://github.com/ldez))
- Enhance documentation. ([#2048](https://github.com/containous/traefik/pull/2048) by [ldez](https://github.com/ldez))
- doc: add notes on server urls with path ([#2045](https://github.com/containous/traefik/pull/2045) by [chulkilee](https://github.com/chulkilee))
- Enhance security headers doc. ([#2042](https://github.com/containous/traefik/pull/2042) by [ldez](https://github.com/ldez))
- HTTPS for images, video and links in docs. ([#2041](https://github.com/containous/traefik/pull/2041) by [ldez](https://github.com/ldez))
- Fix error pages configuration. ([#2038](https://github.com/containous/traefik/pull/2038) by [ldez](https://github.com/ldez))
- Fix Proxy Protocol documentation ([#2253](https://github.com/containous/traefik/pull/2253) by [emilevauge](https://github.com/emilevauge))
- Update GraceTimeOut documentation ([#1875](https://github.com/containous/traefik/pull/1875) by [marco-jantke](https://github.com/marco-jantke))
- Release cycle. ([#1812](https://github.com/containous/traefik/pull/1812) by [ldez](https://github.com/ldez))
- Update contributing guide build steps ([#1801](https://github.com/containous/traefik/pull/1801) by [jsturtevant](https://github.com/jsturtevant))
- Add Nicolas Mengin to maintainers ([#1792](https://github.com/containous/traefik/pull/1792) by [emilevauge](https://github.com/emilevauge))
- Add Julien Salleyron to maintainers ([#1790](https://github.com/containous/traefik/pull/1790) by [emilevauge](https://github.com/emilevauge))
- Change to a more flexible PR review process ([#1781](https://github.com/containous/traefik/pull/1781) by [emilevauge](https://github.com/emilevauge))
- Traefik "bug" command documentation ([#1811](https://github.com/containous/traefik/pull/1811) by [ldez](https://github.com/ldez))
- Change Traefik intro video ([#1893](https://github.com/containous/traefik/pull/1893) by [emilevauge](https://github.com/emilevauge))
- Prepare release v1.4.0-rc1 ([#2021](https://github.com/containous/traefik/pull/2021) by [ldez](https://github.com/ldez))
- Add play-with-docker example ([#1726](https://github.com/containous/traefik/pull/1726) by [marcosnils](https://github.com/marcosnils))
- Add Marco Jantke to maintainers ([#1980](https://github.com/containous/traefik/pull/1980) by [emilevauge](https://github.com/emilevauge))
- Remove Russel from maintainers ([#1614](https://github.com/containous/traefik/pull/1614) by [emilevauge](https://github.com/emilevauge))
- Update CONTRIBUTING.md. ([#1667](https://github.com/containous/traefik/pull/1667) by [timoreimann](https://github.com/timoreimann))
- drop "slave" wording for "worker" ([#1645](https://github.com/containous/traefik/pull/1645) by [djalal](https://github.com/djalal))
- Use more inclusive language in README.md {guys => folks} ([#1640](https://github.com/containous/traefik/pull/1640) by [igorwwwwwwwwwwwwwwwwwwww](https://github.com/igorwwwwwwwwwwwwwwwwwwww))
- Remove Thomas Recloux from maintainers ([#1616](https://github.com/containous/traefik/pull/1616) by [emilevauge](https://github.com/emilevauge))
- Update documentation for 1.4 release ([#2011](https://github.com/containous/traefik/pull/2011) by [emilevauge](https://github.com/emilevauge))
- Small toml documentation update ([#1603](https://github.com/containous/traefik/pull/1603) by [antoine-aumjaud](https://github.com/antoine-aumjaud))
- Add @ldez to maintainers ([#1589](https://github.com/containous/traefik/pull/1589) by [emilevauge](https://github.com/emilevauge))
- doc: add labels documentation. ([#1582](https://github.com/containous/traefik/pull/1582) by [ldez](https://github.com/ldez))
- Update golang version in contributing guide ([#2018](https://github.com/containous/traefik/pull/2018) by [ArikaChen](https://github.com/ArikaChen))
- toml page - replace li by table ([#1995](https://github.com/containous/traefik/pull/1995) by [jmaitrehenry](https://github.com/jmaitrehenry))
**Misc:**
- Merge v1.3.7 ([#2013](https://github.com/containous/traefik/pull/2013) by [ldez](https://github.com/ldez))
- Merge 1.3.6 ([#1992](https://github.com/containous/traefik/pull/1992) by [ldez](https://github.com/ldez))
- Merge 1.3.5 ([#1909](https://github.com/containous/traefik/pull/1909) by [ldez](https://github.com/ldez))
- Merge 1.3.3 ([#1836](https://github.com/containous/traefik/pull/1836) by [ldez](https://github.com/ldez))
- Merge v1.3.2 to master ([#1809](https://github.com/containous/traefik/pull/1809) by [ldez](https://github.com/ldez))
- Merge current v1.3 ([#1797](https://github.com/containous/traefik/pull/1797) by [ldez](https://github.com/ldez))
- Merge current v1.3 ([#1786](https://github.com/containous/traefik/pull/1786) by [ldez](https://github.com/ldez))
- Merge v1.3.1 to master ([#1763](https://github.com/containous/traefik/pull/1763) by [ldez](https://github.com/ldez))
- Merge current v1.3 ([#1753](https://github.com/containous/traefik/pull/1753) by [ldez](https://github.com/ldez))
- Merge current v1.3 ([#1705](https://github.com/containous/traefik/pull/1705) by [ldez](https://github.com/ldez))
- Merge current v1.3 to master ([#1697](https://github.com/containous/traefik/pull/1697) by [ldez](https://github.com/ldez))
- Merge v1 3 0 ([#1692](https://github.com/containous/traefik/pull/1692) by [ldez](https://github.com/ldez))
- Merge current v1.3 to master (rc3) ([#1666](https://github.com/containous/traefik/pull/1666) by [ldez](https://github.com/ldez))
- Merge current v1.3 to master ([#1643](https://github.com/containous/traefik/pull/1643) by [ldez](https://github.com/ldez))
- Merge v1.3.0-rc2 master ([#1613](https://github.com/containous/traefik/pull/1613) by [emilevauge](https://github.com/emilevauge))
- Merge v1.3 branch into master [2017-05-11] ([#1548](https://github.com/containous/traefik/pull/1548) by [timoreimann](https://github.com/timoreimann))
## [v1.4.0-rc5](https://github.com/containous/traefik/tree/v1.4.0-rc5) (2017-10-10) ## [v1.4.0-rc5](https://github.com/containous/traefik/tree/v1.4.0-rc5) (2017-10-10)
[All Commits](https://github.com/containous/traefik/compare/v1.4.0-rc4...v1.4.0-rc5) [All Commits](https://github.com/containous/traefik/compare/v1.4.0-rc4...v1.4.0-rc5)

View file

@ -14,11 +14,13 @@ import (
"time" "time"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/cenk/backoff"
"github.com/containous/flaeg" "github.com/containous/flaeg"
"github.com/containous/staert" "github.com/containous/staert"
"github.com/containous/traefik/acme" "github.com/containous/traefik/acme"
"github.com/containous/traefik/cluster" "github.com/containous/traefik/cluster"
"github.com/containous/traefik/configuration" "github.com/containous/traefik/configuration"
"github.com/containous/traefik/job"
"github.com/containous/traefik/log" "github.com/containous/traefik/log"
"github.com/containous/traefik/provider/ecs" "github.com/containous/traefik/provider/ecs"
"github.com/containous/traefik/provider/kubernetes" "github.com/containous/traefik/provider/kubernetes"
@ -196,7 +198,15 @@ Complete documentation is available at https://traefik.io`,
traefikConfiguration.Cluster.Store = &types.Store{Prefix: kv.Prefix, Store: kv.Store} traefikConfiguration.Cluster.Store = &types.Store{Prefix: kv.Prefix, Store: kv.Store}
} }
s.AddSource(kv) s.AddSource(kv)
if _, err := s.LoadConfig(); err != nil { operation := func() error {
_, err := s.LoadConfig()
return err
}
notify := func(err error, time time.Duration) {
log.Errorf("Load config error: %+v, retrying in %s", err, time)
}
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
if err != nil {
fmtlog.Printf("Error loading configuration: %s\n", err) fmtlog.Printf("Error loading configuration: %s\n", err)
os.Exit(-1) os.Exit(-1)
} }
@ -275,7 +285,7 @@ func configureLogging(globalConfiguration *configuration.GlobalConfiguration) {
// configure log output file // configure log output file
logFile := globalConfiguration.TraefikLogsFile logFile := globalConfiguration.TraefikLogsFile
if len(logFile) > 0 { if len(logFile) > 0 {
log.Warn("top-level traefiklogsfile has been deprecated -- please use traefiklog.filepath") log.Warn("top-level traefikLogsFile has been deprecated -- please use traefiklog.filepath")
} }
if globalConfiguration.TraefikLog != nil && len(globalConfiguration.TraefikLog.FilePath) > 0 { if globalConfiguration.TraefikLog != nil && len(globalConfiguration.TraefikLog.FilePath) > 0 {
logFile = globalConfiguration.TraefikLog.FilePath logFile = globalConfiguration.TraefikLog.FilePath

View file

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"regexp"
"strings" "strings"
"time" "time"
@ -87,14 +86,25 @@ type GlobalConfiguration struct {
DynamoDB *dynamodb.Provider `description:"Enable DynamoDB backend with default settings" export:"true"` DynamoDB *dynamodb.Provider `description:"Enable DynamoDB backend with default settings" export:"true"`
} }
// SetEffectiveConfiguration adds missing configuration parameters derived from // SetEffectiveConfiguration adds missing configuration parameters derived from existing ones.
// existing ones. It also takes care of maintaining backwards compatibility. // It also takes care of maintaining backwards compatibility.
func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) { func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
if len(gc.EntryPoints) == 0 { if len(gc.EntryPoints) == 0 {
gc.EntryPoints = map[string]*EntryPoint{"http": {Address: ":80"}} gc.EntryPoints = map[string]*EntryPoint{"http": {
Address: ":80",
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
}}
gc.DefaultEntryPoints = []string{"http"} gc.DefaultEntryPoints = []string{"http"}
} }
// ForwardedHeaders must be remove in the next breaking version
for entryPointName := range gc.EntryPoints {
entryPoint := gc.EntryPoints[entryPointName]
if entryPoint.ForwardedHeaders == nil {
entryPoint.ForwardedHeaders = &ForwardedHeaders{Insecure: true}
}
}
// Make sure LifeCycle isn't nil to spare nil checks elsewhere. // Make sure LifeCycle isn't nil to spare nil checks elsewhere.
if gc.LifeCycle == nil { if gc.LifeCycle == nil {
gc.LifeCycle = &LifeCycle{} gc.LifeCycle = &LifeCycle{}
@ -252,79 +262,101 @@ func (ep *EntryPoints) String() string {
// Set's argument is a string to be parsed to set the flag. // Set's argument is a string to be parsed to set the flag.
// It's a comma-separated list, so we split it. // It's a comma-separated list, so we split it.
func (ep *EntryPoints) Set(value string) error { func (ep *EntryPoints) Set(value string) error {
result, err := parseEntryPointsConfiguration(value) result := parseEntryPointsConfiguration(value)
if err != nil {
return err
}
var configTLS *TLS var configTLS *TLS
if len(result["TLS"]) > 0 { if len(result["tls"]) > 0 {
certs := Certificates{} certs := Certificates{}
if err := certs.Set(result["TLS"]); err != nil { if err := certs.Set(result["tls"]); err != nil {
return err return err
} }
configTLS = &TLS{ configTLS = &TLS{
Certificates: certs, Certificates: certs,
} }
} else if len(result["TLSACME"]) > 0 { } else if len(result["tls_acme"]) > 0 {
configTLS = &TLS{ configTLS = &TLS{
Certificates: Certificates{}, Certificates: Certificates{},
} }
} }
if len(result["CA"]) > 0 { if len(result["ca"]) > 0 {
files := strings.Split(result["CA"], ",") files := strings.Split(result["ca"], ",")
configTLS.ClientCAFiles = files configTLS.ClientCAFiles = files
} }
var redirect *Redirect var redirect *Redirect
if len(result["RedirectEntryPoint"]) > 0 || len(result["RedirectRegex"]) > 0 || len(result["RedirectReplacement"]) > 0 { if len(result["redirect_entrypoint"]) > 0 || len(result["redirect_regex"]) > 0 || len(result["redirect_replacement"]) > 0 {
redirect = &Redirect{ redirect = &Redirect{
EntryPoint: result["RedirectEntryPoint"], EntryPoint: result["redirect_entrypoint"],
Regex: result["RedirectRegex"], Regex: result["redirect_regex"],
Replacement: result["RedirectReplacement"], Replacement: result["redirect_replacement"],
} }
} }
whiteListSourceRange := []string{} whiteListSourceRange := []string{}
if len(result["WhiteListSourceRange"]) > 0 { if len(result["whitelistsourcerange"]) > 0 {
whiteListSourceRange = strings.Split(result["WhiteListSourceRange"], ",") whiteListSourceRange = strings.Split(result["whitelistsourcerange"], ",")
} }
compress := toBool(result, "Compress") compress := toBool(result, "compress")
var proxyProtocol *ProxyProtocol var proxyProtocol *ProxyProtocol
if len(result["ProxyProtocol"]) > 0 { ppTrustedIPs := result["proxyprotocol_trustedips"]
trustedIPs := strings.Split(result["ProxyProtocol"], ",") if len(result["proxyprotocol_insecure"]) > 0 || len(ppTrustedIPs) > 0 {
proxyProtocol = &ProxyProtocol{ proxyProtocol = &ProxyProtocol{
TrustedIPs: trustedIPs, Insecure: toBool(result, "proxyprotocol_insecure"),
}
if len(ppTrustedIPs) > 0 {
proxyProtocol.TrustedIPs = strings.Split(ppTrustedIPs, ",")
} }
} }
(*ep)[result["Name"]] = &EntryPoint{ // TODO must be changed to false by default in the next breaking version.
Address: result["Address"], forwardedHeaders := &ForwardedHeaders{Insecure: true}
if _, ok := result["forwardedheaders_insecure"]; ok {
forwardedHeaders.Insecure = toBool(result, "forwardedheaders_insecure")
}
fhTrustedIPs := result["forwardedheaders_trustedips"]
if len(fhTrustedIPs) > 0 {
// TODO must be removed in the next breaking version.
forwardedHeaders.Insecure = toBool(result, "forwardedheaders_insecure")
forwardedHeaders.TrustedIPs = strings.Split(fhTrustedIPs, ",")
}
if proxyProtocol != nil && proxyProtocol.Insecure {
log.Warn("ProxyProtocol.Insecure:true is dangerous. Please use 'ProxyProtocol.TrustedIPs:IPs' and remove 'ProxyProtocol.Insecure:true'")
}
(*ep)[result["name"]] = &EntryPoint{
Address: result["address"],
TLS: configTLS, TLS: configTLS,
Redirect: redirect, Redirect: redirect,
Compress: compress, Compress: compress,
WhitelistSourceRange: whiteListSourceRange, WhitelistSourceRange: whiteListSourceRange,
ProxyProtocol: proxyProtocol, ProxyProtocol: proxyProtocol,
ForwardedHeaders: forwardedHeaders,
} }
return nil return nil
} }
func parseEntryPointsConfiguration(value string) (map[string]string, error) { func parseEntryPointsConfiguration(raw string) map[string]string {
regex := regexp.MustCompile(`(?:Name:(?P<Name>\S*))\s*(?:Address:(?P<Address>\S*))?\s*(?:TLS:(?P<TLS>\S*))?\s*(?P<TLSACME>TLS)?\s*(?:CA:(?P<CA>\S*))?\s*(?:Redirect\.EntryPoint:(?P<RedirectEntryPoint>\S*))?\s*(?:Redirect\.Regex:(?P<RedirectRegex>\S*))?\s*(?:Redirect\.Replacement:(?P<RedirectReplacement>\S*))?\s*(?:Compress:(?P<Compress>\S*))?\s*(?:WhiteListSourceRange:(?P<WhiteListSourceRange>\S*))?\s*(?:ProxyProtocol\.TrustedIPs:(?P<ProxyProtocol>\S*))?`) sections := strings.Fields(raw)
match := regex.FindAllStringSubmatch(value, -1)
if match == nil { config := make(map[string]string)
return nil, fmt.Errorf("bad EntryPoints format: %s", value) for _, part := range sections {
} field := strings.SplitN(part, ":", 2)
matchResult := match[0] name := strings.ToLower(strings.Replace(field[0], ".", "_", -1))
result := make(map[string]string) if len(field) > 1 {
for i, name := range regex.SubexpNames() { config[name] = field[1]
if i != 0 && len(matchResult[i]) != 0 { } else {
result[name] = matchResult[i] if strings.EqualFold(name, "TLS") {
config["tls_acme"] = "TLS"
} else {
config[name] = ""
}
} }
} }
return result, nil return config
} }
func toBool(conf map[string]string, key string) bool { func toBool(conf map[string]string, key string) bool {
@ -359,8 +391,9 @@ type EntryPoint struct {
Redirect *Redirect `export:"true"` Redirect *Redirect `export:"true"`
Auth *types.Auth `export:"true"` Auth *types.Auth `export:"true"`
WhitelistSourceRange []string WhitelistSourceRange []string
Compress bool `export:"true"` Compress bool `export:"true"`
ProxyProtocol *ProxyProtocol `export:"true"` ProxyProtocol *ProxyProtocol `export:"true"`
ForwardedHeaders *ForwardedHeaders `export:"true"`
} }
// Redirect configures a redirection of an entry point to another, or to an URL // Redirect configures a redirection of an entry point to another, or to an URL
@ -512,6 +545,13 @@ type ForwardingTimeouts struct {
// ProxyProtocol contains Proxy-Protocol configuration // ProxyProtocol contains Proxy-Protocol configuration
type ProxyProtocol struct { type ProxyProtocol struct {
Insecure bool
TrustedIPs []string
}
// ForwardedHeaders Trust client forwarding headers
type ForwardedHeaders struct {
Insecure bool
TrustedIPs []string TrustedIPs []string
} }

View file

@ -1,7 +1,6 @@
package configuration package configuration
import ( import (
"fmt"
"testing" "testing"
"time" "time"
@ -22,36 +21,37 @@ func Test_parseEntryPointsConfiguration(t *testing.T) {
}{ }{
{ {
name: "all parameters", name: "all parameters",
value: "Name:foo Address:bar TLS:goo TLS CA:car Redirect.EntryPoint:RedirectEntryPoint Redirect.Regex:RedirectRegex Redirect.Replacement:RedirectReplacement Compress:true WhiteListSourceRange:WhiteListSourceRange ProxyProtocol.TrustedIPs:192.168.0.1", value: "Name:foo TLS:goo TLS CA:car Redirect.EntryPoint:RedirectEntryPoint Redirect.Regex:RedirectRegex Redirect.Replacement:RedirectReplacement Compress:true WhiteListSourceRange:WhiteListSourceRange ProxyProtocol.TrustedIPs:192.168.0.1 ProxyProtocol.Insecure:false Address::8000",
expectedResult: map[string]string{ expectedResult: map[string]string{
"Name": "foo", "name": "foo",
"Address": "bar", "address": ":8000",
"CA": "car", "ca": "car",
"TLS": "goo", "tls": "goo",
"TLSACME": "TLS", "tls_acme": "TLS",
"RedirectEntryPoint": "RedirectEntryPoint", "redirect_entrypoint": "RedirectEntryPoint",
"RedirectRegex": "RedirectRegex", "redirect_regex": "RedirectRegex",
"RedirectReplacement": "RedirectReplacement", "redirect_replacement": "RedirectReplacement",
"WhiteListSourceRange": "WhiteListSourceRange", "whitelistsourcerange": "WhiteListSourceRange",
"ProxyProtocol": "192.168.0.1", "proxyprotocol_trustedips": "192.168.0.1",
"Compress": "true", "proxyprotocol_insecure": "false",
"compress": "true",
}, },
}, },
{ {
name: "compress on", name: "compress on",
value: "Name:foo Compress:on", value: "name:foo Compress:on",
expectedResult: map[string]string{ expectedResult: map[string]string{
"Name": "foo", "name": "foo",
"Compress": "on", "compress": "on",
}, },
}, },
{ {
name: "TLS", name: "TLS",
value: "Name:foo TLS:goo TLS", value: "Name:foo TLS:goo TLS",
expectedResult: map[string]string{ expectedResult: map[string]string{
"Name": "foo", "name": "foo",
"TLS": "goo", "tls": "goo",
"TLSACME": "TLS", "tls_acme": "TLS",
}, },
}, },
} }
@ -61,14 +61,7 @@ func Test_parseEntryPointsConfiguration(t *testing.T) {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
t.Parallel() t.Parallel()
conf, err := parseEntryPointsConfiguration(test.value) conf := parseEntryPointsConfiguration(test.value)
if err != nil {
t.Error(err)
}
for key, value := range conf {
fmt.Println(key, value)
}
assert.Len(t, conf, len(test.expectedResult)) assert.Len(t, conf, len(test.expectedResult))
assert.Equal(t, test.expectedResult, conf) assert.Equal(t, test.expectedResult, conf)
@ -139,11 +132,11 @@ func TestEntryPoints_Set(t *testing.T) {
expectedEntryPoint *EntryPoint expectedEntryPoint *EntryPoint
}{ }{
{ {
name: "all parameters", name: "all parameters camelcase",
expression: "Name:foo Address:bar TLS:goo,gii TLS CA:car Redirect.EntryPoint:RedirectEntryPoint Redirect.Regex:RedirectRegex Redirect.Replacement:RedirectReplacement Compress:true WhiteListSourceRange:Range ProxyProtocol.TrustedIPs:192.168.0.1", expression: "Name:foo Address::8000 TLS:goo,gii TLS CA:car Redirect.EntryPoint:RedirectEntryPoint Redirect.Regex:RedirectRegex Redirect.Replacement:RedirectReplacement Compress:true WhiteListSourceRange:Range ProxyProtocol.TrustedIPs:192.168.0.1 ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24",
expectedEntryPointName: "foo", expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{ expectedEntryPoint: &EntryPoint{
Address: "bar", Address: ":8000",
Redirect: &Redirect{ Redirect: &Redirect{
EntryPoint: "RedirectEntryPoint", EntryPoint: "RedirectEntryPoint",
Regex: "RedirectRegex", Regex: "RedirectRegex",
@ -153,6 +146,9 @@ func TestEntryPoints_Set(t *testing.T) {
ProxyProtocol: &ProxyProtocol{ ProxyProtocol: &ProxyProtocol{
TrustedIPs: []string{"192.168.0.1"}, TrustedIPs: []string{"192.168.0.1"},
}, },
ForwardedHeaders: &ForwardedHeaders{
TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"},
},
WhitelistSourceRange: []string{"Range"}, WhitelistSourceRange: []string{"Range"},
TLS: &TLS{ TLS: &TLS{
ClientCAFiles: []string{"car"}, ClientCAFiles: []string{"car"},
@ -165,6 +161,106 @@ func TestEntryPoints_Set(t *testing.T) {
}, },
}, },
}, },
{
name: "all parameters lowercase",
expression: "name:foo address::8000 tls:goo,gii tls ca:car redirect.entryPoint:RedirectEntryPoint redirect.regex:RedirectRegex redirect.replacement:RedirectReplacement compress:true whiteListSourceRange:Range proxyProtocol.trustedIPs:192.168.0.1 forwardedHeaders.trustedIPs:10.0.0.3/24,20.0.0.3/24",
expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{
Address: ":8000",
Redirect: &Redirect{
EntryPoint: "RedirectEntryPoint",
Regex: "RedirectRegex",
Replacement: "RedirectReplacement",
},
Compress: true,
ProxyProtocol: &ProxyProtocol{
TrustedIPs: []string{"192.168.0.1"},
},
ForwardedHeaders: &ForwardedHeaders{
TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"},
},
WhitelistSourceRange: []string{"Range"},
TLS: &TLS{
ClientCAFiles: []string{"car"},
Certificates: Certificates{
{
CertFile: FileOrContent("goo"),
KeyFile: FileOrContent("gii"),
},
},
},
},
},
{
name: "default",
expression: "Name:foo",
expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{
WhitelistSourceRange: []string{},
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
},
},
{
name: "ForwardedHeaders insecure true",
expression: "Name:foo ForwardedHeaders.Insecure:true",
expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{
WhitelistSourceRange: []string{},
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
},
},
{
name: "ForwardedHeaders insecure false",
expression: "Name:foo ForwardedHeaders.Insecure:false",
expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{
WhitelistSourceRange: []string{},
ForwardedHeaders: &ForwardedHeaders{Insecure: false},
},
},
{
name: "ForwardedHeaders TrustedIPs",
expression: "Name:foo ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24",
expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{
WhitelistSourceRange: []string{},
ForwardedHeaders: &ForwardedHeaders{
TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"},
},
},
},
{
name: "ProxyProtocol insecure true",
expression: "Name:foo ProxyProtocol.Insecure:true",
expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{
WhitelistSourceRange: []string{},
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
ProxyProtocol: &ProxyProtocol{Insecure: true},
},
},
{
name: "ProxyProtocol insecure false",
expression: "Name:foo ProxyProtocol.Insecure:false",
expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{
WhitelistSourceRange: []string{},
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
ProxyProtocol: &ProxyProtocol{},
},
},
{
name: "ProxyProtocol TrustedIPs",
expression: "Name:foo ProxyProtocol.TrustedIPs:10.0.0.3/24,20.0.0.3/24",
expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{
WhitelistSourceRange: []string{},
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
ProxyProtocol: &ProxyProtocol{
TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"},
},
},
},
{ {
name: "compress on", name: "compress on",
expression: "Name:foo Compress:on", expression: "Name:foo Compress:on",
@ -172,6 +268,7 @@ func TestEntryPoints_Set(t *testing.T) {
expectedEntryPoint: &EntryPoint{ expectedEntryPoint: &EntryPoint{
Compress: true, Compress: true,
WhitelistSourceRange: []string{}, WhitelistSourceRange: []string{},
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
}, },
}, },
{ {
@ -181,6 +278,7 @@ func TestEntryPoints_Set(t *testing.T) {
expectedEntryPoint: &EntryPoint{ expectedEntryPoint: &EntryPoint{
Compress: true, Compress: true,
WhitelistSourceRange: []string{}, WhitelistSourceRange: []string{},
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
}, },
}, },
} }

View file

@ -383,21 +383,19 @@ The default cookie name is an abbreviation of a sha1 (ex: `_1d52e`).
On subsequent requests, the client will be directed to the backend stored in the cookie if it is still healthy. On subsequent requests, the client will be directed to the backend stored in the cookie if it is still healthy.
If not, a new backend will be assigned. If not, a new backend will be assigned.
To activate sticky session:
```toml ```toml
[backends] [backends]
[backends.backend1] [backends.backend1]
# Enable sticky session
[backends.backend1.loadbalancer.stickiness] [backends.backend1.loadbalancer.stickiness]
```
To customize the cookie name: # Customize the cookie name
#
```toml # Optional
[backends] # Default: a sha1 (6 chars)
[backends.backend1] #
[backends.backend1.loadbalancer.stickiness] # cookieName = "my_cookie"
cookieName = "my_cookie"
``` ```
The deprecated way: The deprecated way:

View file

@ -188,17 +188,52 @@ To enable IP whitelisting at the entrypoint level.
whiteListSourceRange = ["127.0.0.1/32", "192.168.1.7"] whiteListSourceRange = ["127.0.0.1/32", "192.168.1.7"]
``` ```
## ProxyProtocol Support ## ProxyProtocol
To enable [ProxyProtocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) support. To enable [ProxyProtocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) support.
Only IPs in `trustedIPs` will lead to remote client address replacement: you should declare your load-balancer IP or CIDR range here. Only IPs in `trustedIPs` will lead to remote client address replacement: you should declare your load-balancer IP or CIDR range here (in testing environment, you can trust everyone using `insecure = true`).
!!! danger
When queuing Træfik behind another load-balancer, be sure to carefully configure Proxy Protocol on both sides.
Otherwise, it could introduce a security risk in your system by forging requests.
```toml ```toml
[entryPoints] [entryPoints]
[entryPoints.http] [entryPoints.http]
address = ":80" address = ":80"
[entryPoints.http.proxyProtocol]
trustedIPs = ["127.0.0.1/32", "192.168.1.7"] # Enable ProxyProtocol
[entryPoints.http.proxyProtocol]
# List of trusted IPs
#
# Required
# Default: []
#
trustedIPs = ["127.0.0.1/32", "192.168.1.7"]
# Insecure mode FOR TESTING ENVIRONNEMENT ONLY
#
# Optional
# Default: false
#
# insecure = true
```
## Forwarded Header
Only IPs in `trustedIPs` will be authorize to trust the client forwarded headers (`X-Forwarded-*`).
```toml
[entryPoints]
[entryPoints.http]
address = ":80"
# Enable Forwarded Headers
[entryPoints.http.forwardedHeaders]
# List of trusted IPs
#
# Required
# Default: []
#
trustedIPs = ["127.0.0.1/32", "192.168.1.7"]
``` ```
²

6
glide.lock generated
View file

@ -1,5 +1,5 @@
hash: 6881e0574a026dde78c9d7f03a4aa56f6c6dc585d2b6d3e6f4ae1a94810b7f88 hash: b929df3c022d8a67b8d174f81257a502670c3683e801f8a53283c2965c921c6e
updated: 2017-10-02T18:32:16.848940186+02:00 updated: 2017-10-16T23:09:16.848940186+02:00
imports: imports:
- name: cloud.google.com/go - name: cloud.google.com/go
version: 2e6a95edb1071d750f6d7db777bf66cd2997af6c version: 2e6a95edb1071d750f6d7db777bf66cd2997af6c
@ -485,7 +485,7 @@ imports:
- name: github.com/urfave/negroni - name: github.com/urfave/negroni
version: 490e6a555d47ca891a89a150d0c1ef3922dfffe9 version: 490e6a555d47ca891a89a150d0c1ef3922dfffe9
- name: github.com/vulcand/oxy - name: github.com/vulcand/oxy
version: 648088ee0902cf8d8337826ae2a82444008720e2 version: c024a22700b56debed9a9c8dbb297210a7ece02d
repo: https://github.com/containous/oxy.git repo: https://github.com/containous/oxy.git
vcs: git vcs: git
subpackages: subpackages:

View file

@ -12,7 +12,7 @@ import:
- package: github.com/cenk/backoff - package: github.com/cenk/backoff
- package: github.com/containous/flaeg - package: github.com/containous/flaeg
- package: github.com/vulcand/oxy - package: github.com/vulcand/oxy
version: 648088ee0902cf8d8337826ae2a82444008720e2 version: c024a22700b56debed9a9c8dbb297210a7ece02d
repo: https://github.com/containous/oxy.git repo: https://github.com/containous/oxy.git
vcs: git vcs: git
subpackages: subpackages:

View file

@ -24,19 +24,20 @@ func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) {
s.composeProject.Start(c) s.composeProject.Start(c)
consul := s.composeProject.Container(c, "consul") consul := s.composeProject.Container(c, "consul")
s.consulIP = consul.NetworkSettings.IPAddress s.consulIP = consul.NetworkSettings.IPAddress
config := api.DefaultConfig() config := api.DefaultConfig()
config.Address = s.consulIP + ":8500" config.Address = s.consulIP + ":8500"
consulClient, err := api.NewClient(config) s.createConsulClient(config, c)
if err != nil {
c.Fatalf("Error creating consul client. %v", err)
}
s.consulClient = consulClient
// Wait for consul to elect itself leader // Wait for consul to elect itself leader
err = try.Do(3*time.Second, func() error { err := s.waitToElectConsulLeader()
leader, err := consulClient.Status().Leader() c.Assert(err, checker.IsNil)
}
func (s *ConsulCatalogSuite) waitToElectConsulLeader() error {
return try.Do(3*time.Second, func() error {
leader, err := s.consulClient.Status().Leader()
if err != nil || len(leader) == 0 { if err != nil || len(leader) == 0 {
return fmt.Errorf("Leader not found. %v", err) return fmt.Errorf("Leader not found. %v", err)
@ -44,7 +45,17 @@ func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) {
return nil return nil
}) })
c.Assert(err, checker.IsNil) }
func (s *ConsulCatalogSuite) createConsulClient(config *api.Config, c *check.C) *api.Client {
consulClient, err := api.NewClient(config)
if err != nil {
c.Fatalf("Error creating consul client. %v", err)
}
s.consulClient = consulClient
return consulClient
}
func (s *ConsulCatalogSuite) startConsulService(c *check.C) {
} }
func (s *ConsulCatalogSuite) registerService(name string, address string, port int, tags []string) error { func (s *ConsulCatalogSuite) registerService(name string, address string, port int, tags []string) error {
@ -332,3 +343,50 @@ func (s *ConsulCatalogSuite) TestBasicAuthSimpleService(c *check.C) {
err = try.Request(req, 5*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody()) err = try.Request(req, 5*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
} }
func (s *ConsulCatalogSuite) TestRetryWithConsulServer(c *check.C) {
//Scale consul to 0 to be able to start traefik before and test retry
s.composeProject.Scale(c, "consul", 0)
cmd, display := s.traefikCmd(
withConfigFile("fixtures/consul_catalog/simple.toml"),
"--consulCatalog",
"--consulCatalog.watch=false",
"--consulCatalog.exposedByDefault=true",
"--consulCatalog.endpoint="+s.consulIP+":8500",
"--consulCatalog.domain=consul.localhost")
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
// Wait for Traefik to turn ready.
err = try.GetRequest("http://127.0.0.1:8000/", 2*time.Second, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil)
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil)
req.Host = "test.consul.localhost"
// Request should fail
err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusNotFound), try.HasBody())
c.Assert(err, checker.IsNil)
// Scale consul to 1
s.composeProject.Scale(c, "consul", 1)
s.waitToElectConsulLeader()
nginx := s.composeProject.Container(c, "nginx1")
// Register service
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
// Provider consul catalog should be present
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 10*time.Second, try.BodyContains("consul_catalog"))
c.Assert(err, checker.IsNil)
// Should be ok
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil)
}

View file

@ -26,7 +26,7 @@ func NewIPWhitelister(whitelistStrings []string) (*IPWhiteLister, error) {
whiteLister := IPWhiteLister{} whiteLister := IPWhiteLister{}
ip, err := whitelist.NewIP(whitelistStrings) ip, err := whitelist.NewIP(whitelistStrings, false)
if err != nil { if err != nil {
return nil, fmt.Errorf("parsing CIDR whitelist %s: %v", whitelistStrings, err) return nil, fmt.Errorf("parsing CIDR whitelist %s: %v", whitelistStrings, err)
} }

View file

@ -110,7 +110,7 @@ func getChangedHealthyKeys(currState []string, prevState []string) ([]string, []
return fun.Keys(addedKeys).([]string), fun.Keys(removedKeys).([]string) return fun.Keys(addedKeys).([]string), fun.Keys(removedKeys).([]string)
} }
func (p *CatalogProvider) watchHealthState(stopCh <-chan struct{}, watchCh chan<- map[string][]string) { func (p *CatalogProvider) watchHealthState(stopCh <-chan struct{}, watchCh chan<- map[string][]string, errorCh chan<- error) {
health := p.client.Health() health := p.client.Health()
catalog := p.client.Catalog() catalog := p.client.Catalog()
@ -131,6 +131,7 @@ func (p *CatalogProvider) watchHealthState(stopCh <-chan struct{}, watchCh chan<
healthyState, meta, err := health.State("passing", options) healthyState, meta, err := health.State("passing", options)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to retrieve health checks") log.WithError(err).Error("Failed to retrieve health checks")
errorCh <- err
return return
} }
@ -154,6 +155,7 @@ func (p *CatalogProvider) watchHealthState(stopCh <-chan struct{}, watchCh chan<
data, _, err := catalog.Services(&api.QueryOptions{}) data, _, err := catalog.Services(&api.QueryOptions{})
if err != nil { if err != nil {
log.Errorf("Failed to list services: %s", err) log.Errorf("Failed to list services: %s", err)
errorCh <- err
return return
} }
@ -186,7 +188,7 @@ type Service struct {
Nodes []string Nodes []string
} }
func (p *CatalogProvider) watchCatalogServices(stopCh <-chan struct{}, watchCh chan<- map[string][]string) { func (p *CatalogProvider) watchCatalogServices(stopCh <-chan struct{}, watchCh chan<- map[string][]string, errorCh chan<- error) {
catalog := p.client.Catalog() catalog := p.client.Catalog()
safe.Go(func() { safe.Go(func() {
@ -205,6 +207,7 @@ func (p *CatalogProvider) watchCatalogServices(stopCh <-chan struct{}, watchCh c
data, meta, err := catalog.Services(options) data, meta, err := catalog.Services(options)
if err != nil { if err != nil {
log.Errorf("Failed to list services: %s", err) log.Errorf("Failed to list services: %s", err)
errorCh <- err
return return
} }
@ -220,6 +223,7 @@ func (p *CatalogProvider) watchCatalogServices(stopCh <-chan struct{}, watchCh c
nodes, _, err := catalog.Service(key, "", &api.QueryOptions{}) nodes, _, err := catalog.Service(key, "", &api.QueryOptions{})
if err != nil { if err != nil {
log.Errorf("Failed to get detail of service %s: %s", key, err) log.Errorf("Failed to get detail of service %s: %s", key, err)
errorCh <- err
return return
} }
nodesID := getServiceIds(nodes) nodesID := getServiceIds(nodes)
@ -393,17 +397,19 @@ func (p *CatalogProvider) getBasicAuth(tags []string) []string {
return []string{} return []string{}
} }
func (p *CatalogProvider) hasStickinessLabel(tags []string) bool { func (p *CatalogProvider) getSticky(tags []string) string {
stickinessTag := p.getTag(types.LabelBackendLoadbalancerStickiness, tags, "")
stickyTag := p.getTag(types.LabelBackendLoadbalancerSticky, tags, "") stickyTag := p.getTag(types.LabelBackendLoadbalancerSticky, tags, "")
if len(stickyTag) > 0 { if len(stickyTag) > 0 {
log.Warn("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness) log.Warnf("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness)
} else {
stickyTag = "false"
} }
return stickyTag
}
stickiness := len(stickinessTag) > 0 && strings.EqualFold(strings.TrimSpace(stickinessTag), "true") func (p *CatalogProvider) hasStickinessLabel(tags []string) bool {
sticky := len(stickyTag) > 0 && strings.EqualFold(strings.TrimSpace(stickyTag), "true") stickinessTag := p.getTag(types.LabelBackendLoadbalancerStickiness, tags, "")
return stickiness || sticky return len(stickinessTag) > 0 && strings.EqualFold(strings.TrimSpace(stickinessTag), "true")
} }
func (p *CatalogProvider) getStickinessCookieName(tags []string) string { func (p *CatalogProvider) getStickinessCookieName(tags []string) string {
@ -461,6 +467,7 @@ func (p *CatalogProvider) buildConfig(catalog []catalogUpdate) *types.Configurat
"getBackendName": p.getBackendName, "getBackendName": p.getBackendName,
"getBackendAddress": p.getBackendAddress, "getBackendAddress": p.getBackendAddress,
"getBasicAuth": p.getBasicAuth, "getBasicAuth": p.getBasicAuth,
"getSticky": p.getSticky,
"hasStickinessLabel": p.hasStickinessLabel, "hasStickinessLabel": p.hasStickinessLabel,
"getStickinessCookieName": p.getStickinessCookieName, "getStickinessCookieName": p.getStickinessCookieName,
"getAttribute": p.getAttribute, "getAttribute": p.getAttribute,
@ -531,9 +538,10 @@ func (p *CatalogProvider) getNodes(index map[string][]string) ([]catalogUpdate,
func (p *CatalogProvider) watch(configurationChan chan<- types.ConfigMessage, stop chan bool) error { func (p *CatalogProvider) watch(configurationChan chan<- types.ConfigMessage, stop chan bool) error {
stopCh := make(chan struct{}) stopCh := make(chan struct{})
watchCh := make(chan map[string][]string) watchCh := make(chan map[string][]string)
errorCh := make(chan error)
p.watchHealthState(stopCh, watchCh) p.watchHealthState(stopCh, watchCh, errorCh)
p.watchCatalogServices(stopCh, watchCh) p.watchCatalogServices(stopCh, watchCh, errorCh)
defer close(stopCh) defer close(stopCh)
defer close(watchCh) defer close(watchCh)
@ -556,6 +564,8 @@ func (p *CatalogProvider) watch(configurationChan chan<- types.ConfigMessage, st
ProviderName: "consul_catalog", ProviderName: "consul_catalog",
Configuration: configuration, Configuration: configuration,
} }
case err := <-errorCh:
return err
} }
} }
} }

View file

@ -9,6 +9,7 @@ import (
"github.com/BurntSushi/ty/fun" "github.com/BurntSushi/ty/fun"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"github.com/hashicorp/consul/api" "github.com/hashicorp/consul/api"
"github.com/stretchr/testify/assert"
) )
func TestConsulCatalogGetFrontendRule(t *testing.T) { func TestConsulCatalogGetFrontendRule(t *testing.T) {
@ -891,3 +892,45 @@ func TestConsulCatalogGetBasicAuth(t *testing.T) {
}) })
} }
} }
func TestConsulCatalogHasStickinessLabel(t *testing.T) {
testCases := []struct {
desc string
tags []string
expected bool
}{
{
desc: "label missing",
tags: []string{},
expected: false,
},
{
desc: "stickiness=true",
tags: []string{
types.LabelBackendLoadbalancerStickiness + "=true",
},
expected: true,
},
{
desc: "stickiness=false",
tags: []string{
types.LabelBackendLoadbalancerStickiness + "=false",
},
expected: false,
},
}
provider := &CatalogProvider{
Prefix: "traefik",
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := provider.hasStickinessLabel(test.tags)
assert.Equal(t, actual, test.expected)
})
}
}

View file

@ -275,6 +275,7 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con
"hasMaxConnLabels": p.hasMaxConnLabels, "hasMaxConnLabels": p.hasMaxConnLabels,
"getMaxConnAmount": p.getMaxConnAmount, "getMaxConnAmount": p.getMaxConnAmount,
"getMaxConnExtractorFunc": p.getMaxConnExtractorFunc, "getMaxConnExtractorFunc": p.getMaxConnExtractorFunc,
"getSticky": p.getSticky,
"getStickinessCookieName": p.getStickinessCookieName, "getStickinessCookieName": p.getStickinessCookieName,
"hasStickinessLabel": p.hasStickinessLabel, "hasStickinessLabel": p.hasStickinessLabel,
"getIsBackendLBSwarm": p.getIsBackendLBSwarm, "getIsBackendLBSwarm": p.getIsBackendLBSwarm,
@ -465,10 +466,10 @@ func (p *Provider) getServiceProtocol(container dockerData, serviceName string)
func (p *Provider) hasLoadBalancerLabel(container dockerData) bool { func (p *Provider) hasLoadBalancerLabel(container dockerData) bool {
_, errMethod := getLabel(container, types.LabelBackendLoadbalancerMethod) _, errMethod := getLabel(container, types.LabelBackendLoadbalancerMethod)
_, errSticky := getLabel(container, types.LabelBackendLoadbalancerSticky) _, errSticky := getLabel(container, types.LabelBackendLoadbalancerSticky)
if errMethod != nil && errSticky != nil { _, errStickiness := getLabel(container, types.LabelBackendLoadbalancerStickiness)
return false _, errCookieName := getLabel(container, types.LabelBackendLoadbalancerStickinessCookieName)
}
return true return errMethod == nil || errSticky == nil || errStickiness == nil || errCookieName == nil
} }
func (p *Provider) hasMaxConnLabels(container dockerData) bool { func (p *Provider) hasMaxConnLabels(container dockerData) bool {
@ -645,14 +646,18 @@ func (p *Provider) getWeight(container dockerData) string {
} }
func (p *Provider) hasStickinessLabel(container dockerData) bool { func (p *Provider) hasStickinessLabel(container dockerData) bool {
_, errStickiness := getLabel(container, types.LabelBackendLoadbalancerStickiness) labelStickiness, errStickiness := getLabel(container, types.LabelBackendLoadbalancerStickiness)
return errStickiness == nil && len(labelStickiness) > 0 && strings.EqualFold(strings.TrimSpace(labelStickiness), "true")
}
label, errSticky := getLabel(container, types.LabelBackendLoadbalancerSticky) func (p *Provider) getSticky(container dockerData) string {
if len(label) > 0 { if label, err := getLabel(container, types.LabelBackendLoadbalancerSticky); err == nil {
log.Warn("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness) if len(label) > 0 {
log.Warnf("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness)
}
return label
} }
return "false"
return errStickiness == nil || (errSticky == nil && strings.EqualFold(strings.TrimSpace(label), "true"))
} }
func (p *Provider) getStickinessCookieName(container dockerData) string { func (p *Provider) getStickinessCookieName(container dockerData) string {
@ -823,7 +828,7 @@ func (p *Provider) listServices(ctx context.Context, dockerClient client.APIClie
return []dockerData{}, err return []dockerData{}, err
} }
networkListArgs := filters.NewArgs() networkListArgs := filters.NewArgs()
networkListArgs.Add("driver", "overlay") networkListArgs.Add("scope", "swarm")
networkList, err := dockerClient.NetworkList(ctx, dockertypes.NetworkListOptions{Filters: networkListArgs}) networkList, err := dockerClient.NetworkList(ctx, dockertypes.NetworkListOptions{Filters: networkListArgs})

View file

@ -10,6 +10,7 @@ import (
docker "github.com/docker/docker/api/types" docker "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
"github.com/stretchr/testify/assert"
) )
func TestDockerGetFrontendName(t *testing.T) { func TestDockerGetFrontendName(t *testing.T) {
@ -1051,3 +1052,42 @@ func TestDockerLoadDockerConfig(t *testing.T) {
}) })
} }
} }
func TestDockerHasStickinessLabel(t *testing.T) {
testCases := []struct {
desc string
container docker.ContainerJSON
expected bool
}{
{
desc: "no stickiness-label",
container: containerJSON(),
expected: false,
},
{
desc: "stickiness true",
container: containerJSON(labels(map[string]string{
types.LabelBackendLoadbalancerStickiness: "true",
})),
expected: true,
},
{
desc: "stickiness false",
container: containerJSON(labels(map[string]string{
types.LabelBackendLoadbalancerStickiness: "false",
})),
expected: false,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
dockerData := parseContainer(test.container)
provider := &Provider{}
actual := provider.hasStickinessLabel(dockerData)
assert.Equal(t, actual, test.expected)
})
}
}

View file

@ -184,6 +184,7 @@ func (p *Provider) loadECSConfig(ctx context.Context, client *awsClient) (*types
"getFrontendRule": p.getFrontendRule, "getFrontendRule": p.getFrontendRule,
"getBasicAuth": p.getBasicAuth, "getBasicAuth": p.getBasicAuth,
"getLoadBalancerMethod": p.getLoadBalancerMethod, "getLoadBalancerMethod": p.getLoadBalancerMethod,
"getLoadBalancerSticky": p.getLoadBalancerSticky,
"hasStickinessLabel": p.hasStickinessLabel, "hasStickinessLabel": p.hasStickinessLabel,
"getStickinessCookieName": p.getStickinessCookieName, "getStickinessCookieName": p.getStickinessCookieName,
} }
@ -485,16 +486,20 @@ func (p *Provider) getFirstInstanceLabel(instances []ecsInstance, labelName stri
return "" return ""
} }
func (p *Provider) getLoadBalancerSticky(instances []ecsInstance) string {
if len(instances) > 0 {
label := p.getFirstInstanceLabel(instances, types.LabelBackendLoadbalancerSticky)
if label != "" {
log.Warnf("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness)
return label
}
}
return "false"
}
func (p *Provider) hasStickinessLabel(instances []ecsInstance) bool { func (p *Provider) hasStickinessLabel(instances []ecsInstance) bool {
stickinessLabel := p.getFirstInstanceLabel(instances, types.LabelBackendLoadbalancerStickiness) stickinessLabel := p.getFirstInstanceLabel(instances, types.LabelBackendLoadbalancerStickiness)
return len(stickinessLabel) > 0 && strings.EqualFold(strings.TrimSpace(stickinessLabel), "true")
stickyLabel := p.getFirstInstanceLabel(instances, types.LabelBackendLoadbalancerSticky)
if len(stickyLabel) > 0 {
log.Warn("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness)
}
stickiness := len(stickinessLabel) > 0 && strings.EqualFold(strings.TrimSpace(stickinessLabel), "true")
sticky := len(stickyLabel) > 0 && strings.EqualFold(strings.TrimSpace(stickyLabel), "true")
return stickiness || sticky
} }
func (p *Provider) getStickinessCookieName(instances []ecsInstance) string { func (p *Provider) getStickinessCookieName(instances []ecsInstance) string {

View file

@ -18,7 +18,6 @@ import (
"github.com/containous/traefik/log" "github.com/containous/traefik/log"
"github.com/containous/traefik/provider" "github.com/containous/traefik/provider"
"github.com/containous/traefik/safe" "github.com/containous/traefik/safe"
"github.com/containous/traefik/server/cookie"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"k8s.io/client-go/pkg/api/v1" "k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/pkg/apis/extensions/v1beta1" "k8s.io/client-go/pkg/apis/extensions/v1beta1"
@ -180,11 +179,13 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
log.Warnf("Unknown value '%s' for %s, falling back to %s", passHostHeaderAnnotation, types.LabelFrontendPassHostHeader, PassHostHeader) log.Warnf("Unknown value '%s' for %s, falling back to %s", passHostHeaderAnnotation, types.LabelFrontendPassHostHeader, PassHostHeader)
} }
if realm := i.Annotations[annotationKubernetesAuthRealm]; realm != "" && realm != traefikDefaultRealm { if realm := i.Annotations[annotationKubernetesAuthRealm]; realm != "" && realm != traefikDefaultRealm {
return nil, errors.New("no realm customization supported") log.Errorf("Value for annotation %q on ingress %s/%s invalid: no realm customization supported", annotationKubernetesAuthRealm, i.ObjectMeta.Namespace, i.ObjectMeta.Name)
delete(templateObjects.Backends, r.Host+pa.Path)
continue
} }
witelistSourceRangeAnnotation := i.Annotations[annotationKubernetesWhitelistSourceRange] whitelistSourceRangeAnnotation := i.Annotations[annotationKubernetesWhitelistSourceRange]
whitelistSourceRange := provider.SplitAndTrimString(witelistSourceRangeAnnotation) whitelistSourceRange := provider.SplitAndTrimString(whitelistSourceRangeAnnotation)
if _, exists := templateObjects.Frontends[r.Host+pa.Path]; !exists { if _, exists := templateObjects.Frontends[r.Host+pa.Path]; !exists {
basicAuthCreds, err := handleBasicAuthConfig(i, k8sClient) basicAuthCreds, err := handleBasicAuthConfig(i, k8sClient)
@ -247,13 +248,15 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
templateObjects.Backends[r.Host+pa.Path].LoadBalancer.Method = "drr" templateObjects.Backends[r.Host+pa.Path].LoadBalancer.Method = "drr"
} }
if len(service.Annotations[types.LabelBackendLoadbalancerSticky]) > 0 { if sticky := service.Annotations[types.LabelBackendLoadbalancerSticky]; len(sticky) > 0 {
log.Warn("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness) log.Warnf("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness)
templateObjects.Backends[r.Host+pa.Path].LoadBalancer.Sticky = strings.EqualFold(strings.TrimSpace(sticky), "true")
} }
if service.Annotations[types.LabelBackendLoadbalancerSticky] == "true" || service.Annotations[types.LabelBackendLoadbalancerStickiness] == "true" { if service.Annotations[types.LabelBackendLoadbalancerStickiness] == "true" {
templateObjects.Backends[r.Host+pa.Path].LoadBalancer.Stickiness = &types.Stickiness{ templateObjects.Backends[r.Host+pa.Path].LoadBalancer.Stickiness = &types.Stickiness{}
CookieName: cookie.GenerateName(r.Host + pa.Path), if cookieName := service.Annotations[types.LabelBackendLoadbalancerStickinessCookieName]; len(cookieName) > 0 {
templateObjects.Backends[r.Host+pa.Path].LoadBalancer.Stickiness.CookieName = cookieName
} }
} }

View file

@ -885,9 +885,7 @@ func TestServiceAnnotations(t *testing.T) {
CircuitBreaker: nil, CircuitBreaker: nil,
LoadBalancer: &types.LoadBalancer{ LoadBalancer: &types.LoadBalancer{
Method: "wrr", Method: "wrr",
Stickiness: &types.Stickiness{ Sticky: true,
CookieName: "_4155f",
},
}, },
}, },
}, },
@ -916,7 +914,7 @@ func TestServiceAnnotations(t *testing.T) {
}, },
} }
assert.Equal(t, expected, actual) assert.EqualValues(t, expected, actual)
} }
func TestIngressAnnotations(t *testing.T) { func TestIngressAnnotations(t *testing.T) {
@ -1091,6 +1089,34 @@ func TestIngressAnnotations(t *testing.T) {
}, },
}, },
}, },
{
ObjectMeta: v1.ObjectMeta{
Namespace: "testing",
Annotations: map[string]string{
"ingress.kubernetes.io/auth-realm": "customized",
},
},
Spec: v1beta1.IngressSpec{
Rules: []v1beta1.IngressRule{
{
Host: "auth-realm-customized",
IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []v1beta1.HTTPIngressPath{
{
Path: "/auth-realm-customized",
Backend: v1beta1.IngressBackend{
ServiceName: "service1",
ServicePort: intstr.FromInt(80),
},
},
},
},
},
},
},
},
},
} }
services := []*v1.Service{ services := []*v1.Service{
{ {

View file

@ -144,6 +144,7 @@ func (p *Provider) loadConfig() *types.Configuration {
"Get": p.get, "Get": p.get,
"SplitGet": p.splitGet, "SplitGet": p.splitGet,
"Last": p.last, "Last": p.last,
"getSticky": p.getSticky,
"hasStickinessLabel": p.hasStickinessLabel, "hasStickinessLabel": p.hasStickinessLabel,
"getStickinessCookieName": p.getStickinessCookieName, "getStickinessCookieName": p.getStickinessCookieName,
} }
@ -242,14 +243,19 @@ func (p *Provider) checkConstraints(keys ...string) bool {
return true return true
} }
func (p *Provider) hasStickinessLabel(rootPath string) bool { func (p *Provider) getSticky(rootPath string) string {
stickiness, err := p.kvclient.Exists(rootPath + "/loadbalancer/stickiness") stickyValue := p.get("", rootPath, "/loadbalancer", "/sticky")
if err != nil { if len(stickyValue) > 0 {
log.Debugf("Error occurs when check stickiness: %v", err) log.Warnf("Deprecated configuration found: %s. Please use %s.", "loadbalancer/sticky", "loadbalancer/stickiness")
} else {
stickyValue = "false"
} }
sticky := p.get("false", rootPath, "/loadbalancer", "/sticky") return stickyValue
}
return stickiness || (len(sticky) != 0 && strings.EqualFold(strings.TrimSpace(sticky), "true")) func (p *Provider) hasStickinessLabel(rootPath string) bool {
stickinessValue := p.get("false", rootPath, "/loadbalancer", "/stickiness")
return len(stickinessValue) > 0 && strings.EqualFold(strings.TrimSpace(stickinessValue), "true")
} }
func (p *Provider) getStickinessCookieName(rootPath string) string { func (p *Provider) getStickinessCookieName(rootPath string) string {

110
provider/kv/kv_mock_test.go Normal file
View file

@ -0,0 +1,110 @@
package kv
import (
"errors"
"strings"
"github.com/containous/traefik/types"
"github.com/docker/libkv/store"
)
type KvMock struct {
Provider
}
func (provider *KvMock) loadConfig() *types.Configuration {
return nil
}
// Override Get/List to return a error
type KvError struct {
Get error
List error
}
// Extremely limited mock store so we can test initialization
type Mock struct {
Error KvError
KVPairs []*store.KVPair
WatchTreeMethod func() <-chan []*store.KVPair
}
func (s *Mock) Put(key string, value []byte, opts *store.WriteOptions) error {
return errors.New("Put not supported")
}
func (s *Mock) Get(key string) (*store.KVPair, error) {
if err := s.Error.Get; err != nil {
return nil, err
}
for _, kvPair := range s.KVPairs {
if kvPair.Key == key {
return kvPair, nil
}
}
return nil, store.ErrKeyNotFound
}
func (s *Mock) Delete(key string) error {
return errors.New("Delete not supported")
}
// Exists mock
func (s *Mock) Exists(key string) (bool, error) {
if err := s.Error.Get; err != nil {
return false, err
}
for _, kvPair := range s.KVPairs {
if strings.HasPrefix(kvPair.Key, key) {
return true, nil
}
}
return false, store.ErrKeyNotFound
}
// Watch mock
func (s *Mock) Watch(key string, stopCh <-chan struct{}) (<-chan *store.KVPair, error) {
return nil, errors.New("Watch not supported")
}
// WatchTree mock
func (s *Mock) WatchTree(prefix string, stopCh <-chan struct{}) (<-chan []*store.KVPair, error) {
return s.WatchTreeMethod(), nil
}
// NewLock mock
func (s *Mock) NewLock(key string, options *store.LockOptions) (store.Locker, error) {
return nil, errors.New("NewLock not supported")
}
// List mock
func (s *Mock) List(prefix string) ([]*store.KVPair, error) {
if err := s.Error.List; err != nil {
return nil, err
}
kv := []*store.KVPair{}
for _, kvPair := range s.KVPairs {
if strings.HasPrefix(kvPair.Key, prefix) && !strings.ContainsAny(strings.TrimPrefix(kvPair.Key, prefix), "/") {
kv = append(kv, kvPair)
}
}
return kv, nil
}
// DeleteTree mock
func (s *Mock) DeleteTree(prefix string) error {
return errors.New("DeleteTree not supported")
}
// AtomicPut mock
func (s *Mock) AtomicPut(key string, value []byte, previous *store.KVPair, opts *store.WriteOptions) (bool, *store.KVPair, error) {
return false, nil, errors.New("AtomicPut not supported")
}
// AtomicDelete mock
func (s *Mock) AtomicDelete(key string, previous *store.KVPair) (bool, error) {
return false, errors.New("AtomicDelete not supported")
}
// Close mock
func (s *Mock) Close() {}

View file

@ -1,10 +1,8 @@
package kv package kv
import ( import (
"errors"
"reflect" "reflect"
"sort" "sort"
"strings"
"testing" "testing"
"time" "time"
@ -237,14 +235,6 @@ func TestKvLast(t *testing.T) {
} }
} }
type KvMock struct {
Provider
}
func (provider *KvMock) loadConfig() *types.Configuration {
return nil
}
func TestKvWatchTree(t *testing.T) { func TestKvWatchTree(t *testing.T) {
returnedChans := make(chan chan []*store.KVPair) returnedChans := make(chan chan []*store.KVPair)
provider := &KvMock{ provider := &KvMock{
@ -288,91 +278,6 @@ func TestKvWatchTree(t *testing.T) {
} }
} }
// Override Get/List to return a error
type KvError struct {
Get error
List error
}
// Extremely limited mock store so we can test initialization
type Mock struct {
Error KvError
KVPairs []*store.KVPair
WatchTreeMethod func() <-chan []*store.KVPair
}
func (s *Mock) Put(key string, value []byte, opts *store.WriteOptions) error {
return errors.New("Put not supported")
}
func (s *Mock) Get(key string) (*store.KVPair, error) {
if err := s.Error.Get; err != nil {
return nil, err
}
for _, kvPair := range s.KVPairs {
if kvPair.Key == key {
return kvPair, nil
}
}
return nil, store.ErrKeyNotFound
}
func (s *Mock) Delete(key string) error {
return errors.New("Delete not supported")
}
// Exists mock
func (s *Mock) Exists(key string) (bool, error) {
return false, errors.New("Exists not supported")
}
// Watch mock
func (s *Mock) Watch(key string, stopCh <-chan struct{}) (<-chan *store.KVPair, error) {
return nil, errors.New("Watch not supported")
}
// WatchTree mock
func (s *Mock) WatchTree(prefix string, stopCh <-chan struct{}) (<-chan []*store.KVPair, error) {
return s.WatchTreeMethod(), nil
}
// NewLock mock
func (s *Mock) NewLock(key string, options *store.LockOptions) (store.Locker, error) {
return nil, errors.New("NewLock not supported")
}
// List mock
func (s *Mock) List(prefix string) ([]*store.KVPair, error) {
if err := s.Error.List; err != nil {
return nil, err
}
kv := []*store.KVPair{}
for _, kvPair := range s.KVPairs {
if strings.HasPrefix(kvPair.Key, prefix) && !strings.ContainsAny(strings.TrimPrefix(kvPair.Key, prefix), "/") {
kv = append(kv, kvPair)
}
}
return kv, nil
}
// DeleteTree mock
func (s *Mock) DeleteTree(prefix string) error {
return errors.New("DeleteTree not supported")
}
// AtomicPut mock
func (s *Mock) AtomicPut(key string, value []byte, previous *store.KVPair, opts *store.WriteOptions) (bool, *store.KVPair, error) {
return false, nil, errors.New("AtomicPut not supported")
}
// AtomicDelete mock
func (s *Mock) AtomicDelete(key string, previous *store.KVPair) (bool, error) {
return false, errors.New("AtomicDelete not supported")
}
// Close mock
func (s *Mock) Close() {}
func TestKVLoadConfig(t *testing.T) { func TestKVLoadConfig(t *testing.T) {
provider := &Provider{ provider := &Provider{
Prefix: "traefik", Prefix: "traefik",
@ -463,3 +368,55 @@ func TestKVLoadConfig(t *testing.T) {
t.Fatalf("expected %+v, got %+v", expected.Frontends, actual.Frontends) t.Fatalf("expected %+v, got %+v", expected.Frontends, actual.Frontends)
} }
} }
func TestKVHasStickinessLabel(t *testing.T) {
testCases := []struct {
desc string
KVPairs []*store.KVPair
expected bool
}{
{
desc: "without option",
expected: false,
},
{
desc: "with cookie name without stickiness=true",
KVPairs: []*store.KVPair{
{
Key: "loadbalancer/stickiness/cookiename",
Value: []byte("foo"),
},
},
expected: false,
},
{
desc: "stickiness=true",
KVPairs: []*store.KVPair{
{
Key: "loadbalancer/stickiness",
Value: []byte("true"),
},
},
expected: true,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
p := &Provider{
kvclient: &Mock{
KVPairs: test.KVPairs,
},
}
actual := p.hasStickinessLabel("")
if actual != test.expected {
t.Fatalf("expected %v, got %v", test.expected, actual)
}
})
}
}

View file

@ -188,8 +188,9 @@ func (p *Provider) loadMarathonConfig() *types.Configuration {
"getMaxConnAmount": p.getMaxConnAmount, "getMaxConnAmount": p.getMaxConnAmount,
"getLoadBalancerMethod": p.getLoadBalancerMethod, "getLoadBalancerMethod": p.getLoadBalancerMethod,
"getCircuitBreakerExpression": p.getCircuitBreakerExpression, "getCircuitBreakerExpression": p.getCircuitBreakerExpression,
"getStickinessCookieName": p.getStickinessCookieName, "getSticky": p.getSticky,
"hasStickinessLabel": p.hasStickinessLabel, "hasStickinessLabel": p.hasStickinessLabel,
"getStickinessCookieName": p.getStickinessCookieName,
"hasHealthCheckLabels": p.hasHealthCheckLabels, "hasHealthCheckLabels": p.hasHealthCheckLabels,
"getHealthCheckPath": p.getHealthCheckPath, "getHealthCheckPath": p.getHealthCheckPath,
"getHealthCheckInterval": p.getHealthCheckInterval, "getHealthCheckInterval": p.getHealthCheckInterval,
@ -429,15 +430,17 @@ func (p *Provider) getProtocol(application marathon.Application, serviceName str
return "http" return "http"
} }
func (p *Provider) hasStickinessLabel(application marathon.Application) bool { func (p *Provider) getSticky(application marathon.Application) string {
_, okStickiness := p.getAppLabel(application, types.LabelBackendLoadbalancerStickiness) if sticky, ok := p.getAppLabel(application, types.LabelBackendLoadbalancerSticky); ok {
log.Warnf("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness)
label, okSticky := p.getAppLabel(application, types.LabelBackendLoadbalancerSticky) return sticky
if len(label) > 0 {
log.Warn("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness)
} }
return "false"
}
return okStickiness || (okSticky && strings.EqualFold(strings.TrimSpace(label), "true")) func (p *Provider) hasStickinessLabel(application marathon.Application) bool {
labelStickiness, okStickiness := p.getAppLabel(application, types.LabelBackendLoadbalancerStickiness)
return okStickiness && len(labelStickiness) > 0 && strings.EqualFold(strings.TrimSpace(labelStickiness), "true")
} }
func (p *Provider) getStickinessCookieName(application marathon.Application) string { func (p *Provider) getStickinessCookieName(application marathon.Application) string {

View file

@ -854,9 +854,39 @@ func TestMarathonGetProtocol(t *testing.T) {
}) })
} }
} }
func TestMarathonGetSticky(t *testing.T) {
testCases := []struct {
desc string
application marathon.Application
expected string
}{
{
desc: "label missing",
application: application(),
expected: "false",
},
{
desc: "label existing",
application: application(label(types.LabelBackendLoadbalancerSticky, "true")),
expected: "true",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
provider := &Provider{}
actual := provider.getSticky(test.application)
if actual != test.expected {
t.Errorf("actual %q, expected %q", actual, test.expected)
}
})
}
}
func TestMarathonHasStickinessLabel(t *testing.T) { func TestMarathonHasStickinessLabel(t *testing.T) {
cases := []struct { testCases := []struct {
desc string desc string
application marathon.Application application marathon.Application
expected bool expected bool
@ -867,35 +897,26 @@ func TestMarathonHasStickinessLabel(t *testing.T) {
expected: false, expected: false,
}, },
{ {
desc: "label existing and value equals true (deprecated)", desc: "stickiness=true",
application: application(label(types.LabelBackendLoadbalancerSticky, "true")),
expected: true,
},
{
desc: "label existing and value equals false (deprecated)",
application: application(label(types.LabelBackendLoadbalancerSticky, "false")),
expected: false,
},
{
desc: "label existing and value equals true",
application: application(label(types.LabelBackendLoadbalancerStickiness, "true")), application: application(label(types.LabelBackendLoadbalancerStickiness, "true")),
expected: true, expected: true,
}, },
{ {
desc: "label existing and value equals false ", desc: "stickiness=false ",
application: application(label(types.LabelBackendLoadbalancerStickiness, "true")), application: application(label(types.LabelBackendLoadbalancerStickiness, "true")),
expected: true, expected: true,
}, },
} }
for _, c := range cases { for _, test := range testCases {
c := c test := test
t.Run(c.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
provider := &Provider{} provider := &Provider{}
actual := provider.hasStickinessLabel(c.application) actual := provider.hasStickinessLabel(test.application)
if actual != c.expected { if actual != test.expected {
t.Errorf("actual %q, expected %q", actual, c.expected) t.Errorf("actual %q, expected %q", actual, test.expected)
} }
}) })
} }

View file

@ -85,7 +85,7 @@ func (p *Provider) intervalPoll(client rancher.Client, updateConfiguration func(
_, cancel := context.WithCancel(context.Background()) _, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
ticker := time.NewTicker(time.Duration(p.RefreshSeconds)) ticker := time.NewTicker(time.Second * time.Duration(p.RefreshSeconds))
defer ticker.Stop() defer ticker.Stop()
var version string var version string

View file

@ -92,10 +92,10 @@ func (p *Provider) getLoadBalancerMethod(service rancherData) string {
func (p *Provider) hasLoadBalancerLabel(service rancherData) bool { func (p *Provider) hasLoadBalancerLabel(service rancherData) bool {
_, errMethod := getServiceLabel(service, types.LabelBackendLoadbalancerMethod) _, errMethod := getServiceLabel(service, types.LabelBackendLoadbalancerMethod)
_, errSticky := getServiceLabel(service, types.LabelBackendLoadbalancerSticky) _, errSticky := getServiceLabel(service, types.LabelBackendLoadbalancerSticky)
if errMethod != nil && errSticky != nil { _, errStickiness := getServiceLabel(service, types.LabelBackendLoadbalancerStickiness)
return false _, errCookieName := getServiceLabel(service, types.LabelBackendLoadbalancerStickinessCookieName)
}
return true return errMethod == nil || errSticky == nil || errStickiness == nil || errCookieName == nil
} }
func (p *Provider) hasCircuitBreakerLabel(service rancherData) bool { func (p *Provider) hasCircuitBreakerLabel(service rancherData) bool {
@ -112,15 +112,18 @@ func (p *Provider) getCircuitBreakerExpression(service rancherData) string {
return "NetworkErrorRatio() > 1" return "NetworkErrorRatio() > 1"
} }
func (p *Provider) hasStickinessLabel(service rancherData) bool { func (p *Provider) getSticky(service rancherData) string {
_, errStickiness := getServiceLabel(service, types.LabelBackendLoadbalancerStickiness) if _, err := getServiceLabel(service, types.LabelBackendLoadbalancerSticky); err == nil {
log.Warnf("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness)
label, errSticky := getServiceLabel(service, types.LabelBackendLoadbalancerSticky) return "true"
if len(label) > 0 {
log.Warn("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness)
} }
return "false"
}
return errStickiness == nil || (errSticky == nil && strings.EqualFold(strings.TrimSpace(label), "true")) func (p *Provider) hasStickinessLabel(service rancherData) bool {
labelStickiness, errStickiness := getServiceLabel(service, types.LabelBackendLoadbalancerStickiness)
return errStickiness == nil && len(labelStickiness) > 0 && strings.EqualFold(strings.TrimSpace(labelStickiness), "true")
} }
func (p *Provider) getStickinessCookieName(service rancherData, backendName string) string { func (p *Provider) getStickinessCookieName(service rancherData, backendName string) string {
@ -233,6 +236,7 @@ func (p *Provider) loadRancherConfig(services []rancherData) *types.Configuratio
"hasMaxConnLabels": p.hasMaxConnLabels, "hasMaxConnLabels": p.hasMaxConnLabels,
"getMaxConnAmount": p.getMaxConnAmount, "getMaxConnAmount": p.getMaxConnAmount,
"getMaxConnExtractorFunc": p.getMaxConnExtractorFunc, "getMaxConnExtractorFunc": p.getMaxConnExtractorFunc,
"getSticky": p.getSticky,
"hasStickinessLabel": p.hasStickinessLabel, "hasStickinessLabel": p.hasStickinessLabel,
"getStickinessCookieName": p.getStickinessCookieName, "getStickinessCookieName": p.getStickinessCookieName,
} }

View file

@ -6,6 +6,7 @@ import (
"testing" "testing"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"github.com/stretchr/testify/assert"
) )
func TestRancherServiceFilter(t *testing.T) { func TestRancherServiceFilter(t *testing.T) {
@ -597,3 +598,53 @@ func TestRancherLoadRancherConfig(t *testing.T) {
} }
} }
} }
func TestRancherHasStickinessLabel(t *testing.T) {
provider := &Provider{
Domain: "rancher.localhost",
}
testCases := []struct {
desc string
service rancherData
expected bool
}{
{
desc: "no labels",
service: rancherData{
Name: "test-service",
},
expected: false,
},
{
desc: "stickiness=true",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
types.LabelBackendLoadbalancerStickiness: "true",
},
},
expected: true,
},
{
desc: "stickiness=true",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
types.LabelBackendLoadbalancerStickiness: "false",
},
},
expected: false,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := provider.hasStickinessLabel(test.service)
assert.Equal(t, actual, test.expected)
})
}
}

51
server/header_rewriter.go Normal file
View file

@ -0,0 +1,51 @@
package server
import (
"net"
"net/http"
"os"
"github.com/containous/traefik/whitelist"
"github.com/vulcand/oxy/forward"
)
// NewHeaderRewriter Create a header rewriter
func NewHeaderRewriter(trustedIPs []string, insecure bool) (forward.ReqRewriter, error) {
IPs, err := whitelist.NewIP(trustedIPs, insecure)
if err != nil {
return nil, err
}
h, err := os.Hostname()
if err != nil {
h = "localhost"
}
return &headerRewriter{
secureRewriter: &forward.HeaderRewriter{TrustForwardHeader: true, Hostname: h},
insecureRewriter: &forward.HeaderRewriter{TrustForwardHeader: false, Hostname: h},
ips: IPs,
insecure: insecure,
}, nil
}
type headerRewriter struct {
secureRewriter forward.ReqRewriter
insecureRewriter forward.ReqRewriter
insecure bool
ips *whitelist.IP
}
func (h *headerRewriter) Rewrite(req *http.Request) {
clientIP, _, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
h.secureRewriter.Rewrite(req)
}
authorized, _, err := h.ips.Contains(clientIP)
if h.insecure || authorized {
h.secureRewriter.Rewrite(req)
} else {
h.insecureRewriter.Rewrite(req)
}
}

View file

@ -656,7 +656,7 @@ func (server *Server) prepareServer(entryPointName string, entryPoint *configura
} }
if entryPoint.ProxyProtocol != nil { if entryPoint.ProxyProtocol != nil {
IPs, err := whitelist.NewIP(entryPoint.ProxyProtocol.TrustedIPs) IPs, err := whitelist.NewIP(entryPoint.ProxyProtocol.TrustedIPs, entryPoint.ProxyProtocol.Insecure)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("Error creating whitelist: %s", err) return nil, nil, fmt.Errorf("Error creating whitelist: %s", err)
} }
@ -807,11 +807,19 @@ func (server *Server) loadConfig(configurations types.Configurations, globalConf
continue frontend continue frontend
} }
rewriter, err := NewHeaderRewriter(entryPoint.ForwardedHeaders.TrustedIPs, entryPoint.ForwardedHeaders.Insecure)
if err != nil {
log.Errorf("Error creating rewriter for frontend %s: %v", frontendName, err)
log.Errorf("Skipping frontend %s...", frontendName)
continue frontend
}
fwd, err := forward.New( fwd, err := forward.New(
forward.Logger(oxyLogger), forward.Logger(oxyLogger),
forward.PassHostHeader(frontend.PassHostHeader), forward.PassHostHeader(frontend.PassHostHeader),
forward.RoundTripper(roundTripper), forward.RoundTripper(roundTripper),
forward.ErrorHandler(errorHandler), forward.ErrorHandler(errorHandler),
forward.Rewriter(rewriter),
) )
if err != nil { if err != nil {
@ -1174,27 +1182,32 @@ func (server *Server) configureFrontends(frontends map[string]*types.Frontend) {
} }
func (*Server) configureBackends(backends map[string]*types.Backend) { func (*Server) configureBackends(backends map[string]*types.Backend) {
for backendName, backend := range backends { for backendName := range backends {
backend := backends[backendName]
if backend.LoadBalancer != nil && backend.LoadBalancer.Sticky { if backend.LoadBalancer != nil && backend.LoadBalancer.Sticky {
log.Warn("Deprecated configuration found: %s. Please use %s.", "backend.LoadBalancer.Sticky", "backend.LoadBalancer.Stickiness") log.Warnf("Deprecated configuration found: %s. Please use %s.", "backend.LoadBalancer.Sticky", "backend.LoadBalancer.Stickiness")
} }
_, err := types.NewLoadBalancerMethod(backend.LoadBalancer) _, err := types.NewLoadBalancerMethod(backend.LoadBalancer)
if err == nil { if err == nil {
if backend.LoadBalancer != nil && backend.LoadBalancer.Stickiness == nil && backend.LoadBalancer.Sticky { if backend.LoadBalancer != nil && backend.LoadBalancer.Stickiness == nil && backend.LoadBalancer.Sticky {
backend.LoadBalancer.Stickiness = &types.Stickiness{} backend.LoadBalancer.Stickiness = &types.Stickiness{
CookieName: "_TRAEFIK_BACKEND",
}
} }
} else { } else {
log.Debugf("Validation of load balancer method for backend %s failed: %s. Using default method wrr.", backendName, err) log.Debugf("Validation of load balancer method for backend %s failed: %s. Using default method wrr.", backendName, err)
var stickiness *types.Stickiness var stickiness *types.Stickiness
if backend.LoadBalancer != nil { if backend.LoadBalancer != nil {
if backend.LoadBalancer.Stickiness != nil { if backend.LoadBalancer.Stickiness == nil {
stickiness = backend.LoadBalancer.Stickiness if backend.LoadBalancer.Sticky {
} else if backend.LoadBalancer.Sticky { stickiness = &types.Stickiness{
if backend.LoadBalancer.Stickiness == nil { CookieName: "_TRAEFIK_BACKEND",
stickiness = &types.Stickiness{} }
} }
} else {
stickiness = backend.LoadBalancer.Stickiness
} }
} }
backend.LoadBalancer = &types.LoadBalancer{ backend.LoadBalancer = &types.LoadBalancer{

View file

@ -96,7 +96,10 @@ func TestPrepareServerTimeouts(t *testing.T) {
t.Parallel() t.Parallel()
entryPointName := "http" entryPointName := "http"
entryPoint := &configuration.EntryPoint{Address: "localhost:0"} entryPoint := &configuration.EntryPoint{
Address: "localhost:0",
ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true},
}
router := middlewares.NewHandlerSwitcher(mux.NewRouter()) router := middlewares.NewHandlerSwitcher(mux.NewRouter())
srv := NewServer(test.globalConfig) srv := NewServer(test.globalConfig)
@ -210,7 +213,9 @@ func TestServerLoadConfigHealthCheckOptions(t *testing.T) {
t.Run(fmt.Sprintf("%s/hc=%t", lbMethod, healthCheck != nil), func(t *testing.T) { t.Run(fmt.Sprintf("%s/hc=%t", lbMethod, healthCheck != nil), func(t *testing.T) {
globalConfig := configuration.GlobalConfiguration{ globalConfig := configuration.GlobalConfiguration{
EntryPoints: configuration.EntryPoints{ EntryPoints: configuration.EntryPoints{
"http": &configuration.EntryPoint{}, "http": &configuration.EntryPoint{
ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true},
},
}, },
HealthCheck: &configuration.HealthCheckConfig{Interval: flaeg.Duration(5 * time.Second)}, HealthCheck: &configuration.HealthCheckConfig{Interval: flaeg.Duration(5 * time.Second)},
} }
@ -383,7 +388,7 @@ func TestNewServerWithWhitelistSourceRange(t *testing.T) {
func TestServerLoadConfigEmptyBasicAuth(t *testing.T) { func TestServerLoadConfigEmptyBasicAuth(t *testing.T) {
globalConfig := configuration.GlobalConfiguration{ globalConfig := configuration.GlobalConfiguration{
EntryPoints: configuration.EntryPoints{ EntryPoints: configuration.EntryPoints{
"http": &configuration.EntryPoint{}, "http": &configuration.EntryPoint{ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true}},
}, },
} }
@ -492,7 +497,7 @@ func TestConfigureBackends(t *testing.T) {
} }
} }
func TestServerEntrypointWhitelistConfig(t *testing.T) { func TestServerEntryPointWhitelistConfig(t *testing.T) {
tests := []struct { tests := []struct {
desc string desc string
entrypoint *configuration.EntryPoint entrypoint *configuration.EntryPoint
@ -501,7 +506,8 @@ func TestServerEntrypointWhitelistConfig(t *testing.T) {
{ {
desc: "no whitelist middleware if no config on entrypoint", desc: "no whitelist middleware if no config on entrypoint",
entrypoint: &configuration.EntryPoint{ entrypoint: &configuration.EntryPoint{
Address: ":0", Address: ":0",
ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true},
}, },
wantMiddleware: false, wantMiddleware: false,
}, },
@ -512,6 +518,7 @@ func TestServerEntrypointWhitelistConfig(t *testing.T) {
WhitelistSourceRange: []string{ WhitelistSourceRange: []string{
"127.0.0.1/32", "127.0.0.1/32",
}, },
ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true},
}, },
wantMiddleware: true, wantMiddleware: true,
}, },
@ -633,7 +640,7 @@ func TestServerResponseEmptyBackend(t *testing.T) {
globalConfig := configuration.GlobalConfiguration{ globalConfig := configuration.GlobalConfiguration{
EntryPoints: configuration.EntryPoints{ EntryPoints: configuration.EntryPoints{
"http": &configuration.EntryPoint{}, "http": &configuration.EntryPoint{ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true}},
}, },
} }
dynamicConfigs := types.Configurations{"config": test.dynamicConfig(testServer.URL)} dynamicConfigs := types.Configurations{"config": test.dynamicConfig(testServer.URL)}

View file

@ -18,10 +18,10 @@
[backends."backend-{{$service}}".loadbalancer] [backends."backend-{{$service}}".loadbalancer]
method = "{{getAttribute "backend.loadbalancer" .Attributes "wrr"}}" method = "{{getAttribute "backend.loadbalancer" .Attributes "wrr"}}"
sticky = {{getAttribute "backend.loadbalancer.sticky" .Attributes "false"}} sticky = {{getSticky .Attributes}}
{{if hasStickinessLabel .Attributes}} {{if hasStickinessLabel .Attributes}}
[Backends."backend-{{$service}}".LoadBalancer.Stickiness] [backends."backend-{{$service}}".loadbalancer.stickiness]
cookieName = {{getStickinessCookieName .Attributes}} cookieName = "{{getStickinessCookieName .Attributes}}"
{{end}} {{end}}
{{if hasMaxconnAttributes .Attributes}} {{if hasMaxconnAttributes .Attributes}}

View file

@ -8,9 +8,10 @@
{{if hasLoadBalancerLabel $backend}} {{if hasLoadBalancerLabel $backend}}
[backends.backend-{{$backendName}}.loadbalancer] [backends.backend-{{$backendName}}.loadbalancer]
method = "{{getLoadBalancerMethod $backend}}" method = "{{getLoadBalancerMethod $backend}}"
sticky = {{getSticky $backend}}
{{if hasStickinessLabel $backend}} {{if hasStickinessLabel $backend}}
[Backends."{{$backendName}}".LoadBalancer.Stickiness] [backends.backend-{{$backendName}}.loadBalancer.stickiness]
cookieName = {{getStickinessCookieName $backend}} cookieName = "{{getStickinessCookieName $backend}}"
{{end}} {{end}}
{{end}} {{end}}

View file

@ -1,9 +1,10 @@
[backends]{{range $serviceName, $instances := .Services}} [backends]{{range $serviceName, $instances := .Services}}
[backends.backend-{{ $serviceName }}.loadbalancer] [backends.backend-{{ $serviceName }}.loadbalancer]
method = "{{ getLoadBalancerMethod $instances}}" method = "{{ getLoadBalancerMethod $instances}}"
sticky = {{ getLoadBalancerSticky $instances}}
{{if hasStickinessLabel $instances}} {{if hasStickinessLabel $instances}}
[Backends.backend-{{ $serviceName }}.LoadBalancer.Stickiness] [backends.backend-{{ $serviceName }}.loadbalancer.stickiness]
cookieName = {{getStickinessCookieName $instances}} cookieName = "{{getStickinessCookieName $instances}}"
{{end}} {{end}}
{{range $index, $i := $instances}} {{range $index, $i := $instances}}

View file

@ -6,9 +6,12 @@
{{end}} {{end}}
[backends."{{$backendName}}".loadbalancer] [backends."{{$backendName}}".loadbalancer]
method = "{{$backend.LoadBalancer.Method}}" method = "{{$backend.LoadBalancer.Method}}"
{{if $backend.LoadBalancer.Sticky}}
sticky = true
{{end}}
{{if $backend.LoadBalancer.Stickiness}} {{if $backend.LoadBalancer.Stickiness}}
[Backends."{{$backendName}}".LoadBalancer.Stickiness] [backends."{{$backendName}}".loadbalancer.stickiness]
cookieName = {{$backend.LoadBalancer.Stickiness.CookieName}} cookieName = "{{$backend.LoadBalancer.Stickiness.CookieName}}"
{{end}} {{end}}
{{range $serverName, $server := $backend.Servers}} {{range $serverName, $server := $backend.Servers}}
[backends."{{$backendName}}".servers."{{$serverName}}"] [backends."{{$backendName}}".servers."{{$serverName}}"]

View file

@ -16,9 +16,9 @@
{{with $loadBalancer}} {{with $loadBalancer}}
[backends."{{$backendName}}".loadBalancer] [backends."{{$backendName}}".loadBalancer]
method = "{{$loadBalancer}}" method = "{{$loadBalancer}}"
sticky = {{ Get "false" . "/loadbalancer/" "sticky" }} sticky = {{ getSticky . }}
{{if hasStickinessLabel $backend}} {{if hasStickinessLabel $backend}}
[Backends."{{$backendName}}".LoadBalancer.Stickiness] [backends."{{$backendName}}".loadBalancer.stickiness]
cookieName = {{getStickinessCookieName $backend}} cookieName = {{getStickinessCookieName $backend}}
{{end}} {{end}}
{{end}} {{end}}

View file

@ -20,9 +20,10 @@
{{ if hasLoadBalancerLabels $app }} {{ if hasLoadBalancerLabels $app }}
[backends."backend{{getBackend $app $serviceName }}".loadbalancer] [backends."backend{{getBackend $app $serviceName }}".loadbalancer]
method = "{{getLoadBalancerMethod $app }}" method = "{{getLoadBalancerMethod $app }}"
sticky = {{getSticky $app}}
{{if hasStickinessLabel $app}} {{if hasStickinessLabel $app}}
[Backends."backend{{getBackend $app $serviceName }}".LoadBalancer.Stickiness] [backends."backend{{getBackend $app $serviceName }}".loadbalancer.stickiness]
cookieName = {{getStickinessCookieName $app}} cookieName = "{{getStickinessCookieName $app}}"
{{end}} {{end}}
{{end}} {{end}}
{{ if hasCircuitBreakerLabels $app }} {{ if hasCircuitBreakerLabels $app }}

View file

@ -8,9 +8,10 @@
{{if hasLoadBalancerLabel $backend}} {{if hasLoadBalancerLabel $backend}}
[backends.backend-{{$backendName}}.loadbalancer] [backends.backend-{{$backendName}}.loadbalancer]
method = "{{getLoadBalancerMethod $backend}}" method = "{{getLoadBalancerMethod $backend}}"
sticky = {{getSticky $backend}}
{{if hasStickinessLabel $backend}} {{if hasStickinessLabel $backend}}
[Backends."{{$backendName}}".LoadBalancer.Stickiness] [backends.backend-{{$backendName}}.loadbalancer.stickiness]
cookieName = {{getStickinessCookieName $backend}} cookieName = "{{getStickinessCookieName $backend}}"
{{end}} {{end}}
{{end}} {{end}}

View file

@ -249,6 +249,12 @@ func (f *httpForwarder) copyRequest(req *http.Request, u *url.URL) *http.Request
if f.rewriter != nil { if f.rewriter != nil {
f.rewriter.Rewrite(outReq) f.rewriter.Rewrite(outReq)
} }
if req.ContentLength == 0 {
// https://github.com/golang/go/issues/16036: nil Body for http.Transport retries
outReq.Body = nil
}
return outReq return outReq
} }

View file

@ -11,26 +11,29 @@ import (
type IP struct { type IP struct {
whiteListsIPs []*net.IP whiteListsIPs []*net.IP
whiteListsNet []*net.IPNet whiteListsNet []*net.IPNet
insecure bool
} }
// NewIP builds a new IP given a list of CIDR-Strings to whitelist // NewIP builds a new IP given a list of CIDR-Strings to whitelist
func NewIP(whitelistStrings []string) (*IP, error) { func NewIP(whitelistStrings []string, insecure bool) (*IP, error) {
if len(whitelistStrings) == 0 { if len(whitelistStrings) == 0 && !insecure {
return nil, errors.New("no whiteListsNet provided") return nil, errors.New("no whiteListsNet provided")
} }
ip := IP{} ip := IP{}
for _, whitelistString := range whitelistStrings { if !insecure {
ipAddr := net.ParseIP(whitelistString) for _, whitelistString := range whitelistStrings {
if ipAddr != nil { ipAddr := net.ParseIP(whitelistString)
ip.whiteListsIPs = append(ip.whiteListsIPs, &ipAddr) if ipAddr != nil {
} else { ip.whiteListsIPs = append(ip.whiteListsIPs, &ipAddr)
_, whitelist, err := net.ParseCIDR(whitelistString) } else {
if err != nil { _, whitelist, err := net.ParseCIDR(whitelistString)
return nil, fmt.Errorf("parsing CIDR whitelist %s: %v", whitelist, err) if err != nil {
return nil, fmt.Errorf("parsing CIDR whitelist %s: %v", whitelist, err)
}
ip.whiteListsNet = append(ip.whiteListsNet, whitelist)
} }
ip.whiteListsNet = append(ip.whiteListsNet, whitelist)
} }
} }
@ -39,6 +42,10 @@ func NewIP(whitelistStrings []string) (*IP, error) {
// Contains checks if provided address is in the white list // Contains checks if provided address is in the white list
func (ip *IP) Contains(addr string) (bool, net.IP, error) { func (ip *IP) Contains(addr string) (bool, net.IP, error) {
if ip.insecure {
return true, nil, nil
}
ipAddr, err := ipFromRemoteAddr(addr) ipAddr, err := ipFromRemoteAddr(addr)
if err != nil { if err != nil {
return false, nil, fmt.Errorf("unable to parse address: %s: %s", addr, err) return false, nil, fmt.Errorf("unable to parse address: %s: %s", addr, err)
@ -50,6 +57,10 @@ func (ip *IP) Contains(addr string) (bool, net.IP, error) {
// ContainsIP checks if provided address is in the white list // ContainsIP checks if provided address is in the white list
func (ip *IP) ContainsIP(addr net.IP) (bool, error) { func (ip *IP) ContainsIP(addr net.IP) (bool, error) {
if ip.insecure {
return true, nil
}
for _, whiteListIP := range ip.whiteListsIPs { for _, whiteListIP := range ip.whiteListsIPs {
if whiteListIP.Equal(addr) { if whiteListIP.Equal(addr) {
return true, nil return true, nil

View file

@ -75,7 +75,7 @@ func TestNew(t *testing.T) {
test := test test := test
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
whitelister, err := NewIP(test.whitelistStrings) whitelister, err := NewIP(test.whitelistStrings, false)
if test.errMessage != "" { if test.errMessage != "" {
require.EqualError(t, err, test.errMessage) require.EqualError(t, err, test.errMessage)
} else { } else {
@ -275,7 +275,7 @@ func TestIsAllowed(t *testing.T) {
test := test test := test
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
whiteLister, err := NewIP(test.whitelistStrings) whiteLister, err := NewIP(test.whitelistStrings, false)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, whiteLister) require.NotNil(t, whiteLister)
@ -306,7 +306,7 @@ func TestBrokenIPs(t *testing.T) {
"\\&$§&/(", "\\&$§&/(",
} }
whiteLister, err := NewIP([]string{"1.2.3.4/24"}) whiteLister, err := NewIP([]string{"1.2.3.4/24"}, false)
require.NoError(t, err) require.NoError(t, err)
for _, testIP := range brokenIPs { for _, testIP := range brokenIPs {