forked from SW/traefik
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
251798a778 | ||
|
|
73306a1533 | ||
|
|
b3eb629785 | ||
|
|
9c59df5e9c | ||
|
|
2a88b25712 | ||
|
|
b952f814c1 | ||
|
|
6d6f8b28d7 | ||
|
|
118d56fc40 | ||
|
|
fbf90e6981 | ||
|
|
607faace07 | ||
|
|
685962545a | ||
|
|
34d29e7a10 | ||
|
|
05f3e60366 | ||
|
|
ac4086d0ac | ||
|
|
ede2be1f66 | ||
|
|
86cc6df374 | ||
|
|
619621f239 | ||
|
|
73ba7ed2d2 | ||
|
|
a49b537d9c | ||
|
|
45328ab719 | ||
|
|
c0b0f3f0f7 | ||
|
|
a4560fa20d | ||
|
|
fbdb6e6e78 | ||
|
|
8d58f33a28 | ||
|
|
d2a2362be5 | ||
|
|
4c0a3721d0 | ||
|
|
3bf4a8fbe2 | ||
|
|
2da7fa0397 | ||
|
|
0d58e8d1ad | ||
|
|
dad76e0478 | ||
|
|
79aab5aab8 | ||
|
|
6622027c7c | ||
|
|
401c171bbd | ||
|
|
63bb770b9c | ||
|
|
c7b24f4e9c | ||
|
|
25725e9b2f | ||
|
|
aaf5aa4506 | ||
|
|
9297055ad8 | ||
|
|
a79868fadc | ||
|
|
ca55dfe1c6 | ||
|
|
5780dc2b15 | ||
|
|
764bf59d4d | ||
|
|
7543709ecf | ||
|
|
1048348ae6 | ||
|
|
ba822acb23 | ||
|
|
f5dd233a3b |
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.6
|
||||
- for Traefik v2: use branch v2.7
|
||||
|
||||
Bug fixes:
|
||||
- for Traefik v1: use branch v1.7
|
||||
- for Traefik v2: use branch v2.6
|
||||
- for Traefik v2: use branch v2.7
|
||||
|
||||
Enhancements:
|
||||
- for Traefik v1: we only accept bug fixes
|
||||
|
||||
@@ -217,3 +217,6 @@
|
||||
[[issues.exclude-rules]]
|
||||
path = "(.+)\\.go"
|
||||
text = "struct-tag: unknown option 'inline' in JSON tag"
|
||||
[[issues.exclude-rules]]
|
||||
path = "pkg/server/router/tcp/manager.go"
|
||||
text = "Function 'buildEntryPointHandler' is too long (.+)"
|
||||
|
||||
@@ -64,7 +64,7 @@ blocks:
|
||||
- name: GH_VERSION
|
||||
value: 1.12.1
|
||||
- name: CODENAME
|
||||
value: "rocamadour"
|
||||
value: "epoisses"
|
||||
- name: IN_DOCKER
|
||||
value: ""
|
||||
prologue:
|
||||
|
||||
103
CHANGELOG.md
103
CHANGELOG.md
@@ -1,3 +1,64 @@
|
||||
## [v2.7.1](https://github.com/traefik/traefik/tree/v2.7.1) (2022-06-13)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.7.0...v2.7.1)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[acme]** Update go-acme/lego to v4.7.0 ([#9065](https://github.com/traefik/traefik/pull/9065) by [ldez](https://github.com/ldez))
|
||||
- **[logs]** Fix invalid placeholder in log message ([#9084](https://github.com/traefik/traefik/pull/9084) by [ldez](https://github.com/ldez))
|
||||
|
||||
**Documentation:**
|
||||
- **[hub]** Hub documentation ([#9090](https://github.com/traefik/traefik/pull/9090) by [ldez](https://github.com/ldez))
|
||||
- **[k8s,k8s/gatewayapi]** Update Gateway API link from v1alpha1 to v1alpha2 ([#9083](https://github.com/traefik/traefik/pull/9083) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- **[k8s,k8s/gatewayapi]** Update Gateway API links ([#9058](https://github.com/traefik/traefik/pull/9058) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- **[middleware]** Fix typo in stripPrefix middleware docs ([#9051](https://github.com/traefik/traefik/pull/9051) by [rbarbey](https://github.com/rbarbey))
|
||||
- **[rules]** Fix rule expression rendering ([#9076](https://github.com/traefik/traefik/pull/9076) by [ldez](https://github.com/ldez))
|
||||
- Update the link for contributor swag ([#9056](https://github.com/traefik/traefik/pull/9056) by [tfny](https://github.com/tfny))
|
||||
- Fix Traefik version s/2.6/2.7/ ([#9047](https://github.com/traefik/traefik/pull/9047) by [mpl](https://github.com/mpl))
|
||||
- Update the contributing docs for clarity and to encourage community activity ([#9035](https://github.com/traefik/traefik/pull/9035) by [tfny](https://github.com/tfny))
|
||||
|
||||
## [v2.7.0](https://github.com/traefik/traefik/tree/v2.7.0) (2022-05-24)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.7.0-rc1...v2.7.0)
|
||||
|
||||
**Enhancements:**
|
||||
- **[consulcatalog]** Watch for Consul events to rebuild the dynamic configuration ([#8476](https://github.com/traefik/traefik/pull/8476) by [JasonWangA](https://github.com/JasonWangA))
|
||||
- **[healthcheck]** Add Failover service ([#8825](https://github.com/traefik/traefik/pull/8825) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- **[http3]** Configure advertised port using h3 server option ([#8778](https://github.com/traefik/traefik/pull/8778) by [kevinpollet](https://github.com/kevinpollet))
|
||||
- **[http3]** Upgrade quic-go to v0.25.0 ([#8760](https://github.com/traefik/traefik/pull/8760) by [sylr](https://github.com/sylr))
|
||||
- **[hub]** Add Traefik Hub Integration (Experimental Feature) ([#8837](https://github.com/traefik/traefik/pull/8837) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- **[k8s/crd,k8s]** Allow empty services in Kubernetes CRD ([#8802](https://github.com/traefik/traefik/pull/8802) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- **[metrics]** Support InfluxDB v2 metrics backend ([#8250](https://github.com/traefik/traefik/pull/8250) by [sh7dm](https://github.com/sh7dm))
|
||||
- **[plugins]** Remove Pilot token setup constraint to use plugins ([#8869](https://github.com/traefik/traefik/pull/8869) by [ldez](https://github.com/ldez))
|
||||
- **[provider]** Refactor configuration reload/throttling ([#6633](https://github.com/traefik/traefik/pull/6633) by [rkojedzinszky](https://github.com/rkojedzinszky))
|
||||
- **[rules,tcp]** Add HostSNIRegexp rule matcher for TCP ([#8849](https://github.com/traefik/traefik/pull/8849) by [rtribotte](https://github.com/rtribotte))
|
||||
- **[tcp]** Add muxer for TCP Routers ([#8182](https://github.com/traefik/traefik/pull/8182) by [dtomcej](https://github.com/dtomcej))
|
||||
- **[webui,pilot]** Add Traefik Hub access and remove Pilot access ([#8848](https://github.com/traefik/traefik/pull/8848) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- **[webui]** Add a link to service on router detail view ([#8821](https://github.com/traefik/traefik/pull/8821) by [Tchoupinax](https://github.com/Tchoupinax))
|
||||
|
||||
**Bug fixes:**
|
||||
- **[hub]** Skip Provide when TLS is nil ([#9031](https://github.com/traefik/traefik/pull/9031) by [ldez](https://github.com/ldez))
|
||||
- **[tcp]** Fix TCP-TLS/HTTPS routing precedence ([#9024](https://github.com/traefik/traefik/pull/9024) by [rtribotte](https://github.com/rtribotte))
|
||||
- **[webui,hub]** Use dedicated entrypoint for the tunnels ([#9023](https://github.com/traefik/traefik/pull/9023) by [youkoulayley](https://github.com/youkoulayley))
|
||||
|
||||
**Documentation:**
|
||||
- **[hub]** Fix Traefik Hub TLS documentation ([#8883](https://github.com/traefik/traefik/pull/8883) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- Add a Feature Deprecation page ([#8868](https://github.com/traefik/traefik/pull/8868) by [ddtmachado](https://github.com/ddtmachado))
|
||||
- Prepare release v2.7.0-rc1 ([#8879](https://github.com/traefik/traefik/pull/8879) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- Prepare release v2.7.0-rc2 ([#8900](https://github.com/traefik/traefik/pull/8900) by [rtribotte](https://github.com/rtribotte))
|
||||
|
||||
**Misc:**
|
||||
- Merge current v2.6 into v2.7 ([#8984](https://github.com/traefik/traefik/pull/8984) by [kevinpollet](https://github.com/kevinpollet))
|
||||
- Merge current v2.6 into v2.7 ([#8958](https://github.com/traefik/traefik/pull/8958) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- Merge current v2.6 into v2.7 ([#8899](https://github.com/traefik/traefik/pull/8899) by [rtribotte](https://github.com/rtribotte))
|
||||
- Merge current v2.6 into master ([#8877](https://github.com/traefik/traefik/pull/8877) by [rtribotte](https://github.com/rtribotte))
|
||||
- Merge current v2.6 into master ([#8865](https://github.com/traefik/traefik/pull/8865) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- Merge current v2.6 into master ([#8832](https://github.com/traefik/traefik/pull/8832) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- Merge current v2.6 into master ([#8793](https://github.com/traefik/traefik/pull/8793) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- Merge current v2.6 into master ([#8777](https://github.com/traefik/traefik/pull/8777) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- Merge current v2.6 into master ([#8757](https://github.com/traefik/traefik/pull/8757) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- Merge current v2.6 into master ([#8754](https://github.com/traefik/traefik/pull/8754) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- Merge current v2.6 into master ([#8736](https://github.com/traefik/traefik/pull/8736) by [kevinpollet](https://github.com/kevinpollet))
|
||||
- Merge current v2.6 into master ([#8689](https://github.com/traefik/traefik/pull/8689) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- Merge current v2.6 into master ([#8666](https://github.com/traefik/traefik/pull/8666) by [tomMoulard](https://github.com/tomMoulard))
|
||||
|
||||
## [v2.6.7](https://github.com/traefik/traefik/tree/v2.6.7) (2022-05-23)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.6.6...v2.6.7)
|
||||
|
||||
@@ -44,6 +105,15 @@ Release canceled.
|
||||
|
||||
Release canceled.
|
||||
|
||||
## [v2.7.0-rc2](https://github.com/traefik/traefik/tree/v2.7.0-rc2) (2022-03-29)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.7.0-rc1...v2.7.0-rc2)
|
||||
|
||||
**Documentation:**
|
||||
- **[hub]** Fix Traefik Hub TLS documentation ([#8883](https://github.com/traefik/traefik/pull/8883) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
|
||||
**Misc:**
|
||||
- Merge current v2.6 into v2.7 ([#8899](https://github.com/traefik/traefik/pull/8899) by [rtribotte](https://github.com/rtribotte))
|
||||
|
||||
## [v2.6.3](https://github.com/traefik/traefik/tree/v2.6.3) (2022-03-28)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.6.2...v2.6.3)
|
||||
|
||||
@@ -51,6 +121,39 @@ Release canceled.
|
||||
- **[plugins]** Fix slice parsing for plugins ([#8886](https://github.com/traefik/traefik/pull/8886) by [ldez](https://github.com/ldez))
|
||||
- **[tls]** Return TLS unrecognized_name error when no certificate is available ([#8893](https://github.com/traefik/traefik/pull/8893) by [rtribotte](https://github.com/rtribotte))
|
||||
|
||||
## [v2.7.0-rc1](https://github.com/traefik/traefik/tree/v2.7.0-rc1) (2022-03-24)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.6.0-rc1...v2.7.0-rc1)
|
||||
|
||||
**Enhancements:**
|
||||
- **[consulcatalog]** Watch for Consul events to rebuild the dynamic configuration ([#8476](https://github.com/traefik/traefik/pull/8476) by [JasonWangA](https://github.com/JasonWangA))
|
||||
- **[healthcheck]** Add Failover service ([#8825](https://github.com/traefik/traefik/pull/8825) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- **[http3]** Configure advertised port using h3 server option ([#8778](https://github.com/traefik/traefik/pull/8778) by [kevinpollet](https://github.com/kevinpollet))
|
||||
- **[http3]** Upgrade quic-go to v0.25.0 ([#8760](https://github.com/traefik/traefik/pull/8760) by [sylr](https://github.com/sylr))
|
||||
- **[hub]** Add Traefik Hub Integration (Experimental Feature) ([#8837](https://github.com/traefik/traefik/pull/8837) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- **[k8s/crd,k8s]** Allow empty services in Kubernetes CRD ([#8802](https://github.com/traefik/traefik/pull/8802) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- **[metrics]** Support InfluxDB v2 metrics backend ([#8250](https://github.com/traefik/traefik/pull/8250) by [sh7dm](https://github.com/sh7dm))
|
||||
- **[plugins]** Remove Pilot token setup constraint to use plugins ([#8869](https://github.com/traefik/traefik/pull/8869) by [ldez](https://github.com/ldez))
|
||||
- **[provider]** Refactor configuration reload/throttling ([#6633](https://github.com/traefik/traefik/pull/6633) by [rkojedzinszky](https://github.com/rkojedzinszky))
|
||||
- **[rules,tcp]** Add HostSNIRegexp rule matcher for TCP ([#8849](https://github.com/traefik/traefik/pull/8849) by [rtribotte](https://github.com/rtribotte))
|
||||
- **[tcp]** Add muxer for TCP Routers ([#8182](https://github.com/traefik/traefik/pull/8182) by [dtomcej](https://github.com/dtomcej))
|
||||
- **[webui,pilot]** Add Traefik Hub access and remove Pilot access ([#8848](https://github.com/traefik/traefik/pull/8848) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- **[webui]** Add a link to service on router detail view ([#8821](https://github.com/traefik/traefik/pull/8821) by [Tchoupinax](https://github.com/Tchoupinax))
|
||||
|
||||
**Documentation:**
|
||||
- Add a Feature Deprecation page ([#8868](https://github.com/traefik/traefik/pull/8868) by [ddtmachado](https://github.com/ddtmachado))
|
||||
|
||||
**Misc:**
|
||||
- Merge current v2.6 into master ([#8877](https://github.com/traefik/traefik/pull/8877) by [rtribotte](https://github.com/rtribotte))
|
||||
- Merge current v2.6 into master ([#8865](https://github.com/traefik/traefik/pull/8865) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- Merge current v2.6 into master ([#8832](https://github.com/traefik/traefik/pull/8832) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- Merge current v2.6 into master ([#8793](https://github.com/traefik/traefik/pull/8793) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- Merge current v2.6 into master ([#8777](https://github.com/traefik/traefik/pull/8777) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- Merge current v2.6 into master ([#8757](https://github.com/traefik/traefik/pull/8757) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- Merge current v2.6 into master ([#8754](https://github.com/traefik/traefik/pull/8754) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- Merge current v2.6 into master ([#8736](https://github.com/traefik/traefik/pull/8736) by [kevinpollet](https://github.com/kevinpollet))
|
||||
- Merge current v2.6 into master ([#8689](https://github.com/traefik/traefik/pull/8689) by [tomMoulard](https://github.com/tomMoulard))
|
||||
- Merge current v2.6 into master ([#8666](https://github.com/traefik/traefik/pull/8666) by [tomMoulard](https://github.com/tomMoulard))
|
||||
|
||||
## [v2.6.2](https://github.com/traefik/traefik/tree/v2.6.2) (2022-03-24)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.6.1...v2.6.2)
|
||||
|
||||
|
||||
4
Makefile
4
Makefile
@@ -34,8 +34,6 @@ DOCKER_RUN_TRAEFIK_NOTTY := docker run $(INTEGRATION_OPTS) $(if $(DOCKER_NON_INT
|
||||
|
||||
IN_DOCKER ?= true
|
||||
|
||||
PLATFORM_URL := $(if $(PLATFORM_URL),$(PLATFORM_URL),"https://pilot.traefik.io")
|
||||
|
||||
.PHONY: default
|
||||
default: binary
|
||||
|
||||
@@ -60,7 +58,7 @@ endif
|
||||
## Build WebUI Docker image
|
||||
.PHONY: build-webui-image
|
||||
build-webui-image:
|
||||
docker build -t traefik-webui --build-arg ARG_PLATFORM_URL=$(PLATFORM_URL) -f webui/Dockerfile webui
|
||||
docker build -t traefik-webui -f webui/Dockerfile webui
|
||||
|
||||
## Clean WebUI static generated assets
|
||||
.PHONY: clean-webui
|
||||
|
||||
@@ -27,10 +27,10 @@ func initPlugins(staticCfg *static.Configuration) (*plugins.Client, map[string]p
|
||||
var client *plugins.Client
|
||||
plgs := map[string]plugins.Descriptor{}
|
||||
|
||||
if isPilotEnabled(staticCfg) && hasPlugins(staticCfg) {
|
||||
if hasPlugins(staticCfg) {
|
||||
opts := plugins.ClientOptions{
|
||||
Output: outputDir,
|
||||
Token: staticCfg.Pilot.Token,
|
||||
Token: getPilotToken(staticCfg),
|
||||
}
|
||||
|
||||
var err error
|
||||
@@ -79,6 +79,14 @@ func isPilotEnabled(staticCfg *static.Configuration) bool {
|
||||
return staticCfg.Pilot != nil && staticCfg.Pilot.Token != ""
|
||||
}
|
||||
|
||||
func getPilotToken(staticCfg *static.Configuration) string {
|
||||
if staticCfg.Pilot == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return staticCfg.Pilot.Token
|
||||
}
|
||||
|
||||
func hasPlugins(staticCfg *static.Configuration) bool {
|
||||
return staticCfg.Experimental != nil && len(staticCfg.Experimental.Plugins) > 0
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/pilot"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/acme"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/aggregator"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/hub"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/traefik"
|
||||
"github.com/traefik/traefik/v2/pkg/safe"
|
||||
"github.com/traefik/traefik/v2/pkg/server"
|
||||
@@ -180,8 +181,7 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
||||
tlsManager := traefiktls.NewManager()
|
||||
httpChallengeProvider := acme.NewChallengeHTTP()
|
||||
|
||||
// we need to wait at least 2 times the ProvidersThrottleDuration to be sure to handle the challenge.
|
||||
tlsChallengeProvider := acme.NewChallengeTLSALPN(time.Duration(staticConfiguration.Providers.ProvidersThrottleDuration) * 2)
|
||||
tlsChallengeProvider := acme.NewChallengeTLSALPN()
|
||||
err = providerAggregator.AddProvider(tlsChallengeProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -240,6 +240,19 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
||||
}
|
||||
}
|
||||
|
||||
// Traefik Hub
|
||||
|
||||
if staticConfiguration.Hub != nil {
|
||||
if err = providerAggregator.AddProvider(staticConfiguration.Hub); err != nil {
|
||||
return nil, fmt.Errorf("adding Traefik Hub provider: %w", err)
|
||||
}
|
||||
|
||||
// API is mandatory for Traefik Hub to access the dynamic configuration.
|
||||
if staticConfiguration.API == nil {
|
||||
staticConfiguration.API = &static.API{}
|
||||
}
|
||||
}
|
||||
|
||||
// Metrics
|
||||
|
||||
metricRegistries := registerMetricClients(staticConfiguration.Metrics)
|
||||
@@ -265,7 +278,6 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
||||
watcher := server.NewConfigurationWatcher(
|
||||
routinesPool,
|
||||
providerAggregator,
|
||||
time.Duration(staticConfiguration.Providers.ProvidersThrottleDuration),
|
||||
getDefaultsEntrypoints(staticConfiguration),
|
||||
"internal",
|
||||
)
|
||||
@@ -323,7 +335,10 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := resolverNames[rt.TLS.CertResolver]; !ok {
|
||||
if _, ok := resolverNames[rt.TLS.CertResolver]; !ok &&
|
||||
// "traefik-hub" is an allowed certificate resolver name in a Traefik Hub Experimental feature context.
|
||||
// It is used to activate its own certificate resolution, even though it is not a "classical" traefik certificate resolver.
|
||||
(staticConfiguration.Hub == nil || rt.TLS.CertResolver != "traefik-hub") {
|
||||
log.WithoutContext().Errorf("the router %s uses a non-existent resolver: %s", rtName, rt.TLS.CertResolver)
|
||||
}
|
||||
}
|
||||
@@ -346,6 +361,11 @@ func getHTTPChallengeHandler(acmeProviders []*acme.Provider, httpChallengeProvid
|
||||
func getDefaultsEntrypoints(staticConfiguration *static.Configuration) []string {
|
||||
var defaultEntryPoints []string
|
||||
for name, cfg := range staticConfiguration.EntryPoints {
|
||||
// Traefik Hub entryPoint should not be part of the set of default entryPoints.
|
||||
if hub.APIEntrypoint == name || hub.TunnelEntrypoint == name {
|
||||
continue
|
||||
}
|
||||
|
||||
protocol, err := cfg.GetProtocol()
|
||||
if err != nil {
|
||||
// Should never happen because Traefik should not start if protocol is invalid.
|
||||
@@ -450,6 +470,16 @@ func registerMetricClients(metricsConfig *types.Metrics) []metrics.Registry {
|
||||
metricsConfig.InfluxDB.Address, metricsConfig.InfluxDB.PushInterval)
|
||||
}
|
||||
|
||||
if metricsConfig.InfluxDB2 != nil {
|
||||
ctx := log.With(context.Background(), log.Str(log.MetricsProviderName, "influxdb2"))
|
||||
influxDB2Register := metrics.RegisterInfluxDB2(ctx, metricsConfig.InfluxDB2)
|
||||
if influxDB2Register != nil {
|
||||
registries = append(registries, influxDB2Register)
|
||||
log.FromContext(ctx).Debugf("Configured InfluxDB v2 metrics: pushing to %s (%s org/%s bucket) once every %s",
|
||||
metricsConfig.InfluxDB2.Address, metricsConfig.InfluxDB2.Org, metricsConfig.InfluxDB2.Bucket, metricsConfig.InfluxDB2.PushInterval)
|
||||
}
|
||||
}
|
||||
|
||||
return registries
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,6 @@ Spread the Love & Tell Us about It
|
||||
|
||||
There are many ways to contribute to the project, and there is one that always spark joy: when we see/read about users talking about how Traefik helps them solve their problems.
|
||||
|
||||
If you're talking about Traefik, [let us know](https://blog.traefik.io/spread-the-love-ba5a40aa72e7) and we'll promote your enthusiasm!
|
||||
If you're talking about Traefik, [let us know](https://traefik.io/submit-my-contribution/) and we'll promote your enthusiasm!
|
||||
|
||||
Also, if you've written about Traefik or shared useful information you'd like to promote, feel free to add links in the [dedicated wiki page on Github](https://github.com/traefik/traefik/wiki/Awesome-Traefik).
|
||||
|
||||
@@ -8,41 +8,54 @@ description: "Help us help you! Learn how to submit an issue, following the guid
|
||||
Help Us Help You!
|
||||
{: .subtitle }
|
||||
|
||||
Issues are perfect for requesting a feature/enhancement or reporting a suspected bug.
|
||||
We use the [GitHub issue tracker](https://github.com/traefik/traefik/issues) to keep track of issues in Traefik.
|
||||
|
||||
The process of sorting and checking the issues is a daunting task, and requires a lot of work (more than an hour a day ... just for sorting).
|
||||
To save us some time and get quicker feedback, be sure to follow the guide lines below.
|
||||
To help us (and other community members) quickly and easily understand what you need,
|
||||
be sure to follow the guidelines below.
|
||||
|
||||
!!! important "Getting Help Vs Reporting an Issue"
|
||||
|
||||
The issue tracker is not a general support forum, but a place to report bugs and asks for new features.
|
||||
|
||||
For end-user related support questions, try using first:
|
||||
|
||||
- the Traefik community forum: [](https://community.traefik.io/)
|
||||
For end-user related support questions, try using the [Traefik Community Forum](https://community.traefik.io/)
|
||||
[](https://community.traefik.io/)
|
||||
|
||||
## Issue Title
|
||||
|
||||
The title must be short and descriptive. (~60 characters)
|
||||
|
||||
## Description
|
||||
Examples:
|
||||
|
||||
Follow the [issue template](https://github.com/traefik/traefik/blob/master/.github/ISSUE_TEMPLATE.md) as much as possible.
|
||||
|
||||
Explain us in which conditions you encountered the issue, what is your context.
|
||||
|
||||
Remain as clear and concise as possible
|
||||
|
||||
Take time to polish the format of your message so we'll enjoy reading it and working on it.
|
||||
Help the readers focus on what matters, and help them understand the structure of your message (see the [GitHub Markdown Syntax](https://docs.github.com/en/get-started/writing-on-github)).
|
||||
* Bug: Duplicate requests in access logs
|
||||
* Feature: Support TCP
|
||||
|
||||
## Feature Request
|
||||
|
||||
Traefik is an open-source project and aims to be the best edge router possible.
|
||||
Traefik is an open source project and aims to be the best edge router possible.
|
||||
|
||||
Remember when asking for new features that these must be useful to the majority (and not only useful in edge case scenarios, or hack-like setups).
|
||||
Follow the [issue template](https://github.com/traefik/traefik/blob/master/.github/ISSUE_TEMPLATE/feature-request.yml) as much as possible.
|
||||
|
||||
Do you best to explain what you're looking for, and why it would improve Traefik for everyone.
|
||||
Do your best to explain what you're looking for, and why it would improve Traefik for everyone.
|
||||
Be detailed and share the use-case(s) to allow us to see the value of your feature request as quickly as possible.
|
||||
Features with a lot of positive interaction (claps, +1s, conversation about how this would impact them) indicate higher community interest and help us to prioritize.
|
||||
|
||||
If you are interested in creating a PR for your feature request, let us know in the the issue so we can work with you.
|
||||
It can take a lot of work to make sure a PR can integrate with our existing code and planning with the team ahead of time can make sure that your PR can be accepted and merged quickly.
|
||||
|
||||
## Issues or Possible Bug Reports
|
||||
|
||||
Follow the [issue template](https://github.com/traefik/traefik/blob/master/.github/ISSUE_TEMPLATE/bug_report.yml) as much as possible.
|
||||
|
||||
Explain the conditions in which you encountered the issue; what is your context?
|
||||
Share any logs you may have and make sure to share the steps it takes to reproduce your issue or bug.
|
||||
|
||||
Remain as clear and concise as possible.
|
||||
|
||||
Take time to polish the format of your message so we'll enjoy reading it and working on it.
|
||||
Help your readers focus on what matters and help them understand the structure of your message (see the [GitHub Markdown Syntax](https://docs.github.com/en/get-started/writing-on-github)).
|
||||
|
||||
## International English
|
||||
|
||||
|
||||
20
docs/content/deprecation/features.md
Normal file
20
docs/content/deprecation/features.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Feature Deprecation Notices
|
||||
|
||||
This page is maintained and updated periodically to reflect our roadmap and any decisions around feature deprecation.
|
||||
|
||||
| Feature | Deprecated | End of Support | Removal |
|
||||
|-------------------------------------------------------|------------|----------------|---------|
|
||||
| [Pilot Dashboard (Metrics)](#pilot-dashboard-metrics) | 2.7 | 2.8 | 2.9 |
|
||||
| [Pilot Plugins](#pilot-plugins) | 2.7 | 2.8 | 2.9 |
|
||||
|
||||
## Impact
|
||||
|
||||
### Pilot Dashboard (Metrics)
|
||||
|
||||
Metrics will continue to function normally up to 2.8, when they will be disabled.
|
||||
In 2.9, the Pilot platform and all Traefik integration code will be permanently removed.
|
||||
|
||||
### Pilot Plugins
|
||||
|
||||
Starting on 2.7 the pilot token will not be a requirement anymore.
|
||||
At 2.9, a new plugin catalog home should be available, decoupled from pilot.
|
||||
@@ -79,7 +79,7 @@ traefik --help
|
||||
# or
|
||||
|
||||
docker run traefik[:version] --help
|
||||
# ex: docker run traefik:v2.6 --help
|
||||
# ex: docker run traefik:v2.7 --help
|
||||
```
|
||||
|
||||
All available arguments can also be found [here](../reference/static-configuration/cli.md).
|
||||
|
||||
@@ -16,12 +16,12 @@ You can install Traefik with the following flavors:
|
||||
|
||||
Choose one of the [official Docker images](https://hub.docker.com/_/traefik) and run it with one sample configuration file:
|
||||
|
||||
* [YAML](https://raw.githubusercontent.com/traefik/traefik/v2.6/traefik.sample.yml)
|
||||
* [TOML](https://raw.githubusercontent.com/traefik/traefik/v2.6/traefik.sample.toml)
|
||||
* [YAML](https://raw.githubusercontent.com/traefik/traefik/v2.7/traefik.sample.yml)
|
||||
* [TOML](https://raw.githubusercontent.com/traefik/traefik/v2.7/traefik.sample.toml)
|
||||
|
||||
```bash
|
||||
docker run -d -p 8080:8080 -p 80:80 \
|
||||
-v $PWD/traefik.yml:/etc/traefik/traefik.yml traefik:v2.6
|
||||
-v $PWD/traefik.yml:/etc/traefik/traefik.yml traefik:v2.7
|
||||
```
|
||||
|
||||
For more details, go to the [Docker provider documentation](../providers/docker.md)
|
||||
@@ -29,7 +29,7 @@ For more details, go to the [Docker provider documentation](../providers/docker.
|
||||
!!! tip
|
||||
|
||||
* Prefer a fixed version than the latest that could be an unexpected version.
|
||||
ex: `traefik:v2.6`
|
||||
ex: `traefik:v2.7`
|
||||
* Docker images are based from the [Alpine Linux Official image](https://hub.docker.com/_/alpine).
|
||||
* Any orchestrator using docker images can fetch the official Traefik docker image.
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ version: '3'
|
||||
services:
|
||||
reverse-proxy:
|
||||
# The official v2 Traefik docker image
|
||||
image: traefik:v2.6
|
||||
image: traefik:v2.7
|
||||
# Enables the web UI and tells Traefik to listen to docker
|
||||
command: --api.insecure=true --providers.docker
|
||||
ports:
|
||||
|
||||
@@ -337,15 +337,17 @@ For complete details, refer to your provider's _Additional configuration_ link.
|
||||
| [Hetzner](https://hetzner.com) | `hetzner` | `HETZNER_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/hetzner) |
|
||||
| [hosting.de](https://www.hosting.de) | `hostingde` | `HOSTINGDE_API_KEY`, `HOSTINGDE_ZONE_NAME` | [Additional configuration](https://go-acme.github.io/lego/dns/hostingde) |
|
||||
| [Hosttech](https://www.hosttech.eu) | `hosttech` | `HOSTTECH_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/hosttech) |
|
||||
| [HyperOne](https://www.hyperone.com) | `hyperone` | `HYPERONE_PASSPORT_LOCATION`, `HYPERONE_LOCATION_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/hyperone) |
|
||||
| [Hurricane Electric](https://dns.he.net) | `hurricane` | `HURRICANE_TOKENS` [^6] | [Additional configuration](https://go-acme.github.io/lego/dns/hurricane) |
|
||||
| [HyperOne](https://www.hyperone.com) | `hyperone` | `HYPERONE_PASSPORT_LOCATION`, `HYPERONE_LOCATION_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/hyperone) |
|
||||
| [IBM Cloud (SoftLayer)](https://www.ibm.com/cloud/) | `ibmcloud` | `SOFTLAYER_USERNAME`, `SOFTLAYER_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/ibmcloud) |
|
||||
| [IIJ DNS Platform Service](https://www.iij.ad.jp) | `iijdpf` | `IIJ_DPF_API_TOKEN` , `IIJ_DPF_DPM_SERVICE_CODE` | [Additional configuration](https://go-acme.github.io/lego/dns/iijdpf) |
|
||||
| [IIJ](https://www.iij.ad.jp/) | `iij` | `IIJ_API_ACCESS_KEY`, `IIJ_API_SECRET_KEY`, `IIJ_DO_SERVICE_CODE` | [Additional configuration](https://go-acme.github.io/lego/dns/iij) |
|
||||
| [Infoblox](https://www.infoblox.com/) | `infoblox` | `INFOBLOX_USER`, `INFOBLOX_PASSWORD`, `INFOBLOX_HOST` | [Additional configuration](https://go-acme.github.io/lego/dns/infoblox) |
|
||||
| [Infomaniak](https://www.infomaniak.com) | `infomaniak` | `INFOMANIAK_ACCESS_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/infomaniak) |
|
||||
| [Internet.bs](https://internetbs.net) | `internetbs` | `INTERNET_BS_API_KEY`, `INTERNET_BS_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/internetbs) |
|
||||
| [INWX](https://www.inwx.de/en) | `inwx` | `INWX_USERNAME`, `INWX_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/inwx) |
|
||||
| [ionos](https://ionos.com/) | `ionos` | `IONOS_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/ionos) |
|
||||
| [iwantmyname](https://iwantmyname.com) | `iwantmyname` | `IWANTMYNAME_USERNAME` , `IWANTMYNAME_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/iwantmyname) |
|
||||
| [Joker.com](https://joker.com) | `joker` | `JOKER_API_MODE` with `JOKER_API_KEY` or `JOKER_USERNAME`, `JOKER_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/joker) |
|
||||
| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `DNS_ZONE` | [Additional configuration](https://go-acme.github.io/lego/dns/lightsail) |
|
||||
| [Linode v4](https://www.linode.com) | `linode` | `LINODE_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/linode) |
|
||||
@@ -385,6 +387,7 @@ For complete details, refer to your provider's _Additional configuration_ link.
|
||||
| [TransIP](https://www.transip.nl/) | `transip` | `TRANSIP_ACCOUNT_NAME`, `TRANSIP_PRIVATE_KEY_PATH` | [Additional configuration](https://go-acme.github.io/lego/dns/transip) |
|
||||
| [UKFast SafeDNS](https://www.ukfast.co.uk/dns-hosting.html) | `safedns` | `SAFEDNS_AUTH_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/safedns) |
|
||||
| [VegaDNS](https://github.com/shupp/VegaDNS-API) | `vegadns` | `SECRET_VEGADNS_KEY`, `SECRET_VEGADNS_SECRET`, `VEGADNS_URL` | [Additional configuration](https://go-acme.github.io/lego/dns/vegadns) |
|
||||
| [Vercel](https://vercel.com) | `vercel` | `VERCEL_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/vercel) |
|
||||
| [Versio](https://www.versio.nl/domeinnamen) | `versio` | `VERSIO_USERNAME`, `VERSIO_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/versio) |
|
||||
| [VinylDNS](https://www.vinyldns.io) | `vinyldns` | `VINYLDNS_ACCESS_KEY`, `VINYLDNS_SECRET_KEY`, `VINYLDNS_HOST` | [Additional configuration](https://go-acme.github.io/lego/dns/vinyldns) |
|
||||
| [Vscale](https://vscale.io/) | `vscale` | `VSCALE_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/vscale) |
|
||||
|
||||
@@ -87,7 +87,7 @@ The `prefixes` option defines the prefixes to strip from the request URL.
|
||||
For instance, `/products` also matches `/products/shoes` and `/products/shirts`.
|
||||
|
||||
If your backend is serving assets (e.g., images or JavaScript files), it can use the `X-Forwarded-Prefix` header to properly construct relative URLs.
|
||||
Using the previous example, the backend should return `/products/shoes/image.png` (and not `/images.png`, which Traefik would likely not be able to associate with the same backend).
|
||||
Using the previous example, the backend should return `/products/shoes/image.png` (and not `/image.png`, which Traefik would likely not be able to associate with the same backend).
|
||||
|
||||
### `forceSlash`
|
||||
|
||||
|
||||
@@ -109,7 +109,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://doc.traefik.io/traefik/v2.6/reference/dynamic-configuration/kubernetes-crd/#definitions
|
||||
# https://doc.traefik.io/traefik/v2.7/reference/dynamic-configuration/kubernetes-crd/#definitions
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
@@ -280,7 +280,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://doc.traefik.io/traefik/v2.6/reference/dynamic-configuration/kubernetes-crd/#definitions
|
||||
# https://doc.traefik.io/traefik/v2.7/reference/dynamic-configuration/kubernetes-crd/#definitions
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: TLSOption
|
||||
metadata:
|
||||
|
||||
@@ -254,7 +254,7 @@ version: "3.7"
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:v2.6
|
||||
image: traefik:v2.7
|
||||
environment:
|
||||
- TZ=US/Alaska
|
||||
command:
|
||||
|
||||
219
docs/content/observability/metrics/influxdb2.md
Normal file
219
docs/content/observability/metrics/influxdb2.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# InfluxDB v2
|
||||
|
||||
To enable the InfluxDB2:
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
metrics:
|
||||
influxDB2: {}
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[metrics]
|
||||
[metrics.influxDB2]
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.influxdb2=true
|
||||
```
|
||||
|
||||
#### `address`
|
||||
|
||||
_Required, Default="http://localhost:8086"_
|
||||
|
||||
Address of the InfluxDB v2 instance.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
metrics:
|
||||
influxDB2:
|
||||
address: http://localhost:8086
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[metrics]
|
||||
[metrics.influxDB2]
|
||||
address = "http://localhost:8086"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.influxdb2.address=http://localhost:8086
|
||||
```
|
||||
|
||||
#### `token`
|
||||
|
||||
_Required, Default=""_
|
||||
|
||||
Token with which to connect to InfluxDB v2.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
metrics:
|
||||
influxDB2:
|
||||
token: secret
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[metrics]
|
||||
[metrics.influxDB2]
|
||||
token = "secret"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.influxdb2.token=secret
|
||||
```
|
||||
|
||||
#### `org`
|
||||
|
||||
_Required, Default=""_
|
||||
|
||||
Organisation where metrics will be stored.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
metrics:
|
||||
influxDB2:
|
||||
org: my-org
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[metrics]
|
||||
[metrics.influxDB2]
|
||||
org = "my-org"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.influxdb2.org=my-org
|
||||
```
|
||||
|
||||
#### `bucket`
|
||||
|
||||
_Required, Default=""_
|
||||
|
||||
Bucket where metrics will be stored.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
metrics:
|
||||
influxDB2:
|
||||
bucket: my-bucket
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[metrics]
|
||||
[metrics.influxDB2]
|
||||
bucket = "my-bucket"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.influxdb2.bucket=my-bucket
|
||||
```
|
||||
|
||||
#### `addEntryPointsLabels`
|
||||
|
||||
_Optional, Default=true_
|
||||
|
||||
Enable metrics on entry points.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
metrics:
|
||||
influxDB2:
|
||||
addEntryPointsLabels: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[metrics]
|
||||
[metrics.influxDB2]
|
||||
addEntryPointsLabels = true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.influxdb2.addEntryPointsLabels=true
|
||||
```
|
||||
|
||||
#### `addRoutersLabels`
|
||||
|
||||
_Optional, Default=false_
|
||||
|
||||
Enable metrics on routers.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
metrics:
|
||||
influxDB2:
|
||||
addRoutersLabels: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[metrics]
|
||||
[metrics.influxDB2]
|
||||
addRoutersLabels = true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.influxdb2.addrouterslabels=true
|
||||
```
|
||||
|
||||
#### `addServicesLabels`
|
||||
|
||||
_Optional, Default=true_
|
||||
|
||||
Enable metrics on services.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
metrics:
|
||||
influxDB2:
|
||||
addServicesLabels: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[metrics]
|
||||
[metrics.influxDB2]
|
||||
addServicesLabels = true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.influxdb2.addServicesLabels=true
|
||||
```
|
||||
|
||||
#### `pushInterval`
|
||||
|
||||
_Optional, Default=10s_
|
||||
|
||||
The interval used by the exporter to push metrics to InfluxDB server.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
metrics:
|
||||
influxDB2:
|
||||
pushInterval: 10s
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[metrics]
|
||||
[metrics.influxDB2]
|
||||
pushInterval = "10s"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.influxdb2.pushInterval=10s
|
||||
```
|
||||
|
||||
#### `additionalLabels`
|
||||
|
||||
_Optional, Default={}_
|
||||
|
||||
Additional labels (InfluxDB tags) on all metrics.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
metrics:
|
||||
influxDB2:
|
||||
additionalLabels:
|
||||
host: example.com
|
||||
environment: production
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[metrics]
|
||||
[metrics.influxDB2]
|
||||
[metrics.influxDB2.additionalLabels]
|
||||
host = "example.com"
|
||||
environment = "production"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.influxdb2.additionallabels.host=example.com --metrics.influxdb2.additionallabels.environment=production
|
||||
```
|
||||
@@ -9,16 +9,17 @@ Traefik supports 4 metrics backends:
|
||||
|
||||
- [Datadog](./datadog.md)
|
||||
- [InfluxDB](./influxdb.md)
|
||||
- [InfluxDB2](./influxdb2.md)
|
||||
- [Prometheus](./prometheus.md)
|
||||
- [StatsD](./statsd.md)
|
||||
|
||||
## Global Metrics
|
||||
|
||||
| Metric | DataDog | InfluxDB | Prometheus | StatsD |
|
||||
|-------------------------------------------------------------------------|---------|----------|------------|--------|
|
||||
| [Configuration reloads](#configuration-reloads) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Last Configuration Reload Success](#last-configuration-reload-success) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [TLS certificates expiration](#tls-certificates-expiration) | ✓ | ✓ | ✓ | ✓ |
|
||||
| Metric | DataDog | InfluxDB / InfluxDB2 | Prometheus | StatsD |
|
||||
|-------------------------------------------------------------------------|---------|----------------------|------------|--------|
|
||||
| [Configuration reloads](#configuration-reloads) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Last Configuration Reload Success](#last-configuration-reload-success) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [TLS certificates expiration](#tls-certificates-expiration) | ✓ | ✓ | ✓ | ✓ |
|
||||
|
||||
### Configuration Reloads
|
||||
|
||||
@@ -28,7 +29,7 @@ The total count of configuration reloads.
|
||||
config.reload.total
|
||||
```
|
||||
|
||||
```influxdb tab="InfluxDB"
|
||||
```influxdb tab="InfluxDB / InfluxDB2"
|
||||
traefik.config.reload.total
|
||||
```
|
||||
|
||||
@@ -49,7 +50,7 @@ The timestamp of the last configuration reload success.
|
||||
config.reload.lastSuccessTimestamp
|
||||
```
|
||||
|
||||
```influxdb tab="InfluxDB"
|
||||
```influxdb tab="InfluxDB / InfluxDB2"
|
||||
traefik.config.reload.lastSuccessTimestamp
|
||||
```
|
||||
|
||||
@@ -72,7 +73,7 @@ The expiration date of certificates.
|
||||
tls.certs.notAfterTimestamp
|
||||
```
|
||||
|
||||
```influxdb tab="InfluxDB"
|
||||
```influxdb tab="InfluxDB / InfluxDB2"
|
||||
traefik.tls.certs.notAfterTimestamp
|
||||
```
|
||||
|
||||
@@ -87,12 +88,12 @@ traefik_tls_certs_not_after
|
||||
|
||||
## EntryPoint Metrics
|
||||
|
||||
| Metric | DataDog | InfluxDB | Prometheus | StatsD |
|
||||
|-----------------------------------------------------------|---------|----------|------------|--------|
|
||||
| [HTTP Requests Count](#http-requests-count) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [HTTPS Requests Count](#https-requests-count) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Request Duration Histogram](#request-duration-histogram) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Open Connections Count](#open-connections-count) | ✓ | ✓ | ✓ | ✓ |
|
||||
| Metric | DataDog | InfluxDB / InfluxDB2 | Prometheus | StatsD |
|
||||
|-----------------------------------------------------------|---------|----------------------|------------|--------|
|
||||
| [HTTP Requests Count](#http-requests-count) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [HTTPS Requests Count](#https-requests-count) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Request Duration Histogram](#request-duration-histogram) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Open Connections Count](#open-connections-count) | ✓ | ✓ | ✓ | ✓ |
|
||||
|
||||
### HTTP Requests Count
|
||||
|
||||
@@ -104,7 +105,7 @@ The total count of HTTP requests received by an entrypoint.
|
||||
entrypoint.request.total
|
||||
```
|
||||
|
||||
```influxdb tab="InfluxDB"
|
||||
```influxdb tab="InfluxDB / InfluxDB2"
|
||||
traefik.entrypoint.requests.total
|
||||
```
|
||||
|
||||
@@ -127,7 +128,7 @@ The total count of HTTPS requests received by an entrypoint.
|
||||
entrypoint.request.tls.total
|
||||
```
|
||||
|
||||
```influxdb tab="InfluxDB"
|
||||
```influxdb tab="InfluxDB / InfluxDB2"
|
||||
traefik.entrypoint.requests.tls.total
|
||||
```
|
||||
|
||||
@@ -150,7 +151,7 @@ Request processing duration histogram on an entrypoint.
|
||||
entrypoint.request.duration
|
||||
```
|
||||
|
||||
```influxdb tab="InfluxDB"
|
||||
```influxdb tab="InfluxDB / InfluxDB2"
|
||||
traefik.entrypoint.request.duration
|
||||
```
|
||||
|
||||
@@ -173,7 +174,7 @@ The current count of open connections on an entrypoint.
|
||||
entrypoint.connections.open
|
||||
```
|
||||
|
||||
```influxdb tab="InfluxDB"
|
||||
```influxdb tab="InfluxDB / InfluxDB2"
|
||||
traefik.entrypoint.connections.open
|
||||
```
|
||||
|
||||
@@ -188,12 +189,12 @@ traefik_entrypoint_open_connections
|
||||
|
||||
## Router Metrics
|
||||
|
||||
| Metric | DataDog | InfluxDB | Prometheus | StatsD |
|
||||
|-------------------------------------------------------------|---------|----------|------------|--------|
|
||||
| [HTTP Requests Count](#http-requests-count_1) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [HTTPS Requests Count](#https-requests-count_1) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Request Duration Histogram](#request-duration-histogram_1) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Open Connections Count](#open-connections-count_1) | ✓ | ✓ | ✓ | ✓ |
|
||||
| Metric | DataDog | InfluxDB / InfluxDB2 | Prometheus | StatsD |
|
||||
|-------------------------------------------------------------|---------|----------------------|------------|--------|
|
||||
| [HTTP Requests Count](#http-requests-count_1) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [HTTPS Requests Count](#https-requests-count_1) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Request Duration Histogram](#request-duration-histogram_1) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Open Connections Count](#open-connections-count_1) | ✓ | ✓ | ✓ | ✓ |
|
||||
|
||||
### HTTP Requests Count
|
||||
|
||||
@@ -205,7 +206,7 @@ The total count of HTTP requests handled by a router.
|
||||
router.request.total
|
||||
```
|
||||
|
||||
```influxdb tab="InfluxDB"
|
||||
```influxdb tab="InfluxDB / InfluxDB2"
|
||||
traefik.router.requests.total
|
||||
```
|
||||
|
||||
@@ -228,7 +229,7 @@ The total count of HTTPS requests handled by a router.
|
||||
router.request.tls.total
|
||||
```
|
||||
|
||||
```influxdb tab="InfluxDB"
|
||||
```influxdb tab="InfluxDB / InfluxDB2"
|
||||
traefik.router.requests.tls.total
|
||||
```
|
||||
|
||||
@@ -251,7 +252,7 @@ Request processing duration histogram on a router.
|
||||
router.request.duration
|
||||
```
|
||||
|
||||
```influxdb tab="InfluxDB"
|
||||
```influxdb tab="InfluxDB / InfluxDB2"
|
||||
traefik.router.request.duration
|
||||
```
|
||||
|
||||
@@ -274,7 +275,7 @@ The current count of open connections on a router.
|
||||
router.connections.open
|
||||
```
|
||||
|
||||
```influxdb tab="InfluxDB"
|
||||
```influxdb tab="InfluxDB / InfluxDB2"
|
||||
traefik.router.connections.open
|
||||
```
|
||||
|
||||
@@ -289,14 +290,14 @@ traefik_router_open_connections
|
||||
|
||||
## Service Metrics
|
||||
|
||||
| Metric | DataDog | InfluxDB | Prometheus | StatsD |
|
||||
|-------------------------------------------------------------|---------|----------|------------|--------|
|
||||
| [HTTP Requests Count](#http-requests-count_2) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [HTTPS Requests Count](#https-requests-count_2) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Request Duration Histogram](#request-duration-histogram_2) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Open Connections Count](#open-connections-count_2) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Requests Retries Count](#requests-retries-count) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Service Server UP](#service-server-up) | ✓ | ✓ | ✓ | ✓ |
|
||||
| Metric | DataDog | InfluxDB / InfluxDB2 | Prometheus | StatsD |
|
||||
|-------------------------------------------------------------|---------|----------------------|------------|--------|
|
||||
| [HTTP Requests Count](#http-requests-count_2) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [HTTPS Requests Count](#https-requests-count_2) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Request Duration Histogram](#request-duration-histogram_2) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Open Connections Count](#open-connections-count_2) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Requests Retries Count](#requests-retries-count) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Service Server UP](#service-server-up) | ✓ | ✓ | ✓ | ✓ |
|
||||
|
||||
### HTTP Requests Count
|
||||
|
||||
@@ -308,7 +309,7 @@ The total count of HTTP requests processed on a service.
|
||||
service.request.total
|
||||
```
|
||||
|
||||
```influxdb tab="InfluxDB"
|
||||
```influxdb tab="InfluxDB / InfluxDB2"
|
||||
traefik.service.requests.total
|
||||
```
|
||||
|
||||
@@ -331,7 +332,7 @@ The total count of HTTPS requests processed on a service.
|
||||
router.service.tls.total
|
||||
```
|
||||
|
||||
```influxdb tab="InfluxDB"
|
||||
```influxdb tab="InfluxDB / InfluxDB2"
|
||||
traefik.service.requests.tls.total
|
||||
```
|
||||
|
||||
@@ -354,7 +355,7 @@ Request processing duration histogram on a service.
|
||||
service.request.duration
|
||||
```
|
||||
|
||||
```influxdb tab="InfluxDB"
|
||||
```influxdb tab="InfluxDB / InfluxDB2"
|
||||
traefik.service.request.duration
|
||||
```
|
||||
|
||||
@@ -377,7 +378,7 @@ The current count of open connections on a service.
|
||||
service.connections.open
|
||||
```
|
||||
|
||||
```influxdb tab="InfluxDB"
|
||||
```influxdb tab="InfluxDB / InfluxDB2"
|
||||
traefik.service.connections.open
|
||||
```
|
||||
|
||||
@@ -400,7 +401,7 @@ The count of requests retries on a service.
|
||||
service.retries.total
|
||||
```
|
||||
|
||||
```influxdb tab="InfluxDB"
|
||||
```influxdb tab="InfluxDB / InfluxDB2"
|
||||
traefik.service.retries.total
|
||||
```
|
||||
|
||||
@@ -423,7 +424,7 @@ Current service's server status, described by a gauge with a value of 0 for a do
|
||||
service.server.up
|
||||
```
|
||||
|
||||
```influxdb tab="InfluxDB"
|
||||
```influxdb tab="InfluxDB / InfluxDB2"
|
||||
traefik.service.server.up
|
||||
```
|
||||
|
||||
|
||||
@@ -726,3 +726,27 @@ providers:
|
||||
--providers.consulcatalog.namespace=production
|
||||
# ...
|
||||
```
|
||||
|
||||
### `watch`
|
||||
|
||||
_Optional, Default=false_
|
||||
|
||||
When set to `true`, watches for Consul changes ([Consul watches checks](https://www.consul.io/docs/dynamic-app-config/watches#checks)).
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
consulCatalog:
|
||||
watch: true
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.consulCatalog]
|
||||
watch = true
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.consulcatalog.watch=true
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -257,7 +257,7 @@ See the sections [Docker API Access](#docker-api-access) and [Docker Swarm API A
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:v2.6 # The official v2 Traefik docker image
|
||||
image: traefik:v2.7 # The official v2 Traefik docker image
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
|
||||
@@ -269,11 +269,38 @@ providers:
|
||||
--providers.kubernetescrd.throttleDuration=10s
|
||||
```
|
||||
|
||||
### `allowEmptyServices`
|
||||
|
||||
_Optional, Default: false_
|
||||
|
||||
If the parameter is set to `true`,
|
||||
it allows the creation of an empty [servers load balancer](../routing/services/index.md#servers-load-balancer) if the targeted Kubernetes service has no endpoints available.
|
||||
With IngressRoute resources,
|
||||
this results in `503` HTTP responses instead of `404` ones.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
kubernetesCRD:
|
||||
allowEmptyServices: true
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.kubernetesCRD]
|
||||
allowEmptyServices = true
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetesCRD.allowEmptyServices=true
|
||||
```
|
||||
|
||||
### `allowCrossNamespace`
|
||||
|
||||
_Optional, Default: false_
|
||||
|
||||
If the parameter is set to `true`, IngressRoutes are able to reference resources in other namespaces than theirs.
|
||||
If the parameter is set to `true`,
|
||||
IngressRoute are able to reference resources in namespaces other than theirs.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
|
||||
@@ -450,7 +450,11 @@ providers:
|
||||
|
||||
### `allowEmptyServices`
|
||||
|
||||
_Optional, Default: false
|
||||
_Optional, Default: false_
|
||||
|
||||
If the parameter is set to `true`,
|
||||
it allows the creation of an empty [servers load balancer](../routing/services/index.md#servers-load-balancer) if the targeted Kubernetes service has no endpoints available.
|
||||
This results in `503` HTTP responses instead of `404` ones.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
@@ -469,14 +473,12 @@ providers:
|
||||
--providers.kubernetesingress.allowEmptyServices=true
|
||||
```
|
||||
|
||||
Allow the creation of services if there are no endpoints available.
|
||||
This results in `503` http responses instead of `404`.
|
||||
|
||||
### `allowExternalNameServices`
|
||||
|
||||
_Optional, Default: false_
|
||||
|
||||
If the parameter is set to `true`, Ingresses are able to reference ExternalName services.
|
||||
If the parameter is set to `true`,
|
||||
Ingresses are able to reference ExternalName services.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
@@ -498,4 +500,4 @@ providers:
|
||||
### Further
|
||||
|
||||
To learn more about the various aspects of the Ingress specification that Traefik supports,
|
||||
many examples of Ingresses definitions are located in the test [examples](https://github.com/traefik/traefik/tree/v2.6/pkg/provider/kubernetes/ingress/fixtures) of the Traefik repository.
|
||||
many examples of Ingresses definitions are located in the test [examples](https://github.com/traefik/traefik/tree/v2.7/pkg/provider/kubernetes/ingress/fixtures) of the Traefik repository.
|
||||
|
||||
@@ -167,6 +167,7 @@
|
||||
- "traefik.tcp.routers.tcprouter0.entrypoints=foobar, foobar"
|
||||
- "traefik.tcp.routers.tcprouter0.middlewares=foobar, foobar"
|
||||
- "traefik.tcp.routers.tcprouter0.rule=foobar"
|
||||
- "traefik.tcp.routers.tcprouter0.priority=42"
|
||||
- "traefik.tcp.routers.tcprouter0.service=foobar"
|
||||
- "traefik.tcp.routers.tcprouter0.tls=true"
|
||||
- "traefik.tcp.routers.tcprouter0.tls.certresolver=foobar"
|
||||
@@ -179,6 +180,7 @@
|
||||
- "traefik.tcp.routers.tcprouter1.entrypoints=foobar, foobar"
|
||||
- "traefik.tcp.routers.tcprouter1.middlewares=foobar, foobar"
|
||||
- "traefik.tcp.routers.tcprouter1.rule=foobar"
|
||||
- "traefik.tcp.routers.tcprouter1.priority=42"
|
||||
- "traefik.tcp.routers.tcprouter1.service=foobar"
|
||||
- "traefik.tcp.routers.tcprouter1.tls=true"
|
||||
- "traefik.tcp.routers.tcprouter1.tls.certresolver=foobar"
|
||||
|
||||
@@ -95,6 +95,12 @@
|
||||
secure = true
|
||||
httpOnly = true
|
||||
sameSite = "foobar"
|
||||
[http.services.Service04]
|
||||
[http.services.Service04.failover]
|
||||
service = "foobar"
|
||||
fallback = "foobar"
|
||||
|
||||
[http.services.Service04.failover.healthCheck]
|
||||
[http.middlewares]
|
||||
[http.middlewares.Middleware00]
|
||||
[http.middlewares.Middleware00.addPrefix]
|
||||
@@ -325,6 +331,7 @@
|
||||
middlewares = ["foobar", "foobar"]
|
||||
service = "foobar"
|
||||
rule = "foobar"
|
||||
priority = 42
|
||||
[tcp.routers.TCPRouter0.tls]
|
||||
passthrough = true
|
||||
options = "foobar"
|
||||
@@ -342,6 +349,7 @@
|
||||
middlewares = ["foobar", "foobar"]
|
||||
service = "foobar"
|
||||
rule = "foobar"
|
||||
priority = 42
|
||||
[tcp.routers.TCPRouter1.tls]
|
||||
passthrough = true
|
||||
options = "foobar"
|
||||
|
||||
@@ -95,6 +95,11 @@ http:
|
||||
secure: true
|
||||
httpOnly: true
|
||||
sameSite: foobar
|
||||
Service04:
|
||||
failover:
|
||||
service: foobar
|
||||
fallback: foobar
|
||||
healthCheck: {}
|
||||
middlewares:
|
||||
Middleware00:
|
||||
addPrefix:
|
||||
@@ -366,6 +371,7 @@ tcp:
|
||||
- foobar
|
||||
service: foobar
|
||||
rule: foobar
|
||||
priority: 42
|
||||
tls:
|
||||
passthrough: true
|
||||
options: foobar
|
||||
@@ -388,6 +394,7 @@ tcp:
|
||||
- foobar
|
||||
service: foobar
|
||||
rule: foobar
|
||||
priority: 42
|
||||
tls:
|
||||
passthrough: true
|
||||
options: foobar
|
||||
|
||||
@@ -25,7 +25,7 @@ spec:
|
||||
serviceAccountName: traefik-controller
|
||||
containers:
|
||||
- name: traefik
|
||||
image: traefik:v2.6
|
||||
image: traefik:v2.7
|
||||
args:
|
||||
- --entrypoints.web.address=:80
|
||||
- --entrypoints.websecure.address=:443
|
||||
|
||||
@@ -233,6 +233,9 @@
|
||||
| `traefik/http/services/Service03/weighted/sticky/cookie/name` | `foobar` |
|
||||
| `traefik/http/services/Service03/weighted/sticky/cookie/sameSite` | `foobar` |
|
||||
| `traefik/http/services/Service03/weighted/sticky/cookie/secure` | `true` |
|
||||
| `traefik/http/services/Service04/failover/fallback` | `foobar` |
|
||||
| `traefik/http/services/Service04/failover/healthCheck` | `` |
|
||||
| `traefik/http/services/Service04/failover/service` | `foobar` |
|
||||
| `traefik/tcp/middlewares/TCPMiddleware00/ipWhiteList/sourceRange/0` | `foobar` |
|
||||
| `traefik/tcp/middlewares/TCPMiddleware00/ipWhiteList/sourceRange/1` | `foobar` |
|
||||
| `traefik/tcp/middlewares/TCPMiddleware01/inFlightConn/amount` | `42` |
|
||||
@@ -240,6 +243,7 @@
|
||||
| `traefik/tcp/routers/TCPRouter0/entryPoints/1` | `foobar` |
|
||||
| `traefik/tcp/routers/TCPRouter0/middlewares/0` | `foobar` |
|
||||
| `traefik/tcp/routers/TCPRouter0/middlewares/1` | `foobar` |
|
||||
| `traefik/tcp/routers/TCPRouter0/priority` | `42` |
|
||||
| `traefik/tcp/routers/TCPRouter0/rule` | `foobar` |
|
||||
| `traefik/tcp/routers/TCPRouter0/service` | `foobar` |
|
||||
| `traefik/tcp/routers/TCPRouter0/tls/certResolver` | `foobar` |
|
||||
@@ -255,6 +259,7 @@
|
||||
| `traefik/tcp/routers/TCPRouter1/entryPoints/1` | `foobar` |
|
||||
| `traefik/tcp/routers/TCPRouter1/middlewares/0` | `foobar` |
|
||||
| `traefik/tcp/routers/TCPRouter1/middlewares/1` | `foobar` |
|
||||
| `traefik/tcp/routers/TCPRouter1/priority` | `42` |
|
||||
| `traefik/tcp/routers/TCPRouter1/rule` | `foobar` |
|
||||
| `traefik/tcp/routers/TCPRouter1/service` | `foobar` |
|
||||
| `traefik/tcp/routers/TCPRouter1/tls/certResolver` | `foobar` |
|
||||
|
||||
@@ -167,6 +167,7 @@
|
||||
"traefik.tcp.routers.tcprouter0.entrypoints": "foobar, foobar",
|
||||
"traefik.tcp.routers.tcprouter0.middlewares": "foobar, foobar",
|
||||
"traefik.tcp.routers.tcprouter0.rule": "foobar",
|
||||
"traefik.tcp.routers.tcprouter0.priority": "42",
|
||||
"traefik.tcp.routers.tcprouter0.service": "foobar",
|
||||
"traefik.tcp.routers.tcprouter0.tls": "true",
|
||||
"traefik.tcp.routers.tcprouter0.tls.certresolver": "foobar",
|
||||
@@ -179,6 +180,7 @@
|
||||
"traefik.tcp.routers.tcprouter1.entrypoints": "foobar, foobar",
|
||||
"traefik.tcp.routers.tcprouter1.middlewares": "foobar, foobar",
|
||||
"traefik.tcp.routers.tcprouter1.rule": "foobar",
|
||||
"traefik.tcp.routers.tcprouter1.priority": "42",
|
||||
"traefik.tcp.routers.tcprouter1.service": "foobar",
|
||||
"traefik.tcp.routers.tcprouter1.tls": "true",
|
||||
"traefik.tcp.routers.tcprouter1.tls.certresolver": "foobar",
|
||||
|
||||
@@ -62,6 +62,8 @@ spec:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
priority:
|
||||
type: integer
|
||||
services:
|
||||
items:
|
||||
description: ServiceTCP defines an upstream to proxy traffic.
|
||||
|
||||
@@ -183,6 +183,9 @@ Timeout defines how long to wait on an idle session before releasing the related
|
||||
`--experimental.http3`:
|
||||
Enable HTTP3. (Default: ```false```)
|
||||
|
||||
`--experimental.hub`:
|
||||
Enable the Traefik Hub provider. (Default: ```false```)
|
||||
|
||||
`--experimental.kubernetesgateway`:
|
||||
Allow the Kubernetes gateway api provider usage. (Default: ```false```)
|
||||
|
||||
@@ -216,6 +219,21 @@ resolv.conf used for DNS resolving (Default: ```/etc/resolv.conf```)
|
||||
`--hostresolver.resolvdepth`:
|
||||
The maximal depth of DNS recursive resolving (Default: ```5```)
|
||||
|
||||
`--hub`:
|
||||
Traefik Hub configuration. (Default: ```false```)
|
||||
|
||||
`--hub.tls.ca`:
|
||||
The certificate authority authenticates the Traefik Hub Agent certificate.
|
||||
|
||||
`--hub.tls.cert`:
|
||||
The TLS certificate for Traefik Proxy as a TLS client.
|
||||
|
||||
`--hub.tls.insecure`:
|
||||
Enables an insecure TLS connection that uses default credentials, and which has no peer authentication between Traefik Proxy and the Traefik Hub Agent. (Default: ```false```)
|
||||
|
||||
`--hub.tls.key`:
|
||||
The TLS key for Traefik Proxy as a TLS client.
|
||||
|
||||
`--log`:
|
||||
Traefik log settings. (Default: ```false```)
|
||||
|
||||
@@ -285,6 +303,36 @@ InfluxDB retention policy used when protocol is http.
|
||||
`--metrics.influxdb.username`:
|
||||
InfluxDB username (only with http).
|
||||
|
||||
`--metrics.influxdb2`:
|
||||
InfluxDB v2 metrics exporter type. (Default: ```false```)
|
||||
|
||||
`--metrics.influxdb2.addentrypointslabels`:
|
||||
Enable metrics on entry points. (Default: ```true```)
|
||||
|
||||
`--metrics.influxdb2.additionallabels.<name>`:
|
||||
Additional labels (influxdb tags) on all metrics
|
||||
|
||||
`--metrics.influxdb2.address`:
|
||||
InfluxDB v2 address. (Default: ```http://localhost:8086```)
|
||||
|
||||
`--metrics.influxdb2.addrouterslabels`:
|
||||
Enable metrics on routers. (Default: ```false```)
|
||||
|
||||
`--metrics.influxdb2.addserviceslabels`:
|
||||
Enable metrics on services. (Default: ```true```)
|
||||
|
||||
`--metrics.influxdb2.bucket`:
|
||||
InfluxDB v2 bucket ID.
|
||||
|
||||
`--metrics.influxdb2.org`:
|
||||
InfluxDB v2 org ID.
|
||||
|
||||
`--metrics.influxdb2.pushinterval`:
|
||||
InfluxDB v2 push interval. (Default: ```10```)
|
||||
|
||||
`--metrics.influxdb2.token`:
|
||||
InfluxDB v2 access token.
|
||||
|
||||
`--metrics.prometheus`:
|
||||
Prometheus metrics exporter type. (Default: ```false```)
|
||||
|
||||
@@ -456,6 +504,9 @@ Name of the Traefik service in Consul Catalog (needs to be registered via the or
|
||||
`--providers.consulcatalog.stale`:
|
||||
Use stale consistency for catalog reads. (Default: ```false```)
|
||||
|
||||
`--providers.consulcatalog.watch`:
|
||||
Watch Consul API events. (Default: ```false```)
|
||||
|
||||
`--providers.docker`:
|
||||
Enable Docker backend with default settings. (Default: ```false```)
|
||||
|
||||
@@ -615,6 +666,9 @@ Enable Kubernetes backend with default settings. (Default: ```false```)
|
||||
`--providers.kubernetescrd.allowcrossnamespace`:
|
||||
Allow cross namespace resource reference. (Default: ```false```)
|
||||
|
||||
`--providers.kubernetescrd.allowemptyservices`:
|
||||
Allow the creation of services without endpoints. (Default: ```false```)
|
||||
|
||||
`--providers.kubernetescrd.allowexternalnameservices`:
|
||||
Allow ExternalName services. (Default: ```false```)
|
||||
|
||||
|
||||
@@ -183,6 +183,9 @@ Timeout defines how long to wait on an idle session before releasing the related
|
||||
`TRAEFIK_EXPERIMENTAL_HTTP3`:
|
||||
Enable HTTP3. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_EXPERIMENTAL_HUB`:
|
||||
Enable the Traefik Hub provider. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_EXPERIMENTAL_KUBERNETESGATEWAY`:
|
||||
Allow the Kubernetes gateway api provider usage. (Default: ```false```)
|
||||
|
||||
@@ -216,6 +219,21 @@ resolv.conf used for DNS resolving (Default: ```/etc/resolv.conf```)
|
||||
`TRAEFIK_HOSTRESOLVER_RESOLVDEPTH`:
|
||||
The maximal depth of DNS recursive resolving (Default: ```5```)
|
||||
|
||||
`TRAEFIK_HUB`:
|
||||
Traefik Hub configuration. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_HUB_TLS_CA`:
|
||||
The certificate authority authenticates the Traefik Hub Agent certificate.
|
||||
|
||||
`TRAEFIK_HUB_TLS_CERT`:
|
||||
The TLS certificate for Traefik Proxy as a TLS client.
|
||||
|
||||
`TRAEFIK_HUB_TLS_INSECURE`:
|
||||
Enables an insecure TLS connection that uses default credentials, and which has no peer authentication between Traefik Proxy and the Traefik Hub Agent. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_HUB_TLS_KEY`:
|
||||
The TLS key for Traefik Proxy as a TLS client.
|
||||
|
||||
`TRAEFIK_LOG`:
|
||||
Traefik log settings. (Default: ```false```)
|
||||
|
||||
@@ -252,6 +270,36 @@ Datadog push interval. (Default: ```10```)
|
||||
`TRAEFIK_METRICS_INFLUXDB`:
|
||||
InfluxDB metrics exporter type. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_METRICS_INFLUXDB2`:
|
||||
InfluxDB v2 metrics exporter type. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_METRICS_INFLUXDB2_ADDENTRYPOINTSLABELS`:
|
||||
Enable metrics on entry points. (Default: ```true```)
|
||||
|
||||
`TRAEFIK_METRICS_INFLUXDB2_ADDITIONALLABELS_<NAME>`:
|
||||
Additional labels (influxdb tags) on all metrics
|
||||
|
||||
`TRAEFIK_METRICS_INFLUXDB2_ADDRESS`:
|
||||
InfluxDB v2 address. (Default: ```http://localhost:8086```)
|
||||
|
||||
`TRAEFIK_METRICS_INFLUXDB2_ADDROUTERSLABELS`:
|
||||
Enable metrics on routers. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_METRICS_INFLUXDB2_ADDSERVICESLABELS`:
|
||||
Enable metrics on services. (Default: ```true```)
|
||||
|
||||
`TRAEFIK_METRICS_INFLUXDB2_BUCKET`:
|
||||
InfluxDB v2 bucket ID.
|
||||
|
||||
`TRAEFIK_METRICS_INFLUXDB2_ORG`:
|
||||
InfluxDB v2 org ID.
|
||||
|
||||
`TRAEFIK_METRICS_INFLUXDB2_PUSHINTERVAL`:
|
||||
InfluxDB v2 push interval. (Default: ```10```)
|
||||
|
||||
`TRAEFIK_METRICS_INFLUXDB2_TOKEN`:
|
||||
InfluxDB v2 access token.
|
||||
|
||||
`TRAEFIK_METRICS_INFLUXDB_ADDENTRYPOINTSLABELS`:
|
||||
Enable metrics on entry points. (Default: ```true```)
|
||||
|
||||
@@ -423,6 +471,9 @@ Name of the Traefik service in Consul Catalog (needs to be registered via the or
|
||||
`TRAEFIK_PROVIDERS_CONSULCATALOG_STALE`:
|
||||
Use stale consistency for catalog reads. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_CONSULCATALOG_WATCH`:
|
||||
Watch Consul API events. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_CONSUL_ENDPOINTS`:
|
||||
KV store endpoints (Default: ```127.0.0.1:8500```)
|
||||
|
||||
@@ -615,6 +666,9 @@ Enable Kubernetes backend with default settings. (Default: ```false```)
|
||||
`TRAEFIK_PROVIDERS_KUBERNETESCRD_ALLOWCROSSNAMESPACE`:
|
||||
Allow cross namespace resource reference. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_KUBERNETESCRD_ALLOWEMPTYSERVICES`:
|
||||
Allow the creation of services without endpoints. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_KUBERNETESCRD_ALLOWEXTERNALNAMESERVICES`:
|
||||
Allow ExternalName services. (Default: ```false```)
|
||||
|
||||
|
||||
@@ -122,6 +122,7 @@
|
||||
allowExternalNameServices = true
|
||||
labelSelector = "foobar"
|
||||
ingressClass = "foobar"
|
||||
allowEmptyServices = true
|
||||
throttleDuration = "42s"
|
||||
[providers.kubernetesGateway]
|
||||
endpoint = "foobar"
|
||||
@@ -154,6 +155,7 @@
|
||||
connectByDefault = true
|
||||
serviceName = "foobar"
|
||||
namespace = "foobar"
|
||||
watch = true
|
||||
[providers.consulCatalog.endpoint]
|
||||
address = "foobar"
|
||||
scheme = "foobar"
|
||||
@@ -285,6 +287,17 @@
|
||||
addServicesLabels = true
|
||||
[metrics.influxDB.additionalLabels]
|
||||
foobar = "foobar"
|
||||
[metrics.influxDB2]
|
||||
address = "foobar"
|
||||
token = "foobar"
|
||||
pushInterval = "42s"
|
||||
org = "foobar"
|
||||
bucket = "foobar"
|
||||
addEntryPointsLabels = true
|
||||
addRoutersLabels = true
|
||||
addServicesLabels = true
|
||||
[metrics.influxDB2.additionalLabels]
|
||||
foobar = "foobar"
|
||||
|
||||
[ping]
|
||||
entryPoint = "foobar"
|
||||
@@ -412,9 +425,17 @@
|
||||
token = "foobar"
|
||||
dashboard = true
|
||||
|
||||
[hub]
|
||||
[hub.tls]
|
||||
insecure = true
|
||||
ca = "foobar"
|
||||
cert = "foobar"
|
||||
key = "foobar"
|
||||
|
||||
[experimental]
|
||||
kubernetesGateway = true
|
||||
http3 = true
|
||||
hub = true
|
||||
[experimental.plugins]
|
||||
[experimental.plugins.Descriptor0]
|
||||
moduleName = "foobar"
|
||||
|
||||
@@ -133,6 +133,7 @@ providers:
|
||||
labelSelector: foobar
|
||||
ingressClass: foobar
|
||||
throttleDuration: 42s
|
||||
allowEmptyServices: true
|
||||
kubernetesGateway:
|
||||
endpoint: foobar
|
||||
token: foobar
|
||||
@@ -166,6 +167,7 @@ providers:
|
||||
connectByDefault: true
|
||||
serviceName: foobar
|
||||
namespace: foobar
|
||||
watch: true
|
||||
endpoint:
|
||||
address: foobar
|
||||
scheme: foobar
|
||||
@@ -307,6 +309,18 @@ metrics:
|
||||
addServicesLabels: true
|
||||
additionalLabels:
|
||||
foobar: foobar
|
||||
influxDB2:
|
||||
address: foobar
|
||||
token: foobar
|
||||
pushInterval: 42s
|
||||
org: foobar
|
||||
bucket: foobar
|
||||
addEntryPointsLabels: true
|
||||
addRoutersLabels: true
|
||||
addServicesLabels: true
|
||||
additionalLabels:
|
||||
foobar: foobar
|
||||
|
||||
ping:
|
||||
entryPoint: foobar
|
||||
manualRouting: true
|
||||
@@ -432,9 +446,16 @@ certificatesResolvers:
|
||||
pilot:
|
||||
token: foobar
|
||||
dashboard: true
|
||||
hub:
|
||||
tls:
|
||||
insecure: true
|
||||
ca: foobar
|
||||
cert: foobar
|
||||
key: foobar
|
||||
experimental:
|
||||
kubernetesGateway: true
|
||||
http3: true
|
||||
hub: true
|
||||
plugins:
|
||||
Descriptor0:
|
||||
moduleName: foobar
|
||||
|
||||
@@ -104,7 +104,8 @@ For example, to change the rule, you could add the tag ```traefik.http.routers.m
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.priority`"
|
||||
<!-- TODO doc priority in routers page -->
|
||||
|
||||
See [priority](../routers/index.md#priority) for more information.
|
||||
|
||||
```yaml
|
||||
traefik.http.routers.myrouter.priority=42
|
||||
|
||||
@@ -543,6 +543,14 @@ You can declare TCP Routers and/or Services using labels.
|
||||
- "traefik.tcp.routers.mytcprouter.tls.passthrough=true"
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.routers.<router_name>.priority`"
|
||||
|
||||
See [priority](../routers/index.md#priority_1) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.routers.myrouter.priority=42"
|
||||
```
|
||||
|
||||
#### TCP Services
|
||||
|
||||
??? info "`traefik.tcp.services.<service_name>.loadbalancer.server.port`"
|
||||
|
||||
@@ -384,6 +384,14 @@ You can declare TCP Routers and/or Services using labels.
|
||||
traefik.tcp.routers.mytcprouter.tls.passthrough=true
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.routers.<router_name>.priority`"
|
||||
|
||||
See [priority](../routers/index.md#priority_1) for more information.
|
||||
|
||||
```yaml
|
||||
traefik.tcp.routers.myrouter.priority=42
|
||||
```
|
||||
|
||||
#### TCP Services
|
||||
|
||||
??? info "`traefik.tcp.services.<service_name>.loadbalancer.server.port`"
|
||||
|
||||
@@ -48,7 +48,7 @@ The Kubernetes Ingress Controller, The Custom Resource Way.
|
||||
serviceAccountName: traefik-ingress-controller
|
||||
containers:
|
||||
- name: traefik
|
||||
image: traefik:v2.6
|
||||
image: traefik:v2.7
|
||||
args:
|
||||
- --log.level=DEBUG
|
||||
- --api
|
||||
@@ -362,27 +362,27 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne
|
||||
- b.example.net
|
||||
```
|
||||
|
||||
| Ref | Attribute | Purpose |
|
||||
|------|--------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [1] | `entryPoints` | List of [entry points](../routers/index.md#entrypoints) names |
|
||||
| [2] | `routes` | List of routes |
|
||||
| [3] | `routes[n].match` | Defines the [rule](../routers/index.md#rule) corresponding to an underlying router. |
|
||||
| [4] | `routes[n].priority` | [Disambiguate](../routers/index.md#priority) rules of the same length, for route matching |
|
||||
| [5] | `routes[n].middlewares` | List of reference to [Middleware](#kind-middleware) |
|
||||
| [6] | `middlewares[n].name` | Defines the [Middleware](#kind-middleware) name |
|
||||
| [7] | `middlewares[n].namespace` | Defines the [Middleware](#kind-middleware) namespace |
|
||||
| [8] | `routes[n].services` | List of any combination of [TraefikService](#kind-traefikservice) and reference to a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) (See below for `ExternalName Service` setup) |
|
||||
| [9] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). This can be a reference to a named port. |
|
||||
| Ref | Attribute | Purpose |
|
||||
|------|--------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [1] | `entryPoints` | List of [entry points](../routers/index.md#entrypoints) names |
|
||||
| [2] | `routes` | List of routes |
|
||||
| [3] | `routes[n].match` | Defines the [rule](../routers/index.md#rule) corresponding to an underlying router. |
|
||||
| [4] | `routes[n].priority` | Defines the [priority](../routers/index.md#priority) to disambiguate rules of the same length, for route matching |
|
||||
| [5] | `routes[n].middlewares` | List of reference to [Middleware](#kind-middleware) |
|
||||
| [6] | `middlewares[n].name` | Defines the [Middleware](#kind-middleware) name |
|
||||
| [7] | `middlewares[n].namespace` | Defines the [Middleware](#kind-middleware) namespace |
|
||||
| [8] | `routes[n].services` | List of any combination of [TraefikService](#kind-traefikservice) and reference to a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) (See below for `ExternalName Service` setup) |
|
||||
| [9] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). This can be a reference to a named port. |
|
||||
| [10] | `services[n].serversTransport` | Defines the reference to a [ServersTransport](#kind-serverstransport). The ServersTransport namespace is assumed to be the [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) namespace (see [ServersTransport reference](#serverstransport-reference)). |
|
||||
| [11] | `tls` | Defines [TLS](../routers/index.md#tls) certificate configuration |
|
||||
| [12] | `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace) |
|
||||
| [13] | `tls.options` | Defines the reference to a [TLSOption](#kind-tlsoption) |
|
||||
| [14] | `options.name` | Defines the [TLSOption](#kind-tlsoption) name |
|
||||
| [15] | `options.namespace` | Defines the [TLSOption](#kind-tlsoption) namespace |
|
||||
| [16] | `tls.certResolver` | Defines the reference to a [CertResolver](../routers/index.md#certresolver) |
|
||||
| [17] | `tls.domains` | List of [domains](../routers/index.md#domains) |
|
||||
| [18] | `domains[n].main` | Defines the main domain name |
|
||||
| [19] | `domains[n].sans` | List of SANs (alternative domains) |
|
||||
| [11] | `tls` | Defines [TLS](../routers/index.md#tls) certificate configuration |
|
||||
| [12] | `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace) |
|
||||
| [13] | `tls.options` | Defines the reference to a [TLSOption](#kind-tlsoption) |
|
||||
| [14] | `options.name` | Defines the [TLSOption](#kind-tlsoption) name |
|
||||
| [15] | `options.namespace` | Defines the [TLSOption](#kind-tlsoption) namespace |
|
||||
| [16] | `tls.certResolver` | Defines the reference to a [CertResolver](../routers/index.md#certresolver) |
|
||||
| [17] | `tls.domains` | List of [domains](../routers/index.md#domains) |
|
||||
| [18] | `domains[n].main` | Defines the main domain name |
|
||||
| [19] | `domains[n].sans` | List of SANs (alternative domains) |
|
||||
|
||||
??? example "Declaring an IngressRoute"
|
||||
|
||||
@@ -1093,54 +1093,56 @@ Register the `IngressRouteTCP` [kind](../../reference/dynamic-configuration/kube
|
||||
- footcp
|
||||
routes: # [2]
|
||||
- match: HostSNI(`*`) # [3]
|
||||
priority: 10 # [4]
|
||||
middlewares:
|
||||
- name: middleware1 # [4]
|
||||
namespace: default # [5]
|
||||
services: # [6]
|
||||
- name: foo # [7]
|
||||
port: 8080 # [8]
|
||||
weight: 10 # [9]
|
||||
terminationDelay: 400 # [10]
|
||||
proxyProtocol: # [11]
|
||||
version: 1 # [12]
|
||||
tls: # [13]
|
||||
secretName: supersecret # [14]
|
||||
options: # [15]
|
||||
name: opt # [16]
|
||||
namespace: default # [17]
|
||||
certResolver: foo # [18]
|
||||
domains: # [19]
|
||||
- main: example.net # [20]
|
||||
sans: # [21]
|
||||
- name: middleware1 # [5]
|
||||
namespace: default # [6]
|
||||
services: # [7]
|
||||
- name: foo # [8]
|
||||
port: 8080 # [9]
|
||||
weight: 10 # [10]
|
||||
terminationDelay: 400 # [11]
|
||||
proxyProtocol: # [12]
|
||||
version: 1 # [13]
|
||||
tls: # [14]
|
||||
secretName: supersecret # [15]
|
||||
options: # [16]
|
||||
name: opt # [17]
|
||||
namespace: default # [18]
|
||||
certResolver: foo # [19]
|
||||
domains: # [20]
|
||||
- main: example.net # [21]
|
||||
sans: # [22]
|
||||
- a.example.net
|
||||
- b.example.net
|
||||
passthrough: false # [22]
|
||||
passthrough: false # [23]
|
||||
```
|
||||
|
||||
| Ref | Attribute | Purpose |
|
||||
|------|--------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [1] | `entryPoints` | List of [entrypoints](../routers/index.md#entrypoints_1) names |
|
||||
| [2] | `routes` | List of routes |
|
||||
| [3] | `routes[n].match` | Defines the [rule](../routers/index.md#rule_1) corresponding to an underlying router |
|
||||
| [4] | `middlewares[n].name` | Defines the [MiddlewareTCP](#kind-middlewaretcp) name |
|
||||
| [5] | `middlewares[n].namespace` | Defines the [MiddlewareTCP](#kind-middlewaretcp) namespace |
|
||||
| [6] | `routes[n].services` | List of [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) definitions (See below for `ExternalName Service` setup) |
|
||||
| [7] | `services[n].name` | Defines the name of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) |
|
||||
| [8] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). This can be a reference to a named port. |
|
||||
| [9] | `services[n].weight` | Defines the weight to apply to the server load balancing |
|
||||
| [10] | `services[n].terminationDelay` | corresponds to the deadline that the proxy sets, after one of its connected peers indicates it has closed the writing capability of its connection, to close the reading capability as well, hence fully terminating the connection. It is a duration in milliseconds, defaulting to 100. A negative value means an infinite deadline (i.e. the reading capability is never closed). |
|
||||
| [11] | `proxyProtocol` | Defines the [PROXY protocol](../services/index.md#proxy-protocol) configuration |
|
||||
| [12] | `version` | Defines the [PROXY protocol](../services/index.md#proxy-protocol) version |
|
||||
| [13] | `tls` | Defines [TLS](../routers/index.md#tls_1) certificate configuration |
|
||||
| [14] | `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace) |
|
||||
| [15] | `tls.options` | Defines the reference to a [TLSOption](#kind-tlsoption) |
|
||||
| [16] | `options.name` | Defines the [TLSOption](#kind-tlsoption) name |
|
||||
| [17] | `options.namespace` | Defines the [TLSOption](#kind-tlsoption) namespace |
|
||||
| [18] | `tls.certResolver` | Defines the reference to a [CertResolver](../routers/index.md#certresolver_1) |
|
||||
| [19] | `tls.domains` | List of [domains](../routers/index.md#domains_1) |
|
||||
| [20] | `domains[n].main` | Defines the main domain name |
|
||||
| [21] | `domains[n].sans` | List of SANs (alternative domains) |
|
||||
| [22] | `tls.passthrough` | If `true`, delegates the TLS termination to the backend |
|
||||
| [3] | `routes[n].match` | Defines the [rule](../routers/index.md#rule_1) of the underlying router |
|
||||
| [4] | `routes[n].priority` | Defines the [priority](../routers/index.md#priority_1) to disambiguate rules of the same length, for route matching |
|
||||
| [5] | `middlewares[n].name` | Defines the [MiddlewareTCP](#kind-middlewaretcp) name |
|
||||
| [6] | `middlewares[n].namespace` | Defines the [MiddlewareTCP](#kind-middlewaretcp) namespace |
|
||||
| [7] | `routes[n].services` | List of [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) definitions (See below for `ExternalName Service` setup) |
|
||||
| [8] | `services[n].name` | Defines the name of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) |
|
||||
| [9] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). This can be a reference to a named port. |
|
||||
| [10] | `services[n].weight` | Defines the weight to apply to the server load balancing |
|
||||
| [11] | `services[n].terminationDelay` | corresponds to the deadline that the proxy sets, after one of its connected peers indicates it has closed the writing capability of its connection, to close the reading capability as well, hence fully terminating the connection. It is a duration in milliseconds, defaulting to 100. A negative value means an infinite deadline (i.e. the reading capability is never closed). |
|
||||
| [12] | `proxyProtocol` | Defines the [PROXY protocol](../services/index.md#proxy-protocol) configuration |
|
||||
| [13] | `version` | Defines the [PROXY protocol](../services/index.md#proxy-protocol) version |
|
||||
| [14] | `tls` | Defines [TLS](../routers/index.md#tls_1) certificate configuration |
|
||||
| [15] | `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace) |
|
||||
| [16] | `tls.options` | Defines the reference to a [TLSOption](#kind-tlsoption) |
|
||||
| [17] | `options.name` | Defines the [TLSOption](#kind-tlsoption) name |
|
||||
| [18] | `options.namespace` | Defines the [TLSOption](#kind-tlsoption) namespace |
|
||||
| [19] | `tls.certResolver` | Defines the reference to a [CertResolver](../routers/index.md#certresolver_1) |
|
||||
| [20] | `tls.domains` | List of [domains](../routers/index.md#domains_1) |
|
||||
| [21] | `domains[n].main` | Defines the main domain name |
|
||||
| [22] | `domains[n].sans` | List of SANs (alternative domains) |
|
||||
| [23] | `tls.passthrough` | If `true`, delegates the TLS termination to the backend |
|
||||
|
||||
??? example "Declaring an IngressRouteTCP"
|
||||
|
||||
@@ -1156,6 +1158,7 @@ Register the `IngressRouteTCP` [kind](../../reference/dynamic-configuration/kube
|
||||
routes:
|
||||
# Match is the rule corresponding to an underlying router.
|
||||
- match: HostSNI(`*`)
|
||||
priority: 10
|
||||
services:
|
||||
- name: foo
|
||||
port: 8080
|
||||
|
||||
@@ -18,7 +18,7 @@ The Kubernetes Gateway API, The Experimental Way. {: .subtitle }
|
||||
```yaml tab="Whoami Service"
|
||||
--8<-- "content/reference/dynamic-configuration/kubernetes-whoami-svc.yml"
|
||||
```
|
||||
|
||||
|
||||
```yaml tab="Traefik Service"
|
||||
--8<-- "content/reference/dynamic-configuration/kubernetes-gateway-traefik-lb-svc.yml"
|
||||
```
|
||||
@@ -44,14 +44,14 @@ You can find an excerpt of the supported Kubernetes Gateway API resources in the
|
||||
| [GatewayClass](#kind-gatewayclass) | Defines a set of Gateways that share a common configuration and behaviour | [GatewayClass](https://gateway-api.sigs.k8s.io/v1alpha2/api-types/gatewayclass) |
|
||||
| [Gateway](#kind-gateway) | Describes how traffic can be translated to Services within the cluster | [Gateway](https://gateway-api.sigs.k8s.io/v1alpha2/api-types/gateway) |
|
||||
| [HTTPRoute](#kind-httproute) | HTTP rules for mapping requests from a Gateway to Kubernetes Services | [Route](https://gateway-api.sigs.k8s.io/v1alpha2/api-types/httproute) |
|
||||
| [TCPRoute](#kind-tcproute) | Allows mapping TCP requests from a Gateway to Kubernetes Services | [Route](https://gateway-api.sigs.k8s.io/concepts/api-overview/#tcproute-and-udproute)|
|
||||
| [TLSRoute](#kind-tlsroute) | Allows mapping TLS requests from a Gateway to Kubernetes Services | [Route](https://gateway-api.sigs.k8s.io/concepts/api-overview/#tcproute-and-udproute)|
|
||||
| [TCPRoute](#kind-tcproute) | Allows mapping TCP requests from a Gateway to Kubernetes Services | [Route](https://gateway-api.sigs.k8s.io/v1alpha2/guides/tcp/)|
|
||||
| [TLSRoute](#kind-tlsroute) | Allows mapping TLS requests from a Gateway to Kubernetes Services | [Route](https://gateway-api.sigs.k8s.io/v1alpha2/guides/tls/)|
|
||||
|
||||
### Kind: `GatewayClass`
|
||||
|
||||
`GatewayClass` is cluster-scoped resource defined by the infrastructure provider. This resource represents a class of
|
||||
Gateways that can be instantiated. More details on the
|
||||
GatewayClass [official documentation](https://gateway-api.sigs.k8s.io/v1alpha1/api-types/gatewayclass/).
|
||||
GatewayClass [official documentation](https://gateway-api.sigs.k8s.io/v1alpha2/api-types/gatewayclass/).
|
||||
|
||||
The `GatewayClass` should be declared by the infrastructure provider, otherwise please register the `GatewayClass`
|
||||
[definition](../../reference/dynamic-configuration/kubernetes-gateway.md#definitions) in the Kubernetes cluster before
|
||||
@@ -101,7 +101,7 @@ Depending on the Listener Protocol, different modes and Route types are supporte
|
||||
gatewayClassName: my-gateway-class # [1]
|
||||
listeners: # [2]
|
||||
- name: http # [3]
|
||||
protocol: HTTP # [4]
|
||||
protocol: HTTP # [4]
|
||||
port: 80 # [5]
|
||||
allowedRoutes: # [9]
|
||||
kinds:
|
||||
@@ -109,7 +109,7 @@ Depending on the Listener Protocol, different modes and Route types are supporte
|
||||
namespaces:
|
||||
from: Selector # [11]
|
||||
selector: # [12]
|
||||
matchLabels:
|
||||
matchLabels:
|
||||
app: foo
|
||||
```
|
||||
|
||||
@@ -123,7 +123,7 @@ Depending on the Listener Protocol, different modes and Route types are supporte
|
||||
gatewayClassName: my-gateway-class # [1]
|
||||
listeners: # [2]
|
||||
- name: https # [3]
|
||||
protocol: HTTPS # [4]
|
||||
protocol: HTTPS # [4]
|
||||
port: 443 # [5]
|
||||
tls: # [7]
|
||||
certificateRefs: # [8]
|
||||
@@ -135,7 +135,7 @@ Depending on the Listener Protocol, different modes and Route types are supporte
|
||||
namespaces:
|
||||
from: Selector # [11]
|
||||
selector: # [12]
|
||||
matchLabels:
|
||||
matchLabels:
|
||||
app: foo
|
||||
```
|
||||
|
||||
@@ -149,15 +149,15 @@ Depending on the Listener Protocol, different modes and Route types are supporte
|
||||
gatewayClassName: my-gateway-class # [1]
|
||||
listeners: # [2]
|
||||
- name: tcp # [3]
|
||||
protocol: TCP # [4]
|
||||
protocol: TCP # [4]
|
||||
port: 8000 # [5]
|
||||
allowedRoutes: # [9]
|
||||
kinds:
|
||||
- kind: TCPRoute # [10]
|
||||
- kind: TCPRoute # [10]
|
||||
namespaces:
|
||||
from: Selector # [11]
|
||||
selector: # [12]
|
||||
matchLabels:
|
||||
matchLabels:
|
||||
app: footcp
|
||||
```
|
||||
|
||||
@@ -171,7 +171,7 @@ Depending on the Listener Protocol, different modes and Route types are supporte
|
||||
gatewayClassName: my-gateway-class # [1]
|
||||
listeners: # [2]
|
||||
- name: tls # [3]
|
||||
protocol: TLS # [4]
|
||||
protocol: TLS # [4]
|
||||
port: 443 # [5]
|
||||
hostname: foo.com # [6]
|
||||
tls: # [7]
|
||||
@@ -184,7 +184,7 @@ Depending on the Listener Protocol, different modes and Route types are supporte
|
||||
namespaces:
|
||||
from: Selector # [11]
|
||||
selector: # [12]
|
||||
matchLabels:
|
||||
matchLabels:
|
||||
app: footcp
|
||||
```
|
||||
|
||||
@@ -222,7 +222,7 @@ Kubernetes cluster before creating `HTTPRoute` objects.
|
||||
parentRefs: # [1]
|
||||
- name: my-tcp-gateway # [2]
|
||||
namespace: default # [3]
|
||||
sectionName: tcp # [4]
|
||||
sectionName: tcp # [4]
|
||||
hostnames: # [5]
|
||||
- whoami
|
||||
rules: # [6]
|
||||
@@ -329,7 +329,7 @@ Kubernetes cluster before creating `TLSRoute` objects.
|
||||
namespace: default # [3]
|
||||
sectionName: tcp # [4]
|
||||
hostnames: # [5]
|
||||
- whoami
|
||||
- whoami
|
||||
rules: # [6]
|
||||
- backendRefs: # [7]
|
||||
- name: whoamitcp # [8]
|
||||
|
||||
@@ -146,7 +146,7 @@ which in turn will create the resulting routers, services, handlers, etc.
|
||||
serviceAccountName: traefik-ingress-controller
|
||||
containers:
|
||||
- name: traefik
|
||||
image: traefik:v2.6
|
||||
image: traefik:v2.7
|
||||
args:
|
||||
- --entrypoints.web.address=:80
|
||||
- --providers.kubernetesingress
|
||||
@@ -537,7 +537,7 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d
|
||||
serviceAccountName: traefik-ingress-controller
|
||||
containers:
|
||||
- name: traefik
|
||||
image: traefik:v2.6
|
||||
image: traefik:v2.7
|
||||
args:
|
||||
- --entrypoints.websecure.address=:443
|
||||
- --entrypoints.websecure.http.tls
|
||||
@@ -746,7 +746,7 @@ For more options, please refer to the available [annotations](#on-ingress).
|
||||
serviceAccountName: traefik-ingress-controller
|
||||
containers:
|
||||
- name: traefik
|
||||
image: traefik:v2.6
|
||||
image: traefik:v2.7
|
||||
args:
|
||||
- --entrypoints.websecure.address=:443
|
||||
- --providers.kubernetesingress
|
||||
|
||||
@@ -371,7 +371,6 @@ You can declare TCP Routers and/or Services using KV.
|
||||
| Key (Path) | Value |
|
||||
|-----------------------------------------------|----------|
|
||||
| `traefik/tcp/routers/mytcprouter/tls/options` | `foobar` |
|
||||
|
||||
|
||||
??? info "`traefik/tcp/routers/<router_name>/tls/passthrough`"
|
||||
|
||||
@@ -381,6 +380,14 @@ You can declare TCP Routers and/or Services using KV.
|
||||
|---------------------------------------------------|--------|
|
||||
| `traefik/tcp/routers/mytcprouter/tls/passthrough` | `true` |
|
||||
|
||||
??? info "`traefik/tcp/routers/<router_name>/priority`"
|
||||
|
||||
See [priority](../routers/index.md#priority_1) for more information.
|
||||
|
||||
| Key (Path) | Value |
|
||||
|------------------------------------------|-------|
|
||||
| `traefik/tcp/routers/myrouter/priority` | `42` |
|
||||
|
||||
#### TCP Services
|
||||
|
||||
??? info "`traefik/tcp/services/<service_name>/loadbalancer/servers/<n>/url`"
|
||||
|
||||
@@ -417,6 +417,14 @@ You can declare TCP Routers and/or Services using labels.
|
||||
"traefik.tcp.routers.mytcprouter.tls.passthrough": "true"
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.routers.<router_name>.priority`"
|
||||
|
||||
See [priority](../routers/index.md#priority_1) for more information.
|
||||
|
||||
```json
|
||||
"traefik.tcp.routers.myrouter.priority": "42"
|
||||
```
|
||||
|
||||
#### TCP Services
|
||||
|
||||
??? info "`traefik.tcp.services.<service_name>.loadbalancer.server.port`"
|
||||
|
||||
@@ -420,6 +420,14 @@ You can declare TCP Routers and/or Services using labels.
|
||||
- "traefik.tcp.routers.mytcprouter.tls.passthrough=true"
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.routers.<router_name>.priority`"
|
||||
|
||||
See [priority](../routers/index.md#priority_1) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.routers.myrouter.priority=42"
|
||||
```
|
||||
|
||||
#### TCP Services
|
||||
|
||||
??? info "`traefik.tcp.services.<service_name>.loadbalancer.server.port`"
|
||||
|
||||
@@ -217,7 +217,7 @@ If the rule is verified, the router becomes active, calls middlewares, and then
|
||||
??? tip "Backticks or Quotes?"
|
||||
To set the value of a rule, use [backticks](https://en.wiktionary.org/wiki/backtick) ``` ` ``` or escaped double-quotes `\"`.
|
||||
|
||||
Single quotes `'` are not accepted as values are [Golang's String Literals](https://golang.org/ref/spec#String_literals).
|
||||
Single quotes `'` are not accepted since the values are [Golang's String Literals](https://golang.org/ref/spec#String_literals).
|
||||
|
||||
!!! example "Host is example.com"
|
||||
|
||||
@@ -262,11 +262,12 @@ The table below lists all the available matchers:
|
||||
|
||||
!!! info "Combining Matchers Using Operators and Parenthesis"
|
||||
|
||||
You can combine multiple matchers using the AND (`&&`) and OR (`||`) operators. You can also use parenthesis.
|
||||
The usual AND (`&&`) and OR (`||`) logical operators can be used, with the expected precedence rules,
|
||||
as well as parentheses.
|
||||
|
||||
!!! info "Invert a matcher"
|
||||
!!! info "Inverting a matcher"
|
||||
|
||||
You can invert a matcher by using the `!` operator.
|
||||
One can invert a matcher by using the `!` operator.
|
||||
|
||||
!!! important "Rule, Middleware, and Services"
|
||||
|
||||
@@ -800,20 +801,147 @@ If you want to limit the router scope to a set of entry points, set the entry po
|
||||
|
||||
### Rule
|
||||
|
||||
| Rule | Description |
|
||||
|--------------------------------|-------------------------------------------------------------------------|
|
||||
| ```HostSNI(`domain-1`, ...)``` | Check if the Server Name Indication corresponds to the given `domains`. |
|
||||
Rules are a set of matchers configured with values, that determine if a particular request matches specific criteria.
|
||||
If the rule is verified, the router becomes active, calls middlewares, and then forwards the request to the service.
|
||||
|
||||
??? tip "Backticks or Quotes?"
|
||||
|
||||
To set the value of a rule, use [backticks](https://en.wiktionary.org/wiki/backtick) ``` ` ``` or escaped double-quotes `\"`.
|
||||
|
||||
Single quotes `'` are not accepted since the values are [Golang's String Literals](https://golang.org/ref/spec#String_literals).
|
||||
|
||||
!!! example "HostSNI is example.com"
|
||||
|
||||
```toml
|
||||
rule = "HostSNI(`example.com`)"
|
||||
```
|
||||
|
||||
!!! example "HostSNI is example.com OR HostSNI is example.org AND ClientIP is 0.0.0.0"
|
||||
|
||||
```toml
|
||||
rule = "HostSNI(`example.com`) || (HostSNI(`example.org`) && ClientIP(`0.0.0.0`))"
|
||||
```
|
||||
|
||||
The table below lists all the available matchers:
|
||||
|
||||
| Rule | Description |
|
||||
|---------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------|
|
||||
| ```HostSNI(`domain-1`, ...)``` | Check if the Server Name Indication corresponds to the given `domains`. |
|
||||
| ```HostSNIRegexp(`example.com`, `{subdomain:[a-z]+}.example.com`, ...)``` | Check if the Server Name Indication matches the given regular expressions. See "Regexp Syntax" below. |
|
||||
| ```ClientIP(`10.0.0.0/16`, `::1`)``` | Check if the request client IP is one of the given IP/CIDR. It accepts IPv4, IPv6 and CIDR formats. |
|
||||
|
||||
!!! important "Non-ASCII Domain Names"
|
||||
|
||||
Non-ASCII characters are not supported in the `HostSNI` expression, and by doing so the associated TCP router will be invalid.
|
||||
Non-ASCII characters are not supported in the `HostSNI` and `HostSNIRegexp` expressions, and so using them would invalidate the associated TCP router.
|
||||
Domain names containing non-ASCII characters must be provided as punycode encoded values ([rfc 3492](https://tools.ietf.org/html/rfc3492)).
|
||||
|
||||
!!! important "Regexp Syntax"
|
||||
|
||||
`HostSNIRegexp` accepts an expression with zero or more groups enclosed by curly braces, which are called named regexps.
|
||||
Named regexps, of the form `{name:regexp}`, are the only expressions considered for regexp matching.
|
||||
The regexp name (`name` in the above example) is an arbitrary value, that exists only for historical reasons.
|
||||
|
||||
Any `regexp` supported by [Go's regexp package](https://golang.org/pkg/regexp/) may be used.
|
||||
|
||||
!!! important "HostSNI & TLS"
|
||||
|
||||
It is important to note that the Server Name Indication is an extension of the TLS protocol.
|
||||
Hence, only TLS routers will be able to specify a domain name with that rule.
|
||||
However, non-TLS routers will have to explicitly use that rule with `*` (every domain) to state that every non-TLS request will be handled by the router.
|
||||
However, there is one special use case for HostSNI with non-TLS routers:
|
||||
when one wants a non-TLS router that matches all (non-TLS) requests,
|
||||
one should use the specific ```HostSNI(`*`)``` syntax.
|
||||
|
||||
!!! info "Combining Matchers Using Operators and Parenthesis"
|
||||
|
||||
The usual AND (`&&`) and OR (`||`) logical operators can be used, with the expected precedence rules,
|
||||
as well as parentheses.
|
||||
|
||||
!!! info "Inverting a matcher"
|
||||
|
||||
One can invert a matcher by using the `!` operator.
|
||||
|
||||
!!! important "Rule, Middleware, and Services"
|
||||
|
||||
The rule is evaluated "before" any middleware has the opportunity to work, and "before" the request is forwarded to the service.
|
||||
|
||||
### Priority
|
||||
|
||||
To avoid path overlap, routes are sorted, by default, in descending order using rules length.
|
||||
The priority is directly equal to the length of the rule, and so the longest length has the highest priority.
|
||||
|
||||
A value of `0` for the priority is ignored: `priority = 0` means that the default rules length sorting is used.
|
||||
|
||||
??? info "How default priorities are computed"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
## Dynamic configuration
|
||||
tcp:
|
||||
routers:
|
||||
Router-1:
|
||||
rule: "ClientIP(`192.168.0.12`)"
|
||||
# ...
|
||||
Router-2:
|
||||
rule: "ClientIP(`192.168.0.0/24`)"
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
## Dynamic configuration
|
||||
[tcp.routers]
|
||||
[tcp.routers.Router-1]
|
||||
rule = "ClientIP(`192.168.0.12`)"
|
||||
# ...
|
||||
[tcp.routers.Router-2]
|
||||
rule = "ClientIP(`192.168.0.0/24`)"
|
||||
# ...
|
||||
```
|
||||
|
||||
The table below shows that `Router-2` has a higher computed priority than `Router-1`.
|
||||
|
||||
| Name | Rule | Priority |
|
||||
|----------|-------------------------------------------------------------|----------|
|
||||
| Router-1 | ```ClientIP(`192.168.0.12`)``` | 24 |
|
||||
| Router-2 | ```ClientIP(`192.168.0.0/24`)``` | 26 |
|
||||
|
||||
Which means that requests from `192.168.0.12` would go to Router-2 even though Router-1 is intended to specifically handle them.
|
||||
To achieve this intention, a priority (higher than 26) should be set on Router-1.
|
||||
|
||||
??? example "Setting priorities -- using the [File Provider](../../providers/file.md)"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
## Dynamic configuration
|
||||
tcp:
|
||||
routers:
|
||||
Router-1:
|
||||
rule: "ClientIP(`192.168.0.12`)"
|
||||
entryPoints:
|
||||
- "web"
|
||||
service: service-1
|
||||
priority: 2
|
||||
Router-2:
|
||||
rule: "ClientIP(`192.168.0.0/24`)"
|
||||
entryPoints:
|
||||
- "web"
|
||||
priority: 1
|
||||
service: service-2
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
## Dynamic configuration
|
||||
[tcp.routers]
|
||||
[tcp.routers.Router-1]
|
||||
rule = "ClientIP(`192.168.0.12`)"
|
||||
entryPoints = ["web"]
|
||||
service = "service-1"
|
||||
priority = 2
|
||||
[tcp.routers.Router-2]
|
||||
rule = "ClientIP(`192.168.0.0/24`)"
|
||||
entryPoints = ["web"]
|
||||
priority = 1
|
||||
service = "service-2"
|
||||
```
|
||||
|
||||
In this configuration, the priority is configured so that `Router-1` will handle requests from `192.168.0.12`.
|
||||
|
||||
### Middlewares
|
||||
|
||||
|
||||
@@ -1217,6 +1217,139 @@ http:
|
||||
url = "http://private-ip-server-2/"
|
||||
```
|
||||
|
||||
### Failover (service)
|
||||
|
||||
A failover service job is to forward all requests to a fallback service when the main service becomes unreachable.
|
||||
|
||||
!!! info "Relation to HealthCheck"
|
||||
|
||||
The failover service relies on the HealthCheck system to get notified when its main service becomes unreachable,
|
||||
which means HealthCheck needs to be enabled and functional on the main service.
|
||||
However, HealthCheck does not need to be enabled on the failover service itself for it to be functional.
|
||||
It is only required in order to propagate upwards the information when the failover itself becomes down
|
||||
(i.e. both its main and its fallback are down too).
|
||||
|
||||
!!! info "Supported Providers"
|
||||
|
||||
This strategy can currently only be defined with the [File](../../providers/file.md) provider.
|
||||
|
||||
```yaml tab="YAML"
|
||||
## Dynamic configuration
|
||||
http:
|
||||
services:
|
||||
app:
|
||||
failover:
|
||||
service: main
|
||||
fallback: backup
|
||||
|
||||
main:
|
||||
loadBalancer:
|
||||
healthCheck:
|
||||
path: /status
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
servers:
|
||||
- url: "http://private-ip-server-1/"
|
||||
|
||||
backup:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://private-ip-server-2/"
|
||||
```
|
||||
|
||||
```toml tab="TOML"
|
||||
## Dynamic configuration
|
||||
[http.services]
|
||||
[http.services.app]
|
||||
[http.services.app.failover]
|
||||
service = "main"
|
||||
fallback = "backup"
|
||||
|
||||
[http.services.main]
|
||||
[http.services.main.loadBalancer]
|
||||
[http.services.main.loadBalancer.healthCheck]
|
||||
path = "/health"
|
||||
interval = "10s"
|
||||
timeout = "3s"
|
||||
[[http.services.main.loadBalancer.servers]]
|
||||
url = "http://private-ip-server-1/"
|
||||
|
||||
[http.services.backup]
|
||||
[http.services.backup.loadBalancer]
|
||||
[[http.services.backup.loadBalancer.servers]]
|
||||
url = "http://private-ip-server-2/"
|
||||
```
|
||||
|
||||
#### Health Check
|
||||
|
||||
HealthCheck enables automatic self-healthcheck for this service,
|
||||
i.e. if the main and the fallback services become unreachable,
|
||||
the information is propagated upwards to its parent.
|
||||
|
||||
!!! info "All or nothing"
|
||||
|
||||
If HealthCheck is enabled for a given service, but any of its descendants does
|
||||
not have it enabled, the creation of the service will fail.
|
||||
|
||||
HealthCheck on a Failover service can currently only be defined with the [File](../../providers/file.md) provider.
|
||||
|
||||
```yaml tab="YAML"
|
||||
## Dynamic configuration
|
||||
http:
|
||||
services:
|
||||
app:
|
||||
failover:
|
||||
healthCheck: {}
|
||||
service: main
|
||||
fallback: backup
|
||||
|
||||
main:
|
||||
loadBalancer:
|
||||
healthCheck:
|
||||
path: /status
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
servers:
|
||||
- url: "http://private-ip-server-1/"
|
||||
|
||||
backup:
|
||||
loadBalancer:
|
||||
healthCheck:
|
||||
path: /status
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
servers:
|
||||
- url: "http://private-ip-server-2/"
|
||||
```
|
||||
|
||||
```toml tab="TOML"
|
||||
## Dynamic configuration
|
||||
[http.services]
|
||||
[http.services.app]
|
||||
[http.services.app.failover.healthCheck]
|
||||
[http.services.app.failover]
|
||||
service = "main"
|
||||
fallback = "backup"
|
||||
|
||||
[http.services.main]
|
||||
[http.services.main.loadBalancer]
|
||||
[http.services.main.loadBalancer.healthCheck]
|
||||
path = "/health"
|
||||
interval = "10s"
|
||||
timeout = "3s"
|
||||
[[http.services.main.loadBalancer.servers]]
|
||||
url = "http://private-ip-server-1/"
|
||||
|
||||
[http.services.backup]
|
||||
[http.services.backup.loadBalancer]
|
||||
[http.services.backup.loadBalancer.healthCheck]
|
||||
path = "/health"
|
||||
interval = "10s"
|
||||
timeout = "3s"
|
||||
[[http.services.backup.loadBalancer.servers]]
|
||||
url = "http://private-ip-server-2/"
|
||||
```
|
||||
|
||||
## Configuring TCP Services
|
||||
|
||||
### General
|
||||
|
||||
347
docs/content/traefik-hub/index.md
Normal file
347
docs/content/traefik-hub/index.md
Normal file
@@ -0,0 +1,347 @@
|
||||
# Traefik Hub (Experimental)
|
||||
|
||||
## Overview
|
||||
|
||||
Once the Traefik Hub Experimental feature is enabled in Traefik,
|
||||
Traefik and its local agent communicate together.
|
||||
|
||||
This agent can:
|
||||
|
||||
* get the Traefik metrics to display them in the Traefik Hub UI
|
||||
* secure the Traefik routers
|
||||
* provide ACME certificates to Traefik
|
||||
* transfer requests from the SaaS Platform to Traefik (and then avoid the users to expose directly their infrastructure on the internet)
|
||||
|
||||
!!! warning "Traefik Hub Entrypoints"
|
||||
|
||||
When the Traefik Hub feature is enabled, Traefik exposes some services meant for the Traefik Hub Agent on dedicated entrypoints (on ports `9900` and `9901` by default).
|
||||
Given their sensitive nature, those services should not be publicly exposed.
|
||||
Also those dedicated entrypoints, regardless of how they are created (default, or user-defined), should not be used by anything other than the Hub Agent.
|
||||
|
||||
!!! important "Learn More About Traefik Hub"
|
||||
|
||||
This section is intended only as a brief overview for Traefik users who are not familiar with Traefik Hub.
|
||||
To explore all that Traefik Hub has to offer, please consult the [Traefik Hub Documentation](https://doc.traefik.io/traefik-hub).
|
||||
|
||||
!!! Note "Prerequisites"
|
||||
|
||||
* Traefik Hub is compatible with Traefik Proxy 2.7 or later.
|
||||
* The Traefik Hub Agent must be installed to connect to the Traefik Hub platform.
|
||||
* Activate this feature in the experimental section of the static configuration.
|
||||
|
||||
!!! example "Minimal Static Configuration to Activate Traefik Hub for Docker"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
experimental:
|
||||
hub: true
|
||||
|
||||
hub:
|
||||
tls:
|
||||
insecure: true
|
||||
|
||||
metrics:
|
||||
prometheus:
|
||||
addRoutersLabels: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[experimental]
|
||||
hub = true
|
||||
|
||||
[hub]
|
||||
[hub.tls]
|
||||
insecure = true
|
||||
|
||||
[metrics]
|
||||
[metrics.prometheus]
|
||||
addRoutersLabels = true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--experimental.hub
|
||||
--hub.tls.insecure
|
||||
--metrics.prometheus.addrouterslabels
|
||||
```
|
||||
|
||||
!!! example "Minimal Static Configuration to Activate Traefik Hub for Kubernetes"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
experimental:
|
||||
hub: true
|
||||
|
||||
hub: {}
|
||||
|
||||
metrics:
|
||||
prometheus:
|
||||
addRoutersLabels: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[experimental]
|
||||
hub = true
|
||||
|
||||
[hub]
|
||||
|
||||
[metrics]
|
||||
[metrics.prometheus]
|
||||
addRoutersLabels = true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--experimental.hub
|
||||
--hub
|
||||
--metrics.prometheus.addrouterslabels
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Entrypoints
|
||||
|
||||
#### `traefikhub-api`
|
||||
|
||||
This entrypoint is used to communicate between the Hub agent and Traefik.
|
||||
It allows the Hub agent to create routing.
|
||||
|
||||
This dedicated Traefik Hub entryPoint should not be used by anything other than Traefik Hub.
|
||||
|
||||
The default port is `9900`.
|
||||
To change the port, you have to define an entrypoint named `traefikhub-api`.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
entryPoints:
|
||||
traefikhub-api: ":8000"
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[entryPoints.traefikhub-api]
|
||||
address = ":8000"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--entrypoints.traefikhub-api.address=:8000
|
||||
```
|
||||
|
||||
#### `traefikhub-tunl`
|
||||
|
||||
This entrypoint is used to communicate between Traefik Hub and Traefik.
|
||||
It allows to create secured tunnels.
|
||||
|
||||
This dedicated Traefik Hub entryPoint should not be used by anything other than Traefik Hub.
|
||||
|
||||
The default port is `9901`.
|
||||
To change the port, you have to define an entrypoint named `traefikhub-tunl`.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
entryPoints:
|
||||
traefikhub-tunl: ":8000"
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[entryPoints.traefikhub-tunl]
|
||||
address = ":8000"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--entrypoints.traefikhub-tunl.address=:8000
|
||||
```
|
||||
|
||||
### `tls`
|
||||
|
||||
_Optional, Default=None_
|
||||
|
||||
This section is required when using the Hub agent for Docker.
|
||||
|
||||
This section allows configuring mutual TLS connection between Traefik Proxy and the Traefik Hub Agent.
|
||||
The key and the certificate are the credentials for Traefik Proxy as a TLS client.
|
||||
The certificate authority authenticates the Traefik Hub Agent certificate.
|
||||
|
||||
!!! note "Certificate Domain"
|
||||
|
||||
The certificate must be valid for the `proxy.traefik` domain.
|
||||
|
||||
!!! note "Certificates Definition"
|
||||
|
||||
Certificates can be defined either by their content or their path.
|
||||
|
||||
!!! note "Insecure Mode"
|
||||
|
||||
The `insecure` option is mutually exclusive with any other option.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
hub:
|
||||
tls:
|
||||
ca: /path/to/ca.pem
|
||||
cert: /path/to/cert.pem
|
||||
key: /path/to/key.pem
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[hub.tls]
|
||||
ca= "/path/to/ca.pem"
|
||||
cert= "/path/to/cert.pem"
|
||||
key= "/path/to/key.pem"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--hub.tls.ca=/path/to/ca.pem
|
||||
--hub.tls.cert=/path/to/cert.pem
|
||||
--hub.tls.key=/path/to/key.pem
|
||||
```
|
||||
|
||||
### `tls.ca`
|
||||
|
||||
The certificate authority authenticates the Traefik Hub Agent certificate.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
hub:
|
||||
tls:
|
||||
ca: |-
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
|
||||
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
|
||||
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
||||
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
|
||||
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
|
||||
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
|
||||
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
|
||||
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[hub.tls]
|
||||
ca = """-----BEGIN CERTIFICATE-----
|
||||
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
|
||||
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
|
||||
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
||||
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
|
||||
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
|
||||
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
|
||||
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
|
||||
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
|
||||
-----END CERTIFICATE-----"""
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--hub.tls.ca=-----BEGIN CERTIFICATE-----
|
||||
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
|
||||
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
|
||||
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
||||
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
|
||||
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
|
||||
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
|
||||
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
|
||||
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
### `tls.cert`
|
||||
|
||||
The TLS certificate for Traefik Proxy as a TLS client.
|
||||
|
||||
!!! note "Certificate Domain"
|
||||
|
||||
The certificate must be valid for the `proxy.traefik` domain.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
hub:
|
||||
tls:
|
||||
cert: |-
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
|
||||
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
|
||||
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
||||
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
|
||||
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
|
||||
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
|
||||
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
|
||||
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[hub.tls]
|
||||
cert = """-----BEGIN CERTIFICATE-----
|
||||
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
|
||||
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
|
||||
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
||||
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
|
||||
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
|
||||
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
|
||||
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
|
||||
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
|
||||
-----END CERTIFICATE-----"""
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--hub.tls.cert=-----BEGIN CERTIFICATE-----
|
||||
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
|
||||
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
|
||||
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
||||
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
|
||||
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
|
||||
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
|
||||
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
|
||||
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
### `tls.key`
|
||||
|
||||
The TLS key for Traefik Proxy as a TLS client.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
hub:
|
||||
tls:
|
||||
key: |-
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgm+XJ3LVrTbbirJea
|
||||
O+Crj2ADVsVHjMuiyd72VE3lgxihRANCAARlopg+PYbweeaO7qNse3684PAqo0NV
|
||||
AwfmfGG0CBJRlLrWYtbBm+9SEhgs0/AfPxrHwLv5y6KEQLpPO+fwN4Z4
|
||||
-----END PRIVATE KEY-----
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[hub.tls]
|
||||
key = """-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgm+XJ3LVrTbbirJea
|
||||
O+Crj2ADVsVHjMuiyd72VE3lgxihRANCAARlopg+PYbweeaO7qNse3684PAqo0NV
|
||||
AwfmfGG0CBJRlLrWYtbBm+9SEhgs0/AfPxrHwLv5y6KEQLpPO+fwN4Z4
|
||||
-----END PRIVATE KEY-----"""
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--hub.tls.key=-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgm+XJ3LVrTbbirJea
|
||||
O+Crj2ADVsVHjMuiyd72VE3lgxihRANCAARlopg+PYbweeaO7qNse3684PAqo0NV
|
||||
AwfmfGG0CBJRlLrWYtbBm+9SEhgs0/AfPxrHwLv5y6KEQLpPO+fwN4Z4
|
||||
-----END PRIVATE KEY-----
|
||||
```
|
||||
|
||||
### `tls.insecure`
|
||||
|
||||
_Optional, Default=false_
|
||||
|
||||
Enables an insecure TLS connection that uses default credentials,
|
||||
and which has no peer authentication between Traefik Proxy and the Traefik Hub Agent.
|
||||
The `insecure` option is mutually exclusive with any other option.
|
||||
|
||||
!!! warning "Security Consideration"
|
||||
|
||||
Do not use this setup in production.
|
||||
This option implies sensitive data can be exposed to potential malicious third-party programs.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
hub:
|
||||
tls:
|
||||
insecure: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[hub.tls]
|
||||
insecure = true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--hub.tls.insecure=true
|
||||
```
|
||||
@@ -26,7 +26,7 @@ spec:
|
||||
serviceAccountName: traefik-ingress-controller
|
||||
containers:
|
||||
- name: traefik
|
||||
image: traefik:v2.6
|
||||
image: traefik:v2.7
|
||||
args:
|
||||
- --api.insecure
|
||||
- --accesslog
|
||||
|
||||
@@ -26,5 +26,5 @@ node:
|
||||
- K3S_CLUSTER_SECRET=somethingtotallyrandom
|
||||
volumes:
|
||||
# this is where you would place a alternative traefik image (saved as a .tar file with
|
||||
# 'docker save'), if you want to use it, instead of the traefik:v2.6 image.
|
||||
# 'docker save'), if you want to use it, instead of the traefik:v2.7 image.
|
||||
- /somewhere/on/your/host/custom-image:/var/lib/rancher/k3s/agent/images
|
||||
|
||||
@@ -3,7 +3,7 @@ version: "3.3"
|
||||
services:
|
||||
|
||||
traefik:
|
||||
image: "traefik:v2.6"
|
||||
image: "traefik:v2.7"
|
||||
container_name: "traefik"
|
||||
command:
|
||||
#- "--log.level=DEBUG"
|
||||
|
||||
@@ -13,7 +13,7 @@ secrets:
|
||||
services:
|
||||
|
||||
traefik:
|
||||
image: "traefik:v2.6"
|
||||
image: "traefik:v2.7"
|
||||
container_name: "traefik"
|
||||
command:
|
||||
#- "--log.level=DEBUG"
|
||||
|
||||
@@ -3,7 +3,7 @@ version: "3.3"
|
||||
services:
|
||||
|
||||
traefik:
|
||||
image: "traefik:v2.6"
|
||||
image: "traefik:v2.7"
|
||||
container_name: "traefik"
|
||||
command:
|
||||
#- "--log.level=DEBUG"
|
||||
|
||||
@@ -3,7 +3,7 @@ version: "3.3"
|
||||
services:
|
||||
|
||||
traefik:
|
||||
image: "traefik:v2.6"
|
||||
image: "traefik:v2.7"
|
||||
container_name: "traefik"
|
||||
command:
|
||||
#- "--log.level=DEBUG"
|
||||
|
||||
@@ -3,7 +3,7 @@ version: "3.3"
|
||||
services:
|
||||
|
||||
traefik:
|
||||
image: "traefik:v2.6"
|
||||
image: "traefik:v2.7"
|
||||
container_name: "traefik"
|
||||
command:
|
||||
#- "--log.level=DEBUG"
|
||||
|
||||
@@ -135,6 +135,7 @@ nav:
|
||||
- 'Overview': 'middlewares/tcp/overview.md'
|
||||
- 'InFlightConn': 'middlewares/tcp/inflightconn.md'
|
||||
- 'IpWhitelist': 'middlewares/tcp/ipwhitelist.md'
|
||||
- 'Traefik Hub': 'traefik-hub/index.md'
|
||||
- 'Plugins & Traefik Pilot': 'plugins/index.md'
|
||||
- 'Operations':
|
||||
- 'CLI': 'operations/cli.md'
|
||||
@@ -148,6 +149,7 @@ nav:
|
||||
- 'Overview': 'observability/metrics/overview.md'
|
||||
- 'Datadog': 'observability/metrics/datadog.md'
|
||||
- 'InfluxDB': 'observability/metrics/influxdb.md'
|
||||
- 'InfluxDB2': 'observability/metrics/influxdb2.md'
|
||||
- 'Prometheus': 'observability/metrics/prometheus.md'
|
||||
- 'StatsD': 'observability/metrics/statsd.md'
|
||||
- 'Tracing':
|
||||
@@ -199,3 +201,4 @@ nav:
|
||||
- 'Rancher': 'reference/dynamic-configuration/rancher.md'
|
||||
- 'Deprecation Notices':
|
||||
- 'Releases': 'deprecation/releases.md'
|
||||
- 'Features': 'deprecation/features.md'
|
||||
|
||||
@@ -8,8 +8,8 @@ COPY ./webui/ $WEBUI_DIR/
|
||||
|
||||
WORKDIR $WEBUI_DIR
|
||||
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
RUN yarn install
|
||||
RUN yarn build
|
||||
|
||||
# BUILD
|
||||
FROM golang:1.17-alpine as gobuild
|
||||
|
||||
30
go.mod
30
go.mod
@@ -17,10 +17,9 @@ require (
|
||||
github.com/docker/compose/v2 v2.0.1
|
||||
github.com/docker/docker v20.10.7+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/eapache/channels v1.1.0
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2
|
||||
github.com/go-acme/lego/v4 v4.6.0
|
||||
github.com/go-acme/lego/v4 v4.7.0
|
||||
github.com/go-check/check v0.0.0-00010101000000-000000000000
|
||||
github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea
|
||||
github.com/golang/protobuf v1.5.2
|
||||
@@ -32,13 +31,14 @@ require (
|
||||
github.com/hashicorp/go-hclog v0.16.1
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/hashicorp/go-version v1.3.0
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.7.0
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d
|
||||
github.com/instana/go-sensor v1.38.3
|
||||
github.com/klauspost/compress v1.13.0
|
||||
github.com/kvtools/valkeyrie v0.4.0
|
||||
github.com/lucas-clemente/quic-go v0.23.0
|
||||
github.com/lucas-clemente/quic-go v0.25.0
|
||||
github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f
|
||||
github.com/miekg/dns v1.1.45
|
||||
github.com/miekg/dns v1.1.47
|
||||
github.com/mitchellh/copystructure v1.0.0
|
||||
github.com/mitchellh/hashstructure v1.0.0
|
||||
github.com/mitchellh/mapstructure v1.4.2
|
||||
@@ -128,7 +128,7 @@ require (
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||
github.com/cpu/goacmedns v0.1.1 // indirect
|
||||
github.com/deepmap/oapi-codegen v1.6.1 // indirect
|
||||
github.com/deepmap/oapi-codegen v1.8.2 // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e // indirect
|
||||
github.com/dnsimple/dnsimple-go v0.70.1 // indirect
|
||||
@@ -139,7 +139,6 @@ require (
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/donovanhide/eventsource v0.0.0-20170630084216-b8f31a59085e // indirect
|
||||
github.com/eapache/queue v1.1.0 // indirect
|
||||
github.com/elastic/go-licenser v0.3.1 // indirect
|
||||
github.com/elastic/go-sysinfo v1.1.1 // indirect
|
||||
github.com/elastic/go-windows v1.0.0 // indirect
|
||||
@@ -147,7 +146,7 @@ require (
|
||||
github.com/exoscale/egoscale v0.67.0 // indirect
|
||||
github.com/fatih/color v1.12.0 // indirect
|
||||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/fvbommel/sortorder v1.0.1 // indirect
|
||||
github.com/go-errors/errors v1.0.1 // indirect
|
||||
github.com/go-logfmt/logfmt v0.5.0 // indirect
|
||||
@@ -163,11 +162,11 @@ require (
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/snappy v0.0.3 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/google/go-cmp v0.5.7 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.2.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 // indirect
|
||||
github.com/googleapis/gnostic v0.5.5 // indirect
|
||||
github.com/gophercloud/gophercloud v0.16.0 // indirect
|
||||
@@ -181,7 +180,7 @@ require (
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.0 // indirect
|
||||
github.com/hashicorp/go-msgpack v0.5.5 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.2 // indirect
|
||||
@@ -196,14 +195,15 @@ require (
|
||||
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect
|
||||
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
|
||||
github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea // indirect
|
||||
github.com/jarcoal/httpmock v1.0.6 // indirect
|
||||
github.com/jarcoal/httpmock v1.0.8 // indirect
|
||||
github.com/jcchavezs/porto v0.1.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect
|
||||
github.com/jonboulle/clockwork v0.2.2 // indirect
|
||||
github.com/json-iterator/go v1.1.11 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b // indirect
|
||||
@@ -220,12 +220,14 @@ require (
|
||||
github.com/marten-seemann/qpack v0.2.1 // indirect
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.4 // indirect
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.0 // indirect
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
github.com/mattn/go-isatty v0.0.12 // indirect
|
||||
github.com/mattn/go-shellwords v1.0.12 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/miekg/pkcs11 v1.0.3 // indirect
|
||||
github.com/mimuret/golang-iij-dpf v0.7.1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.14.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.1 // indirect
|
||||
@@ -235,7 +237,7 @@ require (
|
||||
github.com/moby/sys/mountinfo v0.4.1 // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
|
||||
github.com/nrdcg/auroradns v1.0.1 // indirect
|
||||
@@ -281,7 +283,7 @@ require (
|
||||
github.com/transip/gotransip/v6 v6.6.1 // indirect
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 // indirect
|
||||
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
|
||||
github.com/vultr/govultr/v2 v2.7.1 // indirect
|
||||
github.com/vultr/govultr/v2 v2.16.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
|
||||
60
go.sum
60
go.sum
@@ -475,8 +475,9 @@ github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deepmap/oapi-codegen v1.6.1 h1:2BvsmRb6pogGNtr8Ann+esAbSKFXx2CZN18VpAMecnw=
|
||||
github.com/deepmap/oapi-codegen v1.6.1/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M=
|
||||
github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU=
|
||||
github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
|
||||
github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
|
||||
@@ -552,8 +553,6 @@ github.com/donovanhide/eventsource v0.0.0-20170630084216-b8f31a59085e h1:rMOGp6H
|
||||
github.com/donovanhide/eventsource v0.0.0-20170630084216-b8f31a59085e/go.mod h1:56wL82FO0bfMU5RvfXoIwSOP2ggqqxT+tAfNEIyxuHw=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eapache/channels v1.1.0 h1:F1taHcn7/F0i8DYqKXJnyhJcVpp2kgFcNePxXtnyu4k=
|
||||
github.com/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0=
|
||||
github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=
|
||||
@@ -611,8 +610,9 @@ github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2
|
||||
github.com/frankban/quicktest v1.11.0/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
|
||||
github.com/fvbommel/sortorder v1.0.1 h1:dSnXLt4mJYH25uDDGa3biZNQsozaUWDSWeKJ0qqFfzE=
|
||||
github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
||||
@@ -620,13 +620,14 @@ github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2 h1:df6OFl8WNX
|
||||
github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2/go.mod h1:GLyXJD41gBO/NPKVPGQbhyyC06eugGy15QEZyUkE2/s=
|
||||
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
|
||||
github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
|
||||
github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-acme/lego/v4 v4.6.0 h1:w1rQtE/YHY5SupCTRpRJQbaZ6bkySJJ0z+kl8p6pVJU=
|
||||
github.com/go-acme/lego/v4 v4.6.0/go.mod h1:v19/zU0bumGNzvsbx07zQ6c9IxAvy55XIKhXCZio3NQ=
|
||||
github.com/go-acme/lego/v4 v4.7.0 h1:f5/9EigoIC3Lu14XUsbDzlJ1ZcTYBN3zu/0TJExQaOc=
|
||||
github.com/go-acme/lego/v4 v4.7.0/go.mod h1:hoPWeY+jooDbgbe5GUqHTGRGdENDhrkiypiDCAqgmmg=
|
||||
github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
|
||||
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
|
||||
@@ -776,8 +777,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-containerregistry v0.0.0-20191015185424-71da34e4d9b3/go.mod h1:ZXFeSndFcK4vB1NR4voH1Zm38K7ViUNiYtfIBDxrwf0=
|
||||
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
@@ -808,6 +810,7 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
@@ -816,8 +819,9 @@ github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2/go.mod h1:DavVbd41
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
@@ -920,8 +924,9 @@ github.com/hashicorp/go-raftchunking v0.6.1/go.mod h1:cGlg3JtDy7qy6c/3Bu660Mic1J
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hashicorp/go-retryablehttp v0.6.7/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
@@ -995,8 +1000,12 @@ github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.7.0 h1:QgP5mlBE9sGnzplpnf96pr+p7uqlIlL4W2GAP3n+XZg=
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.7.0/go.mod h1:Y/0W1+TZir7ypoQZYd2IrnVOKB3Tq6oegAQeSVN/+EU=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d h1:/WZQPMZNsjZ7IlCpsLGdQBINg5bxKQ1K1sh6awxLtkA=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU=
|
||||
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
|
||||
github.com/infobloxopen/infoblox-go-client v1.1.1 h1:728A6LbLjptj/7kZjHyIxQnm768PWHfGFm0HH8FnbtU=
|
||||
github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI=
|
||||
github.com/instana/go-sensor v1.38.3 h1:/PdHEDveLmUCvK+O6REvSv8kKljX8vaj9JMjMeCHAWk=
|
||||
@@ -1009,8 +1018,9 @@ github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80s
|
||||
github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
|
||||
github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4=
|
||||
github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
|
||||
github.com/jarcoal/httpmock v1.0.6 h1:e81vOSexXU3mJuJ4l//geOmKIt+Vkxerk1feQBC8D0g=
|
||||
github.com/jarcoal/httpmock v1.0.6/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
|
||||
github.com/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4k=
|
||||
github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
|
||||
github.com/jcchavezs/porto v0.1.0 h1:Xmxxn25zQMmgE7/yHYmh19KcItG81hIwfbEEFnd6w/Q=
|
||||
github.com/jcchavezs/porto v0.1.0/go.mod h1:fESH0gzDHiutHRdX2hv27ojnOVFco37hg1W6E9EZF4A=
|
||||
github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03 h1:FUwcHNlEqkqLjLBdCp5PRlCFijNjvcYANOZXzCfXwCM=
|
||||
@@ -1052,8 +1062,9 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
@@ -1126,8 +1137,8 @@ github.com/liquidweb/liquidweb-go v1.6.3/go.mod h1:SuXXp+thr28LnjEw18AYtWwIbWMHS
|
||||
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
|
||||
github.com/looplab/fsm v0.1.0 h1:Qte7Zdn/5hBNbXzP7yxVU4OIFHWXBovyTT2LaBTyC20=
|
||||
github.com/looplab/fsm v0.1.0/go.mod h1:m2VaOfDHxqXBBMgc26m6yUOwkFn8H2AlJDE+jd/uafI=
|
||||
github.com/lucas-clemente/quic-go v0.23.0 h1:5vFnKtZ6nHDFsc/F3uuiF4T3y/AXaQdxjUqiVw26GZE=
|
||||
github.com/lucas-clemente/quic-go v0.23.0/go.mod h1:paZuzjXCE5mj6sikVLMvqXk8lJV2AsqtJ6bDhjEfxx0=
|
||||
github.com/lucas-clemente/quic-go v0.25.0 h1:K+X9Gvd7JXsOHtU0N2icZ2Nw3rx82uBej3mP4CLgibc=
|
||||
github.com/lucas-clemente/quic-go v0.25.0/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
@@ -1155,6 +1166,8 @@ github.com/marten-seemann/qtls-go1-16 v0.1.4 h1:xbHbOGGhrenVtII6Co8akhLEdrawwB2i
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.0 h1:P9ggrs5xtwiqXv/FHNwntmuLMNq3KaSIG93AtAZ48xk=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.0/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1 h1:EnzzN9fPUkUck/1CuY1FlzBaIYMoiBsdwTNmNGkwUUM=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1/go.mod h1:PUhIQk19LoFt2174H4+an8TYvWOGjb/hHwphBeaDHwI=
|
||||
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
@@ -1191,11 +1204,13 @@ github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00v
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
github.com/miekg/dns v1.1.45 h1:g5fRIhm9nx7g8osrAvgb16QJfmyMsyOCb+J7LSv+Qzk=
|
||||
github.com/miekg/dns v1.1.45/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/dns v1.1.47 h1:J9bWiXbqMbnZPcY8Qi2E3EWIBsIm6MZzzJB9VRg5gL8=
|
||||
github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/pkcs11 v1.0.3 h1:iMwmD7I5225wv84WxIG/bmxz9AXjWvTWIbM/TYHvWtw=
|
||||
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/mimuret/golang-iij-dpf v0.7.1 h1:MHEZKx6gNGTvq1+3PYUNfTZ/qtGNNK4+zo+0Rdo4jY4=
|
||||
github.com/mimuret/golang-iij-dpf v0.7.1/go.mod h1:IXWYcQVIHYzuM+W7kDWX0mseHDfUoqMuarxMXHVTir0=
|
||||
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
||||
@@ -1254,8 +1269,9 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
|
||||
@@ -1320,6 +1336,8 @@ github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k
|
||||
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
|
||||
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
@@ -1330,8 +1348,10 @@ github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
||||
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
|
||||
github.com/onsi/gomega v1.14.0 h1:ep6kpPVwmr/nTbklSx2nrLNSIO62DoYAhnPNIMhK8gI=
|
||||
github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
@@ -1684,7 +1704,6 @@ github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
|
||||
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
|
||||
@@ -1711,8 +1730,8 @@ github.com/vulcand/oxy v1.4.1 h1:8FUsbr5xhSJqNlSrpUBcw93WuZIEI9JUyvThB9YqqF8=
|
||||
github.com/vulcand/oxy v1.4.1/go.mod h1:Yq8OBb0XWU/7nPSglwUH5LS2Pcp4yvad8SVayobZbSo=
|
||||
github.com/vulcand/predicate v1.2.0 h1:uFsW1gcnnR7R+QTID+FVcs0sSYlIGntoGOTb3rQJt50=
|
||||
github.com/vulcand/predicate v1.2.0/go.mod h1:VipoNYXny6c8N381zGUWkjuuNHiRbeAZhE7Qm9c+2GA=
|
||||
github.com/vultr/govultr/v2 v2.7.1 h1:uF9ERet++Gb+7Cqs3p1P6b6yebeaZqVd7t5P2uZCaJU=
|
||||
github.com/vultr/govultr/v2 v2.7.1/go.mod h1:BvOhVe6/ZpjwcoL6/unkdQshmbS9VGbowI4QT+3DGVU=
|
||||
github.com/vultr/govultr/v2 v2.16.0 h1:2Wa8znp9FrKLBCA5mp1XOk65k2g60uKnmaqO4z3ERtU=
|
||||
github.com/vultr/govultr/v2 v2.16.0/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI=
|
||||
github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
|
||||
github.com/weppos/publicsuffix-go v0.5.0 h1:rutRtjBJViU/YjcI5d80t4JAVvDltS6bciJg2K1HrLU=
|
||||
github.com/weppos/publicsuffix-go v0.5.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
|
||||
@@ -2105,6 +2124,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 h1:8IVLkfbr2cLhv0a/vKq4UFUcJym8RmDoDboxCFWEjYE=
|
||||
golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
|
||||
@@ -446,26 +446,28 @@ func (s *AcmeSuite) retrieveAcmeCertificate(c *check.C, testCase acmeTestCase) {
|
||||
backend := startTestServer("9010", http.StatusOK, "")
|
||||
defer backend.Close()
|
||||
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
|
||||
// wait for traefik (generating acme account take some seconds)
|
||||
err = try.Do(60*time.Second, func() error {
|
||||
_, errGet := client.Get("https://127.0.0.1:5001")
|
||||
return errGet
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
for _, sub := range testCase.subCases {
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
|
||||
// wait for traefik (generating acme account take some seconds)
|
||||
err = try.Do(60*time.Second, func() error {
|
||||
_, errGet := client.Get("https://127.0.0.1:5001")
|
||||
return errGet
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
client = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: sub.host,
|
||||
},
|
||||
// Needed so that each subcase redoes the SSL handshake
|
||||
DisableKeepAlives: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -479,10 +481,6 @@ func (s *AcmeSuite) retrieveAcmeCertificate(c *check.C, testCase acmeTestCase) {
|
||||
// Retry to send a Request which uses the LE generated certificate
|
||||
err = try.Do(60*time.Second, func() error {
|
||||
resp, err = client.Do(req)
|
||||
|
||||
// /!\ If connection is not closed, SSLHandshake will only be done during the first trial /!\
|
||||
req.Close = true
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
105
integration/conf_throttling_test.go
Normal file
105
integration/conf_throttling_test.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-check/check"
|
||||
"github.com/traefik/traefik/v2/integration/try"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
checker "github.com/vdemeester/shakers"
|
||||
)
|
||||
|
||||
type ThrottlingSuite struct{ BaseSuite }
|
||||
|
||||
func (s *ThrottlingSuite) SetUpSuite(c *check.C) {
|
||||
s.createComposeProject(c, "rest")
|
||||
s.composeUp(c)
|
||||
}
|
||||
|
||||
func (s *ThrottlingSuite) TestThrottleConfReload(c *check.C) {
|
||||
cmd, display := s.traefikCmd(withConfigFile("fixtures/throttling/simple.toml"))
|
||||
|
||||
defer display(c)
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer s.killCmd(cmd)
|
||||
|
||||
// wait for Traefik
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1000*time.Millisecond, try.BodyContains("rest@internal"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Expected a 404 as we did not configure anything.
|
||||
err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
config := &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"serviceHTTP": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://" + s.getComposeServiceIP(c, "whoami1") + ":80",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
router := &dynamic.Router{
|
||||
EntryPoints: []string{"web"},
|
||||
Middlewares: []string{},
|
||||
Service: "serviceHTTP",
|
||||
Rule: "PathPrefix(`/`)",
|
||||
}
|
||||
|
||||
confChanges := 10
|
||||
|
||||
for i := 0; i < confChanges; i++ {
|
||||
config.HTTP.Routers[fmt.Sprintf("routerHTTP%d", i)] = router
|
||||
data, err := json.Marshal(config)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
request, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8080/api/providers/rest", bytes.NewReader(data))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
response, err := http.DefaultClient.Do(request)
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(response.StatusCode, checker.Equals, http.StatusOK)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
|
||||
reloadsRegexp := regexp.MustCompile(`traefik_config_reloads_total (\d*)\n`)
|
||||
|
||||
resp, err := http.Get("http://127.0.0.1:8080/metrics")
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
fields := reloadsRegexp.FindStringSubmatch(string(body))
|
||||
c.Assert(len(fields), checker.Equals, 2)
|
||||
|
||||
reloads, err := strconv.Atoi(fields[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// The test tries to trigger a config reload with the REST API every 200ms,
|
||||
// 10 times (so for 2s in total).
|
||||
// Therefore the throttling (set at 400ms for this test) should only let
|
||||
// (2s / 400 ms =) 5 config reloads happen in theory.
|
||||
// In addition, we have to take into account the extra config reload from the internal provider (5 + 1).
|
||||
c.Assert(reloads, checker.LessOrEqualThan, 6)
|
||||
}
|
||||
@@ -234,6 +234,80 @@ func (s *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) {
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) TestSimpleConfigurationWithWatch(c *check.C) {
|
||||
tempObjects := struct {
|
||||
ConsulAddress string
|
||||
DefaultRule string
|
||||
}{
|
||||
ConsulAddress: s.consulURL,
|
||||
DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)",
|
||||
}
|
||||
|
||||
file := s.adaptFile(c, "fixtures/consul_catalog/simple_watch.toml", tempObjects)
|
||||
defer os.Remove(file)
|
||||
|
||||
reg := &api.AgentServiceRegistration{
|
||||
ID: "whoami1",
|
||||
Name: "whoami",
|
||||
Tags: []string{"traefik.enable=true"},
|
||||
Port: 80,
|
||||
Address: s.getComposeServiceIP(c, "whoami1"),
|
||||
}
|
||||
err := s.registerService(reg, false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
cmd, display := s.traefikCmd(withConfigFile(file))
|
||||
defer display(c)
|
||||
err = cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer s.killCmd(cmd)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "whoami.consul.localhost"
|
||||
|
||||
err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContainsOr("Hostname: whoami1"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = s.deregisterService("whoami1", false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusNotFound))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
whoamiIP := s.getComposeServiceIP(c, "whoami1")
|
||||
reg.Check = &api.AgentServiceCheck{
|
||||
CheckID: "some-ok-check",
|
||||
TCP: whoamiIP + ":80",
|
||||
Name: "some-ok-check",
|
||||
Interval: "1s",
|
||||
Timeout: "1s",
|
||||
}
|
||||
|
||||
err = s.registerService(reg, false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContainsOr("Hostname: whoami1"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
reg.Check = &api.AgentServiceCheck{
|
||||
CheckID: "some-failing-check",
|
||||
TCP: ":80",
|
||||
Name: "some-failing-check",
|
||||
Interval: "1s",
|
||||
Timeout: "1s",
|
||||
}
|
||||
|
||||
err = s.registerService(reg, false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusNotFound))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = s.deregisterService("whoami1", false)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) TestRegisterServiceWithoutIP(c *check.C) {
|
||||
tempObjects := struct {
|
||||
ConsulAddress string
|
||||
|
||||
22
integration/fixtures/consul_catalog/simple_watch.toml
Normal file
22
integration/fixtures/consul_catalog/simple_watch.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[global]
|
||||
checkNewVersion = false
|
||||
sendAnonymousUsage = false
|
||||
|
||||
[log]
|
||||
level = "DEBUG"
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.web]
|
||||
address = ":8000"
|
||||
|
||||
[api]
|
||||
insecure = true
|
||||
|
||||
[providers]
|
||||
[providers.consulCatalog]
|
||||
exposedByDefault = true
|
||||
refreshInterval = "500ms"
|
||||
defaultRule = "{{ .DefaultRule }}"
|
||||
watch = true
|
||||
[providers.consulCatalog.endpoint]
|
||||
address = "{{ .ConsulAddress }}"
|
||||
@@ -260,6 +260,8 @@ spec:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
priority:
|
||||
type: integer
|
||||
services:
|
||||
items:
|
||||
description: ServiceTCP defines an upstream to proxy traffic.
|
||||
|
||||
22
integration/fixtures/throttling/simple.toml
Normal file
22
integration/fixtures/throttling/simple.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[global]
|
||||
checkNewVersion = false
|
||||
sendAnonymousUsage = false
|
||||
|
||||
[log]
|
||||
level = "DEBUG"
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.web]
|
||||
address = ":8000"
|
||||
|
||||
[api]
|
||||
insecure = true
|
||||
|
||||
[providers]
|
||||
providersThrottleDuration = "400ms"
|
||||
[providers.rest]
|
||||
insecure = true
|
||||
|
||||
[metrics]
|
||||
[metrics.prometheus]
|
||||
buckets = [0.1,0.3,1.2,5.0]
|
||||
@@ -68,6 +68,7 @@ func Test(t *testing.T) {
|
||||
check.Suite(&SimpleSuite{})
|
||||
check.Suite(&TCPSuite{})
|
||||
check.Suite(&TimeoutSuite{})
|
||||
check.Suite(&ThrottlingSuite{})
|
||||
check.Suite(&TLSClientHeadersSuite{})
|
||||
check.Suite(&TracingSuite{})
|
||||
check.Suite(&UDPSuite{})
|
||||
|
||||
@@ -663,7 +663,7 @@ func (s *SimpleSuite) TestTCPRouterConfigErrors(c *check.C) {
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// router4 has an unsupported Rule
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/tcp/routers/router4@file", 1000*time.Millisecond, try.BodyContains("unknown rule Host(`mydomain.com`)"))
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/tcp/routers/router4@file", 1000*time.Millisecond, try.BodyContains("invalid rule: \\\"Host(`mydomain.com`)\\\""))
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ type features struct {
|
||||
Tracing string `json:"tracing"`
|
||||
Metrics string `json:"metrics"`
|
||||
AccessLog bool `json:"accessLog"`
|
||||
Hub bool `json:"hub"`
|
||||
// TODO add certificates resolvers
|
||||
}
|
||||
|
||||
@@ -247,6 +248,7 @@ func getFeatures(conf static.Configuration) features {
|
||||
Tracing: getTracing(conf),
|
||||
Metrics: getMetrics(conf),
|
||||
AccessLog: conf.AccessLog != nil,
|
||||
Hub: conf.Hub != nil,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/config/static"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/docker"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/file"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/hub"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/ingress"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/marathon"
|
||||
@@ -265,6 +266,7 @@ func TestHandler_Overview(t *testing.T) {
|
||||
Tracing: &static.Tracing{
|
||||
Jaeger: &jaeger.Config{},
|
||||
},
|
||||
Hub: &hub.Provider{},
|
||||
},
|
||||
confDyn: runtime.Configuration{},
|
||||
expected: expected{
|
||||
|
||||
5
pkg/api/testdata/overview-dynamic.json
vendored
5
pkg/api/testdata/overview-dynamic.json
vendored
@@ -2,7 +2,8 @@
|
||||
"features": {
|
||||
"accessLog": false,
|
||||
"metrics": "",
|
||||
"tracing": ""
|
||||
"tracing": "",
|
||||
"hub": false
|
||||
},
|
||||
"http": {
|
||||
"middlewares": {
|
||||
@@ -50,4 +51,4 @@
|
||||
"warnings": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
5
pkg/api/testdata/overview-empty.json
vendored
5
pkg/api/testdata/overview-empty.json
vendored
@@ -2,7 +2,8 @@
|
||||
"features": {
|
||||
"accessLog": false,
|
||||
"metrics": "",
|
||||
"tracing": ""
|
||||
"tracing": "",
|
||||
"hub": false
|
||||
},
|
||||
"http": {
|
||||
"middlewares": {
|
||||
@@ -50,4 +51,4 @@
|
||||
"warnings": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
5
pkg/api/testdata/overview-features.json
vendored
5
pkg/api/testdata/overview-features.json
vendored
@@ -2,7 +2,8 @@
|
||||
"features": {
|
||||
"accessLog": false,
|
||||
"metrics": "Prometheus",
|
||||
"tracing": "Jaeger"
|
||||
"tracing": "Jaeger",
|
||||
"hub": true
|
||||
},
|
||||
"http": {
|
||||
"middlewares": {
|
||||
@@ -50,4 +51,4 @@
|
||||
"warnings": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
5
pkg/api/testdata/overview-providers.json
vendored
5
pkg/api/testdata/overview-providers.json
vendored
@@ -2,7 +2,8 @@
|
||||
"features": {
|
||||
"accessLog": false,
|
||||
"metrics": "",
|
||||
"tracing": ""
|
||||
"tracing": "",
|
||||
"hub": false
|
||||
},
|
||||
"http": {
|
||||
"middlewares": {
|
||||
@@ -60,4 +61,4 @@
|
||||
"warnings": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ type Service struct {
|
||||
LoadBalancer *ServersLoadBalancer `json:"loadBalancer,omitempty" toml:"loadBalancer,omitempty" yaml:"loadBalancer,omitempty" export:"true"`
|
||||
Weighted *WeightedRoundRobin `json:"weighted,omitempty" toml:"weighted,omitempty" yaml:"weighted,omitempty" label:"-" export:"true"`
|
||||
Mirroring *Mirroring `json:"mirroring,omitempty" toml:"mirroring,omitempty" yaml:"mirroring,omitempty" label:"-" export:"true"`
|
||||
Failover *Failover `json:"failover,omitempty" toml:"failover,omitempty" yaml:"failover,omitempty" label:"-" export:"true"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
@@ -76,6 +77,15 @@ func (m *Mirroring) SetDefaults() {
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// Failover holds the Failover configuration.
|
||||
type Failover struct {
|
||||
Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"`
|
||||
Fallback string `json:"fallback,omitempty" toml:"fallback,omitempty" yaml:"fallback,omitempty" export:"true"`
|
||||
HealthCheck *HealthCheck `json:"healthCheck,omitempty" toml:"healthCheck,omitempty" yaml:"healthCheck,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// MirrorService holds the MirrorService configuration.
|
||||
type MirrorService struct {
|
||||
Name string `json:"name,omitempty" toml:"name,omitempty" yaml:"name,omitempty" export:"true"`
|
||||
@@ -98,7 +108,7 @@ type WeightedRoundRobin struct {
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// WRRService is a reference to a service load-balanced with weighted round robin.
|
||||
// WRRService is a reference to a service load-balanced with weighted round-robin.
|
||||
type WRRService struct {
|
||||
Name string `json:"name,omitempty" toml:"name,omitempty" yaml:"name,omitempty" export:"true"`
|
||||
Weight *int `json:"weight,omitempty" toml:"weight,omitempty" yaml:"weight,omitempty" export:"true"`
|
||||
|
||||
@@ -52,6 +52,7 @@ type TCPRouter struct {
|
||||
Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
|
||||
Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"`
|
||||
Rule string `json:"rule,omitempty" toml:"rule,omitempty" yaml:"rule,omitempty"`
|
||||
Priority int `json:"priority,omitempty" toml:"priority,omitempty,omitzero" yaml:"priority,omitempty" export:"true"`
|
||||
TLS *RouterTCPTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
||||
}
|
||||
|
||||
|
||||
@@ -285,6 +285,27 @@ func (in *ErrorPage) DeepCopy() *ErrorPage {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Failover) DeepCopyInto(out *Failover) {
|
||||
*out = *in
|
||||
if in.HealthCheck != nil {
|
||||
in, out := &in.HealthCheck, &out.HealthCheck
|
||||
*out = new(HealthCheck)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Failover.
|
||||
func (in *Failover) DeepCopy() *Failover {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Failover)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ForwardAuth) DeepCopyInto(out *ForwardAuth) {
|
||||
*out = *in
|
||||
@@ -1171,6 +1192,11 @@ func (in *Service) DeepCopyInto(out *Service) {
|
||||
*out = new(Mirroring)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Failover != nil {
|
||||
in, out := &in.Failover, &out.Failover
|
||||
*out = new(Failover)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -176,11 +176,13 @@ func TestDecodeConfiguration(t *testing.T) {
|
||||
"traefik.tcp.middlewares.Middleware0.ipwhitelist.sourcerange": "foobar, fiibar",
|
||||
"traefik.tcp.middlewares.Middleware2.inflightconn.amount": "42",
|
||||
"traefik.tcp.routers.Router0.rule": "foobar",
|
||||
"traefik.tcp.routers.Router0.priority": "42",
|
||||
"traefik.tcp.routers.Router0.entrypoints": "foobar, fiibar",
|
||||
"traefik.tcp.routers.Router0.service": "foobar",
|
||||
"traefik.tcp.routers.Router0.tls.passthrough": "false",
|
||||
"traefik.tcp.routers.Router0.tls.options": "foo",
|
||||
"traefik.tcp.routers.Router1.rule": "foobar",
|
||||
"traefik.tcp.routers.Router1.priority": "42",
|
||||
"traefik.tcp.routers.Router1.entrypoints": "foobar, fiibar",
|
||||
"traefik.tcp.routers.Router1.service": "foobar",
|
||||
"traefik.tcp.routers.Router1.tls.options": "foo",
|
||||
@@ -211,8 +213,9 @@ func TestDecodeConfiguration(t *testing.T) {
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
Service: "foobar",
|
||||
Rule: "foobar",
|
||||
Service: "foobar",
|
||||
Rule: "foobar",
|
||||
Priority: 42,
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: false,
|
||||
Options: "foo",
|
||||
@@ -223,8 +226,9 @@ func TestDecodeConfiguration(t *testing.T) {
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
Service: "foobar",
|
||||
Rule: "foobar",
|
||||
Service: "foobar",
|
||||
Rule: "foobar",
|
||||
Priority: 42,
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: false,
|
||||
Options: "foo",
|
||||
@@ -699,8 +703,9 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
Service: "foobar",
|
||||
Rule: "foobar",
|
||||
Service: "foobar",
|
||||
Rule: "foobar",
|
||||
Priority: 42,
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: false,
|
||||
Options: "foo",
|
||||
@@ -711,8 +716,9 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
Service: "foobar",
|
||||
Rule: "foobar",
|
||||
Service: "foobar",
|
||||
Rule: "foobar",
|
||||
Priority: 42,
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: false,
|
||||
Options: "foo",
|
||||
@@ -1333,11 +1339,13 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||
"traefik.TCP.Middlewares.Middleware0.IPWhiteList.SourceRange": "foobar, fiibar",
|
||||
"traefik.TCP.Middlewares.Middleware2.InFlightConn.Amount": "42",
|
||||
"traefik.TCP.Routers.Router0.Rule": "foobar",
|
||||
"traefik.TCP.Routers.Router0.Priority": "42",
|
||||
"traefik.TCP.Routers.Router0.EntryPoints": "foobar, fiibar",
|
||||
"traefik.TCP.Routers.Router0.Service": "foobar",
|
||||
"traefik.TCP.Routers.Router0.TLS.Passthrough": "false",
|
||||
"traefik.TCP.Routers.Router0.TLS.Options": "foo",
|
||||
"traefik.TCP.Routers.Router1.Rule": "foobar",
|
||||
"traefik.TCP.Routers.Router1.Priority": "42",
|
||||
"traefik.TCP.Routers.Router1.EntryPoints": "foobar, fiibar",
|
||||
"traefik.TCP.Routers.Router1.Service": "foobar",
|
||||
"traefik.TCP.Routers.Router1.TLS.Passthrough": "false",
|
||||
|
||||
@@ -59,6 +59,11 @@ type HTTPConfig struct {
|
||||
TLS *TLSConfig `description:"Default TLS configuration for the routers linked to the entry point." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
}
|
||||
|
||||
// HTTP3Config is the HTTP3 configuration of an entry point.
|
||||
type HTTP3Config struct {
|
||||
AdvertisedPort int32 `description:"UDP port to advertise, on which HTTP/3 is available." json:"advertisedPort,omitempty" toml:"advertisedPort,omitempty" yaml:"advertisedPort,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// Redirections is a set of redirection for an entry point.
|
||||
type Redirections struct {
|
||||
EntryPoint *RedirectEntryPoint `description:"Set of redirection for an entry point." json:"entryPoint,omitempty" toml:"entryPoint,omitempty" yaml:"entryPoint,omitempty" export:"true"`
|
||||
@@ -72,11 +77,6 @@ type RedirectEntryPoint struct {
|
||||
Priority int `description:"Priority of the generated router." json:"priority,omitempty" toml:"priority,omitempty" yaml:"priority,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// HTTP3Config is the HTTP3 configuration of an entry point.
|
||||
type HTTP3Config struct {
|
||||
AdvertisedPort int32 `description:"UDP port to advertise, on which HTTP/3 is available." json:"advertisedPort,omitempty" toml:"advertisedPort,omitempty" yaml:"advertisedPort,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (r *RedirectEntryPoint) SetDefaults() {
|
||||
r.Scheme = "https"
|
||||
|
||||
@@ -9,4 +9,5 @@ type Experimental struct {
|
||||
|
||||
KubernetesGateway bool `description:"Allow the Kubernetes gateway api provider usage." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" export:"true"`
|
||||
HTTP3 bool `description:"Enable HTTP3." json:"http3,omitempty" toml:"http3,omitempty" yaml:"http3,omitempty" export:"true"`
|
||||
Hub bool `description:"Enable the Traefik Hub provider." json:"hub,omitempty" toml:"hub,omitempty" yaml:"hub,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
53
pkg/config/static/hub.go
Normal file
53
pkg/config/static/hub.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package static
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/hub"
|
||||
)
|
||||
|
||||
func (c *Configuration) initHubProvider() error {
|
||||
// Hub provider is an experimental feature. It requires the experimental flag to be enabled before continuing.
|
||||
if c.Experimental == nil || !c.Experimental.Hub {
|
||||
return errors.New("the experimental flag for Hub is not set")
|
||||
}
|
||||
|
||||
if _, ok := c.EntryPoints[hub.TunnelEntrypoint]; !ok {
|
||||
var ep EntryPoint
|
||||
ep.SetDefaults()
|
||||
ep.Address = ":9901"
|
||||
c.EntryPoints[hub.TunnelEntrypoint] = &ep
|
||||
log.WithoutContext().Infof("The entryPoint %q is created on port 9901 to allow exposition of services.", hub.TunnelEntrypoint)
|
||||
}
|
||||
|
||||
if c.Hub.TLS == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.Hub.TLS.Insecure && (c.Hub.TLS.CA != "" || c.Hub.TLS.Cert != "" || c.Hub.TLS.Key != "") {
|
||||
return errors.New("mTLS configuration for Hub and insecure TLS for Hub are mutually exclusive")
|
||||
}
|
||||
|
||||
if !c.Hub.TLS.Insecure && (c.Hub.TLS.CA == "" || c.Hub.TLS.Cert == "" || c.Hub.TLS.Key == "") {
|
||||
return errors.New("incomplete mTLS configuration for Hub")
|
||||
}
|
||||
|
||||
if c.Hub.TLS.Insecure {
|
||||
log.WithoutContext().Warn("Hub is in `insecure` mode. Do not run in production with this setup.")
|
||||
}
|
||||
|
||||
if _, ok := c.EntryPoints[hub.APIEntrypoint]; !ok {
|
||||
var ep EntryPoint
|
||||
ep.SetDefaults()
|
||||
ep.Address = ":9900"
|
||||
c.EntryPoints[hub.APIEntrypoint] = &ep
|
||||
log.WithoutContext().Infof("The entryPoint %q is created on port 9900 to allow Traefik to communicate with the Hub Agent for Traefik.", hub.APIEntrypoint)
|
||||
}
|
||||
|
||||
c.EntryPoints[hub.APIEntrypoint].HTTP.TLS = &TLSConfig{
|
||||
Options: "traefik-hub",
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/provider/ecs"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/file"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/http"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/hub"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/gateway"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/ingress"
|
||||
@@ -78,6 +79,8 @@ type Configuration struct {
|
||||
|
||||
Pilot *Pilot `description:"Traefik Pilot configuration." json:"pilot,omitempty" toml:"pilot,omitempty" yaml:"pilot,omitempty" export:"true"`
|
||||
|
||||
Hub *hub.Provider `description:"Traefik Hub configuration." json:"hub,omitempty" toml:"hub,omitempty" yaml:"hub,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
|
||||
Experimental *Experimental `description:"experimental features." json:"experimental,omitempty" toml:"experimental,omitempty" yaml:"experimental,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
@@ -196,10 +199,14 @@ type Providers struct {
|
||||
// It also takes care of maintaining backwards compatibility.
|
||||
func (c *Configuration) SetEffectiveConfiguration() {
|
||||
// Creates the default entry point if needed
|
||||
if len(c.EntryPoints) == 0 {
|
||||
if !c.hasUserDefinedEntrypoint() {
|
||||
ep := &EntryPoint{Address: ":80"}
|
||||
ep.SetDefaults()
|
||||
c.EntryPoints = EntryPoints{"http": ep}
|
||||
// TODO: double check this tomorrow
|
||||
if c.EntryPoints == nil {
|
||||
c.EntryPoints = make(EntryPoints)
|
||||
}
|
||||
c.EntryPoints["http"] = ep
|
||||
}
|
||||
|
||||
// Creates the internal traefik entry point if needed
|
||||
@@ -214,6 +221,15 @@ func (c *Configuration) SetEffectiveConfiguration() {
|
||||
}
|
||||
}
|
||||
|
||||
if c.Hub != nil {
|
||||
if err := c.initHubProvider(); err != nil {
|
||||
c.Hub = nil
|
||||
log.WithoutContext().Errorf("Unable to activate the Hub provider: %v", err)
|
||||
} else {
|
||||
log.WithoutContext().Debugf("Experimental Hub provider has been activated.")
|
||||
}
|
||||
}
|
||||
|
||||
if c.Providers.Docker != nil {
|
||||
if c.Providers.Docker.SwarmModeRefreshSeconds <= 0 {
|
||||
c.Providers.Docker.SwarmModeRefreshSeconds = ptypes.Duration(15 * time.Second)
|
||||
@@ -269,6 +285,21 @@ func (c *Configuration) SetEffectiveConfiguration() {
|
||||
c.initACMEProvider()
|
||||
}
|
||||
|
||||
func (c *Configuration) hasUserDefinedEntrypoint() bool {
|
||||
if len(c.EntryPoints) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
switch len(c.EntryPoints) {
|
||||
case 1:
|
||||
return c.EntryPoints[hub.TunnelEntrypoint] == nil
|
||||
case 2:
|
||||
return c.EntryPoints[hub.TunnelEntrypoint] == nil || c.EntryPoints[hub.APIEntrypoint] == nil
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Configuration) initACMEProvider() {
|
||||
for _, resolver := range c.CertificatesResolvers {
|
||||
if resolver.ACME != nil {
|
||||
|
||||
88
pkg/config/static/static_config_test.go
Normal file
88
pkg/config/static/static_config_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package static
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/hub"
|
||||
)
|
||||
|
||||
func TestHasEntrypoint(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
entryPoints map[string]*EntryPoint
|
||||
assert assert.BoolAssertionFunc
|
||||
}{
|
||||
{
|
||||
desc: "no user defined entryPoints",
|
||||
assert: assert.False,
|
||||
},
|
||||
{
|
||||
desc: "user defined entryPoints",
|
||||
entryPoints: map[string]*EntryPoint{
|
||||
"foo": {},
|
||||
},
|
||||
assert: assert.True,
|
||||
},
|
||||
{
|
||||
desc: "user defined entryPoints + hub entryPoint (tunnel)",
|
||||
entryPoints: map[string]*EntryPoint{
|
||||
"foo": {},
|
||||
hub.TunnelEntrypoint: {},
|
||||
},
|
||||
assert: assert.True,
|
||||
},
|
||||
{
|
||||
desc: "hub entryPoint (tunnel)",
|
||||
entryPoints: map[string]*EntryPoint{
|
||||
hub.TunnelEntrypoint: {},
|
||||
},
|
||||
assert: assert.False,
|
||||
},
|
||||
{
|
||||
desc: "user defined entryPoints + hub entryPoint (api)",
|
||||
entryPoints: map[string]*EntryPoint{
|
||||
"foo": {},
|
||||
hub.APIEntrypoint: {},
|
||||
},
|
||||
assert: assert.True,
|
||||
},
|
||||
{
|
||||
desc: "hub entryPoint (api)",
|
||||
entryPoints: map[string]*EntryPoint{
|
||||
hub.APIEntrypoint: {},
|
||||
},
|
||||
assert: assert.True,
|
||||
},
|
||||
{
|
||||
desc: "user defined entryPoints + hub entryPoints (tunnel, api)",
|
||||
entryPoints: map[string]*EntryPoint{
|
||||
"foo": {},
|
||||
hub.TunnelEntrypoint: {},
|
||||
hub.APIEntrypoint: {},
|
||||
},
|
||||
assert: assert.True,
|
||||
},
|
||||
{
|
||||
desc: "hub entryPoints (tunnel, api)",
|
||||
entryPoints: map[string]*EntryPoint{
|
||||
hub.TunnelEntrypoint: {},
|
||||
hub.APIEntrypoint: {},
|
||||
},
|
||||
assert: assert.False,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := &Configuration{
|
||||
EntryPoints: test.entryPoints,
|
||||
}
|
||||
|
||||
test.assert(t, cfg.hasUserDefinedEntrypoint())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ func RegisterInfluxDB(ctx context.Context, config *types.InfluxDB) Registry {
|
||||
return registry
|
||||
}
|
||||
|
||||
// initInfluxDBTicker creates a influxDBClient.
|
||||
// initInfluxDBClient creates a influxDBClient.
|
||||
func initInfluxDBClient(ctx context.Context, config *types.InfluxDB) *influx.Influx {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
|
||||
144
pkg/metrics/influxdb2.go
Normal file
144
pkg/metrics/influxdb2.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
"github.com/go-kit/kit/metrics/influx"
|
||||
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
||||
influxdb2api "github.com/influxdata/influxdb-client-go/v2/api"
|
||||
"github.com/influxdata/influxdb-client-go/v2/api/write"
|
||||
influxdb2log "github.com/influxdata/influxdb-client-go/v2/log"
|
||||
influxdb "github.com/influxdata/influxdb1-client/v2"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/safe"
|
||||
"github.com/traefik/traefik/v2/pkg/types"
|
||||
)
|
||||
|
||||
var (
|
||||
influxDB2Ticker *time.Ticker
|
||||
influxDB2Store *influx.Influx
|
||||
influxDB2Client influxdb2.Client
|
||||
)
|
||||
|
||||
// RegisterInfluxDB2 creates metrics exporter for InfluxDB2.
|
||||
func RegisterInfluxDB2(ctx context.Context, config *types.InfluxDB2) Registry {
|
||||
if influxDB2Client == nil {
|
||||
var err error
|
||||
if influxDB2Client, err = newInfluxDB2Client(config); err != nil {
|
||||
log.FromContext(ctx).Error(err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if influxDB2Store == nil {
|
||||
influxDB2Store = influx.New(
|
||||
config.AdditionalLabels,
|
||||
influxdb.BatchPointsConfig{},
|
||||
kitlog.LoggerFunc(func(kv ...interface{}) error {
|
||||
log.FromContext(ctx).Error(kv)
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
|
||||
influxDB2Ticker = time.NewTicker(time.Duration(config.PushInterval))
|
||||
|
||||
safe.Go(func() {
|
||||
wc := influxDB2Client.WriteAPIBlocking(config.Org, config.Bucket)
|
||||
influxDB2Store.WriteLoop(ctx, influxDB2Ticker.C, influxDB2Writer{wc: wc})
|
||||
})
|
||||
}
|
||||
|
||||
registry := &standardRegistry{
|
||||
configReloadsCounter: influxDB2Store.NewCounter(influxDBConfigReloadsName),
|
||||
configReloadsFailureCounter: influxDB2Store.NewCounter(influxDBConfigReloadsFailureName),
|
||||
lastConfigReloadSuccessGauge: influxDB2Store.NewGauge(influxDBLastConfigReloadSuccessName),
|
||||
lastConfigReloadFailureGauge: influxDB2Store.NewGauge(influxDBLastConfigReloadFailureName),
|
||||
tlsCertsNotAfterTimestampGauge: influxDB2Store.NewGauge(influxDBTLSCertsNotAfterTimestampName),
|
||||
}
|
||||
|
||||
if config.AddEntryPointsLabels {
|
||||
registry.epEnabled = config.AddEntryPointsLabels
|
||||
registry.entryPointReqsCounter = influxDB2Store.NewCounter(influxDBEntryPointReqsName)
|
||||
registry.entryPointReqsTLSCounter = influxDB2Store.NewCounter(influxDBEntryPointReqsTLSName)
|
||||
registry.entryPointReqDurationHistogram, _ = NewHistogramWithScale(influxDB2Store.NewHistogram(influxDBEntryPointReqDurationName), time.Second)
|
||||
registry.entryPointOpenConnsGauge = influxDB2Store.NewGauge(influxDBEntryPointOpenConnsName)
|
||||
}
|
||||
|
||||
if config.AddRoutersLabels {
|
||||
registry.routerEnabled = config.AddRoutersLabels
|
||||
registry.routerReqsCounter = influxDB2Store.NewCounter(influxDBRouterReqsName)
|
||||
registry.routerReqsTLSCounter = influxDB2Store.NewCounter(influxDBRouterReqsTLSName)
|
||||
registry.routerReqDurationHistogram, _ = NewHistogramWithScale(influxDB2Store.NewHistogram(influxDBRouterReqsDurationName), time.Second)
|
||||
registry.routerOpenConnsGauge = influxDB2Store.NewGauge(influxDBORouterOpenConnsName)
|
||||
}
|
||||
|
||||
if config.AddServicesLabels {
|
||||
registry.svcEnabled = config.AddServicesLabels
|
||||
registry.serviceReqsCounter = influxDB2Store.NewCounter(influxDBServiceReqsName)
|
||||
registry.serviceReqsTLSCounter = influxDB2Store.NewCounter(influxDBServiceReqsTLSName)
|
||||
registry.serviceReqDurationHistogram, _ = NewHistogramWithScale(influxDB2Store.NewHistogram(influxDBServiceReqsDurationName), time.Second)
|
||||
registry.serviceRetriesCounter = influxDB2Store.NewCounter(influxDBServiceRetriesTotalName)
|
||||
registry.serviceOpenConnsGauge = influxDB2Store.NewGauge(influxDBServiceOpenConnsName)
|
||||
registry.serviceServerUpGauge = influxDB2Store.NewGauge(influxDBServiceServerUpName)
|
||||
}
|
||||
|
||||
return registry
|
||||
}
|
||||
|
||||
// StopInfluxDB2 stops and resets InfluxDB2 client, ticker and store.
|
||||
func StopInfluxDB2() {
|
||||
if influxDB2Client != nil {
|
||||
influxDB2Client.Close()
|
||||
}
|
||||
influxDB2Client = nil
|
||||
|
||||
if influxDB2Ticker != nil {
|
||||
influxDB2Ticker.Stop()
|
||||
}
|
||||
influxDB2Ticker = nil
|
||||
|
||||
influxDB2Store = nil
|
||||
}
|
||||
|
||||
// newInfluxDB2Client creates an influxdb2.Client.
|
||||
func newInfluxDB2Client(config *types.InfluxDB2) (influxdb2.Client, error) {
|
||||
if config.Token == "" || config.Org == "" || config.Bucket == "" {
|
||||
return nil, errors.New("token, org or bucket property is missing")
|
||||
}
|
||||
|
||||
// Disable InfluxDB2 logs.
|
||||
// See https://github.com/influxdata/influxdb-client-go/blob/v2.7.0/options.go#L128
|
||||
influxdb2log.Log = nil
|
||||
|
||||
return influxdb2.NewClient(config.Address, config.Token), nil
|
||||
}
|
||||
|
||||
type influxDB2Writer struct {
|
||||
wc influxdb2api.WriteAPIBlocking
|
||||
}
|
||||
|
||||
func (w influxDB2Writer) Write(bp influxdb.BatchPoints) error {
|
||||
ctx := log.With(context.Background(), log.Str(log.MetricsProviderName, "influxdb2"))
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
wps := make([]*write.Point, 0, len(bp.Points()))
|
||||
for _, p := range bp.Points() {
|
||||
fields, err := p.Fields()
|
||||
if err != nil {
|
||||
logger.Errorf("Error while getting %s point fields: %s", p.Name(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
wps = append(wps, influxdb2.NewPoint(
|
||||
p.Name(),
|
||||
p.Tags(),
|
||||
fields,
|
||||
p.Time(),
|
||||
))
|
||||
}
|
||||
|
||||
return w.wc.WritePoint(ctx, wps...)
|
||||
}
|
||||
145
pkg/metrics/influxdb2_test.go
Normal file
145
pkg/metrics/influxdb2_test.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v2/pkg/types"
|
||||
)
|
||||
|
||||
func TestInfluxDB2(t *testing.T) {
|
||||
c := make(chan *string)
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
bodyStr := string(body)
|
||||
c <- &bodyStr
|
||||
_, _ = fmt.Fprintln(w, "ok")
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
influxDB2Registry := RegisterInfluxDB2(context.Background(),
|
||||
&types.InfluxDB2{
|
||||
Address: ts.URL,
|
||||
Token: "test-token",
|
||||
PushInterval: ptypes.Duration(10 * time.Millisecond),
|
||||
Org: "test-org",
|
||||
Bucket: "test-bucket",
|
||||
AddEntryPointsLabels: true,
|
||||
AddRoutersLabels: true,
|
||||
AddServicesLabels: true,
|
||||
})
|
||||
defer StopInfluxDB2()
|
||||
|
||||
if !influxDB2Registry.IsEpEnabled() || !influxDB2Registry.IsRouterEnabled() || !influxDB2Registry.IsSvcEnabled() {
|
||||
t.Fatalf("InfluxDB2Registry should return true for IsEnabled(), IsRouterEnabled() and IsSvcEnabled()")
|
||||
}
|
||||
|
||||
expectedServer := []string{
|
||||
`(traefik\.config\.reload\.total count=1) [\d]{19}`,
|
||||
`(traefik\.config\.reload\.total\.failure count=1) [\d]{19}`,
|
||||
`(traefik\.config\.reload\.lastSuccessTimestamp value=1) [\d]{19}`,
|
||||
`(traefik\.config\.reload\.lastFailureTimestamp value=1) [\d]{19}`,
|
||||
}
|
||||
|
||||
influxDB2Registry.ConfigReloadsCounter().Add(1)
|
||||
influxDB2Registry.ConfigReloadsFailureCounter().Add(1)
|
||||
influxDB2Registry.LastConfigReloadSuccessGauge().Set(1)
|
||||
influxDB2Registry.LastConfigReloadFailureGauge().Set(1)
|
||||
msgServer := <-c
|
||||
|
||||
assertMessage(t, *msgServer, expectedServer)
|
||||
|
||||
expectedTLS := []string{
|
||||
`(traefik\.tls\.certs\.notAfterTimestamp,key=value value=1) [\d]{19}`,
|
||||
}
|
||||
|
||||
influxDB2Registry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1)
|
||||
msgTLS := <-c
|
||||
|
||||
assertMessage(t, *msgTLS, expectedTLS)
|
||||
|
||||
expectedEntrypoint := []string{
|
||||
`(traefik\.entrypoint\.requests\.total,code=200,entrypoint=test,method=GET count=1) [\d]{19}`,
|
||||
`(traefik\.entrypoint\.requests\.tls\.total,entrypoint=test,tls_cipher=bar,tls_version=foo count=1) [\d]{19}`,
|
||||
`(traefik\.entrypoint\.request\.duration(?:,code=[\d]{3})?,entrypoint=test p50=10000,p90=10000,p95=10000,p99=10000) [\d]{19}`,
|
||||
`(traefik\.entrypoint\.connections\.open,entrypoint=test value=1) [\d]{19}`,
|
||||
}
|
||||
|
||||
influxDB2Registry.EntryPointReqsCounter().With("entrypoint", "test", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet).Add(1)
|
||||
influxDB2Registry.EntryPointReqsTLSCounter().With("entrypoint", "test", "tls_version", "foo", "tls_cipher", "bar").Add(1)
|
||||
influxDB2Registry.EntryPointReqDurationHistogram().With("entrypoint", "test").Observe(10000)
|
||||
influxDB2Registry.EntryPointOpenConnsGauge().With("entrypoint", "test").Set(1)
|
||||
msgEntrypoint := <-c
|
||||
|
||||
assertMessage(t, *msgEntrypoint, expectedEntrypoint)
|
||||
|
||||
expectedRouter := []string{
|
||||
`(traefik\.router\.requests\.total,code=200,method=GET,router=demo,service=test count=1) [\d]{19}`,
|
||||
`(traefik\.router\.requests\.total,code=404,method=GET,router=demo,service=test count=1) [\d]{19}`,
|
||||
`(traefik\.router\.requests\.tls\.total,router=demo,service=test,tls_cipher=bar,tls_version=foo count=1) [\d]{19}`,
|
||||
`(traefik\.router\.request\.duration,code=200,router=demo,service=test p50=10000,p90=10000,p95=10000,p99=10000) [\d]{19}`,
|
||||
`(traefik\.router\.connections\.open,router=demo,service=test value=1) [\d]{19}`,
|
||||
}
|
||||
|
||||
influxDB2Registry.RouterReqsCounter().With("router", "demo", "service", "test", "code", strconv.Itoa(http.StatusNotFound), "method", http.MethodGet).Add(1)
|
||||
influxDB2Registry.RouterReqsCounter().With("router", "demo", "service", "test", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet).Add(1)
|
||||
influxDB2Registry.RouterReqsTLSCounter().With("router", "demo", "service", "test", "tls_version", "foo", "tls_cipher", "bar").Add(1)
|
||||
influxDB2Registry.RouterReqDurationHistogram().With("router", "demo", "service", "test", "code", strconv.Itoa(http.StatusOK)).Observe(10000)
|
||||
influxDB2Registry.RouterOpenConnsGauge().With("router", "demo", "service", "test").Set(1)
|
||||
msgRouter := <-c
|
||||
|
||||
assertMessage(t, *msgRouter, expectedRouter)
|
||||
|
||||
expectedService := []string{
|
||||
`(traefik\.service\.requests\.total,code=200,method=GET,service=test count=1) [\d]{19}`,
|
||||
`(traefik\.service\.requests\.total,code=404,method=GET,service=test count=1) [\d]{19}`,
|
||||
`(traefik\.service\.requests\.tls\.total,service=test,tls_cipher=bar,tls_version=foo count=1) [\d]{19}`,
|
||||
`(traefik\.service\.request\.duration,code=200,service=test p50=10000,p90=10000,p95=10000,p99=10000) [\d]{19}`,
|
||||
`(traefik\.service\.server\.up,service=test,url=http://127.0.0.1 value=1) [\d]{19}`,
|
||||
}
|
||||
|
||||
influxDB2Registry.ServiceReqsCounter().With("service", "test", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet).Add(1)
|
||||
influxDB2Registry.ServiceReqsCounter().With("service", "test", "code", strconv.Itoa(http.StatusNotFound), "method", http.MethodGet).Add(1)
|
||||
influxDB2Registry.ServiceReqsTLSCounter().With("service", "test", "tls_version", "foo", "tls_cipher", "bar").Add(1)
|
||||
influxDB2Registry.ServiceReqDurationHistogram().With("service", "test", "code", strconv.Itoa(http.StatusOK)).Observe(10000)
|
||||
influxDB2Registry.ServiceServerUpGauge().With("service", "test", "url", "http://127.0.0.1").Set(1)
|
||||
msgService := <-c
|
||||
|
||||
assertMessage(t, *msgService, expectedService)
|
||||
|
||||
expectedServiceRetries := []string{
|
||||
`(traefik\.service\.retries\.total,service=test count=2) [\d]{19}`,
|
||||
`(traefik\.service\.retries\.total,service=foobar count=1) [\d]{19}`,
|
||||
}
|
||||
|
||||
influxDB2Registry.ServiceRetriesCounter().With("service", "test").Add(1)
|
||||
influxDB2Registry.ServiceRetriesCounter().With("service", "test").Add(1)
|
||||
influxDB2Registry.ServiceRetriesCounter().With("service", "foobar").Add(1)
|
||||
|
||||
msgServiceRetries := <-c
|
||||
|
||||
assertMessage(t, *msgServiceRetries, expectedServiceRetries)
|
||||
|
||||
expectedServiceOpenConns := []string{
|
||||
`(traefik\.service\.connections\.open,service=test value=2) [\d]{19}`,
|
||||
`(traefik\.service\.connections\.open,service=foobar value=1) [\d]{19}`,
|
||||
}
|
||||
|
||||
influxDB2Registry.ServiceOpenConnsGauge().With("service", "test").Add(1)
|
||||
influxDB2Registry.ServiceOpenConnsGauge().With("service", "test").Add(1)
|
||||
influxDB2Registry.ServiceOpenConnsGauge().With("service", "foobar").Add(1)
|
||||
|
||||
msgServiceOpenConns := <-c
|
||||
|
||||
assertMessage(t, *msgServiceOpenConns, expectedServiceOpenConns)
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stvp/go-udp-testing"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v2/pkg/types"
|
||||
@@ -125,10 +126,8 @@ func TestInfluxDBHTTP(t *testing.T) {
|
||||
c := make(chan *string)
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "can't read body "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
bodyStr := string(body)
|
||||
c <- &bodyStr
|
||||
_, _ = fmt.Fprintln(w, "ok")
|
||||
@@ -140,7 +139,7 @@ func TestInfluxDBHTTP(t *testing.T) {
|
||||
&types.InfluxDB{
|
||||
Address: ts.URL,
|
||||
Protocol: "http",
|
||||
PushInterval: ptypes.Duration(time.Second),
|
||||
PushInterval: ptypes.Duration(10 * time.Millisecond),
|
||||
Database: "test",
|
||||
RetentionPolicy: "autogen",
|
||||
AddEntryPointsLabels: true,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package rules
|
||||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -10,11 +10,14 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/ip"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares/requestdecorator"
|
||||
"github.com/traefik/traefik/v2/pkg/rules"
|
||||
"github.com/vulcand/predicate"
|
||||
)
|
||||
|
||||
var funcs = map[string]func(*mux.Route, ...string) error{
|
||||
"Host": host,
|
||||
const hostMatcher = "Host"
|
||||
|
||||
var httpFuncs = map[string]func(*mux.Route, ...string) error{
|
||||
hostMatcher: host,
|
||||
"HostHeader": host,
|
||||
"HostRegexp": hostRegexp,
|
||||
"ClientIP": clientIP,
|
||||
@@ -26,33 +29,38 @@ var funcs = map[string]func(*mux.Route, ...string) error{
|
||||
"Query": query,
|
||||
}
|
||||
|
||||
// Router handle routing with rules.
|
||||
type Router struct {
|
||||
// Muxer handles routing with rules.
|
||||
type Muxer struct {
|
||||
*mux.Router
|
||||
parser predicate.Parser
|
||||
}
|
||||
|
||||
// NewRouter returns a new router instance.
|
||||
func NewRouter() (*Router, error) {
|
||||
parser, err := newParser()
|
||||
// NewMuxer returns a new muxer instance.
|
||||
func NewMuxer() (*Muxer, error) {
|
||||
var matchers []string
|
||||
for matcher := range httpFuncs {
|
||||
matchers = append(matchers, matcher)
|
||||
}
|
||||
|
||||
parser, err := rules.NewParser(matchers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Router{
|
||||
return &Muxer{
|
||||
Router: mux.NewRouter().SkipClean(true),
|
||||
parser: parser,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AddRoute add a new route to the router.
|
||||
func (r *Router) AddRoute(rule string, priority int, handler http.Handler) error {
|
||||
func (r *Muxer) AddRoute(rule string, priority int, handler http.Handler) error {
|
||||
parse, err := r.parser.Parse(rule)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while parsing rule %s: %w", rule, err)
|
||||
}
|
||||
|
||||
buildTree, ok := parse.(treeBuilder)
|
||||
buildTree, ok := parse.(rules.TreeBuilder)
|
||||
if !ok {
|
||||
return fmt.Errorf("error while parsing rule %s", rule)
|
||||
}
|
||||
@@ -72,23 +80,40 @@ func (r *Router) AddRoute(rule string, priority int, handler http.Handler) error
|
||||
return nil
|
||||
}
|
||||
|
||||
type tree struct {
|
||||
matcher string
|
||||
not bool
|
||||
value []string
|
||||
ruleLeft *tree
|
||||
ruleRight *tree
|
||||
// ParseDomains extract domains from rule.
|
||||
func ParseDomains(rule string) ([]string, error) {
|
||||
var matchers []string
|
||||
for matcher := range httpFuncs {
|
||||
matchers = append(matchers, matcher)
|
||||
}
|
||||
|
||||
parser, err := rules.NewParser(matchers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parse, err := parser.Parse(rule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buildTree, ok := parse.(rules.TreeBuilder)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error while parsing rule %s", rule)
|
||||
}
|
||||
|
||||
return buildTree().ParseMatchers([]string{hostMatcher}), nil
|
||||
}
|
||||
|
||||
func path(route *mux.Route, paths ...string) error {
|
||||
rt := route.Subrouter()
|
||||
|
||||
for _, path := range paths {
|
||||
tmpRt := rt.Path(path)
|
||||
if tmpRt.GetError() != nil {
|
||||
return tmpRt.GetError()
|
||||
if err := rt.Path(path).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -96,11 +121,11 @@ func pathPrefix(route *mux.Route, paths ...string) error {
|
||||
rt := route.Subrouter()
|
||||
|
||||
for _, path := range paths {
|
||||
tmpRt := rt.PathPrefix(path)
|
||||
if tmpRt.GetError() != nil {
|
||||
return tmpRt.GetError()
|
||||
if err := rt.PathPrefix(path).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -220,33 +245,34 @@ func query(route *mux.Route, query ...string) error {
|
||||
return route.GetError()
|
||||
}
|
||||
|
||||
func addRuleOnRouter(router *mux.Router, rule *tree) error {
|
||||
switch rule.matcher {
|
||||
func addRuleOnRouter(router *mux.Router, rule *rules.Tree) error {
|
||||
switch rule.Matcher {
|
||||
case "and":
|
||||
route := router.NewRoute()
|
||||
err := addRuleOnRoute(route, rule.ruleLeft)
|
||||
err := addRuleOnRoute(route, rule.RuleLeft)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return addRuleOnRoute(route, rule.ruleRight)
|
||||
return addRuleOnRoute(route, rule.RuleRight)
|
||||
case "or":
|
||||
err := addRuleOnRouter(router, rule.ruleLeft)
|
||||
err := addRuleOnRouter(router, rule.RuleLeft)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return addRuleOnRouter(router, rule.ruleRight)
|
||||
return addRuleOnRouter(router, rule.RuleRight)
|
||||
default:
|
||||
err := checkRule(rule)
|
||||
err := rules.CheckRule(rule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rule.not {
|
||||
return not(funcs[rule.matcher])(router.NewRoute(), rule.value...)
|
||||
if rule.Not {
|
||||
return not(httpFuncs[rule.Matcher])(router.NewRoute(), rule.Value...)
|
||||
}
|
||||
return funcs[rule.matcher](router.NewRoute(), rule.value...)
|
||||
|
||||
return httpFuncs[rule.Matcher](router.NewRoute(), rule.Value...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,48 +290,36 @@ func not(m func(*mux.Route, ...string) error) func(*mux.Route, ...string) error
|
||||
}
|
||||
}
|
||||
|
||||
func addRuleOnRoute(route *mux.Route, rule *tree) error {
|
||||
switch rule.matcher {
|
||||
func addRuleOnRoute(route *mux.Route, rule *rules.Tree) error {
|
||||
switch rule.Matcher {
|
||||
case "and":
|
||||
err := addRuleOnRoute(route, rule.ruleLeft)
|
||||
err := addRuleOnRoute(route, rule.RuleLeft)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return addRuleOnRoute(route, rule.ruleRight)
|
||||
return addRuleOnRoute(route, rule.RuleRight)
|
||||
case "or":
|
||||
subRouter := route.Subrouter()
|
||||
|
||||
err := addRuleOnRouter(subRouter, rule.ruleLeft)
|
||||
err := addRuleOnRouter(subRouter, rule.RuleLeft)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return addRuleOnRouter(subRouter, rule.ruleRight)
|
||||
return addRuleOnRouter(subRouter, rule.RuleRight)
|
||||
default:
|
||||
err := checkRule(rule)
|
||||
err := rules.CheckRule(rule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rule.not {
|
||||
return not(funcs[rule.matcher])(route, rule.value...)
|
||||
if rule.Not {
|
||||
return not(httpFuncs[rule.Matcher])(route, rule.Value...)
|
||||
}
|
||||
return funcs[rule.matcher](route, rule.value...)
|
||||
}
|
||||
}
|
||||
|
||||
func checkRule(rule *tree) error {
|
||||
if len(rule.value) == 0 {
|
||||
return fmt.Errorf("no args for matcher %s", rule.matcher)
|
||||
return httpFuncs[rule.Matcher](route, rule.Value...)
|
||||
}
|
||||
|
||||
for _, v := range rule.value {
|
||||
if len(v) == 0 {
|
||||
return fmt.Errorf("empty args for matcher %s, %v", rule.matcher, rule.value)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsASCII checks if the given string contains only ASCII characters.
|
||||
@@ -1,4 +1,4 @@
|
||||
package rules
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
@@ -635,10 +635,10 @@ func Test_addRoute(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
router, err := NewRouter()
|
||||
muxer, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.AddRoute(test.rule, 0, handler)
|
||||
err = muxer.AddRoute(test.rule, 0, handler)
|
||||
if test.expectedError {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
@@ -659,7 +659,7 @@ func Test_addRoute(t *testing.T) {
|
||||
for key, value := range test.headers {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
reqHost.ServeHTTP(w, req, router.ServeHTTP)
|
||||
reqHost.ServeHTTP(w, req, muxer.ServeHTTP)
|
||||
results[calledURL] = w.Code
|
||||
}
|
||||
assert.Equal(t, test.expected, results)
|
||||
@@ -787,7 +787,7 @@ func Test_addRoutePriority(t *testing.T) {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
router, err := NewRouter()
|
||||
muxer, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, route := range test.cases {
|
||||
@@ -796,16 +796,16 @@ func Test_addRoutePriority(t *testing.T) {
|
||||
w.Header().Set("X-From", route.xFrom)
|
||||
})
|
||||
|
||||
err := router.AddRoute(route.rule, route.priority, handler)
|
||||
err := muxer.AddRoute(route.rule, route.priority, handler)
|
||||
require.NoError(t, err, route.rule)
|
||||
}
|
||||
|
||||
router.SortRoutes()
|
||||
muxer.SortRoutes()
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, test.path, nil)
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
muxer.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, test.expected, w.Header().Get("X-From"))
|
||||
})
|
||||
@@ -900,44 +900,42 @@ func TestParseDomains(t *testing.T) {
|
||||
errorExpected bool
|
||||
}{
|
||||
{
|
||||
description: "Many host rules",
|
||||
expression: "Host(`foo.bar`,`test.bar`)",
|
||||
domain: []string{"foo.bar", "test.bar"},
|
||||
errorExpected: false,
|
||||
description: "Unknown rule",
|
||||
expression: "Foobar(`foo.bar`,`test.bar`)",
|
||||
errorExpected: true,
|
||||
},
|
||||
{
|
||||
description: "Many host rules upper",
|
||||
expression: "HOST(`foo.bar`,`test.bar`)",
|
||||
domain: []string{"foo.bar", "test.bar"},
|
||||
errorExpected: false,
|
||||
description: "Several host rules",
|
||||
expression: "Host(`foo.bar`,`test.bar`)",
|
||||
domain: []string{"foo.bar", "test.bar"},
|
||||
},
|
||||
{
|
||||
description: "Many host rules lower",
|
||||
expression: "host(`foo.bar`,`test.bar`)",
|
||||
domain: []string{"foo.bar", "test.bar"},
|
||||
errorExpected: false,
|
||||
description: "Several host rules upper",
|
||||
expression: "HOST(`foo.bar`,`test.bar`)",
|
||||
domain: []string{"foo.bar", "test.bar"},
|
||||
},
|
||||
{
|
||||
description: "No host rule",
|
||||
expression: "Path(`/test`)",
|
||||
errorExpected: false,
|
||||
description: "Several host rules lower",
|
||||
expression: "host(`foo.bar`,`test.bar`)",
|
||||
domain: []string{"foo.bar", "test.bar"},
|
||||
},
|
||||
{
|
||||
description: "Host rule and another rule",
|
||||
expression: "Host(`foo.bar`) && Path(`/test`)",
|
||||
domain: []string{"foo.bar"},
|
||||
errorExpected: false,
|
||||
description: "No host rule",
|
||||
expression: "Path(`/test`)",
|
||||
},
|
||||
{
|
||||
description: "Host rule to trim and another rule",
|
||||
expression: "Host(`Foo.Bar`) && Path(`/test`)",
|
||||
domain: []string{"foo.bar"},
|
||||
errorExpected: false,
|
||||
description: "Host rule and another rule",
|
||||
expression: "Host(`foo.bar`) && Path(`/test`)",
|
||||
domain: []string{"foo.bar"},
|
||||
},
|
||||
{
|
||||
description: "Host rule with no domain",
|
||||
expression: "Host() && Path(`/test`)",
|
||||
errorExpected: false,
|
||||
description: "Host rule to trim and another rule",
|
||||
expression: "Host(`Foo.Bar`) && Path(`/test`)",
|
||||
domain: []string{"foo.bar"},
|
||||
},
|
||||
{
|
||||
description: "Host rule with no domain",
|
||||
expression: "Host() && Path(`/test`)",
|
||||
},
|
||||
}
|
||||
|
||||
462
pkg/muxer/tcp/mux.go
Normal file
462
pkg/muxer/tcp/mux.go
Normal file
@@ -0,0 +1,462 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/traefik/traefik/v2/pkg/ip"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/rules"
|
||||
"github.com/traefik/traefik/v2/pkg/tcp"
|
||||
"github.com/traefik/traefik/v2/pkg/types"
|
||||
"github.com/vulcand/predicate"
|
||||
)
|
||||
|
||||
var tcpFuncs = map[string]func(*matchersTree, ...string) error{
|
||||
"HostSNI": hostSNI,
|
||||
"HostSNIRegexp": hostSNIRegexp,
|
||||
"ClientIP": clientIP,
|
||||
}
|
||||
|
||||
// ParseHostSNI extracts the HostSNIs declared in a rule.
|
||||
// This is a first naive implementation used in TCP routing.
|
||||
func ParseHostSNI(rule string) ([]string, error) {
|
||||
var matchers []string
|
||||
for matcher := range tcpFuncs {
|
||||
matchers = append(matchers, matcher)
|
||||
}
|
||||
|
||||
parser, err := rules.NewParser(matchers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parse, err := parser.Parse(rule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buildTree, ok := parse.(rules.TreeBuilder)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error while parsing rule %s", rule)
|
||||
}
|
||||
|
||||
return buildTree().ParseMatchers([]string{"HostSNI"}), nil
|
||||
}
|
||||
|
||||
// ConnData contains TCP connection metadata.
|
||||
type ConnData struct {
|
||||
serverName string
|
||||
remoteIP string
|
||||
}
|
||||
|
||||
// NewConnData builds a connData struct from the given parameters.
|
||||
func NewConnData(serverName string, conn tcp.WriteCloser) (ConnData, error) {
|
||||
remoteIP, _, err := net.SplitHostPort(conn.RemoteAddr().String())
|
||||
if err != nil {
|
||||
return ConnData{}, fmt.Errorf("error while parsing remote address %q: %w", conn.RemoteAddr().String(), err)
|
||||
}
|
||||
|
||||
// as per https://datatracker.ietf.org/doc/html/rfc6066:
|
||||
// > The hostname is represented as a byte string using ASCII encoding without a trailing dot.
|
||||
// so there is no need to trim a potential trailing dot
|
||||
serverName = types.CanonicalDomain(serverName)
|
||||
|
||||
return ConnData{
|
||||
serverName: types.CanonicalDomain(serverName),
|
||||
remoteIP: remoteIP,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Muxer defines a muxer that handles TCP routing with rules.
|
||||
type Muxer struct {
|
||||
routes []*route
|
||||
parser predicate.Parser
|
||||
}
|
||||
|
||||
// NewMuxer returns a TCP muxer.
|
||||
func NewMuxer() (*Muxer, error) {
|
||||
var matcherNames []string
|
||||
for matcherName := range tcpFuncs {
|
||||
matcherNames = append(matcherNames, matcherName)
|
||||
}
|
||||
|
||||
parser, err := rules.NewParser(matcherNames)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while creating rules parser: %w", err)
|
||||
}
|
||||
|
||||
return &Muxer{parser: parser}, nil
|
||||
}
|
||||
|
||||
// Match returns the handler of the first route matching the connection metadata,
|
||||
// and whether the match is exactly from the rule HostSNI(*).
|
||||
func (m Muxer) Match(meta ConnData) (tcp.Handler, bool) {
|
||||
for _, route := range m.routes {
|
||||
if route.matchers.match(meta) {
|
||||
return route.handler, route.catchAll
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// AddRoute adds a new route, associated to the given handler, at the given
|
||||
// priority, to the muxer.
|
||||
func (m *Muxer) AddRoute(rule string, priority int, handler tcp.Handler) error {
|
||||
parse, err := m.parser.Parse(rule)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while parsing rule %s: %w", rule, err)
|
||||
}
|
||||
|
||||
buildTree, ok := parse.(rules.TreeBuilder)
|
||||
if !ok {
|
||||
return fmt.Errorf("error while parsing rule %s", rule)
|
||||
}
|
||||
|
||||
ruleTree := buildTree()
|
||||
|
||||
var matchers matchersTree
|
||||
err = addRule(&matchers, ruleTree)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var catchAll bool
|
||||
if ruleTree.RuleLeft == nil && ruleTree.RuleRight == nil && len(ruleTree.Value) == 1 {
|
||||
catchAll = ruleTree.Value[0] == "*" && strings.EqualFold(ruleTree.Matcher, "HostSNI")
|
||||
}
|
||||
|
||||
// Special case for when the catchAll fallback is present.
|
||||
// When no user-defined priority is found, the lowest computable priority minus one is used,
|
||||
// in order to make the fallback the last to be evaluated.
|
||||
if priority == 0 && catchAll {
|
||||
priority = -1
|
||||
}
|
||||
|
||||
// Default value, which means the user has not set it, so we'll compute it.
|
||||
if priority == 0 {
|
||||
priority = len(rule)
|
||||
}
|
||||
|
||||
newRoute := &route{
|
||||
handler: handler,
|
||||
matchers: matchers,
|
||||
catchAll: catchAll,
|
||||
priority: priority,
|
||||
}
|
||||
m.routes = append(m.routes, newRoute)
|
||||
|
||||
sort.Sort(routes(m.routes))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addRule(tree *matchersTree, rule *rules.Tree) error {
|
||||
switch rule.Matcher {
|
||||
case "and", "or":
|
||||
tree.operator = rule.Matcher
|
||||
tree.left = &matchersTree{}
|
||||
err := addRule(tree.left, rule.RuleLeft)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tree.right = &matchersTree{}
|
||||
return addRule(tree.right, rule.RuleRight)
|
||||
default:
|
||||
err := rules.CheckRule(rule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = tcpFuncs[rule.Matcher](tree, rule.Value...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rule.Not {
|
||||
matcherFunc := tree.matcher
|
||||
tree.matcher = func(meta ConnData) bool {
|
||||
return !matcherFunc(meta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasRoutes returns whether the muxer has routes.
|
||||
func (m *Muxer) HasRoutes() bool {
|
||||
return len(m.routes) > 0
|
||||
}
|
||||
|
||||
// routes implements sort.Interface.
|
||||
type routes []*route
|
||||
|
||||
// Len implements sort.Interface.
|
||||
func (r routes) Len() int { return len(r) }
|
||||
|
||||
// Swap implements sort.Interface.
|
||||
func (r routes) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
||||
|
||||
// Less implements sort.Interface.
|
||||
func (r routes) Less(i, j int) bool { return r[i].priority > r[j].priority }
|
||||
|
||||
// route holds the matchers to match TCP route,
|
||||
// and the handler that will serve the connection.
|
||||
type route struct {
|
||||
// matchers tree structure reflecting the rule.
|
||||
matchers matchersTree
|
||||
// handler responsible for handling the route.
|
||||
handler tcp.Handler
|
||||
// catchAll indicates whether the route rule has exactly the catchAll value (HostSNI(`*`)).
|
||||
catchAll bool
|
||||
// priority is used to disambiguate between two (or more) rules that would
|
||||
// all match for a given request.
|
||||
// Computed from the matching rule length, if not user-set.
|
||||
priority int
|
||||
}
|
||||
|
||||
// matcher is a matcher func used to match connection properties.
|
||||
type matcher func(meta ConnData) bool
|
||||
|
||||
// matchersTree represents the matchers tree structure.
|
||||
type matchersTree struct {
|
||||
// If matcher is not nil, it means that this matcherTree is a leaf of the tree.
|
||||
// It is therefore mutually exclusive with left and right.
|
||||
matcher matcher
|
||||
// operator to combine the evaluation of left and right leaves.
|
||||
operator string
|
||||
// Mutually exclusive with matcher.
|
||||
left *matchersTree
|
||||
right *matchersTree
|
||||
}
|
||||
|
||||
func (m *matchersTree) match(meta ConnData) bool {
|
||||
if m == nil {
|
||||
// This should never happen as it should have been detected during parsing.
|
||||
log.WithoutContext().Warnf("Rule matcher is nil")
|
||||
return false
|
||||
}
|
||||
|
||||
if m.matcher != nil {
|
||||
return m.matcher(meta)
|
||||
}
|
||||
|
||||
switch m.operator {
|
||||
case "or":
|
||||
return m.left.match(meta) || m.right.match(meta)
|
||||
case "and":
|
||||
return m.left.match(meta) && m.right.match(meta)
|
||||
default:
|
||||
// This should never happen as it should have been detected during parsing.
|
||||
log.WithoutContext().Warnf("Invalid rule operator %s", m.operator)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func clientIP(tree *matchersTree, clientIPs ...string) error {
|
||||
checker, err := ip.NewChecker(clientIPs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not initialize IP Checker for \"ClientIP\" matcher: %w", err)
|
||||
}
|
||||
|
||||
tree.matcher = func(meta ConnData) bool {
|
||||
if meta.remoteIP == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
ok, err := checker.Contains(meta.remoteIP)
|
||||
if err != nil {
|
||||
log.WithoutContext().Warnf("\"ClientIP\" matcher: could not match remote address: %v", err)
|
||||
return false
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var almostFQDN = regexp.MustCompile(`^[[:alnum:]\.-]+$`)
|
||||
|
||||
// hostSNI checks if the SNI Host of the connection match the matcher host.
|
||||
func hostSNI(tree *matchersTree, hosts ...string) error {
|
||||
if len(hosts) == 0 {
|
||||
return errors.New("empty value for \"HostSNI\" matcher is not allowed")
|
||||
}
|
||||
|
||||
for i, host := range hosts {
|
||||
// Special case to allow global wildcard
|
||||
if host == "*" {
|
||||
continue
|
||||
}
|
||||
|
||||
if !almostFQDN.MatchString(host) {
|
||||
return fmt.Errorf("invalid value for \"HostSNI\" matcher, %q is not a valid hostname", host)
|
||||
}
|
||||
|
||||
hosts[i] = strings.ToLower(host)
|
||||
}
|
||||
|
||||
tree.matcher = func(meta ConnData) bool {
|
||||
// Since a HostSNI(`*`) rule has been provided as catchAll for non-TLS TCP,
|
||||
// it allows matching with an empty serverName.
|
||||
// Which is why we make sure to take that case into account before
|
||||
// checking meta.serverName.
|
||||
if hosts[0] == "*" {
|
||||
return true
|
||||
}
|
||||
|
||||
if meta.serverName == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, host := range hosts {
|
||||
if host == "*" {
|
||||
return true
|
||||
}
|
||||
|
||||
if host == meta.serverName {
|
||||
return true
|
||||
}
|
||||
|
||||
// trim trailing period in case of FQDN
|
||||
host = strings.TrimSuffix(host, ".")
|
||||
if host == meta.serverName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// hostSNIRegexp checks if the SNI Host of the connection matches the matcher host regexp.
|
||||
func hostSNIRegexp(tree *matchersTree, templates ...string) error {
|
||||
if len(templates) == 0 {
|
||||
return fmt.Errorf("empty value for \"HostSNIRegexp\" matcher is not allowed")
|
||||
}
|
||||
|
||||
var regexps []*regexp.Regexp
|
||||
|
||||
for _, template := range templates {
|
||||
preparedPattern, err := preparePattern(template)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid pattern value for \"HostSNIRegexp\" matcher, %q is not a valid pattern: %w", template, err)
|
||||
}
|
||||
|
||||
regexp, err := regexp.Compile(preparedPattern)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
regexps = append(regexps, regexp)
|
||||
}
|
||||
|
||||
tree.matcher = func(meta ConnData) bool {
|
||||
for _, regexp := range regexps {
|
||||
if regexp.MatchString(meta.serverName) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: expose more of containous/mux fork to get rid of the following copied code (https://github.com/containous/mux/blob/8ffa4f6d063c/regexp.go).
|
||||
|
||||
// preparePattern builds a regexp pattern from the initial user defined expression.
|
||||
// This function reuses the code dedicated to host matching of the newRouteRegexp func from the gorilla/mux library.
|
||||
// https://github.com/containous/mux/tree/8ffa4f6d063c1e2b834a73be6a1515cca3992618.
|
||||
func preparePattern(template string) (string, error) {
|
||||
// Check if it is well-formed.
|
||||
idxs, errBraces := braceIndices(template)
|
||||
if errBraces != nil {
|
||||
return "", errBraces
|
||||
}
|
||||
|
||||
defaultPattern := "[^.]+"
|
||||
pattern := bytes.NewBufferString("")
|
||||
|
||||
// Host SNI matching is case-insensitive
|
||||
fmt.Fprint(pattern, "(?i)")
|
||||
|
||||
pattern.WriteByte('^')
|
||||
var end int
|
||||
var err error
|
||||
for i := 0; i < len(idxs); i += 2 {
|
||||
// Set all values we are interested in.
|
||||
raw := template[end:idxs[i]]
|
||||
end = idxs[i+1]
|
||||
parts := strings.SplitN(template[idxs[i]+1:end-1], ":", 2)
|
||||
name := parts[0]
|
||||
patt := defaultPattern
|
||||
if len(parts) == 2 {
|
||||
patt = parts[1]
|
||||
}
|
||||
// Name or pattern can't be empty.
|
||||
if name == "" || patt == "" {
|
||||
return "", fmt.Errorf("mux: missing name or pattern in %q",
|
||||
template[idxs[i]:end])
|
||||
}
|
||||
// Build the regexp pattern.
|
||||
fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt)
|
||||
|
||||
// Append variable name and compiled pattern.
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Add the remaining.
|
||||
raw := template[end:]
|
||||
pattern.WriteString(regexp.QuoteMeta(raw))
|
||||
pattern.WriteByte('$')
|
||||
|
||||
return pattern.String(), nil
|
||||
}
|
||||
|
||||
// varGroupName builds a capturing group name for the indexed variable.
|
||||
// This function is a copy of varGroupName func from the gorilla/mux library.
|
||||
// https://github.com/containous/mux/tree/8ffa4f6d063c1e2b834a73be6a1515cca3992618.
|
||||
func varGroupName(idx int) string {
|
||||
return "v" + strconv.Itoa(idx)
|
||||
}
|
||||
|
||||
// braceIndices returns the first level curly brace indices from a string.
|
||||
// This function is a copy of braceIndices func from the gorilla/mux library.
|
||||
// https://github.com/containous/mux/tree/8ffa4f6d063c1e2b834a73be6a1515cca3992618.
|
||||
func braceIndices(s string) ([]int, error) {
|
||||
var level, idx int
|
||||
var idxs []int
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '{':
|
||||
if level++; level == 1 {
|
||||
idx = i
|
||||
}
|
||||
case '}':
|
||||
if level--; level == 0 {
|
||||
idxs = append(idxs, idx, i+1)
|
||||
} else if level < 0 {
|
||||
return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
if level != 0 {
|
||||
return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
|
||||
}
|
||||
return idxs, nil
|
||||
}
|
||||
995
pkg/muxer/tcp/mux_test.go
Normal file
995
pkg/muxer/tcp/mux_test.go
Normal file
@@ -0,0 +1,995 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/traefik/v2/pkg/tcp"
|
||||
)
|
||||
|
||||
type fakeConn struct {
|
||||
call map[string]int
|
||||
remoteAddr net.Addr
|
||||
}
|
||||
|
||||
func (f *fakeConn) Read(b []byte) (n int, err error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (f *fakeConn) Write(b []byte) (n int, err error) {
|
||||
f.call[string(b)]++
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (f *fakeConn) Close() error {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (f *fakeConn) LocalAddr() net.Addr {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (f *fakeConn) RemoteAddr() net.Addr {
|
||||
return f.remoteAddr
|
||||
}
|
||||
|
||||
func (f *fakeConn) SetDeadline(t time.Time) error {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (f *fakeConn) SetReadDeadline(t time.Time) error {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (f *fakeConn) SetWriteDeadline(t time.Time) error {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (f *fakeConn) CloseWrite() error {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func Test_addTCPRoute(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
rule string
|
||||
serverName string
|
||||
remoteAddr string
|
||||
routeErr bool
|
||||
matchErr bool
|
||||
}{
|
||||
{
|
||||
desc: "no tree",
|
||||
routeErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Rule with no matcher",
|
||||
rule: "rulewithnotmatcher",
|
||||
routeErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Empty HostSNI rule",
|
||||
rule: "HostSNI()",
|
||||
serverName: "foobar",
|
||||
routeErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Empty HostSNI rule",
|
||||
rule: "HostSNI(``)",
|
||||
serverName: "foobar",
|
||||
routeErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNI rule matching",
|
||||
rule: "HostSNI(`foobar`)",
|
||||
serverName: "foobar",
|
||||
},
|
||||
{
|
||||
desc: "Valid negative HostSNI rule matching",
|
||||
rule: "!HostSNI(`bar`)",
|
||||
serverName: "foobar",
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNI rule matching with alternative case",
|
||||
rule: "hostsni(`foobar`)",
|
||||
serverName: "foobar",
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNI rule matching with alternative case",
|
||||
rule: "HOSTSNI(`foobar`)",
|
||||
serverName: "foobar",
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNI rule not matching",
|
||||
rule: "HostSNI(`foobar`)",
|
||||
serverName: "bar",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Empty HostSNIRegexp rule",
|
||||
rule: "HostSNIRegexp()",
|
||||
serverName: "foobar",
|
||||
routeErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Empty HostSNIRegexp rule",
|
||||
rule: "HostSNIRegexp(``)",
|
||||
serverName: "foobar",
|
||||
routeErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNIRegexp rule matching",
|
||||
rule: "HostSNIRegexp(`{subdomain:[a-z]+}.foobar`)",
|
||||
serverName: "sub.foobar",
|
||||
},
|
||||
{
|
||||
desc: "Valid negative HostSNIRegexp rule matching",
|
||||
rule: "!HostSNIRegexp(`bar`)",
|
||||
serverName: "foobar",
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNIRegexp rule matching with alternative case",
|
||||
rule: "hostsniregexp(`foobar`)",
|
||||
serverName: "foobar",
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNIRegexp rule matching with alternative case",
|
||||
rule: "HOSTSNIREGEXP(`foobar`)",
|
||||
serverName: "foobar",
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNIRegexp rule not matching",
|
||||
rule: "HostSNIRegexp(`foobar`)",
|
||||
serverName: "bar",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Valid negative HostSNI rule not matching",
|
||||
rule: "!HostSNI(`bar`)",
|
||||
serverName: "bar",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNIRegexp rule matching empty servername",
|
||||
rule: "HostSNIRegexp(`{subdomain:[a-z]*}`)",
|
||||
serverName: "",
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNIRegexp rule with one name",
|
||||
rule: "HostSNIRegexp(`{dummy}`)",
|
||||
serverName: "toto",
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNIRegexp rule with one name 2",
|
||||
rule: "HostSNIRegexp(`{dummy}`)",
|
||||
serverName: "toto.com",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Empty ClientIP rule",
|
||||
rule: "ClientIP()",
|
||||
routeErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Empty ClientIP rule",
|
||||
rule: "ClientIP(``)",
|
||||
routeErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Invalid ClientIP",
|
||||
rule: "ClientIP(`invalid`)",
|
||||
routeErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Invalid remoteAddr",
|
||||
rule: "ClientIP(`10.0.0.1`)",
|
||||
remoteAddr: "not.an.IP:80",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Valid ClientIP rule matching",
|
||||
rule: "ClientIP(`10.0.0.1`)",
|
||||
remoteAddr: "10.0.0.1:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid negative ClientIP rule matching",
|
||||
rule: "!ClientIP(`20.0.0.1`)",
|
||||
remoteAddr: "10.0.0.1:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid ClientIP rule matching with alternative case",
|
||||
rule: "clientip(`10.0.0.1`)",
|
||||
remoteAddr: "10.0.0.1:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid ClientIP rule matching with alternative case",
|
||||
rule: "CLIENTIP(`10.0.0.1`)",
|
||||
remoteAddr: "10.0.0.1:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid ClientIP rule not matching",
|
||||
rule: "ClientIP(`10.0.0.1`)",
|
||||
remoteAddr: "10.0.0.2:80",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Valid negative ClientIP rule not matching",
|
||||
rule: "!ClientIP(`10.0.0.2`)",
|
||||
remoteAddr: "10.0.0.2:80",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Valid ClientIP rule matching IPv6",
|
||||
rule: "ClientIP(`10::10`)",
|
||||
remoteAddr: "[10::10]:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid negative ClientIP rule matching IPv6",
|
||||
rule: "!ClientIP(`10::10`)",
|
||||
remoteAddr: "[::1]:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid ClientIP rule not matching IPv6",
|
||||
rule: "ClientIP(`10::10`)",
|
||||
remoteAddr: "[::1]:80",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Valid ClientIP rule matching multiple IPs",
|
||||
rule: "ClientIP(`10.0.0.1`, `10.0.0.0`)",
|
||||
remoteAddr: "10.0.0.0:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid ClientIP rule matching CIDR",
|
||||
rule: "ClientIP(`11.0.0.0/24`)",
|
||||
remoteAddr: "11.0.0.0:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid ClientIP rule not matching CIDR",
|
||||
rule: "ClientIP(`11.0.0.0/24`)",
|
||||
remoteAddr: "10.0.0.0:80",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Valid ClientIP rule matching CIDR IPv6",
|
||||
rule: "ClientIP(`11::/16`)",
|
||||
remoteAddr: "[11::]:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid ClientIP rule not matching CIDR IPv6",
|
||||
rule: "ClientIP(`11::/16`)",
|
||||
remoteAddr: "[10::]:80",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Valid ClientIP rule matching multiple CIDR",
|
||||
rule: "ClientIP(`11.0.0.0/16`, `10.0.0.0/16`)",
|
||||
remoteAddr: "10.0.0.0:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid ClientIP rule not matching CIDR and matching IP",
|
||||
rule: "ClientIP(`11.0.0.0/16`, `10.0.0.0`)",
|
||||
remoteAddr: "10.0.0.0:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid ClientIP rule matching CIDR and not matching IP",
|
||||
rule: "ClientIP(`11.0.0.0`, `10.0.0.0/16`)",
|
||||
remoteAddr: "10.0.0.0:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNI and ClientIP rule matching",
|
||||
rule: "HostSNI(`foobar`) && ClientIP(`10.0.0.1`)",
|
||||
serverName: "foobar",
|
||||
remoteAddr: "10.0.0.1:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid negative HostSNI and ClientIP rule matching",
|
||||
rule: "!HostSNI(`bar`) && ClientIP(`10.0.0.1`)",
|
||||
serverName: "foobar",
|
||||
remoteAddr: "10.0.0.1:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNI and negative ClientIP rule matching",
|
||||
rule: "HostSNI(`foobar`) && !ClientIP(`10.0.0.2`)",
|
||||
serverName: "foobar",
|
||||
remoteAddr: "10.0.0.1:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid negative HostSNI and negative ClientIP rule matching",
|
||||
rule: "!HostSNI(`bar`) && !ClientIP(`10.0.0.2`)",
|
||||
serverName: "foobar",
|
||||
remoteAddr: "10.0.0.1:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid negative HostSNI or negative ClientIP rule matching",
|
||||
rule: "!(HostSNI(`bar`) || ClientIP(`10.0.0.2`))",
|
||||
serverName: "foobar",
|
||||
remoteAddr: "10.0.0.1:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid negative HostSNI and negative ClientIP rule matching",
|
||||
rule: "!(HostSNI(`bar`) && ClientIP(`10.0.0.2`))",
|
||||
serverName: "foobar",
|
||||
remoteAddr: "10.0.0.2:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid negative HostSNI and negative ClientIP rule matching",
|
||||
rule: "!(HostSNI(`bar`) && ClientIP(`10.0.0.2`))",
|
||||
serverName: "bar",
|
||||
remoteAddr: "10.0.0.1:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid negative HostSNI and negative ClientIP rule matching",
|
||||
rule: "!(HostSNI(`bar`) && ClientIP(`10.0.0.2`))",
|
||||
serverName: "bar",
|
||||
remoteAddr: "10.0.0.2:80",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Valid negative HostSNI and negative ClientIP rule matching",
|
||||
rule: "!(HostSNI(`bar`) && ClientIP(`10.0.0.2`))",
|
||||
serverName: "foobar",
|
||||
remoteAddr: "10.0.0.1:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNI and ClientIP rule not matching",
|
||||
rule: "HostSNI(`foobar`) && ClientIP(`10.0.0.1`)",
|
||||
serverName: "bar",
|
||||
remoteAddr: "10.0.0.1:80",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNI and ClientIP rule not matching",
|
||||
rule: "HostSNI(`foobar`) && ClientIP(`10.0.0.1`)",
|
||||
serverName: "foobar",
|
||||
remoteAddr: "10.0.0.2:80",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNI or ClientIP rule matching",
|
||||
rule: "HostSNI(`foobar`) || ClientIP(`10.0.0.1`)",
|
||||
serverName: "foobar",
|
||||
remoteAddr: "10.0.0.1:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNI or ClientIP rule matching",
|
||||
rule: "HostSNI(`foobar`) || ClientIP(`10.0.0.1`)",
|
||||
serverName: "bar",
|
||||
remoteAddr: "10.0.0.1:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNI or ClientIP rule matching",
|
||||
rule: "HostSNI(`foobar`) || ClientIP(`10.0.0.1`)",
|
||||
serverName: "foobar",
|
||||
remoteAddr: "10.0.0.2:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNI or ClientIP rule not matching",
|
||||
rule: "HostSNI(`foobar`) || ClientIP(`10.0.0.1`)",
|
||||
serverName: "bar",
|
||||
remoteAddr: "10.0.0.2:80",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNI x 3 OR rule matching",
|
||||
rule: "HostSNI(`foobar`) || HostSNI(`foo`) || HostSNI(`bar`)",
|
||||
serverName: "foobar",
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNI x 3 OR rule not matching",
|
||||
rule: "HostSNI(`foobar`) || HostSNI(`foo`) || HostSNI(`bar`)",
|
||||
serverName: "baz",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNI and ClientIP Combined rule matching",
|
||||
rule: "HostSNI(`foobar`) || HostSNI(`bar`) && ClientIP(`10.0.0.1`)",
|
||||
serverName: "foobar",
|
||||
remoteAddr: "10.0.0.2:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNI and ClientIP Combined rule matching",
|
||||
rule: "HostSNI(`foobar`) || HostSNI(`bar`) && ClientIP(`10.0.0.1`)",
|
||||
serverName: "bar",
|
||||
remoteAddr: "10.0.0.1:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNI and ClientIP Combined rule not matching",
|
||||
rule: "HostSNI(`foobar`) || HostSNI(`bar`) && ClientIP(`10.0.0.1`)",
|
||||
serverName: "bar",
|
||||
remoteAddr: "10.0.0.2:80",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNI and ClientIP Combined rule not matching",
|
||||
rule: "HostSNI(`foobar`) || HostSNI(`bar`) && ClientIP(`10.0.0.1`)",
|
||||
serverName: "baz",
|
||||
remoteAddr: "10.0.0.1:80",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNI and ClientIP complex combined rule matching",
|
||||
rule: "(HostSNI(`foobar`) || HostSNI(`bar`)) && (ClientIP(`10.0.0.1`) || ClientIP(`10.0.0.2`))",
|
||||
serverName: "bar",
|
||||
remoteAddr: "10.0.0.1:80",
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNI and ClientIP complex combined rule not matching",
|
||||
rule: "(HostSNI(`foobar`) || HostSNI(`bar`)) && (ClientIP(`10.0.0.1`) || ClientIP(`10.0.0.2`))",
|
||||
serverName: "baz",
|
||||
remoteAddr: "10.0.0.1:80",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNI and ClientIP complex combined rule not matching",
|
||||
rule: "(HostSNI(`foobar`) || HostSNI(`bar`)) && (ClientIP(`10.0.0.1`) || ClientIP(`10.0.0.2`))",
|
||||
serverName: "bar",
|
||||
remoteAddr: "10.0.0.3:80",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Valid HostSNI and ClientIP more complex (but absurd) combined rule matching",
|
||||
rule: "(HostSNI(`foobar`) || (HostSNI(`bar`) && !HostSNI(`foobar`))) && ((ClientIP(`10.0.0.1`) && !ClientIP(`10.0.0.2`)) || ClientIP(`10.0.0.2`)) ",
|
||||
serverName: "bar",
|
||||
remoteAddr: "10.0.0.1:80",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
msg := "BYTES"
|
||||
handler := tcp.HandlerFunc(func(conn tcp.WriteCloser) {
|
||||
_, err := conn.Write([]byte(msg))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
router, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.AddRoute(test.rule, 0, handler)
|
||||
if test.routeErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
addr := "0.0.0.0:0"
|
||||
if test.remoteAddr != "" {
|
||||
addr = test.remoteAddr
|
||||
}
|
||||
|
||||
conn := &fakeConn{
|
||||
call: map[string]int{},
|
||||
remoteAddr: fakeAddr{addr: addr},
|
||||
}
|
||||
|
||||
connData, err := NewConnData(test.serverName, conn)
|
||||
require.NoError(t, err)
|
||||
|
||||
matchingHandler, _ := router.Match(connData)
|
||||
if test.matchErr {
|
||||
require.Nil(t, matchingHandler)
|
||||
return
|
||||
}
|
||||
|
||||
require.NotNil(t, matchingHandler)
|
||||
|
||||
matchingHandler.ServeTCP(conn)
|
||||
|
||||
n, ok := conn.call[msg]
|
||||
assert.Equal(t, n, 1)
|
||||
assert.True(t, ok)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type fakeAddr struct {
|
||||
addr string
|
||||
}
|
||||
|
||||
func (f fakeAddr) String() string {
|
||||
return f.addr
|
||||
}
|
||||
|
||||
func (f fakeAddr) Network() string {
|
||||
panic("Implement me")
|
||||
}
|
||||
|
||||
func TestParseHostSNI(t *testing.T) {
|
||||
testCases := []struct {
|
||||
description string
|
||||
expression string
|
||||
domain []string
|
||||
errorExpected bool
|
||||
}{
|
||||
{
|
||||
description: "Unknown rule",
|
||||
expression: "Foobar(`foo.bar`,`test.bar`)",
|
||||
errorExpected: true,
|
||||
},
|
||||
{
|
||||
description: "Many hostSNI rules",
|
||||
expression: "HostSNI(`foo.bar`,`test.bar`)",
|
||||
domain: []string{"foo.bar", "test.bar"},
|
||||
},
|
||||
{
|
||||
description: "Many hostSNI rules upper",
|
||||
expression: "HOSTSNI(`foo.bar`,`test.bar`)",
|
||||
domain: []string{"foo.bar", "test.bar"},
|
||||
},
|
||||
{
|
||||
description: "Many hostSNI rules lower",
|
||||
expression: "hostsni(`foo.bar`,`test.bar`)",
|
||||
domain: []string{"foo.bar", "test.bar"},
|
||||
},
|
||||
{
|
||||
description: "No hostSNI rule",
|
||||
expression: "ClientIP(`10.1`)",
|
||||
},
|
||||
{
|
||||
description: "HostSNI rule and another rule",
|
||||
expression: "HostSNI(`foo.bar`) && ClientIP(`10.1`)",
|
||||
domain: []string{"foo.bar"},
|
||||
},
|
||||
{
|
||||
description: "HostSNI rule to lower and another rule",
|
||||
expression: "HostSNI(`Foo.Bar`) && ClientIP(`10.1`)",
|
||||
domain: []string{"foo.bar"},
|
||||
},
|
||||
{
|
||||
description: "HostSNI rule with no domain",
|
||||
expression: "HostSNI() && ClientIP(`10.1`)",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.expression, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
domains, err := ParseHostSNI(test.expression)
|
||||
|
||||
if test.errorExpected {
|
||||
require.Errorf(t, err, "unable to parse correctly the domains in the HostSNI rule from %q", test.expression)
|
||||
} else {
|
||||
require.NoError(t, err, "%s: Error while parsing domain.", test.expression)
|
||||
}
|
||||
|
||||
assert.EqualValues(t, test.domain, domains, "%s: Error parsing domains from expression.", test.expression)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_HostSNICatchAll(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
rule string
|
||||
isCatchAll bool
|
||||
}{
|
||||
{
|
||||
desc: "HostSNI(`foobar`) is not catchAll",
|
||||
rule: "HostSNI(`foobar`)",
|
||||
},
|
||||
{
|
||||
desc: "HostSNI(`*`) is catchAll",
|
||||
rule: "HostSNI(`*`)",
|
||||
isCatchAll: true,
|
||||
},
|
||||
{
|
||||
desc: "HOSTSNI(`*`) is catchAll",
|
||||
rule: "HOSTSNI(`*`)",
|
||||
isCatchAll: true,
|
||||
},
|
||||
{
|
||||
desc: `HostSNI("*") is catchAll`,
|
||||
rule: `HostSNI("*")`,
|
||||
isCatchAll: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
muxer, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = muxer.AddRoute(test.rule, 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {}))
|
||||
require.NoError(t, err)
|
||||
|
||||
handler, catchAll := muxer.Match(ConnData{
|
||||
serverName: "foobar",
|
||||
})
|
||||
require.NotNil(t, handler)
|
||||
assert.Equal(t, test.isCatchAll, catchAll)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_HostSNI(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
ruleHosts []string
|
||||
serverName string
|
||||
buildErr bool
|
||||
matchErr bool
|
||||
}{
|
||||
{
|
||||
desc: "Empty",
|
||||
buildErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Non ASCII host",
|
||||
ruleHosts: []string{"héhé"},
|
||||
buildErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Not Matching hosts",
|
||||
ruleHosts: []string{"foobar"},
|
||||
serverName: "bar",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Matching globing host `*`",
|
||||
ruleHosts: []string{"*"},
|
||||
serverName: "foobar",
|
||||
},
|
||||
{
|
||||
desc: "Matching globing host `*` and empty serverName",
|
||||
ruleHosts: []string{"*"},
|
||||
serverName: "",
|
||||
},
|
||||
{
|
||||
desc: "Matching globing host `*` and another non matching host",
|
||||
ruleHosts: []string{"foo", "*"},
|
||||
serverName: "bar",
|
||||
},
|
||||
{
|
||||
desc: "Matching globing host `*` and another non matching host, and empty servername",
|
||||
ruleHosts: []string{"foo", "*"},
|
||||
serverName: "",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Not Matching globing host with subdomain",
|
||||
ruleHosts: []string{"*.bar"},
|
||||
buildErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Not Matching host with trailing dot with ",
|
||||
ruleHosts: []string{"foobar."},
|
||||
serverName: "foobar.",
|
||||
},
|
||||
{
|
||||
desc: "Matching host with trailing dot",
|
||||
ruleHosts: []string{"foobar."},
|
||||
serverName: "foobar",
|
||||
},
|
||||
{
|
||||
desc: "Matching hosts",
|
||||
ruleHosts: []string{"foobar"},
|
||||
serverName: "foobar",
|
||||
},
|
||||
{
|
||||
desc: "Matching hosts with subdomains",
|
||||
ruleHosts: []string{"foo.bar"},
|
||||
serverName: "foo.bar",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
matcherTree := &matchersTree{}
|
||||
err := hostSNI(matcherTree, test.ruleHosts...)
|
||||
if test.buildErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
meta := ConnData{
|
||||
serverName: test.serverName,
|
||||
}
|
||||
|
||||
assert.Equal(t, test.matchErr, !matcherTree.match(meta))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_HostSNIRegexp(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
pattern string
|
||||
serverNames map[string]bool
|
||||
buildErr bool
|
||||
}{
|
||||
{
|
||||
desc: "unbalanced braces",
|
||||
pattern: "subdomain:(foo\\.)?bar\\.com}",
|
||||
buildErr: true,
|
||||
},
|
||||
{
|
||||
desc: "empty group name",
|
||||
pattern: "{:(foo\\.)?bar\\.com}",
|
||||
buildErr: true,
|
||||
},
|
||||
{
|
||||
desc: "empty capturing group",
|
||||
pattern: "{subdomain:}",
|
||||
buildErr: true,
|
||||
},
|
||||
{
|
||||
desc: "malformed capturing group",
|
||||
pattern: "{subdomain:(foo\\.?bar\\.com}",
|
||||
buildErr: true,
|
||||
},
|
||||
{
|
||||
desc: "not interpreted as a regexp",
|
||||
pattern: "bar.com",
|
||||
serverNames: map[string]bool{
|
||||
"bar.com": true,
|
||||
"barucom": false,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "capturing group",
|
||||
pattern: "{subdomain:(foo\\.)?bar\\.com}",
|
||||
serverNames: map[string]bool{
|
||||
"foo.bar.com": true,
|
||||
"bar.com": true,
|
||||
"fooubar.com": false,
|
||||
"barucom": false,
|
||||
"barcom": false,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "non capturing group",
|
||||
pattern: "{subdomain:(?:foo\\.)?bar\\.com}",
|
||||
serverNames: map[string]bool{
|
||||
"foo.bar.com": true,
|
||||
"bar.com": true,
|
||||
"fooubar.com": false,
|
||||
"barucom": false,
|
||||
"barcom": false,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "regex insensitive",
|
||||
pattern: "{dummy:[A-Za-z-]+\\.bar\\.com}",
|
||||
serverNames: map[string]bool{
|
||||
"FOO.bar.com": true,
|
||||
"foo.bar.com": true,
|
||||
"fooubar.com": false,
|
||||
"barucom": false,
|
||||
"barcom": false,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "insensitive host",
|
||||
pattern: "{dummy:[a-z-]+\\.bar\\.com}",
|
||||
serverNames: map[string]bool{
|
||||
"FOO.bar.com": true,
|
||||
"foo.bar.com": true,
|
||||
"fooubar.com": false,
|
||||
"barucom": false,
|
||||
"barcom": false,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "insensitive host simple",
|
||||
pattern: "foo.bar.com",
|
||||
serverNames: map[string]bool{
|
||||
"FOO.bar.com": true,
|
||||
"foo.bar.com": true,
|
||||
"fooubar.com": false,
|
||||
"barucom": false,
|
||||
"barcom": false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
matchersTree := &matchersTree{}
|
||||
err := hostSNIRegexp(matchersTree, test.pattern)
|
||||
if test.buildErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
for serverName, match := range test.serverNames {
|
||||
meta := ConnData{
|
||||
serverName: serverName,
|
||||
}
|
||||
|
||||
assert.Equal(t, match, matchersTree.match(meta))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ClientIP(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
ruleCIDRs []string
|
||||
remoteIP string
|
||||
buildErr bool
|
||||
matchErr bool
|
||||
}{
|
||||
{
|
||||
desc: "Empty",
|
||||
buildErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Malformed CIDR",
|
||||
ruleCIDRs: []string{"héhé"},
|
||||
buildErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Not matching empty remote IP",
|
||||
ruleCIDRs: []string{"20.20.20.20"},
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Not matching IP",
|
||||
ruleCIDRs: []string{"20.20.20.20"},
|
||||
remoteIP: "10.10.10.10",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Matching IP",
|
||||
ruleCIDRs: []string{"10.10.10.10"},
|
||||
remoteIP: "10.10.10.10",
|
||||
},
|
||||
{
|
||||
desc: "Not matching multiple IPs",
|
||||
ruleCIDRs: []string{"20.20.20.20", "30.30.30.30"},
|
||||
remoteIP: "10.10.10.10",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Matching multiple IPs",
|
||||
ruleCIDRs: []string{"10.10.10.10", "20.20.20.20", "30.30.30.30"},
|
||||
remoteIP: "20.20.20.20",
|
||||
},
|
||||
{
|
||||
desc: "Not matching CIDR",
|
||||
ruleCIDRs: []string{"20.0.0.0/24"},
|
||||
remoteIP: "10.10.10.10",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Matching CIDR",
|
||||
ruleCIDRs: []string{"20.0.0.0/8"},
|
||||
remoteIP: "20.10.10.10",
|
||||
},
|
||||
{
|
||||
desc: "Not matching multiple CIDRs",
|
||||
ruleCIDRs: []string{"10.0.0.0/24", "20.0.0.0/24"},
|
||||
remoteIP: "10.10.10.10",
|
||||
matchErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Matching multiple CIDRs",
|
||||
ruleCIDRs: []string{"10.0.0.0/8", "20.0.0.0/8"},
|
||||
remoteIP: "20.10.10.10",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
matchersTree := &matchersTree{}
|
||||
err := clientIP(matchersTree, test.ruleCIDRs...)
|
||||
if test.buildErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
meta := ConnData{
|
||||
remoteIP: test.remoteIP,
|
||||
}
|
||||
|
||||
assert.Equal(t, test.matchErr, !matchersTree.match(meta))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Priority(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
rules map[string]int
|
||||
serverName string
|
||||
remoteIP string
|
||||
expectedRule string
|
||||
}{
|
||||
{
|
||||
desc: "One matching rule, calculated priority",
|
||||
rules: map[string]int{
|
||||
"HostSNI(`bar`)": 0,
|
||||
"HostSNI(`foobar`)": 0,
|
||||
},
|
||||
expectedRule: "HostSNI(`bar`)",
|
||||
serverName: "bar",
|
||||
},
|
||||
{
|
||||
desc: "One matching rule, custom priority",
|
||||
rules: map[string]int{
|
||||
"HostSNI(`foobar`)": 0,
|
||||
"HostSNI(`bar`)": 10000,
|
||||
},
|
||||
expectedRule: "HostSNI(`foobar`)",
|
||||
serverName: "foobar",
|
||||
},
|
||||
{
|
||||
desc: "Two matching rules, calculated priority",
|
||||
rules: map[string]int{
|
||||
"HostSNI(`foobar`)": 0,
|
||||
"HostSNI(`foobar`, `bar`)": 0,
|
||||
},
|
||||
expectedRule: "HostSNI(`foobar`, `bar`)",
|
||||
serverName: "foobar",
|
||||
},
|
||||
{
|
||||
desc: "Two matching rules, custom priority",
|
||||
rules: map[string]int{
|
||||
"HostSNI(`foobar`)": 10000,
|
||||
"HostSNI(`foobar`, `bar`)": 0,
|
||||
},
|
||||
expectedRule: "HostSNI(`foobar`)",
|
||||
serverName: "foobar",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
muxer, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
matchedRule := ""
|
||||
for rule, priority := range test.rules {
|
||||
rule := rule
|
||||
err := muxer.AddRoute(rule, priority, tcp.HandlerFunc(func(conn tcp.WriteCloser) {
|
||||
matchedRule = rule
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
handler, _ := muxer.Match(ConnData{
|
||||
serverName: test.serverName,
|
||||
remoteIP: test.remoteIP,
|
||||
})
|
||||
require.NotNil(t, handler)
|
||||
|
||||
handler.ServeTCP(nil)
|
||||
assert.Equal(t, test.expectedRule, matchedRule)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,6 @@ const providerNameALPN = "tlsalpn.acme"
|
||||
|
||||
// ChallengeTLSALPN TLSALPN challenge provider implements challenge.Provider.
|
||||
type ChallengeTLSALPN struct {
|
||||
Timeout time.Duration
|
||||
|
||||
chans map[string]chan struct{}
|
||||
muChans sync.Mutex
|
||||
|
||||
@@ -29,11 +27,10 @@ type ChallengeTLSALPN struct {
|
||||
}
|
||||
|
||||
// NewChallengeTLSALPN creates a new ChallengeTLSALPN.
|
||||
func NewChallengeTLSALPN(timeout time.Duration) *ChallengeTLSALPN {
|
||||
func NewChallengeTLSALPN() *ChallengeTLSALPN {
|
||||
return &ChallengeTLSALPN{
|
||||
Timeout: timeout,
|
||||
chans: make(map[string]chan struct{}),
|
||||
certs: make(map[string]*Certificate),
|
||||
chans: make(map[string]chan struct{}),
|
||||
certs: make(map[string]*Certificate),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,12 +58,13 @@ func (c *ChallengeTLSALPN) Present(domain, _, keyAuth string) error {
|
||||
|
||||
c.configurationChan <- conf
|
||||
|
||||
timer := time.NewTimer(c.Timeout)
|
||||
// Present should return when its dynamic configuration has been received and applied by Traefik.
|
||||
// The timer exists in case the above does not happen, to ensure the challenge cleanup.
|
||||
timer := time.NewTimer(time.Minute)
|
||||
defer timer.Stop()
|
||||
|
||||
select {
|
||||
case t := <-timer.C:
|
||||
timer.Stop()
|
||||
|
||||
c.muChans.Lock()
|
||||
c.cleanChan(string(certPEMBlock))
|
||||
c.muChans.Unlock()
|
||||
@@ -103,6 +101,11 @@ func (c *ChallengeTLSALPN) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ThrottleDuration returns the throttle duration.
|
||||
func (c *ChallengeTLSALPN) ThrottleDuration() time.Duration {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik using the given configuration channel.
|
||||
func (c *ChallengeTLSALPN) Provide(configurationChan chan<- dynamic.Message, _ *safe.Pool) error {
|
||||
c.configurationChan = configurationChan
|
||||
|
||||
@@ -21,7 +21,8 @@ import (
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/rules"
|
||||
httpmuxer "github.com/traefik/traefik/v2/pkg/muxer/http"
|
||||
tcpmuxer "github.com/traefik/traefik/v2/pkg/muxer/tcp"
|
||||
"github.com/traefik/traefik/v2/pkg/safe"
|
||||
traefiktls "github.com/traefik/traefik/v2/pkg/tls"
|
||||
"github.com/traefik/traefik/v2/pkg/types"
|
||||
@@ -180,6 +181,11 @@ func isAccountMatchingCaServer(ctx context.Context, accountURI, serverURI string
|
||||
return cau.Hostname() == aru.Hostname()
|
||||
}
|
||||
|
||||
// ThrottleDuration returns the throttle duration.
|
||||
func (p *Provider) ThrottleDuration() time.Duration {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Provide allows the file provider to provide configurations to traefik
|
||||
// using the given Configuration channel.
|
||||
func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
|
||||
@@ -418,7 +424,7 @@ func (p *Provider) watchNewDomains(ctx context.Context) {
|
||||
})
|
||||
}
|
||||
} else {
|
||||
domains, err := rules.ParseHostSNI(route.Rule)
|
||||
domains, err := tcpmuxer.ParseHostSNI(route.Rule)
|
||||
if err != nil {
|
||||
logger.Errorf("Error parsing domains in provider ACME: %v", err)
|
||||
continue
|
||||
@@ -447,7 +453,7 @@ func (p *Provider) watchNewDomains(ctx context.Context) {
|
||||
})
|
||||
}
|
||||
} else {
|
||||
domains, err := rules.ParseDomains(route.Rule)
|
||||
domains, err := httpmuxer.ParseDomains(route.Rule)
|
||||
if err != nil {
|
||||
log.FromContext(ctxRouter).Errorf("Error parsing domains in provider ACME: %v", err)
|
||||
continue
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package aggregator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/config/static"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
@@ -11,16 +14,63 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/safe"
|
||||
)
|
||||
|
||||
// throttled defines what kind of config refresh throttling the aggregator should
|
||||
// set up for a given provider.
|
||||
// If a provider implements throttled, the configuration changes it sends will be
|
||||
// taken into account no more often than the frequency inferred from ThrottleDuration().
|
||||
// If ThrottleDuration returns zero, no throttling will take place.
|
||||
// If throttled is not implemented, the throttling will be set up in accordance
|
||||
// with the global providersThrottleDuration option.
|
||||
type throttled interface {
|
||||
ThrottleDuration() time.Duration
|
||||
}
|
||||
|
||||
// maybeThrottledProvide returns the Provide method of the given provider,
|
||||
// potentially augmented with some throttling depending on whether and how the
|
||||
// provider implements the throttled interface.
|
||||
func maybeThrottledProvide(prd provider.Provider, defaultDuration time.Duration) func(chan<- dynamic.Message, *safe.Pool) error {
|
||||
providerThrottleDuration := defaultDuration
|
||||
if throttled, ok := prd.(throttled); ok {
|
||||
// per-provider throttling
|
||||
providerThrottleDuration = throttled.ThrottleDuration()
|
||||
}
|
||||
|
||||
if providerThrottleDuration == 0 {
|
||||
// throttling disabled
|
||||
return prd.Provide
|
||||
}
|
||||
|
||||
return func(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
|
||||
rc := newRingChannel()
|
||||
pool.GoCtx(func(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case msg := <-rc.out():
|
||||
configurationChan <- msg
|
||||
time.Sleep(providerThrottleDuration)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return prd.Provide(rc.in(), pool)
|
||||
}
|
||||
}
|
||||
|
||||
// ProviderAggregator aggregates providers.
|
||||
type ProviderAggregator struct {
|
||||
internalProvider provider.Provider
|
||||
fileProvider provider.Provider
|
||||
providers []provider.Provider
|
||||
internalProvider provider.Provider
|
||||
fileProvider provider.Provider
|
||||
providers []provider.Provider
|
||||
providersThrottleDuration time.Duration
|
||||
}
|
||||
|
||||
// NewProviderAggregator returns an aggregate of all the providers configured in the static configuration.
|
||||
func NewProviderAggregator(conf static.Providers) ProviderAggregator {
|
||||
p := ProviderAggregator{}
|
||||
p := ProviderAggregator{
|
||||
providersThrottleDuration: time.Duration(conf.ProvidersThrottleDuration),
|
||||
}
|
||||
|
||||
if conf.File != nil {
|
||||
p.quietAddProvider(conf.File)
|
||||
@@ -119,26 +169,26 @@ func (p ProviderAggregator) Init() error {
|
||||
// Provide calls the provide method of every providers.
|
||||
func (p ProviderAggregator) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
|
||||
if p.fileProvider != nil {
|
||||
launchProvider(configurationChan, pool, p.fileProvider)
|
||||
p.launchProvider(configurationChan, pool, p.fileProvider)
|
||||
}
|
||||
|
||||
for _, prd := range p.providers {
|
||||
prd := prd
|
||||
safe.Go(func() {
|
||||
launchProvider(configurationChan, pool, prd)
|
||||
p.launchProvider(configurationChan, pool, prd)
|
||||
})
|
||||
}
|
||||
|
||||
// internal provider must be the last because we use it to know if all the providers are loaded.
|
||||
// ConfigurationWatcher will wait for this requiredProvider before applying configurations.
|
||||
if p.internalProvider != nil {
|
||||
launchProvider(configurationChan, pool, p.internalProvider)
|
||||
p.launchProvider(configurationChan, pool, p.internalProvider)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func launchProvider(configurationChan chan<- dynamic.Message, pool *safe.Pool, prd provider.Provider) {
|
||||
func (p ProviderAggregator) launchProvider(configurationChan chan<- dynamic.Message, pool *safe.Pool, prd provider.Provider) {
|
||||
jsonConf, err := redactor.RemoveCredentials(prd)
|
||||
if err != nil {
|
||||
log.WithoutContext().Debugf("Cannot marshal the provider configuration %T: %v", prd, err)
|
||||
@@ -147,9 +197,8 @@ func launchProvider(configurationChan chan<- dynamic.Message, pool *safe.Pool, p
|
||||
log.WithoutContext().Infof("Starting provider %T", prd)
|
||||
log.WithoutContext().Debugf("%T provider configuration: %s", prd, jsonConf)
|
||||
|
||||
currentProvider := prd
|
||||
err = currentProvider.Provide(configurationChan, pool)
|
||||
if err != nil {
|
||||
if err := maybeThrottledProvide(prd, p.providersThrottleDuration)(configurationChan, pool); err != nil {
|
||||
log.WithoutContext().Errorf("Cannot start the provider %T: %v", prd, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
71
pkg/provider/aggregator/ring_channel.go
Normal file
71
pkg/provider/aggregator/ring_channel.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package aggregator
|
||||
|
||||
import (
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
)
|
||||
|
||||
// RingChannel implements a channel in a way that never blocks the writer.
|
||||
// Specifically, if a value is written to a RingChannel when its buffer is full then the oldest
|
||||
// value in the buffer is discarded to make room (just like a standard ring-buffer).
|
||||
// Note that Go's scheduler can cause discarded values when they could be avoided, simply by scheduling
|
||||
// the writer before the reader, so caveat emptor.
|
||||
type RingChannel struct {
|
||||
input, output chan dynamic.Message
|
||||
buffer *dynamic.Message
|
||||
}
|
||||
|
||||
func newRingChannel() *RingChannel {
|
||||
ch := &RingChannel{
|
||||
input: make(chan dynamic.Message),
|
||||
output: make(chan dynamic.Message),
|
||||
}
|
||||
go ch.ringBuffer()
|
||||
return ch
|
||||
}
|
||||
|
||||
func (ch *RingChannel) in() chan<- dynamic.Message {
|
||||
return ch.input
|
||||
}
|
||||
|
||||
func (ch *RingChannel) out() <-chan dynamic.Message {
|
||||
return ch.output
|
||||
}
|
||||
|
||||
// for all buffered cases.
|
||||
func (ch *RingChannel) ringBuffer() {
|
||||
var input, output chan dynamic.Message
|
||||
var next dynamic.Message
|
||||
input = ch.input
|
||||
|
||||
for input != nil || output != nil {
|
||||
select {
|
||||
// Prefer to write if possible, which is surprisingly effective in reducing
|
||||
// dropped elements due to overflow. The naive read/write select chooses randomly
|
||||
// when both channels are ready, which produces unnecessary drops 50% of the time.
|
||||
case output <- next:
|
||||
ch.buffer = nil
|
||||
default:
|
||||
select {
|
||||
case elem, open := <-input:
|
||||
if !open {
|
||||
input = nil
|
||||
break
|
||||
}
|
||||
|
||||
ch.buffer = &elem
|
||||
case output <- next:
|
||||
ch.buffer = nil
|
||||
}
|
||||
}
|
||||
|
||||
if ch.buffer == nil {
|
||||
output = nil
|
||||
continue
|
||||
}
|
||||
|
||||
output = ch.output
|
||||
next = *ch.buffer
|
||||
}
|
||||
|
||||
close(ch.output)
|
||||
}
|
||||
@@ -12,8 +12,6 @@ import (
|
||||
type connectCert struct {
|
||||
root []string
|
||||
leaf keyPair
|
||||
// err is used to propagate to the caller (Provide) any error occurring within the certificate watcher goroutines.
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *connectCert) getRoot() []traefiktls.FileOrContent {
|
||||
|
||||
@@ -56,10 +56,12 @@ type Provider struct {
|
||||
ConnectByDefault bool `description:"Consider every service as Connect capable by default." json:"connectByDefault,omitempty" toml:"connectByDefault,omitempty" yaml:"connectByDefault,omitempty" export:"true"`
|
||||
ServiceName string `description:"Name of the Traefik service in Consul Catalog (needs to be registered via the orchestrator or manually)." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"`
|
||||
Namespace string `description:"Sets the namespace used to discover services (Consul Enterprise only)." json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty" export:"true"`
|
||||
Watch bool `description:"Watch Consul API events." json:"watch,omitempty" toml:"watch,omitempty" yaml:"watch,omitempty" export:"true"`
|
||||
|
||||
client *api.Client
|
||||
defaultRuleTpl *template.Template
|
||||
certChan chan *connectCert
|
||||
client *api.Client
|
||||
defaultRuleTpl *template.Template
|
||||
certChan chan *connectCert
|
||||
watchServicesChan chan struct{}
|
||||
}
|
||||
|
||||
// EndpointConfig holds configurations of the endpoint.
|
||||
@@ -98,7 +100,9 @@ func (p *Provider) Init() error {
|
||||
}
|
||||
|
||||
p.defaultRuleTpl = defaultRuleTpl
|
||||
p.certChan = make(chan *connectCert)
|
||||
p.certChan = make(chan *connectCert, 1)
|
||||
p.watchServicesChan = make(chan struct{}, 1)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -107,27 +111,31 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
||||
var err error
|
||||
p.client, err = createClient(p.Namespace, p.Endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create consul client: %w", err)
|
||||
return fmt.Errorf("failed to create consul client: %w", err)
|
||||
}
|
||||
|
||||
if p.ConnectAware {
|
||||
leafWatcher, rootWatcher, err := p.createConnectTLSWatchers()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create consul watch plans: %w", err)
|
||||
}
|
||||
pool.GoCtx(func(routineCtx context.Context) {
|
||||
p.watchConnectTLS(routineCtx, leafWatcher, rootWatcher)
|
||||
})
|
||||
}
|
||||
|
||||
var certInfo *connectCert
|
||||
|
||||
pool.GoCtx(func(routineCtx context.Context) {
|
||||
ctxLog := log.With(routineCtx, log.Str(log.ProviderName, "consulcatalog"))
|
||||
logger := log.FromContext(ctxLog)
|
||||
|
||||
operation := func() error {
|
||||
var err error
|
||||
ctx, cancel := context.WithCancel(ctxLog)
|
||||
|
||||
// When the operation terminates, we want to clean up the
|
||||
// goroutines in watchConnectTLS and watchServices.
|
||||
defer cancel()
|
||||
|
||||
errChan := make(chan error, 2)
|
||||
|
||||
if p.ConnectAware {
|
||||
go func() {
|
||||
if err := p.watchConnectTLS(ctx); err != nil {
|
||||
errChan <- fmt.Errorf("failed to watch connect certificates: %w", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
var certInfo *connectCert
|
||||
|
||||
// If we are running in connect aware mode then we need to
|
||||
// make sure that we obtain the certificates before starting
|
||||
@@ -137,37 +145,46 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
||||
if p.ConnectAware && !certInfo.isReady() {
|
||||
logger.Infof("Waiting for Connect certificate before building first configuration")
|
||||
select {
|
||||
case <-routineCtx.Done():
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
|
||||
case err = <-errChan:
|
||||
return err
|
||||
|
||||
case certInfo = <-p.certChan:
|
||||
if certInfo.err != nil {
|
||||
return backoff.Permanent(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get configuration at the provider's startup.
|
||||
err = p.loadConfiguration(ctxLog, certInfo, configurationChan)
|
||||
if err != nil {
|
||||
if err = p.loadConfiguration(ctx, certInfo, configurationChan); err != nil {
|
||||
return fmt.Errorf("failed to get consul catalog data: %w", err)
|
||||
}
|
||||
|
||||
// Periodic refreshes.
|
||||
ticker := time.NewTicker(time.Duration(p.RefreshInterval))
|
||||
defer ticker.Stop()
|
||||
go func() {
|
||||
// Periodic refreshes.
|
||||
if !p.Watch {
|
||||
repeatSend(ctx, time.Duration(p.RefreshInterval), p.watchServicesChan)
|
||||
return
|
||||
}
|
||||
|
||||
if err := p.watchServices(ctx); err != nil {
|
||||
errChan <- fmt.Errorf("failed to watch services: %w", err)
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-routineCtx.Done():
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-ticker.C:
|
||||
|
||||
case err = <-errChan:
|
||||
return err
|
||||
|
||||
case certInfo = <-p.certChan:
|
||||
if certInfo.err != nil {
|
||||
return backoff.Permanent(err)
|
||||
}
|
||||
case <-p.watchServicesChan:
|
||||
}
|
||||
err = p.loadConfiguration(ctxLog, certInfo, configurationChan)
|
||||
if err != nil {
|
||||
|
||||
if err = p.loadConfiguration(ctx, certInfo, configurationChan); err != nil {
|
||||
return fmt.Errorf("failed to refresh consul catalog data: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -330,6 +347,67 @@ func (p *Provider) fetchService(ctx context.Context, name string, connectEnabled
|
||||
return consulServices, statuses, err
|
||||
}
|
||||
|
||||
// watchServices watches for update events of the services list and statuses,
|
||||
// and transmits them to the caller through the p.watchServicesChan.
|
||||
func (p *Provider) watchServices(ctx context.Context) error {
|
||||
servicesWatcher, err := watch.Parse(map[string]interface{}{"type": "services"})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create services watcher plan: %w", err)
|
||||
}
|
||||
|
||||
servicesWatcher.HybridHandler = func(_ watch.BlockingParamVal, _ interface{}) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case p.watchServicesChan <- struct{}{}:
|
||||
default:
|
||||
// Event chan is full, discard event.
|
||||
}
|
||||
}
|
||||
|
||||
checksWatcher, err := watch.Parse(map[string]interface{}{"type": "checks"})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create checks watcher plan: %w", err)
|
||||
}
|
||||
|
||||
checksWatcher.HybridHandler = func(_ watch.BlockingParamVal, _ interface{}) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case p.watchServicesChan <- struct{}{}:
|
||||
default:
|
||||
// Event chan is full, discard event.
|
||||
}
|
||||
}
|
||||
|
||||
logger := hclog.New(&hclog.LoggerOptions{
|
||||
Name: "consulcatalog",
|
||||
Level: hclog.LevelFromString(logrus.GetLevel().String()),
|
||||
JSONFormat: true,
|
||||
})
|
||||
|
||||
errChan := make(chan error, 2)
|
||||
|
||||
defer func() {
|
||||
servicesWatcher.Stop()
|
||||
checksWatcher.Stop()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
errChan <- servicesWatcher.RunWithClientAndHclog(p.client, logger)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
errChan <- checksWatcher.RunWithClientAndHclog(p.client, logger)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
|
||||
case err = <-errChan:
|
||||
return fmt.Errorf("services or checks watcher terminated: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
func rootsWatchHandler(ctx context.Context, dest chan<- []string) func(watch.BlockingParamVal, interface{}) {
|
||||
return func(_ watch.BlockingParamVal, raw interface{}) {
|
||||
if raw == nil {
|
||||
@@ -348,7 +426,10 @@ func rootsWatchHandler(ctx context.Context, dest chan<- []string) func(watch.Blo
|
||||
roots = append(roots, root.RootCertPEM)
|
||||
}
|
||||
|
||||
dest <- roots
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case dest <- roots:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,65 +451,59 @@ func leafWatcherHandler(ctx context.Context, dest chan<- keyPair) func(watch.Blo
|
||||
return
|
||||
}
|
||||
|
||||
dest <- keyPair{
|
||||
kp := keyPair{
|
||||
cert: v.CertPEM,
|
||||
key: v.PrivateKeyPEM,
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case dest <- kp:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) createConnectTLSWatchers() (*watch.Plan, *watch.Plan, error) {
|
||||
// watchConnectTLS watches for updates of the root certificate or the leaf
|
||||
// certificate, and transmits them to the caller via p.certChan.
|
||||
func (p *Provider) watchConnectTLS(ctx context.Context) error {
|
||||
leafChan := make(chan keyPair)
|
||||
leafWatcher, err := watch.Parse(map[string]interface{}{
|
||||
"type": "connect_leaf",
|
||||
"service": p.ServiceName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create leaf cert watcher plan: %w", err)
|
||||
return fmt.Errorf("failed to create leaf cert watcher plan: %w", err)
|
||||
}
|
||||
leafWatcher.HybridHandler = leafWatcherHandler(ctx, leafChan)
|
||||
|
||||
rootWatcher, err := watch.Parse(map[string]interface{}{
|
||||
rootsChan := make(chan []string)
|
||||
rootsWatcher, err := watch.Parse(map[string]interface{}{
|
||||
"type": "connect_roots",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create root cert watcher plan: %w", err)
|
||||
return fmt.Errorf("failed to create roots cert watcher plan: %w", err)
|
||||
}
|
||||
rootsWatcher.HybridHandler = rootsWatchHandler(ctx, rootsChan)
|
||||
|
||||
return leafWatcher, rootWatcher, nil
|
||||
}
|
||||
|
||||
// watchConnectTLS watches for updates of the root certificate or the leaf
|
||||
// certificate, and transmits them to the caller via p.certChan. Any error is also
|
||||
// propagated up through p.certChan, in connectCert.err.
|
||||
func (p *Provider) watchConnectTLS(ctx context.Context, leafWatcher *watch.Plan, rootWatcher *watch.Plan) {
|
||||
ctxLog := log.With(ctx, log.Str(log.ProviderName, "consulcatalog"))
|
||||
logger := log.FromContext(ctxLog)
|
||||
|
||||
leafChan := make(chan keyPair)
|
||||
rootChan := make(chan []string)
|
||||
|
||||
leafWatcher.HybridHandler = leafWatcherHandler(ctx, leafChan)
|
||||
rootWatcher.HybridHandler = rootsWatchHandler(ctx, rootChan)
|
||||
|
||||
logOpts := &hclog.LoggerOptions{
|
||||
hclogger := hclog.New(&hclog.LoggerOptions{
|
||||
Name: "consulcatalog",
|
||||
Level: hclog.LevelFromString(logrus.GetLevel().String()),
|
||||
JSONFormat: true,
|
||||
}
|
||||
})
|
||||
|
||||
hclogger := hclog.New(logOpts)
|
||||
errChan := make(chan error, 2)
|
||||
|
||||
go func() {
|
||||
err := leafWatcher.RunWithClientAndHclog(p.client, hclogger)
|
||||
if err != nil {
|
||||
p.certChan <- &connectCert{err: err}
|
||||
}
|
||||
defer func() {
|
||||
leafWatcher.Stop()
|
||||
rootsWatcher.Stop()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
err := rootWatcher.RunWithClientAndHclog(p.client, hclogger)
|
||||
if err != nil {
|
||||
p.certChan <- &connectCert{err: err}
|
||||
}
|
||||
errChan <- leafWatcher.RunWithClientAndHclog(p.client, hclogger)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
errChan <- rootsWatcher.RunWithClientAndHclog(p.client, hclogger)
|
||||
}()
|
||||
|
||||
var (
|
||||
@@ -440,20 +515,28 @@ func (p *Provider) watchConnectTLS(ctx context.Context, leafWatcher *watch.Plan,
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
leafWatcher.Stop()
|
||||
rootWatcher.Stop()
|
||||
return
|
||||
case rootCerts = <-rootChan:
|
||||
return nil
|
||||
|
||||
case err := <-errChan:
|
||||
return fmt.Errorf("leaf or roots watcher terminated: %w", err)
|
||||
|
||||
case rootCerts = <-rootsChan:
|
||||
case leafCerts = <-leafChan:
|
||||
}
|
||||
|
||||
newCertInfo := &connectCert{
|
||||
root: rootCerts,
|
||||
leaf: leafCerts,
|
||||
}
|
||||
if newCertInfo.isReady() && !newCertInfo.equals(certInfo) {
|
||||
logger.Debugf("Updating connect certs for service %s", p.ServiceName)
|
||||
log.FromContext(ctx).Debugf("Updating connect certs for service %s", p.ServiceName)
|
||||
|
||||
certInfo = newCertInfo
|
||||
p.certChan <- newCertInfo
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case p.certChan <- newCertInfo:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -487,3 +570,25 @@ func createClient(namespace string, endpoint *EndpointConfig) (*api.Client, erro
|
||||
|
||||
return api.NewClient(&config)
|
||||
}
|
||||
|
||||
func repeatSend(ctx context.Context, interval time.Duration, c chan<- struct{}) {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
||||
case <-ticker.C:
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
||||
case c <- struct{}{}:
|
||||
default:
|
||||
// Chan is full, discard event.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
147
pkg/provider/hub/handler.go
Normal file
147
pkg/provider/hub/handler.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package hub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
mux *http.ServeMux
|
||||
|
||||
client http.Client
|
||||
|
||||
entryPoint string
|
||||
port int
|
||||
tlsCfg *TLS
|
||||
|
||||
// Accessed atomically.
|
||||
lastCfgUnixNano int64
|
||||
|
||||
cfgChan chan<- dynamic.Message
|
||||
}
|
||||
|
||||
func newHandler(entryPoint string, port int, cfgChan chan<- dynamic.Message, tlsCfg *TLS, client http.Client) http.Handler {
|
||||
h := &handler{
|
||||
mux: http.NewServeMux(),
|
||||
entryPoint: entryPoint,
|
||||
port: port,
|
||||
cfgChan: cfgChan,
|
||||
tlsCfg: tlsCfg,
|
||||
client: client,
|
||||
}
|
||||
|
||||
h.mux.HandleFunc("/config", h.handleConfig)
|
||||
h.mux.HandleFunc("/discover-ip", h.handleDiscoverIP)
|
||||
h.mux.HandleFunc("/state", h.handleState)
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
type configRequest struct {
|
||||
UnixNano int64 `json:"unixNano"`
|
||||
Configuration *dynamic.Configuration `json:"configuration"`
|
||||
}
|
||||
|
||||
func (h *handler) handleConfig(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodPost {
|
||||
http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
payload := &configRequest{Configuration: emptyDynamicConfiguration()}
|
||||
if err := json.NewDecoder(req.Body).Decode(payload); err != nil {
|
||||
err = fmt.Errorf("decoding config request: %w", err)
|
||||
log.WithoutContext().Errorf("Handling config: %v", err)
|
||||
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
cfg := payload.Configuration
|
||||
patchDynamicConfiguration(cfg, h.entryPoint, h.port, h.tlsCfg)
|
||||
|
||||
// We can safely drop messages here if the other end is not ready to receive them
|
||||
// as the agent will re-apply the same configuration.
|
||||
select {
|
||||
case h.cfgChan <- dynamic.Message{ProviderName: "hub", Configuration: cfg}:
|
||||
atomic.StoreInt64(&h.lastCfgUnixNano, payload.UnixNano)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) handleDiscoverIP(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodGet {
|
||||
http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
xff := req.Header.Get("X-Forwarded-For")
|
||||
port := req.URL.Query().Get("port")
|
||||
nonce := req.URL.Query().Get("nonce")
|
||||
|
||||
if err := h.doDiscoveryReq(req.Context(), xff, port, nonce); err != nil {
|
||||
err = fmt.Errorf("doing discovery request: %w", err)
|
||||
log.WithoutContext().Errorf("Handling IP discovery: %v", err)
|
||||
http.Error(rw, http.StatusText(http.StatusBadGateway), http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(rw).Encode(xff); err != nil {
|
||||
err = fmt.Errorf("encoding discover ip response: %w", err)
|
||||
log.WithoutContext().Errorf("Handling IP discovery: %v", err)
|
||||
http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) doDiscoveryReq(ctx context.Context, ip, port, nonce string) error {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://%s", net.JoinHostPort(ip, port)), http.NoBody)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating request: %w", err)
|
||||
}
|
||||
|
||||
q := make(url.Values)
|
||||
q.Set("nonce", nonce)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
req.Host = "agent.traefik"
|
||||
|
||||
resp, err := h.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("doing request: %w", err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type stateResponse struct {
|
||||
LastConfigUnixNano int64 `json:"lastConfigUnixNano"`
|
||||
}
|
||||
|
||||
func (h *handler) handleState(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodGet {
|
||||
http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
resp := stateResponse{
|
||||
LastConfigUnixNano: atomic.LoadInt64(&h.lastCfgUnixNano),
|
||||
}
|
||||
if err := json.NewEncoder(rw).Encode(resp); err != nil {
|
||||
err = fmt.Errorf("encoding last config received response: %w", err)
|
||||
log.WithoutContext().Errorf("Handling state: %v", err)
|
||||
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
h.mux.ServeHTTP(rw, req)
|
||||
}
|
||||
168
pkg/provider/hub/handler_test.go
Normal file
168
pkg/provider/hub/handler_test.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package hub
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/tls/generate"
|
||||
)
|
||||
|
||||
func TestHandleConfig(t *testing.T) {
|
||||
cfgChan := make(chan dynamic.Message, 1)
|
||||
|
||||
client, err := createAgentClient(&TLS{Insecure: true})
|
||||
require.NoError(t, err)
|
||||
h := newHandler("traefik-hub-ep", 42, cfgChan, nil, client)
|
||||
|
||||
cfg := emptyDynamicConfiguration()
|
||||
cfg.HTTP.Routers["foo"] = &dynamic.Router{
|
||||
EntryPoints: []string{"ep"},
|
||||
Service: "bar",
|
||||
Rule: "Host(`foo.com`)",
|
||||
}
|
||||
|
||||
req := configRequest{Configuration: cfg}
|
||||
|
||||
b, err := json.Marshal(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
server := httptest.NewServer(h)
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
resp, err := http.Post(server.URL+"/config", "application/json", bytes.NewReader(b))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
select {
|
||||
case gotCfgRaw := <-cfgChan:
|
||||
patchDynamicConfiguration(cfg, "traefik-hub-ep", 42, nil)
|
||||
assert.Equal(t, cfg, gotCfgRaw.Configuration)
|
||||
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("Configuration not received")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandle_Config_MethodNotAllowed(t *testing.T) {
|
||||
cfgChan := make(chan dynamic.Message, 1)
|
||||
client, err := createAgentClient(&TLS{Insecure: true})
|
||||
require.NoError(t, err)
|
||||
h := newHandler("traefik-hub-ep", 42, cfgChan, nil, client)
|
||||
|
||||
server := httptest.NewServer(h)
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
resp, err := http.Get(server.URL + "/config")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestHandle_DiscoverIP(t *testing.T) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
port := listener.Addr().(*net.TCPAddr).Port
|
||||
nonce := "XVlBzgbaiCMRAjWw"
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
var handlerCallCount int
|
||||
mux.HandleFunc("/", func(_ http.ResponseWriter, req *http.Request) {
|
||||
handlerCallCount++
|
||||
assert.Equal(t, nonce, req.URL.Query().Get("nonce"))
|
||||
})
|
||||
|
||||
certificate, err := generate.DefaultCertificate()
|
||||
require.NoError(t, err)
|
||||
agentServer := &http.Server{
|
||||
Handler: mux,
|
||||
TLSConfig: &tls.Config{
|
||||
Certificates: []tls.Certificate{*certificate},
|
||||
InsecureSkipVerify: true,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
},
|
||||
}
|
||||
t.Cleanup(func() { _ = agentServer.Close() })
|
||||
|
||||
rdy := make(chan struct{})
|
||||
|
||||
go func(s *http.Server) {
|
||||
close(rdy)
|
||||
if err = s.ServeTLS(listener, "", ""); errors.Is(err, http.ErrServerClosed) {
|
||||
return
|
||||
}
|
||||
}(agentServer)
|
||||
|
||||
<-rdy
|
||||
|
||||
cfgChan := make(chan dynamic.Message, 1)
|
||||
client, err := createAgentClient(&TLS{Insecure: true})
|
||||
require.NoError(t, err)
|
||||
h := newHandler("traefik-hub-ep", 42, cfgChan, nil, client)
|
||||
|
||||
traefikServer := httptest.NewServer(h)
|
||||
t.Cleanup(traefikServer.Close)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, traefikServer.URL+"/discover-ip", http.NoBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
q := make(url.Values)
|
||||
q.Set("port", strconv.Itoa(port))
|
||||
q.Set("nonce", nonce)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
// Simulate a call from behind different proxies.
|
||||
req.Header.Add("X-Forwarded-For", "127.0.0.1")
|
||||
req.Header.Add("X-Forwarded-For", "10.10.0.13")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
err = resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
assert.Equal(t, 1, handlerCallCount)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var ip string
|
||||
err = json.NewDecoder(resp.Body).Decode(&ip)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "127.0.0.1", ip)
|
||||
}
|
||||
|
||||
func TestHandle_DiscoverIP_MethodNotAllowed(t *testing.T) {
|
||||
cfgChan := make(chan dynamic.Message, 1)
|
||||
client, err := createAgentClient(&TLS{Insecure: true})
|
||||
require.NoError(t, err)
|
||||
h := newHandler("traefik-hub-ep", 42, cfgChan, nil, client)
|
||||
|
||||
server := httptest.NewServer(h)
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
resp, err := http.Post(server.URL+"/discover-ip", "", http.NoBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode)
|
||||
}
|
||||
217
pkg/provider/hub/hub.go
Normal file
217
pkg/provider/hub/hub.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package hub
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/provider"
|
||||
"github.com/traefik/traefik/v2/pkg/safe"
|
||||
ttls "github.com/traefik/traefik/v2/pkg/tls"
|
||||
)
|
||||
|
||||
var _ provider.Provider = (*Provider)(nil)
|
||||
|
||||
// Entrypoints created for Hub.
|
||||
const (
|
||||
APIEntrypoint = "traefikhub-api"
|
||||
TunnelEntrypoint = "traefikhub-tunl"
|
||||
)
|
||||
|
||||
// Provider holds configurations of the provider.
|
||||
type Provider struct {
|
||||
TLS *TLS `description:"TLS configuration for mTLS communication between Traefik and Hub Agent." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
|
||||
|
||||
server *http.Server
|
||||
}
|
||||
|
||||
// TLS configures the mTLS connection between Traefik Proxy and the Traefik Hub Agent.
|
||||
type TLS struct {
|
||||
Insecure bool `description:"Enables an insecure TLS connection that uses default credentials, and which has no peer authentication between Traefik Proxy and the Traefik Hub Agent." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"`
|
||||
CA ttls.FileOrContent `description:"The certificate authority authenticates the Traefik Hub Agent certificate." json:"ca,omitempty" toml:"ca,omitempty" yaml:"ca,omitempty" loggable:"false"`
|
||||
Cert ttls.FileOrContent `description:"The TLS certificate for Traefik Proxy as a TLS client." json:"cert,omitempty" toml:"cert,omitempty" yaml:"cert,omitempty" loggable:"false"`
|
||||
Key ttls.FileOrContent `description:"The TLS key for Traefik Proxy as a TLS client." json:"key,omitempty" toml:"key,omitempty" yaml:"key,omitempty" loggable:"false"`
|
||||
}
|
||||
|
||||
// Init the provider.
|
||||
func (p *Provider) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Provide allows the hub provider to provide configurations to traefik using the given configuration channel.
|
||||
func (p *Provider) Provide(configurationChan chan<- dynamic.Message, _ *safe.Pool) error {
|
||||
if p.TLS == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return fmt.Errorf("listener: %w", err)
|
||||
}
|
||||
|
||||
port := listener.Addr().(*net.TCPAddr).Port
|
||||
|
||||
client, err := createAgentClient(p.TLS)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating Hub Agent HTTP client: %w", err)
|
||||
}
|
||||
|
||||
p.server = &http.Server{Handler: newHandler(APIEntrypoint, port, configurationChan, p.TLS, client)}
|
||||
|
||||
// TODO: this is going to be leaky (because no context to make it terminate)
|
||||
// if/when Provide lifecycle differs with Traefik lifecycle.
|
||||
go func() {
|
||||
if err = p.server.Serve(listener); err != nil {
|
||||
log.WithoutContext().WithField(log.ProviderName, "hub").Errorf("Unexpected error while running server: %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
exposeAPIAndMetrics(configurationChan, APIEntrypoint, port, p.TLS)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func exposeAPIAndMetrics(cfgChan chan<- dynamic.Message, ep string, port int, tlsCfg *TLS) {
|
||||
cfg := emptyDynamicConfiguration()
|
||||
|
||||
patchDynamicConfiguration(cfg, ep, port, tlsCfg)
|
||||
|
||||
cfgChan <- dynamic.Message{ProviderName: "hub", Configuration: cfg}
|
||||
}
|
||||
|
||||
func patchDynamicConfiguration(cfg *dynamic.Configuration, ep string, port int, tlsCfg *TLS) {
|
||||
cfg.HTTP.Routers["traefik-hub-agent-api"] = &dynamic.Router{
|
||||
EntryPoints: []string{ep},
|
||||
Service: "api@internal",
|
||||
Rule: "Host(`proxy.traefik`) && PathPrefix(`/api`)",
|
||||
}
|
||||
cfg.HTTP.Routers["traefik-hub-agent-metrics"] = &dynamic.Router{
|
||||
EntryPoints: []string{ep},
|
||||
Service: "prometheus@internal",
|
||||
Rule: "Host(`proxy.traefik`) && PathPrefix(`/metrics`)",
|
||||
}
|
||||
|
||||
cfg.HTTP.Routers["traefik-hub-agent-service"] = &dynamic.Router{
|
||||
EntryPoints: []string{ep},
|
||||
Service: "traefik-hub-agent-service",
|
||||
Rule: "Host(`proxy.traefik`) && PathPrefix(`/config`, `/discover-ip`, `/state`)",
|
||||
}
|
||||
|
||||
cfg.HTTP.Services["traefik-hub-agent-service"] = &dynamic.Service{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: fmt.Sprintf("http://127.0.0.1:%d", port),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if tlsCfg == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if tlsCfg.Insecure {
|
||||
cfg.TLS.Options["traefik-hub"] = ttls.Options{
|
||||
MinVersion: "VersionTLS13",
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
cfg.TLS.Options["traefik-hub"] = ttls.Options{
|
||||
ClientAuth: ttls.ClientAuth{
|
||||
CAFiles: []ttls.FileOrContent{tlsCfg.CA},
|
||||
ClientAuthType: "RequireAndVerifyClientCert",
|
||||
},
|
||||
SniStrict: true,
|
||||
MinVersion: "VersionTLS13",
|
||||
}
|
||||
|
||||
cfg.TLS.Certificates = append(cfg.TLS.Certificates, &ttls.CertAndStores{
|
||||
Certificate: ttls.Certificate{
|
||||
CertFile: tlsCfg.Cert,
|
||||
KeyFile: tlsCfg.Key,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func emptyDynamicConfiguration() *dynamic.Configuration {
|
||||
return &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: make(map[string]*dynamic.Router),
|
||||
Middlewares: make(map[string]*dynamic.Middleware),
|
||||
Services: make(map[string]*dynamic.Service),
|
||||
ServersTransports: make(map[string]*dynamic.ServersTransport),
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: make(map[string]*dynamic.TCPRouter),
|
||||
Services: make(map[string]*dynamic.TCPService),
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
Stores: make(map[string]ttls.Store),
|
||||
Options: make(map[string]ttls.Options),
|
||||
},
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: make(map[string]*dynamic.UDPRouter),
|
||||
Services: make(map[string]*dynamic.UDPService),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createAgentClient(tlsCfg *TLS) (http.Client, error) {
|
||||
var client http.Client
|
||||
if tlsCfg.Insecure {
|
||||
client.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
},
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
caContent, err := tlsCfg.CA.Read()
|
||||
if err != nil {
|
||||
return client, fmt.Errorf("reading CA: %w", err)
|
||||
}
|
||||
|
||||
roots := x509.NewCertPool()
|
||||
if ok := roots.AppendCertsFromPEM(caContent); !ok {
|
||||
return client, errors.New("appending CA error")
|
||||
}
|
||||
|
||||
certContent, err := tlsCfg.Cert.Read()
|
||||
if err != nil {
|
||||
return client, fmt.Errorf("reading Cert: %w", err)
|
||||
}
|
||||
keyContent, err := tlsCfg.Key.Read()
|
||||
if err != nil {
|
||||
return client, fmt.Errorf("reading Key: %w", err)
|
||||
}
|
||||
|
||||
certificate, err := tls.X509KeyPair(certContent, keyContent)
|
||||
if err != nil {
|
||||
return client, fmt.Errorf("creating key pair: %w", err)
|
||||
}
|
||||
|
||||
// mTLS
|
||||
client.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: roots,
|
||||
Certificates: []tls.Certificate{certificate},
|
||||
ServerName: "agent.traefik",
|
||||
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
},
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
@@ -229,3 +229,26 @@ subsets:
|
||||
ports:
|
||||
- name: web
|
||||
port: 80
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: whoami-without-endpoints-subsets
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: myapp
|
||||
port: 80
|
||||
|
||||
selector:
|
||||
app: traefiklabs
|
||||
task: whoami
|
||||
|
||||
---
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: whoami-without-endpoints-subsets
|
||||
namespace: default
|
||||
|
||||
@@ -238,3 +238,26 @@ subsets:
|
||||
ports:
|
||||
- name: myapp
|
||||
port: 8000
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: whoamitcp-without-endpoints-subsets
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: myapp
|
||||
port: 8000
|
||||
|
||||
selector:
|
||||
app: traefiklabs
|
||||
task: whoamitcp
|
||||
|
||||
---
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: whoamitcp-without-endpoints-subsets
|
||||
namespace: default
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRouteTCP
|
||||
metadata:
|
||||
name: test.route
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- foo
|
||||
|
||||
routes:
|
||||
- match: HostSNI(`foo.com`)
|
||||
services:
|
||||
- name: whoamitcp-without-endpoints-subsets
|
||||
port: 8000
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user