mirror of
https://github.com/containous/traefik.git
synced 2025-10-07 15:33:25 +03:00
Compare commits
48 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
07e6491ace | ||
|
32ea014d07 | ||
|
a3fd484728 | ||
|
9b0348577a | ||
|
efe03bc9da | ||
|
cce935493a | ||
|
f196de90e1 | ||
|
c2a294c872 | ||
|
8e5d4c6ae9 | ||
|
1ccbf743cb | ||
|
1cfcf0d318 | ||
|
eb07a5ca1a | ||
|
56ea028e81 | ||
|
05c547f211 | ||
|
dcd9f2ea96 | ||
|
84e20aa9c3 | ||
|
b5a5e259ed | ||
|
8488214e93 | ||
|
b74767bfa4 | ||
|
da2278b29a | ||
|
cfebed7328 | ||
|
4e441d09ed | ||
|
8f5dd7bd9d | ||
|
d04e2d717c | ||
|
cdd24e91b4 | ||
|
4fd6b10b7d | ||
|
86315e0f18 | ||
|
c20af070e3 | ||
|
8593581cbf | ||
|
857fbb933e | ||
|
8103992977 | ||
|
c5b92b5260 | ||
|
fb527dac1c | ||
|
c19cf125e8 | ||
|
435d28c790 | ||
|
4ce4bd7121 | ||
|
020ab5f347 | ||
|
ad7fb8e82b | ||
|
0528c054a6 | ||
|
ad99c5bbea | ||
|
8272be0eda | ||
|
0a6ff446c7 | ||
|
9a9644bafe | ||
|
a57e118a1a | ||
|
d2414feaff | ||
|
6aa56788ea | ||
|
1aa450c028 | ||
|
f9ff6049d3 |
1
.github/workflows/build.yaml
vendored
1
.github/workflows/build.yaml
vendored
@@ -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
|
||||
|
1
.github/workflows/experimental.yaml
vendored
1
.github/workflows/experimental.yaml
vendored
@@ -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
|
||||
|
1
.github/workflows/release.yaml
vendored
1
.github/workflows/release.yaml
vendored
@@ -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
|
||||
|
2
.github/workflows/test-integration.yaml
vendored
2
.github/workflows/test-integration.yaml
vendored
@@ -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
|
||||
|
1
.github/workflows/test-unit.yaml
vendored
1
.github/workflows/test-unit.yaml
vendored
@@ -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
|
||||
|
5
.github/workflows/validate.yaml
vendored
5
.github/workflows/validate.yaml
vendored
@@ -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: |
|
||||
|
@@ -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
|
||||
|
85
CHANGELOG.md
85
CHANGELOG.md
@@ -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)
|
||||
|
||||
|
@@ -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:
|
||||
|
@@ -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{
|
||||
|
@@ -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 |
|
||||
|
@@ -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.
|
||||
|
@@ -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:
|
||||
|
@@ -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.
|
||||
|
@@ -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.
|
||||
|
@@ -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.
|
||||
|
@@ -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.
|
||||
|
@@ -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 |
|
||||
|
@@ -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 |
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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 -->
|
||||
|
||||
|
@@ -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 |
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
14
go.mod
@@ -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
26
go.sum
@@ -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=
|
||||
|
57
integration/fixtures/acme/acme_domains_1712362990.toml
Normal file
57
integration/fixtures/acme/acme_domains_1712362990.toml
Normal 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",
|
||||
]
|
||||
|
@@ -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-----
|
||||
"""]
|
||||
|
||||
|
@@ -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-----
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||
|
@@ -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}}"
|
||||
|
20
integration/testdata/rawdata-consul.json
vendored
20
integration/testdata/rawdata-consul.json
vendored
@@ -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"
|
||||
|
@@ -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"
|
||||
|
30
integration/testdata/rawdata-crd.json
vendored
30
integration/testdata/rawdata-crd.json
vendored
@@ -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"
|
||||
],
|
||||
|
20
integration/testdata/rawdata-etcd.json
vendored
20
integration/testdata/rawdata-etcd.json
vendored
@@ -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"
|
||||
|
20
integration/testdata/rawdata-gateway.json
vendored
20
integration/testdata/rawdata-gateway.json
vendored
@@ -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"
|
||||
|
@@ -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"
|
||||
|
30
integration/testdata/rawdata-ingress.json
vendored
30
integration/testdata/rawdata-ingress.json
vendored
@@ -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"
|
||||
|
@@ -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"
|
||||
|
15
integration/testdata/rawdata-ingressclass.json
vendored
15
integration/testdata/rawdata-ingressclass.json
vendored
@@ -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"
|
||||
|
20
integration/testdata/rawdata-redis.json
vendored
20
integration/testdata/rawdata-redis.json
vendored
@@ -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"
|
||||
|
20
integration/testdata/rawdata-zk.json
vendored
20
integration/testdata/rawdata-zk.json
vendored
@@ -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"
|
||||
|
@@ -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",
|
||||
},
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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"`
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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{}
|
||||
|
@@ -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{
|
||||
|
@@ -11,7 +11,6 @@ const (
|
||||
ServiceName = "serviceName"
|
||||
MetricsProviderName = "metricsProviderName"
|
||||
TracingProviderName = "tracingProviderName"
|
||||
ServerName = "serverName"
|
||||
ServerIndex = "serverIndex"
|
||||
TLSStoreName = "tlsStoreName"
|
||||
ServersTransportName = "serversTransport"
|
||||
|
@@ -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,
|
||||
|
@@ -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}})`,
|
||||
|
@@ -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()
|
||||
|
@@ -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))
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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])
|
||||
}
|
||||
|
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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++
|
||||
}
|
||||
|
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@@ -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"
|
||||
|
||||
|
@@ -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"`
|
||||
}
|
||||
|
||||
|
@@ -60,6 +60,7 @@ metadata:
|
||||
spec:
|
||||
forwardAuth:
|
||||
address: test.com
|
||||
headerField: X-Header-Field
|
||||
tls:
|
||||
certSecret: tlssecret
|
||||
caSecret: casecret
|
||||
|
@@ -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,
|
||||
}
|
||||
|
@@ -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-----",
|
||||
|
@@ -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.
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@@ -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"
|
||||
}
|
||||
|
@@ -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")
|
||||
}
|
||||
}
|
||||
|
@@ -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()
|
||||
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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")
|
||||
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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 }
|
||||
|
@@ -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),
|
||||
|
@@ -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"),
|
||||
|
@@ -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.
|
||||
|
@@ -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]
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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 }
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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")
|
||||
}
|
||||
|
@@ -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))
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -67,7 +67,7 @@ export default function PaginationMixin (opts = {}) {
|
||||
this.fetchMore()
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
beforeUnmount () {
|
||||
clearInterval(this.pollingInterval)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user