Compare commits
107 Commits
v1.6.0-rc6
...
v1.6
Author | SHA1 | Date | |
---|---|---|---|
|
e6a2873799 | ||
|
9510e603f1 | ||
|
9a76369908 | ||
|
913d8737cc | ||
|
b98f5ed8b1 | ||
|
157c796294 | ||
|
845f1a7377 | ||
|
27e4a8a227 | ||
|
cf2d7497e4 | ||
|
df41cd925e | ||
|
feeb7f81a6 | ||
|
2beb5236d0 | ||
|
f062ee80c8 | ||
|
a7bb768e98 | ||
|
07be89d6e9 | ||
|
d81c4e6d1a | ||
|
60b4095c75 | ||
|
7ff6e6b66f | ||
|
dbe720f0f1 | ||
|
f0ab2721a5 | ||
|
a7c158f0e1 | ||
|
bdc0e3bfcf | ||
|
f173ff02e3 | ||
|
bacd58ed7b | ||
|
f323df466d | ||
|
b1836587f2 | ||
|
dbc3b85cd0 | ||
|
5eda08e9b8 | ||
|
ec6e46e2cb | ||
|
aa705dd691 | ||
|
1c3e4124f8 | ||
|
c1757372d3 | ||
|
5b2b29043c | ||
|
bb3f28ffa7 | ||
|
6ceb2af4a7 | ||
|
b59276ff1c | ||
|
2e95832812 | ||
|
2240bf9430 | ||
|
db036edccd | ||
|
08e1f626c1 | ||
|
c0d08f5e3e | ||
|
dec3f0798a | ||
|
62ded580ce | ||
|
446d73fcf5 | ||
|
e299775d67 | ||
|
2c18750537 | ||
|
f317e50136 | ||
|
1d84bda7ca | ||
|
ae7c947ba5 | ||
|
6d07729c55 | ||
|
1d7bf200a8 | ||
|
6bc59f8b33 | ||
|
b2cf03fa5c | ||
|
36e273714d | ||
|
6be77b7fb9 | ||
|
6bcf45f136 | ||
|
8bca8236db | ||
|
fb5aa4c9c1 | ||
|
3f5772c62a | ||
|
2d946d7ee7 | ||
|
10ca35dccd | ||
|
e3671cbb04 | ||
|
a525d02cc5 | ||
|
1cc1a4e6e2 | ||
|
3f0af3fe09 | ||
|
bc26d9f0de | ||
|
0ba28bbc8b | ||
|
550184275a | ||
|
c376083ecb | ||
|
1db5fcf200 | ||
|
16b2555ab3 | ||
|
d9a5258f40 | ||
|
190ebbed27 | ||
|
a0872c9e31 | ||
|
f5b306e7ff | ||
|
7a1feb3c51 | ||
|
1e8df9f245 | ||
|
b72937e8fb | ||
|
67847c3117 | ||
|
a2a0c80acb | ||
|
b3fd06fb45 | ||
|
c5db8d903c | ||
|
8fcd242494 | ||
|
ebd9af900e | ||
|
b02381c2d5 | ||
|
9b199ea756 | ||
|
ec3b913ee4 | ||
|
c210ab31d9 | ||
|
6c1fa91c70 | ||
|
04bab185f6 | ||
|
2213b4cf37 | ||
|
1d770e5636 | ||
|
b7e15e0a2c | ||
|
9c651ae913 | ||
|
e09d5cb4ec | ||
|
cae353b9f6 | ||
|
edb5b3d711 | ||
|
667a0c41ed | ||
|
2975acdc82 | ||
|
76dcbe3429 | ||
|
d8e2d464ad | ||
|
5f8bcb0c26 | ||
|
7ef8d6fa10 | ||
|
5924a40222 | ||
|
95ce4f5c1e | ||
|
f258f20b04 | ||
|
7e2ad827aa |
1
.gitignore
vendored
@@ -6,6 +6,7 @@
|
||||
/traefik
|
||||
/traefik.toml
|
||||
/static/
|
||||
/webui/.tmp/
|
||||
.vscode/
|
||||
/site/
|
||||
*.log
|
||||
|
293
CHANGELOG.md
@@ -1,5 +1,298 @@
|
||||
# Change Log
|
||||
|
||||
## [v1.6.6](https://github.com/containous/traefik/tree/v1.6.6) (2018-08-20)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.6.5...v1.6.6)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[acme]** Avoid duplicated ACME resolution ([#3751](https://github.com/containous/traefik/pull/3751) by [nmengin](https://github.com/nmengin))
|
||||
- **[api]** Remove TLS in API ([#3788](https://github.com/containous/traefik/pull/3788) by [Juliens](https://github.com/Juliens))
|
||||
- **[cluster]** Remove unusable `--cluster` flag ([#3616](https://github.com/containous/traefik/pull/3616) by [dtomcej](https://github.com/dtomcej))
|
||||
- **[ecs]** Fix bad condition in ECS provider ([#3609](https://github.com/containous/traefik/pull/3609) by [mmatur](https://github.com/mmatur))
|
||||
- Set keepalive on TCP socket so idleTimeout works ([#3740](https://github.com/containous/traefik/pull/3740) by [ajardan](https://github.com/ajardan))
|
||||
|
||||
**Documentation:**
|
||||
- A tiny rewording on the documentation API's page ([#3794](https://github.com/containous/traefik/pull/3794) by [dduportal](https://github.com/dduportal))
|
||||
- Adding warnings and solution about the configuration exposure ([#3790](https://github.com/containous/traefik/pull/3790) by [dduportal](https://github.com/dduportal))
|
||||
- Fix path to the debug pprof API ([#3608](https://github.com/containous/traefik/pull/3608) by [multani](https://github.com/multani))
|
||||
|
||||
**Misc:**
|
||||
- **[oxy,websocket]** Update oxy dependency ([#3777](https://github.com/containous/traefik/pull/3777) by [Juliens](https://github.com/Juliens))
|
||||
|
||||
## [v1.6.5](https://github.com/containous/traefik/tree/v1.6.5) (2018-07-09)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.6.4...v1.6.5)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[acme]** Add a mutex on local store for HTTPChallenges ([#3579](https://github.com/containous/traefik/pull/3579) by [Juliens](https://github.com/Juliens))
|
||||
- **[consulcatalog]** Split the error handling from Consul Catalog (deadlock) ([#3560](https://github.com/containous/traefik/pull/3560) by [ortz](https://github.com/ortz))
|
||||
- **[docker]** segment labels: multiple frontends for one backend. ([#3511](https://github.com/containous/traefik/pull/3511) by [ldez](https://github.com/ldez))
|
||||
- **[kv]** Better support on same prefix at the same level in the KV ([#3532](https://github.com/containous/traefik/pull/3532) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- **[logs]** Add logs when error is generated in error handler ([#3567](https://github.com/containous/traefik/pull/3567) by [Juliens](https://github.com/Juliens))
|
||||
- **[middleware]** Create middleware to be able to handle HTTP pipelining correctly ([#3513](https://github.com/containous/traefik/pull/3513) by [mmatur](https://github.com/mmatur))
|
||||
|
||||
**Documentation:**
|
||||
- **[acme]** The gandiv5 provider works with wildcard ([#3506](https://github.com/containous/traefik/pull/3506) by [manu5801](https://github.com/manu5801))
|
||||
- **[kv]** Update keyFile first/last line comment in kv-config.md ([#3558](https://github.com/containous/traefik/pull/3558) by [madnight](https://github.com/madnight))
|
||||
- Minor formatting issue in user-guide ([#3546](https://github.com/containous/traefik/pull/3546) by [Vanuan](https://github.com/Vanuan))
|
||||
|
||||
## [v1.6.4](https://github.com/containous/traefik/tree/v1.6.4) (2018-06-15)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.6.3...v1.6.4)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[acme]** Use logrus writer instead of os.Stderr ([#3498](https://github.com/containous/traefik/pull/3498) by [ldez](https://github.com/ldez))
|
||||
- **[consulcatalog]** Enclose IPv6 addresses in "[]" ([#3477](https://github.com/containous/traefik/pull/3477) by [herver](https://github.com/herver))
|
||||
- **[docker,ecs,marathon,mesos,rancher]** Use net.JoinHostPort for servers URL ([#3484](https://github.com/containous/traefik/pull/3484) by [ldez](https://github.com/ldez))
|
||||
- **[docker]** Backend name with docker-compose and segments. ([#3485](https://github.com/containous/traefik/pull/3485) by [ldez](https://github.com/ldez))
|
||||
- **[oxy]** Handle buffer pool for oxy ([#3450](https://github.com/containous/traefik/pull/3450) by [Juliens](https://github.com/Juliens))
|
||||
|
||||
**Documentation:**
|
||||
- **[acme]** The exoscale provider works with wildcard ([#3479](https://github.com/containous/traefik/pull/3479) by [greut](https://github.com/greut))
|
||||
- **[consul,docker]** Edit wording ([#3438](https://github.com/containous/traefik/pull/3438) by [mayank23](https://github.com/mayank23))
|
||||
- **[k8s]** Add missing annotation documentation. ([#3454](https://github.com/containous/traefik/pull/3454) by [ldez](https://github.com/ldez))
|
||||
- **[kv]** Fix typo in kv user guide ([#3474](https://github.com/containous/traefik/pull/3474) by [shambarick](https://github.com/shambarick))
|
||||
- Clean metrics documentation. ([#3488](https://github.com/containous/traefik/pull/3488) by [ldez](https://github.com/ldez))
|
||||
|
||||
## [v1.6.3](https://github.com/containous/traefik/tree/v1.6.3) (2018-06-05)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.6.2...v1.6.3)
|
||||
|
||||
**Enhancements:**
|
||||
- **[acme]** Add user agent for ACME ([#3431](https://github.com/containous/traefik/pull/3431) by [ldez](https://github.com/ldez))
|
||||
- **[acme]** Use to the stable version of Lego ([#3418](https://github.com/containous/traefik/pull/3418) by [ldez](https://github.com/ldez))
|
||||
|
||||
**Bug fixes:**
|
||||
- **[acme,cluster]** Improve ACME account registration URI management ([#3398](https://github.com/containous/traefik/pull/3398) by [nmengin](https://github.com/nmengin))
|
||||
- **[acme,cluster]** Remove ACME empty certificates from KV store ([#3389](https://github.com/containous/traefik/pull/3389) by [nmengin](https://github.com/nmengin))
|
||||
- **[consulcatalog]** Reflect changes in catalog healthy nodes in healthCheck watch ([#3390](https://github.com/containous/traefik/pull/3390) by [thebinary](https://github.com/thebinary))
|
||||
- **[consulcatalog]** Detect change when service or node are in maintenance mode ([#3434](https://github.com/containous/traefik/pull/3434) by [mmatur](https://github.com/mmatur))
|
||||
- **[k8s]** Update Kubernetes provider to support IPv6 Backends ([#3432](https://github.com/containous/traefik/pull/3432) by [dtomcej](https://github.com/dtomcej))
|
||||
- **[logs,middleware]** Add URL and Host for some access logs. ([#3430](https://github.com/containous/traefik/pull/3430) by [ldez](https://github.com/ldez))
|
||||
- **[metrics]** Improve Prometheus metrics removal ([#3287](https://github.com/containous/traefik/pull/3287) by [marco-jantke](https://github.com/marco-jantke))
|
||||
- **[middleware]** Whitelist and XFF. ([#3411](https://github.com/containous/traefik/pull/3411) by [ldez](https://github.com/ldez))
|
||||
- **[middleware]** Error pages and header merge ([#3394](https://github.com/containous/traefik/pull/3394) by [ldez](https://github.com/ldez))
|
||||
- **[websocket]** Includes the headers in the HTTP response of a websocket request ([#3425](https://github.com/containous/traefik/pull/3425) by [geraldcroes](https://github.com/geraldcroes))
|
||||
- **[webui]** Webui Whitelist overflow. ([#3412](https://github.com/containous/traefik/pull/3412) by [ldez](https://github.com/ldez))
|
||||
|
||||
**Documentation:**
|
||||
- **[acme]** Docs: ACME Overhaul ([#3421](https://github.com/containous/traefik/pull/3421) by [Dargmuesli](https://github.com/Dargmuesli))
|
||||
- **[acme]** Minor documentation changes ([#3405](https://github.com/containous/traefik/pull/3405) by [amincheloh](https://github.com/amincheloh))
|
||||
- **[k8s]** Helm installation using values ([#3392](https://github.com/containous/traefik/pull/3392) by [erikaulin](https://github.com/erikaulin))
|
||||
- **[k8s]** Update Kubernetes Port Documentation ([#3368](https://github.com/containous/traefik/pull/3368) by [dtomcej](https://github.com/dtomcej))
|
||||
|
||||
## [v1.6.2](https://github.com/containous/traefik/tree/v1.6.2) (2018-05-22)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.6.1...v1.6.2)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[acme]** fix: acme errors management. ([#3329](https://github.com/containous/traefik/pull/3329) by [ldez](https://github.com/ldez))
|
||||
- **[acme]** Force to use ACME v02 endpoint. ([#3358](https://github.com/containous/traefik/pull/3358) by [ldez](https://github.com/ldez))
|
||||
- **[file]** No template parsing on traefik configuration file ([#3347](https://github.com/containous/traefik/pull/3347) by [Juliens](https://github.com/Juliens))
|
||||
- **[k8s]** Add redirect-permanent to kubernetes template ([#3332](https://github.com/containous/traefik/pull/3332) by [dtomcej](https://github.com/dtomcej))
|
||||
- **[logs]** Enhance Load-balancing method validation log. ([#3361](https://github.com/containous/traefik/pull/3361) by [ldez](https://github.com/ldez))
|
||||
- **[middleware]** Fix error pages content. ([#3337](https://github.com/containous/traefik/pull/3337) by [ldez](https://github.com/ldez))
|
||||
- **[webui]** Route rules overlaps in UI ([#3333](https://github.com/containous/traefik/pull/3333) by [ldez](https://github.com/ldez))
|
||||
- **[webui]** WebUI typo into the buffering section. ([#3363](https://github.com/containous/traefik/pull/3363) by [ldez](https://github.com/ldez))
|
||||
|
||||
**Documentation:**
|
||||
- **[acme]** Update caServer to letsencrypt one in examples ([#3339](https://github.com/containous/traefik/pull/3339) by [woernfl](https://github.com/woernfl))
|
||||
- **[docker]** Add command for basic auth with Docker Compose ([#3346](https://github.com/containous/traefik/pull/3346) by [DeamonMV](https://github.com/DeamonMV))
|
||||
- **[docker]** Removes ambiguity with the word 'default' ([#3344](https://github.com/containous/traefik/pull/3344) by [ldez](https://github.com/ldez))
|
||||
- **[kv]** Add basicAuth example for KV ([#3274](https://github.com/containous/traefik/pull/3274) by [MichaelErmer](https://github.com/MichaelErmer))
|
||||
- **[provider]** Update docs to reflect Provider wording ([#3331](https://github.com/containous/traefik/pull/3331) by [dtomcej](https://github.com/dtomcej))
|
||||
- **[servicefabric]** Update docs to match SF provider labels ([#3335](https://github.com/containous/traefik/pull/3335) by [jjcollinge](https://github.com/jjcollinge))
|
||||
|
||||
## [v1.6.1](https://github.com/containous/traefik/tree/v1.6.1) (2018-05-14)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.6.0...v1.6.1)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[acme]** Add missing deprecation info in CLI help. ([#3291](https://github.com/containous/traefik/pull/3291) by [ldez](https://github.com/ldez))
|
||||
- **[docker,marathon,rancher]** Fix segment backend name ([#3317](https://github.com/containous/traefik/pull/3317) by [ldez](https://github.com/ldez))
|
||||
- **[logs,middleware]** Error when accesslog and error pages ([#3314](https://github.com/containous/traefik/pull/3314) by [ldez](https://github.com/ldez))
|
||||
- **[middleware,tracing]** Fix wrong tag in forward span in tracing middleware ([#3279](https://github.com/containous/traefik/pull/3279) by [mmatur](https://github.com/mmatur))
|
||||
- **[webui]** Fix webui ([#3299](https://github.com/containous/traefik/pull/3299) by [ldez](https://github.com/ldez))
|
||||
|
||||
**Documentation:**
|
||||
- **[k8s]** Add Documentation update for Kubernetes Ingress ([#3294](https://github.com/containous/traefik/pull/3294) by [dtomcej](https://github.com/dtomcej))
|
||||
- **[tls]** Enhance entry point TLS CLI reference. ([#3290](https://github.com/containous/traefik/pull/3290) by [ldez](https://github.com/ldez))
|
||||
- Typo in documentation ([#3261](https://github.com/containous/traefik/pull/3261) by [blakethepatton](https://github.com/blakethepatton))
|
||||
|
||||
## [v1.6.0](https://github.com/containous/traefik/tree/v1.6.0) (2018-04-30)
|
||||
[Commits](https://github.com/containous/traefik/compare/v1.5.0-rc1...v1.6.0)
|
||||
[Commits pre RC](https://github.com/containous/traefik/compare/v1.5.0-rc1...v1.6.0-rc1)
|
||||
|
||||
**Enhancements:**
|
||||
- **[acme]** Create ACME Provider ([#2889](https://github.com/containous/traefik/pull/2889) by [nmengin](https://github.com/nmengin))
|
||||
- **[acme]** Update Lego (Gandi API v5, cloudxns, ...) ([#2844](https://github.com/containous/traefik/pull/2844) by [ldez](https://github.com/ldez))
|
||||
- **[acme]** Simplify storing renewed acme certificate ([#2614](https://github.com/containous/traefik/pull/2614) by [ferhatelmas](https://github.com/ferhatelmas))
|
||||
- **[acme]** ACME V2 Integration ([#3063](https://github.com/containous/traefik/pull/3063) by [nmengin](https://github.com/nmengin))
|
||||
- **[acme]** Bump Lego Version for GoDaddy DNS Provider ([#2482](https://github.com/containous/traefik/pull/2482) by [sjawhar](https://github.com/sjawhar))
|
||||
- **[acme]** Delete TLS-SNI-01 challenge from ACME ([#2971](https://github.com/containous/traefik/pull/2971) by [nmengin](https://github.com/nmengin))
|
||||
- **[acme]** Create backup file during migration from ACME V1 to ACME V2 ([#3191](https://github.com/containous/traefik/pull/3191) by [nmengin](https://github.com/nmengin))
|
||||
- **[acme]** Generate wildcard certificate with SANs in ACME ([#3167](https://github.com/containous/traefik/pull/3167) by [nmengin](https://github.com/nmengin))
|
||||
- **[api,cluster]** Added cluster/leader endpoint ([#3009](https://github.com/containous/traefik/pull/3009) by [aantono](https://github.com/aantono))
|
||||
- **[authentication]** Forward Authentication: add X-Forwarded-Uri ([#2398](https://github.com/containous/traefik/pull/2398) by [sebastianbauer](https://github.com/sebastianbauer))
|
||||
- **[boltdb,consul,etcd,kv,zk]** Add all available configuration to KV Backend ([#2652](https://github.com/containous/traefik/pull/2652) by [ldez](https://github.com/ldez))
|
||||
- **[boltdb,consul,etcd,kv,zk]** homogenization of templates: KV ([#2661](https://github.com/containous/traefik/pull/2661) by [ldez](https://github.com/ldez))
|
||||
- **[boltdb,consul,etcd,kv,zk]** Homogenization of the providers (part 1): KV ([#2616](https://github.com/containous/traefik/pull/2616) by [ldez](https://github.com/ldez))
|
||||
- **[consul,consulcatalog]** Homogenization of templates: Consul Catalog ([#2668](https://github.com/containous/traefik/pull/2668) by [ldez](https://github.com/ldez))
|
||||
- **[consul,consulcatalog]** Split consul and consul catalog. ([#2655](https://github.com/containous/traefik/pull/2655) by [ldez](https://github.com/ldez))
|
||||
- **[consulcatalog,ecs,mesos]** Factorize labels managements. ([#3099](https://github.com/containous/traefik/pull/3099) by [ldez](https://github.com/ldez))
|
||||
- **[consulcatalog]** Check for endpoints while detecting Consul service changes ([#2882](https://github.com/containous/traefik/pull/2882) by [caseycs](https://github.com/caseycs))
|
||||
- **[consulcatalog]** TLS Support for ConsulCatalog ([#2900](https://github.com/containous/traefik/pull/2900) by [mmatur](https://github.com/mmatur))
|
||||
- **[consulcatalog]** Add all available tags to Consul Catalog Backend ([#2646](https://github.com/containous/traefik/pull/2646) by [ldez](https://github.com/ldez))
|
||||
- **[docker,docker/swarm]** Fix support for macvlan driver in docker provider ([#2827](https://github.com/containous/traefik/pull/2827) by [mmatur](https://github.com/mmatur))
|
||||
- **[docker,marathon,rancher]** Segments Labels: Rancher & Marathon ([#3073](https://github.com/containous/traefik/pull/3073) by [ldez](https://github.com/ldez))
|
||||
- **[docker]** Add all available labels to Docker Backend ([#2584](https://github.com/containous/traefik/pull/2584) by [ldez](https://github.com/ldez))
|
||||
- **[docker]** Homogenization of templates: Docker ([#2659](https://github.com/containous/traefik/pull/2659) by [ldez](https://github.com/ldez))
|
||||
- **[docker]** Custom headers by service labels for docker backends ([#2514](https://github.com/containous/traefik/pull/2514) by [Tiscs](https://github.com/Tiscs))
|
||||
- **[docker]** Segment labels: Docker ([#3055](https://github.com/containous/traefik/pull/3055) by [ldez](https://github.com/ldez))
|
||||
- **[dynamodb,ecs]** Upgrade AWS SKD to version v1.13.1 ([#2908](https://github.com/containous/traefik/pull/2908) by [mmatur](https://github.com/mmatur))
|
||||
- **[ecs]** Add all available labels to ECS Backend ([#2605](https://github.com/containous/traefik/pull/2605) by [ldez](https://github.com/ldez))
|
||||
- **[ecs]** Homogenization of templates: ECS ([#2663](https://github.com/containous/traefik/pull/2663) by [ldez](https://github.com/ldez))
|
||||
- **[ecs]** Factorize labels managements. ([#3159](https://github.com/containous/traefik/pull/3159) by [ldez](https://github.com/ldez))
|
||||
- **[eureka]** Homogenization of templates: Eureka ([#2846](https://github.com/containous/traefik/pull/2846) by [ldez](https://github.com/ldez))
|
||||
- **[eureka]** Replace Delay by RefreshSecond in Eureka ([#2972](https://github.com/containous/traefik/pull/2972) by [ldez](https://github.com/ldez))
|
||||
- **[file]** Added support for templates to file provider ([#2991](https://github.com/containous/traefik/pull/2991) by [aantono](https://github.com/aantono))
|
||||
- **[healthcheck]** Toggle /ping to artificially return unhealthy response on SIGTERM during requestAcceptGraceTimeout interval ([#3062](https://github.com/containous/traefik/pull/3062) by [ravilr](https://github.com/ravilr))
|
||||
- **[healthcheck]** Improve logging output for failing healthchecks ([#2443](https://github.com/containous/traefik/pull/2443) by [marco-jantke](https://github.com/marco-jantke))
|
||||
- **[k8s,tls]** Add support for fetching k8s Ingress TLS data from secrets ([#2439](https://github.com/containous/traefik/pull/2439) by [gopenguin](https://github.com/gopenguin))
|
||||
- **[k8s]** Introduce k8s informer factory ([#2867](https://github.com/containous/traefik/pull/2867) by [yue9944882](https://github.com/yue9944882))
|
||||
- **[k8s]** Add all available annotations to k8s Backend ([#2612](https://github.com/containous/traefik/pull/2612) by [ldez](https://github.com/ldez))
|
||||
- **[k8s]** Bump kubernetes/client-go ([#2848](https://github.com/containous/traefik/pull/2848) by [yue9944882](https://github.com/yue9944882))
|
||||
- **[k8s]** Add app-root annotation support for kubernetes ingress ([#2522](https://github.com/containous/traefik/pull/2522) by [yue9944882](https://github.com/yue9944882))
|
||||
- **[k8s]** Builders in k8s tests ([#2513](https://github.com/containous/traefik/pull/2513) by [ldez](https://github.com/ldez))
|
||||
- **[k8s]** Allow custom value for kubernetes.io/ingress.class annotation ([#2222](https://github.com/containous/traefik/pull/2222) by [yuvipanda](https://github.com/yuvipanda))
|
||||
- **[logs,middleware]** Add access log filter for retry attempts ([#3042](https://github.com/containous/traefik/pull/3042) by [marco-jantke](https://github.com/marco-jantke))
|
||||
- **[logs,middleware]** Add username in accesslog ([#2111](https://github.com/containous/traefik/pull/2111) by [bastiaanb](https://github.com/bastiaanb))
|
||||
- **[logs,middleware]** Ultimate Access log filter ([#2988](https://github.com/containous/traefik/pull/2988) by [mmatur](https://github.com/mmatur))
|
||||
- **[logs]** Allow overriding the log level in debug mode. ([#3050](https://github.com/containous/traefik/pull/3050) by [timoreimann](https://github.com/timoreimann))
|
||||
- **[logs]** Display file log when test fails. ([#2801](https://github.com/containous/traefik/pull/2801) by [ldez](https://github.com/ldez))
|
||||
- **[marathon]** Remove health check filter from Marathon tasks. ([#2817](https://github.com/containous/traefik/pull/2817) by [timoreimann](https://github.com/timoreimann))
|
||||
- **[marathon]** Add all available labels to Marathon Backend ([#2602](https://github.com/containous/traefik/pull/2602) by [ldez](https://github.com/ldez))
|
||||
- **[marathon]** homogenization of templates: Marathon ([#2665](https://github.com/containous/traefik/pull/2665) by [ldez](https://github.com/ldez))
|
||||
- **[mesos]** Add all available labels to Mesos Backend ([#2687](https://github.com/containous/traefik/pull/2687) by [ldez](https://github.com/ldez))
|
||||
- **[metrics]** Added entrypoint metrics to influxdb ([#2992](https://github.com/containous/traefik/pull/2992) by [adityacs](https://github.com/adityacs))
|
||||
- **[metrics]** Remove unnecessary conversion ([#2850](https://github.com/containous/traefik/pull/2850) by [ferhatelmas](https://github.com/ferhatelmas))
|
||||
- **[metrics]** Extend metrics and rebuild prometheus exporting logic ([#2567](https://github.com/containous/traefik/pull/2567) by [marco-jantke](https://github.com/marco-jantke))
|
||||
- **[metrics]** Added missing metrics to registry for DataDog and StatsD ([#2890](https://github.com/containous/traefik/pull/2890) by [aantono](https://github.com/aantono))
|
||||
- **[middleware,consul,consulcatalog,docker,ecs,k8s,marathon,mesos,rancher]** New option in secure middleware ([#2958](https://github.com/containous/traefik/pull/2958) by [mmatur](https://github.com/mmatur))
|
||||
- **[middleware,consulcatalog,docker,ecs,k8s,kv,marathon,mesos,rancher]** Ability to use "X-Forwarded-For" as a source of IP for white list. ([#3070](https://github.com/containous/traefik/pull/3070) by [ldez](https://github.com/ldez))
|
||||
- **[middleware,docker]** Use pointer of error pages ([#2607](https://github.com/containous/traefik/pull/2607) by [ldez](https://github.com/ldez))
|
||||
- **[middleware,provider]** Redirection: permanent move option. ([#2774](https://github.com/containous/traefik/pull/2774) by [ldez](https://github.com/ldez))
|
||||
- **[middleware]** Add tests on IPWhiteLister. ([#3106](https://github.com/containous/traefik/pull/3106) by [ldez](https://github.com/ldez))
|
||||
- **[middleware]** Change port of traefik for error pages integration test ([#2907](https://github.com/containous/traefik/pull/2907) by [mmatur](https://github.com/mmatur))
|
||||
- **[middleware]** Remove unnecessary returns in tracing setup ([#2880](https://github.com/containous/traefik/pull/2880) by [ferhatelmas](https://github.com/ferhatelmas))
|
||||
- **[middleware]** Request buffering middleware ([#2217](https://github.com/containous/traefik/pull/2217) by [harnash](https://github.com/harnash))
|
||||
- **[middleware]** Add new options to the CLI entrypoint definition. ([#2799](https://github.com/containous/traefik/pull/2799) by [ldez](https://github.com/ldez))
|
||||
- **[provider]** No error pages must return nil. ([#2610](https://github.com/containous/traefik/pull/2610) by [ldez](https://github.com/ldez))
|
||||
- **[provider]** Homogenization of the providers (part 1) ([#2518](https://github.com/containous/traefik/pull/2518) by [ldez](https://github.com/ldez))
|
||||
- **[rancher]** Add all available labels to Rancher Backend ([#2601](https://github.com/containous/traefik/pull/2601) by [ldez](https://github.com/ldez))
|
||||
- **[rancher]** Homogenization of templates: Rancher ([#2662](https://github.com/containous/traefik/pull/2662) by [ldez](https://github.com/ldez))
|
||||
- **[rules]** Externalize Træfik rules in a dedicated package ([#2933](https://github.com/containous/traefik/pull/2933) by [nmengin](https://github.com/nmengin))
|
||||
- **[servicefabric]** Use shared label system ([#3197](https://github.com/containous/traefik/pull/3197) by [ldez](https://github.com/ldez))
|
||||
- **[servicefabric]** Update Service Fabric backend. ([#3064](https://github.com/containous/traefik/pull/3064) by [ldez](https://github.com/ldez))
|
||||
- **[servicefabric]** Add white list for Service Fabric ([#3079](https://github.com/containous/traefik/pull/3079) by [ldez](https://github.com/ldez))
|
||||
- **[tls]** Use default entryPoints when certificates are added with no entryPoints. ([#2534](https://github.com/containous/traefik/pull/2534) by [nmengin](https://github.com/nmengin))
|
||||
- **[tracing]** Handle zipkin collector creation ([#2860](https://github.com/containous/traefik/pull/2860) by [ferhatelmas](https://github.com/ferhatelmas))
|
||||
- **[tracing]** Opentracing support ([#2587](https://github.com/containous/traefik/pull/2587) by [tcolgate](https://github.com/tcolgate) and [mmatur](https://github.com/mmatur))
|
||||
- **[webui]** New web ui ([#2226](https://github.com/containous/traefik/pull/2226) by [jkuri](https://github.com/jkuri))
|
||||
- **[webui]** Add status code text to webui bar chart tooltip ([#2639](https://github.com/containous/traefik/pull/2639) by [wader](https://github.com/wader))
|
||||
- Logger and Leaks ([#2847](https://github.com/containous/traefik/pull/2847) by [ldez](https://github.com/ldez))
|
||||
- Separate command from the main package ([#2951](https://github.com/containous/traefik/pull/2951) by [Juliens](https://github.com/Juliens))
|
||||
- Use context in Server ([#3007](https://github.com/containous/traefik/pull/3007) by [Juliens](https://github.com/Juliens))
|
||||
|
||||
**Bug fixes:**
|
||||
- **[acme]** Check all the C/N and SANs of provided certificates before generating ACME certificates in ACME provider ([#2970](https://github.com/containous/traefik/pull/2970) by [nmengin](https://github.com/nmengin))
|
||||
- **[acme]** Update lego. ([#3158](https://github.com/containous/traefik/pull/3158) by [ldez](https://github.com/ldez))
|
||||
- **[acme]** Fix panic with wrong ACME configuration ([#3084](https://github.com/containous/traefik/pull/3084) by [nmengin](https://github.com/nmengin))
|
||||
- **[acme]** Minor updates to dumpcerts.sh ([#3116](https://github.com/containous/traefik/pull/3116) by [mathuin](https://github.com/mathuin))
|
||||
- **[acme]** Add ACME certificates only on ACME EntryPoint ([#3136](https://github.com/containous/traefik/pull/3136) by [nmengin](https://github.com/nmengin))
|
||||
- **[acme]** Add TTL and custom Timeout in DigitalOcean DNS provider ([#3143](https://github.com/containous/traefik/pull/3143) by [ldez](https://github.com/ldez))
|
||||
- **[acme]** Fix acme.json file automatic creation ([#3156](https://github.com/containous/traefik/pull/3156) by [nmengin](https://github.com/nmengin))
|
||||
- **[acme]** Fix wildcard match to ACME domains in cluster mode ([#3080](https://github.com/containous/traefik/pull/3080) by [oldmantaiter](https://github.com/oldmantaiter))
|
||||
- **[api,cluster]** Moved /api/cluster/leadership handler under public routes (requires no authentication) ([#3101](https://github.com/containous/traefik/pull/3101) by [aantono](https://github.com/aantono))
|
||||
- **[authentication,middleware]** Forward auth: copy response headers when auth failed. ([#3207](https://github.com/containous/traefik/pull/3207) by [ldez](https://github.com/ldez))
|
||||
- **[consul,docker,ecs,eureka,k8s,kv,marathon,mesos,rancher]** Server weight zero ([#3130](https://github.com/containous/traefik/pull/3130) by [ldez](https://github.com/ldez))
|
||||
- **[docker,k8s,marathon]** Fix custom headers template ([#2622](https://github.com/containous/traefik/pull/2622) by [ldez](https://github.com/ldez))
|
||||
- **[docker,marathon,mesos,rancher]** Fix: label 'traefik.domain' ([#3201](https://github.com/containous/traefik/pull/3201) by [ldez](https://github.com/ldez))
|
||||
- **[docker,rancher]** Frontend rule and segment labels. ([#3091](https://github.com/containous/traefik/pull/3091) by [ldez](https://github.com/ldez))
|
||||
- **[docker,rancher]** Ignore server for container with empty IP address. ([#3213](https://github.com/containous/traefik/pull/3213) by [ldez](https://github.com/ldez))
|
||||
- **[docker]** Fix multiple frontends with docker-compose --scale ([#3190](https://github.com/containous/traefik/pull/3190) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- **[healthcheck]** Remove unnecessary mutex usage in health checks ([#2726](https://github.com/containous/traefik/pull/2726) by [marco-jantke](https://github.com/marco-jantke))
|
||||
- **[k8s]** Missing annotation prefix support. ([#2915](https://github.com/containous/traefik/pull/2915) by [ldez](https://github.com/ldez))
|
||||
- **[k8s]** Remove hardcoded frontend prefix in Kubernetes template ([#2914](https://github.com/containous/traefik/pull/2914) by [psalaberria002](https://github.com/psalaberria002))
|
||||
- **[k8s]** Limit label selector to Ingress factory. ([#3137](https://github.com/containous/traefik/pull/3137) by [timoreimann](https://github.com/timoreimann))
|
||||
- **[k8s]** Fixes prefixed annotations support. ([#3110](https://github.com/containous/traefik/pull/3110) by [ldez](https://github.com/ldez))
|
||||
- **[logs,middleware]** Fix bad access log ([#2682](https://github.com/containous/traefik/pull/2682) by [mmatur](https://github.com/mmatur))
|
||||
- **[logs]** Add missing argument in log. ([#3188](https://github.com/containous/traefik/pull/3188) by [chemidy](https://github.com/chemidy))
|
||||
- **[marathon]** Several apps with same backend name in Marathon. ([#3109](https://github.com/containous/traefik/pull/3109) by [ldez](https://github.com/ldez))
|
||||
- **[mesos]** fix: overflow on 32 bits arch. ([#3127](https://github.com/containous/traefik/pull/3127) by [ldez](https://github.com/ldez))
|
||||
- **[metrics]** Fix duplicated tags in InfluxDB ([#3189](https://github.com/containous/traefik/pull/3189) by [mmatur](https://github.com/mmatur))
|
||||
- **[middleware,consul,consulcatalog,docker,ecs,kv,marathon,mesos,rancher]** Fix: error pages ([#3138](https://github.com/containous/traefik/pull/3138) by [ldez](https://github.com/ldez))
|
||||
- **[middleware,tracing]** Fix <nil> tracer value in KV ([#2911](https://github.com/containous/traefik/pull/2911) by [mmatur](https://github.com/mmatur))
|
||||
- **[middleware,tracing]** Fix nil value when tracing is enabled ([#3192](https://github.com/containous/traefik/pull/3192) by [mmatur](https://github.com/mmatur))
|
||||
- **[middleware]** Use responseModifier to override secure headers ([#2946](https://github.com/containous/traefik/pull/2946) by [mmatur](https://github.com/mmatur))
|
||||
- **[middleware]** Correct conditional setting of buffering retry expression. ([#2865](https://github.com/containous/traefik/pull/2865) by [ldez](https://github.com/ldez))
|
||||
- **[middleware]** Fix high memory usage in retry middleware ([#2740](https://github.com/containous/traefik/pull/2740) by [marco-jantke](https://github.com/marco-jantke))
|
||||
- **[middleware]** Fix whitelist and XFF. ([#3211](https://github.com/containous/traefik/pull/3211) by [ldez](https://github.com/ldez))
|
||||
- **[middleware]** Fix panic in atomic on ARM and x86-32 platforms ([#3195](https://github.com/containous/traefik/pull/3195) by [mmatur](https://github.com/mmatur))
|
||||
- **[middleware]** Redirect to HTTPS first before basic auth if header redirect (secure) is set ([#3187](https://github.com/containous/traefik/pull/3187) by [SantoDE](https://github.com/SantoDE))
|
||||
- **[middleware]** Fix error pages redirect and headers. ([#3217](https://github.com/containous/traefik/pull/3217) by [ldez](https://github.com/ldez))
|
||||
- **[provider]** Add some missing quotes in templates ([#2973](https://github.com/containous/traefik/pull/2973) by [ldez](https://github.com/ldez))
|
||||
- **[servicefabric]** Fix backend name for stateful service and more. ([#3183](https://github.com/containous/traefik/pull/3183) by [ldez](https://github.com/ldez))
|
||||
- **[tracing]** Fix missing configuration for jaeger reporter ([#2720](https://github.com/containous/traefik/pull/2720) by [mmatur](https://github.com/mmatur))
|
||||
- **[tracing]** Tracing statusCodeTracker need to implement CloseNotify ([#2733](https://github.com/containous/traefik/pull/2733) by [mmatur](https://github.com/mmatur))
|
||||
- **[tracing]** Fix integration tests in tracing ([#2759](https://github.com/containous/traefik/pull/2759) by [mmatur](https://github.com/mmatur))
|
||||
- **[webui]** Remove useless ACME tab from UI. ([#3154](https://github.com/containous/traefik/pull/3154) by [ldez](https://github.com/ldez))
|
||||
- **[webui]** Add redirect section. ([#3243](https://github.com/containous/traefik/pull/3243) by [ldez](https://github.com/ldez))
|
||||
|
||||
**Documentation:**
|
||||
- **[docker]** Add default values for some Docker labels ([#2604](https://github.com/containous/traefik/pull/2604) by [ldez](https://github.com/ldez))
|
||||
- **[file]** Add documentation about Templating in backend file ([#3223](https://github.com/containous/traefik/pull/3223) by [nmengin](https://github.com/nmengin))
|
||||
- **[k8s]** Update traefik-ds.yaml with --api command line parameter ([#2803](https://github.com/containous/traefik/pull/2803) by [maniankara](https://github.com/maniankara))
|
||||
- **[k8s]** Remove web provider in example ([#2807](https://github.com/containous/traefik/pull/2807) by [pigletfly](https://github.com/pigletfly))
|
||||
- **[k8s]** Drop capabilities in Kubernetes DaemonSet example ([#3028](https://github.com/containous/traefik/pull/3028) by [nogoegst](https://github.com/nogoegst))
|
||||
- **[k8s]** Docs: Fix typos in k8s user-guide ([#2898](https://github.com/containous/traefik/pull/2898) by [cez81](https://github.com/cez81))
|
||||
- **[k8s]** Change boolean annotation values to string ([#2839](https://github.com/containous/traefik/pull/2839) by [hobti01](https://github.com/hobti01))
|
||||
- **[k8s]** Update kubernetes.md ([#3093](https://github.com/containous/traefik/pull/3093) by [rdrgporto](https://github.com/rdrgporto))
|
||||
- **[k8s]** Document custom k8s ingress class usage in guide. ([#3242](https://github.com/containous/traefik/pull/3242) by [timoreimann](https://github.com/timoreimann))
|
||||
- **[k8s]** Update kubernetes.md ([#3171](https://github.com/containous/traefik/pull/3171) by [andreyfedoseev](https://github.com/andreyfedoseev))
|
||||
- **[provider]** Split security labels and custom labels documentation. ([#2872](https://github.com/containous/traefik/pull/2872) by [ldez](https://github.com/ldez))
|
||||
- **[provider]** Remove non-supported label. ([#3065](https://github.com/containous/traefik/pull/3065) by [ldez](https://github.com/ldez))
|
||||
- **[provider]** Remove obsolete paragraph about error pages. ([#2608](https://github.com/containous/traefik/pull/2608) by [ldez](https://github.com/ldez))
|
||||
- **[provider]** Cleaning labels/annotations documentation. ([#3245](https://github.com/containous/traefik/pull/3245) by [ldez](https://github.com/ldez))
|
||||
- **[provider]** Fix template version documentation. ([#3184](https://github.com/containous/traefik/pull/3184) by [ldez](https://github.com/ldez))
|
||||
- **[servicefabric]** Add SF to supported backends in docs ([#3033](https://github.com/containous/traefik/pull/3033) by [lawrencegripper](https://github.com/lawrencegripper))
|
||||
- **[servicefabric]** Update SF white list documentation section. ([#3082](https://github.com/containous/traefik/pull/3082) by [ldez](https://github.com/ldez))
|
||||
- **[tracing]** Fix typo in doc for rate limit label ([#2790](https://github.com/containous/traefik/pull/2790) by [mmatur](https://github.com/mmatur))
|
||||
- **[tracing]** Add Tracing entry in the documentation. ([#2713](https://github.com/containous/traefik/pull/2713) by [ldez](https://github.com/ldez))
|
||||
- **[tracing]** Fix documentation for tracing with Jaeger ([#3227](https://github.com/containous/traefik/pull/3227) by [mmatur](https://github.com/mmatur))
|
||||
- **[webui]** doc: update Traefik images. ([#3241](https://github.com/containous/traefik/pull/3241) by [ldez](https://github.com/ldez))
|
||||
- Fix typo in documentation ([#3215](https://github.com/containous/traefik/pull/3215) by [arnaslu](https://github.com/arnaslu))
|
||||
- Minor improvements to documentation ([#3221](https://github.com/containous/traefik/pull/3221) by [colincoller](https://github.com/colincoller))
|
||||
- Update some examples ([#3150](https://github.com/containous/traefik/pull/3150) by [zaporylie](https://github.com/zaporylie))
|
||||
- Normalize parameter names in configs ([#3132](https://github.com/containous/traefik/pull/3132) by [kachkaev](https://github.com/kachkaev))
|
||||
- Fixed documentation urls on README.md ([#3102](https://github.com/containous/traefik/pull/3102) by [emir](https://github.com/emir))
|
||||
- Fix typo and tweak formatting in quickstart ([#3250](https://github.com/containous/traefik/pull/3250) by [alexymik](https://github.com/alexymik))
|
||||
- Fix basic documentation ([#3086](https://github.com/containous/traefik/pull/3086) by [mmatur](https://github.com/mmatur))
|
||||
- Prepare release v1.6.0-rc6 ([#3199](https://github.com/containous/traefik/pull/3199) by [mmatur](https://github.com/mmatur))
|
||||
- Prepare release v1.6.0-rc5 ([#3179](https://github.com/containous/traefik/pull/3179) by [Juliens](https://github.com/Juliens))
|
||||
- Prepare release v1.6.0-rc4 ([#3126](https://github.com/containous/traefik/pull/3126) by [ldez](https://github.com/ldez))
|
||||
- Prepare release v1.6.0-rc3 ([#3096](https://github.com/containous/traefik/pull/3096) by [ldez](https://github.com/ldez))
|
||||
- Prepare release v1.6.0-rc2 ([#3087](https://github.com/containous/traefik/pull/3087) by [nmengin](https://github.com/nmengin))
|
||||
- Prepare release v1.6.0-rc1 ([#3078](https://github.com/containous/traefik/pull/3078) by [Juliens](https://github.com/Juliens))
|
||||
- Prepare release v1.6.0 ([#3251](https://github.com/containous/traefik/pull/3251) by [Juliens](https://github.com/Juliens))
|
||||
|
||||
**Misc:**
|
||||
- **[oxy]** Disable closeNotify when method GET for http pipelining ([#3108](https://github.com/containous/traefik/pull/3108) by [Juliens](https://github.com/Juliens))
|
||||
- **[boltdb,consul,etcd,kv,zk]** Migrate from libkv to valkeyrie library ([#2743](https://github.com/containous/traefik/pull/2743) by [nmengin](https://github.com/nmengin))
|
||||
- Drop unnecessary type conversions ([#2583](https://github.com/containous/traefik/pull/2583) by [ferhatelmas](https://github.com/ferhatelmas))
|
||||
- Code simplification ([#2516](https://github.com/containous/traefik/pull/2516) by [ferhatelmas](https://github.com/ferhatelmas))
|
||||
- Merge v1.5.4 into master ([#3024](https://github.com/containous/traefik/pull/3024) by [ldez](https://github.com/ldez))
|
||||
- Merge v1.5.3 into master ([#2943](https://github.com/containous/traefik/pull/2943) by [ldez](https://github.com/ldez))
|
||||
- Merge v1.5.2 into master ([#2843](https://github.com/containous/traefik/pull/2843) by [ldez](https://github.com/ldez))
|
||||
- Merge v1.5.1 into master ([#2781](https://github.com/containous/traefik/pull/2781) by [ldez](https://github.com/ldez))
|
||||
- Merge v1.5.0-rc5 into master ([#2708](https://github.com/containous/traefik/pull/2708) by [ldez](https://github.com/ldez))
|
||||
- Merge v1.5.0-rc3 into master ([#2600](https://github.com/containous/traefik/pull/2600) by [ldez](https://github.com/ldez))
|
||||
- Merge v1.5.0-rc2 into master ([#2536](https://github.com/containous/traefik/pull/2536) by [ldez](https://github.com/ldez))
|
||||
|
||||
## [v1.6.0-rc6](https://github.com/containous/traefik/tree/v1.6.0-rc6) (2018-04-17)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.6.0-rc5...v1.6.0-rc6)
|
||||
|
||||
|
33
Gopkg.lock
generated
@@ -257,8 +257,8 @@
|
||||
[[projects]]
|
||||
name = "github.com/containous/staert"
|
||||
packages = ["."]
|
||||
revision = "cc00c303ccbd2491ddc1dccc9eb7ccadd807557e"
|
||||
version = "v3.1.0"
|
||||
revision = "66717a0e0ca950c4b6dc8c87b46da0b8495c6e41"
|
||||
version = "v3.1.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/containous/traefik-extra-service-fabric"
|
||||
@@ -320,9 +320,10 @@
|
||||
version = "v3.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/dnsimple/dnsimple-go"
|
||||
packages = ["dnsimple"]
|
||||
revision = "f2d9b723cc9547d182e24ac2e527ae25d25fc93f"
|
||||
revision = "bbe1a2c87affea187478e24d3aea3cac25f870b3"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/docker/cli"
|
||||
@@ -991,9 +992,10 @@
|
||||
revision = "1f5c07e90700ae93ddcba0c7af7d9c7201646ccc"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/ovh/go-ovh"
|
||||
packages = ["ovh"]
|
||||
revision = "4b1fea467323b74c5f462f0947f402b428ca0626"
|
||||
revision = "91b7eb631d2eced3e706932a0b36ee8b5ee22e92"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -1215,7 +1217,7 @@
|
||||
"roundrobin",
|
||||
"utils"
|
||||
]
|
||||
revision = "6956548a7fa4272adeadf828455109c53933ea86"
|
||||
revision = "885e42fe04d8e0efa6c18facad4e0fc5757cde9b"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/vulcand/predicate"
|
||||
@@ -1241,10 +1243,11 @@
|
||||
revision = "0c8571ac0ce161a5feb57375a9cdf148c98c0f70"
|
||||
|
||||
[[projects]]
|
||||
branch = "containous-fork"
|
||||
branch = "master"
|
||||
name = "github.com/xenolf/lego"
|
||||
packages = [
|
||||
"acmev2",
|
||||
"acme",
|
||||
"log",
|
||||
"providers/dns",
|
||||
"providers/dns/auroradns",
|
||||
"providers/dns/azure",
|
||||
@@ -1262,9 +1265,9 @@
|
||||
"providers/dns/fastdns",
|
||||
"providers/dns/gandi",
|
||||
"providers/dns/gandiv5",
|
||||
"providers/dns/gcloud",
|
||||
"providers/dns/glesys",
|
||||
"providers/dns/godaddy",
|
||||
"providers/dns/googlecloud",
|
||||
"providers/dns/lightsail",
|
||||
"providers/dns/linode",
|
||||
"providers/dns/namecheap",
|
||||
@@ -1278,8 +1281,7 @@
|
||||
"providers/dns/route53",
|
||||
"providers/dns/vultr"
|
||||
]
|
||||
revision = "2817d2131186742bc98830c73a5d9c255b3f4537"
|
||||
source = "github.com/containous/lego"
|
||||
revision = "7fedfd1388f016c7ca7ed92a7f2024d06a7e20d8"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -1319,6 +1321,7 @@
|
||||
revision = "22ae77b79946ea320088417e4d50825671d82d57"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/oauth2"
|
||||
packages = [
|
||||
".",
|
||||
@@ -1327,7 +1330,7 @@
|
||||
"jws",
|
||||
"jwt"
|
||||
]
|
||||
revision = "7fdf09982454086d5570c7db3e11f360194830ca"
|
||||
revision = "ec22f46f877b4505e0117eeaab541714644fdd28"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -1366,6 +1369,7 @@
|
||||
revision = "8be79e1e0910c292df4e79c241bb7e8f7e725959"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "google.golang.org/api"
|
||||
packages = [
|
||||
"dns/v1",
|
||||
@@ -1373,7 +1377,7 @@
|
||||
"googleapi",
|
||||
"googleapi/internal/uritemplates"
|
||||
]
|
||||
revision = "1575df15c1bb8b18ad4d9bc5ca495cc85b0764fe"
|
||||
revision = "de943baf05a022a8f921b544b7827bacaba1aed5"
|
||||
|
||||
[[projects]]
|
||||
name = "google.golang.org/appengine"
|
||||
@@ -1444,6 +1448,7 @@
|
||||
revision = "cb884138e64c9a8bf5c7d6106d74b0fca082df0c"
|
||||
|
||||
[[projects]]
|
||||
branch = "v2"
|
||||
name = "gopkg.in/ns1/ns1-go.v2"
|
||||
packages = [
|
||||
"rest",
|
||||
@@ -1453,7 +1458,7 @@
|
||||
"rest/model/filter",
|
||||
"rest/model/monitor"
|
||||
]
|
||||
revision = "c563826f4cbef9c11bebeb9f20a3f7afe9c1e2f4"
|
||||
revision = "a5bcac82d3f637d3928d30476610891935b2d691"
|
||||
|
||||
[[projects]]
|
||||
name = "gopkg.in/square/go-jose.v2"
|
||||
@@ -1674,6 +1679,6 @@
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "c7d91203842be1915ca08a31917a079489bff7ffc6f2e494330e9556b4730a06"
|
||||
inputs-digest = "ad34e6336e6f19b82c52e991d22c5b43b9144ed7dc83d7b17197583ace43f346"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
@@ -62,7 +62,7 @@
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/containous/staert"
|
||||
version = "3.1.0"
|
||||
version = "3.1.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/containous/traefik-extra-service-fabric"
|
||||
@@ -181,9 +181,9 @@
|
||||
name = "github.com/vulcand/oxy"
|
||||
|
||||
[[constraint]]
|
||||
branch = "containous-fork"
|
||||
branch = "master"
|
||||
name = "github.com/xenolf/lego"
|
||||
source = "github.com/containous/lego"
|
||||
# version = "1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "google.golang.org/grpc"
|
||||
|
14
README.md
@@ -9,12 +9,12 @@
|
||||
[](https://microbadger.com/images/traefik)
|
||||
[](https://github.com/containous/traefik/blob/master/LICENSE.md)
|
||||
[](https://traefik.herokuapp.com)
|
||||
[](https://twitter.com/intent/follow?screen_name=traefikproxy)
|
||||
[](https://twitter.com/intent/follow?screen_name=traefik)
|
||||
|
||||
|
||||
Træfik is a modern HTTP reverse proxy and load balancer that makes deploying microservices easy.
|
||||
Træfik integrates with your existing infrastructure components ([Docker](https://www.docker.com/), [Swarm mode](https://docs.docker.com/engine/swarm/), [Kubernetes](https://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Rancher](https://rancher.com), [Amazon ECS](https://aws.amazon.com/ecs), ...) and configures itself automatically and dynamically.
|
||||
Telling Træfik where your orchestrator is could be the _only_ configuration step you need to do.
|
||||
Pointing Træfik at your orchestrator should be the _only_ configuration step you need.
|
||||
|
||||
---
|
||||
|
||||
@@ -63,7 +63,7 @@ _(But if you'd rather configure some of your routes manually, Træfik supports t
|
||||
- Websocket, HTTP/2, GRPC ready
|
||||
- Provides metrics (Rest, Prometheus, Datadog, Statsd, InfluxDB)
|
||||
- Keeps access logs (JSON, CLF)
|
||||
- [Fast](https://docs.traefik.io/benchmarks) ... which is nice
|
||||
- Fast
|
||||
- Exposes a Rest API
|
||||
- Packaged as a single binary file (made with :heart: with go) and available as a [tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) docker image
|
||||
|
||||
@@ -164,12 +164,10 @@ Each version is supported until the next one is released (e.g. 1.1.x will be sup
|
||||
|
||||
We use [Semantic Versioning](http://semver.org/)
|
||||
|
||||
## Plumbing
|
||||
## Mailing lists
|
||||
|
||||
- [Oxy](https://github.com/vulcand/oxy): an awesome proxy library made by Mailgun folks
|
||||
- [Gorilla mux](https://github.com/gorilla/mux): famous request router
|
||||
- [Negroni](https://github.com/urfave/negroni): web middlewares made simple
|
||||
- [Lego](https://github.com/xenolf/lego): the best [Let's Encrypt](https://letsencrypt.org) library in go
|
||||
- General announcements, new releases: mail at news+subscribe@traefik.io or on [the online viewer](https://groups.google.com/a/traefik.io/forum/#!forum/news)
|
||||
- Security announcements: mail at security+subscribe@traefik.io or on [the online viewer](https://groups.google.com/a/traefik.io/forum/#!forum/security).
|
||||
|
||||
## Credits
|
||||
|
||||
|
@@ -8,14 +8,16 @@ import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
acmeprovider "github.com/containous/traefik/provider/acme"
|
||||
"github.com/containous/traefik/types"
|
||||
acme "github.com/xenolf/lego/acmev2"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
// Account is used to store lets encrypt registration info
|
||||
@@ -42,6 +44,11 @@ func (a *Account) Init() error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = a.RemoveAccountV1Values()
|
||||
if err != nil {
|
||||
log.Errorf("Unable to remove ACME Account V1 values during account initialization: %v", err)
|
||||
}
|
||||
|
||||
for _, cert := range a.ChallengeCerts {
|
||||
if cert.certificate == nil {
|
||||
certificate, err := tls.X509KeyPair(cert.Certificate, cert.PrivateKey)
|
||||
@@ -103,6 +110,29 @@ func (a *Account) GetPrivateKey() crypto.PrivateKey {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveAccountV1Values removes ACME account V1 values
|
||||
func (a *Account) RemoveAccountV1Values() error {
|
||||
// Check if ACME Account is in ACME V1 format
|
||||
if a.Registration != nil {
|
||||
isOldRegistration, err := regexp.MatchString(acmeprovider.RegistrationURLPathV1Regexp, a.Registration.URI)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isOldRegistration {
|
||||
a.reset()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Account) reset() {
|
||||
log.Debug("Reset ACME account object.")
|
||||
a.Email = ""
|
||||
a.Registration = nil
|
||||
a.PrivateKey = nil
|
||||
}
|
||||
|
||||
// Certificate is used to store certificate info
|
||||
type Certificate struct {
|
||||
Domain string
|
||||
@@ -152,11 +182,23 @@ func (dc *DomainsCertificates) removeDuplicates() {
|
||||
}
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) removeEmpty() {
|
||||
certs := []*DomainsCertificate{}
|
||||
for _, cert := range dc.Certs {
|
||||
if cert.Certificate != nil && len(cert.Certificate.Certificate) > 0 && len(cert.Certificate.PrivateKey) > 0 {
|
||||
certs = append(certs, cert)
|
||||
}
|
||||
}
|
||||
dc.Certs = certs
|
||||
}
|
||||
|
||||
// Init DomainsCertificates
|
||||
func (dc *DomainsCertificates) Init() error {
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
|
||||
dc.removeEmpty()
|
||||
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
tlsCert, err := tls.X509KeyPair(domainsCertificate.Certificate.Certificate, domainsCertificate.Certificate.PrivateKey)
|
||||
if err != nil {
|
||||
|
75
acme/acme.go
@@ -9,9 +9,9 @@ import (
|
||||
fmtlog "log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
@@ -25,8 +25,11 @@ import (
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/tls/generate"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/containous/traefik/version"
|
||||
"github.com/eapache/channels"
|
||||
acme "github.com/xenolf/lego/acmev2"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/xenolf/lego/acme"
|
||||
legolog "github.com/xenolf/lego/log"
|
||||
"github.com/xenolf/lego/providers/dns"
|
||||
)
|
||||
|
||||
@@ -41,15 +44,15 @@ type ACME struct {
|
||||
Email string `description:"Email address used for registration"`
|
||||
Domains []types.Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
|
||||
Storage string `description:"File or key used for certificates storage."`
|
||||
StorageFile string // deprecated
|
||||
OnDemand bool `description:"Enable on demand certificate generation. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."` //deprecated
|
||||
StorageFile string // Deprecated
|
||||
OnDemand bool `description:"(Deprecated) Enable on demand certificate generation. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."` // Deprecated
|
||||
OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
|
||||
CAServer string `description:"CA server to use."`
|
||||
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
|
||||
DNSChallenge *acmeprovider.DNSChallenge `description:"Activate DNS-01 Challenge"`
|
||||
HTTPChallenge *acmeprovider.HTTPChallenge `description:"Activate HTTP-01 Challenge"`
|
||||
DNSProvider string `description:"Activate DNS-01 Challenge (Deprecated)"` // deprecated
|
||||
DelayDontCheckDNS flaeg.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."` // deprecated
|
||||
DNSProvider string `description:"(Deprecated) Activate DNS-01 Challenge"` // Deprecated
|
||||
DelayDontCheckDNS flaeg.Duration `description:"(Deprecated) Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."` // Deprecated
|
||||
ACMELogging bool `description:"Enable debug logging of ACME actions."`
|
||||
client *acme.Client
|
||||
defaultCertificate *tls.Certificate
|
||||
@@ -59,22 +62,32 @@ type ACME struct {
|
||||
jobs *channels.InfiniteChannel
|
||||
TLSConfig *tls.Config `description:"TLS config in case wildcard certs are used"`
|
||||
dynamicCerts *safe.Safe
|
||||
resolvingDomains map[string]struct{}
|
||||
resolvingDomainsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (a *ACME) init() error {
|
||||
acme.UserAgent = fmt.Sprintf("containous-traefik/%s", version.Version)
|
||||
|
||||
if a.ACMELogging {
|
||||
acme.Logger = fmtlog.New(os.Stderr, "legolog: ", fmtlog.LstdFlags)
|
||||
legolog.Logger = fmtlog.New(log.WriterLevel(logrus.DebugLevel), "legolog: ", 0)
|
||||
} else {
|
||||
acme.Logger = fmtlog.New(ioutil.Discard, "", 0)
|
||||
legolog.Logger = fmtlog.New(ioutil.Discard, "", 0)
|
||||
}
|
||||
|
||||
// no certificates in TLS config, so we add a default one
|
||||
cert, err := generate.DefaultCertificate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.defaultCertificate = cert
|
||||
|
||||
a.jobs = channels.NewInfiniteChannel()
|
||||
|
||||
// Init the currently resolved domain map
|
||||
a.resolvingDomains = make(map[string]struct{})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -178,6 +191,10 @@ func (a *ACME) leadershipListener(elected bool) error {
|
||||
|
||||
account := object.(*Account)
|
||||
account.Init()
|
||||
// Reset Account values if caServer changed, thus registration URI can be updated
|
||||
if account != nil && account.Registration != nil && !strings.HasPrefix(account.Registration.URI, a.CAServer) {
|
||||
account.reset()
|
||||
}
|
||||
|
||||
var needRegister bool
|
||||
if account == nil || len(account.Email) == 0 {
|
||||
@@ -492,6 +509,10 @@ func (a *ACME) LoadCertificateForDomains(domains []string) {
|
||||
if len(uncheckedDomains) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
a.addResolvingDomains(uncheckedDomains)
|
||||
defer a.removeResolvingDomains(uncheckedDomains)
|
||||
|
||||
certificate, err := a.getDomainsCertificates(uncheckedDomains)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting ACME certificates %+v : %v", uncheckedDomains, err)
|
||||
@@ -523,6 +544,24 @@ func (a *ACME) LoadCertificateForDomains(domains []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *ACME) addResolvingDomains(resolvingDomains []string) {
|
||||
a.resolvingDomainsMutex.Lock()
|
||||
defer a.resolvingDomainsMutex.Unlock()
|
||||
|
||||
for _, domain := range resolvingDomains {
|
||||
a.resolvingDomains[domain] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *ACME) removeResolvingDomains(resolvingDomains []string) {
|
||||
a.resolvingDomainsMutex.Lock()
|
||||
defer a.resolvingDomainsMutex.Unlock()
|
||||
|
||||
for _, domain := range resolvingDomains {
|
||||
delete(a.resolvingDomains, domain)
|
||||
}
|
||||
}
|
||||
|
||||
// Get provided certificate which check a domains list (Main and SANs)
|
||||
// from static and dynamic provided certificates
|
||||
func (a *ACME) getProvidedCertificate(domains string) *tls.Certificate {
|
||||
@@ -558,6 +597,9 @@ func searchProvidedCertificateForDomains(domain string, certs map[string]*tls.Ce
|
||||
// Get provided certificate which check a domains list (Main and SANs)
|
||||
// from static and dynamic provided certificates
|
||||
func (a *ACME) getUncheckedDomains(domains []string, account *Account) []string {
|
||||
a.resolvingDomainsMutex.RLock()
|
||||
defer a.resolvingDomainsMutex.RUnlock()
|
||||
|
||||
log.Debugf("Looking for provided certificate to validate %s...", domains)
|
||||
allCerts := make(map[string]*tls.Certificate)
|
||||
|
||||
@@ -580,6 +622,13 @@ func (a *ACME) getUncheckedDomains(domains []string, account *Account) []string
|
||||
}
|
||||
}
|
||||
|
||||
// Get currently resolved domains
|
||||
for domain := range a.resolvingDomains {
|
||||
if _, ok := allCerts[domain]; !ok {
|
||||
allCerts[domain] = &tls.Certificate{}
|
||||
}
|
||||
}
|
||||
|
||||
// Get Configuration Domains
|
||||
for i := 0; i < len(a.Domains); i++ {
|
||||
allCerts[a.Domains[i].Main] = &tls.Certificate{}
|
||||
@@ -611,11 +660,13 @@ func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) {
|
||||
domains = fun.Map(types.CanonicalDomain, domains).([]string)
|
||||
log.Debugf("Loading ACME certificates %s...", domains)
|
||||
bundle := true
|
||||
certificate, failures := a.client.ObtainCertificate(domains, bundle, nil, OSCPMustStaple)
|
||||
if len(failures) > 0 {
|
||||
log.Error(failures)
|
||||
return nil, fmt.Errorf("cannot obtain certificates %+v", failures)
|
||||
|
||||
certificate, err := a.client.ObtainCertificate(domains, bundle, nil, OSCPMustStaple)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return nil, fmt.Errorf("cannot obtain certificates: %+v", err)
|
||||
}
|
||||
|
||||
log.Debugf("Loaded ACME certificates %s", domains)
|
||||
return &Certificate{
|
||||
Domain: certificate.Domain,
|
||||
|
@@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -14,7 +15,7 @@ import (
|
||||
"github.com/containous/traefik/tls/generate"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
acme "github.com/xenolf/lego/acmev2"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
func TestDomainsSet(t *testing.T) {
|
||||
@@ -330,9 +331,12 @@ func TestAcme_getUncheckedCertificates(t *testing.T) {
|
||||
mm["*.containo.us"] = &tls.Certificate{}
|
||||
mm["traefik.acme.io"] = &tls.Certificate{}
|
||||
|
||||
a := ACME{TLSConfig: &tls.Config{NameToCertificate: mm}}
|
||||
dm := make(map[string]struct{})
|
||||
dm["*.traefik.wtf"] = struct{}{}
|
||||
|
||||
domains := []string{"traefik.containo.us", "trae.containo.us"}
|
||||
a := ACME{TLSConfig: &tls.Config{NameToCertificate: mm}, resolvingDomains: dm}
|
||||
|
||||
domains := []string{"traefik.containo.us", "trae.containo.us", "foo.traefik.wtf"}
|
||||
uncheckedDomains := a.getUncheckedDomains(domains, nil)
|
||||
assert.Empty(t, uncheckedDomains)
|
||||
domains = []string{"traefik.acme.io", "trae.acme.io"}
|
||||
@@ -350,6 +354,9 @@ func TestAcme_getUncheckedCertificates(t *testing.T) {
|
||||
account := Account{DomainsCertificate: domainsCertificates}
|
||||
uncheckedDomains = a.getUncheckedDomains(domains, &account)
|
||||
assert.Empty(t, uncheckedDomains)
|
||||
domains = []string{"traefik.containo.us", "trae.containo.us", "traefik.wtf"}
|
||||
uncheckedDomains = a.getUncheckedDomains(domains, nil)
|
||||
assert.Len(t, uncheckedDomains, 1)
|
||||
}
|
||||
|
||||
func TestAcme_getProvidedCertificate(t *testing.T) {
|
||||
@@ -550,3 +557,268 @@ func TestAcme_getCertificateForDomain(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveEmptyCertificates(t *testing.T) {
|
||||
now := time.Now()
|
||||
fooCert, fooKey, _ := generate.KeyPair("foo.com", now)
|
||||
acmeCert, acmeKey, _ := generate.KeyPair("acme.wtf", now.Add(24*time.Hour))
|
||||
barCert, barKey, _ := generate.KeyPair("bar.com", now)
|
||||
testCases := []struct {
|
||||
desc string
|
||||
dc *DomainsCertificates
|
||||
expectedDc *DomainsCertificates
|
||||
}{
|
||||
{
|
||||
desc: "No empty certificate",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: fooCert,
|
||||
PrivateKey: fooKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: barCert,
|
||||
PrivateKey: barKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: fooCert,
|
||||
PrivateKey: fooKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: barCert,
|
||||
PrivateKey: barKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "First certificate is nil",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: barCert,
|
||||
PrivateKey: barKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: nil,
|
||||
PrivateKey: barKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Last certificate is empty",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: fooCert,
|
||||
PrivateKey: fooKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{},
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: fooCert,
|
||||
PrivateKey: fooKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "First and last certificates are nil or empty",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{},
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "All certificates are nil or empty",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "foo24.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{},
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
a := &Account{DomainsCertificate: *test.dc}
|
||||
a.Init()
|
||||
|
||||
assert.Equal(t, len(test.expectedDc.Certs), len(a.DomainsCertificate.Certs))
|
||||
sort.Sort(&a.DomainsCertificate)
|
||||
sort.Sort(test.expectedDc)
|
||||
for key, value := range test.expectedDc.Certs {
|
||||
assert.Equal(t, value.Domains.Main, a.DomainsCertificate.Certs[key].Domains.Main)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
acme "github.com/xenolf/lego/acmev2"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
var _ acme.ChallengeProviderTimeout = (*challengeHTTPProvider)(nil)
|
||||
|
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/provider/acme"
|
||||
@@ -51,24 +50,6 @@ func (s *LocalStore) Get() (*Account, error) {
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// RemoveAccountV1Values removes ACME account V1 values
|
||||
func RemoveAccountV1Values(account *Account) error {
|
||||
// Check if ACME Account is in ACME V1 format
|
||||
if account != nil && account.Registration != nil {
|
||||
isOldRegistration, err := regexp.MatchString(acme.RegistrationURLPathV1Regexp, account.Registration.URI)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isOldRegistration {
|
||||
account.Email = ""
|
||||
account.Registration = nil
|
||||
account.PrivateKey = nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConvertToNewFormat converts old acme.json format to the new one and store the result into the file (used for the backward compatibility)
|
||||
func ConvertToNewFormat(fileName string) {
|
||||
localStore := acme.NewLocalStore(fileName)
|
||||
@@ -99,13 +80,13 @@ func ConvertToNewFormat(fileName string) {
|
||||
if account != nil && len(account.Email) > 0 {
|
||||
err = backupACMEFile(fileName, account)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to create a backup for the V1 formatted ACME file: %s", err.Error())
|
||||
log.Errorf("Unable to create a backup for the V1 formatted ACME file: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = RemoveAccountV1Values(account)
|
||||
err = account.RemoveAccountV1Values()
|
||||
if err != nil {
|
||||
log.Errorf("Unable to remove ACME Account V1 values: %s", err.Error())
|
||||
log.Errorf("Unable to remove ACME Account V1 values during format conversion: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@@ -14,9 +14,19 @@ type DashboardHandler struct{}
|
||||
// AddRoutes add dashboard routes on a router
|
||||
func (g DashboardHandler) AddRoutes(router *mux.Router) {
|
||||
// Expose dashboard
|
||||
router.Methods(http.MethodGet).Path("/").HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||
http.Redirect(response, request, request.Header.Get("X-Forwarded-Prefix")+"/dashboard/", 302)
|
||||
})
|
||||
router.Methods(http.MethodGet).PathPrefix("/dashboard/").
|
||||
router.Methods(http.MethodGet).
|
||||
Path("/").
|
||||
HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||
http.Redirect(response, request, request.Header.Get("X-Forwarded-Prefix")+"/dashboard/", 302)
|
||||
})
|
||||
|
||||
router.Methods(http.MethodGet).
|
||||
Path("/dashboard/status").
|
||||
HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||
http.Redirect(response, request, "/dashboard/", 302)
|
||||
})
|
||||
|
||||
router.Methods(http.MethodGet).
|
||||
PathPrefix("/dashboard/").
|
||||
Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: genstatic.Asset, AssetInfo: genstatic.AssetInfo, AssetDir: genstatic.AssetDir, Prefix: "static"})))
|
||||
}
|
||||
|
@@ -1089,6 +1089,7 @@ var _templatesKubernetesTmpl = []byte(`[backends]
|
||||
entryPoint = "{{ $frontend.Redirect.EntryPoint }}"
|
||||
regex = "{{ $frontend.Redirect.Regex }}"
|
||||
replacement = "{{ $frontend.Redirect.Replacement }}"
|
||||
permanent = {{ $frontend.Redirect.Permanent }}
|
||||
{{end}}
|
||||
|
||||
{{if $frontend.Errors }}
|
||||
|
@@ -220,7 +220,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||
SamplingServerURL: "http://localhost:5778/sampling",
|
||||
SamplingType: "const",
|
||||
SamplingParam: 1.0,
|
||||
LocalAgentHostPort: "127.0.0.1:6832",
|
||||
LocalAgentHostPort: "127.0.0.1:6831",
|
||||
},
|
||||
Zipkin: &zipkin.Config{
|
||||
HTTPEndpoint: "http://localhost:9411/api/v1/spans",
|
||||
|
@@ -140,7 +140,7 @@ func migrateACMEData(fileName string) (*acme.Account, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = acme.RemoveAccountV1Values(account)
|
||||
err = account.RemoveAccountV1Values()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -175,7 +175,7 @@ func runCmd(globalConfiguration *configuration.GlobalConfiguration, configFile s
|
||||
log.Debugf("Global configuration loaded %s", string(jsonConf))
|
||||
if acme.IsEnabled() {
|
||||
store := acme.NewLocalStore(acme.Get().Storage)
|
||||
acme.Get().Store = &store
|
||||
acme.Get().Store = store
|
||||
}
|
||||
svr := server.NewServer(*globalConfiguration, configuration.NewProviderAggregator(globalConfiguration))
|
||||
if acme.IsEnabled() && acme.Get().OnHostRule {
|
||||
|
@@ -50,24 +50,27 @@ const (
|
||||
// DefaultGraceTimeout controls how long Traefik serves pending requests
|
||||
// prior to shutting down.
|
||||
DefaultGraceTimeout = 10 * time.Second
|
||||
|
||||
// DefaultAcmeCAServer is the default ACME API endpoint
|
||||
DefaultAcmeCAServer = "https://acme-v02.api.letsencrypt.org/directory"
|
||||
)
|
||||
|
||||
// GlobalConfiguration holds global configuration (with providers, etc.).
|
||||
// It's populated from the traefik configuration file passed as an argument to the binary.
|
||||
type GlobalConfiguration struct {
|
||||
LifeCycle *LifeCycle `description:"Timeouts influencing the server life cycle" export:"true"`
|
||||
GraceTimeOut flaeg.Duration `short:"g" description:"(Deprecated) Duration to give active requests a chance to finish before Traefik stops" export:"true"` // Deprecated
|
||||
Debug bool `short:"d" description:"Enable debug mode" export:"true"`
|
||||
CheckNewVersion bool `description:"Periodically check if a new version has been released" export:"true"`
|
||||
SendAnonymousUsage bool `description:"send periodically anonymous usage statistics" export:"true"`
|
||||
AccessLogsFile string `description:"(Deprecated) Access logs file" export:"true"` // Deprecated
|
||||
AccessLog *types.AccessLog `description:"Access log settings" export:"true"`
|
||||
TraefikLogsFile string `description:"(Deprecated) Traefik logs file. Stdout is used when omitted or empty" export:"true"` // Deprecated
|
||||
TraefikLog *types.TraefikLog `description:"Traefik log settings" export:"true"`
|
||||
Tracing *tracing.Tracing `description:"OpenTracing configuration" export:"true"`
|
||||
LogLevel string `short:"l" description:"Log level" export:"true"`
|
||||
EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key;prod/traefik.crt,prod/traefik.key'" export:"true"`
|
||||
Cluster *types.Cluster `description:"Enable clustering" export:"true"`
|
||||
LifeCycle *LifeCycle `description:"Timeouts influencing the server life cycle" export:"true"`
|
||||
GraceTimeOut flaeg.Duration `short:"g" description:"(Deprecated) Duration to give active requests a chance to finish before Traefik stops" export:"true"` // Deprecated
|
||||
Debug bool `short:"d" description:"Enable debug mode" export:"true"`
|
||||
CheckNewVersion bool `description:"Periodically check if a new version has been released" export:"true"`
|
||||
SendAnonymousUsage bool `description:"send periodically anonymous usage statistics" export:"true"`
|
||||
AccessLogsFile string `description:"(Deprecated) Access logs file" export:"true"` // Deprecated
|
||||
AccessLog *types.AccessLog `description:"Access log settings" export:"true"`
|
||||
TraefikLogsFile string `description:"(Deprecated) Traefik logs file. Stdout is used when omitted or empty" export:"true"` // Deprecated
|
||||
TraefikLog *types.TraefikLog `description:"Traefik log settings" export:"true"`
|
||||
Tracing *tracing.Tracing `description:"OpenTracing configuration" export:"true"`
|
||||
LogLevel string `short:"l" description:"Log level" export:"true"`
|
||||
EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key;prod/traefik.crt,prod/traefik.key'" export:"true"`
|
||||
Cluster *types.Cluster
|
||||
Constraints types.Constraints `description:"Filter services by constraint, matching with service tags" export:"true"`
|
||||
ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL" export:"true"`
|
||||
DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint" export:"true"`
|
||||
@@ -105,13 +108,13 @@ type GlobalConfiguration struct {
|
||||
|
||||
// WebCompatibility is a configuration to handle compatibility with deprecated web provider options
|
||||
type WebCompatibility struct {
|
||||
Address string `description:"Web administration port" export:"true"`
|
||||
CertFile string `description:"SSL certificate" export:"true"`
|
||||
KeyFile string `description:"SSL certificate" export:"true"`
|
||||
ReadOnly bool `description:"Enable read only API" export:"true"`
|
||||
Statistics *types.Statistics `description:"Enable more detailed statistics" export:"true"`
|
||||
Metrics *types.Metrics `description:"Enable a metrics exporter" export:"true"`
|
||||
Path string `description:"Root path for dashboard and API" export:"true"`
|
||||
Address string `description:"(Deprecated) Web administration port" export:"true"`
|
||||
CertFile string `description:"(Deprecated) SSL certificate" export:"true"`
|
||||
KeyFile string `description:"(Deprecated) SSL certificate" export:"true"`
|
||||
ReadOnly bool `description:"(Deprecated) Enable read only API" export:"true"`
|
||||
Statistics *types.Statistics `description:"(Deprecated) Enable more detailed statistics" export:"true"`
|
||||
Metrics *types.Metrics `description:"(Deprecated) Enable a metrics exporter" export:"true"`
|
||||
Path string `description:"(Deprecated) Root path for dashboard and API" export:"true"`
|
||||
Auth *types.Auth `export:"true"`
|
||||
Debug bool `export:"true"`
|
||||
}
|
||||
@@ -304,14 +307,8 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
|
||||
gc.Web.Path += "/"
|
||||
}
|
||||
|
||||
// Try to fallback to traefik config file in case the file provider is enabled
|
||||
// but has no file name configured and is not in a directory mode.
|
||||
if gc.File != nil && len(gc.File.Filename) == 0 && len(gc.File.Directory) == 0 {
|
||||
if len(configFile) > 0 {
|
||||
gc.File.Filename = configFile
|
||||
} else {
|
||||
log.Errorln("Error using file configuration backend, no filename defined")
|
||||
}
|
||||
if gc.File != nil {
|
||||
gc.File.TraefikFile = configFile
|
||||
}
|
||||
|
||||
gc.initACMEProvider()
|
||||
@@ -327,7 +324,7 @@ func (gc *GlobalConfiguration) initTracing() {
|
||||
SamplingServerURL: "http://localhost:5778/sampling",
|
||||
SamplingType: "const",
|
||||
SamplingParam: 1.0,
|
||||
LocalAgentHostPort: "127.0.0.1:6832",
|
||||
LocalAgentHostPort: "127.0.0.1:6831",
|
||||
}
|
||||
}
|
||||
if gc.Tracing.Zipkin != nil {
|
||||
@@ -356,7 +353,14 @@ func (gc *GlobalConfiguration) initTracing() {
|
||||
|
||||
func (gc *GlobalConfiguration) initACMEProvider() {
|
||||
if gc.ACME != nil {
|
||||
// TODO: to remove in the futurs
|
||||
gc.ACME.CAServer = getSafeACMECAServer(gc.ACME.CAServer)
|
||||
|
||||
if gc.ACME.DNSChallenge != nil && gc.ACME.HTTPChallenge != nil {
|
||||
log.Warn("Unable to use DNS challenge and HTTP challenge at the same time. Fallback to DNS challenge.")
|
||||
gc.ACME.HTTPChallenge = nil
|
||||
}
|
||||
|
||||
// TODO: to remove in the future
|
||||
if len(gc.ACME.StorageFile) > 0 && len(gc.ACME.Storage) == 0 {
|
||||
log.Warn("ACME.StorageFile is deprecated, use ACME.Storage instead")
|
||||
gc.ACME.Storage = gc.ACME.StorageFile
|
||||
@@ -391,6 +395,26 @@ func (gc *GlobalConfiguration) initACMEProvider() {
|
||||
}
|
||||
}
|
||||
|
||||
func getSafeACMECAServer(caServerSrc string) string {
|
||||
if len(caServerSrc) == 0 {
|
||||
return DefaultAcmeCAServer
|
||||
}
|
||||
|
||||
if strings.HasPrefix(caServerSrc, "https://acme-v01.api.letsencrypt.org") {
|
||||
caServer := strings.Replace(caServerSrc, "v01", "v02", 1)
|
||||
log.Warnf("The CA server %[1]q refers to a v01 endpoint of the ACME API, please change to %[2]q. Fallback to %[2]q.", caServerSrc, caServer)
|
||||
return caServer
|
||||
}
|
||||
|
||||
if strings.HasPrefix(caServerSrc, "https://acme-staging.api.letsencrypt.org") {
|
||||
caServer := strings.Replace(caServerSrc, "https://acme-staging.api.letsencrypt.org", "https://acme-staging-v02.api.letsencrypt.org", 1)
|
||||
log.Warnf("The CA server %[1]q refers to a v01 endpoint of the ACME API, please change to %[2]q. Fallback to %[2]q.", caServerSrc, caServer)
|
||||
return caServer
|
||||
}
|
||||
|
||||
return caServerSrc
|
||||
}
|
||||
|
||||
// ValidateConfiguration validate that configuration is coherent
|
||||
func (gc *GlobalConfiguration) ValidateConfiguration() {
|
||||
if gc.ACME != nil {
|
||||
|
@@ -65,24 +65,28 @@ func TestSetEffectiveConfigurationGraceTimeout(t *testing.T) {
|
||||
|
||||
func TestSetEffectiveConfigurationFileProviderFilename(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
fileProvider *file.Provider
|
||||
wantFileProviderFilename string
|
||||
desc string
|
||||
fileProvider *file.Provider
|
||||
wantFileProviderFilename string
|
||||
wantFileProviderTraefikFile string
|
||||
}{
|
||||
{
|
||||
desc: "no filename for file provider given",
|
||||
fileProvider: &file.Provider{},
|
||||
wantFileProviderFilename: defaultConfigFile,
|
||||
desc: "no filename for file provider given",
|
||||
fileProvider: &file.Provider{},
|
||||
wantFileProviderFilename: "",
|
||||
wantFileProviderTraefikFile: defaultConfigFile,
|
||||
},
|
||||
{
|
||||
desc: "filename for file provider given",
|
||||
fileProvider: &file.Provider{BaseProvider: provider.BaseProvider{Filename: "other.toml"}},
|
||||
wantFileProviderFilename: "other.toml",
|
||||
desc: "filename for file provider given",
|
||||
fileProvider: &file.Provider{BaseProvider: provider.BaseProvider{Filename: "other.toml"}},
|
||||
wantFileProviderFilename: "other.toml",
|
||||
wantFileProviderTraefikFile: defaultConfigFile,
|
||||
},
|
||||
{
|
||||
desc: "directory for file provider given",
|
||||
fileProvider: &file.Provider{Directory: "/"},
|
||||
wantFileProviderFilename: "",
|
||||
desc: "directory for file provider given",
|
||||
fileProvider: &file.Provider{Directory: "/"},
|
||||
wantFileProviderFilename: "",
|
||||
wantFileProviderTraefikFile: defaultConfigFile,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -98,6 +102,7 @@ func TestSetEffectiveConfigurationFileProviderFilename(t *testing.T) {
|
||||
gc.SetEffectiveConfiguration(defaultConfigFile)
|
||||
|
||||
assert.Equal(t, test.wantFileProviderFilename, gc.File.Filename)
|
||||
assert.Equal(t, test.wantFileProviderTraefikFile, gc.File.TraefikFile)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -139,7 +144,7 @@ func TestSetEffectiveConfigurationTracing(t *testing.T) {
|
||||
SamplingServerURL: "http://localhost:5778/sampling",
|
||||
SamplingType: "const",
|
||||
SamplingParam: 1.0,
|
||||
LocalAgentHostPort: "127.0.0.1:6832",
|
||||
LocalAgentHostPort: "127.0.0.1:6831",
|
||||
},
|
||||
Zipkin: nil,
|
||||
},
|
||||
@@ -152,7 +157,7 @@ func TestSetEffectiveConfigurationTracing(t *testing.T) {
|
||||
SamplingServerURL: "http://localhost:5778/sampling",
|
||||
SamplingType: "const",
|
||||
SamplingParam: 1.0,
|
||||
LocalAgentHostPort: "127.0.0.1:6832",
|
||||
LocalAgentHostPort: "127.0.0.1:6831",
|
||||
},
|
||||
},
|
||||
expected: &tracing.Tracing{
|
||||
@@ -174,7 +179,7 @@ func TestSetEffectiveConfigurationTracing(t *testing.T) {
|
||||
SamplingServerURL: "http://localhost:5778/sampling",
|
||||
SamplingType: "const",
|
||||
SamplingParam: 1.0,
|
||||
LocalAgentHostPort: "127.0.0.1:6832",
|
||||
LocalAgentHostPort: "127.0.0.1:6831",
|
||||
},
|
||||
Zipkin: &zipkin.Config{
|
||||
HTTPEndpoint: "http://powpow:9411/api/v1/spans",
|
||||
|
@@ -170,7 +170,7 @@ func TestEntryPoints_Set(t *testing.T) {
|
||||
name: "all parameters camelcase",
|
||||
expression: "Name:foo " +
|
||||
"Address::8000 " +
|
||||
"TLS:goo,gii " +
|
||||
"TLS:goo,gii;foo,fii " +
|
||||
"TLS " +
|
||||
"CA:car " +
|
||||
"CA.Optional:true " +
|
||||
@@ -203,6 +203,10 @@ func TestEntryPoints_Set(t *testing.T) {
|
||||
CertFile: tls.FileOrContent("goo"),
|
||||
KeyFile: tls.FileOrContent("gii"),
|
||||
},
|
||||
{
|
||||
CertFile: tls.FileOrContent("foo"),
|
||||
KeyFile: tls.FileOrContent("fii"),
|
||||
},
|
||||
},
|
||||
ClientCA: tls.ClientCA{
|
||||
Files: []string{"car"},
|
||||
@@ -272,7 +276,7 @@ func TestEntryPoints_Set(t *testing.T) {
|
||||
name: "all parameters lowercase",
|
||||
expression: "Name:foo " +
|
||||
"address::8000 " +
|
||||
"tls:goo,gii " +
|
||||
"tls:goo,gii;foo,fii " +
|
||||
"tls " +
|
||||
"ca:car " +
|
||||
"ca.Optional:true " +
|
||||
@@ -303,6 +307,10 @@ func TestEntryPoints_Set(t *testing.T) {
|
||||
CertFile: tls.FileOrContent("goo"),
|
||||
KeyFile: tls.FileOrContent("gii"),
|
||||
},
|
||||
{
|
||||
CertFile: tls.FileOrContent("foo"),
|
||||
KeyFile: tls.FileOrContent("fii"),
|
||||
},
|
||||
},
|
||||
ClientCA: tls.ClientCA{
|
||||
Files: []string{"car"},
|
||||
|
@@ -1,4 +1,4 @@
|
||||
FROM alpine
|
||||
FROM alpine:3.14
|
||||
|
||||
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/bin
|
||||
|
||||
|
106
docs/basics.md
@@ -262,7 +262,7 @@ This allows for setting headers such as `X-Script-Name` to be added to the reque
|
||||
!!! warning
|
||||
If the custom header name is the same as one header name of the request or response, it will be replaced.
|
||||
|
||||
In this example, all matches to the path `/cheese` will have the `X-Script-Name` header added to the proxied request, and the `X-Custom-Response-Header` added to the response.
|
||||
In this example, all matches to the path `/cheese` will have the `X-Script-Name` header added to the proxied request and the `X-Custom-Response-Header` header added to the response.
|
||||
|
||||
```toml
|
||||
[frontends]
|
||||
@@ -276,7 +276,7 @@ In this example, all matches to the path `/cheese` will have the `X-Script-Name`
|
||||
rule = "PathPrefixStrip:/cheese"
|
||||
```
|
||||
|
||||
In this second example, all matches to the path `/cheese` will have the `X-Script-Name` header added to the proxied request, the `X-Custom-Request-Header` removed to the request and the `X-Custom-Response-Header` removed to the response.
|
||||
In this second example, all matches to the path `/cheese` will have the `X-Script-Name` header added to the proxied request, the `X-Custom-Request-Header` header removed from the request, and the `X-Custom-Response-Header` header removed from the response.
|
||||
|
||||
```toml
|
||||
[frontends]
|
||||
@@ -323,12 +323,49 @@ In this example, traffic routed through the first frontend will have the `X-Fram
|
||||
|
||||
A backend is responsible to load-balance the traffic coming from one or more frontends to a set of http servers.
|
||||
|
||||
#### Servers
|
||||
|
||||
Servers are simply defined using a `url`. You can also apply a custom `weight` to each server (this will be used by load-balancing).
|
||||
|
||||
!!! note
|
||||
Paths in `url` are ignored. Use `Modifier` to specify paths instead.
|
||||
|
||||
Here is an example of backends and servers definition:
|
||||
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
# ...
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://172.17.0.2:80"
|
||||
weight = 10
|
||||
[backends.backend1.servers.server2]
|
||||
url = "http://172.17.0.3:80"
|
||||
weight = 1
|
||||
[backends.backend2]
|
||||
# ...
|
||||
[backends.backend2.servers.server1]
|
||||
url = "http://172.17.0.4:80"
|
||||
weight = 1
|
||||
[backends.backend2.servers.server2]
|
||||
url = "http://172.17.0.5:80"
|
||||
weight = 2
|
||||
```
|
||||
|
||||
- Two backends are defined: `backend1` and `backend2`
|
||||
- `backend1` will forward the traffic to two servers: `http://172.17.0.2:80"` with weight `10` and `http://172.17.0.3:80` with weight `1`.
|
||||
- `backend2` will forward the traffic to two servers: `http://172.17.0.4:80"` with weight `1` and `http://172.17.0.5:80` with weight `2`.
|
||||
|
||||
#### Load-balancing
|
||||
|
||||
Various methods of load-balancing are supported:
|
||||
|
||||
- `wrr`: Weighted Round Robin.
|
||||
- `drr`: Dynamic Round Robin: increases weights on servers that perform better than others.
|
||||
It also rolls back to original weights if the servers have changed.
|
||||
|
||||
#### Circuit breakers
|
||||
|
||||
A circuit breaker can also be applied to a backend, preventing high loads on failing servers.
|
||||
Initial state is Standby. CB observes the statistics and does not modify the request.
|
||||
In case the condition matches, CB enters Tripped state, where it responds with predefined code or redirects to another frontend.
|
||||
@@ -346,6 +383,26 @@ For example:
|
||||
- `LatencyAtQuantileMS(50.0) > 50`: watch latency at quantile in milliseconds.
|
||||
- `ResponseCodeRatio(500, 600, 0, 600) > 0.5`: ratio of response codes in ranges [500-600) and [0-600).
|
||||
|
||||
Here is an example of backends and servers definition:
|
||||
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.circuitbreaker]
|
||||
expression = "NetworkErrorRatio() > 0.5"
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://172.17.0.2:80"
|
||||
weight = 10
|
||||
[backends.backend1.servers.server2]
|
||||
url = "http://172.17.0.3:80"
|
||||
weight = 1
|
||||
```
|
||||
|
||||
- `backend1` will forward the traffic to two servers: `http://172.17.0.2:80"` with weight `10` and `http://172.17.0.3:80` with weight `1` using default `wrr` load-balancing strategy.
|
||||
- a circuit breaker is added on `backend1` using the expression `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window
|
||||
|
||||
#### Maximum connections
|
||||
|
||||
To proactively prevent backends from being overwhelmed with high load, a maximum connection limit can also be applied to each backend.
|
||||
|
||||
Maximum connections can be configured by specifying an integer value for `maxconn.amount` and `maxconn.extractorfunc` which is a strategy used to determine how to categorize requests in order to evaluate the maximum connections.
|
||||
@@ -357,13 +414,14 @@ For example:
|
||||
[backends.backend1.maxconn]
|
||||
amount = 10
|
||||
extractorfunc = "request.host"
|
||||
# ...
|
||||
```
|
||||
|
||||
- `backend1` will return `HTTP code 429 Too Many Requests` if there are already 10 requests in progress for the same Host header.
|
||||
- Another possible value for `extractorfunc` is `client.ip` which will categorize requests based on client source ip.
|
||||
- Lastly `extractorfunc` can take the value of `request.header.ANY_HEADER` which will categorize requests based on `ANY_HEADER` that you provide.
|
||||
|
||||
### Sticky sessions
|
||||
#### Sticky sessions
|
||||
|
||||
Sticky sessions are supported with both load balancers.
|
||||
When sticky sessions are enabled, a cookie is set on the initial request.
|
||||
@@ -371,7 +429,6 @@ 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.
|
||||
|
||||
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
@@ -395,10 +452,10 @@ The deprecated way:
|
||||
sticky = true
|
||||
```
|
||||
|
||||
### Health Check
|
||||
#### Health Check
|
||||
|
||||
A health check can be configured in order to remove a backend from LB rotation as long as it keeps returning HTTP status codes other than `200 OK` to HTTP GET requests periodically carried out by Traefik.
|
||||
The check is defined by a pathappended to the backend URL and an interval (given in a format understood by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration)) specifying how often the health check should be executed (the default being 30 seconds).
|
||||
The check is defined by a path appended to the backend URL and an interval (given in a format understood by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration)) specifying how often the health check should be executed (the default being 30 seconds).
|
||||
Each backend must respond to the health check within 5 seconds.
|
||||
By default, the port of the backend server is used, however, this may be overridden.
|
||||
|
||||
@@ -424,43 +481,6 @@ To use a different port for the healthcheck:
|
||||
port = 8080
|
||||
```
|
||||
|
||||
### Servers
|
||||
|
||||
Servers are simply defined using a `url`. You can also apply a custom `weight` to each server (this will be used by load-balancing).
|
||||
|
||||
!!! note
|
||||
Paths in `url` are ignored. Use `Modifier` to specify paths instead.
|
||||
|
||||
Here is an example of backends and servers definition:
|
||||
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.circuitbreaker]
|
||||
expression = "NetworkErrorRatio() > 0.5"
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://172.17.0.2:80"
|
||||
weight = 10
|
||||
[backends.backend1.servers.server2]
|
||||
url = "http://172.17.0.3:80"
|
||||
weight = 1
|
||||
[backends.backend2]
|
||||
[backends.backend2.LoadBalancer]
|
||||
method = "drr"
|
||||
[backends.backend2.servers.server1]
|
||||
url = "http://172.17.0.4:80"
|
||||
weight = 1
|
||||
[backends.backend2.servers.server2]
|
||||
url = "http://172.17.0.5:80"
|
||||
weight = 2
|
||||
```
|
||||
|
||||
- Two backends are defined: `backend1` and `backend2`
|
||||
- `backend1` will forward the traffic to two servers: `http://172.17.0.2:80"` with weight `10` and `http://172.17.0.3:80` with weight `1` using default `wrr` load-balancing strategy.
|
||||
- `backend2` will forward the traffic to two servers: `http://172.17.0.4:80"` with weight `1` and `http://172.17.0.5:80` with weight `2` using `drr` load-balancing strategy.
|
||||
- a circuit breaker is added on `backend1` using the expression `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
Træfik's configuration has two parts:
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# ACME (Let's Encrypt) configuration
|
||||
# ACME (Let's Encrypt) Configuration
|
||||
|
||||
See also [Let's Encrypt examples](/user-guide/examples/#lets-encrypt-support) and [Docker & Let's Encrypt user guide](/user-guide/docker-and-lets-encrypt).
|
||||
See [Let's Encrypt examples](/user-guide/examples/#lets-encrypt-support) and [Docker & Let's Encrypt user guide](/user-guide/docker-and-lets-encrypt) as well.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -63,14 +63,14 @@ entryPoint = "https"
|
||||
#
|
||||
# acmeLogging = true
|
||||
|
||||
# Enable on demand certificate generation.
|
||||
# Deprecated. Enable on demand certificate generation.
|
||||
#
|
||||
# Optional (Deprecated)
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# onDemand = true
|
||||
|
||||
# Enable certificate generation on frontends Host rules.
|
||||
# Enable certificate generation on frontends host rules.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
@@ -78,8 +78,8 @@ entryPoint = "https"
|
||||
# onHostRule = true
|
||||
|
||||
# CA server to use.
|
||||
# - Uncomment the line to run on the staging let's encrypt server.
|
||||
# - Leave comment to go to prod.
|
||||
# Uncomment the line to use Let's Encrypt's staging server,
|
||||
# leave commented to go to prod.
|
||||
#
|
||||
# Optional
|
||||
# Default: "https://acme-v02.api.letsencrypt.org/directory"
|
||||
@@ -94,15 +94,13 @@ entryPoint = "https"
|
||||
# sans = ["test1.local1.com", "test2.local1.com"]
|
||||
# [[acme.domains]]
|
||||
# main = "local2.com"
|
||||
# sans = ["test1.local2.com", "test2.local2.com"]
|
||||
# [[acme.domains]]
|
||||
# main = "local3.com"
|
||||
# [[acme.domains]]
|
||||
# main = "local4.com"
|
||||
# main = "*.local3.com"
|
||||
# sans = ["local3.com", "test1.test1.local3.com"]
|
||||
|
||||
# Use a HTTP-01 acme challenge.
|
||||
# Use a HTTP-01 ACME challenge.
|
||||
#
|
||||
# Optional but recommend
|
||||
# Optional (but recommended)
|
||||
#
|
||||
[acme.httpChallenge]
|
||||
|
||||
@@ -112,21 +110,21 @@ entryPoint = "https"
|
||||
#
|
||||
entryPoint = "http"
|
||||
|
||||
# Use a DNS-01/DNS-01 acme challenge rather than HTTP-01 challenge.
|
||||
# Note : Mandatory for wildcard certificates generation.
|
||||
# Use a DNS-01 ACME challenge rather than HTTP-01 challenge.
|
||||
# Note: mandatory for wildcard certificate generation.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [acme.dnsChallenge]
|
||||
|
||||
# Provider used.
|
||||
# DNS provider used.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
# provider = "digitalocean"
|
||||
|
||||
# By default, the provider will verify the TXT DNS challenge record before letting ACME verify.
|
||||
# If delayBeforeCheck is greater than zero, avoid this & instead just wait so many seconds.
|
||||
# If delayBeforeCheck is greater than zero, this check is delayed for the configured duration in seconds.
|
||||
# Useful if internal networks block external DNS queries.
|
||||
#
|
||||
# Optional
|
||||
@@ -135,98 +133,134 @@ entryPoint = "https"
|
||||
# delayBeforeCheck = 0
|
||||
```
|
||||
|
||||
!!! note
|
||||
If `HTTP-01` challenge is used, `acme.httpChallenge.entryPoint` has to be defined and reachable by Let's Encrypt through the port 80.
|
||||
These are Let's Encrypt limitations as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
|
||||
### `caServer`
|
||||
|
||||
!!! note
|
||||
Wildcard certificates can be generated only if `acme.dnsChallenge`
|
||||
option is enable.
|
||||
The CA server to use.
|
||||
|
||||
### Let's Encrypt downtime
|
||||
|
||||
Let's Encrypt functionality will be limited until Træfik is restarted.
|
||||
|
||||
If Let's Encrypt is not reachable, these certificates will be used :
|
||||
|
||||
- ACME certificates already generated before downtime
|
||||
- Expired ACME certificates
|
||||
- Provided certificates
|
||||
|
||||
!!! note
|
||||
Default Træfik certificate will be used instead of ACME certificates for new (sub)domains (which need Let's Encrypt challenge).
|
||||
|
||||
### `storage`
|
||||
This example shows the usage of Let's Encrypt's staging server:
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
storage = "acme.json"
|
||||
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
# ...
|
||||
```
|
||||
|
||||
The `storage` option sets where are stored your ACME certificates.
|
||||
### `dnsChallenge`
|
||||
|
||||
There are two kind of `storage` :
|
||||
Use the `DNS-01` challenge to generate and renew ACME certificates by provisioning a DNS record.
|
||||
|
||||
- a JSON file,
|
||||
- a KV store entry.
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
[acme.dnsChallenge]
|
||||
provider = "digitalocean"
|
||||
delayBeforeCheck = 0
|
||||
# ...
|
||||
```
|
||||
|
||||
!!! danger "DEPRECATED"
|
||||
`storage` replaces `storageFile` which is deprecated.
|
||||
#### `delayBeforeCheck`
|
||||
|
||||
By default, the `provider` will verify the TXT DNS challenge record before letting ACME verify.
|
||||
If `delayBeforeCheck` is greater than zero, this check is delayed for the configured duration in seconds.
|
||||
|
||||
Useful if internal networks block external DNS queries.
|
||||
|
||||
!!! note
|
||||
During Træfik configuration migration from a configuration file to a KV store (thanks to `storeconfig` subcommand as described [here](/user-guide/kv-config/#store-configuration-in-key-value-store)), if ACME certificates have to be migrated too, use both `storageFile` and `storage`.
|
||||
A `provider` is mandatory.
|
||||
|
||||
- `storageFile` will contain the path to the `acme.json` file to migrate.
|
||||
- `storage` will contain the key where the certificates will be stored.
|
||||
#### `provider`
|
||||
|
||||
#### Store data in a file
|
||||
Here is a list of supported `provider`s, that can automate the DNS verification, along with the required environment variables and their [wildcard & root domain support](/configuration/acme/#wildcard-domains) for each. Do not hesitate to complete it.
|
||||
|
||||
ACME certificates can be stored in a JSON file which with the `600` right mode.
|
||||
| Provider Name | Provider Code | Environment Variables | Wildcard & Root Domain Support |
|
||||
|--------------------------------------------------------|----------------|-----------------------------------------------------------------------------------------------------------------------------|--------------------------------|
|
||||
| [Auroradns](https://www.pcextreme.com/aurora/dns) | `auroradns` | `AURORA_USER_ID`, `AURORA_KEY`, `AURORA_ENDPOINT` | Not tested yet |
|
||||
| [Azure](https://azure.microsoft.com/services/dns/) | `azure` | `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SUBSCRIPTION_ID`, `AZURE_TENANT_ID`, `AZURE_RESOURCE_GROUP` | Not tested yet |
|
||||
| [Blue Cat](https://www.bluecatnetworks.com/) | `bluecat` | `BLUECAT_SERVER_URL`, `BLUECAT_USER_NAME`, `BLUECAT_PASSWORD`, `BLUECAT_CONFIG_NAME`, `BLUECAT_DNS_VIEW` | Not tested yet |
|
||||
| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | `CLOUDFLARE_EMAIL`, `CLOUDFLARE_API_KEY` - The `Global API Key` needs to be used, not the `Origin CA Key` | YES |
|
||||
| [CloudXNS](https://www.cloudxns.net) | `cloudxns` | `CLOUDXNS_API_KEY`, `CLOUDXNS_SECRET_KEY` | Not tested yet |
|
||||
| [DigitalOcean](https://www.digitalocean.com) | `digitalocean` | `DO_AUTH_TOKEN` | YES |
|
||||
| [DNSimple](https://dnsimple.com) | `dnsimple` | `DNSIMPLE_OAUTH_TOKEN`, `DNSIMPLE_BASE_URL` | Not tested yet |
|
||||
| [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET`, `DNSMADEEASY_SANDBOX` | Not tested yet |
|
||||
| [DNSPod](http://www.dnspod.net/) | `dnspod` | `DNSPOD_API_KEY` | Not tested yet |
|
||||
| [Duck DNS](https://www.duckdns.org/) | `duckdns` | `DUCKDNS_TOKEN` | Not tested yet |
|
||||
| [Dyn](https://dyn.com) | `dyn` | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` | Not tested yet |
|
||||
| External Program | `exec` | `EXEC_PATH` | Not tested yet |
|
||||
| [Exoscale](https://www.exoscale.ch) | `exoscale` | `EXOSCALE_API_KEY`, `EXOSCALE_API_SECRET`, `EXOSCALE_ENDPOINT` | YES |
|
||||
| [Fast DNS](https://www.akamai.com/) | `fastdns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | Not tested yet |
|
||||
| [Gandi](https://www.gandi.net) | `gandi` | `GANDI_API_KEY` | Not tested yet |
|
||||
| [Gandi V5](http://doc.livedns.gandi.net) | `gandiv5` | `GANDIV5_API_KEY` | YES |
|
||||
| [Glesys](https://glesys.com/) | `glesys` | `GLESYS_API_USER`, `GLESYS_API_KEY`, `GLESYS_DOMAIN` | Not tested yet |
|
||||
| [GoDaddy](https://godaddy.com/domains) | `godaddy` | `GODADDY_API_KEY`, `GODADDY_API_SECRET` | Not tested yet |
|
||||
| [Google Cloud DNS](https://cloud.google.com/dns/docs/) | `gcloud` | `GCE_PROJECT`, `GCE_SERVICE_ACCOUNT_FILE` | YES |
|
||||
| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `DNS_ZONE` | Not tested yet |
|
||||
| [Linode](https://www.linode.com) | `linode` | `LINODE_API_KEY` | Not tested yet |
|
||||
| manual | - | none, but you need to run Træfik interactively, turn on `acmeLogging` to see instructions and press <kbd>Enter</kbd>. | YES |
|
||||
| [Namecheap](https://www.namecheap.com) | `namecheap` | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` | Not tested yet |
|
||||
| [name.com](https://www.name.com/) | `namedotcom` | `NAMECOM_USERNAME`, `NAMECOM_API_TOKEN`, `NAMECOM_SERVER` | Not tested yet |
|
||||
| [Ns1](https://ns1.com/) | `ns1` | `NS1_API_KEY` | Not tested yet |
|
||||
| [Open Telekom Cloud](https://cloud.telekom.de/en/) | `otc` | `OTC_DOMAIN_NAME`, `OTC_USER_NAME`, `OTC_PASSWORD`, `OTC_PROJECT_NAME`, `OTC_IDENTITY_ENDPOINT` | Not tested yet |
|
||||
| [OVH](https://www.ovh.com) | `ovh` | `OVH_ENDPOINT`, `OVH_APPLICATION_KEY`, `OVH_APPLICATION_SECRET`, `OVH_CONSUMER_KEY` | YES |
|
||||
| [PowerDNS](https://www.powerdns.com) | `pdns` | `PDNS_API_KEY`, `PDNS_API_URL` | Not tested yet |
|
||||
| [Rackspace](https://www.rackspace.com/cloud/dns) | `rackspace` | `RACKSPACE_USER`, `RACKSPACE_API_KEY` | Not tested yet |
|
||||
| [RFC2136](https://tools.ietf.org/html/rfc2136) | `rfc2136` | `RFC2136_TSIG_KEY`, `RFC2136_TSIG_SECRET`, `RFC2136_TSIG_ALGORITHM`, `RFC2136_NAMESERVER` | Not tested yet |
|
||||
| [Route 53](https://aws.amazon.com/route53/) | `route53` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `AWS_HOSTED_ZONE_ID` or a configured user/instance IAM profile. | YES |
|
||||
| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | Not tested yet |
|
||||
|
||||
There are two ways to store ACME certificates in a file from Docker:
|
||||
### `domains`
|
||||
|
||||
You can provide SANs (alternative domains) to each main domain.
|
||||
All domains must have A/AAAA records pointing to Træfik.
|
||||
Each domain & SAN will lead to a certificate request.
|
||||
|
||||
- create a file on your host and mount it as a volume:
|
||||
```toml
|
||||
storage = "acme.json"
|
||||
```
|
||||
```bash
|
||||
docker run -v "/my/host/acme.json:acme.json" traefik
|
||||
```
|
||||
- mount the folder containing the file as a volume
|
||||
```toml
|
||||
storage = "/etc/traefik/acme/acme.json"
|
||||
```
|
||||
```bash
|
||||
docker run -v "/my/host/acme:/etc/traefik/acme" traefik
|
||||
[acme]
|
||||
# ...
|
||||
[[acme.domains]]
|
||||
main = "local1.com"
|
||||
sans = ["test1.local1.com", "test2.local1.com"]
|
||||
[[acme.domains]]
|
||||
main = "local2.com"
|
||||
[[acme.domains]]
|
||||
main = "*.local3.com"
|
||||
sans = ["local3.com", "test1.test1.local3.com"]
|
||||
# ...
|
||||
```
|
||||
|
||||
!!! warning
|
||||
This file cannot be shared per many instances of Træfik at the same time.
|
||||
If you have to use Træfik cluster mode, please use [a KV Store entry](/configuration/acme/#storage-kv-entry).
|
||||
|
||||
#### Store data in a KV store entry
|
||||
|
||||
ACME certificates can be stored in a KV Store entry.
|
||||
|
||||
```toml
|
||||
storage = "traefik/acme/account"
|
||||
```
|
||||
|
||||
**This kind of storage is mandatory in cluster mode.**
|
||||
|
||||
Because KV stores (like Consul) have limited entries size, the certificates list is compressed before to be set in a KV store entry.
|
||||
Take note that Let's Encrypt applies [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
||||
|
||||
!!! note
|
||||
It's possible to store up to approximately 100 ACME certificates in Consul.
|
||||
Wildcard certificates can only be verified through a `DNS-01` challenge.
|
||||
|
||||
#### Wildcard Domains
|
||||
|
||||
[ACME V2](https://community.letsencrypt.org/t/acme-v2-and-wildcard-certificate-support-is-live/55579) allows wildcard certificate support.
|
||||
As described in [Let's Encrypt's post](https://community.letsencrypt.org/t/staging-endpoint-for-acme-v2/49605) wildcard certificates can only be generated through a [`DNS-01` challenge](/configuration/acme/#dnschallenge).
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
[[acme.domains]]
|
||||
main = "*.local1.com"
|
||||
sans = ["local1.com"]
|
||||
# ...
|
||||
```
|
||||
|
||||
It is not possible to request a double wildcard certificate for a domain (for example `*.*.local.com`).
|
||||
Due to ACME limitation it is not possible to define wildcards in SANs (alternative domains). Thus, the wildcard domain has to be defined as a main domain.
|
||||
Most likely the root domain should receive a certificate too, so it needs to be specified as SAN and 2 `DNS-01` challenges are executed.
|
||||
In this case the generated DNS TXT record for both domains is the same.
|
||||
Eventhough this behaviour is [DNS RFC](https://community.letsencrypt.org/t/wildcard-issuance-two-txt-records-for-the-same-name/54528/2) compliant, it can lead to problems as all DNS providers keep DNS records cached for a certain time (TTL) and this TTL can be superior to the challenge timeout making the `DNS-01` challenge fail.
|
||||
The Træfik ACME client library [LEGO](https://github.com/xenolf/lego) supports some but not all DNS providers to work around this issue.
|
||||
The [`provider` table](/configuration/acme/#provider) indicates if they allow generating certificates for a wildcard domain and its root domain.
|
||||
|
||||
### `httpChallenge`
|
||||
|
||||
Use `HTTP-01` challenge to generate/renew ACME certificates.
|
||||
Use the `HTTP-01` challenge to generate and renew ACME certificates by provisioning a HTTP resource under a well-known URI.
|
||||
|
||||
The redirection is fully compatible with the HTTP-01 challenge.
|
||||
You can use redirection with HTTP-01 challenge without problem.
|
||||
Redirection is fully compatible with the `HTTP-01` challenge.
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
@@ -236,6 +270,10 @@ entryPoint = "https"
|
||||
entryPoint = "http"
|
||||
```
|
||||
|
||||
!!! note
|
||||
If the `HTTP-01` challenge is used, `acme.httpChallenge.entryPoint` has to be defined and reachable by Let's Encrypt through port 80.
|
||||
This is a Let's Encrypt limitation as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
|
||||
|
||||
#### `entryPoint`
|
||||
|
||||
Specify the entryPoint to use during the challenges.
|
||||
@@ -259,73 +297,7 @@ defaultEntryPoints = ["http", "https"]
|
||||
```
|
||||
|
||||
!!! note
|
||||
`acme.httpChallenge.entryPoint` has to be reachable by Let's Encrypt through the port 80.
|
||||
It's a Let's Encrypt limitation as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
|
||||
|
||||
### `dnsChallenge`
|
||||
|
||||
Use `DNS-01/DNS-01` challenge to generate/renew ACME certificates.
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
[acme.dnsChallenge]
|
||||
provider = "digitalocean"
|
||||
delayBeforeCheck = 0
|
||||
# ...
|
||||
```
|
||||
|
||||
!!! note
|
||||
ACME wildcard certificates can only be generated thanks to a `DNS-01` challenge.
|
||||
|
||||
#### `provider`
|
||||
|
||||
Select the provider that matches the DNS domain that will host the challenge TXT record, and provide environment variables to enable setting it:
|
||||
|
||||
| Provider Name | Provider code | Configuration |
|
||||
|--------------------------------------------------------|----------------|---------------------------------------------------------------------------------------------------------------------------|
|
||||
| [Auroradns](https://www.pcextreme.com/aurora/dns) | `auroradns` | `AURORA_USER_ID`, `AURORA_KEY`, `AURORA_ENDPOINT` |
|
||||
| [Azure](https://azure.microsoft.com/services/dns/) | `azure` | `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SUBSCRIPTION_ID`, `AZURE_TENANT_ID`, `AZURE_RESOURCE_GROUP` |
|
||||
| [Blue Cat](https://www.bluecatnetworks.com/) | `bluecat` | `BLUECAT_SERVER_URL`, `BLUECAT_USER_NAME`, `BLUECAT_PASSWORD`, `BLUECAT_CONFIG_NAME`, `BLUECAT_DNS_VIEW` |
|
||||
| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | `CLOUDFLARE_EMAIL`, `CLOUDFLARE_API_KEY` - The Cloudflare `Global API Key` needs to be used and not the `Origin CA Key` |
|
||||
| [CloudXNS](https://www.cloudxns.net) | `cloudxns` | `CLOUDXNS_API_KEY`, `CLOUDXNS_SECRET_KEY` |
|
||||
| [DigitalOcean](https://www.digitalocean.com) | `digitalocean` | `DO_AUTH_TOKEN` |
|
||||
| [DNSimple](https://dnsimple.com) | `dnsimple` | `DNSIMPLE_OAUTH_TOKEN`, `DNSIMPLE_BASE_URL` |
|
||||
| [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET`, `DNSMADEEASY_SANDBOX` |
|
||||
| [DNSPod](http://www.dnspod.net/) | `dnspod` | `DNSPOD_API_KEY` |
|
||||
| [Duck DNS](https://www.duckdns.org/) | `duckdns` | `DUCKDNS_TOKEN` |
|
||||
| [Dyn](https://dyn.com) | `dyn` | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` |
|
||||
| External Program | `exec` | `EXEC_PATH` |
|
||||
| [Exoscale](https://www.exoscale.ch) | `exoscale` | `EXOSCALE_API_KEY`, `EXOSCALE_API_SECRET`, `EXOSCALE_ENDPOINT` |
|
||||
| [Fast DNS](https://www.akamai.com/) | `fastdns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` |
|
||||
| [Gandi](https://www.gandi.net) | `gandi` | `GANDI_API_KEY` |
|
||||
| [Gandi V5](http://doc.livedns.gandi.net) | `gandiv5` | `GANDIV5_API_KEY` |
|
||||
| [Glesys](https://glesys.com/) | `glesys` | `GLESYS_API_USER`, `GLESYS_API_KEY`, `GLESYS_DOMAIN` |
|
||||
| [GoDaddy](https://godaddy.com/domains) | `godaddy` | `GODADDY_API_KEY`, `GODADDY_API_SECRET` |
|
||||
| [Google Cloud DNS](https://cloud.google.com/dns/docs/) | `gcloud` | `GCE_PROJECT`, `GCE_SERVICE_ACCOUNT_FILE` |
|
||||
| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `DNS_ZONE` |
|
||||
| [Linode](https://www.linode.com) | `linode` | `LINODE_API_KEY` |
|
||||
| manual | - | none, but run Træfik interactively & turn on `acmeLogging` to see instructions & press <kbd>Enter</kbd>. |
|
||||
| [Namecheap](https://www.namecheap.com) | `namecheap` | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` |
|
||||
| [name.com](https://www.name.com/) | `namedotcom` | `NAMECOM_USERNAME`, `NAMECOM_API_TOKEN`, `NAMECOM_SERVER` |
|
||||
| [Ns1](https://ns1.com/) | `ns1` | `NS1_API_KEY` |
|
||||
| [Open Telekom Cloud](https://cloud.telekom.de/en/) | `otc` | `OTC_DOMAIN_NAME`, `OTC_USER_NAME`, `OTC_PASSWORD`, `OTC_PROJECT_NAME`, `OTC_IDENTITY_ENDPOINT` |
|
||||
| [OVH](https://www.ovh.com) | `ovh` | `OVH_ENDPOINT`, `OVH_APPLICATION_KEY`, `OVH_APPLICATION_SECRET`, `OVH_CONSUMER_KEY` |
|
||||
| [PowerDNS](https://www.powerdns.com) | `pdns` | `PDNS_API_KEY`, `PDNS_API_URL` |
|
||||
| [Rackspace](https://www.rackspace.com/cloud/dns) | `rackspace` | `RACKSPACE_USER`, `RACKSPACE_API_KEY` |
|
||||
| [RFC2136](https://tools.ietf.org/html/rfc2136) | `rfc2136` | `RFC2136_TSIG_KEY`, `RFC2136_TSIG_SECRET`, `RFC2136_TSIG_ALGORITHM`, `RFC2136_NAMESERVER` |
|
||||
| [Route 53](https://aws.amazon.com/route53/) | `route53` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `AWS_HOSTED_ZONE_ID` or configured user/instance IAM profile. |
|
||||
| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` |
|
||||
|
||||
#### `delayBeforeCheck`
|
||||
|
||||
By default, the `provider` will verify the TXT DNS challenge record before letting ACME verify.
|
||||
If `delayBeforeCheck` is greater than zero, avoid this & instead just wait so many seconds.
|
||||
|
||||
Useful if internal networks block external DNS queries.
|
||||
|
||||
!!! note
|
||||
This field has no sense if a `provider` is not defined.
|
||||
`acme.httpChallenge.entryPoint` has to be reachable through port 80. It's a Let's Encrypt limitation as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
|
||||
|
||||
### `onDemand` (Deprecated)
|
||||
|
||||
@@ -339,15 +311,15 @@ onDemand = true
|
||||
# ...
|
||||
```
|
||||
|
||||
Enable on demand certificate.
|
||||
Enable on demand certificate generation.
|
||||
|
||||
This will request a certificate from Let's Encrypt during the first TLS handshake for a host name that does not yet have a certificate.
|
||||
This will request certificates from Let's Encrypt during the first TLS handshake for host names that do not yet have certificates.
|
||||
|
||||
!!! warning
|
||||
TLS handshakes will be slow when requesting a host name certificate for the first time, this can lead to DoS attacks.
|
||||
TLS handshakes are slow when requesting a host name certificate for the first time. This can lead to DoS attacks!
|
||||
|
||||
!!! warning
|
||||
Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
||||
Take note that Let's Encrypt applies [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
||||
|
||||
### `onHostRule`
|
||||
|
||||
@@ -358,199 +330,94 @@ onHostRule = true
|
||||
# ...
|
||||
```
|
||||
|
||||
Enable certificate generation on frontends `Host` rules (for frontends wired on the `acme.entryPoint`).
|
||||
Enable certificate generation on frontend `Host` rules (for frontends wired to the `acme.entryPoint`).
|
||||
|
||||
This will request a certificate from Let's Encrypt for each frontend with a Host rule.
|
||||
|
||||
For example, a rule `Host:test1.traefik.io,test2.traefik.io` will request a certificate with main domain `test1.traefik.io` and SAN `test2.traefik.io`.
|
||||
For example, the rule `Host:test1.traefik.io,test2.traefik.io` will request a certificate with main domain `test1.traefik.io` and SAN `test2.traefik.io`.
|
||||
|
||||
!!! warning
|
||||
`onHostRule` option can not be used to generate wildcard certificates.
|
||||
Refer to [the wildcard generation section](/configuration/acme/#wildcard-domain) for more information.
|
||||
Refer to [wildcard generation](/configuration/acme/#wildcard-domain) for further information.
|
||||
|
||||
### `caServer`
|
||||
### `storage`
|
||||
|
||||
The `storage` option sets the location where your ACME certificates are saved to.
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
storage = "acme.json"
|
||||
# ...
|
||||
```
|
||||
|
||||
CA server to use.
|
||||
The value can refer to two kinds of storage:
|
||||
|
||||
- Uncomment the line to run on the staging Let's Encrypt server.
|
||||
- Leave comment to go to prod.
|
||||
- a JSON file
|
||||
- a KV store entry
|
||||
|
||||
### `domains`
|
||||
!!! danger "DEPRECATED"
|
||||
`storage` replaces `storageFile` which is deprecated.
|
||||
|
||||
!!! note
|
||||
During migration to a KV store use both `storageFile` and `storage` to migrate ACME certificates too. See [`storeconfig` subcommand](/user-guide/kv-config/#store-configuration-in-key-value-store) for further information.
|
||||
|
||||
#### As a File
|
||||
|
||||
ACME certificates can be stored in a JSON file that needs to have file mode `600`.
|
||||
|
||||
In Docker you can either mount the JSON file or the folder containing it:
|
||||
|
||||
```bash
|
||||
docker run -v "/my/host/acme.json:acme.json" traefik
|
||||
```
|
||||
```bash
|
||||
docker run -v "/my/host/acme:/etc/traefik/acme" traefik
|
||||
```
|
||||
|
||||
!!! warning
|
||||
This file cannot be shared across multiple instances of Træfik at the same time. Please use a [KV Store entry](/configuration/acme/#as-a-key-value-store-entry) instead.
|
||||
|
||||
#### As a Key Value Store Entry
|
||||
|
||||
ACME certificates can be stored in a KV Store entry. This kind of storage is **mandatory in cluster mode**.
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
[[acme.domains]]
|
||||
main = "local1.com"
|
||||
sans = ["test1.local1.com", "test2.local1.com"]
|
||||
[[acme.domains]]
|
||||
main = "local2.com"
|
||||
sans = ["test1.local2.com", "test2.local2.com"]
|
||||
[[acme.domains]]
|
||||
main = "local3.com"
|
||||
[[acme.domains]]
|
||||
main = "*.local4.com"
|
||||
sans = ["local4.com", "test1.test1.local4.com"]
|
||||
# ...
|
||||
storage = "traefik/acme/account"
|
||||
```
|
||||
|
||||
#### Wildcard domains
|
||||
Because KV stores (like Consul) have limited entry size the certificates list is compressed before it is saved as KV store entry.
|
||||
|
||||
Wildcard domain has to be defined as a main domain.
|
||||
All domains must have A/AAAA records pointing to Træfik.
|
||||
!!! note
|
||||
It is possible to store up to approximately 100 ACME certificates in Consul.
|
||||
|
||||
Due to ACME limitation, it's not possible to define a wildcard as a SAN (alternative domains).
|
||||
It's neither possible to define a wildcard on a wildcard domain (for example `*.*.local.com`).
|
||||
#### ACME v2 Migration
|
||||
|
||||
!!! warning
|
||||
Note that Let's Encrypt has [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
||||
During migration from ACME v1 to ACME v2, using a storage file, a backup of the original file is created in the same place as the latter (with a `.bak` extension).
|
||||
|
||||
Each domain & SANs will lead to a certificate request.
|
||||
For example: if `acme.storage`'s value is `/etc/traefik/acme/acme.json`, the backup file will be `/etc/traefik/acme/acme.json.bak`.
|
||||
|
||||
#### Others domains
|
||||
|
||||
You can provide SANs (alternative domains) to each main domain.
|
||||
All domains must have A/AAAA records pointing to Træfik.
|
||||
|
||||
!!! warning
|
||||
Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
||||
|
||||
Each domain & SANs will lead to a certificate request.
|
||||
!!! note
|
||||
When Træfik is launched in a container, the storage file's parent directory needs to be mounted to be able to access the backup file on the host.
|
||||
Otherwise the backup file will be deleted when the container is stopped. Træfik will only generate it once!
|
||||
|
||||
### `dnsProvider` (Deprecated)
|
||||
|
||||
!!! danger "DEPRECATED"
|
||||
This option is deprecated, use [dnsChallenge.provider](/configuration/acme/#dnschallenge) instead.
|
||||
This option is deprecated. Please use [dnsChallenge.provider](/configuration/acme/#provider) instead.
|
||||
|
||||
### `delayDontCheckDNS` (Deprecated)
|
||||
|
||||
!!! danger "DEPRECATED"
|
||||
This option is deprecated, use [dnsChallenge.delayBeforeCheck](/configuration/acme/#dnschallenge) instead.
|
||||
This option is deprecated. Please use [dnsChallenge.delayBeforeCheck](/configuration/acme/#dnschallenge) instead.
|
||||
|
||||
## Wildcard certificates
|
||||
## Fallbacks
|
||||
|
||||
[ACME V2](https://community.letsencrypt.org/t/acme-v2-and-wildcard-certificate-support-is-live/55579) allows wildcard certificate support.
|
||||
However, this feature needs a specific configuration.
|
||||
If Let's Encrypt is not reachable, these certificates will be used:
|
||||
|
||||
### DNS-01 Challenge
|
||||
|
||||
As described in [Let's Encrypt post](https://community.letsencrypt.org/t/staging-endpoint-for-acme-v2/49605), wildcard certificates can only be generated through a `DNS-01` Challenge.
|
||||
This challenge is linked to the Træfik option `acme.dnsChallenge`.
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
[acme.dnsChallenge]
|
||||
provider = "digitalocean"
|
||||
delayBeforeCheck = 0
|
||||
# ...
|
||||
```
|
||||
|
||||
For more information about this option, please refer to the [dnsChallenge section](/configuration/acme/#dnschallenge).
|
||||
|
||||
### Wildcard domain
|
||||
|
||||
Wildcard domains can currently be provided only by to the `acme.domains` option.
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
[[acme.domains]]
|
||||
main = "*.local1.com"
|
||||
sans = ["local1.com"]
|
||||
[[acme.domains]]
|
||||
main = "*.local2.com"
|
||||
# ...
|
||||
```
|
||||
|
||||
For more information about this option, please refer to the [domains section](/configuration/acme/#domains).
|
||||
|
||||
### Limitations
|
||||
|
||||
Let's Encrypt wildcard support have some limitations to take into account :
|
||||
|
||||
- Wildcard domain can not be a SAN (alternative domain),
|
||||
- Wildcard domain on a wildcard domain is forbidden (for example `*.*.local.com`),
|
||||
- A DNS-01 Challenge is executed for each domain (CN and SANs), DNS provider can not manage correctly this behavior as explained in the [DNS provider support section](/configuration/acme/#dns-provider-support)
|
||||
|
||||
|
||||
### DNS provider support
|
||||
|
||||
All DNS providers allow creating ACME wildcard certificates.
|
||||
However, many troubles can appear for wildcard domains with SANs.
|
||||
|
||||
If a wildcard domain is defined with it root domain as SAN, as described below, 2 DNS-01 Challenges will be executed.
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
[[acme.domains]]
|
||||
main = "*.local1.com"
|
||||
sans = ["local1.com"]
|
||||
# ...
|
||||
```
|
||||
|
||||
When a DNS-01 Challenge is done, Let's Encrypt checks if a TXT record is created with a given name and a given value.
|
||||
When a certificate is generated for a wildcard domain is defined with it root domain as SAN, the requested TXT record name for both the wildcard domain and the root domain is the same.
|
||||
|
||||
The [DNS RFC](https://community.letsencrypt.org/t/wildcard-issuance-two-txt-records-for-the-same-name/54528/2) allows this behavior.
|
||||
But all DNS providers keep TXT records values in a cache with a TTL.
|
||||
In function of the parameters given by the Træfik ACME client library ([LEGO](https://github.com/xenolf/lego)), the TXT record TTL can be superior to challenge Timeout.
|
||||
In that event, the DNS-01 Challenge will not work correctly.
|
||||
|
||||
[LEGO](https://github.com/xenolf/lego) will involve in the way to be adapted to all of DNS providers.
|
||||
Meanwhile, the table described below contains all the DNS providers supported by Træfik and indicates if they allow generating certificates for a wildcard domain and its root domain.
|
||||
Do not hesitate to complete it.
|
||||
|
||||
| Provider Name | Provider code | Wildcard and Root Domain Support |
|
||||
|--------------------------------------------------------|----------------|----------------------------------|
|
||||
| [Auroradns](https://www.pcextreme.com/aurora/dns) | `auroradns` | Not tested yet |
|
||||
| [Azure](https://azure.microsoft.com/services/dns/) | `azure` | Not tested yet |
|
||||
| [Blue Cat](https://www.bluecatnetworks.com/) | `bluecat` | Not tested yet |
|
||||
| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | YES |
|
||||
| [CloudXNS](https://www.cloudxns.net) | `cloudxns` | Not tested yet |
|
||||
| [DigitalOcean](https://www.digitalocean.com) | `digitalocean` | YES |
|
||||
| [DNSimple](https://dnsimple.com) | `dnsimple` | Not tested yet |
|
||||
| [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | Not tested yet |
|
||||
| [DNSPod](http://www.dnspod.net/) | `dnspod` | Not tested yet |
|
||||
| [Duck DNS](https://www.duckdns.org/) | `duckdns` | Not tested yet |
|
||||
| [Dyn](https://dyn.com) | `dyn` | Not tested yet |
|
||||
| External Program | `exec` | Not tested yet |
|
||||
| [Exoscale](https://www.exoscale.ch) | `exoscale` | Not tested yet |
|
||||
| [Fast DNS](https://www.akamai.com/) | `fastdns` | Not tested yet |
|
||||
| [Gandi](https://www.gandi.net) | `gandi` | Not tested yet |
|
||||
| [Gandi V5](http://doc.livedns.gandi.net) | `gandiv5` | Not tested yet |
|
||||
| [Glesys](https://glesys.com/) | `glesys` | Not tested yet |
|
||||
| [GoDaddy](https://godaddy.com/domains) | `godaddy` | Not tested yet |
|
||||
| [Google Cloud DNS](https://cloud.google.com/dns/docs/) | `gcloud` | YES |
|
||||
| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | Not tested yet |
|
||||
| [Linode](https://www.linode.com) | `linode` | Not tested yet |
|
||||
| manual | - | YES |
|
||||
| [Namecheap](https://www.namecheap.com) | `namecheap` | Not tested yet |
|
||||
| [name.com](https://www.name.com/) | `namedotcom` | Not tested yet |
|
||||
| [Ns1](https://ns1.com/) | `ns1` | Not tested yet |
|
||||
| [Open Telekom Cloud](https://cloud.telekom.de/en/) | `otc` | Not tested yet |
|
||||
| [OVH](https://www.ovh.com) | `ovh` | YES |
|
||||
| [PowerDNS](https://www.powerdns.com) | `pdns` | Not tested yet |
|
||||
| [Rackspace](https://www.rackspace.com/cloud/dns) | `rackspace` | Not tested yet |
|
||||
| [RFC2136](https://tools.ietf.org/html/rfc2136) | `rfc2136` | Not tested yet |
|
||||
| [Route 53](https://aws.amazon.com/route53/) | `route53` | YES |
|
||||
| [VULTR](https://www.vultr.com) | `vultr` | Not tested yet |
|
||||
|
||||
## ACME V2 migration
|
||||
|
||||
During migration from ACME V1 to ACME V2 with a storage file, a backup is created with the content of the ACME V1 file.
|
||||
To obtain the name of the backup file, Træfik concatenates the option `acme.storage` and the suffix `.bak`.
|
||||
|
||||
For example : if `acme.storage` value is `/etc/traefik/acme/acme.json`, the backup file will be named `/etc/traefik/acme/acme.json.bak`.
|
||||
1. ACME certificates already generated before downtime
|
||||
1. Expired ACME certificates
|
||||
1. Provided certificates
|
||||
|
||||
!!! note
|
||||
When Træfik is launched in a container, do not forget to create a volume of the parent folder to get the backup file on the host.
|
||||
Otherwise, the backup file will be deleted when the container will be stopped and Træfik will not generate it again.
|
||||
For new (sub)domains which need Let's Encrypt authentification, the default Træfik certificate will be used until Træfik is restarted.
|
||||
|
@@ -4,6 +4,9 @@
|
||||
|
||||
```toml
|
||||
# API definition
|
||||
# Warning: Enabling API will expose Træfik's configuration.
|
||||
# It is not recommended in production,
|
||||
# unless secured by authentication and authorizations
|
||||
[api]
|
||||
# Name of the related entry point
|
||||
#
|
||||
@@ -12,7 +15,7 @@
|
||||
#
|
||||
entryPoint = "traefik"
|
||||
|
||||
# Enabled Dashboard
|
||||
# Enable Dashboard
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
@@ -21,7 +24,7 @@
|
||||
|
||||
# Enable debug mode.
|
||||
# This will install HTTP handlers to expose Go expvars under /debug/vars and
|
||||
# pprof profiling data under /debug/pprof.
|
||||
# pprof profiling data under /debug/pprof/.
|
||||
# Additionally, the log level will be set to DEBUG.
|
||||
#
|
||||
# Optional
|
||||
@@ -30,7 +33,7 @@
|
||||
debug = true
|
||||
```
|
||||
|
||||
For more customization, see [entry points](/configuration/entrypoints/) documentation and [examples](/user-guide/examples/#ping-health-check).
|
||||
For more customization, see [entry points](/configuration/entrypoints/) documentation.
|
||||
|
||||
## Web UI
|
||||
|
||||
@@ -38,6 +41,22 @@ For more customization, see [entry points](/configuration/entrypoints/) document
|
||||
|
||||

|
||||
|
||||
## Security
|
||||
|
||||
Enabling the API will expose all configuration elements,
|
||||
including sensitive data.
|
||||
|
||||
It is not recommended in production,
|
||||
unless secured by authentication and authorizations.
|
||||
|
||||
A good sane default (but not exhaustive) set of recommendations
|
||||
would be to apply the following protection mechanism:
|
||||
|
||||
* _At application level:_ enabling HTTP [Basic Authentication](#authentication)
|
||||
* _At transport level:_ NOT exposing publicly the API's port,
|
||||
keeping it restricted over internal networks
|
||||
(restricted networks as in https://en.wikipedia.org/wiki/Principle_of_least_privilege).
|
||||
|
||||
## API
|
||||
|
||||
| Path | Method | Description |
|
||||
|
@@ -1,13 +1,13 @@
|
||||
# BoltDB Backend
|
||||
# BoltDB Provider
|
||||
|
||||
Træfik can be configured to use BoltDB as a backend configuration.
|
||||
Træfik can be configured to use BoltDB as a provider.
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# BoltDB configuration backend
|
||||
# BoltDB Provider
|
||||
################################################################
|
||||
|
||||
# Enable BoltDB configuration backend.
|
||||
# Enable BoltDB Provider.
|
||||
[boltdb]
|
||||
|
||||
# BoltDB file.
|
||||
@@ -56,4 +56,4 @@ filename = "boltdb.tmpl"
|
||||
# insecureSkipVerify = true
|
||||
```
|
||||
|
||||
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
|
||||
|
@@ -1,13 +1,13 @@
|
||||
# Consul Key-Value Backend
|
||||
# Consul Key-Value Provider
|
||||
|
||||
Træfik can be configured to use Consul as a backend configuration.
|
||||
Træfik can be configured to use Consul as a provider.
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Consul KV configuration backend
|
||||
# Consul KV Provider
|
||||
################################################################
|
||||
|
||||
# Enable Consul KV configuration backend.
|
||||
# Enable Consul KV Provider.
|
||||
[consul]
|
||||
|
||||
# Consul server endpoint.
|
||||
@@ -56,6 +56,6 @@ prefix = "traefik"
|
||||
# insecureSkipVerify = true
|
||||
```
|
||||
|
||||
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
|
||||
|
||||
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure.
|
||||
|
@@ -1,13 +1,13 @@
|
||||
# Consul Catalog backend
|
||||
# Consul Catalog Provider
|
||||
|
||||
Træfik can be configured to use service discovery catalog of Consul as a backend configuration.
|
||||
Træfik can be configured to use service discovery catalog of Consul as a provider.
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Consul Catalog configuration backend
|
||||
# Consul Catalog Provider
|
||||
################################################################
|
||||
|
||||
# Enable Consul Catalog configuration backend.
|
||||
# Enable Consul Catalog Provider.
|
||||
[consulCatalog]
|
||||
|
||||
# Consul server endpoint.
|
||||
@@ -76,9 +76,9 @@ prefix = "traefik"
|
||||
# templateVersion = 2
|
||||
```
|
||||
|
||||
This backend will create routes matching on hostname based on the service name used in Consul.
|
||||
This provider will create routes matching on hostname based on the service name used in Consul.
|
||||
|
||||
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
|
||||
|
||||
## Tags
|
||||
|
||||
@@ -90,7 +90,6 @@ Additional settings can be defined using Consul Catalog tags.
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `<prefix>.enable=false` | Disable this container in Træfik. |
|
||||
| `<prefix>.port=80` | Register this port. Useful when the container exposes multiples ports. |
|
||||
| `<prefix>.protocol=https` | Override the default `http` protocol. |
|
||||
| `<prefix>.weight=10` | Assign this weight to the container. |
|
||||
| `traefik.backend.buffering.maxRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
@@ -146,7 +145,17 @@ Additional settings can be defined using Consul Catalog tags.
|
||||
| Label | Description |
|
||||
|-----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `<prefix>.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.<br>Format: `Host1,Host2` |
|
||||
| `<prefix>.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `<prefix>.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
|
||||
| `<prefix>.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `<prefix>.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `<prefix>.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `<prefix>.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
|
||||
| `<prefix>.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `<prefix>.frontend.headers.hostsProxyHeaders=EXPR` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` |
|
||||
| `<prefix>.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
| `<prefix>.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
|
||||
| `<prefix>.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||
| `<prefix>.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
|
||||
| `<prefix>.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
|
||||
| `<prefix>.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
|
||||
@@ -154,16 +163,6 @@ Additional settings can be defined using Consul Catalog tags.
|
||||
| `<prefix>.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
|
||||
| `<prefix>.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
|
||||
| `<prefix>.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
|
||||
| `<prefix>.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
|
||||
| `<prefix>.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `<prefix>.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `<prefix>.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `<prefix>.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `<prefix>.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `<prefix>.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
|
||||
| `<prefix>.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
|
||||
| `<prefix>.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||
| `<prefix>.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
|
||||
### Examples
|
||||
|
||||
|
@@ -1,16 +1,16 @@
|
||||
|
||||
# Docker Backend
|
||||
# Docker Provider
|
||||
|
||||
Træfik can be configured to use Docker as a backend configuration.
|
||||
Træfik can be configured to use Docker as a provider.
|
||||
|
||||
## Docker
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Docker configuration backend
|
||||
# Docker Provider
|
||||
################################################################
|
||||
|
||||
# Enable Docker configuration backend.
|
||||
# Enable Docker Provider.
|
||||
[docker]
|
||||
|
||||
# Docker server endpoint. Can be a tcp or a unix socket endpoint.
|
||||
@@ -82,17 +82,17 @@ swarmMode = false
|
||||
# insecureSkipVerify = true
|
||||
```
|
||||
|
||||
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
|
||||
|
||||
|
||||
## Docker Swarm Mode
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Docker Swarm Mode configuration backend
|
||||
# Docker Swarm Mode Provider
|
||||
################################################################
|
||||
|
||||
# Enable Docker configuration backend.
|
||||
# Enable Docker Provider.
|
||||
[docker]
|
||||
|
||||
# Docker server endpoint.
|
||||
@@ -159,7 +159,7 @@ exposedByDefault = false
|
||||
# insecureSkipVerify = true
|
||||
```
|
||||
|
||||
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
|
||||
|
||||
## Labels: overriding default behavior
|
||||
|
||||
@@ -196,6 +196,7 @@ Labels can be used on containers to override default behavior.
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.docker.network` | Set the docker network to use for connections to this container. [1] |
|
||||
| `traefik.domain` | Default domain used for frontend rules. |
|
||||
| `traefik.enable=false` | Disable this container in Træfik |
|
||||
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. |
|
||||
| `traefik.protocol=https` | Override the default `http` protocol |
|
||||
@@ -217,7 +218,7 @@ Labels can be used on containers to override default behavior.
|
||||
| `traefik.backend.loadbalancer.swarm=true` | Use Swarm's inbuilt load balancer (only relevant under Swarm Mode). |
|
||||
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
|
||||
| `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` [2] |
|
||||
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`.<br>Overrides `defaultEntryPoints` |
|
||||
| `traefik.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
@@ -242,6 +243,10 @@ If a container is linked to several networks, be sure to set the proper network
|
||||
For instance when deploying docker `stack` from compose files, the compose defined networks will be prefixed with the `stack` name.
|
||||
Or if your service references external network use it's name instead.
|
||||
|
||||
[2] `traefik.frontend.auth.basic=EXPR`:
|
||||
To create `user:password` pair, it's possible to use this command `echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g`.
|
||||
The result will be `user:$$apr1$$9Cv/OMGj$$ZomWQzuQbL.3TRCS81A1g/`, note additional symbol `$` makes escaping.
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
| Label | Description |
|
||||
@@ -254,7 +259,17 @@ Or if your service references external network use it's name instead.
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.<br>Format: `Host1,Host2` |
|
||||
| `traefik.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `traefik.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
|
||||
| `traefik.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `traefik.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `traefik.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `traefik.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
|
||||
| `traefik.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `traefik.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` |
|
||||
| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
|
||||
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||
| `traefik.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
|
||||
| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
|
||||
| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
|
||||
@@ -262,16 +277,6 @@ Or if your service references external network use it's name instead.
|
||||
| `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
|
||||
| `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
|
||||
| `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
|
||||
| `traefik.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
|
||||
| `traefik.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `traefik.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `traefik.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `traefik.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `traefik.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `traefik.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
|
||||
| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
|
||||
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||
| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
|
||||
### On containers with Multiple Ports (segment labels)
|
||||
|
||||
@@ -281,62 +286,63 @@ You can define as many segments as ports exposed in a container.
|
||||
|
||||
Segment labels override the default behavior.
|
||||
|
||||
| Label | Description |
|
||||
|---------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the segment labels could be used. |
|
||||
| `traefik.<segment_name>.protocol` | Overrides `traefik.protocol`. |
|
||||
| `traefik.<segment_name>.weight` | Assign this segment weight. Overrides `traefik.weight`. |
|
||||
| `traefik.<segment_name>.frontend.auth.basic` | Sets a Basic Auth for that frontend |
|
||||
| `traefik.<segment_name>.frontend.backend=BACKEND` | Assign this segment frontend to `BACKEND`. Default is to assign to the segment backend. |
|
||||
| `traefik.<segment_name>.frontend.entryPoints` | Overrides `traefik.frontend.entrypoints` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.<segment_name>.frontend.passHostHeader` | Overrides `traefik.frontend.passHostHeader`. |
|
||||
| `traefik.<segment_name>.frontend.passTLSCert` | Overrides `traefik.frontend.passTLSCert`. |
|
||||
| `traefik.<segment_name>.frontend.priority` | Overrides `traefik.frontend.priority`. |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.<segment_name>.frontend.redirect.entryPoint=https` | Overrides `traefik.frontend.redirect.entryPoint`. |
|
||||
| `traefik.<segment_name>.frontend.redirect.regex=^http://localhost/(.*)` | Overrides `traefik.frontend.redirect.regex`. |
|
||||
| `traefik.<segment_name>.frontend.redirect.replacement=http://mydomain/$1` | Overrides `traefik.frontend.redirect.replacement`. |
|
||||
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Return 301 instead of 302. |
|
||||
| `traefik.<segment_name>.frontend.rule` | Overrides `traefik.frontend.rule`. |
|
||||
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Overrides `traefik.frontend.whiteList.sourceRange`. |
|
||||
| `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Overrides `traefik.frontend.whiteList.useXForwardedFor`. |
|
||||
| Label | Description |
|
||||
|---------------------------------------------------------------------------|-------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
|
||||
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
|
||||
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
|
||||
| `traefik.<segment_name>.protocol=http` | Same as `traefik.protocol` |
|
||||
| `traefik.<segment_name>.weight=10` | Same as `traefik.weight` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` |
|
||||
| `traefik.<segment_name>.frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | Same as `traefik.frontend.errors.<name>.backend` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | Same as `traefik.frontend.errors.<name>.query` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.status=RANGE` | Same as `traefik.frontend.errors.<name>.status` |
|
||||
| `traefik.<segment_name>.frontend.passHostHeader=true` | Same as `traefik.frontend.passHostHeader` |
|
||||
| `traefik.<segment_name>.frontend.passTLSCert=true` | Same as `traefik.frontend.passTLSCert` |
|
||||
| `traefik.<segment_name>.frontend.priority=10` | Same as `traefik.frontend.priority` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.extractorFunc=EXP` | Same as `traefik.frontend.rateLimit.extractorFunc` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.period=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.period` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.average=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.average` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.burst=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.burst` |
|
||||
| `traefik.<segment_name>.frontend.redirect.entryPoint=https` | Same as `traefik.frontend.redirect.entryPoint` |
|
||||
| `traefik.<segment_name>.frontend.redirect.regex=^http://localhost/(.*)` | Same as `traefik.frontend.redirect.regex` |
|
||||
| `traefik.<segment_name>.frontend.redirect.replacement=http://mydomain/$1` | Same as `traefik.frontend.redirect.replacement` |
|
||||
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Same as `traefik.frontend.redirect.permanent` |
|
||||
| `traefik.<segment_name>.frontend.rule=EXP` | Same as `traefik.frontend.rule` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Same as `traefik.frontend.whiteList.sourceRange` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Same as `traefik.frontend.whiteList.useXForwardedFor` |
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.frontend.headers.customRequestHeaders=EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container.<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.<segment_name>.frontend.headers.customResponseHeaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client.<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------------------|----------------------------------------------------------|
|
||||
| `traefik.<segment_name>.frontend.headers.customRequestHeaders=EXPR ` | Same as `traefik.frontend.headers.customRequestHeaders` |
|
||||
| `traefik.<segment_name>.frontend.headers.customResponseHeaders=EXPR` | Same as `traefik.frontend.headers.customResponseHeaders` |
|
||||
|
||||
#### Security Headers
|
||||
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.<br>Format: `Host1,Host2` |
|
||||
| `traefik.<segment_name>.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.<segment_name>.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
|
||||
| `traefik.<segment_name>.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
|
||||
| `traefik.<segment_name>.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
|
||||
| `traefik.<segment_name>.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
|
||||
| `traefik.<segment_name>.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `traefik.<segment_name>.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `traefik.<segment_name>.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `traefik.<segment_name>.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `traefik.<segment_name>.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `traefik.<segment_name>.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
|
||||
| `traefik.<segment_name>.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
|
||||
| `traefik.<segment_name>.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||
| `traefik.<segment_name>.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------------------------|--------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.frontend.headers.allowedHosts=EXPR` | Same as `traefik.frontend.headers.allowedHosts` |
|
||||
| `traefik.<segment_name>.frontend.headers.browserXSSFilter=true` | Same as `traefik.frontend.headers.browserXSSFilter` |
|
||||
| `traefik.<segment_name>.frontend.headers.contentSecurityPolicy=VALUE` | Same as `traefik.frontend.headers.contentSecurityPolicy` |
|
||||
| `traefik.<segment_name>.frontend.headers.contentTypeNosniff=true` | Same as `traefik.frontend.headers.contentTypeNosniff` |
|
||||
| `traefik.<segment_name>.frontend.headers.customBrowserXSSValue=VALUE` | Same as `traefik.frontend.headers.customBrowserXSSValue` |
|
||||
| `traefik.<segment_name>.frontend.headers.customFrameOptionsValue=VALUE` | Same as `traefik.frontend.headers.customFrameOptionsValue` |
|
||||
| `traefik.<segment_name>.frontend.headers.forceSTSHeader=false` | Same as `traefik.frontend.headers.forceSTSHeader` |
|
||||
| `traefik.<segment_name>.frontend.headers.frameDeny=false` | Same as `traefik.frontend.headers.frameDeny` |
|
||||
| `traefik.<segment_name>.frontend.headers.hostsProxyHeaders=EXPR` | Same as `traefik.frontend.headers.hostsProxyHeaders` |
|
||||
| `traefik.<segment_name>.frontend.headers.isDevelopment=false` | Same as `traefik.frontend.headers.isDevelopment` |
|
||||
| `traefik.<segment_name>.frontend.headers.publicKey=VALUE` | Same as `traefik.frontend.headers.publicKey` |
|
||||
| `traefik.<segment_name>.frontend.headers.referrerPolicy=VALUE` | Same as `traefik.frontend.headers.referrerPolicy` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLRedirect=true` | Same as `traefik.frontend.headers.SSLRedirect` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLTemporaryRedirect=true` | Same as `traefik.frontend.headers.SSLTemporaryRedirect` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLHost=HOST` | Same as `traefik.frontend.headers.SSLHost` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLProxyHeaders=EXPR` | Same as `traefik.frontend.headers.SSLProxyHeaders=EXPR` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSSeconds=315360000` | Same as `traefik.frontend.headers.STSSeconds=315360000` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSIncludeSubdomains=true` | Same as `traefik.frontend.headers.STSIncludeSubdomains=true` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSPreload=true` | Same as `traefik.frontend.headers.STSPreload=true` |
|
||||
|
||||
!!! note
|
||||
If a label is defined both as a `container label` and a `segment label` (for example `traefik.<segment_name>.port=PORT` and `traefik.port=PORT` ), the `segment label` is used to defined the `<segment_name>` property (`port` in the example).
|
||||
|
@@ -1,15 +1,15 @@
|
||||
# DynamoDB Backend
|
||||
# DynamoDB Provider
|
||||
|
||||
Træfik can be configured to use Amazon DynamoDB as a backend configuration.
|
||||
Træfik can be configured to use Amazon DynamoDB as a provider.
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# DynamoDB configuration backend
|
||||
# DynamoDB Provider
|
||||
################################################################
|
||||
|
||||
# Enable DynamoDB configuration backend.
|
||||
# Enable DynamoDB Provider.
|
||||
[dynamodb]
|
||||
|
||||
# Region to use when connecting to AWS.
|
||||
@@ -68,4 +68,3 @@ Items in the `dynamodb` table must have three attributes:
|
||||
See `types/types.go` for details.
|
||||
The presence or absence of this attribute determines its type.
|
||||
So an item should never have both a `frontend` and a `backend` attribute.
|
||||
|
||||
|
@@ -1,15 +1,15 @@
|
||||
# ECS Backend
|
||||
# ECS Provider
|
||||
|
||||
Træfik can be configured to use Amazon ECS as a backend configuration.
|
||||
Træfik can be configured to use Amazon ECS as a provider.
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# ECS configuration backend
|
||||
# ECS Provider
|
||||
################################################################
|
||||
|
||||
# Enable ECS configuration backend.
|
||||
# Enable ECS Provider.
|
||||
[ecs]
|
||||
|
||||
# ECS Cluster Name.
|
||||
@@ -33,6 +33,7 @@ clusters = ["default"]
|
||||
watch = true
|
||||
|
||||
# Default domain used.
|
||||
# Can be overridden by setting the "traefik.domain" label.
|
||||
#
|
||||
# Optional
|
||||
# Default: ""
|
||||
@@ -135,6 +136,7 @@ Labels can be used on task containers to override default behaviour:
|
||||
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.domain` | Default domain used for frontend rules. |
|
||||
| `traefik.enable=false` | Disable this container in Træfik |
|
||||
| `traefik.port=80` | Override the default `port` value. Overrides `NetworkBindings` from Docker Container |
|
||||
| `traefik.protocol=https` | Override the default `http` protocol |
|
||||
@@ -187,7 +189,17 @@ Labels can be used on task containers to override default behaviour:
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.<br>Format: `Host1,Host2` |
|
||||
| `traefik.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `traefik.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
|
||||
| `traefik.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `traefik.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `traefik.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `traefik.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
|
||||
| `traefik.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `traefik.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` |
|
||||
| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
|
||||
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||
| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
| `traefik.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
|
||||
| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
|
||||
| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
|
||||
@@ -195,13 +207,3 @@ Labels can be used on task containers to override default behaviour:
|
||||
| `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
|
||||
| `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
|
||||
| `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
|
||||
| `traefik.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
|
||||
| `traefik.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `traefik.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `traefik.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `traefik.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `traefik.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `traefik.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
|
||||
| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
|
||||
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||
| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
|
@@ -1,13 +1,13 @@
|
||||
# Etcd Backend
|
||||
# Etcd Provider
|
||||
|
||||
Træfik can be configured to use Etcd as a backend configuration.
|
||||
Træfik can be configured to use Etcd as a provider.
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Etcd configuration backend
|
||||
# Etcd Provider
|
||||
################################################################
|
||||
|
||||
# Enable Etcd configuration backend.
|
||||
# Enable Etcd Provider.
|
||||
[etcd]
|
||||
|
||||
# Etcd server endpoint.
|
||||
@@ -66,7 +66,7 @@ useAPIV3 = true
|
||||
# insecureSkipVerify = true
|
||||
```
|
||||
|
||||
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
|
||||
|
||||
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure.
|
||||
|
||||
|
@@ -1,13 +1,13 @@
|
||||
# Eureka Backend
|
||||
# Eureka Provider
|
||||
|
||||
Træfik can be configured to use Eureka as a backend configuration.
|
||||
Træfik can be configured to use Eureka as a provider.
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Eureka configuration backend
|
||||
# Eureka Provider
|
||||
################################################################
|
||||
|
||||
# Enable Eureka configuration backend.
|
||||
# Enable Eureka Provider.
|
||||
[eureka]
|
||||
|
||||
# Eureka server endpoint.
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# File Backends
|
||||
# File Provider
|
||||
|
||||
Træfik can be configured with a file.
|
||||
|
||||
@@ -140,19 +140,20 @@ Træfik can be configured with a file.
|
||||
# ...
|
||||
```
|
||||
|
||||
## Configuration mode
|
||||
## Configuration Mode
|
||||
|
||||
You have three choices:
|
||||
You have two choices:
|
||||
|
||||
- [Simple](/configuration/backends/file/#simple)
|
||||
- [Rules in a Separate File](/configuration/backends/file/#rules-in-a-separate-file)
|
||||
- [Multiple `.toml` Files](/configuration/backends/file/#multiple-toml-files)
|
||||
- [Rules in Træfik configuration file](/configuration/backends/file/#rules-in-trfik-configuration-file)
|
||||
- [Rules in dedicated files](/configuration/backends/file/#rules-in-dedicated-files)
|
||||
|
||||
To enable the file backend, you must either pass the `--file` option to the Træfik binary or put the `[file]` section (with or without inner settings) in the configuration file.
|
||||
|
||||
The configuration file allows managing both backends/frontends and HTTPS certificates (which are not [Let's Encrypt](https://letsencrypt.org) certificates generated through Træfik).
|
||||
|
||||
### Simple
|
||||
TOML templating can be used if rules are not defined in the Træfik configuration file.
|
||||
|
||||
### Rules in Træfik Configuration File
|
||||
|
||||
Add your configuration at the end of the global configuration file `traefik.toml`:
|
||||
|
||||
@@ -197,9 +198,16 @@ defaultEntryPoints = ["http", "https"]
|
||||
Adding certificates directly to the entryPoint is still maintained but certificates declared in this way cannot be managed dynamically.
|
||||
It's recommended to use the file provider to declare certificates.
|
||||
|
||||
### Rules in a Separate File
|
||||
!!! warning
|
||||
TOML templating cannot be used if rules are defined in the Træfik configuration file.
|
||||
|
||||
Put your rules in a separate file, for example `rules.toml`:
|
||||
### Rules in Dedicated Files
|
||||
|
||||
Træfik allows defining rules in one or more separate files.
|
||||
|
||||
#### One Separate File
|
||||
|
||||
You have to specify the file path in the `file.filename` option.
|
||||
|
||||
```toml
|
||||
# traefik.toml
|
||||
@@ -213,8 +221,31 @@ defaultEntryPoints = ["http", "https"]
|
||||
|
||||
[file]
|
||||
filename = "rules.toml"
|
||||
watch = true
|
||||
```
|
||||
|
||||
The option `file.watch` allows Træfik to watch file changes automatically.
|
||||
|
||||
#### Multiple Separated Files
|
||||
|
||||
You could have multiple `.toml` files in a directory (and recursively in its sub-directories):
|
||||
|
||||
```toml
|
||||
[file]
|
||||
directory = "/path/to/config/"
|
||||
watch = true
|
||||
```
|
||||
|
||||
The option `file.watch` allows Træfik to watch file changes automatically.
|
||||
|
||||
#### Separate Files Content
|
||||
|
||||
If you are defining rules in one or more separate files, you can use two formats.
|
||||
|
||||
##### Simple Format
|
||||
|
||||
Backends, Frontends and TLS certificates are defined one at time, as described in the file `rules.toml`:
|
||||
|
||||
```toml
|
||||
# rules.toml
|
||||
[backends]
|
||||
@@ -239,18 +270,34 @@ defaultEntryPoints = ["http", "https"]
|
||||
# ...
|
||||
```
|
||||
|
||||
### Multiple `.toml` Files
|
||||
##### TOML Templating
|
||||
|
||||
You could have multiple `.toml` files in a directory (and recursively in its sub-directories):
|
||||
!!! warning
|
||||
TOML templating can only be used **if rules are defined in one or more separate files**.
|
||||
Templating will not work in the Træfik configuration file.
|
||||
|
||||
Træfik allows using TOML templating.
|
||||
|
||||
Thus, it's possible to define easily lot of Backends, Frontends and TLS certificates as described in the file `template-rules.toml` :
|
||||
|
||||
```toml
|
||||
[file]
|
||||
directory = "/path/to/config/"
|
||||
```
|
||||
# template-rules.toml
|
||||
[backends]
|
||||
{{ range $i, $e := until 100 }}
|
||||
[backends.backend{{ $e }}]
|
||||
#...
|
||||
{{ end }}
|
||||
|
||||
If you want Træfik to watch file changes automatically, just add:
|
||||
[frontends]
|
||||
{{ range $i, $e := until 100 }}
|
||||
[frontends.frontend{{ $e }}]
|
||||
#...
|
||||
{{ end }}
|
||||
|
||||
```toml
|
||||
[file]
|
||||
watch = true
|
||||
|
||||
# HTTPS certificate
|
||||
{{ range $i, $e := until 100 }}
|
||||
[[tls]]
|
||||
#...
|
||||
{{ end }}
|
||||
```
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# Kubernetes Ingress Backend
|
||||
# Kubernetes Ingress Provider
|
||||
|
||||
Træfik can be configured to use Kubernetes Ingress as a backend configuration.
|
||||
Træfik can be configured to use Kubernetes Ingress as a provider.
|
||||
|
||||
See also [Kubernetes user guide](/user-guide/kubernetes).
|
||||
|
||||
@@ -8,10 +8,10 @@ See also [Kubernetes user guide](/user-guide/kubernetes).
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Kubernetes Ingress configuration backend
|
||||
# Kubernetes Ingress Provider
|
||||
################################################################
|
||||
|
||||
# Enable Kubernetes Ingress configuration backend.
|
||||
# Enable Kubernetes Ingress Provider.
|
||||
[kubernetes]
|
||||
|
||||
# Kubernetes server endpoint.
|
||||
@@ -112,7 +112,7 @@ Although traefik will connect directly to the endpoints (pods), it still checks
|
||||
If the service port defined in the ingress spec is 443, then the backend communication protocol is assumed to be TLS, and will connect via TLS automatically.
|
||||
|
||||
!!! note
|
||||
Please note that by enabling TLS communication between traefik and your pods, you will have to have trusted certificates that have the proper trust chain and IP subject name.
|
||||
Please note that by enabling TLS communication between traefik and your pods, you will have to have trusted certificates that have the proper trust chain and IP subject name.
|
||||
If this is not an option, you may need to skip TLS certificate verification.
|
||||
See the [insecureSkipVerify](/configuration/commons/#main-section) setting for more details.
|
||||
|
||||
@@ -137,7 +137,8 @@ The following general annotations are applicable on the Ingress object:
|
||||
| `traefik.ingress.kubernetes.io/redirect-replacement: http://mydomain/$1` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-regex`. |
|
||||
| `traefik.ingress.kubernetes.io/rewrite-target: /users` | Replaces each matched Ingress path with the specified one, and adds the old path to the `X-Replaced-Path` header. |
|
||||
| `traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip` | Override the default frontend rule type. Default: `PathPrefix`. |
|
||||
| `traefik.ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"` | A comma-separated list of IP ranges permitted for access. all source IPs are permitted if the list is empty or a single range is ill-formatted. |
|
||||
| `traefik.ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"` | A comma-separated list of IP ranges permitted for access. all source IPs are permitted if the list is empty or a single range is ill-formatted. Please note, you may have to set `service.spec.externalTrafficPolicy` to the value `Local` to preserve the source IP of the request for filtering. Please see [this link](https://kubernetes.io/docs/tutorials/services/source-ip/) for more information.|
|
||||
| `ingress.kubernetes.io/whitelist-x-forwarded-for: "true"` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
| `traefik.ingress.kubernetes.io/app-root: "/index.html"` | Redirects all requests for `/` to the defined path. (4) |
|
||||
|
||||
<1> `traefik.ingress.kubernetes.io/error-pages` example:
|
||||
@@ -218,28 +219,28 @@ The following security annotations are applicable on the Ingress object:
|
||||
| Annotation | Description |
|
||||
| ----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `ingress.kubernetes.io/allowed-hosts: EXPR` | Provides a list of allowed hosts that requests will be processed. Format: `Host1,Host2` |
|
||||
| `ingress.kubernetes.io/browser-xss-filter: "true"` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `ingress.kubernetes.io/content-security-policy: VALUE` | Adds CSP Header with the custom value. |
|
||||
| `ingress.kubernetes.io/content-type-nosniff: "true"` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `ingress.kubernetes.io/custom-browser-xss-value: VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `ingress.kubernetes.io/custom-frame-options-value: VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `ingress.kubernetes.io/force-hsts: "false"` | Adds the STS header to non-SSL requests. |
|
||||
| `ingress.kubernetes.io/frame-deny: "false"` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `ingress.kubernetes.io/hsts-max-age: "315360000"` | Sets the max-age of the HSTS header. |
|
||||
| `ingress.kubernetes.io/hsts-include-subdomains: "true"` | Adds the IncludeSubdomains section of the STS header. |
|
||||
| `ingress.kubernetes.io/hsts-preload: "true"` | Adds the preload flag to the HSTS header. |
|
||||
| `ingress.kubernetes.io/is-development: "false"` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
| `ingress.kubernetes.io/proxy-headers: EXPR` | Provides a list of headers that the proxied hostname may be stored. Format: `HEADER1,HEADER2` |
|
||||
| `ingress.kubernetes.io/public-key: VALUE` | Adds pinned HTST public key header. |
|
||||
| `ingress.kubernetes.io/referrer-policy: VALUE` | Adds referrer policy header. |
|
||||
| `ingress.kubernetes.io/ssl-redirect: "true"` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
|
||||
| `ingress.kubernetes.io/ssl-temporary-redirect: "true"` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
|
||||
| `ingress.kubernetes.io/ssl-host: HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
|
||||
| `ingress.kubernetes.io/ssl-proxy-headers: EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`). Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `ingress.kubernetes.io/hsts-max-age: "315360000"` | Sets the max-age of the HSTS header. |
|
||||
| `ingress.kubernetes.io/hsts-include-subdomains: "true"` | Adds the IncludeSubdomains section of the STS header. |
|
||||
| `ingress.kubernetes.io/hsts-preload: "true"` | Adds the preload flag to the HSTS header. |
|
||||
| `ingress.kubernetes.io/force-hsts: "false"` | Adds the STS header to non-SSL requests. |
|
||||
| `ingress.kubernetes.io/frame-deny: "false"` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `ingress.kubernetes.io/custom-frame-options-value: VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `ingress.kubernetes.io/content-type-nosniff: "true"` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `ingress.kubernetes.io/browser-xss-filter: "true"` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `ingress.kubernetes.io/custom-browser-xss-value: VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `ingress.kubernetes.io/content-security-policy: VALUE` | Adds CSP Header with the custom value. |
|
||||
| `ingress.kubernetes.io/public-key: VALUE` | Adds pinned HTST public key header. |
|
||||
| `ingress.kubernetes.io/referrer-policy: VALUE` | Adds referrer policy header. |
|
||||
| `ingress.kubernetes.io/is-development: "false"` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
|
||||
### Authentication
|
||||
|
||||
Is possible to add additional authentication annotations to the Ingress object.
|
||||
Additional authentication annotations can be added to the Ingress object.
|
||||
The source of the authentication is a Secret object that contains the credentials.
|
||||
|
||||
| Annotation | Description |
|
||||
@@ -253,3 +254,12 @@ The following limitations hold:
|
||||
|
||||
- The realm is not configurable; the only supported (and default) value is `traefik`.
|
||||
- The Secret must contain a single file only.
|
||||
|
||||
### TLS certificates management
|
||||
|
||||
TLS certificates can be managed in Secrets objects.
|
||||
More information are available in the [User Guide](/user-guide/kubernetes/#add-a-tls-certificate-to-the-ingress).
|
||||
|
||||
!!! note
|
||||
Only TLS certificates provided by users can be stored in Kubernetes Secrets.
|
||||
[Let's Encrypt](https://letsencrypt.org) certificates cannot be managed in Kubernets Secrets yet.
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# Marathon Backend
|
||||
# Marathon Provider
|
||||
|
||||
Træfik can be configured to use Marathon as a backend configuration.
|
||||
Træfik can be configured to use Marathon as a provider.
|
||||
|
||||
See also [Marathon user guide](/user-guide/marathon).
|
||||
|
||||
@@ -9,10 +9,10 @@ See also [Marathon user guide](/user-guide/marathon).
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Mesos/Marathon configuration backend
|
||||
# Mesos/Marathon Provider
|
||||
################################################################
|
||||
|
||||
# Enable Marathon configuration backend.
|
||||
# Enable Marathon Provider.
|
||||
[marathon]
|
||||
|
||||
# Marathon server endpoint.
|
||||
@@ -157,7 +157,7 @@ domain = "marathon.localhost"
|
||||
# respectReadinessChecks = true
|
||||
```
|
||||
|
||||
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
|
||||
|
||||
## Labels: overriding default behavior
|
||||
|
||||
@@ -171,6 +171,7 @@ The following labels can be defined on Marathon applications. They adjust the be
|
||||
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.domain` | Default domain used for frontend rules. |
|
||||
| `traefik.enable=false` | Disable this container in Træfik |
|
||||
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. |
|
||||
| `traefik.portIndex=1` | Register port by index in the application's ports array. Useful when the application exposes multiple ports. |
|
||||
@@ -225,7 +226,17 @@ The following labels can be defined on Marathon applications. They adjust the be
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.<br>Format: `Host1,Host2` |
|
||||
| `traefik.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `traefik.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
|
||||
| `traefik.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `traefik.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `traefik.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `traefik.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
|
||||
| `traefik.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `traefik.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` |
|
||||
| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
|
||||
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||
| `traefik.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
|
||||
| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
|
||||
| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
|
||||
@@ -233,16 +244,6 @@ The following labels can be defined on Marathon applications. They adjust the be
|
||||
| `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
|
||||
| `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
|
||||
| `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
|
||||
| `traefik.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
|
||||
| `traefik.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `traefik.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `traefik.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `traefik.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `traefik.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `traefik.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
|
||||
| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
|
||||
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||
| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
|
||||
### Applications with Multiple Ports (segment labels)
|
||||
|
||||
@@ -252,61 +253,61 @@ You can define as many segments as ports exposed in an application.
|
||||
|
||||
Segment labels override the default behavior.
|
||||
|
||||
| Label | Description |
|
||||
|---------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.portIndex=1` | Create a service binding with frontend/backend using this port index. Overrides `traefik.portIndex`. |
|
||||
| `traefik.<segment_name>.port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the service labels could be used. |
|
||||
| `traefik.<segment_name>.protocol=http` | Overrides `traefik.protocol`. |
|
||||
| `traefik.<segment_name>.weight=10` | Assign this service weight. Overrides `traefik.weight`. |
|
||||
| `traefik.<segment_name>.frontend.auth.basic=EXPR` | Sets a Basic Auth for that frontend |
|
||||
| `traefik.<segment_name>.frontend.backend=BACKEND` | Assign this service frontend to `BACKEND`. Default is to assign to the service backend. |
|
||||
| `traefik.<segment_name>.frontend.entryPoints=https` | Overrides `traefik.frontend.entrypoints` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.<segment_name>.frontend.passHostHeader=true` | Overrides `traefik.frontend.passHostHeader`. |
|
||||
| `traefik.<segment_name>.frontend.passTLSCert=true` | Overrides `traefik.frontend.passTLSCert`. |
|
||||
| `traefik.<segment_name>.frontend.priority=10` | Overrides `traefik.frontend.priority`. |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.<segment_name>.frontend.redirect.entryPoint=https` | Overrides `traefik.frontend.redirect.entryPoint`. |
|
||||
| `traefik.<segment_name>.frontend.redirect.regex=^http://localhost/(.*)` | Overrides `traefik.frontend.redirect.regex`. |
|
||||
| `traefik.<segment_name>.frontend.redirect.replacement=http://mydomain/$1` | Overrides `traefik.frontend.redirect.replacement`. |
|
||||
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Return 301 instead of 302. |
|
||||
| `traefik.<segment_name>.frontend.rule=EXP` | Overrides `traefik.frontend.rule`. Default: `{service_name}.{sub_domain}.{domain}` |
|
||||
| `traefik.<segment_name>.frontend.whitelistSourceRange=RANGE` | Overrides `traefik.frontend.whitelistSourceRange`. |
|
||||
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Overrides `traefik.frontend.whiteList.sourceRange`. |
|
||||
| `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
| Label | Description |
|
||||
|---------------------------------------------------------------------------|-------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
|
||||
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
|
||||
| `traefik.<segment_name>.portIndex=1` | Same as `traefik.portIndex` |
|
||||
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
|
||||
| `traefik.<segment_name>.protocol=http` | Same as `traefik.protocol` |
|
||||
| `traefik.<segment_name>.weight=10` | Same as `traefik.weight` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` |
|
||||
| `traefik.<segment_name>.frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | Same as `traefik.frontend.errors.<name>.backend` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | Same as `traefik.frontend.errors.<name>.query` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.status=RANGE` | Same as `traefik.frontend.errors.<name>.status` |
|
||||
| `traefik.<segment_name>.frontend.passHostHeader=true` | Same as `traefik.frontend.passHostHeader` |
|
||||
| `traefik.<segment_name>.frontend.passTLSCert=true` | Same as `traefik.frontend.passTLSCert` |
|
||||
| `traefik.<segment_name>.frontend.priority=10` | Same as `traefik.frontend.priority` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.extractorFunc=EXP` | Same as `traefik.frontend.rateLimit.extractorFunc` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.period=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.period` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.average=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.average` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.burst=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.burst` |
|
||||
| `traefik.<segment_name>.frontend.redirect.entryPoint=https` | Same as `traefik.frontend.redirect.entryPoint` |
|
||||
| `traefik.<segment_name>.frontend.redirect.regex=^http://localhost/(.*)` | Same as `traefik.frontend.redirect.regex` |
|
||||
| `traefik.<segment_name>.frontend.redirect.replacement=http://mydomain/$1` | Same as `traefik.frontend.redirect.replacement` |
|
||||
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Same as `traefik.frontend.redirect.permanent` |
|
||||
| `traefik.<segment_name>.frontend.rule=EXP` | Same as `traefik.frontend.rule` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Same as `traefik.frontend.whiteList.sourceRange` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Same as `traefik.frontend.whiteList.useXForwardedFor` |
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.frontend.headers.customRequestHeaders=EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container.<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.<segment_name>.frontend.headers.customResponseHeaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client.<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------------------|----------------------------------------------------------|
|
||||
| `traefik.<segment_name>.frontend.headers.customRequestHeaders=EXPR ` | Same as `traefik.frontend.headers.customRequestHeaders` |
|
||||
| `traefik.<segment_name>.frontend.headers.customResponseHeaders=EXPR` | Same as `traefik.frontend.headers.customResponseHeaders` |
|
||||
|
||||
#### Security Headers
|
||||
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.<br>Format: `Host1,Host2` |
|
||||
| `traefik.<segment_name>.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.<segment_name>.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
|
||||
| `traefik.<segment_name>.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
|
||||
| `traefik.<segment_name>.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
|
||||
| `traefik.<segment_name>.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
|
||||
| `traefik.<segment_name>.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `traefik.<segment_name>.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `traefik.<segment_name>.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `traefik.<segment_name>.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `traefik.<segment_name>.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `traefik.<segment_name>.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
|
||||
| `traefik.<segment_name>.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
|
||||
| `traefik.<segment_name>.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||
| `traefik.<segment_name>.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------------------------|--------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.frontend.headers.allowedHosts=EXPR` | Same as `traefik.frontend.headers.allowedHosts` |
|
||||
| `traefik.<segment_name>.frontend.headers.browserXSSFilter=true` | Same as `traefik.frontend.headers.browserXSSFilter` |
|
||||
| `traefik.<segment_name>.frontend.headers.contentSecurityPolicy=VALUE` | Same as `traefik.frontend.headers.contentSecurityPolicy` |
|
||||
| `traefik.<segment_name>.frontend.headers.contentTypeNosniff=true` | Same as `traefik.frontend.headers.contentTypeNosniff` |
|
||||
| `traefik.<segment_name>.frontend.headers.customBrowserXSSValue=VALUE` | Same as `traefik.frontend.headers.customBrowserXSSValue` |
|
||||
| `traefik.<segment_name>.frontend.headers.customFrameOptionsValue=VALUE` | Same as `traefik.frontend.headers.customFrameOptionsValue` |
|
||||
| `traefik.<segment_name>.frontend.headers.forceSTSHeader=false` | Same as `traefik.frontend.headers.forceSTSHeader` |
|
||||
| `traefik.<segment_name>.frontend.headers.frameDeny=false` | Same as `traefik.frontend.headers.frameDeny` |
|
||||
| `traefik.<segment_name>.frontend.headers.hostsProxyHeaders=EXPR` | Same as `traefik.frontend.headers.hostsProxyHeaders` |
|
||||
| `traefik.<segment_name>.frontend.headers.isDevelopment=false` | Same as `traefik.frontend.headers.isDevelopment` |
|
||||
| `traefik.<segment_name>.frontend.headers.publicKey=VALUE` | Same as `traefik.frontend.headers.publicKey` |
|
||||
| `traefik.<segment_name>.frontend.headers.referrerPolicy=VALUE` | Same as `traefik.frontend.headers.referrerPolicy` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLRedirect=true` | Same as `traefik.frontend.headers.SSLRedirect` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLTemporaryRedirect=true` | Same as `traefik.frontend.headers.SSLTemporaryRedirect` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLHost=HOST` | Same as `traefik.frontend.headers.SSLHost` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLProxyHeaders=EXPR` | Same as `traefik.frontend.headers.SSLProxyHeaders=EXPR` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSSeconds=315360000` | Same as `traefik.frontend.headers.STSSeconds=315360000` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSIncludeSubdomains=true` | Same as `traefik.frontend.headers.STSIncludeSubdomains=true` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSPreload=true` | Same as `traefik.frontend.headers.STSPreload=true` |
|
||||
|
@@ -1,13 +1,13 @@
|
||||
# Mesos Generic Backend
|
||||
# Mesos Generic Provider
|
||||
|
||||
Træfik can be configured to use Mesos as a backend configuration.
|
||||
Træfik can be configured to use Mesos as a provider.
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Mesos configuration backend
|
||||
# Mesos Provider
|
||||
################################################################
|
||||
|
||||
# Enable Mesos configuration backend.
|
||||
# Enable Mesos Provider.
|
||||
[mesos]
|
||||
|
||||
# Mesos server endpoint.
|
||||
@@ -108,6 +108,7 @@ The following labels can be defined on Mesos tasks. They adjust the behavior for
|
||||
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.domain` | Default domain used for frontend rules. |
|
||||
| `traefik.enable=false` | Disable this container in Træfik |
|
||||
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. |
|
||||
| `traefik.portIndex=1` | Register port by index in the application's ports array. Useful when the application exposes multiple ports. |
|
||||
@@ -160,7 +161,17 @@ The following labels can be defined on Mesos tasks. They adjust the behavior for
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.<br>Format: `Host1,Host2` |
|
||||
| `traefik.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `traefik.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
|
||||
| `traefik.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `traefik.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `traefik.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `traefik.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
|
||||
| `traefik.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `traefik.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` |
|
||||
| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
|
||||
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||
| `traefik.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
|
||||
| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
|
||||
| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
|
||||
@@ -168,13 +179,3 @@ The following labels can be defined on Mesos tasks. They adjust the behavior for
|
||||
| `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
|
||||
| `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
|
||||
| `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
|
||||
| `traefik.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
|
||||
| `traefik.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `traefik.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `traefik.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `traefik.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `traefik.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `traefik.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
|
||||
| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
|
||||
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||
| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
|
@@ -1,15 +1,15 @@
|
||||
# Rancher Backend
|
||||
# Rancher Provider
|
||||
|
||||
Træfik can be configured to use Rancher as a backend configuration.
|
||||
Træfik can be configured to use Rancher as a provider.
|
||||
|
||||
## Global Configuration
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Rancher configuration backend
|
||||
# Rancher Provider
|
||||
################################################################
|
||||
|
||||
# Enable Rancher configuration backend.
|
||||
# Enable Rancher Provider.
|
||||
[rancher]
|
||||
|
||||
# Default domain used.
|
||||
@@ -64,13 +64,13 @@ enableServiceHealthFilter = true
|
||||
# templateVersion = 2
|
||||
```
|
||||
|
||||
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
|
||||
|
||||
## Rancher Metadata Service
|
||||
|
||||
```toml
|
||||
# Enable Rancher metadata service configuration backend instead of the API
|
||||
# configuration backend.
|
||||
# Enable Rancher metadata service provider instead of the API
|
||||
# provider.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
@@ -97,7 +97,7 @@ prefix = "/2016-07-29"
|
||||
## Rancher API
|
||||
|
||||
```toml
|
||||
# Enable Rancher API configuration backend.
|
||||
# Enable Rancher API provider.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
@@ -140,6 +140,7 @@ Labels can be used on task containers to override default behavior:
|
||||
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.domain` | Default domain used for frontend rules. |
|
||||
| `traefik.enable=false` | Disable this container in Træfik |
|
||||
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. |
|
||||
| `traefik.protocol=https` | Override the default `http` protocol |
|
||||
@@ -192,7 +193,17 @@ Labels can be used on task containers to override default behavior:
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.<br>Format: `Host1,Host2` |
|
||||
| `traefik.frontend.headers.hostsProxyHeaders=EXPR` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` |
|
||||
| `traefik.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `traefik.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
|
||||
| `traefik.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `traefik.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `traefik.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `traefik.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
|
||||
| `traefik.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `traefik.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` |
|
||||
| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
|
||||
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||
| `traefik.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
|
||||
| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
|
||||
| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
|
||||
@@ -200,16 +211,6 @@ Labels can be used on task containers to override default behavior:
|
||||
| `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
|
||||
| `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
|
||||
| `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
|
||||
| `traefik.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
|
||||
| `traefik.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `traefik.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `traefik.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `traefik.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `traefik.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `traefik.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
|
||||
| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
|
||||
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||
| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
|
||||
### On containers with Multiple Ports (segment labels)
|
||||
|
||||
@@ -219,59 +220,60 @@ You can define as many segments as ports exposed in a container.
|
||||
|
||||
Segment labels override the default behavior.
|
||||
|
||||
| Label | Description |
|
||||
|---------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the segment labels could be used. |
|
||||
| `traefik.<segment_name>.protocol` | Overrides `traefik.protocol`. |
|
||||
| `traefik.<segment_name>.weight` | Assign this segment weight. Overrides `traefik.weight`. |
|
||||
| `traefik.<segment_name>.frontend.auth.basic` | Sets a Basic Auth for that frontend |
|
||||
| `traefik.<segment_name>.frontend.backend=BACKEND` | Assign this segment frontend to `BACKEND`. Default is to assign to the segment backend. |
|
||||
| `traefik.<segment_name>.frontend.entryPoints` | Overrides `traefik.frontend.entrypoints` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.<segment_name>.frontend.passHostHeader` | Overrides `traefik.frontend.passHostHeader`. |
|
||||
| `traefik.<segment_name>.frontend.passTLSCert` | Overrides `traefik.frontend.passTLSCert`. |
|
||||
| `traefik.<segment_name>.frontend.priority` | Overrides `traefik.frontend.priority`. |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.<segment_name>.frontend.redirect.entryPoint=https` | Overrides `traefik.frontend.redirect.entryPoint`. |
|
||||
| `traefik.<segment_name>.frontend.redirect.regex=^http://localhost/(.*)` | Overrides `traefik.frontend.redirect.regex`. |
|
||||
| `traefik.<segment_name>.frontend.redirect.replacement=http://mydomain/$1` | Overrides `traefik.frontend.redirect.replacement`. |
|
||||
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Return 301 instead of 302. |
|
||||
| `traefik.<segment_name>.frontend.rule` | Overrides `traefik.frontend.rule`. |
|
||||
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Overrides `traefik.frontend.whiteList.sourceRange`. |
|
||||
| `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Overrides `traefik.frontend.whiteList.useXForwardedFor`. |
|
||||
| Label | Description |
|
||||
|---------------------------------------------------------------------------|-------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
|
||||
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
|
||||
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
|
||||
| `traefik.<segment_name>.protocol=http` | Same as `traefik.protocol` |
|
||||
| `traefik.<segment_name>.weight=10` | Same as `traefik.weight` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` |
|
||||
| `traefik.<segment_name>.frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | Same as `traefik.frontend.errors.<name>.backend` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | Same as `traefik.frontend.errors.<name>.query` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.status=RANGE` | Same as `traefik.frontend.errors.<name>.status` |
|
||||
| `traefik.<segment_name>.frontend.passHostHeader=true` | Same as `traefik.frontend.passHostHeader` |
|
||||
| `traefik.<segment_name>.frontend.passTLSCert=true` | Same as `traefik.frontend.passTLSCert` |
|
||||
| `traefik.<segment_name>.frontend.priority=10` | Same as `traefik.frontend.priority` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.extractorFunc=EXP` | Same as `traefik.frontend.rateLimit.extractorFunc` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.period=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.period` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.average=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.average` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.burst=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.burst` |
|
||||
| `traefik.<segment_name>.frontend.redirect.entryPoint=https` | Same as `traefik.frontend.redirect.entryPoint` |
|
||||
| `traefik.<segment_name>.frontend.redirect.regex=^http://localhost/(.*)` | Same as `traefik.frontend.redirect.regex` |
|
||||
| `traefik.<segment_name>.frontend.redirect.replacement=http://mydomain/$1` | Same as `traefik.frontend.redirect.replacement` |
|
||||
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Same as `traefik.frontend.redirect.permanent` |
|
||||
| `traefik.<segment_name>.frontend.rule=EXP` | Same as `traefik.frontend.rule` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Same as `traefik.frontend.whiteList.sourceRange` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Same as `traefik.frontend.whiteList.useXForwardedFor` |
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------------------|-----------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.frontend.headers.customRequestHeaders=EXPR ` | overrides `traefik.frontend.headers.customRequestHeaders=EXPR ` |
|
||||
| `traefik.<segment_name>.frontend.headers.customResponseHeaders=EXPR` | overrides `traefik.frontend.headers.customResponseHeaders=EXPR` |
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------------------|------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.frontend.headers.customRequestHeaders=EXPR ` | overrides `traefik.frontend.headers.customRequestHeaders` |
|
||||
| `traefik.<segment_name>.frontend.headers.customResponseHeaders=EXPR` | overrides `traefik.frontend.headers.customResponseHeaders` |
|
||||
|
||||
#### Security Headers
|
||||
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------------------------|--------------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.frontend.headers.allowedHosts=EXPR` | overrides `traefik.frontend.headers.allowedHosts=EXPR` |
|
||||
| `traefik.<segment_name>.frontend.headers.hostsProxyHeaders=EXPR` | overrides `traefik.frontend.headers.hostsProxyHeaders=EXPR` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLRedirect=true` | overrides `traefik.frontend.headers.SSLRedirect=true` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLTemporaryRedirect=true` | overrides `traefik.frontend.headers.SSLTemporaryRedirect=true` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLHost=HOST` | overrides `traefik.frontend.headers.SSLHost=HOST` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLProxyHeaders=EXPR` | overrides `traefik.frontend.headers.SSLProxyHeaders=EXPR` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSSeconds=315360000` | overrides `traefik.frontend.headers.STSSeconds=315360000` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSIncludeSubdomains=true` | overrides `traefik.frontend.headers.STSIncludeSubdomains=true` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSPreload=true` | overrides `traefik.frontend.headers.STSPreload=true` |
|
||||
| `traefik.<segment_name>.frontend.headers.forceSTSHeader=false` | overrides `traefik.frontend.headers.forceSTSHeader=false` |
|
||||
| `traefik.<segment_name>.frontend.headers.frameDeny=false` | overrides `traefik.frontend.headers.frameDeny=false` |
|
||||
| `traefik.<segment_name>.frontend.headers.customFrameOptionsValue=VALUE` | overrides `traefik.frontend.headers.customFrameOptionsValue=VALUE` |
|
||||
| `traefik.<segment_name>.frontend.headers.contentTypeNosniff=true` | overrides `traefik.frontend.headers.contentTypeNosniff=true` |
|
||||
| `traefik.<segment_name>.frontend.headers.browserXSSFilter=true` | overrides `traefik.frontend.headers.browserXSSFilter=true` |
|
||||
| `traefik.<segment_name>.frontend.headers.customBrowserXSSValue=VALUE` | overrides `traefik.frontend.headers.customBrowserXSSValue=VALUE` |
|
||||
| `traefik.<segment_name>.frontend.headers.contentSecurityPolicy=VALUE` | overrides `traefik.frontend.headers.contentSecurityPolicy=VALUE` |
|
||||
| `traefik.<segment_name>.frontend.headers.publicKey=VALUE` | overrides `traefik.frontend.headers.publicKey=VALUE` |
|
||||
| `traefik.<segment_name>.frontend.headers.referrerPolicy=VALUE` | overrides `traefik.frontend.headers.referrerPolicy=VALUE` |
|
||||
| `traefik.<segment_name>.frontend.headers.isDevelopment=false` | overrides `traefik.frontend.headers.isDevelopment=false` |
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------------------------|--------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.frontend.headers.allowedHosts=EXPR` | overrides `traefik.frontend.headers.allowedHosts` |
|
||||
| `traefik.<segment_name>.frontend.headers.browserXSSFilter=true` | overrides `traefik.frontend.headers.browserXSSFilter` |
|
||||
| `traefik.<segment_name>.frontend.headers.contentSecurityPolicy=VALUE` | overrides `traefik.frontend.headers.contentSecurityPolicy` |
|
||||
| `traefik.<segment_name>.frontend.headers.contentTypeNosniff=true` | overrides `traefik.frontend.headers.contentTypeNosniff` |
|
||||
| `traefik.<segment_name>.frontend.headers.customBrowserXSSValue=VALUE` | overrides `traefik.frontend.headers.customBrowserXSSValue` |
|
||||
| `traefik.<segment_name>.frontend.headers.customFrameOptionsValue=VALUE` | overrides `traefik.frontend.headers.customFrameOptionsValue` |
|
||||
| `traefik.<segment_name>.frontend.headers.forceSTSHeader=false` | overrides `traefik.frontend.headers.forceSTSHeader` |
|
||||
| `traefik.<segment_name>.frontend.headers.frameDeny=false` | overrides `traefik.frontend.headers.frameDeny` |
|
||||
| `traefik.<segment_name>.frontend.headers.hostsProxyHeaders=EXPR` | overrides `traefik.frontend.headers.hostsProxyHeaders` |
|
||||
| `traefik.<segment_name>.frontend.headers.isDevelopment=false` | overrides `traefik.frontend.headers.isDevelopment` |
|
||||
| `traefik.<segment_name>.frontend.headers.publicKey=VALUE` | overrides `traefik.frontend.headers.publicKey` |
|
||||
| `traefik.<segment_name>.frontend.headers.referrerPolicy=VALUE` | overrides `traefik.frontend.headers.referrerPolicy` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLRedirect=true` | overrides `traefik.frontend.headers.SSLRedirect` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLTemporaryRedirect=true` | overrides `traefik.frontend.headers.SSLTemporaryRedirect` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLHost=HOST` | overrides `traefik.frontend.headers.SSLHost` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLProxyHeaders=EXPR` | overrides `traefik.frontend.headers.SSLProxyHeaders` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSSeconds=315360000` | overrides `traefik.frontend.headers.STSSeconds` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSIncludeSubdomains=true` | overrides `traefik.frontend.headers.STSIncludeSubdomains` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSPreload=true` | overrides `traefik.frontend.headers.STSPreload` |
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# Rest Backend
|
||||
# Rest Provider
|
||||
|
||||
Træfik can be configured:
|
||||
|
||||
@@ -7,7 +7,7 @@ Træfik can be configured:
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
# Enable rest backend.
|
||||
# Enable REST Provider.
|
||||
[rest]
|
||||
# Name of the related entry point
|
||||
#
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# Azure Service Fabric Backend
|
||||
# Azure Service Fabric Provider
|
||||
|
||||
Træfik can be configured to use Azure Service Fabric as a backend configuration.
|
||||
Træfik can be configured to use Azure Service Fabric as a provider.
|
||||
|
||||
See [this repository for an example deployment package and further documentation.](https://aka.ms/traefikonsf)
|
||||
|
||||
@@ -8,10 +8,10 @@ See [this repository for an example deployment package and further documentation
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Azure Service Fabric provider
|
||||
# Azure Service Fabric Provider
|
||||
################################################################
|
||||
|
||||
# Enable Azure Service Fabric configuration backend
|
||||
# Enable Azure Service Fabric Provider
|
||||
[serviceFabric]
|
||||
|
||||
# Azure Service Fabric Management Endpoint
|
||||
@@ -61,7 +61,7 @@ Here is an example of an extension setting Træfik labels:
|
||||
<Extension Name="Traefik">
|
||||
<Labels xmlns="http://schemas.microsoft.com/2015/03/fabact-no-schema">
|
||||
<Label Key="traefik.frontend.rule.example2">PathPrefixStrip: /a/path/to/strip</Label>
|
||||
<Label Key="traefik.expose">true</Label>
|
||||
<Label Key="traefik.enable">true</Label>
|
||||
<Label Key="traefik.frontend.passHostHeader">true</Label>
|
||||
</Labels>
|
||||
</Extension>
|
||||
@@ -98,8 +98,9 @@ Labels, set through extensions or the property manager, can be used on services
|
||||
|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.enable=false` | Disable this container in Træfik |
|
||||
| `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||
| `traefik.backend.group.name` | Group all services with the same name into a single backend in Træfik |
|
||||
| `traefik.backend.group.weight` | Set the weighting of the current services nodes in the backend group |
|
||||
| `traefik.servicefabric.groupname` | Group all services with the same name into a single backend in Træfik |
|
||||
| `traefik.servicefabric.groupweight` | Set the weighting of the current services nodes in the backend group |
|
||||
| `traefik.servicefabric.enablelabeloverrides` | Toggle whether labels can be overridden using the Service Fabric Property Manager API |
|
||||
| `traefik.backend.healthcheck.path=/health` | Enable health check for the backend, hitting the container at `path`. |
|
||||
| `traefik.backend.healthcheck.port=8080` | Allow to use a different port for the health check. |
|
||||
| `traefik.backend.healthcheck.interval=1s` | Define the health check interval. |
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# Web Backend
|
||||
# Web Provider
|
||||
|
||||
!!! danger "DEPRECATED"
|
||||
The web provider is deprecated, please use the [api](/configuration/api.md), the [ping](/configuration/ping.md), the [metrics](/configuration/metrics) and the [rest](/configuration/backends/rest.md) provider.
|
||||
@@ -12,7 +12,7 @@ Træfik can be configured:
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
# Enable web backend.
|
||||
# Enable Web Provider.
|
||||
[web]
|
||||
|
||||
# Web administration port.
|
||||
|
@@ -1,13 +1,13 @@
|
||||
# Zookeeper Backend
|
||||
# Zookeeper Provider
|
||||
|
||||
Træfik can be configured to use Zookeeper as a backend configuration.
|
||||
Træfik can be configured to use Zookeeper as a provider.
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Zookeeper configuration backend
|
||||
# Zookeeper Provider
|
||||
################################################################
|
||||
|
||||
# Enable Zookeeperconfiguration backend.
|
||||
# Enable Zookeeper Provider.
|
||||
[zookeeper]
|
||||
|
||||
# Zookeeper server endpoint.
|
||||
@@ -56,6 +56,6 @@ prefix = "traefik"
|
||||
# insecureSkipVerify = true
|
||||
```
|
||||
|
||||
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
|
||||
|
||||
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure.
|
||||
|
@@ -18,7 +18,7 @@
|
||||
|
||||
# Enable debug mode.
|
||||
# This will install HTTP handlers to expose Go expvars under /debug/vars and
|
||||
# pprof profiling data under /debug/pprof.
|
||||
# pprof profiling data under /debug/pprof/.
|
||||
# The log level will be set to DEBUG unless `logLevel` is specified.
|
||||
#
|
||||
# Optional
|
||||
@@ -33,7 +33,7 @@
|
||||
#
|
||||
# checkNewVersion = false
|
||||
|
||||
# Backends throttle duration.
|
||||
# Providers throttle duration.
|
||||
#
|
||||
# Optional
|
||||
# Default: "2s"
|
||||
@@ -85,7 +85,7 @@ Can be provided in a format supported by [time.ParseDuration](https://golang.org
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
**Note:** in this time frame no new requests are accepted.
|
||||
|
||||
- `providersThrottleDuration`: Backends throttle duration: minimum duration in seconds between 2 events from providers before applying a new configuration.
|
||||
- `providersThrottleDuration`: Providers throttle duration: minimum duration in seconds between 2 events from providers before applying a new configuration.
|
||||
It avoids unnecessary reloads if multiples events are sent in a short amount of time.
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
@@ -108,7 +108,7 @@ Each frontend can specify its own entrypoints.
|
||||
|
||||
In a micro-service architecture, with a central service discovery, setting constraints limits Træfik scope to a smaller number of routes.
|
||||
|
||||
Træfik filters services according to service attributes/tags set in your configuration backends.
|
||||
Træfik filters services according to service attributes/tags set in your providers.
|
||||
|
||||
Supported filters:
|
||||
|
||||
@@ -136,9 +136,9 @@ constraints = ["tag==us-*"]
|
||||
constraints = ["tag!=us-*", "tag!=asia-*"]
|
||||
```
|
||||
|
||||
### Backend-specific
|
||||
### provider-specific
|
||||
|
||||
Supported backends:
|
||||
Supported Providers:
|
||||
|
||||
- Docker
|
||||
- Consul K/V
|
||||
@@ -151,12 +151,12 @@ Supported backends:
|
||||
- Kubernetes (using a provider-specific mechanism based on label selectors)
|
||||
|
||||
```toml
|
||||
# Backend-specific constraint
|
||||
# Provider-specific constraint
|
||||
[consulCatalog]
|
||||
# ...
|
||||
constraints = ["tag==api"]
|
||||
|
||||
# Backend-specific constraint
|
||||
# Provider-specific constraint
|
||||
[marathon]
|
||||
# ...
|
||||
constraints = ["tag==api", "tag!=v*-beta"]
|
||||
@@ -421,12 +421,12 @@ idleTimeout = "360s"
|
||||
!!! warning
|
||||
For advanced users only.
|
||||
|
||||
Supported by all backends except: File backend, Web backend and DynamoDB backend.
|
||||
Supported by all providers except: File Provider, Web Provider and DynamoDB Provider.
|
||||
|
||||
```toml
|
||||
[backend_name]
|
||||
[provider_name]
|
||||
|
||||
# Override default configuration template. For advanced users :)
|
||||
# Override default provider configuration template. For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
# Default: ""
|
||||
|
@@ -106,7 +106,7 @@ traefik:
|
||||
```ini
|
||||
Name:foo
|
||||
Address::80
|
||||
TLS:goo,gii
|
||||
TLS:/my/path/foo.cert,/my/path/foo.key;/my/path/goo.cert,/my/path/goo.key;/my/path/hoo.cert,/my/path/hoo.key
|
||||
TLS
|
||||
CA:car
|
||||
CA.Optional:true
|
||||
@@ -118,7 +118,7 @@ Compress:true
|
||||
WhiteList.SourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16
|
||||
WhiteList.UseXForwardedFor:true
|
||||
ProxyProtocol.TrustedIPs:192.168.0.1
|
||||
ProxyProtocol.Insecure:tue
|
||||
ProxyProtocol.Insecure:true
|
||||
ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24
|
||||
Auth.Basic.Users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0
|
||||
Auth.Digest.Users:test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e
|
||||
|
@@ -80,7 +80,8 @@
|
||||
|
||||
# ...
|
||||
```
|
||||
### InfluxDB
|
||||
|
||||
## InfluxDB
|
||||
|
||||
```toml
|
||||
[metrics]
|
||||
@@ -105,22 +106,3 @@
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
## Statistics
|
||||
|
||||
```toml
|
||||
# Metrics definition
|
||||
[metrics]
|
||||
# ...
|
||||
|
||||
# Enable more detailed statistics.
|
||||
[metrics.statistics]
|
||||
|
||||
# Number of recent errors logged.
|
||||
#
|
||||
# Default: 10
|
||||
#
|
||||
recentErrors = 10
|
||||
|
||||
# ...
|
||||
```
|
||||
|
@@ -48,11 +48,14 @@ Træfik supports two backends: Jaeger and Zipkin.
|
||||
|
||||
# Local Agent Host Port instructs reporter to send spans to jaeger-agent at this address
|
||||
#
|
||||
# Default: "127.0.0.1:6832"
|
||||
# Default: "127.0.0.1:6831"
|
||||
#
|
||||
localAgentHostPort = "127.0.0.1:6832"
|
||||
localAgentHostPort = "127.0.0.1:6831"
|
||||
```
|
||||
|
||||
!!! warning
|
||||
Træfik is only able to send data over compact thrift protocol to the [Jaeger agent](https://www.jaegertracing.io/docs/deployment/#agent).
|
||||
|
||||
## Zipkin
|
||||
|
||||
```toml
|
||||
|
Before Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 208 KiB |
Before Width: | Height: | Size: 255 KiB After Width: | Height: | Size: 274 KiB |
@@ -7,25 +7,26 @@
|
||||
[](https://goreportcard.com/report/github.com/containous/traefik)
|
||||
[](https://github.com/containous/traefik/blob/master/LICENSE.md)
|
||||
[](https://traefik.herokuapp.com)
|
||||
[](https://twitter.com/intent/follow?screen_name=traefikproxy)
|
||||
[](https://twitter.com/intent/follow?screen_name=traefik)
|
||||
|
||||
|
||||
Træfik is a modern HTTP reverse proxy and load balancer that makes deploying microservices easy.
|
||||
Træfik integrates with your existing infrastructure components ([Docker](https://www.docker.com/), [Swarm mode](https://docs.docker.com/engine/swarm/), [Kubernetes](https://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Rancher](https://rancher.com), [Amazon ECS](https://aws.amazon.com/ecs), ...) and configures itself automatically and dynamically.
|
||||
Telling Træfik where your orchestrator is could be the _only_ configuration step you need to do.
|
||||
Pointing Træfik at your orchestrator should be the _only_ configuration step you need.
|
||||
|
||||
## Overview
|
||||
|
||||
Imagine that you have deployed a bunch of microservices with the help of an orchestrator (like Swarm or Kubernetes) or a service registry (like etcd or consul).
|
||||
Now you want users to access these microservices, and you need a reverse proxy.
|
||||
|
||||
Traditional reverse-proxies require that you configure _each_ route that will connect paths and subdomains to _each_ microservice. In an environment where you add, remove, kill, upgrade, or scale your services _many_ times a day, the task of keeping the routes up to date becomes tedious.
|
||||
Traditional reverse-proxies require that you configure _each_ route that will connect paths and subdomains to _each_ microservice.
|
||||
In an environment where you add, remove, kill, upgrade, or scale your services _many_ times a day, the task of keeping the routes up to date becomes tedious.
|
||||
|
||||
**This is when Træfik can help you!**
|
||||
|
||||
Træfik listens to your service registry/orchestrator API and instantly generates the routes so your microservices are connected to the outside world -- without further intervention from your part.
|
||||
Træfik listens to your service registry/orchestrator API and instantly generates the routes so your microservices are connected to the outside world -- without further intervention from your part.
|
||||
|
||||
**Run Træfik and let it do the work for you!**
|
||||
**Run Træfik and let it do the work for you!**
|
||||
_(But if you'd rather configure some of your routes manually, Træfik supports that too!)_
|
||||
|
||||

|
||||
@@ -41,12 +42,12 @@ _(But if you'd rather configure some of your routes manually, Træfik supports t
|
||||
- Websocket, HTTP/2, GRPC ready
|
||||
- Provides metrics (Rest, Prometheus, Datadog, Statsd, InfluxDB)
|
||||
- Keeps access logs (JSON, CLF)
|
||||
- [Fast](/benchmarks) ... which is nice
|
||||
- Fast
|
||||
- Exposes a Rest API
|
||||
- Packaged as a single binary file (made with :heart: with go) and available as a [tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) docker image
|
||||
|
||||
|
||||
## Supported backends
|
||||
## Supported Providers
|
||||
|
||||
- [Docker](/configuration/backends/docker/) / [Swarm mode](/configuration/backends/docker/#docker-swarm-mode)
|
||||
- [Kubernetes](/configuration/backends/kubernetes/)
|
||||
@@ -76,35 +77,39 @@ version: '3'
|
||||
|
||||
services:
|
||||
reverse-proxy:
|
||||
image: traefik #The official Traefik docker image
|
||||
command: --api --docker #Enables the web UI and tells Træfik to listen to docker
|
||||
image: traefik # The official Traefik docker image
|
||||
command: --api --docker # Enables the web UI and tells Træfik to listen to docker
|
||||
ports:
|
||||
- "80:80" #The HTTP port
|
||||
- "8080:8080" #The Web UI (enabled by --api)
|
||||
- "80:80" # The HTTP port
|
||||
- "8080:8080" # The Web UI (enabled by --api)
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock #So that Traefik can listen to the Docker events
|
||||
- /var/run/docker.sock:/var/run/docker.sock # So that Traefik can listen to the Docker events
|
||||
```
|
||||
|
||||
!!! warning
|
||||
Enabling the Web UI with the `--api` flag might exposes configuration elements. You can read more about this on the [API/Dashboard's Security section](/configuration/api#security).
|
||||
|
||||
|
||||
**That's it. Now you can launch Træfik!**
|
||||
|
||||
Start your `reverse-proxy` with the following command:
|
||||
|
||||
```shell
|
||||
docker-compose up -d reverse-proxy
|
||||
docker-compose up -d reverse-proxy
|
||||
```
|
||||
|
||||
You can open a browser and go to [http://localhost:8080](http://localhost:8080) to see Træfik's dashboard (we'll go back there once we have launched a service in step 2).
|
||||
|
||||
### 2 — Launch a Service — Træfik Detects It and Creates a Route for You
|
||||
### 2 — Launch a Service — Træfik Detects It and Creates a Route for You
|
||||
|
||||
Now that we have a Træfik instance up and running, we will deploy new services.
|
||||
Now that we have a Træfik instance up and running, we will deploy new services.
|
||||
|
||||
Edit your `docker-compose.yml` file and add the following at the end of your file.
|
||||
Edit your `docker-compose.yml` file and add the following at the end of your file.
|
||||
|
||||
```yaml
|
||||
# ...
|
||||
# ...
|
||||
whoami:
|
||||
image: emilevauge/whoami #A container that exposes an API to show it's IP address
|
||||
image: emilevauge/whoami # A container that exposes an API to show its IP address
|
||||
labels:
|
||||
- "traefik.frontend.rule=Host:whoami.docker.localhost"
|
||||
```
|
||||
@@ -112,7 +117,7 @@ Edit your `docker-compose.yml` file and add the following at the end of your fil
|
||||
The above defines `whoami`: a simple web service that outputs information about the machine it is deployed on (its IP address, host, and so on).
|
||||
|
||||
Start the `whoami` service with the following command:
|
||||
|
||||
|
||||
```shell
|
||||
docker-compose up -d whoami
|
||||
```
|
||||
@@ -135,9 +140,9 @@ IP: 172.27.0.3
|
||||
### 3 — Launch More Instances — Traefik Load Balances Them
|
||||
|
||||
Run more instances of your `whoami` service with the following command:
|
||||
|
||||
|
||||
```shell
|
||||
docker-compose up -d --scale whoami=2
|
||||
docker-compose up -d --scale whoami=2
|
||||
```
|
||||
|
||||
Go back to your browser ([http://localhost:8080](http://localhost:8080)) and see that Træfik has automatically detected the new instance of the container.
|
||||
@@ -164,9 +169,10 @@ IP: 172.27.0.4
|
||||
|
||||
### 4 — Enjoy Træfik's Magic
|
||||
|
||||
Now that you have a basic understanding of how Træfik can automatically create the routes to your services and load balance them, it might be time to dive into [the documentation](https://docs.traefik.io/) and let Træfik work for you! Whatever your infrastructure is, there is probably [an available Træfik backend](https://docs.traefik.io/configuration/backends/available) that will do the job.
|
||||
Now that you have a basic understanding of how Træfik can automatically create the routes to your services and load balance them, it might be time to dive into [the documentation](/) and let Træfik work for you!
|
||||
Whatever your infrastructure is, there is probably [an available Træfik provider](/#supported-providers) that will do the job.
|
||||
|
||||
Our recommendation would be to see for yourself how simple it is to enable HTTPS with [Træfik's let's encrypt integration](https://docs.traefik.io/user-guide/examples/#lets-encrypt-support) using the dedicated [user guide](https://docs.traefik.io/user-guide/docker-and-lets-encrypt/).
|
||||
Our recommendation would be to see for yourself how simple it is to enable HTTPS with [Træfik's let's encrypt integration](/user-guide/examples/#lets-encrypt-support) using the dedicated [user guide](/user-guide/docker-and-lets-encrypt/).
|
||||
|
||||
## Resources
|
||||
|
||||
@@ -196,4 +202,20 @@ Using the tiny Docker image:
|
||||
|
||||
```shell
|
||||
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.toml traefik
|
||||
```
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
### Security Advisories
|
||||
|
||||
We strongly advise you to join our mailing list to be aware of the latest announcements from our security team. You can subscribe sending a mail to security+subscribe@traefik.io or on [the online viewer](https://groups.google.com/a/traefik.io/forum/#!forum/security).
|
||||
|
||||
### CVE
|
||||
|
||||
Reported vulnerabilities can be found on
|
||||
[cve.mitre.org](https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=traefik).
|
||||
|
||||
### Report a Vulnerability
|
||||
|
||||
We want to keep Træfik safe for everyone.
|
||||
If you've discovered a security vulnerability in Træfik, we appreciate your help in disclosing it to us in a responsible manner, using [this form](https://security.traefik.io).
|
@@ -9,9 +9,9 @@ If you want to use Let's Encrypt with Træfik, sharing configuration or TLS cert
|
||||
Ok, could we mount a shared volume used by all my instances? Yes, you can, but it will not work.
|
||||
When you use Let's Encrypt, you need to store certificates, but not only.
|
||||
When Træfik generates a new certificate, it configures a challenge and once Let's Encrypt will verify the ownership of the domain, it will ping back the challenge.
|
||||
If the challenge is not knowing by other Træfik instances, the validation will fail.
|
||||
If the challenge is not known by other Træfik instances, the validation will fail.
|
||||
|
||||
For more information about challenge: [Automatic Certificate Management Environment (ACME)](https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#http-challenge)
|
||||
For more information about the challenge: [Automatic Certificate Management Environment (ACME)](https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#http-challenge)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
|
@@ -8,7 +8,7 @@ In addition, we want to use Let's Encrypt to automatically generate and renew SS
|
||||
|
||||
## Setting Up
|
||||
|
||||
In order for this to work, you'll need a server with a public IP address, with Docker installed on it.
|
||||
In order for this to work, you'll need a server with a public IP address, with Docker and docker-compose installed on it.
|
||||
|
||||
In this example, we're using the fictitious domain _my-awesome-app.org_.
|
||||
|
||||
@@ -113,7 +113,7 @@ This is the minimum configuration required to do the following:
|
||||
- Log `ERROR`-level messages (or more severe) to the console, but silence `DEBUG`-level messages
|
||||
- Check for new versions of Træfik periodically
|
||||
- Create two entry points, namely an `HTTP` endpoint on port `80`, and an `HTTPS` endpoint on port `443` where all incoming traffic on port `80` will immediately get redirected to `HTTPS`.
|
||||
- Enable the Docker configuration backend and listen for container events on the Docker unix socket we've mounted earlier. However, **new containers will not be exposed by Træfik by default, we'll get into this in a bit!**
|
||||
- Enable the Docker provider and listen for container events on the Docker unix socket we've mounted earlier. However, **new containers will not be exposed by Træfik by default, we'll get into this in a bit!**
|
||||
- Enable automatic request and configuration of SSL certificates using Let's Encrypt.
|
||||
These certificates will be stored in the `acme.json` file, which you can back-up yourself and store off-premises.
|
||||
|
||||
@@ -123,7 +123,7 @@ Alright, let's boot the container. From the `/opt/traefik` directory, run `docke
|
||||
|
||||
Now that we've fully configured and started Træfik, it's time to get our applications running!
|
||||
|
||||
Let's take a simple example of a micro-service project consisting of various services, where some will be exposed to the outside world and some will not.
|
||||
Let's take a simple example of a micro-service project consisting of various services, where some will be exposed to the outside world and some will not.
|
||||
|
||||
The `docker-compose.yml` of our project looks like this:
|
||||
|
||||
@@ -145,12 +145,11 @@ services:
|
||||
expose:
|
||||
- "9000"
|
||||
labels:
|
||||
- "traefik.backend=my-awesome-app-app"
|
||||
- "traefik.docker.network=web"
|
||||
- "traefik.frontend.rule=Host:app.my-awesome-app.org"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.port=9000"
|
||||
- "traefik.default.protocol=http"
|
||||
- "traefik.basic.frontend.rule=Host:app.my-awesome-app.org"
|
||||
- "traefik.basic.port=9000"
|
||||
- "traefik.basic.protocol=http"
|
||||
- "traefik.admin.frontend.rule=Host:admin-app.my-awesome-app.org"
|
||||
- "traefik.admin.protocol=https"
|
||||
- "traefik.admin.port=9443"
|
||||
@@ -204,12 +203,11 @@ Thanks to Docker labels, we can tell Træfik how to create its internal routing
|
||||
Let's take a look at the labels themselves for the `app` service, which is a HTTP webservice listing on port 9000:
|
||||
|
||||
```yaml
|
||||
- "traefik.backend=my-awesome-app-app"
|
||||
- "traefik.docker.network=web"
|
||||
- "traefik.frontend.rule=Host:app.my-awesome-app.org"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.port=9000"
|
||||
- "traefik.default.protocol=http"
|
||||
- "traefik.basic.frontend.rule=Host:app.my-awesome-app.org"
|
||||
- "traefik.basic.port=9000"
|
||||
- "traefik.basic.protocol=http"
|
||||
- "traefik.admin.frontend.rule=Host:admin-app.my-awesome-app.org"
|
||||
- "traefik.admin.protocol=https"
|
||||
- "traefik.admin.port=9443"
|
||||
@@ -221,11 +219,11 @@ We use both `container labels` and `service labels`.
|
||||
|
||||
First, we specify the `backend` name which corresponds to the actual service we're routing **to**.
|
||||
|
||||
We also tell Træfik to use the `web` network to route HTTP traffic to this container.
|
||||
We also tell Træfik to use the `web` network to route HTTP traffic to this container.
|
||||
With the `traefik.enable` label, we tell Træfik to include this container in its internal configuration.
|
||||
|
||||
With the `frontend.rule` label, we tell Træfik that we want to route to this container if the incoming HTTP request contains the `Host` `app.my-awesome-app.org`.
|
||||
Essentially, this is the actual rule used for Layer-7 load balancing.
|
||||
Essentially, this is the actual rule used for Layer-7 load balancing.
|
||||
|
||||
Finally but not unimportantly, we tell Træfik to route **to** port `9000`, since that is the actual TCP/IP port the container actually listens on.
|
||||
|
||||
@@ -236,11 +234,11 @@ Finally but not unimportantly, we tell Træfik to route **to** port `9000`, sinc
|
||||
When both `container labels` and `service labels` are defined, `container labels` are just used as default values for missing `service labels` but no frontend/backend are going to be defined only with these labels.
|
||||
Obviously, labels `traefik.frontend.rule` and `traefik.port` described above, will only be used to complete information set in `service labels` during the container frontends/bakends creation.
|
||||
|
||||
In the example, two service names are defined : `default` and `admin`.
|
||||
In the example, two service names are defined : `basic` and `admin`.
|
||||
They allow creating two frontends and two backends.
|
||||
|
||||
- `default` has only one `service label` : `traefik.default.protocol`.
|
||||
Træfik will use values set in `traefik.frontend.rule` and `traefik.port` to create the `default` frontend and backend.
|
||||
- `basic` has only one `service label` : `traefik.basic.protocol`.
|
||||
Træfik will use values set in `traefik.frontend.rule` and `traefik.port` to create the `basic` frontend and backend.
|
||||
The frontend listens to incoming HTTP requests which contain the `Host` `app.my-awesome-app.org` and redirect them in `HTTP` to the port `9000` of the backend.
|
||||
- `admin` has all the `services labels` needed to create the `admin` frontend and backend (`traefik.admin.frontend.rule`, `traefik.admin.protocol`, `traefik.admin.port`).
|
||||
Træfik will create a frontend to listen to incoming HTTP requests which contain the `Host` `admin-app.my-awesome-app.org` and redirect them in `HTTPS` to the port `9443` of the backend.
|
||||
|
@@ -68,7 +68,7 @@ defaultEntryPoints = ["http", "https"]
|
||||
[acme]
|
||||
email = "test@traefik.io"
|
||||
storage = "acme.json"
|
||||
caServer = "http://172.18.0.1:4000/directory"
|
||||
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
entryPoint = "https"
|
||||
[acme.httpChallenge]
|
||||
entryPoint = "http"
|
||||
@@ -103,7 +103,7 @@ Træfik generates these certificates when it starts and it needs to be restart i
|
||||
email = "test@traefik.io"
|
||||
storage = "acme.json"
|
||||
onHostRule = true
|
||||
caServer = "http://172.18.0.1:4000/directory"
|
||||
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
entryPoint = "https"
|
||||
[acme.httpChallenge]
|
||||
entryPoint = "http"
|
||||
@@ -140,7 +140,7 @@ If a backend is added with a `onHost` rule, Træfik will automatically generate
|
||||
email = "test@traefik.io"
|
||||
storage = "acme.json"
|
||||
onDemand = true
|
||||
caServer = "http://172.18.0.1:4000/directory"
|
||||
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
entryPoint = "https"
|
||||
[acme.httpChallenge]
|
||||
entryPoint = "http"
|
||||
@@ -167,7 +167,7 @@ This configuration allows generating a Let's Encrypt certificate (thanks to `HTT
|
||||
[acme]
|
||||
email = "test@traefik.io"
|
||||
storage = "acme.json"
|
||||
caServer = "http://172.18.0.1:4000/directory"
|
||||
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
entryPoint = "https"
|
||||
[acme.dnsChallenge]
|
||||
provider = "digitalocean" # DNS Provider name (cloudflare, OVH, gandi...)
|
||||
|
@@ -81,9 +81,11 @@ For namespaced restrictions, one RoleBinding is required per watched namespace a
|
||||
It is possible to use Træfik with a [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) or a [DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/) object,
|
||||
whereas both options have their own pros and cons:
|
||||
|
||||
- The scalability is much better when using a Deployment, because you will have a Single-Pod-per-Node model when using the DaemonSet.
|
||||
- It is possible to exclusively run a Service on a dedicated set of machines using taints and tolerations with a DaemonSet.
|
||||
- On the other hand the DaemonSet allows you to access any Node directly on Port 80 and 443, where you have to setup a [Service](https://kubernetes.io/docs/concepts/services-networking/service/) object with a Deployment.
|
||||
- The scalability can be much better when using a Deployment, because you will have a Single-Pod-per-Node model when using a DaemonSet, whereas you may need less replicas based on your environment when using a Deployment.
|
||||
- DaemonSets automatically scale to new nodes, when the nodes join the cluster, whereas Deployment pods are only scheduled on new nodes if required.
|
||||
- DaemonSets ensure that only one replica of pods run on any single node. Deployments require affinity settings if you want to ensure that two pods don't end up on the same node.
|
||||
- DaemonSets can be run with the `NET_BIND_SERVICE` capability, which will allow it to bind to port 80/443/etc on each host. This will allow bypassing the kube-proxy, and reduce traffic hops. Note that this is against the Kubernetes Best Practices [Guidelines](https://kubernetes.io/docs/concepts/configuration/overview/#services), and raises the potential for scheduling/scaling issues. Despite potential issues, this remains the choice for most ingress controllers.
|
||||
- If you are unsure which to choose, start with the Daemonset.
|
||||
|
||||
The Deployment objects looks like this:
|
||||
|
||||
@@ -118,6 +120,11 @@ spec:
|
||||
containers:
|
||||
- image: traefik
|
||||
name: traefik-ingress-lb
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
- name: admin
|
||||
containerPort: 8080
|
||||
args:
|
||||
- --api
|
||||
- --kubernetes
|
||||
@@ -172,7 +179,6 @@ spec:
|
||||
spec:
|
||||
serviceAccountName: traefik-ingress-controller
|
||||
terminationGracePeriodSeconds: 60
|
||||
hostNetwork: true
|
||||
containers:
|
||||
- image: traefik
|
||||
name: traefik-ingress-lb
|
||||
@@ -208,11 +214,13 @@ spec:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
name: admin
|
||||
type: NodePort
|
||||
```
|
||||
|
||||
[examples/k8s/traefik-ds.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/traefik-ds.yaml)
|
||||
|
||||
!!! note
|
||||
This will create a Daemonset that uses privileged ports 80/8080 on the host. This may not work on all providers, but illustrates the static (non-NodePort) hostPort binding. The `traefik-ingress-service` can still be used inside the cluster to access the DaemonSet pods.
|
||||
|
||||
To deploy Træfik to your cluster start by submitting one of the YAML files to the cluster with `kubectl`:
|
||||
|
||||
```shell
|
||||
@@ -293,7 +301,21 @@ Install the Træfik chart by:
|
||||
```shell
|
||||
helm install stable/traefik
|
||||
```
|
||||
Install the Træfik chart using a values.yaml file.
|
||||
|
||||
```shell
|
||||
helm install --values values.yaml stable/traefik
|
||||
```
|
||||
|
||||
```yaml
|
||||
dashboard:
|
||||
enabled: true
|
||||
domain: traefik-ui.minikube
|
||||
kubernetes:
|
||||
namespaces:
|
||||
- default
|
||||
- kube-system
|
||||
```
|
||||
For more information, check out [the documentation](https://github.com/kubernetes/charts/tree/master/stable/traefik).
|
||||
|
||||
## Submitting an Ingress to the Cluster
|
||||
@@ -350,7 +372,8 @@ We should now be able to visit [traefik-ui.minikube](http://traefik-ui.minikube)
|
||||
### Add a TLS Certificate to the Ingress
|
||||
|
||||
!!! note
|
||||
For this example to work you need a TLS entrypoint. You don't have to provide a TLS certificate at this point. For more details see [here](/configuration/entrypoints/).
|
||||
For this example to work you need a TLS entrypoint. You don't have to provide a TLS certificate at this point.
|
||||
For more details see [here](/configuration/entrypoints/).
|
||||
|
||||
To setup an HTTPS-protected ingress, you can leverage the TLS feature of the ingress resource.
|
||||
|
||||
@@ -374,7 +397,8 @@ spec:
|
||||
- secretName: traefik-ui-tls-cert
|
||||
```
|
||||
|
||||
In addition to the modified ingress you need to provide the TLS certificate via a Kubernetes secret in the same namespace as the ingress. The following two commands will generate a new certificate and create a secret containing the key and cert files.
|
||||
In addition to the modified ingress you need to provide the TLS certificate via a Kubernetes secret in the same namespace as the ingress.
|
||||
The following two commands will generate a new certificate and create a secret containing the key and cert files.
|
||||
|
||||
```shell
|
||||
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=traefik-ui.minikube"
|
||||
@@ -384,13 +408,16 @@ kubectl -n kube-system create secret tls traefik-ui-tls-cert --key=tls.key --cer
|
||||
If there are any errors while loading the TLS section of an ingress, the whole ingress will be skipped.
|
||||
|
||||
!!! note
|
||||
The secret must have two entries named `tls.key`and `tls.crt`. See the [Kubernetes documentation](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) for more details.
|
||||
The secret must have two entries named `tls.key`and `tls.crt`.
|
||||
See the [Kubernetes documentation](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) for more details.
|
||||
|
||||
!!! note
|
||||
The TLS certificates will be added to all entrypoints defined by the ingress annotation `traefik.frontend.entryPoints`. If no such annotation is provided, the TLS certificates will be added to all TLS-enabled `defaultEntryPoints`.
|
||||
The TLS certificates will be added to all entrypoints defined by the ingress annotation `traefik.frontend.entryPoints`.
|
||||
If no such annotation is provided, the TLS certificates will be added to all TLS-enabled `defaultEntryPoints`.
|
||||
|
||||
!!! note
|
||||
The field `hosts` in the TLS configuration is ignored. Instead, the domains provided by the certificate are used for this purpose. It is recommended to not use wildcard certificates as they will match globally.
|
||||
The field `hosts` in the TLS configuration is ignored. Instead, the domains provided by the certificate are used for this purpose.
|
||||
It is recommended to not use wildcard certificates as they will match globally.
|
||||
|
||||
## Basic Authentication
|
||||
|
||||
@@ -831,13 +858,21 @@ Sometimes Træfik runs along other Ingress controller implementations. One such
|
||||
|
||||
The `kubernetes.io/ingress.class` annotation can be attached to any Ingress object in order to control whether Træfik should handle it.
|
||||
|
||||
If the annotation is missing, contains an empty value, or the value `traefik`, then the Træfik controller will take responsibility and process the associated Ingress object. If the annotation contains any other value (usually the name of a different Ingress controller), Træfik will ignore the object.
|
||||
If the annotation is missing, contains an empty value, or the value `traefik`, then the Træfik controller will take responsibility and process the associated Ingress object.
|
||||
If the annotation contains any other value (usually the name of a different Ingress controller), Træfik will ignore the object.
|
||||
|
||||
It is also possible to set the `ingressClass` option in Træfik to a particular value.
|
||||
If that's the case and the value contains a `traefik` prefix, then only those Ingress objects matching the same value will be processed.
|
||||
For instance, setting the option to `traefik-internal` causes Træfik to process Ingress objects with the same `kubernetes.io/ingress.class` annotation value, ignoring all other objects (including those with a `traefik` value, empty value, and missing annotation).
|
||||
|
||||
### Between multiple Træfik Deployments
|
||||
|
||||
Sometimes multiple Træfik Deployments are supposed to run concurrently. For instance, it is conceivable to have one Deployment deal with internal and another one with external traffic.
|
||||
Sometimes multiple Træfik Deployments are supposed to run concurrently.
|
||||
For instance, it is conceivable to have one Deployment deal with internal and another one with external traffic.
|
||||
|
||||
For such cases, it is advisable to classify Ingress objects through a label and configure the `labelSelector` option per each Træfik Deployment accordingly. To stick with the internal/external example above, all Ingress objects meant for internal traffic could receive a `traffic-type: internal` label while objects designated for external traffic receive a `traffic-type: external` label. The label selectors on the Træfik Deployments would then be `traffic-type=internal` and `traffic-type=external`, respectively.
|
||||
For such cases, it is advisable to classify Ingress objects through a label and configure the `labelSelector` option per each Træfik Deployment accordingly.
|
||||
To stick with the internal/external example above, all Ingress objects meant for internal traffic could receive a `traffic-type: internal` label while objects designated for external traffic receive a `traffic-type: external` label.
|
||||
The label selectors on the Træfik Deployments would then be `traffic-type=internal` and `traffic-type=external`, respectively.
|
||||
|
||||
## Production advice
|
||||
|
||||
|
@@ -76,7 +76,7 @@ defaultEntryPoints = ["http", "https"]
|
||||
address = ":80"
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
|
||||
|
||||
[entryPoints.https.tls]
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
certFile = "integration/fixtures/https/snitest.com.cert"
|
||||
@@ -85,9 +85,9 @@ defaultEntryPoints = ["http", "https"]
|
||||
certFile = """-----BEGIN CERTIFICATE-----
|
||||
<cert file content>
|
||||
-----END CERTIFICATE-----"""
|
||||
keyFile = """-----BEGIN CERTIFICATE-----
|
||||
keyFile = """-----BEGIN PRIVATE KEY-----
|
||||
<key file content>
|
||||
-----END CERTIFICATE-----"""
|
||||
-----END PRIVATE KEY-----"""
|
||||
[entryPoints.other-https]
|
||||
address = ":4443"
|
||||
[entryPoints.other-https.tls]
|
||||
@@ -164,7 +164,7 @@ If a Consul ACL is used to restrict Træfik read/write access, one of the follow
|
||||
key "traefik" {
|
||||
policy = "write"
|
||||
},
|
||||
|
||||
|
||||
session "" {
|
||||
policy = "write"
|
||||
}
|
||||
@@ -266,6 +266,10 @@ Here is the toml configuration we would like to store in the store :
|
||||
backend = "backend1"
|
||||
passHostHeader = true
|
||||
priority = 10
|
||||
basicAuth = [
|
||||
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
||||
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
]
|
||||
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||
[frontends.frontend2.routes.test_1]
|
||||
rule = "Host:{subdomain:[a-z]+}.localhost"
|
||||
@@ -325,13 +329,15 @@ And there, the same dynamic configuration in a KV Store (using `prefix = "traefi
|
||||
|
||||
- frontend 2
|
||||
|
||||
| Key | Value |
|
||||
|----------------------------------------------------|--------------------|
|
||||
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
||||
| `/traefik/frontends/frontend2/passhostheader` | `true` |
|
||||
| `/traefik/frontends/frontend2/priority` | `10` |
|
||||
| `/traefik/frontends/frontend2/entrypoints` | `http,https` |
|
||||
| `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` |
|
||||
| Key | Value |
|
||||
|----------------------------------------------------|-----------------------------------------------|
|
||||
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
||||
| `/traefik/frontends/frontend2/passhostheader` | `true` |
|
||||
| `/traefik/frontends/frontend2/priority` | `10` |
|
||||
| `/traefik/frontends/frontend2/basicauth/0` | `test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/` |
|
||||
| `/traefik/frontends/frontend2/basicauth/1` | `test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0` |
|
||||
| `/traefik/frontends/frontend2/entrypoints` | `http,https` |
|
||||
| `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` |
|
||||
|
||||
- certificate 1
|
||||
|
||||
@@ -349,7 +355,7 @@ And there, the same dynamic configuration in a KV Store (using `prefix = "traefi
|
||||
|---------------------------------------|-----------------------|
|
||||
| `/traefik/tls/2/entrypoints` | `https,other-https` |
|
||||
| `/traefik/tls/2/certificate/certfile` | `<cert file content>` |
|
||||
| `/traefik/tls/2/certificate/certfile` | `<key file content>` |
|
||||
| `/traefik/tls/2/certificate/keyfile` | `<key file content>` |
|
||||
|
||||
### Atomic configuration changes
|
||||
|
||||
@@ -422,7 +428,7 @@ Træfik will not start but the [static configuration](/basics/#static-trfik-conf
|
||||
|
||||
If you configured ACME (Let's Encrypt), your registration account and your certificates will also be uploaded.
|
||||
|
||||
If you configured a file backend `[file]`, all your dynamic configuration (backends, frontends...) will be uploaded to the Key-value store.
|
||||
If you configured a file provider `[file]`, all your dynamic configuration (backends, frontends...) will be uploaded to the Key-value store.
|
||||
|
||||
To upload your ACME certificates to the KV store, get your Traefik TOML file and add the new `storage` option in the `acme` section:
|
||||
|
||||
|
@@ -101,8 +101,8 @@ Let's explain this command:
|
||||
| `--constraint=node.role==manager` | we ask docker to schedule Træfik on a manager node. |
|
||||
| `--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock` | we bind mount the docker socket where Træfik is scheduled to be able to speak to the daemon. |
|
||||
| `--network traefik-net` | we attach the Træfik service (and thus the underlying container) to the `traefik-net` network. |
|
||||
| `--docker` | enable docker backend, and `--docker.swarmMode` to enable the swarm mode on Træfik. |
|
||||
| `--api | activate the webUI on port 8080 |
|
||||
| `--docker` | enable docker provider, and `--docker.swarmMode` to enable the swarm mode on Træfik. |
|
||||
| `--api` | activate the webUI on port 8080 |
|
||||
|
||||
|
||||
## Deploy your apps
|
||||
|
@@ -104,7 +104,7 @@ Let's explain this command:
|
||||
| `--net=my-net` | run the container on the network my-net |
|
||||
| `-v /var/lib/boot2docker/:/ssl` | mount the ssl keys generated by docker-machine |
|
||||
| `-c /dev/null` | empty config file |
|
||||
| `--docker` | enable docker backend |
|
||||
| `--docker` | enable docker provider |
|
||||
| `--docker.endpoint=tcp://172.18.0.1:2376` | connect to the swarm master using the docker_gwbridge network |
|
||||
| `--docker.tls` | enable TLS using the docker-machine keys |
|
||||
| `--api` | activate the webUI on port 8080 |
|
||||
|
@@ -50,7 +50,7 @@ start_boulder() {
|
||||
# Script usage
|
||||
show_usage() {
|
||||
echo
|
||||
echo "USAGE : manage_acme_docker_environment.sh [--start|--stop|--restart]"
|
||||
echo "USAGE : manage_acme_docker_environment.sh [--dev|--start|--stop|--restart]"
|
||||
echo
|
||||
}
|
||||
|
||||
|
@@ -28,6 +28,11 @@ spec:
|
||||
containers:
|
||||
- image: traefik
|
||||
name: traefik-ingress-lb
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
- name: admin
|
||||
containerPort: 8080
|
||||
args:
|
||||
- --api
|
||||
- --kubernetes
|
||||
|
@@ -21,7 +21,6 @@ spec:
|
||||
spec:
|
||||
serviceAccountName: traefik-ingress-controller
|
||||
terminationGracePeriodSeconds: 60
|
||||
hostNetwork: true
|
||||
containers:
|
||||
- image: traefik
|
||||
name: traefik-ingress-lb
|
||||
@@ -31,6 +30,7 @@ spec:
|
||||
hostPort: 80
|
||||
- name: admin
|
||||
containerPort: 8080
|
||||
hostport: 8080
|
||||
securityContext:
|
||||
capabilities:
|
||||
drop:
|
||||
@@ -57,4 +57,3 @@ spec:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
name: admin
|
||||
type: NodePort
|
||||
|
@@ -13,11 +13,11 @@ version: '3'
|
||||
|
||||
services:
|
||||
reverse-proxy:
|
||||
image: traefik #The official Traefik docker image
|
||||
command: --api --docker #Enables the web UI and tells Træfik to listen to docker
|
||||
image: traefik # The official Traefik docker image
|
||||
command: --api --docker # Enables the web UI and tells Træfik to listen to docker
|
||||
ports:
|
||||
- "80:80" #The HTTP port
|
||||
- "8080:8080" #The Web UI (enabled by --api)
|
||||
- "80:80" # The HTTP port
|
||||
- "8080:8080" # The Web UI (enabled by --api)
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock #So that Traefik can listen to the Docker events
|
||||
```
|
||||
@@ -41,7 +41,7 @@ Edit your `docker-compose.yml` file and add the following at the end of your fil
|
||||
```yaml
|
||||
# ...
|
||||
whoami:
|
||||
image: emilevauge/whoami #A container that exposes an API to show it's IP address
|
||||
image: emilevauge/whoami # A container that exposes an API to show its IP address
|
||||
labels:
|
||||
- "traefik.frontend.rule=Host:whoami.docker.localhost"
|
||||
```
|
||||
@@ -101,6 +101,7 @@ IP: 172.27.0.4
|
||||
|
||||
### 4 — Enjoy Træfik's Magic
|
||||
|
||||
Now that you have a basic understanding of how Træfik can automatically create the routes to your services and load balance them, it might be time to dive into [the documentation](https://docs.traefik.io/) and let Træfik work for you! Whatever your infrastructure is, there is probably [an available Træfik backend](https://docs.traefik.io/configuration/backends/available) that will do the job.
|
||||
Now that you have a basic understanding of how Træfik can automatically create the routes to your services and load balance them, it might be time to dive into [the documentation](https://docs.traefik.io/) and let Træfik work for you!
|
||||
Whatever your infrastructure is, there is probably [an available Træfik backend](https://docs.traefik.io/#supported-backends) that will do the job.
|
||||
|
||||
Our recommendation would be to see for yourself how simple it is to enable HTTPS with [Træfik's let's encrypt integration](https://docs.traefik.io/user-guide/examples/#lets-encrypt-support) using the dedicated [user guide](https://docs.traefik.io/user-guide/docker-and-lets-encrypt/).
|
||||
Our recommendation would be to see for yourself how simple it is to enable HTTPS with [Træfik's let's encrypt integration](https://docs.traefik.io/user-guide/examples/#lets-encrypt-support) using the dedicated [user guide](https://docs.traefik.io/user-guide/docker-and-lets-encrypt/).
|
||||
|
@@ -30,7 +30,7 @@ type accessLogValue struct {
|
||||
code string
|
||||
user string
|
||||
frontendName string
|
||||
backendName string
|
||||
backendURL string
|
||||
}
|
||||
|
||||
func (s *AccessLogSuite) SetUpSuite(c *check.C) {
|
||||
@@ -103,7 +103,7 @@ func (s *AccessLogSuite) TestAccessLogAuthFrontend(c *check.C) {
|
||||
code: "401",
|
||||
user: "-",
|
||||
frontendName: "Auth for frontend-Host-frontend-auth-docker-local",
|
||||
backendName: "-",
|
||||
backendURL: "/",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ func (s *AccessLogSuite) TestAccessLogAuthEntrypoint(c *check.C) {
|
||||
code: "401",
|
||||
user: "-",
|
||||
frontendName: "Auth for entrypoint",
|
||||
backendName: "-",
|
||||
backendURL: "/",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ func (s *AccessLogSuite) TestAccessLogAuthEntrypointSuccess(c *check.C) {
|
||||
code: "200",
|
||||
user: "test",
|
||||
frontendName: "Host-entrypoint-auth-docker",
|
||||
backendName: "http://172.17.0",
|
||||
backendURL: "http://172.17.0",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -247,14 +247,14 @@ func (s *AccessLogSuite) TestAccessLogDigestAuthEntrypoint(c *check.C) {
|
||||
code: "401",
|
||||
user: "-",
|
||||
frontendName: "Auth for entrypoint",
|
||||
backendName: "-",
|
||||
backendURL: "/",
|
||||
},
|
||||
{
|
||||
formatOnly: false,
|
||||
code: "200",
|
||||
user: "test",
|
||||
frontendName: "Host-entrypoint-digest-auth-docker",
|
||||
backendName: "http://172.17.0",
|
||||
backendURL: "http://172.17.0",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -355,7 +355,7 @@ func (s *AccessLogSuite) TestAccessLogEntrypointRedirect(c *check.C) {
|
||||
code: "302",
|
||||
user: "-",
|
||||
frontendName: "entrypoint redirect for frontend-",
|
||||
backendName: "-",
|
||||
backendURL: "/",
|
||||
},
|
||||
{
|
||||
formatOnly: true,
|
||||
@@ -405,7 +405,7 @@ func (s *AccessLogSuite) TestAccessLogFrontendRedirect(c *check.C) {
|
||||
code: "302",
|
||||
user: "-",
|
||||
frontendName: "frontend redirect for frontend-Path-",
|
||||
backendName: "-",
|
||||
backendURL: "/",
|
||||
},
|
||||
{
|
||||
formatOnly: true,
|
||||
@@ -461,7 +461,7 @@ func (s *AccessLogSuite) TestAccessLogRateLimit(c *check.C) {
|
||||
code: "429",
|
||||
user: "-",
|
||||
frontendName: "rate limit for frontend-Host-ratelimit",
|
||||
backendName: "/",
|
||||
backendURL: "/",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -512,7 +512,7 @@ func (s *AccessLogSuite) TestAccessLogBackendNotFound(c *check.C) {
|
||||
code: "404",
|
||||
user: "-",
|
||||
frontendName: "backend not found",
|
||||
backendName: "/",
|
||||
backendURL: "/",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -557,7 +557,7 @@ func (s *AccessLogSuite) TestAccessLogEntrypointWhitelist(c *check.C) {
|
||||
code: "403",
|
||||
user: "-",
|
||||
frontendName: "ipwhitelister for entrypoint httpWhitelistReject",
|
||||
backendName: "-",
|
||||
backendURL: "/",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -604,7 +604,7 @@ func (s *AccessLogSuite) TestAccessLogFrontendWhitelist(c *check.C) {
|
||||
code: "403",
|
||||
user: "-",
|
||||
frontendName: "ipwhitelister for frontend-Host-frontend-whitelist",
|
||||
backendName: "-",
|
||||
backendURL: "/",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -734,7 +734,7 @@ func checkAccessLogExactValues(c *check.C, line string, i int, v accessLogValue)
|
||||
c.Assert(results[accesslog.OriginStatus], checker.Equals, v.code)
|
||||
c.Assert(results[accesslog.RequestCount], checker.Equals, fmt.Sprintf("%d", i+1))
|
||||
c.Assert(results[accesslog.FrontendName], checker.Matches, `^"?`+v.frontendName+`.*$`)
|
||||
c.Assert(results[accesslog.BackendURL], checker.Matches, `^"?`+v.backendName+`.*$`)
|
||||
c.Assert(results[accesslog.BackendURL], checker.Matches, `^"?`+v.backendURL+`.*$`)
|
||||
c.Assert(results[accesslog.Duration], checker.Matches, `^\d+ms$`)
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -41,7 +42,7 @@ func (s *ConsulCatalogSuite) waitToElectConsulLeader() error {
|
||||
leader, err := s.consulClient.Status().Leader()
|
||||
|
||||
if err != nil || len(leader) == 0 {
|
||||
return fmt.Errorf("Leader not found. %v", err)
|
||||
return fmt.Errorf("leader not found. %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -55,9 +56,6 @@ func (s *ConsulCatalogSuite) createConsulClient(config *api.Config, c *check.C)
|
||||
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 {
|
||||
catalog := s.consulClient.Catalog()
|
||||
@@ -78,28 +76,45 @@ func (s *ConsulCatalogSuite) registerService(name string, address string, port i
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) registerAgentService(name string, address string, port int, tags []string) error {
|
||||
func (s *ConsulCatalogSuite) registerAgentService(name string, address string, port int, tags []string, withHealthCheck bool) error {
|
||||
agent := s.consulClient.Agent()
|
||||
err := agent.ServiceRegister(
|
||||
var healthCheck *api.AgentServiceCheck
|
||||
if withHealthCheck {
|
||||
healthCheck = &api.AgentServiceCheck{
|
||||
HTTP: "http://" + address,
|
||||
Interval: "10s",
|
||||
}
|
||||
} else {
|
||||
healthCheck = nil
|
||||
}
|
||||
return agent.ServiceRegister(
|
||||
&api.AgentServiceRegistration{
|
||||
ID: address,
|
||||
Tags: tags,
|
||||
Name: name,
|
||||
Address: address,
|
||||
Port: port,
|
||||
Check: &api.AgentServiceCheck{
|
||||
HTTP: "http://" + address,
|
||||
Interval: "10s",
|
||||
},
|
||||
Check: healthCheck,
|
||||
},
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) registerCheck(name string, address string, port int) error {
|
||||
agent := s.consulClient.Agent()
|
||||
checkRegistration := &api.AgentCheckRegistration{
|
||||
ID: fmt.Sprintf("%s-%s", name, address),
|
||||
Name: name,
|
||||
ServiceID: address,
|
||||
}
|
||||
checkRegistration.HTTP = fmt.Sprintf("http://%s:%d/health", address, port)
|
||||
checkRegistration.Interval = "2s"
|
||||
checkRegistration.CheckID = address
|
||||
return agent.CheckRegister(checkRegistration)
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) deregisterAgentService(address string) error {
|
||||
agent := s.consulClient.Agent()
|
||||
err := agent.ServiceDeregister(address)
|
||||
return err
|
||||
return agent.ServiceDeregister(address)
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) deregisterService(name string, address string) error {
|
||||
@@ -115,6 +130,22 @@ func (s *ConsulCatalogSuite) deregisterService(name string, address string) erro
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) consulEnableServiceMaintenance(name string) error {
|
||||
return s.consulClient.Agent().EnableServiceMaintenance(name, fmt.Sprintf("Maintenance mode for service %s", name))
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) consulDisableServiceMaintenance(name string) error {
|
||||
return s.consulClient.Agent().DisableServiceMaintenance(name)
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) consulEnableNodeMaintenance() error {
|
||||
return s.consulClient.Agent().EnableNodeMaintenance("Maintenance mode for node")
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) consulDisableNodeMaintenance() error {
|
||||
return s.consulClient.Agent().DisableNodeMaintenance()
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) {
|
||||
cmd, display := s.traefikCmd(
|
||||
withConfigFile("fixtures/consul_catalog/simple.toml"),
|
||||
@@ -273,7 +304,7 @@ func (s *ConsulCatalogSuite) TestRefreshConfigWithMultipleNodeWithoutHealthCheck
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
||||
|
||||
err = s.registerAgentService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1"})
|
||||
err = s.registerAgentService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1"}, true)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering agent service"))
|
||||
defer s.deregisterAgentService(whoami.NetworkSettings.IPAddress)
|
||||
|
||||
@@ -514,3 +545,132 @@ func (s *ConsulCatalogSuite) TestRetryWithConsulServer(c *check.C) {
|
||||
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) TestServiceWithMultipleHealthCheck(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()
|
||||
|
||||
whoami := s.composeProject.Container(c, "whoami1")
|
||||
// Register service
|
||||
err = s.registerAgentService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1"}, true)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering agent service"))
|
||||
defer s.deregisterAgentService(whoami.NetworkSettings.IPAddress)
|
||||
|
||||
// Register one healthcheck
|
||||
err = s.registerCheck("test", whoami.NetworkSettings.IPAddress, 80)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering check"))
|
||||
|
||||
// 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)
|
||||
|
||||
// Change health value of service to critical
|
||||
reqHealth, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s:80/health", whoami.NetworkSettings.IPAddress), bytes.NewBuffer([]byte("500")))
|
||||
c.Assert(err, checker.IsNil)
|
||||
reqHealth.Host = "test.consul.localhost"
|
||||
|
||||
err = try.Request(reqHealth, 10*time.Second, try.StatusCodeIs(http.StatusOK))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Should be a 404
|
||||
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusNotFound), try.HasBody())
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Change health value of service to passing
|
||||
reqHealth, err = http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s:80/health", whoami.NetworkSettings.IPAddress), bytes.NewBuffer([]byte("200")))
|
||||
c.Assert(err, checker.IsNil)
|
||||
err = try.Request(reqHealth, 10*time.Second, try.StatusCodeIs(http.StatusOK))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Should be a 200
|
||||
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) TestMaintenanceMode(c *check.C) {
|
||||
cmd, display := s.traefikCmd(
|
||||
withConfigFile("fixtures/consul_catalog/simple.toml"),
|
||||
"--consulCatalog",
|
||||
"--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)
|
||||
|
||||
whoami := s.composeProject.Container(c, "whoami1")
|
||||
|
||||
err = s.registerAgentService("test", whoami.NetworkSettings.IPAddress, 80, []string{}, false)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "test.consul.localhost"
|
||||
|
||||
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Enable service maintenance mode
|
||||
err = s.consulEnableServiceMaintenance(whoami.NetworkSettings.IPAddress)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusNotFound), try.HasBody())
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Disable service maintenance mode
|
||||
err = s.consulDisableServiceMaintenance(whoami.NetworkSettings.IPAddress)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Enable node maintenance mode
|
||||
err = s.consulEnableNodeMaintenance()
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusNotFound), try.HasBody())
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Disable node maintenance mode
|
||||
err = s.consulDisableNodeMaintenance()
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
@@ -194,12 +194,14 @@ func (s *ConsulSuite) TestNominalConfiguration(c *check.C) {
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/test2", nil)
|
||||
try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
|
||||
c.Assert(err, checker.IsNil)
|
||||
err = try.Request(req, 500*time.Millisecond, 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 = "test2.localhost"
|
||||
try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
|
||||
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
@@ -583,21 +585,14 @@ func (s *ConsulSuite) TestSNIDynamicTlsConfig(c *check.C) {
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// wait for traefik
|
||||
err = try.GetRequest("http://127.0.0.1:8081/api/providers", 60*time.Second, try.BodyContains("MIIEpQIBAAKCAQEA1RducBK6EiFDv3TYB8ZcrfKWRVaSfHzWicO3J5WdST9oS7hG"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
client := &http.Client{Transport: tr1}
|
||||
req.Host = tr1.TLSClientConfig.ServerName
|
||||
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
|
||||
req.Header.Set("Accept", "*/*")
|
||||
var resp *http.Response
|
||||
resp, err = client.Do(req)
|
||||
|
||||
err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn("snitest.com"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
cn := resp.TLS.PeerCertificates[0].Subject.CommonName
|
||||
c.Assert(cn, checker.Equals, "snitest.com")
|
||||
|
||||
// now we configure the second keypair in consul and the request for host "snitest.org" will use the second keypair
|
||||
for key, value := range tlsconfigure2 {
|
||||
@@ -612,18 +607,12 @@ func (s *ConsulSuite) TestSNIDynamicTlsConfig(c *check.C) {
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// waiting for traefik to pull configuration
|
||||
err = try.GetRequest("http://127.0.0.1:8081/api/providers", 30*time.Second, try.BodyContains("MIIEogIBAAKCAQEAvG9kL+vF57+MICehzbqcQAUlAOSl5r"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
client = &http.Client{Transport: tr2}
|
||||
req.Host = tr2.TLSClientConfig.ServerName
|
||||
req.Header.Set("Host", tr2.TLSClientConfig.ServerName)
|
||||
req.Header.Set("Accept", "*/*")
|
||||
resp, err = client.Do(req)
|
||||
|
||||
err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn("snitest.org"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
cn = resp.TLS.PeerCertificates[0].Subject.CommonName
|
||||
c.Assert(cn, checker.Equals, "snitest.org")
|
||||
}
|
||||
|
@@ -532,21 +532,14 @@ func (s *Etcd3Suite) TestSNIDynamicTlsConfig(c *check.C) {
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
// wait for Træfik
|
||||
err = try.GetRequest("http://127.0.0.1:8081/api/providers", 60*time.Second, try.BodyContains(string("MIIEpQIBAAKCAQEA1RducBK6EiFDv3TYB8ZcrfKWRVaSfHzWicO3J5WdST9oS7h")))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
client := &http.Client{Transport: tr1}
|
||||
req.Host = tr1.TLSClientConfig.ServerName
|
||||
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
|
||||
req.Header.Set("Accept", "*/*")
|
||||
var resp *http.Response
|
||||
resp, err = client.Do(req)
|
||||
|
||||
err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn("snitest.com"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
cn := resp.TLS.PeerCertificates[0].Subject.CommonName
|
||||
c.Assert(cn, checker.Equals, "snitest.com")
|
||||
|
||||
// now we configure the second keypair in etcd and the request for host "snitest.org" will use the second keypair
|
||||
|
||||
@@ -562,20 +555,14 @@ func (s *Etcd3Suite) TestSNIDynamicTlsConfig(c *check.C) {
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// waiting for Træfik to pull configuration
|
||||
err = try.GetRequest("http://127.0.0.1:8081/api/providers", 30*time.Second, try.BodyContains("MIIEogIBAAKCAQEAvG9kL+vF57+MICehzbqcQAUlAOSl5r"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
client = &http.Client{Transport: tr2}
|
||||
req.Host = tr2.TLSClientConfig.ServerName
|
||||
req.Header.Set("Host", tr2.TLSClientConfig.ServerName)
|
||||
req.Header.Set("Accept", "*/*")
|
||||
resp, err = client.Do(req)
|
||||
|
||||
err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn("snitest.org"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
cn = resp.TLS.PeerCertificates[0].Subject.CommonName
|
||||
c.Assert(cn, checker.Equals, "snitest.org")
|
||||
}
|
||||
|
||||
func (s *Etcd3Suite) TestDeleteSNIDynamicTlsConfig(c *check.C) {
|
||||
@@ -646,21 +633,14 @@ func (s *Etcd3Suite) TestDeleteSNIDynamicTlsConfig(c *check.C) {
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
// wait for Træfik
|
||||
err = try.GetRequest(traefikWebEtcdURL+"api/providers", 60*time.Second, try.BodyContains(string("MIIEpQIBAAKCAQEA1RducBK6EiFDv3TYB8ZcrfKWRVaSfHzWicO3J5WdST9oS7h")))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
client := &http.Client{Transport: tr1}
|
||||
req.Host = tr1.TLSClientConfig.ServerName
|
||||
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
|
||||
req.Header.Set("Accept", "*/*")
|
||||
var resp *http.Response
|
||||
resp, err = client.Do(req)
|
||||
|
||||
err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn("snitest.com"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
cn := resp.TLS.PeerCertificates[0].Subject.CommonName
|
||||
c.Assert(cn, checker.Equals, "snitest.com")
|
||||
|
||||
// now we delete the tls cert/key pairs,so the endpoint show use default cert/key pair
|
||||
for key := range tlsconfigure1 {
|
||||
@@ -668,18 +648,12 @@ func (s *Etcd3Suite) TestDeleteSNIDynamicTlsConfig(c *check.C) {
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
// waiting for Træfik to pull configuration
|
||||
err = try.GetRequest(traefikWebEtcdURL+"api/providers", 30*time.Second, try.BodyNotContains("MIIEpQIBAAKCAQEA1RducBK6EiFDv3TYB8ZcrfKWRVaSfHzWicO3J5WdST9oS7h"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
client = &http.Client{Transport: tr1}
|
||||
req.Host = tr1.TLSClientConfig.ServerName
|
||||
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
|
||||
req.Header.Set("Accept", "*/*")
|
||||
resp, err = client.Do(req)
|
||||
|
||||
err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn("TRAEFIK DEFAULT CERT"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
cn = resp.TLS.PeerCertificates[0].Subject.CommonName
|
||||
c.Assert(cn, checker.Equals, "TRAEFIK DEFAULT CERT")
|
||||
}
|
||||
|
@@ -548,21 +548,14 @@ func (s *EtcdSuite) TestSNIDynamicTlsConfig(c *check.C) {
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
// wait for Træfik
|
||||
err = try.GetRequest("http://127.0.0.1:8081/api/providers", 60*time.Second, try.BodyContains(string("MIIEpQIBAAKCAQEA1RducBK6EiFDv3TYB8ZcrfKWRVaSfHzWicO3J5WdST9oS7h")))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
client := &http.Client{Transport: tr1}
|
||||
req.Host = tr1.TLSClientConfig.ServerName
|
||||
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
|
||||
req.Header.Set("Accept", "*/*")
|
||||
var resp *http.Response
|
||||
resp, err = client.Do(req)
|
||||
|
||||
err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn("snitest.com"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
cn := resp.TLS.PeerCertificates[0].Subject.CommonName
|
||||
c.Assert(cn, checker.Equals, "snitest.com")
|
||||
|
||||
// now we configure the second keypair in etcd and the request for host "snitest.org" will use the second keypair
|
||||
|
||||
@@ -578,18 +571,12 @@ func (s *EtcdSuite) TestSNIDynamicTlsConfig(c *check.C) {
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// waiting for Træfik to pull configuration
|
||||
err = try.GetRequest("http://127.0.0.1:8081/api/providers", 30*time.Second, try.BodyContains("MIIEogIBAAKCAQEAvG9kL+vF57+MICehzbqcQAUlAOSl5r"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
client = &http.Client{Transport: tr2}
|
||||
req.Host = tr2.TLSClientConfig.ServerName
|
||||
req.Header.Set("Host", tr2.TLSClientConfig.ServerName)
|
||||
req.Header.Set("Accept", "*/*")
|
||||
resp, err = client.Do(req)
|
||||
|
||||
err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn("snitest.org"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
cn = resp.TLS.PeerCertificates[0].Subject.CommonName
|
||||
c.Assert(cn, checker.Equals, "snitest.org")
|
||||
}
|
||||
|
@@ -14,6 +14,7 @@ defaultEntryPoints = ["http", "https"]
|
||||
email = "test@traefik.io"
|
||||
storage = "/tmp/acme.json"
|
||||
entryPoint = "https"
|
||||
acmeLogging = true
|
||||
onDemand = {{.OnDemand}}
|
||||
onHostRule = {{.OnHostRule}}
|
||||
caServer = "http://{{.BoulderHost}}:4001/directory"
|
||||
|
@@ -13,6 +13,7 @@ defaultEntryPoints = ["http", "https"]
|
||||
email = "test@traefik.io"
|
||||
storage = "/tmp/acme.json"
|
||||
entryPoint = "https"
|
||||
acmeLogging = true
|
||||
onDemand = {{.OnDemand}}
|
||||
onHostRule = {{.OnHostRule}}
|
||||
caServer = "http://{{.BoulderHost}}:4001/directory"
|
||||
|
@@ -16,6 +16,7 @@ defaultEntryPoints = ["http", "https"]
|
||||
email = "test@traefik.io"
|
||||
storage = "/tmp/acme.json"
|
||||
entryPoint = "https"
|
||||
acmeLogging = true
|
||||
onDemand = {{.OnDemand}}
|
||||
onHostRule = {{.OnHostRule}}
|
||||
caServer = "http://{{.BoulderHost}}:4001/directory"
|
||||
|
@@ -14,6 +14,7 @@ defaultEntryPoints = ["http", "https"]
|
||||
email = "test@traefik.io"
|
||||
storage = "/tmp/acme.json"
|
||||
entryPoint = "https"
|
||||
acmeLogging = true
|
||||
onDemand = {{.OnDemand}}
|
||||
onHostRule = {{.OnHostRule}}
|
||||
caServer = "http://{{.BoulderHost}}:4001/directory"
|
||||
|
@@ -17,6 +17,7 @@ email = "test@traefik.io"
|
||||
storage = "/tmp/acme.json"
|
||||
entryPoint = "https"
|
||||
onHostRule = true
|
||||
acmeLogging = true
|
||||
caServer = "http://{{.BoulderHost}}:4001/directory"
|
||||
# No challenge defined
|
||||
|
||||
|
@@ -16,6 +16,7 @@ defaultEntryPoints = ["http", "https"]
|
||||
email = "test@traefik.io"
|
||||
storage = "/tmp/acme.json"
|
||||
entryPoint = "https"
|
||||
acmeLogging = true
|
||||
onHostRule = true
|
||||
caServer = "http://wrongurl:4001/directory"
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
[backends]
|
||||
[backends.backend2]
|
||||
[backends.backend2.servers.server1]
|
||||
url = "http://172.17.0.2:80"
|
||||
url = "http://172.17.0.123:80"
|
||||
weight = 1
|
||||
|
||||
[frontends]
|
||||
|
@@ -14,6 +14,7 @@ defaultEntryPoints = ["http", "https"]
|
||||
email = "test@traefik.io"
|
||||
storage = "/tmp/acme.json"
|
||||
entryPoint = "https"
|
||||
acmeLogging = true
|
||||
onDemand = {{.OnDemand}}
|
||||
onHostRule = {{.OnHostRule}}
|
||||
caServer = "http://{{.BoulderHost}}:4001/directory"
|
||||
|
@@ -14,6 +14,7 @@ defaultEntryPoints = ["http", "https"]
|
||||
email = "test@traefik.io"
|
||||
storage = "/tmp/acme.json"
|
||||
entryPoint = "https"
|
||||
acmeLogging = true
|
||||
onDemand = false
|
||||
onHostRule = false
|
||||
caServer = "http://{{.BoulderHost}}:4001/directory"
|
||||
|
@@ -14,6 +14,7 @@ defaultEntryPoints = ["http", "https"]
|
||||
email = "test@traefik.io"
|
||||
storage = "/tmp/acme.jsonl"
|
||||
entryPoint = "https"
|
||||
acmeLogging = true
|
||||
onDemand = {{.OnDemand}}
|
||||
onHostRule = {{.OnHostRule}}
|
||||
caServer = "http://{{.BoulderHost}}:4001/directory"
|
||||
|
@@ -88,6 +88,31 @@ func HasBody() ResponseCondition {
|
||||
}
|
||||
}
|
||||
|
||||
// HasCn returns a retry condition function.
|
||||
// The condition returns an error if the cn is not correct.
|
||||
func HasCn(cn string) ResponseCondition {
|
||||
return func(res *http.Response) error {
|
||||
if res.TLS == nil {
|
||||
return errors.New("response doesn't have TLS")
|
||||
}
|
||||
|
||||
if len(res.TLS.PeerCertificates) == 0 {
|
||||
return errors.New("response TLS doesn't have peer certificates")
|
||||
}
|
||||
|
||||
if res.TLS.PeerCertificates[0] == nil {
|
||||
return errors.New("first peer certificate is nil")
|
||||
}
|
||||
|
||||
commonName := res.TLS.PeerCertificates[0].Subject.CommonName
|
||||
if cn != commonName {
|
||||
return fmt.Errorf("common name don't match: %s != %s", cn, commonName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// StatusCodeIs returns a retry condition function.
|
||||
// The condition returns an error if the given response's status code is not the
|
||||
// given HTTP status code.
|
||||
|
@@ -31,7 +31,7 @@ func Sleep(d time.Duration) {
|
||||
// response body needs to be closed or not. Callers are expected to close on
|
||||
// their own if the function returns a nil error.
|
||||
func Response(req *http.Request, timeout time.Duration) (*http.Response, error) {
|
||||
return doTryRequest(req, timeout)
|
||||
return doTryRequest(req, timeout, nil)
|
||||
}
|
||||
|
||||
// ResponseUntilStatusCode is like Request, but returns the response for further
|
||||
@@ -40,7 +40,7 @@ func Response(req *http.Request, timeout time.Duration) (*http.Response, error)
|
||||
// response body needs to be closed or not. Callers are expected to close on
|
||||
// their own if the function returns a nil error.
|
||||
func ResponseUntilStatusCode(req *http.Request, timeout time.Duration, statusCode int) (*http.Response, error) {
|
||||
return doTryRequest(req, timeout, StatusCodeIs(statusCode))
|
||||
return doTryRequest(req, timeout, nil, StatusCodeIs(statusCode))
|
||||
}
|
||||
|
||||
// GetRequest is like Do, but runs a request against the given URL and applies
|
||||
@@ -48,7 +48,7 @@ func ResponseUntilStatusCode(req *http.Request, timeout time.Duration, statusCod
|
||||
// ResponseCondition may be nil, in which case only the request against the URL must
|
||||
// succeed.
|
||||
func GetRequest(url string, timeout time.Duration, conditions ...ResponseCondition) error {
|
||||
resp, err := doTryGet(url, timeout, conditions...)
|
||||
resp, err := doTryGet(url, timeout, nil, conditions...)
|
||||
|
||||
if resp != nil && resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
@@ -62,7 +62,21 @@ func GetRequest(url string, timeout time.Duration, conditions ...ResponseConditi
|
||||
// ResponseCondition may be nil, in which case only the request against the URL must
|
||||
// succeed.
|
||||
func Request(req *http.Request, timeout time.Duration, conditions ...ResponseCondition) error {
|
||||
resp, err := doTryRequest(req, timeout, conditions...)
|
||||
resp, err := doTryRequest(req, timeout, nil, conditions...)
|
||||
|
||||
if resp != nil && resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// RequestWithTransport is like Do, but runs a request against the given URL and applies
|
||||
// the condition on the response.
|
||||
// ResponseCondition may be nil, in which case only the request against the URL must
|
||||
// succeed.
|
||||
func RequestWithTransport(req *http.Request, timeout time.Duration, transport *http.Transport, conditions ...ResponseCondition) error {
|
||||
resp, err := doTryRequest(req, timeout, transport, conditions...)
|
||||
|
||||
if resp != nil && resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
@@ -112,24 +126,27 @@ func Do(timeout time.Duration, operation DoCondition) error {
|
||||
}
|
||||
}
|
||||
|
||||
func doTryGet(url string, timeout time.Duration, conditions ...ResponseCondition) (*http.Response, error) {
|
||||
func doTryGet(url string, timeout time.Duration, transport *http.Transport, conditions ...ResponseCondition) (*http.Response, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return doTryRequest(req, timeout, conditions...)
|
||||
return doTryRequest(req, timeout, transport, conditions...)
|
||||
}
|
||||
|
||||
func doTryRequest(request *http.Request, timeout time.Duration, conditions ...ResponseCondition) (*http.Response, error) {
|
||||
return doRequest(Do, timeout, request, conditions...)
|
||||
func doTryRequest(request *http.Request, timeout time.Duration, transport *http.Transport, conditions ...ResponseCondition) (*http.Response, error) {
|
||||
return doRequest(Do, timeout, request, transport, conditions...)
|
||||
}
|
||||
|
||||
func doRequest(action timedAction, timeout time.Duration, request *http.Request, conditions ...ResponseCondition) (*http.Response, error) {
|
||||
func doRequest(action timedAction, timeout time.Duration, request *http.Request, transport *http.Transport, conditions ...ResponseCondition) (*http.Response, error) {
|
||||
var resp *http.Response
|
||||
return resp, action(timeout, func() error {
|
||||
var err error
|
||||
client := http.DefaultClient
|
||||
if transport != nil {
|
||||
client.Transport = transport
|
||||
}
|
||||
|
||||
resp, err = client.Do(request)
|
||||
if err != nil {
|
||||
|
@@ -36,25 +36,18 @@ const (
|
||||
backendServerUpName = metricNamePrefix + "backend_server_up"
|
||||
)
|
||||
|
||||
const (
|
||||
// generationAgeForever indicates that a metric never gets outdated.
|
||||
generationAgeForever = 0
|
||||
// generationAgeDefault is the default age of three generations.
|
||||
generationAgeDefault = 3
|
||||
)
|
||||
|
||||
// promState holds all metric state internally and acts as the only Collector we register for Prometheus.
|
||||
//
|
||||
// This enables control to remove metrics that belong to outdated configuration.
|
||||
// As an example why this is required, consider Traefik learns about a new service.
|
||||
// It populates the 'traefik_server_backend_up' metric for it with a value of 1 (alive).
|
||||
// When the backend is undeployed now the metric is still there in the client library
|
||||
// and will be until Traefik would be restarted.
|
||||
// and will be returned on the metrics endpoint until Traefik would be restarted.
|
||||
//
|
||||
// To solve this problem promState keeps track of configuration generations.
|
||||
// Every time a new configuration is loaded, the generation is increased by one.
|
||||
// Metrics that "belong" to a dynamic configuration part of Traefik (e.g. backend, entrypoint)
|
||||
// are removed, given they were tracked more than 3 generations ago.
|
||||
// To solve this problem promState keeps track of Traefik's dynamic configuration.
|
||||
// Metrics that "belong" to a dynamic configuration part like backends or entrypoints
|
||||
// are removed after they were scraped at least once when the corresponding object
|
||||
// doesn't exist anymore.
|
||||
var promState = newPrometheusState()
|
||||
|
||||
// PrometheusHandler exposes Prometheus routes.
|
||||
@@ -163,40 +156,66 @@ func RegisterPrometheus(config *types.Prometheus) Registry {
|
||||
}
|
||||
}
|
||||
|
||||
// OnConfigurationUpdate increases the current generation of the prometheus state.
|
||||
func OnConfigurationUpdate() {
|
||||
promState.IncGeneration()
|
||||
// OnConfigurationUpdate receives the current configuration from Traefik.
|
||||
// It then converts the configuration to the optimized package internal format
|
||||
// and sets it to the promState.
|
||||
func OnConfigurationUpdate(configurations types.Configurations) {
|
||||
dynamicConfig := newDynamicConfig()
|
||||
|
||||
for _, config := range configurations {
|
||||
for _, frontend := range config.Frontends {
|
||||
for _, entrypointName := range frontend.EntryPoints {
|
||||
dynamicConfig.entrypoints[entrypointName] = true
|
||||
}
|
||||
}
|
||||
|
||||
for backendName, backend := range config.Backends {
|
||||
dynamicConfig.backends[backendName] = make(map[string]bool)
|
||||
for _, server := range backend.Servers {
|
||||
dynamicConfig.backends[backendName][server.URL] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
promState.SetDynamicConfig(dynamicConfig)
|
||||
}
|
||||
|
||||
func newPrometheusState() *prometheusState {
|
||||
collectors := make(chan *collector)
|
||||
state := make(map[string]*collector)
|
||||
|
||||
return &prometheusState{
|
||||
collectors: collectors,
|
||||
state: state,
|
||||
collectors: make(chan *collector),
|
||||
dynamicConfig: newDynamicConfig(),
|
||||
state: make(map[string]*collector),
|
||||
}
|
||||
}
|
||||
|
||||
type prometheusState struct {
|
||||
currentGeneration int
|
||||
collectors chan *collector
|
||||
describers []func(ch chan<- *stdprometheus.Desc)
|
||||
collectors chan *collector
|
||||
describers []func(ch chan<- *stdprometheus.Desc)
|
||||
|
||||
mtx sync.Mutex
|
||||
state map[string]*collector
|
||||
mtx sync.Mutex
|
||||
dynamicConfig *dynamicConfig
|
||||
state map[string]*collector
|
||||
}
|
||||
|
||||
func (ps *prometheusState) IncGeneration() {
|
||||
// reset is a utility method for unit testing. It should be called after each
|
||||
// test run that changes promState internally in order to avoid dependencies
|
||||
// between unit tests.
|
||||
func (ps *prometheusState) reset() {
|
||||
ps.collectors = make(chan *collector)
|
||||
ps.describers = []func(ch chan<- *stdprometheus.Desc){}
|
||||
ps.dynamicConfig = newDynamicConfig()
|
||||
ps.state = make(map[string]*collector)
|
||||
}
|
||||
|
||||
func (ps *prometheusState) SetDynamicConfig(dynamicConfig *dynamicConfig) {
|
||||
ps.mtx.Lock()
|
||||
defer ps.mtx.Unlock()
|
||||
ps.currentGeneration++
|
||||
ps.dynamicConfig = dynamicConfig
|
||||
}
|
||||
|
||||
func (ps *prometheusState) ListenValueUpdates() {
|
||||
for collector := range ps.collectors {
|
||||
ps.mtx.Lock()
|
||||
collector.lastTrackedGeneration = ps.currentGeneration
|
||||
ps.state[collector.id] = collector
|
||||
ps.mtx.Unlock()
|
||||
}
|
||||
@@ -212,42 +231,89 @@ func (ps *prometheusState) Describe(ch chan<- *stdprometheus.Desc) {
|
||||
|
||||
// Collect implements prometheus.Collector. It calls the Collect
|
||||
// method of all metrics it received on the collectors channel.
|
||||
// It's also responsible to remove metrics that were tracked
|
||||
// at least three generations ago. Those metrics are cleaned up
|
||||
// after the Collect of them were called.
|
||||
// It's also responsible to remove metrics that belong to an outdated configuration.
|
||||
// The removal happens only after their Collect method was called to ensure that
|
||||
// also those metrics will be exported on the current scrape.
|
||||
func (ps *prometheusState) Collect(ch chan<- stdprometheus.Metric) {
|
||||
ps.mtx.Lock()
|
||||
defer ps.mtx.Unlock()
|
||||
|
||||
outdatedKeys := []string{}
|
||||
var outdatedKeys []string
|
||||
for key, cs := range ps.state {
|
||||
cs.collector.Collect(ch)
|
||||
|
||||
if cs.maxAge == generationAgeForever {
|
||||
continue
|
||||
}
|
||||
if ps.currentGeneration-cs.lastTrackedGeneration >= cs.maxAge {
|
||||
if ps.isOutdated(cs) {
|
||||
outdatedKeys = append(outdatedKeys, key)
|
||||
}
|
||||
}
|
||||
|
||||
for _, key := range outdatedKeys {
|
||||
ps.state[key].delete()
|
||||
delete(ps.state, key)
|
||||
}
|
||||
}
|
||||
|
||||
func newCollector(metricName string, lnvs labelNamesValues, c stdprometheus.Collector) *collector {
|
||||
maxAge := generationAgeDefault
|
||||
// isOutdated checks whether the passed collector has labels that mark
|
||||
// it as belonging to an outdated configuration of Traefik.
|
||||
func (ps *prometheusState) isOutdated(collector *collector) bool {
|
||||
labels := collector.labels
|
||||
|
||||
// metrics without labels should never become outdated
|
||||
if len(lnvs) == 0 {
|
||||
maxAge = generationAgeForever
|
||||
if entrypointName, ok := labels["entrypoint"]; ok && !ps.dynamicConfig.hasEntrypoint(entrypointName) {
|
||||
return true
|
||||
}
|
||||
|
||||
if backendName, ok := labels["backend"]; ok {
|
||||
if !ps.dynamicConfig.hasBackend(backendName) {
|
||||
return true
|
||||
}
|
||||
if url, ok := labels["url"]; ok && !ps.dynamicConfig.hasServerURL(backendName, url) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func newDynamicConfig() *dynamicConfig {
|
||||
return &dynamicConfig{
|
||||
entrypoints: make(map[string]bool),
|
||||
backends: make(map[string]map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
// dynamicConfig holds the current configuration for entrypoints, backends,
|
||||
// and server URLs in an optimized way to check for existence. This provides
|
||||
// a performant way to check whether the collected metrics belong to the
|
||||
// current configuration or to an outdated one.
|
||||
type dynamicConfig struct {
|
||||
entrypoints map[string]bool
|
||||
backends map[string]map[string]bool
|
||||
}
|
||||
|
||||
func (d *dynamicConfig) hasEntrypoint(entrypointName string) bool {
|
||||
_, ok := d.entrypoints[entrypointName]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (d *dynamicConfig) hasBackend(backendName string) bool {
|
||||
_, ok := d.backends[backendName]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (d *dynamicConfig) hasServerURL(backendName, serverURL string) bool {
|
||||
if backend, hasBackend := d.backends[backendName]; hasBackend {
|
||||
_, ok := backend[serverURL]
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func newCollector(metricName string, labels stdprometheus.Labels, c stdprometheus.Collector, delete func()) *collector {
|
||||
return &collector{
|
||||
id: buildMetricID(metricName, lnvs),
|
||||
maxAge: maxAge,
|
||||
id: buildMetricID(metricName, labels),
|
||||
labels: labels,
|
||||
collector: c,
|
||||
delete: delete,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,16 +321,19 @@ func newCollector(metricName string, lnvs labelNamesValues, c stdprometheus.Coll
|
||||
// It adds information on how many generations this metric should be present
|
||||
// in the /metrics output, relatived to the time it was last tracked.
|
||||
type collector struct {
|
||||
id string
|
||||
collector stdprometheus.Collector
|
||||
lastTrackedGeneration int
|
||||
maxAge int
|
||||
id string
|
||||
labels stdprometheus.Labels
|
||||
collector stdprometheus.Collector
|
||||
delete func()
|
||||
}
|
||||
|
||||
func buildMetricID(metricName string, lnvs labelNamesValues) string {
|
||||
newLnvs := append([]string{}, lnvs...)
|
||||
sort.Strings(newLnvs)
|
||||
return metricName + ":" + strings.Join(newLnvs, "|")
|
||||
func buildMetricID(metricName string, labels stdprometheus.Labels) string {
|
||||
var labelNamesValues []string
|
||||
for name, value := range labels {
|
||||
labelNamesValues = append(labelNamesValues, name, value)
|
||||
}
|
||||
sort.Strings(labelNamesValues)
|
||||
return metricName + ":" + strings.Join(labelNamesValues, "|")
|
||||
}
|
||||
|
||||
func newCounterFrom(collectors chan<- *collector, opts stdprometheus.CounterOpts, labelNames []string) *counter {
|
||||
@@ -297,9 +366,12 @@ func (c *counter) With(labelValues ...string) metrics.Counter {
|
||||
}
|
||||
|
||||
func (c *counter) Add(delta float64) {
|
||||
collector := c.cv.With(c.labelNamesValues.ToLabels())
|
||||
labels := c.labelNamesValues.ToLabels()
|
||||
collector := c.cv.With(labels)
|
||||
collector.Add(delta)
|
||||
c.collectors <- newCollector(c.name, c.labelNamesValues, collector)
|
||||
c.collectors <- newCollector(c.name, labels, collector, func() {
|
||||
c.cv.Delete(labels)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *counter) Describe(ch chan<- *stdprometheus.Desc) {
|
||||
@@ -336,15 +408,21 @@ func (g *gauge) With(labelValues ...string) metrics.Gauge {
|
||||
}
|
||||
|
||||
func (g *gauge) Add(delta float64) {
|
||||
collector := g.gv.With(g.labelNamesValues.ToLabels())
|
||||
labels := g.labelNamesValues.ToLabels()
|
||||
collector := g.gv.With(labels)
|
||||
collector.Add(delta)
|
||||
g.collectors <- newCollector(g.name, g.labelNamesValues, collector)
|
||||
g.collectors <- newCollector(g.name, labels, collector, func() {
|
||||
g.gv.Delete(labels)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *gauge) Set(value float64) {
|
||||
collector := g.gv.With(g.labelNamesValues.ToLabels())
|
||||
labels := g.labelNamesValues.ToLabels()
|
||||
collector := g.gv.With(labels)
|
||||
collector.Set(value)
|
||||
g.collectors <- newCollector(g.name, g.labelNamesValues, collector)
|
||||
g.collectors <- newCollector(g.name, labels, collector, func() {
|
||||
g.gv.Delete(labels)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *gauge) Describe(ch chan<- *stdprometheus.Desc) {
|
||||
@@ -377,9 +455,12 @@ func (h *histogram) With(labelValues ...string) metrics.Histogram {
|
||||
}
|
||||
|
||||
func (h *histogram) Observe(value float64) {
|
||||
collector := h.hv.With(h.labelNamesValues.ToLabels())
|
||||
labels := h.labelNamesValues.ToLabels()
|
||||
collector := h.hv.With(labels)
|
||||
collector.Observe(value)
|
||||
h.collectors <- newCollector(h.name, h.labelNamesValues, collector)
|
||||
h.collectors <- newCollector(h.name, labels, collector, func() {
|
||||
h.hv.Delete(labels)
|
||||
})
|
||||
}
|
||||
|
||||
func (h *histogram) Describe(ch chan<- *stdprometheus.Desc) {
|
||||
|
@@ -7,12 +7,16 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
th "github.com/containous/traefik/testhelpers"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
)
|
||||
|
||||
func TestPrometheus(t *testing.T) {
|
||||
// Reset state of global promState.
|
||||
defer promState.reset()
|
||||
|
||||
prometheusRegistry := RegisterPrometheus(&types.Prometheus{})
|
||||
defer prometheus.Unregister(promState)
|
||||
|
||||
@@ -177,56 +181,94 @@ func TestPrometheus(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrometheusGenerationLogicForMetricWithLabel(t *testing.T) {
|
||||
func TestPrometheusMetricRemoval(t *testing.T) {
|
||||
// Reset state of global promState.
|
||||
defer promState.reset()
|
||||
|
||||
prometheusRegistry := RegisterPrometheus(&types.Prometheus{})
|
||||
defer prometheus.Unregister(promState)
|
||||
|
||||
// Metrics with labels belonging to a specific configuration in Traefik
|
||||
// should be removed when the generationMaxAge is exceeded. As example
|
||||
// we use the traefik_backend_requests_total metric.
|
||||
configurations := make(types.Configurations)
|
||||
configurations["providerName"] = th.BuildConfiguration(
|
||||
th.WithFrontends(
|
||||
th.WithFrontend("backend1", th.WithEntryPoints("entrypoint1")),
|
||||
),
|
||||
th.WithBackends(
|
||||
th.WithBackendNew("backend1", th.WithServersNew(th.WithServerNew("http://localhost:9000"))),
|
||||
),
|
||||
)
|
||||
OnConfigurationUpdate(configurations)
|
||||
|
||||
// Register some metrics manually that are not part of the active configuration.
|
||||
// Those metrics should be part of the /metrics output on the first scrape but
|
||||
// should be removed after that scrape.
|
||||
prometheusRegistry.
|
||||
EntrypointReqsCounter().
|
||||
With("entrypoint", "entrypoint2", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet, "protocol", "http").
|
||||
Add(1)
|
||||
prometheusRegistry.
|
||||
BackendReqsCounter().
|
||||
With("backend", "backend1", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet, "protocol", "http").
|
||||
With("backend", "backend2", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet, "protocol", "http").
|
||||
Add(1)
|
||||
prometheusRegistry.
|
||||
BackendServerUpGauge().
|
||||
With("backend", "backend1", "url", "http://localhost:9999").
|
||||
Set(1)
|
||||
|
||||
delayForTrackingCompletion()
|
||||
|
||||
assertMetricsExist(t, mustScrape(), entrypointReqsTotalName, backendReqsTotalName, backendServerUpName)
|
||||
assertMetricsAbsent(t, mustScrape(), entrypointReqsTotalName, backendReqsTotalName, backendServerUpName)
|
||||
|
||||
// To verify that metrics belonging to active configurations are not removed
|
||||
// here the counter examples.
|
||||
prometheusRegistry.
|
||||
EntrypointReqsCounter().
|
||||
With("entrypoint", "entrypoint1", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet, "protocol", "http").
|
||||
Add(1)
|
||||
|
||||
delayForTrackingCompletion()
|
||||
|
||||
assertMetricExists(t, backendReqsTotalName, mustScrape())
|
||||
|
||||
// Increase the config generation one more than the max age of a metric.
|
||||
for i := 0; i < generationAgeDefault+1; i++ {
|
||||
OnConfigurationUpdate()
|
||||
}
|
||||
|
||||
// On the next scrape the metric still exists and will be removed
|
||||
// after the scrape completed.
|
||||
assertMetricExists(t, backendReqsTotalName, mustScrape())
|
||||
|
||||
// Now the metric should be absent.
|
||||
assertMetricAbsent(t, backendReqsTotalName, mustScrape())
|
||||
assertMetricsExist(t, mustScrape(), entrypointReqsTotalName)
|
||||
assertMetricsExist(t, mustScrape(), entrypointReqsTotalName)
|
||||
}
|
||||
|
||||
func TestPrometheusGenerationLogicForMetricWithoutLabel(t *testing.T) {
|
||||
func TestPrometheusRemovedMetricsReset(t *testing.T) {
|
||||
// Reset state of global promState.
|
||||
defer promState.reset()
|
||||
|
||||
prometheusRegistry := RegisterPrometheus(&types.Prometheus{})
|
||||
defer prometheus.Unregister(promState)
|
||||
|
||||
// Metrics without labels like traefik_config_reloads_total should live forever
|
||||
// and never get removed.
|
||||
prometheusRegistry.ConfigReloadsCounter().Add(1)
|
||||
labelNamesValues := []string{
|
||||
"backend", "backend",
|
||||
"code", strconv.Itoa(http.StatusOK),
|
||||
"method", http.MethodGet,
|
||||
"protocol", "http",
|
||||
}
|
||||
prometheusRegistry.
|
||||
BackendReqsCounter().
|
||||
With(labelNamesValues...).
|
||||
Add(3)
|
||||
|
||||
delayForTrackingCompletion()
|
||||
|
||||
assertMetricExists(t, configReloadsTotalName, mustScrape())
|
||||
metricsFamilies := mustScrape()
|
||||
assertCounterValue(t, 3, findMetricFamily(backendReqsTotalName, metricsFamilies), labelNamesValues...)
|
||||
|
||||
// Increase the config generation one more than the max age of a metric.
|
||||
for i := 0; i < generationAgeDefault+100; i++ {
|
||||
OnConfigurationUpdate()
|
||||
}
|
||||
// There is no dynamic configuration and so this metric will be deleted
|
||||
// after the first scrape.
|
||||
assertMetricsAbsent(t, mustScrape(), backendReqsTotalName)
|
||||
|
||||
// Scrape two times in order to verify, that it is not removed after the
|
||||
// first scrape completed.
|
||||
assertMetricExists(t, configReloadsTotalName, mustScrape())
|
||||
assertMetricExists(t, configReloadsTotalName, mustScrape())
|
||||
prometheusRegistry.
|
||||
BackendReqsCounter().
|
||||
With(labelNamesValues...).
|
||||
Add(1)
|
||||
|
||||
delayForTrackingCompletion()
|
||||
|
||||
metricsFamilies = mustScrape()
|
||||
assertCounterValue(t, 1, findMetricFamily(backendReqsTotalName, metricsFamilies), labelNamesValues...)
|
||||
}
|
||||
|
||||
// Tracking and gathering the metrics happens concurrently.
|
||||
@@ -247,17 +289,23 @@ func mustScrape() []*dto.MetricFamily {
|
||||
return families
|
||||
}
|
||||
|
||||
func assertMetricExists(t *testing.T, name string, families []*dto.MetricFamily) {
|
||||
func assertMetricsExist(t *testing.T, families []*dto.MetricFamily, metricNames ...string) {
|
||||
t.Helper()
|
||||
if findMetricFamily(name, families) == nil {
|
||||
t.Errorf("gathered metrics do not contain %q", name)
|
||||
|
||||
for _, metricName := range metricNames {
|
||||
if findMetricFamily(metricName, families) == nil {
|
||||
t.Errorf("gathered metrics should contain %q", metricName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertMetricAbsent(t *testing.T, name string, families []*dto.MetricFamily) {
|
||||
func assertMetricsAbsent(t *testing.T, families []*dto.MetricFamily, metricNames ...string) {
|
||||
t.Helper()
|
||||
if findMetricFamily(name, families) != nil {
|
||||
t.Errorf("gathered metrics contain %q, but should not", name)
|
||||
|
||||
for _, metricName := range metricNames {
|
||||
if findMetricFamily(metricName, families) != nil {
|
||||
t.Errorf("gathered metrics should not contain %q", metricName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,6 +318,58 @@ func findMetricFamily(name string, families []*dto.MetricFamily) *dto.MetricFami
|
||||
return nil
|
||||
}
|
||||
|
||||
func findMetricByLabelNamesValues(family *dto.MetricFamily, labelNamesValues ...string) *dto.Metric {
|
||||
if family == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, metric := range family.Metric {
|
||||
if hasMetricAllLabelPairs(metric, labelNamesValues...) {
|
||||
return metric
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasMetricAllLabelPairs(metric *dto.Metric, labelNamesValues ...string) bool {
|
||||
for i := 0; i < len(labelNamesValues); i += 2 {
|
||||
name, val := labelNamesValues[i], labelNamesValues[i+1]
|
||||
if !hasMetricLabelPair(metric, name, val) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func hasMetricLabelPair(metric *dto.Metric, labelName, labelValue string) bool {
|
||||
for _, labelPair := range metric.Label {
|
||||
if labelPair.GetName() == labelName && labelPair.GetValue() == labelValue {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func assertCounterValue(t *testing.T, want float64, family *dto.MetricFamily, labelNamesValues ...string) {
|
||||
t.Helper()
|
||||
|
||||
metric := findMetricByLabelNamesValues(family, labelNamesValues...)
|
||||
|
||||
if metric == nil {
|
||||
t.Error("metric must not be nil")
|
||||
return
|
||||
}
|
||||
if metric.Counter == nil {
|
||||
t.Errorf("metric %s must be a counter", family.GetName())
|
||||
return
|
||||
}
|
||||
|
||||
if cv := metric.Counter.GetValue(); cv != want {
|
||||
t.Errorf("metric %s has value %v, want %v", family.GetName(), cv, want)
|
||||
}
|
||||
}
|
||||
|
||||
func buildCounterAssert(t *testing.T, metricName string, expectedValue int) func(family *dto.MetricFamily) {
|
||||
return func(family *dto.MetricFamily) {
|
||||
if cv := int(family.Metric[0].Counter.GetValue()); cv != expectedValue {
|
||||
|
@@ -101,19 +101,25 @@ func openAccessLogFile(filePath string) (*os.File, error) {
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// GetLogDataTable gets the request context object that contains logging data. This accretes
|
||||
// data as the request passes through the middleware chain.
|
||||
// GetLogDataTable gets the request context object that contains logging data.
|
||||
// This creates data as the request passes through the middleware chain.
|
||||
func GetLogDataTable(req *http.Request) *LogData {
|
||||
return req.Context().Value(DataTableKey).(*LogData)
|
||||
if ld, ok := req.Context().Value(DataTableKey).(*LogData); ok {
|
||||
return ld
|
||||
}
|
||||
log.Errorf("%s is nil", DataTableKey)
|
||||
return &LogData{Core: make(CoreLogData)}
|
||||
}
|
||||
|
||||
func (l *LogHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
|
||||
now := time.Now().UTC()
|
||||
core := make(CoreLogData)
|
||||
|
||||
core := CoreLogData{
|
||||
StartUTC: now,
|
||||
StartLocal: now.Local(),
|
||||
}
|
||||
|
||||
logDataTable := &LogData{Core: core, Request: req.Header}
|
||||
core[StartUTC] = now
|
||||
core[StartLocal] = now.Local()
|
||||
|
||||
reqWithDataTable := req.WithContext(context.WithValue(req.Context(), DataTableKey, logDataTable))
|
||||
|
||||
@@ -267,23 +273,26 @@ func (l *LogHandler) redactHeaders(headers http.Header, fields logrus.Fields, pr
|
||||
}
|
||||
|
||||
func (l *LogHandler) keepAccessLog(statusCode, retryAttempts int) bool {
|
||||
switch {
|
||||
case l.config.Filters == nil:
|
||||
if l.config.Filters == nil {
|
||||
// no filters were specified
|
||||
return true
|
||||
case len(l.httpCodeRanges) == 0 && l.config.Filters.RetryAttempts == false:
|
||||
}
|
||||
|
||||
if len(l.httpCodeRanges) == 0 && !l.config.Filters.RetryAttempts {
|
||||
// empty filters were specified, e.g. by passing --accessLog.filters only (without other filter options)
|
||||
return true
|
||||
case l.httpCodeRanges.Contains(statusCode):
|
||||
return true
|
||||
case l.config.Filters.RetryAttempts == true && retryAttempts > 0:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
if l.httpCodeRanges.Contains(statusCode) {
|
||||
return true
|
||||
}
|
||||
|
||||
if l.config.Filters.RetryAttempts && retryAttempts > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
var requestCounter uint64 // Request ID
|
||||
|
||||
|
@@ -3,15 +3,14 @@ package accesslog
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/urfave/negroni"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
)
|
||||
|
||||
// SaveBackend sends the backend name to the logger. These are always used with a corresponding
|
||||
// SaveFrontend handler.
|
||||
// SaveBackend sends the backend name to the logger.
|
||||
// These are always used with a corresponding SaveFrontend handler.
|
||||
type SaveBackend struct {
|
||||
next http.Handler
|
||||
backendName string
|
||||
@@ -23,63 +22,9 @@ func NewSaveBackend(next http.Handler, backendName string) http.Handler {
|
||||
}
|
||||
|
||||
func (sb *SaveBackend) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
table := GetLogDataTable(r)
|
||||
table.Core[BackendName] = sb.backendName
|
||||
table.Core[BackendURL] = r.URL // note that this is *not* the original incoming URL
|
||||
table.Core[BackendAddr] = r.URL.Host
|
||||
|
||||
crw := &captureResponseWriter{rw: rw}
|
||||
start := time.Now().UTC()
|
||||
|
||||
sb.next.ServeHTTP(crw, r)
|
||||
|
||||
// use UTC to handle switchover of daylight saving correctly
|
||||
table.Core[OriginDuration] = time.Now().UTC().Sub(start)
|
||||
table.Core[OriginStatus] = crw.Status()
|
||||
table.Core[OriginStatusLine] = fmt.Sprintf("%03d %s", crw.Status(), http.StatusText(crw.Status()))
|
||||
// make copy of headers so we can ensure there is no subsequent mutation during response processing
|
||||
table.OriginResponse = make(http.Header)
|
||||
utils.CopyHeaders(table.OriginResponse, crw.Header())
|
||||
table.Core[OriginContentSize] = crw.Size()
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
|
||||
// SaveFrontend sends the frontend name to the logger. These are sometimes used with a corresponding
|
||||
// SaveBackend handler, but not always. For example, redirected requests don't reach a backend.
|
||||
type SaveFrontend struct {
|
||||
next http.Handler
|
||||
frontendName string
|
||||
}
|
||||
|
||||
// NewSaveFrontend creates a SaveFrontend handler.
|
||||
func NewSaveFrontend(next http.Handler, frontendName string) http.Handler {
|
||||
return &SaveFrontend{next, frontendName}
|
||||
}
|
||||
|
||||
func (sb *SaveFrontend) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
table := GetLogDataTable(r)
|
||||
table.Core[FrontendName] = strings.TrimPrefix(sb.frontendName, "frontend-")
|
||||
|
||||
sb.next.ServeHTTP(rw, r)
|
||||
}
|
||||
|
||||
// SaveNegroniFrontend sends the frontend name to the logger.
|
||||
type SaveNegroniFrontend struct {
|
||||
next negroni.Handler
|
||||
frontendName string
|
||||
}
|
||||
|
||||
// NewSaveNegroniFrontend creates a SaveNegroniFrontend handler.
|
||||
func NewSaveNegroniFrontend(next negroni.Handler, frontendName string) negroni.Handler {
|
||||
return &SaveNegroniFrontend{next, frontendName}
|
||||
}
|
||||
|
||||
func (sb *SaveNegroniFrontend) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
table := GetLogDataTable(r)
|
||||
table.Core[FrontendName] = strings.TrimPrefix(sb.frontendName, "frontend-")
|
||||
|
||||
sb.next.ServeHTTP(rw, r, next)
|
||||
serveSaveBackend(rw, r, sb.backendName, func(crw *captureResponseWriter) {
|
||||
sb.next.ServeHTTP(crw, r)
|
||||
})
|
||||
}
|
||||
|
||||
// SaveNegroniBackend sends the backend name to the logger.
|
||||
@@ -94,13 +39,21 @@ func NewSaveNegroniBackend(next negroni.Handler, backendName string) negroni.Han
|
||||
}
|
||||
|
||||
func (sb *SaveNegroniBackend) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
serveSaveBackend(rw, r, sb.backendName, func(crw *captureResponseWriter) {
|
||||
sb.next.ServeHTTP(crw, r, next)
|
||||
})
|
||||
}
|
||||
|
||||
func serveSaveBackend(rw http.ResponseWriter, r *http.Request, backendName string, apply func(*captureResponseWriter)) {
|
||||
table := GetLogDataTable(r)
|
||||
table.Core[BackendName] = sb.backendName
|
||||
table.Core[BackendName] = backendName
|
||||
table.Core[BackendURL] = r.URL // note that this is *not* the original incoming URL
|
||||
table.Core[BackendAddr] = r.URL.Host
|
||||
|
||||
crw := &captureResponseWriter{rw: rw}
|
||||
start := time.Now().UTC()
|
||||
|
||||
sb.next.ServeHTTP(crw, r, next)
|
||||
apply(crw)
|
||||
|
||||
// use UTC to handle switchover of daylight saving correctly
|
||||
table.Core[OriginDuration] = time.Now().UTC().Sub(start)
|
||||
|
51
middlewares/accesslog/save_frontend.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package accesslog
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/negroni"
|
||||
)
|
||||
|
||||
// SaveFrontend sends the frontend name to the logger.
|
||||
// These are sometimes used with a corresponding SaveBackend handler, but not always.
|
||||
// For example, redirected requests don't reach a backend.
|
||||
type SaveFrontend struct {
|
||||
next http.Handler
|
||||
frontendName string
|
||||
}
|
||||
|
||||
// NewSaveFrontend creates a SaveFrontend handler.
|
||||
func NewSaveFrontend(next http.Handler, frontendName string) http.Handler {
|
||||
return &SaveFrontend{next, frontendName}
|
||||
}
|
||||
|
||||
func (sf *SaveFrontend) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
serveSaveFrontend(r, sf.frontendName, func() {
|
||||
sf.next.ServeHTTP(rw, r)
|
||||
})
|
||||
}
|
||||
|
||||
// SaveNegroniFrontend sends the frontend name to the logger.
|
||||
type SaveNegroniFrontend struct {
|
||||
next negroni.Handler
|
||||
frontendName string
|
||||
}
|
||||
|
||||
// NewSaveNegroniFrontend creates a SaveNegroniFrontend handler.
|
||||
func NewSaveNegroniFrontend(next negroni.Handler, frontendName string) negroni.Handler {
|
||||
return &SaveNegroniFrontend{next, frontendName}
|
||||
}
|
||||
|
||||
func (sf *SaveNegroniFrontend) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
serveSaveFrontend(r, sf.frontendName, func() {
|
||||
sf.next.ServeHTTP(rw, r, next)
|
||||
})
|
||||
}
|
||||
|
||||
func serveSaveFrontend(r *http.Request, frontendName string, apply func()) {
|
||||
table := GetLogDataTable(r)
|
||||
table.Core[FrontendName] = strings.TrimPrefix(frontendName, "frontend-")
|
||||
|
||||
apply()
|
||||
}
|
@@ -25,6 +25,7 @@ func Forward(config *types.Forward, w http.ResponseWriter, r *http.Request, next
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
|
||||
if config.TLS != nil {
|
||||
tlsConfig, err := config.TLS.CreateTLSConfig()
|
||||
if err != nil {
|
||||
@@ -32,10 +33,12 @@ func Forward(config *types.Forward, w http.ResponseWriter, r *http.Request, next
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
httpClient.Transport = &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
}
|
||||
|
||||
forwardReq, err := http.NewRequest(http.MethodGet, config.Address, nil)
|
||||
tracing.LogRequest(tracing.GetSpan(r), forwardReq)
|
||||
if err != nil {
|
||||
@@ -68,6 +71,8 @@ func Forward(config *types.Forward, w http.ResponseWriter, r *http.Request, next
|
||||
if forwardResponse.StatusCode < http.StatusOK || forwardResponse.StatusCode >= http.StatusMultipleChoices {
|
||||
log.Debugf("Remote error %s. StatusCode: %d", config.Address, forwardResponse.StatusCode)
|
||||
|
||||
utils.CopyHeaders(w.Header(), forwardResponse.Header)
|
||||
|
||||
// Grab the location header, if any.
|
||||
redirectURL, err := forwardResponse.Location()
|
||||
|
||||
@@ -79,12 +84,7 @@ func Forward(config *types.Forward, w http.ResponseWriter, r *http.Request, next
|
||||
}
|
||||
} else if redirectURL.String() != "" {
|
||||
// Set the location in our response if one was sent back.
|
||||
w.Header().Add("Location", redirectURL.String())
|
||||
}
|
||||
|
||||
// Pass any Set-Cookie headers the forward auth server provides
|
||||
for _, cookie := range forwardResponse.Cookies() {
|
||||
w.Header().Add("Set-Cookie", cookie.String())
|
||||
w.Header().Set("Location", redirectURL.String())
|
||||
}
|
||||
|
||||
tracing.LogResponseCode(tracing.GetSpan(r), forwardResponse.StatusCode)
|
||||
|
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/containous/traefik/testhelpers"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/negroni"
|
||||
)
|
||||
|
||||
@@ -110,7 +111,6 @@ func TestForwardAuthRedirect(t *testing.T) {
|
||||
assert.Equal(t, http.StatusFound, res.StatusCode, "they should be equal")
|
||||
|
||||
location, err := res.Location()
|
||||
|
||||
assert.NoError(t, err, "there should be no error")
|
||||
assert.Equal(t, "http://example.com/redirect-test", location.String(), "they should be equal")
|
||||
|
||||
@@ -119,10 +119,11 @@ func TestForwardAuthRedirect(t *testing.T) {
|
||||
assert.NotEmpty(t, string(body), "there should be something in the body")
|
||||
}
|
||||
|
||||
func TestForwardAuthCookie(t *testing.T) {
|
||||
func TestForwardAuthFailResponseHeaders(t *testing.T) {
|
||||
authTs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
cookie := &http.Cookie{Name: "example", Value: "testing", Path: "/"}
|
||||
http.SetCookie(w, cookie)
|
||||
w.Header().Add("X-Foo", "bar")
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
}))
|
||||
defer authTs.Close()
|
||||
@@ -142,23 +143,36 @@ func TestForwardAuthCookie(t *testing.T) {
|
||||
ts := httptest.NewServer(n)
|
||||
defer ts.Close()
|
||||
|
||||
client := &http.Client{}
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
|
||||
client := &http.Client{}
|
||||
res, err := client.Do(req)
|
||||
assert.NoError(t, err, "there should be no error")
|
||||
assert.Equal(t, http.StatusForbidden, res.StatusCode, "they should be equal")
|
||||
|
||||
require.Len(t, res.Cookies(), 1)
|
||||
for _, cookie := range res.Cookies() {
|
||||
assert.Equal(t, "testing", cookie.Value, "they should be equal")
|
||||
}
|
||||
|
||||
expectedHeaders := http.Header{
|
||||
"Content-Length": []string{"10"},
|
||||
"Content-Type": []string{"text/plain; charset=utf-8"},
|
||||
"X-Foo": []string{"bar"},
|
||||
"Set-Cookie": []string{"example=testing; Path=/"},
|
||||
"X-Content-Type-Options": []string{"nosniff"},
|
||||
}
|
||||
|
||||
assert.Len(t, res.Header, 6)
|
||||
for key, value := range expectedHeaders {
|
||||
assert.Equal(t, value, res.Header[key])
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
assert.NoError(t, err, "there should be no error")
|
||||
assert.Equal(t, "Forbidden\n", string(body), "they should be equal")
|
||||
}
|
||||
|
||||
func Test_writeHeader(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
headers map[string]string
|
||||
|
@@ -3,15 +3,17 @@ package errorpages
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/middlewares"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/vulcand/oxy/forward"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
)
|
||||
@@ -75,8 +77,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.
|
||||
recorder := newResponseRecorder(w)
|
||||
next.ServeHTTP(recorder, req)
|
||||
|
||||
w.WriteHeader(recorder.GetCode())
|
||||
|
||||
// check the recorder code against the configured http status code ranges
|
||||
for _, block := range h.httpCodeRanges {
|
||||
if recorder.GetCode() >= block[0] && recorder.GetCode() <= block[1] {
|
||||
@@ -88,20 +88,47 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.
|
||||
query = strings.Replace(query, "{status}", strconv.Itoa(recorder.GetCode()), -1)
|
||||
}
|
||||
|
||||
if newReq, err := http.NewRequest(http.MethodGet, h.backendURL+query, nil); err != nil {
|
||||
w.Write([]byte(http.StatusText(recorder.GetCode())))
|
||||
} else {
|
||||
h.backendHandler.ServeHTTP(w, newReq)
|
||||
pageReq, err := newRequest(h.backendURL + query)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
w.WriteHeader(recorder.GetCode())
|
||||
fmt.Fprint(w, http.StatusText(recorder.GetCode()))
|
||||
return
|
||||
}
|
||||
|
||||
recorderErrorPage := newResponseRecorder(w)
|
||||
utils.CopyHeaders(pageReq.Header, req.Header)
|
||||
|
||||
h.backendHandler.ServeHTTP(recorderErrorPage, pageReq.WithContext(req.Context()))
|
||||
|
||||
utils.CopyHeaders(w.Header(), recorderErrorPage.Header())
|
||||
w.WriteHeader(recorder.GetCode())
|
||||
w.Write(recorderErrorPage.GetBody().Bytes())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// did not catch a configured status code so proceed with the request
|
||||
utils.CopyHeaders(w.Header(), recorder.Header())
|
||||
w.WriteHeader(recorder.GetCode())
|
||||
w.Write(recorder.GetBody().Bytes())
|
||||
}
|
||||
|
||||
func newRequest(baseURL string) (*http.Request, error) {
|
||||
u, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error pages: error when parse URL: %v", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error pages: error when create query: %v", err)
|
||||
}
|
||||
|
||||
req.RequestURI = u.RequestURI()
|
||||
return req, nil
|
||||
}
|
||||
|
||||
type responseRecorder interface {
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
@@ -142,64 +169,65 @@ type responseRecorderWithCloseNotify struct {
|
||||
|
||||
// CloseNotify returns a channel that receives at most a
|
||||
// single value (true) when the client connection has gone away.
|
||||
func (rw *responseRecorderWithCloseNotify) CloseNotify() <-chan bool {
|
||||
return rw.responseWriter.(http.CloseNotifier).CloseNotify()
|
||||
func (r *responseRecorderWithCloseNotify) CloseNotify() <-chan bool {
|
||||
return r.responseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
// Header returns the response headers.
|
||||
func (rw *responseRecorderWithoutCloseNotify) Header() http.Header {
|
||||
if rw.HeaderMap == nil {
|
||||
rw.HeaderMap = make(http.Header)
|
||||
func (r *responseRecorderWithoutCloseNotify) Header() http.Header {
|
||||
if r.HeaderMap == nil {
|
||||
r.HeaderMap = make(http.Header)
|
||||
}
|
||||
return rw.HeaderMap
|
||||
|
||||
return r.HeaderMap
|
||||
}
|
||||
|
||||
func (rw *responseRecorderWithoutCloseNotify) GetCode() int {
|
||||
return rw.Code
|
||||
func (r *responseRecorderWithoutCloseNotify) GetCode() int {
|
||||
return r.Code
|
||||
}
|
||||
|
||||
func (rw *responseRecorderWithoutCloseNotify) GetBody() *bytes.Buffer {
|
||||
return rw.Body
|
||||
func (r *responseRecorderWithoutCloseNotify) GetBody() *bytes.Buffer {
|
||||
return r.Body
|
||||
}
|
||||
|
||||
func (rw *responseRecorderWithoutCloseNotify) IsStreamingResponseStarted() bool {
|
||||
return rw.streamingResponseStarted
|
||||
func (r *responseRecorderWithoutCloseNotify) IsStreamingResponseStarted() bool {
|
||||
return r.streamingResponseStarted
|
||||
}
|
||||
|
||||
// Write always succeeds and writes to rw.Body, if not nil.
|
||||
func (rw *responseRecorderWithoutCloseNotify) Write(buf []byte) (int, error) {
|
||||
if rw.err != nil {
|
||||
return 0, rw.err
|
||||
func (r *responseRecorderWithoutCloseNotify) Write(buf []byte) (int, error) {
|
||||
if r.err != nil {
|
||||
return 0, r.err
|
||||
}
|
||||
return rw.Body.Write(buf)
|
||||
return r.Body.Write(buf)
|
||||
}
|
||||
|
||||
// WriteHeader sets rw.Code.
|
||||
func (rw *responseRecorderWithoutCloseNotify) WriteHeader(code int) {
|
||||
rw.Code = code
|
||||
func (r *responseRecorderWithoutCloseNotify) WriteHeader(code int) {
|
||||
r.Code = code
|
||||
}
|
||||
|
||||
// Hijack hijacks the connection
|
||||
func (rw *responseRecorderWithoutCloseNotify) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return rw.responseWriter.(http.Hijacker).Hijack()
|
||||
func (r *responseRecorderWithoutCloseNotify) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return r.responseWriter.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
// Flush sends any buffered data to the client.
|
||||
func (rw *responseRecorderWithoutCloseNotify) Flush() {
|
||||
if !rw.streamingResponseStarted {
|
||||
utils.CopyHeaders(rw.responseWriter.Header(), rw.Header())
|
||||
rw.responseWriter.WriteHeader(rw.Code)
|
||||
rw.streamingResponseStarted = true
|
||||
func (r *responseRecorderWithoutCloseNotify) Flush() {
|
||||
if !r.streamingResponseStarted {
|
||||
utils.CopyHeaders(r.responseWriter.Header(), r.Header())
|
||||
r.responseWriter.WriteHeader(r.Code)
|
||||
r.streamingResponseStarted = true
|
||||
}
|
||||
|
||||
_, err := rw.responseWriter.Write(rw.Body.Bytes())
|
||||
_, err := r.responseWriter.Write(r.Body.Bytes())
|
||||
if err != nil {
|
||||
log.Errorf("Error writing response in responseRecorder: %s", err)
|
||||
rw.err = err
|
||||
log.Errorf("Error writing response in responseRecorder: %v", err)
|
||||
r.err = err
|
||||
}
|
||||
rw.Body.Reset()
|
||||
r.Body.Reset()
|
||||
|
||||
if flusher, ok := rw.responseWriter.(http.Flusher); ok {
|
||||
if flusher, ok := r.responseWriter.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
|
@@ -65,7 +65,7 @@ func TestHandler(t *testing.T) {
|
||||
errorPage: &types.ErrorPage{Backend: "error", Query: "/{status}", Status: []string{"503-503"}},
|
||||
backendCode: http.StatusServiceUnavailable,
|
||||
backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.RequestURI() == "/"+strconv.Itoa(503) {
|
||||
if r.RequestURI == "/503" {
|
||||
fmt.Fprintln(w, "My 503 page.")
|
||||
} else {
|
||||
fmt.Fprintln(w, "Failed")
|
||||
@@ -82,7 +82,7 @@ func TestHandler(t *testing.T) {
|
||||
errorPage: &types.ErrorPage{Backend: "error", Query: "/{status}", Status: []string{"503"}},
|
||||
backendCode: http.StatusServiceUnavailable,
|
||||
backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.RequestURI() == "/"+strconv.Itoa(503) {
|
||||
if r.RequestURI == "/503" {
|
||||
fmt.Fprintln(w, "My 503 page.")
|
||||
} else {
|
||||
fmt.Fprintln(w, "Failed")
|
||||
@@ -239,7 +239,7 @@ func TestHandlerOldWay(t *testing.T) {
|
||||
|
||||
func TestHandlerOldWayIntegration(t *testing.T) {
|
||||
errorPagesServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.RequestURI() == "/"+strconv.Itoa(503) {
|
||||
if r.URL.RequestURI() == "/503" {
|
||||
fmt.Fprintln(w, "My 503 page.")
|
||||
} else {
|
||||
fmt.Fprintln(w, "Test Server")
|
||||
|
@@ -38,21 +38,15 @@ func NewIPWhiteLister(whiteList []string, useXForwardedFor bool) (*IPWhiteLister
|
||||
}
|
||||
|
||||
func (wl *IPWhiteLister) handle(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
allowed, ip, err := wl.whiteLister.IsAuthorized(r)
|
||||
err := wl.whiteLister.IsAuthorized(r)
|
||||
if err != nil {
|
||||
tracing.SetErrorAndDebugLog(r, "request %+v matched none of the white list - rejecting", r)
|
||||
tracing.SetErrorAndDebugLog(r, "request %+v - rejecting: %v", r, err)
|
||||
reject(w)
|
||||
return
|
||||
}
|
||||
|
||||
if allowed {
|
||||
tracing.SetErrorAndDebugLog(r, "request %+v matched white list %s - passing", r, wl.whiteLister)
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
tracing.SetErrorAndDebugLog(r, "source-IP %s matched none of the white list - rejecting", ip)
|
||||
reject(w)
|
||||
tracing.SetErrorAndDebugLog(r, "request %+v matched white list %s - passing", r, wl.whiteLister)
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (wl *IPWhiteLister) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
@@ -63,5 +57,8 @@ func reject(w http.ResponseWriter) {
|
||||
statusCode := http.StatusForbidden
|
||||
|
||||
w.WriteHeader(statusCode)
|
||||
w.Write([]byte(http.StatusText(statusCode)))
|
||||
_, err := w.Write([]byte(http.StatusText(statusCode)))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
@@ -88,6 +88,13 @@ func TestIPWhiteLister_ServeHTTP(t *testing.T) {
|
||||
xForwardedFor: []string{"30.30.30.30", "40.40.40.40"},
|
||||
expected: 200,
|
||||
},
|
||||
{
|
||||
desc: "authorized with only one X-Forwarded-For",
|
||||
whiteList: []string{"30.30.30.30"},
|
||||
useXForwardedFor: true,
|
||||
xForwardedFor: []string{"30.30.30.30"},
|
||||
expected: 200,
|
||||
},
|
||||
{
|
||||
desc: "non authorized with X-Forwarded-For",
|
||||
whiteList: []string{"30.30.30.30"},
|
||||
|
62
middlewares/pipelining/pipelining.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package pipelining
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Pipelining returns a middleware
|
||||
type Pipelining struct {
|
||||
next http.Handler
|
||||
}
|
||||
|
||||
// NewPipelining returns a new Pipelining instance
|
||||
func NewPipelining(next http.Handler) *Pipelining {
|
||||
return &Pipelining{
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pipelining) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
// https://github.com/golang/go/blob/3d59583836630cf13ec4bfbed977d27b1b7adbdc/src/net/http/server.go#L201-L218
|
||||
if r.Method == http.MethodPut || r.Method == http.MethodPost {
|
||||
p.next.ServeHTTP(rw, r)
|
||||
} else {
|
||||
p.next.ServeHTTP(&writerWithoutCloseNotify{rw}, r)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// writerWithoutCloseNotify helps to disable closeNotify
|
||||
type writerWithoutCloseNotify struct {
|
||||
W http.ResponseWriter
|
||||
}
|
||||
|
||||
// Header returns the response headers.
|
||||
func (w *writerWithoutCloseNotify) Header() http.Header {
|
||||
return w.W.Header()
|
||||
}
|
||||
|
||||
// Write writes the data to the connection as part of an HTTP reply.
|
||||
func (w *writerWithoutCloseNotify) Write(buf []byte) (int, error) {
|
||||
return w.W.Write(buf)
|
||||
}
|
||||
|
||||
// WriteHeader sends an HTTP response header with the provided
|
||||
// status code.
|
||||
func (w *writerWithoutCloseNotify) WriteHeader(code int) {
|
||||
w.W.WriteHeader(code)
|
||||
}
|
||||
|
||||
// Flush sends any buffered data to the client.
|
||||
func (w *writerWithoutCloseNotify) Flush() {
|
||||
if f, ok := w.W.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// Hijack hijacks the connection.
|
||||
func (w *writerWithoutCloseNotify) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return w.W.(http.Hijacker).Hijack()
|
||||
}
|
69
middlewares/pipelining/pipelining_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package pipelining
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type recorderWithCloseNotify struct {
|
||||
*httptest.ResponseRecorder
|
||||
}
|
||||
|
||||
func (r *recorderWithCloseNotify) CloseNotify() <-chan bool {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func TestNewPipelining(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
HTTPMethod string
|
||||
implementCloseNotifier bool
|
||||
}{
|
||||
{
|
||||
desc: "should not implement CloseNotifier with GET method",
|
||||
HTTPMethod: http.MethodGet,
|
||||
implementCloseNotifier: false,
|
||||
},
|
||||
{
|
||||
desc: "should implement CloseNotifier with PUT method",
|
||||
HTTPMethod: http.MethodPut,
|
||||
implementCloseNotifier: true,
|
||||
},
|
||||
{
|
||||
desc: "should implement CloseNotifier with POST method",
|
||||
HTTPMethod: http.MethodPost,
|
||||
implementCloseNotifier: true,
|
||||
},
|
||||
{
|
||||
desc: "should not implement CloseNotifier with GET method",
|
||||
HTTPMethod: http.MethodHead,
|
||||
implementCloseNotifier: false,
|
||||
},
|
||||
{
|
||||
desc: "should not implement CloseNotifier with PROPFIND method",
|
||||
HTTPMethod: "PROPFIND",
|
||||
implementCloseNotifier: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, ok := w.(http.CloseNotifier)
|
||||
assert.Equal(t, test.implementCloseNotifier, ok)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
handler := NewPipelining(nextHandler)
|
||||
|
||||
req := httptest.NewRequest(test.HTTPMethod, "http://localhost", nil)
|
||||
|
||||
handler.ServeHTTP(&recorderWithCloseNotify{httptest.NewRecorder()}, req)
|
||||
})
|
||||
}
|
||||
}
|
@@ -33,7 +33,7 @@ func (f *forwarderMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request,
|
||||
span.SetTag("frontend.name", f.frontend)
|
||||
span.SetTag("backend.name", f.backend)
|
||||
ext.HTTPMethod.Set(span, r.Method)
|
||||
ext.HTTPUrl.Set(span, r.URL.String())
|
||||
ext.HTTPUrl.Set(span, fmt.Sprintf("%s%s", r.URL.String(), r.RequestURI))
|
||||
span.SetTag("http.host", r.Host)
|
||||
|
||||
InjectRequestHeaders(r)
|
||||
|
@@ -73,7 +73,10 @@ func (t *Tracing) IsEnabled() bool {
|
||||
// Close tracer
|
||||
func (t *Tracing) Close() {
|
||||
if t.closer != nil {
|
||||
t.closer.Close()
|
||||
err := t.closer.Close()
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,10 +107,13 @@ func GetSpan(r *http.Request) opentracing.Span {
|
||||
// InjectRequestHeaders used to inject OpenTracing headers into the request
|
||||
func InjectRequestHeaders(r *http.Request) {
|
||||
if span := GetSpan(r); span != nil {
|
||||
opentracing.GlobalTracer().Inject(
|
||||
err := opentracing.GlobalTracer().Inject(
|
||||
span.Context(),
|
||||
opentracing.HTTPHeaders,
|
||||
opentracing.HTTPHeadersCarrier(r.Header))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
44
mkdocs.yml
@@ -16,14 +16,11 @@ theme:
|
||||
include_sidebar: true
|
||||
favicon: img/traefik.icon.png
|
||||
logo: img/traefik.logo.png
|
||||
palette:
|
||||
primary: 'blue'
|
||||
accent: 'light blue'
|
||||
feature:
|
||||
tabs: false
|
||||
palette:
|
||||
primary: 'cyan'
|
||||
accent: 'cyan'
|
||||
feature:
|
||||
tabs: false
|
||||
i18n:
|
||||
prev: 'Previous'
|
||||
next: 'Next'
|
||||
@@ -45,7 +42,7 @@ google_analytics:
|
||||
# - type: 'slack'
|
||||
# link: 'https://traefik.herokuapp.com'
|
||||
# - type: 'twitter'
|
||||
# link: 'https://twitter.com/traefikproxy'
|
||||
# link: 'https://twitter.com/traefik'
|
||||
|
||||
extra_css:
|
||||
- theme/styles/extra.css
|
||||
@@ -69,27 +66,27 @@ pages:
|
||||
- 'Logs': 'configuration/logs.md'
|
||||
- 'EntryPoints': 'configuration/entrypoints.md'
|
||||
- 'Let''s Encrypt': 'configuration/acme.md'
|
||||
- 'Backend: Web': 'configuration/backends/web.md'
|
||||
- 'Backend: BoltDB': 'configuration/backends/boltdb.md'
|
||||
- 'Backend: Consul': 'configuration/backends/consul.md'
|
||||
- 'Backend: Consul Catalog': 'configuration/backends/consulcatalog.md'
|
||||
- 'Backend: Docker': 'configuration/backends/docker.md'
|
||||
- 'Backend: DynamoDB': 'configuration/backends/dynamodb.md'
|
||||
- 'Backend: ECS': 'configuration/backends/ecs.md'
|
||||
- 'Backend: Etcd': 'configuration/backends/etcd.md'
|
||||
- 'Backend: Eureka': 'configuration/backends/eureka.md'
|
||||
- 'Backend: File': 'configuration/backends/file.md'
|
||||
- 'Backend: Kubernetes Ingress': 'configuration/backends/kubernetes.md'
|
||||
- 'Backend: Marathon': 'configuration/backends/marathon.md'
|
||||
- 'Backend: Mesos': 'configuration/backends/mesos.md'
|
||||
- 'Backend: Rancher': 'configuration/backends/rancher.md'
|
||||
- 'Backend: Rest': 'configuration/backends/rest.md'
|
||||
- 'Backend: Azure Service Fabric': 'configuration/backends/servicefabric.md'
|
||||
- 'Backend: Zookeeper': 'configuration/backends/zookeeper.md'
|
||||
- 'API / Dashboard': 'configuration/api.md'
|
||||
- 'BoltDB': 'configuration/backends/boltdb.md'
|
||||
- 'Consul': 'configuration/backends/consul.md'
|
||||
- 'Consul Catalog': 'configuration/backends/consulcatalog.md'
|
||||
- 'Docker': 'configuration/backends/docker.md'
|
||||
- 'DynamoDB': 'configuration/backends/dynamodb.md'
|
||||
- 'ECS': 'configuration/backends/ecs.md'
|
||||
- 'Etcd': 'configuration/backends/etcd.md'
|
||||
- 'Eureka': 'configuration/backends/eureka.md'
|
||||
- 'File': 'configuration/backends/file.md'
|
||||
- 'Kubernetes Ingress': 'configuration/backends/kubernetes.md'
|
||||
- 'Marathon': 'configuration/backends/marathon.md'
|
||||
- 'Mesos': 'configuration/backends/mesos.md'
|
||||
- 'Rancher': 'configuration/backends/rancher.md'
|
||||
- 'Rest': 'configuration/backends/rest.md'
|
||||
- 'Azure Service Fabric': 'configuration/backends/servicefabric.md'
|
||||
- 'Zookeeper': 'configuration/backends/zookeeper.md'
|
||||
- 'Ping': 'configuration/ping.md'
|
||||
- 'Metrics': 'configuration/metrics.md'
|
||||
- 'Tracing': 'configuration/tracing.md'
|
||||
- 'Web (Deprecated)': 'configuration/backends/web.md'
|
||||
- User Guides:
|
||||
- 'Configuration Examples': 'user-guide/examples.md'
|
||||
- 'Swarm Mode Cluster': 'user-guide/swarm-mode.md'
|
||||
@@ -101,4 +98,3 @@ pages:
|
||||
- 'Clustering/HA': 'user-guide/cluster.md'
|
||||
- 'gRPC Example': 'user-guide/grpc.md'
|
||||
- 'Traefik cluster example with Swarm': 'user-guide/cluster-docker-consul.md'
|
||||
- Benchmarks: benchmarks.md
|
||||
|
@@ -7,7 +7,7 @@ import (
|
||||
"crypto/x509"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
acme "github.com/xenolf/lego/acmev2"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
// Account is used to store lets encrypt registration info
|
||||
@@ -19,7 +19,7 @@ type Account struct {
|
||||
|
||||
const (
|
||||
// RegistrationURLPathV1Regexp is a regexp which match ACME registration URL in the V1 format
|
||||
RegistrationURLPathV1Regexp string = `^.*/acme/reg/\d+$`
|
||||
RegistrationURLPathV1Regexp = `^.*/acme/reg/\d+$`
|
||||
)
|
||||
|
||||
// NewAccount creates an account
|
||||
|
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
acme "github.com/xenolf/lego/acmev2"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
func dnsOverrideDelay(delay flaeg.Duration) error {
|
||||
@@ -34,15 +34,9 @@ func getTokenValue(token, domain string, store Store) []byte {
|
||||
var result []byte
|
||||
|
||||
operation := func() error {
|
||||
var ok bool
|
||||
httpChallenges, err := store.GetHTTPChallenges()
|
||||
if err != nil {
|
||||
return fmt.Errorf("HTTPChallenges not available : %s", err)
|
||||
}
|
||||
if result, ok = httpChallenges[token][domain]; !ok {
|
||||
return fmt.Errorf("cannot find challenge for token %v", token)
|
||||
}
|
||||
return nil
|
||||
var err error
|
||||
result, err = store.GetHTTPChallengeToken(token, domain)
|
||||
return err
|
||||
}
|
||||
|
||||
notify := func(err error, time time.Duration) {
|
||||
@@ -60,40 +54,9 @@ func getTokenValue(token, domain string, store Store) []byte {
|
||||
}
|
||||
|
||||
func presentHTTPChallenge(domain, token, keyAuth string, store Store) error {
|
||||
httpChallenges, err := store.GetHTTPChallenges()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get HTTPChallenges : %s", err)
|
||||
}
|
||||
|
||||
if httpChallenges == nil {
|
||||
httpChallenges = map[string]map[string][]byte{}
|
||||
}
|
||||
|
||||
if _, ok := httpChallenges[token]; !ok {
|
||||
httpChallenges[token] = map[string][]byte{}
|
||||
}
|
||||
|
||||
httpChallenges[token][domain] = []byte(keyAuth)
|
||||
|
||||
return store.SaveHTTPChallenges(httpChallenges)
|
||||
return store.SetHTTPChallengeToken(token, domain, []byte(keyAuth))
|
||||
}
|
||||
|
||||
func cleanUpHTTPChallenge(domain, token string, store Store) error {
|
||||
httpChallenges, err := store.GetHTTPChallenges()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get HTTPChallenges : %s", err)
|
||||
}
|
||||
|
||||
log.Debugf("Challenge CleanUp for domain %s", domain)
|
||||
|
||||
if _, ok := httpChallenges[token]; ok {
|
||||
if _, domainOk := httpChallenges[token][domain]; domainOk {
|
||||
delete(httpChallenges[token], domain)
|
||||
}
|
||||
if len(httpChallenges[token]) == 0 {
|
||||
delete(httpChallenges, token)
|
||||
}
|
||||
return store.SaveHTTPChallenges(httpChallenges)
|
||||
}
|
||||
return nil
|
||||
return store.RemoveHTTPChallengeToken(token, domain)
|
||||
}
|
||||
|
@@ -2,9 +2,11 @@ package acme
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
"sync"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
@@ -17,11 +19,12 @@ type LocalStore struct {
|
||||
filename string
|
||||
storedData *StoredData
|
||||
SaveDataChan chan *StoredData `json:"-"`
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// NewLocalStore initializes a new LocalStore with a file name
|
||||
func NewLocalStore(filename string) LocalStore {
|
||||
store := LocalStore{filename: filename, SaveDataChan: make(chan *StoredData)}
|
||||
func NewLocalStore(filename string) *LocalStore {
|
||||
store := &LocalStore{filename: filename, SaveDataChan: make(chan *StoredData)}
|
||||
store.listenSaveAction()
|
||||
return store
|
||||
}
|
||||
@@ -60,6 +63,7 @@ func (s *LocalStore) get() (*StoredData, error) {
|
||||
return nil, err
|
||||
}
|
||||
if isOldRegistration {
|
||||
log.Debug("Reset ACME account.")
|
||||
s.storedData.Account = nil
|
||||
s.SaveDataChan <- s.storedData
|
||||
}
|
||||
@@ -148,13 +152,59 @@ func (s *LocalStore) SaveCertificates(certificates []*Certificate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetHTTPChallenges returns ACME HTTP Challenges list
|
||||
func (s *LocalStore) GetHTTPChallenges() (map[string]map[string][]byte, error) {
|
||||
return s.storedData.HTTPChallenges, nil
|
||||
// GetHTTPChallengeToken Get the http challenge token from the store
|
||||
func (s *LocalStore) GetHTTPChallengeToken(token, domain string) ([]byte, error) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
if s.storedData.HTTPChallenges == nil {
|
||||
s.storedData.HTTPChallenges = map[string]map[string][]byte{}
|
||||
}
|
||||
|
||||
if _, ok := s.storedData.HTTPChallenges[token]; !ok {
|
||||
return nil, fmt.Errorf("cannot find challenge for token %v", token)
|
||||
}
|
||||
|
||||
result, ok := s.storedData.HTTPChallenges[token][domain]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot find challenge for token %v", token)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// SaveHTTPChallenges stores ACME HTTP Challenges list
|
||||
func (s *LocalStore) SaveHTTPChallenges(httpChallenges map[string]map[string][]byte) error {
|
||||
s.storedData.HTTPChallenges = httpChallenges
|
||||
// SetHTTPChallengeToken Set the http challenge token in the store
|
||||
func (s *LocalStore) SetHTTPChallengeToken(token, domain string, keyAuth []byte) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if s.storedData.HTTPChallenges == nil {
|
||||
s.storedData.HTTPChallenges = map[string]map[string][]byte{}
|
||||
}
|
||||
|
||||
if _, ok := s.storedData.HTTPChallenges[token]; !ok {
|
||||
s.storedData.HTTPChallenges[token] = map[string][]byte{}
|
||||
}
|
||||
|
||||
s.storedData.HTTPChallenges[token][domain] = []byte(keyAuth)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveHTTPChallengeToken Remove the http challenge token in the store
|
||||
func (s *LocalStore) RemoveHTTPChallengeToken(token, domain string) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if s.storedData.HTTPChallenges == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, ok := s.storedData.HTTPChallenges[token]; ok {
|
||||
if _, domainOk := s.storedData.HTTPChallenges[token][domain]; domainOk {
|
||||
delete(s.storedData.HTTPChallenges[token], domain)
|
||||
}
|
||||
if len(s.storedData.HTTPChallenges[token]) == 0 {
|
||||
delete(s.storedData.HTTPChallenges, token)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -8,7 +8,6 @@ import (
|
||||
fmtlog "log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -22,8 +21,11 @@ import (
|
||||
"github.com/containous/traefik/safe"
|
||||
traefikTLS "github.com/containous/traefik/tls"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/containous/traefik/version"
|
||||
"github.com/pkg/errors"
|
||||
acme "github.com/xenolf/lego/acmev2"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/xenolf/lego/acme"
|
||||
legolog "github.com/xenolf/lego/log"
|
||||
"github.com/xenolf/lego/providers/dns"
|
||||
)
|
||||
|
||||
@@ -61,6 +63,8 @@ type Provider struct {
|
||||
clientMutex sync.Mutex
|
||||
configFromListenerChan chan types.Configuration
|
||||
pool *safe.Pool
|
||||
resolvingDomains map[string]struct{}
|
||||
resolvingDomainsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
// Certificate is a struct which contains all data needed from an ACME certificate
|
||||
@@ -97,10 +101,11 @@ func (p *Provider) SetConfigListenerChan(configFromListenerChan chan types.Confi
|
||||
}
|
||||
|
||||
func (p *Provider) init() error {
|
||||
acme.UserAgent = fmt.Sprintf("containous-traefik/%s", version.Version)
|
||||
if p.ACMELogging {
|
||||
acme.Logger = fmtlog.New(os.Stderr, "legolog: ", fmtlog.LstdFlags)
|
||||
legolog.Logger = fmtlog.New(log.WriterLevel(logrus.DebugLevel), "legolog: ", 0)
|
||||
} else {
|
||||
acme.Logger = fmtlog.New(ioutil.Discard, "", 0)
|
||||
legolog.Logger = fmtlog.New(ioutil.Discard, "", 0)
|
||||
}
|
||||
|
||||
var err error
|
||||
@@ -114,11 +119,19 @@ func (p *Provider) init() error {
|
||||
return fmt.Errorf("unable to get ACME account : %v", err)
|
||||
}
|
||||
|
||||
// Reset Account if caServer changed, thus registration URI can be updated
|
||||
if p.account != nil && p.account.Registration != nil && !strings.HasPrefix(p.account.Registration.URI, p.CAServer) {
|
||||
p.account = nil
|
||||
}
|
||||
|
||||
p.certificates, err = p.Store.GetCertificates()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get ACME certificates : %v", err)
|
||||
}
|
||||
|
||||
// Init the currently resolved domain map
|
||||
p.resolvingDomains = make(map[string]struct{})
|
||||
|
||||
p.watchCertificate()
|
||||
p.watchNewDomains()
|
||||
|
||||
@@ -168,7 +181,7 @@ func (p *Provider) watchNewDomains() {
|
||||
}
|
||||
|
||||
if len(domains) == 0 {
|
||||
log.Debugf("No domain parsed in rule %q", route.Rule)
|
||||
log.Debugf("No domain parsed in rule %q in provider ACME", route.Rule)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -218,6 +231,9 @@ func (p *Provider) resolveCertificate(domain types.Domain, domainFromConfigurati
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
p.addResolvingDomains(uncheckedDomains)
|
||||
defer p.removeResolvingDomains(uncheckedDomains)
|
||||
|
||||
log.Debugf("Loading ACME certificates %+v...", uncheckedDomains)
|
||||
client, err := p.getClient()
|
||||
if err != nil {
|
||||
@@ -226,9 +242,9 @@ func (p *Provider) resolveCertificate(domain types.Domain, domainFromConfigurati
|
||||
|
||||
bundle := true
|
||||
|
||||
certificate, failures := client.ObtainCertificate(uncheckedDomains, bundle, nil, OSCPMustStaple)
|
||||
if len(failures) > 0 {
|
||||
return nil, fmt.Errorf("cannot obtain certificates %+v", failures)
|
||||
certificate, err := client.ObtainCertificate(uncheckedDomains, bundle, nil, OSCPMustStaple)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot obtain certificates: %+v", err)
|
||||
}
|
||||
|
||||
if len(certificate.Certificate) == 0 || len(certificate.PrivateKey) == 0 {
|
||||
@@ -243,7 +259,25 @@ func (p *Provider) resolveCertificate(domain types.Domain, domainFromConfigurati
|
||||
}
|
||||
p.addCertificateForDomain(domain, certificate.Certificate, certificate.PrivateKey)
|
||||
|
||||
return &certificate, nil
|
||||
return certificate, nil
|
||||
}
|
||||
|
||||
func (p *Provider) removeResolvingDomains(resolvingDomains []string) {
|
||||
p.resolvingDomainsMutex.Lock()
|
||||
defer p.resolvingDomainsMutex.Unlock()
|
||||
|
||||
for _, domain := range resolvingDomains {
|
||||
delete(p.resolvingDomains, domain)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) addResolvingDomains(resolvingDomains []string) {
|
||||
p.resolvingDomainsMutex.Lock()
|
||||
defer p.resolvingDomainsMutex.Unlock()
|
||||
|
||||
for _, domain := range resolvingDomains {
|
||||
p.resolvingDomains[domain] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) getClient() (*acme.Client, error) {
|
||||
@@ -315,7 +349,6 @@ func (p *Provider) getClient() (*acme.Client, error) {
|
||||
}
|
||||
p.client = client
|
||||
}
|
||||
|
||||
return p.client, nil
|
||||
}
|
||||
|
||||
@@ -347,7 +380,6 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||
safe.Go(func() {
|
||||
if _, err := p.resolveCertificate(domain, true); err != nil {
|
||||
log.Errorf("Unable to obtain ACME certificate for domains %q : %v", strings.Join(domain.ToStrArray(), ","), err)
|
||||
} else {
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -401,15 +433,6 @@ func (p *Provider) watchCertificate() {
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Provider) deleteCertificateForDomain(domain types.Domain) {
|
||||
for k, cert := range p.certificates {
|
||||
if reflect.DeepEqual(cert.Domain, domain) {
|
||||
p.certificates = append(p.certificates[:k], p.certificates[k+1:]...)
|
||||
}
|
||||
}
|
||||
p.saveCertificates()
|
||||
}
|
||||
|
||||
func (p *Provider) saveCertificates() {
|
||||
err := p.Store.SaveCertificates(p.certificates)
|
||||
if err != nil {
|
||||
@@ -506,6 +529,9 @@ func (p *Provider) AddRoutes(router *mux.Router) {
|
||||
// Get provided certificate which check a domains list (Main and SANs)
|
||||
// from static and dynamic provided certificates
|
||||
func (p *Provider) getUncheckedDomains(domainsToCheck []string, checkConfigurationDomains bool) []string {
|
||||
p.resolvingDomainsMutex.RLock()
|
||||
defer p.resolvingDomainsMutex.RUnlock()
|
||||
|
||||
log.Debugf("Looking for provided certificate(s) to validate %q...", domainsToCheck)
|
||||
var allCerts []string
|
||||
|
||||
@@ -526,6 +552,11 @@ func (p *Provider) getUncheckedDomains(domainsToCheck []string, checkConfigurati
|
||||
allCerts = append(allCerts, strings.Join(certificate.Domain.ToStrArray(), ","))
|
||||
}
|
||||
|
||||
// Get currently resolved domains
|
||||
for domain := range p.resolvingDomains {
|
||||
allCerts = append(allCerts, domain)
|
||||
}
|
||||
|
||||
// Get Configuration Domains
|
||||
if checkConfigurationDomains {
|
||||
for i := 0; i < len(p.Domains); i++ {
|
||||
@@ -543,8 +574,9 @@ func searchUncheckedDomains(domainsToCheck []string, existentDomains []string) [
|
||||
uncheckedDomains = append(uncheckedDomains, domainToCheck)
|
||||
}
|
||||
}
|
||||
|
||||
if len(uncheckedDomains) == 0 {
|
||||
log.Debugf("No ACME certificate to generate for domains %q.", domainsToCheck)
|
||||
log.Debugf("No ACME certificate generation required for domains %q.", domainsToCheck)
|
||||
} else {
|
||||
log.Debugf("Domains %q need ACME certificates generation for domains %q.", domainsToCheck, strings.Join(uncheckedDomains, ","))
|
||||
}
|
||||
|
@@ -26,6 +26,7 @@ func TestGetUncheckedCertificates(t *testing.T) {
|
||||
desc string
|
||||
dynamicCerts *safe.Safe
|
||||
staticCerts map[string]*tls.Certificate
|
||||
resolvingDomains map[string]struct{}
|
||||
acmeCertificates []*Certificate
|
||||
domains []string
|
||||
expectedDomains []string
|
||||
@@ -138,17 +139,55 @@ func TestGetUncheckedCertificates(t *testing.T) {
|
||||
},
|
||||
expectedDomains: []string{"traefik.wtf"},
|
||||
},
|
||||
{
|
||||
desc: "all domains already managed by ACME",
|
||||
domains: []string{"traefik.wtf", "foo.traefik.wtf"},
|
||||
resolvingDomains: map[string]struct{}{
|
||||
"traefik.wtf": {},
|
||||
"foo.traefik.wtf": {},
|
||||
},
|
||||
expectedDomains: []string{},
|
||||
},
|
||||
{
|
||||
desc: "one domain already managed by ACME",
|
||||
domains: []string{"traefik.wtf", "foo.traefik.wtf"},
|
||||
resolvingDomains: map[string]struct{}{
|
||||
"traefik.wtf": {},
|
||||
},
|
||||
expectedDomains: []string{"foo.traefik.wtf"},
|
||||
},
|
||||
{
|
||||
desc: "wildcard domain already managed by ACME checks the domains",
|
||||
domains: []string{"bar.traefik.wtf", "foo.traefik.wtf"},
|
||||
resolvingDomains: map[string]struct{}{
|
||||
"*.traefik.wtf": {},
|
||||
},
|
||||
expectedDomains: []string{},
|
||||
},
|
||||
{
|
||||
desc: "wildcard domain already managed by ACME checks domains and another domain checks one other domain, one domain still unchecked",
|
||||
domains: []string{"traefik.wtf", "bar.traefik.wtf", "foo.traefik.wtf", "acme.wtf"},
|
||||
resolvingDomains: map[string]struct{}{
|
||||
"*.traefik.wtf": {},
|
||||
"traefik.wtf": {},
|
||||
},
|
||||
expectedDomains: []string{"acme.wtf"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
if test.resolvingDomains == nil {
|
||||
test.resolvingDomains = make(map[string]struct{})
|
||||
}
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
acmeProvider := Provider{
|
||||
dynamicCerts: test.dynamicCerts,
|
||||
staticCerts: test.staticCerts,
|
||||
certificates: test.acmeCertificates,
|
||||
dynamicCerts: test.dynamicCerts,
|
||||
staticCerts: test.staticCerts,
|
||||
certificates: test.acmeCertificates,
|
||||
resolvingDomains: test.resolvingDomains,
|
||||
}
|
||||
|
||||
domains := acmeProvider.getUncheckedDomains(test.domains, false)
|
||||
|
@@ -13,6 +13,7 @@ type Store interface {
|
||||
SaveAccount(*Account) error
|
||||
GetCertificates() ([]*Certificate, error)
|
||||
SaveCertificates([]*Certificate) error
|
||||
GetHTTPChallenges() (map[string]map[string][]byte, error)
|
||||
SaveHTTPChallenges(map[string]map[string][]byte) error
|
||||
GetHTTPChallengeToken(token, domain string) ([]byte, error)
|
||||
SetHTTPChallengeToken(token, domain string, keyAuth []byte) error
|
||||
RemoveHTTPChallengeToken(token, domain string) error
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -116,7 +117,7 @@ func (p *Provider) getServer(node *api.ServiceEntry) types.Server {
|
||||
address := getBackendAddress(node)
|
||||
|
||||
return types.Server{
|
||||
URL: fmt.Sprintf("%s://%s:%d", scheme, address, node.Service.Port),
|
||||
URL: fmt.Sprintf("%s://%s", scheme, net.JoinHostPort(address, strconv.Itoa(node.Service.Port))),
|
||||
Weight: p.getWeight(node.Service.Tags),
|
||||
}
|
||||
}
|
||||
@@ -237,19 +238,6 @@ func hasTag(name string, tags []string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func hasTagPrefix(name string, tags []string) bool {
|
||||
lowerName := strings.ToLower(name)
|
||||
|
||||
for _, tag := range tags {
|
||||
lowerTag := strings.ToLower(tag)
|
||||
|
||||
if strings.HasPrefix(lowerTag, lowerName) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getTag(name string, tags []string, defaultValue string) string {
|
||||
lowerName := strings.ToLower(name)
|
||||
|
||||
|