mirror of
https://github.com/containous/traefik.git
synced 2025-09-09 17:44:30 +03:00
Compare commits
55 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1f81260694 | ||
|
cbcd999257 | ||
|
a0724fd1ca | ||
|
3fe9c9ab58 | ||
|
fb3839e096 | ||
|
eef3ca0295 | ||
|
c9dc0226fd | ||
|
d2e458f673 | ||
|
e0f265db15 | ||
|
39a3cefc21 | ||
|
89db08eb93 | ||
|
f40cf2cd8e | ||
|
50bb69b796 | ||
|
a7d7c2b98b | ||
|
8dfc0d9dda | ||
|
0e6dce7093 | ||
|
ef38810425 | ||
|
ecd51a1428 | ||
|
78097b96c9 | ||
|
2af8589afd | ||
|
efcc9d51d4 | ||
|
a87c104172 | ||
|
b2c59be8de | ||
|
2685e06528 | ||
|
ba49012447 | ||
|
407eda0ba0 | ||
|
5b1dc0bfbd | ||
|
00db3a0922 | ||
|
abdb3b9475 | ||
|
9761161163 | ||
|
e5104021b1 | ||
|
42a8d84a1f | ||
|
3fd330c2fb | ||
|
8f340afca1 | ||
|
e28d9426b9 | ||
|
b3078b75cd | ||
|
424b97994e | ||
|
1db22f4a1b | ||
|
7afd2dbd20 | ||
|
cdb2446e32 | ||
|
ac8c9215cd | ||
|
dfca01e469 | ||
|
587d3f9012 | ||
|
e30ab07439 | ||
|
e6e026f420 | ||
|
2036518813 | ||
|
7536f5e83c | ||
|
229402594f | ||
|
97873ddb5d | ||
|
dbf303d5d6 | ||
|
7346b3e326 | ||
|
93cf947e2a | ||
|
c37ad5c8bf | ||
|
80a68de91b | ||
|
6d3bad1ae0 |
64
CHANGELOG.md
64
CHANGELOG.md
@@ -1,3 +1,67 @@
|
|||||||
|
## [v2.0.7](https://github.com/containous/traefik/tree/v2.0.7) (2019-12-09)
|
||||||
|
[All Commits](https://github.com/containous/traefik/compare/v2.0.6...v2.0.7)
|
||||||
|
|
||||||
|
**Bug fixes:**
|
||||||
|
- **[logs,middleware]** Remove mirroring impact in accesslog ([#5967](https://github.com/containous/traefik/pull/5967) by [juliens](https://github.com/juliens))
|
||||||
|
- **[middleware]** fix: PassClientTLSCert middleware separators and formatting ([#5921](https://github.com/containous/traefik/pull/5921) by [ldez](https://github.com/ldez))
|
||||||
|
- **[server]** Do not stop to listen on tcp listeners on temporary errors ([#5935](https://github.com/containous/traefik/pull/5935) by [skwair](https://github.com/skwair))
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
- **[acme,k8s/crd,k8s/ingress]** Document LE caveats with Kubernetes on v2 ([#5902](https://github.com/containous/traefik/pull/5902) by [dtomcej](https://github.com/dtomcej))
|
||||||
|
- **[acme]** The Cloudflare hint for the GLOBAL API KEY for CF MAIL/API_KEY ([#5964](https://github.com/containous/traefik/pull/5964) by [EugenMayer](https://github.com/EugenMayer))
|
||||||
|
- **[acme]** Improve documentation for ACME/Let's Encrypt ([#5819](https://github.com/containous/traefik/pull/5819) by [dduportal](https://github.com/dduportal))
|
||||||
|
- **[file]** Improve documentation on file provider limitations with file system notifications ([#5939](https://github.com/containous/traefik/pull/5939) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||||
|
- Make trailing slash more prominent for the "secure dashboard setup" too ([#5963](https://github.com/containous/traefik/pull/5963) by [EugenMayer](https://github.com/EugenMayer))
|
||||||
|
- Fix Docker example in "Strip and Rewrite Path Prefixes" in migration guide ([#5949](https://github.com/containous/traefik/pull/5949) by [q210](https://github.com/q210))
|
||||||
|
- readme: Fix link to file backend/provider documentation ([#5945](https://github.com/containous/traefik/pull/5945) by [hartwork](https://github.com/hartwork))
|
||||||
|
|
||||||
|
## [v2.0.6](https://github.com/containous/traefik/tree/v2.0.6) (2019-12-02)
|
||||||
|
[All Commits](https://github.com/containous/traefik/compare/v2.0.5...v2.0.6)
|
||||||
|
|
||||||
|
**Bug fixes:**
|
||||||
|
- **[acme]** Update go-acme/lego to 3.2.0 ([#5839](https://github.com/containous/traefik/pull/5839) by [kolaente](https://github.com/kolaente))
|
||||||
|
- **[cli,healthcheck]** Uses, if it exists, the ping entry point provided in the static configuration ([#5867](https://github.com/containous/traefik/pull/5867) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||||
|
- **[healthcheck]** Healthcheck managed for all related services ([#5860](https://github.com/containous/traefik/pull/5860) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||||
|
- **[logs,middleware]** Do not give responsewriter or its headers to asynchronous logging goroutine ([#5840](https://github.com/containous/traefik/pull/5840) by [mpl](https://github.com/mpl))
|
||||||
|
- **[middleware]** X-Forwarded-Proto must not skip the redirection. ([#5836](https://github.com/containous/traefik/pull/5836) by [ldez](https://github.com/ldez))
|
||||||
|
- **[middleware]** fix: location header rewrite. ([#5835](https://github.com/containous/traefik/pull/5835) by [ldez](https://github.com/ldez))
|
||||||
|
- **[middleware]** Remove Request Headers CORS Preflight Requirement ([#5903](https://github.com/containous/traefik/pull/5903) by [dtomcej](https://github.com/dtomcej))
|
||||||
|
- **[rancher]** Change service name in rancher provider to make webui service details view work ([#5895](https://github.com/containous/traefik/pull/5895) by [SantoDE](https://github.com/SantoDE))
|
||||||
|
- **[tracing]** Fix extraction for zipkin tracing ([#5920](https://github.com/containous/traefik/pull/5920) by [jcchavezs](https://github.com/jcchavezs))
|
||||||
|
- **[webui]** Web UI: Avoid unnecessary duplicated api calls ([#5884](https://github.com/containous/traefik/pull/5884) by [matthieuh](https://github.com/matthieuh))
|
||||||
|
- **[webui]** Web UI: Avoid some router properties to overflow their container ([#5872](https://github.com/containous/traefik/pull/5872) by [matthieuh](https://github.com/matthieuh))
|
||||||
|
- **[webui]** Web UI: Fix displayed tcp service details ([#5868](https://github.com/containous/traefik/pull/5868) by [matthieuh](https://github.com/matthieuh))
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
- **[acme]** doc: fix wrong acme information ([#5837](https://github.com/containous/traefik/pull/5837) by [ldez](https://github.com/ldez))
|
||||||
|
- **[docker,docker/swarm]** Add Swarm section to the Docker Provider Documentation ([#5874](https://github.com/containous/traefik/pull/5874) by [dduportal](https://github.com/dduportal))
|
||||||
|
- **[docker]** Update router entrypoint example ([#5766](https://github.com/containous/traefik/pull/5766) by [woto](https://github.com/woto))
|
||||||
|
- **[k8s/helm]** Mention the experimental Helm Chart in the installation section of documentation ([#5879](https://github.com/containous/traefik/pull/5879) by [dduportal](https://github.com/dduportal))
|
||||||
|
- doc: remove double quotes on CLI flags. ([#5862](https://github.com/containous/traefik/pull/5862) by [ldez](https://github.com/ldez))
|
||||||
|
- Fixed spelling error ([#5834](https://github.com/containous/traefik/pull/5834) by [blakebuthod](https://github.com/blakebuthod))
|
||||||
|
- Add back the security section from v1 ([#5832](https://github.com/containous/traefik/pull/5832) by [pascalandy](https://github.com/pascalandy))
|
||||||
|
|
||||||
|
## [v2.0.5](https://github.com/containous/traefik/tree/v2.0.5) (2019-11-14)
|
||||||
|
[All Commits](https://github.com/containous/traefik/compare/v2.0.4...v2.0.5)
|
||||||
|
|
||||||
|
**Bug fixes:**
|
||||||
|
- **[metrics]** fix: metric with services LB. ([#5759](https://github.com/containous/traefik/pull/5759) by [ldez](https://github.com/ldez))
|
||||||
|
- **[middleware]** fix: stripPrefix middleware with empty resulting path. ([#5806](https://github.com/containous/traefik/pull/5806) by [ldez](https://github.com/ldez))
|
||||||
|
- **[middleware]** Fix rate limiting and SSE ([#5737](https://github.com/containous/traefik/pull/5737) by [sylr](https://github.com/sylr))
|
||||||
|
- **[tracing]** Upgrades zipkin library to avoid errors when using textMap. ([#5754](https://github.com/containous/traefik/pull/5754) by [jcchavezs](https://github.com/jcchavezs))
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
- **[acme,cluster]** Update ACME storage docs to remove reference to KV store in CE ([#5433](https://github.com/containous/traefik/pull/5433) by [bradjones1](https://github.com/bradjones1))
|
||||||
|
- **[api]** docs: remove field api.entryPoint ([#5776](https://github.com/containous/traefik/pull/5776) by [waitingsong](https://github.com/waitingsong))
|
||||||
|
- **[api]** Adds missed quotes in api.md ([#5787](https://github.com/containous/traefik/pull/5787) by [woto](https://github.com/woto))
|
||||||
|
- **[docker/swarm]** Dashboard example with swarm ([#5795](https://github.com/containous/traefik/pull/5795) by [dduportal](https://github.com/dduportal))
|
||||||
|
- **[docker]** Fix error in link description for priority ([#5746](https://github.com/containous/traefik/pull/5746) by [ASDFGamer](https://github.com/ASDFGamer))
|
||||||
|
- **[k8s]** Wrong endpoint on the TLS secret example ([#5817](https://github.com/containous/traefik/pull/5817) by [yacinelazaar](https://github.com/yacinelazaar))
|
||||||
|
- **[middleware,docker]** Double dollar on docker-compose config ([#5775](https://github.com/containous/traefik/pull/5775) by [clery](https://github.com/clery))
|
||||||
|
- Fix quickstart link in README ([#5794](https://github.com/containous/traefik/pull/5794) by [mcky](https://github.com/mcky))
|
||||||
|
- fix typo in v1 to v2 migration guide ([#5820](https://github.com/containous/traefik/pull/5820) by [fschl](https://github.com/fschl))
|
||||||
|
- slashes ended up in bad place. ([#5798](https://github.com/containous/traefik/pull/5798) by [icepic](https://github.com/icepic))
|
||||||
|
|
||||||
## [v2.0.4](https://github.com/containous/traefik/tree/v2.0.4) (2019-10-28)
|
## [v2.0.4](https://github.com/containous/traefik/tree/v2.0.4) (2019-10-28)
|
||||||
[All Commits](https://github.com/containous/traefik/compare/v2.0.3...v2.0.4)
|
[All Commits](https://github.com/containous/traefik/compare/v2.0.3...v2.0.4)
|
||||||
|
|
||||||
|
@@ -73,11 +73,11 @@ _(But if you'd rather configure some of your routes manually, Traefik supports t
|
|||||||
- [Kubernetes](https://docs.traefik.io/providers/kubernetes-crd/)
|
- [Kubernetes](https://docs.traefik.io/providers/kubernetes-crd/)
|
||||||
- [Marathon](https://docs.traefik.io/providers/marathon/)
|
- [Marathon](https://docs.traefik.io/providers/marathon/)
|
||||||
- [Rancher](https://docs.traefik.io/providers/rancher/) (Metadata)
|
- [Rancher](https://docs.traefik.io/providers/rancher/) (Metadata)
|
||||||
- [File](https://docs.traefik.io/configuration/backends/file)
|
- [File](https://docs.traefik.io/providers/file/)
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
To get your hands on Traefik, you can use the [5-Minute Quickstart](http://docs.traefik.io/#the-traefik-quickstart-using-docker) in our documentation (you will need Docker).
|
To get your hands on Traefik, you can use the [5-Minute Quickstart](https://docs.traefik.io/getting-started/quick-start/) in our documentation (you will need Docker).
|
||||||
|
|
||||||
## Web UI
|
## Web UI
|
||||||
|
|
||||||
|
@@ -51,9 +51,14 @@ func Do(staticConfiguration static.Configuration) (*http.Response, error) {
|
|||||||
return nil, errors.New("please enable `ping` to use health check")
|
return nil, errors.New("please enable `ping` to use health check")
|
||||||
}
|
}
|
||||||
|
|
||||||
pingEntryPoint, ok := staticConfiguration.EntryPoints["traefik"]
|
ep := staticConfiguration.Ping.EntryPoint
|
||||||
|
if ep == "" {
|
||||||
|
ep = "traefik"
|
||||||
|
}
|
||||||
|
|
||||||
|
pingEntryPoint, ok := staticConfiguration.EntryPoints[ep]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("missing `ping` entrypoint")
|
return nil, fmt.Errorf("ping: missing %s entry point", ep)
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{Timeout: 5 * time.Second}
|
client := &http.Client{Timeout: 5 * time.Second}
|
||||||
|
@@ -7,5 +7,6 @@
|
|||||||
"MD026": false,
|
"MD026": false,
|
||||||
"MD033": false,
|
"MD033": false,
|
||||||
"MD034": false,
|
"MD034": false,
|
||||||
"MD036": false
|
"MD036": false,
|
||||||
|
"MD046": false
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,5 @@
|
|||||||
|
FROM alpine:3.15 as alpine
|
||||||
|
|
||||||
FROM alpine:3.9 as alpine
|
|
||||||
|
|
||||||
# The "build-dependencies" virtual package provides build tools for html-proofer installation.
|
|
||||||
# It compile ruby-nokogiri, because alpine native version is always out of date
|
|
||||||
# This virtual package is cleaned at the end.
|
|
||||||
RUN apk --no-cache --no-progress add \
|
RUN apk --no-cache --no-progress add \
|
||||||
libcurl \
|
libcurl \
|
||||||
ruby \
|
ruby \
|
||||||
@@ -11,21 +7,21 @@ RUN apk --no-cache --no-progress add \
|
|||||||
ruby-etc \
|
ruby-etc \
|
||||||
ruby-ffi \
|
ruby-ffi \
|
||||||
ruby-json \
|
ruby-json \
|
||||||
&& apk add --no-cache --virtual build-dependencies \
|
ruby-nokogiri
|
||||||
build-base \
|
RUN gem install html-proofer --version 3.19.3 --no-document -- --use-system-libraries
|
||||||
libcurl \
|
|
||||||
libxml2-dev \
|
|
||||||
libxslt-dev \
|
|
||||||
ruby-dev \
|
|
||||||
&& gem install --no-document html-proofer -v 3.10.2 \
|
|
||||||
&& apk del build-dependencies
|
|
||||||
|
|
||||||
# After Ruby, some NodeJS YAY!
|
# After Ruby, some NodeJS YAY!
|
||||||
RUN apk --no-cache --no-progress add \
|
RUN apk --no-cache --no-progress add \
|
||||||
git \
|
git \
|
||||||
nodejs \
|
nodejs \
|
||||||
npm \
|
npm
|
||||||
&& npm install markdownlint@0.12.0 markdownlint-cli@0.13.0 --global
|
|
||||||
|
# To handle 'not get uid/gid'
|
||||||
|
RUN npm config set unsafe-perm true
|
||||||
|
|
||||||
|
RUN npm install --global \
|
||||||
|
markdownlint@0.17.2 \
|
||||||
|
markdownlint-cli@0.19.0
|
||||||
|
|
||||||
# Finally the shell tools we need for later
|
# Finally the shell tools we need for later
|
||||||
# tini helps to terminate properly all the parallelized tasks when sending CTRL-C
|
# tini helps to terminate properly all the parallelized tasks when sending CTRL-C
|
||||||
|
@@ -62,6 +62,7 @@ Requirements:
|
|||||||
|
|
||||||
- `go` v1.13+
|
- `go` v1.13+
|
||||||
- environment variable `GO111MODULE=on`
|
- environment variable `GO111MODULE=on`
|
||||||
|
- go-bindata `GO111MODULE=off go get -u github.com/containous/go-bindata/...`
|
||||||
|
|
||||||
!!! tip "Source Directory"
|
!!! tip "Source Directory"
|
||||||
|
|
||||||
|
16
docs/content/contributing/submitting-security-issues.md
Normal file
16
docs/content/contributing/submitting-security-issues.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Security
|
||||||
|
|
||||||
|
## Security Advisories
|
||||||
|
|
||||||
|
We strongly advise you to join our mailing list to be aware of the latest announcements from our security team.
|
||||||
|
You can subscribe sending a mail to security+subscribe@traefik.io or on [the online viewer](https://groups.google.com/a/traefik.io/forum/#!forum/security).
|
||||||
|
|
||||||
|
## CVE
|
||||||
|
|
||||||
|
Reported vulnerabilities can be found on
|
||||||
|
[cve.mitre.org](https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=traefik).
|
||||||
|
|
||||||
|
## Report a Vulnerability
|
||||||
|
|
||||||
|
We want to keep Traefik safe for everyone.
|
||||||
|
If you've discovered a security vulnerability in Traefik, we appreciate your help in disclosing it to us in a responsible manner, using [this form](https://security.traefik.io).
|
@@ -3,6 +3,7 @@
|
|||||||
You can install Traefik with the following flavors:
|
You can install Traefik with the following flavors:
|
||||||
|
|
||||||
* [Use the official Docker image](./#use-the-official-docker-image)
|
* [Use the official Docker image](./#use-the-official-docker-image)
|
||||||
|
* [(Experimental) Use the Helm Chart](./#use-the-helm-chart)
|
||||||
* [Use the binary distribution](./#use-the-binary-distribution)
|
* [Use the binary distribution](./#use-the-binary-distribution)
|
||||||
* [Compile your binary from the sources](./#compile-your-binary-from-the-sources)
|
* [Compile your binary from the sources](./#compile-your-binary-from-the-sources)
|
||||||
|
|
||||||
@@ -24,6 +25,70 @@ For more details, go to the [Docker provider documentation](../providers/docker.
|
|||||||
* Docker images are based from the [Alpine Linux Official image](https://hub.docker.com/_/alpine).
|
* Docker images are based from the [Alpine Linux Official image](https://hub.docker.com/_/alpine).
|
||||||
* All the orchestrator using docker images could fetch the official Traefik docker image.
|
* All the orchestrator using docker images could fetch the official Traefik docker image.
|
||||||
|
|
||||||
|
## Use the Helm Chart
|
||||||
|
|
||||||
|
!!! warning "Experimental Helm Chart"
|
||||||
|
|
||||||
|
Please note that the Helm Chart for Traefik v2 is still experimental.
|
||||||
|
|
||||||
|
The Traefik Stable Chart from
|
||||||
|
[Helm's default charts repository](https://github.com/helm/charts/tree/master/stable/traefik) is still using [Traefik v1.7](https://docs.traefik.io/v1.7).
|
||||||
|
|
||||||
|
Traefik can be installed in Kubernetes using the v2.0 Helm chart from <https://github.com/containous/traefik-helm-chart>.
|
||||||
|
|
||||||
|
Ensure that the following requirements are met:
|
||||||
|
|
||||||
|
* Kubernetes 1.14+
|
||||||
|
* Helm version 2.x is [installed](https://v2.helm.sh/docs/using_helm/) and initialized with Tiller
|
||||||
|
|
||||||
|
Retrieve the latest chart version from the repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Retrieve Chart from the repository
|
||||||
|
git clone https://github.com/containous/traefik-helm-chart
|
||||||
|
```
|
||||||
|
|
||||||
|
And install it with the `helm` command line:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install ./traefik-helm-chart
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! tip "Helm Features"
|
||||||
|
|
||||||
|
All [Helm features](https://v2.helm.sh/docs/using_helm/#using-helm) are supported.
|
||||||
|
For instance, installing the chart in a dedicated namespace:
|
||||||
|
|
||||||
|
```bash tab="Install in a Dedicated Namespace"
|
||||||
|
# Install in the namespace "traefik-v2"
|
||||||
|
helm install --namespace=traefik-v2 \
|
||||||
|
./traefik-helm-chart
|
||||||
|
```
|
||||||
|
|
||||||
|
??? example "Installing with Custom Values"
|
||||||
|
|
||||||
|
You can customize the installation by specifying custom values,
|
||||||
|
as with [any helm chart](https://v2.helm.sh/docs/using_helm/#customizing-the-chart-before-installing).
|
||||||
|
{: #helm-custom-values }
|
||||||
|
|
||||||
|
The values are not (yet) documented, but are self-explanatory:
|
||||||
|
you can look at the [default `values.yaml`](https://github.com/containous/traefik-helm-chart/blob/master/values.yaml) file to explore possibilities.
|
||||||
|
|
||||||
|
Example of installation with logging set to `DEBUG`:
|
||||||
|
|
||||||
|
```bash tab="Using Helm CLI"
|
||||||
|
helm install --namespace=traefik-v2 \
|
||||||
|
--set="logs.loglevel=DEBUG" \
|
||||||
|
./traefik-helm-chart
|
||||||
|
```
|
||||||
|
|
||||||
|
```yml tab="With a custom values file"
|
||||||
|
# File custom-values.yml
|
||||||
|
## Install with "helm install --values=./custom-values.yml ./traefik-helm-chart
|
||||||
|
logs:
|
||||||
|
loglevel: DEBUG
|
||||||
|
```
|
||||||
|
|
||||||
## Use the Binary Distribution
|
## Use the Binary Distribution
|
||||||
|
|
||||||
Grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page.
|
Grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page.
|
||||||
|
4
docs/content/https/.markdownlint.json
Normal file
4
docs/content/https/.markdownlint.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../.markdownlint.json",
|
||||||
|
"MD041": false
|
||||||
|
}
|
@@ -8,6 +8,45 @@ You can configure Traefik to use an ACME provider (like Let's Encrypt) for autom
|
|||||||
!!! warning "Let's Encrypt and Rate Limiting"
|
!!! warning "Let's Encrypt and Rate Limiting"
|
||||||
Note that Let's Encrypt API has [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
Note that Let's Encrypt API has [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
||||||
|
|
||||||
|
Use Let's Encrypt staging server with the [`caServer`](#caserver) configuration option
|
||||||
|
when experimenting to avoid hitting this limit too fast.
|
||||||
|
|
||||||
|
## Certificate Resolvers
|
||||||
|
|
||||||
|
Traefik requires you to define "Certificate Resolvers" in the [static configuration](../getting-started/configuration-overview.md#the-static-configuration),
|
||||||
|
which are responsible for retrieving certificates from an ACME server.
|
||||||
|
|
||||||
|
Then, each ["router"](../routing/routers/index.md) is configured to enable TLS,
|
||||||
|
and is associated to a certificate resolver through the [`tls.certresolver` configuration option](../routing/routers/index.md#certresolver).
|
||||||
|
|
||||||
|
Certificates are requested for domain names retrieved from the router's [dynamic configuration](../getting-started/configuration-overview.md#the-dynamic-configuration).
|
||||||
|
|
||||||
|
You can read more about this retrieval mechanism in the following section: [ACME Domain Definition](#domain-definition).
|
||||||
|
|
||||||
|
## Domain Definition
|
||||||
|
|
||||||
|
Certificate resolvers request certificates for a set of the domain names
|
||||||
|
inferred from routers, with the following logic:
|
||||||
|
|
||||||
|
- If the router has a [`tls.domains`](../routing/routers/index.md#domains) option set,
|
||||||
|
then the certificate resolver uses the `main` (and optionally `sans`) option of `tls.domains` to know the domain names for this router.
|
||||||
|
|
||||||
|
- If no [`tls.domains`](../routing/routers/index.md#domains) option is set,
|
||||||
|
then the certificate resolver uses the [router's rule](../routing/routers/index.md#rule),
|
||||||
|
by checking the `Host()` matchers.
|
||||||
|
Please note that [multiple `Host()` matchers can be used](../routing/routers/index.md#certresolver)) for specifying multiple domain names for this router.
|
||||||
|
|
||||||
|
Please note that:
|
||||||
|
|
||||||
|
- When multiple domain names are inferred from a given router,
|
||||||
|
only **one** certificate is requested with the first domain name as the main domain,
|
||||||
|
and the other domains as ["SANs" (Subject Alternative Name)](https://en.wikipedia.org/wiki/Subject_Alternative_Name).
|
||||||
|
|
||||||
|
- As [ACME V2 supports "wildcard domains"](#wildcard-domains),
|
||||||
|
any router can provide a [wildcard domain](https://en.wikipedia.org/wiki/Wildcard_certificate) name, as "main" domain or as "SAN" domain.
|
||||||
|
|
||||||
|
Please check the [configuration examples below](#configuration-examples) for more details.
|
||||||
|
|
||||||
## Configuration Examples
|
## Configuration Examples
|
||||||
|
|
||||||
??? example "Enabling ACME"
|
??? example "Enabling ACME"
|
||||||
@@ -47,11 +86,11 @@ You can configure Traefik to use an ACME provider (like Let's Encrypt) for autom
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--entryPoints.web.address=":80"
|
--entryPoints.web.address=:80
|
||||||
--entryPoints.websecure.address=":443"
|
--entryPoints.websecure.address=:443
|
||||||
# ...
|
# ...
|
||||||
--certificatesResolvers.sample.acme.email="your-email@your-domain.org"
|
--certificatesResolvers.sample.acme.email=your-email@your-domain.org
|
||||||
--certificatesResolvers.sample.acme.storage="acme.json"
|
--certificatesResolvers.sample.acme.storage=acme.json
|
||||||
# used during the challenge
|
# used during the challenge
|
||||||
--certificatesResolvers.sample.acme.httpChallenge.entryPoint=web
|
--certificatesResolvers.sample.acme.httpChallenge.entryPoint=web
|
||||||
```
|
```
|
||||||
@@ -75,6 +114,26 @@ You can configure Traefik to use an ACME provider (like Let's Encrypt) for autom
|
|||||||
--8<-- "content/https/ref-acme.txt"
|
--8<-- "content/https/ref-acme.txt"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
??? example "Single Domain from Router's Rule Example"
|
||||||
|
|
||||||
|
* A certificate for the domain `company.com` is requested:
|
||||||
|
|
||||||
|
--8<-- "content/https/include-acme-single-domain-example.md"
|
||||||
|
|
||||||
|
??? example "Multiple Domains from Router's Rule Example"
|
||||||
|
|
||||||
|
* A certificate for the domains `company.com` (main) and `blog.company.org`
|
||||||
|
is requested:
|
||||||
|
|
||||||
|
--8<-- "content/https/include-acme-multiple-domains-from-rule-example.md"
|
||||||
|
|
||||||
|
??? example "Multiple Domains from Router's `tls.domain` Example"
|
||||||
|
|
||||||
|
* A certificate for the domains `company.com` (main) and `*.company.org` (SAN)
|
||||||
|
is requested:
|
||||||
|
|
||||||
|
--8<-- "content/https/include-acme-multiple-domains-example.md"
|
||||||
|
|
||||||
## Automatic Renewals
|
## Automatic Renewals
|
||||||
|
|
||||||
Traefik automatically tracks the expiry date of ACME certificates it generates.
|
Traefik automatically tracks the expiry date of ACME certificates it generates.
|
||||||
@@ -84,6 +143,13 @@ If there are less than 30 days remaining before the certificate expires, Traefik
|
|||||||
!!! info ""
|
!!! info ""
|
||||||
Certificates that are no longer used may still be renewed, as Traefik does not currently check if the certificate is being used before renewing.
|
Certificates that are no longer used may still be renewed, as Traefik does not currently check if the certificate is being used before renewing.
|
||||||
|
|
||||||
|
## Using LetsEncrypt with Kubernetes
|
||||||
|
|
||||||
|
When using LetsEncrypt with kubernetes, there are some known caveats with both the [ingress](../providers/kubernetes-ingress.md) and [crd](../providers/kubernetes-crd.md) providers.
|
||||||
|
|
||||||
|
!!! info ""
|
||||||
|
If you intend to run multiple instances of Traefik with LetsEncrypt, please ensure you read the sections on those provider pages.
|
||||||
|
|
||||||
## The Different ACME Challenges
|
## The Different ACME Challenges
|
||||||
|
|
||||||
!!! important "Defining a certificates resolver does not result in all routers automatically using it. Each router that is supposed to use the resolver must [reference](../routing/routers/index.md#certresolver) it."
|
!!! important "Defining a certificates resolver does not result in all routers automatically using it. Each router that is supposed to use the resolver must [reference](../routing/routers/index.md#certresolver) it."
|
||||||
@@ -156,8 +222,8 @@ when using the `HTTP-01` challenge, `certificatesResolvers.sample.acme.httpChall
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--entryPoints.web.address=":80"
|
--entryPoints.web.address=:80
|
||||||
--entryPoints.websecure.address=":443"
|
--entryPoints.websecure.address=:443
|
||||||
# ...
|
# ...
|
||||||
--certificatesResolvers.sample.acme.httpChallenge.entryPoint=web
|
--certificatesResolvers.sample.acme.httpChallenge.entryPoint=web
|
||||||
```
|
```
|
||||||
@@ -215,11 +281,12 @@ For example, `CF_API_EMAIL_FILE=/run/secrets/traefik_cf-api-email` could be used
|
|||||||
| [ACME DNS](https://github.com/joohoi/acme-dns) | `acme-dns` | `ACME_DNS_API_BASE`, `ACME_DNS_STORAGE_PATH` | [Additional configuration](https://go-acme.github.io/lego/dns/acme-dns) |
|
| [ACME DNS](https://github.com/joohoi/acme-dns) | `acme-dns` | `ACME_DNS_API_BASE`, `ACME_DNS_STORAGE_PATH` | [Additional configuration](https://go-acme.github.io/lego/dns/acme-dns) |
|
||||||
| [Alibaba Cloud](https://www.alibabacloud.com) | `alidns` | `ALICLOUD_ACCESS_KEY`, `ALICLOUD_SECRET_KEY`, `ALICLOUD_REGION_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/alidns) |
|
| [Alibaba Cloud](https://www.alibabacloud.com) | `alidns` | `ALICLOUD_ACCESS_KEY`, `ALICLOUD_SECRET_KEY`, `ALICLOUD_REGION_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/alidns) |
|
||||||
| [Auroradns](https://www.pcextreme.com/aurora/dns) | `auroradns` | `AURORA_USER_ID`, `AURORA_KEY`, `AURORA_ENDPOINT` | [Additional configuration](https://go-acme.github.io/lego/dns/auroradns) |
|
| [Auroradns](https://www.pcextreme.com/aurora/dns) | `auroradns` | `AURORA_USER_ID`, `AURORA_KEY`, `AURORA_ENDPOINT` | [Additional configuration](https://go-acme.github.io/lego/dns/auroradns) |
|
||||||
|
| [Autodns](https://www.internetx.com/domains/autodns/) | `autodns` | `AUTODNS_API_USER`, `AUTODNS_API_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/autodns) |
|
||||||
| [Azure](https://azure.microsoft.com/services/dns/) | `azure` | `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SUBSCRIPTION_ID`, `AZURE_TENANT_ID`, `AZURE_RESOURCE_GROUP`, `[AZURE_METADATA_ENDPOINT]` | [Additional configuration](https://go-acme.github.io/lego/dns/azure) |
|
| [Azure](https://azure.microsoft.com/services/dns/) | `azure` | `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SUBSCRIPTION_ID`, `AZURE_TENANT_ID`, `AZURE_RESOURCE_GROUP`, `[AZURE_METADATA_ENDPOINT]` | [Additional configuration](https://go-acme.github.io/lego/dns/azure) |
|
||||||
| [Bindman](https://github.com/labbsr0x/bindman-dns-webhook) | `bindman` | `BINDMAN_MANAGER_ADDRESS` | [Additional configuration](https://go-acme.github.io/lego/dns/bindman) |
|
| [Bindman](https://github.com/labbsr0x/bindman-dns-webhook) | `bindman` | `BINDMAN_MANAGER_ADDRESS` | [Additional configuration](https://go-acme.github.io/lego/dns/bindman) |
|
||||||
| [Blue Cat](https://www.bluecatnetworks.com/) | `bluecat` | `BLUECAT_SERVER_URL`, `BLUECAT_USER_NAME`, `BLUECAT_PASSWORD`, `BLUECAT_CONFIG_NAME`, `BLUECAT_DNS_VIEW` | [Additional configuration](https://go-acme.github.io/lego/dns/bluecat) |
|
| [Blue Cat](https://www.bluecatnetworks.com/) | `bluecat` | `BLUECAT_SERVER_URL`, `BLUECAT_USER_NAME`, `BLUECAT_PASSWORD`, `BLUECAT_CONFIG_NAME`, `BLUECAT_DNS_VIEW` | [Additional configuration](https://go-acme.github.io/lego/dns/bluecat) |
|
||||||
| [ClouDNS](https://www.cloudns.net/) | `cloudns` | `CLOUDNS_AUTH_ID`, `CLOUDNS_AUTH_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/cloudns) |
|
| [ClouDNS](https://www.cloudns.net/) | `cloudns` | `CLOUDNS_AUTH_ID`, `CLOUDNS_AUTH_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/cloudns) |
|
||||||
| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | `CF_API_EMAIL`, `CF_API_KEY` or `CF_DNS_API_TOKEN`, `[CF_ZONE_API_TOKEN]` [^5] | [Additional configuration](https://go-acme.github.io/lego/dns/cloudflare) |
|
| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | `CF_API_EMAIL`, `CF_API_KEY` [^5] or `CF_DNS_API_TOKEN`, `[CF_ZONE_API_TOKEN]` | [Additional configuration](https://go-acme.github.io/lego/dns/cloudflare) |
|
||||||
| [CloudXNS](https://www.cloudxns.net) | `cloudxns` | `CLOUDXNS_API_KEY`, `CLOUDXNS_SECRET_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/cloudxns) |
|
| [CloudXNS](https://www.cloudxns.net) | `cloudxns` | `CLOUDXNS_API_KEY`, `CLOUDXNS_SECRET_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/cloudxns) |
|
||||||
| [ConoHa](https://www.conoha.jp) | `conoha` | `CONOHA_TENANT_ID`, `CONOHA_API_USERNAME`, `CONOHA_API_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/conoha) |
|
| [ConoHa](https://www.conoha.jp) | `conoha` | `CONOHA_TENANT_ID`, `CONOHA_API_USERNAME`, `CONOHA_API_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/conoha) |
|
||||||
| [DigitalOcean](https://www.digitalocean.com) | `digitalocean` | `DO_AUTH_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/digitalocean) |
|
| [DigitalOcean](https://www.digitalocean.com) | `digitalocean` | `DO_AUTH_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/digitalocean) |
|
||||||
@@ -311,7 +378,7 @@ certificatesResolvers:
|
|||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
# ...
|
# ...
|
||||||
--certificatesResolvers.sample.acme.dnsChallenge.resolvers:="1.1.1.1:53,8.8.8.8:53"
|
--certificatesResolvers.sample.acme.dnsChallenge.resolvers:=1.1.1.1:53,8.8.8.8:53
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Wildcard Domains
|
#### Wildcard Domains
|
||||||
@@ -319,7 +386,9 @@ certificatesResolvers:
|
|||||||
[ACME V2](https://community.letsencrypt.org/t/acme-v2-and-wildcard-certificate-support-is-live/55579) supports wildcard certificates.
|
[ACME V2](https://community.letsencrypt.org/t/acme-v2-and-wildcard-certificate-support-is-live/55579) supports wildcard certificates.
|
||||||
As described in [Let's Encrypt's post](https://community.letsencrypt.org/t/staging-endpoint-for-acme-v2/49605) wildcard certificates can only be generated through a [`DNS-01` challenge](#dnschallenge).
|
As described in [Let's Encrypt's post](https://community.letsencrypt.org/t/staging-endpoint-for-acme-v2/49605) wildcard certificates can only be generated through a [`DNS-01` challenge](#dnschallenge).
|
||||||
|
|
||||||
## `caServer`
|
## More Configuration
|
||||||
|
|
||||||
|
### `caServer`
|
||||||
|
|
||||||
??? example "Using the Let's Encrypt staging server"
|
??? example "Using the Let's Encrypt staging server"
|
||||||
|
|
||||||
@@ -341,11 +410,11 @@ As described in [Let's Encrypt's post](https://community.letsencrypt.org/t/stagi
|
|||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
# ...
|
# ...
|
||||||
--certificatesResolvers.sample.acme.caServer="https://acme-staging-v02.api.letsencrypt.org/directory"
|
--certificatesResolvers.sample.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
## `storage`
|
### `storage`
|
||||||
|
|
||||||
The `storage` option sets the location where your ACME certificates are saved to.
|
The `storage` option sets the location where your ACME certificates are saved to.
|
||||||
|
|
||||||
@@ -375,7 +444,7 @@ The value can refer to some kinds of storage:
|
|||||||
|
|
||||||
- a JSON file
|
- a JSON file
|
||||||
|
|
||||||
### In a File
|
#### In a File
|
||||||
|
|
||||||
ACME certificates can be stored in a JSON file that needs to have a `600` file mode .
|
ACME certificates can be stored in a JSON file that needs to have a `600` file mode .
|
||||||
|
|
||||||
@@ -390,7 +459,7 @@ docker run -v "/my/host/acme:/etc/traefik/acme" traefik
|
|||||||
```
|
```
|
||||||
|
|
||||||
!!! warning
|
!!! warning
|
||||||
For concurrency reason, this file cannot be shared across multiple instances of Traefik. Use a key value store entry instead.
|
For concurrency reason, this file cannot be shared across multiple instances of Traefik.
|
||||||
|
|
||||||
## Fallback
|
## Fallback
|
||||||
|
|
||||||
|
88
docs/content/https/include-acme-multiple-domains-example.md
Normal file
88
docs/content/https/include-acme-multiple-domains-example.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
|
||||||
|
```yaml tab="Docker"
|
||||||
|
## Dynamic configuration
|
||||||
|
labels:
|
||||||
|
- traefik.http.routers.blog.rule=Host(`company.com`) && Path(`/blog`)
|
||||||
|
- traefik.http.routers.blog.tls=true
|
||||||
|
- traefik.http.routers.blog.tls.certresolver=le
|
||||||
|
- traefik.http.routers.blog.tls.domains[0].main=company.org
|
||||||
|
- traefik.http.routers.blog.tls.domains[0].sans=*.company.org
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Docker (Swarm)"
|
||||||
|
## Dynamic configuration
|
||||||
|
deploy:
|
||||||
|
labels:
|
||||||
|
- traefik.http.routers.blog.rule=Host(`company.com`) && Path(`/blog`)
|
||||||
|
- traefik.http.services.blog-svc.loadbalancer.server.port=8080"
|
||||||
|
- traefik.http.routers.blog.tls=true
|
||||||
|
- traefik.http.routers.blog.tls.certresolver=le
|
||||||
|
- traefik.http.routers.blog.tls.domains[0].main=company.org
|
||||||
|
- traefik.http.routers.blog.tls.domains[0].sans=*.company.org
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Kubernetes"
|
||||||
|
---
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: blogtls
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- match: Host(`company.com`) && Path(`/blog`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: blog
|
||||||
|
port: 8080
|
||||||
|
tls:
|
||||||
|
certResolver: le
|
||||||
|
```
|
||||||
|
|
||||||
|
```json tab="Marathon"
|
||||||
|
labels: {
|
||||||
|
"traefik.http.routers.blog.rule": "Host(`company.com`) && Path(`/blog`)",
|
||||||
|
"traefik.http.routers.blog.tls": "true",
|
||||||
|
"traefik.http.routers.blog.tls.certresolver": "le",
|
||||||
|
"traefik.http.routers.blog.tls.domains[0].main": "company.com",
|
||||||
|
"traefik.http.routers.blog.tls.domains[0].sans": "*.company.com",
|
||||||
|
"traefik.http.services.blog-svc.loadbalancer.server.port": "8080"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Rancher"
|
||||||
|
## Dynamic configuration
|
||||||
|
labels:
|
||||||
|
- traefik.http.routers.blog.rule=Host(`company.com`) && Path(`/blog`)
|
||||||
|
- traefik.http.routers.blog.tls=true
|
||||||
|
- traefik.http.routers.blog.tls.certresolver=le
|
||||||
|
- traefik.http.routers.blog.tls.domains[0].main=company.org
|
||||||
|
- traefik.http.routers.blog.tls.domains[0].sans=*.company.org
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
## Dynamic configuration
|
||||||
|
[http.routers]
|
||||||
|
[http.routers.blog]
|
||||||
|
rule = "Host(`company.com`) && Path(`/blog`)"
|
||||||
|
[http.routers.blog.tls]
|
||||||
|
certResolver = "le" # From static configuration
|
||||||
|
[[http.routers.blog.tls.domains]]
|
||||||
|
main = "company.org"
|
||||||
|
sans = ["*.company.org"]
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
## Dynamic configuration
|
||||||
|
http:
|
||||||
|
routers:
|
||||||
|
blog:
|
||||||
|
rule: "Host(`company.com`) && Path(`/blog`)"
|
||||||
|
tls:
|
||||||
|
certResolver: le
|
||||||
|
domains:
|
||||||
|
- main: "company.org"
|
||||||
|
sans:
|
||||||
|
- "*.company.org"
|
||||||
|
```
|
@@ -0,0 +1,72 @@
|
|||||||
|
|
||||||
|
```yaml tab="Docker"
|
||||||
|
## Dynamic configuration
|
||||||
|
labels:
|
||||||
|
- traefik.http.routers.blog.rule=(Host(`company.com`) && Path(`/blog`)) || Host(`blog.company.org`)
|
||||||
|
- traefik.http.routers.blog.tls=true
|
||||||
|
- traefik.http.routers.blog.tls.certresolver=le
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Docker (Swarm)"
|
||||||
|
## Dynamic configuration
|
||||||
|
deploy:
|
||||||
|
labels:
|
||||||
|
- traefik.http.routers.blog.rule=(Host(`company.com`) && Path(`/blog`)) || Host(`blog.company.org`)
|
||||||
|
- traefik.http.services.blog-svc.loadbalancer.server.port=8080"
|
||||||
|
- traefik.http.routers.blog.tls=true
|
||||||
|
- traefik.http.routers.blog.tls.certresolver=le
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Kubernetes"
|
||||||
|
---
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: blogtls
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- match: (Host(`company.com`) && Path(`/blog`)) || Host(`blog.company.org`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: blog
|
||||||
|
port: 8080
|
||||||
|
tls: {}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json tab="Marathon"
|
||||||
|
labels: {
|
||||||
|
"traefik.http.routers.blog.rule": "(Host(`company.com`) && Path(`/blog`)) || Host(`blog.company.org`)",
|
||||||
|
"traefik.http.routers.blog.tls": "true",
|
||||||
|
"traefik.http.routers.blog.tls.certresolver": "le",
|
||||||
|
"traefik.http.services.blog-svc.loadbalancer.server.port": "8080"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Rancher"
|
||||||
|
## Dynamic configuration
|
||||||
|
labels:
|
||||||
|
- traefik.http.routers.blog.rule=(Host(`company.com`) && Path(`/blog`)) || Host(`blog.company.org`)
|
||||||
|
- traefik.http.routers.blog.tls=true
|
||||||
|
- traefik.http.routers.blog.tls.certresolver=le
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
## Dynamic configuration
|
||||||
|
[http.routers]
|
||||||
|
[http.routers.blog]
|
||||||
|
rule = "(Host(`company.com`) && Path(`/blog`)) || Host(`blog.company.org`)"
|
||||||
|
[http.routers.blog.tls]
|
||||||
|
certResolver = "le" # From static configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
## Dynamic configuration
|
||||||
|
http:
|
||||||
|
routers:
|
||||||
|
blog:
|
||||||
|
rule: "(Host(`company.com`) && Path(`/blog`)) || Host(`blog.company.org`)"
|
||||||
|
tls:
|
||||||
|
certResolver: le
|
||||||
|
```
|
72
docs/content/https/include-acme-single-domain-example.md
Normal file
72
docs/content/https/include-acme-single-domain-example.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
|
||||||
|
```yaml tab="Docker"
|
||||||
|
## Dynamic configuration
|
||||||
|
labels:
|
||||||
|
- traefik.http.routers.blog.rule=Host(`company.com`) && Path(`/blog`)
|
||||||
|
- traefik.http.routers.blog.tls=true
|
||||||
|
- traefik.http.routers.blog.tls.certresolver=le
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Docker (Swarm)"
|
||||||
|
## Dynamic configuration
|
||||||
|
deploy:
|
||||||
|
labels:
|
||||||
|
- traefik.http.routers.blog.rule=Host(`company.com`) && Path(`/blog`)
|
||||||
|
- traefik.http.services.blog-svc.loadbalancer.server.port=8080"
|
||||||
|
- traefik.http.routers.blog.tls=true
|
||||||
|
- traefik.http.routers.blog.tls.certresolver=le
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Kubernetes"
|
||||||
|
---
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: blogtls
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- match: Host(`company.com`) && Path(`/blog`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: blog
|
||||||
|
port: 8080
|
||||||
|
tls: {}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json tab="Marathon"
|
||||||
|
labels: {
|
||||||
|
"traefik.http.routers.blog.rule": "Host(`company.com`) && Path(`/blog`)",
|
||||||
|
"traefik.http.routers.blog.tls": "true",
|
||||||
|
"traefik.http.routers.blog.tls.certresolver": "le",
|
||||||
|
"traefik.http.services.blog-svc.loadbalancer.server.port": "8080"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Rancher"
|
||||||
|
## Dynamic configuration
|
||||||
|
labels:
|
||||||
|
- traefik.http.routers.blog.rule=Host(`company.com`) && Path(`/blog`)
|
||||||
|
- traefik.http.routers.blog.tls=true
|
||||||
|
- traefik.http.routers.blog.tls.certresolver=le
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="Single Domain"
|
||||||
|
## Dynamic configuration
|
||||||
|
[http.routers]
|
||||||
|
[http.routers.blog]
|
||||||
|
rule = "Host(`company.com`) && Path(`/blog`)"
|
||||||
|
[http.routers.blog.tls]
|
||||||
|
certResolver = "le" # From static configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
## Dynamic configuration
|
||||||
|
http:
|
||||||
|
routers:
|
||||||
|
blog:
|
||||||
|
rule: "Host(`company.com`) && Path(`/blog`)"
|
||||||
|
tls:
|
||||||
|
certResolver: le
|
||||||
|
```
|
@@ -4,13 +4,13 @@
|
|||||||
#
|
#
|
||||||
# Required
|
# Required
|
||||||
#
|
#
|
||||||
--certificatesResolvers.sample.acme.email="test@traefik.io"
|
--certificatesResolvers.sample.acme.email=test@traefik.io
|
||||||
|
|
||||||
# File or key used for certificates storage.
|
# File or key used for certificates storage.
|
||||||
#
|
#
|
||||||
# Required
|
# Required
|
||||||
#
|
#
|
||||||
--certificatesResolvers.sample.acme.storage="acme.json"
|
--certificatesResolvers.sample.acme.storage=acme.json
|
||||||
|
|
||||||
# CA server to use.
|
# CA server to use.
|
||||||
# Uncomment the line to use Let's Encrypt's staging server,
|
# Uncomment the line to use Let's Encrypt's staging server,
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
# Optional
|
# Optional
|
||||||
# Default: "https://acme-v02.api.letsencrypt.org/directory"
|
# Default: "https://acme-v02.api.letsencrypt.org/directory"
|
||||||
#
|
#
|
||||||
--certificatesResolvers.sample.acme.caServer="https://acme-staging-v02.api.letsencrypt.org/directory"
|
--certificatesResolvers.sample.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory
|
||||||
|
|
||||||
# KeyType to use.
|
# KeyType to use.
|
||||||
#
|
#
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
# Optional
|
# Optional
|
||||||
# Default: empty
|
# Default: empty
|
||||||
#
|
#
|
||||||
--certificatesResolvers.sample.acme.dnsChallenge.resolvers="1.1.1.1:53,8.8.8.8:53"
|
--certificatesResolvers.sample.acme.dnsChallenge.resolvers=1.1.1.1:53,8.8.8.8:53
|
||||||
|
|
||||||
# Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready.
|
# Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready.
|
||||||
#
|
#
|
||||||
|
@@ -20,4 +20,4 @@ Developing Traefik, our main goal is to make it simple to use, and we're sure yo
|
|||||||
|
|
||||||
!!! info
|
!!! info
|
||||||
|
|
||||||
If you're a business running critical services behind Traefik, know that [Containous](https://containo.us), the company that sponsors Traefik's development, can provide [commercial support](https://containo.us/services/#commercial-support) and develops an [Enterprise Edition](https://containo.us/traefikee/) of Traefik.
|
If you're a business running critical services behind Traefik, know that [Containous](https://containo.us), the company that sponsors Traefik's development, can provide [commercial support](https://info.containo.us/commercial-services) and develops an [Enterprise Edition](https://containo.us/traefikee/) of Traefik.
|
||||||
|
@@ -58,4 +58,5 @@ http:
|
|||||||
|
|
||||||
### `prefix`
|
### `prefix`
|
||||||
|
|
||||||
`prefix` is the string to add before the current path in the requested URL. It should include the leading slash (`/`).
|
`prefix` is the string to add before the current path in the requested URL.
|
||||||
|
It should include the leading slash (`/`).
|
||||||
|
@@ -380,7 +380,7 @@ In the example, it is the part between `-----BEGIN CERTIFICATE-----` and `-----E
|
|||||||
!!! info "Extracted data"
|
!!! info "Extracted data"
|
||||||
|
|
||||||
The delimiters and `\n` will be removed.
|
The delimiters and `\n` will be removed.
|
||||||
If there are more than one certificate, they are separated by a "`;`".
|
If there are more than one certificate, they are separated by a "`,`".
|
||||||
|
|
||||||
!!! warning "`X-Forwarded-Tls-Client-Cert` value could exceed the web server header size limit"
|
!!! warning "`X-Forwarded-Tls-Client-Cert` value could exceed the web server header size limit"
|
||||||
|
|
||||||
@@ -395,12 +395,12 @@ The value of the header will be an escaped concatenation of all the selected cer
|
|||||||
The following example shows an unescaped result that uses all the available fields:
|
The following example shows an unescaped result that uses all the available fields:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Subject="DC=org,DC=cheese,C=FR,C=US,ST=Cheese org state,ST=Cheese com state,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=*.cheese.com",Issuer="DC=org,DC=cheese,C=FR,C=US,ST=Signing State,ST=Signing State 2,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=Simple Signing CA 2",NB=1544094616,NA=1607166616,SAN=*.cheese.org,*.cheese.net,*.cheese.com,test@cheese.org,test@cheese.net,10.0.1.0,10.0.1.2
|
Subject="DC=org,DC=cheese,C=FR,C=US,ST=Cheese org state,ST=Cheese com state,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=*.cheese.com";Issuer="DC=org,DC=cheese,C=FR,C=US,ST=Signing State,ST=Signing State 2,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=Simple Signing CA 2";NB="1544094616";NA="1607166616";SAN="*.cheese.org,*.cheese.net,*.cheese.com,test@cheese.org,test@cheese.net,10.0.1.0,10.0.1.2"
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! info "Multiple certificates"
|
!!! info "Multiple certificates"
|
||||||
|
|
||||||
If there are more than one certificate, they are separated by a `;`.
|
If there are more than one certificate, they are separated by a `,`.
|
||||||
|
|
||||||
#### `info.notAfter`
|
#### `info.notAfter`
|
||||||
|
|
||||||
@@ -416,7 +416,7 @@ The data are taken from the following certificate part:
|
|||||||
The escape `notAfter` info part will be like:
|
The escape `notAfter` info part will be like:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
NA=1607166616
|
NA="1607166616"
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `info.notBefore`
|
#### `info.notBefore`
|
||||||
@@ -433,7 +433,7 @@ Validity
|
|||||||
The escape `notBefore` info part will be like:
|
The escape `notBefore` info part will be like:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
NB=1544094616
|
NB="1544094616"
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `info.sans`
|
#### `info.sans`
|
||||||
@@ -450,7 +450,7 @@ The data are taken from the following certificate part:
|
|||||||
The escape SANs info part will be like:
|
The escape SANs info part will be like:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
SAN=*.cheese.org,*.cheese.net,*.cheese.com,test@cheese.org,test@cheese.net,10.0.1.0,10.0.1.2
|
SAN="*.cheese.org,*.cheese.net,*.cheese.com,test@cheese.org,test@cheese.net,10.0.1.0,10.0.1.2"
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! info "multiple values"
|
!!! info "multiple values"
|
||||||
|
@@ -15,7 +15,7 @@ The ReplaceRegex replace a path from an url to another with regex matching and r
|
|||||||
# Replace path with regex
|
# Replace path with regex
|
||||||
labels:
|
labels:
|
||||||
- "traefik.http.middlewares.test-replacepathregex.replacepathregex.regex=^/foo/(.*)"
|
- "traefik.http.middlewares.test-replacepathregex.replacepathregex.regex=^/foo/(.*)"
|
||||||
- "traefik.http.middlewares.test-replacepathregex.replacepathregex.replacement=/bar/$1"
|
- "traefik.http.middlewares.test-replacepathregex.replacepathregex.replacement=/bar/$$1"
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Kubernetes"
|
```yaml tab="Kubernetes"
|
||||||
|
@@ -85,3 +85,85 @@ If your backend is serving assets (e.g., images or Javascript files), chances ar
|
|||||||
Continuing on the 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).
|
Continuing on the 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).
|
||||||
|
|
||||||
The `X-Forwarded-Prefix` header can be queried to build such URLs dynamically.
|
The `X-Forwarded-Prefix` header can be queried to build such URLs dynamically.
|
||||||
|
|
||||||
|
### `forceSlash`
|
||||||
|
|
||||||
|
_Optional, Default=true_
|
||||||
|
|
||||||
|
```yaml tab="Docker"
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.example.stripprefix.prefixes=/foobar"
|
||||||
|
- "traefik.http.middlewares.example.stripprefix.forceslash=false"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Kubernetes"
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: example
|
||||||
|
spec:
|
||||||
|
stripPrefix:
|
||||||
|
prefixes:
|
||||||
|
- "/foobar"
|
||||||
|
forceSlash: false
|
||||||
|
```
|
||||||
|
|
||||||
|
```json tab="Marathon"
|
||||||
|
"labels": {
|
||||||
|
"traefik.http.middlewares.example.stripprefix.prefixes": "/foobar",
|
||||||
|
"traefik.http.middlewares.example.stripprefix.forceslash": "false"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Rancher"
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.example.stripprefix.prefixes=/foobar"
|
||||||
|
- "traefik.http.middlewares.example.stripprefix.forceSlash=false"
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
[http.middlewares]
|
||||||
|
[http.middlewares.example.stripPrefix]
|
||||||
|
prefixes = ["/foobar"]
|
||||||
|
forceSlash = false
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
http:
|
||||||
|
middlewares:
|
||||||
|
example:
|
||||||
|
stripPrefix:
|
||||||
|
prefixes:
|
||||||
|
- "/foobar"
|
||||||
|
forceSlash: false
|
||||||
|
```
|
||||||
|
|
||||||
|
The `forceSlash` option makes sure that the resulting stripped path is not the empty string, by replacing it with `/` when necessary.
|
||||||
|
|
||||||
|
This option was added to keep the initial (non-intuitive) behavior of this middleware, in order to avoid introducing a breaking change.
|
||||||
|
|
||||||
|
It's recommended to explicitly set `forceSlash` to `false`.
|
||||||
|
|
||||||
|
??? info "Behavior examples"
|
||||||
|
|
||||||
|
- `forceSlash=true`
|
||||||
|
|
||||||
|
| Path | Prefix to strip | Result |
|
||||||
|
|------------|-----------------|--------|
|
||||||
|
| `/` | `/` | `/` |
|
||||||
|
| `/foo` | `/foo` | `/` |
|
||||||
|
| `/foo/` | `/foo` | `/` |
|
||||||
|
| `/foo/` | `/foo/` | `/` |
|
||||||
|
| `/bar` | `/foo` | `/bar` |
|
||||||
|
| `/foo/bar` | `/foo` | `/bar` |
|
||||||
|
|
||||||
|
- `forceSlash=false`
|
||||||
|
|
||||||
|
| Path | Prefix to strip | Result |
|
||||||
|
|------------|-----------------|--------|
|
||||||
|
| `/` | `/` | empty |
|
||||||
|
| `/foo` | `/foo` | empty |
|
||||||
|
| `/foo/` | `/foo` | `/` |
|
||||||
|
| `/foo/` | `/foo/` | empty |
|
||||||
|
| `/bar` | `/foo` | `/bar` |
|
||||||
|
| `/foo/bar` | `/foo` | `/bar` |
|
||||||
|
@@ -519,7 +519,7 @@ Use Case: Incoming requests to `http://company.org/admin` are forwarded to the w
|
|||||||
with the path `/admin` stripped, e.g. to `http://<IP>:<port>/`. In this case, you must:
|
with the path `/admin` stripped, e.g. to `http://<IP>:<port>/`. In this case, you must:
|
||||||
|
|
||||||
* First, configure a router named `admin` with a rule matching at least the path prefix with the `PathPrefix` keyword,
|
* First, configure a router named `admin` with a rule matching at least the path prefix with the `PathPrefix` keyword,
|
||||||
* Then, define a middlware of type [`stripprefix`](../../middlewares/stripprefix/), which remove the prefix `/admin`, associated to the router `admin`.
|
* Then, define a middleware of type [`stripprefix`](../../middlewares/stripprefix/), which remove the prefix `/admin`, associated to the router `admin`.
|
||||||
|
|
||||||
!!! example "Strip Path Prefix When Forwarding to Backend"
|
!!! example "Strip Path Prefix When Forwarding to Backend"
|
||||||
|
|
||||||
@@ -560,8 +560,8 @@ with the path `/admin` stripped, e.g. to `http://<IP>:<port>/`. In this case, yo
|
|||||||
```yaml tab="Docker"
|
```yaml tab="Docker"
|
||||||
labels:
|
labels:
|
||||||
- "traefik.http.routers.admin.rule=Host(`company.org`) && PathPrefix(`/admin`)"
|
- "traefik.http.routers.admin.rule=Host(`company.org`) && PathPrefix(`/admin`)"
|
||||||
|
- "traefik.http.routers.admin.middlewares=admin-stripprefix"
|
||||||
- "traefik.http.middlewares.admin-stripprefix.stripprefix.prefixes=/admin"
|
- "traefik.http.middlewares.admin-stripprefix.stripprefix.prefixes=/admin"
|
||||||
- "traefik.http.routers.web.middlewares=admin-stripprefix@docker"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Kubernetes IngressRoute"
|
```yaml tab="Kubernetes IngressRoute"
|
||||||
@@ -718,11 +718,11 @@ with the path `/admin` stripped, e.g. to `http://<IP>:<port>/`. In this case, yo
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--entryPoints.web.address=":80"
|
--entryPoints.web.address=:80
|
||||||
--entryPoints.websecure.address=":443"
|
--entryPoints.websecure.address=:443
|
||||||
--certificatesResolvers.sample.acme.email: your-email@your-domain.org
|
--certificatesResolvers.sample.acme.email=your-email@your-domain.org
|
||||||
--certificatesResolvers.sample.acme.storage: acme.json
|
--certificatesResolvers.sample.acme.storage=acme.json
|
||||||
--certificatesResolvers.sample.acme.httpChallenge.entryPoint: web
|
--certificatesResolvers.sample.acme.httpChallenge.entryPoint=web
|
||||||
```
|
```
|
||||||
|
|
||||||
## Traefik Logs
|
## Traefik Logs
|
||||||
@@ -744,9 +744,9 @@ There is no more log configuration at the root level.
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--logLevel="DEBUG"
|
--logLevel=DEBUG
|
||||||
--traefikLog.filePath="/path/to/traefik.log"
|
--traefikLog.filePath=/path/to/traefik.log
|
||||||
--traefikLog.format="json"
|
--traefikLog.format=json
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! info "v2"
|
!!! info "v2"
|
||||||
@@ -768,9 +768,9 @@ There is no more log configuration at the root level.
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--log.level="DEBUG"
|
--log.level=DEBUG
|
||||||
--log.filePath="/path/to/traefik.log"
|
--log.filePath=/path/to/traefik.log
|
||||||
--log.format="json"
|
--log.format=json
|
||||||
```
|
```
|
||||||
|
|
||||||
## Tracing
|
## Tracing
|
||||||
@@ -794,12 +794,12 @@ Traefik v2 retains OpenTracing support. The `backend` root option from the v1 is
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.backend="jaeger"
|
--tracing.backend=jaeger
|
||||||
--tracing.servicename="tracing"
|
--tracing.servicename=tracing
|
||||||
--tracing.jaeger.localagenthostport="12.0.0.1:6831"
|
--tracing.jaeger.localagenthostport=12.0.0.1:6831
|
||||||
--tracing.jaeger.samplingparam="1.0"
|
--tracing.jaeger.samplingparam=1.0
|
||||||
--tracing.jaeger.samplingserverurl="http://12.0.0.1:5778/sampling"
|
--tracing.jaeger.samplingserverurl=http://12.0.0.1:5778/sampling
|
||||||
--tracing.jaeger.samplingtype="const"
|
--tracing.jaeger.samplingtype=const
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! info "v2"
|
!!! info "v2"
|
||||||
@@ -827,11 +827,11 @@ Traefik v2 retains OpenTracing support. The `backend` root option from the v1 is
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.servicename="tracing"
|
--tracing.servicename=tracing
|
||||||
--tracing.jaeger.localagenthostport="12.0.0.1:6831"
|
--tracing.jaeger.localagenthostport=12.0.0.1:6831
|
||||||
--tracing.jaeger.samplingparam="1.0"
|
--tracing.jaeger.samplingparam=1.0
|
||||||
--tracing.jaeger.samplingserverurl="http://12.0.0.1:5778/sampling"
|
--tracing.jaeger.samplingserverurl=http://12.0.0.1:5778/sampling
|
||||||
--tracing.jaeger.samplingtype="const"
|
--tracing.jaeger.samplingtype=const
|
||||||
```
|
```
|
||||||
|
|
||||||
## Metrics
|
## Metrics
|
||||||
@@ -852,7 +852,7 @@ For a basic configuration, the [metrics configuration](../observability/metrics/
|
|||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--metrics.prometheus.buckets=[0.1,0.3,1.2,5.0]
|
--metrics.prometheus.buckets=[0.1,0.3,1.2,5.0]
|
||||||
--metrics.prometheus.entrypoint="traefik"
|
--metrics.prometheus.entrypoint=traefik
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! info "v2"
|
!!! info "v2"
|
||||||
@@ -878,7 +878,7 @@ For a basic configuration, the [metrics configuration](../observability/metrics/
|
|||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--metrics.prometheus.buckets=[0.1,0.3,1.2,5.0]
|
--metrics.prometheus.buckets=[0.1,0.3,1.2,5.0]
|
||||||
--metrics.prometheus.entrypoint="metrics"
|
--metrics.prometheus.entrypoint=metrics
|
||||||
```
|
```
|
||||||
|
|
||||||
## No More Root Level Key/Values
|
## No More Root Level Key/Values
|
||||||
@@ -908,14 +908,14 @@ Each root item has been moved to a related section or removed.
|
|||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--checknewversion=false
|
--checknewversion=false
|
||||||
--sendanonymoususage=true
|
--sendanonymoususage=true
|
||||||
--loglevel="DEBUG"
|
--loglevel=DEBUG
|
||||||
--insecureskipverify=true
|
--insecureskipverify=true
|
||||||
--rootcas="/mycert.cert"
|
--rootcas=/mycert.cert
|
||||||
--maxidleconnsperhost=200
|
--maxidleconnsperhost=200
|
||||||
--providersthrottleduration="2s"
|
--providersthrottleduration=2s
|
||||||
--allowminweightzero=true
|
--allowminweightzero=true
|
||||||
--debug=true
|
--debug=true
|
||||||
--defaultentrypoints="web","web-secure"
|
--defaultentrypoints=web,web-secure
|
||||||
--keeptrailingslash=true
|
--keeptrailingslash=true
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -961,9 +961,9 @@ Each root item has been moved to a related section or removed.
|
|||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--global.checknewversion=true
|
--global.checknewversion=true
|
||||||
--global.sendanonymoususage=true
|
--global.sendanonymoususage=true
|
||||||
--log.level="DEBUG"
|
--log.level=DEBUG
|
||||||
--serverstransport.insecureskipverify=true
|
--serverstransport.insecureskipverify=true
|
||||||
--serverstransport.rootcas="/mycert.cert"
|
--serverstransport.rootcas=/mycert.cert
|
||||||
--serverstransport.maxidleconnsperhost=42
|
--serverstransport.maxidleconnsperhost=42
|
||||||
--providers.providersthrottleduration=42
|
--providers.providersthrottleduration=42
|
||||||
```
|
```
|
||||||
@@ -974,7 +974,7 @@ You need to activate the API to access the [dashboard](../operations/dashboard.m
|
|||||||
As the dashboard access is now secured by default you can either:
|
As the dashboard access is now secured by default you can either:
|
||||||
|
|
||||||
* define a [specific router](../operations/api.md#configuration) with the `api@internal` service and one authentication middleware like the following example
|
* define a [specific router](../operations/api.md#configuration) with the `api@internal` service and one authentication middleware like the following example
|
||||||
* or use the [unsecure](../operations/api.md#insecure) option of the API
|
* or use the [insecure](../operations/api.md#insecure) option of the API
|
||||||
|
|
||||||
!!! info "Dashboard with k8s and dedicated router"
|
!!! info "Dashboard with k8s and dedicated router"
|
||||||
|
|
||||||
@@ -1029,12 +1029,12 @@ As the dashboard access is now secured by default you can either:
|
|||||||
[api]
|
[api]
|
||||||
|
|
||||||
[providers.file]
|
[providers.file]
|
||||||
filename = "/dynamic-conf.toml"
|
directory = "/path/to/dynamic/config"
|
||||||
|
|
||||||
##---------------------##
|
##---------------------##
|
||||||
|
|
||||||
## dynamic configuration
|
## dynamic configuration
|
||||||
# dynamic-conf.toml
|
# /path/to/dynamic/config/dynamic-conf.toml
|
||||||
|
|
||||||
[http.routers.api]
|
[http.routers.api]
|
||||||
rule = "Host(`traefik.docker.localhost`)"
|
rule = "Host(`traefik.docker.localhost`)"
|
||||||
@@ -1061,12 +1061,12 @@ As the dashboard access is now secured by default you can either:
|
|||||||
|
|
||||||
providers:
|
providers:
|
||||||
file:
|
file:
|
||||||
filename: /dynamic-conf.yaml
|
directory: /path/to/dynamic/config
|
||||||
|
|
||||||
##---------------------##
|
##---------------------##
|
||||||
|
|
||||||
## dynamic configuration
|
## dynamic configuration
|
||||||
# dynamic-conf.yaml
|
# /path/to/dynamic/config/dynamic-conf.yaml
|
||||||
|
|
||||||
http:
|
http:
|
||||||
routers:
|
routers:
|
||||||
|
@@ -61,7 +61,7 @@ accessLog:
|
|||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
# Configuring a buffer of 100 lines
|
# Configuring a buffer of 100 lines
|
||||||
--accesslog=true
|
--accesslog=true
|
||||||
--accesslog.filepath="/path/to/access.log"
|
--accesslog.filepath=/path/to/access.log
|
||||||
--accesslog.bufferingsize=100
|
--accesslog.bufferingsize=100
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -104,11 +104,11 @@ accessLog:
|
|||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
# Configuring Multiple Filters
|
# Configuring Multiple Filters
|
||||||
--accesslog=true
|
--accesslog=true
|
||||||
--accesslog.filepath="/path/to/access.log"
|
--accesslog.filepath=/path/to/access.log
|
||||||
--accesslog.format="json"
|
--accesslog.format=json
|
||||||
--accesslog.filters.statuscodes="200, 300-302"
|
--accesslog.filters.statuscodes=200,300-302
|
||||||
--accesslog.filters.retryattempts
|
--accesslog.filters.retryattempts
|
||||||
--accesslog.filters.minduration="10ms"
|
--accesslog.filters.minduration=10ms
|
||||||
```
|
```
|
||||||
|
|
||||||
### Limiting the Fields
|
### Limiting the Fields
|
||||||
@@ -164,14 +164,14 @@ accessLog:
|
|||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
# Limiting the Logs to Specific Fields
|
# Limiting the Logs to Specific Fields
|
||||||
--accesslog=true
|
--accesslog=true
|
||||||
--accesslog.filepath="/path/to/access.log"
|
--accesslog.filepath=/path/to/access.log
|
||||||
--accesslog.format="json"
|
--accesslog.format=json
|
||||||
--accesslog.fields.defaultmode="keep"
|
--accesslog.fields.defaultmode=keep
|
||||||
--accesslog.fields.names.ClientUsername="drop"
|
--accesslog.fields.names.ClientUsername=drop
|
||||||
--accesslog.fields.headers.defaultmode="keep"
|
--accesslog.fields.headers.defaultmode=keep
|
||||||
--accesslog.fields.headers.names.User-Agent="redact"
|
--accesslog.fields.headers.names.User-Agent=redact
|
||||||
--accesslog.fields.headers.names.Authorization="drop"
|
--accesslog.fields.headers.names.Authorization=drop
|
||||||
--accesslog.fields.headers.names.Content-Type="keep"
|
--accesslog.fields.headers.names.Content-Type=keep
|
||||||
```
|
```
|
||||||
|
|
||||||
??? info "Available Fields"
|
??? info "Available Fields"
|
||||||
|
@@ -30,7 +30,7 @@ log:
|
|||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
# Writing Logs to a File
|
# Writing Logs to a File
|
||||||
--log.filePath="/path/to/traefik.log"
|
--log.filePath=/path/to/traefik.log
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `format`
|
#### `format`
|
||||||
@@ -53,8 +53,8 @@ log:
|
|||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
# Writing Logs to a File, in JSON
|
# Writing Logs to a File, in JSON
|
||||||
--log.filePath="/path/to/traefik.log"
|
--log.filePath=/path/to/traefik.log
|
||||||
--log.format="json"
|
--log.format=json
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `level`
|
#### `level`
|
||||||
@@ -72,7 +72,7 @@ log:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--log.level="DEBUG"
|
--log.level=DEBUG
|
||||||
```
|
```
|
||||||
|
|
||||||
## Log Rotation
|
## Log Rotation
|
||||||
|
@@ -35,7 +35,7 @@ metrics:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--metrics.datadog.address="127.0.0.1:8125"
|
--metrics.datadog.address=127.0.0.1:8125
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `addEntryPointsLabels`
|
#### `addEntryPointsLabels`
|
||||||
|
@@ -35,7 +35,7 @@ metrics:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--metrics.influxdb.address="localhost:8089"
|
--metrics.influxdb.address=localhost:8089
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `protocol`
|
#### `protocol`
|
||||||
@@ -57,7 +57,7 @@ metrics:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--metrics.influxdb.protocol="udp"
|
--metrics.influxdb.protocol=udp
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `database`
|
#### `database`
|
||||||
@@ -69,17 +69,17 @@ InfluxDB database used when protocol is http.
|
|||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
[metrics]
|
[metrics]
|
||||||
[metrics.influxDB]
|
[metrics.influxDB]
|
||||||
database = ""
|
database = "db"
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
metrics:
|
metrics:
|
||||||
influxDB:
|
influxDB:
|
||||||
database: ""
|
database: "db"
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--metrics.influxdb.database=""
|
--metrics.influxdb.database=db
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `retentionPolicy`
|
#### `retentionPolicy`
|
||||||
@@ -91,17 +91,17 @@ InfluxDB retention policy used when protocol is http.
|
|||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
[metrics]
|
[metrics]
|
||||||
[metrics.influxDB]
|
[metrics.influxDB]
|
||||||
retentionPolicy = ""
|
retentionPolicy = "two_hours"
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
metrics:
|
metrics:
|
||||||
influxDB:
|
influxDB:
|
||||||
retentionPolicy: ""
|
retentionPolicy: "two_hours"
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--metrics.influxdb.retentionPolicy=""
|
--metrics.influxdb.retentionPolicy=two_hours
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `username`
|
#### `username`
|
||||||
@@ -113,17 +113,17 @@ InfluxDB username (only with http).
|
|||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
[metrics]
|
[metrics]
|
||||||
[metrics.influxDB]
|
[metrics.influxDB]
|
||||||
username = ""
|
username = "john"
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
metrics:
|
metrics:
|
||||||
influxDB:
|
influxDB:
|
||||||
username: ""
|
username: "john"
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--metrics.influxdb.username=""
|
--metrics.influxdb.username=john
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `password`
|
#### `password`
|
||||||
@@ -135,17 +135,17 @@ InfluxDB password (only with http).
|
|||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
[metrics]
|
[metrics]
|
||||||
[metrics.influxDB]
|
[metrics.influxDB]
|
||||||
password = ""
|
password = "secret"
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
metrics:
|
metrics:
|
||||||
influxDB:
|
influxDB:
|
||||||
password: ""
|
password: "secret"
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--metrics.influxdb.password=""
|
--metrics.influxdb.password=secret
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `addEntryPointsLabels`
|
#### `addEntryPointsLabels`
|
||||||
|
@@ -113,6 +113,6 @@ metrics:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--entryPoints.metrics.address=":8082"
|
--entryPoints.metrics.address=:8082
|
||||||
--metrics.prometheus.entryPoint="metrics"
|
--metrics.prometheus.entryPoint=metrics
|
||||||
```
|
```
|
||||||
|
@@ -35,7 +35,7 @@ metrics:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--metrics.statsd.address="localhost:8125"
|
--metrics.statsd.address=localhost:8125
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `addEntryPointsLabels`
|
#### `addEntryPointsLabels`
|
||||||
|
@@ -35,7 +35,7 @@ tracing:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.datadog.localAgentHostPort="127.0.0.1:8126"
|
--tracing.datadog.localAgentHostPort=127.0.0.1:8126
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `debug`
|
#### `debug`
|
||||||
@@ -79,7 +79,7 @@ tracing:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.datadog.globalTag="sample"
|
--tracing.datadog.globalTag=sample
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `prioritySampling`
|
#### `prioritySampling`
|
||||||
|
@@ -35,7 +35,7 @@ tracing:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.haystack.localAgentHost="127.0.0.1"
|
--tracing.haystack.localAgentHost=127.0.0.1
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `localAgentPort`
|
#### `localAgentPort`
|
||||||
@@ -79,7 +79,7 @@ tracing:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.haystack.globalTag="sample:test"
|
--tracing.haystack.globalTag=sample:test
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `traceIDHeaderName`
|
#### `traceIDHeaderName`
|
||||||
@@ -101,7 +101,7 @@ tracing:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.haystack.traceIDHeaderName="sample"
|
--tracing.haystack.traceIDHeaderName=sample
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `parentIDHeaderName`
|
#### `parentIDHeaderName`
|
||||||
@@ -123,7 +123,7 @@ tracing:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.haystack.parentIDHeaderName="sample"
|
--tracing.haystack.parentIDHeaderName=sample
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `spanIDHeaderName`
|
#### `spanIDHeaderName`
|
||||||
@@ -168,5 +168,5 @@ tracing:
|
|||||||
|
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.haystack.baggagePrefixHeaderName="sample"
|
--tracing.haystack.baggagePrefixHeaderName=sample
|
||||||
```
|
```
|
||||||
|
@@ -35,7 +35,7 @@ tracing:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.instana.localAgentHost="127.0.0.1"
|
--tracing.instana.localAgentHost=127.0.0.1
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `localAgentPort`
|
#### `localAgentPort`
|
||||||
@@ -86,5 +86,5 @@ tracing:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.instana.logLevel="info"
|
--tracing.instana.logLevel=info
|
||||||
```
|
```
|
||||||
|
@@ -39,7 +39,7 @@ tracing:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.jaeger.samplingServerURL="http://localhost:5778/sampling"
|
--tracing.jaeger.samplingServerURL=http://localhost:5778/sampling
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `samplingType`
|
#### `samplingType`
|
||||||
@@ -61,7 +61,7 @@ tracing:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.jaeger.samplingType="const"
|
--tracing.jaeger.samplingType=const
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `samplingParam`
|
#### `samplingParam`
|
||||||
@@ -89,7 +89,7 @@ tracing:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.jaeger.samplingParam="1.0"
|
--tracing.jaeger.samplingParam=1.0
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `localAgentHostPort`
|
#### `localAgentHostPort`
|
||||||
@@ -111,7 +111,7 @@ tracing:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.jaeger.localAgentHostPort="127.0.0.1:6831"
|
--tracing.jaeger.localAgentHostPort=127.0.0.1:6831
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `gen128Bit`
|
#### `gen128Bit`
|
||||||
@@ -159,7 +159,7 @@ tracing:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.jaeger.propagation="jaeger"
|
--tracing.jaeger.propagation=jaeger
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `traceContextHeaderName`
|
#### `traceContextHeaderName`
|
||||||
@@ -182,7 +182,7 @@ tracing:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.jaeger.traceContextHeaderName="uber-trace-id"
|
--tracing.jaeger.traceContextHeaderName=uber-trace-id
|
||||||
```
|
```
|
||||||
|
|
||||||
### `collector`
|
### `collector`
|
||||||
@@ -206,7 +206,7 @@ tracing:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.jaeger.collector.endpoint="http://127.0.0.1:14268/api/traces?format=jaeger.thrift"
|
--tracing.jaeger.collector.endpoint=http://127.0.0.1:14268/api/traces?format=jaeger.thrift
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `user`
|
#### `user`
|
||||||
@@ -229,7 +229,7 @@ tracing:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.jaeger.collector.user="my-user"
|
--tracing.jaeger.collector.user=my-user
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `password`
|
#### `password`
|
||||||
@@ -252,5 +252,5 @@ tracing:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.jaeger.collector.password="my-password"
|
--tracing.jaeger.collector.password=my-password
|
||||||
```
|
```
|
||||||
|
@@ -52,7 +52,7 @@ tracing:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.serviceName="traefik"
|
--tracing.serviceName=traefik
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `spanNameLimit`
|
#### `spanNameLimit`
|
||||||
|
@@ -35,7 +35,7 @@ tracing:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.zipkin.httpEndpoint="http://localhost:9411/api/v2/spans"
|
--tracing.zipkin.httpEndpoint=http://localhost:9411/api/v2/spans
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `sameSpan`
|
#### `sameSpan`
|
||||||
@@ -101,5 +101,5 @@ tracing:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.zipkin.sampleRate="0.2"
|
--tracing.zipkin.sampleRate=0.2
|
||||||
```
|
```
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../.markdownlint.json",
|
"extends": "../../.markdownlint.json",
|
||||||
|
"MD041": false,
|
||||||
"MD046": false
|
"MD046": false
|
||||||
}
|
}
|
||||||
|
@@ -43,63 +43,7 @@ api: {}
|
|||||||
And then define a routing configuration on Traefik itself with the
|
And then define a routing configuration on Traefik itself with the
|
||||||
[dynamic configuration](../getting-started/configuration-overview.md#the-dynamic-configuration):
|
[dynamic configuration](../getting-started/configuration-overview.md#the-dynamic-configuration):
|
||||||
|
|
||||||
```yaml tab="Docker"
|
--8<-- "content/operations/include-api-examples.md"
|
||||||
# Dynamic Configuration
|
|
||||||
labels:
|
|
||||||
- "traefik.http.routers.api.rule=Host(`traefik.domain.com`)
|
|
||||||
- "traefik.http.routers.api.service=api@internal"
|
|
||||||
- "traefik.http.routers.api.middlewares=auth"
|
|
||||||
- "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
|
|
||||||
```
|
|
||||||
|
|
||||||
```json tab="Marathon"
|
|
||||||
"labels": {
|
|
||||||
"traefik.http.routers.api.rule": "Host(`traefik.domain.com`)",
|
|
||||||
"traefik.http.routers.api.service": "api@internal",
|
|
||||||
"traefik.http.routers.api.middlewares": "auth",
|
|
||||||
"traefik.http.middlewares.auth.basicauth.users": "test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml tab="Rancher"
|
|
||||||
# Dynamic Configuration
|
|
||||||
labels:
|
|
||||||
- "traefik.http.routers.api.rule=Host(`traefik.domain.com`)
|
|
||||||
- "traefik.http.routers.api.service=api@internal"
|
|
||||||
- "traefik.http.routers.api.middlewares=auth"
|
|
||||||
- "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
|
|
||||||
```
|
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
|
||||||
# Dynamic Configuration
|
|
||||||
[http.routers.my-api]
|
|
||||||
rule="Host(`traefik.domain.com`)
|
|
||||||
service="api@internal"
|
|
||||||
middlewares=["auth"]
|
|
||||||
|
|
||||||
[http.middlewares.auth.basicAuth]
|
|
||||||
users = [
|
|
||||||
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
|
||||||
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
|
||||||
# Dynamic Configuration
|
|
||||||
http:
|
|
||||||
routers:
|
|
||||||
api:
|
|
||||||
rule: Host(`traefik.domain.com`)
|
|
||||||
service: api@internal
|
|
||||||
middlewares:
|
|
||||||
- auth
|
|
||||||
middlewares:
|
|
||||||
auth:
|
|
||||||
basicAuth:
|
|
||||||
users:
|
|
||||||
- "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"
|
|
||||||
- "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"
|
|
||||||
```
|
|
||||||
|
|
||||||
??? warning "The router's [rule](../../routing/routers#rule) must catch requests for the URI path `/api`"
|
??? warning "The router's [rule](../../routing/routers#rule) must catch requests for the URI path `/api`"
|
||||||
Using an "Host" rule is recommended, by catching all the incoming traffic on this host domain to the API.
|
Using an "Host" rule is recommended, by catching all the incoming traffic on this host domain to the API.
|
||||||
|
@@ -60,8 +60,8 @@ api:
|
|||||||
--api.dashboard=true
|
--api.dashboard=true
|
||||||
```
|
```
|
||||||
|
|
||||||
Then define a routing configuration on Traefik itself,
|
Then define a routing configuration on Traefik itself,
|
||||||
with a router attached to the service `api@internal` in the
|
with a router attached to the service `api@internal` in the
|
||||||
[dynamic configuration](../getting-started/configuration-overview.md#the-dynamic-configuration),
|
[dynamic configuration](../getting-started/configuration-overview.md#the-dynamic-configuration),
|
||||||
to allow defining:
|
to allow defining:
|
||||||
|
|
||||||
@@ -73,64 +73,7 @@ to allow defining:
|
|||||||
through Traefik itself (sometimes referred as "Traefik-ception").
|
through Traefik itself (sometimes referred as "Traefik-ception").
|
||||||
|
|
||||||
??? example "Dashboard Dynamic Configuration Examples"
|
??? example "Dashboard Dynamic Configuration Examples"
|
||||||
|
--8<-- "content/operations/include-api-examples.md"
|
||||||
```yaml tab="Docker"
|
|
||||||
# Dynamic Configuration
|
|
||||||
labels:
|
|
||||||
- "traefik.http.routers.api.rule=Host(`traefik.domain.com`)
|
|
||||||
- "traefik.http.routers.api.service=api@internal"
|
|
||||||
- "traefik.http.routers.api.middlewares=auth"
|
|
||||||
- "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
|
|
||||||
```
|
|
||||||
|
|
||||||
```json tab="Marathon"
|
|
||||||
"labels": {
|
|
||||||
"traefik.http.routers.api.rule": "Host(`traefik.domain.com`)",
|
|
||||||
"traefik.http.routers.api.service": "api@internal",
|
|
||||||
"traefik.http.routers.api.middlewares": "auth",
|
|
||||||
"traefik.http.middlewares.auth.basicauth.users": "test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml tab="Rancher"
|
|
||||||
# Dynamic Configuration
|
|
||||||
labels:
|
|
||||||
- "traefik.http.routers.api.rule=Host(`traefik.domain.com`)
|
|
||||||
- "traefik.http.routers.api.service=api@internal"
|
|
||||||
- "traefik.http.routers.api.middlewares=auth"
|
|
||||||
- "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
|
|
||||||
```
|
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
|
||||||
# Dynamic Configuration
|
|
||||||
[http.routers.my-api]
|
|
||||||
rule="Host(`traefik.domain.com`)
|
|
||||||
service="api@internal"
|
|
||||||
middlewares=["auth"]
|
|
||||||
|
|
||||||
[http.middlewares.auth.basicAuth]
|
|
||||||
users = [
|
|
||||||
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
|
||||||
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
|
||||||
# Dynamic Configuration
|
|
||||||
http:
|
|
||||||
routers:
|
|
||||||
api:
|
|
||||||
rule: Host(`traefik.domain.com`)
|
|
||||||
service: api@internal
|
|
||||||
middlewares:
|
|
||||||
- auth
|
|
||||||
middlewares:
|
|
||||||
auth:
|
|
||||||
basicAuth:
|
|
||||||
users:
|
|
||||||
- "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"
|
|
||||||
- "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dashboard Router Rule
|
### Dashboard Router Rule
|
||||||
|
|
||||||
@@ -142,19 +85,17 @@ We recommend to use a "Host Based rule" as ```Host(`traefik.domain.com`)``` to m
|
|||||||
or to make sure that the defined rule captures both prefixes:
|
or to make sure that the defined rule captures both prefixes:
|
||||||
|
|
||||||
```bash tab="Host Rule"
|
```bash tab="Host Rule"
|
||||||
# Matches http://traefik.domain.com/api or http://traefik.domain.com/dashboard
|
# The dashboard can be accessed on http://traefik.domain.com/dashboard/
|
||||||
rule = "Host(`traefik.domain.com`)"
|
rule = "Host(`traefik.domain.com`)"
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="Path Prefix Rule"
|
```bash tab="Path Prefix Rule"
|
||||||
# Matches http://traefik.domain.com/api , http://domain.com/api or http://traefik.domain.com/dashboard
|
# The dashboard can be accessed on http://domain.com/dashboard/ or http://traefik.domain.com/dashboard/
|
||||||
# but does not match http://traefik.domain.com/hello
|
|
||||||
rule = "PathPrefix(`/api`) || PathPrefix(`/dashboard`)"
|
rule = "PathPrefix(`/api`) || PathPrefix(`/dashboard`)"
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="Combination of Rules"
|
```bash tab="Combination of Rules"
|
||||||
# Matches http://traefik.domain.com/api or http://traefik.domain.com/dashboard
|
# The dashboard can be accessed on http://traefik.domain.com/dashboard/
|
||||||
# but does not match http://traefik.domain.com/hello
|
|
||||||
rule = "Host(`traefik.domain.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
|
rule = "Host(`traefik.domain.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
69
docs/content/operations/include-api-examples.md
Normal file
69
docs/content/operations/include-api-examples.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
```yaml tab="Docker"
|
||||||
|
# Dynamic Configuration
|
||||||
|
labels:
|
||||||
|
- "traefik.http.routers.api.rule=Host(`traefik.domain.com`)"
|
||||||
|
- "traefik.http.routers.api.service=api@internal"
|
||||||
|
- "traefik.http.routers.api.middlewares=auth"
|
||||||
|
- "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Docker (Swarm)"
|
||||||
|
# Dynamic Configuration
|
||||||
|
deploy:
|
||||||
|
labels:
|
||||||
|
- "traefik.http.routers.api.rule=Host(`traefik.domain.com`)"
|
||||||
|
- "traefik.http.routers.api.service=api@internal"
|
||||||
|
- "traefik.http.routers.api.middlewares=auth"
|
||||||
|
- "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
|
||||||
|
# Dummy service for Swarm port detection. The port can be any valid integer value.
|
||||||
|
- "traefik.http.services.dummy-svc.loadbalancer.server.port=9999"
|
||||||
|
```
|
||||||
|
|
||||||
|
```json tab="Marathon"
|
||||||
|
"labels": {
|
||||||
|
"traefik.http.routers.api.rule": "Host(`traefik.domain.com`)",
|
||||||
|
"traefik.http.routers.api.service": "api@internal",
|
||||||
|
"traefik.http.routers.api.middlewares": "auth",
|
||||||
|
"traefik.http.middlewares.auth.basicauth.users": "test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Rancher"
|
||||||
|
# Dynamic Configuration
|
||||||
|
labels:
|
||||||
|
- "traefik.http.routers.api.rule=Host(`traefik.domain.com`)"
|
||||||
|
- "traefik.http.routers.api.service=api@internal"
|
||||||
|
- "traefik.http.routers.api.middlewares=auth"
|
||||||
|
- "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
# Dynamic Configuration
|
||||||
|
[http.routers.my-api]
|
||||||
|
rule = "Host(`traefik.domain.com`)"
|
||||||
|
service = "api@internal"
|
||||||
|
middlewares = ["auth"]
|
||||||
|
|
||||||
|
[http.middlewares.auth.basicAuth]
|
||||||
|
users = [
|
||||||
|
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
||||||
|
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
# Dynamic Configuration
|
||||||
|
http:
|
||||||
|
routers:
|
||||||
|
api:
|
||||||
|
rule: Host(`traefik.domain.com`)
|
||||||
|
service: api@internal
|
||||||
|
middlewares:
|
||||||
|
- auth
|
||||||
|
middlewares:
|
||||||
|
auth:
|
||||||
|
basicAuth:
|
||||||
|
users:
|
||||||
|
- "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"
|
||||||
|
- "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"
|
||||||
|
```
|
@@ -55,6 +55,6 @@ ping:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--entryPoints.ping.address=":8082"
|
--entryPoints.ping.address=:8082
|
||||||
--ping.entryPoint="ping"
|
--ping.entryPoint=ping
|
||||||
```
|
```
|
||||||
|
@@ -7,6 +7,9 @@ A Story of Labels & Containers
|
|||||||
|
|
||||||
Attach labels to your containers and let Traefik do the rest!
|
Attach labels to your containers and let Traefik do the rest!
|
||||||
|
|
||||||
|
Traefik works with both [Docker (standalone) Engine](https://docs.docker.com/engine/)
|
||||||
|
and [Docker Swarm Mode](https://docs.docker.com/engine/swarm/).
|
||||||
|
|
||||||
!!! tip "The Quick Start Uses Docker"
|
!!! tip "The Quick Start Uses Docker"
|
||||||
If you haven't already, maybe you'd like to go through the [quick start](../getting-started/quick-start.md) that uses the docker provider!
|
If you haven't already, maybe you'd like to go through the [quick start](../getting-started/quick-start.md) that uses the docker provider!
|
||||||
|
|
||||||
@@ -64,7 +67,7 @@ Attach labels to your containers and let Traefik do the rest!
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.docker.endpoint="tcp://127.0.0.1:2375"
|
--providers.docker.endpoint=tcp://127.0.0.1:2375
|
||||||
--providers.docker.swarmMode=true
|
--providers.docker.swarmMode=true
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -80,15 +83,136 @@ Attach labels to your containers and let Traefik do the rest!
|
|||||||
- traefik.http.services.my-container-service.loadbalancer.server.port=8080
|
- traefik.http.services.my-container-service.loadbalancer.server.port=8080
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! important "Labels in Docker Swarm Mode"
|
|
||||||
While in Swarm Mode, Traefik uses labels found on services, not on individual containers.
|
|
||||||
|
|
||||||
Therefore, if you use a compose file with Swarm Mode, labels should be defined in the `deploy` part of your service.
|
|
||||||
This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file/#labels-1)).
|
|
||||||
|
|
||||||
## Routing Configuration
|
## Routing Configuration
|
||||||
|
|
||||||
See the dedicated section in [routing](../routing/providers/docker.md).
|
When using Docker as a [provider](https://docs.traefik.io/providers/overview/),
|
||||||
|
Trafik uses [container labels](https://docs.docker.com/engine/reference/commandline/run/#set-metadata-on-container--l---label---label-file) to retrieve its routing configuration.
|
||||||
|
|
||||||
|
See the list of labels in the dedicated [routing](../routing/providers/docker.md) section.
|
||||||
|
|
||||||
|
### Routing Configuration with Labels
|
||||||
|
|
||||||
|
By default, Traefik watches for [container level labels](https://docs.docker.com/config/labels-custom-metadata/) on a standalone Docker Engine.
|
||||||
|
|
||||||
|
When using Docker Compose, labels are specified by the directive
|
||||||
|
[`labels`](https://docs.docker.com/compose/compose-file/#labels) from the
|
||||||
|
["services" objects](https://docs.docker.com/compose/compose-file/#service-configuration-reference).
|
||||||
|
|
||||||
|
!!! tip "Not Only Docker"
|
||||||
|
Please note that any tool like Nomad, Terraform, Ansible, etc.
|
||||||
|
that is able to define a Docker container with labels can work
|
||||||
|
with Traefik & the Docker provider.
|
||||||
|
|
||||||
|
### Port Detection
|
||||||
|
|
||||||
|
Traefik retrieves the private IP and port of containers from the Docker API.
|
||||||
|
|
||||||
|
Ports detection works as follows:
|
||||||
|
|
||||||
|
- If a container [exposes](https://docs.docker.com/engine/reference/builder/#expose) only one port,
|
||||||
|
then Traefik uses this port for private communication.
|
||||||
|
- If a container [exposes](https://docs.docker.com/engine/reference/builder/#expose) multiple ports,
|
||||||
|
or does not expose any port, then you must manually specify which port Traefik should use for communication
|
||||||
|
by using the label `traefik.http.services.<service_name>.loadbalancer.server.port`
|
||||||
|
(Read more on this label in the dedicated section in [routing](../routing/providers/docker.md#port)).
|
||||||
|
|
||||||
|
### Docker API Access
|
||||||
|
|
||||||
|
Traefik requires access to the docker socket to get its dynamic configuration.
|
||||||
|
|
||||||
|
You can specify which Docker API Endpoint to use with the directive [`endpoint`](#endpoint).
|
||||||
|
|
||||||
|
!!! warning "Security Note"
|
||||||
|
|
||||||
|
Accessing the Docker API without any restriction is a security concern:
|
||||||
|
If Traefik is attacked, then the attacker might get access to the underlying host.
|
||||||
|
{: #security-note }
|
||||||
|
|
||||||
|
As explained in the Docker documentation: ([Docker Daemon Attack Surface page](https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface)):
|
||||||
|
|
||||||
|
!!! quote
|
||||||
|
[...] only **trusted** users should be allowed to control your Docker daemon [...]
|
||||||
|
|
||||||
|
??? success "Solutions"
|
||||||
|
|
||||||
|
Expose the Docker socket over TCP, instead of the default Unix socket file.
|
||||||
|
It allows different implementation levels of the [AAA (Authentication, Authorization, Accounting) concepts](https://en.wikipedia.org/wiki/AAA_(computer_security)), depending on your security assessment:
|
||||||
|
|
||||||
|
- Authentication with Client Certificates as described in ["Protect the Docker daemon socket."](https://docs.docker.com/engine/security/https/)
|
||||||
|
- Authorize and filter requests to restrict possible actions with [the TecnativaDocker Socket Proxy](https://github.com/Tecnativa/docker-socket-proxy).
|
||||||
|
- Authorization with the [Docker Authorization Plugin Mechanism](https://docs.docker.com/engine/extend/plugins_authorization/)
|
||||||
|
- Accounting at networking level, by exposing the socket only inside a Docker private network, only available for Traefik.
|
||||||
|
- Accounting at container level, by exposing the socket on a another container than Traefik's.
|
||||||
|
With Swarm mode, it allows scheduling of Traefik on worker nodes, with only the "socket exposer" container on the manager nodes.
|
||||||
|
- Accounting at kernel level, by enforcing kernel calls with mechanisms like [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux), to only allows an identified set of actions for Traefik's process (or the "socket exposer" process).
|
||||||
|
|
||||||
|
??? info "More Resources and Examples"
|
||||||
|
- ["Paranoid about mounting /var/run/docker.sock?"](https://medium.com/@containeroo/traefik-2-0-paranoid-about-mounting-var-run-docker-sock-22da9cb3e78c)
|
||||||
|
- [Traefik and Docker: A Discussion with Docker Captain, Bret Fisher](https://blog.containo.us/traefik-and-docker-a-discussion-with-docker-captain-bret-fisher-7f0b9a54ff88)
|
||||||
|
- [KubeCon EU 2018 Keynote, Running with Scissors, from Liz Rice](https://www.youtube.com/watch?v=ltrV-Qmh3oY)
|
||||||
|
- [Don't expose the Docker socket (not even to a container)](https://www.lvh.io/posts/dont-expose-the-docker-socket-not-even-to-a-container/)
|
||||||
|
- [A thread on Stack Overflow about sharing the `/var/run/docker.sock` file](https://news.ycombinator.com/item?id=17983623)
|
||||||
|
- [To DinD or not to DinD](https://blog.loof.fr/2018/01/to-dind-or-not-do-dind.html)
|
||||||
|
- [Traefik issue GH-4174 about security with Docker socket](https://github.com/containous/traefik/issues/4174)
|
||||||
|
- [Inspecting Docker Activity with Socat](https://developers.redhat.com/blog/2015/02/25/inspecting-docker-activity-with-socat/)
|
||||||
|
- [Letting Traefik run on Worker Nodes](https://blog.mikesir87.io/2018/07/letting-traefik-run-on-worker-nodes/)
|
||||||
|
- [Docker Socket Proxy from Tecnativa](https://github.com/Tecnativa/docker-socket-proxy)
|
||||||
|
|
||||||
|
## Docker Swarm Mode
|
||||||
|
|
||||||
|
To enable Docker Swarm (instead of standalone Docker) as a configuration provider,
|
||||||
|
set the [`swarmMode`](#swarmmode) directive to `true`.
|
||||||
|
|
||||||
|
### Routing Configuration with Labels
|
||||||
|
|
||||||
|
While in Swarm Mode, Traefik uses labels found on services, not on individual containers.
|
||||||
|
|
||||||
|
Therefore, if you use a compose file with Swarm Mode, labels should be defined in the
|
||||||
|
[`deploy`](https://docs.docker.com/compose/compose-file/#labels-1) part of your service.
|
||||||
|
|
||||||
|
This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file)).
|
||||||
|
|
||||||
|
### Port Detection
|
||||||
|
|
||||||
|
Docker Swarm does not provide any [port detection](#port-detection) information to Traefik.
|
||||||
|
|
||||||
|
Therefore you **must** specify the port to use for communication by using the label `traefik.http.services.<service_name>.loadbalancer.server.port`
|
||||||
|
(Check the reference for this label in the [routing section for Docker](../routing/providers/docker.md#port)).
|
||||||
|
|
||||||
|
### Docker API Access
|
||||||
|
|
||||||
|
Docker Swarm Mode follows the same rules as Docker [API Access](#docker-api-access).
|
||||||
|
|
||||||
|
As the Swarm API is only exposed on the [manager nodes](https://docs.docker.com/engine/swarm/how-swarm-mode-works/nodes/#manager-nodes), you should schedule Traefik on the Swarm manager nodes by default,
|
||||||
|
by deploying Traefik with a [constraint](https://success.docker.com/article/using-contraints-and-labels-to-control-the-placement-of-containers) on the node's "role":
|
||||||
|
|
||||||
|
```shell tab="With Docker CLI"
|
||||||
|
docker service create \
|
||||||
|
--constraint=node.role==manager \
|
||||||
|
#... \
|
||||||
|
```
|
||||||
|
|
||||||
|
```yml tab="With Docker Compose"
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
traefik:
|
||||||
|
# ...
|
||||||
|
deploy:
|
||||||
|
placement:
|
||||||
|
constraints:
|
||||||
|
- node.role == manager
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! tip "Scheduling Traefik on Worker Nodes"
|
||||||
|
|
||||||
|
Following the guidelines given in the previous section ["Docker API Access"](#docker-api-access),
|
||||||
|
if you expose the Docker API through TCP, then Traefik can be scheduled on any node if the TCP
|
||||||
|
socket is reachable.
|
||||||
|
|
||||||
|
Please consider the security implications by reading the [Security Note](#security-note).
|
||||||
|
|
||||||
|
A good example can be found on [Bret Fisher's repository](https://github.com/BretFisher/dogvscat/blob/master/stack-proxy-global.yml#L124).
|
||||||
|
|
||||||
## Provider Configuration
|
## Provider Configuration
|
||||||
|
|
||||||
@@ -108,51 +232,10 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.docker.endpoint="unix:///var/run/docker.sock"
|
--providers.docker.endpoint=unix:///var/run/docker.sock
|
||||||
```
|
```
|
||||||
|
|
||||||
Traefik requires access to the docker socket to get its dynamic configuration.
|
See the sections [Docker API Access](#docker-api-access) and [Docker Swarm API Access](#docker-api-access_1) for more information.
|
||||||
|
|
||||||
??? warning "Security Notes"
|
|
||||||
|
|
||||||
Depending on your context, accessing the Docker API without any restriction can be a security concern: If Traefik is attacked, then the attacker might get access to the Docker (or Swarm Mode) backend.
|
|
||||||
|
|
||||||
As explained in the Docker documentation: ([Docker Daemon Attack Surface page](https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface)):
|
|
||||||
|
|
||||||
`[...] only **trusted** users should be allowed to control your Docker daemon [...]`
|
|
||||||
|
|
||||||
!!! tip "Improved Security"
|
|
||||||
|
|
||||||
[TraefikEE](https://containo.us/traefikee) solves this problem by separating the control plane (connected to Docker) and the data plane (handling the requests).
|
|
||||||
|
|
||||||
??? info "Resources about Docker's Security"
|
|
||||||
|
|
||||||
- [KubeCon EU 2018 Keynote, Running with Scissors, from Liz Rice](https://www.youtube.com/watch?v=ltrV-Qmh3oY)
|
|
||||||
- [Don't expose the Docker socket (not even to a container)](https://www.lvh.io/posts/dont-expose-the-docker-socket-not-even-to-a-container/)
|
|
||||||
- [A thread on Stack Overflow about sharing the `/var/run/docker.sock` file](https://news.ycombinator.com/item?id=17983623)
|
|
||||||
- [To DinD or not to DinD](https://blog.loof.fr/2018/01/to-dind-or-not-do-dind.html)
|
|
||||||
|
|
||||||
??? tip "Security Compensation"
|
|
||||||
|
|
||||||
Expose the Docker socket over TCP, instead of the default Unix socket file.
|
|
||||||
It allows different implementation levels of the [AAA (Authentication, Authorization, Accounting) concepts](https://en.wikipedia.org/wiki/AAA_(computer_security)), depending on your security assessment:
|
|
||||||
|
|
||||||
- Authentication with Client Certificates as described in ["Protect the Docker daemon socket."](https://docs.docker.com/engine/security/https/)
|
|
||||||
- Authorization with the [Docker Authorization Plugin Mechanism](https://docs.docker.com/engine/extend/plugins_authorization/)
|
|
||||||
- Accounting at networking level, by exposing the socket only inside a Docker private network, only available for Traefik.
|
|
||||||
- Accounting at container level, by exposing the socket on a another container than Traefik's.
|
|
||||||
With Swarm mode, it allows scheduling of Traefik on worker nodes, with only the "socket exposer" container on the manager nodes.
|
|
||||||
- Accounting at kernel level, by enforcing kernel calls with mechanisms like [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux), to only allows an identified set of actions for Traefik's process (or the "socket exposer" process).
|
|
||||||
|
|
||||||
??? info "Additional Resources"
|
|
||||||
|
|
||||||
- [Traefik issue GH-4174 about security with Docker socket](https://github.com/containous/traefik/issues/4174)
|
|
||||||
- [Inspecting Docker Activity with Socat](https://developers.redhat.com/blog/2015/02/25/inspecting-docker-activity-with-socat/)
|
|
||||||
- [Letting Traefik run on Worker Nodes](https://blog.mikesir87.io/2018/07/letting-traefik-run-on-worker-nodes/)
|
|
||||||
- [Docker Socket Proxy from Tecnativa](https://github.com/Tecnativa/docker-socket-proxy)
|
|
||||||
|
|
||||||
!!! info "Traefik & Swarm Mode"
|
|
||||||
To let Traefik access the Docker Socket of the Swarm manager, it is mandatory to schedule Traefik on the Swarm manager nodes.
|
|
||||||
|
|
||||||
??? example "Using the docker.sock"
|
??? example "Using the docker.sock"
|
||||||
|
|
||||||
@@ -186,7 +269,7 @@ Traefik requires access to the docker socket to get its dynamic configuration.
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.docker.endpoint="unix:///var/run/docker.sock"
|
--providers.docker.endpoint=unix:///var/run/docker.sock
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -311,7 +394,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.docker.defaultRule="Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
|
--providers.docker.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -343,7 +426,7 @@ providers:
|
|||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
Activates the Swarm Mode.
|
Activates the Swarm Mode (instead of standalone Docker).
|
||||||
|
|
||||||
### `swarmModeRefreshSeconds`
|
### `swarmModeRefreshSeconds`
|
||||||
|
|
||||||
@@ -375,19 +458,19 @@ _Optional, Default=""_
|
|||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
[providers.docker]
|
[providers.docker]
|
||||||
constraints = "Label(`a.label.name`, `foo`)"
|
constraints = "Label(`a.label.name`,`foo`)"
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
providers:
|
providers:
|
||||||
docker:
|
docker:
|
||||||
constraints: "Label(`a.label.name`, `foo`)"
|
constraints: "Label(`a.label.name`,`foo`)"
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.docker.constraints="Label(`a.label.name`, `foo`)"
|
--providers.docker.constraints=Label(`a.label.name`,`foo`)
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@@ -23,17 +23,17 @@ You can write one of these mutually exclusive configuration elements:
|
|||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
[providers.file]
|
[providers.file]
|
||||||
filename = "/my/path/to/dynamic-conf.toml"
|
directory = "/path/to/dynamic/conf"
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
providers:
|
providers:
|
||||||
file:
|
file:
|
||||||
filename: "/my/path/to/dynamic-conf.yml"
|
directory: "/path/to/dynamic/conf"
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.file.filename=/my/path/to/dynamic_conf.toml
|
--providers.file.directory=/path/to/dynamic/conf
|
||||||
```
|
```
|
||||||
|
|
||||||
Declaring Routers, Middlewares & Services:
|
Declaring Routers, Middlewares & Services:
|
||||||
@@ -100,6 +100,22 @@ You can write one of these mutually exclusive configuration elements:
|
|||||||
|
|
||||||
If you're in a hurry, maybe you'd rather go through the [dynamic configuration](../reference/dynamic-configuration/file.md) references and the [static configuration](../reference/static-configuration/overview.md).
|
If you're in a hurry, maybe you'd rather go through the [dynamic configuration](../reference/dynamic-configuration/file.md) references and the [static configuration](../reference/static-configuration/overview.md).
|
||||||
|
|
||||||
|
!!! warning "Limitations"
|
||||||
|
|
||||||
|
With the file provider, Traefik listens for file system notifications to update the dynamic configuration.
|
||||||
|
|
||||||
|
If you use a mounted/bound file system in your orchestrator (like docker or kubernetes), the way the files are linked may be a source of errors.
|
||||||
|
If the link between the file systems is broken, when a source file/directory is changed/renamed, nothing will be reported to the linked file/directory, so the file system notifications will be neither triggered nor caught.
|
||||||
|
|
||||||
|
For example, in docker, if the host file is renamed, the link to the mounted file will be broken and the container's file will not be updated.
|
||||||
|
To avoid this kind of issue, a good practice is to:
|
||||||
|
|
||||||
|
* set the Traefik [**directory**](#directory) configuration with the parent directory
|
||||||
|
* mount/bind the parent directory
|
||||||
|
|
||||||
|
As it is very difficult to listen to all file system notifications, Traefik use [fsnotify](https://github.com/fsnotify/fsnotify).
|
||||||
|
If using a directory with a mounted directory does not fix your issue, please check your file system compatibility with fsnotify.
|
||||||
|
|
||||||
### `filename`
|
### `filename`
|
||||||
|
|
||||||
Defines the path of the configuration file.
|
Defines the path of the configuration file.
|
||||||
@@ -148,19 +164,19 @@ It works with both the `filename` and the `directory` options.
|
|||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
[providers]
|
[providers]
|
||||||
[providers.file]
|
[providers.file]
|
||||||
filename = "dynamic_conf.toml"
|
directory = "/path/to/dynamic/conf"
|
||||||
watch = true
|
watch = true
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
providers:
|
providers:
|
||||||
file:
|
file:
|
||||||
filename: dynamic_conf.yml
|
directory: /path/to/dynamic/conf
|
||||||
watch: true
|
watch: true
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.file.filename=dynamic_conf.toml
|
--providers.file.directory=/my/path/to/dynamic/conf
|
||||||
--providers.file.watch=true
|
--providers.file.watch=true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@@ -12,6 +12,23 @@ we ended up writing a [Custom Resource Definition](https://kubernetes.io/docs/co
|
|||||||
|
|
||||||
See the dedicated section in [routing](../routing/providers/kubernetes-crd.md).
|
See the dedicated section in [routing](../routing/providers/kubernetes-crd.md).
|
||||||
|
|
||||||
|
## LetsEncrypt Support with the Custom Resource Definition Provider
|
||||||
|
|
||||||
|
By design, Traefik is a stateless application, meaning that it only derives its configuration from the environment it runs in, without additional configuration.
|
||||||
|
For this reason, users can run multiple instances of Traefik at the same time to achieve HA, as is a common pattern in the kubernetes ecosystem.
|
||||||
|
|
||||||
|
When using a single instance of Traefik with LetsEncrypt, no issues should be encountered, however this could be a single point of failure.
|
||||||
|
Unfortunately, it is not possible to run multiple instances of Traefik 2.0 with LetsEncrypt enabled, because there is no way to ensure that the correct instance of Traefik will receive the challenge request, and subsequent responses.
|
||||||
|
Previous versions of Traefik used a [KV store](https://docs.traefik.io/v1.7/configuration/acme/#storage) to attempt to achieve this, but due to sub-optimal performance was dropped as a feature in 2.0.
|
||||||
|
|
||||||
|
If you require LetsEncrypt with HA in a kubernetes environment, we recommend using [TraefikEE](https://containo.us/traefikee/) where distributed LetsEncrypt is a supported feature.
|
||||||
|
|
||||||
|
If you are wanting to continue to run Traefik Community Edition, LetsEncrypt HA can be achieved by using a Certificate Controller such as [Cert-Manager](https://docs.cert-manager.io/en/latest/index.html).
|
||||||
|
When using Cert-Manager to manage certificates, it will create secrets in your namespaces that can be referenced as TLS secrets in your [ingress objects](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls).
|
||||||
|
When using the Traefik Kubernetes CRD Provider, unfortunately Cert-Manager cannot interface directly with the CRDs _yet_, but this is being worked on by our team.
|
||||||
|
A workaround it to enable the [Kubernetes Ingress provider](./kubernetes-ingress.md) to allow Cert-Manager to create ingress objects to complete the challenges.
|
||||||
|
Please note that this still requires manual intervention to create the certificates through Cert-Manager, but once created, Cert-Manager will keep the certificate renewed.
|
||||||
|
|
||||||
## Provider Configuration
|
## Provider Configuration
|
||||||
|
|
||||||
### `endpoint`
|
### `endpoint`
|
||||||
@@ -32,7 +49,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.kubernetescrd.endpoint="http://localhost:8080"
|
--providers.kubernetescrd.endpoint=http://localhost:8080
|
||||||
```
|
```
|
||||||
|
|
||||||
The Kubernetes server endpoint as URL.
|
The Kubernetes server endpoint as URL.
|
||||||
@@ -66,7 +83,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.kubernetescrd.token="mytoken"
|
--providers.kubernetescrd.token=mytoken
|
||||||
```
|
```
|
||||||
|
|
||||||
Bearer token used for the Kubernetes client configuration.
|
Bearer token used for the Kubernetes client configuration.
|
||||||
@@ -89,7 +106,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.kubernetescrd.certauthfilepath="/my/ca.crt"
|
--providers.kubernetescrd.certauthfilepath=/my/ca.crt
|
||||||
```
|
```
|
||||||
|
|
||||||
Path to the certificate authority file.
|
Path to the certificate authority file.
|
||||||
@@ -115,7 +132,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.kubernetescrd.namespaces="default,production"
|
--providers.kubernetescrd.namespaces=default,production
|
||||||
```
|
```
|
||||||
|
|
||||||
Array of namespaces to watch.
|
Array of namespaces to watch.
|
||||||
@@ -164,7 +181,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.kubernetescrd.ingressclass="traefik-internal"
|
--providers.kubernetescrd.ingressclass=traefik-internal
|
||||||
```
|
```
|
||||||
|
|
||||||
Value of `kubernetes.io/ingress.class` annotation that identifies Ingress objects to be processed.
|
Value of `kubernetes.io/ingress.class` annotation that identifies Ingress objects to be processed.
|
||||||
@@ -190,7 +207,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.kubernetescrd.throttleDuration="10s"
|
--providers.kubernetescrd.throttleDuration=10s
|
||||||
```
|
```
|
||||||
|
|
||||||
## Further
|
## Further
|
||||||
|
@@ -47,6 +47,20 @@ spec:
|
|||||||
servicePort: 80
|
servicePort: 80
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## LetsEncrypt Support with the Ingress Provider
|
||||||
|
|
||||||
|
By design, Traefik is a stateless application, meaning that it only derives its configuration from the environment it runs in, without additional configuration.
|
||||||
|
For this reason, users can run multiple instances of Traefik at the same time to achieve HA, as is a common pattern in the kubernetes ecosystem.
|
||||||
|
|
||||||
|
When using a single instance of Traefik with LetsEncrypt, no issues should be encountered, however this could be a single point of failure.
|
||||||
|
Unfortunately, it is not possible to run multiple instances of Traefik 2.0 with LetsEncrypt enabled, because there is no way to ensure that the correct instance of Traefik will receive the challenge request, and subsequent responses.
|
||||||
|
Previous versions of Traefik used a [KV store](https://docs.traefik.io/v1.7/configuration/acme/#storage) to attempt to achieve this, but due to sub-optimal performance was dropped as a feature in 2.0.
|
||||||
|
|
||||||
|
If you require LetsEncrypt with HA in a kubernetes environment, we recommend using [TraefikEE](https://containo.us/traefikee/) where distributed LetsEncrypt is a supported feature.
|
||||||
|
|
||||||
|
If you are wanting to continue to run Traefik Community Edition, LetsEncrypt HA can be achieved by using a Certificate Controller such as [Cert-Manager](https://docs.cert-manager.io/en/latest/index.html).
|
||||||
|
When using Cert-Manager to manage certificates, it will create secrets in your namespaces that can be referenced as TLS secrets in your [ingress objects](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls).
|
||||||
|
|
||||||
## Provider Configuration
|
## Provider Configuration
|
||||||
|
|
||||||
### `endpoint`
|
### `endpoint`
|
||||||
@@ -67,7 +81,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.kubernetesingress.endpoint="http://localhost:8080"
|
--providers.kubernetesingress.endpoint=http://localhost:8080
|
||||||
```
|
```
|
||||||
|
|
||||||
The Kubernetes server endpoint as URL, which is only used when the behavior based on environment variables described below does not apply.
|
The Kubernetes server endpoint as URL, which is only used when the behavior based on environment variables described below does not apply.
|
||||||
@@ -99,7 +113,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.kubernetesingress.token="mytoken"
|
--providers.kubernetesingress.token=mytoken
|
||||||
```
|
```
|
||||||
|
|
||||||
Bearer token used for the Kubernetes client configuration.
|
Bearer token used for the Kubernetes client configuration.
|
||||||
@@ -122,7 +136,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.kubernetesingress.certauthfilepath="/my/ca.crt"
|
--providers.kubernetesingress.certauthfilepath=/my/ca.crt
|
||||||
```
|
```
|
||||||
|
|
||||||
Path to the certificate authority file.
|
Path to the certificate authority file.
|
||||||
@@ -171,7 +185,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.kubernetesingress.namespaces="default,production"
|
--providers.kubernetesingress.namespaces=default,production
|
||||||
```
|
```
|
||||||
|
|
||||||
Array of namespaces to watch.
|
Array of namespaces to watch.
|
||||||
@@ -220,7 +234,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.kubernetesingress.ingressclass="traefik-internal"
|
--providers.kubernetesingress.ingressclass=traefik-internal
|
||||||
```
|
```
|
||||||
|
|
||||||
Value of `kubernetes.io/ingress.class` annotation that identifies Ingress objects to be processed.
|
Value of `kubernetes.io/ingress.class` annotation that identifies Ingress objects to be processed.
|
||||||
@@ -249,7 +263,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.kubernetesingress.ingressendpoint.hostname="foo.com"
|
--providers.kubernetesingress.ingressendpoint.hostname=foo.com
|
||||||
```
|
```
|
||||||
|
|
||||||
Hostname used for Kubernetes Ingress endpoints.
|
Hostname used for Kubernetes Ingress endpoints.
|
||||||
@@ -273,7 +287,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.kubernetesingress.ingressendpoint.ip="1.2.3.4"
|
--providers.kubernetesingress.ingressendpoint.ip=1.2.3.4
|
||||||
```
|
```
|
||||||
|
|
||||||
IP used for Kubernetes Ingress endpoints.
|
IP used for Kubernetes Ingress endpoints.
|
||||||
@@ -297,7 +311,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.kubernetesingress.ingressendpoint.publishedservice="foo-service"
|
--providers.kubernetesingress.ingressendpoint.publishedservice=foo-service
|
||||||
```
|
```
|
||||||
|
|
||||||
Published Kubernetes Service to copy status from.
|
Published Kubernetes Service to copy status from.
|
||||||
@@ -320,7 +334,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.kubernetesingress.throttleDuration="10s"
|
--providers.kubernetesingress.throttleDuration=10s
|
||||||
```
|
```
|
||||||
|
|
||||||
## Further
|
## Further
|
||||||
|
@@ -74,8 +74,8 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.marathon.basic.httpbasicauthuser="foo"
|
--providers.marathon.basic.httpbasicauthuser=foo
|
||||||
--providers.marathon.basic.httpbasicpassword="bar"
|
--providers.marathon.basic.httpbasicpassword=bar
|
||||||
```
|
```
|
||||||
|
|
||||||
Enables Marathon basic authentication.
|
Enables Marathon basic authentication.
|
||||||
@@ -98,7 +98,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.marathon.dcosToken="xxxxxx"
|
--providers.marathon.dcosToken=xxxxxx
|
||||||
```
|
```
|
||||||
|
|
||||||
DCOSToken for DCOS environment.
|
DCOSToken for DCOS environment.
|
||||||
@@ -123,7 +123,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.marathon.defaultRule="Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
|
--providers.marathon.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -182,7 +182,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.marathon.endpoint="http://10.241.1.71:8080,10.241.1.72:8080,10.241.1.73:8080"
|
--providers.marathon.endpoint=http://10.241.1.71:8080,10.241.1.72:8080,10.241.1.73:8080
|
||||||
```
|
```
|
||||||
|
|
||||||
Marathon server endpoint.
|
Marathon server endpoint.
|
||||||
@@ -223,19 +223,19 @@ _Optional, Default=""_
|
|||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
[providers.marathon]
|
[providers.marathon]
|
||||||
constraints = "Label(`a.label.name`, `foo`)"
|
constraints = "Label(`a.label.name`,`foo`)"
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
providers:
|
providers:
|
||||||
marathon:
|
marathon:
|
||||||
constraints: "Label(`a.label.name`, `foo`)"
|
constraints: "Label(`a.label.name`,`foo`)"
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.marathon.constraints="Label(`a.label.name`, `foo`)"
|
--providers.marathon.constraints=Label(`a.label.name`,`foo`)
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -389,7 +389,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.marathon.responseHeaderTimeout="66s"
|
--providers.marathon.responseHeaderTimeout=66s
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -532,7 +532,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.marathon.responseHeaderTimeout="10s"
|
--providers.marathon.responseHeaderTimeout=10s
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@@ -104,7 +104,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.rancher.defaultRule="Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
|
--providers.rancher.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -209,7 +209,7 @@ providers:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.rancher.prefix="/test"
|
--providers.rancher.prefix=/test
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -221,19 +221,19 @@ _Optional, Default=""_
|
|||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
[providers.rancher]
|
[providers.rancher]
|
||||||
constraints = "Label(`a.label.name`, `foo`)"
|
constraints = "Label(`a.label.name`,`foo`)"
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
providers:
|
providers:
|
||||||
rancher:
|
rancher:
|
||||||
constraints: "Label(`a.label.name`, `foo`)"
|
constraints: "Label(`a.label.name`,`foo`)"
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.rancher.constraints="Label(`a.label.name`, `foo`)"
|
--providers.rancher.constraints=Label(`a.label.name`,`foo`)
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@@ -17,4 +17,4 @@
|
|||||||
--providers.rancher.intervalPoll=false
|
--providers.rancher.intervalPoll=false
|
||||||
|
|
||||||
# Prefix used for accessing the Rancher metadata service
|
# Prefix used for accessing the Rancher metadata service
|
||||||
--providers.rancher.prefix="/latest"
|
--providers.rancher.prefix=/latest
|
||||||
|
@@ -18,4 +18,4 @@ providers:
|
|||||||
intervalPoll: false
|
intervalPoll: false
|
||||||
|
|
||||||
# Prefix used for accessing the Rancher metadata service
|
# Prefix used for accessing the Rancher metadata service
|
||||||
prefix: "/latest"
|
prefix: /latest
|
||||||
|
@@ -103,6 +103,7 @@
|
|||||||
- "traefik.http.middlewares.middleware17.replacepathregex.regex=foobar"
|
- "traefik.http.middlewares.middleware17.replacepathregex.regex=foobar"
|
||||||
- "traefik.http.middlewares.middleware17.replacepathregex.replacement=foobar"
|
- "traefik.http.middlewares.middleware17.replacepathregex.replacement=foobar"
|
||||||
- "traefik.http.middlewares.middleware18.retry.attempts=42"
|
- "traefik.http.middlewares.middleware18.retry.attempts=42"
|
||||||
|
- "traefik.http.middlewares.middleware19.stripprefix.forceslash=true"
|
||||||
- "traefik.http.middlewares.middleware19.stripprefix.prefixes=foobar, foobar"
|
- "traefik.http.middlewares.middleware19.stripprefix.prefixes=foobar, foobar"
|
||||||
- "traefik.http.middlewares.middleware20.stripprefixregex.regex=foobar, foobar"
|
- "traefik.http.middlewares.middleware20.stripprefixregex.regex=foobar, foobar"
|
||||||
- "traefik.http.routers.router0.entrypoints=foobar, foobar"
|
- "traefik.http.routers.router0.entrypoints=foobar, foobar"
|
||||||
@@ -129,38 +130,22 @@
|
|||||||
- "traefik.http.routers.router1.tls.domains[1].main=foobar"
|
- "traefik.http.routers.router1.tls.domains[1].main=foobar"
|
||||||
- "traefik.http.routers.router1.tls.domains[1].sans=foobar, foobar"
|
- "traefik.http.routers.router1.tls.domains[1].sans=foobar, foobar"
|
||||||
- "traefik.http.routers.router1.tls.options=foobar"
|
- "traefik.http.routers.router1.tls.options=foobar"
|
||||||
- "traefik.http.services.service0.loadbalancer.healthcheck.headers.name0=foobar"
|
- "traefik.http.services.service01.loadbalancer.healthcheck.headers.name0=foobar"
|
||||||
- "traefik.http.services.service0.loadbalancer.healthcheck.headers.name1=foobar"
|
- "traefik.http.services.service01.loadbalancer.healthcheck.headers.name1=foobar"
|
||||||
- "traefik.http.services.service0.loadbalancer.healthcheck.hostname=foobar"
|
- "traefik.http.services.service01.loadbalancer.healthcheck.hostname=foobar"
|
||||||
- "traefik.http.services.service0.loadbalancer.healthcheck.interval=foobar"
|
- "traefik.http.services.service01.loadbalancer.healthcheck.interval=foobar"
|
||||||
- "traefik.http.services.service0.loadbalancer.healthcheck.path=foobar"
|
- "traefik.http.services.service01.loadbalancer.healthcheck.path=foobar"
|
||||||
- "traefik.http.services.service0.loadbalancer.healthcheck.port=42"
|
- "traefik.http.services.service01.loadbalancer.healthcheck.port=42"
|
||||||
- "traefik.http.services.service0.loadbalancer.healthcheck.scheme=foobar"
|
- "traefik.http.services.service01.loadbalancer.healthcheck.scheme=foobar"
|
||||||
- "traefik.http.services.service0.loadbalancer.healthcheck.timeout=foobar"
|
- "traefik.http.services.service01.loadbalancer.healthcheck.timeout=foobar"
|
||||||
- "traefik.http.services.service0.loadbalancer.passhostheader=true"
|
- "traefik.http.services.service01.loadbalancer.passhostheader=true"
|
||||||
- "traefik.http.services.service0.loadbalancer.responseforwarding.flushinterval=foobar"
|
- "traefik.http.services.service01.loadbalancer.responseforwarding.flushinterval=foobar"
|
||||||
- "traefik.http.services.service0.loadbalancer.sticky=true"
|
- "traefik.http.services.service01.loadbalancer.sticky=true"
|
||||||
- "traefik.http.services.service0.loadbalancer.sticky.cookie.httponly=true"
|
- "traefik.http.services.service01.loadbalancer.sticky.cookie.httponly=true"
|
||||||
- "traefik.http.services.service0.loadbalancer.sticky.cookie.name=foobar"
|
- "traefik.http.services.service01.loadbalancer.sticky.cookie.name=foobar"
|
||||||
- "traefik.http.services.service0.loadbalancer.sticky.cookie.secure=true"
|
- "traefik.http.services.service01.loadbalancer.sticky.cookie.secure=true"
|
||||||
- "traefik.http.services.service0.loadbalancer.server.port=foobar"
|
- "traefik.http.services.service01.loadbalancer.server.port=foobar"
|
||||||
- "traefik.http.services.service0.loadbalancer.server.scheme=foobar"
|
- "traefik.http.services.service01.loadbalancer.server.scheme=foobar"
|
||||||
- "traefik.http.services.service1.loadbalancer.healthcheck.headers.name0=foobar"
|
|
||||||
- "traefik.http.services.service1.loadbalancer.healthcheck.headers.name1=foobar"
|
|
||||||
- "traefik.http.services.service1.loadbalancer.healthcheck.hostname=foobar"
|
|
||||||
- "traefik.http.services.service1.loadbalancer.healthcheck.interval=foobar"
|
|
||||||
- "traefik.http.services.service1.loadbalancer.healthcheck.path=foobar"
|
|
||||||
- "traefik.http.services.service1.loadbalancer.healthcheck.port=42"
|
|
||||||
- "traefik.http.services.service1.loadbalancer.healthcheck.scheme=foobar"
|
|
||||||
- "traefik.http.services.service1.loadbalancer.healthcheck.timeout=foobar"
|
|
||||||
- "traefik.http.services.service1.loadbalancer.passhostheader=true"
|
|
||||||
- "traefik.http.services.service1.loadbalancer.responseforwarding.flushinterval=foobar"
|
|
||||||
- "traefik.http.services.service1.loadbalancer.sticky=true"
|
|
||||||
- "traefik.http.services.service1.loadbalancer.sticky.cookie.httponly=true"
|
|
||||||
- "traefik.http.services.service1.loadbalancer.sticky.cookie.name=foobar"
|
|
||||||
- "traefik.http.services.service1.loadbalancer.sticky.cookie.secure=true"
|
|
||||||
- "traefik.http.services.service1.loadbalancer.server.port=foobar"
|
|
||||||
- "traefik.http.services.service1.loadbalancer.server.scheme=foobar"
|
|
||||||
- "traefik.tcp.routers.tcprouter0.entrypoints=foobar, foobar"
|
- "traefik.tcp.routers.tcprouter0.entrypoints=foobar, foobar"
|
||||||
- "traefik.tcp.routers.tcprouter0.rule=foobar"
|
- "traefik.tcp.routers.tcprouter0.rule=foobar"
|
||||||
- "traefik.tcp.routers.tcprouter0.service=foobar"
|
- "traefik.tcp.routers.tcprouter0.service=foobar"
|
||||||
@@ -183,7 +168,5 @@
|
|||||||
- "traefik.tcp.routers.tcprouter1.tls.domains[1].sans=foobar, foobar"
|
- "traefik.tcp.routers.tcprouter1.tls.domains[1].sans=foobar, foobar"
|
||||||
- "traefik.tcp.routers.tcprouter1.tls.options=foobar"
|
- "traefik.tcp.routers.tcprouter1.tls.options=foobar"
|
||||||
- "traefik.tcp.routers.tcprouter1.tls.passthrough=true"
|
- "traefik.tcp.routers.tcprouter1.tls.passthrough=true"
|
||||||
- "traefik.tcp.services.tcpservice0.loadbalancer.server.port=foobar"
|
- "traefik.tcp.services.tcpservice01.loadbalancer.terminationdelay=42"
|
||||||
- "traefik.tcp.services.tcpservice0.loadbalancer.terminationdelay=100"
|
- "traefik.tcp.services.tcpservice01.loadbalancer.server.port=foobar"
|
||||||
- "traefik.tcp.services.tcpservice1.loadbalancer.server.port=foobar"
|
|
||||||
- "traefik.tcp.services.tcpservice1.loadbalancer.terminationdelay=100"
|
|
||||||
|
@@ -245,6 +245,7 @@
|
|||||||
[http.middlewares.Middleware19]
|
[http.middlewares.Middleware19]
|
||||||
[http.middlewares.Middleware19.stripPrefix]
|
[http.middlewares.Middleware19.stripPrefix]
|
||||||
prefixes = ["foobar", "foobar"]
|
prefixes = ["foobar", "foobar"]
|
||||||
|
forceSlash = true
|
||||||
[http.middlewares.Middleware20]
|
[http.middlewares.Middleware20]
|
||||||
[http.middlewares.Middleware20.stripPrefixRegex]
|
[http.middlewares.Middleware20.stripPrefixRegex]
|
||||||
regex = ["foobar", "foobar"]
|
regex = ["foobar", "foobar"]
|
||||||
@@ -284,25 +285,25 @@
|
|||||||
main = "foobar"
|
main = "foobar"
|
||||||
sans = ["foobar", "foobar"]
|
sans = ["foobar", "foobar"]
|
||||||
[tcp.services]
|
[tcp.services]
|
||||||
[tcp.services.TCPService0]
|
[tcp.services.TCPService01]
|
||||||
[tcp.services.TCPService0.loadBalancer]
|
[tcp.services.TCPService01.loadBalancer]
|
||||||
terminationDelay = 100
|
terminationDelay = 42
|
||||||
|
|
||||||
[[tcp.services.TCPService0.loadBalancer.servers]]
|
[[tcp.services.TCPService01.loadBalancer.servers]]
|
||||||
address = "foobar"
|
address = "foobar"
|
||||||
|
|
||||||
[[tcp.services.TCPService0.loadBalancer.servers]]
|
[[tcp.services.TCPService01.loadBalancer.servers]]
|
||||||
address = "foobar"
|
address = "foobar"
|
||||||
|
[tcp.services.TCPService02]
|
||||||
|
[tcp.services.TCPService02.weighted]
|
||||||
|
|
||||||
[tcp.services.TCPService1]
|
[[tcp.services.TCPService02.weighted.services]]
|
||||||
[tcp.services.TCPService1.loadBalancer]
|
name = "foobar"
|
||||||
terminationDelay = 100
|
weight = 42
|
||||||
|
|
||||||
[[tcp.services.TCPService1.loadBalancer.servers]]
|
[[tcp.services.TCPService02.weighted.services]]
|
||||||
address = "foobar"
|
name = "foobar"
|
||||||
|
weight = 42
|
||||||
[[tcp.services.TCPService1.loadBalancer.servers]]
|
|
||||||
address = "foobar"
|
|
||||||
|
|
||||||
[tls]
|
[tls]
|
||||||
|
|
||||||
|
@@ -2,11 +2,11 @@ http:
|
|||||||
routers:
|
routers:
|
||||||
Router0:
|
Router0:
|
||||||
entryPoints:
|
entryPoints:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
middlewares:
|
middlewares:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
service: foobar
|
service: foobar
|
||||||
rule: foobar
|
rule: foobar
|
||||||
priority: 42
|
priority: 42
|
||||||
@@ -14,21 +14,21 @@ http:
|
|||||||
options: foobar
|
options: foobar
|
||||||
certResolver: foobar
|
certResolver: foobar
|
||||||
domains:
|
domains:
|
||||||
- main: foobar
|
- main: foobar
|
||||||
sans:
|
sans:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
- main: foobar
|
- main: foobar
|
||||||
sans:
|
sans:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
Router1:
|
Router1:
|
||||||
entryPoints:
|
entryPoints:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
middlewares:
|
middlewares:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
service: foobar
|
service: foobar
|
||||||
rule: foobar
|
rule: foobar
|
||||||
priority: 42
|
priority: 42
|
||||||
@@ -36,14 +36,14 @@ http:
|
|||||||
options: foobar
|
options: foobar
|
||||||
certResolver: foobar
|
certResolver: foobar
|
||||||
domains:
|
domains:
|
||||||
- main: foobar
|
- main: foobar
|
||||||
sans:
|
sans:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
- main: foobar
|
- main: foobar
|
||||||
sans:
|
sans:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
services:
|
services:
|
||||||
Service01:
|
Service01:
|
||||||
loadBalancer:
|
loadBalancer:
|
||||||
@@ -53,8 +53,8 @@ http:
|
|||||||
secure: true
|
secure: true
|
||||||
httpOnly: true
|
httpOnly: true
|
||||||
servers:
|
servers:
|
||||||
- url: foobar
|
- url: foobar
|
||||||
- url: foobar
|
- url: foobar
|
||||||
healthCheck:
|
healthCheck:
|
||||||
scheme: foobar
|
scheme: foobar
|
||||||
path: foobar
|
path: foobar
|
||||||
@@ -72,17 +72,17 @@ http:
|
|||||||
mirroring:
|
mirroring:
|
||||||
service: foobar
|
service: foobar
|
||||||
mirrors:
|
mirrors:
|
||||||
- name: foobar
|
- name: foobar
|
||||||
percent: 42
|
percent: 42
|
||||||
- name: foobar
|
- name: foobar
|
||||||
percent: 42
|
percent: 42
|
||||||
Service03:
|
Service03:
|
||||||
weighted:
|
weighted:
|
||||||
services:
|
services:
|
||||||
- name: foobar
|
- name: foobar
|
||||||
weight: 42
|
weight: 42
|
||||||
- name: foobar
|
- name: foobar
|
||||||
weight: 42
|
weight: 42
|
||||||
sticky:
|
sticky:
|
||||||
cookie:
|
cookie:
|
||||||
name: foobar
|
name: foobar
|
||||||
@@ -95,8 +95,8 @@ http:
|
|||||||
Middleware01:
|
Middleware01:
|
||||||
basicAuth:
|
basicAuth:
|
||||||
users:
|
users:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
usersFile: foobar
|
usersFile: foobar
|
||||||
realm: foobar
|
realm: foobar
|
||||||
removeHeader: true
|
removeHeader: true
|
||||||
@@ -111,8 +111,8 @@ http:
|
|||||||
Middleware03:
|
Middleware03:
|
||||||
chain:
|
chain:
|
||||||
middlewares:
|
middlewares:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
Middleware04:
|
Middleware04:
|
||||||
circuitBreaker:
|
circuitBreaker:
|
||||||
expression: foobar
|
expression: foobar
|
||||||
@@ -121,8 +121,8 @@ http:
|
|||||||
Middleware06:
|
Middleware06:
|
||||||
digestAuth:
|
digestAuth:
|
||||||
users:
|
users:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
usersFile: foobar
|
usersFile: foobar
|
||||||
removeHeader: true
|
removeHeader: true
|
||||||
realm: foobar
|
realm: foobar
|
||||||
@@ -130,8 +130,8 @@ http:
|
|||||||
Middleware07:
|
Middleware07:
|
||||||
errors:
|
errors:
|
||||||
status:
|
status:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
service: foobar
|
service: foobar
|
||||||
query: foobar
|
query: foobar
|
||||||
Middleware08:
|
Middleware08:
|
||||||
@@ -145,8 +145,8 @@ http:
|
|||||||
insecureSkipVerify: true
|
insecureSkipVerify: true
|
||||||
trustForwardHeader: true
|
trustForwardHeader: true
|
||||||
authResponseHeaders:
|
authResponseHeaders:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
Middleware09:
|
Middleware09:
|
||||||
headers:
|
headers:
|
||||||
customRequestHeaders:
|
customRequestHeaders:
|
||||||
@@ -157,23 +157,23 @@ http:
|
|||||||
name1: foobar
|
name1: foobar
|
||||||
accessControlAllowCredentials: true
|
accessControlAllowCredentials: true
|
||||||
accessControlAllowHeaders:
|
accessControlAllowHeaders:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
accessControlAllowMethods:
|
accessControlAllowMethods:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
accessControlAllowOrigin: foobar
|
accessControlAllowOrigin: foobar
|
||||||
accessControlExposeHeaders:
|
accessControlExposeHeaders:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
accessControlMaxAge: 42
|
accessControlMaxAge: 42
|
||||||
addVaryHeader: true
|
addVaryHeader: true
|
||||||
allowedHosts:
|
allowedHosts:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
hostsProxyHeaders:
|
hostsProxyHeaders:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
sslRedirect: true
|
sslRedirect: true
|
||||||
sslTemporaryRedirect: true
|
sslTemporaryRedirect: true
|
||||||
sslHost: foobar
|
sslHost: foobar
|
||||||
@@ -198,13 +198,13 @@ http:
|
|||||||
Middleware10:
|
Middleware10:
|
||||||
ipWhiteList:
|
ipWhiteList:
|
||||||
sourceRange:
|
sourceRange:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
ipStrategy:
|
ipStrategy:
|
||||||
depth: 42
|
depth: 42
|
||||||
excludedIPs:
|
excludedIPs:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
Middleware11:
|
Middleware11:
|
||||||
inFlightReq:
|
inFlightReq:
|
||||||
amount: 42
|
amount: 42
|
||||||
@@ -212,8 +212,8 @@ http:
|
|||||||
ipstrategy:
|
ipstrategy:
|
||||||
depth: 42
|
depth: 42
|
||||||
excludedIPs:
|
excludedIPs:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
requestHeaderName: foobar
|
requestHeaderName: foobar
|
||||||
requestHost: true
|
requestHost: true
|
||||||
Middleware12:
|
Middleware12:
|
||||||
@@ -247,8 +247,8 @@ http:
|
|||||||
ipstrategy:
|
ipstrategy:
|
||||||
depth: 42
|
depth: 42
|
||||||
excludedIPs:
|
excludedIPs:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
requestHeaderName: foobar
|
requestHeaderName: foobar
|
||||||
requestHost: true
|
requestHost: true
|
||||||
Middleware14:
|
Middleware14:
|
||||||
@@ -274,19 +274,20 @@ http:
|
|||||||
Middleware19:
|
Middleware19:
|
||||||
stripPrefix:
|
stripPrefix:
|
||||||
prefixes:
|
prefixes:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
|
forceSlash: true
|
||||||
Middleware20:
|
Middleware20:
|
||||||
stripPrefixRegex:
|
stripPrefixRegex:
|
||||||
regex:
|
regex:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
tcp:
|
tcp:
|
||||||
routers:
|
routers:
|
||||||
TCPRouter0:
|
TCPRouter0:
|
||||||
entryPoints:
|
entryPoints:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
service: foobar
|
service: foobar
|
||||||
rule: foobar
|
rule: foobar
|
||||||
tls:
|
tls:
|
||||||
@@ -294,18 +295,18 @@ tcp:
|
|||||||
options: foobar
|
options: foobar
|
||||||
certResolver: foobar
|
certResolver: foobar
|
||||||
domains:
|
domains:
|
||||||
- main: foobar
|
- main: foobar
|
||||||
sans:
|
sans:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
- main: foobar
|
- main: foobar
|
||||||
sans:
|
sans:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
TCPRouter1:
|
TCPRouter1:
|
||||||
entryPoints:
|
entryPoints:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
service: foobar
|
service: foobar
|
||||||
rule: foobar
|
rule: foobar
|
||||||
tls:
|
tls:
|
||||||
@@ -313,60 +314,61 @@ tcp:
|
|||||||
options: foobar
|
options: foobar
|
||||||
certResolver: foobar
|
certResolver: foobar
|
||||||
domains:
|
domains:
|
||||||
- main: foobar
|
- main: foobar
|
||||||
sans:
|
sans:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
- main: foobar
|
- main: foobar
|
||||||
sans:
|
sans:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
services:
|
services:
|
||||||
TCPService0:
|
TCPService01:
|
||||||
loadBalancer:
|
loadBalancer:
|
||||||
terminationDelay: 100
|
terminationDelay: 42
|
||||||
servers:
|
servers:
|
||||||
- address: foobar
|
- address: foobar
|
||||||
- address: foobar
|
- address: foobar
|
||||||
TCPService1:
|
TCPService02:
|
||||||
loadBalancer:
|
weighted:
|
||||||
terminationDelay: 100
|
services:
|
||||||
servers:
|
- name: foobar
|
||||||
- address: foobar
|
weight: 42
|
||||||
- address: foobar
|
- name: foobar
|
||||||
|
weight: 42
|
||||||
tls:
|
tls:
|
||||||
certificates:
|
certificates:
|
||||||
- certFile: foobar
|
- certFile: foobar
|
||||||
keyFile: foobar
|
keyFile: foobar
|
||||||
stores:
|
stores:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
- certFile: foobar
|
- certFile: foobar
|
||||||
keyFile: foobar
|
keyFile: foobar
|
||||||
stores:
|
stores:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
options:
|
options:
|
||||||
Options0:
|
Options0:
|
||||||
minVersion: foobar
|
minVersion: foobar
|
||||||
cipherSuites:
|
cipherSuites:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
clientAuth:
|
clientAuth:
|
||||||
caFiles:
|
caFiles:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
clientAuthType: foobar
|
clientAuthType: foobar
|
||||||
sniStrict: true
|
sniStrict: true
|
||||||
Options1:
|
Options1:
|
||||||
minVersion: foobar
|
minVersion: foobar
|
||||||
cipherSuites:
|
cipherSuites:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
clientAuth:
|
clientAuth:
|
||||||
caFiles:
|
caFiles:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
clientAuthType: foobar
|
clientAuthType: foobar
|
||||||
sniStrict: true
|
sniStrict: true
|
||||||
stores:
|
stores:
|
||||||
|
@@ -103,6 +103,7 @@
|
|||||||
"traefik.http.middlewares.middleware17.replacepathregex.regex": "foobar",
|
"traefik.http.middlewares.middleware17.replacepathregex.regex": "foobar",
|
||||||
"traefik.http.middlewares.middleware17.replacepathregex.replacement": "foobar",
|
"traefik.http.middlewares.middleware17.replacepathregex.replacement": "foobar",
|
||||||
"traefik.http.middlewares.middleware18.retry.attempts": "42",
|
"traefik.http.middlewares.middleware18.retry.attempts": "42",
|
||||||
|
"traefik.http.middlewares.middleware19.stripprefix.forceslash": "true",
|
||||||
"traefik.http.middlewares.middleware19.stripprefix.prefixes": "foobar, foobar",
|
"traefik.http.middlewares.middleware19.stripprefix.prefixes": "foobar, foobar",
|
||||||
"traefik.http.middlewares.middleware20.stripprefixregex.regex": "foobar, foobar",
|
"traefik.http.middlewares.middleware20.stripprefixregex.regex": "foobar, foobar",
|
||||||
"traefik.http.routers.router0.entrypoints": "foobar, foobar",
|
"traefik.http.routers.router0.entrypoints": "foobar, foobar",
|
||||||
@@ -110,7 +111,6 @@
|
|||||||
"traefik.http.routers.router0.priority": "42",
|
"traefik.http.routers.router0.priority": "42",
|
||||||
"traefik.http.routers.router0.rule": "foobar",
|
"traefik.http.routers.router0.rule": "foobar",
|
||||||
"traefik.http.routers.router0.service": "foobar",
|
"traefik.http.routers.router0.service": "foobar",
|
||||||
"traefik.http.routers.router0.tls": "true",
|
|
||||||
"traefik.http.routers.router0.tls.certresolver": "foobar",
|
"traefik.http.routers.router0.tls.certresolver": "foobar",
|
||||||
"traefik.http.routers.router0.tls.domains[0].main": "foobar",
|
"traefik.http.routers.router0.tls.domains[0].main": "foobar",
|
||||||
"traefik.http.routers.router0.tls.domains[0].sans": "foobar, foobar",
|
"traefik.http.routers.router0.tls.domains[0].sans": "foobar, foobar",
|
||||||
@@ -122,49 +122,30 @@
|
|||||||
"traefik.http.routers.router1.priority": "42",
|
"traefik.http.routers.router1.priority": "42",
|
||||||
"traefik.http.routers.router1.rule": "foobar",
|
"traefik.http.routers.router1.rule": "foobar",
|
||||||
"traefik.http.routers.router1.service": "foobar",
|
"traefik.http.routers.router1.service": "foobar",
|
||||||
"traefik.http.routers.router1.tls": "true",
|
|
||||||
"traefik.http.routers.router1.tls.certresolver": "foobar",
|
"traefik.http.routers.router1.tls.certresolver": "foobar",
|
||||||
"traefik.http.routers.router1.tls.domains[0].main": "foobar",
|
"traefik.http.routers.router1.tls.domains[0].main": "foobar",
|
||||||
"traefik.http.routers.router1.tls.domains[0].sans": "foobar, foobar",
|
"traefik.http.routers.router1.tls.domains[0].sans": "foobar, foobar",
|
||||||
"traefik.http.routers.router1.tls.domains[1].main": "foobar",
|
"traefik.http.routers.router1.tls.domains[1].main": "foobar",
|
||||||
"traefik.http.routers.router1.tls.domains[1].sans": "foobar, foobar",
|
"traefik.http.routers.router1.tls.domains[1].sans": "foobar, foobar",
|
||||||
"traefik.http.routers.router1.tls.options": "foobar",
|
"traefik.http.routers.router1.tls.options": "foobar",
|
||||||
"traefik.http.services.service0.loadbalancer.healthcheck.headers.name0": "foobar",
|
"traefik.http.services.service01.loadbalancer.healthcheck.headers.name0": "foobar",
|
||||||
"traefik.http.services.service0.loadbalancer.healthcheck.headers.name1": "foobar",
|
"traefik.http.services.service01.loadbalancer.healthcheck.headers.name1": "foobar",
|
||||||
"traefik.http.services.service0.loadbalancer.healthcheck.hostname": "foobar",
|
"traefik.http.services.service01.loadbalancer.healthcheck.hostname": "foobar",
|
||||||
"traefik.http.services.service0.loadbalancer.healthcheck.interval": "foobar",
|
"traefik.http.services.service01.loadbalancer.healthcheck.interval": "foobar",
|
||||||
"traefik.http.services.service0.loadbalancer.healthcheck.path": "foobar",
|
"traefik.http.services.service01.loadbalancer.healthcheck.path": "foobar",
|
||||||
"traefik.http.services.service0.loadbalancer.healthcheck.port": "42",
|
"traefik.http.services.service01.loadbalancer.healthcheck.port": "42",
|
||||||
"traefik.http.services.service0.loadbalancer.healthcheck.scheme": "foobar",
|
"traefik.http.services.service01.loadbalancer.healthcheck.scheme": "foobar",
|
||||||
"traefik.http.services.service0.loadbalancer.healthcheck.timeout": "foobar",
|
"traefik.http.services.service01.loadbalancer.healthcheck.timeout": "foobar",
|
||||||
"traefik.http.services.service0.loadbalancer.passhostheader": "true",
|
"traefik.http.services.service01.loadbalancer.passhostheader": "true",
|
||||||
"traefik.http.services.service0.loadbalancer.responseforwarding.flushinterval": "foobar",
|
"traefik.http.services.service01.loadbalancer.responseforwarding.flushinterval": "foobar",
|
||||||
"traefik.http.services.service0.loadbalancer.sticky": "true",
|
"traefik.http.services.service01.loadbalancer.sticky.cookie.httponly": "true",
|
||||||
"traefik.http.services.service0.loadbalancer.sticky.cookie.httponly": "true",
|
"traefik.http.services.service01.loadbalancer.sticky.cookie.name": "foobar",
|
||||||
"traefik.http.services.service0.loadbalancer.sticky.cookie.name": "foobar",
|
"traefik.http.services.service01.loadbalancer.sticky.cookie.secure": "true",
|
||||||
"traefik.http.services.service0.loadbalancer.sticky.cookie.secure": "true",
|
"traefik.http.services.service01.loadbalancer.server.port": "foobar",
|
||||||
"traefik.http.services.service0.loadbalancer.server.port": "foobar",
|
"traefik.http.services.service01.loadbalancer.server.scheme": "foobar",
|
||||||
"traefik.http.services.service0.loadbalancer.server.scheme": "foobar",
|
|
||||||
"traefik.http.services.service1.loadbalancer.healthcheck.headers.name0": "foobar",
|
|
||||||
"traefik.http.services.service1.loadbalancer.healthcheck.headers.name1": "foobar",
|
|
||||||
"traefik.http.services.service1.loadbalancer.healthcheck.hostname": "foobar",
|
|
||||||
"traefik.http.services.service1.loadbalancer.healthcheck.interval": "foobar",
|
|
||||||
"traefik.http.services.service1.loadbalancer.healthcheck.path": "foobar",
|
|
||||||
"traefik.http.services.service1.loadbalancer.healthcheck.port": "42",
|
|
||||||
"traefik.http.services.service1.loadbalancer.healthcheck.scheme": "foobar",
|
|
||||||
"traefik.http.services.service1.loadbalancer.healthcheck.timeout": "foobar",
|
|
||||||
"traefik.http.services.service1.loadbalancer.passhostheader": "true",
|
|
||||||
"traefik.http.services.service1.loadbalancer.responseforwarding.flushinterval": "foobar",
|
|
||||||
"traefik.http.services.service1.loadbalancer.sticky": "true",
|
|
||||||
"traefik.http.services.service1.loadbalancer.sticky.cookie.httponly": "true",
|
|
||||||
"traefik.http.services.service1.loadbalancer.sticky.cookie.name": "foobar",
|
|
||||||
"traefik.http.services.service1.loadbalancer.sticky.cookie.secure": "true",
|
|
||||||
"traefik.http.services.service1.loadbalancer.server.port": "foobar",
|
|
||||||
"traefik.http.services.service1.loadbalancer.server.scheme": "foobar",
|
|
||||||
"traefik.tcp.routers.tcprouter0.entrypoints": "foobar, foobar",
|
"traefik.tcp.routers.tcprouter0.entrypoints": "foobar, foobar",
|
||||||
"traefik.tcp.routers.tcprouter0.rule": "foobar",
|
"traefik.tcp.routers.tcprouter0.rule": "foobar",
|
||||||
"traefik.tcp.routers.tcprouter0.service": "foobar",
|
"traefik.tcp.routers.tcprouter0.service": "foobar",
|
||||||
"traefik.tcp.routers.tcprouter0.tls": "true",
|
|
||||||
"traefik.tcp.routers.tcprouter0.tls.certresolver": "foobar",
|
"traefik.tcp.routers.tcprouter0.tls.certresolver": "foobar",
|
||||||
"traefik.tcp.routers.tcprouter0.tls.domains[0].main": "foobar",
|
"traefik.tcp.routers.tcprouter0.tls.domains[0].main": "foobar",
|
||||||
"traefik.tcp.routers.tcprouter0.tls.domains[0].sans": "foobar, foobar",
|
"traefik.tcp.routers.tcprouter0.tls.domains[0].sans": "foobar, foobar",
|
||||||
@@ -175,7 +156,6 @@
|
|||||||
"traefik.tcp.routers.tcprouter1.entrypoints": "foobar, foobar",
|
"traefik.tcp.routers.tcprouter1.entrypoints": "foobar, foobar",
|
||||||
"traefik.tcp.routers.tcprouter1.rule": "foobar",
|
"traefik.tcp.routers.tcprouter1.rule": "foobar",
|
||||||
"traefik.tcp.routers.tcprouter1.service": "foobar",
|
"traefik.tcp.routers.tcprouter1.service": "foobar",
|
||||||
"traefik.tcp.routers.tcprouter1.tls": "true",
|
|
||||||
"traefik.tcp.routers.tcprouter1.tls.certresolver": "foobar",
|
"traefik.tcp.routers.tcprouter1.tls.certresolver": "foobar",
|
||||||
"traefik.tcp.routers.tcprouter1.tls.domains[0].main": "foobar",
|
"traefik.tcp.routers.tcprouter1.tls.domains[0].main": "foobar",
|
||||||
"traefik.tcp.routers.tcprouter1.tls.domains[0].sans": "foobar, foobar",
|
"traefik.tcp.routers.tcprouter1.tls.domains[0].sans": "foobar, foobar",
|
||||||
@@ -183,7 +163,5 @@
|
|||||||
"traefik.tcp.routers.tcprouter1.tls.domains[1].sans": "foobar, foobar",
|
"traefik.tcp.routers.tcprouter1.tls.domains[1].sans": "foobar, foobar",
|
||||||
"traefik.tcp.routers.tcprouter1.tls.options": "foobar",
|
"traefik.tcp.routers.tcprouter1.tls.options": "foobar",
|
||||||
"traefik.tcp.routers.tcprouter1.tls.passthrough": "true",
|
"traefik.tcp.routers.tcprouter1.tls.passthrough": "true",
|
||||||
"traefik.tcp.services.tcpservice0.loadbalancer.server.port": "foobar",
|
"traefik.tcp.services.tcpservice01.loadbalancer.terminationdelay": "42",
|
||||||
"traefik.tcp.services.tcpservice0.loadbalancer.terminationDelay": "100",
|
"traefik.tcp.services.tcpservice01.loadbalancer.server.port": "foobar",
|
||||||
"traefik.tcp.services.tcpservice1.loadbalancer.server.port": "foobar"
|
|
||||||
"traefik.tcp.services.tcpservice1.loadbalancer.terminationDelay": "100",
|
|
||||||
|
@@ -128,9 +128,9 @@ You can define them using a toml file, CLI arguments, or a key-value store.
|
|||||||
--entryPoints.name.transport.respondingTimeouts.writeTimeout=42
|
--entryPoints.name.transport.respondingTimeouts.writeTimeout=42
|
||||||
--entryPoints.name.transport.respondingTimeouts.idleTimeout=42
|
--entryPoints.name.transport.respondingTimeouts.idleTimeout=42
|
||||||
--entryPoints.name.proxyProtocol.insecure=true
|
--entryPoints.name.proxyProtocol.insecure=true
|
||||||
--entryPoints.name.proxyProtocol.trustedIPs="127.0.0.1,192.168.0.1"
|
--entryPoints.name.proxyProtocol.trustedIPs=127.0.0.1,192.168.0.1
|
||||||
--entryPoints.name.forwardedHeaders.insecure=true
|
--entryPoints.name.forwardedHeaders.insecure=true
|
||||||
--entryPoints.name.forwardedHeaders.trustedIPs="127.0.0.1,192.168.0.1"
|
--entryPoints.name.forwardedHeaders.trustedIPs=127.0.0.1,192.168.0.1
|
||||||
```
|
```
|
||||||
|
|
||||||
### Forwarded Header
|
### Forwarded Header
|
||||||
|
@@ -33,9 +33,9 @@ Static configuration:
|
|||||||
address = ":8081"
|
address = ":8081"
|
||||||
|
|
||||||
[providers]
|
[providers]
|
||||||
# Enable the file provider to define routers / middlewares / services in a file
|
# Enable the file provider to define routers / middlewares / services in file
|
||||||
[providers.file]
|
[providers.file]
|
||||||
filename = "dynamic_conf.toml"
|
directory = "/path/to/dynamic/conf"
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
@@ -45,17 +45,17 @@ entryPoints:
|
|||||||
address: :8081
|
address: :8081
|
||||||
|
|
||||||
providers:
|
providers:
|
||||||
# Enable the file provider to define routers / middlewares / services in a file
|
# Enable the file provider to define routers / middlewares / services in file
|
||||||
file:
|
file:
|
||||||
filename: dynamic_conf.yml
|
directory: /path/to/dynamic/conf
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
# Listen on port 8081 for incoming requests
|
# Listen on port 8081 for incoming requests
|
||||||
--entryPoints.web.address=:8081
|
--entryPoints.web.address=:8081
|
||||||
|
|
||||||
# Enable the file provider to define routers / middlewares / services in a file
|
# Enable the file provider to define routers / middlewares / services in file
|
||||||
--providers.file.filename=dynamic_conf.toml
|
--providers.file.directory=/path/to/dynamic/conf
|
||||||
```
|
```
|
||||||
|
|
||||||
Dynamic configuration:
|
Dynamic configuration:
|
||||||
@@ -133,9 +133,9 @@ http:
|
|||||||
address = ":8081"
|
address = ":8081"
|
||||||
|
|
||||||
[providers]
|
[providers]
|
||||||
# Enable the file provider to define routers / middlewares / services in a file
|
# Enable the file provider to define routers / middlewares / services in file
|
||||||
[providers.file]
|
[providers.file]
|
||||||
filename = "dynamic_conf.toml"
|
directory = "/path/to/dynamic/conf"
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
@@ -144,17 +144,17 @@ http:
|
|||||||
# Listen on port 8081 for incoming requests
|
# Listen on port 8081 for incoming requests
|
||||||
address: :8081
|
address: :8081
|
||||||
providers:
|
providers:
|
||||||
# Enable the file provider to define routers / middlewares / services in a file
|
# Enable the file provider to define routers / middlewares / services in file
|
||||||
file:
|
file:
|
||||||
filename: dynamic_conf.yml
|
directory: /path/to/dynamic/conf
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
# Listen on port 8081 for incoming requests
|
# Listen on port 8081 for incoming requests
|
||||||
--entryPoints.web.address=":8081"
|
--entryPoints.web.address=:8081
|
||||||
|
|
||||||
# Enable the file provider to define routers / middlewares / services in a file
|
# Enable the file provider to define routers / middlewares / services in file
|
||||||
--providers.file.filename=dynamic_conf.toml
|
--providers.file.directory=/path/to/dynamic/conf
|
||||||
```
|
```
|
||||||
|
|
||||||
**Dynamic Configuration**
|
**Dynamic Configuration**
|
||||||
|
@@ -82,7 +82,7 @@ Attach labels to your containers and let Traefik do the rest!
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.docker.endpoint="tcp://127.0.0.1:2375"
|
--providers.docker.endpoint=tcp://127.0.0.1:2375
|
||||||
--providers.docker.swarmMode=true
|
--providers.docker.swarmMode=true
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -165,7 +165,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers
|
|||||||
See [entry points](../routers/index.md#entrypoints) for more information.
|
See [entry points](../routers/index.md#entrypoints) for more information.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- "traefik.http.routers.myrouter.entrypoints=web,websecure"
|
- "traefik.http.routers.myrouter.entrypoints=ep1,ep2"
|
||||||
```
|
```
|
||||||
|
|
||||||
??? info "`traefik.http.routers.<router_name>.middlewares`"
|
??? info "`traefik.http.routers.<router_name>.middlewares`"
|
||||||
@@ -226,7 +226,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers
|
|||||||
|
|
||||||
??? info "`traefik.http.routers.<router_name>.priority`"
|
??? info "`traefik.http.routers.<router_name>.priority`"
|
||||||
|
|
||||||
See [options](../routers/index.md#priority) for more information.
|
See [priority](../routers/index.md#priority) for more information.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- "traefik.http.routers.myrouter.priority=42"
|
- "traefik.http.routers.myrouter.priority=42"
|
||||||
@@ -243,11 +243,12 @@ you'd add the label `traefik.http.services.<name-of-your-choice>.loadbalancer.pa
|
|||||||
!!! warning "The character `@` is not authorized in the service name `<service_name>`."
|
!!! warning "The character `@` is not authorized in the service name `<service_name>`."
|
||||||
|
|
||||||
??? info "`traefik.http.services.<service_name>.loadbalancer.server.port`"
|
??? info "`traefik.http.services.<service_name>.loadbalancer.server.port`"
|
||||||
|
|
||||||
Registers a port.
|
Registers a port.
|
||||||
Useful when the container exposes multiples ports.
|
Useful when the container exposes multiples ports.
|
||||||
|
|
||||||
Mandatory for Docker Swarm.
|
Mandatory for Docker Swarm (see the section ["Port Detection with Docker Swarm"](../../providers/docker.md#port-detection_1)).
|
||||||
|
{: #port }
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- "traefik.http.services.myservice.loadbalancer.server.port=8080"
|
- "traefik.http.services.myservice.loadbalancer.server.port=8080"
|
||||||
|
@@ -189,7 +189,7 @@ metadata:
|
|||||||
|
|
||||||
spec:
|
spec:
|
||||||
entryPoints:
|
entryPoints:
|
||||||
- web
|
- websecure
|
||||||
routes:
|
routes:
|
||||||
- match: Host(`foo.com`) && PathPrefix(`/bar`)
|
- match: Host(`foo.com`) && PathPrefix(`/bar`)
|
||||||
kind: Rule
|
kind: Rule
|
||||||
|
@@ -67,7 +67,7 @@ For example, to change the routing rule, you could add the label ```"traefik.htt
|
|||||||
See [entry points](../routers/index.md#entrypoints) for more information.
|
See [entry points](../routers/index.md#entrypoints) for more information.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"traefik.http.routers.myrouter.entrypoints": "web,websecure"
|
"traefik.http.routers.myrouter.entrypoints": "ep1,ep2"
|
||||||
```
|
```
|
||||||
|
|
||||||
??? info "`traefik.http.routers.<router_name>.middlewares`"
|
??? info "`traefik.http.routers.<router_name>.middlewares`"
|
||||||
@@ -91,7 +91,7 @@ For example, to change the routing rule, you could add the label ```"traefik.htt
|
|||||||
See [tls](../routers/index.md#tls) for more information.
|
See [tls](../routers/index.md#tls) for more information.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"traefik.http.routers.myrouter>.tls": "true"
|
"traefik.http.routers.myrouter.tls": "true"
|
||||||
```
|
```
|
||||||
|
|
||||||
??? info "`traefik.http.routers.<router_name>.tls.certresolver`"
|
??? info "`traefik.http.routers.<router_name>.tls.certresolver`"
|
||||||
@@ -128,7 +128,7 @@ For example, to change the routing rule, you could add the label ```"traefik.htt
|
|||||||
|
|
||||||
??? info "`traefik.http.routers.<router_name>.priority`"
|
??? info "`traefik.http.routers.<router_name>.priority`"
|
||||||
|
|
||||||
See [options](../routers/index.md#priority) for more information.
|
See [priority](../routers/index.md#priority) for more information.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"traefik.http.routers.myrouter.priority": "42"
|
"traefik.http.routers.myrouter.priority": "42"
|
||||||
|
@@ -72,7 +72,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers
|
|||||||
See [entry points](../routers/index.md#entrypoints) for more information.
|
See [entry points](../routers/index.md#entrypoints) for more information.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- "traefik.http.routers.myrouter.entrypoints=web,websecure"
|
- "traefik.http.routers.myrouter.entrypoints=ep1,ep2"
|
||||||
```
|
```
|
||||||
|
|
||||||
??? info "`traefik.http.routers.<router_name>.middlewares`"
|
??? info "`traefik.http.routers.<router_name>.middlewares`"
|
||||||
@@ -133,7 +133,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers
|
|||||||
|
|
||||||
??? info "`traefik.http.routers.<router_name>.priority`"
|
??? info "`traefik.http.routers.<router_name>.priority`"
|
||||||
|
|
||||||
See [options](../routers/index.md#priority) for more information.
|
See [priority](../routers/index.md#priority) for more information.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- "traefik.http.routers.myrouter.priority=42"
|
- "traefik.http.routers.myrouter.priority=42"
|
||||||
|
@@ -78,8 +78,8 @@ In the process, routers may use pieces of [middleware](../../middlewares/overvie
|
|||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
## Static configuration
|
## Static configuration
|
||||||
--entryPoints.web.address=":80"
|
--entryPoints.web.address=:80
|
||||||
--entryPoints.mysql.address=":3306"
|
--entryPoints.mysql.address=:3306
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuring HTTP Routers
|
## Configuring HTTP Routers
|
||||||
@@ -140,9 +140,9 @@ If you want to limit the router scope to a set of entry points, set the `entryPo
|
|||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
## Static configuration
|
## Static configuration
|
||||||
--entrypoints.web.address=":80"
|
--entrypoints.web.address=:80
|
||||||
--entrypoints.websecure.address=":443"
|
--entrypoints.websecure.address=:443
|
||||||
--entrypoints.other.address=":9090"
|
--entrypoints.other.address=:9090
|
||||||
```
|
```
|
||||||
|
|
||||||
??? example "Listens to Specific EntryPoints"
|
??? example "Listens to Specific EntryPoints"
|
||||||
@@ -198,9 +198,9 @@ If you want to limit the router scope to a set of entry points, set the `entryPo
|
|||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
## Static configuration
|
## Static configuration
|
||||||
--entrypoints.web.address=":80"
|
--entrypoints.web.address=:80
|
||||||
--entrypoints.websecure.address=":443"
|
--entrypoints.websecure.address=:443
|
||||||
--entrypoints.other.address=":9090"
|
--entrypoints.other.address=:9090
|
||||||
```
|
```
|
||||||
|
|
||||||
### Rule
|
### Rule
|
||||||
@@ -300,7 +300,7 @@ A value of `0` for the priority is ignored: `priority = 0` means that the defaul
|
|||||||
|
|
||||||
The previous table shows that `Router-1` has a higher priority than `Router-2`.
|
The previous table shows that `Router-1` has a higher priority than `Router-2`.
|
||||||
|
|
||||||
To solve this issue, the priority must be setted.
|
To solve this issue, the priority must be set.
|
||||||
|
|
||||||
??? example "Set priorities -- using the [File Provider](../../providers/file.md)"
|
??? example "Set priorities -- using the [File Provider](../../providers/file.md)"
|
||||||
|
|
||||||
@@ -419,10 +419,6 @@ Traefik will terminate the SSL connections (meaning that it will send decrypted
|
|||||||
tls: {}
|
tls: {}
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! info "HTTPS & ACME"
|
|
||||||
|
|
||||||
In the current version, with [ACME](../../https/acme.md) enabled, automatic certificate generation will apply to every router declaring a TLS section.
|
|
||||||
|
|
||||||
!!! important "Routers for HTTP & HTTPS"
|
!!! important "Routers for HTTP & HTTPS"
|
||||||
|
|
||||||
If you need to define the same route for both HTTP and HTTPS requests, you will need to define two different routers:
|
If you need to define the same route for both HTTP and HTTPS requests, you will need to define two different routers:
|
||||||
@@ -704,9 +700,9 @@ If you want to limit the router scope to a set of entry points, set the entry po
|
|||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
## Static configuration
|
## Static configuration
|
||||||
--entrypoints.web.address=":80"
|
--entrypoints.web.address=:80
|
||||||
--entrypoints.websecure.address=":443"
|
--entrypoints.websecure.address=:443
|
||||||
--entrypoints.other.address=":9090"
|
--entrypoints.other.address=:9090
|
||||||
```
|
```
|
||||||
|
|
||||||
??? example "Listens to Specific Entry Points"
|
??? example "Listens to Specific Entry Points"
|
||||||
@@ -768,9 +764,9 @@ If you want to limit the router scope to a set of entry points, set the entry po
|
|||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
## Static configuration
|
## Static configuration
|
||||||
--entrypoints.web.address=":80"
|
--entrypoints.web.address=:80
|
||||||
--entrypoints.websecure.address=":443"
|
--entrypoints.websecure.address=:443
|
||||||
--entrypoints.other.address=":9090"
|
--entrypoints.other.address=:9090
|
||||||
```
|
```
|
||||||
|
|
||||||
### Rule
|
### Rule
|
||||||
@@ -846,10 +842,6 @@ Services are the target for the router.
|
|||||||
passthrough: true
|
passthrough: true
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! info "TLS & ACME"
|
|
||||||
|
|
||||||
In the current version, with [ACME](../../https/acme.md) enabled, automatic certificate generation will apply to every router declaring a TLS section.
|
|
||||||
|
|
||||||
#### `options`
|
#### `options`
|
||||||
|
|
||||||
The `options` field enables fine-grained control of the TLS parameters.
|
The `options` field enables fine-grained control of the TLS parameters.
|
||||||
|
@@ -599,12 +599,12 @@ This strategy can only be defined with [File](../../providers/file.md).
|
|||||||
[tcp.services.appv1]
|
[tcp.services.appv1]
|
||||||
[tcp.services.appv1.loadBalancer]
|
[tcp.services.appv1.loadBalancer]
|
||||||
[[tcp.services.appv1.loadBalancer.servers]]
|
[[tcp.services.appv1.loadBalancer.servers]]
|
||||||
address = "private-ip-server-1/:8080"
|
address = "private-ip-server-1:8080/"
|
||||||
|
|
||||||
[tcp.services.appv2]
|
[tcp.services.appv2]
|
||||||
[tcp.services.appv2.loadBalancer]
|
[tcp.services.appv2.loadBalancer]
|
||||||
[[tcp.services.appv2.loadBalancer.servers]]
|
[[tcp.services.appv2.loadBalancer.servers]]
|
||||||
address = "private-ip-server-2/:8080"
|
address = "private-ip-server-2:8080/"
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="YAML"
|
```yaml tab="YAML"
|
||||||
|
@@ -16,7 +16,7 @@ Static configuration:
|
|||||||
[api]
|
[api]
|
||||||
|
|
||||||
[providers.file]
|
[providers.file]
|
||||||
filename = "dynamic_conf.toml"
|
directory = "/path/to/dynamic/config"
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
@@ -26,18 +26,18 @@ entryPoints:
|
|||||||
|
|
||||||
providers:
|
providers:
|
||||||
file:
|
file:
|
||||||
filename: dynamic_conf.yml
|
directory: /path/to/dynamic/config
|
||||||
|
|
||||||
api: {}
|
api: {}
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="CLI"
|
```yaml tab="CLI"
|
||||||
--entryPoints.web.address=":80"
|
--entryPoints.web.address=:80
|
||||||
--providers.file.filename=dynamic_conf.toml
|
--providers.file.directory=/path/to/dynamic/config
|
||||||
--api.insecure=true
|
--api.insecure=true
|
||||||
```
|
```
|
||||||
|
|
||||||
`dynamic_conf.{toml,yml}`:
|
`/path/to/dynamic/config/dynamic_conf.{toml,yml}`:
|
||||||
|
|
||||||
```toml tab="TOML"
|
```toml tab="TOML"
|
||||||
## dynamic configuration ##
|
## dynamic configuration ##
|
||||||
@@ -132,7 +132,7 @@ Static configuration:
|
|||||||
[api]
|
[api]
|
||||||
|
|
||||||
[provider.file]
|
[provider.file]
|
||||||
filename = "dynamic_conf.toml"
|
directory = "/path/to/dynamic/config"
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
@@ -147,20 +147,20 @@ serversTransport:
|
|||||||
|
|
||||||
providers:
|
providers:
|
||||||
file:
|
file:
|
||||||
filename: dynamic_conf.yml
|
directory: /path/to/dynamic/config
|
||||||
|
|
||||||
api: {}
|
api: {}
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="CLI"
|
```yaml tab="CLI"
|
||||||
--entryPoints.websecure.address=":4443"
|
--entryPoints.websecure.address=:4443
|
||||||
# For secure connection on backend.local
|
# For secure connection on backend.local
|
||||||
--serversTransport.rootCAs=./backend.cert
|
--serversTransport.rootCAs=./backend.cert
|
||||||
--providers.file.filename=dynamic_conf.toml
|
--providers.file.directory=/path/to/dynamic/config
|
||||||
--api.insecure=true
|
--api.insecure=true
|
||||||
```
|
```
|
||||||
|
|
||||||
`dynamic_conf.{toml,yml}`:
|
`/path/to/dynamic/config/dynamic_conf.{toml,yml}`:
|
||||||
|
|
||||||
```toml tab="TOML"
|
```toml tab="TOML"
|
||||||
## dynamic configuration ##
|
## dynamic configuration ##
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
FROM alpine:3.9
|
FROM alpine:3.10
|
||||||
|
|
||||||
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/bin
|
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/bin
|
||||||
|
|
||||||
|
@@ -26,11 +26,7 @@ theme:
|
|||||||
prev: 'Previous'
|
prev: 'Previous'
|
||||||
next: 'Next'
|
next: 'Next'
|
||||||
|
|
||||||
copyright: "Copyright © 2016-2019 Containous"
|
copyright: "Copyright © 2016-2020 Containous"
|
||||||
|
|
||||||
google_analytics:
|
|
||||||
- 'UA-51880359-3'
|
|
||||||
- 'docs.traefik.io'
|
|
||||||
|
|
||||||
extra_css:
|
extra_css:
|
||||||
- assets/styles/extra.css # Our custom styles
|
- assets/styles/extra.css # Our custom styles
|
||||||
@@ -42,6 +38,9 @@ extra_javascript:
|
|||||||
|
|
||||||
plugins:
|
plugins:
|
||||||
- search
|
- search
|
||||||
|
- exclude:
|
||||||
|
glob:
|
||||||
|
- "**/include-*.md"
|
||||||
|
|
||||||
# https://squidfunk.github.io/mkdocs-material/extensions/admonition/
|
# https://squidfunk.github.io/mkdocs-material/extensions/admonition/
|
||||||
# https://facelessuser.github.io/pymdown-extensions/
|
# https://facelessuser.github.io/pymdown-extensions/
|
||||||
@@ -154,6 +153,7 @@ nav:
|
|||||||
- 'Thank You!': 'contributing/thank-you.md'
|
- 'Thank You!': 'contributing/thank-you.md'
|
||||||
- 'Submitting Issues': 'contributing/submitting-issues.md'
|
- 'Submitting Issues': 'contributing/submitting-issues.md'
|
||||||
- 'Submitting PRs': 'contributing/submitting-pull-requests.md'
|
- 'Submitting PRs': 'contributing/submitting-pull-requests.md'
|
||||||
|
- 'Security': 'contributing/submitting-security-issues.md'
|
||||||
- 'Building and Testing': 'contributing/building-testing.md'
|
- 'Building and Testing': 'contributing/building-testing.md'
|
||||||
- 'Documentation': 'contributing/documentation.md'
|
- 'Documentation': 'contributing/documentation.md'
|
||||||
- 'Data Collection': 'contributing/data-collection.md'
|
- 'Data Collection': 'contributing/data-collection.md'
|
||||||
|
@@ -1,5 +1,26 @@
|
|||||||
mkdocs==1.0.4
|
mkdocs==1.0.4
|
||||||
pymdown-extensions==6.0
|
pymdown-extensions==6.1
|
||||||
mkdocs-bootswatch==1.0
|
mkdocs-bootswatch==1.0
|
||||||
mkdocs-material==4.0.2
|
mkdocs-material==4.4.3
|
||||||
markdown-include==0.5.1
|
markdown-include==0.5.1
|
||||||
|
mkdocs-exclude==1.0.2
|
||||||
|
Jinja2==3.0.0
|
||||||
|
|
||||||
|
click==8.1.3
|
||||||
|
csscompressor==0.9.5
|
||||||
|
htmlmin==0.1.12
|
||||||
|
importlib-metadata==4.12.0
|
||||||
|
jsmin==3.0.1
|
||||||
|
livereload==2.6.3
|
||||||
|
Markdown==3.3.7
|
||||||
|
MarkupSafe==2.1.1
|
||||||
|
mkdocs-exclude==1.0.2
|
||||||
|
mkdocs-minify-plugin==0.5.0
|
||||||
|
pep562==1.1
|
||||||
|
Pygments==2.12.0
|
||||||
|
pymdown-extensions==6.1
|
||||||
|
PyYAML==6.0.1
|
||||||
|
six==1.16.0
|
||||||
|
tornado==6.2
|
||||||
|
typing-extensions==4.3.0
|
||||||
|
zipp==3.8.1
|
||||||
|
@@ -1 +1 @@
|
|||||||
3.6
|
3.7
|
||||||
|
12
docs/theme/main.html
vendored
12
docs/theme/main.html
vendored
@@ -1,5 +1,15 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block analytics %}
|
||||||
|
<!-- Google Tag Manager -->
|
||||||
|
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
||||||
|
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
||||||
|
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||||
|
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
||||||
|
})(window,document,'script','dataLayer','GTM-NMWC63S');</script>
|
||||||
|
<!-- End Google Tag Manager -->
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block footer %}
|
{% block footer %}
|
||||||
|
|
||||||
{% import "partials/language.html" as lang with context %}
|
{% import "partials/language.html" as lang with context %}
|
||||||
@@ -34,4 +44,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@@ -7,8 +7,8 @@ RUN mkdir -p $WEBUI_DIR
|
|||||||
COPY ./webui/ $WEBUI_DIR/
|
COPY ./webui/ $WEBUI_DIR/
|
||||||
|
|
||||||
WORKDIR $WEBUI_DIR
|
WORKDIR $WEBUI_DIR
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# BUILD
|
# BUILD
|
||||||
@@ -38,10 +38,12 @@ COPY --from=webui /src/static/ /go/src/github.com/containous/traefik/static/
|
|||||||
RUN ./script/make.sh generate binary
|
RUN ./script/make.sh generate binary
|
||||||
|
|
||||||
## IMAGE
|
## IMAGE
|
||||||
FROM scratch
|
FROM alpine:3.10
|
||||||
|
|
||||||
|
RUN apk --no-cache --no-progress add bash curl ca-certificates tzdata \
|
||||||
|
&& update-ca-certificates \
|
||||||
|
&& rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
COPY --from=gobuild /usr/share/zoneinfo /usr/share/zoneinfo
|
|
||||||
COPY --from=gobuild /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
|
||||||
COPY --from=gobuild /go/src/github.com/containous/traefik/dist/traefik /
|
COPY --from=gobuild /go/src/github.com/containous/traefik/dist/traefik /
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
6
go.mod
6
go.mod
@@ -39,7 +39,7 @@ require (
|
|||||||
github.com/felixge/httpsnoop v1.0.0 // indirect
|
github.com/felixge/httpsnoop v1.0.0 // indirect
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||||
github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2
|
github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2
|
||||||
github.com/go-acme/lego/v3 v3.1.0
|
github.com/go-acme/lego/v3 v3.2.0
|
||||||
github.com/go-check/check v0.0.0-00010101000000-000000000000
|
github.com/go-check/check v0.0.0-00010101000000-000000000000
|
||||||
github.com/go-kit/kit v0.9.0
|
github.com/go-kit/kit v0.9.0
|
||||||
github.com/golang/protobuf v1.3.2
|
github.com/golang/protobuf v1.3.2
|
||||||
@@ -66,7 +66,7 @@ require (
|
|||||||
github.com/opencontainers/runc v1.0.0-rc8 // indirect
|
github.com/opencontainers/runc v1.0.0-rc8 // indirect
|
||||||
github.com/opentracing/basictracer-go v1.0.0 // indirect
|
github.com/opentracing/basictracer-go v1.0.0 // indirect
|
||||||
github.com/opentracing/opentracing-go v1.1.0
|
github.com/opentracing/opentracing-go v1.1.0
|
||||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.3
|
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5
|
||||||
github.com/openzipkin/zipkin-go v0.2.1
|
github.com/openzipkin/zipkin-go v0.2.1
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/philhofer/fwd v1.0.0 // indirect
|
github.com/philhofer/fwd v1.0.0 // indirect
|
||||||
@@ -82,7 +82,7 @@ require (
|
|||||||
github.com/uber/jaeger-client-go v2.16.0+incompatible
|
github.com/uber/jaeger-client-go v2.16.0+incompatible
|
||||||
github.com/uber/jaeger-lib v2.0.0+incompatible
|
github.com/uber/jaeger-lib v2.0.0+incompatible
|
||||||
github.com/unrolled/render v1.0.1
|
github.com/unrolled/render v1.0.1
|
||||||
github.com/unrolled/secure v1.0.4
|
github.com/unrolled/secure v1.0.5
|
||||||
github.com/vdemeester/shakers v0.1.0
|
github.com/vdemeester/shakers v0.1.0
|
||||||
github.com/vulcand/oxy v1.0.0
|
github.com/vulcand/oxy v1.0.0
|
||||||
github.com/vulcand/predicate v1.1.0
|
github.com/vulcand/predicate v1.1.0
|
||||||
|
12
go.sum
12
go.sum
@@ -181,8 +181,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
|||||||
github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2 h1:df6OFl8WNXk82xxP3R9ZPZ5seOA8XZkwLdbEzZF1/xI=
|
github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2 h1:df6OFl8WNXk82xxP3R9ZPZ5seOA8XZkwLdbEzZF1/xI=
|
||||||
github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2/go.mod h1:GLyXJD41gBO/NPKVPGQbhyyC06eugGy15QEZyUkE2/s=
|
github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2/go.mod h1:GLyXJD41gBO/NPKVPGQbhyyC06eugGy15QEZyUkE2/s=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/go-acme/lego/v3 v3.1.0 h1:yanYFoYW8azFkCvJfIk7edWWfjkYkhDxe45ZsxoW4Xk=
|
github.com/go-acme/lego/v3 v3.2.0 h1:z0zvNlL1niv/1qA06V5X1BRC5PeLoGKAlVaWthXQz9c=
|
||||||
github.com/go-acme/lego/v3 v3.1.0/go.mod h1:074uqt+JS6plx+c9Xaiz6+L+GBb+7itGtzfcDM2AhEE=
|
github.com/go-acme/lego/v3 v3.2.0/go.mod h1:074uqt+JS6plx+c9Xaiz6+L+GBb+7itGtzfcDM2AhEE=
|
||||||
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
|
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
|
||||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
@@ -396,8 +396,8 @@ github.com/opentracing/basictracer-go v1.0.0 h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7l
|
|||||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||||
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
|
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
|
||||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.3 h1:XudIMByQMXJ6oDHy4SipNyo35LxjA69Z7v1nL0aAZvA=
|
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 h1:ZCnq+JUrvXcDVhX/xRolRBZifmabN1HcS1wrPSvxhrU=
|
||||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.3/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
|
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
|
||||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||||
github.com/openzipkin/zipkin-go v0.2.1 h1:noL5/5Uf1HpVl3wNsfkZhIKbSWCVi5jgqkONNx8PXcA=
|
github.com/openzipkin/zipkin-go v0.2.1 h1:noL5/5Uf1HpVl3wNsfkZhIKbSWCVi5jgqkONNx8PXcA=
|
||||||
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||||
@@ -488,8 +488,8 @@ github.com/uber/jaeger-lib v2.0.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6
|
|||||||
github.com/ugorji/go v0.0.0-20171019201919-bdcc60b419d1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
|
github.com/ugorji/go v0.0.0-20171019201919-bdcc60b419d1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
|
||||||
github.com/unrolled/render v1.0.1 h1:VDDnQQVfBMsOsp3VaCJszSO0nkBIVEYoPWeRThk9spY=
|
github.com/unrolled/render v1.0.1 h1:VDDnQQVfBMsOsp3VaCJszSO0nkBIVEYoPWeRThk9spY=
|
||||||
github.com/unrolled/render v1.0.1/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0obPf6NBdM=
|
github.com/unrolled/render v1.0.1/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0obPf6NBdM=
|
||||||
github.com/unrolled/secure v1.0.4 h1:DksfKsRTyXP2R8quDdOOuRpRO45VprFL0X9t9+JX1PU=
|
github.com/unrolled/secure v1.0.5 h1:KRGJ8DQC3jKpERjBKF3H6b3HcAsM/SRTVwfNJnWs25E=
|
||||||
github.com/unrolled/secure v1.0.4/go.mod h1:R6rugAuzh4TQpbFAq69oqZggyBQxFRFQIewtz5z7Jsc=
|
github.com/unrolled/secure v1.0.5/go.mod h1:R6rugAuzh4TQpbFAq69oqZggyBQxFRFQIewtz5z7Jsc=
|
||||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
github.com/vdemeester/shakers v0.1.0 h1:K+n9sSyUCg2ywmZkv+3c7vsYZfivcfKhMh8kRxCrONM=
|
github.com/vdemeester/shakers v0.1.0 h1:K+n9sSyUCg2ywmZkv+3c7vsYZfivcfKhMh8kRxCrONM=
|
||||||
github.com/vdemeester/shakers v0.1.0/go.mod h1:IZ1HHynUOQt32iQ3rvAeVddXLd19h/6LWiKsh9RZtAQ=
|
github.com/vdemeester/shakers v0.1.0/go.mod h1:IZ1HHynUOQt32iQ3rvAeVddXLd19h/6LWiKsh9RZtAQ=
|
||||||
|
@@ -0,0 +1,40 @@
|
|||||||
|
[global]
|
||||||
|
checkNewVersion = false
|
||||||
|
sendAnonymousUsage = false
|
||||||
|
|
||||||
|
[log]
|
||||||
|
level = "DEBUG"
|
||||||
|
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.web1]
|
||||||
|
address = ":8000"
|
||||||
|
[entryPoints.web2]
|
||||||
|
address = ":9000"
|
||||||
|
|
||||||
|
[api]
|
||||||
|
insecure = true
|
||||||
|
|
||||||
|
[providers.file]
|
||||||
|
filename = "{{ .SelfFilename }}"
|
||||||
|
|
||||||
|
## dynamic configuration ##
|
||||||
|
|
||||||
|
[http.routers]
|
||||||
|
[http.routers.router1]
|
||||||
|
entryPoints = ["web1"]
|
||||||
|
service = "service1"
|
||||||
|
rule = "Host(`test.localhost`)"
|
||||||
|
|
||||||
|
[http.routers.router2]
|
||||||
|
entryPoints = ["web2"]
|
||||||
|
service = "service1"
|
||||||
|
rule = "Host(`test.localhost`)"
|
||||||
|
|
||||||
|
[http.services]
|
||||||
|
[http.services.service1.loadBalancer]
|
||||||
|
[http.services.service1.loadBalancer.healthcheck]
|
||||||
|
path = "/health"
|
||||||
|
interval = "1s"
|
||||||
|
timeout = "0.9s"
|
||||||
|
[[http.services.service1.loadBalancer.servers]]
|
||||||
|
url = "http://{{.Server1}}:80"
|
@@ -205,3 +205,69 @@ func (s *HealthCheckSuite) TestPortOverload(c *check.C) {
|
|||||||
err = try.Request(frontendHealthReq, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable))
|
err = try.Request(frontendHealthReq, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checks if all the loadbalancers created will correctly update the server status
|
||||||
|
func (s *HealthCheckSuite) TestMultipleRoutersOnSameService(c *check.C) {
|
||||||
|
file := s.adaptFile(c, "fixtures/healthcheck/multiple-routers-one-same-service.toml", struct {
|
||||||
|
Server1 string
|
||||||
|
}{s.whoami1IP})
|
||||||
|
defer os.Remove(file)
|
||||||
|
|
||||||
|
cmd, display := s.traefikCmd(withConfigFile(file))
|
||||||
|
defer display(c)
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
// wait for traefik
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`test.localhost`)"))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// Set whoami health to 200 to be sure to start with the wanted status
|
||||||
|
client := &http.Client{}
|
||||||
|
statusOkReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBuffer([]byte("200")))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
_, err = client.Do(statusOkReq)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// check healthcheck on web1 entrypoint
|
||||||
|
healthReqWeb1, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
healthReqWeb1.Host = "test.localhost"
|
||||||
|
err = try.Request(healthReqWeb1, 1*time.Second, try.StatusCodeIs(http.StatusOK))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// check healthcheck on web2 entrypoint
|
||||||
|
healthReqWeb2, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:9000/health", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
healthReqWeb2.Host = "test.localhost"
|
||||||
|
|
||||||
|
err = try.Request(healthReqWeb2, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// Set whoami health to 500
|
||||||
|
statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBuffer([]byte("500")))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
_, err = client.Do(statusInternalServerErrorReq)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// Verify no backend service is available due to failing health checks
|
||||||
|
err = try.Request(healthReqWeb1, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = try.Request(healthReqWeb2, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// Change one whoami health to 200
|
||||||
|
statusOKReq1, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBuffer([]byte("200")))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
_, err = client.Do(statusOKReq1)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// Verify health check
|
||||||
|
err = try.Request(healthReqWeb1, 3*time.Second, try.StatusCodeIs(http.StatusOK))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = try.Request(healthReqWeb2, 3*time.Second, try.StatusCodeIs(http.StatusOK))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
@@ -355,7 +355,13 @@ type Retry struct {
|
|||||||
|
|
||||||
// StripPrefix holds the StripPrefix configuration.
|
// StripPrefix holds the StripPrefix configuration.
|
||||||
type StripPrefix struct {
|
type StripPrefix struct {
|
||||||
Prefixes []string `json:"prefixes,omitempty" toml:"prefixes,omitempty" yaml:"prefixes,omitempty"`
|
Prefixes []string `json:"prefixes,omitempty" toml:"prefixes,omitempty" yaml:"prefixes,omitempty"`
|
||||||
|
ForceSlash bool `json:"forceSlash,omitempty" toml:"forceSlash,omitempty" yaml:"forceSlash,omitempty"` // Deprecated
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaults Default values for a StripPrefix.
|
||||||
|
func (s *StripPrefix) SetDefaults() {
|
||||||
|
s.ForceSlash = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=true
|
// +k8s:deepcopy-gen=true
|
||||||
|
@@ -368,6 +368,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||||||
"foobar",
|
"foobar",
|
||||||
"fiibar",
|
"fiibar",
|
||||||
},
|
},
|
||||||
|
ForceSlash: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Middleware18": {
|
"Middleware18": {
|
||||||
@@ -771,6 +772,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||||||
"foobar",
|
"foobar",
|
||||||
"fiibar",
|
"fiibar",
|
||||||
},
|
},
|
||||||
|
ForceSlash: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Middleware18": {
|
"Middleware18": {
|
||||||
@@ -1091,6 +1093,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||||||
"traefik.HTTP.Middlewares.Middleware15.ReplacePathRegex.Replacement": "foobar",
|
"traefik.HTTP.Middlewares.Middleware15.ReplacePathRegex.Replacement": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware16.Retry.Attempts": "42",
|
"traefik.HTTP.Middlewares.Middleware16.Retry.Attempts": "42",
|
||||||
"traefik.HTTP.Middlewares.Middleware17.StripPrefix.Prefixes": "foobar, fiibar",
|
"traefik.HTTP.Middlewares.Middleware17.StripPrefix.Prefixes": "foobar, fiibar",
|
||||||
|
"traefik.HTTP.Middlewares.Middleware17.StripPrefix.ForceSlash": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware18.StripPrefixRegex.Regex": "foobar, fiibar",
|
"traefik.HTTP.Middlewares.Middleware18.StripPrefixRegex.Regex": "foobar, fiibar",
|
||||||
"traefik.HTTP.Middlewares.Middleware19.Compress": "true",
|
"traefik.HTTP.Middlewares.Middleware19.Compress": "true",
|
||||||
|
|
||||||
|
@@ -25,14 +25,20 @@ const (
|
|||||||
var singleton *HealthCheck
|
var singleton *HealthCheck
|
||||||
var once sync.Once
|
var once sync.Once
|
||||||
|
|
||||||
// BalancerHandler includes functionality for load-balancing management.
|
// Balancer is the set of operations required to manage the list of servers in a
|
||||||
type BalancerHandler interface {
|
// load-balancer.
|
||||||
ServeHTTP(w http.ResponseWriter, req *http.Request)
|
type Balancer interface {
|
||||||
Servers() []*url.URL
|
Servers() []*url.URL
|
||||||
RemoveServer(u *url.URL) error
|
RemoveServer(u *url.URL) error
|
||||||
UpsertServer(u *url.URL, options ...roundrobin.ServerOption) error
|
UpsertServer(u *url.URL, options ...roundrobin.ServerOption) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BalancerHandler includes functionality for load-balancing management.
|
||||||
|
type BalancerHandler interface {
|
||||||
|
ServeHTTP(w http.ResponseWriter, req *http.Request)
|
||||||
|
Balancer
|
||||||
|
}
|
||||||
|
|
||||||
// metricsRegistry is a local interface in the health check package, exposing only the required metrics
|
// metricsRegistry is a local interface in the health check package, exposing only the required metrics
|
||||||
// necessary for the health check package. This makes it easier for the tests.
|
// necessary for the health check package. This makes it easier for the tests.
|
||||||
type metricsRegistry interface {
|
type metricsRegistry interface {
|
||||||
@@ -49,7 +55,7 @@ type Options struct {
|
|||||||
Transport http.RoundTripper
|
Transport http.RoundTripper
|
||||||
Interval time.Duration
|
Interval time.Duration
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
LB BalancerHandler
|
LB Balancer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt Options) String() string {
|
func (opt Options) String() string {
|
||||||
@@ -146,18 +152,18 @@ func (hc *HealthCheck) checkBackend(ctx context.Context, backend *BackendConfig)
|
|||||||
enabledURLs := backend.LB.Servers()
|
enabledURLs := backend.LB.Servers()
|
||||||
var newDisabledURLs []backendURL
|
var newDisabledURLs []backendURL
|
||||||
// FIXME re enable metrics
|
// FIXME re enable metrics
|
||||||
for _, disableURL := range backend.disabledURLs {
|
for _, disabledURL := range backend.disabledURLs {
|
||||||
// FIXME serverUpMetricValue := float64(0)
|
// FIXME serverUpMetricValue := float64(0)
|
||||||
if err := checkHealth(disableURL.url, backend); err == nil {
|
if err := checkHealth(disabledURL.url, backend); err == nil {
|
||||||
logger.Warnf("Health check up: Returning to server list. Backend: %q URL: %q Weight: %d",
|
logger.Warnf("Health check up: Returning to server list. Backend: %q URL: %q Weight: %d",
|
||||||
backend.name, disableURL.url.String(), disableURL.weight)
|
backend.name, disabledURL.url.String(), disabledURL.weight)
|
||||||
if err = backend.LB.UpsertServer(disableURL.url, roundrobin.Weight(disableURL.weight)); err != nil {
|
if err = backend.LB.UpsertServer(disabledURL.url, roundrobin.Weight(disabledURL.weight)); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
}
|
}
|
||||||
// FIXME serverUpMetricValue = 1
|
// FIXME serverUpMetricValue = 1
|
||||||
} else {
|
} else {
|
||||||
logger.Warnf("Health check still failing. Backend: %q URL: %q Reason: %s", backend.name, disableURL.url.String(), err)
|
logger.Warnf("Health check still failing. Backend: %q URL: %q Reason: %s", backend.name, disabledURL.url.String(), err)
|
||||||
newDisabledURLs = append(newDisabledURLs, disableURL)
|
newDisabledURLs = append(newDisabledURLs, disabledURL)
|
||||||
}
|
}
|
||||||
// FIXME labelValues := []string{"backend", backend.name, "url", backendurl.url.String()}
|
// FIXME labelValues := []string{"backend", backend.name, "url", backendurl.url.String()}
|
||||||
// FIXME hc.metrics.BackendServerUpGauge().With(labelValues...).Set(serverUpMetricValue)
|
// FIXME hc.metrics.BackendServerUpGauge().With(labelValues...).Set(serverUpMetricValue)
|
||||||
@@ -177,7 +183,7 @@ func (hc *HealthCheck) checkBackend(ctx context.Context, backend *BackendConfig)
|
|||||||
weight = 1
|
weight = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.Warnf("Health check failed: Remove from server list. Backend: %q URL: %q Weight: %d Reason: %s", backend.name, enableURL.String(), weight, err)
|
logger.Warnf("Health check failed, removing from server list. Backend: %q URL: %q Weight: %d Reason: %s", backend.name, enableURL.String(), weight, err)
|
||||||
if err := backend.LB.RemoveServer(enableURL); err != nil {
|
if err := backend.LB.RemoveServer(enableURL); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
}
|
}
|
||||||
@@ -281,3 +287,38 @@ func (lb *LbStatusUpdater) UpsertServer(u *url.URL, options ...roundrobin.Server
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Balancers is a list of Balancers(s) that implements the Balancer interface.
|
||||||
|
type Balancers []Balancer
|
||||||
|
|
||||||
|
// Servers returns the servers url from all the BalancerHandler
|
||||||
|
func (b Balancers) Servers() []*url.URL {
|
||||||
|
var servers []*url.URL
|
||||||
|
for _, lb := range b {
|
||||||
|
servers = append(servers, lb.Servers()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveServer removes the given server from all the BalancerHandler,
|
||||||
|
// and updates the status of the server to "DOWN".
|
||||||
|
func (b Balancers) RemoveServer(u *url.URL) error {
|
||||||
|
for _, lb := range b {
|
||||||
|
if err := lb.RemoveServer(u); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpsertServer adds the given server to all the BalancerHandler,
|
||||||
|
// and updates the status of the server to "UP".
|
||||||
|
func (b Balancers) UpsertServer(u *url.URL, options ...roundrobin.ServerOption) error {
|
||||||
|
for _, lb := range b {
|
||||||
|
if err := lb.UpsertServer(u, options...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@@ -229,8 +229,10 @@ func OnConfigurationUpdate(dynConf dynamic.Configurations, entryPoints []string)
|
|||||||
|
|
||||||
for serviceName, service := range config.HTTP.Services {
|
for serviceName, service := range config.HTTP.Services {
|
||||||
dynamicConfig.services[fmt.Sprintf("%s@%s", serviceName, key)] = make(map[string]bool)
|
dynamicConfig.services[fmt.Sprintf("%s@%s", serviceName, key)] = make(map[string]bool)
|
||||||
for _, server := range service.LoadBalancer.Servers {
|
if service.LoadBalancer != nil {
|
||||||
dynamicConfig.services[fmt.Sprintf("%s@%s", serviceName, key)][server.URL] = true
|
for _, server := range service.LoadBalancer.Servers {
|
||||||
|
dynamicConfig.services[fmt.Sprintf("%s@%s", serviceName, key)][server.URL] = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -291,6 +291,11 @@ func TestPrometheusMetricRemoval(t *testing.T) {
|
|||||||
th.WithLoadBalancerServices(th.WithService("bar",
|
th.WithLoadBalancerServices(th.WithService("bar",
|
||||||
th.WithServers(th.WithServer("http://localhost:9000"))),
|
th.WithServers(th.WithServer("http://localhost:9000"))),
|
||||||
),
|
),
|
||||||
|
func(cfg *dynamic.HTTPConfiguration) {
|
||||||
|
cfg.Services["fii"] = &dynamic.Service{
|
||||||
|
Weighted: &dynamic.WeightedRoundRobin{},
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -116,7 +116,18 @@ type CoreLogData map[string]interface{}
|
|||||||
// LogData is the data captured by the middleware so that it can be logged.
|
// LogData is the data captured by the middleware so that it can be logged.
|
||||||
type LogData struct {
|
type LogData struct {
|
||||||
Core CoreLogData
|
Core CoreLogData
|
||||||
Request http.Header
|
Request request
|
||||||
OriginResponse http.Header
|
OriginResponse http.Header
|
||||||
DownstreamResponse http.Header
|
DownstreamResponse downstreamResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
type downstreamResponse struct {
|
||||||
|
headers http.Header
|
||||||
|
status int
|
||||||
|
size int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type request struct {
|
||||||
|
headers http.Header
|
||||||
|
count int64
|
||||||
}
|
}
|
||||||
|
@@ -47,8 +47,6 @@ func (n noopCloser) Close() error {
|
|||||||
|
|
||||||
type handlerParams struct {
|
type handlerParams struct {
|
||||||
logDataTable *LogData
|
logDataTable *LogData
|
||||||
crr *captureRequestReader
|
|
||||||
crw *captureResponseWriter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler will write each request and its response to the access log.
|
// Handler will write each request and its response to the access log.
|
||||||
@@ -122,7 +120,7 @@ func NewHandler(config *types.AccessLog) (*Handler, error) {
|
|||||||
go func() {
|
go func() {
|
||||||
defer logHandler.wg.Done()
|
defer logHandler.wg.Done()
|
||||||
for handlerParams := range logHandler.logHandlerChan {
|
for handlerParams := range logHandler.logHandlerChan {
|
||||||
logHandler.logTheRoundTrip(handlerParams.logDataTable, handlerParams.crr, handlerParams.crw)
|
logHandler.logTheRoundTrip(handlerParams.logDataTable)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@@ -162,7 +160,12 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http
|
|||||||
StartLocal: now.Local(),
|
StartLocal: now.Local(),
|
||||||
}
|
}
|
||||||
|
|
||||||
logDataTable := &LogData{Core: core, Request: req.Header}
|
logDataTable := &LogData{
|
||||||
|
Core: core,
|
||||||
|
Request: request{
|
||||||
|
headers: req.Header,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
reqWithDataTable := req.WithContext(context.WithValue(req.Context(), DataTableKey, logDataTable))
|
reqWithDataTable := req.WithContext(context.WithValue(req.Context(), DataTableKey, logDataTable))
|
||||||
|
|
||||||
@@ -205,16 +208,21 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http
|
|||||||
core[ClientUsername] = usernameIfPresent(reqWithDataTable.URL)
|
core[ClientUsername] = usernameIfPresent(reqWithDataTable.URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
logDataTable.DownstreamResponse = crw.Header()
|
logDataTable.DownstreamResponse = downstreamResponse{
|
||||||
|
headers: crw.Header().Clone(),
|
||||||
|
status: crw.Status(),
|
||||||
|
size: crw.Size(),
|
||||||
|
}
|
||||||
|
if crr != nil {
|
||||||
|
logDataTable.Request.count = crr.count
|
||||||
|
}
|
||||||
|
|
||||||
if h.config.BufferingSize > 0 {
|
if h.config.BufferingSize > 0 {
|
||||||
h.logHandlerChan <- handlerParams{
|
h.logHandlerChan <- handlerParams{
|
||||||
logDataTable: logDataTable,
|
logDataTable: logDataTable,
|
||||||
crr: crr,
|
|
||||||
crw: crw,
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
h.logTheRoundTrip(logDataTable, crr, crw)
|
h.logTheRoundTrip(logDataTable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +272,7 @@ func usernameIfPresent(theURL *url.URL) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Logging handler to log frontend name, backend name, and elapsed time.
|
// Logging handler to log frontend name, backend name, and elapsed time.
|
||||||
func (h *Handler) logTheRoundTrip(logDataTable *LogData, crr *captureRequestReader, crw *captureResponseWriter) {
|
func (h *Handler) logTheRoundTrip(logDataTable *LogData) {
|
||||||
core := logDataTable.Core
|
core := logDataTable.Core
|
||||||
|
|
||||||
retryAttempts, ok := core[RetryAttempts].(int)
|
retryAttempts, ok := core[RetryAttempts].(int)
|
||||||
@@ -272,23 +280,22 @@ func (h *Handler) logTheRoundTrip(logDataTable *LogData, crr *captureRequestRead
|
|||||||
retryAttempts = 0
|
retryAttempts = 0
|
||||||
}
|
}
|
||||||
core[RetryAttempts] = retryAttempts
|
core[RetryAttempts] = retryAttempts
|
||||||
|
core[RequestContentSize] = logDataTable.Request.count
|
||||||
|
|
||||||
if crr != nil {
|
status := logDataTable.DownstreamResponse.status
|
||||||
core[RequestContentSize] = crr.count
|
core[DownstreamStatus] = status
|
||||||
}
|
|
||||||
|
|
||||||
core[DownstreamStatus] = crw.Status()
|
|
||||||
|
|
||||||
// n.b. take care to perform time arithmetic using UTC to avoid errors at DST boundaries.
|
// n.b. take care to perform time arithmetic using UTC to avoid errors at DST boundaries.
|
||||||
totalDuration := time.Now().UTC().Sub(core[StartUTC].(time.Time))
|
totalDuration := time.Now().UTC().Sub(core[StartUTC].(time.Time))
|
||||||
core[Duration] = totalDuration
|
core[Duration] = totalDuration
|
||||||
|
|
||||||
if h.keepAccessLog(crw.Status(), retryAttempts, totalDuration) {
|
if h.keepAccessLog(status, retryAttempts, totalDuration) {
|
||||||
core[DownstreamContentSize] = crw.Size()
|
size := logDataTable.DownstreamResponse.size
|
||||||
|
core[DownstreamContentSize] = size
|
||||||
if original, ok := core[OriginContentSize]; ok {
|
if original, ok := core[OriginContentSize]; ok {
|
||||||
o64 := original.(int64)
|
o64 := original.(int64)
|
||||||
if crw.Size() != o64 && crw.Size() != 0 {
|
if size != o64 && size != 0 {
|
||||||
core[GzipRatio] = float64(o64) / float64(crw.Size())
|
core[GzipRatio] = float64(o64) / float64(size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,9 +312,9 @@ func (h *Handler) logTheRoundTrip(logDataTable *LogData, crr *captureRequestRead
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h.redactHeaders(logDataTable.Request, fields, "request_")
|
h.redactHeaders(logDataTable.Request.headers, fields, "request_")
|
||||||
h.redactHeaders(logDataTable.OriginResponse, fields, "origin_")
|
h.redactHeaders(logDataTable.OriginResponse, fields, "origin_")
|
||||||
h.redactHeaders(logDataTable.DownstreamResponse, fields, "downstream_")
|
h.redactHeaders(logDataTable.DownstreamResponse.headers, fields, "downstream_")
|
||||||
|
|
||||||
h.mu.Lock()
|
h.mu.Lock()
|
||||||
defer h.mu.Unlock()
|
defer h.mu.Unlock()
|
||||||
|
@@ -192,6 +192,7 @@ func TestLoggerJSON(t *testing.T) {
|
|||||||
Format: JSONFormat,
|
Format: JSONFormat,
|
||||||
},
|
},
|
||||||
expected: map[string]func(t *testing.T, value interface{}){
|
expected: map[string]func(t *testing.T, value interface{}){
|
||||||
|
RequestContentSize: assertFloat64(0),
|
||||||
RequestHost: assertString(testHostname),
|
RequestHost: assertString(testHostname),
|
||||||
RequestAddr: assertString(testHostname),
|
RequestAddr: assertString(testHostname),
|
||||||
RequestMethod: assertString(testMethod),
|
RequestMethod: assertString(testMethod),
|
||||||
|
@@ -41,23 +41,35 @@ func New(ctx context.Context, next http.Handler, config dynamic.AddPrefix, name
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ap *addPrefix) GetTracingInformation() (string, ext.SpanKindEnum) {
|
func (a *addPrefix) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||||
return ap.name, tracing.SpanKindNoneEnum
|
return a.name, tracing.SpanKindNoneEnum
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ap *addPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (a *addPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
logger := log.FromContext(middlewares.GetLoggerCtx(req.Context(), ap.name, typeName))
|
logger := log.FromContext(middlewares.GetLoggerCtx(req.Context(), a.name, typeName))
|
||||||
|
|
||||||
oldURLPath := req.URL.Path
|
oldURLPath := req.URL.Path
|
||||||
req.URL.Path = ap.prefix + req.URL.Path
|
req.URL.Path = ensureLeadingSlash(a.prefix + req.URL.Path)
|
||||||
logger.Debugf("URL.Path is now %s (was %s).", req.URL.Path, oldURLPath)
|
logger.Debugf("URL.Path is now %s (was %s).", req.URL.Path, oldURLPath)
|
||||||
|
|
||||||
if req.URL.RawPath != "" {
|
if req.URL.RawPath != "" {
|
||||||
oldURLRawPath := req.URL.RawPath
|
oldURLRawPath := req.URL.RawPath
|
||||||
req.URL.RawPath = ap.prefix + req.URL.RawPath
|
req.URL.RawPath = ensureLeadingSlash(a.prefix + req.URL.RawPath)
|
||||||
logger.Debugf("URL.RawPath is now %s (was %s).", req.URL.RawPath, oldURLRawPath)
|
logger.Debugf("URL.RawPath is now %s (was %s).", req.URL.RawPath, oldURLRawPath)
|
||||||
}
|
}
|
||||||
req.RequestURI = req.URL.RequestURI()
|
req.RequestURI = req.URL.RequestURI()
|
||||||
|
|
||||||
ap.next.ServeHTTP(rw, req)
|
a.next.ServeHTTP(rw, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureLeadingSlash(str string) string {
|
||||||
|
if str == "" {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
if str[0] == '/' {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
return "/" + str
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,6 @@ import (
|
|||||||
|
|
||||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||||
"github.com/containous/traefik/v2/pkg/testhelpers"
|
"github.com/containous/traefik/v2/pkg/testhelpers"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -47,7 +46,6 @@ func TestNewAddPrefix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAddPrefix(t *testing.T) {
|
func TestAddPrefix(t *testing.T) {
|
||||||
logrus.SetLevel(logrus.DebugLevel)
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
prefix dynamic.AddPrefix
|
prefix dynamic.AddPrefix
|
||||||
@@ -61,6 +59,12 @@ func TestAddPrefix(t *testing.T) {
|
|||||||
path: "/b",
|
path: "/b",
|
||||||
expectedPath: "/a/b",
|
expectedPath: "/a/b",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Works with missing leading slash",
|
||||||
|
prefix: dynamic.AddPrefix{Prefix: "a"},
|
||||||
|
path: "/",
|
||||||
|
expectedPath: "/a/",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "Works with a raw path",
|
desc: "Works with a raw path",
|
||||||
prefix: dynamic.AddPrefix{Prefix: "/a"},
|
prefix: dynamic.AddPrefix{Prefix: "/a"},
|
||||||
|
@@ -221,13 +221,11 @@ func (s *Header) processCorsHeaders(rw http.ResponseWriter, req *http.Request) b
|
|||||||
}
|
}
|
||||||
|
|
||||||
reqAcMethod := req.Header.Get("Access-Control-Request-Method")
|
reqAcMethod := req.Header.Get("Access-Control-Request-Method")
|
||||||
reqAcHeaders := req.Header.Get("Access-Control-Request-Headers")
|
|
||||||
originHeader := req.Header.Get("Origin")
|
originHeader := req.Header.Get("Origin")
|
||||||
|
|
||||||
if reqAcMethod != "" && reqAcHeaders != "" && originHeader != "" && req.Method == http.MethodOptions {
|
if reqAcMethod != "" && originHeader != "" && req.Method == http.MethodOptions {
|
||||||
// If the request is an OPTIONS request with an Access-Control-Request-Method header,
|
// If the request is an OPTIONS request with an Access-Control-Request-Method header,
|
||||||
// and Access-Control-Request-Headers headers, and Origin headers,
|
// and Origin headers, then it is a CORS preflight request,
|
||||||
// then it is a CORS preflight request,
|
|
||||||
// and we need to build a custom response: https://www.w3.org/TR/cors/#preflight-request
|
// and we need to build a custom response: https://www.w3.org/TR/cors/#preflight-request
|
||||||
if s.headers.AccessControlAllowCredentials {
|
if s.headers.AccessControlAllowCredentials {
|
||||||
rw.Header().Set("Access-Control-Allow-Credentials", "true")
|
rw.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||||
|
@@ -275,6 +275,25 @@ func TestCORSPreflights(t *testing.T) {
|
|||||||
"Access-Control-Allow-Headers": {"origin,X-Forwarded-For"},
|
"Access-Control-Allow-Headers": {"origin,X-Forwarded-For"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "No Request Headers Preflight",
|
||||||
|
header: NewHeader(emptyHandler, dynamic.Headers{
|
||||||
|
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
|
||||||
|
AccessControlAllowOrigin: "*",
|
||||||
|
AccessControlAllowHeaders: []string{"origin", "X-Forwarded-For"},
|
||||||
|
AccessControlMaxAge: 600,
|
||||||
|
}),
|
||||||
|
requestHeaders: map[string][]string{
|
||||||
|
"Access-Control-Request-Method": {"GET", "OPTIONS"},
|
||||||
|
"Origin": {"https://foo.bar.org"},
|
||||||
|
},
|
||||||
|
expected: map[string][]string{
|
||||||
|
"Access-Control-Allow-Origin": {"*"},
|
||||||
|
"Access-Control-Max-Age": {"600"},
|
||||||
|
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
|
||||||
|
"Access-Control-Allow-Headers": {"origin,X-Forwarded-For"},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
|
@@ -18,10 +18,17 @@ import (
|
|||||||
"github.com/opentracing/opentracing-go/ext"
|
"github.com/opentracing/opentracing-go/ext"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const typeName = "PassClientTLSCert"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
xForwardedTLSClientCert = "X-Forwarded-Tls-Client-Cert"
|
xForwardedTLSClientCert = "X-Forwarded-Tls-Client-Cert"
|
||||||
xForwardedTLSClientCertInfo = "X-Forwarded-Tls-Client-Cert-Info"
|
xForwardedTLSClientCertInfo = "X-Forwarded-Tls-Client-Cert-Info"
|
||||||
typeName = "PassClientTLSCert"
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
certSeparator = ","
|
||||||
|
fieldSeparator = ";"
|
||||||
|
subFieldSeparator = ","
|
||||||
)
|
)
|
||||||
|
|
||||||
var attributeTypeNames = map[string]string{
|
var attributeTypeNames = map[string]string{
|
||||||
@@ -55,6 +62,29 @@ func newDistinguishedNameOptions(info *dynamic.TLSCLientCertificateDNInfo) *Dist
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tlsClientCertificateInfo is a struct for specifying the configuration for the passTLSClientCert middleware.
|
||||||
|
type tlsClientCertificateInfo struct {
|
||||||
|
notAfter bool
|
||||||
|
notBefore bool
|
||||||
|
sans bool
|
||||||
|
subject *DistinguishedNameOptions
|
||||||
|
issuer *DistinguishedNameOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTLSClientCertificateInfo(info *dynamic.TLSClientCertificateInfo) *tlsClientCertificateInfo {
|
||||||
|
if info == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tlsClientCertificateInfo{
|
||||||
|
issuer: newDistinguishedNameOptions(info.Issuer),
|
||||||
|
notAfter: info.NotAfter,
|
||||||
|
notBefore: info.NotBefore,
|
||||||
|
subject: newDistinguishedNameOptions(info.Subject),
|
||||||
|
sans: info.Sans,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// passTLSClientCert is a middleware that helps setup a few tls info features.
|
// passTLSClientCert is a middleware that helps setup a few tls info features.
|
||||||
type passTLSClientCert struct {
|
type passTLSClientCert struct {
|
||||||
next http.Handler
|
next http.Handler
|
||||||
@@ -71,45 +101,84 @@ func New(ctx context.Context, next http.Handler, config dynamic.PassTLSClientCer
|
|||||||
next: next,
|
next: next,
|
||||||
name: name,
|
name: name,
|
||||||
pem: config.PEM,
|
pem: config.PEM,
|
||||||
info: newTLSClientInfo(config.Info),
|
info: newTLSClientCertificateInfo(config.Info),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// tlsClientCertificateInfo is a struct for specifying the configuration for the passTLSClientCert middleware.
|
|
||||||
type tlsClientCertificateInfo struct {
|
|
||||||
notAfter bool
|
|
||||||
notBefore bool
|
|
||||||
sans bool
|
|
||||||
subject *DistinguishedNameOptions
|
|
||||||
issuer *DistinguishedNameOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTLSClientInfo(info *dynamic.TLSClientCertificateInfo) *tlsClientCertificateInfo {
|
|
||||||
if info == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tlsClientCertificateInfo{
|
|
||||||
issuer: newDistinguishedNameOptions(info.Issuer),
|
|
||||||
notAfter: info.NotAfter,
|
|
||||||
notBefore: info.NotBefore,
|
|
||||||
subject: newDistinguishedNameOptions(info.Subject),
|
|
||||||
sans: info.Sans,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *passTLSClientCert) GetTracingInformation() (string, ext.SpanKindEnum) {
|
func (p *passTLSClientCert) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||||
return p.name, tracing.SpanKindNoneEnum
|
return p.name, tracing.SpanKindNoneEnum
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *passTLSClientCert) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (p *passTLSClientCert) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
ctx := middlewares.GetLoggerCtx(req.Context(), p.name, typeName)
|
ctx := middlewares.GetLoggerCtx(req.Context(), p.name, typeName)
|
||||||
|
logger := log.FromContext(ctx)
|
||||||
|
|
||||||
|
if p.pem {
|
||||||
|
if req.TLS != nil && len(req.TLS.PeerCertificates) > 0 {
|
||||||
|
req.Header.Set(xForwardedTLSClientCert, getCertificates(ctx, req.TLS.PeerCertificates))
|
||||||
|
} else {
|
||||||
|
logger.Warn("Tried to extract a certificate on a request without mutual TLS")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.info != nil {
|
||||||
|
if req.TLS != nil && len(req.TLS.PeerCertificates) > 0 {
|
||||||
|
headerContent := p.getCertInfo(ctx, req.TLS.PeerCertificates)
|
||||||
|
req.Header.Set(xForwardedTLSClientCertInfo, url.QueryEscape(headerContent))
|
||||||
|
} else {
|
||||||
|
logger.Warn("Tried to extract a certificate on a request without mutual TLS")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
p.modifyRequestHeaders(ctx, req)
|
|
||||||
p.next.ServeHTTP(rw, req)
|
p.next.ServeHTTP(rw, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDNInfo(ctx context.Context, prefix string, options *DistinguishedNameOptions, cs *pkix.Name) string {
|
// getCertInfo Build a string with the wanted client certificates information
|
||||||
|
// - the `,` is used to separate certificates
|
||||||
|
// - the `;` is used to separate root fields
|
||||||
|
// - the value of root fields is always wrapped by double quote
|
||||||
|
// - if a field is empty, the field is ignored
|
||||||
|
func (p *passTLSClientCert) getCertInfo(ctx context.Context, certs []*x509.Certificate) string {
|
||||||
|
var headerValues []string
|
||||||
|
|
||||||
|
for _, peerCert := range certs {
|
||||||
|
var values []string
|
||||||
|
|
||||||
|
if p.info != nil {
|
||||||
|
subject := getDNInfo(ctx, p.info.subject, &peerCert.Subject)
|
||||||
|
if subject != "" {
|
||||||
|
values = append(values, fmt.Sprintf(`Subject="%s"`, strings.TrimSuffix(subject, subFieldSeparator)))
|
||||||
|
}
|
||||||
|
|
||||||
|
issuer := getDNInfo(ctx, p.info.issuer, &peerCert.Issuer)
|
||||||
|
if issuer != "" {
|
||||||
|
values = append(values, fmt.Sprintf(`Issuer="%s"`, strings.TrimSuffix(issuer, subFieldSeparator)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.info.notBefore {
|
||||||
|
values = append(values, fmt.Sprintf(`NB="%d"`, uint64(peerCert.NotBefore.Unix())))
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.info.notAfter {
|
||||||
|
values = append(values, fmt.Sprintf(`NA="%d"`, uint64(peerCert.NotAfter.Unix())))
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.info.sans {
|
||||||
|
sans := getSANs(peerCert)
|
||||||
|
if len(sans) > 0 {
|
||||||
|
values = append(values, fmt.Sprintf(`SAN="%s"`, strings.Join(sans, subFieldSeparator)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value := strings.Join(values, fieldSeparator)
|
||||||
|
headerValues = append(headerValues, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(headerValues, certSeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDNInfo(ctx context.Context, options *DistinguishedNameOptions, cs *pkix.Name) string {
|
||||||
if options == nil {
|
if options == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -120,7 +189,7 @@ func getDNInfo(ctx context.Context, prefix string, options *DistinguishedNameOpt
|
|||||||
for _, name := range cs.Names {
|
for _, name := range cs.Names {
|
||||||
// Domain Component - RFC 2247
|
// Domain Component - RFC 2247
|
||||||
if options.DomainComponent && attributeTypeNames[name.Type.String()] == "DC" {
|
if options.DomainComponent && attributeTypeNames[name.Type.String()] == "DC" {
|
||||||
content.WriteString(fmt.Sprintf("DC=%s,", name.Value))
|
content.WriteString(fmt.Sprintf("DC=%s%s", name.Value, subFieldSeparator))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,11 +217,7 @@ func getDNInfo(ctx context.Context, prefix string, options *DistinguishedNameOpt
|
|||||||
writePart(ctx, content, cs.CommonName, "CN")
|
writePart(ctx, content, cs.CommonName, "CN")
|
||||||
}
|
}
|
||||||
|
|
||||||
if content.Len() > 0 {
|
return content.String()
|
||||||
return prefix + `="` + strings.TrimSuffix(content.String(), ",") + `"`
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeParts(ctx context.Context, content io.StringWriter, entries []string, prefix string) {
|
func writeParts(ctx context.Context, content io.StringWriter, entries []string, prefix string) {
|
||||||
@@ -163,135 +228,63 @@ func writeParts(ctx context.Context, content io.StringWriter, entries []string,
|
|||||||
|
|
||||||
func writePart(ctx context.Context, content io.StringWriter, entry string, prefix string) {
|
func writePart(ctx context.Context, content io.StringWriter, entry string, prefix string) {
|
||||||
if len(entry) > 0 {
|
if len(entry) > 0 {
|
||||||
_, err := content.WriteString(fmt.Sprintf("%s=%s,", prefix, entry))
|
_, err := content.WriteString(fmt.Sprintf("%s=%s%s", prefix, entry, subFieldSeparator))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FromContext(ctx).Error(err)
|
log.FromContext(ctx).Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getXForwardedTLSClientCertInfo Build a string with the wanted client certificates information
|
|
||||||
// like Subject="C=%s,ST=%s,L=%s,O=%s,CN=%s",NB=%d,NA=%d,SAN=%s;
|
|
||||||
func (p *passTLSClientCert) getXForwardedTLSClientCertInfo(ctx context.Context, certs []*x509.Certificate) string {
|
|
||||||
var headerValues []string
|
|
||||||
|
|
||||||
for _, peerCert := range certs {
|
|
||||||
var values []string
|
|
||||||
var sans string
|
|
||||||
var nb string
|
|
||||||
var na string
|
|
||||||
|
|
||||||
if p.info != nil {
|
|
||||||
subject := getDNInfo(ctx, "Subject", p.info.subject, &peerCert.Subject)
|
|
||||||
if len(subject) > 0 {
|
|
||||||
values = append(values, subject)
|
|
||||||
}
|
|
||||||
|
|
||||||
issuer := getDNInfo(ctx, "Issuer", p.info.issuer, &peerCert.Issuer)
|
|
||||||
if len(issuer) > 0 {
|
|
||||||
values = append(values, issuer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ci := p.info
|
|
||||||
if ci != nil {
|
|
||||||
if ci.notBefore {
|
|
||||||
nb = fmt.Sprintf("NB=%d", uint64(peerCert.NotBefore.Unix()))
|
|
||||||
values = append(values, nb)
|
|
||||||
}
|
|
||||||
if ci.notAfter {
|
|
||||||
na = fmt.Sprintf("NA=%d", uint64(peerCert.NotAfter.Unix()))
|
|
||||||
values = append(values, na)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ci.sans {
|
|
||||||
sans = fmt.Sprintf("SAN=%s", strings.Join(getSANs(peerCert), ","))
|
|
||||||
values = append(values, sans)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
value := strings.Join(values, ",")
|
|
||||||
headerValues = append(headerValues, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(headerValues, ";")
|
|
||||||
}
|
|
||||||
|
|
||||||
// modifyRequestHeaders set the wanted headers with the certificates information.
|
|
||||||
func (p *passTLSClientCert) modifyRequestHeaders(ctx context.Context, r *http.Request) {
|
|
||||||
logger := log.FromContext(ctx)
|
|
||||||
|
|
||||||
if p.pem {
|
|
||||||
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
|
|
||||||
r.Header.Set(xForwardedTLSClientCert, getXForwardedTLSClientCert(ctx, r.TLS.PeerCertificates))
|
|
||||||
} else {
|
|
||||||
logger.Warn("Tried to extract a certificate on a request without mutual TLS")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.info != nil {
|
|
||||||
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
|
|
||||||
headerContent := p.getXForwardedTLSClientCertInfo(ctx, r.TLS.PeerCertificates)
|
|
||||||
r.Header.Set(xForwardedTLSClientCertInfo, url.QueryEscape(headerContent))
|
|
||||||
} else {
|
|
||||||
logger.Warn("Tried to extract a certificate on a request without mutual TLS")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sanitize As we pass the raw certificates, remove the useless data and make it http request compliant.
|
// sanitize As we pass the raw certificates, remove the useless data and make it http request compliant.
|
||||||
func sanitize(cert []byte) string {
|
func sanitize(cert []byte) string {
|
||||||
s := string(cert)
|
cleaned := strings.NewReplacer(
|
||||||
r := strings.NewReplacer("-----BEGIN CERTIFICATE-----", "",
|
"-----BEGIN CERTIFICATE-----", "",
|
||||||
"-----END CERTIFICATE-----", "",
|
"-----END CERTIFICATE-----", "",
|
||||||
"\n", "")
|
"\n", "",
|
||||||
cleaned := r.Replace(s)
|
).Replace(string(cert))
|
||||||
|
|
||||||
return url.QueryEscape(cleaned)
|
return url.QueryEscape(cleaned)
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractCertificate extract the certificate from the request.
|
// getCertificates Build a string with the client certificates.
|
||||||
func extractCertificate(ctx context.Context, cert *x509.Certificate) string {
|
func getCertificates(ctx context.Context, certs []*x509.Certificate) string {
|
||||||
b := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
|
|
||||||
certPEM := pem.EncodeToMemory(&b)
|
|
||||||
if certPEM == nil {
|
|
||||||
log.FromContext(ctx).Error("Cannot extract the certificate content")
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return sanitize(certPEM)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getXForwardedTLSClientCert Build a string with the client certificates.
|
|
||||||
func getXForwardedTLSClientCert(ctx context.Context, certs []*x509.Certificate) string {
|
|
||||||
var headerValues []string
|
var headerValues []string
|
||||||
|
|
||||||
for _, peerCert := range certs {
|
for _, peerCert := range certs {
|
||||||
headerValues = append(headerValues, extractCertificate(ctx, peerCert))
|
headerValues = append(headerValues, extractCertificate(ctx, peerCert))
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.Join(headerValues, ",")
|
return strings.Join(headerValues, certSeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractCertificate extract the certificate from the request.
|
||||||
|
func extractCertificate(ctx context.Context, cert *x509.Certificate) string {
|
||||||
|
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
|
||||||
|
if certPEM == nil {
|
||||||
|
log.FromContext(ctx).Error("Cannot extract the certificate content")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return sanitize(certPEM)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getSANs get the Subject Alternate Name values.
|
// getSANs get the Subject Alternate Name values.
|
||||||
func getSANs(cert *x509.Certificate) []string {
|
func getSANs(cert *x509.Certificate) []string {
|
||||||
var sans []string
|
|
||||||
if cert == nil {
|
if cert == nil {
|
||||||
return sans
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sans []string
|
||||||
sans = append(sans, cert.DNSNames...)
|
sans = append(sans, cert.DNSNames...)
|
||||||
sans = append(sans, cert.EmailAddresses...)
|
sans = append(sans, cert.EmailAddresses...)
|
||||||
|
|
||||||
var ips []string
|
|
||||||
for _, ip := range cert.IPAddresses {
|
for _, ip := range cert.IPAddresses {
|
||||||
ips = append(ips, ip.String())
|
sans = append(sans, ip.String())
|
||||||
}
|
}
|
||||||
sans = append(sans, ips...)
|
|
||||||
|
|
||||||
var uris []string
|
|
||||||
for _, uri := range cert.URIs {
|
for _, uri := range cert.URIs {
|
||||||
uris = append(uris, uri.String())
|
sans = append(sans, uri.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return append(sans, uris...)
|
return sans
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,7 @@ import (
|
|||||||
|
|
||||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||||
"github.com/containous/traefik/v2/pkg/testhelpers"
|
"github.com/containous/traefik/v2/pkg/testhelpers"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -113,6 +114,7 @@ Cg+XKmHzexmTnKaKac2w9ZECpRsQ9IBdQq9OghIwPtOnERTOUJEEgNcqA+9xELjb
|
|||||||
pQ==
|
pQ==
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
`
|
`
|
||||||
|
|
||||||
minimalCheeseCrt = `-----BEGIN CERTIFICATE-----
|
minimalCheeseCrt = `-----BEGIN CERTIFICATE-----
|
||||||
MIIEQDCCAygCFFRY0OBk/L5Se0IZRj3CMljawL2UMA0GCSqGSIb3DQEBCwUAMIIB
|
MIIEQDCCAygCFFRY0OBk/L5Se0IZRj3CMljawL2UMA0GCSqGSIb3DQEBCwUAMIIB
|
||||||
hDETMBEGCgmSJomT8ixkARkWA29yZzEWMBQGCgmSJomT8ixkARkWBmNoZWVzZTEP
|
hDETMBEGCgmSJomT8ixkARkWA29yZzEWMBQGCgmSJomT8ixkARkWBmNoZWVzZTEP
|
||||||
@@ -262,47 +264,6 @@ jECvgAY7Nfd9mZ1KtyNaW31is+kag7NsvjxU/kM=
|
|||||||
-----END CERTIFICATE-----`
|
-----END CERTIFICATE-----`
|
||||||
)
|
)
|
||||||
|
|
||||||
func getCleanCertContents(certContents []string) string {
|
|
||||||
var re = regexp.MustCompile("-----BEGIN CERTIFICATE-----(?s)(.*)")
|
|
||||||
|
|
||||||
var cleanedCertContent []string
|
|
||||||
for _, certContent := range certContents {
|
|
||||||
cert := re.FindString(certContent)
|
|
||||||
cleanedCertContent = append(cleanedCertContent, sanitize([]byte(cert)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(cleanedCertContent, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCertificate(certContent string) *x509.Certificate {
|
|
||||||
roots := x509.NewCertPool()
|
|
||||||
ok := roots.AppendCertsFromPEM([]byte(signingCA))
|
|
||||||
if !ok {
|
|
||||||
panic("failed to parse root certificate")
|
|
||||||
}
|
|
||||||
|
|
||||||
block, _ := pem.Decode([]byte(certContent))
|
|
||||||
if block == nil {
|
|
||||||
panic("failed to parse certificate PEM")
|
|
||||||
}
|
|
||||||
cert, err := x509.ParseCertificate(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
panic("failed to parse certificate: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return cert
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildTLSWith(certContents []string) *tls.ConnectionState {
|
|
||||||
var peerCertificates []*x509.Certificate
|
|
||||||
|
|
||||||
for _, certContent := range certContents {
|
|
||||||
peerCertificates = append(peerCertificates, getCertificate(certContent))
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tls.ConnectionState{PeerCertificates: peerCertificates}
|
|
||||||
}
|
|
||||||
|
|
||||||
var next = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
var next = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
_, err := w.Write([]byte("bar"))
|
_, err := w.Write([]byte("bar"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -310,59 +271,7 @@ var next = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
func getExpectedSanitized(s string) string {
|
func TestPassTLSClientCert_PEM(t *testing.T) {
|
||||||
return url.QueryEscape(strings.Replace(s, "\n", "", -1))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSanitize(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
desc string
|
|
||||||
toSanitize []byte
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "Empty",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "With a minimal cert",
|
|
||||||
toSanitize: []byte(minimalCheeseCrt),
|
|
||||||
expected: getExpectedSanitized(`MIIEQDCCAygCFFRY0OBk/L5Se0IZRj3CMljawL2UMA0GCSqGSIb3DQEBCwUAMIIB
|
|
||||||
hDETMBEGCgmSJomT8ixkARkWA29yZzEWMBQGCgmSJomT8ixkARkWBmNoZWVzZTEP
|
|
||||||
MA0GA1UECgwGQ2hlZXNlMREwDwYDVQQKDAhDaGVlc2UgMjEfMB0GA1UECwwWU2lt
|
|
||||||
cGxlIFNpZ25pbmcgU2VjdGlvbjEhMB8GA1UECwwYU2ltcGxlIFNpZ25pbmcgU2Vj
|
|
||||||
dGlvbiAyMRowGAYDVQQDDBFTaW1wbGUgU2lnbmluZyBDQTEcMBoGA1UEAwwTU2lt
|
|
||||||
cGxlIFNpZ25pbmcgQ0EgMjELMAkGA1UEBhMCRlIxCzAJBgNVBAYTAlVTMREwDwYD
|
|
||||||
VQQHDAhUT1VMT1VTRTENMAsGA1UEBwwETFlPTjEWMBQGA1UECAwNU2lnbmluZyBT
|
|
||||||
dGF0ZTEYMBYGA1UECAwPU2lnbmluZyBTdGF0ZSAyMSEwHwYJKoZIhvcNAQkBFhJz
|
|
||||||
aW1wbGVAc2lnbmluZy5jb20xIjAgBgkqhkiG9w0BCQEWE3NpbXBsZTJAc2lnbmlu
|
|
||||||
Zy5jb20wHhcNMTgxMjA2MTExMDM2WhcNMjEwOTI1MTExMDM2WjAzMQswCQYDVQQG
|
|
||||||
EwJGUjETMBEGA1UECAwKU29tZS1TdGF0ZTEPMA0GA1UECgwGQ2hlZXNlMIIBIjAN
|
|
||||||
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAskX/bUtwFo1gF2BTPNaNcTUMaRFu
|
|
||||||
FMZozK8IgLjccZ4kZ0R9oFO6Yp8Zl/IvPaf7tE26PI7XP7eHriUdhnQzX7iioDd0
|
|
||||||
RZa68waIhAGc+xPzRFrP3b3yj3S2a9Rve3c0K+SCV+EtKAwsxMqQDhoo9PcBfo5B
|
|
||||||
RHfht07uD5MncUcGirwN+/pxHV5xzAGPcc7On0/5L7bq/G+63nhu78zw9XyuLaHC
|
|
||||||
PM5VbOUvpyIESJHbMMzTdFGL8ob9VKO+Kr1kVGdEA9i8FLGl3xz/GBKuW/JD0xyW
|
|
||||||
DrU29mri5vYWHmkuv7ZWHGXnpXjTtPHwveE9/0/ArnmpMyR9JtqFr1oEvQIDAQAB
|
|
||||||
MA0GCSqGSIb3DQEBCwUAA4IBAQBHta+NWXI08UHeOkGzOTGRiWXsOH2dqdX6gTe9
|
|
||||||
xF1AIjyoQ0gvpoGVvlnChSzmlUj+vnx/nOYGIt1poE3hZA3ZHZD/awsvGyp3GwWD
|
|
||||||
IfXrEViSCIyF+8tNNKYyUcEO3xdAsAUGgfUwwF/mZ6MBV5+A/ZEEILlTq8zFt9dV
|
|
||||||
vdKzIt7fZYxYBBHFSarl1x8pDgWXlf3hAufevGJXip9xGYmznF0T5cq1RbWJ4be3
|
|
||||||
/9K7yuWhuBYC3sbTbCneHBa91M82za+PIISc1ygCYtWSBoZKSAqLk0rkZpHaekDP
|
|
||||||
WqeUSNGYV//RunTeuRDAf5OxehERb1srzBXhRZ3cZdzXbgR/`),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range testCases {
|
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
require.Equal(t, test.expected, sanitize(test.toSanitize), "The sanitized certificates should be equal")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTLSClientHeadersWithPEM(t *testing.T) {
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
certContents []string // set the request TLS attribute if defined
|
certContents []string // set the request TLS attribute if defined
|
||||||
@@ -417,70 +326,36 @@ func TestTLSClientHeadersWithPEM(t *testing.T) {
|
|||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
require.Equal(t, http.StatusOK, res.Code, "Http Status should be OK")
|
assert.Equal(t, http.StatusOK, res.Code, "Http Status should be OK")
|
||||||
require.Equal(t, "bar", res.Body.String(), "Should be the expected body")
|
assert.Equal(t, "bar", res.Body.String(), "Should be the expected body")
|
||||||
|
|
||||||
if test.expectedHeader != "" {
|
if test.expectedHeader != "" {
|
||||||
require.Equal(t, getCleanCertContents(test.certContents), req.Header.Get(xForwardedTLSClientCert), "The request header should contain the cleaned certificate")
|
expected := getCleanCertContents(test.certContents)
|
||||||
|
assert.Equal(t, expected, req.Header.Get(xForwardedTLSClientCert), "The request header should contain the cleaned certificate")
|
||||||
} else {
|
} else {
|
||||||
require.Empty(t, req.Header.Get(xForwardedTLSClientCert))
|
assert.Empty(t, req.Header.Get(xForwardedTLSClientCert))
|
||||||
}
|
}
|
||||||
require.Empty(t, res.Header().Get(xForwardedTLSClientCert), "The response header should be always empty")
|
|
||||||
|
assert.Empty(t, res.Header().Get(xForwardedTLSClientCert), "The response header should be always empty")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetSans(t *testing.T) {
|
func TestPassTLSClientCert_certInfo(t *testing.T) {
|
||||||
urlFoo, err := url.Parse("my.foo.com")
|
minimalCheeseCertAllInfo := strings.Join([]string{
|
||||||
require.NoError(t, err)
|
`Subject="C=FR,ST=Some-State,O=Cheese"`,
|
||||||
urlBar, err := url.Parse("my.bar.com")
|
`Issuer="DC=org,DC=cheese,C=FR,C=US,ST=Signing State,ST=Signing State 2,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=Simple Signing CA 2"`,
|
||||||
require.NoError(t, err)
|
`NB="1544094636"`,
|
||||||
|
`NA="1632568236"`,
|
||||||
|
}, fieldSeparator)
|
||||||
|
|
||||||
testCases := []struct {
|
completeCertAllInfo := strings.Join([]string{
|
||||||
desc string
|
`Subject="DC=org,DC=cheese,C=FR,C=US,ST=Cheese org state,ST=Cheese com state,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=*.cheese.com"`,
|
||||||
cert *x509.Certificate // set the request TLS attribute if defined
|
`Issuer="DC=org,DC=cheese,C=FR,C=US,ST=Signing State,ST=Signing State 2,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=Simple Signing CA 2"`,
|
||||||
expected []string
|
`NB="1544094616"`,
|
||||||
}{
|
`NA="1607166616"`,
|
||||||
{
|
`SAN="*.cheese.org,*.cheese.net,*.cheese.com,test@cheese.org,test@cheese.net,10.0.1.0,10.0.1.2"`,
|
||||||
desc: "With nil",
|
}, fieldSeparator)
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "Certificate without Sans",
|
|
||||||
cert: &x509.Certificate{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "Certificate with all Sans",
|
|
||||||
cert: &x509.Certificate{
|
|
||||||
DNSNames: []string{"foo", "bar"},
|
|
||||||
EmailAddresses: []string{"test@test.com", "test2@test.com"},
|
|
||||||
IPAddresses: []net.IP{net.IPv4(10, 0, 0, 1), net.IPv4(10, 0, 0, 2)},
|
|
||||||
URIs: []*url.URL{urlFoo, urlBar},
|
|
||||||
},
|
|
||||||
expected: []string{"foo", "bar", "test@test.com", "test2@test.com", "10.0.0.1", "10.0.0.2", urlFoo.String(), urlBar.String()},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range testCases {
|
|
||||||
sans := getSANs(test.cert)
|
|
||||||
|
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
if len(test.expected) > 0 {
|
|
||||||
for i, expected := range test.expected {
|
|
||||||
require.Equal(t, expected, sans[i])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
require.Empty(t, sans)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTLSClientHeadersWithCertInfo(t *testing.T) {
|
|
||||||
minimalCheeseCertAllInfo := `Subject="C=FR,ST=Some-State,O=Cheese",Issuer="DC=org,DC=cheese,C=FR,C=US,ST=Signing State,ST=Signing State 2,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=Simple Signing CA 2",NB=1544094636,NA=1632568236,SAN=`
|
|
||||||
completeCertAllInfo := `Subject="DC=org,DC=cheese,C=FR,C=US,ST=Cheese org state,ST=Cheese com state,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=*.cheese.com",Issuer="DC=org,DC=cheese,C=FR,C=US,ST=Signing State,ST=Signing State 2,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=Simple Signing CA 2",NB=1544094616,NA=1607166616,SAN=*.cheese.org,*.cheese.net,*.cheese.com,test@cheese.org,test@cheese.net,10.0.1.0,10.0.1.2`
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
@@ -547,7 +422,7 @@ func TestTLSClientHeadersWithCertInfo(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedHeader: url.QueryEscape(minimalCheeseCertAllInfo),
|
expectedHeader: minimalCheeseCertAllInfo,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "TLS with simple certificate, with some info",
|
desc: "TLS with simple certificate, with some info",
|
||||||
@@ -564,7 +439,7 @@ func TestTLSClientHeadersWithCertInfo(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedHeader: url.QueryEscape(`Subject="O=Cheese",Issuer="C=FR,C=US",NA=1632568236,SAN=`),
|
expectedHeader: `Subject="O=Cheese";Issuer="C=FR,C=US";NA="1632568236"`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "TLS with complete certificate, with all info",
|
desc: "TLS with complete certificate, with all info",
|
||||||
@@ -594,7 +469,7 @@ func TestTLSClientHeadersWithCertInfo(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedHeader: url.QueryEscape(completeCertAllInfo),
|
expectedHeader: completeCertAllInfo,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "TLS with 2 certificates, with all info",
|
desc: "TLS with 2 certificates, with all info",
|
||||||
@@ -624,7 +499,7 @@ func TestTLSClientHeadersWithCertInfo(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedHeader: url.QueryEscape(strings.Join([]string{minimalCheeseCertAllInfo, completeCertAllInfo}, ";")),
|
expectedHeader: strings.Join([]string{minimalCheeseCertAllInfo, completeCertAllInfo}, certSeparator),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,15 +520,157 @@ func TestTLSClientHeadersWithCertInfo(t *testing.T) {
|
|||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
require.Equal(t, http.StatusOK, res.Code, "Http Status should be OK")
|
assert.Equal(t, http.StatusOK, res.Code, "Http Status should be OK")
|
||||||
require.Equal(t, "bar", res.Body.String(), "Should be the expected body")
|
assert.Equal(t, "bar", res.Body.String(), "Should be the expected body")
|
||||||
|
|
||||||
if test.expectedHeader != "" {
|
if test.expectedHeader != "" {
|
||||||
require.Equal(t, test.expectedHeader, req.Header.Get(xForwardedTLSClientCertInfo), "The request header should contain the cleaned certificate")
|
unescape, err := url.QueryUnescape(req.Header.Get(xForwardedTLSClientCertInfo))
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, test.expectedHeader, unescape, "The request header should contain the cleaned certificate")
|
||||||
} else {
|
} else {
|
||||||
require.Empty(t, req.Header.Get(xForwardedTLSClientCertInfo))
|
assert.Empty(t, req.Header.Get(xForwardedTLSClientCertInfo))
|
||||||
}
|
}
|
||||||
require.Empty(t, res.Header().Get(xForwardedTLSClientCertInfo), "The response header should be always empty")
|
|
||||||
|
assert.Empty(t, res.Header().Get(xForwardedTLSClientCertInfo), "The response header should be always empty")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_sanitize(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
toSanitize []byte
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Empty",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "With a minimal cert",
|
||||||
|
toSanitize: []byte(minimalCheeseCrt),
|
||||||
|
expected: `MIIEQDCCAygCFFRY0OBk/L5Se0IZRj3CMljawL2UMA0GCSqGSIb3DQEBCwUAMIIB
|
||||||
|
hDETMBEGCgmSJomT8ixkARkWA29yZzEWMBQGCgmSJomT8ixkARkWBmNoZWVzZTEP
|
||||||
|
MA0GA1UECgwGQ2hlZXNlMREwDwYDVQQKDAhDaGVlc2UgMjEfMB0GA1UECwwWU2lt
|
||||||
|
cGxlIFNpZ25pbmcgU2VjdGlvbjEhMB8GA1UECwwYU2ltcGxlIFNpZ25pbmcgU2Vj
|
||||||
|
dGlvbiAyMRowGAYDVQQDDBFTaW1wbGUgU2lnbmluZyBDQTEcMBoGA1UEAwwTU2lt
|
||||||
|
cGxlIFNpZ25pbmcgQ0EgMjELMAkGA1UEBhMCRlIxCzAJBgNVBAYTAlVTMREwDwYD
|
||||||
|
VQQHDAhUT1VMT1VTRTENMAsGA1UEBwwETFlPTjEWMBQGA1UECAwNU2lnbmluZyBT
|
||||||
|
dGF0ZTEYMBYGA1UECAwPU2lnbmluZyBTdGF0ZSAyMSEwHwYJKoZIhvcNAQkBFhJz
|
||||||
|
aW1wbGVAc2lnbmluZy5jb20xIjAgBgkqhkiG9w0BCQEWE3NpbXBsZTJAc2lnbmlu
|
||||||
|
Zy5jb20wHhcNMTgxMjA2MTExMDM2WhcNMjEwOTI1MTExMDM2WjAzMQswCQYDVQQG
|
||||||
|
EwJGUjETMBEGA1UECAwKU29tZS1TdGF0ZTEPMA0GA1UECgwGQ2hlZXNlMIIBIjAN
|
||||||
|
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAskX/bUtwFo1gF2BTPNaNcTUMaRFu
|
||||||
|
FMZozK8IgLjccZ4kZ0R9oFO6Yp8Zl/IvPaf7tE26PI7XP7eHriUdhnQzX7iioDd0
|
||||||
|
RZa68waIhAGc+xPzRFrP3b3yj3S2a9Rve3c0K+SCV+EtKAwsxMqQDhoo9PcBfo5B
|
||||||
|
RHfht07uD5MncUcGirwN+/pxHV5xzAGPcc7On0/5L7bq/G+63nhu78zw9XyuLaHC
|
||||||
|
PM5VbOUvpyIESJHbMMzTdFGL8ob9VKO+Kr1kVGdEA9i8FLGl3xz/GBKuW/JD0xyW
|
||||||
|
DrU29mri5vYWHmkuv7ZWHGXnpXjTtPHwveE9/0/ArnmpMyR9JtqFr1oEvQIDAQAB
|
||||||
|
MA0GCSqGSIb3DQEBCwUAA4IBAQBHta+NWXI08UHeOkGzOTGRiWXsOH2dqdX6gTe9
|
||||||
|
xF1AIjyoQ0gvpoGVvlnChSzmlUj+vnx/nOYGIt1poE3hZA3ZHZD/awsvGyp3GwWD
|
||||||
|
IfXrEViSCIyF+8tNNKYyUcEO3xdAsAUGgfUwwF/mZ6MBV5+A/ZEEILlTq8zFt9dV
|
||||||
|
vdKzIt7fZYxYBBHFSarl1x8pDgWXlf3hAufevGJXip9xGYmznF0T5cq1RbWJ4be3
|
||||||
|
/9K7yuWhuBYC3sbTbCneHBa91M82za+PIISc1ygCYtWSBoZKSAqLk0rkZpHaekDP
|
||||||
|
WqeUSNGYV//RunTeuRDAf5OxehERb1srzBXhRZ3cZdzXbgR/`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
content := sanitize(test.toSanitize)
|
||||||
|
|
||||||
|
expected := url.QueryEscape(strings.Replace(test.expected, "\n", "", -1))
|
||||||
|
assert.Equal(t, expected, content, "The sanitized certificates should be equal")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_getSANs(t *testing.T) {
|
||||||
|
urlFoo := testhelpers.MustParseURL("my.foo.com")
|
||||||
|
urlBar := testhelpers.MustParseURL("my.bar.com")
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
cert *x509.Certificate // set the request TLS attribute if defined
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "With nil",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Certificate without Sans",
|
||||||
|
cert: &x509.Certificate{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Certificate with all Sans",
|
||||||
|
cert: &x509.Certificate{
|
||||||
|
DNSNames: []string{"foo", "bar"},
|
||||||
|
EmailAddresses: []string{"test@test.com", "test2@test.com"},
|
||||||
|
IPAddresses: []net.IP{net.IPv4(10, 0, 0, 1), net.IPv4(10, 0, 0, 2)},
|
||||||
|
URIs: []*url.URL{urlFoo, urlBar},
|
||||||
|
},
|
||||||
|
expected: []string{"foo", "bar", "test@test.com", "test2@test.com", "10.0.0.1", "10.0.0.2", urlFoo.String(), urlBar.String()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
sans := getSANs(test.cert)
|
||||||
|
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if len(test.expected) > 0 {
|
||||||
|
for i, expected := range test.expected {
|
||||||
|
assert.Equal(t, expected, sans[i])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert.Empty(t, sans)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCleanCertContents(certContents []string) string {
|
||||||
|
exp := regexp.MustCompile("-----BEGIN CERTIFICATE-----(?s)(.*)")
|
||||||
|
|
||||||
|
var cleanedCertContent []string
|
||||||
|
for _, certContent := range certContents {
|
||||||
|
cert := sanitize([]byte(exp.FindString(certContent)))
|
||||||
|
cleanedCertContent = append(cleanedCertContent, cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(cleanedCertContent, certSeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildTLSWith(certContents []string) *tls.ConnectionState {
|
||||||
|
var peerCertificates []*x509.Certificate
|
||||||
|
|
||||||
|
for _, certContent := range certContents {
|
||||||
|
peerCertificates = append(peerCertificates, getCertificate(certContent))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tls.ConnectionState{PeerCertificates: peerCertificates}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCertificate(certContent string) *x509.Certificate {
|
||||||
|
roots := x509.NewCertPool()
|
||||||
|
ok := roots.AppendCertsFromPEM([]byte(signingCA))
|
||||||
|
if !ok {
|
||||||
|
panic("failed to parse root certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
block, _ := pem.Decode([]byte(certContent))
|
||||||
|
if block == nil {
|
||||||
|
panic("failed to parse certificate PEM")
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to parse certificate: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return cert
|
||||||
|
}
|
||||||
|
@@ -5,7 +5,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||||
@@ -35,8 +34,7 @@ type rateLimiter struct {
|
|||||||
sourceMatcher utils.SourceExtractor
|
sourceMatcher utils.SourceExtractor
|
||||||
next http.Handler
|
next http.Handler
|
||||||
|
|
||||||
bucketsMu sync.Mutex
|
buckets *ttlmap.TtlMap // actual buckets, keyed by source.
|
||||||
buckets *ttlmap.TtlMap // actual buckets, keyed by source.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a rate limiter middleware.
|
// New returns a rate limiter middleware.
|
||||||
@@ -57,7 +55,7 @@ func New(ctx context.Context, next http.Handler, config dynamic.RateLimit, name
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
buckets, err := ttlmap.NewMap(maxSources)
|
buckets, err := ttlmap.NewConcurrent(maxSources)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -104,9 +102,6 @@ func (rl *rateLimiter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
logger.Infof("ignoring token bucket amount > 1: %d", amount)
|
logger.Infof("ignoring token bucket amount > 1: %d", amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
rl.bucketsMu.Lock()
|
|
||||||
defer rl.bucketsMu.Unlock()
|
|
||||||
|
|
||||||
var bucket *rate.Limiter
|
var bucket *rate.Limiter
|
||||||
if rlSource, exists := rl.buckets.Get(source); exists {
|
if rlSource, exists := rl.buckets.Get(source); exists {
|
||||||
bucket = rlSource.(*rate.Limiter)
|
bucket = rlSource.(*rate.Limiter)
|
||||||
|
@@ -132,19 +132,13 @@ func rawURL(req *http.Request) string {
|
|||||||
uri = match[4]
|
uri = match[4]
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.TLS != nil || isXForwardedHTTPS(req) {
|
if req.TLS != nil {
|
||||||
scheme = "https"
|
scheme = "https"
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.Join([]string{scheme, "://", host, port, uri}, "")
|
return strings.Join([]string{scheme, "://", host, port, uri}, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func isXForwardedHTTPS(request *http.Request) bool {
|
|
||||||
xForwardedProto := request.Header.Get("X-Forwarded-Proto")
|
|
||||||
|
|
||||||
return len(xForwardedProto) > 0 && xForwardedProto == "https"
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyString(in string, out io.Writer, req *http.Request) error {
|
func applyString(in string, out io.Writer, req *http.Request) error {
|
||||||
t, err := template.New("t").Parse(in)
|
t, err := template.New("t").Parse(in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -19,6 +19,7 @@ func TestRedirectRegexHandler(t *testing.T) {
|
|||||||
config dynamic.RedirectRegex
|
config dynamic.RedirectRegex
|
||||||
method string
|
method string
|
||||||
url string
|
url string
|
||||||
|
headers map[string]string
|
||||||
secured bool
|
secured bool
|
||||||
expectedURL string
|
expectedURL string
|
||||||
expectedStatus int
|
expectedStatus int
|
||||||
@@ -104,6 +105,19 @@ func TestRedirectRegexHandler(t *testing.T) {
|
|||||||
expectedURL: "https://foo:443",
|
expectedURL: "https://foo:443",
|
||||||
expectedStatus: http.StatusFound,
|
expectedStatus: http.StatusFound,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "HTTP to HTTPS, with X-Forwarded-Proto",
|
||||||
|
config: dynamic.RedirectRegex{
|
||||||
|
Regex: `http://foo:80`,
|
||||||
|
Replacement: "https://foo:443",
|
||||||
|
},
|
||||||
|
url: "http://foo:80",
|
||||||
|
headers: map[string]string{
|
||||||
|
"X-Forwarded-Proto": "https",
|
||||||
|
},
|
||||||
|
expectedURL: "https://foo:443",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "HTTPS to HTTP",
|
desc: "HTTPS to HTTP",
|
||||||
config: dynamic.RedirectRegex{
|
config: dynamic.RedirectRegex{
|
||||||
@@ -171,12 +185,18 @@ func TestRedirectRegexHandler(t *testing.T) {
|
|||||||
if test.method != "" {
|
if test.method != "" {
|
||||||
method = test.method
|
method = test.method
|
||||||
}
|
}
|
||||||
r := testhelpers.MustNewRequest(method, test.url, nil)
|
|
||||||
|
req := testhelpers.MustNewRequest(method, test.url, nil)
|
||||||
if test.secured {
|
if test.secured {
|
||||||
r.TLS = &tls.ConnectionState{}
|
req.TLS = &tls.ConnectionState{}
|
||||||
}
|
}
|
||||||
r.Header.Set("X-Foo", "bar")
|
|
||||||
handler.ServeHTTP(recorder, r)
|
for k, v := range test.headers {
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("X-Foo", "bar")
|
||||||
|
handler.ServeHTTP(recorder, req)
|
||||||
|
|
||||||
assert.Equal(t, test.expectedStatus, recorder.Code)
|
assert.Equal(t, test.expectedStatus, recorder.Code)
|
||||||
switch test.expectedStatus {
|
switch test.expectedStatus {
|
||||||
|
@@ -19,6 +19,7 @@ func TestRedirectSchemeHandler(t *testing.T) {
|
|||||||
config dynamic.RedirectScheme
|
config dynamic.RedirectScheme
|
||||||
method string
|
method string
|
||||||
url string
|
url string
|
||||||
|
headers map[string]string
|
||||||
secured bool
|
secured bool
|
||||||
expectedURL string
|
expectedURL string
|
||||||
expectedStatus int
|
expectedStatus int
|
||||||
@@ -39,6 +40,18 @@ func TestRedirectSchemeHandler(t *testing.T) {
|
|||||||
expectedURL: "https://foo",
|
expectedURL: "https://foo",
|
||||||
expectedStatus: http.StatusFound,
|
expectedStatus: http.StatusFound,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "HTTP to HTTPS, with X-Forwarded-Proto",
|
||||||
|
config: dynamic.RedirectScheme{
|
||||||
|
Scheme: "https",
|
||||||
|
},
|
||||||
|
url: "http://foo",
|
||||||
|
headers: map[string]string{
|
||||||
|
"X-Forwarded-Proto": "https",
|
||||||
|
},
|
||||||
|
expectedURL: "https://foo",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "HTTP with port to HTTPS without port",
|
desc: "HTTP with port to HTTPS without port",
|
||||||
config: dynamic.RedirectScheme{
|
config: dynamic.RedirectScheme{
|
||||||
@@ -197,13 +210,17 @@ func TestRedirectSchemeHandler(t *testing.T) {
|
|||||||
if test.method != "" {
|
if test.method != "" {
|
||||||
method = test.method
|
method = test.method
|
||||||
}
|
}
|
||||||
r := httptest.NewRequest(method, test.url, nil)
|
req := httptest.NewRequest(method, test.url, nil)
|
||||||
|
|
||||||
|
for k, v := range test.headers {
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
if test.secured {
|
if test.secured {
|
||||||
r.TLS = &tls.ConnectionState{}
|
req.TLS = &tls.ConnectionState{}
|
||||||
}
|
}
|
||||||
r.Header.Set("X-Foo", "bar")
|
req.Header.Set("X-Foo", "bar")
|
||||||
handler.ServeHTTP(recorder, r)
|
handler.ServeHTTP(recorder, req)
|
||||||
|
|
||||||
assert.Equal(t, test.expectedStatus, recorder.Code)
|
assert.Equal(t, test.expectedStatus, recorder.Code)
|
||||||
|
|
||||||
@@ -223,9 +240,9 @@ func TestRedirectSchemeHandler(t *testing.T) {
|
|||||||
|
|
||||||
if re.Match([]byte(test.url)) {
|
if re.Match([]byte(test.url)) {
|
||||||
match := re.FindStringSubmatch(test.url)
|
match := re.FindStringSubmatch(test.url)
|
||||||
r.RequestURI = match[4]
|
req.RequestURI = match[4]
|
||||||
|
|
||||||
handler.ServeHTTP(recorder, r)
|
handler.ServeHTTP(recorder, req)
|
||||||
|
|
||||||
assert.Equal(t, test.expectedStatus, recorder.Code)
|
assert.Equal(t, test.expectedStatus, recorder.Code)
|
||||||
if test.expectedStatus == http.StatusMovedPermanently ||
|
if test.expectedStatus == http.StatusMovedPermanently ||
|
||||||
|
@@ -20,18 +20,20 @@ const (
|
|||||||
|
|
||||||
// stripPrefix is a middleware used to strip prefix from an URL request.
|
// stripPrefix is a middleware used to strip prefix from an URL request.
|
||||||
type stripPrefix struct {
|
type stripPrefix struct {
|
||||||
next http.Handler
|
next http.Handler
|
||||||
prefixes []string
|
prefixes []string
|
||||||
name string
|
forceSlash bool // TODO Must be removed (breaking), the default behavior must be forceSlash=false
|
||||||
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new strip prefix middleware.
|
// New creates a new strip prefix middleware.
|
||||||
func New(ctx context.Context, next http.Handler, config dynamic.StripPrefix, name string) (http.Handler, error) {
|
func New(ctx context.Context, next http.Handler, config dynamic.StripPrefix, name string) (http.Handler, error) {
|
||||||
log.FromContext(middlewares.GetLoggerCtx(ctx, name, typeName)).Debug("Creating middleware")
|
log.FromContext(middlewares.GetLoggerCtx(ctx, name, typeName)).Debug("Creating middleware")
|
||||||
return &stripPrefix{
|
return &stripPrefix{
|
||||||
prefixes: config.Prefixes,
|
prefixes: config.Prefixes,
|
||||||
next: next,
|
forceSlash: config.ForceSlash,
|
||||||
name: name,
|
next: next,
|
||||||
|
name: name,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,9 +44,9 @@ func (s *stripPrefix) GetTracingInformation() (string, ext.SpanKindEnum) {
|
|||||||
func (s *stripPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (s *stripPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
for _, prefix := range s.prefixes {
|
for _, prefix := range s.prefixes {
|
||||||
if strings.HasPrefix(req.URL.Path, prefix) {
|
if strings.HasPrefix(req.URL.Path, prefix) {
|
||||||
req.URL.Path = getPrefixStripped(req.URL.Path, prefix)
|
req.URL.Path = s.getPrefixStripped(req.URL.Path, prefix)
|
||||||
if req.URL.RawPath != "" {
|
if req.URL.RawPath != "" {
|
||||||
req.URL.RawPath = getPrefixStripped(req.URL.RawPath, prefix)
|
req.URL.RawPath = s.getPrefixStripped(req.URL.RawPath, prefix)
|
||||||
}
|
}
|
||||||
s.serveRequest(rw, req, strings.TrimSpace(prefix))
|
s.serveRequest(rw, req, strings.TrimSpace(prefix))
|
||||||
return
|
return
|
||||||
@@ -59,10 +61,25 @@ func (s *stripPrefix) serveRequest(rw http.ResponseWriter, req *http.Request, pr
|
|||||||
s.next.ServeHTTP(rw, req)
|
s.next.ServeHTTP(rw, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPrefixStripped(s, prefix string) string {
|
func (s *stripPrefix) getPrefixStripped(urlPath, prefix string) string {
|
||||||
return ensureLeadingSlash(strings.TrimPrefix(s, prefix))
|
if s.forceSlash {
|
||||||
|
// Only for compatibility reason with the previous behavior,
|
||||||
|
// but the previous behavior is wrong.
|
||||||
|
// This needs to be removed in the next breaking version.
|
||||||
|
return "/" + strings.TrimPrefix(strings.TrimPrefix(urlPath, prefix), "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
return ensureLeadingSlash(strings.TrimPrefix(urlPath, prefix))
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureLeadingSlash(str string) string {
|
func ensureLeadingSlash(str string) string {
|
||||||
return "/" + strings.TrimPrefix(str, "/")
|
if str == "" {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
if str[0] == '/' {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
return "/" + str
|
||||||
}
|
}
|
||||||
|
@@ -31,6 +31,17 @@ func TestStripPrefix(t *testing.T) {
|
|||||||
expectedStatusCode: http.StatusOK,
|
expectedStatusCode: http.StatusOK,
|
||||||
expectedPath: "/noprefixes",
|
expectedPath: "/noprefixes",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "wildcard (.*) requests (ForceSlash)",
|
||||||
|
config: dynamic.StripPrefix{
|
||||||
|
Prefixes: []string{"/"},
|
||||||
|
ForceSlash: true,
|
||||||
|
},
|
||||||
|
path: "/",
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedPath: "/",
|
||||||
|
expectedHeader: "/",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "wildcard (.*) requests",
|
desc: "wildcard (.*) requests",
|
||||||
config: dynamic.StripPrefix{
|
config: dynamic.StripPrefix{
|
||||||
@@ -38,9 +49,20 @@ func TestStripPrefix(t *testing.T) {
|
|||||||
},
|
},
|
||||||
path: "/",
|
path: "/",
|
||||||
expectedStatusCode: http.StatusOK,
|
expectedStatusCode: http.StatusOK,
|
||||||
expectedPath: "/",
|
expectedPath: "",
|
||||||
expectedHeader: "/",
|
expectedHeader: "/",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "prefix and path matching (ForceSlash)",
|
||||||
|
config: dynamic.StripPrefix{
|
||||||
|
Prefixes: []string{"/stat"},
|
||||||
|
ForceSlash: true,
|
||||||
|
},
|
||||||
|
path: "/stat",
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedPath: "/",
|
||||||
|
expectedHeader: "/stat",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "prefix and path matching",
|
desc: "prefix and path matching",
|
||||||
config: dynamic.StripPrefix{
|
config: dynamic.StripPrefix{
|
||||||
@@ -48,9 +70,20 @@ func TestStripPrefix(t *testing.T) {
|
|||||||
},
|
},
|
||||||
path: "/stat",
|
path: "/stat",
|
||||||
expectedStatusCode: http.StatusOK,
|
expectedStatusCode: http.StatusOK,
|
||||||
expectedPath: "/",
|
expectedPath: "",
|
||||||
expectedHeader: "/stat",
|
expectedHeader: "/stat",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "path prefix on exactly matching path (ForceSlash)",
|
||||||
|
config: dynamic.StripPrefix{
|
||||||
|
Prefixes: []string{"/stat/"},
|
||||||
|
ForceSlash: true,
|
||||||
|
},
|
||||||
|
path: "/stat/",
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedPath: "/",
|
||||||
|
expectedHeader: "/stat/",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "path prefix on exactly matching path",
|
desc: "path prefix on exactly matching path",
|
||||||
config: dynamic.StripPrefix{
|
config: dynamic.StripPrefix{
|
||||||
@@ -58,7 +91,7 @@ func TestStripPrefix(t *testing.T) {
|
|||||||
},
|
},
|
||||||
path: "/stat/",
|
path: "/stat/",
|
||||||
expectedStatusCode: http.StatusOK,
|
expectedStatusCode: http.StatusOK,
|
||||||
expectedPath: "/",
|
expectedPath: "",
|
||||||
expectedHeader: "/stat/",
|
expectedHeader: "/stat/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -101,6 +134,17 @@ func TestStripPrefix(t *testing.T) {
|
|||||||
expectedPath: "/us",
|
expectedPath: "/us",
|
||||||
expectedHeader: "/stat",
|
expectedHeader: "/stat",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "later prefix matching (ForceSlash)",
|
||||||
|
config: dynamic.StripPrefix{
|
||||||
|
Prefixes: []string{"/mismatch", "/stat"},
|
||||||
|
ForceSlash: true,
|
||||||
|
},
|
||||||
|
path: "/stat",
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedPath: "/",
|
||||||
|
expectedHeader: "/stat",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "later prefix matching",
|
desc: "later prefix matching",
|
||||||
config: dynamic.StripPrefix{
|
config: dynamic.StripPrefix{
|
||||||
@@ -108,7 +152,7 @@ func TestStripPrefix(t *testing.T) {
|
|||||||
},
|
},
|
||||||
path: "/stat",
|
path: "/stat",
|
||||||
expectedStatusCode: http.StatusOK,
|
expectedStatusCode: http.StatusOK,
|
||||||
expectedPath: "/",
|
expectedPath: "",
|
||||||
expectedHeader: "/stat",
|
expectedHeader: "/stat",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -162,12 +206,15 @@ func TestStripPrefix(t *testing.T) {
|
|||||||
assert.Equal(t, test.expectedRawPath, actualRawPath, "Unexpected raw path.")
|
assert.Equal(t, test.expectedRawPath, actualRawPath, "Unexpected raw path.")
|
||||||
assert.Equal(t, test.expectedHeader, actualHeader, "Unexpected '%s' header.", ForwardedPrefixHeader)
|
assert.Equal(t, test.expectedHeader, actualHeader, "Unexpected '%s' header.", ForwardedPrefixHeader)
|
||||||
|
|
||||||
expectedURI := test.expectedPath
|
expectedRequestURI := test.expectedPath
|
||||||
if test.expectedRawPath != "" {
|
if test.expectedRawPath != "" {
|
||||||
// go HTTP uses the raw path when existent in the RequestURI
|
// go HTTP uses the raw path when existent in the RequestURI
|
||||||
expectedURI = test.expectedRawPath
|
expectedRequestURI = test.expectedRawPath
|
||||||
}
|
}
|
||||||
assert.Equal(t, expectedURI, requestURI, "Unexpected request URI.")
|
if test.expectedPath == "" {
|
||||||
|
expectedRequestURI = "/"
|
||||||
|
}
|
||||||
|
assert.Equal(t, expectedRequestURI, requestURI, "Unexpected request URI.")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -60,12 +60,12 @@ func (s *stripPrefixRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request)
|
|||||||
|
|
||||||
req.Header.Add(stripprefix.ForwardedPrefixHeader, prefix)
|
req.Header.Add(stripprefix.ForwardedPrefixHeader, prefix)
|
||||||
|
|
||||||
req.URL.Path = strings.Replace(req.URL.Path, prefix, "", 1)
|
req.URL.Path = ensureLeadingSlash(strings.Replace(req.URL.Path, prefix, "", 1))
|
||||||
if req.URL.RawPath != "" {
|
if req.URL.RawPath != "" {
|
||||||
req.URL.RawPath = req.URL.RawPath[len(prefix):]
|
req.URL.RawPath = ensureLeadingSlash(req.URL.RawPath[len(prefix):])
|
||||||
}
|
}
|
||||||
|
|
||||||
req.RequestURI = ensureLeadingSlash(req.URL.RequestURI())
|
req.RequestURI = req.URL.RequestURI()
|
||||||
s.next.ServeHTTP(rw, req)
|
s.next.ServeHTTP(rw, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -75,5 +75,13 @@ func (s *stripPrefixRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ensureLeadingSlash(str string) string {
|
func ensureLeadingSlash(str string) string {
|
||||||
return "/" + strings.TrimPrefix(str, "/")
|
if str == "" {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
if str[0] == '/' {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
return "/" + str
|
||||||
}
|
}
|
||||||
|
@@ -31,42 +31,66 @@ func TestStripPrefixRegex(t *testing.T) {
|
|||||||
expectedPath: "/a/test",
|
expectedPath: "/a/test",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/a/test",
|
path: "/a/test/",
|
||||||
expectedStatusCode: http.StatusOK,
|
expectedStatusCode: http.StatusOK,
|
||||||
expectedPath: "/a/test",
|
expectedPath: "/a/test/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/a/api/",
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedPath: "",
|
||||||
|
expectedHeader: "/a/api/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/a/api/test",
|
path: "/a/api/test",
|
||||||
expectedStatusCode: http.StatusOK,
|
expectedStatusCode: http.StatusOK,
|
||||||
expectedPath: "test",
|
expectedPath: "/test",
|
||||||
|
expectedHeader: "/a/api/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/a/api/test/",
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedPath: "/test/",
|
||||||
expectedHeader: "/a/api/",
|
expectedHeader: "/a/api/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/b/api/",
|
path: "/b/api/",
|
||||||
expectedStatusCode: http.StatusOK,
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedPath: "",
|
||||||
expectedHeader: "/b/api/",
|
expectedHeader: "/b/api/",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/b/api",
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedPath: "/b/api",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/b/api/test1",
|
path: "/b/api/test1",
|
||||||
expectedStatusCode: http.StatusOK,
|
expectedStatusCode: http.StatusOK,
|
||||||
expectedPath: "test1",
|
expectedPath: "/test1",
|
||||||
expectedHeader: "/b/api/",
|
expectedHeader: "/b/api/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/b/api2/test2",
|
path: "/b/api2/test2",
|
||||||
expectedStatusCode: http.StatusOK,
|
expectedStatusCode: http.StatusOK,
|
||||||
expectedPath: "test2",
|
expectedPath: "/test2",
|
||||||
expectedHeader: "/b/api2/",
|
expectedHeader: "/b/api2/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/c/api/123/",
|
path: "/c/api/123/",
|
||||||
expectedStatusCode: http.StatusOK,
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedPath: "",
|
||||||
expectedHeader: "/c/api/123/",
|
expectedHeader: "/c/api/123/",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/c/api/123",
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedPath: "/c/api/123",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/c/api/123/test3",
|
path: "/c/api/123/test3",
|
||||||
expectedStatusCode: http.StatusOK,
|
expectedStatusCode: http.StatusOK,
|
||||||
expectedPath: "test3",
|
expectedPath: "/test3",
|
||||||
expectedHeader: "/c/api/123/",
|
expectedHeader: "/c/api/123/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -77,8 +101,8 @@ func TestStripPrefixRegex(t *testing.T) {
|
|||||||
{
|
{
|
||||||
path: "/a/api/a%2Fb",
|
path: "/a/api/a%2Fb",
|
||||||
expectedStatusCode: http.StatusOK,
|
expectedStatusCode: http.StatusOK,
|
||||||
expectedPath: "a/b",
|
expectedPath: "/a/b",
|
||||||
expectedRawPath: "a%2Fb",
|
expectedRawPath: "/a%2Fb",
|
||||||
expectedHeader: "/a/api/",
|
expectedHeader: "/a/api/",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -88,11 +112,12 @@ func TestStripPrefixRegex(t *testing.T) {
|
|||||||
t.Run(test.path, func(t *testing.T) {
|
t.Run(test.path, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
var actualPath, actualRawPath, actualHeader string
|
var actualPath, actualRawPath, actualHeader, requestURI string
|
||||||
handlerPath := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handlerPath := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
actualPath = r.URL.Path
|
actualPath = r.URL.Path
|
||||||
actualRawPath = r.URL.RawPath
|
actualRawPath = r.URL.RawPath
|
||||||
actualHeader = r.Header.Get(stripprefix.ForwardedPrefixHeader)
|
actualHeader = r.Header.Get(stripprefix.ForwardedPrefixHeader)
|
||||||
|
requestURI = r.RequestURI
|
||||||
})
|
})
|
||||||
handler, err := New(context.Background(), handlerPath, testPrefixRegex, "foo-strip-prefix-regex")
|
handler, err := New(context.Background(), handlerPath, testPrefixRegex, "foo-strip-prefix-regex")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -106,6 +131,18 @@ func TestStripPrefixRegex(t *testing.T) {
|
|||||||
assert.Equal(t, test.expectedPath, actualPath, "Unexpected path.")
|
assert.Equal(t, test.expectedPath, actualPath, "Unexpected path.")
|
||||||
assert.Equal(t, test.expectedRawPath, actualRawPath, "Unexpected raw path.")
|
assert.Equal(t, test.expectedRawPath, actualRawPath, "Unexpected raw path.")
|
||||||
assert.Equal(t, test.expectedHeader, actualHeader, "Unexpected '%s' header.", stripprefix.ForwardedPrefixHeader)
|
assert.Equal(t, test.expectedHeader, actualHeader, "Unexpected '%s' header.", stripprefix.ForwardedPrefixHeader)
|
||||||
|
|
||||||
|
if test.expectedPath != test.path {
|
||||||
|
expectedRequestURI := test.expectedPath
|
||||||
|
if test.expectedRawPath != "" {
|
||||||
|
// go HTTP uses the raw path when existent in the RequestURI
|
||||||
|
expectedRequestURI = test.expectedRawPath
|
||||||
|
}
|
||||||
|
if test.expectedPath == "" {
|
||||||
|
expectedRequestURI = "/"
|
||||||
|
}
|
||||||
|
assert.Equal(t, expectedRequestURI, requestURI, "Unexpected request URI.")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -34,7 +34,11 @@ type entryPointMiddleware struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *entryPointMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (e *entryPointMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
spanCtx, _ := e.Extract(opentracing.HTTPHeaders, tracing.HTTPHeadersCarrier(req.Header))
|
spanCtx, err := e.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(middlewares.GetLoggerCtx(req.Context(), "tracing", entryPointTypeName)).
|
||||||
|
Debugf("Failed to extract the context: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
span, req, finish := e.StartSpanf(req, ext.SpanKindRPCServerEnum, "EntryPoint", []string{e.entryPoint, req.Host}, " ", ext.RPCServerOption(spanCtx))
|
span, req, finish := e.StartSpanf(req, ext.SpanKindRPCServerEnum, "EntryPoint", []string{e.entryPoint, req.Host}, " ", ext.RPCServerOption(spanCtx))
|
||||||
defer finish()
|
defer finish()
|
||||||
|
@@ -193,7 +193,7 @@ func (p *Provider) parseMetadataSourcedRancherData(ctx context.Context, stacks [
|
|||||||
}
|
}
|
||||||
|
|
||||||
service := rancherData{
|
service := rancherData{
|
||||||
Name: service.Name + "/" + stack.Name,
|
Name: service.Name + "_" + stack.Name,
|
||||||
State: service.State,
|
State: service.State,
|
||||||
Labels: service.Labels,
|
Labels: service.Labels,
|
||||||
Port: servicePort,
|
Port: servicePort,
|
||||||
|
@@ -135,6 +135,10 @@ func (e *TCPEntryPoint) startTCP(ctx context.Context) {
|
|||||||
conn, err := e.listener.Accept()
|
conn, err := e.listener.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
|
if netErr, ok := err.(net.Error); ok && netErr.Temporary() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,6 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/v2/pkg/middlewares/accesslog"
|
||||||
"github.com/containous/traefik/v2/pkg/safe"
|
"github.com/containous/traefik/v2/pkg/safe"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -63,11 +64,21 @@ func (m *Mirroring) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
if handler.count*100 < total*uint64(handler.percent) {
|
if handler.count*100 < total*uint64(handler.percent) {
|
||||||
handler.count++
|
handler.count++
|
||||||
handler.lock.Unlock()
|
handler.lock.Unlock()
|
||||||
|
|
||||||
|
// In ServeHTTP, we rely on the presence of the accesslog datatable found in the
|
||||||
|
// request's context to know whether we should mutate said datatable (and
|
||||||
|
// contribute some fields to the log). In this instance, we do not want the mirrors
|
||||||
|
// mutating (i.e. changing the service name in) the logs related to the mirrored
|
||||||
|
// server. Especially since it would result in unguarded concurrent reads/writes on
|
||||||
|
// the datatable. Therefore, we reset any potential datatable key in the new
|
||||||
|
// context that we pass around.
|
||||||
|
ctx := context.WithValue(req.Context(), accesslog.DataTableKey, nil)
|
||||||
|
|
||||||
// When a request served by m.handler is successful, req.Context will be canceled,
|
// When a request served by m.handler is successful, req.Context will be canceled,
|
||||||
// which would trigger a cancellation of the ongoing mirrored requests.
|
// which would trigger a cancellation of the ongoing mirrored requests.
|
||||||
// Therefore, we give a new, non-cancellable context to each of the mirrored calls,
|
// Therefore, we give a new, non-cancellable context to each of the mirrored calls,
|
||||||
// so they can terminate by themselves.
|
// so they can terminate by themselves.
|
||||||
handler.ServeHTTP(m.rw, req.WithContext(contextStopPropagation{req.Context()}))
|
handler.ServeHTTP(m.rw, req.WithContext(contextStopPropagation{ctx}))
|
||||||
} else {
|
} else {
|
||||||
handler.lock.Unlock()
|
handler.lock.Unlock()
|
||||||
}
|
}
|
||||||
|
@@ -40,7 +40,7 @@ func NewManager(configs map[string]*runtime.ServiceInfo, defaultRoundTripper htt
|
|||||||
metricsRegistry: metricsRegistry,
|
metricsRegistry: metricsRegistry,
|
||||||
bufferPool: newBufferPool(),
|
bufferPool: newBufferPool(),
|
||||||
defaultRoundTripper: defaultRoundTripper,
|
defaultRoundTripper: defaultRoundTripper,
|
||||||
balancers: make(map[string][]healthcheck.BalancerHandler),
|
balancers: make(map[string]healthcheck.Balancers),
|
||||||
configs: configs,
|
configs: configs,
|
||||||
api: api,
|
api: api,
|
||||||
rest: rest,
|
rest: rest,
|
||||||
@@ -53,10 +53,14 @@ type Manager struct {
|
|||||||
metricsRegistry metrics.Registry
|
metricsRegistry metrics.Registry
|
||||||
bufferPool httputil.BufferPool
|
bufferPool httputil.BufferPool
|
||||||
defaultRoundTripper http.RoundTripper
|
defaultRoundTripper http.RoundTripper
|
||||||
balancers map[string][]healthcheck.BalancerHandler
|
// balancers is the map of all Balancers, keyed by service name.
|
||||||
configs map[string]*runtime.ServiceInfo
|
// There is one Balancer per service handler, and there is one service handler per reference to a service
|
||||||
api http.Handler
|
// (e.g. if 2 routers refer to the same service name, 2 service handlers are created),
|
||||||
rest http.Handler
|
// which is why there is not just one Balancer per service name.
|
||||||
|
balancers map[string]healthcheck.Balancers
|
||||||
|
configs map[string]*runtime.ServiceInfo
|
||||||
|
api http.Handler
|
||||||
|
rest http.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildHTTP Creates a http.Handler for a service configuration.
|
// BuildHTTP Creates a http.Handler for a service configuration.
|
||||||
@@ -110,14 +114,14 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string, respons
|
|||||||
}
|
}
|
||||||
case conf.Weighted != nil:
|
case conf.Weighted != nil:
|
||||||
var err error
|
var err error
|
||||||
lb, err = m.getLoadBalancerWRRServiceHandler(ctx, serviceName, conf.Weighted, responseModifier)
|
lb, err = m.getWRRServiceHandler(ctx, serviceName, conf.Weighted, responseModifier)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conf.AddError(err, true)
|
conf.AddError(err, true)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case conf.Mirroring != nil:
|
case conf.Mirroring != nil:
|
||||||
var err error
|
var err error
|
||||||
lb, err = m.getLoadBalancerMirrorServiceHandler(ctx, serviceName, conf.Mirroring, responseModifier)
|
lb, err = m.getMirrorServiceHandler(ctx, serviceName, conf.Mirroring, responseModifier)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conf.AddError(err, true)
|
conf.AddError(err, true)
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -131,7 +135,7 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string, respons
|
|||||||
return lb, nil
|
return lb, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) getLoadBalancerMirrorServiceHandler(ctx context.Context, serviceName string, config *dynamic.Mirroring, responseModifier func(*http.Response) error) (http.Handler, error) {
|
func (m *Manager) getMirrorServiceHandler(ctx context.Context, serviceName string, config *dynamic.Mirroring, responseModifier func(*http.Response) error) (http.Handler, error) {
|
||||||
serviceHandler, err := m.BuildHTTP(ctx, config.Service, responseModifier)
|
serviceHandler, err := m.BuildHTTP(ctx, config.Service, responseModifier)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -152,7 +156,7 @@ func (m *Manager) getLoadBalancerMirrorServiceHandler(ctx context.Context, servi
|
|||||||
return handler, nil
|
return handler, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) getLoadBalancerWRRServiceHandler(ctx context.Context, serviceName string, config *dynamic.WeightedRoundRobin, responseModifier func(*http.Response) error) (http.Handler, error) {
|
func (m *Manager) getWRRServiceHandler(ctx context.Context, serviceName string, config *dynamic.WeightedRoundRobin, responseModifier func(*http.Response) error) (http.Handler, error) {
|
||||||
// TODO Handle accesslog and metrics with multiple service name
|
// TODO Handle accesslog and metrics with multiple service name
|
||||||
if config.Sticky != nil && config.Sticky.Cookie != nil {
|
if config.Sticky != nil && config.Sticky.Cookie != nil {
|
||||||
config.Sticky.Cookie.Name = cookie.GetName(config.Sticky.Cookie.Name, serviceName)
|
config.Sticky.Cookie.Name = cookie.GetName(config.Sticky.Cookie.Name, serviceName)
|
||||||
@@ -218,15 +222,12 @@ func (m *Manager) LaunchHealthCheck() {
|
|||||||
for serviceName, balancers := range m.balancers {
|
for serviceName, balancers := range m.balancers {
|
||||||
ctx := log.With(context.Background(), log.Str(log.ServiceName, serviceName))
|
ctx := log.With(context.Background(), log.Str(log.ServiceName, serviceName))
|
||||||
|
|
||||||
// TODO aggregate
|
|
||||||
balancer := balancers[0]
|
|
||||||
|
|
||||||
// TODO Should all the services handle healthcheck? Handle different types
|
// TODO Should all the services handle healthcheck? Handle different types
|
||||||
service := m.configs[serviceName].LoadBalancer
|
service := m.configs[serviceName].LoadBalancer
|
||||||
|
|
||||||
// Health Check
|
// Health Check
|
||||||
var backendHealthCheck *healthcheck.BackendConfig
|
var backendHealthCheck *healthcheck.BackendConfig
|
||||||
if hcOpts := buildHealthCheckOptions(ctx, balancer, serviceName, service.HealthCheck); hcOpts != nil {
|
if hcOpts := buildHealthCheckOptions(ctx, balancers, serviceName, service.HealthCheck); hcOpts != nil {
|
||||||
log.FromContext(ctx).Debugf("Setting up healthcheck for service %s with %s", serviceName, *hcOpts)
|
log.FromContext(ctx).Debugf("Setting up healthcheck for service %s with %s", serviceName, *hcOpts)
|
||||||
|
|
||||||
hcOpts.Transport = m.defaultRoundTripper
|
hcOpts.Transport = m.defaultRoundTripper
|
||||||
@@ -242,7 +243,7 @@ func (m *Manager) LaunchHealthCheck() {
|
|||||||
healthcheck.GetHealthCheck().SetBackendsConfiguration(context.Background(), backendConfigs)
|
healthcheck.GetHealthCheck().SetBackendsConfiguration(context.Background(), backendConfigs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildHealthCheckOptions(ctx context.Context, lb healthcheck.BalancerHandler, backend string, hc *dynamic.HealthCheck) *healthcheck.Options {
|
func buildHealthCheckOptions(ctx context.Context, lb healthcheck.Balancer, backend string, hc *dynamic.HealthCheck) *healthcheck.Options {
|
||||||
if hc == nil || hc.Path == "" {
|
if hc == nil || hc.Path == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -80,7 +80,8 @@ func (f FileOrContent) IsPath() bool {
|
|||||||
|
|
||||||
func (f FileOrContent) Read() ([]byte, error) {
|
func (f FileOrContent) Read() ([]byte, error) {
|
||||||
var content []byte
|
var content []byte
|
||||||
if _, err := os.Stat(f.String()); err == nil {
|
if f.IsPath() {
|
||||||
|
var err error
|
||||||
content, err = ioutil.ReadFile(f.String())
|
content, err = ioutil.ReadFile(f.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@@ -1,25 +0,0 @@
|
|||||||
package tracing
|
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
// HTTPHeadersCarrier custom implementation to fix duplicated headers
|
|
||||||
// It has been fixed in https://github.com/opentracing/opentracing-go/pull/191
|
|
||||||
type HTTPHeadersCarrier http.Header
|
|
||||||
|
|
||||||
// Set conforms to the TextMapWriter interface.
|
|
||||||
func (c HTTPHeadersCarrier) Set(key, val string) {
|
|
||||||
h := http.Header(c)
|
|
||||||
h.Set(key, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ForeachKey conforms to the TextMapReader interface.
|
|
||||||
func (c HTTPHeadersCarrier) ForeachKey(handler func(key, val string) error) error {
|
|
||||||
for k, vals := range c {
|
|
||||||
for _, v := range vals {
|
|
||||||
if err := handler(k, v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@@ -134,7 +134,7 @@ func InjectRequestHeaders(r *http.Request) {
|
|||||||
err := opentracing.GlobalTracer().Inject(
|
err := opentracing.GlobalTracer().Inject(
|
||||||
span.Context(),
|
span.Context(),
|
||||||
opentracing.HTTPHeaders,
|
opentracing.HTTPHeaders,
|
||||||
HTTPHeadersCarrier(r.Header))
|
opentracing.HTTPHeadersCarrier(r.Header))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FromContext(r.Context()).Error(err)
|
log.FromContext(r.Context()).Error(err)
|
||||||
}
|
}
|
||||||
|
@@ -95,12 +95,12 @@
|
|||||||
# Enable API and dashboard
|
# Enable API and dashboard
|
||||||
[api]
|
[api]
|
||||||
|
|
||||||
# Name of the related entry point
|
# Enable the API in insecure mode
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
# Default: "traefik"
|
# Default: true
|
||||||
#
|
#
|
||||||
# entryPoint = "traefik"
|
# insecure = false
|
||||||
|
|
||||||
# Enabled Dashboard
|
# Enabled Dashboard
|
||||||
#
|
#
|
||||||
|
@@ -3,15 +3,12 @@ import { APP } from '../_helpers/APP'
|
|||||||
const apiBase = '/http'
|
const apiBase = '/http'
|
||||||
|
|
||||||
function getAllRouters (params) {
|
function getAllRouters (params) {
|
||||||
return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}`)
|
return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`)
|
||||||
.then(body => {
|
.then(body => {
|
||||||
const total = body.data ? body.data.length : 0
|
const total = body.data ? body.data.length : 0
|
||||||
return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`)
|
console.log('Success -> HttpService -> getAllRouters', body.data)
|
||||||
.then(body => {
|
// TODO - suggestion: add the total-pages in api response to optimize the query
|
||||||
console.log('Success -> HttpService -> getAllRouters', body.data)
|
return { data: body.data || [], total }
|
||||||
// TODO - suggestion: add the total-pages in api response to optimize the query
|
|
||||||
return { data: body.data || [], total }
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,15 +21,12 @@ function getRouterByName (name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getAllServices (params) {
|
function getAllServices (params) {
|
||||||
return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}`)
|
return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`)
|
||||||
.then(body => {
|
.then(body => {
|
||||||
const total = body.data ? body.data.length : 0
|
const total = body.data ? body.data.length : 0
|
||||||
return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`)
|
console.log('Success -> HttpService -> getAllServices', body.data)
|
||||||
.then(body => {
|
// TODO - suggestion: add the total-pages in api response to optimize the query
|
||||||
console.log('Success -> HttpService -> getAllServices', body.data)
|
return { data: body.data || [], total }
|
||||||
// TODO - suggestion: add the total-pages in api response to optimize the query
|
|
||||||
return { data: body.data || [], total }
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,15 +39,12 @@ function getServiceByName (name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getAllMiddlewares (params) {
|
function getAllMiddlewares (params) {
|
||||||
return APP.api.get(`${apiBase}/middlewares?search=${params.query}&status=${params.status}`)
|
return APP.api.get(`${apiBase}/middlewares?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`)
|
||||||
.then(body => {
|
.then(body => {
|
||||||
const total = body.data ? body.data.length : 0
|
const total = body.data ? body.data.length : 0
|
||||||
return APP.api.get(`${apiBase}/middlewares?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`)
|
console.log('Success -> HttpService -> getAllMiddlewares', body.data)
|
||||||
.then(body => {
|
// TODO - suggestion: add the total-pages in api response to optimize the query
|
||||||
console.log('Success -> HttpService -> getAllMiddlewares', body.data)
|
return { data: body.data || [], total }
|
||||||
// TODO - suggestion: add the total-pages in api response to optimize the query
|
|
||||||
return { data: body.data || [], total }
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user