Merge branch 'v3.0' of github.com:traefik/traefik
This commit is contained in:
commit
44af01ae7c
120 changed files with 2790 additions and 828 deletions
|
@ -161,7 +161,10 @@ linters-settings:
|
|||
- len
|
||||
- suite-extra-assert-call
|
||||
- suite-thelper
|
||||
|
||||
staticcheck:
|
||||
checks:
|
||||
- all
|
||||
- -SA1019
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
|
|
|
@ -193,10 +193,13 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
|||
|
||||
tsProviders := initTailscaleProviders(staticConfiguration, &providerAggregator)
|
||||
|
||||
// Metrics
|
||||
// Observability
|
||||
|
||||
metricRegistries := registerMetricClients(staticConfiguration.Metrics)
|
||||
metricsRegistry := metrics.NewMultiRegistry(metricRegistries)
|
||||
accessLog := setupAccessLog(staticConfiguration.AccessLog)
|
||||
tracer, tracerCloser := setupTracing(staticConfiguration.Tracing)
|
||||
observabilityMgr := middleware.NewObservabilityMgr(*staticConfiguration, metricsRegistry, accessLog, tracer, tracerCloser)
|
||||
|
||||
// Entrypoints
|
||||
|
||||
|
@ -263,14 +266,11 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
|||
roundTripperManager := service.NewRoundTripperManager(spiffeX509Source)
|
||||
dialerManager := tcp.NewDialerManager(spiffeX509Source)
|
||||
acmeHTTPHandler := getHTTPChallengeHandler(acmeProviders, httpChallengeProvider)
|
||||
managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, metricsRegistry, roundTripperManager, acmeHTTPHandler)
|
||||
managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, observabilityMgr, roundTripperManager, acmeHTTPHandler)
|
||||
|
||||
// Router factory
|
||||
accessLog := setupAccessLog(staticConfiguration.AccessLog)
|
||||
|
||||
tracer, tracerCloser := setupTracing(staticConfiguration.Tracing)
|
||||
chainBuilder := middleware.NewChainBuilder(metricsRegistry, accessLog, tracer)
|
||||
routerFactory := server.NewRouterFactory(*staticConfiguration, managerFactory, tlsManager, chainBuilder, pluginBuilder, metricsRegistry, dialerManager)
|
||||
routerFactory := server.NewRouterFactory(*staticConfiguration, managerFactory, tlsManager, observabilityMgr, pluginBuilder, dialerManager)
|
||||
|
||||
// Watcher
|
||||
|
||||
|
@ -351,7 +351,7 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
|||
}
|
||||
})
|
||||
|
||||
return server.NewServer(routinesPool, serverEntryPointsTCP, serverEntryPointsUDP, watcher, chainBuilder, accessLog, tracerCloser), nil
|
||||
return server.NewServer(routinesPool, serverEntryPointsTCP, serverEntryPointsUDP, watcher, observabilityMgr), nil
|
||||
}
|
||||
|
||||
func getHTTPChallengeHandler(acmeProviders []*acme.Provider, httpChallengeProvider http.Handler) http.Handler {
|
||||
|
|
|
@ -85,6 +85,7 @@ At specified intervals (`checkPeriod`), the circuit breaker evaluates `expressio
|
|||
### Open
|
||||
|
||||
While open, the fallback mechanism takes over the normal service calls for a duration of `FallbackDuration`.
|
||||
The fallback mechanism returns a `HTTP 503` (or `ResponseCode`) to the client.
|
||||
After this duration, it enters the recovering state.
|
||||
|
||||
### Recovering
|
||||
|
@ -179,3 +180,9 @@ The duration for which the circuit breaker will wait before trying to recover (f
|
|||
_Optional, Default="10s"_
|
||||
|
||||
The duration for which the circuit breaker will try to recover (as soon as it is in recovering state).
|
||||
|
||||
### `ResponseCode`
|
||||
|
||||
_Optional, Default="503"_
|
||||
|
||||
The status code that the circuit breaker will return while it is in the open state.
|
||||
|
|
|
@ -52,3 +52,16 @@ http:
|
|||
[http.middlewares]
|
||||
[http.middlewares.autodetect.contentType]
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### `autoDetect`
|
||||
|
||||
!!! warning
|
||||
|
||||
`autoDetect` option is deprecated and should not be used.
|
||||
Moreover, it is redundant with an empty ContentType middleware declaration.
|
||||
|
||||
`autoDetect` specifies whether to let the `Content-Type` header,
|
||||
if it has not been set by the backend,
|
||||
be automatically set to a value derived from the contents of the response.
|
||||
|
|
|
@ -314,11 +314,43 @@ The `allowedHosts` option lists fully qualified domain names that are allowed.
|
|||
|
||||
The `hostsProxyHeaders` option is a set of header keys that may hold a proxied hostname value for the request.
|
||||
|
||||
### `sslRedirect`
|
||||
|
||||
!!! warning
|
||||
|
||||
Deprecated in favor of [EntryPoint redirection](../../routing/entrypoints.md#redirection) or the [RedirectScheme middleware](./redirectscheme.md).
|
||||
|
||||
The `sslRedirect` only allow HTTPS requests when set to `true`.
|
||||
|
||||
### `sslTemporaryRedirect`
|
||||
|
||||
!!! warning
|
||||
|
||||
Deprecated in favor of [EntryPoint redirection](../../routing/entrypoints.md#redirection) or the [RedirectScheme middleware](./redirectscheme.md).
|
||||
|
||||
Set `sslTemporaryRedirect` to `true` to force an SSL redirection using a 302 (instead of a 301).
|
||||
|
||||
### `sslHost`
|
||||
|
||||
!!! warning
|
||||
|
||||
Deprecated in favor of the [RedirectRegex middleware](./redirectregex.md).
|
||||
|
||||
The `sslHost` option is the host name that is used to redirect HTTP requests to HTTPS.
|
||||
|
||||
### `sslProxyHeaders`
|
||||
|
||||
The `sslProxyHeaders` option is set of header keys with associated values that would indicate a valid HTTPS request.
|
||||
It can be useful when using other proxies (example: `"X-Forwarded-Proto": "https"`).
|
||||
|
||||
### `sslForceHost`
|
||||
|
||||
!!! warning
|
||||
|
||||
Deprecated in favor of the [RedirectRegex middleware](./redirectregex.md).
|
||||
|
||||
Set `sslForceHost` to `true` and set `sslHost` to force requests to use `SSLHost` regardless of whether they already use SSL.
|
||||
|
||||
### `stsSeconds`
|
||||
|
||||
The `stsSeconds` is the max-age of the `Strict-Transport-Security` header.
|
||||
|
@ -370,6 +402,14 @@ The `publicKey` implements HPKP to prevent MITM attacks with forged certificates
|
|||
|
||||
The `referrerPolicy` allows sites to control whether browsers forward the `Referer` header to other sites.
|
||||
|
||||
### `featurePolicy`
|
||||
|
||||
!!! warning
|
||||
|
||||
Deprecated in favor of [`permissionsPolicy`](#permissionsPolicy)
|
||||
|
||||
The `featurePolicy` allows sites to control browser features.
|
||||
|
||||
### `permissionsPolicy`
|
||||
|
||||
The `permissionsPolicy` allows sites to control browser features.
|
||||
|
|
|
@ -76,3 +76,72 @@ For instance, `/products` also matches `/products/shoes` and `/products/shirts`.
|
|||
|
||||
If your backend is serving assets (e.g., images or JavaScript files), it can use the `X-Forwarded-Prefix` header to properly construct relative URLs.
|
||||
Using the previous example, the backend should return `/products/shoes/image.png` (and not `/image.png`, which Traefik would likely not be able to associate with the same backend).
|
||||
|
||||
### `forceSlash`
|
||||
|
||||
_Optional, Default=true_
|
||||
|
||||
!!! warning
|
||||
|
||||
`forceSlash` option is deprecated and should not be used.
|
||||
|
||||
The `forceSlash` option ensures the resulting stripped path is not the empty string, by replacing it with `/` when necessary.
|
||||
|
||||
??? info "Behavior examples"
|
||||
|
||||
- `forceSlash=true`
|
||||
|
||||
| Path | Prefix to strip | Result |
|
||||
|------------|-----------------|--------|
|
||||
| `/` | `/` | `/` |
|
||||
| `/foo` | `/foo` | `/` |
|
||||
| `/foo/` | `/foo` | `/` |
|
||||
| `/foo/` | `/foo/` | `/` |
|
||||
| `/bar` | `/foo` | `/bar` |
|
||||
| `/foo/bar` | `/foo` | `/bar` |
|
||||
|
||||
- `forceSlash=false`
|
||||
|
||||
| Path | Prefix to strip | Result |
|
||||
|------------|-----------------|--------|
|
||||
| `/` | `/` | empty |
|
||||
| `/foo` | `/foo` | empty |
|
||||
| `/foo/` | `/foo` | `/` |
|
||||
| `/foo/` | `/foo/` | empty |
|
||||
| `/bar` | `/foo` | `/bar` |
|
||||
| `/foo/bar` | `/foo` | `/bar` |
|
||||
|
||||
```yaml tab="Docker"
|
||||
labels:
|
||||
- "traefik.http.middlewares.example.stripprefix.prefixes=/foobar"
|
||||
- "traefik.http.middlewares.example.stripprefix.forceSlash=false"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes"
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: example
|
||||
spec:
|
||||
stripPrefix:
|
||||
prefixes:
|
||||
- "/foobar"
|
||||
forceSlash: false
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
http:
|
||||
middlewares:
|
||||
example:
|
||||
stripPrefix:
|
||||
prefixes:
|
||||
- "/foobar"
|
||||
forceSlash: false
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[http.middlewares]
|
||||
[http.middlewares.example.stripPrefix]
|
||||
prefixes = ["/foobar"]
|
||||
forceSlash = false
|
||||
```
|
||||
|
|
|
@ -693,3 +693,13 @@ Here are two possible transition strategies:
|
|||
This allows continued compatibility with the existing infrastructure.
|
||||
|
||||
Please check the [OpenTelemetry Tracing provider documention](../observability/tracing/opentelemetry.md) for more information.
|
||||
|
||||
#### Internal Resources Observability (AccessLogs, Metrics and Tracing)
|
||||
|
||||
In v3, observability for internal routers or services (e.g.: `ping@internal`) is disabled by default.
|
||||
To enable it one should use the new `addInternals` option for AccessLogs, Metrics or Tracing.
|
||||
Please take a look at the observability documentation for more information:
|
||||
|
||||
- [AccessLogs](../observability/access-logs.md#addinternals)
|
||||
- [Metrics](../observability/metrics/overview.md#addinternals)
|
||||
- [AccessLogs](../observability/tracing/overview.md#addinternals)
|
||||
|
|
|
@ -26,6 +26,26 @@ accessLog: {}
|
|||
--accesslog=true
|
||||
```
|
||||
|
||||
### `addInternals`
|
||||
|
||||
_Optional, Default="false"_
|
||||
|
||||
Enables accessLogs for internal resources.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
accesslog:
|
||||
addInternals: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[accesslog]
|
||||
addInternals = true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--accesslog.addinternals
|
||||
```
|
||||
|
||||
### `filePath`
|
||||
|
||||
By default access logs are written to the standard output.
|
||||
|
|
|
@ -27,6 +27,8 @@ _Required, Default="127.0.0.1:8125"_
|
|||
|
||||
Address instructs exporter to send metrics to datadog-agent at this address.
|
||||
|
||||
This address can be a Unix Domain Socket (UDS) address with the following form: `unix:///path/to/datadog.socket`.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
metrics:
|
||||
datadog:
|
||||
|
|
|
@ -14,6 +14,28 @@ Traefik supports these metrics backends:
|
|||
|
||||
Traefik Proxy hosts an official Grafana dashboard for both [on-premises](https://grafana.com/grafana/dashboards/17346) and [Kubernetes](https://grafana.com/grafana/dashboards/17347) deployments.
|
||||
|
||||
## Common Options
|
||||
|
||||
### `addInternals`
|
||||
|
||||
_Optional, Default="false"_
|
||||
|
||||
Enables metrics for internal resources.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
metrics:
|
||||
addInternals: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[metrics]
|
||||
addInternals = true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.addinternals
|
||||
```
|
||||
|
||||
## Global Metrics
|
||||
|
||||
| Metric | Type | [Labels](#labels) | Description |
|
||||
|
|
42
docs/content/observability/overview.md
Normal file
42
docs/content/observability/overview.md
Normal file
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
title: "Traefik Observability Overview"
|
||||
description: "Traefik provides Logs, Access Logs, Metrics and Tracing. Read the full documentation to get started."
|
||||
---
|
||||
|
||||
# Overview
|
||||
|
||||
Traefik's Observability system
|
||||
{: .subtitle }
|
||||
|
||||
## Logs
|
||||
|
||||
Traefik logs informs about everything that happens within Traefik (startup, configuration, events, shutdown, and so on).
|
||||
|
||||
Read the [Logs documentation](./logs.md) to learn how to configure it.
|
||||
|
||||
## Access Logs
|
||||
|
||||
Access logs are a key part of observability in Traefik.
|
||||
|
||||
They are providing valuable insights about incoming traffic, and allow to monitor it.
|
||||
The access logs record detailed information about each request received by Traefik,
|
||||
including the source IP address, requested URL, response status code, and more.
|
||||
|
||||
Read the [Access Logs documentation](./access-logs.md) to learn how to configure it.
|
||||
|
||||
## Metrics
|
||||
|
||||
Traefik offers a metrics feature that provides valuable insights about the performance and usage.
|
||||
These metrics include the number of requests received, the requests duration, and more.
|
||||
|
||||
Traefik supports these metrics systems: Prometheus, Datadog, InfluxDB 2.X, and StatsD.
|
||||
|
||||
Read the [Metrics documentation](./metrics/overview.md) to learn how to configure it.
|
||||
|
||||
## Tracing
|
||||
|
||||
The Traefik tracing system allows developers to gain deep visibility into the flow of requests through their infrastructure.
|
||||
|
||||
Traefik supports these tracing with OpenTelemetry.
|
||||
|
||||
Read the [Tracing documentation](./tracing/overview.md) to learn how to configure it.
|
|
@ -14,10 +14,8 @@ Traefik uses [OpenTelemetry](https://opentelemetry.io/ "Link to website of OTel"
|
|||
|
||||
Please check our dedicated [OTel docs](./opentelemetry.md) to learn more.
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
|
||||
To enable the tracing:
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
|
@ -34,6 +32,26 @@ tracing: {}
|
|||
|
||||
### Common Options
|
||||
|
||||
#### `addInternals`
|
||||
|
||||
_Optional, Default="false"_
|
||||
|
||||
Enables tracing for internal resources.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
tracing:
|
||||
addInternals: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[tracing]
|
||||
addInternals = true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--tracing.addinternals
|
||||
```
|
||||
|
||||
#### `serviceName`
|
||||
|
||||
_Required, Default="traefik"_
|
||||
|
|
|
@ -16,11 +16,13 @@
|
|||
- "traefik.http.middlewares.middleware05.circuitbreaker.expression=foobar"
|
||||
- "traefik.http.middlewares.middleware05.circuitbreaker.fallbackduration=42s"
|
||||
- "traefik.http.middlewares.middleware05.circuitbreaker.recoveryduration=42s"
|
||||
- "traefik.http.middlewares.middleware05.circuitbreaker.responsecode=42"
|
||||
- "traefik.http.middlewares.middleware06.compress=true"
|
||||
- "traefik.http.middlewares.middleware06.compress.excludedcontenttypes=foobar, foobar"
|
||||
- "traefik.http.middlewares.middleware06.compress.includedcontenttypes=foobar, foobar"
|
||||
- "traefik.http.middlewares.middleware06.compress.minresponsebodybytes=42"
|
||||
- "traefik.http.middlewares.middleware07.contenttype=true"
|
||||
- "traefik.http.middlewares.middleware07.contenttype.autodetect=true"
|
||||
- "traefik.http.middlewares.middleware08.digestauth.headerfield=foobar"
|
||||
- "traefik.http.middlewares.middleware08.digestauth.realm=foobar"
|
||||
- "traefik.http.middlewares.middleware08.digestauth.removeheader=true"
|
||||
|
@ -35,6 +37,7 @@
|
|||
- "traefik.http.middlewares.middleware10.forwardauth.authresponseheaders=foobar, foobar"
|
||||
- "traefik.http.middlewares.middleware10.forwardauth.authresponseheadersregex=foobar"
|
||||
- "traefik.http.middlewares.middleware10.forwardauth.tls.ca=foobar"
|
||||
- "traefik.http.middlewares.middleware10.forwardauth.tls.caoptional=true"
|
||||
- "traefik.http.middlewares.middleware10.forwardauth.tls.cert=foobar"
|
||||
- "traefik.http.middlewares.middleware10.forwardauth.tls.insecureskipverify=true"
|
||||
- "traefik.http.middlewares.middleware10.forwardauth.tls.key=foobar"
|
||||
|
@ -58,6 +61,7 @@
|
|||
- "traefik.http.middlewares.middleware12.headers.customrequestheaders.name1=foobar"
|
||||
- "traefik.http.middlewares.middleware12.headers.customresponseheaders.name0=foobar"
|
||||
- "traefik.http.middlewares.middleware12.headers.customresponseheaders.name1=foobar"
|
||||
- "traefik.http.middlewares.middleware12.headers.featurepolicy=foobar"
|
||||
- "traefik.http.middlewares.middleware12.headers.forcestsheader=true"
|
||||
- "traefik.http.middlewares.middleware12.headers.framedeny=true"
|
||||
- "traefik.http.middlewares.middleware12.headers.hostsproxyheaders=foobar, foobar"
|
||||
|
@ -65,8 +69,12 @@
|
|||
- "traefik.http.middlewares.middleware12.headers.permissionspolicy=foobar"
|
||||
- "traefik.http.middlewares.middleware12.headers.publickey=foobar"
|
||||
- "traefik.http.middlewares.middleware12.headers.referrerpolicy=foobar"
|
||||
- "traefik.http.middlewares.middleware12.headers.sslforcehost=true"
|
||||
- "traefik.http.middlewares.middleware12.headers.sslhost=foobar"
|
||||
- "traefik.http.middlewares.middleware12.headers.sslproxyheaders.name0=foobar"
|
||||
- "traefik.http.middlewares.middleware12.headers.sslproxyheaders.name1=foobar"
|
||||
- "traefik.http.middlewares.middleware12.headers.sslredirect=true"
|
||||
- "traefik.http.middlewares.middleware12.headers.ssltemporaryredirect=true"
|
||||
- "traefik.http.middlewares.middleware12.headers.stsincludesubdomains=true"
|
||||
- "traefik.http.middlewares.middleware12.headers.stspreload=true"
|
||||
- "traefik.http.middlewares.middleware12.headers.stsseconds=42"
|
||||
|
@ -126,6 +134,7 @@
|
|||
- "traefik.http.middlewares.middleware22.replacepathregex.replacement=foobar"
|
||||
- "traefik.http.middlewares.middleware23.retry.attempts=42"
|
||||
- "traefik.http.middlewares.middleware23.retry.initialinterval=42s"
|
||||
- "traefik.http.middlewares.middleware24.stripprefix.forceslash=true"
|
||||
- "traefik.http.middlewares.middleware24.stripprefix.prefixes=foobar, foobar"
|
||||
- "traefik.http.middlewares.middleware25.stripprefixregex.regex=foobar, foobar"
|
||||
- "traefik.http.routers.router0.entrypoints=foobar, foobar"
|
||||
|
@ -213,6 +222,7 @@
|
|||
- "traefik.tcp.services.tcpservice01.loadbalancer.proxyprotocol=true"
|
||||
- "traefik.tcp.services.tcpservice01.loadbalancer.proxyprotocol.version=42"
|
||||
- "traefik.tcp.services.tcpservice01.loadbalancer.serverstransport=foobar"
|
||||
- "traefik.tcp.services.tcpservice01.loadbalancer.terminationdelay=42"
|
||||
- "traefik.tcp.services.tcpservice01.loadbalancer.server.port=foobar"
|
||||
- "traefik.tcp.services.tcpservice01.loadbalancer.server.tls=true"
|
||||
- "traefik.udp.routers.udprouter0.entrypoints=foobar, foobar"
|
||||
|
|
|
@ -137,6 +137,7 @@
|
|||
checkPeriod = "42s"
|
||||
fallbackDuration = "42s"
|
||||
recoveryDuration = "42s"
|
||||
responseCode = 42
|
||||
[http.middlewares.Middleware06]
|
||||
[http.middlewares.Middleware06.compress]
|
||||
excludedContentTypes = ["foobar", "foobar"]
|
||||
|
@ -144,6 +145,7 @@
|
|||
minResponseBodyBytes = 42
|
||||
[http.middlewares.Middleware07]
|
||||
[http.middlewares.Middleware07.contentType]
|
||||
autoDetect = true
|
||||
[http.middlewares.Middleware08]
|
||||
[http.middlewares.Middleware08.digestAuth]
|
||||
users = ["foobar", "foobar"]
|
||||
|
@ -169,6 +171,7 @@
|
|||
cert = "foobar"
|
||||
key = "foobar"
|
||||
insecureSkipVerify = true
|
||||
caOptional = true
|
||||
[http.middlewares.Middleware11]
|
||||
[http.middlewares.Middleware11.grpcWeb]
|
||||
allowOrigins = ["foobar", "foobar"]
|
||||
|
@ -198,6 +201,11 @@
|
|||
referrerPolicy = "foobar"
|
||||
permissionsPolicy = "foobar"
|
||||
isDevelopment = true
|
||||
featurePolicy = "foobar"
|
||||
sslRedirect = true
|
||||
sslTemporaryRedirect = true
|
||||
sslHost = "foobar"
|
||||
sslForceHost = true
|
||||
[http.middlewares.Middleware12.headers.customRequestHeaders]
|
||||
name0 = "foobar"
|
||||
name1 = "foobar"
|
||||
|
@ -297,6 +305,7 @@
|
|||
[http.middlewares.Middleware24]
|
||||
[http.middlewares.Middleware24.stripPrefix]
|
||||
prefixes = ["foobar", "foobar"]
|
||||
forceSlash = true
|
||||
[http.middlewares.Middleware25]
|
||||
[http.middlewares.Middleware25.stripPrefixRegex]
|
||||
regex = ["foobar", "foobar"]
|
||||
|
@ -394,6 +403,7 @@
|
|||
[tcp.services.TCPService01]
|
||||
[tcp.services.TCPService01.loadBalancer]
|
||||
serversTransport = "foobar"
|
||||
terminationDelay = 42
|
||||
[tcp.services.TCPService01.loadBalancer.proxyProtocol]
|
||||
version = 42
|
||||
|
||||
|
@ -513,6 +523,7 @@
|
|||
curvePreferences = ["foobar", "foobar"]
|
||||
sniStrict = true
|
||||
alpnProtocols = ["foobar", "foobar"]
|
||||
preferServerCipherSuites = true
|
||||
[tls.options.Options0.clientAuth]
|
||||
caFiles = ["foobar", "foobar"]
|
||||
clientAuthType = "foobar"
|
||||
|
@ -523,6 +534,7 @@
|
|||
curvePreferences = ["foobar", "foobar"]
|
||||
sniStrict = true
|
||||
alpnProtocols = ["foobar", "foobar"]
|
||||
preferServerCipherSuites = true
|
||||
[tls.options.Options1.clientAuth]
|
||||
caFiles = ["foobar", "foobar"]
|
||||
clientAuthType = "foobar"
|
||||
|
|
|
@ -142,6 +142,7 @@ http:
|
|||
checkPeriod: 42s
|
||||
fallbackDuration: 42s
|
||||
recoveryDuration: 42s
|
||||
responseCode: 42
|
||||
Middleware06:
|
||||
compress:
|
||||
excludedContentTypes:
|
||||
|
@ -152,7 +153,8 @@ http:
|
|||
- foobar
|
||||
minResponseBodyBytes: 42
|
||||
Middleware07:
|
||||
contentType: {}
|
||||
contentType:
|
||||
autoDetect: true
|
||||
Middleware08:
|
||||
digestAuth:
|
||||
users:
|
||||
|
@ -177,6 +179,7 @@ http:
|
|||
cert: foobar
|
||||
key: foobar
|
||||
insecureSkipVerify: true
|
||||
caOptional: true
|
||||
trustForwardHeader: true
|
||||
authResponseHeaders:
|
||||
- foobar
|
||||
|
@ -242,6 +245,11 @@ http:
|
|||
referrerPolicy: foobar
|
||||
permissionsPolicy: foobar
|
||||
isDevelopment: true
|
||||
featurePolicy: foobar
|
||||
sslRedirect: true
|
||||
sslTemporaryRedirect: true
|
||||
sslHost: foobar
|
||||
sslForceHost: true
|
||||
Middleware13:
|
||||
ipAllowList:
|
||||
sourceRange:
|
||||
|
@ -346,6 +354,7 @@ http:
|
|||
prefixes:
|
||||
- foobar
|
||||
- foobar
|
||||
forceSlash: true
|
||||
Middleware25:
|
||||
stripPrefixRegex:
|
||||
regex:
|
||||
|
@ -463,6 +472,7 @@ tcp:
|
|||
- address: foobar
|
||||
tls: true
|
||||
serversTransport: foobar
|
||||
terminationDelay: 42
|
||||
TCPService02:
|
||||
weighted:
|
||||
services:
|
||||
|
@ -583,6 +593,7 @@ tls:
|
|||
alpnProtocols:
|
||||
- foobar
|
||||
- foobar
|
||||
preferServerCipherSuites: true
|
||||
Options1:
|
||||
minVersion: foobar
|
||||
maxVersion: foobar
|
||||
|
@ -601,6 +612,7 @@ tls:
|
|||
alpnProtocols:
|
||||
- foobar
|
||||
- foobar
|
||||
preferServerCipherSuites: true
|
||||
stores:
|
||||
Store0:
|
||||
defaultCertificate:
|
||||
|
|
|
@ -393,6 +393,18 @@ spec:
|
|||
between Traefik and your servers. Can only be used on
|
||||
a Kubernetes Service.
|
||||
type: string
|
||||
terminationDelay:
|
||||
description: 'TerminationDelay defines the deadline that
|
||||
the proxy sets, after one of its connected peers indicates
|
||||
it has closed the writing capability of its connection,
|
||||
to close the reading capability as well, hence fully
|
||||
terminating the connection. It is a duration in milliseconds,
|
||||
defaulting to 100. A negative value means an infinite
|
||||
deadline (i.e. the reading capability is never closed).
|
||||
Deprecated: TerminationDelay is not supported APIVersion
|
||||
traefik.io/v1, please use ServersTransport to configure
|
||||
the TerminationDelay instead.'
|
||||
type: integer
|
||||
tls:
|
||||
description: TLS determines whether to use TLS when dialing
|
||||
with the backend.
|
||||
|
@ -779,9 +791,17 @@ spec:
|
|||
type: object
|
||||
contentType:
|
||||
description: ContentType holds the content-type middleware configuration.
|
||||
This middleware sets the `Content-Type` header value to the media
|
||||
type detected from the response content, when it is not set by the
|
||||
backend.
|
||||
This middleware exists to enable the correct behavior until at least
|
||||
the default one can be changed in a future version.
|
||||
properties:
|
||||
autoDetect:
|
||||
description: 'AutoDetect specifies whether to let the `Content-Type`
|
||||
header, if it has not been set by the backend, be automatically
|
||||
set to a value derived from the contents of the response. Deprecated:
|
||||
AutoDetect option is deprecated, Content-Type middleware is
|
||||
only meant to be used to enable the content-type detection,
|
||||
please remove any usage of this option.'
|
||||
type: boolean
|
||||
type: object
|
||||
digestAuth:
|
||||
description: 'DigestAuth holds the digest auth middleware configuration.
|
||||
|
@ -972,6 +992,10 @@ spec:
|
|||
description: TLS defines the configuration used to secure the
|
||||
connection to the authentication server.
|
||||
properties:
|
||||
caOptional:
|
||||
description: 'Deprecated: TLS client authentication is a server
|
||||
side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634).'
|
||||
type: boolean
|
||||
caSecret:
|
||||
description: CASecret is the name of the referenced Kubernetes
|
||||
Secret containing the CA to validate the server certificate.
|
||||
|
@ -1090,6 +1114,10 @@ spec:
|
|||
description: CustomResponseHeaders defines the header names and
|
||||
values to apply to the response.
|
||||
type: object
|
||||
featurePolicy:
|
||||
description: 'Deprecated: FeaturePolicy option is deprecated,
|
||||
please use PermissionsPolicy instead.'
|
||||
type: string
|
||||
forceSTSHeader:
|
||||
description: ForceSTSHeader defines whether to add the STS header
|
||||
even when the connection is HTTP.
|
||||
|
@ -1125,6 +1153,14 @@ spec:
|
|||
value. This allows sites to control whether browsers forward
|
||||
the Referer header to other sites.
|
||||
type: string
|
||||
sslForceHost:
|
||||
description: 'Deprecated: SSLForceHost option is deprecated, please
|
||||
use RedirectRegex instead.'
|
||||
type: boolean
|
||||
sslHost:
|
||||
description: 'Deprecated: SSLHost option is deprecated, please
|
||||
use RedirectRegex instead.'
|
||||
type: string
|
||||
sslProxyHeaders:
|
||||
additionalProperties:
|
||||
type: string
|
||||
|
@ -1133,6 +1169,14 @@ spec:
|
|||
useful when using other proxies (example: "X-Forwarded-Proto":
|
||||
"https").'
|
||||
type: object
|
||||
sslRedirect:
|
||||
description: 'Deprecated: SSLRedirect option is deprecated, please
|
||||
use EntryPoint redirection or RedirectScheme instead.'
|
||||
type: boolean
|
||||
sslTemporaryRedirect:
|
||||
description: 'Deprecated: SSLTemporaryRedirect option is deprecated,
|
||||
please use EntryPoint redirection or RedirectScheme instead.'
|
||||
type: boolean
|
||||
stsIncludeSubdomains:
|
||||
description: STSIncludeSubdomains defines whether the includeSubDomains
|
||||
directive is appended to the Strict-Transport-Security header.
|
||||
|
@ -1504,6 +1548,12 @@ spec:
|
|||
This middleware removes the specified prefixes from the URL path.
|
||||
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefix/'
|
||||
properties:
|
||||
forceSlash:
|
||||
description: 'Deprecated: ForceSlash option is deprecated, please
|
||||
remove any usage of this option. ForceSlash ensures that the
|
||||
resulting stripped path is not the empty string, by replacing
|
||||
it with / when necessary. Default: true.'
|
||||
type: boolean
|
||||
prefixes:
|
||||
description: Prefixes defines the prefixes to strip from the request
|
||||
URL.
|
||||
|
@ -1578,7 +1628,9 @@ spec:
|
|||
type: integer
|
||||
type: object
|
||||
ipAllowList:
|
||||
description: IPAllowList defines the IPAllowList middleware configuration.
|
||||
description: 'IPAllowList defines the IPAllowList middleware configuration.
|
||||
This middleware accepts/refuses connections based on the client
|
||||
IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/tcp/ipallowlist/'
|
||||
properties:
|
||||
sourceRange:
|
||||
description: SourceRange defines the allowed IPs (or ranges of
|
||||
|
@ -1589,7 +1641,8 @@ spec:
|
|||
type: object
|
||||
ipWhiteList:
|
||||
description: 'IPWhiteList defines the IPWhiteList middleware configuration.
|
||||
Deprecated: please use IPAllowList instead.'
|
||||
This middleware accepts/refuses connections based on the client
|
||||
IP. Deprecated: please use IPAllowList instead. More info: https://doc.traefik.io/traefik/v3.0/middlewares/tcp/ipwhitelist/'
|
||||
properties:
|
||||
sourceRange:
|
||||
description: SourceRange defines the allowed IPs (or ranges of
|
||||
|
@ -1940,6 +1993,12 @@ spec:
|
|||
will accept. Possible values: VersionTLS10, VersionTLS11, VersionTLS12,
|
||||
VersionTLS13. Default: VersionTLS10.'
|
||||
type: string
|
||||
preferServerCipherSuites:
|
||||
description: 'PreferServerCipherSuites defines whether the server
|
||||
chooses a cipher suite among his own instead of among the client''s.
|
||||
It is enabled automatically when minVersion or maxVersion is set.
|
||||
Deprecated: https://github.com/golang/go/issues/45430'
|
||||
type: boolean
|
||||
sniStrict:
|
||||
description: SniStrict defines whether Traefik allows connections
|
||||
from clients connections that do not specify a server_name extension.
|
||||
|
|
|
@ -20,12 +20,13 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| `traefik/http/middlewares/Middleware05/circuitBreaker/expression` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware05/circuitBreaker/fallbackDuration` | `42s` |
|
||||
| `traefik/http/middlewares/Middleware05/circuitBreaker/recoveryDuration` | `42s` |
|
||||
| `traefik/http/middlewares/Middleware05/circuitBreaker/responseCode` | `42` |
|
||||
| `traefik/http/middlewares/Middleware06/compress/excludedContentTypes/0` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware06/compress/excludedContentTypes/1` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware06/compress/includedContentTypes/0` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware06/compress/includedContentTypes/1` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware06/compress/minResponseBodyBytes` | `42` |
|
||||
| `traefik/http/middlewares/Middleware07/contentType` | `` |
|
||||
| `traefik/http/middlewares/Middleware07/contentType/autoDetect` | `true` |
|
||||
| `traefik/http/middlewares/Middleware08/digestAuth/headerField` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware08/digestAuth/realm` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware08/digestAuth/removeHeader` | `true` |
|
||||
|
@ -45,6 +46,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| `traefik/http/middlewares/Middleware10/forwardAuth/authResponseHeaders/1` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware10/forwardAuth/authResponseHeadersRegex` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware10/forwardAuth/tls/ca` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware10/forwardAuth/tls/caOptional` | `true` |
|
||||
| `traefik/http/middlewares/Middleware10/forwardAuth/tls/cert` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware10/forwardAuth/tls/insecureSkipVerify` | `true` |
|
||||
| `traefik/http/middlewares/Middleware10/forwardAuth/tls/key` | `foobar` |
|
||||
|
@ -75,6 +77,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| `traefik/http/middlewares/Middleware12/headers/customRequestHeaders/name1` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/customResponseHeaders/name0` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/customResponseHeaders/name1` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/featurePolicy` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/forceSTSHeader` | `true` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/frameDeny` | `true` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/hostsProxyHeaders/0` | `foobar` |
|
||||
|
@ -83,8 +86,12 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| `traefik/http/middlewares/Middleware12/headers/permissionsPolicy` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/publicKey` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/referrerPolicy` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/sslForceHost` | `true` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/sslHost` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/sslProxyHeaders/name0` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/sslProxyHeaders/name1` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/sslRedirect` | `true` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/sslTemporaryRedirect` | `true` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/stsIncludeSubdomains` | `true` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/stsPreload` | `true` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/stsSeconds` | `42` |
|
||||
|
@ -148,6 +155,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| `traefik/http/middlewares/Middleware22/replacePathRegex/replacement` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware23/retry/attempts` | `42` |
|
||||
| `traefik/http/middlewares/Middleware23/retry/initialInterval` | `42s` |
|
||||
| `traefik/http/middlewares/Middleware24/stripPrefix/forceSlash` | `true` |
|
||||
| `traefik/http/middlewares/Middleware24/stripPrefix/prefixes/0` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware24/stripPrefix/prefixes/1` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware25/stripPrefixRegex/regex/0` | `foobar` |
|
||||
|
@ -341,6 +349,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| `traefik/tcp/services/TCPService01/loadBalancer/servers/1/address` | `foobar` |
|
||||
| `traefik/tcp/services/TCPService01/loadBalancer/servers/1/tls` | `true` |
|
||||
| `traefik/tcp/services/TCPService01/loadBalancer/serversTransport` | `foobar` |
|
||||
| `traefik/tcp/services/TCPService01/loadBalancer/terminationDelay` | `42` |
|
||||
| `traefik/tcp/services/TCPService02/weighted/services/0/name` | `foobar` |
|
||||
| `traefik/tcp/services/TCPService02/weighted/services/0/weight` | `42` |
|
||||
| `traefik/tcp/services/TCPService02/weighted/services/1/name` | `foobar` |
|
||||
|
@ -364,6 +373,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| `traefik/tls/options/Options0/curvePreferences/1` | `foobar` |
|
||||
| `traefik/tls/options/Options0/maxVersion` | `foobar` |
|
||||
| `traefik/tls/options/Options0/minVersion` | `foobar` |
|
||||
| `traefik/tls/options/Options0/preferServerCipherSuites` | `true` |
|
||||
| `traefik/tls/options/Options0/sniStrict` | `true` |
|
||||
| `traefik/tls/options/Options1/alpnProtocols/0` | `foobar` |
|
||||
| `traefik/tls/options/Options1/alpnProtocols/1` | `foobar` |
|
||||
|
@ -376,6 +386,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| `traefik/tls/options/Options1/curvePreferences/1` | `foobar` |
|
||||
| `traefik/tls/options/Options1/maxVersion` | `foobar` |
|
||||
| `traefik/tls/options/Options1/minVersion` | `foobar` |
|
||||
| `traefik/tls/options/Options1/preferServerCipherSuites` | `true` |
|
||||
| `traefik/tls/options/Options1/sniStrict` | `true` |
|
||||
| `traefik/tls/stores/Store0/defaultCertificate/certFile` | `foobar` |
|
||||
| `traefik/tls/stores/Store0/defaultCertificate/keyFile` | `foobar` |
|
||||
|
|
|
@ -116,6 +116,18 @@ spec:
|
|||
between Traefik and your servers. Can only be used on
|
||||
a Kubernetes Service.
|
||||
type: string
|
||||
terminationDelay:
|
||||
description: 'TerminationDelay defines the deadline that
|
||||
the proxy sets, after one of its connected peers indicates
|
||||
it has closed the writing capability of its connection,
|
||||
to close the reading capability as well, hence fully
|
||||
terminating the connection. It is a duration in milliseconds,
|
||||
defaulting to 100. A negative value means an infinite
|
||||
deadline (i.e. the reading capability is never closed).
|
||||
Deprecated: TerminationDelay is not supported APIVersion
|
||||
traefik.io/v1, please use ServersTransport to configure
|
||||
the TerminationDelay instead.'
|
||||
type: integer
|
||||
tls:
|
||||
description: TLS determines whether to use TLS when dialing
|
||||
with the backend.
|
||||
|
|
|
@ -190,9 +190,17 @@ spec:
|
|||
type: object
|
||||
contentType:
|
||||
description: ContentType holds the content-type middleware configuration.
|
||||
This middleware sets the `Content-Type` header value to the media
|
||||
type detected from the response content, when it is not set by the
|
||||
backend.
|
||||
This middleware exists to enable the correct behavior until at least
|
||||
the default one can be changed in a future version.
|
||||
properties:
|
||||
autoDetect:
|
||||
description: 'AutoDetect specifies whether to let the `Content-Type`
|
||||
header, if it has not been set by the backend, be automatically
|
||||
set to a value derived from the contents of the response. Deprecated:
|
||||
AutoDetect option is deprecated, Content-Type middleware is
|
||||
only meant to be used to enable the content-type detection,
|
||||
please remove any usage of this option.'
|
||||
type: boolean
|
||||
type: object
|
||||
digestAuth:
|
||||
description: 'DigestAuth holds the digest auth middleware configuration.
|
||||
|
@ -383,6 +391,10 @@ spec:
|
|||
description: TLS defines the configuration used to secure the
|
||||
connection to the authentication server.
|
||||
properties:
|
||||
caOptional:
|
||||
description: 'Deprecated: TLS client authentication is a server
|
||||
side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634).'
|
||||
type: boolean
|
||||
caSecret:
|
||||
description: CASecret is the name of the referenced Kubernetes
|
||||
Secret containing the CA to validate the server certificate.
|
||||
|
@ -501,6 +513,10 @@ spec:
|
|||
description: CustomResponseHeaders defines the header names and
|
||||
values to apply to the response.
|
||||
type: object
|
||||
featurePolicy:
|
||||
description: 'Deprecated: FeaturePolicy option is deprecated,
|
||||
please use PermissionsPolicy instead.'
|
||||
type: string
|
||||
forceSTSHeader:
|
||||
description: ForceSTSHeader defines whether to add the STS header
|
||||
even when the connection is HTTP.
|
||||
|
@ -536,6 +552,14 @@ spec:
|
|||
value. This allows sites to control whether browsers forward
|
||||
the Referer header to other sites.
|
||||
type: string
|
||||
sslForceHost:
|
||||
description: 'Deprecated: SSLForceHost option is deprecated, please
|
||||
use RedirectRegex instead.'
|
||||
type: boolean
|
||||
sslHost:
|
||||
description: 'Deprecated: SSLHost option is deprecated, please
|
||||
use RedirectRegex instead.'
|
||||
type: string
|
||||
sslProxyHeaders:
|
||||
additionalProperties:
|
||||
type: string
|
||||
|
@ -544,6 +568,14 @@ spec:
|
|||
useful when using other proxies (example: "X-Forwarded-Proto":
|
||||
"https").'
|
||||
type: object
|
||||
sslRedirect:
|
||||
description: 'Deprecated: SSLRedirect option is deprecated, please
|
||||
use EntryPoint redirection or RedirectScheme instead.'
|
||||
type: boolean
|
||||
sslTemporaryRedirect:
|
||||
description: 'Deprecated: SSLTemporaryRedirect option is deprecated,
|
||||
please use EntryPoint redirection or RedirectScheme instead.'
|
||||
type: boolean
|
||||
stsIncludeSubdomains:
|
||||
description: STSIncludeSubdomains defines whether the includeSubDomains
|
||||
directive is appended to the Strict-Transport-Security header.
|
||||
|
@ -915,6 +947,12 @@ spec:
|
|||
This middleware removes the specified prefixes from the URL path.
|
||||
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefix/'
|
||||
properties:
|
||||
forceSlash:
|
||||
description: 'Deprecated: ForceSlash option is deprecated, please
|
||||
remove any usage of this option. ForceSlash ensures that the
|
||||
resulting stripped path is not the empty string, by replacing
|
||||
it with / when necessary. Default: true.'
|
||||
type: boolean
|
||||
prefixes:
|
||||
description: Prefixes defines the prefixes to strip from the request
|
||||
URL.
|
||||
|
|
|
@ -46,7 +46,9 @@ spec:
|
|||
type: integer
|
||||
type: object
|
||||
ipAllowList:
|
||||
description: IPAllowList defines the IPAllowList middleware configuration.
|
||||
description: 'IPAllowList defines the IPAllowList middleware configuration.
|
||||
This middleware accepts/refuses connections based on the client
|
||||
IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/tcp/ipallowlist/'
|
||||
properties:
|
||||
sourceRange:
|
||||
description: SourceRange defines the allowed IPs (or ranges of
|
||||
|
@ -57,7 +59,8 @@ spec:
|
|||
type: object
|
||||
ipWhiteList:
|
||||
description: 'IPWhiteList defines the IPWhiteList middleware configuration.
|
||||
Deprecated: please use IPAllowList instead.'
|
||||
This middleware accepts/refuses connections based on the client
|
||||
IP. Deprecated: please use IPAllowList instead. More info: https://doc.traefik.io/traefik/v3.0/middlewares/tcp/ipwhitelist/'
|
||||
properties:
|
||||
sourceRange:
|
||||
description: SourceRange defines the allowed IPs (or ranges of
|
||||
|
|
|
@ -86,6 +86,12 @@ spec:
|
|||
will accept. Possible values: VersionTLS10, VersionTLS11, VersionTLS12,
|
||||
VersionTLS13. Default: VersionTLS10.'
|
||||
type: string
|
||||
preferServerCipherSuites:
|
||||
description: 'PreferServerCipherSuites defines whether the server
|
||||
chooses a cipher suite among his own instead of among the client''s.
|
||||
It is enabled automatically when minVersion or maxVersion is set.
|
||||
Deprecated: https://github.com/golang/go/issues/45430'
|
||||
type: boolean
|
||||
sniStrict:
|
||||
description: SniStrict defines whether Traefik allows connections
|
||||
from clients connections that do not specify a server_name extension.
|
||||
|
|
|
@ -6,6 +6,9 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
`--accesslog`:
|
||||
Access log settings. (Default: ```false```)
|
||||
|
||||
`--accesslog.addinternals`:
|
||||
Enables access log for internal services (ping, dashboard, etc...). (Default: ```false```)
|
||||
|
||||
`--accesslog.bufferingsize`:
|
||||
Number of access log lines to process in a buffered way. (Default: ```0```)
|
||||
|
||||
|
@ -180,6 +183,9 @@ Trust all. (Default: ```false```)
|
|||
`--entrypoints.<name>.proxyprotocol.trustedips`:
|
||||
Trust only selected IPs.
|
||||
|
||||
`--entrypoints.<name>.reuseport`:
|
||||
Enables EntryPoints from the same or different processes listening on the same TCP/UDP port. (Default: ```false```)
|
||||
|
||||
`--entrypoints.<name>.transport.keepalivemaxrequests`:
|
||||
Maximum number of requests before closing a keep-alive connection. (Default: ```0```)
|
||||
|
||||
|
@ -264,6 +270,9 @@ Maximum size in megabytes of the log file before it gets rotated. (Default: ```0
|
|||
`--log.nocolor`:
|
||||
When using the 'common' format, disables the colorized output. (Default: ```false```)
|
||||
|
||||
`--metrics.addinternals`:
|
||||
Enables metrics for internal services (ping, dashboard, etc...). (Default: ```false```)
|
||||
|
||||
`--metrics.datadog`:
|
||||
Datadog metrics exporter type. (Default: ```false```)
|
||||
|
||||
|
@ -990,6 +999,9 @@ Defines the allowed SPIFFE trust domain.
|
|||
`--tracing`:
|
||||
OpenTracing configuration. (Default: ```false```)
|
||||
|
||||
`--tracing.addinternals`:
|
||||
Enables tracing for internal services (ping, dashboard, etc...). (Default: ```false```)
|
||||
|
||||
`--tracing.globalattributes.<name>`:
|
||||
Defines additional attributes (key:value) on all spans.
|
||||
|
||||
|
|
|
@ -6,6 +6,9 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
`TRAEFIK_ACCESSLOG`:
|
||||
Access log settings. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_ACCESSLOG_ADDINTERNALS`:
|
||||
Enables access log for internal services (ping, dashboard, etc...). (Default: ```false```)
|
||||
|
||||
`TRAEFIK_ACCESSLOG_BUFFERINGSIZE`:
|
||||
Number of access log lines to process in a buffered way. (Default: ```0```)
|
||||
|
||||
|
@ -180,6 +183,9 @@ Trust all. (Default: ```false```)
|
|||
`TRAEFIK_ENTRYPOINTS_<NAME>_PROXYPROTOCOL_TRUSTEDIPS`:
|
||||
Trust only selected IPs.
|
||||
|
||||
`TRAEFIK_ENTRYPOINTS_<NAME>_REUSEPORT`:
|
||||
Enables EntryPoints from the same or different processes listening on the same TCP/UDP port. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_KEEPALIVEMAXREQUESTS`:
|
||||
Maximum number of requests before closing a keep-alive connection. (Default: ```0```)
|
||||
|
||||
|
@ -264,6 +270,9 @@ Maximum size in megabytes of the log file before it gets rotated. (Default: ```0
|
|||
`TRAEFIK_LOG_NOCOLOR`:
|
||||
When using the 'common' format, disables the colorized output. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_METRICS_ADDINTERNALS`:
|
||||
Enables metrics for internal services (ping, dashboard, etc...). (Default: ```false```)
|
||||
|
||||
`TRAEFIK_METRICS_DATADOG`:
|
||||
Datadog metrics exporter type. (Default: ```false```)
|
||||
|
||||
|
@ -990,6 +999,9 @@ Defines the allowed SPIFFE trust domain.
|
|||
`TRAEFIK_TRACING`:
|
||||
OpenTracing configuration. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_TRACING_ADDINTERNALS`:
|
||||
Enables tracing for internal services (ping, dashboard, etc...). (Default: ```false```)
|
||||
|
||||
`TRAEFIK_TRACING_GLOBALATTRIBUTES_<NAME>`:
|
||||
Defines additional attributes (key:value) on all spans.
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
[entryPoints]
|
||||
[entryPoints.EntryPoint0]
|
||||
address = "foobar"
|
||||
reusePort = true
|
||||
asDefault = true
|
||||
[entryPoints.EntryPoint0.transport]
|
||||
keepAliveMaxTime = "42s"
|
||||
|
@ -276,6 +277,7 @@
|
|||
disableDashboardAd = true
|
||||
|
||||
[metrics]
|
||||
addInternals = true
|
||||
[metrics.prometheus]
|
||||
buckets = [42.0, 42.0]
|
||||
addEntryPointsLabels = true
|
||||
|
@ -350,6 +352,7 @@
|
|||
filePath = "foobar"
|
||||
format = "foobar"
|
||||
bufferingSize = 42
|
||||
addInternals = true
|
||||
[accessLog.filters]
|
||||
statusCodes = ["foobar", "foobar"]
|
||||
retryAttempts = true
|
||||
|
@ -368,6 +371,7 @@
|
|||
[tracing]
|
||||
serviceName = "foobar"
|
||||
sampleRate = 42.0
|
||||
addInternals = true
|
||||
[tracing.headers]
|
||||
name0 = "foobar"
|
||||
name1 = "foobar"
|
||||
|
|
|
@ -35,6 +35,7 @@ tcpServersTransport:
|
|||
entryPoints:
|
||||
EntryPoint0:
|
||||
address: foobar
|
||||
reusePort: true
|
||||
asDefault: true
|
||||
transport:
|
||||
lifeCycle:
|
||||
|
@ -307,6 +308,7 @@ api:
|
|||
debug: true
|
||||
disableDashboardAd: true
|
||||
metrics:
|
||||
addInternals: true
|
||||
prometheus:
|
||||
buckets:
|
||||
- 42
|
||||
|
@ -398,6 +400,7 @@ accessLog:
|
|||
name0: foobar
|
||||
name1: foobar
|
||||
bufferingSize: 42
|
||||
addInternals: true
|
||||
tracing:
|
||||
serviceName: foobar
|
||||
headers:
|
||||
|
@ -407,6 +410,7 @@ tracing:
|
|||
name0: foobar
|
||||
name1: foobar
|
||||
sampleRate: 42
|
||||
addInternals: true
|
||||
otlp:
|
||||
grpc:
|
||||
endpoint: foobar
|
||||
|
|
|
@ -233,6 +233,79 @@ If both TCP and UDP are wanted for the same port, two entryPoints definitions ar
|
|||
|
||||
Full details for how to specify `address` can be found in [net.Listen](https://golang.org/pkg/net/#Listen) (and [net.Dial](https://golang.org/pkg/net/#Dial)) of the doc for go.
|
||||
|
||||
### ReusePort
|
||||
|
||||
_Optional, Default=false_
|
||||
|
||||
The `ReusePort` option enables EntryPoints from the same or different processes
|
||||
listening on the same TCP/UDP port by utilizing the `SO_REUSEPORT` socket option.
|
||||
It also allows the kernel to act like a load balancer to distribute incoming
|
||||
connections between entry points.
|
||||
|
||||
For example, you can use it with the [transport.lifeCycle](#lifecycle) to do
|
||||
canary deployments against Traefik itself. Like upgrading Traefik version or
|
||||
reloading the static configuration without any service downtime.
|
||||
|
||||
!!! warning "Supported platforms"
|
||||
|
||||
The `ReusePort` option currently works only on Linux, FreeBSD, OpenBSD and Darwin.
|
||||
It will be ignored on other platforms.
|
||||
|
||||
There is a known bug in the Linux kernel that may cause unintended TCP connection failures when using the `ReusePort` option.
|
||||
For more details, see https://lwn.net/Articles/853637/.
|
||||
|
||||
??? example "Listen on the same port"
|
||||
|
||||
```yaml tab="File (yaml)"
|
||||
entryPoints:
|
||||
web:
|
||||
address: ":80"
|
||||
reusePort: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[entryPoints.web]
|
||||
address = ":80"
|
||||
reusePort = true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--entrypoints.web.address=:80
|
||||
--entrypoints.web.reusePort=true
|
||||
```
|
||||
|
||||
Now it is possible to run multiple Traefik processes with the same EntryPoint configuration.
|
||||
|
||||
??? example "Listen on the same port but bind to a different host"
|
||||
|
||||
```yaml tab="File (yaml)"
|
||||
entryPoints:
|
||||
web:
|
||||
address: ":80"
|
||||
reusePort: true
|
||||
privateWeb:
|
||||
address: "192.168.1.2:80"
|
||||
reusePort: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[entryPoints.web]
|
||||
address = ":80"
|
||||
reusePort = true
|
||||
[entryPoints.privateWeb]
|
||||
address = "192.168.1.2:80"
|
||||
reusePort = true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--entrypoints.web.address=:80
|
||||
--entrypoints.web.reusePort=true
|
||||
--entrypoints.privateWeb.address=192.168.1.2:80
|
||||
--entrypoints.privateWeb.reusePort=true
|
||||
```
|
||||
|
||||
Requests to `192.168.1.2:80` will only be handled by routers that have `privateWeb` as the entry point.
|
||||
|
||||
### AsDefault
|
||||
|
||||
_Optional, Default=false_
|
||||
|
|
|
@ -1617,6 +1617,46 @@ Below are the available options for the PROXY protocol:
|
|||
version = 1
|
||||
```
|
||||
|
||||
#### Termination Delay
|
||||
|
||||
!!! warning
|
||||
|
||||
Deprecated in favor of [`serversTransport.terminationDelay`](#terminationdelay).
|
||||
Please note that if any `serversTransport` configuration on the servers load balancer is found,
|
||||
it will take precedence over the servers load balancer `terminationDelay` value,
|
||||
even if the `serversTransport.terminationDelay` is undefined.
|
||||
|
||||
As a proxy between a client and a server, it can happen that either side (e.g. client side) decides to terminate its writing capability on the connection (i.e. issuance of a FIN packet).
|
||||
The proxy needs to propagate that intent to the other side, and so when that happens, it also does the same on its connection with the other side (e.g. backend side).
|
||||
|
||||
However, if for some reason (bad implementation, or malicious intent) the other side does not eventually do the same as well,
|
||||
the connection would stay half-open, which would lock resources for however long.
|
||||
|
||||
To that end, as soon as the proxy enters this termination sequence, it sets a deadline on fully terminating the connections on both sides.
|
||||
|
||||
The termination delay controls that deadline.
|
||||
It is a duration in milliseconds, defaulting to 100.
|
||||
A negative value means an infinite deadline (i.e. the connection is never fully terminated by the proxy itself).
|
||||
|
||||
??? example "A Service with a termination delay -- Using the [File Provider](../../providers/file.md)"
|
||||
|
||||
```yaml tab="YAML"
|
||||
## Dynamic configuration
|
||||
tcp:
|
||||
services:
|
||||
my-service:
|
||||
loadBalancer:
|
||||
terminationDelay: 200
|
||||
```
|
||||
|
||||
```toml tab="TOML"
|
||||
## Dynamic configuration
|
||||
[tcp.services]
|
||||
[tcp.services.my-service.loadBalancer]
|
||||
[[tcp.services.my-service.loadBalancer]]
|
||||
terminationDelay = 200
|
||||
```
|
||||
|
||||
### Weighted Round Robin
|
||||
|
||||
The Weighted Round Robin (alias `WRR`) load-balancer of services is in charge of balancing the requests between multiple services based on provided weights.
|
||||
|
|
|
@ -149,11 +149,16 @@ nav:
|
|||
- 'API': 'operations/api.md'
|
||||
- 'Ping': 'operations/ping.md'
|
||||
- 'Observability':
|
||||
- 'Overview': 'observability/overview.md'
|
||||
- 'Logs': 'observability/logs.md'
|
||||
- 'Access Logs': 'observability/access-logs.md'
|
||||
- 'Metrics':
|
||||
- 'Overview': 'observability/metrics/overview.md'
|
||||
- 'Datadog': 'observability/metrics/datadog.md'
|
||||
- 'InfluxDB2': 'observability/metrics/influxdb2.md'
|
||||
- 'OpenTelemetry': 'observability/metrics/opentelemetry.md'
|
||||
- 'Prometheus': 'observability/metrics/prometheus.md'
|
||||
- 'StatsD': 'observability/metrics/statsd.md'
|
||||
- 'Tracing':
|
||||
- 'Overview': 'observability/tracing/overview.md'
|
||||
- 'OpenTelemetry': 'observability/tracing/opentelemetry.md'
|
||||
|
|
2
go.mod
2
go.mod
|
@ -80,6 +80,7 @@ require (
|
|||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
||||
golang.org/x/mod v0.13.0
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/sys v0.15.0
|
||||
golang.org/x/text v0.13.0
|
||||
golang.org/x/time v0.3.0
|
||||
golang.org/x/tools v0.14.0
|
||||
|
@ -315,7 +316,6 @@ require (
|
|||
golang.org/x/arch v0.4.0 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/oauth2 v0.13.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/term v0.13.0 // indirect
|
||||
google.golang.org/api v0.128.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
|
|
|
@ -61,7 +61,7 @@ func (s *AccessLogSuite) TestAccessLog() {
|
|||
ensureWorkingDirectoryIsClean()
|
||||
|
||||
// Start Traefik
|
||||
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
|
||||
s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
|
||||
|
||||
defer func() {
|
||||
traefikLog, err := os.ReadFile(traefikTestLogFile)
|
||||
|
@ -130,7 +130,7 @@ func (s *AccessLogSuite) TestAccessLogAuthFrontend() {
|
|||
}
|
||||
|
||||
// Start Traefik
|
||||
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
|
||||
s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
|
||||
|
||||
s.checkStatsForLogFile()
|
||||
|
||||
|
@ -194,7 +194,7 @@ func (s *AccessLogSuite) TestAccessLogDigestAuthMiddleware() {
|
|||
}
|
||||
|
||||
// Start Traefik
|
||||
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
|
||||
s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
|
||||
|
||||
s.checkStatsForLogFile()
|
||||
|
||||
|
@ -304,7 +304,7 @@ func (s *AccessLogSuite) TestAccessLogFrontendRedirect() {
|
|||
}
|
||||
|
||||
// Start Traefik
|
||||
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
|
||||
s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
|
||||
|
||||
s.checkStatsForLogFile()
|
||||
|
||||
|
@ -410,7 +410,7 @@ func (s *AccessLogSuite) TestAccessLogRateLimit() {
|
|||
}
|
||||
|
||||
// Start Traefik
|
||||
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
|
||||
s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
|
||||
|
||||
s.checkStatsForLogFile()
|
||||
|
||||
|
@ -454,7 +454,7 @@ func (s *AccessLogSuite) TestAccessLogBackendNotFound() {
|
|||
}
|
||||
|
||||
// Start Traefik
|
||||
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
|
||||
s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
|
||||
|
||||
s.waitForTraefik("server1")
|
||||
|
||||
|
@ -494,7 +494,7 @@ func (s *AccessLogSuite) TestAccessLogFrontendAllowlist() {
|
|||
}
|
||||
|
||||
// Start Traefik
|
||||
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
|
||||
s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
|
||||
|
||||
s.checkStatsForLogFile()
|
||||
|
||||
|
@ -534,7 +534,7 @@ func (s *AccessLogSuite) TestAccessLogAuthFrontendSuccess() {
|
|||
}
|
||||
|
||||
// Start Traefik
|
||||
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
|
||||
s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
|
||||
|
||||
s.checkStatsForLogFile()
|
||||
|
||||
|
@ -575,7 +575,7 @@ func (s *AccessLogSuite) TestAccessLogPreflightHeadersMiddleware() {
|
|||
}
|
||||
|
||||
// Start Traefik
|
||||
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
|
||||
s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
|
||||
|
||||
s.checkStatsForLogFile()
|
||||
|
||||
|
@ -603,6 +603,56 @@ func (s *AccessLogSuite) TestAccessLogPreflightHeadersMiddleware() {
|
|||
s.checkNoOtherTraefikProblems()
|
||||
}
|
||||
|
||||
func (s *AccessLogSuite) TestAccessLogDisabledForInternals() {
|
||||
ensureWorkingDirectoryIsClean()
|
||||
|
||||
file := s.adaptFile("fixtures/access_log/access_log_ping.toml", struct{}{})
|
||||
|
||||
// Start Traefik.
|
||||
s.traefikCmd(withConfigFile(file))
|
||||
|
||||
defer func() {
|
||||
traefikLog, err := os.ReadFile(traefikTestLogFile)
|
||||
require.NoError(s.T(), err)
|
||||
log.Info().Msg(string(traefikLog))
|
||||
}()
|
||||
|
||||
// waitForTraefik makes at least one call to the rawdata api endpoint,
|
||||
// but the logs for this endpoint are ignored in checkAccessLogOutput.
|
||||
s.waitForTraefik("customPing")
|
||||
|
||||
s.checkStatsForLogFile()
|
||||
|
||||
// Verify Traefik started OK.
|
||||
s.checkTraefikStarted()
|
||||
|
||||
// Make some requests on the internal ping router.
|
||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/ping", nil)
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||
require.NoError(s.T(), err)
|
||||
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
// Make some requests on the custom ping router.
|
||||
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/ping", nil)
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||
require.NoError(s.T(), err)
|
||||
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
// Verify access.log output as expected.
|
||||
count := s.checkAccessLogOutput()
|
||||
|
||||
require.Equal(s.T(), 0, count)
|
||||
|
||||
// Verify no other Traefik problems.
|
||||
s.checkNoOtherTraefikProblems()
|
||||
}
|
||||
|
||||
func (s *AccessLogSuite) checkNoOtherTraefikProblems() {
|
||||
traefikLog, err := os.ReadFile(traefikTestLogFile)
|
||||
require.NoError(s.T(), err)
|
||||
|
@ -612,6 +662,8 @@ func (s *AccessLogSuite) checkNoOtherTraefikProblems() {
|
|||
}
|
||||
|
||||
func (s *AccessLogSuite) checkAccessLogOutput() int {
|
||||
s.T().Helper()
|
||||
|
||||
lines := s.extractLines()
|
||||
count := 0
|
||||
for i, line := range lines {
|
||||
|
@ -624,6 +676,8 @@ func (s *AccessLogSuite) checkAccessLogOutput() int {
|
|||
}
|
||||
|
||||
func (s *AccessLogSuite) checkAccessLogExactValuesOutput(values []accessLogValue) int {
|
||||
s.T().Helper()
|
||||
|
||||
lines := s.extractLines()
|
||||
count := 0
|
||||
for i, line := range lines {
|
||||
|
@ -641,6 +695,8 @@ func (s *AccessLogSuite) checkAccessLogExactValuesOutput(values []accessLogValue
|
|||
}
|
||||
|
||||
func (s *AccessLogSuite) extractLines() []string {
|
||||
s.T().Helper()
|
||||
|
||||
accessLog, err := os.ReadFile(traefikTestAccessLogFile)
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
|
@ -656,6 +712,8 @@ func (s *AccessLogSuite) extractLines() []string {
|
|||
}
|
||||
|
||||
func (s *AccessLogSuite) checkStatsForLogFile() {
|
||||
s.T().Helper()
|
||||
|
||||
err := try.Do(1*time.Second, func() error {
|
||||
if _, errStat := os.Stat(traefikTestLogFile); errStat != nil {
|
||||
return fmt.Errorf("could not get stats for log file: %w", errStat)
|
||||
|
@ -671,6 +729,8 @@ func ensureWorkingDirectoryIsClean() {
|
|||
}
|
||||
|
||||
func (s *AccessLogSuite) checkTraefikStarted() []byte {
|
||||
s.T().Helper()
|
||||
|
||||
traefikLog, err := os.ReadFile(traefikTestLogFile)
|
||||
require.NoError(s.T(), err)
|
||||
if len(traefikLog) > 0 {
|
||||
|
@ -680,6 +740,8 @@ func (s *AccessLogSuite) checkTraefikStarted() []byte {
|
|||
}
|
||||
|
||||
func (s *BaseSuite) CheckAccessLogFormat(line string, i int) {
|
||||
s.T().Helper()
|
||||
|
||||
results, err := accesslog.ParseAccessLog(line)
|
||||
require.NoError(s.T(), err)
|
||||
assert.Len(s.T(), results, 14)
|
||||
|
@ -692,6 +754,8 @@ func (s *BaseSuite) CheckAccessLogFormat(line string, i int) {
|
|||
}
|
||||
|
||||
func (s *AccessLogSuite) checkAccessLogExactValues(line string, i int, v accessLogValue) {
|
||||
s.T().Helper()
|
||||
|
||||
results, err := accesslog.ParseAccessLog(line)
|
||||
require.NoError(s.T(), err)
|
||||
assert.Len(s.T(), results, 14)
|
||||
|
|
30
integration/fixtures/access_log/access_log_ping.toml
Normal file
30
integration/fixtures/access_log/access_log_ping.toml
Normal file
|
@ -0,0 +1,30 @@
|
|||
[global]
|
||||
checkNewVersion = false
|
||||
sendAnonymousUsage = false
|
||||
|
||||
[log]
|
||||
level = "ERROR"
|
||||
filePath = "traefik.log"
|
||||
|
||||
[accessLog]
|
||||
filePath = "access.log"
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.web]
|
||||
address = ":8000"
|
||||
|
||||
[api]
|
||||
insecure = true
|
||||
|
||||
[ping]
|
||||
|
||||
[providers]
|
||||
[providers.file]
|
||||
filename = "{{ .SelfFilename }}"
|
||||
|
||||
## dynamic configuration ##
|
||||
[http.routers]
|
||||
[http.routers.customPing]
|
||||
entryPoints = ["web"]
|
||||
rule = "PathPrefix(`/ping`)"
|
||||
service = "ping@internal"
|
|
@ -393,6 +393,18 @@ spec:
|
|||
between Traefik and your servers. Can only be used on
|
||||
a Kubernetes Service.
|
||||
type: string
|
||||
terminationDelay:
|
||||
description: 'TerminationDelay defines the deadline that
|
||||
the proxy sets, after one of its connected peers indicates
|
||||
it has closed the writing capability of its connection,
|
||||
to close the reading capability as well, hence fully
|
||||
terminating the connection. It is a duration in milliseconds,
|
||||
defaulting to 100. A negative value means an infinite
|
||||
deadline (i.e. the reading capability is never closed).
|
||||
Deprecated: TerminationDelay is not supported APIVersion
|
||||
traefik.io/v1, please use ServersTransport to configure
|
||||
the TerminationDelay instead.'
|
||||
type: integer
|
||||
tls:
|
||||
description: TLS determines whether to use TLS when dialing
|
||||
with the backend.
|
||||
|
@ -779,9 +791,17 @@ spec:
|
|||
type: object
|
||||
contentType:
|
||||
description: ContentType holds the content-type middleware configuration.
|
||||
This middleware sets the `Content-Type` header value to the media
|
||||
type detected from the response content, when it is not set by the
|
||||
backend.
|
||||
This middleware exists to enable the correct behavior until at least
|
||||
the default one can be changed in a future version.
|
||||
properties:
|
||||
autoDetect:
|
||||
description: 'AutoDetect specifies whether to let the `Content-Type`
|
||||
header, if it has not been set by the backend, be automatically
|
||||
set to a value derived from the contents of the response. Deprecated:
|
||||
AutoDetect option is deprecated, Content-Type middleware is
|
||||
only meant to be used to enable the content-type detection,
|
||||
please remove any usage of this option.'
|
||||
type: boolean
|
||||
type: object
|
||||
digestAuth:
|
||||
description: 'DigestAuth holds the digest auth middleware configuration.
|
||||
|
@ -972,6 +992,10 @@ spec:
|
|||
description: TLS defines the configuration used to secure the
|
||||
connection to the authentication server.
|
||||
properties:
|
||||
caOptional:
|
||||
description: 'Deprecated: TLS client authentication is a server
|
||||
side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634).'
|
||||
type: boolean
|
||||
caSecret:
|
||||
description: CASecret is the name of the referenced Kubernetes
|
||||
Secret containing the CA to validate the server certificate.
|
||||
|
@ -1090,6 +1114,10 @@ spec:
|
|||
description: CustomResponseHeaders defines the header names and
|
||||
values to apply to the response.
|
||||
type: object
|
||||
featurePolicy:
|
||||
description: 'Deprecated: FeaturePolicy option is deprecated,
|
||||
please use PermissionsPolicy instead.'
|
||||
type: string
|
||||
forceSTSHeader:
|
||||
description: ForceSTSHeader defines whether to add the STS header
|
||||
even when the connection is HTTP.
|
||||
|
@ -1125,6 +1153,14 @@ spec:
|
|||
value. This allows sites to control whether browsers forward
|
||||
the Referer header to other sites.
|
||||
type: string
|
||||
sslForceHost:
|
||||
description: 'Deprecated: SSLForceHost option is deprecated, please
|
||||
use RedirectRegex instead.'
|
||||
type: boolean
|
||||
sslHost:
|
||||
description: 'Deprecated: SSLHost option is deprecated, please
|
||||
use RedirectRegex instead.'
|
||||
type: string
|
||||
sslProxyHeaders:
|
||||
additionalProperties:
|
||||
type: string
|
||||
|
@ -1133,6 +1169,14 @@ spec:
|
|||
useful when using other proxies (example: "X-Forwarded-Proto":
|
||||
"https").'
|
||||
type: object
|
||||
sslRedirect:
|
||||
description: 'Deprecated: SSLRedirect option is deprecated, please
|
||||
use EntryPoint redirection or RedirectScheme instead.'
|
||||
type: boolean
|
||||
sslTemporaryRedirect:
|
||||
description: 'Deprecated: SSLTemporaryRedirect option is deprecated,
|
||||
please use EntryPoint redirection or RedirectScheme instead.'
|
||||
type: boolean
|
||||
stsIncludeSubdomains:
|
||||
description: STSIncludeSubdomains defines whether the includeSubDomains
|
||||
directive is appended to the Strict-Transport-Security header.
|
||||
|
@ -1504,6 +1548,12 @@ spec:
|
|||
This middleware removes the specified prefixes from the URL path.
|
||||
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefix/'
|
||||
properties:
|
||||
forceSlash:
|
||||
description: 'Deprecated: ForceSlash option is deprecated, please
|
||||
remove any usage of this option. ForceSlash ensures that the
|
||||
resulting stripped path is not the empty string, by replacing
|
||||
it with / when necessary. Default: true.'
|
||||
type: boolean
|
||||
prefixes:
|
||||
description: Prefixes defines the prefixes to strip from the request
|
||||
URL.
|
||||
|
@ -1578,7 +1628,9 @@ spec:
|
|||
type: integer
|
||||
type: object
|
||||
ipAllowList:
|
||||
description: IPAllowList defines the IPAllowList middleware configuration.
|
||||
description: 'IPAllowList defines the IPAllowList middleware configuration.
|
||||
This middleware accepts/refuses connections based on the client
|
||||
IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/tcp/ipallowlist/'
|
||||
properties:
|
||||
sourceRange:
|
||||
description: SourceRange defines the allowed IPs (or ranges of
|
||||
|
@ -1589,7 +1641,8 @@ spec:
|
|||
type: object
|
||||
ipWhiteList:
|
||||
description: 'IPWhiteList defines the IPWhiteList middleware configuration.
|
||||
Deprecated: please use IPAllowList instead.'
|
||||
This middleware accepts/refuses connections based on the client
|
||||
IP. Deprecated: please use IPAllowList instead. More info: https://doc.traefik.io/traefik/v3.0/middlewares/tcp/ipwhitelist/'
|
||||
properties:
|
||||
sourceRange:
|
||||
description: SourceRange defines the allowed IPs (or ranges of
|
||||
|
@ -1940,6 +1993,12 @@ spec:
|
|||
will accept. Possible values: VersionTLS10, VersionTLS11, VersionTLS12,
|
||||
VersionTLS13. Default: VersionTLS10.'
|
||||
type: string
|
||||
preferServerCipherSuites:
|
||||
description: 'PreferServerCipherSuites defines whether the server
|
||||
chooses a cipher suite among his own instead of among the client''s.
|
||||
It is enabled automatically when minVersion or maxVersion is set.
|
||||
Deprecated: https://github.com/golang/go/issues/45430'
|
||||
type: boolean
|
||||
sniStrict:
|
||||
description: SniStrict defines whether Traefik allows connections
|
||||
from clients connections that do not specify a server_name extension.
|
||||
|
|
|
@ -19,5 +19,6 @@
|
|||
insecure = true
|
||||
|
||||
[metrics]
|
||||
addInternals = true
|
||||
[metrics.prometheus]
|
||||
buckets = [0.1,0.3,1.2,5.0]
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
[api]
|
||||
insecure = true
|
||||
|
||||
[ping]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.web]
|
||||
address = ":8000"
|
||||
|
@ -47,6 +49,10 @@
|
|||
Service = "service3"
|
||||
Middlewares = ["retry", "basic-auth"]
|
||||
Rule = "Path(`/auth`)"
|
||||
[http.routers.customPing]
|
||||
entryPoints = ["web"]
|
||||
rule = "PathPrefix(`/ping`)"
|
||||
service = "ping@internal"
|
||||
|
||||
[http.middlewares]
|
||||
[http.middlewares.retry.retry]
|
||||
|
|
|
@ -153,17 +153,16 @@ func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() {
|
|||
RequiredConsecutiveSuccesses: 0,
|
||||
},
|
||||
SupportedFeatures: sets.New[ksuite.SupportedFeature]().
|
||||
Insert(ksuite.GatewayCoreFeatures.UnsortedList()...),
|
||||
Insert(ksuite.GatewayCoreFeatures.UnsortedList()...).
|
||||
Insert(ksuite.ReferenceGrantCoreFeatures.UnsortedList()...),
|
||||
EnableAllSupportedFeatures: false,
|
||||
RunTest: *k8sConformanceRunTest,
|
||||
// Until the feature are all supported, following tests are skipped.
|
||||
SkipTests: []string{
|
||||
"HTTPExactPathMatching",
|
||||
"HTTPRouteHostnameIntersection",
|
||||
"GatewaySecretReferenceGrantAllInNamespace",
|
||||
"HTTPRouteListenerHostnameMatching",
|
||||
"HTTPRouteRequestHeaderModifier",
|
||||
"GatewaySecretInvalidReferenceGrant",
|
||||
"GatewayClassObservedGenerationBump",
|
||||
"HTTPRouteInvalidNonExistentBackendRef",
|
||||
"GatewayWithAttachedRoutes",
|
||||
|
@ -171,14 +170,11 @@ func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() {
|
|||
"HTTPRouteDisallowedKind",
|
||||
"HTTPRouteInvalidReferenceGrant",
|
||||
"HTTPRouteObservedGenerationBump",
|
||||
"GatewayInvalidRouteKind",
|
||||
"TLSRouteSimpleSameNamespace",
|
||||
"TLSRouteInvalidReferenceGrant",
|
||||
"HTTPRouteInvalidCrossNamespaceParentRef",
|
||||
"HTTPRouteInvalidParentRefNotMatchingSectionName",
|
||||
"GatewaySecretReferenceGrantSpecific",
|
||||
"GatewayModifyListeners",
|
||||
"GatewaySecretMissingReferenceGrant",
|
||||
"GatewayInvalidTLSConfiguration",
|
||||
"HTTPRouteInvalidCrossNamespaceBackendRef",
|
||||
"HTTPRouteMatchingAcrossRoutes",
|
||||
|
|
|
@ -57,7 +57,7 @@ func (s *LogRotationSuite) TearDownSuite() {
|
|||
|
||||
func (s *LogRotationSuite) TestAccessLogRotation() {
|
||||
// Start Traefik
|
||||
cmd, _ := s.cmdTraefik(withConfigFile("fixtures/access_log_config.toml"))
|
||||
cmd, _ := s.cmdTraefik(withConfigFile("fixtures/access_log/access_log_base.toml"))
|
||||
defer s.displayTraefikLogFile(traefikTestLogFile)
|
||||
|
||||
// Verify Traefik started ok
|
||||
|
|
|
@ -287,6 +287,10 @@ func (s *SimpleSuite) TestMetricsPrometheusDefaultEntryPoint() {
|
|||
|
||||
err = try.GetRequest("http://127.0.0.1:8080/metrics", 1*time.Second, try.BodyContains("_service_"))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
// No metrics for internals.
|
||||
err = try.GetRequest("http://127.0.0.1:8080/metrics", 1*time.Second, try.BodyNotContains("router=\"api@internal\"", "service=\"api@internal\""))
|
||||
require.NoError(s.T(), err)
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestMetricsPrometheusTwoRoutersOneService() {
|
||||
|
|
|
@ -414,6 +414,67 @@ func (s *TracingSuite) TestOpentelemetryAuth() {
|
|||
s.checkTraceContent(contains)
|
||||
}
|
||||
|
||||
func (s *TracingSuite) TestNoInternals() {
|
||||
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
|
||||
WhoamiIP: s.whoamiIP,
|
||||
WhoamiPort: s.whoamiPort,
|
||||
IP: s.otelCollectorIP,
|
||||
IsHTTP: true,
|
||||
})
|
||||
|
||||
s.traefikCmd(withConfigFile(file))
|
||||
|
||||
// wait for traefik
|
||||
err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth"))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/ping", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
|
||||
require.NoError(s.T(), err)
|
||||
err = try.GetRequest("http://127.0.0.1:8080/ping", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
baseURL, err := url.Parse("http://" + s.tempoIP + ":3200/api/search")
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
req := &http.Request{
|
||||
Method: http.MethodGet,
|
||||
URL: baseURL,
|
||||
}
|
||||
// Wait for traces to be available.
|
||||
time.Sleep(10 * time.Second)
|
||||
resp, err := try.Response(req, 5*time.Second)
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
out := &TraceResponse{}
|
||||
content, err := io.ReadAll(resp.Body)
|
||||
require.NoError(s.T(), err)
|
||||
err = json.Unmarshal(content, &out)
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
s.NotEmptyf(len(out.Traces), "expected at least one trace")
|
||||
|
||||
for _, t := range out.Traces {
|
||||
baseURL, err := url.Parse("http://" + s.tempoIP + ":3200/api/traces/" + t.TraceID)
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
req := &http.Request{
|
||||
Method: http.MethodGet,
|
||||
URL: baseURL,
|
||||
}
|
||||
|
||||
resp, err := try.Response(req, 5*time.Second)
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
content, err := io.ReadAll(resp.Body)
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
require.NotContains(s.T(), content, "@internal")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TracingSuite) checkTraceContent(expectedJSON []map[string]string) {
|
||||
s.T().Helper()
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package dynamic
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v3/pkg/ip"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
@ -54,9 +54,13 @@ type GrpcWeb struct {
|
|||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// ContentType holds the content-type middleware configuration.
|
||||
// This middleware sets the `Content-Type` header value to the media type detected from the response content,
|
||||
// when it is not set by the backend.
|
||||
type ContentType struct{}
|
||||
// This middleware exists to enable the correct behavior until at least the default one can be changed in a future version.
|
||||
type ContentType struct {
|
||||
// AutoDetect specifies whether to let the `Content-Type` header, if it has not been set by the backend,
|
||||
// be automatically set to a value derived from the contents of the response.
|
||||
// Deprecated: AutoDetect option is deprecated, Content-Type middleware is only meant to be used to enable the content-type detection, please remove any usage of this option.
|
||||
AutoDetect *bool `json:"autoDetect,omitempty" toml:"autoDetect,omitempty" yaml:"autoDetect,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
|
@ -141,6 +145,8 @@ type CircuitBreaker struct {
|
|||
FallbackDuration ptypes.Duration `json:"fallbackDuration,omitempty" toml:"fallbackDuration,omitempty" yaml:"fallbackDuration,omitempty" export:"true"`
|
||||
// RecoveryDuration is the duration for which the circuit breaker will try to recover (as soon as it is in recovering state).
|
||||
RecoveryDuration ptypes.Duration `json:"recoveryDuration,omitempty" toml:"recoveryDuration,omitempty" yaml:"recoveryDuration,omitempty" export:"true"`
|
||||
// ResponseCode is the status code that the circuit breaker will return while it is in the open state.
|
||||
ResponseCode int `json:"responseCode,omitempty" toml:"responseCode,omitempty" yaml:"responseCode,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values on a RateLimit.
|
||||
|
@ -148,6 +154,7 @@ func (c *CircuitBreaker) SetDefaults() {
|
|||
c.CheckPeriod = ptypes.Duration(100 * time.Millisecond)
|
||||
c.FallbackDuration = ptypes.Duration(10 * time.Second)
|
||||
c.RecoveryDuration = ptypes.Duration(10 * time.Second)
|
||||
c.ResponseCode = http.StatusServiceUnavailable
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
@ -214,7 +221,7 @@ type ForwardAuth struct {
|
|||
// Address defines the authentication server address.
|
||||
Address string `json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"`
|
||||
// TLS defines the configuration used to secure the connection to the authentication server.
|
||||
TLS *types.ClientTLS `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
|
||||
TLS *ClientTLS `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
|
||||
// TrustForwardHeader defines whether to trust (ie: forward) all X-Forwarded-* headers.
|
||||
TrustForwardHeader bool `json:"trustForwardHeader,omitempty" toml:"trustForwardHeader,omitempty" yaml:"trustForwardHeader,omitempty" export:"true"`
|
||||
// AuthResponseHeaders defines the list of headers to copy from the authentication server response and set on forwarded request, replacing any existing conflicting headers.
|
||||
|
@ -231,6 +238,20 @@ type ForwardAuth struct {
|
|||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// ClientTLS holds TLS specific configurations as client
|
||||
// CA, Cert and Key can be either path or file contents.
|
||||
// TODO: remove this struct when CAOptional option will be removed.
|
||||
type ClientTLS struct {
|
||||
CA string `description:"TLS CA" json:"ca,omitempty" toml:"ca,omitempty" yaml:"ca,omitempty"`
|
||||
Cert string `description:"TLS cert" json:"cert,omitempty" toml:"cert,omitempty" yaml:"cert,omitempty"`
|
||||
Key string `description:"TLS key" json:"key,omitempty" toml:"key,omitempty" yaml:"key,omitempty" loggable:"false"`
|
||||
InsecureSkipVerify bool `description:"TLS insecure skip verify" json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" export:"true"`
|
||||
// Deprecated: TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634).
|
||||
CAOptional *bool `description:"TLS CA.Optional" json:"caOptional,omitempty" toml:"caOptional,omitempty" yaml:"caOptional,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// Headers holds the headers middleware configuration.
|
||||
// This middleware manages the requests and responses headers.
|
||||
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/headers/#customrequestheaders
|
||||
|
@ -299,6 +320,17 @@ type Headers struct {
|
|||
// If you would like your development environment to mimic production with complete Host blocking, SSL redirects,
|
||||
// and STS headers, leave this as false.
|
||||
IsDevelopment bool `json:"isDevelopment,omitempty" toml:"isDevelopment,omitempty" yaml:"isDevelopment,omitempty" export:"true"`
|
||||
|
||||
// Deprecated: FeaturePolicy option is deprecated, please use PermissionsPolicy instead.
|
||||
FeaturePolicy *string `json:"featurePolicy,omitempty" toml:"featurePolicy,omitempty" yaml:"featurePolicy,omitempty" export:"true"`
|
||||
// Deprecated: SSLRedirect option is deprecated, please use EntryPoint redirection or RedirectScheme instead.
|
||||
SSLRedirect *bool `json:"sslRedirect,omitempty" toml:"sslRedirect,omitempty" yaml:"sslRedirect,omitempty" export:"true"`
|
||||
// Deprecated: SSLTemporaryRedirect option is deprecated, please use EntryPoint redirection or RedirectScheme instead.
|
||||
SSLTemporaryRedirect *bool `json:"sslTemporaryRedirect,omitempty" toml:"sslTemporaryRedirect,omitempty" yaml:"sslTemporaryRedirect,omitempty" export:"true"`
|
||||
// Deprecated: SSLHost option is deprecated, please use RedirectRegex instead.
|
||||
SSLHost *string `json:"sslHost,omitempty" toml:"sslHost,omitempty" yaml:"sslHost,omitempty"`
|
||||
// Deprecated: SSLForceHost option is deprecated, please use RedirectRegex instead.
|
||||
SSLForceHost *bool `json:"sslForceHost,omitempty" toml:"sslForceHost,omitempty" yaml:"sslForceHost,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// HasCustomHeadersDefined checks to see if any of the custom header elements have been set.
|
||||
|
@ -323,6 +355,10 @@ func (h *Headers) HasCorsHeadersDefined() bool {
|
|||
func (h *Headers) HasSecureHeadersDefined() bool {
|
||||
return h != nil && (len(h.AllowedHosts) != 0 ||
|
||||
len(h.HostsProxyHeaders) != 0 ||
|
||||
(h.SSLRedirect != nil && *h.SSLRedirect) ||
|
||||
(h.SSLTemporaryRedirect != nil && *h.SSLTemporaryRedirect) ||
|
||||
(h.SSLForceHost != nil && *h.SSLForceHost) ||
|
||||
(h.SSLHost != nil && *h.SSLHost != "") ||
|
||||
len(h.SSLProxyHeaders) != 0 ||
|
||||
h.STSSeconds != 0 ||
|
||||
h.STSIncludeSubdomains ||
|
||||
|
@ -336,6 +372,7 @@ func (h *Headers) HasSecureHeadersDefined() bool {
|
|||
h.ContentSecurityPolicy != "" ||
|
||||
h.PublicKey != "" ||
|
||||
h.ReferrerPolicy != "" ||
|
||||
(h.FeaturePolicy != nil && *h.FeaturePolicy != "") ||
|
||||
h.PermissionsPolicy != "" ||
|
||||
h.IsDevelopment)
|
||||
}
|
||||
|
@ -553,6 +590,11 @@ type Retry struct {
|
|||
type StripPrefix struct {
|
||||
// Prefixes defines the prefixes to strip from the request URL.
|
||||
Prefixes []string `json:"prefixes,omitempty" toml:"prefixes,omitempty" yaml:"prefixes,omitempty" export:"true"`
|
||||
|
||||
// Deprecated: ForceSlash option is deprecated, please remove any usage of this option.
|
||||
// ForceSlash ensures that the resulting stripped path is not the empty string, by replacing it with / when necessary.
|
||||
// Default: true.
|
||||
ForceSlash *bool `json:"forceSlash,omitempty" toml:"forceSlash,omitempty" yaml:"forceSlash,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
|
|
@ -86,6 +86,14 @@ type TCPServersLoadBalancer struct {
|
|||
ProxyProtocol *ProxyProtocol `json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
||||
Servers []TCPServer `json:"servers,omitempty" toml:"servers,omitempty" yaml:"servers,omitempty" label-slice-as-struct:"server" export:"true"`
|
||||
ServersTransport string `json:"serversTransport,omitempty" toml:"serversTransport,omitempty" yaml:"serversTransport,omitempty" export:"true"`
|
||||
|
||||
// TerminationDelay, corresponds to the deadline that the proxy sets, after one
|
||||
// of its connected peers indicates it has closed the writing capability of its
|
||||
// connection, to close the reading capability as well, hence fully terminating the
|
||||
// connection. It is a duration in milliseconds, defaulting to 100. A negative value
|
||||
// means an infinite deadline (i.e. the reading capability is never closed).
|
||||
// Deprecated: use ServersTransport to configure the TerminationDelay instead.
|
||||
TerminationDelay *int `json:"terminationDelay,omitempty" toml:"terminationDelay,omitempty" yaml:"terminationDelay,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// Mergeable tells if the given service is mergeable.
|
||||
|
|
|
@ -124,6 +124,27 @@ func (in *CircuitBreaker) DeepCopy() *CircuitBreaker {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClientTLS) DeepCopyInto(out *ClientTLS) {
|
||||
*out = *in
|
||||
if in.CAOptional != nil {
|
||||
in, out := &in.CAOptional, &out.CAOptional
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientTLS.
|
||||
func (in *ClientTLS) DeepCopy() *ClientTLS {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ClientTLS)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Compress) DeepCopyInto(out *Compress) {
|
||||
*out = *in
|
||||
|
@ -219,6 +240,11 @@ func (in Configurations) DeepCopy() Configurations {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ContentType) DeepCopyInto(out *ContentType) {
|
||||
*out = *in
|
||||
if in.AutoDetect != nil {
|
||||
in, out := &in.AutoDetect, &out.AutoDetect
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -316,8 +342,8 @@ func (in *ForwardAuth) DeepCopyInto(out *ForwardAuth) {
|
|||
*out = *in
|
||||
if in.TLS != nil {
|
||||
in, out := &in.TLS, &out.TLS
|
||||
*out = new(types.ClientTLS)
|
||||
**out = **in
|
||||
*out = new(ClientTLS)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.AuthResponseHeaders != nil {
|
||||
in, out := &in.AuthResponseHeaders, &out.AuthResponseHeaders
|
||||
|
@ -534,6 +560,31 @@ func (in *Headers) DeepCopyInto(out *Headers) {
|
|||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.FeaturePolicy != nil {
|
||||
in, out := &in.FeaturePolicy, &out.FeaturePolicy
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.SSLRedirect != nil {
|
||||
in, out := &in.SSLRedirect, &out.SSLRedirect
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.SSLTemporaryRedirect != nil {
|
||||
in, out := &in.SSLTemporaryRedirect, &out.SSLTemporaryRedirect
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.SSLHost != nil {
|
||||
in, out := &in.SSLHost, &out.SSLHost
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.SSLForceHost != nil {
|
||||
in, out := &in.SSLForceHost, &out.SSLForceHost
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -794,7 +845,7 @@ func (in *Middleware) DeepCopyInto(out *Middleware) {
|
|||
if in.ContentType != nil {
|
||||
in, out := &in.ContentType, &out.ContentType
|
||||
*out = new(ContentType)
|
||||
**out = **in
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.GrpcWeb != nil {
|
||||
in, out := &in.GrpcWeb, &out.GrpcWeb
|
||||
|
@ -1362,6 +1413,11 @@ func (in *StripPrefix) DeepCopyInto(out *StripPrefix) {
|
|||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ForceSlash != nil {
|
||||
in, out := &in.ForceSlash, &out.ForceSlash
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1652,6 +1708,11 @@ func (in *TCPServersLoadBalancer) DeepCopyInto(out *TCPServersLoadBalancer) {
|
|||
*out = make([]TCPServer, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.TerminationDelay != nil {
|
||||
in, out := &in.TerminationDelay, &out.TerminationDelay
|
||||
*out = new(int)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -9,9 +9,11 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
)
|
||||
|
||||
func Bool(v bool) *bool { return &v }
|
||||
func String(v string) *string { return &v }
|
||||
|
||||
func TestDecodeConfiguration(t *testing.T) {
|
||||
labels := map[string]string{
|
||||
"traefik.http.middlewares.Middleware0.addprefix.prefix": "foobar",
|
||||
|
@ -30,6 +32,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
"traefik.HTTP.Middlewares.Middleware4.circuitbreaker.checkperiod": "1s",
|
||||
"traefik.HTTP.Middlewares.Middleware4.circuitbreaker.fallbackduration": "1s",
|
||||
"traefik.HTTP.Middlewares.Middleware4.circuitbreaker.recoveryduration": "1s",
|
||||
"traefik.HTTP.Middlewares.Middleware4.circuitbreaker.responsecode": "403",
|
||||
"traefik.http.middlewares.Middleware5.digestauth.headerfield": "foobar",
|
||||
"traefik.http.middlewares.Middleware5.digestauth.realm": "foobar",
|
||||
"traefik.http.middlewares.Middleware5.digestauth.removeheader": "true",
|
||||
|
@ -42,6 +45,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
"traefik.http.middlewares.Middleware7.forwardauth.authresponseheaders": "foobar, fiibar",
|
||||
"traefik.http.middlewares.Middleware7.forwardauth.authrequestheaders": "foobar, fiibar",
|
||||
"traefik.http.middlewares.Middleware7.forwardauth.tls.ca": "foobar",
|
||||
"traefik.http.middlewares.Middleware7.forwardauth.tls.caoptional": "true",
|
||||
"traefik.http.middlewares.Middleware7.forwardauth.tls.cert": "foobar",
|
||||
"traefik.http.middlewares.Middleware7.forwardauth.tls.insecureskipverify": "true",
|
||||
"traefik.http.middlewares.Middleware7.forwardauth.tls.key": "foobar",
|
||||
|
@ -70,9 +74,14 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
"traefik.http.middlewares.Middleware8.headers.isdevelopment": "true",
|
||||
"traefik.http.middlewares.Middleware8.headers.publickey": "foobar",
|
||||
"traefik.http.middlewares.Middleware8.headers.referrerpolicy": "foobar",
|
||||
"traefik.http.middlewares.Middleware8.headers.featurepolicy": "foobar",
|
||||
"traefik.http.middlewares.Middleware8.headers.permissionspolicy": "foobar",
|
||||
"traefik.http.middlewares.Middleware8.headers.sslforcehost": "true",
|
||||
"traefik.http.middlewares.Middleware8.headers.sslhost": "foobar",
|
||||
"traefik.http.middlewares.Middleware8.headers.sslproxyheaders.name0": "foobar",
|
||||
"traefik.http.middlewares.Middleware8.headers.sslproxyheaders.name1": "foobar",
|
||||
"traefik.http.middlewares.Middleware8.headers.sslredirect": "true",
|
||||
"traefik.http.middlewares.Middleware8.headers.ssltemporaryredirect": "true",
|
||||
"traefik.http.middlewares.Middleware8.headers.stsincludesubdomains": "true",
|
||||
"traefik.http.middlewares.Middleware8.headers.stspreload": "true",
|
||||
"traefik.http.middlewares.Middleware8.headers.stsseconds": "42",
|
||||
|
@ -123,6 +132,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
"traefik.http.middlewares.Middleware16.retry.attempts": "42",
|
||||
"traefik.http.middlewares.Middleware16.retry.initialinterval": "1s",
|
||||
"traefik.http.middlewares.Middleware17.stripprefix.prefixes": "foobar, fiibar",
|
||||
"traefik.http.middlewares.Middleware17.stripprefix.forceslash": "true",
|
||||
"traefik.http.middlewares.Middleware18.stripprefixregex.regex": "foobar, fiibar",
|
||||
"traefik.http.middlewares.Middleware19.compress.minresponsebodybytes": "42",
|
||||
"traefik.http.middlewares.Middleware20.plugin.tomato.aaa": "foo1",
|
||||
|
@ -193,9 +203,11 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
"traefik.tcp.routers.Router1.tls.options": "foo",
|
||||
"traefik.tcp.routers.Router1.tls.passthrough": "false",
|
||||
"traefik.tcp.services.Service0.loadbalancer.server.Port": "42",
|
||||
"traefik.tcp.services.Service0.loadbalancer.TerminationDelay": "42",
|
||||
"traefik.tcp.services.Service0.loadbalancer.proxyProtocol.version": "42",
|
||||
"traefik.tcp.services.Service0.loadbalancer.serversTransport": "foo",
|
||||
"traefik.tcp.services.Service1.loadbalancer.server.Port": "42",
|
||||
"traefik.tcp.services.Service1.loadbalancer.TerminationDelay": "42",
|
||||
"traefik.tcp.services.Service1.loadbalancer.proxyProtocol": "true",
|
||||
"traefik.tcp.services.Service1.loadbalancer.serversTransport": "foo",
|
||||
|
||||
|
@ -260,6 +272,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
Port: "42",
|
||||
},
|
||||
},
|
||||
TerminationDelay: func(i int) *int { return &i }(42),
|
||||
ProxyProtocol: &dynamic.ProxyProtocol{Version: 42},
|
||||
ServersTransport: "foo",
|
||||
},
|
||||
|
@ -271,6 +284,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
Port: "42",
|
||||
},
|
||||
},
|
||||
TerminationDelay: func(i int) *int { return &i }(42),
|
||||
ProxyProtocol: &dynamic.ProxyProtocol{Version: 2},
|
||||
ServersTransport: "foo",
|
||||
},
|
||||
|
@ -458,6 +472,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
ForceSlash: Bool(true),
|
||||
},
|
||||
},
|
||||
"Middleware18": {
|
||||
|
@ -496,6 +511,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
CheckPeriod: ptypes.Duration(time.Second),
|
||||
FallbackDuration: ptypes.Duration(time.Second),
|
||||
RecoveryDuration: ptypes.Duration(time.Second),
|
||||
ResponseCode: 403,
|
||||
},
|
||||
},
|
||||
"Middleware5": {
|
||||
|
@ -523,11 +539,12 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
"Middleware7": {
|
||||
ForwardAuth: &dynamic.ForwardAuth{
|
||||
Address: "foobar",
|
||||
TLS: &types.ClientTLS{
|
||||
TLS: &dynamic.ClientTLS{
|
||||
CA: "foobar",
|
||||
Cert: "foobar",
|
||||
Key: "foobar",
|
||||
InsecureSkipVerify: true,
|
||||
CAOptional: Bool(true),
|
||||
},
|
||||
TrustForwardHeader: true,
|
||||
AuthResponseHeaders: []string{
|
||||
|
@ -581,10 +598,14 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
SSLRedirect: Bool(true),
|
||||
SSLTemporaryRedirect: Bool(true),
|
||||
SSLHost: String("foobar"),
|
||||
SSLProxyHeaders: map[string]string{
|
||||
"name0": "foobar",
|
||||
"name1": "foobar",
|
||||
},
|
||||
SSLForceHost: Bool(true),
|
||||
STSSeconds: 42,
|
||||
STSIncludeSubdomains: true,
|
||||
STSPreload: true,
|
||||
|
@ -597,6 +618,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
ContentSecurityPolicy: "foobar",
|
||||
PublicKey: "foobar",
|
||||
ReferrerPolicy: "foobar",
|
||||
FeaturePolicy: String("foobar"),
|
||||
PermissionsPolicy: "foobar",
|
||||
IsDevelopment: true,
|
||||
},
|
||||
|
@ -756,6 +778,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
ServersTransport: "foo",
|
||||
TerminationDelay: func(i int) *int { return &i }(42),
|
||||
},
|
||||
},
|
||||
"Service1": {
|
||||
|
@ -766,6 +789,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
ServersTransport: "foo",
|
||||
TerminationDelay: func(i int) *int { return &i }(42),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -950,6 +974,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
ForceSlash: Bool(true),
|
||||
},
|
||||
},
|
||||
"Middleware18": {
|
||||
|
@ -996,6 +1021,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
CheckPeriod: ptypes.Duration(time.Second),
|
||||
FallbackDuration: ptypes.Duration(time.Second),
|
||||
RecoveryDuration: ptypes.Duration(time.Second),
|
||||
ResponseCode: 404,
|
||||
},
|
||||
},
|
||||
"Middleware5": {
|
||||
|
@ -1023,11 +1049,12 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
"Middleware7": {
|
||||
ForwardAuth: &dynamic.ForwardAuth{
|
||||
Address: "foobar",
|
||||
TLS: &types.ClientTLS{
|
||||
TLS: &dynamic.ClientTLS{
|
||||
CA: "foobar",
|
||||
Cert: "foobar",
|
||||
Key: "foobar",
|
||||
InsecureSkipVerify: true,
|
||||
CAOptional: Bool(true),
|
||||
},
|
||||
TrustForwardHeader: true,
|
||||
AuthResponseHeaders: []string{
|
||||
|
@ -1081,10 +1108,14 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
SSLRedirect: Bool(true),
|
||||
SSLTemporaryRedirect: Bool(true),
|
||||
SSLHost: String("foobar"),
|
||||
SSLProxyHeaders: map[string]string{
|
||||
"name0": "foobar",
|
||||
"name1": "foobar",
|
||||
},
|
||||
SSLForceHost: Bool(true),
|
||||
STSSeconds: 42,
|
||||
STSIncludeSubdomains: true,
|
||||
STSPreload: true,
|
||||
|
@ -1097,6 +1128,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
ContentSecurityPolicy: "foobar",
|
||||
PublicKey: "foobar",
|
||||
ReferrerPolicy: "foobar",
|
||||
FeaturePolicy: String("foobar"),
|
||||
PermissionsPolicy: "foobar",
|
||||
IsDevelopment: true,
|
||||
},
|
||||
|
@ -1206,6 +1238,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
"traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.CheckPeriod": "1000000000",
|
||||
"traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.FallbackDuration": "1000000000",
|
||||
"traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.RecoveryDuration": "1000000000",
|
||||
"traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.ResponseCode": "404",
|
||||
"traefik.HTTP.Middlewares.Middleware5.DigestAuth.HeaderField": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware5.DigestAuth.Realm": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware5.DigestAuth.RemoveHeader": "true",
|
||||
|
@ -1218,6 +1251,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.AuthResponseHeaders": "foobar, fiibar",
|
||||
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.AuthRequestHeaders": "foobar, fiibar",
|
||||
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.CA": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.CAOptional": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.Cert": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.InsecureSkipVerify": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.Key": "foobar",
|
||||
|
@ -1246,9 +1280,14 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
"traefik.HTTP.Middlewares.Middleware8.Headers.IsDevelopment": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.PublicKey": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.ReferrerPolicy": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.FeaturePolicy": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.PermissionsPolicy": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.SSLForceHost": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.SSLHost": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.SSLProxyHeaders.name0": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.SSLProxyHeaders.name1": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.SSLRedirect": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.SSLTemporaryRedirect": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.STSIncludeSubdomains": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.STSPreload": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.STSSeconds": "42",
|
||||
|
@ -1300,6 +1339,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
"traefik.HTTP.Middlewares.Middleware16.Retry.Attempts": "42",
|
||||
"traefik.HTTP.Middlewares.Middleware16.Retry.InitialInterval": "1000000000",
|
||||
"traefik.HTTP.Middlewares.Middleware17.StripPrefix.Prefixes": "foobar, fiibar",
|
||||
"traefik.HTTP.Middlewares.Middleware17.StripPrefix.ForceSlash": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware18.StripPrefixRegex.Regex": "foobar, fiibar",
|
||||
"traefik.HTTP.Middlewares.Middleware19.Compress.MinResponseBodyBytes": "42",
|
||||
"traefik.HTTP.Middlewares.Middleware20.Plugin.tomato.aaa": "foo1",
|
||||
|
@ -1369,9 +1409,11 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
"traefik.TCP.Services.Service0.LoadBalancer.server.Port": "42",
|
||||
"traefik.TCP.Services.Service0.LoadBalancer.server.TLS": "false",
|
||||
"traefik.TCP.Services.Service0.LoadBalancer.ServersTransport": "foo",
|
||||
"traefik.TCP.Services.Service0.LoadBalancer.TerminationDelay": "42",
|
||||
"traefik.TCP.Services.Service1.LoadBalancer.server.Port": "42",
|
||||
"traefik.TCP.Services.Service1.LoadBalancer.server.TLS": "false",
|
||||
"traefik.TCP.Services.Service1.LoadBalancer.ServersTransport": "foo",
|
||||
"traefik.TCP.Services.Service1.LoadBalancer.TerminationDelay": "42",
|
||||
|
||||
"traefik.UDP.Routers.Router0.EntryPoints": "foobar, fiibar",
|
||||
"traefik.UDP.Routers.Router0.Service": "foobar",
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
// EntryPoint holds the entry point configuration.
|
||||
type EntryPoint struct {
|
||||
Address string `description:"Entry point address." json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"`
|
||||
ReusePort bool `description:"Enables EntryPoints from the same or different processes listening on the same TCP/UDP port." json:"reusePort,omitempty" toml:"reusePort,omitempty" yaml:"reusePort,omitempty"`
|
||||
AsDefault bool `description:"Adds this EntryPoint to the list of default EntryPoints to be used on routers that don't have any Entrypoint defined." json:"asDefault,omitempty" toml:"asDefault,omitempty" yaml:"asDefault,omitempty"`
|
||||
Transport *EntryPointsTransport `description:"Configures communication between clients and Traefik." json:"transport,omitempty" toml:"transport,omitempty" yaml:"transport,omitempty" export:"true"`
|
||||
ProxyProtocol *ProxyProtocol `description:"Proxy-Protocol configuration." json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
|
|
|
@ -197,6 +197,7 @@ type Tracing struct {
|
|||
Headers map[string]string `description:"Defines additional headers to be sent with the payloads." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty" export:"true"`
|
||||
GlobalAttributes map[string]string `description:"Defines additional attributes (key:value) on all spans." json:"globalAttributes,omitempty" toml:"globalAttributes,omitempty" yaml:"globalAttributes,omitempty" export:"true"`
|
||||
SampleRate float64 `description:"Sets the rate between 0.0 and 1.0 of requests to trace." json:"sampleRate,omitempty" toml:"sampleRate,omitempty" yaml:"sampleRate,omitempty" export:"true"`
|
||||
AddInternals bool `description:"Enables tracing for internal services (ping, dashboard, etc...)." json:"addInternals,omitempty" toml:"addInternals,omitempty" yaml:"addInternals,omitempty" export:"true"`
|
||||
|
||||
OTLP *opentelemetry.Config `description:"Settings for OpenTelemetry." json:"otlp,omitempty" toml:"otlp,omitempty" yaml:"otlp,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
}
|
||||
|
@ -218,7 +219,7 @@ type Providers struct {
|
|||
KubernetesIngress *ingress.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesIngress,omitempty" toml:"kubernetesIngress,omitempty" yaml:"kubernetesIngress,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
KubernetesCRD *crd.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesCRD,omitempty" toml:"kubernetesCRD,omitempty" yaml:"kubernetesCRD,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
KubernetesGateway *gateway.Provider `description:"Enable Kubernetes gateway api provider with default settings." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Rest *rest.Provider ` description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Rest *rest.Provider `description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
ConsulCatalog *consulcatalog.ProviderBuilder `description:"Enable ConsulCatalog backend with default settings." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Nomad *nomad.ProviderBuilder `description:"Enable Nomad backend with default settings." json:"nomad,omitempty" toml:"nomad,omitempty" yaml:"nomad,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Ecs *ecs.Provider `description:"Enable AWS ECS backend with default settings." json:"ecs,omitempty" toml:"ecs,omitempty" yaml:"ecs,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
|
|
|
@ -2,6 +2,7 @@ package metrics
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/metrics/dogstatsd"
|
||||
|
@ -16,6 +17,8 @@ var (
|
|||
datadogLoopCancelFunc context.CancelFunc
|
||||
)
|
||||
|
||||
const unixAddressPrefix = "unix://"
|
||||
|
||||
// Metric names consistent with https://github.com/DataDog/integrations-extras/pull/64
|
||||
const (
|
||||
ddConfigReloadsName = "config.reload.total"
|
||||
|
@ -99,10 +102,7 @@ func RegisterDatadog(ctx context.Context, config *types.Datadog) Registry {
|
|||
}
|
||||
|
||||
func initDatadogClient(ctx context.Context, config *types.Datadog) {
|
||||
address := config.Address
|
||||
if len(address) == 0 {
|
||||
address = "localhost:8125"
|
||||
}
|
||||
network, address := parseDatadogAddress(config.Address)
|
||||
|
||||
ctx, datadogLoopCancelFunc = context.WithCancel(ctx)
|
||||
|
||||
|
@ -110,10 +110,27 @@ func initDatadogClient(ctx context.Context, config *types.Datadog) {
|
|||
ticker := time.NewTicker(time.Duration(config.PushInterval))
|
||||
defer ticker.Stop()
|
||||
|
||||
datadogClient.SendLoop(ctx, ticker.C, "udp", address)
|
||||
datadogClient.SendLoop(ctx, ticker.C, network, address)
|
||||
})
|
||||
}
|
||||
|
||||
func parseDatadogAddress(address string) (string, string) {
|
||||
network := "udp"
|
||||
|
||||
var addr string
|
||||
switch {
|
||||
case strings.HasPrefix(address, unixAddressPrefix):
|
||||
network = "unix"
|
||||
addr = address[len(unixAddressPrefix):]
|
||||
case address != "":
|
||||
addr = address
|
||||
default:
|
||||
addr = "localhost:8125"
|
||||
}
|
||||
|
||||
return network, addr
|
||||
}
|
||||
|
||||
// StopDatadog stops the Datadog metrics pusher.
|
||||
func StopDatadog() {
|
||||
if datadogLoopCancelFunc != nil {
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stvp/go-udp-testing"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
|
@ -39,6 +40,44 @@ func TestDatadogWithPrefix(t *testing.T) {
|
|||
testDatadogRegistry(t, "testPrefix", datadogRegistry)
|
||||
}
|
||||
|
||||
func TestDatadog_parseDatadogAddress(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
address string
|
||||
expNetwork string
|
||||
expAddress string
|
||||
}{
|
||||
{
|
||||
desc: "empty address",
|
||||
expNetwork: "udp",
|
||||
expAddress: "localhost:8125",
|
||||
},
|
||||
{
|
||||
desc: "udp address",
|
||||
address: "127.0.0.1:8080",
|
||||
expNetwork: "udp",
|
||||
expAddress: "127.0.0.1:8080",
|
||||
},
|
||||
{
|
||||
desc: "unix address",
|
||||
address: "unix:///path/to/datadog.socket",
|
||||
expNetwork: "unix",
|
||||
expAddress: "/path/to/datadog.socket",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
gotNetwork, gotAddress := parseDatadogAddress(test.address)
|
||||
assert.Equal(t, test.expNetwork, gotNetwork)
|
||||
assert.Equal(t, test.expAddress, gotAddress)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testDatadogRegistry(t *testing.T, metricsPrefix string, datadogRegistry Registry) {
|
||||
t.Helper()
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/connectionheader"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
"github.com/vulcand/oxy/v2/forward"
|
||||
"github.com/vulcand/oxy/v2/utils"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
@ -53,7 +54,8 @@ type forwardAuth struct {
|
|||
|
||||
// NewForward creates a forward auth middleware.
|
||||
func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAuth, name string) (http.Handler, error) {
|
||||
middlewares.GetLogger(ctx, name, typeNameForward).Debug().Msg("Creating middleware")
|
||||
logger := middlewares.GetLogger(ctx, name, typeNameForward)
|
||||
logger.Debug().Msg("Creating middleware")
|
||||
|
||||
addAuthCookiesToResponse := make(map[string]struct{})
|
||||
for _, cookieName := range config.AddAuthCookiesToResponse {
|
||||
|
@ -79,7 +81,18 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu
|
|||
}
|
||||
|
||||
if config.TLS != nil {
|
||||
tlsConfig, err := config.TLS.CreateTLSConfig(ctx)
|
||||
if config.TLS.CAOptional != nil {
|
||||
logger.Warn().Msg("CAOptional option is deprecated, TLS client authentication is a server side option, please remove any usage of this option.")
|
||||
}
|
||||
|
||||
clientTLS := &types.ClientTLS{
|
||||
CA: config.TLS.CA,
|
||||
Cert: config.TLS.Cert,
|
||||
Key: config.TLS.Key,
|
||||
InsecureSkipVerify: config.TLS.InsecureSkipVerify,
|
||||
}
|
||||
|
||||
tlsConfig, err := clientTLS.CreateTLSConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create client TLS configuration: %w", err)
|
||||
}
|
||||
|
|
|
@ -30,12 +30,14 @@ func New(ctx context.Context, next http.Handler, confCircuitBreaker dynamic.Circ
|
|||
logger.Debug().Msg("Creating middleware")
|
||||
logger.Debug().Msgf("Setting up with expression: %s", expression)
|
||||
|
||||
responseCode := confCircuitBreaker.ResponseCode
|
||||
|
||||
cbOpts := []cbreaker.Option{
|
||||
cbreaker.Fallback(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
tracing.SetStatusErrorf(req.Context(), "blocked by circuit-breaker (%q)", expression)
|
||||
rw.WriteHeader(http.StatusServiceUnavailable)
|
||||
rw.WriteHeader(responseCode)
|
||||
|
||||
if _, err := rw.Write([]byte(http.StatusText(http.StatusServiceUnavailable))); err != nil {
|
||||
if _, err := rw.Write([]byte(http.StatusText(responseCode))); err != nil {
|
||||
log.Ctx(req.Context()).Error().Err(err).Send()
|
||||
}
|
||||
})),
|
||||
|
|
|
@ -138,6 +138,7 @@ func (r *responseWriter) Write(p []byte) (int, error) {
|
|||
// If we detect a contentEncoding, we know we are never going to compress.
|
||||
if r.rw.Header().Get(contentEncoding) != "" {
|
||||
r.compressionDisabled = true
|
||||
r.rw.WriteHeader(r.statusCode)
|
||||
return r.rw.Write(p)
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ func Test_Vary(t *testing.T) {
|
|||
rw := httptest.NewRecorder()
|
||||
h.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rw.Code)
|
||||
assert.Equal(t, http.StatusAccepted, rw.Code)
|
||||
assert.Equal(t, acceptEncoding, rw.Header().Get(vary))
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ func Test_SmallBodyNoCompression(t *testing.T) {
|
|||
h.ServeHTTP(rw, req)
|
||||
|
||||
// With less than 1024 bytes the response should not be compressed.
|
||||
assert.Equal(t, http.StatusOK, rw.Code)
|
||||
assert.Equal(t, http.StatusAccepted, rw.Code)
|
||||
assert.Empty(t, rw.Header().Get(contentEncoding))
|
||||
assert.Equal(t, smallTestBody, rw.Body.Bytes())
|
||||
}
|
||||
|
@ -55,6 +55,7 @@ func Test_AlreadyCompressed(t *testing.T) {
|
|||
rw := httptest.NewRecorder()
|
||||
h.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, http.StatusAccepted, rw.Code)
|
||||
assert.Equal(t, bigTestBody, rw.Body.Bytes())
|
||||
}
|
||||
|
||||
|
@ -749,6 +750,7 @@ func newTestHandler(t *testing.T, body []byte) http.Handler {
|
|||
rw.Header().Set("Content-Encoding", "br")
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusAccepted)
|
||||
_, err := rw.Write(body)
|
||||
require.NoError(t, err)
|
||||
}),
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
)
|
||||
|
||||
|
@ -18,8 +19,19 @@ type contentType struct {
|
|||
}
|
||||
|
||||
// New creates a new handler.
|
||||
func New(ctx context.Context, next http.Handler, name string) (http.Handler, error) {
|
||||
middlewares.GetLogger(ctx, name, typeName).Debug().Msg("Creating middleware")
|
||||
func New(ctx context.Context, next http.Handler, config dynamic.ContentType, name string) (http.Handler, error) {
|
||||
logger := middlewares.GetLogger(ctx, name, typeName)
|
||||
logger.Debug().Msg("Creating middleware")
|
||||
|
||||
if config.AutoDetect != nil {
|
||||
logger.Warn().Msg("AutoDetect option is deprecated, Content-Type middleware is only meant to be used to enable the content-type detection, please remove any usage of this option.")
|
||||
|
||||
// Disable content-type detection (idempotent).
|
||||
if !*config.AutoDetect {
|
||||
return next, nil
|
||||
}
|
||||
}
|
||||
|
||||
return &contentType{next: next, name: name}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/testhelpers"
|
||||
)
|
||||
|
||||
|
@ -60,7 +61,7 @@ func TestAutoDetection(t *testing.T) {
|
|||
|
||||
if test.autoDetect {
|
||||
var err error
|
||||
next, err = New(context.Background(), next, "foo-content-type")
|
||||
next, err = New(context.Background(), next, dynamic.ContentType{}, "foo-content-type")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
|
|
|
@ -21,15 +21,27 @@ type stripPrefix struct {
|
|||
next http.Handler
|
||||
prefixes []string
|
||||
name string
|
||||
|
||||
// Deprecated: Must be removed (breaking), the default behavior must be forceSlash=false
|
||||
forceSlash bool
|
||||
}
|
||||
|
||||
// New creates a new strip prefix middleware.
|
||||
func New(ctx context.Context, next http.Handler, config dynamic.StripPrefix, name string) (http.Handler, error) {
|
||||
middlewares.GetLogger(ctx, name, typeName).Debug().Msg("Creating middleware")
|
||||
logger := middlewares.GetLogger(ctx, name, typeName)
|
||||
logger.Debug().Msg("Creating middleware")
|
||||
|
||||
if config.ForceSlash != nil {
|
||||
logger.Warn().Msgf("`ForceSlash` option is deprecated, please remove any usage of this option.")
|
||||
}
|
||||
// Handle default value (here because of deprecation and the removal of setDefault).
|
||||
forceSlash := config.ForceSlash != nil && *config.ForceSlash
|
||||
|
||||
return &stripPrefix{
|
||||
prefixes: config.Prefixes,
|
||||
next: next,
|
||||
name: name,
|
||||
prefixes: config.Prefixes,
|
||||
next: next,
|
||||
name: name,
|
||||
forceSlash: forceSlash,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -58,6 +70,13 @@ func (s *stripPrefix) serveRequest(rw http.ResponseWriter, req *http.Request, pr
|
|||
}
|
||||
|
||||
func (s *stripPrefix) getPrefixStripped(urlPath, prefix string) string {
|
||||
if s.forceSlash {
|
||||
// Only for compatibility reason with the previous behavior,
|
||||
// but the previous behavior is wrong.
|
||||
// This needs to be removed in the next breaking version.
|
||||
return "/" + strings.TrimPrefix(strings.TrimPrefix(urlPath, prefix), "/")
|
||||
}
|
||||
|
||||
return ensureLeadingSlash(strings.TrimPrefix(urlPath, prefix))
|
||||
}
|
||||
|
||||
|
|
|
@ -146,6 +146,9 @@ func TestStripPrefix(t *testing.T) {
|
|||
requestURI = r.RequestURI
|
||||
})
|
||||
|
||||
pointer := func(v bool) *bool { return &v }
|
||||
test.config.ForceSlash = pointer(false)
|
||||
|
||||
handler, err := New(context.Background(), next, test.config, "foo-strip-prefix")
|
||||
require.NoError(t, err)
|
||||
|
||||
|
|
|
@ -12,9 +12,10 @@ import (
|
|||
|
||||
// Muxer handles routing with rules.
|
||||
type Muxer struct {
|
||||
routes routes
|
||||
parser predicate.Parser
|
||||
parserV2 predicate.Parser
|
||||
routes routes
|
||||
parser predicate.Parser
|
||||
parserV2 predicate.Parser
|
||||
defaultHandler http.Handler
|
||||
}
|
||||
|
||||
// NewMuxer returns a new muxer instance.
|
||||
|
@ -40,8 +41,9 @@ func NewMuxer() (*Muxer, error) {
|
|||
}
|
||||
|
||||
return &Muxer{
|
||||
parser: parser,
|
||||
parserV2: parserV2,
|
||||
parser: parser,
|
||||
parserV2: parserV2,
|
||||
defaultHandler: http.NotFoundHandler(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -55,7 +57,12 @@ func (m *Muxer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
http.NotFoundHandler().ServeHTTP(rw, req)
|
||||
m.defaultHandler.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
// SetDefaultHandler sets the muxer default handler.
|
||||
func (m *Muxer) SetDefaultHandler(handler http.Handler) {
|
||||
m.defaultHandler = handler
|
||||
}
|
||||
|
||||
// GetRulePriority computes the priority for a given rule.
|
||||
|
|
|
@ -1,196 +0,0 @@
|
|||
package crd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
traefikv1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
var _ Client = (*clientMock)(nil)
|
||||
|
||||
func init() {
|
||||
// required by k8s.MustParseYaml
|
||||
err := traefikv1alpha1.AddToScheme(kscheme.Scheme)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
type clientMock struct {
|
||||
services []*corev1.Service
|
||||
secrets []*corev1.Secret
|
||||
endpoints []*corev1.Endpoints
|
||||
|
||||
apiServiceError error
|
||||
apiSecretError error
|
||||
apiEndpointsError error
|
||||
|
||||
ingressRoutes []*traefikv1alpha1.IngressRoute
|
||||
ingressRouteTCPs []*traefikv1alpha1.IngressRouteTCP
|
||||
ingressRouteUDPs []*traefikv1alpha1.IngressRouteUDP
|
||||
middlewares []*traefikv1alpha1.Middleware
|
||||
middlewareTCPs []*traefikv1alpha1.MiddlewareTCP
|
||||
tlsOptions []*traefikv1alpha1.TLSOption
|
||||
tlsStores []*traefikv1alpha1.TLSStore
|
||||
traefikServices []*traefikv1alpha1.TraefikService
|
||||
serversTransports []*traefikv1alpha1.ServersTransport
|
||||
serversTransportTCPs []*traefikv1alpha1.ServersTransportTCP
|
||||
|
||||
watchChan chan interface{}
|
||||
}
|
||||
|
||||
func newClientMock(paths ...string) clientMock {
|
||||
var c clientMock
|
||||
|
||||
for _, path := range paths {
|
||||
yamlContent, err := os.ReadFile(filepath.FromSlash("./fixtures/" + path))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
k8sObjects := k8s.MustParseYaml(yamlContent)
|
||||
for _, obj := range k8sObjects {
|
||||
switch o := obj.(type) {
|
||||
case *corev1.Service:
|
||||
c.services = append(c.services, o)
|
||||
case *corev1.Endpoints:
|
||||
c.endpoints = append(c.endpoints, o)
|
||||
case *traefikv1alpha1.IngressRoute:
|
||||
c.ingressRoutes = append(c.ingressRoutes, o)
|
||||
case *traefikv1alpha1.IngressRouteTCP:
|
||||
c.ingressRouteTCPs = append(c.ingressRouteTCPs, o)
|
||||
case *traefikv1alpha1.IngressRouteUDP:
|
||||
c.ingressRouteUDPs = append(c.ingressRouteUDPs, o)
|
||||
case *traefikv1alpha1.Middleware:
|
||||
c.middlewares = append(c.middlewares, o)
|
||||
case *traefikv1alpha1.MiddlewareTCP:
|
||||
c.middlewareTCPs = append(c.middlewareTCPs, o)
|
||||
case *traefikv1alpha1.TraefikService:
|
||||
c.traefikServices = append(c.traefikServices, o)
|
||||
case *traefikv1alpha1.TLSOption:
|
||||
c.tlsOptions = append(c.tlsOptions, o)
|
||||
case *traefikv1alpha1.ServersTransport:
|
||||
c.serversTransports = append(c.serversTransports, o)
|
||||
case *traefikv1alpha1.ServersTransportTCP:
|
||||
c.serversTransportTCPs = append(c.serversTransportTCPs, o)
|
||||
case *traefikv1alpha1.TLSStore:
|
||||
c.tlsStores = append(c.tlsStores, o)
|
||||
case *corev1.Secret:
|
||||
c.secrets = append(c.secrets, o)
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown runtime object %+v %T", o, o))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c clientMock) GetIngressRoutes() []*traefikv1alpha1.IngressRoute {
|
||||
return c.ingressRoutes
|
||||
}
|
||||
|
||||
func (c clientMock) GetIngressRouteTCPs() []*traefikv1alpha1.IngressRouteTCP {
|
||||
return c.ingressRouteTCPs
|
||||
}
|
||||
|
||||
func (c clientMock) GetIngressRouteUDPs() []*traefikv1alpha1.IngressRouteUDP {
|
||||
return c.ingressRouteUDPs
|
||||
}
|
||||
|
||||
func (c clientMock) GetMiddlewares() []*traefikv1alpha1.Middleware {
|
||||
return c.middlewares
|
||||
}
|
||||
|
||||
func (c clientMock) GetMiddlewareTCPs() []*traefikv1alpha1.MiddlewareTCP {
|
||||
return c.middlewareTCPs
|
||||
}
|
||||
|
||||
func (c clientMock) GetTraefikService(namespace, name string) (*traefikv1alpha1.TraefikService, bool, error) {
|
||||
for _, svc := range c.traefikServices {
|
||||
if svc.Namespace == namespace && svc.Name == name {
|
||||
return svc, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
func (c clientMock) GetTraefikServices() []*traefikv1alpha1.TraefikService {
|
||||
return c.traefikServices
|
||||
}
|
||||
|
||||
func (c clientMock) GetTLSOptions() []*traefikv1alpha1.TLSOption {
|
||||
return c.tlsOptions
|
||||
}
|
||||
|
||||
func (c clientMock) GetTLSStores() []*traefikv1alpha1.TLSStore {
|
||||
return c.tlsStores
|
||||
}
|
||||
|
||||
func (c clientMock) GetServersTransports() []*traefikv1alpha1.ServersTransport {
|
||||
return c.serversTransports
|
||||
}
|
||||
|
||||
func (c clientMock) GetServersTransportTCPs() []*traefikv1alpha1.ServersTransportTCP {
|
||||
return c.serversTransportTCPs
|
||||
}
|
||||
|
||||
func (c clientMock) GetTLSOption(namespace, name string) (*traefikv1alpha1.TLSOption, bool, error) {
|
||||
for _, option := range c.tlsOptions {
|
||||
if option.Namespace == namespace && option.Name == name {
|
||||
return option, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
func (c clientMock) GetService(namespace, name string) (*corev1.Service, bool, error) {
|
||||
if c.apiServiceError != nil {
|
||||
return nil, false, c.apiServiceError
|
||||
}
|
||||
|
||||
for _, service := range c.services {
|
||||
if service.Namespace == namespace && service.Name == name {
|
||||
return service, true, nil
|
||||
}
|
||||
}
|
||||
return nil, false, c.apiServiceError
|
||||
}
|
||||
|
||||
func (c clientMock) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) {
|
||||
if c.apiEndpointsError != nil {
|
||||
return nil, false, c.apiEndpointsError
|
||||
}
|
||||
|
||||
for _, endpoints := range c.endpoints {
|
||||
if endpoints.Namespace == namespace && endpoints.Name == name {
|
||||
return endpoints, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return &corev1.Endpoints{}, false, nil
|
||||
}
|
||||
|
||||
func (c clientMock) GetSecret(namespace, name string) (*corev1.Secret, bool, error) {
|
||||
if c.apiSecretError != nil {
|
||||
return nil, false, c.apiSecretError
|
||||
}
|
||||
|
||||
for _, secret := range c.secrets {
|
||||
if secret.Namespace == namespace && secret.Name == name {
|
||||
return secret, true, nil
|
||||
}
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
func (c clientMock) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) {
|
||||
return c.watchChan, nil
|
||||
}
|
|
@ -166,7 +166,7 @@ subsets:
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: external-svc
|
||||
name: external-svc-tcp
|
||||
namespace: default
|
||||
spec:
|
||||
externalName: external.domain
|
||||
|
@ -176,7 +176,7 @@ spec:
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: external.service.with.port
|
||||
name: external.service.with.port.tcp
|
||||
namespace: default
|
||||
spec:
|
||||
externalName: external.domain
|
||||
|
@ -186,19 +186,6 @@ spec:
|
|||
protocol: TCP
|
||||
port: 80
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: external.service.without.port
|
||||
namespace: default
|
||||
spec:
|
||||
externalName: external.domain
|
||||
type: ExternalName
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
|
@ -266,7 +253,7 @@ metadata:
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: native-svc
|
||||
name: native-svc-tcp
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
|
|
|
@ -11,5 +11,5 @@ spec:
|
|||
routes:
|
||||
- match: HostSNI(`foo.com`)
|
||||
services:
|
||||
- name: external-svc
|
||||
- name: external-svc-tcp
|
||||
port: 8000
|
||||
|
|
|
@ -11,5 +11,5 @@ spec:
|
|||
routes:
|
||||
- match: HostSNI(`foo.com`)
|
||||
services:
|
||||
- name: external.service.with.port
|
||||
- name: external.service.with.port.tcp
|
||||
port: 80
|
||||
|
|
|
@ -11,4 +11,4 @@ spec:
|
|||
routes:
|
||||
- match: HostSNI(`foo.com`)
|
||||
services:
|
||||
- name: external-svc
|
||||
- name: external-svc-tcp
|
||||
|
|
|
@ -11,6 +11,6 @@ spec:
|
|||
routes:
|
||||
- match: HostSNI(`foo.com`)
|
||||
services:
|
||||
- name: native-svc
|
||||
- name: native-svc-tcp
|
||||
port: 8000
|
||||
nativeLB: true
|
||||
|
|
|
@ -150,7 +150,7 @@ spec:
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: external-svc
|
||||
name: external-svc-udp
|
||||
namespace: default
|
||||
spec:
|
||||
externalName: external.domain
|
||||
|
@ -160,7 +160,7 @@ spec:
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: external.service.with.port
|
||||
name: external.service.with.port.udp
|
||||
namespace: default
|
||||
spec:
|
||||
externalName: external.domain
|
||||
|
@ -170,19 +170,6 @@ spec:
|
|||
protocol: TCP
|
||||
port: 80
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: external.service.without.port
|
||||
namespace: default
|
||||
spec:
|
||||
externalName: external.domain
|
||||
type: ExternalName
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
|
||||
---
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
|
@ -225,7 +212,7 @@ metadata:
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: native-svc
|
||||
name: native-svc-udp
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
|
|
|
@ -10,5 +10,5 @@ spec:
|
|||
|
||||
routes:
|
||||
- services:
|
||||
- name: external-svc
|
||||
- name: external-svc-udp
|
||||
port: 8000
|
||||
|
|
|
@ -10,5 +10,5 @@ spec:
|
|||
|
||||
routes:
|
||||
- services:
|
||||
- name: external.service.with.port
|
||||
- name: external.service.with.port.udp
|
||||
port: 80
|
||||
|
|
|
@ -10,5 +10,5 @@ spec:
|
|||
|
||||
routes:
|
||||
- services:
|
||||
- name: external.service.with.port
|
||||
- name: external.service.with.port.udp
|
||||
port: 80
|
||||
|
|
|
@ -10,4 +10,4 @@ spec:
|
|||
|
||||
routes:
|
||||
- services:
|
||||
- name: external-svc
|
||||
- name: external-svc-udp
|
||||
|
|
|
@ -10,6 +10,6 @@ spec:
|
|||
|
||||
routes:
|
||||
- services:
|
||||
- name: native-svc
|
||||
- name: native-svc-udp
|
||||
port: 8000
|
||||
nativeLB: true
|
||||
|
|
|
@ -40,7 +40,7 @@ spec:
|
|||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: test.route
|
||||
name: test.route.default
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
|
|
|
@ -23,7 +23,7 @@ data:
|
|||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: test.route
|
||||
name: test.route.default
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
|
|
|
@ -738,7 +738,7 @@ func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *traef
|
|||
return forwardAuth, nil
|
||||
}
|
||||
|
||||
forwardAuth.TLS = &types.ClientTLS{
|
||||
forwardAuth.TLS = &dynamic.ClientTLS{
|
||||
InsecureSkipVerify: auth.TLS.InsecureSkipVerify,
|
||||
}
|
||||
|
||||
|
@ -759,6 +759,8 @@ func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *traef
|
|||
forwardAuth.TLS.Key = authSecretKey
|
||||
}
|
||||
|
||||
forwardAuth.TLS.CAOptional = auth.TLS.CAOptional
|
||||
|
||||
return forwardAuth, nil
|
||||
}
|
||||
|
||||
|
@ -1011,8 +1013,9 @@ func buildTLSOptions(ctx context.Context, client Client) map[string]tls.Options
|
|||
CAFiles: clientCAs,
|
||||
ClientAuthType: tlsOption.Spec.ClientAuth.ClientAuthType,
|
||||
},
|
||||
SniStrict: tlsOption.Spec.SniStrict,
|
||||
ALPNProtocols: alpnProtocols,
|
||||
SniStrict: tlsOption.Spec.SniStrict,
|
||||
ALPNProtocols: alpnProtocols,
|
||||
PreferServerCipherSuites: tlsOption.Spec.PreferServerCipherSuites,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -204,6 +204,10 @@ func (p *Provider) createLoadBalancerServerTCP(client Client, parentNamespace st
|
|||
}
|
||||
}
|
||||
|
||||
if service.ServersTransport == "" && service.TerminationDelay != nil {
|
||||
tcpService.LoadBalancer.TerminationDelay = service.TerminationDelay
|
||||
}
|
||||
|
||||
if service.ServersTransport != "" {
|
||||
tcpService.LoadBalancer.ServersTransport, err = p.makeTCPServersTransportKey(parentNamespace, service.ServersTransport)
|
||||
if err != nil {
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
kubefake "k8s.io/client-go/kubernetes/fake"
|
||||
kscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
var _ provider.Provider = (*Provider)(nil)
|
||||
|
@ -30,6 +31,14 @@ var _ provider.Provider = (*Provider)(nil)
|
|||
func Int(v int) *int { return &v }
|
||||
func Bool(v bool) *bool { return &v }
|
||||
|
||||
func init() {
|
||||
// required by k8s.MustParseYaml
|
||||
err := traefikv1alpha1.AddToScheme(kscheme.Scheme)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadIngressRouteTCPs(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
|
@ -1035,6 +1044,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
|
|||
Services: map[string]*dynamic.TCPService{
|
||||
"default-test.route-fdd3e9338e47a45efefc": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
TerminationDelay: Int(500),
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "10.10.0.1:8000",
|
||||
|
@ -1568,6 +1578,23 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
k8sObjects, crdObjects := readResources(t, test.paths)
|
||||
|
||||
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
|
||||
crdClient := traefikcrdfake.NewSimpleClientset(crdObjects...)
|
||||
|
||||
client := newClientImpl(kubeClient, crdClient)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
|
||||
eventCh, err := client.WatchAll(nil, stopCh)
|
||||
require.NoError(t, err)
|
||||
|
||||
if k8sObjects != nil || crdObjects != nil {
|
||||
// just wait for the first event
|
||||
<-eventCh
|
||||
}
|
||||
|
||||
p := Provider{
|
||||
IngressClass: test.ingressClass,
|
||||
AllowCrossNamespace: true,
|
||||
|
@ -1575,8 +1602,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
|
|||
AllowEmptyServices: test.allowEmptyServices,
|
||||
}
|
||||
|
||||
clientMock := newClientMock(test.paths...)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), clientMock)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), client)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
|
@ -3063,6 +3089,15 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
Options: "default-foo",
|
||||
},
|
||||
},
|
||||
"default-test-route-default-6b204d94623b3df4370c": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "default-test-route-default-6b204d94623b3df4370c",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
|
||||
Priority: 12,
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "default-foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
|
@ -3082,6 +3117,22 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
"default-test-route-default-6b204d94623b3df4370c": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
|
@ -3602,7 +3653,7 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
"default-forwardauth": {
|
||||
ForwardAuth: &dynamic.ForwardAuth{
|
||||
Address: "test.com",
|
||||
TLS: &types.ClientTLS{
|
||||
TLS: &dynamic.ClientTLS{
|
||||
CA: "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----",
|
||||
Cert: "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----",
|
||||
Key: "-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----",
|
||||
|
@ -4017,6 +4068,13 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
Priority: 12,
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
},
|
||||
"default-test-route-default-6b204d94623b3df4370c": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "default-test-route-default-6b204d94623b3df4370c",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
|
||||
Priority: 12,
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
|
@ -4036,6 +4094,22 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
"default-test-route-default-6b204d94623b3df4370c": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
|
@ -4521,6 +4595,23 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
k8sObjects, crdObjects := readResources(t, test.paths)
|
||||
|
||||
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
|
||||
crdClient := traefikcrdfake.NewSimpleClientset(crdObjects...)
|
||||
|
||||
client := newClientImpl(kubeClient, crdClient)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
|
||||
eventCh, err := client.WatchAll(nil, stopCh)
|
||||
require.NoError(t, err)
|
||||
|
||||
if k8sObjects != nil || crdObjects != nil {
|
||||
// just wait for the first event
|
||||
<-eventCh
|
||||
}
|
||||
|
||||
p := Provider{
|
||||
IngressClass: test.ingressClass,
|
||||
AllowCrossNamespace: test.allowCrossNamespace,
|
||||
|
@ -4528,8 +4619,7 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
AllowEmptyServices: test.allowEmptyServices,
|
||||
}
|
||||
|
||||
clientMock := newClientMock(test.paths...)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), clientMock)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), client)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
|
@ -5016,6 +5106,23 @@ func TestLoadIngressRouteUDPs(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
k8sObjects, crdObjects := readResources(t, test.paths)
|
||||
|
||||
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
|
||||
crdClient := traefikcrdfake.NewSimpleClientset(crdObjects...)
|
||||
|
||||
client := newClientImpl(kubeClient, crdClient)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
|
||||
eventCh, err := client.WatchAll(nil, stopCh)
|
||||
require.NoError(t, err)
|
||||
|
||||
if k8sObjects != nil || crdObjects != nil {
|
||||
// just wait for the first event
|
||||
<-eventCh
|
||||
}
|
||||
|
||||
p := Provider{
|
||||
IngressClass: test.ingressClass,
|
||||
AllowCrossNamespace: true,
|
||||
|
@ -5023,8 +5130,7 @@ func TestLoadIngressRouteUDPs(t *testing.T) {
|
|||
AllowEmptyServices: test.allowEmptyServices,
|
||||
}
|
||||
|
||||
clientMock := newClientMock(test.paths...)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), clientMock)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), client)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
|
@ -6435,43 +6541,7 @@ func TestCrossNamespace(t *testing.T) {
|
|||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var k8sObjects []runtime.Object
|
||||
var crdObjects []runtime.Object
|
||||
for _, path := range test.paths {
|
||||
yamlContent, err := os.ReadFile(filepath.FromSlash("./fixtures/" + path))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
objects := k8s.MustParseYaml(yamlContent)
|
||||
for _, obj := range objects {
|
||||
switch o := obj.(type) {
|
||||
case *corev1.Service, *corev1.Endpoints, *corev1.Secret:
|
||||
k8sObjects = append(k8sObjects, o)
|
||||
case *traefikv1alpha1.IngressRoute:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *traefikv1alpha1.IngressRouteTCP:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *traefikv1alpha1.IngressRouteUDP:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *traefikv1alpha1.Middleware:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *traefikv1alpha1.MiddlewareTCP:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *traefikv1alpha1.TraefikService:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *traefikv1alpha1.TLSOption:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *traefikv1alpha1.TLSStore:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *traefikv1alpha1.ServersTransport:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *traefikv1alpha1.ServersTransportTCP:
|
||||
crdObjects = append(crdObjects, o)
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
k8sObjects, crdObjects := readResources(t, test.paths)
|
||||
|
||||
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
|
||||
crdClient := traefikcrdfake.NewSimpleClientset(crdObjects...)
|
||||
|
@ -6742,37 +6812,7 @@ func TestExternalNameService(t *testing.T) {
|
|||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var k8sObjects []runtime.Object
|
||||
var crdObjects []runtime.Object
|
||||
for _, path := range test.paths {
|
||||
yamlContent, err := os.ReadFile(filepath.FromSlash("./fixtures/" + path))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
objects := k8s.MustParseYaml(yamlContent)
|
||||
for _, obj := range objects {
|
||||
switch o := obj.(type) {
|
||||
case *corev1.Service, *corev1.Endpoints, *corev1.Secret:
|
||||
k8sObjects = append(k8sObjects, o)
|
||||
case *traefikv1alpha1.IngressRoute:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *traefikv1alpha1.IngressRouteTCP:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *traefikv1alpha1.IngressRouteUDP:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *traefikv1alpha1.Middleware:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *traefikv1alpha1.TraefikService:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *traefikv1alpha1.TLSOption:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *traefikv1alpha1.TLSStore:
|
||||
crdObjects = append(crdObjects, o)
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
k8sObjects, crdObjects := readResources(t, test.paths)
|
||||
|
||||
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
|
||||
crdClient := traefikcrdfake.NewSimpleClientset(crdObjects...)
|
||||
|
@ -6955,37 +6995,7 @@ func TestNativeLB(t *testing.T) {
|
|||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var k8sObjects []runtime.Object
|
||||
var crdObjects []runtime.Object
|
||||
for _, path := range test.paths {
|
||||
yamlContent, err := os.ReadFile(filepath.FromSlash("./fixtures/" + path))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
objects := k8s.MustParseYaml(yamlContent)
|
||||
for _, obj := range objects {
|
||||
switch o := obj.(type) {
|
||||
case *corev1.Service, *corev1.Endpoints, *corev1.Secret:
|
||||
k8sObjects = append(k8sObjects, o)
|
||||
case *traefikv1alpha1.IngressRoute:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *traefikv1alpha1.IngressRouteTCP:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *traefikv1alpha1.IngressRouteUDP:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *traefikv1alpha1.Middleware:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *traefikv1alpha1.TraefikService:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *traefikv1alpha1.TLSOption:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *traefikv1alpha1.TLSStore:
|
||||
crdObjects = append(crdObjects, o)
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
k8sObjects, crdObjects := readResources(t, test.paths)
|
||||
|
||||
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
|
||||
crdClient := traefikcrdfake.NewSimpleClientset(crdObjects...)
|
||||
|
@ -7071,3 +7081,28 @@ func TestCreateBasicAuthCredentials(t *testing.T) {
|
|||
assert.Equal(t, "$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", hashedPassword)
|
||||
assert.True(t, auth.CheckSecret("test2", hashedPassword))
|
||||
}
|
||||
|
||||
func readResources(t *testing.T, paths []string) ([]runtime.Object, []runtime.Object) {
|
||||
t.Helper()
|
||||
|
||||
var k8sObjects []runtime.Object
|
||||
var crdObjects []runtime.Object
|
||||
for _, path := range paths {
|
||||
yamlContent, err := os.ReadFile(filepath.FromSlash("./fixtures/" + path))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
objects := k8s.MustParseYaml(yamlContent)
|
||||
for _, obj := range objects {
|
||||
switch obj.GetObjectKind().GroupVersionKind().Group {
|
||||
case "traefik.io":
|
||||
crdObjects = append(crdObjects, obj)
|
||||
default:
|
||||
k8sObjects = append(k8sObjects, obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return k8sObjects, crdObjects
|
||||
}
|
||||
|
|
|
@ -72,6 +72,13 @@ type ServiceTCP struct {
|
|||
Port intstr.IntOrString `json:"port"`
|
||||
// Weight defines the weight used when balancing requests between multiple Kubernetes Service.
|
||||
Weight *int `json:"weight,omitempty"`
|
||||
// TerminationDelay defines the deadline that the proxy sets, after one of its connected peers indicates
|
||||
// it has closed the writing capability of its connection, to close the reading capability as well,
|
||||
// hence fully terminating the connection.
|
||||
// It is a duration in milliseconds, defaulting to 100.
|
||||
// A negative value means an infinite deadline (i.e. the reading capability is never closed).
|
||||
// Deprecated: TerminationDelay is not supported APIVersion traefik.io/v1, please use ServersTransport to configure the TerminationDelay instead.
|
||||
TerminationDelay *int `json:"terminationDelay,omitempty"`
|
||||
// ProxyProtocol defines the PROXY protocol configuration.
|
||||
// More info: https://doc.traefik.io/traefik/v3.0/routing/services/#proxy-protocol
|
||||
ProxyProtocol *dynamic.ProxyProtocol `json:"proxyProtocol,omitempty"`
|
||||
|
|
|
@ -171,6 +171,9 @@ type ClientTLS struct {
|
|||
CertSecret string `json:"certSecret,omitempty"`
|
||||
// InsecureSkipVerify defines whether the server certificates should be validated.
|
||||
InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"`
|
||||
|
||||
// Deprecated: TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634).
|
||||
CAOptional *bool `json:"caOptional,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
|
|
@ -26,9 +26,13 @@ type MiddlewareTCPSpec struct {
|
|||
// InFlightConn defines the InFlightConn middleware configuration.
|
||||
InFlightConn *dynamic.TCPInFlightConn `json:"inFlightConn,omitempty"`
|
||||
// IPWhiteList defines the IPWhiteList middleware configuration.
|
||||
// This middleware accepts/refuses connections based on the client IP.
|
||||
// Deprecated: please use IPAllowList instead.
|
||||
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/tcp/ipwhitelist/
|
||||
IPWhiteList *dynamic.TCPIPWhiteList `json:"ipWhiteList,omitempty"`
|
||||
// IPAllowList defines the IPAllowList middleware configuration.
|
||||
// This middleware accepts/refuses connections based on the client IP.
|
||||
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/tcp/ipallowlist/
|
||||
IPAllowList *dynamic.TCPIPAllowList `json:"ipAllowList,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,11 @@ type TLSOptionSpec struct {
|
|||
// ALPNProtocols defines the list of supported application level protocols for the TLS handshake, in order of preference.
|
||||
// More info: https://doc.traefik.io/traefik/v3.0/https/tls/#alpn-protocols
|
||||
ALPNProtocols []string `json:"alpnProtocols,omitempty"`
|
||||
|
||||
// PreferServerCipherSuites defines whether the server chooses a cipher suite among his own instead of among the client's.
|
||||
// It is enabled automatically when minVersion or maxVersion is set.
|
||||
// Deprecated: https://github.com/golang/go/issues/45430
|
||||
PreferServerCipherSuites *bool `json:"preferServerCipherSuites,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
|
|
@ -146,6 +146,11 @@ func (in *ClientAuth) DeepCopy() *ClientAuth {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClientTLS) DeepCopyInto(out *ClientTLS) {
|
||||
*out = *in
|
||||
if in.CAOptional != nil {
|
||||
in, out := &in.CAOptional, &out.CAOptional
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -213,7 +218,7 @@ func (in *ForwardAuth) DeepCopyInto(out *ForwardAuth) {
|
|||
if in.TLS != nil {
|
||||
in, out := &in.TLS, &out.TLS
|
||||
*out = new(ClientTLS)
|
||||
**out = **in
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.AddAuthCookiesToResponse != nil {
|
||||
in, out := &in.AddAuthCookiesToResponse, &out.AddAuthCookiesToResponse
|
||||
|
@ -777,7 +782,7 @@ func (in *MiddlewareSpec) DeepCopyInto(out *MiddlewareSpec) {
|
|||
if in.ContentType != nil {
|
||||
in, out := &in.ContentType, &out.ContentType
|
||||
*out = new(dynamic.ContentType)
|
||||
**out = **in
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.GrpcWeb != nil {
|
||||
in, out := &in.GrpcWeb, &out.GrpcWeb
|
||||
|
@ -1318,6 +1323,11 @@ func (in *ServiceTCP) DeepCopyInto(out *ServiceTCP) {
|
|||
*out = new(int)
|
||||
**out = **in
|
||||
}
|
||||
if in.TerminationDelay != nil {
|
||||
in, out := &in.TerminationDelay, &out.TerminationDelay
|
||||
*out = new(int)
|
||||
**out = **in
|
||||
}
|
||||
if in.ProxyProtocol != nil {
|
||||
in, out := &in.ProxyProtocol, &out.ProxyProtocol
|
||||
*out = new(dynamic.ProxyProtocol)
|
||||
|
@ -1517,6 +1527,11 @@ func (in *TLSOptionSpec) DeepCopyInto(out *TLSOptionSpec) {
|
|||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.PreferServerCipherSuites != nil {
|
||||
in, out := &in.PreferServerCipherSuites, &out.PreferServerCipherSuites
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"k8s.io/client-go/tools/clientcmd"
|
||||
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||
gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
|
||||
gateclientset "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned"
|
||||
gateinformers "sigs.k8s.io/gateway-api/pkg/client/informers/externalversions"
|
||||
)
|
||||
|
@ -34,13 +35,7 @@ func (reh *resourceEventHandler) OnAdd(obj interface{}, isInInitialList bool) {
|
|||
}
|
||||
|
||||
func (reh *resourceEventHandler) OnUpdate(oldObj, newObj interface{}) {
|
||||
switch oldObj.(type) {
|
||||
case *gatev1.GatewayClass:
|
||||
// Skip update for gateway classes. We only manage addition or deletion for this cluster-wide resource.
|
||||
return
|
||||
default:
|
||||
eventHandlerFunc(reh.ev, newObj)
|
||||
}
|
||||
eventHandlerFunc(reh.ev, newObj)
|
||||
}
|
||||
|
||||
func (reh *resourceEventHandler) OnDelete(obj interface{}) {
|
||||
|
@ -59,6 +54,7 @@ type Client interface {
|
|||
GetHTTPRoutes(namespaces []string) ([]*gatev1.HTTPRoute, error)
|
||||
GetTCPRoutes(namespaces []string) ([]*gatev1alpha2.TCPRoute, error)
|
||||
GetTLSRoutes(namespaces []string) ([]*gatev1alpha2.TLSRoute, error)
|
||||
GetReferenceGrants(namespace string) ([]*gatev1beta1.ReferenceGrant, error)
|
||||
GetService(namespace, name string) (*corev1.Service, bool, error)
|
||||
GetSecret(namespace, name string) (*corev1.Secret, bool, error)
|
||||
GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error)
|
||||
|
@ -189,9 +185,6 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// TODO manage Reference Policy
|
||||
// https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.ReferencePolicy
|
||||
|
||||
for _, ns := range namespaces {
|
||||
factoryGateway := gateinformers.NewSharedInformerFactoryWithOptions(c.csGateway, resyncPeriod, gateinformers.WithNamespace(ns))
|
||||
_, err = factoryGateway.Gateway().V1().Gateways().Informer().AddEventHandler(eventHandler)
|
||||
|
@ -210,6 +203,10 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = factoryGateway.Gateway().V1beta1().ReferenceGrants().Informer().AddEventHandler(eventHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
factoryKube := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns))
|
||||
_, err = factoryKube.Core().V1().Services().Informer().AddEventHandler(eventHandler)
|
||||
|
@ -363,6 +360,21 @@ func (c *clientWrapper) GetTLSRoutes(namespaces []string) ([]*gatev1alpha2.TLSRo
|
|||
return tlsRoutes, nil
|
||||
}
|
||||
|
||||
func (c *clientWrapper) GetReferenceGrants(namespace string) ([]*gatev1beta1.ReferenceGrant, error) {
|
||||
if !c.isWatchedNamespace(namespace) {
|
||||
log.Warn().Msgf("Failed to get ReferenceGrants: %q is not within watched namespaces", namespace)
|
||||
|
||||
return nil, fmt.Errorf("failed to get ReferenceGrants: namespace %s is not within watched namespaces", namespace)
|
||||
}
|
||||
|
||||
referenceGrants, err := c.factoriesGateway[c.lookupNamespace(namespace)].Gateway().V1beta1().ReferenceGrants().Lister().ReferenceGrants(namespace).List(labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return referenceGrants, nil
|
||||
}
|
||||
|
||||
func (c *clientWrapper) GetGateways() []*gatev1.Gateway {
|
||||
var result []*gatev1.Gateway
|
||||
|
||||
|
@ -388,7 +400,7 @@ func (c *clientWrapper) UpdateGatewayClassStatus(gatewayClass *gatev1.GatewayCla
|
|||
var newConditions []metav1.Condition
|
||||
for _, cond := range gc.Status.Conditions {
|
||||
// No update for identical condition.
|
||||
if cond.Type == condition.Type && cond.Status == condition.Status {
|
||||
if cond.Type == condition.Type && cond.Status == condition.Status && cond.ObservedGeneration == condition.ObservedGeneration {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -470,7 +482,7 @@ func conditionsEquals(conditionsA, conditionsB []metav1.Condition) bool {
|
|||
for _, conditionA := range conditionsA {
|
||||
for _, conditionB := range conditionsB {
|
||||
if conditionA.Type == conditionB.Type {
|
||||
if conditionA.Reason != conditionB.Reason || conditionA.Status != conditionB.Status || conditionA.Message != conditionB.Message {
|
||||
if conditionA.Reason != conditionB.Reason || conditionA.Status != conditionB.Status || conditionA.Message != conditionB.Message || conditionA.ObservedGeneration != conditionB.ObservedGeneration {
|
||||
return false
|
||||
}
|
||||
conditionMatches++
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
kscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||
gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
|
||||
)
|
||||
|
||||
var _ Client = (*clientMock)(nil)
|
||||
|
@ -23,6 +24,11 @@ func init() {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
err = gatev1beta1.AddToScheme(kscheme.Scheme)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = gatev1.AddToScheme(kscheme.Scheme)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -39,11 +45,12 @@ type clientMock struct {
|
|||
apiSecretError error
|
||||
apiEndpointsError error
|
||||
|
||||
gatewayClasses []*gatev1.GatewayClass
|
||||
gateways []*gatev1.Gateway
|
||||
httpRoutes []*gatev1.HTTPRoute
|
||||
tcpRoutes []*gatev1alpha2.TCPRoute
|
||||
tlsRoutes []*gatev1alpha2.TLSRoute
|
||||
gatewayClasses []*gatev1.GatewayClass
|
||||
gateways []*gatev1.Gateway
|
||||
httpRoutes []*gatev1.HTTPRoute
|
||||
tcpRoutes []*gatev1alpha2.TCPRoute
|
||||
tlsRoutes []*gatev1alpha2.TLSRoute
|
||||
referenceGrants []*gatev1beta1.ReferenceGrant
|
||||
|
||||
watchChan chan interface{}
|
||||
}
|
||||
|
@ -78,6 +85,8 @@ func newClientMock(paths ...string) clientMock {
|
|||
c.tcpRoutes = append(c.tcpRoutes, o)
|
||||
case *gatev1alpha2.TLSRoute:
|
||||
c.tlsRoutes = append(c.tlsRoutes, o)
|
||||
case *gatev1beta1.ReferenceGrant:
|
||||
c.referenceGrants = append(c.referenceGrants, o)
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown runtime object %+v %T", o, o))
|
||||
}
|
||||
|
@ -190,6 +199,16 @@ func (c clientMock) GetTLSRoutes(namespaces []string) ([]*gatev1alpha2.TLSRoute,
|
|||
return tlsRoutes, nil
|
||||
}
|
||||
|
||||
func (c clientMock) GetReferenceGrants(namespace string) ([]*gatev1beta1.ReferenceGrant, error) {
|
||||
var referenceGrants []*gatev1beta1.ReferenceGrant
|
||||
for _, referenceGrant := range c.referenceGrants {
|
||||
if inNamespace(referenceGrant.ObjectMeta, namespace) {
|
||||
referenceGrants = append(referenceGrants, referenceGrant)
|
||||
}
|
||||
}
|
||||
return referenceGrants, nil
|
||||
}
|
||||
|
||||
func (c clientMock) GetService(namespace, name string) (*corev1.Service, bool, error) {
|
||||
if c.apiServiceError != nil {
|
||||
return nil, false, c.apiServiceError
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: ReferenceGrant
|
||||
metadata:
|
||||
name: secret-from-default
|
||||
namespace: secret-namespace
|
||||
spec:
|
||||
from:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: Gateway
|
||||
namespace: default
|
||||
to:
|
||||
- group: ""
|
||||
kind: Secret
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: supersecret
|
||||
namespace: secret-namespace
|
||||
|
||||
data:
|
||||
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
|
||||
---
|
||||
kind: GatewayClass
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-gateway-class
|
||||
spec:
|
||||
controllerName: traefik.io/gateway-controller
|
||||
|
||||
---
|
||||
kind: Gateway
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-gateway
|
||||
namespace: default
|
||||
spec:
|
||||
gatewayClassName: my-gateway-class
|
||||
listeners: # Use GatewayClass defaults for listener definition.
|
||||
- name: tls
|
||||
protocol: TLS
|
||||
port: 9000
|
||||
hostname: foo.example.com
|
||||
tls:
|
||||
mode: Terminate # Default mode
|
||||
certificateRefs:
|
||||
- kind: Secret
|
||||
name: supersecret
|
||||
namespace: secret-namespace
|
||||
group: ""
|
||||
allowedRoutes:
|
||||
kinds:
|
||||
- kind: TCPRoute
|
||||
group: gateway.networking.k8s.io
|
||||
namespaces:
|
||||
from: Same
|
||||
|
||||
---
|
||||
kind: TCPRoute
|
||||
apiVersion: gateway.networking.k8s.io/v1alpha2
|
||||
metadata:
|
||||
name: tcp-app-1
|
||||
namespace: default
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: my-gateway
|
||||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- name: whoamitcp
|
||||
port: 9000
|
||||
weight: 1
|
||||
kind: Service
|
||||
group: ""
|
|
@ -0,0 +1,64 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: supersecret
|
||||
namespace: secret-namespace
|
||||
|
||||
data:
|
||||
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
|
||||
---
|
||||
kind: GatewayClass
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-gateway-class
|
||||
spec:
|
||||
controllerName: traefik.io/gateway-controller
|
||||
|
||||
---
|
||||
kind: Gateway
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-gateway
|
||||
namespace: default
|
||||
spec:
|
||||
gatewayClassName: my-gateway-class
|
||||
listeners: # Use GatewayClass defaults for listener definition.
|
||||
- name: tls
|
||||
protocol: TLS
|
||||
port: 9000
|
||||
hostname: foo.example.com
|
||||
tls:
|
||||
mode: Terminate # Default mode
|
||||
certificateRefs:
|
||||
- kind: Secret
|
||||
name: supersecret
|
||||
namespace: secret-namespace
|
||||
group: ""
|
||||
allowedRoutes:
|
||||
kinds:
|
||||
- kind: TCPRoute
|
||||
group: gateway.networking.k8s.io
|
||||
namespaces:
|
||||
from: Same
|
||||
|
||||
---
|
||||
kind: TCPRoute
|
||||
apiVersion: gateway.networking.k8s.io/v1alpha2
|
||||
metadata:
|
||||
name: tcp-app-1
|
||||
namespace: default
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: my-gateway
|
||||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- name: whoamitcp
|
||||
port: 9000
|
||||
weight: 1
|
||||
kind: Service
|
||||
group: ""
|
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: ReferenceGrant
|
||||
metadata:
|
||||
name: secret-from-default
|
||||
namespace: secret-namespace
|
||||
spec:
|
||||
from:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: Gateway
|
||||
namespace: differentnamespace
|
||||
to:
|
||||
- group: ""
|
||||
kind: Secret
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: supersecret
|
||||
namespace: secret-namespace
|
||||
|
||||
data:
|
||||
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
|
||||
---
|
||||
kind: GatewayClass
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-gateway-class
|
||||
spec:
|
||||
controllerName: traefik.io/gateway-controller
|
||||
|
||||
---
|
||||
kind: Gateway
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-gateway
|
||||
namespace: default
|
||||
spec:
|
||||
gatewayClassName: my-gateway-class
|
||||
listeners: # Use GatewayClass defaults for listener definition.
|
||||
- name: tls
|
||||
protocol: TLS
|
||||
port: 9000
|
||||
hostname: foo.example.com
|
||||
tls:
|
||||
mode: Terminate # Default mode
|
||||
certificateRefs:
|
||||
- kind: Secret
|
||||
name: supersecret
|
||||
namespace: secret-namespace
|
||||
group: ""
|
||||
allowedRoutes:
|
||||
kinds:
|
||||
- kind: TCPRoute
|
||||
group: gateway.networking.k8s.io
|
||||
namespaces:
|
||||
from: Same
|
||||
|
||||
---
|
||||
kind: TCPRoute
|
||||
apiVersion: gateway.networking.k8s.io/v1alpha2
|
||||
metadata:
|
||||
name: tcp-app-1
|
||||
namespace: default
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: my-gateway
|
||||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- name: whoamitcp
|
||||
port: 9000
|
||||
weight: 1
|
||||
kind: Service
|
||||
group: ""
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: ReferenceGrant
|
||||
metadata:
|
||||
name: secret-from-default
|
||||
namespace: secret-namespace
|
||||
spec:
|
||||
from:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: Gateway
|
||||
namespace: default
|
||||
to:
|
||||
- group: ""
|
||||
kind: Secret
|
||||
name: differentsecret
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: supersecret
|
||||
namespace: secret-namespace
|
||||
|
||||
data:
|
||||
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
|
||||
---
|
||||
kind: GatewayClass
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-gateway-class
|
||||
spec:
|
||||
controllerName: traefik.io/gateway-controller
|
||||
|
||||
---
|
||||
kind: Gateway
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-gateway
|
||||
namespace: default
|
||||
spec:
|
||||
gatewayClassName: my-gateway-class
|
||||
listeners: # Use GatewayClass defaults for listener definition.
|
||||
- name: tls
|
||||
protocol: TLS
|
||||
port: 9000
|
||||
hostname: foo.example.com
|
||||
tls:
|
||||
mode: Terminate # Default mode
|
||||
certificateRefs:
|
||||
- kind: Secret
|
||||
name: supersecret
|
||||
namespace: secret-namespace
|
||||
group: ""
|
||||
allowedRoutes:
|
||||
kinds:
|
||||
- kind: TCPRoute
|
||||
group: gateway.networking.k8s.io
|
||||
namespaces:
|
||||
from: Same
|
||||
|
||||
---
|
||||
kind: TCPRoute
|
||||
apiVersion: gateway.networking.k8s.io/v1alpha2
|
||||
metadata:
|
||||
name: tcp-app-1
|
||||
namespace: default
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: my-gateway
|
||||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- name: whoamitcp
|
||||
port: 9000
|
||||
weight: 1
|
||||
kind: Service
|
||||
group: ""
|
|
@ -34,11 +34,14 @@ import (
|
|||
"k8s.io/utils/ptr"
|
||||
"k8s.io/utils/strings/slices"
|
||||
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
|
||||
)
|
||||
|
||||
const (
|
||||
providerName = "kubernetesgateway"
|
||||
|
||||
groupCore = "core"
|
||||
|
||||
kindGateway = "Gateway"
|
||||
kindTraefikService = "TraefikService"
|
||||
kindHTTPRoute = "HTTPRoute"
|
||||
|
@ -348,6 +351,7 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway *
|
|||
Name: listener.Name,
|
||||
SupportedKinds: []gatev1.RouteGroupKind{},
|
||||
Conditions: []metav1.Condition{},
|
||||
// AttachedRoutes: 0 TODO Set to number of Routes associated with a Listener regardless of Gateway or Route status
|
||||
}
|
||||
|
||||
supportedKinds, conditions := supportedRouteKinds(listener.Protocol)
|
||||
|
@ -356,9 +360,8 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway *
|
|||
continue
|
||||
}
|
||||
|
||||
listenerStatuses[i].SupportedKinds = supportedKinds
|
||||
|
||||
routeKinds, conditions := getAllowedRouteKinds(gateway, listener, supportedKinds)
|
||||
listenerStatuses[i].SupportedKinds = routeKinds
|
||||
if len(conditions) > 0 {
|
||||
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, conditions...)
|
||||
continue
|
||||
|
@ -474,7 +477,7 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway *
|
|||
certificateRef := listener.TLS.CertificateRefs[0]
|
||||
|
||||
if certificateRef.Kind == nil || *certificateRef.Kind != "Secret" ||
|
||||
certificateRef.Group == nil || (*certificateRef.Group != "" && *certificateRef.Group != "core") {
|
||||
certificateRef.Group == nil || (*certificateRef.Group != "" && *certificateRef.Group != groupCore) {
|
||||
// update "ResolvedRefs" status true with "InvalidCertificateRef" reason
|
||||
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{
|
||||
Type: string(gatev1.ListenerConditionResolvedRefs),
|
||||
|
@ -482,43 +485,74 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway *
|
|||
ObservedGeneration: gateway.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.ListenerReasonInvalidCertificateRef),
|
||||
Message: fmt.Sprintf("Unsupported TLS CertificateRef group/kind: %v/%v", certificateRef.Group, certificateRef.Kind),
|
||||
Message: fmt.Sprintf("Unsupported TLS CertificateRef group/kind: %s/%s", groupToString(certificateRef.Group), kindToString(certificateRef.Kind)),
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO Support ReferencePolicy to support cross namespace references.
|
||||
certificateNamespace := gateway.Namespace
|
||||
if certificateRef.Namespace != nil && string(*certificateRef.Namespace) != gateway.Namespace {
|
||||
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{
|
||||
Type: string(gatev1.ListenerConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: gateway.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.ListenerReasonInvalidCertificateRef),
|
||||
Message: "Cross namespace secrets are not supported",
|
||||
})
|
||||
|
||||
continue
|
||||
certificateNamespace = string(*certificateRef.Namespace)
|
||||
}
|
||||
|
||||
configKey := gateway.Namespace + "/" + string(certificateRef.Name)
|
||||
if _, tlsExists := tlsConfigs[configKey]; !tlsExists {
|
||||
tlsConf, err := getTLS(client, certificateRef.Name, gateway.Namespace)
|
||||
if certificateNamespace != gateway.Namespace {
|
||||
referenceGrants, err := client.GetReferenceGrants(certificateNamespace)
|
||||
if err != nil {
|
||||
// update "ResolvedRefs" status true with "InvalidCertificateRef" reason
|
||||
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{
|
||||
Type: string(gatev1.ListenerConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: gateway.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.ListenerReasonInvalidCertificateRef),
|
||||
Message: fmt.Sprintf("Error while retrieving certificate: %v", err),
|
||||
Reason: string(gatev1.ListenerReasonRefNotPermitted),
|
||||
Message: fmt.Sprintf("Cannot find any ReferenceGrant: %v", err),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
referenceGrants = filterReferenceGrantsFrom(referenceGrants, "gateway.networking.k8s.io", "Gateway", gateway.Namespace)
|
||||
referenceGrants = filterReferenceGrantsTo(referenceGrants, groupCore, "Secret", string(certificateRef.Name))
|
||||
if len(referenceGrants) == 0 {
|
||||
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{
|
||||
Type: string(gatev1.ListenerConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: gateway.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.ListenerReasonRefNotPermitted),
|
||||
Message: "Required ReferenceGrant for cross namespace secret reference is missing",
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
configKey := certificateNamespace + "/" + string(certificateRef.Name)
|
||||
if _, tlsExists := tlsConfigs[configKey]; !tlsExists {
|
||||
tlsConf, err := getTLS(client, certificateRef.Name, certificateNamespace)
|
||||
if err != nil {
|
||||
// update "ResolvedRefs" status false with "InvalidCertificateRef" reason
|
||||
// update "Programmed" status false with "Invalid" reason
|
||||
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions,
|
||||
metav1.Condition{
|
||||
Type: string(gatev1.ListenerConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: gateway.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.ListenerReasonInvalidCertificateRef),
|
||||
Message: fmt.Sprintf("Error while retrieving certificate: %v", err),
|
||||
},
|
||||
metav1.Condition{
|
||||
Type: string(gatev1.ListenerConditionProgrammed),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: gateway.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.ListenerReasonInvalid),
|
||||
Message: fmt.Sprintf("Error while retrieving certificate: %v", err),
|
||||
},
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
tlsConfigs[configKey] = tlsConf
|
||||
}
|
||||
}
|
||||
|
@ -548,15 +582,32 @@ func (p *Provider) makeGatewayStatus(gateway *gatev1.Gateway, listenerStatuses [
|
|||
var result error
|
||||
for i, listener := range listenerStatuses {
|
||||
if len(listener.Conditions) == 0 {
|
||||
// GatewayConditionReady "Ready", GatewayConditionReason "ListenerReady"
|
||||
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{
|
||||
Type: string(gatev1.ListenerReasonAccepted),
|
||||
Status: metav1.ConditionTrue,
|
||||
ObservedGeneration: gateway.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: "ListenerReady",
|
||||
Message: "No error found",
|
||||
})
|
||||
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions,
|
||||
metav1.Condition{
|
||||
Type: string(gatev1.ListenerConditionAccepted),
|
||||
Status: metav1.ConditionTrue,
|
||||
ObservedGeneration: gateway.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.ListenerReasonAccepted),
|
||||
Message: "No error found",
|
||||
},
|
||||
metav1.Condition{
|
||||
Type: string(gatev1.ListenerConditionResolvedRefs),
|
||||
Status: metav1.ConditionTrue,
|
||||
ObservedGeneration: gateway.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.ListenerReasonResolvedRefs),
|
||||
Message: "No error found",
|
||||
},
|
||||
metav1.Condition{
|
||||
Type: string(gatev1.ListenerConditionProgrammed),
|
||||
Status: metav1.ConditionTrue,
|
||||
ObservedGeneration: gateway.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.ListenerReasonProgrammed),
|
||||
Message: "No error found",
|
||||
},
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
@ -565,6 +616,7 @@ func (p *Provider) makeGatewayStatus(gateway *gatev1.Gateway, listenerStatuses [
|
|||
result = multierror.Append(result, errors.New(condition.Message))
|
||||
}
|
||||
}
|
||||
gatewayStatus.Listeners = listenerStatuses
|
||||
|
||||
if result != nil {
|
||||
// GatewayConditionReady "Ready", GatewayConditionReason "ListenersNotValid"
|
||||
|
@ -580,8 +632,6 @@ func (p *Provider) makeGatewayStatus(gateway *gatev1.Gateway, listenerStatuses [
|
|||
return gatewayStatus, result
|
||||
}
|
||||
|
||||
gatewayStatus.Listeners = listenerStatuses
|
||||
|
||||
gatewayStatus.Conditions = append(gatewayStatus.Conditions,
|
||||
// update "Accepted" status with "Accepted" reason
|
||||
metav1.Condition{
|
||||
|
@ -656,7 +706,7 @@ func getAllowedRouteKinds(gateway *gatev1.Gateway, listener gatev1.Listener, sup
|
|||
}
|
||||
|
||||
var (
|
||||
routeKinds []gatev1.RouteGroupKind
|
||||
routeKinds = []gatev1.RouteGroupKind{}
|
||||
conditions []metav1.Condition
|
||||
)
|
||||
|
||||
|
@ -672,12 +722,12 @@ func getAllowedRouteKinds(gateway *gatev1.Gateway, listener gatev1.Listener, sup
|
|||
|
||||
if !isSupported {
|
||||
conditions = append(conditions, metav1.Condition{
|
||||
Type: string(gatev1.ListenerConditionAccepted),
|
||||
Status: metav1.ConditionTrue,
|
||||
Type: string(gatev1.ListenerConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: gateway.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.ListenerReasonInvalidRouteKinds),
|
||||
Message: fmt.Sprintf("Listener protocol %q does not support RouteGroupKind %v/%s", listener.Protocol, routeKind.Group, routeKind.Kind),
|
||||
Message: fmt.Sprintf("Listener protocol %q does not support RouteGroupKind %s/%s", listener.Protocol, groupToString(routeKind.Group), routeKind.Kind),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
@ -712,7 +762,7 @@ func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, li
|
|||
|
||||
routes, err := client.GetHTTPRoutes(namespaces)
|
||||
if err != nil {
|
||||
// update "ResolvedRefs" status true with "InvalidRoutesRef" reason
|
||||
// update "ResolvedRefs" status true with "RefNotPermitted" reason
|
||||
return []metav1.Condition{{
|
||||
Type: string(gatev1.ListenerConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
|
@ -757,7 +807,7 @@ func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, li
|
|||
for _, routeRule := range route.Spec.Rules {
|
||||
rule, err := extractRule(routeRule, hostRule)
|
||||
if err != nil {
|
||||
// update "ResolvedRefs" status true with "DroppedRoutes" reason
|
||||
// update "ResolvedRefs" status true with "UnsupportedPathOrHeaderType" reason
|
||||
conditions = append(conditions, metav1.Condition{
|
||||
Type: string(gatev1.ListenerConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
|
@ -1048,7 +1098,7 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener gatev1.Li
|
|||
Type: string(gatev1.GatewayClassConditionStatusAccepted),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: gateway.Generation,
|
||||
Reason: string(gatev1.ListenerConditionConflicted),
|
||||
Reason: string(gatev1.ListenerReasonHostnameConflict),
|
||||
Message: fmt.Sprintf("No hostname match between listener: %v and route: %v", listener.Hostname, route.Spec.Hostnames),
|
||||
LastTransitionTime: metav1.Now(),
|
||||
})
|
||||
|
@ -1059,7 +1109,7 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener gatev1.Li
|
|||
|
||||
rule, err := hostSNIRule(hostnames)
|
||||
if err != nil {
|
||||
// update "ResolvedRefs" status true with "DroppedRoutes" reason
|
||||
// update "ResolvedRefs" status true with "InvalidHostnames" reason
|
||||
conditions = append(conditions, metav1.Condition{
|
||||
Type: string(gatev1.ListenerConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
|
@ -1111,7 +1161,7 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener gatev1.Li
|
|||
|
||||
wrrService, subServices, err := loadTCPServices(client, route.Namespace, routeRule.BackendRefs)
|
||||
if err != nil {
|
||||
// update "ResolvedRefs" status true with "DroppedRoutes" reason
|
||||
// update "ResolvedRefs" status true with "InvalidBackendRefs" reason
|
||||
conditions = append(conditions, metav1.Condition{
|
||||
Type: string(gatev1.ListenerConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
|
@ -1526,7 +1576,7 @@ func loadServices(client Client, namespace string, backendRefs []gatev1.HTTPBack
|
|||
continue
|
||||
}
|
||||
|
||||
if *backendRef.Group != "" && *backendRef.Group != "core" && *backendRef.Kind != "Service" {
|
||||
if *backendRef.Group != "" && *backendRef.Group != groupCore && *backendRef.Kind != "Service" {
|
||||
return nil, nil, fmt.Errorf("unsupported HTTPBackendRef %s/%s/%s", *backendRef.Group, *backendRef.Kind, backendRef.Name)
|
||||
}
|
||||
|
||||
|
@ -1649,7 +1699,7 @@ func loadTCPServices(client Client, namespace string, backendRefs []gatev1.Backe
|
|||
continue
|
||||
}
|
||||
|
||||
if *backendRef.Group != "" && *backendRef.Group != "core" && *backendRef.Kind != "Service" {
|
||||
if *backendRef.Group != "" && *backendRef.Group != groupCore && *backendRef.Kind != "Service" {
|
||||
return nil, nil, fmt.Errorf("unsupported BackendRef %s/%s/%s", *backendRef.Group, *backendRef.Kind, backendRef.Name)
|
||||
}
|
||||
|
||||
|
@ -1880,3 +1930,65 @@ func makeListenerKey(l gatev1.Listener) string {
|
|||
|
||||
return fmt.Sprintf("%s|%s|%d", l.Protocol, hostname, l.Port)
|
||||
}
|
||||
|
||||
func filterReferenceGrantsFrom(referenceGrants []*gatev1beta1.ReferenceGrant, group, kind, namespace string) []*gatev1beta1.ReferenceGrant {
|
||||
var matchingReferenceGrants []*gatev1beta1.ReferenceGrant
|
||||
for _, referenceGrant := range referenceGrants {
|
||||
if referenceGrantMatchesFrom(referenceGrant, group, kind, namespace) {
|
||||
matchingReferenceGrants = append(matchingReferenceGrants, referenceGrant)
|
||||
}
|
||||
}
|
||||
return matchingReferenceGrants
|
||||
}
|
||||
|
||||
func referenceGrantMatchesFrom(referenceGrant *gatev1beta1.ReferenceGrant, group, kind, namespace string) bool {
|
||||
for _, from := range referenceGrant.Spec.From {
|
||||
sanitizedGroup := string(from.Group)
|
||||
if sanitizedGroup == "" {
|
||||
sanitizedGroup = groupCore
|
||||
}
|
||||
if string(from.Namespace) != namespace || string(from.Kind) != kind || sanitizedGroup != group {
|
||||
continue
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func filterReferenceGrantsTo(referenceGrants []*gatev1beta1.ReferenceGrant, group, kind, name string) []*gatev1beta1.ReferenceGrant {
|
||||
var matchingReferenceGrants []*gatev1beta1.ReferenceGrant
|
||||
for _, referenceGrant := range referenceGrants {
|
||||
if referenceGrantMatchesTo(referenceGrant, group, kind, name) {
|
||||
matchingReferenceGrants = append(matchingReferenceGrants, referenceGrant)
|
||||
}
|
||||
}
|
||||
return matchingReferenceGrants
|
||||
}
|
||||
|
||||
func referenceGrantMatchesTo(referenceGrant *gatev1beta1.ReferenceGrant, group, kind, name string) bool {
|
||||
for _, to := range referenceGrant.Spec.To {
|
||||
sanitizedGroup := string(to.Group)
|
||||
if sanitizedGroup == "" {
|
||||
sanitizedGroup = groupCore
|
||||
}
|
||||
if string(to.Kind) != kind || sanitizedGroup != group || (to.Name != nil && string(*to.Name) != name) {
|
||||
continue
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func groupToString(p *gatev1.Group) string {
|
||||
if p == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return string(*p)
|
||||
}
|
||||
|
||||
func kindToString(p *gatev1.Kind) string {
|
||||
if p == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return string(*p)
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/ptr"
|
||||
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
|
||||
)
|
||||
|
||||
var _ provider.Provider = (*Provider)(nil)
|
||||
|
@ -4628,6 +4629,196 @@ func TestLoadMixedRoutes(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLoadRoutesWithReferenceGrants(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
ingressClass string
|
||||
paths []string
|
||||
expected *dynamic.Configuration
|
||||
entryPoints map[string]Entrypoint
|
||||
}{
|
||||
{
|
||||
desc: "Empty",
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Empty because ReferenceGrant for Secret is missing",
|
||||
paths: []string{"services.yml", "referencegrant/for_secret_missing.yml"},
|
||||
entryPoints: map[string]Entrypoint{
|
||||
"tls": {Address: ":9000"},
|
||||
},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Empty because ReferenceGrant spec.from does not match",
|
||||
paths: []string{"services.yml", "referencegrant/for_secret_not_matching_from.yml"},
|
||||
entryPoints: map[string]Entrypoint{
|
||||
"tls": {Address: ":9000"},
|
||||
},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Empty because ReferenceGrant spec.to does not match",
|
||||
paths: []string{"services.yml", "referencegrant/for_secret_not_matching_to.yml"},
|
||||
entryPoints: map[string]Entrypoint{
|
||||
"tls": {Address: ":9000"},
|
||||
},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "For Secret",
|
||||
paths: []string{"services.yml", "referencegrant/for_secret.yml"},
|
||||
entryPoints: map[string]Entrypoint{
|
||||
"tls": {Address: ":9000"},
|
||||
},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{
|
||||
"default-tcp-app-1-my-gateway-tls-e3b0c44298fc1c149afb": {
|
||||
EntryPoints: []string{"tls"},
|
||||
Service: "default-tcp-app-1-my-gateway-tls-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{
|
||||
"default-tcp-app-1-my-gateway-tls-e3b0c44298fc1c149afb-wrr-0": {
|
||||
Weighted: &dynamic.TCPWeightedRoundRobin{
|
||||
Services: []dynamic.TCPWRRService{{
|
||||
Name: "default-whoamitcp-9000",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
}},
|
||||
},
|
||||
},
|
||||
"default-whoamitcp-9000": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "10.10.0.9:9000",
|
||||
},
|
||||
{
|
||||
Address: "10.10.0.10:9000",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
Certificates: []*tls.CertAndStores{
|
||||
{
|
||||
Certificate: tls.Certificate{
|
||||
CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"),
|
||||
KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if test.expected == nil {
|
||||
return
|
||||
}
|
||||
|
||||
p := Provider{EntryPoints: test.entryPoints}
|
||||
conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...))
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_hostRule(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
|
@ -5644,3 +5835,247 @@ func kindPtr(kind gatev1.Kind) *gatev1.Kind {
|
|||
func pathMatchTypePtr(p gatev1.PathMatchType) *gatev1.PathMatchType { return &p }
|
||||
|
||||
func headerMatchTypePtr(h gatev1.HeaderMatchType) *gatev1.HeaderMatchType { return &h }
|
||||
|
||||
func Test_referenceGrantMatchesFrom(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
referenceGrant gatev1beta1.ReferenceGrant
|
||||
group string
|
||||
kind string
|
||||
namespace string
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
desc: "matches",
|
||||
referenceGrant: gatev1beta1.ReferenceGrant{
|
||||
Spec: gatev1beta1.ReferenceGrantSpec{
|
||||
From: []gatev1beta1.ReferenceGrantFrom{
|
||||
{
|
||||
Group: "correct-group",
|
||||
Kind: "correct-kind",
|
||||
Namespace: "correct-namespace",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
group: "correct-group",
|
||||
kind: "correct-kind",
|
||||
namespace: "correct-namespace",
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
desc: "empty group matches core",
|
||||
referenceGrant: gatev1beta1.ReferenceGrant{
|
||||
Spec: gatev1beta1.ReferenceGrantSpec{
|
||||
From: []gatev1beta1.ReferenceGrantFrom{
|
||||
{
|
||||
Group: "",
|
||||
Kind: "correct-kind",
|
||||
Namespace: "correct-namespace",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
group: "core",
|
||||
kind: "correct-kind",
|
||||
namespace: "correct-namespace",
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
desc: "wrong group",
|
||||
referenceGrant: gatev1beta1.ReferenceGrant{
|
||||
Spec: gatev1beta1.ReferenceGrantSpec{
|
||||
From: []gatev1beta1.ReferenceGrantFrom{
|
||||
{
|
||||
Group: "wrong-group",
|
||||
Kind: "correct-kind",
|
||||
Namespace: "correct-namespace",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
group: "correct-group",
|
||||
kind: "correct-kind",
|
||||
namespace: "correct-namespace",
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
desc: "wrong kind",
|
||||
referenceGrant: gatev1beta1.ReferenceGrant{
|
||||
Spec: gatev1beta1.ReferenceGrantSpec{
|
||||
From: []gatev1beta1.ReferenceGrantFrom{
|
||||
{
|
||||
Group: "correct-group",
|
||||
Kind: "wrong-kind",
|
||||
Namespace: "correct-namespace",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
group: "correct-group",
|
||||
kind: "correct-kind",
|
||||
namespace: "correct-namespace",
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
desc: "wrong namespace",
|
||||
referenceGrant: gatev1beta1.ReferenceGrant{
|
||||
Spec: gatev1beta1.ReferenceGrantSpec{
|
||||
From: []gatev1beta1.ReferenceGrantFrom{
|
||||
{
|
||||
Group: "correct-group",
|
||||
Kind: "correct-kind",
|
||||
Namespace: "wrong-namespace",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
group: "correct-group",
|
||||
kind: "correct-kind",
|
||||
namespace: "correct-namespace",
|
||||
expectedResult: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.Equal(t, test.expectedResult, referenceGrantMatchesFrom(&test.referenceGrant, test.group, test.kind, test.namespace))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_referenceGrantMatchesTo(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
referenceGrant gatev1beta1.ReferenceGrant
|
||||
group string
|
||||
kind string
|
||||
name string
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
desc: "matches",
|
||||
referenceGrant: gatev1beta1.ReferenceGrant{
|
||||
Spec: gatev1beta1.ReferenceGrantSpec{
|
||||
To: []gatev1beta1.ReferenceGrantTo{
|
||||
{
|
||||
Group: "correct-group",
|
||||
Kind: "correct-kind",
|
||||
Name: objectNamePtr("correct-name"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
group: "correct-group",
|
||||
kind: "correct-kind",
|
||||
name: "correct-name",
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
desc: "matches without name",
|
||||
referenceGrant: gatev1beta1.ReferenceGrant{
|
||||
Spec: gatev1beta1.ReferenceGrantSpec{
|
||||
To: []gatev1beta1.ReferenceGrantTo{
|
||||
{
|
||||
Group: "correct-group",
|
||||
Kind: "correct-kind",
|
||||
Name: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
group: "correct-group",
|
||||
kind: "correct-kind",
|
||||
name: "some-name",
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
desc: "empty group matches core",
|
||||
referenceGrant: gatev1beta1.ReferenceGrant{
|
||||
Spec: gatev1beta1.ReferenceGrantSpec{
|
||||
To: []gatev1beta1.ReferenceGrantTo{
|
||||
{
|
||||
Group: "",
|
||||
Kind: "correct-kind",
|
||||
Name: objectNamePtr("correct-name"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
group: "core",
|
||||
kind: "correct-kind",
|
||||
name: "correct-name",
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
desc: "wrong group",
|
||||
referenceGrant: gatev1beta1.ReferenceGrant{
|
||||
Spec: gatev1beta1.ReferenceGrantSpec{
|
||||
To: []gatev1beta1.ReferenceGrantTo{
|
||||
{
|
||||
Group: "wrong-group",
|
||||
Kind: "correct-kind",
|
||||
Name: objectNamePtr("correct-name"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
group: "correct-group",
|
||||
kind: "correct-kind",
|
||||
name: "correct-namespace",
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
desc: "wrong kind",
|
||||
referenceGrant: gatev1beta1.ReferenceGrant{
|
||||
Spec: gatev1beta1.ReferenceGrantSpec{
|
||||
To: []gatev1beta1.ReferenceGrantTo{
|
||||
{
|
||||
Group: "correct-group",
|
||||
Kind: "wrong-kind",
|
||||
Name: objectNamePtr("correct-name"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
group: "correct-group",
|
||||
kind: "correct-kind",
|
||||
name: "correct-name",
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
desc: "wrong name",
|
||||
referenceGrant: gatev1beta1.ReferenceGrant{
|
||||
Spec: gatev1beta1.ReferenceGrantSpec{
|
||||
To: []gatev1beta1.ReferenceGrantTo{
|
||||
{
|
||||
Group: "correct-group",
|
||||
Kind: "correct-kind",
|
||||
Name: objectNamePtr("wrong-name"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
group: "correct-group",
|
||||
kind: "correct-kind",
|
||||
name: "correct-name",
|
||||
expectedResult: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.Equal(t, test.expectedResult, referenceGrantMatchesTo(&test.referenceGrant, test.group, test.kind, test.name))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func objectNamePtr(objectName gatev1.ObjectName) *gatev1.ObjectName {
|
||||
return &objectName
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
|
||||
// MustParseYaml parses a YAML to objects.
|
||||
func MustParseYaml(content []byte) []runtime.Object {
|
||||
acceptedK8sTypes := regexp.MustCompile(`^(Namespace|Deployment|Endpoints|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|MiddlewareTCP|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|ServersTransportTCP|GatewayClass|Gateway|HTTPRoute|TCPRoute|TLSRoute)$`)
|
||||
acceptedK8sTypes := regexp.MustCompile(`^(Namespace|Deployment|Endpoints|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|MiddlewareTCP|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|ServersTransportTCP|GatewayClass|Gateway|HTTPRoute|TCPRoute|TLSRoute|ReferenceGrant)$`)
|
||||
|
||||
files := strings.Split(string(content), "---\n")
|
||||
retVal := make([]runtime.Object, 0, len(files))
|
||||
|
|
|
@ -15,6 +15,9 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
)
|
||||
|
||||
func Bool(v bool) *bool { return &v }
|
||||
func String(v string) *string { return &v }
|
||||
|
||||
func Test_buildConfiguration(t *testing.T) {
|
||||
provider := newProviderMock(mapToPairs(map[string]string{
|
||||
"traefik/http/routers/Router0/entryPoints/0": "foobar",
|
||||
|
@ -79,6 +82,7 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
"traefik/http/middlewares/Middleware08/forwardAuth/tls/key": "foobar",
|
||||
"traefik/http/middlewares/Middleware08/forwardAuth/tls/insecureSkipVerify": "true",
|
||||
"traefik/http/middlewares/Middleware08/forwardAuth/tls/ca": "foobar",
|
||||
"traefik/http/middlewares/Middleware08/forwardAuth/tls/caOptional": "true",
|
||||
"traefik/http/middlewares/Middleware08/forwardAuth/tls/cert": "foobar",
|
||||
"traefik/http/middlewares/Middleware08/forwardAuth/address": "foobar",
|
||||
"traefik/http/middlewares/Middleware08/forwardAuth/trustForwardHeader": "true",
|
||||
|
@ -105,8 +109,12 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
"traefik/http/middlewares/Middleware09/headers/accessControlAllowOriginListRegex/1": "foobar",
|
||||
"traefik/http/middlewares/Middleware09/headers/contentTypeNosniff": "true",
|
||||
"traefik/http/middlewares/Middleware09/headers/accessControlAllowCredentials": "true",
|
||||
"traefik/http/middlewares/Middleware09/headers/featurePolicy": "foobar",
|
||||
"traefik/http/middlewares/Middleware09/headers/permissionsPolicy": "foobar",
|
||||
"traefik/http/middlewares/Middleware09/headers/forceSTSHeader": "true",
|
||||
"traefik/http/middlewares/Middleware09/headers/sslRedirect": "true",
|
||||
"traefik/http/middlewares/Middleware09/headers/sslHost": "foobar",
|
||||
"traefik/http/middlewares/Middleware09/headers/sslForceHost": "true",
|
||||
"traefik/http/middlewares/Middleware09/headers/sslProxyHeaders/name1": "foobar",
|
||||
"traefik/http/middlewares/Middleware09/headers/sslProxyHeaders/name0": "foobar",
|
||||
"traefik/http/middlewares/Middleware09/headers/allowedHosts/0": "foobar",
|
||||
|
@ -125,6 +133,7 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
"traefik/http/middlewares/Middleware09/headers/addVaryHeader": "true",
|
||||
"traefik/http/middlewares/Middleware09/headers/hostsProxyHeaders/0": "foobar",
|
||||
"traefik/http/middlewares/Middleware09/headers/hostsProxyHeaders/1": "foobar",
|
||||
"traefik/http/middlewares/Middleware09/headers/sslTemporaryRedirect": "true",
|
||||
"traefik/http/middlewares/Middleware09/headers/customBrowserXSSValue": "foobar",
|
||||
"traefik/http/middlewares/Middleware09/headers/referrerPolicy": "foobar",
|
||||
"traefik/http/middlewares/Middleware09/headers/accessControlExposeHeaders/0": "foobar",
|
||||
|
@ -171,6 +180,7 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
"traefik/http/middlewares/Middleware04/circuitBreaker/checkPeriod": "1s",
|
||||
"traefik/http/middlewares/Middleware04/circuitBreaker/fallbackDuration": "1s",
|
||||
"traefik/http/middlewares/Middleware04/circuitBreaker/recoveryDuration": "1s",
|
||||
"traefik/http/middlewares/Middleware04/circuitBreaker/responseCode": "404",
|
||||
"traefik/http/middlewares/Middleware07/errors/status/0": "foobar",
|
||||
"traefik/http/middlewares/Middleware07/errors/status/1": "foobar",
|
||||
"traefik/http/middlewares/Middleware07/errors/service": "foobar",
|
||||
|
@ -200,6 +210,7 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
"traefik/http/middlewares/Middleware18/retry/attempts": "42",
|
||||
"traefik/http/middlewares/Middleware19/stripPrefix/prefixes/0": "foobar",
|
||||
"traefik/http/middlewares/Middleware19/stripPrefix/prefixes/1": "foobar",
|
||||
"traefik/http/middlewares/Middleware19/stripPrefix/forceSlash": "true",
|
||||
"traefik/tcp/routers/TCPRouter0/entryPoints/0": "foobar",
|
||||
"traefik/tcp/routers/TCPRouter0/entryPoints/1": "foobar",
|
||||
"traefik/tcp/routers/TCPRouter0/service": "foobar",
|
||||
|
@ -226,6 +237,7 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
"traefik/tcp/routers/TCPRouter1/tls/passthrough": "true",
|
||||
"traefik/tcp/routers/TCPRouter1/tls/options": "foobar",
|
||||
"traefik/tcp/routers/TCPRouter1/tls/certResolver": "foobar",
|
||||
"traefik/tcp/services/TCPService01/loadBalancer/terminationDelay": "42",
|
||||
"traefik/tcp/services/TCPService01/loadBalancer/servers/0/address": "foobar",
|
||||
"traefik/tcp/services/TCPService01/loadBalancer/servers/1/address": "foobar",
|
||||
"traefik/tcp/services/TCPService02/weighted/services/0/name": "foobar",
|
||||
|
@ -370,6 +382,7 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
"foobar",
|
||||
"foobar",
|
||||
},
|
||||
ForceSlash: Bool(true),
|
||||
},
|
||||
},
|
||||
"Middleware00": {
|
||||
|
@ -392,6 +405,7 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
CheckPeriod: ptypes.Duration(time.Second),
|
||||
FallbackDuration: ptypes.Duration(time.Second),
|
||||
RecoveryDuration: ptypes.Duration(time.Second),
|
||||
ResponseCode: 404,
|
||||
},
|
||||
},
|
||||
"Middleware05": {
|
||||
|
@ -402,11 +416,12 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
"Middleware08": {
|
||||
ForwardAuth: &dynamic.ForwardAuth{
|
||||
Address: "foobar",
|
||||
TLS: &types.ClientTLS{
|
||||
TLS: &dynamic.ClientTLS{
|
||||
CA: "foobar",
|
||||
Cert: "foobar",
|
||||
Key: "foobar",
|
||||
InsecureSkipVerify: true,
|
||||
CAOptional: Bool(true),
|
||||
},
|
||||
TrustForwardHeader: true,
|
||||
AuthResponseHeaders: []string{
|
||||
|
@ -579,10 +594,14 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
"foobar",
|
||||
"foobar",
|
||||
},
|
||||
SSLRedirect: Bool(true),
|
||||
SSLTemporaryRedirect: Bool(true),
|
||||
SSLHost: String("foobar"),
|
||||
SSLProxyHeaders: map[string]string{
|
||||
"name1": "foobar",
|
||||
"name0": "foobar",
|
||||
},
|
||||
SSLForceHost: Bool(true),
|
||||
STSSeconds: 42,
|
||||
STSIncludeSubdomains: true,
|
||||
STSPreload: true,
|
||||
|
@ -595,6 +614,7 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
ContentSecurityPolicy: "foobar",
|
||||
PublicKey: "foobar",
|
||||
ReferrerPolicy: "foobar",
|
||||
FeaturePolicy: String("foobar"),
|
||||
PermissionsPolicy: "foobar",
|
||||
IsDevelopment: true,
|
||||
},
|
||||
|
@ -755,6 +775,7 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
Services: map[string]*dynamic.TCPService{
|
||||
"TCPService01": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
TerminationDelay: func(v int) *int { return &v }(42),
|
||||
Servers: []dynamic.TCPServer{
|
||||
{Address: "foobar"},
|
||||
{Address: "foobar"},
|
||||
|
|
|
@ -263,7 +263,7 @@ func init() {
|
|||
},
|
||||
ForwardAuth: &dynamic.ForwardAuth{
|
||||
Address: "127.0.0.1",
|
||||
TLS: &types.ClientTLS{
|
||||
TLS: &dynamic.ClientTLS{
|
||||
CA: "ca.pem",
|
||||
Cert: "cert.pem",
|
||||
Key: "cert.pem",
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/containous/alice"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/metrics"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
|
||||
metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
|
||||
tracingMiddle "github.com/traefik/traefik/v3/pkg/middlewares/tracing"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// ChainBuilder Creates a middleware chain by entry point. It is used for middlewares that are created almost systematically and that need to be created before all others.
|
||||
type ChainBuilder struct {
|
||||
metricsRegistry metrics.Registry
|
||||
accessLoggerMiddleware *accesslog.Handler
|
||||
tracer trace.Tracer
|
||||
}
|
||||
|
||||
// NewChainBuilder Creates a new ChainBuilder.
|
||||
func NewChainBuilder(metricsRegistry metrics.Registry, accessLoggerMiddleware *accesslog.Handler, tracer trace.Tracer) *ChainBuilder {
|
||||
return &ChainBuilder{
|
||||
metricsRegistry: metricsRegistry,
|
||||
accessLoggerMiddleware: accessLoggerMiddleware,
|
||||
tracer: tracer,
|
||||
}
|
||||
}
|
||||
|
||||
// Build a middleware chain by entry point.
|
||||
func (c *ChainBuilder) Build(ctx context.Context, entryPointName string) alice.Chain {
|
||||
chain := alice.New()
|
||||
|
||||
if c.accessLoggerMiddleware != nil || c.metricsRegistry != nil && (c.metricsRegistry.IsEpEnabled() || c.metricsRegistry.IsRouterEnabled() || c.metricsRegistry.IsSvcEnabled()) {
|
||||
chain = chain.Append(capture.Wrap)
|
||||
}
|
||||
|
||||
if c.accessLoggerMiddleware != nil {
|
||||
chain = chain.Append(accesslog.WrapHandler(c.accessLoggerMiddleware))
|
||||
}
|
||||
|
||||
if c.tracer != nil {
|
||||
chain = chain.Append(tracingMiddle.WrapEntryPointHandler(ctx, c.tracer, entryPointName))
|
||||
}
|
||||
|
||||
if c.metricsRegistry != nil && c.metricsRegistry.IsEpEnabled() {
|
||||
metricsHandler := metricsMiddle.WrapEntryPointHandler(ctx, c.metricsRegistry, entryPointName)
|
||||
chain = chain.Append(tracingMiddle.WrapMiddleware(ctx, metricsHandler))
|
||||
}
|
||||
|
||||
return chain
|
||||
}
|
||||
|
||||
// Close accessLogger and tracer.
|
||||
func (c *ChainBuilder) Close() {
|
||||
if c.accessLoggerMiddleware != nil {
|
||||
if err := c.accessLoggerMiddleware.Close(); err != nil {
|
||||
log.Error().Err(err).Msg("Could not close the access log file")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -184,7 +184,7 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
|
|||
return nil, badConf
|
||||
}
|
||||
middleware = func(next http.Handler) (http.Handler, error) {
|
||||
return contenttype.New(ctx, next, middlewareName)
|
||||
return contenttype.New(ctx, next, *config.ContentType, middlewareName)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,7 +240,8 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
|
|||
|
||||
// IPWhiteList
|
||||
if config.IPWhiteList != nil {
|
||||
log.Warn().Msg("IPWhiteList is deprecated, please use IPAllowList instead.")
|
||||
qualifiedName := provider.GetQualifiedName(ctx, middlewareName)
|
||||
log.Warn().Msgf("Middleware %q of type IPWhiteList is deprecated, please use IPAllowList instead.", qualifiedName)
|
||||
|
||||
if middleware != nil {
|
||||
return nil, badConf
|
||||
|
@ -386,6 +387,9 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
|
|||
return nil, fmt.Errorf("invalid middleware %q configuration: invalid middleware type or middleware does not exist", middlewareName)
|
||||
}
|
||||
|
||||
// The tracing middleware is a NOOP if tracing is not setup on the middleware chain.
|
||||
// Hence, regarding internal resources' observability deactivation,
|
||||
// this would not enable tracing.
|
||||
return tracing.WrapMiddleware(ctx, middleware), nil
|
||||
}
|
||||
|
||||
|
|
140
pkg/server/middleware/observability.go
Normal file
140
pkg/server/middleware/observability.go
Normal file
|
@ -0,0 +1,140 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/alice"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/config/static"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"github.com/traefik/traefik/v3/pkg/metrics"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
|
||||
metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
|
||||
tracingMiddle "github.com/traefik/traefik/v3/pkg/middlewares/tracing"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// ObservabilityMgr is a manager for observability (AccessLogs, Metrics and Tracing) enablement.
|
||||
type ObservabilityMgr struct {
|
||||
config static.Configuration
|
||||
accessLoggerMiddleware *accesslog.Handler
|
||||
metricsRegistry metrics.Registry
|
||||
tracer trace.Tracer
|
||||
tracerCloser io.Closer
|
||||
}
|
||||
|
||||
// NewObservabilityMgr creates a new ObservabilityMgr.
|
||||
func NewObservabilityMgr(config static.Configuration, metricsRegistry metrics.Registry, accessLoggerMiddleware *accesslog.Handler, tracer trace.Tracer, tracerCloser io.Closer) *ObservabilityMgr {
|
||||
return &ObservabilityMgr{
|
||||
config: config,
|
||||
metricsRegistry: metricsRegistry,
|
||||
accessLoggerMiddleware: accessLoggerMiddleware,
|
||||
tracer: tracer,
|
||||
tracerCloser: tracerCloser,
|
||||
}
|
||||
}
|
||||
|
||||
// BuildEPChain an observability middleware chain by entry point.
|
||||
func (c *ObservabilityMgr) BuildEPChain(ctx context.Context, entryPointName string, resourceName string) alice.Chain {
|
||||
chain := alice.New()
|
||||
|
||||
if c == nil {
|
||||
return chain
|
||||
}
|
||||
|
||||
if c.accessLoggerMiddleware != nil || c.metricsRegistry != nil && (c.metricsRegistry.IsEpEnabled() || c.metricsRegistry.IsRouterEnabled() || c.metricsRegistry.IsSvcEnabled()) {
|
||||
if c.ShouldAddAccessLogs(resourceName) || c.ShouldAddMetrics(resourceName) {
|
||||
chain = chain.Append(capture.Wrap)
|
||||
}
|
||||
}
|
||||
|
||||
if c.accessLoggerMiddleware != nil && c.ShouldAddAccessLogs(resourceName) {
|
||||
chain = chain.Append(accesslog.WrapHandler(c.accessLoggerMiddleware))
|
||||
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
|
||||
return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.InitServiceFields), nil
|
||||
})
|
||||
}
|
||||
|
||||
if c.tracer != nil && c.ShouldAddTracing(resourceName) {
|
||||
chain = chain.Append(tracingMiddle.WrapEntryPointHandler(ctx, c.tracer, entryPointName))
|
||||
}
|
||||
|
||||
if c.metricsRegistry != nil && c.metricsRegistry.IsEpEnabled() && c.ShouldAddMetrics(resourceName) {
|
||||
metricsHandler := metricsMiddle.WrapEntryPointHandler(ctx, c.metricsRegistry, entryPointName)
|
||||
|
||||
if c.tracer != nil && c.ShouldAddTracing(resourceName) {
|
||||
chain = chain.Append(tracingMiddle.WrapMiddleware(ctx, metricsHandler))
|
||||
} else {
|
||||
chain = chain.Append(metricsHandler)
|
||||
}
|
||||
}
|
||||
|
||||
return chain
|
||||
}
|
||||
|
||||
// ShouldAddAccessLogs returns whether the access logs should be enabled for the given resource.
|
||||
func (c *ObservabilityMgr) ShouldAddAccessLogs(resourceName string) bool {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return c.config.AccessLog != nil && (c.config.AccessLog.AddInternals || !strings.HasSuffix(resourceName, "@internal"))
|
||||
}
|
||||
|
||||
// ShouldAddMetrics returns whether the metrics should be enabled for the given resource.
|
||||
func (c *ObservabilityMgr) ShouldAddMetrics(resourceName string) bool {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return c.config.Metrics != nil && (c.config.Metrics.AddInternals || !strings.HasSuffix(resourceName, "@internal"))
|
||||
}
|
||||
|
||||
// ShouldAddTracing returns whether the tracing should be enabled for the given resource.
|
||||
func (c *ObservabilityMgr) ShouldAddTracing(resourceName string) bool {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return c.config.Tracing != nil && (c.config.Tracing.AddInternals || !strings.HasSuffix(resourceName, "@internal"))
|
||||
}
|
||||
|
||||
// MetricsRegistry is an accessor to the metrics registry.
|
||||
func (c *ObservabilityMgr) MetricsRegistry() metrics.Registry {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.metricsRegistry
|
||||
}
|
||||
|
||||
// Close closes the accessLogger and tracer.
|
||||
func (c *ObservabilityMgr) Close() {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if c.accessLoggerMiddleware != nil {
|
||||
if err := c.accessLoggerMiddleware.Close(); err != nil {
|
||||
log.Error().Err(err).Msg("Could not close the access log file")
|
||||
}
|
||||
}
|
||||
|
||||
if c.tracerCloser != nil {
|
||||
if err := c.tracerCloser.Close(); err != nil {
|
||||
log.Error().Err(err).Msg("Could not close the tracer")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ObservabilityMgr) RotateAccessLogs() error {
|
||||
if c.accessLoggerMiddleware == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.accessLoggerMiddleware.Rotate()
|
||||
}
|
|
@ -10,7 +10,6 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/config/runtime"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"github.com/traefik/traefik/v3/pkg/metrics"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/denyrouterrecursion"
|
||||
metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
|
||||
|
@ -35,21 +34,19 @@ type serviceManager interface {
|
|||
type Manager struct {
|
||||
routerHandlers map[string]http.Handler
|
||||
serviceManager serviceManager
|
||||
metricsRegistry metrics.Registry
|
||||
observabilityMgr *middleware.ObservabilityMgr
|
||||
middlewaresBuilder middlewareBuilder
|
||||
chainBuilder *middleware.ChainBuilder
|
||||
conf *runtime.Configuration
|
||||
tlsManager *tls.Manager
|
||||
}
|
||||
|
||||
// NewManager creates a new Manager.
|
||||
func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, chainBuilder *middleware.ChainBuilder, metricsRegistry metrics.Registry, tlsManager *tls.Manager) *Manager {
|
||||
func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, observabilityMgr *middleware.ObservabilityMgr, tlsManager *tls.Manager) *Manager {
|
||||
return &Manager{
|
||||
routerHandlers: make(map[string]http.Handler),
|
||||
serviceManager: serviceManager,
|
||||
metricsRegistry: metricsRegistry,
|
||||
observabilityMgr: observabilityMgr,
|
||||
middlewaresBuilder: middlewaresBuilder,
|
||||
chainBuilder: chainBuilder,
|
||||
conf: conf,
|
||||
tlsManager: tlsManager,
|
||||
}
|
||||
|
@ -73,49 +70,49 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t
|
|||
logger := log.Ctx(rootCtx).With().Str(logs.EntryPointName, entryPointName).Logger()
|
||||
ctx := logger.WithContext(rootCtx)
|
||||
|
||||
handler, err := m.buildEntryPointHandler(ctx, routers)
|
||||
handler, err := m.buildEntryPointHandler(ctx, entryPointName, routers)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
continue
|
||||
}
|
||||
|
||||
handlerWithAccessLog, err := alice.New(func(next http.Handler) (http.Handler, error) {
|
||||
return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.InitServiceFields), nil
|
||||
}).Then(handler)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
entryPointHandlers[entryPointName] = handler
|
||||
} else {
|
||||
entryPointHandlers[entryPointName] = handlerWithAccessLog
|
||||
}
|
||||
entryPointHandlers[entryPointName] = handler
|
||||
}
|
||||
|
||||
// Create default handlers.
|
||||
for _, entryPointName := range entryPoints {
|
||||
logger := log.Ctx(rootCtx).With().Str(logs.EntryPointName, entryPointName).Logger()
|
||||
ctx := logger.WithContext(rootCtx)
|
||||
|
||||
handler, ok := entryPointHandlers[entryPointName]
|
||||
if !ok || handler == nil {
|
||||
handler = BuildDefaultHTTPRouter()
|
||||
if ok || handler != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
handlerWithMiddlewares, err := m.chainBuilder.Build(ctx, entryPointName).Then(handler)
|
||||
handler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "").Then(BuildDefaultHTTPRouter())
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
continue
|
||||
}
|
||||
entryPointHandlers[entryPointName] = handlerWithMiddlewares
|
||||
entryPointHandlers[entryPointName] = handler
|
||||
}
|
||||
|
||||
return entryPointHandlers
|
||||
}
|
||||
|
||||
func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*runtime.RouterInfo) (http.Handler, error) {
|
||||
func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName string, configs map[string]*runtime.RouterInfo) (http.Handler, error) {
|
||||
muxer, err := httpmuxer.NewMuxer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "defaultHandler").Then(http.NotFoundHandler())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
muxer.SetDefaultHandler(defaultHandler)
|
||||
|
||||
for routerName, routerConfig := range configs {
|
||||
logger := log.Ctx(ctx).With().Str(logs.RouterName, routerName).Logger()
|
||||
ctxRouter := logger.WithContext(provider.AddInContext(ctx, routerName))
|
||||
|
@ -131,6 +128,14 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
|||
continue
|
||||
}
|
||||
|
||||
observabilityChain := m.observabilityMgr.BuildEPChain(ctx, entryPointName, routerConfig.Service)
|
||||
handler, err = observabilityChain.Then(handler)
|
||||
if err != nil {
|
||||
routerConfig.AddError(err, true)
|
||||
logger.Error().Err(err).Send()
|
||||
continue
|
||||
}
|
||||
|
||||
if err = muxer.AddRoute(routerConfig.Rule, routerConfig.RuleSyntax, routerConfig.Priority, handler); err != nil {
|
||||
routerConfig.AddError(err, true)
|
||||
logger.Error().Err(err).Send()
|
||||
|
@ -167,6 +172,12 @@ func (m *Manager) buildRouterHandler(ctx context.Context, routerName string, rou
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Prevents from enabling observability for internal resources.
|
||||
if !m.observabilityMgr.ShouldAddAccessLogs(provider.GetQualifiedName(ctx, routerConfig.Service)) {
|
||||
m.routerHandlers[routerName] = handler
|
||||
return m.routerHandlers[routerName], nil
|
||||
}
|
||||
|
||||
handlerWithAccessLog, err := alice.New(func(next http.Handler) (http.Handler, error) {
|
||||
return accesslog.NewFieldHandler(next, accesslog.RouterName, routerName, nil), nil
|
||||
}).Then(handler)
|
||||
|
@ -200,10 +211,20 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn
|
|||
|
||||
chain := alice.New()
|
||||
|
||||
if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsRouterEnabled() &&
|
||||
m.observabilityMgr.ShouldAddMetrics(provider.GetQualifiedName(ctx, router.Service)) {
|
||||
chain = chain.Append(metricsMiddle.WrapRouterHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, provider.GetQualifiedName(ctx, router.Service)))
|
||||
}
|
||||
|
||||
// Prevents from enabling tracing for internal resources.
|
||||
if !m.observabilityMgr.ShouldAddTracing(provider.GetQualifiedName(ctx, router.Service)) {
|
||||
return chain.Extend(*mHandler).Then(sHandler)
|
||||
}
|
||||
|
||||
chain = chain.Append(tracing.WrapRouterHandler(ctx, routerName, router.Rule, provider.GetQualifiedName(ctx, router.Service)))
|
||||
|
||||
if m.metricsRegistry != nil && m.metricsRegistry.IsRouterEnabled() {
|
||||
metricsHandler := metricsMiddle.WrapRouterHandler(ctx, m.metricsRegistry, routerName, provider.GetQualifiedName(ctx, router.Service))
|
||||
if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsRouterEnabled() {
|
||||
metricsHandler := metricsMiddle.WrapRouterHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, provider.GetQualifiedName(ctx, router.Service))
|
||||
chain = chain.Append(tracing.WrapMiddleware(ctx, metricsHandler))
|
||||
}
|
||||
|
||||
|
|
|
@ -9,21 +9,15 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/alice"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/config/runtime"
|
||||
"github.com/traefik/traefik/v3/pkg/metrics"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/requestdecorator"
|
||||
"github.com/traefik/traefik/v3/pkg/server/middleware"
|
||||
"github.com/traefik/traefik/v3/pkg/server/service"
|
||||
"github.com/traefik/traefik/v3/pkg/testhelpers"
|
||||
"github.com/traefik/traefik/v3/pkg/tls"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
)
|
||||
|
||||
func TestRouterManager_Get(t *testing.T) {
|
||||
|
@ -319,10 +313,9 @@ func TestRouterManager_Get(t *testing.T) {
|
|||
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
|
||||
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
|
||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
||||
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
|
||||
tlsManager := tls.NewManager()
|
||||
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager)
|
||||
|
||||
handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
|
||||
|
||||
|
@ -341,126 +334,6 @@ func TestRouterManager_Get(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAccessLog(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
|
||||
t.Cleanup(func() { server.Close() })
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
routersConfig map[string]*dynamic.Router
|
||||
serviceConfig map[string]*dynamic.Service
|
||||
middlewaresConfig map[string]*dynamic.Middleware
|
||||
entryPoints []string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "apply routerName in accesslog (first match)",
|
||||
routersConfig: map[string]*dynamic.Router{
|
||||
"foo": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
"bar": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`bar.foo`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
entryPoints: []string{"web"},
|
||||
expected: "foo",
|
||||
},
|
||||
{
|
||||
desc: "apply routerName in accesslog (second match)",
|
||||
routersConfig: map[string]*dynamic.Router{
|
||||
"foo": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`bar.foo`)",
|
||||
},
|
||||
"bar": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
entryPoints: []string{"web"},
|
||||
expected: "bar",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
rtConf := runtime.NewConfig(dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Services: test.serviceConfig,
|
||||
Routers: test.routersConfig,
|
||||
Middlewares: test.middlewaresConfig,
|
||||
},
|
||||
})
|
||||
|
||||
roundTripperManager := service.NewRoundTripperManager(nil)
|
||||
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
|
||||
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
|
||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
||||
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
|
||||
tlsManager := tls.NewManager()
|
||||
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
|
||||
|
||||
handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil)
|
||||
|
||||
accesslogger, err := accesslog.NewHandler(&types.AccessLog{
|
||||
Format: "json",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
reqHost := requestdecorator.New(nil)
|
||||
|
||||
chain := alice.New()
|
||||
chain = chain.Append(capture.Wrap)
|
||||
chain = chain.Append(accesslog.WrapHandler(accesslogger))
|
||||
handler, err := chain.Then(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
reqHost.ServeHTTP(w, req, handlers["web"].ServeHTTP)
|
||||
|
||||
data := accesslog.GetLogData(req)
|
||||
require.NotNil(t, data)
|
||||
|
||||
assert.Equal(t, test.expected, data.Core[accesslog.RouterName])
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRuntimeConfiguration(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
|
@ -788,11 +661,10 @@ func TestRuntimeConfiguration(t *testing.T) {
|
|||
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
|
||||
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
|
||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
||||
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
|
||||
tlsManager := tls.NewManager()
|
||||
tlsManager.UpdateConfigs(context.Background(), nil, test.tlsOptions, nil)
|
||||
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager)
|
||||
|
||||
_ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
|
||||
_ = routerManager.BuildHandlers(context.Background(), entryPoints, true)
|
||||
|
@ -866,10 +738,9 @@ func TestProviderOnMiddlewares(t *testing.T) {
|
|||
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
|
||||
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
|
||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
||||
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
|
||||
tlsManager := tls.NewManager()
|
||||
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager)
|
||||
|
||||
_ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
|
||||
|
||||
|
@ -935,10 +806,9 @@ func BenchmarkRouterServe(b *testing.B) {
|
|||
|
||||
serviceManager := service.NewManager(rtConf.Services, nil, nil, staticRoundTripperGetter{res})
|
||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
||||
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
|
||||
tlsManager := tls.NewManager()
|
||||
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager)
|
||||
|
||||
handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false)
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/config/runtime"
|
||||
"github.com/traefik/traefik/v3/pkg/config/static"
|
||||
"github.com/traefik/traefik/v3/pkg/metrics"
|
||||
"github.com/traefik/traefik/v3/pkg/server/middleware"
|
||||
tcpmiddleware "github.com/traefik/traefik/v3/pkg/server/middleware/tcp"
|
||||
"github.com/traefik/traefik/v3/pkg/server/router"
|
||||
|
@ -25,13 +24,12 @@ type RouterFactory struct {
|
|||
entryPointsTCP []string
|
||||
entryPointsUDP []string
|
||||
|
||||
managerFactory *service.ManagerFactory
|
||||
metricsRegistry metrics.Registry
|
||||
managerFactory *service.ManagerFactory
|
||||
|
||||
pluginBuilder middleware.PluginsBuilder
|
||||
|
||||
chainBuilder *middleware.ChainBuilder
|
||||
tlsManager *tls.Manager
|
||||
observabilityMgr *middleware.ObservabilityMgr
|
||||
tlsManager *tls.Manager
|
||||
|
||||
dialerManager *tcp.DialerManager
|
||||
|
||||
|
@ -40,7 +38,7 @@ type RouterFactory struct {
|
|||
|
||||
// NewRouterFactory creates a new RouterFactory.
|
||||
func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *service.ManagerFactory, tlsManager *tls.Manager,
|
||||
chainBuilder *middleware.ChainBuilder, pluginBuilder middleware.PluginsBuilder, metricsRegistry metrics.Registry, dialerManager *tcp.DialerManager,
|
||||
observabilityMgr *middleware.ObservabilityMgr, pluginBuilder middleware.PluginsBuilder, dialerManager *tcp.DialerManager,
|
||||
) *RouterFactory {
|
||||
var entryPointsTCP, entryPointsUDP []string
|
||||
for name, cfg := range staticConfiguration.EntryPoints {
|
||||
|
@ -58,14 +56,13 @@ func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *
|
|||
}
|
||||
|
||||
return &RouterFactory{
|
||||
entryPointsTCP: entryPointsTCP,
|
||||
entryPointsUDP: entryPointsUDP,
|
||||
managerFactory: managerFactory,
|
||||
metricsRegistry: metricsRegistry,
|
||||
tlsManager: tlsManager,
|
||||
chainBuilder: chainBuilder,
|
||||
pluginBuilder: pluginBuilder,
|
||||
dialerManager: dialerManager,
|
||||
entryPointsTCP: entryPointsTCP,
|
||||
entryPointsUDP: entryPointsUDP,
|
||||
managerFactory: managerFactory,
|
||||
observabilityMgr: observabilityMgr,
|
||||
tlsManager: tlsManager,
|
||||
pluginBuilder: pluginBuilder,
|
||||
dialerManager: dialerManager,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,7 +80,7 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string
|
|||
|
||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, f.pluginBuilder)
|
||||
|
||||
routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.chainBuilder, f.metricsRegistry, f.tlsManager)
|
||||
routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.observabilityMgr, f.tlsManager)
|
||||
|
||||
handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, false)
|
||||
handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true)
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/config/runtime"
|
||||
"github.com/traefik/traefik/v3/pkg/config/static"
|
||||
"github.com/traefik/traefik/v3/pkg/metrics"
|
||||
"github.com/traefik/traefik/v3/pkg/server/middleware"
|
||||
"github.com/traefik/traefik/v3/pkg/server/service"
|
||||
"github.com/traefik/traefik/v3/pkg/tcp"
|
||||
|
@ -51,12 +50,12 @@ func TestReuseService(t *testing.T) {
|
|||
|
||||
roundTripperManager := service.NewRoundTripperManager(nil)
|
||||
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
|
||||
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager, nil)
|
||||
managerFactory := service.NewManagerFactory(staticConfig, nil, nil, roundTripperManager, nil)
|
||||
tlsManager := tls.NewManager()
|
||||
|
||||
dialerManager := tcp.NewDialerManager(nil)
|
||||
dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}})
|
||||
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(nil, nil, nil), nil, metrics.NewVoidRegistry(), dialerManager)
|
||||
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, nil, nil, dialerManager)
|
||||
|
||||
entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: dynamicConfigs}))
|
||||
|
||||
|
@ -189,12 +188,13 @@ func TestServerResponseEmptyBackend(t *testing.T) {
|
|||
|
||||
roundTripperManager := service.NewRoundTripperManager(nil)
|
||||
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
|
||||
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager, nil)
|
||||
managerFactory := service.NewManagerFactory(staticConfig, nil, nil, roundTripperManager, nil)
|
||||
tlsManager := tls.NewManager()
|
||||
|
||||
dialerManager := tcp.NewDialerManager(nil)
|
||||
dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}})
|
||||
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(nil, nil, nil), nil, metrics.NewVoidRegistry(), dialerManager)
|
||||
observabiltyMgr := middleware.NewObservabilityMgr(staticConfig, nil, nil, nil, nil)
|
||||
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, observabiltyMgr, nil, dialerManager)
|
||||
|
||||
entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: test.config(testServer.URL)}))
|
||||
|
||||
|
@ -232,14 +232,12 @@ func TestInternalServices(t *testing.T) {
|
|||
|
||||
roundTripperManager := service.NewRoundTripperManager(nil)
|
||||
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
|
||||
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager, nil)
|
||||
managerFactory := service.NewManagerFactory(staticConfig, nil, nil, roundTripperManager, nil)
|
||||
tlsManager := tls.NewManager()
|
||||
|
||||
voidRegistry := metrics.NewVoidRegistry()
|
||||
|
||||
dialerManager := tcp.NewDialerManager(nil)
|
||||
dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}})
|
||||
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(voidRegistry, nil, nil), nil, voidRegistry, dialerManager)
|
||||
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, nil, nil, dialerManager)
|
||||
|
||||
entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: dynamicConfigs}))
|
||||
|
||||
|
|
|
@ -3,47 +3,39 @@ package server
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/metrics"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
|
||||
"github.com/traefik/traefik/v3/pkg/safe"
|
||||
"github.com/traefik/traefik/v3/pkg/server/middleware"
|
||||
)
|
||||
|
||||
// Server is the reverse-proxy/load-balancer engine.
|
||||
type Server struct {
|
||||
watcher *ConfigurationWatcher
|
||||
tcpEntryPoints TCPEntryPoints
|
||||
udpEntryPoints UDPEntryPoints
|
||||
chainBuilder *middleware.ChainBuilder
|
||||
|
||||
accessLoggerMiddleware *accesslog.Handler
|
||||
watcher *ConfigurationWatcher
|
||||
tcpEntryPoints TCPEntryPoints
|
||||
udpEntryPoints UDPEntryPoints
|
||||
observabilityMgr *middleware.ObservabilityMgr
|
||||
|
||||
signals chan os.Signal
|
||||
stopChan chan bool
|
||||
|
||||
routinesPool *safe.Pool
|
||||
|
||||
tracerCloser io.Closer
|
||||
}
|
||||
|
||||
// NewServer returns an initialized Server.
|
||||
func NewServer(routinesPool *safe.Pool, entryPoints TCPEntryPoints, entryPointsUDP UDPEntryPoints, watcher *ConfigurationWatcher, chainBuilder *middleware.ChainBuilder, accessLoggerMiddleware *accesslog.Handler, tracerCloser io.Closer) *Server {
|
||||
func NewServer(routinesPool *safe.Pool, entryPoints TCPEntryPoints, entryPointsUDP UDPEntryPoints, watcher *ConfigurationWatcher, observabilityMgr *middleware.ObservabilityMgr) *Server {
|
||||
srv := &Server{
|
||||
watcher: watcher,
|
||||
tcpEntryPoints: entryPoints,
|
||||
chainBuilder: chainBuilder,
|
||||
accessLoggerMiddleware: accessLoggerMiddleware,
|
||||
signals: make(chan os.Signal, 1),
|
||||
stopChan: make(chan bool, 1),
|
||||
routinesPool: routinesPool,
|
||||
udpEntryPoints: entryPointsUDP,
|
||||
tracerCloser: tracerCloser,
|
||||
watcher: watcher,
|
||||
tcpEntryPoints: entryPoints,
|
||||
observabilityMgr: observabilityMgr,
|
||||
signals: make(chan os.Signal, 1),
|
||||
stopChan: make(chan bool, 1),
|
||||
routinesPool: routinesPool,
|
||||
udpEntryPoints: entryPointsUDP,
|
||||
}
|
||||
|
||||
srv.configureSignals()
|
||||
|
@ -105,13 +97,7 @@ func (s *Server) Close() {
|
|||
|
||||
close(s.stopChan)
|
||||
|
||||
s.chainBuilder.Close()
|
||||
|
||||
if s.tracerCloser != nil {
|
||||
if err := s.tracerCloser.Close(); err != nil {
|
||||
log.Error().Err(err).Msg("Could not close the tracer")
|
||||
}
|
||||
}
|
||||
s.observabilityMgr.Close()
|
||||
|
||||
cancel()
|
||||
}
|
||||
|
|
15
pkg/server/server_entrypoint_listenconfig_other.go
Normal file
15
pkg/server/server_entrypoint_listenconfig_other.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
//go:build !(linux || freebsd || openbsd || darwin)
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/traefik/traefik/v3/pkg/config/static"
|
||||
)
|
||||
|
||||
// newListenConfig creates a new net.ListenConfig for the given configuration of
|
||||
// the entry point.
|
||||
func newListenConfig(configuration *static.EntryPoint) (lc net.ListenConfig) {
|
||||
return
|
||||
}
|
44
pkg/server/server_entrypoint_listenconfig_other_test.go
Normal file
44
pkg/server/server_entrypoint_listenconfig_other_test.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
//go:build !(linux || freebsd || openbsd || darwin)
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/traefik/v3/pkg/config/static"
|
||||
)
|
||||
|
||||
func TestNewListenConfig(t *testing.T) {
|
||||
ep := static.EntryPoint{Address: ":0"}
|
||||
listenConfig := newListenConfig(&ep)
|
||||
require.Nil(t, listenConfig.Control)
|
||||
require.Zero(t, listenConfig.KeepAlive)
|
||||
|
||||
l1, err := listenConfig.Listen(context.Background(), "tcp", ep.Address)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, l1)
|
||||
defer l1.Close()
|
||||
|
||||
l2, err := listenConfig.Listen(context.Background(), "tcp", l1.Addr().String())
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "address already in use")
|
||||
require.Nil(t, l2)
|
||||
|
||||
ep = static.EntryPoint{Address: ":0", ReusePort: true}
|
||||
listenConfig = newListenConfig(&ep)
|
||||
require.Nil(t, listenConfig.Control)
|
||||
require.Zero(t, listenConfig.KeepAlive)
|
||||
|
||||
l3, err := listenConfig.Listen(context.Background(), "tcp", ep.Address)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, l3)
|
||||
defer l3.Close()
|
||||
|
||||
l4, err := listenConfig.Listen(context.Background(), "tcp", l3.Addr().String())
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "address already in use")
|
||||
require.Nil(t, l4)
|
||||
}
|
44
pkg/server/server_entrypoint_listenconfig_unix.go
Normal file
44
pkg/server/server_entrypoint_listenconfig_unix.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
//go:build linux || freebsd || openbsd || darwin
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"github.com/traefik/traefik/v3/pkg/config/static"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// newListenConfig creates a new net.ListenConfig for the given configuration of
|
||||
// the entry point.
|
||||
func newListenConfig(configuration *static.EntryPoint) (lc net.ListenConfig) {
|
||||
if configuration != nil && configuration.ReusePort {
|
||||
lc.Control = controlReusePort
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// controlReusePort is a net.ListenConfig.Control function that enables SO_REUSEPORT
|
||||
// on the socket.
|
||||
func controlReusePort(network, address string, c syscall.RawConn) error {
|
||||
var setSockOptErr error
|
||||
err := c.Control(func(fd uintptr) {
|
||||
// Note that net.ListenConfig enables unix.SO_REUSEADDR by default,
|
||||
// as seen in https://go.dev/src/net/sockopt_linux.go. Therefore, no
|
||||
// additional action is required to enable it here.
|
||||
|
||||
setSockOptErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unixSOREUSEPORT, 1)
|
||||
if setSockOptErr != nil {
|
||||
return
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("control: %w", err)
|
||||
}
|
||||
if setSockOptErr != nil {
|
||||
return fmt.Errorf("setsockopt: %w", setSockOptErr)
|
||||
}
|
||||
return nil
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue