Compare commits
109 Commits
v2.0.0-alp
...
v1.7.11
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a1c3510ea | ||
|
|
787b0a3ac7 | ||
|
|
a6d86cdddb | ||
|
|
9559a56011 | ||
|
|
07e8042192 | ||
|
|
edd8e36dcc | ||
|
|
e291dcb8d1 | ||
|
|
de6e365b74 | ||
|
|
98a5e08553 | ||
|
|
ba44619828 | ||
|
|
86d700c845 | ||
|
|
b2f11afcc2 | ||
|
|
cfb356c68e | ||
|
|
266f5d18a8 | ||
|
|
305af43fb9 | ||
|
|
30545808d9 | ||
|
|
358f125a58 | ||
|
|
57ae9a80d5 | ||
|
|
e32c021f16 | ||
|
|
0db2a9aadd | ||
|
|
4f4dab3ca5 | ||
|
|
eaee39e534 | ||
|
|
d85eb0495c | ||
|
|
8aa618775d | ||
|
|
f6b7e333be | ||
|
|
108d9dbb3f | ||
|
|
2a1fa32950 | ||
|
|
ee7aa77833 | ||
|
|
fcc4cab614 | ||
|
|
1206cd52fc | ||
|
|
5cdba752a4 | ||
|
|
b48ea1e173 | ||
|
|
373040f552 | ||
|
|
443902a0f0 | ||
|
|
9eb02d9b03 | ||
|
|
2eb651645d | ||
|
|
630571fdc8 | ||
|
|
00fc43ebce | ||
|
|
6d906fa4c8 | ||
|
|
6a4c7796e3 | ||
|
|
67704e333d | ||
|
|
76c9cea856 | ||
|
|
0366fb9bc2 | ||
|
|
5fed947eaa | ||
|
|
c289279d24 | ||
|
|
418dca1113 | ||
|
|
ed293e3058 | ||
|
|
40bb0cd879 | ||
|
|
32f5e0df8f | ||
|
|
40b8a93930 | ||
|
|
b52b0f58b9 | ||
|
|
0016db0856 | ||
|
|
5aeca4507e | ||
|
|
11bfb49e65 | ||
|
|
d0cdb5608f | ||
|
|
8bb8ad5e02 | ||
|
|
b488d8365c | ||
|
|
e2bd7b45d1 | ||
|
|
c8ea2ce703 | ||
|
|
7d1edcd735 | ||
|
|
9660c31d3d | ||
|
|
37ac19583a | ||
|
|
ca60c52199 | ||
|
|
9cddf5616a | ||
|
|
f8ba843ad2 | ||
|
|
e598d6bcca | ||
|
|
52ec0e8d34 | ||
|
|
13c32dee11 | ||
|
|
6db120fd8e | ||
|
|
b6b3a9ed7f | ||
|
|
b5a4d0797a | ||
|
|
9ec2887494 | ||
|
|
94de6eb7f2 | ||
|
|
0e643f06a3 | ||
|
|
4db0dd9a7f | ||
|
|
ee19e66aba | ||
|
|
16ed13ba6f | ||
|
|
8f5e972843 | ||
|
|
4b70ff82b9 | ||
|
|
1468e1f697 | ||
|
|
7a963e75fd | ||
|
|
9203ba5f95 | ||
|
|
302849282c | ||
|
|
2a227fa4c7 | ||
|
|
31b80683c8 | ||
|
|
6aa040ad9f | ||
|
|
617de78d14 | ||
|
|
44605731dc | ||
|
|
9d26839e54 | ||
|
|
7a93bcc869 | ||
|
|
0840fd0f96 | ||
|
|
4a5f5440d7 | ||
|
|
479ee9af49 | ||
|
|
ef6c1fac4b | ||
|
|
53201b65b0 | ||
|
|
9cb4d56c03 | ||
|
|
f25ebc7e3d | ||
|
|
4c25cda55f | ||
|
|
18962b61d6 | ||
|
|
d0aa993661 | ||
|
|
df688dba0e | ||
|
|
69585c58cd | ||
|
|
b889b0191c | ||
|
|
9b0586cba7 | ||
|
|
a20f5bda0b | ||
|
|
28d5731b87 | ||
|
|
66486eacdc | ||
|
|
2b82ed054c | ||
|
|
cd6f0c0ded |
1
.github/ISSUE_TEMPLATE.md
vendored
@@ -23,6 +23,7 @@ If you intend to ask a support question: DO NOT FILE AN ISSUE.
|
||||
HOW TO WRITE A GOOD ISSUE?
|
||||
|
||||
- Respect the issue template as much as possible.
|
||||
- If possible, use the command `traefik bug`. See https://www.youtube.com/watch?v=Lyz62L8m93I.
|
||||
- The title should be short and descriptive.
|
||||
- Explain the conditions which led you to report this issue: the context.
|
||||
- The context should lead to something, an idea or a problem that you’re facing.
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@@ -27,6 +27,7 @@ Bug
|
||||
HOW TO WRITE A GOOD BUG REPORT?
|
||||
|
||||
- Respect the issue template as much as possible.
|
||||
- If possible, use the command `traefik bug`. See https://www.youtube.com/watch?v=Lyz62L8m93I.
|
||||
- The title should be short and descriptive.
|
||||
- Explain the conditions which led you to report this issue: the context.
|
||||
- The context should lead to something, an idea or a problem that you’re facing.
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
@@ -27,6 +27,7 @@ Feature
|
||||
HOW TO WRITE A GOOD ISSUE?
|
||||
|
||||
- Respect the issue template as much as possible.
|
||||
- If possible, use the command `traefik bug`. See https://www.youtube.com/watch?v=Lyz62L8m93I.
|
||||
- The title should be short and descriptive.
|
||||
- Explain the conditions which led you to report this issue: the context.
|
||||
- The context should lead to something, an idea or a problem that you’re facing.
|
||||
|
||||
9
.gitignore
vendored
@@ -3,14 +3,15 @@
|
||||
*.iml
|
||||
.vscode/
|
||||
.DS_Store
|
||||
/dist
|
||||
/static/
|
||||
/autogen/genstatic/gen.go
|
||||
/webui/.tmp/
|
||||
/examples/acme/acme.json
|
||||
/site/
|
||||
/docs/site/
|
||||
/static/
|
||||
/autogen/
|
||||
/traefik
|
||||
/traefik.toml
|
||||
/dist
|
||||
/traefik
|
||||
*.log
|
||||
*.exe
|
||||
cover.out
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
[run]
|
||||
deadline = "10m"
|
||||
skip-files = [
|
||||
"^old/.*",
|
||||
]
|
||||
|
||||
[linters-settings]
|
||||
|
||||
[linters-settings.govet]
|
||||
check-shadowing = false
|
||||
|
||||
[linters-settings.golint]
|
||||
min-confidence = 0.0
|
||||
|
||||
[linters-settings.gocyclo]
|
||||
min-complexity = 14.0
|
||||
|
||||
[linters-settings.maligned]
|
||||
suggest-new = true
|
||||
|
||||
[linters-settings.goconst]
|
||||
min-len = 3.0
|
||||
min-occurrences = 4.0
|
||||
|
||||
[linters-settings.misspell]
|
||||
locale = "US"
|
||||
|
||||
[linters]
|
||||
enable-all = true
|
||||
disable = [
|
||||
"gocyclo", # FIXME must be fixed
|
||||
"gosec",
|
||||
"dupl",
|
||||
"maligned",
|
||||
"lll",
|
||||
"unparam",
|
||||
"prealloc",
|
||||
"scopelint",
|
||||
"gochecknoinits",
|
||||
"gochecknoglobals",
|
||||
]
|
||||
|
||||
[issues]
|
||||
exclude-use-default = false
|
||||
max-per-linter = 0
|
||||
max-same-issues = 0
|
||||
exclude = [
|
||||
"SA1019: http.CloseNotifier is deprecated: the CloseNotifier interface predates Go's context package. New code should use Request.Context instead.", # FIXME must be fixed
|
||||
"Error return value of .((os\\.)?std(out|err)\\..*|.*Close|.*Flush|os\\.Remove(All)?|.*printf?|os\\.(Un)?Setenv). is not checked",
|
||||
"should have a package comment, unless it's in another file for this package",
|
||||
]
|
||||
[[issues.exclude-rules]]
|
||||
path = ".+_test.go"
|
||||
linters = ["goconst"]
|
||||
[[issues.exclude-rules]]
|
||||
path = "provider/label/internal/.+_test.go"
|
||||
text = "U1000: field `(foo|fuu)` is unused"
|
||||
[[issues.exclude-rules]]
|
||||
path = "middlewares/recovery/recovery.go"
|
||||
text = "`logger` can be `github.com/containous/traefik/vendor/github.com/stretchr/testify/assert.TestingT`"
|
||||
[[issues.exclude-rules]]
|
||||
path = "integration/.+_test.go"
|
||||
text = "Error return value of `cmd\\.Process\\.Kill` is not checked"
|
||||
[[issues.exclude-rules]]
|
||||
path = "integration/(consul_catalog_test|constraint_test).go"
|
||||
text = "Error return value of `(s.deregisterService|s.deregisterAgentService)` is not checked"
|
||||
[[issues.exclude-rules]]
|
||||
path = "integration/grpc_test.go"
|
||||
text = "Error return value of `closer` is not checked"
|
||||
[[issues.exclude-rules]]
|
||||
path = "provider/kubernetes/builder_(endpoint|service)_test.go"
|
||||
text = "(U1000: func )?`(.+)` is unused"
|
||||
[[issues.exclude-rules]]
|
||||
path = "provider/docker/builder_test.go"
|
||||
text = "(U1000: func )?`(.+)` is unused"
|
||||
[[issues.exclude-rules]]
|
||||
path = "cmd/configuration.go"
|
||||
text = "string `traefik` has (\\d) occurrences, make it a constant"
|
||||
[[issues.exclude-rules]]
|
||||
path = "h2c/h2c.go"
|
||||
text = "Error return value of `rw.Write` is not checked"
|
||||
[[issues.exclude-rules]]
|
||||
path = "server/service/bufferpool.go"
|
||||
text = "SA6002: argument should be pointer-like to avoid allocations"
|
||||
[[issues.exclude-rules]] # FIXME must be fixed
|
||||
path = "acme/.+.go"
|
||||
text = "(assignment copies lock value to domainsCerts|literal copies lock value from)"
|
||||
[[issues.exclude-rules]] # FIXME must be fixed
|
||||
path = "cmd/context.go"
|
||||
text = "S1000: should use a simple channel send/receive instead of `select` with a single case"
|
||||
42
.gometalinter.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"Vendor": true,
|
||||
"Sort": [
|
||||
"path",
|
||||
"line",
|
||||
"column",
|
||||
"severity",
|
||||
"linter"
|
||||
],
|
||||
"Test": true,
|
||||
"Cyclo": 15,
|
||||
"Enable": [
|
||||
"gotypex",
|
||||
"nakedret",
|
||||
"vet",
|
||||
"goimports",
|
||||
"golint",
|
||||
"ineffassign",
|
||||
"gotype",
|
||||
"misspell",
|
||||
"structcheck",
|
||||
"gosimple",
|
||||
"unconvert",
|
||||
"varcheck",
|
||||
"errcheck",
|
||||
"unused",
|
||||
"deadcode",
|
||||
"staticcheck"
|
||||
],
|
||||
"Disable": [
|
||||
"gas",
|
||||
"maligned",
|
||||
"interfacer",
|
||||
"goconst",
|
||||
"gocyclo",
|
||||
"vetshadow"
|
||||
],
|
||||
"Exclude": [
|
||||
"autogen/.*"
|
||||
],
|
||||
"Deadline": "5m"
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
project_name: traefik
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go generate
|
||||
|
||||
builds:
|
||||
- binary: traefik
|
||||
|
||||
main: ./cmd/traefik/traefik.go
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
ldflags:
|
||||
- -s -w -X github.com/containous/traefik/pkg/version.Version={{.Version}} -X github.com/containous/traefik/pkg/version.Codename={{.Env.CODENAME}} -X github.com/containous/traefik/pkg/version.BuildDate={{.Date}}
|
||||
|
||||
goos:
|
||||
- linux
|
||||
- darwin
|
||||
- windows
|
||||
- freebsd
|
||||
- openbsd
|
||||
goarch:
|
||||
- amd64
|
||||
- 386
|
||||
- arm
|
||||
- arm64
|
||||
- ppc64le
|
||||
goarm:
|
||||
- 7
|
||||
- 6
|
||||
- 5
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: 386
|
||||
- goos: openbsd
|
||||
goarch: arm
|
||||
- goos: freebsd
|
||||
goarch: arm
|
||||
|
||||
changelog:
|
||||
skip: true
|
||||
|
||||
archive:
|
||||
name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm
|
||||
}}v{{ .Arm }}{{ end }}'
|
||||
format: tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
files:
|
||||
- LICENSE.md
|
||||
- CHANGELOG.md
|
||||
|
||||
release:
|
||||
disable: true
|
||||
10
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
- repo: git://github.com/pre-commit/pre-commit-hooks
|
||||
sha: 44e1753f98b0da305332abe26856c3e621c5c439
|
||||
hooks:
|
||||
- id: detect-private-key
|
||||
- repo: git://github.com/containous/pre-commit-hooks
|
||||
sha: 35e641b5107671e94102b0ce909648559e568d61
|
||||
hooks:
|
||||
- id: goFmt
|
||||
- id: goLint
|
||||
- id: goErrcheck
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
curl -O https://dl.google.com/go/go1.12.linux-amd64.tar.gz
|
||||
|
||||
tar -xvf go1.12.linux-amd64.tar.gz
|
||||
rm -rf go1.12.linux-amd64.tar.gz
|
||||
|
||||
sudo mkdir -p /usr/local/golang/1.12/go
|
||||
sudo mv go /usr/local/golang/1.12/
|
||||
|
||||
sudo rm /usr/local/bin/go
|
||||
sudo chmod +x /usr/local/golang/1.12/go/bin/go
|
||||
sudo ln -s /usr/local/golang/1.12/go/bin/go /usr/local/bin/go
|
||||
|
||||
export GOROOT="/usr/local/golang/1.12/go"
|
||||
export GOTOOLDIR="/usr/local/golang/1.12/go/pkg/tool/linux_amd64"
|
||||
|
||||
go version
|
||||
@@ -10,7 +10,7 @@ else
|
||||
export VERSION=''
|
||||
fi
|
||||
|
||||
export CODENAME=faisselle
|
||||
export CODENAME=maroilles
|
||||
|
||||
export N_MAKE_JOBS=2
|
||||
|
||||
|
||||
18
.travis.yml
@@ -11,11 +11,12 @@ env:
|
||||
global:
|
||||
- REPO: $TRAVIS_REPO_SLUG
|
||||
- VERSION: $TRAVIS_TAG
|
||||
- CODENAME: faisselle
|
||||
- CODENAME: maroilles
|
||||
- N_MAKE_JOBS: 2
|
||||
|
||||
script:
|
||||
- echo "Skipping tests... (Tests are executed on SemaphoreCI)"
|
||||
- if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then make docs; fi
|
||||
- if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then make docs-verify; fi
|
||||
|
||||
before_deploy:
|
||||
- >
|
||||
@@ -26,12 +27,12 @@ before_deploy:
|
||||
docker version;
|
||||
make image;
|
||||
if [ "$TRAVIS_TAG" ]; then
|
||||
make release-packages;
|
||||
make -j${N_MAKE_JOBS} crossbinary-parallel;
|
||||
tar cfz dist/traefik-${VERSION}.src.tar.gz --exclude-vcs --exclude dist .;
|
||||
fi;
|
||||
curl -sfL https://raw.githubusercontent.com/containous/structor/master/godownloader.sh | bash -s -- -b "${GOPATH}/bin" v1.7.0
|
||||
structor -o containous -r traefik --dockerfile-url="https://raw.githubusercontent.com/containous/traefik/v1.7/docs.Dockerfile" --menu.js-url="https://raw.githubusercontent.com/containous/structor/master/traefik-menu.js.gotmpl" --rqts-url="https://raw.githubusercontent.com/containous/structor/master/requirements-override.txt" --force-edit-url --exp-branch=master --debug;
|
||||
curl -sfL https://raw.githubusercontent.com/containous/structor/master/godownloader.sh | bash -s -- -b "${GOPATH}/bin" ${STRUCTOR_VERSION}
|
||||
structor -o containous -r traefik --dockerfile-url="https://raw.githubusercontent.com/containous/traefik/v1.7/docs.Dockerfile" --menu.js-url="https://raw.githubusercontent.com/containous/structor/master/traefik-menu.js.gotmpl" --rqts-url="https://raw.githubusercontent.com/containous/structor/master/requirements-override.txt" --exp-branch=master --force-edit-url --debug;
|
||||
fi
|
||||
|
||||
deploy:
|
||||
- provider: releases
|
||||
api_key: ${GITHUB_TOKEN}
|
||||
@@ -47,11 +48,6 @@ deploy:
|
||||
on:
|
||||
repo: containous/traefik
|
||||
tags: true
|
||||
- provider: script
|
||||
script: sh script/deploy-docker.sh
|
||||
skip_cleanup: true
|
||||
on:
|
||||
repo: containous/traefik
|
||||
- provider: pages
|
||||
edge: false
|
||||
github_token: ${GITHUB_TOKEN}
|
||||
|
||||
118
CHANGELOG.md
@@ -1,98 +1,40 @@
|
||||
# Change Log
|
||||
|
||||
## [v2.0.0-alpha1](https://github.com/containous/traefik/tree/v2.0.0-alpha1) (2019-03-18)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.7.0-rc1...v2.0.0-alpha1)
|
||||
## [v1.7.11](https://github.com/containous/traefik/tree/v1.7.11) (2019-04-26)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.7.10...v1.7.11)
|
||||
|
||||
**Enhancements:**
|
||||
- **[acme,kv]** Remove Deprecated StorageFile ([#4252](https://github.com/containous/traefik/pull/4252) by [juliens](https://github.com/juliens))
|
||||
- **[acme]** Migrate to go-acme/lego. ([#4589](https://github.com/containous/traefik/pull/4589) by [ldez](https://github.com/ldez))
|
||||
- **[authentication,logs,etcd]** Remove deprecated elements ([#3715](https://github.com/containous/traefik/pull/3715) by [geraldcroes](https://github.com/geraldcroes))
|
||||
- **[authentication,middleware]** Basic Auth custom realm ([#3917](https://github.com/containous/traefik/pull/3917) by [tcoupin](https://github.com/tcoupin))
|
||||
- **[docker]** Adds default rule system on Docker provider. ([#4413](https://github.com/containous/traefik/pull/4413) by [ldez](https://github.com/ldez))
|
||||
- **[docker]** Adds Docker provider support ([#4399](https://github.com/containous/traefik/pull/4399) by [ldez](https://github.com/ldez))
|
||||
- **[docker]** Update to Go1.12. Support of TLS1.3 ([#4540](https://github.com/containous/traefik/pull/4540) by [ldez](https://github.com/ldez))
|
||||
- **[etcd]** Remove etcd v2 ([#3739](https://github.com/containous/traefik/pull/3739) by [geraldcroes](https://github.com/geraldcroes))
|
||||
- **[k8s/ingress]** Adds Kubernetes provider support ([#4476](https://github.com/containous/traefik/pull/4476) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- **[k8s/ingress]** Adds update ingress status ([#4603](https://github.com/containous/traefik/pull/4603) by [juliens](https://github.com/juliens))
|
||||
- **[k8s/ingress]** k8s integration tests ([#4569](https://github.com/containous/traefik/pull/4569) by [juliens](https://github.com/juliens))
|
||||
- **[k8s/ingress]** Custom resource definition ([#4591](https://github.com/containous/traefik/pull/4591) by [ldez](https://github.com/ldez))
|
||||
- **[marathon]** Adds Marathon support. ([#4415](https://github.com/containous/traefik/pull/4415) by [ldez](https://github.com/ldez))
|
||||
- **[metrics]** Add HTTP authentication to influxdb metric backend ([#3600](https://github.com/containous/traefik/pull/3600) by [halfa](https://github.com/halfa))
|
||||
- **[middleware,provider]** IPStrategy for selecting IP in whitelist ([#3778](https://github.com/containous/traefik/pull/3778) by [juliens](https://github.com/juliens))
|
||||
- **[middleware,provider]** Enables the use of elements declared in other providers ([#4372](https://github.com/containous/traefik/pull/4372) by [geraldcroes](https://github.com/geraldcroes))
|
||||
- **[middleware]** Migrates the pass client tls cert middleware ([#4373](https://github.com/containous/traefik/pull/4373) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- **[middleware]** Migrates Compress from bool to struct ([#3714](https://github.com/containous/traefik/pull/3714) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- **[middleware]** Updates for jaeger tracing client. ([#3688](https://github.com/containous/traefik/pull/3688) by [tcolgate](https://github.com/tcolgate))
|
||||
- **[middleware]** Add forwarded headers on entry point configuration ([#4364](https://github.com/containous/traefik/pull/4364) by [juliens](https://github.com/juliens))
|
||||
- **[middleware]** SchemeRedirect Middleware ([#4400](https://github.com/containous/traefik/pull/4400) by [geraldcroes](https://github.com/geraldcroes))
|
||||
- **[provider]** Add health check timeout parameter ([#3813](https://github.com/containous/traefik/pull/3813) by [jbiel](https://github.com/jbiel))
|
||||
- **[provider]** Removes deprecated templates ([#3649](https://github.com/containous/traefik/pull/3649) by [geraldcroes](https://github.com/geraldcroes))
|
||||
- **[provider]** Remove everything templates related ([#4595](https://github.com/containous/traefik/pull/4595) by [mpl](https://github.com/mpl))
|
||||
- **[provider]** Small code enhancements on providers ([#3707](https://github.com/containous/traefik/pull/3707) by [vdemeester](https://github.com/vdemeester))
|
||||
- **[provider]** Migrate rest provider ([#4253](https://github.com/containous/traefik/pull/4253) by [juliens](https://github.com/juliens))
|
||||
- **[provider]** Labels parser. ([#4236](https://github.com/containous/traefik/pull/4236) by [ldez](https://github.com/ldez))
|
||||
- **[rules]** New rule syntax ([#4437](https://github.com/containous/traefik/pull/4437) by [juliens](https://github.com/juliens))
|
||||
- **[server]** Dynamic Configuration Refactoring ([#4168](https://github.com/containous/traefik/pull/4168) by [ldez](https://github.com/ldez))
|
||||
- **[server]** Remove old global config and use new static config ([#4222](https://github.com/containous/traefik/pull/4222) by [juliens](https://github.com/juliens))
|
||||
- **[tcp]** Adds TCP support ([#4587](https://github.com/containous/traefik/pull/4587) by [juliens](https://github.com/juliens))
|
||||
- **[tracing]** Instana tracer implementation ([#4453](https://github.com/containous/traefik/pull/4453) by [notsureifkevin](https://github.com/notsureifkevin))
|
||||
- **[tracing]** Make Zipkin trace rate configurable ([#3968](https://github.com/containous/traefik/pull/3968) by [negz](https://github.com/negz))
|
||||
- **[webui]** Upgrade angular cli version ([#4450](https://github.com/containous/traefik/pull/4450) by [Slashgear](https://github.com/Slashgear))
|
||||
- **[webui]** Update docker node version ([#4448](https://github.com/containous/traefik/pull/4448) by [Slashgear](https://github.com/Slashgear))
|
||||
- **[webui]** Ignore target/dependencies in docker copy ([#4449](https://github.com/containous/traefik/pull/4449) by [Slashgear](https://github.com/Slashgear))
|
||||
- **[webui]** Format code with prettier ([#4463](https://github.com/containous/traefik/pull/4463) by [Slashgear](https://github.com/Slashgear))
|
||||
- **[webui]** No need for npm progress=false ([#3702](https://github.com/containous/traefik/pull/3702) by [vdemeester](https://github.com/vdemeester))
|
||||
- **[webui]** Migrate to a work in progress webui ([#4568](https://github.com/containous/traefik/pull/4568) by [Slashgear](https://github.com/Slashgear))
|
||||
- **[webui]** Include lint in build process ([#4462](https://github.com/containous/traefik/pull/4462) by [Slashgear](https://github.com/Slashgear))
|
||||
- **[webui]** Dropping rxjs-compat in favor of pipe ([#4520](https://github.com/containous/traefik/pull/4520) by [imcotton](https://github.com/imcotton))
|
||||
- New packaging system. ([#4593](https://github.com/containous/traefik/pull/4593) by [ldez](https://github.com/ldez))
|
||||
- Updates Backoff ([#4457](https://github.com/containous/traefik/pull/4457) by [ldez](https://github.com/ldez))
|
||||
- Remove the bug command ([#4556](https://github.com/containous/traefik/pull/4556) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- Small code enhancements ([#3712](https://github.com/containous/traefik/pull/3712) by [mmatur](https://github.com/mmatur))
|
||||
- Remove deprecated elements ([#3666](https://github.com/containous/traefik/pull/3666) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- Clean old ([#4612](https://github.com/containous/traefik/pull/4612) by [ldez](https://github.com/ldez))
|
||||
- Update anonymize/collect ([#4590](https://github.com/containous/traefik/pull/4590) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- **[k8s,k8s/ingress]** Enhance k8s tests maintainability ([#4696](https://github.com/containous/traefik/pull/4696) by [ldez](https://github.com/ldez))
|
||||
|
||||
**Bug fixes:**
|
||||
- **[k8s,k8s/crd]** Remove IngressEndpoint in CRD provider ([#4616](https://github.com/containous/traefik/pull/4616) by [juliens](https://github.com/juliens))
|
||||
- **[logs]** Allow user to configure traefik log ([#4604](https://github.com/containous/traefik/pull/4604) by [mmatur](https://github.com/mmatur))
|
||||
- **[server]** Fix lock problem in server ([#4600](https://github.com/containous/traefik/pull/4600) by [juliens](https://github.com/juliens))
|
||||
- Clean files during tests. ([#4607](https://github.com/containous/traefik/pull/4607) by [ldez](https://github.com/ldez))
|
||||
- **[acme]** fix: update lego. ([#4800](https://github.com/containous/traefik/pull/4800) by [ldez](https://github.com/ldez))
|
||||
- **[authentication,middleware]** Forward all header values from forward auth response ([#4515](https://github.com/containous/traefik/pull/4515) by [ctas582](https://github.com/ctas582))
|
||||
- **[cluster]** Remove usage of github.com/satori/go.uuid ([#4722](https://github.com/containous/traefik/pull/4722) by [aaslamin](https://github.com/aaslamin))
|
||||
- **[kv]** Enhance KV client error management ([#4819](https://github.com/containous/traefik/pull/4819) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- **[tls]** Improve log message about redundant TLS certificate ([#4765](https://github.com/containous/traefik/pull/4765) by [mpl](https://github.com/mpl))
|
||||
- **[tracing]** Update zipkin-go-opentracing. ([#4720](https://github.com/containous/traefik/pull/4720) by [ldez](https://github.com/ldez))
|
||||
|
||||
**Documentation:**
|
||||
- **[acme,docker]** Synchronize documentation ([#4571](https://github.com/containous/traefik/pull/4571) by [juliens](https://github.com/juliens))
|
||||
- **[acme]** Enhance acme page. ([#4611](https://github.com/containous/traefik/pull/4611) by [ldez](https://github.com/ldez))
|
||||
- **[acme]** Rename Docker_Acme.md to Readme.md ([#4025](https://github.com/containous/traefik/pull/4025) by [vineetvermait](https://github.com/vineetvermait))
|
||||
- **[acme]** fix: some DNS provider link. ([#3637](https://github.com/containous/traefik/pull/3637) by [ldez](https://github.com/ldez))
|
||||
- **[file]** Update the file provider documentation ([#4588](https://github.com/containous/traefik/pull/4588) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- **[tcp]** Use rule HostSNI in documentation ([#4592](https://github.com/containous/traefik/pull/4592) by [bbinet](https://github.com/bbinet))
|
||||
- Documentation Revamp ([#4475](https://github.com/containous/traefik/pull/4475) by [geraldcroes](https://github.com/geraldcroes))
|
||||
- Add Gerald, Jean-Baptiste and Damien to maintainers ([#3982](https://github.com/containous/traefik/pull/3982) by [emilevauge](https://github.com/emilevauge))
|
||||
- fix broken links in readme.md ([#3967](https://github.com/containous/traefik/pull/3967) by [AndrewSav](https://github.com/AndrewSav))
|
||||
- Add master overhaul notice ([#3961](https://github.com/containous/traefik/pull/3961) by [emilevauge](https://github.com/emilevauge))
|
||||
- Complete maintainers processes ([#3696](https://github.com/containous/traefik/pull/3696) by [mmatur](https://github.com/mmatur))
|
||||
- Complete maintainers processes ([#3681](https://github.com/containous/traefik/pull/3681) by [emilevauge](https://github.com/emilevauge))
|
||||
- Adds a maintainer's page into the documentation. ([#4614](https://github.com/containous/traefik/pull/4614) by [ldez](https://github.com/ldez))
|
||||
- **[acme]** Documentation Update: Hosting.de wildcard support tested ([#4747](https://github.com/containous/traefik/pull/4747) by [martinhoefling](https://github.com/martinhoefling))
|
||||
- **[acme]** Update Wildcard Domain documentation ([#4682](https://github.com/containous/traefik/pull/4682) by [DWSR](https://github.com/DWSR))
|
||||
- **[middleware]** Keep consistent order ([#4690](https://github.com/containous/traefik/pull/4690) by [maxifom](https://github.com/maxifom))
|
||||
|
||||
**Misc:**
|
||||
- Cherry pick v1.7 into master ([#4565](https://github.com/containous/traefik/pull/4565) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- Cherry pick v1.7 into master ([#4511](https://github.com/containous/traefik/pull/4511) by [ldez](https://github.com/ldez))
|
||||
- Cherry pick v1.7 into master ([#4492](https://github.com/containous/traefik/pull/4492) by [ldez](https://github.com/ldez))
|
||||
- Cherry pick v1.7 into master ([#4440](https://github.com/containous/traefik/pull/4440) by [ldez](https://github.com/ldez))
|
||||
- Cherry pick v1.7 into master ([#4365](https://github.com/containous/traefik/pull/4365) by [ldez](https://github.com/ldez))
|
||||
- Cherry pick v1.7 into master ([#4303](https://github.com/containous/traefik/pull/4303) by [ldez](https://github.com/ldez))
|
||||
- Cherry pick v1.7 into master ([#4271](https://github.com/containous/traefik/pull/4271) by [ldez](https://github.com/ldez))
|
||||
- Cherry pick v1.7 into master ([#4268](https://github.com/containous/traefik/pull/4268) by [ldez](https://github.com/ldez))
|
||||
- Cherry pick v1.7 into master ([#4229](https://github.com/containous/traefik/pull/4229) by [juliens](https://github.com/juliens))
|
||||
- Cherry pick v1.7 into master ([#4206](https://github.com/containous/traefik/pull/4206) by [ldez](https://github.com/ldez))
|
||||
- Merge v1.7.4 into master ([#4137](https://github.com/containous/traefik/pull/4137) by [ldez](https://github.com/ldez))
|
||||
- Merge v1.7.3 into master ([#4046](https://github.com/containous/traefik/pull/4046) by [ldez](https://github.com/ldez))
|
||||
- Merge current v1.7 into master ([#3992](https://github.com/containous/traefik/pull/3992) by [ldez](https://github.com/ldez))
|
||||
- Merge v1.7.2 into master ([#3983](https://github.com/containous/traefik/pull/3983) by [ldez](https://github.com/ldez))
|
||||
- Merge v1.7.0 into master ([#3925](https://github.com/containous/traefik/pull/3925) by [ldez](https://github.com/ldez))
|
||||
- Merge v1.7.0-rc5 into master ([#3903](https://github.com/containous/traefik/pull/3903) by [ldez](https://github.com/ldez))
|
||||
- Merge v1.7.0-rc4 into master ([#3867](https://github.com/containous/traefik/pull/3867) by [ldez](https://github.com/ldez))
|
||||
- Merge v1.7.0-rc2 into master ([#3634](https://github.com/containous/traefik/pull/3634) by [ldez](https://github.com/ldez))
|
||||
## [v1.7.10](https://github.com/containous/traefik/tree/v1.7.10) (2019-03-28)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.7.9...v1.7.10)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[acme]** fix: update lego. ([#4670](https://github.com/containous/traefik/pull/4670) by [ldez](https://github.com/ldez))
|
||||
- **[acme]** Migrate to go-acme/lego. ([#4577](https://github.com/containous/traefik/pull/4577) by [ldez](https://github.com/ldez))
|
||||
- **[authentication,middleware]** Reorder Auth and TLSClientHeaders middleware ([#4557](https://github.com/containous/traefik/pull/4557) by [tomberek](https://github.com/tomberek))
|
||||
- **[k8s/ingress]** Support external name service on global default backend ([#4564](https://github.com/containous/traefik/pull/4564) by [kippandrew](https://github.com/kippandrew))
|
||||
- **[k8s/ingress]** Loop through service ports for global backend ([#4486](https://github.com/containous/traefik/pull/4486) by [dtomcej](https://github.com/dtomcej))
|
||||
- **[k8s]** Add entrypoints prefix in kubernetes frontend/backend id ([#4679](https://github.com/containous/traefik/pull/4679) by [juliens](https://github.com/juliens))
|
||||
- **[websocket]** Exclude websocket connections from Average Response Time ([#4313](https://github.com/containous/traefik/pull/4313) by [siyu6974](https://github.com/siyu6974))
|
||||
- **[middleware]** Added support for configuring trace headers for DataDog tracing ([#4516](https://github.com/containous/traefik/pull/4516) by [aantono](https://github.com/aantono))
|
||||
|
||||
**Documentation:**
|
||||
- **[acme]** Add _FILE Environment Variable Documentation ([#4643](https://github.com/containous/traefik/pull/4643) by [dargmuesli](https://github.com/dargmuesli))
|
||||
- **[docker]** Add TraefikEE as security workaround ([#4606](https://github.com/containous/traefik/pull/4606) by [emilevauge](https://github.com/emilevauge))
|
||||
|
||||
## [v1.7.9](https://github.com/containous/traefik/tree/v1.7.9) (2019-02-11)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.7.8...v1.7.9)
|
||||
@@ -1218,7 +1160,7 @@
|
||||
- **[acme,tls]** Rename TLSConfigurations to TLS. ([#2744](https://github.com/containous/traefik/pull/2744) by [ldez](https://github.com/ldez))
|
||||
- **[acme,provider,docker,tls]** Make the TLS certificates management dynamic. ([#2233](https://github.com/containous/traefik/pull/2233) by [nmengin](https://github.com/nmengin))
|
||||
- **[acme]** Add Let's Encrypt HTTP Challenge ([#2701](https://github.com/containous/traefik/pull/2701) by [Juliens](https://github.com/Juliens))
|
||||
- **[acme]** Update github.com/xenolf/lego to 0.4.1 ([#2304](https://github.com/containous/traefik/pull/2304) by [oldmantaiter](https://github.com/oldmantaiter))
|
||||
- **[acme]** Update github.com/go-acme/lego to 0.4.1 ([#2304](https://github.com/containous/traefik/pull/2304) by [oldmantaiter](https://github.com/oldmantaiter))
|
||||
- **[api,healthcheck,metrics,provider,webui]** Split Web into API/Dashboard, ping, metric and Rest Provider ([#2335](https://github.com/containous/traefik/pull/2335) by [Juliens](https://github.com/Juliens))
|
||||
- **[authentication]** Pass through certain forward auth negative response headers ([#2127](https://github.com/containous/traefik/pull/2127) by [wheresmysocks](https://github.com/wheresmysocks))
|
||||
- **[cluster,consul,file]** Add file to storeconfig ([#2419](https://github.com/containous/traefik/pull/2419) by [emilevauge](https://github.com/emilevauge))
|
||||
@@ -1497,7 +1439,7 @@
|
||||
|
||||
**Enhancements:**
|
||||
- **[acme,provider,docker,tls]** Make the TLS certificates management dynamic. ([#2233](https://github.com/containous/traefik/pull/2233) by [nmengin](https://github.com/nmengin))
|
||||
- **[acme]** Update github.com/xenolf/lego to 0.4.1 ([#2304](https://github.com/containous/traefik/pull/2304) by [oldmantaiter](https://github.com/oldmantaiter))
|
||||
- **[acme]** Update github.com/go-acme/lego to 0.4.1 ([#2304](https://github.com/containous/traefik/pull/2304) by [oldmantaiter](https://github.com/oldmantaiter))
|
||||
- **[api,healthcheck,metrics,provider,webui]** Split Web into API/Dashboard, ping, metric and Rest Provider ([#2335](https://github.com/containous/traefik/pull/2335) by [Juliens](https://github.com/Juliens))
|
||||
- **[authentication]** Pass through certain forward auth negative response headers ([#2127](https://github.com/containous/traefik/pull/2127) by [wheresmysocks](https://github.com/wheresmysocks))
|
||||
- **[cluster,consul,file]** Add file to storeconfig ([#2419](https://github.com/containous/traefik/pull/2419) by [emilevauge](https://github.com/emilevauge))
|
||||
|
||||
@@ -2,11 +2,17 @@
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience,nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
@@ -16,36 +22,53 @@ Examples of behavior that contributes to creating a positive environment include
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
|
||||
Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
||||
Representation of a project may be further defined and clarified by project maintainers.
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@containo.us
|
||||
All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances.
|
||||
The project team is obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at contact@containo.us
|
||||
All complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
|
||||
296
CONTRIBUTING.md
@@ -1,3 +1,297 @@
|
||||
# Contributing
|
||||
|
||||
See https://docs.traefik.io.
|
||||
## Building
|
||||
|
||||
You need either [Docker](https://github.com/docker/docker) and `make` (Method 1), or `go` (Method 2) in order to build Traefik.
|
||||
For changes to its dependencies, the `dep` dependency management tool is required.
|
||||
|
||||
### Method 1: Using `Docker` and `Makefile`
|
||||
|
||||
You need to run the `binary` target. This will create binaries for Linux platform in the `dist` folder.
|
||||
|
||||
```bash
|
||||
$ make binary
|
||||
docker build -t "traefik-dev:no-more-godep-ever" -f build.Dockerfile .
|
||||
Sending build context to Docker daemon 295.3 MB
|
||||
Step 0 : FROM golang:1.11-alpine
|
||||
---> 8c6473912976
|
||||
Step 1 : RUN go get github.com/golang/dep/cmd/dep
|
||||
[...]
|
||||
docker run --rm -v "/var/run/docker.sock:/var/run/docker.sock" -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/user/go/src/github.com/containous/traefik/"dist":/go/src/github.com/containous/traefik/"dist"" "traefik-dev:no-more-godep-ever" ./script/make.sh generate binary
|
||||
---> Making bundle: generate (in .)
|
||||
removed 'gen.go'
|
||||
|
||||
---> Making bundle: binary (in .)
|
||||
|
||||
$ ls dist/
|
||||
traefik*
|
||||
```
|
||||
|
||||
### Method 2: Using `go`
|
||||
|
||||
##### Setting up your `go` environment
|
||||
|
||||
- You need `go` v1.9+
|
||||
- It is recommended you clone Traefik into a directory like `~/go/src/github.com/containous/traefik` (This is the official golang workspace hierarchy, and will allow dependencies to resolve properly)
|
||||
- Set your `GOPATH` and `PATH` variable to be set to `~/go` via:
|
||||
|
||||
```bash
|
||||
export GOPATH=~/go
|
||||
export PATH=$PATH:$GOPATH/bin
|
||||
```
|
||||
|
||||
> Note: You will want to add those 2 export lines to your `.bashrc` or `.bash_profile`
|
||||
|
||||
- Verify your environment is setup properly by running `$ go env`. Depending on your OS and environment you should see output similar to:
|
||||
|
||||
```bash
|
||||
GOARCH="amd64"
|
||||
GOBIN=""
|
||||
GOEXE=""
|
||||
GOHOSTARCH="amd64"
|
||||
GOHOSTOS="linux"
|
||||
GOOS="linux"
|
||||
GOPATH="/home/<yourusername>/go"
|
||||
GORACE=""
|
||||
## more go env's will be listed
|
||||
```
|
||||
|
||||
##### Build Traefik
|
||||
|
||||
Once your environment is set up and the Traefik repository cloned you can build Traefik. You need get `go-bindata` once to be able to use `go generate` command as part of the build. The steps to build are:
|
||||
|
||||
```bash
|
||||
cd ~/go/src/github.com/containous/traefik
|
||||
|
||||
# Get go-bindata. Please note, the ellipses are required
|
||||
go get github.com/containous/go-bindata/...
|
||||
|
||||
# Start build
|
||||
|
||||
# generate
|
||||
# (required to merge non-code components into the final binary, such as the web dashboard and provider's Go templates)
|
||||
go generate
|
||||
|
||||
# Standard go build
|
||||
go build ./cmd/traefik
|
||||
# run other commands like tests
|
||||
```
|
||||
|
||||
You will find the Traefik executable in the `~/go/src/github.com/containous/traefik` folder as `traefik`.
|
||||
|
||||
### Updating the templates
|
||||
|
||||
If you happen to update the provider templates (in `/templates`), you need to run `go generate` to update the `autogen` package.
|
||||
|
||||
### Setting up dependency management
|
||||
|
||||
[dep](https://github.com/golang/dep) is not required for building; however, it is necessary to modify dependencies (i.e., add, update, or remove third-party packages)
|
||||
|
||||
You need to use [dep](https://github.com/golang/dep) >= 0.5.0.
|
||||
|
||||
If you want to add a dependency, use `dep ensure -add` to have [dep](https://github.com/golang/dep) put it into the vendor folder and update the dep manifest/lock files (`Gopkg.toml` and `Gopkg.lock`, respectively).
|
||||
|
||||
A following `make dep-prune` run should be triggered to trim down the size of the vendor folder.
|
||||
The final result must be committed into VCS.
|
||||
|
||||
Here's a full example using dep to add a new dependency:
|
||||
|
||||
```bash
|
||||
# install the new main dependency github.com/foo/bar and minimize vendor size
|
||||
$ dep ensure -add github.com/foo/bar
|
||||
# generate (Only required to integrate other components such as web dashboard)
|
||||
$ go generate
|
||||
# Standard go build
|
||||
$ go build ./cmd/traefik
|
||||
# run other commands like tests
|
||||
```
|
||||
|
||||
### Tests
|
||||
|
||||
#### Method 1: `Docker` and `make`
|
||||
|
||||
You can run unit tests using the `test-unit` target and the
|
||||
integration test using the `test-integration` target.
|
||||
|
||||
```bash
|
||||
$ make test-unit
|
||||
docker build -t "traefik-dev:your-feature-branch" -f build.Dockerfile .
|
||||
# […]
|
||||
docker run --rm -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/user/go/src/github/containous/traefik/dist:/go/src/github.com/containous/traefik/dist" "traefik-dev:your-feature-branch" ./script/make.sh generate test-unit
|
||||
---> Making bundle: generate (in .)
|
||||
removed 'gen.go'
|
||||
|
||||
---> Making bundle: test-unit (in .)
|
||||
+ go test -cover -coverprofile=cover.out .
|
||||
ok github.com/containous/traefik 0.005s coverage: 4.1% of statements
|
||||
|
||||
Test success
|
||||
```
|
||||
|
||||
For development purposes, you can specify which tests to run by using:
|
||||
|
||||
```bash
|
||||
# Run every tests in the MyTest suite
|
||||
TESTFLAGS="-check.f MyTestSuite" make test-integration
|
||||
|
||||
# Run the test "MyTest" in the MyTest suite
|
||||
TESTFLAGS="-check.f MyTestSuite.MyTest" make test-integration
|
||||
|
||||
# Run every tests starting with "My", in the MyTest suite
|
||||
TESTFLAGS="-check.f MyTestSuite.My" make test-integration
|
||||
|
||||
# Run every tests ending with "Test", in the MyTest suite
|
||||
TESTFLAGS="-check.f MyTestSuite.*Test" make test-integration
|
||||
```
|
||||
|
||||
More: https://labix.org/gocheck
|
||||
|
||||
#### Method 2: `go`
|
||||
|
||||
Unit tests can be run from the cloned directory by `$ go test ./...` which should return `ok` similar to:
|
||||
|
||||
```
|
||||
ok _/home/user/go/src/github/containous/traefik 0.004s
|
||||
```
|
||||
|
||||
Integration tests must be run from the `integration/` directory and require the `-integration` switch to be passed like this: `$ cd integration && go test -integration ./...`.
|
||||
|
||||
## Documentation
|
||||
|
||||
The [documentation site](http://docs.traefik.io/) is built with [mkdocs](http://mkdocs.org/)
|
||||
|
||||
### Building Documentation
|
||||
|
||||
#### Method 1: `Docker` and `make`
|
||||
|
||||
You can build the documentation and serve it locally with livereloading, using the `docs` target:
|
||||
|
||||
```bash
|
||||
$ make docs
|
||||
docker build -t traefik-docs -f docs.Dockerfile .
|
||||
# […]
|
||||
docker run --rm -v /home/user/go/github/containous/traefik:/mkdocs -p 8000:8000 traefik-docs mkdocs serve
|
||||
# […]
|
||||
[I 170828 20:47:48 server:283] Serving on http://0.0.0.0:8000
|
||||
[I 170828 20:47:48 handlers:60] Start watching changes
|
||||
[I 170828 20:47:48 handlers:62] Start detecting changes
|
||||
```
|
||||
|
||||
And go to [http://127.0.0.1:8000](http://127.0.0.1:8000).
|
||||
|
||||
If you only want to build the documentation without serving it locally, you can use the following command:
|
||||
|
||||
```bash
|
||||
$ make docs-build
|
||||
...
|
||||
```
|
||||
|
||||
#### Method 2: `mkdocs`
|
||||
|
||||
First make sure you have python and pip installed
|
||||
|
||||
```bash
|
||||
$ python --version
|
||||
Python 2.7.2
|
||||
$ pip --version
|
||||
pip 1.5.2
|
||||
```
|
||||
|
||||
Then install mkdocs with pip
|
||||
|
||||
```bash
|
||||
pip install --user -r requirements.txt
|
||||
```
|
||||
|
||||
To build documentation locally and serve it locally,
|
||||
run `mkdocs serve` in the root directory,
|
||||
this should start a server locally to preview your changes.
|
||||
|
||||
```bash
|
||||
$ mkdocs serve
|
||||
INFO - Building documentation...
|
||||
INFO - Cleaning site directory
|
||||
[I 160505 22:31:24 server:281] Serving on http://127.0.0.1:8000
|
||||
[I 160505 22:31:24 handlers:59] Start watching changes
|
||||
[I 160505 22:31:24 handlers:61] Start detecting changes
|
||||
```
|
||||
|
||||
### Verify Documentation
|
||||
|
||||
You can verify that the documentation meets some expectations, as checking for dead links, html markup validity.
|
||||
|
||||
```bash
|
||||
$ make docs-verify
|
||||
docker build -t traefik-docs-verify ./script/docs-verify-docker-image ## Build Validator image
|
||||
...
|
||||
docker run --rm -v /home/travis/build/containous/traefik:/app traefik-docs-verify ## Check for dead links and w3c compliance
|
||||
=== Checking HTML content...
|
||||
Running ["HtmlCheck", "ImageCheck", "ScriptCheck", "LinkCheck"] on /app/site/basics/index.html on *.html...
|
||||
```
|
||||
|
||||
If you recently changed the documentation, do not forget to clean it to have it rebuilt:
|
||||
|
||||
```bash
|
||||
$ make docs-clean docs-verify
|
||||
...
|
||||
```
|
||||
|
||||
Please note that verification can be disabled by setting the environment variable `DOCS_VERIFY_SKIP` to `true`:
|
||||
|
||||
```shell
|
||||
DOCS_VERIFY_SKIP=true make docs-verify
|
||||
...
|
||||
DOCS_LINT_SKIP is true: no linting done.
|
||||
```
|
||||
|
||||
## How to Write a Good Issue
|
||||
|
||||
Please keep in mind that the GitHub issue tracker is not intended as a general support forum, but for reporting bugs and feature requests.
|
||||
|
||||
For end-user related support questions, refer to one of the following:
|
||||
- the Traefik community Slack channel: [](https://slack.traefik.io)
|
||||
- [Stack Overflow](https://stackoverflow.com/questions/tagged/traefik) (using the `traefik` tag)
|
||||
|
||||
### Title
|
||||
|
||||
The title must be short and descriptive. (~60 characters)
|
||||
|
||||
### Description
|
||||
|
||||
- Respect the issue template as much as possible. [template](.github/ISSUE_TEMPLATE.md)
|
||||
- If it's possible use the command `traefik bug`. See https://www.youtube.com/watch?v=Lyz62L8m93I.
|
||||
- Explain the conditions which led you to write this issue: the context.
|
||||
- The context should lead to something, an idea or a problem that you’re facing.
|
||||
- Remain clear and concise.
|
||||
- Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown)
|
||||
|
||||
|
||||
## How to Write a Good Pull Request
|
||||
|
||||
### Title
|
||||
|
||||
The title must be short and descriptive. (~60 characters)
|
||||
|
||||
### Description
|
||||
|
||||
- Respect the pull request template as much as possible. [template](.github/PULL_REQUEST_TEMPLATE.md)
|
||||
- Explain the conditions which led you to write this PR: the context.
|
||||
- The context should lead to something, an idea or a problem that you’re facing.
|
||||
- Remain clear and concise.
|
||||
- Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown)
|
||||
|
||||
### Content
|
||||
|
||||
- Make it small.
|
||||
- Do only one thing.
|
||||
- Write useful descriptions and titles.
|
||||
- Avoid re-formatting.
|
||||
- Make sure the code builds.
|
||||
- Make sure all tests pass.
|
||||
- Add tests.
|
||||
- Address review comments in terms of additional commits.
|
||||
- Do not amend/squash existing ones unless the PR is trivial.
|
||||
- If a PR involves changes to third-party dependencies, the commits pertaining to the vendor folder and the manifest/lock file(s) should be committed separated.
|
||||
|
||||
|
||||
Read [10 tips for better pull requests](http://blog.ploeh.dk/2015/01/15/10-tips-for-better-pull-requests/).
|
||||
|
||||
465
Gopkg.lock
generated
@@ -9,6 +9,14 @@
|
||||
revision = "056a55f54a6cc77b440b31a56a5e7c3982d32811"
|
||||
version = "v0.22.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:a324d70548baeaf58c5a453fafbd0a6bf0d97b584b18c2758a0f8250b6127082"
|
||||
name = "code.cloudfoundry.org/clock"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "02e53af36e6c978af692887ed449b74026d76fec"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:24afd6a7be85997c981693ddcc5f7b37fd3ed5cb119901e14737b9635b944cfb"
|
||||
@@ -88,6 +96,14 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "2fd0705ce648e602e6c9c57329a174270a4f6688"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:25870183293a3fb61cc9afd060a61d63a486f091db72af01a8ea3449f5ca530d"
|
||||
name = "github.com/Masterminds/goutils"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "41ac8693c5c10a92ea1ff5ac3a7f95646f6123b0"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0ce2a409217f52078c6b8642993deb1025940cded6d5054047c1d5c7379f753c"
|
||||
name = "github.com/Masterminds/semver"
|
||||
@@ -97,11 +113,19 @@
|
||||
version = "v1.2.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a49472e7d73071005f436b7da85567220f24bb26fbfccbec45d2cd1359d4c67d"
|
||||
digest = "1:876a1121171c083c4e3a4789683d02a40c0f644c8190da521d15b59799f556d6"
|
||||
name = "github.com/Masterminds/sprig"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "e039e20e500c2c025d9145be375e27cf42a94174"
|
||||
revision = "9f8fceff796fb9f4e992cd2bece016be0121ab74"
|
||||
version = "2.19.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c8e7aea759874984f67a75e43fe764137188b1b062e27639efcba4b801fbcf42"
|
||||
name = "github.com/Microsoft/ApplicationInsights-Go"
|
||||
packages = ["appinsights"]
|
||||
pruneopts = "NUT"
|
||||
revision = "98ac7ca026c26818888600ea0d966987aa56f043"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:915c86626dfd65f8105c48b2972d29f34fd77ff16b31157147cb8ebbddeebbe3"
|
||||
@@ -135,6 +159,21 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "a3fa4a771d87bda2514a90a157e1fed1b6897d2e"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:975b8fd9f8bf97cfdbc6a6af823172e4315b8e0b1d99f7651472f4fe9ff61cbe"
|
||||
name = "github.com/PuerkitoBio/purell"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "8a290539e2e8629dbc4e6bad948158f790ec31f4"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8098cd40cd09879efbf12e33bcd51ead4a66006ac802cd563a66c4f3373b9727"
|
||||
name = "github.com/PuerkitoBio/urlesc"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "5bd2802263f21d8788851d5305584c82a5c75d7e"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e3fc14e683e391975da7c151bd49b968977a6d83244dda5d0a5d0f171a1c23fd"
|
||||
name = "github.com/Shopify/sarama"
|
||||
@@ -161,13 +200,14 @@
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:d4ad87edcd6f6b6cfd1bfe8da6dd090e00901601c9270aa2928842402a361475"
|
||||
digest = "1:779547e35141b520aefb7fc8b9abadc59a3355ed93bc4c6fa93e2311f1685a00"
|
||||
name = "github.com/abronan/valkeyrie"
|
||||
packages = [
|
||||
".",
|
||||
"store",
|
||||
"store/boltdb",
|
||||
"store/consul",
|
||||
"store/etcd/v2",
|
||||
"store/etcd/v3",
|
||||
"store/zookeeper",
|
||||
]
|
||||
@@ -207,20 +247,12 @@
|
||||
version = "1.27.7"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:975108e8d4f5dab096fc991326e96a5716ee8d02e5e7386bb4796171afc4ab9a"
|
||||
name = "github.com/aokoli/goutils"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "3391d3790d23d03408670993e957e8f408993c34"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9752dad5e89cd779096bf2477a4ded16bea7ac62de453c8d6b4bf841d51a8512"
|
||||
digest = "1:b39cf81d5f440b9c0757a25058432d33af867e5201109bf53621356d9dab4b73"
|
||||
name = "github.com/apache/thrift"
|
||||
packages = ["lib/go/thrift"]
|
||||
pruneopts = "NUT"
|
||||
revision = "b2a4d4ae21c789b689dd162deb819665567f481c"
|
||||
version = "0.10.0"
|
||||
revision = "384647d290e2e4a55a14b1b7ef1b7e66293a2c33"
|
||||
version = "v0.12.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -283,6 +315,14 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cdee563173093e5ae7ab2a19c298e0904129719e1919a3c532b7bb0c3398b818"
|
||||
name = "github.com/cenk/backoff"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "1e4cf3da559842a91afcb6ea6141451e6c30c618"
|
||||
version = "v2.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cdee563173093e5ae7ab2a19c298e0904129719e1919a3c532b7bb0c3398b818"
|
||||
name = "github.com/cenkalti/backoff"
|
||||
@@ -315,14 +355,6 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "b2b946a77f5973f420514090d6f6dd58b08303f0"
|
||||
|
||||
[[projects]]
|
||||
branch = "containous-fork"
|
||||
digest = "1:9587a8a4fd4f872f2a1983297f144e8cdbc86664482e74bf91f1d6d8e43f7120"
|
||||
name = "github.com/containous/alice"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "d83ebdd94cbdbcd9c6c6a22e1a0cde05e55d9d90"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3cd675d508d3f9067704d36011c7a262fc0a0bf1ad0361a7d1e60a405d12941e"
|
||||
name = "github.com/containous/flaeg"
|
||||
@@ -350,6 +382,14 @@
|
||||
revision = "7a9987c3a6d46be84e141a5c3191347ec10af17d"
|
||||
version = "v3.1.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:02cd7cf98a158c2388f0d26615be0c5d400fc13fad4448f2ecdb61f49b926c74"
|
||||
name = "github.com/containous/traefik-extra-service-fabric"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "f9f49c0a6d51a8205faf34dd3cb7d628611257e5"
|
||||
version = "v1.5.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:4e9c9d51ef5c98f2a71d6fa56a22f0bb8a4463656f9591ba24ebff2c21bcbd1f"
|
||||
name = "github.com/coreos/bbolt"
|
||||
@@ -358,21 +398,33 @@
|
||||
revision = "32c383e75ce054674c53b5a07e55de85332aee14"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f07fcc19d52f5d9f559573c6b625142bc4b018fd275029c020d06d2bad6f6c8c"
|
||||
digest = "1:8d8acd5ccd6193f1c298c2575812339fb7bd19bac82403a34075937313338217"
|
||||
name = "github.com/coreos/etcd"
|
||||
packages = [
|
||||
"auth/authpb",
|
||||
"client",
|
||||
"clientv3",
|
||||
"clientv3/concurrency",
|
||||
"etcdserver/api/v3rpc/rpctypes",
|
||||
"etcdserver/etcdserverpb",
|
||||
"mvcc/mvccpb",
|
||||
"pkg/pathutil",
|
||||
"pkg/srv",
|
||||
"pkg/types",
|
||||
"version",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "70c8726202dd91e482fb4029fd14af1d4ed1d5af"
|
||||
version = "v3.3.5"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0ef770954bca104ee99b3b6b7f9b240605ac03517d9f98cbc1893daa03f3c038"
|
||||
name = "github.com/coreos/go-semver"
|
||||
packages = ["semver"]
|
||||
pruneopts = "NUT"
|
||||
revision = "8ab6407b697782a06568d4b7f1db25550ec2e4c6"
|
||||
version = "v0.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:fa91847d50d3f656fc2d2d608b9749b97d77528e8988ad8001f957640545e91e"
|
||||
name = "github.com/coreos/go-systemd"
|
||||
@@ -627,6 +679,16 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "30f82fa23fd844bd5bb1e5f216db87fd77b5eb43"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8731ec88b62435bdf4c35484309e6b7afd2e9caac8b0bb5661844d7d9ef70a64"
|
||||
name = "github.com/emicklei/go-restful"
|
||||
packages = [
|
||||
".",
|
||||
"log",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "89ef8af493ab468a45a42bb0d89a06fccdd2fb22"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6f26e34204ddad638d25456c6443c2763aa38954558226722c253e08e9f491fa"
|
||||
name = "github.com/exoscale/egoscale"
|
||||
@@ -673,8 +735,7 @@
|
||||
revision = "73d445a93680fa1a78ae23a5839bad48f32ba1ee"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:7dcfb91047873eb70b3a20ed5c400c4d05d9c1e73c9dac0e71ec0d44589d208a"
|
||||
digest = "1:68a7713d996a30a8394715220e779bbcbc880a18b1e0ab0e12fd1fbbf9c711e6"
|
||||
name = "github.com/go-acme/lego"
|
||||
packages = [
|
||||
"acme",
|
||||
@@ -712,6 +773,7 @@
|
||||
"providers/dns/dnsmadeeasy",
|
||||
"providers/dns/dnsmadeeasy/internal",
|
||||
"providers/dns/dnspod",
|
||||
"providers/dns/dode",
|
||||
"providers/dns/dreamhost",
|
||||
"providers/dns/duckdns",
|
||||
"providers/dns/dyn",
|
||||
@@ -758,7 +820,8 @@
|
||||
"registration",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "0c87df143e630a1c50b4c36c8fbdda6cd993ebba"
|
||||
revision = "3d13faf68920543a393ad6cdfdea429627af2d34"
|
||||
version = "v2.5.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "fork-containous"
|
||||
@@ -804,6 +867,34 @@
|
||||
revision = "390ab7935ee28ec6b286364bba9b4dd6410cb3d5"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e90c60f901104f83cdcad5961d752541a0cd6a0bd996d7fd920bfe8f8c39f241"
|
||||
name = "github.com/go-openapi/jsonpointer"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "46af16f9f7b149af66e5d1bd010e3574dc06de98"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:98abd61947ff5c7c6fcfec5473d02a4821ed3a2dd99a4fbfdb7925b0dd745546"
|
||||
name = "github.com/go-openapi/jsonreference"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9a247af322a3c4780ad9ba3517f517d93925dee18a7e420209f786f5110b1a28"
|
||||
name = "github.com/go-openapi/spec"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "6aced65f8501fe1217321abf0749d354824ba2ff"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:4f5899c71c18d685e67cb365438b433f9d3e0c39f4c865f3d4fe80459b12ffd7"
|
||||
name = "github.com/go-openapi/swag"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "1d0bd113de87027671077d3c71eb3ac5d7dbba72"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:00f1b1d722a012f54670ecc09ee92557d7314bfd9152a085ce91933c1c14b026"
|
||||
name = "github.com/go-resty/resty"
|
||||
@@ -914,7 +1005,7 @@
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:31a16fabf629bd7e0e6ad92939f31847cc57f3fe08898d3c31333da817196c72"
|
||||
digest = "1:36dd0a75423030a37dcb698876834ee7cec249d7c6b50d0892505c736df35ff4"
|
||||
name = "github.com/gophercloud/gophercloud"
|
||||
packages = [
|
||||
".",
|
||||
@@ -928,7 +1019,7 @@
|
||||
"pagination",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "892256c468586d66a6864daeee52441afc0f7e38"
|
||||
revision = "bc37892e196848d7d56ba0db974ba1d7d5c43b2a"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:dbd86d229eacaa86a98b10f8fb3e3fc69a1913e0f4e010e7cc1f92bf12edca92"
|
||||
@@ -1020,11 +1111,12 @@
|
||||
revision = "19f2c401e122352c047a84d6584dd51e2fb8fcc4"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:45e66b20393507035c6a7d15bef5ffe8faf5b083621c1284d9824cc052776de5"
|
||||
digest = "1:dc54242755f5b6721dd880843de6e45fe234838ea9149ec8249951880fd5802f"
|
||||
name = "github.com/huandu/xstrings"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "3959339b333561bf62a38b424fd41517c2c90f40"
|
||||
revision = "f02667b379e2fb5916c3cda2cf31e0eb885d79f8"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -1058,12 +1150,19 @@
|
||||
version = "v1.3.7"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:78efd72f12ed0244e5fbe82bd0ecdbaf3e21402ee9176525ef1138a2fc0d3b17"
|
||||
name = "github.com/instana/go-sensor"
|
||||
branch = "master"
|
||||
digest = "1:a25742a258a504af695b05c2e0cb64274a4e64f4dc0b9e07245e485bad2a49f9"
|
||||
name = "github.com/jjcollinge/logrus-appinsights"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "493edb42228321483dd4d59ade71ca93fa89d66b"
|
||||
version = "1.4.12"
|
||||
revision = "9b66602d496a139e4722bdde32f0f1ac1c12d4a8"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c8bc92de733cd1d4f8a30749c846f79d03552ef72ca686695214226a24ac534e"
|
||||
name = "github.com/jjcollinge/servicefabric"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "8eebe170fa1ba25d3dfb928b3f86a7313b13b9fe"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ac6d01547ec4f7f673311b4663909269bfb8249952de3279799289467837c3cc"
|
||||
@@ -1080,12 +1179,12 @@
|
||||
revision = "72f9bd7c4e0c2a40055ab3d0f09654f730cce982"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:4e903242fe176238aaa469f59d7035f5abf2aa9acfefb8964ddd203651b574e9"
|
||||
digest = "1:63d8b364f0768ffda64b8e6e15c10535f3431e3c69d051dbb37f467ada02df98"
|
||||
name = "github.com/json-iterator/go"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "0ff49de124c6f76f8494e194af75bde0f1a49a29"
|
||||
version = "v1.1.6"
|
||||
revision = "28452fcdec4e44348d2af0d91d1e9e38da3a9e0a"
|
||||
version = "1.0.5"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8b3234b10eacd5edea45bf0c13a585b608749da23f94aaf29b46d9ef8a8babf4"
|
||||
@@ -1154,14 +1253,6 @@
|
||||
revision = "d0d31d8ca62fa3f7e4526ca0ce95de81e4ed001e"
|
||||
version = "v0.5.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8520e78cbe9878f6bf0cfdcfaed5761cd575b3568c260a1c891ac1f5c5c6a726"
|
||||
name = "github.com/looplab/fsm"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "84b5307469f859464403f80919467950a79de1b1"
|
||||
version = "v0.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:196b0d7580e898df15a7cc5371cbfe2b8e22904f5c6c883ed5db0130e551c8fb"
|
||||
name = "github.com/mailgun/minheap"
|
||||
@@ -1192,6 +1283,17 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "c1c17f74874f2a5ea48bfb06b5459d4ef2689749"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f9d15bd15966e80ec044ed83df29c267b87fbf646765939ee37e48287061e71b"
|
||||
name = "github.com/mailru/easyjson"
|
||||
packages = [
|
||||
"buffer",
|
||||
"jlexer",
|
||||
"jwriter",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "d5b7844b561a7bc640052f1b935f7b800330d7e0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:4953945f4fdc12cb7aa0263710534fb64b35a85e4047570fdf1cb03284055f0d"
|
||||
name = "github.com/mattn/go-colorable"
|
||||
@@ -1293,22 +1395,6 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "63d60e9d0dbc60cf9164e6510889b0db6683d98c"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2f42fa12d6911c7b7659738758631bec870b7e9b4c6be5444f963cdcfccc191f"
|
||||
name = "github.com/modern-go/concurrent"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94"
|
||||
version = "1.0.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c6aca19413b13dc59c220ad7430329e2ec454cc310bc6d8de2c7e2b93c18a0f6"
|
||||
name = "github.com/modern-go/reflect2"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd"
|
||||
version = "1.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7bb97a5f80a2429fa605e176e16cf682cbb5ac681f421a06efb4e3b8efae6e5f"
|
||||
name = "github.com/mvdan/xurls"
|
||||
@@ -1383,17 +1469,6 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "a52f2342449246d5bcc273e65cbdcfa5f7d6c63c"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cc405544fecfb5a8e0c409127ef67ce3b91d11143a00121e5b822e4f8eabe7d2"
|
||||
name = "github.com/opentracing/basictracer-go"
|
||||
packages = [
|
||||
".",
|
||||
"wire",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "1b32af207119a14b1b231d451df3ed04a72efebf"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7da29c22bcc5c2ffb308324377dc00b5084650348c2799e573ed226d8cc9faf0"
|
||||
name = "github.com/opentracing/opentracing-go"
|
||||
@@ -1407,8 +1482,8 @@
|
||||
version = "v1.0.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:07c44a0ce6012eafd2f05b715d30852d576aeda7798b8760a2ff51b1e90eb753"
|
||||
name = "github.com/openzipkin/zipkin-go-opentracing"
|
||||
digest = "1:0f4793617dc898d3ee99fe1abab076d3976a9d17d14f8327af2dc3f1ec0fd92c"
|
||||
name = "github.com/openzipkin-contrib/zipkin-go-opentracing"
|
||||
packages = [
|
||||
".",
|
||||
"flag",
|
||||
@@ -1418,18 +1493,19 @@
|
||||
"wire",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "1f5c07e90700ae93ddcba0c7af7d9c7201646ccc"
|
||||
revision = "f0f479ad013a498e4cbfb369414e5d3880903779"
|
||||
version = "v0.3.5"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b52f6b7059f06a11ab6ab039714dda0d93878d9227f84c84ca9b6f046bfb5fef"
|
||||
digest = "1:8a5a270130e940b167027e8c9a07c20d01410bd6053160432351139ddc2cb501"
|
||||
name = "github.com/oracle/oci-go-sdk"
|
||||
packages = [
|
||||
"common",
|
||||
"dns",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "0306ff0303b6c9f6ea6458021adc9f8f09f562b0"
|
||||
version = "v4.2.0"
|
||||
revision = "5295b14d42d1417490d318ff91475c5868e0fd8f"
|
||||
version = "v4.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -1572,17 +1648,18 @@
|
||||
revision = "256dc444b735e061061cf46c809487313d5b0065"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:fff470b0a7bbf05cfe8bfc73bfdf4d21eb009ea84e601f3d27781474e5da960f"
|
||||
digest = "1:253f275bd72c42f8d234712d1574c8b222fe9b72838bfaca11b21ace9c0e3d0a"
|
||||
name = "github.com/sacloud/libsacloud"
|
||||
packages = [
|
||||
".",
|
||||
"api",
|
||||
"sacloud",
|
||||
"sacloud/ostype",
|
||||
"utils/mutexkv",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "306ea89b6ef19334614f7b0fc5aa19595022bb8c"
|
||||
revision = "41c392dee98a83260abbe0fcd5c13beb7c75d103"
|
||||
version = "v1.21.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:142520cf3c9bb85449dd0000f820b8c604531587ee654793c54909be7dabadac"
|
||||
@@ -1608,12 +1685,11 @@
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9d8420bbf131d1618bde6530af37c3799340d3762cc47210c1d9532a4c3a2779"
|
||||
digest = "1:f709d7d110053ada282e5ab1eabcb3581bcf3aaa27d22873ab8d8291c7a474bb"
|
||||
name = "github.com/spf13/pflag"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "298182f68c66c05229eb03ac171abe6e309ee79a"
|
||||
version = "v1.0.3"
|
||||
revision = "cb88ea77998c3f024757528e3305022ab50b43be"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:10301358a67805684f6b525cba6ad7ec014dbd56cccc2926fadc9189faa7889a"
|
||||
@@ -1644,11 +1720,11 @@
|
||||
revision = "c4434f09ec131ecf30f986d5dcb1636508bfa49a"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c269070c7f286c941392076d6c42a4a7a062356f7083a48bd202397e877a965e"
|
||||
digest = "1:84b9a5318d8ce3b8a9b1509bf15734f4f9dcd4decf9d9e9c7346a16c7b64d49e"
|
||||
name = "github.com/thoas/stats"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "152b5d051953fdb6e45f14b6826962aadc032324"
|
||||
revision = "4975baf6a358ed3ddaa42133996e1959f96c9300"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -1671,7 +1747,7 @@
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:3b236e8930d31aeb375fe405c15c2afc581e04bd6cb68da4723e1aa8d2e2da37"
|
||||
digest = "1:21ef4b12d6b6c6dfe389caa82f859d1e35eeb24ce61c60d09db57addb6a95781"
|
||||
name = "github.com/transip/gotransip"
|
||||
packages = [
|
||||
".",
|
||||
@@ -1679,7 +1755,7 @@
|
||||
"util",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "1dc93a7db3567a5ccf865106afac88278ba940cf"
|
||||
revision = "c6e2ce0bbb4a601a909e3b7a773358d6b503e663"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9b2996458a2f7d1f3e0ebf08152acfe8c1106f3fe855d08121c5ee7d801a063f"
|
||||
@@ -1689,7 +1765,7 @@
|
||||
revision = "03389da7e0bf9844767f82690f4d68fc097a1306"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7d3a890e525da3b7014d26dd1d4a0e4d31a479995007cd11989ad31db132e66c"
|
||||
digest = "1:87dee780f88f86f300bbd90116e933347cf4a3c65c1960072d412597a8896d50"
|
||||
name = "github.com/uber/jaeger-client-go"
|
||||
packages = [
|
||||
".",
|
||||
@@ -1709,7 +1785,6 @@
|
||||
"thrift-gen/zipkincore",
|
||||
"transport",
|
||||
"utils",
|
||||
"zipkin",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "1a782e2da844727691fef1757c72eb190c2909f0"
|
||||
@@ -1723,6 +1798,14 @@
|
||||
revision = "ed3a127ec5fef7ae9ea95b01b542c47fbd999ce5"
|
||||
version = "v1.5.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:919fc2a81add8ac4a7a236dceaf3e4c29ba4df96d13c3f2ce13a4d0132ebefb8"
|
||||
name = "github.com/ugorji/go"
|
||||
packages = ["codec"]
|
||||
pruneopts = "NUT"
|
||||
revision = "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:fb6d90081ae53fdb35d0fffa7d82a555df936bbc6db9a93126795655b2317604"
|
||||
name = "github.com/unrolled/render"
|
||||
@@ -1801,6 +1884,42 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "0c8571ac0ce161a5feb57375a9cdf148c98c0f70"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:045bc0ab96bb83bdffd2606f019003da03d1c139d3cb8aad13596863e4dd37d6"
|
||||
name = "go.opencensus.io"
|
||||
packages = [
|
||||
".",
|
||||
"internal",
|
||||
"internal/tagencoding",
|
||||
"metric/metricdata",
|
||||
"metric/metricproducer",
|
||||
"plugin/ochttp",
|
||||
"plugin/ochttp/propagation/b3",
|
||||
"resource",
|
||||
"stats",
|
||||
"stats/internal",
|
||||
"stats/view",
|
||||
"tag",
|
||||
"trace",
|
||||
"trace/internal",
|
||||
"trace/propagation",
|
||||
"trace/tracestate",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "75c0cca22312e51bfd4fafdbe9197ae399e18b38"
|
||||
version = "v0.20.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:02fe59517e10f9b400b500af8ac228c74cecb0cba7a5f438d8283edb97e14270"
|
||||
name = "go.uber.org/ratelimit"
|
||||
packages = [
|
||||
".",
|
||||
"internal/clock",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "c15da02342779cb6dc027fc95ee2277787698f36"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:30c1930f8c9fee79f3af60c8b7cd92edd12a4f22187f5527d53509b1a794f555"
|
||||
@@ -1873,18 +1992,25 @@
|
||||
revision = "fff93fa7cd278d84afc205751523809c464168ab"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ca9ebfc1200ca7423d9778dba9cdd463704753541c99dc4896f15e0b8b2bf1e8"
|
||||
digest = "1:6333c67358d261e0b96d6777a10f08ca074e2348a6c99d9dc1cebd0cf3736cb2"
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"cases",
|
||||
"internal",
|
||||
"internal/gen",
|
||||
"internal/tag",
|
||||
"internal/triegen",
|
||||
"internal/ucd",
|
||||
"language",
|
||||
"runes",
|
||||
"secure/bidirule",
|
||||
"secure/precis",
|
||||
"transform",
|
||||
"unicode/bidi",
|
||||
"unicode/cldr",
|
||||
"unicode/norm",
|
||||
"unicode/rangetable",
|
||||
"width",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "4ee4af566555f5fbe026368b75596286a312663a"
|
||||
@@ -1897,38 +2023,22 @@
|
||||
revision = "8be79e1e0910c292df4e79c241bb7e8f7e725959"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:e46d8e20161401a9cf8765dfa428494a3492a0b56fe114156b7da792bf41ba78"
|
||||
name = "golang.org/x/tools"
|
||||
packages = [
|
||||
"go/ast/astutil",
|
||||
"go/gcexportdata",
|
||||
"go/internal/cgo",
|
||||
"go/internal/gcimporter",
|
||||
"go/internal/packagesdriver",
|
||||
"go/packages",
|
||||
"go/types/typeutil",
|
||||
"imports",
|
||||
"internal/fastwalk",
|
||||
"internal/gopathwalk",
|
||||
"internal/module",
|
||||
"internal/semver",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "589c23e65e65055d47b9ad4a99723bc389136265"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:da32ebe70dd3ec97d2df26281b08b18d05c2f12491ae79f389813f6c8d3006b3"
|
||||
digest = "1:887c07769ee52c81222ea1112b11a3947c703da529d1643d85c216b9537d87b8"
|
||||
name = "google.golang.org/api"
|
||||
packages = [
|
||||
"dns/v1",
|
||||
"gensupport",
|
||||
"googleapi",
|
||||
"googleapi/internal/uritemplates",
|
||||
"googleapi/transport",
|
||||
"internal",
|
||||
"option",
|
||||
"transport/http",
|
||||
"transport/http/internal/propagation",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "de943baf05a022a8f921b544b7827bacaba1aed5"
|
||||
revision = "0cbcb99a9ea0c8023c794b2693cbe1def82ed4d7"
|
||||
version = "v0.3.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7206d98ec77c90c72ec2c405181a1dcf86965803b6dbc4f98ceab7a5047c37a9"
|
||||
@@ -2063,7 +2173,7 @@
|
||||
revision = "53feefa2559fb8dfa8d81baad31be332c97d6c77"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b1c6723e934087c2fa159e1c6a309c3c5c0b9a7d209c2ba6028f21240ebe7606"
|
||||
digest = "1:85cc1b1a331f54ffd419ccd435426ee992603d4ba463b51790a312b59d1a09dc"
|
||||
name = "k8s.io/api"
|
||||
packages = [
|
||||
"admissionregistration/v1alpha1",
|
||||
@@ -2090,18 +2200,17 @@
|
||||
"rbac/v1alpha1",
|
||||
"rbac/v1beta1",
|
||||
"scheduling/v1alpha1",
|
||||
"scheduling/v1beta1",
|
||||
"settings/v1alpha1",
|
||||
"storage/v1",
|
||||
"storage/v1alpha1",
|
||||
"storage/v1beta1",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "e86510ea3fe79b17eda7b8b3bb5cf8811d3af968"
|
||||
version = "kubernetes-1.11.7"
|
||||
revision = "af4bc157c3a209798fc897f6d4aaaaeb6c2e0d6a"
|
||||
version = "kubernetes-1.9.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:1fa62171c3cf8abf9933ec87c1a00881f8f086a5928b2a8481c469ab67cb4104"
|
||||
digest = "1:4f4489c97d0f170a24dbb98fd47e904bae80abe7ece29f337a28cbc5470877b1"
|
||||
name = "k8s.io/apimachinery"
|
||||
packages = [
|
||||
"pkg/api/errors",
|
||||
@@ -2110,7 +2219,7 @@
|
||||
"pkg/apis/meta/internalversion",
|
||||
"pkg/apis/meta/v1",
|
||||
"pkg/apis/meta/v1/unstructured",
|
||||
"pkg/apis/meta/v1beta1",
|
||||
"pkg/apis/meta/v1alpha1",
|
||||
"pkg/conversion",
|
||||
"pkg/conversion/queryparams",
|
||||
"pkg/fields",
|
||||
@@ -2132,30 +2241,26 @@
|
||||
"pkg/util/framer",
|
||||
"pkg/util/intstr",
|
||||
"pkg/util/json",
|
||||
"pkg/util/mergepatch",
|
||||
"pkg/util/net",
|
||||
"pkg/util/runtime",
|
||||
"pkg/util/sets",
|
||||
"pkg/util/strategicpatch",
|
||||
"pkg/util/validation",
|
||||
"pkg/util/validation/field",
|
||||
"pkg/util/wait",
|
||||
"pkg/util/yaml",
|
||||
"pkg/version",
|
||||
"pkg/watch",
|
||||
"third_party/forked/golang/json",
|
||||
"third_party/forked/golang/reflect",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "1525e4dadd2df06debdeb27ee24ac7db5b6413b1"
|
||||
version = "kubernetes-1.11.7"
|
||||
revision = "180eddb345a5be3a157cea1c624700ad5bd27b8f"
|
||||
version = "kubernetes-1.9.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b557d722202c63ce865634b8adc42b7d283b7d71bd52c7becf525abc04b8bed9"
|
||||
digest = "1:73fb394370cf934e4529d6b8c76d44e4ec153079356add273df066957f853451"
|
||||
name = "k8s.io/client-go"
|
||||
packages = [
|
||||
"discovery",
|
||||
"discovery/fake",
|
||||
"informers",
|
||||
"informers/admissionregistration",
|
||||
"informers/admissionregistration/v1alpha1",
|
||||
@@ -2190,7 +2295,6 @@
|
||||
"informers/rbac/v1beta1",
|
||||
"informers/scheduling",
|
||||
"informers/scheduling/v1alpha1",
|
||||
"informers/scheduling/v1beta1",
|
||||
"informers/settings",
|
||||
"informers/settings/v1alpha1",
|
||||
"informers/storage",
|
||||
@@ -2223,7 +2327,6 @@
|
||||
"kubernetes/typed/rbac/v1alpha1",
|
||||
"kubernetes/typed/rbac/v1beta1",
|
||||
"kubernetes/typed/scheduling/v1alpha1",
|
||||
"kubernetes/typed/scheduling/v1beta1",
|
||||
"kubernetes/typed/settings/v1alpha1",
|
||||
"kubernetes/typed/storage/v1",
|
||||
"kubernetes/typed/storage/v1alpha1",
|
||||
@@ -2248,101 +2351,35 @@
|
||||
"listers/rbac/v1alpha1",
|
||||
"listers/rbac/v1beta1",
|
||||
"listers/scheduling/v1alpha1",
|
||||
"listers/scheduling/v1beta1",
|
||||
"listers/settings/v1alpha1",
|
||||
"listers/storage/v1",
|
||||
"listers/storage/v1alpha1",
|
||||
"listers/storage/v1beta1",
|
||||
"pkg/apis/clientauthentication",
|
||||
"pkg/apis/clientauthentication/v1alpha1",
|
||||
"pkg/apis/clientauthentication/v1beta1",
|
||||
"pkg/version",
|
||||
"plugin/pkg/client/auth/exec",
|
||||
"rest",
|
||||
"rest/watch",
|
||||
"testing",
|
||||
"tools/auth",
|
||||
"tools/cache",
|
||||
"tools/clientcmd",
|
||||
"tools/clientcmd/api",
|
||||
"tools/clientcmd/api/latest",
|
||||
"tools/clientcmd/api/v1",
|
||||
"tools/metrics",
|
||||
"tools/pager",
|
||||
"tools/reference",
|
||||
"transport",
|
||||
"util/buffer",
|
||||
"util/cert",
|
||||
"util/connrotation",
|
||||
"util/flowcontrol",
|
||||
"util/homedir",
|
||||
"util/integer",
|
||||
"util/retry",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "7d04d0e2a0a1a4d4a1cd6baa432a2301492e4e65"
|
||||
version = "v8.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7320216e50843bfc1092d20b535a20683abc967e0eba38441c85c3265da10a34"
|
||||
name = "k8s.io/code-generator"
|
||||
packages = [
|
||||
"cmd/client-gen",
|
||||
"cmd/client-gen/args",
|
||||
"cmd/client-gen/generators",
|
||||
"cmd/client-gen/generators/fake",
|
||||
"cmd/client-gen/generators/scheme",
|
||||
"cmd/client-gen/generators/util",
|
||||
"cmd/client-gen/path",
|
||||
"cmd/client-gen/types",
|
||||
"cmd/deepcopy-gen",
|
||||
"cmd/deepcopy-gen/args",
|
||||
"cmd/defaulter-gen",
|
||||
"cmd/defaulter-gen/args",
|
||||
"cmd/informer-gen",
|
||||
"cmd/informer-gen/args",
|
||||
"cmd/informer-gen/generators",
|
||||
"cmd/lister-gen",
|
||||
"cmd/lister-gen/args",
|
||||
"cmd/lister-gen/generators",
|
||||
"pkg/util",
|
||||
]
|
||||
pruneopts = "T"
|
||||
revision = "f8cba74510f397bac80157a6c4ccb0ffbc31b9d0"
|
||||
version = "kubernetes-1.11.7"
|
||||
revision = "78700dec6369ba22221b72770783300f143df150"
|
||||
version = "v6.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:61024ed77a53ac618effed55043bf6a9afbdeb64136bd6a5b0c992d4c0363766"
|
||||
name = "k8s.io/gengo"
|
||||
packages = [
|
||||
"args",
|
||||
"examples/deepcopy-gen/generators",
|
||||
"examples/defaulter-gen/generators",
|
||||
"examples/set-gen/sets",
|
||||
"generator",
|
||||
"namer",
|
||||
"parser",
|
||||
"types",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "0689ccc1d7d65d9dd1bedcc3b0b1ed7df91ba266"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c263611800c3a97991dbcf9d3bc4de390f6224aaa8ca0a7226a9d734f65a416a"
|
||||
name = "k8s.io/klog"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "71442cd4037d612096940ceb0f3fec3f7fff66e0"
|
||||
version = "v0.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:03a96603922fc1f6895ae083e1e16d943b55ef0656b56965351bd87e7d90485f"
|
||||
digest = "1:f5487c07872bdb7c40ffe629430b2fa815f9eca0d2c02bb9e866962eb38a0e70"
|
||||
name = "k8s.io/kube-openapi"
|
||||
packages = ["pkg/util/proto"]
|
||||
packages = ["pkg/common"]
|
||||
pruneopts = "NUT"
|
||||
revision = "15615b16d372105f0c69ff47dfe7402926a65aaa"
|
||||
revision = "275e2ce91dec4c05a4094a7b1daee5560b555ac9"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
@@ -2358,6 +2395,7 @@
|
||||
"github.com/abronan/valkeyrie/store",
|
||||
"github.com/abronan/valkeyrie/store/boltdb",
|
||||
"github.com/abronan/valkeyrie/store/consul",
|
||||
"github.com/abronan/valkeyrie/store/etcd/v2",
|
||||
"github.com/abronan/valkeyrie/store/etcd/v3",
|
||||
"github.com/abronan/valkeyrie/store/zookeeper",
|
||||
"github.com/armon/go-proxyproto",
|
||||
@@ -2371,12 +2409,12 @@
|
||||
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface",
|
||||
"github.com/aws/aws-sdk-go/service/ec2",
|
||||
"github.com/aws/aws-sdk-go/service/ecs",
|
||||
"github.com/cenkalti/backoff",
|
||||
"github.com/containous/alice",
|
||||
"github.com/cenk/backoff",
|
||||
"github.com/containous/flaeg",
|
||||
"github.com/containous/flaeg/parse",
|
||||
"github.com/containous/mux",
|
||||
"github.com/containous/staert",
|
||||
"github.com/containous/traefik-extra-service-fabric",
|
||||
"github.com/coreos/go-systemd/daemon",
|
||||
"github.com/davecgh/go-spew/spew",
|
||||
"github.com/docker/docker/api/types",
|
||||
@@ -2413,11 +2451,12 @@
|
||||
"github.com/go-kit/kit/metrics/statsd",
|
||||
"github.com/golang/protobuf/proto",
|
||||
"github.com/google/go-github/github",
|
||||
"github.com/google/uuid",
|
||||
"github.com/gorilla/websocket",
|
||||
"github.com/hashicorp/consul/api",
|
||||
"github.com/hashicorp/go-version",
|
||||
"github.com/influxdata/influxdb/client/v2",
|
||||
"github.com/instana/go-sensor",
|
||||
"github.com/jjcollinge/servicefabric",
|
||||
"github.com/libkermit/compose/check",
|
||||
"github.com/libkermit/docker",
|
||||
"github.com/libkermit/docker-check",
|
||||
@@ -2438,7 +2477,7 @@
|
||||
"github.com/opentracing/opentracing-go",
|
||||
"github.com/opentracing/opentracing-go/ext",
|
||||
"github.com/opentracing/opentracing-go/log",
|
||||
"github.com/openzipkin/zipkin-go-opentracing",
|
||||
"github.com/openzipkin-contrib/zipkin-go-opentracing",
|
||||
"github.com/patrickmn/go-cache",
|
||||
"github.com/pkg/errors",
|
||||
"github.com/prometheus/client_golang/prometheus",
|
||||
@@ -2447,7 +2486,6 @@
|
||||
"github.com/rancher/go-rancher-metadata/metadata",
|
||||
"github.com/rancher/go-rancher/v2",
|
||||
"github.com/ryanuber/go-glob",
|
||||
"github.com/satori/go.uuid",
|
||||
"github.com/sirupsen/logrus",
|
||||
"github.com/stretchr/testify/assert",
|
||||
"github.com/stretchr/testify/mock",
|
||||
@@ -2456,7 +2494,6 @@
|
||||
"github.com/thoas/stats",
|
||||
"github.com/uber/jaeger-client-go",
|
||||
"github.com/uber/jaeger-client-go/config",
|
||||
"github.com/uber/jaeger-client-go/zipkin",
|
||||
"github.com/uber/jaeger-lib/metrics",
|
||||
"github.com/unrolled/render",
|
||||
"github.com/unrolled/secure",
|
||||
@@ -2469,7 +2506,6 @@
|
||||
"github.com/vulcand/oxy/ratelimit",
|
||||
"github.com/vulcand/oxy/roundrobin",
|
||||
"github.com/vulcand/oxy/utils",
|
||||
"github.com/vulcand/predicate",
|
||||
"golang.org/x/net/http/httpguts",
|
||||
"golang.org/x/net/http2",
|
||||
"golang.org/x/net/http2/hpack",
|
||||
@@ -2479,6 +2515,7 @@
|
||||
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/opentracer",
|
||||
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer",
|
||||
"gopkg.in/fsnotify.v1",
|
||||
"gopkg.in/yaml.v2",
|
||||
"k8s.io/api/core/v1",
|
||||
"k8s.io/api/extensions/v1beta1",
|
||||
"k8s.io/apimachinery/pkg/api/errors",
|
||||
@@ -2486,25 +2523,13 @@
|
||||
"k8s.io/apimachinery/pkg/labels",
|
||||
"k8s.io/apimachinery/pkg/runtime",
|
||||
"k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer",
|
||||
"k8s.io/apimachinery/pkg/types",
|
||||
"k8s.io/apimachinery/pkg/util/intstr",
|
||||
"k8s.io/apimachinery/pkg/watch",
|
||||
"k8s.io/client-go/discovery",
|
||||
"k8s.io/client-go/discovery/fake",
|
||||
"k8s.io/client-go/informers",
|
||||
"k8s.io/client-go/kubernetes",
|
||||
"k8s.io/client-go/kubernetes/scheme",
|
||||
"k8s.io/client-go/rest",
|
||||
"k8s.io/client-go/testing",
|
||||
"k8s.io/client-go/tools/cache",
|
||||
"k8s.io/client-go/tools/clientcmd",
|
||||
"k8s.io/client-go/util/flowcontrol",
|
||||
"k8s.io/code-generator/cmd/client-gen",
|
||||
"k8s.io/code-generator/cmd/deepcopy-gen",
|
||||
"k8s.io/code-generator/cmd/defaulter-gen",
|
||||
"k8s.io/code-generator/cmd/informer-gen",
|
||||
"k8s.io/code-generator/cmd/lister-gen",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
||||
70
Gopkg.toml
@@ -19,22 +19,10 @@
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
|
||||
required = [
|
||||
"k8s.io/code-generator/cmd/client-gen",
|
||||
"k8s.io/code-generator/cmd/deepcopy-gen",
|
||||
"k8s.io/code-generator/cmd/defaulter-gen",
|
||||
"k8s.io/code-generator/cmd/lister-gen",
|
||||
"k8s.io/code-generator/cmd/informer-gen",
|
||||
]
|
||||
|
||||
[prune]
|
||||
non-go = true
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
[[prune.project]]
|
||||
name = "k8s.io/code-generator"
|
||||
non-go = false
|
||||
unused-packages = false
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
@@ -48,6 +36,10 @@ required = [
|
||||
branch = "master"
|
||||
name = "github.com/BurntSushi/ty"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/Masterminds/sprig"
|
||||
version = "2.19.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/NYTimes/gziphandler"
|
||||
@@ -57,6 +49,11 @@ required = [
|
||||
name = "github.com/abbot/go-http-auth"
|
||||
source = "github.com/containous/go-http-auth"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/thoas/stats"
|
||||
# related to https://github.com/thoas/stats/pull/32
|
||||
revision = "4975baf6a358ed3ddaa42133996e1959f96c9300"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/armon/go-proxyproto"
|
||||
@@ -66,8 +63,8 @@ required = [
|
||||
version = "1.13.11"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/cenkalti/backoff"
|
||||
version = "2.1.1"
|
||||
name = "github.com/cenk/backoff"
|
||||
version = "v2.1.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/containous/flaeg"
|
||||
@@ -78,16 +75,12 @@ required = [
|
||||
name = "github.com/containous/mux"
|
||||
|
||||
[[constraint]]
|
||||
branch = "containous-fork"
|
||||
name = "github.com/containous/alice"
|
||||
name = "github.com/containous/staert"
|
||||
version = "3.1.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/containous/staert"
|
||||
version = "3.1.2"
|
||||
|
||||
#[[constraint]]
|
||||
# name = "github.com/containous/traefik-extra-service-fabric"
|
||||
# version = "1.3.0"
|
||||
name = "github.com/containous/traefik-extra-service-fabric"
|
||||
version = "v1.5.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/coreos/go-systemd"
|
||||
@@ -132,9 +125,9 @@ required = [
|
||||
name = "github.com/influxdata/influxdb"
|
||||
version = "1.3.7"
|
||||
|
||||
#[[constraint]]
|
||||
# branch = "master"
|
||||
# name = "github.com/jjcollinge/servicefabric"
|
||||
[[constraint]]
|
||||
name = "github.com/jjcollinge/servicefabric"
|
||||
revision = "8eebe170fa1ba25d3dfb928b3f86a7313b13b9fe"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
@@ -157,10 +150,6 @@ required = [
|
||||
branch = "master"
|
||||
name = "github.com/ryanuber/go-glob"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/satori/go.uuid"
|
||||
version = "1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/stvp/go-udp-testing"
|
||||
@@ -175,7 +164,7 @@ required = [
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/uber/jaeger-lib"
|
||||
version = "1.3.0"
|
||||
version = "1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "v1"
|
||||
@@ -190,9 +179,8 @@ required = [
|
||||
name = "github.com/vulcand/oxy"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/go-acme/lego"
|
||||
# version = "2.4.0"
|
||||
version = "2.4.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "google.golang.org/grpc"
|
||||
@@ -205,23 +193,15 @@ required = [
|
||||
|
||||
[[constraint]]
|
||||
name = "k8s.io/client-go"
|
||||
version = "8.0.0" # 8.0.0
|
||||
|
||||
[[constraint]]
|
||||
name = "k8s.io/code-generator"
|
||||
version = "kubernetes-1.11.7"
|
||||
version = "6.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "k8s.io/api"
|
||||
version = "kubernetes-1.11.7" # "kubernetes-1.11.7"
|
||||
version = "kubernetes-1.9.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "k8s.io/apimachinery"
|
||||
version = "kubernetes-1.11.7" # "kubernetes-1.11.7"
|
||||
|
||||
[[override]]
|
||||
name = "github.com/json-iterator/go"
|
||||
version = "1.1.6"
|
||||
version = "kubernetes-1.9.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
@@ -278,5 +258,5 @@ required = [
|
||||
version = "1.7.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/instana/go-sensor"
|
||||
version = "1.4.12"
|
||||
name = "github.com/google/uuid"
|
||||
version = "0.2.0"
|
||||
|
||||
@@ -13,21 +13,7 @@
|
||||
* Nicolas Mengin [@nmengin](https://github.com/nmengin)
|
||||
* Marco Jantke [@marco-jantke](https://github.com/marco-jantke)
|
||||
* Michaël Matur [@mmatur](https://github.com/mmatur)
|
||||
* Gérald Croës [@geraldcroes](https://github.com/geraldcroes)
|
||||
* Jean-Baptiste Doumenjou [@jbdoumenjou](https://github.com/jbdoumenjou)
|
||||
* Damien Duportal [@dduportal](https://github.com/dduportal)
|
||||
|
||||
## Contributions Daily Meeting
|
||||
|
||||
* 3 Maintainers should attend to a Contributions Daily Meeting where we sort and label new issues ([is:issue label:status/0-needs-triage](https://github.com/containous/traefik/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3Astatus%2F0-needs-triage+)), and review every Pull Requests
|
||||
* Every pull request should be checked during the Contributions Daily Meeting
|
||||
* Even if it’s already assigned
|
||||
* Even PR labelled with `contributor/waiting-for-corrections` or `contributor/waiting-for-feedback`
|
||||
* Issues labeled with `priority/P0` and `priority/P1` should be assigned.
|
||||
* Modifying an issue or a pull request (labels, assignees, milestone) is only possible:
|
||||
* During the Contributions Daily Meeting
|
||||
* By an assigned maintainer
|
||||
* In case of emergency, if a change proposal is approved by 2 other maintainers (on Slack, Discord, etc)
|
||||
|
||||
## PR review process:
|
||||
|
||||
@@ -35,16 +21,15 @@
|
||||
* From `1` to `2`: 1 comment that says “design LGTM” (by a senior maintainer).
|
||||
* From `2` to `3`: 3 LGTM approvals by any maintainer.
|
||||
* If needed, a specific maintainer familiar with a particular domain can be requested for the review.
|
||||
* If a PR has been implemented in pair programming, one peer's LGTM goes into the review for free
|
||||
* Amending someone else's pull request is authorized only in emergency, if a rebase is needed, or if the initial contributor is silent
|
||||
|
||||
We use [PRM](https://github.com/ldez/prm) to manage locally pull requests.
|
||||
|
||||
|
||||
## Bots
|
||||
|
||||
### [Myrmica Lobicornis](https://github.com/containous/lobicornis/)
|
||||
|
||||
Update and Merge Pull Request.
|
||||
**Update and Merge Pull Request**
|
||||
|
||||
The maintainer giving the final LGTM must add the `status/3-needs-merge` label to trigger the merge bot.
|
||||
|
||||
@@ -61,39 +46,31 @@ To prevent the bot from automatically merging a PR, add the label `bot/no-merge`
|
||||
The label `bot/light-review` decreases the number of required LGTM from 3 to 1.
|
||||
|
||||
This label is used when:
|
||||
- Updating the vendors from previously reviewed PRs
|
||||
- Merging branches into the master
|
||||
- Preparing the release
|
||||
|
||||
* Updating the vendors from previously reviewed PRs
|
||||
* Merging branches into the master
|
||||
* Preparing the release
|
||||
|
||||
### [Myrmica Bibikoffi](https://github.com/containous/bibikoffi/)
|
||||
|
||||
* closes stale issues [cron]
|
||||
* use some criterion as number of days between creation, last update, labels, ...
|
||||
|
||||
|
||||
### [Myrmica Aloba](https://github.com/containous/aloba)
|
||||
|
||||
Manage GitHub labels.
|
||||
**Manage GitHub labels**
|
||||
|
||||
* Add labels on new PR [GitHub WebHook]
|
||||
* Add milestone to a new PR based on a branch version (1.4, 1.3, ...) [GitHub WebHook]
|
||||
* Add and remove `contributor/waiting-for-corrections` label when a review request changes [GitHub WebHook]
|
||||
* Weekly report of PR status on Slack (CaptainPR) [cron]
|
||||
|
||||
|
||||
## Labels
|
||||
|
||||
A maintainer that looks at an issue/PR must define its `kind/*`, `area/*`, and `status/*`.
|
||||
|
||||
### Status - Workflow
|
||||
|
||||
The `status/*` labels represent the desired state in the workflow.
|
||||
|
||||
* `status/0-needs-triage`: all the new issues and PRs have this status. _[bot only]_
|
||||
* `status/1-needs-design-review`: needs a design review. **(only for PR)**
|
||||
* `status/2-needs-review`: needs a code/documentation review. **(only for PR)**
|
||||
* `status/3-needs-merge`: ready to merge. **(only for PR)**
|
||||
* `status/4-merge-in-progress`: merge is in progress. _[bot only]_
|
||||
|
||||
### Contributor
|
||||
|
||||
* `contributor/need-more-information`: we need more information from the contributor in order to analyze a problem.
|
||||
@@ -106,8 +83,8 @@ The `status/*` labels represent the desired state in the workflow.
|
||||
* `kind/enhancement`: a new or improved feature.
|
||||
* `kind/question`: a question. **(only for issue)**
|
||||
* `kind/proposal`: a proposal that needs to be discussed.
|
||||
* _Proposal issues_ are design proposals
|
||||
* _Proposal PRs_ are technical prototypes that need to be refined with multiple contributors.
|
||||
* _Proposal issues_ are design proposals
|
||||
* _Proposal PRs_ are technical prototypes that need to be refined with multiple contributors.
|
||||
|
||||
* `kind/bug/possible`: a possible bug that needs analysis before it is confirmed or fixed. **(only for issues)**
|
||||
* `kind/bug/confirmed`: a confirmed bug (reproducible). **(only for issues)**
|
||||
@@ -135,7 +112,6 @@ The `status/*` labels represent the desired state in the workflow.
|
||||
* `area/logs`: Logs related.
|
||||
* `area/middleware`: Middleware related.
|
||||
* `area/middleware/metrics`: Metrics related. (Prometheus, StatsD, ...)
|
||||
* `area/middleware/tracing`: Tracing related. (Jaeger, Zipkin, ...)
|
||||
* `area/oxy`: Oxy related.
|
||||
* `area/provider`: related to all providers.
|
||||
* `area/provider/boltdb`: Boltd DB related.
|
||||
@@ -146,30 +122,34 @@ The `status/*` labels represent the desired state in the workflow.
|
||||
* `area/provider/eureka`: Eureka related.
|
||||
* `area/provider/file`: file provider related.
|
||||
* `area/provider/k8s`: Kubernetes related.
|
||||
* `area/provider/kv`: KV related.
|
||||
* `area/provider/marathon`: Marathon related.
|
||||
* `area/provider/mesos`: Mesos related.
|
||||
* `area/provider/rancher`: Rancher related.
|
||||
* `area/provider/servicefabric`: Azure service fabric related.
|
||||
* `area/provider/zk`: Zoo Keeper related.
|
||||
* `area/rules`: Rules related.
|
||||
* `area/server`: Server related.
|
||||
* `area/sticky-session`: Sticky session related.
|
||||
* `area/tls`: TLS related.
|
||||
* `area/websocket`: WebSocket related.
|
||||
* `area/webui`: Web UI related.
|
||||
|
||||
### Issues Priority
|
||||
### Priority
|
||||
|
||||
* `priority/P0`: needs hot fix.
|
||||
* `priority/P1`: need to be fixed in next release.
|
||||
* `priority/P2`: need to be fixed in the future.
|
||||
* `priority/P3`: maybe.
|
||||
* `priority/P0`: needs a hot fix. **(only for issue)**
|
||||
* `priority/P1`: needs to be fixed the next release. **(only for issue)**
|
||||
* `priority/P2`: needs to be fixed in the future. **(only for issue)**
|
||||
* `priority/P3`: maybe. **(only for issue)**
|
||||
|
||||
### PR size
|
||||
|
||||
Automatically set by a bot.
|
||||
* `size/S`: small PR. **(only for PR)** _[bot only]_
|
||||
* `size/M`: medium PR. **(only for PR)** _[bot only]_
|
||||
* `size/L`: large PR. **(only for PR)** _[bot only]_
|
||||
|
||||
* `size/S`: small PR.
|
||||
* `size/M`: medium PR.
|
||||
* `size/L`: Large PR.
|
||||
### Status - Workflow
|
||||
|
||||
The `status/*` labels represent the desired state in the workflow.
|
||||
|
||||
* `status/0-needs-triage`: all the new issues and PRs have this status. _[bot only]_
|
||||
* `status/1-needs-design-review`: needs a design review. **(only for PR)**
|
||||
* `status/2-needs-review`: needs a code/documentation review. **(only for PR)**
|
||||
* `status/3-needs-merge`: ready to merge. **(only for PR)**
|
||||
* `status/4-merge-in-progress`: merge is in progress. _[bot only]_
|
||||
88
Makefile
@@ -1,4 +1,4 @@
|
||||
.PHONY: all docs docs-serve clear-static
|
||||
.PHONY: all docs-verify docs docs-clean docs-build
|
||||
|
||||
TRAEFIK_ENVS := \
|
||||
-e OS_ARCH_ARG \
|
||||
@@ -13,11 +13,6 @@ TRAEFIK_ENVS := \
|
||||
|
||||
SRCS = $(shell git ls-files '*.go' | grep -v '^vendor/')
|
||||
|
||||
TAG_NAME := $(shell git tag -l --contains HEAD)
|
||||
SHA := $(shell git rev-parse HEAD)
|
||||
VERSION_GIT := $(if $(TAG_NAME),$(TAG_NAME),$(SHA))
|
||||
VERSION := $(if $(VERSION),$(VERSION),$(VERSION_GIT))
|
||||
|
||||
BIND_DIR := "dist"
|
||||
TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/containous/traefik/$(BIND_DIR)"
|
||||
|
||||
@@ -26,11 +21,18 @@ TRAEFIK_DEV_IMAGE := traefik-dev$(if $(GIT_BRANCH),:$(subst /,-,$(GIT_BRANCH)))
|
||||
REPONAME := $(shell echo $(REPO) | tr '[:upper:]' '[:lower:]')
|
||||
TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"containous/traefik")
|
||||
INTEGRATION_OPTS := $(if $(MAKE_DOCKER_HOST),-e "DOCKER_HOST=$(MAKE_DOCKER_HOST)", -e "TEST_CONTAINER=1" -v "/var/run/docker.sock:/var/run/docker.sock")
|
||||
TRAEFIK_DOC_IMAGE := traefik-docs
|
||||
TRAEFIK_DOC_VERIFY_IMAGE := $(TRAEFIK_DOC_IMAGE)-verify
|
||||
DOCS_VERIFY_SKIP ?= false
|
||||
|
||||
DOCKER_BUILD_ARGS := $(if $(DOCKER_VERSION), "--build-arg=DOCKER_VERSION=$(DOCKER_VERSION)",)
|
||||
DOCKER_RUN_OPTS := $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)"
|
||||
DOCKER_RUN_TRAEFIK := docker run $(INTEGRATION_OPTS) -it $(DOCKER_RUN_OPTS)
|
||||
DOCKER_RUN_TRAEFIK_NOTTY := docker run $(INTEGRATION_OPTS) -i $(DOCKER_RUN_OPTS)
|
||||
DOCKER_RUN_DOC_PORT := 8000
|
||||
DOCKER_RUN_DOC_MOUNT := -v $(CURDIR):/mkdocs
|
||||
DOCKER_RUN_DOC_OPTS := --rm $(DOCKER_RUN_DOC_MOUNT) -p $(DOCKER_RUN_DOC_PORT):8000
|
||||
|
||||
|
||||
print-%: ; @echo $*=$($*)
|
||||
|
||||
@@ -42,6 +44,13 @@ all: generate-webui build ## validate all checks, build linux binary, run all te
|
||||
binary: generate-webui build ## build the linux binary
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate binary
|
||||
|
||||
crossbinary: generate-webui build ## cross build the non-linux binaries
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate crossbinary
|
||||
|
||||
crossbinary-parallel:
|
||||
$(MAKE) generate-webui
|
||||
$(MAKE) build crossbinary-default crossbinary-others
|
||||
|
||||
crossbinary-default: generate-webui build
|
||||
$(DOCKER_RUN_TRAEFIK_NOTTY) ./script/make.sh generate crossbinary-default
|
||||
|
||||
@@ -49,6 +58,13 @@ crossbinary-default-parallel:
|
||||
$(MAKE) generate-webui
|
||||
$(MAKE) build crossbinary-default
|
||||
|
||||
crossbinary-others: generate-webui build
|
||||
$(DOCKER_RUN_TRAEFIK_NOTTY) ./script/make.sh generate crossbinary-others
|
||||
|
||||
crossbinary-others-parallel:
|
||||
$(MAKE) generate-webui
|
||||
$(MAKE) build crossbinary-others
|
||||
|
||||
test: build ## run the unit and integration tests
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-unit binary test-integration
|
||||
|
||||
@@ -59,12 +75,15 @@ test-integration: build ## run the integration tests
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate binary test-integration
|
||||
TEST_HOST=1 ./script/make.sh test-integration
|
||||
|
||||
validate: build ## validate code, vendor
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate validate-lint validate-misspell validate-vendor
|
||||
validate: build ## validate code, vendor and autogen
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint validate-misspell validate-vendor validate-autogen
|
||||
|
||||
build: dist
|
||||
docker build $(DOCKER_BUILD_ARGS) -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
|
||||
|
||||
build-webui:
|
||||
docker build -t traefik-webui -f webui/Dockerfile webui
|
||||
|
||||
build-no-cache: dist
|
||||
docker build --no-cache -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
|
||||
|
||||
@@ -77,11 +96,30 @@ image-dirty: binary ## build a docker traefik image
|
||||
image: clear-static binary ## clean up static directory and build a docker traefik image
|
||||
docker build -t $(TRAEFIK_IMAGE) .
|
||||
|
||||
docs:
|
||||
make -C ./docs docs
|
||||
docs-image:
|
||||
docker build -t $(TRAEFIK_DOC_IMAGE) -f docs.Dockerfile .
|
||||
|
||||
docs-serve:
|
||||
make -C ./docs docs-serve
|
||||
docs: docs-image
|
||||
docker run $(DOCKER_RUN_DOC_OPTS) $(TRAEFIK_DOC_IMAGE) mkdocs serve
|
||||
|
||||
docs-build: site
|
||||
|
||||
docs-verify: site
|
||||
ifeq ($(DOCS_VERIFY_SKIP),false)
|
||||
docker build -t $(TRAEFIK_DOC_VERIFY_IMAGE) ./script/docs-verify-docker-image
|
||||
docker run --rm -v $(CURDIR):/app $(TRAEFIK_DOC_VERIFY_IMAGE)
|
||||
else
|
||||
@echo "DOCS_LINT_SKIP is true: no linting done."
|
||||
endif
|
||||
|
||||
site: docs-image
|
||||
docker run $(DOCKER_RUN_DOC_OPTS) $(TRAEFIK_DOC_IMAGE) mkdocs build
|
||||
|
||||
docs-clean:
|
||||
rm -rf $(CURDIR)/site
|
||||
|
||||
clear-static:
|
||||
rm -rf static
|
||||
|
||||
dist:
|
||||
mkdir dist
|
||||
@@ -91,31 +129,21 @@ run-dev:
|
||||
go build ./cmd/traefik
|
||||
./traefik
|
||||
|
||||
clear-static:
|
||||
rm -rf static
|
||||
|
||||
build-webui:
|
||||
docker build -t traefik-webui -f webui/Dockerfile webui
|
||||
|
||||
generate-webui: build-webui
|
||||
if [ ! -d "static" ]; then \
|
||||
mkdir -p static; \
|
||||
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui npm run build; \
|
||||
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui chown -R $(shell id -u):$(shell id -g) ../static; \
|
||||
echo 'For more informations show `webui/readme.md`' > $$PWD/static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md; \
|
||||
fi
|
||||
|
||||
generate-crd:
|
||||
./script/update-generated-crd-code.sh
|
||||
|
||||
lint:
|
||||
script/validate-lint
|
||||
script/validate-golint
|
||||
|
||||
fmt:
|
||||
gofmt -s -l -w $(SRCS)
|
||||
|
||||
pull-images:
|
||||
grep --no-filename -E '^\s+image:' ./integration/resources/compose/*.yml | awk '{print $$2}' | sort | uniq | xargs -P 6 -n 1 docker pull
|
||||
grep --no-filename -E '^\s+image:' ./integration/resources/compose/*.yml | awk '{print $$2}' | sort | uniq | xargs -P 6 -n 1 docker pull
|
||||
|
||||
dep-ensure:
|
||||
dep ensure -v
|
||||
@@ -126,15 +154,3 @@ dep-prune:
|
||||
|
||||
help: ## this help
|
||||
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||
|
||||
release-packages: generate-webui build
|
||||
rm -rf dist
|
||||
$(DOCKER_RUN_TRAEFIK_NOTTY) goreleaser release --skip-publish
|
||||
$(DOCKER_RUN_TRAEFIK_NOTTY) tar cfz dist/traefik-${VERSION}.src.tar.gz \
|
||||
--exclude-vcs \
|
||||
--exclude .idea \
|
||||
--exclude .travis \
|
||||
--exclude .semaphoreci \
|
||||
--exclude .github \
|
||||
--exclude dist .
|
||||
$(DOCKER_RUN_TRAEFIK_NOTTY) chown -R $(shell id -u):$(shell id -g) dist/
|
||||
|
||||
21
README.md
@@ -1,6 +1,6 @@
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/content/assets/img/traefik.logo.png" alt="Traefik" title="Traefik" />
|
||||
<img src="docs/img/traefik.logo.png" alt="Traefik" title="Traefik" />
|
||||
</p>
|
||||
|
||||
[](https://semaphoreci.com/containous/traefik)
|
||||
@@ -23,18 +23,18 @@ Pointing Traefik at your orchestrator should be the _only_ configuration step yo
|
||||
**[Supported backends](#supported-backends)** .
|
||||
**[Quickstart](#quickstart)** .
|
||||
**[Web UI](#web-ui)** .
|
||||
**[Test it](#test-it)** .
|
||||
**[Documentation](#documentation)** .
|
||||
|
||||
. **[Support](#support)** .
|
||||
**[Release cycle](#release-cycle)** .
|
||||
**[Contributing](#contributing)** .
|
||||
**[Maintainers](#maintainers)** .
|
||||
**[Plumbing](#plumbing)** .
|
||||
**[Credits](#credits)** .
|
||||
|
||||
---
|
||||
|
||||
:construction: As stated in the [1.7 release note](https://blog.containo.us/traefik-1-7-yet-another-slice-of-awesomeness-2a9c99737889#782d), a significant update is in progress on the [master](https://github.com/containous/traefik/tree/master) branch. This branch will remain in constant evolution and prone to change with little notice, so use it for test purposes only.
|
||||
|
||||
## Overview
|
||||
|
||||
Imagine that you have deployed a bunch of microservices with the help of an orchestrator (like Swarm or Kubernetes) or a service registry (like etcd or consul).
|
||||
@@ -50,7 +50,7 @@ Traefik listens to your service registry/orchestrator API and instantly generate
|
||||
**Run Traefik and let it do the work for you!**
|
||||
_(But if you'd rather configure some of your routes manually, Traefik supports that too!)_
|
||||
|
||||

|
||||

|
||||
|
||||
## Features
|
||||
|
||||
@@ -58,6 +58,7 @@ _(But if you'd rather configure some of your routes manually, Traefik supports t
|
||||
- Supports multiple load balancing algorithms
|
||||
- Provides HTTPS to your microservices by leveraging [Let's Encrypt](https://letsencrypt.org) (wildcard certificates support)
|
||||
- Circuit breakers, retry
|
||||
- High Availability with cluster mode (beta)
|
||||
- See the magic through its clean web UI
|
||||
- Websocket, HTTP/2, GRPC ready
|
||||
- Provides metrics (Rest, Prometheus, Datadog, Statsd, InfluxDB)
|
||||
@@ -94,8 +95,8 @@ If you are looking for a more comprehensive and real use-case example, you can a
|
||||
|
||||
You can access the simple HTML frontend of Traefik.
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -112,13 +113,13 @@ If you need commercial support, please contact [Containo.us](https://containo.us
|
||||
|
||||
## Download
|
||||
|
||||
- Grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
|
||||
- Grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/v1.7/traefik.sample.toml):
|
||||
|
||||
```shell
|
||||
./traefik --configFile=traefik.toml
|
||||
```
|
||||
|
||||
- Or use the official tiny Docker image and run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
|
||||
- Or use the official tiny Docker image and run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/v1.7/traefik.sample.toml):
|
||||
|
||||
```shell
|
||||
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.toml traefik
|
||||
@@ -170,9 +171,9 @@ We use [Semantic Versioning](http://semver.org/)
|
||||
|
||||
## Credits
|
||||
|
||||
Kudos to [Peka](http://peka.byethost11.com/photoblog/) for his awesome work on the logo .
|
||||
Kudos to [Peka](http://peka.byethost11.com/photoblog/) for his awesome work on the logo .
|
||||
|
||||
Traefik's logo is licensed under the Creative Commons 3.0 Attributions license.
|
||||
|
||||
Traefik's logo was inspired by the gopher stickers made by Takuya Ueda (https://twitter.com/tenntenn).
|
||||
The original Go gopher was designed by Renee French (http://reneefrench.blogspot.com/).
|
||||
The original Go gopher was designed by Renee French (http://reneefrench.blogspot.com/).
|
||||
334
acme/account.go
Normal file
@@ -0,0 +1,334 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
acmeprovider "github.com/containous/traefik/provider/acme"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/go-acme/lego/certcrypto"
|
||||
"github.com/go-acme/lego/registration"
|
||||
)
|
||||
|
||||
// Account is used to store lets encrypt registration info
|
||||
type Account struct {
|
||||
Email string
|
||||
Registration *registration.Resource
|
||||
PrivateKey []byte
|
||||
KeyType certcrypto.KeyType
|
||||
DomainsCertificate DomainsCertificates
|
||||
ChallengeCerts map[string]*ChallengeCert
|
||||
HTTPChallenge map[string]map[string][]byte
|
||||
}
|
||||
|
||||
// ChallengeCert stores a challenge certificate
|
||||
type ChallengeCert struct {
|
||||
Certificate []byte
|
||||
PrivateKey []byte
|
||||
certificate *tls.Certificate
|
||||
}
|
||||
|
||||
// Init account struct
|
||||
func (a *Account) Init() error {
|
||||
err := a.DomainsCertificate.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = a.RemoveAccountV1Values()
|
||||
if err != nil {
|
||||
log.Errorf("Unable to remove ACME Account V1 values during account initialization: %v", err)
|
||||
}
|
||||
|
||||
for _, cert := range a.ChallengeCerts {
|
||||
if cert.certificate == nil {
|
||||
certificate, err := tls.X509KeyPair(cert.Certificate, cert.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cert.certificate = &certificate
|
||||
}
|
||||
|
||||
if cert.certificate.Leaf == nil {
|
||||
leaf, err := x509.ParseCertificate(cert.certificate.Certificate[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cert.certificate.Leaf = leaf
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewAccount creates an account
|
||||
func NewAccount(email string, certs []*DomainsCertificate, keyTypeValue string) (*Account, error) {
|
||||
keyType := acmeprovider.GetKeyType(keyTypeValue)
|
||||
|
||||
// Create a user. New accounts need an email and private key to start
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
domainsCerts := DomainsCertificates{Certs: certs}
|
||||
err = domainsCerts.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Account{
|
||||
Email: email,
|
||||
PrivateKey: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||
KeyType: keyType,
|
||||
DomainsCertificate: DomainsCertificates{Certs: domainsCerts.Certs},
|
||||
ChallengeCerts: map[string]*ChallengeCert{}}, nil
|
||||
}
|
||||
|
||||
// GetEmail returns email
|
||||
func (a *Account) GetEmail() string {
|
||||
return a.Email
|
||||
}
|
||||
|
||||
// GetRegistration returns lets encrypt registration resource
|
||||
func (a *Account) GetRegistration() *registration.Resource {
|
||||
return a.Registration
|
||||
}
|
||||
|
||||
// GetPrivateKey returns private key
|
||||
func (a *Account) GetPrivateKey() crypto.PrivateKey {
|
||||
if privateKey, err := x509.ParsePKCS1PrivateKey(a.PrivateKey); err == nil {
|
||||
return privateKey
|
||||
}
|
||||
|
||||
log.Errorf("Cannot unmarshall private key %+v", a.PrivateKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveAccountV1Values removes ACME account V1 values
|
||||
func (a *Account) RemoveAccountV1Values() error {
|
||||
// Check if ACME Account is in ACME V1 format
|
||||
if a.Registration != nil {
|
||||
isOldRegistration, err := regexp.MatchString(acmeprovider.RegistrationURLPathV1Regexp, a.Registration.URI)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isOldRegistration {
|
||||
a.reset()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Account) reset() {
|
||||
log.Debug("Reset ACME account object.")
|
||||
a.Email = ""
|
||||
a.Registration = nil
|
||||
a.PrivateKey = nil
|
||||
}
|
||||
|
||||
// Certificate is used to store certificate info
|
||||
type Certificate struct {
|
||||
Domain string
|
||||
CertURL string
|
||||
CertStableURL string
|
||||
PrivateKey []byte
|
||||
Certificate []byte
|
||||
}
|
||||
|
||||
// DomainsCertificates stores a certificate for multiple domains
|
||||
type DomainsCertificates struct {
|
||||
Certs []*DomainsCertificate
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) Len() int {
|
||||
return len(dc.Certs)
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) Swap(i, j int) {
|
||||
dc.Certs[i], dc.Certs[j] = dc.Certs[j], dc.Certs[i]
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) Less(i, j int) bool {
|
||||
if reflect.DeepEqual(dc.Certs[i].Domains, dc.Certs[j].Domains) {
|
||||
return dc.Certs[i].tlsCert.Leaf.NotAfter.After(dc.Certs[j].tlsCert.Leaf.NotAfter)
|
||||
}
|
||||
|
||||
if dc.Certs[i].Domains.Main == dc.Certs[j].Domains.Main {
|
||||
return strings.Join(dc.Certs[i].Domains.SANs, ",") < strings.Join(dc.Certs[j].Domains.SANs, ",")
|
||||
}
|
||||
|
||||
return dc.Certs[i].Domains.Main < dc.Certs[j].Domains.Main
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) removeDuplicates() {
|
||||
sort.Sort(dc)
|
||||
for i := 0; i < len(dc.Certs); i++ {
|
||||
for i2 := i + 1; i2 < len(dc.Certs); i2++ {
|
||||
if reflect.DeepEqual(dc.Certs[i].Domains, dc.Certs[i2].Domains) {
|
||||
// delete
|
||||
log.Warnf("Remove duplicate cert: %+v, expiration :%s", dc.Certs[i2].Domains, dc.Certs[i2].tlsCert.Leaf.NotAfter.String())
|
||||
dc.Certs = append(dc.Certs[:i2], dc.Certs[i2+1:]...)
|
||||
i2--
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) removeEmpty() {
|
||||
var certs []*DomainsCertificate
|
||||
for _, cert := range dc.Certs {
|
||||
if cert.Certificate != nil && len(cert.Certificate.Certificate) > 0 && len(cert.Certificate.PrivateKey) > 0 {
|
||||
certs = append(certs, cert)
|
||||
}
|
||||
}
|
||||
dc.Certs = certs
|
||||
}
|
||||
|
||||
// Init DomainsCertificates
|
||||
func (dc *DomainsCertificates) Init() error {
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
|
||||
dc.removeEmpty()
|
||||
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
tlsCert, err := tls.X509KeyPair(domainsCertificate.Certificate.Certificate, domainsCertificate.Certificate.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
domainsCertificate.tlsCert = &tlsCert
|
||||
|
||||
if domainsCertificate.tlsCert.Leaf == nil {
|
||||
leaf, err := x509.ParseCertificate(domainsCertificate.tlsCert.Certificate[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
domainsCertificate.tlsCert.Leaf = leaf
|
||||
}
|
||||
}
|
||||
|
||||
dc.removeDuplicates()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) renewCertificates(acmeCert *Certificate, domain types.Domain) error {
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
if reflect.DeepEqual(domain, domainsCertificate.Domains) {
|
||||
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
domainsCertificate.Certificate = acmeCert
|
||||
domainsCertificate.tlsCert = &tlsCert
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("certificate to renew not found for domain %s", domain.Main)
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) addCertificateForDomains(acmeCert *Certificate, domain types.Domain) (*DomainsCertificate, error) {
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
|
||||
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert := DomainsCertificate{Domains: domain, Certificate: acmeCert, tlsCert: &tlsCert}
|
||||
dc.Certs = append(dc.Certs, &cert)
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) getCertificateForDomain(domainToFind string) (*DomainsCertificate, bool) {
|
||||
dc.lock.RLock()
|
||||
defer dc.lock.RUnlock()
|
||||
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
for _, domain := range domainsCertificate.Domains.ToStrArray() {
|
||||
if strings.HasPrefix(domain, "*.") && types.MatchDomain(domainToFind, domain) {
|
||||
return domainsCertificate, true
|
||||
}
|
||||
if domain == domainToFind {
|
||||
return domainsCertificate, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) exists(domainToFind types.Domain) (*DomainsCertificate, bool) {
|
||||
dc.lock.RLock()
|
||||
defer dc.lock.RUnlock()
|
||||
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
if reflect.DeepEqual(domainToFind, domainsCertificate.Domains) {
|
||||
return domainsCertificate, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) toDomainsMap() map[string]*tls.Certificate {
|
||||
domainsCertificatesMap := make(map[string]*tls.Certificate)
|
||||
|
||||
for _, domainCertificate := range dc.Certs {
|
||||
certKey := domainCertificate.Domains.Main
|
||||
|
||||
if domainCertificate.Domains.SANs != nil {
|
||||
sort.Strings(domainCertificate.Domains.SANs)
|
||||
|
||||
for _, dnsName := range domainCertificate.Domains.SANs {
|
||||
if dnsName != domainCertificate.Domains.Main {
|
||||
certKey += fmt.Sprintf(",%s", dnsName)
|
||||
}
|
||||
}
|
||||
}
|
||||
domainsCertificatesMap[certKey] = domainCertificate.tlsCert
|
||||
}
|
||||
return domainsCertificatesMap
|
||||
}
|
||||
|
||||
// DomainsCertificate contains a certificate for multiple domains
|
||||
type DomainsCertificate struct {
|
||||
Domains types.Domain
|
||||
Certificate *Certificate
|
||||
tlsCert *tls.Certificate
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificate) needRenew() bool {
|
||||
for _, c := range dc.tlsCert.Certificate {
|
||||
crt, err := x509.ParseCertificate(c)
|
||||
if err != nil {
|
||||
// If there's an error, we assume the cert is broken, and needs update
|
||||
return true
|
||||
}
|
||||
|
||||
// <= 30 days left, renew certificate
|
||||
if crt.NotAfter.Before(time.Now().Add(24 * 30 * time.Hour)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
835
acme/acme.go
Normal file
@@ -0,0 +1,835 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
fmtlog "log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/staert"
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/log"
|
||||
acmeprovider "github.com/containous/traefik/provider/acme"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/containous/traefik/version"
|
||||
"github.com/eapache/channels"
|
||||
"github.com/go-acme/lego/certificate"
|
||||
"github.com/go-acme/lego/challenge"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/challenge/http01"
|
||||
"github.com/go-acme/lego/lego"
|
||||
legolog "github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/providers/dns"
|
||||
"github.com/go-acme/lego/registration"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
// OSCPMustStaple enables OSCP stapling as from https://github.com/go-acme/lego/issues/270
|
||||
OSCPMustStaple = false
|
||||
)
|
||||
|
||||
// ACME allows to connect to lets encrypt and retrieve certs
|
||||
// Deprecated Please use provider/acme/Provider
|
||||
type ACME struct {
|
||||
Email string `description:"Email address used for registration"`
|
||||
Domains []types.Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
|
||||
Storage string `description:"File or key used for certificates storage."`
|
||||
StorageFile string // Deprecated
|
||||
OnDemand bool `description:"(Deprecated) Enable on demand certificate generation. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."` // Deprecated
|
||||
OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
|
||||
CAServer string `description:"CA server to use."`
|
||||
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
|
||||
KeyType string `description:"KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'. Default to 'RSA4096'"`
|
||||
DNSChallenge *acmeprovider.DNSChallenge `description:"Activate DNS-01 Challenge"`
|
||||
HTTPChallenge *acmeprovider.HTTPChallenge `description:"Activate HTTP-01 Challenge"`
|
||||
TLSChallenge *acmeprovider.TLSChallenge `description:"Activate TLS-ALPN-01 Challenge"`
|
||||
DNSProvider string `description:"(Deprecated) Activate DNS-01 Challenge"` // Deprecated
|
||||
DelayDontCheckDNS flaeg.Duration `description:"(Deprecated) Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."` // Deprecated
|
||||
ACMELogging bool `description:"Enable debug logging of ACME actions."`
|
||||
OverrideCertificates bool `description:"Enable to override certificates in key-value store when using storeconfig"`
|
||||
client *lego.Client
|
||||
store cluster.Store
|
||||
challengeHTTPProvider *challengeHTTPProvider
|
||||
challengeTLSProvider *challengeTLSProvider
|
||||
checkOnDemandDomain func(domain string) bool
|
||||
jobs *channels.InfiniteChannel
|
||||
TLSConfig *tls.Config `description:"TLS config in case wildcard certs are used"`
|
||||
dynamicCerts *safe.Safe
|
||||
resolvingDomains map[string]struct{}
|
||||
resolvingDomainsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (a *ACME) init() error {
|
||||
if a.ACMELogging {
|
||||
legolog.Logger = fmtlog.New(log.WriterLevel(logrus.InfoLevel), "legolog: ", 0)
|
||||
} else {
|
||||
legolog.Logger = fmtlog.New(ioutil.Discard, "", 0)
|
||||
}
|
||||
|
||||
a.jobs = channels.NewInfiniteChannel()
|
||||
|
||||
// Init the currently resolved domain map
|
||||
a.resolvingDomains = make(map[string]struct{})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddRoutes add routes on internal router
|
||||
func (a *ACME) AddRoutes(router *mux.Router) {
|
||||
router.Methods(http.MethodGet).
|
||||
Path(http01.ChallengePath("{token}")).
|
||||
Handler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
if a.challengeHTTPProvider == nil {
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(req)
|
||||
if token, ok := vars["token"]; ok {
|
||||
domain, _, err := net.SplitHostPort(req.Host)
|
||||
if err != nil {
|
||||
log.Debugf("Unable to split host and port: %v. Fallback to request host.", err)
|
||||
domain = req.Host
|
||||
}
|
||||
tokenValue := a.challengeHTTPProvider.getTokenValue(token, domain)
|
||||
if len(tokenValue) > 0 {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
rw.Write(tokenValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
}
|
||||
|
||||
// CreateClusterConfig creates a tls.config using ACME configuration in cluster mode
|
||||
func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tls.Config, certs *safe.Safe, checkOnDemandDomain func(domain string) bool) error {
|
||||
err := a.init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(a.Storage) == 0 {
|
||||
return errors.New("empty Store, please provide a key for certs storage")
|
||||
}
|
||||
|
||||
a.checkOnDemandDomain = checkOnDemandDomain
|
||||
a.dynamicCerts = certs
|
||||
|
||||
tlsConfig.GetCertificate = a.getCertificate
|
||||
a.TLSConfig = tlsConfig
|
||||
|
||||
listener := func(object cluster.Object) error {
|
||||
account := object.(*Account)
|
||||
account.Init()
|
||||
if !leadership.IsLeader() {
|
||||
a.client, err = a.buildACMEClient(account)
|
||||
if err != nil {
|
||||
log.Errorf("Error building ACME client %+v: %s", object, err.Error())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
datastore, err := cluster.NewDataStore(
|
||||
leadership.Pool.Ctx(),
|
||||
staert.KvSource{
|
||||
Store: leadership.Store,
|
||||
Prefix: a.Storage,
|
||||
},
|
||||
&Account{},
|
||||
listener)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.store = datastore
|
||||
a.challengeTLSProvider = &challengeTLSProvider{store: a.store}
|
||||
|
||||
ticker := time.NewTicker(24 * time.Hour)
|
||||
leadership.Pool.AddGoCtx(func(ctx context.Context) {
|
||||
log.Info("Starting ACME renew job...")
|
||||
defer log.Info("Stopped ACME renew job...")
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
a.renewCertificates()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
leadership.AddListener(a.leadershipListener)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ACME) leadershipListener(elected bool) error {
|
||||
if elected {
|
||||
_, err := a.store.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
transaction, object, err := a.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
account := object.(*Account)
|
||||
account.Init()
|
||||
// Reset Account values if caServer changed, thus registration URI can be updated
|
||||
if account != nil && account.Registration != nil && !isAccountMatchingCaServer(account.Registration.URI, a.CAServer) {
|
||||
log.Info("Account URI does not match the current CAServer. The account will be reset")
|
||||
account.reset()
|
||||
}
|
||||
|
||||
var needRegister bool
|
||||
if account == nil || len(account.Email) == 0 {
|
||||
domainsCerts := DomainsCertificates{Certs: []*DomainsCertificate{}}
|
||||
if account != nil {
|
||||
domainsCerts = account.DomainsCertificate
|
||||
}
|
||||
|
||||
account, err = NewAccount(a.Email, domainsCerts.Certs, a.KeyType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
needRegister = true
|
||||
} else if len(account.KeyType) == 0 {
|
||||
// Set the KeyType if not already defined in the account
|
||||
account.KeyType = acmeprovider.GetKeyType(a.KeyType)
|
||||
}
|
||||
|
||||
a.client, err = a.buildACMEClient(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if needRegister {
|
||||
// New users will need to register; be sure to save it
|
||||
log.Debug("Register...")
|
||||
|
||||
reg, err := a.client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
account.Registration = reg
|
||||
}
|
||||
|
||||
err = transaction.Commit(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.retrieveCertificates()
|
||||
a.renewCertificates()
|
||||
a.runJobs()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isAccountMatchingCaServer(accountURI string, serverURI string) bool {
|
||||
aru, err := url.Parse(accountURI)
|
||||
if err != nil {
|
||||
log.Infof("Unable to parse account.Registration URL : %v", err)
|
||||
return false
|
||||
}
|
||||
cau, err := url.Parse(serverURI)
|
||||
if err != nil {
|
||||
log.Infof("Unable to parse CAServer URL : %v", err)
|
||||
return false
|
||||
}
|
||||
return cau.Hostname() == aru.Hostname()
|
||||
}
|
||||
|
||||
func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
domain := types.CanonicalDomain(clientHello.ServerName)
|
||||
account := a.store.Get().(*Account)
|
||||
|
||||
if challengeCert, ok := a.challengeTLSProvider.getCertificate(domain); ok {
|
||||
log.Debugf("ACME got challenge %s", domain)
|
||||
return challengeCert, nil
|
||||
}
|
||||
|
||||
if providedCertificate := a.getProvidedCertificate(domain); providedCertificate != nil {
|
||||
return providedCertificate, nil
|
||||
}
|
||||
|
||||
if domainCert, ok := account.DomainsCertificate.getCertificateForDomain(domain); ok {
|
||||
log.Debugf("ACME got domain cert %s", domain)
|
||||
return domainCert.tlsCert, nil
|
||||
}
|
||||
|
||||
if a.OnDemand {
|
||||
if a.checkOnDemandDomain != nil && !a.checkOnDemandDomain(domain) {
|
||||
return nil, nil
|
||||
}
|
||||
return a.loadCertificateOnDemand(clientHello)
|
||||
}
|
||||
|
||||
log.Debugf("No certificate found or generated for %s", domain)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (a *ACME) retrieveCertificates() {
|
||||
a.jobs.In() <- func() {
|
||||
log.Info("Retrieving ACME certificates...")
|
||||
|
||||
a.deleteUnnecessaryDomains()
|
||||
|
||||
for i := 0; i < len(a.Domains); i++ {
|
||||
domain := a.Domains[i]
|
||||
|
||||
// check if cert isn't already loaded
|
||||
account := a.store.Get().(*Account)
|
||||
if _, exists := account.DomainsCertificate.exists(domain); !exists {
|
||||
var domains []string
|
||||
domains = append(domains, domain.Main)
|
||||
domains = append(domains, domain.SANs...)
|
||||
domains, err := a.getValidDomains(domains, true)
|
||||
if err != nil {
|
||||
log.Errorf("Error validating ACME certificate for domain %q: %s", domains, err)
|
||||
continue
|
||||
}
|
||||
|
||||
certificateResource, err := a.getDomainsCertificates(domains)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting ACME certificate for domain %q: %s", domains, err)
|
||||
continue
|
||||
}
|
||||
|
||||
transaction, object, err := a.store.Begin()
|
||||
if err != nil {
|
||||
log.Errorf("Error creating ACME store transaction from domain %q: %s", domain, err)
|
||||
continue
|
||||
}
|
||||
|
||||
account = object.(*Account)
|
||||
_, err = account.DomainsCertificate.addCertificateForDomains(certificateResource, domain)
|
||||
if err != nil {
|
||||
log.Errorf("Error adding ACME certificate for domain %q: %s", domains, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err = transaction.Commit(account); err != nil {
|
||||
log.Errorf("Error Saving ACME account %+v: %s", account, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Retrieved ACME certificates")
|
||||
}
|
||||
}
|
||||
|
||||
func (a *ACME) renewCertificates() {
|
||||
a.jobs.In() <- func() {
|
||||
log.Info("Testing certificate renew...")
|
||||
account := a.store.Get().(*Account)
|
||||
for _, certificateResource := range account.DomainsCertificate.Certs {
|
||||
if certificateResource.needRenew() {
|
||||
log.Infof("Renewing certificate from LE : %+v", certificateResource.Domains)
|
||||
renewedACMECert, err := a.renewACMECertificate(certificateResource)
|
||||
if err != nil {
|
||||
log.Errorf("Error renewing certificate from LE: %v", err)
|
||||
continue
|
||||
}
|
||||
operation := func() error {
|
||||
return a.storeRenewedCertificate(certificateResource, renewedACMECert)
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Warnf("Renewed certificate storage error: %v, retrying in %s", err, time)
|
||||
}
|
||||
ebo := backoff.NewExponentialBackOff()
|
||||
ebo.MaxElapsedTime = 60 * time.Second
|
||||
err = backoff.RetryNotify(safe.OperationWithRecover(operation), ebo, notify)
|
||||
if err != nil {
|
||||
log.Errorf("Datastore cannot sync: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *ACME) renewACMECertificate(certificateResource *DomainsCertificate) (*Certificate, error) {
|
||||
renewedCert, err := a.client.Certificate.Renew(certificate.Resource{
|
||||
Domain: certificateResource.Certificate.Domain,
|
||||
CertURL: certificateResource.Certificate.CertURL,
|
||||
CertStableURL: certificateResource.Certificate.CertStableURL,
|
||||
PrivateKey: certificateResource.Certificate.PrivateKey,
|
||||
Certificate: certificateResource.Certificate.Certificate,
|
||||
}, true, OSCPMustStaple)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Infof("Renewed certificate from LE: %+v", certificateResource.Domains)
|
||||
return &Certificate{
|
||||
Domain: renewedCert.Domain,
|
||||
CertURL: renewedCert.CertURL,
|
||||
CertStableURL: renewedCert.CertStableURL,
|
||||
PrivateKey: renewedCert.PrivateKey,
|
||||
Certificate: renewedCert.Certificate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *ACME) storeRenewedCertificate(certificateResource *DomainsCertificate, renewedACMECert *Certificate) error {
|
||||
transaction, object, err := a.store.Begin()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error during transaction initialization for renewing certificate: %v", err)
|
||||
}
|
||||
|
||||
log.Infof("Renewing certificate in data store : %+v ", certificateResource.Domains)
|
||||
account := object.(*Account)
|
||||
err = account.DomainsCertificate.renewCertificates(renewedACMECert, certificateResource.Domains)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error renewing certificate in datastore: %v ", err)
|
||||
}
|
||||
|
||||
log.Infof("Commit certificate renewed in data store : %+v", certificateResource.Domains)
|
||||
if err = transaction.Commit(account); err != nil {
|
||||
return fmt.Errorf("error saving ACME account %+v: %v", account, err)
|
||||
}
|
||||
|
||||
oldAccount := a.store.Get().(*Account)
|
||||
for _, oldCertificateResource := range oldAccount.DomainsCertificate.Certs {
|
||||
if oldCertificateResource.Domains.Main == certificateResource.Domains.Main && strings.Join(oldCertificateResource.Domains.SANs, ",") == strings.Join(certificateResource.Domains.SANs, ",") && certificateResource.Certificate != renewedACMECert {
|
||||
return fmt.Errorf("renewed certificate not stored: %+v", certificateResource.Domains)
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("Certificate successfully renewed in data store: %+v", certificateResource.Domains)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ACME) buildACMEClient(account *Account) (*lego.Client, error) {
|
||||
log.Debug("Building ACME client...")
|
||||
caServer := "https://acme-v02.api.letsencrypt.org/directory"
|
||||
if len(a.CAServer) > 0 {
|
||||
caServer = a.CAServer
|
||||
}
|
||||
|
||||
config := lego.NewConfig(account)
|
||||
config.CADirURL = caServer
|
||||
config.Certificate.KeyType = account.KeyType
|
||||
config.UserAgent = fmt.Sprintf("containous-traefik/%s", version.Version)
|
||||
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// DNS challenge
|
||||
if a.DNSChallenge != nil && len(a.DNSChallenge.Provider) > 0 {
|
||||
log.Debugf("Using DNS Challenge provider: %s", a.DNSChallenge.Provider)
|
||||
|
||||
var provider challenge.Provider
|
||||
provider, err = dns.NewDNSChallengeProviderByName(a.DNSChallenge.Provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = client.Challenge.SetDNS01Provider(provider,
|
||||
dns01.CondOption(len(a.DNSChallenge.Resolvers) > 0, dns01.AddRecursiveNameservers(a.DNSChallenge.Resolvers)),
|
||||
dns01.CondOption(a.DNSChallenge.DisablePropagationCheck || a.DNSChallenge.DelayBeforeCheck > 0,
|
||||
dns01.AddPreCheck(func(_, _ string) (bool, error) {
|
||||
if a.DNSChallenge.DelayBeforeCheck > 0 {
|
||||
log.Debugf("Delaying %d rather than validating DNS propagation now.", a.DNSChallenge.DelayBeforeCheck)
|
||||
time.Sleep(time.Duration(a.DNSChallenge.DelayBeforeCheck))
|
||||
}
|
||||
return true, nil
|
||||
})),
|
||||
)
|
||||
return client, err
|
||||
}
|
||||
|
||||
// HTTP challenge
|
||||
if a.HTTPChallenge != nil && len(a.HTTPChallenge.EntryPoint) > 0 {
|
||||
log.Debug("Using HTTP Challenge provider.")
|
||||
|
||||
a.challengeHTTPProvider = &challengeHTTPProvider{store: a.store}
|
||||
err = client.Challenge.SetHTTP01Provider(a.challengeHTTPProvider)
|
||||
return client, err
|
||||
}
|
||||
|
||||
// TLS Challenge
|
||||
if a.TLSChallenge != nil {
|
||||
log.Debug("Using TLS Challenge provider.")
|
||||
|
||||
err = client.Challenge.SetTLSALPN01Provider(a.challengeTLSProvider)
|
||||
return client, err
|
||||
}
|
||||
|
||||
return nil, errors.New("ACME challenge not specified, please select TLS or HTTP or DNS Challenge")
|
||||
}
|
||||
|
||||
func (a *ACME) loadCertificateOnDemand(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
domain := types.CanonicalDomain(clientHello.ServerName)
|
||||
account := a.store.Get().(*Account)
|
||||
if certificateResource, ok := account.DomainsCertificate.getCertificateForDomain(domain); ok {
|
||||
return certificateResource.tlsCert, nil
|
||||
}
|
||||
certificate, err := a.getDomainsCertificates([]string{domain})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("Got certificate on demand for domain %s", domain)
|
||||
|
||||
transaction, object, err := a.store.Begin()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
account = object.(*Account)
|
||||
cert, err := account.DomainsCertificate.addCertificateForDomains(certificate, types.Domain{Main: domain})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = transaction.Commit(account); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cert.tlsCert, nil
|
||||
}
|
||||
|
||||
// LoadCertificateForDomains loads certificates from ACME for given domains
|
||||
func (a *ACME) LoadCertificateForDomains(domains []string) {
|
||||
a.jobs.In() <- func() {
|
||||
log.Debugf("LoadCertificateForDomains %v...", domains)
|
||||
|
||||
domains, err := a.getValidDomains(domains, false)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting valid domain: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
operation := func() error {
|
||||
if a.client == nil {
|
||||
return errors.New("ACME client still not built")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Error getting ACME client: %v, retrying in %s", err, time)
|
||||
}
|
||||
ebo := backoff.NewExponentialBackOff()
|
||||
ebo.MaxElapsedTime = 30 * time.Second
|
||||
err = backoff.RetryNotify(safe.OperationWithRecover(operation), ebo, notify)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting ACME client: %v", err)
|
||||
return
|
||||
}
|
||||
account := a.store.Get().(*Account)
|
||||
|
||||
// Check provided certificates
|
||||
uncheckedDomains := a.getUncheckedDomains(domains, account)
|
||||
if len(uncheckedDomains) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
a.addResolvingDomains(uncheckedDomains)
|
||||
defer a.removeResolvingDomains(uncheckedDomains)
|
||||
|
||||
cert, err := a.getDomainsCertificates(uncheckedDomains)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting ACME certificates %+v : %v", uncheckedDomains, err)
|
||||
return
|
||||
}
|
||||
log.Debugf("Got certificate for domains %+v", uncheckedDomains)
|
||||
transaction, object, err := a.store.Begin()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Error creating transaction %+v : %v", uncheckedDomains, err)
|
||||
return
|
||||
}
|
||||
var domain types.Domain
|
||||
if len(uncheckedDomains) > 1 {
|
||||
domain = types.Domain{Main: uncheckedDomains[0], SANs: uncheckedDomains[1:]}
|
||||
} else {
|
||||
domain = types.Domain{Main: uncheckedDomains[0]}
|
||||
}
|
||||
account = object.(*Account)
|
||||
_, err = account.DomainsCertificate.addCertificateForDomains(cert, domain)
|
||||
if err != nil {
|
||||
log.Errorf("Error adding ACME certificates %+v : %v", uncheckedDomains, err)
|
||||
return
|
||||
}
|
||||
if err = transaction.Commit(account); err != nil {
|
||||
log.Errorf("Error Saving ACME account %+v: %v", account, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *ACME) addResolvingDomains(resolvingDomains []string) {
|
||||
a.resolvingDomainsMutex.Lock()
|
||||
defer a.resolvingDomainsMutex.Unlock()
|
||||
|
||||
for _, domain := range resolvingDomains {
|
||||
a.resolvingDomains[domain] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *ACME) removeResolvingDomains(resolvingDomains []string) {
|
||||
a.resolvingDomainsMutex.Lock()
|
||||
defer a.resolvingDomainsMutex.Unlock()
|
||||
|
||||
for _, domain := range resolvingDomains {
|
||||
delete(a.resolvingDomains, domain)
|
||||
}
|
||||
}
|
||||
|
||||
// Get provided certificate which check a domains list (Main and SANs)
|
||||
// from static and dynamic provided certificates
|
||||
func (a *ACME) getProvidedCertificate(domains string) *tls.Certificate {
|
||||
log.Debugf("Looking for provided certificate to validate %s...", domains)
|
||||
cert := searchProvidedCertificateForDomains(domains, a.TLSConfig.NameToCertificate)
|
||||
if cert == nil && a.dynamicCerts != nil && a.dynamicCerts.Get() != nil {
|
||||
cert = searchProvidedCertificateForDomains(domains, a.dynamicCerts.Get().(map[string]*tls.Certificate))
|
||||
}
|
||||
if cert == nil {
|
||||
log.Debugf("No provided certificate found for domains %s, get ACME certificate.", domains)
|
||||
}
|
||||
return cert
|
||||
}
|
||||
|
||||
func searchProvidedCertificateForDomains(domain string, certs map[string]*tls.Certificate) *tls.Certificate {
|
||||
// Use regex to test for provided certs that might have been added into TLSConfig
|
||||
for certDomains := range certs {
|
||||
domainChecked := false
|
||||
for _, certDomain := range strings.Split(certDomains, ",") {
|
||||
domainChecked = types.MatchDomain(domain, certDomain)
|
||||
if domainChecked {
|
||||
break
|
||||
}
|
||||
}
|
||||
if domainChecked {
|
||||
log.Debugf("Domain %q checked by provided certificate %q", domain, certDomains)
|
||||
return certs[certDomains]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get provided certificate which check a domains list (Main and SANs)
|
||||
// from static and dynamic provided certificates
|
||||
func (a *ACME) getUncheckedDomains(domains []string, account *Account) []string {
|
||||
a.resolvingDomainsMutex.RLock()
|
||||
defer a.resolvingDomainsMutex.RUnlock()
|
||||
|
||||
log.Debugf("Looking for provided certificate to validate %s...", domains)
|
||||
allCerts := make(map[string]*tls.Certificate)
|
||||
|
||||
// Get static certificates
|
||||
for domains, certificate := range a.TLSConfig.NameToCertificate {
|
||||
allCerts[domains] = certificate
|
||||
}
|
||||
|
||||
// Get dynamic certificates
|
||||
if a.dynamicCerts != nil && a.dynamicCerts.Get() != nil {
|
||||
for domains, certificate := range a.dynamicCerts.Get().(map[string]*tls.Certificate) {
|
||||
allCerts[domains] = certificate
|
||||
}
|
||||
}
|
||||
|
||||
// Get ACME certificates
|
||||
if account != nil {
|
||||
for domains, certificate := range account.DomainsCertificate.toDomainsMap() {
|
||||
allCerts[domains] = certificate
|
||||
}
|
||||
}
|
||||
|
||||
// Get currently resolved domains
|
||||
for domain := range a.resolvingDomains {
|
||||
if _, ok := allCerts[domain]; !ok {
|
||||
allCerts[domain] = &tls.Certificate{}
|
||||
}
|
||||
}
|
||||
|
||||
// Get Configuration Domains
|
||||
for i := 0; i < len(a.Domains); i++ {
|
||||
allCerts[a.Domains[i].Main] = &tls.Certificate{}
|
||||
for _, san := range a.Domains[i].SANs {
|
||||
allCerts[san] = &tls.Certificate{}
|
||||
}
|
||||
}
|
||||
|
||||
return searchUncheckedDomains(domains, allCerts)
|
||||
}
|
||||
|
||||
func searchUncheckedDomains(domains []string, certs map[string]*tls.Certificate) []string {
|
||||
var uncheckedDomains []string
|
||||
for _, domainToCheck := range domains {
|
||||
if !isDomainAlreadyChecked(domainToCheck, certs) {
|
||||
uncheckedDomains = append(uncheckedDomains, domainToCheck)
|
||||
}
|
||||
}
|
||||
|
||||
if len(uncheckedDomains) == 0 {
|
||||
log.Debugf("No ACME certificate to generate for domains %q.", domains)
|
||||
} else {
|
||||
log.Debugf("Domains %q need ACME certificates generation for domains %q.", domains, strings.Join(uncheckedDomains, ","))
|
||||
}
|
||||
return uncheckedDomains
|
||||
}
|
||||
|
||||
func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) {
|
||||
var cleanDomains []string
|
||||
for _, domain := range domains {
|
||||
canonicalDomain := types.CanonicalDomain(domain)
|
||||
cleanDomain := dns01.UnFqdn(canonicalDomain)
|
||||
if canonicalDomain != cleanDomain {
|
||||
log.Warnf("FQDN detected, please remove the trailing dot: %s", canonicalDomain)
|
||||
}
|
||||
cleanDomains = append(cleanDomains, cleanDomain)
|
||||
}
|
||||
|
||||
log.Debugf("Loading ACME certificates %s...", cleanDomains)
|
||||
bundle := true
|
||||
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: cleanDomains,
|
||||
Bundle: bundle,
|
||||
MustStaple: OSCPMustStaple,
|
||||
}
|
||||
|
||||
cert, err := a.client.Certificate.Obtain(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot obtain certificates: %+v", err)
|
||||
}
|
||||
|
||||
log.Debugf("Loaded ACME certificates %s", cleanDomains)
|
||||
return &Certificate{
|
||||
Domain: cert.Domain,
|
||||
CertURL: cert.CertURL,
|
||||
CertStableURL: cert.CertStableURL,
|
||||
PrivateKey: cert.PrivateKey,
|
||||
Certificate: cert.Certificate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *ACME) runJobs() {
|
||||
safe.Go(func() {
|
||||
for job := range a.jobs.Out() {
|
||||
function := job.(func())
|
||||
function()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// getValidDomains checks if given domain is allowed to generate a ACME certificate and return it
|
||||
func (a *ACME) getValidDomains(domains []string, wildcardAllowed bool) ([]string, error) {
|
||||
// Check if the domains array is empty or contains only one empty value
|
||||
if len(domains) == 0 || (len(domains) == 1 && len(domains[0]) == 0) {
|
||||
return nil, errors.New("unable to generate a certificate when no domain is given")
|
||||
}
|
||||
|
||||
if strings.HasPrefix(domains[0], "*") {
|
||||
if !wildcardAllowed {
|
||||
return nil, fmt.Errorf("unable to generate a wildcard certificate for domain %q from a 'Host' rule", strings.Join(domains, ","))
|
||||
}
|
||||
|
||||
if a.DNSChallenge == nil && len(a.DNSProvider) == 0 {
|
||||
return nil, fmt.Errorf("unable to generate a wildcard certificate for domain %q : ACME needs a DNSChallenge", strings.Join(domains, ","))
|
||||
}
|
||||
if strings.HasPrefix(domains[0], "*.*") {
|
||||
return nil, fmt.Errorf("unable to generate a wildcard certificate for domain %q : ACME does not allow '*.*' wildcard domain", strings.Join(domains, ","))
|
||||
}
|
||||
}
|
||||
for _, san := range domains[1:] {
|
||||
if strings.HasPrefix(san, "*") {
|
||||
return nil, fmt.Errorf("unable to generate a certificate for domains %q: SANs can not be a wildcard domain", strings.Join(domains, ","))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
domains = fun.Map(types.CanonicalDomain, domains).([]string)
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
func isDomainAlreadyChecked(domainToCheck string, existentDomains map[string]*tls.Certificate) bool {
|
||||
for certDomains := range existentDomains {
|
||||
for _, certDomain := range strings.Split(certDomains, ",") {
|
||||
if types.MatchDomain(domainToCheck, certDomain) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// deleteUnnecessaryDomains deletes from the configuration :
|
||||
// - Duplicated domains
|
||||
// - Domains which are checked by wildcard domain
|
||||
func (a *ACME) deleteUnnecessaryDomains() {
|
||||
var newDomains []types.Domain
|
||||
|
||||
for idxDomainToCheck, domainToCheck := range a.Domains {
|
||||
keepDomain := true
|
||||
|
||||
for idxDomain, domain := range a.Domains {
|
||||
if idxDomainToCheck == idxDomain {
|
||||
continue
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(domain, domainToCheck) {
|
||||
if idxDomainToCheck > idxDomain {
|
||||
log.Warnf("The domain %v is duplicated in the configuration but will be process by ACME only once.", domainToCheck)
|
||||
keepDomain = false
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
var newDomainsToCheck []string
|
||||
|
||||
// Check if domains can be validated by the wildcard domain
|
||||
domainsMap := make(map[string]*tls.Certificate)
|
||||
domainsMap[domain.Main] = &tls.Certificate{}
|
||||
if len(domain.SANs) > 0 {
|
||||
domainsMap[strings.Join(domain.SANs, ",")] = &tls.Certificate{}
|
||||
}
|
||||
|
||||
for _, domainProcessed := range domainToCheck.ToStrArray() {
|
||||
if idxDomain < idxDomainToCheck && isDomainAlreadyChecked(domainProcessed, domainsMap) {
|
||||
// The domain is duplicated in a CN
|
||||
log.Warnf("Domain %q is duplicated in the configuration or validated by the domain %v. It will be processed once.", domainProcessed, domain)
|
||||
continue
|
||||
} else if domain.Main != domainProcessed && strings.HasPrefix(domain.Main, "*") && types.MatchDomain(domainProcessed, domain.Main) {
|
||||
// Check if a wildcard can validate the domain
|
||||
log.Warnf("Domain %q will not be processed by ACME provider because it is validated by the wildcard %q", domainProcessed, domain.Main)
|
||||
continue
|
||||
}
|
||||
newDomainsToCheck = append(newDomainsToCheck, domainProcessed)
|
||||
}
|
||||
|
||||
// Delete the domain if both Main and SANs can be validated by the wildcard domain
|
||||
// otherwise keep the unchecked values
|
||||
if newDomainsToCheck == nil {
|
||||
keepDomain = false
|
||||
break
|
||||
}
|
||||
domainToCheck.Set(newDomainsToCheck)
|
||||
}
|
||||
|
||||
if keepDomain {
|
||||
newDomains = append(newDomains, domainToCheck)
|
||||
}
|
||||
}
|
||||
|
||||
a.Domains = newDomains
|
||||
}
|
||||
43
acme/acme_example.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"Email": "test@traefik.io",
|
||||
"Registration": {
|
||||
"body": {
|
||||
"resource": "reg",
|
||||
"id": 3,
|
||||
"key": {
|
||||
"kty": "RSA",
|
||||
"n": "y5a71suIqvEtovDmDVQ3SSNagk5IVCFI_TvqWpEXSrdbcDE2C-PTEtEUJuLkYwygcpiWYbPmXgdS628vQCw5Uo4DeDyHiuysJOWBLaWow3p9goOdhnPbGBq0liIR9xXyRoctdipVk8UiO9scWsu4jMBM3sMr7_yBWPfYYiLEQmZGFO3iE7Oqr55h_kncHIj5lUQY1j_jkftqxlxUB5_0quyJ7l915j5QY--eY7h4GEhRvx0TlUpi-CnRtRblGeDDDilXZD6bQN2962WdKecsmRaYx-ttLz6jCPXz2VDJRWNcIS501ne2Zh3hzw_DS6IRd2GIia1Wg4sisi9epC9sumXPHi6xzR6-_i_nsFjdtTkUcV8HmorOYoc820KQVZaLScxa8e7-ixpOd6mr6AIbEf7dBAkb9f_iK3GwpqKD8yNcaj1EQgNSyJSjnKSulXI_GwkGnuXe00Qpb1a8ha5Z8yWg7XmZZnJyAZrmK60RfwRNQ1rO5ioerNUBJ2KYTYNzVjBdob9Ug6Cjh4bEKNNjqcbjQ50_Z97Vw40xzpDQ_fYllc6n92eSuv6olxFJTmK7EhHuanDzITngaqei3zL9RwQ7P-1jfEZ03qmGrQYYqXcsS46PQ8cE-frzY2mKp16pRNCG7-03gKVGV0JHyW1aYbevNUk7OumCAXhC2YOigBk",
|
||||
"e": "AQAB"
|
||||
},
|
||||
"contact": [
|
||||
"mailto:test@traefik.io"
|
||||
],
|
||||
"agreement": "http://boulder:4000/terms/v1"
|
||||
},
|
||||
"uri": "http://127.0.0.1:4000/acme/reg/3",
|
||||
"new_authzr_uri": "http://127.0.0.1:4000/acme/new-authz",
|
||||
"terms_of_service": "http://boulder:4000/terms/v1"
|
||||
},
|
||||
"PrivateKey": "MIIJJwIBAAKCAgEAy5a71suIqvEtovDmDVQ3SSNagk5IVCFI/TvqWpEXSrdbcDE2C+PTEtEUJuLkYwygcpiWYbPmXgdS628vQCw5Uo4DeDyHiuysJOWBLaWow3p9goOdhnPbGBq0liIR9xXyRoctdipVk8UiO9scWsu4jMBM3sMr7/yBWPfYYiLEQmZGFO3iE7Oqr55h/kncHIj5lUQY1j/jkftqxlxUB5/0quyJ7l915j5QY++eY7h4GEhRvx0TlUpi+CnRtRblGeDDDilXZD6bQN2962WdKecsmRaYx+ttLz6jCPXz2VDJRWNcIS501ne2Zh3hzw/DS6IRd2GIia1Wg4sisi9epC9sumXPHi6xzR6+/i/nsFjdtTkUcV8HmorOYoc820KQVZaLScxa8e7+ixpOd6mr6AIbEf7dBAkb9f/iK3GwpqKD8yNcaj1EQgNSyJSjnKSulXI/GwkGnuXe00Qpb1a8ha5Z8yWg7XmZZnJyAZrmK60RfwRNQ1rO5ioerNUBJ2KYTYNzVjBdob9Ug6Cjh4bEKNNjqcbjQ50/Z97Vw40xzpDQ/fYllc6n92eSuv6olxFJTmK7EhHuanDzITngaqei3zL9RwQ7P+1jfEZ03qmGrQYYqXcsS46PQ8cE+frzY2mKp16pRNCG7+03gKVGV0JHyW1aYbevNUk7OumCAXhC2YOigBkCAwEAAQKCAgA8XW1EuwTC6tAFSDhuK1JZNUpY6K05hMUHkQRj5jFpzgQmt/C2hc7H/YZkIVJmrA/G6sdsINNlffZwKH9yH6q/d6w/snLeFl7UcdhjmIL5sxAT6sKCY0fLVd/FxERfZvp3Pw2Tw+mr7v+/j7BQm6cU1M/2HRiiB9SydIqMTpKyvXB6NC6ceOFbQTL9GxlQvKyEPbS/kiH/3vRB7I5d1GfPZmNfcp6ark9X0my8VK4HRSo36H8t/OhrfLrZXvh/O82aHVf0OTv/d8AgU/jNu+XVXoXegUfWglQFDChJf1KuaE+g5w1tqgFDNgkGRD475soXA6xgZi0Iw/B9tN3zALzT4IiAW1q72feeTgKOMA2zGtKXxQZZSOV+DuWFZNz/tT7XqGQThqxM09CHv2WGOe80vobtegXYTUt90hysrqIZmBW5XYdzQlJh1KWTtfCaTrWd47kbGvhkEPc8aA3Ji4/AqfkVXiqwaLu+MSlgzPpRj7U7UAIDqnpZjgttgLp74Ujnk3bTaUzdyyNqYDBG3IFGr/Sv+2GQDAUn/PYRJKWr0BteqOzX9zvW3zY8g9CYVXfK/AW3RMWLV8ly6vH/gWqa9gEuzRNRlzjUU6/HCVbUx3UT8RMWH2TQ0uuQZr5JX1iTwjeeT0dEIly1NnRQC92wcrE4UUTBEF3krGVpDBf0AQKCAQEA4jB8w+2fwzbF8X+gCODcY7sTeJRunzGy+jbdaLkcThuylga+6W3ZgWx0BD30ql9K2mouCVu86fCTnBeXXEC3QoTdgw/EzJ83+4JU3QSDdzs9Ta9vLHyvrpUkQfZ8UZpeLLmFsmsBMbBbnfw0S1TzXDsgrAc+G4tia8nO/Iqu75kEMGzmHQAvmN3iSqc1aTS4qumbB19g+v+csq9NEht4F9jt39KotG+OD3MxCxtMu7vxAkJRjFFcgcbb2Rtqe/kQEKA1vLEAJg27lV4k8XibCSerVUR6IzT8WZHrNiXmpRguTLl2k8uFUdCOOx6aLGyRVJ6+8SgIsMR540vnxwQzEQKCAQEA5mu2wtWT19mvXopC3easPsXIPzc5oaRkqfWZYT1KHcVQ7NIXsE3vCjcf/3igZ8l/FVQ4G4fpk/GoTqlpV5Aq/JHCpVOR2O69uB+W4kWgliejpHvF9gszzAYnC8lIXqDbWiinBhmm3ii8sDGAoBaSDw5NMUq3mI+nd8zZ+jx1bLBczDafmQ0YKr8k0YaROxIgoBgDOQDdSqG387lwzpza2DKI5Al3HfS42zjT0RmBahPiuT2aEoUZmIYuvFY0fEjfkpbdvLyexHfZCILRUGlG1nAwASFg86lp+mFSBJ3E3cvbP0CpbFGxon5u4Ao3/7htoOh6huh7MQ91h41fv1hsiQKCAQAe7WRR4e7jYVzlbX7zV9Oqq0y5QwpxJ/mB7viNNiphn7Xmf5uhDU0dPjgK0HHgzdDNVpFe5DVLg4KbaDpg+dRU+xfSsNhG5kpgUGzMH67eIbJ7Kc64tX/MDkZ74nkTK1lPIjrer3TlV2jfjDmWR1JTPR51hzP9ziwx8tEjhM7woeqJuIoqUvkvHL+xV3WdIgFSFUkGVAtNpp/FauTN4gWktRupbAN3UH2LLUP6ccwnK0aD+Y9u8T0F3av33qDLvL1umIlgeI89pMkOXmYMwmHoeY0axpcwszECCkqwB7SmxEyoXv+Qq9ZZ3ntkKAYKpvmkKWSQUtoFWYgVBS727mMRAoIBABLdwusU/bPwuPEutObiWjwRiaHTbb6UbUGVQGe70vO5EjUxxorC9s2JUe9i+w9EakleyfFHIZLheHxoVp26yio/7QYIX6q5cYM/4uTH+qwQts9i6wSISkdsQYovguNsnEk3huVy+Dy8bSaoBvYUowTkkOF2Uq4FJRskBLz+ckbh8dcuqcaoUdA+Mk+NixqhE1bIYIssTPItZ5hnGJtyMGD/UkIJnF0ximk4r+8w/W2oDypHpvPZPg1E/1KgZE/Az7166NDpSL6haX3O6ECDPi+Uo/mTuBJ7TpgXm9WQ7WuTo3H8Y2LhFYBOhdmGPKuNeDxyjIW7R0rvDxp4MtzB6rECggEAJIl7/qp1lxUQPQJRTsEYBkOtdRw0IGG1Rcj0emhHaBN05c9opCy+Osb7mVeU5ZiULe5kD02phL+36pEumprz7QzN46Y5pZc8AQ2W/QkeL4Wo9U9QzczvQQzc1EqrBkzvQTZtBhn4DRzz0IuTn1beVyHtBZeNpBFgMQFv9VYQuUNwFoTOkkQrBRnYbXH6KEnhF3c/1Hzi4KHVdHdfZ3LH7KFQJ34xio0q2tWQSQYeybmwOXdd9sxpz/Y4KBS9fqm7UrwnPK8yuOc05HLEaws+1iam5YyJprlQo3mGKe0wRztwn44HDeQr70LlFm0lzigVAv0hSiWO1Q5hJL7nDu8m/Q==",
|
||||
"DomainsCertificate": {
|
||||
"Certs": [
|
||||
{
|
||||
"Domains": {
|
||||
"Main": "local1.com",
|
||||
"SANs": [
|
||||
"test1.local1.com",
|
||||
"test2.local1.com"
|
||||
]
|
||||
},
|
||||
"Certificate": {
|
||||
"Domain": "local1.com",
|
||||
"CertURL": "http://127.0.0.1:4000/acme/cert/ffc4f3f14def9ee6ec6a0522b5c0baa3379d",
|
||||
"CertStableURL": "",
|
||||
"PrivateKey": "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS1FJQkFBS0NBZ0VBdVNoTTR4enF6cE5YcFNaNnAvZnQrRmt5VmgyK1BSZXJUelV0OERRSng2UkVjQS9FCnN2RnNIVmNOSkZMS2twYTNlOEd3SUZBakJQNnJPK3hoR1JjWlJrdENON1gyOW5LZFhGbHZkYzJxd0hyTFF5WWkKTTB3ODhTck41VERiNi96TWU2dTB0dERiYWtDbDd6ZEJKUXJ6a1h5ZU1MeVkzTUs3aVkrMHpwL2JqMVhvbk5DdQpaQStkZ3hsMVNrV01DVUYvQk9HNWFyT1hwb0x4S0dQWGdzV3hOTVNLVmJKSHczL3ZqNTViZU92Um5lT3BNWlhvCmMwOWpZT3VBakNka1Z5czBSWHJLNWNCRDRMbVRXdnN4MFdTK2VMVHlGTTdQTHVZM3lEWkNNWEhjVmlqRHhnbFMKYjB1ZVRQcGFUWEQwYkxqZ0RNOUVEdE15ZEJzMUNPWlpPWG9ickN5Q2I1eWxTOFdVd1NzVXM1UldxZnlVbnAvcgpSNGx2c2RZOWRVZjRPdkNMVnJvWWk5NWFGc1Zxa0xLOExuL0Eyc3kxYWlDTnR4RmpKOXRXbWU0V0NhdzRoU0YvCkR4NWVNNWNYR2JSYXduVlZJQlZXeHhzNTBPMFJlUWRvbXBQZEFNS1RDWk9SRmxYaDdOWTdxQVdWRGtpdzhyam8Kekd3Ni9XdjlOR3hTNTliKzc0YVAxcjBxOTZ2RS9Rdi8zTCtjbjhiN0lBLytPYmFKdzhIT3RGbXc4RjBxQkN3MAprYWVVSloxb1JueGFYQUo4RHhHREpFOVdNUzh0QmJtVm16YkxoRkMzeDdVc0xGeTBrSzh1SFBFT3dQb2NKNUFUCkE1UHBvclNEMmFleHA0Z3VqYVp5c1JManpmY0dnaTdva0JFNlZVNWVqRE1iYS9lNERQNEJQUVg5VmtVQ0F3RUEKQVFLQ0FnQmZjMWdYcUp1ZmZMT3REcVlpbXh4UmIrSVVKT2NpWldaSndmZDVvY244NGtEcHFDZFZ2RUZvNnF4NgpzamQ5MURhb2xOUHdCSC9aSGxRMTR3aTNQNEluQzdzS0wwTXVEeTN5SXFUa0RPOWVwSzdPWWdVMWZyTFgvS0lCCjZlc2x2Ny9HYldFTzhhSjdKdktqM0U4NEFtcEg4UDgzenJIYTlJUnJTT3NEcmNNcEpEZHpSOXp1OW1IVDZMYmYKWC9UdC9KYTNkSW42YUxUZ0FSYkRKSjAvN0J3TFFOcXpqT0dUOWdzUWRhbGdMK2x5eEo4L1ViRndhRmVwNmgzdApvbzBHcHQ0ZWgwdTdueDhlNVd3Q2RnWmJsTnpnS3grMC9Gd3dLRHhQZVRFc2ZpOEJONmlkR2NjbVdzd3prTWdtCnJmbERaeGNSWTNRSlZIVHBCL0dTTWZXRFBPQ3dRdGltQk1WN3kxM2hPMTdPWXpSNDBMZnpUalJBbmtna2V2eWYKcFowb3dLR3o4QS9haHhRWWJmYVQ5VEhXV0wrYUpYeUhFanBKckp5aTg3UExVbzhsOFVydU56MDRWNXpLOFJPbgo2cG9EWmVtbm1EYWRlU09pK3hZRWlGT1NwSXNWbzlpcm9jUGFKN2YzYWpiNUU4RHpuN1o1MmhzL2R6akpLcFZJCm5mVDFkUU9SZEowSXRUNlRlQ2RTL0dpS25IS1RtNjR2T21IbmlJcm8rUGRhUmFjV0IrTUJ0VytRd0cyUStyRGkKc3g4NlpQbHRpTVpLMDZ5TVlyVHZUdGk2aFVGaUY5cWh4b3RGazdNQkNrZlIwYUVhaUREQUpKNm1jb1lpRUQ2QgpBVGJhVmpVaGNaUiswYkRST25PN0ozRk5rZmx3K2dMaVhvcXFRRW9pU2ZWb2h5SWY3UUtDQVFFQThjYTM5K0g4CjN3L2Qrcm0yUGNhM0RMQnBYaWU4Z3ZYcGpjazVYSkpvSGVmbnJjZWQrcFpXaTZEYncwYld0MEdtYkxmVjJNSlAKV2I1aTZzSXhmdkN3YlFqbHY0UnExMVA5ZEswT3poMnVpKzZ6cXVBMG5YTVcrN0lJS0cvdDhmS2NJZGRRNnRGcwpFclFVTFBDak56ODA2cHBiSlhPRmVvMW1BK293TGhHNlA3dDhCdlZHSk1NaTNxejNlSUNuVVE2eDNFY01ITXNuClhrM21DUzI1WUZaNk96cytFK254cGVraTAzZmQwblp3UE1jdElHZys1c3hleE9zREsrTHlvb2FqQnc5N0oyUzIKcUNNWXFtT0tLcmxEQ3Y1WmQ4dlZLN3hXVmpKRVhGTTNMZ2pieHBRcCtuVXNVVWxwS01LOVlGS0lRREl0RU9aMApWcWExTXJaOElzN1l5d0tDQVFFQXhBemZIa2pIVGlvTHdZbG5EcEk0MWlOTDh5Y0ZBallrTC94dWhPU2tlVkE4CjdRWDZPZUpDekR3Z0FUYXVqOWR6Y0wwby9yTndWV0xWcnQ3OXk3YnJvVDdFREZKWVNTY25GRXNMTlVWSXRncGkKckNSUXJTL1F2TkVGTmE5K0pRc1dmYkdBNHdIUTFaSjI4MFp1cWMvNlEyUi9kZVh3cUZBQVBHN2NIcEhHWlR6ZQoyRmFRUHFLRkV4WlEyZkpvRys0SVBRNHVQVERybXlGMmVUWXk2T3BaaDBHbWJRYlVTa1dFWDlQRmF1cHJIWVdGCk8wK25DaVVPNVRaMFZoaGR2dUNKMWdPclZHYzhBUlJtUVZ1aUNEWTZCaGlvVTU0ZmZsSXlDTXZ5a3MwcmRXZ3MKWVJ2TmN4TXNlRGJpTDRKSURkMHhiN1d4VUdmVjRVNHZPMks5Vms1N0x3S0NBUUVBMkd1eE1jcXd1RnRUc0tPYwpaaUFDcXZFZTRKRmhSVGtySHlnSW1MelZSaS9ZU3M1c3MycnZmWDA0T3N5bVZ0UUZUVHdoeUMzbktjWXFkVW52ClZGblBFMHJyblV2Qzk0elBUQ205SHZPaTBzK1JORndOdlFMUWgrME5NR1ZBOFZyaU44aXRQZ1RJWU5XaFdianQKNFA1TE45V0QwVHBmT1J4cFBRZmNxT0JsZjdjcmhtNzNvdUNwemZtMmE3OStCaWpKUFF5NzR1cFhDeXRmeHNlUApNSlU0Uk56NjdJaDFMclpKM2xGbDFvYitZT2xKazhDOHpZd1RLT0hWck9zeGxobyt4SXN2Q2t3MDFMelZ6Mi9hCnRmT3Y5NTlHSnQzbXE0ZWpJUFZPQy9iUlpmdTMvMEdSY2dpQTZ5SnpaM0VxWTVaOU1EbTU3VzdjcE5RRlRxZmEKNXEyUmtRS0NBUUErNGhZSzQ3TXg2aUNkTWxKaEJSdS82OUJucktOWm96NFdPalRFNFlXejk3MmpGU0Mrd2tsRQpzeUJjNDBvNGp4WFRHb2wwc04rZU03WndnY3dNTko3OXVHRXZ4cFhVMlA4YTdqc3BHaEVKZXVsTlo5U015R0orCnZkaWE4TEJZZDJiK2FCbjhOay9pd1Rqd0xTNC92NXI1Vk5uaFdpRElDK2tYZVVPWGRwQ1pWbDN3TEV2V0cxRHQKMzJHTmxzZzM5VENsVE5BZUJudjc1VTdYOEQrQ0gvRVpoa0E0aGxFL2hXN0JRZTczclRzd1creHhLc3BjWWFpVwpjdEg3NzVMYUw3Rm1lUVRTYk01OVZpcTZXZ2J0OVY3Rko5R09DSkQzZHF2ZjBITDlEVndjSzQ3WWt3OWlFc3RYCnY5cnEvREhhYUpGNzBGNlFlTTNNbDhSa212WTZJYkEzQW9JQkFRRGt6RmZLeG9HQ3dWUDlua3k4NmFQSjFvd2kKc2FDZEx6RjRWTENRZzkrUXJITzEyY0p5MFFQUnJ2cUQyMGp1cDFlOWJhWVZzbkdYc1FZTFg2NVR6UzJSSCtlSAp6S0NPTTdnMVE3djMxNWpjMDMvN1lQck4rb3RrV0VBOUkyaDZjUE1vY3c0aERTNk02OFlxQVlKTS9RclVhenZhCnhBTFJaZEVkQW1xWDA4VHhuY1hRUEVxYkk0ZnlSZ2pVM1BYR3RRaFFFbERpR2kwbThjQTJNTXdsR1RmbTdOSXgKaENjZ2ZkL296TEp2VUhiMkxLRi82cXEySmJVRHlOMkVoK0xSZUJjdnp6Y1grZE5MdGQxY0Uvcm1SM2hMbWxmNgo3KzRpTVMxK0t1eWV3VlJVUEE1c1F1aUYyVUVoeEs1MUpZK1FpOG9HbERKdGRrOXB3QlZNN1F0WW9KVEwKLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K",
|
||||
"Certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZvakNDQklxZ0F3SUJBZ0lUQVAvRTgvRk43NTdtN0dvRklyWEF1cU0zblRBTkJna3Foa2lHOXcwQkFRc0YKQURBZk1SMHdHd1lEVlFRRERCUm9NbkJ3ZVNCb01tTnJaWElnWm1GclpTQkRRVEFlRncweE9EQXhNVFV3TnpJNQpNREJhRncweE9EQTBNVFV3TnpJNU1EQmFNRVF4RXpBUkJnTlZCQU1UQ214dlkyRnNNUzVqYjIweExUQXJCZ05WCkJBVVRKR1ptWXpSbU0yWXhOR1JsWmpsbFpUWmxZelpoTURVeU1tSTFZekJpWVdFek16YzVaRENDQWlJd0RRWUoKS29aSWh2Y05BUUVCQlFBRGdnSVBBRENDQWdvQ2dnSUJBTGtvVE9NYzZzNlRWNlVtZXFmMzdmaFpNbFlkdmowWApxMDgxTGZBMENjZWtSSEFQeExMeGJCMVhEU1JTeXBLV3QzdkJzQ0JRSXdUK3F6dnNZUmtYR1VaTFFqZTE5dlp5Cm5WeFpiM1hOcXNCNnkwTW1Jak5NUFBFcXplVXcyK3Y4ekh1cnRMYlEyMnBBcGU4M1FTVUs4NUY4bmpDOG1OekMKdTRtUHRNNmYyNDlWNkp6UXJtUVBuWU1aZFVwRmpBbEJmd1RodVdxemw2YUM4U2hqMTRMRnNUVEVpbFd5UjhOLwo3NCtlVzNqcjBaM2pxVEdWNkhOUFkyRHJnSXduWkZjck5FVjZ5dVhBUStDNWsxcjdNZEZrdm5pMDhoVE96eTdtCk44ZzJRakZ4M0ZZb3c4WUpVbTlMbmt6NldrMXc5R3k0NEF6UFJBN1RNblFiTlFqbVdUbDZHNndzZ20rY3BVdkYKbE1FckZMT1VWcW44bEo2ZjYwZUpiN0hXUFhWSCtEcndpMWE2R0l2ZVdoYkZhcEN5dkM1L3dOck10V29namJjUgpZeWZiVnBudUZnbXNPSVVoZnc4ZVhqT1hGeG0wV3NKMVZTQVZWc2NiT2REdEVYa0hhSnFUM1FEQ2t3bVRrUlpWCjRleldPNmdGbFE1SXNQSzQ2TXhzT3Yxci9UUnNVdWZXL3UrR2o5YTlLdmVyeFAwTC85eS9uSi9HK3lBUC9qbTIKaWNQQnpyUlpzUEJkS2dRc05KR25sQ1dkYUVaOFdsd0NmQThSZ3lSUFZqRXZMUVc1bFpzMnk0UlF0OGUxTEN4Ywp0SkN2TGh6eERzRDZIQ2VRRXdPVDZhSzBnOW1uc2FlSUxvMm1jckVTNDgzM0JvSXU2SkFST2xWT1hvd3pHMnYzCnVBeitBVDBGL1ZaRkFnTUJBQUdqZ2dHd01JSUJyREFPQmdOVkhROEJBZjhFQkFNQ0JhQXdIUVlEVlIwbEJCWXcKRkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQndNQ01Bd0dBMVVkRXdFQi93UUNNQUF3SFFZRFZSME9CQllFRk5LZQpBVUZYc2Z2N2lML0lYVVBXdzY2ZU5jQnhNQjhHQTFVZEl3UVlNQmFBRlB0NFR4TDVZQldETEo4WGZ6UVpzeTQyCjZrR0pNR1lHQ0NzR0FRVUZCd0VCQkZvd1dEQWlCZ2dyQmdFRkJRY3dBWVlXYUhSMGNEb3ZMekV5Tnk0d0xqQXUKTVRvME1EQXlMekF5QmdnckJnRUZCUWN3QW9ZbWFIUjBjRG92THpFeU55NHdMakF1TVRvME1EQXdMMkZqYldVdgphWE56ZFdWeUxXTmxjblF3T1FZRFZSMFJCREl3TUlJS2JHOWpZV3d4TG1OdmJZSVFkR1Z6ZERFdWJHOWpZV3d4CkxtTnZiWUlRZEdWemRESXViRzlqWVd3eExtTnZiVEFuQmdOVkhSOEVJREFlTUJ5Z0dxQVloaFpvZEhSd09pOHYKWlhoaGJYQnNaUzVqYjIwdlkzSnNNR0VHQTFVZElBUmFNRmd3Q0FZR1o0RU1BUUlCTUV3R0F5b0RCREJGTUNJRwpDQ3NHQVFVRkJ3SUJGaFpvZEhSd09pOHZaWGhoYlhCc1pTNWpiMjB2WTNCek1COEdDQ3NHQVFVRkJ3SUNNQk1NCkVVUnZJRmRvWVhRZ1ZHaHZkU0JYYVd4ME1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQ3A0Q2FxZlR4THNQTzQKS2JueDJZdEc4bTN3MC9keTVVR1VRNjZHbGxPVTk0L2I0MmNhbTRuNUZrTWlpZ01IaUx4c2JZVXh0cDZKQ3R5cQpLKzFNcDFWWEtSTTVKbFBTNWRIaWhxdHk1U3BrTUhjampwQSs3U2YyVWtoNmpKRWYxTUVJY2JnWnpJRk5IT0hYClVUUUppVFhKcno3blJDZnlQWFZtbWErUGtIRlU4R0VEVzJGOVptU1kzVFBiQWhiWkV2UkZubjUrR1lxbkZuancKWWw3Y0I2MXYwRzVpOGQwbnVvbTB4a2hiNTU3Y3BiZHhLblhsaFU4N2RZSTR5SUdPdUFGUWpYcXFXN2NIZCtXUQpWSDB2dFA3cEgrRmt2YnY4WkkxMHMrNU5ZcCtzZjFQZGQxekJsRmdNSGF3dnFFYUg3SU9sejdkajlCdmtVc0dpClhxQWVqQnFPCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVpakNDQTNLZ0F3SUJBZ0lDRWswd0RRWUpLb1pJaHZjTkFRRUxCUUF3S3pFcE1DY0dBMVVFQXd3Z1kyRmoKYTJ4cGJtY2dZM0o1Y0hSdlozSmhjR2hsY2lCbVlXdGxJRkpQVDFRd0hoY05NVFV4TURJeE1qQXhNVFV5V2hjTgpNakF4TURFNU1qQXhNVFV5V2pBZk1SMHdHd1lEVlFRREV4Um9ZWEJ3ZVNCb1lXTnJaWElnWm1GclpTQkRRVENDCkFTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTUlLUjNtYUJjVVNzbmNYWXpRVDEzRDUKTnIrWjNtTHhNTWgzVFVkdDZzQUNtcWJKMGJ0UmxnWGZNdE5MTTJPVTFJNmEzSnUrdElaU2RuMnYyMUpCd3Z4VQp6cFpRNHp5MmNpbUlpTVFEWkNRSEp3ekM5R1puOEhhVzA5MWl6OUgwR28zQTdXRFh3WU5tc2RMTlJpMDBvMTRVCmpvYVZxYVBzWXJaV3ZSS2FJUnFhVTBoSG1TMEFXd1FTdk4vOTNpTUlYdXlpd3l3bWt3S2JXbm54Q1EvZ3NjdEsKRlV0Y05yd0V4OVdnajZLbGh3RFR5STFRV1NCYnhWWU55VWdQRnpLeHJTbXdNTzB5TmZmN2hvK1FUOXg1K1kvNwpYRTU5UzRNYzRaWHhjWEtldy9nU2xOOVU1bXZUK0QyQmhEdGtDdXBkZnNaTkNRV3AyN0ErYi9EbXJGSTlOcXNDCkF3RUFBYU9DQWNJd2dnRytNQklHQTFVZEV3RUIvd1FJTUFZQkFmOENBUUF3UXdZRFZSMGVCRHd3T3FFNE1BYUMKQkM1dGFXd3dDb2NJQUFBQUFBQUFBQUF3SW9jZ0FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQpBQUFBQUFBd0RnWURWUjBQQVFIL0JBUURBZ0dHTUg4R0NDc0dBUVVGQndFQkJITXdjVEF5QmdnckJnRUZCUWN3CkFZWW1hSFIwY0RvdkwybHpjbWN1ZEhKMWMzUnBaQzV2WTNOd0xtbGtaVzUwY25WemRDNWpiMjB3T3dZSUt3WUIKQlFVSE1BS0dMMmgwZEhBNkx5OWhjSEJ6TG1sa1pXNTBjblZ6ZEM1amIyMHZjbTl2ZEhNdlpITjBjbTl2ZEdOaAplRE11Y0Rkak1COEdBMVVkSXdRWU1CYUFGT21rUCs2ZXBlYnkxZGQ1WUR5VHBpNGtqcGVxTUZRR0ExVWRJQVJOCk1Fc3dDQVlHWjRFTUFRSUJNRDhHQ3lzR0FRUUJndDhUQVFFQk1EQXdMZ1lJS3dZQkJRVUhBZ0VXSW1oMGRIQTYKTHk5amNITXVjbTl2ZEMxNE1TNXNaWFJ6Wlc1amNubHdkQzV2Y21jd1BBWURWUjBmQkRVd016QXhvQytnTFlZcgphSFIwY0RvdkwyTnliQzVwWkdWdWRISjFjM1F1WTI5dEwwUlRWRkpQVDFSRFFWZ3pRMUpNTG1OeWJEQWRCZ05WCkhRNEVGZ1FVKzNoUEV2bGdGWU1zbnhkL05CbXpMamJxUVlrd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFBMFkKQWVMWE9rbHg0aGhDaWtVVWwrQmRuRmZuMWcwVzVBaVFMVk5JT0w2UG5xWHUwd2puaE55aHFkd25maFlNbm95NAppZFJoNGxCNnB6OEdmOXBubExkL0RuV1NWM2dTKy9JL21BbDFkQ2tLYnk2SDJWNzkwZTZJSG1JSzJLWW0zam0rClUrK0ZJZEdwQmRzUVRTZG1pWC9yQXl1eE1ETTBhZE1rTkJ3VGZRbVpRQ3o2bkdIdzFRY1NQWk12WnBzQzhTa3YKZWt6eHNqRjFvdE9yTVVQTlBRdnRUV3JWeDhHbFIycWZ4LzR4YlFhMXYyZnJOdkZCQ21PNTlnb3oram5XdmZUdApqMk5qd0RaN3ZsTUJzUG0xNmRiS1lDODQwdXZSb1pqeHFzZGMzQ2hDWmpxaW1GcWxORy94b1BBOCtkVGljWnpDClhFOWlqUEljdlc2eTFhYTNiR3c9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"ChallengeCerts": {}
|
||||
}
|
||||
803
acme/acme_test.go
Normal file
@@ -0,0 +1,803 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
acmeprovider "github.com/containous/traefik/provider/acme"
|
||||
"github.com/containous/traefik/tls/generate"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDomainsSet(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input string
|
||||
expected types.Domains
|
||||
}{
|
||||
{
|
||||
input: "",
|
||||
expected: types.Domains{},
|
||||
},
|
||||
{
|
||||
input: "foo1.com",
|
||||
expected: types.Domains{
|
||||
types.Domain{Main: "foo1.com"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "foo2.com,bar.net",
|
||||
expected: types.Domains{
|
||||
types.Domain{
|
||||
Main: "foo2.com",
|
||||
SANs: []string{"bar.net"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "foo3.com,bar1.net,bar2.net,bar3.net",
|
||||
expected: types.Domains{
|
||||
types.Domain{
|
||||
Main: "foo3.com",
|
||||
SANs: []string{"bar1.net", "bar2.net", "bar3.net"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
domains := types.Domains{}
|
||||
domains.Set(test.input)
|
||||
assert.Exactly(t, test.expected, domains)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomainsSetAppend(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input string
|
||||
expected types.Domains
|
||||
}{
|
||||
{
|
||||
input: "",
|
||||
expected: types.Domains{},
|
||||
},
|
||||
{
|
||||
input: "foo1.com",
|
||||
expected: types.Domains{
|
||||
types.Domain{Main: "foo1.com"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "foo2.com,bar.net",
|
||||
expected: types.Domains{
|
||||
types.Domain{Main: "foo1.com"},
|
||||
types.Domain{
|
||||
Main: "foo2.com",
|
||||
SANs: []string{"bar.net"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "foo3.com,bar1.net,bar2.net,bar3.net",
|
||||
expected: types.Domains{
|
||||
types.Domain{Main: "foo1.com"},
|
||||
types.Domain{
|
||||
Main: "foo2.com",
|
||||
SANs: []string{"bar.net"},
|
||||
},
|
||||
types.Domain{
|
||||
Main: "foo3.com",
|
||||
SANs: []string{"bar1.net", "bar2.net", "bar3.net"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// append to
|
||||
domains := types.Domains{}
|
||||
for _, test := range testCases {
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
|
||||
domains.Set(test.input)
|
||||
assert.Exactly(t, test.expected, domains)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCertificatesRenew(t *testing.T) {
|
||||
foo1Cert, foo1Key, _ := generate.KeyPair("foo1.com", time.Now())
|
||||
foo2Cert, foo2Key, _ := generate.KeyPair("foo2.com", time.Now())
|
||||
|
||||
domainsCertificates := DomainsCertificates{
|
||||
lock: sync.RWMutex{},
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "foo1.com"},
|
||||
Certificate: &Certificate{
|
||||
Domain: "foo1.com",
|
||||
CertURL: "url",
|
||||
CertStableURL: "url",
|
||||
PrivateKey: foo1Key,
|
||||
Certificate: foo1Cert,
|
||||
},
|
||||
},
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "foo2.com"},
|
||||
Certificate: &Certificate{
|
||||
Domain: "foo2.com",
|
||||
CertURL: "url",
|
||||
CertStableURL: "url",
|
||||
PrivateKey: foo2Key,
|
||||
Certificate: foo2Cert,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
foo1Cert, foo1Key, _ = generate.KeyPair("foo1.com", time.Now())
|
||||
newCertificate := &Certificate{
|
||||
Domain: "foo1.com",
|
||||
CertURL: "url",
|
||||
CertStableURL: "url",
|
||||
PrivateKey: foo1Key,
|
||||
Certificate: foo1Cert,
|
||||
}
|
||||
|
||||
err := domainsCertificates.renewCertificates(newCertificate, types.Domain{Main: "foo1.com"})
|
||||
if err != nil {
|
||||
t.Errorf("Error in renewCertificates :%v", err)
|
||||
}
|
||||
|
||||
if len(domainsCertificates.Certs) != 2 {
|
||||
t.Errorf("Expected domainsCertificates length %d %+v\nGot %+v", 2, domainsCertificates.Certs, len(domainsCertificates.Certs))
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(domainsCertificates.Certs[0].Certificate, newCertificate) {
|
||||
t.Errorf("Expected new certificate %+v \nGot %+v", newCertificate, domainsCertificates.Certs[0].Certificate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveDuplicates(t *testing.T) {
|
||||
now := time.Now()
|
||||
fooCert, fooKey, _ := generate.KeyPair("foo.com", now)
|
||||
foo24Cert, foo24Key, _ := generate.KeyPair("foo.com", now.Add(24*time.Hour))
|
||||
foo48Cert, foo48Key, _ := generate.KeyPair("foo.com", now.Add(48*time.Hour))
|
||||
barCert, barKey, _ := generate.KeyPair("bar.com", now)
|
||||
domainsCertificates := DomainsCertificates{
|
||||
lock: sync.RWMutex{},
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com"},
|
||||
Certificate: &Certificate{
|
||||
Domain: "foo.com",
|
||||
CertURL: "url",
|
||||
CertStableURL: "url",
|
||||
PrivateKey: foo24Key,
|
||||
Certificate: foo24Cert,
|
||||
},
|
||||
},
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com"},
|
||||
Certificate: &Certificate{
|
||||
Domain: "foo.com",
|
||||
CertURL: "url",
|
||||
CertStableURL: "url",
|
||||
PrivateKey: foo48Key,
|
||||
Certificate: foo48Cert,
|
||||
},
|
||||
},
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com"},
|
||||
Certificate: &Certificate{
|
||||
Domain: "foo.com",
|
||||
CertURL: "url",
|
||||
CertStableURL: "url",
|
||||
PrivateKey: fooKey,
|
||||
Certificate: fooCert,
|
||||
},
|
||||
},
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com"},
|
||||
Certificate: &Certificate{
|
||||
Domain: "bar.com",
|
||||
CertURL: "url",
|
||||
CertStableURL: "url",
|
||||
PrivateKey: barKey,
|
||||
Certificate: barCert,
|
||||
},
|
||||
},
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com"},
|
||||
Certificate: &Certificate{
|
||||
Domain: "foo.com",
|
||||
CertURL: "url",
|
||||
CertStableURL: "url",
|
||||
PrivateKey: foo48Key,
|
||||
Certificate: foo48Cert,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
domainsCertificates.Init()
|
||||
|
||||
if len(domainsCertificates.Certs) != 2 {
|
||||
t.Errorf("Expected domainsCertificates length %d %+v\nGot %+v", 2, domainsCertificates.Certs, len(domainsCertificates.Certs))
|
||||
}
|
||||
|
||||
for _, cert := range domainsCertificates.Certs {
|
||||
switch cert.Domains.Main {
|
||||
case "bar.com":
|
||||
continue
|
||||
case "foo.com":
|
||||
if !cert.tlsCert.Leaf.NotAfter.Equal(now.Add(48 * time.Hour).Truncate(1 * time.Second)) {
|
||||
t.Errorf("Bad expiration %s date for domain %+v, now %s", cert.tlsCert.Leaf.NotAfter.String(), cert, now.Add(48*time.Hour).Truncate(1*time.Second).String())
|
||||
}
|
||||
default:
|
||||
t.Errorf("Unknown domain %+v", cert)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcmeClientCreation(t *testing.T) {
|
||||
// Lengthy setup to avoid external web requests - oh for easier golang testing!
|
||||
account := &Account{Email: "f@f"}
|
||||
|
||||
account.PrivateKey, _ = base64.StdEncoding.DecodeString(`
|
||||
MIIBPAIBAAJBAMp2Ni92FfEur+CAvFkgC12LT4l9D53ApbBpDaXaJkzzks+KsLw9zyAxvlrfAyTCQ
|
||||
7tDnEnIltAXyQ0uOFUUdcMCAwEAAQJAK1FbipATZcT9cGVa5x7KD7usytftLW14heQUPXYNV80r/3
|
||||
lmnpvjL06dffRpwkYeN8DATQF/QOcy3NNNGDw/4QIhAPAKmiZFxA/qmRXsuU8Zhlzf16WrNZ68K64
|
||||
asn/h3qZrAiEA1+wFR3WXCPIolOvd7AHjfgcTKQNkoMPywU4FYUNQ1AkCIQDv8yk0qPjckD6HVCPJ
|
||||
llJh9MC0svjevGtNlxJoE3lmEQIhAKXy1wfZ32/XtcrnENPvi6lzxI0T94X7s5pP3aCoPPoJAiEAl
|
||||
cijFkALeQp/qyeXdFld2v9gUN3eCgljgcl0QweRoIc=---`)
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, err := w.Write([]byte(`{
|
||||
"GPHhmRVEDas": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417",
|
||||
"keyChange": "https://foo/acme/key-change",
|
||||
"meta": {
|
||||
"termsOfService": "https://boulder:4431/terms/v7"
|
||||
},
|
||||
"newAccount": "https://foo/acme/new-acct",
|
||||
"newNonce": "https://foo/acme/new-nonce",
|
||||
"newOrder": "https://foo/acme/new-order",
|
||||
"revokeCert": "https://foo/acme/revoke-cert"
|
||||
}`))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
a := ACME{
|
||||
CAServer: ts.URL,
|
||||
DNSChallenge: &acmeprovider.DNSChallenge{
|
||||
Provider: "manual",
|
||||
DelayBeforeCheck: 10,
|
||||
DisablePropagationCheck: true,
|
||||
},
|
||||
}
|
||||
|
||||
client, err := a.buildACMEClient(account)
|
||||
if err != nil {
|
||||
t.Errorf("Error in buildACMEClient: %v", err)
|
||||
}
|
||||
if client == nil {
|
||||
t.Error("No client from buildACMEClient!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcme_getUncheckedCertificates(t *testing.T) {
|
||||
mm := make(map[string]*tls.Certificate)
|
||||
mm["*.containo.us"] = &tls.Certificate{}
|
||||
mm["traefik.acme.io"] = &tls.Certificate{}
|
||||
|
||||
dm := make(map[string]struct{})
|
||||
dm["*.traefik.wtf"] = struct{}{}
|
||||
|
||||
a := ACME{TLSConfig: &tls.Config{NameToCertificate: mm}, resolvingDomains: dm}
|
||||
|
||||
domains := []string{"traefik.containo.us", "trae.containo.us", "foo.traefik.wtf"}
|
||||
uncheckedDomains := a.getUncheckedDomains(domains, nil)
|
||||
assert.Empty(t, uncheckedDomains)
|
||||
domains = []string{"traefik.acme.io", "trae.acme.io"}
|
||||
uncheckedDomains = a.getUncheckedDomains(domains, nil)
|
||||
assert.Len(t, uncheckedDomains, 1)
|
||||
domainsCertificates := DomainsCertificates{Certs: []*DomainsCertificate{
|
||||
{
|
||||
tlsCert: &tls.Certificate{},
|
||||
Domains: types.Domain{
|
||||
Main: "*.acme.wtf",
|
||||
SANs: []string{"trae.acme.io"},
|
||||
},
|
||||
},
|
||||
}}
|
||||
account := Account{DomainsCertificate: domainsCertificates}
|
||||
uncheckedDomains = a.getUncheckedDomains(domains, &account)
|
||||
assert.Empty(t, uncheckedDomains)
|
||||
domains = []string{"traefik.containo.us", "trae.containo.us", "traefik.wtf"}
|
||||
uncheckedDomains = a.getUncheckedDomains(domains, nil)
|
||||
assert.Len(t, uncheckedDomains, 1)
|
||||
}
|
||||
|
||||
func TestAcme_getProvidedCertificate(t *testing.T) {
|
||||
mm := make(map[string]*tls.Certificate)
|
||||
mm["*.containo.us"] = &tls.Certificate{}
|
||||
mm["traefik.acme.io"] = &tls.Certificate{}
|
||||
|
||||
a := ACME{TLSConfig: &tls.Config{NameToCertificate: mm}}
|
||||
|
||||
domain := "traefik.containo.us"
|
||||
certificate := a.getProvidedCertificate(domain)
|
||||
assert.NotNil(t, certificate)
|
||||
domain = "trae.acme.io"
|
||||
certificate = a.getProvidedCertificate(domain)
|
||||
assert.Nil(t, certificate)
|
||||
}
|
||||
|
||||
func TestAcme_getValidDomain(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
domains []string
|
||||
wildcardAllowed bool
|
||||
dnsChallenge *acmeprovider.DNSChallenge
|
||||
expectedErr string
|
||||
expectedDomains []string
|
||||
}{
|
||||
{
|
||||
desc: "valid wildcard",
|
||||
domains: []string{"*.traefik.wtf"},
|
||||
dnsChallenge: &acmeprovider.DNSChallenge{},
|
||||
wildcardAllowed: true,
|
||||
expectedErr: "",
|
||||
expectedDomains: []string{"*.traefik.wtf"},
|
||||
},
|
||||
{
|
||||
desc: "no wildcard",
|
||||
domains: []string{"traefik.wtf", "foo.traefik.wtf"},
|
||||
dnsChallenge: &acmeprovider.DNSChallenge{},
|
||||
expectedErr: "",
|
||||
wildcardAllowed: true,
|
||||
expectedDomains: []string{"traefik.wtf", "foo.traefik.wtf"},
|
||||
},
|
||||
{
|
||||
desc: "unauthorized wildcard",
|
||||
domains: []string{"*.traefik.wtf"},
|
||||
dnsChallenge: &acmeprovider.DNSChallenge{},
|
||||
wildcardAllowed: false,
|
||||
expectedErr: "unable to generate a wildcard certificate for domain \"*.traefik.wtf\" from a 'Host' rule",
|
||||
expectedDomains: nil,
|
||||
},
|
||||
{
|
||||
desc: "no domain",
|
||||
domains: []string{},
|
||||
dnsChallenge: nil,
|
||||
wildcardAllowed: true,
|
||||
expectedErr: "unable to generate a certificate when no domain is given",
|
||||
expectedDomains: nil,
|
||||
},
|
||||
{
|
||||
desc: "no DNSChallenge",
|
||||
domains: []string{"*.traefik.wtf", "foo.traefik.wtf"},
|
||||
dnsChallenge: nil,
|
||||
wildcardAllowed: true,
|
||||
expectedErr: "unable to generate a wildcard certificate for domain \"*.traefik.wtf,foo.traefik.wtf\" : ACME needs a DNSChallenge",
|
||||
expectedDomains: nil,
|
||||
},
|
||||
{
|
||||
desc: "unauthorized wildcard with SAN",
|
||||
domains: []string{"*.*.traefik.wtf", "foo.traefik.wtf"},
|
||||
dnsChallenge: &acmeprovider.DNSChallenge{},
|
||||
wildcardAllowed: true,
|
||||
expectedErr: "unable to generate a wildcard certificate for domain \"*.*.traefik.wtf,foo.traefik.wtf\" : ACME does not allow '*.*' wildcard domain",
|
||||
expectedDomains: nil,
|
||||
},
|
||||
{
|
||||
desc: "wildcard with SANs",
|
||||
domains: []string{"*.traefik.wtf", "traefik.wtf"},
|
||||
dnsChallenge: &acmeprovider.DNSChallenge{},
|
||||
wildcardAllowed: true,
|
||||
expectedErr: "",
|
||||
expectedDomains: []string{"*.traefik.wtf", "traefik.wtf"},
|
||||
},
|
||||
{
|
||||
desc: "unexpected SANs",
|
||||
domains: []string{"*.traefik.wtf", "*.acme.wtf"},
|
||||
dnsChallenge: &acmeprovider.DNSChallenge{},
|
||||
wildcardAllowed: true,
|
||||
expectedErr: "unable to generate a certificate for domains \"*.traefik.wtf,*.acme.wtf\": SANs can not be a wildcard domain",
|
||||
expectedDomains: nil,
|
||||
},
|
||||
}
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
a := ACME{}
|
||||
if test.dnsChallenge != nil {
|
||||
a.DNSChallenge = test.dnsChallenge
|
||||
}
|
||||
domains, err := a.getValidDomains(test.domains, test.wildcardAllowed)
|
||||
|
||||
if len(test.expectedErr) > 0 {
|
||||
assert.EqualError(t, err, test.expectedErr, "Unexpected error.")
|
||||
} else {
|
||||
assert.Equal(t, len(test.expectedDomains), len(domains), "Unexpected domains.")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcme_getCertificateForDomain(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
domain string
|
||||
dc *DomainsCertificates
|
||||
expected *DomainsCertificate
|
||||
expectedFound bool
|
||||
}{
|
||||
{
|
||||
desc: "non-wildcard exact match",
|
||||
domain: "foo.traefik.wtf",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "foo.traefik.wtf",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: &DomainsCertificate{
|
||||
Domains: types.Domain{
|
||||
Main: "foo.traefik.wtf",
|
||||
},
|
||||
},
|
||||
expectedFound: true,
|
||||
},
|
||||
{
|
||||
desc: "non-wildcard no match",
|
||||
domain: "bar.traefik.wtf",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "foo.traefik.wtf",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: nil,
|
||||
expectedFound: false,
|
||||
},
|
||||
{
|
||||
desc: "wildcard match",
|
||||
domain: "foo.traefik.wtf",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "*.traefik.wtf",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: &DomainsCertificate{
|
||||
Domains: types.Domain{
|
||||
Main: "*.traefik.wtf",
|
||||
},
|
||||
},
|
||||
expectedFound: true,
|
||||
},
|
||||
{
|
||||
desc: "wildcard no match",
|
||||
domain: "foo.traefik.wtf",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "*.bar.traefik.wtf",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: nil,
|
||||
expectedFound: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got, found := test.dc.getCertificateForDomain(test.domain)
|
||||
assert.Equal(t, test.expectedFound, found)
|
||||
assert.Equal(t, test.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveEmptyCertificates(t *testing.T) {
|
||||
now := time.Now()
|
||||
fooCert, fooKey, _ := generate.KeyPair("foo.com", now)
|
||||
acmeCert, acmeKey, _ := generate.KeyPair("acme.wtf", now.Add(24*time.Hour))
|
||||
barCert, barKey, _ := generate.KeyPair("bar.com", now)
|
||||
testCases := []struct {
|
||||
desc string
|
||||
dc *DomainsCertificates
|
||||
expectedDc *DomainsCertificates
|
||||
}{
|
||||
{
|
||||
desc: "No empty certificate",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: fooCert,
|
||||
PrivateKey: fooKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: barCert,
|
||||
PrivateKey: barKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: fooCert,
|
||||
PrivateKey: fooKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: barCert,
|
||||
PrivateKey: barKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "First certificate is nil",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: barCert,
|
||||
PrivateKey: barKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: nil,
|
||||
PrivateKey: barKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Last certificate is empty",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: fooCert,
|
||||
PrivateKey: fooKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{},
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: fooCert,
|
||||
PrivateKey: fooKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "First and last certificates are nil or empty",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{},
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "All certificates are nil or empty",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "foo24.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{},
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
a := &Account{DomainsCertificate: *test.dc}
|
||||
a.Init()
|
||||
|
||||
assert.Equal(t, len(test.expectedDc.Certs), len(a.DomainsCertificate.Certs))
|
||||
sort.Sort(&a.DomainsCertificate)
|
||||
sort.Sort(test.expectedDc)
|
||||
for key, value := range test.expectedDc.Certs {
|
||||
assert.Equal(t, value.Domains.Main, a.DomainsCertificate.Certs[key].Domains.Main)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
104
acme/challenge_http_provider.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/go-acme/lego/challenge"
|
||||
)
|
||||
|
||||
var _ challenge.ProviderTimeout = (*challengeHTTPProvider)(nil)
|
||||
|
||||
type challengeHTTPProvider struct {
|
||||
store cluster.Store
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func (c *challengeHTTPProvider) getTokenValue(token, domain string) []byte {
|
||||
log.Debugf("Looking for an existing ACME challenge for token %v...", token)
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
account := c.store.Get().(*Account)
|
||||
if account.HTTPChallenge == nil {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
var result []byte
|
||||
operation := func() error {
|
||||
var ok bool
|
||||
if result, ok = account.HTTPChallenge[token][domain]; !ok {
|
||||
return fmt.Errorf("cannot find challenge for token %v", token)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Error getting challenge for token retrying in %s", time)
|
||||
}
|
||||
|
||||
ebo := backoff.NewExponentialBackOff()
|
||||
ebo.MaxElapsedTime = 60 * time.Second
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), ebo, notify)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting challenge for token: %v", err)
|
||||
return []byte{}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *challengeHTTPProvider) Present(domain, token, keyAuth string) error {
|
||||
log.Debugf("Challenge Present %s", domain)
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
transaction, object, err := c.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
account := object.(*Account)
|
||||
if account.HTTPChallenge == nil {
|
||||
account.HTTPChallenge = map[string]map[string][]byte{}
|
||||
}
|
||||
|
||||
if _, ok := account.HTTPChallenge[token]; !ok {
|
||||
account.HTTPChallenge[token] = map[string][]byte{}
|
||||
}
|
||||
|
||||
account.HTTPChallenge[token][domain] = []byte(keyAuth)
|
||||
|
||||
return transaction.Commit(account)
|
||||
}
|
||||
|
||||
func (c *challengeHTTPProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
log.Debugf("Challenge CleanUp %s", domain)
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
transaction, object, err := c.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
account := object.(*Account)
|
||||
if _, ok := account.HTTPChallenge[token]; ok {
|
||||
if _, domainOk := account.HTTPChallenge[token][domain]; domainOk {
|
||||
delete(account.HTTPChallenge[token], domain)
|
||||
}
|
||||
if len(account.HTTPChallenge[token]) == 0 {
|
||||
delete(account.HTTPChallenge, token)
|
||||
}
|
||||
}
|
||||
|
||||
return transaction.Commit(account)
|
||||
}
|
||||
|
||||
func (c *challengeHTTPProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return 60 * time.Second, 5 * time.Second
|
||||
}
|
||||
128
acme/challenge_tls_provider.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/go-acme/lego/challenge"
|
||||
"github.com/go-acme/lego/challenge/tlsalpn01"
|
||||
)
|
||||
|
||||
var _ challenge.ProviderTimeout = (*challengeTLSProvider)(nil)
|
||||
|
||||
type challengeTLSProvider struct {
|
||||
store cluster.Store
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func (c *challengeTLSProvider) getCertificate(domain string) (cert *tls.Certificate, exists bool) {
|
||||
log.Debugf("Looking for an existing ACME challenge for %s...", domain)
|
||||
|
||||
if !strings.HasSuffix(domain, ".acme.invalid") {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
account := c.store.Get().(*Account)
|
||||
if account.ChallengeCerts == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
account.Init()
|
||||
|
||||
var result *tls.Certificate
|
||||
operation := func() error {
|
||||
for _, cert := range account.ChallengeCerts {
|
||||
for _, dns := range cert.certificate.Leaf.DNSNames {
|
||||
if domain == dns {
|
||||
result = cert.certificate
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("cannot find challenge cert for domain %s", domain)
|
||||
}
|
||||
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Error getting cert: %v, retrying in %s", err, time)
|
||||
}
|
||||
ebo := backoff.NewExponentialBackOff()
|
||||
ebo.MaxElapsedTime = 60 * time.Second
|
||||
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), ebo, notify)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting cert: %v", err)
|
||||
return nil, false
|
||||
|
||||
}
|
||||
return result, true
|
||||
}
|
||||
|
||||
func (c *challengeTLSProvider) Present(domain, token, keyAuth string) error {
|
||||
log.Debugf("Challenge Present %s", domain)
|
||||
|
||||
cert, err := tlsALPN01ChallengeCert(domain, keyAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
transaction, object, err := c.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
account := object.(*Account)
|
||||
if account.ChallengeCerts == nil {
|
||||
account.ChallengeCerts = map[string]*ChallengeCert{}
|
||||
}
|
||||
account.ChallengeCerts[domain] = cert
|
||||
|
||||
return transaction.Commit(account)
|
||||
}
|
||||
|
||||
func (c *challengeTLSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
log.Debugf("Challenge CleanUp %s", domain)
|
||||
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
transaction, object, err := c.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
account := object.(*Account)
|
||||
delete(account.ChallengeCerts, domain)
|
||||
|
||||
return transaction.Commit(account)
|
||||
}
|
||||
|
||||
func (c *challengeTLSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return 60 * time.Second, 5 * time.Second
|
||||
}
|
||||
|
||||
func tlsALPN01ChallengeCert(domain, keyAuth string) (*ChallengeCert, error) {
|
||||
tempCertPEM, rsaPrivPEM, err := tlsalpn01.ChallengeBlocks(domain, keyAuth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certificate, err := tls.X509KeyPair(tempCertPEM, rsaPrivPEM)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ChallengeCert{Certificate: tempCertPEM, PrivateKey: rsaPrivPEM, certificate: &certificate}, nil
|
||||
}
|
||||
177
acme/localStore.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/provider/acme"
|
||||
)
|
||||
|
||||
// LocalStore is a store using a file as storage
|
||||
type LocalStore struct {
|
||||
file string
|
||||
}
|
||||
|
||||
// NewLocalStore create a LocalStore
|
||||
func NewLocalStore(file string) *LocalStore {
|
||||
return &LocalStore{
|
||||
file: file,
|
||||
}
|
||||
}
|
||||
|
||||
// Get loads file into store and returns the Account
|
||||
func (s *LocalStore) Get() (*Account, error) {
|
||||
account := &Account{}
|
||||
|
||||
hasData, err := acme.CheckFile(s.file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if hasData {
|
||||
f, err := os.Open(s.file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
file, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(file, &account); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// ConvertToNewFormat converts old acme.json format to the new one and store the result into the file (used for the backward compatibility)
|
||||
func ConvertToNewFormat(fileName string) {
|
||||
localStore := acme.NewLocalStore(fileName)
|
||||
|
||||
storeAccount, err := localStore.GetAccount()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to read new account, ACME data conversion is not available : %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
storeCertificates, err := localStore.GetCertificates()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to read new certificates, ACME data conversion is not available : %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if storeAccount == nil {
|
||||
localStore := NewLocalStore(fileName)
|
||||
|
||||
account, err := localStore.Get()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to read old account, ACME data conversion is not available : %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert ACME data from old to new format
|
||||
newAccount := &acme.Account{}
|
||||
if account != nil && len(account.Email) > 0 {
|
||||
err = backupACMEFile(fileName, account)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to create a backup for the V1 formatted ACME file: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = account.RemoveAccountV1Values()
|
||||
if err != nil {
|
||||
log.Errorf("Unable to remove ACME Account V1 values during format conversion: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
newAccount = &acme.Account{
|
||||
PrivateKey: account.PrivateKey,
|
||||
Registration: account.Registration,
|
||||
Email: account.Email,
|
||||
KeyType: account.KeyType,
|
||||
}
|
||||
|
||||
var newCertificates []*acme.Certificate
|
||||
for _, cert := range account.DomainsCertificate.Certs {
|
||||
newCertificates = append(newCertificates, &acme.Certificate{
|
||||
Certificate: cert.Certificate.Certificate,
|
||||
Key: cert.Certificate.PrivateKey,
|
||||
Domain: cert.Domains,
|
||||
})
|
||||
}
|
||||
|
||||
// If account is in the old format, storeCertificates is nil or empty and has to be initialized
|
||||
storeCertificates = newCertificates
|
||||
}
|
||||
|
||||
// Store the data in new format into the file even if account is nil
|
||||
// to delete Account in ACME v1 format and keeping the certificates
|
||||
newLocalStore := acme.NewLocalStore(fileName)
|
||||
newLocalStore.SaveDataChan <- &acme.StoredData{Account: newAccount, Certificates: storeCertificates}
|
||||
}
|
||||
}
|
||||
|
||||
func backupACMEFile(originalFileName string, account interface{}) error {
|
||||
// write account to file
|
||||
data, err := json.MarshalIndent(account, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(originalFileName+".bak", data, 0600)
|
||||
}
|
||||
|
||||
// FromNewToOldFormat converts new acme account to the old one (used for the backward compatibility)
|
||||
func FromNewToOldFormat(fileName string) (*Account, error) {
|
||||
localStore := acme.NewLocalStore(fileName)
|
||||
|
||||
storeAccount, err := localStore.GetAccount()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storeCertificates, err := localStore.GetCertificates()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert ACME Account from new to old format
|
||||
// (Needed by the KV stores)
|
||||
var account *Account
|
||||
if storeAccount != nil {
|
||||
account = &Account{
|
||||
Email: storeAccount.Email,
|
||||
PrivateKey: storeAccount.PrivateKey,
|
||||
Registration: storeAccount.Registration,
|
||||
DomainsCertificate: DomainsCertificates{},
|
||||
KeyType: storeAccount.KeyType,
|
||||
}
|
||||
}
|
||||
|
||||
// Convert ACME Certificates from new to old format
|
||||
// (Needed by the KV stores)
|
||||
if len(storeCertificates) > 0 {
|
||||
// Account can be nil if data are migrated from new format
|
||||
// with a ACME V1 Account
|
||||
if account == nil {
|
||||
account = &Account{}
|
||||
}
|
||||
for _, cert := range storeCertificates {
|
||||
_, err := account.DomainsCertificate.addCertificateForDomains(&Certificate{
|
||||
Domain: cert.Domain.Main,
|
||||
Certificate: cert.Certificate,
|
||||
PrivateKey: cert.Key,
|
||||
}, cert.Domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
31
acme/localStore_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
acmeFile := "./acme_example.json"
|
||||
|
||||
folder, prefix := filepath.Split(acmeFile)
|
||||
tmpFile, err := ioutil.TempFile(folder, prefix)
|
||||
defer os.Remove(tmpFile.Name())
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
fileContent, err := ioutil.ReadFile(acmeFile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
tmpFile.Write(fileContent)
|
||||
|
||||
localStore := NewLocalStore(tmpFile.Name())
|
||||
account, err := localStore.Get()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Len(t, account.DomainsCertificate.Certs, 1)
|
||||
}
|
||||
700
anonymize/anonymize_config_test.go
Normal file
@@ -0,0 +1,700 @@
|
||||
package anonymize
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik/acme"
|
||||
"github.com/containous/traefik/api"
|
||||
"github.com/containous/traefik/configuration"
|
||||
"github.com/containous/traefik/middlewares"
|
||||
"github.com/containous/traefik/provider"
|
||||
acmeprovider "github.com/containous/traefik/provider/acme"
|
||||
"github.com/containous/traefik/provider/boltdb"
|
||||
"github.com/containous/traefik/provider/consul"
|
||||
"github.com/containous/traefik/provider/consulcatalog"
|
||||
"github.com/containous/traefik/provider/docker"
|
||||
"github.com/containous/traefik/provider/dynamodb"
|
||||
"github.com/containous/traefik/provider/ecs"
|
||||
"github.com/containous/traefik/provider/etcd"
|
||||
"github.com/containous/traefik/provider/eureka"
|
||||
"github.com/containous/traefik/provider/file"
|
||||
"github.com/containous/traefik/provider/kubernetes"
|
||||
"github.com/containous/traefik/provider/kv"
|
||||
"github.com/containous/traefik/provider/marathon"
|
||||
"github.com/containous/traefik/provider/mesos"
|
||||
"github.com/containous/traefik/provider/rancher"
|
||||
"github.com/containous/traefik/provider/zk"
|
||||
"github.com/containous/traefik/safe"
|
||||
traefiktls "github.com/containous/traefik/tls"
|
||||
"github.com/containous/traefik/types"
|
||||
assetfs "github.com/elazarl/go-bindata-assetfs"
|
||||
"github.com/thoas/stats"
|
||||
)
|
||||
|
||||
func TestDo_globalConfiguration(t *testing.T) {
|
||||
|
||||
config := &configuration.GlobalConfiguration{}
|
||||
|
||||
config.GraceTimeOut = flaeg.Duration(666 * time.Second)
|
||||
config.Debug = true
|
||||
config.CheckNewVersion = true
|
||||
config.AccessLogsFile = "AccessLogsFile"
|
||||
config.AccessLog = &types.AccessLog{
|
||||
FilePath: "AccessLog FilePath",
|
||||
Format: "AccessLog Format",
|
||||
}
|
||||
config.TraefikLogsFile = "TraefikLogsFile"
|
||||
config.LogLevel = "LogLevel"
|
||||
config.EntryPoints = configuration.EntryPoints{
|
||||
"foo": {
|
||||
Address: "foo Address",
|
||||
TLS: &traefiktls.TLS{
|
||||
MinVersion: "foo MinVersion",
|
||||
CipherSuites: []string{"foo CipherSuites 1", "foo CipherSuites 2", "foo CipherSuites 3"},
|
||||
Certificates: traefiktls.Certificates{
|
||||
{CertFile: "CertFile 1", KeyFile: "KeyFile 1"},
|
||||
{CertFile: "CertFile 2", KeyFile: "KeyFile 2"},
|
||||
},
|
||||
ClientCA: traefiktls.ClientCA{
|
||||
Files: traefiktls.FilesOrContents{"foo ClientCAFiles 1", "foo ClientCAFiles 2", "foo ClientCAFiles 3"},
|
||||
Optional: false,
|
||||
},
|
||||
},
|
||||
Redirect: &types.Redirect{
|
||||
Replacement: "foo Replacement",
|
||||
Regex: "foo Regex",
|
||||
EntryPoint: "foo EntryPoint",
|
||||
},
|
||||
Auth: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
UsersFile: "foo Basic UsersFile",
|
||||
Users: types.Users{"foo Basic Users 1", "foo Basic Users 2", "foo Basic Users 3"},
|
||||
},
|
||||
Digest: &types.Digest{
|
||||
UsersFile: "foo Digest UsersFile",
|
||||
Users: types.Users{"foo Digest Users 1", "foo Digest Users 2", "foo Digest Users 3"},
|
||||
},
|
||||
Forward: &types.Forward{
|
||||
Address: "foo Address",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "foo CA",
|
||||
Cert: "foo Cert",
|
||||
Key: "foo Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
TrustForwardHeader: true,
|
||||
},
|
||||
},
|
||||
WhitelistSourceRange: []string{"foo WhitelistSourceRange 1", "foo WhitelistSourceRange 2", "foo WhitelistSourceRange 3"},
|
||||
Compress: true,
|
||||
ProxyProtocol: &configuration.ProxyProtocol{
|
||||
TrustedIPs: []string{"127.0.0.1/32", "192.168.0.1"},
|
||||
},
|
||||
},
|
||||
"fii": {
|
||||
Address: "fii Address",
|
||||
TLS: &traefiktls.TLS{
|
||||
MinVersion: "fii MinVersion",
|
||||
CipherSuites: []string{"fii CipherSuites 1", "fii CipherSuites 2", "fii CipherSuites 3"},
|
||||
Certificates: traefiktls.Certificates{
|
||||
{CertFile: "CertFile 1", KeyFile: "KeyFile 1"},
|
||||
{CertFile: "CertFile 2", KeyFile: "KeyFile 2"},
|
||||
},
|
||||
ClientCA: traefiktls.ClientCA{
|
||||
Files: traefiktls.FilesOrContents{"fii ClientCAFiles 1", "fii ClientCAFiles 2", "fii ClientCAFiles 3"},
|
||||
Optional: false,
|
||||
},
|
||||
},
|
||||
Redirect: &types.Redirect{
|
||||
Replacement: "fii Replacement",
|
||||
Regex: "fii Regex",
|
||||
EntryPoint: "fii EntryPoint",
|
||||
},
|
||||
Auth: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
UsersFile: "fii Basic UsersFile",
|
||||
Users: types.Users{"fii Basic Users 1", "fii Basic Users 2", "fii Basic Users 3"},
|
||||
},
|
||||
Digest: &types.Digest{
|
||||
UsersFile: "fii Digest UsersFile",
|
||||
Users: types.Users{"fii Digest Users 1", "fii Digest Users 2", "fii Digest Users 3"},
|
||||
},
|
||||
Forward: &types.Forward{
|
||||
Address: "fii Address",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "fii CA",
|
||||
Cert: "fii Cert",
|
||||
Key: "fii Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
TrustForwardHeader: true,
|
||||
},
|
||||
},
|
||||
WhitelistSourceRange: []string{"fii WhitelistSourceRange 1", "fii WhitelistSourceRange 2", "fii WhitelistSourceRange 3"},
|
||||
Compress: true,
|
||||
ProxyProtocol: &configuration.ProxyProtocol{
|
||||
TrustedIPs: []string{"127.0.0.1/32", "192.168.0.1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
config.Cluster = &types.Cluster{
|
||||
Node: "Cluster Node",
|
||||
Store: &types.Store{
|
||||
Prefix: "Cluster Store Prefix",
|
||||
// ...
|
||||
},
|
||||
}
|
||||
config.Constraints = types.Constraints{
|
||||
{
|
||||
Key: "Constraints Key 1",
|
||||
Regex: "Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "Constraints Key 1",
|
||||
Regex: "Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
}
|
||||
config.ACME = &acme.ACME{
|
||||
Email: "acme Email",
|
||||
Domains: []types.Domain{
|
||||
{
|
||||
Main: "Domains Main",
|
||||
SANs: []string{"Domains acme SANs 1", "Domains acme SANs 2", "Domains acme SANs 3"},
|
||||
},
|
||||
},
|
||||
Storage: "Storage",
|
||||
StorageFile: "StorageFile",
|
||||
OnDemand: true,
|
||||
OnHostRule: true,
|
||||
CAServer: "CAServer",
|
||||
EntryPoint: "EntryPoint",
|
||||
DNSChallenge: &acmeprovider.DNSChallenge{Provider: "DNSProvider"},
|
||||
DelayDontCheckDNS: 666,
|
||||
ACMELogging: true,
|
||||
TLSConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
// ...
|
||||
},
|
||||
}
|
||||
config.DefaultEntryPoints = configuration.DefaultEntryPoints{"DefaultEntryPoints 1", "DefaultEntryPoints 2", "DefaultEntryPoints 3"}
|
||||
config.ProvidersThrottleDuration = flaeg.Duration(666 * time.Second)
|
||||
config.MaxIdleConnsPerHost = 666
|
||||
config.IdleTimeout = flaeg.Duration(666 * time.Second)
|
||||
config.InsecureSkipVerify = true
|
||||
config.RootCAs = traefiktls.FilesOrContents{"RootCAs 1", "RootCAs 2", "RootCAs 3"}
|
||||
config.Retry = &configuration.Retry{
|
||||
Attempts: 666,
|
||||
}
|
||||
config.HealthCheck = &configuration.HealthCheckConfig{
|
||||
Interval: flaeg.Duration(666 * time.Second),
|
||||
}
|
||||
config.API = &api.Handler{
|
||||
EntryPoint: "traefik",
|
||||
Dashboard: true,
|
||||
Debug: true,
|
||||
CurrentConfigurations: &safe.Safe{},
|
||||
Statistics: &types.Statistics{
|
||||
RecentErrors: 666,
|
||||
},
|
||||
Stats: &stats.Stats{
|
||||
Uptime: time.Now(),
|
||||
Pid: 666,
|
||||
ResponseCounts: map[string]int{"foo": 1},
|
||||
TotalResponseCounts: map[string]int{"bar": 1},
|
||||
TotalResponseTime: time.Now(),
|
||||
},
|
||||
StatsRecorder: &middlewares.StatsRecorder{},
|
||||
DashboardAssets: &assetfs.AssetFS{
|
||||
Asset: func(path string) ([]byte, error) {
|
||||
return nil, nil
|
||||
},
|
||||
AssetDir: func(path string) ([]string, error) {
|
||||
return nil, nil
|
||||
},
|
||||
AssetInfo: func(path string) (os.FileInfo, error) {
|
||||
return nil, nil
|
||||
},
|
||||
Prefix: "fii",
|
||||
},
|
||||
}
|
||||
config.RespondingTimeouts = &configuration.RespondingTimeouts{
|
||||
ReadTimeout: flaeg.Duration(666 * time.Second),
|
||||
WriteTimeout: flaeg.Duration(666 * time.Second),
|
||||
IdleTimeout: flaeg.Duration(666 * time.Second),
|
||||
}
|
||||
config.ForwardingTimeouts = &configuration.ForwardingTimeouts{
|
||||
DialTimeout: flaeg.Duration(666 * time.Second),
|
||||
ResponseHeaderTimeout: flaeg.Duration(666 * time.Second),
|
||||
}
|
||||
config.Docker = &docker.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "docker Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "docker Constraints Key 1",
|
||||
Regex: "docker Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "docker Constraints Key 1",
|
||||
Regex: "docker Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "docker Endpoint",
|
||||
Domain: "docker Domain",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "docker CA",
|
||||
Cert: "docker Cert",
|
||||
Key: "docker Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
ExposedByDefault: true,
|
||||
UseBindPortIP: true,
|
||||
SwarmMode: true,
|
||||
}
|
||||
config.File = &file.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "file Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "file Constraints Key 1",
|
||||
Regex: "file Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "file Constraints Key 1",
|
||||
Regex: "file Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Directory: "file Directory",
|
||||
}
|
||||
config.Web = &configuration.WebCompatibility{
|
||||
Address: "web Address",
|
||||
CertFile: "web CertFile",
|
||||
KeyFile: "web KeyFile",
|
||||
ReadOnly: true,
|
||||
Statistics: &types.Statistics{
|
||||
RecentErrors: 666,
|
||||
},
|
||||
Metrics: &types.Metrics{
|
||||
Prometheus: &types.Prometheus{
|
||||
Buckets: types.Buckets{6.5, 6.6, 6.7},
|
||||
},
|
||||
Datadog: &types.Datadog{
|
||||
Address: "Datadog Address",
|
||||
PushInterval: "Datadog PushInterval",
|
||||
},
|
||||
StatsD: &types.Statsd{
|
||||
Address: "StatsD Address",
|
||||
PushInterval: "StatsD PushInterval",
|
||||
},
|
||||
},
|
||||
Path: "web Path",
|
||||
Auth: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
UsersFile: "web Basic UsersFile",
|
||||
Users: types.Users{"web Basic Users 1", "web Basic Users 2", "web Basic Users 3"},
|
||||
},
|
||||
Digest: &types.Digest{
|
||||
UsersFile: "web Digest UsersFile",
|
||||
Users: types.Users{"web Digest Users 1", "web Digest Users 2", "web Digest Users 3"},
|
||||
},
|
||||
Forward: &types.Forward{
|
||||
Address: "web Address",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "web CA",
|
||||
Cert: "web Cert",
|
||||
Key: "web Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
TrustForwardHeader: true,
|
||||
},
|
||||
},
|
||||
Debug: true,
|
||||
}
|
||||
config.Marathon = &marathon.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "marathon Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "marathon Constraints Key 1",
|
||||
Regex: "marathon Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "marathon Constraints Key 1",
|
||||
Regex: "marathon Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "",
|
||||
Domain: "",
|
||||
ExposedByDefault: true,
|
||||
GroupsAsSubDomains: true,
|
||||
DCOSToken: "",
|
||||
MarathonLBCompatibility: true,
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "marathon CA",
|
||||
Cert: "marathon Cert",
|
||||
Key: "marathon Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
DialerTimeout: flaeg.Duration(666 * time.Second),
|
||||
KeepAlive: flaeg.Duration(666 * time.Second),
|
||||
ForceTaskHostname: true,
|
||||
Basic: &marathon.Basic{
|
||||
HTTPBasicAuthUser: "marathon HTTPBasicAuthUser",
|
||||
HTTPBasicPassword: "marathon HTTPBasicPassword",
|
||||
},
|
||||
RespectReadinessChecks: true,
|
||||
}
|
||||
config.ConsulCatalog = &consulcatalog.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "ConsulCatalog Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "ConsulCatalog Constraints Key 1",
|
||||
Regex: "ConsulCatalog Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "ConsulCatalog Constraints Key 1",
|
||||
Regex: "ConsulCatalog Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "ConsulCatalog Endpoint",
|
||||
Domain: "ConsulCatalog Domain",
|
||||
ExposedByDefault: true,
|
||||
Prefix: "ConsulCatalog Prefix",
|
||||
FrontEndRule: "ConsulCatalog FrontEndRule",
|
||||
}
|
||||
config.Kubernetes = &kubernetes.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "k8s Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "k8s Constraints Key 1",
|
||||
Regex: "k8s Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "k8s Constraints Key 1",
|
||||
Regex: "k8s Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "k8s Endpoint",
|
||||
Token: "k8s Token",
|
||||
CertAuthFilePath: "k8s CertAuthFilePath",
|
||||
DisablePassHostHeaders: true,
|
||||
Namespaces: kubernetes.Namespaces{"k8s Namespaces 1", "k8s Namespaces 2", "k8s Namespaces 3"},
|
||||
LabelSelector: "k8s LabelSelector",
|
||||
}
|
||||
config.Mesos = &mesos.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "mesos Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "mesos Constraints Key 1",
|
||||
Regex: "mesos Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "mesos Constraints Key 1",
|
||||
Regex: "mesos Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "mesos Endpoint",
|
||||
Domain: "mesos Domain",
|
||||
ExposedByDefault: true,
|
||||
GroupsAsSubDomains: true,
|
||||
ZkDetectionTimeout: 666,
|
||||
RefreshSeconds: 666,
|
||||
IPSources: "mesos IPSources",
|
||||
StateTimeoutSecond: 666,
|
||||
Masters: []string{"mesos Masters 1", "mesos Masters 2", "mesos Masters 3"},
|
||||
}
|
||||
config.Eureka = &eureka.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "eureka Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "eureka Constraints Key 1",
|
||||
Regex: "eureka Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "eureka Constraints Key 1",
|
||||
Regex: "eureka Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "eureka Endpoint",
|
||||
Delay: flaeg.Duration(30 * time.Second),
|
||||
RefreshSeconds: flaeg.Duration(30 * time.Second),
|
||||
}
|
||||
config.ECS = &ecs.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "ecs Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "ecs Constraints Key 1",
|
||||
Regex: "ecs Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "ecs Constraints Key 1",
|
||||
Regex: "ecs Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Domain: "ecs Domain",
|
||||
ExposedByDefault: true,
|
||||
RefreshSeconds: 666,
|
||||
Clusters: ecs.Clusters{"ecs Clusters 1", "ecs Clusters 2", "ecs Clusters 3"},
|
||||
Cluster: "ecs Cluster",
|
||||
AutoDiscoverClusters: true,
|
||||
Region: "ecs Region",
|
||||
AccessKeyID: "ecs AccessKeyID",
|
||||
SecretAccessKey: "ecs SecretAccessKey",
|
||||
}
|
||||
config.Rancher = &rancher.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "rancher Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "rancher Constraints Key 1",
|
||||
Regex: "rancher Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "rancher Constraints Key 1",
|
||||
Regex: "rancher Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
APIConfiguration: rancher.APIConfiguration{
|
||||
Endpoint: "rancher Endpoint",
|
||||
AccessKey: "rancher AccessKey",
|
||||
SecretKey: "rancher SecretKey",
|
||||
},
|
||||
API: &rancher.APIConfiguration{
|
||||
Endpoint: "rancher Endpoint",
|
||||
AccessKey: "rancher AccessKey",
|
||||
SecretKey: "rancher SecretKey",
|
||||
},
|
||||
Metadata: &rancher.MetadataConfiguration{
|
||||
IntervalPoll: true,
|
||||
Prefix: "rancher Metadata Prefix",
|
||||
},
|
||||
Domain: "rancher Domain",
|
||||
RefreshSeconds: 666,
|
||||
ExposedByDefault: true,
|
||||
EnableServiceHealthFilter: true,
|
||||
}
|
||||
config.DynamoDB = &dynamodb.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "dynamodb Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "dynamodb Constraints Key 1",
|
||||
Regex: "dynamodb Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "dynamodb Constraints Key 1",
|
||||
Regex: "dynamodb Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
AccessKeyID: "dynamodb AccessKeyID",
|
||||
RefreshSeconds: 666,
|
||||
Region: "dynamodb Region",
|
||||
SecretAccessKey: "dynamodb SecretAccessKey",
|
||||
TableName: "dynamodb TableName",
|
||||
Endpoint: "dynamodb Endpoint",
|
||||
}
|
||||
config.Etcd = &etcd.Provider{
|
||||
Provider: kv.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "etcd Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "etcd Constraints Key 1",
|
||||
Regex: "etcd Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "etcd Constraints Key 1",
|
||||
Regex: "etcd Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "etcd Endpoint",
|
||||
Prefix: "etcd Prefix",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "etcd CA",
|
||||
Cert: "etcd Cert",
|
||||
Key: "etcd Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
Username: "etcd Username",
|
||||
Password: "etcd Password",
|
||||
},
|
||||
}
|
||||
config.Zookeeper = &zk.Provider{
|
||||
Provider: kv.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "zk Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "zk Constraints Key 1",
|
||||
Regex: "zk Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "zk Constraints Key 1",
|
||||
Regex: "zk Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "zk Endpoint",
|
||||
Prefix: "zk Prefix",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "zk CA",
|
||||
Cert: "zk Cert",
|
||||
Key: "zk Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
Username: "zk Username",
|
||||
Password: "zk Password",
|
||||
},
|
||||
}
|
||||
config.Boltdb = &boltdb.Provider{
|
||||
Provider: kv.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "boltdb Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "boltdb Constraints Key 1",
|
||||
Regex: "boltdb Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "boltdb Constraints Key 1",
|
||||
Regex: "boltdb Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "boltdb Endpoint",
|
||||
Prefix: "boltdb Prefix",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "boltdb CA",
|
||||
Cert: "boltdb Cert",
|
||||
Key: "boltdb Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
Username: "boltdb Username",
|
||||
Password: "boltdb Password",
|
||||
},
|
||||
}
|
||||
config.Consul = &consul.Provider{
|
||||
Provider: kv.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "consul Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "consul Constraints Key 1",
|
||||
Regex: "consul Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "consul Constraints Key 1",
|
||||
Regex: "consul Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "consul Endpoint",
|
||||
Prefix: "consul Prefix",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "consul CA",
|
||||
Cert: "consul Cert",
|
||||
Key: "consul Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
Username: "consul Username",
|
||||
Password: "consul Password",
|
||||
},
|
||||
}
|
||||
|
||||
cleanJSON, err := Do(config, true)
|
||||
if err != nil {
|
||||
t.Fatal(err, cleanJSON)
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,11 @@ func Test_doOnJSON(t *testing.T) {
|
||||
"Network": "",
|
||||
"Address": ":80",
|
||||
"TLS": null,
|
||||
"Redirect": {
|
||||
"EntryPoint": "https",
|
||||
"Regex": "",
|
||||
"Replacement": ""
|
||||
},
|
||||
"Auth": null,
|
||||
"Compress": false
|
||||
},
|
||||
@@ -31,6 +36,7 @@ func Test_doOnJSON(t *testing.T) {
|
||||
"Certificates": null,
|
||||
"ClientCAFiles": null
|
||||
},
|
||||
"Redirect": null,
|
||||
"Auth": null,
|
||||
"Compress": false
|
||||
}
|
||||
@@ -58,7 +64,7 @@ func Test_doOnJSON(t *testing.T) {
|
||||
"DNSProvider": "",
|
||||
"DelayDontCheckDNS": 0,
|
||||
"ACMELogging": false,
|
||||
"TLSOptions": null
|
||||
"TLSConfig": null
|
||||
},
|
||||
"DefaultEntryPoints": [
|
||||
"https",
|
||||
@@ -103,6 +109,11 @@ func Test_doOnJSON(t *testing.T) {
|
||||
"Network": "",
|
||||
"Address": ":80",
|
||||
"TLS": null,
|
||||
"Redirect": {
|
||||
"EntryPoint": "https",
|
||||
"Regex": "",
|
||||
"Replacement": ""
|
||||
},
|
||||
"Auth": null,
|
||||
"Compress": false
|
||||
},
|
||||
@@ -114,6 +125,7 @@ func Test_doOnJSON(t *testing.T) {
|
||||
"Certificates": null,
|
||||
"ClientCAFiles": null
|
||||
},
|
||||
"Redirect": null,
|
||||
"Auth": null,
|
||||
"Compress": false
|
||||
}
|
||||
@@ -141,7 +153,7 @@ func Test_doOnJSON(t *testing.T) {
|
||||
"DNSProvider": "",
|
||||
"DelayDontCheckDNS": 0,
|
||||
"ACMELogging": false,
|
||||
"TLSOptions": null
|
||||
"TLSConfig": null
|
||||
},
|
||||
"DefaultEntryPoints": [
|
||||
"https",
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
"github.com/containous/traefik/log"
|
||||
assetfs "github.com/elazarl/go-bindata-assetfs"
|
||||
)
|
||||
|
||||
@@ -13,10 +13,10 @@ type DashboardHandler struct {
|
||||
Assets *assetfs.AssetFS
|
||||
}
|
||||
|
||||
// Append add dashboard routes on a router
|
||||
func (g DashboardHandler) Append(router *mux.Router) {
|
||||
// AddRoutes add dashboard routes on a router
|
||||
func (g DashboardHandler) AddRoutes(router *mux.Router) {
|
||||
if g.Assets == nil {
|
||||
log.WithoutContext().Error("No assets for dashboard")
|
||||
log.Error("No assets for dashboard")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -24,13 +24,13 @@ func (g DashboardHandler) Append(router *mux.Router) {
|
||||
router.Methods(http.MethodGet).
|
||||
Path("/").
|
||||
HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||
http.Redirect(response, request, request.Header.Get("X-Forwarded-Prefix")+"/dashboard/", http.StatusFound)
|
||||
http.Redirect(response, request, request.Header.Get("X-Forwarded-Prefix")+"/dashboard/", 302)
|
||||
})
|
||||
|
||||
router.Methods(http.MethodGet).
|
||||
Path("/dashboard/status").
|
||||
HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||
http.Redirect(response, request, "/dashboard/", http.StatusFound)
|
||||
http.Redirect(response, request, "/dashboard/", 302)
|
||||
})
|
||||
|
||||
router.Methods(http.MethodGet).
|
||||
@@ -11,8 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
// FIXME Goroutines2 -> Goroutines
|
||||
expvar.Publish("Goroutines2", expvar.Func(goroutines))
|
||||
expvar.Publish("Goroutines", expvar.Func(goroutines))
|
||||
}
|
||||
|
||||
func goroutines() interface{} {
|
||||
@@ -22,8 +21,8 @@ func goroutines() interface{} {
|
||||
// DebugHandler expose debug routes
|
||||
type DebugHandler struct{}
|
||||
|
||||
// Append add debug routes on a router
|
||||
func (g DebugHandler) Append(router *mux.Router) {
|
||||
// AddRoutes add debug routes on a router
|
||||
func (g DebugHandler) AddRoutes(router *mux.Router) {
|
||||
router.Methods(http.MethodGet).Path("/debug/vars").
|
||||
HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
252
api/handler.go
Normal file
@@ -0,0 +1,252 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/middlewares"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/containous/traefik/version"
|
||||
assetfs "github.com/elazarl/go-bindata-assetfs"
|
||||
thoas_stats "github.com/thoas/stats"
|
||||
"github.com/unrolled/render"
|
||||
)
|
||||
|
||||
// Handler expose api routes
|
||||
type Handler struct {
|
||||
EntryPoint string `description:"EntryPoint" export:"true"`
|
||||
Dashboard bool `description:"Activate dashboard" export:"true"`
|
||||
Debug bool `export:"true"`
|
||||
CurrentConfigurations *safe.Safe
|
||||
Statistics *types.Statistics `description:"Enable more detailed statistics" export:"true"`
|
||||
Stats *thoas_stats.Stats `json:"-"`
|
||||
StatsRecorder *middlewares.StatsRecorder `json:"-"`
|
||||
DashboardAssets *assetfs.AssetFS `json:"-"`
|
||||
}
|
||||
|
||||
var (
|
||||
templatesRenderer = render.New(render.Options{
|
||||
Directory: "nowhere",
|
||||
})
|
||||
)
|
||||
|
||||
// AddRoutes add api routes on a router
|
||||
func (p Handler) AddRoutes(router *mux.Router) {
|
||||
if p.Debug {
|
||||
DebugHandler{}.AddRoutes(router)
|
||||
}
|
||||
|
||||
router.Methods(http.MethodGet).Path("/api").HandlerFunc(p.getConfigHandler)
|
||||
router.Methods(http.MethodGet).Path("/api/providers").HandlerFunc(p.getConfigHandler)
|
||||
router.Methods(http.MethodGet).Path("/api/providers/{provider}").HandlerFunc(p.getProviderHandler)
|
||||
router.Methods(http.MethodGet).Path("/api/providers/{provider}/backends").HandlerFunc(p.getBackendsHandler)
|
||||
router.Methods(http.MethodGet).Path("/api/providers/{provider}/backends/{backend}").HandlerFunc(p.getBackendHandler)
|
||||
router.Methods(http.MethodGet).Path("/api/providers/{provider}/backends/{backend}/servers").HandlerFunc(p.getServersHandler)
|
||||
router.Methods(http.MethodGet).Path("/api/providers/{provider}/backends/{backend}/servers/{server}").HandlerFunc(p.getServerHandler)
|
||||
router.Methods(http.MethodGet).Path("/api/providers/{provider}/frontends").HandlerFunc(p.getFrontendsHandler)
|
||||
router.Methods(http.MethodGet).Path("/api/providers/{provider}/frontends/{frontend}").HandlerFunc(p.getFrontendHandler)
|
||||
router.Methods(http.MethodGet).Path("/api/providers/{provider}/frontends/{frontend}/routes").HandlerFunc(p.getRoutesHandler)
|
||||
router.Methods(http.MethodGet).Path("/api/providers/{provider}/frontends/{frontend}/routes/{route}").HandlerFunc(p.getRouteHandler)
|
||||
|
||||
// health route
|
||||
router.Methods(http.MethodGet).Path("/health").HandlerFunc(p.getHealthHandler)
|
||||
|
||||
version.Handler{}.AddRoutes(router)
|
||||
|
||||
if p.Dashboard {
|
||||
DashboardHandler{Assets: p.DashboardAssets}.AddRoutes(router)
|
||||
}
|
||||
}
|
||||
|
||||
func getProviderIDFromVars(vars map[string]string) string {
|
||||
providerID := vars["provider"]
|
||||
// TODO: Deprecated
|
||||
if providerID == "rest" {
|
||||
providerID = "web"
|
||||
}
|
||||
return providerID
|
||||
}
|
||||
|
||||
func (p Handler) getConfigHandler(response http.ResponseWriter, request *http.Request) {
|
||||
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||
err := templatesRenderer.JSON(response, http.StatusOK, currentConfigurations)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (p Handler) getProviderHandler(response http.ResponseWriter, request *http.Request) {
|
||||
providerID := getProviderIDFromVars(mux.Vars(request))
|
||||
|
||||
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||
if provider, ok := currentConfigurations[providerID]; ok {
|
||||
err := templatesRenderer.JSON(response, http.StatusOK, provider)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
} else {
|
||||
http.NotFound(response, request)
|
||||
}
|
||||
}
|
||||
|
||||
func (p Handler) getBackendsHandler(response http.ResponseWriter, request *http.Request) {
|
||||
providerID := getProviderIDFromVars(mux.Vars(request))
|
||||
|
||||
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||
if provider, ok := currentConfigurations[providerID]; ok {
|
||||
err := templatesRenderer.JSON(response, http.StatusOK, provider.Backends)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
} else {
|
||||
http.NotFound(response, request)
|
||||
}
|
||||
}
|
||||
|
||||
func (p Handler) getBackendHandler(response http.ResponseWriter, request *http.Request) {
|
||||
vars := mux.Vars(request)
|
||||
providerID := getProviderIDFromVars(vars)
|
||||
backendID := vars["backend"]
|
||||
|
||||
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||
if provider, ok := currentConfigurations[providerID]; ok {
|
||||
if backend, ok := provider.Backends[backendID]; ok {
|
||||
err := templatesRenderer.JSON(response, http.StatusOK, backend)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
http.NotFound(response, request)
|
||||
}
|
||||
|
||||
func (p Handler) getServersHandler(response http.ResponseWriter, request *http.Request) {
|
||||
vars := mux.Vars(request)
|
||||
providerID := getProviderIDFromVars(vars)
|
||||
backendID := vars["backend"]
|
||||
|
||||
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||
if provider, ok := currentConfigurations[providerID]; ok {
|
||||
if backend, ok := provider.Backends[backendID]; ok {
|
||||
err := templatesRenderer.JSON(response, http.StatusOK, backend.Servers)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
http.NotFound(response, request)
|
||||
}
|
||||
|
||||
func (p Handler) getServerHandler(response http.ResponseWriter, request *http.Request) {
|
||||
vars := mux.Vars(request)
|
||||
providerID := getProviderIDFromVars(vars)
|
||||
backendID := vars["backend"]
|
||||
serverID := vars["server"]
|
||||
|
||||
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||
if provider, ok := currentConfigurations[providerID]; ok {
|
||||
if backend, ok := provider.Backends[backendID]; ok {
|
||||
if server, ok := backend.Servers[serverID]; ok {
|
||||
err := templatesRenderer.JSON(response, http.StatusOK, server)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
http.NotFound(response, request)
|
||||
}
|
||||
|
||||
func (p Handler) getFrontendsHandler(response http.ResponseWriter, request *http.Request) {
|
||||
providerID := getProviderIDFromVars(mux.Vars(request))
|
||||
|
||||
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||
if provider, ok := currentConfigurations[providerID]; ok {
|
||||
err := templatesRenderer.JSON(response, http.StatusOK, provider.Frontends)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
} else {
|
||||
http.NotFound(response, request)
|
||||
}
|
||||
}
|
||||
|
||||
func (p Handler) getFrontendHandler(response http.ResponseWriter, request *http.Request) {
|
||||
vars := mux.Vars(request)
|
||||
providerID := getProviderIDFromVars(vars)
|
||||
frontendID := vars["frontend"]
|
||||
|
||||
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||
if provider, ok := currentConfigurations[providerID]; ok {
|
||||
if frontend, ok := provider.Frontends[frontendID]; ok {
|
||||
err := templatesRenderer.JSON(response, http.StatusOK, frontend)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
http.NotFound(response, request)
|
||||
}
|
||||
|
||||
func (p Handler) getRoutesHandler(response http.ResponseWriter, request *http.Request) {
|
||||
vars := mux.Vars(request)
|
||||
providerID := getProviderIDFromVars(vars)
|
||||
frontendID := vars["frontend"]
|
||||
|
||||
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||
if provider, ok := currentConfigurations[providerID]; ok {
|
||||
if frontend, ok := provider.Frontends[frontendID]; ok {
|
||||
err := templatesRenderer.JSON(response, http.StatusOK, frontend.Routes)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
http.NotFound(response, request)
|
||||
}
|
||||
|
||||
func (p Handler) getRouteHandler(response http.ResponseWriter, request *http.Request) {
|
||||
vars := mux.Vars(request)
|
||||
providerID := getProviderIDFromVars(vars)
|
||||
frontendID := vars["frontend"]
|
||||
routeID := vars["route"]
|
||||
|
||||
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||
if provider, ok := currentConfigurations[providerID]; ok {
|
||||
if frontend, ok := provider.Frontends[frontendID]; ok {
|
||||
if route, ok := frontend.Routes[routeID]; ok {
|
||||
err := templatesRenderer.JSON(response, http.StatusOK, route)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
http.NotFound(response, request)
|
||||
}
|
||||
|
||||
// healthResponse combines data returned by thoas/stats with statistics (if
|
||||
// they are enabled).
|
||||
type healthResponse struct {
|
||||
*thoas_stats.Data
|
||||
*middlewares.Stats
|
||||
}
|
||||
|
||||
func (p *Handler) getHealthHandler(response http.ResponseWriter, request *http.Request) {
|
||||
health := &healthResponse{Data: p.Stats.Data()}
|
||||
if p.StatsRecorder != nil {
|
||||
health.Stats = p.StatsRecorder.Data()
|
||||
}
|
||||
err := templatesRenderer.JSON(response, http.StatusOK, health)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
3125
autogen/gentemplates/gen.go
Normal file
@@ -1,19 +1,17 @@
|
||||
FROM golang:1.12-alpine
|
||||
FROM golang:1.11-alpine
|
||||
|
||||
RUN apk --update upgrade \
|
||||
&& apk --no-cache --no-progress add git mercurial bash gcc musl-dev curl tar \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
&& apk --no-cache --no-progress add git mercurial bash gcc musl-dev curl tar ca-certificates tzdata \
|
||||
&& update-ca-certificates \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
# Download golangci-lint and misspell binary to bin folder in $GOPATH
|
||||
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.15.0 \
|
||||
&& go get github.com/client9/misspell/cmd/misspell
|
||||
|
||||
# Download goreleaser binary to bin folder in $GOPATH
|
||||
RUN curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | sh
|
||||
RUN go get golang.org/x/lint/golint \
|
||||
&& go get github.com/kisielk/errcheck \
|
||||
&& go get github.com/client9/misspell/cmd/misspell
|
||||
|
||||
# Which docker version to test on
|
||||
ARG DOCKER_VERSION=17.03.2
|
||||
ARG DEP_VERSION=0.5.0
|
||||
ARG DEP_VERSION=0.5.1
|
||||
|
||||
# Download go-bindata binary to bin folder in $GOPATH
|
||||
RUN mkdir -p /usr/local/bin \
|
||||
|
||||
247
cluster/datastore.go
Normal file
@@ -0,0 +1,247 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/abronan/valkeyrie/store"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/staert"
|
||||
"github.com/containous/traefik/job"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Metadata stores Object plus metadata
|
||||
type Metadata struct {
|
||||
object Object
|
||||
Object []byte
|
||||
Lock string
|
||||
}
|
||||
|
||||
// NewMetadata returns new Metadata
|
||||
func NewMetadata(object Object) *Metadata {
|
||||
return &Metadata{object: object}
|
||||
}
|
||||
|
||||
// Marshall marshalls object
|
||||
func (m *Metadata) Marshall() error {
|
||||
var err error
|
||||
m.Object, err = json.Marshal(m.object)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Metadata) unmarshall() error {
|
||||
if len(m.Object) == 0 {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(m.Object, m.object)
|
||||
}
|
||||
|
||||
// Listener is called when Object has been changed in KV store
|
||||
type Listener func(Object) error
|
||||
|
||||
var _ Store = (*Datastore)(nil)
|
||||
|
||||
// Datastore holds a struct synced in a KV store
|
||||
type Datastore struct {
|
||||
kv staert.KvSource
|
||||
ctx context.Context
|
||||
localLock *sync.RWMutex
|
||||
meta *Metadata
|
||||
lockKey string
|
||||
listener Listener
|
||||
}
|
||||
|
||||
// NewDataStore creates a Datastore
|
||||
func NewDataStore(ctx context.Context, kvSource staert.KvSource, object Object, listener Listener) (*Datastore, error) {
|
||||
datastore := Datastore{
|
||||
kv: kvSource,
|
||||
ctx: ctx,
|
||||
meta: &Metadata{object: object},
|
||||
lockKey: kvSource.Prefix + "/lock",
|
||||
localLock: &sync.RWMutex{},
|
||||
listener: listener,
|
||||
}
|
||||
err := datastore.watchChanges()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &datastore, nil
|
||||
}
|
||||
|
||||
func (d *Datastore) watchChanges() error {
|
||||
stopCh := make(chan struct{})
|
||||
kvCh, err := d.kv.Watch(d.lockKey, stopCh, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while watching key %s: %v", d.lockKey, err)
|
||||
}
|
||||
safe.Go(func() {
|
||||
ctx, cancel := context.WithCancel(d.ctx)
|
||||
operation := func() error {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
stopCh <- struct{}{}
|
||||
return nil
|
||||
case _, ok := <-kvCh:
|
||||
if !ok {
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
err = d.reload()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.listener != nil {
|
||||
err := d.listener(d.meta.object)
|
||||
if err != nil {
|
||||
log.Errorf("Error calling datastore listener: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Error in watch datastore: %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
log.Errorf("Error in watch datastore: %v", err)
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Datastore) reload() error {
|
||||
log.Debug("Datastore reload")
|
||||
_, err := d.Load()
|
||||
return err
|
||||
}
|
||||
|
||||
// Begin creates a transaction with the KV store.
|
||||
func (d *Datastore) Begin() (Transaction, Object, error) {
|
||||
id := uuid.New().String()
|
||||
log.Debugf("Transaction %s begins", id)
|
||||
remoteLock, err := d.kv.NewLock(d.lockKey, &store.LockOptions{TTL: 20 * time.Second, Value: []byte(id)})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
stopCh := make(chan struct{})
|
||||
ctx, cancel := context.WithCancel(d.ctx)
|
||||
var errLock error
|
||||
go func() {
|
||||
_, errLock = remoteLock.Lock(stopCh)
|
||||
cancel()
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if errLock != nil {
|
||||
return nil, nil, errLock
|
||||
}
|
||||
case <-d.ctx.Done():
|
||||
stopCh <- struct{}{}
|
||||
return nil, nil, d.ctx.Err()
|
||||
}
|
||||
|
||||
// we got the lock! Now make sure we are synced with KV store
|
||||
operation := func() error {
|
||||
meta := d.get()
|
||||
if meta.Lock != id {
|
||||
return fmt.Errorf("object lock value: expected %s, got %s", id, meta.Lock)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Datastore sync error: %v, retrying in %s", err, time)
|
||||
err = d.reload()
|
||||
if err != nil {
|
||||
log.Errorf("Error reloading: %+v", err)
|
||||
}
|
||||
}
|
||||
ebo := backoff.NewExponentialBackOff()
|
||||
ebo.MaxElapsedTime = 60 * time.Second
|
||||
err = backoff.RetryNotify(safe.OperationWithRecover(operation), ebo, notify)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("datastore cannot sync: %v", err)
|
||||
}
|
||||
|
||||
// we synced with KV store, we can now return Setter
|
||||
return &datastoreTransaction{
|
||||
Datastore: d,
|
||||
remoteLock: remoteLock,
|
||||
id: id,
|
||||
}, d.meta.object, nil
|
||||
}
|
||||
|
||||
func (d *Datastore) get() *Metadata {
|
||||
d.localLock.RLock()
|
||||
defer d.localLock.RUnlock()
|
||||
return d.meta
|
||||
}
|
||||
|
||||
// Load load atomically a struct from the KV store
|
||||
func (d *Datastore) Load() (Object, error) {
|
||||
d.localLock.Lock()
|
||||
defer d.localLock.Unlock()
|
||||
|
||||
// clear Object first, as mapstructure's decoder doesn't have ZeroFields set to true for merging purposes
|
||||
d.meta.Object = d.meta.Object[:0]
|
||||
|
||||
err := d.kv.LoadConfig(d.meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = d.meta.unmarshall()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.meta.object, nil
|
||||
}
|
||||
|
||||
// Get atomically a struct from the KV store
|
||||
func (d *Datastore) Get() Object {
|
||||
d.localLock.RLock()
|
||||
defer d.localLock.RUnlock()
|
||||
return d.meta.object
|
||||
}
|
||||
|
||||
var _ Transaction = (*datastoreTransaction)(nil)
|
||||
|
||||
type datastoreTransaction struct {
|
||||
*Datastore
|
||||
remoteLock store.Locker
|
||||
dirty bool
|
||||
id string
|
||||
}
|
||||
|
||||
// Commit allows to set an object in the KV store
|
||||
func (s *datastoreTransaction) Commit(object Object) error {
|
||||
s.localLock.Lock()
|
||||
defer s.localLock.Unlock()
|
||||
if s.dirty {
|
||||
return fmt.Errorf("transaction already used, please begin a new one")
|
||||
}
|
||||
s.Datastore.meta.object = object
|
||||
err := s.Datastore.meta.Marshall()
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshall error: %s", err)
|
||||
}
|
||||
err = s.kv.StoreConfig(s.Datastore.meta)
|
||||
if err != nil {
|
||||
return fmt.Errorf("StoreConfig error: %s", err)
|
||||
}
|
||||
|
||||
err = s.remoteLock.Unlock()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unlock error: %s", err)
|
||||
}
|
||||
|
||||
s.dirty = true
|
||||
log.Debugf("Transaction committed %s", s.id)
|
||||
return nil
|
||||
}
|
||||
146
cluster/leadership.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/docker/leadership"
|
||||
"github.com/unrolled/render"
|
||||
)
|
||||
|
||||
const clusterLeaderKeySuffix = "/leader"
|
||||
|
||||
var templatesRenderer = render.New(render.Options{
|
||||
Directory: "nowhere",
|
||||
})
|
||||
|
||||
// Leadership allows leadership election using a KV store
|
||||
type Leadership struct {
|
||||
*safe.Pool
|
||||
*types.Cluster
|
||||
candidate *leadership.Candidate
|
||||
leader *safe.Safe
|
||||
listeners []LeaderListener
|
||||
}
|
||||
|
||||
// NewLeadership creates a leadership
|
||||
func NewLeadership(ctx context.Context, cluster *types.Cluster) *Leadership {
|
||||
return &Leadership{
|
||||
Pool: safe.NewPool(ctx),
|
||||
Cluster: cluster,
|
||||
candidate: leadership.NewCandidate(cluster.Store, cluster.Store.Prefix+clusterLeaderKeySuffix, cluster.Node, 20*time.Second),
|
||||
listeners: []LeaderListener{},
|
||||
leader: safe.New(false),
|
||||
}
|
||||
}
|
||||
|
||||
// LeaderListener is called when leadership has changed
|
||||
type LeaderListener func(elected bool) error
|
||||
|
||||
// Participate tries to be a leader
|
||||
func (l *Leadership) Participate(pool *safe.Pool) {
|
||||
pool.GoCtx(func(ctx context.Context) {
|
||||
log.Debugf("Node %s running for election", l.Cluster.Node)
|
||||
defer log.Debugf("Node %s no more running for election", l.Cluster.Node)
|
||||
backOff := backoff.NewExponentialBackOff()
|
||||
operation := func() error {
|
||||
return l.run(ctx, l.candidate)
|
||||
}
|
||||
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Leadership election error %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), backOff, notify)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot elect leadership %+v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// AddListener adds a leadership listener
|
||||
func (l *Leadership) AddListener(listener LeaderListener) {
|
||||
l.listeners = append(l.listeners, listener)
|
||||
}
|
||||
|
||||
// Resign resigns from being a leader
|
||||
func (l *Leadership) Resign() {
|
||||
l.candidate.Resign()
|
||||
log.Infof("Node %s resigned", l.Cluster.Node)
|
||||
}
|
||||
|
||||
func (l *Leadership) run(ctx context.Context, candidate *leadership.Candidate) error {
|
||||
electedCh, errCh := candidate.RunForElection()
|
||||
for {
|
||||
select {
|
||||
case elected := <-electedCh:
|
||||
l.onElection(elected)
|
||||
case err := <-errCh:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
l.candidate.Resign()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Leadership) onElection(elected bool) {
|
||||
if elected {
|
||||
log.Infof("Node %s elected leader ♚", l.Cluster.Node)
|
||||
l.leader.Set(true)
|
||||
l.Start()
|
||||
} else {
|
||||
log.Infof("Node %s elected worker ♝", l.Cluster.Node)
|
||||
l.leader.Set(false)
|
||||
l.Stop()
|
||||
}
|
||||
for _, listener := range l.listeners {
|
||||
err := listener(elected)
|
||||
if err != nil {
|
||||
log.Errorf("Error calling Leadership listener: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type leaderResponse struct {
|
||||
Leader bool `json:"leader"`
|
||||
LeaderNode string `json:"leader_node"`
|
||||
}
|
||||
|
||||
func (l *Leadership) getLeaderHandler(response http.ResponseWriter, request *http.Request) {
|
||||
leaderNode := ""
|
||||
leaderKv, err := l.Cluster.Store.Get(l.Cluster.Store.Prefix+clusterLeaderKeySuffix, nil)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
} else {
|
||||
leaderNode = string(leaderKv.Value)
|
||||
}
|
||||
leader := &leaderResponse{Leader: l.IsLeader(), LeaderNode: leaderNode}
|
||||
|
||||
status := http.StatusOK
|
||||
if !leader.Leader {
|
||||
// Set status to be `429`, as this will typically cause load balancers to stop sending requests to the instance without removing them from rotation.
|
||||
status = http.StatusTooManyRequests
|
||||
}
|
||||
|
||||
err = templatesRenderer.JSON(response, status, leader)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// IsLeader returns true if current node is leader
|
||||
func (l *Leadership) IsLeader() bool {
|
||||
return l.leader.Get().(bool)
|
||||
}
|
||||
|
||||
// AddRoutes add dashboard routes on a router
|
||||
func (l *Leadership) AddRoutes(router *mux.Router) {
|
||||
// Expose cluster leader
|
||||
router.Methods(http.MethodGet).Path("/api/cluster/leader").HandlerFunc(l.getLeaderHandler)
|
||||
}
|
||||
16
cluster/store.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package cluster
|
||||
|
||||
// Object is the struct to store
|
||||
type Object interface{}
|
||||
|
||||
// Store is a generic interface to represents a storage
|
||||
type Store interface {
|
||||
Load() (Object, error)
|
||||
Get() Object
|
||||
Begin() (Transaction, Object, error)
|
||||
}
|
||||
|
||||
// Transaction allows to set a struct in the KV store
|
||||
type Transaction interface {
|
||||
Commit(object Object) error
|
||||
}
|
||||
171
cmd/bug/bug.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package bug
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"text/template"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik/anonymize"
|
||||
"github.com/containous/traefik/cmd"
|
||||
"github.com/containous/traefik/cmd/version"
|
||||
)
|
||||
|
||||
const (
|
||||
bugTracker = "https://github.com/containous/traefik/issues/new"
|
||||
bugTemplate = `<!--
|
||||
DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||
|
||||
The issue tracker is for reporting bugs and feature requests only.
|
||||
For end-user related support questions, refer to one of the following:
|
||||
|
||||
- Stack Overflow (using the "traefik" tag): https://stackoverflow.com/questions/tagged/traefik
|
||||
- the Traefik community Slack channel: https://slack.traefik.io
|
||||
|
||||
-->
|
||||
|
||||
### Do you want to request a *feature* or report a *bug*?
|
||||
|
||||
(If you intend to ask a support question: **DO NOT FILE AN ISSUE**.
|
||||
Use [Stack Overflow](https://stackoverflow.com/questions/tagged/traefik)
|
||||
or [Slack](https://slack.traefik.io) instead.)
|
||||
|
||||
|
||||
|
||||
### What did you do?
|
||||
|
||||
<!--
|
||||
|
||||
HOW TO WRITE A GOOD ISSUE?
|
||||
|
||||
- Respect the issue template as more as possible.
|
||||
- If it's possible use the command ` + "`" + "traefik bug" + "`" + `. See https://www.youtube.com/watch?v=Lyz62L8m93I.
|
||||
- The title must be short and descriptive.
|
||||
- Explain the conditions which led you to write this issue: the context.
|
||||
- The context should lead to something, an idea or a problem that you’re facing.
|
||||
- Remain clear and concise.
|
||||
- Format your messages to help the reader focus on what matters and understand the structure of your message, use Markdown syntax https://help.github.com/articles/github-flavored-markdown
|
||||
|
||||
-->
|
||||
|
||||
|
||||
### What did you expect to see?
|
||||
|
||||
|
||||
|
||||
### What did you see instead?
|
||||
|
||||
|
||||
|
||||
### Output of ` + "`" + `traefik version` + "`" + `: (_What version of Traefik are you using?_)
|
||||
|
||||
` + "```" + `
|
||||
{{.Version}}
|
||||
` + "```" + `
|
||||
|
||||
### What is your environment & configuration (arguments, toml, provider, platform, ...)?
|
||||
|
||||
` + "```" + `json
|
||||
{{.Configuration}}
|
||||
` + "```" + `
|
||||
|
||||
<!--
|
||||
Add more configuration information here.
|
||||
-->
|
||||
|
||||
### If applicable, please paste the log output at DEBUG level (` + "`" + `--logLevel=DEBUG` + "`" + ` switch)
|
||||
|
||||
` + "```" + `
|
||||
(paste your output here)
|
||||
` + "```" + `
|
||||
|
||||
`
|
||||
)
|
||||
|
||||
// NewCmd builds a new Bug command
|
||||
func NewCmd(traefikConfiguration *cmd.TraefikConfiguration, traefikPointersConfiguration *cmd.TraefikConfiguration) *flaeg.Command {
|
||||
|
||||
// version Command init
|
||||
return &flaeg.Command{
|
||||
Name: "bug",
|
||||
Description: `Report an issue on Traefik bugtracker`,
|
||||
Config: traefikConfiguration,
|
||||
DefaultPointersConfig: traefikPointersConfiguration,
|
||||
Run: runCmd(traefikConfiguration),
|
||||
Metadata: map[string]string{
|
||||
"parseAllSources": "true",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func runCmd(traefikConfiguration *cmd.TraefikConfiguration) func() error {
|
||||
return func() error {
|
||||
|
||||
body, err := createReport(traefikConfiguration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sendReport(body)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func createReport(traefikConfiguration *cmd.TraefikConfiguration) (string, error) {
|
||||
var versionPrint bytes.Buffer
|
||||
if err := version.GetPrint(&versionPrint); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tmpl, err := template.New("bug").Parse(bugTemplate)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
config, err := anonymize.Do(traefikConfiguration, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
v := struct {
|
||||
Version string
|
||||
Configuration string
|
||||
}{
|
||||
Version: versionPrint.String(),
|
||||
Configuration: config,
|
||||
}
|
||||
|
||||
var bug bytes.Buffer
|
||||
if err := tmpl.Execute(&bug, v); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return bug.String(), nil
|
||||
}
|
||||
|
||||
func sendReport(body string) {
|
||||
URL := bugTracker + "?body=" + url.QueryEscape(body)
|
||||
if err := openBrowser(URL); err != nil {
|
||||
fmt.Printf("Please file a new issue at %s using this template:\n\n", bugTracker)
|
||||
fmt.Print(body)
|
||||
}
|
||||
}
|
||||
|
||||
func openBrowser(URL string) error {
|
||||
var err error
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
err = exec.Command("xdg-open", URL).Start()
|
||||
case "windows":
|
||||
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", URL).Start()
|
||||
case "darwin":
|
||||
err = exec.Command("open", URL).Start()
|
||||
default:
|
||||
err = fmt.Errorf("unsupported platform")
|
||||
}
|
||||
return err
|
||||
}
|
||||
67
cmd/bug/bug_test.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package bug
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/anonymize"
|
||||
"github.com/containous/traefik/cmd"
|
||||
"github.com/containous/traefik/configuration"
|
||||
"github.com/containous/traefik/provider/file"
|
||||
"github.com/containous/traefik/tls"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_createReport(t *testing.T) {
|
||||
traefikConfiguration := &cmd.TraefikConfiguration{
|
||||
ConfigFile: "FOO",
|
||||
GlobalConfiguration: configuration.GlobalConfiguration{
|
||||
EntryPoints: configuration.EntryPoints{
|
||||
"goo": &configuration.EntryPoint{
|
||||
Address: "hoo.bar",
|
||||
Auth: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
UsersFile: "foo Basic UsersFile",
|
||||
Users: types.Users{"foo Basic Users 1", "foo Basic Users 2", "foo Basic Users 3"},
|
||||
},
|
||||
Digest: &types.Digest{
|
||||
UsersFile: "foo Digest UsersFile",
|
||||
Users: types.Users{"foo Digest Users 1", "foo Digest Users 2", "foo Digest Users 3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
File: &file.Provider{
|
||||
Directory: "BAR",
|
||||
},
|
||||
RootCAs: tls.FilesOrContents{"fllf"},
|
||||
},
|
||||
}
|
||||
|
||||
report, err := createReport(traefikConfiguration)
|
||||
assert.NoError(t, err, report)
|
||||
|
||||
// exported anonymous configuration
|
||||
assert.NotContains(t, "web Basic Users ", report)
|
||||
assert.NotContains(t, "foo Digest Users ", report)
|
||||
assert.NotContains(t, "hoo.bar", report)
|
||||
}
|
||||
|
||||
func Test_anonymize_traefikConfiguration(t *testing.T) {
|
||||
traefikConfiguration := &cmd.TraefikConfiguration{
|
||||
ConfigFile: "FOO",
|
||||
GlobalConfiguration: configuration.GlobalConfiguration{
|
||||
EntryPoints: configuration.EntryPoints{
|
||||
"goo": &configuration.EntryPoint{
|
||||
Address: "hoo.bar",
|
||||
},
|
||||
},
|
||||
File: &file.Provider{
|
||||
Directory: "BAR",
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err := anonymize.Do(traefikConfiguration, true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "hoo.bar", traefikConfiguration.GlobalConfiguration.EntryPoints["goo"].Address)
|
||||
}
|
||||
@@ -3,55 +3,182 @@ package cmd
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg/parse"
|
||||
"github.com/containous/traefik/pkg/config/static"
|
||||
"github.com/containous/traefik/pkg/middlewares/accesslog"
|
||||
"github.com/containous/traefik/pkg/ping"
|
||||
"github.com/containous/traefik/pkg/provider/docker"
|
||||
"github.com/containous/traefik/pkg/provider/file"
|
||||
"github.com/containous/traefik/pkg/provider/kubernetes/ingress"
|
||||
"github.com/containous/traefik/pkg/provider/marathon"
|
||||
"github.com/containous/traefik/pkg/provider/rest"
|
||||
"github.com/containous/traefik/pkg/tracing/datadog"
|
||||
"github.com/containous/traefik/pkg/tracing/instana"
|
||||
"github.com/containous/traefik/pkg/tracing/jaeger"
|
||||
"github.com/containous/traefik/pkg/tracing/zipkin"
|
||||
"github.com/containous/traefik/pkg/types"
|
||||
jaegercli "github.com/uber/jaeger-client-go"
|
||||
"github.com/containous/flaeg"
|
||||
servicefabric "github.com/containous/traefik-extra-service-fabric"
|
||||
"github.com/containous/traefik/api"
|
||||
"github.com/containous/traefik/configuration"
|
||||
"github.com/containous/traefik/middlewares/accesslog"
|
||||
"github.com/containous/traefik/middlewares/tracing"
|
||||
"github.com/containous/traefik/middlewares/tracing/datadog"
|
||||
"github.com/containous/traefik/middlewares/tracing/jaeger"
|
||||
"github.com/containous/traefik/middlewares/tracing/zipkin"
|
||||
"github.com/containous/traefik/ping"
|
||||
"github.com/containous/traefik/provider/boltdb"
|
||||
"github.com/containous/traefik/provider/consul"
|
||||
"github.com/containous/traefik/provider/consulcatalog"
|
||||
"github.com/containous/traefik/provider/docker"
|
||||
"github.com/containous/traefik/provider/dynamodb"
|
||||
"github.com/containous/traefik/provider/ecs"
|
||||
"github.com/containous/traefik/provider/etcd"
|
||||
"github.com/containous/traefik/provider/eureka"
|
||||
"github.com/containous/traefik/provider/file"
|
||||
"github.com/containous/traefik/provider/kubernetes"
|
||||
"github.com/containous/traefik/provider/marathon"
|
||||
"github.com/containous/traefik/provider/mesos"
|
||||
"github.com/containous/traefik/provider/rancher"
|
||||
"github.com/containous/traefik/provider/rest"
|
||||
"github.com/containous/traefik/provider/zk"
|
||||
"github.com/containous/traefik/types"
|
||||
sf "github.com/jjcollinge/servicefabric"
|
||||
)
|
||||
|
||||
// TraefikConfiguration holds GlobalConfiguration and other stuff
|
||||
type TraefikConfiguration struct {
|
||||
static.Configuration `mapstructure:",squash" export:"true"`
|
||||
ConfigFile string `short:"c" description:"Configuration file to use (TOML)." export:"true"`
|
||||
}
|
||||
|
||||
// NewTraefikConfiguration creates a TraefikConfiguration with default values
|
||||
func NewTraefikConfiguration() *TraefikConfiguration {
|
||||
return &TraefikConfiguration{
|
||||
Configuration: static.Configuration{
|
||||
Global: &static.Global{
|
||||
CheckNewVersion: true,
|
||||
},
|
||||
EntryPoints: make(static.EntryPoints),
|
||||
Providers: &static.Providers{
|
||||
ProvidersThrottleDuration: parse.Duration(2 * time.Second),
|
||||
},
|
||||
ServersTransport: &static.ServersTransport{
|
||||
MaxIdleConnsPerHost: 200,
|
||||
},
|
||||
},
|
||||
ConfigFile: "",
|
||||
}
|
||||
configuration.GlobalConfiguration `mapstructure:",squash" export:"true"`
|
||||
ConfigFile string `short:"c" description:"Configuration file to use (TOML)." export:"true"`
|
||||
}
|
||||
|
||||
// NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values
|
||||
func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||
// default Docker
|
||||
var defaultDocker docker.Provider
|
||||
defaultDocker.Watch = true
|
||||
defaultDocker.ExposedByDefault = true
|
||||
defaultDocker.Endpoint = "unix:///var/run/docker.sock"
|
||||
defaultDocker.SwarmMode = false
|
||||
defaultDocker.SwarmModeRefreshSeconds = 15
|
||||
|
||||
// default File
|
||||
var defaultFile file.Provider
|
||||
defaultFile.Watch = true
|
||||
defaultFile.Filename = "" // needs equivalent to viper.ConfigFileUsed()
|
||||
|
||||
// default Rest
|
||||
var defaultRest rest.Provider
|
||||
defaultRest.EntryPoint = configuration.DefaultInternalEntryPointName
|
||||
|
||||
// TODO: Deprecated - Web provider, use REST provider instead
|
||||
var defaultWeb configuration.WebCompatibility
|
||||
defaultWeb.Address = ":8080"
|
||||
defaultWeb.Statistics = &types.Statistics{
|
||||
RecentErrors: 10,
|
||||
}
|
||||
|
||||
// TODO: Deprecated - default Metrics
|
||||
defaultWeb.Metrics = &types.Metrics{
|
||||
Prometheus: &types.Prometheus{
|
||||
Buckets: types.Buckets{0.1, 0.3, 1.2, 5},
|
||||
EntryPoint: configuration.DefaultInternalEntryPointName,
|
||||
},
|
||||
Datadog: &types.Datadog{
|
||||
Address: "localhost:8125",
|
||||
PushInterval: "10s",
|
||||
},
|
||||
StatsD: &types.Statsd{
|
||||
Address: "localhost:8125",
|
||||
PushInterval: "10s",
|
||||
},
|
||||
InfluxDB: &types.InfluxDB{
|
||||
Address: "localhost:8089",
|
||||
Protocol: "udp",
|
||||
PushInterval: "10s",
|
||||
},
|
||||
}
|
||||
|
||||
// default Marathon
|
||||
var defaultMarathon marathon.Provider
|
||||
defaultMarathon.Watch = true
|
||||
defaultMarathon.Endpoint = "http://127.0.0.1:8080"
|
||||
defaultMarathon.ExposedByDefault = true
|
||||
defaultMarathon.Constraints = types.Constraints{}
|
||||
defaultMarathon.DialerTimeout = flaeg.Duration(5 * time.Second)
|
||||
defaultMarathon.ResponseHeaderTimeout = flaeg.Duration(60 * time.Second)
|
||||
defaultMarathon.TLSHandshakeTimeout = flaeg.Duration(5 * time.Second)
|
||||
defaultMarathon.KeepAlive = flaeg.Duration(10 * time.Second)
|
||||
|
||||
// default Consul
|
||||
var defaultConsul consul.Provider
|
||||
defaultConsul.Watch = true
|
||||
defaultConsul.Endpoint = "127.0.0.1:8500"
|
||||
defaultConsul.Prefix = "traefik"
|
||||
defaultConsul.Constraints = types.Constraints{}
|
||||
|
||||
// default CatalogProvider
|
||||
var defaultConsulCatalog consulcatalog.Provider
|
||||
defaultConsulCatalog.Endpoint = "127.0.0.1:8500"
|
||||
defaultConsulCatalog.ExposedByDefault = true
|
||||
defaultConsulCatalog.Constraints = types.Constraints{}
|
||||
defaultConsulCatalog.Prefix = "traefik"
|
||||
defaultConsulCatalog.FrontEndRule = "Host:{{.ServiceName}}.{{.Domain}}"
|
||||
defaultConsulCatalog.Stale = false
|
||||
|
||||
// default Etcd
|
||||
var defaultEtcd etcd.Provider
|
||||
defaultEtcd.Watch = true
|
||||
defaultEtcd.Endpoint = "127.0.0.1:2379"
|
||||
defaultEtcd.Prefix = "/traefik"
|
||||
defaultEtcd.Constraints = types.Constraints{}
|
||||
|
||||
// default Zookeeper
|
||||
var defaultZookeeper zk.Provider
|
||||
defaultZookeeper.Watch = true
|
||||
defaultZookeeper.Endpoint = "127.0.0.1:2181"
|
||||
defaultZookeeper.Prefix = "traefik"
|
||||
defaultZookeeper.Constraints = types.Constraints{}
|
||||
|
||||
// default Boltdb
|
||||
var defaultBoltDb boltdb.Provider
|
||||
defaultBoltDb.Watch = true
|
||||
defaultBoltDb.Endpoint = "127.0.0.1:4001"
|
||||
defaultBoltDb.Prefix = "/traefik"
|
||||
defaultBoltDb.Constraints = types.Constraints{}
|
||||
|
||||
// default Kubernetes
|
||||
var defaultKubernetes kubernetes.Provider
|
||||
defaultKubernetes.Watch = true
|
||||
defaultKubernetes.Constraints = types.Constraints{}
|
||||
|
||||
// default Mesos
|
||||
var defaultMesos mesos.Provider
|
||||
defaultMesos.Watch = true
|
||||
defaultMesos.Endpoint = "http://127.0.0.1:5050"
|
||||
defaultMesos.ExposedByDefault = true
|
||||
defaultMesos.Constraints = types.Constraints{}
|
||||
defaultMesos.RefreshSeconds = 30
|
||||
defaultMesos.ZkDetectionTimeout = 30
|
||||
defaultMesos.StateTimeoutSecond = 30
|
||||
|
||||
// default ECS
|
||||
var defaultECS ecs.Provider
|
||||
defaultECS.Watch = true
|
||||
defaultECS.ExposedByDefault = true
|
||||
defaultECS.AutoDiscoverClusters = false
|
||||
defaultECS.Clusters = ecs.Clusters{"default"}
|
||||
defaultECS.RefreshSeconds = 15
|
||||
defaultECS.Constraints = types.Constraints{}
|
||||
|
||||
// default Rancher
|
||||
var defaultRancher rancher.Provider
|
||||
defaultRancher.Watch = true
|
||||
defaultRancher.ExposedByDefault = true
|
||||
defaultRancher.RefreshSeconds = 15
|
||||
|
||||
// default DynamoDB
|
||||
var defaultDynamoDB dynamodb.Provider
|
||||
defaultDynamoDB.Constraints = types.Constraints{}
|
||||
defaultDynamoDB.RefreshSeconds = 15
|
||||
defaultDynamoDB.TableName = "traefik"
|
||||
defaultDynamoDB.Watch = true
|
||||
|
||||
// default Eureka
|
||||
var defaultEureka eureka.Provider
|
||||
defaultEureka.RefreshSeconds = flaeg.Duration(30 * time.Second)
|
||||
|
||||
// default ServiceFabric
|
||||
var defaultServiceFabric servicefabric.Provider
|
||||
defaultServiceFabric.APIVersion = sf.DefaultAPIVersion
|
||||
defaultServiceFabric.RefreshSeconds = 10
|
||||
|
||||
// default Ping
|
||||
var defaultPing = ping.Handler{
|
||||
EntryPoint: "traefik",
|
||||
@@ -76,8 +203,23 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||
},
|
||||
}
|
||||
|
||||
// default HealthCheckConfig
|
||||
healthCheck := configuration.HealthCheckConfig{
|
||||
Interval: flaeg.Duration(configuration.DefaultHealthCheckInterval),
|
||||
}
|
||||
|
||||
// default RespondingTimeouts
|
||||
respondingTimeouts := configuration.RespondingTimeouts{
|
||||
IdleTimeout: flaeg.Duration(configuration.DefaultIdleTimeout),
|
||||
}
|
||||
|
||||
// default ForwardingTimeouts
|
||||
forwardingTimeouts := configuration.ForwardingTimeouts{
|
||||
DialTimeout: flaeg.Duration(configuration.DefaultDialTimeout),
|
||||
}
|
||||
|
||||
// default Tracing
|
||||
defaultTracing := static.Tracing{
|
||||
defaultTracing := tracing.Tracing{
|
||||
Backend: "jaeger",
|
||||
ServiceName: "traefik",
|
||||
SpanNameLimit: 0,
|
||||
@@ -86,16 +228,13 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||
SamplingType: "const",
|
||||
SamplingParam: 1.0,
|
||||
LocalAgentHostPort: "127.0.0.1:6831",
|
||||
Propagation: "jaeger",
|
||||
Gen128Bit: false,
|
||||
TraceContextHeaderName: jaegercli.TraceContextHeaderName,
|
||||
TraceContextHeaderName: "uber-trace-id",
|
||||
},
|
||||
Zipkin: &zipkin.Config{
|
||||
HTTPEndpoint: "http://localhost:9411/api/v1/spans",
|
||||
SameSpan: false,
|
||||
ID128Bit: true,
|
||||
Debug: false,
|
||||
SampleRate: 1.0,
|
||||
},
|
||||
DataDog: &datadog.Config{
|
||||
LocalAgentHostPort: "localhost:8126",
|
||||
@@ -103,15 +242,15 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||
Debug: false,
|
||||
PrioritySampling: false,
|
||||
},
|
||||
Instana: &instana.Config{
|
||||
LocalAgentHost: "localhost",
|
||||
LocalAgentPort: 42699,
|
||||
LogLevel: "info",
|
||||
},
|
||||
}
|
||||
|
||||
// default LifeCycle
|
||||
defaultLifeCycle := configuration.LifeCycle{
|
||||
GraceTimeOut: flaeg.Duration(configuration.DefaultGraceTimeout),
|
||||
}
|
||||
|
||||
// default ApiConfiguration
|
||||
defaultAPI := static.API{
|
||||
defaultAPI := api.Handler{
|
||||
EntryPoint: "traefik",
|
||||
Dashboard: true,
|
||||
}
|
||||
@@ -123,7 +262,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||
defaultMetrics := types.Metrics{
|
||||
Prometheus: &types.Prometheus{
|
||||
Buckets: types.Buckets{0.1, 0.3, 1.2, 5},
|
||||
EntryPoint: static.DefaultInternalEntryPointName,
|
||||
EntryPoint: configuration.DefaultInternalEntryPointName,
|
||||
},
|
||||
Datadog: &types.Datadog{
|
||||
Address: "localhost:8125",
|
||||
@@ -140,57 +279,68 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||
},
|
||||
}
|
||||
|
||||
defaultResolver := types.HostResolverConfig{
|
||||
defaultResolver := configuration.HostResolverConfig{
|
||||
CnameFlattening: false,
|
||||
ResolvConfig: "/etc/resolv.conf",
|
||||
ResolvDepth: 5,
|
||||
}
|
||||
|
||||
var defaultDocker docker.Provider
|
||||
defaultDocker.Watch = true
|
||||
defaultDocker.ExposedByDefault = true
|
||||
defaultDocker.Endpoint = "unix:///var/run/docker.sock"
|
||||
defaultDocker.SwarmMode = false
|
||||
defaultDocker.SwarmModeRefreshSeconds = 15
|
||||
defaultDocker.DefaultRule = docker.DefaultTemplateRule
|
||||
|
||||
// default Rest
|
||||
var defaultRest rest.Provider
|
||||
defaultRest.EntryPoint = static.DefaultInternalEntryPointName
|
||||
|
||||
// default Marathon
|
||||
var defaultMarathon marathon.Provider
|
||||
defaultMarathon.Watch = true
|
||||
defaultMarathon.Endpoint = "http://127.0.0.1:8080"
|
||||
defaultMarathon.ExposedByDefault = true
|
||||
defaultMarathon.DialerTimeout = parse.Duration(5 * time.Second)
|
||||
defaultMarathon.ResponseHeaderTimeout = parse.Duration(60 * time.Second)
|
||||
defaultMarathon.TLSHandshakeTimeout = parse.Duration(5 * time.Second)
|
||||
defaultMarathon.KeepAlive = parse.Duration(10 * time.Second)
|
||||
defaultMarathon.DefaultRule = marathon.DefaultTemplateRule
|
||||
|
||||
// default Kubernetes
|
||||
var defaultKubernetes ingress.Provider
|
||||
defaultKubernetes.Watch = true
|
||||
|
||||
defaultProviders := static.Providers{
|
||||
File: &defaultFile,
|
||||
Docker: &defaultDocker,
|
||||
Rest: &defaultRest,
|
||||
Marathon: &defaultMarathon,
|
||||
Kubernetes: &defaultKubernetes,
|
||||
defaultConfiguration := configuration.GlobalConfiguration{
|
||||
Docker: &defaultDocker,
|
||||
File: &defaultFile,
|
||||
Web: &defaultWeb,
|
||||
Rest: &defaultRest,
|
||||
Marathon: &defaultMarathon,
|
||||
Consul: &defaultConsul,
|
||||
ConsulCatalog: &defaultConsulCatalog,
|
||||
Etcd: &defaultEtcd,
|
||||
Zookeeper: &defaultZookeeper,
|
||||
Boltdb: &defaultBoltDb,
|
||||
Kubernetes: &defaultKubernetes,
|
||||
Mesos: &defaultMesos,
|
||||
ECS: &defaultECS,
|
||||
Rancher: &defaultRancher,
|
||||
Eureka: &defaultEureka,
|
||||
DynamoDB: &defaultDynamoDB,
|
||||
Retry: &configuration.Retry{},
|
||||
HealthCheck: &healthCheck,
|
||||
RespondingTimeouts: &respondingTimeouts,
|
||||
ForwardingTimeouts: &forwardingTimeouts,
|
||||
TraefikLog: &defaultTraefikLog,
|
||||
AccessLog: &defaultAccessLog,
|
||||
LifeCycle: &defaultLifeCycle,
|
||||
Ping: &defaultPing,
|
||||
API: &defaultAPI,
|
||||
Metrics: &defaultMetrics,
|
||||
Tracing: &defaultTracing,
|
||||
HostResolver: &defaultResolver,
|
||||
}
|
||||
|
||||
return &TraefikConfiguration{
|
||||
Configuration: static.Configuration{
|
||||
Providers: &defaultProviders,
|
||||
Log: &defaultTraefikLog,
|
||||
AccessLog: &defaultAccessLog,
|
||||
Ping: &defaultPing,
|
||||
API: &defaultAPI,
|
||||
Metrics: &defaultMetrics,
|
||||
Tracing: &defaultTracing,
|
||||
HostResolver: &defaultResolver,
|
||||
},
|
||||
GlobalConfiguration: defaultConfiguration,
|
||||
}
|
||||
}
|
||||
|
||||
// NewTraefikConfiguration creates a TraefikConfiguration with default values
|
||||
func NewTraefikConfiguration() *TraefikConfiguration {
|
||||
return &TraefikConfiguration{
|
||||
GlobalConfiguration: configuration.GlobalConfiguration{
|
||||
AccessLogsFile: "",
|
||||
TraefikLogsFile: "",
|
||||
EntryPoints: map[string]*configuration.EntryPoint{},
|
||||
Constraints: types.Constraints{},
|
||||
DefaultEntryPoints: []string{"http"},
|
||||
ProvidersThrottleDuration: flaeg.Duration(2 * time.Second),
|
||||
MaxIdleConnsPerHost: 200,
|
||||
IdleTimeout: flaeg.Duration(0),
|
||||
HealthCheck: &configuration.HealthCheckConfig{
|
||||
Interval: flaeg.Duration(configuration.DefaultHealthCheckInterval),
|
||||
},
|
||||
LifeCycle: &configuration.LifeCycle{
|
||||
GraceTimeOut: flaeg.Duration(configuration.DefaultGraceTimeout),
|
||||
},
|
||||
CheckNewVersion: true,
|
||||
},
|
||||
ConfigFile: "",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// ContextWithSignal creates a context canceled when SIGINT or SIGTERM are notified
|
||||
// ContextWithSignal create a context cancelled when SIGINT or SIGTERM are notified
|
||||
func ContextWithSignal(ctx context.Context) context.Context {
|
||||
newCtx, cancel := context.WithCancel(ctx)
|
||||
signals := make(chan os.Signal)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package healthcheck
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -9,7 +10,7 @@ import (
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik/cmd"
|
||||
"github.com/containous/traefik/pkg/config/static"
|
||||
"github.com/containous/traefik/configuration"
|
||||
)
|
||||
|
||||
// NewCmd builds a new HealthCheck command
|
||||
@@ -28,9 +29,9 @@ func NewCmd(traefikConfiguration *cmd.TraefikConfiguration, traefikPointersConfi
|
||||
|
||||
func runCmd(traefikConfiguration *cmd.TraefikConfiguration) func() error {
|
||||
return func() error {
|
||||
traefikConfiguration.Configuration.SetEffectiveConfiguration(traefikConfiguration.ConfigFile)
|
||||
traefikConfiguration.GlobalConfiguration.SetEffectiveConfiguration(traefikConfiguration.ConfigFile)
|
||||
|
||||
resp, errPing := Do(traefikConfiguration.Configuration)
|
||||
resp, errPing := Do(traefikConfiguration.GlobalConfiguration)
|
||||
if errPing != nil {
|
||||
fmt.Printf("Error calling healthcheck: %s\n", errPing)
|
||||
os.Exit(1)
|
||||
@@ -46,28 +47,27 @@ func runCmd(traefikConfiguration *cmd.TraefikConfiguration) func() error {
|
||||
}
|
||||
|
||||
// Do try to do a healthcheck
|
||||
func Do(staticConfiguration static.Configuration) (*http.Response, error) {
|
||||
if staticConfiguration.Ping == nil {
|
||||
func Do(globalConfiguration configuration.GlobalConfiguration) (*http.Response, error) {
|
||||
if globalConfiguration.Ping == nil {
|
||||
return nil, errors.New("please enable `ping` to use health check")
|
||||
}
|
||||
pingEntryPoint, ok := staticConfiguration.EntryPoints[staticConfiguration.Ping.EntryPoint]
|
||||
pingEntryPoint, ok := globalConfiguration.EntryPoints[globalConfiguration.Ping.EntryPoint]
|
||||
if !ok {
|
||||
return nil, errors.New("missing `ping` entrypoint")
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
protocol := "http"
|
||||
|
||||
// FIXME Handle TLS on ping etc...
|
||||
// if pingEntryPoint.TLS != nil {
|
||||
// protocol = "https"
|
||||
// tr := &http.Transport{
|
||||
// TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
// }
|
||||
// client.Transport = tr
|
||||
// }
|
||||
|
||||
if pingEntryPoint.TLS != nil {
|
||||
protocol = "https"
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
client.Transport = tr
|
||||
}
|
||||
path := "/"
|
||||
|
||||
if globalConfiguration.Web != nil {
|
||||
path = globalConfiguration.Web.Path
|
||||
}
|
||||
return client.Head(protocol + "://" + pingEntryPoint.Address + path + "ping")
|
||||
}
|
||||
|
||||
@@ -3,21 +3,26 @@ package storeconfig
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
stdlog "log"
|
||||
"os"
|
||||
|
||||
"github.com/abronan/valkeyrie/store"
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/staert"
|
||||
"github.com/containous/traefik/acme"
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/cmd"
|
||||
"github.com/containous/traefik/log"
|
||||
)
|
||||
|
||||
// NewCmd builds a new StoreConfig command
|
||||
func NewCmd(traefikConfiguration *cmd.TraefikConfiguration, traefikPointersConfiguration *cmd.TraefikConfiguration) *flaeg.Command {
|
||||
return &flaeg.Command{
|
||||
Name: "storeconfig",
|
||||
Description: `Stores the static traefik configuration into a Key-value stores. Traefik will not start.`,
|
||||
Description: `Store the static traefik configuration into a Key-value stores. Traefik will not start.`,
|
||||
Config: traefikConfiguration,
|
||||
DefaultPointersConfig: traefikPointersConfiguration,
|
||||
HideHelp: true, // TODO storeconfig
|
||||
Metadata: map[string]string{
|
||||
"parseAllSources": "true",
|
||||
},
|
||||
@@ -31,21 +36,21 @@ func Run(kv *staert.KvSource, traefikConfiguration *cmd.TraefikConfiguration) fu
|
||||
return fmt.Errorf("error using command storeconfig, no Key-value store defined")
|
||||
}
|
||||
|
||||
fileConfig := traefikConfiguration.Providers.File
|
||||
fileConfig := traefikConfiguration.GlobalConfiguration.File
|
||||
if fileConfig != nil {
|
||||
traefikConfiguration.Providers.File = nil
|
||||
traefikConfiguration.GlobalConfiguration.File = nil
|
||||
if len(fileConfig.Filename) == 0 && len(fileConfig.Directory) == 0 {
|
||||
fileConfig.Filename = traefikConfiguration.ConfigFile
|
||||
}
|
||||
}
|
||||
|
||||
jsonConf, err := json.Marshal(traefikConfiguration.Configuration)
|
||||
jsonConf, err := json.Marshal(traefikConfiguration.GlobalConfiguration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stdlog.Printf("Storing configuration: %s\n", jsonConf)
|
||||
|
||||
err = kv.StoreConfig(traefikConfiguration.Configuration)
|
||||
err = kv.StoreConfig(traefikConfiguration.GlobalConfiguration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -69,82 +74,138 @@ func Run(kv *staert.KvSource, traefikConfiguration *cmd.TraefikConfiguration) fu
|
||||
}
|
||||
}
|
||||
|
||||
// if traefikConfiguration.Configuration.ACME != nil {
|
||||
// account := &acme.Account{}
|
||||
//
|
||||
// accountInitialized, err := keyExists(kv, traefikConfiguration.Configuration.ACME.Storage)
|
||||
// if err != nil && err != store.ErrKeyNotFound {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// // Check to see if ACME account object is already in kv store
|
||||
// if traefikConfiguration.Configuration.ACME.OverrideCertificates || !accountInitialized {
|
||||
//
|
||||
// // Stores the ACME Account into the KV Store
|
||||
// // Certificates in KV Stores will be overridden
|
||||
// meta := cluster.NewMetadata(account)
|
||||
// err = meta.Marshall()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// source := staert.KvSource{
|
||||
// Store: kv,
|
||||
// Prefix: traefikConfiguration.Configuration.ACME.Storage,
|
||||
// }
|
||||
//
|
||||
// err = source.StoreConfig(meta)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
if traefikConfiguration.GlobalConfiguration.ACME != nil {
|
||||
account := &acme.Account{}
|
||||
|
||||
// Migrate ACME data from file to KV store if needed
|
||||
if len(traefikConfiguration.GlobalConfiguration.ACME.StorageFile) > 0 {
|
||||
account, err = migrateACMEData(traefikConfiguration.GlobalConfiguration.ACME.StorageFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
accountInitialized, err := keyExists(kv, traefikConfiguration.GlobalConfiguration.ACME.Storage)
|
||||
if err != nil && err != store.ErrKeyNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check to see if ACME account object is already in kv store
|
||||
if traefikConfiguration.GlobalConfiguration.ACME.OverrideCertificates || !accountInitialized {
|
||||
|
||||
// Store the ACME Account into the KV Store
|
||||
// Certificates in KV Store will be overridden
|
||||
meta := cluster.NewMetadata(account)
|
||||
err = meta.Marshall()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
source := staert.KvSource{
|
||||
Store: kv,
|
||||
Prefix: traefikConfiguration.GlobalConfiguration.ACME.Storage,
|
||||
}
|
||||
|
||||
err = source.StoreConfig(meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Force to delete storagefile
|
||||
return kv.Delete(kv.Prefix + "/acme/storagefile")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// func keyExists(source *staert.KvSource, key string) (bool, error) {
|
||||
// list, err := source.List(key, nil)
|
||||
// if err != nil {
|
||||
// return false, err
|
||||
// }
|
||||
//
|
||||
// return len(list) > 0, nil
|
||||
// }
|
||||
func keyExists(source *staert.KvSource, key string) (bool, error) {
|
||||
list, err := source.List(key, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return len(list) > 0, nil
|
||||
}
|
||||
|
||||
// migrateACMEData allows migrating data from acme.json file to KV store in function of the file format
|
||||
func migrateACMEData(fileName string) (*acme.Account, error) {
|
||||
|
||||
f, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
file, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if the storage file is not empty before to get data
|
||||
account := &acme.Account{}
|
||||
if len(file) > 0 {
|
||||
accountFromNewFormat, err := acme.FromNewToOldFormat(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if accountFromNewFormat == nil {
|
||||
// convert ACME json file to KV store (used for backward compatibility)
|
||||
localStore := acme.NewLocalStore(fileName)
|
||||
|
||||
account, err = localStore.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = account.RemoveAccountV1Values()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
account = accountFromNewFormat
|
||||
}
|
||||
} else {
|
||||
log.Warnf("No data will be imported from the storageFile %q because it is empty.", fileName)
|
||||
}
|
||||
|
||||
err = account.Init()
|
||||
return account, err
|
||||
}
|
||||
|
||||
// CreateKvSource creates KvSource
|
||||
// TLS support is enable for Consul and Etcd backends
|
||||
func CreateKvSource(traefikConfiguration *cmd.TraefikConfiguration) (*staert.KvSource, error) {
|
||||
var kv *staert.KvSource
|
||||
// var kvStore store.Store
|
||||
var kvStore store.Store
|
||||
var err error
|
||||
|
||||
// TODO kv store
|
||||
// switch {
|
||||
// case traefikConfiguration.Providers.Consul != nil:
|
||||
// kvStore, err = traefikConfiguration.Providers.Consul.CreateStore()
|
||||
// kv = &staert.KvSource{
|
||||
// Store: kvStore,
|
||||
// Prefix: traefikConfiguration.Providers.Consul.Prefix,
|
||||
// }
|
||||
// case traefikConfiguration.Providers.Etcd != nil:
|
||||
// kvStore, err = traefikConfiguration.Providers.Etcd.CreateStore()
|
||||
// kv = &staert.KvSource{
|
||||
// Store: kvStore,
|
||||
// Prefix: traefikConfiguration.Providers.Etcd.Prefix,
|
||||
// }
|
||||
// case traefikConfiguration.Providers.Zookeeper != nil:
|
||||
// kvStore, err = traefikConfiguration.Providers.Zookeeper.CreateStore()
|
||||
// kv = &staert.KvSource{
|
||||
// Store: kvStore,
|
||||
// Prefix: traefikConfiguration.Providers.Zookeeper.Prefix,
|
||||
// }
|
||||
// case traefikConfiguration.Providers.Boltdb != nil:
|
||||
// kvStore, err = traefikConfiguration.Providers.Boltdb.CreateStore()
|
||||
// kv = &staert.KvSource{
|
||||
// Store: kvStore,
|
||||
// Prefix: traefikConfiguration.Providers.Boltdb.Prefix,
|
||||
// }
|
||||
// }
|
||||
switch {
|
||||
case traefikConfiguration.Consul != nil:
|
||||
kvStore, err = traefikConfiguration.Consul.CreateStore()
|
||||
kv = &staert.KvSource{
|
||||
Store: kvStore,
|
||||
Prefix: traefikConfiguration.Consul.Prefix,
|
||||
}
|
||||
case traefikConfiguration.Etcd != nil:
|
||||
kvStore, err = traefikConfiguration.Etcd.CreateStore()
|
||||
kv = &staert.KvSource{
|
||||
Store: kvStore,
|
||||
Prefix: traefikConfiguration.Etcd.Prefix,
|
||||
}
|
||||
case traefikConfiguration.Zookeeper != nil:
|
||||
kvStore, err = traefikConfiguration.Zookeeper.CreateStore()
|
||||
kv = &staert.KvSource{
|
||||
Store: kvStore,
|
||||
Prefix: traefikConfiguration.Zookeeper.Prefix,
|
||||
}
|
||||
case traefikConfiguration.Boltdb != nil:
|
||||
kvStore, err = traefikConfiguration.Boltdb.CreateStore()
|
||||
kv = &staert.KvSource{
|
||||
Store: kvStore,
|
||||
Prefix: traefikConfiguration.Boltdb.Prefix,
|
||||
}
|
||||
}
|
||||
return kv, err
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
fmtlog "log"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -12,27 +11,28 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/staert"
|
||||
"github.com/containous/traefik/autogen/genstatic"
|
||||
"github.com/containous/traefik/cmd"
|
||||
"github.com/containous/traefik/cmd/bug"
|
||||
"github.com/containous/traefik/cmd/healthcheck"
|
||||
"github.com/containous/traefik/cmd/storeconfig"
|
||||
cmdVersion "github.com/containous/traefik/cmd/version"
|
||||
"github.com/containous/traefik/pkg/collector"
|
||||
"github.com/containous/traefik/pkg/config"
|
||||
"github.com/containous/traefik/pkg/config/static"
|
||||
"github.com/containous/traefik/pkg/job"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
"github.com/containous/traefik/pkg/provider/aggregator"
|
||||
"github.com/containous/traefik/pkg/provider/kubernetes/k8s"
|
||||
"github.com/containous/traefik/pkg/safe"
|
||||
"github.com/containous/traefik/pkg/server"
|
||||
"github.com/containous/traefik/pkg/server/router"
|
||||
traefiktls "github.com/containous/traefik/pkg/tls"
|
||||
"github.com/containous/traefik/pkg/types"
|
||||
"github.com/containous/traefik/pkg/version"
|
||||
"github.com/containous/traefik/collector"
|
||||
"github.com/containous/traefik/configuration"
|
||||
"github.com/containous/traefik/configuration/router"
|
||||
"github.com/containous/traefik/job"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/provider/ecs"
|
||||
"github.com/containous/traefik/provider/kubernetes"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/server"
|
||||
"github.com/containous/traefik/server/uuid"
|
||||
traefiktls "github.com/containous/traefik/tls"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/containous/traefik/version"
|
||||
"github.com/coreos/go-systemd/daemon"
|
||||
assetfs "github.com/elazarl/go-bindata-assetfs"
|
||||
"github.com/ogier/pflag"
|
||||
@@ -40,52 +40,6 @@ import (
|
||||
"github.com/vulcand/oxy/roundrobin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
goDebug := os.Getenv("GODEBUG")
|
||||
if len(goDebug) > 0 {
|
||||
goDebug += ","
|
||||
}
|
||||
os.Setenv("GODEBUG", goDebug+"tls13=1")
|
||||
}
|
||||
|
||||
// sliceOfStrings is the parser for []string
|
||||
type sliceOfStrings []string
|
||||
|
||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||
// The String method's output will be used in diagnostics.
|
||||
func (s *sliceOfStrings) String() string {
|
||||
return strings.Join(*s, ",")
|
||||
}
|
||||
|
||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||
// Set's argument is a string to be parsed to set the flag.
|
||||
// It's a comma-separated list, so we split it.
|
||||
func (s *sliceOfStrings) Set(value string) error {
|
||||
parts := strings.Split(value, ",")
|
||||
if len(parts) == 0 {
|
||||
return fmt.Errorf("bad []string format: %s", value)
|
||||
}
|
||||
for _, entrypoint := range parts {
|
||||
*s = append(*s, entrypoint)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get return the []string
|
||||
func (s *sliceOfStrings) Get() interface{} {
|
||||
return *s
|
||||
}
|
||||
|
||||
// SetValue sets the []string with val
|
||||
func (s *sliceOfStrings) SetValue(val interface{}) {
|
||||
*s = val.([]string)
|
||||
}
|
||||
|
||||
// Type is type of the struct
|
||||
func (s *sliceOfStrings) Type() string {
|
||||
return "sliceOfStrings"
|
||||
}
|
||||
|
||||
func main() {
|
||||
// traefik config inits
|
||||
traefikConfiguration := cmd.NewTraefikConfiguration()
|
||||
@@ -99,7 +53,8 @@ Complete documentation is available at https://traefik.io`,
|
||||
Config: traefikConfiguration,
|
||||
DefaultPointersConfig: traefikPointersConfiguration,
|
||||
Run: func() error {
|
||||
return runCmd(&traefikConfiguration.Configuration, traefikConfiguration.ConfigFile)
|
||||
runCmd(&traefikConfiguration.GlobalConfiguration, traefikConfiguration.ConfigFile)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -109,22 +64,22 @@ Complete documentation is available at https://traefik.io`,
|
||||
// init flaeg source
|
||||
f := flaeg.New(traefikCmd, os.Args[1:])
|
||||
// add custom parsers
|
||||
f.AddParser(reflect.TypeOf(static.EntryPoints{}), &static.EntryPoints{})
|
||||
|
||||
f.AddParser(reflect.SliceOf(reflect.TypeOf("")), &sliceOfStrings{})
|
||||
f.AddParser(reflect.TypeOf(configuration.EntryPoints{}), &configuration.EntryPoints{})
|
||||
f.AddParser(reflect.TypeOf(configuration.DefaultEntryPoints{}), &configuration.DefaultEntryPoints{})
|
||||
f.AddParser(reflect.TypeOf(traefiktls.FilesOrContents{}), &traefiktls.FilesOrContents{})
|
||||
f.AddParser(reflect.TypeOf(types.Constraints{}), &types.Constraints{})
|
||||
f.AddParser(reflect.TypeOf(k8s.Namespaces{}), &k8s.Namespaces{})
|
||||
f.AddParser(reflect.TypeOf(kubernetes.Namespaces{}), &kubernetes.Namespaces{})
|
||||
f.AddParser(reflect.TypeOf(ecs.Clusters{}), &ecs.Clusters{})
|
||||
f.AddParser(reflect.TypeOf([]types.Domain{}), &types.Domains{})
|
||||
f.AddParser(reflect.TypeOf(types.DNSResolvers{}), &types.DNSResolvers{})
|
||||
f.AddParser(reflect.TypeOf(types.Buckets{}), &types.Buckets{})
|
||||
|
||||
f.AddParser(reflect.TypeOf(types.StatusCodes{}), &types.StatusCodes{})
|
||||
f.AddParser(reflect.TypeOf(types.FieldNames{}), &types.FieldNames{})
|
||||
f.AddParser(reflect.TypeOf(types.FieldHeaderNames{}), &types.FieldHeaderNames{})
|
||||
|
||||
// add commands
|
||||
f.AddCommand(cmdVersion.NewCmd())
|
||||
f.AddCommand(bug.NewCmd(traefikConfiguration, traefikPointersConfiguration))
|
||||
f.AddCommand(storeConfigCmd)
|
||||
f.AddCommand(healthcheck.NewCmd(traefikConfiguration, traefikPointersConfiguration))
|
||||
|
||||
@@ -166,13 +121,19 @@ Complete documentation is available at https://traefik.io`,
|
||||
|
||||
// if a KV Store is enable and no sub-command called in args
|
||||
if kv != nil && usedCmd == traefikCmd {
|
||||
if traefikConfiguration.Cluster == nil {
|
||||
traefikConfiguration.Cluster = &types.Cluster{Node: uuid.Get()}
|
||||
}
|
||||
if traefikConfiguration.Cluster.Store == nil {
|
||||
traefikConfiguration.Cluster.Store = &types.Store{Prefix: kv.Prefix, Store: kv.Store}
|
||||
}
|
||||
s.AddSource(kv)
|
||||
operation := func() error {
|
||||
_, err := s.LoadConfig()
|
||||
return err
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.WithoutContext().Errorf("Load config error: %+v, retrying in %s", err, time)
|
||||
log.Errorf("Load config error: %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
@@ -189,125 +150,135 @@ Complete documentation is available at https://traefik.io`,
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func runCmd(staticConfiguration *static.Configuration, configFile string) error {
|
||||
configureLogging(staticConfiguration)
|
||||
func runCmd(globalConfiguration *configuration.GlobalConfiguration, configFile string) {
|
||||
configureLogging(globalConfiguration)
|
||||
|
||||
if len(configFile) > 0 {
|
||||
log.WithoutContext().Infof("Using TOML configuration file %s", configFile)
|
||||
log.Infof("Using TOML configuration file %s", configFile)
|
||||
}
|
||||
|
||||
http.DefaultTransport.(*http.Transport).Proxy = http.ProxyFromEnvironment
|
||||
|
||||
if err := roundrobin.SetDefaultWeight(0); err != nil {
|
||||
log.WithoutContext().Errorf("Could not set roundrobin default weight: %v", err)
|
||||
if globalConfiguration.AllowMinWeightZero {
|
||||
roundrobin.SetDefaultWeight(0)
|
||||
}
|
||||
|
||||
staticConfiguration.SetEffectiveConfiguration(configFile)
|
||||
staticConfiguration.ValidateConfiguration()
|
||||
globalConfiguration.SetEffectiveConfiguration(configFile)
|
||||
globalConfiguration.ValidateConfiguration()
|
||||
|
||||
log.WithoutContext().Infof("Traefik version %s built on %s", version.Version, version.BuildDate)
|
||||
log.Infof("Traefik version %s built on %s", version.Version, version.BuildDate)
|
||||
|
||||
jsonConf, err := json.Marshal(staticConfiguration)
|
||||
jsonConf, err := json.Marshal(globalConfiguration)
|
||||
if err != nil {
|
||||
log.WithoutContext().Errorf("Could not marshal static configuration: %v", err)
|
||||
log.WithoutContext().Debugf("Static configuration loaded [struct] %#v", staticConfiguration)
|
||||
log.Error(err)
|
||||
log.Debugf("Global configuration loaded [struct] %#v", globalConfiguration)
|
||||
} else {
|
||||
log.WithoutContext().Debugf("Static configuration loaded %s", string(jsonConf))
|
||||
log.Debugf("Global configuration loaded %s", string(jsonConf))
|
||||
}
|
||||
|
||||
if staticConfiguration.API != nil && staticConfiguration.API.Dashboard {
|
||||
staticConfiguration.API.DashboardAssets = &assetfs.AssetFS{Asset: genstatic.Asset, AssetInfo: genstatic.AssetInfo, AssetDir: genstatic.AssetDir, Prefix: "static"}
|
||||
if globalConfiguration.API != nil && globalConfiguration.API.Dashboard {
|
||||
globalConfiguration.API.DashboardAssets = &assetfs.AssetFS{Asset: genstatic.Asset, AssetInfo: genstatic.AssetInfo, AssetDir: genstatic.AssetDir, Prefix: "static"}
|
||||
}
|
||||
|
||||
if staticConfiguration.Global.CheckNewVersion {
|
||||
if globalConfiguration.CheckNewVersion {
|
||||
checkNewVersion()
|
||||
}
|
||||
|
||||
stats(staticConfiguration)
|
||||
stats(globalConfiguration)
|
||||
|
||||
providerAggregator := aggregator.NewProviderAggregator(*staticConfiguration.Providers)
|
||||
providerAggregator := configuration.NewProviderAggregator(globalConfiguration)
|
||||
|
||||
acmeProvider, err := staticConfiguration.InitACMEProvider()
|
||||
acmeprovider, err := globalConfiguration.InitACMEProvider()
|
||||
if err != nil {
|
||||
log.WithoutContext().Errorf("Unable to initialize ACME provider: %v", err)
|
||||
} else if acmeProvider != nil {
|
||||
if err := providerAggregator.AddProvider(acmeProvider); err != nil {
|
||||
log.WithoutContext().Errorf("Unable to add ACME provider to the providers list: %v", err)
|
||||
acmeProvider = nil
|
||||
}
|
||||
}
|
||||
|
||||
serverEntryPointsTCP := make(server.TCPEntryPoints)
|
||||
for entryPointName, config := range staticConfiguration.EntryPoints {
|
||||
ctx := log.With(context.Background(), log.Str(log.EntryPointName, entryPointName))
|
||||
serverEntryPointsTCP[entryPointName], err = server.NewTCPEntryPoint(ctx, config)
|
||||
log.Errorf("Unable to initialize ACME provider: %v", err)
|
||||
} else if acmeprovider != nil {
|
||||
err = providerAggregator.AddProvider(acmeprovider)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while building entryPoint %s: %v", entryPointName, err)
|
||||
}
|
||||
serverEntryPointsTCP[entryPointName].RouteAppenderFactory = router.NewRouteAppenderFactory(*staticConfiguration, entryPointName, acmeProvider)
|
||||
|
||||
}
|
||||
|
||||
tlsManager := traefiktls.NewManager()
|
||||
|
||||
if acmeProvider != nil {
|
||||
acmeProvider.SetTLSManager(tlsManager)
|
||||
if acmeProvider.TLSChallenge != nil &&
|
||||
acmeProvider.HTTPChallenge == nil &&
|
||||
acmeProvider.DNSChallenge == nil {
|
||||
tlsManager.TLSAlpnGetter = acmeProvider.GetTLSALPNCertificate
|
||||
log.Errorf("Unable to add ACME provider to the providers list: %v", err)
|
||||
acmeprovider = nil
|
||||
}
|
||||
}
|
||||
|
||||
svr := server.NewServer(*staticConfiguration, providerAggregator, serverEntryPointsTCP, tlsManager)
|
||||
entryPoints := map[string]server.EntryPoint{}
|
||||
for entryPointName, config := range globalConfiguration.EntryPoints {
|
||||
|
||||
if acmeProvider != nil && acmeProvider.OnHostRule {
|
||||
acmeProvider.SetConfigListenerChan(make(chan config.Configuration))
|
||||
svr.AddListener(acmeProvider.ListenConfiguration)
|
||||
entryPoint := server.EntryPoint{
|
||||
Configuration: config,
|
||||
}
|
||||
|
||||
internalRouter := router.NewInternalRouterAggregator(*globalConfiguration, entryPointName)
|
||||
if acmeprovider != nil {
|
||||
if acmeprovider.HTTPChallenge != nil && entryPointName == acmeprovider.HTTPChallenge.EntryPoint {
|
||||
internalRouter.AddRouter(acmeprovider)
|
||||
}
|
||||
|
||||
// TLS ALPN 01
|
||||
if acmeprovider.TLSChallenge != nil && acmeprovider.HTTPChallenge == nil && acmeprovider.DNSChallenge == nil {
|
||||
entryPoint.TLSALPNGetter = acmeprovider.GetTLSALPNCertificate
|
||||
}
|
||||
|
||||
if acmeprovider.OnDemand && entryPointName == acmeprovider.EntryPoint {
|
||||
entryPoint.OnDemandListener = acmeprovider.ListenRequest
|
||||
}
|
||||
|
||||
if entryPointName == acmeprovider.EntryPoint {
|
||||
entryPoint.CertificateStore = traefiktls.NewCertificateStore()
|
||||
acmeprovider.SetCertificateStore(entryPoint.CertificateStore)
|
||||
log.Debugf("Setting Acme Certificate store from Entrypoint: %s", entryPointName)
|
||||
}
|
||||
}
|
||||
|
||||
entryPoint.InternalRouter = internalRouter
|
||||
entryPoints[entryPointName] = entryPoint
|
||||
}
|
||||
|
||||
svr := server.NewServer(*globalConfiguration, providerAggregator, entryPoints)
|
||||
if acmeprovider != nil && acmeprovider.OnHostRule {
|
||||
acmeprovider.SetConfigListenerChan(make(chan types.Configuration))
|
||||
svr.AddListener(acmeprovider.ListenConfiguration)
|
||||
}
|
||||
ctx := cmd.ContextWithSignal(context.Background())
|
||||
|
||||
if staticConfiguration.Ping != nil {
|
||||
staticConfiguration.Ping.WithContext(ctx)
|
||||
if globalConfiguration.Ping != nil {
|
||||
globalConfiguration.Ping.WithContext(ctx)
|
||||
}
|
||||
|
||||
svr.Start(ctx)
|
||||
svr.StartWithContext(ctx)
|
||||
defer svr.Close()
|
||||
|
||||
sent, err := daemon.SdNotify(false, "READY=1")
|
||||
if !sent && err != nil {
|
||||
log.WithoutContext().Errorf("Failed to notify: %v", err)
|
||||
log.Error("Fail to notify", err)
|
||||
}
|
||||
|
||||
t, err := daemon.SdWatchdogEnabled(false)
|
||||
if err != nil {
|
||||
log.WithoutContext().Errorf("Could not enable Watchdog: %v", err)
|
||||
log.Error("Problem with watchdog", err)
|
||||
} else if t != 0 {
|
||||
// Send a ping each half time given
|
||||
t /= 2
|
||||
log.WithoutContext().Infof("Watchdog activated with timer duration %s", t)
|
||||
t = t / 2
|
||||
log.Info("Watchdog activated with timer each ", t)
|
||||
safe.Go(func() {
|
||||
tick := time.Tick(t)
|
||||
for range tick {
|
||||
_, errHealthCheck := healthcheck.Do(*staticConfiguration)
|
||||
if staticConfiguration.Ping == nil || errHealthCheck == nil {
|
||||
_, errHealthCheck := healthcheck.Do(*globalConfiguration)
|
||||
if globalConfiguration.Ping == nil || errHealthCheck == nil {
|
||||
if ok, _ := daemon.SdNotify(false, "WATCHDOG=1"); !ok {
|
||||
log.WithoutContext().Error("Fail to tick watchdog")
|
||||
log.Error("Fail to tick watchdog")
|
||||
}
|
||||
} else {
|
||||
log.WithoutContext().Error(errHealthCheck)
|
||||
log.Error(errHealthCheck)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
svr.Wait()
|
||||
log.WithoutContext().Info("Shutting down")
|
||||
log.Info("Shutting down")
|
||||
logrus.Exit(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func configureLogging(staticConfiguration *static.Configuration) {
|
||||
func configureLogging(globalConfiguration *configuration.GlobalConfiguration) {
|
||||
// configure default log flags
|
||||
fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags)
|
||||
|
||||
@@ -315,30 +286,31 @@ func configureLogging(staticConfiguration *static.Configuration) {
|
||||
// an explicitly defined log level always has precedence. if none is
|
||||
// given and debug mode is disabled, the default is ERROR, and DEBUG
|
||||
// otherwise.
|
||||
var levelStr string
|
||||
if staticConfiguration.Log != nil {
|
||||
levelStr = strings.ToLower(staticConfiguration.Log.LogLevel)
|
||||
}
|
||||
levelStr := strings.ToLower(globalConfiguration.LogLevel)
|
||||
if levelStr == "" {
|
||||
levelStr = "error"
|
||||
if staticConfiguration.Global.Debug {
|
||||
if globalConfiguration.Debug {
|
||||
levelStr = "debug"
|
||||
}
|
||||
}
|
||||
level, err := logrus.ParseLevel(levelStr)
|
||||
if err != nil {
|
||||
log.WithoutContext().Errorf("Error getting level: %v", err)
|
||||
log.Error("Error getting level", err)
|
||||
}
|
||||
log.SetLevel(level)
|
||||
|
||||
var logFile string
|
||||
if staticConfiguration.Log != nil && len(staticConfiguration.Log.FilePath) > 0 {
|
||||
logFile = staticConfiguration.Log.FilePath
|
||||
// configure log output file
|
||||
logFile := globalConfiguration.TraefikLogsFile
|
||||
if len(logFile) > 0 {
|
||||
log.Warn("top-level traefikLogsFile has been deprecated -- please use traefiklog.filepath")
|
||||
}
|
||||
if globalConfiguration.TraefikLog != nil && len(globalConfiguration.TraefikLog.FilePath) > 0 {
|
||||
logFile = globalConfiguration.TraefikLog.FilePath
|
||||
}
|
||||
|
||||
// configure log format
|
||||
var formatter logrus.Formatter
|
||||
if staticConfiguration.Log != nil && staticConfiguration.Log.Format == "json" {
|
||||
if globalConfiguration.TraefikLog != nil && globalConfiguration.TraefikLog.Format == "json" {
|
||||
formatter = &logrus.JSONFormatter{}
|
||||
} else {
|
||||
disableColors := len(logFile) > 0
|
||||
@@ -350,17 +322,17 @@ func configureLogging(staticConfiguration *static.Configuration) {
|
||||
dir := filepath.Dir(logFile)
|
||||
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
log.WithoutContext().Errorf("Failed to create log path %s: %s", dir, err)
|
||||
log.Errorf("Failed to create log path %s: %s", dir, err)
|
||||
}
|
||||
|
||||
err = log.OpenFile(logFile)
|
||||
logrus.RegisterExitHandler(func() {
|
||||
if err := log.CloseFile(); err != nil {
|
||||
log.WithoutContext().Errorf("Error while closing log: %v", err)
|
||||
log.Error("Error closing log", err)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
log.WithoutContext().Errorf("Error while opening log file %s: %v", logFile, err)
|
||||
log.Error("Error opening file", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -374,25 +346,17 @@ func checkNewVersion() {
|
||||
})
|
||||
}
|
||||
|
||||
func stats(staticConfiguration *static.Configuration) {
|
||||
if staticConfiguration.Global.SendAnonymousUsage == nil {
|
||||
log.WithoutContext().Error(`
|
||||
You haven't specify the sendAnonymousUsage option, it will be enable by default.
|
||||
`)
|
||||
sendAnonymousUsage := true
|
||||
staticConfiguration.Global.SendAnonymousUsage = &sendAnonymousUsage
|
||||
}
|
||||
|
||||
if *staticConfiguration.Global.SendAnonymousUsage {
|
||||
log.WithoutContext().Info(`
|
||||
func stats(globalConfiguration *configuration.GlobalConfiguration) {
|
||||
if globalConfiguration.SendAnonymousUsage {
|
||||
log.Info(`
|
||||
Stats collection is enabled.
|
||||
Many thanks for contributing to Traefik's improvement by allowing us to receive anonymous information from your configuration.
|
||||
Help us improve Traefik by leaving this feature on :)
|
||||
More details on: https://docs.traefik.io/basics/#collected-data
|
||||
`)
|
||||
collect(staticConfiguration)
|
||||
collect(globalConfiguration)
|
||||
} else {
|
||||
log.WithoutContext().Info(`
|
||||
log.Info(`
|
||||
Stats collection is disabled.
|
||||
Help us improve Traefik by turning this feature on :)
|
||||
More details on: https://docs.traefik.io/basics/#collected-data
|
||||
@@ -400,12 +364,12 @@ More details on: https://docs.traefik.io/basics/#collected-data
|
||||
}
|
||||
}
|
||||
|
||||
func collect(staticConfiguration *static.Configuration) {
|
||||
func collect(globalConfiguration *configuration.GlobalConfiguration) {
|
||||
ticker := time.Tick(24 * time.Hour)
|
||||
safe.Go(func() {
|
||||
for time.Sleep(10 * time.Minute); ; <-ticker {
|
||||
if err := collector.Collect(staticConfiguration); err != nil {
|
||||
log.WithoutContext().Debug(err)
|
||||
if err := collector.Collect(globalConfiguration); err != nil {
|
||||
log.Debug(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"text/template"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik/pkg/version"
|
||||
"github.com/containous/traefik/version"
|
||||
)
|
||||
|
||||
var versionTemplate = `Version: {{.Version}}
|
||||
|
||||
@@ -9,15 +9,15 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/pkg/anonymize"
|
||||
"github.com/containous/traefik/pkg/config/static"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
"github.com/containous/traefik/pkg/version"
|
||||
"github.com/containous/traefik/anonymize"
|
||||
"github.com/containous/traefik/configuration"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/version"
|
||||
"github.com/mitchellh/hashstructure"
|
||||
)
|
||||
|
||||
// collectorURL URL where the stats are send
|
||||
const collectorURL = "https://collect.traefik.io/9vxmmkcdmalbdi635d4jgc5p5rx0h7h8"
|
||||
const collectorURL = "https://collect.traefik.io/619df80498b60f985d766ce62f912b7c"
|
||||
|
||||
// Collected data
|
||||
type data struct {
|
||||
@@ -29,15 +29,15 @@ type data struct {
|
||||
}
|
||||
|
||||
// Collect anonymous data.
|
||||
func Collect(staticConfiguration *static.Configuration) error {
|
||||
anonConfig, err := anonymize.Do(staticConfiguration, false)
|
||||
func Collect(globalConfiguration *configuration.GlobalConfiguration) error {
|
||||
anonConfig, err := anonymize.Do(globalConfiguration, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Anonymous stats sent to %s: %s", collectorURL, anonConfig)
|
||||
|
||||
hashConf, err := hashstructure.Hash(staticConfiguration, nil)
|
||||
hashConf, err := hashstructure.Hash(globalConfiguration, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -62,7 +62,7 @@ func Collect(staticConfiguration *static.Configuration) error {
|
||||
|
||||
func makeHTTPClient() *http.Client {
|
||||
dialer := &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
Timeout: configuration.DefaultDialTimeout,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}
|
||||
585
configuration/configuration.go
Normal file
@@ -0,0 +1,585 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
servicefabric "github.com/containous/traefik-extra-service-fabric"
|
||||
"github.com/containous/traefik/acme"
|
||||
"github.com/containous/traefik/api"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/middlewares/tracing"
|
||||
"github.com/containous/traefik/middlewares/tracing/datadog"
|
||||
"github.com/containous/traefik/middlewares/tracing/jaeger"
|
||||
"github.com/containous/traefik/middlewares/tracing/zipkin"
|
||||
"github.com/containous/traefik/ping"
|
||||
acmeprovider "github.com/containous/traefik/provider/acme"
|
||||
"github.com/containous/traefik/provider/boltdb"
|
||||
"github.com/containous/traefik/provider/consul"
|
||||
"github.com/containous/traefik/provider/consulcatalog"
|
||||
"github.com/containous/traefik/provider/docker"
|
||||
"github.com/containous/traefik/provider/dynamodb"
|
||||
"github.com/containous/traefik/provider/ecs"
|
||||
"github.com/containous/traefik/provider/etcd"
|
||||
"github.com/containous/traefik/provider/eureka"
|
||||
"github.com/containous/traefik/provider/file"
|
||||
"github.com/containous/traefik/provider/kubernetes"
|
||||
"github.com/containous/traefik/provider/marathon"
|
||||
"github.com/containous/traefik/provider/mesos"
|
||||
"github.com/containous/traefik/provider/rancher"
|
||||
"github.com/containous/traefik/provider/rest"
|
||||
"github.com/containous/traefik/provider/zk"
|
||||
"github.com/containous/traefik/tls"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/pkg/errors"
|
||||
jaegercli "github.com/uber/jaeger-client-go"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultInternalEntryPointName the name of the default internal entry point
|
||||
DefaultInternalEntryPointName = "traefik"
|
||||
|
||||
// DefaultHealthCheckInterval is the default health check interval.
|
||||
DefaultHealthCheckInterval = 30 * time.Second
|
||||
|
||||
// DefaultDialTimeout when connecting to a backend server.
|
||||
DefaultDialTimeout = 30 * time.Second
|
||||
|
||||
// DefaultIdleTimeout before closing an idle connection.
|
||||
DefaultIdleTimeout = 180 * time.Second
|
||||
|
||||
// DefaultGraceTimeout controls how long Traefik serves pending requests
|
||||
// prior to shutting down.
|
||||
DefaultGraceTimeout = 10 * time.Second
|
||||
|
||||
// DefaultAcmeCAServer is the default ACME API endpoint
|
||||
DefaultAcmeCAServer = "https://acme-v02.api.letsencrypt.org/directory"
|
||||
)
|
||||
|
||||
// GlobalConfiguration holds global configuration (with providers, etc.).
|
||||
// It's populated from the traefik configuration file passed as an argument to the binary.
|
||||
type GlobalConfiguration struct {
|
||||
LifeCycle *LifeCycle `description:"Timeouts influencing the server life cycle" export:"true"`
|
||||
GraceTimeOut flaeg.Duration `short:"g" description:"(Deprecated) Duration to give active requests a chance to finish before Traefik stops" export:"true"` // Deprecated
|
||||
Debug bool `short:"d" description:"Enable debug mode" export:"true"`
|
||||
CheckNewVersion bool `description:"Periodically check if a new version has been released" export:"true"`
|
||||
SendAnonymousUsage bool `description:"send periodically anonymous usage statistics" export:"true"`
|
||||
AccessLogsFile string `description:"(Deprecated) Access logs file" export:"true"` // Deprecated
|
||||
AccessLog *types.AccessLog `description:"Access log settings" export:"true"`
|
||||
TraefikLogsFile string `description:"(Deprecated) Traefik logs file. Stdout is used when omitted or empty" export:"true"` // Deprecated
|
||||
TraefikLog *types.TraefikLog `description:"Traefik log settings" export:"true"`
|
||||
Tracing *tracing.Tracing `description:"OpenTracing configuration" export:"true"`
|
||||
LogLevel string `short:"l" description:"Log level" export:"true"`
|
||||
EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key;prod/traefik.crt,prod/traefik.key'" export:"true"`
|
||||
Cluster *types.Cluster
|
||||
Constraints types.Constraints `description:"Filter services by constraint, matching with service tags" export:"true"`
|
||||
ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL" export:"true"`
|
||||
DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint" export:"true"`
|
||||
ProvidersThrottleDuration flaeg.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time." export:"true"`
|
||||
MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used" export:"true"`
|
||||
IdleTimeout flaeg.Duration `description:"(Deprecated) maximum amount of time an idle (keep-alive) connection will remain idle before closing itself." export:"true"` // Deprecated
|
||||
InsecureSkipVerify bool `description:"Disable SSL certificate verification" export:"true"`
|
||||
RootCAs tls.FilesOrContents `description:"Add cert file for self-signed certificate"`
|
||||
Retry *Retry `description:"Enable retry sending request if network error" export:"true"`
|
||||
HealthCheck *HealthCheckConfig `description:"Health check parameters" export:"true"`
|
||||
RespondingTimeouts *RespondingTimeouts `description:"Timeouts for incoming requests to the Traefik instance" export:"true"`
|
||||
ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers" export:"true"`
|
||||
AllowMinWeightZero bool `description:"Allow weight to take 0 as minimum real value." export:"true"` // Deprecated
|
||||
KeepTrailingSlash bool `description:"Do not remove trailing slash." export:"true"` // Deprecated
|
||||
Web *WebCompatibility `description:"(Deprecated) Enable Web backend with default settings" export:"true"` // Deprecated
|
||||
Docker *docker.Provider `description:"Enable Docker backend with default settings" export:"true"`
|
||||
File *file.Provider `description:"Enable File backend with default settings" export:"true"`
|
||||
Marathon *marathon.Provider `description:"Enable Marathon backend with default settings" export:"true"`
|
||||
Consul *consul.Provider `description:"Enable Consul backend with default settings" export:"true"`
|
||||
ConsulCatalog *consulcatalog.Provider `description:"Enable Consul catalog backend with default settings" export:"true"`
|
||||
Etcd *etcd.Provider `description:"Enable Etcd backend with default settings" export:"true"`
|
||||
Zookeeper *zk.Provider `description:"Enable Zookeeper backend with default settings" export:"true"`
|
||||
Boltdb *boltdb.Provider `description:"Enable Boltdb backend with default settings" export:"true"`
|
||||
Kubernetes *kubernetes.Provider `description:"Enable Kubernetes backend with default settings" export:"true"`
|
||||
Mesos *mesos.Provider `description:"Enable Mesos backend with default settings" export:"true"`
|
||||
Eureka *eureka.Provider `description:"Enable Eureka backend with default settings" export:"true"`
|
||||
ECS *ecs.Provider `description:"Enable ECS backend with default settings" export:"true"`
|
||||
Rancher *rancher.Provider `description:"Enable Rancher backend with default settings" export:"true"`
|
||||
DynamoDB *dynamodb.Provider `description:"Enable DynamoDB backend with default settings" export:"true"`
|
||||
ServiceFabric *servicefabric.Provider `description:"Enable Service Fabric backend with default settings" export:"true"`
|
||||
Rest *rest.Provider `description:"Enable Rest backend with default settings" export:"true"`
|
||||
API *api.Handler `description:"Enable api/dashboard" export:"true"`
|
||||
Metrics *types.Metrics `description:"Enable a metrics exporter" export:"true"`
|
||||
Ping *ping.Handler `description:"Enable ping" export:"true"`
|
||||
HostResolver *HostResolverConfig `description:"Enable CNAME Flattening" export:"true"`
|
||||
}
|
||||
|
||||
// WebCompatibility is a configuration to handle compatibility with deprecated web provider options
|
||||
type WebCompatibility struct {
|
||||
Address string `description:"(Deprecated) Web administration port" export:"true"`
|
||||
CertFile string `description:"(Deprecated) SSL certificate" export:"true"`
|
||||
KeyFile string `description:"(Deprecated) SSL certificate" export:"true"`
|
||||
ReadOnly bool `description:"(Deprecated) Enable read only API" export:"true"`
|
||||
Statistics *types.Statistics `description:"(Deprecated) Enable more detailed statistics" export:"true"`
|
||||
Metrics *types.Metrics `description:"(Deprecated) Enable a metrics exporter" export:"true"`
|
||||
Path string `description:"(Deprecated) Root path for dashboard and API" export:"true"`
|
||||
Auth *types.Auth `export:"true"`
|
||||
Debug bool `export:"true"`
|
||||
}
|
||||
|
||||
func (gc *GlobalConfiguration) handleWebDeprecation() {
|
||||
if gc.Web != nil {
|
||||
log.Warn("web provider configuration is deprecated, you should use these options : api, rest provider, ping and metrics")
|
||||
|
||||
if gc.API != nil || gc.Metrics != nil || gc.Ping != nil || gc.Rest != nil {
|
||||
log.Warn("web option is ignored if you use it with one of these options : api, rest provider, ping or metrics")
|
||||
return
|
||||
}
|
||||
gc.EntryPoints[DefaultInternalEntryPointName] = &EntryPoint{
|
||||
Address: gc.Web.Address,
|
||||
Auth: gc.Web.Auth,
|
||||
}
|
||||
if gc.Web.CertFile != "" {
|
||||
gc.EntryPoints[DefaultInternalEntryPointName].TLS = &tls.TLS{
|
||||
Certificates: []tls.Certificate{
|
||||
{
|
||||
CertFile: tls.FileOrContent(gc.Web.CertFile),
|
||||
KeyFile: tls.FileOrContent(gc.Web.KeyFile),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if gc.API == nil {
|
||||
gc.API = &api.Handler{
|
||||
EntryPoint: DefaultInternalEntryPointName,
|
||||
Statistics: gc.Web.Statistics,
|
||||
Dashboard: true,
|
||||
}
|
||||
}
|
||||
|
||||
if gc.Ping == nil {
|
||||
gc.Ping = &ping.Handler{
|
||||
EntryPoint: DefaultInternalEntryPointName,
|
||||
}
|
||||
}
|
||||
|
||||
if gc.Metrics == nil {
|
||||
gc.Metrics = gc.Web.Metrics
|
||||
}
|
||||
|
||||
if !gc.Debug {
|
||||
gc.Debug = gc.Web.Debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetEffectiveConfiguration adds missing configuration parameters derived from existing ones.
|
||||
// It also takes care of maintaining backwards compatibility.
|
||||
func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
|
||||
if len(gc.EntryPoints) == 0 {
|
||||
gc.EntryPoints = map[string]*EntryPoint{"http": {
|
||||
Address: ":80",
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
}}
|
||||
gc.DefaultEntryPoints = []string{"http"}
|
||||
}
|
||||
|
||||
gc.handleWebDeprecation()
|
||||
|
||||
if (gc.API != nil && gc.API.EntryPoint == DefaultInternalEntryPointName) ||
|
||||
(gc.Ping != nil && gc.Ping.EntryPoint == DefaultInternalEntryPointName) ||
|
||||
(gc.Metrics != nil && gc.Metrics.Prometheus != nil && gc.Metrics.Prometheus.EntryPoint == DefaultInternalEntryPointName) ||
|
||||
(gc.Rest != nil && gc.Rest.EntryPoint == DefaultInternalEntryPointName) {
|
||||
if _, ok := gc.EntryPoints[DefaultInternalEntryPointName]; !ok {
|
||||
gc.EntryPoints[DefaultInternalEntryPointName] = &EntryPoint{Address: ":8080"}
|
||||
}
|
||||
}
|
||||
|
||||
for entryPointName := range gc.EntryPoints {
|
||||
entryPoint := gc.EntryPoints[entryPointName]
|
||||
// ForwardedHeaders must be remove in the next breaking version
|
||||
if entryPoint.ForwardedHeaders == nil {
|
||||
entryPoint.ForwardedHeaders = &ForwardedHeaders{Insecure: true}
|
||||
}
|
||||
|
||||
if len(entryPoint.WhitelistSourceRange) > 0 {
|
||||
log.Warnf("Deprecated configuration found: %s. Please use %s.", "whiteListSourceRange", "whiteList.sourceRange")
|
||||
|
||||
if entryPoint.WhiteList == nil {
|
||||
entryPoint.WhiteList = &types.WhiteList{
|
||||
SourceRange: entryPoint.WhitelistSourceRange,
|
||||
}
|
||||
entryPoint.WhitelistSourceRange = nil
|
||||
}
|
||||
}
|
||||
|
||||
if entryPoint.TLS != nil && entryPoint.TLS.DefaultCertificate == nil && len(entryPoint.TLS.Certificates) > 0 {
|
||||
log.Infof("No tls.defaultCertificate given for %s: using the first item in tls.certificates as a fallback.", entryPointName)
|
||||
entryPoint.TLS.DefaultCertificate = &entryPoint.TLS.Certificates[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure LifeCycle isn't nil to spare nil checks elsewhere.
|
||||
if gc.LifeCycle == nil {
|
||||
gc.LifeCycle = &LifeCycle{}
|
||||
}
|
||||
|
||||
// Prefer legacy grace timeout parameter for backwards compatibility reasons.
|
||||
if gc.GraceTimeOut > 0 {
|
||||
log.Warn("top-level grace period configuration has been deprecated -- please use lifecycle grace period")
|
||||
gc.LifeCycle.GraceTimeOut = gc.GraceTimeOut
|
||||
}
|
||||
|
||||
if gc.Docker != nil {
|
||||
if len(gc.Docker.Filename) != 0 && gc.Docker.TemplateVersion != 2 {
|
||||
log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
|
||||
gc.Docker.TemplateVersion = 1
|
||||
} else {
|
||||
gc.Docker.TemplateVersion = 2
|
||||
}
|
||||
|
||||
if gc.Docker.SwarmModeRefreshSeconds <= 0 {
|
||||
gc.Docker.SwarmModeRefreshSeconds = 15
|
||||
}
|
||||
}
|
||||
|
||||
if gc.Marathon != nil {
|
||||
if len(gc.Marathon.Filename) != 0 && gc.Marathon.TemplateVersion != 2 {
|
||||
log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
|
||||
gc.Marathon.TemplateVersion = 1
|
||||
} else {
|
||||
gc.Marathon.TemplateVersion = 2
|
||||
}
|
||||
}
|
||||
|
||||
if gc.Mesos != nil {
|
||||
if len(gc.Mesos.Filename) != 0 && gc.Mesos.TemplateVersion != 2 {
|
||||
log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
|
||||
gc.Mesos.TemplateVersion = 1
|
||||
} else {
|
||||
gc.Mesos.TemplateVersion = 2
|
||||
}
|
||||
}
|
||||
|
||||
if gc.Eureka != nil {
|
||||
if gc.Eureka.Delay != 0 {
|
||||
log.Warn("Delay has been deprecated -- please use RefreshSeconds")
|
||||
gc.Eureka.RefreshSeconds = gc.Eureka.Delay
|
||||
}
|
||||
}
|
||||
|
||||
if gc.ECS != nil {
|
||||
if len(gc.ECS.Filename) != 0 && gc.ECS.TemplateVersion != 2 {
|
||||
log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
|
||||
gc.ECS.TemplateVersion = 1
|
||||
} else {
|
||||
gc.ECS.TemplateVersion = 2
|
||||
}
|
||||
}
|
||||
|
||||
if gc.ConsulCatalog != nil {
|
||||
if len(gc.ConsulCatalog.Filename) != 0 && gc.ConsulCatalog.TemplateVersion != 2 {
|
||||
log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
|
||||
gc.ConsulCatalog.TemplateVersion = 1
|
||||
} else {
|
||||
gc.ConsulCatalog.TemplateVersion = 2
|
||||
}
|
||||
}
|
||||
|
||||
if gc.Rancher != nil {
|
||||
if len(gc.Rancher.Filename) != 0 && gc.Rancher.TemplateVersion != 2 {
|
||||
log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
|
||||
gc.Rancher.TemplateVersion = 1
|
||||
} else {
|
||||
gc.Rancher.TemplateVersion = 2
|
||||
}
|
||||
|
||||
// Ensure backwards compatibility for now
|
||||
if len(gc.Rancher.AccessKey) > 0 ||
|
||||
len(gc.Rancher.Endpoint) > 0 ||
|
||||
len(gc.Rancher.SecretKey) > 0 {
|
||||
|
||||
if gc.Rancher.API == nil {
|
||||
gc.Rancher.API = &rancher.APIConfiguration{
|
||||
AccessKey: gc.Rancher.AccessKey,
|
||||
SecretKey: gc.Rancher.SecretKey,
|
||||
Endpoint: gc.Rancher.Endpoint,
|
||||
}
|
||||
}
|
||||
log.Warn("Deprecated configuration found: rancher.[accesskey|secretkey|endpoint]. " +
|
||||
"Please use rancher.api.[accesskey|secretkey|endpoint] instead.")
|
||||
}
|
||||
|
||||
if gc.Rancher.Metadata != nil && len(gc.Rancher.Metadata.Prefix) == 0 {
|
||||
gc.Rancher.Metadata.Prefix = "latest"
|
||||
}
|
||||
}
|
||||
|
||||
if gc.API != nil {
|
||||
gc.API.Debug = gc.Debug
|
||||
}
|
||||
|
||||
if gc.Web != nil && (gc.Web.Path == "" || !strings.HasSuffix(gc.Web.Path, "/")) {
|
||||
gc.Web.Path += "/"
|
||||
}
|
||||
|
||||
if gc.File != nil {
|
||||
gc.File.TraefikFile = configFile
|
||||
}
|
||||
|
||||
gc.initACMEProvider()
|
||||
gc.initTracing()
|
||||
}
|
||||
|
||||
func (gc *GlobalConfiguration) initTracing() {
|
||||
if gc.Tracing != nil {
|
||||
switch gc.Tracing.Backend {
|
||||
case jaeger.Name:
|
||||
if gc.Tracing.Jaeger == nil {
|
||||
gc.Tracing.Jaeger = &jaeger.Config{
|
||||
SamplingServerURL: "http://localhost:5778/sampling",
|
||||
SamplingType: "const",
|
||||
SamplingParam: 1.0,
|
||||
LocalAgentHostPort: "127.0.0.1:6831",
|
||||
TraceContextHeaderName: jaegercli.TraceContextHeaderName,
|
||||
}
|
||||
}
|
||||
if gc.Tracing.Zipkin != nil {
|
||||
log.Warn("Zipkin configuration will be ignored")
|
||||
gc.Tracing.Zipkin = nil
|
||||
}
|
||||
if gc.Tracing.DataDog != nil {
|
||||
log.Warn("DataDog configuration will be ignored")
|
||||
gc.Tracing.DataDog = nil
|
||||
}
|
||||
case zipkin.Name:
|
||||
if gc.Tracing.Zipkin == nil {
|
||||
gc.Tracing.Zipkin = &zipkin.Config{
|
||||
HTTPEndpoint: "http://localhost:9411/api/v1/spans",
|
||||
SameSpan: false,
|
||||
ID128Bit: true,
|
||||
Debug: false,
|
||||
}
|
||||
}
|
||||
if gc.Tracing.Jaeger != nil {
|
||||
log.Warn("Jaeger configuration will be ignored")
|
||||
gc.Tracing.Jaeger = nil
|
||||
}
|
||||
if gc.Tracing.DataDog != nil {
|
||||
log.Warn("DataDog configuration will be ignored")
|
||||
gc.Tracing.DataDog = nil
|
||||
}
|
||||
case datadog.Name:
|
||||
if gc.Tracing.DataDog == nil {
|
||||
gc.Tracing.DataDog = &datadog.Config{
|
||||
LocalAgentHostPort: "localhost:8126",
|
||||
GlobalTag: "",
|
||||
Debug: false,
|
||||
PrioritySampling: false,
|
||||
}
|
||||
}
|
||||
if gc.Tracing.Zipkin != nil {
|
||||
log.Warn("Zipkin configuration will be ignored")
|
||||
gc.Tracing.Zipkin = nil
|
||||
}
|
||||
if gc.Tracing.Jaeger != nil {
|
||||
log.Warn("Jaeger configuration will be ignored")
|
||||
gc.Tracing.Jaeger = nil
|
||||
}
|
||||
default:
|
||||
log.Warnf("Unknown tracer %q", gc.Tracing.Backend)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gc *GlobalConfiguration) initACMEProvider() {
|
||||
if gc.ACME != nil {
|
||||
gc.ACME.CAServer = getSafeACMECAServer(gc.ACME.CAServer)
|
||||
|
||||
if gc.ACME.DNSChallenge != nil && gc.ACME.HTTPChallenge != nil {
|
||||
log.Warn("Unable to use DNS challenge and HTTP challenge at the same time. Fallback to DNS challenge.")
|
||||
gc.ACME.HTTPChallenge = nil
|
||||
}
|
||||
|
||||
if gc.ACME.DNSChallenge != nil && gc.ACME.TLSChallenge != nil {
|
||||
log.Warn("Unable to use DNS challenge and TLS challenge at the same time. Fallback to DNS challenge.")
|
||||
gc.ACME.TLSChallenge = nil
|
||||
}
|
||||
|
||||
if gc.ACME.HTTPChallenge != nil && gc.ACME.TLSChallenge != nil {
|
||||
log.Warn("Unable to use HTTP challenge and TLS challenge at the same time. Fallback to TLS challenge.")
|
||||
gc.ACME.HTTPChallenge = nil
|
||||
}
|
||||
|
||||
for _, domain := range gc.ACME.Domains {
|
||||
if domain.Main != dns01.UnFqdn(domain.Main) {
|
||||
log.Warnf("FQDN detected, please remove the trailing dot: %s", domain.Main)
|
||||
}
|
||||
for _, san := range domain.SANs {
|
||||
if san != dns01.UnFqdn(san) {
|
||||
log.Warnf("FQDN detected, please remove the trailing dot: %s", san)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: to remove in the future
|
||||
if len(gc.ACME.StorageFile) > 0 && len(gc.ACME.Storage) == 0 {
|
||||
log.Warn("ACME.StorageFile is deprecated, use ACME.Storage instead")
|
||||
gc.ACME.Storage = gc.ACME.StorageFile
|
||||
}
|
||||
|
||||
if len(gc.ACME.DNSProvider) > 0 {
|
||||
log.Warn("ACME.DNSProvider is deprecated, use ACME.DNSChallenge instead")
|
||||
gc.ACME.DNSChallenge = &acmeprovider.DNSChallenge{Provider: gc.ACME.DNSProvider, DelayBeforeCheck: gc.ACME.DelayDontCheckDNS}
|
||||
}
|
||||
|
||||
if gc.ACME.OnDemand {
|
||||
log.Warn("ACME.OnDemand is deprecated")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InitACMEProvider create an acme provider from the ACME part of globalConfiguration
|
||||
func (gc *GlobalConfiguration) InitACMEProvider() (*acmeprovider.Provider, error) {
|
||||
if gc.ACME != nil {
|
||||
if len(gc.ACME.Storage) == 0 {
|
||||
// Delete the ACME configuration to avoid starting ACME in cluster mode
|
||||
gc.ACME = nil
|
||||
return nil, errors.New("unable to initialize ACME provider with no storage location for the certificates")
|
||||
}
|
||||
// TODO: Remove when Provider ACME will replace totally ACME
|
||||
// If provider file, use Provider ACME instead of ACME
|
||||
if gc.Cluster == nil {
|
||||
provider := &acmeprovider.Provider{}
|
||||
provider.Configuration = &acmeprovider.Configuration{
|
||||
KeyType: gc.ACME.KeyType,
|
||||
OnHostRule: gc.ACME.OnHostRule,
|
||||
OnDemand: gc.ACME.OnDemand,
|
||||
Email: gc.ACME.Email,
|
||||
Storage: gc.ACME.Storage,
|
||||
HTTPChallenge: gc.ACME.HTTPChallenge,
|
||||
DNSChallenge: gc.ACME.DNSChallenge,
|
||||
TLSChallenge: gc.ACME.TLSChallenge,
|
||||
Domains: gc.ACME.Domains,
|
||||
ACMELogging: gc.ACME.ACMELogging,
|
||||
CAServer: gc.ACME.CAServer,
|
||||
EntryPoint: gc.ACME.EntryPoint,
|
||||
}
|
||||
|
||||
store := acmeprovider.NewLocalStore(provider.Storage)
|
||||
provider.Store = store
|
||||
acme.ConvertToNewFormat(provider.Storage)
|
||||
gc.ACME = nil
|
||||
return provider, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func getSafeACMECAServer(caServerSrc string) string {
|
||||
if len(caServerSrc) == 0 {
|
||||
return DefaultAcmeCAServer
|
||||
}
|
||||
|
||||
if strings.HasPrefix(caServerSrc, "https://acme-v01.api.letsencrypt.org") {
|
||||
caServer := strings.Replace(caServerSrc, "v01", "v02", 1)
|
||||
log.Warnf("The CA server %[1]q refers to a v01 endpoint of the ACME API, please change to %[2]q. Fallback to %[2]q.", caServerSrc, caServer)
|
||||
return caServer
|
||||
}
|
||||
|
||||
if strings.HasPrefix(caServerSrc, "https://acme-staging.api.letsencrypt.org") {
|
||||
caServer := strings.Replace(caServerSrc, "https://acme-staging.api.letsencrypt.org", "https://acme-staging-v02.api.letsencrypt.org", 1)
|
||||
log.Warnf("The CA server %[1]q refers to a v01 endpoint of the ACME API, please change to %[2]q. Fallback to %[2]q.", caServerSrc, caServer)
|
||||
return caServer
|
||||
}
|
||||
|
||||
return caServerSrc
|
||||
}
|
||||
|
||||
// ValidateConfiguration validate that configuration is coherent
|
||||
func (gc *GlobalConfiguration) ValidateConfiguration() {
|
||||
if gc.ACME != nil {
|
||||
if _, ok := gc.EntryPoints[gc.ACME.EntryPoint]; !ok {
|
||||
log.Fatalf("Unknown entrypoint %q for ACME configuration", gc.ACME.EntryPoint)
|
||||
} else {
|
||||
if gc.EntryPoints[gc.ACME.EntryPoint].TLS == nil {
|
||||
log.Fatalf("Entrypoint %q has no TLS configuration for ACME configuration", gc.ACME.EntryPoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultEntryPoints holds default entry points
|
||||
type DefaultEntryPoints []string
|
||||
|
||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||
// The String method's output will be used in diagnostics.
|
||||
func (dep *DefaultEntryPoints) String() string {
|
||||
return strings.Join(*dep, ",")
|
||||
}
|
||||
|
||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||
// Set's argument is a string to be parsed to set the flag.
|
||||
// It's a comma-separated list, so we split it.
|
||||
func (dep *DefaultEntryPoints) Set(value string) error {
|
||||
entrypoints := strings.Split(value, ",")
|
||||
if len(entrypoints) == 0 {
|
||||
return fmt.Errorf("bad DefaultEntryPoints format: %s", value)
|
||||
}
|
||||
for _, entrypoint := range entrypoints {
|
||||
*dep = append(*dep, entrypoint)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get return the EntryPoints map
|
||||
func (dep *DefaultEntryPoints) Get() interface{} {
|
||||
return *dep
|
||||
}
|
||||
|
||||
// SetValue sets the EntryPoints map with val
|
||||
func (dep *DefaultEntryPoints) SetValue(val interface{}) {
|
||||
*dep = val.(DefaultEntryPoints)
|
||||
}
|
||||
|
||||
// Type is type of the struct
|
||||
func (dep *DefaultEntryPoints) Type() string {
|
||||
return "defaultentrypoints"
|
||||
}
|
||||
|
||||
// Retry contains request retry config
|
||||
type Retry struct {
|
||||
Attempts int `description:"Number of attempts" export:"true"`
|
||||
}
|
||||
|
||||
// HealthCheckConfig contains health check configuration parameters.
|
||||
type HealthCheckConfig struct {
|
||||
Interval flaeg.Duration `description:"Default periodicity of enabled health checks" export:"true"`
|
||||
}
|
||||
|
||||
// RespondingTimeouts contains timeout configurations for incoming requests to the Traefik instance.
|
||||
type RespondingTimeouts struct {
|
||||
ReadTimeout flaeg.Duration `description:"ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set" export:"true"`
|
||||
WriteTimeout flaeg.Duration `description:"WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set" export:"true"`
|
||||
IdleTimeout flaeg.Duration `description:"IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. Defaults to 180 seconds. If zero, no timeout is set" export:"true"`
|
||||
}
|
||||
|
||||
// ForwardingTimeouts contains timeout configurations for forwarding requests to the backend servers.
|
||||
type ForwardingTimeouts struct {
|
||||
DialTimeout flaeg.Duration `description:"The amount of time to wait until a connection to a backend server can be established. Defaults to 30 seconds. If zero, no timeout exists" export:"true"`
|
||||
ResponseHeaderTimeout flaeg.Duration `description:"The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists" export:"true"`
|
||||
}
|
||||
|
||||
// LifeCycle contains configurations relevant to the lifecycle (such as the
|
||||
// shutdown phase) of Traefik.
|
||||
type LifeCycle struct {
|
||||
RequestAcceptGraceTimeout flaeg.Duration `description:"Duration to keep accepting requests before Traefik initiates the graceful shutdown procedure"`
|
||||
GraceTimeOut flaeg.Duration `description:"Duration to give active requests a chance to finish before Traefik stops"`
|
||||
}
|
||||
|
||||
// HostResolverConfig contain configuration for CNAME Flattening
|
||||
type HostResolverConfig struct {
|
||||
CnameFlattening bool `description:"A flag to enable/disable CNAME flattening" export:"true"`
|
||||
ResolvConfig string `description:"resolv.conf used for DNS resolving" export:"true"`
|
||||
ResolvDepth int `description:"The maximal depth of DNS recursive resolving" export:"true"`
|
||||
}
|
||||
271
configuration/configuration_test.go
Normal file
@@ -0,0 +1,271 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik/acme"
|
||||
"github.com/containous/traefik/middlewares/tracing"
|
||||
"github.com/containous/traefik/middlewares/tracing/jaeger"
|
||||
"github.com/containous/traefik/middlewares/tracing/zipkin"
|
||||
"github.com/containous/traefik/provider"
|
||||
acmeprovider "github.com/containous/traefik/provider/acme"
|
||||
"github.com/containous/traefik/provider/file"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const defaultConfigFile = "traefik.toml"
|
||||
|
||||
func TestSetEffectiveConfigurationGraceTimeout(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
legacyGraceTimeout time.Duration
|
||||
lifeCycleGraceTimeout time.Duration
|
||||
wantGraceTimeout time.Duration
|
||||
}{
|
||||
{
|
||||
desc: "legacy grace timeout given only",
|
||||
legacyGraceTimeout: 5 * time.Second,
|
||||
wantGraceTimeout: 5 * time.Second,
|
||||
},
|
||||
{
|
||||
desc: "legacy and life cycle grace timeouts given",
|
||||
legacyGraceTimeout: 5 * time.Second,
|
||||
lifeCycleGraceTimeout: 12 * time.Second,
|
||||
wantGraceTimeout: 5 * time.Second,
|
||||
},
|
||||
{
|
||||
desc: "legacy grace timeout omitted",
|
||||
legacyGraceTimeout: 0,
|
||||
lifeCycleGraceTimeout: 12 * time.Second,
|
||||
wantGraceTimeout: 12 * time.Second,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
gc := &GlobalConfiguration{
|
||||
GraceTimeOut: flaeg.Duration(test.legacyGraceTimeout),
|
||||
}
|
||||
if test.lifeCycleGraceTimeout > 0 {
|
||||
gc.LifeCycle = &LifeCycle{
|
||||
GraceTimeOut: flaeg.Duration(test.lifeCycleGraceTimeout),
|
||||
}
|
||||
}
|
||||
|
||||
gc.SetEffectiveConfiguration(defaultConfigFile)
|
||||
|
||||
assert.Equal(t, test.wantGraceTimeout, time.Duration(gc.LifeCycle.GraceTimeOut))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetEffectiveConfigurationFileProviderFilename(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
fileProvider *file.Provider
|
||||
wantFileProviderFilename string
|
||||
wantFileProviderTraefikFile string
|
||||
}{
|
||||
{
|
||||
desc: "no filename for file provider given",
|
||||
fileProvider: &file.Provider{},
|
||||
wantFileProviderFilename: "",
|
||||
wantFileProviderTraefikFile: defaultConfigFile,
|
||||
},
|
||||
{
|
||||
desc: "filename for file provider given",
|
||||
fileProvider: &file.Provider{BaseProvider: provider.BaseProvider{Filename: "other.toml"}},
|
||||
wantFileProviderFilename: "other.toml",
|
||||
wantFileProviderTraefikFile: defaultConfigFile,
|
||||
},
|
||||
{
|
||||
desc: "directory for file provider given",
|
||||
fileProvider: &file.Provider{Directory: "/"},
|
||||
wantFileProviderFilename: "",
|
||||
wantFileProviderTraefikFile: defaultConfigFile,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
gc := &GlobalConfiguration{
|
||||
File: test.fileProvider,
|
||||
}
|
||||
|
||||
gc.SetEffectiveConfiguration(defaultConfigFile)
|
||||
|
||||
assert.Equal(t, test.wantFileProviderFilename, gc.File.Filename)
|
||||
assert.Equal(t, test.wantFileProviderTraefikFile, gc.File.TraefikFile)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetEffectiveConfigurationTracing(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
tracing *tracing.Tracing
|
||||
expected *tracing.Tracing
|
||||
}{
|
||||
{
|
||||
desc: "no tracing configuration",
|
||||
tracing: &tracing.Tracing{},
|
||||
expected: &tracing.Tracing{},
|
||||
},
|
||||
{
|
||||
desc: "tracing bad backend name",
|
||||
tracing: &tracing.Tracing{
|
||||
Backend: "powpow",
|
||||
},
|
||||
expected: &tracing.Tracing{
|
||||
Backend: "powpow",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "tracing jaeger backend name",
|
||||
tracing: &tracing.Tracing{
|
||||
Backend: "jaeger",
|
||||
Zipkin: &zipkin.Config{
|
||||
HTTPEndpoint: "http://localhost:9411/api/v1/spans",
|
||||
SameSpan: false,
|
||||
ID128Bit: true,
|
||||
Debug: false,
|
||||
},
|
||||
},
|
||||
expected: &tracing.Tracing{
|
||||
Backend: "jaeger",
|
||||
Jaeger: &jaeger.Config{
|
||||
SamplingServerURL: "http://localhost:5778/sampling",
|
||||
SamplingType: "const",
|
||||
SamplingParam: 1.0,
|
||||
LocalAgentHostPort: "127.0.0.1:6831",
|
||||
TraceContextHeaderName: "uber-trace-id",
|
||||
},
|
||||
Zipkin: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "tracing zipkin backend name",
|
||||
tracing: &tracing.Tracing{
|
||||
Backend: "zipkin",
|
||||
Jaeger: &jaeger.Config{
|
||||
SamplingServerURL: "http://localhost:5778/sampling",
|
||||
SamplingType: "const",
|
||||
SamplingParam: 1.0,
|
||||
LocalAgentHostPort: "127.0.0.1:6831",
|
||||
TraceContextHeaderName: "uber-trace-id",
|
||||
},
|
||||
},
|
||||
expected: &tracing.Tracing{
|
||||
Backend: "zipkin",
|
||||
Jaeger: nil,
|
||||
Zipkin: &zipkin.Config{
|
||||
HTTPEndpoint: "http://localhost:9411/api/v1/spans",
|
||||
SameSpan: false,
|
||||
ID128Bit: true,
|
||||
Debug: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "tracing zipkin backend name value override",
|
||||
tracing: &tracing.Tracing{
|
||||
Backend: "zipkin",
|
||||
Jaeger: &jaeger.Config{
|
||||
SamplingServerURL: "http://localhost:5778/sampling",
|
||||
SamplingType: "const",
|
||||
SamplingParam: 1.0,
|
||||
LocalAgentHostPort: "127.0.0.1:6831",
|
||||
TraceContextHeaderName: "uber-trace-id",
|
||||
},
|
||||
Zipkin: &zipkin.Config{
|
||||
HTTPEndpoint: "http://powpow:9411/api/v1/spans",
|
||||
SameSpan: true,
|
||||
ID128Bit: true,
|
||||
Debug: true,
|
||||
},
|
||||
},
|
||||
expected: &tracing.Tracing{
|
||||
Backend: "zipkin",
|
||||
Jaeger: nil,
|
||||
Zipkin: &zipkin.Config{
|
||||
HTTPEndpoint: "http://powpow:9411/api/v1/spans",
|
||||
SameSpan: true,
|
||||
ID128Bit: true,
|
||||
Debug: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
gc := &GlobalConfiguration{
|
||||
Tracing: test.tracing,
|
||||
}
|
||||
|
||||
gc.SetEffectiveConfiguration(defaultConfigFile)
|
||||
|
||||
assert.Equal(t, test.expected, gc.Tracing)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitACMEProvider(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
acmeConfiguration *acme.ACME
|
||||
expectedConfiguration *acmeprovider.Provider
|
||||
noError bool
|
||||
}{
|
||||
{
|
||||
desc: "No ACME configuration",
|
||||
acmeConfiguration: nil,
|
||||
expectedConfiguration: nil,
|
||||
noError: true,
|
||||
},
|
||||
{
|
||||
desc: "ACME configuration with storage",
|
||||
acmeConfiguration: &acme.ACME{Storage: "foo/acme.json"},
|
||||
expectedConfiguration: &acmeprovider.Provider{Configuration: &acmeprovider.Configuration{Storage: "foo/acme.json"}},
|
||||
noError: true,
|
||||
},
|
||||
{
|
||||
desc: "ACME configuration with no storage",
|
||||
acmeConfiguration: &acme.ACME{},
|
||||
expectedConfiguration: nil,
|
||||
noError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
gc := &GlobalConfiguration{
|
||||
ACME: test.acmeConfiguration,
|
||||
}
|
||||
|
||||
configuration, err := gc.InitACMEProvider()
|
||||
|
||||
assert.True(t, (err == nil) == test.noError)
|
||||
|
||||
if test.expectedConfiguration == nil {
|
||||
assert.Nil(t, configuration)
|
||||
} else {
|
||||
assert.Equal(t, test.expectedConfiguration.Storage, configuration.Storage)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
296
configuration/entrypoints.go
Normal file
@@ -0,0 +1,296 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/tls"
|
||||
"github.com/containous/traefik/types"
|
||||
)
|
||||
|
||||
// EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...)
|
||||
type EntryPoint struct {
|
||||
Address string
|
||||
TLS *tls.TLS `export:"true"`
|
||||
Redirect *types.Redirect `export:"true"`
|
||||
Auth *types.Auth `export:"true"`
|
||||
WhitelistSourceRange []string // Deprecated
|
||||
WhiteList *types.WhiteList `export:"true"`
|
||||
Compress bool `export:"true"`
|
||||
ProxyProtocol *ProxyProtocol `export:"true"`
|
||||
ForwardedHeaders *ForwardedHeaders `export:"true"`
|
||||
}
|
||||
|
||||
// ProxyProtocol contains Proxy-Protocol configuration
|
||||
type ProxyProtocol struct {
|
||||
Insecure bool `export:"true"`
|
||||
TrustedIPs []string
|
||||
}
|
||||
|
||||
// ForwardedHeaders Trust client forwarding headers
|
||||
type ForwardedHeaders struct {
|
||||
Insecure bool `export:"true"`
|
||||
TrustedIPs []string
|
||||
}
|
||||
|
||||
// EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...)
|
||||
type EntryPoints map[string]*EntryPoint
|
||||
|
||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||
// The String method's output will be used in diagnostics.
|
||||
func (ep EntryPoints) String() string {
|
||||
return fmt.Sprintf("%+v", map[string]*EntryPoint(ep))
|
||||
}
|
||||
|
||||
// Get return the EntryPoints map
|
||||
func (ep *EntryPoints) Get() interface{} {
|
||||
return *ep
|
||||
}
|
||||
|
||||
// SetValue sets the EntryPoints map with val
|
||||
func (ep *EntryPoints) SetValue(val interface{}) {
|
||||
*ep = val.(EntryPoints)
|
||||
}
|
||||
|
||||
// Type is type of the struct
|
||||
func (ep *EntryPoints) Type() string {
|
||||
return "entrypoints"
|
||||
}
|
||||
|
||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||
// Set's argument is a string to be parsed to set the flag.
|
||||
// It's a comma-separated list, so we split it.
|
||||
func (ep *EntryPoints) Set(value string) error {
|
||||
result := parseEntryPointsConfiguration(value)
|
||||
|
||||
var whiteListSourceRange []string
|
||||
if len(result["whitelistsourcerange"]) > 0 {
|
||||
whiteListSourceRange = strings.Split(result["whitelistsourcerange"], ",")
|
||||
}
|
||||
|
||||
compress := toBool(result, "compress")
|
||||
|
||||
configTLS, err := makeEntryPointTLS(result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
(*ep)[result["name"]] = &EntryPoint{
|
||||
Address: result["address"],
|
||||
TLS: configTLS,
|
||||
Auth: makeEntryPointAuth(result),
|
||||
Redirect: makeEntryPointRedirect(result),
|
||||
Compress: compress,
|
||||
WhitelistSourceRange: whiteListSourceRange,
|
||||
WhiteList: makeWhiteList(result),
|
||||
ProxyProtocol: makeEntryPointProxyProtocol(result),
|
||||
ForwardedHeaders: makeEntryPointForwardedHeaders(result),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeWhiteList(result map[string]string) *types.WhiteList {
|
||||
var wl *types.WhiteList
|
||||
if rawRange, ok := result["whitelist_sourcerange"]; ok {
|
||||
wl = &types.WhiteList{
|
||||
SourceRange: strings.Split(rawRange, ","),
|
||||
UseXForwardedFor: toBool(result, "whitelist_usexforwardedfor"),
|
||||
}
|
||||
}
|
||||
return wl
|
||||
}
|
||||
|
||||
func makeEntryPointAuth(result map[string]string) *types.Auth {
|
||||
var basic *types.Basic
|
||||
if v, ok := result["auth_basic_users"]; ok {
|
||||
basic = &types.Basic{
|
||||
Users: strings.Split(v, ","),
|
||||
RemoveHeader: toBool(result, "auth_basic_removeheader"),
|
||||
}
|
||||
}
|
||||
|
||||
var digest *types.Digest
|
||||
if v, ok := result["auth_digest_users"]; ok {
|
||||
digest = &types.Digest{
|
||||
Users: strings.Split(v, ","),
|
||||
RemoveHeader: toBool(result, "auth_digest_removeheader"),
|
||||
}
|
||||
}
|
||||
|
||||
var forward *types.Forward
|
||||
if address, ok := result["auth_forward_address"]; ok {
|
||||
var clientTLS *types.ClientTLS
|
||||
|
||||
cert := result["auth_forward_tls_cert"]
|
||||
key := result["auth_forward_tls_key"]
|
||||
insecureSkipVerify := toBool(result, "auth_forward_tls_insecureskipverify")
|
||||
|
||||
if len(cert) > 0 && len(key) > 0 || insecureSkipVerify {
|
||||
clientTLS = &types.ClientTLS{
|
||||
CA: result["auth_forward_tls_ca"],
|
||||
CAOptional: toBool(result, "auth_forward_tls_caoptional"),
|
||||
Cert: cert,
|
||||
Key: key,
|
||||
InsecureSkipVerify: insecureSkipVerify,
|
||||
}
|
||||
}
|
||||
|
||||
var authResponseHeaders []string
|
||||
if v, ok := result["auth_forward_authresponseheaders"]; ok {
|
||||
authResponseHeaders = strings.Split(v, ",")
|
||||
}
|
||||
|
||||
forward = &types.Forward{
|
||||
Address: address,
|
||||
TLS: clientTLS,
|
||||
TrustForwardHeader: toBool(result, "auth_forward_trustforwardheader"),
|
||||
AuthResponseHeaders: authResponseHeaders,
|
||||
}
|
||||
}
|
||||
|
||||
var auth *types.Auth
|
||||
if basic != nil || digest != nil || forward != nil {
|
||||
auth = &types.Auth{
|
||||
Basic: basic,
|
||||
Digest: digest,
|
||||
Forward: forward,
|
||||
HeaderField: result["auth_headerfield"],
|
||||
}
|
||||
}
|
||||
|
||||
return auth
|
||||
}
|
||||
|
||||
func makeEntryPointProxyProtocol(result map[string]string) *ProxyProtocol {
|
||||
var proxyProtocol *ProxyProtocol
|
||||
|
||||
ppTrustedIPs := result["proxyprotocol_trustedips"]
|
||||
if len(result["proxyprotocol_insecure"]) > 0 || len(ppTrustedIPs) > 0 {
|
||||
proxyProtocol = &ProxyProtocol{
|
||||
Insecure: toBool(result, "proxyprotocol_insecure"),
|
||||
}
|
||||
if len(ppTrustedIPs) > 0 {
|
||||
proxyProtocol.TrustedIPs = strings.Split(ppTrustedIPs, ",")
|
||||
}
|
||||
}
|
||||
|
||||
if proxyProtocol != nil && proxyProtocol.Insecure {
|
||||
log.Warn("ProxyProtocol.Insecure:true is dangerous. Please use 'ProxyProtocol.TrustedIPs:IPs' and remove 'ProxyProtocol.Insecure:true'")
|
||||
}
|
||||
|
||||
return proxyProtocol
|
||||
}
|
||||
|
||||
func makeEntryPointForwardedHeaders(result map[string]string) *ForwardedHeaders {
|
||||
// TODO must be changed to false by default in the next breaking version.
|
||||
forwardedHeaders := &ForwardedHeaders{Insecure: true}
|
||||
if _, ok := result["forwardedheaders_insecure"]; ok {
|
||||
forwardedHeaders.Insecure = toBool(result, "forwardedheaders_insecure")
|
||||
}
|
||||
|
||||
fhTrustedIPs := result["forwardedheaders_trustedips"]
|
||||
if len(fhTrustedIPs) > 0 {
|
||||
// TODO must be removed in the next breaking version.
|
||||
forwardedHeaders.Insecure = toBool(result, "forwardedheaders_insecure")
|
||||
forwardedHeaders.TrustedIPs = strings.Split(fhTrustedIPs, ",")
|
||||
}
|
||||
|
||||
return forwardedHeaders
|
||||
}
|
||||
|
||||
func makeEntryPointRedirect(result map[string]string) *types.Redirect {
|
||||
var redirect *types.Redirect
|
||||
|
||||
if len(result["redirect_entrypoint"]) > 0 || len(result["redirect_regex"]) > 0 || len(result["redirect_replacement"]) > 0 {
|
||||
redirect = &types.Redirect{
|
||||
EntryPoint: result["redirect_entrypoint"],
|
||||
Regex: result["redirect_regex"],
|
||||
Replacement: result["redirect_replacement"],
|
||||
Permanent: toBool(result, "redirect_permanent"),
|
||||
}
|
||||
}
|
||||
|
||||
return redirect
|
||||
}
|
||||
|
||||
func makeEntryPointTLS(result map[string]string) (*tls.TLS, error) {
|
||||
var configTLS *tls.TLS
|
||||
|
||||
if len(result["tls"]) > 0 {
|
||||
certs := tls.Certificates{}
|
||||
if err := certs.Set(result["tls"]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configTLS = &tls.TLS{
|
||||
Certificates: certs,
|
||||
}
|
||||
} else if len(result["tls_acme"]) > 0 {
|
||||
configTLS = &tls.TLS{
|
||||
Certificates: tls.Certificates{},
|
||||
}
|
||||
}
|
||||
|
||||
if configTLS != nil {
|
||||
if len(result["ca"]) > 0 {
|
||||
files := tls.FilesOrContents{}
|
||||
files.Set(result["ca"])
|
||||
optional := toBool(result, "ca_optional")
|
||||
configTLS.ClientCA = tls.ClientCA{
|
||||
Files: files,
|
||||
Optional: optional,
|
||||
}
|
||||
}
|
||||
|
||||
if len(result["tls_minversion"]) > 0 {
|
||||
configTLS.MinVersion = result["tls_minversion"]
|
||||
}
|
||||
|
||||
if len(result["tls_ciphersuites"]) > 0 {
|
||||
configTLS.CipherSuites = strings.Split(result["tls_ciphersuites"], ",")
|
||||
}
|
||||
|
||||
if len(result["tls_snistrict"]) > 0 {
|
||||
configTLS.SniStrict = toBool(result, "tls_snistrict")
|
||||
}
|
||||
|
||||
if len(result["tls_defaultcertificate_cert"]) > 0 && len(result["tls_defaultcertificate_key"]) > 0 {
|
||||
configTLS.DefaultCertificate = &tls.Certificate{
|
||||
CertFile: tls.FileOrContent(result["tls_defaultcertificate_cert"]),
|
||||
KeyFile: tls.FileOrContent(result["tls_defaultcertificate_key"]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return configTLS, nil
|
||||
}
|
||||
|
||||
func parseEntryPointsConfiguration(raw string) map[string]string {
|
||||
sections := strings.Fields(raw)
|
||||
|
||||
config := make(map[string]string)
|
||||
for _, part := range sections {
|
||||
field := strings.SplitN(part, ":", 2)
|
||||
name := strings.ToLower(strings.Replace(field[0], ".", "_", -1))
|
||||
if len(field) > 1 {
|
||||
config[name] = field[1]
|
||||
} else {
|
||||
if strings.EqualFold(name, "TLS") {
|
||||
config["tls_acme"] = "TLS"
|
||||
} else {
|
||||
config[name] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func toBool(conf map[string]string, key string) bool {
|
||||
if val, ok := conf[key]; ok {
|
||||
return strings.EqualFold(val, "true") ||
|
||||
strings.EqualFold(val, "enable") ||
|
||||
strings.EqualFold(val, "on")
|
||||
}
|
||||
return false
|
||||
}
|
||||
493
configuration/entrypoints_test.go
Normal file
@@ -0,0 +1,493 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/tls"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_parseEntryPointsConfiguration(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
value string
|
||||
expectedResult map[string]string
|
||||
}{
|
||||
{
|
||||
name: "all parameters",
|
||||
value: "Name:foo " +
|
||||
"Address::8000 " +
|
||||
"TLS:goo,gii " +
|
||||
"TLS " +
|
||||
"TLS.MinVersion:VersionTLS11 " +
|
||||
"TLS.CipherSuites:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA " +
|
||||
"CA:car " +
|
||||
"CA.Optional:true " +
|
||||
"Redirect.EntryPoint:https " +
|
||||
"Redirect.Regex:http://localhost/(.*) " +
|
||||
"Redirect.Replacement:http://mydomain/$1 " +
|
||||
"Redirect.Permanent:true " +
|
||||
"Compress:true " +
|
||||
"ProxyProtocol.TrustedIPs:192.168.0.1 " +
|
||||
"ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 " +
|
||||
"Auth.Basic.Users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 " +
|
||||
"Auth.Basic.RemoveHeader:true " +
|
||||
"Auth.Digest.Users:test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e " +
|
||||
"Auth.Digest.RemoveHeader:true " +
|
||||
"Auth.HeaderField:X-WebAuth-User " +
|
||||
"Auth.Forward.Address:https://authserver.com/auth " +
|
||||
"Auth.Forward.AuthResponseHeaders:X-Auth,X-Test,X-Secret " +
|
||||
"Auth.Forward.TrustForwardHeader:true " +
|
||||
"Auth.Forward.TLS.CA:path/to/local.crt " +
|
||||
"Auth.Forward.TLS.CAOptional:true " +
|
||||
"Auth.Forward.TLS.Cert:path/to/foo.cert " +
|
||||
"Auth.Forward.TLS.Key:path/to/foo.key " +
|
||||
"Auth.Forward.TLS.InsecureSkipVerify:true " +
|
||||
"WhiteListSourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " +
|
||||
"whiteList.sourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " +
|
||||
"whiteList.useXForwardedFor:true ",
|
||||
expectedResult: map[string]string{
|
||||
"address": ":8000",
|
||||
"auth_basic_users": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
"auth_basic_removeheader": "true",
|
||||
"auth_digest_users": "test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e",
|
||||
"auth_digest_removeheader": "true",
|
||||
"auth_forward_address": "https://authserver.com/auth",
|
||||
"auth_forward_authresponseheaders": "X-Auth,X-Test,X-Secret",
|
||||
"auth_forward_tls_ca": "path/to/local.crt",
|
||||
"auth_forward_tls_caoptional": "true",
|
||||
"auth_forward_tls_cert": "path/to/foo.cert",
|
||||
"auth_forward_tls_insecureskipverify": "true",
|
||||
"auth_forward_tls_key": "path/to/foo.key",
|
||||
"auth_forward_trustforwardheader": "true",
|
||||
"auth_headerfield": "X-WebAuth-User",
|
||||
"ca": "car",
|
||||
"ca_optional": "true",
|
||||
"compress": "true",
|
||||
"forwardedheaders_trustedips": "10.0.0.3/24,20.0.0.3/24",
|
||||
"name": "foo",
|
||||
"proxyprotocol_trustedips": "192.168.0.1",
|
||||
"redirect_entrypoint": "https",
|
||||
"redirect_permanent": "true",
|
||||
"redirect_regex": "http://localhost/(.*)",
|
||||
"redirect_replacement": "http://mydomain/$1",
|
||||
"tls": "goo,gii",
|
||||
"tls_acme": "TLS",
|
||||
"tls_ciphersuites": "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
|
||||
"tls_minversion": "VersionTLS11",
|
||||
"whitelistsourcerange": "10.42.0.0/16,152.89.1.33/32,afed:be44::/16",
|
||||
"whitelist_sourcerange": "10.42.0.0/16,152.89.1.33/32,afed:be44::/16",
|
||||
"whitelist_usexforwardedfor": "true",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "compress on",
|
||||
value: "name:foo Compress:on",
|
||||
expectedResult: map[string]string{
|
||||
"name": "foo",
|
||||
"compress": "on",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "TLS",
|
||||
value: "Name:foo TLS:goo TLS",
|
||||
expectedResult: map[string]string{
|
||||
"name": "foo",
|
||||
"tls": "goo",
|
||||
"tls_acme": "TLS",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
conf := parseEntryPointsConfiguration(test.value)
|
||||
|
||||
assert.Len(t, conf, len(test.expectedResult))
|
||||
assert.Equal(t, test.expectedResult, conf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_toBool(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
value string
|
||||
key string
|
||||
expectedBool bool
|
||||
}{
|
||||
{
|
||||
name: "on",
|
||||
value: "on",
|
||||
key: "foo",
|
||||
expectedBool: true,
|
||||
},
|
||||
{
|
||||
name: "true",
|
||||
value: "true",
|
||||
key: "foo",
|
||||
expectedBool: true,
|
||||
},
|
||||
{
|
||||
name: "enable",
|
||||
value: "enable",
|
||||
key: "foo",
|
||||
expectedBool: true,
|
||||
},
|
||||
{
|
||||
name: "arbitrary string",
|
||||
value: "bar",
|
||||
key: "foo",
|
||||
expectedBool: false,
|
||||
},
|
||||
{
|
||||
name: "no existing entry",
|
||||
value: "bar",
|
||||
key: "fii",
|
||||
expectedBool: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
conf := map[string]string{
|
||||
"foo": test.value,
|
||||
}
|
||||
|
||||
result := toBool(conf, test.key)
|
||||
|
||||
assert.Equal(t, test.expectedBool, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEntryPoints_Set(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
expression string
|
||||
expectedEntryPointName string
|
||||
expectedEntryPoint *EntryPoint
|
||||
}{
|
||||
{
|
||||
name: "all parameters camelcase",
|
||||
expression: "Name:foo " +
|
||||
"Address::8000 " +
|
||||
"TLS:goo,gii;foo,fii " +
|
||||
"TLS " +
|
||||
"TLS.MinVersion:VersionTLS11 " +
|
||||
"TLS.CipherSuites:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA " +
|
||||
"CA:car " +
|
||||
"CA.Optional:true " +
|
||||
"Redirect.EntryPoint:https " +
|
||||
"Redirect.Regex:http://localhost/(.*) " +
|
||||
"Redirect.Replacement:http://mydomain/$1 " +
|
||||
"Redirect.Permanent:true " +
|
||||
"Compress:true " +
|
||||
"ProxyProtocol.TrustedIPs:192.168.0.1 " +
|
||||
"ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 " +
|
||||
"Auth.Basic.Users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 " +
|
||||
"Auth.Basic.RemoveHeader:true " +
|
||||
"Auth.Digest.Users:test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e " +
|
||||
"Auth.Digest.RemoveHeader:true " +
|
||||
"Auth.HeaderField:X-WebAuth-User " +
|
||||
"Auth.Forward.Address:https://authserver.com/auth " +
|
||||
"Auth.Forward.AuthResponseHeaders:X-Auth,X-Test,X-Secret " +
|
||||
"Auth.Forward.TrustForwardHeader:true " +
|
||||
"Auth.Forward.TLS.CA:path/to/local.crt " +
|
||||
"Auth.Forward.TLS.CAOptional:true " +
|
||||
"Auth.Forward.TLS.Cert:path/to/foo.cert " +
|
||||
"Auth.Forward.TLS.Key:path/to/foo.key " +
|
||||
"Auth.Forward.TLS.InsecureSkipVerify:true " +
|
||||
"WhiteListSourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " +
|
||||
"whiteList.sourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " +
|
||||
"whiteList.useXForwardedFor:true ",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
Address: ":8000",
|
||||
TLS: &tls.TLS{
|
||||
MinVersion: "VersionTLS11",
|
||||
CipherSuites: []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384", "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"},
|
||||
Certificates: tls.Certificates{
|
||||
{
|
||||
CertFile: tls.FileOrContent("goo"),
|
||||
KeyFile: tls.FileOrContent("gii"),
|
||||
},
|
||||
{
|
||||
CertFile: tls.FileOrContent("foo"),
|
||||
KeyFile: tls.FileOrContent("fii"),
|
||||
},
|
||||
},
|
||||
ClientCA: tls.ClientCA{
|
||||
Files: tls.FilesOrContents{"car"},
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
Redirect: &types.Redirect{
|
||||
EntryPoint: "https",
|
||||
Regex: "http://localhost/(.*)",
|
||||
Replacement: "http://mydomain/$1",
|
||||
Permanent: true,
|
||||
},
|
||||
Auth: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
RemoveHeader: true,
|
||||
Users: types.Users{
|
||||
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
||||
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
},
|
||||
},
|
||||
Digest: &types.Digest{
|
||||
RemoveHeader: true,
|
||||
Users: types.Users{
|
||||
"test:traefik:a2688e031edb4be6a3797f3882655c05",
|
||||
"test2:traefik:518845800f9e2bfb1f1f740ec24f074e",
|
||||
},
|
||||
},
|
||||
Forward: &types.Forward{
|
||||
Address: "https://authserver.com/auth",
|
||||
AuthResponseHeaders: []string{"X-Auth", "X-Test", "X-Secret"},
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "path/to/local.crt",
|
||||
CAOptional: true,
|
||||
Cert: "path/to/foo.cert",
|
||||
Key: "path/to/foo.key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
TrustForwardHeader: true,
|
||||
},
|
||||
HeaderField: "X-WebAuth-User",
|
||||
},
|
||||
WhitelistSourceRange: []string{
|
||||
"10.42.0.0/16",
|
||||
"152.89.1.33/32",
|
||||
"afed:be44::/16",
|
||||
},
|
||||
WhiteList: &types.WhiteList{
|
||||
SourceRange: []string{
|
||||
"10.42.0.0/16",
|
||||
"152.89.1.33/32",
|
||||
"afed:be44::/16",
|
||||
},
|
||||
UseXForwardedFor: true,
|
||||
},
|
||||
Compress: true,
|
||||
ProxyProtocol: &ProxyProtocol{
|
||||
Insecure: false,
|
||||
TrustedIPs: []string{"192.168.0.1"},
|
||||
},
|
||||
ForwardedHeaders: &ForwardedHeaders{
|
||||
Insecure: false,
|
||||
TrustedIPs: []string{
|
||||
"10.0.0.3/24",
|
||||
"20.0.0.3/24",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all parameters lowercase",
|
||||
expression: "Name:foo " +
|
||||
"address::8000 " +
|
||||
"tls:goo,gii;foo,fii " +
|
||||
"tls " +
|
||||
"tls.minversion:VersionTLS11 " +
|
||||
"tls.ciphersuites:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA " +
|
||||
"ca:car " +
|
||||
"ca.Optional:true " +
|
||||
"redirect.entryPoint:https " +
|
||||
"redirect.regex:http://localhost/(.*) " +
|
||||
"redirect.replacement:http://mydomain/$1 " +
|
||||
"redirect.permanent:true " +
|
||||
"compress:true " +
|
||||
"whiteListSourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " +
|
||||
"proxyProtocol.TrustedIPs:192.168.0.1 " +
|
||||
"forwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 " +
|
||||
"auth.basic.users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 " +
|
||||
"auth.digest.users:test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e " +
|
||||
"auth.headerField:X-WebAuth-User " +
|
||||
"auth.forward.address:https://authserver.com/auth " +
|
||||
"auth.forward.authResponseHeaders:X-Auth,X-Test,X-Secret " +
|
||||
"auth.forward.trustForwardHeader:true " +
|
||||
"auth.forward.tls.ca:path/to/local.crt " +
|
||||
"auth.forward.tls.caOptional:true " +
|
||||
"auth.forward.tls.cert:path/to/foo.cert " +
|
||||
"auth.forward.tls.key:path/to/foo.key " +
|
||||
"auth.forward.tls.insecureSkipVerify:true ",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
Address: ":8000",
|
||||
TLS: &tls.TLS{
|
||||
MinVersion: "VersionTLS11",
|
||||
CipherSuites: []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384", "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"},
|
||||
Certificates: tls.Certificates{
|
||||
{
|
||||
CertFile: tls.FileOrContent("goo"),
|
||||
KeyFile: tls.FileOrContent("gii"),
|
||||
},
|
||||
{
|
||||
CertFile: tls.FileOrContent("foo"),
|
||||
KeyFile: tls.FileOrContent("fii"),
|
||||
},
|
||||
},
|
||||
ClientCA: tls.ClientCA{
|
||||
Files: tls.FilesOrContents{"car"},
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
Redirect: &types.Redirect{
|
||||
EntryPoint: "https",
|
||||
Regex: "http://localhost/(.*)",
|
||||
Replacement: "http://mydomain/$1",
|
||||
Permanent: true,
|
||||
},
|
||||
Auth: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
Users: types.Users{
|
||||
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
||||
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
},
|
||||
},
|
||||
Digest: &types.Digest{
|
||||
Users: types.Users{
|
||||
"test:traefik:a2688e031edb4be6a3797f3882655c05",
|
||||
"test2:traefik:518845800f9e2bfb1f1f740ec24f074e",
|
||||
},
|
||||
},
|
||||
Forward: &types.Forward{
|
||||
Address: "https://authserver.com/auth",
|
||||
AuthResponseHeaders: []string{"X-Auth", "X-Test", "X-Secret"},
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "path/to/local.crt",
|
||||
CAOptional: true,
|
||||
Cert: "path/to/foo.cert",
|
||||
Key: "path/to/foo.key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
TrustForwardHeader: true,
|
||||
},
|
||||
HeaderField: "X-WebAuth-User",
|
||||
},
|
||||
WhitelistSourceRange: []string{
|
||||
"10.42.0.0/16",
|
||||
"152.89.1.33/32",
|
||||
"afed:be44::/16",
|
||||
},
|
||||
Compress: true,
|
||||
ProxyProtocol: &ProxyProtocol{
|
||||
Insecure: false,
|
||||
TrustedIPs: []string{"192.168.0.1"},
|
||||
},
|
||||
ForwardedHeaders: &ForwardedHeaders{
|
||||
Insecure: false,
|
||||
TrustedIPs: []string{
|
||||
"10.0.0.3/24",
|
||||
"20.0.0.3/24",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "default",
|
||||
expression: "Name:foo",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ForwardedHeaders insecure true",
|
||||
expression: "Name:foo ForwardedHeaders.Insecure:true",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ForwardedHeaders insecure false",
|
||||
expression: "Name:foo ForwardedHeaders.Insecure:false",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ForwardedHeaders TrustedIPs",
|
||||
expression: "Name:foo ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ForwardedHeaders: &ForwardedHeaders{
|
||||
TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ProxyProtocol insecure true",
|
||||
expression: "Name:foo ProxyProtocol.Insecure:true",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
ProxyProtocol: &ProxyProtocol{Insecure: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ProxyProtocol insecure false",
|
||||
expression: "Name:foo ProxyProtocol.Insecure:false",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
ProxyProtocol: &ProxyProtocol{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ProxyProtocol TrustedIPs",
|
||||
expression: "Name:foo ProxyProtocol.TrustedIPs:10.0.0.3/24,20.0.0.3/24",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
ProxyProtocol: &ProxyProtocol{
|
||||
TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "compress on",
|
||||
expression: "Name:foo Compress:on",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
Compress: true,
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "compress true",
|
||||
expression: "Name:foo Compress:true",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
Compress: true,
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
eps := EntryPoints{}
|
||||
err := eps.Set(test.expression)
|
||||
require.NoError(t, err)
|
||||
|
||||
ep := eps[test.expectedEntryPointName]
|
||||
assert.EqualValues(t, test.expectedEntryPoint, ep)
|
||||
})
|
||||
}
|
||||
}
|
||||
113
configuration/provider_aggregator.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/provider"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
)
|
||||
|
||||
// ProviderAggregator aggregate providers
|
||||
type ProviderAggregator struct {
|
||||
providers []provider.Provider
|
||||
constraints types.Constraints
|
||||
}
|
||||
|
||||
// NewProviderAggregator return an aggregate of all the providers configured in GlobalConfiguration
|
||||
func NewProviderAggregator(gc *GlobalConfiguration) ProviderAggregator {
|
||||
provider := ProviderAggregator{
|
||||
constraints: gc.Constraints,
|
||||
}
|
||||
if gc.Docker != nil {
|
||||
provider.quietAddProvider(gc.Docker)
|
||||
}
|
||||
if gc.Marathon != nil {
|
||||
provider.quietAddProvider(gc.Marathon)
|
||||
}
|
||||
if gc.File != nil {
|
||||
provider.quietAddProvider(gc.File)
|
||||
}
|
||||
if gc.Rest != nil {
|
||||
provider.quietAddProvider(gc.Rest)
|
||||
}
|
||||
if gc.Consul != nil {
|
||||
provider.quietAddProvider(gc.Consul)
|
||||
}
|
||||
if gc.ConsulCatalog != nil {
|
||||
provider.quietAddProvider(gc.ConsulCatalog)
|
||||
}
|
||||
if gc.Etcd != nil {
|
||||
provider.quietAddProvider(gc.Etcd)
|
||||
}
|
||||
if gc.Zookeeper != nil {
|
||||
provider.quietAddProvider(gc.Zookeeper)
|
||||
}
|
||||
if gc.Boltdb != nil {
|
||||
provider.quietAddProvider(gc.Boltdb)
|
||||
}
|
||||
if gc.Kubernetes != nil {
|
||||
provider.quietAddProvider(gc.Kubernetes)
|
||||
}
|
||||
if gc.Mesos != nil {
|
||||
provider.quietAddProvider(gc.Mesos)
|
||||
}
|
||||
if gc.Eureka != nil {
|
||||
provider.quietAddProvider(gc.Eureka)
|
||||
}
|
||||
if gc.ECS != nil {
|
||||
provider.quietAddProvider(gc.ECS)
|
||||
}
|
||||
if gc.Rancher != nil {
|
||||
provider.quietAddProvider(gc.Rancher)
|
||||
}
|
||||
if gc.DynamoDB != nil {
|
||||
provider.quietAddProvider(gc.DynamoDB)
|
||||
}
|
||||
if gc.ServiceFabric != nil {
|
||||
provider.quietAddProvider(gc.ServiceFabric)
|
||||
}
|
||||
return provider
|
||||
}
|
||||
|
||||
func (p *ProviderAggregator) quietAddProvider(provider provider.Provider) {
|
||||
err := p.AddProvider(provider)
|
||||
if err != nil {
|
||||
log.Errorf("Error initializing provider %T: %v", provider, err)
|
||||
}
|
||||
}
|
||||
|
||||
// AddProvider add a provider in the providers map
|
||||
func (p *ProviderAggregator) AddProvider(provider provider.Provider) error {
|
||||
err := provider.Init(p.constraints)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.providers = append(p.providers, provider)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Init the provider
|
||||
func (p ProviderAggregator) Init(_ types.Constraints) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Provide call the provide method of every providers
|
||||
func (p ProviderAggregator) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
for _, p := range p.providers {
|
||||
jsonConf, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
log.Debugf("Unable to marshal provider conf %T with error: %v", p, err)
|
||||
}
|
||||
log.Infof("Starting provider %T %s", p, jsonConf)
|
||||
currentProvider := p
|
||||
safe.Go(func() {
|
||||
err := currentProvider.Provide(configurationChan, pool)
|
||||
if err != nil {
|
||||
log.Errorf("Error starting provider %T: %v", p, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
132
configuration/router/internal_router.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/configuration"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/metrics"
|
||||
"github.com/containous/traefik/middlewares"
|
||||
mauth "github.com/containous/traefik/middlewares/auth"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/urfave/negroni"
|
||||
)
|
||||
|
||||
// NewInternalRouterAggregator Create a new internalRouterAggregator
|
||||
func NewInternalRouterAggregator(globalConfiguration configuration.GlobalConfiguration, entryPointName string) *InternalRouterAggregator {
|
||||
var serverMiddlewares []negroni.Handler
|
||||
|
||||
if globalConfiguration.EntryPoints[entryPointName].WhiteList != nil {
|
||||
ipWhitelistMiddleware, err := middlewares.NewIPWhiteLister(
|
||||
globalConfiguration.EntryPoints[entryPointName].WhiteList.SourceRange,
|
||||
globalConfiguration.EntryPoints[entryPointName].WhiteList.UseXForwardedFor)
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating whitelist middleware: %s", err)
|
||||
}
|
||||
if ipWhitelistMiddleware != nil {
|
||||
serverMiddlewares = append(serverMiddlewares, ipWhitelistMiddleware)
|
||||
}
|
||||
}
|
||||
|
||||
if globalConfiguration.EntryPoints[entryPointName].Auth != nil {
|
||||
authMiddleware, err := mauth.NewAuthenticator(globalConfiguration.EntryPoints[entryPointName].Auth, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating authenticator middleware: %s", err)
|
||||
}
|
||||
serverMiddlewares = append(serverMiddlewares, authMiddleware)
|
||||
}
|
||||
|
||||
router := InternalRouterAggregator{}
|
||||
routerWithPrefix := InternalRouterAggregator{}
|
||||
routerWithPrefixAndMiddleware := InternalRouterAggregator{}
|
||||
|
||||
if globalConfiguration.Metrics != nil && globalConfiguration.Metrics.Prometheus != nil && globalConfiguration.Metrics.Prometheus.EntryPoint == entryPointName {
|
||||
routerWithPrefixAndMiddleware.AddRouter(metrics.PrometheusHandler{})
|
||||
}
|
||||
|
||||
if globalConfiguration.Rest != nil && globalConfiguration.Rest.EntryPoint == entryPointName {
|
||||
routerWithPrefixAndMiddleware.AddRouter(globalConfiguration.Rest)
|
||||
}
|
||||
|
||||
if globalConfiguration.API != nil && globalConfiguration.API.EntryPoint == entryPointName {
|
||||
routerWithPrefixAndMiddleware.AddRouter(globalConfiguration.API)
|
||||
}
|
||||
|
||||
if globalConfiguration.Ping != nil && globalConfiguration.Ping.EntryPoint == entryPointName {
|
||||
routerWithPrefix.AddRouter(globalConfiguration.Ping)
|
||||
}
|
||||
|
||||
if globalConfiguration.ACME != nil && globalConfiguration.ACME.HTTPChallenge != nil && globalConfiguration.ACME.HTTPChallenge.EntryPoint == entryPointName {
|
||||
router.AddRouter(globalConfiguration.ACME)
|
||||
}
|
||||
|
||||
realRouterWithMiddleware := WithMiddleware{router: &routerWithPrefixAndMiddleware, routerMiddlewares: serverMiddlewares}
|
||||
if globalConfiguration.Web != nil && globalConfiguration.Web.Path != "" {
|
||||
router.AddRouter(&WithPrefix{PathPrefix: globalConfiguration.Web.Path, Router: &routerWithPrefix})
|
||||
router.AddRouter(&WithPrefix{PathPrefix: globalConfiguration.Web.Path, Router: &realRouterWithMiddleware})
|
||||
} else {
|
||||
router.AddRouter(&routerWithPrefix)
|
||||
router.AddRouter(&realRouterWithMiddleware)
|
||||
}
|
||||
|
||||
return &router
|
||||
}
|
||||
|
||||
// WithMiddleware router with internal middleware
|
||||
type WithMiddleware struct {
|
||||
router types.InternalRouter
|
||||
routerMiddlewares []negroni.Handler
|
||||
}
|
||||
|
||||
// AddRoutes Add routes to the router
|
||||
func (wm *WithMiddleware) AddRoutes(systemRouter *mux.Router) {
|
||||
realRouter := systemRouter.PathPrefix("/").Subrouter()
|
||||
|
||||
wm.router.AddRoutes(realRouter)
|
||||
|
||||
if len(wm.routerMiddlewares) > 0 {
|
||||
realRouter.Walk(wrapRoute(wm.routerMiddlewares))
|
||||
}
|
||||
}
|
||||
|
||||
// WithPrefix router which add a prefix
|
||||
type WithPrefix struct {
|
||||
Router types.InternalRouter
|
||||
PathPrefix string
|
||||
}
|
||||
|
||||
// AddRoutes Add routes to the router
|
||||
func (wp *WithPrefix) AddRoutes(systemRouter *mux.Router) {
|
||||
realRouter := systemRouter.PathPrefix("/").Subrouter()
|
||||
if wp.PathPrefix != "" {
|
||||
realRouter = systemRouter.PathPrefix(wp.PathPrefix).Subrouter()
|
||||
realRouter.StrictSlash(true)
|
||||
realRouter.SkipClean(true)
|
||||
}
|
||||
wp.Router.AddRoutes(realRouter)
|
||||
}
|
||||
|
||||
// InternalRouterAggregator InternalRouter that aggregate other internalRouter
|
||||
type InternalRouterAggregator struct {
|
||||
internalRouters []types.InternalRouter
|
||||
}
|
||||
|
||||
// AddRouter add a router in the aggregator
|
||||
func (r *InternalRouterAggregator) AddRouter(router types.InternalRouter) {
|
||||
r.internalRouters = append(r.internalRouters, router)
|
||||
}
|
||||
|
||||
// AddRoutes Add routes to the router
|
||||
func (r *InternalRouterAggregator) AddRoutes(systemRouter *mux.Router) {
|
||||
for _, router := range r.internalRouters {
|
||||
router.AddRoutes(systemRouter)
|
||||
}
|
||||
}
|
||||
|
||||
// wrapRoute with middlewares
|
||||
func wrapRoute(middlewares []negroni.Handler) func(*mux.Route, *mux.Router, []*mux.Route) error {
|
||||
return func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
|
||||
middles := append(middlewares, negroni.Wrap(route.GetHandler()))
|
||||
route.Handler(negroni.New(middles...))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
346
configuration/router/internal_router_test.go
Normal file
@@ -0,0 +1,346 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/acme"
|
||||
"github.com/containous/traefik/api"
|
||||
"github.com/containous/traefik/configuration"
|
||||
"github.com/containous/traefik/ping"
|
||||
acmeprovider "github.com/containous/traefik/provider/acme"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/negroni"
|
||||
)
|
||||
|
||||
func TestNewInternalRouterAggregatorWithWebPath(t *testing.T) {
|
||||
currentConfiguration := &safe.Safe{}
|
||||
currentConfiguration.Set(types.Configurations{})
|
||||
|
||||
globalConfiguration := configuration.GlobalConfiguration{
|
||||
Web: &configuration.WebCompatibility{
|
||||
Path: "/prefix",
|
||||
},
|
||||
API: &api.Handler{
|
||||
EntryPoint: "traefik",
|
||||
CurrentConfigurations: currentConfiguration,
|
||||
},
|
||||
Ping: &ping.Handler{
|
||||
EntryPoint: "traefik",
|
||||
},
|
||||
ACME: &acme.ACME{
|
||||
HTTPChallenge: &acmeprovider.HTTPChallenge{
|
||||
EntryPoint: "traefik",
|
||||
},
|
||||
},
|
||||
EntryPoints: configuration.EntryPoints{
|
||||
"traefik": &configuration.EntryPoint{},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
testedURL string
|
||||
expectedStatusCode int
|
||||
}{
|
||||
{
|
||||
desc: "Ping without prefix",
|
||||
testedURL: "/ping",
|
||||
expectedStatusCode: 502,
|
||||
},
|
||||
{
|
||||
desc: "Ping with prefix",
|
||||
testedURL: "/prefix/ping",
|
||||
expectedStatusCode: 200,
|
||||
},
|
||||
{
|
||||
desc: "acme without prefix",
|
||||
testedURL: "/.well-known/acme-challenge/token",
|
||||
expectedStatusCode: 404,
|
||||
},
|
||||
{
|
||||
desc: "api without prefix",
|
||||
testedURL: "/api",
|
||||
expectedStatusCode: 502,
|
||||
},
|
||||
{
|
||||
desc: "api with prefix",
|
||||
testedURL: "/prefix/api",
|
||||
expectedStatusCode: 200,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
router := NewInternalRouterAggregator(globalConfiguration, "traefik")
|
||||
|
||||
internalMuxRouter := mux.NewRouter()
|
||||
router.AddRoutes(internalMuxRouter)
|
||||
internalMuxRouter.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
})
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, test.testedURL, nil)
|
||||
internalMuxRouter.ServeHTTP(recorder, request)
|
||||
|
||||
assert.Equal(t, test.expectedStatusCode, recorder.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewInternalRouterAggregatorWithAuth(t *testing.T) {
|
||||
currentConfiguration := &safe.Safe{}
|
||||
currentConfiguration.Set(types.Configurations{})
|
||||
|
||||
globalConfiguration := configuration.GlobalConfiguration{
|
||||
API: &api.Handler{
|
||||
EntryPoint: "traefik",
|
||||
CurrentConfigurations: currentConfiguration,
|
||||
},
|
||||
Ping: &ping.Handler{
|
||||
EntryPoint: "traefik",
|
||||
},
|
||||
ACME: &acme.ACME{
|
||||
HTTPChallenge: &acmeprovider.HTTPChallenge{
|
||||
EntryPoint: "traefik",
|
||||
},
|
||||
},
|
||||
EntryPoints: configuration.EntryPoints{
|
||||
"traefik": &configuration.EntryPoint{
|
||||
Auth: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
Users: types.Users{"test:test"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
testedURL string
|
||||
expectedStatusCode int
|
||||
}{
|
||||
{
|
||||
desc: "Wrong url",
|
||||
testedURL: "/wrong",
|
||||
expectedStatusCode: 502,
|
||||
},
|
||||
{
|
||||
desc: "Ping without auth",
|
||||
testedURL: "/ping",
|
||||
expectedStatusCode: 200,
|
||||
},
|
||||
{
|
||||
desc: "acme without auth",
|
||||
testedURL: "/.well-known/acme-challenge/token",
|
||||
expectedStatusCode: 404,
|
||||
},
|
||||
{
|
||||
desc: "api with auth",
|
||||
testedURL: "/api",
|
||||
expectedStatusCode: 401,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
router := NewInternalRouterAggregator(globalConfiguration, "traefik")
|
||||
|
||||
internalMuxRouter := mux.NewRouter()
|
||||
router.AddRoutes(internalMuxRouter)
|
||||
internalMuxRouter.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
})
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, test.testedURL, nil)
|
||||
internalMuxRouter.ServeHTTP(recorder, request)
|
||||
|
||||
assert.Equal(t, test.expectedStatusCode, recorder.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewInternalRouterAggregatorWithAuthAndPrefix(t *testing.T) {
|
||||
currentConfiguration := &safe.Safe{}
|
||||
currentConfiguration.Set(types.Configurations{})
|
||||
|
||||
globalConfiguration := configuration.GlobalConfiguration{
|
||||
Web: &configuration.WebCompatibility{
|
||||
Path: "/prefix",
|
||||
},
|
||||
API: &api.Handler{
|
||||
EntryPoint: "traefik",
|
||||
CurrentConfigurations: currentConfiguration,
|
||||
},
|
||||
Ping: &ping.Handler{
|
||||
EntryPoint: "traefik",
|
||||
},
|
||||
ACME: &acme.ACME{
|
||||
HTTPChallenge: &acmeprovider.HTTPChallenge{
|
||||
EntryPoint: "traefik",
|
||||
},
|
||||
},
|
||||
EntryPoints: configuration.EntryPoints{
|
||||
"traefik": &configuration.EntryPoint{
|
||||
Auth: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
Users: types.Users{"test:test"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
testedURL string
|
||||
expectedStatusCode int
|
||||
}{
|
||||
{
|
||||
desc: "Ping without prefix",
|
||||
testedURL: "/ping",
|
||||
expectedStatusCode: 502,
|
||||
},
|
||||
{
|
||||
desc: "Ping without auth and with prefix",
|
||||
testedURL: "/prefix/ping",
|
||||
expectedStatusCode: 200,
|
||||
},
|
||||
{
|
||||
desc: "acme without auth and without prefix",
|
||||
testedURL: "/.well-known/acme-challenge/token",
|
||||
expectedStatusCode: 404,
|
||||
},
|
||||
{
|
||||
desc: "api with auth and prefix",
|
||||
testedURL: "/prefix/api",
|
||||
expectedStatusCode: 401,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
router := NewInternalRouterAggregator(globalConfiguration, "traefik")
|
||||
|
||||
internalMuxRouter := mux.NewRouter()
|
||||
router.AddRoutes(internalMuxRouter)
|
||||
internalMuxRouter.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
})
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, test.testedURL, nil)
|
||||
internalMuxRouter.ServeHTTP(recorder, request)
|
||||
|
||||
assert.Equal(t, test.expectedStatusCode, recorder.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type MockInternalRouterFunc func(systemRouter *mux.Router)
|
||||
|
||||
func (m MockInternalRouterFunc) AddRoutes(systemRouter *mux.Router) {
|
||||
m(systemRouter)
|
||||
}
|
||||
|
||||
func TestWithMiddleware(t *testing.T) {
|
||||
router := WithMiddleware{
|
||||
router: MockInternalRouterFunc(func(systemRouter *mux.Router) {
|
||||
systemRouter.Handle("/test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("router"))
|
||||
}))
|
||||
}),
|
||||
routerMiddlewares: []negroni.Handler{
|
||||
negroni.HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
rw.Write([]byte("before middleware1|"))
|
||||
next.ServeHTTP(rw, r)
|
||||
rw.Write([]byte("|after middleware1"))
|
||||
|
||||
}),
|
||||
negroni.HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
rw.Write([]byte("before middleware2|"))
|
||||
next.ServeHTTP(rw, r)
|
||||
rw.Write([]byte("|after middleware2"))
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
internalMuxRouter := mux.NewRouter()
|
||||
router.AddRoutes(internalMuxRouter)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||
internalMuxRouter.ServeHTTP(recorder, request)
|
||||
|
||||
obtained := recorder.Body.String()
|
||||
|
||||
assert.Equal(t, "before middleware1|before middleware2|router|after middleware2|after middleware1", obtained)
|
||||
}
|
||||
|
||||
func TestWithPrefix(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
prefix string
|
||||
testedURL string
|
||||
expectedStatusCode int
|
||||
}{
|
||||
{
|
||||
desc: "No prefix",
|
||||
testedURL: "/test",
|
||||
expectedStatusCode: 200,
|
||||
},
|
||||
{
|
||||
desc: "With prefix and wrong url",
|
||||
prefix: "/prefix",
|
||||
testedURL: "/test",
|
||||
expectedStatusCode: 404,
|
||||
},
|
||||
{
|
||||
desc: "With prefix",
|
||||
prefix: "/prefix",
|
||||
testedURL: "/prefix/test",
|
||||
expectedStatusCode: 200,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
router := WithPrefix{
|
||||
Router: MockInternalRouterFunc(func(systemRouter *mux.Router) {
|
||||
systemRouter.Handle("/test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
}),
|
||||
|
||||
PathPrefix: test.prefix,
|
||||
}
|
||||
internalMuxRouter := mux.NewRouter()
|
||||
router.AddRoutes(internalMuxRouter)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, test.testedURL, nil)
|
||||
internalMuxRouter.ServeHTTP(recorder, request)
|
||||
|
||||
assert.Equal(t, test.expectedStatusCode, recorder.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM alpine:3.9
|
||||
FROM alpine:3.7
|
||||
|
||||
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/bin
|
||||
|
||||
@@ -6,5 +6,5 @@ COPY requirements.txt /mkdocs/
|
||||
WORKDIR /mkdocs
|
||||
VOLUME /mkdocs
|
||||
|
||||
RUN apk --no-cache --no-progress add py3-pip \
|
||||
&& pip3 install --user -r requirements.txt
|
||||
RUN apk --no-cache --no-progress add py-pip \
|
||||
&& pip install --user -r requirements.txt
|
||||
@@ -1 +0,0 @@
|
||||
site/
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"no-hard-tabs": false,
|
||||
"MD007": { "indent": 4 },
|
||||
"MD009": false,
|
||||
"MD013": false,
|
||||
"MD026": false,
|
||||
"MD033": false,
|
||||
"MD034": false
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
|
||||
#######
|
||||
# This Makefile contains all targets related to the documentation
|
||||
#######
|
||||
|
||||
DOCS_VERIFY_SKIP ?= false
|
||||
DOCS_LINT_SKIP ?= false
|
||||
|
||||
TRAEFIK_DOCS_BUILD_IMAGE ?= traefik-docs
|
||||
TRAEFIK_DOCS_CHECK_IMAGE ?= $(TRAEFIK_DOCS_BUILD_IMAGE)-check
|
||||
|
||||
SITE_DIR := $(CURDIR)/site
|
||||
|
||||
DOCKER_RUN_DOC_PORT := 8000
|
||||
DOCKER_RUN_DOC_MOUNTS := -v $(CURDIR):/mkdocs
|
||||
DOCKER_RUN_DOC_OPTS := --rm $(DOCKER_RUN_DOC_MOUNTS) -p $(DOCKER_RUN_DOC_PORT):8000
|
||||
|
||||
# Default: generates the documentation into $(SITE_DIR)
|
||||
docs: docs-clean docs-image docs-lint docs-build docs-verify
|
||||
|
||||
# Writer Mode: build and serve docs on http://localhost:8000 with livereload
|
||||
docs-serve: docs-image
|
||||
docker run $(DOCKER_RUN_DOC_OPTS) $(TRAEFIK_DOCS_BUILD_IMAGE) mkdocs serve
|
||||
|
||||
# Utilities Targets for each step
|
||||
docs-image:
|
||||
docker build -t $(TRAEFIK_DOCS_BUILD_IMAGE) -f docs.Dockerfile ./
|
||||
|
||||
docs-build: docs-image
|
||||
docker run $(DOCKER_RUN_DOC_OPTS) $(TRAEFIK_DOCS_BUILD_IMAGE) sh -c "mkdocs build \
|
||||
&& chown -R $(shell id -u):$(shell id -g) ./site"
|
||||
|
||||
docs-verify: docs-build
|
||||
@if [ "$(DOCS_VERIFY_SKIP)" != "true" ]; then \
|
||||
docker build -t $(TRAEFIK_DOCS_CHECK_IMAGE) -f check.Dockerfile ./; \
|
||||
docker run --rm -v $(CURDIR):/app $(TRAEFIK_DOCS_CHECK_IMAGE) /verify.sh; \
|
||||
else \
|
||||
echo "DOCS_VERIFY_SKIP is true: no verification done."; \
|
||||
fi
|
||||
|
||||
docs-lint:
|
||||
@if [ "$(DOCS_LINT_SKIP)" != "true" ]; then \
|
||||
docker build -t $(TRAEFIK_DOCS_CHECK_IMAGE) -f check.Dockerfile ./ && \
|
||||
docker run --rm -v $(CURDIR):/app $(TRAEFIK_DOCS_CHECK_IMAGE) /lint.sh; \
|
||||
else \
|
||||
echo "DOCS_LINT_SKIP is true: no linting done."; \
|
||||
fi
|
||||
|
||||
docs-clean:
|
||||
rm -rf $(SITE_DIR)
|
||||
|
||||
.PHONY: all docs-verify docs docs-clean docs-build docs-lint
|
||||
772
docs/basics.md
Normal file
@@ -0,0 +1,772 @@
|
||||
# Basics
|
||||
|
||||
## Concepts
|
||||
|
||||
Let's take our example from the [overview](/#overview) again:
|
||||
|
||||
|
||||
> Imagine that you have deployed a bunch of microservices on your infrastructure. You probably used a service registry (like etcd or consul) and/or an orchestrator (swarm, Mesos/Marathon) to manage all these services.
|
||||
> If you want your users to access some of your microservices from the Internet, you will have to use a reverse proxy and configure it using virtual hosts or prefix paths:
|
||||
|
||||
> - domain `api.domain.com` will point the microservice `api` in your private network
|
||||
> - path `domain.com/web` will point the microservice `web` in your private network
|
||||
> - domain `backoffice.domain.com` will point the microservices `backoffice` in your private network, load-balancing between your multiple instances
|
||||
|
||||
> 
|
||||
|
||||
Let's zoom on Traefik and have an overview of its internal architecture:
|
||||
|
||||
|
||||

|
||||
|
||||
- Incoming requests end on [entrypoints](#entrypoints), as the name suggests, they are the network entry points into Traefik (listening port, SSL, traffic redirection...).
|
||||
- Traffic is then forwarded to a matching [frontend](#frontends). A frontend defines routes from [entrypoints](#entrypoints) to [backends](#backends).
|
||||
Routes are created using requests fields (`Host`, `Path`, `Headers`...) and can match or not a request.
|
||||
- The [frontend](#frontends) will then send the request to a [backend](#backends). A backend can be composed by one or more [servers](#servers), and by a load-balancing strategy.
|
||||
- Finally, the [server](#servers) will forward the request to the corresponding microservice in the private network.
|
||||
|
||||
### Entrypoints
|
||||
|
||||
Entrypoints are the network entry points into Traefik.
|
||||
They can be defined using:
|
||||
|
||||
- a port (80, 443...)
|
||||
- SSL (Certificates, Keys, authentication with a client certificate signed by a trusted CA...)
|
||||
- redirection to another entrypoint (redirect `HTTP` to `HTTPS`)
|
||||
|
||||
Here is an example of entrypoints definition:
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.redirect]
|
||||
entryPoint = "https"
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
certFile = "tests/traefik.crt"
|
||||
keyFile = "tests/traefik.key"
|
||||
```
|
||||
|
||||
- Two entrypoints are defined `http` and `https`.
|
||||
- `http` listens on port `80` and `https` on port `443`.
|
||||
- We enable SSL on `https` by giving a certificate and a key.
|
||||
- We also redirect all the traffic from entrypoint `http` to `https`.
|
||||
|
||||
And here is another example with client certificate authentication:
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
[entryPoints.https.tls.ClientCA]
|
||||
files = ["tests/clientca1.crt", "tests/clientca2.crt"]
|
||||
optional = false
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
certFile = "tests/traefik.crt"
|
||||
keyFile = "tests/traefik.key"
|
||||
```
|
||||
|
||||
- We enable SSL on `https` by giving a certificate and a key.
|
||||
- One or several files containing Certificate Authorities in PEM format are added.
|
||||
- It is possible to have multiple CA:s in the same file or keep them in separate files.
|
||||
|
||||
### Frontends
|
||||
|
||||
A frontend consists of a set of rules that determine how incoming requests are forwarded from an entrypoint to a backend.
|
||||
|
||||
Rules may be classified in one of two groups: Modifiers and matchers.
|
||||
|
||||
#### Modifiers
|
||||
|
||||
Modifier rules only modify the request. They do not have any impact on routing decisions being made.
|
||||
|
||||
Following is the list of existing modifier rules:
|
||||
|
||||
- `AddPrefix: /products`: Add path prefix to the existing request path prior to forwarding the request to the backend.
|
||||
- `ReplacePath: /serverless-path`: Replaces the path and adds the old path to the `X-Replaced-Path` header. Useful for mapping to AWS Lambda or Google Cloud Functions.
|
||||
- `ReplacePathRegex: ^/api/v2/(.*) /api/$1`: Replaces the path with a regular expression and adds the old path to the `X-Replaced-Path` header. Separate the regular expression and the replacement by a space.
|
||||
|
||||
#### Matchers
|
||||
|
||||
Matcher rules determine if a particular request should be forwarded to a backend.
|
||||
|
||||
The associativity rule is the following:
|
||||
|
||||
- `,` is the `OR` operator (works **only inside a matcher**, ex: `Host:foo.com,bar.com`).
|
||||
- i.e., forward a request if any rule matches.
|
||||
- Does not work for `Headers` and `HeadersRegexp`.
|
||||
- `;` is the `AND` operator (works **only between matchers**, ex: `Host:foo.com;Path:/bar`)
|
||||
- i.e., forward a request if all rules match
|
||||
|
||||
Following is the list of existing matcher rules along with examples:
|
||||
|
||||
| Matcher | Description |
|
||||
|------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `Headers: Content-Type, application/json` | Match HTTP header. It accepts a comma-separated key/value pair where both key and value must be literals. |
|
||||
| `HeadersRegexp: Content-Type, application/(text/json)` | Match HTTP header. It accepts a comma-separated key/value pair where the key must be a literal and the value may be a literal or a regular expression. |
|
||||
| `Host: traefik.io, www.traefik.io` | Match request host. It accepts a sequence of literal hosts. |
|
||||
| `HostRegexp: traefik.io, {subdomain:[a-z]+}.traefik.io` | Match request host. It accepts a sequence of literal and regular expression hosts. |
|
||||
| `Method: GET, POST, PUT` | Match request HTTP method. It accepts a sequence of HTTP methods. |
|
||||
| `Path: /products/, /articles/{category}/{id:[0-9]+}` | Match exact request path. It accepts a sequence of literal and regular expression paths. |
|
||||
| `PathStrip: /products/` | Match exact path and strip off the path prior to forwarding the request to the backend. It accepts a sequence of literal paths. |
|
||||
| `PathStripRegex: /articles/{category}/{id:[0-9]+}` | Match exact path and strip off the path prior to forwarding the request to the backend. It accepts a sequence of literal and regular expression paths. |
|
||||
| `PathPrefix: /products/, /articles/{category}/{id:[0-9]+}` | Match request prefix path. It accepts a sequence of literal and regular expression prefix paths. |
|
||||
| `PathPrefixStrip: /products/` | Match request prefix path and strip off the path prefix prior to forwarding the request to the backend. It accepts a sequence of literal prefix paths. Starting with Traefik 1.3, the stripped prefix path will be available in the `X-Forwarded-Prefix` header. |
|
||||
| `PathPrefixStripRegex: /articles/{category}/{id:[0-9]+}` | Match request prefix path and strip off the path prefix prior to forwarding the request to the backend. It accepts a sequence of literal and regular expression prefix paths. Starting with Traefik 1.3, the stripped prefix path will be available in the `X-Forwarded-Prefix` header. |
|
||||
| `Query: foo=bar, bar=baz` | Match Query String parameters. It accepts a sequence of key=value pairs. |
|
||||
|
||||
In order to use regular expressions with Host and Path matchers, you must declare an arbitrarily named variable followed by the colon-separated regular expression, all enclosed in curly braces. Any pattern supported by [Go's regexp package](https://golang.org/pkg/regexp/) may be used (example: `/posts/{id:[0-9]+}`).
|
||||
|
||||
!!! note
|
||||
The variable has no special meaning; however, it is required by the [gorilla/mux](https://github.com/gorilla/mux) dependency which embeds the regular expression and defines the syntax.
|
||||
|
||||
You can optionally enable `passHostHeader` to forward client `Host` header to the backend.
|
||||
You can also optionally configure the `passTLSClientCert` option to pass the Client certificates to the backend in a specific header.
|
||||
|
||||
##### Path Matcher Usage Guidelines
|
||||
|
||||
This section explains when to use the various path matchers.
|
||||
|
||||
Use `Path` if your backend listens on the exact path only. For instance, `Path: /products` would match `/products` but not `/products/shoes`.
|
||||
|
||||
Use a `*Prefix*` matcher if your backend listens on a particular base path but also serves requests on sub-paths.
|
||||
For instance, `PathPrefix: /products` would match `/products` but also `/products/shoes` and `/products/shirts`.
|
||||
Since the path is forwarded as-is, your backend is expected to listen on `/products`.
|
||||
|
||||
Use a `*Strip` matcher if your backend listens on the root path (`/`) but should be routeable on a specific prefix.
|
||||
For instance, `PathPrefixStrip: /products` would match `/products` but also `/products/shoes` and `/products/shirts`.
|
||||
Since the path is stripped prior to forwarding, your backend is expected to listen on `/`.
|
||||
If your backend is serving assets (e.g., images or Javascript files), chances are it must return properly constructed relative URLs.
|
||||
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 (available since Traefik 1.3) can be queried to build such URLs dynamically.
|
||||
|
||||
Instead of distinguishing your backends by path only, you can add a Host matcher to the mix.
|
||||
That way, namespacing of your backends happens on the basis of hosts in addition to paths.
|
||||
|
||||
#### Examples
|
||||
|
||||
Here is an example of frontends definition:
|
||||
|
||||
```toml
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend2"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "Host:test.localhost,test2.localhost"
|
||||
[frontends.frontend2]
|
||||
backend = "backend1"
|
||||
passHostHeader = true
|
||||
[frontends.frontend2.passTLSClientCert]
|
||||
pem = true
|
||||
priority = 10
|
||||
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||
[frontends.frontend2.routes.test_1]
|
||||
rule = "HostRegexp:localhost,{subdomain:[a-z]+}.localhost"
|
||||
[frontends.frontend3]
|
||||
backend = "backend2"
|
||||
[frontends.frontend3.routes.test_1]
|
||||
rule = "Host:test3.localhost;Path:/test"
|
||||
```
|
||||
|
||||
- Three frontends are defined: `frontend1`, `frontend2` and `frontend3`
|
||||
- `frontend1` will forward the traffic to the `backend2` if the rule `Host:test.localhost,test2.localhost` is matched
|
||||
- `frontend2` will forward the traffic to the `backend1` if the rule `HostRegexp:localhost,{subdomain:[a-z]+}.localhost` is matched (forwarding client `Host` header to the backend)
|
||||
- `frontend3` will forward the traffic to the `backend2` if the rules `Host:test3.localhost` **AND** `Path:/test` are matched
|
||||
|
||||
#### Combining multiple rules
|
||||
|
||||
As seen in the previous example, you can combine multiple rules.
|
||||
In TOML file, you can use multiple routes:
|
||||
|
||||
```toml
|
||||
[frontends.frontend3]
|
||||
backend = "backend2"
|
||||
[frontends.frontend3.routes.test_1]
|
||||
rule = "Host:test3.localhost"
|
||||
[frontends.frontend3.routes.test_2]
|
||||
rule = "Path:/test"
|
||||
```
|
||||
|
||||
Here `frontend3` will forward the traffic to the `backend2` if the rules `Host:test3.localhost` **AND** `Path:/test` are matched.
|
||||
|
||||
You can also use the notation using a `;` separator, same result:
|
||||
|
||||
```toml
|
||||
[frontends.frontend3]
|
||||
backend = "backend2"
|
||||
[frontends.frontend3.routes.test_1]
|
||||
rule = "Host:test3.localhost;Path:/test"
|
||||
```
|
||||
|
||||
Finally, you can create a rule to bind multiple domains or Path to a frontend, using the `,` separator:
|
||||
|
||||
```toml
|
||||
[frontends.frontend2]
|
||||
[frontends.frontend2.routes.test_1]
|
||||
rule = "Host:test1.localhost,test2.localhost"
|
||||
[frontends.frontend3]
|
||||
backend = "backend2"
|
||||
[frontends.frontend3.routes.test_1]
|
||||
rule = "Path:/test1,/test2"
|
||||
```
|
||||
|
||||
#### Rules Order
|
||||
|
||||
When combining `Modifier` rules with `Matcher` rules, it is important to remember that `Modifier` rules **ALWAYS** apply after the `Matcher` rules.
|
||||
|
||||
The following rules are both `Matchers` and `Modifiers`, so the `Matcher` portion of the rule will apply first, and the `Modifier` will apply later.
|
||||
|
||||
- `PathStrip`
|
||||
- `PathStripRegex`
|
||||
- `PathPrefixStrip`
|
||||
- `PathPrefixStripRegex`
|
||||
|
||||
`Modifiers` will be applied in a pre-determined order regardless of their order in the `rule` configuration section.
|
||||
|
||||
1. `PathStrip`
|
||||
2. `PathPrefixStrip`
|
||||
3. `PathStripRegex`
|
||||
4. `PathPrefixStripRegex`
|
||||
5. `AddPrefix`
|
||||
6. `ReplacePath`
|
||||
|
||||
#### Priorities
|
||||
|
||||
By default, routes will be sorted (in descending order) using rules length (to avoid path overlap):
|
||||
- `PathPrefix:/foo;Host:foo.com` (length == 28) will be matched before `PathPrefixStrip:/foobar` (length == 23) will be matched before `PathPrefix:/foo,/bar` (length == 20).
|
||||
- A priority value of 0 will be ignored, so the default value will be calculated (rules length).
|
||||
|
||||
You can customize priority by frontend. The priority value override the rule length during sorting:
|
||||
|
||||
```toml
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
priority = 20
|
||||
passHostHeader = true
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "PathPrefix:/to"
|
||||
[frontends.frontend2]
|
||||
backend = "backend2"
|
||||
passHostHeader = true
|
||||
[frontends.frontend2.routes.test_1]
|
||||
rule = "PathPrefix:/toto"
|
||||
```
|
||||
|
||||
Here, `frontend1` will be matched before `frontend2` (`20 > 16`).
|
||||
|
||||
#### Custom headers
|
||||
|
||||
Custom headers can be configured through the frontends, to add headers to either requests or responses that match the frontend's rules.
|
||||
This allows for setting headers such as `X-Script-Name` to be added to the request, or custom headers to be added to the response.
|
||||
|
||||
!!! warning
|
||||
If the custom header name is the same as one header name of the request or response, it will be replaced.
|
||||
|
||||
In this example, all matches to the path `/cheese` will have the `X-Script-Name` header added to the proxied request and the `X-Custom-Response-Header` header added to the response.
|
||||
|
||||
```toml
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
[frontends.frontend1.headers.customresponseheaders]
|
||||
X-Custom-Response-Header = "True"
|
||||
[frontends.frontend1.headers.customrequestheaders]
|
||||
X-Script-Name = "test"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "PathPrefixStrip:/cheese"
|
||||
```
|
||||
|
||||
In this second example, all matches to the path `/cheese` will have the `X-Script-Name` header added to the proxied request, the `X-Custom-Request-Header` header removed from the request, and the `X-Custom-Response-Header` header removed from the response.
|
||||
|
||||
```toml
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
[frontends.frontend1.headers.customresponseheaders]
|
||||
X-Custom-Response-Header = ""
|
||||
[frontends.frontend1.headers.customrequestheaders]
|
||||
X-Script-Name = "test"
|
||||
X-Custom-Request-Header = ""
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "PathPrefixStrip:/cheese"
|
||||
```
|
||||
|
||||
#### Security headers
|
||||
|
||||
Security related headers (HSTS headers, SSL redirection, Browser XSS filter, etc) can be added and configured per frontend in a similar manner to the custom headers above.
|
||||
This functionality allows for some easy security features to quickly be set.
|
||||
|
||||
An example of some of the security headers:
|
||||
|
||||
```toml
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
[frontends.frontend1.headers]
|
||||
FrameDeny = true
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "PathPrefixStrip:/cheddar"
|
||||
[frontends.frontend2]
|
||||
backend = "backend2"
|
||||
[frontends.frontend2.headers]
|
||||
SSLRedirect = true
|
||||
[frontends.frontend2.routes.test_1]
|
||||
rule = "PathPrefixStrip:/stilton"
|
||||
```
|
||||
|
||||
In this example, traffic routed through the first frontend will have the `X-Frame-Options` header set to `DENY`, and the second will only allow HTTPS request through, otherwise will return a 301 HTTPS redirect.
|
||||
|
||||
!!! note
|
||||
The detailed documentation for those security headers can be found in [unrolled/secure](https://github.com/unrolled/secure#available-options).
|
||||
|
||||
### Backends
|
||||
|
||||
A backend is responsible to load-balance the traffic coming from one or more frontends to a set of http servers.
|
||||
|
||||
#### Servers
|
||||
|
||||
Servers are simply defined using a `url`. You can also apply a custom `weight` to each server (this will be used by load-balancing).
|
||||
|
||||
!!! note
|
||||
Paths in `url` are ignored. Use `Modifier` to specify paths instead.
|
||||
|
||||
Here is an example of backends and servers definition:
|
||||
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
# ...
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://172.17.0.2:80"
|
||||
weight = 10
|
||||
[backends.backend1.servers.server2]
|
||||
url = "http://172.17.0.3:80"
|
||||
weight = 1
|
||||
[backends.backend2]
|
||||
# ...
|
||||
[backends.backend2.servers.server1]
|
||||
url = "https://172.17.0.4:443"
|
||||
weight = 1
|
||||
[backends.backend2.servers.server2]
|
||||
url = "https://172.17.0.5:443"
|
||||
weight = 2
|
||||
[backends.backend3]
|
||||
# ...
|
||||
[backends.backend3.servers.server1]
|
||||
url = "h2c://172.17.0.6:80"
|
||||
weight = 1
|
||||
```
|
||||
|
||||
- Two backends are defined: `backend1` and `backend2`
|
||||
- `backend1` will forward the traffic to two servers: `172.17.0.2:80` with weight `10` and `172.17.0.3:80` with weight `1`.
|
||||
- `backend2` will forward the traffic to two servers: `172.17.0.4:443` with weight `1` and `172.17.0.5:443` with weight `2` both using TLS.
|
||||
- `backend3` will forward the traffic to: `172.17.0.6:80` with weight `1` using HTTP2 without TLS.
|
||||
|
||||
#### Load-balancing
|
||||
|
||||
Various methods of load-balancing are supported:
|
||||
|
||||
- `wrr`: Weighted Round Robin.
|
||||
- `drr`: Dynamic Round Robin: increases weights on servers that perform better than others.
|
||||
It also rolls back to original weights if the servers have changed.
|
||||
|
||||
#### Circuit breakers
|
||||
|
||||
A circuit breaker can also be applied to a backend, preventing high loads on failing servers.
|
||||
Initial state is Standby. CB observes the statistics and does not modify the request.
|
||||
In case the condition matches, CB enters Tripped state, where it responds with predefined code or redirects to another frontend.
|
||||
Once Tripped timer expires, CB enters Recovering state and resets all stats.
|
||||
In case the condition does not match and recovery timer expires, CB enters Standby state.
|
||||
|
||||
It can be configured using:
|
||||
|
||||
- Methods: `LatencyAtQuantileMS`, `NetworkErrorRatio`, `ResponseCodeRatio`
|
||||
- Operators: `AND`, `OR`, `EQ`, `NEQ`, `LT`, `LE`, `GT`, `GE`
|
||||
|
||||
For example:
|
||||
|
||||
- `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window for a frontend.
|
||||
- `LatencyAtQuantileMS(50.0) > 50`: watch latency at quantile in milliseconds.
|
||||
- `ResponseCodeRatio(500, 600, 0, 600) > 0.5`: ratio of response codes in ranges [500-600) and [0-600).
|
||||
|
||||
Here is an example of backends and servers definition:
|
||||
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.circuitbreaker]
|
||||
expression = "NetworkErrorRatio() > 0.5"
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://172.17.0.2:80"
|
||||
weight = 10
|
||||
[backends.backend1.servers.server2]
|
||||
url = "http://172.17.0.3:80"
|
||||
weight = 1
|
||||
```
|
||||
|
||||
- `backend1` will forward the traffic to two servers: `http://172.17.0.2:80"` with weight `10` and `http://172.17.0.3:80` with weight `1` using default `wrr` load-balancing strategy.
|
||||
- a circuit breaker is added on `backend1` using the expression `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window
|
||||
|
||||
#### Maximum connections
|
||||
|
||||
To proactively prevent backends from being overwhelmed with high load, a maximum connection limit can also be applied to each backend.
|
||||
|
||||
Maximum connections can be configured by specifying an integer value for `maxconn.amount` and `maxconn.extractorfunc` which is a strategy used to determine how to categorize requests in order to evaluate the maximum connections.
|
||||
|
||||
For example:
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.maxconn]
|
||||
amount = 10
|
||||
extractorfunc = "request.host"
|
||||
# ...
|
||||
```
|
||||
|
||||
- `backend1` will return `HTTP code 429 Too Many Requests` if there are already 10 requests in progress for the same Host header.
|
||||
- Another possible value for `extractorfunc` is `client.ip` which will categorize requests based on client source ip.
|
||||
- Lastly `extractorfunc` can take the value of `request.header.ANY_HEADER` which will categorize requests based on `ANY_HEADER` that you provide.
|
||||
|
||||
#### Sticky sessions
|
||||
|
||||
Sticky sessions are supported with both load balancers.
|
||||
When sticky sessions are enabled, a cookie is set on the initial request.
|
||||
The default cookie name is an abbreviation of a sha1 (ex: `_1d52e`).
|
||||
On subsequent requests, the client will be directed to the backend stored in the cookie if it is still healthy.
|
||||
If not, a new backend will be assigned.
|
||||
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
# Enable sticky session
|
||||
[backends.backend1.loadbalancer.stickiness]
|
||||
|
||||
# Customize the cookie name
|
||||
#
|
||||
# Optional
|
||||
# Default: a sha1 (6 chars)
|
||||
#
|
||||
# cookieName = "my_cookie"
|
||||
```
|
||||
|
||||
The deprecated way:
|
||||
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.loadbalancer]
|
||||
sticky = true
|
||||
```
|
||||
|
||||
#### Health Check
|
||||
|
||||
A health check can be configured in order to remove a backend from LB rotation as long as it keeps returning HTTP status codes other than `2xx` or `3xx` to HTTP GET requests periodically carried out by Traefik.
|
||||
The check is defined by a path appended to the backend URL and an interval (given in a format understood by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration)) specifying how often the health check should be executed (the default being 30 seconds).
|
||||
Each backend must respond to the health check within 5 seconds.
|
||||
By default, the port of the backend server is used, however, this may be overridden.
|
||||
|
||||
A recovering backend returning `2xx` or `3xx` responses again is being returned to the LB rotation pool.
|
||||
|
||||
For example:
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.healthcheck]
|
||||
path = "/health"
|
||||
interval = "10s"
|
||||
```
|
||||
|
||||
To use a different port for the health check:
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.healthcheck]
|
||||
path = "/health"
|
||||
interval = "10s"
|
||||
port = 8080
|
||||
```
|
||||
|
||||
|
||||
To use a different scheme for the health check:
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.healthcheck]
|
||||
path = "/health"
|
||||
interval = "10s"
|
||||
scheme = "http"
|
||||
```
|
||||
|
||||
Additional http headers and hostname to health check request can be specified, for instance:
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.healthcheck]
|
||||
path = "/health"
|
||||
interval = "10s"
|
||||
hostname = "myhost.com"
|
||||
port = 8080
|
||||
[backends.backend1.healthcheck.headers]
|
||||
My-Custom-Header = "foo"
|
||||
My-Header = "bar"
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Traefik's configuration has two parts:
|
||||
|
||||
- The [static Traefik configuration](/basics#static-traefik-configuration) which is loaded only at the beginning.
|
||||
- The [dynamic Traefik configuration](/basics#dynamic-traefik-configuration) which can be hot-reloaded (no need to restart the process).
|
||||
|
||||
### Static Traefik configuration
|
||||
|
||||
The static configuration is the global configuration which is setting up connections to configuration backends and entrypoints.
|
||||
|
||||
Traefik can be configured using many configuration sources with the following precedence order.
|
||||
Each item takes precedence over the item below it:
|
||||
|
||||
- [Key-value store](/basics/#key-value-stores)
|
||||
- [Arguments](/basics/#arguments)
|
||||
- [Configuration file](/basics/#configuration-file)
|
||||
- Default
|
||||
|
||||
It means that arguments override configuration file, and key-value store overrides arguments.
|
||||
|
||||
!!! note
|
||||
the provider-enabling argument parameters (e.g., `--docker`) set all default values for the specific provider.
|
||||
It must not be used if a configuration source with less precedence wants to set a non-default provider value.
|
||||
|
||||
#### Configuration file
|
||||
|
||||
By default, Traefik will try to find a `traefik.toml` in the following places:
|
||||
|
||||
- `/etc/traefik/`
|
||||
- `$HOME/.traefik/`
|
||||
- `.` _the working directory_
|
||||
|
||||
You can override this by setting a `configFile` argument:
|
||||
|
||||
```bash
|
||||
traefik --configFile=foo/bar/myconfigfile.toml
|
||||
```
|
||||
|
||||
Please refer to the [global configuration](/configuration/commons) section to get documentation on it.
|
||||
|
||||
#### Arguments
|
||||
|
||||
Each argument (and command) is described in the help section:
|
||||
|
||||
```bash
|
||||
traefik --help
|
||||
```
|
||||
|
||||
Note that all default values will be displayed as well.
|
||||
|
||||
#### Key-value stores
|
||||
|
||||
Traefik supports several Key-value stores:
|
||||
|
||||
- [Consul](https://consul.io)
|
||||
- [etcd](https://coreos.com/etcd/)
|
||||
- [ZooKeeper](https://zookeeper.apache.org/)
|
||||
- [boltdb](https://github.com/boltdb/bolt)
|
||||
|
||||
Please refer to the [User Guide Key-value store configuration](/user-guide/kv-config/) section to get documentation on it.
|
||||
|
||||
### Dynamic Traefik configuration
|
||||
|
||||
The dynamic configuration concerns :
|
||||
|
||||
- [Frontends](/basics/#frontends)
|
||||
- [Backends](/basics/#backends)
|
||||
- [Servers](/basics/#servers)
|
||||
- HTTPS Certificates
|
||||
|
||||
Traefik can hot-reload those rules which could be provided by [multiple configuration backends](/configuration/commons).
|
||||
|
||||
We only need to enable `watch` option to make Traefik watch configuration backend changes and generate its configuration automatically.
|
||||
Routes to services will be created and updated instantly at any changes.
|
||||
|
||||
Please refer to the [configuration backends](/configuration/commons) section to get documentation on it.
|
||||
|
||||
## Commands
|
||||
|
||||
### traefik
|
||||
|
||||
Usage:
|
||||
```bash
|
||||
traefik [command] [--flag=flag_argument]
|
||||
```
|
||||
|
||||
List of Traefik available commands with description :
|
||||
|
||||
- `version` : Print version
|
||||
- `storeconfig` : Store the static Traefik configuration into a Key-value stores. Please refer to the [Store Traefik configuration](/user-guide/kv-config/#store-configuration-in-key-value-store) section to get documentation on it.
|
||||
- `bug`: The easiest way to submit a pre-filled issue.
|
||||
- `healthcheck`: Calls Traefik `/ping` to check health.
|
||||
|
||||
Each command may have related flags.
|
||||
|
||||
All those related flags will be displayed with :
|
||||
|
||||
```bash
|
||||
traefik [command] --help
|
||||
```
|
||||
|
||||
Each command is described at the beginning of the help section:
|
||||
|
||||
```bash
|
||||
traefik --help
|
||||
|
||||
# or
|
||||
|
||||
docker run traefik[:version] --help
|
||||
# ex: docker run traefik:1.5 --help
|
||||
```
|
||||
|
||||
### Command: bug
|
||||
|
||||
Here is the easiest way to submit a pre-filled issue on [Traefik GitHub](https://github.com/containous/traefik).
|
||||
|
||||
```bash
|
||||
traefik bug
|
||||
```
|
||||
|
||||
Watch [this demo](https://www.youtube.com/watch?v=Lyz62L8m93I).
|
||||
|
||||
### Command: healthcheck
|
||||
|
||||
This command allows to check the health of Traefik. Its exit status is `0` if Traefik is healthy and `1` if it is unhealthy.
|
||||
|
||||
This can be used with Docker [HEALTHCHECK](https://docs.docker.com/engine/reference/builder/#healthcheck) instruction or any other health check orchestration mechanism.
|
||||
|
||||
!!! note
|
||||
The [`ping`](/configuration/ping) must be enabled to allow the `healthcheck` command to call `/ping`.
|
||||
|
||||
```bash
|
||||
traefik healthcheck
|
||||
```
|
||||
```bash
|
||||
OK: http://:8082/ping
|
||||
```
|
||||
|
||||
|
||||
## Collected Data
|
||||
|
||||
**This feature is disabled by default.**
|
||||
|
||||
You can read the public proposal on this topic [here](https://github.com/containous/traefik/issues/2369).
|
||||
|
||||
### Why ?
|
||||
|
||||
In order to help us learn more about how Traefik is being used and improve it, we collect anonymous usage statistics from running instances.
|
||||
Those data help us prioritize our developments and focus on what's more important (for example, which configuration backend is used and which is not used).
|
||||
|
||||
### What ?
|
||||
|
||||
Once a day (the first call begins 10 minutes after the start of Traefik), we collect:
|
||||
|
||||
- the Traefik version
|
||||
- a hash of the configuration
|
||||
- an **anonymous version** of the static configuration:
|
||||
- token, user name, password, URL, IP, domain, email, etc, are removed
|
||||
|
||||
!!! note
|
||||
We do not collect the dynamic configuration (frontends & backends).
|
||||
|
||||
!!! note
|
||||
We do not collect data behind the scenes to run advertising programs or to sell such data to third-party.
|
||||
|
||||
#### Here is an example
|
||||
|
||||
- Source configuration:
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
[api]
|
||||
|
||||
[Docker]
|
||||
endpoint = "tcp://10.10.10.10:2375"
|
||||
domain = "foo.bir"
|
||||
exposedByDefault = true
|
||||
swarmMode = true
|
||||
|
||||
[Docker.TLS]
|
||||
ca = "dockerCA"
|
||||
cert = "dockerCert"
|
||||
key = "dockerKey"
|
||||
insecureSkipVerify = true
|
||||
|
||||
[ECS]
|
||||
domain = "foo.bar"
|
||||
exposedByDefault = true
|
||||
clusters = ["foo-bar"]
|
||||
region = "us-west-2"
|
||||
accessKeyID = "AccessKeyID"
|
||||
secretAccessKey = "SecretAccessKey"
|
||||
```
|
||||
|
||||
- Obfuscated and anonymous configuration:
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
[api]
|
||||
|
||||
[Docker]
|
||||
endpoint = "xxxx"
|
||||
domain = "xxxx"
|
||||
exposedByDefault = true
|
||||
swarmMode = true
|
||||
|
||||
[Docker.TLS]
|
||||
ca = "xxxx"
|
||||
cert = "xxxx"
|
||||
key = "xxxx"
|
||||
insecureSkipVerify = false
|
||||
|
||||
[ECS]
|
||||
domain = "xxxx"
|
||||
exposedByDefault = true
|
||||
clusters = []
|
||||
region = "us-west-2"
|
||||
accessKeyID = "xxxx"
|
||||
secretAccessKey = "xxxx"
|
||||
```
|
||||
|
||||
### Show me the code !
|
||||
|
||||
If you want to dig into more details, here is the source code of the collecting system: [collector.go](https://github.com/containous/traefik/blob/v1.7/collector/collector.go)
|
||||
|
||||
By default we anonymize all configuration fields, except fields tagged with `export=true`.
|
||||
|
||||
### How to enable this ?
|
||||
|
||||
You can enable the collecting system by:
|
||||
|
||||
- adding this line in the configuration TOML file:
|
||||
|
||||
```toml
|
||||
# Send anonymous usage data
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
sendAnonymousUsage = true
|
||||
```
|
||||
|
||||
- adding this flag in the CLI:
|
||||
|
||||
```bash
|
||||
./traefik --sendAnonymousUsage=true
|
||||
```
|
||||
@@ -1,43 +0,0 @@
|
||||
|
||||
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 \
|
||||
libcurl \
|
||||
ruby \
|
||||
ruby-bigdecimal \
|
||||
ruby-etc \
|
||||
ruby-ffi \
|
||||
ruby-json \
|
||||
&& apk add --no-cache --virtual build-dependencies \
|
||||
build-base \
|
||||
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!
|
||||
RUN apk --no-cache --no-progress add \
|
||||
git \
|
||||
nodejs \
|
||||
npm \
|
||||
&& npm install markdownlint@0.12.0 markdownlint-cli@0.13.0 --global
|
||||
|
||||
# Finally the shell tools we need for later
|
||||
# tini helps to terminate properly all the parallelized tasks when sending CTRL-C
|
||||
RUN apk --no-cache --no-progress add \
|
||||
ca-certificates \
|
||||
curl \
|
||||
tini
|
||||
|
||||
COPY ./scripts/verify.sh /verify.sh
|
||||
COPY ./scripts/lint.sh /lint.sh
|
||||
|
||||
WORKDIR /app
|
||||
VOLUME ["/tmp","/app"]
|
||||
|
||||
ENTRYPOINT ["/sbin/tini","-g","sh"]
|
||||
@@ -1,116 +1,280 @@
|
||||
# ACME
|
||||
# ACME (Let's Encrypt) Configuration
|
||||
|
||||
Automatic HTTPS
|
||||
{: .subtitle }
|
||||
See [Let's Encrypt examples](/user-guide/examples/#lets-encrypt-support) and [Docker & Let's Encrypt user guide](/user-guide/docker-and-lets-encrypt) as well.
|
||||
|
||||
You can configure Traefik to use an ACME provider (like Let's Encrypt) for automatic certificate generation.
|
||||
## Configuration
|
||||
|
||||
!!! warning "Let's Encrypt and Rate Limiting"
|
||||
Note that Let's Encrypt API has [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
||||
```toml
|
||||
# Sample entrypoint configuration when using ACME.
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
```
|
||||
|
||||
## Configuration Examples
|
||||
```toml
|
||||
# Enable ACME (Let's Encrypt): automatic SSL.
|
||||
[acme]
|
||||
|
||||
??? example "Enabling ACME"
|
||||
# Email address used for registration.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
email = "test@traefik.io"
|
||||
|
||||
```toml
|
||||
[entrypoints]
|
||||
[entrypoints.web]
|
||||
address = ":80"
|
||||
|
||||
[entrypoints.http-tls]
|
||||
address = ":443"
|
||||
|
||||
[acme] # every router with TLS enabled will now be able to use ACME for its certificates
|
||||
email = "your-email@your-domain.org"
|
||||
storage = "acme.json"
|
||||
onHostRule = true # dynamic generation based on the Host() & HostSNI() matchers
|
||||
[acme.httpChallenge]
|
||||
entryPoint = "web" # used during the challenge
|
||||
```
|
||||
# File used for certificates storage.
|
||||
#
|
||||
# Optional (Deprecated)
|
||||
#
|
||||
#storageFile = "acme.json"
|
||||
|
||||
??? example "Configuring Wildcard Certificates"
|
||||
# File or key used for certificates storage.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
storage = "acme.json"
|
||||
# or `storage = "traefik/acme/account"` if using KV store.
|
||||
|
||||
```toml
|
||||
[entrypoints]
|
||||
[entrypoints.web]
|
||||
address = ":80"
|
||||
# Entrypoint to proxy acme apply certificates to.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
entryPoint = "https"
|
||||
|
||||
[entrypoints.http-tls]
|
||||
address = ":443"
|
||||
# Deprecated, replaced by [acme.dnsChallenge].
|
||||
#
|
||||
# Optional.
|
||||
#
|
||||
# dnsProvider = "digitalocean"
|
||||
|
||||
[acme]
|
||||
email = "your-email@your-domain.org"
|
||||
storage = "acme.json"
|
||||
[acme.dnsChallenge]
|
||||
provider = "xxx"
|
||||
# Deprecated, replaced by [acme.dnsChallenge.delayBeforeCheck].
|
||||
#
|
||||
# Optional
|
||||
# Default: 0
|
||||
#
|
||||
# delayDontCheckDNS = 0
|
||||
|
||||
[[acme.domains]]
|
||||
main = "*.mydomain.com"
|
||||
sans = ["mydomain.com"]
|
||||
```
|
||||
# If true, display debug log messages from the acme client library.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# acmeLogging = true
|
||||
|
||||
!!! note "Configuration Reference"
|
||||
# If true, override certificates in key-value store when using storeconfig.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# overrideCertificates = true
|
||||
|
||||
There are many available options for ACME. For a quick glance at what's possible, browse the [configuration reference](../reference/acme.md).
|
||||
# Deprecated. Enable on demand certificate generation.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# onDemand = true
|
||||
|
||||
## The Different ACME Challenges
|
||||
# Enable certificate generation on frontends host rules.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# onHostRule = true
|
||||
|
||||
### `tlsChallenge`
|
||||
# CA server to use.
|
||||
# Uncomment the line to use Let's Encrypt's staging server,
|
||||
# leave commented to go to prod.
|
||||
#
|
||||
# Optional
|
||||
# Default: "https://acme-v02.api.letsencrypt.org/directory"
|
||||
#
|
||||
# caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
|
||||
# KeyType to use.
|
||||
#
|
||||
# Optional
|
||||
# Default: "RSA4096"
|
||||
#
|
||||
# Available values : "EC256", "EC384", "RSA2048", "RSA4096", "RSA8192"
|
||||
#
|
||||
# KeyType = "RSA4096"
|
||||
|
||||
# Use a TLS-ALPN-01 ACME challenge.
|
||||
#
|
||||
# Optional (but recommended)
|
||||
#
|
||||
[acme.tlsChallenge]
|
||||
|
||||
# Use a HTTP-01 ACME challenge.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [acme.httpChallenge]
|
||||
|
||||
# EntryPoint to use for the HTTP-01 challenges.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
# entryPoint = "http"
|
||||
|
||||
# Use a DNS-01 ACME challenge rather than HTTP-01 challenge.
|
||||
# Note: mandatory for wildcard certificate generation.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [acme.dnsChallenge]
|
||||
|
||||
# DNS provider used.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
# provider = "digitalocean"
|
||||
|
||||
# By default, the provider will verify the TXT DNS challenge record before letting ACME verify.
|
||||
# If delayBeforeCheck is greater than zero, this check is delayed for the configured duration in seconds.
|
||||
# Useful if internal networks block external DNS queries.
|
||||
#
|
||||
# Optional
|
||||
# Default: 0
|
||||
#
|
||||
# delayBeforeCheck = 0
|
||||
|
||||
# Use following DNS servers to resolve the FQDN authority.
|
||||
#
|
||||
# Optional
|
||||
# Default: empty
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# NOT RECOMMENDED:
|
||||
# Increase the risk of reaching Let's Encrypt's rate limits.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# disablePropagationCheck = true
|
||||
|
||||
# Domains list.
|
||||
# Only domains defined here can generate wildcard certificates.
|
||||
# The certificates for these domains are negotiated at traefik startup only.
|
||||
#
|
||||
# [[acme.domains]]
|
||||
# main = "local1.com"
|
||||
# sans = ["test1.local1.com", "test2.local1.com"]
|
||||
# [[acme.domains]]
|
||||
# main = "local2.com"
|
||||
# [[acme.domains]]
|
||||
# main = "*.local3.com"
|
||||
# sans = ["local3.com", "test1.test1.local3.com"]
|
||||
```
|
||||
|
||||
### `caServer`
|
||||
|
||||
The CA server to use.
|
||||
|
||||
This example shows the usage of Let's Encrypt's staging server:
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
# ...
|
||||
```
|
||||
|
||||
### ACME Challenge
|
||||
|
||||
#### `tlsChallenge`
|
||||
|
||||
Use the `TLS-ALPN-01` challenge to generate and renew ACME certificates by provisioning a TLS certificate.
|
||||
|
||||
As described on the Let's Encrypt [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72),
|
||||
when using the `TLS-ALPN-01` challenge, Traefik must be reachable by Let's Encrypt through port 443.
|
||||
|
||||
??? example "Configuring the `tlsChallenge`"
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
[acme.tlsChallenge]
|
||||
```
|
||||
|
||||
### `httpChallenge`
|
||||
|
||||
Use the `HTTP-01` challenge to generate and renew ACME certificates by provisioning an HTTP resource under a well-known URI.
|
||||
|
||||
As described on the Let's Encrypt [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72),
|
||||
when using the `HTTP-01` challenge, `acme.httpChallenge.entryPoint` must be reachable by Let's Encrypt through port 80.
|
||||
|
||||
??? example "Using an EntryPoint Called http for the `httpChallenge`"
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
[acme.httpChallenge]
|
||||
entryPoint = "http"
|
||||
```
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
entryPoint = "https"
|
||||
[acme.tlsChallenge]
|
||||
```
|
||||
|
||||
!!! note
|
||||
Redirection is fully compatible with the `HTTP-01` challenge.
|
||||
If the `TLS-ALPN-01` challenge is used, `acme.entryPoint` has to be reachable by Let's Encrypt through port 443.
|
||||
This is a Let's Encrypt limitation as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
|
||||
|
||||
### `dnsChallenge`
|
||||
#### `httpChallenge`
|
||||
|
||||
Use the `HTTP-01` challenge to generate and renew ACME certificates by provisioning a HTTP resource under a well-known URI.
|
||||
|
||||
Redirection is fully compatible with the `HTTP-01` challenge.
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
entryPoint = "https"
|
||||
[acme.httpChallenge]
|
||||
entryPoint = "http"
|
||||
```
|
||||
|
||||
!!! note
|
||||
If the `HTTP-01` challenge is used, `acme.httpChallenge.entryPoint` has to be defined and reachable by Let's Encrypt through port 80.
|
||||
This is a Let's Encrypt limitation as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
|
||||
|
||||
##### `entryPoint`
|
||||
|
||||
Specify the entryPoint to use during the challenges.
|
||||
|
||||
```toml
|
||||
defaultEntryPoints = ["http", "https"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
# ...
|
||||
|
||||
[acme]
|
||||
# ...
|
||||
entryPoint = "https"
|
||||
[acme.httpChallenge]
|
||||
entryPoint = "http"
|
||||
```
|
||||
|
||||
!!! note
|
||||
`acme.httpChallenge.entryPoint` has to be reachable through port 80. It's a Let's Encrypt limitation as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
|
||||
|
||||
#### `dnsChallenge`
|
||||
|
||||
Use the `DNS-01` challenge to generate and renew ACME certificates by provisioning a DNS record.
|
||||
|
||||
??? example "Configuring a `dnsChallenge` with the DigitalOcean Provider"
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
[acme.dnsChallenge]
|
||||
provider = "digitalocean"
|
||||
delayBeforeCheck = 0
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
[acme.dnsChallenge]
|
||||
provider = "digitalocean"
|
||||
delayBeforeCheck = 0
|
||||
# ...
|
||||
```
|
||||
##### `delayBeforeCheck`
|
||||
|
||||
!!! important
|
||||
A `provider` is mandatory.
|
||||
By default, the `provider` will verify the TXT DNS challenge record before letting ACME verify.
|
||||
If `delayBeforeCheck` is greater than zero, this check is delayed for the configured duration in seconds.
|
||||
|
||||
#### `providers`
|
||||
|
||||
Here is a list of supported `providers`, that can automate the DNS verification,
|
||||
along with the required environment variables and their [wildcard & root domain support](#wildcard-domains).
|
||||
Do not hesitate to complete it.
|
||||
Useful if internal networks block external DNS queries.
|
||||
|
||||
!!! note
|
||||
A `provider` is mandatory.
|
||||
|
||||
##### `provider`
|
||||
|
||||
Here is a list of supported `provider`s, that can automate the DNS verification, along with the required environment variables and their [wildcard & root domain support](/configuration/acme/#wildcard-domains) for each.
|
||||
Do not hesitate to complete it.
|
||||
Every lego environment variable can be overridden by their respective `_FILE` counterpart, which should have a filepath to a file that contains the secret as its value.
|
||||
For example, `CF_API_EMAIL_FILE=/run/secrets/traefik_cf-api-email` could be used to provide a Cloudflare API email address as a Docker secret named `traefik_cf-api-email`.
|
||||
|
||||
| Provider Name | Provider Code | Environment Variables | Wildcard & Root Domain Support |
|
||||
|-------------------------------------------------------------|----------------|---------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------|
|
||||
@@ -127,6 +291,7 @@ Do not hesitate to complete it.
|
||||
| [DNSimple](https://dnsimple.com) | `dnsimple` | `DNSIMPLE_OAUTH_TOKEN`, `DNSIMPLE_BASE_URL` | YES |
|
||||
| [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET`, `DNSMADEEASY_SANDBOX` | Not tested yet |
|
||||
| [DNSPod](https://www.dnspod.com/) | `dnspod` | `DNSPOD_API_KEY` | Not tested yet |
|
||||
| [Domain Offensive (do.de)](https://www.do.de/) | `dode` | `DODE_TOKEN` | YES |
|
||||
| [DreamHost](https://www.dreamhost.com/) | `dreamhost` | `DREAMHOST_API_KEY` | YES |
|
||||
| [Duck DNS](https://www.duckdns.org/) | `duckdns` | `DUCKDNS_TOKEN` | YES |
|
||||
| [Dyn](https://dyn.com) | `dyn` | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` | Not tested yet |
|
||||
@@ -137,9 +302,9 @@ Do not hesitate to complete it.
|
||||
| [Gandi v5](http://doc.livedns.gandi.net) | `gandiv5` | `GANDIV5_API_KEY` | YES |
|
||||
| [Glesys](https://glesys.com/) | `glesys` | `GLESYS_API_USER`, `GLESYS_API_KEY`, `GLESYS_DOMAIN` | Not tested yet |
|
||||
| [GoDaddy](https://godaddy.com/domains) | `godaddy` | `GODADDY_API_KEY`, `GODADDY_API_SECRET` | Not tested yet |
|
||||
| [Google Cloud DNS](https://cloud.google.com/dns/docs/) | `gcloud` | `GCE_PROJECT`, Application Default Credentials [^2] [^3], [`GCE_SERVICE_ACCOUNT_FILE`] | YES |
|
||||
| [hosting.de](https://www.hosting.de) | `hostingde` | `HOSTINGDE_API_KEY`, `HOSTINGDE_ZONE_NAME` | Not tested yet |
|
||||
| HTTP request | `httpreq` | `HTTPREQ_ENDPOINT`, `HTTPREQ_MODE`, `HTTPREQ_USERNAME`, `HTTPREQ_PASSWORD` [^1] | YES |
|
||||
| [Google Cloud DNS](https://cloud.google.com/dns/docs/) | `gcloud` | `GCE_PROJECT`, Application Default Credentials (2) (3), [`GCE_SERVICE_ACCOUNT_FILE`] | YES |
|
||||
| [hosting.de](https://www.hosting.de) | `hostingde` | `HOSTINGDE_API_KEY`, `HOSTINGDE_ZONE_NAME` | YES |
|
||||
| HTTP request | `httpreq` | `HTTPREQ_ENDPOINT`, `HTTPREQ_MODE`, `HTTPREQ_USERNAME`, `HTTPREQ_PASSWORD` (1) | YES |
|
||||
| [IIJ](https://www.iij.ad.jp/) | `iij` | `IIJ_API_ACCESS_KEY`, `IIJ_API_SECRET_KEY`, `IIJ_DO_SERVICE_CODE` | Not tested yet |
|
||||
| [INWX](https://www.inwx.de/en) | `inwx` | `INWX_USERNAME`, `INWX_PASSWORD` | YES |
|
||||
| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `DNS_ZONE` | Not tested yet |
|
||||
@@ -169,14 +334,9 @@ Do not hesitate to complete it.
|
||||
| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | Not tested yet |
|
||||
| [Zone.ee](https://www.zone.ee) | `zoneee` | `ZONEEE_API_USER`, `ZONEEE_API_KEY` | YES |
|
||||
|
||||
[^1]: more information about the HTTP message format can be found [here](https://go-acme.github.io/lego/dns/httpreq/)
|
||||
[^2]: [providing_credentials_to_your_application](https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application)
|
||||
[^3]: [google/default.go](https://github.com/golang/oauth2/blob/36a7019397c4c86cf59eeab3bc0d188bac444277/google/default.go#L61-L76)
|
||||
|
||||
!!! note "`delayBeforeCheck`"
|
||||
By default, the `provider` verifies the TXT record _before_ letting ACME verify.
|
||||
You can delay this operation by specifying a delay (in seconds) with `delayBeforeCheck` (value must be greater than zero).
|
||||
This option is useful when internal networks block external DNS queries.
|
||||
- (1): more information about the HTTP message format can be found [here](https://go-acme.github.io/lego/dns/httpreq/)
|
||||
- (2): https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application
|
||||
- (3): https://github.com/golang/oauth2/blob/36a7019397c4c86cf59eeab3bc0d188bac444277/google/default.go#L61-L76
|
||||
|
||||
#### `resolvers`
|
||||
|
||||
@@ -184,106 +344,112 @@ Use custom DNS servers to resolve the FQDN authority.
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
[acme.dnsChallenge]
|
||||
# ...
|
||||
resolvers = ["1.1.1.1:53", "8.8.8.8:53"]
|
||||
```
|
||||
|
||||
#### Wildcard Domains
|
||||
|
||||
[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).
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
[[acme.domains]]
|
||||
main = "*.local1.com"
|
||||
sans = ["local1.com"]
|
||||
|
||||
# ...
|
||||
[acme.dnsChallenge]
|
||||
# ...
|
||||
resolvers = ["1.1.1.1:53", "8.8.8.8:53"]
|
||||
```
|
||||
|
||||
!!! note "Double Wildcard Certificates"
|
||||
It is not possible to request a double wildcard certificate for a domain (for example `*.*.local.com`).
|
||||
### `domains`
|
||||
|
||||
Due to an ACME limitation it is not possible to define wildcards in SANs (alternative domains).
|
||||
Thus, the wildcard domain has to be defined as a main domain.
|
||||
|
||||
Most likely the root domain should receive a certificate too, so it needs to be specified as SAN and 2 `DNS-01` challenges are executed.
|
||||
In this case the generated DNS TXT record for both domains is the same.
|
||||
Even though this behavior is [DNS RFC](https://community.letsencrypt.org/t/wildcard-issuance-two-txt-records-for-the-same-name/54528/2) compliant,
|
||||
it can lead to problems as all DNS providers keep DNS records cached for a given time (TTL) and this TTL can be greater than the challenge timeout making the `DNS-01` challenge fail.
|
||||
|
||||
The Traefik ACME client library [LEGO](https://github.com/go-acme/lego) supports some but not all DNS providers to work around this issue.
|
||||
The [Supported `provider` table](#providers) indicates if they allow generating certificates for a wildcard domain and its root domain.
|
||||
|
||||
## Known Domains, SANs
|
||||
|
||||
You can set SANs (alternative domains) for each main domain.
|
||||
Every domain must have A/AAAA records pointing to Traefik.
|
||||
You can provide SANs (alternative domains) to each main domain.
|
||||
All domains must have A/AAAA records pointing to Traefik.
|
||||
Each domain & SAN will lead to a certificate request.
|
||||
|
||||
!!! note
|
||||
The certificates for the domains listed in `acme.domains` are negotiated at traefik startup only.
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
[[acme.domains]]
|
||||
main = "local1.com"
|
||||
sans = ["test1.local1.com", "test2.local1.com"]
|
||||
[[acme.domains]]
|
||||
main = "local2.com"
|
||||
[[acme.domains]]
|
||||
main = "*.local3.com"
|
||||
sans = ["local3.com", "test1.test1.local3.com"]
|
||||
# ...
|
||||
[[acme.domains]]
|
||||
main = "local1.com"
|
||||
sans = ["test1.local1.com", "test2.local1.com"]
|
||||
[[acme.domains]]
|
||||
main = "local2.com"
|
||||
[[acme.domains]]
|
||||
main = "*.local3.com"
|
||||
sans = ["local3.com", "test1.test1.local3.com"]
|
||||
# ...
|
||||
```
|
||||
|
||||
!!! important
|
||||
The certificates for the domains listed in `acme.domains` are negotiated at Traefik startup only.
|
||||
!!! warning
|
||||
Take note that Let's Encrypt applies [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
||||
|
||||
!!! note
|
||||
Wildcard certificates can only be verified through a `DNS-01` challenge.
|
||||
|
||||
## `caServer`
|
||||
#### Wildcard Domains
|
||||
|
||||
??? example "Using the Let's Encrypt staging server"
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
# ...
|
||||
```
|
||||
|
||||
## `onHostRule`
|
||||
|
||||
Enable certificate generation on [routers](../routing/routers/index.md) `Host` & `HostSNI` rules.
|
||||
|
||||
This will request a certificate from Let's Encrypt for each router with a Host rule.
|
||||
[ACME V2](https://community.letsencrypt.org/t/acme-v2-and-wildcard-certificate-support-is-live/55579) allows wildcard certificate support.
|
||||
As described in [Let's Encrypt's post](https://community.letsencrypt.org/t/staging-endpoint-for-acme-v2/49605) wildcard certificates can only be generated through a [`DNS-01` challenge](/configuration/acme/#dnschallenge).
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
onHostRule = true
|
||||
# ...
|
||||
# ...
|
||||
[[acme.domains]]
|
||||
main = "*.local1.com"
|
||||
sans = ["local1.com"]
|
||||
# ...
|
||||
```
|
||||
|
||||
!!! note "Multiple Hosts in a Rule"
|
||||
The rule `Host(test1.traefik.io,test2.traefik.io)` will request a certificate with the main domain `test1.traefik.io` and SAN `test2.traefik.io`.
|
||||
It is not possible to request a double wildcard certificate for a domain (for example `*.*.local.com`).
|
||||
Most likely the root domain should receive a certificate too, so it needs to be specified as SAN and 2 `DNS-01` challenges are executed.
|
||||
In this case the generated DNS TXT record for both domains is the same.
|
||||
Even though this behaviour is [DNS RFC](https://community.letsencrypt.org/t/wildcard-issuance-two-txt-records-for-the-same-name/54528/2) compliant, it can lead to problems as all DNS providers keep DNS records cached for a certain time (TTL) and this TTL can be superior to the challenge timeout making the `DNS-01` challenge fail.
|
||||
The Traefik ACME client library [LEGO](https://github.com/go-acme/lego) supports some but not all DNS providers to work around this issue.
|
||||
The [`provider` table](/configuration/acme/#provider) indicates if they allow generating certificates for a wildcard domain and its root domain.
|
||||
|
||||
### `onDemand` (Deprecated)
|
||||
|
||||
!!! danger "DEPRECATED"
|
||||
This option is deprecated.
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
onDemand = true
|
||||
# ...
|
||||
```
|
||||
|
||||
Enable on demand certificate generation.
|
||||
|
||||
This will request certificates from Let's Encrypt during the first TLS handshake for host names that do not yet have certificates.
|
||||
|
||||
!!! warning
|
||||
`onHostRule` option can not be used to generate wildcard certificates. Refer to [wildcard generation](#wildcard-domains) for further information.
|
||||
TLS handshakes are slow when requesting a host name certificate for the first time. This can lead to DoS attacks!
|
||||
|
||||
## `storage`
|
||||
!!! warning
|
||||
Take note that Let's Encrypt applies [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
||||
|
||||
### `onHostRule`
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
onHostRule = true
|
||||
# ...
|
||||
```
|
||||
|
||||
Enable certificate generation on frontend `Host` rules (for frontends wired to the `acme.entryPoint`).
|
||||
|
||||
This will request a certificate from Let's Encrypt for each frontend with a Host rule.
|
||||
|
||||
For example, the rule `Host:test1.traefik.io,test2.traefik.io` will request a certificate with main domain `test1.traefik.io` and SAN `test2.traefik.io`.
|
||||
|
||||
!!! warning
|
||||
`onHostRule` option can not be used to generate wildcard certificates.
|
||||
Refer to [wildcard generation](/configuration/acme/#wildcard-domains) for further information.
|
||||
|
||||
### `storage`
|
||||
|
||||
The `storage` option sets the location where your ACME certificates are saved to.
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
storage = "acme.json"
|
||||
# ...
|
||||
# ...
|
||||
storage = "acme.json"
|
||||
# ...
|
||||
```
|
||||
|
||||
The value can refer to two kinds of storage:
|
||||
@@ -291,43 +457,68 @@ The value can refer to two kinds of storage:
|
||||
- a JSON file
|
||||
- a KV store entry
|
||||
|
||||
### In a File
|
||||
!!! danger "DEPRECATED"
|
||||
`storage` replaces `storageFile` which is deprecated.
|
||||
|
||||
ACME certificates can be stored in a JSON file that needs to have a `600` file mode .
|
||||
!!! note
|
||||
During migration to a KV store use both `storageFile` and `storage` to migrate ACME certificates too. See [`storeconfig` subcommand](/user-guide/kv-config/#store-configuration-in-key-value-store) for further information.
|
||||
|
||||
In Docker you can mount either the JSON file, or the folder containing it:
|
||||
#### As a File
|
||||
|
||||
ACME certificates can be stored in a JSON file that needs to have file mode `600`.
|
||||
|
||||
In Docker you can either mount the JSON file or the folder containing it:
|
||||
|
||||
```bash
|
||||
docker run -v "/my/host/acme.json:acme.json" traefik
|
||||
```
|
||||
|
||||
```bash
|
||||
docker run -v "/my/host/acme:/etc/traefik/acme" traefik
|
||||
```
|
||||
|
||||
!!! warning
|
||||
For concurrency reason, this file cannot be shared across multiple instances of Traefik. Use a key value store entry instead.
|
||||
This file cannot be shared across multiple instances of Traefik at the same time. Please use a [KV Store entry](/configuration/acme/#as-a-key-value-store-entry) instead.
|
||||
|
||||
### In a a Key Value Store Entry
|
||||
#### As a Key Value Store Entry
|
||||
|
||||
ACME certificates can be stored in a key-value store entry.
|
||||
ACME certificates can be stored in a KV Store entry. This kind of storage is **mandatory in cluster mode**.
|
||||
|
||||
```toml
|
||||
storage = "traefik/acme/account"
|
||||
```
|
||||
|
||||
!!! note "Storage Size"
|
||||
Because KV stores (like Consul) have limited entry size the certificates list is compressed before it is saved as KV store entry.
|
||||
|
||||
Because key-value stores have limited entry size, the certificates list is compressed _before_ it is saved.
|
||||
For example, it is possible to store up to _approximately_ 100 ACME certificates in Consul.
|
||||
!!! note
|
||||
It is possible to store up to approximately 100 ACME certificates in Consul.
|
||||
|
||||
#### ACME v2 Migration
|
||||
|
||||
During migration from ACME v1 to ACME v2, using a storage file, a backup of the original file is created in the same place as the latter (with a `.bak` extension).
|
||||
|
||||
For example: if `acme.storage`'s value is `/etc/traefik/acme/acme.json`, the backup file will be `/etc/traefik/acme/acme.json.bak`.
|
||||
|
||||
!!! note
|
||||
When Traefik is launched in a container, the storage file's parent directory needs to be mounted to be able to access the backup file on the host.
|
||||
Otherwise the backup file will be deleted when the container is stopped. Traefik will only generate it once!
|
||||
|
||||
### `dnsProvider` (Deprecated)
|
||||
|
||||
!!! danger "DEPRECATED"
|
||||
This option is deprecated. Please use [dnsChallenge.provider](/configuration/acme/#provider) instead.
|
||||
|
||||
### `delayDontCheckDNS` (Deprecated)
|
||||
|
||||
!!! danger "DEPRECATED"
|
||||
This option is deprecated. Please use [dnsChallenge.delayBeforeCheck](/configuration/acme/#dnschallenge) instead.
|
||||
|
||||
## Fallbacks
|
||||
|
||||
If Let's Encrypt is not reachable, the following certificates will apply:
|
||||
If Let's Encrypt is not reachable, these certificates will be used:
|
||||
|
||||
1. Previously generated ACME certificates (before downtime)
|
||||
1. ACME certificates already generated before downtime
|
||||
1. Expired ACME certificates
|
||||
1. Provided certificates
|
||||
|
||||
!!! note
|
||||
For new (sub)domains which need Let's Encrypt authentication, the default Traefik certificate will be used until Traefik is restarted.
|
||||
For new (sub)domains which need Let's Encrypt authentification, the default Traefik certificate will be used until Traefik is restarted.
|
||||
@@ -107,8 +107,7 @@ Additional settings can be defined using Consul Catalog tags.
|
||||
| `<prefix>.backend.circuitbreaker.expression=EXPR` | Creates a [circuit breaker](/basics/#backends) to be used against the backend. ex: `NetworkErrorRatio() > 0.` |
|
||||
| `<prefix>.backend.responseForwarding.flushInterval=10ms` | Defines the interval between two flushes when forwarding response from backend to client. |
|
||||
| `<prefix>.backend.healthcheck.path=/health` | Enables health check for the backend, hitting the container at `path`. |
|
||||
| `<prefix>.backend.healthcheck.interval=5s` | Defines the health check interval. |
|
||||
| `<prefix>.backend.healthcheck.timeout=3s` | Defines the health check request timeout |
|
||||
| `<prefix>.backend.healthcheck.interval=1s` | Defines the health check interval. |
|
||||
| `<prefix>.backend.healthcheck.port=8080` | Sets a different port for the health check. |
|
||||
| `traefik.backend.healthcheck.scheme=http` | Overrides the server URL scheme. |
|
||||
| `<prefix>.backend.healthcheck.hostname=foobar.com` | Defines the health check hostname. |
|
||||
@@ -116,6 +115,7 @@ Additional settings can be defined using Consul Catalog tags.
|
||||
| `<prefix>.backend.loadbalancer.method=drr` | Overrides the default `wrr` load balancer algorithm. |
|
||||
| `<prefix>.backend.loadbalancer.stickiness=true` | Enables backend sticky sessions. |
|
||||
| `<prefix>.backend.loadbalancer.stickiness.cookieName=NAME` | Sets the cookie name manually for sticky sessions. |
|
||||
| `<prefix>.backend.loadbalancer.sticky=true` | Enables backend sticky sessions. (DEPRECATED) |
|
||||
| `<prefix>.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
|
||||
| `<prefix>.backend.maxconn.extractorfunc=client.ip` | Sets the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |
|
||||
| `<prefix>.frontend.auth.basic=EXPR` | Sets basic authentication to this frontend in CSV format: `User:Hash,User:Hash` (DEPRECATED). |
|
||||
@@ -169,9 +169,7 @@ Additional settings can be defined using Consul Catalog tags.
|
||||
| `<prefix>.frontend.redirect.permanent=true` | Returns 301 instead of 302. |
|
||||
| `<prefix>.frontend.rule=EXPR` | Overrides the default frontend rule. Default: `Host:{{.ServiceName}}.{{.Domain}}`. |
|
||||
| `<prefix>.frontend.whiteList.sourceRange=RANGE` | Sets a list of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `<prefix>.frontend.whiteList.ipStrategy=true` | Uses the default IPStrategy.<br>Can be used when there is an existing `clientIPStrategy` but you want the remote address for whitelisting. |
|
||||
| `<prefix>.frontend.whiteList.ipStrategy.depth=5` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| `<prefix>.frontend.whiteList.ipStrategy.excludedIPs=127.0.0.1` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| `<prefix>.frontend.whiteList.useXForwardedFor=true` | Uses `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
|
||||
### Multiple frontends for a single service
|
||||
|
||||
538
docs/configuration/backends/docker.md
Normal file
@@ -0,0 +1,538 @@
|
||||
|
||||
# Docker Provider
|
||||
|
||||
Traefik can be configured to use Docker as a provider.
|
||||
|
||||
## Docker
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Docker Provider
|
||||
################################################################
|
||||
|
||||
# Enable Docker Provider.
|
||||
[docker]
|
||||
|
||||
# Docker server endpoint. Can be a tcp or a unix socket endpoint.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
endpoint = "unix:///var/run/docker.sock"
|
||||
|
||||
# Default base domain used for the frontend rules.
|
||||
# Can be overridden by setting the "traefik.domain" label on a container.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
domain = "docker.localhost"
|
||||
|
||||
# Enable watch docker changes.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
watch = true
|
||||
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# filename = "docker.tmpl"
|
||||
|
||||
# Override template version
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
# - "1": previous template version (must be used only with older custom templates, see "filename")
|
||||
# - "2": current template version (must be used to force template version when "filename" is used)
|
||||
#
|
||||
# templateVersion = 2
|
||||
|
||||
# Expose containers by default in Traefik.
|
||||
# If set to false, containers that don't have `traefik.enable=true` will be ignored.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
exposedByDefault = true
|
||||
|
||||
# Use the IP address from the binded port instead of the inner network one.
|
||||
#
|
||||
# In case no IP address is attached to the binded port (or in case
|
||||
# there is no bind), the inner network one will be used as a fallback.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
usebindportip = true
|
||||
|
||||
# Use Swarm Mode services as data provider.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
swarmMode = false
|
||||
|
||||
# Polling interval (in seconds) for Swarm Mode.
|
||||
#
|
||||
# Optional
|
||||
# Default: 15
|
||||
#
|
||||
swarmModeRefreshSeconds = 15
|
||||
|
||||
# Define a default docker network to use for connections to all containers.
|
||||
# Can be overridden by the traefik.docker.network label.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
network = "web"
|
||||
|
||||
# Enable docker TLS connection.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [docker.tls]
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/docker.crt"
|
||||
# key = "/etc/ssl/docker.key"
|
||||
# insecureSkipVerify = true
|
||||
```
|
||||
|
||||
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
|
||||
|
||||
## Docker Swarm Mode
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Docker Swarm Mode Provider
|
||||
################################################################
|
||||
|
||||
# Enable Docker Provider.
|
||||
[docker]
|
||||
|
||||
# Docker server endpoint.
|
||||
# Can be a tcp or a unix socket endpoint.
|
||||
#
|
||||
# Required
|
||||
# Default: "unix:///var/run/docker.sock"
|
||||
#
|
||||
# swarm classic (1.12-)
|
||||
# endpoint = "tcp://127.0.0.1:2375"
|
||||
# docker swarm mode (1.12+)
|
||||
endpoint = "tcp://127.0.0.1:2377"
|
||||
|
||||
# Default base domain used for the frontend rules.
|
||||
# Can be overridden by setting the "traefik.domain" label on a services.
|
||||
#
|
||||
# Optional
|
||||
# Default: ""
|
||||
#
|
||||
domain = "docker.localhost"
|
||||
|
||||
# Enable watch docker changes.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
watch = true
|
||||
|
||||
# Use Docker Swarm Mode as data provider.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
swarmMode = true
|
||||
|
||||
# Define a default docker network to use for connections to all containers.
|
||||
# Can be overridden by the traefik.docker.network label.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
network = "web"
|
||||
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# filename = "docker.tmpl"
|
||||
|
||||
# Override template version
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
# - "1": previous template version (must be used only with older custom templates, see "filename")
|
||||
# - "2": current template version (must be used to force template version when "filename" is used)
|
||||
#
|
||||
# templateVersion = 2
|
||||
|
||||
# Expose services by default in Traefik.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
exposedByDefault = false
|
||||
|
||||
# Enable docker TLS connection.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [docker.tls]
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/docker.crt"
|
||||
# key = "/etc/ssl/docker.key"
|
||||
# insecureSkipVerify = true
|
||||
```
|
||||
|
||||
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Security Challenge with the Docker Socket
|
||||
|
||||
Traefik requires access to the docker socket to get its dynamic configuration,
|
||||
by watching the Docker API through this socket.
|
||||
|
||||
!!! important
|
||||
Depending on your context and your usage, accessing the Docker API without any restriction might be a security concern.
|
||||
|
||||
As explained on 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 [...]`
|
||||
|
||||
If the Traefik processes (handling requests from the outside world) is attacked,
|
||||
then the attacker can access the Docker (or Swarm Mode) backend.
|
||||
|
||||
Also, when using Swarm Mode, it is mandatory to schedule Traefik's containers on the Swarm manager nodes,
|
||||
to let Traefik accessing the Docker Socket of the Swarm manager node.
|
||||
|
||||
More information 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.html)
|
||||
- [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)
|
||||
|
||||
### Workarounds
|
||||
|
||||
!!! note "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).
|
||||
|
||||
Another possible workaround is to 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 [the "Protect the Docker daemon socket" page of Docker's documentation](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).
|
||||
|
||||
Use the following ressources to get started:
|
||||
|
||||
- [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)
|
||||
|
||||
## Labels: overriding default behavior
|
||||
|
||||
### Using Docker with Swarm Mode
|
||||
|
||||
If you use a compose file with the 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)).
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
services:
|
||||
whoami:
|
||||
deploy:
|
||||
labels:
|
||||
traefik.docker.network: traefik
|
||||
```
|
||||
|
||||
### Using Docker Compose
|
||||
|
||||
If you are intending to use only Docker Compose commands (e.g. `docker-compose up --scale whoami=2 -d`), labels should be under your service, otherwise they will be ignored.
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
services:
|
||||
whoami:
|
||||
labels:
|
||||
traefik.docker.network: traefik
|
||||
```
|
||||
|
||||
### On Containers
|
||||
|
||||
Labels can be used on containers to override default behavior.
|
||||
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.docker.network` | Overrides the default docker network to use for connections to the container. [1] |
|
||||
| `traefik.domain` | Sets the default base domain for the frontend rules. For more information, check the [Container Labels section's of the user guide "Let's Encrypt & Docker"](/user-guide/docker-and-lets-encrypt/#container-labels) |
|
||||
| `traefik.enable=false` | Disables this container in Traefik. |
|
||||
| `traefik.port=80` | Registers this port. Useful when the container exposes multiples ports. |
|
||||
| `traefik.tags=foo,bar,myTag` | Adds Traefik tags to the Docker container/service to be used in [constraints](/configuration/commons/#constraints). |
|
||||
| `traefik.protocol=https` | Overrides the default `http` protocol |
|
||||
| `traefik.weight=10` | Assigns this weight to the container |
|
||||
| `traefik.backend=foo` | Overrides the container name by `foo` in the generated name of the backend. |
|
||||
| `traefik.backend.buffering.maxRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.maxResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.retryExpression=EXPR` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.circuitbreaker.expression=EXPR` | Creates a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||
| `traefik.backend.responseForwarding.flushInterval=10ms` | Defines the interval between two flushes when forwarding response from backend to client. |
|
||||
| `traefik.backend.healthcheck.path=/health` | Enables health check for the backend, hitting the container at `path`. |
|
||||
| `traefik.backend.healthcheck.interval=1s` | Defines the health check interval. |
|
||||
| `traefik.backend.healthcheck.port=8080` | Sets a different port for the health check. |
|
||||
| `traefik.backend.healthcheck.scheme=http` | Overrides the server URL scheme. |
|
||||
| `traefik.backend.healthcheck.hostname=foobar.com` | Defines the health check hostname. |
|
||||
| `traefik.backend.healthcheck.headers=EXPR` | Defines the health check request headers <br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.backend.loadbalancer.method=drr` | Overrides the default `wrr` load balancer algorithm |
|
||||
| `traefik.backend.loadbalancer.stickiness=true` | Enables backend sticky sessions |
|
||||
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Sets the cookie name manually for sticky sessions |
|
||||
| `traefik.backend.loadbalancer.sticky=true` | Enables backend sticky sessions (DEPRECATED) |
|
||||
| `traefik.backend.loadbalancer.swarm=true` | Uses Swarm's inbuilt load balancer (only relevant under Swarm Mode) [3]. |
|
||||
| `traefik.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
|
||||
| `traefik.backend.maxconn.extractorfunc=client.ip` | Sets the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets the basic authentication to this frontend in CSV format: `User:Hash,User:Hash` [2] (DEPRECATED). |
|
||||
| `traefik.frontend.auth.basic.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
|
||||
| `traefik.frontend.auth.basic.users=EXPR` | Sets the basic authentication to this frontend in CSV format: `User:Hash,User:Hash` [2]. |
|
||||
| `traefik.frontend.auth.basic.usersFile=/path/.htpasswd` | Sets the basic authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
|
||||
| `traefik.frontend.auth.digest.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
|
||||
| `traefik.frontend.auth.digest.users=EXPR` | Sets the digest authentication to this frontend in CSV format: `User:Realm:Hash,User:Realm:Hash`. |
|
||||
| `traefik.frontend.auth.digest.usersFile=/path/.htdigest` | Sets the digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
|
||||
| `traefik.frontend.auth.forward.address=https://example.com` | Sets the URL of the authentication server. |
|
||||
| `traefik.frontend.auth.forward.authResponseHeaders=EXPR` | Sets the forward authentication authResponseHeaders in CSV format: `X-Auth-User,X-Auth-Header` |
|
||||
| `traefik.frontend.auth.forward.tls.ca=/path/ca.pem` | Sets the Certificate Authority (CA) for the TLS connection with the authentication server. |
|
||||
| `traefik.frontend.auth.forward.tls.caOptional=true` | Checks the certificates if present but do not force to be signed by a specified Certificate Authority (CA). |
|
||||
| `traefik.frontend.auth.forward.tls.cert=/path/server.pem` | Sets the Certificate for the TLS connection with the authentication server. |
|
||||
| `traefik.frontend.auth.forward.tls.insecureSkipVerify=true` | If set to true invalid SSL certificates are accepted. |
|
||||
| `traefik.frontend.auth.forward.tls.key=/path/server.key` | Sets the Certificate for the TLS connection with the authentication server. |
|
||||
| `traefik.frontend.auth.forward.trustForwardHeader=true` | Trusts X-Forwarded-* headers. |
|
||||
| `traefik.frontend.auth.headerField=X-WebAuth-User` | Sets the header user to pass the authenticated user to the application. |
|
||||
| `traefik.frontend.entryPoints=http,https` | Assigns this frontend to entry points `http` and `https`.<br>Overrides `defaultEntryPoints` |
|
||||
| `traefik.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.passHostHeader=true` | Forwards client `Host` header to the backend. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.commonName=true` | Add the issuer.commonName field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.country=true` | Add the issuer.country field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.domainComponent=true` | Add the issuer.domainComponent field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.locality=true` | Add the issuer.locality field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.organization=true` | Add the issuer.organization field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.province=true` | Add the issuer.province field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.serialNumber=true` | Add the issuer.serialNumber field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.notAfter=true` | Add the noAfter field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.notBefore=true` | Add the noBefore field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.sans=true` | Add the sans field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.commonName=true` | Add the subject.commonName field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.country=true` | Add the subject.country field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.domainComponent=true` | Add the subject.domainComponent field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.locality=true` | Add the subject.locality field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.organization=true` | Add the subject.organization field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.province=true` | Add the subject.province field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.serialNumber=true` | Add the subject.serialNumber field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.pem=true` | Pass the escaped pem in the `X-Forwarded-Ssl-Client-Cert` header. |
|
||||
| `traefik.frontend.passTLSCert=true` | Forwards TLS Client certificates to the backend (DEPRECATED). |
|
||||
| `traefik.frontend.priority=10` | Overrides default frontend priority |
|
||||
| `traefik.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint to this frontend (e.g. HTTPS) |
|
||||
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirects to another URL to this frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
||||
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirects to another URL to this frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
||||
| `traefik.frontend.redirect.permanent=true` | Returns 301 instead of 302. |
|
||||
| `traefik.frontend.rule=EXPR` | Overrides the default frontend rule. Default: `Host:{containerName}.{domain}` or `Host:{service}.{project_name}.{domain}` if you are using `docker-compose`. |
|
||||
| `traefik.frontend.whiteList.sourceRange=RANGE` | Sets a list of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access.<br>If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.frontend.whiteList.useXForwardedFor=true` | Uses `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
|
||||
[1] `traefik.docker.network`:
|
||||
If a container is linked to several networks, be sure to set the proper network name (you can check with `docker inspect <container_id>`) otherwise it will randomly pick one (depending on how docker is returning them).
|
||||
For instance when deploying docker `stack` from compose files, the compose defined networks will be prefixed with the `stack` name.
|
||||
Or if your service references external network use it's name instead.
|
||||
|
||||
[2] `traefik.frontend.auth.basic.users=EXPR`:
|
||||
To create `user:password` pair, it's possible to use this command:
|
||||
`echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g`.
|
||||
The result will be `user:$$apr1$$9Cv/OMGj$$ZomWQzuQbL.3TRCS81A1g/`, note additional symbol `$` makes escaping.
|
||||
|
||||
[3] `traefik.backend.loadbalancer.swarm`:
|
||||
If you enable this option, Traefik will use the virtual IP provided by docker swarm instead of the containers IPs.
|
||||
Which means that Traefik will not perform any kind of load balancing and will delegate this task to swarm.
|
||||
It also means that Traefik will manipulate only one backend, not one backend per container.
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.frontend.headers.customRequestHeaders=EXPR` | Provides the container with custom request headers that will be appended to each request forwarded to the container.<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.frontend.headers.customResponseHeaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client.<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
|
||||
#### Security Headers
|
||||
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.<br>Format: `Host1,Host2` |
|
||||
| `traefik.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `traefik.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
|
||||
| `traefik.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `traefik.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `traefik.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `traefik.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
|
||||
| `traefik.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `traefik.frontend.headers.hostsProxyHeaders=EXPR` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` |
|
||||
| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
| `traefik.frontend.headers.publicKey=VALUE` | Adds HPKP header. |
|
||||
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||
| `traefik.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
|
||||
| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
|
||||
| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
|
||||
| `traefik.frontend.headers.SSLForceHost=true` | If `SSLForceHost` is `true` and `SSLHost` is set, requests will be forced to use `SSLHost` even the ones that are already using SSL. Default is false. |
|
||||
| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
|
||||
| `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
|
||||
| `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
|
||||
|
||||
### On containers with Multiple Ports (segment labels)
|
||||
|
||||
Segment labels are used to define routes to a container exposing multiple ports.
|
||||
A segment is a group of labels that apply to a port exposed by a container.
|
||||
You can define as many segments as ports exposed in a container.
|
||||
|
||||
Segment labels override the default behavior.
|
||||
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------------------------------------|----------------------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
|
||||
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
|
||||
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
|
||||
| `traefik.<segment_name>.protocol=http` | Same as `traefik.protocol` |
|
||||
| `traefik.<segment_name>.weight=10` | Same as `traefik.weight` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic.removeHeader=true` | Same as `traefik.frontend.auth.basic.removeHeader` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic.users=EXPR` | Same as `traefik.frontend.auth.basic.users` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic.usersFile=/path/.htpasswd` | Same as `traefik.frontend.auth.basic.usersFile` |
|
||||
| `traefik.<segment_name>.frontend.auth.digest.removeHeader=true` | Same as `traefik.frontend.auth.digest.removeHeader` |
|
||||
| `traefik.<segment_name>.frontend.auth.digest.users=EXPR` | Same as `traefik.frontend.auth.digest.users` |
|
||||
| `traefik.<segment_name>.frontend.auth.digest.usersFile=/path/.htdigest` | Same as `traefik.frontend.auth.digest.usersFile` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.address=https://example.com` | Same as `traefik.frontend.auth.forward.address` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.authResponseHeaders=EXPR` | Same as `traefik.frontend.auth.forward.authResponseHeaders` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.ca=/path/ca.pem` | Same as `traefik.frontend.auth.forward.tls.ca` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.caOptional=true` | Same as `traefik.frontend.auth.forward.tls.caOptional` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.cert=/path/server.pem` | Same as `traefik.frontend.auth.forward.tls.cert` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.insecureSkipVerify=true` | Same as `traefik.frontend.auth.forward.tls.insecureSkipVerify` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.key=/path/server.key` | Same as `traefik.frontend.auth.forward.tls.key` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.trustForwardHeader=true` | Same as `traefik.frontend.auth.forward.trustForwardHeader` |
|
||||
| `traefik.<segment_name>.frontend.auth.headerField=X-WebAuth-User` | Same as `traefik.frontend.auth.headerField` |
|
||||
| `traefik.<segment_name>.frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | Same as `traefik.frontend.errors.<name>.backend` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | Same as `traefik.frontend.errors.<name>.query` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.status=RANGE` | Same as `traefik.frontend.errors.<name>.status` |
|
||||
| `traefik.<segment_name>.frontend.passHostHeader=true` | Same as `traefik.frontend.passHostHeader` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.issuer.commonName=true` | Same as `traefik.frontend.passTLSClientCert.infos.issuer.commonName` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.issuer.country=true` | Same as `traefik.frontend.passTLSClientCert.infos.issuer.country` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.issuer.domainComponent=true` | Same as `traefik.frontend.passTLSClientCert.infos.issuer.domainComponent` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.issuer.locality=true` | Same as `traefik.frontend.passTLSClientCert.infos.issuer.locality` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.issuer.organization=true` | Same as `traefik.frontend.passTLSClientCert.infos.issuer.organization` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.issuer.province=true` | Same as `traefik.frontend.passTLSClientCert.infos.issuer.province` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.issuer.serialNumber=true` | Same as `traefik.frontend.passTLSClientCert.infos.issuer.serialNumber` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.notAfter=true` | Same as `traefik.frontend.passTLSClientCert.infos.notAfter` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.notBefore=true` | Same as `traefik.frontend.passTLSClientCert.infos.notBefore` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.sans=true` | Same as `traefik.frontend.passTLSClientCert.infos.sans` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.commonName=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.commonName` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.country=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.country` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.domainComponent=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.domainComponent` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.locality=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.locality` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.organization=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.organization` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.province=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.province` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.serialNumber=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.serialNumber` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.pem=true` | Same as `traefik.frontend.passTLSClientCert.infos.pem` |
|
||||
| `traefik.<segment_name>.frontend.passTLSCert=true` | Same as `traefik.frontend.passTLSCert` |
|
||||
| `traefik.<segment_name>.frontend.priority=10` | Same as `traefik.frontend.priority` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.extractorFunc=EXP` | Same as `traefik.frontend.rateLimit.extractorFunc` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.period=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.period` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.average=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.average` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.burst=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.burst` |
|
||||
| `traefik.<segment_name>.frontend.redirect.entryPoint=https` | Same as `traefik.frontend.redirect.entryPoint` |
|
||||
| `traefik.<segment_name>.frontend.redirect.regex=^http://localhost/(.*)` | Same as `traefik.frontend.redirect.regex` |
|
||||
| `traefik.<segment_name>.frontend.redirect.replacement=http://mydomain/$1` | Same as `traefik.frontend.redirect.replacement` |
|
||||
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Same as `traefik.frontend.redirect.permanent` |
|
||||
| `traefik.<segment_name>.frontend.rule=EXP` | Same as `traefik.frontend.rule` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Same as `traefik.frontend.whiteList.sourceRange` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Same as `traefik.frontend.whiteList.useXForwardedFor` |
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------------------|----------------------------------------------------------|
|
||||
| `traefik.<segment_name>.frontend.headers.customRequestHeaders=EXPR` | Same as `traefik.frontend.headers.customRequestHeaders` |
|
||||
| `traefik.<segment_name>.frontend.headers.customResponseHeaders=EXPR` | Same as `traefik.frontend.headers.customResponseHeaders` |
|
||||
|
||||
#### Security Headers
|
||||
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------------------------|--------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.frontend.headers.allowedHosts=EXPR` | Same as `traefik.frontend.headers.allowedHosts` |
|
||||
| `traefik.<segment_name>.frontend.headers.browserXSSFilter=true` | Same as `traefik.frontend.headers.browserXSSFilter` |
|
||||
| `traefik.<segment_name>.frontend.headers.contentSecurityPolicy=VALUE` | Same as `traefik.frontend.headers.contentSecurityPolicy` |
|
||||
| `traefik.<segment_name>.frontend.headers.contentTypeNosniff=true` | Same as `traefik.frontend.headers.contentTypeNosniff` |
|
||||
| `traefik.<segment_name>.frontend.headers.customBrowserXSSValue=VALUE` | Same as `traefik.frontend.headers.customBrowserXSSValue` |
|
||||
| `traefik.<segment_name>.frontend.headers.customFrameOptionsValue=VALUE` | Same as `traefik.frontend.headers.customFrameOptionsValue` |
|
||||
| `traefik.<segment_name>.frontend.headers.forceSTSHeader=false` | Same as `traefik.frontend.headers.forceSTSHeader` |
|
||||
| `traefik.<segment_name>.frontend.headers.frameDeny=false` | Same as `traefik.frontend.headers.frameDeny` |
|
||||
| `traefik.<segment_name>.frontend.headers.hostsProxyHeaders=EXPR` | Same as `traefik.frontend.headers.hostsProxyHeaders` |
|
||||
| `traefik.<segment_name>.frontend.headers.isDevelopment=false` | Same as `traefik.frontend.headers.isDevelopment` |
|
||||
| `traefik.<segment_name>.frontend.headers.publicKey=VALUE` | Same as `traefik.frontend.headers.publicKey` |
|
||||
| `traefik.<segment_name>.frontend.headers.referrerPolicy=VALUE` | Same as `traefik.frontend.headers.referrerPolicy` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLRedirect=true` | Same as `traefik.frontend.headers.SSLRedirect` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLTemporaryRedirect=true` | Same as `traefik.frontend.headers.SSLTemporaryRedirect` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLHost=HOST` | Same as `traefik.frontend.headers.SSLHost` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLForceHost=true` | Same as `traefik.frontend.headers.SSLForceHost` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLProxyHeaders=EXPR` | Same as `traefik.frontend.headers.SSLProxyHeaders=EXPR` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSSeconds=315360000` | Same as `traefik.frontend.headers.STSSeconds=315360000` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSIncludeSubdomains=true` | Same as `traefik.frontend.headers.STSIncludeSubdomains=true` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSPreload=true` | Same as `traefik.frontend.headers.STSPreload=true` |
|
||||
|
||||
!!! note
|
||||
If a label is defined both as a `container label` and a `segment label` (for example `traefik.<segment_name>.port=PORT` and `traefik.port=PORT` ), the `segment label` is used to defined the `<segment_name>` property (`port` in the example).
|
||||
|
||||
It's possible to mix `container labels` and `segment labels`, in this case `container labels` are used as default value for missing `segment labels` but no frontends are going to be created with the `container labels`.
|
||||
|
||||
More details in this [example](/user-guide/docker-and-lets-encrypt/#labels).
|
||||
|
||||
!!! warning
|
||||
When running inside a container, Traefik will need network access through:
|
||||
|
||||
`docker network connect <network> <traefik-container>`
|
||||
|
||||
## usebindportip
|
||||
|
||||
The default behavior of Traefik is to route requests to the IP/Port of the matching container.
|
||||
When setting `usebindportip` to true, you tell Traefik to use the IP/Port attached to the container's binding instead of the inner network IP/Port.
|
||||
|
||||
When used in conjunction with the `traefik.port` label (that tells Traefik to route requests to a specific port), Traefik tries to find a binding with `traefik.port` port to select the container. If it can't find such a binding, Traefik falls back on the internal network IP of the container, but still uses the `traefik.port` that is set in the label.
|
||||
|
||||
Below is a recap of the behavior of `usebindportip` in different situations.
|
||||
|
||||
| traefik.port label | Container's binding | Routes to |
|
||||
|--------------------|----------------------------------------------------|----------------|
|
||||
| - | - | IntIP:IntPort |
|
||||
| - | ExtPort:IntPort | IntIP:IntPort |
|
||||
| - | ExtIp:ExtPort:IntPort | ExtIp:ExtPort |
|
||||
| LblPort | - | IntIp:LblPort |
|
||||
| LblPort | ExtIp:ExtPort:LblPort | ExtIp:ExtPort |
|
||||
| LblPort | ExtIp:ExtPort:OtherPort | IntIp:LblPort |
|
||||
| LblPort | ExtIp1:ExtPort1:IntPort1 & ExtIp2:LblPort:IntPort2 | ExtIp2:LblPort |
|
||||
|
||||
!!! note
|
||||
In the above table, ExtIp stands for "external IP found in the binding", IntIp stands for "internal network container's IP", ExtPort stands for "external Port found in the binding", and IntPort stands for "internal network container's port."
|
||||
@@ -12,6 +12,12 @@ Traefik can be configured to use Amazon ECS as a provider.
|
||||
# Enable ECS Provider.
|
||||
[ecs]
|
||||
|
||||
# ECS Cluster Name.
|
||||
#
|
||||
# DEPRECATED - Please use `clusters`.
|
||||
#
|
||||
cluster = "default"
|
||||
|
||||
# ECS Clusters Name.
|
||||
#
|
||||
# Optional
|
||||
@@ -126,9 +132,9 @@ Traefik needs the following policy to read ECS information:
|
||||
}
|
||||
```
|
||||
|
||||
## Labels: overriding default behavior
|
||||
## Labels: overriding default behaviour
|
||||
|
||||
Labels can be used on task containers to override default behavior:
|
||||
Labels can be used on task containers to override default behaviour:
|
||||
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
@@ -146,8 +152,7 @@ Labels can be used on task containers to override default behavior:
|
||||
| `traefik.backend.circuitbreaker.expression=EXPR` | Creates a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||
| `traefik.backend.responseForwarding.flushInterval=10ms` | Defines the interval between two flushes when forwarding response from backend to client. |
|
||||
| `traefik.backend.healthcheck.path=/health` | Enables health check for the backend, hitting the container at `path`. |
|
||||
| `traefik.backend.healthcheck.interval=5s` | Defines the health check interval. (Default: 30s) |
|
||||
| `traefik.backend.healthcheck.timeout=3s` | Defines the health check request timeout. (Default: 5s) |
|
||||
| `traefik.backend.healthcheck.interval=1s` | Defines the health check interval. (Default: 30s) |
|
||||
| `traefik.backend.healthcheck.scheme=http` | Overrides the server URL scheme. |
|
||||
| `traefik.backend.healthcheck.port=8080` | Sets a different port for the health check. |
|
||||
| `traefik.backend.healthcheck.hostname=foobar.com` | Defines the health check hostname. |
|
||||
@@ -155,6 +160,7 @@ Labels can be used on task containers to override default behavior:
|
||||
| `traefik.backend.loadbalancer.method=drr` | Overrides the default `wrr` load balancer algorithm |
|
||||
| `traefik.backend.loadbalancer.stickiness=true` | Enables backend sticky sessions |
|
||||
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Sets the cookie manually name for sticky sessions |
|
||||
| `traefik.backend.loadbalancer.sticky=true` | Enables backend sticky sessions (DEPRECATED) |
|
||||
| `traefik.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
|
||||
| `traefik.backend.maxconn.extractorfunc=client.ip` | Sets the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication to this frontend in CSV format: `User:Hash,User:Hash` (DEPRECATED). |
|
||||
@@ -176,7 +182,7 @@ Labels can be used on task containers to override default behavior:
|
||||
| `traefik.frontend.auth.removeHeader=true` | If set to true, removes the Authorization header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.commonName=true` | Add the issuer.commonName field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.country=true` | Add the issuer.country field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.domainComponent=true` | Add the issuer.domainComponent field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.domainComponent=true` | Add the issuer.domainComponent field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.locality=true` | Add the issuer.locality field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.organization=true` | Add the issuer.organization field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.province=true` | Add the issuer.province field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
@@ -186,7 +192,7 @@ Labels can be used on task containers to override default behavior:
|
||||
| `traefik.frontend.passTLSClientCert.infos.sans=true` | Add the sans field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.commonName=true` | Add the subject.commonName field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.country=true` | Add the subject.country field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.domainComponent=true` | Add the subject.domainComponent field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.domainComponent=true` | Add the subject.domainComponent field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.locality=true` | Add the subject.locality field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.organization=true` | Add the subject.organization field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.province=true` | Add the subject.province field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
@@ -209,9 +215,7 @@ Labels can be used on task containers to override default behavior:
|
||||
| `traefik.frontend.redirect.permanent=true` | Returns 301 instead of 302. |
|
||||
| `traefik.frontend.rule=EXPR` | Overrides the default frontend rule. Default: `Host:{instance_name}.{domain}`. |
|
||||
| `traefik.frontend.whiteList.sourceRange=RANGE` | Sets a list of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.frontend.whiteList.ipStrategy=true` | Uses the default IPStrategy.<br>Can be used when there is an existing `clientIPStrategy` but you want the remote address for whitelisting. |
|
||||
| `traefik.frontend.whiteList.ipStrategy.depth=5` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| `traefik.frontend.whiteList.ipStrategy.excludedIPs=127.0.0.1` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| `traefik.frontend.whiteList.useXForwardedFor=true` | Uses `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
|
||||
### Custom Headers
|
||||
|
||||
@@ -313,9 +317,6 @@ Segment labels override the default behavior.
|
||||
| `traefik.<segment_name>.frontend.rule=EXP` | Same as `traefik.frontend.rule` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Same as `traefik.frontend.whiteList.sourceRange` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Same as `traefik.frontend.whiteList.useXForwardedFor` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy=true` | Same as `traefik.frontend.whiteList.ipStrategy` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy.depth=5` | Same as `traefik.frontend.whiteList.ipStrategy.depth` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy.excludedIPs=127.0.0.1` | Same as `traefik.frontend.whiteList.ipStrategy.excludedIPs` |
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
@@ -31,6 +31,16 @@ watch = true
|
||||
#
|
||||
prefix = "/traefik"
|
||||
|
||||
# Force to use API V3 (otherwise still use API V2)
|
||||
#
|
||||
# Deprecated
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
useAPIV3 = true
|
||||
|
||||
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
@@ -59,3 +69,7 @@ prefix = "/traefik"
|
||||
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
|
||||
|
||||
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure.
|
||||
|
||||
!!! note
|
||||
The option `useAPIV3` allows using Etcd API V3 only if it's set to true.
|
||||
This option is **deprecated** and API V2 won't be supported in the future.
|
||||
@@ -40,7 +40,6 @@ Traefik can be configured with a file.
|
||||
path = "/health"
|
||||
port = 88
|
||||
interval = "30s"
|
||||
timeout = "5s"
|
||||
scheme = "http"
|
||||
hostname = "myhost.com"
|
||||
[backends.backend1.healthcheck.headers]
|
||||
@@ -59,6 +58,11 @@ Traefik can be configured with a file.
|
||||
passHostHeader = true
|
||||
priority = 42
|
||||
|
||||
# Use frontends.frontend1.auth.basic below instead
|
||||
basicAuth = [
|
||||
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
||||
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
]
|
||||
[frontends.frontend1.passTLSClientCert]
|
||||
pem = true
|
||||
[frontends.frontend1.passTLSClientCert.infos]
|
||||
@@ -109,9 +113,7 @@ Traefik can be configured with a file.
|
||||
|
||||
[frontends.frontend1.whiteList]
|
||||
sourceRange = ["10.42.0.0/16", "152.89.1.33/32", "afed:be44::/16"]
|
||||
[frontends.frontend1.whiteList.IPStrategy]
|
||||
depth = 6
|
||||
excludedIPs = ["152.89.1.33/32", "afed:be44::/16"]
|
||||
useXForwardedFor = true
|
||||
|
||||
[frontends.frontend1.routes]
|
||||
[frontends.frontend1.routes.route0]
|
||||
@@ -164,13 +164,10 @@ The following general annotations are applicable on the Ingress object:
|
||||
| `traefik.ingress.kubernetes.io/request-modifier: AddPrefix: /users` | Adds a [request modifier](/basics/#modifiers) to the backend request. |
|
||||
| `traefik.ingress.kubernetes.io/rewrite-target: /users` | Replaces each matched Ingress path with the specified one, and adds the old path to the `X-Replaced-Path` header. |
|
||||
| `traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip` | Overrides the default frontend rule type. Only path-related matchers can be specified [(`Path`, `PathPrefix`, `PathStrip`, `PathPrefixStrip`)](/basics/#path-matcher-usage-guidelines).(5) |
|
||||
| `traefik.ingress.kubernetes.io/request-modifier: AddPrefix: /users` | Add a [request modifier](/basics/#modifiers) to the backend request. |
|
||||
| `traefik.ingress.kubernetes.io/service-weights: <YML>` | Set ingress backend weights specified as percentage or decimal numbers in YAML. (6) |
|
||||
| `traefik.ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"` | A comma-separated list of IP ranges permitted for access (7). |
|
||||
| `traefik.ingress.kubernetes.io/whiteList-ipstrategy=true` | Uses the default IPStrategy.<br>Can be used when there is an existing `clientIPStrategy` but you want the remote address for whitelisting. |
|
||||
| `traefik.ingress.kubernetes.io/whiteList-ipstrategy-depth=5` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| `traefik.ingress.kubernetes.io/whiteList-ipstrategy-excludedIPs=127.0.0. 1` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| `ingress.kubernetes.io/protocol: <NAME>` | Set the protocol Traefik will use to communicate with pods. Acceptable protocols: http,https,h2c |
|
||||
| `ingress.kubernetes.io/whitelist-x-forwarded-for: "true"` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
| `ingress.kubernetes.io/protocol:<NAME>` | Set the protocol Traefik will use to communicate with pods. Acceptable protocols: http,https,h2c |
|
||||
|
||||
<1> `traefik.ingress.kubernetes.io/app-root`:
|
||||
Non-root paths will not be affected by this annotation and handled normally.
|
||||
@@ -278,6 +275,7 @@ The following annotations are applicable on the Service object associated with a
|
||||
| Annotation | Description |
|
||||
|--------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.ingress.kubernetes.io/buffering: <YML>` | (1) See the [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.loadbalancer.sticky: "true"` | Enable backend sticky sessions (DEPRECATED). |
|
||||
| `traefik.ingress.kubernetes.io/affinity: "true"` | Enable backend sticky sessions. |
|
||||
| `traefik.ingress.kubernetes.io/circuit-breaker-expression: <expression>` | Set the circuit breaker expression for the backend. |
|
||||
| `traefik.ingress.kubernetes.io/responseforwarding-flushinterval: "10ms` | Defines the interval between two flushes when forwarding response from backend to client. |
|
||||
@@ -193,89 +193,87 @@ They may be specified on one of two levels: Application or service.
|
||||
|
||||
The following labels can be defined on Marathon applications. They adjust the behavior for the entire application.
|
||||
|
||||
| Label | Description |
|
||||
|---------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.domain` | Sets the default base domain used for the frontend rules. |
|
||||
| `traefik.enable=false` | Disables this container in Traefik. |
|
||||
| `traefik.port=80` | Registers this port. Useful when the container exposes multiples ports. |
|
||||
| `traefik.portIndex=1` | Registers port by index in the application's ports array. Useful when the application exposes multiple ports. |
|
||||
| `traefik.protocol=https` | Overrides the default `http` protocol. |
|
||||
| `traefik.weight=10` | Assigns this weight to the container. |
|
||||
| `traefik.backend=foo` | Overrides the application name by `foo` in the generated name of the backend. |
|
||||
| `traefik.backend.buffering.maxRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.maxResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.retryExpression=EXPR` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.circuitbreaker.expression=EXPR` | Creates a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||
| `traefik.backend.responseForwarding.flushInterval=10ms` | Defines the interval between two flushes when forwarding response from backend to client. |
|
||||
| `traefik.backend.healthcheck.path=/health` | Enables health check for the backend, hitting the container at `path`. |
|
||||
| `traefik.backend.healthcheck.interval=5s` | Defines the health check interval. (Default: 30s) |
|
||||
| `traefik.backend.healthcheck.timeout=3s` | Defines the health check request timeout. (Default: 5s) |
|
||||
| `traefik.backend.healthcheck.port=8080` | Sets a different port for the health check. |
|
||||
| `traefik.backend.healthcheck.scheme=http` | Overrides the server URL scheme. |
|
||||
| `traefik.backend.healthcheck.hostname=foobar.com` | Defines the health check hostname. |
|
||||
| `traefik.backend.healthcheck.headers=EXPR` | Defines the health check request headers <br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.backend.loadbalancer.method=drr` | Overrides the default `wrr` load balancer algorithm |
|
||||
| `traefik.backend.loadbalancer.stickiness=true` | Enables backend sticky sessions |
|
||||
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Sets the cookie name manually for sticky sessions |
|
||||
| `traefik.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
|
||||
| `traefik.backend.maxconn.extractorfunc=client.ip` | Sets the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication to this frontend in CSV format: `User:Hash,User:Hash` (DEPRECATED). |
|
||||
| `traefik.frontend.auth.basic.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
|
||||
| `traefik.frontend.auth.basic.users=EXPR` | Sets basic authentication to this frontend in CSV format: `User:Hash,User:Hash`. |
|
||||
| `traefik.frontend.auth.basic.usersFile=/path/.htpasswd` | Sets basic authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
|
||||
| `traefik.frontend.auth.digest.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
|
||||
| `traefik.frontend.auth.digest.users=EXPR` | Sets digest authentication to this frontend in CSV format: `User:Realm:Hash,User:Realm:Hash`. |
|
||||
| `traefik.frontend.auth.digest.usersFile=/path/.htdigest` | Sets digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
|
||||
| `traefik.frontend.auth.forward.address=https://example.com` | Sets the URL of the authentication server. |
|
||||
| `traefik.frontend.auth.forward.authResponseHeaders=EXPR` | Sets the forward authentication authResponseHeaders in CSV format: `X-Auth-User,X-Auth-Header` |
|
||||
| `traefik.frontend.auth.forward.tls.ca=/path/ca.pem` | Sets the Certificate Authority (CA) for the TLS connection with the authentication server. |
|
||||
| `traefik.frontend.auth.forward.tls.caOptional=true` | Checks the certificates if present but do not force to be signed by a specified Certificate Authority (CA). |
|
||||
| `traefik.frontend.auth.forward.tls.cert=/path/server.pem` | Sets the Certificate for the TLS connection with the authentication server. |
|
||||
| `traefik.frontend.auth.forward.tls.insecureSkipVerify=true` | If set to true invalid SSL certificates are accepted. |
|
||||
| `traefik.frontend.auth.forward.tls.key=/path/server.key` | Sets the Certificate for the TLS connection with the authentication server. |
|
||||
| `traefik.frontend.auth.forward.trustForwardHeader=true` | Trusts X-Forwarded-* headers. |
|
||||
| `traefik.frontend.auth.headerField=X-WebAuth-User` | Sets the header used to pass the authenticated user to the application. |
|
||||
| `traefik.frontend.auth.removeHeader=true` | If set to true, removes the Authorization header. |
|
||||
| `traefik.frontend.entryPoints=http,https` | Assigns this frontend to entry points `http` and `https`.<br>Overrides `defaultEntryPoints` |
|
||||
| `traefik.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.passHostHeader=true` | Forwards client `Host` header to the backend. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.commonName=true` | Add the issuer.commonName field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.country=true` | Add the issuer.country field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.domainComponent=true` | Add the issuer.domainComponent field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.locality=true` | Add the issuer.locality field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.organization=true` | Add the issuer.organization field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.province=true` | Add the issuer.province field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.serialNumber=true` | Add the issuer.serialNumber field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.notAfter=true` | Add the noAfter field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.notBefore=true` | Add the noBefore field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.sans=true` | Add the sans field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.commonName=true` | Add the subject.commonName field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.country=true` | Add the subject.country field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.domainComponent=true` | Add the subject.domainComponent field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.locality=true` | Add the subject.locality field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.organization=true`| Add the subject.organization field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.province=true` | Add the subject.province field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.serialNumber=true`| Add the subject.serialNumber field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.pem=true` | Pass the escaped pem in the `X-Forwarded-Ssl-Client-Cert` header. |
|
||||
| `traefik.frontend.passTLSCert=true` | Forwards TLS Client certificates to the backend. |
|
||||
| `traefik.frontend.priority=10` | Overrides default frontend priority |
|
||||
| `traefik.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint to this frontend (e.g. HTTPS) |
|
||||
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirects to another URL to this frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
||||
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirects to another URL to this frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
||||
| `traefik.frontend.redirect.permanent=true` | Returns 301 instead of 302. |
|
||||
| `traefik.frontend.rule=EXPR` | Overrides the default frontend rule. Default: `Host:{sub_domain}.{domain}`. |
|
||||
| `traefik.frontend.whiteList.sourceRange=RANGE` | Sets a list of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.frontend.whiteList.ipStrategy=true` | Uses the default IPStrategy.<br>Can be used when there is an existing `clientIPStrategy` but you want the remote address for whitelisting. |
|
||||
| `traefik.frontend.whiteList.ipStrategy.depth=5` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| `traefik.frontend.whiteList.ipStrategy.excludedIPs=127.0.0.1` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.domain` | Sets the default base domain used for the frontend rules. |
|
||||
| `traefik.enable=false` | Disables this container in Traefik. |
|
||||
| `traefik.port=80` | Registers this port. Useful when the container exposes multiples ports. |
|
||||
| `traefik.portIndex=1` | Registers port by index in the application's ports array. Useful when the application exposes multiple ports. |
|
||||
| `traefik.protocol=https` | Overrides the default `http` protocol. |
|
||||
| `traefik.weight=10` | Assigns this weight to the container. |
|
||||
| `traefik.backend=foo` | Overrides the application name by `foo` in the generated name of the backend. |
|
||||
| `traefik.backend.buffering.maxRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.maxResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.retryExpression=EXPR` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.circuitbreaker.expression=EXPR` | Creates a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||
| `traefik.backend.responseForwarding.flushInterval=10ms` | Defines the interval between two flushes when forwarding response from backend to client. |
|
||||
| `traefik.backend.healthcheck.path=/health` | Enables health check for the backend, hitting the container at `path`. |
|
||||
| `traefik.backend.healthcheck.interval=1s` | Defines the health check interval. (Default: 30s) |
|
||||
| `traefik.backend.healthcheck.port=8080` | Sets a different port for the health check. |
|
||||
| `traefik.backend.healthcheck.scheme=http` | Overrides the server URL scheme. |
|
||||
| `traefik.backend.healthcheck.hostname=foobar.com` | Defines the health check hostname. |
|
||||
| `traefik.backend.healthcheck.headers=EXPR` | Defines the health check request headers <br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.backend.loadbalancer.method=drr` | Overrides the default `wrr` load balancer algorithm |
|
||||
| `traefik.backend.loadbalancer.stickiness=true` | Enables backend sticky sessions |
|
||||
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Sets the cookie name manually for sticky sessions |
|
||||
| `traefik.backend.loadbalancer.sticky=true` | Enables backend sticky sessions (DEPRECATED) |
|
||||
| `traefik.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
|
||||
| `traefik.backend.maxconn.extractorfunc=client.ip` | Sets the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication to this frontend in CSV format: `User:Hash,User:Hash` (DEPRECATED). |
|
||||
| `traefik.frontend.auth.basic.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
|
||||
| `traefik.frontend.auth.basic.users=EXPR` | Sets basic authentication to this frontend in CSV format: `User:Hash,User:Hash`. |
|
||||
| `traefik.frontend.auth.basic.usersFile=/path/.htpasswd` | Sets basic authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
|
||||
| `traefik.frontend.auth.digest.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
|
||||
| `traefik.frontend.auth.digest.users=EXPR` | Sets digest authentication to this frontend in CSV format: `User:Realm:Hash,User:Realm:Hash`. |
|
||||
| `traefik.frontend.auth.digest.usersFile=/path/.htdigest` | Sets digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
|
||||
| `traefik.frontend.auth.forward.address=https://example.com` | Sets the URL of the authentication server. |
|
||||
| `traefik.frontend.auth.forward.authResponseHeaders=EXPR` | Sets the forward authentication authResponseHeaders in CSV format: `X-Auth-User,X-Auth-Header` |
|
||||
| `traefik.frontend.auth.forward.tls.ca=/path/ca.pem` | Sets the Certificate Authority (CA) for the TLS connection with the authentication server. |
|
||||
| `traefik.frontend.auth.forward.tls.caOptional=true` | Checks the certificates if present but do not force to be signed by a specified Certificate Authority (CA). |
|
||||
| `traefik.frontend.auth.forward.tls.cert=/path/server.pem` | Sets the Certificate for the TLS connection with the authentication server. |
|
||||
| `traefik.frontend.auth.forward.tls.insecureSkipVerify=true` | If set to true invalid SSL certificates are accepted. |
|
||||
| `traefik.frontend.auth.forward.tls.key=/path/server.key` | Sets the Certificate for the TLS connection with the authentication server. |
|
||||
| `traefik.frontend.auth.forward.trustForwardHeader=true` | Trusts X-Forwarded-* headers. |
|
||||
| `traefik.frontend.auth.headerField=X-WebAuth-User` | Sets the header used to pass the authenticated user to the application. |
|
||||
| `traefik.frontend.auth.removeHeader=true` | If set to true, removes the Authorization header. |
|
||||
| `traefik.frontend.entryPoints=http,https` | Assigns this frontend to entry points `http` and `https`.<br>Overrides `defaultEntryPoints` |
|
||||
| `traefik.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.passHostHeader=true` | Forwards client `Host` header to the backend. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.commonName=true` | Add the issuer.commonName field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.country=true` | Add the issuer.country field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.domainComponent=true` | Add the issuer.domainComponent field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.locality=true` | Add the issuer.locality field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.organization=true` | Add the issuer.organization field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.province=true` | Add the issuer.province field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.serialNumber=true` | Add the issuer.serialNumber field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.notAfter=true` | Add the noAfter field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.notBefore=true` | Add the noBefore field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.sans=true` | Add the sans field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.commonName=true` | Add the subject.commonName field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.country=true` | Add the subject.country field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.domainComponent=true` | Add the subject.domainComponent field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.locality=true` | Add the subject.locality field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.organization=true` | Add the subject.organization field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.province=true` | Add the subject.province field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.serialNumber=true` | Add the subject.serialNumber field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.pem=true` | Pass the escaped pem in the `X-Forwarded-Ssl-Client-Cert` header. |
|
||||
| `traefik.frontend.passTLSCert=true` | Forwards TLS Client certificates to the backend. |
|
||||
| `traefik.frontend.priority=10` | Overrides default frontend priority |
|
||||
| `traefik.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint to this frontend (e.g. HTTPS) |
|
||||
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirects to another URL to this frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
||||
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirects to another URL to this frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
||||
| `traefik.frontend.redirect.permanent=true` | Returns 301 instead of 302. |
|
||||
| `traefik.frontend.rule=EXPR` | Overrides the default frontend rule. Default: `Host:{sub_domain}.{domain}`. |
|
||||
| `traefik.frontend.whiteList.sourceRange=RANGE` | Sets a list of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.frontend.whiteList.useXForwardedFor=true` | Uses `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
@@ -318,36 +316,36 @@ You can define as many segments as ports exposed in an application.
|
||||
|
||||
Segment labels override the default behavior.
|
||||
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------------------------------|--------------------------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
|
||||
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
|
||||
| `traefik.<segment_name>.portIndex=1` | Same as `traefik.portIndex` |
|
||||
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
|
||||
| `traefik.<segment_name>.protocol=http` | Same as `traefik.protocol` |
|
||||
| `traefik.<segment_name>.weight=10` | Same as `traefik.weight` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic.removeHeader=true` | Same as `traefik.frontend.auth.basic.removeHeader` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic.users=EXPR` | Same as `traefik.frontend.auth.basic.users` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic.usersFile=/path/.htpasswd` | Same as `traefik.frontend.auth.basic.usersFile` |
|
||||
| `traefik.<segment_name>.frontend.auth.digest.removeHeader=true` | Same as `traefik.frontend.auth.digest.removeHeader` |
|
||||
| `traefik.<segment_name>.frontend.auth.digest.users=EXPR` | Same as `traefik.frontend.auth.digest.users` |
|
||||
| `traefik.<segment_name>.frontend.auth.digest.usersFile=/path/.htdigest` | Same as `traefik.frontend.auth.digest.usersFile` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.address=https://example.com` | Same as `traefik.frontend.auth.forward.address` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.authResponseHeaders=EXPR` | Same as `traefik.frontend.auth.forward.authResponseHeaders` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.ca=/path/ca.pem` | Same as `traefik.frontend.auth.forward.tls.ca` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.caOptional=true` | Same as `traefik.frontend.auth.forward.tls.caOptional` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.cert=/path/server.pem` | Same as `traefik.frontend.auth.forward.tls.cert` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.insecureSkipVerify=true` | Same as `traefik.frontend.auth.forward.tls.insecureSkipVerify` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.key=/path/server.key` | Same as `traefik.frontend.auth.forward.tls.key` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.trustForwardHeader=true` | Same as `traefik.frontend.auth.forward.trustForwardHeader` |
|
||||
| `traefik.<segment_name>.frontend.auth.headerField=X-WebAuth-User` | Same as `traefik.frontend.auth.headerField` |
|
||||
| `traefik.<segment_name>.frontend.auth.removeHeader=true` | Same as `traefik.frontend.auth.removeHeader` |
|
||||
| `traefik.<segment_name>.frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | Same as `traefik.frontend.errors.<name>.backend` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | Same as `traefik.frontend.errors.<name>.query` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.status=RANGE` | Same as `traefik.frontend.errors.<name>.status` |
|
||||
| `traefik.<segment_name>.frontend.passHostHeader=true` | Same as `traefik.frontend.passHostHeader` |
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------------------------------------|----------------------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
|
||||
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
|
||||
| `traefik.<segment_name>.portIndex=1` | Same as `traefik.portIndex` |
|
||||
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
|
||||
| `traefik.<segment_name>.protocol=http` | Same as `traefik.protocol` |
|
||||
| `traefik.<segment_name>.weight=10` | Same as `traefik.weight` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic.removeHeader=true` | Same as `traefik.frontend.auth.basic.removeHeader` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic.users=EXPR` | Same as `traefik.frontend.auth.basic.users` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic.usersFile=/path/.htpasswd` | Same as `traefik.frontend.auth.basic.usersFile` |
|
||||
| `traefik.<segment_name>.frontend.auth.digest.removeHeader=true` | Same as `traefik.frontend.auth.digest.removeHeader` |
|
||||
| `traefik.<segment_name>.frontend.auth.digest.users=EXPR` | Same as `traefik.frontend.auth.digest.users` |
|
||||
| `traefik.<segment_name>.frontend.auth.digest.usersFile=/path/.htdigest` | Same as `traefik.frontend.auth.digest.usersFile` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.address=https://example.com` | Same as `traefik.frontend.auth.forward.address` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.authResponseHeaders=EXPR` | Same as `traefik.frontend.auth.forward.authResponseHeaders` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.ca=/path/ca.pem` | Same as `traefik.frontend.auth.forward.tls.ca` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.caOptional=true` | Same as `traefik.frontend.auth.forward.tls.caOptional` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.cert=/path/server.pem` | Same as `traefik.frontend.auth.forward.tls.cert` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.insecureSkipVerify=true` | Same as `traefik.frontend.auth.forward.tls.insecureSkipVerify` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.key=/path/server.key` | Same as `traefik.frontend.auth.forward.tls.key` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.trustForwardHeader=true` | Same as `traefik.frontend.auth.forward.trustForwardHeader` |
|
||||
| `traefik.<segment_name>.frontend.auth.headerField=X-WebAuth-User` | Same as `traefik.frontend.auth.headerField` |
|
||||
| `traefik.<segment_name>.frontend.auth.removeHeader=true` | Same as `traefik.frontend.auth.removeHeader` |
|
||||
| `traefik.<segment_name>.frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | Same as `traefik.frontend.errors.<name>.backend` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | Same as `traefik.frontend.errors.<name>.query` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.status=RANGE` | Same as `traefik.frontend.errors.<name>.status` |
|
||||
| `traefik.<segment_name>.frontend.passHostHeader=true` | Same as `traefik.frontend.passHostHeader` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.issuer.commonName=true` | Same as `traefik.frontend.passTLSClientCert.infos.issuer.commonName` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.issuer.domainComponent=true` | Same as `traefik.frontend.passTLSClientCert.infos.issuer.domainComponent` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.issuer.country=true` | Same as `traefik.frontend.passTLSClientCert.infos.issuer.country` |
|
||||
@@ -355,32 +353,30 @@ Segment labels override the default behavior.
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.issuer.organization=true` | Same as `traefik.frontend.passTLSClientCert.infos.issuer.organization` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.issuer.province=true` | Same as `traefik.frontend.passTLSClientCert.infos.issuer.province` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.issuer.serialNumber=true` | Same as `traefik.frontend.passTLSClientCert.infos.issuer.serialNumber` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.notAfter=true` | Same as `traefik.frontend.passTLSClientCert.infos.notAfter` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.notBefore=true` | Same as `traefik.frontend.passTLSClientCert.infos.notBefore` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.sans=true` | Same as `traefik.frontend.passTLSClientCert.infos.sans` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.commonName=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.commonName` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.notAfter=true` | Same as `traefik.frontend.passTLSClientCert.infos.notAfter` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.notBefore=true` | Same as `traefik.frontend.passTLSClientCert.infos.notBefore` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.sans=true` | Same as `traefik.frontend.passTLSClientCert.infos.sans` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.commonName=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.commonName` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.domainComponent=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.domainComponent` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.country=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.country` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.locality=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.locality` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.organization=true`| Same as `traefik.frontend.passTLSClientCert.infos.subject.organization` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.province=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.province` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.serialNumber=true`| Same as `traefik.frontend.passTLSClientCert.infos.subject.serialNumber` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.pem=true` | Same as `traefik.frontend.passTLSClientCert.infos.pem` |
|
||||
| `traefik.<segment_name>.frontend.passTLSCert=true` | Same as `traefik.frontend.passTLSCert` |
|
||||
| `traefik.<segment_name>.frontend.priority=10` | Same as `traefik.frontend.priority` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.extractorFunc=EXP` | Same as `traefik.frontend.rateLimit.extractorFunc` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.period=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.period` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.average=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.average` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.burst=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.burst` |
|
||||
| `traefik.<segment_name>.frontend.redirect.entryPoint=https` | Same as `traefik.frontend.redirect.entryPoint` |
|
||||
| `traefik.<segment_name>.frontend.redirect.regex=^http://localhost/(.*)` | Same as `traefik.frontend.redirect.regex` |
|
||||
| `traefik.<segment_name>.frontend.redirect.replacement=http://mydomain/$1` | Same as `traefik.frontend.redirect.replacement` |
|
||||
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Same as `traefik.frontend.redirect.permanent` |
|
||||
| `traefik.<segment_name>.frontend.rule=EXP` | Same as `traefik.frontend.rule` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Same as `traefik.frontend.whiteList.sourceRange` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy=true` | Same as `traefik.frontend.whiteList.ipStrategy` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy.depth=5` | Same as `traefik.frontend.whiteList.ipStrategy.depth` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy.excludedIPs=127.0.0.1` | Same as `traefik.frontend.whiteList.ipStrategy.excludedIPs` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.country=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.country` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.locality=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.locality` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.organization=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.organization` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.province=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.province` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.serialNumber=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.serialNumber` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.pem=true` | Same as `traefik.frontend.passTLSClientCert.infos.pem` |
|
||||
| `traefik.<segment_name>.frontend.passTLSCert=true` | Same as `traefik.frontend.passTLSCert` |
|
||||
| `traefik.<segment_name>.frontend.priority=10` | Same as `traefik.frontend.priority` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.extractorFunc=EXP` | Same as `traefik.frontend.rateLimit.extractorFunc` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.period=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.period` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.average=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.average` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.burst=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.burst` |
|
||||
| `traefik.<segment_name>.frontend.redirect.entryPoint=https` | Same as `traefik.frontend.redirect.entryPoint` |
|
||||
| `traefik.<segment_name>.frontend.redirect.regex=^http://localhost/(.*)` | Same as `traefik.frontend.redirect.regex` |
|
||||
| `traefik.<segment_name>.frontend.redirect.replacement=http://mydomain/$1` | Same as `traefik.frontend.redirect.replacement` |
|
||||
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Same as `traefik.frontend.redirect.permanent` |
|
||||
| `traefik.<segment_name>.frontend.rule=EXP` | Same as `traefik.frontend.rule` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Same as `traefik.frontend.whiteList.sourceRange` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Same as `traefik.frontend.whiteList.useXForwardedFor` |
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
@@ -106,90 +106,87 @@ domain = "mesos.localhost"
|
||||
|
||||
The following labels can be defined on Mesos tasks. They adjust the behavior for the entire application.
|
||||
|
||||
| Label | Description |
|
||||
|---------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.domain` | Sets the default domain for the frontend rules. |
|
||||
| `traefik.enable=false` | Disables this container in Traefik. |
|
||||
| `traefik.port=80` | Registers this port. Useful when the application exposes multiple ports. |
|
||||
| `traefik.portName=web` | Registers port by name in the application's ports array. Useful when the application exposes multiple ports. |
|
||||
| `traefik.portIndex=1` | Registers port by index in the application's ports array. Useful when the application exposes multiple ports. |
|
||||
| `traefik.protocol=https` | Overrides the default `http` protocol |
|
||||
| `traefik.weight=10` | Assigns this weight to the container |
|
||||
| `traefik.backend=foo` | Overrides the task name by `foo` in the generated name of the backend. |
|
||||
| `traefik.backend.buffering.maxRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.maxResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.retryExpression=EXPR` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.circuitbreaker.expression=EXPR` | Creates a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||
| `traefik.backend.responseForwarding.flushInterval=10ms` | Defines the interval between two flushes when forwarding response from backend to client. |
|
||||
| `traefik.backend.healthcheck.path=/health` | Enables health check for the backend, hitting the container at `path`. |
|
||||
| `traefik.backend.healthcheck.interval=5s` | Defines the health check interval. (Default: 30s) |
|
||||
| `traefik.backend.healthcheck.timeout=3s` | Defines the health check request timeout. (Default: 5s) |
|
||||
| `traefik.backend.healthcheck.scheme=http` | Overrides the server URL scheme. |
|
||||
| `traefik.backend.healthcheck.port=8080` | Sets a different port for the health check. |
|
||||
| `traefik.backend.healthcheck.hostname=foobar.com` | Defines the health check hostname. |
|
||||
| `traefik.backend.healthcheck.headers=EXPR` | Defines the health check request headers <br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.backend.loadbalancer.method=drr` | Overrides the default `wrr` load balancer algorithm |
|
||||
| `traefik.backend.loadbalancer.stickiness=true` | Enables backend sticky sessions |
|
||||
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Sets the cookie manually name for sticky sessions |
|
||||
| `traefik.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
|
||||
| `traefik.backend.maxconn.extractorfunc=client.ip` | Sets the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication to this frontend in CSV format: `User:Hash,User:Hash` (DEPRECATED). |
|
||||
| `traefik.frontend.auth.basic.users=EXPR` | Sets basic authentication to this frontend in CSV format: `User:Hash,User:Hash`. |
|
||||
| `traefik.frontend.auth.basic.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
|
||||
| `traefik.frontend.auth.basic.usersFile=/path/.htpasswd` | Sets basic authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
|
||||
| `traefik.frontend.auth.digest.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
|
||||
| `traefik.frontend.auth.digest.users=EXPR` | Sets digest authentication to this frontend in CSV format: `User:Realm:Hash,User:Realm:Hash`. |
|
||||
| `traefik.frontend.auth.digest.usersFile=/path/.htdigest` | Sets digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
|
||||
| `traefik.frontend.auth.forward.address=https://example.com` | Sets the URL of the authentication server. |
|
||||
| `traefik.frontend.auth.forward.authResponseHeaders=EXPR` | Sets the forward authentication authResponseHeaders in CSV format: `X-Auth-User,X-Auth-Header` |
|
||||
| `traefik.frontend.auth.forward.tls.ca=/path/ca.pem` | Sets the Certificate Authority (CA) for the TLS connection with the authentication server. |
|
||||
| `traefik.frontend.auth.forward.tls.caOptional=true` | Checks the certificates if present but do not force to be signed by a specified Certificate Authority (CA). |
|
||||
| `traefik.frontend.auth.forward.tls.cert=/path/server.pem` | Sets the Certificate for the TLS connection with the authentication server. |
|
||||
| `traefik.frontend.auth.forward.tls.insecureSkipVerify=true` | If set to true invalid SSL certificates are accepted. |
|
||||
| `traefik.frontend.auth.forward.tls.key=/path/server.key` | Sets the Certificate for the TLS connection with the authentication server. |
|
||||
| `traefik.frontend.auth.forward.trustForwardHeader=true` | Trusts X-Forwarded-* headers. |
|
||||
| `traefik.frontend.auth.headerField=X-WebAuth-User` | Sets the header used to pass the authenticated user to the application. |
|
||||
| `traefik.frontend.auth.removeHeader=true` | If set to true, removes the Authorization header. |
|
||||
| `traefik.frontend.entryPoints=http,https` | Assigns this frontend to entry points `http` and `https`.<br>Overrides `defaultEntryPoints` |
|
||||
| `traefik.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.passHostHeader=true` | Forwards client `Host` header to the backend. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.commonName=true` | Add the issuer.commonName field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.country=true` | Add the issuer.country field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.domainComponent=true` | Add the issuer.domainComponent field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.locality=true` | Add the issuer.locality field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.organization=true` | Add the issuer.organization field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.province=true` | Add the issuer.province field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.serialNumber=true` | Add the issuer.serialNumber field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.notAfter=true` | Add the noAfter field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.notBefore=true` | Add the noBefore field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.sans=true` | Add the sans field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.commonName=true` | Add the subject.commonName field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.country=true` | Add the subject.country field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.domainComponent=true` | Add the subject.domainComponent field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.locality=true` | Add the subject.locality field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.organization=true`| Add the subject.organization field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.province=true` | Add the subject.province field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.serialNumber=true`| Add the subject.serialNumber field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.pem=true` | Pass the escaped pem in the `X-Forwarded-Ssl-Client-Cert` header. |
|
||||
| `traefik.frontend.passTLSCert=true` | Forwards TLS Client certificates to the backend. |
|
||||
| `traefik.frontend.priority=10` | Overrides default frontend priority |
|
||||
| `traefik.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint to this frontend (e.g. HTTPS) |
|
||||
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirects to another URL to this frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
||||
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirects to another URL to this frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
||||
| `traefik.frontend.redirect.permanent=true` | Returns 301 instead of 302. |
|
||||
| `traefik.frontend.rule=EXPR` | Overrides the default frontend rule. Default: `Host:{discovery_name}.{domain}`. |
|
||||
| `traefik.frontend.whiteList.sourceRange=RANGE` | Sets a list of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.frontend.whiteList.ipStrategy=true` | Uses the default IPStrategy.<br>Can be used when there is an existing `clientIPStrategy` but you want the remote address for whitelisting. |
|
||||
| `traefik.frontend.whiteList.ipStrategy.depth=5` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| `traefik.frontend.whiteList.ipStrategy.excludedIPs=127.0.0.1` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.domain` | Sets the default base domain for the frontend rules. |
|
||||
| `traefik.enable=false` | Disables this container in Traefik. |
|
||||
| `traefik.port=80` | Registers this port. Useful when the application exposes multiple ports. |
|
||||
| `traefik.portName=web` | Registers port by name in the application's ports array. Useful when the application exposes multiple ports. |
|
||||
| `traefik.portIndex=1` | Registers port by index in the application's ports array. Useful when the application exposes multiple ports. |
|
||||
| `traefik.protocol=https` | Overrides the default `http` protocol |
|
||||
| `traefik.weight=10` | Assigns this weight to the container |
|
||||
| `traefik.backend=foo` | Overrides the task name by `foo` in the generated name of the backend. |
|
||||
| `traefik.backend.buffering.maxRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.maxResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.retryExpression=EXPR` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.circuitbreaker.expression=EXPR` | Creates a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||
| `traefik.backend.responseForwarding.flushInterval=10ms` | Defines the interval between two flushes when forwarding response from backend to client. |
|
||||
| `traefik.backend.healthcheck.path=/health` | Enables health check for the backend, hitting the container at `path`. |
|
||||
| `traefik.backend.healthcheck.interval=1s` | Defines the health check interval. (Default: 30s) |
|
||||
| `traefik.backend.healthcheck.scheme=http` | Overrides the server URL scheme. |
|
||||
| `traefik.backend.healthcheck.port=8080` | Sets a different port for the health check. |
|
||||
| `traefik.backend.healthcheck.hostname=foobar.com` | Defines the health check hostname. |
|
||||
| `traefik.backend.healthcheck.headers=EXPR` | Defines the health check request headers <br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.backend.loadbalancer.method=drr` | Overrides the default `wrr` load balancer algorithm |
|
||||
| `traefik.backend.loadbalancer.stickiness=true` | Enables backend sticky sessions |
|
||||
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Sets the cookie manually name for sticky sessions |
|
||||
| `traefik.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
|
||||
| `traefik.backend.maxconn.extractorfunc=client.ip` | Sets the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication to this frontend in CSV format: `User:Hash,User:Hash` (DEPRECATED). |
|
||||
| `traefik.frontend.auth.basic.users=EXPR` | Sets basic authentication to this frontend in CSV format: `User:Hash,User:Hash`. |
|
||||
| `traefik.frontend.auth.basic.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
|
||||
| `traefik.frontend.auth.basic.usersFile=/path/.htpasswd` | Sets basic authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
|
||||
| `traefik.frontend.auth.digest.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
|
||||
| `traefik.frontend.auth.digest.users=EXPR` | Sets digest authentication to this frontend in CSV format: `User:Realm:Hash,User:Realm:Hash`. |
|
||||
| `traefik.frontend.auth.digest.usersFile=/path/.htdigest` | Sets digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
|
||||
| `traefik.frontend.auth.forward.address=https://example.com` | Sets the URL of the authentication server. |
|
||||
| `traefik.frontend.auth.forward.authResponseHeaders=EXPR` | Sets the forward authentication authResponseHeaders in CSV format: `X-Auth-User,X-Auth-Header` |
|
||||
| `traefik.frontend.auth.forward.tls.ca=/path/ca.pem` | Sets the Certificate Authority (CA) for the TLS connection with the authentication server. |
|
||||
| `traefik.frontend.auth.forward.tls.caOptional=true` | Checks the certificates if present but do not force to be signed by a specified Certificate Authority (CA). |
|
||||
| `traefik.frontend.auth.forward.tls.cert=/path/server.pem` | Sets the Certificate for the TLS connection with the authentication server. |
|
||||
| `traefik.frontend.auth.forward.tls.insecureSkipVerify=true` | If set to true invalid SSL certificates are accepted. |
|
||||
| `traefik.frontend.auth.forward.tls.key=/path/server.key` | Sets the Certificate for the TLS connection with the authentication server. |
|
||||
| `traefik.frontend.auth.forward.trustForwardHeader=true` | Trusts X-Forwarded-* headers. |
|
||||
| `traefik.frontend.auth.headerField=X-WebAuth-User` | Sets the header used to pass the authenticated user to the application. |
|
||||
| `traefik.frontend.auth.removeHeader=true` | If set to true, removes the Authorization header. |
|
||||
| `traefik.frontend.entryPoints=http,https` | Assigns this frontend to entry points `http` and `https`.<br>Overrides `defaultEntryPoints` |
|
||||
| `traefik.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.passHostHeader=true` | Forwards client `Host` header to the backend. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.commonName=true` | Add the issuer.commonName field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.country=true` | Add the issuer.country field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.domainComponent=true` | Add the issuer.domainComponent field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.locality=true` | Add the issuer.locality field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.organization=true` | Add the issuer.organization field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.province=true` | Add the issuer.province field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.serialNumber=true` | Add the issuer.serialNumber field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.notAfter=true` | Add the noAfter field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.notBefore=true` | Add the noBefore field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.sans=true` | Add the sans field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.commonName=true` | Add the subject.commonName field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.country=true` | Add the subject.country field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.domainComponent=true` | Add the subject.domainComponent field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.locality=true` | Add the subject.locality field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.organization=true` | Add the subject.organization field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.province=true` | Add the subject.province field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.serialNumber=true` | Add the subject.serialNumber field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.pem=true` | Pass the escaped pem in the `X-Forwarded-Ssl-Client-Cert` header. |
|
||||
| `traefik.frontend.passTLSCert=true` | Forwards TLS Client certificates to the backend. |
|
||||
| `traefik.frontend.priority=10` | Overrides default frontend priority |
|
||||
| `traefik.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint to this frontend (e.g. HTTPS) |
|
||||
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirects to another URL to this frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
||||
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirects to another URL to this frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
||||
| `traefik.frontend.redirect.permanent=true` | Returns 301 instead of 302. |
|
||||
| `traefik.frontend.rule=EXPR` | Overrides the default frontend rule. Default: `Host:{discovery_name}.{domain}`. |
|
||||
| `traefik.frontend.whiteList.sourceRange=RANGE` | Sets a list of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.frontend.whiteList.useXForwardedFor=true` | Uses `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
|
||||
### Custom Headers
|
||||
|
||||
@@ -233,62 +230,60 @@ Additionally, if a segment name matches a named port, that port will be used unl
|
||||
|
||||
Segment labels override the default behavior.
|
||||
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------------------------------------|-------------------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
|
||||
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
|
||||
| `traefik.<segment_name>.portIndex=1` | Same as `traefik.portIndex` |
|
||||
| `traefik.<segment_name>.portName=web` | Same as `traefik.portName` |
|
||||
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
|
||||
| `traefik.<segment_name>.protocol=http` | Same as `traefik.protocol` |
|
||||
| `traefik.<segment_name>.weight=10` | Same as `traefik.weight` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic.removeHeader=true` | Same as `traefik.frontend.auth.basic.removeHeader` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic.users=EXPR` | Same as `traefik.frontend.auth.basic.users` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic.usersFile=/path/.htpasswd` | Same as `traefik.frontend.auth.basic.usersFile` |
|
||||
| `traefik.<segment_name>.frontend.auth.digest.removeHeader=true` | Same as `traefik.frontend.auth.digest.removeHeader` |
|
||||
| `traefik.<segment_name>.frontend.auth.digest.users=EXPR` | Same as `traefik.frontend.auth.digest.users` |
|
||||
| `traefik.<segment_name>.frontend.auth.digest.usersFile=/path/.htdigest` | Same as `traefik.frontend.auth.digest.usersFile` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.address=https://example.com` | Same as `traefik.frontend.auth.forward.address` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.authResponseHeaders=EXPR` | Same as `traefik.frontend.auth.forward.authResponseHeaders` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.ca=/path/ca.pem` | Same as `traefik.frontend.auth.forward.tls.ca` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.caOptional=true` | Same as `traefik.frontend.auth.forward.tls.caOptional` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.cert=/path/server.pem` | Same as `traefik.frontend.auth.forward.tls.cert` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.insecureSkipVerify=true` | Same as `traefik.frontend.auth.forward.tls.insecureSkipVerify` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.key=/path/server.key` | Same as `traefik.frontend.auth.forward.tls.key` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.trustForwardHeader=true` | Same as `traefik.frontend.auth.forward.trustForwardHeader` |
|
||||
| `traefik.<segment_name>.frontend.auth.headerField=X-WebAuth-User` | Same as `traefik.frontend.auth.headerField` |
|
||||
| `traefik.<segment_name>.frontend.auth.removeHeader=true` | Same as `traefik.frontend.auth.removeHeader` |
|
||||
| `traefik.<segment_name>.frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | Same as `traefik.frontend.errors.<name>.backend` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | Same as `traefik.frontend.errors.<name>.query` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.status=RANGE` | Same as `traefik.frontend.errors.<name>.status` |
|
||||
| `traefik.<segment_name>.frontend.passHostHeader=true` | Same as `traefik.frontend.passHostHeader` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.notAfter=true` | Same as `traefik.frontend.passTLSClientCert.infos.notAfter` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.notBefore=true` | Same as `traefik.frontend.passTLSClientCert.infos.notBefore` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.sans=true` | Same as `traefik.frontend.passTLSClientCert.infos.sans` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.commonName=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.commonName` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.country=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.country` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.locality=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.locality` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.organization=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.organization` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.province=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.province` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.serialNumber=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.serialNumber` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.pem=true` | Same as `traefik.frontend.passTLSClientCert.infos.pem` |
|
||||
| `traefik.<segment_name>.frontend.passTLSCert=true` | Same as `traefik.frontend.passTLSCert` |
|
||||
| `traefik.<segment_name>.frontend.priority=10` | Same as `traefik.frontend.priority` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.extractorFunc=EXP` | Same as `traefik.frontend.rateLimit.extractorFunc` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.period=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.period` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.average=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.average` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.burst=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.burst` |
|
||||
| `traefik.<segment_name>.frontend.redirect.entryPoint=https` | Same as `traefik.frontend.redirect.entryPoint` |
|
||||
| `traefik.<segment_name>.frontend.redirect.regex=^http://localhost/(.*)` | Same as `traefik.frontend.redirect.regex` |
|
||||
| `traefik.<segment_name>.frontend.redirect.replacement=http://mydomain/$1` | Same as `traefik.frontend.redirect.replacement` |
|
||||
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Same as `traefik.frontend.redirect.permanent` |
|
||||
| `traefik.<segment_name>.frontend.rule=EXP` | Same as `traefik.frontend.rule` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Same as `traefik.frontend.whiteList.sourceRange` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy=true` | Same as `traefik.frontend.whiteList.ipStrategy` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy.depth=5` | Same as `traefik.frontend.whiteList.ipStrategy.depth` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy.excludedIPs=127.0.0.1` | Same as `traefik.frontend.whiteList.ipStrategy.excludedIPs` |
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------------------------------|------------------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
|
||||
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
|
||||
| `traefik.<segment_name>.portIndex=1` | Same as `traefik.portIndex` |
|
||||
| `traefik.<segment_name>.portName=web` | Same as `traefik.portName` |
|
||||
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
|
||||
| `traefik.<segment_name>.protocol=http` | Same as `traefik.protocol` |
|
||||
| `traefik.<segment_name>.weight=10` | Same as `traefik.weight` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic.removeHeader=true` | Same as `traefik.frontend.auth.basic.removeHeader` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic.users=EXPR` | Same as `traefik.frontend.auth.basic.users` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic.usersFile=/path/.htpasswd` | Same as `traefik.frontend.auth.basic.usersFile` |
|
||||
| `traefik.<segment_name>.frontend.auth.digest.removeHeader=true` | Same as `traefik.frontend.auth.digest.removeHeader` |
|
||||
| `traefik.<segment_name>.frontend.auth.digest.users=EXPR` | Same as `traefik.frontend.auth.digest.users` |
|
||||
| `traefik.<segment_name>.frontend.auth.digest.usersFile=/path/.htdigest` | Same as `traefik.frontend.auth.digest.usersFile` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.address=https://example.com` | Same as `traefik.frontend.auth.forward.address` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.authResponseHeaders=EXPR` | Same as `traefik.frontend.auth.forward.authResponseHeaders` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.ca=/path/ca.pem` | Same as `traefik.frontend.auth.forward.tls.ca` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.caOptional=true` | Same as `traefik.frontend.auth.forward.tls.caOptional` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.cert=/path/server.pem` | Same as `traefik.frontend.auth.forward.tls.cert` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.insecureSkipVerify=true` | Same as `traefik.frontend.auth.forward.tls.insecureSkipVerify` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.tls.key=/path/server.key` | Same as `traefik.frontend.auth.forward.tls.key` |
|
||||
| `traefik.<segment_name>.frontend.auth.forward.trustForwardHeader=true` | Same as `traefik.frontend.auth.forward.trustForwardHeader` |
|
||||
| `traefik.<segment_name>.frontend.auth.headerField=X-WebAuth-User` | Same as `traefik.frontend.auth.headerField` |
|
||||
| `traefik.<segment_name>.frontend.auth.removeHeader=true` | Same as `traefik.frontend.auth.removeHeader` |
|
||||
| `traefik.<segment_name>.frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | Same as `traefik.frontend.errors.<name>.backend` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | Same as `traefik.frontend.errors.<name>.query` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.status=RANGE` | Same as `traefik.frontend.errors.<name>.status` |
|
||||
| `traefik.<segment_name>.frontend.passHostHeader=true` | Same as `traefik.frontend.passHostHeader` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.notAfter=true` | Same as `traefik.frontend.passTLSClientCert.infos.notAfter` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.notBefore=true` | Same as `traefik.frontend.passTLSClientCert.infos.notBefore` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.sans=true` | Same as `traefik.frontend.passTLSClientCert.infos.sans` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.commonName=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.commonName` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.country=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.country` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.locality=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.locality` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.organization=true`| Same as `traefik.frontend.passTLSClientCert.infos.subject.organization`|
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.province=true` | Same as `traefik.frontend.passTLSClientCert.infos.subject.province` |
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.infos.subject.serialNumber=true`| Same as `traefik.frontend.passTLSClientCert.infos.subject.serialNumber`|
|
||||
| `traefik.<segment_name>.frontend.passTLSClientCert.pem=true` | Same as `traefik.frontend.passTLSClientCert.infos.pem` |
|
||||
| `traefik.<segment_name>.frontend.passTLSCert=true` | Same as `traefik.frontend.passTLSCert` |
|
||||
| `traefik.<segment_name>.frontend.priority=10` | Same as `traefik.frontend.priority` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.extractorFunc=EXP` | Same as `traefik.frontend.rateLimit.extractorFunc` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.period=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.period` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.average=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.average` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.burst=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.burst` |
|
||||
| `traefik.<segment_name>.frontend.redirect.entryPoint=https` | Same as `traefik.frontend.redirect.entryPoint` |
|
||||
| `traefik.<segment_name>.frontend.redirect.regex=^http://localhost/(.*)` | Same as `traefik.frontend.redirect.regex` |
|
||||
| `traefik.<segment_name>.frontend.redirect.replacement=http://mydomain/$1` | Same as `traefik.frontend.redirect.replacement` |
|
||||
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Same as `traefik.frontend.redirect.permanent` |
|
||||
| `traefik.<segment_name>.frontend.rule=EXP` | Same as `traefik.frontend.rule` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Same as `traefik.frontend.whiteList.sourceRange` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Same as `traefik.frontend.whiteList.useXForwardedFor` |
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
@@ -138,87 +138,85 @@ secretKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
|
||||
Labels can be used on task containers to override default behavior:
|
||||
|
||||
| Label | Description |
|
||||
|---------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.domain` | Sets the default base domain for the frontend rules. |
|
||||
| `traefik.enable=false` | Disables this container in Traefik. |
|
||||
| `traefik.port=80` | Registers this port. Useful when the container exposes multiple ports. |
|
||||
| `traefik.protocol=https` | Overrides the default `http` protocol. |
|
||||
| `traefik.weight=10` | Assigns this weight to the container. |
|
||||
| `traefik.backend=foo` | Overrides the service name by `foo` in the generated name of the backend. |
|
||||
| `traefik.backend.buffering.maxRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.maxResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.retryExpression=EXPR` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.circuitbreaker.expression=EXPR` | Creates a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||
| `traefik.backend.responseForwarding.flushInterval=10ms` | Defines the interval between two flushes when forwarding response from backend to client. |
|
||||
| `traefik.backend.healthcheck.path=/health` | Enables health check for the backend, hitting the container at `path`. |
|
||||
| `traefik.backend.healthcheck.interval=5s` | Defines the health check interval. |
|
||||
| `traefik.backend.healthcheck.timeout=3s ` | Defines the health check request timeout. |
|
||||
| `traefik.backend.healthcheck.port=8080` | Sets a different port for the health check. |
|
||||
| `traefik.backend.healthcheck.scheme=http` | Overrides the server URL scheme. |
|
||||
| `traefik.backend.healthcheck.hostname=foobar.com` | Defines the health check hostname. |
|
||||
| `traefik.backend.healthcheck.headers=EXPR` | Defines the health check request headers <br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.backend.loadbalancer.method=drr` | Overrides the default `wrr` load balancer algorithm |
|
||||
| `traefik.backend.loadbalancer.stickiness=true` | Enables backend sticky sessions |
|
||||
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Sets the cookie name manually for sticky sessions |
|
||||
| `traefik.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
|
||||
| `traefik.backend.maxconn.extractorfunc=client.ip` | Sets the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets the basic authentication to this frontend in CSV format: `User:Hash,User:Hash` (DEPRECATED). |
|
||||
| `traefik.frontend.auth.basic.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
|
||||
| `traefik.frontend.auth.basic.users=EXPR` | Sets the basic authentication to this frontend in CSV format: `User:Hash,User:Hash` . |
|
||||
| `traefik.frontend.auth.basic.usersFile=/path/.htpasswd` | Sets the basic authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
|
||||
| `traefik.frontend.auth.digest.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
|
||||
| `traefik.frontend.auth.digest.users=EXPR` | Sets the digest authentication to this frontend in CSV format: `User:Realm:Hash,User:Realm:Hash`. |
|
||||
| `traefik.frontend.auth.digest.usersFile=/path/.htdigest` | Sets the digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
|
||||
| `traefik.frontend.auth.forward.address=https://example.com` | Sets the URL of the authentication server. |
|
||||
| `traefik.frontend.auth.forward.authResponseHeaders=EXPR` | Sets the forward authentication authResponseHeaders in CSV format: `X-Auth-User,X-Auth-Header` |
|
||||
| `traefik.frontend.auth.forward.tls.ca=/path/ca.pem` | Sets the Certificate Authority (CA) for the TLS connection with the authentication server. |
|
||||
| `traefik.frontend.auth.forward.tls.caOptional=true` | Checks the certificates if present but do not force to be signed by a specified Certificate Authority (CA). |
|
||||
| `traefik.frontend.auth.forward.tls.cert=/path/server.pem` | Sets the Certificate for the TLS connection with the authentication server. |
|
||||
| `traefik.frontend.auth.forward.tls.insecureSkipVerify=true` | If set to true invalid SSL certificates are accepted. |
|
||||
| `traefik.frontend.auth.forward.tls.key=/path/server.key` | Sets the Certificate for the TLS connection with the authentication server. |
|
||||
| `traefik.frontend.auth.forward.trustForwardHeader=true` | Trusts X-Forwarded-* headers. |
|
||||
| `traefik.frontend.auth.headerField=X-WebAuth-User` | Sets the header used to pass the authenticated user to the application. |
|
||||
| `traefik.frontend.entryPoints=http,https` | Assigns this frontend to entry points `http` and `https`.<br>Overrides `defaultEntryPoints` |
|
||||
| `traefik.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.passHostHeader=true` | Forwards client `Host` header to the backend. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.commonName=true` | Add the issuer.commonName field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.country=true` | Add the issuer.country field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.domainComponent=true` | Add the issuer.domainComponent field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.locality=true` | Add the issuer.locality field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.organization=true` | Add the issuer.organization field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.province=true` | Add the issuer.province field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.serialNumber=true` | Add the issuer.serialNumber field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.notAfter=true` | Add the noAfter field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.notBefore=true` | Add the noBefore field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.sans=true` | Add the sans field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.commonName=true` | Add the subject.commonName field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.country=true` | Add the subject.country field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.domainComponent=true` | Add the subject.domainComponent field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.locality=true` | Add the subject.locality field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.organization=true`| Add the subject.organization field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.province=true` | Add the subject.province field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.serialNumber=true`| Add the subject.serialNumber field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.pem=true` | Pass the escaped pem in the `X-Forwarded-Ssl-Client-Cert` header. |
|
||||
| `traefik.frontend.passTLSCert=true` | Forwards TLS Client certificates to the backend. |
|
||||
| `traefik.frontend.priority=10` | Overrides default frontend priority |
|
||||
| `traefik.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint to this frontend (e.g. HTTPS) |
|
||||
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirects to another URL to this frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
||||
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirects to another URL to this frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
||||
| `traefik.frontend.redirect.permanent=true` | Returns 301 instead of 302. |
|
||||
| `traefik.frontend.rule=EXPR` | Overrides the default frontend rule. Default: `Host:{containerName}.{domain}` or `Host:{service}.{project_name}.{domain}` if you are using `docker-compose`. |
|
||||
| `traefik.frontend.whiteList.sourceRange=RANGE` | Sets a list of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access.<br>If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.frontend.whiteList.ipStrategy=true` | Uses the default IPStrategy.<br>Can be used when there is an existing `clientIPStrategy` but you want the remote address for whitelisting. |
|
||||
| `traefik.frontend.whiteList.ipStrategy.depth=5` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| `traefik.frontend.whiteList.ipStrategy.excludedIPs=127.0.0.1` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.domain` | Sets the default base domain for the frontend rules. |
|
||||
| `traefik.enable=false` | Disables this container in Traefik. |
|
||||
| `traefik.port=80` | Registers this port. Useful when the container exposes multiple ports. |
|
||||
| `traefik.protocol=https` | Overrides the default `http` protocol. |
|
||||
| `traefik.weight=10` | Assigns this weight to the container. |
|
||||
| `traefik.backend=foo` | Overrides the service name by `foo` in the generated name of the backend. |
|
||||
| `traefik.backend.buffering.maxRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.maxResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.retryExpression=EXPR` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.circuitbreaker.expression=EXPR` | Creates a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||
| `traefik.backend.responseForwarding.flushInterval=10ms` | Defines the interval between two flushes when forwarding response from backend to client. |
|
||||
| `traefik.backend.healthcheck.path=/health` | Enables health check for the backend, hitting the container at `path`. |
|
||||
| `traefik.backend.healthcheck.interval=1s` | Defines the health check interval. |
|
||||
| `traefik.backend.healthcheck.port=8080` | Sets a different port for the health check. |
|
||||
| `traefik.backend.healthcheck.scheme=http` | Overrides the server URL scheme. |
|
||||
| `traefik.backend.healthcheck.hostname=foobar.com` | Defines the health check hostname. |
|
||||
| `traefik.backend.healthcheck.headers=EXPR` | Defines the health check request headers <br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.backend.loadbalancer.method=drr` | Overrides the default `wrr` load balancer algorithm |
|
||||
| `traefik.backend.loadbalancer.stickiness=true` | Enables backend sticky sessions |
|
||||
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Sets the cookie name manually for sticky sessions |
|
||||
| `traefik.backend.loadbalancer.sticky=true` | Enables backend sticky sessions (DEPRECATED) |
|
||||
| `traefik.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
|
||||
| `traefik.backend.maxconn.extractorfunc=client.ip` | Sets the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets the basic authentication to this frontend in CSV format: `User:Hash,User:Hash` (DEPRECATED). |
|
||||
| `traefik.frontend.auth.basic.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
|
||||
| `traefik.frontend.auth.basic.users=EXPR` | Sets the basic authentication to this frontend in CSV format: `User:Hash,User:Hash` . |
|
||||
| `traefik.frontend.auth.basic.usersFile=/path/.htpasswd` | Sets the basic authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
|
||||
| `traefik.frontend.auth.digest.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
|
||||
| `traefik.frontend.auth.digest.users=EXPR` | Sets the digest authentication to this frontend in CSV format: `User:Realm:Hash,User:Realm:Hash`. |
|
||||
| `traefik.frontend.auth.digest.usersFile=/path/.htdigest` | Sets the digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
|
||||
| `traefik.frontend.auth.forward.address=https://example.com` | Sets the URL of the authentication server. |
|
||||
| `traefik.frontend.auth.forward.authResponseHeaders=EXPR` | Sets the forward authentication authResponseHeaders in CSV format: `X-Auth-User,X-Auth-Header` |
|
||||
| `traefik.frontend.auth.forward.tls.ca=/path/ca.pem` | Sets the Certificate Authority (CA) for the TLS connection with the authentication server. |
|
||||
| `traefik.frontend.auth.forward.tls.caOptional=true` | Checks the certificates if present but do not force to be signed by a specified Certificate Authority (CA). |
|
||||
| `traefik.frontend.auth.forward.tls.cert=/path/server.pem` | Sets the Certificate for the TLS connection with the authentication server. |
|
||||
| `traefik.frontend.auth.forward.tls.insecureSkipVerify=true` | If set to true invalid SSL certificates are accepted. |
|
||||
| `traefik.frontend.auth.forward.tls.key=/path/server.key` | Sets the Certificate for the TLS connection with the authentication server. |
|
||||
| `traefik.frontend.auth.forward.trustForwardHeader=true` | Trusts X-Forwarded-* headers. |
|
||||
| `traefik.frontend.auth.headerField=X-WebAuth-User` | Sets the header used to pass the authenticated user to the application. |
|
||||
| `traefik.frontend.entryPoints=http,https` | Assigns this frontend to entry points `http` and `https`.<br>Overrides `defaultEntryPoints` |
|
||||
| `traefik.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.passHostHeader=true` | Forwards client `Host` header to the backend. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.commonName=true` | Add the issuer.commonName field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.country=true` | Add the issuer.country field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.domainComponent=true` | Add the issuer.domainComponent field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.locality=true` | Add the issuer.locality field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.organization=true` | Add the issuer.organization field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.province=true` | Add the issuer.province field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.issuer.serialNumber=true` | Add the issuer.serialNumber field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.notAfter=true` | Add the noAfter field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.notBefore=true` | Add the noBefore field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.sans=true` | Add the sans field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.commonName=true` | Add the subject.commonName field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.country=true` | Add the subject.country field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.domainComponent=true` | Add the subject.domainComponent field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.locality=true` | Add the subject.locality field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.organization=true` | Add the subject.organization field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.province=true` | Add the subject.province field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.infos.subject.serialNumber=true` | Add the subject.serialNumber field in a escaped client infos in the `X-Forwarded-Ssl-Client-Cert-Infos` header. |
|
||||
| `traefik.frontend.passTLSClientCert.pem=true` | Pass the escaped pem in the `X-Forwarded-Ssl-Client-Cert` header. |
|
||||
| `traefik.frontend.passTLSCert=true` | Forwards TLS Client certificates to the backend. |
|
||||
| `traefik.frontend.priority=10` | Overrides default frontend priority |
|
||||
| `traefik.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint to this frontend (e.g. HTTPS) |
|
||||
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirects to another URL to this frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
||||
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirects to another URL to this frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
||||
| `traefik.frontend.redirect.permanent=true` | Returns 301 instead of 302. |
|
||||
| `traefik.frontend.rule=EXPR` | Overrides the default frontend rule. Default: `Host:{containerName}.{domain}` or `Host:{service}.{project_name}.{domain}` if you are using `docker-compose`. |
|
||||
| `traefik.frontend.whiteList.sourceRange=RANGE` | Sets a list of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access.<br>If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.frontend.whiteList.useXForwardedFor=true` | Uses `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
@@ -318,9 +316,7 @@ Segment labels override the default behavior.
|
||||
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Same as `traefik.frontend.redirect.permanent` |
|
||||
| `traefik.<segment_name>.frontend.rule=EXP` | Same as `traefik.frontend.rule` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Same as `traefik.frontend.whiteList.sourceRange` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy=true` | Same as `traefik.frontend.whiteList.ipStrategy` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy.depth=5` | Same as `traefik.frontend.whiteList.ipStrategy.depth` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.ipStrategy.excludedIPs=127.0.0.1` | Same as `traefik.frontend.whiteList.ipStrategy.excludedIPs` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Same as `traefik.frontend.whiteList.useXForwardedFor` |
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
@@ -94,39 +94,38 @@ curl -X PUT \
|
||||
|
||||
Labels, set through extensions or the property manager, can be used on services to override default behavior.
|
||||
|
||||
| Label | Description |
|
||||
|-----------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.enable=false` | Disable this container in Traefik |
|
||||
| `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||
| `traefik.servicefabric.groupname` | Group all services with the same name into a single backend in Traefik |
|
||||
| `traefik.servicefabric.groupweight` | Set the weighting of the current services nodes in the backend group |
|
||||
| `traefik.servicefabric.enablelabeloverrides` | Toggle whether labels can be overridden using the Service Fabric Property Manager API |
|
||||
| `traefik.backend.healthcheck.path=/health` | Enable health check for the backend, hitting the container at `path`. |
|
||||
| `traefik.backend.healthcheck.port=8080` | Allow to use a different port for the health check. |
|
||||
| `traefik.backend.healthcheck.interval=5s` | Define the health check interval. |
|
||||
| `traefik.backend.healthcheck.timeout=3s` | Define the health check request timeout. |
|
||||
| `traefik.backend.healthcheck.hostname=foobar.com` | Define the health check hostname. |
|
||||
| `traefik.backend.healthcheck.headers=EXPR` | Define the health check request headers <br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm |
|
||||
| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions |
|
||||
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
|
||||
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
|
||||
| `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |
|
||||
| `traefik.backend.weight=10` | Assign this weight to the container |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
||||
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`.<br>Overrides `defaultEntryPoints` |
|
||||
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
|
||||
| `traefik.frontend.passTLSCert=true` | Forward TLS Client certificates to the backend. |
|
||||
| `traefik.frontend.priority=10` | Override default frontend priority |
|
||||
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
|
||||
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
||||
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
||||
| `traefik.frontend.redirect.permanent=true` | Return 301 instead of 302. |
|
||||
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Defaults to SF address. |
|
||||
| `traefik.frontend.whiteList.sourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access.<br>If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.frontend.whiteList.ipStrategy=true` | Uses the default IPStrategy.<br>Can be used when there is an existing `clientIPStrategy` but you want the remote address for whitelisting. |
|
||||
| `traefik.frontend.whiteList.ipStrategy.depth=5` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| `traefik.frontend.whiteList.ipStrategy.excludedIPs=127.0.0.1` | See [whitelist](/configuration/entrypoints/#white-listing) |
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.enable=false` | Disable this container in Traefik |
|
||||
| `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||
| `traefik.servicefabric.groupname` | Group all services with the same name into a single backend in Traefik |
|
||||
| `traefik.servicefabric.groupweight` | Set the weighting of the current services nodes in the backend group |
|
||||
| `traefik.servicefabric.enablelabeloverrides` | Toggle whether labels can be overridden using the Service Fabric Property Manager API |
|
||||
| `traefik.servicefabric.endpointname` | Specify the name of the endpoint |
|
||||
| `traefik.backend.healthcheck.path=/health` | Enable health check for the backend, hitting the container at `path`. |
|
||||
| `traefik.backend.healthcheck.port=8080` | Allow to use a different port for the health check. |
|
||||
| `traefik.backend.healthcheck.interval=1s` | Define the health check interval. |
|
||||
| `traefik.backend.healthcheck.hostname=foobar.com` | Define the health check hostname. |
|
||||
| `traefik.backend.healthcheck.headers=EXPR` | Define the health check request headers <br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm |
|
||||
| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions |
|
||||
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
|
||||
| `traefik.backend.loadbalancer.sticky=true` | Enable backend sticky sessions (DEPRECATED) |
|
||||
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
|
||||
| `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |
|
||||
| `traefik.backend.weight=10` | Assign this weight to the container |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
||||
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`.<br>Overrides `defaultEntryPoints` |
|
||||
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
|
||||
| `traefik.frontend.passTLSCert=true` | Forward TLS Client certificates to the backend. |
|
||||
| `traefik.frontend.priority=10` | Override default frontend priority |
|
||||
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
|
||||
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
||||
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
||||
| `traefik.frontend.redirect.permanent=true` | Return 301 instead of 302. |
|
||||
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Defaults to SF address. |
|
||||
| `traefik.frontend.whiteList.sourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access.<br>If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.frontend.whiteList.useXForwardedFor=true` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
|
||||
### Custom Headers
|
||||
|
||||
503
docs/configuration/backends/web.md
Normal file
@@ -0,0 +1,503 @@
|
||||
# Web Provider
|
||||
|
||||
!!! danger "DEPRECATED"
|
||||
The web provider is deprecated, please use the [api](/configuration/api.md), the [ping](/configuration/ping.md), the [metrics](/configuration/metrics) and the [rest](/configuration/backends/rest.md) provider.
|
||||
|
||||
Traefik can be configured:
|
||||
|
||||
- using a RESTful api.
|
||||
- to use a monitoring system (like Prometheus, DataDog or StatD, ...).
|
||||
- to expose a Web Dashboard.
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
# Enable Web Provider.
|
||||
[web]
|
||||
|
||||
# Web administration port.
|
||||
#
|
||||
# Required
|
||||
# Default: ":8080"
|
||||
#
|
||||
address = ":8080"
|
||||
|
||||
# SSL certificate and key used.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# certFile = "traefik.crt"
|
||||
# keyFile = "traefik.key"
|
||||
|
||||
# Set REST API to read-only mode.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
readOnly = true
|
||||
|
||||
# Set the root path for webui and API
|
||||
#
|
||||
# Deprecated
|
||||
# Optional
|
||||
#
|
||||
# path = "/mypath"
|
||||
#
|
||||
```
|
||||
|
||||
## Web UI
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### Authentication
|
||||
|
||||
!!! note
|
||||
The `/ping` path of the API is excluded from authentication (since 1.4).
|
||||
|
||||
#### Basic Authentication
|
||||
|
||||
Passwords can be encoded in MD5, SHA1 and BCrypt: you can use `htpasswd` to generate those ones.
|
||||
|
||||
Users can be specified directly in the TOML file, or indirectly by referencing an external file;
|
||||
if both are provided, the two are merged, with external file contents having precedence.
|
||||
|
||||
```toml
|
||||
[web]
|
||||
# ...
|
||||
|
||||
# To enable basic auth on the webui with 2 user/pass: test:test and test2:test2
|
||||
[web.auth.basic]
|
||||
users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
|
||||
usersFile = "/path/to/.htpasswd"
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
#### Digest Authentication
|
||||
|
||||
You can use `htdigest` to generate those ones.
|
||||
|
||||
Users can be specified directly in the TOML file, or indirectly by referencing an external file;
|
||||
if both are provided, the two are merged, with external file contents having precedence
|
||||
|
||||
```toml
|
||||
[web]
|
||||
# ...
|
||||
|
||||
# To enable digest auth on the webui with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
|
||||
[web.auth.digest]
|
||||
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
|
||||
usersFile = "/path/to/.htdigest"
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
|
||||
## Metrics
|
||||
|
||||
You can enable Traefik to export internal metrics to different monitoring systems.
|
||||
|
||||
### Prometheus
|
||||
|
||||
```toml
|
||||
[web]
|
||||
# ...
|
||||
|
||||
# To enable Traefik to export internal metrics to Prometheus
|
||||
[web.metrics.prometheus]
|
||||
|
||||
# Buckets for latency metrics
|
||||
#
|
||||
# Optional
|
||||
# Default: [0.1, 0.3, 1.2, 5]
|
||||
buckets=[0.1,0.3,1.2,5.0]
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
### DataDog
|
||||
|
||||
```toml
|
||||
[web]
|
||||
# ...
|
||||
|
||||
# DataDog metrics exporter type
|
||||
[web.metrics.datadog]
|
||||
|
||||
# DataDog's address.
|
||||
#
|
||||
# Required
|
||||
# Default: "localhost:8125"
|
||||
#
|
||||
address = "localhost:8125"
|
||||
|
||||
# DataDog push interval
|
||||
#
|
||||
# Optional
|
||||
# Default: "10s"
|
||||
#
|
||||
pushinterval = "10s"
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
### StatsD
|
||||
|
||||
```toml
|
||||
[web]
|
||||
# ...
|
||||
|
||||
# StatsD metrics exporter type
|
||||
[web.metrics.statsd]
|
||||
|
||||
# StatD's address.
|
||||
#
|
||||
# Required
|
||||
# Default: "localhost:8125"
|
||||
#
|
||||
address = "localhost:8125"
|
||||
|
||||
# StatD push interval
|
||||
#
|
||||
# Optional
|
||||
# Default: "10s"
|
||||
#
|
||||
pushinterval = "10s"
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
### InfluxDB
|
||||
|
||||
```toml
|
||||
[web]
|
||||
# ...
|
||||
|
||||
# InfluxDB metrics exporter type
|
||||
[web.metrics.influxdb]
|
||||
|
||||
# InfluxDB's address.
|
||||
#
|
||||
# Required
|
||||
# Default: "localhost:8089"
|
||||
#
|
||||
address = "localhost:8089"
|
||||
|
||||
# InfluxDB's address protocol (udp or http)
|
||||
#
|
||||
# Required
|
||||
# Default: "udp"
|
||||
#
|
||||
protocol = "udp"
|
||||
|
||||
# InfluxDB push interval
|
||||
#
|
||||
# Optional
|
||||
# Default: "10s"
|
||||
#
|
||||
pushinterval = "10s"
|
||||
|
||||
# InfluxDB database used when protocol is http
|
||||
#
|
||||
# Optional
|
||||
# Default: ""
|
||||
#
|
||||
database = ""
|
||||
|
||||
# InfluxDB retention policy used when protocol is http
|
||||
#
|
||||
# Optional
|
||||
# Default: ""
|
||||
#
|
||||
retentionpolicy = ""
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
## Statistics
|
||||
|
||||
```toml
|
||||
[web]
|
||||
# ...
|
||||
|
||||
# Enable more detailed statistics.
|
||||
[web.statistics]
|
||||
|
||||
# Number of recent errors logged.
|
||||
#
|
||||
# Default: 10
|
||||
#
|
||||
recentErrors = 10
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
|
||||
## API
|
||||
|
||||
| Path | Method | Description |
|
||||
|-----------------------------------------------------------------|:-------------:|----------------------------------------------------------------------------------------------------|
|
||||
| `/` | `GET` | Provides a simple HTML frontend of Traefik |
|
||||
| `/ping` | `GET`, `HEAD` | A simple endpoint to check for Traefik process liveness. Return a code `200` with the content: `OK` |
|
||||
| `/health` | `GET` | JSON health metrics |
|
||||
| `/api` | `GET` | Configuration for all providers |
|
||||
| `/api/providers` | `GET` | Providers |
|
||||
| `/api/providers/{provider}` | `GET`, `PUT` | Get or update provider |
|
||||
| `/api/providers/{provider}/backends` | `GET` | List backends |
|
||||
| `/api/providers/{provider}/backends/{backend}` | `GET` | Get backend |
|
||||
| `/api/providers/{provider}/backends/{backend}/servers` | `GET` | List servers in backend |
|
||||
| `/api/providers/{provider}/backends/{backend}/servers/{server}` | `GET` | Get a server in a backend |
|
||||
| `/api/providers/{provider}/frontends` | `GET` | List frontends |
|
||||
| `/api/providers/{provider}/frontends/{frontend}` | `GET` | Get a frontend |
|
||||
| `/api/providers/{provider}/frontends/{frontend}/routes` | `GET` | List routes in a frontend |
|
||||
| `/api/providers/{provider}/frontends/{frontend}/routes/{route}` | `GET` | Get a route in a frontend |
|
||||
| `/metrics` | `GET` | Export internal metrics |
|
||||
|
||||
### Example
|
||||
|
||||
#### Ping
|
||||
|
||||
```shell
|
||||
curl -sv "http://localhost:8080/ping"
|
||||
```
|
||||
```shell
|
||||
* Trying ::1...
|
||||
* Connected to localhost (::1) port 8080 (\#0)
|
||||
> GET /ping HTTP/1.1
|
||||
> Host: localhost:8080
|
||||
> User-Agent: curl/7.43.0
|
||||
> Accept: */*
|
||||
>
|
||||
< HTTP/1.1 200 OK
|
||||
< Date: Thu, 25 Aug 2016 01:35:36 GMT
|
||||
< Content-Length: 2
|
||||
< Content-Type: text/plain; charset=utf-8
|
||||
<
|
||||
* Connection \#0 to host localhost left intact
|
||||
OK
|
||||
```
|
||||
|
||||
#### Health
|
||||
|
||||
```shell
|
||||
curl -s "http://localhost:8080/health" | jq .
|
||||
```
|
||||
```json
|
||||
{
|
||||
// Traefik PID
|
||||
"pid": 2458,
|
||||
// Traefik server uptime (formated time)
|
||||
"uptime": "39m6.885931127s",
|
||||
// Traefik server uptime in seconds
|
||||
"uptime_sec": 2346.885931127,
|
||||
// current server date
|
||||
"time": "2015-10-07 18:32:24.362238909 +0200 CEST",
|
||||
// current server date in seconds
|
||||
"unixtime": 1444235544,
|
||||
// count HTTP response status code in realtime
|
||||
"status_code_count": {
|
||||
"502": 1
|
||||
},
|
||||
// count HTTP response status code since Traefik started
|
||||
"total_status_code_count": {
|
||||
"200": 7,
|
||||
"404": 21,
|
||||
"502": 13
|
||||
},
|
||||
// count HTTP response
|
||||
"count": 1,
|
||||
// count HTTP response
|
||||
"total_count": 41,
|
||||
// sum of all response time (formated time)
|
||||
"total_response_time": "35.456865605s",
|
||||
// sum of all response time in seconds
|
||||
"total_response_time_sec": 35.456865605,
|
||||
// average response time (formated time)
|
||||
"average_response_time": "864.8016ms",
|
||||
// average response time in seconds
|
||||
"average_response_time_sec": 0.8648016000000001,
|
||||
|
||||
// request statistics [requires --web.statistics to be set]
|
||||
// ten most recent requests with 4xx and 5xx status codes
|
||||
"recent_errors": [
|
||||
{
|
||||
// status code
|
||||
"status_code": 500,
|
||||
// description of status code
|
||||
"status": "Internal Server Error",
|
||||
// request HTTP method
|
||||
"method": "GET",
|
||||
// request host name
|
||||
"host": "localhost",
|
||||
// request path
|
||||
"path": "/path",
|
||||
// RFC 3339 formatted date/time
|
||||
"time": "2016-10-21T16:59:15.418495872-07:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Provider configurations
|
||||
|
||||
```shell
|
||||
curl -s "http://localhost:8080/api" | jq .
|
||||
```
|
||||
```json
|
||||
{
|
||||
"file": {
|
||||
"frontends": {
|
||||
"frontend2": {
|
||||
"routes": {
|
||||
"test_2": {
|
||||
"rule": "Path:/test"
|
||||
}
|
||||
},
|
||||
"backend": "backend1"
|
||||
},
|
||||
"frontend1": {
|
||||
"routes": {
|
||||
"test_1": {
|
||||
"rule": "Host:test.localhost"
|
||||
}
|
||||
},
|
||||
"backend": "backend2"
|
||||
}
|
||||
},
|
||||
"backends": {
|
||||
"backend2": {
|
||||
"loadBalancer": {
|
||||
"method": "drr"
|
||||
},
|
||||
"servers": {
|
||||
"server2": {
|
||||
"weight": 2,
|
||||
"URL": "http://172.17.0.5:80"
|
||||
},
|
||||
"server1": {
|
||||
"weight": 1,
|
||||
"url": "http://172.17.0.4:80"
|
||||
}
|
||||
}
|
||||
},
|
||||
"backend1": {
|
||||
"loadBalancer": {
|
||||
"method": "wrr"
|
||||
},
|
||||
"circuitBreaker": {
|
||||
"expression": "NetworkErrorRatio() > 0.5"
|
||||
},
|
||||
"servers": {
|
||||
"server2": {
|
||||
"weight": 1,
|
||||
"url": "http://172.17.0.3:80"
|
||||
},
|
||||
"server1": {
|
||||
"weight": 10,
|
||||
"url": "http://172.17.0.2:80"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Deprecation compatibility
|
||||
|
||||
#### Address
|
||||
|
||||
As the web provider is deprecated, you can handle the `Address` option like this:
|
||||
|
||||
```toml
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
[entryPoints.foo]
|
||||
address = ":8082"
|
||||
|
||||
[entryPoints.bar]
|
||||
address = ":8083"
|
||||
|
||||
[ping]
|
||||
entryPoint = "foo"
|
||||
|
||||
[api]
|
||||
entryPoint = "bar"
|
||||
```
|
||||
|
||||
In the above example, you would access a regular path, administration panel, and health-check as follows:
|
||||
|
||||
* Regular path: `http://hostname:80/path`
|
||||
* Admin Panel: `http://hostname:8083/`
|
||||
* Ping URL: `http://hostname:8082/ping`
|
||||
|
||||
In the above example, it is _very_ important to create a named dedicated entry point, and do **not** include it in `defaultEntryPoints`.
|
||||
Otherwise, you are likely to expose _all_ services via that entry point.
|
||||
|
||||
#### Path
|
||||
|
||||
As the web provider is deprecated, you can handle the `Path` option like this:
|
||||
|
||||
```toml
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
[entryPoints.foo]
|
||||
address = ":8080"
|
||||
|
||||
[entryPoints.bar]
|
||||
address = ":8081"
|
||||
|
||||
# Activate API and Dashboard
|
||||
[api]
|
||||
entryPoint = "bar"
|
||||
dashboard = true
|
||||
|
||||
[file]
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://127.0.0.1:8081"
|
||||
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
entryPoints = ["foo"]
|
||||
backend = "backend1"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "PathPrefixStrip:/yourprefix;PathPrefix:/yourprefix"
|
||||
```
|
||||
|
||||
#### Authentication
|
||||
|
||||
As the web provider is deprecated, you can handle the `auth` option like this:
|
||||
|
||||
```toml
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
[entryPoints.foo]
|
||||
address=":8080"
|
||||
[entryPoints.foo.auth]
|
||||
[entryPoints.foo.auth.basic]
|
||||
users = [
|
||||
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
||||
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
]
|
||||
|
||||
[api]
|
||||
entrypoint="foo"
|
||||
```
|
||||
|
||||
For more information, see [entry points](/configuration/entrypoints/) .
|
||||
@@ -3,6 +3,19 @@
|
||||
## Main Section
|
||||
|
||||
```toml
|
||||
# DEPRECATED - for general usage instruction see [lifeCycle.graceTimeOut].
|
||||
#
|
||||
# If both the deprecated option and the new one are given, the deprecated one
|
||||
# takes precedence.
|
||||
# A value of zero is equivalent to omitting the parameter, causing
|
||||
# [lifeCycle.graceTimeOut] to be effective. Pass zero to the new option in
|
||||
# order to disable the grace period.
|
||||
#
|
||||
# Optional
|
||||
# Default: "0s"
|
||||
#
|
||||
# graceTimeOut = "10s"
|
||||
|
||||
# Enable debug mode.
|
||||
# This will install HTTP handlers to expose Go expvars under /debug/vars and
|
||||
# pprof profiling data under /debug/pprof/.
|
||||
@@ -63,8 +76,22 @@
|
||||
# Default: ["http"]
|
||||
#
|
||||
# defaultEntryPoints = ["http", "https"]
|
||||
|
||||
# Allow the use of 0 as server weight.
|
||||
# - false: a weight 0 means internally a weight of 1.
|
||||
# - true: a weight 0 means internally a weight of 0 (a server with a weight of 0 is removed from the available servers).
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# AllowMinWeightZero = true
|
||||
```
|
||||
|
||||
- `graceTimeOut`: Duration to give active requests a chance to finish before Traefik stops.
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
**Note:** in this time frame no new requests are accepted.
|
||||
|
||||
- `providersThrottleDuration`: Providers throttle duration: minimum duration in seconds between 2 events from providers before applying a new configuration.
|
||||
It avoids unnecessary reloads if multiples events are sent in a short amount of time.
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
@@ -83,7 +110,7 @@ If you encounter 'too many open files' errors, you can either increase this valu
|
||||
- `defaultEntryPoints`: Entrypoints to be used by frontends that do not specify any entrypoint.
|
||||
Each frontend can specify its own entrypoints.
|
||||
|
||||
- `keepTrailingSlash`: Tells Traefik whether it should keep the trailing slashes that might be present in the paths of incoming requests (true), or if it should redirect to the slashless version of the URL (default behavior: false)
|
||||
- `keepTrailingSlash`: Tells Træfik whether it should keep the trailing slashes that might be present in the paths of incoming requests (true), or if it should redirect to the slashless version of the URL (default behavior: false)
|
||||
|
||||
!!! note
|
||||
Beware that the value of `keepTrailingSlash` can have a significant impact on the way your frontend rules are interpreted.
|
||||
@@ -103,6 +130,189 @@ Each frontend can specify its own entrypoints.
|
||||
| http://foo.com/path | true | Path:/path | Proceeds with the request |
|
||||
|
||||
|
||||
## Constraints
|
||||
|
||||
In a micro-service architecture, with a central service discovery, setting constraints limits Traefik scope to a smaller number of routes.
|
||||
|
||||
Traefik filters services according to service attributes/tags set in your providers.
|
||||
|
||||
Supported filters:
|
||||
|
||||
- `tag`
|
||||
|
||||
### Simple
|
||||
|
||||
```toml
|
||||
# Simple matching constraint
|
||||
constraints = ["tag==api"]
|
||||
|
||||
# Simple mismatching constraint
|
||||
constraints = ["tag!=api"]
|
||||
|
||||
# Globbing
|
||||
constraints = ["tag==us-*"]
|
||||
```
|
||||
|
||||
### Multiple
|
||||
|
||||
```toml
|
||||
# Multiple constraints
|
||||
# - "tag==" must match with at least one tag
|
||||
# - "tag!=" must match with none of tags
|
||||
constraints = ["tag!=us-*", "tag!=asia-*"]
|
||||
```
|
||||
|
||||
### provider-specific
|
||||
|
||||
Supported Providers:
|
||||
|
||||
- Docker
|
||||
- Consul K/V
|
||||
- BoltDB
|
||||
- Zookeeper
|
||||
- ECS
|
||||
- Etcd
|
||||
- Consul Catalog
|
||||
- Rancher
|
||||
- Marathon
|
||||
- Kubernetes (using a provider-specific mechanism based on label selectors)
|
||||
|
||||
```toml
|
||||
# Provider-specific constraint
|
||||
[consulCatalog]
|
||||
# ...
|
||||
constraints = ["tag==api"]
|
||||
|
||||
# Provider-specific constraint
|
||||
[marathon]
|
||||
# ...
|
||||
constraints = ["tag==api", "tag!=v*-beta"]
|
||||
```
|
||||
|
||||
|
||||
## Custom Error pages
|
||||
|
||||
Custom error pages can be returned, in lieu of the default, according to frontend-configured ranges of HTTP Status codes.
|
||||
|
||||
In the example below, if a 503 status is returned from the frontend "website", the custom error page at http://2.3.4.5/503.html is returned with the actual status code set in the HTTP header.
|
||||
|
||||
!!! note
|
||||
The `503.html` page itself is not hosted on Traefik, but some other infrastructure.
|
||||
|
||||
```toml
|
||||
[frontends]
|
||||
[frontends.website]
|
||||
backend = "website"
|
||||
[frontends.website.errors]
|
||||
[frontends.website.errors.network]
|
||||
status = ["500-599"]
|
||||
backend = "error"
|
||||
query = "/{status}.html"
|
||||
[frontends.website.routes.website]
|
||||
rule = "Host: website.mydomain.com"
|
||||
|
||||
[backends]
|
||||
[backends.website]
|
||||
[backends.website.servers.website]
|
||||
url = "https://1.2.3.4"
|
||||
[backends.error]
|
||||
[backends.error.servers.error]
|
||||
url = "http://2.3.4.5"
|
||||
```
|
||||
|
||||
In the above example, the error page rendered was based on the status code.
|
||||
Instead, the query parameter can also be set to some generic error page like so: `query = "/500s.html"`
|
||||
|
||||
Now the `500s.html` error page is returned for the configured code range.
|
||||
The configured status code ranges are inclusive; that is, in the above example, the `500s.html` page will be returned for status codes `500` through, and including, `599`.
|
||||
|
||||
|
||||
## Rate limiting
|
||||
|
||||
Rate limiting can be configured per frontend.
|
||||
Multiple sets of rates can be added to each frontend, but the time periods must be unique.
|
||||
|
||||
```toml
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
# ...
|
||||
[frontends.frontend1.ratelimit]
|
||||
extractorfunc = "client.ip"
|
||||
[frontends.frontend1.ratelimit.rateset.rateset1]
|
||||
period = "10s"
|
||||
average = 100
|
||||
burst = 200
|
||||
[frontends.frontend1.ratelimit.rateset.rateset2]
|
||||
period = "3s"
|
||||
average = 5
|
||||
burst = 10
|
||||
```
|
||||
|
||||
In the above example, frontend1 is configured to limit requests by the client's ip address.
|
||||
An average of 100 requests every 10 seconds is allowed and an average of 5 requests every 3 seconds.
|
||||
These can "burst" up to 200 and 10 in each period respectively.
|
||||
|
||||
Valid values for `extractorfunc` are:
|
||||
* `client.ip`
|
||||
* `request.host`
|
||||
* `request.header.<header name>`
|
||||
|
||||
## Buffering
|
||||
|
||||
In some cases request/buffering can be enabled for a specific backend.
|
||||
By enabling this, Traefik will read the entire request into memory (possibly buffering large requests into disk) and will reject requests that are over a specified limit.
|
||||
This may help services deal with large data (multipart/form-data for example) more efficiently and should minimise time spent when sending data to a backend server.
|
||||
|
||||
For more information please check [oxy/buffer](http://godoc.org/github.com/vulcand/oxy/buffer) documentation.
|
||||
|
||||
Example configuration:
|
||||
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.buffering]
|
||||
maxRequestBodyBytes = 10485760
|
||||
memRequestBodyBytes = 2097152
|
||||
maxResponseBodyBytes = 10485760
|
||||
memResponseBodyBytes = 2097152
|
||||
retryExpression = "IsNetworkError() && Attempts() <= 2"
|
||||
```
|
||||
|
||||
## Retry Configuration
|
||||
|
||||
```toml
|
||||
# Enable retry sending request if network error
|
||||
[retry]
|
||||
|
||||
# Number of attempts
|
||||
#
|
||||
# Optional
|
||||
# Default: (number servers in backend) -1
|
||||
#
|
||||
# attempts = 3
|
||||
```
|
||||
|
||||
|
||||
## Health Check Configuration
|
||||
|
||||
```toml
|
||||
# Enable custom health check options.
|
||||
[healthcheck]
|
||||
|
||||
# Set the default health check interval.
|
||||
#
|
||||
# Optional
|
||||
# Default: "30s"
|
||||
#
|
||||
# interval = "30s"
|
||||
```
|
||||
|
||||
- `interval` set the default health check interval.
|
||||
Will only be effective if health check paths are defined.
|
||||
Given provider-specific support, the value may be overridden on a per-backend basis.
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
|
||||
## Life Cycle
|
||||
|
||||
Controls the behavior of Traefik during the shutdown phase.
|
||||
@@ -214,6 +424,29 @@ If zero, no timeout exists.
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
|
||||
|
||||
### Idle Timeout (deprecated)
|
||||
|
||||
Use [respondingTimeouts](/configuration/commons/#responding-timeouts) instead of `idleTimeout`.
|
||||
In the case both settings are configured, the deprecated option will be overwritten.
|
||||
|
||||
`idleTimeout` is the maximum amount of time an idle (keep-alive) connection will remain idle before closing itself.
|
||||
This is set to enforce closing of stale client connections.
|
||||
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
|
||||
```toml
|
||||
# idleTimeout
|
||||
#
|
||||
# DEPRECATED - see [respondingTimeouts] section.
|
||||
#
|
||||
# Optional
|
||||
# Default: "180s"
|
||||
#
|
||||
idleTimeout = "360s"
|
||||
```
|
||||
|
||||
## Host Resolver
|
||||
|
||||
`hostResolver` are used for request host matching process.
|
||||
@@ -252,7 +485,7 @@ Refer to [ACME configuration](/configuration/acme) for more information.
|
||||
!!! warning
|
||||
For advanced users only.
|
||||
|
||||
Supported by all providers except: File Provider, Rest Provider and DynamoDB Provider.
|
||||
Supported by all providers except: File Provider, Web Provider and DynamoDB Provider.
|
||||
|
||||
```toml
|
||||
[provider_name]
|
||||
@@ -301,6 +534,39 @@ Example:
|
||||
{{end}}
|
||||
```
|
||||
|
||||
### Using ping for an external Load-balancer rotation health check
|
||||
## Pass TLS Client Cert
|
||||
|
||||
If you are running Traefik behind an external Load-balancer, and want to configure rotation health check on the Load-balancer to take a Traefik instance out of rotation gracefully, you can configure [lifecycle.requestAcceptGraceTimeout](/configuration/commons.md#life-cycle) and the ping endpoint will return `503` response on traefik server termination, so that the Load-balancer can take the terminating traefik instance out of rotation, before it stops responding.
|
||||
```toml
|
||||
# Pass the escaped client cert infos selected below in a `X-Forwarded-Ssl-Client-Cert-Infos` header.
|
||||
[frontends.frontend1.passTLSClientCert]
|
||||
pem = true
|
||||
[frontends.frontend1.passTLSClientCert.infos]
|
||||
notBefore = true
|
||||
notAfter = true
|
||||
[frontends.frontend1.passTLSClientCert.infos.subject]
|
||||
country = true
|
||||
domainComponent = true
|
||||
province = true
|
||||
locality = true
|
||||
organization = true
|
||||
commonName = true
|
||||
serialNumber = true
|
||||
[frontends.frontend1.passTLSClientCert.infos.issuer]
|
||||
country = true
|
||||
domainComponent = true
|
||||
province = true
|
||||
locality = true
|
||||
organization = true
|
||||
commonName = true
|
||||
serialNumber = true
|
||||
```
|
||||
|
||||
Pass TLS Client Cert `pem` defines if the escaped pem is added to a `X-Forwarded-Ssl-Client-Cert` header.
|
||||
Pass TLS Client Cert `infos` defines how the certificate data are added to a `X-Forwarded-Ssl-Client-Cert-Infos` header.
|
||||
|
||||
The following example shows an unescaped result that uses all the available fields:
|
||||
If there are more than one certificate, they are separated by a `;`
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
531
docs/configuration/entrypoints.md
Normal file
@@ -0,0 +1,531 @@
|
||||
# Entry Points Definition
|
||||
|
||||
## Reference
|
||||
|
||||
### TOML
|
||||
|
||||
```toml
|
||||
defaultEntryPoints = ["http", "https"]
|
||||
|
||||
# ...
|
||||
# ...
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
compress = true
|
||||
|
||||
[entryPoints.http.whitelist]
|
||||
sourceRange = ["10.42.0.0/16", "152.89.1.33/32", "afed:be44::/16"]
|
||||
useXForwardedFor = true
|
||||
|
||||
[entryPoints.http.tls]
|
||||
minVersion = "VersionTLS12"
|
||||
cipherSuites = [
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_RSA_WITH_AES_256_GCM_SHA384"
|
||||
]
|
||||
[[entryPoints.http.tls.certificates]]
|
||||
certFile = "path/to/my.cert"
|
||||
keyFile = "path/to/my.key"
|
||||
[[entryPoints.http.tls.certificates]]
|
||||
certFile = "path/to/other.cert"
|
||||
keyFile = "path/to/other.key"
|
||||
# ...
|
||||
[entryPoints.http.tls.clientCA]
|
||||
files = ["path/to/ca1.crt", "path/to/ca2.crt"]
|
||||
optional = false
|
||||
|
||||
[entryPoints.http.redirect]
|
||||
entryPoint = "https"
|
||||
regex = "^http://localhost/(.*)"
|
||||
replacement = "http://mydomain/$1"
|
||||
permanent = true
|
||||
|
||||
[entryPoints.http.auth]
|
||||
headerField = "X-WebAuth-User"
|
||||
[entryPoints.http.auth.basic]
|
||||
removeHeader = true
|
||||
users = [
|
||||
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
||||
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
]
|
||||
usersFile = "/path/to/.htpasswd"
|
||||
[entryPoints.http.auth.digest]
|
||||
removeHeader = true
|
||||
users = [
|
||||
"test:traefik:a2688e031edb4be6a3797f3882655c05",
|
||||
"test2:traefik:518845800f9e2bfb1f1f740ec24f074e",
|
||||
]
|
||||
usersFile = "/path/to/.htdigest"
|
||||
[entryPoints.http.auth.forward]
|
||||
address = "https://authserver.com/auth"
|
||||
trustForwardHeader = true
|
||||
authResponseHeaders = ["X-Auth-User"]
|
||||
[entryPoints.http.auth.forward.tls]
|
||||
ca = "path/to/local.crt"
|
||||
caOptional = true
|
||||
cert = "path/to/foo.cert"
|
||||
key = "path/to/foo.key"
|
||||
insecureSkipVerify = true
|
||||
|
||||
[entryPoints.http.proxyProtocol]
|
||||
insecure = true
|
||||
trustedIPs = ["10.10.10.1", "10.10.10.2"]
|
||||
|
||||
[entryPoints.http.forwardedHeaders]
|
||||
trustedIPs = ["10.10.10.1", "10.10.10.2"]
|
||||
|
||||
[entryPoints.https]
|
||||
# ...
|
||||
```
|
||||
|
||||
### CLI
|
||||
|
||||
For more information about the CLI, see the documentation about [Traefik command](/basics/#traefik).
|
||||
|
||||
```shell
|
||||
--entryPoints='Name:http Address::80'
|
||||
--entryPoints='Name:https Address::443 TLS'
|
||||
```
|
||||
|
||||
!!! note
|
||||
Whitespace is used as option separator and `,` is used as value separator for the list.
|
||||
The names of the options are case-insensitive.
|
||||
|
||||
In compose file the entrypoint syntax is different. Notice how quotes are used:
|
||||
|
||||
```yaml
|
||||
traefik:
|
||||
image: traefik
|
||||
command:
|
||||
- --defaultentrypoints=powpow
|
||||
- "--entryPoints=Name:powpow Address::42 Compress:true"
|
||||
```
|
||||
or
|
||||
```yaml
|
||||
traefik:
|
||||
image: traefik
|
||||
command: --defaultentrypoints=powpow --entryPoints='Name:powpow Address::42 Compress:true'
|
||||
```
|
||||
|
||||
#### All available options:
|
||||
|
||||
```ini
|
||||
Name:foo
|
||||
Address::80
|
||||
TLS:/my/path/foo.cert,/my/path/foo.key;/my/path/goo.cert,/my/path/goo.key;/my/path/hoo.cert,/my/path/hoo.key
|
||||
TLS
|
||||
TLS.MinVersion:VersionTLS11
|
||||
TLS.CipherSuites:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384
|
||||
TLS.SniStrict:true
|
||||
TLS.DefaultCertificate.Cert:path/to/foo.cert
|
||||
TLS.DefaultCertificate.Key:path/to/foo.key
|
||||
CA:car
|
||||
CA.Optional:true
|
||||
Redirect.EntryPoint:https
|
||||
Redirect.Regex:http://localhost/(.*)
|
||||
Redirect.Replacement:http://mydomain/$1
|
||||
Redirect.Permanent:true
|
||||
Compress:true
|
||||
WhiteList.SourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16
|
||||
WhiteList.UseXForwardedFor:true
|
||||
ProxyProtocol.TrustedIPs:192.168.0.1
|
||||
ProxyProtocol.Insecure:true
|
||||
ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24
|
||||
Auth.Basic.Users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0
|
||||
Auth.Basic.Removeheader:true
|
||||
Auth.Digest.Users:test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e
|
||||
Auth.Digest.Removeheader:true
|
||||
Auth.HeaderField:X-WebAuth-User
|
||||
Auth.Forward.Address:https://authserver.com/auth
|
||||
Auth.Forward.AuthResponseHeaders:X-Auth,X-Test,X-Secret
|
||||
Auth.Forward.TrustForwardHeader:true
|
||||
Auth.Forward.TLS.CA:path/to/local.crt
|
||||
Auth.Forward.TLS.CAOptional:true
|
||||
Auth.Forward.TLS.Cert:path/to/foo.cert
|
||||
Auth.Forward.TLS.Key:path/to/foo.key
|
||||
Auth.Forward.TLS.InsecureSkipVerify:true
|
||||
```
|
||||
|
||||
## Basic
|
||||
|
||||
```toml
|
||||
# Entrypoints definition
|
||||
#
|
||||
# Default:
|
||||
# [entryPoints]
|
||||
# [entryPoints.http]
|
||||
# address = ":80"
|
||||
#
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
```
|
||||
|
||||
## Redirect HTTP to HTTPS
|
||||
|
||||
To redirect an http entrypoint to an https entrypoint (with SNI support).
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.redirect]
|
||||
entryPoint = "https"
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
certFile = "integration/fixtures/https/snitest.com.cert"
|
||||
keyFile = "integration/fixtures/https/snitest.com.key"
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
certFile = "integration/fixtures/https/snitest.org.cert"
|
||||
keyFile = "integration/fixtures/https/snitest.org.key"
|
||||
```
|
||||
|
||||
!!! note
|
||||
Please note that `regex` and `replacement` do not have to be set in the `redirect` structure if an entrypoint is defined for the redirection (they will not be used in this case).
|
||||
|
||||
## Rewriting URL
|
||||
|
||||
To redirect an entrypoint rewriting the URL.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.redirect]
|
||||
regex = "^http://localhost/(.*)"
|
||||
replacement = "http://mydomain/$1"
|
||||
```
|
||||
|
||||
!!! note
|
||||
Please note that `regex` and `replacement` do not have to be set in the `redirect` structure if an `entrypoint` is defined for the redirection (they will not be used in this case).
|
||||
|
||||
Care should be taken when defining replacement expand variables: `$1x` is equivalent to `${1x}`, not `${1}x` (see [Regexp.Expand](https://golang.org/pkg/regexp/#Regexp.Expand)), so use `${1}` syntax.
|
||||
|
||||
Regular expressions and replacements can be tested using online tools such as [Go Playground](https://play.golang.org/p/mWU9p-wk2ru) or the [Regex101](https://regex101.com/r/58sIgx/2).
|
||||
|
||||
## TLS
|
||||
|
||||
### Static Certificates
|
||||
|
||||
Define an entrypoint with SNI support.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
certFile = "integration/fixtures/https/snitest.com.cert"
|
||||
keyFile = "integration/fixtures/https/snitest.com.key"
|
||||
```
|
||||
|
||||
!!! note
|
||||
If an empty TLS configuration is provided, default self-signed certificates are generated.
|
||||
|
||||
|
||||
### Dynamic Certificates
|
||||
|
||||
If you need to add or remove TLS certificates while Traefik is started, Dynamic TLS certificates are supported using the [file provider](/configuration/backends/file).
|
||||
|
||||
|
||||
## TLS Mutual Authentication
|
||||
|
||||
TLS Mutual Authentication can be `optional` or not.
|
||||
If it's `optional`, Traefik will authorize connection with certificates not signed by a specified Certificate Authority (CA).
|
||||
Otherwise, Traefik will only accept clients that present a certificate signed by a specified Certificate Authority (CA).
|
||||
`ClientCAFiles` can be configured with multiple `CA:s` in the same file or use multiple files containing one or several `CA:s`.
|
||||
The `CA:s` has to be in PEM format.
|
||||
|
||||
By default, `ClientCAFiles` is not optional, all clients will be required to present a valid cert.
|
||||
The requirement will apply to all server certs in the entrypoint.
|
||||
|
||||
In the example below both `snitest.com` and `snitest.org` will require client certs
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
[entryPoints.https.tls.ClientCA]
|
||||
files = ["tests/clientca1.crt", "tests/clientca2.crt"]
|
||||
optional = false
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
certFile = "integration/fixtures/https/snitest.com.cert"
|
||||
keyFile = "integration/fixtures/https/snitest.com.key"
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
certFile = "integration/fixtures/https/snitest.org.cert"
|
||||
keyFile = "integration/fixtures/https/snitest.org.key"
|
||||
```
|
||||
|
||||
!!! note
|
||||
The deprecated argument `ClientCAFiles` allows adding Client CA files which are mandatory.
|
||||
If this parameter exists, the new ones are not checked.
|
||||
|
||||
## Authentication
|
||||
|
||||
### Basic Authentication
|
||||
|
||||
Passwords can be encoded in MD5, SHA1 and BCrypt: you can use `htpasswd` to generate them.
|
||||
|
||||
Users can be specified directly in the TOML file, or indirectly by referencing an external file;
|
||||
if both are provided, the two are merged, with external file contents having precedence.
|
||||
|
||||
```toml
|
||||
# To enable basic auth on an entrypoint with 2 user/pass: test:test and test2:test2
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.auth.basic]
|
||||
users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
|
||||
usersFile = "/path/to/.htpasswd"
|
||||
```
|
||||
|
||||
Optionally, you can:
|
||||
|
||||
- pass authenticated user to application via headers
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.auth]
|
||||
headerField = "X-WebAuth-User" # <-- header for the authenticated user
|
||||
[entryPoints.http.auth.basic]
|
||||
users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
|
||||
```
|
||||
|
||||
- remove the Authorization header
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.auth]
|
||||
[entryPoints.http.auth.basic]
|
||||
removeHeader = true # <-- remove the Authorization header
|
||||
users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
|
||||
```
|
||||
|
||||
### Digest Authentication
|
||||
|
||||
You can use `htdigest` to generate them.
|
||||
|
||||
Users can be specified directly in the TOML file, or indirectly by referencing an external file;
|
||||
if both are provided, the two are merged, with external file contents having precedence
|
||||
|
||||
```toml
|
||||
# To enable digest auth on an entrypoint with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.auth.digest]
|
||||
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
|
||||
usersFile = "/path/to/.htdigest"
|
||||
```
|
||||
|
||||
Optionally, you can!
|
||||
|
||||
- pass authenticated user to application via headers.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.auth]
|
||||
headerField = "X-WebAuth-User" # <-- header for the authenticated user
|
||||
[entryPoints.http.auth.digest]
|
||||
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
|
||||
```
|
||||
|
||||
- remove the Authorization header.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.auth]
|
||||
[entryPoints.http.auth.digest]
|
||||
removeHeader = true # <-- remove the Authorization header
|
||||
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
|
||||
```
|
||||
|
||||
### Forward Authentication
|
||||
|
||||
This configuration will first forward the request to `http://authserver.com/auth`.
|
||||
|
||||
If the response code is 2XX, access is granted and the original request is performed.
|
||||
Otherwise, the response from the authentication server is returned.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
# ...
|
||||
# To enable forward auth on an entrypoint
|
||||
[entryPoints.http.auth.forward]
|
||||
address = "https://authserver.com/auth"
|
||||
|
||||
# Trust existing X-Forwarded-* headers.
|
||||
# Useful with another reverse proxy in front of Traefik.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
trustForwardHeader = true
|
||||
|
||||
# Copy headers from the authentication server to the request.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
authResponseHeaders = ["X-Auth-User", "X-Secret"]
|
||||
|
||||
# Enable forward auth TLS connection.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
[entryPoints.http.auth.forward.tls]
|
||||
ca = "path/to/local.crt"
|
||||
caOptional = true
|
||||
cert = "path/to/foo.cert"
|
||||
key = "path/to/foo.key"
|
||||
```
|
||||
|
||||
## Specify Minimum TLS Version
|
||||
|
||||
To specify an https entry point with a minimum TLS version, and specifying an array of cipher suites (from [crypto/tls](https://godoc.org/crypto/tls#pkg-constants)).
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
minVersion = "VersionTLS12"
|
||||
cipherSuites = [
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_RSA_WITH_AES_256_GCM_SHA384"
|
||||
]
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
certFile = "integration/fixtures/https/snitest.com.cert"
|
||||
keyFile = "integration/fixtures/https/snitest.com.key"
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
certFile = "integration/fixtures/https/snitest.org.cert"
|
||||
keyFile = "integration/fixtures/https/snitest.org.key"
|
||||
```
|
||||
|
||||
## Strict SNI Checking
|
||||
|
||||
To enable strict SNI checking, so that connections cannot be made if a matching certificate does not exist.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
sniStrict = true
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
certFile = "integration/fixtures/https/snitest.com.cert"
|
||||
keyFile = "integration/fixtures/https/snitest.com.key"
|
||||
```
|
||||
|
||||
## Default Certificate
|
||||
|
||||
To enable a default certificate to serve, so that connections without SNI or without a matching domain will be served this certificate.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
[entryPoints.https.tls.defaultCertificate]
|
||||
certFile = "integration/fixtures/https/snitest.com.cert"
|
||||
keyFile = "integration/fixtures/https/snitest.com.key"
|
||||
```
|
||||
|
||||
!!! note
|
||||
There can only be one `defaultCertificate` set per entrypoint.
|
||||
Use a single set of square brackets `[ ]`, instead of the two needed for normal certificates.
|
||||
If no default certificate is provided, a self-signed certificate will be generated by Traefik, and used instead.
|
||||
|
||||
## Compression
|
||||
|
||||
To enable compression support using gzip format.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
compress = true
|
||||
```
|
||||
|
||||
Responses are compressed when:
|
||||
|
||||
* The response body is larger than `512` bytes
|
||||
* And the `Accept-Encoding` request header contains `gzip`
|
||||
* And the response is not already compressed, i.e. the `Content-Encoding` response header is not already set.
|
||||
|
||||
## White Listing
|
||||
|
||||
To enable IP white listing at the entry point level.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
[entryPoints.http.whiteList]
|
||||
sourceRange = ["127.0.0.1/32", "192.168.1.7"]
|
||||
# useXForwardedFor = true
|
||||
```
|
||||
|
||||
## ProxyProtocol
|
||||
|
||||
To enable [ProxyProtocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) support.
|
||||
Only IPs in `trustedIPs` will lead to remote client address replacement: you should declare your load-balancer IP or CIDR range here (in testing environment, you can trust everyone using `insecure = true`).
|
||||
|
||||
!!! danger
|
||||
When queuing Traefik behind another load-balancer, be sure to carefully configure Proxy Protocol on both sides.
|
||||
Otherwise, it could introduce a security risk in your system by forging requests.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
# Enable ProxyProtocol
|
||||
[entryPoints.http.proxyProtocol]
|
||||
# List of trusted IPs
|
||||
#
|
||||
# Required
|
||||
# Default: []
|
||||
#
|
||||
trustedIPs = ["127.0.0.1/32", "192.168.1.7"]
|
||||
|
||||
# Insecure mode FOR TESTING ENVIRONNEMENT ONLY
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# insecure = true
|
||||
```
|
||||
|
||||
## Forwarded Header
|
||||
|
||||
Only IPs in `trustedIPs` will be authorized to trust the client forwarded headers (`X-Forwarded-*`).
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
# Enable Forwarded Headers
|
||||
[entryPoints.http.forwardedHeaders]
|
||||
# List of trusted IPs
|
||||
#
|
||||
# Required
|
||||
# Default: []
|
||||
#
|
||||
trustedIPs = ["127.0.0.1/32", "192.168.1.7"]
|
||||
```
|
||||
294
docs/configuration/logs.md
Normal file
@@ -0,0 +1,294 @@
|
||||
# Logs Definition
|
||||
|
||||
## Reference
|
||||
|
||||
### TOML
|
||||
|
||||
```toml
|
||||
logLevel = "INFO"
|
||||
|
||||
[traefikLog]
|
||||
filePath = "/path/to/traefik.log"
|
||||
format = "json"
|
||||
|
||||
[accessLog]
|
||||
filePath = "/path/to/access.log"
|
||||
format = "json"
|
||||
|
||||
[accessLog.filters]
|
||||
statusCodes = ["200", "300-302"]
|
||||
retryAttempts = true
|
||||
minDuration = "10ms"
|
||||
|
||||
[accessLog.fields]
|
||||
defaultMode = "keep"
|
||||
[accessLog.fields.names]
|
||||
"ClientUsername" = "drop"
|
||||
# ...
|
||||
|
||||
[accessLog.fields.headers]
|
||||
defaultMode = "keep"
|
||||
[accessLog.fields.headers.names]
|
||||
"User-Agent" = "redact"
|
||||
"Authorization" = "drop"
|
||||
"Content-Type" = "keep"
|
||||
# ...
|
||||
```
|
||||
|
||||
### CLI
|
||||
|
||||
For more information about the CLI, see the documentation about [Traefik command](/basics/#traefik).
|
||||
|
||||
```shell
|
||||
--logLevel="DEBUG"
|
||||
--traefikLog.filePath="/path/to/traefik.log"
|
||||
--traefikLog.format="json"
|
||||
--accessLog.filePath="/path/to/access.log"
|
||||
--accessLog.format="json"
|
||||
--accessLog.filters.statusCodes="200,300-302"
|
||||
--accessLog.filters.retryAttempts="true"
|
||||
--accessLog.filters.minDuration="10ms"
|
||||
--accessLog.fields.defaultMode="keep"
|
||||
--accessLog.fields.names="Username=drop Hostname=drop"
|
||||
--accessLog.fields.headers.defaultMode="keep"
|
||||
--accessLog.fields.headers.names="User-Agent=redact Authorization=drop Content-Type=keep"
|
||||
```
|
||||
|
||||
|
||||
## Traefik Logs
|
||||
|
||||
By default the Traefik log is written to stdout in text format.
|
||||
|
||||
To write the logs into a log file specify the `filePath`:
|
||||
|
||||
```toml
|
||||
[traefikLog]
|
||||
filePath = "/path/to/traefik.log"
|
||||
```
|
||||
|
||||
To write JSON format logs, specify `json` as the format:
|
||||
|
||||
```toml
|
||||
[traefikLog]
|
||||
filePath = "/path/to/traefik.log"
|
||||
format = "json"
|
||||
```
|
||||
|
||||
|
||||
Deprecated way (before 1.4):
|
||||
|
||||
!!! danger "DEPRECATED"
|
||||
`traefikLogsFile` is deprecated, use [traefikLog](/configuration/logs/#traefik-logs) instead.
|
||||
|
||||
```toml
|
||||
# Traefik logs file
|
||||
# If not defined, logs to stdout
|
||||
#
|
||||
# DEPRECATED - see [traefikLog] lower down
|
||||
# In case both traefikLogsFile and traefikLog.filePath are specified, the latter will take precedence.
|
||||
# Optional
|
||||
#
|
||||
traefikLogsFile = "log/traefik.log"
|
||||
```
|
||||
|
||||
To customize the log level:
|
||||
|
||||
```toml
|
||||
# Log level
|
||||
#
|
||||
# Optional
|
||||
# Default: "ERROR"
|
||||
#
|
||||
# Accepted values, in order of severity: "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "PANIC"
|
||||
# Messages at and above the selected level will be logged.
|
||||
#
|
||||
logLevel = "ERROR"
|
||||
```
|
||||
|
||||
|
||||
## Access Logs
|
||||
|
||||
Access logs are written when `[accessLog]` is defined.
|
||||
By default it will write to stdout and produce logs in the textual Common Log Format (CLF), extended with additional fields.
|
||||
|
||||
To enable access logs using the default settings just add the `[accessLog]` entry:
|
||||
|
||||
```toml
|
||||
[accessLog]
|
||||
```
|
||||
|
||||
To write the logs into a log file specify the `filePath`:
|
||||
|
||||
```toml
|
||||
[accessLog]
|
||||
filePath = "/path/to/access.log"
|
||||
```
|
||||
|
||||
To write JSON format logs, specify `json` as the format:
|
||||
|
||||
```toml
|
||||
[accessLog]
|
||||
filePath = "/path/to/access.log"
|
||||
format = "json"
|
||||
```
|
||||
|
||||
To write the logs in async, specify `bufferingSize` as the format (must be >0):
|
||||
|
||||
```toml
|
||||
[accessLog]
|
||||
filePath = "/path/to/access.log"
|
||||
# Buffering Size
|
||||
#
|
||||
# Optional
|
||||
# Default: 0
|
||||
#
|
||||
# Number of access log lines to process in a buffered way.
|
||||
#
|
||||
bufferingSize = 100
|
||||
```
|
||||
|
||||
To filter logs you can specify a set of filters which are logically "OR-connected". Thus, specifying multiple filters will keep more access logs than specifying only one:
|
||||
|
||||
```toml
|
||||
[accessLog]
|
||||
filePath = "/path/to/access.log"
|
||||
format = "json"
|
||||
|
||||
[accessLog.filters]
|
||||
|
||||
# statusCodes: keep access logs with status codes in the specified range
|
||||
#
|
||||
# Optional
|
||||
# Default: []
|
||||
#
|
||||
statusCodes = ["200", "300-302"]
|
||||
|
||||
# retryAttempts: keep access logs when at least one retry happened
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
retryAttempts = true
|
||||
|
||||
# minDuration: keep access logs when request took longer than the specified duration
|
||||
#
|
||||
# Optional
|
||||
# Default: 0
|
||||
#
|
||||
minDuration = "10ms"
|
||||
```
|
||||
|
||||
To customize logs format:
|
||||
|
||||
```toml
|
||||
[accessLog]
|
||||
filePath = "/path/to/access.log"
|
||||
format = "json"
|
||||
|
||||
[accessLog.filters]
|
||||
|
||||
# statusCodes keep only access logs with status codes in the specified range
|
||||
#
|
||||
# Optional
|
||||
# Default: []
|
||||
#
|
||||
statusCodes = ["200", "300-302"]
|
||||
|
||||
[accessLog.fields]
|
||||
|
||||
# defaultMode
|
||||
#
|
||||
# Optional
|
||||
# Default: "keep"
|
||||
#
|
||||
# Accepted values "keep", "drop"
|
||||
#
|
||||
defaultMode = "keep"
|
||||
|
||||
# Fields map which is used to override fields defaultMode
|
||||
[accessLog.fields.names]
|
||||
"ClientUsername" = "drop"
|
||||
# ...
|
||||
|
||||
[accessLog.fields.headers]
|
||||
# defaultMode
|
||||
#
|
||||
# Optional
|
||||
# Default: "keep"
|
||||
#
|
||||
# Accepted values "keep", "drop", "redact"
|
||||
#
|
||||
defaultMode = "keep"
|
||||
# Fields map which is used to override headers defaultMode
|
||||
[accessLog.fields.headers.names]
|
||||
"User-Agent" = "redact"
|
||||
"Authorization" = "drop"
|
||||
"Content-Type" = "keep"
|
||||
# ...
|
||||
```
|
||||
|
||||
|
||||
### List of all available fields
|
||||
|
||||
```ini
|
||||
StartUTC
|
||||
StartLocal
|
||||
Duration
|
||||
FrontendName
|
||||
BackendName
|
||||
BackendURL
|
||||
BackendAddr
|
||||
ClientAddr
|
||||
ClientHost
|
||||
ClientPort
|
||||
ClientUsername
|
||||
RequestAddr
|
||||
RequestHost
|
||||
RequestPort
|
||||
RequestMethod
|
||||
RequestPath
|
||||
RequestProtocol
|
||||
RequestLine
|
||||
RequestContentSize
|
||||
OriginDuration
|
||||
OriginContentSize
|
||||
OriginStatus
|
||||
OriginStatusLine
|
||||
DownstreamStatus
|
||||
DownstreamStatusLine
|
||||
DownstreamContentSize
|
||||
RequestCount
|
||||
GzipRatio
|
||||
Overhead
|
||||
RetryAttempts
|
||||
```
|
||||
|
||||
Deprecated way (before 1.4):
|
||||
|
||||
!!! danger "DEPRECATED"
|
||||
`accessLogsFile` is deprecated, use [accessLog](/configuration/logs/#access-logs) instead.
|
||||
|
||||
```toml
|
||||
# Access logs file
|
||||
#
|
||||
# DEPRECATED - see [accessLog]
|
||||
#
|
||||
accessLogsFile = "log/access.log"
|
||||
```
|
||||
|
||||
### CLF - Common Log Format
|
||||
|
||||
By default, Traefik use the CLF (`common`) as access log format.
|
||||
|
||||
```html
|
||||
<remote_IP_address> - <client_user_name_if_available> [<timestamp>] "<request_method> <request_path> <request_protocol>" <origin_server_HTTP_status> <origin_server_content_size> "<request_referrer>" "<request_user_agent>" <number_of_requests_received_since_Traefik_started> "<Traefik_frontend_name>" "<Traefik_backend_URL>" <request_duration_in_ms>ms
|
||||
```
|
||||
|
||||
|
||||
## Log Rotation
|
||||
|
||||
Traefik will close and reopen its log files, assuming they're configured, on receipt of a USR1 signal.
|
||||
This allows the logs to be rotated and processed by an external program, such as `logrotate`.
|
||||
|
||||
!!! note
|
||||
This does not work on Windows due to the lack of USR signals.
|
||||
@@ -1,7 +1,3 @@
|
||||
# Metrics
|
||||
|
||||
## Old Content
|
||||
|
||||
# Metrics Definition
|
||||
|
||||
## Prometheus
|
||||
@@ -108,20 +104,6 @@
|
||||
#
|
||||
protocol = "udp"
|
||||
|
||||
# InfluxDB's username
|
||||
#
|
||||
# Optional
|
||||
# Default: "" (no username)
|
||||
#
|
||||
username = ""
|
||||
|
||||
# InfluxDB's password
|
||||
#
|
||||
# Optional
|
||||
# Default: "" (no password)
|
||||
#
|
||||
password = ""
|
||||
|
||||
# InfluxDB push interval
|
||||
#
|
||||
# Optional
|
||||
@@ -145,4 +127,3 @@
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
91
docs/configuration/ping.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# Ping Definition
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
# Ping definition
|
||||
[ping]
|
||||
# Name of the related entry point
|
||||
#
|
||||
# Optional
|
||||
# Default: "traefik"
|
||||
#
|
||||
entryPoint = "traefik"
|
||||
```
|
||||
|
||||
| Path | Method | Description |
|
||||
|---------|---------------|----------------------------------------------------------------------------------------------------|
|
||||
| `/ping` | `GET`, `HEAD` | A simple endpoint to check for Traefik process liveness. Return a code `200` with the content: `OK` |
|
||||
|
||||
|
||||
!!! warning
|
||||
Even if you have authentication configured on entry point, the `/ping` path of the api is excluded from authentication.
|
||||
|
||||
## Examples
|
||||
|
||||
The `/ping` health-check URL is enabled with the command-line `--ping` or config file option `[ping]`.
|
||||
Thus, if you have a regular path for `/foo` and an entrypoint on `:80`, you would access them as follows:
|
||||
|
||||
* Regular path: `http://hostname:80/foo`
|
||||
* Admin panel: `http://hostname:8080/`
|
||||
* Ping URL: `http://hostname:8080/ping`
|
||||
|
||||
However, for security reasons, you may want to be able to expose the `/ping` health-check URL to outside health-checkers, e.g. an Internet service or cloud load-balancer, _without_ exposing your dashboard's port.
|
||||
In many environments, the security staff may not _allow_ you to expose it.
|
||||
|
||||
You have two options:
|
||||
|
||||
* Enable `/ping` on a regular entry point
|
||||
* Enable `/ping` on a dedicated port
|
||||
|
||||
### Ping health check on a regular entry point
|
||||
|
||||
To proxy `/ping` from a regular entry point to the administration one without exposing the dashboard, do the following:
|
||||
|
||||
```toml
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
[ping]
|
||||
entryPoint = "http"
|
||||
|
||||
```
|
||||
|
||||
The above link `ping` on the `http` entry point and then expose it on port `80`
|
||||
|
||||
### Enable ping health check on dedicated port
|
||||
|
||||
If you do not want to or cannot expose the health-check on a regular entry point - e.g. your security rules do not allow it, or you have a conflicting path - then you can enable health-check on its own entry point.
|
||||
Use the following configuration:
|
||||
|
||||
```toml
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.ping]
|
||||
address = ":8082"
|
||||
|
||||
[ping]
|
||||
entryPoint = "ping"
|
||||
```
|
||||
|
||||
The above is similar to the previous example, but instead of enabling `/ping` on the _default_ entry point, we enable it on a _dedicated_ entry point.
|
||||
|
||||
In the above example, you would access a regular path and health-check as follows:
|
||||
|
||||
* Regular path: `http://hostname:80/foo`
|
||||
* Ping URL: `http://hostname:8082/ping`
|
||||
|
||||
Note the dedicated port `:8082` for `/ping`.
|
||||
|
||||
In the above example, it is _very_ important to create a named dedicated entry point, and do **not** include it in `defaultEntryPoints`.
|
||||
Otherwise, you are likely to expose _all_ services via this entry point.
|
||||
|
||||
### Using ping for external Load-balancer rotation health check
|
||||
|
||||
If you are running traefik behind a external Load-balancer, and want to configure rotation health check on the Load-balancer to take a traefik instance out of rotation gracefully, you can configure [lifecycle.requestAcceptGraceTimeout](/configuration/commons.md#life-cycle) and the ping endpoint will return `503` response on traefik server termination, so that the Load-balancer can take the terminating traefik instance out of rotation, before it stops responding.
|
||||
172
docs/configuration/tracing.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Tracing
|
||||
|
||||
The tracing system allows developers to visualize call flows in their infrastructure.
|
||||
|
||||
We use [OpenTracing](http://opentracing.io). It is an open standard designed for distributed tracing.
|
||||
|
||||
Traefik supports three tracing backends: Jaeger, Zipkin and DataDog.
|
||||
|
||||
## Jaeger
|
||||
|
||||
```toml
|
||||
# Tracing definition
|
||||
[tracing]
|
||||
# Backend name used to send tracing data
|
||||
#
|
||||
# Default: "jaeger"
|
||||
#
|
||||
backend = "jaeger"
|
||||
|
||||
# Service name used in Jaeger backend
|
||||
#
|
||||
# Default: "traefik"
|
||||
#
|
||||
serviceName = "traefik"
|
||||
|
||||
# Span name limit allows for name truncation in case of very long Frontend/Backend names
|
||||
# This can prevent certain tracing providers to drop traces that exceed their length limits
|
||||
#
|
||||
# Default: 0 - no truncation will occur
|
||||
#
|
||||
spanNameLimit = 0
|
||||
|
||||
[tracing.jaeger]
|
||||
# Sampling Server URL is the address of jaeger-agent's HTTP sampling server
|
||||
#
|
||||
# Default: "http://localhost:5778/sampling"
|
||||
#
|
||||
samplingServerURL = "http://localhost:5778/sampling"
|
||||
|
||||
# Sampling Type specifies the type of the sampler: const, probabilistic, rateLimiting
|
||||
#
|
||||
# Default: "const"
|
||||
#
|
||||
samplingType = "const"
|
||||
|
||||
# Sampling Param is a value passed to the sampler.
|
||||
# Valid values for Param field are:
|
||||
# - for "const" sampler, 0 or 1 for always false/true respectively
|
||||
# - for "probabilistic" sampler, a probability between 0 and 1
|
||||
# - for "rateLimiting" sampler, the number of spans per second
|
||||
#
|
||||
# Default: 1.0
|
||||
#
|
||||
samplingParam = 1.0
|
||||
|
||||
# Local Agent Host Port instructs reporter to send spans to jaeger-agent at this address
|
||||
#
|
||||
# Default: "127.0.0.1:6831"
|
||||
#
|
||||
localAgentHostPort = "127.0.0.1:6831"
|
||||
|
||||
# Trace Context Header Name is the http header name used to propagate tracing context.
|
||||
# This must be in lower-case to avoid mismatches when decoding incoming headers.
|
||||
#
|
||||
# Default: "uber-trace-id"
|
||||
#
|
||||
traceContextHeaderName = "uber-trace-id"
|
||||
```
|
||||
|
||||
!!! warning
|
||||
Traefik is only able to send data over compact thrift protocol to the [Jaeger agent](https://www.jaegertracing.io/docs/deployment/#agent).
|
||||
|
||||
## Zipkin
|
||||
|
||||
```toml
|
||||
# Tracing definition
|
||||
[tracing]
|
||||
# Backend name used to send tracing data
|
||||
#
|
||||
# Default: "jaeger"
|
||||
#
|
||||
backend = "zipkin"
|
||||
|
||||
# Service name used in Zipkin backend
|
||||
#
|
||||
# Default: "traefik"
|
||||
#
|
||||
serviceName = "traefik"
|
||||
|
||||
# Span name limit allows for name truncation in case of very long Frontend/Backend names
|
||||
# This can prevent certain tracing providers to drop traces that exceed their length limits
|
||||
#
|
||||
# Default: 0 - no truncation will occur
|
||||
#
|
||||
spanNameLimit = 150
|
||||
|
||||
[tracing.zipkin]
|
||||
# Zipking HTTP endpoint used to send data
|
||||
#
|
||||
# Default: "http://localhost:9411/api/v1/spans"
|
||||
#
|
||||
httpEndpoint = "http://localhost:9411/api/v1/spans"
|
||||
|
||||
# Enable Zipkin debug
|
||||
#
|
||||
# Default: false
|
||||
#
|
||||
debug = false
|
||||
|
||||
# Use ZipKin SameSpan RPC style traces
|
||||
#
|
||||
# Default: false
|
||||
#
|
||||
sameSpan = false
|
||||
|
||||
# Use ZipKin 128 bit root span IDs
|
||||
#
|
||||
# Default: true
|
||||
#
|
||||
id128Bit = true
|
||||
```
|
||||
|
||||
## DataDog
|
||||
|
||||
```toml
|
||||
# Tracing definition
|
||||
[tracing]
|
||||
# Backend name used to send tracing data
|
||||
#
|
||||
# Default: "jaeger"
|
||||
#
|
||||
backend = "datadog"
|
||||
|
||||
# Service name used in DataDog backend
|
||||
#
|
||||
# Default: "traefik"
|
||||
#
|
||||
serviceName = "traefik"
|
||||
|
||||
# Span name limit allows for name truncation in case of very long Frontend/Backend names
|
||||
# This can prevent certain tracing providers to drop traces that exceed their length limits
|
||||
#
|
||||
# Default: 0 - no truncation will occur
|
||||
#
|
||||
spanNameLimit = 100
|
||||
|
||||
[tracing.datadog]
|
||||
# Local Agent Host Port instructs reporter to send spans to datadog-tracing-agent at this address
|
||||
#
|
||||
# Default: "127.0.0.1:8126"
|
||||
#
|
||||
localAgentHostPort = "127.0.0.1:8126"
|
||||
|
||||
# Enable DataDog debug
|
||||
#
|
||||
# Default: false
|
||||
#
|
||||
debug = false
|
||||
|
||||
# Apply shared tag in a form of Key:Value to all the traces
|
||||
#
|
||||
# Default: ""
|
||||
#
|
||||
globalTag = ""
|
||||
|
||||
# Enable priority sampling. When using distributed tracing, this option must be enabled in order
|
||||
# to get all the parts of a distributed trace sampled.
|
||||
#
|
||||
# Default: false
|
||||
#
|
||||
prioritySampling = false
|
||||
```
|
||||
|
Before Width: | Height: | Size: 361 KiB |
|
Before Width: | Height: | Size: 208 KiB |
|
Before Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 376 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 120 KiB |