1
0
mirror of https://github.com/containous/traefik.git synced 2025-10-03 01:44:23 +03:00

Compare commits

...

71 Commits

Author SHA1 Message Date
Romain
b7be71c02a Prepare release v2.11.22 2025-03-31 09:48:04 +02:00
Adam Duke
6e9d713668 Bump github.com/vulcand/oxy/v2 to v2.0.3 2025-03-31 09:24:06 +02:00
Rohit Lohar
ddb32ef86f Allow underscore character in hostSNI matcher 2025-03-28 11:36:04 +01:00
Kevin Pollet
2087e11f55 Bump nokogiri to 1.18.6 and html-proofer to 5.0.10 2025-03-26 17:52:05 +01:00
Romain
a5d46fc6ef Change boolean module properties default value to undefined 2025-03-26 10:22:05 +01:00
Kevin Pollet
84742275a4 Bump golang.org/x/net to v0.37.0 2025-03-26 10:06:05 +01:00
Kevin Pollet
54a2d657f3 Bump github.com/redis/go-redis/v9 to v9.6.3 2025-03-26 09:48:05 +01:00
Kevin Pollet
08b90ade94 Bump github.com/golang-jwt/jwt to v4.5.2 and v5.2.2 2025-03-26 09:30:05 +01:00
Romain
8ba99adc50 Error level log for configuration-related TLS errors with backends
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
2025-03-21 11:00:06 +01:00
Kevin Pollet
b02946147d Bump golang.org/x/net to v0.36.0 2025-03-14 09:24:05 +01:00
Nicolas Mengin
137c632793 Add Security Support 2025-03-14 09:10:04 +01:00
nmengin
e76b65f44d Add Security Support column in deprecation section 2025-03-13 17:08:15 +01:00
Gérald Croës
55ebaee4a7 Clarifies that retry middleware uses TCP, not HTTP status codes 2025-03-13 09:44:04 +01:00
Romain
4ff76e13c4 Remove documentation for OriginStatusLine and DownstreamStatusLine accessLogs fields 2025-03-11 15:32:04 +01:00
Eng Zer Jun
14e400bcd0 Bump AWS SDK to v2 2025-03-10 11:50:04 +01:00
Michel Loiseleur
7cfd10db62 Update codegen to v0.30.10 2025-03-05 10:20:05 +01:00
Alan
9d8a42111f Add tip for dynamic configuration updates of Redis 2025-02-28 14:18:05 +01:00
Kevin Pollet
0dfd12ee61 Bump github.com/go-jose/go-jose/v4 to v4.0.5 2025-02-25 14:06:04 +01:00
Kevin Pollet
a3fd484728 Prepare release v2.11.21 2025-02-24 15:32:06 +01:00
Kevin Pollet
f196de90e1 Enable the retry middleware in the proxy
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
2025-02-21 11:36:05 +01:00
Kevin Pollet
c2a294c872 Retry should send headers on Write
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
2025-02-21 10:52:04 +01:00
Ludovic Fernandez
8e5d4c6ae9 Bum github.com/go-acme/lego/v4 to v4.22.2 2025-02-21 09:36:04 +01:00
Kevin Pollet
eb07a5ca1a Bump github.com/traefik/paerser to v0.2.2
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
2025-02-14 11:24:04 +01:00
Ludovic Fernandez
84e20aa9c3 chore: update linter 2025-02-12 10:02:04 +01:00
Romain
4e441d09ed Prepare release v2.11.20 2025-01-31 15:16:04 +01:00
khai-pi
8f5dd7bd9d Change docker-compose to docker compose 2025-01-31 14:30:05 +01:00
Julien Salleyron
86315e0f18 Fix ACME write when traefik is shutting down 2025-01-31 11:06:04 +01:00
Kevin Pollet
c20af070e3 Set check-latest to true in Go setup 2025-01-30 14:06:04 +01:00
Kevin Pollet
8593581cbf Fix integration tests for HTTPS 2025-01-29 17:04:05 +01:00
Romain
8103992977 Prepare release v2.11.19 2025-01-29 11:36:08 +01:00
Kevin Pollet
c5b92b5260 Do not create a logger instance for each proxy 2025-01-27 11:24:04 +01:00
DoubleREW
c19cf125e8 Fix auto refresh not clearing on component unmount 2025-01-21 14:58:04 +01:00
Nelson Isioma
435d28c790 changing log message when client cert is not available to debug 2025-01-17 09:42:04 +01:00
Kevin Pollet
8272be0eda Remove awesome.traefik.io reference in documentation section 2025-01-13 10:28:04 +01:00
Kevin Pollet
d2414feaff Add test to check that SettingEnableConnectProtocol frame is not sent 2025-01-08 11:02:37 +01:00
Kevin Pollet
1aa450c028 Prepare release v2.11.18 2025-01-07 16:24:04 +01:00
Romain
f9ff6049d3 Disable http2 connect setting for websocket by default
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
Co-authored-by: Julien Salleyron <julien.salleyron@gmail.com>
Co-authored-by: Michael <michael.matur@gmail.com>
2025-01-07 16:12:04 +01:00
Michael
ee8305549a Allow release only on traefik/traefik repo 2025-01-06 10:28:04 +01:00
Kevin Pollet
a31b026364 Prepare release v2.11.17 2025-01-06 10:00:07 +01:00
Thomas Francis
20d496268c Fix typo in basicauth note 2025-01-06 09:36:08 +01:00
Ludovic Fernandez
5f3c30e37b chore: update linter 2025-01-03 09:58:04 +01:00
Ludovic Fernandez
38ac1e75a2 Update go-acme/lego to v4.21.0 2025-01-02 12:46:04 +01:00
Kevin Pollet
109a8712cc Update copyright for 2025 2025-01-02 12:08:04 +01:00
Hannes Braun
278e739242 Fix allowACMEByPass TOML example 2024-12-30 16:08:03 +01:00
Michael
db31a4c961 Add webui static files in release tarball 2024-12-20 16:46:04 +01:00
Kevin Pollet
35ce6baaae Bump golang.org/x/net to v0.33.0 2024-12-20 14:36:06 +01:00
Daniel Anugerah
95f20fc753 Configure ErrorLog in httputil.ReverseProxy 2024-12-20 14:18:04 +01:00
Kevin Pollet
1c0094048b Prepare release v2.11.16 2024-12-16 10:48:04 +01:00
Romain
590ddfc990 Update nokogiri gem to v1.16.8 2024-12-12 15:12:04 +01:00
Kevin Pollet
39d7b77609 Bump Dockerfile to Alpine v3.21 2024-12-12 14:44:05 +01:00
Romain
74e0abf8bf Update golang.org/x dependencies 2024-12-12 13:02:04 +01:00
Kevin Pollet
cc14c165c0 Prepare release v2.11.15 2024-12-10 14:18:04 +01:00
Michael
f2ba4353b2 Fix experimental build ci 2024-12-10 12:12:05 +01:00
Michael
42df9afeaf Fix release by using github action 2024-12-06 16:56:06 +01:00
Kevin Pollet
2df655cefe Update github.com/quic-go/quic-go to v0.48.2 2024-12-06 16:36:05 +01:00
Ludovic Fernandez
c120b70483 Update go-acme/lego to v4.20.4 2024-11-22 09:54:04 +01:00
Kevin Pollet
8eadfbb990 Prepare release v2.11.14 2024-11-20 15:26:04 +01:00
Julien Salleyron
cc80568d9e Fix internal handlers ServiceBuilder composition 2024-11-19 14:52:04 +01:00
Kevin Pollet
8ffd1854db Fix the defaultRule CLI examples 2024-11-18 14:40:05 +01:00
bluepuma77
6baa110adb Update access-logs.md, add examples for accesslog.format 2024-11-18 11:58:04 +01:00
Antoine
5658c8ac06 Fix spelling, grammar, and rephrase sections for clarity in some documentation pages 2024-11-18 11:42:04 +01:00
davefu113
1c80f12bc2 Apply keepalive config to h2c entrypoints 2024-11-18 09:56:04 +01:00
Michel Loiseleur
ef5f1b1508 Improve documentation on dashboard 2024-11-14 11:14:04 +01:00
Kevin Pollet
8c19652361 Fix absolute link in the migration guide 2024-11-12 17:06:03 +01:00
Kevin Pollet
e5c80637fc Add X-Forwarded-Prefix to the migration guide
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
2024-11-12 15:04:04 +01:00
Ludovic Fernandez
f437fb4230 chore: update linter 2024-11-12 10:56:06 +01:00
Ludovic Fernandez
9c50129520 Update go-acme/lego to v4.20.2 2024-11-12 10:32:09 +01:00
Dominik Schwaiger
00a5f4c401 Fix a small typo in entrypoints documentation 2024-11-12 10:14:04 +01:00
Romain
a79cdd1dfa Change level of peeking first byte error log to DEBUG
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
2024-11-08 14:28:08 +01:00
Romain
2096fd7081 Drop untrusted X-Forwarded-Prefix header
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
2024-11-08 12:12:35 +01:00
Anchal Sharma
6f18344c56 Add a warning about environment variables casing for static configuration 2024-10-30 10:54:04 +01:00
226 changed files with 2588 additions and 2056 deletions

View File

@@ -16,36 +16,7 @@ env:
jobs:
build-webui:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup node
uses: actions/setup-node@v4
with:
node-version-file: webui/.nvmrc
cache: yarn
cache-dependency-path: webui/yarn.lock
- name: Build webui
working-directory: ./webui
run: |
yarn install
yarn build
- name: Package webui
run: |
tar czvf webui.tar.gz ./webui/static/
- name: Artifact webui
uses: actions/upload-artifact@v4
with:
name: webui.tar.gz
path: webui.tar.gz
uses: ./.github/workflows/template-webui.yaml
build:
runs-on: ubuntu-latest
@@ -90,6 +61,7 @@ jobs:
ImageOS: ${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.goarm }}
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
- name: Artifact webui
uses: actions/download-artifact@v4
@@ -97,7 +69,9 @@ jobs:
name: webui.tar.gz
- name: Untar webui
run: tar xvf webui.tar.gz
run: |
tar xvf webui.tar.gz
rm webui.tar.gz
- name: Build
env:

View File

@@ -12,36 +12,28 @@ env:
jobs:
build-webui:
if: github.repository == 'traefik/traefik'
uses: ./.github/workflows/template-webui.yaml
experimental:
if: github.repository == 'traefik/traefik'
name: Build experimental image on branch
runs-on: ubuntu-latest
steps:
# https://github.com/marketplace/actions/checkout
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup node
uses: actions/setup-node@v4
with:
node-version-file: webui/.nvmrc
cache: yarn
cache-dependency-path: webui/yarn.lock
- name: Build webui
working-directory: ./webui
run: |
yarn install
yarn build
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v5
env:
ImageOS: ${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.goarm }}
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
- name: Build
run: make generate binary
@@ -61,6 +53,16 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Artifact webui
uses: actions/download-artifact@v4
with:
name: webui.tar.gz
- name: Untar webui
run: |
tar xvf webui.tar.gz
rm webui.tar.gz
- name: Build docker experimental image
env:
DOCKER_BUILDX_ARGS: "--push"

138
.github/workflows/release.yaml vendored Normal file
View File

@@ -0,0 +1,138 @@
name: Release
on:
push:
tags:
- 'v*.*.*'
env:
GO_VERSION: '1.23'
CGO_ENABLED: 0
VERSION: ${{ github.ref_name }}
TRAEFIKER_EMAIL: "traefiker@traefik.io"
CODENAME: mimolette
jobs:
build-webui:
if: github.ref_type == 'tag' && github.repository == 'traefik/traefik'
uses: ./.github/workflows/template-webui.yaml
build:
if: github.ref_type == 'tag' && github.repository == 'traefik/traefik'
runs-on: ubuntu-latest
strategy:
matrix:
os: [ linux-amd64, linux-386, linux-arm, linux-arm64, linux-ppc64le, linux-s390x, linux-riscv64, darwin, windows-amd64, windows-arm64, windows-386, freebsd, openbsd ]
needs:
- build-webui
steps:
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v5
env:
# Ensure cache consistency on Linux, see https://github.com/actions/setup-go/pull/383
ImageOS: ${{ matrix.os }}
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
- name: Artifact webui
uses: actions/download-artifact@v4
with:
name: webui.tar.gz
- name: Untar webui
run: |
tar xvf webui.tar.gz
rm webui.tar.gz
- name: Go generate
run: go generate
- name: Generate goreleaser file
run: |
GORELEASER_CONFIG_FILE_PATH=$(go run ./internal/release "${{ matrix.os }}")
echo "GORELEASER_CONFIG_FILE_PATH=$GORELEASER_CONFIG_FILE_PATH" >> $GITHUB_ENV
- name: Build with goreleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
# 'latest', 'nightly', or a semver
version: '~> v2'
args: release --clean --timeout="90m" --config "${{ env.GORELEASER_CONFIG_FILE_PATH }}"
- name: Artifact binaries
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.os }}-binaries
path: |
dist/**/*_checksums.txt
dist/**/*.tar.gz
dist/**/*.zip
retention-days: 1
release:
if: github.ref_type == 'tag' && github.repository == 'traefik/traefik'
runs-on: ubuntu-latest
needs:
- build
steps:
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Artifact webui
uses: actions/download-artifact@v4
with:
name: webui.tar.gz
- name: Untar webui
run: |
tar xvf webui.tar.gz
rm webui.tar.gz
- name: Retrieve the secret and decode it to a file
env:
TRAEFIKER_RSA: ${{ secrets.TRAEFIKER_RSA }}
run: |
mkdir -p ~/.ssh
echo "${TRAEFIKER_RSA}" | base64 --decode > ~/.ssh/traefiker_rsa
- name: Download All Artifacts
uses: actions/download-artifact@v4
with:
path: dist/
pattern: "*-binaries"
merge-multiple: true
- name: Publish Release
env:
GH_TOKEN: ${{ github.token }}
run: |
cat dist/**/*_checksums.txt >> "dist/traefik_${VERSION}_checksums.txt"
rm dist/**/*_checksums.txt
tar cfz "dist/traefik-${VERSION}.src.tar.gz" \
--exclude-vcs \
--exclude .idea \
--exclude .travis \
--exclude .semaphoreci \
--exclude .github \
--exclude dist .
chown -R "$(id -u)":"$(id -g)" dist/
gh release create ${VERSION} ./dist/**/traefik*.{zip,tar.gz} ./dist/traefik*.{tar.gz,txt} --repo traefik/traefik --title ${VERSION} --notes ${VERSION}
./script/deploy.sh

37
.github/workflows/template-webui.yaml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Build Web UI
on:
workflow_call: {}
jobs:
build-webui:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup node
uses: actions/setup-node@v4
with:
node-version-file: webui/.nvmrc
cache: yarn
cache-dependency-path: webui/yarn.lock
- name: Build webui
working-directory: ./webui
run: |
yarn install
yarn build
- name: Package webui
run: |
tar czvf webui.tar.gz ./webui/static/
- name: Artifact webui
uses: actions/upload-artifact@v4
with:
name: webui.tar.gz
path: webui.tar.gz
retention-days: 1

View File

@@ -28,6 +28,7 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
- name: Avoid generating webui
run: touch webui/static/index.html
@@ -55,6 +56,7 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
- name: Avoid generating webui
run: touch webui/static/index.html

View File

@@ -27,6 +27,7 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
- name: Avoid generating webui
run: touch webui/static/index.html

View File

@@ -7,7 +7,7 @@ on:
env:
GO_VERSION: '1.23'
GOLANGCI_LINT_VERSION: v1.61.0
GOLANGCI_LINT_VERSION: v1.64.2
MISSPELL_VERSION: v0.6.0
jobs:
@@ -25,6 +25,7 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
@@ -44,6 +45,7 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
- name: Install misspell ${{ env.MISSPELL_VERSION }}
run: curl -sfL https://raw.githubusercontent.com/golangci/misspell/master/install-misspell.sh | sh -s -- -b $(go env GOPATH)/bin ${MISSPELL_VERSION}
@@ -67,6 +69,7 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
- name: go generate
run: |

View File

@@ -1,5 +1,6 @@
run:
timeout: 10m
relative-path-mode: cfg
linters-settings:
govet:
@@ -141,6 +142,9 @@ linters-settings:
- name: unreachable-code
- name: redefines-builtin-id
gomoddirectives:
tool-forbidden: true
toolchain-pattern: 'go1\.\d+\.\d+$'
go-version-pattern: '^1\.\d+(\.0)?$'
replace-allow-list:
- github.com/abbot/go-http-auth
- github.com/gorilla/mux
@@ -159,8 +163,7 @@ linters-settings:
linters:
enable-all: true
disable:
- execinquery # deprecated
- gomnd # deprecated
- tenv # Deprecated
- sqlclosecheck # not relevant (SQL)
- rowserrcheck # not relevant (SQL)
- cyclop # duplicate of gocyclo
@@ -197,7 +200,6 @@ linters:
- maintidx # kind of duplicate of gocyclo
- nonamedreturns # Too strict
- gosmopolitan # not relevant
- exportloopref # Not relevant since go1.22
issues:
exclude-use-default: false
@@ -273,3 +275,14 @@ issues:
- path: pkg/provider/acme/local_store.go
linters:
- musttag
- path: pkg/tls/certificate.go
text: 'the methods of "Certificates" use pointer receiver and non-pointer receiver.'
linters:
- recvcheck
output:
show-stats: true
sort-results: true
sort-order:
- linter
- file

View File

@@ -1,12 +1,11 @@
project_name: traefik
version: 2
[[if .GOARCH]]
dist: "./dist/[[ .GOOS ]]-[[ .GOARCH ]]"
[[else]]
dist: "./dist/[[ .GOOS ]]"
[[ if eq .GOOS "linux" ]]
before:
hooks:
- go generate
[[ end ]]
[[end]]
builds:
- binary: traefik
@@ -21,6 +20,9 @@ builds:
goos:
- "[[ .GOOS ]]"
goarch:
[[if .GOARCH]]
- "[[ .GOARCH ]]"
[[else]]
- amd64
- '386'
- arm
@@ -28,6 +30,7 @@ builds:
- ppc64le
- s390x
- riscv64
[[end]]
goarm:
- '7'
- '6'

View File

@@ -1,63 +1,13 @@
version: v1.0
name: Traefik
name: Traefik Release - deprecated
agent:
machine:
type: e1-standard-4
os_image: ubuntu2004
fail_fast:
stop:
when: "branch != 'master'"
auto_cancel:
queued:
when: "branch != 'master'"
running:
when: "branch != 'master'"
global_job_config:
prologue:
commands:
- curl -sSfL https://raw.githubusercontent.com/ldez/semgo/master/godownloader.sh | sudo sh -s -- -b "/usr/local/bin"
- sudo semgo go1.23
- export "GOPATH=$(go env GOPATH)"
- export "SEMAPHORE_GIT_DIR=${GOPATH}/src/github.com/traefik/${SEMAPHORE_PROJECT_NAME}"
- export "PATH=${GOPATH}/bin:${PATH}"
- mkdir -vp "${SEMAPHORE_GIT_DIR}" "${GOPATH}/bin"
- export GOPROXY=https://proxy.golang.org,direct
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "${GOPATH}/bin" v1.61.0
- curl -sSfL https://gist.githubusercontent.com/traefiker/6d7ac019c11d011e4f131bb2cca8900e/raw/goreleaser.sh | bash -s -- -b "${GOPATH}/bin"
- checkout
- cache restore traefik-$(checksum go.sum)
type: f1-standard-2
os_image: ubuntu2204
blocks:
- name: Release
dependencies: []
run:
when: "tag =~ '.*'"
- name: 'Do nothing'
task:
agent:
machine:
type: e1-standard-8
os_image: ubuntu2004
secrets:
- name: traefik
env_vars:
- name: GH_VERSION
value: 2.32.1
- name: CODENAME
value: "mimolette"
prologue:
commands:
- export VERSION=${SEMAPHORE_GIT_TAG_NAME}
- curl -sSL -o /tmp/gh_${GH_VERSION}_linux_amd64.tar.gz https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz
- tar -zxvf /tmp/gh_${GH_VERSION}_linux_amd64.tar.gz -C /tmp
- sudo mv /tmp/gh_${GH_VERSION}_linux_amd64/bin/gh /usr/local/bin/gh
- sudo rm -rf ~/.phpbrew ~/.kerl ~/.sbt ~/.nvm ~/.npm ~/.kiex /usr/lib/jvm /opt/az /opt/firefox /usr/lib/google-cloud-sdk ~/.rbenv ~/.pip_download_cache # Remove unnecessary data.
- sudo service docker stop && sudo umount /var/lib/docker && sudo service docker start # Unmounts the docker disk and the whole system disk is usable.
jobs:
- name: Release
- name: 'Do nothing'
commands:
- make release-packages
- gh release create ${SEMAPHORE_GIT_TAG_NAME} ./dist/**/traefik*.{zip,tar.gz} ./dist/traefik*.{tar.gz,txt} --repo traefik/traefik --title ${SEMAPHORE_GIT_TAG_NAME} --notes ${SEMAPHORE_GIT_TAG_NAME}
- ./script/deploy.sh
- echo "Do nothing"

View File

@@ -1,3 +1,105 @@
## [v2.11.22](https://github.com/traefik/traefik/tree/v2.11.22) (2025-03-31)
[All Commits](https://github.com/traefik/traefik/compare/v2.11.21...v2.11.22)
**Bug fixes:**
- **[ecs,logs]** Bump AWS SDK to v2 ([#11359](https://github.com/traefik/traefik/pull/11359) by [Juneezee](https://github.com/Juneezee))
- **[logs,tls]** Error level log for configuration-related TLS errors with backends ([#11611](https://github.com/traefik/traefik/pull/11611) by [rtribotte](https://github.com/rtribotte))
- **[rules]** Allow underscore character in HostSNI matcher ([#11557](https://github.com/traefik/traefik/pull/11557) by [rohitlohar45](https://github.com/rohitlohar45))
- **[server]** Bump github.com/vulcand/oxy/v2 to v2.0.3 ([#11649](https://github.com/traefik/traefik/pull/11649) by [adamvduke](https://github.com/adamvduke))
- **[server]** Bump golang.org/x/net to v0.37.0 ([#11632](https://github.com/traefik/traefik/pull/11632) by [kevinpollet](https://github.com/kevinpollet))
- **[webui]** Change boolean module properties default value to undefined ([#11639](https://github.com/traefik/traefik/pull/11639) by [rtribotte](https://github.com/rtribotte))
- Bump github.com/golang-jwt/jwt to v4.5.2 and v5.2.2 ([#11634](https://github.com/traefik/traefik/pull/11634) by [kevinpollet](https://github.com/kevinpollet))
- Bump github.com/redis/go-redis/v9 to v9.6.3 ([#11633](https://github.com/traefik/traefik/pull/11633) by [kevinpollet](https://github.com/kevinpollet))
- Bump golang.org/x/net to v0.36.0 ([#11608](https://github.com/traefik/traefik/pull/11608) by [kevinpollet](https://github.com/kevinpollet))
- Bump github.com/go-jose/go-jose/v4 to v4.0.5 ([#11571](https://github.com/traefik/traefik/pull/11571) by [kevinpollet](https://github.com/kevinpollet))
**Documentation:**
- **[accesslogs]** Remove documentation for OriginStatusLine and DownstreamStatusLine accessLogs fields ([#11599](https://github.com/traefik/traefik/pull/11599) by [rtribotte](https://github.com/rtribotte))
- **[middleware]** Clarifies that retry middleware uses TCP, not HTTP status codes ([#11603](https://github.com/traefik/traefik/pull/11603) by [geraldcroes](https://github.com/geraldcroes))
- **[redis]** Add tip for dynamic configuration updates of Redis ([#11577](https://github.com/traefik/traefik/pull/11577) by [Alanxtl](https://github.com/Alanxtl))
- Add Security Support ([#11610](https://github.com/traefik/traefik/pull/11610) by [nmengin](https://github.com/nmengin))
# [v2.11.21](https://github.com/traefik/traefik/tree/v2.11.21) (2025-02-24)
[All Commits](https://github.com/traefik/traefik/compare/v2.11.20...v2.11.21)
**Bug fixes:**
- **[acme]** Bump github.com/go-acme/lego/v4 to v4.22.2 ([#11537](https://github.com/traefik/traefik/pull/11537) by [ldez](https://github.com/ldez))
- **[cli]** Bump github.com/traefik/paerser to v0.2.2 ([#11530](https://github.com/traefik/traefik/pull/11530) by [kevinpollet](https://github.com/kevinpollet))
- **[middleware]** Enable the retry middleware in the proxy ([#11536](https://github.com/traefik/traefik/pull/11536) by [kevinpollet](https://github.com/kevinpollet))
- **[middleware]** Retry should send headers on Write ([#11534](https://github.com/traefik/traefik/pull/11534) by [kevinpollet](https://github.com/kevinpollet))
## [v2.11.20](https://github.com/traefik/traefik/tree/v2.11.20) (2025-01-31)
[All Commits](https://github.com/traefik/traefik/compare/v2.11.19...v2.11.20)
**Bug fixes:**
- **[acme]** Graceful shutdown for ACME JSON write operation ([#11497](https://github.com/traefik/traefik/pull/11497) by [juliens](https://github.com/juliens))
**Documentation:**
- Change docker-compose to docker compose ([#11496](https://github.com/traefik/traefik/pull/11496) by [khai-pi](https://github.com/khai-pi))
## [v2.11.19](https://github.com/traefik/traefik/tree/v2.11.19) (2025-01-29)
[All Commits](https://github.com/traefik/traefik/compare/v2.11.18...v2.11.19)
**Bug fixes:**
- **[middleware]** Changing log message when client cert is not available to debug ([#11453](https://github.com/traefik/traefik/pull/11453) by [Nelwhix](https://github.com/Nelwhix))
- **[service]** Do not create a logger instance for each proxy ([#11487](https://github.com/traefik/traefik/pull/11487) by [kevinpollet](https://github.com/kevinpollet))
- **[webui]** Fix auto refresh not clearing on component unmount ([#11477](https://github.com/traefik/traefik/pull/11477) by [DoubleREW](https://github.com/DoubleREW))
**Documentation:**
- Remove awesome.traefik.io reference in documentation section ([#11435](https://github.com/traefik/traefik/pull/11435) by [kevinpollet](https://github.com/kevinpollet))
## [v2.11.18](https://github.com/traefik/traefik/tree/v2.11.18) (2025-01-07)
[All Commits](https://github.com/traefik/traefik/compare/v2.11.17...v2.11.18)
**Bug fixes:**
- **[websocket,server]** Disable http2 connect setting for websocket by default ([#11412](https://github.com/traefik/traefik/pull/11412) by [rtribotte](https://github.com/rtribotte))
## [v2.11.17](https://github.com/traefik/traefik/tree/v2.11.17) (2025-01-06)
[All Commits](https://github.com/traefik/traefik/compare/v2.11.16...v2.11.17)
**Bug fixes:**
- **[acme]** Update go-acme/lego to v4.21.0 ([#11368](https://github.com/traefik/traefik/pull/11368) by [ldez](https://github.com/ldez))
- **[middleware]** Fix typo in basicauth note ([#11397](https://github.com/traefik/traefik/pull/11397) by [tieje](https://github.com/tieje))
- **[service]** Configure ErrorLog in httputil.ReverseProxy ([#11344](https://github.com/traefik/traefik/pull/11344) by [peacewalker122](https://github.com/peacewalker122))
- Bump golang.org/x/net to v0.33.0 ([#11365](https://github.com/traefik/traefik/pull/11365) by [kevinpollet](https://github.com/kevinpollet))
**Documentation:**
- **[acme]** Fix allowACMEByPass TOML example ([#11370](https://github.com/traefik/traefik/pull/11370) by [hannesbraun](https://github.com/hannesbraun))
- **[k8s/crd]** Update copyright for 2025 ([#11383](https://github.com/traefik/traefik/pull/11383) by [kevinpollet](https://github.com/kevinpollet))
## [v2.11.16](https://github.com/traefik/traefik/tree/v2.11.16) (2024-12-16)
[All Commits](https://github.com/traefik/traefik/compare/v2.11.15...v2.11.16)
**Bug fixes:**
- **[server]** Update golang.org/x dependencies ([#11336](https://github.com/traefik/traefik/pull/11336) by [rtribotte](https://github.com/rtribotte))
## [v2.11.15](https://github.com/traefik/traefik/tree/v2.11.15) (2024-12-06)
[All Commits](https://github.com/traefik/traefik/compare/v2.11.14...v2.11.15)
**Bug fixes:**
- **[acme]** Update go-acme/lego to v4.20.4 ([#11295](https://github.com/traefik/traefik/pull/11295) by [ldez](https://github.com/ldez))
- **[http3]** Update github.com/quic-go/quic-go to v0.48.2 ([#11320](https://github.com/traefik/traefik/pull/11320) by [kevinpollet](https://github.com/kevinpollet))
## [v2.11.14](https://github.com/traefik/traefik/tree/v2.11.14) (2024-11-20)
[All Commits](https://github.com/traefik/traefik/compare/v2.11.13...v2.11.14)
**Bug fixes:**
- **[acme]** Update go-acme/lego to v4.20.2 ([#11263](https://github.com/traefik/traefik/pull/11263) by [ldez](https://github.com/ldez))
- **[logs,server]** Change level of peeking first byte error log to DEBUG ([#11254](https://github.com/traefik/traefik/pull/11254) by [rtribotte](https://github.com/rtribotte))
- **[middleware,server]** Drop untrusted X-Forwarded-Prefix header ([#11253](https://github.com/traefik/traefik/pull/11253) by [rtribotte](https://github.com/rtribotte))
- **[server]** Apply keepalive config to h2c entrypoints ([#11276](https://github.com/traefik/traefik/pull/11276) by [davefu113](https://github.com/davefu113))
- **[service]** Fix internal handlers ServiceBuilder composition ([#11281](https://github.com/traefik/traefik/pull/11281) by [juliens](https://github.com/juliens))
**Documentation:**
- **[accesslogs]** Update access-logs.md, add examples for accesslog.format ([#11275](https://github.com/traefik/traefik/pull/11275) by [bluepuma77](https://github.com/bluepuma77))
- Fix the defaultRule CLI examples ([#11282](https://github.com/traefik/traefik/pull/11282) by [kevinpollet](https://github.com/kevinpollet))
- Fix spelling, grammar, and rephrase sections for clarity in some documentation pages ([#11280](https://github.com/traefik/traefik/pull/11280) by [AntoineDeveloper](https://github.com/AntoineDeveloper))
- Fix absolute link in the migration guide ([#11269](https://github.com/traefik/traefik/pull/11269) by [kevinpollet](https://github.com/kevinpollet))
- Add X-Forwarded-Prefix to the migration guide ([#11267](https://github.com/traefik/traefik/pull/11267) by [kevinpollet](https://github.com/kevinpollet))
- Fix a small typo in entrypoints documentation ([#11261](https://github.com/traefik/traefik/pull/11261) by [quiode](https://github.com/quiode))
- Add a warning about environment variables casing for static configuration ([#11226](https://github.com/traefik/traefik/pull/11226) by [anchal00](https://github.com/anchal00))
- Improve documentation on dashboard ([#11220](https://github.com/traefik/traefik/pull/11220) by [mloiseleur](https://github.com/mloiseleur))
## [v2.11.13](https://github.com/traefik/traefik/tree/v2.11.13) (2024-10-28)
[All Commits](https://github.com/traefik/traefik/compare/v2.11.12...v2.11.13)

View File

@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1.2
FROM alpine:3.20
FROM alpine:3.21
RUN apk add --no-cache --no-progress ca-certificates tzdata

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs
Copyright (c) 2016-2020 Containous SAS; 2020-2025 Traefik Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -169,7 +169,7 @@ docs-pull-images:
.PHONY: generate-crd
#? generate-crd: Generate CRD clientset and CRD manifests
generate-crd:
@$(CURDIR)/script/code-gen-docker.sh
@$(CURDIR)/script/code-gen.sh
.PHONY: generate-genconf
#? generate-genconf: Generate code from dynamic configuration github.com/traefik/genconf

View File

@@ -61,7 +61,7 @@ _(But if you'd rather configure some of your routes manually, Traefik supports t
- Provides HTTPS to your microservices by leveraging [Let's Encrypt](https://letsencrypt.org) (wildcard certificates support)
- Circuit breakers, retry
- See the magic through its clean web UI
- Websocket, HTTP/2, GRPC ready
- WebSocket, HTTP/2, GRPC ready
- Provides metrics (Rest, Prometheus, Datadog, Statsd, InfluxDB)
- Keeps access logs (JSON, CLF)
- Fast
@@ -91,8 +91,6 @@ You can access the simple HTML frontend of Traefik.
You can find the complete documentation of Traefik v2 at [https://doc.traefik.io/traefik/](https://doc.traefik.io/traefik/).
A collection of contributions around Traefik can be found at [https://awesome.traefik.io](https://awesome.traefik.io).
## Support
To get community support, you can:

View File

@@ -1,7 +1,7 @@
# Security Policy
You can join our security mailing list to be aware of the latest announcements from our security team.
You can subscribe sending a mail to security+subscribe@traefik.io or on [the online viewer](https://groups.google.com/a/traefik.io/forum/#!forum/security).
You can subscribe by sending an email to security+subscribe@traefik.io or on [the online viewer](https://groups.google.com/a/traefik.io/forum/#!forum/security).
Reported vulnerabilities can be found on [cve.mitre.org](https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=traefik).

View File

@@ -187,7 +187,7 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
return nil, err
}
acmeProviders := initACMEProvider(staticConfiguration, &providerAggregator, tlsManager, httpChallengeProvider, tlsChallengeProvider)
acmeProviders := initACMEProvider(staticConfiguration, providerAggregator, tlsManager, httpChallengeProvider, tlsChallengeProvider, routinesPool)
// Entrypoints
@@ -365,7 +365,7 @@ func switchRouter(routerFactory *server.RouterFactory, serverEntryPointsTCP serv
}
// initACMEProvider creates an acme provider from the ACME part of globalConfiguration.
func initACMEProvider(c *static.Configuration, providerAggregator *aggregator.ProviderAggregator, tlsManager *traefiktls.Manager, httpChallengeProvider, tlsChallengeProvider challenge.Provider) []*acme.Provider {
func initACMEProvider(c *static.Configuration, providerAggregator *aggregator.ProviderAggregator, tlsManager *traefiktls.Manager, httpChallengeProvider, tlsChallengeProvider challenge.Provider, routinesPool *safe.Pool) []*acme.Provider {
localStores := map[string]*acme.LocalStore{}
var resolvers []*acme.Provider
@@ -375,7 +375,7 @@ func initACMEProvider(c *static.Configuration, providerAggregator *aggregator.Pr
}
if localStores[resolver.ACME.Storage] == nil {
localStores[resolver.ACME.Storage] = acme.NewLocalStore(resolver.ACME.Storage)
localStores[resolver.ACME.Storage] = acme.NewLocalStore(resolver.ACME.Storage, routinesPool)
}
p := &acme.Provider{

View File

@@ -1,4 +1,4 @@
FROM alpine:3.20
FROM alpine:3.21
RUN apk --no-cache --no-progress add \
build-base \
@@ -14,8 +14,8 @@ RUN apk --no-cache --no-progress add \
ruby-json \
zlib-dev
RUN gem install nokogiri --version 1.15.3 --no-document -- --use-system-libraries
RUN gem install html-proofer --version 5.0.7 --no-document -- --use-system-libraries
RUN gem install nokogiri --version 1.18.6 --no-document -- --use-system-libraries
RUN gem install html-proofer --version 5.0.10 --no-document -- --use-system-libraries
# After Ruby, some NodeJS YAY!
RUN apk --no-cache --no-progress add \

View File

@@ -92,7 +92,7 @@ For development purposes, you can specify which tests to run by using (only work
Create `tailscale.secret` file in `integration` directory.
This file need to contains a [Tailscale auth key](https://tailscale.com/kb/1085/auth-keys)
This file needs to contain a [Tailscale auth key](https://tailscale.com/kb/1085/auth-keys)
(an ephemeral, but reusable, one is recommended).
Add this section to your tailscale ACLs to auto-approve the routes for the

View File

@@ -15,13 +15,13 @@ Let's see how.
### General
This [documentation](https://doc.traefik.io/traefik/ "Link to the official Traefik documentation") is built with [MkDocs](https://mkdocs.org/ "Link to website of MkDocs").
This [documentation](https://doc.traefik.io/traefik/ "Link to the official Traefik documentation") is built with [MkDocs](https://mkdocs.org/ "Link to the website of MkDocs").
### Method 1: `Docker` and `make`
Please make sure you have the following requirements installed:
- [Docker](https://www.docker.com/ "Link to website of Docker")
- [Docker](https://www.docker.com/ "Link to the website of Docker")
You can build the documentation and test it locally (with live reloading), using the `docs-serve` target:
@@ -51,7 +51,7 @@ $ make docs-build
Please make sure you have the following requirements installed:
- [Python](https://www.python.org/ "Link to website of Python")
- [Python](https://www.python.org/ "Link to the website of Python")
- [pip](https://pypi.org/project/pip/ "Link to the website of pip on PyPI")
```bash

View File

@@ -32,7 +32,7 @@ The contributor should also meet one or several of the following requirements:
including those of other maintainers and contributors.
- The contributor is active on Traefik Community forums
or other technical forums/boards such as K8S slack, Reddit, StackOverflow, hacker news.
or other technical forums/boards, such as K8S Slack, Reddit, StackOverflow, and Hacker News.
Any existing active maintainer can create an issue to discuss promoting a contributor to maintainer.
Other maintainers can vote on the issue, and if the quorum is reached, the contributor is promoted to maintainer.

View File

@@ -17,7 +17,7 @@ or the list of [confirmed bugs](https://github.com/traefik/traefik/labels/kind%2
## How We Prioritize
We wish we could review every pull request right away, but because it's a time consuming operation, it's not always possible.
We wish we could review every pull request right away, but because it's a time-consuming operation, it's not always possible.
The PRs we are able to handle the fastest are:
@@ -128,7 +128,7 @@ This label can be used when:
Traefik Proxy is made by the community for the community,
as such the goal is to engage the community to make Traefik the best reverse proxy available.
Part of this goal is maintaining a lean codebase and ensuring code velocity.
unfortunately, this means that sometimes we will not be able to merge a pull request.
Unfortunately, this means that sometimes we will not be able to merge a pull request.
Because we respect the work you did, you will always be told why we are closing your pull request.
If you do not agree with our decision, do not worry; closed pull requests are effortless to recreate,

View File

@@ -8,7 +8,7 @@ description: "Security is a key part of Traefik Proxy. Read the technical docume
## Security Advisories
We strongly advise you to join our mailing list to be aware of the latest announcements from our security team.
You can subscribe sending a mail to security+subscribe@traefik.io or on [the online viewer](https://groups.google.com/a/traefik.io/forum/#!forum/security).
You can subscribe by sending an email to security+subscribe@traefik.io or on [the online viewer](https://groups.google.com/a/traefik.io/forum/#!forum/security).
## CVE

View File

@@ -4,23 +4,31 @@
Below is a non-exhaustive list of versions and their maintenance status:
| Version | Release Date | Community Support |
|---------|--------------|--------------------|
| 3.1 | Jul 15, 2024 | Yes |
| 3.0 | Apr 29, 2024 | Ended Jul 15, 2024 |
| 2.11 | Feb 12, 2024 | Ends Apr 29, 2025 |
| 2.10 | Apr 24, 2023 | Ended Feb 12, 2024 |
| 2.9 | Oct 03, 2022 | Ended Apr 24, 2023 |
| 2.8 | Jun 29, 2022 | Ended Oct 03, 2022 |
| 2.7 | May 24, 2022 | Ended Jun 29, 2022 |
| 2.6 | Jan 24, 2022 | Ended May 24, 2022 |
| 2.5 | Aug 17, 2021 | Ended Jan 24, 2022 |
| 2.4 | Jan 19, 2021 | Ended Aug 17, 2021 |
| 2.3 | Sep 23, 2020 | Ended Jan 19, 2021 |
| 2.2 | Mar 25, 2020 | Ended Sep 23, 2020 |
| 2.1 | Dec 11, 2019 | Ended Mar 25, 2020 |
| 2.0 | Sep 16, 2019 | Ended Dec 11, 2019 |
| 1.7 | Sep 24, 2018 | Ended Dec 31, 2021 |
| Version | Release Date | Active Support | Security Support |
|---------|--------------|--------------------|-------------------|
| 3.3 | Jan 06, 2025 | Yes | Yes |
| 3.2 | Oct 28, 2024 | Ended Jan 06, 2025 | No |
| 3.1 | Jul 15, 2024 | Ended Oct 28, 2024 | No |
| 3.0 | Apr 29, 2024 | Ended Jul 15, 2024 | No |
| 2.11 | Feb 12, 2024 | Ends Apr 29, 2025 | Ends Feb 01, 2026 |
| 2.10 | Apr 24, 2023 | Ended Feb 12, 2024 | No |
| 2.9 | Oct 03, 2022 | Ended Apr 24, 2023 | No |
| 2.8 | Jun 29, 2022 | Ended Oct 03, 2022 | No |
| 2.7 | May 24, 2022 | Ended Jun 29, 2022 | No |
| 2.6 | Jan 24, 2022 | Ended May 24, 2022 | No |
| 2.5 | Aug 17, 2021 | Ended Jan 24, 2022 | No |
| 2.4 | Jan 19, 2021 | Ended Aug 17, 2021 | No |
| 2.3 | Sep 23, 2020 | Ended Jan 19, 2021 | No |
| 2.2 | Mar 25, 2020 | Ended Sep 23, 2020 | No |
| 2.1 | Dec 11, 2019 | Ended Mar 25, 2020 | No |
| 2.0 | Sep 16, 2019 | Ended Dec 11, 2019 | No |
| 1.7 | Sep 24, 2018 | Ended Dec 31, 2021 | No |
??? example "Active Support / Security Support"
- **Active support**: Receives any bug fixes.
- **Security support**: Receives only critical bug and security fixes.
This page is maintained and updated periodically to reflect our roadmap and any decisions affecting the end of support for Traefik Proxy.

View File

@@ -38,7 +38,7 @@ services:
Start your `reverse-proxy` with the following command:
```shell
docker-compose up -d reverse-proxy
docker compose up -d reverse-proxy
```
You can open a browser and go to `http://localhost:8080/api/rawdata` to see Traefik's API rawdata (you'll go back there once you have launched a service in step 2).
@@ -68,7 +68,7 @@ The above defines `whoami`: a web service that outputs information about the mac
Start the `whoami` service with the following command:
```shell
docker-compose up -d whoami
docker compose up -d whoami
```
Go back to your browser (`http://localhost:8080/api/rawdata`) and see that Traefik has automatically detected the new container and updated its own configuration.
@@ -92,7 +92,7 @@ IP: 172.27.0.3
Run more instances of your `whoami` service with the following command:
```shell
docker-compose up -d --scale whoami=2
docker compose up -d --scale whoami=2
```
Go back to your browser (`http://localhost:8080/api/rawdata`) and see that Traefik has automatically detected the new instance of the container.

View File

@@ -316,17 +316,17 @@ For complete details, refer to your provider's _Additional configuration_ link.
| Provider Name | Provider Code | Environment Variables | |
|------------------------------------------------------------------------|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------|
| [ACME DNS](https://github.com/joohoi/acme-dns) | `acme-dns` | `ACME_DNS_API_BASE`, `ACME_DNS_STORAGE_PATH` | [Additional configuration](https://go-acme.github.io/lego/dns/acme-dns) |
| [ACME DNS](https://github.com/joohoi/acme-dns) | `acme-dns` | `ACME_DNS_API_BASE`, `ACME_DNS_STORAGE_PATH`, `ACME_DNS_STORAGE_BASE_URL` | [Additional configuration](https://go-acme.github.io/lego/dns/acme-dns) |
| [Alibaba Cloud](https://www.alibabacloud.com) | `alidns` | `ALICLOUD_ACCESS_KEY`, `ALICLOUD_SECRET_KEY`, `ALICLOUD_REGION_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/alidns) |
| [all-inkl](https://all-inkl.com) | `allinkl` | `ALL_INKL_LOGIN`, `ALL_INKL_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/allinkl) |
| [ArvanCloud](https://www.arvancloud.ir/en) | `arvancloud` | `ARVANCLOUD_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/arvancloud) |
| [Auroradns](https://www.pcextreme.com/dns-health-checks) | `auroradns` | `AURORA_USER_ID`, `AURORA_KEY`, `AURORA_ENDPOINT` | [Additional configuration](https://go-acme.github.io/lego/dns/auroradns) |
| [Autodns](https://www.internetx.com/domains/autodns/) | `autodns` | `AUTODNS_API_USER`, `AUTODNS_API_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/autodns) |
| [Azure](https://azure.microsoft.com/services/dns/) (DEPRECATED) | `azure` | `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SUBSCRIPTION_ID`, `AZURE_TENANT_ID`, `AZURE_RESOURCE_GROUP`, `[AZURE_METADATA_ENDPOINT]` | [Additional configuration](https://go-acme.github.io/lego/dns/azure) |
| [Azure](https://azure.microsoft.com/services/dns/) (DEPRECATED) | `azure` | `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SUBSCRIPTION_ID`, `AZURE_TENANT_ID`, `AZURE_RESOURCE_GROUP`, `[AZURE_METADATA_ENDPOINT]` | [Additional configuration](https://go-acme.github.io/lego/dns/azure) |
| [AzureDNS](https://azure.microsoft.com/services/dns/) | `azuredns` | `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_TENANT_ID`, `AZURE_SUBSCRIPTION_ID`, `AZURE_RESOURCE_GROUP`, `[AZURE_ENVIRONMENT]`, `[AZURE_PRIVATE_ZONE]`, `[AZURE_ZONE_NAME]` | [Additional configuration](https://go-acme.github.io/lego/dns/azuredns) |
| [Bindman](https://github.com/labbsr0x/bindman-dns-webhook) | `bindman` | `BINDMAN_MANAGER_ADDRESS` | [Additional configuration](https://go-acme.github.io/lego/dns/bindman) |
| [Blue Cat](https://www.bluecatnetworks.com/) | `bluecat` | `BLUECAT_SERVER_URL`, `BLUECAT_USER_NAME`, `BLUECAT_PASSWORD`, `BLUECAT_CONFIG_NAME`, `BLUECAT_DNS_VIEW` | [Additional configuration](https://go-acme.github.io/lego/dns/bluecat) |
| [Brandit](https://www.brandit.com) | `brandit` | `BRANDIT_API_USERNAME`, `BRANDIT_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/brandit) |
| [Brandit](https://www.brandit.com) (DEPRECATED) | `brandit` | `BRANDIT_API_USERNAME`, `BRANDIT_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/brandit) |
| [Bunny](https://bunny.net) | `bunny` | `BUNNY_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/bunny) |
| [Checkdomain](https://www.checkdomain.de/) | `checkdomain` | `CHECKDOMAIN_TOKEN`, | [Additional configuration](https://go-acme.github.io/lego/dns/checkdomain/) |
| [Civo](https://www.civo.com/) | `civo` | `CIVO_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/civo) |
@@ -334,9 +334,10 @@ For complete details, refer to your provider's _Additional configuration_ link.
| [CloudDNS](https://vshosting.eu/) | `clouddns` | `CLOUDDNS_CLIENT_ID`, `CLOUDDNS_EMAIL`, `CLOUDDNS_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/clouddns) |
| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | `CF_API_EMAIL`, `CF_API_KEY` [^5] or `CF_DNS_API_TOKEN`, `[CF_ZONE_API_TOKEN]` | [Additional configuration](https://go-acme.github.io/lego/dns/cloudflare) |
| [ClouDNS](https://www.cloudns.net/) | `cloudns` | `CLOUDNS_AUTH_ID`, `CLOUDNS_AUTH_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/cloudns) |
| [CloudXNS](https://www.cloudxns.net) | `cloudxns` | `CLOUDXNS_API_KEY`, `CLOUDXNS_SECRET_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/cloudxns) |
| [CloudXNS](https://www.cloudxns.net) (DEPRECATED) | `cloudxns` | `CLOUDXNS_API_KEY`, `CLOUDXNS_SECRET_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/cloudxns) |
| [ConoHa](https://www.conoha.jp) | `conoha` | `CONOHA_TENANT_ID`, `CONOHA_API_USERNAME`, `CONOHA_API_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/conoha) |
| [Constellix](https://constellix.com) | `constellix` | `CONSTELLIX_API_KEY`, `CONSTELLIX_SECRET_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/constellix) |
| [Core-Networks](https://www.core-networks.de) | `corenetworks` | `CORENETWORKS_LOGIN`, `CORENETWORKS_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/corenetworks) |
| [CPanel and WHM](https://cpanel.net/) | `cpanel` | `CPANEL_MODE`, `CPANEL_USERNAME`, `CPANEL_TOKEN`, `CPANEL_BASE_URL` | [Additional configuration](https://go-acme.github.io/lego/dns/cpanel) |
| [Derak Cloud](https://derak.cloud/) | `derak` | `DERAK_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/derak) |
| [deSEC](https://desec.io) | `desec` | `DESEC_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/desec) |
@@ -392,9 +393,11 @@ For complete details, refer to your provider's _Additional configuration_ link.
| [Loopia](https://loopia.com/) | `loopia` | `LOOPIA_API_PASSWORD`, `LOOPIA_API_USER` | [Additional configuration](https://go-acme.github.io/lego/dns/loopia) |
| [LuaDNS](https://luadns.com) | `luadns` | `LUADNS_API_USERNAME`, `LUADNS_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/luadns) |
| [Mail-in-a-Box](https://mailinabox.email) | `mailinabox` | `MAILINABOX_EMAIL`, `MAILINABOX_PASSWORD`, `MAILINABOX_BASE_URL` | [Additional configuration](https://go-acme.github.io/lego/dns/mailinabox) |
| [ManageEngine CloudDNS](https://clouddns.manageengine.com) | `manageengine` | `MANAGEENGINE_CLIENT_ID`, `MANAGEENGINE_CLIENT_SECRET` | [Additional configuration](https://go-acme.github.io/lego/dns/manageengine) |
| [Metaname](https://metaname.net) | `metaname` | `METANAME_ACCOUNT_REFERENCE`, `METANAME_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/metaname) |
| [mijn.host](https://mijn.host/) | `mijnhost` | `MIJNHOST_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/mijnhost) |
| [Mittwald](https://www.mittwald.de) | `mittwald` | `MITTWALD_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/mittwald) |
| [myaddr.{tools,dev,io}](https://myaddr.tools/) | `myaddr` | `MYADDR_PRIVATE_KEYS_MAPPING` | [Additional configuration](https://go-acme.github.io/lego/dns/myaddr) |
| [MyDNS.jp](https://www.mydns.jp/) | `mydnsjp` | `MYDNSJP_MASTER_ID`, `MYDNSJP_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/mydnsjp) |
| [Mythic Beasts](https://www.mythic-beasts.com) | `mythicbeasts` | `MYTHICBEASTS_USER_NAME`, `MYTHICBEASTS_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/mythicbeasts) |
| [name.com](https://www.name.com/) | `namedotcom` | `NAMECOM_USERNAME`, `NAMECOM_API_TOKEN`, `NAMECOM_SERVER` | [Additional configuration](https://go-acme.github.io/lego/dns/namedotcom) |
@@ -416,8 +419,10 @@ For complete details, refer to your provider's _Additional configuration_ link.
| [Porkbun](https://porkbun.com/) | `porkbun` | `PORKBUN_SECRET_API_KEY`, `PORKBUN_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/porkbun) |
| [PowerDNS](https://www.powerdns.com) | `pdns` | `PDNS_API_KEY`, `PDNS_API_URL` | [Additional configuration](https://go-acme.github.io/lego/dns/pdns) |
| [Rackspace](https://www.rackspace.com/cloud/dns) | `rackspace` | `RACKSPACE_USER`, `RACKSPACE_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/rackspace) |
| [Rainyun/雨云](https://www.rainyun.com) | `rainyun` | `RAINYUN_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/rainyun) |
| [RcodeZero](https://www.rcodezero.at) | `rcodezero` | `RCODEZERO_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/rcodezero) |
| [reg.ru](https://www.reg.ru) | `regru` | `REGRU_USERNAME`, `REGRU_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/regru) |
| [Regfish](https://regfish.de) | `regfish` | `regfish` | [Additional configuration](https://go-acme.github.io/lego/dns/regfish) |
| [RFC2136](https://tools.ietf.org/html/rfc2136) | `rfc2136` | `RFC2136_TSIG_KEY`, `RFC2136_TSIG_SECRET`, `RFC2136_TSIG_ALGORITHM`, `RFC2136_NAMESERVER` | [Additional configuration](https://go-acme.github.io/lego/dns/rfc2136) |
| [RimuHosting](https://rimuhosting.com) | `rimuhosting` | `RIMUHOSTING_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/rimuhosting) |
| [Route 53](https://aws.amazon.com/route53/) | `route53` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `[AWS_REGION]`, `[AWS_HOSTED_ZONE_ID]` or a configured user/instance IAM profile. | [Additional configuration](https://go-acme.github.io/lego/dns/route53) |
@@ -430,8 +435,11 @@ For complete details, refer to your provider's _Additional configuration_ link.
| [Shellrent](https://www.shellrent.com) | `shellrent` | `SHELLRENT_USERNAME`, `SHELLRENT_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/shellrent) |
| [Simply.com](https://www.simply.com/en/domains/) | `simply` | `SIMPLY_ACCOUNT_NAME`, `SIMPLY_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/simply) |
| [Sonic](https://www.sonic.com/) | `sonic` | `SONIC_USER_ID`, `SONIC_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/sonic) |
| [Spaceship](https://spaceship.com) | `spaceship` | `SPACESHIP_API_KEY`, `SPACESHIP_API_SECRET` | [Additional configuration](https://go-acme.github.io/lego/dns/spaceship) |
| [Stackpath](https://www.stackpath.com/) | `stackpath` | `STACKPATH_CLIENT_ID`, `STACKPATH_CLIENT_SECRET`, `STACKPATH_STACK_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/stackpath) |
| [Technitium](https://technitium.com) | `technitium` | `TECHNITIUM_SERVER_BASE_URL`, `TECHNITIUM_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/technitium) |
| [Tencent Cloud DNS](https://cloud.tencent.com/product/cns) | `tencentcloud` | `TENCENTCLOUD_SECRET_ID`, `TENCENTCLOUD_SECRET_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/tencentcloud) |
| [Timeweb Cloud](https://timeweb.cloud) | `timewebcloud` | `TIMEWEBCLOUD_AUTH_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/timewebcloud) |
| [TransIP](https://www.transip.nl/) | `transip` | `TRANSIP_ACCOUNT_NAME`, `TRANSIP_PRIVATE_KEY_PATH` | [Additional configuration](https://go-acme.github.io/lego/dns/transip) |
| [UKFast SafeDNS](https://docs.ukfast.co.uk/domains/safedns/index.html) | `safedns` | `SAFEDNS_AUTH_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/safedns) |
| [Ultradns](https://neustarsecurityservices.com/dns-services) | `ultradns` | `ULTRADNS_USERNAME`, `ULTRADNS_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/ultradns) |
@@ -441,11 +449,13 @@ For complete details, refer to your provider's _Additional configuration_ link.
| [Versio](https://www.versio.nl/domeinnamen) | `versio` | `VERSIO_USERNAME`, `VERSIO_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/versio) |
| [VinylDNS](https://www.vinyldns.io) | `vinyldns` | `VINYLDNS_ACCESS_KEY`, `VINYLDNS_SECRET_KEY`, `VINYLDNS_HOST` | [Additional configuration](https://go-acme.github.io/lego/dns/vinyldns) |
| [VK Cloud](https://mcs.mail.ru/) | `vkcloud` | `VK_CLOUD_PASSWORD`, `VK_CLOUD_PROJECT_ID`, `VK_CLOUD_USERNAME` | [Additional configuration](https://go-acme.github.io/lego/dns/vkcloud) |
| [Volcano Engine](https://www.volcengine.com) | `volcengine` | `VOLC_ACCESSKEY`, `VOLC_SECRETKEY` | [Additional configuration](https://go-acme.github.io/lego/dns/volcengine) |
| [Vscale](https://vscale.io/) | `vscale` | `VSCALE_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/vscale) |
| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/vultr) |
| [Webnames](https://www.webnames.ru/) | `webnames` | `WEBNAMES_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/webnames) |
| [Websupport](https://websupport.sk) | `websupport` | `WEBSUPPORT_API_KEY`, `WEBSUPPORT_SECRET` | [Additional configuration](https://go-acme.github.io/lego/dns/websupport) |
| [WEDOS](https://www.wedos.com) | `wedos` | `WEDOS_USERNAME`, `WEDOS_WAPI_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/wedos) |
| [West.cn/西部数码](https://www.west.cn) | `westcn` | `WESTCN_USERNAME`, `WESTCN_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/westcn) |
| [Yandex 360](https://360.yandex.ru) | `yandex360` | `YANDEX360_OAUTH_TOKEN`, `YANDEX360_ORG_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/yandex360) |
| [Yandex Cloud](https://cloud.yandex.com/en/) | `yandexcloud` | `YANDEX_CLOUD_FOLDER_ID`, `YANDEX_CLOUD_IAM_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/yandexcloud) |
| [Yandex](https://yandex.com) | `yandex` | `YANDEX_PDD_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/yandex) |

View File

@@ -21,7 +21,7 @@ The BasicAuth middleware grants access to services to authorized users only.
# To create user:password pair, it's possible to use this command:
# echo $(htpasswd -nB user) | sed -e s/\\$/\\$\\$/g
#
# Also note that dollar signs should NOT be doubled when they not evaluated (e.g. Ansible docker_container module).
# Also note that dollar signs should NOT be doubled when they are not being evaluated (e.g. Ansible docker_container module).
labels:
- "traefik.http.middlewares.test-auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
```

View File

@@ -12,8 +12,11 @@ Retrying until it Succeeds
TODO: add schema
-->
The Retry middleware reissues requests a given number of times to a backend server if that server does not reply.
As soon as the server answers, the middleware stops retrying, regardless of the response status.
The Retry middleware reissues requests a given number of times when it cannot contact the backend service.
This applies at the transport level (TCP).
If the service does not respond to the initial connection attempt, the middleware retries.
However, once the service responds, regardless of the HTTP status code, the middleware considers it operational and stops retrying.
This means that the retry mechanism does not handle HTTP errors; it only retries when there is no response at the TCP level.
The Retry middleware has an optional configuration to enable an exponential backoff.
## Configuration Examples

View File

@@ -649,3 +649,10 @@ As a consequence, middlewares do not have access to those Connection headers,
and a new option has been introduced to specify which ones could go through the middleware chain before being removed: `<entrypoint>.forwardedHeaders.connection`.
Please check out the [entrypoint forwarded headers connection option configuration](../routing/entrypoints.md#forwarded-headers) documentation.
## v2.11.14
### X-Forwarded-Prefix
In `v2.11.14`, the `X-Forwarded-Prefix` header is now handled like the other `X-Forwarded-*` headers: Traefik removes it when it's sent from an untrusted source.
Please refer to the Forwarded headers [documentation](../routing/entrypoints.md#forwarded-headers) for more details.

View File

@@ -59,6 +59,20 @@ If the given format is unsupported, the default (CLF) is used instead.
<remote_IP_address> - <client_user_name_if_available> [<timestamp>] "<request_method> <request_path> <request_protocol>" <HTTP_status> <content-length> "<request_referrer>" "<request_user_agent>" <number_of_requests_received_since_Traefik_started> "<Traefik_router_name>" "<Traefik_server_URL>" <request_duration_in_ms>ms
```
```yaml tab="File (YAML)"
accessLog:
format: "json"
```
```toml tab="File (TOML)"
[accessLog]
format = "json"
```
```bash tab="CLI"
--accesslog.format=json
```
### `bufferingSize`
To write the logs in an asynchronous fashion, specify a `bufferingSize` option.
@@ -222,9 +236,7 @@ accessLog:
| `OriginDuration` | The time taken (in nanoseconds) by the origin server ('upstream') to return its response. |
| `OriginContentSize` | The content length specified by the origin server, or 0 if unspecified. |
| `OriginStatus` | The HTTP status code returned by the origin server. If the request was handled by this Traefik instance (e.g. with a redirect), then this value will be absent (0). |
| `OriginStatusLine` | `OriginStatus` + Status code explanation |
| `DownstreamStatus` | The HTTP status code returned to the client. |
| `DownstreamStatusLine` | `DownstreamStatus` + Status code explanation |
| `DownstreamContentSize` | The number of bytes in the response entity returned to the client. This is in addition to the "Content-Length" header, which may be present in the origin response. |
| `RequestCount` | The number of requests received since the Traefik instance started. |
| `GzipRatio` | The response body compression ratio achieved. |

View File

@@ -70,7 +70,7 @@ And then define a routing configuration on Traefik itself with the
### `insecure`
Enable the API in `insecure` mode, which means that the API will be available directly on the entryPoint named `traefik`.
Enable the API in `insecure` mode, which means that the API will be available directly on the entryPoint named `traefik`, on path `/api`.
!!! info
If the entryPoint named `traefik` is not configured, it will be automatically created on port 8080.

View File

@@ -37,32 +37,15 @@ Start by enabling the dashboard by using the following option from [Traefik's AP
on the [static configuration](../getting-started/configuration-overview.md#the-static-configuration):
```yaml tab="File (YAML)"
api:
# Dashboard
#
# Optional
# Default: true
#
dashboard: true
api: {}
```
```toml tab="File (TOML)"
[api]
# Dashboard
#
# Optional
# Default: true
#
dashboard = true
```
```bash tab="CLI"
# Dashboard
#
# Optional
# Default: true
#
--api.dashboard=true
--api=true
```
Then define a routing configuration on Traefik itself,
@@ -106,27 +89,47 @@ rule = "Host(`traefik.example.com`) && PathPrefix(`/api`, `/dashboard`)"
## Insecure Mode
This mode is not recommended because it does not allow the use of security features.
When _insecure_ mode is enabled, one can access the dashboard on the `traefik` port (default: `8080`) of the Traefik instance,
at the following URL: `http://<Traefik IP>:8080/dashboard/` (trailing slash is mandatory).
To enable the "insecure mode", use the following options from [Traefik's API](./api.md#insecure):
This mode is **not** recommended because it does not allow security features.
For example, it is not possible to add an authentication middleware with this mode.
It should be used for testing purpose **only**.
To enable the _insecure_ mode, use the following options from [Traefik's API](./api.md#insecure):
```yaml tab="File (YAML)"
api:
dashboard: true
insecure: true
```
```toml tab="File (TOML)"
[api]
dashboard = true
insecure = true
```
```bash tab="CLI"
--api.dashboard=true --api.insecure=true
--api.insecure=true
```
You can now access the dashboard on the port `8080` of the Traefik instance,
at the following URL: `http://<Traefik IP>:8080/dashboard/` (trailing slash is mandatory).
## Disable The Dashboard
By default, the dashboard is enabled when the API is enabled.
If necessary, the dashboard can be disabled by using the following option.
```yaml tab="File (YAML)"
api:
dashboard: false
```
```toml tab="File (TOML)"
[api]
dashboard = false
```
```bash tab="CLI"
--api.dashboard=false
```
{!traefik-for-business-applications.md!}

View File

@@ -525,7 +525,7 @@ providers:
```
```bash tab="CLI"
--providers.consulcatalog.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
--providers.consulcatalog.defaultRule='Host(`{{ .Name }}.{{ index .Labels "customLabel"}}`)'
# ...
```

View File

@@ -466,7 +466,7 @@ providers:
```
```bash tab="CLI"
--providers.docker.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
--providers.docker.defaultRule='Host(`{{ .Name }}.{{ index .Labels "customLabel"}}`)'
# ...
```

View File

@@ -259,7 +259,7 @@ providers:
```
```bash tab="CLI"
--providers.ecs.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
--providers.ecs.defaultRule='Host(`{{ .Name }}.{{ index .Labels "customLabel"}}`)'
# ...
```
@@ -301,7 +301,8 @@ _Optional_
If `region` is not provided, it is resolved from the EC2 metadata endpoint for EC2 tasks.
In a FARGATE context it is resolved from the `AWS_REGION` environment variable.
If `accessKeyID` and `secretAccessKey` are not provided, credentials are resolved in the following order:
If `accessKeyID` and `secretAccessKey` are not provided, credentials are resolved in the order specified by the
[default credential chain of AWS SDK for Go V2](https://docs.aws.amazon.com/sdk-for-go/v2/developer-guide/configure-gosdk.html#specifying-credentials):
- Using the environment variables `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_SESSION_TOKEN`.
- Using shared credentials, determined by `AWS_PROFILE` and `AWS_SHARED_CREDENTIALS_FILE`, defaults to `default` and `~/.aws/credentials`.

View File

@@ -138,7 +138,7 @@ providers:
```
```bash tab="CLI"
--providers.marathon.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
--providers.marathon.defaultRule='Host(`{{ .Name }}.{{ index .Labels "customLabel"}}`)'
# ...
```

View File

@@ -374,7 +374,7 @@ providers:
```
```bash tab="CLI"
--providers.nomad.defaultRule="Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
--providers.nomad.defaultRule='Host(`{{ .Name }}.{{ index .Labels "customLabel"}}`)'
# ...
```

View File

@@ -121,7 +121,7 @@ providers:
```
```bash tab="CLI"
--providers.rancher.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
--providers.rancher.defaultRule='Host(`{{ .Name }}.{{ index .Labels "customLabel"}}`)'
# ...
```

View File

@@ -10,6 +10,12 @@ A Story of KV store & Containers
Store your configuration in Redis and let Traefik do the rest!
!!! tip "Dynamic configuration updates"
Dynamic configuration updates require Redis [keyspace notifications](https://redis.io/docs/latest/develop/use/keyspace-notifications) to be enabled.
Cloud-managed Redis services (e.g., GCP Memorystore, AWS ElastiCache) may disable this by default due to CPU performance issues.
For more information, see the [Redis](https://redis.io/docs/latest/develop/use/keyspace-notifications/) documentation or refer to your cloud provider's documentation for specific configuration steps.
## Routing Configuration
See the dedicated section in [routing](../routing/providers/kv.md).

View File

@@ -5,4 +5,23 @@ description: "Reference the environment variables for static configuration in Tr
# Static Configuration: Environment variables
!!! warning "Environment Variable Casing"
Traefik normalizes the environment variable key-value pairs by lowercasing them.
This means that when you interpolate a string in an environment variable's name,
that string will be treated as lowercase, regardless of its original casing.
For example, assuming you have set environment variables as follows:
```bash
export TRAEFIK_ENTRYPOINTS_WEB=true
export TRAEFIK_ENTRYPOINTS_WEB_ADDRESS=:80
export TRAEFIK_CERTIFICATESRESOLVERS_myResolver=true
export TRAEFIK_CERTIFICATESRESOLVERS_myResolver_ACME_CASERVER=....
```
Although the Entrypoint is named `WEB` and the Certificate Resolver is named `myResolver`,
they have to be referenced respectively as `web`, and `myresolver` in the configuration.
--8<-- "content/reference/static-configuration/env-ref.md"

View File

@@ -254,8 +254,7 @@ entryPoints:
```toml tab="File (TOML)"
[entryPoints.foo]
[entryPoints.foo.allowACMEByPass]
allowACMEByPass = true
allowACMEByPass = true
```
```bash tab="CLI"
@@ -472,7 +471,7 @@ Setting them has no effect for UDP entryPoints.
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.
We strongly suggest to adapt this value accordingly to the your needs.
We strongly suggest adapting this value accordingly to your needs.
```yaml tab="File (YAML)"
## Static configuration

View File

@@ -31,7 +31,7 @@ Our starting point is the docker-compose configuration file, to start the k3s cl
You can start it with:
```bash
docker-compose -f k3s.yml up
docker compose -f k3s.yml up
```
```yaml

View File

@@ -46,7 +46,7 @@ For the DNS challenge, you'll need:
#- "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
```
- Run `docker-compose up -d` within the folder where you created the previous file.
- Run `docker compose up -d` within the folder where you created the previous file.
- Wait a bit and visit `https://your_own_domain` to confirm everything went fine.
!!! Note

View File

@@ -32,7 +32,7 @@ For the HTTP challenge you will need:
#- "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
```
- Run `docker-compose up -d` within the folder where you created the previous file.
- Run `docker compose up -d` within the folder where you created the previous file.
- Wait a bit and visit `https://your_own_domain` to confirm everything went fine.
!!! Note

View File

@@ -32,7 +32,7 @@ For the TLS challenge you will need:
#- "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
```
- Run `docker-compose up -d` within the folder where you created the previous file.
- Run `docker compose up -d` within the folder where you created the previous file.
- Wait a bit and visit `https://your_own_domain` to confirm everything went fine.
!!! Note

View File

@@ -46,7 +46,7 @@ Create a `docker-compose.yml` file with the following content:
Replace `whoami.localhost` by your **own domain** within the `traefik.http.routers.whoami.rule` label of the `whoami` service.
Now run `docker-compose up -d` within the folder where you created the previous file.
Now run `docker compose up -d` within the folder where you created the previous file.
This will start Docker Compose in background mode.
!!! info "This can take a moment"

View File

@@ -1,4 +1,4 @@
FROM alpine:3.20
FROM alpine:3.21
ENV PATH="${PATH}:/venv/bin"

View File

@@ -27,7 +27,7 @@ theme:
prev: 'Previous'
next: 'Next'
copyright: 'Traefik Labs • Copyright &copy; 2016-2024'
copyright: 'Traefik Labs • Copyright &copy; 2016-2025'
extra_javascript:
- assets/js/hljs/highlight.pack.js # Download from https://highlightjs.org/download/ and enable YAML, TOML and Dockerfile

153
go.mod
View File

@@ -7,7 +7,13 @@ require (
github.com/ExpediaDotCom/haystack-client-go v0.0.0-20190315171017-e7edbdf53a61 // No tag on the repo.
github.com/Masterminds/sprig/v3 v3.2.3
github.com/abbot/go-http-auth v0.0.0-00010101000000-000000000000
github.com/aws/aws-sdk-go v1.44.327
github.com/aws/aws-sdk-go-v2 v1.36.2
github.com/aws/aws-sdk-go-v2/config v1.28.7
github.com/aws/aws-sdk-go-v2/credentials v1.17.48
github.com/aws/aws-sdk-go-v2/service/ec2 v1.203.1
github.com/aws/aws-sdk-go-v2/service/ecs v1.53.15
github.com/aws/aws-sdk-go-v2/service/ssm v1.56.13
github.com/aws/smithy-go v1.22.2
github.com/cenkalti/backoff/v4 v4.3.0
github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd // No tag on the repo.
github.com/coreos/go-systemd/v22 v22.5.0
@@ -15,19 +21,19 @@ require (
github.com/docker/docker v27.1.1+incompatible
github.com/docker/go-connections v0.5.0
github.com/fatih/structs v1.1.0
github.com/fsnotify/fsnotify v1.7.0
github.com/fsnotify/fsnotify v1.8.0
github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2 // No tag on the repo.
github.com/go-acme/lego/v4 v4.19.2
github.com/go-acme/lego/v4 v4.22.2
github.com/go-kit/kit v0.13.0
github.com/go-kit/log v0.2.1
github.com/golang/protobuf v1.5.4
github.com/google/go-github/v28 v28.1.1
github.com/gorilla/mux v1.8.1
github.com/gorilla/websocket v1.5.0
github.com/gorilla/websocket v1.5.3
github.com/hashicorp/consul/api v1.26.1
github.com/hashicorp/go-hclog v1.6.3
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-version v1.6.0
github.com/hashicorp/go-version v1.7.0
github.com/hashicorp/nomad/api v0.0.0-20231213195942-64e3dca9274b // No tag on the repo.
github.com/influxdata/influxdb-client-go/v2 v2.7.0
github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab // No tag on the repo.
@@ -51,29 +57,28 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // No tag on the repo.
github.com/prometheus/client_golang v1.19.1
github.com/prometheus/client_model v0.5.0
github.com/quic-go/quic-go v0.47.0
github.com/quic-go/quic-go v0.48.2
github.com/rancher/go-rancher-metadata v0.0.0-20200311180630-7f4c936a06ac // No tag on the repo.
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.9.0
github.com/stretchr/testify v1.10.0
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154 // No tag on the repo.
github.com/testcontainers/testcontainers-go v0.32.0
github.com/traefik/paerser v0.2.1
github.com/traefik/paerser v0.2.2
github.com/traefik/yaegi v0.16.1
github.com/uber/jaeger-client-go v2.30.0+incompatible
github.com/uber/jaeger-lib v2.4.1+incompatible
github.com/unrolled/render v1.0.2
github.com/unrolled/secure v1.0.9
github.com/vulcand/oxy/v2 v2.0.0
github.com/vulcand/oxy/v2 v2.0.3
github.com/vulcand/predicate v1.2.0
go.elastic.co/apm/module/apmot/v2 v2.4.8
go.elastic.co/apm/v2 v2.4.8
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // No tag on the repo.
golang.org/x/mod v0.21.0
golang.org/x/net v0.29.0
golang.org/x/text v0.18.0
golang.org/x/time v0.6.0
golang.org/x/tools v0.25.0
google.golang.org/grpc v1.66.1
golang.org/x/mod v0.22.0
golang.org/x/net v0.37.0
golang.org/x/text v0.23.0
golang.org/x/time v0.8.0
golang.org/x/tools v0.28.0
google.golang.org/grpc v1.67.1
gopkg.in/DataDog/dd-trace-go.v1 v1.56.1
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.26.3
@@ -86,17 +91,17 @@ require (
)
require (
cloud.google.com/go/auth v0.9.3 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
cloud.google.com/go/compute/metadata v0.5.1 // indirect
cloud.google.com/go/auth v0.13.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
dario.cat/mergo v1.0.0 // indirect
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
@@ -124,35 +129,30 @@ require (
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
github.com/VividCortex/gohistogram v1.0.0 // indirect
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.63.15 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.63.72 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c // indirect
github.com/aws/aws-sdk-go-v2 v1.30.5 // indirect
github.com/aws/aws-sdk-go-v2/config v1.27.33 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.32 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.33 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.33 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 // indirect
github.com/aws/aws-sdk-go-v2/service/lightsail v1.40.6 // indirect
github.com/aws/aws-sdk-go-v2/service/route53 v1.43.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 // indirect
github.com/aws/smithy-go v1.20.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.14 // indirect
github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.8 // indirect
github.com/aws/aws-sdk-go-v2/service/route53 v1.46.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.24.11 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.10 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.9 // indirect
github.com/benbjohnson/clock v1.3.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/civo/civogo v0.3.11 // indirect
github.com/cloudflare/cloudflare-go v0.104.0 // indirect
github.com/cloudflare/cloudflare-go v0.112.0 // indirect
github.com/containerd/containerd v1.7.20 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/cpu/goacmedns v0.1.1 // indirect
github.com/cpuguy83/dockercfg v0.3.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/deepmap/oapi-codegen v1.9.1 // indirect
@@ -168,13 +168,13 @@ require (
github.com/elastic/go-windows v1.0.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/exoscale/egoscale/v3 v3.1.5 // indirect
github.com/exoscale/egoscale/v3 v3.1.7 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
@@ -185,16 +185,15 @@ require (
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.16.0 // indirect
github.com/go-resty/resty/v2 v2.13.1 // indirect
github.com/go-resty/resty/v2 v2.16.2 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/go-viper/mapstructure/v2 v2.1.0 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/go-zookeeper/zk v1.0.3 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/goccy/go-json v0.10.4 // indirect
github.com/gofrs/flock v0.12.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
@@ -203,8 +202,8 @@ require (
github.com/google/s2a-go v0.1.8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
github.com/gophercloud/gophercloud v1.14.0 // indirect
github.com/googleapis/gax-go/v2 v2.14.0 // indirect
github.com/gophercloud/gophercloud v1.14.1 // indirect
github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 // indirect
github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf // indirect
github.com/hashicorp/cronexpr v1.1.2 // indirect
@@ -218,7 +217,7 @@ require (
github.com/hashicorp/hcl v1.0.1-vault-5 // indirect
github.com/hashicorp/serf v0.10.1 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.114 // indirect
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128 // indirect
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect
@@ -234,7 +233,7 @@ require (
github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
github.com/labbsr0x/goh v1.0.1 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/linode/linodego v1.40.0 // indirect
github.com/linode/linodego v1.44.0 // indirect
github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
github.com/liquidweb/liquidweb-go v1.6.4 // indirect
github.com/looplab/fsm v0.1.0 // indirect
@@ -261,9 +260,10 @@ require (
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
github.com/nrdcg/auroradns v1.1.0 // indirect
github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 // indirect
github.com/nrdcg/desec v0.8.0 // indirect
github.com/nrdcg/desec v0.10.0 // indirect
github.com/nrdcg/dnspod-go v0.4.0 // indirect
github.com/nrdcg/freemyip v0.2.0 // indirect
github.com/nrdcg/freemyip v0.3.0 // indirect
github.com/nrdcg/goacmedns v0.2.0 // indirect
github.com/nrdcg/goinwx v0.10.0 // indirect
github.com/nrdcg/mailinabox v0.2.0 // indirect
github.com/nrdcg/namesilo v0.2.1 // indirect
@@ -275,10 +275,11 @@ require (
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 // indirect
github.com/oracle/oci-go-sdk/v65 v65.73.0 // indirect
github.com/oracle/oci-go-sdk/v65 v65.81.1 // indirect
github.com/outcaste-io/ristretto v0.2.3 // indirect
github.com/ovh/go-ovh v1.6.0 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/peterhellberg/link v1.2.0 // indirect
github.com/philhofer/fwd v1.1.2 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
@@ -287,10 +288,11 @@ require (
github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/redis/go-redis/v9 v9.2.1 // indirect
github.com/redis/go-redis/v9 v9.6.3 // indirect
github.com/regfish/regfish-dnsapi-go v0.1.1 // indirect
github.com/sacloud/api-client-go v0.2.10 // indirect
github.com/sacloud/go-http v0.1.8 // indirect
github.com/sacloud/iaas-api-go v1.12.0 // indirect
github.com/sacloud/iaas-api-go v1.14.0 // indirect
github.com/sacloud/packages-go v0.0.10 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
@@ -298,12 +300,12 @@ require (
github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect
github.com/segmentio/fasthash v1.0.3 // indirect
github.com/selectel/domains-go v1.1.0 // indirect
github.com/selectel/go-selvpcclient/v3 v3.1.1 // indirect
github.com/selectel/go-selvpcclient/v3 v3.2.1 // indirect
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
github.com/softlayer/softlayer-go v1.1.5 // indirect
github.com/softlayer/softlayer-go v1.1.7 // indirect
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
github.com/sony/gobreaker v0.5.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
@@ -313,18 +315,19 @@ require (
github.com/spf13/viper v1.18.2 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1002 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1002 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1065 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065 // indirect
github.com/tinylib/msgp v1.1.8 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/transip/gotransip/v6 v6.26.0 // indirect
github.com/ultradns/ultradns-go-sdk v1.7.0-20240913052650-970ca9a // indirect
github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec // indirect
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
github.com/volcengine/volc-sdk-golang v1.0.189 // indirect
github.com/vultr/govultr/v3 v3.9.1 // indirect
github.com/yandex-cloud/go-genproto v0.0.0-20240911120709-1fa0cb6f47c2 // indirect
github.com/yandex-cloud/go-sdk v0.0.0-20240911121212-e4e74d0d02f5 // indirect
github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c // indirect
github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
go.elastic.co/apm/module/apmhttp/v2 v2.4.8 // indirect
go.elastic.co/fastjson v1.1.0 // indirect
@@ -332,7 +335,6 @@ require (
go.etcd.io/etcd/client/pkg/v3 v3.5.10 // indirect
go.etcd.io/etcd/client/v3 v3.5.10 // indirect
go.mongodb.org/mongo-driver v1.12.1 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
go.opentelemetry.io/otel v1.29.0 // indirect
go.opentelemetry.io/otel/metric v1.29.0 // indirect
@@ -345,20 +347,21 @@ require (
go.uber.org/zap v1.21.0 // indirect
go4.org/intern v0.0.0-20230525184215-6c62f75575cb // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/term v0.24.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect
golang.org/x/oauth2 v0.24.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/term v0.30.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/api v0.197.0 // indirect
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/protobuf v1.34.2 // indirect
google.golang.org/api v0.214.0 // indirect
google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect
google.golang.org/protobuf v1.35.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/ns1/ns1-go.v2 v2.12.0 // indirect
gopkg.in/ns1/ns1-go.v2 v2.13.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
howett.net/plist v0.0.0-20181124034731-591f970eefbb // indirect
inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a // indirect

428
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -9,24 +9,24 @@
# Use certificate in net/internal/testcert.go
rootCAs = [ """
-----BEGIN CERTIFICATE-----
MIIDOTCCAiGgAwIBAgIQSRJrEpBGFc7tNb1fb5pKFzANBgkqhkiG9w0BAQsFADAS
MIIDSDCCAjCgAwIBAgIQEP/md970HysdBTpuzDOf0DANBgkqhkiG9w0BAQsFADAS
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEA6Gba5tHV1dAKouAaXO3/ebDUU4rvwCUg/CNaJ2PT5xLD4N1Vcb8r
bFSW2HXKq+MPfVdwIKR/1DczEoAGf/JWQTW7EgzlXrCd3rlajEX2D73faWJekD0U
aUgz5vtrTXZ90BQL7WvRICd7FlEZ6FPOcPlumiyNmzUqtwGhO+9ad1W5BqJaRI6P
YfouNkwR6Na4TzSj5BrqUfP0FwDizKSJ0XXmh8g8G9mtwxOSN3Ru1QFc61Xyeluk
POGKBV/q6RBNklTNe0gI8usUMlYyoC7ytppNMW7X2vodAelSu25jgx2anj9fDVZu
h7AXF5+4nJS4AAt0n1lNY7nGSsdZas8PbQIDAQABo4GIMIGFMA4GA1UdDwEB/wQE
MIIBCgKCAQEAxcl69ROJdxjN+MJZnbFrYxyQooADCsJ6VDkuMyNQIix/Hk15Nk/u
FyBX1Me++aEpGmY3RIY4fUvELqT/srvAHsTXwVVSttMcY8pcAFmXSqo3x4MuUTG/
jCX3Vftj0r3EM5M8ImY1rzA/jqTTLJg00rD+DmuDABcqQvoXw/RV8w1yTRi5BPoH
DFD/AWTt/YgMvk1l2Yq/xI8VbMUIpjBoGXxWsSevQ5i2s1mk9/yZzu0Ysp1tTlzD
qOPa4ysFjBitdXiwfxjxtv5nXqOCP5rheKO0sWLk0fetMp1OV5JSJMAJw6c2ZMkl
U2WMqAEpRjdE/vHfIuNg+yGaRRqI07NZRQIDAQABo4GXMIGUMA4GA1UdDwEB/wQE
AwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBStsdjh3/JCXXYlQryOrL4Sh7BW5TAuBgNVHREEJzAlggtleGFtcGxlLmNv
bYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAxWGI
5NhpF3nwwy/4yB4i/CwwSpLrWUa70NyhvprUBC50PxiXav1TeDzwzLx/o5HyNwsv
cxv3HdkLW59i/0SlJSrNnWdfZ19oTcS+6PtLoVyISgtyN6DpkKpdG1cOkW3Cy2P2
+tK/tKHRP1Y/Ra0RiDpOAmqn0gCOFGz8+lqDIor/T7MTpibL3IxqWfPrvfVRHL3B
grw/ZQTTIVjjh4JBSW3WyWgNo/ikC1lrVxzl4iPUGptxT36Cr7Zk2Bsg0XqwbOvK
5d+NTDREkSnUbie4GeutujmX3Dsx88UiV6UY/4lHJa6I5leHUNOHahRbpbWeOfs/
WkBKOclmOV2xlTVuPw==
DgQWBBQR5QIzmacmw78ZI1C4MXw7Q0wJ1jA9BgNVHREENjA0ggtleGFtcGxlLmNv
bYINKi5leGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG
9w0BAQsFAAOCAQEACrRNgiioUDzxQftd0fwOa6iRRcPampZRDtuaF68yNHoNWbOu
LUwc05eOWxRq3iABGSk2xg+FXM3DDeW4HhAhCFptq7jbVZ+4Jj6HeJG9mYRatAxR
Y/dEpa0D0EHhDxxVg6UzKOXB355n0IetGE/aWvyTV9SiDs6QsaC57Q9qq1/mitx5
2GFBoapol9L5FxCc77bztzK8CpLujkBi25Vk6GAFbl27opLfpyxkM+rX/T6MXCPO
6/YBacNZ7ff1/57Etg4i5mNA6ubCpuc4Gi9oYqCNNohftr2lkJr7REdDR6OW0lsL
rF7r4gUnKeC7mYIH1zypY7laskopiLFAfe96Kg==
-----END CERTIFICATE-----
"""]

View File

@@ -1,20 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDOTCCAiGgAwIBAgIQSRJrEpBGFc7tNb1fb5pKFzANBgkqhkiG9w0BAQsFADAS
MIIDSDCCAjCgAwIBAgIQEP/md970HysdBTpuzDOf0DANBgkqhkiG9w0BAQsFADAS
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEA6Gba5tHV1dAKouAaXO3/ebDUU4rvwCUg/CNaJ2PT5xLD4N1Vcb8r
bFSW2HXKq+MPfVdwIKR/1DczEoAGf/JWQTW7EgzlXrCd3rlajEX2D73faWJekD0U
aUgz5vtrTXZ90BQL7WvRICd7FlEZ6FPOcPlumiyNmzUqtwGhO+9ad1W5BqJaRI6P
YfouNkwR6Na4TzSj5BrqUfP0FwDizKSJ0XXmh8g8G9mtwxOSN3Ru1QFc61Xyeluk
POGKBV/q6RBNklTNe0gI8usUMlYyoC7ytppNMW7X2vodAelSu25jgx2anj9fDVZu
h7AXF5+4nJS4AAt0n1lNY7nGSsdZas8PbQIDAQABo4GIMIGFMA4GA1UdDwEB/wQE
MIIBCgKCAQEAxcl69ROJdxjN+MJZnbFrYxyQooADCsJ6VDkuMyNQIix/Hk15Nk/u
FyBX1Me++aEpGmY3RIY4fUvELqT/srvAHsTXwVVSttMcY8pcAFmXSqo3x4MuUTG/
jCX3Vftj0r3EM5M8ImY1rzA/jqTTLJg00rD+DmuDABcqQvoXw/RV8w1yTRi5BPoH
DFD/AWTt/YgMvk1l2Yq/xI8VbMUIpjBoGXxWsSevQ5i2s1mk9/yZzu0Ysp1tTlzD
qOPa4ysFjBitdXiwfxjxtv5nXqOCP5rheKO0sWLk0fetMp1OV5JSJMAJw6c2ZMkl
U2WMqAEpRjdE/vHfIuNg+yGaRRqI07NZRQIDAQABo4GXMIGUMA4GA1UdDwEB/wQE
AwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBStsdjh3/JCXXYlQryOrL4Sh7BW5TAuBgNVHREEJzAlggtleGFtcGxlLmNv
bYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAxWGI
5NhpF3nwwy/4yB4i/CwwSpLrWUa70NyhvprUBC50PxiXav1TeDzwzLx/o5HyNwsv
cxv3HdkLW59i/0SlJSrNnWdfZ19oTcS+6PtLoVyISgtyN6DpkKpdG1cOkW3Cy2P2
+tK/tKHRP1Y/Ra0RiDpOAmqn0gCOFGz8+lqDIor/T7MTpibL3IxqWfPrvfVRHL3B
grw/ZQTTIVjjh4JBSW3WyWgNo/ikC1lrVxzl4iPUGptxT36Cr7Zk2Bsg0XqwbOvK
5d+NTDREkSnUbie4GeutujmX3Dsx88UiV6UY/4lHJa6I5leHUNOHahRbpbWeOfs/
WkBKOclmOV2xlTVuPw==
DgQWBBQR5QIzmacmw78ZI1C4MXw7Q0wJ1jA9BgNVHREENjA0ggtleGFtcGxlLmNv
bYINKi5leGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG
9w0BAQsFAAOCAQEACrRNgiioUDzxQftd0fwOa6iRRcPampZRDtuaF68yNHoNWbOu
LUwc05eOWxRq3iABGSk2xg+FXM3DDeW4HhAhCFptq7jbVZ+4Jj6HeJG9mYRatAxR
Y/dEpa0D0EHhDxxVg6UzKOXB355n0IetGE/aWvyTV9SiDs6QsaC57Q9qq1/mitx5
2GFBoapol9L5FxCc77bztzK8CpLujkBi25Vk6GAFbl27opLfpyxkM+rX/T6MXCPO
6/YBacNZ7ff1/57Etg4i5mNA6ubCpuc4Gi9oYqCNNohftr2lkJr7REdDR6OW0lsL
rF7r4gUnKeC7mYIH1zypY7laskopiLFAfe96Kg==
-----END CERTIFICATE-----

View File

@@ -41,7 +41,7 @@ func (s *HTTPSuite) TestSimpleConfiguration() {
Services: map[string]*dynamic.Service{
"serviceHTTP": {
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: boolRef(true),
PassHostHeader: pointer(true),
Servers: []dynamic.Server{
{
URL: "http://bacon:80",
@@ -81,6 +81,4 @@ func startTestServerWithResponse(response []byte) (ts *httptest.Server) {
return ts
}
func boolRef(b bool) *bool {
return &b
}
func pointer[T any](v T) *T { return &v }

View File

@@ -16,6 +16,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v2/integration/try"
"golang.org/x/net/http2"
"golang.org/x/net/websocket"
)
@@ -451,6 +452,44 @@ func (s *WebsocketSuite) TestSSLhttp2() {
assert.Equal(s.T(), "OK", string(msg))
}
func (s *WebsocketSuite) TestSettingEnableConnectProtocol() {
file := s.adaptFile("fixtures/websocket/config_https.toml", struct {
WebsocketServer string
}{
WebsocketServer: "http://127.0.0.1",
})
s.traefikCmd(withConfigFile(file), "--log.level=DEBUG", "--accesslog")
// Wait for traefik.
err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
require.NoError(s.T(), err)
// Add client self-signed cert.
roots := x509.NewCertPool()
certContent, err := os.ReadFile("./resources/tls/local.cert")
require.NoError(s.T(), err)
roots.AppendCertsFromPEM(certContent)
// Open a connection to inspect SettingsFrame.
conn, err := tls.Dial("tcp", "127.0.0.1:8000", &tls.Config{
RootCAs: roots,
NextProtos: []string{"h2"},
})
require.NoError(s.T(), err)
framer := http2.NewFramer(nil, conn)
frame, err := framer.ReadFrame()
require.NoError(s.T(), err)
fr, ok := frame.(*http2.SettingsFrame)
require.True(s.T(), ok)
_, ok = fr.Value(http2.SettingEnableConnectProtocol)
assert.False(s.T(), ok)
}
func (s *WebsocketSuite) TestHeaderAreForwarded() {
upgrader := gorillawebsocket.Upgrader{} // use default options

View File

@@ -25,14 +25,22 @@ func main() {
ParseFiles("./.goreleaser.yml.tmpl"),
)
outputPath := path.Join(os.TempDir(), fmt.Sprintf(".goreleaser_%s.yml", goos))
goarch := ""
outputFileName := fmt.Sprintf(".goreleaser_%s.yml", goos)
if strings.Contains(goos, "-") {
split := strings.Split(goos, "-")
goos = split[0]
goarch = split[1]
}
outputPath := path.Join(os.TempDir(), outputFileName)
output, err := os.Create(outputPath)
if err != nil {
log.Fatal(err)
}
err = tmpl.Execute(output, map[string]string{"GOOS": goos})
err = tmpl.Execute(output, map[string]string{"GOOS": goos, "GOARCH": goarch})
if err != nil {
log.Fatal(err)
}

View File

@@ -3,7 +3,7 @@ package dashboard
import (
"io/fs"
"net/http"
"net/url"
"strings"
"github.com/gorilla/mux"
"github.com/traefik/traefik/v2/webui"
@@ -25,7 +25,8 @@ func Append(router *mux.Router, customAssets fs.FS) {
router.Methods(http.MethodGet).
Path("/").
HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
http.Redirect(resp, req, safePrefix(req)+"/dashboard/", http.StatusFound)
prefix := strings.TrimSuffix(req.Header.Get("X-Forwarded-Prefix"), "/")
http.Redirect(resp, req, prefix+"/dashboard/", http.StatusFound)
})
router.Methods(http.MethodGet).
@@ -48,21 +49,3 @@ func (g Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Security-Policy", "frame-src 'self' https://traefik.io https://*.traefik.io;")
http.FileServerFS(assets).ServeHTTP(w, r)
}
func safePrefix(req *http.Request) string {
prefix := req.Header.Get("X-Forwarded-Prefix")
if prefix == "" {
return ""
}
parse, err := url.Parse(prefix)
if err != nil {
return ""
}
if parse.Host != "" {
return ""
}
return parse.Path
}

View File

@@ -10,53 +10,8 @@ import (
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_safePrefix(t *testing.T) {
testCases := []struct {
desc string
value string
expected string
}{
{
desc: "host",
value: "https://example.com",
expected: "",
},
{
desc: "host with path",
value: "https://example.com/foo/bar?test",
expected: "",
},
{
desc: "path",
value: "/foo/bar",
expected: "/foo/bar",
},
{
desc: "path without leading slash",
value: "foo/bar",
expected: "foo/bar",
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
req, err := http.NewRequest(http.MethodGet, "http://localhost", nil)
require.NoError(t, err)
req.Header.Set("X-Forwarded-Prefix", test.value)
prefix := safePrefix(req)
assert.Equal(t, test.expected, prefix)
})
}
}
func Test_ContentSecurityPolicy(t *testing.T) {
testCases := []struct {
desc string

View File

@@ -19,7 +19,7 @@ import (
"github.com/traefik/traefik/v2/pkg/config/static"
)
func Bool(v bool) *bool { return &v }
func pointer[T any](v T) *T { return &v }
func TestHandler_HTTP(t *testing.T) {
type expected struct {
@@ -337,7 +337,7 @@ func TestHandler_HTTP(t *testing.T) {
si := &runtime.ServiceInfo{
Service: &dynamic.Service{
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
Servers: []dynamic.Server{
{
URL: "http://127.0.0.1",
@@ -354,7 +354,7 @@ func TestHandler_HTTP(t *testing.T) {
si := &runtime.ServiceInfo{
Service: &dynamic.Service{
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
Servers: []dynamic.Server{
{
URL: "http://127.0.0.2",
@@ -423,7 +423,7 @@ func TestHandler_HTTP(t *testing.T) {
si := &runtime.ServiceInfo{
Service: &dynamic.Service{
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
Servers: []dynamic.Server{
{
URL: "http://127.0.0.1",
@@ -440,7 +440,7 @@ func TestHandler_HTTP(t *testing.T) {
si := &runtime.ServiceInfo{
Service: &dynamic.Service{
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
Servers: []dynamic.Server{
{
URL: "http://127.0.0.2",
@@ -457,7 +457,7 @@ func TestHandler_HTTP(t *testing.T) {
si := &runtime.ServiceInfo{
Service: &dynamic.Service{
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
Servers: []dynamic.Server{
{
URL: "http://127.0.0.3",
@@ -487,7 +487,7 @@ func TestHandler_HTTP(t *testing.T) {
si := &runtime.ServiceInfo{
Service: &dynamic.Service{
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
Servers: []dynamic.Server{
{
URL: "http://127.0.0.1",
@@ -505,7 +505,7 @@ func TestHandler_HTTP(t *testing.T) {
si := &runtime.ServiceInfo{
Service: &dynamic.Service{
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
Servers: []dynamic.Server{
{
URL: "http://127.0.0.2",
@@ -536,7 +536,7 @@ func TestHandler_HTTP(t *testing.T) {
si := &runtime.ServiceInfo{
Service: &dynamic.Service{
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
Servers: []dynamic.Server{
{
URL: "http://127.0.0.1",
@@ -554,7 +554,7 @@ func TestHandler_HTTP(t *testing.T) {
si := &runtime.ServiceInfo{
Service: &dynamic.Service{
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
Servers: []dynamic.Server{
{
URL: "http://127.0.0.2",
@@ -585,7 +585,7 @@ func TestHandler_HTTP(t *testing.T) {
si := &runtime.ServiceInfo{
Service: &dynamic.Service{
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
Servers: []dynamic.Server{
{
URL: "http://127.0.0.1",
@@ -614,7 +614,7 @@ func TestHandler_HTTP(t *testing.T) {
si := &runtime.ServiceInfo{
Service: &dynamic.Service{
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
Servers: []dynamic.Server{
{
URL: "http://127.0.0.1",
@@ -643,7 +643,7 @@ func TestHandler_HTTP(t *testing.T) {
si := &runtime.ServiceInfo{
Service: &dynamic.Service{
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
Servers: []dynamic.Server{
{
URL: "http://127.0.0.1",

View File

@@ -38,7 +38,7 @@ func TestHandler_RawData(t *testing.T) {
"foo-service@myprovider": {
Service: &dynamic.Service{
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
Servers: []dynamic.Server{
{
URL: "http://127.0.0.1",

View File

@@ -4,7 +4,7 @@
/*
The MIT License (MIT)
Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs
Copyright (c) 2016-2020 Containous SAS; 2020-2025 Traefik Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -13,6 +13,8 @@ import (
"github.com/traefik/traefik/v2/pkg/types"
)
func pointer[T any](v T) *T { return &v }
func TestDecodeConfiguration(t *testing.T) {
labels := map[string]string{
"traefik.http.middlewares.Middleware0.addprefix.prefix": "foobar",
@@ -265,7 +267,7 @@ func TestDecodeConfiguration(t *testing.T) {
Port: "42",
},
},
TerminationDelay: func(i int) *int { return &i }(42),
TerminationDelay: pointer(42),
ProxyProtocol: &dynamic.ProxyProtocol{Version: 42},
},
},
@@ -276,7 +278,7 @@ func TestDecodeConfiguration(t *testing.T) {
Port: "42",
},
},
TerminationDelay: func(i int) *int { return &i }(42),
TerminationDelay: pointer(42),
ProxyProtocol: &dynamic.ProxyProtocol{Version: 2},
},
},
@@ -665,9 +667,9 @@ func TestDecodeConfiguration(t *testing.T) {
"name0": "foobar",
"name1": "foobar",
},
FollowRedirects: func(v bool) *bool { return &v }(true),
FollowRedirects: pointer(true),
},
PassHostHeader: func(v bool) *bool { return &v }(true),
PassHostHeader: pointer(true),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: "foobar",
},
@@ -693,9 +695,9 @@ func TestDecodeConfiguration(t *testing.T) {
"name0": "foobar",
"name1": "foobar",
},
FollowRedirects: func(v bool) *bool { return &v }(true),
FollowRedirects: pointer(true),
},
PassHostHeader: func(v bool) *bool { return &v }(true),
PassHostHeader: pointer(true),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: "foobar",
},
@@ -773,7 +775,7 @@ func TestEncodeConfiguration(t *testing.T) {
Port: "42",
},
},
TerminationDelay: func(i int) *int { return &i }(42),
TerminationDelay: pointer(42),
},
},
"Service1": {
@@ -783,7 +785,7 @@ func TestEncodeConfiguration(t *testing.T) {
Port: "42",
},
},
TerminationDelay: func(i int) *int { return &i }(42),
TerminationDelay: pointer(42),
},
},
},
@@ -1170,7 +1172,7 @@ func TestEncodeConfiguration(t *testing.T) {
"name1": "foobar",
},
},
PassHostHeader: func(v bool) *bool { return &v }(true),
PassHostHeader: pointer(true),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: "foobar",
},
@@ -1197,7 +1199,7 @@ func TestEncodeConfiguration(t *testing.T) {
"name1": "foobar",
},
},
PassHostHeader: func(v bool) *bool { return &v }(true),
PassHostHeader: pointer(true),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: "foobar",
},

View File

@@ -24,14 +24,14 @@ type EntryPoint struct {
// GetAddress strips any potential protocol part of the address field of the
// entry point, in order to return the actual address.
func (ep EntryPoint) GetAddress() string {
func (ep *EntryPoint) GetAddress() string {
splitN := strings.SplitN(ep.Address, "/", 2)
return splitN[0]
}
// GetProtocol returns the protocol part of the address field of the entry point.
// If none is specified, it defaults to "tcp".
func (ep EntryPoint) GetProtocol() (string, error) {
func (ep *EntryPoint) GetProtocol() (string, error) {
splitN := strings.SplitN(ep.Address, "/", 2)
if len(splitN) < 2 {
return "tcp", nil

View File

@@ -278,7 +278,7 @@ func TestNewRequest(t *testing.T) {
if test.expected.err {
require.Error(t, err)
assert.Nil(t, nil)
assert.Nil(t, req)
} else {
require.NoError(t, err, "failed to create new backend request")
require.NotNil(t, req)

View File

@@ -12,7 +12,6 @@ import (
"net/url"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"testing"
@@ -809,16 +808,16 @@ func assertValidLogData(t *testing.T, expected string, logData []byte) {
assert.Equal(t, resultExpected[OriginContentSize], result[OriginContentSize], formatErrMessage)
assert.Equal(t, resultExpected[RequestRefererHeader], result[RequestRefererHeader], formatErrMessage)
assert.Equal(t, resultExpected[RequestUserAgentHeader], result[RequestUserAgentHeader], formatErrMessage)
assert.Regexp(t, regexp.MustCompile(`\d*`), result[RequestCount], formatErrMessage)
assert.Regexp(t, `\d*`, result[RequestCount], formatErrMessage)
assert.Equal(t, resultExpected[RouterName], result[RouterName], formatErrMessage)
assert.Equal(t, resultExpected[ServiceURL], result[ServiceURL], formatErrMessage)
assert.Regexp(t, regexp.MustCompile(`\d*ms`), result[Duration], formatErrMessage)
assert.Regexp(t, `\d*ms`, result[Duration], formatErrMessage)
}
func captureStdout(t *testing.T) (out *os.File, restoreStdout func()) {
t.Helper()
file, err := os.CreateTemp("", "testlogger")
file, err := os.CreateTemp(t.TempDir(), "testlogger")
require.NoError(t, err, "failed to create temp file")
original := os.Stdout

View File

@@ -20,6 +20,7 @@ const (
xForwardedServer = "X-Forwarded-Server"
xForwardedURI = "X-Forwarded-Uri"
xForwardedMethod = "X-Forwarded-Method"
xForwardedPrefix = "X-Forwarded-Prefix"
xForwardedTLSClientCert = "X-Forwarded-Tls-Client-Cert"
xForwardedTLSClientCertInfo = "X-Forwarded-Tls-Client-Cert-Info"
xRealIP = "X-Real-Ip"
@@ -35,6 +36,7 @@ var xHeaders = []string{
xForwardedServer,
xForwardedURI,
xForwardedMethod,
xForwardedPrefix,
xForwardedTLSClientCert,
xForwardedTLSClientCertInfo,
xRealIP,

View File

@@ -48,6 +48,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: {"GET"},
xForwardedTLSClientCert: {"Cert"},
xForwardedTLSClientCertInfo: {"CertInfo"},
xForwardedPrefix: {"/prefix"},
},
expectedHeaders: map[string]string{
xForwardedFor: "10.0.1.0, 10.0.1.12",
@@ -55,6 +56,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: "GET",
xForwardedTLSClientCert: "Cert",
xForwardedTLSClientCertInfo: "CertInfo",
xForwardedPrefix: "/prefix",
},
},
{
@@ -68,6 +70,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: {"GET"},
xForwardedTLSClientCert: {"Cert"},
xForwardedTLSClientCertInfo: {"CertInfo"},
xForwardedPrefix: {"/prefix"},
},
expectedHeaders: map[string]string{
xForwardedFor: "",
@@ -75,6 +78,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: "",
xForwardedTLSClientCert: "",
xForwardedTLSClientCertInfo: "",
xForwardedPrefix: "",
},
},
{
@@ -88,6 +92,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: {"GET"},
xForwardedTLSClientCert: {"Cert"},
xForwardedTLSClientCertInfo: {"CertInfo"},
xForwardedPrefix: {"/prefix"},
},
expectedHeaders: map[string]string{
xForwardedFor: "10.0.1.0, 10.0.1.12",
@@ -95,6 +100,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: "GET",
xForwardedTLSClientCert: "Cert",
xForwardedTLSClientCertInfo: "CertInfo",
xForwardedPrefix: "/prefix",
},
},
{
@@ -108,6 +114,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: {"GET"},
xForwardedTLSClientCert: {"Cert"},
xForwardedTLSClientCertInfo: {"CertInfo"},
xForwardedPrefix: {"/prefix"},
},
expectedHeaders: map[string]string{
xForwardedFor: "",
@@ -115,6 +122,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: "",
xForwardedTLSClientCert: "",
xForwardedTLSClientCertInfo: "",
xForwardedPrefix: "",
},
},
{
@@ -128,6 +136,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: {"GET"},
xForwardedTLSClientCert: {"Cert"},
xForwardedTLSClientCertInfo: {"CertInfo"},
xForwardedPrefix: {"/prefix"},
},
expectedHeaders: map[string]string{
xForwardedFor: "10.0.1.0, 10.0.1.12",
@@ -135,6 +144,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: "GET",
xForwardedTLSClientCert: "Cert",
xForwardedTLSClientCertInfo: "CertInfo",
xForwardedPrefix: "/prefix",
},
},
{
@@ -148,6 +158,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: {"GET"},
xForwardedTLSClientCert: {"Cert"},
xForwardedTLSClientCertInfo: {"CertInfo"},
xForwardedPrefix: {"/prefix"},
},
expectedHeaders: map[string]string{
xForwardedFor: "",
@@ -155,6 +166,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: "",
xForwardedTLSClientCert: "",
xForwardedTLSClientCertInfo: "",
xForwardedPrefix: "",
},
},
{
@@ -283,6 +295,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort,
xForwardedTLSClientCert,
xForwardedTLSClientCertInfo,
xForwardedPrefix,
xRealIP,
},
xForwardedProto: {"foo"},
@@ -293,6 +306,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort: {"foo"},
xForwardedTLSClientCert: {"foo"},
xForwardedTLSClientCertInfo: {"foo"},
xForwardedPrefix: {"foo"},
xRealIP: {"foo"},
},
expectedHeaders: map[string]string{
@@ -304,6 +318,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort: "80",
xForwardedTLSClientCert: "",
xForwardedTLSClientCertInfo: "",
xForwardedPrefix: "",
xRealIP: "",
connection: "",
},
@@ -321,6 +336,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort,
xForwardedTLSClientCert,
xForwardedTLSClientCertInfo,
xForwardedPrefix,
xRealIP,
},
xForwardedProto: {"foo"},
@@ -331,6 +347,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort: {"foo"},
xForwardedTLSClientCert: {"foo"},
xForwardedTLSClientCertInfo: {"foo"},
xForwardedPrefix: {"foo"},
xRealIP: {"foo"},
},
expectedHeaders: map[string]string{
@@ -342,6 +359,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort: "foo",
xForwardedTLSClientCert: "foo",
xForwardedTLSClientCertInfo: "foo",
xForwardedPrefix: "foo",
xRealIP: "foo",
connection: "",
},
@@ -358,6 +376,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort,
xForwardedTLSClientCert,
xForwardedTLSClientCertInfo,
xForwardedPrefix,
xRealIP,
},
incomingHeaders: map[string][]string{
@@ -370,6 +389,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort,
xForwardedTLSClientCert,
xForwardedTLSClientCertInfo,
xForwardedPrefix,
xRealIP,
},
xForwardedProto: {"foo"},
@@ -380,6 +400,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort: {"foo"},
xForwardedTLSClientCert: {"foo"},
xForwardedTLSClientCertInfo: {"foo"},
xForwardedPrefix: {"foo"},
xRealIP: {"foo"},
},
expectedHeaders: map[string]string{
@@ -391,6 +412,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort: "80",
xForwardedTLSClientCert: "",
xForwardedTLSClientCertInfo: "",
xForwardedPrefix: "",
xRealIP: "",
connection: "",
},
@@ -407,6 +429,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort,
xForwardedTLSClientCert,
xForwardedTLSClientCertInfo,
xForwardedPrefix,
xRealIP,
},
incomingHeaders: map[string][]string{
@@ -419,6 +442,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort,
xForwardedTLSClientCert,
xForwardedTLSClientCertInfo,
xForwardedPrefix,
xRealIP,
},
xForwardedProto: {"foo"},
@@ -429,6 +453,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort: {"foo"},
xForwardedTLSClientCert: {"foo"},
xForwardedTLSClientCertInfo: {"foo"},
xForwardedPrefix: {"foo"},
xRealIP: {"foo"},
},
expectedHeaders: map[string]string{
@@ -440,6 +465,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort: "foo",
xForwardedTLSClientCert: "foo",
xForwardedTLSClientCertInfo: "foo",
xForwardedPrefix: "foo",
xRealIP: "foo",
connection: "",
},

View File

@@ -152,7 +152,7 @@ func (p *passTLSClientCert) ServeHTTP(rw http.ResponseWriter, req *http.Request)
if req.TLS != nil && len(req.TLS.PeerCertificates) > 0 {
req.Header.Set(xForwardedTLSClientCert, getCertificates(ctx, req.TLS.PeerCertificates))
} else {
logger.Warn("Tried to extract a certificate on a request without mutual TLS")
logger.Debug("Tried to extract a certificate on a request without mutual TLS")
}
}
@@ -161,7 +161,7 @@ func (p *passTLSClientCert) ServeHTTP(rw http.ResponseWriter, req *http.Request)
headerContent := p.getCertInfo(ctx, req.TLS.PeerCertificates)
req.Header.Set(xForwardedTLSClientCertInfo, url.QueryEscape(headerContent))
} else {
logger.Warn("Tried to extract a certificate on a request without mutual TLS")
logger.Debug("Tried to extract a certificate on a request without mutual TLS")
}
}

View File

@@ -37,6 +37,48 @@ type Listener interface {
// each of them about a retry attempt.
type Listeners []Listener
// Retried exists to implement the Listener interface. It calls Retried on each of its slice entries.
func (l Listeners) Retried(req *http.Request, attempt int) {
for _, listener := range l {
listener.Retried(req, attempt)
}
}
type shouldRetryContextKey struct{}
// ShouldRetry is a function allowing to enable/disable the retry middleware mechanism.
type ShouldRetry func(shouldRetry bool)
// ContextShouldRetry returns the ShouldRetry function if it has been set by the Retry middleware in the chain.
func ContextShouldRetry(ctx context.Context) ShouldRetry {
f, _ := ctx.Value(shouldRetryContextKey{}).(ShouldRetry)
return f
}
// WrapHandler wraps a given http.Handler to inject the httptrace.ClientTrace in the request context when it is needed
// by the retry middleware.
func WrapHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if shouldRetry := ContextShouldRetry(req.Context()); shouldRetry != nil {
shouldRetry(true)
trace := &httptrace.ClientTrace{
WroteHeaders: func() {
shouldRetry(false)
},
WroteRequest: func(httptrace.WroteRequestInfo) {
shouldRetry(false)
},
}
newCtx := httptrace.WithClientTrace(req.Context(), trace)
next.ServeHTTP(rw, req.WithContext(newCtx))
return
}
next.ServeHTTP(rw, req)
})
}
// retry is a middleware that retries requests.
type retry struct {
attempts int
@@ -83,19 +125,13 @@ func (r *retry) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
attempts := 1
operation := func() error {
shouldRetry := attempts < r.attempts
retryResponseWriter := newResponseWriter(rw, shouldRetry)
remainAttempts := attempts < r.attempts
retryResponseWriter := newResponseWriter(rw)
// Disable retries when the backend already received request data
trace := &httptrace.ClientTrace{
WroteHeaders: func() {
retryResponseWriter.DisableRetries()
},
WroteRequest: func(httptrace.WroteRequestInfo) {
retryResponseWriter.DisableRetries()
},
var shouldRetry ShouldRetry = func(shouldRetry bool) {
retryResponseWriter.SetShouldRetry(remainAttempts && shouldRetry)
}
newCtx := httptrace.WithClientTrace(req.Context(), trace)
newCtx := context.WithValue(req.Context(), shouldRetryContextKey{}, shouldRetry)
r.next.ServeHTTP(retryResponseWriter, req.Clone(newCtx))
@@ -142,25 +178,17 @@ func (r *retry) newBackOff() backoff.BackOff {
return b
}
// Retried exists to implement the Listener interface. It calls Retried on each of its slice entries.
func (l Listeners) Retried(req *http.Request, attempt int) {
for _, listener := range l {
listener.Retried(req, attempt)
}
}
type responseWriter interface {
http.ResponseWriter
http.Flusher
ShouldRetry() bool
DisableRetries()
SetShouldRetry(shouldRetry bool)
}
func newResponseWriter(rw http.ResponseWriter, shouldRetry bool) responseWriter {
func newResponseWriter(rw http.ResponseWriter) responseWriter {
responseWriter := &responseWriterWithoutCloseNotify{
responseWriter: rw,
headers: make(http.Header),
shouldRetry: shouldRetry,
}
if _, ok := rw.(http.CloseNotifier); ok {
return &responseWriterWithCloseNotify{
@@ -181,8 +209,8 @@ func (r *responseWriterWithoutCloseNotify) ShouldRetry() bool {
return r.shouldRetry
}
func (r *responseWriterWithoutCloseNotify) DisableRetries() {
r.shouldRetry = false
func (r *responseWriterWithoutCloseNotify) SetShouldRetry(shouldRetry bool) {
r.shouldRetry = shouldRetry
}
func (r *responseWriterWithoutCloseNotify) Header() http.Header {
@@ -193,23 +221,17 @@ func (r *responseWriterWithoutCloseNotify) Header() http.Header {
}
func (r *responseWriterWithoutCloseNotify) Write(buf []byte) (int, error) {
if r.ShouldRetry() {
if r.shouldRetry {
return len(buf), nil
}
if !r.written {
r.WriteHeader(http.StatusOK)
}
return r.responseWriter.Write(buf)
}
func (r *responseWriterWithoutCloseNotify) WriteHeader(code int) {
if r.ShouldRetry() && code == http.StatusServiceUnavailable {
// We get a 503 HTTP Status Code when there is no backend server in the pool
// to which the request could be sent. Also, note that r.ShouldRetry()
// will never return true in case there was a connection established to
// the backend server and so we can be sure that the 503 was produced
// inside Traefik already and we don't have to retry in this cases.
r.DisableRetries()
}
if r.ShouldRetry() || r.written {
if r.shouldRetry || r.written {
return
}

View File

@@ -105,12 +105,21 @@ func TestRetry(t *testing.T) {
t.Parallel()
retryAttempts := 0
next := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
// This signals that a connection will be established with the backend
// to enable the Retry middleware mechanism.
shouldRetry := ContextShouldRetry(req.Context())
if shouldRetry != nil {
shouldRetry(true)
}
retryAttempts++
if retryAttempts > test.amountFaultyEndpoints {
// calls WroteHeaders on httptrace.
_ = r.Write(io.Discard)
// This signals that request headers have been sent to the backend.
if shouldRetry != nil {
shouldRetry(false)
}
rw.WriteHeader(http.StatusOK)
return
@@ -152,27 +161,16 @@ func TestRetryEmptyServerList(t *testing.T) {
assert.Equal(t, 0, retryListener.timesCalled)
}
func TestRetryListeners(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/", nil)
retryListeners := Listeners{&countingRetryListener{}, &countingRetryListener{}}
retryListeners.Retried(req, 1)
retryListeners.Retried(req, 1)
for _, retryListener := range retryListeners {
listener := retryListener.(*countingRetryListener)
if listener.timesCalled != 2 {
t.Errorf("retry listener was called %d time(s), want %d time(s)", listener.timesCalled, 2)
}
}
}
func TestMultipleRetriesShouldNotLooseHeaders(t *testing.T) {
attempt := 0
expectedHeaderName := "X-Foo-Test-2"
expectedHeaderValue := "bar"
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
shouldRetry := ContextShouldRetry(req.Context())
if shouldRetry != nil {
shouldRetry(true)
}
headerName := fmt.Sprintf("X-Foo-Test-%d", attempt)
rw.Header().Add(headerName, expectedHeaderValue)
if attempt < 2 {
@@ -181,43 +179,54 @@ func TestMultipleRetriesShouldNotLooseHeaders(t *testing.T) {
}
// Request has been successfully written to backend
trace := httptrace.ContextClientTrace(req.Context())
trace.WroteHeaders()
shouldRetry(false)
// And we decide to answer to client
// And we decide to answer to client.
rw.WriteHeader(http.StatusNoContent)
})
retry, err := New(context.Background(), next, dynamic.Retry{Attempts: 3}, &countingRetryListener{}, "traefikTest")
require.NoError(t, err)
responseRecorder := httptest.NewRecorder()
retry.ServeHTTP(responseRecorder, testhelpers.MustNewRequest(http.MethodGet, "http://test", http.NoBody))
res := httptest.NewRecorder()
retry.ServeHTTP(res, testhelpers.MustNewRequest(http.MethodGet, "http://test", http.NoBody))
headerValue := responseRecorder.Header().Get(expectedHeaderName)
// Validate if we have the correct header
if headerValue != expectedHeaderValue {
t.Errorf("Expected to have %s for header %s, got %s", expectedHeaderValue, expectedHeaderName, headerValue)
}
// The third header attempt is kept.
headerValue := res.Header().Get("X-Foo-Test-2")
assert.Equal(t, expectedHeaderValue, headerValue)
// Validate that we don't have headers from previous attempts
for i := range attempt {
headerName := fmt.Sprintf("X-Foo-Test-%d", i)
headerValue = responseRecorder.Header().Get("headerName")
headerValue = res.Header().Get(headerName)
if headerValue != "" {
t.Errorf("Expected no value for header %s, got %s", headerName, headerValue)
}
}
}
// countingRetryListener is a Listener implementation to count the times the Retried fn is called.
type countingRetryListener struct {
timesCalled int
}
func TestRetryShouldNotLooseHeadersOnWrite(t *testing.T) {
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.Header().Add("X-Foo-Test", "bar")
func (l *countingRetryListener) Retried(req *http.Request, attempt int) {
l.timesCalled++
// Request has been successfully written to backend.
shouldRetry := ContextShouldRetry(req.Context())
if shouldRetry != nil {
shouldRetry(false)
}
// And we decide to answer to client without calling WriteHeader.
_, err := rw.Write([]byte("bar"))
require.NoError(t, err)
})
retry, err := New(context.Background(), next, dynamic.Retry{Attempts: 3}, &countingRetryListener{}, "traefikTest")
require.NoError(t, err)
res := httptest.NewRecorder()
retry.ServeHTTP(res, testhelpers.MustNewRequest(http.MethodGet, "http://test", http.NoBody))
headerValue := res.Header().Get("X-Foo-Test")
assert.Equal(t, "bar", headerValue)
}
func TestRetryWithFlush(t *testing.T) {
@@ -275,12 +284,24 @@ func TestRetryWebsocket(t *testing.T) {
t.Parallel()
retryAttempts := 0
next := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
// This signals that a connection will be established with the backend
// to enable the Retry middleware mechanism.
shouldRetry := ContextShouldRetry(req.Context())
if shouldRetry != nil {
shouldRetry(true)
}
retryAttempts++
if retryAttempts > test.amountFaultyEndpoints {
// This signals that request headers have been sent to the backend.
if shouldRetry != nil {
shouldRetry(false)
}
upgrader := websocket.Upgrader{}
_, err := upgrader.Upgrade(rw, r, nil)
_, err := upgrader.Upgrade(rw, req, nil)
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
@@ -387,3 +408,12 @@ func Test1xxResponses(t *testing.T) {
assert.Equal(t, 0, retryListener.timesCalled)
}
// countingRetryListener is a Listener implementation to count the times the Retried fn is called.
type countingRetryListener struct {
timesCalled int
}
func (l *countingRetryListener) Retried(req *http.Request, attempt int) {
l.timesCalled++
}

View File

@@ -38,24 +38,24 @@ type MockSpan struct {
Tags map[string]interface{}
}
func (n MockSpan) Context() opentracing.SpanContext { return MockSpanContext{} }
func (n MockSpan) SetBaggageItem(key, val string) opentracing.Span {
return MockSpan{Tags: make(map[string]interface{})}
func (n *MockSpan) Context() opentracing.SpanContext { return MockSpanContext{} }
func (n *MockSpan) SetBaggageItem(key, val string) opentracing.Span {
return &MockSpan{Tags: make(map[string]interface{})}
}
func (n MockSpan) BaggageItem(key string) string { return "" }
func (n MockSpan) SetTag(key string, value interface{}) opentracing.Span {
func (n *MockSpan) BaggageItem(key string) string { return "" }
func (n *MockSpan) SetTag(key string, value interface{}) opentracing.Span {
n.Tags[key] = value
return n
}
func (n MockSpan) LogFields(fields ...log.Field) {}
func (n MockSpan) LogKV(keyVals ...interface{}) {}
func (n MockSpan) Finish() {}
func (n MockSpan) FinishWithOptions(opts opentracing.FinishOptions) {}
func (n MockSpan) SetOperationName(operationName string) opentracing.Span { return n }
func (n MockSpan) Tracer() opentracing.Tracer { return MockTracer{} }
func (n MockSpan) LogEvent(event string) {}
func (n MockSpan) LogEventWithPayload(event string, payload interface{}) {}
func (n MockSpan) Log(data opentracing.LogData) {}
func (n *MockSpan) LogFields(fields ...log.Field) {}
func (n *MockSpan) LogKV(keyVals ...interface{}) {}
func (n *MockSpan) Finish() {}
func (n *MockSpan) FinishWithOptions(opts opentracing.FinishOptions) {}
func (n *MockSpan) SetOperationName(operationName string) opentracing.Span { return n }
func (n *MockSpan) Tracer() opentracing.Tracer { return MockTracer{} }
func (n *MockSpan) LogEvent(event string) {}
func (n *MockSpan) LogEventWithPayload(event string, payload interface{}) {}
func (n *MockSpan) Log(data opentracing.LogData) {}
func (n *MockSpan) Reset() {
n.Tags = make(map[string]interface{})
}

View File

@@ -102,7 +102,7 @@ func NewMuxer() (*Muxer, error) {
// Match returns the handler of the first route matching the connection metadata,
// and whether the match is exactly from the rule HostSNI(*).
func (m Muxer) Match(meta ConnData) (tcp.Handler, bool) {
func (m *Muxer) Match(meta ConnData) (tcp.Handler, bool) {
for _, route := range m.routes {
if route.matchers.match(meta) {
return route.handler, route.catchAll
@@ -310,7 +310,7 @@ func alpn(tree *matchersTree, protos ...string) error {
return nil
}
var hostOrIP = regexp.MustCompile(`^[[:alnum:]\.\-\:]+$`)
var hostOrIP = regexp.MustCompile(`^[[:word:]\.\-\:]+$`)
// hostSNI checks if the SNI Host of the connection match the matcher host.
func hostSNI(tree *matchersTree, hosts ...string) error {

View File

@@ -743,6 +743,11 @@ func Test_HostSNI(t *testing.T) {
ruleHosts: []string{"foo.bar"},
serverName: "foo.bar",
},
{
desc: "Matching hosts with subdomains with _",
ruleHosts: []string{"foo_bar.example.com"},
serverName: "foo_bar.example.com",
},
{
desc: "Matching IPv4",
ruleHosts: []string{"127.0.0.1"},

View File

@@ -1,6 +1,7 @@
package acme
import (
"context"
"encoding/json"
"io"
"os"
@@ -22,9 +23,9 @@ type LocalStore struct {
}
// NewLocalStore initializes a new LocalStore with a file name.
func NewLocalStore(filename string) *LocalStore {
func NewLocalStore(filename string, routinesPool *safe.Pool) *LocalStore {
store := &LocalStore{filename: filename, saveDataChan: make(chan map[string]*StoredData)}
store.listenSaveAction()
store.listenSaveAction(routinesPool)
return store
}
@@ -99,18 +100,31 @@ func (s *LocalStore) get(resolverName string) (*StoredData, error) {
}
// listenSaveAction listens to a chan to store ACME data in json format into `LocalStore.filename`.
func (s *LocalStore) listenSaveAction() {
safe.Go(func() {
func (s *LocalStore) listenSaveAction(routinesPool *safe.Pool) {
routinesPool.GoCtx(func(ctx context.Context) {
logger := log.WithoutContext().WithField(log.ProviderName, "acme")
for object := range s.saveDataChan {
data, err := json.MarshalIndent(object, "", " ")
if err != nil {
logger.Error(err)
}
for {
select {
case <-ctx.Done():
return
err = os.WriteFile(s.filename, data, 0o600)
if err != nil {
logger.Error(err)
case object := <-s.saveDataChan:
select {
case <-ctx.Done():
// Stop handling events because Traefik is shutting down.
return
default:
}
data, err := json.MarshalIndent(object, "", " ")
if err != nil {
logger.Error(err)
}
err = os.WriteFile(s.filename, data, 0o600)
if err != nil {
logger.Error(err)
}
}
}
})

View File

@@ -1,6 +1,7 @@
package acme
import (
"context"
"fmt"
"os"
"path/filepath"
@@ -9,6 +10,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/traefik/traefik/v2/pkg/safe"
)
func TestLocalStore_GetAccount(t *testing.T) {
@@ -45,7 +47,7 @@ func TestLocalStore_GetAccount(t *testing.T) {
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
s := NewLocalStore(test.filename)
s := NewLocalStore(test.filename, safe.NewPool(context.Background()))
account, err := s.GetAccount("test")
require.NoError(t, err)
@@ -58,7 +60,7 @@ func TestLocalStore_GetAccount(t *testing.T) {
func TestLocalStore_SaveAccount(t *testing.T) {
acmeFile := filepath.Join(t.TempDir(), "acme.json")
s := NewLocalStore(acmeFile)
s := NewLocalStore(acmeFile, safe.NewPool(context.Background()))
email := "some@email.com"

View File

@@ -67,8 +67,8 @@ type ProviderAggregator struct {
}
// NewProviderAggregator returns an aggregate of all the providers configured in the static configuration.
func NewProviderAggregator(conf static.Providers) ProviderAggregator {
p := ProviderAggregator{
func NewProviderAggregator(conf static.Providers) *ProviderAggregator {
p := &ProviderAggregator{
providersThrottleDuration: time.Duration(conf.ProvidersThrottleDuration),
}
@@ -172,12 +172,12 @@ func (p *ProviderAggregator) AddProvider(provider provider.Provider) error {
}
// Init the provider.
func (p ProviderAggregator) Init() error {
func (p *ProviderAggregator) Init() error {
return nil
}
// Provide calls the provide method of every providers.
func (p ProviderAggregator) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
func (p *ProviderAggregator) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
if p.fileProvider != nil {
p.launchProvider(configurationChan, pool, p.fileProvider)
}
@@ -197,7 +197,7 @@ func (p ProviderAggregator) Provide(configurationChan chan<- dynamic.Message, po
return nil
}
func (p ProviderAggregator) launchProvider(configurationChan chan<- dynamic.Message, pool *safe.Pool, prd provider.Provider) {
func (p *ProviderAggregator) launchProvider(configurationChan chan<- dynamic.Message, pool *safe.Pool, prd provider.Provider) {
jsonConf, err := redactor.RemoveCredentials(prd)
if err != nil {
log.WithoutContext().Debugf("Cannot marshal the provider configuration %T: %v", prd, err)

View File

@@ -3,7 +3,9 @@ package provider
import (
"bytes"
"context"
"maps"
"reflect"
"slices"
"sort"
"strings"
"text/template"
@@ -13,7 +15,6 @@ import (
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/log"
"github.com/traefik/traefik/v2/pkg/tls"
"golang.org/x/exp/maps"
)
// Merge merges multiple configurations.
@@ -384,7 +385,7 @@ func BuildTCPRouterConfiguration(ctx context.Context, configuration *dynamic.TCP
if len(configuration.Services) > 1 {
delete(configuration.Routers, routerName)
loggerRouter.
Errorf("Router %s cannot be linked automatically with multiple Services: %q", routerName, maps.Keys(configuration.Services))
Errorf("Router %s cannot be linked automatically with multiple Services: %q", routerName, slices.Collect(maps.Keys(configuration.Services)))
continue
}
@@ -406,7 +407,7 @@ func BuildUDPRouterConfiguration(ctx context.Context, configuration *dynamic.UDP
if len(configuration.Services) > 1 {
delete(configuration.Routers, routerName)
loggerRouter.
Errorf("Router %s cannot be linked automatically with multiple Services: %q", routerName, maps.Keys(configuration.Services))
Errorf("Router %s cannot be linked automatically with multiple Services: %q", routerName, slices.Collect(maps.Keys(configuration.Services)))
continue
}
@@ -453,7 +454,7 @@ func BuildRouterConfiguration(ctx context.Context, configuration *dynamic.HTTPCo
if len(configuration.Services) > 1 {
delete(configuration.Routers, routerName)
loggerRouter.
Errorf("Router %s cannot be linked automatically with multiple Services: %q", routerName, maps.Keys(configuration.Services))
Errorf("Router %s cannot be linked automatically with multiple Services: %q", routerName, slices.Collect(maps.Keys(configuration.Services)))
continue
}

View File

@@ -13,8 +13,7 @@ import (
"github.com/traefik/traefik/v2/pkg/types"
)
func Int(v int) *int { return &v }
func Bool(v bool) *bool { return &v }
func pointer[T any](v T) *T { return &v }
func TestDefaultRule(t *testing.T) {
testCases := []struct {
@@ -64,7 +63,7 @@ func TestDefaultRule(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -118,7 +117,7 @@ func TestDefaultRule(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -164,7 +163,7 @@ func TestDefaultRule(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -210,7 +209,7 @@ func TestDefaultRule(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -262,7 +261,7 @@ func TestDefaultRule(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -350,7 +349,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -407,7 +406,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "https://127.0.0.1:443",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
ServersTransport: "tls-ns-dc1-dev-Test",
},
},
@@ -497,7 +496,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "https://127.0.0.2:444",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
ServersTransport: "tls-ns-dc1-dev-Test",
},
},
@@ -578,7 +577,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
"Test2": {
@@ -588,7 +587,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -651,7 +650,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -711,7 +710,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -774,7 +773,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -826,7 +825,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -879,7 +878,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -924,7 +923,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -982,7 +981,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -1030,7 +1029,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
"Service2": {
@@ -1040,7 +1039,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -1216,7 +1215,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -1267,7 +1266,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -1344,7 +1343,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -1408,7 +1407,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -1488,7 +1487,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.3:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -1548,7 +1547,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -1622,7 +1621,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.3:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -1688,7 +1687,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -1740,7 +1739,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -1793,7 +1792,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "h2c://127.0.0.1:8080",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -1840,7 +1839,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
"Service2": {
@@ -1850,7 +1849,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:8080",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -2074,7 +2073,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -2137,7 +2136,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -2188,7 +2187,7 @@ func Test_buildConfiguration(t *testing.T) {
Address: "127.0.0.1:80",
},
},
TerminationDelay: Int(100),
TerminationDelay: pointer(100),
},
},
},
@@ -2241,7 +2240,7 @@ func Test_buildConfiguration(t *testing.T) {
Address: "127.0.0.1:80",
},
},
TerminationDelay: Int(100),
TerminationDelay: pointer(100),
},
},
},
@@ -2337,7 +2336,7 @@ func Test_buildConfiguration(t *testing.T) {
Address: "127.0.0.1:80",
},
},
TerminationDelay: Int(100),
TerminationDelay: pointer(100),
},
},
},
@@ -2393,7 +2392,7 @@ func Test_buildConfiguration(t *testing.T) {
Address: "127.0.0.1:8080",
},
},
TerminationDelay: Int(100),
TerminationDelay: pointer(100),
},
},
},
@@ -2515,7 +2514,7 @@ func Test_buildConfiguration(t *testing.T) {
Address: "127.0.0.2:80",
},
},
TerminationDelay: Int(100),
TerminationDelay: pointer(100),
},
},
},
@@ -2544,7 +2543,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -2631,7 +2630,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -2668,7 +2667,7 @@ func Test_buildConfiguration(t *testing.T) {
Address: "127.0.0.1:80",
},
},
TerminationDelay: Int(100),
TerminationDelay: pointer(100),
},
},
},
@@ -2760,7 +2759,7 @@ func Test_buildConfiguration(t *testing.T) {
Address: "127.0.0.1:80",
},
},
TerminationDelay: Int(200),
TerminationDelay: pointer(200),
},
},
},
@@ -2844,7 +2843,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "https://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
ServersTransport: "tls-ns-dc1-Test",
},
},
@@ -2855,7 +2854,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "https://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
ServersTransport: "tls-ns-dc1-Test",
},
},
@@ -2933,7 +2932,7 @@ func Test_buildConfiguration(t *testing.T) {
Servers: []dynamic.TCPServer{
{Address: "127.0.0.1:80"},
},
TerminationDelay: Int(100),
TerminationDelay: pointer(100),
},
},
"Test-17573747155436217342": {
@@ -2941,7 +2940,7 @@ func Test_buildConfiguration(t *testing.T) {
Servers: []dynamic.TCPServer{
{Address: "127.0.0.2:80"},
},
TerminationDelay: Int(100),
TerminationDelay: pointer(100),
},
},
},
@@ -3136,7 +3135,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},

View File

@@ -307,7 +307,7 @@ func (p *Provider) getIPPort(ctx context.Context, container dockerData, serverPo
return ip, port, nil
}
func (p Provider) getIPAddress(ctx context.Context, container dockerData) string {
func (p *Provider) getIPAddress(ctx context.Context, container dockerData) string {
logger := log.FromContext(ctx)
netNotFound := false

View File

@@ -71,7 +71,7 @@ func TestDefaultRule(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -130,7 +130,7 @@ func TestDefaultRule(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -191,7 +191,7 @@ func TestDefaultRule(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -244,7 +244,7 @@ func TestDefaultRule(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -297,7 +297,7 @@ func TestDefaultRule(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -356,7 +356,7 @@ func TestDefaultRule(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -579,7 +579,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -658,7 +658,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
"Test2": {
@@ -668,7 +668,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -747,7 +747,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -807,7 +807,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -868,7 +868,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -921,7 +921,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -987,7 +987,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -1043,7 +1043,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
"Service2": {
@@ -1053,7 +1053,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -1113,7 +1113,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -1353,7 +1353,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -1412,7 +1412,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -1508,7 +1508,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -1591,7 +1591,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -1696,7 +1696,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.3:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -1773,7 +1773,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -1872,7 +1872,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.3:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -1954,7 +1954,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -2026,7 +2026,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
"Test2": {
@@ -2036,7 +2036,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -2096,7 +2096,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -2157,7 +2157,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "h2c://127.0.0.1:8080",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -2212,7 +2212,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
"Service2": {
@@ -2222,7 +2222,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:8080",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -2418,7 +2418,7 @@ func Test_buildConfiguration(t *testing.T) {
Services: map[string]*dynamic.Service{
"Test": {
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -2487,7 +2487,7 @@ func Test_buildConfiguration(t *testing.T) {
Services: map[string]*dynamic.TCPService{
"Test": {
LoadBalancer: &dynamic.TCPServersLoadBalancer{
TerminationDelay: Int(100),
TerminationDelay: pointer(100),
},
},
},
@@ -2676,7 +2676,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -2747,7 +2747,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -2806,7 +2806,7 @@ func Test_buildConfiguration(t *testing.T) {
Address: "127.0.0.1:80",
},
},
TerminationDelay: Int(100),
TerminationDelay: pointer(100),
},
},
},
@@ -2867,7 +2867,7 @@ func Test_buildConfiguration(t *testing.T) {
Address: "127.0.0.1:80",
},
},
TerminationDelay: Int(100),
TerminationDelay: pointer(100),
},
},
},
@@ -2979,7 +2979,7 @@ func Test_buildConfiguration(t *testing.T) {
Address: "127.0.0.1:80",
},
},
TerminationDelay: Int(100),
TerminationDelay: pointer(100),
},
},
},
@@ -3043,7 +3043,7 @@ func Test_buildConfiguration(t *testing.T) {
Address: "127.0.0.1:8080",
},
},
TerminationDelay: Int(100),
TerminationDelay: pointer(100),
},
},
},
@@ -3215,7 +3215,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -3314,7 +3314,7 @@ func Test_buildConfiguration(t *testing.T) {
Address: "127.0.0.1:8080",
},
},
TerminationDelay: Int(200),
TerminationDelay: pointer(200),
},
},
},
@@ -3391,7 +3391,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://192.168.0.1:8081",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -3451,7 +3451,7 @@ func Test_buildConfiguration(t *testing.T) {
URL: "http://127.0.0.1:79",
},
},
PassHostHeader: Bool(true),
PassHostHeader: pointer(true),
},
},
},
@@ -3958,6 +3958,4 @@ func TestSwarmGetPort(t *testing.T) {
}
}
func Int(v int) *int { return &v }
func Bool(v bool) *bool { return &v }
func pointer[T any](v T) *T { return &v }

View File

@@ -1,10 +1,13 @@
package ecs
import "github.com/aws/aws-sdk-go/service/ecs"
import (
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
ecstypes "github.com/aws/aws-sdk-go-v2/service/ecs/types"
)
func instance(ops ...func(*ecsInstance)) ecsInstance {
e := &ecsInstance{
containerDefinition: &ecs.ContainerDefinition{},
containerDefinition: &ecstypes.ContainerDefinition{},
}
for _, op := range ops {
@@ -36,7 +39,7 @@ func iMachine(opts ...func(*machine)) func(*ecsInstance) {
}
}
func mState(state string) func(*machine) {
func mState(state ec2types.InstanceStateName) func(*machine) {
return func(m *machine) {
m.state = state
}
@@ -48,7 +51,7 @@ func mPrivateIP(ip string) func(*machine) {
}
}
func mHealthStatus(status string) func(*machine) {
func mHealthStatus(status ecstypes.HealthStatus) func(*machine) {
return func(m *machine) {
m.healthStatus = status
}
@@ -64,10 +67,10 @@ func mPorts(opts ...func(*portMapping)) func(*machine) {
}
}
func mPort(containerPort, hostPort int32, protocol string) func(*portMapping) {
func mPort(containerPort, hostPort int32, protocol ecstypes.TransportProtocol) func(*portMapping) {
return func(pm *portMapping) {
pm.containerPort = int64(containerPort)
pm.hostPort = int64(hostPort)
pm.containerPort = containerPort
pm.hostPort = hostPort
pm.protocol = protocol
}
}

View File

@@ -6,9 +6,9 @@ import (
"fmt"
"net"
"strconv"
"strings"
"github.com/aws/aws-sdk-go/service/ec2"
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
ecstypes "github.com/aws/aws-sdk-go-v2/service/ecs/types"
"github.com/docker/go-connections/nat"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/config/label"
@@ -163,12 +163,12 @@ func (p *Provider) filterInstance(ctx context.Context, instance ecsInstance) boo
return false
}
if strings.ToLower(instance.machine.state) != ec2.InstanceStateNameRunning {
if instance.machine.state != ec2types.InstanceStateNameRunning {
logger.Debugf("Filtering ecs instance with an incorrect state %s (%s) (state = %s)", instance.Name, instance.ID, instance.machine.state)
return false
}
if instance.machine.healthStatus == "UNHEALTHY" {
if instance.machine.healthStatus == ecstypes.HealthStatusUnhealthy {
logger.Debugf("Filtering unhealthy ecs instance %s (%s)", instance.Name, instance.ID)
return false
}
@@ -293,9 +293,9 @@ func (p *Provider) getIPPort(instance ecsInstance, serverPort string) (string, s
func getPort(instance ecsInstance, serverPort string) string {
if len(serverPort) > 0 {
for _, port := range instance.machine.ports {
containerPort := strconv.FormatInt(port.containerPort, 10)
containerPort := strconv.FormatInt(int64(port.containerPort), 10)
if serverPort == containerPort {
return strconv.FormatInt(port.hostPort, 10)
return strconv.FormatInt(int64(port.hostPort), 10)
}
}
@@ -304,7 +304,7 @@ func getPort(instance ecsInstance, serverPort string) string {
var ports []nat.Port
for _, port := range instance.machine.ports {
natPort, err := nat.NewPort(port.protocol, strconv.FormatInt(port.hostPort, 10))
natPort, err := nat.NewPort(string(port.protocol), strconv.FormatInt(int64(port.hostPort), 10))
if err != nil {
continue
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,21 +3,22 @@ package ecs
import (
"context"
"fmt"
"os"
"iter"
"slices"
"strings"
"text/template"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/defaults"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/aws/aws-sdk-go/service/ssm"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/ec2"
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/aws/aws-sdk-go-v2/service/ecs"
ecstypes "github.com/aws/aws-sdk-go-v2/service/ecs/types"
"github.com/aws/aws-sdk-go-v2/service/ssm"
ssmtypes "github.com/aws/aws-sdk-go-v2/service/ssm/types"
"github.com/aws/smithy-go/logging"
"github.com/cenkalti/backoff/v4"
"github.com/patrickmn/go-cache"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
@@ -47,29 +48,29 @@ type Provider struct {
type ecsInstance struct {
Name string
ID string
containerDefinition *ecs.ContainerDefinition
containerDefinition *ecstypes.ContainerDefinition
machine *machine
Labels map[string]string
ExtraConf configuration
}
type portMapping struct {
containerPort int64
hostPort int64
protocol string
containerPort int32
hostPort int32
protocol ecstypes.TransportProtocol
}
type machine struct {
state string
state ec2types.InstanceStateName
privateIP string
ports []portMapping
healthStatus string
healthStatus ecstypes.HealthStatus
}
type awsClient struct {
ecs *ecs.ECS
ec2 *ec2.EC2
ssm *ssm.SSM
ecs *ecs.Client
ec2 *ec2.Client
ssm *ssm.Client
}
// DefaultTemplateRule The default template for the default rule.
@@ -100,56 +101,40 @@ func (p *Provider) Init() error {
return nil
}
func (p *Provider) createClient(logger log.Logger) (*awsClient, error) {
sess, err := session.NewSessionWithOptions(session.Options{
SharedConfigState: session.SharedConfigEnable,
})
func (p *Provider) createClient(ctx context.Context, logger log.Logger) (*awsClient, error) {
optFns := []func(*config.LoadOptions) error{
config.WithLogger(logging.LoggerFunc(func(_ logging.Classification, format string, args ...interface{}) {
logger.Debugf(format, args...)
})),
}
if p.Region != "" {
optFns = append(optFns, config.WithRegion(p.Region))
} else {
logger.Infoln("No region provided, will retrieve region from the EC2 Metadata service")
optFns = append(optFns, config.WithEC2IMDSRegion())
}
if p.AccessKeyID != "" && p.SecretAccessKey != "" {
// From https://docs.aws.amazon.com/sdk-for-go/v2/developer-guide/configure-gosdk.html#specify-credentials-programmatically:
// "If you explicitly provide credentials, as in this example, the SDK uses only those credentials."
// this makes sure that user-defined credentials always have the highest priority
staticCreds := aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(p.AccessKeyID, p.SecretAccessKey, ""))
optFns = append(optFns, config.WithCredentialsProvider(staticCreds))
// If the access key and secret access key are not provided, config.LoadDefaultConfig
// will look for the credentials in the default credential chain.
// See https://docs.aws.amazon.com/sdk-for-go/v2/developer-guide/configure-gosdk.html#specifying-credentials.
}
cfg, err := config.LoadDefaultConfig(ctx, optFns...)
if err != nil {
return nil, err
}
ec2meta := ec2metadata.New(sess)
if p.Region == "" && ec2meta.Available() {
logger.Infoln("No region provided, querying instance metadata endpoint...")
identity, err := ec2meta.GetInstanceIdentityDocument()
if err != nil {
return nil, err
}
p.Region = identity.Region
}
cfg := aws.NewConfig().
WithCredentials(credentials.NewChainCredentials([]credentials.Provider{
&credentials.StaticProvider{
Value: credentials.Value{
AccessKeyID: p.AccessKeyID,
SecretAccessKey: p.SecretAccessKey,
},
},
&credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{},
defaults.RemoteCredProvider(*(defaults.Config()), defaults.Handlers()),
stscreds.NewWebIdentityRoleProviderWithOptions(
sts.New(sess),
os.Getenv("AWS_ROLE_ARN"),
"",
stscreds.FetchTokenPath(os.Getenv("AWS_WEB_IDENTITY_TOKEN_FILE")),
),
}))
// Set the region if it is defined by the user or resolved from the EC2 metadata.
if p.Region != "" {
cfg.Region = &p.Region
}
cfg.WithLogger(aws.LoggerFunc(func(args ...interface{}) {
logger.Debug(args...)
}))
return &awsClient{
ecs.New(sess, cfg),
ec2.New(sess, cfg),
ssm.New(sess, cfg),
ecs.NewFromConfig(cfg),
ec2.NewFromConfig(cfg),
ssm.NewFromConfig(cfg),
}, nil
}
@@ -160,7 +145,7 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
logger := log.FromContext(ctxLog)
operation := func() error {
awsClient, err := p.createClient(logger)
awsClient, err := p.createClient(ctxLog, logger)
if err != nil {
return fmt.Errorf("unable to create AWS client: %w", err)
}
@@ -218,28 +203,19 @@ func (p *Provider) loadConfiguration(ctx context.Context, client *awsClient, con
func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsInstance, error) {
logger := log.FromContext(ctx)
var clustersArn []*string
var clusters []string
if p.AutoDiscoverClusters {
input := &ecs.ListClustersInput{}
for {
result, err := client.ecs.ListClusters(input)
paginator := ecs.NewListClustersPaginator(client.ecs, input)
for paginator.HasMorePages() {
page, err := paginator.NextPage(ctx)
if err != nil {
return nil, err
}
if result != nil {
clustersArn = append(clustersArn, result.ClusterArns...)
input.NextToken = result.NextToken
if result.NextToken == nil {
break
}
} else {
break
}
}
for _, cArn := range clustersArn {
clusters = append(clusters, *cArn)
clusters = append(clusters, page.ClusterArns...)
}
} else {
clusters = p.Clusters
@@ -251,13 +227,19 @@ func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsI
for _, c := range clusters {
input := &ecs.ListTasksInput{
Cluster: &c,
DesiredStatus: aws.String(ecs.DesiredStatusRunning),
DesiredStatus: ecstypes.DesiredStatusRunning,
}
tasks := make(map[string]*ecs.Task)
err := client.ecs.ListTasksPagesWithContext(ctx, input, func(page *ecs.ListTasksOutput, lastPage bool) bool {
tasks := make(map[string]ecstypes.Task)
paginator := ecs.NewListTasksPaginator(client.ecs, input)
for paginator.HasMorePages() {
page, err := paginator.NextPage(ctx)
if err != nil {
return nil, fmt.Errorf("listing tasks: %w", err)
}
if len(page.TaskArns) > 0 {
resp, err := client.ecs.DescribeTasksWithContext(ctx, &ecs.DescribeTasksInput{
resp, err := client.ecs.DescribeTasks(ctx, &ecs.DescribeTasksInput{
Tasks: page.TaskArns,
Cluster: &c,
})
@@ -265,16 +247,12 @@ func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsI
logger.Errorf("Unable to describe tasks for %v", page.TaskArns)
} else {
for _, t := range resp.Tasks {
if aws.StringValue(t.LastStatus) == ecs.DesiredStatusRunning {
tasks[aws.StringValue(t.TaskArn)] = t
if aws.ToString(t.LastStatus) == string(ecstypes.DesiredStatusRunning) {
tasks[aws.ToString(t.TaskArn)] = t
}
}
}
}
return !lastPage
})
if err != nil {
return nil, fmt.Errorf("listing tasks: %w", err)
}
// Skip to the next cluster if there are no tasks found on
@@ -288,7 +266,7 @@ func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsI
return nil, err
}
miInstances := make(map[string]*ssm.InstanceInformation)
miInstances := make(map[string]ssmtypes.InstanceInformation)
if p.ECSAnywhere {
// Try looking up for instances on ECS Anywhere
miInstances, err = p.lookupMiInstances(ctx, client, &c, tasks)
@@ -303,74 +281,67 @@ func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsI
}
for key, task := range tasks {
containerInstance := ec2Instances[aws.StringValue(task.ContainerInstanceArn)]
containerInstance, hasContainerInstance := ec2Instances[aws.ToString(task.ContainerInstanceArn)]
taskDef := taskDefinitions[key]
for _, container := range task.Containers {
var containerDefinition *ecs.ContainerDefinition
var containerDefinition *ecstypes.ContainerDefinition
for _, def := range taskDef.ContainerDefinitions {
if aws.StringValue(container.Name) == aws.StringValue(def.Name) {
containerDefinition = def
if aws.ToString(container.Name) == aws.ToString(def.Name) {
containerDefinition = &def
break
}
}
if containerDefinition == nil {
logger.Debugf("Unable to find container definition for %s", aws.StringValue(container.Name))
logger.Debugf("Unable to find container definition for %s", aws.ToString(container.Name))
continue
}
var mach *machine
if aws.StringValue(taskDef.NetworkMode) == "awsvpc" && len(task.Attachments) != 0 {
if taskDef.NetworkMode == ecstypes.NetworkModeAwsvpc && len(task.Attachments) != 0 {
if len(container.NetworkInterfaces) == 0 {
logger.Errorf("Skip container %s: no network interfaces", aws.StringValue(container.Name))
logger.Errorf("Skip container %s: no network interfaces", aws.ToString(container.Name))
continue
}
var ports []portMapping
for _, mapping := range containerDefinition.PortMappings {
if mapping != nil {
protocol := "TCP"
if aws.StringValue(mapping.Protocol) == "udp" {
protocol = "UDP"
}
ports = append(ports, portMapping{
hostPort: aws.Int64Value(mapping.HostPort),
containerPort: aws.Int64Value(mapping.ContainerPort),
protocol: protocol,
})
}
ports = append(ports, portMapping{
hostPort: aws.ToInt32(mapping.HostPort),
containerPort: aws.ToInt32(mapping.ContainerPort),
protocol: mapping.Protocol,
})
}
mach = &machine{
privateIP: aws.StringValue(container.NetworkInterfaces[0].PrivateIpv4Address),
privateIP: aws.ToString(container.NetworkInterfaces[0].PrivateIpv4Address),
ports: ports,
state: aws.StringValue(task.LastStatus),
healthStatus: aws.StringValue(task.HealthStatus),
state: ec2types.InstanceStateName(strings.ToLower(aws.ToString(task.LastStatus))),
healthStatus: task.HealthStatus,
}
} else {
miContainerInstance := miInstances[aws.StringValue(task.ContainerInstanceArn)]
if containerInstance == nil && miContainerInstance == nil {
logger.Errorf("Unable to find container instance information for %s", aws.StringValue(container.Name))
miContainerInstance, hasMiContainerInstance := miInstances[aws.ToString(task.ContainerInstanceArn)]
if !hasContainerInstance && !hasMiContainerInstance {
logger.Errorf("Unable to find container instance information for %s", aws.ToString(container.Name))
continue
}
var ports []portMapping
for _, mapping := range container.NetworkBindings {
if mapping != nil {
ports = append(ports, portMapping{
hostPort: aws.Int64Value(mapping.HostPort),
containerPort: aws.Int64Value(mapping.ContainerPort),
})
}
ports = append(ports, portMapping{
hostPort: aws.ToInt32(mapping.HostPort),
containerPort: aws.ToInt32(mapping.ContainerPort),
protocol: mapping.Protocol,
})
}
var privateIPAddress, stateName string
if containerInstance != nil {
privateIPAddress = aws.StringValue(containerInstance.PrivateIpAddress)
stateName = aws.StringValue(containerInstance.State.Name)
} else if miContainerInstance != nil {
privateIPAddress = aws.StringValue(miContainerInstance.IPAddress)
stateName = aws.StringValue(task.LastStatus)
var privateIPAddress string
var stateName ec2types.InstanceStateName
if hasContainerInstance {
privateIPAddress = aws.ToString(containerInstance.PrivateIpAddress)
stateName = containerInstance.State.Name
} else if hasMiContainerInstance {
privateIPAddress = aws.ToString(miContainerInstance.IPAddress)
stateName = ec2types.InstanceStateName(strings.ToLower(aws.ToString(task.LastStatus)))
}
mach = &machine{
@@ -381,11 +352,11 @@ func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsI
}
instance := ecsInstance{
Name: fmt.Sprintf("%s-%s", strings.Replace(aws.StringValue(task.Group), ":", "-", 1), *container.Name),
Name: fmt.Sprintf("%s-%s", strings.Replace(aws.ToString(task.Group), ":", "-", 1), aws.ToString(container.Name)),
ID: key[len(key)-12:],
containerDefinition: containerDefinition,
machine: mach,
Labels: aws.StringValueMap(containerDefinition.DockerLabels),
Labels: containerDefinition.DockerLabels,
}
extraConf, err := p.getConfiguration(instance)
@@ -403,21 +374,21 @@ func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsI
return instances, nil
}
func (p *Provider) lookupMiInstances(ctx context.Context, client *awsClient, clusterName *string, ecsDatas map[string]*ecs.Task) (map[string]*ssm.InstanceInformation, error) {
func (p *Provider) lookupMiInstances(ctx context.Context, client *awsClient, clusterName *string, ecsDatas map[string]ecstypes.Task) (map[string]ssmtypes.InstanceInformation, error) {
instanceIDs := make(map[string]string)
miInstances := make(map[string]*ssm.InstanceInformation)
miInstances := make(map[string]ssmtypes.InstanceInformation)
var containerInstancesArns []*string
var instanceArns []*string
var containerInstancesArns []string
var instanceArns []string
for _, task := range ecsDatas {
if task.ContainerInstanceArn != nil {
containerInstancesArns = append(containerInstancesArns, task.ContainerInstanceArn)
containerInstancesArns = append(containerInstancesArns, *task.ContainerInstanceArn)
}
}
for _, arns := range p.chunkIDs(containerInstancesArns) {
resp, err := client.ecs.DescribeContainerInstancesWithContext(ctx, &ecs.DescribeContainerInstancesInput{
for arns := range chunkIDs(containerInstancesArns) {
resp, err := client.ecs.DescribeContainerInstances(ctx, &ecs.DescribeContainerInstancesInput{
ContainerInstances: arns,
Cluster: clusterName,
})
@@ -426,23 +397,21 @@ func (p *Provider) lookupMiInstances(ctx context.Context, client *awsClient, clu
}
for _, container := range resp.ContainerInstances {
instanceIDs[aws.StringValue(container.Ec2InstanceId)] = aws.StringValue(container.ContainerInstanceArn)
instanceIDs[aws.ToString(container.Ec2InstanceId)] = aws.ToString(container.ContainerInstanceArn)
// Disallow EC2 Instance IDs
// This prevents considering EC2 instances in ECS
// and getting InvalidInstanceID.Malformed error when calling the describe-instances endpoint.
if !strings.HasPrefix(aws.StringValue(container.Ec2InstanceId), "mi-") {
continue
if strings.HasPrefix(aws.ToString(container.Ec2InstanceId), "mi-") {
instanceArns = append(instanceArns, *container.Ec2InstanceId)
}
instanceArns = append(instanceArns, container.Ec2InstanceId)
}
}
if len(instanceArns) > 0 {
for _, ids := range p.chunkIDs(instanceArns) {
for ids := range chunkIDs(instanceArns) {
input := &ssm.DescribeInstanceInformationInput{
Filters: []*ssm.InstanceInformationStringFilter{
Filters: []ssmtypes.InstanceInformationStringFilter{
{
Key: aws.String("InstanceIds"),
Values: ids,
@@ -450,18 +419,18 @@ func (p *Provider) lookupMiInstances(ctx context.Context, client *awsClient, clu
},
}
err := client.ssm.DescribeInstanceInformationPagesWithContext(ctx, input, func(page *ssm.DescribeInstanceInformationOutput, lastPage bool) bool {
if len(page.InstanceInformationList) > 0 {
for _, i := range page.InstanceInformationList {
if i.InstanceId != nil {
miInstances[instanceIDs[aws.StringValue(i.InstanceId)]] = i
}
paginator := ssm.NewDescribeInstanceInformationPaginator(client.ssm, input)
for paginator.HasMorePages() {
page, err := paginator.NextPage(ctx)
if err != nil {
return nil, fmt.Errorf("describing instances: %w", err)
}
for _, i := range page.InstanceInformationList {
if i.InstanceId != nil {
miInstances[instanceIDs[aws.ToString(i.InstanceId)]] = i
}
}
return !lastPage
})
if err != nil {
return nil, fmt.Errorf("describing instances: %w", err)
}
}
}
@@ -469,21 +438,21 @@ func (p *Provider) lookupMiInstances(ctx context.Context, client *awsClient, clu
return miInstances, nil
}
func (p *Provider) lookupEc2Instances(ctx context.Context, client *awsClient, clusterName *string, ecsDatas map[string]*ecs.Task) (map[string]*ec2.Instance, error) {
func (p *Provider) lookupEc2Instances(ctx context.Context, client *awsClient, clusterName *string, ecsDatas map[string]ecstypes.Task) (map[string]ec2types.Instance, error) {
instanceIDs := make(map[string]string)
ec2Instances := make(map[string]*ec2.Instance)
ec2Instances := make(map[string]ec2types.Instance)
var containerInstancesArns []*string
var instanceArns []*string
var containerInstancesArns []string
var instanceArns []string
for _, task := range ecsDatas {
if task.ContainerInstanceArn != nil {
containerInstancesArns = append(containerInstancesArns, task.ContainerInstanceArn)
containerInstancesArns = append(containerInstancesArns, *task.ContainerInstanceArn)
}
}
for _, arns := range p.chunkIDs(containerInstancesArns) {
resp, err := client.ecs.DescribeContainerInstancesWithContext(ctx, &ecs.DescribeContainerInstancesInput{
for arns := range chunkIDs(containerInstancesArns) {
resp, err := client.ecs.DescribeContainerInstances(ctx, &ecs.DescribeContainerInstancesInput{
ContainerInstances: arns,
Cluster: clusterName,
})
@@ -492,38 +461,38 @@ func (p *Provider) lookupEc2Instances(ctx context.Context, client *awsClient, cl
}
for _, container := range resp.ContainerInstances {
instanceIDs[aws.StringValue(container.Ec2InstanceId)] = aws.StringValue(container.ContainerInstanceArn)
instanceIDs[aws.ToString(container.Ec2InstanceId)] = aws.ToString(container.ContainerInstanceArn)
// Disallow Instance IDs of the form mi-*
// This prevents considering external instances in ECS Anywhere setups
// and getting InvalidInstanceID.Malformed error when calling the describe-instances endpoint.
if strings.HasPrefix(aws.StringValue(container.Ec2InstanceId), "mi-") {
if strings.HasPrefix(aws.ToString(container.Ec2InstanceId), "mi-") {
continue
}
instanceArns = append(instanceArns, container.Ec2InstanceId)
if container.Ec2InstanceId != nil {
instanceArns = append(instanceArns, *container.Ec2InstanceId)
}
}
}
if len(instanceArns) > 0 {
for _, ids := range p.chunkIDs(instanceArns) {
for ids := range chunkIDs(instanceArns) {
input := &ec2.DescribeInstancesInput{
InstanceIds: ids,
}
err := client.ec2.DescribeInstancesPagesWithContext(ctx, input, func(page *ec2.DescribeInstancesOutput, lastPage bool) bool {
if len(page.Reservations) > 0 {
for _, r := range page.Reservations {
for _, i := range r.Instances {
if i.InstanceId != nil {
ec2Instances[instanceIDs[aws.StringValue(i.InstanceId)]] = i
}
paginator := ec2.NewDescribeInstancesPaginator(client.ec2, input)
for paginator.HasMorePages() {
page, err := paginator.NextPage(ctx)
if err != nil {
return nil, fmt.Errorf("describing instances: %w", err)
}
for _, r := range page.Reservations {
for _, i := range r.Instances {
if i.InstanceId != nil {
ec2Instances[instanceIDs[aws.ToString(i.InstanceId)]] = i
}
}
}
return !lastPage
})
if err != nil {
return nil, fmt.Errorf("describing instances: %w", err)
}
}
}
@@ -531,16 +500,16 @@ func (p *Provider) lookupEc2Instances(ctx context.Context, client *awsClient, cl
return ec2Instances, nil
}
func (p *Provider) lookupTaskDefinitions(ctx context.Context, client *awsClient, taskDefArns map[string]*ecs.Task) (map[string]*ecs.TaskDefinition, error) {
func (p *Provider) lookupTaskDefinitions(ctx context.Context, client *awsClient, taskDefArns map[string]ecstypes.Task) (map[string]*ecstypes.TaskDefinition, error) {
logger := log.FromContext(ctx)
taskDef := make(map[string]*ecs.TaskDefinition)
taskDef := make(map[string]*ecstypes.TaskDefinition)
for arn, task := range taskDefArns {
if definition, ok := existingTaskDefCache.Get(arn); ok {
taskDef[arn] = definition.(*ecs.TaskDefinition)
taskDef[arn] = definition.(*ecstypes.TaskDefinition)
logger.Debugf("Found cached task definition for %s. Skipping the call", arn)
} else {
resp, err := client.ecs.DescribeTaskDefinitionWithContext(ctx, &ecs.DescribeTaskDefinitionInput{
resp, err := client.ecs.DescribeTaskDefinition(ctx, &ecs.DescribeTaskDefinitionInput{
TaskDefinition: task.TaskDefinitionArn,
})
if err != nil {
@@ -556,16 +525,6 @@ func (p *Provider) lookupTaskDefinitions(ctx context.Context, client *awsClient,
// chunkIDs ECS expects no more than 100 parameters be passed to a API call;
// thus, pack each string into an array capped at 100 elements.
func (p *Provider) chunkIDs(ids []*string) [][]*string {
var chunked [][]*string
for i := 0; i < len(ids); i += 100 {
var sliceEnd int
if i+100 < len(ids) {
sliceEnd = i + 100
} else {
sliceEnd = len(ids)
}
chunked = append(chunked, ids[i:sliceEnd])
}
return chunked
func chunkIDs(ids []string) iter.Seq[[]string] {
return slices.Chunk(ids, 100)
}

View File

@@ -3,13 +3,10 @@ package ecs
import (
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/stretchr/testify/assert"
)
func TestChunkIDs(t *testing.T) {
provider := &Provider{}
testCases := []struct {
desc string
count int
@@ -71,13 +68,13 @@ func TestChunkIDs(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
var IDs []*string
var IDs []string
for range test.count {
IDs = append(IDs, aws.String("a"))
IDs = append(IDs, "a")
}
var outCount []int
for _, el := range provider.chunkIDs(IDs) {
for el := range chunkIDs(IDs) {
outCount = append(outCount, len(el))
}

View File

@@ -1,7 +1,7 @@
/*
The MIT License (MIT)
Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs
Copyright (c) 2016-2020 Containous SAS; 2020-2025 Traefik Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,7 +1,7 @@
/*
The MIT License (MIT)
Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs
Copyright (c) 2016-2020 Containous SAS; 2020-2025 Traefik Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,7 +1,7 @@
/*
The MIT License (MIT)
Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs
Copyright (c) 2016-2020 Containous SAS; 2020-2025 Traefik Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,7 +1,7 @@
/*
The MIT License (MIT)
Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs
Copyright (c) 2016-2020 Containous SAS; 2020-2025 Traefik Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,7 +1,7 @@
/*
The MIT License (MIT)
Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs
Copyright (c) 2016-2020 Containous SAS; 2020-2025 Traefik Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,7 +1,7 @@
/*
The MIT License (MIT)
Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs
Copyright (c) 2016-2020 Containous SAS; 2020-2025 Traefik Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,7 +1,7 @@
/*
The MIT License (MIT)
Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs
Copyright (c) 2016-2020 Containous SAS; 2020-2025 Traefik Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,7 +1,7 @@
/*
The MIT License (MIT)
Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs
Copyright (c) 2016-2020 Containous SAS; 2020-2025 Traefik Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,7 +1,7 @@
/*
The MIT License (MIT)
Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs
Copyright (c) 2016-2020 Containous SAS; 2020-2025 Traefik Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,7 +1,7 @@
/*
The MIT License (MIT)
Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs
Copyright (c) 2016-2020 Containous SAS; 2020-2025 Traefik Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,7 +1,7 @@
/*
The MIT License (MIT)
Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs
Copyright (c) 2016-2020 Containous SAS; 2020-2025 Traefik Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,7 +1,7 @@
/*
The MIT License (MIT)
Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs
Copyright (c) 2016-2020 Containous SAS; 2020-2025 Traefik Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,7 +1,7 @@
/*
The MIT License (MIT)
Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs
Copyright (c) 2016-2020 Containous SAS; 2020-2025 Traefik Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,7 +1,7 @@
/*
The MIT License (MIT)
Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs
Copyright (c) 2016-2020 Containous SAS; 2020-2025 Traefik Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,7 +1,7 @@
/*
The MIT License (MIT)
Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs
Copyright (c) 2016-2020 Containous SAS; 2020-2025 Traefik Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,7 +1,7 @@
/*
The MIT License (MIT)
Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs
Copyright (c) 2016-2020 Containous SAS; 2020-2025 Traefik Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

Some files were not shown because too many files have changed in this diff Show More