mirror of
https://github.com/traefik/traefik.git
synced 2026-02-03 20:39:51 -05:00
Merge 6be505af7f into a4a91344ed
This commit is contained in:
commit
7e5f59bbbf
16 changed files with 862 additions and 68 deletions
|
|
@ -587,4 +587,55 @@ spec:
|
|||
disableSessionTickets: true
|
||||
```
|
||||
|
||||
### Encrypted Client Hello Keys
|
||||
|
||||
_Optional_
|
||||
|
||||
The `ECH Keys` option enables the server-side ECH feature. This option does not impact clients that do not support ECH.
|
||||
|
||||
The configuration file should be in PEM format and requires both a private key and an ECH configuration block.
|
||||
[Reference](https://www.ietf.org/archive/id/draft-farrell-tls-pemesni-09.html)
|
||||
|
||||
Below is an example of the configuration file:
|
||||
|
||||
```text
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VuBCIEICjd4yGRdsoP9gU7YT7My8DHx1Tjme8GYDXrOMCi8v1V
|
||||
-----END PRIVATE KEY-----
|
||||
-----BEGIN ECHCONFIG-----
|
||||
AD7+DQA65wAgACA8wVN2BtscOl3vQheUzHeIkVmKIiydUhDCliA4iyQRCwAEAAEA
|
||||
AQALZXhhbXBsZS5jb20AAA==
|
||||
-----END ECHCONFIG-----
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
# Dynamic configuration
|
||||
|
||||
tls:
|
||||
options:
|
||||
default:
|
||||
echKeys:
|
||||
- example.pem
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
# Dynamic configuration
|
||||
|
||||
[tls.options]
|
||||
[tls.options.default]
|
||||
echKeys = ["example.pem"]
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes"
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: TLSOption
|
||||
metadata:
|
||||
name: default
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
echKeys:
|
||||
- example.pem
|
||||
```
|
||||
|
||||
{% include-markdown "includes/traefik-for-business-applications.md" %}
|
||||
|
|
|
|||
|
|
@ -598,6 +598,7 @@
|
|||
sniStrict = true
|
||||
alpnProtocols = ["foobar", "foobar"]
|
||||
disableSessionTickets = true
|
||||
echKeys = ["foobar", "foobar"]
|
||||
preferServerCipherSuites = true
|
||||
[tls.options.Options0.clientAuth]
|
||||
caFiles = ["foobar", "foobar"]
|
||||
|
|
@ -610,6 +611,7 @@
|
|||
sniStrict = true
|
||||
alpnProtocols = ["foobar", "foobar"]
|
||||
disableSessionTickets = true
|
||||
echKeys = ["foobar", "foobar"]
|
||||
preferServerCipherSuites = true
|
||||
[tls.options.Options1.clientAuth]
|
||||
caFiles = ["foobar", "foobar"]
|
||||
|
|
|
|||
|
|
@ -673,6 +673,9 @@ tls:
|
|||
- foobar
|
||||
- foobar
|
||||
disableSessionTickets: true
|
||||
echKeys:
|
||||
- foobar
|
||||
- foobar
|
||||
preferServerCipherSuites: true
|
||||
Options1:
|
||||
minVersion: foobar
|
||||
|
|
@ -693,6 +696,9 @@ tls:
|
|||
- foobar
|
||||
- foobar
|
||||
disableSessionTickets: true
|
||||
echKeys:
|
||||
- foobar
|
||||
- foobar
|
||||
preferServerCipherSuites: true
|
||||
stores:
|
||||
Store0:
|
||||
|
|
|
|||
|
|
@ -199,7 +199,6 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| <a id="opt-traefikhttproutersRouter0middlewares1" href="#opt-traefikhttproutersRouter0middlewares1" title="#opt-traefikhttproutersRouter0middlewares1">`traefik/http/routers/Router0/middlewares/1`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttproutersRouter0observabilityaccessLogs" href="#opt-traefikhttproutersRouter0observabilityaccessLogs" title="#opt-traefikhttproutersRouter0observabilityaccessLogs">`traefik/http/routers/Router0/observability/accessLogs`</a> | `true` |
|
||||
| <a id="opt-traefikhttproutersRouter0observabilitymetrics" href="#opt-traefikhttproutersRouter0observabilitymetrics" title="#opt-traefikhttproutersRouter0observabilitymetrics">`traefik/http/routers/Router0/observability/metrics`</a> | `true` |
|
||||
| <a id="opt-traefikhttproutersRouter0observabilitytraceVerbosity" href="#opt-traefikhttproutersRouter0observabilitytraceVerbosity" title="#opt-traefikhttproutersRouter0observabilitytraceVerbosity">`traefik/http/routers/Router0/observability/traceVerbosity`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttproutersRouter0observabilitytracing" href="#opt-traefikhttproutersRouter0observabilitytracing" title="#opt-traefikhttproutersRouter0observabilitytracing">`traefik/http/routers/Router0/observability/tracing`</a> | `true` |
|
||||
| <a id="opt-traefikhttproutersRouter0priority" href="#opt-traefikhttproutersRouter0priority" title="#opt-traefikhttproutersRouter0priority">`traefik/http/routers/Router0/priority`</a> | `42` |
|
||||
| <a id="opt-traefikhttproutersRouter0rule" href="#opt-traefikhttproutersRouter0rule" title="#opt-traefikhttproutersRouter0rule">`traefik/http/routers/Router0/rule`</a> | `foobar` |
|
||||
|
|
@ -219,7 +218,6 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| <a id="opt-traefikhttproutersRouter1middlewares1" href="#opt-traefikhttproutersRouter1middlewares1" title="#opt-traefikhttproutersRouter1middlewares1">`traefik/http/routers/Router1/middlewares/1`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttproutersRouter1observabilityaccessLogs" href="#opt-traefikhttproutersRouter1observabilityaccessLogs" title="#opt-traefikhttproutersRouter1observabilityaccessLogs">`traefik/http/routers/Router1/observability/accessLogs`</a> | `true` |
|
||||
| <a id="opt-traefikhttproutersRouter1observabilitymetrics" href="#opt-traefikhttproutersRouter1observabilitymetrics" title="#opt-traefikhttproutersRouter1observabilitymetrics">`traefik/http/routers/Router1/observability/metrics`</a> | `true` |
|
||||
| <a id="opt-traefikhttproutersRouter1observabilitytraceVerbosity" href="#opt-traefikhttproutersRouter1observabilitytraceVerbosity" title="#opt-traefikhttproutersRouter1observabilitytraceVerbosity">`traefik/http/routers/Router1/observability/traceVerbosity`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttproutersRouter1observabilitytracing" href="#opt-traefikhttproutersRouter1observabilitytracing" title="#opt-traefikhttproutersRouter1observabilitytracing">`traefik/http/routers/Router1/observability/tracing`</a> | `true` |
|
||||
| <a id="opt-traefikhttproutersRouter1priority" href="#opt-traefikhttproutersRouter1priority" title="#opt-traefikhttproutersRouter1priority">`traefik/http/routers/Router1/priority`</a> | `42` |
|
||||
| <a id="opt-traefikhttproutersRouter1rule" href="#opt-traefikhttproutersRouter1rule" title="#opt-traefikhttproutersRouter1rule">`traefik/http/routers/Router1/rule`</a> | `foobar` |
|
||||
|
|
@ -282,63 +280,56 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| <a id="opt-traefikhttpservicesService01failoverfallback" href="#opt-traefikhttpservicesService01failoverfallback" title="#opt-traefikhttpservicesService01failoverfallback">`traefik/http/services/Service01/failover/fallback`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService01failoverhealthCheck" href="#opt-traefikhttpservicesService01failoverhealthCheck" title="#opt-traefikhttpservicesService01failoverhealthCheck">`traefik/http/services/Service01/failover/healthCheck`</a> | `` |
|
||||
| <a id="opt-traefikhttpservicesService01failoverservice" href="#opt-traefikhttpservicesService01failoverservice" title="#opt-traefikhttpservicesService01failoverservice">`traefik/http/services/Service01/failover/service`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService02highestRandomWeighthealthCheck" href="#opt-traefikhttpservicesService02highestRandomWeighthealthCheck" title="#opt-traefikhttpservicesService02highestRandomWeighthealthCheck">`traefik/http/services/Service02/highestRandomWeight/healthCheck`</a> | `` |
|
||||
| <a id="opt-traefikhttpservicesService02highestRandomWeightservices0name" href="#opt-traefikhttpservicesService02highestRandomWeightservices0name" title="#opt-traefikhttpservicesService02highestRandomWeightservices0name">`traefik/http/services/Service02/highestRandomWeight/services/0/name`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService02highestRandomWeightservices0weight" href="#opt-traefikhttpservicesService02highestRandomWeightservices0weight" title="#opt-traefikhttpservicesService02highestRandomWeightservices0weight">`traefik/http/services/Service02/highestRandomWeight/services/0/weight`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService02highestRandomWeightservices1name" href="#opt-traefikhttpservicesService02highestRandomWeightservices1name" title="#opt-traefikhttpservicesService02highestRandomWeightservices1name">`traefik/http/services/Service02/highestRandomWeight/services/1/name`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService02highestRandomWeightservices1weight" href="#opt-traefikhttpservicesService02highestRandomWeightservices1weight" title="#opt-traefikhttpservicesService02highestRandomWeightservices1weight">`traefik/http/services/Service02/highestRandomWeight/services/1/weight`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerhealthCheckfollowRedirects" href="#opt-traefikhttpservicesService03loadBalancerhealthCheckfollowRedirects" title="#opt-traefikhttpservicesService03loadBalancerhealthCheckfollowRedirects">`traefik/http/services/Service03/loadBalancer/healthCheck/followRedirects`</a> | `true` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerhealthCheckheadersname0" href="#opt-traefikhttpservicesService03loadBalancerhealthCheckheadersname0" title="#opt-traefikhttpservicesService03loadBalancerhealthCheckheadersname0">`traefik/http/services/Service03/loadBalancer/healthCheck/headers/name0`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerhealthCheckheadersname1" href="#opt-traefikhttpservicesService03loadBalancerhealthCheckheadersname1" title="#opt-traefikhttpservicesService03loadBalancerhealthCheckheadersname1">`traefik/http/services/Service03/loadBalancer/healthCheck/headers/name1`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerhealthCheckhostname" href="#opt-traefikhttpservicesService03loadBalancerhealthCheckhostname" title="#opt-traefikhttpservicesService03loadBalancerhealthCheckhostname">`traefik/http/services/Service03/loadBalancer/healthCheck/hostname`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerhealthCheckinterval" href="#opt-traefikhttpservicesService03loadBalancerhealthCheckinterval" title="#opt-traefikhttpservicesService03loadBalancerhealthCheckinterval">`traefik/http/services/Service03/loadBalancer/healthCheck/interval`</a> | `42s` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerhealthCheckmethod" href="#opt-traefikhttpservicesService03loadBalancerhealthCheckmethod" title="#opt-traefikhttpservicesService03loadBalancerhealthCheckmethod">`traefik/http/services/Service03/loadBalancer/healthCheck/method`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerhealthCheckmode" href="#opt-traefikhttpservicesService03loadBalancerhealthCheckmode" title="#opt-traefikhttpservicesService03loadBalancerhealthCheckmode">`traefik/http/services/Service03/loadBalancer/healthCheck/mode`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerhealthCheckpath" href="#opt-traefikhttpservicesService03loadBalancerhealthCheckpath" title="#opt-traefikhttpservicesService03loadBalancerhealthCheckpath">`traefik/http/services/Service03/loadBalancer/healthCheck/path`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerhealthCheckport" href="#opt-traefikhttpservicesService03loadBalancerhealthCheckport" title="#opt-traefikhttpservicesService03loadBalancerhealthCheckport">`traefik/http/services/Service03/loadBalancer/healthCheck/port`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerhealthCheckscheme" href="#opt-traefikhttpservicesService03loadBalancerhealthCheckscheme" title="#opt-traefikhttpservicesService03loadBalancerhealthCheckscheme">`traefik/http/services/Service03/loadBalancer/healthCheck/scheme`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerhealthCheckstatus" href="#opt-traefikhttpservicesService03loadBalancerhealthCheckstatus" title="#opt-traefikhttpservicesService03loadBalancerhealthCheckstatus">`traefik/http/services/Service03/loadBalancer/healthCheck/status`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerhealthChecktimeout" href="#opt-traefikhttpservicesService03loadBalancerhealthChecktimeout" title="#opt-traefikhttpservicesService03loadBalancerhealthChecktimeout">`traefik/http/services/Service03/loadBalancer/healthCheck/timeout`</a> | `42s` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerhealthCheckunhealthyInterval" href="#opt-traefikhttpservicesService03loadBalancerhealthCheckunhealthyInterval" title="#opt-traefikhttpservicesService03loadBalancerhealthCheckunhealthyInterval">`traefik/http/services/Service03/loadBalancer/healthCheck/unhealthyInterval`</a> | `42s` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerpassHostHeader" href="#opt-traefikhttpservicesService03loadBalancerpassHostHeader" title="#opt-traefikhttpservicesService03loadBalancerpassHostHeader">`traefik/http/services/Service03/loadBalancer/passHostHeader`</a> | `true` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerpassiveHealthCheckfailureWindow" href="#opt-traefikhttpservicesService03loadBalancerpassiveHealthCheckfailureWindow" title="#opt-traefikhttpservicesService03loadBalancerpassiveHealthCheckfailureWindow">`traefik/http/services/Service03/loadBalancer/passiveHealthCheck/failureWindow`</a> | `42s` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerpassiveHealthCheckmaxFailedAttempts" href="#opt-traefikhttpservicesService03loadBalancerpassiveHealthCheckmaxFailedAttempts" title="#opt-traefikhttpservicesService03loadBalancerpassiveHealthCheckmaxFailedAttempts">`traefik/http/services/Service03/loadBalancer/passiveHealthCheck/maxFailedAttempts`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerresponseForwardingflushInterval" href="#opt-traefikhttpservicesService03loadBalancerresponseForwardingflushInterval" title="#opt-traefikhttpservicesService03loadBalancerresponseForwardingflushInterval">`traefik/http/services/Service03/loadBalancer/responseForwarding/flushInterval`</a> | `42s` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerservers0preservePath" href="#opt-traefikhttpservicesService03loadBalancerservers0preservePath" title="#opt-traefikhttpservicesService03loadBalancerservers0preservePath">`traefik/http/services/Service03/loadBalancer/servers/0/preservePath`</a> | `true` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerservers0url" href="#opt-traefikhttpservicesService03loadBalancerservers0url" title="#opt-traefikhttpservicesService03loadBalancerservers0url">`traefik/http/services/Service03/loadBalancer/servers/0/url`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerservers0weight" href="#opt-traefikhttpservicesService03loadBalancerservers0weight" title="#opt-traefikhttpservicesService03loadBalancerservers0weight">`traefik/http/services/Service03/loadBalancer/servers/0/weight`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerservers1preservePath" href="#opt-traefikhttpservicesService03loadBalancerservers1preservePath" title="#opt-traefikhttpservicesService03loadBalancerservers1preservePath">`traefik/http/services/Service03/loadBalancer/servers/1/preservePath`</a> | `true` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerservers1url" href="#opt-traefikhttpservicesService03loadBalancerservers1url" title="#opt-traefikhttpservicesService03loadBalancerservers1url">`traefik/http/services/Service03/loadBalancer/servers/1/url`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerservers1weight" href="#opt-traefikhttpservicesService03loadBalancerservers1weight" title="#opt-traefikhttpservicesService03loadBalancerservers1weight">`traefik/http/services/Service03/loadBalancer/servers/1/weight`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerserversTransport" href="#opt-traefikhttpservicesService03loadBalancerserversTransport" title="#opt-traefikhttpservicesService03loadBalancerserversTransport">`traefik/http/services/Service03/loadBalancer/serversTransport`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerstickycookiedomain" href="#opt-traefikhttpservicesService03loadBalancerstickycookiedomain" title="#opt-traefikhttpservicesService03loadBalancerstickycookiedomain">`traefik/http/services/Service03/loadBalancer/sticky/cookie/domain`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerstickycookiehttpOnly" href="#opt-traefikhttpservicesService03loadBalancerstickycookiehttpOnly" title="#opt-traefikhttpservicesService03loadBalancerstickycookiehttpOnly">`traefik/http/services/Service03/loadBalancer/sticky/cookie/httpOnly`</a> | `true` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerstickycookiemaxAge" href="#opt-traefikhttpservicesService03loadBalancerstickycookiemaxAge" title="#opt-traefikhttpservicesService03loadBalancerstickycookiemaxAge">`traefik/http/services/Service03/loadBalancer/sticky/cookie/maxAge`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerstickycookiename" href="#opt-traefikhttpservicesService03loadBalancerstickycookiename" title="#opt-traefikhttpservicesService03loadBalancerstickycookiename">`traefik/http/services/Service03/loadBalancer/sticky/cookie/name`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerstickycookiepath" href="#opt-traefikhttpservicesService03loadBalancerstickycookiepath" title="#opt-traefikhttpservicesService03loadBalancerstickycookiepath">`traefik/http/services/Service03/loadBalancer/sticky/cookie/path`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerstickycookiesameSite" href="#opt-traefikhttpservicesService03loadBalancerstickycookiesameSite" title="#opt-traefikhttpservicesService03loadBalancerstickycookiesameSite">`traefik/http/services/Service03/loadBalancer/sticky/cookie/sameSite`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerstickycookiesecure" href="#opt-traefikhttpservicesService03loadBalancerstickycookiesecure" title="#opt-traefikhttpservicesService03loadBalancerstickycookiesecure">`traefik/http/services/Service03/loadBalancer/sticky/cookie/secure`</a> | `true` |
|
||||
| <a id="opt-traefikhttpservicesService03loadBalancerstrategy" href="#opt-traefikhttpservicesService03loadBalancerstrategy" title="#opt-traefikhttpservicesService03loadBalancerstrategy">`traefik/http/services/Service03/loadBalancer/strategy`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService04mirroringhealthCheck" href="#opt-traefikhttpservicesService04mirroringhealthCheck" title="#opt-traefikhttpservicesService04mirroringhealthCheck">`traefik/http/services/Service04/mirroring/healthCheck`</a> | `` |
|
||||
| <a id="opt-traefikhttpservicesService04mirroringmaxBodySize" href="#opt-traefikhttpservicesService04mirroringmaxBodySize" title="#opt-traefikhttpservicesService04mirroringmaxBodySize">`traefik/http/services/Service04/mirroring/maxBodySize`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService04mirroringmirrorBody" href="#opt-traefikhttpservicesService04mirroringmirrorBody" title="#opt-traefikhttpservicesService04mirroringmirrorBody">`traefik/http/services/Service04/mirroring/mirrorBody`</a> | `true` |
|
||||
| <a id="opt-traefikhttpservicesService04mirroringmirrors0name" href="#opt-traefikhttpservicesService04mirroringmirrors0name" title="#opt-traefikhttpservicesService04mirroringmirrors0name">`traefik/http/services/Service04/mirroring/mirrors/0/name`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService04mirroringmirrors0percent" href="#opt-traefikhttpservicesService04mirroringmirrors0percent" title="#opt-traefikhttpservicesService04mirroringmirrors0percent">`traefik/http/services/Service04/mirroring/mirrors/0/percent`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService04mirroringmirrors1name" href="#opt-traefikhttpservicesService04mirroringmirrors1name" title="#opt-traefikhttpservicesService04mirroringmirrors1name">`traefik/http/services/Service04/mirroring/mirrors/1/name`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService04mirroringmirrors1percent" href="#opt-traefikhttpservicesService04mirroringmirrors1percent" title="#opt-traefikhttpservicesService04mirroringmirrors1percent">`traefik/http/services/Service04/mirroring/mirrors/1/percent`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService04mirroringservice" href="#opt-traefikhttpservicesService04mirroringservice" title="#opt-traefikhttpservicesService04mirroringservice">`traefik/http/services/Service04/mirroring/service`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService05weightedhealthCheck" href="#opt-traefikhttpservicesService05weightedhealthCheck" title="#opt-traefikhttpservicesService05weightedhealthCheck">`traefik/http/services/Service05/weighted/healthCheck`</a> | `` |
|
||||
| <a id="opt-traefikhttpservicesService05weightedservices0name" href="#opt-traefikhttpservicesService05weightedservices0name" title="#opt-traefikhttpservicesService05weightedservices0name">`traefik/http/services/Service05/weighted/services/0/name`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService05weightedservices0weight" href="#opt-traefikhttpservicesService05weightedservices0weight" title="#opt-traefikhttpservicesService05weightedservices0weight">`traefik/http/services/Service05/weighted/services/0/weight`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService05weightedservices1name" href="#opt-traefikhttpservicesService05weightedservices1name" title="#opt-traefikhttpservicesService05weightedservices1name">`traefik/http/services/Service05/weighted/services/1/name`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService05weightedservices1weight" href="#opt-traefikhttpservicesService05weightedservices1weight" title="#opt-traefikhttpservicesService05weightedservices1weight">`traefik/http/services/Service05/weighted/services/1/weight`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService05weightedstickycookiedomain" href="#opt-traefikhttpservicesService05weightedstickycookiedomain" title="#opt-traefikhttpservicesService05weightedstickycookiedomain">`traefik/http/services/Service05/weighted/sticky/cookie/domain`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService05weightedstickycookiehttpOnly" href="#opt-traefikhttpservicesService05weightedstickycookiehttpOnly" title="#opt-traefikhttpservicesService05weightedstickycookiehttpOnly">`traefik/http/services/Service05/weighted/sticky/cookie/httpOnly`</a> | `true` |
|
||||
| <a id="opt-traefikhttpservicesService05weightedstickycookiemaxAge" href="#opt-traefikhttpservicesService05weightedstickycookiemaxAge" title="#opt-traefikhttpservicesService05weightedstickycookiemaxAge">`traefik/http/services/Service05/weighted/sticky/cookie/maxAge`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService05weightedstickycookiename" href="#opt-traefikhttpservicesService05weightedstickycookiename" title="#opt-traefikhttpservicesService05weightedstickycookiename">`traefik/http/services/Service05/weighted/sticky/cookie/name`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService05weightedstickycookiepath" href="#opt-traefikhttpservicesService05weightedstickycookiepath" title="#opt-traefikhttpservicesService05weightedstickycookiepath">`traefik/http/services/Service05/weighted/sticky/cookie/path`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService05weightedstickycookiesameSite" href="#opt-traefikhttpservicesService05weightedstickycookiesameSite" title="#opt-traefikhttpservicesService05weightedstickycookiesameSite">`traefik/http/services/Service05/weighted/sticky/cookie/sameSite`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService05weightedstickycookiesecure" href="#opt-traefikhttpservicesService05weightedstickycookiesecure" title="#opt-traefikhttpservicesService05weightedstickycookiesecure">`traefik/http/services/Service05/weighted/sticky/cookie/secure`</a> | `true` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerhealthCheckfollowRedirects" href="#opt-traefikhttpservicesService02loadBalancerhealthCheckfollowRedirects" title="#opt-traefikhttpservicesService02loadBalancerhealthCheckfollowRedirects">`traefik/http/services/Service02/loadBalancer/healthCheck/followRedirects`</a> | `true` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerhealthCheckheadersname0" href="#opt-traefikhttpservicesService02loadBalancerhealthCheckheadersname0" title="#opt-traefikhttpservicesService02loadBalancerhealthCheckheadersname0">`traefik/http/services/Service02/loadBalancer/healthCheck/headers/name0`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerhealthCheckheadersname1" href="#opt-traefikhttpservicesService02loadBalancerhealthCheckheadersname1" title="#opt-traefikhttpservicesService02loadBalancerhealthCheckheadersname1">`traefik/http/services/Service02/loadBalancer/healthCheck/headers/name1`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerhealthCheckhostname" href="#opt-traefikhttpservicesService02loadBalancerhealthCheckhostname" title="#opt-traefikhttpservicesService02loadBalancerhealthCheckhostname">`traefik/http/services/Service02/loadBalancer/healthCheck/hostname`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerhealthCheckinterval" href="#opt-traefikhttpservicesService02loadBalancerhealthCheckinterval" title="#opt-traefikhttpservicesService02loadBalancerhealthCheckinterval">`traefik/http/services/Service02/loadBalancer/healthCheck/interval`</a> | `42s` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerhealthCheckmethod" href="#opt-traefikhttpservicesService02loadBalancerhealthCheckmethod" title="#opt-traefikhttpservicesService02loadBalancerhealthCheckmethod">`traefik/http/services/Service02/loadBalancer/healthCheck/method`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerhealthCheckmode" href="#opt-traefikhttpservicesService02loadBalancerhealthCheckmode" title="#opt-traefikhttpservicesService02loadBalancerhealthCheckmode">`traefik/http/services/Service02/loadBalancer/healthCheck/mode`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerhealthCheckpath" href="#opt-traefikhttpservicesService02loadBalancerhealthCheckpath" title="#opt-traefikhttpservicesService02loadBalancerhealthCheckpath">`traefik/http/services/Service02/loadBalancer/healthCheck/path`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerhealthCheckport" href="#opt-traefikhttpservicesService02loadBalancerhealthCheckport" title="#opt-traefikhttpservicesService02loadBalancerhealthCheckport">`traefik/http/services/Service02/loadBalancer/healthCheck/port`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerhealthCheckscheme" href="#opt-traefikhttpservicesService02loadBalancerhealthCheckscheme" title="#opt-traefikhttpservicesService02loadBalancerhealthCheckscheme">`traefik/http/services/Service02/loadBalancer/healthCheck/scheme`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerhealthCheckstatus" href="#opt-traefikhttpservicesService02loadBalancerhealthCheckstatus" title="#opt-traefikhttpservicesService02loadBalancerhealthCheckstatus">`traefik/http/services/Service02/loadBalancer/healthCheck/status`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerhealthChecktimeout" href="#opt-traefikhttpservicesService02loadBalancerhealthChecktimeout" title="#opt-traefikhttpservicesService02loadBalancerhealthChecktimeout">`traefik/http/services/Service02/loadBalancer/healthCheck/timeout`</a> | `42s` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerhealthCheckunhealthyInterval" href="#opt-traefikhttpservicesService02loadBalancerhealthCheckunhealthyInterval" title="#opt-traefikhttpservicesService02loadBalancerhealthCheckunhealthyInterval">`traefik/http/services/Service02/loadBalancer/healthCheck/unhealthyInterval`</a> | `42s` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerpassHostHeader" href="#opt-traefikhttpservicesService02loadBalancerpassHostHeader" title="#opt-traefikhttpservicesService02loadBalancerpassHostHeader">`traefik/http/services/Service02/loadBalancer/passHostHeader`</a> | `true` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerresponseForwardingflushInterval" href="#opt-traefikhttpservicesService02loadBalancerresponseForwardingflushInterval" title="#opt-traefikhttpservicesService02loadBalancerresponseForwardingflushInterval">`traefik/http/services/Service02/loadBalancer/responseForwarding/flushInterval`</a> | `42s` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerservers0preservePath" href="#opt-traefikhttpservicesService02loadBalancerservers0preservePath" title="#opt-traefikhttpservicesService02loadBalancerservers0preservePath">`traefik/http/services/Service02/loadBalancer/servers/0/preservePath`</a> | `true` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerservers0url" href="#opt-traefikhttpservicesService02loadBalancerservers0url" title="#opt-traefikhttpservicesService02loadBalancerservers0url">`traefik/http/services/Service02/loadBalancer/servers/0/url`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerservers0weight" href="#opt-traefikhttpservicesService02loadBalancerservers0weight" title="#opt-traefikhttpservicesService02loadBalancerservers0weight">`traefik/http/services/Service02/loadBalancer/servers/0/weight`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerservers1preservePath" href="#opt-traefikhttpservicesService02loadBalancerservers1preservePath" title="#opt-traefikhttpservicesService02loadBalancerservers1preservePath">`traefik/http/services/Service02/loadBalancer/servers/1/preservePath`</a> | `true` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerservers1url" href="#opt-traefikhttpservicesService02loadBalancerservers1url" title="#opt-traefikhttpservicesService02loadBalancerservers1url">`traefik/http/services/Service02/loadBalancer/servers/1/url`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerservers1weight" href="#opt-traefikhttpservicesService02loadBalancerservers1weight" title="#opt-traefikhttpservicesService02loadBalancerservers1weight">`traefik/http/services/Service02/loadBalancer/servers/1/weight`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerserversTransport" href="#opt-traefikhttpservicesService02loadBalancerserversTransport" title="#opt-traefikhttpservicesService02loadBalancerserversTransport">`traefik/http/services/Service02/loadBalancer/serversTransport`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerstickycookiedomain" href="#opt-traefikhttpservicesService02loadBalancerstickycookiedomain" title="#opt-traefikhttpservicesService02loadBalancerstickycookiedomain">`traefik/http/services/Service02/loadBalancer/sticky/cookie/domain`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerstickycookiehttpOnly" href="#opt-traefikhttpservicesService02loadBalancerstickycookiehttpOnly" title="#opt-traefikhttpservicesService02loadBalancerstickycookiehttpOnly">`traefik/http/services/Service02/loadBalancer/sticky/cookie/httpOnly`</a> | `true` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerstickycookiemaxAge" href="#opt-traefikhttpservicesService02loadBalancerstickycookiemaxAge" title="#opt-traefikhttpservicesService02loadBalancerstickycookiemaxAge">`traefik/http/services/Service02/loadBalancer/sticky/cookie/maxAge`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerstickycookiename" href="#opt-traefikhttpservicesService02loadBalancerstickycookiename" title="#opt-traefikhttpservicesService02loadBalancerstickycookiename">`traefik/http/services/Service02/loadBalancer/sticky/cookie/name`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerstickycookiepath" href="#opt-traefikhttpservicesService02loadBalancerstickycookiepath" title="#opt-traefikhttpservicesService02loadBalancerstickycookiepath">`traefik/http/services/Service02/loadBalancer/sticky/cookie/path`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerstickycookiesameSite" href="#opt-traefikhttpservicesService02loadBalancerstickycookiesameSite" title="#opt-traefikhttpservicesService02loadBalancerstickycookiesameSite">`traefik/http/services/Service02/loadBalancer/sticky/cookie/sameSite`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerstickycookiesecure" href="#opt-traefikhttpservicesService02loadBalancerstickycookiesecure" title="#opt-traefikhttpservicesService02loadBalancerstickycookiesecure">`traefik/http/services/Service02/loadBalancer/sticky/cookie/secure`</a> | `true` |
|
||||
| <a id="opt-traefikhttpservicesService02loadBalancerstrategy" href="#opt-traefikhttpservicesService02loadBalancerstrategy" title="#opt-traefikhttpservicesService02loadBalancerstrategy">`traefik/http/services/Service02/loadBalancer/strategy`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService03mirroringhealthCheck" href="#opt-traefikhttpservicesService03mirroringhealthCheck" title="#opt-traefikhttpservicesService03mirroringhealthCheck">`traefik/http/services/Service03/mirroring/healthCheck`</a> | `` |
|
||||
| <a id="opt-traefikhttpservicesService03mirroringmaxBodySize" href="#opt-traefikhttpservicesService03mirroringmaxBodySize" title="#opt-traefikhttpservicesService03mirroringmaxBodySize">`traefik/http/services/Service03/mirroring/maxBodySize`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService03mirroringmirrorBody" href="#opt-traefikhttpservicesService03mirroringmirrorBody" title="#opt-traefikhttpservicesService03mirroringmirrorBody">`traefik/http/services/Service03/mirroring/mirrorBody`</a> | `true` |
|
||||
| <a id="opt-traefikhttpservicesService03mirroringmirrors0name" href="#opt-traefikhttpservicesService03mirroringmirrors0name" title="#opt-traefikhttpservicesService03mirroringmirrors0name">`traefik/http/services/Service03/mirroring/mirrors/0/name`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService03mirroringmirrors0percent" href="#opt-traefikhttpservicesService03mirroringmirrors0percent" title="#opt-traefikhttpservicesService03mirroringmirrors0percent">`traefik/http/services/Service03/mirroring/mirrors/0/percent`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService03mirroringmirrors1name" href="#opt-traefikhttpservicesService03mirroringmirrors1name" title="#opt-traefikhttpservicesService03mirroringmirrors1name">`traefik/http/services/Service03/mirroring/mirrors/1/name`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService03mirroringmirrors1percent" href="#opt-traefikhttpservicesService03mirroringmirrors1percent" title="#opt-traefikhttpservicesService03mirroringmirrors1percent">`traefik/http/services/Service03/mirroring/mirrors/1/percent`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService03mirroringservice" href="#opt-traefikhttpservicesService03mirroringservice" title="#opt-traefikhttpservicesService03mirroringservice">`traefik/http/services/Service03/mirroring/service`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService04weightedhealthCheck" href="#opt-traefikhttpservicesService04weightedhealthCheck" title="#opt-traefikhttpservicesService04weightedhealthCheck">`traefik/http/services/Service04/weighted/healthCheck`</a> | `` |
|
||||
| <a id="opt-traefikhttpservicesService04weightedservices0name" href="#opt-traefikhttpservicesService04weightedservices0name" title="#opt-traefikhttpservicesService04weightedservices0name">`traefik/http/services/Service04/weighted/services/0/name`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService04weightedservices0weight" href="#opt-traefikhttpservicesService04weightedservices0weight" title="#opt-traefikhttpservicesService04weightedservices0weight">`traefik/http/services/Service04/weighted/services/0/weight`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService04weightedservices1name" href="#opt-traefikhttpservicesService04weightedservices1name" title="#opt-traefikhttpservicesService04weightedservices1name">`traefik/http/services/Service04/weighted/services/1/name`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService04weightedservices1weight" href="#opt-traefikhttpservicesService04weightedservices1weight" title="#opt-traefikhttpservicesService04weightedservices1weight">`traefik/http/services/Service04/weighted/services/1/weight`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService04weightedstickycookiedomain" href="#opt-traefikhttpservicesService04weightedstickycookiedomain" title="#opt-traefikhttpservicesService04weightedstickycookiedomain">`traefik/http/services/Service04/weighted/sticky/cookie/domain`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService04weightedstickycookiehttpOnly" href="#opt-traefikhttpservicesService04weightedstickycookiehttpOnly" title="#opt-traefikhttpservicesService04weightedstickycookiehttpOnly">`traefik/http/services/Service04/weighted/sticky/cookie/httpOnly`</a> | `true` |
|
||||
| <a id="opt-traefikhttpservicesService04weightedstickycookiemaxAge" href="#opt-traefikhttpservicesService04weightedstickycookiemaxAge" title="#opt-traefikhttpservicesService04weightedstickycookiemaxAge">`traefik/http/services/Service04/weighted/sticky/cookie/maxAge`</a> | `42` |
|
||||
| <a id="opt-traefikhttpservicesService04weightedstickycookiename" href="#opt-traefikhttpservicesService04weightedstickycookiename" title="#opt-traefikhttpservicesService04weightedstickycookiename">`traefik/http/services/Service04/weighted/sticky/cookie/name`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService04weightedstickycookiepath" href="#opt-traefikhttpservicesService04weightedstickycookiepath" title="#opt-traefikhttpservicesService04weightedstickycookiepath">`traefik/http/services/Service04/weighted/sticky/cookie/path`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService04weightedstickycookiesameSite" href="#opt-traefikhttpservicesService04weightedstickycookiesameSite" title="#opt-traefikhttpservicesService04weightedstickycookiesameSite">`traefik/http/services/Service04/weighted/sticky/cookie/sameSite`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpservicesService04weightedstickycookiesecure" href="#opt-traefikhttpservicesService04weightedstickycookiesecure" title="#opt-traefikhttpservicesService04weightedstickycookiesecure">`traefik/http/services/Service04/weighted/sticky/cookie/secure`</a> | `true` |
|
||||
| <a id="opt-traefiktcpmiddlewaresTCPMiddleware01ipAllowListsourceRange0" href="#opt-traefiktcpmiddlewaresTCPMiddleware01ipAllowListsourceRange0" title="#opt-traefiktcpmiddlewaresTCPMiddleware01ipAllowListsourceRange0">`traefik/tcp/middlewares/TCPMiddleware01/ipAllowList/sourceRange/0`</a> | `foobar` |
|
||||
| <a id="opt-traefiktcpmiddlewaresTCPMiddleware01ipAllowListsourceRange1" href="#opt-traefiktcpmiddlewaresTCPMiddleware01ipAllowListsourceRange1" title="#opt-traefiktcpmiddlewaresTCPMiddleware01ipAllowListsourceRange1">`traefik/tcp/middlewares/TCPMiddleware01/ipAllowList/sourceRange/1`</a> | `foobar` |
|
||||
| <a id="opt-traefiktcpmiddlewaresTCPMiddleware02ipWhiteListsourceRange0" href="#opt-traefiktcpmiddlewaresTCPMiddleware02ipWhiteListsourceRange0" title="#opt-traefiktcpmiddlewaresTCPMiddleware02ipWhiteListsourceRange0">`traefik/tcp/middlewares/TCPMiddleware02/ipWhiteList/sourceRange/0`</a> | `foobar` |
|
||||
|
|
@ -437,6 +428,8 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| <a id="opt-traefiktlsoptionsOptions0curvePreferences0" href="#opt-traefiktlsoptionsOptions0curvePreferences0" title="#opt-traefiktlsoptionsOptions0curvePreferences0">`traefik/tls/options/Options0/curvePreferences/0`</a> | `foobar` |
|
||||
| <a id="opt-traefiktlsoptionsOptions0curvePreferences1" href="#opt-traefiktlsoptionsOptions0curvePreferences1" title="#opt-traefiktlsoptionsOptions0curvePreferences1">`traefik/tls/options/Options0/curvePreferences/1`</a> | `foobar` |
|
||||
| <a id="opt-traefiktlsoptionsOptions0disableSessionTickets" href="#opt-traefiktlsoptionsOptions0disableSessionTickets" title="#opt-traefiktlsoptionsOptions0disableSessionTickets">`traefik/tls/options/Options0/disableSessionTickets`</a> | `true` |
|
||||
| <a id="opt-traefiktlsoptionsOptions0echKeys0" href="#opt-traefiktlsoptionsOptions0echKeys0" title="#opt-traefiktlsoptionsOptions0echKeys0">`traefik/tls/options/Options0/echKeys/0`</a> | `foobar` |
|
||||
| <a id="opt-traefiktlsoptionsOptions0echKeys1" href="#opt-traefiktlsoptionsOptions0echKeys1" title="#opt-traefiktlsoptionsOptions0echKeys1">`traefik/tls/options/Options0/echKeys/1`</a> | `foobar` |
|
||||
| <a id="opt-traefiktlsoptionsOptions0maxVersion" href="#opt-traefiktlsoptionsOptions0maxVersion" title="#opt-traefiktlsoptionsOptions0maxVersion">`traefik/tls/options/Options0/maxVersion`</a> | `foobar` |
|
||||
| <a id="opt-traefiktlsoptionsOptions0minVersion" href="#opt-traefiktlsoptionsOptions0minVersion" title="#opt-traefiktlsoptionsOptions0minVersion">`traefik/tls/options/Options0/minVersion`</a> | `foobar` |
|
||||
| <a id="opt-traefiktlsoptionsOptions0preferServerCipherSuites" href="#opt-traefiktlsoptionsOptions0preferServerCipherSuites" title="#opt-traefiktlsoptionsOptions0preferServerCipherSuites">`traefik/tls/options/Options0/preferServerCipherSuites`</a> | `true` |
|
||||
|
|
@ -451,6 +444,8 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| <a id="opt-traefiktlsoptionsOptions1curvePreferences0" href="#opt-traefiktlsoptionsOptions1curvePreferences0" title="#opt-traefiktlsoptionsOptions1curvePreferences0">`traefik/tls/options/Options1/curvePreferences/0`</a> | `foobar` |
|
||||
| <a id="opt-traefiktlsoptionsOptions1curvePreferences1" href="#opt-traefiktlsoptionsOptions1curvePreferences1" title="#opt-traefiktlsoptionsOptions1curvePreferences1">`traefik/tls/options/Options1/curvePreferences/1`</a> | `foobar` |
|
||||
| <a id="opt-traefiktlsoptionsOptions1disableSessionTickets" href="#opt-traefiktlsoptionsOptions1disableSessionTickets" title="#opt-traefiktlsoptionsOptions1disableSessionTickets">`traefik/tls/options/Options1/disableSessionTickets`</a> | `true` |
|
||||
| <a id="opt-traefiktlsoptionsOptions1echKeys0" href="#opt-traefiktlsoptionsOptions1echKeys0" title="#opt-traefiktlsoptionsOptions1echKeys0">`traefik/tls/options/Options1/echKeys/0`</a> | `foobar` |
|
||||
| <a id="opt-traefiktlsoptionsOptions1echKeys1" href="#opt-traefiktlsoptionsOptions1echKeys1" title="#opt-traefiktlsoptionsOptions1echKeys1">`traefik/tls/options/Options1/echKeys/1`</a> | `foobar` |
|
||||
| <a id="opt-traefiktlsoptionsOptions1maxVersion" href="#opt-traefiktlsoptionsOptions1maxVersion" title="#opt-traefiktlsoptionsOptions1maxVersion">`traefik/tls/options/Options1/maxVersion`</a> | `foobar` |
|
||||
| <a id="opt-traefiktlsoptionsOptions1minVersion" href="#opt-traefiktlsoptionsOptions1minVersion" title="#opt-traefiktlsoptionsOptions1minVersion">`traefik/tls/options/Options1/minVersion`</a> | `foobar` |
|
||||
| <a id="opt-traefiktlsoptionsOptions1preferServerCipherSuites" href="#opt-traefiktlsoptionsOptions1preferServerCipherSuites" title="#opt-traefiktlsoptionsOptions1preferServerCipherSuites">`traefik/tls/options/Options1/preferServerCipherSuites`</a> | `true` |
|
||||
|
|
|
|||
|
|
@ -620,6 +620,7 @@
|
|||
sniStrict = true
|
||||
alpnProtocols = ["foobar", "foobar"]
|
||||
disableSessionTickets = true
|
||||
echKeys = ["foobar", "foobar"]
|
||||
preferServerCipherSuites = true
|
||||
[tls.options.Options0.clientAuth]
|
||||
caFiles = ["foobar", "foobar"]
|
||||
|
|
@ -632,6 +633,7 @@
|
|||
sniStrict = true
|
||||
alpnProtocols = ["foobar", "foobar"]
|
||||
disableSessionTickets = true
|
||||
echKeys = ["foobar", "foobar"]
|
||||
preferServerCipherSuites = true
|
||||
[tls.options.Options1.clientAuth]
|
||||
caFiles = ["foobar", "foobar"]
|
||||
|
|
|
|||
|
|
@ -701,6 +701,9 @@ tls:
|
|||
- foobar
|
||||
- foobar
|
||||
disableSessionTickets: true
|
||||
echKeys:
|
||||
- foobar
|
||||
- foobar
|
||||
preferServerCipherSuites: true
|
||||
Options1:
|
||||
minVersion: foobar
|
||||
|
|
@ -721,6 +724,9 @@ tls:
|
|||
- foobar
|
||||
- foobar
|
||||
disableSessionTickets: true
|
||||
echKeys:
|
||||
- foobar
|
||||
- foobar
|
||||
preferServerCipherSuites: true
|
||||
stores:
|
||||
Store0:
|
||||
|
|
|
|||
3
go.mod
3
go.mod
|
|
@ -16,6 +16,7 @@ require (
|
|||
github.com/aws/aws-sdk-go-v2/service/ssm v1.56.13
|
||||
github.com/aws/smithy-go v1.24.0
|
||||
github.com/cenkalti/backoff/v4 v4.3.0
|
||||
github.com/cloudflare/circl v1.6.2
|
||||
github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd // No tag on the repo.
|
||||
github.com/coreos/go-systemd/v22 v22.5.0
|
||||
github.com/docker/cli v28.3.3+incompatible
|
||||
|
|
@ -53,6 +54,7 @@ require (
|
|||
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // No tag on the repo.
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pires/go-proxyproto v0.8.1
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // No tag on the repo.
|
||||
github.com/prometheus/client_golang v1.23.0
|
||||
github.com/prometheus/client_model v0.6.2
|
||||
|
|
@ -323,7 +325,6 @@ require (
|
|||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/peterhellberg/link v1.2.0 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/pquerna/otp v1.5.0 // indirect
|
||||
github.com/prometheus/common v0.65.0 // indirect
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -294,6 +294,8 @@ github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5P
|
|||
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
|
||||
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.6.2 h1:hL7VBpHHKzrV5WTfHCaBsgx/HGbBYlgrwvNXEVDYYsQ=
|
||||
github.com/cloudflare/circl v1.6.2/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
|
|
|
|||
46
internal/ech/cmd/main.go
Normal file
46
internal/ech/cmd/main.go
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
// Command ech provides utilities for generating ECH (Encrypted Client Hello) keys.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// go run ./internal/ech/cmd generate example.com,example.org
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/traefik/traefik/v3/internal/ech"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 3 {
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
command := os.Args[1]
|
||||
switch command {
|
||||
case "generate":
|
||||
names := strings.Split(os.Args[2], ",")
|
||||
if err := ech.GenerateMultiple(os.Stdout, names); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown command: %s\n", command)
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func printUsage() {
|
||||
fmt.Fprintln(os.Stderr, "Usage: go run ./internal/ech/cmd <command> [arguments]")
|
||||
fmt.Fprintln(os.Stderr, "")
|
||||
fmt.Fprintln(os.Stderr, "Commands:")
|
||||
fmt.Fprintln(os.Stderr, " generate <sni,sni,...> Generate ECH keys for the given SNI names (comma-separated)")
|
||||
fmt.Fprintln(os.Stderr, "")
|
||||
fmt.Fprintln(os.Stderr, "Examples:")
|
||||
fmt.Fprintln(os.Stderr, " go run ./internal/ech/cmd generate example.com")
|
||||
fmt.Fprintln(os.Stderr, " go run ./internal/ech/cmd generate example.com,example.org")
|
||||
}
|
||||
41
internal/ech/ech.go
Normal file
41
internal/ech/ech.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// Package ech provides utilities for generating and working with
|
||||
// Encrypted Client Hello (ECH) keys.
|
||||
package ech
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/traefik/traefik/v3/pkg/tls"
|
||||
)
|
||||
|
||||
// Generate creates a new ECH key for the given public name (SNI) and writes
|
||||
// the PEM-encoded result to the provided writer.
|
||||
func Generate(w io.Writer, publicName string) error {
|
||||
key, err := tls.NewECHKey(publicName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate ECH key for %s: %w", publicName, err)
|
||||
}
|
||||
|
||||
data, err := tls.MarshalECHKey(key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal ECH key for %s: %w", publicName, err)
|
||||
}
|
||||
|
||||
if _, err = w.Write(data); err != nil {
|
||||
return fmt.Errorf("failed to write ECH key for %s: %w", publicName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateMultiple creates ECH keys for multiple public names and writes
|
||||
// all PEM-encoded results to the provided writer.
|
||||
func GenerateMultiple(w io.Writer, publicNames []string) error {
|
||||
for _, name := range publicNames {
|
||||
if err := Generate(w, name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -267,6 +267,18 @@ func (p *Provider) loadFileConfig(ctx context.Context, filename string, parseTem
|
|||
}
|
||||
options.ClientAuth.CAFiles = caCerts
|
||||
|
||||
var echKeyContents []types.FileOrContent
|
||||
for _, echKey := range options.ECHKeys {
|
||||
content, err := echKey.Read()
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Error().Err(err).Send()
|
||||
continue
|
||||
}
|
||||
|
||||
echKeyContents = append(echKeyContents, types.FileOrContent(content))
|
||||
}
|
||||
options.ECHKeys = echKeyContents
|
||||
|
||||
configuration.TLS.Options[name] = options
|
||||
}
|
||||
}
|
||||
|
|
|
|||
165
pkg/tls/ech.go
Normal file
165
pkg/tls/ech.go
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
package tls
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
|
||||
"github.com/cloudflare/circl/hpke"
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
)
|
||||
|
||||
// sha256PrivateKeyLength is the required private key length for SHA-256 based ECDH.
|
||||
const sha256PrivateKeyLength = 32
|
||||
|
||||
func UnmarshalECHKey(data []byte) (*tls.EncryptedClientHelloKey, error) {
|
||||
var k tls.EncryptedClientHelloKey
|
||||
for {
|
||||
block, rest := pem.Decode(data)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
|
||||
switch block.Type {
|
||||
case "PRIVATE KEY":
|
||||
k.PrivateKey = block.Bytes
|
||||
case "ECHCONFIG":
|
||||
k.Config = block.Bytes[2:] // Skip the first two bytes (length prefix)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown PEM block %s", block.Type)
|
||||
}
|
||||
|
||||
data = rest
|
||||
}
|
||||
|
||||
if len(k.Config) == 0 || len(k.PrivateKey) == 0 {
|
||||
return nil, errors.New("missing ECH configuration or private key in PEM file")
|
||||
}
|
||||
|
||||
// go ecdh now only supports SHA-256 (32-byte private key)
|
||||
if len(k.PrivateKey) < sha256PrivateKeyLength {
|
||||
return nil, fmt.Errorf("invalid private key length: expected at least %d bytes, got %d bytes", sha256PrivateKeyLength, len(k.PrivateKey))
|
||||
} else if len(k.PrivateKey) > sha256PrivateKeyLength {
|
||||
k.PrivateKey = k.PrivateKey[len(k.PrivateKey)-sha256PrivateKeyLength:]
|
||||
}
|
||||
|
||||
k.SendAsRetry = true
|
||||
|
||||
return &k, nil
|
||||
}
|
||||
|
||||
func MarshalECHKey(k *tls.EncryptedClientHelloKey) ([]byte, error) {
|
||||
if len(k.Config) == 0 || len(k.PrivateKey) == 0 {
|
||||
return nil, errors.New("missing ECH configuration or private key")
|
||||
}
|
||||
configBytes := make([]byte, 2+len(k.Config))
|
||||
binary.BigEndian.PutUint16(configBytes, uint16(len(k.Config)))
|
||||
copy(configBytes[2:], k.Config)
|
||||
var pemData []byte
|
||||
pemData = append(pemData, pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: k.PrivateKey})...)
|
||||
pemData = append(pemData, pem.EncodeToMemory(&pem.Block{Type: "ECHCONFIG", Bytes: configBytes})...)
|
||||
|
||||
return pemData, nil
|
||||
}
|
||||
|
||||
type echCipher struct {
|
||||
KDFID uint16
|
||||
AEADID uint16
|
||||
}
|
||||
|
||||
type echExtension struct {
|
||||
Type uint16
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type echConfig struct {
|
||||
Version uint16
|
||||
Length uint16
|
||||
|
||||
ConfigID uint8
|
||||
KemID uint16
|
||||
PublicKey []byte
|
||||
SymmetricCipherSuite []echCipher
|
||||
|
||||
MaxNameLength uint8
|
||||
PublicName []byte
|
||||
Extensions []echExtension
|
||||
}
|
||||
|
||||
func NewECHKey(publicName string) (*tls.EncryptedClientHelloKey, error) {
|
||||
publicKey, privateKey, err := hpke.KEM_X25519_HKDF_SHA256.Scheme().GenerateKeyPair()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
publicKeyBytes, err := publicKey.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
privateKeyBytes, err := privateKey.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := echConfig{
|
||||
Version: 0xfe0d, // ECH version 0xfe0d
|
||||
Length: 0x0000,
|
||||
ConfigID: uint8(rand.Uint()),
|
||||
KemID: uint16(hpke.KEM_X25519_HKDF_SHA256),
|
||||
PublicKey: publicKeyBytes,
|
||||
SymmetricCipherSuite: []echCipher{
|
||||
{KDFID: uint16(hpke.KDF_HKDF_SHA256), AEADID: uint16(hpke.AEAD_AES256GCM)},
|
||||
},
|
||||
MaxNameLength: 32,
|
||||
PublicName: []byte(publicName),
|
||||
Extensions: nil,
|
||||
}
|
||||
if len(config.PublicName) > int(config.MaxNameLength) {
|
||||
return nil, fmt.Errorf("public name exceeds maximum length of %d bytes", config.MaxNameLength)
|
||||
}
|
||||
|
||||
var b cryptobyte.Builder
|
||||
b.AddUint16(config.Version) // Version
|
||||
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddUint8(config.ConfigID)
|
||||
b.AddUint16(config.KemID)
|
||||
b.AddUint16(uint16(len(config.PublicKey)))
|
||||
b.AddBytes(config.PublicKey)
|
||||
b.AddUint16LengthPrefixed(func(c *cryptobyte.Builder) {
|
||||
for _, cipher := range config.SymmetricCipherSuite {
|
||||
c.AddUint16(cipher.KDFID)
|
||||
c.AddUint16(cipher.AEADID)
|
||||
}
|
||||
})
|
||||
b.AddUint8(config.MaxNameLength)
|
||||
b.AddUint8(uint8(len(config.PublicName)))
|
||||
b.AddBytes(config.PublicName)
|
||||
b.AddUint16LengthPrefixed(func(c *cryptobyte.Builder) {
|
||||
for _, ext := range config.Extensions {
|
||||
c.AddUint16(ext.Type)
|
||||
c.AddUint16(uint16(len(ext.Data)))
|
||||
c.AddBytes(ext.Data)
|
||||
}
|
||||
})
|
||||
})
|
||||
configBytes, err := b.Bytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tls.EncryptedClientHelloKey{
|
||||
Config: configBytes,
|
||||
PrivateKey: privateKeyBytes,
|
||||
SendAsRetry: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ECHConfigToConfigList(echConfig []byte) ([]byte, error) {
|
||||
var b cryptobyte.Builder
|
||||
b.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
|
||||
child.AddBytes(echConfig)
|
||||
})
|
||||
return b.Bytes()
|
||||
}
|
||||
441
pkg/tls/ech_test.go
Normal file
441
pkg/tls/ech_test.go
Normal file
|
|
@ -0,0 +1,441 @@
|
|||
package tls
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// ECHRequestConfig is a configuration struct for making ECH-enabled requests.
|
||||
// This is only used for testing purposes.
|
||||
type ECHRequestConfig[T []byte | string] struct {
|
||||
URL string
|
||||
Host string
|
||||
ECH T
|
||||
Insecure bool
|
||||
}
|
||||
|
||||
// RequestWithECH sends a GET request to a server using the provided ECH configuration.
|
||||
// This is only used for testing purposes.
|
||||
func RequestWithECH[T []byte | string](c ECHRequestConfig[T]) (body []byte, err error) {
|
||||
// Decode the ECH configuration from base64 if it's a string, otherwise use it directly.
|
||||
var ech []byte
|
||||
if s, ok := any(c.ECH).(string); ok {
|
||||
ech, err = base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
ech = []byte(c.ECH)
|
||||
}
|
||||
|
||||
requestURL, err := url.Parse(c.URL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse URL: %w", err)
|
||||
}
|
||||
|
||||
if c.Host == "" {
|
||||
c.Host = requestURL.Hostname()
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
ServerName: c.Host,
|
||||
EncryptedClientHelloConfigList: ech,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
InsecureSkipVerify: c.Insecure,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
req := &http.Request{
|
||||
Method: http.MethodGet,
|
||||
URL: requestURL,
|
||||
Host: c.Host,
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func TestNewECHKey(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
publicName string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
desc: "valid short public name",
|
||||
publicName: "server.local",
|
||||
},
|
||||
{
|
||||
desc: "valid public name at max length",
|
||||
publicName: "abcdefghijklmnopqrstuvwxyz012345", // 32 chars
|
||||
},
|
||||
{
|
||||
desc: "public name exceeds max length",
|
||||
publicName: "abcdefghijklmnopqrstuvwxyz0123456", // 33 chars
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
echKey, err := NewECHKey(test.publicName)
|
||||
|
||||
if test.expectError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, echKey.Config)
|
||||
assert.NotEmpty(t, echKey.PrivateKey)
|
||||
assert.True(t, echKey.SendAsRetry)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshalECHKey(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
publicName string
|
||||
}{
|
||||
{
|
||||
desc: "standard domain",
|
||||
publicName: "server.local",
|
||||
},
|
||||
{
|
||||
desc: "subdomain",
|
||||
publicName: "api.example.com",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
echKey, err := NewECHKey(test.publicName)
|
||||
require.NoError(t, err)
|
||||
|
||||
echKeyBytes, err := MarshalECHKey(echKey)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, echKeyBytes)
|
||||
|
||||
newKey, err := UnmarshalECHKey(echKeyBytes)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, echKey.Config, newKey.Config)
|
||||
assert.Equal(t, echKey.PrivateKey, newKey.PrivateKey)
|
||||
assert.True(t, newKey.SendAsRetry)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalECHKey_Errors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
key *tls.EncryptedClientHelloKey
|
||||
}{
|
||||
{
|
||||
desc: "missing config",
|
||||
key: &tls.EncryptedClientHelloKey{
|
||||
PrivateKey: []byte("some-key"),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "missing private key",
|
||||
key: &tls.EncryptedClientHelloKey{
|
||||
Config: []byte("some-config"),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "both missing",
|
||||
key: &tls.EncryptedClientHelloKey{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := MarshalECHKey(test.key)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalECHKey_Errors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
data []byte
|
||||
}{
|
||||
{
|
||||
desc: "empty data",
|
||||
data: []byte{},
|
||||
},
|
||||
{
|
||||
desc: "invalid PEM",
|
||||
data: []byte("not a valid PEM"),
|
||||
},
|
||||
{
|
||||
desc: "unknown PEM block type",
|
||||
data: pem.EncodeToMemory(&pem.Block{Type: "UNKNOWN", Bytes: []byte("data")}),
|
||||
},
|
||||
{
|
||||
desc: "missing private key",
|
||||
data: func() []byte {
|
||||
// Create ECHCONFIG block with length prefix
|
||||
configBytes := append([]byte{0, 4}, []byte("test")...)
|
||||
return pem.EncodeToMemory(&pem.Block{Type: "ECHCONFIG", Bytes: configBytes})
|
||||
}(),
|
||||
},
|
||||
{
|
||||
desc: "missing config",
|
||||
data: pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: make([]byte, 32)}),
|
||||
},
|
||||
{
|
||||
desc: "private key too short",
|
||||
data: func() []byte {
|
||||
var pemData []byte
|
||||
pemData = append(pemData, pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: make([]byte, 16)})...)
|
||||
configBytes := append([]byte{0, 4}, []byte("test")...)
|
||||
pemData = append(pemData, pem.EncodeToMemory(&pem.Block{Type: "ECHCONFIG", Bytes: configBytes})...)
|
||||
return pemData
|
||||
}(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := UnmarshalECHKey(test.data)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestECHConfigToConfigList(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
config []byte
|
||||
}{
|
||||
{
|
||||
desc: "empty config",
|
||||
config: []byte{},
|
||||
},
|
||||
{
|
||||
desc: "simple config",
|
||||
config: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
configList, err := ECHConfigToConfigList(test.config)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Config list should have 2-byte length prefix followed by the config
|
||||
expectedLen := 2 + len(test.config)
|
||||
assert.Len(t, configList, expectedLen)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestWithECH(t *testing.T) {
|
||||
const commonName = "server.local"
|
||||
|
||||
echKey, err := NewECHKey(commonName)
|
||||
require.NoError(t, err)
|
||||
|
||||
testCert, err := generateTestCert(commonName)
|
||||
require.NoError(t, err)
|
||||
|
||||
echConfigList, err := ECHConfigToConfigList(echKey.Config)
|
||||
require.NoError(t, err)
|
||||
|
||||
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, "Hello, ECH-enabled TLS server!")
|
||||
}))
|
||||
|
||||
server.TLS = &tls.Config{
|
||||
Certificates: []tls.Certificate{testCert},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
EncryptedClientHelloKeys: []tls.EncryptedClientHelloKey{*echKey},
|
||||
}
|
||||
|
||||
server.StartTLS()
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
config ECHRequestConfig[[]byte]
|
||||
expectedBody string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
desc: "successful ECH request with bytes",
|
||||
config: ECHRequestConfig[[]byte]{
|
||||
URL: server.URL + "/",
|
||||
Host: commonName,
|
||||
ECH: echConfigList,
|
||||
Insecure: true,
|
||||
},
|
||||
expectedBody: "Hello, ECH-enabled TLS server!",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
response, err := RequestWithECH(test.config)
|
||||
|
||||
if test.expectError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedBody, string(response))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestWithECH_StringConfig(t *testing.T) {
|
||||
const commonName = "server.local"
|
||||
|
||||
echKey, err := NewECHKey(commonName)
|
||||
require.NoError(t, err)
|
||||
|
||||
testCert, err := generateTestCert(commonName)
|
||||
require.NoError(t, err)
|
||||
|
||||
echConfigList, err := ECHConfigToConfigList(echKey.Config)
|
||||
require.NoError(t, err)
|
||||
|
||||
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, "Hello from string config!")
|
||||
}))
|
||||
|
||||
server.TLS = &tls.Config{
|
||||
Certificates: []tls.Certificate{testCert},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
EncryptedClientHelloKeys: []tls.EncryptedClientHelloKey{*echKey},
|
||||
}
|
||||
|
||||
server.StartTLS()
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
// Test with base64-encoded string config
|
||||
echConfigBase64 := base64.StdEncoding.EncodeToString(echConfigList)
|
||||
|
||||
response, err := RequestWithECH(ECHRequestConfig[string]{
|
||||
URL: server.URL + "/",
|
||||
Host: commonName,
|
||||
ECH: echConfigBase64,
|
||||
Insecure: true,
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Hello from string config!", string(response))
|
||||
}
|
||||
|
||||
func TestRequestWithECH_Errors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
config ECHRequestConfig[string]
|
||||
}{
|
||||
{
|
||||
desc: "invalid base64 ECH config",
|
||||
config: ECHRequestConfig[string]{
|
||||
URL: "https://localhost:12345/",
|
||||
ECH: "not-valid-base64!!!",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "invalid URL",
|
||||
config: ECHRequestConfig[string]{
|
||||
URL: "://invalid-url",
|
||||
ECH: "dGVzdA==",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := RequestWithECH(test.config)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func generateTestCert(commonName string) (tls.Certificate, error) {
|
||||
rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, fmt.Errorf("failed to generate RSA key: %w", err)
|
||||
}
|
||||
|
||||
keyBytes := x509.MarshalPKCS1PrivateKey(rsaKey)
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: keyBytes,
|
||||
})
|
||||
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(24 * time.Hour)
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{CommonName: commonName},
|
||||
DNSNames: []string{commonName, "localhost"},
|
||||
SignatureAlgorithm: x509.SHA256WithRSA,
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
BasicConstraintsValid: true,
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
}
|
||||
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &rsaKey.PublicKey, rsaKey)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, fmt.Errorf("failed to create certificate: %w", err)
|
||||
}
|
||||
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: derBytes,
|
||||
})
|
||||
|
||||
return tls.X509KeyPair(certPEM, keyPEM)
|
||||
}
|
||||
|
|
@ -42,14 +42,15 @@ type ClientAuth struct {
|
|||
|
||||
// Options configures TLS for an entry point.
|
||||
type Options struct {
|
||||
MinVersion string `json:"minVersion,omitempty" toml:"minVersion,omitempty" yaml:"minVersion,omitempty" export:"true"`
|
||||
MaxVersion string `json:"maxVersion,omitempty" toml:"maxVersion,omitempty" yaml:"maxVersion,omitempty" export:"true"`
|
||||
CipherSuites []string `json:"cipherSuites,omitempty" toml:"cipherSuites,omitempty" yaml:"cipherSuites,omitempty" export:"true"`
|
||||
CurvePreferences []string `json:"curvePreferences,omitempty" toml:"curvePreferences,omitempty" yaml:"curvePreferences,omitempty" export:"true"`
|
||||
ClientAuth ClientAuth `json:"clientAuth,omitempty" toml:"clientAuth,omitempty" yaml:"clientAuth,omitempty"`
|
||||
SniStrict bool `json:"sniStrict,omitempty" toml:"sniStrict,omitempty" yaml:"sniStrict,omitempty" export:"true"`
|
||||
ALPNProtocols []string `json:"alpnProtocols,omitempty" toml:"alpnProtocols,omitempty" yaml:"alpnProtocols,omitempty" export:"true"`
|
||||
DisableSessionTickets bool `json:"disableSessionTickets,omitempty" toml:"disableSessionTickets,omitempty" yaml:"disableSessionTickets,omitempty" export:"true"`
|
||||
MinVersion string `json:"minVersion,omitempty" toml:"minVersion,omitempty" yaml:"minVersion,omitempty" export:"true"`
|
||||
MaxVersion string `json:"maxVersion,omitempty" toml:"maxVersion,omitempty" yaml:"maxVersion,omitempty" export:"true"`
|
||||
CipherSuites []string `json:"cipherSuites,omitempty" toml:"cipherSuites,omitempty" yaml:"cipherSuites,omitempty" export:"true"`
|
||||
CurvePreferences []string `json:"curvePreferences,omitempty" toml:"curvePreferences,omitempty" yaml:"curvePreferences,omitempty" export:"true"`
|
||||
ClientAuth ClientAuth `json:"clientAuth,omitempty" toml:"clientAuth,omitempty" yaml:"clientAuth,omitempty"`
|
||||
SniStrict bool `json:"sniStrict,omitempty" toml:"sniStrict,omitempty" yaml:"sniStrict,omitempty" export:"true"`
|
||||
ALPNProtocols []string `json:"alpnProtocols,omitempty" toml:"alpnProtocols,omitempty" yaml:"alpnProtocols,omitempty" export:"true"`
|
||||
DisableSessionTickets bool `json:"disableSessionTickets,omitempty" toml:"disableSessionTickets,omitempty" yaml:"disableSessionTickets,omitempty" export:"true"`
|
||||
ECHKeys []types.FileOrContent `json:"echKeys,omitempty" toml:"echKeys,omitempty" yaml:"echKeys,omitempty" export:"true"`
|
||||
|
||||
// Deprecated: https://github.com/golang/go/issues/45430
|
||||
PreferServerCipherSuites *bool `json:"preferServerCipherSuites,omitempty" toml:"preferServerCipherSuites,omitempty" yaml:"preferServerCipherSuites,omitempty" export:"true"`
|
||||
|
|
|
|||
|
|
@ -513,6 +513,24 @@ func buildTLSConfig(tlsOption Options) (*tls.Config, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Set the EncryptedClientHelloKeys if set in the config
|
||||
if tlsOption.ECHKeys != nil {
|
||||
conf.EncryptedClientHelloKeys = make([]tls.EncryptedClientHelloKey, 0, len(tlsOption.ECHKeys))
|
||||
for _, content := range tlsOption.ECHKeys {
|
||||
data, err := content.Read()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ECH key file failed: %w", err)
|
||||
}
|
||||
|
||||
echKey, err := UnmarshalECHKey(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshalling ECH key failed: %w", err)
|
||||
}
|
||||
|
||||
conf.EncryptedClientHelloKeys = append(conf.EncryptedClientHelloKeys, *echKey)
|
||||
}
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -116,6 +116,11 @@ func (in *Options) DeepCopyInto(out *Options) {
|
|||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ECHKeys != nil {
|
||||
in, out := &in.ECHKeys, &out.ECHKeys
|
||||
*out = make([]types.FileOrContent, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.PreferServerCipherSuites != nil {
|
||||
in, out := &in.PreferServerCipherSuites, &out.PreferServerCipherSuites
|
||||
*out = new(bool)
|
||||
|
|
|
|||
Loading…
Reference in a new issue