Merge 'v1.4.0' into master
This commit is contained in:
commit
7192aa86b5
38 changed files with 1165 additions and 327 deletions
220
CHANGELOG.md
220
CHANGELOG.md
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
@ -361,6 +393,7 @@ type EntryPoint struct {
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
# Enable ProxyProtocol
|
||||||
[entryPoints.http.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"]
|
trustedIPs = ["127.0.0.1/32", "192.168.1.7"]
|
||||||
```
|
```
|
||||||
²
|
|
6
glide.lock
generated
6
glide.lock
generated
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
if len(label) > 0 {
|
|
||||||
log.Warn("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return errStickiness == nil || (errSticky == nil && strings.EqualFold(strings.TrimSpace(label), "true"))
|
func (p *Provider) getSticky(container dockerData) string {
|
||||||
|
if label, err := getLabel(container, types.LabelBackendLoadbalancerSticky); err == nil {
|
||||||
|
if len(label) > 0 {
|
||||||
|
log.Warnf("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness)
|
||||||
|
}
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
return "false"
|
||||||
}
|
}
|
||||||
|
|
||||||
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})
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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{
|
||||||
{
|
{
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
|
return stickyValue
|
||||||
}
|
}
|
||||||
sticky := p.get("false", rootPath, "/loadbalancer", "/sticky")
|
|
||||||
|
|
||||||
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
110
provider/kv/kv_mock_test.go
Normal 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() {}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
51
server/header_rewriter.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,28 +1182,33 @@ 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 {
|
|
||||||
stickiness = backend.LoadBalancer.Stickiness
|
|
||||||
} else if backend.LoadBalancer.Sticky {
|
|
||||||
if backend.LoadBalancer.Stickiness == nil {
|
if backend.LoadBalancer.Stickiness == nil {
|
||||||
stickiness = &types.Stickiness{}
|
if backend.LoadBalancer.Sticky {
|
||||||
|
stickiness = &types.Stickiness{
|
||||||
|
CookieName: "_TRAEFIK_BACKEND",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
stickiness = backend.LoadBalancer.Stickiness
|
||||||
|
}
|
||||||
}
|
}
|
||||||
backend.LoadBalancer = &types.LoadBalancer{
|
backend.LoadBalancer = &types.LoadBalancer{
|
||||||
Method: "wrr",
|
Method: "wrr",
|
||||||
|
|
|
@ -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
|
||||||
|
@ -502,6 +507,7 @@ 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)}
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
|
@ -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}}"]
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
|
@ -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 }}
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
||||||
|
|
6
vendor/github.com/vulcand/oxy/forward/fwd.go
generated
vendored
6
vendor/github.com/vulcand/oxy/forward/fwd.go
generated
vendored
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,16 +11,18 @@ 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{}
|
||||||
|
|
||||||
|
if !insecure {
|
||||||
for _, whitelistString := range whitelistStrings {
|
for _, whitelistString := range whitelistStrings {
|
||||||
ipAddr := net.ParseIP(whitelistString)
|
ipAddr := net.ParseIP(whitelistString)
|
||||||
if ipAddr != nil {
|
if ipAddr != nil {
|
||||||
|
@ -33,12 +35,17 @@ func NewIP(whitelistStrings []string) (*IP, error) {
|
||||||
ip.whiteListsNet = append(ip.whiteListsNet, whitelist)
|
ip.whiteListsNet = append(ip.whiteListsNet, whitelist)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &ip, nil
|
return &ip, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue