forked from SW/traefik
Compare commits
68 Commits
v2.1.0-rc2
...
v2.1.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4461ecfed1 | ||
|
|
bd676922c3 | ||
|
|
49356cadd4 | ||
|
|
c02f222005 | ||
|
|
d3977ce40e | ||
|
|
7283d7eb2f | ||
|
|
807dc46ad0 | ||
|
|
0837ec9b70 | ||
|
|
b380522df8 | ||
|
|
c127d34d32 | ||
|
|
bc0b97d5d8 | ||
|
|
431abe79f3 | ||
|
|
4f669bdd66 | ||
|
|
8930236396 | ||
|
|
4d0aee67be | ||
|
|
b501c6d5bf | ||
|
|
7dcee38b21 | ||
|
|
903c63ac13 | ||
|
|
a98c9f99d1 | ||
|
|
b5ae141fb6 | ||
|
|
7eb866ffee | ||
|
|
61e59d74e0 | ||
|
|
e2982185d6 | ||
|
|
bdf4c6723f | ||
|
|
1d4f10bead | ||
|
|
aac3e2d4fb | ||
|
|
87dd6badac | ||
|
|
1b6c7af3eb | ||
|
|
5c091a1871 | ||
|
|
fb3839e096 | ||
|
|
eef3ca0295 | ||
|
|
c9dc0226fd | ||
|
|
1a7a3a4233 | ||
|
|
d2e458f673 | ||
|
|
e0f265db15 | ||
|
|
39a3cefc21 | ||
|
|
89db08eb93 | ||
|
|
f40cf2cd8e | ||
|
|
50bb69b796 | ||
|
|
a7d7c2b98b | ||
|
|
8dfc0d9dda | ||
|
|
0e6dce7093 | ||
|
|
ddbf4470a1 | ||
|
|
bc063ad773 | ||
|
|
ef38810425 | ||
|
|
5ccca8d708 | ||
|
|
89919dbe36 | ||
|
|
ecd51a1428 | ||
|
|
4cb9eec257 | ||
|
|
78097b96c9 | ||
|
|
2af8589afd | ||
|
|
cf1ace3a73 | ||
|
|
efcc9d51d4 | ||
|
|
a87c104172 | ||
|
|
b2c59be8de | ||
|
|
2685e06528 | ||
|
|
a99673122e | ||
|
|
ba49012447 | ||
|
|
407eda0ba0 | ||
|
|
5b1dc0bfbd | ||
|
|
772b260b37 | ||
|
|
00db3a0922 | ||
|
|
2bcc1b7fb4 | ||
|
|
433c848c8d | ||
|
|
abdb3b9475 | ||
|
|
9761161163 | ||
|
|
e5104021b1 | ||
|
|
9ef4f47ba0 |
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -3,11 +3,11 @@ PLEASE READ THIS MESSAGE.
|
||||
|
||||
Documentation fixes or enhancements:
|
||||
- for Traefik v1: use branch v1.7
|
||||
- for Traefik v2: use branch v2.0
|
||||
- for Traefik v2: use branch v2.1
|
||||
|
||||
Bug fixes:
|
||||
- for Traefik v1: use branch v1.7
|
||||
- for Traefik v2: use branch v2.0
|
||||
- for Traefik v2: use branch v2.1
|
||||
|
||||
Enhancements:
|
||||
- for Traefik v1: we only accept bug fixes
|
||||
|
||||
142
CHANGELOG.md
142
CHANGELOG.md
@@ -1,5 +1,143 @@
|
||||
## [v2.0.4](https://github.com/containous/traefik/tree/v2.0.4) (2019-10-28)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v2.0.3...v2.0.4)
|
||||
## [v2.1.2](https://github.com/containous/traefik/tree/v2.1.2) (2020-01-07)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v2.1.1...v2.1.2)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[authentication,middleware,tracing]** fix(tracing): makes sure tracing headers are being propagated when using forwardAuth ([#6072](https://github.com/containous/traefik/pull/6072) by [jcchavezs](https://github.com/jcchavezs))
|
||||
- **[cli]** fix: invalid label/flag parsing. ([#6028](https://github.com/containous/traefik/pull/6028) by [ldez](https://github.com/ldez))
|
||||
- **[consulcatalog]** Query consul catalog for service health separately ([#6046](https://github.com/containous/traefik/pull/6046) by [SantoDE](https://github.com/SantoDE))
|
||||
- **[k8s,k8s/crd]** Restore ExternalName https support for Kubernetes CRD ([#6037](https://github.com/containous/traefik/pull/6037) by [kpeiruza](https://github.com/kpeiruza))
|
||||
- **[k8s,k8s/crd]** Log the ignored namespace only when needed ([#6087](https://github.com/containous/traefik/pull/6087) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- **[k8s,k8s/ingress]** k8s Ingress: fix crash on rules with nil http ([#6121](https://github.com/containous/traefik/pull/6121) by [grimmy](https://github.com/grimmy))
|
||||
- **[logs]** Improves error message when a configuration file is empty. ([#6135](https://github.com/containous/traefik/pull/6135) by [ldez](https://github.com/ldez))
|
||||
- **[server]** Handle respondingTimeout and better shutdown tests. ([#6115](https://github.com/containous/traefik/pull/6115) by [juliens](https://github.com/juliens))
|
||||
- **[server]** Don't set user-agent to Go-http-client/1.1 ([#6030](https://github.com/containous/traefik/pull/6030) by [sh7dm](https://github.com/sh7dm))
|
||||
- **[tracing]** fix: Malformed x-b3-traceid Header ([#6079](https://github.com/containous/traefik/pull/6079) by [ldez](https://github.com/ldez))
|
||||
- **[webui]** fix: dashboard redirect loop ([#6078](https://github.com/containous/traefik/pull/6078) by [ldez](https://github.com/ldez))
|
||||
|
||||
**Documentation:**
|
||||
- **[acme]** Use consistent name in ACME documentation ([#6019](https://github.com/containous/traefik/pull/6019) by [ldez](https://github.com/ldez))
|
||||
- **[api,k8s/crd]** Add a documentation example for dashboard and api for kubernetes CRD ([#6022](https://github.com/containous/traefik/pull/6022) by [dduportal](https://github.com/dduportal))
|
||||
- **[cli]** Fix examples for the use of websecure via CLI ([#6116](https://github.com/containous/traefik/pull/6116) by [tiagoboeing](https://github.com/tiagoboeing))
|
||||
- **[k8s,k8s/crd]** Improve documentation about Kubernetes IngressRoute ([#6058](https://github.com/containous/traefik/pull/6058) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- **[middleware]** Improve sourceRange explanation for ipWhiteList ([#6070](https://github.com/containous/traefik/pull/6070) by [der-domi](https://github.com/der-domi))
|
||||
|
||||
## [v2.1.1](https://github.com/containous/traefik/tree/v2.1.1) (2019-12-12)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v2.1.0...v2.1.1)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[logs,middleware,metrics]** CloseNotifier: return pointer instead of value ([#6010](https://github.com/containous/traefik/pull/6010) by [mpl](https://github.com/mpl))
|
||||
|
||||
**Documentation:**
|
||||
- Add Migration Guide for Traefik v2.1 ([#6017](https://github.com/containous/traefik/pull/6017) by [SantoDE](https://github.com/SantoDE))
|
||||
|
||||
## [v2.1.0](https://github.com/containous/traefik/tree/v2.1.0) (2019-12-10)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v2.0.0-rc1...v2.1.0)
|
||||
|
||||
**Enhancements:**
|
||||
- **[consulcatalog]** Add consul catalog options: requireConsistent, stale, cache ([#5752](https://github.com/containous/traefik/pull/5752) by [ldez](https://github.com/ldez))
|
||||
- **[consulcatalog]** Add Consul Catalog provider ([#5395](https://github.com/containous/traefik/pull/5395) by [negasus](https://github.com/negasus))
|
||||
- **[k8s,k8s/crd,service]** Support for all services kinds (and sticky) in CRD ([#5711](https://github.com/containous/traefik/pull/5711) by [mpl](https://github.com/mpl))
|
||||
- **[metrics]** Added configurable prefix for statsd metrics collection ([#5336](https://github.com/containous/traefik/pull/5336) by [schulterklopfer](https://github.com/schulterklopfer))
|
||||
- **[middleware]** Conditional compression based on request Content-Type ([#5721](https://github.com/containous/traefik/pull/5721) by [ldez](https://github.com/ldez))
|
||||
- **[server]** Add internal provider ([#5815](https://github.com/containous/traefik/pull/5815) by [ldez](https://github.com/ldez))
|
||||
- **[tls]** Add support for MaxVersion in tls.Options ([#5650](https://github.com/containous/traefik/pull/5650) by [kmeekva](https://github.com/kmeekva))
|
||||
- **[tls]** Add tls option for Elliptic Curve Preferences ([#5466](https://github.com/containous/traefik/pull/5466) by [ksarink](https://github.com/ksarink))
|
||||
- **[tracing]** Update jaeger dependencies ([#5637](https://github.com/containous/traefik/pull/5637) by [mmatur](https://github.com/mmatur))
|
||||
|
||||
**Bug fixes:**
|
||||
- **[api]** fix: debug endpoint when insecure API. ([#5937](https://github.com/containous/traefik/pull/5937) by [ldez](https://github.com/ldez))
|
||||
- **[cli]** fix: sub command help ([#5887](https://github.com/containous/traefik/pull/5887) by [ldez](https://github.com/ldez))
|
||||
- **[consulcatalog]** fix: consul catalog constraints. ([#5913](https://github.com/containous/traefik/pull/5913) by [ldez](https://github.com/ldez))
|
||||
- **[consulcatalog]** Service registered with same id on Consul Catalog ([#5900](https://github.com/containous/traefik/pull/5900) by [mmatur](https://github.com/mmatur))
|
||||
- **[consulcatalog]** Fix empty address for registering service without IP ([#5826](https://github.com/containous/traefik/pull/5826) by [mmatur](https://github.com/mmatur))
|
||||
- **[logs,middleware,metrics]** detect CloseNotify capability in accesslog and metrics ([#5985](https://github.com/containous/traefik/pull/5985) by [mpl](https://github.com/mpl))
|
||||
- **[server]** fix: remove double call to server Close. ([#5960](https://github.com/containous/traefik/pull/5960) by [ldez](https://github.com/ldez))
|
||||
- **[webui]** Fix weighted service provider icon ([#5983](https://github.com/containous/traefik/pull/5983) by [sh7dm](https://github.com/sh7dm))
|
||||
- **[webui]** Fix http/tcp resources pagination ([#5986](https://github.com/containous/traefik/pull/5986) by [matthieuh](https://github.com/matthieuh))
|
||||
- **[webui]** Use valid condition in the service details panel UI ([#5984](https://github.com/containous/traefik/pull/5984) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- **[webui]** Web UI: Avoid polling on /api/entrypoints ([#5863](https://github.com/containous/traefik/pull/5863) by [matthieuh](https://github.com/matthieuh))
|
||||
- **[webui]** Web UI: Sync toolbar table state with url query params ([#5861](https://github.com/containous/traefik/pull/5861) by [matthieuh](https://github.com/matthieuh))
|
||||
|
||||
**Documentation:**
|
||||
- **[consulcatalog]** fix: Consul Catalog documentation. ([#5725](https://github.com/containous/traefik/pull/5725) by [ldez](https://github.com/ldez))
|
||||
- **[consulcatalog]** Fix consul catalog documentation ([#5661](https://github.com/containous/traefik/pull/5661) by [mmatur](https://github.com/mmatur))
|
||||
- Prepare release v2.1.0-rc2 ([#5846](https://github.com/containous/traefik/pull/5846) by [ldez](https://github.com/ldez))
|
||||
- Prepare release v2.1.0-rc1 ([#5844](https://github.com/containous/traefik/pull/5844) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- Several documentation fixes ([#5987](https://github.com/containous/traefik/pull/5987) by [ldez](https://github.com/ldez))
|
||||
- Prepare release v2.1.0-rc3 ([#5929](https://github.com/containous/traefik/pull/5929) by [ldez](https://github.com/ldez))
|
||||
|
||||
**Misc:**
|
||||
- **[cli]** Add custom help function to command ([#5923](https://github.com/containous/traefik/pull/5923) by [Ullaakut](https://github.com/Ullaakut))
|
||||
- **[server]** fix: use MaxInt32. ([#5845](https://github.com/containous/traefik/pull/5845) by [ldez](https://github.com/ldez))
|
||||
- Merge current v2.0 branch into master ([#5841](https://github.com/containous/traefik/pull/5841) by [ldez](https://github.com/ldez))
|
||||
- Merge current v2.0 branch into master ([#5749](https://github.com/containous/traefik/pull/5749) by [ldez](https://github.com/ldez))
|
||||
- Merge current v2.0 branch into master ([#5619](https://github.com/containous/traefik/pull/5619) by [ldez](https://github.com/ldez))
|
||||
- Merge current v2.0 branch into master ([#5464](https://github.com/containous/traefik/pull/5464) by [ldez](https://github.com/ldez))
|
||||
- Merge v2.0.0 into master ([#5402](https://github.com/containous/traefik/pull/5402) by [ldez](https://github.com/ldez))
|
||||
- Merge v2.0.0-rc3 into master ([#5354](https://github.com/containous/traefik/pull/5354) by [ldez](https://github.com/ldez))
|
||||
- Merge v2.0.0-rc1 into master ([#5253](https://github.com/containous/traefik/pull/5253) by [ldez](https://github.com/ldez))
|
||||
- Merge current v2.0 branch into v2.1 ([#5977](https://github.com/containous/traefik/pull/5977) by [ldez](https://github.com/ldez))
|
||||
- Merge current v2.0 branch into v2.1 ([#5931](https://github.com/containous/traefik/pull/5931) by [ldez](https://github.com/ldez))
|
||||
- Merge current v2.0 branch into v2.1 ([#5928](https://github.com/containous/traefik/pull/5928) by [ldez](https://github.com/ldez))
|
||||
|
||||
## [v2.0.7](https://github.com/containous/traefik/tree/v2.0.7) (2019-12-09)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v2.0.6...v2.0.7)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[logs,middleware]** Remove mirroring impact in accesslog ([#5967](https://github.com/containous/traefik/pull/5967) by [juliens](https://github.com/juliens))
|
||||
- **[middleware]** fix: PassClientTLSCert middleware separators and formatting ([#5921](https://github.com/containous/traefik/pull/5921) by [ldez](https://github.com/ldez))
|
||||
- **[server]** Do not stop to listen on tcp listeners on temporary errors ([#5935](https://github.com/containous/traefik/pull/5935) by [skwair](https://github.com/skwair))
|
||||
|
||||
**Documentation:**
|
||||
- **[acme,k8s/crd,k8s/ingress]** Document LE caveats with Kubernetes on v2 ([#5902](https://github.com/containous/traefik/pull/5902) by [dtomcej](https://github.com/dtomcej))
|
||||
- **[acme]** The Cloudflare hint for the GLOBAL API KEY for CF MAIL/API_KEY ([#5964](https://github.com/containous/traefik/pull/5964) by [EugenMayer](https://github.com/EugenMayer))
|
||||
- **[acme]** Improve documentation for ACME/Let's Encrypt ([#5819](https://github.com/containous/traefik/pull/5819) by [dduportal](https://github.com/dduportal))
|
||||
- **[file]** Improve documentation on file provider limitations with file system notifications ([#5939](https://github.com/containous/traefik/pull/5939) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- Make trailing slash more prominent for the "secure dashboard setup" too ([#5963](https://github.com/containous/traefik/pull/5963) by [EugenMayer](https://github.com/EugenMayer))
|
||||
- Fix Docker example in "Strip and Rewrite Path Prefixes" in migration guide ([#5949](https://github.com/containous/traefik/pull/5949) by [q210](https://github.com/q210))
|
||||
- readme: Fix link to file backend/provider documentation ([#5945](https://github.com/containous/traefik/pull/5945) by [hartwork](https://github.com/hartwork))
|
||||
|
||||
## [v2.1.0-rc3](https://github.com/containous/traefik/tree/v2.1.0-rc3) (2019-12-02)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v2.1.0-rc2...v2.1.0-rc3)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[cli]** fix: sub command help ([#5887](https://github.com/containous/traefik/pull/5887) by [ldez](https://github.com/ldez))
|
||||
- **[consulcatalog]** fix: consul catalog constraints. ([#5913](https://github.com/containous/traefik/pull/5913) by [ldez](https://github.com/ldez))
|
||||
- **[consulcatalog]** Service registered with same id on Consul Catalog ([#5900](https://github.com/containous/traefik/pull/5900) by [mmatur](https://github.com/mmatur))
|
||||
- **[webui]** Web UI: Avoid polling on /api/entrypoints ([#5863](https://github.com/containous/traefik/pull/5863) by [matthieuh](https://github.com/matthieuh))
|
||||
- **[webui]** Web UI: Sync toolbar table state with url query params ([#5861](https://github.com/containous/traefik/pull/5861) by [matthieuh](https://github.com/matthieuh))
|
||||
|
||||
**Misc:**
|
||||
- **[cli]** Add custom help function to command ([#5923](https://github.com/containous/traefik/pull/5923) by [Ullaakut](https://github.com/Ullaakut))
|
||||
|
||||
## [v2.0.6](https://github.com/containous/traefik/tree/v2.0.6) (2019-12-02)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v2.0.5...v2.0.6)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[acme]** Update go-acme/lego to 3.2.0 ([#5839](https://github.com/containous/traefik/pull/5839) by [kolaente](https://github.com/kolaente))
|
||||
- **[cli,healthcheck]** Uses, if it exists, the ping entry point provided in the static configuration ([#5867](https://github.com/containous/traefik/pull/5867) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- **[healthcheck]** Healthcheck managed for all related services ([#5860](https://github.com/containous/traefik/pull/5860) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- **[logs,middleware]** Do not give responsewriter or its headers to asynchronous logging goroutine ([#5840](https://github.com/containous/traefik/pull/5840) by [mpl](https://github.com/mpl))
|
||||
- **[middleware]** X-Forwarded-Proto must not skip the redirection. ([#5836](https://github.com/containous/traefik/pull/5836) by [ldez](https://github.com/ldez))
|
||||
- **[middleware]** fix: location header rewrite. ([#5835](https://github.com/containous/traefik/pull/5835) by [ldez](https://github.com/ldez))
|
||||
- **[middleware]** Remove Request Headers CORS Preflight Requirement ([#5903](https://github.com/containous/traefik/pull/5903) by [dtomcej](https://github.com/dtomcej))
|
||||
- **[rancher]** Change service name in rancher provider to make webui service details view work ([#5895](https://github.com/containous/traefik/pull/5895) by [SantoDE](https://github.com/SantoDE))
|
||||
- **[tracing]** Fix extraction for zipkin tracing ([#5920](https://github.com/containous/traefik/pull/5920) by [jcchavezs](https://github.com/jcchavezs))
|
||||
- **[webui]** Web UI: Avoid unnecessary duplicated api calls ([#5884](https://github.com/containous/traefik/pull/5884) by [matthieuh](https://github.com/matthieuh))
|
||||
- **[webui]** Web UI: Avoid some router properties to overflow their container ([#5872](https://github.com/containous/traefik/pull/5872) by [matthieuh](https://github.com/matthieuh))
|
||||
- **[webui]** Web UI: Fix displayed tcp service details ([#5868](https://github.com/containous/traefik/pull/5868) by [matthieuh](https://github.com/matthieuh))
|
||||
|
||||
**Documentation:**
|
||||
- **[acme]** doc: fix wrong acme information ([#5837](https://github.com/containous/traefik/pull/5837) by [ldez](https://github.com/ldez))
|
||||
- **[docker,docker/swarm]** Add Swarm section to the Docker Provider Documentation ([#5874](https://github.com/containous/traefik/pull/5874) by [dduportal](https://github.com/dduportal))
|
||||
- **[docker]** Update router entrypoint example ([#5766](https://github.com/containous/traefik/pull/5766) by [woto](https://github.com/woto))
|
||||
- **[k8s/helm]** Mention the experimental Helm Chart in the installation section of documentation ([#5879](https://github.com/containous/traefik/pull/5879) by [dduportal](https://github.com/dduportal))
|
||||
- doc: remove double quotes on CLI flags. ([#5862](https://github.com/containous/traefik/pull/5862) by [ldez](https://github.com/ldez))
|
||||
- Fixed spelling error ([#5834](https://github.com/containous/traefik/pull/5834) by [blakebuthod](https://github.com/blakebuthod))
|
||||
- Add back the security section from v1 ([#5832](https://github.com/containous/traefik/pull/5832) by [pascalandy](https://github.com/pascalandy))
|
||||
|
||||
## [v2.1.0-rc2](https://github.com/containous/traefik/tree/v2.0.4) (2019-11-15)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v2.0.0-rc1...v2.1.0-rc2)
|
||||
|
||||
Fixes int overflow.
|
||||
Same changelog as v2.1.0-rc1
|
||||
|
||||
@@ -73,7 +73,7 @@ _(But if you'd rather configure some of your routes manually, Traefik supports t
|
||||
- [Kubernetes](https://docs.traefik.io/providers/kubernetes-crd/)
|
||||
- [Marathon](https://docs.traefik.io/providers/marathon/)
|
||||
- [Rancher](https://docs.traefik.io/providers/rancher/) (Metadata)
|
||||
- [File](https://docs.traefik.io/configuration/backends/file)
|
||||
- [File](https://docs.traefik.io/providers/file/)
|
||||
|
||||
## Quickstart
|
||||
|
||||
|
||||
@@ -51,9 +51,14 @@ func Do(staticConfiguration static.Configuration) (*http.Response, error) {
|
||||
return nil, errors.New("please enable `ping` to use health check")
|
||||
}
|
||||
|
||||
pingEntryPoint, ok := staticConfiguration.EntryPoints["traefik"]
|
||||
ep := staticConfiguration.Ping.EntryPoint
|
||||
if ep == "" {
|
||||
ep = "traefik"
|
||||
}
|
||||
|
||||
pingEntryPoint, ok := staticConfiguration.EntryPoints[ep]
|
||||
if !ok {
|
||||
return nil, errors.New("missing `ping` entrypoint")
|
||||
return nil, fmt.Errorf("ping: missing %s entry point", ep)
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
|
||||
@@ -69,10 +69,10 @@ Complete documentation is available at https://traefik.io`,
|
||||
err = cli.Execute(cmdTraefik)
|
||||
if err != nil {
|
||||
stdlog.Println(err)
|
||||
os.Exit(1)
|
||||
logrus.Exit(1)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
logrus.Exit(0)
|
||||
}
|
||||
|
||||
func runCmd(staticConfiguration *static.Configuration) error {
|
||||
@@ -156,7 +156,6 @@ func runCmd(staticConfiguration *static.Configuration) error {
|
||||
|
||||
svr.Wait()
|
||||
log.WithoutContext().Info("Shutting down")
|
||||
logrus.Exit(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -173,7 +172,7 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
||||
|
||||
acmeProviders := initACMEProvider(staticConfiguration, &providerAggregator, tlsManager)
|
||||
|
||||
serverEntryPointsTCP, err := server.NewTCPEntryPoints(*staticConfiguration)
|
||||
serverEntryPointsTCP, err := server.NewTCPEntryPoints(staticConfiguration.EntryPoints)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -15,8 +15,12 @@ RUN gem install html-proofer --version 3.13.0 --no-document -- --use-system-libr
|
||||
RUN apk --no-cache --no-progress add \
|
||||
git \
|
||||
nodejs \
|
||||
npm \
|
||||
&& npm install --global \
|
||||
npm
|
||||
|
||||
# To handle 'not get uid/gid'
|
||||
RUN npm config set unsafe-perm true
|
||||
|
||||
RUN npm install --global \
|
||||
markdownlint@0.17.2 \
|
||||
markdownlint-cli@0.19.0
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ Requirements:
|
||||
|
||||
- `go` v1.13+
|
||||
- environment variable `GO111MODULE=on`
|
||||
- go-bindata `GO111MODULE=off go get -u github.com/containous/go-bindata/...`
|
||||
- [go-bindata](https://github.com/containous/go-bindata) `GO111MODULE=off go get -u github.com/containous/go-bindata/...`
|
||||
|
||||
!!! tip "Source Directory"
|
||||
|
||||
@@ -98,30 +98,32 @@ Requirements:
|
||||
#### Build Traefik
|
||||
|
||||
Once you've set up your go environment and cloned the source repository, you can build Traefik.
|
||||
Beforehand, you need to get `go-bindata` (the first time) in order to be able to use the `go generate` command (which is part of the build process).
|
||||
|
||||
Beforehand, you need to get [go-bindata](https://github.com/containous/go-bindata) (the first time) in order to be able to use the `go generate` command (which is part of the build process).
|
||||
|
||||
```bash
|
||||
cd ~/go/src/github.com/containous/traefik
|
||||
|
||||
# Get go-bindata. (Important: the ellipses are required.)
|
||||
GO111MODULE=off go get github.com/containous/go-bindata/...
|
||||
```
|
||||
|
||||
# Let's build
|
||||
```bash
|
||||
# Generate UI static files
|
||||
rm -rf static/ autogen/; make generate-webui
|
||||
|
||||
# generate
|
||||
# (required to merge non-code components into the final binary, such as the web dashboard and the provider's templates)
|
||||
# required to merge non-code components into the final binary,
|
||||
# such as the web dashboard/UI
|
||||
go generate
|
||||
```
|
||||
|
||||
```bash
|
||||
# Standard go build
|
||||
go build ./cmd/traefik
|
||||
```
|
||||
|
||||
You will find the Traefik executable (`traefik`) in the `~/go/src/github.com/containous/traefik` directory.
|
||||
|
||||
### Updating the templates
|
||||
|
||||
If you happen to update the provider's templates (located in `/templates`), you must run `go generate` to update the `autogen` package.
|
||||
|
||||
## Testing
|
||||
|
||||
### Method 1: `Docker` and `make`
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
You can install Traefik with the following flavors:
|
||||
|
||||
* [Use the official Docker image](./#use-the-official-docker-image)
|
||||
* [(Experimental) Use the Helm Chart](./#use-the-helm-chart)
|
||||
* [Use the binary distribution](./#use-the-binary-distribution)
|
||||
* [Compile your binary from the sources](./#compile-your-binary-from-the-sources)
|
||||
|
||||
@@ -24,6 +25,70 @@ For more details, go to the [Docker provider documentation](../providers/docker.
|
||||
* Docker images are based from the [Alpine Linux Official image](https://hub.docker.com/_/alpine).
|
||||
* All the orchestrator using docker images could fetch the official Traefik docker image.
|
||||
|
||||
## Use the Helm Chart
|
||||
|
||||
!!! warning "Experimental Helm Chart"
|
||||
|
||||
Please note that the Helm Chart for Traefik v2 is still experimental.
|
||||
|
||||
The Traefik Stable Chart from
|
||||
[Helm's default charts repository](https://github.com/helm/charts/tree/master/stable/traefik) is still using [Traefik v1.7](https://docs.traefik.io/v1.7).
|
||||
|
||||
Traefik can be installed in Kubernetes using the v2.0 Helm chart from <https://github.com/containous/traefik-helm-chart>.
|
||||
|
||||
Ensure that the following requirements are met:
|
||||
|
||||
* Kubernetes 1.14+
|
||||
* Helm version 2.x is [installed](https://v2.helm.sh/docs/using_helm/) and initialized with Tiller
|
||||
|
||||
Retrieve the latest chart version from the repository:
|
||||
|
||||
```bash
|
||||
# Retrieve Chart from the repository
|
||||
git clone https://github.com/containous/traefik-helm-chart
|
||||
```
|
||||
|
||||
And install it with the `helm` command line:
|
||||
|
||||
```bash
|
||||
helm install ./traefik-helm-chart
|
||||
```
|
||||
|
||||
!!! tip "Helm Features"
|
||||
|
||||
All [Helm features](https://v2.helm.sh/docs/using_helm/#using-helm) are supported.
|
||||
For instance, installing the chart in a dedicated namespace:
|
||||
|
||||
```bash tab="Install in a Dedicated Namespace"
|
||||
# Install in the namespace "traefik-v2"
|
||||
helm install --namespace=traefik-v2 \
|
||||
./traefik-helm-chart
|
||||
```
|
||||
|
||||
??? example "Installing with Custom Values"
|
||||
|
||||
You can customize the installation by specifying custom values,
|
||||
as with [any helm chart](https://v2.helm.sh/docs/using_helm/#customizing-the-chart-before-installing).
|
||||
{: #helm-custom-values }
|
||||
|
||||
The values are not (yet) documented, but are self-explanatory:
|
||||
you can look at the [default `values.yaml`](https://github.com/containous/traefik-helm-chart/blob/master/values.yaml) file to explore possibilities.
|
||||
|
||||
Example of installation with logging set to `DEBUG`:
|
||||
|
||||
```bash tab="Using Helm CLI"
|
||||
helm install --namespace=traefik-v2 \
|
||||
--set="logs.loglevel=DEBUG" \
|
||||
./traefik-helm-chart
|
||||
```
|
||||
|
||||
```yml tab="With a custom values file"
|
||||
# File custom-values.yml
|
||||
## Install with "helm install --values=./custom-values.yml ./traefik-helm-chart
|
||||
logs:
|
||||
loglevel: DEBUG
|
||||
```
|
||||
|
||||
## Use the Binary Distribution
|
||||
|
||||
Grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page.
|
||||
|
||||
4
docs/content/https/.markdownlint.json
Normal file
4
docs/content/https/.markdownlint.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../../.markdownlint.json",
|
||||
"MD041": false
|
||||
}
|
||||
@@ -8,6 +8,45 @@ You can configure Traefik to use an ACME provider (like Let's Encrypt) for autom
|
||||
!!! warning "Let's Encrypt and Rate Limiting"
|
||||
Note that Let's Encrypt API has [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
||||
|
||||
Use Let's Encrypt staging server with the [`caServer`](#caserver) configuration option
|
||||
when experimenting to avoid hitting this limit too fast.
|
||||
|
||||
## Certificate Resolvers
|
||||
|
||||
Traefik requires you to define "Certificate Resolvers" in the [static configuration](../getting-started/configuration-overview.md#the-static-configuration),
|
||||
which are responsible for retrieving certificates from an ACME server.
|
||||
|
||||
Then, each ["router"](../routing/routers/index.md) is configured to enable TLS,
|
||||
and is associated to a certificate resolver through the [`tls.certresolver` configuration option](../routing/routers/index.md#certresolver).
|
||||
|
||||
Certificates are requested for domain names retrieved from the router's [dynamic configuration](../getting-started/configuration-overview.md#the-dynamic-configuration).
|
||||
|
||||
You can read more about this retrieval mechanism in the following section: [ACME Domain Definition](#domain-definition).
|
||||
|
||||
## Domain Definition
|
||||
|
||||
Certificate resolvers request certificates for a set of the domain names
|
||||
inferred from routers, with the following logic:
|
||||
|
||||
- If the router has a [`tls.domains`](../routing/routers/index.md#domains) option set,
|
||||
then the certificate resolver uses the `main` (and optionally `sans`) option of `tls.domains` to know the domain names for this router.
|
||||
|
||||
- If no [`tls.domains`](../routing/routers/index.md#domains) option is set,
|
||||
then the certificate resolver uses the [router's rule](../routing/routers/index.md#rule),
|
||||
by checking the `Host()` matchers.
|
||||
Please note that [multiple `Host()` matchers can be used](../routing/routers/index.md#certresolver)) for specifying multiple domain names for this router.
|
||||
|
||||
Please note that:
|
||||
|
||||
- When multiple domain names are inferred from a given router,
|
||||
only **one** certificate is requested with the first domain name as the main domain,
|
||||
and the other domains as ["SANs" (Subject Alternative Name)](https://en.wikipedia.org/wiki/Subject_Alternative_Name).
|
||||
|
||||
- As [ACME V2 supports "wildcard domains"](#wildcard-domains),
|
||||
any router can provide a [wildcard domain](https://en.wikipedia.org/wiki/Wildcard_certificate) name, as "main" domain or as "SAN" domain.
|
||||
|
||||
Please check the [configuration examples below](#configuration-examples) for more details.
|
||||
|
||||
## Configuration Examples
|
||||
|
||||
??? example "Enabling ACME"
|
||||
@@ -20,10 +59,10 @@ You can configure Traefik to use an ACME provider (like Let's Encrypt) for autom
|
||||
[entryPoints.web-secure]
|
||||
address = ":443"
|
||||
|
||||
[certificatesResolvers.sample.acme]
|
||||
[certificatesResolvers.le.acme]
|
||||
email = "your-email@your-domain.org"
|
||||
storage = "acme.json"
|
||||
[certificatesResolvers.sample.acme.httpChallenge]
|
||||
[certificatesResolvers.le.acme.httpChallenge]
|
||||
# used during the challenge
|
||||
entryPoint = "web"
|
||||
```
|
||||
@@ -47,13 +86,13 @@ You can configure Traefik to use an ACME provider (like Let's Encrypt) for autom
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--entryPoints.web.address=":80"
|
||||
--entryPoints.websecure.address=":443"
|
||||
--entryPoints.web.address=:80
|
||||
--entryPoints.websecure.address=:443
|
||||
# ...
|
||||
--certificatesResolvers.sample.acme.email="your-email@your-domain.org"
|
||||
--certificatesResolvers.sample.acme.storage="acme.json"
|
||||
--certificatesResolvers.le.acme.email=your-email@your-domain.org
|
||||
--certificatesResolvers.le.acme.storage=acme.json
|
||||
# used during the challenge
|
||||
--certificatesResolvers.sample.acme.httpChallenge.entryPoint=web
|
||||
--certificatesResolvers.le.acme.httpChallenge.entryPoint=web
|
||||
```
|
||||
|
||||
!!! important "Defining a certificates resolver does not result in all routers automatically using it. Each router that is supposed to use the resolver must [reference](../routing/routers/index.md#certresolver) it."
|
||||
@@ -75,6 +114,26 @@ You can configure Traefik to use an ACME provider (like Let's Encrypt) for autom
|
||||
--8<-- "content/https/ref-acme.txt"
|
||||
```
|
||||
|
||||
??? example "Single Domain from Router's Rule Example"
|
||||
|
||||
* A certificate for the domain `company.com` is requested:
|
||||
|
||||
--8<-- "content/https/include-acme-single-domain-example.md"
|
||||
|
||||
??? example "Multiple Domains from Router's Rule Example"
|
||||
|
||||
* A certificate for the domains `company.com` (main) and `blog.company.org`
|
||||
is requested:
|
||||
|
||||
--8<-- "content/https/include-acme-multiple-domains-from-rule-example.md"
|
||||
|
||||
??? example "Multiple Domains from Router's `tls.domain` Example"
|
||||
|
||||
* A certificate for the domains `company.com` (main) and `*.company.org` (SAN)
|
||||
is requested:
|
||||
|
||||
--8<-- "content/https/include-acme-multiple-domains-example.md"
|
||||
|
||||
## Automatic Renewals
|
||||
|
||||
Traefik automatically tracks the expiry date of ACME certificates it generates.
|
||||
@@ -84,6 +143,13 @@ If there are less than 30 days remaining before the certificate expires, Traefik
|
||||
!!! info ""
|
||||
Certificates that are no longer used may still be renewed, as Traefik does not currently check if the certificate is being used before renewing.
|
||||
|
||||
## Using LetsEncrypt with Kubernetes
|
||||
|
||||
When using LetsEncrypt with kubernetes, there are some known caveats with both the [ingress](../providers/kubernetes-ingress.md) and [crd](../providers/kubernetes-crd.md) providers.
|
||||
|
||||
!!! info ""
|
||||
If you intend to run multiple instances of Traefik with LetsEncrypt, please ensure you read the sections on those provider pages.
|
||||
|
||||
## The Different ACME Challenges
|
||||
|
||||
!!! important "Defining a certificates resolver does not result in all routers automatically using it. Each router that is supposed to use the resolver must [reference](../routing/routers/index.md#certresolver) it."
|
||||
@@ -98,9 +164,9 @@ when using the `TLS-ALPN-01` challenge, Traefik must be reachable by Let's Encry
|
||||
??? example "Configuring the `tlsChallenge`"
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[certificatesResolvers.sample.acme]
|
||||
[certificatesResolvers.le.acme]
|
||||
# ...
|
||||
[certificatesResolvers.sample.acme.tlsChallenge]
|
||||
[certificatesResolvers.le.acme.tlsChallenge]
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
@@ -113,7 +179,7 @@ when using the `TLS-ALPN-01` challenge, Traefik must be reachable by Let's Encry
|
||||
|
||||
```bash tab="CLI"
|
||||
# ...
|
||||
--certificatesResolvers.sample.acme.tlsChallenge=true
|
||||
--certificatesResolvers.le.acme.tlsChallenge=true
|
||||
```
|
||||
|
||||
### `httpChallenge`
|
||||
@@ -121,7 +187,7 @@ when using the `TLS-ALPN-01` challenge, Traefik must be reachable by Let's Encry
|
||||
Use the `HTTP-01` challenge to generate and renew ACME certificates by provisioning an HTTP resource under a well-known URI.
|
||||
|
||||
As described on the Let's Encrypt [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72),
|
||||
when using the `HTTP-01` challenge, `certificatesResolvers.sample.acme.httpChallenge.entryPoint` must be reachable by Let's Encrypt through port 80.
|
||||
when using the `HTTP-01` challenge, `certificatesResolvers.le.acme.httpChallenge.entryPoint` must be reachable by Let's Encrypt through port 80.
|
||||
|
||||
??? example "Using an EntryPoint Called http for the `httpChallenge`"
|
||||
|
||||
@@ -133,9 +199,9 @@ when using the `HTTP-01` challenge, `certificatesResolvers.sample.acme.httpChall
|
||||
[entryPoints.web-secure]
|
||||
address = ":443"
|
||||
|
||||
[certificatesResolvers.sample.acme]
|
||||
[certificatesResolvers.le.acme]
|
||||
# ...
|
||||
[certificatesResolvers.sample.acme.httpChallenge]
|
||||
[certificatesResolvers.le.acme.httpChallenge]
|
||||
entryPoint = "web"
|
||||
```
|
||||
|
||||
@@ -156,10 +222,10 @@ when using the `HTTP-01` challenge, `certificatesResolvers.sample.acme.httpChall
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--entryPoints.web.address=":80"
|
||||
--entryPoints.websecure.address=":443"
|
||||
--entryPoints.web.address=:80
|
||||
--entryPoints.websecure.address=:443
|
||||
# ...
|
||||
--certificatesResolvers.sample.acme.httpChallenge.entryPoint=web
|
||||
--certificatesResolvers.le.acme.httpChallenge.entryPoint=web
|
||||
```
|
||||
|
||||
!!! info ""
|
||||
@@ -172,9 +238,9 @@ Use the `DNS-01` challenge to generate and renew ACME certificates by provisioni
|
||||
??? example "Configuring a `dnsChallenge` with the DigitalOcean Provider"
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[certificatesResolvers.sample.acme]
|
||||
[certificatesResolvers.le.acme]
|
||||
# ...
|
||||
[certificatesResolvers.sample.acme.dnsChallenge]
|
||||
[certificatesResolvers.le.acme.dnsChallenge]
|
||||
provider = "digitalocean"
|
||||
delayBeforeCheck = 0
|
||||
# ...
|
||||
@@ -193,8 +259,8 @@ Use the `DNS-01` challenge to generate and renew ACME certificates by provisioni
|
||||
|
||||
```bash tab="CLI"
|
||||
# ...
|
||||
--certificatesResolvers.sample.acme.dnsChallenge.provider=digitalocean
|
||||
--certificatesResolvers.sample.acme.dnsChallenge.delayBeforeCheck=0
|
||||
--certificatesResolvers.le.acme.dnsChallenge.provider=digitalocean
|
||||
--certificatesResolvers.le.acme.dnsChallenge.delayBeforeCheck=0
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -220,7 +286,7 @@ For example, `CF_API_EMAIL_FILE=/run/secrets/traefik_cf-api-email` could be used
|
||||
| [Bindman](https://github.com/labbsr0x/bindman-dns-webhook) | `bindman` | `BINDMAN_MANAGER_ADDRESS` | [Additional configuration](https://go-acme.github.io/lego/dns/bindman) |
|
||||
| [Blue Cat](https://www.bluecatnetworks.com/) | `bluecat` | `BLUECAT_SERVER_URL`, `BLUECAT_USER_NAME`, `BLUECAT_PASSWORD`, `BLUECAT_CONFIG_NAME`, `BLUECAT_DNS_VIEW` | [Additional configuration](https://go-acme.github.io/lego/dns/bluecat) |
|
||||
| [ClouDNS](https://www.cloudns.net/) | `cloudns` | `CLOUDNS_AUTH_ID`, `CLOUDNS_AUTH_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/cloudns) |
|
||||
| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | `CF_API_EMAIL`, `CF_API_KEY` or `CF_DNS_API_TOKEN`, `[CF_ZONE_API_TOKEN]` [^5] | [Additional configuration](https://go-acme.github.io/lego/dns/cloudflare) |
|
||||
| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | `CF_API_EMAIL`, `CF_API_KEY` [^5] or `CF_DNS_API_TOKEN`, `[CF_ZONE_API_TOKEN]` | [Additional configuration](https://go-acme.github.io/lego/dns/cloudflare) |
|
||||
| [CloudXNS](https://www.cloudxns.net) | `cloudxns` | `CLOUDXNS_API_KEY`, `CLOUDXNS_SECRET_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/cloudxns) |
|
||||
| [ConoHa](https://www.conoha.jp) | `conoha` | `CONOHA_TENANT_ID`, `CONOHA_API_USERNAME`, `CONOHA_API_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/conoha) |
|
||||
| [DigitalOcean](https://www.digitalocean.com) | `digitalocean` | `DO_AUTH_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/digitalocean) |
|
||||
@@ -291,9 +357,9 @@ For example, `CF_API_EMAIL_FILE=/run/secrets/traefik_cf-api-email` could be used
|
||||
Use custom DNS servers to resolve the FQDN authority.
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[certificatesResolvers.sample.acme]
|
||||
[certificatesResolvers.le.acme]
|
||||
# ...
|
||||
[certificatesResolvers.sample.acme.dnsChallenge]
|
||||
[certificatesResolvers.le.acme.dnsChallenge]
|
||||
# ...
|
||||
resolvers = ["1.1.1.1:53", "8.8.8.8:53"]
|
||||
```
|
||||
@@ -312,7 +378,7 @@ certificatesResolvers:
|
||||
|
||||
```bash tab="CLI"
|
||||
# ...
|
||||
--certificatesResolvers.sample.acme.dnsChallenge.resolvers:="1.1.1.1:53,8.8.8.8:53"
|
||||
--certificatesResolvers.le.acme.dnsChallenge.resolvers:=1.1.1.1:53,8.8.8.8:53
|
||||
```
|
||||
|
||||
#### Wildcard Domains
|
||||
@@ -320,12 +386,14 @@ certificatesResolvers:
|
||||
[ACME V2](https://community.letsencrypt.org/t/acme-v2-and-wildcard-certificate-support-is-live/55579) supports wildcard certificates.
|
||||
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](#dnschallenge).
|
||||
|
||||
## `caServer`
|
||||
## More Configuration
|
||||
|
||||
### `caServer`
|
||||
|
||||
??? example "Using the Let's Encrypt staging server"
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[certificatesResolvers.sample.acme]
|
||||
[certificatesResolvers.le.acme]
|
||||
# ...
|
||||
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
# ...
|
||||
@@ -342,16 +410,16 @@ As described in [Let's Encrypt's post](https://community.letsencrypt.org/t/stagi
|
||||
|
||||
```bash tab="CLI"
|
||||
# ...
|
||||
--certificatesResolvers.sample.acme.caServer="https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
--certificatesResolvers.le.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
# ...
|
||||
```
|
||||
|
||||
## `storage`
|
||||
### `storage`
|
||||
|
||||
The `storage` option sets the location where your ACME certificates are saved to.
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[certificatesResolvers.sample.acme]
|
||||
[certificatesResolvers.le.acme]
|
||||
# ...
|
||||
storage = "acme.json"
|
||||
# ...
|
||||
@@ -368,7 +436,7 @@ certificatesResolvers:
|
||||
|
||||
```bash tab="CLI"
|
||||
# ...
|
||||
--certificatesResolvers.sample.acme.storage=acme.json
|
||||
--certificatesResolvers.le.acme.storage=acme.json
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -376,7 +444,7 @@ The value can refer to some kinds of storage:
|
||||
|
||||
- a JSON file
|
||||
|
||||
### In a File
|
||||
#### In a File
|
||||
|
||||
ACME certificates can be stored in a JSON file that needs to have a `600` file mode .
|
||||
|
||||
|
||||
88
docs/content/https/include-acme-multiple-domains-example.md
Normal file
88
docs/content/https/include-acme-multiple-domains-example.md
Normal file
@@ -0,0 +1,88 @@
|
||||
|
||||
```yaml tab="Docker"
|
||||
## Dynamic configuration
|
||||
labels:
|
||||
- traefik.http.routers.blog.rule=Host(`company.com`) && Path(`/blog`)
|
||||
- traefik.http.routers.blog.tls=true
|
||||
- traefik.http.routers.blog.tls.certresolver=le
|
||||
- traefik.http.routers.blog.tls.domains[0].main=company.org
|
||||
- traefik.http.routers.blog.tls.domains[0].sans=*.company.org
|
||||
```
|
||||
|
||||
```yaml tab="Docker (Swarm)"
|
||||
## Dynamic configuration
|
||||
deploy:
|
||||
labels:
|
||||
- traefik.http.routers.blog.rule=Host(`company.com`) && Path(`/blog`)
|
||||
- traefik.http.services.blog-svc.loadbalancer.server.port=8080"
|
||||
- traefik.http.routers.blog.tls=true
|
||||
- traefik.http.routers.blog.tls.certresolver=le
|
||||
- traefik.http.routers.blog.tls.domains[0].main=company.org
|
||||
- traefik.http.routers.blog.tls.domains[0].sans=*.company.org
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes"
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: blogtls
|
||||
spec:
|
||||
entryPoints:
|
||||
- websecure
|
||||
routes:
|
||||
- match: Host(`company.com`) && Path(`/blog`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: blog
|
||||
port: 8080
|
||||
tls:
|
||||
certResolver: le
|
||||
```
|
||||
|
||||
```json tab="Marathon"
|
||||
labels: {
|
||||
"traefik.http.routers.blog.rule": "Host(`company.com`) && Path(`/blog`)",
|
||||
"traefik.http.routers.blog.tls": "true",
|
||||
"traefik.http.routers.blog.tls.certresolver": "le",
|
||||
"traefik.http.routers.blog.tls.domains[0].main": "company.com",
|
||||
"traefik.http.routers.blog.tls.domains[0].sans": "*.company.com",
|
||||
"traefik.http.services.blog-svc.loadbalancer.server.port": "8080"
|
||||
}
|
||||
```
|
||||
|
||||
```yaml tab="Rancher"
|
||||
## Dynamic configuration
|
||||
labels:
|
||||
- traefik.http.routers.blog.rule=Host(`company.com`) && Path(`/blog`)
|
||||
- traefik.http.routers.blog.tls=true
|
||||
- traefik.http.routers.blog.tls.certresolver=le
|
||||
- traefik.http.routers.blog.tls.domains[0].main=company.org
|
||||
- traefik.http.routers.blog.tls.domains[0].sans=*.company.org
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
## Dynamic configuration
|
||||
[http.routers]
|
||||
[http.routers.blog]
|
||||
rule = "Host(`company.com`) && Path(`/blog`)"
|
||||
[http.routers.blog.tls]
|
||||
certResolver = "le" # From static configuration
|
||||
[[http.routers.blog.tls.domains]]
|
||||
main = "company.org"
|
||||
sans = ["*.company.org"]
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
## Dynamic configuration
|
||||
http:
|
||||
routers:
|
||||
blog:
|
||||
rule: "Host(`company.com`) && Path(`/blog`)"
|
||||
tls:
|
||||
certResolver: le
|
||||
domains:
|
||||
- main: "company.org"
|
||||
sans:
|
||||
- "*.company.org"
|
||||
```
|
||||
@@ -0,0 +1,72 @@
|
||||
|
||||
```yaml tab="Docker"
|
||||
## Dynamic configuration
|
||||
labels:
|
||||
- traefik.http.routers.blog.rule=(Host(`company.com`) && Path(`/blog`)) || Host(`blog.company.org`)
|
||||
- traefik.http.routers.blog.tls=true
|
||||
- traefik.http.routers.blog.tls.certresolver=le
|
||||
```
|
||||
|
||||
```yaml tab="Docker (Swarm)"
|
||||
## Dynamic configuration
|
||||
deploy:
|
||||
labels:
|
||||
- traefik.http.routers.blog.rule=(Host(`company.com`) && Path(`/blog`)) || Host(`blog.company.org`)
|
||||
- traefik.http.routers.blog.tls=true
|
||||
- traefik.http.routers.blog.tls.certresolver=le
|
||||
- traefik.http.services.blog-svc.loadbalancer.server.port=8080"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes"
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: blogtls
|
||||
spec:
|
||||
entryPoints:
|
||||
- websecure
|
||||
routes:
|
||||
- match: (Host(`company.com`) && Path(`/blog`)) || Host(`blog.company.org`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: blog
|
||||
port: 8080
|
||||
tls: {}
|
||||
```
|
||||
|
||||
```json tab="Marathon"
|
||||
labels: {
|
||||
"traefik.http.routers.blog.rule": "(Host(`company.com`) && Path(`/blog`)) || Host(`blog.company.org`)",
|
||||
"traefik.http.routers.blog.tls": "true",
|
||||
"traefik.http.routers.blog.tls.certresolver": "le",
|
||||
"traefik.http.services.blog-svc.loadbalancer.server.port": "8080"
|
||||
}
|
||||
```
|
||||
|
||||
```yaml tab="Rancher"
|
||||
## Dynamic configuration
|
||||
labels:
|
||||
- traefik.http.routers.blog.rule=(Host(`company.com`) && Path(`/blog`)) || Host(`blog.company.org`)
|
||||
- traefik.http.routers.blog.tls=true
|
||||
- traefik.http.routers.blog.tls.certresolver=le
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
## Dynamic configuration
|
||||
[http.routers]
|
||||
[http.routers.blog]
|
||||
rule = "(Host(`company.com`) && Path(`/blog`)) || Host(`blog.company.org`)"
|
||||
[http.routers.blog.tls]
|
||||
certResolver = "le" # From static configuration
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
## Dynamic configuration
|
||||
http:
|
||||
routers:
|
||||
blog:
|
||||
rule: "(Host(`company.com`) && Path(`/blog`)) || Host(`blog.company.org`)"
|
||||
tls:
|
||||
certResolver: le
|
||||
```
|
||||
72
docs/content/https/include-acme-single-domain-example.md
Normal file
72
docs/content/https/include-acme-single-domain-example.md
Normal file
@@ -0,0 +1,72 @@
|
||||
|
||||
```yaml tab="Docker"
|
||||
## Dynamic configuration
|
||||
labels:
|
||||
- traefik.http.routers.blog.rule=Host(`company.com`) && Path(`/blog`)
|
||||
- traefik.http.routers.blog.tls=true
|
||||
- traefik.http.routers.blog.tls.certresolver=le
|
||||
```
|
||||
|
||||
```yaml tab="Docker (Swarm)"
|
||||
## Dynamic configuration
|
||||
deploy:
|
||||
labels:
|
||||
- traefik.http.routers.blog.rule=Host(`company.com`) && Path(`/blog`)
|
||||
- traefik.http.routers.blog.tls=true
|
||||
- traefik.http.routers.blog.tls.certresolver=le
|
||||
- traefik.http.services.blog-svc.loadbalancer.server.port=8080"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes"
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: blogtls
|
||||
spec:
|
||||
entryPoints:
|
||||
- websecure
|
||||
routes:
|
||||
- match: Host(`company.com`) && Path(`/blog`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: blog
|
||||
port: 8080
|
||||
tls: {}
|
||||
```
|
||||
|
||||
```json tab="Marathon"
|
||||
labels: {
|
||||
"traefik.http.routers.blog.rule": "Host(`company.com`) && Path(`/blog`)",
|
||||
"traefik.http.routers.blog.tls": "true",
|
||||
"traefik.http.routers.blog.tls.certresolver": "le",
|
||||
"traefik.http.services.blog-svc.loadbalancer.server.port": "8080"
|
||||
}
|
||||
```
|
||||
|
||||
```yaml tab="Rancher"
|
||||
## Dynamic configuration
|
||||
labels:
|
||||
- traefik.http.routers.blog.rule=Host(`company.com`) && Path(`/blog`)
|
||||
- traefik.http.routers.blog.tls=true
|
||||
- traefik.http.routers.blog.tls.certresolver=le
|
||||
```
|
||||
|
||||
```toml tab="Single Domain"
|
||||
## Dynamic configuration
|
||||
[http.routers]
|
||||
[http.routers.blog]
|
||||
rule = "Host(`company.com`) && Path(`/blog`)"
|
||||
[http.routers.blog.tls]
|
||||
certResolver = "le" # From static configuration
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
## Dynamic configuration
|
||||
http:
|
||||
routers:
|
||||
blog:
|
||||
rule: "Host(`company.com`) && Path(`/blog`)"
|
||||
tls:
|
||||
certResolver: le
|
||||
```
|
||||
@@ -35,13 +35,13 @@
|
||||
#
|
||||
# Optional (but recommended)
|
||||
#
|
||||
[certificatesResolvers.sample.acme.tlsChallenge]
|
||||
[certificatesResolvers.le.acme.tlsChallenge]
|
||||
|
||||
# Use a HTTP-01 ACME challenge.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [certificatesResolvers.sample.acme.httpChallenge]
|
||||
# [certificatesResolvers.le.acme.httpChallenge]
|
||||
|
||||
# EntryPoint to use for the HTTP-01 challenges.
|
||||
#
|
||||
@@ -54,7 +54,7 @@
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [certificatesResolvers.sample.acme.dnsChallenge]
|
||||
# [certificatesResolvers.le.acme.dnsChallenge]
|
||||
|
||||
# DNS provider used.
|
||||
#
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
#
|
||||
# Required
|
||||
#
|
||||
--certificatesResolvers.sample.acme.email="test@traefik.io"
|
||||
--certificatesResolvers.le.acme.email=test@traefik.io
|
||||
|
||||
# File or key used for certificates storage.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
--certificatesResolvers.sample.acme.storage="acme.json"
|
||||
--certificatesResolvers.le.acme.storage=acme.json
|
||||
|
||||
# CA server to use.
|
||||
# Uncomment the line to use Let's Encrypt's staging server,
|
||||
@@ -19,7 +19,7 @@
|
||||
# Optional
|
||||
# Default: "https://acme-v02.api.letsencrypt.org/directory"
|
||||
#
|
||||
--certificatesResolvers.sample.acme.caServer="https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
--certificatesResolvers.le.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
|
||||
# KeyType to use.
|
||||
#
|
||||
@@ -28,38 +28,38 @@
|
||||
#
|
||||
# Available values : "EC256", "EC384", "RSA2048", "RSA4096", "RSA8192"
|
||||
#
|
||||
--certificatesResolvers.sample.acme.keyType=RSA4096
|
||||
--certificatesResolvers.le.acme.keyType=RSA4096
|
||||
|
||||
# Use a TLS-ALPN-01 ACME challenge.
|
||||
#
|
||||
# Optional (but recommended)
|
||||
#
|
||||
--certificatesResolvers.sample.acme.tlsChallenge=true
|
||||
--certificatesResolvers.le.acme.tlsChallenge=true
|
||||
|
||||
# Use a HTTP-01 ACME challenge.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
--certificatesResolvers.sample.acme.httpChallenge=true
|
||||
--certificatesResolvers.le.acme.httpChallenge=true
|
||||
|
||||
# EntryPoint to use for the HTTP-01 challenges.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
--certificatesResolvers.sample.acme.httpChallenge.entryPoint=web
|
||||
--certificatesResolvers.le.acme.httpChallenge.entryPoint=web
|
||||
|
||||
# Use a DNS-01 ACME challenge rather than HTTP-01 challenge.
|
||||
# Note: mandatory for wildcard certificate generation.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
--certificatesResolvers.sample.acme.dnsChallenge=true
|
||||
--certificatesResolvers.le.acme.dnsChallenge=true
|
||||
|
||||
# DNS provider used.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
--certificatesResolvers.sample.acme.dnsChallenge.provider=digitalocean
|
||||
--certificatesResolvers.le.acme.dnsChallenge.provider=digitalocean
|
||||
|
||||
# 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.
|
||||
@@ -68,14 +68,14 @@
|
||||
# Optional
|
||||
# Default: 0
|
||||
#
|
||||
--certificatesResolvers.sample.acme.dnsChallenge.delayBeforeCheck=0
|
||||
--certificatesResolvers.le.acme.dnsChallenge.delayBeforeCheck=0
|
||||
|
||||
# Use following DNS servers to resolve the FQDN authority.
|
||||
#
|
||||
# Optional
|
||||
# Default: empty
|
||||
#
|
||||
--certificatesResolvers.sample.acme.dnsChallenge.resolvers="1.1.1.1:53,8.8.8.8:53"
|
||||
--certificatesResolvers.le.acme.dnsChallenge.resolvers=1.1.1.1:53,8.8.8.8:53
|
||||
|
||||
# Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready.
|
||||
#
|
||||
@@ -85,4 +85,4 @@
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
--certificatesResolvers.sample.acme.dnsChallenge.disablePropagationCheck=true
|
||||
--certificatesResolvers.le.acme.dnsChallenge.disablePropagationCheck=true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
certificatesResolvers:
|
||||
sample:
|
||||
le:
|
||||
# Enable ACME (Let's Encrypt): automatic SSL.
|
||||
acme:
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ tls:
|
||||
|
||||
In the above example, we've used the [file provider](../providers/file.md) to handle these definitions.
|
||||
It is the only available method to configure the certificates (as well as the options and the stores).
|
||||
However, in [Kubernetes](../providers/kubernetes-crd.md), the certificates can and must be provided by [secrets](../routing/providers/kubernetes-crd.md#tls).
|
||||
However, in [Kubernetes](../providers/kubernetes-crd.md), the certificates can and must be provided by [secrets](https://kubernetes.io/docs/concepts/configuration/secret/).
|
||||
|
||||
## Certificates Stores
|
||||
|
||||
|
||||
@@ -20,4 +20,4 @@ Developing Traefik, our main goal is to make it simple to use, and we're sure yo
|
||||
|
||||
!!! info
|
||||
|
||||
If you're a business running critical services behind Traefik, know that [Containous](https://containo.us), the company that sponsors Traefik's development, can provide [commercial support](https://containo.us/services/#commercial-support) and develops an [Enterprise Edition](https://containo.us/traefikee/) of Traefik.
|
||||
If you're a business running critical services behind Traefik, know that [Containous](https://containo.us), the company that sponsors Traefik's development, can provide [commercial support](https://info.containo.us/commercial-services) and develops an [Enterprise Edition](https://containo.us/traefikee/) of Traefik.
|
||||
|
||||
@@ -66,7 +66,7 @@ http:
|
||||
|
||||
### `sourceRange`
|
||||
|
||||
The `sourceRange` option sets the allowed IPs (or ranges of allowed IPs).
|
||||
The `sourceRange` option sets the allowed IPs (or ranges of allowed IPs by using CIDR notation).
|
||||
|
||||
### `ipStrategy`
|
||||
|
||||
|
||||
@@ -406,7 +406,7 @@ In the example, it is the part between `-----BEGIN CERTIFICATE-----` and `-----E
|
||||
!!! info "Extracted data"
|
||||
|
||||
The delimiters and `\n` will be removed.
|
||||
If there are more than one certificate, they are separated by a "`;`".
|
||||
If there are more than one certificate, they are separated by a "`,`".
|
||||
|
||||
!!! warning "`X-Forwarded-Tls-Client-Cert` value could exceed the web server header size limit"
|
||||
|
||||
@@ -421,12 +421,12 @@ The value of the header will be an escaped concatenation of all the selected cer
|
||||
The following example shows an unescaped result that uses all the available fields:
|
||||
|
||||
```text
|
||||
Subject="DC=org,DC=cheese,C=FR,C=US,ST=Cheese org state,ST=Cheese com state,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=*.cheese.com",Issuer="DC=org,DC=cheese,C=FR,C=US,ST=Signing State,ST=Signing State 2,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=Simple Signing CA 2",NB=1544094616,NA=1607166616,SAN=*.cheese.org,*.cheese.net,*.cheese.com,test@cheese.org,test@cheese.net,10.0.1.0,10.0.1.2
|
||||
Subject="DC=org,DC=cheese,C=FR,C=US,ST=Cheese org state,ST=Cheese com state,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=*.cheese.com";Issuer="DC=org,DC=cheese,C=FR,C=US,ST=Signing State,ST=Signing State 2,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=Simple Signing CA 2";NB="1544094616";NA="1607166616";SAN="*.cheese.org,*.cheese.net,*.cheese.com,test@cheese.org,test@cheese.net,10.0.1.0,10.0.1.2"
|
||||
```
|
||||
|
||||
!!! info "Multiple certificates"
|
||||
|
||||
If there are more than one certificate, they are separated by a `;`.
|
||||
If there are more than one certificate, they are separated by a `,`.
|
||||
|
||||
#### `info.notAfter`
|
||||
|
||||
@@ -442,7 +442,7 @@ The data are taken from the following certificate part:
|
||||
The escape `notAfter` info part will be like:
|
||||
|
||||
```text
|
||||
NA=1607166616
|
||||
NA="1607166616"
|
||||
```
|
||||
|
||||
#### `info.notBefore`
|
||||
@@ -459,7 +459,7 @@ Validity
|
||||
The escape `notBefore` info part will be like:
|
||||
|
||||
```text
|
||||
NB=1544094616
|
||||
NB="1544094616"
|
||||
```
|
||||
|
||||
#### `info.sans`
|
||||
@@ -476,7 +476,7 @@ The data are taken from the following certificate part:
|
||||
The escape SANs info part will be like:
|
||||
|
||||
```text
|
||||
SAN=*.cheese.org,*.cheese.net,*.cheese.com,test@cheese.org,test@cheese.net,10.0.1.0,10.0.1.2
|
||||
SAN="*.cheese.org,*.cheese.net,*.cheese.com,test@cheese.org,test@cheese.net,10.0.1.0,10.0.1.2"
|
||||
```
|
||||
|
||||
!!! info "multiple values"
|
||||
|
||||
@@ -104,7 +104,7 @@ Then any router can refer to an instance of the wanted middleware.
|
||||
|
||||
```yaml tab="K8s IngressRoute"
|
||||
# The definitions below require the definitions for the Middleware and IngressRoute kinds.
|
||||
# https://docs.traefik.io/v2.0/providers/kubernetes-crd/#traefik-ingressroute-definition
|
||||
# https://docs.traefik.io/v2.1/reference/dynamic-configuration/kubernetes-crd/#definitions
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
@@ -278,7 +278,7 @@ Then, a [router's TLS field](../routing/routers/index.md#tls) can refer to one o
|
||||
|
||||
```yaml tab="K8s IngressRoute"
|
||||
# The definitions below require the definitions for the TLSOption and IngressRoute kinds.
|
||||
# https://docs.traefik.io/v2.0/providers/kubernetes-crd/#traefik-ingressroute-definition
|
||||
# https://docs.traefik.io/v2.1/reference/dynamic-configuration/kubernetes-crd/#definitions
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: TLSOption
|
||||
metadata:
|
||||
@@ -560,8 +560,8 @@ with the path `/admin` stripped, e.g. to `http://<IP>:<port>/`. In this case, yo
|
||||
```yaml tab="Docker"
|
||||
labels:
|
||||
- "traefik.http.routers.admin.rule=Host(`company.org`) && PathPrefix(`/admin`)"
|
||||
- "traefik.http.routers.admin.middlewares=admin-stripprefix"
|
||||
- "traefik.http.middlewares.admin-stripprefix.stripprefix.prefixes=/admin"
|
||||
- "traefik.http.routers.web.middlewares=admin-stripprefix@docker"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes IngressRoute"
|
||||
@@ -718,11 +718,11 @@ with the path `/admin` stripped, e.g. to `http://<IP>:<port>/`. In this case, yo
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--entryPoints.web.address=":80"
|
||||
--entryPoints.websecure.address=":443"
|
||||
--certificatesResolvers.sample.acme.email: your-email@your-domain.org
|
||||
--certificatesResolvers.sample.acme.storage: acme.json
|
||||
--certificatesResolvers.sample.acme.httpChallenge.entryPoint: web
|
||||
--entryPoints.web.address=:80
|
||||
--entryPoints.websecure.address=:443
|
||||
--certificatesResolvers.sample.acme.email=your-email@your-domain.org
|
||||
--certificatesResolvers.sample.acme.storage=acme.json
|
||||
--certificatesResolvers.sample.acme.httpChallenge.entryPoint=web
|
||||
```
|
||||
|
||||
## Traefik Logs
|
||||
@@ -744,9 +744,9 @@ There is no more log configuration at the root level.
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--logLevel="DEBUG"
|
||||
--traefikLog.filePath="/path/to/traefik.log"
|
||||
--traefikLog.format="json"
|
||||
--logLevel=DEBUG
|
||||
--traefikLog.filePath=/path/to/traefik.log
|
||||
--traefikLog.format=json
|
||||
```
|
||||
|
||||
!!! info "v2"
|
||||
@@ -768,9 +768,9 @@ There is no more log configuration at the root level.
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--log.level="DEBUG"
|
||||
--log.filePath="/path/to/traefik.log"
|
||||
--log.format="json"
|
||||
--log.level=DEBUG
|
||||
--log.filePath=/path/to/traefik.log
|
||||
--log.format=json
|
||||
```
|
||||
|
||||
## Tracing
|
||||
@@ -794,12 +794,12 @@ Traefik v2 retains OpenTracing support. The `backend` root option from the v1 is
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.backend="jaeger"
|
||||
--tracing.servicename="tracing"
|
||||
--tracing.jaeger.localagenthostport="12.0.0.1:6831"
|
||||
--tracing.jaeger.samplingparam="1.0"
|
||||
--tracing.jaeger.samplingserverurl="http://12.0.0.1:5778/sampling"
|
||||
--tracing.jaeger.samplingtype="const"
|
||||
--tracing.backend=jaeger
|
||||
--tracing.servicename=tracing
|
||||
--tracing.jaeger.localagenthostport=12.0.0.1:6831
|
||||
--tracing.jaeger.samplingparam=1.0
|
||||
--tracing.jaeger.samplingserverurl=http://12.0.0.1:5778/sampling
|
||||
--tracing.jaeger.samplingtype=const
|
||||
```
|
||||
|
||||
!!! info "v2"
|
||||
@@ -827,11 +827,11 @@ Traefik v2 retains OpenTracing support. The `backend` root option from the v1 is
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.servicename="tracing"
|
||||
--tracing.jaeger.localagenthostport="12.0.0.1:6831"
|
||||
--tracing.jaeger.samplingparam="1.0"
|
||||
--tracing.jaeger.samplingserverurl="http://12.0.0.1:5778/sampling"
|
||||
--tracing.jaeger.samplingtype="const"
|
||||
--tracing.servicename=tracing
|
||||
--tracing.jaeger.localagenthostport=12.0.0.1:6831
|
||||
--tracing.jaeger.samplingparam=1.0
|
||||
--tracing.jaeger.samplingserverurl=http://12.0.0.1:5778/sampling
|
||||
--tracing.jaeger.samplingtype=const
|
||||
```
|
||||
|
||||
## Metrics
|
||||
@@ -852,7 +852,7 @@ For a basic configuration, the [metrics configuration](../observability/metrics/
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.prometheus.buckets=[0.1,0.3,1.2,5.0]
|
||||
--metrics.prometheus.entrypoint="traefik"
|
||||
--metrics.prometheus.entrypoint=traefik
|
||||
```
|
||||
|
||||
!!! info "v2"
|
||||
@@ -878,7 +878,7 @@ For a basic configuration, the [metrics configuration](../observability/metrics/
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.prometheus.buckets=[0.1,0.3,1.2,5.0]
|
||||
--metrics.prometheus.entrypoint="metrics"
|
||||
--metrics.prometheus.entrypoint=metrics
|
||||
```
|
||||
|
||||
## No More Root Level Key/Values
|
||||
@@ -908,14 +908,14 @@ Each root item has been moved to a related section or removed.
|
||||
```bash tab="CLI"
|
||||
--checknewversion=false
|
||||
--sendanonymoususage=true
|
||||
--loglevel="DEBUG"
|
||||
--loglevel=DEBUG
|
||||
--insecureskipverify=true
|
||||
--rootcas="/mycert.cert"
|
||||
--rootcas=/mycert.cert
|
||||
--maxidleconnsperhost=200
|
||||
--providersthrottleduration="2s"
|
||||
--providersthrottleduration=2s
|
||||
--allowminweightzero=true
|
||||
--debug=true
|
||||
--defaultentrypoints="web","web-secure"
|
||||
--defaultentrypoints=web,web-secure
|
||||
--keeptrailingslash=true
|
||||
```
|
||||
|
||||
@@ -961,9 +961,9 @@ Each root item has been moved to a related section or removed.
|
||||
```bash tab="CLI"
|
||||
--global.checknewversion=true
|
||||
--global.sendanonymoususage=true
|
||||
--log.level="DEBUG"
|
||||
--log.level=DEBUG
|
||||
--serverstransport.insecureskipverify=true
|
||||
--serverstransport.rootcas="/mycert.cert"
|
||||
--serverstransport.rootcas=/mycert.cert
|
||||
--serverstransport.maxidleconnsperhost=42
|
||||
--providers.providersthrottleduration=42
|
||||
```
|
||||
@@ -1029,12 +1029,12 @@ As the dashboard access is now secured by default you can either:
|
||||
[api]
|
||||
|
||||
[providers.file]
|
||||
filename = "/dynamic-conf.toml"
|
||||
directory = "/path/to/dynamic/config"
|
||||
|
||||
##---------------------##
|
||||
|
||||
## dynamic configuration
|
||||
# dynamic-conf.toml
|
||||
# /path/to/dynamic/config/dynamic-conf.toml
|
||||
|
||||
[http.routers.api]
|
||||
rule = "Host(`traefik.docker.localhost`)"
|
||||
@@ -1061,12 +1061,12 @@ As the dashboard access is now secured by default you can either:
|
||||
|
||||
providers:
|
||||
file:
|
||||
filename: /dynamic-conf.yaml
|
||||
directory: /path/to/dynamic/config
|
||||
|
||||
##---------------------##
|
||||
|
||||
## dynamic configuration
|
||||
# dynamic-conf.yaml
|
||||
# /path/to/dynamic/config/dynamic-conf.yaml
|
||||
|
||||
http:
|
||||
routers:
|
||||
|
||||
99
docs/content/migration/v2.md
Normal file
99
docs/content/migration/v2.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Migration: Steps needed between the versions
|
||||
|
||||
## v2.0 to v2.1
|
||||
|
||||
In v2.1, a new CRD called `TraefikService` was added. While updating an installation to v2.1,
|
||||
it is required to apply that CRD before as well as enhance the existing `ClusterRole` definition to allow Traefik to use that CRD.
|
||||
|
||||
To add that CRD and enhance the permissions, following definitions need to be applied to the cluster.
|
||||
|
||||
```yaml tab="TraefikService"
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: traefikservices.traefik.containo.us
|
||||
|
||||
spec:
|
||||
group: traefik.containo.us
|
||||
version: v1alpha1
|
||||
names:
|
||||
kind: TraefikService
|
||||
plural: traefikservices
|
||||
singular: traefikservice
|
||||
scope: Namespaced
|
||||
```
|
||||
|
||||
```yaml tab="ClusterRole"
|
||||
kind: ClusterRole
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: traefik-ingress-controller
|
||||
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- services
|
||||
- endpoints
|
||||
- secrets
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- ingresses
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- ingresses/status
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- traefik.containo.us
|
||||
resources:
|
||||
- middlewares
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- traefik.containo.us
|
||||
resources:
|
||||
- ingressroutes
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- traefik.containo.us
|
||||
resources:
|
||||
- ingressroutetcps
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- traefik.containo.us
|
||||
resources:
|
||||
- tlsoptions
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- traefik.containo.us
|
||||
resources:
|
||||
- traefikservices
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
```
|
||||
|
||||
After having both resources applied, Traefik will work properly.
|
||||
@@ -61,7 +61,7 @@ accessLog:
|
||||
```bash tab="CLI"
|
||||
# Configuring a buffer of 100 lines
|
||||
--accesslog=true
|
||||
--accesslog.filepath="/path/to/access.log"
|
||||
--accesslog.filepath=/path/to/access.log
|
||||
--accesslog.bufferingsize=100
|
||||
```
|
||||
|
||||
@@ -104,11 +104,11 @@ accessLog:
|
||||
```bash tab="CLI"
|
||||
# Configuring Multiple Filters
|
||||
--accesslog=true
|
||||
--accesslog.filepath="/path/to/access.log"
|
||||
--accesslog.format="json"
|
||||
--accesslog.filters.statuscodes="200, 300-302"
|
||||
--accesslog.filepath=/path/to/access.log
|
||||
--accesslog.format=json
|
||||
--accesslog.filters.statuscodes=200,300-302
|
||||
--accesslog.filters.retryattempts
|
||||
--accesslog.filters.minduration="10ms"
|
||||
--accesslog.filters.minduration=10ms
|
||||
```
|
||||
|
||||
### Limiting the Fields
|
||||
@@ -164,14 +164,14 @@ accessLog:
|
||||
```bash tab="CLI"
|
||||
# Limiting the Logs to Specific Fields
|
||||
--accesslog=true
|
||||
--accesslog.filepath="/path/to/access.log"
|
||||
--accesslog.format="json"
|
||||
--accesslog.fields.defaultmode="keep"
|
||||
--accesslog.fields.names.ClientUsername="drop"
|
||||
--accesslog.fields.headers.defaultmode="keep"
|
||||
--accesslog.fields.headers.names.User-Agent="redact"
|
||||
--accesslog.fields.headers.names.Authorization="drop"
|
||||
--accesslog.fields.headers.names.Content-Type="keep"
|
||||
--accesslog.filepath=/path/to/access.log
|
||||
--accesslog.format=json
|
||||
--accesslog.fields.defaultmode=keep
|
||||
--accesslog.fields.names.ClientUsername=drop
|
||||
--accesslog.fields.headers.defaultmode=keep
|
||||
--accesslog.fields.headers.names.User-Agent=redact
|
||||
--accesslog.fields.headers.names.Authorization=drop
|
||||
--accesslog.fields.headers.names.Content-Type=keep
|
||||
```
|
||||
|
||||
??? info "Available Fields"
|
||||
|
||||
@@ -30,7 +30,7 @@ log:
|
||||
|
||||
```bash tab="CLI"
|
||||
# Writing Logs to a File
|
||||
--log.filePath="/path/to/traefik.log"
|
||||
--log.filePath=/path/to/traefik.log
|
||||
```
|
||||
|
||||
#### `format`
|
||||
@@ -53,8 +53,8 @@ log:
|
||||
|
||||
```bash tab="CLI"
|
||||
# Writing Logs to a File, in JSON
|
||||
--log.filePath="/path/to/traefik.log"
|
||||
--log.format="json"
|
||||
--log.filePath=/path/to/traefik.log
|
||||
--log.format=json
|
||||
```
|
||||
|
||||
#### `level`
|
||||
@@ -72,7 +72,7 @@ log:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--log.level="DEBUG"
|
||||
--log.level=DEBUG
|
||||
```
|
||||
|
||||
## Log Rotation
|
||||
|
||||
@@ -35,7 +35,7 @@ metrics:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.datadog.address="127.0.0.1:8125"
|
||||
--metrics.datadog.address=127.0.0.1:8125
|
||||
```
|
||||
|
||||
#### `addEntryPointsLabels`
|
||||
|
||||
@@ -35,7 +35,7 @@ metrics:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.influxdb.address="localhost:8089"
|
||||
--metrics.influxdb.address=localhost:8089
|
||||
```
|
||||
|
||||
#### `protocol`
|
||||
@@ -57,7 +57,7 @@ metrics:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.influxdb.protocol="udp"
|
||||
--metrics.influxdb.protocol=udp
|
||||
```
|
||||
|
||||
#### `database`
|
||||
@@ -69,17 +69,17 @@ InfluxDB database used when protocol is http.
|
||||
```toml tab="File (TOML)"
|
||||
[metrics]
|
||||
[metrics.influxDB]
|
||||
database = ""
|
||||
database = "db"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
metrics:
|
||||
influxDB:
|
||||
database: ""
|
||||
database: "db"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.influxdb.database=""
|
||||
--metrics.influxdb.database=db
|
||||
```
|
||||
|
||||
#### `retentionPolicy`
|
||||
@@ -91,17 +91,17 @@ InfluxDB retention policy used when protocol is http.
|
||||
```toml tab="File (TOML)"
|
||||
[metrics]
|
||||
[metrics.influxDB]
|
||||
retentionPolicy = ""
|
||||
retentionPolicy = "two_hours"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
metrics:
|
||||
influxDB:
|
||||
retentionPolicy: ""
|
||||
retentionPolicy: "two_hours"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.influxdb.retentionPolicy=""
|
||||
--metrics.influxdb.retentionPolicy=two_hours
|
||||
```
|
||||
|
||||
#### `username`
|
||||
@@ -113,17 +113,17 @@ InfluxDB username (only with http).
|
||||
```toml tab="File (TOML)"
|
||||
[metrics]
|
||||
[metrics.influxDB]
|
||||
username = ""
|
||||
username = "john"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
metrics:
|
||||
influxDB:
|
||||
username: ""
|
||||
username: "john"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.influxdb.username=""
|
||||
--metrics.influxdb.username=john
|
||||
```
|
||||
|
||||
#### `password`
|
||||
@@ -135,17 +135,17 @@ InfluxDB password (only with http).
|
||||
```toml tab="File (TOML)"
|
||||
[metrics]
|
||||
[metrics.influxDB]
|
||||
password = ""
|
||||
password = "secret"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
metrics:
|
||||
influxDB:
|
||||
password: ""
|
||||
password: "secret"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.influxdb.password=""
|
||||
--metrics.influxdb.password=secret
|
||||
```
|
||||
|
||||
#### `addEntryPointsLabels`
|
||||
|
||||
@@ -113,8 +113,8 @@ metrics:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--entryPoints.metrics.address=":8082"
|
||||
--metrics.prometheus.entryPoint="metrics"
|
||||
--entryPoints.metrics.address=:8082
|
||||
--metrics.prometheus.entryPoint=metrics
|
||||
```
|
||||
|
||||
#### `manualRouting`
|
||||
|
||||
@@ -35,7 +35,7 @@ metrics:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.statsd.address="localhost:8125"
|
||||
--metrics.statsd.address=localhost:8125
|
||||
```
|
||||
|
||||
#### `addEntryPointsLabels`
|
||||
|
||||
@@ -35,7 +35,7 @@ tracing:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.datadog.localAgentHostPort="127.0.0.1:8126"
|
||||
--tracing.datadog.localAgentHostPort=127.0.0.1:8126
|
||||
```
|
||||
|
||||
#### `debug`
|
||||
@@ -79,7 +79,7 @@ tracing:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.datadog.globalTag="sample"
|
||||
--tracing.datadog.globalTag=sample
|
||||
```
|
||||
|
||||
#### `prioritySampling`
|
||||
|
||||
@@ -35,7 +35,7 @@ tracing:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.haystack.localAgentHost="127.0.0.1"
|
||||
--tracing.haystack.localAgentHost=127.0.0.1
|
||||
```
|
||||
|
||||
#### `localAgentPort`
|
||||
@@ -79,7 +79,7 @@ tracing:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.haystack.globalTag="sample:test"
|
||||
--tracing.haystack.globalTag=sample:test
|
||||
```
|
||||
|
||||
#### `traceIDHeaderName`
|
||||
@@ -101,7 +101,7 @@ tracing:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.haystack.traceIDHeaderName="sample"
|
||||
--tracing.haystack.traceIDHeaderName=sample
|
||||
```
|
||||
|
||||
#### `parentIDHeaderName`
|
||||
@@ -123,7 +123,7 @@ tracing:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.haystack.parentIDHeaderName="sample"
|
||||
--tracing.haystack.parentIDHeaderName=sample
|
||||
```
|
||||
|
||||
#### `spanIDHeaderName`
|
||||
@@ -168,5 +168,5 @@ tracing:
|
||||
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.haystack.baggagePrefixHeaderName="sample"
|
||||
--tracing.haystack.baggagePrefixHeaderName=sample
|
||||
```
|
||||
|
||||
@@ -35,7 +35,7 @@ tracing:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.instana.localAgentHost="127.0.0.1"
|
||||
--tracing.instana.localAgentHost=127.0.0.1
|
||||
```
|
||||
|
||||
#### `localAgentPort`
|
||||
@@ -86,5 +86,5 @@ tracing:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.instana.logLevel="info"
|
||||
--tracing.instana.logLevel=info
|
||||
```
|
||||
|
||||
@@ -39,7 +39,7 @@ tracing:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.jaeger.samplingServerURL="http://localhost:5778/sampling"
|
||||
--tracing.jaeger.samplingServerURL=http://localhost:5778/sampling
|
||||
```
|
||||
|
||||
#### `samplingType`
|
||||
@@ -61,7 +61,7 @@ tracing:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.jaeger.samplingType="const"
|
||||
--tracing.jaeger.samplingType=const
|
||||
```
|
||||
|
||||
#### `samplingParam`
|
||||
@@ -89,7 +89,7 @@ tracing:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.jaeger.samplingParam="1.0"
|
||||
--tracing.jaeger.samplingParam=1.0
|
||||
```
|
||||
|
||||
#### `localAgentHostPort`
|
||||
@@ -111,7 +111,7 @@ tracing:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.jaeger.localAgentHostPort="127.0.0.1:6831"
|
||||
--tracing.jaeger.localAgentHostPort=127.0.0.1:6831
|
||||
```
|
||||
|
||||
#### `gen128Bit`
|
||||
@@ -159,7 +159,7 @@ tracing:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.jaeger.propagation="jaeger"
|
||||
--tracing.jaeger.propagation=jaeger
|
||||
```
|
||||
|
||||
#### `traceContextHeaderName`
|
||||
@@ -182,7 +182,7 @@ tracing:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.jaeger.traceContextHeaderName="uber-trace-id"
|
||||
--tracing.jaeger.traceContextHeaderName=uber-trace-id
|
||||
```
|
||||
|
||||
### `collector`
|
||||
@@ -206,7 +206,7 @@ tracing:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.jaeger.collector.endpoint="http://127.0.0.1:14268/api/traces?format=jaeger.thrift"
|
||||
--tracing.jaeger.collector.endpoint=http://127.0.0.1:14268/api/traces?format=jaeger.thrift
|
||||
```
|
||||
|
||||
#### `user`
|
||||
@@ -229,7 +229,7 @@ tracing:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.jaeger.collector.user="my-user"
|
||||
--tracing.jaeger.collector.user=my-user
|
||||
```
|
||||
|
||||
#### `password`
|
||||
@@ -252,5 +252,5 @@ tracing:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.jaeger.collector.password="my-password"
|
||||
--tracing.jaeger.collector.password=my-password
|
||||
```
|
||||
|
||||
@@ -52,7 +52,7 @@ tracing:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.serviceName="traefik"
|
||||
--tracing.serviceName=traefik
|
||||
```
|
||||
|
||||
#### `spanNameLimit`
|
||||
|
||||
@@ -35,7 +35,7 @@ tracing:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.zipkin.httpEndpoint="http://localhost:9411/api/v2/spans"
|
||||
--tracing.zipkin.httpEndpoint=http://localhost:9411/api/v2/spans
|
||||
```
|
||||
|
||||
#### `sameSpan`
|
||||
@@ -101,5 +101,5 @@ tracing:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.zipkin.sampleRate="0.2"
|
||||
--tracing.zipkin.sampleRate=0.2
|
||||
```
|
||||
|
||||
@@ -85,19 +85,17 @@ We recommend to use a "Host Based rule" as ```Host(`traefik.domain.com`)``` to m
|
||||
or to make sure that the defined rule captures both prefixes:
|
||||
|
||||
```bash tab="Host Rule"
|
||||
# Matches http://traefik.domain.com/api or http://traefik.domain.com/dashboard
|
||||
# The dashboard can be accessed on http://traefik.domain.com/dashboard/
|
||||
rule = "Host(`traefik.domain.com`)"
|
||||
```
|
||||
|
||||
```bash tab="Path Prefix Rule"
|
||||
# Matches http://traefik.domain.com/api , http://domain.com/api or http://traefik.domain.com/dashboard
|
||||
# but does not match http://traefik.domain.com/hello
|
||||
# The dashboard can be accessed on http://domain.com/dashboard/ or http://traefik.domain.com/dashboard/
|
||||
rule = "PathPrefix(`/api`) || PathPrefix(`/dashboard`)"
|
||||
```
|
||||
|
||||
```bash tab="Combination of Rules"
|
||||
# Matches http://traefik.domain.com/api or http://traefik.domain.com/dashboard
|
||||
# but does not match http://traefik.domain.com/hello
|
||||
# The dashboard can be accessed on http://traefik.domain.com/dashboard/
|
||||
rule = "Host(`traefik.domain.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
|
||||
```
|
||||
|
||||
|
||||
@@ -19,6 +19,28 @@ deploy:
|
||||
- "traefik.http.services.dummy-svc.loadbalancer.server.port=9999"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes CRD"
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: traefik-dashboard
|
||||
spec:
|
||||
routes:
|
||||
- match: Host(`traefik.domain.com`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: api@internal
|
||||
kind: TraefikService
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: auth
|
||||
spec:
|
||||
basicAuth:
|
||||
secret: secretName # Kubernetes secret named "secretName"
|
||||
```
|
||||
|
||||
```yaml tab="Consul Catalog"
|
||||
# Dynamic Configuration
|
||||
- "traefik.http.routers.api.rule=Host(`traefik.domain.com`)"
|
||||
|
||||
@@ -55,11 +55,11 @@ ping:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--entryPoints.ping.address=":8082"
|
||||
--ping.entryPoint="ping"
|
||||
--entryPoints.ping.address=:8082
|
||||
--ping.entryPoint=ping
|
||||
```
|
||||
|
||||
#### `manualRouting`
|
||||
### `manualRouting`
|
||||
|
||||
_Optional, Default=false_
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
# Traefik & Consul Catalog
|
||||
|
||||
A Story of Labels, Services & Containers
|
||||
A Story of Tags, Services & Instances
|
||||
{: .subtitle }
|
||||
|
||||

|
||||
|
||||
Attach labels to your services and let Traefik do the rest!
|
||||
Attach tags to your services and let Traefik do the rest!
|
||||
|
||||
## Configuration Examples
|
||||
|
||||
??? example "Configuring Consul Catalog & Deploying / Exposing Services"
|
||||
|
||||
Enabling the consulcatalog provider
|
||||
Enabling the consul catalog provider
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.consulCatalog]
|
||||
@@ -26,11 +26,10 @@ Attach labels to your services and let Traefik do the rest!
|
||||
--providers.consulcatalog=true
|
||||
```
|
||||
|
||||
Attaching labels to services
|
||||
Attaching tags to services
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- traefik.http.services.my-service.rule=Host(`mydomain.com`)
|
||||
- traefik.http.services.my-service.rule=Host(`mydomain.com`)
|
||||
```
|
||||
|
||||
## Routing Configuration
|
||||
@@ -65,27 +64,27 @@ Defines the polling interval.
|
||||
|
||||
### `prefix`
|
||||
|
||||
_Optional, Default=/latest_
|
||||
_required, Default="traefik"_
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.consulCatalog]
|
||||
prefix = "/test"
|
||||
prefix = "test"
|
||||
# ...
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
consulCatalog:
|
||||
prefix: /test
|
||||
prefix: test
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.consulcatalog.prefix=/test
|
||||
--providers.consulcatalog.prefix=test
|
||||
# ...
|
||||
```
|
||||
|
||||
Prefix used for accessing the Consul service metadata.
|
||||
The prefix for Consul Catalog tags defining traefik labels.
|
||||
|
||||
### `requireConsistent`
|
||||
|
||||
@@ -161,7 +160,7 @@ Use local agent caching for catalog reads.
|
||||
|
||||
### `endpoint`
|
||||
|
||||
Defines Consul server endpoint.
|
||||
Defines the Consul server endpoint.
|
||||
|
||||
#### `address`
|
||||
|
||||
@@ -504,7 +503,7 @@ providers:
|
||||
```
|
||||
|
||||
Expose Consul Catalog services by default in Traefik.
|
||||
If set to false, services that don't have a `traefik.enable=true` label will be ignored from the resulting routing configuration.
|
||||
If set to false, services that don't have a `traefik.enable=true` tag will be ignored from the resulting routing configuration.
|
||||
|
||||
See also [Restrict the Scope of Service Discovery](./overview.md#restrict-the-scope-of-service-discovery).
|
||||
|
||||
@@ -532,13 +531,13 @@ providers:
|
||||
|
||||
The default host rule for all services.
|
||||
|
||||
For a given container if no routing rule was defined by a label, it is defined by this defaultRule instead.
|
||||
For a given service if no routing rule was defined by a tag, it is defined by this defaultRule instead.
|
||||
It must be a valid [Go template](https://golang.org/pkg/text/template/),
|
||||
augmented with the [sprig template functions](http://masterminds.github.io/sprig/).
|
||||
The service name can be accessed as the `Name` identifier,
|
||||
and the template has access to all the labels defined on this container.
|
||||
and the template has access to all the labels (i.e. tags beginning with the `prefix`) defined on this service.
|
||||
|
||||
This option can be overridden on a container basis with the `traefik.http.routers.Router1.rule` label.
|
||||
The option can be overridden on an instance basis with the `traefik.http.routers.{name-of-your-choice}.rule` tag.
|
||||
|
||||
### `constraints`
|
||||
|
||||
@@ -546,58 +545,59 @@ _Optional, Default=""_
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.consulCatalog]
|
||||
constraints = "Label(`a.label.name`, `foo`)"
|
||||
constraints = "Tag(`a.tag.name`)"
|
||||
# ...
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
consulCatalog:
|
||||
constraints: "Label(`a.label.name`, `foo`)"
|
||||
constraints: "Tag(`a.tag.name`)"
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.consulcatalog.constraints="Label(`a.label.name`, `foo`)"
|
||||
--providers.consulcatalog.constraints="Tag(`a.tag.name`)"
|
||||
# ...
|
||||
```
|
||||
|
||||
Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container.
|
||||
That is to say, if none of the container's labels match the expression, no route for the container is created.
|
||||
If the expression is empty, all detected containers are included.
|
||||
Constraints is an expression that Traefik matches against the service's tags to determine whether to create any route for that service.
|
||||
That is to say, if none of the service's tags match the expression, no route for that service is created.
|
||||
If the expression is empty, all detected services are included.
|
||||
|
||||
The expression syntax is based on the `Label("key", "value")`, and `LabelRegex("key", "value")` functions, as well as the usual boolean logic, as shown in examples below.
|
||||
The expression syntax is based on the `Tag("tag")`, and `TagRegex("tag")` functions,
|
||||
as well as the usual boolean logic, as shown in examples below.
|
||||
|
||||
??? example "Constraints Expression Examples"
|
||||
|
||||
```toml
|
||||
# Includes only containers having a label with key `a.label.name` and value `foo`
|
||||
constraints = "Label(`a.label.name`, `foo`)"
|
||||
# Includes only services having the tag `a.tag.name=foo`
|
||||
constraints = "Tag(`a.tag.name=foo`)"
|
||||
```
|
||||
|
||||
```toml
|
||||
# Excludes containers having any label with key `a.label.name` and value `foo`
|
||||
constraints = "!Label(`a.label.name`, `value`)"
|
||||
# Excludes services having any tag `a.tag.name=foo`
|
||||
constraints = "!Tag(`a.tag.name=foo`)"
|
||||
```
|
||||
|
||||
```toml
|
||||
# With logical AND.
|
||||
constraints = "Label(`a.label.name`, `valueA`) && Label(`another.label.name`, `valueB`)"
|
||||
constraints = "Tag(`a.tag.name`) && Tag(`another.tag.name`)"
|
||||
```
|
||||
|
||||
```toml
|
||||
# With logical OR.
|
||||
constraints = "Label(`a.label.name`, `valueA`) || Label(`another.label.name`, `valueB`)"
|
||||
constraints = "Tag(`a.tag.name`) || Tag(`another.tag.name`)"
|
||||
```
|
||||
|
||||
```toml
|
||||
# With logical AND and OR, with precedence set by parentheses.
|
||||
constraints = "Label(`a.label.name`, `valueA`) && (Label(`another.label.name`, `valueB`) || Label(`yet.another.label.name`, `valueC`))"
|
||||
constraints = "Tag(`a.tag.name`) && (Tag(`another.tag.name`) || Tag(`yet.another.tag.name`))"
|
||||
```
|
||||
|
||||
```toml
|
||||
# Includes only containers having a label with key `a.label.name` and a value matching the `a.+` regular expression.
|
||||
constraints = "LabelRegex(`a.label.name`, `a.+`)"
|
||||
# Includes only services having a tag matching the `a\.tag\.t.+` regular expression.
|
||||
constraints = "TagRegex(`a\.tag\.t.+`)"
|
||||
```
|
||||
|
||||
See also [Restrict the Scope of Service Discovery](./overview.md#restrict-the-scope-of-service-discovery).
|
||||
|
||||
@@ -7,6 +7,9 @@ A Story of Labels & Containers
|
||||
|
||||
Attach labels to your containers and let Traefik do the rest!
|
||||
|
||||
Traefik works with both [Docker (standalone) Engine](https://docs.docker.com/engine/)
|
||||
and [Docker Swarm Mode](https://docs.docker.com/engine/swarm/).
|
||||
|
||||
!!! tip "The Quick Start Uses Docker"
|
||||
If you haven't already, maybe you'd like to go through the [quick start](../getting-started/quick-start.md) that uses the docker provider!
|
||||
|
||||
@@ -64,7 +67,7 @@ Attach labels to your containers and let Traefik do the rest!
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.docker.endpoint="tcp://127.0.0.1:2375"
|
||||
--providers.docker.endpoint=tcp://127.0.0.1:2375
|
||||
--providers.docker.swarmMode=true
|
||||
```
|
||||
|
||||
@@ -80,15 +83,136 @@ Attach labels to your containers and let Traefik do the rest!
|
||||
- traefik.http.services.my-container-service.loadbalancer.server.port=8080
|
||||
```
|
||||
|
||||
!!! important "Labels in Docker Swarm Mode"
|
||||
While in Swarm Mode, Traefik uses labels found on services, not on individual containers.
|
||||
|
||||
Therefore, if you use a compose file with Swarm Mode, labels should be defined in the `deploy` part of your service.
|
||||
This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file/#labels-1)).
|
||||
|
||||
## Routing Configuration
|
||||
|
||||
See the dedicated section in [routing](../routing/providers/docker.md).
|
||||
When using Docker as a [provider](https://docs.traefik.io/providers/overview/),
|
||||
Traefik uses [container labels](https://docs.docker.com/engine/reference/commandline/run/#set-metadata-on-container--l---label---label-file) to retrieve its routing configuration.
|
||||
|
||||
See the list of labels in the dedicated [routing](../routing/providers/docker.md) section.
|
||||
|
||||
### Routing Configuration with Labels
|
||||
|
||||
By default, Traefik watches for [container level labels](https://docs.docker.com/config/labels-custom-metadata/) on a standalone Docker Engine.
|
||||
|
||||
When using Docker Compose, labels are specified by the directive
|
||||
[`labels`](https://docs.docker.com/compose/compose-file/#labels) from the
|
||||
["services" objects](https://docs.docker.com/compose/compose-file/#service-configuration-reference).
|
||||
|
||||
!!! tip "Not Only Docker"
|
||||
Please note that any tool like Nomad, Terraform, Ansible, etc.
|
||||
that is able to define a Docker container with labels can work
|
||||
with Traefik & the Docker provider.
|
||||
|
||||
### Port Detection
|
||||
|
||||
Traefik retrieves the private IP and port of containers from the Docker API.
|
||||
|
||||
Ports detection works as follows:
|
||||
|
||||
- If a container [exposes](https://docs.docker.com/engine/reference/builder/#expose) only one port,
|
||||
then Traefik uses this port for private communication.
|
||||
- If a container [exposes](https://docs.docker.com/engine/reference/builder/#expose) multiple ports,
|
||||
or does not expose any port, then you must manually specify which port Traefik should use for communication
|
||||
by using the label `traefik.http.services.<service_name>.loadbalancer.server.port`
|
||||
(Read more on this label in the dedicated section in [routing](../routing/providers/docker.md#port)).
|
||||
|
||||
### Docker API Access
|
||||
|
||||
Traefik requires access to the docker socket to get its dynamic configuration.
|
||||
|
||||
You can specify which Docker API Endpoint to use with the directive [`endpoint`](#endpoint).
|
||||
|
||||
!!! warning "Security Note"
|
||||
|
||||
Accessing the Docker API without any restriction is a security concern:
|
||||
If Traefik is attacked, then the attacker might get access to the underlying host.
|
||||
{: #security-note }
|
||||
|
||||
As explained in the Docker documentation: ([Docker Daemon Attack Surface page](https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface)):
|
||||
|
||||
!!! quote
|
||||
[...] only **trusted** users should be allowed to control your Docker daemon [...]
|
||||
|
||||
??? success "Solutions"
|
||||
|
||||
Expose the Docker socket over TCP, instead of the default Unix socket file.
|
||||
It allows different implementation levels of the [AAA (Authentication, Authorization, Accounting) concepts](https://en.wikipedia.org/wiki/AAA_(computer_security)), depending on your security assessment:
|
||||
|
||||
- Authentication with Client Certificates as described in ["Protect the Docker daemon socket."](https://docs.docker.com/engine/security/https/)
|
||||
- Authorize and filter requests to restrict possible actions with [the TecnativaDocker Socket Proxy](https://github.com/Tecnativa/docker-socket-proxy).
|
||||
- Authorization with the [Docker Authorization Plugin Mechanism](https://docs.docker.com/engine/extend/plugins_authorization/)
|
||||
- Accounting at networking level, by exposing the socket only inside a Docker private network, only available for Traefik.
|
||||
- Accounting at container level, by exposing the socket on a another container than Traefik's.
|
||||
With Swarm mode, it allows scheduling of Traefik on worker nodes, with only the "socket exposer" container on the manager nodes.
|
||||
- Accounting at kernel level, by enforcing kernel calls with mechanisms like [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux), to only allows an identified set of actions for Traefik's process (or the "socket exposer" process).
|
||||
|
||||
??? info "More Resources and Examples"
|
||||
- ["Paranoid about mounting /var/run/docker.sock?"](https://medium.com/@containeroo/traefik-2-0-paranoid-about-mounting-var-run-docker-sock-22da9cb3e78c)
|
||||
- [Traefik and Docker: A Discussion with Docker Captain, Bret Fisher](https://blog.containo.us/traefik-and-docker-a-discussion-with-docker-captain-bret-fisher-7f0b9a54ff88)
|
||||
- [KubeCon EU 2018 Keynote, Running with Scissors, from Liz Rice](https://www.youtube.com/watch?v=ltrV-Qmh3oY)
|
||||
- [Don't expose the Docker socket (not even to a container)](https://www.lvh.io/posts/dont-expose-the-docker-socket-not-even-to-a-container/)
|
||||
- [A thread on Stack Overflow about sharing the `/var/run/docker.sock` file](https://news.ycombinator.com/item?id=17983623)
|
||||
- [To DinD or not to DinD](https://blog.loof.fr/2018/01/to-dind-or-not-do-dind.html)
|
||||
- [Traefik issue GH-4174 about security with Docker socket](https://github.com/containous/traefik/issues/4174)
|
||||
- [Inspecting Docker Activity with Socat](https://developers.redhat.com/blog/2015/02/25/inspecting-docker-activity-with-socat/)
|
||||
- [Letting Traefik run on Worker Nodes](https://blog.mikesir87.io/2018/07/letting-traefik-run-on-worker-nodes/)
|
||||
- [Docker Socket Proxy from Tecnativa](https://github.com/Tecnativa/docker-socket-proxy)
|
||||
|
||||
## Docker Swarm Mode
|
||||
|
||||
To enable Docker Swarm (instead of standalone Docker) as a configuration provider,
|
||||
set the [`swarmMode`](#swarmmode) directive to `true`.
|
||||
|
||||
### Routing Configuration with Labels
|
||||
|
||||
While in Swarm Mode, Traefik uses labels found on services, not on individual containers.
|
||||
|
||||
Therefore, if you use a compose file with Swarm Mode, labels should be defined in the
|
||||
[`deploy`](https://docs.docker.com/compose/compose-file/#labels-1) part of your service.
|
||||
|
||||
This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file)).
|
||||
|
||||
### Port Detection
|
||||
|
||||
Docker Swarm does not provide any [port detection](#port-detection) information to Traefik.
|
||||
|
||||
Therefore you **must** specify the port to use for communication by using the label `traefik.http.services.<service_name>.loadbalancer.server.port`
|
||||
(Check the reference for this label in the [routing section for Docker](../routing/providers/docker.md#port)).
|
||||
|
||||
### Docker API Access
|
||||
|
||||
Docker Swarm Mode follows the same rules as Docker [API Access](#docker-api-access).
|
||||
|
||||
As the Swarm API is only exposed on the [manager nodes](https://docs.docker.com/engine/swarm/how-swarm-mode-works/nodes/#manager-nodes), you should schedule Traefik on the Swarm manager nodes by default,
|
||||
by deploying Traefik with a [constraint](https://success.docker.com/article/using-contraints-and-labels-to-control-the-placement-of-containers) on the node's "role":
|
||||
|
||||
```shell tab="With Docker CLI"
|
||||
docker service create \
|
||||
--constraint=node.role==manager \
|
||||
#... \
|
||||
```
|
||||
|
||||
```yml tab="With Docker Compose"
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
traefik:
|
||||
# ...
|
||||
deploy:
|
||||
placement:
|
||||
constraints:
|
||||
- node.role == manager
|
||||
```
|
||||
|
||||
!!! tip "Scheduling Traefik on Worker Nodes"
|
||||
|
||||
Following the guidelines given in the previous section ["Docker API Access"](#docker-api-access),
|
||||
if you expose the Docker API through TCP, then Traefik can be scheduled on any node if the TCP
|
||||
socket is reachable.
|
||||
|
||||
Please consider the security implications by reading the [Security Note](#security-note).
|
||||
|
||||
A good example can be found on [Bret Fisher's repository](https://github.com/BretFisher/dogvscat/blob/master/stack-proxy-global.yml#L124).
|
||||
|
||||
## Provider Configuration
|
||||
|
||||
@@ -108,51 +232,10 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.docker.endpoint="unix:///var/run/docker.sock"
|
||||
--providers.docker.endpoint=unix:///var/run/docker.sock
|
||||
```
|
||||
|
||||
Traefik requires access to the docker socket to get its dynamic configuration.
|
||||
|
||||
??? warning "Security Notes"
|
||||
|
||||
Depending on your context, accessing the Docker API without any restriction can be a security concern: If Traefik is attacked, then the attacker might get access to the Docker (or Swarm Mode) backend.
|
||||
|
||||
As explained in the Docker documentation: ([Docker Daemon Attack Surface page](https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface)):
|
||||
|
||||
`[...] only **trusted** users should be allowed to control your Docker daemon [...]`
|
||||
|
||||
!!! tip "Improved Security"
|
||||
|
||||
[TraefikEE](https://containo.us/traefikee) solves this problem by separating the control plane (connected to Docker) and the data plane (handling the requests).
|
||||
|
||||
??? info "Resources about Docker's Security"
|
||||
|
||||
- [KubeCon EU 2018 Keynote, Running with Scissors, from Liz Rice](https://www.youtube.com/watch?v=ltrV-Qmh3oY)
|
||||
- [Don't expose the Docker socket (not even to a container)](https://www.lvh.io/posts/dont-expose-the-docker-socket-not-even-to-a-container/)
|
||||
- [A thread on Stack Overflow about sharing the `/var/run/docker.sock` file](https://news.ycombinator.com/item?id=17983623)
|
||||
- [To DinD or not to DinD](https://blog.loof.fr/2018/01/to-dind-or-not-do-dind.html)
|
||||
|
||||
??? tip "Security Compensation"
|
||||
|
||||
Expose the Docker socket over TCP, instead of the default Unix socket file.
|
||||
It allows different implementation levels of the [AAA (Authentication, Authorization, Accounting) concepts](https://en.wikipedia.org/wiki/AAA_(computer_security)), depending on your security assessment:
|
||||
|
||||
- Authentication with Client Certificates as described in ["Protect the Docker daemon socket."](https://docs.docker.com/engine/security/https/)
|
||||
- Authorization with the [Docker Authorization Plugin Mechanism](https://docs.docker.com/engine/extend/plugins_authorization/)
|
||||
- Accounting at networking level, by exposing the socket only inside a Docker private network, only available for Traefik.
|
||||
- Accounting at container level, by exposing the socket on a another container than Traefik's.
|
||||
With Swarm mode, it allows scheduling of Traefik on worker nodes, with only the "socket exposer" container on the manager nodes.
|
||||
- Accounting at kernel level, by enforcing kernel calls with mechanisms like [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux), to only allows an identified set of actions for Traefik's process (or the "socket exposer" process).
|
||||
|
||||
??? info "Additional Resources"
|
||||
|
||||
- [Traefik issue GH-4174 about security with Docker socket](https://github.com/containous/traefik/issues/4174)
|
||||
- [Inspecting Docker Activity with Socat](https://developers.redhat.com/blog/2015/02/25/inspecting-docker-activity-with-socat/)
|
||||
- [Letting Traefik run on Worker Nodes](https://blog.mikesir87.io/2018/07/letting-traefik-run-on-worker-nodes/)
|
||||
- [Docker Socket Proxy from Tecnativa](https://github.com/Tecnativa/docker-socket-proxy)
|
||||
|
||||
!!! info "Traefik & Swarm Mode"
|
||||
To let Traefik access the Docker Socket of the Swarm manager, it is mandatory to schedule Traefik on the Swarm manager nodes.
|
||||
See the sections [Docker API Access](#docker-api-access) and [Docker Swarm API Access](#docker-api-access_1) for more information.
|
||||
|
||||
??? example "Using the docker.sock"
|
||||
|
||||
@@ -186,7 +269,7 @@ Traefik requires access to the docker socket to get its dynamic configuration.
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.docker.endpoint="unix:///var/run/docker.sock"
|
||||
--providers.docker.endpoint=unix:///var/run/docker.sock
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -311,7 +394,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.docker.defaultRule="Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
|
||||
--providers.docker.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -343,7 +426,7 @@ providers:
|
||||
# ...
|
||||
```
|
||||
|
||||
Activates the Swarm Mode.
|
||||
Activates the Swarm Mode (instead of standalone Docker).
|
||||
|
||||
### `swarmModeRefreshSeconds`
|
||||
|
||||
@@ -375,19 +458,19 @@ _Optional, Default=""_
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.docker]
|
||||
constraints = "Label(`a.label.name`, `foo`)"
|
||||
constraints = "Label(`a.label.name`,`foo`)"
|
||||
# ...
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
docker:
|
||||
constraints: "Label(`a.label.name`, `foo`)"
|
||||
constraints: "Label(`a.label.name`,`foo`)"
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.docker.constraints="Label(`a.label.name`, `foo`)"
|
||||
--providers.docker.constraints=Label(`a.label.name`,`foo`)
|
||||
# ...
|
||||
```
|
||||
|
||||
|
||||
@@ -23,17 +23,17 @@ You can write one of these mutually exclusive configuration elements:
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.file]
|
||||
filename = "/my/path/to/dynamic-conf.toml"
|
||||
directory = "/path/to/dynamic/conf"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
file:
|
||||
filename: "/my/path/to/dynamic-conf.yml"
|
||||
directory: "/path/to/dynamic/conf"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.file.filename=/my/path/to/dynamic_conf.toml
|
||||
--providers.file.directory=/path/to/dynamic/conf
|
||||
```
|
||||
|
||||
Declaring Routers, Middlewares & Services:
|
||||
@@ -100,6 +100,22 @@ You can write one of these mutually exclusive configuration elements:
|
||||
|
||||
If you're in a hurry, maybe you'd rather go through the [dynamic configuration](../reference/dynamic-configuration/file.md) references and the [static configuration](../reference/static-configuration/overview.md).
|
||||
|
||||
!!! warning "Limitations"
|
||||
|
||||
With the file provider, Traefik listens for file system notifications to update the dynamic configuration.
|
||||
|
||||
If you use a mounted/bound file system in your orchestrator (like docker or kubernetes), the way the files are linked may be a source of errors.
|
||||
If the link between the file systems is broken, when a source file/directory is changed/renamed, nothing will be reported to the linked file/directory, so the file system notifications will be neither triggered nor caught.
|
||||
|
||||
For example, in docker, if the host file is renamed, the link to the mounted file will be broken and the container's file will not be updated.
|
||||
To avoid this kind of issue, a good practice is to:
|
||||
|
||||
* set the Traefik [**directory**](#directory) configuration with the parent directory
|
||||
* mount/bind the parent directory
|
||||
|
||||
As it is very difficult to listen to all file system notifications, Traefik use [fsnotify](https://github.com/fsnotify/fsnotify).
|
||||
If using a directory with a mounted directory does not fix your issue, please check your file system compatibility with fsnotify.
|
||||
|
||||
### `filename`
|
||||
|
||||
Defines the path of the configuration file.
|
||||
@@ -148,19 +164,19 @@ It works with both the `filename` and the `directory` options.
|
||||
```toml tab="File (TOML)"
|
||||
[providers]
|
||||
[providers.file]
|
||||
filename = "dynamic_conf.toml"
|
||||
directory = "/path/to/dynamic/conf"
|
||||
watch = true
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
file:
|
||||
filename: dynamic_conf.yml
|
||||
directory: /path/to/dynamic/conf
|
||||
watch: true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.file.filename=dynamic_conf.toml
|
||||
--providers.file.directory=/my/path/to/dynamic/conf
|
||||
--providers.file.watch=true
|
||||
```
|
||||
|
||||
|
||||
@@ -8,9 +8,60 @@ Traefik used to support Kubernetes only through the [Kubernetes Ingress provider
|
||||
However, as the community expressed the need to benefit from Traefik features without resorting to (lots of) annotations,
|
||||
we ended up writing a [Custom Resource Definition](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) (alias CRD in the following) for an IngressRoute type, defined below, in order to provide a better way to configure access to a Kubernetes cluster.
|
||||
|
||||
## Configuration Requirements
|
||||
|
||||
!!! tip "All Steps for a Successful Deployment"
|
||||
|
||||
* Add/update **all** the Traefik resources [definitions](../reference/dynamic-configuration/kubernetes-crd.md#definitions)
|
||||
* Add/update the [RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) for the Traefik custom resources
|
||||
* Use [Helm Chart](../getting-started/install-traefik.md#use-the-helm-chart) or use a custom Traefik Deployment
|
||||
* Enable the kubernetesCRD provider
|
||||
* Apply the needed kubernetesCRD provider [configuration](#provider-configuration)
|
||||
* Add all needed traefik custom [resources](../reference/dynamic-configuration/kubernetes-crd.md#resources)
|
||||
|
||||
??? example "Initializing Resource Definition and RBAC"
|
||||
|
||||
```yaml tab="Traefik Resource Definition"
|
||||
# All resources definition must be declared
|
||||
--8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition.yml"
|
||||
```
|
||||
|
||||
```yaml tab="RBAC for Traefik CRD"
|
||||
--8<-- "content/reference/dynamic-configuration/kubernetes-crd-rbac.yml"
|
||||
```
|
||||
|
||||
## Resource Configuration
|
||||
|
||||
See the dedicated section in [routing](../routing/providers/kubernetes-crd.md).
|
||||
When using KubernetesCRD as a provider,
|
||||
Traefik uses [Custom Resource Definition](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) to retrieve its routing configuration.
|
||||
Traefik Custom Resource Definitions are a Kubernetes implementation of the Traefik concepts. The main particularities are:
|
||||
|
||||
* The usage of `name` **and** `namespace` to refer to another Kubernetes resource.
|
||||
* The usage of [secret](https://kubernetes.io/docs/concepts/configuration/secret/) for sensible data like:
|
||||
* TLS certificate.
|
||||
* Authentication data.
|
||||
* The structure of the configuration.
|
||||
* The obligation to declare all the [definitions](../reference/dynamic-configuration/kubernetes-crd.md#definitions).
|
||||
|
||||
The Traefik CRD are building blocks which you can assemble according to your needs.
|
||||
See the list of CRDs in the dedicated [routing section](../routing/providers/kubernetes-crd.md).
|
||||
|
||||
## LetsEncrypt Support with the Custom Resource Definition Provider
|
||||
|
||||
By design, Traefik is a stateless application, meaning that it only derives its configuration from the environment it runs in, without additional configuration.
|
||||
For this reason, users can run multiple instances of Traefik at the same time to achieve HA, as is a common pattern in the kubernetes ecosystem.
|
||||
|
||||
When using a single instance of Traefik with LetsEncrypt, no issues should be encountered, however this could be a single point of failure.
|
||||
Unfortunately, it is not possible to run multiple instances of Traefik 2.0 with LetsEncrypt enabled, because there is no way to ensure that the correct instance of Traefik will receive the challenge request, and subsequent responses.
|
||||
Previous versions of Traefik used a [KV store](https://docs.traefik.io/v1.7/configuration/acme/#storage) to attempt to achieve this, but due to sub-optimal performance was dropped as a feature in 2.0.
|
||||
|
||||
If you require LetsEncrypt with HA in a kubernetes environment, we recommend using [TraefikEE](https://containo.us/traefikee/) where distributed LetsEncrypt is a supported feature.
|
||||
|
||||
If you are wanting to continue to run Traefik Community Edition, LetsEncrypt HA can be achieved by using a Certificate Controller such as [Cert-Manager](https://docs.cert-manager.io/en/latest/index.html).
|
||||
When using Cert-Manager to manage certificates, it will create secrets in your namespaces that can be referenced as TLS secrets in your [ingress objects](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls).
|
||||
When using the Traefik Kubernetes CRD Provider, unfortunately Cert-Manager cannot interface directly with the CRDs _yet_, but this is being worked on by our team.
|
||||
A workaround it to enable the [Kubernetes Ingress provider](./kubernetes-ingress.md) to allow Cert-Manager to create ingress objects to complete the challenges.
|
||||
Please note that this still requires manual intervention to create the certificates through Cert-Manager, but once created, Cert-Manager will keep the certificate renewed.
|
||||
|
||||
## Provider Configuration
|
||||
|
||||
@@ -32,7 +83,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetescrd.endpoint="http://localhost:8080"
|
||||
--providers.kubernetescrd.endpoint=http://localhost:8080
|
||||
```
|
||||
|
||||
The Kubernetes server endpoint as URL.
|
||||
@@ -66,7 +117,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetescrd.token="mytoken"
|
||||
--providers.kubernetescrd.token=mytoken
|
||||
```
|
||||
|
||||
Bearer token used for the Kubernetes client configuration.
|
||||
@@ -89,7 +140,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetescrd.certauthfilepath="/my/ca.crt"
|
||||
--providers.kubernetescrd.certauthfilepath=/my/ca.crt
|
||||
```
|
||||
|
||||
Path to the certificate authority file.
|
||||
@@ -115,7 +166,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetescrd.namespaces="default,production"
|
||||
--providers.kubernetescrd.namespaces=default,production
|
||||
```
|
||||
|
||||
Array of namespaces to watch.
|
||||
@@ -164,7 +215,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetescrd.ingressclass="traefik-internal"
|
||||
--providers.kubernetescrd.ingressclass=traefik-internal
|
||||
```
|
||||
|
||||
Value of `kubernetes.io/ingress.class` annotation that identifies Ingress objects to be processed.
|
||||
@@ -190,7 +241,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetescrd.throttleDuration="10s"
|
||||
--providers.kubernetescrd.throttleDuration=10s
|
||||
```
|
||||
|
||||
## Further
|
||||
|
||||
@@ -47,6 +47,20 @@ spec:
|
||||
servicePort: 80
|
||||
```
|
||||
|
||||
## LetsEncrypt Support with the Ingress Provider
|
||||
|
||||
By design, Traefik is a stateless application, meaning that it only derives its configuration from the environment it runs in, without additional configuration.
|
||||
For this reason, users can run multiple instances of Traefik at the same time to achieve HA, as is a common pattern in the kubernetes ecosystem.
|
||||
|
||||
When using a single instance of Traefik with LetsEncrypt, no issues should be encountered, however this could be a single point of failure.
|
||||
Unfortunately, it is not possible to run multiple instances of Traefik 2.0 with LetsEncrypt enabled, because there is no way to ensure that the correct instance of Traefik will receive the challenge request, and subsequent responses.
|
||||
Previous versions of Traefik used a [KV store](https://docs.traefik.io/v1.7/configuration/acme/#storage) to attempt to achieve this, but due to sub-optimal performance was dropped as a feature in 2.0.
|
||||
|
||||
If you require LetsEncrypt with HA in a kubernetes environment, we recommend using [TraefikEE](https://containo.us/traefikee/) where distributed LetsEncrypt is a supported feature.
|
||||
|
||||
If you are wanting to continue to run Traefik Community Edition, LetsEncrypt HA can be achieved by using a Certificate Controller such as [Cert-Manager](https://docs.cert-manager.io/en/latest/index.html).
|
||||
When using Cert-Manager to manage certificates, it will create secrets in your namespaces that can be referenced as TLS secrets in your [ingress objects](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls).
|
||||
|
||||
## Provider Configuration
|
||||
|
||||
### `endpoint`
|
||||
@@ -67,7 +81,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetesingress.endpoint="http://localhost:8080"
|
||||
--providers.kubernetesingress.endpoint=http://localhost:8080
|
||||
```
|
||||
|
||||
The Kubernetes server endpoint as URL, which is only used when the behavior based on environment variables described below does not apply.
|
||||
@@ -99,7 +113,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetesingress.token="mytoken"
|
||||
--providers.kubernetesingress.token=mytoken
|
||||
```
|
||||
|
||||
Bearer token used for the Kubernetes client configuration.
|
||||
@@ -122,7 +136,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetesingress.certauthfilepath="/my/ca.crt"
|
||||
--providers.kubernetesingress.certauthfilepath=/my/ca.crt
|
||||
```
|
||||
|
||||
Path to the certificate authority file.
|
||||
@@ -171,7 +185,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetesingress.namespaces="default,production"
|
||||
--providers.kubernetesingress.namespaces=default,production
|
||||
```
|
||||
|
||||
Array of namespaces to watch.
|
||||
@@ -220,7 +234,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetesingress.ingressclass="traefik-internal"
|
||||
--providers.kubernetesingress.ingressclass=traefik-internal
|
||||
```
|
||||
|
||||
Value of `kubernetes.io/ingress.class` annotation that identifies Ingress objects to be processed.
|
||||
@@ -249,7 +263,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetesingress.ingressendpoint.hostname="foo.com"
|
||||
--providers.kubernetesingress.ingressendpoint.hostname=foo.com
|
||||
```
|
||||
|
||||
Hostname used for Kubernetes Ingress endpoints.
|
||||
@@ -273,7 +287,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetesingress.ingressendpoint.ip="1.2.3.4"
|
||||
--providers.kubernetesingress.ingressendpoint.ip=1.2.3.4
|
||||
```
|
||||
|
||||
IP used for Kubernetes Ingress endpoints.
|
||||
@@ -297,7 +311,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetesingress.ingressendpoint.publishedservice="foo-service"
|
||||
--providers.kubernetesingress.ingressendpoint.publishedservice=foo-service
|
||||
```
|
||||
|
||||
Published Kubernetes Service to copy status from.
|
||||
@@ -320,7 +334,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetesingress.throttleDuration="10s"
|
||||
--providers.kubernetesingress.throttleDuration=10s
|
||||
```
|
||||
|
||||
## Further
|
||||
|
||||
@@ -74,8 +74,8 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.marathon.basic.httpbasicauthuser="foo"
|
||||
--providers.marathon.basic.httpbasicpassword="bar"
|
||||
--providers.marathon.basic.httpbasicauthuser=foo
|
||||
--providers.marathon.basic.httpbasicpassword=bar
|
||||
```
|
||||
|
||||
Enables Marathon basic authentication.
|
||||
@@ -98,7 +98,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.marathon.dcosToken="xxxxxx"
|
||||
--providers.marathon.dcosToken=xxxxxx
|
||||
```
|
||||
|
||||
DCOSToken for DCOS environment.
|
||||
@@ -123,7 +123,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.marathon.defaultRule="Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
|
||||
--providers.marathon.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -182,7 +182,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.marathon.endpoint="http://10.241.1.71:8080,10.241.1.72:8080,10.241.1.73:8080"
|
||||
--providers.marathon.endpoint=http://10.241.1.71:8080,10.241.1.72:8080,10.241.1.73:8080
|
||||
```
|
||||
|
||||
Marathon server endpoint.
|
||||
@@ -223,19 +223,19 @@ _Optional, Default=""_
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.marathon]
|
||||
constraints = "Label(`a.label.name`, `foo`)"
|
||||
constraints = "Label(`a.label.name`,`foo`)"
|
||||
# ...
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
marathon:
|
||||
constraints: "Label(`a.label.name`, `foo`)"
|
||||
constraints: "Label(`a.label.name`,`foo`)"
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.marathon.constraints="Label(`a.label.name`, `foo`)"
|
||||
--providers.marathon.constraints=Label(`a.label.name`,`foo`)
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -389,7 +389,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.marathon.responseHeaderTimeout="66s"
|
||||
--providers.marathon.responseHeaderTimeout=66s
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -532,7 +532,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.marathon.responseHeaderTimeout="10s"
|
||||
--providers.marathon.responseHeaderTimeout=10s
|
||||
# ...
|
||||
```
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.rancher.defaultRule="Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
|
||||
--providers.rancher.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -209,7 +209,7 @@ providers:
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.rancher.prefix="/test"
|
||||
--providers.rancher.prefix=/test
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -221,19 +221,19 @@ _Optional, Default=""_
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.rancher]
|
||||
constraints = "Label(`a.label.name`, `foo`)"
|
||||
constraints = "Label(`a.label.name`,`foo`)"
|
||||
# ...
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
rancher:
|
||||
constraints: "Label(`a.label.name`, `foo`)"
|
||||
constraints: "Label(`a.label.name`,`foo`)"
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.rancher.constraints="Label(`a.label.name`, `foo`)"
|
||||
--providers.rancher.constraints=Label(`a.label.name`,`foo`)
|
||||
# ...
|
||||
```
|
||||
|
||||
|
||||
@@ -17,4 +17,4 @@
|
||||
--providers.rancher.intervalPoll=false
|
||||
|
||||
# Prefix used for accessing the Rancher metadata service
|
||||
--providers.rancher.prefix="/latest"
|
||||
--providers.rancher.prefix=/latest
|
||||
|
||||
@@ -18,4 +18,4 @@ providers:
|
||||
intervalPoll: false
|
||||
|
||||
# Prefix used for accessing the Rancher metadata service
|
||||
prefix: "/latest"
|
||||
prefix: /latest
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: ingressroutes.traefik.containo.us
|
||||
|
||||
spec:
|
||||
group: traefik.containo.us
|
||||
version: v1alpha1
|
||||
names:
|
||||
kind: IngressRoute
|
||||
plural: ingressroutes
|
||||
singular: ingressroute
|
||||
scope: Namespaced
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: middlewares.traefik.containo.us
|
||||
|
||||
spec:
|
||||
group: traefik.containo.us
|
||||
version: v1alpha1
|
||||
names:
|
||||
kind: Middleware
|
||||
plural: middlewares
|
||||
singular: middleware
|
||||
scope: Namespaced
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: ingressroutetcps.traefik.containo.us
|
||||
|
||||
spec:
|
||||
group: traefik.containo.us
|
||||
version: v1alpha1
|
||||
names:
|
||||
kind: IngressRouteTCP
|
||||
plural: ingressroutetcps
|
||||
singular: ingressroutetcp
|
||||
scope: Namespaced
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: tlsoptions.traefik.containo.us
|
||||
|
||||
spec:
|
||||
group: traefik.containo.us
|
||||
version: v1alpha1
|
||||
names:
|
||||
kind: TLSOption
|
||||
plural: tlsoptions
|
||||
singular: tlsoption
|
||||
scope: Namespaced
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: traefikservices.traefik.containo.us
|
||||
|
||||
spec:
|
||||
group: traefik.containo.us
|
||||
version: v1alpha1
|
||||
names:
|
||||
kind: TraefikService
|
||||
plural: traefikservices
|
||||
singular: traefikservice
|
||||
scope: Namespaced
|
||||
@@ -0,0 +1,13 @@
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: ingressroutetcps.traefik.containo.us
|
||||
|
||||
spec:
|
||||
group: traefik.containo.us
|
||||
version: v1alpha1
|
||||
names:
|
||||
kind: IngressRouteTCP
|
||||
plural: ingressroutetcps
|
||||
singular: ingressroutetcp
|
||||
scope: Namespaced
|
||||
@@ -0,0 +1,57 @@
|
||||
kind: ClusterRole
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: traefik-ingress-controller
|
||||
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- services
|
||||
- endpoints
|
||||
- secrets
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- ingresses
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- ingresses/status
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- traefik.containo.us
|
||||
resources:
|
||||
- middlewares
|
||||
- ingressroutes
|
||||
- traefikservices
|
||||
- ingressroutetcps
|
||||
- tlsoptions
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: traefik-ingress-controller
|
||||
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: traefik-ingress-controller
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: traefik-ingress-controller
|
||||
namespace: default
|
||||
@@ -0,0 +1,157 @@
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: TraefikService
|
||||
metadata:
|
||||
name: wrr2
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
weighted:
|
||||
services:
|
||||
- name: s1
|
||||
weight: 1
|
||||
port: 80
|
||||
# Optional, as it is the default value
|
||||
kind: Service
|
||||
- name: s3
|
||||
weight: 1
|
||||
port: 80
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: TraefikService
|
||||
metadata:
|
||||
name: wrr1
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
weighted:
|
||||
services:
|
||||
- name: wrr2
|
||||
kind: TraefikService
|
||||
weight: 1
|
||||
- name: s3
|
||||
weight: 1
|
||||
port: 80
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: TraefikService
|
||||
metadata:
|
||||
name: mirror1
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
mirroring:
|
||||
name: s1
|
||||
port: 80
|
||||
mirrors:
|
||||
- name: s3
|
||||
percent: 20
|
||||
port: 80
|
||||
- name: mirror2
|
||||
kind: TraefikService
|
||||
percent: 20
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: TraefikService
|
||||
metadata:
|
||||
name: mirror2
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
mirroring:
|
||||
name: wrr2
|
||||
kind: TraefikService
|
||||
mirrors:
|
||||
- name: s2
|
||||
# Optional, as it is the default value
|
||||
kind: Service
|
||||
percent: 20
|
||||
port: 80
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: ingressroute
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
- web-secure
|
||||
routes:
|
||||
- match: Host(`foo.com`) && PathPrefix(`/bar`)
|
||||
kind: Rule
|
||||
priority: 12
|
||||
# defining several services is possible and allowed, but for now the servers of
|
||||
# all the services (for a given route) get merged altogether under the same
|
||||
# load-balancing strategy.
|
||||
services:
|
||||
- name: s1
|
||||
port: 80
|
||||
healthCheck:
|
||||
path: /health
|
||||
host: baz.com
|
||||
intervalSeconds: 7
|
||||
timeoutSeconds: 60
|
||||
# strategy defines the load balancing strategy between the servers. It defaults
|
||||
# to Round Robin, and for now only Round Robin is supported anyway.
|
||||
strategy: RoundRobin
|
||||
- name: s2
|
||||
port: 433
|
||||
healthCheck:
|
||||
path: /health
|
||||
host: baz.com
|
||||
intervalSeconds: 7
|
||||
timeoutSeconds: 60
|
||||
- match: PathPrefix(`/misc`)
|
||||
services:
|
||||
- name: s3
|
||||
port: 80
|
||||
middlewares:
|
||||
- name: stripprefix
|
||||
- name: addprefix
|
||||
- match: PathPrefix(`/misc`)
|
||||
services:
|
||||
- name: s3
|
||||
# Optional, as it is the default value
|
||||
kind: Service
|
||||
port: 8443
|
||||
# scheme allow to override the scheme for the service. (ex: https or h2c)
|
||||
scheme: https
|
||||
- match: PathPrefix(`/lb`)
|
||||
services:
|
||||
- name: wrr1
|
||||
kind: TraefikService
|
||||
- match: PathPrefix(`/mirrored`)
|
||||
services:
|
||||
- name: mirror1
|
||||
kind: TraefikService
|
||||
# use an empty tls object for TLS with Let's Encrypt
|
||||
tls:
|
||||
secretName: supersecret
|
||||
options:
|
||||
name: myTLSOption
|
||||
namespace: default
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRouteTCP
|
||||
metadata:
|
||||
name: ingressroutetcp.crd
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- footcp
|
||||
routes:
|
||||
- match: HostSNI(`bar.com`)
|
||||
services:
|
||||
- name: whoamitcp
|
||||
port: 8080
|
||||
tls:
|
||||
secretName: foosecret
|
||||
passthrough: false
|
||||
options:
|
||||
name: myTLSOption
|
||||
namespace: default
|
||||
@@ -3,6 +3,20 @@
|
||||
Dynamic configuration with Kubernetes Custom Resource
|
||||
{: .subtitle }
|
||||
|
||||
## Definitions
|
||||
|
||||
```yaml
|
||||
--8<-- "content/reference/dynamic-configuration/kubernetes-crd.yml"
|
||||
--8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition.yml"
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
```yaml
|
||||
--8<-- "content/reference/dynamic-configuration/kubernetes-crd-resource.yml"
|
||||
```
|
||||
|
||||
## RBAC
|
||||
|
||||
```yaml
|
||||
--8<-- "content/reference/dynamic-configuration/kubernetes-crd-rbac.yml"
|
||||
```
|
||||
|
||||
@@ -41,7 +41,7 @@ They define the port which will receive the requests (whether HTTP or TCP).
|
||||
[entryPoints.web]
|
||||
address = ":80"
|
||||
|
||||
[entryPoints.web-secure]
|
||||
[entryPoints.websecure]
|
||||
address = ":443"
|
||||
```
|
||||
|
||||
@@ -51,18 +51,18 @@ They define the port which will receive the requests (whether HTTP or TCP).
|
||||
web:
|
||||
address: ":80"
|
||||
|
||||
web-secure:
|
||||
websecure:
|
||||
address: ":443"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
## Static configuration
|
||||
--entryPoints.web.address=:80
|
||||
--entryPoints.web-secure.address=:443
|
||||
--entryPoints.websecure.address=:443
|
||||
```
|
||||
|
||||
- Two entrypoints are defined: one called `web`, and the other called `web-secure`.
|
||||
- `web` listens on port `80`, and `web-secure` on port `443`.
|
||||
- Two entrypoints are defined: one called `web`, and the other called `websecure`.
|
||||
- `web` listens on port `80`, and `websecure` on port `443`.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -128,9 +128,9 @@ You can define them using a toml file, CLI arguments, or a key-value store.
|
||||
--entryPoints.name.transport.respondingTimeouts.writeTimeout=42
|
||||
--entryPoints.name.transport.respondingTimeouts.idleTimeout=42
|
||||
--entryPoints.name.proxyProtocol.insecure=true
|
||||
--entryPoints.name.proxyProtocol.trustedIPs="127.0.0.1,192.168.0.1"
|
||||
--entryPoints.name.proxyProtocol.trustedIPs=127.0.0.1,192.168.0.1
|
||||
--entryPoints.name.forwardedHeaders.insecure=true
|
||||
--entryPoints.name.forwardedHeaders.trustedIPs="127.0.0.1,192.168.0.1"
|
||||
--entryPoints.name.forwardedHeaders.trustedIPs=127.0.0.1,192.168.0.1
|
||||
```
|
||||
|
||||
### Forwarded Header
|
||||
|
||||
@@ -33,9 +33,9 @@ Static configuration:
|
||||
address = ":8081"
|
||||
|
||||
[providers]
|
||||
# Enable the file provider to define routers / middlewares / services in a file
|
||||
# Enable the file provider to define routers / middlewares / services in file
|
||||
[providers.file]
|
||||
filename = "dynamic_conf.toml"
|
||||
directory = "/path/to/dynamic/conf"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
@@ -45,17 +45,17 @@ entryPoints:
|
||||
address: :8081
|
||||
|
||||
providers:
|
||||
# Enable the file provider to define routers / middlewares / services in a file
|
||||
# Enable the file provider to define routers / middlewares / services in file
|
||||
file:
|
||||
filename: dynamic_conf.yml
|
||||
directory: /path/to/dynamic/conf
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
# Listen on port 8081 for incoming requests
|
||||
--entryPoints.web.address=:8081
|
||||
|
||||
# Enable the file provider to define routers / middlewares / services in a file
|
||||
--providers.file.filename=dynamic_conf.toml
|
||||
# Enable the file provider to define routers / middlewares / services in file
|
||||
--providers.file.directory=/path/to/dynamic/conf
|
||||
```
|
||||
|
||||
Dynamic configuration:
|
||||
@@ -133,9 +133,9 @@ http:
|
||||
address = ":8081"
|
||||
|
||||
[providers]
|
||||
# Enable the file provider to define routers / middlewares / services in a file
|
||||
# Enable the file provider to define routers / middlewares / services in file
|
||||
[providers.file]
|
||||
filename = "dynamic_conf.toml"
|
||||
directory = "/path/to/dynamic/conf"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
@@ -144,17 +144,17 @@ http:
|
||||
# Listen on port 8081 for incoming requests
|
||||
address: :8081
|
||||
providers:
|
||||
# Enable the file provider to define routers / middlewares / services in a file
|
||||
# Enable the file provider to define routers / middlewares / services in file
|
||||
file:
|
||||
filename: dynamic_conf.yml
|
||||
directory: /path/to/dynamic/conf
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
# Listen on port 8081 for incoming requests
|
||||
--entryPoints.web.address=":8081"
|
||||
--entryPoints.web.address=:8081
|
||||
|
||||
# Enable the file provider to define routers / middlewares / services in a file
|
||||
--providers.file.filename=dynamic_conf.toml
|
||||
# Enable the file provider to define routers / middlewares / services in file
|
||||
--providers.file.directory=/path/to/dynamic/conf
|
||||
```
|
||||
|
||||
**Dynamic Configuration**
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
# Traefik & Consul Catalog
|
||||
|
||||
A Story of Labels, Services & Containers
|
||||
A Story of Tags, Services & Instances
|
||||
{: .subtitle }
|
||||
|
||||

|
||||
|
||||
Attach labels to your services and let Traefik do the rest!
|
||||
Attach tags to your services and let Traefik do the rest!
|
||||
|
||||
## Routing Configuration
|
||||
|
||||
!!! info "Labels"
|
||||
!!! info "tags"
|
||||
|
||||
- Labels are case insensitive.
|
||||
- The complete list of labels can be found [the reference page](../../reference/dynamic-configuration/consul-catalog.md)
|
||||
- tags are case insensitive.
|
||||
- The complete list of tags can be found [the reference page](../../reference/dynamic-configuration/consul-catalog.md)
|
||||
|
||||
### General
|
||||
|
||||
Traefik creates, for each consul Catalog service, a corresponding [service](../services/index.md) and [router](../routers/index.md).
|
||||
|
||||
The Service automatically gets a server per container in this consul Catalog service, and the router gets a default rule attached to it, based on the service name.
|
||||
The Service automatically gets a server per instance in this consul Catalog service, and the router gets a default rule attached to it, based on the service name.
|
||||
|
||||
### Routers
|
||||
|
||||
To update the configuration of the Router automatically attached to the container, add labels starting with `traefik.routers.{name-of-your-choice}.` and followed by the option you want to change.
|
||||
To update the configuration of the Router automatically attached to the service, add tags starting with `traefik.routers.{name-of-your-choice}.` and followed by the option you want to change.
|
||||
|
||||
For example, to change the rule, you could add the label ```traefik.http.routers.my-container.rule=Host(`mydomain.com`)```.
|
||||
For example, to change the rule, you could add the tag ```traefik.http.routers.my-service.rule=Host(`mydomain.com`)```.
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.rule`"
|
||||
|
||||
See [rule](../routers/index.md#rule) for more information.
|
||||
See [rule](../routers/index.md#rule) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.routers.myrouter.rule=Host(`mydomain.com`)"
|
||||
traefik.http.routers.myrouter.rule=Host(`mydomain.com`)
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.entrypoints`"
|
||||
|
||||
See [entry points](../routers/index.md#entrypoints) for more information.
|
||||
See [entry points](../routers/index.md#entrypoints) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.routers.myrouter.entrypoints=web,websecure"
|
||||
traefik.http.routers.myrouter.entrypoints=web,websecure
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.middlewares`"
|
||||
|
||||
See [middlewares](../routers/index.md#middlewares) and [middlewares overview](../../middlewares/overview.md) for more information.
|
||||
See [middlewares](../routers/index.md#middlewares) and [middlewares overview](../../middlewares/overview.md) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.routers.myrouter.middlewares=auth,prefix,cb"
|
||||
traefik.http.routers.myrouter.middlewares=auth,prefix,cb
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.service`"
|
||||
|
||||
See [rule](../routers/index.md#service) for more information.
|
||||
See [rule](../routers/index.md#service) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.routers.myrouter.service=myservice"
|
||||
traefik.http.routers.myrouter.service=myservice
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.tls`"
|
||||
@@ -63,7 +63,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers
|
||||
See [tls](../routers/index.md#tls) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.routers.myrouter>.tls=true"
|
||||
traefik.http.routers.myrouter>.tls=true
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.tls.certresolver`"
|
||||
@@ -71,7 +71,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers
|
||||
See [certResolver](../routers/index.md#certresolver) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.routers.myrouter.tls.certresolver=myresolver"
|
||||
traefik.http.routers.myrouter.tls.certresolver=myresolver
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.tls.domains[n].main`"
|
||||
@@ -79,7 +79,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers
|
||||
See [domains](../routers/index.md#domains) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.routers.myrouter.tls.domains[0].main=foobar.com"
|
||||
traefik.http.routers.myrouter.tls.domains[0].main=foobar.com
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.tls.domains[n].sans`"
|
||||
@@ -87,7 +87,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers
|
||||
See [domains](../routers/index.md#domains) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.routers.myrouter.tls.domains[0].sans=test.foobar.com,dev.foobar.com"
|
||||
traefik.http.routers.myrouter.tls.domains[0].sans=test.foobar.com,dev.foobar.com
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.tls.options`"
|
||||
@@ -95,31 +95,31 @@ For example, to change the rule, you could add the label ```traefik.http.routers
|
||||
See [options](../routers/index.md#options) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.routers.myrouter.tls.options=foobar"
|
||||
traefik.http.routers.myrouter.tls.options=foobar
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.priority`"
|
||||
<!-- TODO doc priority in routers page -->
|
||||
|
||||
```yaml
|
||||
- "traefik.http.routers.myrouter.priority=42"
|
||||
traefik.http.routers.myrouter.priority=42
|
||||
```
|
||||
|
||||
### Services
|
||||
|
||||
To update the configuration of the Service automatically attached to the container,
|
||||
add labels starting with `traefik.http.services.{name-of-your-choice}.`, followed by the option you want to change.
|
||||
To update the configuration of the Service automatically attached to the service,
|
||||
add tags starting with `traefik.http.services.{name-of-your-choice}.`, followed by the option you want to change.
|
||||
|
||||
For example, to change the `passHostHeader` behavior,
|
||||
you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.passhostheader=false`.
|
||||
you'd add the tag `traefik.http.services.{name-of-your-choice}.loadbalancer.passhostheader=false`.
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.server.port`"
|
||||
|
||||
Registers a port.
|
||||
Useful when the container exposes multiples ports.
|
||||
Useful when the service exposes multiples ports.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.server.port=8080"
|
||||
traefik.http.services.myservice.loadbalancer.server.port=8080
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.server.scheme`"
|
||||
@@ -127,14 +127,14 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
|
||||
Overrides the default scheme.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.server.scheme=http"
|
||||
traefik.http.services.myservice.loadbalancer.server.scheme=http
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.passhostheader`"
|
||||
<!-- TODO doc passHostHeader in services page -->
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.passhostheader=true"
|
||||
traefik.http.services.myservice.loadbalancer.passhostheader=true
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.headers.<header_name>`"
|
||||
@@ -142,7 +142,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
|
||||
See [health check](../services/index.md#health-check) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.healthcheck.headers.X-Foo=foobar"
|
||||
traefik.http.services.myservice.loadbalancer.healthcheck.headers.X-Foo=foobar
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.hostname`"
|
||||
@@ -150,7 +150,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
|
||||
See [health check](../services/index.md#health-check) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.healthcheck.hostname=foobar.com"
|
||||
traefik.http.services.myservice.loadbalancer.healthcheck.hostname=foobar.com
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.interval`"
|
||||
@@ -158,7 +158,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
|
||||
See [health check](../services/index.md#health-check) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.healthcheck.interval=10"
|
||||
traefik.http.services.myservice.loadbalancer.healthcheck.interval=10
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.path`"
|
||||
@@ -166,7 +166,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
|
||||
See [health check](../services/index.md#health-check) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.healthcheck.path=/foo"
|
||||
traefik.http.services.myservice.loadbalancer.healthcheck.path=/foo
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.port`"
|
||||
@@ -174,7 +174,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
|
||||
See [health check](../services/index.md#health-check) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.healthcheck.port=42"
|
||||
traefik.http.services.myservice.loadbalancer.healthcheck.port=42
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.scheme`"
|
||||
@@ -182,7 +182,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
|
||||
See [health check](../services/index.md#health-check) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.healthcheck.scheme=http"
|
||||
traefik.http.services.myservice.loadbalancer.healthcheck.scheme=http
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.timeout`"
|
||||
@@ -190,7 +190,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
|
||||
See [health check](../services/index.md#health-check) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.healthcheck.timeout=10"
|
||||
traefik.http.services.myservice.loadbalancer.healthcheck.timeout=10
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.sticky`"
|
||||
@@ -198,7 +198,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
|
||||
See [sticky sessions](../services/index.md#sticky-sessions) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.sticky=true"
|
||||
traefik.http.services.myservice.loadbalancer.sticky=true
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.sticky.cookie.httponly`"
|
||||
@@ -206,7 +206,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
|
||||
See [sticky sessions](../services/index.md#sticky-sessions) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.sticky.cookie.httponly=true"
|
||||
traefik.http.services.myservice.loadbalancer.sticky.cookie.httponly=true
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.sticky.cookie.name`"
|
||||
@@ -214,7 +214,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
|
||||
See [sticky sessions](../services/index.md#sticky-sessions) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.sticky.cookie.name=foobar"
|
||||
traefik.http.services.myservice.loadbalancer.sticky.cookie.name=foobar
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.sticky.cookie.secure`"
|
||||
@@ -222,7 +222,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
|
||||
See [sticky sessions](../services/index.md#sticky-sessions) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.sticky.cookie.secure=true"
|
||||
traefik.http.services.myservice.loadbalancer.sticky.cookie.secure=true
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.responseforwarding.flushinterval`"
|
||||
@@ -231,12 +231,12 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
|
||||
FlushInterval specifies the flush interval to flush to the client while copying the response body.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.responseforwarding.flushinterval=10"
|
||||
traefik.http.services.myservice.loadbalancer.responseforwarding.flushinterval=10
|
||||
```
|
||||
|
||||
### Middleware
|
||||
|
||||
You can declare pieces of middleware using labels starting with `traefik.http.middlewares.{name-of-your-choice}.`, followed by the middleware type/options.
|
||||
You can declare pieces of middleware using tags starting with `traefik.http.middlewares.{name-of-your-choice}.`, followed by the middleware type/options.
|
||||
|
||||
For example, to declare a middleware [`redirectscheme`](../../middlewares/redirectscheme.md) named `my-redirect`, you'd write `traefik.http.middlewares.my-redirect.redirectscheme.scheme: https`.
|
||||
|
||||
@@ -246,11 +246,10 @@ More information about available middlewares in the dedicated [middlewares secti
|
||||
|
||||
```yaml
|
||||
# ...
|
||||
labels:
|
||||
# Declaring a middleware
|
||||
- traefik.http.middlewares.my-redirect.redirectscheme.scheme=https
|
||||
# Referencing a middleware
|
||||
- traefik.http.routers.my-container.middlewares=my-redirect
|
||||
# Declaring a middleware
|
||||
traefik.http.middlewares.my-redirect.redirectscheme.scheme=https
|
||||
# Referencing a middleware
|
||||
traefik.http.routers.my-service.middlewares=my-redirect
|
||||
```
|
||||
|
||||
!!! warning "Conflicts in Declaration"
|
||||
@@ -259,24 +258,20 @@ More information about available middlewares in the dedicated [middlewares secti
|
||||
|
||||
### TCP
|
||||
|
||||
You can declare TCP Routers and/or Services using labels.
|
||||
You can declare TCP Routers and/or Services using tags.
|
||||
|
||||
??? example "Declaring TCP Routers and Services"
|
||||
|
||||
```yaml
|
||||
services:
|
||||
my-container:
|
||||
# ...
|
||||
labels:
|
||||
- "traefik.tcp.routers.my-router.rule=HostSNI(`my-host.com`)"
|
||||
- "traefik.tcp.routers.my-router.tls=true"
|
||||
- "traefik.tcp.services.my-service.loadbalancer.server.port=4123"
|
||||
traefik.tcp.routers.my-router.rule=HostSNI(`my-host.com`)
|
||||
traefik.tcp.routers.my-router.tls=true
|
||||
traefik.tcp.services.my-service.loadbalancer.server.port=4123
|
||||
```
|
||||
|
||||
!!! warning "TCP and HTTP"
|
||||
|
||||
If you declare a TCP Router/Service, it will prevent Traefik from automatically creating an HTTP Router/Service (like it does by default if no TCP Router/Service is defined).
|
||||
You can declare both a TCP Router/Service and an HTTP Router/Service for the same container (but you have to do so manually).
|
||||
You can declare both a TCP Router/Service and an HTTP Router/Service for the same consul service (but you have to do so manually).
|
||||
|
||||
#### TCP Routers
|
||||
|
||||
@@ -285,7 +280,7 @@ You can declare TCP Routers and/or Services using labels.
|
||||
See [entry points](../routers/index.md#entrypoints_1) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.routers.mytcprouter.entrypoints=ep1,ep2"
|
||||
traefik.tcp.routers.mytcprouter.entrypoints=ep1,ep2
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.routers.<router_name>.rule`"
|
||||
@@ -293,7 +288,7 @@ You can declare TCP Routers and/or Services using labels.
|
||||
See [rule](../routers/index.md#rule_1) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.routers.mytcprouter.rule=HostSNI(`myhost.com`)"
|
||||
traefik.tcp.routers.mytcprouter.rule=HostSNI(`myhost.com`)
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.routers.<router_name>.service`"
|
||||
@@ -301,7 +296,7 @@ You can declare TCP Routers and/or Services using labels.
|
||||
See [service](../routers/index.md#services) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.routers.mytcprouter.service=myservice"
|
||||
traefik.tcp.routers.mytcprouter.service=myservice
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.routers.<router_name>.tls`"
|
||||
@@ -309,7 +304,7 @@ You can declare TCP Routers and/or Services using labels.
|
||||
See [TLS](../routers/index.md#tls_1) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.routers.mytcprouter.tls=true"
|
||||
traefik.tcp.routers.mytcprouter.tls=true
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.routers.<router_name>.tls.certresolver`"
|
||||
@@ -317,7 +312,7 @@ You can declare TCP Routers and/or Services using labels.
|
||||
See [certResolver](../routers/index.md#certresolver_1) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.routers.mytcprouter.tls.certresolver=myresolver"
|
||||
traefik.tcp.routers.mytcprouter.tls.certresolver=myresolver
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.routers.<router_name>.tls.domains[n].main`"
|
||||
@@ -325,7 +320,7 @@ You can declare TCP Routers and/or Services using labels.
|
||||
See [domains](../routers/index.md#domains_1) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.routers.mytcprouter.tls.domains[0].main=foobar.com"
|
||||
traefik.tcp.routers.mytcprouter.tls.domains[0].main=foobar.com
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.routers.<router_name>.tls.domains[n].sans`"
|
||||
@@ -333,7 +328,7 @@ You can declare TCP Routers and/or Services using labels.
|
||||
See [domains](../routers/index.md#domains_1) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.routers.mytcprouter.tls.domains[0].sans=test.foobar.com,dev.foobar.com"
|
||||
traefik.tcp.routers.mytcprouter.tls.domains[0].sans=test.foobar.com,dev.foobar.com
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.routers.<router_name>.tls.options`"
|
||||
@@ -341,7 +336,7 @@ You can declare TCP Routers and/or Services using labels.
|
||||
See [options](../routers/index.md#options_1) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.routers.mytcprouter.tls.options=mysoptions"
|
||||
traefik.tcp.routers.mytcprouter.tls.options=mysoptions
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.routers.<router_name>.tls.passthrough`"
|
||||
@@ -349,7 +344,7 @@ You can declare TCP Routers and/or Services using labels.
|
||||
See [TLS](../routers/index.md#tls_1) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.routers.mytcprouter.tls.passthrough=true"
|
||||
traefik.tcp.routers.mytcprouter.tls.passthrough=true
|
||||
```
|
||||
|
||||
#### TCP Services
|
||||
@@ -359,7 +354,7 @@ You can declare TCP Routers and/or Services using labels.
|
||||
Registers a port of the application.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.services.mytcpservice.loadbalancer.server.port=423"
|
||||
traefik.tcp.services.mytcpservice.loadbalancer.server.port=423
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.services.<service_name>.loadbalancer.terminationdelay`"
|
||||
@@ -367,7 +362,7 @@ You can declare TCP Routers and/or Services using labels.
|
||||
See [termination delay](../services/index.md#termination-delay) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.services.mytcpservice.loadbalancer.terminationdelay=100"
|
||||
traefik.tcp.services.mytcpservice.loadbalancer.terminationdelay=100
|
||||
```
|
||||
|
||||
### Specific Provider Options
|
||||
@@ -375,10 +370,10 @@ You can declare TCP Routers and/or Services using labels.
|
||||
#### `traefik.enable`
|
||||
|
||||
```yaml
|
||||
- "traefik.enable=true"
|
||||
traefik.enable=true
|
||||
```
|
||||
|
||||
You can tell Traefik to consider (or not) the container by setting `traefik.enable` to true or false.
|
||||
You can tell Traefik to consider (or not) the service by setting `traefik.enable` to true or false.
|
||||
|
||||
This option overrides the value of `exposedByDefault`.
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ Attach labels to your containers and let Traefik do the rest!
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.docker.endpoint="tcp://127.0.0.1:2375"
|
||||
--providers.docker.endpoint=tcp://127.0.0.1:2375
|
||||
--providers.docker.swarmMode=true
|
||||
```
|
||||
|
||||
@@ -165,7 +165,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers
|
||||
See [entry points](../routers/index.md#entrypoints) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.routers.myrouter.entrypoints=web,websecure"
|
||||
- "traefik.http.routers.myrouter.entrypoints=ep1,ep2"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.middlewares`"
|
||||
@@ -243,11 +243,12 @@ you'd add the label `traefik.http.services.<name-of-your-choice>.loadbalancer.pa
|
||||
!!! warning "The character `@` is not authorized in the service name `<service_name>`."
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.server.port`"
|
||||
|
||||
|
||||
Registers a port.
|
||||
Useful when the container exposes multiples ports.
|
||||
|
||||
Mandatory for Docker Swarm.
|
||||
Mandatory for Docker Swarm (see the section ["Port Detection with Docker Swarm"](../../providers/docker.md#port-detection_1)).
|
||||
{: #port }
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.server.port=8080"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -67,7 +67,7 @@ For example, to change the routing rule, you could add the label ```"traefik.htt
|
||||
See [entry points](../routers/index.md#entrypoints) for more information.
|
||||
|
||||
```json
|
||||
"traefik.http.routers.myrouter.entrypoints": "web,websecure"
|
||||
"traefik.http.routers.myrouter.entrypoints": "ep1,ep2"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.middlewares`"
|
||||
@@ -91,7 +91,7 @@ For example, to change the routing rule, you could add the label ```"traefik.htt
|
||||
See [tls](../routers/index.md#tls) for more information.
|
||||
|
||||
```json
|
||||
"traefik.http.routers.myrouter>.tls": "true"
|
||||
"traefik.http.routers.myrouter.tls": "true"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.tls.certresolver`"
|
||||
|
||||
@@ -72,7 +72,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers
|
||||
See [entry points](../routers/index.md#entrypoints) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.routers.myrouter.entrypoints=web,websecure"
|
||||
- "traefik.http.routers.myrouter.entrypoints=ep1,ep2"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.middlewares`"
|
||||
|
||||
@@ -78,8 +78,8 @@ In the process, routers may use pieces of [middleware](../../middlewares/overvie
|
||||
|
||||
```bash tab="CLI"
|
||||
## Static configuration
|
||||
--entryPoints.web.address=":80"
|
||||
--entryPoints.mysql.address=":3306"
|
||||
--entryPoints.web.address=:80
|
||||
--entryPoints.mysql.address=:3306
|
||||
```
|
||||
|
||||
## Configuring HTTP Routers
|
||||
@@ -140,9 +140,9 @@ If you want to limit the router scope to a set of entry points, set the `entryPo
|
||||
|
||||
```bash tab="CLI"
|
||||
## Static configuration
|
||||
--entrypoints.web.address=":80"
|
||||
--entrypoints.websecure.address=":443"
|
||||
--entrypoints.other.address=":9090"
|
||||
--entrypoints.web.address=:80
|
||||
--entrypoints.websecure.address=:443
|
||||
--entrypoints.other.address=:9090
|
||||
```
|
||||
|
||||
??? example "Listens to Specific EntryPoints"
|
||||
@@ -198,9 +198,9 @@ If you want to limit the router scope to a set of entry points, set the `entryPo
|
||||
|
||||
```bash tab="CLI"
|
||||
## Static configuration
|
||||
--entrypoints.web.address=":80"
|
||||
--entrypoints.websecure.address=":443"
|
||||
--entrypoints.other.address=":9090"
|
||||
--entrypoints.web.address=:80
|
||||
--entrypoints.websecure.address=:443
|
||||
--entrypoints.other.address=:9090
|
||||
```
|
||||
|
||||
### Rule
|
||||
@@ -700,9 +700,9 @@ If you want to limit the router scope to a set of entry points, set the entry po
|
||||
|
||||
```bash tab="CLI"
|
||||
## Static configuration
|
||||
--entrypoints.web.address=":80"
|
||||
--entrypoints.websecure.address=":443"
|
||||
--entrypoints.other.address=":9090"
|
||||
--entrypoints.web.address=:80
|
||||
--entrypoints.websecure.address=:443
|
||||
--entrypoints.other.address=:9090
|
||||
```
|
||||
|
||||
??? example "Listens to Specific Entry Points"
|
||||
@@ -764,9 +764,9 @@ If you want to limit the router scope to a set of entry points, set the entry po
|
||||
|
||||
```bash tab="CLI"
|
||||
## Static configuration
|
||||
--entrypoints.web.address=":80"
|
||||
--entrypoints.websecure.address=":443"
|
||||
--entrypoints.other.address=":9090"
|
||||
--entrypoints.web.address=:80
|
||||
--entrypoints.websecure.address=:443
|
||||
--entrypoints.other.address=:9090
|
||||
```
|
||||
|
||||
### Rule
|
||||
|
||||
@@ -387,7 +387,9 @@ The WRR is able to load balance the requests between multiple services based on
|
||||
|
||||
This strategy is only available to load balance between [services](./index.md) and not between [servers](./index.md#servers).
|
||||
|
||||
!!! info "This strategy can be defined only with [File](../../providers/file.md)."
|
||||
!!! info "Supported Providers"
|
||||
|
||||
This strategy can be defined currently with the [File](../../providers/file.md) or [IngressRoute](../../providers/kubernetes-crd.md) providers.
|
||||
|
||||
```toml tab="TOML"
|
||||
## Dynamic configuration
|
||||
@@ -438,7 +440,9 @@ http:
|
||||
|
||||
The mirroring is able to mirror requests sent to a service to other services.
|
||||
|
||||
!!! info "This strategy can be defined only with [File](../../providers/file.md)."
|
||||
!!! info "Supported Providers"
|
||||
|
||||
This strategy can be defined currently with the [File](../../providers/file.md) or [IngressRoute](../../providers/kubernetes-crd.md) providers.
|
||||
|
||||
```toml tab="TOML"
|
||||
## Dynamic configuration
|
||||
@@ -583,7 +587,9 @@ The Weighted Round Robin (alias `WRR`) load-balancer of services is in charge of
|
||||
|
||||
This strategy is only available to load balance between [services](./index.md) and not between [servers](./index.md#servers).
|
||||
|
||||
This strategy can only be defined with [File](../../providers/file.md).
|
||||
!!! info "Supported Providers"
|
||||
|
||||
This strategy can be defined currently with the [File](../../providers/file.md) or [IngressRoute](../../providers/kubernetes-crd.md) providers.
|
||||
|
||||
```toml tab="TOML"
|
||||
## Dynamic configuration
|
||||
|
||||
@@ -16,7 +16,7 @@ Static configuration:
|
||||
[api]
|
||||
|
||||
[providers.file]
|
||||
filename = "dynamic_conf.toml"
|
||||
directory = "/path/to/dynamic/config"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
@@ -26,18 +26,18 @@ entryPoints:
|
||||
|
||||
providers:
|
||||
file:
|
||||
filename: dynamic_conf.yml
|
||||
directory: /path/to/dynamic/config
|
||||
|
||||
api: {}
|
||||
```
|
||||
|
||||
```yaml tab="CLI"
|
||||
--entryPoints.web.address=":80"
|
||||
--providers.file.filename=dynamic_conf.toml
|
||||
--entryPoints.web.address=:80
|
||||
--providers.file.directory=/path/to/dynamic/config
|
||||
--api.insecure=true
|
||||
```
|
||||
|
||||
`dynamic_conf.{toml,yml}`:
|
||||
`/path/to/dynamic/config/dynamic_conf.{toml,yml}`:
|
||||
|
||||
```toml tab="TOML"
|
||||
## dynamic configuration ##
|
||||
@@ -132,7 +132,7 @@ Static configuration:
|
||||
[api]
|
||||
|
||||
[provider.file]
|
||||
filename = "dynamic_conf.toml"
|
||||
directory = "/path/to/dynamic/config"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
@@ -147,20 +147,20 @@ serversTransport:
|
||||
|
||||
providers:
|
||||
file:
|
||||
filename: dynamic_conf.yml
|
||||
directory: /path/to/dynamic/config
|
||||
|
||||
api: {}
|
||||
```
|
||||
|
||||
```yaml tab="CLI"
|
||||
--entryPoints.websecure.address=":4443"
|
||||
--entryPoints.websecure.address=:4443
|
||||
# For secure connection on backend.local
|
||||
--serversTransport.rootCAs=./backend.cert
|
||||
--providers.file.filename=dynamic_conf.toml
|
||||
--providers.file.directory=/path/to/dynamic/config
|
||||
--api.insecure=true
|
||||
```
|
||||
|
||||
`dynamic_conf.{toml,yml}`:
|
||||
`/path/to/dynamic/config/dynamic_conf.{toml,yml}`:
|
||||
|
||||
```toml tab="TOML"
|
||||
## dynamic configuration ##
|
||||
|
||||
@@ -44,7 +44,7 @@ plugins:
|
||||
- search
|
||||
- exclude:
|
||||
glob:
|
||||
- include-*.md
|
||||
- "**/include-*.md"
|
||||
|
||||
# https://squidfunk.github.io/mkdocs-material/extensions/admonition/
|
||||
# https://facelessuser.github.io/pymdown-extensions/
|
||||
@@ -154,6 +154,7 @@ nav:
|
||||
- 'HTTP Challenge': 'user-guides/docker-compose/acme-http/index.md'
|
||||
- 'DNS Challenge': 'user-guides/docker-compose/acme-dns/index.md'
|
||||
- 'Migration':
|
||||
- 'Traefik v2 minor migrations': 'migration/v2.md'
|
||||
- 'Traefik v1 to v2': 'migration/v1-to-v2.md'
|
||||
- 'Contributing':
|
||||
- 'Thank You!': 'contributing/thank-you.md'
|
||||
|
||||
@@ -1 +1 @@
|
||||
3.6
|
||||
3.7
|
||||
|
||||
4
go.mod
4
go.mod
@@ -67,7 +67,7 @@ require (
|
||||
github.com/opencontainers/runc v1.0.0-rc8 // indirect
|
||||
github.com/opentracing/basictracer-go v1.0.0 // indirect
|
||||
github.com/opentracing/opentracing-go v1.1.0
|
||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.4
|
||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5
|
||||
github.com/openzipkin/zipkin-go v0.2.1
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/philhofer/fwd v1.0.0 // indirect
|
||||
@@ -80,7 +80,7 @@ require (
|
||||
github.com/stvp/go-udp-testing v0.0.0-20171104055251-c4434f09ec13
|
||||
github.com/tinylib/msgp v1.0.2 // indirect
|
||||
github.com/transip/gotransip v5.8.2+incompatible // indirect
|
||||
github.com/uber/jaeger-client-go v2.19.0+incompatible
|
||||
github.com/uber/jaeger-client-go v2.21.1+incompatible
|
||||
github.com/uber/jaeger-lib v2.2.0+incompatible
|
||||
github.com/unrolled/render v1.0.1
|
||||
github.com/unrolled/secure v1.0.5
|
||||
|
||||
8
go.sum
8
go.sum
@@ -454,8 +454,8 @@ github.com/opentracing/basictracer-go v1.0.0 h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7l
|
||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.4 h1:bzTJRoOZEN7uI1gq594S5HhMYNSud4FKUEwd4aFbsEI=
|
||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.4/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
|
||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 h1:ZCnq+JUrvXcDVhX/xRolRBZifmabN1HcS1wrPSvxhrU=
|
||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/openzipkin/zipkin-go v0.2.1 h1:noL5/5Uf1HpVl3wNsfkZhIKbSWCVi5jgqkONNx8PXcA=
|
||||
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
@@ -554,8 +554,8 @@ github.com/transip/gotransip v5.8.2+incompatible/go.mod h1:uacMoJVmrfOcscM4Bi5NV
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/uber-go/atomic v1.3.2 h1:Azu9lPBWRNKzYXSIwRfgRuDuS0YKsK4NFhiQv98gkxo=
|
||||
github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
|
||||
github.com/uber/jaeger-client-go v2.19.0+incompatible h1:pbwbYfHUoaase0oPQOdZ1GcaUjImYGimUXSQ/+8+Z8Q=
|
||||
github.com/uber/jaeger-client-go v2.19.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||
github.com/uber/jaeger-client-go v2.21.1+incompatible h1:oozboeZmWz+tyh3VZttJWlF3K73mHgbokieceqKccLo=
|
||||
github.com/uber/jaeger-client-go v2.21.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||
github.com/uber/jaeger-lib v2.2.0+incompatible h1:MxZXOiR2JuoANZ3J6DE/U0kSFv/eJ/GfSYVCjK7dyaw=
|
||||
github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
|
||||
github.com/unrolled/render v1.0.1 h1:VDDnQQVfBMsOsp3VaCJszSO0nkBIVEYoPWeRThk9spY=
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/v2/integration/try"
|
||||
@@ -15,8 +14,10 @@ import (
|
||||
|
||||
type ConsulCatalogSuite struct {
|
||||
BaseSuite
|
||||
consulClient *api.Client
|
||||
consulAddress string
|
||||
consulClient *api.Client
|
||||
consulAgentClient *api.Client
|
||||
consulAddress string
|
||||
consulAgentAddress string
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) {
|
||||
@@ -32,6 +33,13 @@ func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) {
|
||||
// Wait for consul to elect itself leader
|
||||
err = s.waitToElectConsulLeader()
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
s.consulAgentAddress = "http://" + s.composeProject.Container(c, "consul-agent").NetworkSettings.IPAddress + ":8500"
|
||||
clientAgent, err := api.NewClient(&api.Config{
|
||||
Address: s.consulAgentAddress,
|
||||
})
|
||||
c.Check(err, check.IsNil)
|
||||
s.consulAgentClient = clientAgent
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) waitToElectConsulLeader() error {
|
||||
@@ -53,31 +61,52 @@ func (s *ConsulCatalogSuite) TearDownSuite(c *check.C) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) registerService(id, name, address, port string, tags []string) error {
|
||||
iPort, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
return err
|
||||
func (s *ConsulCatalogSuite) registerService(reg *api.AgentServiceRegistration, onAgent bool) error {
|
||||
client := s.consulClient
|
||||
if onAgent {
|
||||
client = s.consulAgentClient
|
||||
}
|
||||
|
||||
return s.consulClient.Agent().ServiceRegister(&api.AgentServiceRegistration{
|
||||
ID: id,
|
||||
Name: name,
|
||||
Address: address,
|
||||
Port: iPort,
|
||||
Tags: tags,
|
||||
})
|
||||
return client.Agent().ServiceRegister(reg)
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) deregisterService(id string) error {
|
||||
return s.consulClient.Agent().ServiceDeregister(id)
|
||||
func (s *ConsulCatalogSuite) deregisterService(id string, onAgent bool) error {
|
||||
client := s.consulClient
|
||||
if onAgent {
|
||||
client = s.consulAgentClient
|
||||
}
|
||||
return client.Agent().ServiceDeregister(id)
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) TestWithNotExposedByDefaultAndDefaultsSettings(c *check.C) {
|
||||
err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"})
|
||||
reg1 := &api.AgentServiceRegistration{
|
||||
ID: "whoami1",
|
||||
Name: "whoami",
|
||||
Tags: []string{"traefik.enable=true"},
|
||||
Port: 80,
|
||||
Address: s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress,
|
||||
}
|
||||
err := s.registerService(reg1, false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
err = s.registerService("whoami2", "whoami", s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"})
|
||||
|
||||
reg2 := &api.AgentServiceRegistration{
|
||||
ID: "whoami2",
|
||||
Name: "whoami",
|
||||
Tags: []string{"traefik.enable=true"},
|
||||
Port: 80,
|
||||
Address: s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress,
|
||||
}
|
||||
err = s.registerService(reg2, false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
err = s.registerService("whoami3", "whoami", s.composeProject.Container(c, "whoami3").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"})
|
||||
|
||||
reg3 := &api.AgentServiceRegistration{
|
||||
ID: "whoami3",
|
||||
Name: "whoami",
|
||||
Tags: []string{"traefik.enable=true"},
|
||||
Port: 80,
|
||||
Address: s.composeProject.Container(c, "whoami3").NetworkSettings.IPAddress,
|
||||
}
|
||||
err = s.registerService(reg3, false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
tempObjects := struct {
|
||||
@@ -102,23 +131,30 @@ func (s *ConsulCatalogSuite) TestWithNotExposedByDefaultAndDefaultsSettings(c *c
|
||||
err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2", "Hostname: whoami3"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = s.deregisterService("whoami1")
|
||||
err = s.deregisterService("whoami1", false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
err = s.deregisterService("whoami2")
|
||||
err = s.deregisterService("whoami2", false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
err = s.deregisterService("whoami3")
|
||||
err = s.deregisterService("whoami3", false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) TestByLabels(c *check.C) {
|
||||
labels := []string{
|
||||
"traefik.enable=true",
|
||||
"traefik.http.routers.router1.rule=Path(`/whoami`)",
|
||||
"traefik.http.routers.router1.service=service1",
|
||||
"traefik.http.services.service1.loadBalancer.server.url=http://" + s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress,
|
||||
}
|
||||
containerIP := s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress
|
||||
|
||||
err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels)
|
||||
reg := &api.AgentServiceRegistration{
|
||||
ID: "whoami1",
|
||||
Name: "whoami",
|
||||
Tags: []string{
|
||||
"traefik.enable=true",
|
||||
"traefik.http.routers.router1.rule=Path(`/whoami`)",
|
||||
"traefik.http.routers.router1.service=service1",
|
||||
"traefik.http.services.service1.loadBalancer.server.url=http://" + containerIP,
|
||||
},
|
||||
Port: 80,
|
||||
Address: containerIP,
|
||||
}
|
||||
err := s.registerService(reg, false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
tempObjects := struct {
|
||||
@@ -139,7 +175,7 @@ func (s *ConsulCatalogSuite) TestByLabels(c *check.C) {
|
||||
err = try.GetRequest("http://127.0.0.1:8000/whoami", 2*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2", "Hostname: whoami3"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = s.deregisterService("whoami1")
|
||||
err = s.deregisterService("whoami1", false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
@@ -155,7 +191,14 @@ func (s *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) {
|
||||
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
|
||||
defer os.Remove(file)
|
||||
|
||||
err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"})
|
||||
reg := &api.AgentServiceRegistration{
|
||||
ID: "whoami1",
|
||||
Name: "whoami",
|
||||
Tags: []string{"traefik.enable=true"},
|
||||
Port: 80,
|
||||
Address: s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress,
|
||||
}
|
||||
err := s.registerService(reg, false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
cmd, display := s.traefikCmd(withConfigFile(file))
|
||||
@@ -171,7 +214,7 @@ func (s *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) {
|
||||
err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = s.deregisterService("whoami1")
|
||||
err = s.deregisterService("whoami1", false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
@@ -187,7 +230,14 @@ func (s *ConsulCatalogSuite) TestRegisterServiceWithoutIP(c *check.C) {
|
||||
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
|
||||
defer os.Remove(file)
|
||||
|
||||
err := s.registerService("whoami1", "whoami", "", "80", []string{"traefik.enable=true"})
|
||||
reg := &api.AgentServiceRegistration{
|
||||
ID: "whoami1",
|
||||
Name: "whoami",
|
||||
Tags: []string{"traefik.enable=true"},
|
||||
Port: 80,
|
||||
Address: "",
|
||||
}
|
||||
err := s.registerService(reg, false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
cmd, display := s.traefikCmd(withConfigFile(file))
|
||||
@@ -202,7 +252,7 @@ func (s *ConsulCatalogSuite) TestRegisterServiceWithoutIP(c *check.C) {
|
||||
err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("whoami@consulcatalog", "\"http://127.0.0.1:80\": \"UP\""))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = s.deregisterService("whoami1")
|
||||
err = s.deregisterService("whoami1", false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
@@ -219,7 +269,13 @@ func (s *ConsulCatalogSuite) TestDefaultConsulService(c *check.C) {
|
||||
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
|
||||
defer os.Remove(file)
|
||||
|
||||
err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", nil)
|
||||
reg := &api.AgentServiceRegistration{
|
||||
ID: "whoami1",
|
||||
Name: "whoami",
|
||||
Port: 80,
|
||||
Address: s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress,
|
||||
}
|
||||
err := s.registerService(reg, false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Start traefik
|
||||
@@ -236,7 +292,7 @@ func (s *ConsulCatalogSuite) TestDefaultConsulService(c *check.C) {
|
||||
err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = s.deregisterService("whoami1")
|
||||
err = s.deregisterService("whoami1", false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
@@ -252,14 +308,20 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithTCPLabels(c *check.C) {
|
||||
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
|
||||
defer os.Remove(file)
|
||||
|
||||
// Start a container with some labels
|
||||
labels := []string{
|
||||
"traefik.tcp.Routers.Super.Rule=HostSNI(`my.super.host`)",
|
||||
"traefik.tcp.Routers.Super.tls=true",
|
||||
"traefik.tcp.Services.Super.Loadbalancer.server.port=8080",
|
||||
// Start a container with some tags
|
||||
reg := &api.AgentServiceRegistration{
|
||||
ID: "whoamitcp",
|
||||
Name: "whoamitcp",
|
||||
Tags: []string{
|
||||
"traefik.tcp.Routers.Super.Rule=HostSNI(`my.super.host`)",
|
||||
"traefik.tcp.Routers.Super.tls=true",
|
||||
"traefik.tcp.Services.Super.Loadbalancer.server.port=8080",
|
||||
},
|
||||
Port: 8080,
|
||||
Address: s.composeProject.Container(c, "whoamitcp").NetworkSettings.IPAddress,
|
||||
}
|
||||
|
||||
err := s.registerService("whoamitcp", "whoamitcp", s.composeProject.Container(c, "whoamitcp").NetworkSettings.IPAddress, "8080", labels)
|
||||
err := s.registerService(reg, false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Start traefik
|
||||
@@ -277,7 +339,7 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithTCPLabels(c *check.C) {
|
||||
|
||||
c.Assert(who, checker.Contains, "whoamitcp")
|
||||
|
||||
err = s.deregisterService("whoamitcp")
|
||||
err = s.deregisterService("whoamitcp", false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
@@ -293,18 +355,31 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithLabels(c *check.C) {
|
||||
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
|
||||
defer os.Remove(file)
|
||||
|
||||
// Start a container with some labels
|
||||
labels := []string{
|
||||
"traefik.http.Routers.Super.Rule=Host(`my.super.host`)",
|
||||
// Start a container with some tags
|
||||
reg1 := &api.AgentServiceRegistration{
|
||||
ID: "whoami1",
|
||||
Name: "whoami",
|
||||
Tags: []string{
|
||||
"traefik.http.Routers.Super.Rule=Host(`my.super.host`)",
|
||||
},
|
||||
Port: 80,
|
||||
Address: s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress,
|
||||
}
|
||||
err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels)
|
||||
|
||||
err := s.registerService(reg1, false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Start another container by replacing a '.' by a '-'
|
||||
labels = []string{
|
||||
"traefik.http.Routers.SuperHost.Rule=Host(`my-super.host`)",
|
||||
reg2 := &api.AgentServiceRegistration{
|
||||
ID: "whoami2",
|
||||
Name: "whoami",
|
||||
Tags: []string{
|
||||
"traefik.http.Routers.SuperHost.Rule=Host(`my-super.host`)",
|
||||
},
|
||||
Port: 80,
|
||||
Address: s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress,
|
||||
}
|
||||
err = s.registerService("whoami2", "whoami", s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress, "80", labels)
|
||||
err = s.registerService(reg2, false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Start traefik
|
||||
@@ -328,10 +403,78 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithLabels(c *check.C) {
|
||||
err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami2"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = s.deregisterService("whoami1")
|
||||
err = s.deregisterService("whoami1", false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = s.deregisterService("whoami2")
|
||||
err = s.deregisterService("whoami2", false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) TestSameServiceIDOnDifferentConsulAgent(c *check.C) {
|
||||
tempObjects := struct {
|
||||
ConsulAddress string
|
||||
DefaultRule string
|
||||
}{
|
||||
ConsulAddress: s.consulAddress,
|
||||
DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)",
|
||||
}
|
||||
|
||||
file := s.adaptFile(c, "fixtures/consul_catalog/default_not_exposed.toml", tempObjects)
|
||||
defer os.Remove(file)
|
||||
|
||||
// Start a container with some tags
|
||||
tags := []string{
|
||||
"traefik.enable=true",
|
||||
"traefik.http.Routers.Super.service=whoami",
|
||||
"traefik.http.Routers.Super.Rule=Host(`my.super.host`)",
|
||||
}
|
||||
|
||||
reg1 := &api.AgentServiceRegistration{
|
||||
ID: "whoami",
|
||||
Name: "whoami",
|
||||
Tags: tags,
|
||||
Port: 80,
|
||||
Address: s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress,
|
||||
}
|
||||
err := s.registerService(reg1, false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
reg2 := &api.AgentServiceRegistration{
|
||||
ID: "whoami",
|
||||
Name: "whoami",
|
||||
Tags: tags,
|
||||
Port: 80,
|
||||
Address: s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress,
|
||||
}
|
||||
err = s.registerService(reg2, true)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Start traefik
|
||||
cmd, display := s.traefikCmd(withConfigFile(file))
|
||||
defer display(c)
|
||||
err = cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "my.super.host"
|
||||
|
||||
err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/rawdata", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.Request(req, 2*time.Second, try.StatusCodeIs(200),
|
||||
try.BodyContainsOr(s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress,
|
||||
s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = s.deregisterService("whoami1", false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = s.deregisterService("whoami2", true)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
@@ -347,11 +490,18 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithOneMissingLabels(c *check.C) {
|
||||
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
|
||||
defer os.Remove(file)
|
||||
|
||||
// Start a container with some labels
|
||||
labels := []string{
|
||||
"traefik.random.value=my.super.host",
|
||||
// Start a container with some tags
|
||||
reg := &api.AgentServiceRegistration{
|
||||
ID: "whoami1",
|
||||
Name: "whoami",
|
||||
Tags: []string{
|
||||
"traefik.random.value=my.super.host",
|
||||
},
|
||||
Port: 80,
|
||||
Address: s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress,
|
||||
}
|
||||
err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels)
|
||||
|
||||
err := s.registerService(reg, false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Start traefik
|
||||
@@ -371,3 +521,82 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithOneMissingLabels(c *check.C) {
|
||||
err = try.Request(req, 1500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) TestConsulServiceWithHealthCheck(c *check.C) {
|
||||
tags := []string{
|
||||
"traefik.enable=true",
|
||||
"traefik.http.routers.router1.rule=Path(`/whoami`)",
|
||||
"traefik.http.routers.router1.service=service1",
|
||||
"traefik.http.services.service1.loadBalancer.server.url=http://" + s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress,
|
||||
}
|
||||
|
||||
reg1 := &api.AgentServiceRegistration{
|
||||
ID: "whoami1",
|
||||
Name: "whoami",
|
||||
Tags: tags,
|
||||
Port: 80,
|
||||
Address: s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress,
|
||||
Check: &api.AgentServiceCheck{
|
||||
CheckID: "some-failed-check",
|
||||
TCP: "127.0.0.1:1234",
|
||||
Name: "some-failed-check",
|
||||
Interval: "1s",
|
||||
Timeout: "1s",
|
||||
},
|
||||
}
|
||||
|
||||
err := s.registerService(reg1, false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
tempObjects := struct {
|
||||
ConsulAddress string
|
||||
}{
|
||||
ConsulAddress: s.consulAddress,
|
||||
}
|
||||
|
||||
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
|
||||
defer os.Remove(file)
|
||||
|
||||
cmd, display := s.traefikCmd(withConfigFile(file))
|
||||
defer display(c)
|
||||
err = cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/whoami", 2*time.Second, try.StatusCodeIs(http.StatusNotFound))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = s.deregisterService("whoami1", false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
containerIP := s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress
|
||||
|
||||
reg2 := &api.AgentServiceRegistration{
|
||||
ID: "whoami2",
|
||||
Name: "whoami",
|
||||
Tags: tags,
|
||||
Port: 80,
|
||||
Address: containerIP,
|
||||
Check: &api.AgentServiceCheck{
|
||||
CheckID: "some-ok-check",
|
||||
TCP: containerIP + ":80",
|
||||
Name: "some-ok-check",
|
||||
Interval: "1s",
|
||||
Timeout: "1s",
|
||||
},
|
||||
}
|
||||
|
||||
err = s.registerService(reg2, false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "whoami"
|
||||
|
||||
// FIXME Need to wait for up to 10 seconds (for consul discovery or traefik to boot up ?)
|
||||
err = try.Request(req, 10*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami2"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = s.deregisterService("whoami2", false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
[global]
|
||||
checkNewVersion = false
|
||||
sendAnonymousUsage = false
|
||||
|
||||
[log]
|
||||
level = "DEBUG"
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.web1]
|
||||
address = ":8000"
|
||||
[entryPoints.web2]
|
||||
address = ":9000"
|
||||
|
||||
[api]
|
||||
insecure = true
|
||||
|
||||
[providers.file]
|
||||
filename = "{{ .SelfFilename }}"
|
||||
|
||||
## dynamic configuration ##
|
||||
|
||||
[http.routers]
|
||||
[http.routers.router1]
|
||||
entryPoints = ["web1"]
|
||||
service = "service1"
|
||||
rule = "Host(`test.localhost`)"
|
||||
|
||||
[http.routers.router2]
|
||||
entryPoints = ["web2"]
|
||||
service = "service1"
|
||||
rule = "Host(`test.localhost`)"
|
||||
|
||||
[http.services]
|
||||
[http.services.service1.loadBalancer]
|
||||
[http.services.service1.loadBalancer.healthcheck]
|
||||
path = "/health"
|
||||
interval = "1s"
|
||||
timeout = "0.9s"
|
||||
[[http.services.service1.loadBalancer.servers]]
|
||||
url = "http://{{.Server1}}:80"
|
||||
@@ -205,3 +205,69 @@ func (s *HealthCheckSuite) TestPortOverload(c *check.C) {
|
||||
err = try.Request(frontendHealthReq, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable))
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
// Checks if all the loadbalancers created will correctly update the server status
|
||||
func (s *HealthCheckSuite) TestMultipleRoutersOnSameService(c *check.C) {
|
||||
file := s.adaptFile(c, "fixtures/healthcheck/multiple-routers-one-same-service.toml", struct {
|
||||
Server1 string
|
||||
}{s.whoami1IP})
|
||||
defer os.Remove(file)
|
||||
|
||||
cmd, display := s.traefikCmd(withConfigFile(file))
|
||||
defer display(c)
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
// wait for traefik
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`test.localhost`)"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Set whoami health to 200 to be sure to start with the wanted status
|
||||
client := &http.Client{}
|
||||
statusOkReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBuffer([]byte("200")))
|
||||
c.Assert(err, checker.IsNil)
|
||||
_, err = client.Do(statusOkReq)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// check healthcheck on web1 entrypoint
|
||||
healthReqWeb1, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
healthReqWeb1.Host = "test.localhost"
|
||||
err = try.Request(healthReqWeb1, 1*time.Second, try.StatusCodeIs(http.StatusOK))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// check healthcheck on web2 entrypoint
|
||||
healthReqWeb2, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:9000/health", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
healthReqWeb2.Host = "test.localhost"
|
||||
|
||||
err = try.Request(healthReqWeb2, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Set whoami health to 500
|
||||
statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBuffer([]byte("500")))
|
||||
c.Assert(err, checker.IsNil)
|
||||
_, err = client.Do(statusInternalServerErrorReq)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Verify no backend service is available due to failing health checks
|
||||
err = try.Request(healthReqWeb1, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.Request(healthReqWeb2, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Change one whoami health to 200
|
||||
statusOKReq1, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBuffer([]byte("200")))
|
||||
c.Assert(err, checker.IsNil)
|
||||
_, err = client.Do(statusOKReq1)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Verify health check
|
||||
err = try.Request(healthReqWeb1, 3*time.Second, try.StatusCodeIs(http.StatusOK))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.Request(healthReqWeb2, 3*time.Second, try.StatusCodeIs(http.StatusOK))
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
consul:
|
||||
image: consul:1.6.1
|
||||
image: consul:1.6.2
|
||||
ports:
|
||||
- 8500:8500
|
||||
command: "agent -server -bootstrap -ui -client 0.0.0.0"
|
||||
consul-agent:
|
||||
image: consul:1.6.2
|
||||
ports:
|
||||
- 8501:8500
|
||||
command: "agent -retry-join consul -client 0.0.0.0"
|
||||
links:
|
||||
- consul
|
||||
whoami1:
|
||||
image: containous/whoami:v1.3.0
|
||||
hostname: whoami1
|
||||
|
||||
2
integration/testdata/rawdata-ingress.json
vendored
2
integration/testdata/rawdata-ingress.json
vendored
@@ -60,7 +60,7 @@
|
||||
"middlewares": {
|
||||
"dashboard_redirect@internal": {
|
||||
"redirectRegex": {
|
||||
"regex": "^(http:\\/\\/[^:]+(:\\d+)?)/$",
|
||||
"regex": "^(http:\\/\\/[^:\\/]+(:\\d+)?)\\/$",
|
||||
"replacement": "${1}/dashboard/",
|
||||
"permanent": true
|
||||
},
|
||||
|
||||
@@ -4,18 +4,20 @@ package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Command structure contains program/command information (command name and description).
|
||||
type Command struct {
|
||||
Name string
|
||||
Description string
|
||||
Configuration interface{}
|
||||
Resources []ResourceLoader
|
||||
Run func([]string) error
|
||||
Hidden bool
|
||||
Name string
|
||||
Description string
|
||||
Configuration interface{}
|
||||
Resources []ResourceLoader
|
||||
Run func([]string) error
|
||||
CustomHelpFunc func(io.Writer, *Command) error
|
||||
Hidden bool
|
||||
// AllowArg if not set, disallows any argument that is not a known command or a sub-command.
|
||||
AllowArg bool
|
||||
subCommands []*Command
|
||||
@@ -35,6 +37,15 @@ func (c *Command) AddCommand(cmd *Command) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrintHelp calls the custom help function of the command if it's set.
|
||||
// Otherwise, it calls the default help function.
|
||||
func (c *Command) PrintHelp(w io.Writer) error {
|
||||
if c.CustomHelpFunc != nil {
|
||||
return c.CustomHelpFunc(w, c)
|
||||
}
|
||||
return PrintHelp(w, c)
|
||||
}
|
||||
|
||||
// Execute Executes a command.
|
||||
func Execute(cmd *Command) error {
|
||||
return execute(cmd, os.Args, true)
|
||||
@@ -61,10 +72,12 @@ func execute(cmd *Command, args []string, root bool) error {
|
||||
|
||||
// Calls command by its name.
|
||||
if len(args) >= 2 && cmd.Name == args[1] {
|
||||
if err := run(cmd, args[2:]); err != nil {
|
||||
return fmt.Errorf("command %s error: %v", cmd.Name, err)
|
||||
if len(args) < 3 || !contains(cmd.subCommands, args[2]) {
|
||||
if err := run(cmd, args[2:]); err != nil {
|
||||
return fmt.Errorf("command %s error: %v", cmd.Name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// No sub-command, calls the current command.
|
||||
@@ -78,6 +91,9 @@ func execute(cmd *Command, args []string, root bool) error {
|
||||
// Trying to find the sub-command.
|
||||
for _, subCmd := range cmd.subCommands {
|
||||
if len(args) >= 2 && subCmd.Name == args[1] {
|
||||
return execute(subCmd, args, false)
|
||||
}
|
||||
if len(args) >= 3 && subCmd.Name == args[2] {
|
||||
return execute(subCmd, args[1:], false)
|
||||
}
|
||||
}
|
||||
@@ -87,16 +103,16 @@ func execute(cmd *Command, args []string, root bool) error {
|
||||
|
||||
func run(cmd *Command, args []string) error {
|
||||
if len(args) > 0 && !isFlag(args[0]) && !cmd.AllowArg {
|
||||
_ = PrintHelp(os.Stdout, cmd)
|
||||
_ = cmd.PrintHelp(os.Stdout)
|
||||
return fmt.Errorf("command not found: %s", args[0])
|
||||
}
|
||||
|
||||
if isHelp(args) {
|
||||
return PrintHelp(os.Stdout, cmd)
|
||||
return cmd.PrintHelp(os.Stdout)
|
||||
}
|
||||
|
||||
if cmd.Run == nil {
|
||||
_ = PrintHelp(os.Stdout, cmd)
|
||||
_ = cmd.PrintHelp(os.Stdout)
|
||||
return fmt.Errorf("command %s is not runnable", cmd.Name)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -55,6 +59,63 @@ func TestCommand_AddCommand(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommand_PrintHelp(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
command *Command
|
||||
expectedOutput string
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
desc: "print default help",
|
||||
command: &Command{},
|
||||
expectedOutput: " \n\nUsage: [command] [flags] [arguments]\n\nUse \" [command] --help\" for help on any command.\n\n",
|
||||
},
|
||||
{
|
||||
desc: "print custom help",
|
||||
command: &Command{
|
||||
Name: "root",
|
||||
Description: "Description for root",
|
||||
Configuration: &struct {
|
||||
Foo []struct {
|
||||
Field string
|
||||
}
|
||||
}{},
|
||||
Run: func(args []string) error {
|
||||
return nil
|
||||
},
|
||||
CustomHelpFunc: func(w io.Writer, _ *Command) error {
|
||||
_, _ = fmt.Fprintln(w, "test")
|
||||
return nil
|
||||
},
|
||||
},
|
||||
expectedOutput: "test\n",
|
||||
},
|
||||
{
|
||||
desc: "error is returned from called help",
|
||||
command: &Command{
|
||||
CustomHelpFunc: func(_ io.Writer, _ *Command) error {
|
||||
return errors.New("test")
|
||||
},
|
||||
},
|
||||
expectedError: errors.New("test"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
buffer := &bytes.Buffer{}
|
||||
err := test.command.PrintHelp(buffer)
|
||||
|
||||
assert.Equal(t, test.expectedError, err)
|
||||
assert.Equal(t, test.expectedOutput, buffer.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_execute(t *testing.T) {
|
||||
var called string
|
||||
|
||||
@@ -559,6 +620,88 @@ func Test_execute(t *testing.T) {
|
||||
},
|
||||
expected: expected{result: "root---foo=bar--fii=bir"},
|
||||
},
|
||||
{
|
||||
desc: "sub command help",
|
||||
args: []string{"", "test", "subtest", "--help"},
|
||||
command: func() *Command {
|
||||
rootCmd := &Command{
|
||||
Name: "test",
|
||||
Resources: []ResourceLoader{&FlagLoader{}},
|
||||
}
|
||||
|
||||
subCmd := &Command{
|
||||
Name: "subtest",
|
||||
Resources: []ResourceLoader{&FlagLoader{}},
|
||||
}
|
||||
|
||||
err := rootCmd.AddCommand(subCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
subSubCmd := &Command{
|
||||
Name: "subsubtest",
|
||||
Resources: []ResourceLoader{&FlagLoader{}},
|
||||
}
|
||||
|
||||
err = subCmd.AddCommand(subSubCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
subSubSubCmd := &Command{
|
||||
Name: "subsubsubtest",
|
||||
Resources: []ResourceLoader{&FlagLoader{}},
|
||||
Run: func([]string) error {
|
||||
called = "subsubsubtest"
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
err = subSubCmd.AddCommand(subSubSubCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
return rootCmd
|
||||
},
|
||||
expected: expected{},
|
||||
},
|
||||
{
|
||||
desc: "sub sub command help",
|
||||
args: []string{"", "test", "subtest", "subsubtest", "--help"},
|
||||
command: func() *Command {
|
||||
rootCmd := &Command{
|
||||
Name: "test",
|
||||
Resources: []ResourceLoader{&FlagLoader{}},
|
||||
}
|
||||
|
||||
subCmd := &Command{
|
||||
Name: "subtest",
|
||||
Resources: []ResourceLoader{&FlagLoader{}},
|
||||
}
|
||||
|
||||
err := rootCmd.AddCommand(subCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
subSubCmd := &Command{
|
||||
Name: "subsubtest",
|
||||
Resources: []ResourceLoader{&FlagLoader{}},
|
||||
}
|
||||
|
||||
err = subCmd.AddCommand(subSubCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
subSubSubCmd := &Command{
|
||||
Name: "subsubsubtest",
|
||||
Resources: []ResourceLoader{&FlagLoader{}},
|
||||
Run: func([]string) error {
|
||||
called = "subsubsubtest"
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
err = subSubCmd.AddCommand(subSubSubCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
return rootCmd
|
||||
},
|
||||
expected: expected{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
@@ -756,3 +899,43 @@ Flags:
|
||||
|
||||
`, string(out))
|
||||
}
|
||||
|
||||
func TestName(t *testing.T) {
|
||||
rootCmd := &Command{
|
||||
Name: "test",
|
||||
Resources: []ResourceLoader{&FlagLoader{}},
|
||||
}
|
||||
|
||||
subCmd := &Command{
|
||||
Name: "subtest",
|
||||
Resources: []ResourceLoader{&FlagLoader{}},
|
||||
}
|
||||
|
||||
err := rootCmd.AddCommand(subCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
subSubCmd := &Command{
|
||||
Name: "subsubtest",
|
||||
Resources: []ResourceLoader{&FlagLoader{}},
|
||||
Run: func([]string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
err = subCmd.AddCommand(subSubCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
subSubSubCmd := &Command{
|
||||
Name: "subsubsubtest",
|
||||
Resources: []ResourceLoader{&FlagLoader{}},
|
||||
Run: func([]string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
err = subSubCmd.AddCommand(subSubSubCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = execute(rootCmd, []string{"", "test", "subtest", "subsubtest", "subsubsubtest", "--help"}, true)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ func (f *FileLoader) GetFilename() string {
|
||||
func (f *FileLoader) Load(args []string, cmd *Command) (bool, error) {
|
||||
ref, err := flag.Parse(args, cmd.Configuration)
|
||||
if err != nil {
|
||||
_ = PrintHelp(os.Stdout, cmd)
|
||||
_ = cmd.PrintHelp(os.Stdout)
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,7 @@ import (
|
||||
)
|
||||
|
||||
// decodeFileToNode decodes the configuration in filePath in a tree of untyped nodes.
|
||||
// If filters is not empty, it skips any configuration element whose name is
|
||||
// not among filters.
|
||||
// If filters is not empty, it skips any configuration element whose name is not among filters.
|
||||
func decodeFileToNode(filePath string, filters ...string) (*parser.Node, error) {
|
||||
content, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
@@ -40,7 +39,20 @@ func decodeFileToNode(filePath string, filters ...string) (*parser.Node, error)
|
||||
return nil, fmt.Errorf("unsupported file extension: %s", filePath)
|
||||
}
|
||||
|
||||
return decodeRawToNode(data, parser.DefaultRootName, filters...)
|
||||
if len(data) == 0 {
|
||||
return nil, fmt.Errorf("no configuration found in file: %s", filePath)
|
||||
}
|
||||
|
||||
node, err := decodeRawToNode(data, parser.DefaultRootName, filters...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(node.Children) == 0 {
|
||||
return nil, fmt.Errorf("no valid configuration found in file: %s", filePath)
|
||||
}
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func getRootFieldNames(element interface{}) []string {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/config/parser"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_getRootFieldNames(t *testing.T) {
|
||||
@@ -42,17 +43,43 @@ func Test_getRootFieldNames(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_decodeFileToNode_errors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
confFile string
|
||||
}{
|
||||
{
|
||||
desc: "non existing file",
|
||||
confFile: "./fixtures/not_existing.toml",
|
||||
},
|
||||
{
|
||||
desc: "file without content",
|
||||
confFile: "./fixtures/empty.toml",
|
||||
},
|
||||
{
|
||||
desc: "file without any valid configuration",
|
||||
confFile: "./fixtures/no_conf.toml",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
node, err := decodeFileToNode(test.confFile,
|
||||
"Global", "ServersTransport", "EntryPoints", "Providers", "API", "Metrics", "Ping", "Log", "AccessLog", "Tracing", "HostResolver", "CertificatesResolvers")
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, node)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_decodeFileToNode_compare(t *testing.T) {
|
||||
nodeToml, err := decodeFileToNode("./fixtures/sample.toml",
|
||||
"Global", "ServersTransport", "EntryPoints", "Providers", "API", "Metrics", "Ping", "Log", "AccessLog", "Tracing", "HostResolver", "CertificatesResolvers")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
nodeYaml, err := decodeFileToNode("./fixtures/sample.yml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, nodeToml, nodeYaml)
|
||||
}
|
||||
@@ -60,9 +87,7 @@ func Test_decodeFileToNode_compare(t *testing.T) {
|
||||
func Test_decodeFileToNode_Toml(t *testing.T) {
|
||||
node, err := decodeFileToNode("./fixtures/sample.toml",
|
||||
"Global", "ServersTransport", "EntryPoints", "Providers", "API", "Metrics", "Ping", "Log", "AccessLog", "Tracing", "HostResolver", "CertificatesResolvers")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := &parser.Node{
|
||||
Name: "traefik",
|
||||
@@ -294,9 +319,7 @@ func Test_decodeFileToNode_Toml(t *testing.T) {
|
||||
|
||||
func Test_decodeFileToNode_Yaml(t *testing.T) {
|
||||
node, err := decodeFileToNode("./fixtures/sample.yml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := &parser.Node{
|
||||
Name: "traefik",
|
||||
|
||||
0
pkg/config/file/fixtures/empty.toml
Normal file
0
pkg/config/file/fixtures/empty.toml
Normal file
2
pkg/config/file/fixtures/no_conf.toml
Normal file
2
pkg/config/file/fixtures/no_conf.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[foo]
|
||||
bar = "test"
|
||||
@@ -21,6 +21,10 @@ func DecodeToNode(labels map[string]string, rootName string, filters ...string)
|
||||
|
||||
var parts []string
|
||||
for _, v := range split {
|
||||
if v == "" {
|
||||
return nil, fmt.Errorf("invalid element: %s", key)
|
||||
}
|
||||
|
||||
if v[0] == '[' {
|
||||
return nil, fmt.Errorf("invalid leading character '[' in field name (bracket is a slice delimiter): %s", v)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,15 @@ func TestDecodeToNode(t *testing.T) {
|
||||
in: map[string]string{},
|
||||
expected: expected{node: nil},
|
||||
},
|
||||
{
|
||||
desc: "invalid label, ending by a dot",
|
||||
in: map[string]string{
|
||||
"traefik.http.": "bar",
|
||||
},
|
||||
expected: expected{
|
||||
error: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "level 1",
|
||||
in: map[string]string{
|
||||
|
||||
@@ -25,14 +25,20 @@ const (
|
||||
var singleton *HealthCheck
|
||||
var once sync.Once
|
||||
|
||||
// BalancerHandler includes functionality for load-balancing management.
|
||||
type BalancerHandler interface {
|
||||
ServeHTTP(w http.ResponseWriter, req *http.Request)
|
||||
// Balancer is the set of operations required to manage the list of servers in a
|
||||
// load-balancer.
|
||||
type Balancer interface {
|
||||
Servers() []*url.URL
|
||||
RemoveServer(u *url.URL) error
|
||||
UpsertServer(u *url.URL, options ...roundrobin.ServerOption) error
|
||||
}
|
||||
|
||||
// BalancerHandler includes functionality for load-balancing management.
|
||||
type BalancerHandler interface {
|
||||
ServeHTTP(w http.ResponseWriter, req *http.Request)
|
||||
Balancer
|
||||
}
|
||||
|
||||
// metricsRegistry is a local interface in the health check package, exposing only the required metrics
|
||||
// necessary for the health check package. This makes it easier for the tests.
|
||||
type metricsRegistry interface {
|
||||
@@ -49,7 +55,7 @@ type Options struct {
|
||||
Transport http.RoundTripper
|
||||
Interval time.Duration
|
||||
Timeout time.Duration
|
||||
LB BalancerHandler
|
||||
LB Balancer
|
||||
}
|
||||
|
||||
func (opt Options) String() string {
|
||||
@@ -146,18 +152,18 @@ func (hc *HealthCheck) checkBackend(ctx context.Context, backend *BackendConfig)
|
||||
enabledURLs := backend.LB.Servers()
|
||||
var newDisabledURLs []backendURL
|
||||
// FIXME re enable metrics
|
||||
for _, disableURL := range backend.disabledURLs {
|
||||
for _, disabledURL := range backend.disabledURLs {
|
||||
// FIXME serverUpMetricValue := float64(0)
|
||||
if err := checkHealth(disableURL.url, backend); err == nil {
|
||||
if err := checkHealth(disabledURL.url, backend); err == nil {
|
||||
logger.Warnf("Health check up: Returning to server list. Backend: %q URL: %q Weight: %d",
|
||||
backend.name, disableURL.url.String(), disableURL.weight)
|
||||
if err = backend.LB.UpsertServer(disableURL.url, roundrobin.Weight(disableURL.weight)); err != nil {
|
||||
backend.name, disabledURL.url.String(), disabledURL.weight)
|
||||
if err = backend.LB.UpsertServer(disabledURL.url, roundrobin.Weight(disabledURL.weight)); err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
// FIXME serverUpMetricValue = 1
|
||||
} else {
|
||||
logger.Warnf("Health check still failing. Backend: %q URL: %q Reason: %s", backend.name, disableURL.url.String(), err)
|
||||
newDisabledURLs = append(newDisabledURLs, disableURL)
|
||||
logger.Warnf("Health check still failing. Backend: %q URL: %q Reason: %s", backend.name, disabledURL.url.String(), err)
|
||||
newDisabledURLs = append(newDisabledURLs, disabledURL)
|
||||
}
|
||||
// FIXME labelValues := []string{"backend", backend.name, "url", backendurl.url.String()}
|
||||
// FIXME hc.metrics.BackendServerUpGauge().With(labelValues...).Set(serverUpMetricValue)
|
||||
@@ -177,7 +183,7 @@ func (hc *HealthCheck) checkBackend(ctx context.Context, backend *BackendConfig)
|
||||
weight = 1
|
||||
}
|
||||
}
|
||||
logger.Warnf("Health check failed: Remove from server list. Backend: %q URL: %q Weight: %d Reason: %s", backend.name, enableURL.String(), weight, err)
|
||||
logger.Warnf("Health check failed, removing from server list. Backend: %q URL: %q Weight: %d Reason: %s", backend.name, enableURL.String(), weight, err)
|
||||
if err := backend.LB.RemoveServer(enableURL); err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
@@ -281,3 +287,38 @@ func (lb *LbStatusUpdater) UpsertServer(u *url.URL, options ...roundrobin.Server
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Balancers is a list of Balancers(s) that implements the Balancer interface.
|
||||
type Balancers []Balancer
|
||||
|
||||
// Servers returns the servers url from all the BalancerHandler
|
||||
func (b Balancers) Servers() []*url.URL {
|
||||
var servers []*url.URL
|
||||
for _, lb := range b {
|
||||
servers = append(servers, lb.Servers()...)
|
||||
}
|
||||
|
||||
return servers
|
||||
}
|
||||
|
||||
// RemoveServer removes the given server from all the BalancerHandler,
|
||||
// and updates the status of the server to "DOWN".
|
||||
func (b Balancers) RemoveServer(u *url.URL) error {
|
||||
for _, lb := range b {
|
||||
if err := lb.RemoveServer(u); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpsertServer adds the given server to all the BalancerHandler,
|
||||
// and updates the status of the server to "UP".
|
||||
func (b Balancers) UpsertServer(u *url.URL, options ...roundrobin.ServerOption) error {
|
||||
for _, lb := range b {
|
||||
if err := lb.UpsertServer(u, options...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10,9 +10,23 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
_ middlewares.Stateful = &captureResponseWriter{}
|
||||
_ middlewares.Stateful = &captureResponseWriterWithCloseNotify{}
|
||||
)
|
||||
|
||||
type capturer interface {
|
||||
http.ResponseWriter
|
||||
Size() int64
|
||||
Status() int
|
||||
}
|
||||
|
||||
func newCaptureResponseWriter(rw http.ResponseWriter) capturer {
|
||||
capt := &captureResponseWriter{rw: rw}
|
||||
if _, ok := rw.(http.CloseNotifier); !ok {
|
||||
return capt
|
||||
}
|
||||
return &captureResponseWriterWithCloseNotify{capt}
|
||||
}
|
||||
|
||||
// captureResponseWriter is a wrapper of type http.ResponseWriter
|
||||
// that tracks request status and size
|
||||
type captureResponseWriter struct {
|
||||
@@ -21,6 +35,16 @@ type captureResponseWriter struct {
|
||||
size int64
|
||||
}
|
||||
|
||||
type captureResponseWriterWithCloseNotify struct {
|
||||
*captureResponseWriter
|
||||
}
|
||||
|
||||
// CloseNotify returns a channel that receives at most a
|
||||
// single value (true) when the client connection has gone away.
|
||||
func (r *captureResponseWriterWithCloseNotify) CloseNotify() <-chan bool {
|
||||
return r.rw.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
func (crw *captureResponseWriter) Header() http.Header {
|
||||
return crw.rw.Header()
|
||||
}
|
||||
@@ -52,13 +76,6 @@ func (crw *captureResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error)
|
||||
return nil, nil, fmt.Errorf("not a hijacker: %T", crw.rw)
|
||||
}
|
||||
|
||||
func (crw *captureResponseWriter) CloseNotify() <-chan bool {
|
||||
if c, ok := crw.rw.(http.CloseNotifier); ok {
|
||||
return c.CloseNotify()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (crw *captureResponseWriter) Status() int {
|
||||
return crw.status
|
||||
}
|
||||
|
||||
50
pkg/middlewares/accesslog/capture_response_writer_test.go
Normal file
50
pkg/middlewares/accesslog/capture_response_writer_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package accesslog
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type rwWithCloseNotify struct {
|
||||
*httptest.ResponseRecorder
|
||||
}
|
||||
|
||||
func (r *rwWithCloseNotify) CloseNotify() <-chan bool {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func TestCloseNotifier(t *testing.T) {
|
||||
testCases := []struct {
|
||||
rw http.ResponseWriter
|
||||
desc string
|
||||
implementsCloseNotifier bool
|
||||
}{
|
||||
{
|
||||
rw: httptest.NewRecorder(),
|
||||
desc: "does not implement CloseNotifier",
|
||||
implementsCloseNotifier: false,
|
||||
},
|
||||
{
|
||||
rw: &rwWithCloseNotify{httptest.NewRecorder()},
|
||||
desc: "implements CloseNotifier",
|
||||
implementsCloseNotifier: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, ok := test.rw.(http.CloseNotifier)
|
||||
assert.Equal(t, test.implementsCloseNotifier, ok)
|
||||
|
||||
rw := newCaptureResponseWriter(test.rw)
|
||||
_, impl := rw.(http.CloseNotifier)
|
||||
assert.Equal(t, test.implementsCloseNotifier, impl)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ func AddServiceFields(rw http.ResponseWriter, req *http.Request, next http.Handl
|
||||
|
||||
// AddOriginFields add origin fields
|
||||
func AddOriginFields(rw http.ResponseWriter, req *http.Request, next http.Handler, data *LogData) {
|
||||
crw := &captureResponseWriter{rw: rw}
|
||||
crw := newCaptureResponseWriter(rw)
|
||||
start := time.Now().UTC()
|
||||
|
||||
next.ServeHTTP(crw, req)
|
||||
|
||||
@@ -116,7 +116,18 @@ type CoreLogData map[string]interface{}
|
||||
// LogData is the data captured by the middleware so that it can be logged.
|
||||
type LogData struct {
|
||||
Core CoreLogData
|
||||
Request http.Header
|
||||
Request request
|
||||
OriginResponse http.Header
|
||||
DownstreamResponse http.Header
|
||||
DownstreamResponse downstreamResponse
|
||||
}
|
||||
|
||||
type downstreamResponse struct {
|
||||
headers http.Header
|
||||
status int
|
||||
size int64
|
||||
}
|
||||
|
||||
type request struct {
|
||||
headers http.Header
|
||||
count int64
|
||||
}
|
||||
|
||||
@@ -47,8 +47,6 @@ func (n noopCloser) Close() error {
|
||||
|
||||
type handlerParams struct {
|
||||
logDataTable *LogData
|
||||
crr *captureRequestReader
|
||||
crw *captureResponseWriter
|
||||
}
|
||||
|
||||
// Handler will write each request and its response to the access log.
|
||||
@@ -122,7 +120,7 @@ func NewHandler(config *types.AccessLog) (*Handler, error) {
|
||||
go func() {
|
||||
defer logHandler.wg.Done()
|
||||
for handlerParams := range logHandler.logHandlerChan {
|
||||
logHandler.logTheRoundTrip(handlerParams.logDataTable, handlerParams.crr, handlerParams.crw)
|
||||
logHandler.logTheRoundTrip(handlerParams.logDataTable)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -162,7 +160,12 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http
|
||||
StartLocal: now.Local(),
|
||||
}
|
||||
|
||||
logDataTable := &LogData{Core: core, Request: req.Header}
|
||||
logDataTable := &LogData{
|
||||
Core: core,
|
||||
Request: request{
|
||||
headers: req.Header,
|
||||
},
|
||||
}
|
||||
|
||||
reqWithDataTable := req.WithContext(context.WithValue(req.Context(), DataTableKey, logDataTable))
|
||||
|
||||
@@ -197,7 +200,7 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http
|
||||
core[ClientHost] = forwardedFor
|
||||
}
|
||||
|
||||
crw := &captureResponseWriter{rw: rw}
|
||||
crw := newCaptureResponseWriter(rw)
|
||||
|
||||
next.ServeHTTP(crw, reqWithDataTable)
|
||||
|
||||
@@ -205,16 +208,21 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http
|
||||
core[ClientUsername] = usernameIfPresent(reqWithDataTable.URL)
|
||||
}
|
||||
|
||||
logDataTable.DownstreamResponse = crw.Header()
|
||||
logDataTable.DownstreamResponse = downstreamResponse{
|
||||
headers: crw.Header().Clone(),
|
||||
status: crw.Status(),
|
||||
size: crw.Size(),
|
||||
}
|
||||
if crr != nil {
|
||||
logDataTable.Request.count = crr.count
|
||||
}
|
||||
|
||||
if h.config.BufferingSize > 0 {
|
||||
h.logHandlerChan <- handlerParams{
|
||||
logDataTable: logDataTable,
|
||||
crr: crr,
|
||||
crw: crw,
|
||||
}
|
||||
} else {
|
||||
h.logTheRoundTrip(logDataTable, crr, crw)
|
||||
h.logTheRoundTrip(logDataTable)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,7 +272,7 @@ func usernameIfPresent(theURL *url.URL) string {
|
||||
}
|
||||
|
||||
// Logging handler to log frontend name, backend name, and elapsed time.
|
||||
func (h *Handler) logTheRoundTrip(logDataTable *LogData, crr *captureRequestReader, crw *captureResponseWriter) {
|
||||
func (h *Handler) logTheRoundTrip(logDataTable *LogData) {
|
||||
core := logDataTable.Core
|
||||
|
||||
retryAttempts, ok := core[RetryAttempts].(int)
|
||||
@@ -272,23 +280,22 @@ func (h *Handler) logTheRoundTrip(logDataTable *LogData, crr *captureRequestRead
|
||||
retryAttempts = 0
|
||||
}
|
||||
core[RetryAttempts] = retryAttempts
|
||||
core[RequestContentSize] = logDataTable.Request.count
|
||||
|
||||
if crr != nil {
|
||||
core[RequestContentSize] = crr.count
|
||||
}
|
||||
|
||||
core[DownstreamStatus] = crw.Status()
|
||||
status := logDataTable.DownstreamResponse.status
|
||||
core[DownstreamStatus] = status
|
||||
|
||||
// n.b. take care to perform time arithmetic using UTC to avoid errors at DST boundaries.
|
||||
totalDuration := time.Now().UTC().Sub(core[StartUTC].(time.Time))
|
||||
core[Duration] = totalDuration
|
||||
|
||||
if h.keepAccessLog(crw.Status(), retryAttempts, totalDuration) {
|
||||
core[DownstreamContentSize] = crw.Size()
|
||||
if h.keepAccessLog(status, retryAttempts, totalDuration) {
|
||||
size := logDataTable.DownstreamResponse.size
|
||||
core[DownstreamContentSize] = size
|
||||
if original, ok := core[OriginContentSize]; ok {
|
||||
o64 := original.(int64)
|
||||
if crw.Size() != o64 && crw.Size() != 0 {
|
||||
core[GzipRatio] = float64(o64) / float64(crw.Size())
|
||||
if size != o64 && size != 0 {
|
||||
core[GzipRatio] = float64(o64) / float64(size)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,9 +312,9 @@ func (h *Handler) logTheRoundTrip(logDataTable *LogData, crr *captureRequestRead
|
||||
}
|
||||
}
|
||||
|
||||
h.redactHeaders(logDataTable.Request, fields, "request_")
|
||||
h.redactHeaders(logDataTable.Request.headers, fields, "request_")
|
||||
h.redactHeaders(logDataTable.OriginResponse, fields, "origin_")
|
||||
h.redactHeaders(logDataTable.DownstreamResponse, fields, "downstream_")
|
||||
h.redactHeaders(logDataTable.DownstreamResponse.headers, fields, "downstream_")
|
||||
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
@@ -192,6 +192,7 @@ func TestLoggerJSON(t *testing.T) {
|
||||
Format: JSONFormat,
|
||||
},
|
||||
expected: map[string]func(t *testing.T, value interface{}){
|
||||
RequestContentSize: assertFloat64(0),
|
||||
RequestHost: assertString(testHostname),
|
||||
RequestAddr: assertString(testHostname),
|
||||
RequestMethod: assertString(testMethod),
|
||||
|
||||
@@ -88,9 +88,11 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
writeHeader(req, forwardReq, fa.trustForwardHeader)
|
||||
// Ensure tracing headers are in the request before we copy the headers to the
|
||||
// forwardReq.
|
||||
tracing.InjectRequestHeaders(req)
|
||||
|
||||
tracing.InjectRequestHeaders(forwardReq)
|
||||
writeHeader(req, forwardReq, fa.trustForwardHeader)
|
||||
|
||||
forwardResponse, forwardErr := httpClient.Do(forwardReq)
|
||||
if forwardErr != nil {
|
||||
|
||||
@@ -3,13 +3,18 @@ package auth
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||
tracingMiddleware "github.com/containous/traefik/v2/pkg/middlewares/tracing"
|
||||
"github.com/containous/traefik/v2/pkg/testhelpers"
|
||||
"github.com/containous/traefik/v2/pkg/tracing"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/opentracing/opentracing-go/mocktracer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/vulcand/oxy/forward"
|
||||
@@ -394,3 +399,44 @@ func Test_writeHeader(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestForwardAuthUsesTracing(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("Mockpfx-Ids-Traceid") == "" {
|
||||
t.Errorf("expected Mockpfx-Ids-Traceid header to be present in request")
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
next := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
|
||||
auth := dynamic.ForwardAuth{
|
||||
Address: server.URL,
|
||||
}
|
||||
|
||||
tracer := mocktracer.New()
|
||||
opentracing.SetGlobalTracer(tracer)
|
||||
|
||||
tr, _ := tracing.NewTracing("testApp", 100, &mockBackend{tracer})
|
||||
|
||||
next, err := NewForward(context.Background(), next, auth, "authTest")
|
||||
require.NoError(t, err)
|
||||
|
||||
next = tracingMiddleware.NewEntryPoint(context.Background(), tr, "tracingTest", next)
|
||||
|
||||
ts := httptest.NewServer(next)
|
||||
defer ts.Close()
|
||||
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
}
|
||||
|
||||
type mockBackend struct {
|
||||
opentracing.Tracer
|
||||
}
|
||||
|
||||
func (b *mockBackend) Setup(componentName string) (opentracing.Tracer, io.Closer, error) {
|
||||
return b.Tracer, ioutil.NopCloser(nil), nil
|
||||
}
|
||||
|
||||
@@ -221,13 +221,11 @@ func (s *Header) processCorsHeaders(rw http.ResponseWriter, req *http.Request) b
|
||||
}
|
||||
|
||||
reqAcMethod := req.Header.Get("Access-Control-Request-Method")
|
||||
reqAcHeaders := req.Header.Get("Access-Control-Request-Headers")
|
||||
originHeader := req.Header.Get("Origin")
|
||||
|
||||
if reqAcMethod != "" && reqAcHeaders != "" && originHeader != "" && req.Method == http.MethodOptions {
|
||||
if reqAcMethod != "" && originHeader != "" && req.Method == http.MethodOptions {
|
||||
// If the request is an OPTIONS request with an Access-Control-Request-Method header,
|
||||
// and Access-Control-Request-Headers headers, and Origin headers,
|
||||
// then it is a CORS preflight request,
|
||||
// and Origin headers, then it is a CORS preflight request,
|
||||
// and we need to build a custom response: https://www.w3.org/TR/cors/#preflight-request
|
||||
if s.headers.AccessControlAllowCredentials {
|
||||
rw.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
|
||||
@@ -275,6 +275,25 @@ func TestCORSPreflights(t *testing.T) {
|
||||
"Access-Control-Allow-Headers": {"origin,X-Forwarded-For"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "No Request Headers Preflight",
|
||||
header: NewHeader(emptyHandler, dynamic.Headers{
|
||||
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
|
||||
AccessControlAllowOrigin: "*",
|
||||
AccessControlAllowHeaders: []string{"origin", "X-Forwarded-For"},
|
||||
AccessControlMaxAge: 600,
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Access-Control-Request-Method": {"GET", "OPTIONS"},
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
"Access-Control-Max-Age": {"600"},
|
||||
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
|
||||
"Access-Control-Allow-Headers": {"origin,X-Forwarded-For"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
||||
@@ -89,10 +89,10 @@ func (m *metricsMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request)
|
||||
}(labels)
|
||||
|
||||
start := time.Now()
|
||||
recorder := &responseRecorder{rw, http.StatusOK}
|
||||
recorder := newResponseRecorder(rw)
|
||||
m.next.ServeHTTP(recorder, req)
|
||||
|
||||
labels = append(labels, "code", strconv.Itoa(recorder.statusCode))
|
||||
labels = append(labels, "code", strconv.Itoa(recorder.getCode()))
|
||||
m.reqsCounter.With(labels...).Add(1)
|
||||
m.reqDurationHistogram.With(labels...).Observe(time.Since(start).Seconds())
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// CollectingCounter is a metrics.Counter implementation that enables access to the CounterValue and LastLabelValues.
|
||||
@@ -56,3 +57,44 @@ func newCollectingRetryMetrics() *collectingRetryMetrics {
|
||||
func (m *collectingRetryMetrics) ServiceRetriesCounter() metrics.Counter {
|
||||
return m.retriesCounter
|
||||
}
|
||||
|
||||
type rwWithCloseNotify struct {
|
||||
*httptest.ResponseRecorder
|
||||
}
|
||||
|
||||
func (r *rwWithCloseNotify) CloseNotify() <-chan bool {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func TestCloseNotifier(t *testing.T) {
|
||||
testCases := []struct {
|
||||
rw http.ResponseWriter
|
||||
desc string
|
||||
implementsCloseNotifier bool
|
||||
}{
|
||||
{
|
||||
rw: httptest.NewRecorder(),
|
||||
desc: "does not implement CloseNotifier",
|
||||
implementsCloseNotifier: false,
|
||||
},
|
||||
{
|
||||
rw: &rwWithCloseNotify{httptest.NewRecorder()},
|
||||
desc: "implements CloseNotifier",
|
||||
implementsCloseNotifier: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, ok := test.rw.(http.CloseNotifier)
|
||||
assert.Equal(t, test.implementsCloseNotifier, ok)
|
||||
|
||||
rw := newResponseRecorder(test.rw)
|
||||
_, impl := rw.(http.CloseNotifier)
|
||||
assert.Equal(t, test.implementsCloseNotifier, impl)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,23 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type recorder interface {
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
getCode() int
|
||||
}
|
||||
|
||||
func newResponseRecorder(rw http.ResponseWriter) recorder {
|
||||
rec := &responseRecorder{
|
||||
ResponseWriter: rw,
|
||||
statusCode: http.StatusOK,
|
||||
}
|
||||
if _, ok := rw.(http.CloseNotifier); !ok {
|
||||
return rec
|
||||
}
|
||||
return &responseRecorderWithCloseNotify{rec}
|
||||
}
|
||||
|
||||
// responseRecorder captures information from the response and preserves it for
|
||||
// later analysis.
|
||||
type responseRecorder struct {
|
||||
@@ -13,6 +30,20 @@ type responseRecorder struct {
|
||||
statusCode int
|
||||
}
|
||||
|
||||
type responseRecorderWithCloseNotify struct {
|
||||
*responseRecorder
|
||||
}
|
||||
|
||||
// CloseNotify returns a channel that receives at most a
|
||||
// single value (true) when the client connection has gone away.
|
||||
func (r *responseRecorderWithCloseNotify) CloseNotify() <-chan bool {
|
||||
return r.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
func (r *responseRecorder) getCode() int {
|
||||
return r.statusCode
|
||||
}
|
||||
|
||||
// WriteHeader captures the status code for later retrieval.
|
||||
func (r *responseRecorder) WriteHeader(status int) {
|
||||
r.ResponseWriter.WriteHeader(status)
|
||||
@@ -24,13 +55,6 @@ func (r *responseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return r.ResponseWriter.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
// CloseNotify returns a channel that receives at most a
|
||||
// single value (true) when the client connection has gone
|
||||
// away.
|
||||
func (r *responseRecorder) CloseNotify() <-chan bool {
|
||||
return r.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
// Flush sends any buffered data to the client.
|
||||
func (r *responseRecorder) Flush() {
|
||||
if f, ok := r.ResponseWriter.(http.Flusher); ok {
|
||||
|
||||
@@ -18,10 +18,17 @@ import (
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
)
|
||||
|
||||
const typeName = "PassClientTLSCert"
|
||||
|
||||
const (
|
||||
xForwardedTLSClientCert = "X-Forwarded-Tls-Client-Cert"
|
||||
xForwardedTLSClientCertInfo = "X-Forwarded-Tls-Client-Cert-Info"
|
||||
typeName = "PassClientTLSCert"
|
||||
)
|
||||
|
||||
const (
|
||||
certSeparator = ","
|
||||
fieldSeparator = ";"
|
||||
subFieldSeparator = ","
|
||||
)
|
||||
|
||||
var attributeTypeNames = map[string]string{
|
||||
@@ -55,6 +62,29 @@ func newDistinguishedNameOptions(info *dynamic.TLSCLientCertificateDNInfo) *Dist
|
||||
}
|
||||
}
|
||||
|
||||
// tlsClientCertificateInfo is a struct for specifying the configuration for the passTLSClientCert middleware.
|
||||
type tlsClientCertificateInfo struct {
|
||||
notAfter bool
|
||||
notBefore bool
|
||||
sans bool
|
||||
subject *DistinguishedNameOptions
|
||||
issuer *DistinguishedNameOptions
|
||||
}
|
||||
|
||||
func newTLSClientCertificateInfo(info *dynamic.TLSClientCertificateInfo) *tlsClientCertificateInfo {
|
||||
if info == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &tlsClientCertificateInfo{
|
||||
issuer: newDistinguishedNameOptions(info.Issuer),
|
||||
notAfter: info.NotAfter,
|
||||
notBefore: info.NotBefore,
|
||||
subject: newDistinguishedNameOptions(info.Subject),
|
||||
sans: info.Sans,
|
||||
}
|
||||
}
|
||||
|
||||
// passTLSClientCert is a middleware that helps setup a few tls info features.
|
||||
type passTLSClientCert struct {
|
||||
next http.Handler
|
||||
@@ -71,45 +101,84 @@ func New(ctx context.Context, next http.Handler, config dynamic.PassTLSClientCer
|
||||
next: next,
|
||||
name: name,
|
||||
pem: config.PEM,
|
||||
info: newTLSClientInfo(config.Info),
|
||||
info: newTLSClientCertificateInfo(config.Info),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// tlsClientCertificateInfo is a struct for specifying the configuration for the passTLSClientCert middleware.
|
||||
type tlsClientCertificateInfo struct {
|
||||
notAfter bool
|
||||
notBefore bool
|
||||
sans bool
|
||||
subject *DistinguishedNameOptions
|
||||
issuer *DistinguishedNameOptions
|
||||
}
|
||||
|
||||
func newTLSClientInfo(info *dynamic.TLSClientCertificateInfo) *tlsClientCertificateInfo {
|
||||
if info == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &tlsClientCertificateInfo{
|
||||
issuer: newDistinguishedNameOptions(info.Issuer),
|
||||
notAfter: info.NotAfter,
|
||||
notBefore: info.NotBefore,
|
||||
subject: newDistinguishedNameOptions(info.Subject),
|
||||
sans: info.Sans,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *passTLSClientCert) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return p.name, tracing.SpanKindNoneEnum
|
||||
}
|
||||
|
||||
func (p *passTLSClientCert) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx := middlewares.GetLoggerCtx(req.Context(), p.name, typeName)
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
if p.pem {
|
||||
if req.TLS != nil && len(req.TLS.PeerCertificates) > 0 {
|
||||
req.Header.Set(xForwardedTLSClientCert, getCertificates(ctx, req.TLS.PeerCertificates))
|
||||
} else {
|
||||
logger.Warn("Tried to extract a certificate on a request without mutual TLS")
|
||||
}
|
||||
}
|
||||
|
||||
if p.info != nil {
|
||||
if req.TLS != nil && len(req.TLS.PeerCertificates) > 0 {
|
||||
headerContent := p.getCertInfo(ctx, req.TLS.PeerCertificates)
|
||||
req.Header.Set(xForwardedTLSClientCertInfo, url.QueryEscape(headerContent))
|
||||
} else {
|
||||
logger.Warn("Tried to extract a certificate on a request without mutual TLS")
|
||||
}
|
||||
}
|
||||
|
||||
p.modifyRequestHeaders(ctx, req)
|
||||
p.next.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
func getDNInfo(ctx context.Context, prefix string, options *DistinguishedNameOptions, cs *pkix.Name) string {
|
||||
// getCertInfo Build a string with the wanted client certificates information
|
||||
// - the `,` is used to separate certificates
|
||||
// - the `;` is used to separate root fields
|
||||
// - the value of root fields is always wrapped by double quote
|
||||
// - if a field is empty, the field is ignored
|
||||
func (p *passTLSClientCert) getCertInfo(ctx context.Context, certs []*x509.Certificate) string {
|
||||
var headerValues []string
|
||||
|
||||
for _, peerCert := range certs {
|
||||
var values []string
|
||||
|
||||
if p.info != nil {
|
||||
subject := getDNInfo(ctx, p.info.subject, &peerCert.Subject)
|
||||
if subject != "" {
|
||||
values = append(values, fmt.Sprintf(`Subject="%s"`, strings.TrimSuffix(subject, subFieldSeparator)))
|
||||
}
|
||||
|
||||
issuer := getDNInfo(ctx, p.info.issuer, &peerCert.Issuer)
|
||||
if issuer != "" {
|
||||
values = append(values, fmt.Sprintf(`Issuer="%s"`, strings.TrimSuffix(issuer, subFieldSeparator)))
|
||||
}
|
||||
|
||||
if p.info.notBefore {
|
||||
values = append(values, fmt.Sprintf(`NB="%d"`, uint64(peerCert.NotBefore.Unix())))
|
||||
}
|
||||
|
||||
if p.info.notAfter {
|
||||
values = append(values, fmt.Sprintf(`NA="%d"`, uint64(peerCert.NotAfter.Unix())))
|
||||
}
|
||||
|
||||
if p.info.sans {
|
||||
sans := getSANs(peerCert)
|
||||
if len(sans) > 0 {
|
||||
values = append(values, fmt.Sprintf(`SAN="%s"`, strings.Join(sans, subFieldSeparator)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value := strings.Join(values, fieldSeparator)
|
||||
headerValues = append(headerValues, value)
|
||||
}
|
||||
|
||||
return strings.Join(headerValues, certSeparator)
|
||||
}
|
||||
|
||||
func getDNInfo(ctx context.Context, options *DistinguishedNameOptions, cs *pkix.Name) string {
|
||||
if options == nil {
|
||||
return ""
|
||||
}
|
||||
@@ -120,7 +189,7 @@ func getDNInfo(ctx context.Context, prefix string, options *DistinguishedNameOpt
|
||||
for _, name := range cs.Names {
|
||||
// Domain Component - RFC 2247
|
||||
if options.DomainComponent && attributeTypeNames[name.Type.String()] == "DC" {
|
||||
content.WriteString(fmt.Sprintf("DC=%s,", name.Value))
|
||||
content.WriteString(fmt.Sprintf("DC=%s%s", name.Value, subFieldSeparator))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,11 +217,7 @@ func getDNInfo(ctx context.Context, prefix string, options *DistinguishedNameOpt
|
||||
writePart(ctx, content, cs.CommonName, "CN")
|
||||
}
|
||||
|
||||
if content.Len() > 0 {
|
||||
return prefix + `="` + strings.TrimSuffix(content.String(), ",") + `"`
|
||||
}
|
||||
|
||||
return ""
|
||||
return content.String()
|
||||
}
|
||||
|
||||
func writeParts(ctx context.Context, content io.StringWriter, entries []string, prefix string) {
|
||||
@@ -163,135 +228,63 @@ func writeParts(ctx context.Context, content io.StringWriter, entries []string,
|
||||
|
||||
func writePart(ctx context.Context, content io.StringWriter, entry string, prefix string) {
|
||||
if len(entry) > 0 {
|
||||
_, err := content.WriteString(fmt.Sprintf("%s=%s,", prefix, entry))
|
||||
_, err := content.WriteString(fmt.Sprintf("%s=%s%s", prefix, entry, subFieldSeparator))
|
||||
if err != nil {
|
||||
log.FromContext(ctx).Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getXForwardedTLSClientCertInfo Build a string with the wanted client certificates information
|
||||
// like Subject="C=%s,ST=%s,L=%s,O=%s,CN=%s",NB=%d,NA=%d,SAN=%s;
|
||||
func (p *passTLSClientCert) getXForwardedTLSClientCertInfo(ctx context.Context, certs []*x509.Certificate) string {
|
||||
var headerValues []string
|
||||
|
||||
for _, peerCert := range certs {
|
||||
var values []string
|
||||
var sans string
|
||||
var nb string
|
||||
var na string
|
||||
|
||||
if p.info != nil {
|
||||
subject := getDNInfo(ctx, "Subject", p.info.subject, &peerCert.Subject)
|
||||
if len(subject) > 0 {
|
||||
values = append(values, subject)
|
||||
}
|
||||
|
||||
issuer := getDNInfo(ctx, "Issuer", p.info.issuer, &peerCert.Issuer)
|
||||
if len(issuer) > 0 {
|
||||
values = append(values, issuer)
|
||||
}
|
||||
}
|
||||
|
||||
ci := p.info
|
||||
if ci != nil {
|
||||
if ci.notBefore {
|
||||
nb = fmt.Sprintf("NB=%d", uint64(peerCert.NotBefore.Unix()))
|
||||
values = append(values, nb)
|
||||
}
|
||||
if ci.notAfter {
|
||||
na = fmt.Sprintf("NA=%d", uint64(peerCert.NotAfter.Unix()))
|
||||
values = append(values, na)
|
||||
}
|
||||
|
||||
if ci.sans {
|
||||
sans = fmt.Sprintf("SAN=%s", strings.Join(getSANs(peerCert), ","))
|
||||
values = append(values, sans)
|
||||
}
|
||||
}
|
||||
|
||||
value := strings.Join(values, ",")
|
||||
headerValues = append(headerValues, value)
|
||||
}
|
||||
|
||||
return strings.Join(headerValues, ";")
|
||||
}
|
||||
|
||||
// modifyRequestHeaders set the wanted headers with the certificates information.
|
||||
func (p *passTLSClientCert) modifyRequestHeaders(ctx context.Context, r *http.Request) {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
if p.pem {
|
||||
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
|
||||
r.Header.Set(xForwardedTLSClientCert, getXForwardedTLSClientCert(ctx, r.TLS.PeerCertificates))
|
||||
} else {
|
||||
logger.Warn("Tried to extract a certificate on a request without mutual TLS")
|
||||
}
|
||||
}
|
||||
|
||||
if p.info != nil {
|
||||
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
|
||||
headerContent := p.getXForwardedTLSClientCertInfo(ctx, r.TLS.PeerCertificates)
|
||||
r.Header.Set(xForwardedTLSClientCertInfo, url.QueryEscape(headerContent))
|
||||
} else {
|
||||
logger.Warn("Tried to extract a certificate on a request without mutual TLS")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sanitize As we pass the raw certificates, remove the useless data and make it http request compliant.
|
||||
func sanitize(cert []byte) string {
|
||||
s := string(cert)
|
||||
r := strings.NewReplacer("-----BEGIN CERTIFICATE-----", "",
|
||||
cleaned := strings.NewReplacer(
|
||||
"-----BEGIN CERTIFICATE-----", "",
|
||||
"-----END CERTIFICATE-----", "",
|
||||
"\n", "")
|
||||
cleaned := r.Replace(s)
|
||||
"\n", "",
|
||||
).Replace(string(cert))
|
||||
|
||||
return url.QueryEscape(cleaned)
|
||||
}
|
||||
|
||||
// extractCertificate extract the certificate from the request.
|
||||
func extractCertificate(ctx context.Context, cert *x509.Certificate) string {
|
||||
b := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
|
||||
certPEM := pem.EncodeToMemory(&b)
|
||||
if certPEM == nil {
|
||||
log.FromContext(ctx).Error("Cannot extract the certificate content")
|
||||
return ""
|
||||
}
|
||||
return sanitize(certPEM)
|
||||
}
|
||||
|
||||
// getXForwardedTLSClientCert Build a string with the client certificates.
|
||||
func getXForwardedTLSClientCert(ctx context.Context, certs []*x509.Certificate) string {
|
||||
// getCertificates Build a string with the client certificates.
|
||||
func getCertificates(ctx context.Context, certs []*x509.Certificate) string {
|
||||
var headerValues []string
|
||||
|
||||
for _, peerCert := range certs {
|
||||
headerValues = append(headerValues, extractCertificate(ctx, peerCert))
|
||||
}
|
||||
|
||||
return strings.Join(headerValues, ",")
|
||||
return strings.Join(headerValues, certSeparator)
|
||||
}
|
||||
|
||||
// extractCertificate extract the certificate from the request.
|
||||
func extractCertificate(ctx context.Context, cert *x509.Certificate) string {
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
|
||||
if certPEM == nil {
|
||||
log.FromContext(ctx).Error("Cannot extract the certificate content")
|
||||
return ""
|
||||
}
|
||||
|
||||
return sanitize(certPEM)
|
||||
}
|
||||
|
||||
// getSANs get the Subject Alternate Name values.
|
||||
func getSANs(cert *x509.Certificate) []string {
|
||||
var sans []string
|
||||
if cert == nil {
|
||||
return sans
|
||||
return nil
|
||||
}
|
||||
|
||||
var sans []string
|
||||
sans = append(sans, cert.DNSNames...)
|
||||
sans = append(sans, cert.EmailAddresses...)
|
||||
|
||||
var ips []string
|
||||
for _, ip := range cert.IPAddresses {
|
||||
ips = append(ips, ip.String())
|
||||
sans = append(sans, ip.String())
|
||||
}
|
||||
sans = append(sans, ips...)
|
||||
|
||||
var uris []string
|
||||
for _, uri := range cert.URIs {
|
||||
uris = append(uris, uri.String())
|
||||
sans = append(sans, uri.String())
|
||||
}
|
||||
|
||||
return append(sans, uris...)
|
||||
return sans
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/containous/traefik/v2/pkg/testhelpers"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -113,6 +114,7 @@ Cg+XKmHzexmTnKaKac2w9ZECpRsQ9IBdQq9OghIwPtOnERTOUJEEgNcqA+9xELjb
|
||||
pQ==
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
|
||||
minimalCheeseCrt = `-----BEGIN CERTIFICATE-----
|
||||
MIIEQDCCAygCFFRY0OBk/L5Se0IZRj3CMljawL2UMA0GCSqGSIb3DQEBCwUAMIIB
|
||||
hDETMBEGCgmSJomT8ixkARkWA29yZzEWMBQGCgmSJomT8ixkARkWBmNoZWVzZTEP
|
||||
@@ -262,47 +264,6 @@ jECvgAY7Nfd9mZ1KtyNaW31is+kag7NsvjxU/kM=
|
||||
-----END CERTIFICATE-----`
|
||||
)
|
||||
|
||||
func getCleanCertContents(certContents []string) string {
|
||||
var re = regexp.MustCompile("-----BEGIN CERTIFICATE-----(?s)(.*)")
|
||||
|
||||
var cleanedCertContent []string
|
||||
for _, certContent := range certContents {
|
||||
cert := re.FindString(certContent)
|
||||
cleanedCertContent = append(cleanedCertContent, sanitize([]byte(cert)))
|
||||
}
|
||||
|
||||
return strings.Join(cleanedCertContent, ",")
|
||||
}
|
||||
|
||||
func getCertificate(certContent string) *x509.Certificate {
|
||||
roots := x509.NewCertPool()
|
||||
ok := roots.AppendCertsFromPEM([]byte(signingCA))
|
||||
if !ok {
|
||||
panic("failed to parse root certificate")
|
||||
}
|
||||
|
||||
block, _ := pem.Decode([]byte(certContent))
|
||||
if block == nil {
|
||||
panic("failed to parse certificate PEM")
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
panic("failed to parse certificate: " + err.Error())
|
||||
}
|
||||
|
||||
return cert
|
||||
}
|
||||
|
||||
func buildTLSWith(certContents []string) *tls.ConnectionState {
|
||||
var peerCertificates []*x509.Certificate
|
||||
|
||||
for _, certContent := range certContents {
|
||||
peerCertificates = append(peerCertificates, getCertificate(certContent))
|
||||
}
|
||||
|
||||
return &tls.ConnectionState{PeerCertificates: peerCertificates}
|
||||
}
|
||||
|
||||
var next = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, err := w.Write([]byte("bar"))
|
||||
if err != nil {
|
||||
@@ -310,59 +271,7 @@ var next = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
})
|
||||
|
||||
func getExpectedSanitized(s string) string {
|
||||
return url.QueryEscape(strings.Replace(s, "\n", "", -1))
|
||||
}
|
||||
|
||||
func TestSanitize(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
toSanitize []byte
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "Empty",
|
||||
},
|
||||
{
|
||||
desc: "With a minimal cert",
|
||||
toSanitize: []byte(minimalCheeseCrt),
|
||||
expected: getExpectedSanitized(`MIIEQDCCAygCFFRY0OBk/L5Se0IZRj3CMljawL2UMA0GCSqGSIb3DQEBCwUAMIIB
|
||||
hDETMBEGCgmSJomT8ixkARkWA29yZzEWMBQGCgmSJomT8ixkARkWBmNoZWVzZTEP
|
||||
MA0GA1UECgwGQ2hlZXNlMREwDwYDVQQKDAhDaGVlc2UgMjEfMB0GA1UECwwWU2lt
|
||||
cGxlIFNpZ25pbmcgU2VjdGlvbjEhMB8GA1UECwwYU2ltcGxlIFNpZ25pbmcgU2Vj
|
||||
dGlvbiAyMRowGAYDVQQDDBFTaW1wbGUgU2lnbmluZyBDQTEcMBoGA1UEAwwTU2lt
|
||||
cGxlIFNpZ25pbmcgQ0EgMjELMAkGA1UEBhMCRlIxCzAJBgNVBAYTAlVTMREwDwYD
|
||||
VQQHDAhUT1VMT1VTRTENMAsGA1UEBwwETFlPTjEWMBQGA1UECAwNU2lnbmluZyBT
|
||||
dGF0ZTEYMBYGA1UECAwPU2lnbmluZyBTdGF0ZSAyMSEwHwYJKoZIhvcNAQkBFhJz
|
||||
aW1wbGVAc2lnbmluZy5jb20xIjAgBgkqhkiG9w0BCQEWE3NpbXBsZTJAc2lnbmlu
|
||||
Zy5jb20wHhcNMTgxMjA2MTExMDM2WhcNMjEwOTI1MTExMDM2WjAzMQswCQYDVQQG
|
||||
EwJGUjETMBEGA1UECAwKU29tZS1TdGF0ZTEPMA0GA1UECgwGQ2hlZXNlMIIBIjAN
|
||||
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAskX/bUtwFo1gF2BTPNaNcTUMaRFu
|
||||
FMZozK8IgLjccZ4kZ0R9oFO6Yp8Zl/IvPaf7tE26PI7XP7eHriUdhnQzX7iioDd0
|
||||
RZa68waIhAGc+xPzRFrP3b3yj3S2a9Rve3c0K+SCV+EtKAwsxMqQDhoo9PcBfo5B
|
||||
RHfht07uD5MncUcGirwN+/pxHV5xzAGPcc7On0/5L7bq/G+63nhu78zw9XyuLaHC
|
||||
PM5VbOUvpyIESJHbMMzTdFGL8ob9VKO+Kr1kVGdEA9i8FLGl3xz/GBKuW/JD0xyW
|
||||
DrU29mri5vYWHmkuv7ZWHGXnpXjTtPHwveE9/0/ArnmpMyR9JtqFr1oEvQIDAQAB
|
||||
MA0GCSqGSIb3DQEBCwUAA4IBAQBHta+NWXI08UHeOkGzOTGRiWXsOH2dqdX6gTe9
|
||||
xF1AIjyoQ0gvpoGVvlnChSzmlUj+vnx/nOYGIt1poE3hZA3ZHZD/awsvGyp3GwWD
|
||||
IfXrEViSCIyF+8tNNKYyUcEO3xdAsAUGgfUwwF/mZ6MBV5+A/ZEEILlTq8zFt9dV
|
||||
vdKzIt7fZYxYBBHFSarl1x8pDgWXlf3hAufevGJXip9xGYmznF0T5cq1RbWJ4be3
|
||||
/9K7yuWhuBYC3sbTbCneHBa91M82za+PIISc1ygCYtWSBoZKSAqLk0rkZpHaekDP
|
||||
WqeUSNGYV//RunTeuRDAf5OxehERb1srzBXhRZ3cZdzXbgR/`),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require.Equal(t, test.expected, sanitize(test.toSanitize), "The sanitized certificates should be equal")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSClientHeadersWithPEM(t *testing.T) {
|
||||
func TestPassTLSClientCert_PEM(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
certContents []string // set the request TLS attribute if defined
|
||||
@@ -417,70 +326,36 @@ func TestTLSClientHeadersWithPEM(t *testing.T) {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require.Equal(t, http.StatusOK, res.Code, "Http Status should be OK")
|
||||
require.Equal(t, "bar", res.Body.String(), "Should be the expected body")
|
||||
assert.Equal(t, http.StatusOK, res.Code, "Http Status should be OK")
|
||||
assert.Equal(t, "bar", res.Body.String(), "Should be the expected body")
|
||||
|
||||
if test.expectedHeader != "" {
|
||||
require.Equal(t, getCleanCertContents(test.certContents), req.Header.Get(xForwardedTLSClientCert), "The request header should contain the cleaned certificate")
|
||||
expected := getCleanCertContents(test.certContents)
|
||||
assert.Equal(t, expected, req.Header.Get(xForwardedTLSClientCert), "The request header should contain the cleaned certificate")
|
||||
} else {
|
||||
require.Empty(t, req.Header.Get(xForwardedTLSClientCert))
|
||||
assert.Empty(t, req.Header.Get(xForwardedTLSClientCert))
|
||||
}
|
||||
require.Empty(t, res.Header().Get(xForwardedTLSClientCert), "The response header should be always empty")
|
||||
|
||||
assert.Empty(t, res.Header().Get(xForwardedTLSClientCert), "The response header should be always empty")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSans(t *testing.T) {
|
||||
urlFoo, err := url.Parse("my.foo.com")
|
||||
require.NoError(t, err)
|
||||
urlBar, err := url.Parse("my.bar.com")
|
||||
require.NoError(t, err)
|
||||
func TestPassTLSClientCert_certInfo(t *testing.T) {
|
||||
minimalCheeseCertAllInfo := strings.Join([]string{
|
||||
`Subject="C=FR,ST=Some-State,O=Cheese"`,
|
||||
`Issuer="DC=org,DC=cheese,C=FR,C=US,ST=Signing State,ST=Signing State 2,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=Simple Signing CA 2"`,
|
||||
`NB="1544094636"`,
|
||||
`NA="1632568236"`,
|
||||
}, fieldSeparator)
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
cert *x509.Certificate // set the request TLS attribute if defined
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
desc: "With nil",
|
||||
},
|
||||
{
|
||||
desc: "Certificate without Sans",
|
||||
cert: &x509.Certificate{},
|
||||
},
|
||||
{
|
||||
desc: "Certificate with all Sans",
|
||||
cert: &x509.Certificate{
|
||||
DNSNames: []string{"foo", "bar"},
|
||||
EmailAddresses: []string{"test@test.com", "test2@test.com"},
|
||||
IPAddresses: []net.IP{net.IPv4(10, 0, 0, 1), net.IPv4(10, 0, 0, 2)},
|
||||
URIs: []*url.URL{urlFoo, urlBar},
|
||||
},
|
||||
expected: []string{"foo", "bar", "test@test.com", "test2@test.com", "10.0.0.1", "10.0.0.2", urlFoo.String(), urlBar.String()},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
sans := getSANs(test.cert)
|
||||
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if len(test.expected) > 0 {
|
||||
for i, expected := range test.expected {
|
||||
require.Equal(t, expected, sans[i])
|
||||
}
|
||||
} else {
|
||||
require.Empty(t, sans)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSClientHeadersWithCertInfo(t *testing.T) {
|
||||
minimalCheeseCertAllInfo := `Subject="C=FR,ST=Some-State,O=Cheese",Issuer="DC=org,DC=cheese,C=FR,C=US,ST=Signing State,ST=Signing State 2,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=Simple Signing CA 2",NB=1544094636,NA=1632568236,SAN=`
|
||||
completeCertAllInfo := `Subject="DC=org,DC=cheese,C=FR,C=US,ST=Cheese org state,ST=Cheese com state,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=*.cheese.com",Issuer="DC=org,DC=cheese,C=FR,C=US,ST=Signing State,ST=Signing State 2,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=Simple Signing CA 2",NB=1544094616,NA=1607166616,SAN=*.cheese.org,*.cheese.net,*.cheese.com,test@cheese.org,test@cheese.net,10.0.1.0,10.0.1.2`
|
||||
completeCertAllInfo := strings.Join([]string{
|
||||
`Subject="DC=org,DC=cheese,C=FR,C=US,ST=Cheese org state,ST=Cheese com state,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=*.cheese.com"`,
|
||||
`Issuer="DC=org,DC=cheese,C=FR,C=US,ST=Signing State,ST=Signing State 2,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=Simple Signing CA 2"`,
|
||||
`NB="1544094616"`,
|
||||
`NA="1607166616"`,
|
||||
`SAN="*.cheese.org,*.cheese.net,*.cheese.com,test@cheese.org,test@cheese.net,10.0.1.0,10.0.1.2"`,
|
||||
}, fieldSeparator)
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
@@ -547,7 +422,7 @@ func TestTLSClientHeadersWithCertInfo(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedHeader: url.QueryEscape(minimalCheeseCertAllInfo),
|
||||
expectedHeader: minimalCheeseCertAllInfo,
|
||||
},
|
||||
{
|
||||
desc: "TLS with simple certificate, with some info",
|
||||
@@ -564,7 +439,7 @@ func TestTLSClientHeadersWithCertInfo(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedHeader: url.QueryEscape(`Subject="O=Cheese",Issuer="C=FR,C=US",NA=1632568236,SAN=`),
|
||||
expectedHeader: `Subject="O=Cheese";Issuer="C=FR,C=US";NA="1632568236"`,
|
||||
},
|
||||
{
|
||||
desc: "TLS with complete certificate, with all info",
|
||||
@@ -594,7 +469,7 @@ func TestTLSClientHeadersWithCertInfo(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedHeader: url.QueryEscape(completeCertAllInfo),
|
||||
expectedHeader: completeCertAllInfo,
|
||||
},
|
||||
{
|
||||
desc: "TLS with 2 certificates, with all info",
|
||||
@@ -624,7 +499,7 @@ func TestTLSClientHeadersWithCertInfo(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedHeader: url.QueryEscape(strings.Join([]string{minimalCheeseCertAllInfo, completeCertAllInfo}, ";")),
|
||||
expectedHeader: strings.Join([]string{minimalCheeseCertAllInfo, completeCertAllInfo}, certSeparator),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -645,15 +520,157 @@ func TestTLSClientHeadersWithCertInfo(t *testing.T) {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require.Equal(t, http.StatusOK, res.Code, "Http Status should be OK")
|
||||
require.Equal(t, "bar", res.Body.String(), "Should be the expected body")
|
||||
assert.Equal(t, http.StatusOK, res.Code, "Http Status should be OK")
|
||||
assert.Equal(t, "bar", res.Body.String(), "Should be the expected body")
|
||||
|
||||
if test.expectedHeader != "" {
|
||||
require.Equal(t, test.expectedHeader, req.Header.Get(xForwardedTLSClientCertInfo), "The request header should contain the cleaned certificate")
|
||||
unescape, err := url.QueryUnescape(req.Header.Get(xForwardedTLSClientCertInfo))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedHeader, unescape, "The request header should contain the cleaned certificate")
|
||||
} else {
|
||||
require.Empty(t, req.Header.Get(xForwardedTLSClientCertInfo))
|
||||
assert.Empty(t, req.Header.Get(xForwardedTLSClientCertInfo))
|
||||
}
|
||||
require.Empty(t, res.Header().Get(xForwardedTLSClientCertInfo), "The response header should be always empty")
|
||||
|
||||
assert.Empty(t, res.Header().Get(xForwardedTLSClientCertInfo), "The response header should be always empty")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_sanitize(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
toSanitize []byte
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "Empty",
|
||||
},
|
||||
{
|
||||
desc: "With a minimal cert",
|
||||
toSanitize: []byte(minimalCheeseCrt),
|
||||
expected: `MIIEQDCCAygCFFRY0OBk/L5Se0IZRj3CMljawL2UMA0GCSqGSIb3DQEBCwUAMIIB
|
||||
hDETMBEGCgmSJomT8ixkARkWA29yZzEWMBQGCgmSJomT8ixkARkWBmNoZWVzZTEP
|
||||
MA0GA1UECgwGQ2hlZXNlMREwDwYDVQQKDAhDaGVlc2UgMjEfMB0GA1UECwwWU2lt
|
||||
cGxlIFNpZ25pbmcgU2VjdGlvbjEhMB8GA1UECwwYU2ltcGxlIFNpZ25pbmcgU2Vj
|
||||
dGlvbiAyMRowGAYDVQQDDBFTaW1wbGUgU2lnbmluZyBDQTEcMBoGA1UEAwwTU2lt
|
||||
cGxlIFNpZ25pbmcgQ0EgMjELMAkGA1UEBhMCRlIxCzAJBgNVBAYTAlVTMREwDwYD
|
||||
VQQHDAhUT1VMT1VTRTENMAsGA1UEBwwETFlPTjEWMBQGA1UECAwNU2lnbmluZyBT
|
||||
dGF0ZTEYMBYGA1UECAwPU2lnbmluZyBTdGF0ZSAyMSEwHwYJKoZIhvcNAQkBFhJz
|
||||
aW1wbGVAc2lnbmluZy5jb20xIjAgBgkqhkiG9w0BCQEWE3NpbXBsZTJAc2lnbmlu
|
||||
Zy5jb20wHhcNMTgxMjA2MTExMDM2WhcNMjEwOTI1MTExMDM2WjAzMQswCQYDVQQG
|
||||
EwJGUjETMBEGA1UECAwKU29tZS1TdGF0ZTEPMA0GA1UECgwGQ2hlZXNlMIIBIjAN
|
||||
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAskX/bUtwFo1gF2BTPNaNcTUMaRFu
|
||||
FMZozK8IgLjccZ4kZ0R9oFO6Yp8Zl/IvPaf7tE26PI7XP7eHriUdhnQzX7iioDd0
|
||||
RZa68waIhAGc+xPzRFrP3b3yj3S2a9Rve3c0K+SCV+EtKAwsxMqQDhoo9PcBfo5B
|
||||
RHfht07uD5MncUcGirwN+/pxHV5xzAGPcc7On0/5L7bq/G+63nhu78zw9XyuLaHC
|
||||
PM5VbOUvpyIESJHbMMzTdFGL8ob9VKO+Kr1kVGdEA9i8FLGl3xz/GBKuW/JD0xyW
|
||||
DrU29mri5vYWHmkuv7ZWHGXnpXjTtPHwveE9/0/ArnmpMyR9JtqFr1oEvQIDAQAB
|
||||
MA0GCSqGSIb3DQEBCwUAA4IBAQBHta+NWXI08UHeOkGzOTGRiWXsOH2dqdX6gTe9
|
||||
xF1AIjyoQ0gvpoGVvlnChSzmlUj+vnx/nOYGIt1poE3hZA3ZHZD/awsvGyp3GwWD
|
||||
IfXrEViSCIyF+8tNNKYyUcEO3xdAsAUGgfUwwF/mZ6MBV5+A/ZEEILlTq8zFt9dV
|
||||
vdKzIt7fZYxYBBHFSarl1x8pDgWXlf3hAufevGJXip9xGYmznF0T5cq1RbWJ4be3
|
||||
/9K7yuWhuBYC3sbTbCneHBa91M82za+PIISc1ygCYtWSBoZKSAqLk0rkZpHaekDP
|
||||
WqeUSNGYV//RunTeuRDAf5OxehERb1srzBXhRZ3cZdzXbgR/`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
content := sanitize(test.toSanitize)
|
||||
|
||||
expected := url.QueryEscape(strings.Replace(test.expected, "\n", "", -1))
|
||||
assert.Equal(t, expected, content, "The sanitized certificates should be equal")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getSANs(t *testing.T) {
|
||||
urlFoo := testhelpers.MustParseURL("my.foo.com")
|
||||
urlBar := testhelpers.MustParseURL("my.bar.com")
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
cert *x509.Certificate // set the request TLS attribute if defined
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
desc: "With nil",
|
||||
},
|
||||
{
|
||||
desc: "Certificate without Sans",
|
||||
cert: &x509.Certificate{},
|
||||
},
|
||||
{
|
||||
desc: "Certificate with all Sans",
|
||||
cert: &x509.Certificate{
|
||||
DNSNames: []string{"foo", "bar"},
|
||||
EmailAddresses: []string{"test@test.com", "test2@test.com"},
|
||||
IPAddresses: []net.IP{net.IPv4(10, 0, 0, 1), net.IPv4(10, 0, 0, 2)},
|
||||
URIs: []*url.URL{urlFoo, urlBar},
|
||||
},
|
||||
expected: []string{"foo", "bar", "test@test.com", "test2@test.com", "10.0.0.1", "10.0.0.2", urlFoo.String(), urlBar.String()},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
sans := getSANs(test.cert)
|
||||
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if len(test.expected) > 0 {
|
||||
for i, expected := range test.expected {
|
||||
assert.Equal(t, expected, sans[i])
|
||||
}
|
||||
} else {
|
||||
assert.Empty(t, sans)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getCleanCertContents(certContents []string) string {
|
||||
exp := regexp.MustCompile("-----BEGIN CERTIFICATE-----(?s)(.*)")
|
||||
|
||||
var cleanedCertContent []string
|
||||
for _, certContent := range certContents {
|
||||
cert := sanitize([]byte(exp.FindString(certContent)))
|
||||
cleanedCertContent = append(cleanedCertContent, cert)
|
||||
}
|
||||
|
||||
return strings.Join(cleanedCertContent, certSeparator)
|
||||
}
|
||||
|
||||
func buildTLSWith(certContents []string) *tls.ConnectionState {
|
||||
var peerCertificates []*x509.Certificate
|
||||
|
||||
for _, certContent := range certContents {
|
||||
peerCertificates = append(peerCertificates, getCertificate(certContent))
|
||||
}
|
||||
|
||||
return &tls.ConnectionState{PeerCertificates: peerCertificates}
|
||||
}
|
||||
|
||||
func getCertificate(certContent string) *x509.Certificate {
|
||||
roots := x509.NewCertPool()
|
||||
ok := roots.AppendCertsFromPEM([]byte(signingCA))
|
||||
if !ok {
|
||||
panic("failed to parse root certificate")
|
||||
}
|
||||
|
||||
block, _ := pem.Decode([]byte(certContent))
|
||||
if block == nil {
|
||||
panic("failed to parse certificate PEM")
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
panic("failed to parse certificate: " + err.Error())
|
||||
}
|
||||
|
||||
return cert
|
||||
}
|
||||
|
||||
@@ -34,7 +34,11 @@ type entryPointMiddleware struct {
|
||||
}
|
||||
|
||||
func (e *entryPointMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
spanCtx, _ := e.Extract(opentracing.HTTPHeaders, tracing.HTTPHeadersCarrier(req.Header))
|
||||
spanCtx, err := e.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
|
||||
if err != nil {
|
||||
log.FromContext(middlewares.GetLoggerCtx(req.Context(), "tracing", entryPointTypeName)).
|
||||
Debugf("Failed to extract the context: %v", err)
|
||||
}
|
||||
|
||||
span, req, finish := e.StartSpanf(req, ext.SpanKindRPCServerEnum, "EntryPoint", []string{e.entryPoint, req.Host}, " ", ext.RPCServerOption(spanCtx))
|
||||
defer finish()
|
||||
|
||||
@@ -12,23 +12,23 @@ import (
|
||||
// It is used in order to create a specific and unique pattern for these labels.
|
||||
const MarathonConstraintPrefix = "Traefik-Marathon-505F9E15-BDC7-45E7-828D-C06C7BAB8091"
|
||||
|
||||
type constraintFunc func(map[string]string) bool
|
||||
type constraintLabelFunc func(map[string]string) bool
|
||||
|
||||
// Match reports whether the expression matches with the given labels.
|
||||
// MatchLabels reports whether the expression matches with the given labels.
|
||||
// The expression must match any logical boolean combination of:
|
||||
// - `Label(labelName, labelValue)`
|
||||
// - `LabelRegex(labelName, regexValue)`
|
||||
// - `MarathonConstraint(field:operator:value)`
|
||||
func Match(labels map[string]string, expr string) (bool, error) {
|
||||
func MatchLabels(labels map[string]string, expr string) (bool, error) {
|
||||
if expr == "" {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
p, err := predicate.NewParser(predicate.Def{
|
||||
Operators: predicate.Operators{
|
||||
AND: andFunc,
|
||||
NOT: notFunc,
|
||||
OR: orFunc,
|
||||
AND: andLabelFunc,
|
||||
NOT: notLabelFunc,
|
||||
OR: orLabelFunc,
|
||||
},
|
||||
Functions: map[string]interface{}{
|
||||
"Label": labelFn,
|
||||
@@ -45,20 +45,20 @@ func Match(labels map[string]string, expr string) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
fn, ok := parse.(constraintFunc)
|
||||
fn, ok := parse.(constraintLabelFunc)
|
||||
if !ok {
|
||||
return false, errors.New("not a constraintFunc")
|
||||
return false, errors.New("not a constraintLabelFunc")
|
||||
}
|
||||
return fn(labels), nil
|
||||
}
|
||||
|
||||
func labelFn(name, value string) constraintFunc {
|
||||
func labelFn(name, value string) constraintLabelFunc {
|
||||
return func(labels map[string]string) bool {
|
||||
return labels[name] == value
|
||||
}
|
||||
}
|
||||
|
||||
func labelRegexFn(name, expr string) constraintFunc {
|
||||
func labelRegexFn(name, expr string) constraintLabelFunc {
|
||||
return func(labels map[string]string) bool {
|
||||
matched, err := regexp.MatchString(expr, labels[name])
|
||||
if err != nil {
|
||||
@@ -68,7 +68,7 @@ func labelRegexFn(name, expr string) constraintFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func marathonFn(value string) constraintFunc {
|
||||
func marathonFn(value string) constraintLabelFunc {
|
||||
return func(labels map[string]string) bool {
|
||||
for k, v := range labels {
|
||||
if strings.HasPrefix(k, MarathonConstraintPrefix) {
|
||||
@@ -81,19 +81,19 @@ func marathonFn(value string) constraintFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func andFunc(a, b constraintFunc) constraintFunc {
|
||||
func andLabelFunc(a, b constraintLabelFunc) constraintLabelFunc {
|
||||
return func(labels map[string]string) bool {
|
||||
return a(labels) && b(labels)
|
||||
}
|
||||
}
|
||||
|
||||
func orFunc(a, b constraintFunc) constraintFunc {
|
||||
func orLabelFunc(a, b constraintLabelFunc) constraintLabelFunc {
|
||||
return func(labels map[string]string) bool {
|
||||
return a(labels) || b(labels)
|
||||
}
|
||||
}
|
||||
|
||||
func notFunc(a constraintFunc) constraintFunc {
|
||||
func notLabelFunc(a constraintLabelFunc) constraintLabelFunc {
|
||||
return func(labels map[string]string) bool {
|
||||
return !a(labels)
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMatch(t *testing.T) {
|
||||
func TestMatchLabels(t *testing.T) {
|
||||
testCases := []struct {
|
||||
expr string
|
||||
labels map[string]string
|
||||
@@ -192,7 +192,7 @@ func TestMatch(t *testing.T) {
|
||||
t.Run(test.expr, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
matches, err := Match(test.labels, test.expr)
|
||||
matches, err := MatchLabels(test.labels, test.expr)
|
||||
if test.expectedErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
92
pkg/provider/constraints/constraints_tags.go
Normal file
92
pkg/provider/constraints/constraints_tags.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package constraints
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
|
||||
"github.com/vulcand/predicate"
|
||||
)
|
||||
|
||||
type constraintTagFunc func([]string) bool
|
||||
|
||||
// MatchTags reports whether the expression matches with the given tags.
|
||||
// The expression must match any logical boolean combination of:
|
||||
// - `Tag(tagValue)`
|
||||
// - `TagRegex(regexValue)`
|
||||
func MatchTags(tags []string, expr string) (bool, error) {
|
||||
if expr == "" {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
p, err := predicate.NewParser(predicate.Def{
|
||||
Operators: predicate.Operators{
|
||||
AND: andTagFunc,
|
||||
NOT: notTagFunc,
|
||||
OR: orTagFunc,
|
||||
},
|
||||
Functions: map[string]interface{}{
|
||||
"Tag": tagFn,
|
||||
"TagRegex": tagRegexFn,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
parse, err := p.Parse(expr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
fn, ok := parse.(constraintTagFunc)
|
||||
if !ok {
|
||||
return false, errors.New("not a constraintTagFunc")
|
||||
}
|
||||
return fn(tags), nil
|
||||
}
|
||||
|
||||
func tagFn(name string) constraintTagFunc {
|
||||
return func(tags []string) bool {
|
||||
for _, tag := range tags {
|
||||
if tag == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func tagRegexFn(expr string) constraintTagFunc {
|
||||
return func(tags []string) bool {
|
||||
exp, err := regexp.Compile(expr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
if exp.MatchString(tag) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func andTagFunc(a, b constraintTagFunc) constraintTagFunc {
|
||||
return func(tags []string) bool {
|
||||
return a(tags) && b(tags)
|
||||
}
|
||||
}
|
||||
|
||||
func orTagFunc(a, b constraintTagFunc) constraintTagFunc {
|
||||
return func(tags []string) bool {
|
||||
return a(tags) || b(tags)
|
||||
}
|
||||
}
|
||||
|
||||
func notTagFunc(a constraintTagFunc) constraintTagFunc {
|
||||
return func(tags []string) bool {
|
||||
return !a(tags)
|
||||
}
|
||||
}
|
||||
111
pkg/provider/constraints/constraints_tags_test.go
Normal file
111
pkg/provider/constraints/constraints_tags_test.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package constraints
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMatchTags(t *testing.T) {
|
||||
testCases := []struct {
|
||||
expr string
|
||||
tags []string
|
||||
expected bool
|
||||
expectedErr bool
|
||||
}{
|
||||
{
|
||||
expr: `Tag("world")`,
|
||||
tags: []string{"hello", "world"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
expr: `Tag("worlds")`,
|
||||
tags: []string{"hello", "world"},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
expr: `!Tag("world")`,
|
||||
tags: []string{"hello", "world"},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
expr: `Tag("hello") && Tag("world")`,
|
||||
tags: []string{"hello", "world"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
expr: `Tag("hello") && Tag("worlds")`,
|
||||
tags: []string{"hello", "world"},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
expr: `Tag("hello") && !Tag("world")`,
|
||||
tags: []string{"hello", "world"},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
expr: `Tag("hello") || Tag( "world")`,
|
||||
tags: []string{"hello", "world"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
expr: `Tag( "worlds") || Tag("hello")`,
|
||||
tags: []string{"hello", "world"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
expr: `Tag("hello") || !Tag("world")`,
|
||||
tags: []string{"hello", "world"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
expr: `Tag()`,
|
||||
tags: []string{"hello", "world"},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
expr: `Foo("hello")`,
|
||||
tags: []string{"hello", "world"},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
expr: `Tag("hello")`,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
expr: ``,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
expr: `TagRegex("hel\\w+")`,
|
||||
tags: []string{"hello", "world"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
expr: `TagRegex("hell\\w+s")`,
|
||||
tags: []string{"hello", "world"},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
expr: `!TagRegex("hel\\w+")`,
|
||||
tags: []string{"hello", "world"},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.expr, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
matches, err := MatchTags(test.tags, test.expr)
|
||||
if test.expectedErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, test.expected, matches)
|
||||
})
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user