1
0
mirror of https://github.com/containous/traefik.git synced 2025-09-08 13:44:22 +03:00

Compare commits

...

47 Commits

Author SHA1 Message Date
Romain
a75b2384ea Prepare release v2.11.23 2025-04-17 11:56:03 +02:00
Kevin Pollet
8bdca45861 Bump gopkg.in/DataDog/dd-trace-go.v1 to v1.72.2 2025-04-17 11:48:04 +02:00
Kevin Pollet
7442162e3f Bump golang.org/x/net to v0.38.0 2025-04-17 10:16:04 +02:00
Romain
dd5cb68cb1 Sanitize request path 2025-04-17 10:02:04 +02:00
Ludovic Fernandez
299a16f0a4 Bump github.com/go-acme/lego/v4 to v4.23.1 2025-04-17 09:20:04 +02:00
Landry Benguigui
545f2feacc Add Content-Length header to preflight response 2025-04-16 15:00:05 +02:00
Romain
e3caaf0791 Bump golang.org/x/oauth2 to v0.28.0 2025-04-16 11:58:04 +02:00
Ludovic Fernandez
f794f8a294 chore: update linter 2025-04-11 10:56:05 +02:00
YapWC
8cf22207b5 Typo fix on the Explanation Section for User Guide HTTP Challenge. 2025-04-11 10:18:04 +02:00
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
89 changed files with 1854 additions and 1291 deletions

View File

@@ -61,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

View File

@@ -33,6 +33,7 @@ jobs:
ImageOS: ${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.goarm }}
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
- name: Build
run: make generate binary

View File

@@ -15,11 +15,11 @@ env:
jobs:
build-webui:
if: github.ref_type == 'tag'
if: github.ref_type == 'tag' && github.repository == 'traefik/traefik'
uses: ./.github/workflows/template-webui.yaml
build:
if: github.ref_type == 'tag'
if: github.ref_type == 'tag' && github.repository == 'traefik/traefik'
runs-on: ubuntu-latest
strategy:
@@ -41,6 +41,7 @@ jobs:
ImageOS: ${{ matrix.os }}
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
- name: Artifact webui
uses: actions/download-artifact@v4
@@ -80,7 +81,7 @@ jobs:
retention-days: 1
release:
if: github.ref_type == 'tag'
if: github.ref_type == 'tag' && github.repository == 'traefik/traefik'
runs-on: ubuntu-latest
needs:

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.63.3
GOLANGCI_LINT_VERSION: v2.0.2
MISSPELL_VERSION: v0.6.0
jobs:
@@ -25,9 +25,10 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
uses: golangci/golangci-lint-action@v7
with:
version: "${{ env.GOLANGCI_LINT_VERSION }}"
@@ -44,9 +45,10 @@ 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}
run: curl -sfL https://raw.githubusercontent.com/golangci/misspell/HEAD/install-misspell.sh | sh -s -- -b $(go env GOPATH)/bin ${MISSPELL_VERSION}
- name: Avoid generating webui
run: touch webui/static/index.html
@@ -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,287 +1,315 @@
run:
timeout: 10m
version: "2"
linters-settings:
govet:
enable-all: true
disable:
- shadow
- fieldalignment
gocyclo:
min-complexity: 14
goconst:
min-len: 3
min-occurrences: 4
misspell:
locale: US
funlen:
lines: -1
statements: 120
forbidigo:
forbid:
- ^print(ln)?$
- ^spew\.Print(f|ln)?$
- ^spew\.Dump$
depguard:
rules:
main:
deny:
- pkg: "github.com/instana/testify"
desc: not allowed
- pkg: "github.com/pkg/errors"
desc: Should be replaced by standard lib errors package
godox:
keywords:
- FIXME
importas:
no-unaliased: true
alias:
- alias: composeapi
pkg: github.com/docker/compose/v2/pkg/api
formatters:
enable:
- gci
- gofumpt
exclusions:
generated: lax
paths:
- pkg/provider/kubernetes/crd/generated/
# Standard Kubernetes rewrites:
- alias: corev1
pkg: "k8s.io/api/core/v1"
- alias: netv1
pkg: "k8s.io/api/networking/v1"
- alias: netv1beta1
pkg: "k8s.io/api/networking/v1beta1"
- alias: admv1
pkg: "k8s.io/api/admission/v1"
- alias: admv1beta1
pkg: "k8s.io/api/admission/v1beta1"
- alias: extv1beta1
pkg: "k8s.io/api/extensions/v1beta1"
- alias: metav1
pkg: "k8s.io/apimachinery/pkg/apis/meta/v1"
- alias: ktypes
pkg: "k8s.io/apimachinery/pkg/types"
- alias: kerror
pkg: "k8s.io/apimachinery/pkg/api/errors"
- alias: kclientset
pkg: "k8s.io/client-go/kubernetes"
- alias: kinformers
pkg: "k8s.io/client-go/informers"
- alias: ktesting
pkg: "k8s.io/client-go/testing"
- alias: kschema
pkg: "k8s.io/apimachinery/pkg/runtime/schema"
- alias: kscheme
pkg: "k8s.io/client-go/kubernetes/scheme"
- alias: kversion
pkg: "k8s.io/apimachinery/pkg/version"
- alias: kubefake
pkg: "k8s.io/client-go/kubernetes/fake"
- alias: discoveryfake
pkg: "k8s.io/client-go/discovery/fake"
# Kubernetes Gateway rewrites:
- alias: gateclientset
pkg: "sigs.k8s.io/gateway-api/pkg/client/clientset/gateway/versioned"
- alias: gateinformers
pkg: "sigs.k8s.io/gateway-api/pkg/client/informers/gateway/externalversions"
- alias: gatev1alpha2
pkg: "sigs.k8s.io/gateway-api/apis/v1alpha2"
# Traefik Kubernetes rewrites:
- alias: containousv1alpha1
pkg: "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefikcontainous/v1alpha1"
- alias: traefikv1alpha1
pkg: "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
- alias: traefikclientset
pkg: "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/generated/clientset/versioned"
- alias: traefikinformers
pkg: "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/generated/informers/externalversions"
- alias: traefikscheme
pkg: "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/generated/clientset/versioned/scheme"
- alias: traefikcrdfake
pkg: "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake"
tagalign:
align: false
sort: true
order:
- description
- json
- toml
- yaml
- yml
- label
- label-slice-as-struct
- file
- kv
- export
revive:
rules:
- name: struct-tag
- name: blank-imports
- name: context-as-argument
- name: context-keys-type
- name: dot-imports
- name: error-return
- name: error-strings
- name: error-naming
- name: exported
disabled: true
- name: if-return
- name: increment-decrement
- name: var-naming
- name: var-declaration
- name: package-comments
disabled: true
- name: range
- name: receiver-naming
- name: time-naming
- name: unexported-return
- name: indent-error-flow
- name: errorf
- name: empty-block
- name: superfluous-else
- name: unused-parameter
disabled: true
- 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
- github.com/mailgun/minheap
- github.com/mailgun/multibuf
- github.com/jaguilar/vt100
- github.com/cucumber/godog
testifylint:
disable:
- suite-dont-use-pkg
- require-error
- go-require
errcheck:
exclude-functions:
- fmt.Fprintln
linters:
enable-all: true
default: all
disable:
- sqlclosecheck # not relevant (SQL)
- rowserrcheck # not relevant (SQL)
- bodyclose # too many false-positive
- containedctx # too many false-positive
- contextcheck # too many false-positive
- cyclop # duplicate of gocyclo
- lll # Not relevant
- gocyclo # FIXME must be fixed
- gocognit # Too strict
- nestif # Too many false-positive.
- prealloc # Too many false-positive.
- makezero # Not relevant
- dupl # Too strict
- gosec # Too strict
- gochecknoinits
- gochecknoglobals
- wsl # Too strict
- nlreturn # Not relevant
- mnd # Too strict
- stylecheck # skip because report issues related to some generated files.
- testpackage # Too strict
- tparallel # Not relevant
- paralleltest # Not relevant
- err113 # Too strict
- exhaustive # Not relevant
- exhaustruct # Not relevant
- err113 # Too strict
- wrapcheck # Too strict
- noctx # Too strict
- bodyclose # too many false-positive
- forcetypeassert # Too strict
- tagliatelle # Too strict
- varnamelen # Not relevant
- nilnil # Not relevant
- ireturn # Not relevant
- contextcheck # too many false-positive
- containedctx # too many false-positive
- maintidx # kind of duplicate of gocyclo
- nonamedreturns # Too strict
- gochecknoglobals
- gochecknoinits
- gocognit # Too strict
- gocyclo # FIXME must be fixed
- gosec # Too strict
- gosmopolitan # not relevant
- exportloopref # Not relevant since go1.22
- ireturn # Not relevant
- lll # Not relevant
- maintidx # kind of duplicate of gocyclo
- makezero # Not relevant
- mnd # Too strict
- nestif # Too many false-positive.
- nilnil # Not relevant
- nlreturn # Not relevant
- noctx # Too strict
- nonamedreturns # Too strict
- paralleltest # Not relevant
- prealloc # Too many false-positive.
- rowserrcheck # not relevant (SQL)
- sqlclosecheck # not relevant (SQL)
- tagliatelle # Too strict
- testpackage # Too strict
- tparallel # Not relevant
- varnamelen # Not relevant
- wrapcheck # Too strict
- wsl # Too strict
settings:
depguard:
rules:
main:
deny:
- pkg: github.com/instana/testify
desc: not allowed
- pkg: github.com/pkg/errors
desc: Should be replaced by standard lib errors package
errcheck:
exclude-functions:
- fmt.Fprintln
forbidigo:
forbid:
- pattern: ^print(ln)?$
- pattern: ^spew\.Print(f|ln)?$
- pattern: ^spew\.Dump$
funlen:
lines: -1
statements: 120
goconst:
min-len: 3
min-occurrences: 4
gocyclo:
min-complexity: 14
godox:
keywords:
- FIXME
gomoddirectives:
toolchain-pattern: go1\.\d+\.\d+$
tool-forbidden: true
go-version-pattern: ^1\.\d+(\.0)?$
replace-allow-list:
- github.com/abbot/go-http-auth
- github.com/gorilla/mux
- github.com/mailgun/minheap
- github.com/mailgun/multibuf
- github.com/jaguilar/vt100
- github.com/cucumber/godog
govet:
enable-all: true
disable:
- shadow
- fieldalignment
importas:
no-unaliased: true
alias:
- pkg: github.com/docker/compose/v2/pkg/api
alias: composeapi
# Standard Kubernetes rewrites:
- pkg: k8s.io/api/core/v1
alias: corev1
- pkg: k8s.io/api/networking/v1
alias: netv1
- pkg: k8s.io/api/networking/v1beta1
alias: netv1beta1
- pkg: k8s.io/api/admission/v1
alias: admv1
- pkg: k8s.io/api/admission/v1beta1
alias: admv1beta1
- pkg: k8s.io/api/extensions/v1beta1
alias: extv1beta1
- pkg: k8s.io/apimachinery/pkg/apis/meta/v1
alias: metav1
- pkg: k8s.io/apimachinery/pkg/types
alias: ktypes
- pkg: k8s.io/apimachinery/pkg/api/errors
alias: kerror
- pkg: k8s.io/client-go/kubernetes
alias: kclientset
- pkg: k8s.io/client-go/informers
alias: kinformers
- pkg: k8s.io/client-go/testing
alias: ktesting
- pkg: k8s.io/apimachinery/pkg/runtime/schema
alias: kschema
- pkg: k8s.io/client-go/kubernetes/scheme
alias: kscheme
- pkg: k8s.io/apimachinery/pkg/version
alias: kversion
- pkg: k8s.io/client-go/kubernetes/fake
alias: kubefake
- pkg: k8s.io/client-go/discovery/fake
alias: discoveryfake
# Kubernetes Gateway rewrites:
- pkg: sigs.k8s.io/gateway-api/pkg/client/clientset/gateway/versioned
alias: gateclientset
- pkg: sigs.k8s.io/gateway-api/pkg/client/informers/gateway/externalversions
alias: gateinformers
- pkg: sigs.k8s.io/gateway-api/apis/v1alpha2
alias: gatev1alpha2
# Traefik Kubernetes rewrites:
- pkg: github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefikcontainous/v1alpha1
alias: containousv1alpha1
- pkg: github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefikio/v1alpha1
alias: traefikv1alpha1
- pkg: github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/generated/clientset/versioned
alias: traefikclientset
- pkg: github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/generated/informers/externalversions
alias: traefikinformers
- pkg: github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/generated/clientset/versioned/scheme
alias: traefikscheme
- pkg: github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake
alias: traefikcrdfake
misspell:
locale: US
revive:
rules:
- name: struct-tag
- name: blank-imports
- name: context-as-argument
- name: context-keys-type
- name: dot-imports
- name: error-return
- name: error-strings
- name: error-naming
- name: exported
disabled: true
- name: if-return
- name: increment-decrement
- name: var-naming
- name: var-declaration
- name: package-comments
disabled: true
- name: range
- name: receiver-naming
- name: time-naming
- name: unexported-return
- name: indent-error-flow
- name: errorf
- name: empty-block
- name: superfluous-else
- name: unused-parameter
disabled: true
- name: unreachable-code
- name: redefines-builtin-id
tagalign:
align: false
sort: true
order:
- description
- json
- toml
- yaml
- yml
- label
- label-slice-as-struct
- file
- kv
- export
testifylint:
disable:
- suite-dont-use-pkg
- require-error
- go-require
perfsprint:
err-error: true
errorf: true
sprintf1: true
strconcat: false
staticcheck:
checks:
- all
- '-ST1000'
- '-ST1003'
- '-ST1016'
- '-ST1020'
- '-ST1021'
- '-ST1022'
- '-QF1001'
- '-QF1008' # TODO must be fixed
exclusions:
generated: lax
presets:
- comments
- std-error-handling
rules:
- path: (.+)_test.go
linters:
- canonicalheader
- fatcontext
- funlen
- goconst
- godot
- path: (.+)_test.go
text: ' always receives '
linters:
- unparam
- path: pkg/server/service/bufferpool.go
text: 'SA6002: argument should be pointer-like to avoid allocations'
- path: pkg/server/middleware/middlewares.go
text: Function 'buildConstructor' has too many statements
linters:
- funlen
- path: pkg/tracing/haystack/logger.go
linters:
- goprintffuncname
- path: pkg/tracing/tracing.go
text: printf-like formatting function 'SetErrorWithEvent' should be named 'SetErrorWithEventf'
linters:
- goprintffuncname
- path: pkg/tls/tlsmanager_test.go
text: 'SA1019: config.ClientCAs.Subjects has been deprecated since Go 1.18'
- path: pkg/types/tls_test.go
text: 'SA1019: tlsConfig.RootCAs.Subjects has been deprecated since Go 1.18'
- path: pkg/provider/kubernetes/(crd|gateway)/client.go
linters:
- interfacebloat
- path: pkg/metrics/metrics.go
linters:
- interfacebloat
- path: integration/healthcheck_test.go
text: Duplicate words \(wsp2,\) found
linters:
- dupword
- path: pkg/types/domain_test.go
text: Duplicate words \(sub\) found
linters:
- dupword
- path: pkg/provider/kubernetes/gateway/client_mock_test.go
text: 'unusedwrite: unused write to field'
linters:
- govet
- 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
- path: pkg/config/static/static_config.go
source: 'errors.New\("Consul Catalog provider'
text: 'ST1005: error strings should not be capitalized'
- path: pkg/config/static/static_config.go
source: 'errors.New\("Consul provider'
text: 'ST1005: error strings should not be capitalized'
- path: pkg/config/static/static_config.go
source: 'errors.New\("Nomad provider'
text: 'ST1005: error strings should not be capitalized'
- path: (.+)\.go
text: 'struct-tag: unknown option ''inline'' in JSON tag'
linters:
- revive
- path: (.+)\.go
text: 'struct-tag: unknown option ''omitzero'' in TOML tag'
linters:
- revive
- path: (.+)\.go$
text: 'SA1019: http.CloseNotifier has been deprecated' # FIXME must be fixed
- path: (.+)\.go$
text: 'SA1019: cfg.(SSLRedirect|SSLTemporaryRedirect|SSLHost|SSLForceHost|FeaturePolicy) is deprecated'
- path: (.+)\.go$
text: 'SA1019: c.Providers.(ConsulCatalog|Consul|Nomad).Namespace is deprecated'
- path: (.+)\.go$
text: 'SA1019: dockertypes.ContainerNode is deprecated'
paths:
- pkg/provider/kubernetes/crd/generated/
issues:
exclude-use-default: false
max-issues-per-linter: 0
max-same-issues: 0
exclude-dirs:
- pkg/provider/kubernetes/crd/generated/
exclude:
- 'Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked'
- "should have a package comment, unless it's in another file for this package"
- 'SA1019: http.CloseNotifier has been deprecated' # FIXME must be fixed
- 'SA1019: cfg.SSLRedirect is deprecated'
- 'SA1019: cfg.SSLTemporaryRedirect is deprecated'
- 'SA1019: cfg.SSLHost is deprecated'
- 'SA1019: cfg.SSLForceHost is deprecated'
- 'SA1019: cfg.FeaturePolicy is deprecated'
- 'SA1019: c.Providers.ConsulCatalog.Namespace is deprecated'
- 'SA1019: c.Providers.Consul.Namespace is deprecated'
- 'SA1019: c.Providers.Nomad.Namespace is deprecated'
- 'fmt.Sprintf can be replaced with string'
- 'SA1019: dockertypes.ContainerNode is deprecated'
exclude-rules:
- path: '(.+)_test.go'
linters:
- goconst
- funlen
- godot
- canonicalheader
- fatcontext
- path: '(.+)_test.go'
text: ' always receives '
linters:
- unparam
- path: '(.+)\.go'
text: 'struct-tag: unknown option ''inline'' in JSON tag'
linters:
- revive
- path: pkg/server/service/bufferpool.go
text: 'SA6002: argument should be pointer-like to avoid allocations'
- path: pkg/server/middleware/middlewares.go
text: "Function 'buildConstructor' has too many statements"
linters:
- funlen
- path: pkg/tracing/haystack/logger.go
linters:
- goprintffuncname
- path: pkg/tracing/tracing.go
text: "printf-like formatting function 'SetErrorWithEvent' should be named 'SetErrorWithEventf'"
linters:
- goprintffuncname
- path: pkg/tls/tlsmanager_test.go
text: 'SA1019: config.ClientCAs.Subjects has been deprecated since Go 1.18'
- path: pkg/types/tls_test.go
text: 'SA1019: tlsConfig.RootCAs.Subjects has been deprecated since Go 1.18'
- path: pkg/provider/kubernetes/(crd|gateway)/client.go
linters:
- interfacebloat
- path: pkg/metrics/metrics.go
linters:
- interfacebloat
- path: integration/healthcheck_test.go
text: 'Duplicate words \(wsp2,\) found'
linters:
- dupword
- path: pkg/types/domain_test.go
text: 'Duplicate words \(sub\) found'
linters:
- dupword
- path: pkg/provider/kubernetes/gateway/client_mock_test.go
text: 'unusedwrite: unused write to field'
linters:
- govet
- 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,3 +1,73 @@
## [v2.11.23](https://github.com/traefik/traefik/tree/v2.11.23) (2025-04-17)
[All Commits](https://github.com/traefik/traefik/compare/v2.11.22...v2.11.23)
**Bug fixes:**
- **[acme]** Bump github.com/go-acme/lego/v4 to v4.23.1 ([#11690](https://github.com/traefik/traefik/pull/11690) by [ldez](https://github.com/ldez))
- **[metrics]** Bump gopkg.in/DataDog/dd-trace-go.v1 to v1.72.2 ([#11693](https://github.com/traefik/traefik/pull/11693) by [kevinpollet](https://github.com/kevinpollet))
- **[middleware]** Add Content-Length header to preflight response ([#11682](https://github.com/traefik/traefik/pull/11682) by [lbenguigui](https://github.com/lbenguigui))
- **[server]** Sanitize request path ([#11684](https://github.com/traefik/traefik/pull/11684) by [rtribotte](https://github.com/rtribotte))
- Bump golang.org/x/net to v0.38.0 ([#11691](https://github.com/traefik/traefik/pull/11691) by [kevinpollet](https://github.com/kevinpollet))
- Bump golang.org/x/oauth2 to v0.28.0 ([#11689](https://github.com/traefik/traefik/pull/11689) by [rtribotte](https://github.com/rtribotte))
**Documentation:**
- Typo fix on the Explanation Section for User Guide HTTP Challenge. ([#11676](https://github.com/traefik/traefik/pull/11676) by [YapWC](https://github.com/YapWC))
## [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)

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

@@ -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

@@ -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

@@ -14,8 +14,8 @@ RUN apk --no-cache --no-progress add \
ruby-json \
zlib-dev
RUN gem install nokogiri --version 1.16.8 --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

@@ -4,24 +4,31 @@
Below is a non-exhaustive list of versions and their maintenance status:
| Version | Release Date | Community Support |
|---------|--------------|--------------------|
| 3.2 | Oct 28, 2024 | Yes |
| 3.1 | Jul 15, 2024 | Ended Oct 28, 2024 |
| 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,16 +316,20 @@ 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) |
| [Active24](https://www.active24.cz) | `active24` | `ACTIVE24_API_KEY`, `ACTIVE24_SECRET` | [Additional configuration](https://go-acme.github.io/lego/dns/active24) |
| [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) |
| [Axelname](https://axelname.ru) | `axelname` | `AXELNAME_NICKNAME`, `AXELNAME_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/axelname) |
| [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) |
| [Baidu Cloud](https://cloud.baidu.com) | `baiducloud` | `BAIDUCLOUD_ACCESS_KEY_ID`, `BAIDUCLOUD_SECRET_ACCESS_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/baiducloud) |
| [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) |
| [BookMyName](https://www.bookmyname.com) | `bookmyname` | `BOOKMYNAME_USERNAME`, `BOOKMYNAME_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/bookmyname) |
| [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/) |
@@ -358,6 +362,7 @@ For complete details, refer to your provider's _Additional configuration_ link.
| [Efficient IP](https://efficientip.com) | `efficientip` | `EFFICIENTIP_USERNAME`, `EFFICIENTIP_PASSWORD`, `EFFICIENTIP_HOSTNAME`, `EFFICIENTIP_DNS_NAME` | [Additional configuration](https://go-acme.github.io/lego/dns/efficientip) |
| [Epik](https://www.epik.com) | `epik` | `EPIK_SIGNATURE` | [Additional configuration](https://go-acme.github.io/lego/dns/epik) |
| [Exoscale](https://www.exoscale.com) | `exoscale` | `EXOSCALE_API_KEY`, `EXOSCALE_API_SECRET`, `EXOSCALE_ENDPOINT` | [Additional configuration](https://go-acme.github.io/lego/dns/exoscale) |
| [F5 XC](https://www.f5.com/products/distributed-cloud-services) | `f5xc` | `F5XC_API_TOKEN`, `F5XC_TENANT_NAME`, `F5XC_GROUP_NAME` | [Additional configuration](https://go-acme.github.io/lego/dns/f5xc) |
| [Fast DNS](https://www.akamai.com/) | `fastdns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/edgedns) |
| [Freemyip.com](https://freemyip.com) | `freemyip` | `FREEMYIP_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/freemyip) |
| [G-Core](https://gcore.com/dns/) | `gcore` | `GCORE_PERMANENT_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/gcore) |
@@ -395,8 +400,10 @@ For complete details, refer to your provider's _Additional configuration_ link.
| [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) |
| [Metaregistrar](https://metaregistrar.com) | `metaregistrar` | `METAREGISTRAR_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/metaregistrar) |
| [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) |
@@ -434,6 +441,7 @@ 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) |

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

@@ -656,3 +656,21 @@ Please check out the [entrypoint forwarded headers connection option configurati
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.
## v2.11.23
### Request Path Sanitization
Since `v2.11.23`, the incoming request path is now cleaned before being used to match the router rules and sent to the backends.
Any `/../`, `/./` or duplicate slash segments in the request path is interpreted and/or collapsed.
If you want to disable this behavior, you can set the [`sanitizePath` option](../routing/entrypoints.md#sanitizepath) to `false` in the entryPoint HTTP configuration.
This can be useful when dealing with legacy clients that are not url-encoding data in the request path.
For example, as base64 uses the “/” character internally,
if it's not url encoded,
it can lead to unsafe routing when the `sanitizePath` option is set to `false`.
!!! warning "Security"
Setting the `sanitizePath` option to `false` is not safe.
Ensure every request is properly url encoded instead.

View File

@@ -236,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

@@ -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

@@ -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

@@ -141,6 +141,9 @@ Scheme used for the redirection. (Default: ```https```)
`--entrypoints.<name>.http.redirections.entrypoint.to`:
Targeted entry point of the redirection.
`--entrypoints.<name>.http.sanitizepath`:
Defines whether to enable request path sanitization (removal of /./, /../ and multiple slash sequences). (Default: ```true```)
`--entrypoints.<name>.http.tls`:
Default TLS configuration for the routers linked to the entry point. (Default: ```false```)

View File

@@ -150,6 +150,9 @@ Scheme used for the redirection. (Default: ```https```)
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_REDIRECTIONS_ENTRYPOINT_TO`:
Targeted entry point of the redirection.
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_SANITIZEPATH`:
Defines whether to enable request path sanitization (removal of /./, /../ and multiple slash sequences). (Default: ```true```)
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_TLS`:
Default TLS configuration for the routers linked to the entry point. (Default: ```false```)

View File

@@ -37,6 +37,7 @@
[entryPoints.EntryPoint0.http]
middlewares = ["foobar", "foobar"]
encodeQuerySemicolons = true
sanitizePath = true
[entryPoints.EntryPoint0.http.redirections]
[entryPoints.EntryPoint0.http.redirections.entryPoint]
to = "foobar"

View File

@@ -63,6 +63,7 @@ entryPoints:
- foobar
- foobar
encodeQuerySemicolons: true
sanitizePath: true
http2:
maxConcurrentStreams: 42
http3:

View File

@@ -994,6 +994,56 @@ entryPoints:
| false | foo=bar&baz=bar;foo | foo=bar&baz=bar&foo |
| true | foo=bar&baz=bar;foo | foo=bar&baz=bar%3Bfoo |
### SanitizePath
_Optional, Default=true_
The `sanitizePath` option defines whether to enable the request path sanitization.
When disabled, the incoming request path is passed to the backend as is.
This can be useful when dealing with legacy clients that are not url-encoding data in the request path.
For example, as base64 uses the “/” character internally,
if it's not url encoded,
it can lead to unsafe routing when the `sanitizePath` option is set to `false`.
!!! warning "Security"
Setting the sanitizePath option to false is not safe.
Ensure every request is properly url encoded instead.
```yaml tab="File (YAML)"
entryPoints:
websecure:
address: ':443'
http:
sanitizePath: false
```
```toml tab="File (TOML)"
[entryPoints.websecure]
address = ":443"
[entryPoints.websecure.http]
sanitizePath = false
```
```bash tab="CLI"
--entryPoints.websecure.address=:443
--entryPoints.websecure.http.sanitizePath=false
```
#### Examples
| SanitizePath | Request Path | Resulting Request Path |
|--------------|-----------------|------------------------|
| false | /./foo/bar | /./foo/bar |
| true | /./foo/bar | /foo/bar |
| false | /foo/../bar | /foo/../bar |
| true | /foo/../bar | /bar |
| false | /foo/bar// | /foo/bar// |
| true | /foo/bar// | /foo/bar/ |
| false | /./foo/../bar// | /./foo/../bar// |
| true | /./foo/../bar// | /bar/ |
### Middlewares
The list of middlewares that are prepended by default to the list of middlewares of each router associated to the named entry point.

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
@@ -55,7 +55,7 @@ ports:
- "443:443"
```
- We configure the HTTPS Let's Encrypt challenge:
- We configure the HTTP Let's Encrypt challenge:
```yaml
command:

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"

224
go.mod
View File

@@ -3,11 +3,17 @@ module github.com/traefik/traefik/v2
go 1.23.0
require (
github.com/BurntSushi/toml v1.4.0
github.com/BurntSushi/toml v1.5.0
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.3
github.com/aws/aws-sdk-go-v2/config v1.29.9
github.com/aws/aws-sdk-go-v2/credentials v1.17.62
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
@@ -17,13 +23,13 @@ require (
github.com/fatih/structs v1.1.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.21.0
github.com/go-acme/lego/v4 v4.23.1
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
@@ -39,10 +45,10 @@ require (
github.com/kvtools/valkeyrie v1.0.0
github.com/kvtools/zookeeper v1.0.2
github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f // No tag on the repo.
github.com/miekg/dns v1.1.62
github.com/miekg/dns v1.1.64
github.com/mitchellh/copystructure v1.2.0
github.com/mitchellh/hashstructure v1.0.0
github.com/mitchellh/mapstructure v1.5.0
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // No tag on the repo.
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5
github.com/openzipkin/zipkin-go v0.2.5
@@ -50,71 +56,78 @@ require (
github.com/pires/go-proxyproto v0.6.1
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/prometheus/client_model v0.6.1
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.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/mod v0.22.0
golang.org/x/net v0.33.0
golang.org/x/text v0.21.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
golang.org/x/mod v0.23.0
golang.org/x/net v0.38.0
golang.org/x/text v0.23.0
golang.org/x/time v0.11.0
golang.org/x/tools v0.30.0
google.golang.org/grpc v1.71.0
gopkg.in/DataDog/dd-trace-go.v1 v1.72.2
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.26.3
k8s.io/api v0.26.7
k8s.io/apiextensions-apiserver v0.26.3
k8s.io/apimachinery v0.26.3
k8s.io/client-go v0.26.3
k8s.io/apimachinery v0.26.7
k8s.io/client-go v0.26.7
k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 // No tag on the repo.
mvdan.cc/xurls/v2 v2.5.0
sigs.k8s.io/gateway-api v0.4.0
)
require (
cloud.google.com/go/auth v0.13.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect
cloud.google.com/go/auth v0.15.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.7 // 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.16.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 // 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.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
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
github.com/Azure/go-autorest/autorest v0.11.30 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect
github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.1 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
github.com/DataDog/appsec-internal-go v1.0.0 // indirect
github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 // indirect
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.0-devel.0.20230725154044-2549ba9058df // indirect
github.com/DataDog/datadog-go/v5 v5.3.0 // indirect
github.com/DataDog/go-libddwaf v1.5.0 // indirect
github.com/DataDog/go-tuf v1.0.2-0.5.2 // indirect
github.com/DataDog/sketches-go v1.4.2 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect
github.com/DataDog/appsec-internal-go v1.9.0 // indirect
github.com/DataDog/datadog-agent/pkg/obfuscate v0.58.0 // indirect
github.com/DataDog/datadog-agent/pkg/proto v0.58.0 // indirect
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.58.0 // indirect
github.com/DataDog/datadog-agent/pkg/trace v0.58.0 // indirect
github.com/DataDog/datadog-agent/pkg/util/log v0.58.0 // indirect
github.com/DataDog/datadog-agent/pkg/util/scrubber v0.58.0 // indirect
github.com/DataDog/datadog-go/v5 v5.5.0 // indirect
github.com/DataDog/go-libddwaf/v3 v3.5.1 // indirect
github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20241206090539-a14610dc22b6 // indirect
github.com/DataDog/go-sqllexer v0.0.14 // indirect
github.com/DataDog/go-tuf v1.1.0-0.5.2 // indirect
github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.20.0 // indirect
github.com/DataDog/sketches-go v1.4.5 // indirect
github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.1 // indirect
@@ -123,35 +136,32 @@ 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.72 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.63.100 // 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.32.7 // indirect
github.com/aws/aws-sdk-go-v2/config v1.28.7 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.48 // 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.26 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 // 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.12.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 // 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.8 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 // indirect
github.com/aws/smithy-go v1.22.1 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // 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.15 // indirect
github.com/aws/aws-sdk-go-v2/service/lightsail v1.43.1 // indirect
github.com/aws/aws-sdk-go-v2/service/route53 v1.50.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 // indirect
github.com/baidubce/bce-sdk-go v0.9.223 // 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/cihub/seelog v0.0.0-20170130134532-f561c5e57575 // indirect
github.com/civo/civogo v0.3.11 // indirect
github.com/cloudflare/cloudflare-go v0.112.0 // indirect
github.com/cloudflare/cloudflare-go v0.115.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
@@ -162,18 +172,19 @@ require (
github.com/docker/go-units v0.5.0 // indirect
github.com/donovanhide/eventsource v0.0.0-20170630084216-b8f31a59085e // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.5.0-alpha.1 // indirect
github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4 // indirect
github.com/ebitengine/purego v0.6.0-alpha.5 // indirect
github.com/elastic/go-sysinfo v1.7.1 // indirect
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.7 // indirect
github.com/exoscale/egoscale/v3 v3.1.13 // 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
@@ -184,24 +195,24 @@ 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.16.2 // indirect
github.com/go-resty/resty/v2 v2.16.5 // indirect
github.com/go-task/slim-sprig/v3 v3.0.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.4 // indirect
github.com/goccy/go-json v0.10.5 // 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.1 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // 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-cmp v0.7.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 // indirect
github.com/google/s2a-go v0.1.8 // indirect
github.com/google/s2a-go v0.1.9 // 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.14.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // 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
@@ -211,16 +222,19 @@ require (
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
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.128 // indirect
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.141 // 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
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
github.com/infobloxopen/infoblox-go-client/v2 v2.9.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect
github.com/jonboulle/clockwork v0.4.0 // indirect
@@ -232,11 +246,11 @@ 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.44.0 // indirect
github.com/linode/linodego v1.48.1 // 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
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailgun/minheap v0.0.0-20170619185613-3dbe6c6bf55f // indirect
github.com/mailgun/multibuf v0.1.2 // indirect
@@ -262,6 +276,7 @@ require (
github.com/nrdcg/desec v0.10.0 // indirect
github.com/nrdcg/dnspod-go v0.4.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
@@ -273,33 +288,34 @@ 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.81.1 // indirect
github.com/oracle/oci-go-sdk/v65 v65.87.0 // indirect
github.com/outcaste-io/ristretto v0.2.3 // indirect
github.com/ovh/go-ovh v1.6.0 // indirect
github.com/ovh/go-ovh v1.7.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/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect
github.com/pquerna/otp v1.4.0 // indirect
github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/common v0.54.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.6.1 // indirect
github.com/redis/go-redis/v9 v9.7.0 // indirect
github.com/regfish/regfish-dnsapi-go v0.1.1 // indirect
github.com/ryanuber/go-glob v1.0.0 // 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.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
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 // indirect
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.32 // indirect
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.2.1 // indirect
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
github.com/shirou/gopsutil/v3 v3.24.4 // 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
@@ -313,56 +329,58 @@ 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.1065 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065 // indirect
github.com/tinylib/msgp v1.1.8 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1128 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1128 // indirect
github.com/tinylib/msgp v1.2.1 // 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.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-20241220122821-aeb3b05efd1c // indirect
github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
github.com/volcengine/volc-sdk-golang v1.0.199 // indirect
github.com/vultr/govultr/v3 v3.17.0 // indirect
github.com/yandex-cloud/go-genproto v0.0.0-20250319153614-fb9d3e5eb01a // indirect
github.com/yandex-cloud/go-sdk v0.0.0-20250320143332-9cbcfc5de4ae // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.elastic.co/apm/module/apmhttp/v2 v2.4.8 // indirect
go.elastic.co/fastjson v1.1.0 // indirect
go.etcd.io/etcd/api/v3 v3.5.10 // indirect
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.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
go.opentelemetry.io/otel/trace v1.29.0 // indirect
go.mongodb.org/mongo-driver v1.13.1 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/collector/component v0.104.0 // indirect
go.opentelemetry.io/collector/config/configtelemetry v0.104.0 // indirect
go.opentelemetry.io/collector/pdata v1.11.0 // indirect
go.opentelemetry.io/collector/pdata/pprofile v0.104.0 // indirect
go.opentelemetry.io/collector/semconv v0.104.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
go.opentelemetry.io/otel v1.34.0 // indirect
go.opentelemetry.io/otel/metric v1.34.0 // indirect
go.opentelemetry.io/otel/trace v1.34.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/goleak v1.3.0 // indirect
go.uber.org/mock v0.4.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/ratelimit v0.3.0 // indirect
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.31.0 // indirect
go.uber.org/zap v1.27.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.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/term v0.27.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/api v0.214.0 // indirect
golang.org/x/oauth2 v0.28.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-20231012003039-104605ab7028 // indirect
google.golang.org/api v0.227.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
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.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
k8s.io/klog/v2 v2.90.1 // indirect
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect

522
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

@@ -0,0 +1,41 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[entryPoints]
[entryPoints.web]
address = ":8000"
[entryPoints.web2]
address = ":8001"
[entryPoints.web2.http]
sanitizePath = false
[log]
level = "DEBUG"
[api]
insecure = true
[providers.file]
filename = "{{ .SelfFilename }}"
# dynamic configuration
[http.routers]
[http.routers.without]
rule = "PathPrefix(`/without`)"
service = "whoami"
[http.routers.with]
rule = "PathPrefix(`/with`)"
middlewares = ["test-redirectscheme"]
service = "whoami"
[http.middlewares]
[http.middlewares.test-redirectscheme.redirectScheme]
scheme = "https"
permanent = false
[http.services]
[http.services.whoami.loadBalancer]
[[http.services.whoami.loadBalancer.servers]]
url = "{{ .Server1 }}"

View File

@@ -937,11 +937,6 @@ func (s *HTTPSSuite) TestEntryPointHttpsRedirectAndPathModification() {
hosts: []string{"example.com", "example2.com", "foo.com", "foo2.com", "bar.com", "bar2.com"},
path: "/api/",
},
{
desc: "Stripped URL with double trailing slash redirect",
hosts: []string{"example.com", "example2.com", "foo.com", "foo2.com", "bar.com", "bar2.com"},
path: "/api//",
},
{
desc: "Stripped URL with path redirect",
hosts: []string{"example.com", "example2.com", "foo.com", "foo2.com", "bar.com", "bar2.com"},
@@ -952,21 +947,11 @@ func (s *HTTPSSuite) TestEntryPointHttpsRedirectAndPathModification() {
hosts: []string{"example.com", "example2.com", "foo.com", "foo2.com", "bar.com", "bar2.com"},
path: "/api/bacon/",
},
{
desc: "Stripped URL with path and double trailing slash redirect",
hosts: []string{"example.com", "example2.com", "foo.com", "foo2.com", "bar.com", "bar2.com"},
path: "/api/bacon//",
},
{
desc: "Root Path with redirect",
hosts: []string{"test.com", "test2.com", "pow.com", "pow2.com"},
path: "/",
},
{
desc: "Root Path with double trailing slash redirect",
hosts: []string{"test.com", "test2.com", "pow.com", "pow2.com"},
path: "//",
},
{
desc: "Path modify with redirect",
hosts: []string{"test.com", "test2.com", "pow.com", "pow2.com"},

View File

@@ -85,7 +85,7 @@ func (s *BaseSuite) displayTraefikLogFile(path string) {
// fmt.Printf("%s: Traefik logs: \n", c.TestName())
fmt.Print("Traefik logs: \n")
if errRead == nil {
fmt.Println(content)
fmt.Println(string(content))
} else {
fmt.Println(errRead)
}

View File

@@ -1385,3 +1385,88 @@ func (s *SimpleSuite) TestDenyFragment() {
require.NoError(s.T(), err)
assert.Equal(s.T(), http.StatusBadRequest, resp.StatusCode)
}
func (s *SimpleSuite) TestSanitizePath() {
s.createComposeProject("base")
s.composeUp()
defer s.composeDown()
whoami1URL := "http://" + net.JoinHostPort(s.getComposeServiceIP("whoami1"), "80")
file := s.adaptFile("fixtures/simple_clean_path.toml", struct {
Server1 string
}{whoami1URL})
s.traefikCmd(withConfigFile(file))
err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix(`/with`)"))
require.NoError(s.T(), err)
testCases := []struct {
desc string
request string
target string
body string
expected int
}{
{
desc: "Explicit call to the route with a middleware",
request: "GET /with HTTP/1.1\r\nHost: other.localhost\r\n\r\n",
target: "127.0.0.1:8000",
expected: http.StatusFound,
},
{
desc: "Explicit call to the route without a middleware",
request: "GET /without HTTP/1.1\r\nHost: other.localhost\r\n\r\n",
target: "127.0.0.1:8000",
expected: http.StatusOK,
body: "GET /without HTTP/1.1",
},
{
desc: "Implicit call to the route with a middleware",
request: "GET /without/../with HTTP/1.1\r\nHost: other.localhost\r\n\r\n",
target: "127.0.0.1:8000",
expected: http.StatusFound,
},
{
desc: "Explicit call to the route with a middleware, and disable path sanitization",
request: "GET /with HTTP/1.1\r\nHost: other.localhost\r\n\r\n",
target: "127.0.0.1:8001",
expected: http.StatusFound,
},
{
desc: "Explicit call to the route without a middleware, and disable path sanitization",
request: "GET /without HTTP/1.1\r\nHost: other.localhost\r\n\r\n",
target: "127.0.0.1:8001",
expected: http.StatusOK,
body: "GET /without HTTP/1.1",
},
{
desc: "Implicit call to the route with a middleware, and disable path sanitization",
request: "GET /without/../with HTTP/1.1\r\nHost: other.localhost\r\n\r\n",
target: "127.0.0.1:8001",
// The whoami is redirecting to /with, but the path is not sanitized.
expected: http.StatusMovedPermanently,
},
}
for _, test := range testCases {
conn, err := net.Dial("tcp", test.target)
require.NoError(s.T(), err)
_, err = conn.Write([]byte(test.request))
require.NoError(s.T(), err)
resp, err := http.ReadResponse(bufio.NewReader(conn), nil)
require.NoError(s.T(), err)
assert.Equalf(s.T(), test.expected, resp.StatusCode, "%s failed with %d instead of %d", test.desc, resp.StatusCode, test.expected)
if test.body != "" {
body, err := io.ReadAll(resp.Body)
require.NoError(s.T(), err)
assert.Contains(s.T(), string(body), test.body)
}
}
}

View File

@@ -278,7 +278,7 @@ func (s *TCPSuite) TestWRR() {
time.Sleep(time.Second)
}
assert.EqualValues(s.T(), map[string]int{"whoami-b": 3, "whoami-ab": 1}, call)
assert.Equal(s.T(), map[string]int{"whoami-b": 3, "whoami-ab": 1}, call)
}
func welcome(addr string) (string, error) {

View File

@@ -96,7 +96,7 @@ func (s *UDPSuite) TestWRR() {
call["unknown"]++
}
}
assert.EqualValues(s.T(), map[string]int{"whoami-a": 3, "whoami-b": 2, "whoami-c": 3}, call)
assert.Equal(s.T(), map[string]int{"whoami-a": 3, "whoami-b": 2, "whoami-c": 3}, call)
close(stop)
}()

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

@@ -52,6 +52,7 @@ func (ep *EntryPoint) SetDefaults() {
ep.ForwardedHeaders = &ForwardedHeaders{}
ep.UDP = &UDPConfig{}
ep.UDP.SetDefaults()
ep.HTTP.SetDefaults()
ep.HTTP2 = &HTTP2Config{}
ep.HTTP2.SetDefaults()
}
@@ -61,7 +62,14 @@ type HTTPConfig struct {
Redirections *Redirections `description:"Set of redirection" json:"redirections,omitempty" toml:"redirections,omitempty" yaml:"redirections,omitempty" export:"true"`
Middlewares []string `description:"Default middlewares for the routers linked to the entry point." json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
TLS *TLSConfig `description:"Default TLS configuration for the routers linked to the entry point." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
EncodeQuerySemicolons bool `description:"Defines whether request query semicolons should be URLEncoded." json:"encodeQuerySemicolons,omitempty" toml:"encodeQuerySemicolons,omitempty" yaml:"encodeQuerySemicolons,omitempty"`
EncodeQuerySemicolons bool `description:"Defines whether request query semicolons should be URLEncoded." json:"encodeQuerySemicolons,omitempty" toml:"encodeQuerySemicolons,omitempty" yaml:"encodeQuerySemicolons,omitempty" export:"true"`
SanitizePath *bool `description:"Defines whether to enable request path sanitization (removal of /./, /../ and multiple slash sequences)." json:"sanitizePath,omitempty" toml:"sanitizePath,omitempty" yaml:"sanitizePath,omitempty" export:"true"`
}
// SetDefaults sets the default values.
func (h *HTTPConfig) SetDefaults() {
sanitizePath := true
h.SanitizePath = &sanitizePath
}
// HTTP2Config is the HTTP2 configuration of an entry point.

View File

@@ -364,9 +364,10 @@ func (h *Handler) logTheRoundTrip(logDataTable *LogData) {
func (h *Handler) redactHeaders(headers http.Header, fields logrus.Fields, prefix string) {
for k := range headers {
v := h.config.Fields.KeepHeader(k)
if v == types.AccessLogKeep {
switch v {
case types.AccessLogKeep:
fields[prefix+k] = strings.Join(headers.Values(k), ",")
} else if v == types.AccessLogRedact {
case types.AccessLogRedact:
fields[prefix+k] = "REDACTED"
}
}

View File

@@ -271,7 +271,7 @@ func assertNotEmpty() func(t *testing.T, actual interface{}) {
return func(t *testing.T, actual interface{}) {
t.Helper()
assert.NotEqual(t, "", actual)
assert.NotEmpty(t, actual)
}
}
@@ -510,7 +510,7 @@ func TestLoggerJSON(t *testing.T) {
err = json.Unmarshal(logData, &jsonData)
require.NoError(t, err)
assert.Equal(t, len(test.expected), len(jsonData))
assert.Len(t, jsonData, len(test.expected))
for field, assertion := range test.expected {
assertion(t, jsonData[field])
@@ -567,7 +567,7 @@ func TestLogger_AbortedRequest(t *testing.T) {
err = json.Unmarshal(logData, &jsonData)
require.NoError(t, err)
assert.Equal(t, len(expected), len(jsonData))
assert.Len(t, jsonData, len(expected))
for field, assertion := range expected {
assertion(t, jsonData[field])
@@ -798,7 +798,7 @@ func assertValidLogData(t *testing.T, expected string, logData []byte) {
formatErrMessage := fmt.Sprintf("Expected:\t%q\nActual:\t%q", expected, string(logData))
require.Equal(t, len(resultExpected), len(result), formatErrMessage)
require.Len(t, result, len(resultExpected), formatErrMessage)
assert.Equal(t, resultExpected[ClientHost], result[ClientHost], formatErrMessage)
assert.Equal(t, resultExpected[ClientUsername], result[ClientUsername], formatErrMessage)
assert.Equal(t, resultExpected[RequestMethod], result[RequestMethod], formatErrMessage)

View File

@@ -65,7 +65,7 @@ func TestParseAccessLog(t *testing.T) {
result, err := ParseAccessLog(test.value)
assert.NoError(t, err)
assert.Equal(t, len(test.expected), len(result))
assert.Len(t, result, len(test.expected))
for key, value := range test.expected {
assert.Equal(t, value, result[key])
}

View File

@@ -179,7 +179,7 @@ func TestForwardAuthRemoveHopByHopHeaders(t *testing.T) {
assert.Equal(t, http.StatusFound, res.StatusCode, "they should be equal")
for _, header := range forward.HopHeaders {
assert.Equal(t, "", res.Header.Get(header), "hop-by-hop header '%s' mustn't be set", header)
assert.Empty(t, res.Header.Get(header), "hop-by-hop header '%s' mustn't be set", header)
}
location, err := res.Location()

View File

@@ -70,7 +70,7 @@ func TestShouldNotCompressWhenContentEncodingHeader(t *testing.T) {
assert.Equal(t, gzipValue, rw.Header().Get(contentEncodingHeader))
assert.Equal(t, acceptEncodingHeader, rw.Header().Get(varyHeader))
assert.EqualValues(t, rw.Body.Bytes(), fakeCompressedBody)
assert.Equal(t, rw.Body.Bytes(), fakeCompressedBody)
}
func TestShouldNotCompressWhenNoAcceptEncodingHeader(t *testing.T) {
@@ -90,7 +90,7 @@ func TestShouldNotCompressWhenNoAcceptEncodingHeader(t *testing.T) {
handler.ServeHTTP(rw, req)
assert.Empty(t, rw.Header().Get(contentEncodingHeader))
assert.EqualValues(t, rw.Body.Bytes(), fakeBody)
assert.Equal(t, rw.Body.Bytes(), fakeBody)
}
func TestShouldNotCompressWhenSpecificContentType(t *testing.T) {
@@ -152,7 +152,7 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) {
assert.Empty(t, rw.Header().Get(acceptEncodingHeader))
assert.Empty(t, rw.Header().Get(contentEncodingHeader))
assert.EqualValues(t, rw.Body.Bytes(), baseBody)
assert.Equal(t, rw.Body.Bytes(), baseBody)
})
}
}
@@ -213,7 +213,7 @@ func TestIntegrationShouldNotCompress(t *testing.T) {
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
assert.EqualValues(t, fakeCompressedBody, body)
assert.Equal(t, fakeCompressedBody, body)
})
}
}
@@ -345,12 +345,12 @@ func TestMinResponseBodyBytes(t *testing.T) {
if test.expectedCompression {
assert.Equal(t, gzipValue, rw.Header().Get(contentEncodingHeader))
assert.NotEqualValues(t, rw.Body.Bytes(), fakeBody)
assert.NotEqual(t, rw.Body.Bytes(), fakeBody)
return
}
assert.Empty(t, rw.Header().Get(contentEncodingHeader))
assert.EqualValues(t, rw.Body.Bytes(), fakeBody)
assert.Equal(t, rw.Body.Bytes(), fakeBody)
})
}
}
@@ -432,7 +432,7 @@ func Test1xxResponses(t *testing.T) {
assert.Equal(t, gzipValue, res.Header.Get(contentEncodingHeader))
body, _ := io.ReadAll(res.Body)
assert.NotEqualValues(t, body, fakeBody)
assert.NotEqual(t, body, fakeBody)
}
func BenchmarkCompress(b *testing.B) {

View File

@@ -53,6 +53,7 @@ func NewHeader(next http.Handler, cfg dynamic.Headers) (*Header, error) {
func (s *Header) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// Handle Cors headers and preflight if configured.
if isPreflight := s.processCorsHeaders(rw, req); isPreflight {
rw.Header().Set("Content-Length", "0")
rw.WriteHeader(http.StatusOK)
return
}

View File

@@ -134,6 +134,7 @@ func TestNewHeader_CORSPreflights(t *testing.T) {
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Content-Length": {"0"},
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
"Access-Control-Max-Age": {"600"},
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
@@ -152,6 +153,7 @@ func TestNewHeader_CORSPreflights(t *testing.T) {
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Content-Length": {"0"},
"Access-Control-Allow-Origin": {"*"},
"Access-Control-Max-Age": {"600"},
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
@@ -171,6 +173,7 @@ func TestNewHeader_CORSPreflights(t *testing.T) {
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Content-Length": {"0"},
"Access-Control-Allow-Origin": {"*"},
"Access-Control-Max-Age": {"600"},
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
@@ -191,6 +194,7 @@ func TestNewHeader_CORSPreflights(t *testing.T) {
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Content-Length": {"0"},
"Access-Control-Allow-Origin": {"*"},
"Access-Control-Max-Age": {"600"},
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
@@ -210,6 +214,7 @@ func TestNewHeader_CORSPreflights(t *testing.T) {
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Content-Length": {"0"},
"Access-Control-Allow-Origin": {"*"},
"Access-Control-Max-Age": {"600"},
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},

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")
}
}
@@ -227,11 +227,11 @@ func getIssuerDNInfo(ctx context.Context, options *IssuerDistinguishedNameOption
content := &strings.Builder{}
// Manage non standard attributes
// Manage non-standard attributes
for _, name := range cs.Names {
// Domain Component - RFC 2247
if options.DomainComponent && attributeTypeNames[name.Type.String()] == "DC" {
content.WriteString(fmt.Sprintf("DC=%s%s", name.Value, subFieldSeparator))
_, _ = fmt.Fprintf(content, "DC=%s%s", name.Value, subFieldSeparator)
}
}
@@ -273,7 +273,7 @@ func getSubjectDNInfo(ctx context.Context, options *SubjectDistinguishedNameOpti
for _, name := range cs.Names {
// Domain Component - RFC 2247
if options.DomainComponent && attributeTypeNames[name.Type.String()] == "DC" {
content.WriteString(fmt.Sprintf("DC=%s%s", name.Value, subFieldSeparator))
_, _ = fmt.Fprintf(content, "DC=%s%s", name.Value, subFieldSeparator)
}
}

View File

@@ -266,11 +266,7 @@ func TestRateLimit(t *testing.T) {
end := start.Add(test.loadDuration)
ticker := time.NewTicker(loadPeriod)
defer ticker.Stop()
for {
if time.Now().After(end) {
break
}
for !time.Now().After(end) {
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
req.RemoteAddr = "127.0.0.1:1234"
w := httptest.NewRecorder()

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

@@ -970,7 +970,7 @@ func TestParseDomains(t *testing.T) {
require.NoError(t, err, "%s: Error while parsing domain.", test.expression)
}
assert.EqualValues(t, test.domain, domains, "%s: Error parsing domains from expression.", test.expression)
assert.Equal(t, test.domain, domains, "%s: Error parsing domains from expression.", test.expression)
})
}
}

View File

@@ -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

@@ -623,7 +623,7 @@ func TestParseHostSNI(t *testing.T) {
require.NoError(t, err, "%s: Error while parsing domain.", test.expression)
}
assert.EqualValues(t, test.domain, domains, "%s: Error parsing domains from expression.", test.expression)
assert.Equal(t, test.domain, domains, "%s: Error parsing domains from expression.", test.expression)
})
}
}
@@ -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

@@ -182,7 +182,7 @@ func TestGetUncheckedCertificates(t *testing.T) {
}
domains := acmeProvider.getUncheckedDomains(context.Background(), test.domains, "default")
assert.Equal(t, len(test.expectedDomains), len(domains), "Unexpected domains.")
assert.Len(t, domains, len(test.expectedDomains), "Unexpected domains.")
})
}
}
@@ -250,7 +250,7 @@ func TestProvider_sanitizeDomains(t *testing.T) {
if len(test.expectedErr) > 0 {
assert.EqualError(t, err, test.expectedErr, "Unexpected error.")
} else {
assert.Equal(t, len(test.expectedDomains), len(domains), "Unexpected domains.")
assert.Len(t, domains, len(test.expectedDomains), "Unexpected domains.")
}
})
}

View File

@@ -505,11 +505,12 @@ func (p *Provider) parseService(ctx context.Context, service swarmtypes.Service,
dData.ExtraConf = extraConf
if service.Spec.EndpointSpec != nil {
if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeDNSRR {
switch service.Spec.EndpointSpec.Mode {
case swarmtypes.ResolutionModeDNSRR:
if dData.ExtraConf.Docker.LBSwarm {
logger.Warnf("Ignored %s endpoint-mode not supported, service name: %s. Fallback to Traefik load balancing", swarmtypes.ResolutionModeDNSRR, service.Spec.Annotations.Name)
}
} else if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeVIP {
case swarmtypes.ResolutionModeVIP:
dData.NetworkSettings.Networks = make(map[string]*networkData)
for _, virtualIP := range service.Endpoint.VirtualIPs {
networkService := networkMap[virtualIP.NetworkID]

View File

@@ -280,7 +280,7 @@ func TestListServices(t *testing.T) {
serviceDockerData, err := p.listServices(context.Background(), dockerClient)
assert.NoError(t, err)
assert.Equal(t, len(test.expectedServices), len(serviceDockerData))
assert.Len(t, serviceDockerData, len(test.expectedServices))
for i, serviceName := range test.expectedServices {
if len(serviceDockerData) <= i {
require.Fail(t, "index", "invalid index %d", i)

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
}

View File

@@ -4,7 +4,8 @@ import (
"context"
"testing"
"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/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
@@ -29,10 +30,10 @@ func TestDefaultRule(t *testing.T) {
id("1"),
labels(map[string]string{}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("10.0.0.1"),
mPorts(
mPort(0, 1337, "TCP"),
mPort(0, 1337, ecstypes.TransportProtocolTcp),
),
),
),
@@ -83,10 +84,10 @@ func TestDefaultRule(t *testing.T) {
name("Test"),
labels(map[string]string{}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -139,10 +140,10 @@ func TestDefaultRule(t *testing.T) {
"traefik.domain": "foo.bar",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -193,10 +194,10 @@ func TestDefaultRule(t *testing.T) {
name("Test"),
labels(map[string]string{}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -241,10 +242,10 @@ func TestDefaultRule(t *testing.T) {
name("Test"),
labels(map[string]string{}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -289,10 +290,10 @@ func TestDefaultRule(t *testing.T) {
name("Test"),
labels(map[string]string{}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -380,10 +381,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.services.test": "",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -418,10 +419,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.tcp.services.test": "",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -456,10 +457,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.udp.services.test": "",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -492,10 +493,10 @@ func Test_buildConfiguration(t *testing.T) {
name("Test"),
labels(map[string]string{}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -545,10 +546,10 @@ func Test_buildConfiguration(t *testing.T) {
name("Test"),
labels(map[string]string{}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -556,10 +557,10 @@ func Test_buildConfiguration(t *testing.T) {
name("Test2"),
labels(map[string]string{}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.2"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -625,10 +626,10 @@ func Test_buildConfiguration(t *testing.T) {
name("Test"),
labels(map[string]string{}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -637,10 +638,10 @@ func Test_buildConfiguration(t *testing.T) {
name("Test"),
labels(map[string]string{}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.2"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -695,10 +696,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.services.Service1.loadbalancer.passhostheader": "true",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -752,10 +753,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.routers.Router1.service": "Service1",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -806,10 +807,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.routers.Router1.rule": "Host(`foo.com`)",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -861,10 +862,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.services.Service1.loadbalancer.passhostheader": "true",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -917,10 +918,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.services.Service2.loadbalancer.passhostheader": "true",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -977,10 +978,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.routers.Router1.service": "Service1",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1032,10 +1033,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.services.Service1.loadbalancer.passhostheader": "true",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1046,10 +1047,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.services.Service1.loadbalancer.passhostheader": "false",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1091,10 +1092,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.services.Service1.loadbalancer.passhostheader": "false",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1105,10 +1106,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.services.Service1.loadbalancer.passhostheader": "true",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1119,10 +1120,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.services.Service1.loadbalancer.passhostheader": "true",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1164,10 +1165,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.services.Service1.loadbalancer.passhostheader": "true",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1178,10 +1179,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.services.Service1.loadbalancer.passhostheader": "true",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.2"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1236,10 +1237,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.middlewares.Middleware1.inflightreq.amount": "42",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1298,10 +1299,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.middlewares.Middleware1.inflightreq.amount": "42",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1312,10 +1313,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.middlewares.Middleware1.inflightreq.amount": "42",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.2"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1377,10 +1378,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.middlewares.Middleware1.inflightreq.amount": "42",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1391,10 +1392,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.middlewares.Middleware1.inflightreq.amount": "41",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.2"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1450,10 +1451,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.middlewares.Middleware1.inflightreq.amount": "42",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1464,10 +1465,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.middlewares.Middleware1.inflightreq.amount": "41",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.2"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1478,10 +1479,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.middlewares.Middleware1.inflightreq.amount": "40",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.3"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1540,10 +1541,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.routers.Router1.rule": "Host(`foo.com`)",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1554,10 +1555,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.routers.Router1.rule": "Host(`bar.com`)",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.2"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1607,10 +1608,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.routers.Router1.rule": "Host(`foo.com`)",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1621,10 +1622,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.routers.Router1.rule": "Host(`bar.com`)",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.2"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1635,10 +1636,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.routers.Router1.rule": "Host(`foobar.com`)",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.3"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1691,10 +1692,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.routers.Router1.rule": "Host(`foo.com`)",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1706,10 +1707,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.routers.Router1.rule": "Host(`foo.com`)",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.2"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1763,10 +1764,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.routers.Router1.rule": "Host(`foo.com`)",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1776,10 +1777,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.routers.Router1.rule": "Host(`foo.com`)",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.2"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1835,10 +1836,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.wrong.label": "42",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1891,10 +1892,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.services.Service1.LoadBalancer.server.port": "80",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(80, 8080, "tcp"),
mPort(80, 8080, ecstypes.TransportProtocolTcp),
),
),
),
@@ -1947,10 +1948,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.services.Service1.LoadBalancer.server.port": "8040",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(80, 8080, "tcp"),
mPort(80, 8080, ecstypes.TransportProtocolTcp),
),
),
),
@@ -2007,11 +2008,11 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.services.Service2.LoadBalancer.server.port": "4444",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(4444, 32123, "tcp"),
mPort(4445, 32124, "tcp"),
mPort(4444, 32123, ecstypes.TransportProtocolTcp),
mPort(4445, 32124, ecstypes.TransportProtocolTcp),
),
),
),
@@ -2077,10 +2078,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.services.Service2.LoadBalancer.server.port": "8080",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -2134,7 +2135,7 @@ func Test_buildConfiguration(t *testing.T) {
name("Test"),
labels(map[string]string{}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(),
),
@@ -2170,7 +2171,7 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.middlewares.Middleware1.inflightreq.amount": "42",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(),
),
@@ -2206,10 +2207,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.enable": "false",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -2244,11 +2245,11 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.enable": "false",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mHealthStatus("UNHEALTHY"),
mHealthStatus(ecstypes.HealthStatusUnhealthy),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -2283,10 +2284,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.enable": "false",
}),
iMachine(
mState(ec2.InstanceStateNamePending),
mState(ec2types.InstanceStateNamePending),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -2321,10 +2322,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.tags": "foo",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -2360,10 +2361,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.tags": "foo",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -2417,10 +2418,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.routers.Test.middlewares": "Middleware1",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -2484,10 +2485,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.tcp.routers.Test.middlewares": "Middleware1",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -2546,10 +2547,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.tcp.routers.foo.tls": "true",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -2601,10 +2602,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.udp.routers.foo.entrypoints": "mydns",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "udp"),
mPort(0, 80, ecstypes.TransportProtocolUdp),
),
),
),
@@ -2654,10 +2655,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.tcp.routers.foo.tls": "true",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -2705,10 +2706,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.tcp.services.foo.loadbalancer.server.port": "80",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(80, 8080, "tcp"),
mPort(80, 8080, ecstypes.TransportProtocolTcp),
),
),
),
@@ -2763,10 +2764,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.udp.services.foo.loadbalancer.server.port": "80",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(80, 8080, "udp"),
mPort(80, 8080, ecstypes.TransportProtocolUdp),
),
),
),
@@ -2818,10 +2819,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.services.Service1.loadbalancer.passhostheader": "true",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -2834,10 +2835,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.http.services.Service1.loadbalancer.passhostheader": "true",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.2"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -2910,10 +2911,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.udp.services.foo.loadbalancer.server.port": "8080",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),
@@ -2959,10 +2960,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.tcp.services.foo.loadbalancer.terminationdelay": "200",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(80, 8080, "tcp"),
mPort(80, 8080, ecstypes.TransportProtocolTcp),
),
),
),
@@ -3010,10 +3011,10 @@ func Test_buildConfiguration(t *testing.T) {
"traefik.tls.stores.default.defaultgeneratedcert.domain.sans": "foobar, fiibar",
}),
iMachine(
mState(ec2.InstanceStateNameRunning),
mState(ec2types.InstanceStateNameRunning),
mPrivateIP("127.0.0.1"),
mPorts(
mPort(0, 80, "tcp"),
mPort(0, 80, ecstypes.TransportProtocolTcp),
),
),
),

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

@@ -457,8 +457,8 @@ func (c configBuilder) nameAndService(ctx context.Context, parentNamespace strin
return "", nil, fmt.Errorf("service %s/%s not in the parent resource namespace %s", service.Namespace, service.Name, parentNamespace)
}
switch {
case service.Kind == "" || service.Kind == "Service":
switch service.Kind {
case "", "Service":
serversLB, err := c.buildServersLB(namespace, service)
if err != nil {
return "", nil, err
@@ -467,8 +467,10 @@ func (c configBuilder) nameAndService(ctx context.Context, parentNamespace strin
fullName := fullServiceName(svcCtx, namespace, service, service.Port)
return fullName, serversLB, nil
case service.Kind == "TraefikService":
case "TraefikService":
return fullServiceName(svcCtx, namespace, service, intstr.FromInt(0)), nil, nil
default:
return "", nil, fmt.Errorf("unsupported service kind %s", service.Kind)
}

View File

@@ -375,7 +375,7 @@ func Test_doOnStruct(t *testing.T) {
err := doOnStruct(val, tagExport, test.redactByDefault)
require.NoError(t, err)
assert.EqualValues(t, test.expected, test.base)
assert.Equal(t, test.expected, test.base)
})
}
}

View File

@@ -571,7 +571,12 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
return nil, err
}
handler = denyFragment(handler)
if configuration.HTTP.SanitizePath != nil && *configuration.HTTP.SanitizePath {
// sanitizePath is used to clean the URL path by removing /../, /./ and duplicate slash sequences,
// to make sure the path is interpreted by the backends as it is evaluated inside rule matchers.
handler = sanitizePath(handler)
}
if configuration.HTTP.EncodeQuerySemicolons {
handler = encodeQuerySemicolons(handler)
} else {
@@ -589,6 +594,8 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
})
}
handler = denyFragment(handler)
serverHTTP := &http.Server{
Handler: handler,
ErrorLog: httpServerLogger,
@@ -713,3 +720,20 @@ func denyFragment(h http.Handler) http.Handler {
h.ServeHTTP(rw, req)
})
}
// sanitizePath removes the "..", "." and duplicate slash segments from the URL.
// It cleans the request URL Path and RawPath, and updates the request URI.
func sanitizePath(h http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
r2 := new(http.Request)
*r2 = *req
// Cleans the URL raw path and path.
r2.URL = r2.URL.JoinPath()
// Because the reverse proxy director is building query params from requestURI it needs to be updated as well.
r2.RequestURI = r2.URL.RequestURI()
h.ServeHTTP(rw, r2)
})
}

View File

@@ -8,6 +8,7 @@ import (
"io"
"net"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
@@ -382,3 +383,44 @@ func TestKeepAliveH2c(t *testing.T) {
// to change.
require.Contains(t, err.Error(), "use of closed network connection")
}
func TestSanitizePath(t *testing.T) {
tests := []struct {
path string
expected string
}{
{path: "/b", expected: "/b"},
{path: "/b/", expected: "/b/"},
{path: "/../../b/", expected: "/b/"},
{path: "/../../b", expected: "/b"},
{path: "/a/b/..", expected: "/a"},
{path: "/a/b/../", expected: "/a/"},
{path: "/a/../../b", expected: "/b"},
{path: "/..///b///", expected: "/b/"},
{path: "/a/../b", expected: "/b"},
{path: "/a/./b", expected: "/a/b"},
{path: "/a//b", expected: "/a/b"},
{path: "/a/../../b", expected: "/b"},
{path: "/a/../c/../b", expected: "/b"},
{path: "/a/../../../c/../b", expected: "/b"},
{path: "/a/../c/../../b", expected: "/b"},
{path: "/a/..//c/.././b", expected: "/b"},
}
for _, test := range tests {
t.Run("Testing case: "+test.path, func(t *testing.T) {
t.Parallel()
var callCount int
clean := sanitizePath(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
callCount++
assert.Equal(t, test.expected, r.URL.Path)
}))
request := httptest.NewRequest(http.MethodGet, "http://foo"+test.path, http.NoBody)
clean.ServeHTTP(httptest.NewRecorder(), request)
assert.Equal(t, 1, callCount)
})
}
}

View File

@@ -2,6 +2,7 @@ package service
import (
"context"
"crypto/tls"
"errors"
"fmt"
"io"
@@ -26,6 +27,12 @@ const StatusClientClosedRequest = 499
// StatusClientClosedRequestText non-standard HTTP status for client disconnection.
const StatusClientClosedRequestText = "Client Closed Request"
// errorLogger is a logger instance used to log proxy errors.
// This logger is a shared instance as having one instance by proxy introduces a memory and go routine leak.
// The writer go routine is never stopped as the finalizer is never called.
// See https://github.com/sirupsen/logrus/blob/d1e6332644483cfee14de11099f03645561d55f8/writer.go#L57).
var errorLogger = stdlog.New(log.WithoutContext().WriterLevel(logrus.DebugLevel), "", 0)
func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwarding, roundTripper http.RoundTripper, bufferPool httputil.BufferPool) (http.Handler, error) {
var flushInterval ptypes.Duration
if responseForwarding != nil {
@@ -83,10 +90,11 @@ func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwar
Transport: roundTripper,
FlushInterval: time.Duration(flushInterval),
BufferPool: bufferPool,
ErrorLog: stdlog.New(log.WithoutContext().WriterLevel(logrus.DebugLevel), "", 0),
ErrorLog: errorLogger,
ErrorHandler: func(w http.ResponseWriter, request *http.Request, err error) {
statusCode := http.StatusInternalServerError
logger := log.FromContext(request.Context())
statusCode := http.StatusInternalServerError
switch {
case errors.Is(err, io.EOF):
statusCode = http.StatusBadGateway
@@ -103,7 +111,13 @@ func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwar
}
}
log.Debugf("'%d %s' caused by: %v", statusCode, statusText(statusCode), err)
// Log the error with error level if it is a TLS error related to configuration.
if isTLSConfigError(err) {
logger.Errorf("'%d %s' caused by: %v", statusCode, statusText(statusCode), err)
} else {
logger.Debugf("'%d %s' caused by: %v", statusCode, statusText(statusCode), err)
}
w.WriteHeader(statusCode)
_, werr := w.Write([]byte(statusText(statusCode)))
if werr != nil {
@@ -115,6 +129,22 @@ func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwar
return proxy, nil
}
// isTLSError returns true if the error is a TLS error which is related to configuration.
// We assume that if the error is a tls.RecordHeaderError or a tls.CertificateVerificationError,
// it is related to configuration, because the client should not send a TLS request to a non-TLS server,
// and the client configuration should allow to verify the server certificate.
func isTLSConfigError(err error) bool {
// tls.RecordHeaderError is returned when the client sends a TLS request to a non-TLS server.
var recordHeaderErr tls.RecordHeaderError
if errors.As(err, &recordHeaderErr) {
return true
}
// tls.CertificateVerificationError is returned when the server certificate cannot be verified.
var certVerificationErr *tls.CertificateVerificationError
return errors.As(err, &certVerificationErr)
}
func isWebSocketUpgrade(req *http.Request) bool {
if !httpguts.HeaderValuesContainsToken(req.Header["Connection"], "Upgrade") {
return false

View File

@@ -1,12 +1,15 @@
package service
import (
"crypto/tls"
"errors"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/traefik/traefik/v2/pkg/testhelpers"
)
@@ -35,3 +38,46 @@ func BenchmarkProxy(b *testing.B) {
handler.ServeHTTP(w, req)
}
}
func TestIsTLSConfigError(t *testing.T) {
testCases := []struct {
desc string
err error
expected bool
}{
{
desc: "nil",
},
{
desc: "TLS ECHRejectionError",
err: &tls.ECHRejectionError{},
},
{
desc: "TLS AlertError",
err: tls.AlertError(0),
},
{
desc: "Random error",
err: errors.New("random error"),
},
{
desc: "TLS RecordHeaderError",
err: tls.RecordHeaderError{},
expected: true,
},
{
desc: "TLS CertificateVerificationError",
err: &tls.CertificateVerificationError{},
expected: true,
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := isTLSConfigError(test.err)
require.Equal(t, test.expected, actual)
})
}
}

View File

@@ -21,6 +21,7 @@ import (
"github.com/traefik/traefik/v2/pkg/middlewares/emptybackendhandler"
metricsMiddle "github.com/traefik/traefik/v2/pkg/middlewares/metrics"
"github.com/traefik/traefik/v2/pkg/middlewares/pipelining"
"github.com/traefik/traefik/v2/pkg/middlewares/retry"
"github.com/traefik/traefik/v2/pkg/safe"
"github.com/traefik/traefik/v2/pkg/server/cookie"
"github.com/traefik/traefik/v2/pkg/server/provider"
@@ -283,16 +284,20 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
if err != nil {
return nil, err
}
// The retry wrapping must be done just before the proxy handler,
// to make sure that the retry will not be triggered/disabled by
// middlewares in the chain.
fwd = retry.WrapHandler(fwd)
alHandler := func(next http.Handler) (http.Handler, error) {
return accesslog.NewFieldHandler(next, accesslog.ServiceName, serviceName, accesslog.AddServiceFields), nil
}
chain := alice.New()
if m.metricsRegistry != nil && m.metricsRegistry.IsSvcEnabled() {
chain = chain.Append(metricsMiddle.WrapServiceHandler(ctx, m.metricsRegistry, serviceName))
}
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
return accesslog.NewFieldHandler(next, accesslog.ServiceName, serviceName, accesslog.AddServiceFields), nil
})
handler, err := chain.Append(alHandler).Then(pipelining.New(ctx, fwd, "pipelining"))
handler, err := chain.Then(pipelining.New(ctx, fwd, "pipelining"))
if err != nil {
return nil, err
}

View File

@@ -342,7 +342,7 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
assert.NotNil(t, handler)
req := testhelpers.MustNewRequest(http.MethodGet, "http://callme", nil)
assert.Equal(t, "", req.Header.Get("User-Agent"))
assert.Empty(t, req.Header.Get("User-Agent"))
if test.userAgent != "" {
req.Header.Set("User-Agent", test.userAgent)

View File

@@ -41,7 +41,7 @@ func TestDomain_ToStrArray(t *testing.T) {
t.Parallel()
domains := test.domain.ToStrArray()
assert.EqualValues(t, test.expected, domains)
assert.Equal(t, test.expected, domains)
})
}
}

View File

@@ -110,11 +110,7 @@ func (l *Listener) Shutdown(graceTimeout time.Duration) error {
}
start := time.Now()
end := start.Add(graceTimeout)
for {
if time.Now().After(end) {
break
}
for !time.Now().After(end) {
l.mu.RLock()
if len(l.conns) == 0 {
l.mu.RUnlock()

View File

@@ -1,41 +0,0 @@
#!/usr/bin/env bash
# shellcheck disable=SC2046
set -e -o pipefail
export PROJECT_MODULE="github.com/traefik/traefik"
export MODULE_VERSION="v2"
IMAGE_NAME="kubernetes-codegen:latest"
CURRENT_DIR="$(pwd)"
echo "Building codegen Docker image..."
docker build --build-arg KUBE_VERSION=v0.29.8 \
--build-arg USER="${USER}" \
--build-arg UID="$(id -u)" \
--build-arg GID="$(id -g)" \
-f "./script/codegen.Dockerfile" \
-t "${IMAGE_NAME}" \
"."
echo "Generating Traefik clientSet code and DeepCopy code ..."
docker run --rm \
-v "${CURRENT_DIR}:/go/src/${PROJECT_MODULE}" \
-w "/go/src/${PROJECT_MODULE}" \
-e "PROJECT_MODULE=${PROJECT_MODULE}" \
-e "MODULE_VERSION=${MODULE_VERSION}" \
"${IMAGE_NAME}" \
bash ./script/code-gen.sh
echo "Generating the CRD definitions for the documentation ..."
docker run --rm \
-v "${CURRENT_DIR}:/go/src/${PROJECT_MODULE}" \
-w "/go/src/${PROJECT_MODULE}" \
"${IMAGE_NAME}" \
controller-gen crd:crdVersions=v1 \
paths={./pkg/provider/kubernetes/crd/traefikio/v1alpha1/...,./pkg/provider/kubernetes/crd/traefikcontainous/v1alpha1/...} \
output:dir=./docs/content/reference/dynamic-configuration/
echo "Concatenate the CRD definitions for publication and integration tests ..."
cat "${CURRENT_DIR}"/docs/content/reference/dynamic-configuration/traefik.io_*.yaml > "${CURRENT_DIR}"/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
cat "${CURRENT_DIR}"/docs/content/reference/dynamic-configuration/traefik.containo.us_*.yaml >> "${CURRENT_DIR}"/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
cp -f "${CURRENT_DIR}"/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml "${CURRENT_DIR}"/integration/fixtures/k8s/01-traefik-crd.yml

View File

@@ -2,30 +2,36 @@
set -e -o pipefail
PROJECT_MODULE="github.com/traefik/traefik"
MODULE_VERSION="v2"
KUBE_VERSION=v0.30.10
CURRENT_DIR="$(pwd)"
go install "k8s.io/code-generator/cmd/deepcopy-gen@${KUBE_VERSION}"
go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.16.1
CODEGEN_PKG="$(go env GOPATH)/pkg/mod/k8s.io/code-generator@${KUBE_VERSION}"
# shellcheck disable=SC1091 # Cannot check source of this file
source /go/src/k8s.io/code-generator/kube_codegen.sh
git config --global --add safe.directory "/go/src/${PROJECT_MODULE}"
rm -rf "/go/src/${PROJECT_MODULE}/${MODULE_VERSION}"
mkdir -p "/go/src/${PROJECT_MODULE}/${MODULE_VERSION}/"
# TODO: remove the workaround when the issue is solved in the code-generator
# (https://github.com/kubernetes/code-generator/issues/165).
# Here, we create the soft link named "${PROJECT_MODULE}" to the parent directory of
# Traefik to ensure the layout required by the kube_codegen.sh script.
ln -s "/go/src/${PROJECT_MODULE}/pkg" "/go/src/${PROJECT_MODULE}/${MODULE_VERSION}/"
source "${CODEGEN_PKG}/kube_codegen.sh"
echo "# Generating Traefik clientset and deepcopy code ..."
kube::codegen::gen_helpers \
--input-pkg-root "${PROJECT_MODULE}/pkg" \
--output-base "$(dirname "${BASH_SOURCE[0]}")/../../../.." \
--boilerplate "/go/src/${PROJECT_MODULE}/script/boilerplate.go.tmpl"
--boilerplate "$(dirname "${BASH_SOURCE[0]}")/boilerplate.go.tmpl" \
"${CURRENT_DIR}"
kube::codegen::gen_client \
--with-watch \
--input-pkg-root "${PROJECT_MODULE}/${MODULE_VERSION}/pkg/provider/kubernetes/crd" \
--output-pkg-root "${PROJECT_MODULE}/${MODULE_VERSION}/pkg/provider/kubernetes/crd/generated" \
--output-base "$(dirname "${BASH_SOURCE[0]}")/../../../.." \
--boilerplate "/go/src/${PROJECT_MODULE}/script/boilerplate.go.tmpl"
--output-dir "${CURRENT_DIR}/pkg/provider/kubernetes/crd/generated" \
--output-pkg "${PROJECT_MODULE}/${MODULE_VERSION}/pkg/provider/kubernetes/crd/generated" \
--boilerplate "$(dirname "${BASH_SOURCE[0]}")/boilerplate.go.tmpl" \
"${CURRENT_DIR}/pkg/provider/kubernetes/crd"
rm -rf "/go/src/${PROJECT_MODULE}/${MODULE_VERSION}"
echo "# Generating the CRD definitions for the documentation ..."
controller-gen crd:crdVersions=v1 \
paths={./pkg/provider/kubernetes/crd/traefikio/v1alpha1/...,./pkg/provider/kubernetes/crd/traefikcontainous/v1alpha1/...} \
output:dir=./docs/content/reference/dynamic-configuration/
echo "# Concatenate the CRD definitions for publication and integration tests ..."
cat "${CURRENT_DIR}"/docs/content/reference/dynamic-configuration/traefik.io_*.yaml > "${CURRENT_DIR}"/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
cat "${CURRENT_DIR}"/docs/content/reference/dynamic-configuration/traefik.containo.us_*.yaml >> "${CURRENT_DIR}"/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
cp -f "${CURRENT_DIR}"/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml "${CURRENT_DIR}"/integration/fixtures/k8s/01-traefik-crd.yml

View File

@@ -1,22 +0,0 @@
FROM golang:1.23
ARG USER=$USER
ARG UID=$UID
ARG GID=$GID
RUN useradd -l -m ${USER} --uid=${UID} && echo "${USER}:" chpasswd
USER ${UID}:${GID}
ARG KUBE_VERSION
RUN go install k8s.io/code-generator/cmd/defaulter-gen@$KUBE_VERSION
RUN go install k8s.io/code-generator/cmd/client-gen@$KUBE_VERSION
RUN go install k8s.io/code-generator/cmd/lister-gen@$KUBE_VERSION
RUN go install k8s.io/code-generator/cmd/informer-gen@$KUBE_VERSION
RUN go install k8s.io/code-generator/cmd/deepcopy-gen@$KUBE_VERSION
RUN go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.16.1
RUN mkdir -p $GOPATH/src/k8s.io/code-generator
RUN cp -R $GOPATH/pkg/mod/k8s.io/code-generator@$KUBE_VERSION/* $GOPATH/src/k8s.io/code-generator/
RUN chmod +x $GOPATH/src/k8s.io/code-generator/kube_codegen.sh
WORKDIR $GOPATH/src/k8s.io/code-generator

View File

@@ -4,11 +4,11 @@ RepositoryName = "traefik"
OutputType = "file"
FileName = "traefik_changelog.md"
# example new bugfix v2.11.17
# example new bugfix v2.11.23
CurrentRef = "v2.11"
PreviousRef = "v2.11.16"
PreviousRef = "v2.11.22"
BaseBranch = "v2.11"
FutureCurrentRefName = "v2.11.17"
FutureCurrentRefName = "v2.11.23"
ThresholdPreviousRef = 10
ThresholdCurrentRef = 10

View File

@@ -67,7 +67,7 @@ export default function PaginationMixin (opts = {}) {
this.fetchMore()
}
},
beforeDestroy () {
beforeUnmount () {
clearInterval(this.pollingInterval)
}
}

View File

@@ -14,7 +14,7 @@
<script>
export default {
props: {
dense: Boolean,
dense: { type: Boolean, default: undefined },
classNames: Array[String],
list: Array[Object]
}

View File

@@ -138,7 +138,7 @@ export default {
},
props: {
data: { type: Object, default: undefined, required: false },
dense: Boolean
dense: { type: Boolean, default: undefined }
},
computed: {
isDense () {

View File

@@ -1563,7 +1563,7 @@ export default defineComponent({
},
props: {
data: Array[Object],
dense: Boolean
dense: { type: Boolean, default: undefined }
},
computed: {
protocol () {

View File

@@ -71,7 +71,7 @@ export default {
name: 'PanelMirroringServices',
props: {
data: { type: Object, default: undefined, required: false },
dense: Boolean
dense: { type: Boolean, default: undefined }
},
computed: {
isDense () {

View File

@@ -103,8 +103,8 @@ export default defineComponent({
},
props: {
data: { type: Object, default: undefined, required: false },
dense: Boolean,
hasStatus: Boolean
dense: { type: Boolean, default: undefined },
hasStatus: { type: Boolean, default: undefined }
},
computed: {
isDense () {

View File

@@ -172,7 +172,7 @@ export default defineComponent({
},
props: {
data: { type: Object, default: undefined, required: false },
dense: Boolean
dense: { type: Boolean, default: undefined }
},
computed: {
isDense () {

View File

@@ -67,7 +67,7 @@ export default defineComponent({
components: {},
props: {
data: { type: Object, default: undefined, required: false },
dense: Boolean
dense: { type: Boolean, default: undefined }
},
computed: {
isDense () {

View File

@@ -56,7 +56,7 @@ export default defineComponent({
},
props: {
sticky: { type: Object, default: undefined, required: false },
dense: Boolean
dense: { type: Boolean, default: undefined }
}
})
</script>