diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d7cf99bcb..6f73e85ec 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -6,13 +6,13 @@ on: - '*' env: - GO_VERSION: '1.21' + GO_VERSION: '1.22' CGO_ENABLED: 0 jobs: build-webui: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Check out code @@ -26,7 +26,7 @@ jobs: tar czvf webui.tar.gz ./webui/static/ - name: Artifact webui - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: webui.tar.gz path: webui.tar.gz @@ -35,7 +35,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ ubuntu-20.04, macos-latest, windows-latest ] + os: [ ubuntu-22.04, macos-latest, windows-latest ] needs: - build-webui @@ -51,7 +51,7 @@ jobs: go-version: ${{ env.GO_VERSION }} - name: Artifact webui - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 with: name: webui.tar.gz diff --git a/.github/workflows/check_doc.yml b/.github/workflows/check_doc.yml index 9f08efbb1..9851e1a54 100644 --- a/.github/workflows/check_doc.yml +++ b/.github/workflows/check_doc.yml @@ -9,7 +9,7 @@ jobs: docs: name: Check, verify and build documentation - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Check out code diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index b52635030..a11b57e50 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -14,7 +14,7 @@ jobs: docs: name: Doc Process - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 if: github.repository == 'traefik/traefik' steps: diff --git a/.github/workflows/experimental.yaml b/.github/workflows/experimental.yaml index a26178315..bced49ca8 100644 --- a/.github/workflows/experimental.yaml +++ b/.github/workflows/experimental.yaml @@ -7,7 +7,7 @@ on: - v* env: - GO_VERSION: '1.21' + GO_VERSION: '1.22' CGO_ENABLED: 0 jobs: @@ -15,7 +15,7 @@ jobs: experimental: if: github.repository == 'traefik/traefik' name: Build experimental image on branch - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: diff --git a/.github/workflows/test-conformance.yaml b/.github/workflows/test-conformance.yaml new file mode 100644 index 000000000..707d9d5c0 --- /dev/null +++ b/.github/workflows/test-conformance.yaml @@ -0,0 +1,42 @@ +name: Test K8s Gateway API conformance + +on: + pull_request: + branches: + - '*' + paths: + - 'pkg/provider/kubernetes/gateway/**' + - 'integration/k8s_conformance_test.go' + +env: + GO_VERSION: '1.21' + CGO_ENABLED: 0 + +jobs: + + test-conformance: + runs-on: ubuntu-20.04 + + steps: + - name: Check out code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go ${{ env.GO_VERSION }} + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Avoid generating webui + run: touch webui/static/index.html + + - name: Build binary + run: make binary + + - name: Setcap + run: | + sudo setcap 'cap_net_bind_service=+ep' dist/linux/amd64/traefik + + - name: K8s Gateway API conformance test + run: make test-gateway-api-conformance-ci diff --git a/.github/workflows/test-integration.yaml b/.github/workflows/test-integration.yaml index d5c4e5ca1..665da80f3 100644 --- a/.github/workflows/test-integration.yaml +++ b/.github/workflows/test-integration.yaml @@ -4,18 +4,15 @@ on: pull_request: branches: - '*' - push: - branches: - - 'gh-actions' env: - GO_VERSION: '1.21' + GO_VERSION: '1.22' CGO_ENABLED: 0 jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Check out code @@ -35,7 +32,7 @@ jobs: run: make binary test-integration: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 needs: - build strategy: diff --git a/.github/workflows/test-unit.yaml b/.github/workflows/test-unit.yaml index 7719186f7..c23db68c3 100644 --- a/.github/workflows/test-unit.yaml +++ b/.github/workflows/test-unit.yaml @@ -6,12 +6,12 @@ on: - '*' env: - GO_VERSION: '1.21' + GO_VERSION: '1.22' jobs: test-unit: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Check out code diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml index f836e94ee..82570d790 100644 --- a/.github/workflows/validate.yaml +++ b/.github/workflows/validate.yaml @@ -6,14 +6,14 @@ on: - '*' env: - GO_VERSION: '1.21' - GOLANGCI_LINT_VERSION: v1.55.2 + GO_VERSION: '1.22' + GOLANGCI_LINT_VERSION: v1.56.0 MISSSPELL_VERSION: v0.4.1 jobs: validate: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Check out code @@ -39,7 +39,7 @@ jobs: run: make validate validate-generate: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Check out code diff --git a/.gitignore b/.gitignore index 3fd473f24..03a5e9369 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ plugins-storage/ plugins-local/ traefik_changelog.md integration/tailscale.secret +integration/conformance-reports/ diff --git a/.golangci.yml b/.golangci.yml index 7128a4177..cf35a0d8a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -150,18 +150,14 @@ linters-settings: - github.com/jaguilar/vt100 - github.com/cucumber/godog testifylint: - enable: - - bool-compare - - compares - - empty - - error-is-as - - error-nil - - expected-actual - - float-compare - - len - - suite-extra-assert-call - - suite-thelper - + disable: + - suite-dont-use-pkg + - require-error + - go-require + staticcheck: + checks: + - all + - -SA1019 linters: enable-all: true disable: @@ -216,11 +212,12 @@ linters: issues: exclude-use-default: false - max-per-linter: 0 + max-issues-per-linter: 0 max-same-issues: 0 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" + - 'fmt.Sprintf can be replaced with string addition' exclude-rules: - path: '(.+)_test.go' linters: @@ -280,3 +277,9 @@ issues: text: 'unusedwrite: unused write to field' linters: - govet + - path: pkg/cli/deprecation.go + linters: + - goconst + - path: pkg/cli/loader_file.go + linters: + - goconst diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index a8fc18b6f..bbbb8d239 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -19,13 +19,13 @@ global_job_config: prologue: commands: - curl -sSfL https://raw.githubusercontent.com/ldez/semgo/master/godownloader.sh | sudo sh -s -- -b "/usr/local/bin" - - sudo semgo go1.21 + - sudo semgo go1.22 - export "GOPATH=$(go env GOPATH)" - export "SEMAPHORE_GIT_DIR=${GOPATH}/src/github.com/traefik/${SEMAPHORE_PROJECT_NAME}" - export "PATH=${GOPATH}/bin:${PATH}" - mkdir -vp "${SEMAPHORE_GIT_DIR}" "${GOPATH}/bin" - export GOPROXY=https://proxy.golang.org,direct - - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "${GOPATH}/bin" v1.55.2 + - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "${GOPATH}/bin" v1.56.0 - curl -sSfL https://gist.githubusercontent.com/traefiker/6d7ac019c11d011e4f131bb2cca8900e/raw/goreleaser.sh | bash -s -- -b "${GOPATH}/bin" - checkout - cache restore traefik-$(checksum go.sum) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0cabd687..8f66b736f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## [v2.11.0-rc2](https://github.com/traefik/traefik/tree/v2.11.0-rc2) (2024-01-24) +[All Commits](https://github.com/traefik/traefik/compare/v2.11.0-rc1...v2.11.0-rc2) + +**Bug fixes:** +- **[middleware,tcp]** Add missing TCP IPAllowList middleware constructor ([#10331](https://github.com/traefik/traefik/pull/10331) by [youkoulayley](https://github.com/youkoulayley)) +- **[nomad]** Update the Nomad API dependency to v1.7.2 ([#10327](https://github.com/traefik/traefik/pull/10327) by [jrasell](https://github.com/jrasell)) + +**Documentation:** +- Improve Concepts documentation page ([#10315](https://github.com/traefik/traefik/pull/10315) by [oliver-dvorski](https://github.com/oliver-dvorski)) + ## [v2.11.0-rc1](https://github.com/traefik/traefik/tree/v2.11.0-rc1) (2024-01-02) [All Commits](https://github.com/traefik/traefik/compare/0a7964300166d167f68d5502bc245b3b9c8842b4...v2.11.0-rc1) diff --git a/Makefile b/Makefile index 15565a3ce..5c9b15656 100644 --- a/Makefile +++ b/Makefile @@ -22,40 +22,41 @@ LINT_EXECUTABLES = misspell shellcheck DOCKER_BUILD_PLATFORMS ?= linux/amd64,linux/arm64 .PHONY: default +#? default: Run `make generate` and `make binary` default: generate binary -## Create the "dist" directory +#? dist: Create the "dist" directory dist: mkdir -p dist -## Build WebUI Docker image .PHONY: build-webui-image +#? build-webui-image: Build WebUI Docker image build-webui-image: docker build -t traefik-webui -f webui/Dockerfile webui -## Clean WebUI static generated assets .PHONY: clean-webui +#? clean-webui: Clean WebUI static generated assets clean-webui: rm -r webui/static mkdir -p webui/static printf 'For more information see `webui/readme.md`' > webui/static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md -## Generate WebUI webui/static/index.html: $(MAKE) build-webui-image docker run --rm -v "$(PWD)/webui/static":'/src/webui/static' traefik-webui npm run build:nc docker run --rm -v "$(PWD)/webui/static":'/src/webui/static' traefik-webui chown -R $(shell id -u):$(shell id -g) ./static .PHONY: generate-webui +#? generate-webui: Generate WebUI generate-webui: webui/static/index.html -## Generate code .PHONY: generate +#? generate: Generate code (Dynamic and Static configuration documentation reference files) generate: go generate -## Build the binary .PHONY: binary +#? binary: Build the binary binary: generate-webui dist @echo SHA: $(VERSION) $(CODENAME) $(DATE) CGO_ENABLED=0 GOGC=off GOOS=${GOOS} GOARCH=${GOARCH} go build ${FLAGS[*]} -ldflags "-s -w \ @@ -80,27 +81,39 @@ binary-windows-amd64: export BIN_NAME := traefik.exe binary-windows-amd64: @$(MAKE) binary -## Build the binary for the standard platforms (linux, darwin, windows) .PHONY: crossbinary-default +#? crossbinary-default: Build the binary for the standard platforms (linux, darwin, windows) crossbinary-default: generate generate-webui $(CURDIR)/script/crossbinary-default.sh -## Run the unit and integration tests .PHONY: test +#? test: Run the unit and integration tests test: test-unit test-integration -## Run the unit tests .PHONY: test-unit +#? test-unit: Run the unit tests test-unit: GOOS=$(GOOS) GOARCH=$(GOARCH) go test -cover "-coverprofile=cover.out" -v $(TESTFLAGS) ./pkg/... ./cmd/... -## Run the integration tests .PHONY: test-integration +#? test-integration: Run the integration tests test-integration: binary GOOS=$(GOOS) GOARCH=$(GOARCH) go test ./integration -test.timeout=20m -failfast -v $(TESTFLAGS) -## Pull all Docker images to avoid timeout during integration tests +.PHONY: test-gateway-api-conformance +#? test-gateway-api-conformance: Run the conformance tests +test-gateway-api-conformance: binary + GOOS=$(GOOS) GOARCH=$(GOARCH) go test ./integration -v -test.run K8sConformanceSuite -k8sConformance=true $(TESTFLAGS) + +## TODO: Need to be fixed to work in all situations. +.PHONY: test-gateway-api-conformance-ci +#? test-gateway-api-conformance-ci: Run the conformance tests +test-gateway-api-conformance-ci: + GOOS=$(GOOS) GOARCH=$(GOARCH) go test ./integration -v -test.run K8sConformanceSuite -k8sConformance=true $(TESTFLAGS) + + .PHONY: pull-images +#? pull-images: Pull all Docker images to avoid timeout during integration tests pull-images: grep --no-filename -E '^\s+image:' ./integration/resources/compose/*.yml \ | awk '{print $$2}' \ @@ -108,21 +121,21 @@ pull-images: | uniq \ | xargs -P 6 -n 1 docker pull -## Lint run golangci-lint .PHONY: lint +#? lint: Run golangci-lint lint: golangci-lint run -## Validate code and docs .PHONY: validate-files +#? validate-files: Validate code and docs validate-files: lint $(foreach exec,$(LINT_EXECUTABLES),\ $(if $(shell which $(exec)),,$(error "No $(exec) in PATH"))) $(CURDIR)/script/validate-misspell.sh $(CURDIR)/script/validate-shell-script.sh -## Validate code, docs, and vendor .PHONY: validate +#? validate: Validate code, docs, and vendor validate: lint $(foreach exec,$(EXECUTABLES),\ $(if $(shell which $(exec)),,$(error "No $(exec) in PATH"))) @@ -136,51 +149,57 @@ multi-arch-image-%: binary-linux-amd64 binary-linux-arm64 docker buildx build $(DOCKER_BUILDX_ARGS) -t traefik/traefik:$* --platform=$(DOCKER_BUILD_PLATFORMS) -f Dockerfile . -## Clean up static directory and build a Docker Traefik image .PHONY: build-image +#? build-image: Clean up static directory and build a Docker Traefik image build-image: export DOCKER_BUILDX_ARGS := --load build-image: export DOCKER_BUILD_PLATFORMS := linux/$(GOARCH) build-image: clean-webui @$(MAKE) multi-arch-image-latest -## Build a Docker Traefik image without re-building the webui when it's already built .PHONY: build-image-dirty +#? build-image-dirty: Build a Docker Traefik image without re-building the webui when it's already built build-image-dirty: export DOCKER_BUILDX_ARGS := --load build-image-dirty: export DOCKER_BUILD_PLATFORMS := linux/$(GOARCH) build-image-dirty: @$(MAKE) multi-arch-image-latest -## Build documentation site .PHONY: docs +#? docs: Build documentation site docs: make -C ./docs docs -## Serve the documentation site locally .PHONY: docs-serve +#? docs-serve: Serve the documentation site locally docs-serve: make -C ./docs docs-serve -## Pull image for doc building .PHONY: docs-pull-images +#? docs-pull-images: Pull image for doc building docs-pull-images: make -C ./docs docs-pull-images -## Generate CRD clientset and CRD manifests .PHONY: generate-crd +#? generate-crd: Generate CRD clientset and CRD manifests generate-crd: @$(CURDIR)/script/code-gen-docker.sh -## Generate code from dynamic configuration https://github.com/traefik/genconf .PHONY: generate-genconf +#? generate-genconf: Generate code from dynamic configuration github.com/traefik/genconf generate-genconf: go run ./cmd/internal/gen/ -## Create packages for the release .PHONY: release-packages +#? release-packages: Create packages for the release release-packages: generate-webui $(CURDIR)/script/release-packages.sh -## Format the Code .PHONY: fmt +#? fmt: Format the Code fmt: gofmt -s -l -w $(SRCS) + +.PHONY: help +#? help: Get more info on make commands +help: Makefile + @echo " Choose a command run in traefik:" + @sed -n 's/^#?//p' $< | column -t -s ':' | sort | sed -e 's/^/ /' diff --git a/README.md b/README.md index 26e4e5169..73decf511 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ _(But if you'd rather configure some of your routes manually, Traefik supports t - [Docker](https://doc.traefik.io/traefik/providers/docker/) / [Swarm mode](https://doc.traefik.io/traefik/providers/docker/) - [Kubernetes](https://doc.traefik.io/traefik/providers/kubernetes-crd/) +- [ECS](https://doc.traefik.io/traefik/providers/ecs/) - [File](https://doc.traefik.io/traefik/providers/file/) ## Quickstart diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index 0ac89fb46..821f391ed 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -53,7 +53,7 @@ func main() { // traefik config inits tConfig := cmd.NewTraefikConfiguration() - loaders := []cli.ResourceLoader{&tcli.FileLoader{}, &tcli.FlagLoader{}, &tcli.EnvLoader{}} + loaders := []cli.ResourceLoader{&tcli.DeprecationLoader{}, &tcli.FileLoader{}, &tcli.FlagLoader{}, &tcli.EnvLoader{}} cmdTraefik := &cli.Command{ Name: "traefik", @@ -193,10 +193,13 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err tsProviders := initTailscaleProviders(staticConfiguration, &providerAggregator) - // Metrics + // Observability metricRegistries := registerMetricClients(staticConfiguration.Metrics) metricsRegistry := metrics.NewMultiRegistry(metricRegistries) + accessLog := setupAccessLog(staticConfiguration.AccessLog) + tracer, tracerCloser := setupTracing(staticConfiguration.Tracing) + observabilityMgr := middleware.NewObservabilityMgr(*staticConfiguration, metricsRegistry, accessLog, tracer, tracerCloser) // Entrypoints @@ -263,14 +266,11 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err roundTripperManager := service.NewRoundTripperManager(spiffeX509Source) dialerManager := tcp.NewDialerManager(spiffeX509Source) acmeHTTPHandler := getHTTPChallengeHandler(acmeProviders, httpChallengeProvider) - managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, metricsRegistry, roundTripperManager, acmeHTTPHandler) + managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, observabilityMgr, roundTripperManager, acmeHTTPHandler) // Router factory - accessLog := setupAccessLog(staticConfiguration.AccessLog) - tracer, tracerCloser := setupTracing(staticConfiguration.Tracing) - chainBuilder := middleware.NewChainBuilder(metricsRegistry, accessLog, tracer) - routerFactory := server.NewRouterFactory(*staticConfiguration, managerFactory, tlsManager, chainBuilder, pluginBuilder, metricsRegistry, dialerManager) + routerFactory := server.NewRouterFactory(*staticConfiguration, managerFactory, tlsManager, observabilityMgr, pluginBuilder, dialerManager) // Watcher @@ -351,7 +351,7 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err } }) - return server.NewServer(routinesPool, serverEntryPointsTCP, serverEntryPointsUDP, watcher, chainBuilder, accessLog, tracerCloser), nil + return server.NewServer(routinesPool, serverEntryPointsTCP, serverEntryPointsUDP, watcher, observabilityMgr), nil } func getHTTPChallengeHandler(acmeProviders []*acme.Provider, httpChallengeProvider http.Handler) http.Handler { @@ -520,15 +520,14 @@ func registerMetricClients(metricsConfig *types.Metrics) []metrics.Registry { } } - if metricsConfig.OpenTelemetry != nil { + if metricsConfig.OTLP != nil { logger := log.With().Str(logs.MetricsProviderName, "openTelemetry").Logger() - openTelemetryRegistry := metrics.RegisterOpenTelemetry(logger.WithContext(context.Background()), metricsConfig.OpenTelemetry) + openTelemetryRegistry := metrics.RegisterOpenTelemetry(logger.WithContext(context.Background()), metricsConfig.OTLP) if openTelemetryRegistry != nil { registries = append(registries, openTelemetryRegistry) logger.Debug(). - Str("address", metricsConfig.OpenTelemetry.Address). - Str("pushInterval", metricsConfig.OpenTelemetry.PushInterval.String()). + Str("pushInterval", metricsConfig.OTLP.PushInterval.String()). Msg("Configured OpenTelemetry metrics") } } diff --git a/docs/content/getting-started/quick-start.md b/docs/content/getting-started/quick-start.md index a33eb4b22..122ff530d 100644 --- a/docs/content/getting-started/quick-start.md +++ b/docs/content/getting-started/quick-start.md @@ -19,7 +19,7 @@ version: '3' services: reverse-proxy: - # The official v2 Traefik docker image + # The official v3 Traefik docker image image: traefik:v3.0 # Enables the web UI and tells Traefik to listen to docker command: --api.insecure=true --providers.docker diff --git a/docs/content/https/acme.md b/docs/content/https/acme.md index b614beedd..f423b23db 100644 --- a/docs/content/https/acme.md +++ b/docs/content/https/acme.md @@ -313,7 +313,7 @@ For complete details, refer to your provider's _Additional configuration_ link. | [ACME DNS](https://github.com/joohoi/acme-dns) | `acme-dns` | `ACME_DNS_API_BASE`, `ACME_DNS_STORAGE_PATH` | [Additional configuration](https://go-acme.github.io/lego/dns/acme-dns) | | [Alibaba Cloud](https://www.alibabacloud.com) | `alidns` | `ALICLOUD_ACCESS_KEY`, `ALICLOUD_SECRET_KEY`, `ALICLOUD_REGION_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/alidns) | | [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) | +| [ArvanCloud](https://www.arvancloud.ir/en) | `arvancloud` | `ARVANCLOUD_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/arvancloud) | | [Auroradns](https://www.pcextreme.com/dns-health-checks) | `auroradns` | `AURORA_USER_ID`, `AURORA_KEY`, `AURORA_ENDPOINT` | [Additional configuration](https://go-acme.github.io/lego/dns/auroradns) | | [Autodns](https://www.internetx.com/domains/autodns/) | `autodns` | `AUTODNS_API_USER`, `AUTODNS_API_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/autodns) | | [Azure](https://azure.microsoft.com/services/dns/) (DEPRECATED) | `azure` | `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SUBSCRIPTION_ID`, `AZURE_TENANT_ID`, `AZURE_RESOURCE_GROUP`, `[AZURE_METADATA_ENDPOINT]` | [Additional configuration](https://go-acme.github.io/lego/dns/azure) | @@ -361,6 +361,7 @@ For complete details, refer to your provider's _Additional configuration_ link. | [Hetzner](https://hetzner.com) | `hetzner` | `HETZNER_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/hetzner) | | [hosting.de](https://www.hosting.de) | `hostingde` | `HOSTINGDE_API_KEY`, `HOSTINGDE_ZONE_NAME` | [Additional configuration](https://go-acme.github.io/lego/dns/hostingde) | | [Hosttech](https://www.hosttech.eu) | `hosttech` | `HOSTTECH_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/hosttech) | +| [http.net](https://www.http.net/) | `httpnet` | `HTTPNET_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/httpnet) | | [Hurricane Electric](https://dns.he.net) | `hurricane` | `HURRICANE_TOKENS` [^6] | [Additional configuration](https://go-acme.github.io/lego/dns/hurricane) | | [HyperOne](https://www.hyperone.com) | `hyperone` | `HYPERONE_PASSPORT_LOCATION`, `HYPERONE_LOCATION_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/hyperone) | | [IBM Cloud (SoftLayer)](https://www.ibm.com/cloud/) | `ibmcloud` | `SOFTLAYER_USERNAME`, `SOFTLAYER_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/ibmcloud) | @@ -426,6 +427,7 @@ For complete details, refer to your provider's _Additional configuration_ link. | [VK Cloud](https://mcs.mail.ru/) | `vkcloud` | `VK_CLOUD_PASSWORD`, `VK_CLOUD_PROJECT_ID`, `VK_CLOUD_USERNAME` | [Additional configuration](https://go-acme.github.io/lego/dns/vkcloud) | | [Vscale](https://vscale.io/) | `vscale` | `VSCALE_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/vscale) | | [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/vultr) | +| [Webnames](https://www.webnames.ru/) | `webnames` | `WEBNAMES_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/webnames) | | [Websupport](https://websupport.sk) | `websupport` | `WEBSUPPORT_API_KEY`, `WEBSUPPORT_SECRET` | [Additional configuration](https://go-acme.github.io/lego/dns/websupport) | | [WEDOS](https://www.wedos.com) | `wedos` | `WEDOS_USERNAME`, `WEDOS_WAPI_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/wedos) | | [Yandex 360](https://360.yandex.ru) | `yandex360` | `YANDEX360_OAUTH_TOKEN`, `YANDEX360_ORG_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/yandex360) | diff --git a/docs/content/middlewares/http/circuitbreaker.md b/docs/content/middlewares/http/circuitbreaker.md index be7b1422d..c1ead36b6 100644 --- a/docs/content/middlewares/http/circuitbreaker.md +++ b/docs/content/middlewares/http/circuitbreaker.md @@ -85,6 +85,7 @@ At specified intervals (`checkPeriod`), the circuit breaker evaluates `expressio ### Open While open, the fallback mechanism takes over the normal service calls for a duration of `FallbackDuration`. +The fallback mechanism returns a `HTTP 503` (or `ResponseCode`) to the client. After this duration, it enters the recovering state. ### Recovering @@ -179,3 +180,9 @@ The duration for which the circuit breaker will wait before trying to recover (f _Optional, Default="10s"_ The duration for which the circuit breaker will try to recover (as soon as it is in recovering state). + +### `ResponseCode` + +_Optional, Default="503"_ + +The status code that the circuit breaker will return while it is in the open state. diff --git a/docs/content/middlewares/http/contenttype.md b/docs/content/middlewares/http/contenttype.md index f08bccb39..fd3759edf 100644 --- a/docs/content/middlewares/http/contenttype.md +++ b/docs/content/middlewares/http/contenttype.md @@ -52,3 +52,16 @@ http: [http.middlewares] [http.middlewares.autodetect.contentType] ``` + +## Configuration Options + +### `autoDetect` + +!!! warning + + `autoDetect` option is deprecated and should not be used. + Moreover, it is redundant with an empty ContentType middleware declaration. + +`autoDetect` specifies whether to let the `Content-Type` header, +if it has not been set by the backend, +be automatically set to a value derived from the contents of the response. diff --git a/docs/content/middlewares/http/headers.md b/docs/content/middlewares/http/headers.md index 7c4599b18..d0cb63672 100644 --- a/docs/content/middlewares/http/headers.md +++ b/docs/content/middlewares/http/headers.md @@ -314,11 +314,43 @@ The `allowedHosts` option lists fully qualified domain names that are allowed. The `hostsProxyHeaders` option is a set of header keys that may hold a proxied hostname value for the request. +### `sslRedirect` + +!!! warning + + Deprecated in favor of [EntryPoint redirection](../../routing/entrypoints.md#redirection) or the [RedirectScheme middleware](./redirectscheme.md). + +The `sslRedirect` only allow HTTPS requests when set to `true`. + +### `sslTemporaryRedirect` + +!!! warning + + Deprecated in favor of [EntryPoint redirection](../../routing/entrypoints.md#redirection) or the [RedirectScheme middleware](./redirectscheme.md). + +Set `sslTemporaryRedirect` to `true` to force an SSL redirection using a 302 (instead of a 301). + +### `sslHost` + +!!! warning + + Deprecated in favor of the [RedirectRegex middleware](./redirectregex.md). + +The `sslHost` option is the host name that is used to redirect HTTP requests to HTTPS. + ### `sslProxyHeaders` The `sslProxyHeaders` option is set of header keys with associated values that would indicate a valid HTTPS request. It can be useful when using other proxies (example: `"X-Forwarded-Proto": "https"`). +### `sslForceHost` + +!!! warning + + Deprecated in favor of the [RedirectRegex middleware](./redirectregex.md). + +Set `sslForceHost` to `true` and set `sslHost` to force requests to use `SSLHost` regardless of whether they already use SSL. + ### `stsSeconds` The `stsSeconds` is the max-age of the `Strict-Transport-Security` header. @@ -370,6 +402,14 @@ The `publicKey` implements HPKP to prevent MITM attacks with forged certificates The `referrerPolicy` allows sites to control whether browsers forward the `Referer` header to other sites. +### `featurePolicy` + +!!! warning + + Deprecated in favor of [`permissionsPolicy`](#permissionsPolicy) + +The `featurePolicy` allows sites to control browser features. + ### `permissionsPolicy` The `permissionsPolicy` allows sites to control browser features. diff --git a/docs/content/middlewares/http/stripprefix.md b/docs/content/middlewares/http/stripprefix.md index 5da2543b2..b84442321 100644 --- a/docs/content/middlewares/http/stripprefix.md +++ b/docs/content/middlewares/http/stripprefix.md @@ -76,3 +76,72 @@ For instance, `/products` also matches `/products/shoes` and `/products/shirts`. If your backend is serving assets (e.g., images or JavaScript files), it can use the `X-Forwarded-Prefix` header to properly construct relative URLs. Using the previous example, the backend should return `/products/shoes/image.png` (and not `/image.png`, which Traefik would likely not be able to associate with the same backend). + +### `forceSlash` + +_Optional, Default=true_ + +!!! warning + + `forceSlash` option is deprecated and should not be used. + +The `forceSlash` option ensures the resulting stripped path is not the empty string, by replacing it with `/` when necessary. + +??? info "Behavior examples" + + - `forceSlash=true` + + | Path | Prefix to strip | Result | + |------------|-----------------|--------| + | `/` | `/` | `/` | + | `/foo` | `/foo` | `/` | + | `/foo/` | `/foo` | `/` | + | `/foo/` | `/foo/` | `/` | + | `/bar` | `/foo` | `/bar` | + | `/foo/bar` | `/foo` | `/bar` | + + - `forceSlash=false` + + | Path | Prefix to strip | Result | + |------------|-----------------|--------| + | `/` | `/` | empty | + | `/foo` | `/foo` | empty | + | `/foo/` | `/foo` | `/` | + | `/foo/` | `/foo/` | empty | + | `/bar` | `/foo` | `/bar` | + | `/foo/bar` | `/foo` | `/bar` | + +```yaml tab="Docker" +labels: + - "traefik.http.middlewares.example.stripprefix.prefixes=/foobar" + - "traefik.http.middlewares.example.stripprefix.forceSlash=false" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: example +spec: + stripPrefix: + prefixes: + - "/foobar" + forceSlash: false +``` + +```yaml tab="File (YAML)" +http: + middlewares: + example: + stripPrefix: + prefixes: + - "/foobar" + forceSlash: false +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.example.stripPrefix] + prefixes = ["/foobar"] + forceSlash = false +``` diff --git a/docs/content/migration/v2-to-v3.md b/docs/content/migration/v2-to-v3.md index 70b27f820..08a45d741 100644 --- a/docs/content/migration/v2-to-v3.md +++ b/docs/content/migration/v2-to-v3.md @@ -19,6 +19,8 @@ and how it now looks like in v3. ### Docker & Docker Swarm +#### SwarmMode + In v3, the provider Docker has been split into 2 providers: - Docker provider (without Swarm support) @@ -43,7 +45,7 @@ In v3, the provider Docker has been split into 2 providers: This configuration is now unsupported and would prevent Traefik to start. -#### Remediation +##### Remediation In v3, the `swarmMode` should not be used with the Docker provider, and, to use Swarm, the Swarm provider should be used instead. @@ -64,7 +66,35 @@ In v3, the `swarmMode` should not be used with the Docker provider, and, to use --providers.swarm.endpoint=tcp://127.0.0.1:2377 ``` -### HTTP3 Experimental Configuration +#### TLS.CAOptional + +Docker provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType). + +??? example "An example usage of the TLS.CAOptional option" + + ```yaml tab="File (YAML)" + providers: + docker: + tls: + caOptional: true + ``` + + ```toml tab="File (TOML)" + [providers.docker.tls] + caOptional=true + ``` + + ```bash tab="CLI" + --providers.docker.tls.caOptional=true + ``` + +##### Remediation + +The `tls.caOptional` option should be removed from the Docker provider static configuration. + +### Experimental Configuration + +#### HTTP3 In v3, HTTP/3 is no longer an experimental feature. It can be enabled on entry points without the associated `experimental.http3` option, which is now removed. @@ -86,12 +116,14 @@ It is now unsupported and would prevent Traefik to start. --experimental.http3=true ``` -#### Remediation +##### Remediation The `http3` option should be removed from the static configuration experimental section. ### Consul provider +#### namespace + The Consul provider `namespace` option was deprecated in v2 and is now removed in v3. It is now unsupported and would prevent Traefik to start. @@ -111,7 +143,7 @@ It is now unsupported and would prevent Traefik to start. --consul.namespace=foobar ``` -#### Remediation +##### Remediation In v3, the `namespaces` option should be used instead of the `namespace` option. @@ -132,8 +164,36 @@ In v3, the `namespaces` option should be used instead of the `namespace` option. --consul.namespaces=foobar ``` +#### TLS.CAOptional + +Consul provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType). + +??? example "An example usage of the TLS.CAOptional option" + + ```yaml tab="File (YAML)" + providers: + consul: + tls: + caOptional: true + ``` + + ```toml tab="File (TOML)" + [providers.consul.tls] + caOptional=true + ``` + + ```bash tab="CLI" + --providers.consul.tls.caOptional=true + ``` + +##### Remediation + +The `tls.caOptional` option should be removed from the Consul provider static configuration. + ### ConsulCatalog provider +#### namespace + The ConsulCatalog provider `namespace` option was deprecated in v2 and is now removed in v3. It is now unsupported and would prevent Traefik to start. @@ -153,7 +213,7 @@ It is now unsupported and would prevent Traefik to start. --consulCatalog.namespace=foobar ``` -#### Remediation +##### Remediation In v3, the `namespaces` option should be used instead of the `namespace` option. @@ -174,8 +234,37 @@ In v3, the `namespaces` option should be used instead of the `namespace` option. --consulCatalog.namespaces=foobar ``` +#### Endpoint.TLS.CAOptional + +ConsulCatalog provider `endpoint.tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType). + +??? example "An example usage of the Endpoint.TLS.CAOptional option" + + ```yaml tab="File (YAML)" + providers: + consulCatalog: + endpoint: + tls: + caOptional: true + ``` + + ```toml tab="File (TOML)" + [providers.consulCatalog.endpoint.tls] + caOptional=true + ``` + + ```bash tab="CLI" + --providers.consulCatalog.endpoint.tls.caOptional=true + ``` + +##### Remediation + +The `endpoint.tls.caOptional` option should be removed from the ConsulCatalog provider static configuration. + ### Nomad provider +#### namespace + The Nomad provider `namespace` option was deprecated in v2 and is now removed in v3. It is now unsupported and would prevent Traefik to start. @@ -195,7 +284,7 @@ It is now unsupported and would prevent Traefik to start. --nomad.namespace=foobar ``` -#### Remediation +##### Remediation In v3, the `namespaces` option should be used instead of the `namespace` option. @@ -216,6 +305,33 @@ In v3, the `namespaces` option should be used instead of the `namespace` option. --nomad.namespaces=foobar ``` +#### Endpoint.TLS.CAOptional + +Nomad provider `endpoint.tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType). + +??? example "An example usage of the Endpoint.TLS.CAOptional option" + + ```yaml tab="File (YAML)" + providers: + nomad: + endpoint: + tls: + caOptional: true + ``` + + ```toml tab="File (TOML)" + [providers.nomad.endpoint.tls] + caOptional=true + ``` + + ```bash tab="CLI" + --providers.nomad.endpoint.tls.caOptional=true + ``` + +##### Remediation + +The `endpoint.tls.caOptional` option should be removed from the Nomad provider static configuration. + ### Rancher v1 Provider In v3, the Rancher v1 provider has been removed because Rancher v1 is [no longer actively maintaned](https://rancher.com/docs/os/v1.x/en/support/), @@ -271,6 +387,90 @@ This configuration is now unsupported and would prevent Traefik to start. All Marathon provider related configuration should be removed from the static configuration. +### HTTP Provider + +#### TLS.CAOptional + +HTTP provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType). + +??? example "An example usage of the TLS.CAOptional option" + + ```yaml tab="File (YAML)" + providers: + http: + tls: + caOptional: true + ``` + + ```toml tab="File (TOML)" + [providers.http.tls] + caOptional=true + ``` + + ```bash tab="CLI" + --providers.http.tls.caOptional=true + ``` + +##### Remediation + +The `tls.caOptional` option should be removed from the HTTP provider static configuration. + +### ETCD Provider + +#### TLS.CAOptional + +ETCD provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType). + +??? example "An example usage of the TLS.CAOptional option" + + ```yaml tab="File (YAML)" + providers: + etcd: + tls: + caOptional: true + ``` + + ```toml tab="File (TOML)" + [providers.etcd.tls] + caOptional=true + ``` + + ```bash tab="CLI" + --providers.etcd.tls.caOptional=true + ``` + +##### Remediation + +The `tls.caOptional` option should be removed from the ETCD provider static configuration. + +### Redis Provider + +#### TLS.CAOptional + +Redis provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType). + +??? example "An example usage of the TLS.CAOptional option" + + ```yaml tab="File (YAML)" + providers: + redis: + tls: + caOptional: true + ``` + + ```toml tab="File (TOML)" + [providers.redis.tls] + caOptional=true + ``` + + ```bash tab="CLI" + --providers.redis.tls.caOptional=true + ``` + +##### Remediation + +The `tls.caOptional` option should be removed from the Redis provider static configuration. + ### InfluxDB v1 InfluxDB v1.x maintenance [ended in 2021](https://www.influxdata.com/blog/influxdb-oss-and-enterprise-roadmap-update-from-influxdays-emea/). @@ -326,21 +526,16 @@ All Pilot related configuration should be removed from the static configuration. ## Dynamic configuration -### IPWhiteList +### Router Rule Matchers -In v3, we renamed the `IPWhiteList` middleware to `IPAllowList` without changing anything to the configuration. +In v3, a new rule matchers syntax has been introduced for HTTP and TCP routers. +The default rule matchers syntax is now the v3 one, but for backward compatibility this can be configured. +The v2 rule matchers syntax is deprecated and its support will be removed in the next major version. +For this reason, we encourage migrating to the new syntax. -### Deprecated Options Removal +#### New V3 Syntax Notable Changes -- The `tracing.datadog.globaltag` option has been removed. -- The `tls.caOptional` option has been removed from the ForwardAuth middleware, as well as from the HTTP, Consul, Etcd, Redis, ZooKeeper, Consul Catalog, and Docker providers. -- `sslRedirect`, `sslTemporaryRedirect`, `sslHost`, `sslForceHost` and `featurePolicy` options of the Headers middleware have been removed. -- The `forceSlash` option of the StripPrefix middleware has been removed. -- The `preferServerCipherSuites` option has been removed. - -### Matchers - -In v3, the `Headers` and `HeadersRegexp` matchers have been renamed to `Header` and `HeaderRegexp` respectively. +The `Headers` and `HeadersRegexp` matchers have been renamed to `Header` and `HeaderRegexp` respectively. `PathPrefix` no longer uses regular expressions to match path prefixes. @@ -355,6 +550,87 @@ and should be explicitly combined using logical operators to mimic previous beha `HostHeader` has been removed, use `Host` instead. +#### Remediation + +##### Configure the Default Syntax In Static Configuration + +The default rule matchers syntax is the expected syntax for any router that is not self opt-out from this default value. +It can be configured in the static configuration. + +??? example "An example configuration for the default rule matchers syntax" + + ```yaml tab="File (YAML)" + # static configuration + core: + defaultRuleSyntax: v2 + ``` + + ```toml tab="File (TOML)" + # static configuration + [core] + defaultRuleSyntax="v2" + ``` + + ```bash tab="CLI" + # static configuration + --core.defaultRuleSyntax=v2 + ``` + +##### Configure the Syntax Per Router + +The rule syntax can also be configured on a per-router basis. +This allows to have heterogeneous router configurations and ease migration. + +??? example "An example router with syntax configuration" + +```yaml tab="Docker & Swarm" +labels: + - "traefik.http.routers.test.ruleSyntax=v2" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: test.route + namespace: default + +spec: + routes: + - match: PathPrefix(`/foo`, `/bar`) + syntax: v2 + kind: Rule +``` + +```yaml tab="Consul Catalog" +- "traefik.http.routers.test.ruleSyntax=v2" +``` + +```yaml tab="File (YAML)" +http: + routers: + test: + ruleSyntax: v2 +``` + +```toml tab="File (TOML)" +[http.routers] + [http.routers.test] + ruleSyntax = "v2" +``` + +### IPWhiteList + +In v3, we renamed the `IPWhiteList` middleware to `IPAllowList` without changing anything to the configuration. + +### Deprecated Options Removal + +- The `tracing.datadog.globaltag` option has been removed. +- The `tls.caOptional` option has been removed from the ForwardAuth middleware, as well as from the HTTP, Consul, Etcd, Redis, ZooKeeper, Consul Catalog, and Docker providers. +- `sslRedirect`, `sslTemporaryRedirect`, `sslHost`, `sslForceHost` and `featurePolicy` options of the Headers middleware have been removed. +- The `forceSlash` option of the StripPrefix middleware has been removed. +- The `preferServerCipherSuites` option has been removed. + ### TCP LoadBalancer `terminationDelay` option The TCP LoadBalancer `terminationDelay` option has been removed. @@ -415,3 +691,15 @@ Here are two possible transition strategies: For legacy stacks that cannot immediately upgrade to the latest vendor agents supporting OTLP ingestion, using OpenTelemetry (OTel) collectors with appropriate exporters configuration is a viable solution. This allows continued compatibility with the existing infrastructure. + +Please check the [OpenTelemetry Tracing provider documention](../observability/tracing/opentelemetry.md) for more information. + +#### Internal Resources Observability (AccessLogs, Metrics and Tracing) + +In v3, observability for internal routers or services (e.g.: `ping@internal`) is disabled by default. +To enable it one should use the new `addInternals` option for AccessLogs, Metrics or Tracing. +Please take a look at the observability documentation for more information: + +- [AccessLogs](../observability/access-logs.md#addinternals) +- [Metrics](../observability/metrics/overview.md#addinternals) +- [AccessLogs](../observability/tracing/overview.md#addinternals) diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md index 6db72ea76..ed38e5263 100644 --- a/docs/content/migration/v2.md +++ b/docs/content/migration/v2.md @@ -536,3 +536,30 @@ In `v2.11`, the `IPWhiteList` middleware is deprecated, please use the [IPAllowL ### IPWhiteList (TCP) In `v2.11`, the `IPWhiteList` middleware is deprecated, please use the [IPAllowList](../middlewares/tcp/ipallowlist.md) middleware instead. + +### TLS CipherSuites + +> By default, cipher suites without ECDHE support are no longer offered by either clients or servers during pre-TLS 1.3 handshakes. +> This change can be reverted with the `tlsrsakex=1 GODEBUG` setting. +> (https://go.dev/doc/go1.22#crypto/tls) + +The _RSA key exchange_ cipher suites are way less secure than the modern ECDHE cipher suites and exposes to potential vulnerabilities like [the Marvin Attack](https://people.redhat.com/~hkario/marvin). +Decision has been made to support ECDHE cipher suites only by default. + +The following ciphers have been removed from the default list: + +- `TLS_RSA_WITH_AES_128_CBC_SHA` +- `TLS_RSA_WITH_AES_256_CBC_SHA` +- `TLS_RSA_WITH_AES_128_GCM_SHA256` +- `TLS_RSA_WITH_AES_256_GCM_SHA384` + +To enable these ciphers, please set the option `CipherSuites` in your [TLS configuration](https://doc.traefik.io/traefik/https/tls/#cipher-suites) or set the environment variable `GODEBUG=tlsrsakex=1`. + +### Minimum TLS Version + +> By default, the minimum version offered by `crypto/tls` servers is now TLS 1.2 if not specified with config.MinimumVersion, +> matching the behavior of crypto/tls clients. +> This change can be reverted with the `tls10server=1 GODEBUG` setting. +> (https://go.dev/doc/go1.22#crypto/tls) + +To enable TLS 1.0, please set the option `MinVersion` to `VersionTLS10` in your [TLS configuration](https://doc.traefik.io/traefik/https/tls/#cipher-suites) or set the environment variable `GODEBUG=tls10server=1`. diff --git a/docs/content/observability/access-logs.md b/docs/content/observability/access-logs.md index dac71805f..beb114db1 100644 --- a/docs/content/observability/access-logs.md +++ b/docs/content/observability/access-logs.md @@ -26,6 +26,26 @@ accessLog: {} --accesslog=true ``` +### `addInternals` + +_Optional, Default="false"_ + +Enables accessLogs for internal resources. + +```yaml tab="File (YAML)" +accesslog: + addInternals: true +``` + +```toml tab="File (TOML)" +[accesslog] + addInternals = true +``` + +```bash tab="CLI" +--accesslog.addinternals +``` + ### `filePath` By default access logs are written to the standard output. diff --git a/docs/content/observability/metrics/datadog.md b/docs/content/observability/metrics/datadog.md index 00a5d6c9f..134b726df 100644 --- a/docs/content/observability/metrics/datadog.md +++ b/docs/content/observability/metrics/datadog.md @@ -27,6 +27,8 @@ _Required, Default="127.0.0.1:8125"_ Address instructs exporter to send metrics to datadog-agent at this address. +This address can be a Unix Domain Socket (UDS) address with the following form: `unix:///path/to/datadog.socket`. + ```yaml tab="File (YAML)" metrics: datadog: diff --git a/docs/content/observability/metrics/opentelemetry.md b/docs/content/observability/metrics/opentelemetry.md index af0ba7b20..1c762fc2a 100644 --- a/docs/content/observability/metrics/opentelemetry.md +++ b/docs/content/observability/metrics/opentelemetry.md @@ -5,45 +5,25 @@ description: "Traefik supports several metrics backends, including OpenTelemetry # OpenTelemetry -To enable the OpenTelemetry: +To enable the OpenTelemetry metrics: ```yaml tab="File (YAML)" metrics: - openTelemetry: {} + otlp: {} ``` ```toml tab="File (TOML)" [metrics] - [metrics.openTelemetry] + [metrics.otlp] ``` ```bash tab="CLI" ---metrics.openTelemetry=true +--metrics.otlp=true ``` -!!! info "The OpenTelemetry exporter will export metrics to the collector by using HTTP by default, see the [gRPC Section](#grpc-configuration) to use gRPC." +!!! info "Default protocol" -#### `address` - -_Required, Default="localhost:4318", Format="`:`"_ - -Address of the OpenTelemetry Collector to send metrics to. - -```yaml tab="File (YAML)" -metrics: - openTelemetry: - address: localhost:4318 -``` - -```toml tab="File (TOML)" -[metrics] - [metrics.openTelemetry] - address = "localhost:4318" -``` - -```bash tab="CLI" ---metrics.openTelemetry.address=localhost:4318 -``` + The OpenTelemetry exporter will export metrics to the collector using HTTP by default to https://localhost:4318/v1/metrics, see the [gRPC Section](#grpc-configuration) to use gRPC. #### `addEntryPointsLabels` @@ -53,18 +33,18 @@ Enable metrics on entry points. ```yaml tab="File (YAML)" metrics: - openTelemetry: + otlp: addEntryPointsLabels: true ``` ```toml tab="File (TOML)" [metrics] - [metrics.openTelemetry] + [metrics.otlp] addEntryPointsLabels = true ``` ```bash tab="CLI" ---metrics.openTelemetry.addEntryPointsLabels=true +--metrics.otlp.addEntryPointsLabels=true ``` #### `addRoutersLabels` @@ -75,18 +55,18 @@ Enable metrics on routers. ```yaml tab="File (YAML)" metrics: - openTelemetry: + otlp: addRoutersLabels: true ``` ```toml tab="File (TOML)" [metrics] - [metrics.openTelemetry] + [metrics.otlp] addRoutersLabels = true ``` ```bash tab="CLI" ---metrics.openTelemetry.addRoutersLabels=true +--metrics.otlp.addRoutersLabels=true ``` #### `addServicesLabels` @@ -97,18 +77,18 @@ Enable metrics on services. ```yaml tab="File (YAML)" metrics: - openTelemetry: + otlp: addServicesLabels: true ``` ```toml tab="File (TOML)" [metrics] - [metrics.openTelemetry] + [metrics.otlp] addServicesLabels = true ``` ```bash tab="CLI" ---metrics.openTelemetry.addServicesLabels=true +--metrics.otlp.addServicesLabels=true ``` #### `explicitBoundaries` @@ -119,7 +99,7 @@ Explicit boundaries for Histogram data points. ```yaml tab="File (YAML)" metrics: - openTelemetry: + otlp: explicitBoundaries: - 0.1 - 0.3 @@ -129,59 +109,12 @@ metrics: ```toml tab="File (TOML)" [metrics] - [metrics.openTelemetry] + [metrics.otlp] explicitBoundaries = [0.1,0.3,1.2,5.0] ``` ```bash tab="CLI" ---metrics.openTelemetry.explicitBoundaries=0.1,0.3,1.2,5.0 -``` - -#### `headers` - -_Optional, Default={}_ - -Additional headers sent with metrics by the reporter to the OpenTelemetry Collector. - -```yaml tab="File (YAML)" -metrics: - openTelemetry: - headers: - foo: bar - baz: buz -``` - -```toml tab="File (TOML)" -[metrics] - [metrics.openTelemetry.headers] - foo = "bar" - baz = "buz" -``` - -```bash tab="CLI" ---metrics.openTelemetry.headers.foo=bar --metrics.openTelemetry.headers.baz=buz -``` - -#### `insecure` - -_Optional, Default=false_ - -Allows reporter to send metrics to the OpenTelemetry Collector without using a secured protocol. - -```yaml tab="File (YAML)" -metrics: - openTelemetry: - insecure: true -``` - -```toml tab="File (TOML)" -[metrics] - [metrics.openTelemetry] - insecure = true -``` - -```bash tab="CLI" ---metrics.openTelemetry.insecure=true +--metrics.otlp.explicitBoundaries=0.1,0.3,1.2,5.0 ``` #### `pushInterval` @@ -192,48 +125,95 @@ Interval at which metrics are sent to the OpenTelemetry Collector. ```yaml tab="File (YAML)" metrics: - openTelemetry: + otlp: pushInterval: 10s ``` ```toml tab="File (TOML)" [metrics] - [metrics.openTelemetry] + [metrics.otlp] pushInterval = "10s" ``` ```bash tab="CLI" ---metrics.openTelemetry.pushInterval=10s +--metrics.otlp.pushInterval=10s ``` -#### `path` +### HTTP configuration -_Required, Default="/v1/metrics"_ +_Optional_ -Allows to override the default URL path used for sending metrics. -This option has no effect when using gRPC transport. +This instructs the exporter to send the metrics to the OpenTelemetry Collector using HTTP. ```yaml tab="File (YAML)" metrics: - openTelemetry: - path: /foo/v1/metrics + otlp: + http: {} ``` ```toml tab="File (TOML)" [metrics] - [metrics.openTelemetry] - path = "/foo/v1/metrics" + [metrics.otlp.http] ``` ```bash tab="CLI" ---metrics.openTelemetry.path=/foo/v1/metrics +--metrics.otlp.http=true +``` + +#### `endpoint` + +_Required, Default="http://localhost:4318/v1/metrics", Format="`://:`"_ + +URL of the OpenTelemetry Collector to send metrics to. + +```yaml tab="File (YAML)" +metrics: + otlp: + http: + endpoint: http://localhost:4318/v1/metrics +``` + +```toml tab="File (TOML)" +[metrics] + [metrics.otlp.http] + endpoint = "http://localhost:4318/v1/metrics" +``` + +```bash tab="CLI" +--metrics.otlp.http.endpoint=http://localhost:4318/v1/metrics +``` + +#### `headers` + +_Optional, Default={}_ + +Additional headers sent with metrics by the exporter to the OpenTelemetry Collector. + +```yaml tab="File (YAML)" +metrics: + otlp: + http: + headers: + foo: bar + baz: buz +``` + +```toml tab="File (TOML)" +[metrics] + [metrics.otlp.http.headers] + foo = "bar" + baz = "buz" +``` + +```bash tab="CLI" +--metrics.otlp.http.headers.foo=bar --metrics.otlp.http.headers.baz=buz ``` #### `tls` _Optional_ -Defines the TLS configuration used by the reporter to send metrics to the OpenTelemetry Collector. +Defines the Client TLS configuration used by the exporter to send metrics to the OpenTelemetry Collector. ##### `ca` @@ -244,18 +224,19 @@ it defaults to the system bundle. ```yaml tab="File (YAML)" metrics: - openTelemetry: - tls: - ca: path/to/ca.crt + otlp: + http: + tls: + ca: path/to/ca.crt ``` ```toml tab="File (TOML)" -[metrics.openTelemetry.tls] +[metrics.otlp.http.tls] ca = "path/to/ca.crt" ``` ```bash tab="CLI" ---metrics.openTelemetry.tls.ca=path/to/ca.crt +--metrics.otlp.http.tls.ca=path/to/ca.crt ``` ##### `cert` @@ -267,21 +248,22 @@ When using this option, setting the `key` option is required. ```yaml tab="File (YAML)" metrics: - openTelemetry: - tls: - cert: path/to/foo.cert - key: path/to/foo.key + otlp: + http: + tls: + cert: path/to/foo.cert + key: path/to/foo.key ``` ```toml tab="File (TOML)" -[metrics.openTelemetry.tls] +[metrics.otlp.http.tls] cert = "path/to/foo.cert" key = "path/to/foo.key" ``` ```bash tab="CLI" ---metrics.openTelemetry.tls.cert=path/to/foo.cert ---metrics.openTelemetry.tls.key=path/to/foo.key +--metrics.otlp.http.tls.cert=path/to/foo.cert +--metrics.otlp.http.tls.key=path/to/foo.key ``` ##### `key` @@ -293,21 +275,22 @@ When using this option, setting the `cert` option is required. ```yaml tab="File (YAML)" metrics: - openTelemetry: - tls: - cert: path/to/foo.cert - key: path/to/foo.key + otlp: + http: + tls: + cert: path/to/foo.cert + key: path/to/foo.key ``` ```toml tab="File (TOML)" -[metrics.openTelemetry.tls] +[metrics.otlp.http.tls] cert = "path/to/foo.cert" key = "path/to/foo.key" ``` ```bash tab="CLI" ---metrics.openTelemetry.tls.cert=path/to/foo.cert ---metrics.openTelemetry.tls.key=path/to/foo.key +--metrics.otlp.http.tls.cert=path/to/foo.cert +--metrics.otlp.http.tls.key=path/to/foo.key ``` ##### `insecureSkipVerify` @@ -319,35 +302,218 @@ the TLS connection to the OpenTelemetry Collector accepts any certificate presen ```yaml tab="File (YAML)" metrics: - openTelemetry: - tls: - insecureSkipVerify: true + otlp: + http: + tls: + insecureSkipVerify: true ``` ```toml tab="File (TOML)" -[metrics.openTelemetry.tls] +[metrics.otlp.http.tls] insecureSkipVerify = true ``` ```bash tab="CLI" ---metrics.openTelemetry.tls.insecureSkipVerify=true +--metrics.otlp.http.tls.insecureSkipVerify=true ``` -#### gRPC configuration +### gRPC configuration -This instructs the reporter to send metrics to the OpenTelemetry Collector using gRPC. +_Optional_ + +This instructs the exporter to send metrics to the OpenTelemetry Collector using gRPC. ```yaml tab="File (YAML)" metrics: - openTelemetry: + otlp: grpc: {} ``` ```toml tab="File (TOML)" [metrics] - [metrics.openTelemetry.grpc] + [metrics.otlp.grpc] ``` ```bash tab="CLI" ---metrics.openTelemetry.grpc=true +--metrics.otlp.grpc=true +``` + +#### `endpoint` + +_Required, Default="localhost:4317", Format="`:`"_ + +Address of the OpenTelemetry Collector to send metrics to. + +```yaml tab="File (YAML)" +metrics: + otlp: + grpc: + endpoint: localhost:4317 +``` + +```toml tab="File (TOML)" +[metrics] + [metrics.otlp.grpc] + endpoint = "localhost:4317" +``` + +```bash tab="CLI" +--metrics.otlp.grpc.endpoint=localhost:4317 +``` + +#### `insecure` + +_Optional, Default=false_ + +Allows exporter to send metrics to the OpenTelemetry Collector without using a secured protocol. + +```yaml tab="File (YAML)" +metrics: + otlp: + grpc: + insecure: true +``` + +```toml tab="File (TOML)" +[metrics] + [metrics.otlp.grpc] + insecure = true +``` + +```bash tab="CLI" +--metrics.otlp.grpc.insecure=true +``` + +#### `headers` + +_Optional, Default={}_ + +Additional headers sent with metrics by the exporter to the OpenTelemetry Collector. + +```yaml tab="File (YAML)" +metrics: + otlp: + grpc: + headers: + foo: bar + baz: buz +``` + +```toml tab="File (TOML)" +[metrics] + [metrics.otlp.grpc.headers] + foo = "bar" + baz = "buz" +``` + +```bash tab="CLI" +--metrics.otlp.grpc.headers.foo=bar --metrics.otlp.grpc.headers.baz=buz +``` + +#### `tls` + +_Optional_ + +Defines the Client TLS configuration used by the exporter to send metrics to the OpenTelemetry Collector. + +##### `ca` + +_Optional_ + +`ca` is the path to the certificate authority used for the secure connection to the OpenTelemetry Collector, +it defaults to the system bundle. + +```yaml tab="File (YAML)" +metrics: + otlp: + grpc: + tls: + ca: path/to/ca.crt +``` + +```toml tab="File (TOML)" +[metrics.otlp.grpc.tls] + ca = "path/to/ca.crt" +``` + +```bash tab="CLI" +--metrics.otlp.grpc.tls.ca=path/to/ca.crt +``` + +##### `cert` + +_Optional_ + +`cert` is the path to the public certificate used for the secure connection to the OpenTelemetry Collector. +When using this option, setting the `key` option is required. + +```yaml tab="File (YAML)" +metrics: + otlp: + grpc: + tls: + cert: path/to/foo.cert + key: path/to/foo.key +``` + +```toml tab="File (TOML)" +[metrics.otlp.grpc.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```bash tab="CLI" +--metrics.otlp.grpc.tls.cert=path/to/foo.cert +--metrics.otlp.grpc.tls.key=path/to/foo.key +``` + +##### `key` + +_Optional_ + +`key` is the path to the private key used for the secure connection to the OpenTelemetry Collector. +When using this option, setting the `cert` option is required. + +```yaml tab="File (YAML)" +metrics: + otlp: + grpc: + tls: + cert: path/to/foo.cert + key: path/to/foo.key +``` + +```toml tab="File (TOML)" +[metrics.otlp.grpc.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```bash tab="CLI" +--metrics.otlp.grpc.tls.cert=path/to/foo.cert +--metrics.otlp.grpc.tls.key=path/to/foo.key +``` + +##### `insecureSkipVerify` + +_Optional, Default=false_ + +If `insecureSkipVerify` is `true`, +the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers. + +```yaml tab="File (YAML)" +metrics: + otlp: + grpc: + tls: + insecureSkipVerify: true +``` + +```toml tab="File (TOML)" +[metrics.otlp.grpc.tls] + insecureSkipVerify = true +``` + +```bash tab="CLI" +--metrics.otlp.grpc.tls.insecureSkipVerify=true ``` diff --git a/docs/content/observability/metrics/overview.md b/docs/content/observability/metrics/overview.md index 1d6f04b15..f968e77e6 100644 --- a/docs/content/observability/metrics/overview.md +++ b/docs/content/observability/metrics/overview.md @@ -14,6 +14,28 @@ Traefik supports these metrics backends: Traefik Proxy hosts an official Grafana dashboard for both [on-premises](https://grafana.com/grafana/dashboards/17346) and [Kubernetes](https://grafana.com/grafana/dashboards/17347) deployments. +## Common Options + +### `addInternals` + +_Optional, Default="false"_ + +Enables metrics for internal resources. + +```yaml tab="File (YAML)" +metrics: + addInternals: true +``` + +```toml tab="File (TOML)" +[metrics] + addInternals = true +``` + +```bash tab="CLI" +--metrics.addinternals +``` + ## Global Metrics | Metric | Type | [Labels](#labels) | Description | diff --git a/docs/content/observability/overview.md b/docs/content/observability/overview.md new file mode 100644 index 000000000..2de429b4e --- /dev/null +++ b/docs/content/observability/overview.md @@ -0,0 +1,42 @@ +--- +title: "Traefik Observability Overview" +description: "Traefik provides Logs, Access Logs, Metrics and Tracing. Read the full documentation to get started." +--- + +# Overview + +Traefik's Observability system +{: .subtitle } + +## Logs + +Traefik logs informs about everything that happens within Traefik (startup, configuration, events, shutdown, and so on). + +Read the [Logs documentation](./logs.md) to learn how to configure it. + +## Access Logs + +Access logs are a key part of observability in Traefik. + +They are providing valuable insights about incoming traffic, and allow to monitor it. +The access logs record detailed information about each request received by Traefik, +including the source IP address, requested URL, response status code, and more. + +Read the [Access Logs documentation](./access-logs.md) to learn how to configure it. + +## Metrics + +Traefik offers a metrics feature that provides valuable insights about the performance and usage. +These metrics include the number of requests received, the requests duration, and more. + +Traefik supports these metrics systems: Prometheus, Datadog, InfluxDB 2.X, and StatsD. + +Read the [Metrics documentation](./metrics/overview.md) to learn how to configure it. + +## Tracing + +The Traefik tracing system allows developers to gain deep visibility into the flow of requests through their infrastructure. + +Traefik supports these tracing with OpenTelemetry. + +Read the [Tracing documentation](./tracing/overview.md) to learn how to configure it. diff --git a/docs/content/observability/tracing/opentelemetry.md b/docs/content/observability/tracing/opentelemetry.md index 52b37a0a5..0ea6bd19f 100644 --- a/docs/content/observability/tracing/opentelemetry.md +++ b/docs/content/observability/tracing/opentelemetry.md @@ -21,19 +21,20 @@ tracing: --tracing.otlp=true ``` -!!! info "The OpenTelemetry trace reporter will export traces to the collector using HTTP by default (http://localhost:4318/v1/traces), -see the [gRPC Section](#grpc-configuration) to use gRPC." +!!! info "Default protocol" + + The OpenTelemetry trace exporter will export traces to the collector using HTTP by default to https://localhost:4318/v1/traces, see the [gRPC Section](#grpc-configuration) to use gRPC. !!! info "Trace sampling" - By default, the OpenTelemetry trace reporter will sample 100% of traces. + By default, the OpenTelemetry trace exporter will sample 100% of traces. See [OpenTelemetry's SDK configuration](https://opentelemetry.io/docs/reference/specification/sdk-environment-variables/#general-sdk-configuration) to customize the sampling strategy. ### HTTP configuration _Optional_ -This instructs the reporter to send spans to the OpenTelemetry Collector using HTTP. +This instructs the exporter to send spans to the OpenTelemetry Collector using HTTP. ```yaml tab="File (YAML)" tracing: @@ -73,11 +74,37 @@ tracing: --tracing.otlp.http.endpoint=http://localhost:4318/v1/traces ``` +#### `headers` + +_Optional, Default={}_ + +Additional headers sent with traces by the exporter to the OpenTelemetry Collector. + +```yaml tab="File (YAML)" +tracing: + otlp: + http: + headers: + foo: bar + baz: buz +``` + +```toml tab="File (TOML)" +[tracing] + [tracing.otlp.http.headers] + foo = "bar" + baz = "buz" +``` + +```bash tab="CLI" +--tracing.otlp.http.headers.foo=bar --tracing.otlp.http.headers.baz=buz +``` + #### `tls` _Optional_ -Defines the Client TLS configuration used by the reporter to send spans to the OpenTelemetry Collector. +Defines the Client TLS configuration used by the exporter to send spans to the OpenTelemetry Collector. ##### `ca` @@ -181,11 +208,11 @@ tracing: --tracing.otlp.http.tls.insecureSkipVerify=true ``` -#### gRPC configuration +### gRPC configuration _Optional_ -This instructs the reporter to send spans to the OpenTelemetry Collector using gRPC. +This instructs the exporter to send spans to the OpenTelemetry Collector using gRPC. ```yaml tab="File (YAML)" tracing: @@ -228,7 +255,7 @@ tracing: _Optional, Default=false_ -Allows reporter to send spans to the OpenTelemetry Collector without using a secured protocol. +Allows exporter to send spans to the OpenTelemetry Collector without using a secured protocol. ```yaml tab="File (YAML)" tracing: @@ -247,11 +274,37 @@ tracing: --tracing.otlp.grpc.insecure=true ``` +#### `headers` + +_Optional, Default={}_ + +Additional headers sent with traces by the exporter to the OpenTelemetry Collector. + +```yaml tab="File (YAML)" +tracing: + otlp: + grpc: + headers: + foo: bar + baz: buz +``` + +```toml tab="File (TOML)" +[tracing] + [tracing.otlp.grpc.headers] + foo = "bar" + baz = "buz" +``` + +```bash tab="CLI" +--tracing.otlp.grpc.headers.foo=bar --tracing.otlp.grpc.headers.baz=buz +``` + #### `tls` _Optional_ -Defines the Client TLS configuration used by the reporter to send spans to the OpenTelemetry Collector. +Defines the Client TLS configuration used by the exporter to send spans to the OpenTelemetry Collector. ##### `ca` diff --git a/docs/content/observability/tracing/overview.md b/docs/content/observability/tracing/overview.md index 18b8c4f3e..901377150 100644 --- a/docs/content/observability/tracing/overview.md +++ b/docs/content/observability/tracing/overview.md @@ -14,10 +14,8 @@ Traefik uses [OpenTelemetry](https://opentelemetry.io/ "Link to website of OTel" Please check our dedicated [OTel docs](./opentelemetry.md) to learn more. - ## Configuration - To enable the tracing: ```yaml tab="File (YAML)" @@ -34,6 +32,26 @@ tracing: {} ### Common Options +#### `addInternals` + +_Optional, Default="false"_ + +Enables tracing for internal resources. + +```yaml tab="File (YAML)" +tracing: + addInternals: true +``` + +```toml tab="File (TOML)" +[tracing] + addInternals = true +``` + +```bash tab="CLI" +--tracing.addinternals +``` + #### `serviceName` _Required, Default="traefik"_ @@ -74,30 +92,6 @@ tracing: --tracing.sampleRate=0.2 ``` -#### `headers` - -_Optional, Default={}_ - -Defines additional headers to be sent with the span's payload. - -```yaml tab="File (YAML)" -tracing: - headers: - foo: bar - baz: buz -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.headers] - foo = "bar" - baz = "buz" -``` - -```bash tab="CLI" ---tracing.headers.foo=bar --tracing.headers.baz=buz -``` - #### `globalAttributes` _Optional, Default=empty_ diff --git a/docs/content/reference/dynamic-configuration/docker-labels.yml b/docs/content/reference/dynamic-configuration/docker-labels.yml index 93a785ff4..7f3fec5ad 100644 --- a/docs/content/reference/dynamic-configuration/docker-labels.yml +++ b/docs/content/reference/dynamic-configuration/docker-labels.yml @@ -16,11 +16,13 @@ - "traefik.http.middlewares.middleware05.circuitbreaker.expression=foobar" - "traefik.http.middlewares.middleware05.circuitbreaker.fallbackduration=42s" - "traefik.http.middlewares.middleware05.circuitbreaker.recoveryduration=42s" +- "traefik.http.middlewares.middleware05.circuitbreaker.responsecode=42" - "traefik.http.middlewares.middleware06.compress=true" - "traefik.http.middlewares.middleware06.compress.excludedcontenttypes=foobar, foobar" - "traefik.http.middlewares.middleware06.compress.includedcontenttypes=foobar, foobar" - "traefik.http.middlewares.middleware06.compress.minresponsebodybytes=42" - "traefik.http.middlewares.middleware07.contenttype=true" +- "traefik.http.middlewares.middleware07.contenttype.autodetect=true" - "traefik.http.middlewares.middleware08.digestauth.headerfield=foobar" - "traefik.http.middlewares.middleware08.digestauth.realm=foobar" - "traefik.http.middlewares.middleware08.digestauth.removeheader=true" @@ -35,6 +37,7 @@ - "traefik.http.middlewares.middleware10.forwardauth.authresponseheaders=foobar, foobar" - "traefik.http.middlewares.middleware10.forwardauth.authresponseheadersregex=foobar" - "traefik.http.middlewares.middleware10.forwardauth.tls.ca=foobar" +- "traefik.http.middlewares.middleware10.forwardauth.tls.caoptional=true" - "traefik.http.middlewares.middleware10.forwardauth.tls.cert=foobar" - "traefik.http.middlewares.middleware10.forwardauth.tls.insecureskipverify=true" - "traefik.http.middlewares.middleware10.forwardauth.tls.key=foobar" @@ -58,6 +61,7 @@ - "traefik.http.middlewares.middleware12.headers.customrequestheaders.name1=foobar" - "traefik.http.middlewares.middleware12.headers.customresponseheaders.name0=foobar" - "traefik.http.middlewares.middleware12.headers.customresponseheaders.name1=foobar" +- "traefik.http.middlewares.middleware12.headers.featurepolicy=foobar" - "traefik.http.middlewares.middleware12.headers.forcestsheader=true" - "traefik.http.middlewares.middleware12.headers.framedeny=true" - "traefik.http.middlewares.middleware12.headers.hostsproxyheaders=foobar, foobar" @@ -65,8 +69,12 @@ - "traefik.http.middlewares.middleware12.headers.permissionspolicy=foobar" - "traefik.http.middlewares.middleware12.headers.publickey=foobar" - "traefik.http.middlewares.middleware12.headers.referrerpolicy=foobar" +- "traefik.http.middlewares.middleware12.headers.sslforcehost=true" +- "traefik.http.middlewares.middleware12.headers.sslhost=foobar" - "traefik.http.middlewares.middleware12.headers.sslproxyheaders.name0=foobar" - "traefik.http.middlewares.middleware12.headers.sslproxyheaders.name1=foobar" +- "traefik.http.middlewares.middleware12.headers.sslredirect=true" +- "traefik.http.middlewares.middleware12.headers.ssltemporaryredirect=true" - "traefik.http.middlewares.middleware12.headers.stsincludesubdomains=true" - "traefik.http.middlewares.middleware12.headers.stspreload=true" - "traefik.http.middlewares.middleware12.headers.stsseconds=42" @@ -126,12 +134,14 @@ - "traefik.http.middlewares.middleware22.replacepathregex.replacement=foobar" - "traefik.http.middlewares.middleware23.retry.attempts=42" - "traefik.http.middlewares.middleware23.retry.initialinterval=42s" +- "traefik.http.middlewares.middleware24.stripprefix.forceslash=true" - "traefik.http.middlewares.middleware24.stripprefix.prefixes=foobar, foobar" - "traefik.http.middlewares.middleware25.stripprefixregex.regex=foobar, foobar" - "traefik.http.routers.router0.entrypoints=foobar, foobar" - "traefik.http.routers.router0.middlewares=foobar, foobar" - "traefik.http.routers.router0.priority=42" - "traefik.http.routers.router0.rule=foobar" +- "traefik.http.routers.router0.rulesyntax=foobar" - "traefik.http.routers.router0.service=foobar" - "traefik.http.routers.router0.tls=true" - "traefik.http.routers.router0.tls.certresolver=foobar" @@ -144,6 +154,7 @@ - "traefik.http.routers.router1.middlewares=foobar, foobar" - "traefik.http.routers.router1.priority=42" - "traefik.http.routers.router1.rule=foobar" +- "traefik.http.routers.router1.rulesyntax=foobar" - "traefik.http.routers.router1.service=foobar" - "traefik.http.routers.router1.tls=true" - "traefik.http.routers.router1.tls.certresolver=foobar" @@ -176,6 +187,7 @@ - "traefik.http.services.service02.loadbalancer.sticky.cookie.secure=true" - "traefik.http.services.service02.loadbalancer.server.port=foobar" - "traefik.http.services.service02.loadbalancer.server.scheme=foobar" +- "traefik.http.services.service02.loadbalancer.server.weight=42" - "traefik.tcp.middlewares.tcpmiddleware01.ipallowlist.sourcerange=foobar, foobar" - "traefik.tcp.middlewares.tcpmiddleware02.ipwhitelist.sourcerange=foobar, foobar" - "traefik.tcp.middlewares.tcpmiddleware03.inflightconn.amount=42" @@ -183,6 +195,7 @@ - "traefik.tcp.routers.tcprouter0.middlewares=foobar, foobar" - "traefik.tcp.routers.tcprouter0.priority=42" - "traefik.tcp.routers.tcprouter0.rule=foobar" +- "traefik.tcp.routers.tcprouter0.rulesyntax=foobar" - "traefik.tcp.routers.tcprouter0.service=foobar" - "traefik.tcp.routers.tcprouter0.tls=true" - "traefik.tcp.routers.tcprouter0.tls.certresolver=foobar" @@ -196,6 +209,7 @@ - "traefik.tcp.routers.tcprouter1.middlewares=foobar, foobar" - "traefik.tcp.routers.tcprouter1.priority=42" - "traefik.tcp.routers.tcprouter1.rule=foobar" +- "traefik.tcp.routers.tcprouter1.rulesyntax=foobar" - "traefik.tcp.routers.tcprouter1.service=foobar" - "traefik.tcp.routers.tcprouter1.tls=true" - "traefik.tcp.routers.tcprouter1.tls.certresolver=foobar" @@ -208,6 +222,7 @@ - "traefik.tcp.services.tcpservice01.loadbalancer.proxyprotocol=true" - "traefik.tcp.services.tcpservice01.loadbalancer.proxyprotocol.version=42" - "traefik.tcp.services.tcpservice01.loadbalancer.serverstransport=foobar" +- "traefik.tcp.services.tcpservice01.loadbalancer.terminationdelay=42" - "traefik.tcp.services.tcpservice01.loadbalancer.server.port=foobar" - "traefik.tcp.services.tcpservice01.loadbalancer.server.tls=true" - "traefik.udp.routers.udprouter0.entrypoints=foobar, foobar" diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml index a8264d2ed..d3cf0eaf2 100644 --- a/docs/content/reference/dynamic-configuration/file.toml +++ b/docs/content/reference/dynamic-configuration/file.toml @@ -7,6 +7,7 @@ middlewares = ["foobar", "foobar"] service = "foobar" rule = "foobar" + ruleSyntax = "foobar" priority = 42 [http.routers.Router0.tls] options = "foobar" @@ -24,6 +25,7 @@ middlewares = ["foobar", "foobar"] service = "foobar" rule = "foobar" + ruleSyntax = "foobar" priority = 42 [http.routers.Router1.tls] options = "foobar" @@ -56,9 +58,11 @@ [[http.services.Service02.loadBalancer.servers]] url = "foobar" + weight = 42 [[http.services.Service02.loadBalancer.servers]] url = "foobar" + weight = 42 [http.services.Service02.loadBalancer.healthCheck] scheme = "foobar" mode = "foobar" @@ -133,6 +137,7 @@ checkPeriod = "42s" fallbackDuration = "42s" recoveryDuration = "42s" + responseCode = 42 [http.middlewares.Middleware06] [http.middlewares.Middleware06.compress] excludedContentTypes = ["foobar", "foobar"] @@ -140,6 +145,7 @@ minResponseBodyBytes = 42 [http.middlewares.Middleware07] [http.middlewares.Middleware07.contentType] + autoDetect = true [http.middlewares.Middleware08] [http.middlewares.Middleware08.digestAuth] users = ["foobar", "foobar"] @@ -165,6 +171,7 @@ cert = "foobar" key = "foobar" insecureSkipVerify = true + caOptional = true [http.middlewares.Middleware11] [http.middlewares.Middleware11.grpcWeb] allowOrigins = ["foobar", "foobar"] @@ -194,6 +201,11 @@ referrerPolicy = "foobar" permissionsPolicy = "foobar" isDevelopment = true + featurePolicy = "foobar" + sslRedirect = true + sslTemporaryRedirect = true + sslHost = "foobar" + sslForceHost = true [http.middlewares.Middleware12.headers.customRequestHeaders] name0 = "foobar" name1 = "foobar" @@ -293,6 +305,7 @@ [http.middlewares.Middleware24] [http.middlewares.Middleware24.stripPrefix] prefixes = ["foobar", "foobar"] + forceSlash = true [http.middlewares.Middleware25] [http.middlewares.Middleware25.stripPrefixRegex] regex = ["foobar", "foobar"] @@ -353,6 +366,7 @@ middlewares = ["foobar", "foobar"] service = "foobar" rule = "foobar" + ruleSyntax = "foobar" priority = 42 [tcp.routers.TCPRouter0.tls] passthrough = true @@ -371,6 +385,7 @@ middlewares = ["foobar", "foobar"] service = "foobar" rule = "foobar" + ruleSyntax = "foobar" priority = 42 [tcp.routers.TCPRouter1.tls] passthrough = true @@ -388,6 +403,7 @@ [tcp.services.TCPService01] [tcp.services.TCPService01.loadBalancer] serversTransport = "foobar" + terminationDelay = 42 [tcp.services.TCPService01.loadBalancer.proxyProtocol] version = 42 @@ -507,6 +523,7 @@ curvePreferences = ["foobar", "foobar"] sniStrict = true alpnProtocols = ["foobar", "foobar"] + preferServerCipherSuites = true [tls.options.Options0.clientAuth] caFiles = ["foobar", "foobar"] clientAuthType = "foobar" @@ -517,6 +534,7 @@ curvePreferences = ["foobar", "foobar"] sniStrict = true alpnProtocols = ["foobar", "foobar"] + preferServerCipherSuites = true [tls.options.Options1.clientAuth] caFiles = ["foobar", "foobar"] clientAuthType = "foobar" diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml index 7ae72ad7d..fdba1c332 100644 --- a/docs/content/reference/dynamic-configuration/file.yaml +++ b/docs/content/reference/dynamic-configuration/file.yaml @@ -11,6 +11,7 @@ http: - foobar service: foobar rule: foobar + ruleSyntax: foobar priority: 42 tls: options: foobar @@ -33,6 +34,7 @@ http: - foobar service: foobar rule: foobar + ruleSyntax: foobar priority: 42 tls: options: foobar @@ -63,7 +65,9 @@ http: maxAge: 42 servers: - url: foobar + weight: 42 - url: foobar + weight: 42 healthCheck: scheme: foobar mode: foobar @@ -138,6 +142,7 @@ http: checkPeriod: 42s fallbackDuration: 42s recoveryDuration: 42s + responseCode: 42 Middleware06: compress: excludedContentTypes: @@ -148,7 +153,8 @@ http: - foobar minResponseBodyBytes: 42 Middleware07: - contentType: {} + contentType: + autoDetect: true Middleware08: digestAuth: users: @@ -173,6 +179,7 @@ http: cert: foobar key: foobar insecureSkipVerify: true + caOptional: true trustForwardHeader: true authResponseHeaders: - foobar @@ -238,6 +245,11 @@ http: referrerPolicy: foobar permissionsPolicy: foobar isDevelopment: true + featurePolicy: foobar + sslRedirect: true + sslTemporaryRedirect: true + sslHost: foobar + sslForceHost: true Middleware13: ipAllowList: sourceRange: @@ -342,6 +354,7 @@ http: prefixes: - foobar - foobar + forceSlash: true Middleware25: stripPrefixRegex: regex: @@ -409,6 +422,7 @@ tcp: - foobar service: foobar rule: foobar + ruleSyntax: foobar priority: 42 tls: passthrough: true @@ -432,6 +446,7 @@ tcp: - foobar service: foobar rule: foobar + ruleSyntax: foobar priority: 42 tls: passthrough: true @@ -457,6 +472,7 @@ tcp: - address: foobar tls: true serversTransport: foobar + terminationDelay: 42 TCPService02: weighted: services: @@ -577,6 +593,7 @@ tls: alpnProtocols: - foobar - foobar + preferServerCipherSuites: true Options1: minVersion: foobar maxVersion: foobar @@ -595,6 +612,7 @@ tls: alpnProtocols: - foobar - foobar + preferServerCipherSuites: true stores: Store0: defaultCertificate: diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml index 5a4c63c3c..a95cf1153 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ingressroutes.traefik.io spec: group: traefik.io @@ -20,14 +20,19 @@ spec: description: IngressRoute is the CRD implementation of a Traefik HTTP Router. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -35,10 +40,11 @@ spec: description: IngressRouteSpec defines the desired state of IngressRoute. properties: entryPoints: - description: 'EntryPoints defines the list of entry point names to - bind to. Entry points have to be configured in the static configuration. + description: |- + EntryPoints defines the list of entry point names to bind to. + Entry points have to be configured in the static configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ - Default: all.' + Default: all. items: type: string type: array @@ -48,17 +54,21 @@ spec: description: Route holds the HTTP route configuration. properties: kind: - description: Kind defines the kind of the route. Rule is the - only supported kind. + description: |- + Kind defines the kind of the route. + Rule is the only supported kind. enum: - Rule type: string match: - description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule' + description: |- + Match defines the router's rule. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule type: string middlewares: - description: 'Middlewares defines the list of references to - Middleware resources. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-middleware' + description: |- + Middlewares defines the list of references to Middleware resources. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-middleware items: description: MiddlewareRef is a reference to a Middleware resource. @@ -76,13 +86,14 @@ spec: type: object type: array priority: - description: 'Priority defines the router''s priority. More - info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority' + description: |- + Priority defines the router's priority. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority type: integer services: - description: Services defines the list of Service. It can contain - any combination of TraefikService and/or reference to a Kubernetes - Service. + description: |- + Services defines the list of Service. + It can contain any combination of TraefikService and/or reference to a Kubernetes Service. items: description: Service defines an upstream HTTP service to proxy traffic to. @@ -94,31 +105,32 @@ spec: - TraefikService type: string name: - description: Name defines the name of the referenced Kubernetes - Service or TraefikService. The differentiation between - the two is specified in the Kind field. + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. type: string namespace: description: Namespace defines the namespace of the referenced Kubernetes Service or TraefikService. type: string nativeLB: - description: NativeLB controls, when creating the load-balancer, - whether the LB's children are directly the pods IPs - or if the only child is the Kubernetes Service clusterIP. - The Kubernetes Service itself does load-balance to the - pods. By default, NativeLB is false. + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. type: boolean passHostHeader: - description: PassHostHeader defines whether the client - Host header is forwarded to the upstream Kubernetes - Service. By default, passHostHeader is true. + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. + By default, passHostHeader is true. type: boolean port: anyOf: - type: integer - type: string - description: Port defines the port of a Kubernetes Service. + description: |- + Port defines the port of a Kubernetes Service. This can be a reference to a named port. x-kubernetes-int-or-string: true responseForwarding: @@ -127,30 +139,29 @@ spec: the client. properties: flushInterval: - description: 'FlushInterval defines the interval, - in milliseconds, in between flushes to the client - while copying the response body. A negative value - means to flush immediately after each write to the - client. This configuration is ignored when ReverseProxy - recognizes a response as a streaming response; for - such responses, writes are flushed to the client - immediately. Default: 100ms' + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; + for such responses, writes are flushed to the client immediately. + Default: 100ms type: string type: object scheme: - description: Scheme defines the scheme to use for the - request to the upstream Kubernetes Service. It defaults - to https when Kubernetes Service port is 443, http otherwise. + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. type: string serversTransport: - description: ServersTransport defines the name of ServersTransport - resource to use. It allows to configure the transport - between Traefik and your servers. Can only be used on - a Kubernetes Service. + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. type: string sticky: - description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -161,17 +172,18 @@ spec: JavaScript. type: boolean maxAge: - description: MaxAge indicates the number of seconds - until the cookie expires. When set to a negative - number, the cookie expires immediately. When - set to zero, the cookie never expires. + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. type: integer name: description: Name defines the Cookie name. type: string sameSite: - description: 'SameSite defines the same site policy. - More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite type: string secure: description: Secure defines whether the cookie @@ -181,36 +193,44 @@ spec: type: object type: object strategy: - description: Strategy defines the load balancing strategy - between the servers. RoundRobin is the only supported - value at the moment. + description: |- + Strategy defines the load balancing strategy between the servers. + RoundRobin is the only supported value at the moment. type: string weight: - description: Weight defines the weight and should only - be specified when Name references a TraefikService object - (and to be precise, one that embeds a Weighted Round - Robin). + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object + (and to be precise, one that embeds a Weighted Round Robin). type: integer required: - name type: object type: array + syntax: + description: |- + Syntax defines the router's rule syntax. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rulesyntax + type: string required: - kind - match type: object type: array tls: - description: 'TLS defines the TLS configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls' + description: |- + TLS defines the TLS configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls properties: certResolver: - description: 'CertResolver defines the name of the certificate - resolver to use. Cert resolvers have to be configured in the - static configuration. More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers' + description: |- + CertResolver defines the name of the certificate resolver to use. + Cert resolvers have to be configured in the static configuration. + More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers type: string domains: - description: 'Domains defines the list of domains that will be - used to issue certificates. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains' + description: |- + Domains defines the list of domains that will be used to issue certificates. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains items: description: Domain holds a domain name with SANs. properties: @@ -226,17 +246,20 @@ spec: type: object type: array options: - description: 'Options defines the reference to a TLSOption, that - specifies the parameters of the TLS connection. If not defined, - the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options' + description: |- + Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. + If not defined, the `default` TLSOption is used. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options properties: name: - description: 'Name defines the name of the referenced TLSOption. - More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption' + description: |- + Name defines the name of the referenced TLSOption. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption type: string namespace: - description: 'Namespace defines the namespace of the referenced - TLSOption. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption' + description: |- + Namespace defines the namespace of the referenced TLSOption. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption type: string required: - name @@ -246,17 +269,19 @@ spec: Secret to specify the certificate details. type: string store: - description: Store defines the reference to the TLSStore, that - will be used to store certificates. Please note that only `default` - TLSStore can be used. + description: |- + Store defines the reference to the TLSStore, that will be used to store certificates. + Please note that only `default` TLSStore can be used. properties: name: - description: 'Name defines the name of the referenced TLSStore. - More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore' + description: |- + Name defines the name of the referenced TLSStore. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore type: string namespace: - description: 'Namespace defines the namespace of the referenced - TLSStore. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore' + description: |- + Namespace defines the namespace of the referenced TLSStore. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore type: string required: - name @@ -276,7 +301,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ingressroutetcps.traefik.io spec: group: traefik.io @@ -293,14 +318,19 @@ spec: description: IngressRouteTCP is the CRD implementation of a Traefik TCP Router. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -308,10 +338,11 @@ spec: description: IngressRouteTCPSpec defines the desired state of IngressRouteTCP. properties: entryPoints: - description: 'EntryPoints defines the list of entry point names to - bind to. Entry points have to be configured in the static configuration. + description: |- + EntryPoints defines the list of entry point names to bind to. + Entry points have to be configured in the static configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ - Default: all.' + Default: all. items: type: string type: array @@ -321,7 +352,9 @@ spec: description: RouteTCP holds the TCP route configuration. properties: match: - description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule_1' + description: |- + Match defines the router's rule. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule_1 type: string middlewares: description: Middlewares defines the list of references to MiddlewareTCP @@ -343,8 +376,9 @@ spec: type: object type: array priority: - description: 'Priority defines the router''s priority. More - info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority_1' + description: |- + Priority defines the router's priority. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority_1 type: integer services: description: Services defines the list of TCP services. @@ -361,22 +395,24 @@ spec: Kubernetes Service. type: string nativeLB: - description: NativeLB controls, when creating the load-balancer, - whether the LB's children are directly the pods IPs - or if the only child is the Kubernetes Service clusterIP. - The Kubernetes Service itself does load-balance to the - pods. By default, NativeLB is false. + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. type: boolean port: anyOf: - type: integer - type: string - description: Port defines the port of a Kubernetes Service. + description: |- + Port defines the port of a Kubernetes Service. This can be a reference to a named port. x-kubernetes-int-or-string: true proxyProtocol: - description: 'ProxyProtocol defines the PROXY protocol - configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/services/#proxy-protocol' + description: |- + ProxyProtocol defines the PROXY protocol configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#proxy-protocol properties: version: description: Version defines the PROXY Protocol version @@ -384,11 +420,20 @@ spec: type: integer type: object serversTransport: - description: ServersTransport defines the name of ServersTransportTCP - resource to use. It allows to configure the transport - between Traefik and your servers. Can only be used on - a Kubernetes Service. + description: |- + ServersTransport defines the name of ServersTransportTCP resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. type: string + terminationDelay: + description: |- + TerminationDelay defines the deadline that the proxy sets, after one of its connected peers indicates + it has closed the writing capability of its connection, to close the reading capability as well, + hence fully terminating the connection. + It is a duration in milliseconds, defaulting to 100. + A negative value means an infinite deadline (i.e. the reading capability is never closed). + Deprecated: TerminationDelay is not supported APIVersion traefik.io/v1, please use ServersTransport to configure the TerminationDelay instead. + type: integer tls: description: TLS determines whether to use TLS when dialing with the backend. @@ -402,22 +447,30 @@ spec: - port type: object type: array + syntax: + description: |- + Syntax defines the router's rule syntax. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rulesyntax_1 + type: string required: - match type: object type: array tls: - description: 'TLS defines the TLS configuration on a layer 4 / TCP - Route. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls_1' + description: |- + TLS defines the TLS configuration on a layer 4 / TCP Route. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls_1 properties: certResolver: - description: 'CertResolver defines the name of the certificate - resolver to use. Cert resolvers have to be configured in the - static configuration. More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers' + description: |- + CertResolver defines the name of the certificate resolver to use. + Cert resolvers have to be configured in the static configuration. + More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers type: string domains: - description: 'Domains defines the list of domains that will be - used to issue certificates. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains' + description: |- + Domains defines the list of domains that will be used to issue certificates. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains items: description: Domain holds a domain name with SANs. properties: @@ -433,9 +486,10 @@ spec: type: object type: array options: - description: 'Options defines the reference to a TLSOption, that - specifies the parameters of the TLS connection. If not defined, - the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options' + description: |- + Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. + If not defined, the `default` TLSOption is used. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options properties: name: description: Name defines the name of the referenced Traefik @@ -457,9 +511,9 @@ spec: Secret to specify the certificate details. type: string store: - description: Store defines the reference to the TLSStore, that - will be used to store certificates. Please note that only `default` - TLSStore can be used. + description: |- + Store defines the reference to the TLSStore, that will be used to store certificates. + Please note that only `default` TLSStore can be used. properties: name: description: Name defines the name of the referenced Traefik @@ -487,7 +541,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ingressrouteudps.traefik.io spec: group: traefik.io @@ -504,14 +558,19 @@ spec: description: IngressRouteUDP is a CRD implementation of a Traefik UDP Router. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -519,10 +578,11 @@ spec: description: IngressRouteUDPSpec defines the desired state of a IngressRouteUDP. properties: entryPoints: - description: 'EntryPoints defines the list of entry point names to - bind to. Entry points have to be configured in the static configuration. + description: |- + EntryPoints defines the list of entry point names to bind to. + Entry points have to be configured in the static configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ - Default: all.' + Default: all. items: type: string type: array @@ -546,17 +606,18 @@ spec: Kubernetes Service. type: string nativeLB: - description: NativeLB controls, when creating the load-balancer, - whether the LB's children are directly the pods IPs - or if the only child is the Kubernetes Service clusterIP. - The Kubernetes Service itself does load-balance to the - pods. By default, NativeLB is false. + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. type: boolean port: anyOf: - type: integer - type: string - description: Port defines the port of a Kubernetes Service. + description: |- + Port defines the port of a Kubernetes Service. This can be a reference to a named port. x-kubernetes-int-or-string: true weight: @@ -584,7 +645,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: middlewares.traefik.io spec: group: traefik.io @@ -598,18 +659,24 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: 'Middleware is the CRD implementation of a Traefik Middleware. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/overview/' + description: |- + Middleware is the CRD implementation of a Traefik Middleware. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/overview/ properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -617,33 +684,37 @@ spec: description: MiddlewareSpec defines the desired state of a Middleware. properties: addPrefix: - description: 'AddPrefix holds the add prefix middleware configuration. - This middleware updates the path of a request before forwarding - it. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/addprefix/' + description: |- + AddPrefix holds the add prefix middleware configuration. + This middleware updates the path of a request before forwarding it. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/addprefix/ properties: prefix: - description: Prefix is the string to add before the current path - in the requested URL. It should include a leading slash (/). + description: |- + Prefix is the string to add before the current path in the requested URL. + It should include a leading slash (/). type: string type: object basicAuth: - description: 'BasicAuth holds the basic auth middleware configuration. + description: |- + BasicAuth holds the basic auth middleware configuration. This middleware restricts access to your services to known users. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/ properties: headerField: - description: 'HeaderField defines a header field to store the - authenticated user. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield' + description: |- + HeaderField defines a header field to store the authenticated user. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield type: string realm: - description: 'Realm allows the protected resources on a server - to be partitioned into a set of protection spaces, each with - its own authentication scheme. Default: traefik.' + description: |- + Realm allows the protected resources on a server to be partitioned into a set of protection spaces, each with its own authentication scheme. + Default: traefik. type: string removeHeader: - description: 'RemoveHeader sets the removeHeader option to true - to remove the authorization header before forwarding the request - to your service. Default: false.' + description: |- + RemoveHeader sets the removeHeader option to true to remove the authorization header before forwarding the request to your service. + Default: false. type: boolean secret: description: Secret is the name of the referenced Kubernetes Secret @@ -651,48 +722,49 @@ spec: type: string type: object buffering: - description: 'Buffering holds the buffering middleware configuration. - This middleware retries or limits the size of requests that can - be forwarded to backends. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#maxrequestbodybytes' + description: |- + Buffering holds the buffering middleware configuration. + This middleware retries or limits the size of requests that can be forwarded to backends. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#maxrequestbodybytes properties: maxRequestBodyBytes: - description: 'MaxRequestBodyBytes defines the maximum allowed - body size for the request (in bytes). If the request exceeds - the allowed size, it is not forwarded to the service, and the - client gets a 413 (Request Entity Too Large) response. Default: - 0 (no maximum).' + description: |- + MaxRequestBodyBytes defines the maximum allowed body size for the request (in bytes). + If the request exceeds the allowed size, it is not forwarded to the service, and the client gets a 413 (Request Entity Too Large) response. + Default: 0 (no maximum). format: int64 type: integer maxResponseBodyBytes: - description: 'MaxResponseBodyBytes defines the maximum allowed - response size from the service (in bytes). If the response exceeds - the allowed size, it is not forwarded to the client. The client - gets a 500 (Internal Server Error) response instead. Default: - 0 (no maximum).' + description: |- + MaxResponseBodyBytes defines the maximum allowed response size from the service (in bytes). + If the response exceeds the allowed size, it is not forwarded to the client. The client gets a 500 (Internal Server Error) response instead. + Default: 0 (no maximum). format: int64 type: integer memRequestBodyBytes: - description: 'MemRequestBodyBytes defines the threshold (in bytes) - from which the request will be buffered on disk instead of in - memory. Default: 1048576 (1Mi).' + description: |- + MemRequestBodyBytes defines the threshold (in bytes) from which the request will be buffered on disk instead of in memory. + Default: 1048576 (1Mi). format: int64 type: integer memResponseBodyBytes: - description: 'MemResponseBodyBytes defines the threshold (in bytes) - from which the response will be buffered on disk instead of - in memory. Default: 1048576 (1Mi).' + description: |- + MemResponseBodyBytes defines the threshold (in bytes) from which the response will be buffered on disk instead of in memory. + Default: 1048576 (1Mi). format: int64 type: integer retryExpression: - description: 'RetryExpression defines the retry conditions. It - is a logical combination of functions with operators AND (&&) - and OR (||). More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#retryexpression' + description: |- + RetryExpression defines the retry conditions. + It is a logical combination of functions with operators AND (&&) and OR (||). + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#retryexpression type: string type: object chain: - description: 'Chain holds the configuration of the chain middleware. - This middleware enables to define reusable combinations of other - pieces of middleware. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/chain/' + description: |- + Chain holds the configuration of the chain middleware. + This middleware enables to define reusable combinations of other pieces of middleware. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/chain/ properties: middlewares: description: Middlewares is the list of MiddlewareRef which composes @@ -744,15 +816,15 @@ spec: x-kubernetes-int-or-string: true type: object compress: - description: 'Compress holds the compress middleware configuration. - This middleware compresses responses before sending them to the - client, using gzip compression. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/compress/' + description: |- + Compress holds the compress middleware configuration. + This middleware compresses responses before sending them to the client, using gzip compression. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/compress/ properties: excludedContentTypes: - description: ExcludedContentTypes defines the list of content - types to compare the Content-Type header of the incoming requests - and responses before compressing. `application/grpc` is always - excluded. + description: |- + ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing. + `application/grpc` is always excluded. items: type: string type: array @@ -764,30 +836,38 @@ spec: type: string type: array minResponseBodyBytes: - description: 'MinResponseBodyBytes defines the minimum amount - of bytes a response body must have to be compressed. Default: - 1024.' + description: |- + MinResponseBodyBytes defines the minimum amount of bytes a response body must have to be compressed. + Default: 1024. type: integer type: object contentType: - description: ContentType holds the content-type middleware configuration. - This middleware sets the `Content-Type` header value to the media - type detected from the response content, when it is not set by the - backend. + description: |- + ContentType holds the content-type middleware configuration. + This middleware exists to enable the correct behavior until at least the default one can be changed in a future version. + properties: + autoDetect: + description: |- + AutoDetect specifies whether to let the `Content-Type` header, if it has not been set by the backend, + be automatically set to a value derived from the contents of the response. + Deprecated: AutoDetect option is deprecated, Content-Type middleware is only meant to be used to enable the content-type detection, please remove any usage of this option. + type: boolean type: object digestAuth: - description: 'DigestAuth holds the digest auth middleware configuration. + description: |- + DigestAuth holds the digest auth middleware configuration. This middleware restricts access to your services to known users. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/digestauth/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/digestauth/ properties: headerField: - description: 'HeaderField defines a header field to store the - authenticated user. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield' + description: |- + HeaderField defines a header field to store the authenticated user. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield type: string realm: - description: 'Realm allows the protected resources on a server - to be partitioned into a set of protection spaces, each with - its own authentication scheme. Default: traefik.' + description: |- + Realm allows the protected resources on a server to be partitioned into a set of protection spaces, each with its own authentication scheme. + Default: traefik. type: string removeHeader: description: RemoveHeader defines whether to remove the authorization @@ -799,18 +879,20 @@ spec: type: string type: object errors: - description: 'ErrorPage holds the custom error middleware configuration. - This middleware returns a custom page in lieu of the default, according - to configured ranges of HTTP Status codes. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/' + description: |- + ErrorPage holds the custom error middleware configuration. + This middleware returns a custom page in lieu of the default, according to configured ranges of HTTP Status codes. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/ properties: query: - description: Query defines the URL for the error page (hosted - by service). The {status} variable can be used in order to insert - the status code in the URL. + description: |- + Query defines the URL for the error page (hosted by service). + The {status} variable can be used in order to insert the status code in the URL. type: string service: - description: 'Service defines the reference to a Kubernetes Service - that will serve the error page. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/#service' + description: |- + Service defines the reference to a Kubernetes Service that will serve the error page. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/#service properties: kind: description: Kind defines the kind of the Service. @@ -819,31 +901,32 @@ spec: - TraefikService type: string name: - description: Name defines the name of the referenced Kubernetes - Service or TraefikService. The differentiation between the - two is specified in the Kind field. + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. type: string namespace: description: Namespace defines the namespace of the referenced Kubernetes Service or TraefikService. type: string nativeLB: - description: NativeLB controls, when creating the load-balancer, - whether the LB's children are directly the pods IPs or if - the only child is the Kubernetes Service clusterIP. The - Kubernetes Service itself does load-balance to the pods. + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. By default, NativeLB is false. type: boolean passHostHeader: - description: PassHostHeader defines whether the client Host - header is forwarded to the upstream Kubernetes Service. + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean port: anyOf: - type: integer - type: string - description: Port defines the port of a Kubernetes Service. + description: |- + Port defines the port of a Kubernetes Service. This can be a reference to a named port. x-kubernetes-int-or-string: true responseForwarding: @@ -852,29 +935,29 @@ spec: client. properties: flushInterval: - description: 'FlushInterval defines the interval, in milliseconds, - in between flushes to the client while copying the response - body. A negative value means to flush immediately after - each write to the client. This configuration is ignored - when ReverseProxy recognizes a response as a streaming - response; for such responses, writes are flushed to - the client immediately. Default: 100ms' + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; + for such responses, writes are flushed to the client immediately. + Default: 100ms type: string type: object scheme: - description: Scheme defines the scheme to use for the request - to the upstream Kubernetes Service. It defaults to https - when Kubernetes Service port is 443, http otherwise. + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. type: string serversTransport: - description: ServersTransport defines the name of ServersTransport - resource to use. It allows to configure the transport between - Traefik and your servers. Can only be used on a Kubernetes - Service. + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. type: string sticky: - description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -884,17 +967,18 @@ spec: be accessed by client-side APIs, such as JavaScript. type: boolean maxAge: - description: MaxAge indicates the number of seconds - until the cookie expires. When set to a negative - number, the cookie expires immediately. When set - to zero, the cookie never expires. + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. type: integer name: description: Name defines the Cookie name. type: string sameSite: - description: 'SameSite defines the same site policy. - More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite type: string secure: description: Secure defines whether the cookie can @@ -904,32 +988,34 @@ spec: type: object type: object strategy: - description: Strategy defines the load balancing strategy - between the servers. RoundRobin is the only supported value - at the moment. + description: |- + Strategy defines the load balancing strategy between the servers. + RoundRobin is the only supported value at the moment. type: string weight: - description: Weight defines the weight and should only be - specified when Name references a TraefikService object (and - to be precise, one that embeds a Weighted Round Robin). + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object + (and to be precise, one that embeds a Weighted Round Robin). type: integer required: - name type: object status: - description: Status defines which status or range of statuses - should result in an error page. It can be either a status code - as a number (500), as multiple comma-separated numbers (500,502), - as ranges by separating two codes with a dash (500-599), or - a combination of the two (404,418,500-599). + description: |- + Status defines which status or range of statuses should result in an error page. + It can be either a status code as a number (500), + as multiple comma-separated numbers (500,502), + as ranges by separating two codes with a dash (500-599), + or a combination of the two (404,418,500-599). items: type: string type: array type: object forwardAuth: - description: 'ForwardAuth holds the forward auth middleware configuration. + description: |- + ForwardAuth holds the forward auth middleware configuration. This middleware delegates the request authentication to a Service. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/ properties: addAuthCookiesToResponse: description: AddAuthCookiesToResponse defines the list of cookies @@ -941,9 +1027,9 @@ spec: description: Address defines the authentication server address. type: string authRequestHeaders: - description: AuthRequestHeaders defines the list of the headers - to copy from the request to the authentication server. If not - set or empty then all request headers are passed. + description: |- + AuthRequestHeaders defines the list of the headers to copy from the request to the authentication server. + If not set or empty then all request headers are passed. items: type: string type: array @@ -955,24 +1041,27 @@ spec: type: string type: array authResponseHeadersRegex: - description: 'AuthResponseHeadersRegex defines the regex to match - headers to copy from the authentication server response and - set on forwarded request, after stripping all headers that match - the regex. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/#authresponseheadersregex' + description: |- + AuthResponseHeadersRegex defines the regex to match headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/#authresponseheadersregex type: string tls: description: TLS defines the configuration used to secure the connection to the authentication server. properties: + caOptional: + description: 'Deprecated: TLS client authentication is a server + side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634).' + type: boolean caSecret: - description: CASecret is the name of the referenced Kubernetes - Secret containing the CA to validate the server certificate. + description: |- + CASecret is the name of the referenced Kubernetes Secret containing the CA to validate the server certificate. The CA certificate is extracted from key `tls.ca` or `ca.crt`. type: string certSecret: - description: CertSecret is the name of the referenced Kubernetes - Secret containing the client certificate. The client certificate - is extracted from the keys `tls.crt` and `tls.key`. + description: |- + CertSecret is the name of the referenced Kubernetes Secret containing the client certificate. + The client certificate is extracted from the keys `tls.crt` and `tls.key`. type: string insecureSkipVerify: description: InsecureSkipVerify defines whether the server @@ -985,20 +1074,23 @@ spec: type: boolean type: object grpcWeb: - description: GrpcWeb holds the gRPC web middleware configuration. + description: |- + GrpcWeb holds the gRPC web middleware configuration. This middleware converts a gRPC web request to an HTTP/2 gRPC request. properties: allowOrigins: - description: AllowOrigins is a list of allowable origins. Can - also be a wildcard origin "*". + description: |- + AllowOrigins is a list of allowable origins. + Can also be a wildcard origin "*". items: type: string type: array type: object headers: - description: 'Headers holds the headers middleware configuration. - This middleware manages the requests and responses headers. More - info: https://doc.traefik.io/traefik/v3.0/middlewares/http/headers/#customrequestheaders' + description: |- + Headers holds the headers middleware configuration. + This middleware manages the requests and responses headers. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/headers/#customrequestheaders properties: accessControlAllowCredentials: description: AccessControlAllowCredentials defines whether the @@ -1063,12 +1155,14 @@ spec: header with the nosniff value. type: boolean customBrowserXSSValue: - description: CustomBrowserXSSValue defines the X-XSS-Protection - header value. This overrides the BrowserXssFilter option. + description: |- + CustomBrowserXSSValue defines the X-XSS-Protection header value. + This overrides the BrowserXssFilter option. type: string customFrameOptionsValue: - description: CustomFrameOptionsValue defines the X-Frame-Options - header value. This overrides the FrameDeny option. + description: |- + CustomFrameOptionsValue defines the X-Frame-Options header value. + This overrides the FrameDeny option. type: string customRequestHeaders: additionalProperties: @@ -1082,6 +1176,10 @@ spec: description: CustomResponseHeaders defines the header names and values to apply to the response. type: object + featurePolicy: + description: 'Deprecated: FeaturePolicy option is deprecated, + please use PermissionsPolicy instead.' + type: string forceSTSHeader: description: ForceSTSHeader defines whether to add the STS header even when the connection is HTTP. @@ -1097,34 +1195,49 @@ spec: type: string type: array isDevelopment: - description: IsDevelopment defines whether to mitigate the unwanted - effects of the AllowedHosts, SSL, and STS options when developing. - Usually testing takes place using HTTP, not HTTPS, and on localhost, - not your production domain. If you would like your development - environment to mimic production with complete Host blocking, - SSL redirects, and STS headers, leave this as false. + description: |- + IsDevelopment defines whether to mitigate the unwanted effects of the AllowedHosts, SSL, and STS options when developing. + Usually testing takes place using HTTP, not HTTPS, and on localhost, not your production domain. + If you would like your development environment to mimic production with complete Host blocking, SSL redirects, + and STS headers, leave this as false. type: boolean permissionsPolicy: - description: PermissionsPolicy defines the Permissions-Policy - header value. This allows sites to control browser features. + description: |- + PermissionsPolicy defines the Permissions-Policy header value. + This allows sites to control browser features. type: string publicKey: description: PublicKey is the public key that implements HPKP to prevent MITM attacks with forged certificates. type: string referrerPolicy: - description: ReferrerPolicy defines the Referrer-Policy header - value. This allows sites to control whether browsers forward - the Referer header to other sites. + description: |- + ReferrerPolicy defines the Referrer-Policy header value. + This allows sites to control whether browsers forward the Referer header to other sites. + type: string + sslForceHost: + description: 'Deprecated: SSLForceHost option is deprecated, please + use RedirectRegex instead.' + type: boolean + sslHost: + description: 'Deprecated: SSLHost option is deprecated, please + use RedirectRegex instead.' type: string sslProxyHeaders: additionalProperties: type: string - description: 'SSLProxyHeaders defines the header keys with associated - values that would indicate a valid HTTPS request. It can be - useful when using other proxies (example: "X-Forwarded-Proto": - "https").' + description: |- + SSLProxyHeaders defines the header keys with associated values that would indicate a valid HTTPS request. + It can be useful when using other proxies (example: "X-Forwarded-Proto": "https"). type: object + sslRedirect: + description: 'Deprecated: SSLRedirect option is deprecated, please + use EntryPoint redirection or RedirectScheme instead.' + type: boolean + sslTemporaryRedirect: + description: 'Deprecated: SSLTemporaryRedirect option is deprecated, + please use EntryPoint redirection or RedirectScheme instead.' + type: boolean stsIncludeSubdomains: description: STSIncludeSubdomains defines whether the includeSubDomains directive is appended to the Strict-Transport-Security header. @@ -1134,33 +1247,35 @@ spec: to the Strict-Transport-Security header. type: boolean stsSeconds: - description: STSSeconds defines the max-age of the Strict-Transport-Security - header. If set to 0, the header is not set. + description: |- + STSSeconds defines the max-age of the Strict-Transport-Security header. + If set to 0, the header is not set. format: int64 type: integer type: object inFlightReq: - description: 'InFlightReq holds the in-flight request middleware configuration. - This middleware limits the number of requests being processed and - served concurrently. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/' + description: |- + InFlightReq holds the in-flight request middleware configuration. + This middleware limits the number of requests being processed and served concurrently. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/ properties: amount: - description: Amount defines the maximum amount of allowed simultaneous - in-flight request. The middleware responds with HTTP 429 Too - Many Requests if there are already amount requests in progress - (based on the same sourceCriterion strategy). + description: |- + Amount defines the maximum amount of allowed simultaneous in-flight request. + The middleware responds with HTTP 429 Too Many Requests if there are already amount requests in progress (based on the same sourceCriterion strategy). format: int64 type: integer sourceCriterion: - description: 'SourceCriterion defines what criterion is used to - group requests as originating from a common source. If several - strategies are defined at the same time, an error will be raised. - If none are set, the default is to use the requestHost. More - info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/#sourcecriterion' + description: |- + SourceCriterion defines what criterion is used to group requests as originating from a common source. + If several strategies are defined at the same time, an error will be raised. + If none are set, the default is to use the requestHost. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/#sourcecriterion properties: ipStrategy: - description: 'IPStrategy holds the IP strategy configuration - used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' + description: |- + IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1186,13 +1301,15 @@ spec: type: object type: object ipAllowList: - description: 'IPAllowList holds the IP allowlist middleware configuration. + description: |- + IPAllowList holds the IP allowlist middleware configuration. This middleware accepts / refuses requests based on the client IP. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/ properties: ipStrategy: - description: 'IPStrategy holds the IP strategy configuration used - by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' + description: |- + IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1207,8 +1324,9 @@ spec: type: array type: object rejectStatusCode: - description: RejectStatusCode defines the HTTP status code used - for refused requests. If not set, the default is 403 (Forbidden). + description: |- + RejectStatusCode defines the HTTP status code used for refused requests. + If not set, the default is 403 (Forbidden). type: integer sourceRange: description: SourceRange defines the set of allowed IPs (or ranges @@ -1221,8 +1339,9 @@ spec: description: 'Deprecated: please use IPAllowList instead.' properties: ipStrategy: - description: 'IPStrategy holds the IP strategy configuration used - by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' + description: |- + IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1244,9 +1363,10 @@ spec: type: array type: object passTLSClientCert: - description: 'PassTLSClientCert holds the pass TLS client cert middleware - configuration. This middleware adds the selected data from the passed - client TLS certificate to a header. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/passtlsclientcert/' + description: |- + PassTLSClientCert holds the pass TLS client cert middleware configuration. + This middleware adds the selected data from the passed client TLS certificate to a header. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/passtlsclientcert/ properties: info: description: Info selects the specific client certificate details @@ -1347,46 +1467,48 @@ spec: plugin: additionalProperties: x-kubernetes-preserve-unknown-fields: true - description: 'Plugin defines the middleware plugin configuration. - More info: https://doc.traefik.io/traefik/plugins/' + description: |- + Plugin defines the middleware plugin configuration. + More info: https://doc.traefik.io/traefik/plugins/ type: object rateLimit: - description: 'RateLimit holds the rate limit configuration. This middleware - ensures that services will receive a fair amount of requests, and - allows one to define what fair is. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ratelimit/' + description: |- + RateLimit holds the rate limit configuration. + This middleware ensures that services will receive a fair amount of requests, and allows one to define what fair is. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ratelimit/ properties: average: - description: Average is the maximum rate, by default in requests/s, - allowed for the given source. It defaults to 0, which means - no rate limiting. The rate is actually defined by dividing Average - by Period. So for a rate below 1req/s, one needs to define a - Period larger than a second. + description: |- + Average is the maximum rate, by default in requests/s, allowed for the given source. + It defaults to 0, which means no rate limiting. + The rate is actually defined by dividing Average by Period. So for a rate below 1req/s, + one needs to define a Period larger than a second. format: int64 type: integer burst: - description: Burst is the maximum number of requests allowed to - arrive in the same arbitrarily small period of time. It defaults - to 1. + description: |- + Burst is the maximum number of requests allowed to arrive in the same arbitrarily small period of time. + It defaults to 1. format: int64 type: integer period: anyOf: - type: integer - type: string - description: 'Period, in combination with Average, defines the - actual maximum rate, such as: r = Average / Period. It defaults - to a second.' + description: |- + Period, in combination with Average, defines the actual maximum rate, such as: + r = Average / Period. It defaults to a second. x-kubernetes-int-or-string: true sourceCriterion: - description: SourceCriterion defines what criterion is used to - group requests as originating from a common source. If several - strategies are defined at the same time, an error will be raised. - If none are set, the default is to use the request's remote - address field (as an ipStrategy). + description: |- + SourceCriterion defines what criterion is used to group requests as originating from a common source. + If several strategies are defined at the same time, an error will be raised. + If none are set, the default is to use the request's remote address field (as an ipStrategy). properties: ipStrategy: - description: 'IPStrategy holds the IP strategy configuration - used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' + description: |- + IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1412,9 +1534,10 @@ spec: type: object type: object redirectRegex: - description: 'RedirectRegex holds the redirect regex middleware configuration. + description: |- + RedirectRegex holds the redirect regex middleware configuration. This middleware redirects a request using regex matching and replacement. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectregex/#regex' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectregex/#regex properties: permanent: description: Permanent defines whether the redirection is permanent @@ -1430,9 +1553,10 @@ spec: type: string type: object redirectScheme: - description: 'RedirectScheme holds the redirect scheme middleware - configuration. This middleware redirects requests from a scheme/port - to another. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectscheme/' + description: |- + RedirectScheme holds the redirect scheme middleware configuration. + This middleware redirects requests from a scheme/port to another. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectscheme/ properties: permanent: description: Permanent defines whether the redirection is permanent @@ -1446,9 +1570,10 @@ spec: type: string type: object replacePath: - description: 'ReplacePath holds the replace path middleware configuration. - This middleware replaces the path of the request URL and store the - original path in an X-Replaced-Path header. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepath/' + description: |- + ReplacePath holds the replace path middleware configuration. + This middleware replaces the path of the request URL and store the original path in an X-Replaced-Path header. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepath/ properties: path: description: Path defines the path to use as replacement in the @@ -1456,9 +1581,10 @@ spec: type: string type: object replacePathRegex: - description: 'ReplacePathRegex holds the replace path regex middleware - configuration. This middleware replaces the path of a URL using - regex matching and replacement. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepathregex/' + description: |- + ReplacePathRegex holds the replace path regex middleware configuration. + This middleware replaces the path of a URL using regex matching and replacement. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepathregex/ properties: regex: description: Regex defines the regular expression used to match @@ -1470,11 +1596,11 @@ spec: type: string type: object retry: - description: 'Retry holds the retry middleware configuration. This - 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. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/retry/' + description: |- + Retry holds the retry middleware configuration. + This 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. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/retry/ properties: attempts: description: Attempts defines how many times the request should @@ -1484,18 +1610,26 @@ spec: anyOf: - type: integer - type: string - description: InitialInterval defines the first wait time in the - exponential backoff series. The maximum interval is calculated - as twice the initialInterval. If unspecified, requests will - be retried immediately. The value of initialInterval should - be provided in seconds or as a valid duration format, see https://pkg.go.dev/time#ParseDuration. + description: |- + InitialInterval defines the first wait time in the exponential backoff series. + The maximum interval is calculated as twice the initialInterval. + If unspecified, requests will be retried immediately. + The value of initialInterval should be provided in seconds or as a valid duration format, + see https://pkg.go.dev/time#ParseDuration. x-kubernetes-int-or-string: true type: object stripPrefix: - description: 'StripPrefix holds the strip prefix middleware configuration. + description: |- + StripPrefix holds the strip prefix middleware configuration. This middleware removes the specified prefixes from the URL path. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefix/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefix/ properties: + forceSlash: + description: |- + Deprecated: ForceSlash option is deprecated, please remove any usage of this option. + ForceSlash ensures that the resulting stripped path is not the empty string, by replacing it with / when necessary. + Default: true. + type: boolean prefixes: description: Prefixes defines the prefixes to strip from the request URL. @@ -1504,9 +1638,10 @@ spec: type: array type: object stripPrefixRegex: - description: 'StripPrefixRegex holds the strip prefix regex middleware - configuration. This middleware removes the matching prefixes from - the URL path. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefixregex/' + description: |- + StripPrefixRegex holds the strip prefix regex middleware configuration. + This middleware removes the matching prefixes from the URL path. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefixregex/ properties: regex: description: Regex defines the regular expression to match the @@ -1527,7 +1662,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: middlewaretcps.traefik.io spec: group: traefik.io @@ -1541,18 +1676,24 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: 'MiddlewareTCP is the CRD implementation of a Traefik TCP middleware. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/overview/' + description: |- + MiddlewareTCP is the CRD implementation of a Traefik TCP middleware. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/overview/ properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -1563,14 +1704,17 @@ spec: description: InFlightConn defines the InFlightConn middleware configuration. properties: amount: - description: Amount defines the maximum amount of allowed simultaneous - connections. The middleware closes the connection if there are - already amount connections opened. + description: |- + Amount defines the maximum amount of allowed simultaneous connections. + The middleware closes the connection if there are already amount connections opened. format: int64 type: integer type: object ipAllowList: - description: IPAllowList defines the IPAllowList middleware configuration. + description: |- + IPAllowList defines the IPAllowList middleware configuration. + This middleware accepts/refuses connections based on the client IP. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/tcp/ipallowlist/ properties: sourceRange: description: SourceRange defines the allowed IPs (or ranges of @@ -1580,8 +1724,11 @@ spec: type: array type: object ipWhiteList: - description: 'IPWhiteList defines the IPWhiteList middleware configuration. - Deprecated: please use IPAllowList instead.' + description: |- + IPWhiteList defines the IPWhiteList middleware configuration. + This middleware accepts/refuses connections based on the client IP. + Deprecated: please use IPAllowList instead. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/tcp/ipwhitelist/ properties: sourceRange: description: SourceRange defines the allowed IPs (or ranges of @@ -1602,7 +1749,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: serverstransports.traefik.io spec: group: traefik.io @@ -1616,20 +1763,26 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: 'ServersTransport is the CRD implementation of a ServersTransport. + description: |- + ServersTransport is the CRD implementation of a ServersTransport. If no serversTransport is specified, the default@internal will be used. The default@internal serversTransport is created from the static configuration. - More info: https://doc.traefik.io/traefik/v3.0/routing/services/#serverstransport_1' + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#serverstransport_1 properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -1735,7 +1888,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: serverstransporttcps.traefik.io spec: group: traefik.io @@ -1749,20 +1902,26 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: 'ServersTransportTCP is the CRD implementation of a TCPServersTransport. - If no tcpServersTransport is specified, a default one named default@internal - will be used. The default@internal tcpServersTransport can be configured - in the static configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/services/#serverstransport_3' + description: |- + ServersTransportTCP is the CRD implementation of a TCPServersTransport. + If no tcpServersTransport is specified, a default one named default@internal will be used. + The default@internal tcpServersTransport can be configured in the static configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#serverstransport_3 properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -1808,9 +1967,9 @@ spec: description: InsecureSkipVerify disables TLS certificate verification. type: boolean peerCertURI: - description: MaxIdleConnsPerHost controls the maximum idle (keep-alive) - to keep per-host. PeerCertURI defines the peer cert URI used - to match against SAN URI during the peer certificate verification. + description: |- + MaxIdleConnsPerHost controls the maximum idle (keep-alive) to keep per-host. + PeerCertURI defines the peer cert URI used to match against SAN URI during the peer certificate verification. type: string rootCAsSecrets: description: RootCAsSecrets defines a list of CA secret used to @@ -1849,7 +2008,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: tlsoptions.traefik.io spec: group: traefik.io @@ -1863,19 +2022,24 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: 'TLSOption is the CRD implementation of a Traefik TLS Option, - allowing to configure some parameters of the TLS connection. More info: - https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options' + description: |- + TLSOption is the CRD implementation of a Traefik TLS Option, allowing to configure some parameters of the TLS connection. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -1883,15 +2047,16 @@ spec: description: TLSOptionSpec defines the desired state of a TLSOption. properties: alpnProtocols: - description: 'ALPNProtocols defines the list of supported application - level protocols for the TLS handshake, in order of preference. More - info: https://doc.traefik.io/traefik/v3.0/https/tls/#alpn-protocols' + description: |- + ALPNProtocols defines the list of supported application level protocols for the TLS handshake, in order of preference. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#alpn-protocols items: type: string type: array cipherSuites: - description: 'CipherSuites defines the list of supported cipher suites - for TLS versions up to TLS 1.2. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#cipher-suites' + description: |- + CipherSuites defines the list of supported cipher suites for TLS versions up to TLS 1.2. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#cipher-suites items: type: string type: array @@ -1917,21 +2082,30 @@ spec: type: array type: object curvePreferences: - description: 'CurvePreferences defines the preferred elliptic curves - in a specific order. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#curve-preferences' + description: |- + CurvePreferences defines the preferred elliptic curves in a specific order. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#curve-preferences items: type: string type: array maxVersion: - description: 'MaxVersion defines the maximum TLS version that Traefik - will accept. Possible values: VersionTLS10, VersionTLS11, VersionTLS12, - VersionTLS13. Default: None.' + description: |- + MaxVersion defines the maximum TLS version that Traefik will accept. + Possible values: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13. + Default: None. type: string minVersion: - description: 'MinVersion defines the minimum TLS version that Traefik - will accept. Possible values: VersionTLS10, VersionTLS11, VersionTLS12, - VersionTLS13. Default: VersionTLS10.' + description: |- + MinVersion defines the minimum TLS version that Traefik will accept. + Possible values: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13. + Default: VersionTLS10. type: string + preferServerCipherSuites: + description: |- + PreferServerCipherSuites defines whether the server chooses a cipher suite among his own instead of among the client's. + It is enabled automatically when minVersion or maxVersion is set. + Deprecated: https://github.com/golang/go/issues/45430 + type: boolean sniStrict: description: SniStrict defines whether Traefik allows connections from clients connections that do not specify a server_name extension. @@ -1948,7 +2122,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: tlsstores.traefik.io spec: group: traefik.io @@ -1962,20 +2136,26 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: 'TLSStore is the CRD implementation of a Traefik TLS Store. For - the time being, only the TLSStore named default is supported. This means - that you cannot have two stores that are named default in different Kubernetes - namespaces. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#certificates-stores' + description: |- + TLSStore is the CRD implementation of a Traefik TLS Store. + For the time being, only the TLSStore named default is supported. + This means that you cannot have two stores that are named default in different Kubernetes namespaces. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#certificates-stores properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -2039,7 +2219,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: traefikservices.traefik.io spec: group: traefik.io @@ -2053,19 +2233,27 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: 'TraefikService is the CRD implementation of a Traefik Service. - TraefikService object allows to: - Apply weight to Services on load-balancing - - Mirror traffic on services More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-traefikservice' + description: |- + TraefikService is the CRD implementation of a Traefik Service. + TraefikService object allows to: + - Apply weight to Services on load-balancing + - Mirror traffic on services + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-traefikservice properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -2082,10 +2270,10 @@ spec: - TraefikService type: string maxBodySize: - description: MaxBodySize defines the maximum size allowed for - the body of the request. If the body is larger, the request - is not mirrored. Default value is -1, which means unlimited - size. + description: |- + MaxBodySize defines the maximum size allowed for the body of the request. + If the body is larger, the request is not mirrored. + Default value is -1, which means unlimited size. format: int64 type: integer mirrors: @@ -2101,35 +2289,37 @@ spec: - TraefikService type: string name: - description: Name defines the name of the referenced Kubernetes - Service or TraefikService. The differentiation between - the two is specified in the Kind field. + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. type: string namespace: description: Namespace defines the namespace of the referenced Kubernetes Service or TraefikService. type: string nativeLB: - description: NativeLB controls, when creating the load-balancer, - whether the LB's children are directly the pods IPs or - if the only child is the Kubernetes Service clusterIP. - The Kubernetes Service itself does load-balance to the - pods. By default, NativeLB is false. + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. type: boolean passHostHeader: - description: PassHostHeader defines whether the client Host - header is forwarded to the upstream Kubernetes Service. + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean percent: - description: 'Percent defines the part of the traffic to - mirror. Supported values: 0 to 100.' + description: |- + Percent defines the part of the traffic to mirror. + Supported values: 0 to 100. type: integer port: anyOf: - type: integer - type: string - description: Port defines the port of a Kubernetes Service. + description: |- + Port defines the port of a Kubernetes Service. This can be a reference to a named port. x-kubernetes-int-or-string: true responseForwarding: @@ -2138,30 +2328,29 @@ spec: client. properties: flushInterval: - description: 'FlushInterval defines the interval, in - milliseconds, in between flushes to the client while - copying the response body. A negative value means - to flush immediately after each write to the client. - This configuration is ignored when ReverseProxy recognizes - a response as a streaming response; for such responses, - writes are flushed to the client immediately. Default: - 100ms' + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; + for such responses, writes are flushed to the client immediately. + Default: 100ms type: string type: object scheme: - description: Scheme defines the scheme to use for the request - to the upstream Kubernetes Service. It defaults to https - when Kubernetes Service port is 443, http otherwise. + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. type: string serversTransport: - description: ServersTransport defines the name of ServersTransport - resource to use. It allows to configure the transport - between Traefik and your servers. Can only be used on - a Kubernetes Service. + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. type: string sticky: - description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -2171,17 +2360,18 @@ spec: can be accessed by client-side APIs, such as JavaScript. type: boolean maxAge: - description: MaxAge indicates the number of seconds - until the cookie expires. When set to a negative - number, the cookie expires immediately. When set - to zero, the cookie never expires. + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. type: integer name: description: Name defines the Cookie name. type: string sameSite: - description: 'SameSite defines the same site policy. - More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite type: string secure: description: Secure defines whether the cookie can @@ -2191,13 +2381,13 @@ spec: type: object type: object strategy: - description: Strategy defines the load balancing strategy - between the servers. RoundRobin is the only supported - value at the moment. + description: |- + Strategy defines the load balancing strategy between the servers. + RoundRobin is the only supported value at the moment. type: string weight: - description: Weight defines the weight and should only be - specified when Name references a TraefikService object + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object (and to be precise, one that embeds a Weighted Round Robin). type: integer required: @@ -2205,60 +2395,62 @@ spec: type: object type: array name: - description: Name defines the name of the referenced Kubernetes - Service or TraefikService. The differentiation between the two - is specified in the Kind field. + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. type: string namespace: description: Namespace defines the namespace of the referenced Kubernetes Service or TraefikService. type: string nativeLB: - description: NativeLB controls, when creating the load-balancer, - whether the LB's children are directly the pods IPs or if the - only child is the Kubernetes Service clusterIP. The Kubernetes - Service itself does load-balance to the pods. By default, NativeLB - is false. + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. type: boolean passHostHeader: - description: PassHostHeader defines whether the client Host header - is forwarded to the upstream Kubernetes Service. By default, - passHostHeader is true. + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. + By default, passHostHeader is true. type: boolean port: anyOf: - type: integer - type: string - description: Port defines the port of a Kubernetes Service. This - can be a reference to a named port. + description: |- + Port defines the port of a Kubernetes Service. + This can be a reference to a named port. x-kubernetes-int-or-string: true responseForwarding: description: ResponseForwarding defines how Traefik forwards the response from the upstream Kubernetes Service to the client. properties: flushInterval: - description: 'FlushInterval defines the interval, in milliseconds, - in between flushes to the client while copying the response - body. A negative value means to flush immediately after - each write to the client. This configuration is ignored - when ReverseProxy recognizes a response as a streaming response; + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; for such responses, writes are flushed to the client immediately. - Default: 100ms' + Default: 100ms type: string type: object scheme: - description: Scheme defines the scheme to use for the request - to the upstream Kubernetes Service. It defaults to https when - Kubernetes Service port is 443, http otherwise. + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. type: string serversTransport: - description: ServersTransport defines the name of ServersTransport - resource to use. It allows to configure the transport between - Traefik and your servers. Can only be used on a Kubernetes Service. + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. type: string sticky: - description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -2268,17 +2460,18 @@ spec: accessed by client-side APIs, such as JavaScript. type: boolean maxAge: - description: MaxAge indicates the number of seconds until - the cookie expires. When set to a negative number, the - cookie expires immediately. When set to zero, the cookie - never expires. + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. type: integer name: description: Name defines the Cookie name. type: string sameSite: - description: 'SameSite defines the same site policy. More - info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite type: string secure: description: Secure defines whether the cookie can only @@ -2287,13 +2480,14 @@ spec: type: object type: object strategy: - description: Strategy defines the load balancing strategy between - the servers. RoundRobin is the only supported value at the moment. + description: |- + Strategy defines the load balancing strategy between the servers. + RoundRobin is the only supported value at the moment. type: string weight: - description: Weight defines the weight and should only be specified - when Name references a TraefikService object (and to be precise, - one that embeds a Weighted Round Robin). + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object + (and to be precise, one that embeds a Weighted Round Robin). type: integer required: - name @@ -2315,31 +2509,32 @@ spec: - TraefikService type: string name: - description: Name defines the name of the referenced Kubernetes - Service or TraefikService. The differentiation between - the two is specified in the Kind field. + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. type: string namespace: description: Namespace defines the namespace of the referenced Kubernetes Service or TraefikService. type: string nativeLB: - description: NativeLB controls, when creating the load-balancer, - whether the LB's children are directly the pods IPs or - if the only child is the Kubernetes Service clusterIP. - The Kubernetes Service itself does load-balance to the - pods. By default, NativeLB is false. + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. type: boolean passHostHeader: - description: PassHostHeader defines whether the client Host - header is forwarded to the upstream Kubernetes Service. + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean port: anyOf: - type: integer - type: string - description: Port defines the port of a Kubernetes Service. + description: |- + Port defines the port of a Kubernetes Service. This can be a reference to a named port. x-kubernetes-int-or-string: true responseForwarding: @@ -2348,30 +2543,29 @@ spec: client. properties: flushInterval: - description: 'FlushInterval defines the interval, in - milliseconds, in between flushes to the client while - copying the response body. A negative value means - to flush immediately after each write to the client. - This configuration is ignored when ReverseProxy recognizes - a response as a streaming response; for such responses, - writes are flushed to the client immediately. Default: - 100ms' + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; + for such responses, writes are flushed to the client immediately. + Default: 100ms type: string type: object scheme: - description: Scheme defines the scheme to use for the request - to the upstream Kubernetes Service. It defaults to https - when Kubernetes Service port is 443, http otherwise. + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. type: string serversTransport: - description: ServersTransport defines the name of ServersTransport - resource to use. It allows to configure the transport - between Traefik and your servers. Can only be used on - a Kubernetes Service. + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. type: string sticky: - description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -2381,17 +2575,18 @@ spec: can be accessed by client-side APIs, such as JavaScript. type: boolean maxAge: - description: MaxAge indicates the number of seconds - until the cookie expires. When set to a negative - number, the cookie expires immediately. When set - to zero, the cookie never expires. + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. type: integer name: description: Name defines the Cookie name. type: string sameSite: - description: 'SameSite defines the same site policy. - More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite type: string secure: description: Secure defines whether the cookie can @@ -2401,13 +2596,13 @@ spec: type: object type: object strategy: - description: Strategy defines the load balancing strategy - between the servers. RoundRobin is the only supported - value at the moment. + description: |- + Strategy defines the load balancing strategy between the servers. + RoundRobin is the only supported value at the moment. type: string weight: - description: Weight defines the weight and should only be - specified when Name references a TraefikService object + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object (and to be precise, one that embeds a Weighted Round Robin). type: integer required: @@ -2415,8 +2610,9 @@ spec: type: object type: array sticky: - description: 'Sticky defines whether sticky sessions are enabled. - More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#stickiness-and-load-balancing' + description: |- + Sticky defines whether sticky sessions are enabled. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#stickiness-and-load-balancing properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -2426,17 +2622,18 @@ spec: accessed by client-side APIs, such as JavaScript. type: boolean maxAge: - description: MaxAge indicates the number of seconds until - the cookie expires. When set to a negative number, the - cookie expires immediately. When set to zero, the cookie - never expires. + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. type: integer name: description: Name defines the Cookie name. type: string sameSite: - description: 'SameSite defines the same site policy. More - info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite type: string secure: description: Secure defines whether the cookie can only diff --git a/docs/content/reference/dynamic-configuration/kv-ref.md b/docs/content/reference/dynamic-configuration/kv-ref.md index f078b166a..254801508 100644 --- a/docs/content/reference/dynamic-configuration/kv-ref.md +++ b/docs/content/reference/dynamic-configuration/kv-ref.md @@ -20,12 +20,13 @@ THIS FILE MUST NOT BE EDITED BY HAND | `traefik/http/middlewares/Middleware05/circuitBreaker/expression` | `foobar` | | `traefik/http/middlewares/Middleware05/circuitBreaker/fallbackDuration` | `42s` | | `traefik/http/middlewares/Middleware05/circuitBreaker/recoveryDuration` | `42s` | +| `traefik/http/middlewares/Middleware05/circuitBreaker/responseCode` | `42` | | `traefik/http/middlewares/Middleware06/compress/excludedContentTypes/0` | `foobar` | | `traefik/http/middlewares/Middleware06/compress/excludedContentTypes/1` | `foobar` | | `traefik/http/middlewares/Middleware06/compress/includedContentTypes/0` | `foobar` | | `traefik/http/middlewares/Middleware06/compress/includedContentTypes/1` | `foobar` | | `traefik/http/middlewares/Middleware06/compress/minResponseBodyBytes` | `42` | -| `traefik/http/middlewares/Middleware07/contentType` | `` | +| `traefik/http/middlewares/Middleware07/contentType/autoDetect` | `true` | | `traefik/http/middlewares/Middleware08/digestAuth/headerField` | `foobar` | | `traefik/http/middlewares/Middleware08/digestAuth/realm` | `foobar` | | `traefik/http/middlewares/Middleware08/digestAuth/removeHeader` | `true` | @@ -45,6 +46,7 @@ THIS FILE MUST NOT BE EDITED BY HAND | `traefik/http/middlewares/Middleware10/forwardAuth/authResponseHeaders/1` | `foobar` | | `traefik/http/middlewares/Middleware10/forwardAuth/authResponseHeadersRegex` | `foobar` | | `traefik/http/middlewares/Middleware10/forwardAuth/tls/ca` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/tls/caOptional` | `true` | | `traefik/http/middlewares/Middleware10/forwardAuth/tls/cert` | `foobar` | | `traefik/http/middlewares/Middleware10/forwardAuth/tls/insecureSkipVerify` | `true` | | `traefik/http/middlewares/Middleware10/forwardAuth/tls/key` | `foobar` | @@ -75,6 +77,7 @@ THIS FILE MUST NOT BE EDITED BY HAND | `traefik/http/middlewares/Middleware12/headers/customRequestHeaders/name1` | `foobar` | | `traefik/http/middlewares/Middleware12/headers/customResponseHeaders/name0` | `foobar` | | `traefik/http/middlewares/Middleware12/headers/customResponseHeaders/name1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/featurePolicy` | `foobar` | | `traefik/http/middlewares/Middleware12/headers/forceSTSHeader` | `true` | | `traefik/http/middlewares/Middleware12/headers/frameDeny` | `true` | | `traefik/http/middlewares/Middleware12/headers/hostsProxyHeaders/0` | `foobar` | @@ -83,8 +86,12 @@ THIS FILE MUST NOT BE EDITED BY HAND | `traefik/http/middlewares/Middleware12/headers/permissionsPolicy` | `foobar` | | `traefik/http/middlewares/Middleware12/headers/publicKey` | `foobar` | | `traefik/http/middlewares/Middleware12/headers/referrerPolicy` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/sslForceHost` | `true` | +| `traefik/http/middlewares/Middleware12/headers/sslHost` | `foobar` | | `traefik/http/middlewares/Middleware12/headers/sslProxyHeaders/name0` | `foobar` | | `traefik/http/middlewares/Middleware12/headers/sslProxyHeaders/name1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/sslRedirect` | `true` | +| `traefik/http/middlewares/Middleware12/headers/sslTemporaryRedirect` | `true` | | `traefik/http/middlewares/Middleware12/headers/stsIncludeSubdomains` | `true` | | `traefik/http/middlewares/Middleware12/headers/stsPreload` | `true` | | `traefik/http/middlewares/Middleware12/headers/stsSeconds` | `42` | @@ -148,6 +155,7 @@ THIS FILE MUST NOT BE EDITED BY HAND | `traefik/http/middlewares/Middleware22/replacePathRegex/replacement` | `foobar` | | `traefik/http/middlewares/Middleware23/retry/attempts` | `42` | | `traefik/http/middlewares/Middleware23/retry/initialInterval` | `42s` | +| `traefik/http/middlewares/Middleware24/stripPrefix/forceSlash` | `true` | | `traefik/http/middlewares/Middleware24/stripPrefix/prefixes/0` | `foobar` | | `traefik/http/middlewares/Middleware24/stripPrefix/prefixes/1` | `foobar` | | `traefik/http/middlewares/Middleware25/stripPrefixRegex/regex/0` | `foobar` | @@ -158,6 +166,7 @@ THIS FILE MUST NOT BE EDITED BY HAND | `traefik/http/routers/Router0/middlewares/1` | `foobar` | | `traefik/http/routers/Router0/priority` | `42` | | `traefik/http/routers/Router0/rule` | `foobar` | +| `traefik/http/routers/Router0/ruleSyntax` | `foobar` | | `traefik/http/routers/Router0/service` | `foobar` | | `traefik/http/routers/Router0/tls/certResolver` | `foobar` | | `traefik/http/routers/Router0/tls/domains/0/main` | `foobar` | @@ -173,6 +182,7 @@ THIS FILE MUST NOT BE EDITED BY HAND | `traefik/http/routers/Router1/middlewares/1` | `foobar` | | `traefik/http/routers/Router1/priority` | `42` | | `traefik/http/routers/Router1/rule` | `foobar` | +| `traefik/http/routers/Router1/ruleSyntax` | `foobar` | | `traefik/http/routers/Router1/service` | `foobar` | | `traefik/http/routers/Router1/tls/certResolver` | `foobar` | | `traefik/http/routers/Router1/tls/domains/0/main` | `foobar` | @@ -238,7 +248,9 @@ THIS FILE MUST NOT BE EDITED BY HAND | `traefik/http/services/Service02/loadBalancer/passHostHeader` | `true` | | `traefik/http/services/Service02/loadBalancer/responseForwarding/flushInterval` | `42s` | | `traefik/http/services/Service02/loadBalancer/servers/0/url` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/servers/0/weight` | `42` | | `traefik/http/services/Service02/loadBalancer/servers/1/url` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/servers/1/weight` | `42` | | `traefik/http/services/Service02/loadBalancer/serversTransport` | `foobar` | | `traefik/http/services/Service02/loadBalancer/sticky/cookie/httpOnly` | `true` | | `traefik/http/services/Service02/loadBalancer/sticky/cookie/maxAge` | `42` | @@ -273,6 +285,7 @@ THIS FILE MUST NOT BE EDITED BY HAND | `traefik/tcp/routers/TCPRouter0/middlewares/1` | `foobar` | | `traefik/tcp/routers/TCPRouter0/priority` | `42` | | `traefik/tcp/routers/TCPRouter0/rule` | `foobar` | +| `traefik/tcp/routers/TCPRouter0/ruleSyntax` | `foobar` | | `traefik/tcp/routers/TCPRouter0/service` | `foobar` | | `traefik/tcp/routers/TCPRouter0/tls/certResolver` | `foobar` | | `traefik/tcp/routers/TCPRouter0/tls/domains/0/main` | `foobar` | @@ -289,6 +302,7 @@ THIS FILE MUST NOT BE EDITED BY HAND | `traefik/tcp/routers/TCPRouter1/middlewares/1` | `foobar` | | `traefik/tcp/routers/TCPRouter1/priority` | `42` | | `traefik/tcp/routers/TCPRouter1/rule` | `foobar` | +| `traefik/tcp/routers/TCPRouter1/ruleSyntax` | `foobar` | | `traefik/tcp/routers/TCPRouter1/service` | `foobar` | | `traefik/tcp/routers/TCPRouter1/tls/certResolver` | `foobar` | | `traefik/tcp/routers/TCPRouter1/tls/domains/0/main` | `foobar` | @@ -335,6 +349,7 @@ THIS FILE MUST NOT BE EDITED BY HAND | `traefik/tcp/services/TCPService01/loadBalancer/servers/1/address` | `foobar` | | `traefik/tcp/services/TCPService01/loadBalancer/servers/1/tls` | `true` | | `traefik/tcp/services/TCPService01/loadBalancer/serversTransport` | `foobar` | +| `traefik/tcp/services/TCPService01/loadBalancer/terminationDelay` | `42` | | `traefik/tcp/services/TCPService02/weighted/services/0/name` | `foobar` | | `traefik/tcp/services/TCPService02/weighted/services/0/weight` | `42` | | `traefik/tcp/services/TCPService02/weighted/services/1/name` | `foobar` | @@ -358,6 +373,7 @@ THIS FILE MUST NOT BE EDITED BY HAND | `traefik/tls/options/Options0/curvePreferences/1` | `foobar` | | `traefik/tls/options/Options0/maxVersion` | `foobar` | | `traefik/tls/options/Options0/minVersion` | `foobar` | +| `traefik/tls/options/Options0/preferServerCipherSuites` | `true` | | `traefik/tls/options/Options0/sniStrict` | `true` | | `traefik/tls/options/Options1/alpnProtocols/0` | `foobar` | | `traefik/tls/options/Options1/alpnProtocols/1` | `foobar` | @@ -370,6 +386,7 @@ THIS FILE MUST NOT BE EDITED BY HAND | `traefik/tls/options/Options1/curvePreferences/1` | `foobar` | | `traefik/tls/options/Options1/maxVersion` | `foobar` | | `traefik/tls/options/Options1/minVersion` | `foobar` | +| `traefik/tls/options/Options1/preferServerCipherSuites` | `true` | | `traefik/tls/options/Options1/sniStrict` | `true` | | `traefik/tls/stores/Store0/defaultCertificate/certFile` | `foobar` | | `traefik/tls/stores/Store0/defaultCertificate/keyFile` | `foobar` | diff --git a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml index 41628b58a..9031689c0 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ingressroutes.traefik.io spec: group: traefik.io @@ -20,14 +20,19 @@ spec: description: IngressRoute is the CRD implementation of a Traefik HTTP Router. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -35,10 +40,11 @@ spec: description: IngressRouteSpec defines the desired state of IngressRoute. properties: entryPoints: - description: 'EntryPoints defines the list of entry point names to - bind to. Entry points have to be configured in the static configuration. + description: |- + EntryPoints defines the list of entry point names to bind to. + Entry points have to be configured in the static configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ - Default: all.' + Default: all. items: type: string type: array @@ -48,17 +54,21 @@ spec: description: Route holds the HTTP route configuration. properties: kind: - description: Kind defines the kind of the route. Rule is the - only supported kind. + description: |- + Kind defines the kind of the route. + Rule is the only supported kind. enum: - Rule type: string match: - description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule' + description: |- + Match defines the router's rule. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule type: string middlewares: - description: 'Middlewares defines the list of references to - Middleware resources. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-middleware' + description: |- + Middlewares defines the list of references to Middleware resources. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-middleware items: description: MiddlewareRef is a reference to a Middleware resource. @@ -76,13 +86,14 @@ spec: type: object type: array priority: - description: 'Priority defines the router''s priority. More - info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority' + description: |- + Priority defines the router's priority. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority type: integer services: - description: Services defines the list of Service. It can contain - any combination of TraefikService and/or reference to a Kubernetes - Service. + description: |- + Services defines the list of Service. + It can contain any combination of TraefikService and/or reference to a Kubernetes Service. items: description: Service defines an upstream HTTP service to proxy traffic to. @@ -94,31 +105,32 @@ spec: - TraefikService type: string name: - description: Name defines the name of the referenced Kubernetes - Service or TraefikService. The differentiation between - the two is specified in the Kind field. + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. type: string namespace: description: Namespace defines the namespace of the referenced Kubernetes Service or TraefikService. type: string nativeLB: - description: NativeLB controls, when creating the load-balancer, - whether the LB's children are directly the pods IPs - or if the only child is the Kubernetes Service clusterIP. - The Kubernetes Service itself does load-balance to the - pods. By default, NativeLB is false. + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. type: boolean passHostHeader: - description: PassHostHeader defines whether the client - Host header is forwarded to the upstream Kubernetes - Service. By default, passHostHeader is true. + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. + By default, passHostHeader is true. type: boolean port: anyOf: - type: integer - type: string - description: Port defines the port of a Kubernetes Service. + description: |- + Port defines the port of a Kubernetes Service. This can be a reference to a named port. x-kubernetes-int-or-string: true responseForwarding: @@ -127,30 +139,29 @@ spec: the client. properties: flushInterval: - description: 'FlushInterval defines the interval, - in milliseconds, in between flushes to the client - while copying the response body. A negative value - means to flush immediately after each write to the - client. This configuration is ignored when ReverseProxy - recognizes a response as a streaming response; for - such responses, writes are flushed to the client - immediately. Default: 100ms' + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; + for such responses, writes are flushed to the client immediately. + Default: 100ms type: string type: object scheme: - description: Scheme defines the scheme to use for the - request to the upstream Kubernetes Service. It defaults - to https when Kubernetes Service port is 443, http otherwise. + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. type: string serversTransport: - description: ServersTransport defines the name of ServersTransport - resource to use. It allows to configure the transport - between Traefik and your servers. Can only be used on - a Kubernetes Service. + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. type: string sticky: - description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -161,17 +172,18 @@ spec: JavaScript. type: boolean maxAge: - description: MaxAge indicates the number of seconds - until the cookie expires. When set to a negative - number, the cookie expires immediately. When - set to zero, the cookie never expires. + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. type: integer name: description: Name defines the Cookie name. type: string sameSite: - description: 'SameSite defines the same site policy. - More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite type: string secure: description: Secure defines whether the cookie @@ -181,36 +193,44 @@ spec: type: object type: object strategy: - description: Strategy defines the load balancing strategy - between the servers. RoundRobin is the only supported - value at the moment. + description: |- + Strategy defines the load balancing strategy between the servers. + RoundRobin is the only supported value at the moment. type: string weight: - description: Weight defines the weight and should only - be specified when Name references a TraefikService object - (and to be precise, one that embeds a Weighted Round - Robin). + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object + (and to be precise, one that embeds a Weighted Round Robin). type: integer required: - name type: object type: array + syntax: + description: |- + Syntax defines the router's rule syntax. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rulesyntax + type: string required: - kind - match type: object type: array tls: - description: 'TLS defines the TLS configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls' + description: |- + TLS defines the TLS configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls properties: certResolver: - description: 'CertResolver defines the name of the certificate - resolver to use. Cert resolvers have to be configured in the - static configuration. More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers' + description: |- + CertResolver defines the name of the certificate resolver to use. + Cert resolvers have to be configured in the static configuration. + More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers type: string domains: - description: 'Domains defines the list of domains that will be - used to issue certificates. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains' + description: |- + Domains defines the list of domains that will be used to issue certificates. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains items: description: Domain holds a domain name with SANs. properties: @@ -226,17 +246,20 @@ spec: type: object type: array options: - description: 'Options defines the reference to a TLSOption, that - specifies the parameters of the TLS connection. If not defined, - the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options' + description: |- + Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. + If not defined, the `default` TLSOption is used. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options properties: name: - description: 'Name defines the name of the referenced TLSOption. - More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption' + description: |- + Name defines the name of the referenced TLSOption. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption type: string namespace: - description: 'Namespace defines the namespace of the referenced - TLSOption. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption' + description: |- + Namespace defines the namespace of the referenced TLSOption. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption type: string required: - name @@ -246,17 +269,19 @@ spec: Secret to specify the certificate details. type: string store: - description: Store defines the reference to the TLSStore, that - will be used to store certificates. Please note that only `default` - TLSStore can be used. + description: |- + Store defines the reference to the TLSStore, that will be used to store certificates. + Please note that only `default` TLSStore can be used. properties: name: - description: 'Name defines the name of the referenced TLSStore. - More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore' + description: |- + Name defines the name of the referenced TLSStore. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore type: string namespace: - description: 'Namespace defines the namespace of the referenced - TLSStore. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore' + description: |- + Namespace defines the namespace of the referenced TLSStore. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore type: string required: - name diff --git a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutetcps.yaml b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutetcps.yaml index 94226e14c..930b06c04 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutetcps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutetcps.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ingressroutetcps.traefik.io spec: group: traefik.io @@ -20,14 +20,19 @@ spec: description: IngressRouteTCP is the CRD implementation of a Traefik TCP Router. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -35,10 +40,11 @@ spec: description: IngressRouteTCPSpec defines the desired state of IngressRouteTCP. properties: entryPoints: - description: 'EntryPoints defines the list of entry point names to - bind to. Entry points have to be configured in the static configuration. + description: |- + EntryPoints defines the list of entry point names to bind to. + Entry points have to be configured in the static configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ - Default: all.' + Default: all. items: type: string type: array @@ -48,7 +54,9 @@ spec: description: RouteTCP holds the TCP route configuration. properties: match: - description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule_1' + description: |- + Match defines the router's rule. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule_1 type: string middlewares: description: Middlewares defines the list of references to MiddlewareTCP @@ -70,8 +78,9 @@ spec: type: object type: array priority: - description: 'Priority defines the router''s priority. More - info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority_1' + description: |- + Priority defines the router's priority. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority_1 type: integer services: description: Services defines the list of TCP services. @@ -88,22 +97,24 @@ spec: Kubernetes Service. type: string nativeLB: - description: NativeLB controls, when creating the load-balancer, - whether the LB's children are directly the pods IPs - or if the only child is the Kubernetes Service clusterIP. - The Kubernetes Service itself does load-balance to the - pods. By default, NativeLB is false. + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. type: boolean port: anyOf: - type: integer - type: string - description: Port defines the port of a Kubernetes Service. + description: |- + Port defines the port of a Kubernetes Service. This can be a reference to a named port. x-kubernetes-int-or-string: true proxyProtocol: - description: 'ProxyProtocol defines the PROXY protocol - configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/services/#proxy-protocol' + description: |- + ProxyProtocol defines the PROXY protocol configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#proxy-protocol properties: version: description: Version defines the PROXY Protocol version @@ -111,11 +122,20 @@ spec: type: integer type: object serversTransport: - description: ServersTransport defines the name of ServersTransportTCP - resource to use. It allows to configure the transport - between Traefik and your servers. Can only be used on - a Kubernetes Service. + description: |- + ServersTransport defines the name of ServersTransportTCP resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. type: string + terminationDelay: + description: |- + TerminationDelay defines the deadline that the proxy sets, after one of its connected peers indicates + it has closed the writing capability of its connection, to close the reading capability as well, + hence fully terminating the connection. + It is a duration in milliseconds, defaulting to 100. + A negative value means an infinite deadline (i.e. the reading capability is never closed). + Deprecated: TerminationDelay is not supported APIVersion traefik.io/v1, please use ServersTransport to configure the TerminationDelay instead. + type: integer tls: description: TLS determines whether to use TLS when dialing with the backend. @@ -129,22 +149,30 @@ spec: - port type: object type: array + syntax: + description: |- + Syntax defines the router's rule syntax. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rulesyntax_1 + type: string required: - match type: object type: array tls: - description: 'TLS defines the TLS configuration on a layer 4 / TCP - Route. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls_1' + description: |- + TLS defines the TLS configuration on a layer 4 / TCP Route. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls_1 properties: certResolver: - description: 'CertResolver defines the name of the certificate - resolver to use. Cert resolvers have to be configured in the - static configuration. More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers' + description: |- + CertResolver defines the name of the certificate resolver to use. + Cert resolvers have to be configured in the static configuration. + More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers type: string domains: - description: 'Domains defines the list of domains that will be - used to issue certificates. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains' + description: |- + Domains defines the list of domains that will be used to issue certificates. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains items: description: Domain holds a domain name with SANs. properties: @@ -160,9 +188,10 @@ spec: type: object type: array options: - description: 'Options defines the reference to a TLSOption, that - specifies the parameters of the TLS connection. If not defined, - the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options' + description: |- + Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. + If not defined, the `default` TLSOption is used. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options properties: name: description: Name defines the name of the referenced Traefik @@ -184,9 +213,9 @@ spec: Secret to specify the certificate details. type: string store: - description: Store defines the reference to the TLSStore, that - will be used to store certificates. Please note that only `default` - TLSStore can be used. + description: |- + Store defines the reference to the TLSStore, that will be used to store certificates. + Please note that only `default` TLSStore can be used. properties: name: description: Name defines the name of the referenced Traefik diff --git a/docs/content/reference/dynamic-configuration/traefik.io_ingressrouteudps.yaml b/docs/content/reference/dynamic-configuration/traefik.io_ingressrouteudps.yaml index 76ead3b92..245194c62 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_ingressrouteudps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_ingressrouteudps.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ingressrouteudps.traefik.io spec: group: traefik.io @@ -20,14 +20,19 @@ spec: description: IngressRouteUDP is a CRD implementation of a Traefik UDP Router. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -35,10 +40,11 @@ spec: description: IngressRouteUDPSpec defines the desired state of a IngressRouteUDP. properties: entryPoints: - description: 'EntryPoints defines the list of entry point names to - bind to. Entry points have to be configured in the static configuration. + description: |- + EntryPoints defines the list of entry point names to bind to. + Entry points have to be configured in the static configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ - Default: all.' + Default: all. items: type: string type: array @@ -62,17 +68,18 @@ spec: Kubernetes Service. type: string nativeLB: - description: NativeLB controls, when creating the load-balancer, - whether the LB's children are directly the pods IPs - or if the only child is the Kubernetes Service clusterIP. - The Kubernetes Service itself does load-balance to the - pods. By default, NativeLB is false. + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. type: boolean port: anyOf: - type: integer - type: string - description: Port defines the port of a Kubernetes Service. + description: |- + Port defines the port of a Kubernetes Service. This can be a reference to a named port. x-kubernetes-int-or-string: true weight: diff --git a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml index b7d8c6b52..4ef178a57 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: middlewares.traefik.io spec: group: traefik.io @@ -17,18 +17,24 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: 'Middleware is the CRD implementation of a Traefik Middleware. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/overview/' + description: |- + Middleware is the CRD implementation of a Traefik Middleware. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/overview/ properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -36,33 +42,37 @@ spec: description: MiddlewareSpec defines the desired state of a Middleware. properties: addPrefix: - description: 'AddPrefix holds the add prefix middleware configuration. - This middleware updates the path of a request before forwarding - it. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/addprefix/' + description: |- + AddPrefix holds the add prefix middleware configuration. + This middleware updates the path of a request before forwarding it. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/addprefix/ properties: prefix: - description: Prefix is the string to add before the current path - in the requested URL. It should include a leading slash (/). + description: |- + Prefix is the string to add before the current path in the requested URL. + It should include a leading slash (/). type: string type: object basicAuth: - description: 'BasicAuth holds the basic auth middleware configuration. + description: |- + BasicAuth holds the basic auth middleware configuration. This middleware restricts access to your services to known users. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/ properties: headerField: - description: 'HeaderField defines a header field to store the - authenticated user. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield' + description: |- + HeaderField defines a header field to store the authenticated user. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield type: string realm: - description: 'Realm allows the protected resources on a server - to be partitioned into a set of protection spaces, each with - its own authentication scheme. Default: traefik.' + description: |- + Realm allows the protected resources on a server to be partitioned into a set of protection spaces, each with its own authentication scheme. + Default: traefik. type: string removeHeader: - description: 'RemoveHeader sets the removeHeader option to true - to remove the authorization header before forwarding the request - to your service. Default: false.' + description: |- + RemoveHeader sets the removeHeader option to true to remove the authorization header before forwarding the request to your service. + Default: false. type: boolean secret: description: Secret is the name of the referenced Kubernetes Secret @@ -70,48 +80,49 @@ spec: type: string type: object buffering: - description: 'Buffering holds the buffering middleware configuration. - This middleware retries or limits the size of requests that can - be forwarded to backends. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#maxrequestbodybytes' + description: |- + Buffering holds the buffering middleware configuration. + This middleware retries or limits the size of requests that can be forwarded to backends. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#maxrequestbodybytes properties: maxRequestBodyBytes: - description: 'MaxRequestBodyBytes defines the maximum allowed - body size for the request (in bytes). If the request exceeds - the allowed size, it is not forwarded to the service, and the - client gets a 413 (Request Entity Too Large) response. Default: - 0 (no maximum).' + description: |- + MaxRequestBodyBytes defines the maximum allowed body size for the request (in bytes). + If the request exceeds the allowed size, it is not forwarded to the service, and the client gets a 413 (Request Entity Too Large) response. + Default: 0 (no maximum). format: int64 type: integer maxResponseBodyBytes: - description: 'MaxResponseBodyBytes defines the maximum allowed - response size from the service (in bytes). If the response exceeds - the allowed size, it is not forwarded to the client. The client - gets a 500 (Internal Server Error) response instead. Default: - 0 (no maximum).' + description: |- + MaxResponseBodyBytes defines the maximum allowed response size from the service (in bytes). + If the response exceeds the allowed size, it is not forwarded to the client. The client gets a 500 (Internal Server Error) response instead. + Default: 0 (no maximum). format: int64 type: integer memRequestBodyBytes: - description: 'MemRequestBodyBytes defines the threshold (in bytes) - from which the request will be buffered on disk instead of in - memory. Default: 1048576 (1Mi).' + description: |- + MemRequestBodyBytes defines the threshold (in bytes) from which the request will be buffered on disk instead of in memory. + Default: 1048576 (1Mi). format: int64 type: integer memResponseBodyBytes: - description: 'MemResponseBodyBytes defines the threshold (in bytes) - from which the response will be buffered on disk instead of - in memory. Default: 1048576 (1Mi).' + description: |- + MemResponseBodyBytes defines the threshold (in bytes) from which the response will be buffered on disk instead of in memory. + Default: 1048576 (1Mi). format: int64 type: integer retryExpression: - description: 'RetryExpression defines the retry conditions. It - is a logical combination of functions with operators AND (&&) - and OR (||). More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#retryexpression' + description: |- + RetryExpression defines the retry conditions. + It is a logical combination of functions with operators AND (&&) and OR (||). + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#retryexpression type: string type: object chain: - description: 'Chain holds the configuration of the chain middleware. - This middleware enables to define reusable combinations of other - pieces of middleware. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/chain/' + description: |- + Chain holds the configuration of the chain middleware. + This middleware enables to define reusable combinations of other pieces of middleware. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/chain/ properties: middlewares: description: Middlewares is the list of MiddlewareRef which composes @@ -163,15 +174,15 @@ spec: x-kubernetes-int-or-string: true type: object compress: - description: 'Compress holds the compress middleware configuration. - This middleware compresses responses before sending them to the - client, using gzip compression. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/compress/' + description: |- + Compress holds the compress middleware configuration. + This middleware compresses responses before sending them to the client, using gzip compression. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/compress/ properties: excludedContentTypes: - description: ExcludedContentTypes defines the list of content - types to compare the Content-Type header of the incoming requests - and responses before compressing. `application/grpc` is always - excluded. + description: |- + ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing. + `application/grpc` is always excluded. items: type: string type: array @@ -183,30 +194,38 @@ spec: type: string type: array minResponseBodyBytes: - description: 'MinResponseBodyBytes defines the minimum amount - of bytes a response body must have to be compressed. Default: - 1024.' + description: |- + MinResponseBodyBytes defines the minimum amount of bytes a response body must have to be compressed. + Default: 1024. type: integer type: object contentType: - description: ContentType holds the content-type middleware configuration. - This middleware sets the `Content-Type` header value to the media - type detected from the response content, when it is not set by the - backend. + description: |- + ContentType holds the content-type middleware configuration. + This middleware exists to enable the correct behavior until at least the default one can be changed in a future version. + properties: + autoDetect: + description: |- + AutoDetect specifies whether to let the `Content-Type` header, if it has not been set by the backend, + be automatically set to a value derived from the contents of the response. + Deprecated: AutoDetect option is deprecated, Content-Type middleware is only meant to be used to enable the content-type detection, please remove any usage of this option. + type: boolean type: object digestAuth: - description: 'DigestAuth holds the digest auth middleware configuration. + description: |- + DigestAuth holds the digest auth middleware configuration. This middleware restricts access to your services to known users. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/digestauth/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/digestauth/ properties: headerField: - description: 'HeaderField defines a header field to store the - authenticated user. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield' + description: |- + HeaderField defines a header field to store the authenticated user. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield type: string realm: - description: 'Realm allows the protected resources on a server - to be partitioned into a set of protection spaces, each with - its own authentication scheme. Default: traefik.' + description: |- + Realm allows the protected resources on a server to be partitioned into a set of protection spaces, each with its own authentication scheme. + Default: traefik. type: string removeHeader: description: RemoveHeader defines whether to remove the authorization @@ -218,18 +237,20 @@ spec: type: string type: object errors: - description: 'ErrorPage holds the custom error middleware configuration. - This middleware returns a custom page in lieu of the default, according - to configured ranges of HTTP Status codes. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/' + description: |- + ErrorPage holds the custom error middleware configuration. + This middleware returns a custom page in lieu of the default, according to configured ranges of HTTP Status codes. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/ properties: query: - description: Query defines the URL for the error page (hosted - by service). The {status} variable can be used in order to insert - the status code in the URL. + description: |- + Query defines the URL for the error page (hosted by service). + The {status} variable can be used in order to insert the status code in the URL. type: string service: - description: 'Service defines the reference to a Kubernetes Service - that will serve the error page. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/#service' + description: |- + Service defines the reference to a Kubernetes Service that will serve the error page. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/#service properties: kind: description: Kind defines the kind of the Service. @@ -238,31 +259,32 @@ spec: - TraefikService type: string name: - description: Name defines the name of the referenced Kubernetes - Service or TraefikService. The differentiation between the - two is specified in the Kind field. + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. type: string namespace: description: Namespace defines the namespace of the referenced Kubernetes Service or TraefikService. type: string nativeLB: - description: NativeLB controls, when creating the load-balancer, - whether the LB's children are directly the pods IPs or if - the only child is the Kubernetes Service clusterIP. The - Kubernetes Service itself does load-balance to the pods. + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. By default, NativeLB is false. type: boolean passHostHeader: - description: PassHostHeader defines whether the client Host - header is forwarded to the upstream Kubernetes Service. + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean port: anyOf: - type: integer - type: string - description: Port defines the port of a Kubernetes Service. + description: |- + Port defines the port of a Kubernetes Service. This can be a reference to a named port. x-kubernetes-int-or-string: true responseForwarding: @@ -271,29 +293,29 @@ spec: client. properties: flushInterval: - description: 'FlushInterval defines the interval, in milliseconds, - in between flushes to the client while copying the response - body. A negative value means to flush immediately after - each write to the client. This configuration is ignored - when ReverseProxy recognizes a response as a streaming - response; for such responses, writes are flushed to - the client immediately. Default: 100ms' + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; + for such responses, writes are flushed to the client immediately. + Default: 100ms type: string type: object scheme: - description: Scheme defines the scheme to use for the request - to the upstream Kubernetes Service. It defaults to https - when Kubernetes Service port is 443, http otherwise. + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. type: string serversTransport: - description: ServersTransport defines the name of ServersTransport - resource to use. It allows to configure the transport between - Traefik and your servers. Can only be used on a Kubernetes - Service. + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. type: string sticky: - description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -303,17 +325,18 @@ spec: be accessed by client-side APIs, such as JavaScript. type: boolean maxAge: - description: MaxAge indicates the number of seconds - until the cookie expires. When set to a negative - number, the cookie expires immediately. When set - to zero, the cookie never expires. + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. type: integer name: description: Name defines the Cookie name. type: string sameSite: - description: 'SameSite defines the same site policy. - More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite type: string secure: description: Secure defines whether the cookie can @@ -323,32 +346,34 @@ spec: type: object type: object strategy: - description: Strategy defines the load balancing strategy - between the servers. RoundRobin is the only supported value - at the moment. + description: |- + Strategy defines the load balancing strategy between the servers. + RoundRobin is the only supported value at the moment. type: string weight: - description: Weight defines the weight and should only be - specified when Name references a TraefikService object (and - to be precise, one that embeds a Weighted Round Robin). + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object + (and to be precise, one that embeds a Weighted Round Robin). type: integer required: - name type: object status: - description: Status defines which status or range of statuses - should result in an error page. It can be either a status code - as a number (500), as multiple comma-separated numbers (500,502), - as ranges by separating two codes with a dash (500-599), or - a combination of the two (404,418,500-599). + description: |- + Status defines which status or range of statuses should result in an error page. + It can be either a status code as a number (500), + as multiple comma-separated numbers (500,502), + as ranges by separating two codes with a dash (500-599), + or a combination of the two (404,418,500-599). items: type: string type: array type: object forwardAuth: - description: 'ForwardAuth holds the forward auth middleware configuration. + description: |- + ForwardAuth holds the forward auth middleware configuration. This middleware delegates the request authentication to a Service. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/ properties: addAuthCookiesToResponse: description: AddAuthCookiesToResponse defines the list of cookies @@ -360,9 +385,9 @@ spec: description: Address defines the authentication server address. type: string authRequestHeaders: - description: AuthRequestHeaders defines the list of the headers - to copy from the request to the authentication server. If not - set or empty then all request headers are passed. + description: |- + AuthRequestHeaders defines the list of the headers to copy from the request to the authentication server. + If not set or empty then all request headers are passed. items: type: string type: array @@ -374,24 +399,27 @@ spec: type: string type: array authResponseHeadersRegex: - description: 'AuthResponseHeadersRegex defines the regex to match - headers to copy from the authentication server response and - set on forwarded request, after stripping all headers that match - the regex. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/#authresponseheadersregex' + description: |- + AuthResponseHeadersRegex defines the regex to match headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/#authresponseheadersregex type: string tls: description: TLS defines the configuration used to secure the connection to the authentication server. properties: + caOptional: + description: 'Deprecated: TLS client authentication is a server + side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634).' + type: boolean caSecret: - description: CASecret is the name of the referenced Kubernetes - Secret containing the CA to validate the server certificate. + description: |- + CASecret is the name of the referenced Kubernetes Secret containing the CA to validate the server certificate. The CA certificate is extracted from key `tls.ca` or `ca.crt`. type: string certSecret: - description: CertSecret is the name of the referenced Kubernetes - Secret containing the client certificate. The client certificate - is extracted from the keys `tls.crt` and `tls.key`. + description: |- + CertSecret is the name of the referenced Kubernetes Secret containing the client certificate. + The client certificate is extracted from the keys `tls.crt` and `tls.key`. type: string insecureSkipVerify: description: InsecureSkipVerify defines whether the server @@ -404,20 +432,23 @@ spec: type: boolean type: object grpcWeb: - description: GrpcWeb holds the gRPC web middleware configuration. + description: |- + GrpcWeb holds the gRPC web middleware configuration. This middleware converts a gRPC web request to an HTTP/2 gRPC request. properties: allowOrigins: - description: AllowOrigins is a list of allowable origins. Can - also be a wildcard origin "*". + description: |- + AllowOrigins is a list of allowable origins. + Can also be a wildcard origin "*". items: type: string type: array type: object headers: - description: 'Headers holds the headers middleware configuration. - This middleware manages the requests and responses headers. More - info: https://doc.traefik.io/traefik/v3.0/middlewares/http/headers/#customrequestheaders' + description: |- + Headers holds the headers middleware configuration. + This middleware manages the requests and responses headers. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/headers/#customrequestheaders properties: accessControlAllowCredentials: description: AccessControlAllowCredentials defines whether the @@ -482,12 +513,14 @@ spec: header with the nosniff value. type: boolean customBrowserXSSValue: - description: CustomBrowserXSSValue defines the X-XSS-Protection - header value. This overrides the BrowserXssFilter option. + description: |- + CustomBrowserXSSValue defines the X-XSS-Protection header value. + This overrides the BrowserXssFilter option. type: string customFrameOptionsValue: - description: CustomFrameOptionsValue defines the X-Frame-Options - header value. This overrides the FrameDeny option. + description: |- + CustomFrameOptionsValue defines the X-Frame-Options header value. + This overrides the FrameDeny option. type: string customRequestHeaders: additionalProperties: @@ -501,6 +534,10 @@ spec: description: CustomResponseHeaders defines the header names and values to apply to the response. type: object + featurePolicy: + description: 'Deprecated: FeaturePolicy option is deprecated, + please use PermissionsPolicy instead.' + type: string forceSTSHeader: description: ForceSTSHeader defines whether to add the STS header even when the connection is HTTP. @@ -516,34 +553,49 @@ spec: type: string type: array isDevelopment: - description: IsDevelopment defines whether to mitigate the unwanted - effects of the AllowedHosts, SSL, and STS options when developing. - Usually testing takes place using HTTP, not HTTPS, and on localhost, - not your production domain. If you would like your development - environment to mimic production with complete Host blocking, - SSL redirects, and STS headers, leave this as false. + description: |- + IsDevelopment defines whether to mitigate the unwanted effects of the AllowedHosts, SSL, and STS options when developing. + Usually testing takes place using HTTP, not HTTPS, and on localhost, not your production domain. + If you would like your development environment to mimic production with complete Host blocking, SSL redirects, + and STS headers, leave this as false. type: boolean permissionsPolicy: - description: PermissionsPolicy defines the Permissions-Policy - header value. This allows sites to control browser features. + description: |- + PermissionsPolicy defines the Permissions-Policy header value. + This allows sites to control browser features. type: string publicKey: description: PublicKey is the public key that implements HPKP to prevent MITM attacks with forged certificates. type: string referrerPolicy: - description: ReferrerPolicy defines the Referrer-Policy header - value. This allows sites to control whether browsers forward - the Referer header to other sites. + description: |- + ReferrerPolicy defines the Referrer-Policy header value. + This allows sites to control whether browsers forward the Referer header to other sites. + type: string + sslForceHost: + description: 'Deprecated: SSLForceHost option is deprecated, please + use RedirectRegex instead.' + type: boolean + sslHost: + description: 'Deprecated: SSLHost option is deprecated, please + use RedirectRegex instead.' type: string sslProxyHeaders: additionalProperties: type: string - description: 'SSLProxyHeaders defines the header keys with associated - values that would indicate a valid HTTPS request. It can be - useful when using other proxies (example: "X-Forwarded-Proto": - "https").' + description: |- + SSLProxyHeaders defines the header keys with associated values that would indicate a valid HTTPS request. + It can be useful when using other proxies (example: "X-Forwarded-Proto": "https"). type: object + sslRedirect: + description: 'Deprecated: SSLRedirect option is deprecated, please + use EntryPoint redirection or RedirectScheme instead.' + type: boolean + sslTemporaryRedirect: + description: 'Deprecated: SSLTemporaryRedirect option is deprecated, + please use EntryPoint redirection or RedirectScheme instead.' + type: boolean stsIncludeSubdomains: description: STSIncludeSubdomains defines whether the includeSubDomains directive is appended to the Strict-Transport-Security header. @@ -553,33 +605,35 @@ spec: to the Strict-Transport-Security header. type: boolean stsSeconds: - description: STSSeconds defines the max-age of the Strict-Transport-Security - header. If set to 0, the header is not set. + description: |- + STSSeconds defines the max-age of the Strict-Transport-Security header. + If set to 0, the header is not set. format: int64 type: integer type: object inFlightReq: - description: 'InFlightReq holds the in-flight request middleware configuration. - This middleware limits the number of requests being processed and - served concurrently. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/' + description: |- + InFlightReq holds the in-flight request middleware configuration. + This middleware limits the number of requests being processed and served concurrently. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/ properties: amount: - description: Amount defines the maximum amount of allowed simultaneous - in-flight request. The middleware responds with HTTP 429 Too - Many Requests if there are already amount requests in progress - (based on the same sourceCriterion strategy). + description: |- + Amount defines the maximum amount of allowed simultaneous in-flight request. + The middleware responds with HTTP 429 Too Many Requests if there are already amount requests in progress (based on the same sourceCriterion strategy). format: int64 type: integer sourceCriterion: - description: 'SourceCriterion defines what criterion is used to - group requests as originating from a common source. If several - strategies are defined at the same time, an error will be raised. - If none are set, the default is to use the requestHost. More - info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/#sourcecriterion' + description: |- + SourceCriterion defines what criterion is used to group requests as originating from a common source. + If several strategies are defined at the same time, an error will be raised. + If none are set, the default is to use the requestHost. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/#sourcecriterion properties: ipStrategy: - description: 'IPStrategy holds the IP strategy configuration - used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' + description: |- + IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -605,13 +659,15 @@ spec: type: object type: object ipAllowList: - description: 'IPAllowList holds the IP allowlist middleware configuration. + description: |- + IPAllowList holds the IP allowlist middleware configuration. This middleware accepts / refuses requests based on the client IP. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/ properties: ipStrategy: - description: 'IPStrategy holds the IP strategy configuration used - by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' + description: |- + IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -626,8 +682,9 @@ spec: type: array type: object rejectStatusCode: - description: RejectStatusCode defines the HTTP status code used - for refused requests. If not set, the default is 403 (Forbidden). + description: |- + RejectStatusCode defines the HTTP status code used for refused requests. + If not set, the default is 403 (Forbidden). type: integer sourceRange: description: SourceRange defines the set of allowed IPs (or ranges @@ -640,8 +697,9 @@ spec: description: 'Deprecated: please use IPAllowList instead.' properties: ipStrategy: - description: 'IPStrategy holds the IP strategy configuration used - by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' + description: |- + IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -663,9 +721,10 @@ spec: type: array type: object passTLSClientCert: - description: 'PassTLSClientCert holds the pass TLS client cert middleware - configuration. This middleware adds the selected data from the passed - client TLS certificate to a header. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/passtlsclientcert/' + description: |- + PassTLSClientCert holds the pass TLS client cert middleware configuration. + This middleware adds the selected data from the passed client TLS certificate to a header. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/passtlsclientcert/ properties: info: description: Info selects the specific client certificate details @@ -766,46 +825,48 @@ spec: plugin: additionalProperties: x-kubernetes-preserve-unknown-fields: true - description: 'Plugin defines the middleware plugin configuration. - More info: https://doc.traefik.io/traefik/plugins/' + description: |- + Plugin defines the middleware plugin configuration. + More info: https://doc.traefik.io/traefik/plugins/ type: object rateLimit: - description: 'RateLimit holds the rate limit configuration. This middleware - ensures that services will receive a fair amount of requests, and - allows one to define what fair is. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ratelimit/' + description: |- + RateLimit holds the rate limit configuration. + This middleware ensures that services will receive a fair amount of requests, and allows one to define what fair is. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ratelimit/ properties: average: - description: Average is the maximum rate, by default in requests/s, - allowed for the given source. It defaults to 0, which means - no rate limiting. The rate is actually defined by dividing Average - by Period. So for a rate below 1req/s, one needs to define a - Period larger than a second. + description: |- + Average is the maximum rate, by default in requests/s, allowed for the given source. + It defaults to 0, which means no rate limiting. + The rate is actually defined by dividing Average by Period. So for a rate below 1req/s, + one needs to define a Period larger than a second. format: int64 type: integer burst: - description: Burst is the maximum number of requests allowed to - arrive in the same arbitrarily small period of time. It defaults - to 1. + description: |- + Burst is the maximum number of requests allowed to arrive in the same arbitrarily small period of time. + It defaults to 1. format: int64 type: integer period: anyOf: - type: integer - type: string - description: 'Period, in combination with Average, defines the - actual maximum rate, such as: r = Average / Period. It defaults - to a second.' + description: |- + Period, in combination with Average, defines the actual maximum rate, such as: + r = Average / Period. It defaults to a second. x-kubernetes-int-or-string: true sourceCriterion: - description: SourceCriterion defines what criterion is used to - group requests as originating from a common source. If several - strategies are defined at the same time, an error will be raised. - If none are set, the default is to use the request's remote - address field (as an ipStrategy). + description: |- + SourceCriterion defines what criterion is used to group requests as originating from a common source. + If several strategies are defined at the same time, an error will be raised. + If none are set, the default is to use the request's remote address field (as an ipStrategy). properties: ipStrategy: - description: 'IPStrategy holds the IP strategy configuration - used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' + description: |- + IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -831,9 +892,10 @@ spec: type: object type: object redirectRegex: - description: 'RedirectRegex holds the redirect regex middleware configuration. + description: |- + RedirectRegex holds the redirect regex middleware configuration. This middleware redirects a request using regex matching and replacement. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectregex/#regex' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectregex/#regex properties: permanent: description: Permanent defines whether the redirection is permanent @@ -849,9 +911,10 @@ spec: type: string type: object redirectScheme: - description: 'RedirectScheme holds the redirect scheme middleware - configuration. This middleware redirects requests from a scheme/port - to another. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectscheme/' + description: |- + RedirectScheme holds the redirect scheme middleware configuration. + This middleware redirects requests from a scheme/port to another. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectscheme/ properties: permanent: description: Permanent defines whether the redirection is permanent @@ -865,9 +928,10 @@ spec: type: string type: object replacePath: - description: 'ReplacePath holds the replace path middleware configuration. - This middleware replaces the path of the request URL and store the - original path in an X-Replaced-Path header. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepath/' + description: |- + ReplacePath holds the replace path middleware configuration. + This middleware replaces the path of the request URL and store the original path in an X-Replaced-Path header. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepath/ properties: path: description: Path defines the path to use as replacement in the @@ -875,9 +939,10 @@ spec: type: string type: object replacePathRegex: - description: 'ReplacePathRegex holds the replace path regex middleware - configuration. This middleware replaces the path of a URL using - regex matching and replacement. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepathregex/' + description: |- + ReplacePathRegex holds the replace path regex middleware configuration. + This middleware replaces the path of a URL using regex matching and replacement. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepathregex/ properties: regex: description: Regex defines the regular expression used to match @@ -889,11 +954,11 @@ spec: type: string type: object retry: - description: 'Retry holds the retry middleware configuration. This - 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. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/retry/' + description: |- + Retry holds the retry middleware configuration. + This 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. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/retry/ properties: attempts: description: Attempts defines how many times the request should @@ -903,18 +968,26 @@ spec: anyOf: - type: integer - type: string - description: InitialInterval defines the first wait time in the - exponential backoff series. The maximum interval is calculated - as twice the initialInterval. If unspecified, requests will - be retried immediately. The value of initialInterval should - be provided in seconds or as a valid duration format, see https://pkg.go.dev/time#ParseDuration. + description: |- + InitialInterval defines the first wait time in the exponential backoff series. + The maximum interval is calculated as twice the initialInterval. + If unspecified, requests will be retried immediately. + The value of initialInterval should be provided in seconds or as a valid duration format, + see https://pkg.go.dev/time#ParseDuration. x-kubernetes-int-or-string: true type: object stripPrefix: - description: 'StripPrefix holds the strip prefix middleware configuration. + description: |- + StripPrefix holds the strip prefix middleware configuration. This middleware removes the specified prefixes from the URL path. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefix/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefix/ properties: + forceSlash: + description: |- + Deprecated: ForceSlash option is deprecated, please remove any usage of this option. + ForceSlash ensures that the resulting stripped path is not the empty string, by replacing it with / when necessary. + Default: true. + type: boolean prefixes: description: Prefixes defines the prefixes to strip from the request URL. @@ -923,9 +996,10 @@ spec: type: array type: object stripPrefixRegex: - description: 'StripPrefixRegex holds the strip prefix regex middleware - configuration. This middleware removes the matching prefixes from - the URL path. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefixregex/' + description: |- + StripPrefixRegex holds the strip prefix regex middleware configuration. + This middleware removes the matching prefixes from the URL path. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefixregex/ properties: regex: description: Regex defines the regular expression to match the diff --git a/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml b/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml index 616e48b9c..250ac1b12 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: middlewaretcps.traefik.io spec: group: traefik.io @@ -17,18 +17,24 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: 'MiddlewareTCP is the CRD implementation of a Traefik TCP middleware. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/overview/' + description: |- + MiddlewareTCP is the CRD implementation of a Traefik TCP middleware. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/overview/ properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -39,14 +45,17 @@ spec: description: InFlightConn defines the InFlightConn middleware configuration. properties: amount: - description: Amount defines the maximum amount of allowed simultaneous - connections. The middleware closes the connection if there are - already amount connections opened. + description: |- + Amount defines the maximum amount of allowed simultaneous connections. + The middleware closes the connection if there are already amount connections opened. format: int64 type: integer type: object ipAllowList: - description: IPAllowList defines the IPAllowList middleware configuration. + description: |- + IPAllowList defines the IPAllowList middleware configuration. + This middleware accepts/refuses connections based on the client IP. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/tcp/ipallowlist/ properties: sourceRange: description: SourceRange defines the allowed IPs (or ranges of @@ -56,8 +65,11 @@ spec: type: array type: object ipWhiteList: - description: 'IPWhiteList defines the IPWhiteList middleware configuration. - Deprecated: please use IPAllowList instead.' + description: |- + IPWhiteList defines the IPWhiteList middleware configuration. + This middleware accepts/refuses connections based on the client IP. + Deprecated: please use IPAllowList instead. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/tcp/ipwhitelist/ properties: sourceRange: description: SourceRange defines the allowed IPs (or ranges of diff --git a/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml b/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml index 3ac912f6f..287943fbf 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: serverstransports.traefik.io spec: group: traefik.io @@ -17,20 +17,26 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: 'ServersTransport is the CRD implementation of a ServersTransport. + description: |- + ServersTransport is the CRD implementation of a ServersTransport. If no serversTransport is specified, the default@internal will be used. The default@internal serversTransport is created from the static configuration. - More info: https://doc.traefik.io/traefik/v3.0/routing/services/#serverstransport_1' + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#serverstransport_1 properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object diff --git a/docs/content/reference/dynamic-configuration/traefik.io_serverstransporttcps.yaml b/docs/content/reference/dynamic-configuration/traefik.io_serverstransporttcps.yaml index 22b76caa2..b255d3296 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_serverstransporttcps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_serverstransporttcps.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: serverstransporttcps.traefik.io spec: group: traefik.io @@ -17,20 +17,26 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: 'ServersTransportTCP is the CRD implementation of a TCPServersTransport. - If no tcpServersTransport is specified, a default one named default@internal - will be used. The default@internal tcpServersTransport can be configured - in the static configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/services/#serverstransport_3' + description: |- + ServersTransportTCP is the CRD implementation of a TCPServersTransport. + If no tcpServersTransport is specified, a default one named default@internal will be used. + The default@internal tcpServersTransport can be configured in the static configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#serverstransport_3 properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -76,9 +82,9 @@ spec: description: InsecureSkipVerify disables TLS certificate verification. type: boolean peerCertURI: - description: MaxIdleConnsPerHost controls the maximum idle (keep-alive) - to keep per-host. PeerCertURI defines the peer cert URI used - to match against SAN URI during the peer certificate verification. + description: |- + MaxIdleConnsPerHost controls the maximum idle (keep-alive) to keep per-host. + PeerCertURI defines the peer cert URI used to match against SAN URI during the peer certificate verification. type: string rootCAsSecrets: description: RootCAsSecrets defines a list of CA secret used to diff --git a/docs/content/reference/dynamic-configuration/traefik.io_tlsoptions.yaml b/docs/content/reference/dynamic-configuration/traefik.io_tlsoptions.yaml index 925e4c025..2380e8ef6 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_tlsoptions.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_tlsoptions.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: tlsoptions.traefik.io spec: group: traefik.io @@ -17,19 +17,24 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: 'TLSOption is the CRD implementation of a Traefik TLS Option, - allowing to configure some parameters of the TLS connection. More info: - https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options' + description: |- + TLSOption is the CRD implementation of a Traefik TLS Option, allowing to configure some parameters of the TLS connection. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -37,15 +42,16 @@ spec: description: TLSOptionSpec defines the desired state of a TLSOption. properties: alpnProtocols: - description: 'ALPNProtocols defines the list of supported application - level protocols for the TLS handshake, in order of preference. More - info: https://doc.traefik.io/traefik/v3.0/https/tls/#alpn-protocols' + description: |- + ALPNProtocols defines the list of supported application level protocols for the TLS handshake, in order of preference. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#alpn-protocols items: type: string type: array cipherSuites: - description: 'CipherSuites defines the list of supported cipher suites - for TLS versions up to TLS 1.2. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#cipher-suites' + description: |- + CipherSuites defines the list of supported cipher suites for TLS versions up to TLS 1.2. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#cipher-suites items: type: string type: array @@ -71,21 +77,30 @@ spec: type: array type: object curvePreferences: - description: 'CurvePreferences defines the preferred elliptic curves - in a specific order. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#curve-preferences' + description: |- + CurvePreferences defines the preferred elliptic curves in a specific order. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#curve-preferences items: type: string type: array maxVersion: - description: 'MaxVersion defines the maximum TLS version that Traefik - will accept. Possible values: VersionTLS10, VersionTLS11, VersionTLS12, - VersionTLS13. Default: None.' + description: |- + MaxVersion defines the maximum TLS version that Traefik will accept. + Possible values: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13. + Default: None. type: string minVersion: - description: 'MinVersion defines the minimum TLS version that Traefik - will accept. Possible values: VersionTLS10, VersionTLS11, VersionTLS12, - VersionTLS13. Default: VersionTLS10.' + description: |- + MinVersion defines the minimum TLS version that Traefik will accept. + Possible values: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13. + Default: VersionTLS10. type: string + preferServerCipherSuites: + description: |- + PreferServerCipherSuites defines whether the server chooses a cipher suite among his own instead of among the client's. + It is enabled automatically when minVersion or maxVersion is set. + Deprecated: https://github.com/golang/go/issues/45430 + type: boolean sniStrict: description: SniStrict defines whether Traefik allows connections from clients connections that do not specify a server_name extension. diff --git a/docs/content/reference/dynamic-configuration/traefik.io_tlsstores.yaml b/docs/content/reference/dynamic-configuration/traefik.io_tlsstores.yaml index efafd398c..15c4951ea 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_tlsstores.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_tlsstores.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: tlsstores.traefik.io spec: group: traefik.io @@ -17,20 +17,26 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: 'TLSStore is the CRD implementation of a Traefik TLS Store. For - the time being, only the TLSStore named default is supported. This means - that you cannot have two stores that are named default in different Kubernetes - namespaces. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#certificates-stores' + description: |- + TLSStore is the CRD implementation of a Traefik TLS Store. + For the time being, only the TLSStore named default is supported. + This means that you cannot have two stores that are named default in different Kubernetes namespaces. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#certificates-stores properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object diff --git a/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml b/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml index 8620b4d74..7c8f58a3e 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: traefikservices.traefik.io spec: group: traefik.io @@ -17,19 +17,27 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: 'TraefikService is the CRD implementation of a Traefik Service. - TraefikService object allows to: - Apply weight to Services on load-balancing - - Mirror traffic on services More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-traefikservice' + description: |- + TraefikService is the CRD implementation of a Traefik Service. + TraefikService object allows to: + - Apply weight to Services on load-balancing + - Mirror traffic on services + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-traefikservice properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -46,10 +54,10 @@ spec: - TraefikService type: string maxBodySize: - description: MaxBodySize defines the maximum size allowed for - the body of the request. If the body is larger, the request - is not mirrored. Default value is -1, which means unlimited - size. + description: |- + MaxBodySize defines the maximum size allowed for the body of the request. + If the body is larger, the request is not mirrored. + Default value is -1, which means unlimited size. format: int64 type: integer mirrors: @@ -65,35 +73,37 @@ spec: - TraefikService type: string name: - description: Name defines the name of the referenced Kubernetes - Service or TraefikService. The differentiation between - the two is specified in the Kind field. + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. type: string namespace: description: Namespace defines the namespace of the referenced Kubernetes Service or TraefikService. type: string nativeLB: - description: NativeLB controls, when creating the load-balancer, - whether the LB's children are directly the pods IPs or - if the only child is the Kubernetes Service clusterIP. - The Kubernetes Service itself does load-balance to the - pods. By default, NativeLB is false. + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. type: boolean passHostHeader: - description: PassHostHeader defines whether the client Host - header is forwarded to the upstream Kubernetes Service. + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean percent: - description: 'Percent defines the part of the traffic to - mirror. Supported values: 0 to 100.' + description: |- + Percent defines the part of the traffic to mirror. + Supported values: 0 to 100. type: integer port: anyOf: - type: integer - type: string - description: Port defines the port of a Kubernetes Service. + description: |- + Port defines the port of a Kubernetes Service. This can be a reference to a named port. x-kubernetes-int-or-string: true responseForwarding: @@ -102,30 +112,29 @@ spec: client. properties: flushInterval: - description: 'FlushInterval defines the interval, in - milliseconds, in between flushes to the client while - copying the response body. A negative value means - to flush immediately after each write to the client. - This configuration is ignored when ReverseProxy recognizes - a response as a streaming response; for such responses, - writes are flushed to the client immediately. Default: - 100ms' + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; + for such responses, writes are flushed to the client immediately. + Default: 100ms type: string type: object scheme: - description: Scheme defines the scheme to use for the request - to the upstream Kubernetes Service. It defaults to https - when Kubernetes Service port is 443, http otherwise. + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. type: string serversTransport: - description: ServersTransport defines the name of ServersTransport - resource to use. It allows to configure the transport - between Traefik and your servers. Can only be used on - a Kubernetes Service. + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. type: string sticky: - description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -135,17 +144,18 @@ spec: can be accessed by client-side APIs, such as JavaScript. type: boolean maxAge: - description: MaxAge indicates the number of seconds - until the cookie expires. When set to a negative - number, the cookie expires immediately. When set - to zero, the cookie never expires. + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. type: integer name: description: Name defines the Cookie name. type: string sameSite: - description: 'SameSite defines the same site policy. - More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite type: string secure: description: Secure defines whether the cookie can @@ -155,13 +165,13 @@ spec: type: object type: object strategy: - description: Strategy defines the load balancing strategy - between the servers. RoundRobin is the only supported - value at the moment. + description: |- + Strategy defines the load balancing strategy between the servers. + RoundRobin is the only supported value at the moment. type: string weight: - description: Weight defines the weight and should only be - specified when Name references a TraefikService object + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object (and to be precise, one that embeds a Weighted Round Robin). type: integer required: @@ -169,60 +179,62 @@ spec: type: object type: array name: - description: Name defines the name of the referenced Kubernetes - Service or TraefikService. The differentiation between the two - is specified in the Kind field. + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. type: string namespace: description: Namespace defines the namespace of the referenced Kubernetes Service or TraefikService. type: string nativeLB: - description: NativeLB controls, when creating the load-balancer, - whether the LB's children are directly the pods IPs or if the - only child is the Kubernetes Service clusterIP. The Kubernetes - Service itself does load-balance to the pods. By default, NativeLB - is false. + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. type: boolean passHostHeader: - description: PassHostHeader defines whether the client Host header - is forwarded to the upstream Kubernetes Service. By default, - passHostHeader is true. + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. + By default, passHostHeader is true. type: boolean port: anyOf: - type: integer - type: string - description: Port defines the port of a Kubernetes Service. This - can be a reference to a named port. + description: |- + Port defines the port of a Kubernetes Service. + This can be a reference to a named port. x-kubernetes-int-or-string: true responseForwarding: description: ResponseForwarding defines how Traefik forwards the response from the upstream Kubernetes Service to the client. properties: flushInterval: - description: 'FlushInterval defines the interval, in milliseconds, - in between flushes to the client while copying the response - body. A negative value means to flush immediately after - each write to the client. This configuration is ignored - when ReverseProxy recognizes a response as a streaming response; + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; for such responses, writes are flushed to the client immediately. - Default: 100ms' + Default: 100ms type: string type: object scheme: - description: Scheme defines the scheme to use for the request - to the upstream Kubernetes Service. It defaults to https when - Kubernetes Service port is 443, http otherwise. + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. type: string serversTransport: - description: ServersTransport defines the name of ServersTransport - resource to use. It allows to configure the transport between - Traefik and your servers. Can only be used on a Kubernetes Service. + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. type: string sticky: - description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -232,17 +244,18 @@ spec: accessed by client-side APIs, such as JavaScript. type: boolean maxAge: - description: MaxAge indicates the number of seconds until - the cookie expires. When set to a negative number, the - cookie expires immediately. When set to zero, the cookie - never expires. + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. type: integer name: description: Name defines the Cookie name. type: string sameSite: - description: 'SameSite defines the same site policy. More - info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite type: string secure: description: Secure defines whether the cookie can only @@ -251,13 +264,14 @@ spec: type: object type: object strategy: - description: Strategy defines the load balancing strategy between - the servers. RoundRobin is the only supported value at the moment. + description: |- + Strategy defines the load balancing strategy between the servers. + RoundRobin is the only supported value at the moment. type: string weight: - description: Weight defines the weight and should only be specified - when Name references a TraefikService object (and to be precise, - one that embeds a Weighted Round Robin). + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object + (and to be precise, one that embeds a Weighted Round Robin). type: integer required: - name @@ -279,31 +293,32 @@ spec: - TraefikService type: string name: - description: Name defines the name of the referenced Kubernetes - Service or TraefikService. The differentiation between - the two is specified in the Kind field. + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. type: string namespace: description: Namespace defines the namespace of the referenced Kubernetes Service or TraefikService. type: string nativeLB: - description: NativeLB controls, when creating the load-balancer, - whether the LB's children are directly the pods IPs or - if the only child is the Kubernetes Service clusterIP. - The Kubernetes Service itself does load-balance to the - pods. By default, NativeLB is false. + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. type: boolean passHostHeader: - description: PassHostHeader defines whether the client Host - header is forwarded to the upstream Kubernetes Service. + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean port: anyOf: - type: integer - type: string - description: Port defines the port of a Kubernetes Service. + description: |- + Port defines the port of a Kubernetes Service. This can be a reference to a named port. x-kubernetes-int-or-string: true responseForwarding: @@ -312,30 +327,29 @@ spec: client. properties: flushInterval: - description: 'FlushInterval defines the interval, in - milliseconds, in between flushes to the client while - copying the response body. A negative value means - to flush immediately after each write to the client. - This configuration is ignored when ReverseProxy recognizes - a response as a streaming response; for such responses, - writes are flushed to the client immediately. Default: - 100ms' + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; + for such responses, writes are flushed to the client immediately. + Default: 100ms type: string type: object scheme: - description: Scheme defines the scheme to use for the request - to the upstream Kubernetes Service. It defaults to https - when Kubernetes Service port is 443, http otherwise. + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. type: string serversTransport: - description: ServersTransport defines the name of ServersTransport - resource to use. It allows to configure the transport - between Traefik and your servers. Can only be used on - a Kubernetes Service. + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. type: string sticky: - description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -345,17 +359,18 @@ spec: can be accessed by client-side APIs, such as JavaScript. type: boolean maxAge: - description: MaxAge indicates the number of seconds - until the cookie expires. When set to a negative - number, the cookie expires immediately. When set - to zero, the cookie never expires. + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. type: integer name: description: Name defines the Cookie name. type: string sameSite: - description: 'SameSite defines the same site policy. - More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite type: string secure: description: Secure defines whether the cookie can @@ -365,13 +380,13 @@ spec: type: object type: object strategy: - description: Strategy defines the load balancing strategy - between the servers. RoundRobin is the only supported - value at the moment. + description: |- + Strategy defines the load balancing strategy between the servers. + RoundRobin is the only supported value at the moment. type: string weight: - description: Weight defines the weight and should only be - specified when Name references a TraefikService object + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object (and to be precise, one that embeds a Weighted Round Robin). type: integer required: @@ -379,8 +394,9 @@ spec: type: object type: array sticky: - description: 'Sticky defines whether sticky sessions are enabled. - More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#stickiness-and-load-balancing' + description: |- + Sticky defines whether sticky sessions are enabled. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#stickiness-and-load-balancing properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -390,17 +406,18 @@ spec: accessed by client-side APIs, such as JavaScript. type: boolean maxAge: - description: MaxAge indicates the number of seconds until - the cookie expires. When set to a negative number, the - cookie expires immediately. When set to zero, the cookie - never expires. + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. type: integer name: description: Name defines the Cookie name. type: string sameSite: - description: 'SameSite defines the same site policy. More - info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite type: string secure: description: Secure defines whether the cookie can only diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index aabed9139..179e376c5 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -6,6 +6,9 @@ THIS FILE MUST NOT BE EDITED BY HAND `--accesslog`: Access log settings. (Default: ```false```) +`--accesslog.addinternals`: +Enables access log for internal services (ping, dashboard, etc...). (Default: ```false```) + `--accesslog.bufferingsize`: Number of access log lines to process in a buffered way. (Default: ```0```) @@ -105,6 +108,9 @@ Activate TLS-ALPN-01 Challenge. (Default: ```true```) `--certificatesresolvers..tailscale`: Enables Tailscale certificate resolution. (Default: ```true```) +`--core.defaultrulesyntax`: +Defines the rule parser default syntax (v2 or v3) (Default: ```v3```) + `--entrypoints.`: Entry points definition. (Default: ```false```) @@ -177,6 +183,9 @@ Trust all. (Default: ```false```) `--entrypoints..proxyprotocol.trustedips`: Trust only selected IPs. +`--entrypoints..reuseport`: +Enables EntryPoints from the same or different processes listening on the same TCP/UDP port. (Default: ```false```) + `--entrypoints..transport.keepalivemaxrequests`: Maximum number of requests before closing a keep-alive connection. (Default: ```0```) @@ -261,6 +270,9 @@ Maximum size in megabytes of the log file before it gets rotated. (Default: ```0 `--log.nocolor`: When using the 'common' format, disables the colorized output. (Default: ```false```) +`--metrics.addinternals`: +Enables metrics for internal services (ping, dashboard, etc...). (Default: ```false```) + `--metrics.datadog`: Datadog metrics exporter type. (Default: ```false```) @@ -312,51 +324,63 @@ InfluxDB v2 push interval. (Default: ```10```) `--metrics.influxdb2.token`: InfluxDB v2 access token. -`--metrics.opentelemetry`: +`--metrics.otlp`: OpenTelemetry metrics exporter type. (Default: ```false```) -`--metrics.opentelemetry.addentrypointslabels`: +`--metrics.otlp.addentrypointslabels`: Enable metrics on entry points. (Default: ```true```) -`--metrics.opentelemetry.address`: -Address (host:port) of the collector endpoint. (Default: ```localhost:4318```) - -`--metrics.opentelemetry.addrouterslabels`: +`--metrics.otlp.addrouterslabels`: Enable metrics on routers. (Default: ```false```) -`--metrics.opentelemetry.addserviceslabels`: +`--metrics.otlp.addserviceslabels`: Enable metrics on services. (Default: ```true```) -`--metrics.opentelemetry.explicitboundaries`: +`--metrics.otlp.explicitboundaries`: Boundaries for latency metrics. (Default: ```0.005000, 0.010000, 0.025000, 0.050000, 0.100000, 0.250000, 0.500000, 1.000000, 2.500000, 5.000000, 10.000000```) -`--metrics.opentelemetry.grpc`: -gRPC specific configuration for the OpenTelemetry collector. (Default: ```true```) +`--metrics.otlp.grpc.endpoint`: +Sets the gRPC endpoint (host:port) of the collector. (Default: ```localhost:4317```) -`--metrics.opentelemetry.headers.`: +`--metrics.otlp.grpc.headers.`: Headers sent with payload. -`--metrics.opentelemetry.insecure`: +`--metrics.otlp.grpc.insecure`: Disables client transport security for the exporter. (Default: ```false```) -`--metrics.opentelemetry.path`: -Set the URL path of the collector endpoint. - -`--metrics.opentelemetry.pushinterval`: -Period between calls to collect a checkpoint. (Default: ```10```) - -`--metrics.opentelemetry.tls.ca`: +`--metrics.otlp.grpc.tls.ca`: TLS CA -`--metrics.opentelemetry.tls.cert`: +`--metrics.otlp.grpc.tls.cert`: TLS cert -`--metrics.opentelemetry.tls.insecureskipverify`: +`--metrics.otlp.grpc.tls.insecureskipverify`: TLS insecure skip verify (Default: ```false```) -`--metrics.opentelemetry.tls.key`: +`--metrics.otlp.grpc.tls.key`: TLS key +`--metrics.otlp.http.endpoint`: +Sets the HTTP endpoint (scheme://host:port/path) of the collector. (Default: ```https://localhost:4318```) + +`--metrics.otlp.http.headers.`: +Headers sent with payload. + +`--metrics.otlp.http.tls.ca`: +TLS CA + +`--metrics.otlp.http.tls.cert`: +TLS cert + +`--metrics.otlp.http.tls.insecureskipverify`: +TLS insecure skip verify (Default: ```false```) + +`--metrics.otlp.http.tls.key`: +TLS key + +`--metrics.otlp.pushinterval`: +Period between calls to collect a checkpoint. (Default: ```10```) + `--metrics.prometheus`: Prometheus metrics exporter type. (Default: ```false```) @@ -987,18 +1011,21 @@ Defines the allowed SPIFFE trust domain. `--tracing`: OpenTracing configuration. (Default: ```false```) +`--tracing.addinternals`: +Enables tracing for internal services (ping, dashboard, etc...). (Default: ```false```) + `--tracing.globalattributes.`: Defines additional attributes (key:value) on all spans. -`--tracing.headers.`: -Defines additional headers to be sent with the payloads. - `--tracing.otlp`: Settings for OpenTelemetry. (Default: ```false```) `--tracing.otlp.grpc.endpoint`: Sets the gRPC endpoint (host:port) of the collector. (Default: ```localhost:4317```) +`--tracing.otlp.grpc.headers.`: +Headers sent with payload. + `--tracing.otlp.grpc.insecure`: Disables client transport security for the exporter. (Default: ```false```) @@ -1015,7 +1042,10 @@ TLS insecure skip verify (Default: ```false```) TLS key `--tracing.otlp.http.endpoint`: -Sets the HTTP endpoint (scheme://host:port/v1/traces) of the collector. (Default: ```localhost:4318```) +Sets the HTTP endpoint (scheme://host:port/path) of the collector. (Default: ```https://localhost:4318```) + +`--tracing.otlp.http.headers.`: +Headers sent with payload. `--tracing.otlp.http.tls.ca`: TLS CA diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index 892fb2369..e2a2e73f6 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -6,6 +6,9 @@ THIS FILE MUST NOT BE EDITED BY HAND `TRAEFIK_ACCESSLOG`: Access log settings. (Default: ```false```) +`TRAEFIK_ACCESSLOG_ADDINTERNALS`: +Enables access log for internal services (ping, dashboard, etc...). (Default: ```false```) + `TRAEFIK_ACCESSLOG_BUFFERINGSIZE`: Number of access log lines to process in a buffered way. (Default: ```0```) @@ -105,6 +108,9 @@ Activate TLS-ALPN-01 Challenge. (Default: ```true```) `TRAEFIK_CERTIFICATESRESOLVERS__TAILSCALE`: Enables Tailscale certificate resolution. (Default: ```true```) +`TRAEFIK_CORE_DEFAULTRULESYNTAX`: +Defines the rule parser default syntax (v2 or v3) (Default: ```v3```) + `TRAEFIK_ENTRYPOINTS_`: Entry points definition. (Default: ```false```) @@ -177,6 +183,9 @@ Trust all. (Default: ```false```) `TRAEFIK_ENTRYPOINTS__PROXYPROTOCOL_TRUSTEDIPS`: Trust only selected IPs. +`TRAEFIK_ENTRYPOINTS__REUSEPORT`: +Enables EntryPoints from the same or different processes listening on the same TCP/UDP port. (Default: ```false```) + `TRAEFIK_ENTRYPOINTS__TRANSPORT_KEEPALIVEMAXREQUESTS`: Maximum number of requests before closing a keep-alive connection. (Default: ```0```) @@ -261,6 +270,9 @@ Maximum size in megabytes of the log file before it gets rotated. (Default: ```0 `TRAEFIK_LOG_NOCOLOR`: When using the 'common' format, disables the colorized output. (Default: ```false```) +`TRAEFIK_METRICS_ADDINTERNALS`: +Enables metrics for internal services (ping, dashboard, etc...). (Default: ```false```) + `TRAEFIK_METRICS_DATADOG`: Datadog metrics exporter type. (Default: ```false```) @@ -312,51 +324,63 @@ InfluxDB v2 push interval. (Default: ```10```) `TRAEFIK_METRICS_INFLUXDB2_TOKEN`: InfluxDB v2 access token. -`TRAEFIK_METRICS_OPENTELEMETRY`: +`TRAEFIK_METRICS_OTLP`: OpenTelemetry metrics exporter type. (Default: ```false```) -`TRAEFIK_METRICS_OPENTELEMETRY_ADDENTRYPOINTSLABELS`: +`TRAEFIK_METRICS_OTLP_ADDENTRYPOINTSLABELS`: Enable metrics on entry points. (Default: ```true```) -`TRAEFIK_METRICS_OPENTELEMETRY_ADDRESS`: -Address (host:port) of the collector endpoint. (Default: ```localhost:4318```) - -`TRAEFIK_METRICS_OPENTELEMETRY_ADDROUTERSLABELS`: +`TRAEFIK_METRICS_OTLP_ADDROUTERSLABELS`: Enable metrics on routers. (Default: ```false```) -`TRAEFIK_METRICS_OPENTELEMETRY_ADDSERVICESLABELS`: +`TRAEFIK_METRICS_OTLP_ADDSERVICESLABELS`: Enable metrics on services. (Default: ```true```) -`TRAEFIK_METRICS_OPENTELEMETRY_EXPLICITBOUNDARIES`: +`TRAEFIK_METRICS_OTLP_EXPLICITBOUNDARIES`: Boundaries for latency metrics. (Default: ```0.005000, 0.010000, 0.025000, 0.050000, 0.100000, 0.250000, 0.500000, 1.000000, 2.500000, 5.000000, 10.000000```) -`TRAEFIK_METRICS_OPENTELEMETRY_GRPC`: -gRPC specific configuration for the OpenTelemetry collector. (Default: ```true```) +`TRAEFIK_METRICS_OTLP_GRPC_ENDPOINT`: +Sets the gRPC endpoint (host:port) of the collector. (Default: ```localhost:4317```) -`TRAEFIK_METRICS_OPENTELEMETRY_HEADERS_`: +`TRAEFIK_METRICS_OTLP_GRPC_HEADERS_`: Headers sent with payload. -`TRAEFIK_METRICS_OPENTELEMETRY_INSECURE`: +`TRAEFIK_METRICS_OTLP_GRPC_INSECURE`: Disables client transport security for the exporter. (Default: ```false```) -`TRAEFIK_METRICS_OPENTELEMETRY_PATH`: -Set the URL path of the collector endpoint. - -`TRAEFIK_METRICS_OPENTELEMETRY_PUSHINTERVAL`: -Period between calls to collect a checkpoint. (Default: ```10```) - -`TRAEFIK_METRICS_OPENTELEMETRY_TLS_CA`: +`TRAEFIK_METRICS_OTLP_GRPC_TLS_CA`: TLS CA -`TRAEFIK_METRICS_OPENTELEMETRY_TLS_CERT`: +`TRAEFIK_METRICS_OTLP_GRPC_TLS_CERT`: TLS cert -`TRAEFIK_METRICS_OPENTELEMETRY_TLS_INSECURESKIPVERIFY`: +`TRAEFIK_METRICS_OTLP_GRPC_TLS_INSECURESKIPVERIFY`: TLS insecure skip verify (Default: ```false```) -`TRAEFIK_METRICS_OPENTELEMETRY_TLS_KEY`: +`TRAEFIK_METRICS_OTLP_GRPC_TLS_KEY`: TLS key +`TRAEFIK_METRICS_OTLP_HTTP_ENDPOINT`: +Sets the HTTP endpoint (scheme://host:port/path) of the collector. (Default: ```https://localhost:4318```) + +`TRAEFIK_METRICS_OTLP_HTTP_HEADERS_`: +Headers sent with payload. + +`TRAEFIK_METRICS_OTLP_HTTP_TLS_CA`: +TLS CA + +`TRAEFIK_METRICS_OTLP_HTTP_TLS_CERT`: +TLS cert + +`TRAEFIK_METRICS_OTLP_HTTP_TLS_INSECURESKIPVERIFY`: +TLS insecure skip verify (Default: ```false```) + +`TRAEFIK_METRICS_OTLP_HTTP_TLS_KEY`: +TLS key + +`TRAEFIK_METRICS_OTLP_PUSHINTERVAL`: +Period between calls to collect a checkpoint. (Default: ```10```) + `TRAEFIK_METRICS_PROMETHEUS`: Prometheus metrics exporter type. (Default: ```false```) @@ -987,18 +1011,21 @@ Defines the allowed SPIFFE trust domain. `TRAEFIK_TRACING`: OpenTracing configuration. (Default: ```false```) +`TRAEFIK_TRACING_ADDINTERNALS`: +Enables tracing for internal services (ping, dashboard, etc...). (Default: ```false```) + `TRAEFIK_TRACING_GLOBALATTRIBUTES_`: Defines additional attributes (key:value) on all spans. -`TRAEFIK_TRACING_HEADERS_`: -Defines additional headers to be sent with the payloads. - `TRAEFIK_TRACING_OTLP`: Settings for OpenTelemetry. (Default: ```false```) `TRAEFIK_TRACING_OTLP_GRPC_ENDPOINT`: Sets the gRPC endpoint (host:port) of the collector. (Default: ```localhost:4317```) +`TRAEFIK_TRACING_OTLP_GRPC_HEADERS_`: +Headers sent with payload. + `TRAEFIK_TRACING_OTLP_GRPC_INSECURE`: Disables client transport security for the exporter. (Default: ```false```) @@ -1015,7 +1042,10 @@ TLS insecure skip verify (Default: ```false```) TLS key `TRAEFIK_TRACING_OTLP_HTTP_ENDPOINT`: -Sets the HTTP endpoint (scheme://host:port/v1/traces) of the collector. (Default: ```localhost:4318```) +Sets the HTTP endpoint (scheme://host:port/path) of the collector. (Default: ```https://localhost:4318```) + +`TRAEFIK_TRACING_OTLP_HTTP_HEADERS_`: +Headers sent with payload. `TRAEFIK_TRACING_OTLP_HTTP_TLS_CA`: TLS CA diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index 3edf7ebc9..e3de7aa3b 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -30,6 +30,7 @@ [entryPoints] [entryPoints.EntryPoint0] address = "foobar" + reusePort = true asDefault = true [entryPoints.EntryPoint0.transport] keepAliveMaxTime = "42s" @@ -276,6 +277,7 @@ disableDashboardAd = true [metrics] + addInternals = true [metrics.prometheus] buckets = [42.0, 42.0] addEntryPointsLabels = true @@ -312,24 +314,33 @@ [metrics.influxDB2.additionalLabels] name0 = "foobar" name1 = "foobar" - [metrics.openTelemetry] - address = "foobar" + [metrics.otlp] addEntryPointsLabels = true addRoutersLabels = true addServicesLabels = true explicitBoundaries = [42.0, 42.0] - insecure = true - path = "foobar" pushInterval = "42s" - [metrics.openTelemetry.grpc] - [metrics.openTelemetry.headers] - name0 = "foobar" - name1 = "foobar" - [metrics.openTelemetry.tls] - ca = "foobar" - cert = "foobar" - key = "foobar" - insecureSkipVerify = true + [metrics.otlp.grpc] + endpoint = "foobar" + insecure = true + [metrics.otlp.grpc.tls] + ca = "foobar" + cert = "foobar" + key = "foobar" + insecureSkipVerify = true + [metrics.otlp.grpc.headers] + name0 = "foobar" + name1 = "foobar" + [metrics.otlp.http] + endpoint = "foobar" + [metrics.otlp.http.tls] + ca = "foobar" + cert = "foobar" + key = "foobar" + insecureSkipVerify = true + [metrics.otlp.http.headers] + name0 = "foobar" + name1 = "foobar" [ping] entryPoint = "foobar" @@ -350,6 +361,7 @@ filePath = "foobar" format = "foobar" bufferingSize = 42 + addInternals = true [accessLog.filters] statusCodes = ["foobar", "foobar"] retryAttempts = true @@ -368,9 +380,7 @@ [tracing] serviceName = "foobar" sampleRate = 42.0 - [tracing.headers] - name0 = "foobar" - name1 = "foobar" + addInternals = true [tracing.globalAttributes] name0 = "foobar" name1 = "foobar" @@ -383,6 +393,9 @@ cert = "foobar" key = "foobar" insecureSkipVerify = true + [tracing.otlp.grpc.headers] + name0 = "foobar" + name1 = "foobar" [tracing.otlp.http] endpoint = "foobar" [tracing.otlp.http.tls] @@ -390,6 +403,9 @@ cert = "foobar" key = "foobar" insecureSkipVerify = true + [tracing.otlp.http.headers] + name0 = "foobar" + name1 = "foobar" [hostResolver] cnameFlattening = true @@ -453,5 +469,8 @@ [experimental.localPlugins.LocalDescriptor1] moduleName = "foobar" +[core] + defaultRuleSyntax = "foobar" + [spiffe] workloadAPIAddr = "foobar" diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index a9ccffe9a..bdf314c29 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -35,6 +35,7 @@ tcpServersTransport: entryPoints: EntryPoint0: address: foobar + reusePort: true asDefault: true transport: lifeCycle: @@ -307,6 +308,7 @@ api: debug: true disableDashboardAd: true metrics: + addInternals: true prometheus: buckets: - 42 @@ -345,26 +347,35 @@ metrics: additionalLabels: name0: foobar name1: foobar - openTelemetry: - grpc: {} - address: foobar + otlp: + grpc: + endpoint: foobar + insecure: true + tls: + ca: foobar + cert: foobar + key: foobar + insecureSkipVerify: true + headers: + name0: foobar + name1: foobar + http: + endpoint: foobar + tls: + ca: foobar + cert: foobar + key: foobar + insecureSkipVerify: true + headers: + name0: foobar + name1: foobar addEntryPointsLabels: true addRoutersLabels: true addServicesLabels: true explicitBoundaries: - 42 - 42 - headers: - name0: foobar - name1: foobar - insecure: true - path: foobar pushInterval: 42s - tls: - ca: foobar - cert: foobar - key: foobar - insecureSkipVerify: true ping: entryPoint: foobar manualRouting: true @@ -398,15 +409,14 @@ accessLog: name0: foobar name1: foobar bufferingSize: 42 + addInternals: true tracing: serviceName: foobar - headers: - name0: foobar - name1: foobar globalAttributes: name0: foobar name1: foobar sampleRate: 42 + addInternals: true otlp: grpc: endpoint: foobar @@ -416,6 +426,9 @@ tracing: cert: foobar key: foobar insecureSkipVerify: true + headers: + name0: foobar + name1: foobar http: endpoint: foobar tls: @@ -423,6 +436,9 @@ tracing: cert: foobar key: foobar insecureSkipVerify: true + headers: + name0: foobar + name1: foobar hostResolver: cnameFlattening: true resolvConfig: foobar @@ -486,5 +502,7 @@ experimental: LocalDescriptor1: moduleName: foobar kubernetesGateway: true +core: + defaultRuleSyntax: foobar spiffe: workloadAPIAddr: foobar diff --git a/docs/content/routing/entrypoints.md b/docs/content/routing/entrypoints.md index cd79c3cfd..75376bd9b 100644 --- a/docs/content/routing/entrypoints.md +++ b/docs/content/routing/entrypoints.md @@ -233,6 +233,79 @@ If both TCP and UDP are wanted for the same port, two entryPoints definitions ar Full details for how to specify `address` can be found in [net.Listen](https://golang.org/pkg/net/#Listen) (and [net.Dial](https://golang.org/pkg/net/#Dial)) of the doc for go. +### ReusePort + +_Optional, Default=false_ + +The `ReusePort` option enables EntryPoints from the same or different processes +listening on the same TCP/UDP port by utilizing the `SO_REUSEPORT` socket option. +It also allows the kernel to act like a load balancer to distribute incoming +connections between entry points. + +For example, you can use it with the [transport.lifeCycle](#lifecycle) to do +canary deployments against Traefik itself. Like upgrading Traefik version or +reloading the static configuration without any service downtime. + +!!! warning "Supported platforms" + + The `ReusePort` option currently works only on Linux, FreeBSD, OpenBSD and Darwin. + It will be ignored on other platforms. + + There is a known bug in the Linux kernel that may cause unintended TCP connection failures when using the `ReusePort` option. + For more details, see https://lwn.net/Articles/853637/. + +??? example "Listen on the same port" + + ```yaml tab="File (yaml)" + entryPoints: + web: + address: ":80" + reusePort: true + ``` + + ```toml tab="File (TOML)" + [entryPoints.web] + address = ":80" + reusePort = true + ``` + + ```bash tab="CLI" + --entrypoints.web.address=:80 + --entrypoints.web.reusePort=true + ``` + + Now it is possible to run multiple Traefik processes with the same EntryPoint configuration. + +??? example "Listen on the same port but bind to a different host" + + ```yaml tab="File (yaml)" + entryPoints: + web: + address: ":80" + reusePort: true + privateWeb: + address: "192.168.1.2:80" + reusePort: true + ``` + + ```toml tab="File (TOML)" + [entryPoints.web] + address = ":80" + reusePort = true + [entryPoints.privateWeb] + address = "192.168.1.2:80" + reusePort = true + ``` + + ```bash tab="CLI" + --entrypoints.web.address=:80 + --entrypoints.web.reusePort=true + --entrypoints.privateWeb.address=192.168.1.2:80 + --entrypoints.privateWeb.reusePort=true + ``` + + Requests to `192.168.1.2:80` will only be handled by routers that have `privateWeb` as the entry point. + ### AsDefault _Optional, Default=false_ @@ -650,7 +723,7 @@ The maximum number of requests Traefik can handle before sending a `Connection: ```bash tab="CLI" ## Static configuration --entryPoints.name.address=:8888 - --entryPoints.name.transport.keepAliveRequests=42 + --entryPoints.name.transport.keepAliveMaxRequests=42 ``` #### `keepAliveMaxTime` @@ -680,7 +753,7 @@ The maximum duration Traefik can handle requests before sending a `Connection: C ```bash tab="CLI" ## Static configuration --entryPoints.name.address=:8888 - --entryPoints.name.transport.keepAliveTime=42s + --entryPoints.name.transport.keepAliveMaxTime=42s ``` ### ProxyProtocol diff --git a/docs/content/routing/routers/index.md b/docs/content/routing/routers/index.md index 55b46dc9d..9a4b8c7d0 100644 --- a/docs/content/routing/routers/index.md +++ b/docs/content/routing/routers/index.md @@ -515,6 +515,60 @@ A value of `0` for the priority is ignored: `priority = 0` means that the defaul In this configuration, the priority is configured to allow `Router-2` to handle requests with the `foobar.traefik.com` host. +### RuleSyntax + +In Traefik v3 a new rule syntax has been introduced ([migration guide](../../migration/v2-to-v3.md#router-rule-matchers)). +`ruleSyntax` option allows to configure the rule syntax to be used for parsing the rule on a per-router basis. +This allows to have heterogeneous router configurations and ease migration. + +??? example "Set rule syntax -- using the [File Provider](../../providers/file.md)" + + ```yaml tab="File (YAML)" + ## Dynamic configuration + http: + routers: + Router-v3: + rule: HostRegexp(`[a-z]+\\.traefik\\.com`) + ruleSyntax: v3 + Router-v2: + rule: HostRegexp(`{subdomain:[a-z]+}.traefik.com`) + ruleSyntax: v2 + ``` + + ```toml tab="File (TOML)" + ## Dynamic configuration + [http.routers] + [http.routers.Router-v3] + rule = "HostRegexp(`[a-z]+\\.traefik\\.com`)" + ruleSyntax = v3 + [http.routers.Router-v2] + rule = "HostRegexp(`{subdomain:[a-z]+}.traefik.com`)" + ruleSyntax = v2 + ``` + + ```yaml tab="Kubernetes traefik.io/v1alpha1" + apiVersion: traefik.io/v1alpha1 + kind: IngressRoute + metadata: + name: test.route + namespace: default + + spec: + routes: + # route v3 + - match: HostRegexp(`[a-z]+\\.traefik\\.com`) + syntax: v3 + kind: Rule + + # route v2 + - match: HostRegexp(`{subdomain:[a-z]+}.traefik.com`) + syntax: v2 + kind: Rule + ``` + + In this configuration, the ruleSyntax is configured to allow `Router-v2` to use v2 syntax, + while for `Router-v3` it is configured to use v3 syntax. + ### Middlewares You can attach a list of [middlewares](../../middlewares/overview.md) to each HTTP router. @@ -1161,6 +1215,60 @@ A value of `0` for the priority is ignored: `priority = 0` means that the defaul In this configuration, the priority is configured so that `Router-1` will handle requests from `192.168.0.12`. +### RuleSyntax + +In Traefik v3 a new rule syntax has been introduced ([migration guide](../../migration/v2-to-v3.md#router-rule-matchers)). +`ruleSyntax` option allows to configure the rule syntax to be used for parsing the rule on a per-router basis. +This allows to have heterogeneous router configurations and ease migration. + +??? example "Set rule syntax -- using the [File Provider](../../providers/file.md)" + + ```yaml tab="File (YAML)" + ## Dynamic configuration + tcp: + routers: + Router-v3: + rule: ClientIP(`192.168.0.11`) || ClientIP(`192.168.0.12`) + ruleSyntax: v3 + Router-v2: + rule: ClientIP(`192.168.0.11`, `192.168.0.12`) + ruleSyntax: v2 + ``` + + ```toml tab="File (TOML)" + ## Dynamic configuration + [tcp.routers] + [tcp.routers.Router-v3] + rule = "ClientIP(`192.168.0.11`) || ClientIP(`192.168.0.12`)" + ruleSyntax = v3 + [tcp.routers.Router-v2] + rule = "ClientIP(`192.168.0.11`, `192.168.0.12`)" + ruleSyntax = v2 + ``` + + ```yaml tab="Kubernetes traefik.io/v1alpha1" + apiVersion: traefik.io/v1alpha1 + kind: IngressRouteTCP + metadata: + name: test.route + namespace: default + + spec: + routes: + # route v3 + - match: ClientIP(`192.168.0.11`) || ClientIP(`192.168.0.12`) + syntax: v3 + kind: Rule + + # route v2 + - match: ClientIP(`192.168.0.11`, `192.168.0.12`) + syntax: v2 + kind: Rule + ``` + + In this configuration, the ruleSyntax is configured to allow `Router-v2` to use v2 syntax, + while for `Router-v3` it is configured to use v3 syntax. + ### Middlewares You can attach a list of [middlewares](../../middlewares/overview.md) to each TCP router. diff --git a/docs/content/routing/services/index.md b/docs/content/routing/services/index.md index 7658195e1..57dd2466a 100644 --- a/docs/content/routing/services/index.md +++ b/docs/content/routing/services/index.md @@ -143,6 +143,36 @@ The `url` option point to a specific instance. url = "http://private-ip-server-1/" ``` +The `weight` option allows for weighted load balancing on the servers. + +??? example "A Service with Two Servers with Weight -- Using the [File Provider](../../providers/file.md)" + + ```yaml tab="YAML" + ## Dynamic configuration + http: + services: + my-service: + loadBalancer: + servers: + - url: "http://private-ip-server-1/" + weight: 2 + - url: "http://private-ip-server-2/" + weight: 1 + + ``` + + ```toml tab="TOML" + ## Dynamic configuration + [http.services] + [http.services.my-service.loadBalancer] + [[http.services.my-service.loadBalancer.servers]] + url = "http://private-ip-server-1/" + weight = 2 + [[http.services.my-service.loadBalancer.servers]] + url = "http://private-ip-server-2/" + weight = 1 + ``` + #### Load-balancing For now, only round robin load balancing is supported: @@ -1587,6 +1617,46 @@ Below are the available options for the PROXY protocol: version = 1 ``` +#### Termination Delay + +!!! warning + + Deprecated in favor of [`serversTransport.terminationDelay`](#terminationdelay). + Please note that if any `serversTransport` configuration on the servers load balancer is found, + it will take precedence over the servers load balancer `terminationDelay` value, + even if the `serversTransport.terminationDelay` is undefined. + +As a proxy between a client and a server, it can happen that either side (e.g. client side) decides to terminate its writing capability on the connection (i.e. issuance of a FIN packet). +The proxy needs to propagate that intent to the other side, and so when that happens, it also does the same on its connection with the other side (e.g. backend side). + +However, if for some reason (bad implementation, or malicious intent) the other side does not eventually do the same as well, +the connection would stay half-open, which would lock resources for however long. + +To that end, as soon as the proxy enters this termination sequence, it sets a deadline on fully terminating the connections on both sides. + +The termination delay controls that deadline. +It is a duration in milliseconds, defaulting to 100. +A negative value means an infinite deadline (i.e. the connection is never fully terminated by the proxy itself). + +??? example "A Service with a termination delay -- Using the [File Provider](../../providers/file.md)" + + ```yaml tab="YAML" + ## Dynamic configuration + tcp: + services: + my-service: + loadBalancer: + terminationDelay: 200 + ``` + + ```toml tab="TOML" + ## Dynamic configuration + [tcp.services] + [tcp.services.my-service.loadBalancer] + [[tcp.services.my-service.loadBalancer]] + terminationDelay = 200 + ``` + ### Weighted Round Robin The Weighted Round Robin (alias `WRR`) load-balancer of services is in charge of balancing the requests between multiple services based on provided weights. diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 6ba875a3f..bbb3751ae 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -149,11 +149,16 @@ nav: - 'API': 'operations/api.md' - 'Ping': 'operations/ping.md' - 'Observability': + - 'Overview': 'observability/overview.md' - 'Logs': 'observability/logs.md' - 'Access Logs': 'observability/access-logs.md' - 'Metrics': - 'Overview': 'observability/metrics/overview.md' + - 'Datadog': 'observability/metrics/datadog.md' + - 'InfluxDB2': 'observability/metrics/influxdb2.md' - 'OpenTelemetry': 'observability/metrics/opentelemetry.md' + - 'Prometheus': 'observability/metrics/prometheus.md' + - 'StatsD': 'observability/metrics/statsd.md' - 'Tracing': - 'Overview': 'observability/tracing/overview.md' - 'OpenTelemetry': 'observability/tracing/opentelemetry.md' diff --git a/go.mod b/go.mod index 1e3228e15..702fbd501 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/traefik/traefik/v3 -go 1.21 +go 1.22 require ( github.com/BurntSushi/toml v1.3.2 @@ -17,7 +17,7 @@ require ( github.com/docker/go-connections v0.4.0 github.com/fatih/structs v1.1.0 github.com/fsnotify/fsnotify v1.7.0 - github.com/go-acme/lego/v4 v4.14.0 + github.com/go-acme/lego/v4 v4.15.0 github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea github.com/golang/protobuf v1.5.3 github.com/google/go-github/v28 v28.1.1 @@ -26,9 +26,9 @@ require ( github.com/hashicorp/consul/api v1.26.1 github.com/hashicorp/go-hclog v1.5.0 github.com/hashicorp/go-multierror v1.1.1 - github.com/hashicorp/go-retryablehttp v0.7.4 + github.com/hashicorp/go-retryablehttp v0.7.5 github.com/hashicorp/go-version v1.6.0 - github.com/hashicorp/nomad/api v0.0.0-20231213195942-64e3dca9274b + github.com/hashicorp/nomad/api v0.0.0-20240122103822-8a4bd61caf74 github.com/http-wasm/http-wasm-host-go v0.5.2 github.com/influxdata/influxdb-client-go/v2 v2.7.0 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d @@ -39,7 +39,7 @@ require ( github.com/kvtools/valkeyrie v1.0.0 github.com/kvtools/zookeeper v1.0.2 github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f - github.com/miekg/dns v1.1.55 + github.com/miekg/dns v1.1.58 github.com/mitchellh/copystructure v1.2.0 github.com/mitchellh/hashstructure v1.0.0 github.com/mitchellh/mapstructure v1.5.0 @@ -78,19 +78,21 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.21.0 go.opentelemetry.io/otel/trace v1.21.0 golang.org/x/exp v0.0.0-20231006140011-7918f672742d - golang.org/x/mod v0.13.0 - golang.org/x/net v0.17.0 - golang.org/x/text v0.13.0 - golang.org/x/time v0.3.0 - golang.org/x/tools v0.14.0 + golang.org/x/mod v0.14.0 + golang.org/x/net v0.20.0 + golang.org/x/sys v0.16.0 + golang.org/x/text v0.14.0 + golang.org/x/time v0.5.0 + golang.org/x/tools v0.17.0 google.golang.org/grpc v1.59.0 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.28.3 + k8s.io/api v0.28.4 k8s.io/apiextensions-apiserver v0.28.3 - k8s.io/apimachinery v0.28.3 - k8s.io/client-go v0.28.3 + k8s.io/apimachinery v0.28.4 + k8s.io/client-go v0.28.4 k8s.io/utils v0.0.0-20230726121419-3b25d923346b mvdan.cc/xurls/v2 v2.5.0 + sigs.k8s.io/controller-runtime v0.16.3 sigs.k8s.io/gateway-api v1.0.0 ) @@ -107,8 +109,8 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.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.27 // indirect - github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect + github.com/Azure/go-autorest/autorest v0.11.29 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect @@ -127,26 +129,27 @@ require ( github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect github.com/armon/go-metrics v0.4.1 // indirect - github.com/aws/aws-sdk-go-v2 v1.20.3 // indirect - github.com/aws/aws-sdk-go-v2/config v1.18.28 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.13.27 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.40 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.34 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.34 // indirect - github.com/aws/aws-sdk-go-v2/service/lightsail v1.27.2 // indirect - github.com/aws/aws-sdk-go-v2/service/route53 v1.28.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 // indirect - github.com/aws/smithy-go v1.14.2 // indirect + github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect + github.com/aws/aws-sdk-go-v2/config v1.26.6 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect + github.com/aws/aws-sdk-go-v2/service/lightsail v1.34.0 // indirect + github.com/aws/aws-sdk-go-v2/service/route53 v1.37.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect + github.com/aws/smithy-go v1.19.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/bytedance/sonic v1.10.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/civo/civogo v0.3.11 // indirect - github.com/cloudflare/cloudflare-go v0.70.0 // indirect + github.com/cloudflare/cloudflare-go v0.86.0 // indirect github.com/containerd/containerd v1.7.11 // indirect github.com/containerd/log v0.1.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect @@ -162,12 +165,13 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch v5.7.0+incompatible // indirect - github.com/exoscale/egoscale v0.100.1 // indirect + github.com/evanphx/json-patch/v5 v5.7.0 // indirect + github.com/exoscale/egoscale v0.102.3 // indirect github.com/fatih/color v1.15.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/gin-gonic/gin v1.9.1 // indirect github.com/go-errors/errors v1.0.1 // indirect - github.com/go-jose/go-jose/v3 v3.0.0 // indirect + github.com/go-jose/go-jose/v3 v3.0.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -176,9 +180,10 @@ require ( github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.4 // indirect github.com/go-playground/validator/v10 v10.15.1 // indirect - github.com/go-resty/resty/v2 v2.7.0 // indirect + github.com/go-resty/resty/v2 v2.11.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-zookeeper/zk v1.0.3 // indirect + github.com/goccy/go-json v0.10.2 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect @@ -218,10 +223,9 @@ require ( github.com/kylelemons/godebug v1.1.0 // indirect github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect github.com/labbsr0x/goh v1.0.1 // indirect - github.com/linode/linodego v1.17.2 // indirect - github.com/liquidweb/go-lwApi v0.0.5 // indirect + github.com/linode/linodego v1.28.0 // indirect github.com/liquidweb/liquidweb-cli v0.6.9 // indirect - github.com/liquidweb/liquidweb-go v1.6.3 // indirect + github.com/liquidweb/liquidweb-go v1.6.4 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailgun/minheap v0.0.0-20170619185613-3dbe6c6bf55f // indirect @@ -236,6 +240,7 @@ require ( github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/spdystream v0.2.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -244,22 +249,22 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect github.com/nrdcg/auroradns v1.1.0 // indirect + github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9 // indirect github.com/nrdcg/desec v0.7.0 // indirect github.com/nrdcg/dnspod-go v0.4.0 // indirect github.com/nrdcg/freemyip v0.2.0 // indirect - github.com/nrdcg/goinwx v0.8.2 // indirect + github.com/nrdcg/goinwx v0.10.0 // indirect github.com/nrdcg/namesilo v0.2.1 // indirect github.com/nrdcg/nodion v0.1.0 // indirect - github.com/nrdcg/porkbun v0.2.0 // indirect + github.com/nrdcg/porkbun v0.3.0 // indirect github.com/nzdjb/go-metaname v1.0.0 // indirect github.com/onsi/ginkgo v1.16.5 // indirect github.com/onsi/ginkgo/v2 v2.11.0 // indirect - github.com/onsi/gomega v1.27.10 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc5 // indirect github.com/opencontainers/runc v1.1.5 // indirect github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect - github.com/ovh/go-ovh v1.4.1 // indirect + github.com/ovh/go-ovh v1.4.3 // indirect github.com/pelletier/go-toml/v2 v2.0.9 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -275,13 +280,12 @@ require ( github.com/sacloud/go-http v0.1.6 // indirect github.com/sacloud/iaas-api-go v1.11.1 // indirect github.com/sacloud/packages-go v0.0.9 // indirect - github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17 // indirect + github.com/scaleway/scaleway-sdk-go v1.0.0-beta.22 // indirect github.com/shirou/gopsutil/v3 v3.23.11 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shopspring/decimal v1.2.0 // indirect - github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04 // indirect github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect - github.com/softlayer/softlayer-go v1.1.2 // indirect + github.com/softlayer/softlayer-go v1.1.3 // indirect github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect github.com/spf13/cast v1.3.1 // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -292,8 +296,8 @@ require ( github.com/tidwall/pretty v1.2.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.20.0 // indirect - github.com/ultradns/ultradns-go-sdk v1.5.0-20230427130837-23c9b0c // indirect + github.com/transip/gotransip/v6 v6.23.0 // indirect + github.com/ultradns/ultradns-go-sdk v1.6.1-20231103022937-8589b6a // indirect github.com/vinyldns/go-vinyldns v0.9.16 // indirect github.com/vultr/govultr/v2 v2.17.2 // indirect github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f // indirect @@ -311,10 +315,9 @@ require ( go.uber.org/ratelimit v0.2.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/arch v0.4.0 // indirect - golang.org/x/crypto v0.14.0 // indirect - golang.org/x/oauth2 v0.13.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.13.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/oauth2 v0.16.0 // indirect + golang.org/x/term v0.16.0 // indirect google.golang.org/api v0.128.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect @@ -324,7 +327,7 @@ require ( gopkg.in/h2non/gock.v1 v1.0.16 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/ns1/ns1-go.v2 v2.7.6 // indirect + gopkg.in/ns1/ns1-go.v2 v2.7.13 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/klog/v2 v2.100.1 // indirect diff --git a/go.sum b/go.sum index 9c0c8a0eb..c9bd69ebd 100644 --- a/go.sum +++ b/go.sum @@ -42,11 +42,11 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg6 github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc= -github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A= -github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= +github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw= +github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs= github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= -github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg= -github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= +github.com/Azure/go-autorest/autorest/adal v0.9.22 h1:/GblQdIudfEM3AWWZ0mrYJQSd7JS4S/Mbzh6F0ov0Xc= +github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk= github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 h1:wkAZRgT/pn8HhFyzfe9UnqOjJYqlembgCTi72Bm/xKk= github.com/Azure/go-autorest/autorest/azure/auth v0.5.12/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg= github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 h1:0W/yGmFdTIT77fvdlGZ0LMISoLHFJ7Tx4U0yeB+uFs4= @@ -114,45 +114,44 @@ github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJ github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.44.327 h1:ZS8oO4+7MOBLhkdwIhgtVeDzCeWOlTfKJS7EgggbIEY= github.com/aws/aws-sdk-go v1.44.327/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/aws/aws-sdk-go-v2 v1.19.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2 v1.20.3 h1:lgeKmAZhlj1JqN43bogrM75spIvYnRxqTAh1iupu1yE= -github.com/aws/aws-sdk-go-v2 v1.20.3/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M= -github.com/aws/aws-sdk-go-v2/config v1.18.28 h1:TINEaKyh1Td64tqFvn09iYpKiWjmHYrG1fa91q2gnqw= -github.com/aws/aws-sdk-go-v2/config v1.18.28/go.mod h1:nIL+4/8JdAuNHEjn/gPEXqtnS02Q3NXB/9Z7o5xE4+A= -github.com/aws/aws-sdk-go-v2/credentials v1.13.27 h1:dz0yr/yR1jweAnsCx+BmjerUILVPQ6FS5AwF/OyG1kA= -github.com/aws/aws-sdk-go-v2/credentials v1.13.27/go.mod h1:syOqAek45ZXZp29HlnRS/BNgMIW6uiRmeuQsz4Qh2UE= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 h1:kP3Me6Fy3vdi+9uHd7YLr6ewPxRL+PU6y15urfTaamU= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5/go.mod h1:Gj7tm95r+QsDoN2Fhuz/3npQvcZbkEf5mL70n3Xfluc= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35/go.mod h1:ipR5PvpSPqIqL5Mi82BxLnfMkHVbmco8kUwO2xrCi0M= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.40 h1:CXceCS9BrDInRc74GDCQ8Qyk/Gp9VLdK+Rlve+zELSE= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.40/go.mod h1:5kKmFhLeOVy6pwPDpDNA6/hK/d6URC98pqDDqHgdBx4= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29/go.mod h1:M/eUABlDbw2uVrdAn+UsI6M727qp2fxkp8K0ejcBDUY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.34 h1:B+nZtd22cbko5+793hg7LEaTeLMiZwlgCLUrN5Y0uzg= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.34/go.mod h1:RZP0scceAyhMIQ9JvFp7HvkpcgqjL4l/4C+7RAeGbuM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 h1:8r5m1BoAWkn0TDC34lUculryf7nUF25EgIMdjvGCkgo= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36/go.mod h1:Rmw2M1hMVTwiUhjwMoIBFWFJMhvJbct06sSidxInkhY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29/go.mod h1:fDbkK4o7fpPXWn8YAPmTieAMuB9mk/VgvW64uaUqxd4= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.34 h1:JwvXk+1ePAD9xkFHprhHYqwsxLDcbNFsPI1IAT2sPS0= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.34/go.mod h1:ytsF+t+FApY2lFnN51fJKPhH6ICKOPXKEcwwgmJEdWI= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.27.2 h1:PwNeYoonBzmTdCztKiiutws3U24KrnDBuabzRfIlZY4= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.27.2/go.mod h1:gQhLZrTEath4zik5ixIe6axvgY5jJrgSBDJ360Fxnco= -github.com/aws/aws-sdk-go-v2/service/route53 v1.28.4 h1:p4mTxJfCAyiTT4Wp6p/mOPa6j5MqCSRGot8qZwFs+Z0= -github.com/aws/aws-sdk-go-v2/service/route53 v1.28.4/go.mod h1:VBLWpaHvhQNeu7N9rMEf00SWeOONb/HvaDUxe/7b44k= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 h1:sWDv7cMITPcZ21QdreULwxOOAmE05JjEsT6fCDtDA9k= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.13/go.mod h1:DfX0sWuT46KpcqbMhJ9QWtxAIP1VozkDWf8VAkByjYY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 h1:BFubHS/xN5bjl818QaroN6mQdjneYQ+AOx44KNXlyH4= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13/go.mod h1:BzqsVVFduubEmzrVtUFQQIQdFqvUItF8XUq2EnS8Wog= -github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 h1:e5mnydVdCVWxP+5rPAGi2PYxC7u2OZgH1ypC114H04U= -github.com/aws/aws-sdk-go-v2/service/sts v1.19.3/go.mod h1:yVGZA1CPkmUhBdA039jXNJJG7/6t+G+EBWmFq23xqnY= -github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= -github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ= -github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= +github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= +github.com/aws/aws-sdk-go-v2/config v1.26.6 h1:Z/7w9bUqlRI0FFQpetVuFYEsjzE3h7fpU6HuGmfPL/o= +github.com/aws/aws-sdk-go-v2/config v1.26.6/go.mod h1:uKU6cnDmYCvJ+pxO9S4cWDb2yWWIH5hra+32hVh1MI4= +github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8= +github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 h1:n3GDfwqF2tzEkXlv5cuy4iy7LpKDtqDMcNLfZDu9rls= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.34.0 h1:LvWkxBi/bsWHqj3bFTUuDLl4OAlbaM1HDZ9YPhj5+jg= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.34.0/go.mod h1:35MKNS46RX7Lb9EIFP2bPy3WrJu+bxU6QgLis8K1aa4= +github.com/aws/aws-sdk-go-v2/service/route53 v1.37.0 h1:f3hBZWtpn9clZGXJoqahQeec9ZPZnu22g8pg+zNyif0= +github.com/aws/aws-sdk-go-v2/service/route53 v1.37.0/go.mod h1:8qqfpG4mug2JLlEyWPSFhEGvJiaZ9iPmMDDMYc5Xtas= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= +github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= +github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -197,8 +196,8 @@ github.com/civo/civogo v0.3.11 h1:mON/fyrV946Sbk6paRtOSGsN+asCgCmHCgArf5xmGxM= github.com/civo/civogo v0.3.11/go.mod h1:7+GeeFwc4AYTULaEshpT2vIcl3Qq8HPoxA17viX3l6g= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cloudflare-go v0.70.0 h1:4opGbUygM8DjirUuaz23jn3akuAcnOCEx+0nQtQEcFo= -github.com/cloudflare/cloudflare-go v0.70.0/go.mod h1:VW6GuazkaZ4xEDkFt24lkXQUsE8q7BiGqDniC2s8WEM= +github.com/cloudflare/cloudflare-go v0.86.0 h1:jEKN5VHNYNYtfDL2lUFLTRo+nOVNPFxpXTstVx0rqHI= +github.com/cloudflare/cloudflare-go v0.86.0/go.mod h1:wYW/5UP02TUfBToa/yKbQHV+r6h1NnJ1Je7XjuGM4Jw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= @@ -302,8 +301,10 @@ github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go. github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/exoscale/egoscale v0.100.1 h1:iXsV1Ei7daqe/6FYSCSDyrFs1iUG1l1X9qNh2uMw6z0= -github.com/exoscale/egoscale v0.100.1/go.mod h1:BAb9p4rmyU+Wl400CJZO5270H2sXtdsZjLcm5xMKkz4= +github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc= +github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/exoscale/egoscale v0.102.3 h1:DYqN2ipoLKpiFoprRGQkp2av/Ze7sUYYlGhi1N62tfY= +github.com/exoscale/egoscale v0.102.3/go.mod h1:RPf2Gah6up+6kAEayHTQwqapzXlm93f0VQas/UEGU5c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= @@ -334,15 +335,15 @@ github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwv github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= -github.com/go-acme/lego/v4 v4.14.0 h1:/skZoRHgVh0d2RK7l1g3Ch8HqeqP9LB8ZEjLdGEpcDE= -github.com/go-acme/lego/v4 v4.14.0/go.mod h1:zjmvNCDLGz7GrC1OqdVpVmZFKSRabEDtWbdzmcpBsGo= +github.com/go-acme/lego/v4 v4.15.0 h1:A7MHEU3b+TDFqhC/HmzMJnzPbyeaYvMZQBbqgvbThhU= +github.com/go-acme/lego/v4 v4.15.0/go.mod h1:eeGhjW4zWT7Ccqa3sY7ayEqFLCAICx+mXgkMHKIkLxg= github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= -github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= +github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= +github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea h1:CnEQOUv4ilElSwFB9g/lVmz206oLE4aNZDYngIY1Gvg= @@ -358,6 +359,8 @@ github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= +github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -384,8 +387,8 @@ github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM= github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= -github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= -github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= +github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8= +github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -474,7 +477,6 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -563,8 +565,8 @@ github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= -github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= +github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= @@ -592,8 +594,8 @@ github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= -github.com/hashicorp/nomad/api v0.0.0-20231213195942-64e3dca9274b h1:R1UDhkwGltpSPY9bCBBxIMQd+NY9BkN0vFHnJo/8o8w= -github.com/hashicorp/nomad/api v0.0.0-20231213195942-64e3dca9274b/go.mod h1:ijDwa6o1uG1jFSq6kERiX2PamKGpZzTmo0XOFNeFZgw= +github.com/hashicorp/nomad/api v0.0.0-20240122103822-8a4bd61caf74 h1:Q+WuGTnZkL2cJ7yNsg4Go4GNnRkcahGLiQP/WD41TTA= +github.com/hashicorp/nomad/api v0.0.0-20240122103822-8a4bd61caf74/go.mod h1:ijDwa6o1uG1jFSq6kERiX2PamKGpZzTmo0XOFNeFZgw= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= @@ -703,15 +705,13 @@ github.com/lestrrat-go/jwx v1.2.7/go.mod h1:bw24IXWbavc0R2RsOtpXL7RtMyP589yZ1+L7 github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/linode/linodego v1.17.2 h1:b32dj4662PGG5P9qVa6nBezccWdqgukndlMIuPGq1CQ= -github.com/linode/linodego v1.17.2/go.mod h1:C2iyT3Vg2O2sPxkWka4XAQ5WSUtm5LmTZ3Adw43Ra7Q= +github.com/linode/linodego v1.28.0 h1:lzxxJebsYg5cCWRNDLyL2StW3sfMyAwf/FYfxFjFrlk= +github.com/linode/linodego v1.28.0/go.mod h1:5oAsx+uinHtVo6U77nXXXtox7MWzUW6aEkTOKXxA9uo= github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= -github.com/liquidweb/go-lwApi v0.0.5 h1:CT4cdXzJXmo0bon298kS7NeSk+Gt8/UHpWBBol1NGCA= -github.com/liquidweb/go-lwApi v0.0.5/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAWPjVnwoYM= github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ= -github.com/liquidweb/liquidweb-go v1.6.3 h1:NVHvcnX3eb3BltiIoA+gLYn15nOpkYkdizOEYGSKrk4= -github.com/liquidweb/liquidweb-go v1.6.3/go.mod h1:SuXXp+thr28LnjEw18AYtWwIbWMHSUiajPQs8T9c/Rc= +github.com/liquidweb/liquidweb-go v1.6.4 h1:6S0m3hHSpiLqGD7AFSb7lH/W/qr1wx+tKil9fgIbjMc= +github.com/liquidweb/liquidweb-go v1.6.4/go.mod h1:B934JPIIcdA+uTq2Nz5PgOtG6CuCaEvQKe/Ge/5GgZ4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= @@ -765,8 +765,8 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= -github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= +github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= +github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= github.com/mimuret/golang-iij-dpf v0.9.1 h1:Gj6EhHJkOhr+q2RnvRPJsPMcjuVnWPSccEHyoEehU34= github.com/mimuret/golang-iij-dpf v0.9.1/go.mod h1:sl9KyOkESib9+KRD3HaGpgi1xk7eoN2+d96LCLsME2M= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -796,6 +796,8 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= @@ -831,20 +833,22 @@ github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uY github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nrdcg/auroradns v1.1.0 h1:KekGh8kmf2MNwqZVVYo/fw/ZONt8QMEmbMFOeljteWo= github.com/nrdcg/auroradns v1.1.0/go.mod h1:O7tViUZbAcnykVnrGkXzIJTHoQCHcgalgAe6X1mzHfk= +github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9 h1:qpB3wZR4+MPK92cTC9zZPnndkJgDgPvQqPUAgVc1NXU= +github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9/go.mod h1:HUoHXDrFvidN1NK9Wb/mZKNOfDNutKkzF2Pg71M9hHA= github.com/nrdcg/desec v0.7.0 h1:iuGhi4pstF3+vJWwt292Oqe2+AsSPKDynQna/eu1fDs= github.com/nrdcg/desec v0.7.0/go.mod h1:e1uRqqKv1mJdd5+SQROAhmy75lKMphLzWIuASLkpeFY= github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U= github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ= github.com/nrdcg/freemyip v0.2.0 h1:/GscavT4GVqAY13HExl5UyoB4wlchv6Cg5NYDGsUoJ8= github.com/nrdcg/freemyip v0.2.0/go.mod h1:HjF0Yz0lSb37HD2ihIyGz9esyGcxbCrrGFLPpKevbx4= -github.com/nrdcg/goinwx v0.8.2 h1:RmjiHlEA+lzi3toXyPSaE6hWnBQ0+G+1u7w8C6Fpp4g= -github.com/nrdcg/goinwx v0.8.2/go.mod h1:mnMSTi7CXBu2io4DzdOBoGFA1XclD0sEPWJaDhNgkA4= +github.com/nrdcg/goinwx v0.10.0 h1:6W630bjDxQD6OuXKqrFRYVpTt0G/9GXXm3CeOrN0zJM= +github.com/nrdcg/goinwx v0.10.0/go.mod h1:mnMSTi7CXBu2io4DzdOBoGFA1XclD0sEPWJaDhNgkA4= github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg= github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw= github.com/nrdcg/nodion v0.1.0 h1:zLKaqTn2X0aDuBHHfyA1zFgeZfiCpmu/O9DM73okavw= github.com/nrdcg/nodion v0.1.0/go.mod h1:inbuh3neCtIWlMPZHtEpe43TmRXxHV6+hk97iCZicms= -github.com/nrdcg/porkbun v0.2.0 h1:ghaqPtIKcffba99epWFkK3VWf6TKJT9WMXMgaTqv95Y= -github.com/nrdcg/porkbun v0.2.0/go.mod h1:i0uLMn9ItFsLsSQIAeEu1wQ9/+6EvX1eQw15hulMMRw= +github.com/nrdcg/porkbun v0.3.0 h1:jnRV7j2zd3hmh+tSDOGetJyy3+WklaMxbs7HtTTmWMs= +github.com/nrdcg/porkbun v0.3.0/go.mod h1:jh1DKz96jGHW+NCdG3AmTbbnQeBlNUz1KeSgeN/cBVw= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -891,8 +895,8 @@ github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnh github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/oracle/oci-go-sdk v24.3.0+incompatible h1:x4mcfb4agelf1O4/1/auGlZ1lr97jXRSSN5MxTgG/zU= github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= -github.com/ovh/go-ovh v1.4.1 h1:VBGa5wMyQtTP7Zb+w97zRCh9sLtM/2YKRyy+MEJmWaM= -github.com/ovh/go-ovh v1.4.1/go.mod h1:6bL6pPyUT7tBfI0pqOegJgRjgjuO+mOo+MyXd1EEC0M= +github.com/ovh/go-ovh v1.4.3 h1:Gs3V823zwTFpzgGLZNI6ILS4rmxZgJwJCz54Er9LwD0= +github.com/ovh/go-ovh v1.4.3/go.mod h1:AkPXVtgwB6xlKblMjRKJJmjRp+ogrE7fz2lVgcQY8SY= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= @@ -995,8 +999,8 @@ github.com/sacloud/iaas-api-go v1.11.1/go.mod h1:uBDSa06F/V0OnoR66jGdbH0PVnCJw+N github.com/sacloud/packages-go v0.0.9 h1:GbinkBLC/eirFhHpLjoDW6JV7+95Rnd2d8RWj7Afeks= github.com/sacloud/packages-go v0.0.9/go.mod h1:k+EEUMF2LlncjbNIJNOqLyZ9wjTESPIWIk1OA7x9j2Q= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17 h1:1WuWJu7/e8SqK+uQl7lfk/N/oMZTL2NE/TJsNKRNMc4= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.22 h1:wJrcTdddKOI8TFxs8cemnhKP2EmKy3yfUKHj3ZdfzYo= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.22/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= @@ -1012,8 +1016,6 @@ github.com/shoenig/test v1.7.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsB github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04 h1:ZTzdx88+AcnjqUfJwnz89UBrMSBQ1NEysg9u5d+dU9c= -github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04/go.mod h1:5KS21fpch8TIMyAUv/qQqTa3GZfBDYgjaZbd2KXKYfg= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -1028,8 +1030,8 @@ github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/gunit v1.0.4 h1:tpTjnuH7MLlqhoD21vRoMZbMIi5GmBsAJDFyF67GhZA= github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ= -github.com/softlayer/softlayer-go v1.1.2 h1:rUSSGCyaxymvTOsaFjwr+cGxA8muw3xg2LSrIMNcN/c= -github.com/softlayer/softlayer-go v1.1.2/go.mod h1:hvAbzGH4LRXA6yXY8BNx99yoqZ7urfDdtl9mvBf0G+g= +github.com/softlayer/softlayer-go v1.1.3 h1:dfFzt5eOKIAyB/b78fHMyDu5ICx0ZtxL9NRhBlf831A= +github.com/softlayer/softlayer-go v1.1.3/go.mod h1:Pc7F57OgUKaAam7TtpqkUeqL7QyKknfiUI4R49h41/U= github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e h1:3OgWYFw7jxCZPcvAg+4R8A50GZ+CCkARF10lxu2qDsQ= github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e/go.mod h1:fKZCUVdirrxrBpwd9wb+lSoVixvpwAu8eHzbQB2tums= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -1107,8 +1109,8 @@ github.com/traefik/paerser v0.2.0 h1:zqCLGSXoNlcBd+mzqSCLjon/I6phqIjeJL2xFB2ysgQ github.com/traefik/paerser v0.2.0/go.mod h1:afzaVcgF8A+MpTnPG4wBr4whjanCSYA6vK5RwaYVtRc= github.com/traefik/yaegi v0.15.1 h1:YA5SbaL6HZA0Exh9T/oArRHqGN2HQ+zgmCY7dkoTXu4= github.com/traefik/yaegi v0.15.1/go.mod h1:AVRxhaI2G+nUsaM1zyktzwXn69G3t/AuTDrCiTds9p0= -github.com/transip/gotransip/v6 v6.20.0 h1:AuvwyOZ51f2brzMbTqlRy/wmaM3kF7Vx5Wds8xcDflY= -github.com/transip/gotransip/v6 v6.20.0/go.mod h1:nzv9eN2tdsUrm5nG5ZX6AugYIU4qgsMwIn2c0EZLk8c= +github.com/transip/gotransip/v6 v6.23.0 h1:PsTdjortrEZ8IFFifEryzjVjOy9SgK4ahlnhKBBIQgA= +github.com/transip/gotransip/v6 v6.23.0/go.mod h1:nzv9eN2tdsUrm5nG5ZX6AugYIU4qgsMwIn2c0EZLk8c= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= @@ -1119,8 +1121,8 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/ultradns/ultradns-go-sdk v1.5.0-20230427130837-23c9b0c h1:mKnW6IGLw7uXu6DL6RitufZWcXS6hCnauXRUFof7rKM= -github.com/ultradns/ultradns-go-sdk v1.5.0-20230427130837-23c9b0c/go.mod h1:F4UyVEmq4/m5lAmx+GccrxyRCXmnBjzUL09JLTQFp94= +github.com/ultradns/ultradns-go-sdk v1.6.1-20231103022937-8589b6a h1:w4PK5/N9kq8PfNxBv8a5t1bqlYRrVT7XzT7iTPTtiPk= +github.com/ultradns/ultradns-go-sdk v1.6.1-20231103022937-8589b6a/go.mod h1:Xwz7o+ExFtxR/i0aJDnTXuiccQJlOxDgNe6FsZC4TzQ= github.com/unrolled/render v1.0.2 h1:dGS3EmChQP3yOi1YeFNO/Dx+MbWZhdvhQJTXochM5bs= github.com/unrolled/render v1.0.2/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0obPf6NBdM= github.com/unrolled/secure v1.0.9 h1:BWRuEb1vDrBFFDdbCnKkof3gZ35I/bnHGyt0LB0TNyQ= @@ -1245,9 +1247,12 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1278,8 +1283,9 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1314,20 +1320,22 @@ golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= -golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= +golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1338,8 +1346,9 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1416,15 +1425,20 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -1435,16 +1449,20 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1479,12 +1497,15 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= @@ -1588,8 +1609,8 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ns1/ns1-go.v2 v2.7.6 h1:mCPl7q0jbIGACXvGBljAuuApmKZo3rRi4tlRIEbMvjA= -gopkg.in/ns1/ns1-go.v2 v2.7.6/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYhsPk= +gopkg.in/ns1/ns1-go.v2 v2.7.13 h1:r07CLALg18f/L1KIK1ZJdbirBV349UtYT1rDWGjnaTk= +gopkg.in/ns1/ns1-go.v2 v2.7.13/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= @@ -1619,14 +1640,14 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -k8s.io/api v0.28.3 h1:Gj1HtbSdB4P08C8rs9AR94MfSGpRhJgsS+GF9V26xMM= -k8s.io/api v0.28.3/go.mod h1:MRCV/jr1dW87/qJnZ57U5Pak65LGmQVkKTzf3AtKFHc= +k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY= +k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0= k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08= k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc= -k8s.io/apimachinery v0.28.3 h1:B1wYx8txOaCQG0HmYF6nbpU8dg6HvA06x5tEffvOe7A= -k8s.io/apimachinery v0.28.3/go.mod h1:uQTKmIqs+rAYaq+DFaoD2X7pcjLOqbQX2AOiO0nIpb8= -k8s.io/client-go v0.28.3 h1:2OqNb72ZuTZPKCl+4gTKvqao0AMOl9f3o2ijbAj3LI4= -k8s.io/client-go v0.28.3/go.mod h1:LTykbBp9gsA7SwqirlCXBWtK0guzfhpoW4qSm7i9dxo= +k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8= +k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg= +k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY= +k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4= k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= @@ -1640,6 +1661,8 @@ nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0 nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4= +sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= sigs.k8s.io/gateway-api v1.0.0 h1:iPTStSv41+d9p0xFydll6d7f7MOBGuqXM6p2/zVYMAs= sigs.k8s.io/gateway-api v1.0.0/go.mod h1:4cUgr0Lnp5FZ0Cdq8FdRwCvpiWws7LVhLHGIudLlf4c= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= diff --git a/integration/access_log_test.go b/integration/access_log_test.go index 8e2c9581f..b5e4ec1ef 100644 --- a/integration/access_log_test.go +++ b/integration/access_log_test.go @@ -61,7 +61,7 @@ func (s *AccessLogSuite) TestAccessLog() { ensureWorkingDirectoryIsClean() // Start Traefik - s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) + s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml")) defer func() { traefikLog, err := os.ReadFile(traefikTestLogFile) @@ -130,7 +130,7 @@ func (s *AccessLogSuite) TestAccessLogAuthFrontend() { } // Start Traefik - s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) + s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml")) s.checkStatsForLogFile() @@ -194,7 +194,7 @@ func (s *AccessLogSuite) TestAccessLogDigestAuthMiddleware() { } // Start Traefik - s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) + s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml")) s.checkStatsForLogFile() @@ -304,7 +304,7 @@ func (s *AccessLogSuite) TestAccessLogFrontendRedirect() { } // Start Traefik - s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) + s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml")) s.checkStatsForLogFile() @@ -410,7 +410,7 @@ func (s *AccessLogSuite) TestAccessLogRateLimit() { } // Start Traefik - s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) + s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml")) s.checkStatsForLogFile() @@ -454,7 +454,7 @@ func (s *AccessLogSuite) TestAccessLogBackendNotFound() { } // Start Traefik - s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) + s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml")) s.waitForTraefik("server1") @@ -494,7 +494,7 @@ func (s *AccessLogSuite) TestAccessLogFrontendAllowlist() { } // Start Traefik - s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) + s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml")) s.checkStatsForLogFile() @@ -534,7 +534,7 @@ func (s *AccessLogSuite) TestAccessLogAuthFrontendSuccess() { } // Start Traefik - s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) + s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml")) s.checkStatsForLogFile() @@ -575,7 +575,7 @@ func (s *AccessLogSuite) TestAccessLogPreflightHeadersMiddleware() { } // Start Traefik - s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) + s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml")) s.checkStatsForLogFile() @@ -603,6 +603,56 @@ func (s *AccessLogSuite) TestAccessLogPreflightHeadersMiddleware() { s.checkNoOtherTraefikProblems() } +func (s *AccessLogSuite) TestAccessLogDisabledForInternals() { + ensureWorkingDirectoryIsClean() + + file := s.adaptFile("fixtures/access_log/access_log_ping.toml", struct{}{}) + + // Start Traefik. + s.traefikCmd(withConfigFile(file)) + + defer func() { + traefikLog, err := os.ReadFile(traefikTestLogFile) + require.NoError(s.T(), err) + log.Info().Msg(string(traefikLog)) + }() + + // waitForTraefik makes at least one call to the rawdata api endpoint, + // but the logs for this endpoint are ignored in checkAccessLogOutput. + s.waitForTraefik("customPing") + + s.checkStatsForLogFile() + + // Verify Traefik started OK. + s.checkTraefikStarted() + + // Make some requests on the internal ping router. + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/ping", nil) + require.NoError(s.T(), err) + + err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) + require.NoError(s.T(), err) + err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) + require.NoError(s.T(), err) + + // Make some requests on the custom ping router. + req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/ping", nil) + require.NoError(s.T(), err) + + err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) + require.NoError(s.T(), err) + err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) + require.NoError(s.T(), err) + + // Verify access.log output as expected. + count := s.checkAccessLogOutput() + + require.Equal(s.T(), 0, count) + + // Verify no other Traefik problems. + s.checkNoOtherTraefikProblems() +} + func (s *AccessLogSuite) checkNoOtherTraefikProblems() { traefikLog, err := os.ReadFile(traefikTestLogFile) require.NoError(s.T(), err) @@ -612,6 +662,8 @@ func (s *AccessLogSuite) checkNoOtherTraefikProblems() { } func (s *AccessLogSuite) checkAccessLogOutput() int { + s.T().Helper() + lines := s.extractLines() count := 0 for i, line := range lines { @@ -624,6 +676,8 @@ func (s *AccessLogSuite) checkAccessLogOutput() int { } func (s *AccessLogSuite) checkAccessLogExactValuesOutput(values []accessLogValue) int { + s.T().Helper() + lines := s.extractLines() count := 0 for i, line := range lines { @@ -641,6 +695,8 @@ func (s *AccessLogSuite) checkAccessLogExactValuesOutput(values []accessLogValue } func (s *AccessLogSuite) extractLines() []string { + s.T().Helper() + accessLog, err := os.ReadFile(traefikTestAccessLogFile) require.NoError(s.T(), err) @@ -656,6 +712,8 @@ func (s *AccessLogSuite) extractLines() []string { } func (s *AccessLogSuite) checkStatsForLogFile() { + s.T().Helper() + err := try.Do(1*time.Second, func() error { if _, errStat := os.Stat(traefikTestLogFile); errStat != nil { return fmt.Errorf("could not get stats for log file: %w", errStat) @@ -671,6 +729,8 @@ func ensureWorkingDirectoryIsClean() { } func (s *AccessLogSuite) checkTraefikStarted() []byte { + s.T().Helper() + traefikLog, err := os.ReadFile(traefikTestLogFile) require.NoError(s.T(), err) if len(traefikLog) > 0 { @@ -680,6 +740,8 @@ func (s *AccessLogSuite) checkTraefikStarted() []byte { } func (s *BaseSuite) CheckAccessLogFormat(line string, i int) { + s.T().Helper() + results, err := accesslog.ParseAccessLog(line) require.NoError(s.T(), err) assert.Len(s.T(), results, 14) @@ -692,6 +754,8 @@ func (s *BaseSuite) CheckAccessLogFormat(line string, i int) { } func (s *AccessLogSuite) checkAccessLogExactValues(line string, i int, v accessLogValue) { + s.T().Helper() + results, err := accesslog.ParseAccessLog(line) require.NoError(s.T(), err) assert.Len(s.T(), results, 14) diff --git a/integration/docker_test.go b/integration/docker_test.go index 1a767ff6f..daa6fa06b 100644 --- a/integration/docker_test.go +++ b/integration/docker_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "io" "net/http" + "strings" "testing" "time" @@ -55,6 +56,56 @@ func (s *DockerSuite) TestSimpleConfiguration() { require.NoError(s.T(), err) } +func (s *DockerSuite) TestWRRServer() { + tempObjects := struct { + DockerHost string + DefaultRule string + }{ + DockerHost: s.getDockerHost(), + DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", + } + + file := s.adaptFile("fixtures/docker/simple.toml", tempObjects) + + s.composeUp() + + s.traefikCmd(withConfigFile(file)) + + whoami1IP := s.getComposeServiceIP("wrr-server") + whoami2IP := s.getComposeServiceIP("wrr-server2") + + // Expected a 404 as we did not configure anything + err := try.GetRequest("http://127.0.0.1:8000/", 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) + require.NoError(s.T(), err) + + err = try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains("wrr-server")) + require.NoError(s.T(), err) + + repartition := map[string]int{} + for i := 0; i < 4; i++ { + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil) + req.Host = "my.wrr.host" + require.NoError(s.T(), err) + + response, err := http.DefaultClient.Do(req) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusOK, response.StatusCode) + + body, err := io.ReadAll(response.Body) + require.NoError(s.T(), err) + + if strings.Contains(string(body), whoami1IP) { + repartition[whoami1IP]++ + } + if strings.Contains(string(body), whoami2IP) { + repartition[whoami2IP]++ + } + } + + assert.Equal(s.T(), 3, repartition[whoami1IP]) + assert.Equal(s.T(), 1, repartition[whoami2IP]) +} + func (s *DockerSuite) TestDefaultDockerContainers() { tempObjects := struct { DockerHost string diff --git a/integration/fixtures/access_log_config.toml b/integration/fixtures/access_log/access_log_base.toml similarity index 100% rename from integration/fixtures/access_log_config.toml rename to integration/fixtures/access_log/access_log_base.toml diff --git a/integration/fixtures/access_log/access_log_ping.toml b/integration/fixtures/access_log/access_log_ping.toml new file mode 100644 index 000000000..4e85b93bc --- /dev/null +++ b/integration/fixtures/access_log/access_log_ping.toml @@ -0,0 +1,30 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "ERROR" + filePath = "traefik.log" + +[accessLog] + filePath = "access.log" + +[entryPoints] + [entryPoints.web] + address = ":8000" + +[api] + insecure = true + +[ping] + +[providers] + [providers.file] + filename = "{{ .SelfFilename }}" + +## dynamic configuration ## +[http.routers] + [http.routers.customPing] + entryPoints = ["web"] + rule = "PathPrefix(`/ping`)" + service = "ping@internal" diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index 5a4c63c3c..a95cf1153 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ingressroutes.traefik.io spec: group: traefik.io @@ -20,14 +20,19 @@ spec: description: IngressRoute is the CRD implementation of a Traefik HTTP Router. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -35,10 +40,11 @@ spec: description: IngressRouteSpec defines the desired state of IngressRoute. properties: entryPoints: - description: 'EntryPoints defines the list of entry point names to - bind to. Entry points have to be configured in the static configuration. + description: |- + EntryPoints defines the list of entry point names to bind to. + Entry points have to be configured in the static configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ - Default: all.' + Default: all. items: type: string type: array @@ -48,17 +54,21 @@ spec: description: Route holds the HTTP route configuration. properties: kind: - description: Kind defines the kind of the route. Rule is the - only supported kind. + description: |- + Kind defines the kind of the route. + Rule is the only supported kind. enum: - Rule type: string match: - description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule' + description: |- + Match defines the router's rule. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule type: string middlewares: - description: 'Middlewares defines the list of references to - Middleware resources. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-middleware' + description: |- + Middlewares defines the list of references to Middleware resources. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-middleware items: description: MiddlewareRef is a reference to a Middleware resource. @@ -76,13 +86,14 @@ spec: type: object type: array priority: - description: 'Priority defines the router''s priority. More - info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority' + description: |- + Priority defines the router's priority. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority type: integer services: - description: Services defines the list of Service. It can contain - any combination of TraefikService and/or reference to a Kubernetes - Service. + description: |- + Services defines the list of Service. + It can contain any combination of TraefikService and/or reference to a Kubernetes Service. items: description: Service defines an upstream HTTP service to proxy traffic to. @@ -94,31 +105,32 @@ spec: - TraefikService type: string name: - description: Name defines the name of the referenced Kubernetes - Service or TraefikService. The differentiation between - the two is specified in the Kind field. + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. type: string namespace: description: Namespace defines the namespace of the referenced Kubernetes Service or TraefikService. type: string nativeLB: - description: NativeLB controls, when creating the load-balancer, - whether the LB's children are directly the pods IPs - or if the only child is the Kubernetes Service clusterIP. - The Kubernetes Service itself does load-balance to the - pods. By default, NativeLB is false. + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. type: boolean passHostHeader: - description: PassHostHeader defines whether the client - Host header is forwarded to the upstream Kubernetes - Service. By default, passHostHeader is true. + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. + By default, passHostHeader is true. type: boolean port: anyOf: - type: integer - type: string - description: Port defines the port of a Kubernetes Service. + description: |- + Port defines the port of a Kubernetes Service. This can be a reference to a named port. x-kubernetes-int-or-string: true responseForwarding: @@ -127,30 +139,29 @@ spec: the client. properties: flushInterval: - description: 'FlushInterval defines the interval, - in milliseconds, in between flushes to the client - while copying the response body. A negative value - means to flush immediately after each write to the - client. This configuration is ignored when ReverseProxy - recognizes a response as a streaming response; for - such responses, writes are flushed to the client - immediately. Default: 100ms' + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; + for such responses, writes are flushed to the client immediately. + Default: 100ms type: string type: object scheme: - description: Scheme defines the scheme to use for the - request to the upstream Kubernetes Service. It defaults - to https when Kubernetes Service port is 443, http otherwise. + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. type: string serversTransport: - description: ServersTransport defines the name of ServersTransport - resource to use. It allows to configure the transport - between Traefik and your servers. Can only be used on - a Kubernetes Service. + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. type: string sticky: - description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -161,17 +172,18 @@ spec: JavaScript. type: boolean maxAge: - description: MaxAge indicates the number of seconds - until the cookie expires. When set to a negative - number, the cookie expires immediately. When - set to zero, the cookie never expires. + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. type: integer name: description: Name defines the Cookie name. type: string sameSite: - description: 'SameSite defines the same site policy. - More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite type: string secure: description: Secure defines whether the cookie @@ -181,36 +193,44 @@ spec: type: object type: object strategy: - description: Strategy defines the load balancing strategy - between the servers. RoundRobin is the only supported - value at the moment. + description: |- + Strategy defines the load balancing strategy between the servers. + RoundRobin is the only supported value at the moment. type: string weight: - description: Weight defines the weight and should only - be specified when Name references a TraefikService object - (and to be precise, one that embeds a Weighted Round - Robin). + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object + (and to be precise, one that embeds a Weighted Round Robin). type: integer required: - name type: object type: array + syntax: + description: |- + Syntax defines the router's rule syntax. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rulesyntax + type: string required: - kind - match type: object type: array tls: - description: 'TLS defines the TLS configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls' + description: |- + TLS defines the TLS configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls properties: certResolver: - description: 'CertResolver defines the name of the certificate - resolver to use. Cert resolvers have to be configured in the - static configuration. More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers' + description: |- + CertResolver defines the name of the certificate resolver to use. + Cert resolvers have to be configured in the static configuration. + More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers type: string domains: - description: 'Domains defines the list of domains that will be - used to issue certificates. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains' + description: |- + Domains defines the list of domains that will be used to issue certificates. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains items: description: Domain holds a domain name with SANs. properties: @@ -226,17 +246,20 @@ spec: type: object type: array options: - description: 'Options defines the reference to a TLSOption, that - specifies the parameters of the TLS connection. If not defined, - the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options' + description: |- + Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. + If not defined, the `default` TLSOption is used. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options properties: name: - description: 'Name defines the name of the referenced TLSOption. - More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption' + description: |- + Name defines the name of the referenced TLSOption. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption type: string namespace: - description: 'Namespace defines the namespace of the referenced - TLSOption. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption' + description: |- + Namespace defines the namespace of the referenced TLSOption. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption type: string required: - name @@ -246,17 +269,19 @@ spec: Secret to specify the certificate details. type: string store: - description: Store defines the reference to the TLSStore, that - will be used to store certificates. Please note that only `default` - TLSStore can be used. + description: |- + Store defines the reference to the TLSStore, that will be used to store certificates. + Please note that only `default` TLSStore can be used. properties: name: - description: 'Name defines the name of the referenced TLSStore. - More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore' + description: |- + Name defines the name of the referenced TLSStore. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore type: string namespace: - description: 'Namespace defines the namespace of the referenced - TLSStore. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore' + description: |- + Namespace defines the namespace of the referenced TLSStore. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore type: string required: - name @@ -276,7 +301,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ingressroutetcps.traefik.io spec: group: traefik.io @@ -293,14 +318,19 @@ spec: description: IngressRouteTCP is the CRD implementation of a Traefik TCP Router. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -308,10 +338,11 @@ spec: description: IngressRouteTCPSpec defines the desired state of IngressRouteTCP. properties: entryPoints: - description: 'EntryPoints defines the list of entry point names to - bind to. Entry points have to be configured in the static configuration. + description: |- + EntryPoints defines the list of entry point names to bind to. + Entry points have to be configured in the static configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ - Default: all.' + Default: all. items: type: string type: array @@ -321,7 +352,9 @@ spec: description: RouteTCP holds the TCP route configuration. properties: match: - description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule_1' + description: |- + Match defines the router's rule. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule_1 type: string middlewares: description: Middlewares defines the list of references to MiddlewareTCP @@ -343,8 +376,9 @@ spec: type: object type: array priority: - description: 'Priority defines the router''s priority. More - info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority_1' + description: |- + Priority defines the router's priority. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority_1 type: integer services: description: Services defines the list of TCP services. @@ -361,22 +395,24 @@ spec: Kubernetes Service. type: string nativeLB: - description: NativeLB controls, when creating the load-balancer, - whether the LB's children are directly the pods IPs - or if the only child is the Kubernetes Service clusterIP. - The Kubernetes Service itself does load-balance to the - pods. By default, NativeLB is false. + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. type: boolean port: anyOf: - type: integer - type: string - description: Port defines the port of a Kubernetes Service. + description: |- + Port defines the port of a Kubernetes Service. This can be a reference to a named port. x-kubernetes-int-or-string: true proxyProtocol: - description: 'ProxyProtocol defines the PROXY protocol - configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/services/#proxy-protocol' + description: |- + ProxyProtocol defines the PROXY protocol configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#proxy-protocol properties: version: description: Version defines the PROXY Protocol version @@ -384,11 +420,20 @@ spec: type: integer type: object serversTransport: - description: ServersTransport defines the name of ServersTransportTCP - resource to use. It allows to configure the transport - between Traefik and your servers. Can only be used on - a Kubernetes Service. + description: |- + ServersTransport defines the name of ServersTransportTCP resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. type: string + terminationDelay: + description: |- + TerminationDelay defines the deadline that the proxy sets, after one of its connected peers indicates + it has closed the writing capability of its connection, to close the reading capability as well, + hence fully terminating the connection. + It is a duration in milliseconds, defaulting to 100. + A negative value means an infinite deadline (i.e. the reading capability is never closed). + Deprecated: TerminationDelay is not supported APIVersion traefik.io/v1, please use ServersTransport to configure the TerminationDelay instead. + type: integer tls: description: TLS determines whether to use TLS when dialing with the backend. @@ -402,22 +447,30 @@ spec: - port type: object type: array + syntax: + description: |- + Syntax defines the router's rule syntax. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rulesyntax_1 + type: string required: - match type: object type: array tls: - description: 'TLS defines the TLS configuration on a layer 4 / TCP - Route. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls_1' + description: |- + TLS defines the TLS configuration on a layer 4 / TCP Route. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls_1 properties: certResolver: - description: 'CertResolver defines the name of the certificate - resolver to use. Cert resolvers have to be configured in the - static configuration. More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers' + description: |- + CertResolver defines the name of the certificate resolver to use. + Cert resolvers have to be configured in the static configuration. + More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers type: string domains: - description: 'Domains defines the list of domains that will be - used to issue certificates. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains' + description: |- + Domains defines the list of domains that will be used to issue certificates. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains items: description: Domain holds a domain name with SANs. properties: @@ -433,9 +486,10 @@ spec: type: object type: array options: - description: 'Options defines the reference to a TLSOption, that - specifies the parameters of the TLS connection. If not defined, - the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options' + description: |- + Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. + If not defined, the `default` TLSOption is used. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options properties: name: description: Name defines the name of the referenced Traefik @@ -457,9 +511,9 @@ spec: Secret to specify the certificate details. type: string store: - description: Store defines the reference to the TLSStore, that - will be used to store certificates. Please note that only `default` - TLSStore can be used. + description: |- + Store defines the reference to the TLSStore, that will be used to store certificates. + Please note that only `default` TLSStore can be used. properties: name: description: Name defines the name of the referenced Traefik @@ -487,7 +541,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ingressrouteudps.traefik.io spec: group: traefik.io @@ -504,14 +558,19 @@ spec: description: IngressRouteUDP is a CRD implementation of a Traefik UDP Router. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -519,10 +578,11 @@ spec: description: IngressRouteUDPSpec defines the desired state of a IngressRouteUDP. properties: entryPoints: - description: 'EntryPoints defines the list of entry point names to - bind to. Entry points have to be configured in the static configuration. + description: |- + EntryPoints defines the list of entry point names to bind to. + Entry points have to be configured in the static configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ - Default: all.' + Default: all. items: type: string type: array @@ -546,17 +606,18 @@ spec: Kubernetes Service. type: string nativeLB: - description: NativeLB controls, when creating the load-balancer, - whether the LB's children are directly the pods IPs - or if the only child is the Kubernetes Service clusterIP. - The Kubernetes Service itself does load-balance to the - pods. By default, NativeLB is false. + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. type: boolean port: anyOf: - type: integer - type: string - description: Port defines the port of a Kubernetes Service. + description: |- + Port defines the port of a Kubernetes Service. This can be a reference to a named port. x-kubernetes-int-or-string: true weight: @@ -584,7 +645,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: middlewares.traefik.io spec: group: traefik.io @@ -598,18 +659,24 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: 'Middleware is the CRD implementation of a Traefik Middleware. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/overview/' + description: |- + Middleware is the CRD implementation of a Traefik Middleware. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/overview/ properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -617,33 +684,37 @@ spec: description: MiddlewareSpec defines the desired state of a Middleware. properties: addPrefix: - description: 'AddPrefix holds the add prefix middleware configuration. - This middleware updates the path of a request before forwarding - it. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/addprefix/' + description: |- + AddPrefix holds the add prefix middleware configuration. + This middleware updates the path of a request before forwarding it. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/addprefix/ properties: prefix: - description: Prefix is the string to add before the current path - in the requested URL. It should include a leading slash (/). + description: |- + Prefix is the string to add before the current path in the requested URL. + It should include a leading slash (/). type: string type: object basicAuth: - description: 'BasicAuth holds the basic auth middleware configuration. + description: |- + BasicAuth holds the basic auth middleware configuration. This middleware restricts access to your services to known users. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/ properties: headerField: - description: 'HeaderField defines a header field to store the - authenticated user. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield' + description: |- + HeaderField defines a header field to store the authenticated user. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield type: string realm: - description: 'Realm allows the protected resources on a server - to be partitioned into a set of protection spaces, each with - its own authentication scheme. Default: traefik.' + description: |- + Realm allows the protected resources on a server to be partitioned into a set of protection spaces, each with its own authentication scheme. + Default: traefik. type: string removeHeader: - description: 'RemoveHeader sets the removeHeader option to true - to remove the authorization header before forwarding the request - to your service. Default: false.' + description: |- + RemoveHeader sets the removeHeader option to true to remove the authorization header before forwarding the request to your service. + Default: false. type: boolean secret: description: Secret is the name of the referenced Kubernetes Secret @@ -651,48 +722,49 @@ spec: type: string type: object buffering: - description: 'Buffering holds the buffering middleware configuration. - This middleware retries or limits the size of requests that can - be forwarded to backends. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#maxrequestbodybytes' + description: |- + Buffering holds the buffering middleware configuration. + This middleware retries or limits the size of requests that can be forwarded to backends. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#maxrequestbodybytes properties: maxRequestBodyBytes: - description: 'MaxRequestBodyBytes defines the maximum allowed - body size for the request (in bytes). If the request exceeds - the allowed size, it is not forwarded to the service, and the - client gets a 413 (Request Entity Too Large) response. Default: - 0 (no maximum).' + description: |- + MaxRequestBodyBytes defines the maximum allowed body size for the request (in bytes). + If the request exceeds the allowed size, it is not forwarded to the service, and the client gets a 413 (Request Entity Too Large) response. + Default: 0 (no maximum). format: int64 type: integer maxResponseBodyBytes: - description: 'MaxResponseBodyBytes defines the maximum allowed - response size from the service (in bytes). If the response exceeds - the allowed size, it is not forwarded to the client. The client - gets a 500 (Internal Server Error) response instead. Default: - 0 (no maximum).' + description: |- + MaxResponseBodyBytes defines the maximum allowed response size from the service (in bytes). + If the response exceeds the allowed size, it is not forwarded to the client. The client gets a 500 (Internal Server Error) response instead. + Default: 0 (no maximum). format: int64 type: integer memRequestBodyBytes: - description: 'MemRequestBodyBytes defines the threshold (in bytes) - from which the request will be buffered on disk instead of in - memory. Default: 1048576 (1Mi).' + description: |- + MemRequestBodyBytes defines the threshold (in bytes) from which the request will be buffered on disk instead of in memory. + Default: 1048576 (1Mi). format: int64 type: integer memResponseBodyBytes: - description: 'MemResponseBodyBytes defines the threshold (in bytes) - from which the response will be buffered on disk instead of - in memory. Default: 1048576 (1Mi).' + description: |- + MemResponseBodyBytes defines the threshold (in bytes) from which the response will be buffered on disk instead of in memory. + Default: 1048576 (1Mi). format: int64 type: integer retryExpression: - description: 'RetryExpression defines the retry conditions. It - is a logical combination of functions with operators AND (&&) - and OR (||). More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#retryexpression' + description: |- + RetryExpression defines the retry conditions. + It is a logical combination of functions with operators AND (&&) and OR (||). + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#retryexpression type: string type: object chain: - description: 'Chain holds the configuration of the chain middleware. - This middleware enables to define reusable combinations of other - pieces of middleware. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/chain/' + description: |- + Chain holds the configuration of the chain middleware. + This middleware enables to define reusable combinations of other pieces of middleware. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/chain/ properties: middlewares: description: Middlewares is the list of MiddlewareRef which composes @@ -744,15 +816,15 @@ spec: x-kubernetes-int-or-string: true type: object compress: - description: 'Compress holds the compress middleware configuration. - This middleware compresses responses before sending them to the - client, using gzip compression. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/compress/' + description: |- + Compress holds the compress middleware configuration. + This middleware compresses responses before sending them to the client, using gzip compression. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/compress/ properties: excludedContentTypes: - description: ExcludedContentTypes defines the list of content - types to compare the Content-Type header of the incoming requests - and responses before compressing. `application/grpc` is always - excluded. + description: |- + ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing. + `application/grpc` is always excluded. items: type: string type: array @@ -764,30 +836,38 @@ spec: type: string type: array minResponseBodyBytes: - description: 'MinResponseBodyBytes defines the minimum amount - of bytes a response body must have to be compressed. Default: - 1024.' + description: |- + MinResponseBodyBytes defines the minimum amount of bytes a response body must have to be compressed. + Default: 1024. type: integer type: object contentType: - description: ContentType holds the content-type middleware configuration. - This middleware sets the `Content-Type` header value to the media - type detected from the response content, when it is not set by the - backend. + description: |- + ContentType holds the content-type middleware configuration. + This middleware exists to enable the correct behavior until at least the default one can be changed in a future version. + properties: + autoDetect: + description: |- + AutoDetect specifies whether to let the `Content-Type` header, if it has not been set by the backend, + be automatically set to a value derived from the contents of the response. + Deprecated: AutoDetect option is deprecated, Content-Type middleware is only meant to be used to enable the content-type detection, please remove any usage of this option. + type: boolean type: object digestAuth: - description: 'DigestAuth holds the digest auth middleware configuration. + description: |- + DigestAuth holds the digest auth middleware configuration. This middleware restricts access to your services to known users. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/digestauth/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/digestauth/ properties: headerField: - description: 'HeaderField defines a header field to store the - authenticated user. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield' + description: |- + HeaderField defines a header field to store the authenticated user. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield type: string realm: - description: 'Realm allows the protected resources on a server - to be partitioned into a set of protection spaces, each with - its own authentication scheme. Default: traefik.' + description: |- + Realm allows the protected resources on a server to be partitioned into a set of protection spaces, each with its own authentication scheme. + Default: traefik. type: string removeHeader: description: RemoveHeader defines whether to remove the authorization @@ -799,18 +879,20 @@ spec: type: string type: object errors: - description: 'ErrorPage holds the custom error middleware configuration. - This middleware returns a custom page in lieu of the default, according - to configured ranges of HTTP Status codes. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/' + description: |- + ErrorPage holds the custom error middleware configuration. + This middleware returns a custom page in lieu of the default, according to configured ranges of HTTP Status codes. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/ properties: query: - description: Query defines the URL for the error page (hosted - by service). The {status} variable can be used in order to insert - the status code in the URL. + description: |- + Query defines the URL for the error page (hosted by service). + The {status} variable can be used in order to insert the status code in the URL. type: string service: - description: 'Service defines the reference to a Kubernetes Service - that will serve the error page. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/#service' + description: |- + Service defines the reference to a Kubernetes Service that will serve the error page. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/#service properties: kind: description: Kind defines the kind of the Service. @@ -819,31 +901,32 @@ spec: - TraefikService type: string name: - description: Name defines the name of the referenced Kubernetes - Service or TraefikService. The differentiation between the - two is specified in the Kind field. + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. type: string namespace: description: Namespace defines the namespace of the referenced Kubernetes Service or TraefikService. type: string nativeLB: - description: NativeLB controls, when creating the load-balancer, - whether the LB's children are directly the pods IPs or if - the only child is the Kubernetes Service clusterIP. The - Kubernetes Service itself does load-balance to the pods. + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. By default, NativeLB is false. type: boolean passHostHeader: - description: PassHostHeader defines whether the client Host - header is forwarded to the upstream Kubernetes Service. + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean port: anyOf: - type: integer - type: string - description: Port defines the port of a Kubernetes Service. + description: |- + Port defines the port of a Kubernetes Service. This can be a reference to a named port. x-kubernetes-int-or-string: true responseForwarding: @@ -852,29 +935,29 @@ spec: client. properties: flushInterval: - description: 'FlushInterval defines the interval, in milliseconds, - in between flushes to the client while copying the response - body. A negative value means to flush immediately after - each write to the client. This configuration is ignored - when ReverseProxy recognizes a response as a streaming - response; for such responses, writes are flushed to - the client immediately. Default: 100ms' + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; + for such responses, writes are flushed to the client immediately. + Default: 100ms type: string type: object scheme: - description: Scheme defines the scheme to use for the request - to the upstream Kubernetes Service. It defaults to https - when Kubernetes Service port is 443, http otherwise. + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. type: string serversTransport: - description: ServersTransport defines the name of ServersTransport - resource to use. It allows to configure the transport between - Traefik and your servers. Can only be used on a Kubernetes - Service. + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. type: string sticky: - description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -884,17 +967,18 @@ spec: be accessed by client-side APIs, such as JavaScript. type: boolean maxAge: - description: MaxAge indicates the number of seconds - until the cookie expires. When set to a negative - number, the cookie expires immediately. When set - to zero, the cookie never expires. + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. type: integer name: description: Name defines the Cookie name. type: string sameSite: - description: 'SameSite defines the same site policy. - More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite type: string secure: description: Secure defines whether the cookie can @@ -904,32 +988,34 @@ spec: type: object type: object strategy: - description: Strategy defines the load balancing strategy - between the servers. RoundRobin is the only supported value - at the moment. + description: |- + Strategy defines the load balancing strategy between the servers. + RoundRobin is the only supported value at the moment. type: string weight: - description: Weight defines the weight and should only be - specified when Name references a TraefikService object (and - to be precise, one that embeds a Weighted Round Robin). + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object + (and to be precise, one that embeds a Weighted Round Robin). type: integer required: - name type: object status: - description: Status defines which status or range of statuses - should result in an error page. It can be either a status code - as a number (500), as multiple comma-separated numbers (500,502), - as ranges by separating two codes with a dash (500-599), or - a combination of the two (404,418,500-599). + description: |- + Status defines which status or range of statuses should result in an error page. + It can be either a status code as a number (500), + as multiple comma-separated numbers (500,502), + as ranges by separating two codes with a dash (500-599), + or a combination of the two (404,418,500-599). items: type: string type: array type: object forwardAuth: - description: 'ForwardAuth holds the forward auth middleware configuration. + description: |- + ForwardAuth holds the forward auth middleware configuration. This middleware delegates the request authentication to a Service. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/ properties: addAuthCookiesToResponse: description: AddAuthCookiesToResponse defines the list of cookies @@ -941,9 +1027,9 @@ spec: description: Address defines the authentication server address. type: string authRequestHeaders: - description: AuthRequestHeaders defines the list of the headers - to copy from the request to the authentication server. If not - set or empty then all request headers are passed. + description: |- + AuthRequestHeaders defines the list of the headers to copy from the request to the authentication server. + If not set or empty then all request headers are passed. items: type: string type: array @@ -955,24 +1041,27 @@ spec: type: string type: array authResponseHeadersRegex: - description: 'AuthResponseHeadersRegex defines the regex to match - headers to copy from the authentication server response and - set on forwarded request, after stripping all headers that match - the regex. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/#authresponseheadersregex' + description: |- + AuthResponseHeadersRegex defines the regex to match headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/#authresponseheadersregex type: string tls: description: TLS defines the configuration used to secure the connection to the authentication server. properties: + caOptional: + description: 'Deprecated: TLS client authentication is a server + side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634).' + type: boolean caSecret: - description: CASecret is the name of the referenced Kubernetes - Secret containing the CA to validate the server certificate. + description: |- + CASecret is the name of the referenced Kubernetes Secret containing the CA to validate the server certificate. The CA certificate is extracted from key `tls.ca` or `ca.crt`. type: string certSecret: - description: CertSecret is the name of the referenced Kubernetes - Secret containing the client certificate. The client certificate - is extracted from the keys `tls.crt` and `tls.key`. + description: |- + CertSecret is the name of the referenced Kubernetes Secret containing the client certificate. + The client certificate is extracted from the keys `tls.crt` and `tls.key`. type: string insecureSkipVerify: description: InsecureSkipVerify defines whether the server @@ -985,20 +1074,23 @@ spec: type: boolean type: object grpcWeb: - description: GrpcWeb holds the gRPC web middleware configuration. + description: |- + GrpcWeb holds the gRPC web middleware configuration. This middleware converts a gRPC web request to an HTTP/2 gRPC request. properties: allowOrigins: - description: AllowOrigins is a list of allowable origins. Can - also be a wildcard origin "*". + description: |- + AllowOrigins is a list of allowable origins. + Can also be a wildcard origin "*". items: type: string type: array type: object headers: - description: 'Headers holds the headers middleware configuration. - This middleware manages the requests and responses headers. More - info: https://doc.traefik.io/traefik/v3.0/middlewares/http/headers/#customrequestheaders' + description: |- + Headers holds the headers middleware configuration. + This middleware manages the requests and responses headers. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/headers/#customrequestheaders properties: accessControlAllowCredentials: description: AccessControlAllowCredentials defines whether the @@ -1063,12 +1155,14 @@ spec: header with the nosniff value. type: boolean customBrowserXSSValue: - description: CustomBrowserXSSValue defines the X-XSS-Protection - header value. This overrides the BrowserXssFilter option. + description: |- + CustomBrowserXSSValue defines the X-XSS-Protection header value. + This overrides the BrowserXssFilter option. type: string customFrameOptionsValue: - description: CustomFrameOptionsValue defines the X-Frame-Options - header value. This overrides the FrameDeny option. + description: |- + CustomFrameOptionsValue defines the X-Frame-Options header value. + This overrides the FrameDeny option. type: string customRequestHeaders: additionalProperties: @@ -1082,6 +1176,10 @@ spec: description: CustomResponseHeaders defines the header names and values to apply to the response. type: object + featurePolicy: + description: 'Deprecated: FeaturePolicy option is deprecated, + please use PermissionsPolicy instead.' + type: string forceSTSHeader: description: ForceSTSHeader defines whether to add the STS header even when the connection is HTTP. @@ -1097,34 +1195,49 @@ spec: type: string type: array isDevelopment: - description: IsDevelopment defines whether to mitigate the unwanted - effects of the AllowedHosts, SSL, and STS options when developing. - Usually testing takes place using HTTP, not HTTPS, and on localhost, - not your production domain. If you would like your development - environment to mimic production with complete Host blocking, - SSL redirects, and STS headers, leave this as false. + description: |- + IsDevelopment defines whether to mitigate the unwanted effects of the AllowedHosts, SSL, and STS options when developing. + Usually testing takes place using HTTP, not HTTPS, and on localhost, not your production domain. + If you would like your development environment to mimic production with complete Host blocking, SSL redirects, + and STS headers, leave this as false. type: boolean permissionsPolicy: - description: PermissionsPolicy defines the Permissions-Policy - header value. This allows sites to control browser features. + description: |- + PermissionsPolicy defines the Permissions-Policy header value. + This allows sites to control browser features. type: string publicKey: description: PublicKey is the public key that implements HPKP to prevent MITM attacks with forged certificates. type: string referrerPolicy: - description: ReferrerPolicy defines the Referrer-Policy header - value. This allows sites to control whether browsers forward - the Referer header to other sites. + description: |- + ReferrerPolicy defines the Referrer-Policy header value. + This allows sites to control whether browsers forward the Referer header to other sites. + type: string + sslForceHost: + description: 'Deprecated: SSLForceHost option is deprecated, please + use RedirectRegex instead.' + type: boolean + sslHost: + description: 'Deprecated: SSLHost option is deprecated, please + use RedirectRegex instead.' type: string sslProxyHeaders: additionalProperties: type: string - description: 'SSLProxyHeaders defines the header keys with associated - values that would indicate a valid HTTPS request. It can be - useful when using other proxies (example: "X-Forwarded-Proto": - "https").' + description: |- + SSLProxyHeaders defines the header keys with associated values that would indicate a valid HTTPS request. + It can be useful when using other proxies (example: "X-Forwarded-Proto": "https"). type: object + sslRedirect: + description: 'Deprecated: SSLRedirect option is deprecated, please + use EntryPoint redirection or RedirectScheme instead.' + type: boolean + sslTemporaryRedirect: + description: 'Deprecated: SSLTemporaryRedirect option is deprecated, + please use EntryPoint redirection or RedirectScheme instead.' + type: boolean stsIncludeSubdomains: description: STSIncludeSubdomains defines whether the includeSubDomains directive is appended to the Strict-Transport-Security header. @@ -1134,33 +1247,35 @@ spec: to the Strict-Transport-Security header. type: boolean stsSeconds: - description: STSSeconds defines the max-age of the Strict-Transport-Security - header. If set to 0, the header is not set. + description: |- + STSSeconds defines the max-age of the Strict-Transport-Security header. + If set to 0, the header is not set. format: int64 type: integer type: object inFlightReq: - description: 'InFlightReq holds the in-flight request middleware configuration. - This middleware limits the number of requests being processed and - served concurrently. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/' + description: |- + InFlightReq holds the in-flight request middleware configuration. + This middleware limits the number of requests being processed and served concurrently. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/ properties: amount: - description: Amount defines the maximum amount of allowed simultaneous - in-flight request. The middleware responds with HTTP 429 Too - Many Requests if there are already amount requests in progress - (based on the same sourceCriterion strategy). + description: |- + Amount defines the maximum amount of allowed simultaneous in-flight request. + The middleware responds with HTTP 429 Too Many Requests if there are already amount requests in progress (based on the same sourceCriterion strategy). format: int64 type: integer sourceCriterion: - description: 'SourceCriterion defines what criterion is used to - group requests as originating from a common source. If several - strategies are defined at the same time, an error will be raised. - If none are set, the default is to use the requestHost. More - info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/#sourcecriterion' + description: |- + SourceCriterion defines what criterion is used to group requests as originating from a common source. + If several strategies are defined at the same time, an error will be raised. + If none are set, the default is to use the requestHost. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/#sourcecriterion properties: ipStrategy: - description: 'IPStrategy holds the IP strategy configuration - used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' + description: |- + IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1186,13 +1301,15 @@ spec: type: object type: object ipAllowList: - description: 'IPAllowList holds the IP allowlist middleware configuration. + description: |- + IPAllowList holds the IP allowlist middleware configuration. This middleware accepts / refuses requests based on the client IP. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/ properties: ipStrategy: - description: 'IPStrategy holds the IP strategy configuration used - by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' + description: |- + IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1207,8 +1324,9 @@ spec: type: array type: object rejectStatusCode: - description: RejectStatusCode defines the HTTP status code used - for refused requests. If not set, the default is 403 (Forbidden). + description: |- + RejectStatusCode defines the HTTP status code used for refused requests. + If not set, the default is 403 (Forbidden). type: integer sourceRange: description: SourceRange defines the set of allowed IPs (or ranges @@ -1221,8 +1339,9 @@ spec: description: 'Deprecated: please use IPAllowList instead.' properties: ipStrategy: - description: 'IPStrategy holds the IP strategy configuration used - by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' + description: |- + IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1244,9 +1363,10 @@ spec: type: array type: object passTLSClientCert: - description: 'PassTLSClientCert holds the pass TLS client cert middleware - configuration. This middleware adds the selected data from the passed - client TLS certificate to a header. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/passtlsclientcert/' + description: |- + PassTLSClientCert holds the pass TLS client cert middleware configuration. + This middleware adds the selected data from the passed client TLS certificate to a header. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/passtlsclientcert/ properties: info: description: Info selects the specific client certificate details @@ -1347,46 +1467,48 @@ spec: plugin: additionalProperties: x-kubernetes-preserve-unknown-fields: true - description: 'Plugin defines the middleware plugin configuration. - More info: https://doc.traefik.io/traefik/plugins/' + description: |- + Plugin defines the middleware plugin configuration. + More info: https://doc.traefik.io/traefik/plugins/ type: object rateLimit: - description: 'RateLimit holds the rate limit configuration. This middleware - ensures that services will receive a fair amount of requests, and - allows one to define what fair is. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ratelimit/' + description: |- + RateLimit holds the rate limit configuration. + This middleware ensures that services will receive a fair amount of requests, and allows one to define what fair is. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ratelimit/ properties: average: - description: Average is the maximum rate, by default in requests/s, - allowed for the given source. It defaults to 0, which means - no rate limiting. The rate is actually defined by dividing Average - by Period. So for a rate below 1req/s, one needs to define a - Period larger than a second. + description: |- + Average is the maximum rate, by default in requests/s, allowed for the given source. + It defaults to 0, which means no rate limiting. + The rate is actually defined by dividing Average by Period. So for a rate below 1req/s, + one needs to define a Period larger than a second. format: int64 type: integer burst: - description: Burst is the maximum number of requests allowed to - arrive in the same arbitrarily small period of time. It defaults - to 1. + description: |- + Burst is the maximum number of requests allowed to arrive in the same arbitrarily small period of time. + It defaults to 1. format: int64 type: integer period: anyOf: - type: integer - type: string - description: 'Period, in combination with Average, defines the - actual maximum rate, such as: r = Average / Period. It defaults - to a second.' + description: |- + Period, in combination with Average, defines the actual maximum rate, such as: + r = Average / Period. It defaults to a second. x-kubernetes-int-or-string: true sourceCriterion: - description: SourceCriterion defines what criterion is used to - group requests as originating from a common source. If several - strategies are defined at the same time, an error will be raised. - If none are set, the default is to use the request's remote - address field (as an ipStrategy). + description: |- + SourceCriterion defines what criterion is used to group requests as originating from a common source. + If several strategies are defined at the same time, an error will be raised. + If none are set, the default is to use the request's remote address field (as an ipStrategy). properties: ipStrategy: - description: 'IPStrategy holds the IP strategy configuration - used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' + description: |- + IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1412,9 +1534,10 @@ spec: type: object type: object redirectRegex: - description: 'RedirectRegex holds the redirect regex middleware configuration. + description: |- + RedirectRegex holds the redirect regex middleware configuration. This middleware redirects a request using regex matching and replacement. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectregex/#regex' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectregex/#regex properties: permanent: description: Permanent defines whether the redirection is permanent @@ -1430,9 +1553,10 @@ spec: type: string type: object redirectScheme: - description: 'RedirectScheme holds the redirect scheme middleware - configuration. This middleware redirects requests from a scheme/port - to another. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectscheme/' + description: |- + RedirectScheme holds the redirect scheme middleware configuration. + This middleware redirects requests from a scheme/port to another. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectscheme/ properties: permanent: description: Permanent defines whether the redirection is permanent @@ -1446,9 +1570,10 @@ spec: type: string type: object replacePath: - description: 'ReplacePath holds the replace path middleware configuration. - This middleware replaces the path of the request URL and store the - original path in an X-Replaced-Path header. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepath/' + description: |- + ReplacePath holds the replace path middleware configuration. + This middleware replaces the path of the request URL and store the original path in an X-Replaced-Path header. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepath/ properties: path: description: Path defines the path to use as replacement in the @@ -1456,9 +1581,10 @@ spec: type: string type: object replacePathRegex: - description: 'ReplacePathRegex holds the replace path regex middleware - configuration. This middleware replaces the path of a URL using - regex matching and replacement. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepathregex/' + description: |- + ReplacePathRegex holds the replace path regex middleware configuration. + This middleware replaces the path of a URL using regex matching and replacement. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepathregex/ properties: regex: description: Regex defines the regular expression used to match @@ -1470,11 +1596,11 @@ spec: type: string type: object retry: - description: 'Retry holds the retry middleware configuration. This - 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. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/retry/' + description: |- + Retry holds the retry middleware configuration. + This 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. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/retry/ properties: attempts: description: Attempts defines how many times the request should @@ -1484,18 +1610,26 @@ spec: anyOf: - type: integer - type: string - description: InitialInterval defines the first wait time in the - exponential backoff series. The maximum interval is calculated - as twice the initialInterval. If unspecified, requests will - be retried immediately. The value of initialInterval should - be provided in seconds or as a valid duration format, see https://pkg.go.dev/time#ParseDuration. + description: |- + InitialInterval defines the first wait time in the exponential backoff series. + The maximum interval is calculated as twice the initialInterval. + If unspecified, requests will be retried immediately. + The value of initialInterval should be provided in seconds or as a valid duration format, + see https://pkg.go.dev/time#ParseDuration. x-kubernetes-int-or-string: true type: object stripPrefix: - description: 'StripPrefix holds the strip prefix middleware configuration. + description: |- + StripPrefix holds the strip prefix middleware configuration. This middleware removes the specified prefixes from the URL path. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefix/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefix/ properties: + forceSlash: + description: |- + Deprecated: ForceSlash option is deprecated, please remove any usage of this option. + ForceSlash ensures that the resulting stripped path is not the empty string, by replacing it with / when necessary. + Default: true. + type: boolean prefixes: description: Prefixes defines the prefixes to strip from the request URL. @@ -1504,9 +1638,10 @@ spec: type: array type: object stripPrefixRegex: - description: 'StripPrefixRegex holds the strip prefix regex middleware - configuration. This middleware removes the matching prefixes from - the URL path. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefixregex/' + description: |- + StripPrefixRegex holds the strip prefix regex middleware configuration. + This middleware removes the matching prefixes from the URL path. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefixregex/ properties: regex: description: Regex defines the regular expression to match the @@ -1527,7 +1662,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: middlewaretcps.traefik.io spec: group: traefik.io @@ -1541,18 +1676,24 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: 'MiddlewareTCP is the CRD implementation of a Traefik TCP middleware. - More info: https://doc.traefik.io/traefik/v3.0/middlewares/overview/' + description: |- + MiddlewareTCP is the CRD implementation of a Traefik TCP middleware. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/overview/ properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -1563,14 +1704,17 @@ spec: description: InFlightConn defines the InFlightConn middleware configuration. properties: amount: - description: Amount defines the maximum amount of allowed simultaneous - connections. The middleware closes the connection if there are - already amount connections opened. + description: |- + Amount defines the maximum amount of allowed simultaneous connections. + The middleware closes the connection if there are already amount connections opened. format: int64 type: integer type: object ipAllowList: - description: IPAllowList defines the IPAllowList middleware configuration. + description: |- + IPAllowList defines the IPAllowList middleware configuration. + This middleware accepts/refuses connections based on the client IP. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/tcp/ipallowlist/ properties: sourceRange: description: SourceRange defines the allowed IPs (or ranges of @@ -1580,8 +1724,11 @@ spec: type: array type: object ipWhiteList: - description: 'IPWhiteList defines the IPWhiteList middleware configuration. - Deprecated: please use IPAllowList instead.' + description: |- + IPWhiteList defines the IPWhiteList middleware configuration. + This middleware accepts/refuses connections based on the client IP. + Deprecated: please use IPAllowList instead. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/tcp/ipwhitelist/ properties: sourceRange: description: SourceRange defines the allowed IPs (or ranges of @@ -1602,7 +1749,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: serverstransports.traefik.io spec: group: traefik.io @@ -1616,20 +1763,26 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: 'ServersTransport is the CRD implementation of a ServersTransport. + description: |- + ServersTransport is the CRD implementation of a ServersTransport. If no serversTransport is specified, the default@internal will be used. The default@internal serversTransport is created from the static configuration. - More info: https://doc.traefik.io/traefik/v3.0/routing/services/#serverstransport_1' + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#serverstransport_1 properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -1735,7 +1888,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: serverstransporttcps.traefik.io spec: group: traefik.io @@ -1749,20 +1902,26 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: 'ServersTransportTCP is the CRD implementation of a TCPServersTransport. - If no tcpServersTransport is specified, a default one named default@internal - will be used. The default@internal tcpServersTransport can be configured - in the static configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/services/#serverstransport_3' + description: |- + ServersTransportTCP is the CRD implementation of a TCPServersTransport. + If no tcpServersTransport is specified, a default one named default@internal will be used. + The default@internal tcpServersTransport can be configured in the static configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#serverstransport_3 properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -1808,9 +1967,9 @@ spec: description: InsecureSkipVerify disables TLS certificate verification. type: boolean peerCertURI: - description: MaxIdleConnsPerHost controls the maximum idle (keep-alive) - to keep per-host. PeerCertURI defines the peer cert URI used - to match against SAN URI during the peer certificate verification. + description: |- + MaxIdleConnsPerHost controls the maximum idle (keep-alive) to keep per-host. + PeerCertURI defines the peer cert URI used to match against SAN URI during the peer certificate verification. type: string rootCAsSecrets: description: RootCAsSecrets defines a list of CA secret used to @@ -1849,7 +2008,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: tlsoptions.traefik.io spec: group: traefik.io @@ -1863,19 +2022,24 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: 'TLSOption is the CRD implementation of a Traefik TLS Option, - allowing to configure some parameters of the TLS connection. More info: - https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options' + description: |- + TLSOption is the CRD implementation of a Traefik TLS Option, allowing to configure some parameters of the TLS connection. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -1883,15 +2047,16 @@ spec: description: TLSOptionSpec defines the desired state of a TLSOption. properties: alpnProtocols: - description: 'ALPNProtocols defines the list of supported application - level protocols for the TLS handshake, in order of preference. More - info: https://doc.traefik.io/traefik/v3.0/https/tls/#alpn-protocols' + description: |- + ALPNProtocols defines the list of supported application level protocols for the TLS handshake, in order of preference. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#alpn-protocols items: type: string type: array cipherSuites: - description: 'CipherSuites defines the list of supported cipher suites - for TLS versions up to TLS 1.2. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#cipher-suites' + description: |- + CipherSuites defines the list of supported cipher suites for TLS versions up to TLS 1.2. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#cipher-suites items: type: string type: array @@ -1917,21 +2082,30 @@ spec: type: array type: object curvePreferences: - description: 'CurvePreferences defines the preferred elliptic curves - in a specific order. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#curve-preferences' + description: |- + CurvePreferences defines the preferred elliptic curves in a specific order. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#curve-preferences items: type: string type: array maxVersion: - description: 'MaxVersion defines the maximum TLS version that Traefik - will accept. Possible values: VersionTLS10, VersionTLS11, VersionTLS12, - VersionTLS13. Default: None.' + description: |- + MaxVersion defines the maximum TLS version that Traefik will accept. + Possible values: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13. + Default: None. type: string minVersion: - description: 'MinVersion defines the minimum TLS version that Traefik - will accept. Possible values: VersionTLS10, VersionTLS11, VersionTLS12, - VersionTLS13. Default: VersionTLS10.' + description: |- + MinVersion defines the minimum TLS version that Traefik will accept. + Possible values: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13. + Default: VersionTLS10. type: string + preferServerCipherSuites: + description: |- + PreferServerCipherSuites defines whether the server chooses a cipher suite among his own instead of among the client's. + It is enabled automatically when minVersion or maxVersion is set. + Deprecated: https://github.com/golang/go/issues/45430 + type: boolean sniStrict: description: SniStrict defines whether Traefik allows connections from clients connections that do not specify a server_name extension. @@ -1948,7 +2122,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: tlsstores.traefik.io spec: group: traefik.io @@ -1962,20 +2136,26 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: 'TLSStore is the CRD implementation of a Traefik TLS Store. For - the time being, only the TLSStore named default is supported. This means - that you cannot have two stores that are named default in different Kubernetes - namespaces. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#certificates-stores' + description: |- + TLSStore is the CRD implementation of a Traefik TLS Store. + For the time being, only the TLSStore named default is supported. + This means that you cannot have two stores that are named default in different Kubernetes namespaces. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#certificates-stores properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -2039,7 +2219,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: traefikservices.traefik.io spec: group: traefik.io @@ -2053,19 +2233,27 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: 'TraefikService is the CRD implementation of a Traefik Service. - TraefikService object allows to: - Apply weight to Services on load-balancing - - Mirror traffic on services More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-traefikservice' + description: |- + TraefikService is the CRD implementation of a Traefik Service. + TraefikService object allows to: + - Apply weight to Services on load-balancing + - Mirror traffic on services + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-traefikservice properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -2082,10 +2270,10 @@ spec: - TraefikService type: string maxBodySize: - description: MaxBodySize defines the maximum size allowed for - the body of the request. If the body is larger, the request - is not mirrored. Default value is -1, which means unlimited - size. + description: |- + MaxBodySize defines the maximum size allowed for the body of the request. + If the body is larger, the request is not mirrored. + Default value is -1, which means unlimited size. format: int64 type: integer mirrors: @@ -2101,35 +2289,37 @@ spec: - TraefikService type: string name: - description: Name defines the name of the referenced Kubernetes - Service or TraefikService. The differentiation between - the two is specified in the Kind field. + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. type: string namespace: description: Namespace defines the namespace of the referenced Kubernetes Service or TraefikService. type: string nativeLB: - description: NativeLB controls, when creating the load-balancer, - whether the LB's children are directly the pods IPs or - if the only child is the Kubernetes Service clusterIP. - The Kubernetes Service itself does load-balance to the - pods. By default, NativeLB is false. + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. type: boolean passHostHeader: - description: PassHostHeader defines whether the client Host - header is forwarded to the upstream Kubernetes Service. + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean percent: - description: 'Percent defines the part of the traffic to - mirror. Supported values: 0 to 100.' + description: |- + Percent defines the part of the traffic to mirror. + Supported values: 0 to 100. type: integer port: anyOf: - type: integer - type: string - description: Port defines the port of a Kubernetes Service. + description: |- + Port defines the port of a Kubernetes Service. This can be a reference to a named port. x-kubernetes-int-or-string: true responseForwarding: @@ -2138,30 +2328,29 @@ spec: client. properties: flushInterval: - description: 'FlushInterval defines the interval, in - milliseconds, in between flushes to the client while - copying the response body. A negative value means - to flush immediately after each write to the client. - This configuration is ignored when ReverseProxy recognizes - a response as a streaming response; for such responses, - writes are flushed to the client immediately. Default: - 100ms' + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; + for such responses, writes are flushed to the client immediately. + Default: 100ms type: string type: object scheme: - description: Scheme defines the scheme to use for the request - to the upstream Kubernetes Service. It defaults to https - when Kubernetes Service port is 443, http otherwise. + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. type: string serversTransport: - description: ServersTransport defines the name of ServersTransport - resource to use. It allows to configure the transport - between Traefik and your servers. Can only be used on - a Kubernetes Service. + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. type: string sticky: - description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -2171,17 +2360,18 @@ spec: can be accessed by client-side APIs, such as JavaScript. type: boolean maxAge: - description: MaxAge indicates the number of seconds - until the cookie expires. When set to a negative - number, the cookie expires immediately. When set - to zero, the cookie never expires. + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. type: integer name: description: Name defines the Cookie name. type: string sameSite: - description: 'SameSite defines the same site policy. - More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite type: string secure: description: Secure defines whether the cookie can @@ -2191,13 +2381,13 @@ spec: type: object type: object strategy: - description: Strategy defines the load balancing strategy - between the servers. RoundRobin is the only supported - value at the moment. + description: |- + Strategy defines the load balancing strategy between the servers. + RoundRobin is the only supported value at the moment. type: string weight: - description: Weight defines the weight and should only be - specified when Name references a TraefikService object + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object (and to be precise, one that embeds a Weighted Round Robin). type: integer required: @@ -2205,60 +2395,62 @@ spec: type: object type: array name: - description: Name defines the name of the referenced Kubernetes - Service or TraefikService. The differentiation between the two - is specified in the Kind field. + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. type: string namespace: description: Namespace defines the namespace of the referenced Kubernetes Service or TraefikService. type: string nativeLB: - description: NativeLB controls, when creating the load-balancer, - whether the LB's children are directly the pods IPs or if the - only child is the Kubernetes Service clusterIP. The Kubernetes - Service itself does load-balance to the pods. By default, NativeLB - is false. + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. type: boolean passHostHeader: - description: PassHostHeader defines whether the client Host header - is forwarded to the upstream Kubernetes Service. By default, - passHostHeader is true. + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. + By default, passHostHeader is true. type: boolean port: anyOf: - type: integer - type: string - description: Port defines the port of a Kubernetes Service. This - can be a reference to a named port. + description: |- + Port defines the port of a Kubernetes Service. + This can be a reference to a named port. x-kubernetes-int-or-string: true responseForwarding: description: ResponseForwarding defines how Traefik forwards the response from the upstream Kubernetes Service to the client. properties: flushInterval: - description: 'FlushInterval defines the interval, in milliseconds, - in between flushes to the client while copying the response - body. A negative value means to flush immediately after - each write to the client. This configuration is ignored - when ReverseProxy recognizes a response as a streaming response; + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; for such responses, writes are flushed to the client immediately. - Default: 100ms' + Default: 100ms type: string type: object scheme: - description: Scheme defines the scheme to use for the request - to the upstream Kubernetes Service. It defaults to https when - Kubernetes Service port is 443, http otherwise. + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. type: string serversTransport: - description: ServersTransport defines the name of ServersTransport - resource to use. It allows to configure the transport between - Traefik and your servers. Can only be used on a Kubernetes Service. + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. type: string sticky: - description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -2268,17 +2460,18 @@ spec: accessed by client-side APIs, such as JavaScript. type: boolean maxAge: - description: MaxAge indicates the number of seconds until - the cookie expires. When set to a negative number, the - cookie expires immediately. When set to zero, the cookie - never expires. + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. type: integer name: description: Name defines the Cookie name. type: string sameSite: - description: 'SameSite defines the same site policy. More - info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite type: string secure: description: Secure defines whether the cookie can only @@ -2287,13 +2480,14 @@ spec: type: object type: object strategy: - description: Strategy defines the load balancing strategy between - the servers. RoundRobin is the only supported value at the moment. + description: |- + Strategy defines the load balancing strategy between the servers. + RoundRobin is the only supported value at the moment. type: string weight: - description: Weight defines the weight and should only be specified - when Name references a TraefikService object (and to be precise, - one that embeds a Weighted Round Robin). + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object + (and to be precise, one that embeds a Weighted Round Robin). type: integer required: - name @@ -2315,31 +2509,32 @@ spec: - TraefikService type: string name: - description: Name defines the name of the referenced Kubernetes - Service or TraefikService. The differentiation between - the two is specified in the Kind field. + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. type: string namespace: description: Namespace defines the namespace of the referenced Kubernetes Service or TraefikService. type: string nativeLB: - description: NativeLB controls, when creating the load-balancer, - whether the LB's children are directly the pods IPs or - if the only child is the Kubernetes Service clusterIP. - The Kubernetes Service itself does load-balance to the - pods. By default, NativeLB is false. + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. type: boolean passHostHeader: - description: PassHostHeader defines whether the client Host - header is forwarded to the upstream Kubernetes Service. + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean port: anyOf: - type: integer - type: string - description: Port defines the port of a Kubernetes Service. + description: |- + Port defines the port of a Kubernetes Service. This can be a reference to a named port. x-kubernetes-int-or-string: true responseForwarding: @@ -2348,30 +2543,29 @@ spec: client. properties: flushInterval: - description: 'FlushInterval defines the interval, in - milliseconds, in between flushes to the client while - copying the response body. A negative value means - to flush immediately after each write to the client. - This configuration is ignored when ReverseProxy recognizes - a response as a streaming response; for such responses, - writes are flushed to the client immediately. Default: - 100ms' + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; + for such responses, writes are flushed to the client immediately. + Default: 100ms type: string type: object scheme: - description: Scheme defines the scheme to use for the request - to the upstream Kubernetes Service. It defaults to https - when Kubernetes Service port is 443, http otherwise. + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. type: string serversTransport: - description: ServersTransport defines the name of ServersTransport - resource to use. It allows to configure the transport - between Traefik and your servers. Can only be used on - a Kubernetes Service. + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. type: string sticky: - description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -2381,17 +2575,18 @@ spec: can be accessed by client-side APIs, such as JavaScript. type: boolean maxAge: - description: MaxAge indicates the number of seconds - until the cookie expires. When set to a negative - number, the cookie expires immediately. When set - to zero, the cookie never expires. + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. type: integer name: description: Name defines the Cookie name. type: string sameSite: - description: 'SameSite defines the same site policy. - More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite type: string secure: description: Secure defines whether the cookie can @@ -2401,13 +2596,13 @@ spec: type: object type: object strategy: - description: Strategy defines the load balancing strategy - between the servers. RoundRobin is the only supported - value at the moment. + description: |- + Strategy defines the load balancing strategy between the servers. + RoundRobin is the only supported value at the moment. type: string weight: - description: Weight defines the weight and should only be - specified when Name references a TraefikService object + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object (and to be precise, one that embeds a Weighted Round Robin). type: integer required: @@ -2415,8 +2610,9 @@ spec: type: object type: array sticky: - description: 'Sticky defines whether sticky sessions are enabled. - More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#stickiness-and-load-balancing' + description: |- + Sticky defines whether sticky sessions are enabled. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#stickiness-and-load-balancing properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -2426,17 +2622,18 @@ spec: accessed by client-side APIs, such as JavaScript. type: boolean maxAge: - description: MaxAge indicates the number of seconds until - the cookie expires. When set to a negative number, the - cookie expires immediately. When set to zero, the cookie - never expires. + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. type: integer name: description: Name defines the Cookie name. type: string sameSite: - description: 'SameSite defines the same site policy. More - info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite type: string secure: description: Secure defines whether the cookie can only diff --git a/integration/fixtures/k8s_gateway_conformance.toml b/integration/fixtures/k8s_gateway_conformance.toml new file mode 100644 index 000000000..5114f2dfb --- /dev/null +++ b/integration/fixtures/k8s_gateway_conformance.toml @@ -0,0 +1,21 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "DEBUG" + noColor = true + +[api] + insecure = true + +[experimental] + kubernetesGateway = true + +[entryPoints] + [entryPoints.web] + address = ":80" + [entryPoints.websecure] + address = ":443" + +[providers.kubernetesGateway] diff --git a/integration/fixtures/throttling/simple.toml b/integration/fixtures/throttling/simple.toml index 668d4988b..cd98c44f1 100644 --- a/integration/fixtures/throttling/simple.toml +++ b/integration/fixtures/throttling/simple.toml @@ -19,5 +19,6 @@ insecure = true [metrics] + addInternals = true [metrics.prometheus] buckets = [0.1,0.3,1.2,5.0] diff --git a/integration/fixtures/tracing/simple-opentelemetry.toml b/integration/fixtures/tracing/simple-opentelemetry.toml index 77d779800..0599bb18d 100644 --- a/integration/fixtures/tracing/simple-opentelemetry.toml +++ b/integration/fixtures/tracing/simple-opentelemetry.toml @@ -9,6 +9,8 @@ [api] insecure = true +[ping] + [entryPoints] [entryPoints.web] address = ":8000" @@ -47,6 +49,10 @@ Service = "service3" Middlewares = ["retry", "basic-auth"] Rule = "Path(`/auth`)" + [http.routers.customPing] + entryPoints = ["web"] + rule = "PathPrefix(`/ping`)" + service = "ping@internal" [http.middlewares] [http.middlewares.retry.retry] diff --git a/integration/fixtures/with_default_rule_syntax.toml b/integration/fixtures/with_default_rule_syntax.toml new file mode 100644 index 000000000..76001eac9 --- /dev/null +++ b/integration/fixtures/with_default_rule_syntax.toml @@ -0,0 +1,41 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[core] + defaultRuleSyntax = "v2" + +[log] + level = "DEBUG" + noColor = true + +[entryPoints] + [entryPoints.web] + address = ":8000" + +[api] + insecure = true + +[providers.file] + filename = "{{ .SelfFilename }}" + +## dynamic configuration ## + +[http.routers] + [http.routers.router1] + service = "service1" + rule = "PathPrefix(`/foo`, `/bar`)" + + [http.routers.router2] + service = "service1" + rule = "QueryRegexp(`foo`, `bar`)" + + [http.routers.router3] + service = "service1" + rule = "PathPrefix(`/foo`, `/bar`)" + ruleSyntax = "v3" + +[http.services] + [http.services.service1] + [http.services.service1.loadBalancer] + [http.services.service1.loadBalancer.servers] diff --git a/integration/fixtures/without_default_rule_syntax.toml b/integration/fixtures/without_default_rule_syntax.toml new file mode 100644 index 000000000..772ea7a85 --- /dev/null +++ b/integration/fixtures/without_default_rule_syntax.toml @@ -0,0 +1,38 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "DEBUG" + noColor = true + +[entryPoints] + [entryPoints.web] + address = ":8000" + +[api] + insecure = true + +[providers.file] + filename = "{{ .SelfFilename }}" + +## dynamic configuration ## + +[http.routers] + [http.routers.router1] + service = "service1" + rule = "PathPrefix(`/foo`) || PathPrefix(`/bar`)" + + [http.routers.router2] + service = "service1" + rule = "PathPrefix(`/foo`, `/bar`)" + + [http.routers.router3] + service = "service1" + rule = "QueryRegexp(`foo`, `bar`)" + ruleSyntax = "v2" + +[http.services] + [http.services.service1] + [http.services.service1.loadBalancer] + [http.services.service1.loadBalancer.servers] diff --git a/integration/fixtures/wrr_server.toml b/integration/fixtures/wrr_server.toml new file mode 100644 index 000000000..d1735986d --- /dev/null +++ b/integration/fixtures/wrr_server.toml @@ -0,0 +1,35 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[api] + insecure = true + +[log] + level = "DEBUG" + noColor = true + +[entryPoints] + + [entryPoints.web] + address = ":8000" + +[providers.file] + filename = "{{ .SelfFilename }}" + +## dynamic configuration ## + +[http.routers] + [http.routers.router] + service = "service1" + rule = "Path(`/whoami`)" + +[http.services] + + [http.services.service1.loadBalancer] + [[http.services.service1.loadBalancer.servers]] + url = "{{ .Server1 }}" + weight = 3 + [[http.services.service1.loadBalancer.servers]] + url = "{{ .Server2 }}" + diff --git a/integration/integration_test.go b/integration/integration_test.go index 7149d8fd8..51df6870c 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -35,7 +35,13 @@ import ( "gopkg.in/yaml.v3" ) -var showLog = flag.Bool("tlog", false, "always show Traefik logs") +var ( + showLog = flag.Bool("tlog", false, "always show Traefik logs") + k8sConformance = flag.Bool("k8sConformance", false, "run K8s Gateway API conformance test") + k8sConformanceRunTest = flag.String("k8sConformanceRunTest", "", "run a specific K8s Gateway API conformance test") +) + +const tailscaleSecretFilePath = "tailscale.secret" type composeConfig struct { Services map[string]composeService `yaml:"services"` @@ -99,6 +105,11 @@ func (s *BaseSuite) displayTraefikLogFile(path string) { } func (s *BaseSuite) SetupSuite() { + if isDockerDesktop(context.Background(), s.T()) { + _, err := os.Stat(tailscaleSecretFilePath) + require.NoError(s.T(), err, "Tailscale need to be configured when running integration tests with Docker Desktop: (https://doc.traefik.io/traefik/v2.11/contributing/building-testing/#testing)") + } + // configure default standard log. stdlog.SetFlags(stdlog.Lshortfile | stdlog.LstdFlags) // TODO @@ -124,7 +135,7 @@ func (s *BaseSuite) SetupSuite() { s.hostIP = "172.31.42.1" if isDockerDesktop(ctx, s.T()) { s.hostIP = getDockerDesktopHostIP(ctx, s.T()) - s.setupVPN("tailscale.secret") + s.setupVPN(tailscaleSecretFilePath) } } diff --git a/integration/k8s_conformance_test.go b/integration/k8s_conformance_test.go new file mode 100644 index 000000000..88be63c28 --- /dev/null +++ b/integration/k8s_conformance_test.go @@ -0,0 +1,225 @@ +package integration + +import ( + "context" + "fmt" + "net" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/rs/zerolog/log" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "github.com/traefik/traefik/v3/integration/try" + "github.com/traefik/traefik/v3/pkg/version" + "gopkg.in/yaml.v3" + ktypes "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + kclientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + "sigs.k8s.io/controller-runtime/pkg/client" + gatev1 "sigs.k8s.io/gateway-api/apis/v1" + gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + conformanceV1alpha1 "sigs.k8s.io/gateway-api/conformance/apis/v1alpha1" + "sigs.k8s.io/gateway-api/conformance/tests" + "sigs.k8s.io/gateway-api/conformance/utils/config" + ksuite "sigs.k8s.io/gateway-api/conformance/utils/suite" +) + +// K8sConformanceSuite tests suite. +type K8sConformanceSuite struct{ BaseSuite } + +func TestK8sConformanceSuite(t *testing.T) { + suite.Run(t, new(K8sConformanceSuite)) +} + +func (s *K8sConformanceSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + + s.createComposeProject("k8s") + s.composeUp() + + abs, err := filepath.Abs("./fixtures/k8s/config.skip/kubeconfig.yaml") + require.NoError(s.T(), err) + + err = try.Do(60*time.Second, func() error { + _, err := os.Stat(abs) + return err + }) + require.NoError(s.T(), err) + + data, err := os.ReadFile(abs) + require.NoError(s.T(), err) + + content := strings.ReplaceAll(string(data), "https://server:6443", fmt.Sprintf("https://%s", net.JoinHostPort(s.getComposeServiceIP("server"), "6443"))) + + err = os.WriteFile(abs, []byte(content), 0o644) + require.NoError(s.T(), err) + + err = os.Setenv("KUBECONFIG", abs) + require.NoError(s.T(), err) +} + +func (s *K8sConformanceSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() + + generatedFiles := []string{ + "./fixtures/k8s/config.skip/kubeconfig.yaml", + "./fixtures/k8s/config.skip/k3s.log", + "./fixtures/k8s/rolebindings.yaml", + "./fixtures/k8s/ccm.yaml", + } + + for _, filename := range generatedFiles { + if err := os.Remove(filename); err != nil { + log.Warn().Err(err).Send() + } + } +} + +func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() { + if !*k8sConformance { + s.T().Skip("Skip because it can take a long time to execute. To enable pass the `k8sConformance` flag.") + } + + configFromFlags, err := clientcmd.BuildConfigFromFlags("", os.Getenv("KUBECONFIG")) + if err != nil { + s.T().Fatal(err) + } + + kClient, err := client.New(configFromFlags, client.Options{}) + if err != nil { + s.T().Fatalf("Error initializing Kubernetes client: %v", err) + } + + kClientSet, err := kclientset.NewForConfig(configFromFlags) + if err != nil { + s.T().Fatal(err) + } + + err = gatev1alpha2.AddToScheme(kClient.Scheme()) + require.NoError(s.T(), err) + err = gatev1beta1.AddToScheme(kClient.Scheme()) + require.NoError(s.T(), err) + err = gatev1.AddToScheme(kClient.Scheme()) + require.NoError(s.T(), err) + + s.traefikCmd(withConfigFile("fixtures/k8s_gateway_conformance.toml")) + + // Wait for traefik to start + err = try.GetRequest("http://127.0.0.1:8080/api/entrypoints", 10*time.Second, try.BodyContains(`"name":"web"`)) + require.NoError(s.T(), err) + + err = try.Do(10*time.Second, func() error { + gwc := &gatev1.GatewayClass{} + err := kClient.Get(context.Background(), ktypes.NamespacedName{Name: "my-gateway-class"}, gwc) + if err != nil { + return fmt.Errorf("error fetching GatewayClass: %w", err) + } + + return nil + }) + require.NoError(s.T(), err) + + opts := ksuite.Options{ + Client: kClient, + RestConfig: configFromFlags, + Clientset: kClientSet, + GatewayClassName: "my-gateway-class", + Debug: true, + CleanupBaseResources: true, + TimeoutConfig: config.TimeoutConfig{ + CreateTimeout: 5 * time.Second, + DeleteTimeout: 5 * time.Second, + GetTimeout: 5 * time.Second, + GatewayMustHaveAddress: 5 * time.Second, + GatewayMustHaveCondition: 5 * time.Second, + GatewayStatusMustHaveListeners: 10 * time.Second, + GatewayListenersMustHaveCondition: 5 * time.Second, + GWCMustBeAccepted: 60 * time.Second, // Pod creation in k3s cluster can be long. + HTTPRouteMustNotHaveParents: 5 * time.Second, + HTTPRouteMustHaveCondition: 5 * time.Second, + TLSRouteMustHaveCondition: 5 * time.Second, + RouteMustHaveParents: 5 * time.Second, + ManifestFetchTimeout: 5 * time.Second, + MaxTimeToConsistency: 5 * time.Second, + NamespacesMustBeReady: 60 * time.Second, // Pod creation in k3s cluster can be long. + RequestTimeout: 5 * time.Second, + LatestObservedGenerationSet: 5 * time.Second, + RequiredConsecutiveSuccesses: 0, + }, + SupportedFeatures: sets.New[ksuite.SupportedFeature](). + Insert(ksuite.GatewayCoreFeatures.UnsortedList()...). + Insert(ksuite.ReferenceGrantCoreFeatures.UnsortedList()...), + EnableAllSupportedFeatures: false, + RunTest: *k8sConformanceRunTest, + // Until the feature are all supported, following tests are skipped. + SkipTests: []string{ + "HTTPExactPathMatching", + "HTTPRouteHostnameIntersection", + "HTTPRouteListenerHostnameMatching", + "HTTPRouteRequestHeaderModifier", + "GatewayClassObservedGenerationBump", + "HTTPRouteInvalidNonExistentBackendRef", + "GatewayWithAttachedRoutes", + "HTTPRouteCrossNamespace", + "HTTPRouteDisallowedKind", + "HTTPRouteInvalidReferenceGrant", + "HTTPRouteObservedGenerationBump", + "TLSRouteSimpleSameNamespace", + "TLSRouteInvalidReferenceGrant", + "HTTPRouteInvalidCrossNamespaceParentRef", + "HTTPRouteInvalidParentRefNotMatchingSectionName", + "GatewayModifyListeners", + "GatewayInvalidTLSConfiguration", + "HTTPRouteInvalidCrossNamespaceBackendRef", + "HTTPRouteMatchingAcrossRoutes", + "HTTPRoutePartiallyInvalidViaInvalidReferenceGrant", + "HTTPRouteRedirectHostAndStatus", + "HTTPRouteInvalidBackendRefUnknownKind", + "HTTPRoutePathMatchOrder", + "HTTPRouteSimpleSameNamespace", + "HTTPRouteMatching", + "HTTPRouteHeaderMatching", + "HTTPRouteReferenceGrant", + }, + } + + cSuite, err := ksuite.NewExperimentalConformanceTestSuite(ksuite.ExperimentalConformanceOptions{ + Options: opts, + Implementation: conformanceV1alpha1.Implementation{ + Organization: "traefik", + Project: "traefik", + URL: "https://traefik.io/", + Version: version.Version, + Contact: []string{"@traefik/maintainers"}, + }, + ConformanceProfiles: sets.New[ksuite.ConformanceProfileName]( + ksuite.HTTPConformanceProfileName, + ksuite.TLSConformanceProfileName, + ), + }) + require.NoError(s.T(), err) + + cSuite.Setup(s.T()) + err = cSuite.Run(s.T(), tests.ConformanceTests) + require.NoError(s.T(), err) + + report, err := cSuite.Report() + require.NoError(s.T(), err, "failed generating conformance report") + + report.GatewayAPIVersion = "1.0.0" + + rawReport, err := yaml.Marshal(report) + require.NoError(s.T(), err) + s.T().Logf("Conformance report:\n%s", string(rawReport)) + + require.NoError(s.T(), os.MkdirAll("./conformance-reports", 0o755)) + outFile := filepath.Join("conformance-reports", fmt.Sprintf("traefik-traefik-%d.yaml", time.Now().UnixNano())) + require.NoError(s.T(), os.WriteFile(outFile, rawReport, 0o600)) + s.T().Logf("Report written to: %s", outFile) +} diff --git a/integration/log_rotation_test.go b/integration/log_rotation_test.go index 28d72f8cd..269d17d69 100644 --- a/integration/log_rotation_test.go +++ b/integration/log_rotation_test.go @@ -57,7 +57,7 @@ func (s *LogRotationSuite) TearDownSuite() { func (s *LogRotationSuite) TestAccessLogRotation() { // Start Traefik - cmd, _ := s.cmdTraefik(withConfigFile("fixtures/access_log_config.toml")) + cmd, _ := s.cmdTraefik(withConfigFile("fixtures/access_log/access_log_base.toml")) defer s.displayTraefikLogFile(traefikTestLogFile) // Verify Traefik started ok diff --git a/integration/resources/compose/docker.yml b/integration/resources/compose/docker.yml index e594ec87c..b16571a4b 100644 --- a/integration/resources/compose/docker.yml +++ b/integration/resources/compose/docker.yml @@ -36,3 +36,14 @@ services: labels: traefik.http.Routers.Super.Rule: Host(`my.super.host`) traefik.http.Services.powpow.LoadBalancer.server.Port: 2375 + + wrr-server: + image: traefik/whoami + labels: + traefik.http.Routers.wrr-server.Rule: Host(`my.wrr.host`) + traefik.http.Services.wrr-server.LoadBalancer.server.Weight: 4 + wrr-server2: + image: traefik/whoami + labels: + traefik.http.Routers.wrr-server.Rule: Host(`my.wrr.host`) + traefik.http.Services.wrr-server.LoadBalancer.server.Weight: 1 diff --git a/integration/simple_test.go b/integration/simple_test.go index d8f0f45b7..8e2f14ec1 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -287,6 +287,10 @@ func (s *SimpleSuite) TestMetricsPrometheusDefaultEntryPoint() { err = try.GetRequest("http://127.0.0.1:8080/metrics", 1*time.Second, try.BodyContains("_service_")) require.NoError(s.T(), err) + + // No metrics for internals. + err = try.GetRequest("http://127.0.0.1:8080/metrics", 1*time.Second, try.BodyNotContains("router=\"api@internal\"", "service=\"api@internal\"")) + require.NoError(s.T(), err) } func (s *SimpleSuite) TestMetricsPrometheusTwoRoutersOneService() { @@ -659,6 +663,66 @@ func (s *SimpleSuite) TestSimpleConfigurationHostRequestTrailingPeriod() { } } +func (s *SimpleSuite) TestWithDefaultRuleSyntax() { + file := s.adaptFile("fixtures/with_default_rule_syntax.toml", struct{}{}) + + s.traefikCmd(withConfigFile(file)) + + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix")) + require.NoError(s.T(), err) + + // router1 has no error + err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router1@file", 1*time.Second, try.BodyContains(`"status":"enabled"`)) + require.NoError(s.T(), err) + + err = try.GetRequest("http://127.0.0.1:8000/notfound", 1*time.Second, try.StatusCodeIs(http.StatusNotFound)) + require.NoError(s.T(), err) + + err = try.GetRequest("http://127.0.0.1:8000/foo", 1*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable)) + require.NoError(s.T(), err) + + err = try.GetRequest("http://127.0.0.1:8000/bar", 1*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable)) + require.NoError(s.T(), err) + + // router2 has an error because it uses the wrong rule syntax (v3 instead of v2) + err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router2@file", 1*time.Second, try.BodyContains("error while parsing rule QueryRegexp(`foo`, `bar`): unsupported function: QueryRegexp")) + require.NoError(s.T(), err) + + // router3 has an error because it uses the wrong rule syntax (v2 instead of v3) + err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router3@file", 1*time.Second, try.BodyContains("error while adding rule PathPrefix: unexpected number of parameters; got 2, expected one of [1]")) + require.NoError(s.T(), err) +} + +func (s *SimpleSuite) TestWithoutDefaultRuleSyntax() { + file := s.adaptFile("fixtures/without_default_rule_syntax.toml", struct{}{}) + + s.traefikCmd(withConfigFile(file)) + + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix")) + require.NoError(s.T(), err) + + // router1 has no error + err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router1@file", 1*time.Second, try.BodyContains(`"status":"enabled"`)) + require.NoError(s.T(), err) + + err = try.GetRequest("http://127.0.0.1:8000/notfound", 1*time.Second, try.StatusCodeIs(http.StatusNotFound)) + require.NoError(s.T(), err) + + err = try.GetRequest("http://127.0.0.1:8000/foo", 1*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable)) + require.NoError(s.T(), err) + + err = try.GetRequest("http://127.0.0.1:8000/bar", 1*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable)) + require.NoError(s.T(), err) + + // router2 has an error because it uses the wrong rule syntax (v3 instead of v2) + err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router2@file", 1*time.Second, try.BodyContains("error while adding rule PathPrefix: unexpected number of parameters; got 2, expected one of [1]")) + require.NoError(s.T(), err) + + // router2 has an error because it uses the wrong rule syntax (v2 instead of v3) + err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router3@file", 1*time.Second, try.BodyContains("error while parsing rule QueryRegexp(`foo`, `bar`): unsupported function: QueryRegexp")) + require.NoError(s.T(), err) +} + func (s *SimpleSuite) TestRouterConfigErrors() { file := s.adaptFile("fixtures/router_errors.toml", struct{}{}) @@ -749,6 +813,49 @@ func (s *SimpleSuite) TestUDPServiceConfigErrors() { require.NoError(s.T(), err) } +func (s *SimpleSuite) TestWRRServer() { + s.createComposeProject("base") + + s.composeUp() + defer s.composeDown() + + whoami1IP := s.getComposeServiceIP("whoami1") + whoami2IP := s.getComposeServiceIP("whoami2") + + file := s.adaptFile("fixtures/wrr_server.toml", struct { + Server1 string + Server2 string + }{Server1: "http://" + whoami1IP, Server2: "http://" + whoami2IP}) + + s.traefikCmd(withConfigFile(file)) + + err := try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains("service1")) + require.NoError(s.T(), err) + + repartition := map[string]int{} + for i := 0; i < 4; i++ { + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil) + require.NoError(s.T(), err) + + response, err := http.DefaultClient.Do(req) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusOK, response.StatusCode) + + body, err := io.ReadAll(response.Body) + require.NoError(s.T(), err) + + if strings.Contains(string(body), whoami1IP) { + repartition[whoami1IP]++ + } + if strings.Contains(string(body), whoami2IP) { + repartition[whoami2IP]++ + } + } + + assert.Equal(s.T(), 3, repartition[whoami1IP]) + assert.Equal(s.T(), 1, repartition[whoami2IP]) +} + func (s *SimpleSuite) TestWRR() { s.createComposeProject("base") diff --git a/integration/tcp_test.go b/integration/tcp_test.go index 732bc1f03..059714d92 100644 --- a/integration/tcp_test.go +++ b/integration/tcp_test.go @@ -302,7 +302,7 @@ func (s *TCPSuite) TestWRR() { time.Sleep(time.Second) } - assert.EqualValues(s.T(), call, map[string]int{"whoami-b": 3, "whoami-ab": 1}) + assert.EqualValues(s.T(), map[string]int{"whoami-b": 3, "whoami-ab": 1}, call) } func welcome(addr string) (string, error) { @@ -404,7 +404,6 @@ func guessWhoTLSPassthrough(addr, serverName string) (string, error) { return fmt.Errorf("tls: no valid certificate for serverName %s", serverName) }, }) - if err != nil { return "", err } diff --git a/integration/testdata/rawdata-gateway.json b/integration/testdata/rawdata-gateway.json index f8e364e66..909ce1326 100644 --- a/integration/testdata/rawdata-gateway.json +++ b/integration/testdata/rawdata-gateway.json @@ -34,6 +34,7 @@ ], "service": "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr", "rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)", + "ruleSyntax": "v3", "priority": 31, "status": "enabled", "using": [ @@ -46,6 +47,7 @@ ], "service": "default-http-app-1-my-https-gateway-websecure-1c0cf64bde37d9d0df06-wrr", "rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)", + "ruleSyntax": "v3", "priority": 31, "tls": {}, "status": "enabled", @@ -152,6 +154,7 @@ ], "service": "default-tcp-app-1-my-tcp-gateway-footcp-e3b0c44298fc1c149afb-wrr-0", "rule": "HostSNI(`*`)", + "ruleSyntax": "v3", "priority": -1, "status": "enabled", "using": [ @@ -164,6 +167,7 @@ ], "service": "default-tcp-app-1-my-tls-gateway-footlsterminate-e3b0c44298fc1c149afb-wrr-0", "rule": "HostSNI(`*`)", + "ruleSyntax": "v3", "priority": -1, "tls": { "passthrough": false @@ -179,6 +183,7 @@ ], "service": "default-tls-app-1-my-tls-gateway-footlspassthrough-2279fe75c5156dc5eb26-wrr-0", "rule": "HostSNI(`foo.bar`)", + "ruleSyntax": "v3", "priority": 18, "tls": { "passthrough": true diff --git a/integration/tracing_test.go b/integration/tracing_test.go index c94f3c58b..ef92015bd 100644 --- a/integration/tracing_test.go +++ b/integration/tracing_test.go @@ -414,6 +414,67 @@ func (s *TracingSuite) TestOpentelemetryAuth() { s.checkTraceContent(contains) } +func (s *TracingSuite) TestNoInternals() { + file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{ + WhoamiIP: s.whoamiIP, + WhoamiPort: s.whoamiPort, + IP: s.otelCollectorIP, + IsHTTP: true, + }) + + s.traefikCmd(withConfigFile(file)) + + // wait for traefik + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth")) + require.NoError(s.T(), err) + + err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) + + err = try.GetRequest("http://127.0.0.1:8000/ping", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) + err = try.GetRequest("http://127.0.0.1:8080/ping", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) + + baseURL, err := url.Parse("http://" + s.tempoIP + ":3200/api/search") + require.NoError(s.T(), err) + + req := &http.Request{ + Method: http.MethodGet, + URL: baseURL, + } + // Wait for traces to be available. + time.Sleep(10 * time.Second) + resp, err := try.Response(req, 5*time.Second) + require.NoError(s.T(), err) + + out := &TraceResponse{} + content, err := io.ReadAll(resp.Body) + require.NoError(s.T(), err) + err = json.Unmarshal(content, &out) + require.NoError(s.T(), err) + + s.NotEmptyf(len(out.Traces), "expected at least one trace") + + for _, t := range out.Traces { + baseURL, err := url.Parse("http://" + s.tempoIP + ":3200/api/traces/" + t.TraceID) + require.NoError(s.T(), err) + + req := &http.Request{ + Method: http.MethodGet, + URL: baseURL, + } + + resp, err := try.Response(req, 5*time.Second) + require.NoError(s.T(), err) + + content, err := io.ReadAll(resp.Body) + require.NoError(s.T(), err) + + require.NotContains(s.T(), content, "@internal") + } +} + func (s *TracingSuite) checkTraceContent(expectedJSON []map[string]string) { s.T().Helper() diff --git a/integration/udp_test.go b/integration/udp_test.go index 30753a7f5..e8acadfba 100644 --- a/integration/udp_test.go +++ b/integration/udp_test.go @@ -96,7 +96,7 @@ func (s *UDPSuite) TestWRR() { call["unknown"]++ } } - assert.EqualValues(s.T(), call, map[string]int{"whoami-a": 3, "whoami-b": 2, "whoami-c": 3}) + assert.EqualValues(s.T(), map[string]int{"whoami-a": 3, "whoami-b": 2, "whoami-c": 3}, call) close(stop) }() diff --git a/pkg/api/handler.go b/pkg/api/handler.go index 9c135546a..a8345b9ff 100644 --- a/pkg/api/handler.go +++ b/pkg/api/handler.go @@ -76,7 +76,7 @@ func New(staticConfig static.Configuration, runtimeConfig *runtime.Configuration // createRouter creates API routes and router. func (h Handler) createRouter() *mux.Router { - router := mux.NewRouter() + router := mux.NewRouter().UseEncodedPath() if h.staticConfig.API.Debug { DebugHandler{}.Append(router) diff --git a/pkg/api/handler_entrypoint.go b/pkg/api/handler_entrypoint.go index d21e83d0d..90dc66daa 100644 --- a/pkg/api/handler_entrypoint.go +++ b/pkg/api/handler_entrypoint.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "sort" "strconv" @@ -49,7 +50,13 @@ func (h Handler) getEntryPoints(rw http.ResponseWriter, request *http.Request) { } func (h Handler) getEntryPoint(rw http.ResponseWriter, request *http.Request) { - entryPointID := mux.Vars(request)["entryPointID"] + scapedEntryPointID := mux.Vars(request)["entryPointID"] + + entryPointID, err := url.PathUnescape(scapedEntryPointID) + if err != nil { + writeError(rw, fmt.Sprintf("unable to decode entryPointID %q: %s", scapedEntryPointID, err), http.StatusBadRequest) + return + } rw.Header().Set("Content-Type", "application/json") @@ -64,7 +71,7 @@ func (h Handler) getEntryPoint(rw http.ResponseWriter, request *http.Request) { Name: entryPointID, } - err := json.NewEncoder(rw).Encode(result) + err = json.NewEncoder(rw).Encode(result) if err != nil { log.Ctx(request.Context()).Error().Err(err).Send() writeError(rw, err.Error(), http.StatusInternalServerError) diff --git a/pkg/api/handler_entrypoint_test.go b/pkg/api/handler_entrypoint_test.go index 7d2f358e4..1630f2f7c 100644 --- a/pkg/api/handler_entrypoint_test.go +++ b/pkg/api/handler_entrypoint_test.go @@ -6,6 +6,7 @@ import ( "io" "net/http" "net/http/httptest" + "net/url" "os" "strconv" "testing" @@ -169,6 +170,21 @@ func TestHandler_EntryPoints(t *testing.T) { jsonFile: "testdata/entrypoint-bar.json", }, }, + { + desc: "one entry point by id containing slash", + path: "/api/entrypoints/" + url.PathEscape("foo / bar"), + conf: static.Configuration{ + Global: &static.Global{}, + API: &static.API{}, + EntryPoints: map[string]*static.EntryPoint{ + "foo / bar": {Address: ":81"}, + }, + }, + expected: expected{ + statusCode: http.StatusOK, + jsonFile: "testdata/entrypoint-foo-slash-bar.json", + }, + }, { desc: "one entry point by id, that does not exist", path: "/api/entrypoints/foo", diff --git a/pkg/api/handler_http.go b/pkg/api/handler_http.go index a0f25d4a6..9439d8ab8 100644 --- a/pkg/api/handler_http.go +++ b/pkg/api/handler_http.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "strconv" "strings" @@ -97,7 +98,13 @@ func (h Handler) getRouters(rw http.ResponseWriter, request *http.Request) { } func (h Handler) getRouter(rw http.ResponseWriter, request *http.Request) { - routerID := mux.Vars(request)["routerID"] + scapedRouterID := mux.Vars(request)["routerID"] + + routerID, err := url.PathUnescape(scapedRouterID) + if err != nil { + writeError(rw, fmt.Sprintf("unable to decode routerID %q: %s", scapedRouterID, err), http.StatusBadRequest) + return + } rw.Header().Set("Content-Type", "application/json") @@ -109,7 +116,7 @@ func (h Handler) getRouter(rw http.ResponseWriter, request *http.Request) { result := newRouterRepresentation(routerID, router) - err := json.NewEncoder(rw).Encode(result) + err = json.NewEncoder(rw).Encode(result) if err != nil { log.Ctx(request.Context()).Error().Err(err).Send() writeError(rw, err.Error(), http.StatusInternalServerError) @@ -148,7 +155,13 @@ func (h Handler) getServices(rw http.ResponseWriter, request *http.Request) { } func (h Handler) getService(rw http.ResponseWriter, request *http.Request) { - serviceID := mux.Vars(request)["serviceID"] + scapedServiceID := mux.Vars(request)["serviceID"] + + serviceID, err := url.PathUnescape(scapedServiceID) + if err != nil { + writeError(rw, fmt.Sprintf("unable to decode serviceID %q: %s", scapedServiceID, err), http.StatusBadRequest) + return + } rw.Header().Add("Content-Type", "application/json") @@ -160,7 +173,7 @@ func (h Handler) getService(rw http.ResponseWriter, request *http.Request) { result := newServiceRepresentation(serviceID, service) - err := json.NewEncoder(rw).Encode(result) + err = json.NewEncoder(rw).Encode(result) if err != nil { log.Ctx(request.Context()).Error().Err(err).Send() writeError(rw, err.Error(), http.StatusInternalServerError) @@ -199,7 +212,13 @@ func (h Handler) getMiddlewares(rw http.ResponseWriter, request *http.Request) { } func (h Handler) getMiddleware(rw http.ResponseWriter, request *http.Request) { - middlewareID := mux.Vars(request)["middlewareID"] + scapedMiddlewareID := mux.Vars(request)["middlewareID"] + + middlewareID, err := url.PathUnescape(scapedMiddlewareID) + if err != nil { + writeError(rw, fmt.Sprintf("unable to decode middlewareID %q: %s", scapedMiddlewareID, err), http.StatusBadRequest) + return + } rw.Header().Set("Content-Type", "application/json") @@ -211,7 +230,7 @@ func (h Handler) getMiddleware(rw http.ResponseWriter, request *http.Request) { result := newMiddlewareRepresentation(middlewareID, middleware) - err := json.NewEncoder(rw).Encode(result) + err = json.NewEncoder(rw).Encode(result) if err != nil { log.Ctx(request.Context()).Error().Err(err).Send() writeError(rw, err.Error(), http.StatusInternalServerError) diff --git a/pkg/api/handler_http_test.go b/pkg/api/handler_http_test.go index f9eac0705..e76628261 100644 --- a/pkg/api/handler_http_test.go +++ b/pkg/api/handler_http_test.go @@ -7,6 +7,7 @@ import ( "io" "net/http" "net/http/httptest" + "net/url" "os" "strconv" "testing" @@ -301,6 +302,27 @@ func TestHandler_HTTP(t *testing.T) { jsonFile: "testdata/router-bar.json", }, }, + { + desc: "one router by id containing slash", + path: "/api/http/routers/" + url.PathEscape("foo / bar@myprovider"), + conf: runtime.Configuration{ + Routers: map[string]*runtime.RouterInfo{ + "foo / bar@myprovider": { + Router: &dynamic.Router{ + EntryPoints: []string{"web"}, + Service: "foo-service@myprovider", + Rule: "Host(`foo.bar`)", + Middlewares: []string{"auth", "addPrefixTest@anotherprovider"}, + }, + Status: "enabled", + }, + }, + }, + expected: expected{ + statusCode: http.StatusOK, + jsonFile: "testdata/router-foo-slash-bar.json", + }, + }, { desc: "one router by id, implicitly using default TLS options", path: "/api/http/routers/baz@myprovider", @@ -661,6 +683,35 @@ func TestHandler_HTTP(t *testing.T) { jsonFile: "testdata/service-bar.json", }, }, + { + desc: "one service by id containing slash", + path: "/api/http/services/" + url.PathEscape("foo / bar@myprovider"), + conf: runtime.Configuration{ + Services: map[string]*runtime.ServiceInfo{ + "foo / bar@myprovider": func() *runtime.ServiceInfo { + si := &runtime.ServiceInfo{ + Service: &dynamic.Service{ + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1", + }, + }, + }, + }, + UsedBy: []string{"foo@myprovider", "test@myprovider"}, + } + si.UpdateServerStatus("http://127.0.0.1", "UP") + return si + }(), + }, + }, + expected: expected{ + statusCode: http.StatusOK, + jsonFile: "testdata/service-foo-slash-bar.json", + }, + }, { desc: "one service by id, that does not exist", path: "/api/http/services/nono@myprovider", @@ -897,6 +948,26 @@ func TestHandler_HTTP(t *testing.T) { jsonFile: "testdata/middleware-auth.json", }, }, + { + desc: "one middleware by id containing slash", + path: "/api/http/middlewares/" + url.PathEscape("foo / bar@myprovider"), + conf: runtime.Configuration{ + Middlewares: map[string]*runtime.MiddlewareInfo{ + "foo / bar@myprovider": { + Middleware: &dynamic.Middleware{ + AddPrefix: &dynamic.AddPrefix{ + Prefix: "/titi", + }, + }, + UsedBy: []string{"test@myprovider"}, + }, + }, + }, + expected: expected{ + statusCode: http.StatusOK, + jsonFile: "testdata/middleware-foo-slash-bar.json", + }, + }, { desc: "one middleware by id, that does not exist", path: "/api/http/middlewares/foo@myprovider", diff --git a/pkg/api/handler_tcp.go b/pkg/api/handler_tcp.go index 7c6a6fe67..3ad0fb5b7 100644 --- a/pkg/api/handler_tcp.go +++ b/pkg/api/handler_tcp.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "strconv" "strings" @@ -90,7 +91,13 @@ func (h Handler) getTCPRouters(rw http.ResponseWriter, request *http.Request) { } func (h Handler) getTCPRouter(rw http.ResponseWriter, request *http.Request) { - routerID := mux.Vars(request)["routerID"] + scapedRouterID := mux.Vars(request)["routerID"] + + routerID, err := url.PathUnescape(scapedRouterID) + if err != nil { + writeError(rw, fmt.Sprintf("unable to decode routerID %q: %s", scapedRouterID, err), http.StatusBadRequest) + return + } rw.Header().Set("Content-Type", "application/json") @@ -102,7 +109,7 @@ func (h Handler) getTCPRouter(rw http.ResponseWriter, request *http.Request) { result := newTCPRouterRepresentation(routerID, router) - err := json.NewEncoder(rw).Encode(result) + err = json.NewEncoder(rw).Encode(result) if err != nil { log.Ctx(request.Context()).Error().Err(err).Send() writeError(rw, err.Error(), http.StatusInternalServerError) @@ -141,7 +148,13 @@ func (h Handler) getTCPServices(rw http.ResponseWriter, request *http.Request) { } func (h Handler) getTCPService(rw http.ResponseWriter, request *http.Request) { - serviceID := mux.Vars(request)["serviceID"] + scapedServiceID := mux.Vars(request)["serviceID"] + + serviceID, err := url.PathUnescape(scapedServiceID) + if err != nil { + writeError(rw, fmt.Sprintf("unable to decode serviceID %q: %s", scapedServiceID, err), http.StatusBadRequest) + return + } rw.Header().Set("Content-Type", "application/json") @@ -153,7 +166,7 @@ func (h Handler) getTCPService(rw http.ResponseWriter, request *http.Request) { result := newTCPServiceRepresentation(serviceID, service) - err := json.NewEncoder(rw).Encode(result) + err = json.NewEncoder(rw).Encode(result) if err != nil { log.Ctx(request.Context()).Error().Err(err).Send() writeError(rw, err.Error(), http.StatusInternalServerError) @@ -192,7 +205,13 @@ func (h Handler) getTCPMiddlewares(rw http.ResponseWriter, request *http.Request } func (h Handler) getTCPMiddleware(rw http.ResponseWriter, request *http.Request) { - middlewareID := mux.Vars(request)["middlewareID"] + scapedMiddlewareID := mux.Vars(request)["middlewareID"] + + middlewareID, err := url.PathUnescape(scapedMiddlewareID) + if err != nil { + writeError(rw, fmt.Sprintf("unable to decode middlewareID %q: %s", scapedMiddlewareID, err), http.StatusBadRequest) + return + } rw.Header().Set("Content-Type", "application/json") @@ -204,7 +223,7 @@ func (h Handler) getTCPMiddleware(rw http.ResponseWriter, request *http.Request) result := newTCPMiddlewareRepresentation(middlewareID, middleware) - err := json.NewEncoder(rw).Encode(result) + err = json.NewEncoder(rw).Encode(result) if err != nil { log.Ctx(request.Context()).Error().Err(err).Send() writeError(rw, err.Error(), http.StatusInternalServerError) diff --git a/pkg/api/handler_tcp_test.go b/pkg/api/handler_tcp_test.go index 6c963bce5..2bf5e5706 100644 --- a/pkg/api/handler_tcp_test.go +++ b/pkg/api/handler_tcp_test.go @@ -6,6 +6,7 @@ import ( "io" "net/http" "net/http/httptest" + "net/url" "os" "testing" @@ -295,6 +296,25 @@ func TestHandler_TCP(t *testing.T) { jsonFile: "testdata/tcprouter-bar.json", }, }, + { + desc: "one TCP router by id containing slash", + path: "/api/tcp/routers/" + url.PathEscape("foo / bar@myprovider"), + conf: runtime.Configuration{ + TCPRouters: map[string]*runtime.TCPRouterInfo{ + "foo / bar@myprovider": { + TCPRouter: &dynamic.TCPRouter{ + EntryPoints: []string{"web"}, + Service: "foo-service@myprovider", + Rule: "Host(`foo.bar`)", + }, + }, + }, + }, + expected: expected{ + statusCode: http.StatusOK, + jsonFile: "testdata/tcprouter-foo-slash-bar.json", + }, + }, { desc: "one TCP router by id, that does not exist", path: "/api/tcp/routers/foo@myprovider", @@ -559,6 +579,30 @@ func TestHandler_TCP(t *testing.T) { jsonFile: "testdata/tcpservice-bar.json", }, }, + { + desc: "one tcp service by id containing slash", + path: "/api/tcp/services/" + url.PathEscape("foo / bar@myprovider"), + conf: runtime.Configuration{ + TCPServices: map[string]*runtime.TCPServiceInfo{ + "foo / bar@myprovider": { + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.1:2345", + }, + }, + }, + }, + UsedBy: []string{"foo@myprovider", "test@myprovider"}, + }, + }, + }, + expected: expected{ + statusCode: http.StatusOK, + jsonFile: "testdata/tcpservice-foo-slash-bar.json", + }, + }, { desc: "one tcp service by id, that does not exist", path: "/api/tcp/services/nono@myprovider", @@ -780,6 +824,26 @@ func TestHandler_TCP(t *testing.T) { jsonFile: "testdata/tcpmiddleware-ipallowlist.json", }, }, + { + desc: "one middleware by id containing slash", + path: "/api/tcp/middlewares/" + url.PathEscape("foo / bar@myprovider"), + conf: runtime.Configuration{ + TCPMiddlewares: map[string]*runtime.TCPMiddlewareInfo{ + "foo / bar@myprovider": { + TCPMiddleware: &dynamic.TCPMiddleware{ + IPWhiteList: &dynamic.TCPIPWhiteList{ + SourceRange: []string{"127.0.0.1/32"}, + }, + }, + UsedBy: []string{"bar@myprovider", "test@myprovider"}, + }, + }, + }, + expected: expected{ + statusCode: http.StatusOK, + jsonFile: "testdata/tcpmiddleware-foo-slash-bar.json", + }, + }, { desc: "one middleware by id, that does not exist", path: "/api/tcp/middlewares/foo@myprovider", diff --git a/pkg/api/handler_udp.go b/pkg/api/handler_udp.go index bf637ee72..60f4b7178 100644 --- a/pkg/api/handler_udp.go +++ b/pkg/api/handler_udp.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "strconv" "strings" @@ -74,7 +75,13 @@ func (h Handler) getUDPRouters(rw http.ResponseWriter, request *http.Request) { } func (h Handler) getUDPRouter(rw http.ResponseWriter, request *http.Request) { - routerID := mux.Vars(request)["routerID"] + scapedRouterID := mux.Vars(request)["routerID"] + + routerID, err := url.PathUnescape(scapedRouterID) + if err != nil { + writeError(rw, fmt.Sprintf("unable to decode routerID %q: %s", scapedRouterID, err), http.StatusBadRequest) + return + } rw.Header().Set("Content-Type", "application/json") @@ -86,7 +93,7 @@ func (h Handler) getUDPRouter(rw http.ResponseWriter, request *http.Request) { result := newUDPRouterRepresentation(routerID, router) - err := json.NewEncoder(rw).Encode(result) + err = json.NewEncoder(rw).Encode(result) if err != nil { log.Ctx(request.Context()).Error().Err(err).Send() writeError(rw, err.Error(), http.StatusInternalServerError) @@ -125,7 +132,13 @@ func (h Handler) getUDPServices(rw http.ResponseWriter, request *http.Request) { } func (h Handler) getUDPService(rw http.ResponseWriter, request *http.Request) { - serviceID := mux.Vars(request)["serviceID"] + scapedServiceID := mux.Vars(request)["serviceID"] + + serviceID, err := url.PathUnescape(scapedServiceID) + if err != nil { + writeError(rw, fmt.Sprintf("unable to decode serviceID %q: %s", scapedServiceID, err), http.StatusBadRequest) + return + } rw.Header().Set("Content-Type", "application/json") @@ -137,7 +150,7 @@ func (h Handler) getUDPService(rw http.ResponseWriter, request *http.Request) { result := newUDPServiceRepresentation(serviceID, service) - err := json.NewEncoder(rw).Encode(result) + err = json.NewEncoder(rw).Encode(result) if err != nil { log.Ctx(request.Context()).Error().Err(err).Send() writeError(rw, err.Error(), http.StatusInternalServerError) diff --git a/pkg/api/handler_udp_test.go b/pkg/api/handler_udp_test.go index f9e2cb356..741239940 100644 --- a/pkg/api/handler_udp_test.go +++ b/pkg/api/handler_udp_test.go @@ -6,6 +6,7 @@ import ( "io" "net/http" "net/http/httptest" + "net/url" "os" "testing" @@ -224,6 +225,24 @@ func TestHandler_UDP(t *testing.T) { jsonFile: "testdata/udprouter-bar.json", }, }, + { + desc: "one UDP router by id containing slash", + path: "/api/udp/routers/" + url.PathEscape("foo / bar@myprovider"), + conf: runtime.Configuration{ + UDPRouters: map[string]*runtime.UDPRouterInfo{ + "foo / bar@myprovider": { + UDPRouter: &dynamic.UDPRouter{ + EntryPoints: []string{"web"}, + Service: "foo-service@myprovider", + }, + }, + }, + }, + expected: expected{ + statusCode: http.StatusOK, + jsonFile: "testdata/udprouter-foo-slash-bar.json", + }, + }, { desc: "one UDP router by id, that does not exist", path: "/api/udp/routers/foo@myprovider", @@ -487,6 +506,30 @@ func TestHandler_UDP(t *testing.T) { jsonFile: "testdata/udpservice-bar.json", }, }, + { + desc: "one udp service by id containing slash", + path: "/api/udp/services/" + url.PathEscape("foo / bar@myprovider"), + conf: runtime.Configuration{ + UDPServices: map[string]*runtime.UDPServiceInfo{ + "foo / bar@myprovider": { + UDPService: &dynamic.UDPService{ + LoadBalancer: &dynamic.UDPServersLoadBalancer{ + Servers: []dynamic.UDPServer{ + { + Address: "127.0.0.1:2345", + }, + }, + }, + }, + UsedBy: []string{"foo@myprovider", "test@myprovider"}, + }, + }, + }, + expected: expected{ + statusCode: http.StatusOK, + jsonFile: "testdata/udpservice-foo-slash-bar.json", + }, + }, { desc: "one udp service by id, that does not exist", path: "/api/udp/services/nono@myprovider", diff --git a/pkg/api/testdata/entrypoint-foo-slash-bar.json b/pkg/api/testdata/entrypoint-foo-slash-bar.json new file mode 100644 index 000000000..5f0bcbafc --- /dev/null +++ b/pkg/api/testdata/entrypoint-foo-slash-bar.json @@ -0,0 +1,5 @@ +{ + "address": ":81", + "http": {}, + "name": "foo / bar" +} diff --git a/pkg/api/testdata/middleware-foo-slash-bar.json b/pkg/api/testdata/middleware-foo-slash-bar.json new file mode 100644 index 000000000..5e68c7e3d --- /dev/null +++ b/pkg/api/testdata/middleware-foo-slash-bar.json @@ -0,0 +1,12 @@ +{ + "addPrefix": { + "prefix": "/titi" + }, + "name": "foo / bar@myprovider", + "provider": "myprovider", + "status": "enabled", + "type": "addprefix", + "usedBy": [ + "test@myprovider" + ] +} diff --git a/pkg/api/testdata/router-foo-slash-bar.json b/pkg/api/testdata/router-foo-slash-bar.json new file mode 100644 index 000000000..f9e30c240 --- /dev/null +++ b/pkg/api/testdata/router-foo-slash-bar.json @@ -0,0 +1,17 @@ +{ + "entryPoints": [ + "web" + ], + "middlewares": [ + "auth", + "addPrefixTest@anotherprovider" + ], + "name": "foo / bar@myprovider", + "provider": "myprovider", + "rule": "Host(`foo.bar`)", + "service": "foo-service@myprovider", + "status": "enabled", + "using": [ + "web" + ] +} diff --git a/pkg/api/testdata/service-foo-slash-bar.json b/pkg/api/testdata/service-foo-slash-bar.json new file mode 100644 index 000000000..58cde8530 --- /dev/null +++ b/pkg/api/testdata/service-foo-slash-bar.json @@ -0,0 +1,21 @@ +{ + "loadBalancer": { + "passHostHeader": true, + "servers": [ + { + "url": "http://127.0.0.1" + } + ] + }, + "name": "foo / bar@myprovider", + "provider": "myprovider", + "serverStatus": { + "http://127.0.0.1": "UP" + }, + "status": "enabled", + "type": "loadbalancer", + "usedBy": [ + "foo@myprovider", + "test@myprovider" + ] +} diff --git a/pkg/api/testdata/tcpmiddleware-foo-slash-bar.json b/pkg/api/testdata/tcpmiddleware-foo-slash-bar.json new file mode 100644 index 000000000..985e0380a --- /dev/null +++ b/pkg/api/testdata/tcpmiddleware-foo-slash-bar.json @@ -0,0 +1,13 @@ +{ + "ipWhiteList": { + "sourceRange": ["127.0.0.1/32"] + }, + "name": "foo / bar@myprovider", + "provider": "myprovider", + "status": "enabled", + "type": "ipwhitelist", + "usedBy": [ + "bar@myprovider", + "test@myprovider" + ] +} diff --git a/pkg/api/testdata/tcprouter-foo-slash-bar.json b/pkg/api/testdata/tcprouter-foo-slash-bar.json new file mode 100644 index 000000000..4656ea9f9 --- /dev/null +++ b/pkg/api/testdata/tcprouter-foo-slash-bar.json @@ -0,0 +1,13 @@ +{ + "entryPoints": [ + "web" + ], + "name": "foo / bar@myprovider", + "provider": "myprovider", + "rule": "Host(`foo.bar`)", + "service": "foo-service@myprovider", + "status": "enabled", + "using": [ + "web" + ] +} diff --git a/pkg/api/testdata/tcpservice-foo-slash-bar.json b/pkg/api/testdata/tcpservice-foo-slash-bar.json new file mode 100644 index 000000000..b250966d5 --- /dev/null +++ b/pkg/api/testdata/tcpservice-foo-slash-bar.json @@ -0,0 +1,17 @@ +{ + "loadBalancer": { + "servers": [ + { + "address": "127.0.0.1:2345" + } + ] + }, + "name": "foo / bar@myprovider", + "provider": "myprovider", + "status": "enabled", + "type": "loadbalancer", + "usedBy": [ + "foo@myprovider", + "test@myprovider" + ] +} diff --git a/pkg/api/testdata/udprouter-foo-slash-bar.json b/pkg/api/testdata/udprouter-foo-slash-bar.json new file mode 100644 index 000000000..276c1a907 --- /dev/null +++ b/pkg/api/testdata/udprouter-foo-slash-bar.json @@ -0,0 +1,12 @@ +{ + "entryPoints": [ + "web" + ], + "name": "foo / bar@myprovider", + "provider": "myprovider", + "service": "foo-service@myprovider", + "status": "enabled", + "using": [ + "web" + ] +} diff --git a/pkg/api/testdata/udpservice-foo-slash-bar.json b/pkg/api/testdata/udpservice-foo-slash-bar.json new file mode 100644 index 000000000..b250966d5 --- /dev/null +++ b/pkg/api/testdata/udpservice-foo-slash-bar.json @@ -0,0 +1,17 @@ +{ + "loadBalancer": { + "servers": [ + { + "address": "127.0.0.1:2345" + } + ] + }, + "name": "foo / bar@myprovider", + "provider": "myprovider", + "status": "enabled", + "type": "loadbalancer", + "usedBy": [ + "foo@myprovider", + "test@myprovider" + ] +} diff --git a/pkg/cli/deprecation.go b/pkg/cli/deprecation.go new file mode 100644 index 000000000..379d986a7 --- /dev/null +++ b/pkg/cli/deprecation.go @@ -0,0 +1,541 @@ +package cli + +import ( + "errors" + "os" + "reflect" + "strings" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/traefik/paerser/cli" + "github.com/traefik/paerser/flag" + "github.com/traefik/paerser/parser" +) + +type DeprecationLoader struct{} + +func (d DeprecationLoader) Load(args []string, cmd *cli.Command) (bool, error) { + if logDeprecation(cmd.Configuration, args) { + return true, errors.New("incompatible deprecated static option found") + } + + return false, nil +} + +// logDeprecation prints deprecation hints and returns whether incompatible deprecated options need to be removed. +func logDeprecation(traefikConfiguration interface{}, args []string) bool { + // This part doesn't handle properly a flag defined like this: + // --accesslog true + // where `true` could be considered as a new argument. + // This is not really an issue with the deprecation loader since it will filter the unknown nodes later in this + // function. + for i, arg := range args { + if !strings.Contains(arg, "=") { + args[i] = arg + "=true" + } + } + + labels, err := flag.Parse(args, nil) + if err != nil { + log.Error().Err(err).Msg("deprecated static options analysis failed") + return false + } + + node, err := parser.DecodeToNode(labels, "traefik") + if err != nil { + log.Error().Err(err).Msg("deprecated static options analysis failed") + return false + } + + if node != nil && len(node.Children) > 0 { + config := &configuration{} + filterUnknownNodes(reflect.TypeOf(config), node) + + if len(node.Children) > 0 { + // Telling parser to look for the label struct tag to allow empty values. + err = parser.AddMetadata(config, node, parser.MetadataOpts{TagName: "label"}) + if err != nil { + log.Error().Err(err).Msg("deprecated static options analysis failed") + return false + } + + err = parser.Fill(config, node, parser.FillerOpts{}) + if err != nil { + log.Error().Err(err).Msg("deprecated static options analysis failed") + return false + } + + if config.deprecationNotice(log.With().Str("loader", "FLAG").Logger()) { + return true + } + + // No further deprecation parsing and logging, + // as args configuration contains at least one deprecated option. + return false + } + } + + // FILE + ref, err := flag.Parse(args, traefikConfiguration) + if err != nil { + log.Error().Err(err).Msg("deprecated static options analysis failed") + return false + } + + configFileFlag := "traefik.configfile" + if _, ok := ref["traefik.configFile"]; ok { + configFileFlag = "traefik.configFile" + } + + config := &configuration{} + _, err = loadConfigFiles(ref[configFileFlag], config) + + if err == nil { + if config.deprecationNotice(log.With().Str("loader", "FILE").Logger()) { + return true + } + } + + config = &configuration{} + l := EnvLoader{} + _, err = l.Load(os.Args, &cli.Command{ + Configuration: config, + }) + + if err == nil { + if config.deprecationNotice(log.With().Str("loader", "ENV").Logger()) { + return true + } + } + + return false +} + +func filterUnknownNodes(fType reflect.Type, node *parser.Node) bool { + var children []*parser.Node + for _, child := range node.Children { + if hasKnownNodes(fType, child) { + children = append(children, child) + } + } + + node.Children = children + return len(node.Children) > 0 +} + +func hasKnownNodes(rootType reflect.Type, node *parser.Node) bool { + rType := rootType + if rootType.Kind() == reflect.Pointer { + rType = rootType.Elem() + } + + // unstructured type fitting anything, considering the current node as known. + if rType.Kind() == reflect.Map && rType.Elem().Kind() == reflect.Interface { + return true + } + + // unstructured type fitting anything, considering the current node as known. + if rType.Kind() == reflect.Interface { + return true + } + + // find matching field in struct type. + field, b := findTypedField(rType, node) + if !b { + return b + } + + if len(node.Children) > 0 { + return filterUnknownNodes(field.Type, node) + } + + return true +} + +func findTypedField(rType reflect.Type, node *parser.Node) (reflect.StructField, bool) { + // avoid panicking. + if rType.Kind() != reflect.Struct { + return reflect.StructField{}, false + } + + for i := 0; i < rType.NumField(); i++ { + cField := rType.Field(i) + + // ignore unexported fields. + if cField.PkgPath == "" { + if strings.EqualFold(cField.Name, node.Name) { + node.FieldName = cField.Name + return cField, true + } + } + } + + return reflect.StructField{}, false +} + +// configuration holds the static configuration removed/deprecated options. +type configuration struct { + Experimental *experimental `json:"experimental,omitempty" toml:"experimental,omitempty" yaml:"experimental,omitempty" label:"allowEmpty" file:"allowEmpty"` + Pilot map[string]any `json:"pilot,omitempty" toml:"pilot,omitempty" yaml:"pilot,omitempty" label:"allowEmpty" file:"allowEmpty"` + Providers *providers `json:"providers,omitempty" toml:"providers,omitempty" yaml:"providers,omitempty" label:"allowEmpty" file:"allowEmpty"` + Tracing *tracing `json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" label:"allowEmpty" file:"allowEmpty"` +} + +func (c *configuration) deprecationNotice(logger zerolog.Logger) bool { + if c == nil { + return false + } + + var incompatible bool + if c.Pilot != nil { + incompatible = true + logger.Error().Msg("Pilot configuration has been removed in v3, please remove all Pilot-related static configuration for Traefik to start." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#pilot") + } + + incompatibleExperimental := c.Experimental.deprecationNotice(logger) + incompatibleProviders := c.Providers.deprecationNotice(logger) + incompatibleTracing := c.Tracing.deprecationNotice(logger) + return incompatible || incompatibleExperimental || incompatibleProviders || incompatibleTracing +} + +type providers struct { + Docker *docker `json:"docker,omitempty" toml:"docker,omitempty" yaml:"docker,omitempty" label:"allowEmpty" file:"allowEmpty"` + Swarm *swarm `json:"swarm,omitempty" toml:"swarm,omitempty" yaml:"swarm,omitempty" label:"allowEmpty" file:"allowEmpty"` + Consul *consul `json:"consul,omitempty" toml:"consul,omitempty" yaml:"consul,omitempty" label:"allowEmpty" file:"allowEmpty"` + ConsulCatalog *consulCatalog `json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty" label:"allowEmpty" file:"allowEmpty"` + Nomad *nomad `json:"nomad,omitempty" toml:"nomad,omitempty" yaml:"nomad,omitempty" label:"allowEmpty" file:"allowEmpty"` + Marathon map[string]any `json:"marathon,omitempty" toml:"marathon,omitempty" yaml:"marathon,omitempty" label:"allowEmpty" file:"allowEmpty"` + Rancher map[string]any `json:"rancher,omitempty" toml:"rancher,omitempty" yaml:"rancher,omitempty" label:"allowEmpty" file:"allowEmpty"` + ETCD *etcd `json:"etcd,omitempty" toml:"etcd,omitempty" yaml:"etcd,omitempty" label:"allowEmpty" file:"allowEmpty"` + Redis *redis `json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" label:"allowEmpty" file:"allowEmpty"` + HTTP *http `json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty"` +} + +func (p *providers) deprecationNotice(logger zerolog.Logger) bool { + if p == nil { + return false + } + + var incompatible bool + + if p.Marathon != nil { + incompatible = true + logger.Error().Msg("Marathon provider has been removed in v3, please remove all Marathon-related static configuration for Traefik to start." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#marathon-provider") + } + + if p.Rancher != nil { + incompatible = true + logger.Error().Msg("Rancher provider has been removed in v3, please remove all Rancher-related static configuration for Traefik to start." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#rancher-v1-provider") + } + + dockerIncompatible := p.Docker.deprecationNotice(logger) + consulIncompatible := p.Consul.deprecationNotice(logger) + consulCatalogIncompatible := p.ConsulCatalog.deprecationNotice(logger) + nomadIncompatible := p.Nomad.deprecationNotice(logger) + swarmIncompatible := p.Swarm.deprecationNotice(logger) + etcdIncompatible := p.ETCD.deprecationNotice(logger) + redisIncompatible := p.Redis.deprecationNotice(logger) + httpIncompatible := p.HTTP.deprecationNotice(logger) + return incompatible || + dockerIncompatible || + consulIncompatible || + consulCatalogIncompatible || + nomadIncompatible || + swarmIncompatible || + etcdIncompatible || + redisIncompatible || + httpIncompatible +} + +type tls struct { + CAOptional *bool `json:"caOptional,omitempty" toml:"caOptional,omitempty" yaml:"caOptional,omitempty"` +} + +type docker struct { + SwarmMode *bool `json:"swarmMode,omitempty" toml:"swarmMode,omitempty" yaml:"swarmMode,omitempty"` + TLS *tls `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty"` +} + +func (d *docker) deprecationNotice(logger zerolog.Logger) bool { + if d == nil { + return false + } + + var incompatible bool + + if d.SwarmMode != nil { + incompatible = true + logger.Error().Msg("Docker provider `swarmMode` option has been removed in v3, please use the Swarm Provider instead." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#docker-docker-swarm") + } + + if d.TLS != nil && d.TLS.CAOptional != nil { + incompatible = true + logger.Error().Msg("Docker provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + + "Please remove all occurrences from the static configuration for Traefik to start." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tlscaoptional") + } + + return incompatible +} + +type swarm struct { + TLS *tls `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty"` +} + +func (s *swarm) deprecationNotice(logger zerolog.Logger) bool { + if s == nil { + return false + } + + var incompatible bool + + if s.TLS != nil && s.TLS.CAOptional != nil { + incompatible = true + logger.Error().Msg("Swarm provider `tls.CAOptional` option does not exist, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + + "Please remove all occurrences from the static configuration for Traefik to start.") + } + + return incompatible +} + +type etcd struct { + TLS *tls `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty"` +} + +func (e *etcd) deprecationNotice(logger zerolog.Logger) bool { + if e == nil { + return false + } + + var incompatible bool + + if e.TLS != nil && e.TLS.CAOptional != nil { + incompatible = true + logger.Error().Msg("ETCD provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + + "Please remove all occurrences from the static configuration for Traefik to start." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tlscaoptional_3") + } + + return incompatible +} + +type redis struct { + TLS *tls `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty"` +} + +func (r *redis) deprecationNotice(logger zerolog.Logger) bool { + if r == nil { + return false + } + + var incompatible bool + + if r.TLS != nil && r.TLS.CAOptional != nil { + incompatible = true + logger.Error().Msg("Redis provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + + "Please remove all occurrences from the static configuration for Traefik to start." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tlscaoptional_4") + } + + return incompatible +} + +type consul struct { + Namespace *string `json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty"` + TLS *tls `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty"` +} + +func (c *consul) deprecationNotice(logger zerolog.Logger) bool { + if c == nil { + return false + } + + var incompatible bool + + if c.Namespace != nil { + incompatible = true + logger.Error().Msg("Consul provider `namespace` option has been removed, please use the `namespaces` option instead." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#consul-provider") + } + + if c.TLS != nil && c.TLS.CAOptional != nil { + incompatible = true + logger.Error().Msg("Consul provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + + "Please remove all occurrences from the static configuration for Traefik to start." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tlscaoptional_1") + } + + return incompatible +} + +type consulCatalog struct { + Namespace *string `json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty"` + Endpoint *endpointConfig `json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty" label:"allowEmpty" file:"allowEmpty"` +} + +type endpointConfig struct { + TLS *tls `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty"` +} + +func (c *consulCatalog) deprecationNotice(logger zerolog.Logger) bool { + if c == nil { + return false + } + + var incompatible bool + + if c.Namespace != nil { + incompatible = true + logger.Error().Msg("ConsulCatalog provider `namespace` option has been removed, please use the `namespaces` option instead." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#consulcatalog-provider") + } + + if c.Endpoint != nil && c.Endpoint.TLS != nil && c.Endpoint.TLS.CAOptional != nil { + incompatible = true + logger.Error().Msg("ConsulCatalog provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + + "Please remove all occurrences from the static configuration for Traefik to start." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#endpointtlscaoptional") + } + + return incompatible +} + +type nomad struct { + Namespace *string `json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty"` + Endpoint *endpointConfig `json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty" label:"allowEmpty" file:"allowEmpty"` +} + +func (n *nomad) deprecationNotice(logger zerolog.Logger) bool { + if n == nil { + return false + } + + var incompatible bool + + if n.Namespace != nil { + incompatible = true + logger.Error().Msg("Nomad provider `namespace` option has been removed, please use the `namespaces` option instead." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#nomad-provider") + } + + if n.Endpoint != nil && n.Endpoint.TLS != nil && n.Endpoint.TLS.CAOptional != nil { + incompatible = true + logger.Error().Msg("Nomad provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + + "Please remove all occurrences from the static configuration for Traefik to start." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#endpointtlscaoptional_1") + } + + return incompatible +} + +type http struct { + TLS *tls `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty"` +} + +func (h *http) deprecationNotice(logger zerolog.Logger) bool { + if h == nil { + return false + } + + var incompatible bool + + if h.TLS != nil && h.TLS.CAOptional != nil { + incompatible = true + logger.Error().Msg("HTTP provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + + "Please remove all occurrences from the static configuration for Traefik to start." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tlscaoptional_2") + } + + return incompatible +} + +type experimental struct { + HTTP3 *bool `json:"http3,omitempty" toml:"http3,omitempty" yaml:"http3,omitempty"` +} + +func (e *experimental) deprecationNotice(logger zerolog.Logger) bool { + if e == nil { + return false + } + + if e.HTTP3 != nil { + logger.Error().Msg("HTTP3 is not an experimental feature in v3 and the associated enablement has been removed." + + "Please remove its usage from the static configuration for Traefik to start." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#http3-experimental-configuration") + + return true + } + + return false +} + +type tracing struct { + SpanNameLimit *int `json:"spanNameLimit,omitempty" toml:"spanNameLimit,omitempty" yaml:"spanNameLimit,omitempty"` + Jaeger map[string]any `json:"jaeger,omitempty" toml:"jaeger,omitempty" yaml:"jaeger,omitempty" label:"allowEmpty" file:"allowEmpty"` + Zipkin map[string]any `json:"zipkin,omitempty" toml:"zipkin,omitempty" yaml:"zipkin,omitempty" label:"allowEmpty" file:"allowEmpty"` + Datadog map[string]any `json:"datadog,omitempty" toml:"datadog,omitempty" yaml:"datadog,omitempty" label:"allowEmpty" file:"allowEmpty"` + Instana map[string]any `json:"instana,omitempty" toml:"instana,omitempty" yaml:"instana,omitempty" label:"allowEmpty" file:"allowEmpty"` + Haystack map[string]any `json:"haystack,omitempty" toml:"haystack,omitempty" yaml:"haystack,omitempty" label:"allowEmpty" file:"allowEmpty"` + Elastic map[string]any `json:"elastic,omitempty" toml:"elastic,omitempty" yaml:"elastic,omitempty" label:"allowEmpty" file:"allowEmpty"` +} + +func (t *tracing) deprecationNotice(logger zerolog.Logger) bool { + if t == nil { + return false + } + var incompatible bool + if t.SpanNameLimit != nil { + incompatible = true + logger.Error().Msg("SpanNameLimit option for Tracing has been removed in v3, as Span names are now of a fixed length." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tracing") + } + + if t.Jaeger != nil { + incompatible = true + logger.Error().Msg("Jaeger Tracing backend has been removed in v3, please remove all Jaeger-related Tracing static configuration for Traefik to start." + + "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tracing") + } + + if t.Zipkin != nil { + incompatible = true + logger.Error().Msg("Zipkin Tracing backend has been removed in v3, please remove all Zipkin-related Tracing static configuration for Traefik to start." + + "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tracing") + } + + if t.Datadog != nil { + incompatible = true + logger.Error().Msg("Datadog Tracing backend has been removed in v3, please remove all Datadog-related Tracing static configuration for Traefik to start." + + "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tracing") + } + + if t.Instana != nil { + incompatible = true + logger.Error().Msg("Instana Tracing backend has been removed in v3, please remove all Instana-related Tracing static configuration for Traefik to start." + + "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tracing") + } + + if t.Haystack != nil { + incompatible = true + logger.Error().Msg("Haystack Tracing backend has been removed in v3, please remove all Haystack-related Tracing static configuration for Traefik to start." + + "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tracing") + } + + if t.Elastic != nil { + incompatible = true + logger.Error().Msg("Elastic Tracing backend has been removed in v3, please remove all Elastic-related Tracing static configuration for Traefik to start." + + "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tracing") + } + + return incompatible +} diff --git a/pkg/cli/deprecation_test.go b/pkg/cli/deprecation_test.go new file mode 100644 index 000000000..c225c0e7b --- /dev/null +++ b/pkg/cli/deprecation_test.go @@ -0,0 +1,404 @@ +package cli + +import ( + "testing" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/traefik/paerser/cli" + "github.com/traefik/traefik/v3/cmd" +) + +func ptr[T any](t T) *T { + return &t +} + +func TestDeprecationNotice(t *testing.T) { + tests := []struct { + desc string + config configuration + }{ + { + desc: "Docker provider swarmMode option is incompatible", + config: configuration{ + Providers: &providers{ + Docker: &docker{ + SwarmMode: ptr(true), + }, + }, + }, + }, + { + desc: "Docker provider tls.CAOptional option is incompatible", + config: configuration{ + Providers: &providers{ + Docker: &docker{ + TLS: &tls{ + CAOptional: ptr(true), + }, + }, + }, + }, + }, + { + desc: "Swarm provider tls.CAOptional option is incompatible", + config: configuration{ + Providers: &providers{ + Swarm: &swarm{ + TLS: &tls{ + CAOptional: ptr(true), + }, + }, + }, + }, + }, + { + desc: "Consul provider namespace option is incompatible", + config: configuration{ + Providers: &providers{ + Consul: &consul{ + Namespace: ptr("foobar"), + }, + }, + }, + }, + { + desc: "Consul provider tls.CAOptional option is incompatible", + config: configuration{ + Providers: &providers{ + Consul: &consul{ + TLS: &tls{ + CAOptional: ptr(true), + }, + }, + }, + }, + }, + { + desc: "ConsulCatalog provider namespace option is incompatible", + config: configuration{ + Providers: &providers{ + ConsulCatalog: &consulCatalog{ + Namespace: ptr("foobar"), + }, + }, + }, + }, + { + desc: "ConsulCatalog provider tls.CAOptional option is incompatible", + config: configuration{ + Providers: &providers{ + ConsulCatalog: &consulCatalog{ + Endpoint: &endpointConfig{ + TLS: &tls{ + CAOptional: ptr(true), + }, + }, + }, + }, + }, + }, + { + desc: "Nomad provider namespace option is incompatible", + config: configuration{ + Providers: &providers{ + Nomad: &nomad{ + Namespace: ptr("foobar"), + }, + }, + }, + }, + { + desc: "Nomad provider tls.CAOptional option is incompatible", + config: configuration{ + Providers: &providers{ + Nomad: &nomad{ + Endpoint: &endpointConfig{ + TLS: &tls{ + CAOptional: ptr(true), + }, + }, + }, + }, + }, + }, + { + desc: "Marathon configuration is incompatible", + config: configuration{ + Providers: &providers{ + Marathon: map[string]any{ + "foo": "bar", + }, + }, + }, + }, + { + desc: "Rancher configuration is incompatible", + config: configuration{ + Providers: &providers{ + Rancher: map[string]any{ + "foo": "bar", + }, + }, + }, + }, + { + desc: "ETCD provider tls.CAOptional option is incompatible", + config: configuration{ + Providers: &providers{ + ETCD: &etcd{ + TLS: &tls{ + CAOptional: ptr(true), + }, + }, + }, + }, + }, + { + desc: "Redis provider tls.CAOptional option is incompatible", + config: configuration{ + Providers: &providers{ + Redis: &redis{ + TLS: &tls{ + CAOptional: ptr(true), + }, + }, + }, + }, + }, + { + desc: "HTTP provider tls.CAOptional option is incompatible", + config: configuration{ + Providers: &providers{ + HTTP: &http{ + TLS: &tls{ + CAOptional: ptr(true), + }, + }, + }, + }, + }, + { + desc: "Pilot configuration is incompatible", + config: configuration{ + Pilot: map[string]any{ + "foo": "bar", + }, + }, + }, + { + desc: "Experimental HTTP3 enablement configuration is incompatible", + config: configuration{ + Experimental: &experimental{ + HTTP3: ptr(true), + }, + }, + }, + { + desc: "Tracing SpanNameLimit option is incompatible", + config: configuration{ + Tracing: &tracing{ + SpanNameLimit: ptr(42), + }, + }, + }, + { + desc: "Tracing Jaeger configuration is incompatible", + config: configuration{ + Tracing: &tracing{ + Jaeger: map[string]any{ + "foo": "bar", + }, + }, + }, + }, + { + desc: "Tracing Zipkin configuration is incompatible", + config: configuration{ + Tracing: &tracing{ + Zipkin: map[string]any{ + "foo": "bar", + }, + }, + }, + }, + { + desc: "Tracing Datadog configuration is incompatible", + config: configuration{ + Tracing: &tracing{ + Datadog: map[string]any{ + "foo": "bar", + }, + }, + }, + }, + { + desc: "Tracing Instana configuration is incompatible", + config: configuration{ + Tracing: &tracing{ + Instana: map[string]any{ + "foo": "bar", + }, + }, + }, + }, + { + desc: "Tracing Haystack configuration is incompatible", + config: configuration{ + Tracing: &tracing{ + Haystack: map[string]any{ + "foo": "bar", + }, + }, + }, + }, + { + desc: "Tracing Elastic configuration is incompatible", + config: configuration{ + Tracing: &tracing{ + Elastic: map[string]any{ + "foo": "bar", + }, + }, + }, + }, + } + + for _, test := range tests { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + var gotLog bool + var gotLevel zerolog.Level + testHook := zerolog.HookFunc(func(e *zerolog.Event, level zerolog.Level, message string) { + gotLog = true + gotLevel = level + }) + + logger := log.With().Logger().Hook(testHook) + assert.True(t, test.config.deprecationNotice(logger)) + assert.True(t, gotLog) + assert.Equal(t, zerolog.ErrorLevel, gotLevel) + }) + } +} + +func TestLoad(t *testing.T) { + testCases := []struct { + desc string + args []string + env map[string]string + wantDeprecated bool + }{ + { + desc: "Empty", + args: []string{}, + wantDeprecated: false, + }, + { + desc: "[FLAG] providers.marathon is deprecated", + args: []string{ + "--access-log", + "--log.level=DEBUG", + "--entrypoints.test.http.tls", + "--providers.nomad.endpoint.tls.insecureskipverify=true", + "--providers.marathon", + }, + wantDeprecated: true, + }, + { + desc: "[FLAG] multiple deprecated", + args: []string{ + "--access-log", + "--log.level=DEBUG", + "--entrypoints.test.http.tls", + "--providers.marathon", + "--pilot.token=XXX", + }, + wantDeprecated: true, + }, + { + desc: "[FLAG] no deprecated", + args: []string{ + "--access-log", + "--log.level=DEBUG", + "--entrypoints.test.http.tls", + "--providers.docker", + }, + wantDeprecated: false, + }, + { + desc: "[ENV] providers.marathon is deprecated", + env: map[string]string{ + "TRAEFIK_ACCESS_LOG": "", + "TRAEFIK_LOG_LEVEL": "DEBUG", + "TRAEFIK_ENTRYPOINT_TEST_HTTP_TLS": "true", + "TRAEFIK_PROVIDERS_MARATHON": "true", + }, + wantDeprecated: true, + }, + { + desc: "[ENV] multiple deprecated", + env: map[string]string{ + "TRAEFIK_ACCESS_LOG": "true", + "TRAEFIK_LOG_LEVEL": "DEBUG", + "TRAEFIK_ENTRYPOINT_TEST_HTTP_TLS": "true", + "TRAEFIK_PROVIDERS_MARATHON": "true", + "TRAEFIK_PILOT_TOKEN": "xxx", + }, + wantDeprecated: true, + }, + { + desc: "[ENV] no deprecated", + env: map[string]string{ + "TRAEFIK_ACCESS_LOG": "true", + "TRAEFIK_LOG_LEVEL": "DEBUG", + "TRAEFIK_ENTRYPOINT_TEST_HTTP_TLS": "true", + }, + + wantDeprecated: false, + }, + { + desc: "[FILE] providers.marathon is deprecated", + args: []string{ + "--configfile=./fixtures/traefik_deprecated.toml", + }, + wantDeprecated: true, + }, + { + desc: "[FILE] multiple deprecated", + args: []string{ + "--configfile=./fixtures/traefik_multiple_deprecated.toml", + }, + wantDeprecated: true, + }, + { + desc: "[FILE] no deprecated", + args: []string{ + "--configfile=./fixtures/traefik_no_deprecated.toml", + }, + wantDeprecated: false, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + tconfig := cmd.NewTraefikConfiguration() + c := &cli.Command{Configuration: tconfig} + l := DeprecationLoader{} + + for name, val := range test.env { + t.Setenv(name, val) + } + deprecated, err := l.Load(test.args, c) + assert.Equal(t, test.wantDeprecated, deprecated) + if !test.wantDeprecated { + require.NoError(t, err) + } + }) + } +} diff --git a/pkg/cli/fixtures/traefik_deprecated.toml b/pkg/cli/fixtures/traefik_deprecated.toml new file mode 100644 index 000000000..21fa1d1c1 --- /dev/null +++ b/pkg/cli/fixtures/traefik_deprecated.toml @@ -0,0 +1,5 @@ +[accesslog] + +[entrypoints.test.http.tls] + +[providers.marathon] diff --git a/pkg/cli/fixtures/traefik_multiple_deprecated.toml b/pkg/cli/fixtures/traefik_multiple_deprecated.toml new file mode 100644 index 000000000..0847e9da3 --- /dev/null +++ b/pkg/cli/fixtures/traefik_multiple_deprecated.toml @@ -0,0 +1,8 @@ +[accesslog] + +[entrypoints.test.http.tls] + +[providers.marathon] + +[pilot] + token="xxx" diff --git a/pkg/cli/fixtures/traefik_no_deprecated.toml b/pkg/cli/fixtures/traefik_no_deprecated.toml new file mode 100644 index 000000000..282d2f873 --- /dev/null +++ b/pkg/cli/fixtures/traefik_no_deprecated.toml @@ -0,0 +1,3 @@ +[accesslog] + +[entrypoints.test.http.tls] diff --git a/pkg/collector/collector.go b/pkg/collector/collector.go index e721f8e3d..9b974cc5c 100644 --- a/pkg/collector/collector.go +++ b/pkg/collector/collector.go @@ -20,8 +20,6 @@ import ( const collectorURL = "https://collect.traefik.io/yYaUej3P42cziRVzv6T5w2aYy9po2Mrn" // Collected data. -// -//nolint:musttag // cannot be changed for historical reasons. type data struct { Version string Codename string @@ -67,7 +65,7 @@ func createBody(staticConfiguration *static.Configuration) (*bytes.Buffer, error } buf := new(bytes.Buffer) - err = json.NewEncoder(buf).Encode(data) + err = json.NewEncoder(buf).Encode(data) //nolint:musttag // cannot be changed for historical reasons. if err != nil { return nil, err } diff --git a/pkg/config/dynamic/http_config.go b/pkg/config/dynamic/http_config.go index a447e94eb..541f61b79 100644 --- a/pkg/config/dynamic/http_config.go +++ b/pkg/config/dynamic/http_config.go @@ -37,8 +37,9 @@ type HTTPConfiguration struct { // Model is a set of default router's values. type Model struct { - Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"` - TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` + Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"` + TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` + DefaultRuleSyntax string `json:"-" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"` } // +k8s:deepcopy-gen=true @@ -59,6 +60,7 @@ type Router struct { Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"` Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"` Rule string `json:"rule,omitempty" toml:"rule,omitempty" yaml:"rule,omitempty"` + RuleSyntax string `json:"ruleSyntax,omitempty" toml:"ruleSyntax,omitempty" yaml:"ruleSyntax,omitempty" export:"true"` Priority int `json:"priority,omitempty" toml:"priority,omitempty,omitzero" yaml:"priority,omitempty" export:"true"` TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` DefaultRule bool `json:"-" toml:"-" yaml:"-" label:"-" file:"-"` @@ -225,6 +227,7 @@ func (r *ResponseForwarding) SetDefaults() { // Server holds the server configuration. type Server struct { URL string `json:"url,omitempty" toml:"url,omitempty" yaml:"url,omitempty" label:"-"` + Weight *int `json:"weight,omitempty" toml:"weight,omitempty" yaml:"weight,omitempty" label:"weight"` Scheme string `json:"-" toml:"-" yaml:"-" file:"-"` Port string `json:"-" toml:"-" yaml:"-" file:"-"` } diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go index a2c1794eb..a9be47306 100644 --- a/pkg/config/dynamic/middlewares.go +++ b/pkg/config/dynamic/middlewares.go @@ -1,11 +1,11 @@ package dynamic import ( + "net/http" "time" ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/ip" - "github.com/traefik/traefik/v3/pkg/types" ) // +k8s:deepcopy-gen=true @@ -54,9 +54,13 @@ type GrpcWeb struct { // +k8s:deepcopy-gen=true // ContentType holds the content-type middleware configuration. -// This middleware sets the `Content-Type` header value to the media type detected from the response content, -// when it is not set by the backend. -type ContentType struct{} +// This middleware exists to enable the correct behavior until at least the default one can be changed in a future version. +type ContentType struct { + // AutoDetect specifies whether to let the `Content-Type` header, if it has not been set by the backend, + // be automatically set to a value derived from the contents of the response. + // Deprecated: AutoDetect option is deprecated, Content-Type middleware is only meant to be used to enable the content-type detection, please remove any usage of this option. + AutoDetect *bool `json:"autoDetect,omitempty" toml:"autoDetect,omitempty" yaml:"autoDetect,omitempty" export:"true"` +} // +k8s:deepcopy-gen=true @@ -141,6 +145,8 @@ type CircuitBreaker struct { FallbackDuration ptypes.Duration `json:"fallbackDuration,omitempty" toml:"fallbackDuration,omitempty" yaml:"fallbackDuration,omitempty" export:"true"` // RecoveryDuration is the duration for which the circuit breaker will try to recover (as soon as it is in recovering state). RecoveryDuration ptypes.Duration `json:"recoveryDuration,omitempty" toml:"recoveryDuration,omitempty" yaml:"recoveryDuration,omitempty" export:"true"` + // ResponseCode is the status code that the circuit breaker will return while it is in the open state. + ResponseCode int `json:"responseCode,omitempty" toml:"responseCode,omitempty" yaml:"responseCode,omitempty" export:"true"` } // SetDefaults sets the default values on a RateLimit. @@ -148,6 +154,7 @@ func (c *CircuitBreaker) SetDefaults() { c.CheckPeriod = ptypes.Duration(100 * time.Millisecond) c.FallbackDuration = ptypes.Duration(10 * time.Second) c.RecoveryDuration = ptypes.Duration(10 * time.Second) + c.ResponseCode = http.StatusServiceUnavailable } // +k8s:deepcopy-gen=true @@ -214,7 +221,7 @@ type ForwardAuth struct { // Address defines the authentication server address. Address string `json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"` // TLS defines the configuration used to secure the connection to the authentication server. - TLS *types.ClientTLS `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` + TLS *ClientTLS `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` // TrustForwardHeader defines whether to trust (ie: forward) all X-Forwarded-* headers. TrustForwardHeader bool `json:"trustForwardHeader,omitempty" toml:"trustForwardHeader,omitempty" yaml:"trustForwardHeader,omitempty" export:"true"` // AuthResponseHeaders defines the list of headers to copy from the authentication server response and set on forwarded request, replacing any existing conflicting headers. @@ -231,6 +238,20 @@ type ForwardAuth struct { // +k8s:deepcopy-gen=true +// ClientTLS holds TLS specific configurations as client +// CA, Cert and Key can be either path or file contents. +// TODO: remove this struct when CAOptional option will be removed. +type ClientTLS struct { + CA string `description:"TLS CA" json:"ca,omitempty" toml:"ca,omitempty" yaml:"ca,omitempty"` + Cert string `description:"TLS cert" json:"cert,omitempty" toml:"cert,omitempty" yaml:"cert,omitempty"` + Key string `description:"TLS key" json:"key,omitempty" toml:"key,omitempty" yaml:"key,omitempty" loggable:"false"` + InsecureSkipVerify bool `description:"TLS insecure skip verify" json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" export:"true"` + // Deprecated: TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634). + CAOptional *bool `description:"TLS CA.Optional" json:"caOptional,omitempty" toml:"caOptional,omitempty" yaml:"caOptional,omitempty" export:"true"` +} + +// +k8s:deepcopy-gen=true + // Headers holds the headers middleware configuration. // This middleware manages the requests and responses headers. // More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/headers/#customrequestheaders @@ -299,6 +320,17 @@ type Headers struct { // If you would like your development environment to mimic production with complete Host blocking, SSL redirects, // and STS headers, leave this as false. IsDevelopment bool `json:"isDevelopment,omitempty" toml:"isDevelopment,omitempty" yaml:"isDevelopment,omitempty" export:"true"` + + // Deprecated: FeaturePolicy option is deprecated, please use PermissionsPolicy instead. + FeaturePolicy *string `json:"featurePolicy,omitempty" toml:"featurePolicy,omitempty" yaml:"featurePolicy,omitempty" export:"true"` + // Deprecated: SSLRedirect option is deprecated, please use EntryPoint redirection or RedirectScheme instead. + SSLRedirect *bool `json:"sslRedirect,omitempty" toml:"sslRedirect,omitempty" yaml:"sslRedirect,omitempty" export:"true"` + // Deprecated: SSLTemporaryRedirect option is deprecated, please use EntryPoint redirection or RedirectScheme instead. + SSLTemporaryRedirect *bool `json:"sslTemporaryRedirect,omitempty" toml:"sslTemporaryRedirect,omitempty" yaml:"sslTemporaryRedirect,omitempty" export:"true"` + // Deprecated: SSLHost option is deprecated, please use RedirectRegex instead. + SSLHost *string `json:"sslHost,omitempty" toml:"sslHost,omitempty" yaml:"sslHost,omitempty"` + // Deprecated: SSLForceHost option is deprecated, please use RedirectRegex instead. + SSLForceHost *bool `json:"sslForceHost,omitempty" toml:"sslForceHost,omitempty" yaml:"sslForceHost,omitempty" export:"true"` } // HasCustomHeadersDefined checks to see if any of the custom header elements have been set. @@ -323,6 +355,10 @@ func (h *Headers) HasCorsHeadersDefined() bool { func (h *Headers) HasSecureHeadersDefined() bool { return h != nil && (len(h.AllowedHosts) != 0 || len(h.HostsProxyHeaders) != 0 || + (h.SSLRedirect != nil && *h.SSLRedirect) || + (h.SSLTemporaryRedirect != nil && *h.SSLTemporaryRedirect) || + (h.SSLForceHost != nil && *h.SSLForceHost) || + (h.SSLHost != nil && *h.SSLHost != "") || len(h.SSLProxyHeaders) != 0 || h.STSSeconds != 0 || h.STSIncludeSubdomains || @@ -336,6 +372,7 @@ func (h *Headers) HasSecureHeadersDefined() bool { h.ContentSecurityPolicy != "" || h.PublicKey != "" || h.ReferrerPolicy != "" || + (h.FeaturePolicy != nil && *h.FeaturePolicy != "") || h.PermissionsPolicy != "" || h.IsDevelopment) } @@ -553,6 +590,11 @@ type Retry struct { type StripPrefix struct { // Prefixes defines the prefixes to strip from the request URL. Prefixes []string `json:"prefixes,omitempty" toml:"prefixes,omitempty" yaml:"prefixes,omitempty" export:"true"` + + // Deprecated: ForceSlash option is deprecated, please remove any usage of this option. + // ForceSlash ensures that the resulting stripped path is not the empty string, by replacing it with / when necessary. + // Default: true. + ForceSlash *bool `json:"forceSlash,omitempty" toml:"forceSlash,omitempty" yaml:"forceSlash,omitempty" export:"true"` } // +k8s:deepcopy-gen=true diff --git a/pkg/config/dynamic/tcp_config.go b/pkg/config/dynamic/tcp_config.go index adedd0882..db3484973 100644 --- a/pkg/config/dynamic/tcp_config.go +++ b/pkg/config/dynamic/tcp_config.go @@ -16,11 +16,19 @@ type TCPConfiguration struct { Routers map[string]*TCPRouter `json:"routers,omitempty" toml:"routers,omitempty" yaml:"routers,omitempty" export:"true"` Services map[string]*TCPService `json:"services,omitempty" toml:"services,omitempty" yaml:"services,omitempty" export:"true"` Middlewares map[string]*TCPMiddleware `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"` + Models map[string]*TCPModel `json:"-" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"` ServersTransports map[string]*TCPServersTransport `json:"serversTransports,omitempty" toml:"serversTransports,omitempty" yaml:"serversTransports,omitempty" label:"-" export:"true"` } // +k8s:deepcopy-gen=true +// TCPModel is a set of default router's values. +type TCPModel struct { + DefaultRuleSyntax string `json:"-" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"` +} + +// +k8s:deepcopy-gen=true + // TCPService holds a tcp service configuration (can only be of one type at the same time). type TCPService struct { LoadBalancer *TCPServersLoadBalancer `json:"loadBalancer,omitempty" toml:"loadBalancer,omitempty" yaml:"loadBalancer,omitempty" export:"true"` @@ -56,6 +64,7 @@ type TCPRouter struct { Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"` Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"` Rule string `json:"rule,omitempty" toml:"rule,omitempty" yaml:"rule,omitempty"` + RuleSyntax string `json:"ruleSyntax,omitempty" toml:"ruleSyntax,omitempty" yaml:"ruleSyntax,omitempty" export:"true"` Priority int `json:"priority,omitempty" toml:"priority,omitempty,omitzero" yaml:"priority,omitempty" export:"true"` TLS *RouterTCPTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` } @@ -77,6 +86,14 @@ type TCPServersLoadBalancer struct { ProxyProtocol *ProxyProtocol `json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` Servers []TCPServer `json:"servers,omitempty" toml:"servers,omitempty" yaml:"servers,omitempty" label-slice-as-struct:"server" export:"true"` ServersTransport string `json:"serversTransport,omitempty" toml:"serversTransport,omitempty" yaml:"serversTransport,omitempty" export:"true"` + + // TerminationDelay, corresponds to the deadline that the proxy sets, after one + // of its connected peers indicates it has closed the writing capability of its + // connection, to close the reading capability as well, hence fully terminating the + // connection. It is a duration in milliseconds, defaulting to 100. A negative value + // means an infinite deadline (i.e. the reading capability is never closed). + // Deprecated: use ServersTransport to configure the TerminationDelay instead. + TerminationDelay *int `json:"terminationDelay,omitempty" toml:"terminationDelay,omitempty" yaml:"terminationDelay,omitempty" export:"true"` } // Mergeable tells if the given service is mergeable. diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go index 847e350ba..f04de4275 100644 --- a/pkg/config/dynamic/zz_generated.deepcopy.go +++ b/pkg/config/dynamic/zz_generated.deepcopy.go @@ -124,6 +124,27 @@ func (in *CircuitBreaker) DeepCopy() *CircuitBreaker { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClientTLS) DeepCopyInto(out *ClientTLS) { + *out = *in + if in.CAOptional != nil { + in, out := &in.CAOptional, &out.CAOptional + *out = new(bool) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientTLS. +func (in *ClientTLS) DeepCopy() *ClientTLS { + if in == nil { + return nil + } + out := new(ClientTLS) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Compress) DeepCopyInto(out *Compress) { *out = *in @@ -219,6 +240,11 @@ func (in Configurations) DeepCopy() Configurations { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ContentType) DeepCopyInto(out *ContentType) { *out = *in + if in.AutoDetect != nil { + in, out := &in.AutoDetect, &out.AutoDetect + *out = new(bool) + **out = **in + } return } @@ -316,8 +342,8 @@ func (in *ForwardAuth) DeepCopyInto(out *ForwardAuth) { *out = *in if in.TLS != nil { in, out := &in.TLS, &out.TLS - *out = new(types.ClientTLS) - **out = **in + *out = new(ClientTLS) + (*in).DeepCopyInto(*out) } if in.AuthResponseHeaders != nil { in, out := &in.AuthResponseHeaders, &out.AuthResponseHeaders @@ -534,6 +560,31 @@ func (in *Headers) DeepCopyInto(out *Headers) { (*out)[key] = val } } + if in.FeaturePolicy != nil { + in, out := &in.FeaturePolicy, &out.FeaturePolicy + *out = new(string) + **out = **in + } + if in.SSLRedirect != nil { + in, out := &in.SSLRedirect, &out.SSLRedirect + *out = new(bool) + **out = **in + } + if in.SSLTemporaryRedirect != nil { + in, out := &in.SSLTemporaryRedirect, &out.SSLTemporaryRedirect + *out = new(bool) + **out = **in + } + if in.SSLHost != nil { + in, out := &in.SSLHost, &out.SSLHost + *out = new(string) + **out = **in + } + if in.SSLForceHost != nil { + in, out := &in.SSLForceHost, &out.SSLForceHost + *out = new(bool) + **out = **in + } return } @@ -794,7 +845,7 @@ func (in *Middleware) DeepCopyInto(out *Middleware) { if in.ContentType != nil { in, out := &in.ContentType, &out.ContentType *out = new(ContentType) - **out = **in + (*in).DeepCopyInto(*out) } if in.GrpcWeb != nil { in, out := &in.GrpcWeb, &out.GrpcWeb @@ -1128,6 +1179,11 @@ func (in *RouterTLSConfig) DeepCopy() *RouterTLSConfig { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Server) DeepCopyInto(out *Server) { *out = *in + if in.Weight != nil { + in, out := &in.Weight, &out.Weight + *out = new(int) + **out = **in + } return } @@ -1180,7 +1236,9 @@ func (in *ServersLoadBalancer) DeepCopyInto(out *ServersLoadBalancer) { if in.Servers != nil { in, out := &in.Servers, &out.Servers *out = make([]Server, len(*in)) - copy(*out, *in) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } if in.HealthCheck != nil { in, out := &in.HealthCheck, &out.HealthCheck @@ -1353,6 +1411,11 @@ func (in *StripPrefix) DeepCopyInto(out *StripPrefix) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.ForceSlash != nil { + in, out := &in.ForceSlash, &out.ForceSlash + *out = new(bool) + **out = **in + } return } @@ -1435,6 +1498,21 @@ func (in *TCPConfiguration) DeepCopyInto(out *TCPConfiguration) { (*out)[key] = outVal } } + if in.Models != nil { + in, out := &in.Models, &out.Models + *out = make(map[string]*TCPModel, len(*in)) + for key, val := range *in { + var outVal *TCPModel + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = new(TCPModel) + **out = **in + } + (*out)[key] = outVal + } + } if in.ServersTransports != nil { in, out := &in.ServersTransports, &out.ServersTransports *out = make(map[string]*TCPServersTransport, len(*in)) @@ -1552,6 +1630,22 @@ func (in *TCPMiddleware) DeepCopy() *TCPMiddleware { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TCPModel) DeepCopyInto(out *TCPModel) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPModel. +func (in *TCPModel) DeepCopy() *TCPModel { + if in == nil { + return nil + } + out := new(TCPModel) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TCPRouter) DeepCopyInto(out *TCPRouter) { *out = *in @@ -1612,6 +1706,11 @@ func (in *TCPServersLoadBalancer) DeepCopyInto(out *TCPServersLoadBalancer) { *out = make([]TCPServer, len(*in)) copy(*out, *in) } + if in.TerminationDelay != nil { + in, out := &in.TerminationDelay, &out.TerminationDelay + *out = new(int) + **out = **in + } return } diff --git a/pkg/config/label/label_test.go b/pkg/config/label/label_test.go index 28606cdbc..c6ffb1534 100644 --- a/pkg/config/label/label_test.go +++ b/pkg/config/label/label_test.go @@ -9,9 +9,11 @@ import ( "github.com/stretchr/testify/require" ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/types" ) +func Bool(v bool) *bool { return &v } +func String(v string) *string { return &v } + func TestDecodeConfiguration(t *testing.T) { labels := map[string]string{ "traefik.http.middlewares.Middleware0.addprefix.prefix": "foobar", @@ -30,6 +32,7 @@ func TestDecodeConfiguration(t *testing.T) { "traefik.HTTP.Middlewares.Middleware4.circuitbreaker.checkperiod": "1s", "traefik.HTTP.Middlewares.Middleware4.circuitbreaker.fallbackduration": "1s", "traefik.HTTP.Middlewares.Middleware4.circuitbreaker.recoveryduration": "1s", + "traefik.HTTP.Middlewares.Middleware4.circuitbreaker.responsecode": "403", "traefik.http.middlewares.Middleware5.digestauth.headerfield": "foobar", "traefik.http.middlewares.Middleware5.digestauth.realm": "foobar", "traefik.http.middlewares.Middleware5.digestauth.removeheader": "true", @@ -42,6 +45,7 @@ func TestDecodeConfiguration(t *testing.T) { "traefik.http.middlewares.Middleware7.forwardauth.authresponseheaders": "foobar, fiibar", "traefik.http.middlewares.Middleware7.forwardauth.authrequestheaders": "foobar, fiibar", "traefik.http.middlewares.Middleware7.forwardauth.tls.ca": "foobar", + "traefik.http.middlewares.Middleware7.forwardauth.tls.caoptional": "true", "traefik.http.middlewares.Middleware7.forwardauth.tls.cert": "foobar", "traefik.http.middlewares.Middleware7.forwardauth.tls.insecureskipverify": "true", "traefik.http.middlewares.Middleware7.forwardauth.tls.key": "foobar", @@ -70,9 +74,14 @@ func TestDecodeConfiguration(t *testing.T) { "traefik.http.middlewares.Middleware8.headers.isdevelopment": "true", "traefik.http.middlewares.Middleware8.headers.publickey": "foobar", "traefik.http.middlewares.Middleware8.headers.referrerpolicy": "foobar", + "traefik.http.middlewares.Middleware8.headers.featurepolicy": "foobar", "traefik.http.middlewares.Middleware8.headers.permissionspolicy": "foobar", + "traefik.http.middlewares.Middleware8.headers.sslforcehost": "true", + "traefik.http.middlewares.Middleware8.headers.sslhost": "foobar", "traefik.http.middlewares.Middleware8.headers.sslproxyheaders.name0": "foobar", "traefik.http.middlewares.Middleware8.headers.sslproxyheaders.name1": "foobar", + "traefik.http.middlewares.Middleware8.headers.sslredirect": "true", + "traefik.http.middlewares.Middleware8.headers.ssltemporaryredirect": "true", "traefik.http.middlewares.Middleware8.headers.stsincludesubdomains": "true", "traefik.http.middlewares.Middleware8.headers.stspreload": "true", "traefik.http.middlewares.Middleware8.headers.stsseconds": "42", @@ -123,6 +132,7 @@ func TestDecodeConfiguration(t *testing.T) { "traefik.http.middlewares.Middleware16.retry.attempts": "42", "traefik.http.middlewares.Middleware16.retry.initialinterval": "1s", "traefik.http.middlewares.Middleware17.stripprefix.prefixes": "foobar, fiibar", + "traefik.http.middlewares.Middleware17.stripprefix.forceslash": "true", "traefik.http.middlewares.Middleware18.stripprefixregex.regex": "foobar, fiibar", "traefik.http.middlewares.Middleware19.compress.minresponsebodybytes": "42", "traefik.http.middlewares.Middleware20.plugin.tomato.aaa": "foo1", @@ -193,9 +203,11 @@ func TestDecodeConfiguration(t *testing.T) { "traefik.tcp.routers.Router1.tls.options": "foo", "traefik.tcp.routers.Router1.tls.passthrough": "false", "traefik.tcp.services.Service0.loadbalancer.server.Port": "42", + "traefik.tcp.services.Service0.loadbalancer.TerminationDelay": "42", "traefik.tcp.services.Service0.loadbalancer.proxyProtocol.version": "42", "traefik.tcp.services.Service0.loadbalancer.serversTransport": "foo", "traefik.tcp.services.Service1.loadbalancer.server.Port": "42", + "traefik.tcp.services.Service1.loadbalancer.TerminationDelay": "42", "traefik.tcp.services.Service1.loadbalancer.proxyProtocol": "true", "traefik.tcp.services.Service1.loadbalancer.serversTransport": "foo", @@ -260,6 +272,7 @@ func TestDecodeConfiguration(t *testing.T) { Port: "42", }, }, + TerminationDelay: func(i int) *int { return &i }(42), ProxyProtocol: &dynamic.ProxyProtocol{Version: 42}, ServersTransport: "foo", }, @@ -271,6 +284,7 @@ func TestDecodeConfiguration(t *testing.T) { Port: "42", }, }, + TerminationDelay: func(i int) *int { return &i }(42), ProxyProtocol: &dynamic.ProxyProtocol{Version: 2}, ServersTransport: "foo", }, @@ -458,6 +472,7 @@ func TestDecodeConfiguration(t *testing.T) { "foobar", "fiibar", }, + ForceSlash: Bool(true), }, }, "Middleware18": { @@ -496,6 +511,7 @@ func TestDecodeConfiguration(t *testing.T) { CheckPeriod: ptypes.Duration(time.Second), FallbackDuration: ptypes.Duration(time.Second), RecoveryDuration: ptypes.Duration(time.Second), + ResponseCode: 403, }, }, "Middleware5": { @@ -523,11 +539,12 @@ func TestDecodeConfiguration(t *testing.T) { "Middleware7": { ForwardAuth: &dynamic.ForwardAuth{ Address: "foobar", - TLS: &types.ClientTLS{ + TLS: &dynamic.ClientTLS{ CA: "foobar", Cert: "foobar", Key: "foobar", InsecureSkipVerify: true, + CAOptional: Bool(true), }, TrustForwardHeader: true, AuthResponseHeaders: []string{ @@ -581,10 +598,14 @@ func TestDecodeConfiguration(t *testing.T) { "foobar", "fiibar", }, + SSLRedirect: Bool(true), + SSLTemporaryRedirect: Bool(true), + SSLHost: String("foobar"), SSLProxyHeaders: map[string]string{ "name0": "foobar", "name1": "foobar", }, + SSLForceHost: Bool(true), STSSeconds: 42, STSIncludeSubdomains: true, STSPreload: true, @@ -597,6 +618,7 @@ func TestDecodeConfiguration(t *testing.T) { ContentSecurityPolicy: "foobar", PublicKey: "foobar", ReferrerPolicy: "foobar", + FeaturePolicy: String("foobar"), PermissionsPolicy: "foobar", IsDevelopment: true, }, @@ -756,6 +778,7 @@ func TestEncodeConfiguration(t *testing.T) { }, }, ServersTransport: "foo", + TerminationDelay: func(i int) *int { return &i }(42), }, }, "Service1": { @@ -766,6 +789,7 @@ func TestEncodeConfiguration(t *testing.T) { }, }, ServersTransport: "foo", + TerminationDelay: func(i int) *int { return &i }(42), }, }, }, @@ -950,6 +974,7 @@ func TestEncodeConfiguration(t *testing.T) { "foobar", "fiibar", }, + ForceSlash: Bool(true), }, }, "Middleware18": { @@ -996,6 +1021,7 @@ func TestEncodeConfiguration(t *testing.T) { CheckPeriod: ptypes.Duration(time.Second), FallbackDuration: ptypes.Duration(time.Second), RecoveryDuration: ptypes.Duration(time.Second), + ResponseCode: 404, }, }, "Middleware5": { @@ -1023,11 +1049,12 @@ func TestEncodeConfiguration(t *testing.T) { "Middleware7": { ForwardAuth: &dynamic.ForwardAuth{ Address: "foobar", - TLS: &types.ClientTLS{ + TLS: &dynamic.ClientTLS{ CA: "foobar", Cert: "foobar", Key: "foobar", InsecureSkipVerify: true, + CAOptional: Bool(true), }, TrustForwardHeader: true, AuthResponseHeaders: []string{ @@ -1081,10 +1108,14 @@ func TestEncodeConfiguration(t *testing.T) { "foobar", "fiibar", }, + SSLRedirect: Bool(true), + SSLTemporaryRedirect: Bool(true), + SSLHost: String("foobar"), SSLProxyHeaders: map[string]string{ "name0": "foobar", "name1": "foobar", }, + SSLForceHost: Bool(true), STSSeconds: 42, STSIncludeSubdomains: true, STSPreload: true, @@ -1097,6 +1128,7 @@ func TestEncodeConfiguration(t *testing.T) { ContentSecurityPolicy: "foobar", PublicKey: "foobar", ReferrerPolicy: "foobar", + FeaturePolicy: String("foobar"), PermissionsPolicy: "foobar", IsDevelopment: true, }, @@ -1206,6 +1238,7 @@ func TestEncodeConfiguration(t *testing.T) { "traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.CheckPeriod": "1000000000", "traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.FallbackDuration": "1000000000", "traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.RecoveryDuration": "1000000000", + "traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.ResponseCode": "404", "traefik.HTTP.Middlewares.Middleware5.DigestAuth.HeaderField": "foobar", "traefik.HTTP.Middlewares.Middleware5.DigestAuth.Realm": "foobar", "traefik.HTTP.Middlewares.Middleware5.DigestAuth.RemoveHeader": "true", @@ -1218,6 +1251,7 @@ func TestEncodeConfiguration(t *testing.T) { "traefik.HTTP.Middlewares.Middleware7.ForwardAuth.AuthResponseHeaders": "foobar, fiibar", "traefik.HTTP.Middlewares.Middleware7.ForwardAuth.AuthRequestHeaders": "foobar, fiibar", "traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.CA": "foobar", + "traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.CAOptional": "true", "traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.Cert": "foobar", "traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.InsecureSkipVerify": "true", "traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.Key": "foobar", @@ -1246,9 +1280,14 @@ func TestEncodeConfiguration(t *testing.T) { "traefik.HTTP.Middlewares.Middleware8.Headers.IsDevelopment": "true", "traefik.HTTP.Middlewares.Middleware8.Headers.PublicKey": "foobar", "traefik.HTTP.Middlewares.Middleware8.Headers.ReferrerPolicy": "foobar", + "traefik.HTTP.Middlewares.Middleware8.Headers.FeaturePolicy": "foobar", "traefik.HTTP.Middlewares.Middleware8.Headers.PermissionsPolicy": "foobar", + "traefik.HTTP.Middlewares.Middleware8.Headers.SSLForceHost": "true", + "traefik.HTTP.Middlewares.Middleware8.Headers.SSLHost": "foobar", "traefik.HTTP.Middlewares.Middleware8.Headers.SSLProxyHeaders.name0": "foobar", "traefik.HTTP.Middlewares.Middleware8.Headers.SSLProxyHeaders.name1": "foobar", + "traefik.HTTP.Middlewares.Middleware8.Headers.SSLRedirect": "true", + "traefik.HTTP.Middlewares.Middleware8.Headers.SSLTemporaryRedirect": "true", "traefik.HTTP.Middlewares.Middleware8.Headers.STSIncludeSubdomains": "true", "traefik.HTTP.Middlewares.Middleware8.Headers.STSPreload": "true", "traefik.HTTP.Middlewares.Middleware8.Headers.STSSeconds": "42", @@ -1300,6 +1339,7 @@ func TestEncodeConfiguration(t *testing.T) { "traefik.HTTP.Middlewares.Middleware16.Retry.Attempts": "42", "traefik.HTTP.Middlewares.Middleware16.Retry.InitialInterval": "1000000000", "traefik.HTTP.Middlewares.Middleware17.StripPrefix.Prefixes": "foobar, fiibar", + "traefik.HTTP.Middlewares.Middleware17.StripPrefix.ForceSlash": "true", "traefik.HTTP.Middlewares.Middleware18.StripPrefixRegex.Regex": "foobar, fiibar", "traefik.HTTP.Middlewares.Middleware19.Compress.MinResponseBodyBytes": "42", "traefik.HTTP.Middlewares.Middleware20.Plugin.tomato.aaa": "foo1", @@ -1369,9 +1409,11 @@ func TestEncodeConfiguration(t *testing.T) { "traefik.TCP.Services.Service0.LoadBalancer.server.Port": "42", "traefik.TCP.Services.Service0.LoadBalancer.server.TLS": "false", "traefik.TCP.Services.Service0.LoadBalancer.ServersTransport": "foo", + "traefik.TCP.Services.Service0.LoadBalancer.TerminationDelay": "42", "traefik.TCP.Services.Service1.LoadBalancer.server.Port": "42", "traefik.TCP.Services.Service1.LoadBalancer.server.TLS": "false", "traefik.TCP.Services.Service1.LoadBalancer.ServersTransport": "foo", + "traefik.TCP.Services.Service1.LoadBalancer.TerminationDelay": "42", "traefik.UDP.Routers.Router0.EntryPoints": "foobar, fiibar", "traefik.UDP.Routers.Router0.Service": "foobar", diff --git a/pkg/config/runtime/runtime_http.go b/pkg/config/runtime/runtime_http.go index 1ea5eebd1..97a89f066 100644 --- a/pkg/config/runtime/runtime_http.go +++ b/pkg/config/runtime/runtime_http.go @@ -2,6 +2,7 @@ package runtime import ( "context" + "errors" "fmt" "slices" "sort" @@ -43,7 +44,7 @@ func (c *Configuration) GetRoutersByEntryPoints(ctx context.Context, entryPoints } if entryPointsCount == 0 { - rt.AddError(fmt.Errorf("no valid entryPoint for this router"), true) + rt.AddError(errors.New("no valid entryPoint for this router"), true) logger.Error().Msg("No valid entryPoint for this router") } diff --git a/pkg/config/runtime/runtime_tcp.go b/pkg/config/runtime/runtime_tcp.go index a5826c77d..1c213f7b1 100644 --- a/pkg/config/runtime/runtime_tcp.go +++ b/pkg/config/runtime/runtime_tcp.go @@ -2,6 +2,7 @@ package runtime import ( "context" + "errors" "fmt" "slices" @@ -37,7 +38,7 @@ func (c *Configuration) GetTCPRoutersByEntryPoints(ctx context.Context, entryPoi } if entryPointsCount == 0 { - rt.AddError(fmt.Errorf("no valid entryPoint for this router"), true) + rt.AddError(errors.New("no valid entryPoint for this router"), true) logger.Error().Msg("No valid entryPoint for this router") } } diff --git a/pkg/config/runtime/runtime_udp.go b/pkg/config/runtime/runtime_udp.go index 47d88c84f..e1a867506 100644 --- a/pkg/config/runtime/runtime_udp.go +++ b/pkg/config/runtime/runtime_udp.go @@ -2,6 +2,7 @@ package runtime import ( "context" + "errors" "fmt" "slices" @@ -43,7 +44,7 @@ func (c *Configuration) GetUDPRoutersByEntryPoints(ctx context.Context, entryPoi } if entryPointsCount == 0 { - rt.AddError(fmt.Errorf("no valid entryPoint for this router"), true) + rt.AddError(errors.New("no valid entryPoint for this router"), true) logger.Error().Msg("No valid entryPoint for this router") } } diff --git a/pkg/config/static/entrypoints.go b/pkg/config/static/entrypoints.go index 2c1187ac5..d7b155dbe 100644 --- a/pkg/config/static/entrypoints.go +++ b/pkg/config/static/entrypoints.go @@ -12,6 +12,7 @@ import ( // EntryPoint holds the entry point configuration. type EntryPoint struct { Address string `description:"Entry point address." json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"` + ReusePort bool `description:"Enables EntryPoints from the same or different processes listening on the same TCP/UDP port." json:"reusePort,omitempty" toml:"reusePort,omitempty" yaml:"reusePort,omitempty"` AsDefault bool `description:"Adds this EntryPoint to the list of default EntryPoints to be used on routers that don't have any Entrypoint defined." json:"asDefault,omitempty" toml:"asDefault,omitempty" yaml:"asDefault,omitempty"` Transport *EntryPointsTransport `description:"Configures communication between clients and Traefik." json:"transport,omitempty" toml:"transport,omitempty" yaml:"transport,omitempty" export:"true"` ProxyProtocol *ProxyProtocol `description:"Proxy-Protocol configuration." json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index 9f0ce294f..57af7a165 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -71,11 +71,23 @@ type Configuration struct { CertificatesResolvers map[string]CertificateResolver `description:"Certificates resolvers configuration." json:"certificatesResolvers,omitempty" toml:"certificatesResolvers,omitempty" yaml:"certificatesResolvers,omitempty" export:"true"` - Experimental *Experimental `description:"experimental features." json:"experimental,omitempty" toml:"experimental,omitempty" yaml:"experimental,omitempty" export:"true"` + Experimental *Experimental `description:"Experimental features." json:"experimental,omitempty" toml:"experimental,omitempty" yaml:"experimental,omitempty" export:"true"` + + Core *Core `description:"Core controls." json:"core,omitempty" toml:"core,omitempty" yaml:"core,omitempty" export:"true"` Spiffe *SpiffeClientConfig `description:"SPIFFE integration configuration." json:"spiffe,omitempty" toml:"spiffe,omitempty" yaml:"spiffe,omitempty" export:"true"` } +// Core configures Traefik core behavior. +type Core struct { + DefaultRuleSyntax string `description:"Defines the rule parser default syntax (v2 or v3)" json:"defaultRuleSyntax,omitempty" toml:"defaultRuleSyntax,omitempty" yaml:"defaultRuleSyntax,omitempty"` +} + +// SetDefaults sets the default values. +func (c *Core) SetDefaults() { + c.DefaultRuleSyntax = "v3" +} + // SpiffeClientConfig defines the SPIFFE client configuration. type SpiffeClientConfig struct { WorkloadAPIAddr string `description:"Defines the workload API address." json:"workloadAPIAddr,omitempty" toml:"workloadAPIAddr,omitempty" yaml:"workloadAPIAddr,omitempty"` @@ -182,9 +194,9 @@ func (a *LifeCycle) SetDefaults() { // Tracing holds the tracing configuration. type Tracing struct { ServiceName string `description:"Set the name for this service." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"` - Headers map[string]string `description:"Defines additional headers to be sent with the payloads." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty" export:"true"` GlobalAttributes map[string]string `description:"Defines additional attributes (key:value) on all spans." json:"globalAttributes,omitempty" toml:"globalAttributes,omitempty" yaml:"globalAttributes,omitempty" export:"true"` SampleRate float64 `description:"Sets the rate between 0.0 and 1.0 of requests to trace." json:"sampleRate,omitempty" toml:"sampleRate,omitempty" yaml:"sampleRate,omitempty" export:"true"` + AddInternals bool `description:"Enables tracing for internal services (ping, dashboard, etc...)." json:"addInternals,omitempty" toml:"addInternals,omitempty" yaml:"addInternals,omitempty" export:"true"` OTLP *opentelemetry.Config `description:"Settings for OpenTelemetry." json:"otlp,omitempty" toml:"otlp,omitempty" yaml:"otlp,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` } @@ -206,7 +218,7 @@ type Providers struct { KubernetesIngress *ingress.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesIngress,omitempty" toml:"kubernetesIngress,omitempty" yaml:"kubernetesIngress,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` KubernetesCRD *crd.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesCRD,omitempty" toml:"kubernetesCRD,omitempty" yaml:"kubernetesCRD,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` KubernetesGateway *gateway.Provider `description:"Enable Kubernetes gateway api provider with default settings." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - Rest *rest.Provider ` description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + Rest *rest.Provider `description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` ConsulCatalog *consulcatalog.ProviderBuilder `description:"Enable ConsulCatalog backend with default settings." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` Nomad *nomad.ProviderBuilder `description:"Enable Nomad backend with default settings." json:"nomad,omitempty" toml:"nomad,omitempty" yaml:"nomad,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` Ecs *ecs.Provider `description:"Enable AWS ECS backend with default settings." json:"ecs,omitempty" toml:"ecs,omitempty" yaml:"ecs,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` @@ -317,20 +329,29 @@ func (c *Configuration) ValidateConfiguration() error { acmeEmail = resolver.ACME.Email } + if c.Core != nil { + switch c.Core.DefaultRuleSyntax { + case "v3": // NOOP + case "v2": + // TODO: point to migration guide. + log.Warn().Msgf("v2 rules syntax is now deprecated, please use v3 instead...") + default: + return fmt.Errorf("unsupported default rule syntax configuration: %q", c.Core.DefaultRuleSyntax) + } + } + if c.Tracing != nil && c.Tracing.OTLP != nil { - if c.Tracing.OTLP.HTTP == nil && c.Tracing.OTLP.GRPC == nil { - return errors.New("tracing OTLP: at least one of HTTP and gRPC options should be defined") - } - - if c.Tracing.OTLP.HTTP != nil && c.Tracing.OTLP.GRPC != nil { - return errors.New("tracing OTLP: HTTP and gRPC options are mutually exclusive") - } - if c.Tracing.OTLP.GRPC != nil && c.Tracing.OTLP.GRPC.TLS != nil && c.Tracing.OTLP.GRPC.Insecure { return errors.New("tracing OTLP GRPC: TLS and Insecure options are mutually exclusive") } } + if c.Metrics != nil && c.Metrics.OTLP != nil { + if c.Metrics.OTLP.GRPC != nil && c.Metrics.OTLP.GRPC.TLS != nil && c.Metrics.OTLP.GRPC.Insecure { + return errors.New("metrics OTLP GRPC: TLS and Insecure options are mutually exclusive") + } + } + return nil } diff --git a/pkg/metrics/datadog.go b/pkg/metrics/datadog.go index b09e3407a..fcb8be6d2 100644 --- a/pkg/metrics/datadog.go +++ b/pkg/metrics/datadog.go @@ -2,6 +2,7 @@ package metrics import ( "context" + "strings" "time" "github.com/go-kit/kit/metrics/dogstatsd" @@ -16,6 +17,8 @@ var ( datadogLoopCancelFunc context.CancelFunc ) +const unixAddressPrefix = "unix://" + // Metric names consistent with https://github.com/DataDog/integrations-extras/pull/64 const ( ddConfigReloadsName = "config.reload.total" @@ -99,10 +102,7 @@ func RegisterDatadog(ctx context.Context, config *types.Datadog) Registry { } func initDatadogClient(ctx context.Context, config *types.Datadog) { - address := config.Address - if len(address) == 0 { - address = "localhost:8125" - } + network, address := parseDatadogAddress(config.Address) ctx, datadogLoopCancelFunc = context.WithCancel(ctx) @@ -110,10 +110,27 @@ func initDatadogClient(ctx context.Context, config *types.Datadog) { ticker := time.NewTicker(time.Duration(config.PushInterval)) defer ticker.Stop() - datadogClient.SendLoop(ctx, ticker.C, "udp", address) + datadogClient.SendLoop(ctx, ticker.C, network, address) }) } +func parseDatadogAddress(address string) (string, string) { + network := "udp" + + var addr string + switch { + case strings.HasPrefix(address, unixAddressPrefix): + network = "unix" + addr = address[len(unixAddressPrefix):] + case address != "": + addr = address + default: + addr = "localhost:8125" + } + + return network, addr +} + // StopDatadog stops the Datadog metrics pusher. func StopDatadog() { if datadogLoopCancelFunc != nil { diff --git a/pkg/metrics/datadog_test.go b/pkg/metrics/datadog_test.go index 68e79ba71..cd4b99433 100644 --- a/pkg/metrics/datadog_test.go +++ b/pkg/metrics/datadog_test.go @@ -7,18 +7,20 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stvp/go-udp-testing" ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/types" ) func TestDatadog(t *testing.T) { + t.Cleanup(StopDatadog) + udp.SetAddr(":18125") // This is needed to make sure that UDP Listener listens for data a bit longer, otherwise it will quit after a millisecond udp.Timeout = 5 * time.Second datadogRegistry := RegisterDatadog(context.Background(), &types.Datadog{Address: ":18125", PushInterval: ptypes.Duration(time.Second), AddEntryPointsLabels: true, AddRoutersLabels: true, AddServicesLabels: true}) - defer StopDatadog() if !datadogRegistry.IsEpEnabled() || !datadogRegistry.IsRouterEnabled() || !datadogRegistry.IsSvcEnabled() { t.Errorf("DatadogRegistry should return true for IsEnabled(), IsRouterEnabled() and IsSvcEnabled()") @@ -27,9 +29,7 @@ func TestDatadog(t *testing.T) { } func TestDatadogWithPrefix(t *testing.T) { - t.Cleanup(func() { - StopDatadog() - }) + t.Cleanup(StopDatadog) udp.SetAddr(":18125") // This is needed to make sure that UDP Listener listens for data a bit longer, otherwise it will quit after a millisecond @@ -40,6 +40,44 @@ func TestDatadogWithPrefix(t *testing.T) { testDatadogRegistry(t, "testPrefix", datadogRegistry) } +func TestDatadog_parseDatadogAddress(t *testing.T) { + tests := []struct { + desc string + address string + expNetwork string + expAddress string + }{ + { + desc: "empty address", + expNetwork: "udp", + expAddress: "localhost:8125", + }, + { + desc: "udp address", + address: "127.0.0.1:8080", + expNetwork: "udp", + expAddress: "127.0.0.1:8080", + }, + { + desc: "unix address", + address: "unix:///path/to/datadog.socket", + expNetwork: "unix", + expAddress: "/path/to/datadog.socket", + }, + } + + for _, test := range tests { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + gotNetwork, gotAddress := parseDatadogAddress(test.address) + assert.Equal(t, test.expNetwork, gotNetwork) + assert.Equal(t, test.expAddress, gotAddress) + }) + } +} + func testDatadogRegistry(t *testing.T, metricsPrefix string, datadogRegistry Registry) { t.Helper() diff --git a/pkg/metrics/influxdb2_test.go b/pkg/metrics/influxdb2_test.go index 89b558075..e75141ff4 100644 --- a/pkg/metrics/influxdb2_test.go +++ b/pkg/metrics/influxdb2_test.go @@ -6,7 +6,6 @@ import ( "io" "net/http" "net/http/httptest" - "regexp" "strconv" "testing" "time" @@ -26,7 +25,6 @@ func TestInfluxDB2(t *testing.T) { c <- &bodyStr _, _ = fmt.Fprintln(w, "ok") })) - defer ts.Close() influxDB2Registry := RegisterInfluxDB2(context.Background(), &types.InfluxDB2{ @@ -39,7 +37,11 @@ func TestInfluxDB2(t *testing.T) { AddRoutersLabels: true, AddServicesLabels: true, }) - defer StopInfluxDB2() + + t.Cleanup(func() { + StopInfluxDB2() + ts.Close() + }) if !influxDB2Registry.IsEpEnabled() || !influxDB2Registry.IsRouterEnabled() || !influxDB2Registry.IsSvcEnabled() { t.Fatalf("InfluxDB2Registry should return true for IsEnabled(), IsRouterEnabled() and IsSvcEnabled()") @@ -137,14 +139,3 @@ func TestInfluxDB2(t *testing.T) { assertMessage(t, *msgServiceRetries, expectedServiceRetries) } - -func assertMessage(t *testing.T, msg string, patterns []string) { - t.Helper() - for _, pattern := range patterns { - re := regexp.MustCompile(pattern) - match := re.FindStringSubmatch(msg) - if len(match) != 2 { - t.Errorf("Got %q %v, want %q", msg, match, pattern) - } - } -} diff --git a/pkg/metrics/opentelemetry.go b/pkg/metrics/opentelemetry.go index e866ff5e0..cf9920a25 100644 --- a/pkg/metrics/opentelemetry.go +++ b/pkg/metrics/opentelemetry.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net" + "net/url" "strings" "sync" "time" @@ -30,7 +31,7 @@ var ( ) // RegisterOpenTelemetry registers all OpenTelemetry metrics. -func RegisterOpenTelemetry(ctx context.Context, config *types.OpenTelemetry) Registry { +func RegisterOpenTelemetry(ctx context.Context, config *types.OTLP) Registry { if openTelemetryMeterProvider == nil { var err error if openTelemetryMeterProvider, err = newOpenTelemetryMeterProvider(ctx, config); err != nil { @@ -123,15 +124,15 @@ func StopOpenTelemetry() { } // newOpenTelemetryMeterProvider creates a new controller.Controller. -func newOpenTelemetryMeterProvider(ctx context.Context, config *types.OpenTelemetry) (*sdkmetric.MeterProvider, error) { +func newOpenTelemetryMeterProvider(ctx context.Context, config *types.OTLP) (*sdkmetric.MeterProvider, error) { var ( exporter sdkmetric.Exporter err error ) if config.GRPC != nil { - exporter, err = newGRPCExporter(ctx, config) + exporter, err = newGRPCExporter(ctx, config.GRPC) } else { - exporter, err = newHTTPExporter(ctx, config) + exporter, err = newHTTPExporter(ctx, config.HTTP) } if err != nil { return nil, fmt.Errorf("creating exporter: %w", err) @@ -168,24 +169,24 @@ func newOpenTelemetryMeterProvider(ctx context.Context, config *types.OpenTeleme return meterProvider, nil } -func newHTTPExporter(ctx context.Context, config *types.OpenTelemetry) (sdkmetric.Exporter, error) { - host, port, err := net.SplitHostPort(config.Address) +func newHTTPExporter(ctx context.Context, config *types.OtelHTTP) (sdkmetric.Exporter, error) { + endpoint, err := url.Parse(config.Endpoint) if err != nil { - return nil, fmt.Errorf("invalid collector address %q: %w", config.Address, err) + return nil, fmt.Errorf("invalid collector endpoint %q: %w", config.Endpoint, err) } opts := []otlpmetrichttp.Option{ - otlpmetrichttp.WithEndpoint(fmt.Sprintf("%s:%s", host, port)), + otlpmetrichttp.WithEndpoint(endpoint.Host), otlpmetrichttp.WithHeaders(config.Headers), otlpmetrichttp.WithCompression(otlpmetrichttp.GzipCompression), } - if config.Insecure { + if endpoint.Scheme == "http" { opts = append(opts, otlpmetrichttp.WithInsecure()) } - if config.Path != "" { - opts = append(opts, otlpmetrichttp.WithURLPath(config.Path)) + if endpoint.Path != "" { + opts = append(opts, otlpmetrichttp.WithURLPath(endpoint.Path)) } if config.TLS != nil { @@ -200,10 +201,10 @@ func newHTTPExporter(ctx context.Context, config *types.OpenTelemetry) (sdkmetri return otlpmetrichttp.New(ctx, opts...) } -func newGRPCExporter(ctx context.Context, config *types.OpenTelemetry) (sdkmetric.Exporter, error) { - host, port, err := net.SplitHostPort(config.Address) +func newGRPCExporter(ctx context.Context, config *types.OtelGRPC) (sdkmetric.Exporter, error) { + host, port, err := net.SplitHostPort(config.Endpoint) if err != nil { - return nil, fmt.Errorf("invalid collector address %q: %w", config.Address, err) + return nil, fmt.Errorf("invalid collector endpoint %q: %w", config.Endpoint, err) } opts := []otlpmetricgrpc.Option{ diff --git a/pkg/metrics/opentelemetry_test.go b/pkg/metrics/opentelemetry_test.go index c6bfe99eb..2e3ea328d 100644 --- a/pkg/metrics/opentelemetry_test.go +++ b/pkg/metrics/opentelemetry_test.go @@ -4,10 +4,11 @@ import ( "compress/gzip" "context" "encoding/json" + "fmt" "io" "net/http" "net/http/httptest" - "net/url" + "regexp" "strconv" "testing" "time" @@ -287,10 +288,8 @@ func TestOpenTelemetry_GaugeCollectorSet(t *testing.T) { } func TestOpenTelemetry(t *testing.T) { - t.Parallel() + c := make(chan *string, 5) - c := make(chan *string) - defer close(c) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gzr, err := gzip.NewReader(r.Body) require.NoError(t, err) @@ -310,16 +309,18 @@ func TestOpenTelemetry(t *testing.T) { w.WriteHeader(http.StatusOK) })) - defer ts.Close() - sURL, err := url.Parse(ts.URL) - require.NoError(t, err) + t.Cleanup(func() { + StopOpenTelemetry() + ts.Close() + }) - var cfg types.OpenTelemetry + var cfg types.OTLP (&cfg).SetDefaults() cfg.AddRoutersLabels = true - cfg.Address = sURL.Host - cfg.Insecure = true + cfg.HTTP = &types.OtelHTTP{ + Endpoint: ts.URL, + } cfg.PushInterval = ptypes.Duration(10 * time.Millisecond) registry := RegisterOpenTelemetry(context.Background(), &cfg) @@ -333,57 +334,53 @@ func TestOpenTelemetry(t *testing.T) { `({"key":"service.name","value":{"stringValue":"traefik"}})`, `({"key":"service.version","value":{"stringValue":"` + version.Version + `"}})`, } - msgMisc := <-c - assertMessage(t, *msgMisc, expected) + tryAssertMessage(t, c, expected) // TODO: the len of startUnixNano is no supposed to be 20, it should be 19 - expected = append(expected, + expectedConfig := []string{ `({"name":"traefik_config_reloads_total","description":"Config reloads","unit":"1","sum":{"dataPoints":\[{"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`, `({"name":"traefik_config_last_reload_success","description":"Last config reload success","unit":"ms","gauge":{"dataPoints":\[{"timeUnixNano":"[\d]{19}","asDouble":1}\]}})`, `({"name":"traefik_open_connections","description":"How many open connections exist, by entryPoint and protocol","unit":"1","gauge":{"dataPoints":\[{"attributes":\[{"key":"entrypoint","value":{"stringValue":"test"}},{"key":"protocol","value":{"stringValue":"TCP"}}\],"timeUnixNano":"[\d]{19}","asDouble":1}\]}})`, - ) + } registry.ConfigReloadsCounter().Add(1) registry.LastConfigReloadSuccessGauge().Set(1) registry.OpenConnectionsGauge().With("entrypoint", "test", "protocol", "TCP").Set(1) - msgServer := <-c - assertMessage(t, *msgServer, expected) + tryAssertMessage(t, c, expectedConfig) - expected = append(expected, + expectedTLSCerts := []string{ `({"name":"traefik_tls_certs_not_after","description":"Certificate expiration timestamp","unit":"ms","gauge":{"dataPoints":\[{"attributes":\[{"key":"key","value":{"stringValue":"value"}}\],"timeUnixNano":"[\d]{19}","asDouble":1}\]}})`, - ) + } registry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1) - msgTLS := <-c - assertMessage(t, *msgTLS, expected) + tryAssertMessage(t, c, expectedTLSCerts) - expected = append(expected, + expectedEntryPoints := []string{ `({"name":"traefik_entrypoint_requests_total","description":"How many HTTP requests processed on an entrypoint, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"entrypoint","value":{"stringValue":"test1"}},{"key":"method","value":{"stringValue":"GET"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`, `({"name":"traefik_entrypoint_requests_tls_total","description":"How many HTTP requests with TLS processed on an entrypoint, partitioned by TLS Version and TLS cipher Used.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"entrypoint","value":{"stringValue":"test2"}},{"key":"tls_cipher","value":{"stringValue":"bar"}},{"key":"tls_version","value":{"stringValue":"foo"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`, `({"name":"traefik_entrypoint_request_duration_seconds","description":"How long it took to process the request on an entrypoint, partitioned by status code, protocol, and method.","unit":"ms","histogram":{"dataPoints":\[{"attributes":\[{"key":"entrypoint","value":{"stringValue":"test3"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"1","sum":10000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`, `({"name":"traefik_entrypoint_requests_bytes_total","description":"The total size of requests in bytes handled by an entrypoint, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"entrypoint","value":{"stringValue":"test1"}},{"key":"method","value":{"stringValue":"GET"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`, `({"name":"traefik_entrypoint_responses_bytes_total","description":"The total size of responses in bytes handled by an entrypoint, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"entrypoint","value":{"stringValue":"test1"}},{"key":"method","value":{"stringValue":"GET"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`, - ) + } registry.EntryPointReqsCounter().With(nil, "entrypoint", "test1", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet).Add(1) registry.EntryPointReqsTLSCounter().With("entrypoint", "test2", "tls_version", "foo", "tls_cipher", "bar").Add(1) registry.EntryPointReqDurationHistogram().With("entrypoint", "test3").Observe(10000) registry.EntryPointReqsBytesCounter().With("entrypoint", "test1", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet).Add(1) registry.EntryPointRespsBytesCounter().With("entrypoint", "test1", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet).Add(1) - msgEntrypoint := <-c - assertMessage(t, *msgEntrypoint, expected) + tryAssertMessage(t, c, expectedEntryPoints) - expected = append(expected, + expectedRouters := []string{ `({"name":"traefik_router_requests_total","description":"How many HTTP requests are processed on a router, partitioned by service, status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"(?:200|404)"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"router","value":{"stringValue":"RouterReqsCounter"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1},{"attributes":\[{"key":"code","value":{"stringValue":"(?:200|404)"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"router","value":{"stringValue":"RouterReqsCounter"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`, `({"name":"traefik_router_requests_tls_total","description":"How many HTTP requests with TLS are processed on a router, partitioned by service, TLS Version, and TLS cipher Used.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"router","value":{"stringValue":"demo"}},{"key":"service","value":{"stringValue":"test"}},{"key":"tls_cipher","value":{"stringValue":"bar"}},{"key":"tls_version","value":{"stringValue":"foo"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`, `({"name":"traefik_router_request_duration_seconds","description":"How long it took to process the request on a router, partitioned by service, status code, protocol, and method.","unit":"ms","histogram":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"router","value":{"stringValue":"demo"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"1","sum":10000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`, `({"name":"traefik_router_requests_bytes_total","description":"The total size of requests in bytes handled by a router, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"404"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"router","value":{"stringValue":"RouterReqsCounter"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`, `({"name":"traefik_router_responses_bytes_total","description":"The total size of responses in bytes handled by a router, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"404"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"router","value":{"stringValue":"RouterReqsCounter"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`, - ) + } registry.RouterReqsCounter().With(nil, "router", "RouterReqsCounter", "service", "test", "code", strconv.Itoa(http.StatusNotFound), "method", http.MethodGet).Add(1) registry.RouterReqsCounter().With(nil, "router", "RouterReqsCounter", "service", "test", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet).Add(1) @@ -391,18 +388,17 @@ func TestOpenTelemetry(t *testing.T) { registry.RouterReqDurationHistogram().With("router", "demo", "service", "test", "code", strconv.Itoa(http.StatusOK)).Observe(10000) registry.RouterReqsBytesCounter().With("router", "RouterReqsCounter", "service", "test", "code", strconv.Itoa(http.StatusNotFound), "method", http.MethodGet).Add(1) registry.RouterRespsBytesCounter().With("router", "RouterReqsCounter", "service", "test", "code", strconv.Itoa(http.StatusNotFound), "method", http.MethodGet).Add(1) - msgRouter := <-c - assertMessage(t, *msgRouter, expected) + tryAssertMessage(t, c, expectedRouters) - expected = append(expected, + expectedServices := []string{ `({"name":"traefik_service_requests_total","description":"How many HTTP requests processed on a service, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"(?:200|404)"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"service","value":{"stringValue":"ServiceReqsCounter"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1},{"attributes":\[{"key":"code","value":{"stringValue":"(?:200|404)"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"service","value":{"stringValue":"ServiceReqsCounter"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`, `({"name":"traefik_service_requests_tls_total","description":"How many HTTP requests with TLS processed on a service, partitioned by TLS version and TLS cipher.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"service","value":{"stringValue":"test"}},{"key":"tls_cipher","value":{"stringValue":"bar"}},{"key":"tls_version","value":{"stringValue":"foo"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`, `({"name":"traefik_service_request_duration_seconds","description":"How long it took to process the request on a service, partitioned by status code, protocol, and method.","unit":"ms","histogram":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"1","sum":10000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`, `({"name":"traefik_service_server_up","description":"service server is up, described by gauge value of 0 or 1.","unit":"1","gauge":{"dataPoints":\[{"attributes":\[{"key":"service","value":{"stringValue":"test"}},{"key":"url","value":{"stringValue":"http://127.0.0.1"}}\],"timeUnixNano":"[\d]{19}","asDouble":1}\]}})`, `({"name":"traefik_service_requests_bytes_total","description":"The total size of requests in bytes received by a service, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"404"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"service","value":{"stringValue":"ServiceReqsCounter"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`, `({"name":"traefik_service_responses_bytes_total","description":"The total size of responses in bytes returned by a service, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"404"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"service","value":{"stringValue":"ServiceReqsCounter"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`, - ) + } registry.ServiceReqsCounter().With(nil, "service", "ServiceReqsCounter", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet).Add(1) registry.ServiceReqsCounter().With(nil, "service", "ServiceReqsCounter", "code", strconv.Itoa(http.StatusNotFound), "method", http.MethodGet).Add(1) @@ -411,21 +407,19 @@ func TestOpenTelemetry(t *testing.T) { registry.ServiceServerUpGauge().With("service", "test", "url", "http://127.0.0.1").Set(1) registry.ServiceReqsBytesCounter().With("service", "ServiceReqsCounter", "code", strconv.Itoa(http.StatusNotFound), "method", http.MethodGet).Add(1) registry.ServiceRespsBytesCounter().With("service", "ServiceReqsCounter", "code", strconv.Itoa(http.StatusNotFound), "method", http.MethodGet).Add(1) - msgService := <-c - assertMessage(t, *msgService, expected) + tryAssertMessage(t, c, expectedServices) - expected = append(expected, + expectedServicesRetries := []string{ `({"attributes":\[{"key":"service","value":{"stringValue":"foobar"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1})`, `({"attributes":\[{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":2})`, - ) + } registry.ServiceRetriesCounter().With("service", "test").Add(1) registry.ServiceRetriesCounter().With("service", "test").Add(1) registry.ServiceRetriesCounter().With("service", "foobar").Add(1) - msgServiceRetries := <-c - assertMessage(t, *msgServiceRetries, expected) + tryAssertMessage(t, c, expectedServicesRetries) // We cannot rely on the previous expected pattern, // because this pattern was for matching only one dataPoint in the histogram, @@ -437,14 +431,46 @@ func TestOpenTelemetry(t *testing.T) { registry.EntryPointReqDurationHistogram().With("entrypoint", "myEntrypoint").Observe(10000) registry.EntryPointReqDurationHistogram().With("entrypoint", "myEntrypoint").Observe(20000) - msgEntryPointReqDurationHistogram := <-c - assertMessage(t, *msgEntryPointReqDurationHistogram, expectedEntryPointReqDuration) - - // We need to unlock the HTTP Server for the last export call when stopping - // OpenTelemetry. - go func() { - <-c - }() - StopOpenTelemetry() + tryAssertMessage(t, c, expectedEntryPointReqDuration) +} + +func assertMessage(t *testing.T, msg string, expected []string) { + t.Helper() + errs := verifyMessage(msg, expected) + for _, err := range errs { + t.Error(err) + } +} + +func tryAssertMessage(t *testing.T, c chan *string, expected []string) { + t.Helper() + + var errs []error + timeout := time.After(1 * time.Second) + for { + select { + case <-timeout: + for _, err := range errs { + t.Error(err) + } + case msg := <-c: + errs = verifyMessage(*msg, expected) + if len(errs) == 0 { + return + } + } + } +} + +func verifyMessage(msg string, expected []string) []error { + var errs []error + for _, pattern := range expected { + re := regexp.MustCompile(pattern) + match := re.FindStringSubmatch(msg) + if len(match) != 2 { + errs = append(errs, fmt.Errorf("Got %q %v, want %q", msg, match, pattern)) + } + } + return errs } diff --git a/pkg/middlewares/accesslog/logger_test.go b/pkg/middlewares/accesslog/logger_test.go index 6f7d3a422..4fce3deba 100644 --- a/pkg/middlewares/accesslog/logger_test.go +++ b/pkg/middlewares/accesslog/logger_test.go @@ -27,6 +27,8 @@ import ( "github.com/traefik/traefik/v3/pkg/types" ) +const delta float64 = 1e-10 + var ( logFileNameSuffix = "/traefik/logger/test.log" testContent = "Hello, World" @@ -280,7 +282,7 @@ func assertFloat64(exp float64) func(t *testing.T, actual interface{}) { return func(t *testing.T, actual interface{}) { t.Helper() - assert.Equal(t, exp, actual) + assert.InDelta(t, exp, actual, delta) } } diff --git a/pkg/middlewares/addprefix/add_prefix.go b/pkg/middlewares/addprefix/add_prefix.go index e91facaf9..46f8d98d0 100644 --- a/pkg/middlewares/addprefix/add_prefix.go +++ b/pkg/middlewares/addprefix/add_prefix.go @@ -2,7 +2,7 @@ package addprefix import ( "context" - "fmt" + "errors" "net/http" "github.com/traefik/traefik/v3/pkg/config/dynamic" @@ -33,7 +33,7 @@ func New(ctx context.Context, next http.Handler, config dynamic.AddPrefix, name name: name, } } else { - return nil, fmt.Errorf("prefix cannot be empty") + return nil, errors.New("prefix cannot be empty") } return result, nil diff --git a/pkg/middlewares/auth/forward.go b/pkg/middlewares/auth/forward.go index 6e3b3ac84..daa2dbd27 100644 --- a/pkg/middlewares/auth/forward.go +++ b/pkg/middlewares/auth/forward.go @@ -15,6 +15,7 @@ import ( "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares/connectionheader" "github.com/traefik/traefik/v3/pkg/tracing" + "github.com/traefik/traefik/v3/pkg/types" "github.com/vulcand/oxy/v2/forward" "github.com/vulcand/oxy/v2/utils" "go.opentelemetry.io/otel/trace" @@ -53,7 +54,8 @@ type forwardAuth struct { // NewForward creates a forward auth middleware. func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAuth, name string) (http.Handler, error) { - middlewares.GetLogger(ctx, name, typeNameForward).Debug().Msg("Creating middleware") + logger := middlewares.GetLogger(ctx, name, typeNameForward) + logger.Debug().Msg("Creating middleware") addAuthCookiesToResponse := make(map[string]struct{}) for _, cookieName := range config.AddAuthCookiesToResponse { @@ -79,7 +81,18 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu } if config.TLS != nil { - tlsConfig, err := config.TLS.CreateTLSConfig(ctx) + if config.TLS.CAOptional != nil { + logger.Warn().Msg("CAOptional option is deprecated, TLS client authentication is a server side option, please remove any usage of this option.") + } + + clientTLS := &types.ClientTLS{ + CA: config.TLS.CA, + Cert: config.TLS.Cert, + Key: config.TLS.Key, + InsecureSkipVerify: config.TLS.InsecureSkipVerify, + } + + tlsConfig, err := clientTLS.CreateTLSConfig(ctx) if err != nil { return nil, fmt.Errorf("unable to create client TLS configuration: %w", err) } diff --git a/pkg/middlewares/auth/forward_test.go b/pkg/middlewares/auth/forward_test.go index 461d9b329..57e07314f 100644 --- a/pkg/middlewares/auth/forward_test.go +++ b/pkg/middlewares/auth/forward_test.go @@ -17,6 +17,7 @@ import ( "github.com/traefik/traefik/v3/pkg/testhelpers" "github.com/traefik/traefik/v3/pkg/tracing" "github.com/traefik/traefik/v3/pkg/tracing/opentelemetry" + "github.com/traefik/traefik/v3/pkg/types" "github.com/traefik/traefik/v3/pkg/version" "github.com/vulcand/oxy/v2/forward" "go.opentelemetry.io/otel" @@ -500,7 +501,7 @@ func TestForwardAuthUsesTracing(t *testing.T) { ServiceName: "testApp", SampleRate: 1, OTLP: &opentelemetry.Config{ - HTTP: &opentelemetry.HTTP{ + HTTP: &types.OtelHTTP{ Endpoint: "http://127.0.0.1:8080", }, }, diff --git a/pkg/middlewares/circuitbreaker/circuit_breaker.go b/pkg/middlewares/circuitbreaker/circuit_breaker.go index 3fa06ba23..ba377ea3d 100644 --- a/pkg/middlewares/circuitbreaker/circuit_breaker.go +++ b/pkg/middlewares/circuitbreaker/circuit_breaker.go @@ -30,12 +30,14 @@ func New(ctx context.Context, next http.Handler, confCircuitBreaker dynamic.Circ logger.Debug().Msg("Creating middleware") logger.Debug().Msgf("Setting up with expression: %s", expression) + responseCode := confCircuitBreaker.ResponseCode + cbOpts := []cbreaker.Option{ cbreaker.Fallback(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { tracing.SetStatusErrorf(req.Context(), "blocked by circuit-breaker (%q)", expression) - rw.WriteHeader(http.StatusServiceUnavailable) + rw.WriteHeader(responseCode) - if _, err := rw.Write([]byte(http.StatusText(http.StatusServiceUnavailable))); err != nil { + if _, err := rw.Write([]byte(http.StatusText(responseCode))); err != nil { log.Ctx(req.Context()).Error().Err(err).Send() } })), diff --git a/pkg/middlewares/compress/brotli/brotli.go b/pkg/middlewares/compress/brotli/brotli.go index c95a5ff66..17225f252 100644 --- a/pkg/middlewares/compress/brotli/brotli.go +++ b/pkg/middlewares/compress/brotli/brotli.go @@ -2,6 +2,7 @@ package brotli import ( "bufio" + "errors" "fmt" "io" "mime" @@ -34,11 +35,11 @@ type Config struct { // NewWrapper returns a new Brotli compressing wrapper. func NewWrapper(cfg Config) (func(http.Handler) http.HandlerFunc, error) { if cfg.MinSize < 0 { - return nil, fmt.Errorf("minimum size must be greater than or equal to zero") + return nil, errors.New("minimum size must be greater than or equal to zero") } if len(cfg.ExcludedContentTypes) > 0 && len(cfg.IncludedContentTypes) > 0 { - return nil, fmt.Errorf("excludedContentTypes and includedContentTypes options are mutually exclusive") + return nil, errors.New("excludedContentTypes and includedContentTypes options are mutually exclusive") } var excludedContentTypes []parsedContentType @@ -138,6 +139,7 @@ func (r *responseWriter) Write(p []byte) (int, error) { // If we detect a contentEncoding, we know we are never going to compress. if r.rw.Header().Get(contentEncoding) != "" { r.compressionDisabled = true + r.rw.WriteHeader(r.statusCode) return r.rw.Write(p) } diff --git a/pkg/middlewares/compress/brotli/brotli_test.go b/pkg/middlewares/compress/brotli/brotli_test.go index 5e1bfeea7..445268f85 100644 --- a/pkg/middlewares/compress/brotli/brotli_test.go +++ b/pkg/middlewares/compress/brotli/brotli_test.go @@ -27,7 +27,7 @@ func Test_Vary(t *testing.T) { rw := httptest.NewRecorder() h.ServeHTTP(rw, req) - assert.Equal(t, http.StatusOK, rw.Code) + assert.Equal(t, http.StatusAccepted, rw.Code) assert.Equal(t, acceptEncoding, rw.Header().Get(vary)) } @@ -41,7 +41,7 @@ func Test_SmallBodyNoCompression(t *testing.T) { h.ServeHTTP(rw, req) // With less than 1024 bytes the response should not be compressed. - assert.Equal(t, http.StatusOK, rw.Code) + assert.Equal(t, http.StatusAccepted, rw.Code) assert.Empty(t, rw.Header().Get(contentEncoding)) assert.Equal(t, smallTestBody, rw.Body.Bytes()) } @@ -55,6 +55,7 @@ func Test_AlreadyCompressed(t *testing.T) { rw := httptest.NewRecorder() h.ServeHTTP(rw, req) + assert.Equal(t, http.StatusAccepted, rw.Code) assert.Equal(t, bigTestBody, rw.Body.Bytes()) } @@ -749,6 +750,7 @@ func newTestHandler(t *testing.T, body []byte) http.Handler { rw.Header().Set("Content-Encoding", "br") } + rw.WriteHeader(http.StatusAccepted) _, err := rw.Write(body) require.NoError(t, err) }), diff --git a/pkg/middlewares/compress/compress.go b/pkg/middlewares/compress/compress.go index 0cdd29883..bde909978 100644 --- a/pkg/middlewares/compress/compress.go +++ b/pkg/middlewares/compress/compress.go @@ -2,6 +2,7 @@ package compress import ( "context" + "errors" "fmt" "mime" "net/http" @@ -38,7 +39,7 @@ func New(ctx context.Context, next http.Handler, conf dynamic.Compress, name str middlewares.GetLogger(ctx, name, typeName).Debug().Msg("Creating middleware") if len(conf.ExcludedContentTypes) > 0 && len(conf.IncludedContentTypes) > 0 { - return nil, fmt.Errorf("excludedContentTypes and includedContentTypes options are mutually exclusive") + return nil, errors.New("excludedContentTypes and includedContentTypes options are mutually exclusive") } excludes := []string{"application/grpc"} diff --git a/pkg/middlewares/contenttype/content_type.go b/pkg/middlewares/contenttype/content_type.go index 763f7587e..4d9af4a73 100644 --- a/pkg/middlewares/contenttype/content_type.go +++ b/pkg/middlewares/contenttype/content_type.go @@ -4,6 +4,7 @@ import ( "context" "net/http" + "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" ) @@ -18,8 +19,19 @@ type contentType struct { } // New creates a new handler. -func New(ctx context.Context, next http.Handler, name string) (http.Handler, error) { - middlewares.GetLogger(ctx, name, typeName).Debug().Msg("Creating middleware") +func New(ctx context.Context, next http.Handler, config dynamic.ContentType, name string) (http.Handler, error) { + logger := middlewares.GetLogger(ctx, name, typeName) + logger.Debug().Msg("Creating middleware") + + if config.AutoDetect != nil { + logger.Warn().Msg("AutoDetect option is deprecated, Content-Type middleware is only meant to be used to enable the content-type detection, please remove any usage of this option.") + + // Disable content-type detection (idempotent). + if !*config.AutoDetect { + return next, nil + } + } + return &contentType{next: next, name: name}, nil } diff --git a/pkg/middlewares/contenttype/content_type_test.go b/pkg/middlewares/contenttype/content_type_test.go index 24facb0d2..9f311b5bc 100644 --- a/pkg/middlewares/contenttype/content_type_test.go +++ b/pkg/middlewares/contenttype/content_type_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/testhelpers" ) @@ -60,7 +61,7 @@ func TestAutoDetection(t *testing.T) { if test.autoDetect { var err error - next, err = New(context.Background(), next, "foo-content-type") + next, err = New(context.Background(), next, dynamic.ContentType{}, "foo-content-type") require.NoError(t, err) } diff --git a/pkg/middlewares/stripprefix/strip_prefix.go b/pkg/middlewares/stripprefix/strip_prefix.go index 18717875e..8483f5100 100644 --- a/pkg/middlewares/stripprefix/strip_prefix.go +++ b/pkg/middlewares/stripprefix/strip_prefix.go @@ -21,15 +21,27 @@ type stripPrefix struct { next http.Handler prefixes []string name string + + // Deprecated: Must be removed (breaking), the default behavior must be forceSlash=false + forceSlash bool } // New creates a new strip prefix middleware. func New(ctx context.Context, next http.Handler, config dynamic.StripPrefix, name string) (http.Handler, error) { - middlewares.GetLogger(ctx, name, typeName).Debug().Msg("Creating middleware") + logger := middlewares.GetLogger(ctx, name, typeName) + logger.Debug().Msg("Creating middleware") + + if config.ForceSlash != nil { + logger.Warn().Msgf("`ForceSlash` option is deprecated, please remove any usage of this option.") + } + // Handle default value (here because of deprecation and the removal of setDefault). + forceSlash := config.ForceSlash != nil && *config.ForceSlash + return &stripPrefix{ - prefixes: config.Prefixes, - next: next, - name: name, + prefixes: config.Prefixes, + next: next, + name: name, + forceSlash: forceSlash, }, nil } @@ -58,6 +70,13 @@ func (s *stripPrefix) serveRequest(rw http.ResponseWriter, req *http.Request, pr } func (s *stripPrefix) getPrefixStripped(urlPath, prefix string) string { + if s.forceSlash { + // Only for compatibility reason with the previous behavior, + // but the previous behavior is wrong. + // This needs to be removed in the next breaking version. + return "/" + strings.TrimPrefix(strings.TrimPrefix(urlPath, prefix), "/") + } + return ensureLeadingSlash(strings.TrimPrefix(urlPath, prefix)) } diff --git a/pkg/middlewares/stripprefix/strip_prefix_test.go b/pkg/middlewares/stripprefix/strip_prefix_test.go index 612a282f1..cb2c58826 100644 --- a/pkg/middlewares/stripprefix/strip_prefix_test.go +++ b/pkg/middlewares/stripprefix/strip_prefix_test.go @@ -146,6 +146,9 @@ func TestStripPrefix(t *testing.T) { requestURI = r.RequestURI }) + pointer := func(v bool) *bool { return &v } + test.config.ForceSlash = pointer(false) + handler, err := New(context.Background(), next, test.config, "foo-strip-prefix") require.NoError(t, err) diff --git a/pkg/muxer/http/matcher_test.go b/pkg/muxer/http/matcher_test.go index 1b527a392..c4e601d01 100644 --- a/pkg/muxer/http/matcher_test.go +++ b/pkg/muxer/http/matcher_test.go @@ -73,7 +73,7 @@ func TestClientIPMatcher(t *testing.T) { muxer, err := NewMuxer() require.NoError(t, err) - err = muxer.AddRoute(test.rule, 0, handler) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) return @@ -147,7 +147,7 @@ func TestMethodMatcher(t *testing.T) { muxer, err := NewMuxer() require.NoError(t, err) - err = muxer.AddRoute(test.rule, 0, handler) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) return @@ -265,7 +265,7 @@ func TestHostMatcher(t *testing.T) { muxer, err := NewMuxer() require.NoError(t, err) - err = muxer.AddRoute(test.rule, 0, handler) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) return @@ -365,7 +365,7 @@ func TestHostRegexpMatcher(t *testing.T) { muxer, err := NewMuxer() require.NoError(t, err) - err = muxer.AddRoute(test.rule, 0, handler) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) return @@ -439,7 +439,7 @@ func TestPathMatcher(t *testing.T) { muxer, err := NewMuxer() require.NoError(t, err) - err = muxer.AddRoute(test.rule, 0, handler) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) return @@ -532,7 +532,7 @@ func TestPathRegexpMatcher(t *testing.T) { muxer, err := NewMuxer() require.NoError(t, err) - err = muxer.AddRoute(test.rule, 0, handler) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) return @@ -604,7 +604,7 @@ func TestPathPrefixMatcher(t *testing.T) { muxer, err := NewMuxer() require.NoError(t, err) - err = muxer.AddRoute(test.rule, 0, handler) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) return @@ -692,7 +692,7 @@ func TestHeaderMatcher(t *testing.T) { muxer, err := NewMuxer() require.NoError(t, err) - err = muxer.AddRoute(test.rule, 0, handler) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) return @@ -800,7 +800,7 @@ func TestHeaderRegexpMatcher(t *testing.T) { muxer, err := NewMuxer() require.NoError(t, err) - err = muxer.AddRoute(test.rule, 0, handler) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) return @@ -889,7 +889,7 @@ func TestQueryMatcher(t *testing.T) { muxer, err := NewMuxer() require.NoError(t, err) - err = muxer.AddRoute(test.rule, 0, handler) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) return @@ -1003,7 +1003,7 @@ func TestQueryRegexpMatcher(t *testing.T) { muxer, err := NewMuxer() require.NoError(t, err) - err = muxer.AddRoute(test.rule, 0, handler) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) return diff --git a/pkg/muxer/http/matcher_v2.go b/pkg/muxer/http/matcher_v2.go new file mode 100644 index 000000000..72d5d826a --- /dev/null +++ b/pkg/muxer/http/matcher_v2.go @@ -0,0 +1,226 @@ +package http + +import ( + "fmt" + "net/http" + "strings" + + "github.com/gorilla/mux" + "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/ip" + "github.com/traefik/traefik/v3/pkg/middlewares/requestdecorator" +) + +var httpFuncsV2 = map[string]func(*matchersTree, ...string) error{ + "Host": hostV2, + "HostHeader": hostV2, + "HostRegexp": hostRegexpV2, + "ClientIP": clientIPV2, + "Path": pathV2, + "PathPrefix": pathPrefixV2, + "Method": methodsV2, + "Headers": headersV2, + "HeadersRegexp": headersRegexpV2, + "Query": queryV2, +} + +func pathV2(tree *matchersTree, paths ...string) error { + for _, path := range paths { + if !strings.HasPrefix(path, "/") { + return fmt.Errorf("path %q does not start with a '/'", path) + } + } + + tree.matcher = func(req *http.Request) bool { + for _, path := range paths { + if req.URL.Path == path { + return true + } + } + + return false + } + + return nil +} + +func pathPrefixV2(tree *matchersTree, paths ...string) error { + for _, path := range paths { + if !strings.HasPrefix(path, "/") { + return fmt.Errorf("path %q does not start with a '/'", path) + } + } + + tree.matcher = func(req *http.Request) bool { + for _, path := range paths { + if strings.HasPrefix(req.URL.Path, path) { + return true + } + } + + return false + } + + return nil +} + +func hostV2(tree *matchersTree, hosts ...string) error { + for i, host := range hosts { + if !IsASCII(host) { + return fmt.Errorf("invalid value %q for \"Host\" matcher, non-ASCII characters are not allowed", host) + } + + hosts[i] = strings.ToLower(host) + } + + tree.matcher = func(req *http.Request) bool { + reqHost := requestdecorator.GetCanonizedHost(req.Context()) + if len(reqHost) == 0 { + // If the request is an HTTP/1.0 request, then a Host may not be defined. + if req.ProtoAtLeast(1, 1) { + log.Ctx(req.Context()).Warn().Msgf("Could not retrieve CanonizedHost, rejecting %s", req.Host) + } + + return false + } + + flatH := requestdecorator.GetCNAMEFlatten(req.Context()) + if len(flatH) > 0 { + for _, host := range hosts { + if strings.EqualFold(reqHost, host) || strings.EqualFold(flatH, host) { + return true + } + log.Ctx(req.Context()).Debug().Msgf("CNAMEFlattening: request %s which resolved to %s, is not matched to route %s", reqHost, flatH, host) + } + return false + } + + for _, host := range hosts { + if reqHost == host { + return true + } + + // Check for match on trailing period on host + if last := len(host) - 1; last >= 0 && host[last] == '.' { + h := host[:last] + if reqHost == h { + return true + } + } + + // Check for match on trailing period on request + if last := len(reqHost) - 1; last >= 0 && reqHost[last] == '.' { + h := reqHost[:last] + if h == host { + return true + } + } + } + return false + } + + return nil +} + +func clientIPV2(tree *matchersTree, clientIPs ...string) error { + checker, err := ip.NewChecker(clientIPs) + if err != nil { + return fmt.Errorf("could not initialize IP Checker for \"ClientIP\" matcher: %w", err) + } + + strategy := ip.RemoteAddrStrategy{} + + tree.matcher = func(req *http.Request) bool { + ok, err := checker.Contains(strategy.GetIP(req)) + if err != nil { + log.Ctx(req.Context()).Warn().Err(err).Msg("\"ClientIP\" matcher: could not match remote address") + return false + } + + return ok + } + + return nil +} + +func methodsV2(tree *matchersTree, methods ...string) error { + route := mux.NewRouter().NewRoute() + route.Methods(methods...) + if err := route.GetError(); err != nil { + return err + } + + tree.matcher = func(req *http.Request) bool { + return route.Match(req, &mux.RouteMatch{}) + } + + return nil +} + +func headersV2(tree *matchersTree, headers ...string) error { + route := mux.NewRouter().NewRoute() + route.Headers(headers...) + if err := route.GetError(); err != nil { + return err + } + + tree.matcher = func(req *http.Request) bool { + return route.Match(req, &mux.RouteMatch{}) + } + + return nil +} + +func queryV2(tree *matchersTree, query ...string) error { + var queries []string + for _, elem := range query { + queries = append(queries, strings.SplitN(elem, "=", 2)...) + } + + route := mux.NewRouter().NewRoute() + route.Queries(queries...) + if err := route.GetError(); err != nil { + return err + } + + tree.matcher = func(req *http.Request) bool { + return route.Match(req, &mux.RouteMatch{}) + } + + return nil +} + +func hostRegexpV2(tree *matchersTree, hosts ...string) error { + router := mux.NewRouter() + + for _, host := range hosts { + if !IsASCII(host) { + return fmt.Errorf("invalid value %q for HostRegexp matcher, non-ASCII characters are not allowed", host) + } + + tmpRt := router.Host(host) + if tmpRt.GetError() != nil { + return tmpRt.GetError() + } + } + + tree.matcher = func(req *http.Request) bool { + return router.Match(req, &mux.RouteMatch{}) + } + + return nil +} + +func headersRegexpV2(tree *matchersTree, headers ...string) error { + route := mux.NewRouter().NewRoute() + route.HeadersRegexp(headers...) + if err := route.GetError(); err != nil { + return err + } + + tree.matcher = func(req *http.Request) bool { + return route.Match(req, &mux.RouteMatch{}) + } + + return nil +} diff --git a/pkg/muxer/http/matcher_v2_test.go b/pkg/muxer/http/matcher_v2_test.go new file mode 100644 index 000000000..eac62a005 --- /dev/null +++ b/pkg/muxer/http/matcher_v2_test.go @@ -0,0 +1,1535 @@ +package http + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/traefik/traefik/v3/pkg/middlewares/requestdecorator" + "github.com/traefik/traefik/v3/pkg/testhelpers" +) + +func TestClientIPV2Matcher(t *testing.T) { + testCases := []struct { + desc string + rule string + expected map[string]int + expectedError bool + }{ + { + desc: "invalid ClientIP matcher", + rule: "ClientIP(`1`)", + expectedError: true, + }, + { + desc: "invalid ClientIP matcher (no parameter)", + rule: "ClientIP()", + expectedError: true, + }, + { + desc: "invalid ClientIP matcher (empty parameter)", + rule: "ClientIP(``)", + expectedError: true, + }, + { + desc: "valid ClientIP matcher (many parameters)", + rule: "ClientIP(`127.0.0.1`, `192.168.1.0/24`)", + expected: map[string]int{ + "127.0.0.1": http.StatusOK, + "192.168.1.1": http.StatusOK, + }, + }, + { + desc: "valid ClientIP matcher", + rule: "ClientIP(`127.0.0.1`)", + expected: map[string]int{ + "127.0.0.1": http.StatusOK, + "192.168.1.1": http.StatusNotFound, + }, + }, + { + desc: "valid ClientIP matcher but invalid remote address", + rule: "ClientIP(`127.0.0.1`)", + expected: map[string]int{ + "1": http.StatusNotFound, + }, + }, + { + desc: "valid ClientIP matcher using CIDR", + rule: "ClientIP(`192.168.1.0/24`)", + expected: map[string]int{ + "192.168.1.1": http.StatusOK, + "192.168.1.100": http.StatusOK, + "192.168.2.1": http.StatusNotFound, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, "v2", 0, handler) + if test.expectedError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + results := make(map[string]int) + for remoteAddr := range test.expected { + w := httptest.NewRecorder() + + req := httptest.NewRequest(http.MethodGet, "https://example.com", http.NoBody) + req.RemoteAddr = remoteAddr + + muxer.ServeHTTP(w, req) + results[remoteAddr] = w.Code + } + assert.Equal(t, test.expected, results) + }) + } +} + +func TestMethodV2Matcher(t *testing.T) { + testCases := []struct { + desc string + rule string + expected map[string]int + expectedError bool + }{ + { + desc: "invalid Method matcher (no parameter)", + rule: "Method()", + expectedError: true, + }, + { + desc: "invalid Method matcher (empty parameter)", + rule: "Method(``)", + expectedError: true, + }, + { + desc: "valid Method matcher (many parameters)", + rule: "Method(`GET`, `POST`)", + expected: map[string]int{ + http.MethodGet: http.StatusOK, + http.MethodPost: http.StatusOK, + }, + }, + { + desc: "valid Method matcher", + rule: "Method(`GET`)", + expected: map[string]int{ + http.MethodGet: http.StatusOK, + http.MethodPost: http.StatusNotFound, + strings.ToLower(http.MethodGet): http.StatusNotFound, + }, + }, + { + desc: "valid Method matcher (lower case)", + rule: "Method(`get`)", + expected: map[string]int{ + http.MethodGet: http.StatusOK, + http.MethodPost: http.StatusNotFound, + strings.ToLower(http.MethodGet): http.StatusNotFound, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, "v2", 0, handler) + if test.expectedError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + results := make(map[string]int) + for method := range test.expected { + w := httptest.NewRecorder() + + req := httptest.NewRequest(method, "https://example.com", http.NoBody) + + muxer.ServeHTTP(w, req) + results[method] = w.Code + } + assert.Equal(t, test.expected, results) + }) + } +} + +func TestHostV2Matcher(t *testing.T) { + testCases := []struct { + desc string + rule string + expected map[string]int + expectedError bool + }{ + { + desc: "invalid Host matcher (no parameter)", + rule: "Host()", + expectedError: true, + }, + { + desc: "invalid Host matcher (empty parameter)", + rule: "Host(``)", + expectedError: true, + }, + { + desc: "invalid Host matcher (non-ASCII)", + rule: "Host(`🦭.com`)", + expectedError: true, + }, + { + desc: "valid Host matcher (many parameters)", + rule: "Host(`example.com`, `example.org`)", + expected: map[string]int{ + "https://example.com": http.StatusOK, + "https://example.com:8080": http.StatusOK, + "https://example.com/path": http.StatusOK, + "https://EXAMPLE.COM/path": http.StatusOK, + "https://example.org": http.StatusOK, + "https://example.org/path": http.StatusOK, + }, + }, + { + desc: "valid Host matcher", + rule: "Host(`example.com`)", + expected: map[string]int{ + "https://example.com": http.StatusOK, + "https://example.com:8080": http.StatusOK, + "https://example.com/path": http.StatusOK, + "https://EXAMPLE.COM/path": http.StatusOK, + "https://example.org": http.StatusNotFound, + "https://example.org/path": http.StatusNotFound, + }, + }, + { + desc: "valid Host matcher - matcher ending with a dot", + rule: "Host(`example.com.`)", + expected: map[string]int{ + "https://example.com": http.StatusOK, + "https://example.com/path": http.StatusOK, + "https://example.org": http.StatusNotFound, + "https://example.org/path": http.StatusNotFound, + "https://example.com.": http.StatusOK, + "https://example.com./path": http.StatusOK, + "https://example.org.": http.StatusNotFound, + "https://example.org./path": http.StatusNotFound, + }, + }, + { + desc: "valid Host matcher - URL ending with a dot", + rule: "Host(`example.com`)", + expected: map[string]int{ + "https://example.com.": http.StatusOK, + "https://example.com./path": http.StatusOK, + "https://example.org.": http.StatusNotFound, + "https://example.org./path": http.StatusNotFound, + }, + }, + { + desc: "valid Host matcher - matcher with UPPER case", + rule: "Host(`EXAMPLE.COM`)", + expected: map[string]int{ + "https://example.com": http.StatusOK, + "https://example.com/path": http.StatusOK, + "https://example.org": http.StatusNotFound, + "https://example.org/path": http.StatusNotFound, + }, + }, + { + desc: "valid Host matcher - puny-coded emoji", + rule: "Host(`xn--9t9h.com`)", + expected: map[string]int{ + "https://xn--9t9h.com": http.StatusOK, + "https://xn--9t9h.com/path": http.StatusOK, + "https://example.com": http.StatusNotFound, + "https://example.com/path": http.StatusNotFound, + // The request's sender must use puny-code. + "https://🦭.com": http.StatusNotFound, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, "v2", 0, handler) + if test.expectedError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + // RequestDecorator is necessary for the Host matcher + reqHost := requestdecorator.New(nil) + + results := make(map[string]int) + for calledURL := range test.expected { + w := httptest.NewRecorder() + + req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody) + + reqHost.ServeHTTP(w, req, muxer.ServeHTTP) + results[calledURL] = w.Code + } + assert.Equal(t, test.expected, results) + }) + } +} + +func TestHostRegexpV2Matcher(t *testing.T) { + testCases := []struct { + desc string + rule string + expected map[string]int + expectedError bool + }{ + { + desc: "invalid HostRegexp matcher (no parameter)", + rule: "HostRegexp()", + expectedError: true, + }, + { + desc: "invalid HostRegexp matcher (empty parameter)", + rule: "HostRegexp(``)", + expectedError: true, + }, + { + desc: "invalid HostRegexp matcher (non-ASCII)", + rule: "HostRegexp(`🦭.com`)", + expectedError: true, + }, + { + desc: "valid HostRegexp matcher (invalid regexp)", + rule: "HostRegexp(`(example.com`)", + // This is weird. + expectedError: false, + expected: map[string]int{ + "https://example.com": http.StatusNotFound, + "https://example.com:8080": http.StatusNotFound, + "https://example.com/path": http.StatusNotFound, + "https://example.org": http.StatusNotFound, + "https://example.org/path": http.StatusNotFound, + }, + }, + { + desc: "valid HostRegexp matcher (many parameters)", + rule: "HostRegexp(`example.com`, `example.org`)", + expected: map[string]int{ + "https://example.com": http.StatusOK, + "https://example.com:8080": http.StatusOK, + "https://example.com/path": http.StatusOK, + "https://example.org": http.StatusOK, + "https://example.org/path": http.StatusOK, + }, + }, + { + desc: "valid HostRegexp matcher with case sensitive regexp", + rule: "HostRegexp(`^[A-Z]+\\.com$`)", + expected: map[string]int{ + "https://example.com": http.StatusNotFound, + "https://EXAMPLE.com": http.StatusNotFound, + "https://example.com/path": http.StatusNotFound, + "https://example.org": http.StatusNotFound, + "https://example.org/path": http.StatusNotFound, + }, + }, + { + desc: "valid HostRegexp matcher with Traefik v2 syntax", + rule: "HostRegexp(`{domain:[a-zA-Z-]+\\.com}`)", + expected: map[string]int{ + "https://example.com": http.StatusOK, + "https://example.com/path": http.StatusOK, + "https://example.org": http.StatusNotFound, + "https://example.org/path": http.StatusNotFound, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, "v2", 0, handler) + if test.expectedError { + require.Error(t, err) + return + } + require.NoError(t, err) + + // RequestDecorator is necessary for the HostRegexp matcher + reqHost := requestdecorator.New(nil) + + results := make(map[string]int) + for calledURL := range test.expected { + w := httptest.NewRecorder() + + req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody) + + reqHost.ServeHTTP(w, req, muxer.ServeHTTP) + results[calledURL] = w.Code + } + assert.Equal(t, test.expected, results) + }) + } +} + +func TestPathV2Matcher(t *testing.T) { + testCases := []struct { + desc string + rule string + expected map[string]int + expectedError bool + }{ + { + desc: "invalid Path matcher (no parameter)", + rule: "Path()", + expectedError: true, + }, + { + desc: "invalid Path matcher (empty parameter)", + rule: "Path(``)", + expectedError: true, + }, + { + desc: "invalid Path matcher (no leading /)", + rule: "Path(`css`)", + expectedError: true, + }, + { + desc: "valid Path matcher (many parameters)", + rule: "Path(`/css`, `/js`)", + expected: map[string]int{ + "https://example.com": http.StatusNotFound, + "https://example.com/html": http.StatusNotFound, + "https://example.org/css": http.StatusOK, + "https://example.com/css": http.StatusOK, + "https://example.com/css/": http.StatusNotFound, + "https://example.com/css/main.css": http.StatusNotFound, + "https://example.com/js": http.StatusOK, + "https://example.com/js/main.js": http.StatusNotFound, + }, + }, + { + desc: "valid Path matcher", + rule: "Path(`/css`)", + expected: map[string]int{ + "https://example.com": http.StatusNotFound, + "https://example.com/html": http.StatusNotFound, + "https://example.org/css": http.StatusOK, + "https://example.com/css": http.StatusOK, + "https://example.com/css/": http.StatusNotFound, + "https://example.com/css/main.css": http.StatusNotFound, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, "v2", 0, handler) + if test.expectedError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + results := make(map[string]int) + for calledURL := range test.expected { + w := httptest.NewRecorder() + + req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody) + + muxer.ServeHTTP(w, req) + results[calledURL] = w.Code + } + assert.Equal(t, test.expected, results) + }) + } +} + +func TestPathPrefixV2Matcher(t *testing.T) { + testCases := []struct { + desc string + rule string + expected map[string]int + expectedError bool + }{ + { + desc: "invalid PathPrefix matcher (no parameter)", + rule: "PathPrefix()", + expectedError: true, + }, + { + desc: "invalid PathPrefix matcher (empty parameter)", + rule: "PathPrefix(``)", + expectedError: true, + }, + { + desc: "invalid PathPrefix matcher (no leading /)", + rule: "PathPrefix(`css`)", + expectedError: true, + }, + { + desc: "valid PathPrefix matcher (many parameters)", + rule: "PathPrefix(`/css`, `/js`)", + expected: map[string]int{ + "https://example.com": http.StatusNotFound, + "https://example.com/html": http.StatusNotFound, + "https://example.org/css": http.StatusOK, + "https://example.com/css": http.StatusOK, + "https://example.com/css/": http.StatusOK, + "https://example.com/css/main.css": http.StatusOK, + "https://example.com/js/": http.StatusOK, + "https://example.com/js/main.js": http.StatusOK, + }, + }, + { + desc: "valid PathPrefix matcher", + rule: `PathPrefix("/css")`, + expected: map[string]int{ + "https://example.com": http.StatusNotFound, + "https://example.com/html": http.StatusNotFound, + "https://example.org/css": http.StatusOK, + "https://example.com/css": http.StatusOK, + "https://example.com/css/": http.StatusOK, + "https://example.com/css/main.css": http.StatusOK, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, "v2", 0, handler) + if test.expectedError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + results := make(map[string]int) + for calledURL := range test.expected { + w := httptest.NewRecorder() + + req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody) + + muxer.ServeHTTP(w, req) + results[calledURL] = w.Code + } + assert.Equal(t, test.expected, results) + }) + } +} + +func TestHeadersMatcher(t *testing.T) { + testCases := []struct { + desc string + rule string + expected map[*http.Header]int + expectedError bool + }{ + { + desc: "invalid Header matcher (no parameter)", + rule: "Headers()", + expectedError: true, + }, + { + desc: "invalid Header matcher (missing value parameter)", + rule: "Headers(`X-Forwarded-Host`)", + expectedError: true, + }, + { + desc: "invalid Header matcher (missing value parameter)", + rule: "Headers(`X-Forwarded-Host`, ``)", + expectedError: true, + }, + { + desc: "invalid Header matcher (missing key parameter)", + rule: "Headers(``, `example.com`)", + expectedError: true, + }, + { + desc: "invalid Header matcher (too many parameters)", + rule: "Headers(`X-Forwarded-Host`, `example.com`, `example.org`)", + expectedError: true, + }, + { + desc: "valid Header matcher", + rule: "Headers(`X-Forwarded-Proto`, `https`)", + expected: map[*http.Header]int{ + {"X-Forwarded-Proto": []string{"https"}}: http.StatusOK, + {"x-forwarded-proto": []string{"https"}}: http.StatusNotFound, + {"X-Forwarded-Proto": []string{"http", "https"}}: http.StatusOK, + {"X-Forwarded-Proto": []string{"https", "http"}}: http.StatusOK, + {"X-Forwarded-Host": []string{"example.com"}}: http.StatusNotFound, + }, + }, + { + desc: "valid Header matcher (non-canonical form)", + rule: "Headers(`x-forwarded-proto`, `https`)", + expected: map[*http.Header]int{ + {"X-Forwarded-Proto": []string{"https"}}: http.StatusOK, + {"x-forwarded-proto": []string{"https"}}: http.StatusNotFound, + {"X-Forwarded-Proto": []string{"http", "https"}}: http.StatusOK, + {"X-Forwarded-Proto": []string{"https", "http"}}: http.StatusOK, + {"X-Forwarded-Host": []string{"example.com"}}: http.StatusNotFound, + }, + }, + } + + for _, test := range testCases { + test := test + + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, "v2", 0, handler) + if test.expectedError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + for headers := range test.expected { + w := httptest.NewRecorder() + + req := httptest.NewRequest(http.MethodGet, "https://example.com", http.NoBody) + req.Header = *headers + + muxer.ServeHTTP(w, req) + assert.Equal(t, test.expected[headers], w.Code, headers) + } + }) + } +} + +func TestHeaderRegexpV2Matcher(t *testing.T) { + testCases := []struct { + desc string + rule string + expected map[*http.Header]int + expectedError bool + }{ + { + desc: "invalid HeaderRegexp matcher (no parameter)", + rule: "HeaderRegexp()", + expectedError: true, + }, + { + desc: "invalid HeaderRegexp matcher (missing value parameter)", + rule: "HeadersRegexp(`X-Forwarded-Host`)", + expectedError: true, + }, + { + desc: "invalid HeaderRegexp matcher (missing value parameter)", + rule: "HeadersRegexp(`X-Forwarded-Host`, ``)", + expectedError: true, + }, + { + desc: "invalid HeaderRegexp matcher (missing key parameter)", + rule: "HeadersRegexp(``, `example.com`)", + expectedError: true, + }, + { + desc: "invalid HeaderRegexp matcher (invalid regexp)", + rule: "HeadersRegexp(`X-Forwarded-Host`,`(example.com`)", + expectedError: true, + }, + { + desc: "invalid HeaderRegexp matcher (too many parameters)", + rule: "HeadersRegexp(`X-Forwarded-Host`, `example.com`, `example.org`)", + expectedError: true, + }, + { + desc: "valid HeaderRegexp matcher", + rule: "HeadersRegexp(`X-Forwarded-Proto`, `^https?$`)", + expected: map[*http.Header]int{ + {"X-Forwarded-Proto": []string{"http"}}: http.StatusOK, + {"x-forwarded-proto": []string{"http"}}: http.StatusNotFound, + {"X-Forwarded-Proto": []string{"https"}}: http.StatusOK, + {"X-Forwarded-Proto": []string{"HTTPS"}}: http.StatusNotFound, + {"X-Forwarded-Proto": []string{"ws", "https"}}: http.StatusOK, + {"X-Forwarded-Host": []string{"example.com"}}: http.StatusNotFound, + }, + }, + { + desc: "valid HeaderRegexp matcher (non-canonical form)", + rule: "HeadersRegexp(`x-forwarded-proto`, `^https?$`)", + expected: map[*http.Header]int{ + {"X-Forwarded-Proto": []string{"http"}}: http.StatusOK, + {"x-forwarded-proto": []string{"http"}}: http.StatusNotFound, + {"X-Forwarded-Proto": []string{"https"}}: http.StatusOK, + {"X-Forwarded-Proto": []string{"HTTPS"}}: http.StatusNotFound, + {"X-Forwarded-Proto": []string{"ws", "https"}}: http.StatusOK, + {"X-Forwarded-Host": []string{"example.com"}}: http.StatusNotFound, + }, + }, + { + desc: "valid HeaderRegexp matcher with Traefik v2 syntax", + rule: "HeadersRegexp(`X-Forwarded-Proto`, `http{secure:s?}`)", + expected: map[*http.Header]int{ + {"X-Forwarded-Proto": []string{"http"}}: http.StatusNotFound, + {"X-Forwarded-Proto": []string{"https"}}: http.StatusNotFound, + {"X-Forwarded-Proto": []string{"http{secure:}"}}: http.StatusOK, + {"X-Forwarded-Proto": []string{"HTTP{secure:}"}}: http.StatusNotFound, + {"X-Forwarded-Proto": []string{"http{secure:s}"}}: http.StatusOK, + {"X-Forwarded-Proto": []string{"http{secure:S}"}}: http.StatusNotFound, + {"X-Forwarded-Proto": []string{"HTTPS"}}: http.StatusNotFound, + {"X-Forwarded-Proto": []string{"ws", "http{secure:s}"}}: http.StatusOK, + {"X-Forwarded-Host": []string{"example.com"}}: http.StatusNotFound, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, "v2", 0, handler) + if test.expectedError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + for headers := range test.expected { + w := httptest.NewRecorder() + + req := httptest.NewRequest(http.MethodGet, "https://example.com", http.NoBody) + req.Header = *headers + + muxer.ServeHTTP(w, req) + assert.Equal(t, test.expected[headers], w.Code, *headers) + } + }) + } +} + +func TestHostRegexp(t *testing.T) { + testCases := []struct { + desc string + hostExp string + urls map[string]int + }{ + { + desc: "capturing group", + hostExp: "HostRegexp(`{subdomain:(foo\\.)?bar\\.com}`)", + urls: map[string]int{ + "http://foo.bar.com": http.StatusOK, + "http://bar.com": http.StatusOK, + "http://fooubar.com": http.StatusNotFound, + "http://barucom": http.StatusNotFound, + "http://barcom": http.StatusNotFound, + }, + }, + { + desc: "non capturing group", + hostExp: "HostRegexp(`{subdomain:(?:foo\\.)?bar\\.com}`)", + urls: map[string]int{ + "http://foo.bar.com": http.StatusOK, + "http://bar.com": http.StatusOK, + "http://fooubar.com": http.StatusNotFound, + "http://barucom": http.StatusNotFound, + "http://barcom": http.StatusNotFound, + }, + }, + { + desc: "regex insensitive", + hostExp: "HostRegexp(`{dummy:[A-Za-z-]+\\.bar\\.com}`)", + urls: map[string]int{ + "http://FOO.bar.com": http.StatusOK, + "http://foo.bar.com": http.StatusOK, + "http://fooubar.com": http.StatusNotFound, + "http://barucom": http.StatusNotFound, + "http://barcom": http.StatusNotFound, + }, + }, + { + desc: "insensitive host", + hostExp: "HostRegexp(`{dummy:[a-z-]+\\.bar\\.com}`)", + urls: map[string]int{ + "http://FOO.bar.com": http.StatusOK, + "http://foo.bar.com": http.StatusOK, + "http://fooubar.com": http.StatusNotFound, + "http://barucom": http.StatusNotFound, + "http://barcom": http.StatusNotFound, + }, + }, + { + desc: "insensitive host simple", + hostExp: "HostRegexp(`foo.bar.com`)", + urls: map[string]int{ + "http://FOO.bar.com": http.StatusOK, + "http://foo.bar.com": http.StatusOK, + "http://fooubar.com": http.StatusNotFound, + "http://barucom": http.StatusNotFound, + "http://barcom": http.StatusNotFound, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.hostExp, "v2", 0, handler) + require.NoError(t, err) + + results := make(map[string]int) + for calledURL := range test.urls { + w := httptest.NewRecorder() + + req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody) + + muxer.ServeHTTP(w, req) + results[calledURL] = w.Code + } + assert.Equal(t, test.urls, results) + }) + } +} + +// This test is a copy from the v2 branch mux_test.go file. +func Test_addRoute(t *testing.T) { + testCases := []struct { + desc string + rule string + headers map[string]string + remoteAddr string + expected map[string]int + expectedError bool + }{ + { + desc: "no tree", + expectedError: true, + }, + { + desc: "Rule with no matcher", + rule: "rulewithnotmatcher", + expectedError: true, + }, + { + desc: "Host empty", + rule: "Host(``)", + expectedError: true, + }, + { + desc: "PathPrefix empty", + rule: "PathPrefix(``)", + expectedError: true, + }, + { + desc: "PathPrefix", + rule: "PathPrefix(`/foo`)", + expected: map[string]int{ + "http://localhost/foo": http.StatusOK, + }, + }, + { + desc: "wrong PathPrefix", + rule: "PathPrefix(`/bar`)", + expected: map[string]int{ + "http://localhost/foo": http.StatusNotFound, + }, + }, + { + desc: "Host", + rule: "Host(`localhost`)", + expected: map[string]int{ + "http://localhost/foo": http.StatusOK, + }, + }, + { + desc: "Host IPv4", + rule: "Host(`127.0.0.1`)", + expected: map[string]int{ + "http://127.0.0.1/foo": http.StatusOK, + }, + }, + { + desc: "Host IPv6", + rule: "Host(`10::10`)", + expected: map[string]int{ + "http://10::10/foo": http.StatusOK, + }, + }, + { + desc: "Non-ASCII Host", + rule: "Host(`locàlhost`)", + expectedError: true, + }, + { + desc: "Non-ASCII HostRegexp", + rule: "HostRegexp(`locàlhost`)", + expectedError: true, + }, + { + desc: "HostHeader equivalent to Host", + rule: "HostHeader(`localhost`)", + expected: map[string]int{ + "http://localhost/foo": http.StatusOK, + "http://bar/foo": http.StatusNotFound, + }, + }, + { + desc: "Host with trailing period in rule", + rule: "Host(`localhost.`)", + expected: map[string]int{ + "http://localhost/foo": http.StatusOK, + }, + }, + { + desc: "Host with trailing period in domain", + rule: "Host(`localhost`)", + expected: map[string]int{ + "http://localhost./foo": http.StatusOK, + }, + }, + { + desc: "Host with trailing period in domain and rule", + rule: "Host(`localhost.`)", + expected: map[string]int{ + "http://localhost./foo": http.StatusOK, + }, + }, + { + desc: "wrong Host", + rule: "Host(`nope`)", + expected: map[string]int{ + "http://localhost/foo": http.StatusNotFound, + }, + }, + { + desc: "Host and PathPrefix", + rule: "Host(`localhost`) && PathPrefix(`/foo`)", + expected: map[string]int{ + "http://localhost/foo": http.StatusOK, + }, + }, + { + desc: "Host and PathPrefix wrong PathPrefix", + rule: "Host(`localhost`) && PathPrefix(`/bar`)", + expected: map[string]int{ + "http://localhost/foo": http.StatusNotFound, + }, + }, + { + desc: "Host and PathPrefix wrong Host", + rule: "Host(`nope`) && PathPrefix(`/foo`)", + expected: map[string]int{ + "http://localhost/foo": http.StatusNotFound, + }, + }, + { + desc: "Host and PathPrefix Host OR, first host", + rule: "Host(`nope`,`localhost`) && PathPrefix(`/foo`)", + expected: map[string]int{ + "http://localhost/foo": http.StatusOK, + }, + }, + { + desc: "Host and PathPrefix Host OR, second host", + rule: "Host(`nope`,`localhost`) && PathPrefix(`/foo`)", + expected: map[string]int{ + "http://nope/foo": http.StatusOK, + }, + }, + { + desc: "Host and PathPrefix Host OR, first host and wrong PathPrefix", + rule: "Host(`nope,localhost`) && PathPrefix(`/bar`)", + expected: map[string]int{ + "http://localhost/foo": http.StatusNotFound, + }, + }, + { + desc: "HostRegexp with capturing group", + rule: "HostRegexp(`{subdomain:(foo\\.)?bar\\.com}`)", + expected: map[string]int{ + "http://foo.bar.com": http.StatusOK, + "http://bar.com": http.StatusOK, + "http://fooubar.com": http.StatusNotFound, + "http://barucom": http.StatusNotFound, + "http://barcom": http.StatusNotFound, + }, + }, + { + desc: "HostRegexp with non capturing group", + rule: "HostRegexp(`{subdomain:(?:foo\\.)?bar\\.com}`)", + expected: map[string]int{ + "http://foo.bar.com": http.StatusOK, + "http://bar.com": http.StatusOK, + "http://fooubar.com": http.StatusNotFound, + "http://barucom": http.StatusNotFound, + "http://barcom": http.StatusNotFound, + }, + }, + { + desc: "Methods with GET", + rule: "Method(`GET`)", + expected: map[string]int{ + "http://localhost/foo": http.StatusOK, + }, + }, + { + desc: "Methods with GET and POST", + rule: "Method(`GET`,`POST`)", + expected: map[string]int{ + "http://localhost/foo": http.StatusOK, + }, + }, + { + desc: "Methods with POST", + rule: "Method(`POST`)", + expected: map[string]int{ + // On v2 this test expect a http.StatusMethodNotAllowed status code. + // This was due to a custom behavior of mux https://github.com/containous/mux/blob/b2dd784e613f218225150a5e8b5742c5733bc1b6/mux.go#L130-L132. + // Unfortunately, this behavior cannot be ported easily due to the matcher func signature. + "http://localhost/foo": http.StatusNotFound, + }, + }, + { + desc: "Header with matching header", + rule: "Headers(`Content-Type`,`application/json`)", + headers: map[string]string{ + "Content-Type": "application/json", + }, + expected: map[string]int{ + "http://localhost/foo": http.StatusOK, + }, + }, + { + desc: "Header without matching header", + rule: "Headers(`Content-Type`,`application/foo`)", + headers: map[string]string{ + "Content-Type": "application/json", + }, + expected: map[string]int{ + "http://localhost/foo": http.StatusNotFound, + }, + }, + { + desc: "HeaderRegExp with matching header", + rule: "HeadersRegexp(`Content-Type`, `application/(text|json)`)", + headers: map[string]string{ + "Content-Type": "application/json", + }, + expected: map[string]int{ + "http://localhost/foo": http.StatusOK, + }, + }, + { + desc: "HeaderRegExp without matching header", + rule: "HeadersRegexp(`Content-Type`, `application/(text|json)`)", + headers: map[string]string{ + "Content-Type": "application/foo", + }, + expected: map[string]int{ + "http://localhost/foo": http.StatusNotFound, + }, + }, + { + desc: "HeaderRegExp with matching second header", + rule: "HeadersRegexp(`Content-Type`, `application/(text|json)`)", + headers: map[string]string{ + "Content-Type": "application/text", + }, + expected: map[string]int{ + "http://localhost/foo": http.StatusOK, + }, + }, + { + desc: "Query with multiple params", + rule: "Query(`foo=bar`, `bar=baz`)", + expected: map[string]int{ + "http://localhost/foo?foo=bar&bar=baz": http.StatusOK, + "http://localhost/foo?bar=baz": http.StatusNotFound, + }, + }, + { + desc: "Query with multiple equals", + rule: "Query(`foo=b=ar`)", + expected: map[string]int{ + "http://localhost/foo?foo=b=ar": http.StatusOK, + "http://localhost/foo?foo=bar": http.StatusNotFound, + }, + }, + { + desc: "Rule with simple path", + rule: `Path("/a")`, + expected: map[string]int{ + "http://plop/a": http.StatusOK, + }, + }, + { + desc: `Rule with a simple host`, + rule: `Host("plop")`, + expected: map[string]int{ + "http://plop": http.StatusOK, + }, + }, + { + desc: "Rule with Path AND Host", + rule: `Path("/a") && Host("plop")`, + expected: map[string]int{ + "http://plop/a": http.StatusOK, + "http://plopi/a": http.StatusNotFound, + }, + }, + { + desc: "Rule with Host OR Host", + rule: `Host("tchouk") || Host("pouet")`, + expected: map[string]int{ + "http://tchouk/toto": http.StatusOK, + "http://pouet/a": http.StatusOK, + "http://plopi/a": http.StatusNotFound, + }, + }, + { + desc: "Rule with host OR (host AND path)", + rule: `Host("tchouk") || (Host("pouet") && Path("/powpow"))`, + expected: map[string]int{ + "http://tchouk/toto": http.StatusOK, + "http://tchouk/powpow": http.StatusOK, + "http://pouet/powpow": http.StatusOK, + "http://pouet/toto": http.StatusNotFound, + "http://plopi/a": http.StatusNotFound, + }, + }, + { + desc: "Rule with host OR host AND path", + rule: `Host("tchouk") || Host("pouet") && Path("/powpow")`, + expected: map[string]int{ + "http://tchouk/toto": http.StatusOK, + "http://tchouk/powpow": http.StatusOK, + "http://pouet/powpow": http.StatusOK, + "http://pouet/toto": http.StatusNotFound, + "http://plopi/a": http.StatusNotFound, + }, + }, + { + desc: "Rule with (host OR host) AND path", + rule: `(Host("tchouk") || Host("pouet")) && Path("/powpow")`, + expected: map[string]int{ + "http://tchouk/toto": http.StatusNotFound, + "http://tchouk/powpow": http.StatusOK, + "http://pouet/powpow": http.StatusOK, + "http://pouet/toto": http.StatusNotFound, + "http://plopi/a": http.StatusNotFound, + }, + }, + { + desc: "Rule with multiple host AND path", + rule: `(Host("tchouk","pouet")) && Path("/powpow")`, + expected: map[string]int{ + "http://tchouk/toto": http.StatusNotFound, + "http://tchouk/powpow": http.StatusOK, + "http://pouet/powpow": http.StatusOK, + "http://pouet/toto": http.StatusNotFound, + "http://plopi/a": http.StatusNotFound, + }, + }, + { + desc: "Rule with multiple host AND multiple path", + rule: `Host("tchouk","pouet") && Path("/powpow", "/titi")`, + expected: map[string]int{ + "http://tchouk/toto": http.StatusNotFound, + "http://tchouk/powpow": http.StatusOK, + "http://pouet/powpow": http.StatusOK, + "http://tchouk/titi": http.StatusOK, + "http://pouet/titi": http.StatusOK, + "http://pouet/toto": http.StatusNotFound, + "http://plopi/a": http.StatusNotFound, + }, + }, + { + desc: "Rule with (host AND path) OR (host AND path)", + rule: `(Host("tchouk") && Path("/titi")) || ((Host("pouet")) && Path("/powpow"))`, + expected: map[string]int{ + "http://tchouk/titi": http.StatusOK, + "http://tchouk/powpow": http.StatusNotFound, + "http://pouet/powpow": http.StatusOK, + "http://pouet/toto": http.StatusNotFound, + "http://plopi/a": http.StatusNotFound, + }, + }, + { + desc: "Rule without quote", + rule: `Host(tchouk)`, + expectedError: true, + }, + { + desc: "Rule case UPPER", + rule: `(HOST("tchouk") && PATHPREFIX("/titi"))`, + expected: map[string]int{ + "http://tchouk/titi": http.StatusOK, + "http://tchouk/powpow": http.StatusNotFound, + }, + }, + { + desc: "Rule case lower", + rule: `(host("tchouk") && pathprefix("/titi"))`, + expected: map[string]int{ + "http://tchouk/titi": http.StatusOK, + "http://tchouk/powpow": http.StatusNotFound, + }, + }, + { + desc: "Rule case CamelCase", + rule: `(Host("tchouk") && PathPrefix("/titi"))`, + expected: map[string]int{ + "http://tchouk/titi": http.StatusOK, + "http://tchouk/powpow": http.StatusNotFound, + }, + }, + { + desc: "Rule case Title", + rule: `(Host("tchouk") && Pathprefix("/titi"))`, + expected: map[string]int{ + "http://tchouk/titi": http.StatusOK, + "http://tchouk/powpow": http.StatusNotFound, + }, + }, + { + desc: "Rule Path with error", + rule: `Path("titi")`, + expectedError: true, + }, + { + desc: "Rule PathPrefix with error", + rule: `PathPrefix("titi")`, + expectedError: true, + }, + { + desc: "Rule HostRegexp with error", + rule: `HostRegexp("{test")`, + expectedError: true, + }, + { + desc: "Rule Headers with error", + rule: `Headers("titi")`, + expectedError: true, + }, + { + desc: "Rule HeadersRegexp with error", + rule: `HeadersRegexp("titi")`, + expectedError: true, + }, + { + desc: "Rule Query", + rule: `Query("titi")`, + expectedError: true, + }, + { + desc: "Rule Query with bad syntax", + rule: `Query("titi={test")`, + expectedError: true, + }, + { + desc: "Rule with Path without args", + rule: `Host("tchouk") && Path()`, + expectedError: true, + }, + { + desc: "Rule with an empty path", + rule: `Host("tchouk") && Path("")`, + expectedError: true, + }, + { + desc: "Rule with an empty path", + rule: `Host("tchouk") && Path("", "/titi")`, + expectedError: true, + }, + { + desc: "Rule with not", + rule: `!Host("tchouk")`, + expected: map[string]int{ + "http://tchouk/titi": http.StatusNotFound, + "http://test/powpow": http.StatusOK, + }, + }, + { + desc: "Rule with not on Path", + rule: `!Path("/titi")`, + expected: map[string]int{ + "http://tchouk/titi": http.StatusNotFound, + "http://tchouk/powpow": http.StatusOK, + }, + }, + { + desc: "Rule with not on multiple route with or", + rule: `!(Host("tchouk") || Host("toto"))`, + expected: map[string]int{ + "http://tchouk/titi": http.StatusNotFound, + "http://toto/powpow": http.StatusNotFound, + "http://test/powpow": http.StatusOK, + }, + }, + { + desc: "Rule with not on multiple route with and", + rule: `!(Host("tchouk") && Path("/titi"))`, + expected: map[string]int{ + "http://tchouk/titi": http.StatusNotFound, + "http://tchouk/toto": http.StatusOK, + "http://test/titi": http.StatusOK, + }, + }, + { + desc: "Rule with not on multiple route with and another not", + rule: `!(Host("tchouk") && !Path("/titi"))`, + expected: map[string]int{ + "http://tchouk/titi": http.StatusOK, + "http://toto/titi": http.StatusOK, + "http://tchouk/toto": http.StatusNotFound, + }, + }, + { + desc: "Rule with not on two rule", + rule: `!Host("tchouk") || !Path("/titi")`, + expected: map[string]int{ + "http://tchouk/titi": http.StatusNotFound, + "http://tchouk/toto": http.StatusOK, + "http://test/titi": http.StatusOK, + }, + }, + { + desc: "Rule case with double not", + rule: `!(!(Host("tchouk") && Pathprefix("/titi")))`, + expected: map[string]int{ + "http://tchouk/titi": http.StatusOK, + "http://tchouk/powpow": http.StatusNotFound, + "http://test/titi": http.StatusNotFound, + }, + }, + { + desc: "Rule case with not domain", + rule: `!Host("tchouk") && Pathprefix("/titi")`, + expected: map[string]int{ + "http://tchouk/titi": http.StatusNotFound, + "http://tchouk/powpow": http.StatusNotFound, + "http://toto/powpow": http.StatusNotFound, + "http://toto/titi": http.StatusOK, + }, + }, + { + desc: "Rule with multiple host AND multiple path AND not", + rule: `!(Host("tchouk","pouet") && Path("/powpow", "/titi"))`, + expected: map[string]int{ + "http://tchouk/toto": http.StatusOK, + "http://tchouk/powpow": http.StatusNotFound, + "http://pouet/powpow": http.StatusNotFound, + "http://tchouk/titi": http.StatusNotFound, + "http://pouet/titi": http.StatusNotFound, + "http://pouet/toto": http.StatusOK, + "http://plopi/a": http.StatusOK, + }, + }, + { + desc: "ClientIP empty", + rule: "ClientIP(``)", + expectedError: true, + }, + { + desc: "Invalid ClientIP", + rule: "ClientIP(`invalid`)", + expectedError: true, + }, + { + desc: "Non matching ClientIP", + rule: "ClientIP(`10.10.1.1`)", + remoteAddr: "10.0.0.0", + expected: map[string]int{ + "http://tchouk/toto": http.StatusNotFound, + }, + }, + { + desc: "Non matching IPv6", + rule: "ClientIP(`10::10`)", + remoteAddr: "::1", + expected: map[string]int{ + "http://tchouk/toto": http.StatusNotFound, + }, + }, + { + desc: "Matching IP", + rule: "ClientIP(`10.0.0.0`)", + remoteAddr: "10.0.0.0:8456", + expected: map[string]int{ + "http://tchouk/toto": http.StatusOK, + }, + }, + { + desc: "Matching IPv6", + rule: "ClientIP(`10::10`)", + remoteAddr: "10::10", + expected: map[string]int{ + "http://tchouk/toto": http.StatusOK, + }, + }, + { + desc: "Matching IP among several IP", + rule: "ClientIP(`10.0.0.1`, `10.0.0.0`)", + remoteAddr: "10.0.0.0", + expected: map[string]int{ + "http://tchouk/toto": http.StatusOK, + }, + }, + { + desc: "Non Matching IP with CIDR", + rule: "ClientIP(`11.0.0.0/24`)", + remoteAddr: "10.0.0.0", + expected: map[string]int{ + "http://tchouk/toto": http.StatusNotFound, + }, + }, + { + desc: "Non Matching IPv6 with CIDR", + rule: "ClientIP(`11::/16`)", + remoteAddr: "10::", + expected: map[string]int{ + "http://tchouk/toto": http.StatusNotFound, + }, + }, + { + desc: "Matching IP with CIDR", + rule: "ClientIP(`10.0.0.0/16`)", + remoteAddr: "10.0.0.0", + expected: map[string]int{ + "http://tchouk/toto": http.StatusOK, + }, + }, + { + desc: "Matching IPv6 with CIDR", + rule: "ClientIP(`10::/16`)", + remoteAddr: "10::10", + expected: map[string]int{ + "http://tchouk/toto": http.StatusOK, + }, + }, + { + desc: "Matching IP among several CIDR", + rule: "ClientIP(`11.0.0.0/16`, `10.0.0.0/16`)", + remoteAddr: "10.0.0.0", + expected: map[string]int{ + "http://tchouk/toto": http.StatusOK, + }, + }, + { + desc: "Matching IP among non matching CIDR and matching IP", + rule: "ClientIP(`11.0.0.0/16`, `10.0.0.0`)", + remoteAddr: "10.0.0.0", + expected: map[string]int{ + "http://tchouk/toto": http.StatusOK, + }, + }, + { + desc: "Matching IP among matching CIDR and non matching IP", + rule: "ClientIP(`11.0.0.0`, `10.0.0.0/16`)", + remoteAddr: "10.0.0.0", + expected: map[string]int{ + "http://tchouk/toto": http.StatusOK, + }, + }, + } + + for _, test := range testCases { + test := test + + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, "v2", 0, handler) + if test.expectedError { + require.Error(t, err) + } else { + require.NoError(t, err) + + // RequestDecorator is necessary for the hostV2 rule + reqHost := requestdecorator.New(nil) + + results := make(map[string]int) + for calledURL := range test.expected { + w := httptest.NewRecorder() + + req := testhelpers.MustNewRequest(http.MethodGet, calledURL, nil) + + // Useful for the ClientIP matcher + req.RemoteAddr = test.remoteAddr + + for key, value := range test.headers { + req.Header.Set(key, value) + } + reqHost.ServeHTTP(w, req, muxer.ServeHTTP) + results[calledURL] = w.Code + } + assert.Equal(t, test.expected, results) + } + }) + } +} diff --git a/pkg/muxer/http/mux.go b/pkg/muxer/http/mux.go index 979634aa1..1d84a1433 100644 --- a/pkg/muxer/http/mux.go +++ b/pkg/muxer/http/mux.go @@ -12,8 +12,10 @@ import ( // Muxer handles routing with rules. type Muxer struct { - routes routes - parser predicate.Parser + routes routes + parser predicate.Parser + parserV2 predicate.Parser + defaultHandler http.Handler } // NewMuxer returns a new muxer instance. @@ -28,8 +30,20 @@ func NewMuxer() (*Muxer, error) { return nil, fmt.Errorf("error while creating parser: %w", err) } + var matchersV2 []string + for matcher := range httpFuncsV2 { + matchersV2 = append(matchersV2, matcher) + } + + parserV2, err := rules.NewParser(matchersV2) + if err != nil { + return nil, fmt.Errorf("error while creating v2 parser: %w", err) + } + return &Muxer{ - parser: parser, + parser: parser, + parserV2: parserV2, + defaultHandler: http.NotFoundHandler(), }, nil } @@ -43,7 +57,12 @@ func (m *Muxer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } } - http.NotFoundHandler().ServeHTTP(rw, req) + m.defaultHandler.ServeHTTP(rw, req) +} + +// SetDefaultHandler sets the muxer default handler. +func (m *Muxer) SetDefaultHandler(handler http.Handler) { + m.defaultHandler = handler } // GetRulePriority computes the priority for a given rule. @@ -53,10 +72,26 @@ func GetRulePriority(rule string) int { } // AddRoute add a new route to the router. -func (m *Muxer) AddRoute(rule string, priority int, handler http.Handler) error { - parse, err := m.parser.Parse(rule) - if err != nil { - return fmt.Errorf("error while parsing rule %s: %w", rule, err) +func (m *Muxer) AddRoute(rule string, syntax string, priority int, handler http.Handler) error { + var parse interface{} + var err error + var matcherFuncs map[string]func(*matchersTree, ...string) error + + switch syntax { + case "v2": + parse, err = m.parserV2.Parse(rule) + if err != nil { + return fmt.Errorf("error while parsing rule %s: %w", rule, err) + } + + matcherFuncs = httpFuncsV2 + default: + parse, err = m.parser.Parse(rule) + if err != nil { + return fmt.Errorf("error while parsing rule %s: %w", rule, err) + } + + matcherFuncs = httpFuncs } buildTree, ok := parse.(rules.TreeBuilder) @@ -65,7 +100,7 @@ func (m *Muxer) AddRoute(rule string, priority int, handler http.Handler) error } var matchers matchersTree - err = matchers.addRule(buildTree()) + err = matchers.addRule(buildTree(), matcherFuncs) if err != nil { return fmt.Errorf("error while adding rule %s: %w", rule, err) } @@ -87,6 +122,9 @@ func ParseDomains(rule string) ([]string, error) { for matcher := range httpFuncs { matchers = append(matchers, matcher) } + for matcher := range httpFuncsV2 { + matchers = append(matchers, matcher) + } parser, err := rules.NewParser(matchers) if err != nil { @@ -166,25 +204,27 @@ func (m *matchersTree) match(req *http.Request) bool { } } -func (m *matchersTree) addRule(rule *rules.Tree) error { +type matcherFuncs map[string]func(*matchersTree, ...string) error + +func (m *matchersTree) addRule(rule *rules.Tree, funcs matcherFuncs) error { switch rule.Matcher { case "and", "or": m.operator = rule.Matcher m.left = &matchersTree{} - err := m.left.addRule(rule.RuleLeft) + err := m.left.addRule(rule.RuleLeft, funcs) if err != nil { return fmt.Errorf("error while adding rule %s: %w", rule.Matcher, err) } m.right = &matchersTree{} - return m.right.addRule(rule.RuleRight) + return m.right.addRule(rule.RuleRight, funcs) default: err := rules.CheckRule(rule) if err != nil { return fmt.Errorf("error while checking rule %s: %w", rule.Matcher, err) } - err = httpFuncs[rule.Matcher](m, rule.Value...) + err = funcs[rule.Matcher](m, rule.Value...) if err != nil { return fmt.Errorf("error while adding rule %s: %w", rule.Matcher, err) } diff --git a/pkg/muxer/http/mux_test.go b/pkg/muxer/http/mux_test.go index a31a37881..efa8486a3 100644 --- a/pkg/muxer/http/mux_test.go +++ b/pkg/muxer/http/mux_test.go @@ -231,7 +231,7 @@ func TestMuxer(t *testing.T) { require.NoError(t, err) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - err = muxer.AddRoute(test.rule, 0, handler) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) return @@ -394,7 +394,7 @@ func Test_addRoutePriority(t *testing.T) { route.priority = GetRulePriority(route.rule) } - err := muxer.AddRoute(route.rule, route.priority, handler) + err := muxer.AddRoute(route.rule, "", route.priority, handler) require.NoError(t, err, route.rule) } @@ -519,7 +519,7 @@ func TestEmptyHost(t *testing.T) { muxer, err := NewMuxer() require.NoError(t, err) - err = muxer.AddRoute(test.rule, 0, handler) + err = muxer.AddRoute(test.rule, "", 0, handler) require.NoError(t, err) // RequestDecorator is necessary for the host rule diff --git a/pkg/muxer/tcp/matcher_test.go b/pkg/muxer/tcp/matcher_test.go index 3ba833d49..531492e89 100644 --- a/pkg/muxer/tcp/matcher_test.go +++ b/pkg/muxer/tcp/matcher_test.go @@ -38,7 +38,7 @@ func Test_HostSNICatchAll(t *testing.T) { muxer, err := NewMuxer() require.NoError(t, err) - err = muxer.AddRoute(test.rule, 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {})) + err = muxer.AddRoute(test.rule, "", 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {})) require.NoError(t, err) handler, catchAll := muxer.Match(ConnData{ @@ -144,7 +144,7 @@ func Test_HostSNI(t *testing.T) { muxer, err := NewMuxer() require.NoError(t, err) - err = muxer.AddRoute(test.rule, 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {})) + err = muxer.AddRoute(test.rule, "", 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {})) if test.buildErr { require.Error(t, err) return @@ -227,7 +227,7 @@ func Test_HostSNIRegexp(t *testing.T) { muxer, err := NewMuxer() require.NoError(t, err) - err = muxer.AddRoute(test.rule, 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {})) + err = muxer.AddRoute(test.rule, "", 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {})) if test.buildErr { require.Error(t, err) return @@ -299,7 +299,7 @@ func Test_ClientIP(t *testing.T) { muxer, err := NewMuxer() require.NoError(t, err) - err = muxer.AddRoute(test.rule, 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {})) + err = muxer.AddRoute(test.rule, "", 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {})) if test.buildErr { require.Error(t, err) return @@ -363,7 +363,7 @@ func Test_ALPN(t *testing.T) { muxer, err := NewMuxer() require.NoError(t, err) - err = muxer.AddRoute(test.rule, 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {})) + err = muxer.AddRoute(test.rule, "", 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {})) if test.buildErr { require.Error(t, err) return diff --git a/pkg/muxer/tcp/matcher_v2.go b/pkg/muxer/tcp/matcher_v2.go new file mode 100644 index 000000000..b473f87a4 --- /dev/null +++ b/pkg/muxer/tcp/matcher_v2.go @@ -0,0 +1,240 @@ +package tcp + +import ( + "bytes" + "errors" + "fmt" + "regexp" + "strconv" + "strings" + + "github.com/go-acme/lego/v4/challenge/tlsalpn01" + "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/ip" +) + +var tcpFuncsV2 = map[string]func(*matchersTree, ...string) error{ + "ALPN": alpnV2, + "ClientIP": clientIPV2, + "HostSNI": hostSNIV2, + "HostSNIRegexp": hostSNIRegexpV2, +} + +func clientIPV2(tree *matchersTree, clientIPs ...string) error { + checker, err := ip.NewChecker(clientIPs) + if err != nil { + return fmt.Errorf("could not initialize IP Checker for \"ClientIP\" matcher: %w", err) + } + + tree.matcher = func(meta ConnData) bool { + if meta.remoteIP == "" { + return false + } + + ok, err := checker.Contains(meta.remoteIP) + if err != nil { + log.Warn().Err(err).Msg("ClientIP matcher: could not match remote address") + return false + } + return ok + } + + return nil +} + +// alpnV2 checks if any of the connection ALPN protocols matches one of the matcher protocols. +func alpnV2(tree *matchersTree, protos ...string) error { + if len(protos) == 0 { + return errors.New("empty value for \"ALPN\" matcher is not allowed") + } + + for _, proto := range protos { + if proto == tlsalpn01.ACMETLS1Protocol { + return fmt.Errorf("invalid protocol value for \"ALPN\" matcher, %q is not allowed", proto) + } + } + + tree.matcher = func(meta ConnData) bool { + for _, proto := range meta.alpnProtos { + for _, filter := range protos { + if proto == filter { + return true + } + } + } + + return false + } + + return nil +} + +// hostSNIV2 checks if the SNI Host of the connection match the matcher host. +func hostSNIV2(tree *matchersTree, hosts ...string) error { + if len(hosts) == 0 { + return errors.New("empty value for \"HostSNI\" matcher is not allowed") + } + + for i, host := range hosts { + // Special case to allow global wildcard + if host == "*" { + continue + } + + if !hostOrIP.MatchString(host) { + return fmt.Errorf("invalid value for \"HostSNI\" matcher, %q is not a valid hostname or IP", host) + } + + hosts[i] = strings.ToLower(host) + } + + tree.matcher = func(meta ConnData) bool { + // Since a HostSNI(`*`) rule has been provided as catchAll for non-TLS TCP, + // it allows matching with an empty serverName. + // Which is why we make sure to take that case into account before + // checking meta.serverName. + if hosts[0] == "*" { + return true + } + + if meta.serverName == "" { + return false + } + + for _, host := range hosts { + if host == "*" { + return true + } + + if host == meta.serverName { + return true + } + + // trim trailing period in case of FQDN + host = strings.TrimSuffix(host, ".") + if host == meta.serverName { + return true + } + } + + return false + } + + return nil +} + +// hostSNIRegexpV2 checks if the SNI Host of the connection matches the matcher host regexp. +func hostSNIRegexpV2(tree *matchersTree, templates ...string) error { + if len(templates) == 0 { + return errors.New("empty value for \"HostSNIRegexp\" matcher is not allowed") + } + + var regexps []*regexp.Regexp + + for _, template := range templates { + preparedPattern, err := preparePattern(template) + if err != nil { + return fmt.Errorf("invalid pattern value for \"HostSNIRegexp\" matcher, %q is not a valid pattern: %w", template, err) + } + + regexp, err := regexp.Compile(preparedPattern) + if err != nil { + return err + } + + regexps = append(regexps, regexp) + } + + tree.matcher = func(meta ConnData) bool { + for _, regexp := range regexps { + if regexp.MatchString(meta.serverName) { + return true + } + } + + return false + } + + return nil +} + +// preparePattern builds a regexp pattern from the initial user defined expression. +// This function reuses the code dedicated to host matching of the newRouteRegexp func from the gorilla/mux library. +// https://github.com/containous/mux/tree/8ffa4f6d063c1e2b834a73be6a1515cca3992618. +func preparePattern(template string) (string, error) { + // Check if it is well-formed. + idxs, errBraces := braceIndices(template) + if errBraces != nil { + return "", errBraces + } + + defaultPattern := "[^.]+" + pattern := bytes.NewBufferString("") + + // Host SNI matching is case-insensitive + _, _ = fmt.Fprint(pattern, "(?i)") + + pattern.WriteByte('^') + var end int + for i := 0; i < len(idxs); i += 2 { + // Set all values we are interested in. + raw := template[end:idxs[i]] + end = idxs[i+1] + parts := strings.SplitN(template[idxs[i]+1:end-1], ":", 2) + name := parts[0] + + patt := defaultPattern + if len(parts) == 2 { + patt = parts[1] + } + + // Name or pattern can't be empty. + if name == "" || patt == "" { + return "", fmt.Errorf("mux: missing name or pattern in %q", + template[idxs[i]:end]) + } + + // Build the regexp pattern. + _, _ = fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt) + } + + // Add the remaining. + raw := template[end:] + pattern.WriteString(regexp.QuoteMeta(raw)) + pattern.WriteByte('$') + + return pattern.String(), nil +} + +// varGroupName builds a capturing group name for the indexed variable. +// This function is a copy of varGroupName func from the gorilla/mux library. +// https://github.com/containous/mux/tree/8ffa4f6d063c1e2b834a73be6a1515cca3992618. +func varGroupName(idx int) string { + return "v" + strconv.Itoa(idx) +} + +// braceIndices returns the first level curly brace indices from a string. +// This function is a copy of braceIndices func from the gorilla/mux library. +// https://github.com/containous/mux/tree/8ffa4f6d063c1e2b834a73be6a1515cca3992618. +func braceIndices(s string) ([]int, error) { + var level, idx int + var idxs []int + for i := 0; i < len(s); i++ { + switch s[i] { + case '{': + if level++; level == 1 { + idx = i + } + case '}': + if level--; level == 0 { + idxs = append(idxs, idx, i+1) + } else if level < 0 { + return nil, fmt.Errorf("mux: unbalanced braces in %q", s) + } + } + } + if level != 0 { + return nil, fmt.Errorf("mux: unbalanced braces in %q", s) + } + return idxs, nil +} diff --git a/pkg/muxer/tcp/matcher_v2_test.go b/pkg/muxer/tcp/matcher_v2_test.go new file mode 100644 index 000000000..74ffcff80 --- /dev/null +++ b/pkg/muxer/tcp/matcher_v2_test.go @@ -0,0 +1,1008 @@ +package tcp + +import ( + "fmt" + "testing" + + "github.com/go-acme/lego/v4/challenge/tlsalpn01" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/traefik/traefik/v3/pkg/tcp" +) + +// All the tests in the suite are a copy of tcp muxer tests on branch v2. +// Only the test for route priority has not been copied here, +// because the priority computation is no longer done when calling the muxer AddRoute method. +func Test_addTCPRouteV2(t *testing.T) { + testCases := []struct { + desc string + rule string + serverName string + remoteAddr string + protos []string + routeErr bool + matchErr bool + }{ + { + desc: "no tree", + routeErr: true, + }, + { + desc: "Rule with no matcher", + rule: "rulewithnotmatcher", + routeErr: true, + }, + { + desc: "Empty HostSNI rule", + rule: "HostSNI()", + serverName: "foobar", + routeErr: true, + }, + { + desc: "Empty HostSNI rule", + rule: "HostSNI(``)", + serverName: "foobar", + routeErr: true, + }, + { + desc: "Valid HostSNI rule matching", + rule: "HostSNI(`foobar`)", + serverName: "foobar", + }, + { + desc: "Valid negative HostSNI rule matching", + rule: "!HostSNI(`bar`)", + serverName: "foobar", + }, + { + desc: "Valid HostSNI rule matching with alternative case", + rule: "hostsni(`foobar`)", + serverName: "foobar", + }, + { + desc: "Valid HostSNI rule matching with alternative case", + rule: "HOSTSNI(`foobar`)", + serverName: "foobar", + }, + { + desc: "Valid HostSNI rule not matching", + rule: "HostSNI(`foobar`)", + serverName: "bar", + matchErr: true, + }, + { + desc: "Empty HostSNIRegexp rule", + rule: "HostSNIRegexp()", + serverName: "foobar", + routeErr: true, + }, + { + desc: "Empty HostSNIRegexp rule", + rule: "HostSNIRegexp(``)", + serverName: "foobar", + routeErr: true, + }, + { + desc: "Valid HostSNIRegexp rule matching", + rule: "HostSNIRegexp(`{subdomain:[a-z]+}.foobar`)", + serverName: "sub.foobar", + }, + { + desc: "Valid negative HostSNIRegexp rule matching", + rule: "!HostSNIRegexp(`bar`)", + serverName: "foobar", + }, + { + desc: "Valid HostSNIRegexp rule matching with alternative case", + rule: "hostsniregexp(`foobar`)", + serverName: "foobar", + }, + { + desc: "Valid HostSNIRegexp rule matching with alternative case", + rule: "HOSTSNIREGEXP(`foobar`)", + serverName: "foobar", + }, + { + desc: "Valid HostSNIRegexp rule not matching", + rule: "HostSNIRegexp(`foobar`)", + serverName: "bar", + matchErr: true, + }, + { + desc: "Valid negative HostSNI rule not matching", + rule: "!HostSNI(`bar`)", + serverName: "bar", + matchErr: true, + }, + { + desc: "Valid HostSNIRegexp rule matching empty servername", + rule: "HostSNIRegexp(`{subdomain:[a-z]*}`)", + serverName: "", + }, + { + desc: "Valid HostSNIRegexp rule with one name", + rule: "HostSNIRegexp(`{dummy}`)", + serverName: "toto", + }, + { + desc: "Valid HostSNIRegexp rule with one name 2", + rule: "HostSNIRegexp(`{dummy}`)", + serverName: "toto.com", + matchErr: true, + }, + { + desc: "Empty ClientIP rule", + rule: "ClientIP()", + routeErr: true, + }, + { + desc: "Empty ClientIP rule", + rule: "ClientIP(``)", + routeErr: true, + }, + { + desc: "Invalid ClientIP", + rule: "ClientIP(`invalid`)", + routeErr: true, + }, + { + desc: "Invalid remoteAddr", + rule: "ClientIP(`10.0.0.1`)", + remoteAddr: "not.an.IP:80", + matchErr: true, + }, + { + desc: "Valid ClientIP rule matching", + rule: "ClientIP(`10.0.0.1`)", + remoteAddr: "10.0.0.1:80", + }, + { + desc: "Valid negative ClientIP rule matching", + rule: "!ClientIP(`20.0.0.1`)", + remoteAddr: "10.0.0.1:80", + }, + { + desc: "Valid ClientIP rule matching with alternative case", + rule: "clientip(`10.0.0.1`)", + remoteAddr: "10.0.0.1:80", + }, + { + desc: "Valid ClientIP rule matching with alternative case", + rule: "CLIENTIP(`10.0.0.1`)", + remoteAddr: "10.0.0.1:80", + }, + { + desc: "Valid ClientIP rule not matching", + rule: "ClientIP(`10.0.0.1`)", + remoteAddr: "10.0.0.2:80", + matchErr: true, + }, + { + desc: "Valid negative ClientIP rule not matching", + rule: "!ClientIP(`10.0.0.2`)", + remoteAddr: "10.0.0.2:80", + matchErr: true, + }, + { + desc: "Valid ClientIP rule matching IPv6", + rule: "ClientIP(`10::10`)", + remoteAddr: "[10::10]:80", + }, + { + desc: "Valid negative ClientIP rule matching IPv6", + rule: "!ClientIP(`10::10`)", + remoteAddr: "[::1]:80", + }, + { + desc: "Valid ClientIP rule not matching IPv6", + rule: "ClientIP(`10::10`)", + remoteAddr: "[::1]:80", + matchErr: true, + }, + { + desc: "Valid ClientIP rule matching multiple IPs", + rule: "ClientIP(`10.0.0.1`, `10.0.0.0`)", + remoteAddr: "10.0.0.0:80", + }, + { + desc: "Valid ClientIP rule matching CIDR", + rule: "ClientIP(`11.0.0.0/24`)", + remoteAddr: "11.0.0.0:80", + }, + { + desc: "Valid ClientIP rule not matching CIDR", + rule: "ClientIP(`11.0.0.0/24`)", + remoteAddr: "10.0.0.0:80", + matchErr: true, + }, + { + desc: "Valid ClientIP rule matching CIDR IPv6", + rule: "ClientIP(`11::/16`)", + remoteAddr: "[11::]:80", + }, + { + desc: "Valid ClientIP rule not matching CIDR IPv6", + rule: "ClientIP(`11::/16`)", + remoteAddr: "[10::]:80", + matchErr: true, + }, + { + desc: "Valid ClientIP rule matching multiple CIDR", + rule: "ClientIP(`11.0.0.0/16`, `10.0.0.0/16`)", + remoteAddr: "10.0.0.0:80", + }, + { + desc: "Valid ClientIP rule not matching CIDR and matching IP", + rule: "ClientIP(`11.0.0.0/16`, `10.0.0.0`)", + remoteAddr: "10.0.0.0:80", + }, + { + desc: "Valid ClientIP rule matching CIDR and not matching IP", + rule: "ClientIP(`11.0.0.0`, `10.0.0.0/16`)", + remoteAddr: "10.0.0.0:80", + }, + { + desc: "Valid HostSNI and ClientIP rule matching", + rule: "HostSNI(`foobar`) && ClientIP(`10.0.0.1`)", + serverName: "foobar", + remoteAddr: "10.0.0.1:80", + }, + { + desc: "Valid negative HostSNI and ClientIP rule matching", + rule: "!HostSNI(`bar`) && ClientIP(`10.0.0.1`)", + serverName: "foobar", + remoteAddr: "10.0.0.1:80", + }, + { + desc: "Valid HostSNI and negative ClientIP rule matching", + rule: "HostSNI(`foobar`) && !ClientIP(`10.0.0.2`)", + serverName: "foobar", + remoteAddr: "10.0.0.1:80", + }, + { + desc: "Valid negative HostSNI and negative ClientIP rule matching", + rule: "!HostSNI(`bar`) && !ClientIP(`10.0.0.2`)", + serverName: "foobar", + remoteAddr: "10.0.0.1:80", + }, + { + desc: "Valid negative HostSNI or negative ClientIP rule matching", + rule: "!(HostSNI(`bar`) || ClientIP(`10.0.0.2`))", + serverName: "foobar", + remoteAddr: "10.0.0.1:80", + }, + { + desc: "Valid negative HostSNI and negative ClientIP rule matching", + rule: "!(HostSNI(`bar`) && ClientIP(`10.0.0.2`))", + serverName: "foobar", + remoteAddr: "10.0.0.2:80", + }, + { + desc: "Valid negative HostSNI and negative ClientIP rule matching", + rule: "!(HostSNI(`bar`) && ClientIP(`10.0.0.2`))", + serverName: "bar", + remoteAddr: "10.0.0.1:80", + }, + { + desc: "Valid negative HostSNI and negative ClientIP rule matching", + rule: "!(HostSNI(`bar`) && ClientIP(`10.0.0.2`))", + serverName: "bar", + remoteAddr: "10.0.0.2:80", + matchErr: true, + }, + { + desc: "Valid negative HostSNI and negative ClientIP rule matching", + rule: "!(HostSNI(`bar`) && ClientIP(`10.0.0.2`))", + serverName: "foobar", + remoteAddr: "10.0.0.1:80", + }, + { + desc: "Valid HostSNI and ClientIP rule not matching", + rule: "HostSNI(`foobar`) && ClientIP(`10.0.0.1`)", + serverName: "bar", + remoteAddr: "10.0.0.1:80", + matchErr: true, + }, + { + desc: "Valid HostSNI and ClientIP rule not matching", + rule: "HostSNI(`foobar`) && ClientIP(`10.0.0.1`)", + serverName: "foobar", + remoteAddr: "10.0.0.2:80", + matchErr: true, + }, + { + desc: "Valid HostSNI or ClientIP rule matching", + rule: "HostSNI(`foobar`) || ClientIP(`10.0.0.1`)", + serverName: "foobar", + remoteAddr: "10.0.0.1:80", + }, + { + desc: "Valid HostSNI or ClientIP rule matching", + rule: "HostSNI(`foobar`) || ClientIP(`10.0.0.1`)", + serverName: "bar", + remoteAddr: "10.0.0.1:80", + }, + { + desc: "Valid HostSNI or ClientIP rule matching", + rule: "HostSNI(`foobar`) || ClientIP(`10.0.0.1`)", + serverName: "foobar", + remoteAddr: "10.0.0.2:80", + }, + { + desc: "Valid HostSNI or ClientIP rule not matching", + rule: "HostSNI(`foobar`) || ClientIP(`10.0.0.1`)", + serverName: "bar", + remoteAddr: "10.0.0.2:80", + matchErr: true, + }, + { + desc: "Valid HostSNI x 3 OR rule matching", + rule: "HostSNI(`foobar`) || HostSNI(`foo`) || HostSNI(`bar`)", + serverName: "foobar", + }, + { + desc: "Valid HostSNI x 3 OR rule not matching", + rule: "HostSNI(`foobar`) || HostSNI(`foo`) || HostSNI(`bar`)", + serverName: "baz", + matchErr: true, + }, + { + desc: "Valid HostSNI and ClientIP Combined rule matching", + rule: "HostSNI(`foobar`) || HostSNI(`bar`) && ClientIP(`10.0.0.1`)", + serverName: "foobar", + remoteAddr: "10.0.0.2:80", + }, + { + desc: "Valid HostSNI and ClientIP Combined rule matching", + rule: "HostSNI(`foobar`) || HostSNI(`bar`) && ClientIP(`10.0.0.1`)", + serverName: "bar", + remoteAddr: "10.0.0.1:80", + }, + { + desc: "Valid HostSNI and ClientIP Combined rule not matching", + rule: "HostSNI(`foobar`) || HostSNI(`bar`) && ClientIP(`10.0.0.1`)", + serverName: "bar", + remoteAddr: "10.0.0.2:80", + matchErr: true, + }, + { + desc: "Valid HostSNI and ClientIP Combined rule not matching", + rule: "HostSNI(`foobar`) || HostSNI(`bar`) && ClientIP(`10.0.0.1`)", + serverName: "baz", + remoteAddr: "10.0.0.1:80", + matchErr: true, + }, + { + desc: "Valid HostSNI and ClientIP complex combined rule matching", + rule: "(HostSNI(`foobar`) || HostSNI(`bar`)) && (ClientIP(`10.0.0.1`) || ClientIP(`10.0.0.2`))", + serverName: "bar", + remoteAddr: "10.0.0.1:80", + }, + { + desc: "Valid HostSNI and ClientIP complex combined rule not matching", + rule: "(HostSNI(`foobar`) || HostSNI(`bar`)) && (ClientIP(`10.0.0.1`) || ClientIP(`10.0.0.2`))", + serverName: "baz", + remoteAddr: "10.0.0.1:80", + matchErr: true, + }, + { + desc: "Valid HostSNI and ClientIP complex combined rule not matching", + rule: "(HostSNI(`foobar`) || HostSNI(`bar`)) && (ClientIP(`10.0.0.1`) || ClientIP(`10.0.0.2`))", + serverName: "bar", + remoteAddr: "10.0.0.3:80", + matchErr: true, + }, + { + desc: "Valid HostSNI and ClientIP more complex (but absurd) combined rule matching", + rule: "(HostSNI(`foobar`) || (HostSNI(`bar`) && !HostSNI(`foobar`))) && ((ClientIP(`10.0.0.1`) && !ClientIP(`10.0.0.2`)) || ClientIP(`10.0.0.2`)) ", + serverName: "bar", + remoteAddr: "10.0.0.1:80", + }, + { + desc: "Invalid ALPN rule matching ACME-TLS/1", + rule: fmt.Sprintf("ALPN(`%s`)", tlsalpn01.ACMETLS1Protocol), + protos: []string{"foo"}, + routeErr: true, + }, + { + desc: "Valid ALPN rule matching single protocol", + rule: "ALPN(`foo`)", + protos: []string{"foo"}, + }, + { + desc: "Valid ALPN rule matching ACME-TLS/1 protocol", + rule: "ALPN(`foo`)", + protos: []string{tlsalpn01.ACMETLS1Protocol}, + matchErr: true, + }, + { + desc: "Valid ALPN rule not matching single protocol", + rule: "ALPN(`foo`)", + protos: []string{"bar"}, + matchErr: true, + }, + { + desc: "Valid alternative case ALPN rule matching single protocol without another being supported", + rule: "ALPN(`foo`) && !alpn(`h2`)", + protos: []string{"foo", "bar"}, + }, + { + desc: "Valid alternative case ALPN rule not matching single protocol because of another being supported", + rule: "ALPN(`foo`) && !alpn(`h2`)", + protos: []string{"foo", "h2", "bar"}, + matchErr: true, + }, + { + desc: "Valid complex alternative case ALPN and HostSNI rule", + rule: "ALPN(`foo`) && (!alpn(`h2`) || hostsni(`foo`))", + protos: []string{"foo", "bar"}, + serverName: "foo", + }, + { + desc: "Valid complex alternative case ALPN and HostSNI rule not matching by SNI", + rule: "ALPN(`foo`) && (!alpn(`h2`) || hostsni(`foo`))", + protos: []string{"foo", "bar", "h2"}, + serverName: "bar", + matchErr: true, + }, + { + desc: "Valid complex alternative case ALPN and HostSNI rule matching by ALPN", + rule: "ALPN(`foo`) && (!alpn(`h2`) || hostsni(`foo`))", + protos: []string{"foo", "bar"}, + serverName: "bar", + }, + { + desc: "Valid complex alternative case ALPN and HostSNI rule not matching by protos", + rule: "ALPN(`foo`) && (!alpn(`h2`) || hostsni(`foo`))", + protos: []string{"h2", "bar"}, + serverName: "bar", + matchErr: true, + }, + } + + for _, test := range testCases { + test := test + + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + msg := "BYTES" + handler := tcp.HandlerFunc(func(conn tcp.WriteCloser) { + _, err := conn.Write([]byte(msg)) + require.NoError(t, err) + }) + + router, err := NewMuxer() + require.NoError(t, err) + + err = router.AddRoute(test.rule, "v2", 0, handler) + if test.routeErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + + addr := "0.0.0.0:0" + if test.remoteAddr != "" { + addr = test.remoteAddr + } + + conn := &fakeConn{ + call: map[string]int{}, + remoteAddr: fakeAddr{addr: addr}, + } + + connData, err := NewConnData(test.serverName, conn, test.protos) + require.NoError(t, err) + + matchingHandler, _ := router.Match(connData) + if test.matchErr { + require.Nil(t, matchingHandler) + return + } + + require.NotNil(t, matchingHandler) + + matchingHandler.ServeTCP(conn) + + n, ok := conn.call[msg] + assert.Equal(t, 1, n) + assert.True(t, ok) + }) + } +} + +func TestParseHostSNIV2(t *testing.T) { + testCases := []struct { + description string + expression string + domain []string + errorExpected bool + }{ + { + description: "Unknown rule", + expression: "Foobar(`foo.bar`,`test.bar`)", + errorExpected: true, + }, + { + description: "Many hostSNI rules", + expression: "HostSNI(`foo.bar`,`test.bar`)", + domain: []string{"foo.bar", "test.bar"}, + }, + { + description: "Many hostSNI rules upper", + expression: "HOSTSNI(`foo.bar`,`test.bar`)", + domain: []string{"foo.bar", "test.bar"}, + }, + { + description: "Many hostSNI rules lower", + expression: "hostsni(`foo.bar`,`test.bar`)", + domain: []string{"foo.bar", "test.bar"}, + }, + { + description: "No hostSNI rule", + expression: "ClientIP(`10.1`)", + }, + { + description: "HostSNI rule and another rule", + expression: "HostSNI(`foo.bar`) && ClientIP(`10.1`)", + domain: []string{"foo.bar"}, + }, + { + description: "HostSNI rule to lower and another rule", + expression: "HostSNI(`Foo.Bar`) && ClientIP(`10.1`)", + domain: []string{"foo.bar"}, + }, + { + description: "HostSNI rule with no domain", + expression: "HostSNI() && ClientIP(`10.1`)", + }, + } + + for _, test := range testCases { + test := test + t.Run(test.expression, func(t *testing.T) { + t.Parallel() + + domains, err := ParseHostSNI(test.expression) + + if test.errorExpected { + require.Errorf(t, err, "unable to parse correctly the domains in the HostSNI rule from %q", test.expression) + } else { + require.NoError(t, err, "%s: Error while parsing domain.", test.expression) + } + + assert.EqualValues(t, test.domain, domains, "%s: Error parsing domains from expression.", test.expression) + }) + } +} + +func Test_HostSNICatchAllV2(t *testing.T) { + testCases := []struct { + desc string + rule string + isCatchAll bool + }{ + { + desc: "HostSNI(`foobar`) is not catchAll", + rule: "HostSNI(`foobar`)", + }, + { + desc: "HostSNI(`*`) is catchAll", + rule: "HostSNI(`*`)", + isCatchAll: true, + }, + { + desc: "HOSTSNI(`*`) is catchAll", + rule: "HOSTSNI(`*`)", + isCatchAll: true, + }, + { + desc: `HostSNI("*") is catchAll`, + rule: `HostSNI("*")`, + isCatchAll: true, + }, + } + + for _, test := range testCases { + test := test + + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, "v2", 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {})) + require.NoError(t, err) + + handler, catchAll := muxer.Match(ConnData{ + serverName: "foobar", + }) + require.NotNil(t, handler) + assert.Equal(t, test.isCatchAll, catchAll) + }) + } +} + +func Test_HostSNIV2(t *testing.T) { + testCases := []struct { + desc string + ruleHosts []string + serverName string + buildErr bool + matchErr bool + }{ + { + desc: "Empty", + buildErr: true, + }, + { + desc: "Non ASCII host", + ruleHosts: []string{"héhé"}, + buildErr: true, + }, + { + desc: "Not Matching hosts", + ruleHosts: []string{"foobar"}, + serverName: "bar", + matchErr: true, + }, + { + desc: "Matching globing host `*`", + ruleHosts: []string{"*"}, + serverName: "foobar", + }, + { + desc: "Matching globing host `*` and empty serverName", + ruleHosts: []string{"*"}, + serverName: "", + }, + { + desc: "Matching globing host `*` and another non matching host", + ruleHosts: []string{"foo", "*"}, + serverName: "bar", + }, + { + desc: "Matching globing host `*` and another non matching host, and empty servername", + ruleHosts: []string{"foo", "*"}, + serverName: "", + matchErr: true, + }, + { + desc: "Not Matching globing host with subdomain", + ruleHosts: []string{"*.bar"}, + buildErr: true, + }, + { + desc: "Not Matching host with trailing dot with ", + ruleHosts: []string{"foobar."}, + serverName: "foobar.", + }, + { + desc: "Matching host with trailing dot", + ruleHosts: []string{"foobar."}, + serverName: "foobar", + }, + { + desc: "Matching hosts", + ruleHosts: []string{"foobar", "foo-bar.baz"}, + serverName: "foobar", + }, + { + desc: "Matching hosts with subdomains", + ruleHosts: []string{"foo.bar"}, + serverName: "foo.bar", + }, + { + desc: "Matching IPv4", + ruleHosts: []string{"127.0.0.1"}, + serverName: "127.0.0.1", + }, + { + desc: "Matching IPv6", + ruleHosts: []string{"10::10"}, + serverName: "10::10", + }, + } + + for _, test := range testCases { + test := test + + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + matcherTree := &matchersTree{} + err := hostSNIV2(matcherTree, test.ruleHosts...) + if test.buildErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + meta := ConnData{ + serverName: test.serverName, + } + + assert.Equal(t, test.matchErr, !matcherTree.match(meta)) + }) + } +} + +func Test_HostSNIRegexpV2(t *testing.T) { + testCases := []struct { + desc string + pattern string + serverNames map[string]bool + buildErr bool + }{ + { + desc: "unbalanced braces", + pattern: "subdomain:(foo\\.)?bar\\.com}", + buildErr: true, + }, + { + desc: "empty group name", + pattern: "{:(foo\\.)?bar\\.com}", + buildErr: true, + }, + { + desc: "empty capturing group", + pattern: "{subdomain:}", + buildErr: true, + }, + { + desc: "malformed capturing group", + pattern: "{subdomain:(foo\\.?bar\\.com}", + buildErr: true, + }, + { + desc: "not interpreted as a regexp", + pattern: "bar.com", + serverNames: map[string]bool{ + "bar.com": true, + "barucom": false, + }, + }, + { + desc: "capturing group", + pattern: "{subdomain:(foo\\.)?bar\\.com}", + serverNames: map[string]bool{ + "foo.bar.com": true, + "bar.com": true, + "fooubar.com": false, + "barucom": false, + "barcom": false, + }, + }, + { + desc: "non capturing group", + pattern: "{subdomain:(?:foo\\.)?bar\\.com}", + serverNames: map[string]bool{ + "foo.bar.com": true, + "bar.com": true, + "fooubar.com": false, + "barucom": false, + "barcom": false, + }, + }, + { + desc: "regex insensitive", + pattern: "{dummy:[A-Za-z-]+\\.bar\\.com}", + serverNames: map[string]bool{ + "FOO.bar.com": true, + "foo.bar.com": true, + "fooubar.com": false, + "barucom": false, + "barcom": false, + }, + }, + { + desc: "insensitive host", + pattern: "{dummy:[a-z-]+\\.bar\\.com}", + serverNames: map[string]bool{ + "FOO.bar.com": true, + "foo.bar.com": true, + "fooubar.com": false, + "barucom": false, + "barcom": false, + }, + }, + { + desc: "insensitive host simple", + pattern: "foo.bar.com", + serverNames: map[string]bool{ + "FOO.bar.com": true, + "foo.bar.com": true, + "fooubar.com": false, + "barucom": false, + "barcom": false, + }, + }, + } + + for _, test := range testCases { + test := test + + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + matchersTree := &matchersTree{} + err := hostSNIRegexpV2(matchersTree, test.pattern) + if test.buildErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + for serverName, match := range test.serverNames { + meta := ConnData{ + serverName: serverName, + } + + assert.Equal(t, match, matchersTree.match(meta)) + } + }) + } +} + +func Test_ClientIPV2(t *testing.T) { + testCases := []struct { + desc string + ruleCIDRs []string + remoteIP string + buildErr bool + matchErr bool + }{ + { + desc: "Empty", + buildErr: true, + }, + { + desc: "Malformed CIDR", + ruleCIDRs: []string{"héhé"}, + buildErr: true, + }, + { + desc: "Not matching empty remote IP", + ruleCIDRs: []string{"20.20.20.20"}, + matchErr: true, + }, + { + desc: "Not matching IP", + ruleCIDRs: []string{"20.20.20.20"}, + remoteIP: "10.10.10.10", + matchErr: true, + }, + { + desc: "Matching IP", + ruleCIDRs: []string{"10.10.10.10"}, + remoteIP: "10.10.10.10", + }, + { + desc: "Not matching multiple IPs", + ruleCIDRs: []string{"20.20.20.20", "30.30.30.30"}, + remoteIP: "10.10.10.10", + matchErr: true, + }, + { + desc: "Matching multiple IPs", + ruleCIDRs: []string{"10.10.10.10", "20.20.20.20", "30.30.30.30"}, + remoteIP: "20.20.20.20", + }, + { + desc: "Not matching CIDR", + ruleCIDRs: []string{"20.0.0.0/24"}, + remoteIP: "10.10.10.10", + matchErr: true, + }, + { + desc: "Matching CIDR", + ruleCIDRs: []string{"20.0.0.0/8"}, + remoteIP: "20.10.10.10", + }, + { + desc: "Not matching multiple CIDRs", + ruleCIDRs: []string{"10.0.0.0/24", "20.0.0.0/24"}, + remoteIP: "10.10.10.10", + matchErr: true, + }, + { + desc: "Matching multiple CIDRs", + ruleCIDRs: []string{"10.0.0.0/8", "20.0.0.0/8"}, + remoteIP: "20.10.10.10", + }, + } + + for _, test := range testCases { + test := test + + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + matchersTree := &matchersTree{} + err := clientIPV2(matchersTree, test.ruleCIDRs...) + if test.buildErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + meta := ConnData{ + remoteIP: test.remoteIP, + } + + assert.Equal(t, test.matchErr, !matchersTree.match(meta)) + }) + } +} + +func Test_ALPNV2(t *testing.T) { + testCases := []struct { + desc string + ruleALPNProtos []string + connProto string + buildErr bool + matchErr bool + }{ + { + desc: "Empty", + buildErr: true, + }, + { + desc: "ACME TLS proto", + ruleALPNProtos: []string{tlsalpn01.ACMETLS1Protocol}, + buildErr: true, + }, + { + desc: "Not matching empty proto", + ruleALPNProtos: []string{"h2"}, + matchErr: true, + }, + { + desc: "Not matching ALPN", + ruleALPNProtos: []string{"h2"}, + connProto: "mqtt", + matchErr: true, + }, + { + desc: "Matching ALPN", + ruleALPNProtos: []string{"h2"}, + connProto: "h2", + }, + { + desc: "Not matching multiple ALPNs", + ruleALPNProtos: []string{"h2", "mqtt"}, + connProto: "h2c", + matchErr: true, + }, + { + desc: "Matching multiple ALPNs", + ruleALPNProtos: []string{"h2", "h2c", "mqtt"}, + connProto: "h2c", + }, + } + + for _, test := range testCases { + test := test + + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + matchersTree := &matchersTree{} + err := alpnV2(matchersTree, test.ruleALPNProtos...) + if test.buildErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + meta := ConnData{ + alpnProtos: []string{test.connProto}, + } + + assert.Equal(t, test.matchErr, !matchersTree.match(meta)) + }) + } +} diff --git a/pkg/muxer/tcp/mux.go b/pkg/muxer/tcp/mux.go index f23ce629d..35c4be8a6 100644 --- a/pkg/muxer/tcp/mux.go +++ b/pkg/muxer/tcp/mux.go @@ -41,8 +41,9 @@ func NewConnData(serverName string, conn tcp.WriteCloser, alpnProtos []string) ( // Muxer defines a muxer that handles TCP routing with rules. type Muxer struct { - routes routes - parser predicate.Parser + routes routes + parser predicate.Parser + parserV2 predicate.Parser } // NewMuxer returns a TCP muxer. @@ -57,7 +58,20 @@ func NewMuxer() (*Muxer, error) { return nil, fmt.Errorf("error while creating rules parser: %w", err) } - return &Muxer{parser: parser}, nil + var matchersV2 []string + for matcher := range tcpFuncsV2 { + matchersV2 = append(matchersV2, matcher) + } + + parserV2, err := rules.NewParser(matchersV2) + if err != nil { + return nil, fmt.Errorf("error while creating v2 rules parser: %w", err) + } + + return &Muxer{ + parser: parser, + parserV2: parserV2, + }, nil } // Match returns the handler of the first route matching the connection metadata, @@ -106,10 +120,26 @@ func GetRulePriority(rule string) int { // AddRoute adds a new route, associated to the given handler, at the given // priority, to the muxer. -func (m *Muxer) AddRoute(rule string, priority int, handler tcp.Handler) error { - parse, err := m.parser.Parse(rule) - if err != nil { - return fmt.Errorf("error while parsing rule %s: %w", rule, err) +func (m *Muxer) AddRoute(rule string, syntax string, priority int, handler tcp.Handler) error { + var parse interface{} + var err error + var matcherFuncs map[string]func(*matchersTree, ...string) error + + switch syntax { + case "v2": + parse, err = m.parserV2.Parse(rule) + if err != nil { + return fmt.Errorf("error while parsing rule %s: %w", rule, err) + } + + matcherFuncs = tcpFuncsV2 + default: + parse, err = m.parser.Parse(rule) + if err != nil { + return fmt.Errorf("error while parsing rule %s: %w", rule, err) + } + + matcherFuncs = tcpFuncs } buildTree, ok := parse.(rules.TreeBuilder) @@ -120,7 +150,7 @@ func (m *Muxer) AddRoute(rule string, priority int, handler tcp.Handler) error { ruleTree := buildTree() var matchers matchersTree - err = matchers.addRule(ruleTree) + err = matchers.addRule(ruleTree, matcherFuncs) if err != nil { return fmt.Errorf("error while adding rule %s: %w", rule, err) } @@ -155,6 +185,9 @@ func ParseHostSNI(rule string) ([]string, error) { for matcher := range tcpFuncs { matchers = append(matchers, matcher) } + for matcher := range tcpFuncsV2 { + matchers = append(matchers, matcher) + } parser, err := rules.NewParser(matchers) if err != nil { @@ -237,25 +270,27 @@ func (m *matchersTree) match(meta ConnData) bool { } } -func (m *matchersTree) addRule(rule *rules.Tree) error { +type matcherFuncs map[string]func(*matchersTree, ...string) error + +func (m *matchersTree) addRule(rule *rules.Tree, funcs matcherFuncs) error { switch rule.Matcher { case "and", "or": m.operator = rule.Matcher m.left = &matchersTree{} - err := m.left.addRule(rule.RuleLeft) + err := m.left.addRule(rule.RuleLeft, funcs) if err != nil { return err } m.right = &matchersTree{} - return m.right.addRule(rule.RuleRight) + return m.right.addRule(rule.RuleRight, funcs) default: err := rules.CheckRule(rule) if err != nil { return err } - err = tcpFuncs[rule.Matcher](m, rule.Value...) + err = funcs[rule.Matcher](m, rule.Value...) if err != nil { return err } diff --git a/pkg/muxer/tcp/mux_test.go b/pkg/muxer/tcp/mux_test.go index 5c52089b4..3b108b7ea 100644 --- a/pkg/muxer/tcp/mux_test.go +++ b/pkg/muxer/tcp/mux_test.go @@ -277,7 +277,7 @@ func Test_addTCPRoute(t *testing.T) { router, err := NewMuxer() require.NoError(t, err) - err = router.AddRoute(test.rule, 0, handler) + err = router.AddRoute(test.rule, "", 0, handler) if test.routeErr { require.Error(t, err) return @@ -447,7 +447,7 @@ func Test_Priority(t *testing.T) { matchedRule := "" for rule, priority := range test.rules { rule := rule - err := muxer.AddRoute(rule, priority, tcp.HandlerFunc(func(conn tcp.WriteCloser) { + err := muxer.AddRoute(rule, "", priority, tcp.HandlerFunc(func(conn tcp.WriteCloser) { matchedRule = rule })) require.NoError(t, err) diff --git a/pkg/plugins/builder.go b/pkg/plugins/builder.go index 42f1a5f05..9a5261307 100644 --- a/pkg/plugins/builder.go +++ b/pkg/plugins/builder.go @@ -2,6 +2,7 @@ package plugins import ( "context" + "errors" "fmt" "net/http" "path/filepath" @@ -176,7 +177,7 @@ func getWasmPath(manifest *Manifest) (string, error) { } if !filepath.IsLocal(wasmPath) { - return "", fmt.Errorf("wasmPath must be a local path") + return "", errors.New("wasmPath must be a local path") } return wasmPath, nil diff --git a/pkg/plugins/client.go b/pkg/plugins/client.go index 52369585f..ac4e71bcf 100644 --- a/pkg/plugins/client.go +++ b/pkg/plugins/client.go @@ -228,7 +228,7 @@ func (c *Client) Check(ctx context.Context, pName, pVersion, hash string) error return nil } - return fmt.Errorf("plugin integrity check failed") + return errors.New("plugin integrity check failed") } // Unzip unzip a plugin archive. diff --git a/pkg/provider/file/file.go b/pkg/provider/file/file.go index ad19f573e..6ddc31084 100644 --- a/pkg/provider/file/file.go +++ b/pkg/provider/file/file.go @@ -6,8 +6,10 @@ import ( "errors" "fmt" "os" + "os/signal" "path/filepath" "strings" + "syscall" "text/template" "github.com/Masterminds/sprig/v3" @@ -48,6 +50,8 @@ func (p *Provider) Init() error { // Provide allows the file provider to provide configurations to traefik // using the given configuration channel. func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error { + logger := log.With().Str(logs.ProviderName, providerName).Logger() + if p.Watch { var watchItem string @@ -57,48 +61,44 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. case len(p.Filename) > 0: watchItem = filepath.Dir(p.Filename) default: - return errors.New("error using file configuration provider, neither filename or directory defined") + return errors.New("error using file configuration provider, neither filename nor directory is defined") } - if err := p.addWatcher(pool, watchItem, configurationChan, p.watcherCallback); err != nil { + if err := p.addWatcher(pool, watchItem, configurationChan, p.applyConfiguration); err != nil { return err } } - configuration, err := p.BuildConfiguration() - if err != nil { - if p.Watch { - log.Error(). - Str(logs.ProviderName, providerName). - Err(err). - Msg("Error while building configuration (for the first time)") + pool.GoCtx(func(ctx context.Context) { + signals := make(chan os.Signal, 1) + signal.Notify(signals, syscall.SIGHUP) + for { + select { + case <-ctx.Done(): + return + // signals only receives SIGHUP events. + case <-signals: + if err := p.applyConfiguration(configurationChan); err != nil { + logger.Error().Err(err).Msg("Error while building configuration") + } + } + } + }) + + if err := p.applyConfiguration(configurationChan); err != nil { + if p.Watch { + logger.Err(err).Msg("Error while building configuration (for the first time)") return nil } + return err } - sendConfigToChannel(configurationChan, configuration) return nil } -// BuildConfiguration loads configuration either from file or a directory -// specified by 'Filename'/'Directory' and returns a 'Configuration' object. -func (p *Provider) BuildConfiguration() (*dynamic.Configuration, error) { - ctx := log.With().Str(logs.ProviderName, providerName).Logger().WithContext(context.Background()) - - if len(p.Directory) > 0 { - return p.loadFileConfigFromDirectory(ctx, p.Directory, nil) - } - - if len(p.Filename) > 0 { - return p.loadFileConfig(ctx, p.Filename, true) - } - - return nil, errors.New("error using file configuration provider, neither filename or directory defined") -} - -func (p *Provider) addWatcher(pool *safe.Pool, directory string, configurationChan chan<- dynamic.Message, callback func(chan<- dynamic.Message, fsnotify.Event)) error { +func (p *Provider) addWatcher(pool *safe.Pool, directory string, configurationChan chan<- dynamic.Message, callback func(chan<- dynamic.Message) error) error { watcher, err := fsnotify.NewWatcher() if err != nil { return fmt.Errorf("error creating file watcher: %w", err) @@ -111,6 +111,7 @@ func (p *Provider) addWatcher(pool *safe.Pool, directory string, configurationCh // Process events pool.GoCtx(func(ctx context.Context) { + logger := log.With().Str(logs.ProviderName, providerName).Logger() defer watcher.Close() for { select { @@ -121,39 +122,50 @@ func (p *Provider) addWatcher(pool *safe.Pool, directory string, configurationCh _, evtFileName := filepath.Split(evt.Name) _, confFileName := filepath.Split(p.Filename) if evtFileName == confFileName { - callback(configurationChan, evt) + err := callback(configurationChan) + if err != nil { + logger.Error().Err(err).Msg("Error occurred during watcher callback") + } } } else { - callback(configurationChan, evt) + err := callback(configurationChan) + if err != nil { + logger.Error().Err(err).Msg("Error occurred during watcher callback") + } } case err := <-watcher.Errors: - log.Error().Str(logs.ProviderName, providerName).Err(err).Msg("Watcher event error") + logger.Error().Err(err).Msg("Watcher event error") } } }) return nil } -func (p *Provider) watcherCallback(configurationChan chan<- dynamic.Message, event fsnotify.Event) { - watchItem := p.Filename - if len(p.Directory) > 0 { - watchItem = p.Directory - } - - logger := log.With().Str(logs.ProviderName, providerName).Logger() - - if _, err := os.Stat(watchItem); err != nil { - logger.Error().Err(err).Msgf("Unable to watch %s", watchItem) - return - } - - configuration, err := p.BuildConfiguration() +// applyConfiguration builds the configuration and sends it to the given configurationChan. +func (p *Provider) applyConfiguration(configurationChan chan<- dynamic.Message) error { + configuration, err := p.buildConfiguration() if err != nil { - logger.Error().Err(err).Msg("Error occurred during watcher callback") - return + return err } sendConfigToChannel(configurationChan, configuration) + return nil +} + +// buildConfiguration loads configuration either from file or a directory +// specified by 'Filename'/'Directory' and returns a 'Configuration' object. +func (p *Provider) buildConfiguration() (*dynamic.Configuration, error) { + ctx := log.With().Str(logs.ProviderName, providerName).Logger().WithContext(context.Background()) + + if len(p.Directory) > 0 { + return p.loadFileConfigFromDirectory(ctx, p.Directory, nil) + } + + if len(p.Filename) > 0 { + return p.loadFileConfig(ctx, p.Filename, true) + } + + return nil, errors.New("error using file configuration provider, neither filename nor directory is defined") } func sendConfigToChannel(configurationChan chan<- dynamic.Message, configuration *dynamic.Configuration) { diff --git a/pkg/provider/http/http.go b/pkg/provider/http/http.go index c354e5f8b..4da8a2973 100644 --- a/pkg/provider/http/http.go +++ b/pkg/provider/http/http.go @@ -2,6 +2,7 @@ package http import ( "context" + "errors" "fmt" "hash/fnv" "io" @@ -44,11 +45,11 @@ func (p *Provider) SetDefaults() { // Init the provider. func (p *Provider) Init() error { if p.Endpoint == "" { - return fmt.Errorf("non-empty endpoint is required") + return errors.New("non-empty endpoint is required") } if p.PollInterval <= 0 { - return fmt.Errorf("poll interval must be greater than 0") + return errors.New("poll interval must be greater than 0") } p.httpClient = &http.Client{ diff --git a/pkg/provider/kubernetes/crd/client_mock_test.go b/pkg/provider/kubernetes/crd/client_mock_test.go deleted file mode 100644 index 548966123..000000000 --- a/pkg/provider/kubernetes/crd/client_mock_test.go +++ /dev/null @@ -1,196 +0,0 @@ -package crd - -import ( - "fmt" - "os" - "path/filepath" - - traefikv1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1" - "github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s" - corev1 "k8s.io/api/core/v1" - kscheme "k8s.io/client-go/kubernetes/scheme" -) - -var _ Client = (*clientMock)(nil) - -func init() { - // required by k8s.MustParseYaml - err := traefikv1alpha1.AddToScheme(kscheme.Scheme) - if err != nil { - panic(err) - } -} - -type clientMock struct { - services []*corev1.Service - secrets []*corev1.Secret - endpoints []*corev1.Endpoints - - apiServiceError error - apiSecretError error - apiEndpointsError error - - ingressRoutes []*traefikv1alpha1.IngressRoute - ingressRouteTCPs []*traefikv1alpha1.IngressRouteTCP - ingressRouteUDPs []*traefikv1alpha1.IngressRouteUDP - middlewares []*traefikv1alpha1.Middleware - middlewareTCPs []*traefikv1alpha1.MiddlewareTCP - tlsOptions []*traefikv1alpha1.TLSOption - tlsStores []*traefikv1alpha1.TLSStore - traefikServices []*traefikv1alpha1.TraefikService - serversTransports []*traefikv1alpha1.ServersTransport - serversTransportTCPs []*traefikv1alpha1.ServersTransportTCP - - watchChan chan interface{} -} - -func newClientMock(paths ...string) clientMock { - var c clientMock - - for _, path := range paths { - yamlContent, err := os.ReadFile(filepath.FromSlash("./fixtures/" + path)) - if err != nil { - panic(err) - } - - k8sObjects := k8s.MustParseYaml(yamlContent) - for _, obj := range k8sObjects { - switch o := obj.(type) { - case *corev1.Service: - c.services = append(c.services, o) - case *corev1.Endpoints: - c.endpoints = append(c.endpoints, o) - case *traefikv1alpha1.IngressRoute: - c.ingressRoutes = append(c.ingressRoutes, o) - case *traefikv1alpha1.IngressRouteTCP: - c.ingressRouteTCPs = append(c.ingressRouteTCPs, o) - case *traefikv1alpha1.IngressRouteUDP: - c.ingressRouteUDPs = append(c.ingressRouteUDPs, o) - case *traefikv1alpha1.Middleware: - c.middlewares = append(c.middlewares, o) - case *traefikv1alpha1.MiddlewareTCP: - c.middlewareTCPs = append(c.middlewareTCPs, o) - case *traefikv1alpha1.TraefikService: - c.traefikServices = append(c.traefikServices, o) - case *traefikv1alpha1.TLSOption: - c.tlsOptions = append(c.tlsOptions, o) - case *traefikv1alpha1.ServersTransport: - c.serversTransports = append(c.serversTransports, o) - case *traefikv1alpha1.ServersTransportTCP: - c.serversTransportTCPs = append(c.serversTransportTCPs, o) - case *traefikv1alpha1.TLSStore: - c.tlsStores = append(c.tlsStores, o) - case *corev1.Secret: - c.secrets = append(c.secrets, o) - default: - panic(fmt.Sprintf("Unknown runtime object %+v %T", o, o)) - } - } - } - - return c -} - -func (c clientMock) GetIngressRoutes() []*traefikv1alpha1.IngressRoute { - return c.ingressRoutes -} - -func (c clientMock) GetIngressRouteTCPs() []*traefikv1alpha1.IngressRouteTCP { - return c.ingressRouteTCPs -} - -func (c clientMock) GetIngressRouteUDPs() []*traefikv1alpha1.IngressRouteUDP { - return c.ingressRouteUDPs -} - -func (c clientMock) GetMiddlewares() []*traefikv1alpha1.Middleware { - return c.middlewares -} - -func (c clientMock) GetMiddlewareTCPs() []*traefikv1alpha1.MiddlewareTCP { - return c.middlewareTCPs -} - -func (c clientMock) GetTraefikService(namespace, name string) (*traefikv1alpha1.TraefikService, bool, error) { - for _, svc := range c.traefikServices { - if svc.Namespace == namespace && svc.Name == name { - return svc, true, nil - } - } - - return nil, false, nil -} - -func (c clientMock) GetTraefikServices() []*traefikv1alpha1.TraefikService { - return c.traefikServices -} - -func (c clientMock) GetTLSOptions() []*traefikv1alpha1.TLSOption { - return c.tlsOptions -} - -func (c clientMock) GetTLSStores() []*traefikv1alpha1.TLSStore { - return c.tlsStores -} - -func (c clientMock) GetServersTransports() []*traefikv1alpha1.ServersTransport { - return c.serversTransports -} - -func (c clientMock) GetServersTransportTCPs() []*traefikv1alpha1.ServersTransportTCP { - return c.serversTransportTCPs -} - -func (c clientMock) GetTLSOption(namespace, name string) (*traefikv1alpha1.TLSOption, bool, error) { - for _, option := range c.tlsOptions { - if option.Namespace == namespace && option.Name == name { - return option, true, nil - } - } - - return nil, false, nil -} - -func (c clientMock) GetService(namespace, name string) (*corev1.Service, bool, error) { - if c.apiServiceError != nil { - return nil, false, c.apiServiceError - } - - for _, service := range c.services { - if service.Namespace == namespace && service.Name == name { - return service, true, nil - } - } - return nil, false, c.apiServiceError -} - -func (c clientMock) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) { - if c.apiEndpointsError != nil { - return nil, false, c.apiEndpointsError - } - - for _, endpoints := range c.endpoints { - if endpoints.Namespace == namespace && endpoints.Name == name { - return endpoints, true, nil - } - } - - return &corev1.Endpoints{}, false, nil -} - -func (c clientMock) GetSecret(namespace, name string) (*corev1.Secret, bool, error) { - if c.apiSecretError != nil { - return nil, false, c.apiSecretError - } - - for _, secret := range c.secrets { - if secret.Namespace == namespace && secret.Name == name { - return secret, true, nil - } - } - return nil, false, nil -} - -func (c clientMock) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) { - return c.watchChan, nil -} diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/services.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/services.yml index 8c18ffdc8..1c49644f2 100644 --- a/pkg/provider/kubernetes/crd/fixtures/tcp/services.yml +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/services.yml @@ -166,7 +166,7 @@ subsets: apiVersion: v1 kind: Service metadata: - name: external-svc + name: external-svc-tcp namespace: default spec: externalName: external.domain @@ -176,7 +176,7 @@ spec: apiVersion: v1 kind: Service metadata: - name: external.service.with.port + name: external.service.with.port.tcp namespace: default spec: externalName: external.domain @@ -186,19 +186,6 @@ spec: protocol: TCP port: 80 ---- -apiVersion: v1 -kind: Service -metadata: - name: external.service.without.port - namespace: default -spec: - externalName: external.domain - type: ExternalName - ports: - - name: http - protocol: TCP - --- apiVersion: v1 kind: Service @@ -266,7 +253,7 @@ metadata: apiVersion: v1 kind: Service metadata: - name: native-svc + name: native-svc-tcp namespace: default spec: diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_externalname.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_externalname.yml index 34a105c34..3fc47607d 100644 --- a/pkg/provider/kubernetes/crd/fixtures/tcp/with_externalname.yml +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_externalname.yml @@ -11,5 +11,5 @@ spec: routes: - match: HostSNI(`foo.com`) services: - - name: external-svc + - name: external-svc-tcp port: 8000 diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_externalname_with_port.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_externalname_with_port.yml index f31764a3e..980a8cb96 100644 --- a/pkg/provider/kubernetes/crd/fixtures/tcp/with_externalname_with_port.yml +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_externalname_with_port.yml @@ -11,5 +11,5 @@ spec: routes: - match: HostSNI(`foo.com`) services: - - name: external.service.with.port + - name: external.service.with.port.tcp port: 80 diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_externalname_without_ports.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_externalname_without_ports.yml index 1065d1380..8c0ba1a99 100644 --- a/pkg/provider/kubernetes/crd/fixtures/tcp/with_externalname_without_ports.yml +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_externalname_without_ports.yml @@ -11,4 +11,4 @@ spec: routes: - match: HostSNI(`foo.com`) services: - - name: external-svc + - name: external-svc-tcp diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_native_service_lb.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_native_service_lb.yml index f95202ec0..c2dbfca31 100644 --- a/pkg/provider/kubernetes/crd/fixtures/tcp/with_native_service_lb.yml +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_native_service_lb.yml @@ -11,6 +11,6 @@ spec: routes: - match: HostSNI(`foo.com`) services: - - name: native-svc + - name: native-svc-tcp port: 8000 nativeLB: true diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/services.yml b/pkg/provider/kubernetes/crd/fixtures/udp/services.yml index adaa7272e..90d75d059 100644 --- a/pkg/provider/kubernetes/crd/fixtures/udp/services.yml +++ b/pkg/provider/kubernetes/crd/fixtures/udp/services.yml @@ -150,7 +150,7 @@ spec: apiVersion: v1 kind: Service metadata: - name: external-svc + name: external-svc-udp namespace: default spec: externalName: external.domain @@ -160,7 +160,7 @@ spec: apiVersion: v1 kind: Service metadata: - name: external.service.with.port + name: external.service.with.port.udp namespace: default spec: externalName: external.domain @@ -170,19 +170,6 @@ spec: protocol: TCP port: 80 ---- -apiVersion: v1 -kind: Service -metadata: - name: external.service.without.port - namespace: default -spec: - externalName: external.domain - type: ExternalName - ports: - - name: http - protocol: TCP - --- kind: Endpoints apiVersion: v1 @@ -225,7 +212,7 @@ metadata: apiVersion: v1 kind: Service metadata: - name: native-svc + name: native-svc-udp namespace: default spec: diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname.yml b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname.yml index dbd919422..c0beba368 100644 --- a/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname.yml +++ b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname.yml @@ -10,5 +10,5 @@ spec: routes: - services: - - name: external-svc + - name: external-svc-udp port: 8000 diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_service.yml b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_service.yml index 302332c0c..d642b55c9 100644 --- a/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_service.yml +++ b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_service.yml @@ -10,5 +10,5 @@ spec: routes: - services: - - name: external.service.with.port + - name: external.service.with.port.udp port: 80 diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_with_port.yml b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_with_port.yml index 302332c0c..d642b55c9 100644 --- a/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_with_port.yml +++ b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_with_port.yml @@ -10,5 +10,5 @@ spec: routes: - services: - - name: external.service.with.port + - name: external.service.with.port.udp port: 80 diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_without_ports.yml b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_without_ports.yml index 2a30279bb..606f2029b 100644 --- a/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_without_ports.yml +++ b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_without_ports.yml @@ -10,4 +10,4 @@ spec: routes: - services: - - name: external-svc + - name: external-svc-udp diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/with_native_service_lb.yml b/pkg/provider/kubernetes/crd/fixtures/udp/with_native_service_lb.yml index 6942f106b..3390bbac2 100644 --- a/pkg/provider/kubernetes/crd/fixtures/udp/with_native_service_lb.yml +++ b/pkg/provider/kubernetes/crd/fixtures/udp/with_native_service_lb.yml @@ -10,6 +10,6 @@ spec: routes: - services: - - name: native-svc + - name: native-svc-udp port: 8000 nativeLB: true diff --git a/pkg/provider/kubernetes/crd/fixtures/with_default_tls_options_default_namespace.yml b/pkg/provider/kubernetes/crd/fixtures/with_default_tls_options_default_namespace.yml index ec84b589d..b477eefcb 100644 --- a/pkg/provider/kubernetes/crd/fixtures/with_default_tls_options_default_namespace.yml +++ b/pkg/provider/kubernetes/crd/fixtures/with_default_tls_options_default_namespace.yml @@ -40,7 +40,7 @@ spec: apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: - name: test.route + name: test.route.default namespace: default spec: diff --git a/pkg/provider/kubernetes/crd/fixtures/with_default_tls_store.yml b/pkg/provider/kubernetes/crd/fixtures/with_default_tls_store.yml index 36de8bd50..902a5fc2c 100644 --- a/pkg/provider/kubernetes/crd/fixtures/with_default_tls_store.yml +++ b/pkg/provider/kubernetes/crd/fixtures/with_default_tls_store.yml @@ -23,7 +23,7 @@ data: apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: - name: test.route + name: test.route.default namespace: default spec: diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/factory.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/factory.go index 56302b197..8f6ccf752 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/factory.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/factory.go @@ -50,6 +50,7 @@ type sharedInformerFactory struct { lock sync.Mutex defaultResync time.Duration customResync map[reflect.Type]time.Duration + transform cache.TransformFunc informers map[reflect.Type]cache.SharedIndexInformer // startedInformers is used for tracking which informers have been started. @@ -88,6 +89,14 @@ func WithNamespace(namespace string) SharedInformerOption { } } +// WithTransform sets a transform on all informers. +func WithTransform(transform cache.TransformFunc) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.transform = transform + return factory + } +} + // NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces. func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory { return NewSharedInformerFactoryWithOptions(client, defaultResync) @@ -192,6 +201,7 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal } informer = newFunc(f.client, resyncPeriod) + informer.SetTransform(f.transform) f.informers[informerType] = informer return informer diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index 45b6634af..46f0b628f 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -123,8 +123,6 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. logger := log.With().Str(logs.ProviderName, providerName).Logger() ctxLog := logger.WithContext(context.Background()) - logger.Warn().Msg("CRDs API Version \"traefik.io/v1alpha1\" will not be supported in Traefik v3 itself. However, an automatic migration path to the next version will be available.") - k8sClient, err := p.newK8sClient(ctxLog) if err != nil { return err @@ -719,7 +717,7 @@ func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *traef return nil, nil } if len(auth.Address) == 0 { - return nil, fmt.Errorf("forward authentication requires an address") + return nil, errors.New("forward authentication requires an address") } forwardAuth := &dynamic.ForwardAuth{ @@ -735,7 +733,7 @@ func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *traef return forwardAuth, nil } - forwardAuth.TLS = &types.ClientTLS{ + forwardAuth.TLS = &dynamic.ClientTLS{ InsecureSkipVerify: auth.TLS.InsecureSkipVerify, } @@ -756,6 +754,8 @@ func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *traef forwardAuth.TLS.Key = authSecretKey } + forwardAuth.TLS.CAOptional = auth.TLS.CAOptional + return forwardAuth, nil } @@ -812,7 +812,7 @@ func createBasicAuthMiddleware(client Client, namespace string, basicAuth *traef } if basicAuth.Secret == "" { - return nil, fmt.Errorf("auth secret must be set") + return nil, errors.New("auth secret must be set") } secret, ok, err := client.GetSecret(namespace, basicAuth.Secret) @@ -859,7 +859,7 @@ func createDigestAuthMiddleware(client Client, namespace string, digestAuth *tra } if digestAuth.Secret == "" { - return nil, fmt.Errorf("auth secret must be set") + return nil, errors.New("auth secret must be set") } secret, ok, err := client.GetSecret(namespace, digestAuth.Secret) @@ -1008,8 +1008,9 @@ func buildTLSOptions(ctx context.Context, client Client) map[string]tls.Options CAFiles: clientCAs, ClientAuthType: tlsOption.Spec.ClientAuth.ClientAuthType, }, - SniStrict: tlsOption.Spec.SniStrict, - ALPNProtocols: alpnProtocols, + SniStrict: tlsOption.Spec.SniStrict, + ALPNProtocols: alpnProtocols, + PreferServerCipherSuites: tlsOption.Spec.PreferServerCipherSuites, } } diff --git a/pkg/provider/kubernetes/crd/kubernetes_http.go b/pkg/provider/kubernetes/crd/kubernetes_http.go index ca4f78219..6e3b1f2a5 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_http.go +++ b/pkg/provider/kubernetes/crd/kubernetes_http.go @@ -112,6 +112,7 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli r := &dynamic.Router{ Middlewares: mds, Priority: route.Priority, + RuleSyntax: route.Syntax, EntryPoints: ingressRoute.Spec.EntryPoints, Rule: route.Match, Service: serviceName, diff --git a/pkg/provider/kubernetes/crd/kubernetes_tcp.go b/pkg/provider/kubernetes/crd/kubernetes_tcp.go index b0759786f..f41fe125e 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_tcp.go +++ b/pkg/provider/kubernetes/crd/kubernetes_tcp.go @@ -102,6 +102,7 @@ func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client Middlewares: mds, Rule: route.Match, Priority: route.Priority, + RuleSyntax: route.Syntax, Service: serviceName, } @@ -203,6 +204,10 @@ func (p *Provider) createLoadBalancerServerTCP(client Client, parentNamespace st } } + if service.ServersTransport == "" && service.TerminationDelay != nil { + tcpService.LoadBalancer.TerminationDelay = service.TerminationDelay + } + if service.ServersTransport != "" { tcpService.LoadBalancer.ServersTransport, err = p.makeTCPServersTransportKey(parentNamespace, service.ServersTransport) if err != nil { diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index a6d2c64ca..c851140c9 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -23,6 +23,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" kubefake "k8s.io/client-go/kubernetes/fake" + kscheme "k8s.io/client-go/kubernetes/scheme" ) var _ provider.Provider = (*Provider)(nil) @@ -30,6 +31,14 @@ var _ provider.Provider = (*Provider)(nil) func Int(v int) *int { return &v } func Bool(v bool) *bool { return &v } +func init() { + // required by k8s.MustParseYaml + err := traefikv1alpha1.AddToScheme(kscheme.Scheme) + if err != nil { + panic(err) + } +} + func TestLoadIngressRouteTCPs(t *testing.T) { testCases := []struct { desc string @@ -1035,6 +1044,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Services: map[string]*dynamic.TCPService{ "default-test.route-fdd3e9338e47a45efefc": { LoadBalancer: &dynamic.TCPServersLoadBalancer{ + TerminationDelay: Int(500), Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", @@ -1568,6 +1578,23 @@ func TestLoadIngressRouteTCPs(t *testing.T) { return } + k8sObjects, crdObjects := readResources(t, test.paths) + + kubeClient := kubefake.NewSimpleClientset(k8sObjects...) + crdClient := traefikcrdfake.NewSimpleClientset(crdObjects...) + + client := newClientImpl(kubeClient, crdClient) + + stopCh := make(chan struct{}) + + eventCh, err := client.WatchAll(nil, stopCh) + require.NoError(t, err) + + if k8sObjects != nil || crdObjects != nil { + // just wait for the first event + <-eventCh + } + p := Provider{ IngressClass: test.ingressClass, AllowCrossNamespace: true, @@ -1575,8 +1602,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) { AllowEmptyServices: test.allowEmptyServices, } - clientMock := newClientMock(test.paths...) - conf := p.loadConfigurationFromCRD(context.Background(), clientMock) + conf := p.loadConfigurationFromCRD(context.Background(), client) assert.Equal(t, test.expected, conf) }) } @@ -3063,6 +3089,15 @@ func TestLoadIngressRoutes(t *testing.T) { Options: "default-foo", }, }, + "default-test-route-default-6b204d94623b3df4370c": { + EntryPoints: []string{"web"}, + Service: "default-test-route-default-6b204d94623b3df4370c", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`)", + Priority: 12, + TLS: &dynamic.RouterTLSConfig{ + Options: "default-foo", + }, + }, }, Middlewares: map[string]*dynamic.Middleware{}, Services: map[string]*dynamic.Service{ @@ -3082,6 +3117,22 @@ func TestLoadIngressRoutes(t *testing.T) { }, }, }, + "default-test-route-default-6b204d94623b3df4370c": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: Bool(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, }, ServersTransports: map[string]*dynamic.ServersTransport{}, }, @@ -3602,7 +3653,7 @@ func TestLoadIngressRoutes(t *testing.T) { "default-forwardauth": { ForwardAuth: &dynamic.ForwardAuth{ Address: "test.com", - TLS: &types.ClientTLS{ + TLS: &dynamic.ClientTLS{ CA: "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----", Cert: "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----", Key: "-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----", @@ -4017,6 +4068,13 @@ func TestLoadIngressRoutes(t *testing.T) { Priority: 12, TLS: &dynamic.RouterTLSConfig{}, }, + "default-test-route-default-6b204d94623b3df4370c": { + EntryPoints: []string{"web"}, + Service: "default-test-route-default-6b204d94623b3df4370c", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`)", + Priority: 12, + TLS: &dynamic.RouterTLSConfig{}, + }, }, Middlewares: map[string]*dynamic.Middleware{}, Services: map[string]*dynamic.Service{ @@ -4036,6 +4094,22 @@ func TestLoadIngressRoutes(t *testing.T) { }, }, }, + "default-test-route-default-6b204d94623b3df4370c": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: Bool(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, }, ServersTransports: map[string]*dynamic.ServersTransport{}, }, @@ -4521,6 +4595,23 @@ func TestLoadIngressRoutes(t *testing.T) { return } + k8sObjects, crdObjects := readResources(t, test.paths) + + kubeClient := kubefake.NewSimpleClientset(k8sObjects...) + crdClient := traefikcrdfake.NewSimpleClientset(crdObjects...) + + client := newClientImpl(kubeClient, crdClient) + + stopCh := make(chan struct{}) + + eventCh, err := client.WatchAll(nil, stopCh) + require.NoError(t, err) + + if k8sObjects != nil || crdObjects != nil { + // just wait for the first event + <-eventCh + } + p := Provider{ IngressClass: test.ingressClass, AllowCrossNamespace: test.allowCrossNamespace, @@ -4528,8 +4619,7 @@ func TestLoadIngressRoutes(t *testing.T) { AllowEmptyServices: test.allowEmptyServices, } - clientMock := newClientMock(test.paths...) - conf := p.loadConfigurationFromCRD(context.Background(), clientMock) + conf := p.loadConfigurationFromCRD(context.Background(), client) assert.Equal(t, test.expected, conf) }) } @@ -5016,6 +5106,23 @@ func TestLoadIngressRouteUDPs(t *testing.T) { return } + k8sObjects, crdObjects := readResources(t, test.paths) + + kubeClient := kubefake.NewSimpleClientset(k8sObjects...) + crdClient := traefikcrdfake.NewSimpleClientset(crdObjects...) + + client := newClientImpl(kubeClient, crdClient) + + stopCh := make(chan struct{}) + + eventCh, err := client.WatchAll(nil, stopCh) + require.NoError(t, err) + + if k8sObjects != nil || crdObjects != nil { + // just wait for the first event + <-eventCh + } + p := Provider{ IngressClass: test.ingressClass, AllowCrossNamespace: true, @@ -5023,8 +5130,7 @@ func TestLoadIngressRouteUDPs(t *testing.T) { AllowEmptyServices: test.allowEmptyServices, } - clientMock := newClientMock(test.paths...) - conf := p.loadConfigurationFromCRD(context.Background(), clientMock) + conf := p.loadConfigurationFromCRD(context.Background(), client) assert.Equal(t, test.expected, conf) }) } @@ -6435,43 +6541,7 @@ func TestCrossNamespace(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - var k8sObjects []runtime.Object - var crdObjects []runtime.Object - for _, path := range test.paths { - yamlContent, err := os.ReadFile(filepath.FromSlash("./fixtures/" + path)) - if err != nil { - panic(err) - } - - objects := k8s.MustParseYaml(yamlContent) - for _, obj := range objects { - switch o := obj.(type) { - case *corev1.Service, *corev1.Endpoints, *corev1.Secret: - k8sObjects = append(k8sObjects, o) - case *traefikv1alpha1.IngressRoute: - crdObjects = append(crdObjects, o) - case *traefikv1alpha1.IngressRouteTCP: - crdObjects = append(crdObjects, o) - case *traefikv1alpha1.IngressRouteUDP: - crdObjects = append(crdObjects, o) - case *traefikv1alpha1.Middleware: - crdObjects = append(crdObjects, o) - case *traefikv1alpha1.MiddlewareTCP: - crdObjects = append(crdObjects, o) - case *traefikv1alpha1.TraefikService: - crdObjects = append(crdObjects, o) - case *traefikv1alpha1.TLSOption: - crdObjects = append(crdObjects, o) - case *traefikv1alpha1.TLSStore: - crdObjects = append(crdObjects, o) - case *traefikv1alpha1.ServersTransport: - crdObjects = append(crdObjects, o) - case *traefikv1alpha1.ServersTransportTCP: - crdObjects = append(crdObjects, o) - default: - } - } - } + k8sObjects, crdObjects := readResources(t, test.paths) kubeClient := kubefake.NewSimpleClientset(k8sObjects...) crdClient := traefikcrdfake.NewSimpleClientset(crdObjects...) @@ -6742,37 +6812,7 @@ func TestExternalNameService(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - var k8sObjects []runtime.Object - var crdObjects []runtime.Object - for _, path := range test.paths { - yamlContent, err := os.ReadFile(filepath.FromSlash("./fixtures/" + path)) - if err != nil { - panic(err) - } - - objects := k8s.MustParseYaml(yamlContent) - for _, obj := range objects { - switch o := obj.(type) { - case *corev1.Service, *corev1.Endpoints, *corev1.Secret: - k8sObjects = append(k8sObjects, o) - case *traefikv1alpha1.IngressRoute: - crdObjects = append(crdObjects, o) - case *traefikv1alpha1.IngressRouteTCP: - crdObjects = append(crdObjects, o) - case *traefikv1alpha1.IngressRouteUDP: - crdObjects = append(crdObjects, o) - case *traefikv1alpha1.Middleware: - crdObjects = append(crdObjects, o) - case *traefikv1alpha1.TraefikService: - crdObjects = append(crdObjects, o) - case *traefikv1alpha1.TLSOption: - crdObjects = append(crdObjects, o) - case *traefikv1alpha1.TLSStore: - crdObjects = append(crdObjects, o) - default: - } - } - } + k8sObjects, crdObjects := readResources(t, test.paths) kubeClient := kubefake.NewSimpleClientset(k8sObjects...) crdClient := traefikcrdfake.NewSimpleClientset(crdObjects...) @@ -6955,37 +6995,7 @@ func TestNativeLB(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - var k8sObjects []runtime.Object - var crdObjects []runtime.Object - for _, path := range test.paths { - yamlContent, err := os.ReadFile(filepath.FromSlash("./fixtures/" + path)) - if err != nil { - panic(err) - } - - objects := k8s.MustParseYaml(yamlContent) - for _, obj := range objects { - switch o := obj.(type) { - case *corev1.Service, *corev1.Endpoints, *corev1.Secret: - k8sObjects = append(k8sObjects, o) - case *traefikv1alpha1.IngressRoute: - crdObjects = append(crdObjects, o) - case *traefikv1alpha1.IngressRouteTCP: - crdObjects = append(crdObjects, o) - case *traefikv1alpha1.IngressRouteUDP: - crdObjects = append(crdObjects, o) - case *traefikv1alpha1.Middleware: - crdObjects = append(crdObjects, o) - case *traefikv1alpha1.TraefikService: - crdObjects = append(crdObjects, o) - case *traefikv1alpha1.TLSOption: - crdObjects = append(crdObjects, o) - case *traefikv1alpha1.TLSStore: - crdObjects = append(crdObjects, o) - default: - } - } - } + k8sObjects, crdObjects := readResources(t, test.paths) kubeClient := kubefake.NewSimpleClientset(k8sObjects...) crdClient := traefikcrdfake.NewSimpleClientset(crdObjects...) @@ -7071,3 +7081,28 @@ func TestCreateBasicAuthCredentials(t *testing.T) { assert.Equal(t, "$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", hashedPassword) assert.True(t, auth.CheckSecret("test2", hashedPassword)) } + +func readResources(t *testing.T, paths []string) ([]runtime.Object, []runtime.Object) { + t.Helper() + + var k8sObjects []runtime.Object + var crdObjects []runtime.Object + for _, path := range paths { + yamlContent, err := os.ReadFile(filepath.FromSlash("./fixtures/" + path)) + if err != nil { + panic(err) + } + + objects := k8s.MustParseYaml(yamlContent) + for _, obj := range objects { + switch obj.GetObjectKind().GroupVersionKind().Group { + case "traefik.io": + crdObjects = append(crdObjects, obj) + default: + k8sObjects = append(k8sObjects, obj) + } + } + } + + return k8sObjects, crdObjects +} diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go index 6471381ae..475d160d4 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go @@ -33,6 +33,9 @@ type Route struct { // Priority defines the router's priority. // More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority Priority int `json:"priority,omitempty"` + // Syntax defines the router's rule syntax. + // More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rulesyntax + Syntax string `json:"syntax,omitempty"` // Services defines the list of Service. // It can contain any combination of TraefikService and/or reference to a Kubernetes Service. Services []Service `json:"services,omitempty"` diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroutetcp.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroutetcp.go index 5669e8f4f..9ff6897db 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroutetcp.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroutetcp.go @@ -29,6 +29,9 @@ type RouteTCP struct { // Priority defines the router's priority. // More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority_1 Priority int `json:"priority,omitempty"` + // Syntax defines the router's rule syntax. + // More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rulesyntax_1 + Syntax string `json:"syntax,omitempty"` // Services defines the list of TCP services. Services []ServiceTCP `json:"services,omitempty"` // Middlewares defines the list of references to MiddlewareTCP resources. @@ -69,6 +72,13 @@ type ServiceTCP struct { Port intstr.IntOrString `json:"port"` // Weight defines the weight used when balancing requests between multiple Kubernetes Service. Weight *int `json:"weight,omitempty"` + // TerminationDelay defines the deadline that the proxy sets, after one of its connected peers indicates + // it has closed the writing capability of its connection, to close the reading capability as well, + // hence fully terminating the connection. + // It is a duration in milliseconds, defaulting to 100. + // A negative value means an infinite deadline (i.e. the reading capability is never closed). + // Deprecated: TerminationDelay is not supported APIVersion traefik.io/v1, please use ServersTransport to configure the TerminationDelay instead. + TerminationDelay *int `json:"terminationDelay,omitempty"` // ProxyProtocol defines the PROXY protocol configuration. // More info: https://doc.traefik.io/traefik/v3.0/routing/services/#proxy-protocol ProxyProtocol *dynamic.ProxyProtocol `json:"proxyProtocol,omitempty"` diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go index 1378dc85d..f4ade76a0 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go @@ -171,6 +171,9 @@ type ClientTLS struct { CertSecret string `json:"certSecret,omitempty"` // InsecureSkipVerify defines whether the server certificates should be validated. InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"` + + // Deprecated: TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634). + CAOptional *bool `json:"caOptional,omitempty"` } // +k8s:deepcopy-gen=true diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go index d15402a26..f58736ae8 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go @@ -26,9 +26,13 @@ type MiddlewareTCPSpec struct { // InFlightConn defines the InFlightConn middleware configuration. InFlightConn *dynamic.TCPInFlightConn `json:"inFlightConn,omitempty"` // IPWhiteList defines the IPWhiteList middleware configuration. + // This middleware accepts/refuses connections based on the client IP. // Deprecated: please use IPAllowList instead. + // More info: https://doc.traefik.io/traefik/v3.0/middlewares/tcp/ipwhitelist/ IPWhiteList *dynamic.TCPIPWhiteList `json:"ipWhiteList,omitempty"` // IPAllowList defines the IPAllowList middleware configuration. + // This middleware accepts/refuses connections based on the client IP. + // More info: https://doc.traefik.io/traefik/v3.0/middlewares/tcp/ipallowlist/ IPAllowList *dynamic.TCPIPAllowList `json:"ipAllowList,omitempty"` } diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/tlsoption.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/tlsoption.go index f8132c138..8b330c468 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/tlsoption.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/tlsoption.go @@ -44,6 +44,11 @@ type TLSOptionSpec struct { // ALPNProtocols defines the list of supported application level protocols for the TLS handshake, in order of preference. // More info: https://doc.traefik.io/traefik/v3.0/https/tls/#alpn-protocols ALPNProtocols []string `json:"alpnProtocols,omitempty"` + + // PreferServerCipherSuites defines whether the server chooses a cipher suite among his own instead of among the client's. + // It is enabled automatically when minVersion or maxVersion is set. + // Deprecated: https://github.com/golang/go/issues/45430 + PreferServerCipherSuites *bool `json:"preferServerCipherSuites,omitempty"` } // +k8s:deepcopy-gen=true diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go index 40c9b979d..547f0de4e 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go @@ -146,6 +146,11 @@ func (in *ClientAuth) DeepCopy() *ClientAuth { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClientTLS) DeepCopyInto(out *ClientTLS) { *out = *in + if in.CAOptional != nil { + in, out := &in.CAOptional, &out.CAOptional + *out = new(bool) + **out = **in + } return } @@ -213,7 +218,7 @@ func (in *ForwardAuth) DeepCopyInto(out *ForwardAuth) { if in.TLS != nil { in, out := &in.TLS, &out.TLS *out = new(ClientTLS) - **out = **in + (*in).DeepCopyInto(*out) } if in.AddAuthCookiesToResponse != nil { in, out := &in.AddAuthCookiesToResponse, &out.AddAuthCookiesToResponse @@ -777,7 +782,7 @@ func (in *MiddlewareSpec) DeepCopyInto(out *MiddlewareSpec) { if in.ContentType != nil { in, out := &in.ContentType, &out.ContentType *out = new(dynamic.ContentType) - **out = **in + (*in).DeepCopyInto(*out) } if in.GrpcWeb != nil { in, out := &in.GrpcWeb, &out.GrpcWeb @@ -1318,6 +1323,11 @@ func (in *ServiceTCP) DeepCopyInto(out *ServiceTCP) { *out = new(int) **out = **in } + if in.TerminationDelay != nil { + in, out := &in.TerminationDelay, &out.TerminationDelay + *out = new(int) + **out = **in + } if in.ProxyProtocol != nil { in, out := &in.ProxyProtocol, &out.ProxyProtocol *out = new(dynamic.ProxyProtocol) @@ -1517,6 +1527,11 @@ func (in *TLSOptionSpec) DeepCopyInto(out *TLSOptionSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.PreferServerCipherSuites != nil { + in, out := &in.PreferServerCipherSuites, &out.PreferServerCipherSuites + *out = new(bool) + **out = **in + } return } diff --git a/pkg/provider/kubernetes/gateway/client.go b/pkg/provider/kubernetes/gateway/client.go index 709be079d..dc957e966 100644 --- a/pkg/provider/kubernetes/gateway/client.go +++ b/pkg/provider/kubernetes/gateway/client.go @@ -19,6 +19,7 @@ import ( "k8s.io/client-go/tools/clientcmd" gatev1 "sigs.k8s.io/gateway-api/apis/v1" gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" gateclientset "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" gateinformers "sigs.k8s.io/gateway-api/pkg/client/informers/externalversions" ) @@ -34,13 +35,7 @@ func (reh *resourceEventHandler) OnAdd(obj interface{}, isInInitialList bool) { } func (reh *resourceEventHandler) OnUpdate(oldObj, newObj interface{}) { - switch oldObj.(type) { - case *gatev1.GatewayClass: - // Skip update for gateway classes. We only manage addition or deletion for this cluster-wide resource. - return - default: - eventHandlerFunc(reh.ev, newObj) - } + eventHandlerFunc(reh.ev, newObj) } func (reh *resourceEventHandler) OnDelete(obj interface{}) { @@ -59,6 +54,7 @@ type Client interface { GetHTTPRoutes(namespaces []string) ([]*gatev1.HTTPRoute, error) GetTCPRoutes(namespaces []string) ([]*gatev1alpha2.TCPRoute, error) GetTLSRoutes(namespaces []string) ([]*gatev1alpha2.TLSRoute, error) + GetReferenceGrants(namespace string) ([]*gatev1beta1.ReferenceGrant, error) GetService(namespace, name string) (*corev1.Service, bool, error) GetSecret(namespace, name string) (*corev1.Secret, bool, error) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) @@ -189,9 +185,6 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< return nil, err } - // TODO manage Reference Policy - // https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.ReferencePolicy - for _, ns := range namespaces { factoryGateway := gateinformers.NewSharedInformerFactoryWithOptions(c.csGateway, resyncPeriod, gateinformers.WithNamespace(ns)) _, err = factoryGateway.Gateway().V1().Gateways().Informer().AddEventHandler(eventHandler) @@ -210,6 +203,10 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< if err != nil { return nil, err } + _, err = factoryGateway.Gateway().V1beta1().ReferenceGrants().Informer().AddEventHandler(eventHandler) + if err != nil { + return nil, err + } factoryKube := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns)) _, err = factoryKube.Core().V1().Services().Informer().AddEventHandler(eventHandler) @@ -363,6 +360,21 @@ func (c *clientWrapper) GetTLSRoutes(namespaces []string) ([]*gatev1alpha2.TLSRo return tlsRoutes, nil } +func (c *clientWrapper) GetReferenceGrants(namespace string) ([]*gatev1beta1.ReferenceGrant, error) { + if !c.isWatchedNamespace(namespace) { + log.Warn().Msgf("Failed to get ReferenceGrants: %q is not within watched namespaces", namespace) + + return nil, fmt.Errorf("failed to get ReferenceGrants: namespace %s is not within watched namespaces", namespace) + } + + referenceGrants, err := c.factoriesGateway[c.lookupNamespace(namespace)].Gateway().V1beta1().ReferenceGrants().Lister().ReferenceGrants(namespace).List(labels.Everything()) + if err != nil { + return nil, err + } + + return referenceGrants, nil +} + func (c *clientWrapper) GetGateways() []*gatev1.Gateway { var result []*gatev1.Gateway @@ -388,7 +400,7 @@ func (c *clientWrapper) UpdateGatewayClassStatus(gatewayClass *gatev1.GatewayCla var newConditions []metav1.Condition for _, cond := range gc.Status.Conditions { // No update for identical condition. - if cond.Type == condition.Type && cond.Status == condition.Status { + if cond.Type == condition.Type && cond.Status == condition.Status && cond.ObservedGeneration == condition.ObservedGeneration { return nil } @@ -470,7 +482,7 @@ func conditionsEquals(conditionsA, conditionsB []metav1.Condition) bool { for _, conditionA := range conditionsA { for _, conditionB := range conditionsB { if conditionA.Type == conditionB.Type { - if conditionA.Reason != conditionB.Reason || conditionA.Status != conditionB.Status || conditionA.Message != conditionB.Message { + if conditionA.Reason != conditionB.Reason || conditionA.Status != conditionB.Status || conditionA.Message != conditionB.Message || conditionA.ObservedGeneration != conditionB.ObservedGeneration { return false } conditionMatches++ diff --git a/pkg/provider/kubernetes/gateway/client_mock_test.go b/pkg/provider/kubernetes/gateway/client_mock_test.go index 01611de40..b19dd9609 100644 --- a/pkg/provider/kubernetes/gateway/client_mock_test.go +++ b/pkg/provider/kubernetes/gateway/client_mock_test.go @@ -12,6 +12,7 @@ import ( kscheme "k8s.io/client-go/kubernetes/scheme" gatev1 "sigs.k8s.io/gateway-api/apis/v1" gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) var _ Client = (*clientMock)(nil) @@ -23,6 +24,11 @@ func init() { panic(err) } + err = gatev1beta1.AddToScheme(kscheme.Scheme) + if err != nil { + panic(err) + } + err = gatev1.AddToScheme(kscheme.Scheme) if err != nil { panic(err) @@ -39,11 +45,12 @@ type clientMock struct { apiSecretError error apiEndpointsError error - gatewayClasses []*gatev1.GatewayClass - gateways []*gatev1.Gateway - httpRoutes []*gatev1.HTTPRoute - tcpRoutes []*gatev1alpha2.TCPRoute - tlsRoutes []*gatev1alpha2.TLSRoute + gatewayClasses []*gatev1.GatewayClass + gateways []*gatev1.Gateway + httpRoutes []*gatev1.HTTPRoute + tcpRoutes []*gatev1alpha2.TCPRoute + tlsRoutes []*gatev1alpha2.TLSRoute + referenceGrants []*gatev1beta1.ReferenceGrant watchChan chan interface{} } @@ -78,6 +85,8 @@ func newClientMock(paths ...string) clientMock { c.tcpRoutes = append(c.tcpRoutes, o) case *gatev1alpha2.TLSRoute: c.tlsRoutes = append(c.tlsRoutes, o) + case *gatev1beta1.ReferenceGrant: + c.referenceGrants = append(c.referenceGrants, o) default: panic(fmt.Sprintf("Unknown runtime object %+v %T", o, o)) } @@ -190,6 +199,16 @@ func (c clientMock) GetTLSRoutes(namespaces []string) ([]*gatev1alpha2.TLSRoute, return tlsRoutes, nil } +func (c clientMock) GetReferenceGrants(namespace string) ([]*gatev1beta1.ReferenceGrant, error) { + var referenceGrants []*gatev1beta1.ReferenceGrant + for _, referenceGrant := range c.referenceGrants { + if inNamespace(referenceGrant.ObjectMeta, namespace) { + referenceGrants = append(referenceGrants, referenceGrant) + } + } + return referenceGrants, nil +} + func (c clientMock) GetService(namespace, name string) (*corev1.Service, bool, error) { if c.apiServiceError != nil { return nil, false, c.apiServiceError diff --git a/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret.yml b/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret.yml new file mode 100644 index 000000000..ba5f1a9fb --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret.yml @@ -0,0 +1,78 @@ +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: ReferenceGrant +metadata: + name: secret-from-default + namespace: secret-namespace +spec: + from: + - group: gateway.networking.k8s.io + kind: Gateway + namespace: default + to: + - group: "" + kind: Secret +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: secret-namespace + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: my-gateway-class +spec: + controllerName: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - name: tls + protocol: TLS + port: 9000 + hostname: foo.example.com + tls: + mode: Terminate # Default mode + certificateRefs: + - kind: Secret + name: supersecret + namespace: secret-namespace + group: "" + allowedRoutes: + kinds: + - kind: TCPRoute + group: gateway.networking.k8s.io + namespaces: + from: Same + +--- +kind: TCPRoute +apiVersion: gateway.networking.k8s.io/v1alpha2 +metadata: + name: tcp-app-1 + namespace: default +spec: + parentRefs: + - name: my-gateway + kind: Gateway + group: gateway.networking.k8s.io + rules: + - backendRefs: + - name: whoamitcp + port: 9000 + weight: 1 + kind: Service + group: "" diff --git a/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret_missing.yml b/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret_missing.yml new file mode 100644 index 000000000..660ff18b6 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret_missing.yml @@ -0,0 +1,64 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: secret-namespace + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: my-gateway-class +spec: + controllerName: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - name: tls + protocol: TLS + port: 9000 + hostname: foo.example.com + tls: + mode: Terminate # Default mode + certificateRefs: + - kind: Secret + name: supersecret + namespace: secret-namespace + group: "" + allowedRoutes: + kinds: + - kind: TCPRoute + group: gateway.networking.k8s.io + namespaces: + from: Same + +--- +kind: TCPRoute +apiVersion: gateway.networking.k8s.io/v1alpha2 +metadata: + name: tcp-app-1 + namespace: default +spec: + parentRefs: + - name: my-gateway + kind: Gateway + group: gateway.networking.k8s.io + rules: + - backendRefs: + - name: whoamitcp + port: 9000 + weight: 1 + kind: Service + group: "" diff --git a/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret_not_matching_from.yml b/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret_not_matching_from.yml new file mode 100644 index 000000000..47aba19d8 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret_not_matching_from.yml @@ -0,0 +1,78 @@ +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: ReferenceGrant +metadata: + name: secret-from-default + namespace: secret-namespace +spec: + from: + - group: gateway.networking.k8s.io + kind: Gateway + namespace: differentnamespace + to: + - group: "" + kind: Secret +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: secret-namespace + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: my-gateway-class +spec: + controllerName: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - name: tls + protocol: TLS + port: 9000 + hostname: foo.example.com + tls: + mode: Terminate # Default mode + certificateRefs: + - kind: Secret + name: supersecret + namespace: secret-namespace + group: "" + allowedRoutes: + kinds: + - kind: TCPRoute + group: gateway.networking.k8s.io + namespaces: + from: Same + +--- +kind: TCPRoute +apiVersion: gateway.networking.k8s.io/v1alpha2 +metadata: + name: tcp-app-1 + namespace: default +spec: + parentRefs: + - name: my-gateway + kind: Gateway + group: gateway.networking.k8s.io + rules: + - backendRefs: + - name: whoamitcp + port: 9000 + weight: 1 + kind: Service + group: "" diff --git a/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret_not_matching_to.yml b/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret_not_matching_to.yml new file mode 100644 index 000000000..e08fa7a42 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret_not_matching_to.yml @@ -0,0 +1,79 @@ +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: ReferenceGrant +metadata: + name: secret-from-default + namespace: secret-namespace +spec: + from: + - group: gateway.networking.k8s.io + kind: Gateway + namespace: default + to: + - group: "" + kind: Secret + name: differentsecret +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: secret-namespace + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: my-gateway-class +spec: + controllerName: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - name: tls + protocol: TLS + port: 9000 + hostname: foo.example.com + tls: + mode: Terminate # Default mode + certificateRefs: + - kind: Secret + name: supersecret + namespace: secret-namespace + group: "" + allowedRoutes: + kinds: + - kind: TCPRoute + group: gateway.networking.k8s.io + namespaces: + from: Same + +--- +kind: TCPRoute +apiVersion: gateway.networking.k8s.io/v1alpha2 +metadata: + name: tcp-app-1 + namespace: default +spec: + parentRefs: + - name: my-gateway + kind: Gateway + group: gateway.networking.k8s.io + rules: + - backendRefs: + - name: whoamitcp + port: 9000 + weight: 1 + kind: Service + group: "" diff --git a/pkg/provider/kubernetes/gateway/kubernetes.go b/pkg/provider/kubernetes/gateway/kubernetes.go index c86467b0f..724d5b841 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes.go +++ b/pkg/provider/kubernetes/gateway/kubernetes.go @@ -34,11 +34,14 @@ import ( "k8s.io/utils/ptr" "k8s.io/utils/strings/slices" gatev1 "sigs.k8s.io/gateway-api/apis/v1" + gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) const ( providerName = "kubernetesgateway" + groupCore = "core" + kindGateway = "Gateway" kindTraefikService = "TraefikService" kindHTTPRoute = "HTTPRoute" @@ -230,6 +233,7 @@ func (p *Provider) loadConfigurationFromGateway(ctx context.Context, client Clie err := client.UpdateGatewayClassStatus(gatewayClass, metav1.Condition{ Type: string(gatev1.GatewayClassConditionStatusAccepted), Status: metav1.ConditionTrue, + ObservedGeneration: gatewayClass.Generation, Reason: "Handled", Message: "Handled by Traefik controller", LastTransitionTime: metav1.Now(), @@ -347,6 +351,7 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway * Name: listener.Name, SupportedKinds: []gatev1.RouteGroupKind{}, Conditions: []metav1.Condition{}, + // AttachedRoutes: 0 TODO Set to number of Routes associated with a Listener regardless of Gateway or Route status } supportedKinds, conditions := supportedRouteKinds(listener.Protocol) @@ -355,9 +360,8 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway * continue } - listenerStatuses[i].SupportedKinds = supportedKinds - routeKinds, conditions := getAllowedRouteKinds(gateway, listener, supportedKinds) + listenerStatuses[i].SupportedKinds = routeKinds if len(conditions) > 0 { listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, conditions...) continue @@ -473,7 +477,7 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway * certificateRef := listener.TLS.CertificateRefs[0] if certificateRef.Kind == nil || *certificateRef.Kind != "Secret" || - certificateRef.Group == nil || (*certificateRef.Group != "" && *certificateRef.Group != "core") { + certificateRef.Group == nil || (*certificateRef.Group != "" && *certificateRef.Group != groupCore) { // update "ResolvedRefs" status true with "InvalidCertificateRef" reason listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ Type: string(gatev1.ListenerConditionResolvedRefs), @@ -481,43 +485,74 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway * ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), Reason: string(gatev1.ListenerReasonInvalidCertificateRef), - Message: fmt.Sprintf("Unsupported TLS CertificateRef group/kind: %v/%v", certificateRef.Group, certificateRef.Kind), + Message: fmt.Sprintf("Unsupported TLS CertificateRef group/kind: %s/%s", groupToString(certificateRef.Group), kindToString(certificateRef.Kind)), }) continue } - // TODO Support ReferencePolicy to support cross namespace references. + certificateNamespace := gateway.Namespace if certificateRef.Namespace != nil && string(*certificateRef.Namespace) != gateway.Namespace { - listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ - Type: string(gatev1.ListenerConditionResolvedRefs), - Status: metav1.ConditionFalse, - ObservedGeneration: gateway.Generation, - LastTransitionTime: metav1.Now(), - Reason: string(gatev1.ListenerReasonInvalidCertificateRef), - Message: "Cross namespace secrets are not supported", - }) - - continue + certificateNamespace = string(*certificateRef.Namespace) } - configKey := gateway.Namespace + "/" + string(certificateRef.Name) - if _, tlsExists := tlsConfigs[configKey]; !tlsExists { - tlsConf, err := getTLS(client, certificateRef.Name, gateway.Namespace) + if certificateNamespace != gateway.Namespace { + referenceGrants, err := client.GetReferenceGrants(certificateNamespace) if err != nil { - // update "ResolvedRefs" status true with "InvalidCertificateRef" reason listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), - Reason: string(gatev1.ListenerReasonInvalidCertificateRef), - Message: fmt.Sprintf("Error while retrieving certificate: %v", err), + Reason: string(gatev1.ListenerReasonRefNotPermitted), + Message: fmt.Sprintf("Cannot find any ReferenceGrant: %v", err), + }) + continue + } + + referenceGrants = filterReferenceGrantsFrom(referenceGrants, "gateway.networking.k8s.io", "Gateway", gateway.Namespace) + referenceGrants = filterReferenceGrantsTo(referenceGrants, groupCore, "Secret", string(certificateRef.Name)) + if len(referenceGrants) == 0 { + listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ + Type: string(gatev1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.ListenerReasonRefNotPermitted), + Message: "Required ReferenceGrant for cross namespace secret reference is missing", }) continue } + } + configKey := certificateNamespace + "/" + string(certificateRef.Name) + if _, tlsExists := tlsConfigs[configKey]; !tlsExists { + tlsConf, err := getTLS(client, certificateRef.Name, certificateNamespace) + if err != nil { + // update "ResolvedRefs" status false with "InvalidCertificateRef" reason + // update "Programmed" status false with "Invalid" reason + listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, + metav1.Condition{ + Type: string(gatev1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.ListenerReasonInvalidCertificateRef), + Message: fmt.Sprintf("Error while retrieving certificate: %v", err), + }, + metav1.Condition{ + Type: string(gatev1.ListenerConditionProgrammed), + Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.ListenerReasonInvalid), + Message: fmt.Sprintf("Error while retrieving certificate: %v", err), + }, + ) + + continue + } tlsConfigs[configKey] = tlsConf } } @@ -547,15 +582,32 @@ func (p *Provider) makeGatewayStatus(gateway *gatev1.Gateway, listenerStatuses [ var result error for i, listener := range listenerStatuses { if len(listener.Conditions) == 0 { - // GatewayConditionReady "Ready", GatewayConditionReason "ListenerReady" - listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ - Type: string(gatev1.ListenerReasonAccepted), - Status: metav1.ConditionTrue, - ObservedGeneration: gateway.Generation, - LastTransitionTime: metav1.Now(), - Reason: "ListenerReady", - Message: "No error found", - }) + listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, + metav1.Condition{ + Type: string(gatev1.ListenerConditionAccepted), + Status: metav1.ConditionTrue, + ObservedGeneration: gateway.Generation, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.ListenerReasonAccepted), + Message: "No error found", + }, + metav1.Condition{ + Type: string(gatev1.ListenerConditionResolvedRefs), + Status: metav1.ConditionTrue, + ObservedGeneration: gateway.Generation, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.ListenerReasonResolvedRefs), + Message: "No error found", + }, + metav1.Condition{ + Type: string(gatev1.ListenerConditionProgrammed), + Status: metav1.ConditionTrue, + ObservedGeneration: gateway.Generation, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.ListenerReasonProgrammed), + Message: "No error found", + }, + ) continue } @@ -564,6 +616,7 @@ func (p *Provider) makeGatewayStatus(gateway *gatev1.Gateway, listenerStatuses [ result = multierror.Append(result, errors.New(condition.Message)) } } + gatewayStatus.Listeners = listenerStatuses if result != nil { // GatewayConditionReady "Ready", GatewayConditionReason "ListenersNotValid" @@ -579,15 +632,22 @@ func (p *Provider) makeGatewayStatus(gateway *gatev1.Gateway, listenerStatuses [ return gatewayStatus, result } - gatewayStatus.Listeners = listenerStatuses - gatewayStatus.Conditions = append(gatewayStatus.Conditions, // update "Accepted" status with "Accepted" reason metav1.Condition{ Type: string(gatev1.GatewayConditionAccepted), Status: metav1.ConditionTrue, ObservedGeneration: gateway.Generation, - Reason: string(gatev1.GatewayConditionAccepted), + Reason: string(gatev1.GatewayReasonAccepted), + Message: "Gateway successfully scheduled", + LastTransitionTime: metav1.Now(), + }, + // update "Programmed" status with "Programmed" reason + metav1.Condition{ + Type: string(gatev1.GatewayConditionProgrammed), + Status: metav1.ConditionTrue, + ObservedGeneration: gateway.Generation, + Reason: string(gatev1.GatewayReasonProgrammed), Message: "Gateway successfully scheduled", LastTransitionTime: metav1.Now(), }, @@ -646,7 +706,7 @@ func getAllowedRouteKinds(gateway *gatev1.Gateway, listener gatev1.Listener, sup } var ( - routeKinds []gatev1.RouteGroupKind + routeKinds = []gatev1.RouteGroupKind{} conditions []metav1.Condition ) @@ -662,12 +722,12 @@ func getAllowedRouteKinds(gateway *gatev1.Gateway, listener gatev1.Listener, sup if !isSupported { conditions = append(conditions, metav1.Condition{ - Type: string(gatev1.ListenerConditionAccepted), - Status: metav1.ConditionTrue, + Type: string(gatev1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), Reason: string(gatev1.ListenerReasonInvalidRouteKinds), - Message: fmt.Sprintf("Listener protocol %q does not support RouteGroupKind %v/%s", listener.Protocol, routeKind.Group, routeKind.Kind), + Message: fmt.Sprintf("Listener protocol %q does not support RouteGroupKind %s/%s", listener.Protocol, groupToString(routeKind.Group), routeKind.Kind), }) continue } @@ -702,7 +762,7 @@ func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, li routes, err := client.GetHTTPRoutes(namespaces) if err != nil { - // update "ResolvedRefs" status true with "InvalidRoutesRef" reason + // update "ResolvedRefs" status true with "RefNotPermitted" reason return []metav1.Condition{{ Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, @@ -747,7 +807,7 @@ func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, li for _, routeRule := range route.Spec.Rules { rule, err := extractRule(routeRule, hostRule) if err != nil { - // update "ResolvedRefs" status true with "DroppedRoutes" reason + // update "ResolvedRefs" status true with "UnsupportedPathOrHeaderType" reason conditions = append(conditions, metav1.Condition{ Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, @@ -760,6 +820,7 @@ func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, li router := dynamic.Router{ Rule: rule, + RuleSyntax: "v3", EntryPoints: []string{ep}, } @@ -898,6 +959,7 @@ func gatewayTCPRouteToTCPConf(ctx context.Context, ep string, listener gatev1.Li router := dynamic.TCPRouter{ Rule: "HostSNI(`*`)", EntryPoints: []string{ep}, + RuleSyntax: "v3", } if listener.Protocol == gatev1.TLSProtocolType && listener.TLS != nil { @@ -1036,7 +1098,7 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener gatev1.Li Type: string(gatev1.GatewayClassConditionStatusAccepted), Status: metav1.ConditionFalse, ObservedGeneration: gateway.Generation, - Reason: string(gatev1.ListenerConditionConflicted), + Reason: string(gatev1.ListenerReasonHostnameConflict), Message: fmt.Sprintf("No hostname match between listener: %v and route: %v", listener.Hostname, route.Spec.Hostnames), LastTransitionTime: metav1.Now(), }) @@ -1047,7 +1109,7 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener gatev1.Li rule, err := hostSNIRule(hostnames) if err != nil { - // update "ResolvedRefs" status true with "DroppedRoutes" reason + // update "ResolvedRefs" status true with "InvalidHostnames" reason conditions = append(conditions, metav1.Condition{ Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, @@ -1062,6 +1124,7 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener gatev1.Li router := dynamic.TCPRouter{ Rule: rule, + RuleSyntax: "v3", EntryPoints: []string{ep}, TLS: &dynamic.RouterTCPTLSConfig{ Passthrough: listener.TLS.Mode != nil && *listener.TLS.Mode == gatev1.TLSModePassthrough, @@ -1098,7 +1161,7 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener gatev1.Li wrrService, subServices, err := loadTCPServices(client, route.Namespace, routeRule.BackendRefs) if err != nil { - // update "ResolvedRefs" status true with "DroppedRoutes" reason + // update "ResolvedRefs" status true with "InvalidBackendRefs" reason conditions = append(conditions, metav1.Condition{ Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, @@ -1385,7 +1448,7 @@ func extractHeaderRules(headers []gatev1.HTTPHeaderMatch) ([]string, error) { switch *header.Type { case gatev1.HeaderMatchExact: - headerRules = append(headerRules, fmt.Sprintf("Headers(`%s`,`%s`)", header.Name, header.Value)) + headerRules = append(headerRules, fmt.Sprintf("Header(`%s`,`%s`)", header.Name, header.Value)) default: return nil, fmt.Errorf("unsupported header match type %s", *header.Type) } @@ -1513,7 +1576,7 @@ func loadServices(client Client, namespace string, backendRefs []gatev1.HTTPBack continue } - if *backendRef.Group != "" && *backendRef.Group != "core" && *backendRef.Kind != "Service" { + if *backendRef.Group != "" && *backendRef.Group != groupCore && *backendRef.Kind != "Service" { return nil, nil, fmt.Errorf("unsupported HTTPBackendRef %s/%s/%s", *backendRef.Group, *backendRef.Kind, backendRef.Name) } @@ -1636,7 +1699,7 @@ func loadTCPServices(client Client, namespace string, backendRefs []gatev1.Backe continue } - if *backendRef.Group != "" && *backendRef.Group != "core" && *backendRef.Kind != "Service" { + if *backendRef.Group != "" && *backendRef.Group != groupCore && *backendRef.Kind != "Service" { return nil, nil, fmt.Errorf("unsupported BackendRef %s/%s/%s", *backendRef.Group, *backendRef.Kind, backendRef.Name) } @@ -1867,3 +1930,65 @@ func makeListenerKey(l gatev1.Listener) string { return fmt.Sprintf("%s|%s|%d", l.Protocol, hostname, l.Port) } + +func filterReferenceGrantsFrom(referenceGrants []*gatev1beta1.ReferenceGrant, group, kind, namespace string) []*gatev1beta1.ReferenceGrant { + var matchingReferenceGrants []*gatev1beta1.ReferenceGrant + for _, referenceGrant := range referenceGrants { + if referenceGrantMatchesFrom(referenceGrant, group, kind, namespace) { + matchingReferenceGrants = append(matchingReferenceGrants, referenceGrant) + } + } + return matchingReferenceGrants +} + +func referenceGrantMatchesFrom(referenceGrant *gatev1beta1.ReferenceGrant, group, kind, namespace string) bool { + for _, from := range referenceGrant.Spec.From { + sanitizedGroup := string(from.Group) + if sanitizedGroup == "" { + sanitizedGroup = groupCore + } + if string(from.Namespace) != namespace || string(from.Kind) != kind || sanitizedGroup != group { + continue + } + return true + } + return false +} + +func filterReferenceGrantsTo(referenceGrants []*gatev1beta1.ReferenceGrant, group, kind, name string) []*gatev1beta1.ReferenceGrant { + var matchingReferenceGrants []*gatev1beta1.ReferenceGrant + for _, referenceGrant := range referenceGrants { + if referenceGrantMatchesTo(referenceGrant, group, kind, name) { + matchingReferenceGrants = append(matchingReferenceGrants, referenceGrant) + } + } + return matchingReferenceGrants +} + +func referenceGrantMatchesTo(referenceGrant *gatev1beta1.ReferenceGrant, group, kind, name string) bool { + for _, to := range referenceGrant.Spec.To { + sanitizedGroup := string(to.Group) + if sanitizedGroup == "" { + sanitizedGroup = groupCore + } + if string(to.Kind) != kind || sanitizedGroup != group || (to.Name != nil && string(*to.Name) != name) { + continue + } + return true + } + return false +} + +func groupToString(p *gatev1.Group) string { + if p == nil { + return "" + } + return string(*p) +} + +func kindToString(p *gatev1.Kind) string { + if p == nil { + return "" + } + return string(*p) +} diff --git a/pkg/provider/kubernetes/gateway/kubernetes_test.go b/pkg/provider/kubernetes/gateway/kubernetes_test.go index 7c747a270..d5aeca18b 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes_test.go +++ b/pkg/provider/kubernetes/gateway/kubernetes_test.go @@ -15,6 +15,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" gatev1 "sigs.k8s.io/gateway-api/apis/v1" + gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) var _ provider.Provider = (*Provider)(nil) @@ -550,6 +551,7 @@ func TestLoadHTTPRoutes(t *testing.T) { EntryPoints: []string{"web"}, Service: "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr", Rule: "Host(`foo.com`) && Path(`/bar`)", + RuleSyntax: "v3", }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -609,6 +611,7 @@ func TestLoadHTTPRoutes(t *testing.T) { EntryPoints: []string{"web"}, Service: "api@internal", Rule: "Host(`foo.com`) && Path(`/bar`)", + RuleSyntax: "v3", }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -641,6 +644,7 @@ func TestLoadHTTPRoutes(t *testing.T) { EntryPoints: []string{"web"}, Service: "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr", Rule: "Host(`foo.com`) && Path(`/bar`)", + RuleSyntax: "v3", }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -704,6 +708,7 @@ func TestLoadHTTPRoutes(t *testing.T) { EntryPoints: []string{"websecure"}, Service: "default-http-app-1-my-gateway-websecure-1c0cf64bde37d9d0df06-wrr", Rule: "Host(`foo.com`) && Path(`/bar`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTLSConfig{}, }, }, @@ -773,6 +778,7 @@ func TestLoadHTTPRoutes(t *testing.T) { EntryPoints: []string{"web"}, Service: "default-http-app-1-my-gateway-web-66e726cd8903b49727ae-wrr", Rule: "(Host(`foo.com`) || Host(`bar.com`)) && PathPrefix(`/`)", + RuleSyntax: "v3", }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -832,6 +838,7 @@ func TestLoadHTTPRoutes(t *testing.T) { EntryPoints: []string{"web"}, Service: "default-http-app-1-my-gateway-web-3b78e2feb3295ddd87f0-wrr", Rule: "(Host(`foo.com`) || HostRegexp(`^[a-zA-Z0-9-]+\\.bar\\.com$`)) && PathPrefix(`/`)", + RuleSyntax: "v3", }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -891,6 +898,7 @@ func TestLoadHTTPRoutes(t *testing.T) { EntryPoints: []string{"web"}, Service: "default-http-app-1-my-gateway-web-b0521a61fb43068694b4-wrr", Rule: "(Host(`foo.com`) || HostRegexp(`^[a-zA-Z0-9-]+\\.foo\\.com$`)) && PathPrefix(`/`)", + RuleSyntax: "v3", }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -949,11 +957,13 @@ func TestLoadHTTPRoutes(t *testing.T) { "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06": { EntryPoints: []string{"web"}, Rule: "Host(`foo.com`) && Path(`/bar`)", + RuleSyntax: "v3", Service: "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr", }, "default-http-app-1-my-gateway-web-d737b4933fa88e68ab8a": { EntryPoints: []string{"web"}, Rule: "Host(`foo.com`) && Path(`/bir`)", + RuleSyntax: "v3", Service: "default-http-app-1-my-gateway-web-d737b4933fa88e68ab8a-wrr", }, }, @@ -1039,6 +1049,7 @@ func TestLoadHTTPRoutes(t *testing.T) { "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06": { EntryPoints: []string{"web"}, Rule: "Host(`foo.com`) && Path(`/bar`)", + RuleSyntax: "v3", Service: "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr", }, }, @@ -1124,11 +1135,13 @@ func TestLoadHTTPRoutes(t *testing.T) { EntryPoints: []string{"web"}, Service: "default-http-app-1-my-gateway-http-web-1c0cf64bde37d9d0df06-wrr", Rule: "Host(`foo.com`) && Path(`/bar`)", + RuleSyntax: "v3", }, "default-http-app-1-my-gateway-https-websecure-1c0cf64bde37d9d0df06": { EntryPoints: []string{"websecure"}, Service: "default-http-app-1-my-gateway-https-websecure-1c0cf64bde37d9d0df06-wrr", Rule: "Host(`foo.com`) && Path(`/bar`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTLSConfig{}, }, }, @@ -1213,11 +1226,13 @@ func TestLoadHTTPRoutes(t *testing.T) { EntryPoints: []string{"web"}, Service: "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr", Rule: "Host(`foo.com`) && Path(`/bar`)", + RuleSyntax: "v3", }, "default-http-app-1-my-gateway-websecure-1c0cf64bde37d9d0df06": { EntryPoints: []string{"websecure"}, Service: "default-http-app-1-my-gateway-websecure-1c0cf64bde37d9d0df06-wrr", Rule: "Host(`foo.com`) && Path(`/bar`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTLSConfig{}, }, }, @@ -1293,20 +1308,22 @@ func TestLoadHTTPRoutes(t *testing.T) { }, HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ - "default-http-app-1-my-gateway-web-330d644a7f2079e8f454": { + "default-http-app-1-my-gateway-web-4a1b73e6f83804949a37": { EntryPoints: []string{"web"}, - Service: "default-http-app-1-my-gateway-web-330d644a7f2079e8f454-wrr", - Rule: "Host(`foo.com`) && PathPrefix(`/bar`) && Headers(`my-header`,`foo`) && Headers(`my-header2`,`bar`)", + Service: "default-http-app-1-my-gateway-web-4a1b73e6f83804949a37-wrr", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`) && Header(`my-header`,`foo`) && Header(`my-header2`,`bar`)", + RuleSyntax: "v3", }, - "default-http-app-1-my-gateway-web-fe80e69a38713941ea22": { + "default-http-app-1-my-gateway-web-aaba0f24fd26e1ca2276": { EntryPoints: []string{"web"}, - Service: "default-http-app-1-my-gateway-web-fe80e69a38713941ea22-wrr", - Rule: "Host(`foo.com`) && Path(`/bar`) && Headers(`my-header`,`bar`)", + Service: "default-http-app-1-my-gateway-web-aaba0f24fd26e1ca2276-wrr", + Rule: "Host(`foo.com`) && Path(`/bar`) && Header(`my-header`,`bar`)", + RuleSyntax: "v3", }, }, Middlewares: map[string]*dynamic.Middleware{}, Services: map[string]*dynamic.Service{ - "default-http-app-1-my-gateway-web-330d644a7f2079e8f454-wrr": { + "default-http-app-1-my-gateway-web-4a1b73e6f83804949a37-wrr": { Weighted: &dynamic.WeightedRoundRobin{ Services: []dynamic.WRRService{ { @@ -1316,7 +1333,7 @@ func TestLoadHTTPRoutes(t *testing.T) { }, }, }, - "default-http-app-1-my-gateway-web-fe80e69a38713941ea22-wrr": { + "default-http-app-1-my-gateway-web-aaba0f24fd26e1ca2276-wrr": { Weighted: &dynamic.WeightedRoundRobin{ Services: []dynamic.WRRService{ { @@ -1371,6 +1388,7 @@ func TestLoadHTTPRoutes(t *testing.T) { EntryPoints: []string{"web"}, Service: "default-http-app-default-my-gateway-web-efde1997778109a1f6eb-wrr", Rule: "Host(`foo.com`) && Path(`/foo`)", + RuleSyntax: "v3", }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1430,11 +1448,13 @@ func TestLoadHTTPRoutes(t *testing.T) { EntryPoints: []string{"web"}, Service: "default-http-app-default-my-gateway-web-efde1997778109a1f6eb-wrr", Rule: "Host(`foo.com`) && Path(`/foo`)", + RuleSyntax: "v3", }, "bar-http-app-bar-my-gateway-web-66f5c78d03d948e36597": { EntryPoints: []string{"web"}, Service: "bar-http-app-bar-my-gateway-web-66f5c78d03d948e36597-wrr", Rule: "Host(`bar.com`) && Path(`/bar`)", + RuleSyntax: "v3", }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1520,6 +1540,7 @@ func TestLoadHTTPRoutes(t *testing.T) { EntryPoints: []string{"web"}, Service: "bar-http-app-bar-my-gateway-web-66f5c78d03d948e36597-wrr", Rule: "Host(`bar.com`) && Path(`/bar`)", + RuleSyntax: "v3", }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1579,6 +1600,7 @@ func TestLoadHTTPRoutes(t *testing.T) { EntryPoints: []string{"web"}, Service: "default-http-app-1-my-gateway-web-364ce6ec04c3d49b19c4-wrr", Rule: "Host(`example.org`) && PathPrefix(`/`)", + RuleSyntax: "v3", Middlewares: []string{"default-http-app-1-my-gateway-web-364ce6ec04c3d49b19c4-requestredirect-0"}, }, }, @@ -1647,6 +1669,7 @@ func TestLoadHTTPRoutes(t *testing.T) { EntryPoints: []string{"web"}, Service: "default-http-app-1-my-gateway-web-364ce6ec04c3d49b19c4-wrr", Rule: "Host(`example.org`) && PathPrefix(`/`)", + RuleSyntax: "v3", Middlewares: []string{"default-http-app-1-my-gateway-web-364ce6ec04c3d49b19c4-requestredirect-0"}, }, }, @@ -1912,6 +1935,7 @@ func TestLoadTCPRoutes(t *testing.T) { EntryPoints: []string{"tcp"}, Service: "default-tcp-app-1-my-tcp-gateway-tcp-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", }, }, Middlewares: map[string]*dynamic.TCPMiddleware{}, @@ -1969,11 +1993,13 @@ func TestLoadTCPRoutes(t *testing.T) { EntryPoints: []string{"tcp-1"}, Service: "default-tcp-app-1-my-tcp-gateway-tcp-1-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", }, "default-tcp-app-2-my-tcp-gateway-tcp-2-e3b0c44298fc1c149afb": { EntryPoints: []string{"tcp-2"}, Service: "default-tcp-app-2-my-tcp-gateway-tcp-2-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", }, }, Middlewares: map[string]*dynamic.TCPMiddleware{}, @@ -2053,6 +2079,7 @@ func TestLoadTCPRoutes(t *testing.T) { EntryPoints: []string{"tcp-1"}, Service: "default-tcp-app-my-tcp-gateway-tcp-1-e3b0c44298fc1c149afb-wrr", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", }, }, Middlewares: map[string]*dynamic.TCPMiddleware{}, @@ -2144,6 +2171,7 @@ func TestLoadTCPRoutes(t *testing.T) { EntryPoints: []string{"tcp"}, Service: "default-tcp-app-1-my-gateway-tcp-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", }, }, Middlewares: map[string]*dynamic.TCPMiddleware{}, @@ -2203,6 +2231,7 @@ func TestLoadTCPRoutes(t *testing.T) { EntryPoints: []string{"tls"}, Service: "default-tcp-app-1-my-gateway-tls-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{}, }, }, @@ -2266,6 +2295,7 @@ func TestLoadTCPRoutes(t *testing.T) { EntryPoints: []string{"tcp"}, Service: "default-tcp-app-default-my-tcp-gateway-tcp-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", }, }, Middlewares: map[string]*dynamic.TCPMiddleware{}, @@ -2321,11 +2351,13 @@ func TestLoadTCPRoutes(t *testing.T) { EntryPoints: []string{"tcp"}, Service: "default-tcp-app-default-my-tcp-gateway-tcp-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", }, "bar-tcp-app-bar-my-tcp-gateway-tcp-e3b0c44298fc1c149afb": { EntryPoints: []string{"tcp"}, Service: "bar-tcp-app-bar-my-tcp-gateway-tcp-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", }, }, Middlewares: map[string]*dynamic.TCPMiddleware{}, @@ -2403,6 +2435,7 @@ func TestLoadTCPRoutes(t *testing.T) { EntryPoints: []string{"tcp"}, Service: "bar-tcp-app-bar-my-tcp-gateway-tcp-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", }, }, Middlewares: map[string]*dynamic.TCPMiddleware{}, @@ -2696,6 +2729,7 @@ func TestLoadTLSRoutes(t *testing.T) { EntryPoints: []string{"tcp"}, Service: "default-tcp-app-1-my-tls-gateway-tcp-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{}, }, }, @@ -2761,6 +2795,7 @@ func TestLoadTLSRoutes(t *testing.T) { EntryPoints: []string{"tcp"}, Service: "default-tcp-app-1-my-tls-gateway-tcp-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{ Passthrough: true, }, @@ -2819,6 +2854,7 @@ func TestLoadTLSRoutes(t *testing.T) { EntryPoints: []string{"tcp"}, Service: "default-tls-app-1-my-tls-gateway-tcp-f0dd0dd89f82eae1c270-wrr-0", Rule: "HostSNI(`foo.example.com`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{ Passthrough: true, }, @@ -2878,12 +2914,14 @@ func TestLoadTLSRoutes(t *testing.T) { EntryPoints: []string{"tls"}, Service: "default-tcp-app-1-my-tls-gateway-tls-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{}, }, "default-tls-app-1-my-tls-gateway-tcp-673acf455cb2dab0b43a": { EntryPoints: []string{"tcp"}, Service: "default-tls-app-1-my-tls-gateway-tcp-673acf455cb2dab0b43a-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{ Passthrough: true, }, @@ -2973,6 +3011,7 @@ func TestLoadTLSRoutes(t *testing.T) { EntryPoints: []string{"tls"}, Service: "default-tcp-app-1-my-gateway-tls-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{}, }, }, @@ -3042,6 +3081,7 @@ func TestLoadTLSRoutes(t *testing.T) { EntryPoints: []string{"tls"}, Service: "default-tls-app-1-my-gateway-tls-f0dd0dd89f82eae1c270-wrr-0", Rule: "HostSNI(`foo.example.com`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{ Passthrough: true, }, @@ -3100,6 +3140,7 @@ func TestLoadTLSRoutes(t *testing.T) { EntryPoints: []string{"tls"}, Service: "default-tls-app-1-my-gateway-tls-f0dd0dd89f82eae1c270-wrr-0", Rule: "HostSNI(`foo.example.com`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{ Passthrough: true, }, @@ -3158,6 +3199,7 @@ func TestLoadTLSRoutes(t *testing.T) { EntryPoints: []string{"tls"}, Service: "default-tls-app-1-my-gateway-tls-f0dd0dd89f82eae1c270-wrr-0", Rule: "HostSNI(`foo.example.com`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{ Passthrough: true, }, @@ -3216,6 +3258,7 @@ func TestLoadTLSRoutes(t *testing.T) { EntryPoints: []string{"tls"}, Service: "default-tls-app-1-my-gateway-tls-d5342d75658583f03593-wrr-0", Rule: "HostSNI(`foo.example.com`) || HostSNI(`bar.example.com`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{ Passthrough: true, }, @@ -3274,6 +3317,7 @@ func TestLoadTLSRoutes(t *testing.T) { EntryPoints: []string{"tls"}, Service: "default-tls-app-default-my-gateway-tls-06ae57dcf13ab4c60ee5-wrr-0", Rule: "HostSNI(`foo.default`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{ Passthrough: true, }, @@ -3332,6 +3376,7 @@ func TestLoadTLSRoutes(t *testing.T) { EntryPoints: []string{"tls"}, Service: "default-tls-app-default-my-gateway-tls-06ae57dcf13ab4c60ee5-wrr-0", Rule: "HostSNI(`foo.default`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{ Passthrough: true, }, @@ -3340,6 +3385,7 @@ func TestLoadTLSRoutes(t *testing.T) { EntryPoints: []string{"tls"}, Service: "bar-tls-app-bar-my-gateway-tls-2279fe75c5156dc5eb26-wrr-0", Rule: "HostSNI(`foo.bar`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{ Passthrough: true, }, @@ -3420,6 +3466,7 @@ func TestLoadTLSRoutes(t *testing.T) { EntryPoints: []string{"tls"}, Service: "bar-tls-app-bar-my-gateway-tls-2279fe75c5156dc5eb26-wrr-0", Rule: "HostSNI(`foo.bar`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{ Passthrough: true, }, @@ -3478,6 +3525,7 @@ func TestLoadTLSRoutes(t *testing.T) { EntryPoints: []string{"tcp-1"}, Service: "default-tls-app-my-gateway-tcp-1-673acf455cb2dab0b43a-wrr", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{ Passthrough: true, }, @@ -3702,17 +3750,20 @@ func TestLoadMixedRoutes(t *testing.T) { EntryPoints: []string{"tcp"}, Service: "default-tcp-app-1-my-gateway-tcp-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", }, "default-tcp-app-1-my-gateway-tls-1-e3b0c44298fc1c149afb": { EntryPoints: []string{"tls-1"}, Service: "default-tcp-app-1-my-gateway-tls-1-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{}, }, "default-tls-app-1-my-gateway-tls-2-59130f7db6718b7700c1": { EntryPoints: []string{"tls-2"}, Service: "default-tls-app-1-my-gateway-tls-2-59130f7db6718b7700c1-wrr-0", Rule: "HostSNI(`pass.tls.foo.example.com`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{ Passthrough: true, }, @@ -3771,11 +3822,13 @@ func TestLoadMixedRoutes(t *testing.T) { EntryPoints: []string{"web"}, Service: "default-http-app-1-my-gateway-web-a431b128267aabc954fd-wrr", Rule: "PathPrefix(`/`)", + RuleSyntax: "v3", }, "default-http-app-1-my-gateway-websecure-a431b128267aabc954fd": { EntryPoints: []string{"websecure"}, Service: "default-http-app-1-my-gateway-websecure-a431b128267aabc954fd-wrr", Rule: "PathPrefix(`/`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTLSConfig{}, }, }, @@ -3881,17 +3934,20 @@ func TestLoadMixedRoutes(t *testing.T) { EntryPoints: []string{"tcp"}, Service: "default-tcp-app-default-my-gateway-tcp-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", }, "default-tcp-app-default-my-gateway-tls-1-e3b0c44298fc1c149afb": { EntryPoints: []string{"tls-1"}, Service: "default-tcp-app-default-my-gateway-tls-1-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{}, }, "default-tls-app-default-my-gateway-tls-2-59130f7db6718b7700c1": { EntryPoints: []string{"tls-2"}, Service: "default-tls-app-default-my-gateway-tls-2-59130f7db6718b7700c1-wrr-0", Rule: "HostSNI(`pass.tls.foo.example.com`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{ Passthrough: true, }, @@ -3950,11 +4006,13 @@ func TestLoadMixedRoutes(t *testing.T) { EntryPoints: []string{"web"}, Service: "default-http-app-default-my-gateway-web-a431b128267aabc954fd-wrr", Rule: "PathPrefix(`/`)", + RuleSyntax: "v3", }, "default-http-app-default-my-gateway-websecure-a431b128267aabc954fd": { EntryPoints: []string{"websecure"}, Service: "default-http-app-default-my-gateway-websecure-a431b128267aabc954fd-wrr", Rule: "PathPrefix(`/`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTLSConfig{}, }, }, @@ -4032,17 +4090,20 @@ func TestLoadMixedRoutes(t *testing.T) { EntryPoints: []string{"tcp"}, Service: "default-tcp-app-default-my-gateway-tcp-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", }, "default-tcp-app-default-my-gateway-tls-1-e3b0c44298fc1c149afb": { EntryPoints: []string{"tls-1"}, Service: "default-tcp-app-default-my-gateway-tls-1-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{}, }, "default-tls-app-default-my-gateway-tls-2-59130f7db6718b7700c1": { EntryPoints: []string{"tls-2"}, Service: "default-tls-app-default-my-gateway-tls-2-59130f7db6718b7700c1-wrr-0", Rule: "HostSNI(`pass.tls.foo.example.com`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{ Passthrough: true, }, @@ -4051,11 +4112,13 @@ func TestLoadMixedRoutes(t *testing.T) { EntryPoints: []string{"tcp"}, Service: "bar-tcp-app-bar-my-gateway-tcp-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", }, "bar-tcp-app-bar-my-gateway-tls-1-e3b0c44298fc1c149afb": { EntryPoints: []string{"tls-1"}, Service: "bar-tcp-app-bar-my-gateway-tls-1-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{}, }, }, @@ -4144,22 +4207,26 @@ func TestLoadMixedRoutes(t *testing.T) { EntryPoints: []string{"web"}, Service: "default-http-app-default-my-gateway-web-a431b128267aabc954fd-wrr", Rule: "PathPrefix(`/`)", + RuleSyntax: "v3", }, "default-http-app-default-my-gateway-websecure-a431b128267aabc954fd": { EntryPoints: []string{"websecure"}, Service: "default-http-app-default-my-gateway-websecure-a431b128267aabc954fd-wrr", Rule: "PathPrefix(`/`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTLSConfig{}, }, "bar-http-app-bar-my-gateway-web-a431b128267aabc954fd": { EntryPoints: []string{"web"}, Service: "bar-http-app-bar-my-gateway-web-a431b128267aabc954fd-wrr", Rule: "PathPrefix(`/`)", + RuleSyntax: "v3", }, "bar-http-app-bar-my-gateway-websecure-a431b128267aabc954fd": { EntryPoints: []string{"websecure"}, Service: "bar-http-app-bar-my-gateway-websecure-a431b128267aabc954fd-wrr", Rule: "PathPrefix(`/`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTLSConfig{}, }, }, @@ -4273,17 +4340,20 @@ func TestLoadMixedRoutes(t *testing.T) { EntryPoints: []string{"tcp"}, Service: "bar-tcp-app-bar-my-gateway-tcp-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", }, "bar-tcp-app-bar-my-gateway-tls-1-e3b0c44298fc1c149afb": { EntryPoints: []string{"tls-1"}, Service: "bar-tcp-app-bar-my-gateway-tls-1-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{}, }, "bar-tls-app-bar-my-gateway-tls-2-59130f7db6718b7700c1": { EntryPoints: []string{"tls-2"}, Service: "bar-tls-app-bar-my-gateway-tls-2-59130f7db6718b7700c1-wrr-0", Rule: "HostSNI(`pass.tls.foo.example.com`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{ Passthrough: true, }, @@ -4342,11 +4412,13 @@ func TestLoadMixedRoutes(t *testing.T) { EntryPoints: []string{"web"}, Service: "bar-http-app-bar-my-gateway-web-a431b128267aabc954fd-wrr", Rule: "PathPrefix(`/`)", + RuleSyntax: "v3", }, "bar-http-app-bar-my-gateway-websecure-a431b128267aabc954fd": { EntryPoints: []string{"websecure"}, Service: "bar-http-app-bar-my-gateway-websecure-a431b128267aabc954fd-wrr", Rule: "PathPrefix(`/`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTLSConfig{}, }, }, @@ -4423,11 +4495,13 @@ func TestLoadMixedRoutes(t *testing.T) { EntryPoints: []string{"tcp"}, Service: "default-tcp-app-default-my-gateway-tcp-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", }, "default-tcp-app-default-my-gateway-tls-e3b0c44298fc1c149afb": { EntryPoints: []string{"tls"}, Service: "default-tcp-app-default-my-gateway-tls-e3b0c44298fc1c149afb-wrr-0", Rule: "HostSNI(`*`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTCPTLSConfig{}, }, }, @@ -4474,11 +4548,13 @@ func TestLoadMixedRoutes(t *testing.T) { EntryPoints: []string{"web"}, Service: "default-http-app-default-my-gateway-web-a431b128267aabc954fd-wrr", Rule: "PathPrefix(`/`)", + RuleSyntax: "v3", }, "default-http-app-default-my-gateway-websecure-a431b128267aabc954fd": { EntryPoints: []string{"websecure"}, Service: "default-http-app-default-my-gateway-websecure-a431b128267aabc954fd-wrr", Rule: "PathPrefix(`/`)", + RuleSyntax: "v3", TLS: &dynamic.RouterTLSConfig{}, }, }, @@ -4553,6 +4629,196 @@ func TestLoadMixedRoutes(t *testing.T) { } } +func TestLoadRoutesWithReferenceGrants(t *testing.T) { + testCases := []struct { + desc string + ingressClass string + paths []string + expected *dynamic.Configuration + entryPoints map[string]Entrypoint + }{ + { + desc: "Empty", + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty because ReferenceGrant for Secret is missing", + paths: []string{"services.yml", "referencegrant/for_secret_missing.yml"}, + entryPoints: map[string]Entrypoint{ + "tls": {Address: ":9000"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty because ReferenceGrant spec.from does not match", + paths: []string{"services.yml", "referencegrant/for_secret_not_matching_from.yml"}, + entryPoints: map[string]Entrypoint{ + "tls": {Address: ":9000"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty because ReferenceGrant spec.to does not match", + paths: []string{"services.yml", "referencegrant/for_secret_not_matching_to.yml"}, + entryPoints: map[string]Entrypoint{ + "tls": {Address: ":9000"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "For Secret", + paths: []string{"services.yml", "referencegrant/for_secret.yml"}, + entryPoints: map[string]Entrypoint{ + "tls": {Address: ":9000"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-tcp-app-1-my-gateway-tls-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tls"}, + Service: "default-tcp-app-1-my-gateway-tls-e3b0c44298fc1c149afb-wrr-0", + Rule: "HostSNI(`*`)", + RuleSyntax: "v3", + TLS: &dynamic.RouterTCPTLSConfig{}, + }, + }, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "default-tcp-app-1-my-gateway-tls-e3b0c44298fc1c149afb-wrr-0": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{{ + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }}, + }, + }, + "default-whoamitcp-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.9:9000", + }, + { + Address: "10.10.0.10:9000", + }, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Certificates: []*tls.CertAndStores{ + { + Certificate: tls.Certificate{ + CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + }, + }, + }, + }, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + if test.expected == nil { + return + } + + p := Provider{EntryPoints: test.entryPoints} + conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...)) + assert.Equal(t, test.expected, conf) + }) + } +} + func Test_hostRule(t *testing.T) { testCases := []struct { desc string @@ -4807,7 +5073,7 @@ func Test_extractRule(t *testing.T) { }, }, }, - expectedRule: "Path(`/foo/`) || Headers(`my-header`,`foo`)", + expectedRule: "Path(`/foo/`) || Header(`my-header`,`foo`)", }, { desc: "Path && Header rules", @@ -4828,7 +5094,7 @@ func Test_extractRule(t *testing.T) { }, }, }, - expectedRule: "Path(`/foo/`) && Headers(`my-header`,`foo`)", + expectedRule: "Path(`/foo/`) && Header(`my-header`,`foo`)", }, { desc: "Host && Path && Header rules", @@ -4850,7 +5116,7 @@ func Test_extractRule(t *testing.T) { }, }, }, - expectedRule: "Host(`foo.com`) && Path(`/foo/`) && Headers(`my-header`,`foo`)", + expectedRule: "Host(`foo.com`) && Path(`/foo/`) && Header(`my-header`,`foo`)", }, { desc: "Host && (Path || Header) rules", @@ -4874,7 +5140,7 @@ func Test_extractRule(t *testing.T) { }, }, }, - expectedRule: "Host(`foo.com`) && (Path(`/foo/`) || Headers(`my-header`,`foo`))", + expectedRule: "Host(`foo.com`) && (Path(`/foo/`) || Header(`my-header`,`foo`))", }, } @@ -5569,3 +5835,247 @@ func kindPtr(kind gatev1.Kind) *gatev1.Kind { func pathMatchTypePtr(p gatev1.PathMatchType) *gatev1.PathMatchType { return &p } func headerMatchTypePtr(h gatev1.HeaderMatchType) *gatev1.HeaderMatchType { return &h } + +func Test_referenceGrantMatchesFrom(t *testing.T) { + testCases := []struct { + desc string + referenceGrant gatev1beta1.ReferenceGrant + group string + kind string + namespace string + expectedResult bool + }{ + { + desc: "matches", + referenceGrant: gatev1beta1.ReferenceGrant{ + Spec: gatev1beta1.ReferenceGrantSpec{ + From: []gatev1beta1.ReferenceGrantFrom{ + { + Group: "correct-group", + Kind: "correct-kind", + Namespace: "correct-namespace", + }, + }, + }, + }, + group: "correct-group", + kind: "correct-kind", + namespace: "correct-namespace", + expectedResult: true, + }, + { + desc: "empty group matches core", + referenceGrant: gatev1beta1.ReferenceGrant{ + Spec: gatev1beta1.ReferenceGrantSpec{ + From: []gatev1beta1.ReferenceGrantFrom{ + { + Group: "", + Kind: "correct-kind", + Namespace: "correct-namespace", + }, + }, + }, + }, + group: "core", + kind: "correct-kind", + namespace: "correct-namespace", + expectedResult: true, + }, + { + desc: "wrong group", + referenceGrant: gatev1beta1.ReferenceGrant{ + Spec: gatev1beta1.ReferenceGrantSpec{ + From: []gatev1beta1.ReferenceGrantFrom{ + { + Group: "wrong-group", + Kind: "correct-kind", + Namespace: "correct-namespace", + }, + }, + }, + }, + group: "correct-group", + kind: "correct-kind", + namespace: "correct-namespace", + expectedResult: false, + }, + { + desc: "wrong kind", + referenceGrant: gatev1beta1.ReferenceGrant{ + Spec: gatev1beta1.ReferenceGrantSpec{ + From: []gatev1beta1.ReferenceGrantFrom{ + { + Group: "correct-group", + Kind: "wrong-kind", + Namespace: "correct-namespace", + }, + }, + }, + }, + group: "correct-group", + kind: "correct-kind", + namespace: "correct-namespace", + expectedResult: false, + }, + { + desc: "wrong namespace", + referenceGrant: gatev1beta1.ReferenceGrant{ + Spec: gatev1beta1.ReferenceGrantSpec{ + From: []gatev1beta1.ReferenceGrantFrom{ + { + Group: "correct-group", + Kind: "correct-kind", + Namespace: "wrong-namespace", + }, + }, + }, + }, + group: "correct-group", + kind: "correct-kind", + namespace: "correct-namespace", + expectedResult: false, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + assert.Equal(t, test.expectedResult, referenceGrantMatchesFrom(&test.referenceGrant, test.group, test.kind, test.namespace)) + }) + } +} + +func Test_referenceGrantMatchesTo(t *testing.T) { + testCases := []struct { + desc string + referenceGrant gatev1beta1.ReferenceGrant + group string + kind string + name string + expectedResult bool + }{ + { + desc: "matches", + referenceGrant: gatev1beta1.ReferenceGrant{ + Spec: gatev1beta1.ReferenceGrantSpec{ + To: []gatev1beta1.ReferenceGrantTo{ + { + Group: "correct-group", + Kind: "correct-kind", + Name: objectNamePtr("correct-name"), + }, + }, + }, + }, + group: "correct-group", + kind: "correct-kind", + name: "correct-name", + expectedResult: true, + }, + { + desc: "matches without name", + referenceGrant: gatev1beta1.ReferenceGrant{ + Spec: gatev1beta1.ReferenceGrantSpec{ + To: []gatev1beta1.ReferenceGrantTo{ + { + Group: "correct-group", + Kind: "correct-kind", + Name: nil, + }, + }, + }, + }, + group: "correct-group", + kind: "correct-kind", + name: "some-name", + expectedResult: true, + }, + { + desc: "empty group matches core", + referenceGrant: gatev1beta1.ReferenceGrant{ + Spec: gatev1beta1.ReferenceGrantSpec{ + To: []gatev1beta1.ReferenceGrantTo{ + { + Group: "", + Kind: "correct-kind", + Name: objectNamePtr("correct-name"), + }, + }, + }, + }, + group: "core", + kind: "correct-kind", + name: "correct-name", + expectedResult: true, + }, + { + desc: "wrong group", + referenceGrant: gatev1beta1.ReferenceGrant{ + Spec: gatev1beta1.ReferenceGrantSpec{ + To: []gatev1beta1.ReferenceGrantTo{ + { + Group: "wrong-group", + Kind: "correct-kind", + Name: objectNamePtr("correct-name"), + }, + }, + }, + }, + group: "correct-group", + kind: "correct-kind", + name: "correct-namespace", + expectedResult: false, + }, + { + desc: "wrong kind", + referenceGrant: gatev1beta1.ReferenceGrant{ + Spec: gatev1beta1.ReferenceGrantSpec{ + To: []gatev1beta1.ReferenceGrantTo{ + { + Group: "correct-group", + Kind: "wrong-kind", + Name: objectNamePtr("correct-name"), + }, + }, + }, + }, + group: "correct-group", + kind: "correct-kind", + name: "correct-name", + expectedResult: false, + }, + { + desc: "wrong name", + referenceGrant: gatev1beta1.ReferenceGrant{ + Spec: gatev1beta1.ReferenceGrantSpec{ + To: []gatev1beta1.ReferenceGrantTo{ + { + Group: "correct-group", + Kind: "correct-kind", + Name: objectNamePtr("wrong-name"), + }, + }, + }, + }, + group: "correct-group", + kind: "correct-kind", + name: "correct-name", + expectedResult: false, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + assert.Equal(t, test.expectedResult, referenceGrantMatchesTo(&test.referenceGrant, test.group, test.kind, test.name)) + }) + } +} + +func objectNamePtr(objectName gatev1.ObjectName) *gatev1.ObjectName { + return &objectName +} diff --git a/pkg/provider/kubernetes/ingress/client_test.go b/pkg/provider/kubernetes/ingress/client_test.go index 44d611bcb..0a162e877 100644 --- a/pkg/provider/kubernetes/ingress/client_test.go +++ b/pkg/provider/kubernetes/ingress/client_test.go @@ -2,7 +2,7 @@ package ingress import ( "context" - "fmt" + "errors" "testing" "time" @@ -39,9 +39,9 @@ func TestTranslateNotFoundError(t *testing.T) { }, { desc: "not a kubernetes not found error", - err: fmt.Errorf("bar error"), + err: errors.New("bar error"), expectedExists: false, - expectedError: fmt.Errorf("bar error"), + expectedError: errors.New("bar error"), }, } diff --git a/pkg/provider/kubernetes/k8s/parser.go b/pkg/provider/kubernetes/k8s/parser.go index 769d5edf5..ddcd69b06 100644 --- a/pkg/provider/kubernetes/k8s/parser.go +++ b/pkg/provider/kubernetes/k8s/parser.go @@ -12,7 +12,7 @@ import ( // MustParseYaml parses a YAML to objects. func MustParseYaml(content []byte) []runtime.Object { - acceptedK8sTypes := regexp.MustCompile(`^(Namespace|Deployment|Endpoints|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|MiddlewareTCP|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|ServersTransportTCP|GatewayClass|Gateway|HTTPRoute|TCPRoute|TLSRoute)$`) + acceptedK8sTypes := regexp.MustCompile(`^(Namespace|Deployment|Endpoints|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|MiddlewareTCP|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|ServersTransportTCP|GatewayClass|Gateway|HTTPRoute|TCPRoute|TLSRoute|ReferenceGrant)$`) files := strings.Split(string(content), "---\n") retVal := make([]runtime.Object, 0, len(files)) diff --git a/pkg/provider/kv/kv_test.go b/pkg/provider/kv/kv_test.go index cfe901037..438871031 100644 --- a/pkg/provider/kv/kv_test.go +++ b/pkg/provider/kv/kv_test.go @@ -15,6 +15,9 @@ import ( "github.com/traefik/traefik/v3/pkg/types" ) +func Bool(v bool) *bool { return &v } +func String(v string) *string { return &v } + func Test_buildConfiguration(t *testing.T) { provider := newProviderMock(mapToPairs(map[string]string{ "traefik/http/routers/Router0/entryPoints/0": "foobar", @@ -79,6 +82,7 @@ func Test_buildConfiguration(t *testing.T) { "traefik/http/middlewares/Middleware08/forwardAuth/tls/key": "foobar", "traefik/http/middlewares/Middleware08/forwardAuth/tls/insecureSkipVerify": "true", "traefik/http/middlewares/Middleware08/forwardAuth/tls/ca": "foobar", + "traefik/http/middlewares/Middleware08/forwardAuth/tls/caOptional": "true", "traefik/http/middlewares/Middleware08/forwardAuth/tls/cert": "foobar", "traefik/http/middlewares/Middleware08/forwardAuth/address": "foobar", "traefik/http/middlewares/Middleware08/forwardAuth/trustForwardHeader": "true", @@ -105,8 +109,12 @@ func Test_buildConfiguration(t *testing.T) { "traefik/http/middlewares/Middleware09/headers/accessControlAllowOriginListRegex/1": "foobar", "traefik/http/middlewares/Middleware09/headers/contentTypeNosniff": "true", "traefik/http/middlewares/Middleware09/headers/accessControlAllowCredentials": "true", + "traefik/http/middlewares/Middleware09/headers/featurePolicy": "foobar", "traefik/http/middlewares/Middleware09/headers/permissionsPolicy": "foobar", "traefik/http/middlewares/Middleware09/headers/forceSTSHeader": "true", + "traefik/http/middlewares/Middleware09/headers/sslRedirect": "true", + "traefik/http/middlewares/Middleware09/headers/sslHost": "foobar", + "traefik/http/middlewares/Middleware09/headers/sslForceHost": "true", "traefik/http/middlewares/Middleware09/headers/sslProxyHeaders/name1": "foobar", "traefik/http/middlewares/Middleware09/headers/sslProxyHeaders/name0": "foobar", "traefik/http/middlewares/Middleware09/headers/allowedHosts/0": "foobar", @@ -125,6 +133,7 @@ func Test_buildConfiguration(t *testing.T) { "traefik/http/middlewares/Middleware09/headers/addVaryHeader": "true", "traefik/http/middlewares/Middleware09/headers/hostsProxyHeaders/0": "foobar", "traefik/http/middlewares/Middleware09/headers/hostsProxyHeaders/1": "foobar", + "traefik/http/middlewares/Middleware09/headers/sslTemporaryRedirect": "true", "traefik/http/middlewares/Middleware09/headers/customBrowserXSSValue": "foobar", "traefik/http/middlewares/Middleware09/headers/referrerPolicy": "foobar", "traefik/http/middlewares/Middleware09/headers/accessControlExposeHeaders/0": "foobar", @@ -171,6 +180,7 @@ func Test_buildConfiguration(t *testing.T) { "traefik/http/middlewares/Middleware04/circuitBreaker/checkPeriod": "1s", "traefik/http/middlewares/Middleware04/circuitBreaker/fallbackDuration": "1s", "traefik/http/middlewares/Middleware04/circuitBreaker/recoveryDuration": "1s", + "traefik/http/middlewares/Middleware04/circuitBreaker/responseCode": "404", "traefik/http/middlewares/Middleware07/errors/status/0": "foobar", "traefik/http/middlewares/Middleware07/errors/status/1": "foobar", "traefik/http/middlewares/Middleware07/errors/service": "foobar", @@ -200,6 +210,7 @@ func Test_buildConfiguration(t *testing.T) { "traefik/http/middlewares/Middleware18/retry/attempts": "42", "traefik/http/middlewares/Middleware19/stripPrefix/prefixes/0": "foobar", "traefik/http/middlewares/Middleware19/stripPrefix/prefixes/1": "foobar", + "traefik/http/middlewares/Middleware19/stripPrefix/forceSlash": "true", "traefik/tcp/routers/TCPRouter0/entryPoints/0": "foobar", "traefik/tcp/routers/TCPRouter0/entryPoints/1": "foobar", "traefik/tcp/routers/TCPRouter0/service": "foobar", @@ -226,6 +237,7 @@ func Test_buildConfiguration(t *testing.T) { "traefik/tcp/routers/TCPRouter1/tls/passthrough": "true", "traefik/tcp/routers/TCPRouter1/tls/options": "foobar", "traefik/tcp/routers/TCPRouter1/tls/certResolver": "foobar", + "traefik/tcp/services/TCPService01/loadBalancer/terminationDelay": "42", "traefik/tcp/services/TCPService01/loadBalancer/servers/0/address": "foobar", "traefik/tcp/services/TCPService01/loadBalancer/servers/1/address": "foobar", "traefik/tcp/services/TCPService02/weighted/services/0/name": "foobar", @@ -370,6 +382,7 @@ func Test_buildConfiguration(t *testing.T) { "foobar", "foobar", }, + ForceSlash: Bool(true), }, }, "Middleware00": { @@ -392,6 +405,7 @@ func Test_buildConfiguration(t *testing.T) { CheckPeriod: ptypes.Duration(time.Second), FallbackDuration: ptypes.Duration(time.Second), RecoveryDuration: ptypes.Duration(time.Second), + ResponseCode: 404, }, }, "Middleware05": { @@ -402,11 +416,12 @@ func Test_buildConfiguration(t *testing.T) { "Middleware08": { ForwardAuth: &dynamic.ForwardAuth{ Address: "foobar", - TLS: &types.ClientTLS{ + TLS: &dynamic.ClientTLS{ CA: "foobar", Cert: "foobar", Key: "foobar", InsecureSkipVerify: true, + CAOptional: Bool(true), }, TrustForwardHeader: true, AuthResponseHeaders: []string{ @@ -579,10 +594,14 @@ func Test_buildConfiguration(t *testing.T) { "foobar", "foobar", }, + SSLRedirect: Bool(true), + SSLTemporaryRedirect: Bool(true), + SSLHost: String("foobar"), SSLProxyHeaders: map[string]string{ "name1": "foobar", "name0": "foobar", }, + SSLForceHost: Bool(true), STSSeconds: 42, STSIncludeSubdomains: true, STSPreload: true, @@ -595,6 +614,7 @@ func Test_buildConfiguration(t *testing.T) { ContentSecurityPolicy: "foobar", PublicKey: "foobar", ReferrerPolicy: "foobar", + FeaturePolicy: String("foobar"), PermissionsPolicy: "foobar", IsDevelopment: true, }, @@ -755,6 +775,7 @@ func Test_buildConfiguration(t *testing.T) { Services: map[string]*dynamic.TCPService{ "TCPService01": { LoadBalancer: &dynamic.TCPServersLoadBalancer{ + TerminationDelay: func(v int) *int { return &v }(42), Servers: []dynamic.TCPServer{ {Address: "foobar"}, {Address: "foobar"}, diff --git a/pkg/provider/traefik/fixtures/api_insecure_with_dashboard.json b/pkg/provider/traefik/fixtures/api_insecure_with_dashboard.json index 992a447d1..1dec58871 100644 --- a/pkg/provider/traefik/fixtures/api_insecure_with_dashboard.json +++ b/pkg/provider/traefik/fixtures/api_insecure_with_dashboard.json @@ -22,6 +22,11 @@ "priority": 2147483645 } }, + "services": { + "api": {}, + "dashboard": {}, + "noop": {} + }, "middlewares": { "dashboard_redirect": { "redirectRegex": { @@ -38,11 +43,6 @@ ] } } - }, - "services": { - "api": {}, - "dashboard": {}, - "noop": {} } }, "tcp": {}, diff --git a/pkg/provider/traefik/fixtures/full_configuration.json b/pkg/provider/traefik/fixtures/full_configuration.json index f09614e2e..6e9f2d4b3 100644 --- a/pkg/provider/traefik/fixtures/full_configuration.json +++ b/pkg/provider/traefik/fixtures/full_configuration.json @@ -54,6 +54,14 @@ "priority": 2147483647 } }, + "services": { + "api": {}, + "dashboard": {}, + "noop": {}, + "ping": {}, + "prometheus": {}, + "rest": {} + }, "middlewares": { "dashboard_redirect": { "redirectRegex": { @@ -70,14 +78,6 @@ ] } } - }, - "services": { - "api": {}, - "dashboard": {}, - "noop": {}, - "ping": {}, - "prometheus": {}, - "rest": {} } }, "tcp": {}, diff --git a/pkg/provider/traefik/fixtures/redirection.json b/pkg/provider/traefik/fixtures/redirection.json index 2b3b271fa..73ae77db3 100644 --- a/pkg/provider/traefik/fixtures/redirection.json +++ b/pkg/provider/traefik/fixtures/redirection.json @@ -12,6 +12,9 @@ "rule": "HostRegexp(`^.+$`)" } }, + "services": { + "noop": {} + }, "middlewares": { "redirect-web-to-websecure": { "redirectScheme": { @@ -20,11 +23,8 @@ "permanent": true } } - }, - "services": { - "noop": {} } }, "tcp": {}, "tls": {} -} +} \ No newline at end of file diff --git a/pkg/provider/traefik/fixtures/redirection_port.json b/pkg/provider/traefik/fixtures/redirection_port.json index ead9bc0b1..a9e75438a 100644 --- a/pkg/provider/traefik/fixtures/redirection_port.json +++ b/pkg/provider/traefik/fixtures/redirection_port.json @@ -12,6 +12,9 @@ "rule": "HostRegexp(`^.+$`)" } }, + "services": { + "noop": {} + }, "middlewares": { "redirect-web-to-443": { "redirectScheme": { @@ -20,11 +23,8 @@ "permanent": true } } - }, - "services": { - "noop": {} } }, "tcp": {}, "tls": {} -} +} \ No newline at end of file diff --git a/pkg/provider/traefik/fixtures/redirection_with_protocol.json b/pkg/provider/traefik/fixtures/redirection_with_protocol.json index 2b3b271fa..73ae77db3 100644 --- a/pkg/provider/traefik/fixtures/redirection_with_protocol.json +++ b/pkg/provider/traefik/fixtures/redirection_with_protocol.json @@ -12,6 +12,9 @@ "rule": "HostRegexp(`^.+$`)" } }, + "services": { + "noop": {} + }, "middlewares": { "redirect-web-to-websecure": { "redirectScheme": { @@ -20,11 +23,8 @@ "permanent": true } } - }, - "services": { - "noop": {} } }, "tcp": {}, "tls": {} -} +} \ No newline at end of file diff --git a/pkg/provider/traefik/internal.go b/pkg/provider/traefik/internal.go index 71a6c321a..9a4105623 100644 --- a/pkg/provider/traefik/internal.go +++ b/pkg/provider/traefik/internal.go @@ -65,6 +65,7 @@ func (i *Provider) createConfiguration(ctx context.Context) *dynamic.Configurati TCP: &dynamic.TCPConfiguration{ Routers: make(map[string]*dynamic.TCPRouter), Services: make(map[string]*dynamic.TCPService), + Models: make(map[string]*dynamic.TCPModel), ServersTransports: make(map[string]*dynamic.TCPServersTransport), }, TLS: &dynamic.TLSConfiguration{ @@ -191,8 +192,13 @@ func (i *Provider) getEntryPointPort(name string, def *static.Redirections) (str } func (i *Provider) entryPointModels(cfg *dynamic.Configuration) { + defaultRuleSyntax := "" + if i.staticCfg.Core != nil && i.staticCfg.Core.DefaultRuleSyntax != "" { + defaultRuleSyntax = i.staticCfg.Core.DefaultRuleSyntax + } + for name, ep := range i.staticCfg.EntryPoints { - if len(ep.HTTP.Middlewares) == 0 && ep.HTTP.TLS == nil { + if len(ep.HTTP.Middlewares) == 0 && ep.HTTP.TLS == nil && defaultRuleSyntax == "" { continue } @@ -208,7 +214,19 @@ func (i *Provider) entryPointModels(cfg *dynamic.Configuration) { } } + m.DefaultRuleSyntax = defaultRuleSyntax + cfg.HTTP.Models[name] = m + + if cfg.TCP == nil { + continue + } + + mTCP := &dynamic.TCPModel{ + DefaultRuleSyntax: defaultRuleSyntax, + } + + cfg.TCP.Models[name] = mTCP } } diff --git a/pkg/redactor/redactor_config_test.go b/pkg/redactor/redactor_config_test.go index c3ef9ef9d..be179561a 100644 --- a/pkg/redactor/redactor_config_test.go +++ b/pkg/redactor/redactor_config_test.go @@ -263,7 +263,7 @@ func init() { }, ForwardAuth: &dynamic.ForwardAuth{ Address: "127.0.0.1", - TLS: &types.ClientTLS{ + TLS: &dynamic.ClientTLS{ CA: "ca.pem", Cert: "cert.pem", Key: "cert.pem", @@ -846,17 +846,17 @@ func TestDo_staticConfiguration(t *testing.T) { config.Tracing = &static.Tracing{ ServiceName: "myServiceName", - Headers: map[string]string{ - "foobar": "foobar", - }, GlobalAttributes: map[string]string{ "foobar": "foobar", }, SampleRate: 42, OTLP: &opentelemetry.Config{ - HTTP: &opentelemetry.HTTP{ + HTTP: &types.OtelHTTP{ Endpoint: "foobar", TLS: nil, + Headers: map[string]string{ + "foobar": "foobar", + }, }, }, } diff --git a/pkg/redactor/testdata/anonymized-static-config.json b/pkg/redactor/testdata/anonymized-static-config.json index 97e678c85..a1b7b19ec 100644 --- a/pkg/redactor/testdata/anonymized-static-config.json +++ b/pkg/redactor/testdata/anonymized-static-config.json @@ -344,9 +344,6 @@ }, "tracing": { "serviceName": "myServiceName", - "headers": { - "foobar": "foobar" - }, "globalAttributes": { "foobar": "foobar" }, @@ -407,4 +404,4 @@ } } } -} +} \ No newline at end of file diff --git a/pkg/safe/routine_test.go b/pkg/safe/routine_test.go index 45f87978c..1880b7c4e 100644 --- a/pkg/safe/routine_test.go +++ b/pkg/safe/routine_test.go @@ -2,7 +2,7 @@ package safe import ( "context" - "fmt" + "errors" "sync" "testing" "time" @@ -146,7 +146,7 @@ func TestOperationWithRecoverPanic(t *testing.T) { func TestOperationWithRecoverError(t *testing.T) { operation := func() error { - return fmt.Errorf("ERROR") + return errors.New("ERROR") } err := backoff.Retry(OperationWithRecover(operation), &backoff.StopBackOff{}) if err == nil { diff --git a/pkg/server/aggregator.go b/pkg/server/aggregator.go index bd4fda0f5..c6a88e590 100644 --- a/pkg/server/aggregator.go +++ b/pkg/server/aggregator.go @@ -24,6 +24,7 @@ func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoint Routers: make(map[string]*dynamic.TCPRouter), Services: make(map[string]*dynamic.TCPService), Middlewares: make(map[string]*dynamic.TCPMiddleware), + Models: make(map[string]*dynamic.TCPModel), ServersTransports: make(map[string]*dynamic.TCPServersTransport), }, UDP: &dynamic.UDPConfiguration{ @@ -152,6 +153,13 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration { for name, rt := range cfg.HTTP.Routers { router := rt.DeepCopy() + if !router.DefaultRule && router.RuleSyntax == "" { + for _, model := range cfg.HTTP.Models { + router.RuleSyntax = model.DefaultRuleSyntax + break + } + } + eps := router.EntryPoints router.EntryPoints = nil @@ -183,6 +191,25 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration { cfg.HTTP.Routers = rts + if cfg.TCP == nil || len(cfg.TCP.Models) == 0 { + return cfg + } + + tcpRouters := make(map[string]*dynamic.TCPRouter) + + for _, rt := range cfg.TCP.Routers { + router := rt.DeepCopy() + + if router.RuleSyntax == "" { + for _, model := range cfg.TCP.Models { + router.RuleSyntax = model.DefaultRuleSyntax + break + } + } + } + + cfg.TCP.Routers = tcpRouters + return cfg } diff --git a/pkg/server/aggregator_test.go b/pkg/server/aggregator_test.go index ea1fb639f..1ef3bd8ac 100644 --- a/pkg/server/aggregator_test.go +++ b/pkg/server/aggregator_test.go @@ -473,6 +473,7 @@ func Test_mergeConfiguration_defaultTCPEntryPoint(t *testing.T) { Services: map[string]*dynamic.TCPService{ "service-1@provider-1": {}, }, + Models: map[string]*dynamic.TCPModel{}, ServersTransports: make(map[string]*dynamic.TCPServersTransport), } diff --git a/pkg/server/configurationwatcher_test.go b/pkg/server/configurationwatcher_test.go index 9cd586614..d77874634 100644 --- a/pkg/server/configurationwatcher_test.go +++ b/pkg/server/configurationwatcher_test.go @@ -2,7 +2,7 @@ package server import ( "context" - "fmt" + "errors" "strconv" "sync" "testing" @@ -23,14 +23,14 @@ type mockProvider struct { throttleDuration time.Duration } -func (p *mockProvider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error { +func (p *mockProvider) Provide(configurationChan chan<- dynamic.Message, _ *safe.Pool) error { wait := p.wait if wait == 0 { wait = 20 * time.Millisecond } if len(p.messages) == 0 { - return fmt.Errorf("no messages available") + return errors.New("no messages available") } configurationChan <- p.messages[0] @@ -48,7 +48,7 @@ func (p *mockProvider) Provide(configurationChan chan<- dynamic.Message, pool *s } // ThrottleDuration returns the throttle duration. -func (p mockProvider) ThrottleDuration() time.Duration { +func (p *mockProvider) ThrottleDuration() time.Duration { return p.throttleDuration } @@ -92,6 +92,7 @@ func TestNewConfigurationWatcher(t *testing.T) { Routers: map[string]*dynamic.TCPRouter{}, Middlewares: map[string]*dynamic.TCPMiddleware{}, Services: map[string]*dynamic.TCPService{}, + Models: map[string]*dynamic.TCPModel{}, ServersTransports: map[string]*dynamic.TCPServersTransport{}, }, TLS: &dynamic.TLSConfiguration{ @@ -123,7 +124,7 @@ func TestWaitForRequiredProvider(t *testing.T) { config := &dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("foo")), + th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), th.WithLoadBalancerServices(th.WithService("bar")), ), } @@ -167,60 +168,14 @@ func TestIgnoreTransientConfiguration(t *testing.T) { config := &dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("foo")), + th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), th.WithLoadBalancerServices(th.WithService("bar")), ), } - config2 := &dynamic.Configuration{ + expectedConfig := dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("baz")), - th.WithLoadBalancerServices(th.WithService("toto")), - ), - } - - watcher := NewConfigurationWatcher(routinesPool, &mockProvider{}, []string{"defaultEP"}, "") - - publishedConfigCount := 0 - var lastConfig dynamic.Configuration - blockConfConsumer := make(chan struct{}) - watcher.AddListener(func(config dynamic.Configuration) { - publishedConfigCount++ - lastConfig = config - <-blockConfConsumer - }) - - watcher.Start() - - t.Cleanup(watcher.Stop) - t.Cleanup(routinesPool.Stop) - - watcher.allProvidersConfigs <- dynamic.Message{ - ProviderName: "mock", - Configuration: config, - } - - watcher.allProvidersConfigs <- dynamic.Message{ - ProviderName: "mock", - Configuration: config2, - } - - watcher.allProvidersConfigs <- dynamic.Message{ - ProviderName: "mock", - Configuration: config, - } - - close(blockConfConsumer) - - // give some time so that the configuration can be processed - time.Sleep(20 * time.Millisecond) - - // after 20 milliseconds we should have 1 configs published - assert.Equal(t, 1, publishedConfigCount, "times configs were published") - - expected := dynamic.Configuration{ - HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("defaultEP"))), + th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))), th.WithLoadBalancerServices(th.WithService("bar@mock")), th.WithMiddlewares(), ), @@ -228,6 +183,7 @@ func TestIgnoreTransientConfiguration(t *testing.T) { Routers: map[string]*dynamic.TCPRouter{}, Middlewares: map[string]*dynamic.TCPMiddleware{}, Services: map[string]*dynamic.TCPService{}, + Models: map[string]*dynamic.TCPModel{}, ServersTransports: map[string]*dynamic.TCPServersTransport{}, }, UDP: &dynamic.UDPConfiguration{ @@ -242,7 +198,109 @@ func TestIgnoreTransientConfiguration(t *testing.T) { }, } - assert.Equal(t, expected, lastConfig) + expectedConfig3 := dynamic.Configuration{ + HTTP: th.BuildConfiguration( + th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))), + th.WithLoadBalancerServices(th.WithService("bar-config3@mock")), + th.WithMiddlewares(), + ), + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + Models: map[string]*dynamic.TCPModel{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TLS: &dynamic.TLSConfiguration{ + Options: map[string]tls.Options{ + "default": tls.DefaultTLSOptions, + }, + Stores: map[string]tls.Store{}, + }, + } + + config2 := &dynamic.Configuration{ + HTTP: th.BuildConfiguration( + th.WithRouters(th.WithRouter("baz", th.WithEntryPoints("ep"))), + th.WithLoadBalancerServices(th.WithService("toto")), + ), + } + + config3 := &dynamic.Configuration{ + HTTP: th.BuildConfiguration( + th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), + th.WithLoadBalancerServices(th.WithService("bar-config3")), + ), + } + watcher := NewConfigurationWatcher(routinesPool, &mockProvider{}, []string{}, "") + + // To be able to "block" the writes, we change the chan to remove buffering. + watcher.allProvidersConfigs = make(chan dynamic.Message) + + publishedConfigCount := 0 + + firstConfigHandled := make(chan struct{}) + blockConfConsumer := make(chan struct{}) + blockConfConsumerAssert := make(chan struct{}) + watcher.AddListener(func(config dynamic.Configuration) { + publishedConfigCount++ + + if publishedConfigCount > 2 { + t.Fatal("More than 2 published configuration") + } + + if publishedConfigCount == 1 { + assert.Equal(t, expectedConfig, config) + close(firstConfigHandled) + + <-blockConfConsumer + time.Sleep(500 * time.Millisecond) + } + + if publishedConfigCount == 2 { + assert.Equal(t, expectedConfig3, config) + close(blockConfConsumerAssert) + } + }) + + watcher.Start() + + t.Cleanup(watcher.Stop) + t.Cleanup(routinesPool.Stop) + + watcher.allProvidersConfigs <- dynamic.Message{ + ProviderName: "mock", + Configuration: config, + } + + <-firstConfigHandled + + watcher.allProvidersConfigs <- dynamic.Message{ + ProviderName: "mock", + Configuration: config2, + } + + watcher.allProvidersConfigs <- dynamic.Message{ + ProviderName: "mock", + Configuration: config, + } + + close(blockConfConsumer) + + watcher.allProvidersConfigs <- dynamic.Message{ + ProviderName: "mock", + Configuration: config3, + } + + select { + case <-blockConfConsumerAssert: + case <-time.After(10 * time.Second): + t.Fatal("Timeout") + } } func TestListenProvidersThrottleProviderConfigReload(t *testing.T) { @@ -258,7 +316,7 @@ func TestListenProvidersThrottleProviderConfigReload(t *testing.T) { ProviderName: "mock", Configuration: &dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("foo"+strconv.Itoa(i))), + th.WithRouters(th.WithRouter("foo"+strconv.Itoa(i), th.WithEntryPoints("ep"))), th.WithLoadBalancerServices(th.WithService("bar")), ), }, @@ -318,7 +376,7 @@ func TestListenProvidersSkipsSameConfigurationForProvider(t *testing.T) { ProviderName: "mock", Configuration: &dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("foo")), + th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), th.WithLoadBalancerServices(th.WithService("bar")), ), }, @@ -350,14 +408,14 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) { configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("foo")), + th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), th.WithLoadBalancerServices(th.WithService("bar")), ), } transientConfiguration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("bad")), + th.WithRouters(th.WithRouter("bad", th.WithEntryPoints("ep"))), th.WithLoadBalancerServices(th.WithService("bad")), ), } @@ -372,7 +430,7 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) { }, } - watcher := NewConfigurationWatcher(routinesPool, pvd, []string{"defaultEP"}, "") + watcher := NewConfigurationWatcher(routinesPool, pvd, []string{}, "") var lastConfig dynamic.Configuration watcher.AddListener(func(conf dynamic.Configuration) { @@ -389,7 +447,7 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) { expected := dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("defaultEP"))), + th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))), th.WithLoadBalancerServices(th.WithService("bar@mock")), th.WithMiddlewares(), ), @@ -397,6 +455,7 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) { Routers: map[string]*dynamic.TCPRouter{}, Middlewares: map[string]*dynamic.TCPMiddleware{}, Services: map[string]*dynamic.TCPService{}, + Models: map[string]*dynamic.TCPModel{}, ServersTransports: map[string]*dynamic.TCPServersTransport{}, }, UDP: &dynamic.UDPConfiguration{ @@ -419,14 +478,14 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) { configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("foo")), + th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), th.WithLoadBalancerServices(th.WithService("bar")), ), } transientConfiguration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("bad")), + th.WithRouters(th.WithRouter("bad", th.WithEntryPoints("ep"))), th.WithLoadBalancerServices(th.WithService("bad")), ), } @@ -452,7 +511,7 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) { err := providerAggregator.AddProvider(pvd) assert.NoError(t, err) - watcher := NewConfigurationWatcher(routinesPool, providerAggregator, []string{"defaultEP"}, "") + watcher := NewConfigurationWatcher(routinesPool, providerAggregator, []string{}, "") var configurationReloads int var lastConfig dynamic.Configuration @@ -479,7 +538,7 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) { expected := dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("defaultEP"))), + th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))), th.WithLoadBalancerServices(th.WithService("bar@mock")), th.WithMiddlewares(), ), @@ -487,6 +546,7 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) { Routers: map[string]*dynamic.TCPRouter{}, Middlewares: map[string]*dynamic.TCPMiddleware{}, Services: map[string]*dynamic.TCPService{}, + Models: map[string]*dynamic.TCPModel{}, ServersTransports: map[string]*dynamic.TCPServersTransport{}, }, UDP: &dynamic.UDPConfiguration{ @@ -509,7 +569,7 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) { func TestApplyConfigUnderStress(t *testing.T) { routinesPool := safe.NewPool(context.Background()) - watcher := NewConfigurationWatcher(routinesPool, &mockProvider{}, []string{"defaultEP"}, "") + watcher := NewConfigurationWatcher(routinesPool, &mockProvider{}, []string{}, "") routinesPool.GoCtx(func(ctx context.Context) { i := 0 @@ -519,7 +579,7 @@ func TestApplyConfigUnderStress(t *testing.T) { return case watcher.allProvidersConfigs <- dynamic.Message{ProviderName: "mock", Configuration: &dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("foo"+strconv.Itoa(i))), + th.WithRouters(th.WithRouter("foo"+strconv.Itoa(i), th.WithEntryPoints("ep"))), th.WithLoadBalancerServices(th.WithService("bar")), ), }}: @@ -554,28 +614,28 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) { configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("foo")), + th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), th.WithLoadBalancerServices(th.WithService("bar")), ), } transientConfiguration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("bad")), + th.WithRouters(th.WithRouter("bad", th.WithEntryPoints("ep"))), th.WithLoadBalancerServices(th.WithService("bad")), ), } transientConfiguration2 := &dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("bad2")), + th.WithRouters(th.WithRouter("bad2", th.WithEntryPoints("ep"))), th.WithLoadBalancerServices(th.WithService("bad2")), ), } finalConfiguration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("final")), + th.WithRouters(th.WithRouter("final", th.WithEntryPoints("ep"))), th.WithLoadBalancerServices(th.WithService("final")), ), } @@ -595,7 +655,7 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) { err := providerAggregator.AddProvider(pvd) assert.NoError(t, err) - watcher := NewConfigurationWatcher(routinesPool, providerAggregator, []string{"defaultEP"}, "") + watcher := NewConfigurationWatcher(routinesPool, providerAggregator, []string{}, "") var configurationReloads int var lastConfig dynamic.Configuration @@ -614,7 +674,7 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) { expected := dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("final@mock", th.WithEntryPoints("defaultEP"))), + th.WithRouters(th.WithRouter("final@mock", th.WithEntryPoints("ep"))), th.WithLoadBalancerServices(th.WithService("final@mock")), th.WithMiddlewares(), ), @@ -622,6 +682,7 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) { Routers: map[string]*dynamic.TCPRouter{}, Middlewares: map[string]*dynamic.TCPMiddleware{}, Services: map[string]*dynamic.TCPService{}, + Models: map[string]*dynamic.TCPModel{}, ServersTransports: map[string]*dynamic.TCPServersTransport{}, }, UDP: &dynamic.UDPConfiguration{ @@ -646,7 +707,7 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) { configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("foo")), + th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), th.WithLoadBalancerServices(th.WithService("bar")), ), } @@ -658,7 +719,7 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) { }, } - watcher := NewConfigurationWatcher(routinesPool, pvd, []string{"defaultEP"}, "") + watcher := NewConfigurationWatcher(routinesPool, pvd, []string{}, "") var publishedProviderConfig dynamic.Configuration @@ -677,8 +738,8 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) { expected := dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters( - th.WithRouter("foo@mock", th.WithEntryPoints("defaultEP")), - th.WithRouter("foo@mock2", th.WithEntryPoints("defaultEP")), + th.WithRouter("foo@mock", th.WithEntryPoints("ep")), + th.WithRouter("foo@mock2", th.WithEntryPoints("ep")), ), th.WithLoadBalancerServices( th.WithService("bar@mock"), @@ -690,6 +751,7 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) { Routers: map[string]*dynamic.TCPRouter{}, Middlewares: map[string]*dynamic.TCPMiddleware{}, Services: map[string]*dynamic.TCPService{}, + Models: map[string]*dynamic.TCPModel{}, ServersTransports: map[string]*dynamic.TCPServersTransport{}, }, TLS: &dynamic.TLSConfiguration{ diff --git a/pkg/server/middleware/chainbuilder.go b/pkg/server/middleware/chainbuilder.go deleted file mode 100644 index 980d7a2b5..000000000 --- a/pkg/server/middleware/chainbuilder.go +++ /dev/null @@ -1,63 +0,0 @@ -package middleware - -import ( - "context" - - "github.com/containous/alice" - "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/metrics" - "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" - "github.com/traefik/traefik/v3/pkg/middlewares/capture" - metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics" - tracingMiddle "github.com/traefik/traefik/v3/pkg/middlewares/tracing" - "go.opentelemetry.io/otel/trace" -) - -// ChainBuilder Creates a middleware chain by entry point. It is used for middlewares that are created almost systematically and that need to be created before all others. -type ChainBuilder struct { - metricsRegistry metrics.Registry - accessLoggerMiddleware *accesslog.Handler - tracer trace.Tracer -} - -// NewChainBuilder Creates a new ChainBuilder. -func NewChainBuilder(metricsRegistry metrics.Registry, accessLoggerMiddleware *accesslog.Handler, tracer trace.Tracer) *ChainBuilder { - return &ChainBuilder{ - metricsRegistry: metricsRegistry, - accessLoggerMiddleware: accessLoggerMiddleware, - tracer: tracer, - } -} - -// Build a middleware chain by entry point. -func (c *ChainBuilder) Build(ctx context.Context, entryPointName string) alice.Chain { - chain := alice.New() - - if c.accessLoggerMiddleware != nil || c.metricsRegistry != nil && (c.metricsRegistry.IsEpEnabled() || c.metricsRegistry.IsRouterEnabled() || c.metricsRegistry.IsSvcEnabled()) { - chain = chain.Append(capture.Wrap) - } - - if c.accessLoggerMiddleware != nil { - chain = chain.Append(accesslog.WrapHandler(c.accessLoggerMiddleware)) - } - - if c.tracer != nil { - chain = chain.Append(tracingMiddle.WrapEntryPointHandler(ctx, c.tracer, entryPointName)) - } - - if c.metricsRegistry != nil && c.metricsRegistry.IsEpEnabled() { - metricsHandler := metricsMiddle.WrapEntryPointHandler(ctx, c.metricsRegistry, entryPointName) - chain = chain.Append(tracingMiddle.WrapMiddleware(ctx, metricsHandler)) - } - - return chain -} - -// Close accessLogger and tracer. -func (c *ChainBuilder) Close() { - if c.accessLoggerMiddleware != nil { - if err := c.accessLoggerMiddleware.Close(); err != nil { - log.Error().Err(err).Msg("Could not close the access log file") - } - } -} diff --git a/pkg/server/middleware/middlewares.go b/pkg/server/middleware/middlewares.go index 72da39663..3065b16ae 100644 --- a/pkg/server/middleware/middlewares.go +++ b/pkg/server/middleware/middlewares.go @@ -184,7 +184,7 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) ( return nil, badConf } middleware = func(next http.Handler) (http.Handler, error) { - return contenttype.New(ctx, next, middlewareName) + return contenttype.New(ctx, next, *config.ContentType, middlewareName) } } @@ -240,7 +240,8 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) ( // IPWhiteList if config.IPWhiteList != nil { - log.Warn().Msg("IPWhiteList is deprecated, please use IPAllowList instead.") + qualifiedName := provider.GetQualifiedName(ctx, middlewareName) + log.Warn().Msgf("Middleware %q of type IPWhiteList is deprecated, please use IPAllowList instead.", qualifiedName) if middleware != nil { return nil, badConf @@ -386,6 +387,9 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) ( return nil, fmt.Errorf("invalid middleware %q configuration: invalid middleware type or middleware does not exist", middlewareName) } + // The tracing middleware is a NOOP if tracing is not setup on the middleware chain. + // Hence, regarding internal resources' observability deactivation, + // this would not enable tracing. return tracing.WrapMiddleware(ctx, middleware), nil } diff --git a/pkg/server/middleware/observability.go b/pkg/server/middleware/observability.go new file mode 100644 index 000000000..af65cf480 --- /dev/null +++ b/pkg/server/middleware/observability.go @@ -0,0 +1,140 @@ +package middleware + +import ( + "context" + "io" + "net/http" + "strings" + + "github.com/containous/alice" + "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/config/static" + "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/metrics" + "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" + "github.com/traefik/traefik/v3/pkg/middlewares/capture" + metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics" + tracingMiddle "github.com/traefik/traefik/v3/pkg/middlewares/tracing" + "go.opentelemetry.io/otel/trace" +) + +// ObservabilityMgr is a manager for observability (AccessLogs, Metrics and Tracing) enablement. +type ObservabilityMgr struct { + config static.Configuration + accessLoggerMiddleware *accesslog.Handler + metricsRegistry metrics.Registry + tracer trace.Tracer + tracerCloser io.Closer +} + +// NewObservabilityMgr creates a new ObservabilityMgr. +func NewObservabilityMgr(config static.Configuration, metricsRegistry metrics.Registry, accessLoggerMiddleware *accesslog.Handler, tracer trace.Tracer, tracerCloser io.Closer) *ObservabilityMgr { + return &ObservabilityMgr{ + config: config, + metricsRegistry: metricsRegistry, + accessLoggerMiddleware: accessLoggerMiddleware, + tracer: tracer, + tracerCloser: tracerCloser, + } +} + +// BuildEPChain an observability middleware chain by entry point. +func (c *ObservabilityMgr) BuildEPChain(ctx context.Context, entryPointName string, resourceName string) alice.Chain { + chain := alice.New() + + if c == nil { + return chain + } + + if c.accessLoggerMiddleware != nil || c.metricsRegistry != nil && (c.metricsRegistry.IsEpEnabled() || c.metricsRegistry.IsRouterEnabled() || c.metricsRegistry.IsSvcEnabled()) { + if c.ShouldAddAccessLogs(resourceName) || c.ShouldAddMetrics(resourceName) { + chain = chain.Append(capture.Wrap) + } + } + + if c.accessLoggerMiddleware != nil && c.ShouldAddAccessLogs(resourceName) { + chain = chain.Append(accesslog.WrapHandler(c.accessLoggerMiddleware)) + chain = chain.Append(func(next http.Handler) (http.Handler, error) { + return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.InitServiceFields), nil + }) + } + + if c.tracer != nil && c.ShouldAddTracing(resourceName) { + chain = chain.Append(tracingMiddle.WrapEntryPointHandler(ctx, c.tracer, entryPointName)) + } + + if c.metricsRegistry != nil && c.metricsRegistry.IsEpEnabled() && c.ShouldAddMetrics(resourceName) { + metricsHandler := metricsMiddle.WrapEntryPointHandler(ctx, c.metricsRegistry, entryPointName) + + if c.tracer != nil && c.ShouldAddTracing(resourceName) { + chain = chain.Append(tracingMiddle.WrapMiddleware(ctx, metricsHandler)) + } else { + chain = chain.Append(metricsHandler) + } + } + + return chain +} + +// ShouldAddAccessLogs returns whether the access logs should be enabled for the given resource. +func (c *ObservabilityMgr) ShouldAddAccessLogs(resourceName string) bool { + if c == nil { + return false + } + + return c.config.AccessLog != nil && (c.config.AccessLog.AddInternals || !strings.HasSuffix(resourceName, "@internal")) +} + +// ShouldAddMetrics returns whether the metrics should be enabled for the given resource. +func (c *ObservabilityMgr) ShouldAddMetrics(resourceName string) bool { + if c == nil { + return false + } + + return c.config.Metrics != nil && (c.config.Metrics.AddInternals || !strings.HasSuffix(resourceName, "@internal")) +} + +// ShouldAddTracing returns whether the tracing should be enabled for the given resource. +func (c *ObservabilityMgr) ShouldAddTracing(resourceName string) bool { + if c == nil { + return false + } + + return c.config.Tracing != nil && (c.config.Tracing.AddInternals || !strings.HasSuffix(resourceName, "@internal")) +} + +// MetricsRegistry is an accessor to the metrics registry. +func (c *ObservabilityMgr) MetricsRegistry() metrics.Registry { + if c == nil { + return nil + } + + return c.metricsRegistry +} + +// Close closes the accessLogger and tracer. +func (c *ObservabilityMgr) Close() { + if c == nil { + return + } + + if c.accessLoggerMiddleware != nil { + if err := c.accessLoggerMiddleware.Close(); err != nil { + log.Error().Err(err).Msg("Could not close the access log file") + } + } + + if c.tracerCloser != nil { + if err := c.tracerCloser.Close(); err != nil { + log.Error().Err(err).Msg("Could not close the tracer") + } + } +} + +func (c *ObservabilityMgr) RotateAccessLogs() error { + if c.accessLoggerMiddleware == nil { + return nil + } + + return c.accessLoggerMiddleware.Rotate() +} diff --git a/pkg/server/router/router.go b/pkg/server/router/router.go index 4b383e076..eae645383 100644 --- a/pkg/server/router/router.go +++ b/pkg/server/router/router.go @@ -10,7 +10,6 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/logs" - "github.com/traefik/traefik/v3/pkg/metrics" "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" "github.com/traefik/traefik/v3/pkg/middlewares/denyrouterrecursion" metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics" @@ -35,21 +34,19 @@ type serviceManager interface { type Manager struct { routerHandlers map[string]http.Handler serviceManager serviceManager - metricsRegistry metrics.Registry + observabilityMgr *middleware.ObservabilityMgr middlewaresBuilder middlewareBuilder - chainBuilder *middleware.ChainBuilder conf *runtime.Configuration tlsManager *tls.Manager } // NewManager creates a new Manager. -func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, chainBuilder *middleware.ChainBuilder, metricsRegistry metrics.Registry, tlsManager *tls.Manager) *Manager { +func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, observabilityMgr *middleware.ObservabilityMgr, tlsManager *tls.Manager) *Manager { return &Manager{ routerHandlers: make(map[string]http.Handler), serviceManager: serviceManager, - metricsRegistry: metricsRegistry, + observabilityMgr: observabilityMgr, middlewaresBuilder: middlewaresBuilder, - chainBuilder: chainBuilder, conf: conf, tlsManager: tlsManager, } @@ -73,49 +70,49 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t logger := log.Ctx(rootCtx).With().Str(logs.EntryPointName, entryPointName).Logger() ctx := logger.WithContext(rootCtx) - handler, err := m.buildEntryPointHandler(ctx, routers) + handler, err := m.buildEntryPointHandler(ctx, entryPointName, routers) if err != nil { logger.Error().Err(err).Send() continue } - handlerWithAccessLog, err := alice.New(func(next http.Handler) (http.Handler, error) { - return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.InitServiceFields), nil - }).Then(handler) - if err != nil { - logger.Error().Err(err).Send() - entryPointHandlers[entryPointName] = handler - } else { - entryPointHandlers[entryPointName] = handlerWithAccessLog - } + entryPointHandlers[entryPointName] = handler } + // Create default handlers. for _, entryPointName := range entryPoints { logger := log.Ctx(rootCtx).With().Str(logs.EntryPointName, entryPointName).Logger() ctx := logger.WithContext(rootCtx) handler, ok := entryPointHandlers[entryPointName] - if !ok || handler == nil { - handler = BuildDefaultHTTPRouter() + if ok || handler != nil { + continue } - handlerWithMiddlewares, err := m.chainBuilder.Build(ctx, entryPointName).Then(handler) + handler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "").Then(BuildDefaultHTTPRouter()) if err != nil { logger.Error().Err(err).Send() continue } - entryPointHandlers[entryPointName] = handlerWithMiddlewares + entryPointHandlers[entryPointName] = handler } return entryPointHandlers } -func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*runtime.RouterInfo) (http.Handler, error) { +func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName string, configs map[string]*runtime.RouterInfo) (http.Handler, error) { muxer, err := httpmuxer.NewMuxer() if err != nil { return nil, err } + defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "defaultHandler").Then(http.NotFoundHandler()) + if err != nil { + return nil, err + } + + muxer.SetDefaultHandler(defaultHandler) + for routerName, routerConfig := range configs { logger := log.Ctx(ctx).With().Str(logs.RouterName, routerName).Logger() ctxRouter := logger.WithContext(provider.AddInContext(ctx, routerName)) @@ -131,7 +128,15 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string continue } - if err = muxer.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil { + observabilityChain := m.observabilityMgr.BuildEPChain(ctx, entryPointName, routerConfig.Service) + handler, err = observabilityChain.Then(handler) + if err != nil { + routerConfig.AddError(err, true) + logger.Error().Err(err).Send() + continue + } + + if err = muxer.AddRoute(routerConfig.Rule, routerConfig.RuleSyntax, routerConfig.Priority, handler); err != nil { routerConfig.AddError(err, true) logger.Error().Err(err).Send() continue @@ -167,6 +172,12 @@ func (m *Manager) buildRouterHandler(ctx context.Context, routerName string, rou return nil, err } + // Prevents from enabling observability for internal resources. + if !m.observabilityMgr.ShouldAddAccessLogs(provider.GetQualifiedName(ctx, routerConfig.Service)) { + m.routerHandlers[routerName] = handler + return m.routerHandlers[routerName], nil + } + handlerWithAccessLog, err := alice.New(func(next http.Handler) (http.Handler, error) { return accesslog.NewFieldHandler(next, accesslog.RouterName, routerName, nil), nil }).Then(handler) @@ -200,10 +211,20 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn chain := alice.New() + if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsRouterEnabled() && + m.observabilityMgr.ShouldAddMetrics(provider.GetQualifiedName(ctx, router.Service)) { + chain = chain.Append(metricsMiddle.WrapRouterHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, provider.GetQualifiedName(ctx, router.Service))) + } + + // Prevents from enabling tracing for internal resources. + if !m.observabilityMgr.ShouldAddTracing(provider.GetQualifiedName(ctx, router.Service)) { + return chain.Extend(*mHandler).Then(sHandler) + } + chain = chain.Append(tracing.WrapRouterHandler(ctx, routerName, router.Rule, provider.GetQualifiedName(ctx, router.Service))) - if m.metricsRegistry != nil && m.metricsRegistry.IsRouterEnabled() { - metricsHandler := metricsMiddle.WrapRouterHandler(ctx, m.metricsRegistry, routerName, provider.GetQualifiedName(ctx, router.Service)) + if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsRouterEnabled() { + metricsHandler := metricsMiddle.WrapRouterHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, provider.GetQualifiedName(ctx, router.Service)) chain = chain.Append(tracing.WrapMiddleware(ctx, metricsHandler)) } diff --git a/pkg/server/router/router_test.go b/pkg/server/router/router_test.go index 21e0de141..eb83ed25d 100644 --- a/pkg/server/router/router_test.go +++ b/pkg/server/router/router_test.go @@ -9,21 +9,15 @@ import ( "testing" "time" - "github.com/containous/alice" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/runtime" - "github.com/traefik/traefik/v3/pkg/metrics" - "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" - "github.com/traefik/traefik/v3/pkg/middlewares/capture" "github.com/traefik/traefik/v3/pkg/middlewares/requestdecorator" "github.com/traefik/traefik/v3/pkg/server/middleware" "github.com/traefik/traefik/v3/pkg/server/service" "github.com/traefik/traefik/v3/pkg/testhelpers" "github.com/traefik/traefik/v3/pkg/tls" - "github.com/traefik/traefik/v3/pkg/types" ) func TestRouterManager_Get(t *testing.T) { @@ -319,10 +313,9 @@ func TestRouterManager_Get(t *testing.T) { roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) - chainBuilder := middleware.NewChainBuilder(nil, nil, nil) tlsManager := tls.NewManager() - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager) handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false) @@ -341,126 +334,6 @@ func TestRouterManager_Get(t *testing.T) { } } -func TestAccessLog(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) - - t.Cleanup(func() { server.Close() }) - - testCases := []struct { - desc string - routersConfig map[string]*dynamic.Router - serviceConfig map[string]*dynamic.Service - middlewaresConfig map[string]*dynamic.Middleware - entryPoints []string - expected string - }{ - { - desc: "apply routerName in accesslog (first match)", - routersConfig: map[string]*dynamic.Router{ - "foo": { - EntryPoints: []string{"web"}, - Service: "foo-service", - Rule: "Host(`foo.bar`)", - }, - "bar": { - EntryPoints: []string{"web"}, - Service: "foo-service", - Rule: "Host(`bar.foo`)", - }, - }, - serviceConfig: map[string]*dynamic.Service{ - "foo-service": { - LoadBalancer: &dynamic.ServersLoadBalancer{ - Servers: []dynamic.Server{ - { - URL: server.URL, - }, - }, - }, - }, - }, - entryPoints: []string{"web"}, - expected: "foo", - }, - { - desc: "apply routerName in accesslog (second match)", - routersConfig: map[string]*dynamic.Router{ - "foo": { - EntryPoints: []string{"web"}, - Service: "foo-service", - Rule: "Host(`bar.foo`)", - }, - "bar": { - EntryPoints: []string{"web"}, - Service: "foo-service", - Rule: "Host(`foo.bar`)", - }, - }, - serviceConfig: map[string]*dynamic.Service{ - "foo-service": { - LoadBalancer: &dynamic.ServersLoadBalancer{ - Servers: []dynamic.Server{ - { - URL: server.URL, - }, - }, - }, - }, - }, - entryPoints: []string{"web"}, - expected: "bar", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - rtConf := runtime.NewConfig(dynamic.Configuration{ - HTTP: &dynamic.HTTPConfiguration{ - Services: test.serviceConfig, - Routers: test.routersConfig, - Middlewares: test.middlewaresConfig, - }, - }) - - roundTripperManager := service.NewRoundTripperManager(nil) - roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) - serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) - middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) - chainBuilder := middleware.NewChainBuilder(nil, nil, nil) - tlsManager := tls.NewManager() - - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager) - - handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false) - - w := httptest.NewRecorder() - req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil) - - accesslogger, err := accesslog.NewHandler(&types.AccessLog{ - Format: "json", - }) - require.NoError(t, err) - - reqHost := requestdecorator.New(nil) - - chain := alice.New() - chain = chain.Append(capture.Wrap) - chain = chain.Append(accesslog.WrapHandler(accesslogger)) - handler, err := chain.Then(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - reqHost.ServeHTTP(w, req, handlers["web"].ServeHTTP) - - data := accesslog.GetLogData(req) - require.NotNil(t, data) - - assert.Equal(t, test.expected, data.Core[accesslog.RouterName]) - })) - require.NoError(t, err) - - handler.ServeHTTP(w, req) - }) - } -} - func TestRuntimeConfiguration(t *testing.T) { testCases := []struct { desc string @@ -788,11 +661,10 @@ func TestRuntimeConfiguration(t *testing.T) { roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) - chainBuilder := middleware.NewChainBuilder(nil, nil, nil) tlsManager := tls.NewManager() tlsManager.UpdateConfigs(context.Background(), nil, test.tlsOptions, nil) - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager) _ = routerManager.BuildHandlers(context.Background(), entryPoints, false) _ = routerManager.BuildHandlers(context.Background(), entryPoints, true) @@ -866,10 +738,9 @@ func TestProviderOnMiddlewares(t *testing.T) { roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) - chainBuilder := middleware.NewChainBuilder(nil, nil, nil) tlsManager := tls.NewManager() - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager) _ = routerManager.BuildHandlers(context.Background(), entryPoints, false) @@ -935,10 +806,9 @@ func BenchmarkRouterServe(b *testing.B) { serviceManager := service.NewManager(rtConf.Services, nil, nil, staticRoundTripperGetter{res}) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) - chainBuilder := middleware.NewChainBuilder(nil, nil, nil) tlsManager := tls.NewManager() - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager) handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false) diff --git a/pkg/server/router/tcp/manager.go b/pkg/server/router/tcp/manager.go index df24ed704..cd10d5ccf 100644 --- a/pkg/server/router/tcp/manager.go +++ b/pkg/server/router/tcp/manager.go @@ -311,7 +311,7 @@ func (m *Manager) addTCPHandlers(ctx context.Context, configs map[string]*runtim if routerConfig.TLS == nil { logger.Debug().Msgf("Adding route for %q", routerConfig.Rule) - if err := router.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil { + if err := router.muxerTCP.AddRoute(routerConfig.Rule, routerConfig.RuleSyntax, routerConfig.Priority, handler); err != nil { routerConfig.AddError(err, true) logger.Error().Err(err).Send() } @@ -321,7 +321,7 @@ func (m *Manager) addTCPHandlers(ctx context.Context, configs map[string]*runtim if routerConfig.TLS.Passthrough { logger.Debug().Msgf("Adding Passthrough route for %q", routerConfig.Rule) - if err := router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil { + if err := router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.RuleSyntax, routerConfig.Priority, handler); err != nil { routerConfig.AddError(err, true) logger.Error().Err(err).Send() } @@ -355,7 +355,7 @@ func (m *Manager) addTCPHandlers(ctx context.Context, configs map[string]*runtim logger.Debug().Msgf("Adding special TLS closing route for %q because broken TLS options %s", routerConfig.Rule, tlsOptionsName) - if err := router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, &brokenTLSRouter{}); err != nil { + if err := router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.RuleSyntax, routerConfig.Priority, &brokenTLSRouter{}); err != nil { routerConfig.AddError(err, true) logger.Error().Err(err).Send() } @@ -389,7 +389,7 @@ func (m *Manager) addTCPHandlers(ctx context.Context, configs map[string]*runtim logger.Debug().Msgf("Adding TLS route for %q", routerConfig.Rule) - if err := router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil { + if err := router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.RuleSyntax, routerConfig.Priority, handler); err != nil { routerConfig.AddError(err, true) logger.Error().Err(err).Send() continue diff --git a/pkg/server/router/tcp/router.go b/pkg/server/router/tcp/router.go index 711a60313..74f563486 100644 --- a/pkg/server/router/tcp/router.go +++ b/pkg/server/router/tcp/router.go @@ -201,9 +201,9 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) { conn.Close() } -// AddRoute defines a handler for the given rule. -func (r *Router) AddRoute(rule string, priority int, target tcp.Handler) error { - return r.muxerTCP.AddRoute(rule, priority, target) +// AddTCPRoute defines a handler for the given rule. +func (r *Router) AddTCPRoute(rule string, priority int, target tcp.Handler) error { + return r.muxerTCP.AddRoute(rule, "", priority, target) } // AddHTTPTLSConfig defines a handler for a given sniHost and sets the matching tlsConfig. @@ -267,7 +267,7 @@ func (r *Router) SetHTTPSForwarder(handler tcp.Handler) { } rule := "HostSNI(`" + sniHost + "`)" - if err := r.muxerHTTPS.AddRoute(rule, tcpmuxer.GetRulePriority(rule), tcpHandler); err != nil { + if err := r.muxerHTTPS.AddRoute(rule, "", tcpmuxer.GetRulePriority(rule), tcpHandler); err != nil { log.Error().Err(err).Msg("Error while adding route for host") } } diff --git a/pkg/server/router/tcp/router_test.go b/pkg/server/router/tcp/router_test.go index 29bb2c418..64f32f7f7 100644 --- a/pkg/server/router/tcp/router_test.go +++ b/pkg/server/router/tcp/router_test.go @@ -173,9 +173,11 @@ func Test_Routing(t *testing.T) { map[string]traefiktls.Store{}, map[string]traefiktls.Options{ "default": { + MinVersion: "VersionTLS10", MaxVersion: "VersionTLS10", }, "tls10": { + MinVersion: "VersionTLS10", MaxVersion: "VersionTLS10", }, "tls12": { @@ -947,10 +949,10 @@ func TestPostgres(t *testing.T) { // This test requires to have a TLS route, but does not actually check the // content of the handler. It would require to code a TLS handshake to // check the SNI and content of the handlerFunc. - err = router.muxerTCPTLS.AddRoute("HostSNI(`test.localhost`)", 0, nil) + err = router.muxerTCPTLS.AddRoute("HostSNI(`test.localhost`)", "", 0, nil) require.NoError(t, err) - err = router.AddRoute("HostSNI(`*`)", 0, tcp2.HandlerFunc(func(conn tcp2.WriteCloser) { + err = router.muxerTCP.AddRoute("HostSNI(`*`)", "", 0, tcp2.HandlerFunc(func(conn tcp2.WriteCloser) { _, _ = conn.Write([]byte("OK")) _ = conn.Close() })) diff --git a/pkg/server/routerfactory.go b/pkg/server/routerfactory.go index d7a1bf68c..1ece7096a 100644 --- a/pkg/server/routerfactory.go +++ b/pkg/server/routerfactory.go @@ -6,7 +6,6 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/config/static" - "github.com/traefik/traefik/v3/pkg/metrics" "github.com/traefik/traefik/v3/pkg/server/middleware" tcpmiddleware "github.com/traefik/traefik/v3/pkg/server/middleware/tcp" "github.com/traefik/traefik/v3/pkg/server/router" @@ -25,13 +24,12 @@ type RouterFactory struct { entryPointsTCP []string entryPointsUDP []string - managerFactory *service.ManagerFactory - metricsRegistry metrics.Registry + managerFactory *service.ManagerFactory pluginBuilder middleware.PluginsBuilder - chainBuilder *middleware.ChainBuilder - tlsManager *tls.Manager + observabilityMgr *middleware.ObservabilityMgr + tlsManager *tls.Manager dialerManager *tcp.DialerManager @@ -40,7 +38,7 @@ type RouterFactory struct { // NewRouterFactory creates a new RouterFactory. func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *service.ManagerFactory, tlsManager *tls.Manager, - chainBuilder *middleware.ChainBuilder, pluginBuilder middleware.PluginsBuilder, metricsRegistry metrics.Registry, dialerManager *tcp.DialerManager, + observabilityMgr *middleware.ObservabilityMgr, pluginBuilder middleware.PluginsBuilder, dialerManager *tcp.DialerManager, ) *RouterFactory { var entryPointsTCP, entryPointsUDP []string for name, cfg := range staticConfiguration.EntryPoints { @@ -58,14 +56,13 @@ func NewRouterFactory(staticConfiguration static.Configuration, managerFactory * } return &RouterFactory{ - entryPointsTCP: entryPointsTCP, - entryPointsUDP: entryPointsUDP, - managerFactory: managerFactory, - metricsRegistry: metricsRegistry, - tlsManager: tlsManager, - chainBuilder: chainBuilder, - pluginBuilder: pluginBuilder, - dialerManager: dialerManager, + entryPointsTCP: entryPointsTCP, + entryPointsUDP: entryPointsUDP, + managerFactory: managerFactory, + observabilityMgr: observabilityMgr, + tlsManager: tlsManager, + pluginBuilder: pluginBuilder, + dialerManager: dialerManager, } } @@ -83,7 +80,7 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, f.pluginBuilder) - routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.chainBuilder, f.metricsRegistry, f.tlsManager) + routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.observabilityMgr, f.tlsManager) handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, false) handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true) diff --git a/pkg/server/routerfactory_test.go b/pkg/server/routerfactory_test.go index b935df89e..ebd923f62 100644 --- a/pkg/server/routerfactory_test.go +++ b/pkg/server/routerfactory_test.go @@ -9,7 +9,6 @@ import ( "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/config/static" - "github.com/traefik/traefik/v3/pkg/metrics" "github.com/traefik/traefik/v3/pkg/server/middleware" "github.com/traefik/traefik/v3/pkg/server/service" "github.com/traefik/traefik/v3/pkg/tcp" @@ -51,12 +50,12 @@ func TestReuseService(t *testing.T) { roundTripperManager := service.NewRoundTripperManager(nil) roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) - managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager, nil) + managerFactory := service.NewManagerFactory(staticConfig, nil, nil, roundTripperManager, nil) tlsManager := tls.NewManager() dialerManager := tcp.NewDialerManager(nil) dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}}) - factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(nil, nil, nil), nil, metrics.NewVoidRegistry(), dialerManager) + factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, nil, nil, dialerManager) entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: dynamicConfigs})) @@ -189,12 +188,13 @@ func TestServerResponseEmptyBackend(t *testing.T) { roundTripperManager := service.NewRoundTripperManager(nil) roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) - managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager, nil) + managerFactory := service.NewManagerFactory(staticConfig, nil, nil, roundTripperManager, nil) tlsManager := tls.NewManager() dialerManager := tcp.NewDialerManager(nil) dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}}) - factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(nil, nil, nil), nil, metrics.NewVoidRegistry(), dialerManager) + observabiltyMgr := middleware.NewObservabilityMgr(staticConfig, nil, nil, nil, nil) + factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, observabiltyMgr, nil, dialerManager) entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: test.config(testServer.URL)})) @@ -232,14 +232,12 @@ func TestInternalServices(t *testing.T) { roundTripperManager := service.NewRoundTripperManager(nil) roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) - managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager, nil) + managerFactory := service.NewManagerFactory(staticConfig, nil, nil, roundTripperManager, nil) tlsManager := tls.NewManager() - voidRegistry := metrics.NewVoidRegistry() - dialerManager := tcp.NewDialerManager(nil) dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}}) - factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(voidRegistry, nil, nil), nil, voidRegistry, dialerManager) + factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, nil, nil, dialerManager) entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: dynamicConfigs})) diff --git a/pkg/server/server.go b/pkg/server/server.go index 0f4379f04..9c96bdcc6 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -3,47 +3,39 @@ package server import ( "context" "errors" - "io" "os" "os/signal" "time" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/metrics" - "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" "github.com/traefik/traefik/v3/pkg/safe" "github.com/traefik/traefik/v3/pkg/server/middleware" ) // Server is the reverse-proxy/load-balancer engine. type Server struct { - watcher *ConfigurationWatcher - tcpEntryPoints TCPEntryPoints - udpEntryPoints UDPEntryPoints - chainBuilder *middleware.ChainBuilder - - accessLoggerMiddleware *accesslog.Handler + watcher *ConfigurationWatcher + tcpEntryPoints TCPEntryPoints + udpEntryPoints UDPEntryPoints + observabilityMgr *middleware.ObservabilityMgr signals chan os.Signal stopChan chan bool routinesPool *safe.Pool - - tracerCloser io.Closer } // NewServer returns an initialized Server. -func NewServer(routinesPool *safe.Pool, entryPoints TCPEntryPoints, entryPointsUDP UDPEntryPoints, watcher *ConfigurationWatcher, chainBuilder *middleware.ChainBuilder, accessLoggerMiddleware *accesslog.Handler, tracerCloser io.Closer) *Server { +func NewServer(routinesPool *safe.Pool, entryPoints TCPEntryPoints, entryPointsUDP UDPEntryPoints, watcher *ConfigurationWatcher, observabilityMgr *middleware.ObservabilityMgr) *Server { srv := &Server{ - watcher: watcher, - tcpEntryPoints: entryPoints, - chainBuilder: chainBuilder, - accessLoggerMiddleware: accessLoggerMiddleware, - signals: make(chan os.Signal, 1), - stopChan: make(chan bool, 1), - routinesPool: routinesPool, - udpEntryPoints: entryPointsUDP, - tracerCloser: tracerCloser, + watcher: watcher, + tcpEntryPoints: entryPoints, + observabilityMgr: observabilityMgr, + signals: make(chan os.Signal, 1), + stopChan: make(chan bool, 1), + routinesPool: routinesPool, + udpEntryPoints: entryPointsUDP, } srv.configureSignals() @@ -105,13 +97,7 @@ func (s *Server) Close() { close(s.stopChan) - s.chainBuilder.Close() - - if s.tracerCloser != nil { - if err := s.tracerCloser.Close(); err != nil { - log.Error().Err(err).Msg("Could not close the tracer") - } - } + s.observabilityMgr.Close() cancel() } diff --git a/pkg/server/server_entrypoint_listenconfig_other.go b/pkg/server/server_entrypoint_listenconfig_other.go new file mode 100644 index 000000000..199012284 --- /dev/null +++ b/pkg/server/server_entrypoint_listenconfig_other.go @@ -0,0 +1,15 @@ +//go:build !(linux || freebsd || openbsd || darwin) + +package server + +import ( + "net" + + "github.com/traefik/traefik/v3/pkg/config/static" +) + +// newListenConfig creates a new net.ListenConfig for the given configuration of +// the entry point. +func newListenConfig(configuration *static.EntryPoint) (lc net.ListenConfig) { + return +} diff --git a/pkg/server/server_entrypoint_listenconfig_other_test.go b/pkg/server/server_entrypoint_listenconfig_other_test.go new file mode 100644 index 000000000..f2c736d26 --- /dev/null +++ b/pkg/server/server_entrypoint_listenconfig_other_test.go @@ -0,0 +1,44 @@ +//go:build !(linux || freebsd || openbsd || darwin) + +package server + +import ( + "context" + "net" + "testing" + + "github.com/stretchr/testify/require" + "github.com/traefik/traefik/v3/pkg/config/static" +) + +func TestNewListenConfig(t *testing.T) { + ep := static.EntryPoint{Address: ":0"} + listenConfig := newListenConfig(&ep) + require.Nil(t, listenConfig.Control) + require.Zero(t, listenConfig.KeepAlive) + + l1, err := listenConfig.Listen(context.Background(), "tcp", ep.Address) + require.NoError(t, err) + require.NotNil(t, l1) + defer l1.Close() + + l2, err := listenConfig.Listen(context.Background(), "tcp", l1.Addr().String()) + require.Error(t, err) + require.ErrorContains(t, err, "address already in use") + require.Nil(t, l2) + + ep = static.EntryPoint{Address: ":0", ReusePort: true} + listenConfig = newListenConfig(&ep) + require.Nil(t, listenConfig.Control) + require.Zero(t, listenConfig.KeepAlive) + + l3, err := listenConfig.Listen(context.Background(), "tcp", ep.Address) + require.NoError(t, err) + require.NotNil(t, l3) + defer l3.Close() + + l4, err := listenConfig.Listen(context.Background(), "tcp", l3.Addr().String()) + require.Error(t, err) + require.ErrorContains(t, err, "address already in use") + require.Nil(t, l4) +} diff --git a/pkg/server/server_entrypoint_listenconfig_unix.go b/pkg/server/server_entrypoint_listenconfig_unix.go new file mode 100644 index 000000000..7e43f4f7d --- /dev/null +++ b/pkg/server/server_entrypoint_listenconfig_unix.go @@ -0,0 +1,44 @@ +//go:build linux || freebsd || openbsd || darwin + +package server + +import ( + "fmt" + "net" + "syscall" + + "github.com/traefik/traefik/v3/pkg/config/static" + "golang.org/x/sys/unix" +) + +// newListenConfig creates a new net.ListenConfig for the given configuration of +// the entry point. +func newListenConfig(configuration *static.EntryPoint) (lc net.ListenConfig) { + if configuration != nil && configuration.ReusePort { + lc.Control = controlReusePort + } + return +} + +// controlReusePort is a net.ListenConfig.Control function that enables SO_REUSEPORT +// on the socket. +func controlReusePort(network, address string, c syscall.RawConn) error { + var setSockOptErr error + err := c.Control(func(fd uintptr) { + // Note that net.ListenConfig enables unix.SO_REUSEADDR by default, + // as seen in https://go.dev/src/net/sockopt_linux.go. Therefore, no + // additional action is required to enable it here. + + setSockOptErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unixSOREUSEPORT, 1) + if setSockOptErr != nil { + return + } + }) + if err != nil { + return fmt.Errorf("control: %w", err) + } + if setSockOptErr != nil { + return fmt.Errorf("setsockopt: %w", setSockOptErr) + } + return nil +} diff --git a/pkg/server/server_entrypoint_listenconfig_unix_sockopt_freebsd.go b/pkg/server/server_entrypoint_listenconfig_unix_sockopt_freebsd.go new file mode 100644 index 000000000..e20b61c48 --- /dev/null +++ b/pkg/server/server_entrypoint_listenconfig_unix_sockopt_freebsd.go @@ -0,0 +1,7 @@ +//go:build freebsd + +package server + +import "golang.org/x/sys/unix" + +const unixSOREUSEPORT = unix.SO_REUSEPORT_LB diff --git a/pkg/server/server_entrypoint_listenconfig_unix_sockopt_other.go b/pkg/server/server_entrypoint_listenconfig_unix_sockopt_other.go new file mode 100644 index 000000000..306dd8bd2 --- /dev/null +++ b/pkg/server/server_entrypoint_listenconfig_unix_sockopt_other.go @@ -0,0 +1,7 @@ +//go:build linux || openbsd || darwin + +package server + +import "golang.org/x/sys/unix" + +const unixSOREUSEPORT = unix.SO_REUSEPORT diff --git a/pkg/server/server_entrypoint_listenconfig_unix_test.go b/pkg/server/server_entrypoint_listenconfig_unix_test.go new file mode 100644 index 000000000..a5f7dda0a --- /dev/null +++ b/pkg/server/server_entrypoint_listenconfig_unix_test.go @@ -0,0 +1,56 @@ +//go:build linux || freebsd || openbsd || darwin + +package server + +import ( + "context" + "net" + "testing" + + "github.com/stretchr/testify/require" + "github.com/traefik/traefik/v3/pkg/config/static" +) + +func TestNewListenConfig(t *testing.T) { + ep := static.EntryPoint{Address: ":0"} + listenConfig := newListenConfig(&ep) + require.Nil(t, listenConfig.Control) + require.Zero(t, listenConfig.KeepAlive) + + l1, err := listenConfig.Listen(context.Background(), "tcp", ep.Address) + require.NoError(t, err) + require.NotNil(t, l1) + defer l1.Close() + + l2, err := listenConfig.Listen(context.Background(), "tcp", l1.Addr().String()) + require.Error(t, err) + require.ErrorContains(t, err, "address already in use") + require.Nil(t, l2) + + ep = static.EntryPoint{Address: ":0", ReusePort: true} + listenConfig = newListenConfig(&ep) + require.NotNil(t, listenConfig.Control) + require.Zero(t, listenConfig.KeepAlive) + + l3, err := listenConfig.Listen(context.Background(), "tcp", ep.Address) + require.NoError(t, err) + require.NotNil(t, l3) + defer l3.Close() + + l4, err := listenConfig.Listen(context.Background(), "tcp", l3.Addr().String()) + require.NoError(t, err) + require.NotNil(t, l4) + defer l4.Close() + + _, l3Port, err := net.SplitHostPort(l3.Addr().String()) + require.NoError(t, err) + l5, err := listenConfig.Listen(context.Background(), "tcp", "127.0.0.1:"+l3Port) + require.NoError(t, err) + require.NotNil(t, l5) + defer l5.Close() + + l6, err := listenConfig.Listen(context.Background(), "tcp", l1.Addr().String()) + require.Error(t, err) + require.ErrorContains(t, err, "address already in use") + require.Nil(t, l6) +} diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index e4d2892a8..6a25953d4 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -31,6 +31,7 @@ import ( "github.com/traefik/traefik/v3/pkg/safe" "github.com/traefik/traefik/v3/pkg/server/router" tcprouter "github.com/traefik/traefik/v3/pkg/server/router/tcp" + "github.com/traefik/traefik/v3/pkg/server/service" "github.com/traefik/traefik/v3/pkg/tcp" "github.com/traefik/traefik/v3/pkg/types" "golang.org/x/net/http2" @@ -387,7 +388,7 @@ func writeCloser(conn net.Conn) (tcp.WriteCloser, error) { case *proxyproto.Conn: underlying, ok := typedConn.TCPConn() if !ok { - return nil, fmt.Errorf("underlying connection is not a tcp connection") + return nil, errors.New("underlying connection is not a tcp connection") } return &writeCloserWrapper{writeCloser: underlying, Conn: typedConn}, nil case *net.TCPConn: @@ -460,7 +461,8 @@ func buildProxyProtocolListener(ctx context.Context, entryPoint *static.EntryPoi } func buildListener(ctx context.Context, entryPoint *static.EntryPoint) (net.Listener, error) { - listener, err := net.Listen("tcp", entryPoint.GetAddress()) + listenConfig := newListenConfig(entryPoint) + listener, err := listenConfig.Listen(ctx, "tcp", entryPoint.GetAddress()) if err != nil { return nil, fmt.Errorf("error opening listener: %w", err) } @@ -632,6 +634,16 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati } } + prevConnContext := serverHTTP.ConnContext + serverHTTP.ConnContext = func(ctx context.Context, c net.Conn) context.Context { + // This adds an empty struct in order to store a RoundTripper in the ConnContext in case of Kerberos or NTLM. + ctx = service.AddTransportOnContext(ctx) + if prevConnContext != nil { + return prevConnContext(ctx, c) + } + return ctx + } + // ConfigureServer configures HTTP/2 with the MaxConcurrentStreams option for the given server. // Also keeping behavior the same as // https://cs.opensource.google/go/go/+/refs/tags/go1.17.7:src/net/http/server.go;l=3262 @@ -640,7 +652,6 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati MaxConcurrentStreams: uint32(configuration.HTTP2.MaxConcurrentStreams), NewWriteScheduler: func() http2.WriteScheduler { return http2.NewPriorityWriteScheduler(nil) }, }) - if err != nil { return nil, fmt.Errorf("configure HTTP/2 server: %w", err) } diff --git a/pkg/server/server_entrypoint_tcp_http3.go b/pkg/server/server_entrypoint_tcp_http3.go index 188be8026..827d6638b 100644 --- a/pkg/server/server_entrypoint_tcp_http3.go +++ b/pkg/server/server_entrypoint_tcp_http3.go @@ -33,7 +33,8 @@ func newHTTP3Server(ctx context.Context, configuration *static.EntryPoint, https return nil, errors.New("advertised port must be greater than or equal to zero") } - conn, err := net.ListenPacket("udp", configuration.GetAddress()) + listenConfig := newListenConfig(configuration) + conn, err := listenConfig.ListenPacket(ctx, "udp", configuration.GetAddress()) if err != nil { return nil, fmt.Errorf("starting listener: %w", err) } diff --git a/pkg/server/server_entrypoint_tcp_test.go b/pkg/server/server_entrypoint_tcp_test.go index d050803f3..d53f160f8 100644 --- a/pkg/server/server_entrypoint_tcp_test.go +++ b/pkg/server/server_entrypoint_tcp_test.go @@ -47,7 +47,7 @@ func TestShutdownTCP(t *testing.T) { router, err := tcprouter.NewRouter() require.NoError(t, err) - err = router.AddRoute("HostSNI(`*`)", 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) { + err = router.AddTCPRoute("HostSNI(`*`)", 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) { _, err := http.ReadRequest(bufio.NewReader(conn)) if err != nil { return diff --git a/pkg/server/server_entrypoint_udp.go b/pkg/server/server_entrypoint_udp.go index 347b169db..08ed4fb1f 100644 --- a/pkg/server/server_entrypoint_udp.go +++ b/pkg/server/server_entrypoint_udp.go @@ -3,7 +3,6 @@ package server import ( "context" "fmt" - "net" "sync" "time" @@ -87,12 +86,8 @@ type UDPEntryPoint struct { // NewUDPEntryPoint returns a UDP entry point. func NewUDPEntryPoint(cfg *static.EntryPoint) (*UDPEntryPoint, error) { - addr, err := net.ResolveUDPAddr("udp", cfg.GetAddress()) - if err != nil { - return nil, err - } - - listener, err := udp.Listen("udp", addr, time.Duration(cfg.UDP.Timeout)) + listenConfig := newListenConfig(cfg) + listener, err := udp.Listen(listenConfig, "udp", cfg.GetAddress(), time.Duration(cfg.UDP.Timeout)) if err != nil { return nil, err } diff --git a/pkg/server/server_signals.go b/pkg/server/server_signals.go index 0987abb30..c582fce5c 100644 --- a/pkg/server/server_signals.go +++ b/pkg/server/server_signals.go @@ -24,10 +24,8 @@ func (s *Server) listenSignals(ctx context.Context) { if sig == syscall.SIGUSR1 { log.Info().Msgf("Closing and re-opening log files for rotation: %+v", sig) - if s.accessLoggerMiddleware != nil { - if err := s.accessLoggerMiddleware.Rotate(); err != nil { - log.Error().Err(err).Msg("Error rotating access log") - } + if err := s.observabilityMgr.RotateAccessLogs(); err != nil { + log.Error().Err(err).Msg("Error rotating access log") } } } diff --git a/pkg/server/service/loadbalancer/wrr/wrr.go b/pkg/server/service/loadbalancer/wrr/wrr.go index f38facf35..6aa04c8ec 100644 --- a/pkg/server/service/loadbalancer/wrr/wrr.go +++ b/pkg/server/service/loadbalancer/wrr/wrr.go @@ -4,9 +4,9 @@ import ( "container/heap" "context" "errors" - "fmt" "hash/fnv" "net/http" + "strconv" "sync" "github.com/rs/zerolog/log" @@ -276,5 +276,5 @@ func hash(input string) string { // We purposely ignore the error because the implementation always returns nil. _, _ = hasher.Write([]byte(input)) - return fmt.Sprintf("%x", hasher.Sum64()) + return strconv.FormatUint(hasher.Sum64(), 16) } diff --git a/pkg/server/service/managerfactory.go b/pkg/server/service/managerfactory.go index 524ae01d9..abff2d5a6 100644 --- a/pkg/server/service/managerfactory.go +++ b/pkg/server/service/managerfactory.go @@ -10,11 +10,12 @@ import ( "github.com/traefik/traefik/v3/pkg/config/static" "github.com/traefik/traefik/v3/pkg/metrics" "github.com/traefik/traefik/v3/pkg/safe" + "github.com/traefik/traefik/v3/pkg/server/middleware" ) // ManagerFactory a factory of service manager. type ManagerFactory struct { - metricsRegistry metrics.Registry + observabilityMgr *middleware.ObservabilityMgr roundTripperManager *RoundTripperManager @@ -29,9 +30,9 @@ type ManagerFactory struct { } // NewManagerFactory creates a new ManagerFactory. -func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *safe.Pool, metricsRegistry metrics.Registry, roundTripperManager *RoundTripperManager, acmeHTTPHandler http.Handler) *ManagerFactory { +func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *safe.Pool, observabilityMgr *middleware.ObservabilityMgr, roundTripperManager *RoundTripperManager, acmeHTTPHandler http.Handler) *ManagerFactory { factory := &ManagerFactory{ - metricsRegistry: metricsRegistry, + observabilityMgr: observabilityMgr, routinesPool: routinesPool, roundTripperManager: roundTripperManager, acmeHTTPHandler: acmeHTTPHandler, @@ -72,7 +73,7 @@ func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *s // Build creates a service manager. func (f *ManagerFactory) Build(configuration *runtime.Configuration) *InternalHandlers { - svcManager := NewManager(configuration.Services, f.metricsRegistry, f.routinesPool, f.roundTripperManager) + svcManager := NewManager(configuration.Services, f.observabilityMgr, f.routinesPool, f.roundTripperManager) var apiHandler http.Handler if f.api != nil { diff --git a/pkg/server/service/roundtripper.go b/pkg/server/service/roundtripper.go index 7b7786a07..3259e04fb 100644 --- a/pkg/server/service/roundtripper.go +++ b/pkg/server/service/roundtripper.go @@ -1,6 +1,7 @@ package service import ( + "context" "crypto/tls" "crypto/x509" "errors" @@ -8,6 +9,7 @@ import ( "net" "net/http" "reflect" + "strings" "sync" "time" @@ -180,10 +182,71 @@ func (r *RoundTripperManager) createRoundTripper(cfg *dynamic.ServersTransport) // Return directly HTTP/1.1 transport when HTTP/2 is disabled if cfg.DisableHTTP2 { - return transport, nil + return &KerberosRoundTripper{ + OriginalRoundTripper: transport, + new: func() http.RoundTripper { + return transport.Clone() + }, + }, nil } - return newSmartRoundTripper(transport, cfg.ForwardingTimeouts) + rt, err := newSmartRoundTripper(transport, cfg.ForwardingTimeouts) + if err != nil { + return nil, err + } + return &KerberosRoundTripper{ + OriginalRoundTripper: rt, + new: func() http.RoundTripper { + return rt.Clone() + }, + }, nil +} + +type KerberosRoundTripper struct { + new func() http.RoundTripper + OriginalRoundTripper http.RoundTripper +} + +type stickyRoundTripper struct { + RoundTripper http.RoundTripper +} + +type transportKeyType string + +var transportKey transportKeyType = "transport" + +func AddTransportOnContext(ctx context.Context) context.Context { + return context.WithValue(ctx, transportKey, &stickyRoundTripper{}) +} + +func (k *KerberosRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) { + value, ok := request.Context().Value(transportKey).(*stickyRoundTripper) + if !ok { + return k.OriginalRoundTripper.RoundTrip(request) + } + + if value.RoundTripper != nil { + return value.RoundTripper.RoundTrip(request) + } + + resp, err := k.OriginalRoundTripper.RoundTrip(request) + + // If we found that we are authenticating with Kerberos (Negotiate) or NTLM. + // We put a dedicated roundTripper in the ConnContext. + // This will stick the next calls to the same connection with the backend. + if err == nil && containsNTLMorNegotiate(resp.Header.Values("WWW-Authenticate")) { + value.RoundTripper = k.new() + } + return resp, err +} + +func containsNTLMorNegotiate(h []string) bool { + for _, s := range h { + if strings.HasPrefix(s, "NTLM") || strings.HasPrefix(s, "Negotiate") { + return true + } + } + return false } func createRootCACertPool(rootCAs []types.FileOrContent) *x509.CertPool { diff --git a/pkg/server/service/roundtripper_test.go b/pkg/server/service/roundtripper_test.go index 2d7763888..2ca542d69 100644 --- a/pkg/server/service/roundtripper_test.go +++ b/pkg/server/service/roundtripper_test.go @@ -1,6 +1,7 @@ package service import ( + "context" "crypto/rand" "crypto/rsa" "crypto/tls" @@ -549,3 +550,80 @@ func (s *fakeSpiffeSource) GetX509BundleForTrustDomain(trustDomain spiffeid.Trus func (s *fakeSpiffeSource) GetX509SVID() (*x509svid.SVID, error) { return s.svid, nil } + +type roundTripperFn func(req *http.Request) (*http.Response, error) + +func (r roundTripperFn) RoundTrip(request *http.Request) (*http.Response, error) { + return r(request) +} + +func TestKerberosRoundTripper(t *testing.T) { + testCases := []struct { + desc string + + originalRoundTripperHeaders map[string][]string + + expectedStatusCode []int + expectedDedicatedCount int + expectedOriginalCount int + }{ + { + desc: "without special header", + expectedStatusCode: []int{http.StatusUnauthorized, http.StatusUnauthorized, http.StatusUnauthorized}, + expectedOriginalCount: 3, + }, + { + desc: "with Negotiate (Kerberos)", + originalRoundTripperHeaders: map[string][]string{"Www-Authenticate": {"Negotiate"}}, + expectedStatusCode: []int{http.StatusUnauthorized, http.StatusOK, http.StatusOK}, + expectedOriginalCount: 1, + expectedDedicatedCount: 2, + }, + { + desc: "with NTLM", + originalRoundTripperHeaders: map[string][]string{"Www-Authenticate": {"NTLM"}}, + expectedStatusCode: []int{http.StatusUnauthorized, http.StatusOK, http.StatusOK}, + expectedOriginalCount: 1, + expectedDedicatedCount: 2, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + origCount := 0 + dedicatedCount := 0 + rt := KerberosRoundTripper{ + new: func() http.RoundTripper { + return roundTripperFn(func(req *http.Request) (*http.Response, error) { + dedicatedCount++ + return &http.Response{ + StatusCode: http.StatusOK, + }, nil + }) + }, + OriginalRoundTripper: roundTripperFn(func(req *http.Request) (*http.Response, error) { + origCount++ + return &http.Response{ + StatusCode: http.StatusUnauthorized, + Header: test.originalRoundTripperHeaders, + }, nil + }), + } + + ctx := AddTransportOnContext(context.Background()) + for _, expected := range test.expectedStatusCode { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://127.0.0.1", http.NoBody) + require.NoError(t, err) + resp, err := rt.RoundTrip(req) + require.NoError(t, err) + require.Equal(t, expected, resp.StatusCode) + } + + require.Equal(t, test.expectedOriginalCount, origCount) + require.Equal(t, test.expectedDedicatedCount, dedicatedCount) + }) + } +} diff --git a/pkg/server/service/service.go b/pkg/server/service/service.go index 55fcb9fa2..b01d2327c 100644 --- a/pkg/server/service/service.go +++ b/pkg/server/service/service.go @@ -20,12 +20,12 @@ import ( "github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/healthcheck" "github.com/traefik/traefik/v3/pkg/logs" - "github.com/traefik/traefik/v3/pkg/metrics" "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics" tracingMiddle "github.com/traefik/traefik/v3/pkg/middlewares/tracing" "github.com/traefik/traefik/v3/pkg/safe" "github.com/traefik/traefik/v3/pkg/server/cookie" + "github.com/traefik/traefik/v3/pkg/server/middleware" "github.com/traefik/traefik/v3/pkg/server/provider" "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/failover" "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/mirror" @@ -42,7 +42,7 @@ type RoundTripperGetter interface { // Manager The service manager. type Manager struct { routinePool *safe.Pool - metricsRegistry metrics.Registry + observabilityMgr *middleware.ObservabilityMgr bufferPool httputil.BufferPool roundTripperManager RoundTripperGetter @@ -53,10 +53,10 @@ type Manager struct { } // NewManager creates a new Manager. -func NewManager(configs map[string]*runtime.ServiceInfo, metricsRegistry metrics.Registry, routinePool *safe.Pool, roundTripperManager RoundTripperGetter) *Manager { +func NewManager(configs map[string]*runtime.ServiceInfo, observabilityMgr *middleware.ObservabilityMgr, routinePool *safe.Pool, roundTripperManager RoundTripperGetter) *Manager { return &Manager{ routinePool: routinePool, - metricsRegistry: metricsRegistry, + observabilityMgr: observabilityMgr, bufferPool: newBufferPool(), roundTripperManager: roundTripperManager, services: make(map[string]http.Handler), @@ -302,12 +302,17 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName proxy := buildSingleHostProxy(target, passHostHeader, time.Duration(flushInterval), roundTripper, m.bufferPool) - proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceURL, target.String(), nil) - proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceAddr, target.Host, nil) - proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, serviceName, accesslog.AddServiceFields) + // Prevents from enabling observability for internal resources. - if m.metricsRegistry != nil && m.metricsRegistry.IsSvcEnabled() { - metricsHandler := metricsMiddle.WrapServiceHandler(ctx, m.metricsRegistry, serviceName) + if m.observabilityMgr.ShouldAddAccessLogs(provider.GetQualifiedName(ctx, serviceName)) { + proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceURL, target.String(), nil) + proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceAddr, target.Host, nil) + proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, serviceName, accesslog.AddServiceFields) + } + + if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsSvcEnabled() && + m.observabilityMgr.ShouldAddMetrics(provider.GetQualifiedName(ctx, serviceName)) { + metricsHandler := metricsMiddle.WrapServiceHandler(ctx, m.observabilityMgr.MetricsRegistry(), serviceName) proxy, err = alice.New(). Append(tracingMiddle.WrapMiddleware(ctx, metricsHandler)). @@ -317,9 +322,11 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName } } - proxy = tracingMiddle.NewService(ctx, serviceName, proxy) + if m.observabilityMgr.ShouldAddTracing(provider.GetQualifiedName(ctx, serviceName)) { + proxy = tracingMiddle.NewService(ctx, serviceName, proxy) + } - lb.Add(proxyName, proxy, nil) + lb.Add(proxyName, proxy, server.Weight) // servers are considered UP by default. info.UpdateServerStatus(target.String(), runtime.StatusUp) @@ -330,7 +337,7 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName if service.HealthCheck != nil { m.healthCheckers[serviceName] = healthcheck.NewServiceHealthChecker( ctx, - m.metricsRegistry, + m.observabilityMgr.MetricsRegistry(), service.HealthCheck, lb, info, diff --git a/pkg/server/service/smart_roundtripper.go b/pkg/server/service/smart_roundtripper.go index 3b88b499a..93b8c62d6 100644 --- a/pkg/server/service/smart_roundtripper.go +++ b/pkg/server/service/smart_roundtripper.go @@ -11,7 +11,7 @@ import ( "golang.org/x/net/http2" ) -func newSmartRoundTripper(transport *http.Transport, forwardingTimeouts *dynamic.ForwardingTimeouts) (http.RoundTripper, error) { +func newSmartRoundTripper(transport *http.Transport, forwardingTimeouts *dynamic.ForwardingTimeouts) (*smartRoundTripper, error) { transportHTTP1 := transport.Clone() transportHTTP2, err := http2.ConfigureTransports(transport) @@ -53,6 +53,12 @@ type smartRoundTripper struct { http *http.Transport } +func (m *smartRoundTripper) Clone() http.RoundTripper { + h := m.http.Clone() + h2 := m.http2.Clone() + return &smartRoundTripper{http: h, http2: h2} +} + func (m *smartRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { // If we have a connection upgrade, we don't use HTTP/2 if httpguts.HeaderValuesContainsToken(req.Header["Connection"], "Upgrade") { diff --git a/pkg/server/service/tcp/service.go b/pkg/server/service/tcp/service.go index bbc19b8cd..64c173a51 100644 --- a/pkg/server/service/tcp/service.go +++ b/pkg/server/service/tcp/service.go @@ -13,6 +13,7 @@ import ( "github.com/traefik/traefik/v3/pkg/logs" "github.com/traefik/traefik/v3/pkg/server/provider" "github.com/traefik/traefik/v3/pkg/tcp" + "golang.org/x/net/proxy" ) // Manager is the TCPHandlers factory. @@ -53,6 +54,10 @@ func (m *Manager) BuildTCP(rootCtx context.Context, serviceName string) (tcp.Han case conf.LoadBalancer != nil: loadBalancer := tcp.NewWRRLoadBalancer() + if conf.LoadBalancer.TerminationDelay != nil { + log.Ctx(ctx).Warn().Msgf("Service %q load balancer uses `TerminationDelay`, but this option is deprecated, please use ServersTransport configuration instead.", serviceName) + } + if len(conf.LoadBalancer.ServersTransport) > 0 { conf.LoadBalancer.ServersTransport = provider.GetQualifiedName(ctx, conf.LoadBalancer.ServersTransport) } @@ -72,6 +77,14 @@ func (m *Manager) BuildTCP(rootCtx context.Context, serviceName string) (tcp.Han return nil, err } + // Handle TerminationDelay deprecated option. + if conf.LoadBalancer.ServersTransport == "" && conf.LoadBalancer.TerminationDelay != nil { + dialer = &dialerWrapper{ + Dialer: dialer, + terminationDelay: time.Duration(*conf.LoadBalancer.TerminationDelay), + } + } + handler, err := tcp.NewProxy(server.Address, conf.LoadBalancer.ProxyProtocol, dialer) if err != nil { srvLogger.Error().Err(err).Msg("Failed to create server") @@ -113,3 +126,13 @@ func shuffle[T any](values []T, r *rand.Rand) []T { return shuffled } + +// dialerWrapper is only used to handle TerminationDelay deprecated option on TCPServersLoadBalancer. +type dialerWrapper struct { + proxy.Dialer + terminationDelay time.Duration +} + +func (d dialerWrapper) TerminationDelay() time.Duration { + return d.terminationDelay +} diff --git a/pkg/tcp/chain.go b/pkg/tcp/chain.go index 8cc351ba5..f50262d72 100644 --- a/pkg/tcp/chain.go +++ b/pkg/tcp/chain.go @@ -1,7 +1,7 @@ package tcp import ( - "fmt" + "errors" ) // Constructor A constructor for a piece of TCP middleware. @@ -29,7 +29,7 @@ func NewChain(constructors ...Constructor) Chain { // Then adds an handler at the end of the chain. func (c Chain) Then(h Handler) (Handler, error) { if h == nil { - return nil, fmt.Errorf("cannot add a nil handler to the chain") + return nil, errors.New("cannot add a nil handler to the chain") } for i := range c.constructors { diff --git a/pkg/tcp/wrr_load_balancer.go b/pkg/tcp/wrr_load_balancer.go index d494e0abf..e4eb9650e 100644 --- a/pkg/tcp/wrr_load_balancer.go +++ b/pkg/tcp/wrr_load_balancer.go @@ -1,7 +1,7 @@ package tcp import ( - "fmt" + "errors" "sync" "github.com/rs/zerolog/log" @@ -91,7 +91,7 @@ func gcd(a, b int) int { func (b *WRRLoadBalancer) next() (Handler, error) { if len(b.servers) == 0 { - return nil, fmt.Errorf("no servers in the pool") + return nil, errors.New("no servers in the pool") } // The algo below may look messy, but is actually very simple @@ -101,7 +101,7 @@ func (b *WRRLoadBalancer) next() (Handler, error) { // Maximum weight across all enabled servers max := b.maxWeight() if max == 0 { - return nil, fmt.Errorf("all servers have 0 weight") + return nil, errors.New("all servers have 0 weight") } // GCD across all enabled servers diff --git a/pkg/tls/tls.go b/pkg/tls/tls.go index fa12cbc85..5b1827c97 100644 --- a/pkg/tls/tls.go +++ b/pkg/tls/tls.go @@ -25,6 +25,9 @@ type Options struct { ClientAuth ClientAuth `json:"clientAuth,omitempty" toml:"clientAuth,omitempty" yaml:"clientAuth,omitempty"` SniStrict bool `json:"sniStrict,omitempty" toml:"sniStrict,omitempty" yaml:"sniStrict,omitempty" export:"true"` ALPNProtocols []string `json:"alpnProtocols,omitempty" toml:"alpnProtocols,omitempty" yaml:"alpnProtocols,omitempty" export:"true"` + + // Deprecated: https://github.com/golang/go/issues/45430 + PreferServerCipherSuites *bool `json:"preferServerCipherSuites,omitempty" toml:"preferServerCipherSuites,omitempty" yaml:"preferServerCipherSuites,omitempty" export:"true"` } // SetDefaults sets the default values for an Options struct. diff --git a/pkg/tls/tlsmanager.go b/pkg/tls/tlsmanager.go index 790745d98..b07cfe507 100644 --- a/pkg/tls/tlsmanager.go +++ b/pkg/tls/tlsmanager.go @@ -68,6 +68,13 @@ func (m *Manager) UpdateConfigs(ctx context.Context, stores map[string]Store, co defer m.lock.Unlock() m.configs = configs + for optionName, option := range m.configs { + // Handle `PreferServerCipherSuites` depreciation + if option.PreferServerCipherSuites != nil { + log.Ctx(ctx).Warn().Msgf("TLSOption %q uses `PreferServerCipherSuites` option, but this option is deprecated and ineffective, please remove this option.", optionName) + } + } + m.storesConfig = stores m.certs = certs diff --git a/pkg/tls/tlsmanager_test.go b/pkg/tls/tlsmanager_test.go index 0ec367883..3e19b2d50 100644 --- a/pkg/tls/tlsmanager_test.go +++ b/pkg/tls/tlsmanager_test.go @@ -334,10 +334,6 @@ func TestManager_Get_DefaultValues(t *testing.T) { assert.Equal(t, uint16(tls.VersionTLS12), config.MinVersion) assert.Equal(t, []string{"h2", "http/1.1", "acme-tls/1"}, config.NextProtos) assert.Equal(t, []uint16{ - tls.TLS_RSA_WITH_AES_128_CBC_SHA, - tls.TLS_RSA_WITH_AES_256_CBC_SHA, - tls.TLS_RSA_WITH_AES_128_GCM_SHA256, - tls.TLS_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384, tls.TLS_CHACHA20_POLY1305_SHA256, diff --git a/pkg/tls/zz_generated.deepcopy.go b/pkg/tls/zz_generated.deepcopy.go index 26461ab0d..59ffcb2bf 100644 --- a/pkg/tls/zz_generated.deepcopy.go +++ b/pkg/tls/zz_generated.deepcopy.go @@ -116,6 +116,11 @@ func (in *Options) DeepCopyInto(out *Options) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.PreferServerCipherSuites != nil { + in, out := &in.PreferServerCipherSuites, &out.PreferServerCipherSuites + *out = new(bool) + **out = **in + } return } diff --git a/pkg/tracing/opentelemetry/opentelemetry.go b/pkg/tracing/opentelemetry/opentelemetry.go index 2e13bc20c..24a737206 100644 --- a/pkg/tracing/opentelemetry/opentelemetry.go +++ b/pkg/tracing/opentelemetry/opentelemetry.go @@ -27,44 +27,26 @@ import ( // Config provides configuration settings for the open-telemetry tracer. type Config struct { - GRPC *GRPC `description:"gRPC configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" export:"true"` - HTTP *HTTP `description:"HTTP configuration for the OpenTelemetry collector." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true"` -} - -// GRPC provides configuration settings for the gRPC open-telemetry tracer. -type GRPC struct { - Endpoint string `description:"Sets the gRPC endpoint (host:port) of the collector." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` - - Insecure bool `description:"Disables client transport security for the exporter." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"` - TLS *types.ClientTLS `description:"Defines client transport security parameters." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` + GRPC *types.OtelGRPC `description:"gRPC configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" export:"true"` + HTTP *types.OtelHTTP `description:"HTTP configuration for the OpenTelemetry collector." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true"` } // SetDefaults sets the default values. -func (c *GRPC) SetDefaults() { - c.Endpoint = "localhost:4317" -} - -// HTTP provides configuration settings for the HTTP open-telemetry tracer. -type HTTP struct { - Endpoint string `description:"Sets the HTTP endpoint (scheme://host:port/v1/traces) of the collector." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` - TLS *types.ClientTLS `description:"Defines client transport security parameters." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` -} - -// SetDefaults sets the default values. -func (c *HTTP) SetDefaults() { - c.Endpoint = "localhost:4318" +func (c *Config) SetDefaults() { + c.HTTP = &types.OtelHTTP{} + c.HTTP.SetDefaults() } // Setup sets up the tracer. -func (c *Config) Setup(serviceName string, sampleRate float64, globalAttributes map[string]string, headers map[string]string) (trace.Tracer, io.Closer, error) { +func (c *Config) Setup(serviceName string, sampleRate float64, globalAttributes map[string]string) (trace.Tracer, io.Closer, error) { var ( err error exporter *otlptrace.Exporter ) if c.GRPC != nil { - exporter, err = c.setupGRPCExporter(headers) + exporter, err = c.setupGRPCExporter() } else { - exporter, err = c.setupHTTPExporter(headers) + exporter, err = c.setupHTTPExporter() } if err != nil { return nil, nil, fmt.Errorf("setting up exporter: %w", err) @@ -107,7 +89,7 @@ func (c *Config) Setup(serviceName string, sampleRate float64, globalAttributes return tracerProvider.Tracer("github.com/traefik/traefik"), &tpCloser{provider: tracerProvider}, err } -func (c *Config) setupHTTPExporter(headers map[string]string) (*otlptrace.Exporter, error) { +func (c *Config) setupHTTPExporter() (*otlptrace.Exporter, error) { endpoint, err := url.Parse(c.HTTP.Endpoint) if err != nil { return nil, fmt.Errorf("invalid collector endpoint %q: %w", c.HTTP.Endpoint, err) @@ -115,7 +97,7 @@ func (c *Config) setupHTTPExporter(headers map[string]string) (*otlptrace.Export opts := []otlptracehttp.Option{ otlptracehttp.WithEndpoint(endpoint.Host), - otlptracehttp.WithHeaders(headers), + otlptracehttp.WithHeaders(c.HTTP.Headers), otlptracehttp.WithCompression(otlptracehttp.GzipCompression), } @@ -139,15 +121,15 @@ func (c *Config) setupHTTPExporter(headers map[string]string) (*otlptrace.Export return otlptrace.New(context.Background(), otlptracehttp.NewClient(opts...)) } -func (c *Config) setupGRPCExporter(headers map[string]string) (*otlptrace.Exporter, error) { +func (c *Config) setupGRPCExporter() (*otlptrace.Exporter, error) { host, port, err := net.SplitHostPort(c.GRPC.Endpoint) if err != nil { - return nil, fmt.Errorf("invalid collector address %q: %w", c.GRPC.Endpoint, err) + return nil, fmt.Errorf("invalid collector endpoint %q: %w", c.GRPC.Endpoint, err) } opts := []otlptracegrpc.Option{ otlptracegrpc.WithEndpoint(fmt.Sprintf("%s:%s", host, port)), - otlptracegrpc.WithHeaders(headers), + otlptracegrpc.WithHeaders(c.GRPC.Headers), otlptracegrpc.WithCompressor(gzip.Name), } diff --git a/pkg/tracing/opentelemetry/opentelemetry_test.go b/pkg/tracing/opentelemetry/opentelemetry_test.go index ad7f13a4d..3df441c02 100644 --- a/pkg/tracing/opentelemetry/opentelemetry_test.go +++ b/pkg/tracing/opentelemetry/opentelemetry_test.go @@ -17,6 +17,7 @@ import ( tracingMiddle "github.com/traefik/traefik/v3/pkg/middlewares/tracing" "github.com/traefik/traefik/v3/pkg/tracing" "github.com/traefik/traefik/v3/pkg/tracing/opentelemetry" + "github.com/traefik/traefik/v3/pkg/types" "go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp" ) @@ -74,7 +75,7 @@ func TestTracing(t *testing.T) { ServiceName: "traefik", SampleRate: 1.0, OTLP: &opentelemetry.Config{ - HTTP: &opentelemetry.HTTP{ + HTTP: &types.OtelHTTP{ Endpoint: collector.URL, }, }, diff --git a/pkg/tracing/tracing.go b/pkg/tracing/tracing.go index 6bb382920..4a64ab1b3 100644 --- a/pkg/tracing/tracing.go +++ b/pkg/tracing/tracing.go @@ -20,7 +20,7 @@ import ( // Backend is an abstraction for tracking backend (OpenTelemetry, ...). type Backend interface { - Setup(serviceName string, sampleRate float64, globalAttributes map[string]string, headers map[string]string) (trace.Tracer, io.Closer, error) + Setup(serviceName string, sampleRate float64, globalAttributes map[string]string) (trace.Tracer, io.Closer, error) } // NewTracing Creates a Tracing. @@ -37,11 +37,16 @@ func NewTracing(conf *static.Tracing) (trace.Tracer, io.Closer, error) { backend = defaultBackend } - return backend.Setup(conf.ServiceName, conf.SampleRate, conf.GlobalAttributes, conf.Headers) + return backend.Setup(conf.ServiceName, conf.SampleRate, conf.GlobalAttributes) } // TracerFromContext extracts the trace.Tracer from the given context. func TracerFromContext(ctx context.Context) trace.Tracer { + // Prevent picking trace.noopSpan tracer. + if !trace.SpanContextFromContext(ctx).IsValid() { + return nil + } + span := trace.SpanFromContext(ctx) if span != nil && span.TracerProvider() != nil { return span.TracerProvider().Tracer("github.com/traefik/traefik") diff --git a/pkg/types/logs.go b/pkg/types/logs.go index 657e56102..e0a50ec71 100644 --- a/pkg/types/logs.go +++ b/pkg/types/logs.go @@ -45,6 +45,7 @@ type AccessLog struct { Filters *AccessLogFilters `description:"Access log filters, used to keep only specific access logs." json:"filters,omitempty" toml:"filters,omitempty" yaml:"filters,omitempty" export:"true"` Fields *AccessLogFields `description:"AccessLogFields." json:"fields,omitempty" toml:"fields,omitempty" yaml:"fields,omitempty" export:"true"` BufferingSize int64 `description:"Number of access log lines to process in a buffered way." json:"bufferingSize,omitempty" toml:"bufferingSize,omitempty" yaml:"bufferingSize,omitempty" export:"true"` + AddInternals bool `description:"Enables access log for internal services (ping, dashboard, etc...)." json:"addInternals,omitempty" toml:"addInternals,omitempty" yaml:"addInternals,omitempty" export:"true"` } // SetDefaults sets the default values. diff --git a/pkg/types/metrics.go b/pkg/types/metrics.go index 665333630..28b151a08 100644 --- a/pkg/types/metrics.go +++ b/pkg/types/metrics.go @@ -10,11 +10,13 @@ import ( // Metrics provides options to expose and send Traefik metrics to different third party monitoring systems. type Metrics struct { - Prometheus *Prometheus `description:"Prometheus metrics exporter type." json:"prometheus,omitempty" toml:"prometheus,omitempty" yaml:"prometheus,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - Datadog *Datadog `description:"Datadog metrics exporter type." json:"datadog,omitempty" toml:"datadog,omitempty" yaml:"datadog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - StatsD *Statsd `description:"StatsD metrics exporter type." json:"statsD,omitempty" toml:"statsD,omitempty" yaml:"statsD,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - InfluxDB2 *InfluxDB2 `description:"InfluxDB v2 metrics exporter type." json:"influxDB2,omitempty" toml:"influxDB2,omitempty" yaml:"influxDB2,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - OpenTelemetry *OpenTelemetry `description:"OpenTelemetry metrics exporter type." json:"openTelemetry,omitempty" toml:"openTelemetry,omitempty" yaml:"openTelemetry,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + AddInternals bool `description:"Enables metrics for internal services (ping, dashboard, etc...)." json:"addInternals,omitempty" toml:"addInternals,omitempty" yaml:"addInternals,omitempty" export:"true"` + + Prometheus *Prometheus `description:"Prometheus metrics exporter type." json:"prometheus,omitempty" toml:"prometheus,omitempty" yaml:"prometheus,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + Datadog *Datadog `description:"Datadog metrics exporter type." json:"datadog,omitempty" toml:"datadog,omitempty" yaml:"datadog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + StatsD *Statsd `description:"StatsD metrics exporter type." json:"statsD,omitempty" toml:"statsD,omitempty" yaml:"statsD,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + InfluxDB2 *InfluxDB2 `description:"InfluxDB v2 metrics exporter type." json:"influxDB2,omitempty" toml:"influxDB2,omitempty" yaml:"influxDB2,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + OTLP *OTLP `description:"OpenTelemetry metrics exporter type." json:"otlp,omitempty" toml:"otlp,omitempty" yaml:"otlp,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` } // Prometheus can contain specific configuration used by the Prometheus Metrics exporter. @@ -104,26 +106,23 @@ func (i *InfluxDB2) SetDefaults() { i.AddServicesLabels = true } -// OpenTelemetry contains specific configuration used by the OpenTelemetry Metrics exporter. -type OpenTelemetry struct { - // NOTE: as no gRPC option is implemented yet, the type is empty and is used as a boolean for upward compatibility purposes. - GRPC *struct{} `description:"gRPC specific configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` +// OTLP contains specific configuration used by the OpenTelemetry Metrics exporter. +type OTLP struct { + GRPC *OtelGRPC `description:"gRPC configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" export:"true"` + HTTP *OtelHTTP `description:"HTTP configuration for the OpenTelemetry collector." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true"` - Address string `description:"Address (host:port) of the collector endpoint." json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"` - AddEntryPointsLabels bool `description:"Enable metrics on entry points." json:"addEntryPointsLabels,omitempty" toml:"addEntryPointsLabels,omitempty" yaml:"addEntryPointsLabels,omitempty" export:"true"` - AddRoutersLabels bool `description:"Enable metrics on routers." json:"addRoutersLabels,omitempty" toml:"addRoutersLabels,omitempty" yaml:"addRoutersLabels,omitempty" export:"true"` - AddServicesLabels bool `description:"Enable metrics on services." json:"addServicesLabels,omitempty" toml:"addServicesLabels,omitempty" yaml:"addServicesLabels,omitempty" export:"true"` - ExplicitBoundaries []float64 `description:"Boundaries for latency metrics." json:"explicitBoundaries,omitempty" toml:"explicitBoundaries,omitempty" yaml:"explicitBoundaries,omitempty" export:"true"` - Headers map[string]string `description:"Headers sent with payload." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty" export:"true"` - Insecure bool `description:"Disables client transport security for the exporter." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"` - Path string `description:"Set the URL path of the collector endpoint." json:"path,omitempty" toml:"path,omitempty" yaml:"path,omitempty" export:"true"` - PushInterval types.Duration `description:"Period between calls to collect a checkpoint." json:"pushInterval,omitempty" toml:"pushInterval,omitempty" yaml:"pushInterval,omitempty" export:"true"` - TLS *ClientTLS `description:"Enable TLS support." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` + AddEntryPointsLabels bool `description:"Enable metrics on entry points." json:"addEntryPointsLabels,omitempty" toml:"addEntryPointsLabels,omitempty" yaml:"addEntryPointsLabels,omitempty" export:"true"` + AddRoutersLabels bool `description:"Enable metrics on routers." json:"addRoutersLabels,omitempty" toml:"addRoutersLabels,omitempty" yaml:"addRoutersLabels,omitempty" export:"true"` + AddServicesLabels bool `description:"Enable metrics on services." json:"addServicesLabels,omitempty" toml:"addServicesLabels,omitempty" yaml:"addServicesLabels,omitempty" export:"true"` + ExplicitBoundaries []float64 `description:"Boundaries for latency metrics." json:"explicitBoundaries,omitempty" toml:"explicitBoundaries,omitempty" yaml:"explicitBoundaries,omitempty" export:"true"` + PushInterval types.Duration `description:"Period between calls to collect a checkpoint." json:"pushInterval,omitempty" toml:"pushInterval,omitempty" yaml:"pushInterval,omitempty" export:"true"` } // SetDefaults sets the default values. -func (o *OpenTelemetry) SetDefaults() { - o.Address = "localhost:4318" +func (o *OTLP) SetDefaults() { + o.HTTP = &OtelHTTP{} + o.HTTP.SetDefaults() + o.AddEntryPointsLabels = true o.AddServicesLabels = true o.ExplicitBoundaries = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10} @@ -139,3 +138,28 @@ type Statistics struct { func (s *Statistics) SetDefaults() { s.RecentErrors = 10 } + +// OtelGRPC provides configuration settings for the gRPC open-telemetry. +type OtelGRPC struct { + Endpoint string `description:"Sets the gRPC endpoint (host:port) of the collector." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` + Insecure bool `description:"Disables client transport security for the exporter." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"` + TLS *ClientTLS `description:"Defines client transport security parameters." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` + Headers map[string]string `description:"Headers sent with payload." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"` +} + +// SetDefaults sets the default values. +func (c *OtelGRPC) SetDefaults() { + c.Endpoint = "localhost:4317" +} + +// OtelHTTP provides configuration settings for the HTTP open-telemetry. +type OtelHTTP struct { + Endpoint string `description:"Sets the HTTP endpoint (scheme://host:port/path) of the collector." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` + TLS *ClientTLS `description:"Defines client transport security parameters." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` + Headers map[string]string `description:"Headers sent with payload." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"` +} + +// SetDefaults sets the default values. +func (c *OtelHTTP) SetDefaults() { + c.Endpoint = "https://localhost:4318" +} diff --git a/pkg/udp/conn.go b/pkg/udp/conn.go index 5753a9c13..6b6054b69 100644 --- a/pkg/udp/conn.go +++ b/pkg/udp/conn.go @@ -1,7 +1,9 @@ package udp import ( + "context" "errors" + "fmt" "io" "net" "sync" @@ -33,18 +35,22 @@ type Listener struct { } // Listen creates a new listener. -func Listen(network string, laddr *net.UDPAddr, timeout time.Duration) (*Listener, error) { +func Listen(listenConfig net.ListenConfig, network, address string, timeout time.Duration) (*Listener, error) { if timeout <= 0 { return nil, errors.New("timeout should be greater than zero") } - conn, err := net.ListenUDP(network, laddr) + packetConn, err := listenConfig.ListenPacket(context.Background(), network, address) if err != nil { - return nil, err + return nil, fmt.Errorf("listen packet: %w", err) + } + pConn, ok := packetConn.(*net.UDPConn) + if !ok { + return nil, errors.New("packet conn is not an UDPConn") } l := &Listener{ - pConn: conn, + pConn: pConn, acceptCh: make(chan *Conn), conns: make(map[string]*Conn), accepting: true, diff --git a/pkg/udp/conn_test.go b/pkg/udp/conn_test.go index 44e748474..3c8cc0a7f 100644 --- a/pkg/udp/conn_test.go +++ b/pkg/udp/conn_test.go @@ -14,10 +14,7 @@ import ( ) func TestConsecutiveWrites(t *testing.T) { - addr, err := net.ResolveUDPAddr("udp", ":0") - require.NoError(t, err) - - ln, err := Listen("udp", addr, 3*time.Second) + ln, err := Listen(net.ListenConfig{}, "udp", ":0", 3*time.Second) require.NoError(t, err) defer func() { err := ln.Close() @@ -75,11 +72,7 @@ func TestConsecutiveWrites(t *testing.T) { } func TestListenNotBlocking(t *testing.T) { - addr, err := net.ResolveUDPAddr("udp", ":0") - - require.NoError(t, err) - - ln, err := Listen("udp", addr, 3*time.Second) + ln, err := Listen(net.ListenConfig{}, "udp", ":0", 3*time.Second) require.NoError(t, err) defer func() { err := ln.Close() @@ -165,10 +158,7 @@ func TestListenNotBlocking(t *testing.T) { } func TestListenWithZeroTimeout(t *testing.T) { - addr, err := net.ResolveUDPAddr("udp", ":0") - require.NoError(t, err) - - _, err = Listen("udp", addr, 0) + _, err := Listen(net.ListenConfig{}, "udp", ":0", 0) assert.Error(t, err) } @@ -183,10 +173,7 @@ func TestTimeoutWithoutRead(t *testing.T) { func testTimeout(t *testing.T, withRead bool) { t.Helper() - addr, err := net.ResolveUDPAddr("udp", ":0") - require.NoError(t, err) - - ln, err := Listen("udp", addr, 3*time.Second) + ln, err := Listen(net.ListenConfig{}, "udp", ":0", 3*time.Second) require.NoError(t, err) defer func() { err := ln.Close() @@ -227,10 +214,7 @@ func testTimeout(t *testing.T, withRead bool) { } func TestShutdown(t *testing.T) { - addr, err := net.ResolveUDPAddr("udp", ":0") - require.NoError(t, err) - - l, err := Listen("udp", addr, 3*time.Second) + l, err := Listen(net.ListenConfig{}, "udp", ":0", 3*time.Second) require.NoError(t, err) go func() { @@ -331,10 +315,7 @@ func TestReadLoopMaxDataSize(t *testing.T) { doneCh := make(chan struct{}) - addr, err := net.ResolveUDPAddr("udp", ":0") - require.NoError(t, err) - - l, err := Listen("udp", addr, 3*time.Second) + l, err := Listen(net.ListenConfig{}, "udp", ":0", 3*time.Second) require.NoError(t, err) defer func() { diff --git a/pkg/udp/proxy_test.go b/pkg/udp/proxy_test.go index b3ce2ec2c..3b5703875 100644 --- a/pkg/udp/proxy_test.go +++ b/pkg/udp/proxy_test.go @@ -96,10 +96,7 @@ func TestProxy_ServeUDP_MaxDataSize(t *testing.T) { func newServer(t *testing.T, addr string, handler Handler) { t.Helper() - addrL, err := net.ResolveUDPAddr("udp", addr) - require.NoError(t, err) - - listener, err := Listen("udp", addrL, 3*time.Second) + listener, err := Listen(net.ListenConfig{}, "udp", addr, 3*time.Second) require.NoError(t, err) for { diff --git a/pkg/udp/wrr_load_balancer.go b/pkg/udp/wrr_load_balancer.go index 03fe059e1..8ea6357c8 100644 --- a/pkg/udp/wrr_load_balancer.go +++ b/pkg/udp/wrr_load_balancer.go @@ -1,7 +1,7 @@ package udp import ( - "fmt" + "errors" "sync" "github.com/rs/zerolog/log" @@ -91,7 +91,7 @@ func gcd(a, b int) int { func (b *WRRLoadBalancer) next() (Handler, error) { if len(b.servers) == 0 { - return nil, fmt.Errorf("no servers in the pool") + return nil, errors.New("no servers in the pool") } // The algorithm below may look messy, @@ -101,7 +101,7 @@ func (b *WRRLoadBalancer) next() (Handler, error) { // Maximum weight across all enabled servers max := b.maxWeight() if max == 0 { - return nil, fmt.Errorf("all servers have 0 weight") + return nil, errors.New("all servers have 0 weight") } // GCD across all enabled servers diff --git a/script/code-gen-docker.sh b/script/code-gen-docker.sh index 9b1065507..fe425a6be 100755 --- a/script/code-gen-docker.sh +++ b/script/code-gen-docker.sh @@ -9,7 +9,7 @@ IMAGE_NAME="kubernetes-codegen:latest" CURRENT_DIR="$(pwd)" echo "Building codegen Docker image..." -docker build --build-arg KUBE_VERSION=v0.28.3 \ +docker build --build-arg KUBE_VERSION=v0.29.1 \ --build-arg USER="${USER}" \ --build-arg UID="$(id -u)" \ --build-arg GID="$(id -g)" \ diff --git a/script/codegen.Dockerfile b/script/codegen.Dockerfile index ffb5430eb..315d349c1 100644 --- a/script/codegen.Dockerfile +++ b/script/codegen.Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.21 +FROM golang:1.22 ARG USER=$USER ARG UID=$UID @@ -13,7 +13,7 @@ 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.13.0 +RUN go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.14.0 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/ diff --git a/script/release-packages.sh b/script/release-packages.sh index f442e12dd..5f67c3c4c 100755 --- a/script/release-packages.sh +++ b/script/release-packages.sh @@ -11,7 +11,7 @@ fi rm -rf dist for os in linux darwin windows freebsd openbsd; do - goreleaser release --snapshot --skip=publish -p 2 --timeout="90m" --config "$(go run ./internal/release "$os")" + goreleaser release --skip=publish -p 2 --timeout="90m" --config "$(go run ./internal/release "$os")" go clean -cache done diff --git a/webui/.nvmrc b/webui/.nvmrc index 8ba691606..8b0beab16 100644 --- a/webui/.nvmrc +++ b/webui/.nvmrc @@ -1 +1 @@ -12.11.1 +20.11.0 diff --git a/webui/Dockerfile b/webui/Dockerfile index edb9593bc..3939d56b0 100644 --- a/webui/Dockerfile +++ b/webui/Dockerfile @@ -1,4 +1,4 @@ -FROM node:14.16 +FROM node:20.11 # Current Active LTS release according to (https://nodejs.org/en/about/releases/) ENV WEBUI_DIR /src/webui diff --git a/webui/package.json b/webui/package.json index 23e2be3d4..f114f2ce8 100644 --- a/webui/package.json +++ b/webui/package.json @@ -16,22 +16,23 @@ "build:nc": "yarn build" }, "dependencies": { - "@quasar/extras": "^1.0.0", - "axios": "^0.19.0", + "@quasar/extras": "^1.11.2", + "axios": "^0.21.1", "bowser": "^2.5.2", "chart.js": "^2.8.0", "dot-prop": "^5.2.0", + "core-js": "^3.35.1", "iframe-resizer": "^4.2.11", "lodash.isequal": "4.5.0", "moment": "^2.24.0", - "quasar": "^1.4.4", + "quasar": "^1.22.10", "query-string": "^6.13.1", "vh-check": "^2.0.5", "vue-chartjs": "^3.4.2", "vuex-map-fields": "^1.3.4" }, "devDependencies": { - "@quasar/app": "^1.2.4", + "@quasar/app": "^2.4.3", "@vue/eslint-config-standard": "^4.0.0", "@vue/test-utils": "^1.0.0-beta.29", "babel-eslint": "^10.0.1", @@ -43,9 +44,7 @@ "eslint-plugin-vue": "^5.0.0", "mocha": "^6.2.2", "mocha-webpack": "^2.0.0-beta.0", - "node-sass": "^4.12.0", - "prettier": "1.19.1", - "sass-loader": "^7.1.0" + "prettier": "1.19.1" }, "engines": { "node": ">= 8.9.0", diff --git a/webui/src/statics/app-logo-128x128.png b/webui/public/app-logo-128x128.png similarity index 100% rename from webui/src/statics/app-logo-128x128.png rename to webui/public/app-logo-128x128.png diff --git a/webui/src/statics/icons/apple-icon-120x120.png b/webui/public/icons/apple-icon-120x120.png similarity index 100% rename from webui/src/statics/icons/apple-icon-120x120.png rename to webui/public/icons/apple-icon-120x120.png diff --git a/webui/src/statics/icons/apple-icon-152x152.png b/webui/public/icons/apple-icon-152x152.png similarity index 100% rename from webui/src/statics/icons/apple-icon-152x152.png rename to webui/public/icons/apple-icon-152x152.png diff --git a/webui/src/statics/icons/apple-icon-167x167.png b/webui/public/icons/apple-icon-167x167.png similarity index 100% rename from webui/src/statics/icons/apple-icon-167x167.png rename to webui/public/icons/apple-icon-167x167.png diff --git a/webui/src/statics/icons/apple-icon-180x180.png b/webui/public/icons/apple-icon-180x180.png similarity index 100% rename from webui/src/statics/icons/apple-icon-180x180.png rename to webui/public/icons/apple-icon-180x180.png diff --git a/webui/src/statics/icons/favicon-16x16.png b/webui/public/icons/favicon-16x16.png similarity index 100% rename from webui/src/statics/icons/favicon-16x16.png rename to webui/public/icons/favicon-16x16.png diff --git a/webui/src/statics/icons/favicon-32x32.png b/webui/public/icons/favicon-32x32.png similarity index 100% rename from webui/src/statics/icons/favicon-32x32.png rename to webui/public/icons/favicon-32x32.png diff --git a/webui/src/statics/icons/favicon-96x96.png b/webui/public/icons/favicon-96x96.png similarity index 100% rename from webui/src/statics/icons/favicon-96x96.png rename to webui/public/icons/favicon-96x96.png diff --git a/webui/src/statics/icons/favicon.ico b/webui/public/icons/favicon.ico similarity index 100% rename from webui/src/statics/icons/favicon.ico rename to webui/public/icons/favicon.ico diff --git a/webui/src/statics/icons/icon-128x128.png b/webui/public/icons/icon-128x128.png similarity index 100% rename from webui/src/statics/icons/icon-128x128.png rename to webui/public/icons/icon-128x128.png diff --git a/webui/src/statics/icons/icon-192x192.png b/webui/public/icons/icon-192x192.png similarity index 100% rename from webui/src/statics/icons/icon-192x192.png rename to webui/public/icons/icon-192x192.png diff --git a/webui/src/statics/icons/icon-256x256.png b/webui/public/icons/icon-256x256.png similarity index 100% rename from webui/src/statics/icons/icon-256x256.png rename to webui/public/icons/icon-256x256.png diff --git a/webui/src/statics/icons/icon-384x384.png b/webui/public/icons/icon-384x384.png similarity index 100% rename from webui/src/statics/icons/icon-384x384.png rename to webui/public/icons/icon-384x384.png diff --git a/webui/src/statics/icons/icon-512x512.png b/webui/public/icons/icon-512x512.png similarity index 100% rename from webui/src/statics/icons/icon-512x512.png rename to webui/public/icons/icon-512x512.png diff --git a/webui/src/statics/icons/ms-icon-144x144.png b/webui/public/icons/ms-icon-144x144.png similarity index 100% rename from webui/src/statics/icons/ms-icon-144x144.png rename to webui/public/icons/ms-icon-144x144.png diff --git a/webui/src/statics/icons/safari-pinned-tab.svg b/webui/public/icons/safari-pinned-tab.svg similarity index 100% rename from webui/src/statics/icons/safari-pinned-tab.svg rename to webui/public/icons/safari-pinned-tab.svg diff --git a/webui/src/statics/providers/consul.svg b/webui/public/providers/consul.svg similarity index 100% rename from webui/src/statics/providers/consul.svg rename to webui/public/providers/consul.svg diff --git a/webui/src/statics/providers/consulcatalog.svg b/webui/public/providers/consulcatalog.svg similarity index 100% rename from webui/src/statics/providers/consulcatalog.svg rename to webui/public/providers/consulcatalog.svg diff --git a/webui/src/statics/providers/docker.svg b/webui/public/providers/docker.svg similarity index 100% rename from webui/src/statics/providers/docker.svg rename to webui/public/providers/docker.svg diff --git a/webui/src/statics/providers/ecs.svg b/webui/public/providers/ecs.svg similarity index 100% rename from webui/src/statics/providers/ecs.svg rename to webui/public/providers/ecs.svg diff --git a/webui/src/statics/providers/etcd.svg b/webui/public/providers/etcd.svg similarity index 100% rename from webui/src/statics/providers/etcd.svg rename to webui/public/providers/etcd.svg diff --git a/webui/src/statics/providers/file.svg b/webui/public/providers/file.svg similarity index 100% rename from webui/src/statics/providers/file.svg rename to webui/public/providers/file.svg diff --git a/webui/src/statics/providers/http.svg b/webui/public/providers/http.svg similarity index 100% rename from webui/src/statics/providers/http.svg rename to webui/public/providers/http.svg diff --git a/webui/src/statics/providers/hub.svg b/webui/public/providers/hub.svg similarity index 100% rename from webui/src/statics/providers/hub.svg rename to webui/public/providers/hub.svg diff --git a/webui/src/statics/providers/internal.svg b/webui/public/providers/internal.svg similarity index 100% rename from webui/src/statics/providers/internal.svg rename to webui/public/providers/internal.svg diff --git a/webui/src/statics/providers/kubernetes.svg b/webui/public/providers/kubernetes.svg similarity index 100% rename from webui/src/statics/providers/kubernetes.svg rename to webui/public/providers/kubernetes.svg diff --git a/webui/src/statics/providers/kubernetescrd.svg b/webui/public/providers/kubernetescrd.svg similarity index 100% rename from webui/src/statics/providers/kubernetescrd.svg rename to webui/public/providers/kubernetescrd.svg diff --git a/webui/src/statics/providers/kubernetesgateway.svg b/webui/public/providers/kubernetesgateway.svg similarity index 100% rename from webui/src/statics/providers/kubernetesgateway.svg rename to webui/public/providers/kubernetesgateway.svg diff --git a/webui/src/statics/providers/kubernetesingress.svg b/webui/public/providers/kubernetesingress.svg similarity index 100% rename from webui/src/statics/providers/kubernetesingress.svg rename to webui/public/providers/kubernetesingress.svg diff --git a/webui/src/statics/providers/marathon.svg b/webui/public/providers/marathon.svg similarity index 100% rename from webui/src/statics/providers/marathon.svg rename to webui/public/providers/marathon.svg diff --git a/webui/src/statics/providers/nomad.svg b/webui/public/providers/nomad.svg similarity index 100% rename from webui/src/statics/providers/nomad.svg rename to webui/public/providers/nomad.svg diff --git a/webui/src/statics/providers/plugin.svg b/webui/public/providers/plugin.svg similarity index 100% rename from webui/src/statics/providers/plugin.svg rename to webui/public/providers/plugin.svg diff --git a/webui/src/statics/providers/rancher.svg b/webui/public/providers/rancher.svg similarity index 100% rename from webui/src/statics/providers/rancher.svg rename to webui/public/providers/rancher.svg diff --git a/webui/src/statics/providers/redis.svg b/webui/public/providers/redis.svg similarity index 100% rename from webui/src/statics/providers/redis.svg rename to webui/public/providers/redis.svg diff --git a/webui/src/statics/providers/rest.svg b/webui/public/providers/rest.svg similarity index 100% rename from webui/src/statics/providers/rest.svg rename to webui/public/providers/rest.svg diff --git a/webui/src/statics/providers/zookeeper.svg b/webui/public/providers/zookeeper.svg similarity index 100% rename from webui/src/statics/providers/zookeeper.svg rename to webui/public/providers/zookeeper.svg diff --git a/webui/public/traefiklabs-hub-button-app/main-v1.js b/webui/public/traefiklabs-hub-button-app/main-v1.js new file mode 100644 index 000000000..9d36a8b62 --- /dev/null +++ b/webui/public/traefiklabs-hub-button-app/main-v1.js @@ -0,0 +1,3 @@ +/* eslint-disable */ +!function(){var e={110:function(e,t,n){"use strict";var r=n(441),a={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},l={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},o={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},i={};function u(e){return r.isMemo(e)?o:i[e.$$typeof]||a}i[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},i[r.Memo]=o;var s=Object.defineProperty,c=Object.getOwnPropertyNames,f=Object.getOwnPropertySymbols,d=Object.getOwnPropertyDescriptor,p=Object.getPrototypeOf,h=Object.prototype;e.exports=function e(t,n,r){if("string"!==typeof n){if(h){var a=p(n);a&&a!==h&&e(t,a,r)}var o=c(n);f&&(o=o.concat(f(n)));for(var i=u(t),m=u(n),g=0;g