1
0
mirror of https://github.com/containous/traefik.git synced 2025-10-07 15:33:25 +03:00

Compare commits

..

48 Commits

Author SHA1 Message Date
Kevin Pollet
07e6491ace Prepare release v3.3.4 2025-02-25 11:04:04 +01:00
kevinpollet
32ea014d07 Merge branch v2.11 into v3.3 2025-02-25 10:06:03 +01:00
Kevin Pollet
a3fd484728 Prepare release v2.11.21 2025-02-24 15:32:06 +01:00
Sheddy
9b0348577a Update ACME provider configuration options 2025-02-24 15:26:06 +01:00
Peter Maguire
efe03bc9da Fix incorrect grammar in ACME documentation 2025-02-24 10:42:06 +01:00
Bastien Gysler
cce935493a Fix panic when calling Tracer 2025-02-24 10:26:39 +01:00
Kevin Pollet
f196de90e1 Enable the retry middleware in the proxy
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
2025-02-21 11:36:05 +01:00
Kevin Pollet
c2a294c872 Retry should send headers on Write
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
2025-02-21 10:52:04 +01:00
Ludovic Fernandez
8e5d4c6ae9 Bum github.com/go-acme/lego/v4 to v4.22.2 2025-02-21 09:36:04 +01:00
Kevin Pollet
1ccbf743cb Add WebSocket headers if they are present in the request
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
2025-02-17 20:20:05 +01:00
Kevin Pollet
1cfcf0d318 Chunked responses does not have a Content-Length header
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
2025-02-14 17:44:04 +01:00
Kevin Pollet
eb07a5ca1a Bump github.com/traefik/paerser to v0.2.2
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
2025-02-14 11:24:04 +01:00
Romain
56ea028e81 Change request duration metric unit from millisecond to second
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
2025-02-14 11:22:04 +01:00
Julien Salleyron
05c547f211 Fix double hash in sticky cookie 2025-02-13 16:42:08 +01:00
Romain
dcd9f2ea96 Replace globalAttributes with resourceAttributes in tracing reference 2025-02-13 14:58:05 +01:00
Ludovic Fernandez
84e20aa9c3 chore: update linter 2025-02-12 10:02:04 +01:00
Kevin Pollet
b5a5e259ed Bump github.com/valyala/fasthttp to v1.58.0 2025-02-11 14:26:04 +01:00
Sheddy
8488214e93 Add missing options in entrypoints page 2025-02-10 15:20:04 +01:00
Bruno de Queiroz
b74767bfa4 Use ResourceAttributes instead of GlobalAttributes 2025-02-06 11:24:04 +01:00
Romain
da2278b29a Prepare release v3.3.3 2025-01-31 15:46:04 +01:00
romain
cfebed7328 Merge branch v2.11 into v3.3 2025-01-31 15:20:12 +01:00
Romain
4e441d09ed Prepare release v2.11.20 2025-01-31 15:16:04 +01:00
khai-pi
8f5dd7bd9d Change docker-compose to docker compose 2025-01-31 14:30:05 +01:00
Harold Ozouf
d04e2d717c Add missing headerField in Middleware CRD 2025-01-31 14:28:06 +01:00
Kevin Pollet
cdd24e91b4 Fix content-length header assertion
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
2025-01-31 12:00:05 +01:00
romain
4fd6b10b7d Merge branch v2.11 into v3.3 2025-01-31 11:14:59 +01:00
Julien Salleyron
86315e0f18 Fix ACME write when traefik is shutting down 2025-01-31 11:06:04 +01:00
Kevin Pollet
c20af070e3 Set check-latest to true in Go setup 2025-01-30 14:06:04 +01:00
Kevin Pollet
8593581cbf Fix integration tests for HTTPS 2025-01-29 17:04:05 +01:00
Romain
857fbb933e Do not create observability model by default
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
2025-01-29 13:56:04 +01:00
Romain
8103992977 Prepare release v2.11.19 2025-01-29 11:36:08 +01:00
Kevin Pollet
c5b92b5260 Do not create a logger instance for each proxy 2025-01-27 11:24:04 +01:00
Romain
fb527dac1c Handle responses without content length header
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
2025-01-23 10:00:05 +01:00
DoubleREW
c19cf125e8 Fix auto refresh not clearing on component unmount 2025-01-21 14:58:04 +01:00
Nelson Isioma
435d28c790 changing log message when client cert is not available to debug 2025-01-17 09:42:04 +01:00
Romain
4ce4bd7121 Bring back TraceID and SpanID fields in access logs
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
2025-01-15 16:26:08 +01:00
Kevin Pollet
020ab5f347 Prepare release v3.3.2 2025-01-14 16:46:04 +01:00
Romain
ad7fb8e82b Fix observability configuration on EntryPoints 2025-01-14 16:28:05 +01:00
Kevin Pollet
0528c054a6 Do not read response body for HEAD requests
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
2025-01-14 15:16:05 +01:00
Kevin Pollet
ad99c5bbea Update Gateway API CRDs for integration tests 2025-01-14 10:36:04 +01:00
Kevin Pollet
8272be0eda Remove awesome.traefik.io reference in documentation section 2025-01-13 10:28:04 +01:00
thomscode
0a6ff446c7 Fix deprecated dnsChallenge propagation logging and documentation 2025-01-13 10:06:04 +01:00
Kevin Pollet
9a9644bafe Set content-type when serving webui index 2025-01-13 09:18:04 +01:00
kevinpollet
a57e118a1a Merge branch v2.11 into v3.3 2025-01-08 11:10:59 +01:00
Kevin Pollet
d2414feaff Add test to check that SettingEnableConnectProtocol frame is not sent 2025-01-08 11:02:37 +01:00
Jeff Spiers
6aa56788ea Add missing trailing s to propagation.delayBeforeCheck option 2025-01-08 09:36:04 +01:00
Kevin Pollet
1aa450c028 Prepare release v2.11.18 2025-01-07 16:24:04 +01:00
Romain
f9ff6049d3 Disable http2 connect setting for websocket by default
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
Co-authored-by: Julien Salleyron <julien.salleyron@gmail.com>
Co-authored-by: Michael <michael.matur@gmail.com>
2025-01-07 16:12:04 +01:00
92 changed files with 2567 additions and 4948 deletions

View File

@@ -61,6 +61,7 @@ jobs:
ImageOS: ${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.goarm }}
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
- name: Artifact webui
uses: actions/download-artifact@v4

View File

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

View File

@@ -41,6 +41,7 @@ jobs:
ImageOS: ${{ matrix.os }}
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
- name: Artifact webui
uses: actions/download-artifact@v4

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
run:
timeout: 10m
relative-path-mode: cfg
linters-settings:
govet:
@@ -165,6 +166,7 @@ linters-settings:
linters:
enable-all: true
disable:
- tenv # Deprecated
- sqlclosecheck # not relevant (SQL)
- rowserrcheck # not relevant (SQL)
- cyclop # duplicate of gocyclo
@@ -201,7 +203,6 @@ linters:
- maintidx # kind of duplicate of gocyclo
- nonamedreturns # Too strict
- gosmopolitan # not relevant
- exportloopref # Not relevant since go1.22
issues:
exclude-use-default: false

View File

@@ -1,3 +1,82 @@
## [v3.3.4](https://github.com/traefik/traefik/tree/v3.3.4) (2025-02-25)
[All Commits](https://github.com/traefik/traefik/compare/v3.3.3...v3.3.4)
**Bug fixes:**
- **[fastproxy]** Bump github.com/valyala/fasthttp to v1.58.0 ([#11526](https://github.com/traefik/traefik/pull/11526) by [kevinpollet](https://github.com/kevinpollet))
- **[fastproxy]** Add WebSocket headers if they are present in the request ([#11522](https://github.com/traefik/traefik/pull/11522) by [kevinpollet](https://github.com/kevinpollet))
- **[fastproxy]** Chunked responses does not have a Content-Length header ([#11514](https://github.com/traefik/traefik/pull/11514) by [kevinpollet](https://github.com/kevinpollet))
- **[metrics,otel]** Change request duration metric unit from millisecond to second ([#11523](https://github.com/traefik/traefik/pull/11523) by [rtribotte](https://github.com/rtribotte))
- **[sticky-session]** Fix double hash in sticky cookie ([#11518](https://github.com/traefik/traefik/pull/11518) by [juliens](https://github.com/juliens))
- **[tracing]** Use ResourceAttributes instead of GlobalAttributes ([#11515](https://github.com/traefik/traefik/pull/11515) by [bruno-de-queiroz](https://github.com/bruno-de-queiroz))
- **[tracing]** Fix panic when calling Tracer ([#11479](https://github.com/traefik/traefik/pull/11479) by [basgys](https://github.com/basgys))
**Documentation:**
- **[acme]** Update ACME provider configuration options ([#11564](https://github.com/traefik/traefik/pull/11564) by [sheddy-traefik](https://github.com/sheddy-traefik))
- **[acme]** Fix incorrect grammar in ACME documentation ([#11553](https://github.com/traefik/traefik/pull/11553) by [Peter-Maguire](https://github.com/Peter-Maguire))
- **[metrics,tracing,accesslogs]** Add missing options in entrypoints page ([#11524](https://github.com/traefik/traefik/pull/11524) by [sheddy-traefik](https://github.com/sheddy-traefik))
- **[tracing]** Replace globalAttributes with resourceAttributes in tracing reference ([#11531](https://github.com/traefik/traefik/pull/11531) by [rtribotte](https://github.com/rtribotte))
**Misc:**
- Merge branch v2.11 into v3.3 ([#11567](https://github.com/traefik/traefik/pull/11567) by [kevinpollet](https://github.com/kevinpollet))
# [v2.11.21](https://github.com/traefik/traefik/tree/v2.11.21) (2025-02-24)
[All Commits](https://github.com/traefik/traefik/compare/v2.11.20...v2.11.21)
**Bug fixes:**
- **[acme]** Bump github.com/go-acme/lego/v4 to v4.22.2 ([#11537](https://github.com/traefik/traefik/pull/11537) by [ldez](https://github.com/ldez))
- **[cli]** Bump github.com/traefik/paerser to v0.2.2 ([#11530](https://github.com/traefik/traefik/pull/11530) by [kevinpollet](https://github.com/kevinpollet))
- **[middleware]** Enable the retry middleware in the proxy ([#11536](https://github.com/traefik/traefik/pull/11536) by [kevinpollet](https://github.com/kevinpollet))
- **[middleware]** Retry should send headers on Write ([#11534](https://github.com/traefik/traefik/pull/11534) by [kevinpollet](https://github.com/kevinpollet))
## [v3.3.3](https://github.com/traefik/traefik/tree/v3.3.3) (2025-01-31)
[All Commits](https://github.com/traefik/traefik/compare/v3.3.2...v3.3.3)
**Bug fixes:**
- **[api]** Do not create observability model by default ([#11476](https://github.com/traefik/traefik/pull/11476) by [rtribotte](https://github.com/rtribotte))
- **[fastproxy]** Fix content-length header assertion ([#11498](https://github.com/traefik/traefik/pull/11498) by [kevinpollet](https://github.com/kevinpollet))
- **[fastproxy]** Handle responses without content length header ([#11458](https://github.com/traefik/traefik/pull/11458) by [rtribotte](https://github.com/rtribotte))
- **[k8s/crd,k8s]** Add missing headerField in Middleware CRD ([#11499](https://github.com/traefik/traefik/pull/11499) by [jspdown](https://github.com/jspdown))
- **[tracing,accesslogs]** Bring back TraceID and SpanID fields in access logs ([#11450](https://github.com/traefik/traefik/pull/11450) by [rtribotte](https://github.com/rtribotte))
**Misc:**
- Merge branch v2.11 into v3.3 ([#11502](https://github.com/traefik/traefik/pull/11502) by [rtribotte](https://github.com/rtribotte))
- Merge branch v2.11 into v3.3 ([#11491](https://github.com/traefik/traefik/pull/11491) by [rtribotte](https://github.com/rtribotte))
## [v2.11.20](https://github.com/traefik/traefik/tree/v2.11.20) (2025-01-31)
[All Commits](https://github.com/traefik/traefik/compare/v2.11.19...v2.11.20)
**Bug fixes:**
- **[acme]** Graceful shutdown for ACME JSON write operation ([#11497](https://github.com/traefik/traefik/pull/11497) by [juliens](https://github.com/juliens))
**Documentation:**
- Change docker-compose to docker compose ([#11496](https://github.com/traefik/traefik/pull/11496) by [khai-pi](https://github.com/khai-pi))
## [v2.11.19](https://github.com/traefik/traefik/tree/v2.11.19) (2025-01-29)
[All Commits](https://github.com/traefik/traefik/compare/v2.11.18...v2.11.19)
**Bug fixes:**
- **[middleware]** Changing log message when client cert is not available to debug ([#11453](https://github.com/traefik/traefik/pull/11453) by [Nelwhix](https://github.com/Nelwhix))
- **[service]** Do not create a logger instance for each proxy ([#11487](https://github.com/traefik/traefik/pull/11487) by [kevinpollet](https://github.com/kevinpollet))
- **[webui]** Fix auto refresh not clearing on component unmount ([#11477](https://github.com/traefik/traefik/pull/11477) by [DoubleREW](https://github.com/DoubleREW))
**Documentation:**
- Remove awesome.traefik.io reference in documentation section ([#11435](https://github.com/traefik/traefik/pull/11435) by [kevinpollet](https://github.com/kevinpollet))
## [v3.3.2](https://github.com/traefik/traefik/tree/v3.3.2) (2025-01-14)
[All Commits](https://github.com/traefik/traefik/compare/v3.3.1...v3.3.2)
**Bug fixes:**
- **[fastproxy]** Do not read response body for HEAD requests ([#11442](https://github.com/traefik/traefik/pull/11442) by [kevinpollet](https://github.com/kevinpollet))
- **[metrics,tracing,accesslogs]** Fix observability configuration on EntryPoints ([#11446](https://github.com/traefik/traefik/pull/11446) by [rtribotte](https://github.com/rtribotte))
- **[webui]** Set content-type when serving webui index ([#11428](https://github.com/traefik/traefik/pull/11428) by [kevinpollet](https://github.com/kevinpollet))
**Documentation:**
- **[acme]** Fix deprecated dnsChallenge propagation logging and documentation ([#11433](https://github.com/traefik/traefik/pull/11433) by [thomscode](https://github.com/thomscode))
- **[acme]** Add missing trailing s to propagation.delayBeforeCheck option ([#11417](https://github.com/traefik/traefik/pull/11417) by [jspiers](https://github.com/jspiers))
**Misc:**
- Merge branch v2.11 into v3.3 ([#11419](https://github.com/traefik/traefik/pull/11419) by [kevinpollet](https://github.com/kevinpollet))
## [v3.3.1](https://github.com/traefik/traefik/tree/v3.3.1) (2025-01-07)
[All Commits](https://github.com/traefik/traefik/compare/v3.3.0...v3.3.1)
@@ -10,6 +89,12 @@
**Bug fixes:**
- **[websocket,server]** Disable http2 connect setting for websocket by default ([#11408](https://github.com/traefik/traefik/pull/11408) by [rtribotte](https://github.com/rtribotte))
## [v2.11.18](https://github.com/traefik/traefik/tree/v2.11.18) (2025-01-07)
[All Commits](https://github.com/traefik/traefik/compare/v2.11.17...v2.11.18)
**Bug fixes:**
- **[websocket,server]** Disable http2 connect setting for websocket by default ([#11412](https://github.com/traefik/traefik/pull/11412) by [rtribotte](https://github.com/rtribotte))
## [v3.3.0](https://github.com/traefik/traefik/tree/v3.3.0) (2025-01-06)
[All Commits](https://github.com/traefik/traefik/compare/v3.2.0-rc1...v3.3.0)

View File

@@ -90,8 +90,6 @@ You can access the simple HTML frontend of Traefik.
You can find the complete documentation of Traefik v3 at [https://doc.traefik.io/traefik/](https://doc.traefik.io/traefik/).
A collection of contributions around Traefik can be found at [https://awesome.traefik.io](https://awesome.traefik.io).
## Support
To get community support, you can:

View File

@@ -193,7 +193,7 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
return nil, err
}
acmeProviders := initACMEProvider(staticConfiguration, providerAggregator, tlsManager, httpChallengeProvider, tlsChallengeProvider)
acmeProviders := initACMEProvider(staticConfiguration, providerAggregator, tlsManager, httpChallengeProvider, tlsChallengeProvider, routinesPool)
// Tailscale
@@ -445,7 +445,7 @@ func switchRouter(routerFactory *server.RouterFactory, serverEntryPointsTCP serv
}
// initACMEProvider creates and registers acme.Provider instances corresponding to the configured ACME certificate resolvers.
func initACMEProvider(c *static.Configuration, providerAggregator *aggregator.ProviderAggregator, tlsManager *traefiktls.Manager, httpChallengeProvider, tlsChallengeProvider challenge.Provider) []*acme.Provider {
func initACMEProvider(c *static.Configuration, providerAggregator *aggregator.ProviderAggregator, tlsManager *traefiktls.Manager, httpChallengeProvider, tlsChallengeProvider challenge.Provider, routinesPool *safe.Pool) []*acme.Provider {
localStores := map[string]*acme.LocalStore{}
var resolvers []*acme.Provider
@@ -455,7 +455,7 @@ func initACMEProvider(c *static.Configuration, providerAggregator *aggregator.Pr
}
if localStores[resolver.ACME.Storage] == nil {
localStores[resolver.ACME.Storage] = acme.NewLocalStore(resolver.ACME.Storage)
localStores[resolver.ACME.Storage] = acme.NewLocalStore(resolver.ACME.Storage, routinesPool)
}
p := &acme.Provider{

View File

@@ -6,7 +6,8 @@ Below is a non-exhaustive list of versions and their maintenance status:
| Version | Release Date | Community Support |
|---------|--------------|--------------------|
| 3.2 | Oct 28, 2024 | Yes |
| 3.3 | Jan 06, 2025 | Yes |
| 3.2 | Oct 28, 2024 | Ended Jan 06, 2025 |
| 3.1 | Jul 15, 2024 | Ended Oct 28, 2024 |
| 3.0 | Apr 29, 2024 | Ended Jul 15, 2024 |
| 2.11 | Feb 12, 2024 | Ends Apr 29, 2025 |

View File

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

View File

@@ -316,7 +316,7 @@ For complete details, refer to your provider's _Additional configuration_ link.
| Provider Name | Provider Code | Environment Variables | |
|------------------------------------------------------------------------|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------|
| [ACME DNS](https://github.com/joohoi/acme-dns) | `acme-dns` | `ACME_DNS_API_BASE`, `ACME_DNS_STORAGE_PATH` | [Additional configuration](https://go-acme.github.io/lego/dns/acme-dns) |
| [ACME DNS](https://github.com/joohoi/acme-dns) | `acme-dns` | `ACME_DNS_API_BASE`, `ACME_DNS_STORAGE_PATH`, `ACME_DNS_STORAGE_BASE_URL` | [Additional configuration](https://go-acme.github.io/lego/dns/acme-dns) |
| [Alibaba Cloud](https://www.alibabacloud.com) | `alidns` | `ALICLOUD_ACCESS_KEY`, `ALICLOUD_SECRET_KEY`, `ALICLOUD_REGION_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/alidns) |
| [all-inkl](https://all-inkl.com) | `allinkl` | `ALL_INKL_LOGIN`, `ALL_INKL_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/allinkl) |
| [ArvanCloud](https://www.arvancloud.ir/en) | `arvancloud` | `ARVANCLOUD_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/arvancloud) |
@@ -397,6 +397,7 @@ For complete details, refer to your provider's _Additional configuration_ link.
| [Metaname](https://metaname.net) | `metaname` | `METANAME_ACCOUNT_REFERENCE`, `METANAME_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/metaname) |
| [mijn.host](https://mijn.host/) | `mijnhost` | `MIJNHOST_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/mijnhost) |
| [Mittwald](https://www.mittwald.de) | `mittwald` | `MITTWALD_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/mittwald) |
| [myaddr.{tools,dev,io}](https://myaddr.tools/) | `myaddr` | `MYADDR_PRIVATE_KEYS_MAPPING` | [Additional configuration](https://go-acme.github.io/lego/dns/myaddr) |
| [MyDNS.jp](https://www.mydns.jp/) | `mydnsjp` | `MYDNSJP_MASTER_ID`, `MYDNSJP_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/mydnsjp) |
| [Mythic Beasts](https://www.mythic-beasts.com) | `mythicbeasts` | `MYTHICBEASTS_USER_NAME`, `MYTHICBEASTS_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/mythicbeasts) |
| [name.com](https://www.name.com/) | `namedotcom` | `NAMECOM_USERNAME`, `NAMECOM_API_TOKEN`, `NAMECOM_SERVER` | [Additional configuration](https://go-acme.github.io/lego/dns/namedotcom) |
@@ -434,6 +435,7 @@ For complete details, refer to your provider's _Additional configuration_ link.
| [Shellrent](https://www.shellrent.com) | `shellrent` | `SHELLRENT_USERNAME`, `SHELLRENT_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/shellrent) |
| [Simply.com](https://www.simply.com/en/domains/) | `simply` | `SIMPLY_ACCOUNT_NAME`, `SIMPLY_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/simply) |
| [Sonic](https://www.sonic.com/) | `sonic` | `SONIC_USER_ID`, `SONIC_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/sonic) |
| [Spaceship](https://spaceship.com) | `spaceship` | `SPACESHIP_API_KEY`, `SPACESHIP_API_SECRET` | [Additional configuration](https://go-acme.github.io/lego/dns/spaceship) |
| [Stackpath](https://www.stackpath.com/) | `stackpath` | `STACKPATH_CLIENT_ID`, `STACKPATH_CLIENT_SECRET`, `STACKPATH_STACK_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/stackpath) |
| [Technitium](https://technitium.com) | `technitium` | `TECHNITIUM_SERVER_BASE_URL`, `TECHNITIUM_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/technitium) |
| [Tencent Cloud DNS](https://cloud.tencent.com/product/cns) | `tencentcloud` | `TENCENTCLOUD_SECRET_ID`, `TENCENTCLOUD_SECRET_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/tencentcloud) |
@@ -503,7 +505,7 @@ certificatesResolvers:
By default, the `provider` verifies the TXT record _before_ letting ACME verify.
You can delay this operation by specifying a delay (in seconds) with `delayBeforeCheck` (value must be greater than zero).
You can delay this operation by specifying a delay (in seconds) with `delayBeforeChecks` (value must be greater than zero).
This option is useful when internal networks block external DNS queries.
@@ -538,7 +540,7 @@ certificatesResolvers:
Disables the challenge TXT record propagation checks, before notifying ACME that the DNS challenge is ready.
Please note that disabling checks can prevent the challenge to succeed.
Please note that disabling checks can prevent the challenge from succeeding.
```yaml tab="File (YAML)"
certificatesResolvers:

View File

@@ -173,10 +173,17 @@ please use the `traefik.swarm.network` and `traefik.swarm.lbswarm` labels instea
### ACME DNS Certificate Resolver
In `v3.3`, the `acme.dnsChallenge.delaybeforecheck` and `acme.dnsChallenge.disablepropagationcheck` options of the ACME certificate resolver are deprecated,
please use respectively `acme.dnsChallenge.propagation.delayBeforeCheck` and `acme.dnsChallenge.propagation.disableAllChecks` options instead.
please use respectively `acme.dnsChallenge.propagation.delayBeforeChecks` and `acme.dnsChallenge.propagation.disableChecks` options instead.
### Tracing Global Attributes
In `v3.3`, the `tracing.globalAttributes` option has been deprecated, please use the `tracing.resourceAttributes` option instead.
The `tracing.globalAttributes` option is misleading as its name does not reflect the operation of adding resource attributes to be sent to the collector,
and will be removed in the next major version.
## v3.3.4
### OpenTelemetry Request Duration metric
In `v3.3.4`, the OpenTelemetry Request Duration metric (named `traefik_(entrypoint|router|service)_request_duration_seconds`) unit has been changed from milliseconds to seconds.
To be consistent with the naming and other metrics providers, the metric now reports the duration in seconds.

View File

@@ -163,7 +163,7 @@ tracing:
#### `safeQueryParams`
_Optional, Default={}_
_Optional, Default=[]_
By default, all query parameters are redacted.
Defines the list of query parameters to not redact.

View File

@@ -1238,6 +1238,11 @@ spec:
description: ForwardBody defines whether to send the request body
to the authentication server.
type: boolean
headerField:
description: |-
HeaderField defines a header field to store the authenticated user.
More info: https://doc.traefik.io/traefik/v3.3/middlewares/http/forwardauth/#headerfield
type: string
maxBodySize:
description: MaxBodySize defines the maximum body size in bytes
allowed to be forwarded to the authentication server.

View File

@@ -496,6 +496,11 @@ spec:
description: ForwardBody defines whether to send the request body
to the authentication server.
type: boolean
headerField:
description: |-
HeaderField defines a header field to store the authenticated user.
More info: https://doc.traefik.io/traefik/v3.3/middlewares/http/forwardauth/#headerfield
type: string
maxBodySize:
description: MaxBodySize defines the maximum body size in bytes
allowed to be forwarded to the authentication server.

View File

@@ -57,6 +57,7 @@ additionalArguments:
| Field | Description | Default | Required |
|:------|:------------|:--------|:---------|
| `address` | Define the port, and optionally the hostname, on which to listen for incoming connections and packets.<br /> It also defines the protocol to use (TCP or UDP).<br /> If no protocol is specified, the default is TCP. The format is:`[host]:port[/tcp\|/udp]`. | - | Yes |
| `accessLogs` | Defines whether a router attached to this EntryPoint produces access-logs by default. Nonetheless, a router defining its own observability configuration will opt-out from this default. | true | No |
| `asDefault` | Mark the `entryPoint` to be in the list of default `entryPoints`.<br /> `entryPoints`in this list are used (by default) on HTTP and TCP routers that do not define their own `entryPoints` option.<br /> More information [here](#asdefault). | false | No |
| `forwardedHeaders.trustedIPs` | Set the IPs or CIDR from where Traefik trusts the forwarded headers information (`X-Forwarded-*`). | - | No |
| `forwardedHeaders.insecure` | Set the insecure mode to always trust the forwarded headers information (`X-Forwarded-*`).<br />We recommend to use this option only for tests purposes, not in production. | false | No |
@@ -72,9 +73,11 @@ additionalArguments:
| `http2.maxConcurrentStreams` | Set the number of concurrent streams per connection that each client is allowed to initiate. <br /> The value must be greater than zero. | 250 | No |
| `http3` | Enable HTTP/3 protocol on the `entryPoint`. <br /> HTTP/3 requires a TCP `entryPoint`. as HTTP/3 always starts as a TCP connection that then gets upgraded to UDP. In most scenarios, this `entryPoint` is the same as the one used for TLS traffic.<br /> More information [here](#http3. | - | No |
| `http3.advertisedPort` | Set the UDP port to advertise as the HTTP/3 authority. <br /> It defaults to the entryPoint's address port. <br /> It can be used to override the authority in the `alt-svc` header, for example if the public facing port is different from where Traefik is listening. | - | No |
| `metrics` | Defines whether a router attached to this EntryPoint produces metrics by default. Nonetheless, a router defining its own observability configuration will opt-out from this default.| true | No |
| `proxyProtocol.trustedIPs` | Enable PROXY protocol with Trusted IPs. <br /> Traefik supports [PROXY protocol](https://www.haproxy.org/download/2.0/doc/proxy-protocol.txt) version 1 and 2. <br /> If PROXY protocol header parsing is enabled for the entry point, this entry point can accept connections with or without PROXY protocol headers. <br /> If the PROXY protocol header is passed, then the version is determined automatically.<br /> More information [here](#proxyprotocol-and-load-balancers).| - | No |
| `proxyProtocol.insecure` | Enable PROXY protocol trusting every incoming connection. <br /> Every remote client address will be replaced (`trustedIPs`) won't have any effect). <br /> Traefik supports [PROXY protocol](https://www.haproxy.org/download/2.0/doc/proxy-protocol.txt) version 1 and 2. <br /> If PROXY protocol header parsing is enabled for the entry point, this entry point can accept connections with or without PROXY protocol headers. <br /> If the PROXY protocol header is passed, then the version is determined automatically.<br />We recommend to use this option only for tests purposes, not in production.<br /> More information [here](#proxyprotocol-and-load-balancers). | - | No |
| `reusePort` | Enable `entryPoints` from the same or different processes listening on the same TCP/UDP port by utilizing the `SO_REUSEPORT` socket option. <br /> It also allows the kernel to act like a load balancer to distribute incoming connections between entry points..<br /> More information [here](#reuseport). | false | No |
| `tracing` | Defines whether a router attached to this EntryPoint produces traces by default. Nonetheless, a router defining its own observability configuration will opt-out from this default.| true | No |
| `transport.`<br />`respondingTimeouts.`<br />`readTimeout` | Set the timeouts for incoming requests to the Traefik instance. This is the maximum duration for reading the entire request, including the body. Setting them has no effect for UDP `entryPoints`.<br /> If zero, no timeout exists. <br />Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).<br />If no units are provided, the value is parsed assuming seconds. | 60s (seconds) | No |
| `transport.`<br />`respondingTimeouts.`<br />`writeTimeout` | Maximum duration before timing out writes of the response. <br /> It covers the time from the end of the request header read to the end of the response write. <br /> If zero, no timeout exists. <br />Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).<br />If no units are provided, the value is parsed assuming seconds. | 0s (seconds) | No |
| `transport.`<br />`respondingTimeouts.`<br />`idleTimeout` | Maximum duration an idle (keep-alive) connection will remain idle before closing itself. <br /> If zero, no timeout exists <br />Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).<br />If no units are provided, the value is parsed assuming seconds| 180s (seconds) | No |

View File

@@ -36,27 +36,27 @@ tracing: {}
## Configuration Options
| Field | Description | Default | Required |
|:-----------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------|
| `tracing.addInternals` | Enables tracing for internal resources (e.g.: `ping@internal`). | false | No |
| `tracing.serviceName` | Service name used in selected backend. | "traefik" | No |
| `tracing.sampleRate` | The proportion of requests to trace, specified between 0.0 and 1.0. | 1.0 | No |
| `tracing.globalAttributes` | Applies a list of shared key:value attributes on all spans. | {} | No |
| `tracing.capturedRequestHeaders` | Defines the list of request headers to add as attributes.<br />It applies to client and server kind spans.| {} | No |
| `tracing.capturedResponseHeaders` | Defines the list of response headers to add as attributes.<br />It applies to client and server kind spans.| {} |False |
| `tracing.safeQueryParams` | By default, all query parameters are redacted.<br />Defines the list of query parameters to not redact. | {} | No |
| `tracing.otlp.http` | This instructs the exporter to send the tracing to the OpenTelemetry Collector using HTTP.<br /> Setting the sub-options with their default values. | null/false | No |
| `tracing.otlp.http.endpoint` | URL of the OpenTelemetry Collector to send tracing to.<br /> Format="`<scheme>://<host>:<port><path>`" | "http://localhost:4318/v1/tracing" | Yes |
| `tracing.otlp.http.headers` | Additional headers sent with tracing by the exporter to the OpenTelemetry Collector. | | No |
| `tracing.otlp.http.tls.ca` | Path to the certificate authority used for the secure connection to the OpenTelemetry Collector, it defaults to the system bundle. | "" | No |
| `tracing.otlp.http.tls.cert` | Path to the public certificate used for the secure connection to the OpenTelemetry Collector. When using this option, setting the `key` option is required. | "" | No |
| `tracing.otlp.http.tls.key` | This instructs the exporter to send the tracing to the OpenTelemetry Collector using HTTP.<br /> Setting the sub-options with their default values. | ""null/false "" | No |
| `tracing.otlp.http.tls.insecureskipverify` |If `insecureSkipVerify` is `true`, the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers. | false | Yes |
| `tracing.otlp.grpc` | This instructs the exporter to send tracing to the OpenTelemetry Collector using gRPC. | false | No |
| `tracing.otlp.grpc.endpoint` | Address of the OpenTelemetry Collector to send tracing to.<br /> Format="`<host>:<port>`" | "localhost:4317" | Yes |
| `tracing.otlp.grpc.headers` | Additional headers sent with tracing by the exporter to the OpenTelemetry Collector. | {} | No |
| `tracing.otlp.grpc.insecure` |Allows exporter to send tracing to the OpenTelemetry Collector without using a secured protocol. | false | Yes |
| `tracing.otlp.grpc.tls.ca` | Path to the certificate authority used for the secure connection to the OpenTelemetry Collector, it defaults to the system bundle. | "" | No |
| `tracing.otlp.grpc.tls.cert` | Path to the public certificate used for the secure connection to the OpenTelemetry Collector. When using this option, setting the `key` option is required. | "" | No |
| `tracing.otlp.grpc.tls.key` | This instructs the exporter to send the tracing to the OpenTelemetry Collector using HTTP.<br /> Setting the sub-options with their default values. | ""null/false "" | No |
| `tracing.otlp.grpc.tls.insecureskipverify` |If `insecureSkipVerify` is `true`, the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers. | false | Yes |
| Field | Description | Default | Required |
|:-------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------|:---------|
| `tracing.addInternals` | Enables tracing for internal resources (e.g.: `ping@internal`). | false | No |
| `tracing.serviceName` | Service name used in selected backend. | "traefik" | No |
| `tracing.sampleRate` | The proportion of requests to trace, specified between 0.0 and 1.0. | 1.0 | No |
| `tracing.resourceAttributes` | Defines additional resource attributes to be sent to the collector. | [] | No |
| `tracing.capturedRequestHeaders` | Defines the list of request headers to add as attributes.<br />It applies to client and server kind spans.| [] | No |
| `tracing.capturedResponseHeaders` | Defines the list of response headers to add as attributes.<br />It applies to client and server kind spans.| [] |False |
| `tracing.safeQueryParams` | By default, all query parameters are redacted.<br />Defines the list of query parameters to not redact. | [] | No |
| `tracing.otlp.http` | This instructs the exporter to send the tracing to the OpenTelemetry Collector using HTTP.<br /> Setting the sub-options with their default values. | null/false | No |
| `tracing.otlp.http.endpoint` | URL of the OpenTelemetry Collector to send tracing to.<br /> Format="`<scheme>://<host>:<port><path>`" | "http://localhost:4318/v1/tracing" | Yes |
| `tracing.otlp.http.headers` | Additional headers sent with tracing by the exporter to the OpenTelemetry Collector. | | No |
| `tracing.otlp.http.tls.ca` | Path to the certificate authority used for the secure connection to the OpenTelemetry Collector, it defaults to the system bundle. | "" | No |
| `tracing.otlp.http.tls.cert` | Path to the public certificate used for the secure connection to the OpenTelemetry Collector. When using this option, setting the `key` option is required. | "" | No |
| `tracing.otlp.http.tls.key` | This instructs the exporter to send the tracing to the OpenTelemetry Collector using HTTP.<br /> Setting the sub-options with their default values. | ""null/false "" | No |
| `tracing.otlp.http.tls.insecureskipverify` |If `insecureSkipVerify` is `true`, the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers. | false | Yes |
| `tracing.otlp.grpc` | This instructs the exporter to send tracing to the OpenTelemetry Collector using gRPC. | false | No |
| `tracing.otlp.grpc.endpoint` | Address of the OpenTelemetry Collector to send tracing to.<br /> Format="`<host>:<port>`" | "localhost:4317" | Yes |
| `tracing.otlp.grpc.headers` | Additional headers sent with tracing by the exporter to the OpenTelemetry Collector. | [] | No |
| `tracing.otlp.grpc.insecure` |Allows exporter to send tracing to the OpenTelemetry Collector without using a secured protocol. | false | Yes |
| `tracing.otlp.grpc.tls.ca` | Path to the certificate authority used for the secure connection to the OpenTelemetry Collector, it defaults to the system bundle. | "" | No |
| `tracing.otlp.grpc.tls.cert` | Path to the public certificate used for the secure connection to the OpenTelemetry Collector. When using this option, setting the `key` option is required. | "" | No |
| `tracing.otlp.grpc.tls.key` | This instructs the exporter to send the tracing to the OpenTelemetry Collector using HTTP.<br /> Setting the sub-options with their default values. | ""null/false "" | No |
| `tracing.otlp.grpc.tls.insecureskipverify` |If `insecureSkipVerify` is `true`, the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers. | false | Yes |

View File

@@ -52,21 +52,21 @@ providers:
## Configuration Options
| Field | Description | Default | Required |
|:------|:----------------------------------------------------------|:---------------------|:---------|
| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.<br />If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.<br />**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No |
| `providers.kubernetesCRD.endpoint` | Server endpoint URL.<br />More information [here](#endpoint). | "" | No |
| `providers.kubernetesCRD.token` | Bearer token used for the Kubernetes client configuration. | "" | No |
| `providers.kubernetesCRD.certAuthFilePath` | Path to the certificate authority file.<br />Used for the Kubernetes client configuration. | "" | No |
| `providers.kubernetesCRD.namespaces` | Array of namespaces to watch.<br />If left empty, watch all namespaces. | {} | No |
| `providers.kubernetesCRD.labelselector` | Allow filtering on specific resource objects only using label selectors.<br />Only to Traefik [Custom Resources](#list-of-resources) (they all must match the filter).<br />No effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`.<br />See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. | "" | No |
| `providers.kubernetesCRD.ingressClass` | Value of `kubernetes.io/ingress.class` annotation that identifies resource objects to be processed.<br />If empty, resources missing the annotation, having an empty value, or the value `traefik` are processed. | "" | No |
| `providers.kubernetesCRD.throttleDuration` | Minimum amount of time to wait between two Kubernetes events before producing a new configuration.<br />This prevents a Kubernetes cluster that updates many times per second from continuously changing your Traefik configuration.<br />If empty, every event is caught. | 0s | No |
| `providers.kubernetesCRD.allowEmptyServices` | Allows creating a route to reach a service that has no endpoint available.<br />It allows Traefik to handle the requests and responses targeting this service (applying middleware or observability operations) before returning a `503` HTTP Status. | false | No |
| `providers.kubernetesCRD.allowCrossNamespace` | Allows the `IngressRoutes` to reference resources in namespaces other than theirs. | false | No |
| `providers.kubernetesCRD.allowExternalNameServices` | Allows the `IngressRoutes` to reference ExternalName services. | false | No |
| `providers.kubernetesCRD.nativeLBByDefault` | Allow using the Kubernetes Service load balancing between the pods instead of the one provided by Traefik for every `IngressRoute` by default.<br />It can br overridden in the [`ServerTransport`](../../../../routing/services/index.md#serverstransport). | false | No |
| `providers.kubernetesCRD.disableClusterScopeResources` | Prevent from discovering cluster scope resources (`IngressClass` and `Nodes`).<br />By doing so, it alleviates the requirement of giving Traefik the rights to look up for cluster resources.<br />Furthermore, Traefik will not handle IngressRoutes with IngressClass references, therefore such Ingresses will be ignored (please note that annotations are not affected by this option).<br />This will also prevent from using the `NodePortLB` options on services. | false | No |
| Field | Description | Default | Required |
|:------|:----------------------------------------------------------|:--------|:---------|
| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.<br />If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.<br />**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No |
| `providers.kubernetesCRD.endpoint` | Server endpoint URL.<br />More information [here](#endpoint). | "" | No |
| `providers.kubernetesCRD.token` | Bearer token used for the Kubernetes client configuration. | "" | No |
| `providers.kubernetesCRD.certAuthFilePath` | Path to the certificate authority file.<br />Used for the Kubernetes client configuration. | "" | No |
| `providers.kubernetesCRD.namespaces` | Array of namespaces to watch.<br />If left empty, watch all namespaces. | [] | No |
| `providers.kubernetesCRD.labelselector` | Allow filtering on specific resource objects only using label selectors.<br />Only to Traefik [Custom Resources](#list-of-resources) (they all must match the filter).<br />No effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`.<br />See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. | "" | No |
| `providers.kubernetesCRD.ingressClass` | Value of `kubernetes.io/ingress.class` annotation that identifies resource objects to be processed.<br />If empty, resources missing the annotation, having an empty value, or the value `traefik` are processed. | "" | No |
| `providers.kubernetesCRD.throttleDuration` | Minimum amount of time to wait between two Kubernetes events before producing a new configuration.<br />This prevents a Kubernetes cluster that updates many times per second from continuously changing your Traefik configuration.<br />If empty, every event is caught. | 0s | No |
| `providers.kubernetesCRD.allowEmptyServices` | Allows creating a route to reach a service that has no endpoint available.<br />It allows Traefik to handle the requests and responses targeting this service (applying middleware or observability operations) before returning a `503` HTTP Status. | false | No |
| `providers.kubernetesCRD.allowCrossNamespace` | Allows the `IngressRoutes` to reference resources in namespaces other than theirs. | false | No |
| `providers.kubernetesCRD.allowExternalNameServices` | Allows the `IngressRoutes` to reference ExternalName services. | false | No |
| `providers.kubernetesCRD.nativeLBByDefault` | Allow using the Kubernetes Service load balancing between the pods instead of the one provided by Traefik for every `IngressRoute` by default.<br />It can br overridden in the [`ServerTransport`](../../../../routing/services/index.md#serverstransport). | false | No |
| `providers.kubernetesCRD.disableClusterScopeResources` | Prevent from discovering cluster scope resources (`IngressClass` and `Nodes`).<br />By doing so, it alleviates the requirement of giving Traefik the rights to look up for cluster resources.<br />Furthermore, Traefik will not handle IngressRoutes with IngressClass references, therefore such Ingresses will be ignored (please note that annotations are not affected by this option).<br />This will also prevent from using the `NodePortLB` options on services. | false | No |
### endpoint

View File

@@ -67,20 +67,20 @@ providers:
<!-- markdownlint-disable MD013 -->
| Field | Description | Default | Required |
|:------|:----------------------------------------------------------|:---------------------|:---------|
| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.<br />If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.<br />**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No |
| `providers.kubernetesGateway.endpoint` | Server endpoint URL.<br />More information [here](#endpoint). | "" | No |
| `providers.kubernetesGateway.experimentalChannel` | Toggles support for the Experimental Channel resources ([Gateway API release channels documentation](https://gateway-api.sigs.k8s.io/concepts/versioning/#release-channels)).<br />(ex: `TCPRoute` and `TLSRoute`)| false | No |
| `providers.kubernetesGateway.token` | Bearer token used for the Kubernetes client configuration. | "" | No |
| `providers.kubernetesGateway.certAuthFilePath` | Path to the certificate authority file.<br />Used for the Kubernetes client configuration. | "" | No |
| `providers.kubernetesGateway.namespaces` | Array of namespaces to watch.<br />If left empty, watch all namespaces. | {} | No |
| `providers.kubernetesGateway.labelselector` | Allow filtering on specific resource objects only using label selectors.<br />Only to Traefik [Custom Resources](./kubernetes-crd.md#list-of-resources) (they all must match the filter).<br />No effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`.<br />See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. | "" | No |
| `providers.kubernetesGateway.throttleDuration` | Minimum amount of time to wait between two Kubernetes events before producing a new configuration.<br />This prevents a Kubernetes cluster that updates many times per second from continuously changing your Traefik configuration.<br />If empty, every event is caught. | 0s | No |
| `providers.kubernetesGateway.nativeLBByDefault` | Defines whether to use Native Kubernetes load-balancing mode by default. For more information, please check out the `traefik.io/service.nativelb` service annotation documentation. | false | No |
| `providers.kubernetesGateway.`<br />`statusAddress.hostname` | Hostname copied to the Gateway `status.addresses`. | "" | No |
| `providers.kubernetesGateway.`<br />`statusAddress.ip` | IP address copied to the Gateway `status.addresses`, and currently only supports one IP value (IPv4 or IPv6). | "" | No |
| `providers.kubernetesGateway.`<br />`statusAddress.publishedService` | The Kubernetes service to copy status addresses from.<br />When using third parties tools like External-DNS, this option can be used to copy the service `loadbalancer.status` (containing the service's endpoints IPs) to the gateways. | "" | No |
| Field | Description | Default | Required |
|:------|:----------------------------------------------------------|:--------|:---------|
| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.<br />If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.<br />**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No |
| `providers.kubernetesGateway.endpoint` | Server endpoint URL.<br />More information [here](#endpoint). | "" | No |
| `providers.kubernetesGateway.experimentalChannel` | Toggles support for the Experimental Channel resources ([Gateway API release channels documentation](https://gateway-api.sigs.k8s.io/concepts/versioning/#release-channels)).<br />(ex: `TCPRoute` and `TLSRoute`)| false | No |
| `providers.kubernetesGateway.token` | Bearer token used for the Kubernetes client configuration. | "" | No |
| `providers.kubernetesGateway.certAuthFilePath` | Path to the certificate authority file.<br />Used for the Kubernetes client configuration. | "" | No |
| `providers.kubernetesGateway.namespaces` | Array of namespaces to watch.<br />If left empty, watch all namespaces. | [] | No |
| `providers.kubernetesGateway.labelselector` | Allow filtering on specific resource objects only using label selectors.<br />Only to Traefik [Custom Resources](./kubernetes-crd.md#list-of-resources) (they all must match the filter).<br />No effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`.<br />See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. | "" | No |
| `providers.kubernetesGateway.throttleDuration` | Minimum amount of time to wait between two Kubernetes events before producing a new configuration.<br />This prevents a Kubernetes cluster that updates many times per second from continuously changing your Traefik configuration.<br />If empty, every event is caught. | 0s | No |
| `providers.kubernetesGateway.nativeLBByDefault` | Defines whether to use Native Kubernetes load-balancing mode by default. For more information, please check out the `traefik.io/service.nativelb` service annotation documentation. | false | No |
| `providers.kubernetesGateway.`<br />`statusAddress.hostname` | Hostname copied to the Gateway `status.addresses`. | "" | No |
| `providers.kubernetesGateway.`<br />`statusAddress.ip` | IP address copied to the Gateway `status.addresses`, and currently only supports one IP value (IPv4 or IPv6). | "" | No |
| `providers.kubernetesGateway.`<br />`statusAddress.publishedService` | The Kubernetes service to copy status addresses from.<br />When using third parties tools like External-DNS, this option can be used to copy the service `loadbalancer.status` (containing the service's endpoints IPs) to the gateways. | "" | No |
<!-- markdownlint-enable MD013 -->

View File

@@ -79,15 +79,17 @@ ACME certificate resolvers have the following configuration options:
| `acme.caServer` | CA server to use. | https://acme-v02.api.letsencrypt.org/directory | No |
| `acme.preferredChain` | Preferred chain to use. If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used. | "" | No |
| `acme.keyType` | KeyType to use. | "RSA4096" | No |
| `acme.eab` | Enable external account binding.| "" | No |
| `acme.eab.kid` | Key identifier from External CA. | | No |
| `acme.eab` | Enable external account binding.| | No |
| `acme.eab.kid` | Key identifier from External CA. | "" | No |
| `acme.eab.hmacEncoded` | HMAC key from External CA, should be in Base64 URL Encoding without padding format. | "" | No |
| `acme.certificatesDuration` | The certificates' duration in hours, exclusively used to determine renewal dates. | 2160 | No |
| `acme.dnsChallenge` | Enable DNS-01 challenge. More information [here](#dnschallenge). | - | No |
| `acme.dnsChallenge.provider` | DNS provider to use. | | No |
| `acme.dnsChallenge.delayBeforeCheck` | By default, the provider will verify the TXT DNS challenge record before letting ACME verify. If `delayBeforeCheck` is greater than zero, this check is delayed for the configured duration in seconds. Useful if internal networks block external DNS queries. | | No |
| `acme.dnsChallenge.resolvers` | DNS servers to resolve the FQDN authority. | | No |
| `acme.dnsChallenge.disablePropagationCheck` | Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready. | | No |
| `acme.dnsChallenge.provider` | DNS provider to use. | "" | No |
| `acme.dnsChallenge.resolvers` | DNS servers to resolve the FQDN authority. | [] | No |
| `acme.dnsChallenge.propagation.delayBeforeChecks` | By default, the provider will verify the TXT DNS challenge record before letting ACME verify. If `delayBeforeCheck` is greater than zero, this check is delayed for the configured duration in seconds. This is Useful if internal networks block external DNS queries. | 0s | No |
| `acme.dnsChallenge.propagation.disableChecks` | Disables the challenge TXT record propagation checks, before notifying ACME that the DNS challenge is ready. Please note that disabling checks can prevent the challenge from succeeding. | false | No |
| `acme.dnsChallenge.propagation.requireAllRNS` | Enables the challenge TXT record to be propagated to all recursive nameservers. If you have disabled authoritative nameservers checks (with `propagation.disableANSChecks`), it is recommended to check all recursive nameservers instead. | false | No |
| `acme.dnsChallenge.propagation.disableANSChecks` | Disables the challenge TXT record propagation checks against authoritative nameservers. This option will skip the propagation check against the nameservers of the authority (SOA). It should be used only if the nameservers of the authority are not reachable. | false | No |
| `acme.httpChallenge` | Enable HTTP-01 challenge. More information [here](#httpchallenge). | | No |
| `acme.httpChallenge.entryPoint` | EntryPoint to use for the HTTP-01 challenges. Must be reachable by Let's Encrypt through port 80 | "" | Yes |
| `acme.tlsChallenge` | Enable TLS-ALPN-01 challenge. Traefik must be reachable by Let's Encrypt through port 443. More information [here](#tlschallenge). | - | No |

View File

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

View File

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

View File

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

View File

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

View File

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

14
go.mod
View File

@@ -6,7 +6,7 @@ require (
github.com/BurntSushi/toml v1.4.0
github.com/Masterminds/sprig/v3 v3.2.3
github.com/abbot/go-http-auth v0.0.0-00010101000000-000000000000 // No tag on the repo.
github.com/andybalholm/brotli v1.1.0
github.com/andybalholm/brotli v1.1.1
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/aws/aws-sdk-go v1.44.327
github.com/cenkalti/backoff/v4 v4.3.0
@@ -17,7 +17,7 @@ require (
github.com/docker/go-connections v0.5.0
github.com/fatih/structs v1.1.0
github.com/fsnotify/fsnotify v1.8.0
github.com/go-acme/lego/v4 v4.21.0
github.com/go-acme/lego/v4 v4.22.2
github.com/go-kit/kit v0.13.0
github.com/go-kit/log v0.2.1
github.com/golang/protobuf v1.5.4
@@ -33,7 +33,7 @@ require (
github.com/http-wasm/http-wasm-host-go v0.7.0
github.com/influxdata/influxdb-client-go/v2 v2.7.0
github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab // No tag on the repo.
github.com/klauspost/compress v1.17.11-0.20241004063537-dbd6c381492a // Required to have the content-type fix: https://github.com/klauspost/compress/pull/1013
github.com/klauspost/compress v1.17.11
github.com/kvtools/consul v1.0.2
github.com/kvtools/etcdv3 v1.0.2
github.com/kvtools/redis v1.1.0
@@ -63,11 +63,11 @@ require (
github.com/tetratelabs/wazero v1.8.0
github.com/tidwall/gjson v1.17.0
github.com/traefik/grpc-web v0.16.0
github.com/traefik/paerser v0.2.1
github.com/traefik/paerser v0.2.2
github.com/traefik/yaegi v0.16.1
github.com/unrolled/render v1.0.2
github.com/unrolled/secure v1.0.9
github.com/valyala/fasthttp v1.55.0
github.com/valyala/fasthttp v1.58.0
github.com/vulcand/oxy/v2 v2.0.0
github.com/vulcand/predicate v1.2.0
go.opentelemetry.io/collector/pdata v1.10.0
@@ -87,7 +87,6 @@ require (
go.opentelemetry.io/otel/sdk/log v0.8.0
go.opentelemetry.io/otel/sdk/metric v1.28.0
go.opentelemetry.io/otel/trace v1.32.0
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // No tag on the repo.
golang.org/x/mod v0.22.0
golang.org/x/net v0.33.0
golang.org/x/sync v0.10.0
@@ -169,7 +168,6 @@ require (
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/cpu/goacmedns v0.1.1 // indirect
github.com/cpuguy83/dockercfg v0.3.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/deepmap/oapi-codegen v1.9.1 // indirect
@@ -281,6 +279,7 @@ require (
github.com/nrdcg/desec v0.10.0 // indirect
github.com/nrdcg/dnspod-go v0.4.0 // indirect
github.com/nrdcg/freemyip v0.3.0 // indirect
github.com/nrdcg/goacmedns v0.2.0 // indirect
github.com/nrdcg/goinwx v0.10.0 // indirect
github.com/nrdcg/mailinabox v0.2.0 // indirect
github.com/nrdcg/namesilo v0.2.1 // indirect
@@ -361,6 +360,7 @@ require (
go.uber.org/zap v1.26.0 // indirect
golang.org/x/arch v0.4.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect
golang.org/x/oauth2 v0.24.0 // indirect
golang.org/x/term v0.27.0 // indirect
google.golang.org/api v0.214.0 // indirect

26
go.sum
View File

@@ -135,8 +135,8 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/aliyun/alibaba-cloud-sdk-go v1.63.72 h1:HvFZUzEbNvfe8F2Mg0wBGv90bPhWDxgVtDHR5zoBOU0=
github.com/aliyun/alibaba-cloud-sdk-go v1.63.72/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
@@ -259,8 +259,6 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4=
github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ=
github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E=
github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
@@ -357,8 +355,8 @@ 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.21.0 h1:arEW+8o5p7VI8Bk1kr/PDlgD1DrxtTH1gJ4b7mehL8o=
github.com/go-acme/lego/v4 v4.21.0/go.mod h1:HrSWzm3Ckj45Ie3i+p1zKVobbQoMOaGu9m4up0dUeDI=
github.com/go-acme/lego/v4 v4.22.2 h1:ck+HllWrV/rZGeYohsKQ5iKNnU/WAZxwOdiu6cxky+0=
github.com/go-acme/lego/v4 v4.22.2/go.mod h1:E2FndyI3Ekv0usNJt46mFb9LVpV/XBYT+4E3tz02Tzo=
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=
@@ -713,8 +711,8 @@ github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/compress v1.17.11-0.20241004063537-dbd6c381492a h1:cwHOqPB4H4iQq8177kf2SxpjNbcjJ2m3lNwKIe28Hqg=
github.com/klauspost/compress v1.17.11-0.20241004063537-dbd6c381492a/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
@@ -905,6 +903,8 @@ 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.3.0 h1:0D2rXgvLwe2RRaVIjyUcQ4S26+cIS2iFwnhzDsEuuwc=
github.com/nrdcg/freemyip v0.3.0/go.mod h1:c1PscDvA0ukBF0dwelU/IwOakNKnVxetpAQ863RMJoM=
github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0=
github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg=
github.com/nrdcg/goinwx v0.10.0 h1:6W630bjDxQD6OuXKqrFRYVpTt0G/9GXXm3CeOrN0zJM=
github.com/nrdcg/goinwx v0.10.0/go.mod h1:mnMSTi7CXBu2io4DzdOBoGFA1XclD0sEPWJaDhNgkA4=
github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CFk=
@@ -1187,8 +1187,8 @@ github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9f
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/traefik/grpc-web v0.16.0 h1:eeUWZaFg6ZU0I9dWOYE2D5qkNzRBmXzzuRlxdltascY=
github.com/traefik/grpc-web v0.16.0/go.mod h1:2ttniSv7pTgBWIU2HZLokxRfFX3SA60c/DTmQQgVml4=
github.com/traefik/paerser v0.2.1 h1:LFgeak1NmjEHF53c9ENdXdL1UMkF/lD5t+7Evsz4hH4=
github.com/traefik/paerser v0.2.1/go.mod h1:7BBDd4FANoVgaTZG+yh26jI6CA2nds7D/4VTEdIsh24=
github.com/traefik/paerser v0.2.2 h1:cpzW/ZrQrBh3mdwD/jnp6aXASiUFKOVr6ldP+keJTcQ=
github.com/traefik/paerser v0.2.2/go.mod h1:7BBDd4FANoVgaTZG+yh26jI6CA2nds7D/4VTEdIsh24=
github.com/traefik/yaegi v0.16.1 h1:f1De3DVJqIDKmnasUF6MwmWv1dSEEat0wcpXhD2On3E=
github.com/traefik/yaegi v0.16.1/go.mod h1:4eVhbPb3LnD2VigQjhYbEJ69vDRFdT2HQNrXx8eEwUY=
github.com/transip/gotransip/v6 v6.26.0 h1:Aejfvh8rSp8Mj2GX/RpdBjMCv+Iy/DmgfNgczPDP550=
@@ -1218,8 +1218,8 @@ github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8=
github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM=
github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE=
github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ=
@@ -1243,6 +1243,8 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c h1:Rnr+lDYXVkP+3eT8/d68iq4G/UeIhyCQk+HKa8toTvg=
github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo=
github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 h1:qmpz0Kvr9GAng8LAhRcKIpY71CEAcL3EBkftVlsP5Cw=

View File

@@ -0,0 +1,57 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
noColor = true
[entryPoints]
[entryPoints.web]
address = ":5002"
[entryPoints.websecure]
address = ":5001"
[certificatesResolvers.default.acme]
email = "test@traefik.io"
storage = "/tmp/acme.json"
keyType = ""
caServer = "https://172.31.42.2:14000/dir"
[certificatesResolvers.default.acme.tlsChallenge]
[api]
insecure = true
[providers.file]
filename = "fixtures/acme/acme_domains_1712362990.toml"
## dynamic configuration ##
[http.services]
[http.services.test.loadBalancer]
[[http.services.test.loadBalancer.servers]]
url = "http://127.0.0.1:9010"
[http.routers]
[http.routers.test]
entryPoints = ["websecure"]
rule = "PathPrefix(`/`)"
service = "test"
[http.routers.test.tls]
certResolver = "default"
[[http.routers.test.tls.domains]]
main = "acme.wtf"
sans = [
"traefik.acme.wtf",
]

View File

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

View File

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

View File

@@ -1238,6 +1238,11 @@ spec:
description: ForwardBody defines whether to send the request body
to the authentication server.
type: boolean
headerField:
description: |-
HeaderField defines a header field to store the authenticated user.
More info: https://doc.traefik.io/traefik/v3.3/middlewares/http/forwardauth/#headerfield
type: string
maxBodySize:
description: MaxBodySize defines the maximum body size in bytes
allowed to be forwarded to the authentication server.

View File

@@ -50,7 +50,7 @@
Rule = "Path(`/basic`)"
[http.routers.router1]
Service = "service1"
Middlewares = ["retry", "ratelimit-1"]
Middlewares = ["ratelimit-1"]
Rule = "Path(`/ratelimit`)"
[http.routers.router2]
Service = "service2"
@@ -58,8 +58,12 @@
Rule = "Path(`/retry`)"
[http.routers.router3]
Service = "service3"
Middlewares = ["retry", "basic-auth"]
Middlewares = ["basic-auth"]
Rule = "Path(`/auth`)"
[http.routers.router4]
Service = "service4"
Middlewares = ["retry", "basic-auth"]
Rule = "Path(`/retry-auth`)"
[http.routers.customPing]
entryPoints = ["web"]
rule = "PathPrefix(`/ping`)"
@@ -98,3 +102,9 @@
passHostHeader = true
[[http.services.service3.loadBalancer.servers]]
url = "http://{{.WhoamiIP}}:{{.WhoamiPort}}"
[http.services.service4]
[http.services.service4.loadBalancer]
passHostHeader = true
[[http.services.service4.loadBalancer.servers]]
url = "http://{{.WhoamiIP}}:{{.WhoamiPort}}"

View File

@@ -12,6 +12,11 @@
"rule": "Host(`kv1.localhost`)",
"priority": 42,
"tls": {},
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"web"
@@ -42,6 +47,11 @@
}
]
},
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"web"
@@ -55,6 +65,11 @@
"rule": "PathPrefix(`/api`)",
"ruleSyntax": "v3",
"priority": 9223372036854775806,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"traefik"
@@ -72,6 +87,11 @@
"rule": "PathPrefix(`/`)",
"ruleSyntax": "v3",
"priority": 9223372036854775805,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"traefik"

View File

@@ -7,6 +7,11 @@
"service": "api@internal",
"rule": "PathPrefix(`/api`)",
"priority": 18,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"web"
@@ -22,6 +27,11 @@
"tls": {
"options": "default-mytlsoption"
},
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"web"

View File

@@ -7,6 +7,11 @@
"service": "api@internal",
"rule": "PathPrefix(`/api`)",
"priority": 18,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"web"
@@ -22,6 +27,11 @@
"tls": {
"options": "default-mytlsoption"
},
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"web"
@@ -37,6 +47,11 @@
"service": "default-test2-route-23c7f4c450289ee29016",
"rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/tobestripped`)",
"priority": 46,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"web"
@@ -49,6 +64,11 @@
"service": "default-wrr1",
"rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/wrr1`)",
"priority": 38,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"web"
@@ -61,6 +81,11 @@
"service": "default-testst-route-60ad45fcb5fc1f5f3629",
"rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/serverstransport`)",
"priority": 50,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"web"
@@ -73,6 +98,11 @@
"service": "other-ns-wrr3",
"rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/c`)",
"priority": 35,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"error": [
"the service \"other-ns-wrr3@kubernetescrd\" does not exist"
],

View File

@@ -12,6 +12,11 @@
"rule": "Host(`kv1.localhost`)",
"priority": 42,
"tls": {},
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"web"
@@ -42,6 +47,11 @@
}
]
},
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"web"
@@ -55,6 +65,11 @@
"rule": "PathPrefix(`/api`)",
"ruleSyntax": "v3",
"priority": 9223372036854775806,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"traefik"
@@ -72,6 +87,11 @@
"rule": "PathPrefix(`/`)",
"ruleSyntax": "v3",
"priority": 9223372036854775805,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"traefik"

View File

@@ -8,6 +8,11 @@
"rule": "PathPrefix(`/api`)",
"ruleSyntax": "v3",
"priority": 9223372036854775806,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"traefik"
@@ -25,6 +30,11 @@
"rule": "PathPrefix(`/`)",
"ruleSyntax": "v3",
"priority": 9223372036854775805,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"traefik"
@@ -38,6 +48,11 @@
"rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)",
"ruleSyntax": "v3",
"priority": 100008,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"web"
@@ -52,6 +67,11 @@
"ruleSyntax": "v3",
"priority": 100008,
"tls": {},
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"websecure"

View File

@@ -8,6 +8,11 @@
"rule": "PathPrefix(`/api`)",
"ruleSyntax": "v3",
"priority": 9223372036854775806,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"traefik"
@@ -25,6 +30,11 @@
"rule": "PathPrefix(`/`)",
"ruleSyntax": "v3",
"priority": 9223372036854775805,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"traefik"
@@ -37,6 +47,11 @@
"service": "default-whoami-http",
"rule": "Host(`whoami.test`) \u0026\u0026 PathPrefix(`/whoami`)",
"priority": 44,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"web"

View File

@@ -8,6 +8,11 @@
"rule": "PathPrefix(`/api`)",
"ruleSyntax": "v3",
"priority": 9223372036854775806,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"traefik"
@@ -25,6 +30,11 @@
"rule": "PathPrefix(`/`)",
"ruleSyntax": "v3",
"priority": 9223372036854775805,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"traefik"
@@ -37,6 +47,11 @@
"service": "default-whoami-http",
"rule": "Host(`whoami.test.https`) \u0026\u0026 PathPrefix(`/whoami`)",
"priority": 50,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"web"
@@ -49,6 +64,11 @@
"service": "default-whoami-http",
"rule": "Host(`whoami.test`) \u0026\u0026 PathPrefix(`/whoami`)",
"priority": 44,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"web"
@@ -61,6 +81,11 @@
"service": "default-whoami-80",
"rule": "Host(`whoami.test.drop`) \u0026\u0026 PathPrefix(`/drop`)",
"priority": 47,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"web"
@@ -73,6 +98,11 @@
"service": "default-whoami-80",
"rule": "Host(`whoami.test.keep`) \u0026\u0026 PathPrefix(`/keep`)",
"priority": 47,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"web"

View File

@@ -8,6 +8,11 @@
"rule": "PathPrefix(`/api`)",
"ruleSyntax": "v3",
"priority": 9223372036854775806,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"traefik"
@@ -25,6 +30,11 @@
"rule": "PathPrefix(`/`)",
"ruleSyntax": "v3",
"priority": 9223372036854775805,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"traefik"

View File

@@ -8,6 +8,11 @@
"rule": "PathPrefix(`/api`)",
"ruleSyntax": "v3",
"priority": 9223372036854775806,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"traefik"
@@ -25,6 +30,11 @@
"rule": "PathPrefix(`/`)",
"ruleSyntax": "v3",
"priority": 9223372036854775805,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"traefik"
@@ -37,6 +47,11 @@
"service": "default-whoami-80",
"rule": "Host(`whoami.test.keep`) \u0026\u0026 PathPrefix(`/keep`)",
"priority": 47,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"web"

View File

@@ -12,6 +12,11 @@
"rule": "Host(`kv1.localhost`)",
"priority": 42,
"tls": {},
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"web"
@@ -42,6 +47,11 @@
}
]
},
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"web"
@@ -55,6 +65,11 @@
"rule": "PathPrefix(`/api`)",
"ruleSyntax": "v3",
"priority": 9223372036854775806,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"traefik"
@@ -72,6 +87,11 @@
"rule": "PathPrefix(`/`)",
"ruleSyntax": "v3",
"priority": 9223372036854775805,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"traefik"

View File

@@ -12,6 +12,11 @@
"rule": "Host(`kv1.localhost`)",
"priority": 42,
"tls": {},
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"web"
@@ -42,6 +47,11 @@
}
]
},
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"web"
@@ -55,6 +65,11 @@
"rule": "PathPrefix(`/api`)",
"ruleSyntax": "v3",
"priority": 9223372036854775806,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"traefik"
@@ -72,6 +87,11 @@
"rule": "PathPrefix(`/`)",
"ruleSyntax": "v3",
"priority": 9223372036854775805,
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
},
"status": "enabled",
"using": [
"traefik"

View File

@@ -77,7 +77,7 @@ func (s *TracingSuite) TearDownTest() {
s.composeStop("tempo")
}
func (s *TracingSuite) TestOpentelemetryBasic_HTTP() {
func (s *TracingSuite) TestOpenTelemetryBasic_HTTP() {
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
WhoamiIP: s.whoamiIP,
WhoamiPort: s.whoamiPort,
@@ -144,7 +144,7 @@ func (s *TracingSuite) TestOpentelemetryBasic_HTTP() {
s.checkTraceContent(contains)
}
func (s *TracingSuite) TestOpentelemetryBasic_gRPC() {
func (s *TracingSuite) TestOpenTelemetryBasic_gRPC() {
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
WhoamiIP: s.whoamiIP,
WhoamiPort: s.whoamiPort,
@@ -201,7 +201,7 @@ func (s *TracingSuite) TestOpentelemetryBasic_gRPC() {
s.checkTraceContent(contains)
}
func (s *TracingSuite) TestOpentelemetryRateLimit() {
func (s *TracingSuite) TestOpenTelemetryRateLimit() {
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
WhoamiIP: s.whoamiIP,
WhoamiPort: s.whoamiPort,
@@ -248,48 +248,26 @@ func (s *TracingSuite) TestOpentelemetryRateLimit() {
"batches.0.scopeSpans.0.spans.0.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "ratelimit-1@file",
"batches.0.scopeSpans.0.spans.1.name": "Retry",
"batches.0.scopeSpans.0.spans.1.name": "Router",
"batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.service.name\").value.stringValue": "service1@file",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.router.name\").value.stringValue": "router1@file",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"http.route\").value.stringValue": "Path(`/ratelimit`)",
"batches.0.scopeSpans.0.spans.2.name": "RateLimiter",
"batches.0.scopeSpans.0.spans.2.name": "Metrics",
"batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "ratelimit-1@file",
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
"batches.0.scopeSpans.0.spans.3.name": "Retry",
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.request.resend_count\").value.intValue": "1",
"batches.0.scopeSpans.0.spans.4.name": "RateLimiter",
"batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "ratelimit-1@file",
"batches.0.scopeSpans.0.spans.5.name": "Retry",
"batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"http.request.resend_count\").value.intValue": "2",
"batches.0.scopeSpans.0.spans.6.name": "Router",
"batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"traefik.service.name\").value.stringValue": "service1@file",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"traefik.router.name\").value.stringValue": "router1@file",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.route\").value.stringValue": "Path(`/ratelimit`)",
"batches.0.scopeSpans.0.spans.7.name": "Metrics",
"batches.0.scopeSpans.0.spans.7.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
"batches.0.scopeSpans.0.spans.8.name": "EntryPoint",
"batches.0.scopeSpans.0.spans.8.kind": "SPAN_KIND_SERVER",
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"entry_point\").value.stringValue": "web",
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"url.path\").value.stringValue": "/ratelimit",
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"url.query\").value.stringValue": "",
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"http.response.status_code\").value.intValue": "429",
"batches.0.scopeSpans.0.spans.3.name": "EntryPoint",
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_SERVER",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"entry_point\").value.stringValue": "web",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"url.path\").value.stringValue": "/ratelimit",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"url.query\").value.stringValue": "",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.response.status_code\").value.intValue": "429",
},
{
"batches.0.scopeSpans.0.scope.name": "github.com/traefik/traefik",
@@ -318,37 +296,33 @@ func (s *TracingSuite) TestOpentelemetryRateLimit() {
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "ratelimit-1@file",
"batches.0.scopeSpans.0.spans.4.name": "Retry",
"batches.0.scopeSpans.0.spans.4.name": "Router",
"batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.service.name\").value.stringValue": "service1@file",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.router.name\").value.stringValue": "router1@file",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"http.route\").value.stringValue": "Path(`/ratelimit`)",
"batches.0.scopeSpans.0.spans.5.name": "Router",
"batches.0.scopeSpans.0.spans.5.name": "Metrics",
"batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.service.name\").value.stringValue": "service1@file",
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.router.name\").value.stringValue": "router1@file",
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"http.route\").value.stringValue": "Path(`/ratelimit`)",
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
"batches.0.scopeSpans.0.spans.6.name": "Metrics",
"batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
"batches.0.scopeSpans.0.spans.7.name": "EntryPoint",
"batches.0.scopeSpans.0.spans.7.kind": "SPAN_KIND_SERVER",
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"entry_point\").value.stringValue": "web",
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"url.path\").value.stringValue": "/ratelimit",
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"url.query\").value.stringValue": "",
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"http.response.status_code\").value.intValue": "200",
"batches.0.scopeSpans.0.spans.6.name": "EntryPoint",
"batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_SERVER",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"entry_point\").value.stringValue": "web",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"url.path\").value.stringValue": "/ratelimit",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"url.query\").value.stringValue": "",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.response.status_code\").value.intValue": "200",
},
}
s.checkTraceContent(contains)
}
func (s *TracingSuite) TestOpentelemetryRetry() {
func (s *TracingSuite) TestOpenTelemetryRetry() {
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
WhoamiIP: s.whoamiIP,
WhoamiPort: 81,
@@ -471,7 +445,7 @@ func (s *TracingSuite) TestOpentelemetryRetry() {
s.checkTraceContent(contains)
}
func (s *TracingSuite) TestOpentelemetryAuth() {
func (s *TracingSuite) TestOpenTelemetryAuth() {
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
WhoamiIP: s.whoamiIP,
WhoamiPort: s.whoamiPort,
@@ -498,59 +472,90 @@ func (s *TracingSuite) TestOpentelemetryAuth() {
"batches.0.scopeSpans.0.spans.0.status.message": "Authentication failed",
"batches.0.scopeSpans.0.spans.0.status.code": "STATUS_CODE_ERROR",
"batches.0.scopeSpans.0.spans.1.name": "Retry",
"batches.0.scopeSpans.0.spans.1.name": "Router",
"batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.service.name\").value.stringValue": "service3@file",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.router.name\").value.stringValue": "router3@file",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"http.route\").value.stringValue": "Path(`/auth`)",
"batches.0.scopeSpans.0.spans.2.name": "BasicAuth",
"batches.0.scopeSpans.0.spans.2.name": "Metrics",
"batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "basic-auth@file",
"batches.0.scopeSpans.0.spans.2.status.message": "Authentication failed",
"batches.0.scopeSpans.0.spans.2.status.code": "STATUS_CODE_ERROR",
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
"batches.0.scopeSpans.0.spans.3.name": "Retry",
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.request.resend_count\").value.intValue": "1",
"batches.0.scopeSpans.0.spans.4.name": "BasicAuth",
"batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "basic-auth@file",
"batches.0.scopeSpans.0.spans.4.status.message": "Authentication failed",
"batches.0.scopeSpans.0.spans.4.status.code": "STATUS_CODE_ERROR",
"batches.0.scopeSpans.0.spans.5.name": "Retry",
"batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"http.request.resend_count\").value.intValue": "2",
"batches.0.scopeSpans.0.spans.6.name": "Router",
"batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"traefik.service.name\").value.stringValue": "service3@file",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"traefik.router.name\").value.stringValue": "router3@file",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.route\").value.stringValue": "Path(`/auth`)",
"batches.0.scopeSpans.0.spans.7.name": "Metrics",
"batches.0.scopeSpans.0.spans.7.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
"batches.0.scopeSpans.0.spans.8.name": "EntryPoint",
"batches.0.scopeSpans.0.spans.8.kind": "SPAN_KIND_SERVER",
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"entry_point\").value.stringValue": "web",
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"url.path\").value.stringValue": "/auth",
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"url.query\").value.stringValue": "",
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"http.response.status_code\").value.intValue": "401",
"batches.0.scopeSpans.0.spans.3.name": "EntryPoint",
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_SERVER",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"entry_point\").value.stringValue": "web",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"url.path\").value.stringValue": "/auth",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"url.query\").value.stringValue": "",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.response.status_code\").value.intValue": "401",
},
}
s.checkTraceContent(contains)
}
func (s *TracingSuite) TestOpentelemetrySafeURL() {
func (s *TracingSuite) TestOpenTelemetryAuthWithRetry() {
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
WhoamiIP: s.whoamiIP,
WhoamiPort: s.whoamiPort,
IP: s.otelCollectorIP,
})
defer os.Remove(file)
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/retry-auth", 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized))
require.NoError(s.T(), err)
contains := []map[string]string{
{
"batches.0.scopeSpans.0.scope.name": "github.com/traefik/traefik",
"batches.0.scopeSpans.0.spans.0.name": "BasicAuth",
"batches.0.scopeSpans.0.spans.0.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "basic-auth@file",
"batches.0.scopeSpans.0.spans.0.status.message": "Authentication failed",
"batches.0.scopeSpans.0.spans.0.status.code": "STATUS_CODE_ERROR",
"batches.0.scopeSpans.0.spans.1.name": "Retry",
"batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
"batches.0.scopeSpans.0.spans.2.name": "Router",
"batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.service.name\").value.stringValue": "service4@file",
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.router.name\").value.stringValue": "router4@file",
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"http.route\").value.stringValue": "Path(`/retry-auth`)",
"batches.0.scopeSpans.0.spans.3.name": "Metrics",
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
"batches.0.scopeSpans.0.spans.4.name": "EntryPoint",
"batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_SERVER",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"entry_point\").value.stringValue": "web",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"url.path\").value.stringValue": "/retry-auth",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"url.query\").value.stringValue": "",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"http.response.status_code\").value.intValue": "401",
},
}
s.checkTraceContent(contains)
}
func (s *TracingSuite) TestOpenTelemetrySafeURL() {
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
WhoamiIP: s.whoamiIP,
WhoamiPort: s.whoamiPort,
@@ -593,30 +598,26 @@ func (s *TracingSuite) TestOpentelemetrySafeURL() {
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "basic-auth@file",
"batches.0.scopeSpans.0.spans.4.name": "Retry",
"batches.0.scopeSpans.0.spans.4.name": "Router",
"batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.service.name\").value.stringValue": "service3@file",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.router.name\").value.stringValue": "router3@file",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"http.route\").value.stringValue": "Path(`/auth`)",
"batches.0.scopeSpans.0.spans.5.name": "Router",
"batches.0.scopeSpans.0.spans.5.name": "Metrics",
"batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.service.name\").value.stringValue": "service3@file",
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.router.name\").value.stringValue": "router3@file",
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"http.route\").value.stringValue": "Path(`/auth`)",
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
"batches.0.scopeSpans.0.spans.6.name": "Metrics",
"batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
"batches.0.scopeSpans.0.spans.7.name": "EntryPoint",
"batches.0.scopeSpans.0.spans.7.kind": "SPAN_KIND_SERVER",
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"entry_point\").value.stringValue": "web",
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"url.path\").value.stringValue": "/auth",
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"url.query\").value.stringValue": "api_key=REDACTED",
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"http.response.status_code\").value.intValue": "200",
"batches.0.scopeSpans.0.spans.6.name": "EntryPoint",
"batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_SERVER",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"entry_point\").value.stringValue": "web",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"url.path\").value.stringValue": "/auth",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"url.query\").value.stringValue": "api_key=REDACTED",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.response.status_code\").value.intValue": "200",
},
}

View File

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

View File

@@ -29,14 +29,10 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
assets = webui.FS
}
// allow iframes from traefik domains only
// Allow iframes from traefik domains only.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src
w.Header().Set("Content-Security-Policy", "frame-src 'self' https://traefik.io https://*.traefik.io;")
// The content type must be guessed by the file server.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
w.Header().Del("Content-Type")
if r.RequestURI == "/" {
indexTemplate, err := template.ParseFS(assets, "index.html")
if err != nil {
@@ -45,6 +41,8 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
apiPath := strings.TrimSuffix(h.BasePath, "/") + "/api/"
if err = indexTemplate.Execute(w, indexTemplateData{APIUrl: apiPath}); err != nil {
log.Error().Err(err).Msg("Unable to render index template")
@@ -55,6 +53,10 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
// The content type must be guessed by the file server.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
w.Header().Del("Content-Type")
http.FileServerFS(assets).ServeHTTP(w, r)
}
@@ -84,13 +86,11 @@ func Append(router *mux.Router, basePath string, customAssets fs.FS) error {
router.Methods(http.MethodGet).
Path(dashboardPath).
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// allow iframes from our domains only
// Allow iframes from our domains only.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src
w.Header().Set("Content-Security-Policy", "frame-src 'self' https://traefik.io https://*.traefik.io;")
// The content type must be guessed by the file server.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
w.Header().Del("Content-Type")
w.Header().Set("Content-Type", "text/html; charset=utf-8")
apiPath := strings.TrimSuffix(basePath, "/") + "/api/"
if err = indexTemplate.Execute(w, indexTemplateData{APIUrl: apiPath}); err != nil {
@@ -103,7 +103,7 @@ func Append(router *mux.Router, basePath string, customAssets fs.FS) error {
router.Methods(http.MethodGet).
PathPrefix(dashboardPath).
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// allow iframes from traefik domains only
// Allow iframes from traefik domains only.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src
w.Header().Set("Content-Security-Policy", "frame-src 'self' https://traefik.io https://*.traefik.io;")
@@ -113,5 +113,6 @@ func Append(router *mux.Router, basePath string, customAssets fs.FS) error {
http.StripPrefix(dashboardPath, http.FileServerFS(assets)).ServeHTTP(w, r)
})
return nil
}

View File

@@ -1,10 +1,9 @@
package api
import (
"cmp"
"net/url"
"sort"
"golang.org/x/exp/constraints"
)
const (
@@ -357,7 +356,7 @@ func sortByName[T orderedWithName](direction string, results []T) {
})
}
func sortByFunc[T orderedWithName, U constraints.Ordered](direction string, results []T, fn func(int) U) {
func sortByFunc[T orderedWithName, U cmp.Ordered](direction string, results []T, fn func(int) U) {
// Ascending
if direction == ascendantSorting {
sort.Slice(results, func(i, j int) bool {

View File

@@ -252,7 +252,7 @@ type ForwardAuth struct {
// AddAuthCookiesToResponse defines the list of cookies to copy from the authentication server response to the response.
AddAuthCookiesToResponse []string `json:"addAuthCookiesToResponse,omitempty" toml:"addAuthCookiesToResponse,omitempty" yaml:"addAuthCookiesToResponse,omitempty" export:"true"`
// HeaderField defines a header field to store the authenticated user.
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/#headerfield
// More info: https://doc.traefik.io/traefik/v3.3/middlewares/http/forwardauth/#headerfield
HeaderField string `json:"headerField,omitempty" toml:"headerField,omitempty" yaml:"headerField,omitempty" export:"true"`
// ForwardBody defines whether to send the request body to the authentication server.
ForwardBody bool `json:"forwardBody,omitempty" toml:"forwardBody,omitempty" yaml:"forwardBody,omitempty" export:"true"`

View File

@@ -60,8 +60,6 @@ func (ep *EntryPoint) SetDefaults() {
ep.HTTP.SetDefaults()
ep.HTTP2 = &HTTP2Config{}
ep.HTTP2.SetDefaults()
ep.Observability = &ObservabilityConfig{}
ep.Observability.SetDefaults()
}
// HTTPConfig is the HTTP configuration of an entry point.
@@ -164,14 +162,15 @@ func (u *UDPConfig) SetDefaults() {
// ObservabilityConfig holds the observability configuration for an entry point.
type ObservabilityConfig struct {
AccessLogs bool `json:"accessLogs,omitempty" toml:"accessLogs,omitempty" yaml:"accessLogs,omitempty" export:"true"`
Tracing bool `json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" export:"true"`
Metrics bool `json:"metrics,omitempty" toml:"metrics,omitempty" yaml:"metrics,omitempty" export:"true"`
AccessLogs *bool `json:"accessLogs,omitempty" toml:"accessLogs,omitempty" yaml:"accessLogs,omitempty" export:"true"`
Tracing *bool `json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" export:"true"`
Metrics *bool `json:"metrics,omitempty" toml:"metrics,omitempty" yaml:"metrics,omitempty" export:"true"`
}
// SetDefaults sets the default values.
func (o *ObservabilityConfig) SetDefaults() {
o.AccessLogs = true
o.Tracing = true
o.Metrics = true
defaultValue := true
o.AccessLogs = &defaultValue
o.Tracing = &defaultValue
o.Metrics = &defaultValue
}

View File

@@ -321,7 +321,7 @@ func (c *Configuration) SetEffectiveConfiguration() {
}
if resolver.ACME.DNSChallenge.DisablePropagationCheck {
log.Warn().Msgf("disablePropagationCheck is now deprecated, please use propagation.disableAllChecks instead.")
log.Warn().Msgf("disablePropagationCheck is now deprecated, please use propagation.disableChecks instead.")
if resolver.ACME.DNSChallenge.Propagation == nil {
resolver.ACME.DNSChallenge.Propagation = &acmeprovider.Propagation{}
@@ -331,7 +331,7 @@ func (c *Configuration) SetEffectiveConfiguration() {
}
if resolver.ACME.DNSChallenge.DelayBeforeCheck > 0 {
log.Warn().Msgf("delayBeforeCheck is now deprecated, please use propagation.delayBeforeCheck instead.")
log.Warn().Msgf("delayBeforeCheck is now deprecated, please use propagation.delayBeforeChecks instead.")
if resolver.ACME.DNSChallenge.Propagation == nil {
resolver.ACME.DNSChallenge.Propagation = &acmeprovider.Propagation{}

View File

@@ -77,11 +77,6 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) {
UDP: &UDPConfig{
Timeout: 3000000000,
},
Observability: &ObservabilityConfig{
AccessLogs: true,
Tracing: true,
Metrics: true,
},
}},
Providers: &Providers{},
},
@@ -127,11 +122,6 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) {
UDP: &UDPConfig{
Timeout: 3000000000,
},
Observability: &ObservabilityConfig{
AccessLogs: true,
Tracing: true,
Metrics: true,
},
}},
Providers: &Providers{},
CertificatesResolvers: map[string]CertificateResolver{
@@ -188,11 +178,6 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) {
UDP: &UDPConfig{
Timeout: 3000000000,
},
Observability: &ObservabilityConfig{
AccessLogs: true,
Tracing: true,
Metrics: true,
},
}},
Providers: &Providers{},
CertificatesResolvers: map[string]CertificateResolver{
@@ -253,11 +238,6 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) {
UDP: &UDPConfig{
Timeout: 3000000000,
},
Observability: &ObservabilityConfig{
AccessLogs: true,
Tracing: true,
Metrics: true,
},
}},
Providers: &Providers{},
CertificatesResolvers: map[string]CertificateResolver{

View File

@@ -11,7 +11,6 @@ const (
ServiceName = "serviceName"
MetricsProviderName = "metricsProviderName"
TracingProviderName = "tracingProviderName"
ServerName = "serverName"
ServerIndex = "serverIndex"
TLSStoreName = "tlsStoreName"
ServersTransportName = "serversTransport"

View File

@@ -132,7 +132,7 @@ func RegisterOpenTelemetry(ctx context.Context, config *types.OTLP) Registry {
"How many HTTP requests with TLS processed on an entrypoint, partitioned by TLS Version and TLS cipher Used.")
reg.entryPointReqDurationHistogram, _ = NewHistogramWithScale(newOTLPHistogramFrom(meter, entryPointReqDurationName,
"How long it took to process the request on an entrypoint, partitioned by status code, protocol, and method.",
"ms"), time.Second)
"s"), time.Second)
reg.entryPointReqsBytesCounter = newOTLPCounterFrom(meter, entryPointReqsBytesTotalName,
"The total size of requests in bytes handled by an entrypoint, partitioned by status code, protocol, and method.")
reg.entryPointRespsBytesCounter = newOTLPCounterFrom(meter, entryPointRespsBytesTotalName,
@@ -146,7 +146,7 @@ func RegisterOpenTelemetry(ctx context.Context, config *types.OTLP) Registry {
"How many HTTP requests with TLS are processed on a router, partitioned by service, TLS Version, and TLS cipher Used.")
reg.routerReqDurationHistogram, _ = NewHistogramWithScale(newOTLPHistogramFrom(meter, routerReqDurationName,
"How long it took to process the request on a router, partitioned by service, status code, protocol, and method.",
"ms"), time.Second)
"s"), time.Second)
reg.routerReqsBytesCounter = newOTLPCounterFrom(meter, routerReqsBytesTotalName,
"The total size of requests in bytes handled by a router, partitioned by status code, protocol, and method.")
reg.routerRespsBytesCounter = newOTLPCounterFrom(meter, routerRespsBytesTotalName,
@@ -160,7 +160,7 @@ func RegisterOpenTelemetry(ctx context.Context, config *types.OTLP) Registry {
"How many HTTP requests with TLS processed on a service, partitioned by TLS version and TLS cipher.")
reg.serviceReqDurationHistogram, _ = NewHistogramWithScale(newOTLPHistogramFrom(meter, serviceReqDurationName,
"How long it took to process the request on a service, partitioned by status code, protocol, and method.",
"ms"), time.Second)
"s"), time.Second)
reg.serviceRetriesCounter = newOTLPCounterFrom(meter, serviceRetriesTotalName,
"How many request retries happened on a service.")
reg.serviceServerUpGauge = newOTLPGaugeFrom(meter, serviceServerUpName,

View File

@@ -376,7 +376,7 @@ func TestOpenTelemetry(t *testing.T) {
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","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.075,0.1,0.25,0.5,0.75,1,2.5,5,7.5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`,
`({"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":"s","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","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.075,0.1,0.25,0.5,0.75,1,2.5,5,7.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}})`,
}
@@ -392,7 +392,7 @@ func TestOpenTelemetry(t *testing.T) {
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","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.075,0.1,0.25,0.5,0.75,1,2.5,5,7.5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`,
`({"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":"s","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","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.075,0.1,0.25,0.5,0.75,1,2.5,5,7.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}})`,
}
@@ -409,7 +409,7 @@ func TestOpenTelemetry(t *testing.T) {
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","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.075,0.1,0.25,0.5,0.75,1,2.5,5,7.5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`,
`({"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":"s","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","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.075,0.1,0.25,0.5,0.75,1,2.5,5,7.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"}}\],"startTimeUnixNano":"[\d]{19}","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}})`,

View File

@@ -24,6 +24,7 @@ import (
traefiktls "github.com/traefik/traefik/v3/pkg/tls"
"github.com/traefik/traefik/v3/pkg/types"
"go.opentelemetry.io/contrib/bridges/otellogrus"
"go.opentelemetry.io/otel/trace"
)
type key string
@@ -209,6 +210,12 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http
},
}
if span := trace.SpanFromContext(req.Context()); span != nil {
spanContext := span.SpanContext()
logDataTable.Core[TraceID] = spanContext.TraceID().String()
logDataTable.Core[SpanID] = spanContext.SpanID().String()
}
reqWithDataTable := req.WithContext(context.WithValue(req.Context(), DataTableKey, logDataTable))
core[RequestCount] = nextRequestCount()

View File

@@ -28,6 +28,7 @@ import (
"github.com/traefik/traefik/v3/pkg/types"
"go.opentelemetry.io/collector/pdata/plog/plogotlp"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
)
const delta float64 = 1e-10
@@ -409,6 +410,8 @@ func TestLoggerJSON(t *testing.T) {
"time": assertNotEmpty(),
"StartLocal": assertNotEmpty(),
"StartUTC": assertNotEmpty(),
TraceID: assertNotEmpty(),
SpanID: assertNotEmpty(),
},
},
{
@@ -452,6 +455,8 @@ func TestLoggerJSON(t *testing.T) {
"time": assertNotEmpty(),
StartLocal: assertNotEmpty(),
StartUTC: assertNotEmpty(),
TraceID: assertNotEmpty(),
SpanID: assertNotEmpty(),
},
},
{
@@ -627,6 +632,8 @@ func TestLogger_AbortedRequest(t *testing.T) {
"downstream_Content-Type": assertString("text/plain"),
"downstream_Transfer-Encoding": assertString("chunked"),
"downstream_Cache-Control": assertString("no-cache"),
TraceID: assertNotEmpty(),
SpanID: assertNotEmpty(),
}
config := &types.AccessLog{
@@ -945,6 +952,10 @@ func doLoggingTLSOpt(t *testing.T, config *types.AccessLog, enableTLS bool) {
}
}
tracer := noop.Tracer{}
spanCtx, _ := tracer.Start(req.Context(), "test")
req = req.WithContext(spanCtx)
chain := alice.New()
chain = chain.Append(capture.Wrap)
chain = chain.Append(WrapHandler(logger))

View File

@@ -8,7 +8,6 @@ import (
"github.com/containous/alice"
"github.com/rs/zerolog/log"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
"github.com/traefik/traefik/v3/pkg/tracing"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
@@ -63,12 +62,6 @@ func (e *entryPointTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request)
e.tracer.CaptureServerRequest(span, req)
if logData := accesslog.GetLogData(req); logData != nil {
spanContext := span.SpanContext()
logData.Core[accesslog.TraceID] = spanContext.TraceID().String()
logData.Core[accesslog.SpanID] = spanContext.SpanID().String()
}
recorder := newStatusCodeRecorder(rw, http.StatusOK)
e.next.ServeHTTP(recorder, req)

View File

@@ -7,7 +7,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
"github.com/traefik/traefik/v3/pkg/tracing"
"go.opentelemetry.io/otel/attribute"
)
@@ -79,27 +78,3 @@ func TestEntryPointMiddleware_tracing(t *testing.T) {
})
}
}
func TestEntryPointMiddleware_tracingInfoIntoLog(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "http://www.test.com/", http.NoBody)
req = req.WithContext(
context.WithValue(
req.Context(),
accesslog.DataTableKey,
&accesslog.LogData{Core: accesslog.CoreLogData{}},
),
)
next := http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {})
tracer := &mockTracer{}
handler := newEntryPoint(context.Background(), tracing.NewTracer(tracer, []string{}, []string{}, []string{}), "test", next)
handler.ServeHTTP(httptest.NewRecorder(), req)
expectedSpanCtx := tracer.spans[0].SpanContext()
logData := accesslog.GetLogData(req)
assert.Equal(t, expectedSpanCtx.TraceID().String(), logData.Core[accesslog.TraceID])
assert.Equal(t, expectedSpanCtx.SpanID().String(), logData.Core[accesslog.SpanID])
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -89,9 +89,9 @@ type DNSChallenge struct {
Resolvers []string `description:"Use following DNS servers to resolve the FQDN authority." json:"resolvers,omitempty" toml:"resolvers,omitempty" yaml:"resolvers,omitempty"`
Propagation *Propagation `description:"DNS propagation checks configuration" json:"propagation,omitempty" toml:"propagation,omitempty" yaml:"propagation,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
// Deprecated: please use Propagation.DelayBeforeCheck instead.
// Deprecated: please use Propagation.DelayBeforeChecks instead.
DelayBeforeCheck ptypes.Duration `description:"(Deprecated) Assume DNS propagates after a delay in seconds rather than finding and querying nameservers." json:"delayBeforeCheck,omitempty" toml:"delayBeforeCheck,omitempty" yaml:"delayBeforeCheck,omitempty" export:"true"`
// Deprecated: please use Propagation.DisableAllChecks instead.
// Deprecated: please use Propagation.DisableChecks instead.
DisablePropagationCheck bool `description:"(Deprecated) Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready. [not recommended]" json:"disablePropagationCheck,omitempty" toml:"disablePropagationCheck,omitempty" yaml:"disablePropagationCheck,omitempty" export:"true"`
}

View File

@@ -60,6 +60,7 @@ metadata:
spec:
forwardAuth:
address: test.com
headerField: X-Header-Field
tls:
certSecret: tlssecret
caSecret: casecret

View File

@@ -789,6 +789,7 @@ func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *traef
AuthResponseHeadersRegex: auth.AuthResponseHeadersRegex,
AuthRequestHeaders: auth.AuthRequestHeaders,
AddAuthCookiesToResponse: auth.AddAuthCookiesToResponse,
HeaderField: auth.HeaderField,
ForwardBody: auth.ForwardBody,
PreserveLocationHeader: auth.PreserveLocationHeader,
}

View File

@@ -3961,6 +3961,7 @@ func TestLoadIngressRoutes(t *testing.T) {
ForwardAuth: &dynamic.ForwardAuth{
Address: "test.com",
MaxBodySize: pointer(int64(-1)),
HeaderField: "X-Header-Field",
TLS: &dynamic.ClientTLS{
CA: "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----",
Cert: "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----",

View File

@@ -161,6 +161,9 @@ type ForwardAuth struct {
TLS *ClientTLS `json:"tls,omitempty"`
// AddAuthCookiesToResponse defines the list of cookies to copy from the authentication server response to the response.
AddAuthCookiesToResponse []string `json:"addAuthCookiesToResponse,omitempty"`
// HeaderField defines a header field to store the authenticated user.
// More info: https://doc.traefik.io/traefik/v3.3/middlewares/http/forwardauth/#headerfield
HeaderField string `json:"headerField,omitempty"`
// ForwardBody defines whether to send the request body to the authentication server.
ForwardBody bool `json:"forwardBody,omitempty"`
// MaxBodySize defines the maximum body size in bytes allowed to be forwarded to the authentication server.

View File

@@ -229,33 +229,32 @@ func (i *Provider) entryPointModels(cfg *dynamic.Configuration) {
}
}
if len(ep.HTTP.Middlewares) == 0 && ep.HTTP.TLS == nil && defaultRuleSyntax == "" {
if len(ep.HTTP.Middlewares) == 0 && ep.HTTP.TLS == nil && defaultRuleSyntax == "" && ep.Observability == nil {
continue
}
m := &dynamic.Model{
Middlewares: ep.HTTP.Middlewares,
httpModel := &dynamic.Model{
DefaultRuleSyntax: defaultRuleSyntax,
Middlewares: ep.HTTP.Middlewares,
}
if ep.Observability != nil {
m.Observability = dynamic.RouterObservabilityConfig{
AccessLogs: &ep.Observability.AccessLogs,
Tracing: &ep.Observability.Tracing,
Metrics: &ep.Observability.Metrics,
httpModel.Observability = dynamic.RouterObservabilityConfig{
AccessLogs: ep.Observability.AccessLogs,
Tracing: ep.Observability.Tracing,
Metrics: ep.Observability.Metrics,
}
}
if ep.HTTP.TLS != nil {
m.TLS = &dynamic.RouterTLSConfig{
httpModel.TLS = &dynamic.RouterTLSConfig{
Options: ep.HTTP.TLS.Options,
CertResolver: ep.HTTP.TLS.CertResolver,
Domains: ep.HTTP.TLS.Domains,
}
}
m.DefaultRuleSyntax = defaultRuleSyntax
cfg.HTTP.Models[name] = m
cfg.HTTP.Models[name] = httpModel
}
}

View File

@@ -18,6 +18,8 @@ import (
var updateExpected = flag.Bool("update_expected", false, "Update expected files in fixtures")
func pointer[T any](v T) *T { return &v }
func Test_createConfiguration(t *testing.T) {
testCases := []struct {
desc string
@@ -185,9 +187,9 @@ func Test_createConfiguration(t *testing.T) {
},
},
Observability: &static.ObservabilityConfig{
AccessLogs: false,
Tracing: false,
Metrics: false,
AccessLogs: pointer(false),
Tracing: pointer(false),
Metrics: pointer(false),
},
},
},

View File

@@ -20,8 +20,9 @@ import (
// rwWithUpgrade contains a ResponseWriter and an upgradeHandler,
// used to upgrade the connection (e.g. Websockets).
type rwWithUpgrade struct {
RW http.ResponseWriter
Upgrade upgradeHandler
ReqMethod string
RW http.ResponseWriter
Upgrade upgradeHandler
}
// conn is an enriched net.Conn.
@@ -178,7 +179,6 @@ func (c *conn) handleResponse(r rwWithUpgrade) error {
}
res.Reset()
res.Header.Reset()
res.Header.SetNoDefaultContentType(true)
continue
@@ -211,7 +211,7 @@ func (c *conn) handleResponse(r rwWithUpgrade) error {
r.RW.WriteHeader(res.StatusCode())
if res.Header.ContentLength() == 0 {
if noResponseBodyExpected(r.ReqMethod) {
return nil
}
@@ -221,8 +221,14 @@ func (c *conn) handleResponse(r rwWithUpgrade) error {
return nil
}
contentLength := res.Header.ContentLength()
if contentLength == 0 {
return nil
}
// Chunked response, Content-Length is set to -1 by FastProxy when "Transfer-Encoding: chunked" header is received.
if res.Header.ContentLength() == -1 {
if contentLength == -1 {
cbr := httputil.NewChunkedReader(c.br)
b := c.bufferPool.Get()
@@ -262,6 +268,23 @@ func (c *conn) handleResponse(r rwWithUpgrade) error {
return nil
}
// Response without Content-Length header.
// The message body length is determined by the number of bytes received prior to the server closing the connection.
if contentLength == -2 {
b := c.bufferPool.Get()
if b == nil {
b = make([]byte, bufferSize)
}
defer c.bufferPool.Put(b)
if _, err := io.CopyBuffer(r.RW, c.br, b); err != nil {
return err
}
return nil
}
// Response with a valid Content-Length header.
brl := c.limitedReaderPool.Get()
if brl == nil {
brl = &io.LimitedReader{}
@@ -444,8 +467,8 @@ func (c *connPool) askForNewConn(errCh chan<- error) {
c.releaseConn(newConn)
}
// isBodyAllowedForStatus reports whether a given response status code
// permits a body. See RFC 7230, section 3.3.
// isBodyAllowedForStatus reports whether a given response status code permits a body.
// See RFC 7230, section 3.3.
// From https://github.com/golang/go/blame/master/src/net/http/transfer.go#L459
func isBodyAllowedForStatus(status int) bool {
switch {
@@ -458,3 +481,9 @@ func isBodyAllowedForStatus(status int) bool {
}
return true
}
// noResponseBodyExpected reports whether a given request method permits a body.
// From https://github.com/golang/go/blame/master/src/net/http/transfer.go#L250
func noResponseBodyExpected(requestMethod string) bool {
return requestMethod == "HEAD"
}

View File

@@ -171,6 +171,7 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if reqUpType != "" {
outReq.Header.Set("Connection", "Upgrade")
outReq.Header.Set("Upgrade", reqUpType)
if strings.EqualFold(reqUpType, "websocket") {
cleanWebSocketHeaders(&outReq.Header)
}
@@ -284,8 +285,9 @@ func (p *ReverseProxy) roundTrip(rw http.ResponseWriter, req *http.Request, outR
// Sending the responseWriter unlocks the connection readLoop, to handle the response.
co.RWCh <- rwWithUpgrade{
RW: rw,
Upgrade: upgradeResponseHandler(req.Context(), reqUpType),
ReqMethod: req.Method,
RW: rw,
Upgrade: upgradeResponseHandler(req.Context(), reqUpType),
}
if err := <-co.ErrCh; err != nil {
@@ -350,7 +352,7 @@ func isGraphic(s string) bool {
type fasthttpHeader interface {
Peek(key string) []byte
Set(key string, value string)
SetBytesV(key string, value []byte)
SetCanonical(key []byte, value []byte)
DelBytes(key []byte)
Del(key string)
ConnectionUpgrade() bool
@@ -381,18 +383,33 @@ func fixPragmaCacheControl(header fasthttpHeader) {
// Sec-WebSocket-Protocol and Sec-WebSocket-Version to be case-sensitive.
// https://tools.ietf.org/html/rfc6455#page-20
func cleanWebSocketHeaders(headers fasthttpHeader) {
headers.SetBytesV("Sec-WebSocket-Key", headers.Peek("Sec-Websocket-Key"))
headers.Del("Sec-Websocket-Key")
secWebsocketKey := headers.Peek("Sec-Websocket-Key")
if len(secWebsocketKey) > 0 {
headers.SetCanonical([]byte("Sec-WebSocket-Key"), secWebsocketKey)
headers.Del("Sec-Websocket-Key")
}
headers.SetBytesV("Sec-WebSocket-Extensions", headers.Peek("Sec-Websocket-Extensions"))
headers.Del("Sec-Websocket-Extensions")
secWebsocketExtensions := headers.Peek("Sec-Websocket-Extensions")
if len(secWebsocketExtensions) > 0 {
headers.SetCanonical([]byte("Sec-WebSocket-Extensions"), secWebsocketExtensions)
headers.Del("Sec-Websocket-Extensions")
}
headers.SetBytesV("Sec-WebSocket-Accept", headers.Peek("Sec-Websocket-Accept"))
headers.Del("Sec-Websocket-Accept")
secWebsocketAccept := headers.Peek("Sec-Websocket-Accept")
if len(secWebsocketAccept) > 0 {
headers.SetCanonical([]byte("Sec-WebSocket-Accept"), secWebsocketAccept)
headers.Del("Sec-Websocket-Accept")
}
headers.SetBytesV("Sec-WebSocket-Protocol", headers.Peek("Sec-Websocket-Protocol"))
headers.Del("Sec-Websocket-Protocol")
secWebsocketProtocol := headers.Peek("Sec-Websocket-Protocol")
if len(secWebsocketProtocol) > 0 {
headers.SetCanonical([]byte("Sec-WebSocket-Protocol"), secWebsocketProtocol)
headers.Del("Sec-Websocket-Protocol")
}
headers.SetBytesV("Sec-WebSocket-Version", headers.Peek("Sec-Websocket-Version"))
headers.DelBytes([]byte("Sec-Websocket-Version"))
secWebsocketVersion := headers.Peek("Sec-Websocket-Version")
if len(secWebsocketVersion) > 0 {
headers.SetCanonical([]byte("Sec-WebSocket-Version"), secWebsocketVersion)
headers.Del("Sec-Websocket-Version")
}
}

View File

@@ -278,6 +278,113 @@ func TestPreservePath(t *testing.T) {
assert.Equal(t, http.StatusOK, res.Code)
}
func TestHeadRequest(t *testing.T) {
var callCount int
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
callCount++
assert.Equal(t, http.MethodHead, req.Method)
rw.Header().Set("Content-Length", "42")
}))
t.Cleanup(server.Close)
builder := NewProxyBuilder(&transportManagerMock{}, static.FastProxyConfig{})
serverURL, err := url.JoinPath(server.URL)
require.NoError(t, err)
proxyHandler, err := builder.Build("", testhelpers.MustParseURL(serverURL), true, true)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodHead, "/", http.NoBody)
res := httptest.NewRecorder()
proxyHandler.ServeHTTP(res, req)
assert.Equal(t, 1, callCount)
assert.Equal(t, http.StatusOK, res.Code)
}
func TestNoContentLength(t *testing.T) {
backendListener, err := net.Listen("tcp", ":0")
require.NoError(t, err)
t.Cleanup(func() {
_ = backendListener.Close()
})
go func() {
t.Helper()
conn, err := backendListener.Accept()
require.NoError(t, err)
_, err = conn.Write([]byte("HTTP/1.1 200 OK\r\n\r\nfoo"))
require.NoError(t, err)
// CloseWrite the connection to signal the end of the response.
if v, ok := conn.(interface{ CloseWrite() error }); ok {
err = v.CloseWrite()
require.NoError(t, err)
}
}()
builder := NewProxyBuilder(&transportManagerMock{}, static.FastProxyConfig{})
serverURL := "http://" + backendListener.Addr().String()
proxyHandler, err := builder.Build("", testhelpers.MustParseURL(serverURL), true, true)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
res := httptest.NewRecorder()
proxyHandler.ServeHTTP(res, req)
assert.Equal(t, http.StatusOK, res.Code)
assert.Equal(t, "foo", res.Body.String())
}
func TestTransferEncodingChunked(t *testing.T) {
backendServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
flusher, ok := rw.(http.Flusher)
require.True(t, ok)
for i := range 3 {
_, err := rw.Write([]byte(fmt.Sprintf("chunk %d\n", i)))
require.NoError(t, err)
flusher.Flush()
}
}))
t.Cleanup(backendServer.Close)
builder := NewProxyBuilder(&transportManagerMock{}, static.FastProxyConfig{})
proxyHandler, err := builder.Build("", testhelpers.MustParseURL(backendServer.URL), true, true)
require.NoError(t, err)
proxyServer := httptest.NewServer(proxyHandler)
t.Cleanup(proxyServer.Close)
req, err := http.NewRequest(http.MethodGet, proxyServer.URL, http.NoBody)
require.NoError(t, err)
res, err := http.DefaultClient.Do(req)
require.NoError(t, err)
t.Cleanup(func() { _ = res.Body.Close() })
assert.Equal(t, http.StatusOK, res.StatusCode)
assert.Equal(t, []string{"chunked"}, res.TransferEncoding)
body, err := io.ReadAll(res.Body)
require.NoError(t, err)
assert.Equal(t, "chunk 0\nchunk 1\nchunk 2\n", string(body))
}
func newCertificate(t *testing.T, domain string) *tls.Certificate {
t.Helper()

View File

@@ -18,9 +18,12 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/traefik/traefik/v3/pkg/testhelpers"
"github.com/valyala/fasthttp"
"golang.org/x/net/websocket"
)
const dialTimeout = time.Second
func TestWebSocketUpgradeCase(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
challengeKey := r.Header.Get("Sec-Websocket-Key")
@@ -49,6 +52,31 @@ func TestWebSocketUpgradeCase(t *testing.T) {
conn.Close()
}
func TestCleanWebSocketHeaders(t *testing.T) {
// Asserts that no headers are sent if the request contain anything.
req := fasthttp.AcquireRequest()
defer fasthttp.ReleaseRequest(req)
cleanWebSocketHeaders(&req.Header)
want := "GET / HTTP/1.1\r\n\r\n"
assert.Equal(t, want, req.Header.String())
// Asserts that the Sec-WebSocket-* is enforced.
req.Reset()
req.Header.Set("Sec-Websocket-Key", "key")
req.Header.Set("Sec-Websocket-Extensions", "extensions")
req.Header.Set("Sec-Websocket-Accept", "accept")
req.Header.Set("Sec-Websocket-Protocol", "protocol")
req.Header.Set("Sec-Websocket-Version", "version")
cleanWebSocketHeaders(&req.Header)
want = "GET / HTTP/1.1\r\nSec-WebSocket-Key: key\r\nSec-WebSocket-Extensions: extensions\r\nSec-WebSocket-Accept: accept\r\nSec-WebSocket-Protocol: protocol\r\nSec-WebSocket-Version: version\r\n\r\n"
assert.Equal(t, want, req.Header.String())
}
func TestWebSocketTCPClose(t *testing.T) {
errChan := make(chan error, 1)
upgrader := gorillawebsocket.Upgrader{}
@@ -535,29 +563,6 @@ func TestForwardsWebsocketTraffic(t *testing.T) {
assert.Equal(t, "ok", resp)
}
func createTLSWebsocketServer() *httptest.Server {
upgrader := gorillawebsocket.Upgrader{}
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
defer conn.Close()
for {
mt, message, err := conn.ReadMessage()
if err != nil {
break
}
err = conn.WriteMessage(mt, message)
if err != nil {
break
}
}
}))
return srv
}
func TestWebSocketTransferTLSConfig(t *testing.T) {
srv := createTLSWebsocketServer()
defer srv.Close()
@@ -592,7 +597,28 @@ func TestWebSocketTransferTLSConfig(t *testing.T) {
assert.Equal(t, "ok", resp)
}
const dialTimeout = time.Second
func createTLSWebsocketServer() *httptest.Server {
upgrader := gorillawebsocket.Upgrader{}
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
defer conn.Close()
for {
mt, message, err := conn.ReadMessage()
if err != nil {
break
}
err = conn.WriteMessage(mt, message)
if err != nil {
break
}
}
}))
return srv
}
type websocketRequestOpt func(w *websocketRequest)

View File

@@ -70,7 +70,9 @@ func directorBuilder(target *url.URL, passHostHeader bool, preservePath bool) fu
outReq.Host = outReq.URL.Host
}
cleanWebSocketHeaders(outReq)
if isWebSocketUpgrade(outReq) {
cleanWebSocketHeaders(outReq)
}
}
}
@@ -79,10 +81,6 @@ func directorBuilder(target *url.URL, passHostHeader bool, preservePath bool) fu
// Sec-WebSocket-Protocol and Sec-WebSocket-Version to be case-sensitive.
// https://tools.ietf.org/html/rfc6455#page-20
func cleanWebSocketHeaders(req *http.Request) {
if !isWebSocketUpgrade(req) {
return
}
req.Header["Sec-WebSocket-Key"] = req.Header["Sec-Websocket-Key"]
delete(req.Header, "Sec-Websocket-Key")

View File

@@ -2,6 +2,7 @@ package httputil
import (
"bufio"
"bytes"
"crypto/tls"
"errors"
"fmt"
@@ -18,6 +19,8 @@ import (
"golang.org/x/net/websocket"
)
const dialTimeout = time.Second
func TestWebSocketTCPClose(t *testing.T) {
errChan := make(chan error, 1)
upgrader := gorillawebsocket.Upgrader{}
@@ -419,28 +422,6 @@ func TestForwardsWebsocketTraffic(t *testing.T) {
assert.Equal(t, "ok", resp)
}
func createTLSWebsocketServer() *httptest.Server {
upgrader := gorillawebsocket.Upgrader{}
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
defer conn.Close()
for {
mt, message, err := conn.ReadMessage()
if err != nil {
break
}
err = conn.WriteMessage(mt, message)
if err != nil {
break
}
}
}))
return srv
}
func TestWebSocketTransferTLSConfig(t *testing.T) {
srv := createTLSWebsocketServer()
defer srv.Close()
@@ -495,7 +476,58 @@ func TestWebSocketTransferTLSConfig(t *testing.T) {
assert.Equal(t, "ok", resp)
}
const dialTimeout = time.Second
func TestCleanWebSocketHeaders(t *testing.T) {
// Asserts that no headers are sent if the request contain anything.
req := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
req.Header.Del("User-Agent")
cleanWebSocketHeaders(req)
b := bytes.NewBuffer(nil)
err := req.Header.Write(b)
require.NoError(t, err)
assert.Empty(t, b)
// Asserts that the Sec-WebSocket-* is enforced.
req.Header.Set("Sec-Websocket-Key", "key")
req.Header.Set("Sec-Websocket-Extensions", "extensions")
req.Header.Set("Sec-Websocket-Accept", "accept")
req.Header.Set("Sec-Websocket-Protocol", "protocol")
req.Header.Set("Sec-Websocket-Version", "version")
cleanWebSocketHeaders(req)
want := http.Header{
"Sec-WebSocket-Key": {"key"},
"Sec-WebSocket-Extensions": {"extensions"},
"Sec-WebSocket-Accept": {"accept"},
"Sec-WebSocket-Protocol": {"protocol"},
"Sec-WebSocket-Version": {"version"},
}
assert.Equal(t, want, req.Header)
}
func createTLSWebsocketServer() *httptest.Server {
var upgrader gorillawebsocket.Upgrader
return httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
defer conn.Close()
for {
mt, message, err := conn.ReadMessage()
if err != nil {
break
}
err = conn.WriteMessage(mt, message)
if err != nil {
break
}
}
}))
}
type websocketRequestOpt func(w *websocketRequest)

View File

@@ -191,14 +191,14 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
cp.Observability.AccessLogs = m.Observability.AccessLogs
}
if cp.Observability.Tracing == nil {
cp.Observability.Tracing = m.Observability.Tracing
}
if cp.Observability.Metrics == nil {
cp.Observability.Metrics = m.Observability.Metrics
}
if cp.Observability.Tracing == nil {
cp.Observability.Tracing = m.Observability.Tracing
}
rtName := name
if len(eps) > 1 {
rtName = epName + "-" + name
@@ -215,6 +215,9 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
cfg.HTTP.Routers = rts
}
// Apply default observability model to HTTP routers.
applyDefaultObservabilityModel(cfg)
if cfg.TCP == nil || len(cfg.TCP.Models) == 0 {
return cfg
}
@@ -238,3 +241,38 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
return cfg
}
// applyDefaultObservabilityModel applies the default observability model to the configuration.
// This function is used to ensure that the observability configuration is set for all routers,
// and make sure it is serialized and available in the API.
// We could have introduced a "default" model, but it would have been more complex to manage for now.
// This could be generalized in the future.
func applyDefaultObservabilityModel(cfg dynamic.Configuration) {
if cfg.HTTP != nil {
for _, router := range cfg.HTTP.Routers {
if router.Observability == nil {
router.Observability = &dynamic.RouterObservabilityConfig{
AccessLogs: pointer(true),
Metrics: pointer(true),
Tracing: pointer(true),
}
continue
}
if router.Observability.AccessLogs == nil {
router.Observability.AccessLogs = pointer(true)
}
if router.Observability.Tracing == nil {
router.Observability.Tracing = pointer(true)
}
if router.Observability.Metrics == nil {
router.Observability.Metrics = pointer(true)
}
}
}
}
func pointer[T any](v T) *T { return &v }

View File

@@ -9,8 +9,6 @@ import (
"github.com/traefik/traefik/v3/pkg/tls"
)
func pointer[T any](v T) *T { return &v }
func Test_mergeConfiguration(t *testing.T) {
testCases := []struct {
desc string
@@ -508,6 +506,33 @@ func Test_applyModel(t *testing.T) {
},
},
},
{
desc: "without model, one router",
input: dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{"test": {}},
Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service),
Models: make(map[string]*dynamic.Model),
},
},
expected: dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"test": {
Observability: &dynamic.RouterObservabilityConfig{
AccessLogs: pointer(true),
Metrics: pointer(true),
Tracing: pointer(true),
},
},
},
Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service),
Models: make(map[string]*dynamic.Model),
},
},
},
{
desc: "with model, not used",
input: dynamic.Configuration{
@@ -560,10 +585,14 @@ func Test_applyModel(t *testing.T) {
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"test": {
EntryPoints: []string{"websecure"},
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{},
Observability: &dynamic.RouterObservabilityConfig{},
EntryPoints: []string{"websecure"},
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{},
Observability: &dynamic.RouterObservabilityConfig{
AccessLogs: pointer(true),
Metrics: pointer(true),
Tracing: pointer(true),
},
},
},
Middlewares: make(map[string]*dynamic.Middleware),
@@ -659,9 +688,9 @@ func Test_applyModel(t *testing.T) {
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{CertResolver: "router"},
Observability: &dynamic.RouterObservabilityConfig{
AccessLogs: nil,
Tracing: nil,
Metrics: nil,
AccessLogs: pointer(true),
Metrics: pointer(true),
Tracing: pointer(true),
},
},
},
@@ -700,12 +729,21 @@ func Test_applyModel(t *testing.T) {
Routers: map[string]*dynamic.Router{
"test": {
EntryPoints: []string{"web"},
Observability: &dynamic.RouterObservabilityConfig{
AccessLogs: pointer(true),
Metrics: pointer(true),
Tracing: pointer(true),
},
},
"websecure-test": {
EntryPoints: []string{"websecure"},
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{},
Observability: &dynamic.RouterObservabilityConfig{},
EntryPoints: []string{"websecure"},
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{},
Observability: &dynamic.RouterObservabilityConfig{
AccessLogs: pointer(true),
Metrics: pointer(true),
Tracing: pointer(true),
},
},
},
Middlewares: make(map[string]*dynamic.Middleware),

View File

@@ -84,7 +84,8 @@ func TestNewConfigurationWatcher(t *testing.T) {
th.WithRouters(
th.WithRouter("test@mock",
th.WithEntryPoints("e"),
th.WithServiceName("scv"))),
th.WithServiceName("scv"),
th.WithObservability())),
th.WithMiddlewares(),
th.WithLoadBalancerServices(),
),
@@ -175,7 +176,7 @@ func TestIgnoreTransientConfiguration(t *testing.T) {
expectedConfig := dynamic.Configuration{
HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))),
th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"), th.WithObservability())),
th.WithLoadBalancerServices(th.WithService("bar@mock")),
th.WithMiddlewares(),
),
@@ -200,7 +201,7 @@ func TestIgnoreTransientConfiguration(t *testing.T) {
expectedConfig3 := dynamic.Configuration{
HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))),
th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"), th.WithObservability())),
th.WithLoadBalancerServices(th.WithService("bar-config3@mock")),
th.WithMiddlewares(),
),
@@ -447,7 +448,7 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) {
expected := dynamic.Configuration{
HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))),
th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"), th.WithObservability())),
th.WithLoadBalancerServices(th.WithService("bar@mock")),
th.WithMiddlewares(),
),
@@ -538,7 +539,7 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) {
expected := dynamic.Configuration{
HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))),
th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"), th.WithObservability())),
th.WithLoadBalancerServices(th.WithService("bar@mock")),
th.WithMiddlewares(),
),
@@ -674,7 +675,7 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) {
expected := dynamic.Configuration{
HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("final@mock", th.WithEntryPoints("ep"))),
th.WithRouters(th.WithRouter("final@mock", th.WithEntryPoints("ep"), th.WithObservability())),
th.WithLoadBalancerServices(th.WithService("final@mock")),
th.WithMiddlewares(),
),
@@ -738,8 +739,8 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) {
expected := dynamic.Configuration{
HTTP: th.BuildConfiguration(
th.WithRouters(
th.WithRouter("foo@mock", th.WithEntryPoints("ep")),
th.WithRouter("foo@mock2", th.WithEntryPoints("ep")),
th.WithRouter("foo@mock", th.WithEntryPoints("ep"), th.WithObservability()),
th.WithRouter("foo@mock2", th.WithEntryPoints("ep"), th.WithObservability()),
),
th.WithLoadBalancerServices(
th.WithService("bar@mock"),

View File

@@ -110,7 +110,7 @@ func (o *ObservabilityMgr) ShouldAddAccessLogs(serviceName string, observability
return false
}
return observabilityConfig == nil || observabilityConfig.AccessLogs != nil && *observabilityConfig.AccessLogs
return observabilityConfig == nil || observabilityConfig.AccessLogs == nil || *observabilityConfig.AccessLogs
}
// ShouldAddMetrics returns whether the metrics should be enabled for the given resource and the observability config.
@@ -127,7 +127,7 @@ func (o *ObservabilityMgr) ShouldAddMetrics(serviceName string, observabilityCon
return false
}
return observabilityConfig == nil || observabilityConfig.Metrics != nil && *observabilityConfig.Metrics
return observabilityConfig == nil || observabilityConfig.Metrics == nil || *observabilityConfig.Metrics
}
// ShouldAddTracing returns whether the tracing should be enabled for the given serviceName and the observability config.
@@ -144,7 +144,7 @@ func (o *ObservabilityMgr) ShouldAddTracing(serviceName string, observabilityCon
return false
}
return observabilityConfig == nil || observabilityConfig.Tracing != nil && *observabilityConfig.Tracing
return observabilityConfig == nil || observabilityConfig.Tracing == nil || *observabilityConfig.Tracing
}
// MetricsRegistry is an accessor to the metrics registry.

View File

@@ -3,6 +3,8 @@ package wrr
import (
"container/heap"
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"hash/fnv"
"net/http"
@@ -15,9 +17,10 @@ import (
type namedHandler struct {
http.Handler
name string
weight float64
deadline float64
name string
hashedName string
weight float64
deadline float64
}
type stickyCookie struct {
@@ -53,9 +56,10 @@ type Balancer struct {
handlersMu sync.RWMutex
// References all the handlers by name and also by the hashed value of the name.
handlerMap map[string]*namedHandler
handlers []*namedHandler
curDeadline float64
stickyMap map[string]*namedHandler
compatibilityStickyMap map[string]*namedHandler
handlers []*namedHandler
curDeadline float64
// status is a record of which child services of the Balancer are healthy, keyed
// by name of child service. A service is initially added to the map when it is
// created via Add, and it is later removed or added to the map as needed,
@@ -73,7 +77,6 @@ func New(sticky *dynamic.Sticky, wantHealthCheck bool) *Balancer {
balancer := &Balancer{
status: make(map[string]struct{}),
fenced: make(map[string]struct{}),
handlerMap: make(map[string]*namedHandler),
wantsHealthCheck: wantHealthCheck,
}
if sticky != nil && sticky.Cookie != nil {
@@ -88,6 +91,9 @@ func New(sticky *dynamic.Sticky, wantHealthCheck bool) *Balancer {
if sticky.Cookie.Path != nil {
balancer.stickyCookie.path = *sticky.Cookie.Path
}
balancer.stickyMap = make(map[string]*namedHandler)
balancer.compatibilityStickyMap = make(map[string]*namedHandler)
}
return balancer
@@ -218,7 +224,7 @@ func (b *Balancer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if err == nil && cookie != nil {
b.handlersMu.RLock()
handler, ok := b.handlerMap[cookie.Value]
handler, ok := b.stickyMap[cookie.Value]
b.handlersMu.RUnlock()
if ok && handler != nil {
@@ -230,6 +236,22 @@ func (b *Balancer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
}
b.handlersMu.RLock()
handler, ok = b.compatibilityStickyMap[cookie.Value]
b.handlersMu.RUnlock()
if ok && handler != nil {
b.handlersMu.RLock()
_, isHealthy := b.status[handler.name]
b.handlersMu.RUnlock()
if isHealthy {
b.writeStickyCookie(w, handler)
handler.ServeHTTP(w, req)
return
}
}
}
}
@@ -244,21 +266,25 @@ func (b *Balancer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
if b.stickyCookie != nil {
cookie := &http.Cookie{
Name: b.stickyCookie.name,
Value: hash(server.name),
Path: b.stickyCookie.path,
HttpOnly: b.stickyCookie.httpOnly,
Secure: b.stickyCookie.secure,
SameSite: convertSameSite(b.stickyCookie.sameSite),
MaxAge: b.stickyCookie.maxAge,
}
http.SetCookie(w, cookie)
b.writeStickyCookie(w, server)
}
server.ServeHTTP(w, req)
}
func (b *Balancer) writeStickyCookie(w http.ResponseWriter, handler *namedHandler) {
cookie := &http.Cookie{
Name: b.stickyCookie.name,
Value: handler.hashedName,
Path: b.stickyCookie.path,
HttpOnly: b.stickyCookie.httpOnly,
Secure: b.stickyCookie.secure,
SameSite: convertSameSite(b.stickyCookie.sameSite),
MaxAge: b.stickyCookie.maxAge,
}
http.SetCookie(w, cookie)
}
// Add adds a handler.
// A handler with a non-positive weight is ignored.
func (b *Balancer) Add(name string, handler http.Handler, weight *int, fenced bool) {
@@ -280,15 +306,41 @@ func (b *Balancer) Add(name string, handler http.Handler, weight *int, fenced bo
if fenced {
b.fenced[name] = struct{}{}
}
b.handlerMap[name] = h
b.handlerMap[hash(name)] = h
if b.stickyCookie != nil {
sha256HashedName := sha256Hash(name)
h.hashedName = sha256HashedName
b.stickyMap[sha256HashedName] = h
b.compatibilityStickyMap[name] = h
hashedName := fnvHash(name)
b.compatibilityStickyMap[hashedName] = h
// server.URL was fnv hashed in service.Manager
// so we can have "double" fnv hash in already existing cookies
hashedName = fnvHash(hashedName)
b.compatibilityStickyMap[hashedName] = h
}
b.handlersMu.Unlock()
}
func hash(input string) string {
func fnvHash(input string) string {
hasher := fnv.New64()
// We purposely ignore the error because the implementation always returns nil.
_, _ = hasher.Write([]byte(input))
return strconv.FormatUint(hasher.Sum64(), 16)
}
func sha256Hash(input string) string {
hash := sha256.New()
// We purposely ignore the error because the implementation always returns nil.
_, _ = hash.Write([]byte(input))
hashedInput := hex.EncodeToString(hash.Sum(nil))
if len(hashedInput) < 16 {
return hashedInput
}
return hashedInput[:16]
}

View File

@@ -296,7 +296,7 @@ func TestSticky_FallBack(t *testing.T) {
rw.WriteHeader(http.StatusOK)
}), pointer(2), false)
recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}}
recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}, cookies: make(map[string]*http.Cookie)}
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.AddCookie(&http.Cookie{Name: "test", Value: "second"})
@@ -373,7 +373,7 @@ func TestSticky_Fenced(t *testing.T) {
rw.WriteHeader(http.StatusOK)
}), pointer(1), true)
recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}}
recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}, cookies: make(map[string]*http.Cookie)}
stickyReq := httptest.NewRequest(http.MethodGet, "/", nil)
stickyReq.AddCookie(&http.Cookie{Name: "test", Value: "fenced"})
@@ -391,3 +391,99 @@ func TestSticky_Fenced(t *testing.T) {
assert.Equal(t, 2, recorder.save["first"])
assert.Equal(t, 2, recorder.save["second"])
}
func TestStickyWithCompatibility(t *testing.T) {
testCases := []struct {
desc string
servers []string
cookies []*http.Cookie
expectedCookies []*http.Cookie
expectedServer string
}{
{
desc: "No previous cookie",
servers: []string{"first"},
expectedServer: "first",
expectedCookies: []*http.Cookie{
{Name: "test", Value: sha256Hash("first")},
},
},
{
desc: "Sha256 previous cookie",
servers: []string{"first", "second"},
cookies: []*http.Cookie{
{Name: "test", Value: sha256Hash("first")},
},
expectedServer: "first",
expectedCookies: []*http.Cookie{},
},
{
desc: "Raw previous cookie",
servers: []string{"first", "second"},
cookies: []*http.Cookie{
{Name: "test", Value: "first"},
},
expectedServer: "first",
expectedCookies: []*http.Cookie{
{Name: "test", Value: sha256Hash("first")},
},
},
{
desc: "Fnv previous cookie",
servers: []string{"first", "second"},
cookies: []*http.Cookie{
{Name: "test", Value: fnvHash("first")},
},
expectedServer: "first",
expectedCookies: []*http.Cookie{
{Name: "test", Value: sha256Hash("first")},
},
},
{
desc: "Double fnv previous cookie",
servers: []string{"first", "second"},
cookies: []*http.Cookie{
{Name: "test", Value: fnvHash(fnvHash("first"))},
},
expectedServer: "first",
expectedCookies: []*http.Cookie{
{Name: "test", Value: sha256Hash("first")},
},
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
balancer := New(&dynamic.Sticky{Cookie: &dynamic.Cookie{Name: "test"}}, false)
for _, server := range test.servers {
balancer.Add(server, http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
_, _ = rw.Write([]byte(server))
}), pointer(1), false)
}
// Do it twice, to be sure it's not just the luck.
for range 2 {
req := httptest.NewRequest(http.MethodGet, "/", nil)
for _, cookie := range test.cookies {
req.AddCookie(cookie)
}
recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}, cookies: make(map[string]*http.Cookie)}
balancer.ServeHTTP(recorder, req)
assert.Equal(t, test.expectedServer, recorder.Body.String())
assert.Len(t, recorder.cookies, len(test.expectedCookies))
for _, cookie := range test.expectedCookies {
assert.Equal(t, cookie.Value, recorder.cookies[cookie.Name].Value)
}
}
})
}
}

View File

@@ -2,11 +2,9 @@ package service
import (
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"hash/fnv"
"math/rand"
"net/http"
"net/url"
@@ -24,6 +22,7 @@ import (
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/middlewares/retry"
"github.com/traefik/traefik/v3/pkg/proxy/httputil"
"github.com/traefik/traefik/v3/pkg/safe"
"github.com/traefik/traefik/v3/pkg/server/cookie"
@@ -335,18 +334,13 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
lb := wrr.New(service.Sticky, service.HealthCheck != nil)
healthCheckTargets := make(map[string]*url.URL)
for _, server := range shuffle(service.Servers, m.rand) {
hasher := fnv.New64a()
_, _ = hasher.Write([]byte(server.URL)) // this will never return an error.
proxyName := hex.EncodeToString(hasher.Sum(nil))
for i, server := range shuffle(service.Servers, m.rand) {
target, err := url.Parse(server.URL)
if err != nil {
return nil, fmt.Errorf("error parsing server URL %s: %w", server.URL, err)
}
logger.Debug().Str(logs.ServerName, proxyName).Stringer("target", target).
logger.Debug().Int(logs.ServerIndex, i).Str("URL", server.URL).
Msg("Creating server")
qualifiedSvcName := provider.GetQualifiedName(ctx, serviceName)
@@ -356,6 +350,10 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
if err != nil {
return nil, fmt.Errorf("error building proxy for server URL %s: %w", server.URL, err)
}
// The retry wrapping must be done just before the proxy handler,
// to make sure that the retry will not be triggered/disabled by
// middlewares in the chain.
proxy = retry.WrapHandler(proxy)
// Prevents from enabling observability for internal resources.
@@ -392,12 +390,12 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
proxy, _ = capture.Wrap(proxy)
}
lb.Add(proxyName, proxy, server.Weight, server.Fenced)
lb.Add(server.URL, proxy, server.Weight, server.Fenced)
// servers are considered UP by default.
info.UpdateServerStatus(target.String(), runtime.StatusUp)
healthCheckTargets[proxyName] = target
healthCheckTargets[server.URL] = target
}
if service.HealthCheck != nil {

View File

@@ -53,6 +53,17 @@ func WithServiceName(serviceName string) func(*dynamic.Router) {
}
}
// WithObservability is a helper to create a configuration.
func WithObservability() func(*dynamic.Router) {
return func(r *dynamic.Router) {
r.Observability = &dynamic.RouterObservabilityConfig{
AccessLogs: pointer(true),
Metrics: pointer(true),
Tracing: pointer(true),
}
}
}
// WithLoadBalancerServices is a helper to create a configuration.
func WithLoadBalancerServices(opts ...func(service *dynamic.ServersLoadBalancer) string) func(*dynamic.HTTPConfiguration) {
return func(c *dynamic.HTTPConfiguration) {
@@ -149,3 +160,5 @@ func WithSticky(cookieName string) func(*dynamic.ServersLoadBalancer) {
}
}
}
func pointer[T any](v T) *T { return &v }

View File

@@ -25,7 +25,7 @@ import (
// Backend is an abstraction for tracking backend (OpenTelemetry, ...).
type Backend interface {
Setup(serviceName string, sampleRate float64, globalAttributes map[string]string) (trace.Tracer, io.Closer, error)
Setup(serviceName string, sampleRate float64, resourceAttributes map[string]string) (trace.Tracer, io.Closer, error)
}
// NewTracing Creates a Tracing.
@@ -44,7 +44,7 @@ func NewTracing(conf *static.Tracing) (*Tracer, io.Closer, error) {
otel.SetTextMapPropagator(autoprop.NewTextMapPropagator())
tr, closer, err := backend.Setup(conf.ServiceName, conf.SampleRate, conf.GlobalAttributes)
tr, closer, err := backend.Setup(conf.ServiceName, conf.SampleRate, conf.ResourceAttributes)
if err != nil {
return nil, nil, err
}
@@ -151,7 +151,7 @@ func (t *Tracer) Start(ctx context.Context, spanName string, opts ...trace.SpanS
spanCtx, span := t.Tracer.Start(ctx, spanName, opts...)
wrappedSpan := &Span{Span: span, tracerProvider: &TracerProvider{tracer: t}}
wrappedSpan := &Span{Span: span, tracerProvider: &TracerProvider{TracerProvider: span.TracerProvider(), tracer: t}}
return trace.ContextWithSpan(spanCtx, wrappedSpan), wrappedSpan
}

View File

@@ -2,6 +2,7 @@ package tracing
import (
"compress/gzip"
"context"
"encoding/json"
"io"
"net/http"
@@ -73,6 +74,7 @@ func TestTracing(t *testing.T) {
desc string
propagators string
headers map[string]string
resourceAttributes map[string]string
wantServiceHeadersFn func(t *testing.T, headers http.Header)
assertFn func(*testing.T, string)
}{
@@ -85,6 +87,17 @@ func TestTracing(t *testing.T) {
assert.Regexp(t, `({"key":"service.version","value":{"stringValue":"dev"}})`, trace)
},
},
{
desc: "resource attributes must be propagated",
resourceAttributes: map[string]string{
"service.environment": "custom",
},
assertFn: func(t *testing.T, trace string) {
t.Helper()
assert.Regexp(t, `({"key":"service.environment","value":{"stringValue":"custom"}})`, trace)
},
},
{
desc: "TraceContext propagation",
propagators: "tracecontext",
@@ -328,8 +341,9 @@ func TestTracing(t *testing.T) {
})
tracingConfig := &static.Tracing{
ServiceName: "traefik",
SampleRate: 1.0,
ServiceName: "traefik",
SampleRate: 1.0,
ResourceAttributes: test.resourceAttributes,
OTLP: &types.OTelTracing{
HTTP: &types.OTelHTTP{
Endpoint: collector.URL,
@@ -379,3 +393,25 @@ func TestTracing(t *testing.T) {
})
}
}
// TestTracerProvider ensures that Tracer returns a valid TracerProvider
// when using the default Traefik Tracer and a custom one.
func TestTracerProvider(t *testing.T) {
t.Parallel()
otlpConfig := &types.OTelTracing{}
otlpConfig.SetDefaults()
config := &static.Tracing{OTLP: otlpConfig}
tracer, closer, err := NewTracing(config)
if err != nil {
t.Fatal(err)
}
closer.Close()
_, span := tracer.Start(context.Background(), "test")
defer span.End()
span.TracerProvider().Tracer("github.com/traefik/traefik")
span.TracerProvider().Tracer("other")
}

View File

@@ -36,7 +36,7 @@ func (c *OTelTracing) SetDefaults() {
}
// Setup sets up the tracer.
func (c *OTelTracing) Setup(serviceName string, sampleRate float64, globalAttributes map[string]string) (trace.Tracer, io.Closer, error) {
func (c *OTelTracing) Setup(serviceName string, sampleRate float64, resourceAttributes map[string]string) (trace.Tracer, io.Closer, error) {
var (
err error
exporter *otlptrace.Exporter
@@ -55,7 +55,7 @@ func (c *OTelTracing) Setup(serviceName string, sampleRate float64, globalAttrib
semconv.ServiceVersionKey.String(version.Version),
}
for k, v := range globalAttributes {
for k, v := range resourceAttributes {
attr = append(attr, attribute.String(k, v))
}

View File

@@ -4,11 +4,11 @@ RepositoryName = "traefik"
OutputType = "file"
FileName = "traefik_changelog.md"
# example new bugfix v3.3.1
# example new bugfix v3.3.4
CurrentRef = "v3.3"
PreviousRef = "v3.3.0"
PreviousRef = "v3.3.3"
BaseBranch = "v3.3"
FutureCurrentRefName = "v3.3.1"
FutureCurrentRefName = "v3.3.4"
ThresholdPreviousRef = 10
ThresholdCurrentRef = 10

View File

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