diff --git a/CHANGELOG.md b/CHANGELOG.md index acc1bc899..837a06fc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,225 @@ # 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) [All Commits](https://github.com/containous/traefik/compare/v1.4.0-rc4...v1.4.0-rc5) diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index 3b3e704f4..a3fb63de3 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -14,11 +14,13 @@ import ( "time" "github.com/Sirupsen/logrus" + "github.com/cenk/backoff" "github.com/containous/flaeg" "github.com/containous/staert" "github.com/containous/traefik/acme" "github.com/containous/traefik/cluster" "github.com/containous/traefik/configuration" + "github.com/containous/traefik/job" "github.com/containous/traefik/log" "github.com/containous/traefik/provider/ecs" "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} } 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) os.Exit(-1) } @@ -275,7 +285,7 @@ func configureLogging(globalConfiguration *configuration.GlobalConfiguration) { // configure log output file logFile := globalConfiguration.TraefikLogsFile 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 { logFile = globalConfiguration.TraefikLog.FilePath diff --git a/configuration/configuration.go b/configuration/configuration.go index 7ac80dd3c..225a99c60 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -5,7 +5,6 @@ import ( "fmt" "io/ioutil" "os" - "regexp" "strings" "time" @@ -87,14 +86,25 @@ type GlobalConfiguration struct { DynamoDB *dynamodb.Provider `description:"Enable DynamoDB backend with default settings" export:"true"` } -// SetEffectiveConfiguration adds missing configuration parameters derived from -// existing ones. It also takes care of maintaining backwards compatibility. +// SetEffectiveConfiguration adds missing configuration parameters derived from existing ones. +// It also takes care of maintaining backwards compatibility. func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) { 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"} } + // 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. if gc.LifeCycle == nil { 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. // It's a comma-separated list, so we split it. func (ep *EntryPoints) Set(value string) error { - result, err := parseEntryPointsConfiguration(value) - if err != nil { - return err - } + result := parseEntryPointsConfiguration(value) var configTLS *TLS - if len(result["TLS"]) > 0 { + if len(result["tls"]) > 0 { certs := Certificates{} - if err := certs.Set(result["TLS"]); err != nil { + if err := certs.Set(result["tls"]); err != nil { return err } configTLS = &TLS{ Certificates: certs, } - } else if len(result["TLSACME"]) > 0 { + } else if len(result["tls_acme"]) > 0 { configTLS = &TLS{ Certificates: Certificates{}, } } - if len(result["CA"]) > 0 { - files := strings.Split(result["CA"], ",") + if len(result["ca"]) > 0 { + files := strings.Split(result["ca"], ",") configTLS.ClientCAFiles = files } 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{ - EntryPoint: result["RedirectEntryPoint"], - Regex: result["RedirectRegex"], - Replacement: result["RedirectReplacement"], + EntryPoint: result["redirect_entrypoint"], + Regex: result["redirect_regex"], + Replacement: result["redirect_replacement"], } } whiteListSourceRange := []string{} - if len(result["WhiteListSourceRange"]) > 0 { - whiteListSourceRange = strings.Split(result["WhiteListSourceRange"], ",") + if len(result["whitelistsourcerange"]) > 0 { + whiteListSourceRange = strings.Split(result["whitelistsourcerange"], ",") } - compress := toBool(result, "Compress") + compress := toBool(result, "compress") var proxyProtocol *ProxyProtocol - if len(result["ProxyProtocol"]) > 0 { - trustedIPs := strings.Split(result["ProxyProtocol"], ",") + ppTrustedIPs := result["proxyprotocol_trustedips"] + if len(result["proxyprotocol_insecure"]) > 0 || len(ppTrustedIPs) > 0 { proxyProtocol = &ProxyProtocol{ - TrustedIPs: trustedIPs, + Insecure: toBool(result, "proxyprotocol_insecure"), + } + if len(ppTrustedIPs) > 0 { + proxyProtocol.TrustedIPs = strings.Split(ppTrustedIPs, ",") } } - (*ep)[result["Name"]] = &EntryPoint{ - Address: result["Address"], + // TODO must be changed to false by default in the next breaking version. + 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, Redirect: redirect, Compress: compress, WhitelistSourceRange: whiteListSourceRange, ProxyProtocol: proxyProtocol, + ForwardedHeaders: forwardedHeaders, } return nil } -func parseEntryPointsConfiguration(value string) (map[string]string, error) { - regex := regexp.MustCompile(`(?:Name:(?P\S*))\s*(?:Address:(?P
\S*))?\s*(?:TLS:(?P\S*))?\s*(?PTLS)?\s*(?:CA:(?P\S*))?\s*(?:Redirect\.EntryPoint:(?P\S*))?\s*(?:Redirect\.Regex:(?P\S*))?\s*(?:Redirect\.Replacement:(?P\S*))?\s*(?:Compress:(?P\S*))?\s*(?:WhiteListSourceRange:(?P\S*))?\s*(?:ProxyProtocol\.TrustedIPs:(?P\S*))?`) - match := regex.FindAllStringSubmatch(value, -1) - if match == nil { - return nil, fmt.Errorf("bad EntryPoints format: %s", value) - } - matchResult := match[0] - result := make(map[string]string) - for i, name := range regex.SubexpNames() { - if i != 0 && len(matchResult[i]) != 0 { - result[name] = matchResult[i] +func parseEntryPointsConfiguration(raw string) map[string]string { + sections := strings.Fields(raw) + + config := make(map[string]string) + for _, part := range sections { + field := strings.SplitN(part, ":", 2) + name := strings.ToLower(strings.Replace(field[0], ".", "_", -1)) + if len(field) > 1 { + config[name] = field[1] + } else { + 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 { @@ -359,8 +391,9 @@ type EntryPoint struct { Redirect *Redirect `export:"true"` Auth *types.Auth `export:"true"` WhitelistSourceRange []string - Compress bool `export:"true"` - ProxyProtocol *ProxyProtocol `export:"true"` + Compress bool `export:"true"` + ProxyProtocol *ProxyProtocol `export:"true"` + ForwardedHeaders *ForwardedHeaders `export:"true"` } // 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 type ProxyProtocol struct { + Insecure bool + TrustedIPs []string +} + +// ForwardedHeaders Trust client forwarding headers +type ForwardedHeaders struct { + Insecure bool TrustedIPs []string } diff --git a/configuration/configuration_test.go b/configuration/configuration_test.go index 7f949b6e6..215036b06 100644 --- a/configuration/configuration_test.go +++ b/configuration/configuration_test.go @@ -1,7 +1,6 @@ package configuration import ( - "fmt" "testing" "time" @@ -22,36 +21,37 @@ func Test_parseEntryPointsConfiguration(t *testing.T) { }{ { 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{ - "Name": "foo", - "Address": "bar", - "CA": "car", - "TLS": "goo", - "TLSACME": "TLS", - "RedirectEntryPoint": "RedirectEntryPoint", - "RedirectRegex": "RedirectRegex", - "RedirectReplacement": "RedirectReplacement", - "WhiteListSourceRange": "WhiteListSourceRange", - "ProxyProtocol": "192.168.0.1", - "Compress": "true", + "name": "foo", + "address": ":8000", + "ca": "car", + "tls": "goo", + "tls_acme": "TLS", + "redirect_entrypoint": "RedirectEntryPoint", + "redirect_regex": "RedirectRegex", + "redirect_replacement": "RedirectReplacement", + "whitelistsourcerange": "WhiteListSourceRange", + "proxyprotocol_trustedips": "192.168.0.1", + "proxyprotocol_insecure": "false", + "compress": "true", }, }, { name: "compress on", - value: "Name:foo Compress:on", + value: "name:foo Compress:on", expectedResult: map[string]string{ - "Name": "foo", - "Compress": "on", + "name": "foo", + "compress": "on", }, }, { name: "TLS", value: "Name:foo TLS:goo TLS", expectedResult: map[string]string{ - "Name": "foo", - "TLS": "goo", - "TLSACME": "TLS", + "name": "foo", + "tls": "goo", + "tls_acme": "TLS", }, }, } @@ -61,14 +61,7 @@ func Test_parseEntryPointsConfiguration(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() - conf, err := parseEntryPointsConfiguration(test.value) - if err != nil { - t.Error(err) - } - - for key, value := range conf { - fmt.Println(key, value) - } + conf := parseEntryPointsConfiguration(test.value) assert.Len(t, conf, len(test.expectedResult)) assert.Equal(t, test.expectedResult, conf) @@ -139,11 +132,11 @@ func TestEntryPoints_Set(t *testing.T) { expectedEntryPoint *EntryPoint }{ { - name: "all parameters", - 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", + name: "all parameters camelcase", + 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: "bar", + Address: ":8000", Redirect: &Redirect{ EntryPoint: "RedirectEntryPoint", Regex: "RedirectRegex", @@ -153,6 +146,9 @@ func TestEntryPoints_Set(t *testing.T) { 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"}, @@ -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", expression: "Name:foo Compress:on", @@ -172,6 +268,7 @@ func TestEntryPoints_Set(t *testing.T) { expectedEntryPoint: &EntryPoint{ Compress: true, WhitelistSourceRange: []string{}, + ForwardedHeaders: &ForwardedHeaders{Insecure: true}, }, }, { @@ -181,6 +278,7 @@ func TestEntryPoints_Set(t *testing.T) { expectedEntryPoint: &EntryPoint{ Compress: true, WhitelistSourceRange: []string{}, + ForwardedHeaders: &ForwardedHeaders{Insecure: true}, }, }, } diff --git a/docs/basics.md b/docs/basics.md index c3ef994b3..024c8a144 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -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. If not, a new backend will be assigned. -To activate sticky session: ```toml [backends] [backends.backend1] + # Enable sticky session [backends.backend1.loadbalancer.stickiness] -``` -To customize the cookie name: - -```toml -[backends] - [backends.backend1] - [backends.backend1.loadbalancer.stickiness] - cookieName = "my_cookie" + # Customize the cookie name + # + # Optional + # Default: a sha1 (6 chars) + # + # cookieName = "my_cookie" ``` The deprecated way: diff --git a/docs/configuration/entrypoints.md b/docs/configuration/entrypoints.md index 3defd7fd5..7bb55c8f3 100644 --- a/docs/configuration/entrypoints.md +++ b/docs/configuration/entrypoints.md @@ -188,17 +188,52 @@ To enable IP whitelisting at the entrypoint level. 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. -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 [entryPoints] [entryPoints.http] - address = ":80" - [entryPoints.http.proxyProtocol] - trustedIPs = ["127.0.0.1/32", "192.168.1.7"] + address = ":80" + + # Enable ProxyProtocol + [entryPoints.http.proxyProtocol] + # List of trusted IPs + # + # Required + # Default: [] + # + trustedIPs = ["127.0.0.1/32", "192.168.1.7"] + + # Insecure mode FOR TESTING ENVIRONNEMENT ONLY + # + # Optional + # Default: false + # + # insecure = true +``` + +## Forwarded Header + +Only IPs in `trustedIPs` will be authorize to trust the client forwarded headers (`X-Forwarded-*`). + +```toml +[entryPoints] + [entryPoints.http] + address = ":80" + + # Enable Forwarded Headers + [entryPoints.http.forwardedHeaders] + # List of trusted IPs + # + # Required + # Default: [] + # + trustedIPs = ["127.0.0.1/32", "192.168.1.7"] ``` -² \ No newline at end of file diff --git a/glide.lock b/glide.lock index 37d27ce29..851172607 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 6881e0574a026dde78c9d7f03a4aa56f6c6dc585d2b6d3e6f4ae1a94810b7f88 -updated: 2017-10-02T18:32:16.848940186+02:00 +hash: b929df3c022d8a67b8d174f81257a502670c3683e801f8a53283c2965c921c6e +updated: 2017-10-16T23:09:16.848940186+02:00 imports: - name: cloud.google.com/go version: 2e6a95edb1071d750f6d7db777bf66cd2997af6c @@ -485,7 +485,7 @@ imports: - name: github.com/urfave/negroni version: 490e6a555d47ca891a89a150d0c1ef3922dfffe9 - name: github.com/vulcand/oxy - version: 648088ee0902cf8d8337826ae2a82444008720e2 + version: c024a22700b56debed9a9c8dbb297210a7ece02d repo: https://github.com/containous/oxy.git vcs: git subpackages: diff --git a/glide.yaml b/glide.yaml index 779024699..aa8aeddea 100644 --- a/glide.yaml +++ b/glide.yaml @@ -12,7 +12,7 @@ import: - package: github.com/cenk/backoff - package: github.com/containous/flaeg - package: github.com/vulcand/oxy - version: 648088ee0902cf8d8337826ae2a82444008720e2 + version: c024a22700b56debed9a9c8dbb297210a7ece02d repo: https://github.com/containous/oxy.git vcs: git subpackages: diff --git a/integration/consul_catalog_test.go b/integration/consul_catalog_test.go index 1ec14f6a1..fdd132122 100644 --- a/integration/consul_catalog_test.go +++ b/integration/consul_catalog_test.go @@ -24,19 +24,20 @@ func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) { s.composeProject.Start(c) consul := s.composeProject.Container(c, "consul") - s.consulIP = consul.NetworkSettings.IPAddress config := api.DefaultConfig() config.Address = s.consulIP + ":8500" - consulClient, err := api.NewClient(config) - if err != nil { - c.Fatalf("Error creating consul client. %v", err) - } - s.consulClient = consulClient + s.createConsulClient(config, c) // Wait for consul to elect itself leader - err = try.Do(3*time.Second, func() error { - leader, err := consulClient.Status().Leader() + err := s.waitToElectConsulLeader() + 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 { return fmt.Errorf("Leader not found. %v", err) @@ -44,7 +45,17 @@ func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) { 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 { @@ -332,3 +343,50 @@ func (s *ConsulCatalogSuite) TestBasicAuthSimpleService(c *check.C) { err = try.Request(req, 5*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody()) 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) +} diff --git a/middlewares/ip_whitelister.go b/middlewares/ip_whitelister.go index 490986c29..29eb76063 100644 --- a/middlewares/ip_whitelister.go +++ b/middlewares/ip_whitelister.go @@ -26,7 +26,7 @@ func NewIPWhitelister(whitelistStrings []string) (*IPWhiteLister, error) { whiteLister := IPWhiteLister{} - ip, err := whitelist.NewIP(whitelistStrings) + ip, err := whitelist.NewIP(whitelistStrings, false) if err != nil { return nil, fmt.Errorf("parsing CIDR whitelist %s: %v", whitelistStrings, err) } diff --git a/provider/consul/consul_catalog.go b/provider/consul/consul_catalog.go index dd2c75156..db9c7bbc2 100644 --- a/provider/consul/consul_catalog.go +++ b/provider/consul/consul_catalog.go @@ -110,7 +110,7 @@ func getChangedHealthyKeys(currState []string, prevState []string) ([]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() catalog := p.client.Catalog() @@ -131,6 +131,7 @@ func (p *CatalogProvider) watchHealthState(stopCh <-chan struct{}, watchCh chan< healthyState, meta, err := health.State("passing", options) if err != nil { log.WithError(err).Error("Failed to retrieve health checks") + errorCh <- err return } @@ -154,6 +155,7 @@ func (p *CatalogProvider) watchHealthState(stopCh <-chan struct{}, watchCh chan< data, _, err := catalog.Services(&api.QueryOptions{}) if err != nil { log.Errorf("Failed to list services: %s", err) + errorCh <- err return } @@ -186,7 +188,7 @@ type Service struct { 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() safe.Go(func() { @@ -205,6 +207,7 @@ func (p *CatalogProvider) watchCatalogServices(stopCh <-chan struct{}, watchCh c data, meta, err := catalog.Services(options) if err != nil { log.Errorf("Failed to list services: %s", err) + errorCh <- err return } @@ -220,6 +223,7 @@ func (p *CatalogProvider) watchCatalogServices(stopCh <-chan struct{}, watchCh c nodes, _, err := catalog.Service(key, "", &api.QueryOptions{}) if err != nil { log.Errorf("Failed to get detail of service %s: %s", key, err) + errorCh <- err return } nodesID := getServiceIds(nodes) @@ -393,17 +397,19 @@ func (p *CatalogProvider) getBasicAuth(tags []string) []string { return []string{} } -func (p *CatalogProvider) hasStickinessLabel(tags []string) bool { - stickinessTag := p.getTag(types.LabelBackendLoadbalancerStickiness, tags, "") - +func (p *CatalogProvider) getSticky(tags []string) string { stickyTag := p.getTag(types.LabelBackendLoadbalancerSticky, tags, "") 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") - sticky := len(stickyTag) > 0 && strings.EqualFold(strings.TrimSpace(stickyTag), "true") - return stickiness || sticky +func (p *CatalogProvider) hasStickinessLabel(tags []string) bool { + stickinessTag := p.getTag(types.LabelBackendLoadbalancerStickiness, tags, "") + return len(stickinessTag) > 0 && strings.EqualFold(strings.TrimSpace(stickinessTag), "true") } func (p *CatalogProvider) getStickinessCookieName(tags []string) string { @@ -461,6 +467,7 @@ func (p *CatalogProvider) buildConfig(catalog []catalogUpdate) *types.Configurat "getBackendName": p.getBackendName, "getBackendAddress": p.getBackendAddress, "getBasicAuth": p.getBasicAuth, + "getSticky": p.getSticky, "hasStickinessLabel": p.hasStickinessLabel, "getStickinessCookieName": p.getStickinessCookieName, "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 { stopCh := make(chan struct{}) watchCh := make(chan map[string][]string) + errorCh := make(chan error) - p.watchHealthState(stopCh, watchCh) - p.watchCatalogServices(stopCh, watchCh) + p.watchHealthState(stopCh, watchCh, errorCh) + p.watchCatalogServices(stopCh, watchCh, errorCh) defer close(stopCh) defer close(watchCh) @@ -556,6 +564,8 @@ func (p *CatalogProvider) watch(configurationChan chan<- types.ConfigMessage, st ProviderName: "consul_catalog", Configuration: configuration, } + case err := <-errorCh: + return err } } } diff --git a/provider/consul/consul_catalog_test.go b/provider/consul/consul_catalog_test.go index 7cd3c25eb..8ffdd3e0d 100644 --- a/provider/consul/consul_catalog_test.go +++ b/provider/consul/consul_catalog_test.go @@ -9,6 +9,7 @@ import ( "github.com/BurntSushi/ty/fun" "github.com/containous/traefik/types" "github.com/hashicorp/consul/api" + "github.com/stretchr/testify/assert" ) 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) + }) + } +} diff --git a/provider/docker/docker.go b/provider/docker/docker.go index ec0b7f707..2b7f80454 100644 --- a/provider/docker/docker.go +++ b/provider/docker/docker.go @@ -275,6 +275,7 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con "hasMaxConnLabels": p.hasMaxConnLabels, "getMaxConnAmount": p.getMaxConnAmount, "getMaxConnExtractorFunc": p.getMaxConnExtractorFunc, + "getSticky": p.getSticky, "getStickinessCookieName": p.getStickinessCookieName, "hasStickinessLabel": p.hasStickinessLabel, "getIsBackendLBSwarm": p.getIsBackendLBSwarm, @@ -465,10 +466,10 @@ func (p *Provider) getServiceProtocol(container dockerData, serviceName string) func (p *Provider) hasLoadBalancerLabel(container dockerData) bool { _, errMethod := getLabel(container, types.LabelBackendLoadbalancerMethod) _, errSticky := getLabel(container, types.LabelBackendLoadbalancerSticky) - if errMethod != nil && errSticky != nil { - return false - } - return true + _, errStickiness := getLabel(container, types.LabelBackendLoadbalancerStickiness) + _, errCookieName := getLabel(container, types.LabelBackendLoadbalancerStickinessCookieName) + + return errMethod == nil || errSticky == nil || errStickiness == nil || errCookieName == nil } 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 { - _, 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) +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 errStickiness == nil || (errSticky == nil && strings.EqualFold(strings.TrimSpace(label), "true")) + return "false" } func (p *Provider) getStickinessCookieName(container dockerData) string { @@ -823,7 +828,7 @@ func (p *Provider) listServices(ctx context.Context, dockerClient client.APIClie return []dockerData{}, err } networkListArgs := filters.NewArgs() - networkListArgs.Add("driver", "overlay") + networkListArgs.Add("scope", "swarm") networkList, err := dockerClient.NetworkList(ctx, dockertypes.NetworkListOptions{Filters: networkListArgs}) diff --git a/provider/docker/docker_test.go b/provider/docker/docker_test.go index a8f3a0bf0..718890e3e 100644 --- a/provider/docker/docker_test.go +++ b/provider/docker/docker_test.go @@ -10,6 +10,7 @@ import ( docker "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/go-connections/nat" + "github.com/stretchr/testify/assert" ) 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) + }) + } +} diff --git a/provider/ecs/ecs.go b/provider/ecs/ecs.go index 6f825420a..8b09821a4 100644 --- a/provider/ecs/ecs.go +++ b/provider/ecs/ecs.go @@ -184,6 +184,7 @@ func (p *Provider) loadECSConfig(ctx context.Context, client *awsClient) (*types "getFrontendRule": p.getFrontendRule, "getBasicAuth": p.getBasicAuth, "getLoadBalancerMethod": p.getLoadBalancerMethod, + "getLoadBalancerSticky": p.getLoadBalancerSticky, "hasStickinessLabel": p.hasStickinessLabel, "getStickinessCookieName": p.getStickinessCookieName, } @@ -485,16 +486,20 @@ func (p *Provider) getFirstInstanceLabel(instances []ecsInstance, labelName stri 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 { stickinessLabel := p.getFirstInstanceLabel(instances, types.LabelBackendLoadbalancerStickiness) - - 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 + return len(stickinessLabel) > 0 && strings.EqualFold(strings.TrimSpace(stickinessLabel), "true") } func (p *Provider) getStickinessCookieName(instances []ecsInstance) string { diff --git a/provider/kubernetes/kubernetes.go b/provider/kubernetes/kubernetes.go index 9b8b27e96..602796422 100644 --- a/provider/kubernetes/kubernetes.go +++ b/provider/kubernetes/kubernetes.go @@ -18,7 +18,6 @@ import ( "github.com/containous/traefik/log" "github.com/containous/traefik/provider" "github.com/containous/traefik/safe" - "github.com/containous/traefik/server/cookie" "github.com/containous/traefik/types" "k8s.io/client-go/pkg/api/v1" "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) } 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] - whitelistSourceRange := provider.SplitAndTrimString(witelistSourceRangeAnnotation) + whitelistSourceRangeAnnotation := i.Annotations[annotationKubernetesWhitelistSourceRange] + whitelistSourceRange := provider.SplitAndTrimString(whitelistSourceRangeAnnotation) if _, exists := templateObjects.Frontends[r.Host+pa.Path]; !exists { 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" } - if len(service.Annotations[types.LabelBackendLoadbalancerSticky]) > 0 { - log.Warn("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness) + if sticky := service.Annotations[types.LabelBackendLoadbalancerSticky]; len(sticky) > 0 { + 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" { - templateObjects.Backends[r.Host+pa.Path].LoadBalancer.Stickiness = &types.Stickiness{ - CookieName: cookie.GenerateName(r.Host + pa.Path), + if service.Annotations[types.LabelBackendLoadbalancerStickiness] == "true" { + templateObjects.Backends[r.Host+pa.Path].LoadBalancer.Stickiness = &types.Stickiness{} + if cookieName := service.Annotations[types.LabelBackendLoadbalancerStickinessCookieName]; len(cookieName) > 0 { + templateObjects.Backends[r.Host+pa.Path].LoadBalancer.Stickiness.CookieName = cookieName } } diff --git a/provider/kubernetes/kubernetes_test.go b/provider/kubernetes/kubernetes_test.go index 5d33f73de..32750f4ce 100644 --- a/provider/kubernetes/kubernetes_test.go +++ b/provider/kubernetes/kubernetes_test.go @@ -885,9 +885,7 @@ func TestServiceAnnotations(t *testing.T) { CircuitBreaker: nil, LoadBalancer: &types.LoadBalancer{ Method: "wrr", - Stickiness: &types.Stickiness{ - CookieName: "_4155f", - }, + Sticky: true, }, }, }, @@ -916,7 +914,7 @@ func TestServiceAnnotations(t *testing.T) { }, } - assert.Equal(t, expected, actual) + assert.EqualValues(t, expected, actual) } 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{ { diff --git a/provider/kv/kv.go b/provider/kv/kv.go index 36beb8650..1ebb4298f 100644 --- a/provider/kv/kv.go +++ b/provider/kv/kv.go @@ -144,6 +144,7 @@ func (p *Provider) loadConfig() *types.Configuration { "Get": p.get, "SplitGet": p.splitGet, "Last": p.last, + "getSticky": p.getSticky, "hasStickinessLabel": p.hasStickinessLabel, "getStickinessCookieName": p.getStickinessCookieName, } @@ -242,14 +243,19 @@ func (p *Provider) checkConstraints(keys ...string) bool { return true } -func (p *Provider) hasStickinessLabel(rootPath string) bool { - stickiness, err := p.kvclient.Exists(rootPath + "/loadbalancer/stickiness") - if err != nil { - log.Debugf("Error occurs when check stickiness: %v", err) +func (p *Provider) getSticky(rootPath string) string { + stickyValue := p.get("", rootPath, "/loadbalancer", "/sticky") + if len(stickyValue) > 0 { + log.Warnf("Deprecated configuration found: %s. Please use %s.", "loadbalancer/sticky", "loadbalancer/stickiness") + } else { + stickyValue = "false" } - sticky := p.get("false", rootPath, "/loadbalancer", "/sticky") + return stickyValue +} - return stickiness || (len(sticky) != 0 && strings.EqualFold(strings.TrimSpace(sticky), "true")) +func (p *Provider) hasStickinessLabel(rootPath string) bool { + stickinessValue := p.get("false", rootPath, "/loadbalancer", "/stickiness") + return len(stickinessValue) > 0 && strings.EqualFold(strings.TrimSpace(stickinessValue), "true") } func (p *Provider) getStickinessCookieName(rootPath string) string { diff --git a/provider/kv/kv_mock_test.go b/provider/kv/kv_mock_test.go new file mode 100644 index 000000000..fddb2192e --- /dev/null +++ b/provider/kv/kv_mock_test.go @@ -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() {} diff --git a/provider/kv/kv_test.go b/provider/kv/kv_test.go index 3daf2d98d..a78c0ac90 100644 --- a/provider/kv/kv_test.go +++ b/provider/kv/kv_test.go @@ -1,10 +1,8 @@ package kv import ( - "errors" "reflect" "sort" - "strings" "testing" "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) { returnedChans := make(chan chan []*store.KVPair) 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) { provider := &Provider{ Prefix: "traefik", @@ -463,3 +368,55 @@ func TestKVLoadConfig(t *testing.T) { 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) + } + }) + } +} diff --git a/provider/marathon/marathon.go b/provider/marathon/marathon.go index d8639b13c..41e61a703 100644 --- a/provider/marathon/marathon.go +++ b/provider/marathon/marathon.go @@ -188,8 +188,9 @@ func (p *Provider) loadMarathonConfig() *types.Configuration { "getMaxConnAmount": p.getMaxConnAmount, "getLoadBalancerMethod": p.getLoadBalancerMethod, "getCircuitBreakerExpression": p.getCircuitBreakerExpression, - "getStickinessCookieName": p.getStickinessCookieName, + "getSticky": p.getSticky, "hasStickinessLabel": p.hasStickinessLabel, + "getStickinessCookieName": p.getStickinessCookieName, "hasHealthCheckLabels": p.hasHealthCheckLabels, "getHealthCheckPath": p.getHealthCheckPath, "getHealthCheckInterval": p.getHealthCheckInterval, @@ -429,15 +430,17 @@ func (p *Provider) getProtocol(application marathon.Application, serviceName str return "http" } -func (p *Provider) hasStickinessLabel(application marathon.Application) bool { - _, okStickiness := p.getAppLabel(application, types.LabelBackendLoadbalancerStickiness) - - label, okSticky := p.getAppLabel(application, types.LabelBackendLoadbalancerSticky) - if len(label) > 0 { - log.Warn("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness) +func (p *Provider) getSticky(application marathon.Application) string { + if sticky, ok := p.getAppLabel(application, types.LabelBackendLoadbalancerSticky); ok { + log.Warnf("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness) + return sticky } + 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 { diff --git a/provider/marathon/marathon_test.go b/provider/marathon/marathon_test.go index e78fd3f0b..4c58ab62e 100644 --- a/provider/marathon/marathon_test.go +++ b/provider/marathon/marathon_test.go @@ -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) { - cases := []struct { + testCases := []struct { desc string application marathon.Application expected bool @@ -867,35 +897,26 @@ func TestMarathonHasStickinessLabel(t *testing.T) { expected: false, }, { - desc: "label existing and value equals true (deprecated)", - 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", + desc: "stickiness=true", application: application(label(types.LabelBackendLoadbalancerStickiness, "true")), expected: true, }, { - desc: "label existing and value equals false ", + desc: "stickiness=false ", application: application(label(types.LabelBackendLoadbalancerStickiness, "true")), expected: true, }, } - for _, c := range cases { - c := c - t.Run(c.desc, func(t *testing.T) { + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { t.Parallel() + provider := &Provider{} - actual := provider.hasStickinessLabel(c.application) - if actual != c.expected { - t.Errorf("actual %q, expected %q", actual, c.expected) + actual := provider.hasStickinessLabel(test.application) + if actual != test.expected { + t.Errorf("actual %q, expected %q", actual, test.expected) } }) } diff --git a/provider/rancher/metadata.go b/provider/rancher/metadata.go index 24c9c585b..37c470ecc 100644 --- a/provider/rancher/metadata.go +++ b/provider/rancher/metadata.go @@ -85,7 +85,7 @@ func (p *Provider) intervalPoll(client rancher.Client, updateConfiguration func( _, cancel := context.WithCancel(context.Background()) defer cancel() - ticker := time.NewTicker(time.Duration(p.RefreshSeconds)) + ticker := time.NewTicker(time.Second * time.Duration(p.RefreshSeconds)) defer ticker.Stop() var version string diff --git a/provider/rancher/rancher.go b/provider/rancher/rancher.go index 6327f460b..341a285cc 100644 --- a/provider/rancher/rancher.go +++ b/provider/rancher/rancher.go @@ -92,10 +92,10 @@ func (p *Provider) getLoadBalancerMethod(service rancherData) string { func (p *Provider) hasLoadBalancerLabel(service rancherData) bool { _, errMethod := getServiceLabel(service, types.LabelBackendLoadbalancerMethod) _, errSticky := getServiceLabel(service, types.LabelBackendLoadbalancerSticky) - if errMethod != nil && errSticky != nil { - return false - } - return true + _, errStickiness := getServiceLabel(service, types.LabelBackendLoadbalancerStickiness) + _, errCookieName := getServiceLabel(service, types.LabelBackendLoadbalancerStickinessCookieName) + + return errMethod == nil || errSticky == nil || errStickiness == nil || errCookieName == nil } func (p *Provider) hasCircuitBreakerLabel(service rancherData) bool { @@ -112,15 +112,18 @@ func (p *Provider) getCircuitBreakerExpression(service rancherData) string { return "NetworkErrorRatio() > 1" } -func (p *Provider) hasStickinessLabel(service rancherData) bool { - _, errStickiness := getServiceLabel(service, types.LabelBackendLoadbalancerStickiness) - - label, errSticky := getServiceLabel(service, types.LabelBackendLoadbalancerSticky) - if len(label) > 0 { - log.Warn("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness) +func (p *Provider) getSticky(service rancherData) string { + if _, err := getServiceLabel(service, types.LabelBackendLoadbalancerSticky); err == nil { + log.Warnf("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness) + return "true" } + 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 { @@ -233,6 +236,7 @@ func (p *Provider) loadRancherConfig(services []rancherData) *types.Configuratio "hasMaxConnLabels": p.hasMaxConnLabels, "getMaxConnAmount": p.getMaxConnAmount, "getMaxConnExtractorFunc": p.getMaxConnExtractorFunc, + "getSticky": p.getSticky, "hasStickinessLabel": p.hasStickinessLabel, "getStickinessCookieName": p.getStickinessCookieName, } diff --git a/provider/rancher/rancher_test.go b/provider/rancher/rancher_test.go index 7fcf03568..92426eef4 100644 --- a/provider/rancher/rancher_test.go +++ b/provider/rancher/rancher_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/containous/traefik/types" + "github.com/stretchr/testify/assert" ) 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) + }) + } +} diff --git a/server/header_rewriter.go b/server/header_rewriter.go new file mode 100644 index 000000000..aebd9fcb7 --- /dev/null +++ b/server/header_rewriter.go @@ -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) + } +} diff --git a/server/server.go b/server/server.go index b65c200e1..35737173c 100644 --- a/server/server.go +++ b/server/server.go @@ -656,7 +656,7 @@ func (server *Server) prepareServer(entryPointName string, entryPoint *configura } if entryPoint.ProxyProtocol != nil { - IPs, err := whitelist.NewIP(entryPoint.ProxyProtocol.TrustedIPs) + IPs, err := whitelist.NewIP(entryPoint.ProxyProtocol.TrustedIPs, entryPoint.ProxyProtocol.Insecure) if err != nil { return nil, nil, fmt.Errorf("Error creating whitelist: %s", err) } @@ -807,11 +807,19 @@ func (server *Server) loadConfig(configurations types.Configurations, globalConf 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( forward.Logger(oxyLogger), forward.PassHostHeader(frontend.PassHostHeader), forward.RoundTripper(roundTripper), forward.ErrorHandler(errorHandler), + forward.Rewriter(rewriter), ) if err != nil { @@ -1174,27 +1182,32 @@ func (server *Server) configureFrontends(frontends map[string]*types.Frontend) { } 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 { - 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) if err == nil { 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 { log.Debugf("Validation of load balancer method for backend %s failed: %s. Using default method wrr.", backendName, err) var stickiness *types.Stickiness if backend.LoadBalancer != nil { - if backend.LoadBalancer.Stickiness != nil { - stickiness = backend.LoadBalancer.Stickiness - } else if backend.LoadBalancer.Sticky { - if backend.LoadBalancer.Stickiness == nil { - stickiness = &types.Stickiness{} + if backend.LoadBalancer.Stickiness == nil { + if backend.LoadBalancer.Sticky { + stickiness = &types.Stickiness{ + CookieName: "_TRAEFIK_BACKEND", + } } + } else { + stickiness = backend.LoadBalancer.Stickiness } } backend.LoadBalancer = &types.LoadBalancer{ diff --git a/server/server_test.go b/server/server_test.go index a5ec9b091..f661a44a3 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -96,7 +96,10 @@ func TestPrepareServerTimeouts(t *testing.T) { t.Parallel() entryPointName := "http" - entryPoint := &configuration.EntryPoint{Address: "localhost:0"} + entryPoint := &configuration.EntryPoint{ + Address: "localhost:0", + ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true}, + } router := middlewares.NewHandlerSwitcher(mux.NewRouter()) 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) { globalConfig := configuration.GlobalConfiguration{ EntryPoints: configuration.EntryPoints{ - "http": &configuration.EntryPoint{}, + "http": &configuration.EntryPoint{ + ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true}, + }, }, HealthCheck: &configuration.HealthCheckConfig{Interval: flaeg.Duration(5 * time.Second)}, } @@ -383,7 +388,7 @@ func TestNewServerWithWhitelistSourceRange(t *testing.T) { func TestServerLoadConfigEmptyBasicAuth(t *testing.T) { globalConfig := configuration.GlobalConfiguration{ 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 { desc string entrypoint *configuration.EntryPoint @@ -501,7 +506,8 @@ func TestServerEntrypointWhitelistConfig(t *testing.T) { { desc: "no whitelist middleware if no config on entrypoint", entrypoint: &configuration.EntryPoint{ - Address: ":0", + Address: ":0", + ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true}, }, wantMiddleware: false, }, @@ -512,6 +518,7 @@ func TestServerEntrypointWhitelistConfig(t *testing.T) { WhitelistSourceRange: []string{ "127.0.0.1/32", }, + ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true}, }, wantMiddleware: true, }, @@ -633,7 +640,7 @@ func TestServerResponseEmptyBackend(t *testing.T) { globalConfig := configuration.GlobalConfiguration{ EntryPoints: configuration.EntryPoints{ - "http": &configuration.EntryPoint{}, + "http": &configuration.EntryPoint{ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true}}, }, } dynamicConfigs := types.Configurations{"config": test.dynamicConfig(testServer.URL)} diff --git a/templates/consul_catalog.tmpl b/templates/consul_catalog.tmpl index b055a4aa4..1d5861735 100644 --- a/templates/consul_catalog.tmpl +++ b/templates/consul_catalog.tmpl @@ -18,10 +18,10 @@ [backends."backend-{{$service}}".loadbalancer] method = "{{getAttribute "backend.loadbalancer" .Attributes "wrr"}}" - sticky = {{getAttribute "backend.loadbalancer.sticky" .Attributes "false"}} + sticky = {{getSticky .Attributes}} {{if hasStickinessLabel .Attributes}} - [Backends."backend-{{$service}}".LoadBalancer.Stickiness] - cookieName = {{getStickinessCookieName .Attributes}} + [backends."backend-{{$service}}".loadbalancer.stickiness] + cookieName = "{{getStickinessCookieName .Attributes}}" {{end}} {{if hasMaxconnAttributes .Attributes}} diff --git a/templates/docker.tmpl b/templates/docker.tmpl index 6e18a9904..c2a7cf54b 100644 --- a/templates/docker.tmpl +++ b/templates/docker.tmpl @@ -8,9 +8,10 @@ {{if hasLoadBalancerLabel $backend}} [backends.backend-{{$backendName}}.loadbalancer] method = "{{getLoadBalancerMethod $backend}}" + sticky = {{getSticky $backend}} {{if hasStickinessLabel $backend}} - [Backends."{{$backendName}}".LoadBalancer.Stickiness] - cookieName = {{getStickinessCookieName $backend}} + [backends.backend-{{$backendName}}.loadBalancer.stickiness] + cookieName = "{{getStickinessCookieName $backend}}" {{end}} {{end}} diff --git a/templates/ecs.tmpl b/templates/ecs.tmpl index 8581988f3..a1777470a 100644 --- a/templates/ecs.tmpl +++ b/templates/ecs.tmpl @@ -1,9 +1,10 @@ [backends]{{range $serviceName, $instances := .Services}} [backends.backend-{{ $serviceName }}.loadbalancer] method = "{{ getLoadBalancerMethod $instances}}" + sticky = {{ getLoadBalancerSticky $instances}} {{if hasStickinessLabel $instances}} - [Backends.backend-{{ $serviceName }}.LoadBalancer.Stickiness] - cookieName = {{getStickinessCookieName $instances}} + [backends.backend-{{ $serviceName }}.loadbalancer.stickiness] + cookieName = "{{getStickinessCookieName $instances}}" {{end}} {{range $index, $i := $instances}} diff --git a/templates/kubernetes.tmpl b/templates/kubernetes.tmpl index 47fec081c..8f31c3e79 100644 --- a/templates/kubernetes.tmpl +++ b/templates/kubernetes.tmpl @@ -6,9 +6,12 @@ {{end}} [backends."{{$backendName}}".loadbalancer] method = "{{$backend.LoadBalancer.Method}}" + {{if $backend.LoadBalancer.Sticky}} + sticky = true + {{end}} {{if $backend.LoadBalancer.Stickiness}} - [Backends."{{$backendName}}".LoadBalancer.Stickiness] - cookieName = {{$backend.LoadBalancer.Stickiness.CookieName}} + [backends."{{$backendName}}".loadbalancer.stickiness] + cookieName = "{{$backend.LoadBalancer.Stickiness.CookieName}}" {{end}} {{range $serverName, $server := $backend.Servers}} [backends."{{$backendName}}".servers."{{$serverName}}"] diff --git a/templates/kv.tmpl b/templates/kv.tmpl index ebbbb39c2..291a14d38 100644 --- a/templates/kv.tmpl +++ b/templates/kv.tmpl @@ -16,9 +16,9 @@ {{with $loadBalancer}} [backends."{{$backendName}}".loadBalancer] method = "{{$loadBalancer}}" - sticky = {{ Get "false" . "/loadbalancer/" "sticky" }} + sticky = {{ getSticky . }} {{if hasStickinessLabel $backend}} - [Backends."{{$backendName}}".LoadBalancer.Stickiness] + [backends."{{$backendName}}".loadBalancer.stickiness] cookieName = {{getStickinessCookieName $backend}} {{end}} {{end}} diff --git a/templates/marathon.tmpl b/templates/marathon.tmpl index e5c802a63..e03fd601c 100644 --- a/templates/marathon.tmpl +++ b/templates/marathon.tmpl @@ -20,9 +20,10 @@ {{ if hasLoadBalancerLabels $app }} [backends."backend{{getBackend $app $serviceName }}".loadbalancer] method = "{{getLoadBalancerMethod $app }}" + sticky = {{getSticky $app}} {{if hasStickinessLabel $app}} - [Backends."backend{{getBackend $app $serviceName }}".LoadBalancer.Stickiness] - cookieName = {{getStickinessCookieName $app}} + [backends."backend{{getBackend $app $serviceName }}".loadbalancer.stickiness] + cookieName = "{{getStickinessCookieName $app}}" {{end}} {{end}} {{ if hasCircuitBreakerLabels $app }} diff --git a/templates/rancher.tmpl b/templates/rancher.tmpl index 90a10ac56..a5355c252 100644 --- a/templates/rancher.tmpl +++ b/templates/rancher.tmpl @@ -8,9 +8,10 @@ {{if hasLoadBalancerLabel $backend}} [backends.backend-{{$backendName}}.loadbalancer] method = "{{getLoadBalancerMethod $backend}}" + sticky = {{getSticky $backend}} {{if hasStickinessLabel $backend}} - [Backends."{{$backendName}}".LoadBalancer.Stickiness] - cookieName = {{getStickinessCookieName $backend}} + [backends.backend-{{$backendName}}.loadbalancer.stickiness] + cookieName = "{{getStickinessCookieName $backend}}" {{end}} {{end}} diff --git a/vendor/github.com/vulcand/oxy/forward/fwd.go b/vendor/github.com/vulcand/oxy/forward/fwd.go index a30c76ba0..09225f4c6 100644 --- a/vendor/github.com/vulcand/oxy/forward/fwd.go +++ b/vendor/github.com/vulcand/oxy/forward/fwd.go @@ -249,6 +249,12 @@ func (f *httpForwarder) copyRequest(req *http.Request, u *url.URL) *http.Request if f.rewriter != nil { 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 } diff --git a/whitelist/ip.go b/whitelist/ip.go index af3b691e7..322404fab 100644 --- a/whitelist/ip.go +++ b/whitelist/ip.go @@ -11,26 +11,29 @@ import ( type IP struct { whiteListsIPs []*net.IP whiteListsNet []*net.IPNet + insecure bool } // NewIP builds a new IP given a list of CIDR-Strings to whitelist -func NewIP(whitelistStrings []string) (*IP, error) { - if len(whitelistStrings) == 0 { +func NewIP(whitelistStrings []string, insecure bool) (*IP, error) { + if len(whitelistStrings) == 0 && !insecure { return nil, errors.New("no whiteListsNet provided") } ip := IP{} - for _, whitelistString := range whitelistStrings { - ipAddr := net.ParseIP(whitelistString) - if ipAddr != nil { - ip.whiteListsIPs = append(ip.whiteListsIPs, &ipAddr) - } else { - _, whitelist, err := net.ParseCIDR(whitelistString) - if err != nil { - return nil, fmt.Errorf("parsing CIDR whitelist %s: %v", whitelist, err) + if !insecure { + for _, whitelistString := range whitelistStrings { + ipAddr := net.ParseIP(whitelistString) + if ipAddr != nil { + ip.whiteListsIPs = append(ip.whiteListsIPs, &ipAddr) + } else { + _, whitelist, err := net.ParseCIDR(whitelistString) + if err != nil { + return nil, fmt.Errorf("parsing CIDR whitelist %s: %v", whitelist, err) + } + ip.whiteListsNet = append(ip.whiteListsNet, whitelist) } - ip.whiteListsNet = append(ip.whiteListsNet, whitelist) } } @@ -39,6 +42,10 @@ func NewIP(whitelistStrings []string) (*IP, error) { // Contains checks if provided address is in the white list func (ip *IP) Contains(addr string) (bool, net.IP, error) { + if ip.insecure { + return true, nil, nil + } + ipAddr, err := ipFromRemoteAddr(addr) if err != nil { 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 func (ip *IP) ContainsIP(addr net.IP) (bool, error) { + if ip.insecure { + return true, nil + } + for _, whiteListIP := range ip.whiteListsIPs { if whiteListIP.Equal(addr) { return true, nil diff --git a/whitelist/ip_test.go b/whitelist/ip_test.go index 46ab7142f..abd65f297 100644 --- a/whitelist/ip_test.go +++ b/whitelist/ip_test.go @@ -75,7 +75,7 @@ func TestNew(t *testing.T) { test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() - whitelister, err := NewIP(test.whitelistStrings) + whitelister, err := NewIP(test.whitelistStrings, false) if test.errMessage != "" { require.EqualError(t, err, test.errMessage) } else { @@ -275,7 +275,7 @@ func TestIsAllowed(t *testing.T) { test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() - whiteLister, err := NewIP(test.whitelistStrings) + whiteLister, err := NewIP(test.whitelistStrings, false) require.NoError(t, err) 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) for _, testIP := range brokenIPs {