diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml
index e0e2139e5..b1c8cb4f5 100644
--- a/docs/content/reference/dynamic-configuration/file.toml
+++ b/docs/content/reference/dynamic-configuration/file.toml
@@ -378,6 +378,9 @@
serverName = "foobar"
insecureSkipVerify = true
rootCAs = ["foobar", "foobar"]
+ cipherSuites = ["foobar", "foobar"]
+ minVersion = "foobar"
+ maxVersion = "foobar"
maxIdleConnsPerHost = 42
disableHTTP2 = true
peerCertURI = "foobar"
@@ -402,6 +405,9 @@
serverName = "foobar"
insecureSkipVerify = true
rootCAs = ["foobar", "foobar"]
+ cipherSuites = ["foobar", "foobar"]
+ minVersion = "foobar"
+ maxVersion = "foobar"
maxIdleConnsPerHost = 42
disableHTTP2 = true
peerCertURI = "foobar"
diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml
index e2ab16e54..392a927a4 100644
--- a/docs/content/reference/dynamic-configuration/file.yaml
+++ b/docs/content/reference/dynamic-configuration/file.yaml
@@ -437,6 +437,11 @@ http:
keyFile: foobar
- certFile: foobar
keyFile: foobar
+ cipherSuites:
+ - foobar
+ - foobar
+ minVersion: foobar
+ maxVersion: foobar
maxIdleConnsPerHost: 42
forwardingTimeouts:
dialTimeout: 42s
@@ -462,6 +467,11 @@ http:
keyFile: foobar
- certFile: foobar
keyFile: foobar
+ cipherSuites:
+ - foobar
+ - foobar
+ minVersion: foobar
+ maxVersion: foobar
maxIdleConnsPerHost: 42
forwardingTimeouts:
dialTimeout: 42s
diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
index ef7c35ea0..a2af326f9 100644
--- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
+++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
@@ -2281,6 +2281,12 @@ spec:
items:
type: string
type: array
+ cipherSuites:
+ description: CipherSuites defines the cipher suites to use when contacting
+ backend servers.
+ items:
+ type: string
+ type: array
disableHTTP2:
description: DisableHTTP2 disables HTTP/2 for connections with backend
servers.
@@ -2341,6 +2347,14 @@ spec:
to keep per-host.
minimum: -1
type: integer
+ maxVersion:
+ description: MaxVersion defines the maximum TLS version to use when
+ contacting backend servers.
+ type: string
+ minVersion:
+ description: MinVersion defines the minimum TLS version to use when
+ contacting backend servers.
+ type: string
peerCertURI:
description: PeerCertURI defines the peer cert URI used to match against
SAN URI during the peer certificate verification.
diff --git a/docs/content/reference/dynamic-configuration/kv-ref.md b/docs/content/reference/dynamic-configuration/kv-ref.md
index a9d2984b4..7a2b9229e 100644
--- a/docs/content/reference/dynamic-configuration/kv-ref.md
+++ b/docs/content/reference/dynamic-configuration/kv-ref.md
@@ -237,6 +237,8 @@ THIS FILE MUST NOT BE EDITED BY HAND
| `traefik/http/serversTransports/ServersTransport0/certificates/0/keyFile` | `foobar` |
| `traefik/http/serversTransports/ServersTransport0/certificates/1/certFile` | `foobar` |
| `traefik/http/serversTransports/ServersTransport0/certificates/1/keyFile` | `foobar` |
+| `traefik/http/serversTransports/ServersTransport0/cipherSuites/0` | `foobar` |
+| `traefik/http/serversTransports/ServersTransport0/cipherSuites/1` | `foobar` |
| `traefik/http/serversTransports/ServersTransport0/disableHTTP2` | `true` |
| `traefik/http/serversTransports/ServersTransport0/forwardingTimeouts/dialTimeout` | `42s` |
| `traefik/http/serversTransports/ServersTransport0/forwardingTimeouts/idleConnTimeout` | `42s` |
@@ -245,6 +247,8 @@ THIS FILE MUST NOT BE EDITED BY HAND
| | `42s` |
| `traefik/http/serversTransports/ServersTransport0/insecureSkipVerify` | `true` |
| `traefik/http/serversTransports/ServersTransport0/maxIdleConnsPerHost` | `42` |
+| `traefik/http/serversTransports/ServersTransport0/maxVersion` | `foobar` |
+| `traefik/http/serversTransports/ServersTransport0/minVersion` | `foobar` |
| `traefik/http/serversTransports/ServersTransport0/peerCertURI` | `foobar` |
| `traefik/http/serversTransports/ServersTransport0/rootCAs/0` | `foobar` |
| `traefik/http/serversTransports/ServersTransport0/rootCAs/1` | `foobar` |
@@ -256,6 +260,8 @@ THIS FILE MUST NOT BE EDITED BY HAND
| `traefik/http/serversTransports/ServersTransport1/certificates/0/keyFile` | `foobar` |
| `traefik/http/serversTransports/ServersTransport1/certificates/1/certFile` | `foobar` |
| `traefik/http/serversTransports/ServersTransport1/certificates/1/keyFile` | `foobar` |
+| `traefik/http/serversTransports/ServersTransport1/cipherSuites/0` | `foobar` |
+| `traefik/http/serversTransports/ServersTransport1/cipherSuites/1` | `foobar` |
| `traefik/http/serversTransports/ServersTransport1/disableHTTP2` | `true` |
| `traefik/http/serversTransports/ServersTransport1/forwardingTimeouts/dialTimeout` | `42s` |
| `traefik/http/serversTransports/ServersTransport1/forwardingTimeouts/idleConnTimeout` | `42s` |
@@ -264,6 +270,8 @@ THIS FILE MUST NOT BE EDITED BY HAND
| | `42s` |
| `traefik/http/serversTransports/ServersTransport1/insecureSkipVerify` | `true` |
| `traefik/http/serversTransports/ServersTransport1/maxIdleConnsPerHost` | `42` |
+| `traefik/http/serversTransports/ServersTransport1/maxVersion` | `foobar` |
+| `traefik/http/serversTransports/ServersTransport1/minVersion` | `foobar` |
| `traefik/http/serversTransports/ServersTransport1/peerCertURI` | `foobar` |
| `traefik/http/serversTransports/ServersTransport1/rootCAs/0` | `foobar` |
| `traefik/http/serversTransports/ServersTransport1/rootCAs/1` | `foobar` |
diff --git a/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml b/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml
index 3e22e0107..b8fa0ee01 100644
--- a/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml
+++ b/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml
@@ -49,6 +49,12 @@ spec:
items:
type: string
type: array
+ cipherSuites:
+ description: CipherSuites defines the cipher suites to use when contacting
+ backend servers.
+ items:
+ type: string
+ type: array
disableHTTP2:
description: DisableHTTP2 disables HTTP/2 for connections with backend
servers.
@@ -109,6 +115,14 @@ spec:
to keep per-host.
minimum: -1
type: integer
+ maxVersion:
+ description: MaxVersion defines the maximum TLS version to use when
+ contacting backend servers.
+ type: string
+ minVersion:
+ description: MinVersion defines the minimum TLS version to use when
+ contacting backend servers.
+ type: string
peerCertURI:
description: PeerCertURI defines the peer cert URI used to match against
SAN URI during the peer certificate verification.
diff --git a/docs/content/reference/routing-configuration/http/load-balancing/serverstransport.md b/docs/content/reference/routing-configuration/http/load-balancing/serverstransport.md
index 5774e60df..0c1d22fd9 100644
--- a/docs/content/reference/routing-configuration/http/load-balancing/serverstransport.md
+++ b/docs/content/reference/routing-configuration/http/load-balancing/serverstransport.md
@@ -35,6 +35,11 @@ http:
- "spiffe://example.org/id1"
- "spiffe://example.org/id2"
trustDomain: "example.org"
+ cipherSuites:
+ - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
+ - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
+ minVersion: VersionTLS12
+ maxVersion: VersionTLS12
```
```toml tab="Structured (TOML)"
@@ -46,6 +51,9 @@ http:
maxIdleConnsPerHost = 100
disableHTTP2 = true
peerCertURI = "spiffe://example.org/peer"
+ cipherSuites = ["TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"]
+ minVersion = "VersionTLS12"
+ maxVersion = "VersionTLS12"
[http.serversTransports.mytransport.forwardingTimeouts]
dialTimeout = "30s"
@@ -100,6 +108,9 @@ labels:
| `certificates` | Defines the list of certificates (as file paths, or data bytes) that will be set as client certificates for mTLS. | [] | No |
| `insecureSkipVerify` | Controls whether the server's certificate chain and host name is verified. | false | No |
| `rootcas` | Set of root certificate authorities to use when verifying server certificates. (for mTLS connections). | [] | No |
+| `cipherSuites` | Defines the cipher suites to use when contacting backend servers. | [] | No |
+| `minVersion` | Defines the minimum TLS version to use when contacting backend servers. | "" | No |
+| `maxVersion` | Defines the maximum TLS version to use when contacting backend servers. | "" | No |
| `maxIdleConnsPerHost` | Maximum idle (keep-alive) connections to keep per-host. | 200 | No |
| `disableHTTP2` | Disables HTTP/2 for connections with servers. | false | No |
| `peerCertURI` | Defines the URI used to match against SAN URIs during the server's certificate verification. | "" | No |
diff --git a/docs/content/reference/routing-configuration/kubernetes/crd/http/serverstransport.md b/docs/content/reference/routing-configuration/kubernetes/crd/http/serverstransport.md
index 63f5c04a2..bf86e1563 100644
--- a/docs/content/reference/routing-configuration/kubernetes/crd/http/serverstransport.md
+++ b/docs/content/reference/routing-configuration/kubernetes/crd/http/serverstransport.md
@@ -67,6 +67,21 @@ spec:
| `serverstransport.`
`forwardingTimeouts.idleConnTimeout` | Maximum amount of time an idle (keep-alive) connection will remain idle before closing itself.
Zero means no timeout. | 90s | No |
| `serverstransport.`
`spiffe.ids` | Allow SPIFFE IDs.
This takes precedence over the SPIFFE TrustDomain. | | No |
| `serverstransport.`
`spiffe.trustDomain` | Allow SPIFFE trust domain. | "" | No |
+| `serverstransport.`
`serverName` | Defines the server name that will be used for SNI. | | No |
+| `serverstransport.`
`insecureSkipVerify` | Controls whether the server's certificate chain and host name is verified. | false | No |
+| `serverstransport.`
`rootcas` | Set of root certificate authorities to use when verifying server certificates. (for mTLS connections). | | No |
+| `serverstransport.`
`certificatesSecrets` | Certificates to present to the server for mTLS. | | No |
+| `serverstransport.`
`cipherSuites` | Defines the cipher suites to use when contacting backend servers. | [] | No |
+| `serverstransport.`
`minVersion` | Defines the minimum TLS version to use when contacting backend servers. | "" | No |
+| `serverstransport.`
`maxVersion` | Defines the maximum TLS version to use when contacting backend servers. | "" | No |
+| `serverstransport.`
`maxIdleConnsPerHost` | Maximum idle (keep-alive) connections to keep per-host. | 200 | No |
+| `serverstransport.`
`disableHTTP2` | Disables HTTP/2 for connections with servers. | false | No |
+| `serverstransport.`
`peerCertURI` | Defines the URI used to match against SAN URIs during the server's certificate verification. | "" | No |
+| `serverstransport.`
`forwardingTimeouts.dialTimeout` | Amount of time to wait until a connection to a server can be established.
Zero means no timeout. | 30s | No |
+| | Amount of time to wait for a server's response headers after fully writing the request (including its body, if any).
Zero means no timeout | 0s | No |
+| `serverstransport.`
`forwardingTimeouts.idleConnTimeout` | Maximum amount of time an idle (keep-alive) connection will remain idle before closing itself.
Zero means no timeout. | 90s | No |
+| `serverstransport.`
`spiffe.ids` | Allow SPIFFE IDs.
This takes precedence over the SPIFFE TrustDomain. | | No |
+| `serverstransport.`
`spiffe.trustDomain` | Allow SPIFFE trust domain. | "" | No |
!!! note "CA Secret"
The CA secret must contain a base64 encoded certificate under either a tls.ca or a ca.crt key.
diff --git a/docs/content/reference/routing-configuration/other-providers/file.toml b/docs/content/reference/routing-configuration/other-providers/file.toml
index 9e00b358f..ec728baaa 100644
--- a/docs/content/reference/routing-configuration/other-providers/file.toml
+++ b/docs/content/reference/routing-configuration/other-providers/file.toml
@@ -380,6 +380,9 @@
serverName = "foobar"
insecureSkipVerify = true
rootCAs = ["foobar", "foobar"]
+ cipherSuites = ["foobar", "foobar"]
+ minVersion = "foobar"
+ maxVersion = "foobar"
maxIdleConnsPerHost = 42
disableHTTP2 = true
peerCertURI = "foobar"
@@ -404,6 +407,9 @@
serverName = "foobar"
insecureSkipVerify = true
rootCAs = ["foobar", "foobar"]
+ cipherSuites = ["foobar", "foobar"]
+ minVersion = "foobar"
+ maxVersion = "foobar"
maxIdleConnsPerHost = 42
disableHTTP2 = true
peerCertURI = "foobar"
diff --git a/docs/content/reference/routing-configuration/other-providers/file.yaml b/docs/content/reference/routing-configuration/other-providers/file.yaml
index fdb8f2c1e..0b6033287 100644
--- a/docs/content/reference/routing-configuration/other-providers/file.yaml
+++ b/docs/content/reference/routing-configuration/other-providers/file.yaml
@@ -443,6 +443,11 @@ http:
keyFile: foobar
- certFile: foobar
keyFile: foobar
+ cipherSuites:
+ - foobar
+ - foobar
+ minVersion: foobar
+ maxVersion: foobar
maxIdleConnsPerHost: 42
forwardingTimeouts:
dialTimeout: 42s
@@ -468,6 +473,11 @@ http:
keyFile: foobar
- certFile: foobar
keyFile: foobar
+ cipherSuites:
+ - foobar
+ - foobar
+ minVersion: foobar
+ maxVersion: foobar
maxIdleConnsPerHost: 42
forwardingTimeouts:
dialTimeout: 42s
diff --git a/docs/content/routing/providers/kubernetes-crd.md b/docs/content/routing/providers/kubernetes-crd.md
index e9fa28b55..505c1ac13 100644
--- a/docs/content/routing/providers/kubernetes-crd.md
+++ b/docs/content/routing/providers/kubernetes-crd.md
@@ -1869,6 +1869,11 @@ Register the `TLSStore` kind in the Kubernetes cluster before creating `TLSStore
- spiffe://trust-domain/id1
- spiffe://trust-domain/id2
trustDomain: "spiffe://trust-domain" # [14]
+ cipherSuites: # [15]
+ - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
+ - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
+ minVersion: VersionTLS11 # [16]
+ maxVersion: VersionTLS12 # [17]
```
| Ref | Attribute | Purpose |
@@ -1887,6 +1892,9 @@ Register the `TLSStore` kind in the Kubernetes cluster before creating `TLSStore
| [12] | `spiffe` | The spiffe configuration. |
| [13] | `ids` | Defines the allowed SPIFFE IDs (takes precedence over the SPIFFE TrustDomain). |
| [14] | `trustDomain` | Defines the allowed SPIFFE trust domain. |
+| [15] | `cipherSuites` | Defines the cipher suites to use when contacting backend servers. |
+| [16] | `minVersion` | Defines the minimum TLS version to use when contacting backend servers. |
+| [17] | `maxVersion` | Defines the maximum TLS version to use when contacting backend servers. |
!!! info "CA Secret"
diff --git a/docs/content/routing/services/index.md b/docs/content/routing/services/index.md
index 0c736b1b1..67db47694 100644
--- a/docs/content/routing/services/index.md
+++ b/docs/content/routing/services/index.md
@@ -800,6 +800,129 @@ data:
ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
```
+#### `cipherSuites`
+
+_Optional_
+
+`cipherSuites` defines the cipher suites to use when contacting backend servers.
+
+This option allows you to control the cryptographic algorithms used for backend connections, which is useful for:
+
+- Connecting to legacy backends that only support specific cipher suites
+- Enforcing security policies (e.g., requiring Perfect Forward Secrecy)
+- Meeting compliance requirements
+
+If not specified, Go's default cipher suites are used.
+
+```yaml tab="File (YAML)"
+## Dynamic configuration
+http:
+ serversTransports:
+ mytransport:
+ cipherSuites:
+ - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
+ - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
+```
+
+```toml tab="File (TOML)"
+## Dynamic configuration
+[http.serversTransports.mytransport]
+ cipherSuites = ["TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"]
+```
+
+```yaml tab="Kubernetes"
+apiVersion: traefik.io/v1alpha1
+kind: ServersTransport
+metadata:
+ name: mytransport
+ namespace: default
+spec:
+ cipherSuites:
+ - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
+ - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
+```
+
+#### `minVersion`
+
+_Optional_
+
+`minVersion` defines the minimum TLS version to use when contacting backend servers.
+
+Use this option to enforce a minimum security level for backend connections.
+
+!!! info "Valid Values"
+ - `VersionTLS10` (discouraged - deprecated and insecure)
+ - `VersionTLS11` (discouraged - deprecated and insecure)
+ - `VersionTLS12` (recommended minimum)
+ - `VersionTLS13` (most secure)
+
+If not specified, Go's default minimum version is used.
+
+```yaml tab="File (YAML)"
+## Dynamic configuration
+http:
+ serversTransports:
+ mytransport:
+ minVersion: VersionTLS12
+```
+
+```toml tab="File (TOML)"
+## Dynamic configuration
+[http.serversTransports.mytransport]
+ minVersion = "VersionTLS12"
+```
+
+```yaml tab="Kubernetes"
+apiVersion: traefik.io/v1alpha1
+kind: ServersTransport
+metadata:
+ name: mytransport
+ namespace: default
+spec:
+ minVersion: VersionTLS12
+```
+
+#### `maxVersion`
+
+_Optional_
+
+`maxVersion` defines the maximum TLS version to use when contacting backend servers.
+
+!!! warning "Use with Caution"
+ We discourage using this option to disable TLS 1.3. It should only be used for connecting to legacy backends that don't support newer TLS versions.
+
+!!! info "Valid Values"
+ - `VersionTLS10`
+ - `VersionTLS11`
+ - `VersionTLS12`
+ - `VersionTLS13`
+
+If not specified, Go's default maximum version (latest) is used.
+
+```yaml tab="File (YAML)"
+## Dynamic configuration
+http:
+ serversTransports:
+ mytransport:
+ maxVersion: VersionTLS12
+```
+
+```toml tab="File (TOML)"
+## Dynamic configuration
+[http.serversTransports.mytransport]
+ maxVersion = "VersionTLS12"
+```
+
+```yaml tab="Kubernetes"
+apiVersion: traefik.io/v1alpha1
+kind: ServersTransport
+metadata:
+ name: mytransport
+ namespace: default
+spec:
+ maxVersion: VersionTLS12
+```
+
#### `maxIdleConnsPerHost`
_Optional, Default=2_
diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml
index ef7c35ea0..a2af326f9 100644
--- a/integration/fixtures/k8s/01-traefik-crd.yml
+++ b/integration/fixtures/k8s/01-traefik-crd.yml
@@ -2281,6 +2281,12 @@ spec:
items:
type: string
type: array
+ cipherSuites:
+ description: CipherSuites defines the cipher suites to use when contacting
+ backend servers.
+ items:
+ type: string
+ type: array
disableHTTP2:
description: DisableHTTP2 disables HTTP/2 for connections with backend
servers.
@@ -2341,6 +2347,14 @@ spec:
to keep per-host.
minimum: -1
type: integer
+ maxVersion:
+ description: MaxVersion defines the maximum TLS version to use when
+ contacting backend servers.
+ type: string
+ minVersion:
+ description: MinVersion defines the minimum TLS version to use when
+ contacting backend servers.
+ type: string
peerCertURI:
description: PeerCertURI defines the peer cert URI used to match against
SAN URI during the peer certificate verification.
diff --git a/pkg/config/dynamic/http_config.go b/pkg/config/dynamic/http_config.go
index afb503e44..6107a0e65 100644
--- a/pkg/config/dynamic/http_config.go
+++ b/pkg/config/dynamic/http_config.go
@@ -449,6 +449,9 @@ type ServersTransport struct {
InsecureSkipVerify bool `description:"Disables SSL certificate verification." json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" export:"true"`
RootCAs []types.FileOrContent `description:"Defines a list of CA certificates used to validate server certificates." json:"rootCAs,omitempty" toml:"rootCAs,omitempty" yaml:"rootCAs,omitempty"`
Certificates traefiktls.Certificates `description:"Defines a list of client certificates for mTLS." json:"certificates,omitempty" toml:"certificates,omitempty" yaml:"certificates,omitempty" export:"true"`
+ CipherSuites []string `description:"Defines the cipher suites to use when contacting backend servers." json:"cipherSuites,omitempty" toml:"cipherSuites,omitempty" yaml:"cipherSuites,omitempty" export:"true"`
+ MinVersion string `description:"Defines the minimum TLS version to use when contacting backend servers." json:"minVersion,omitempty" toml:"minVersion,omitempty" yaml:"minVersion,omitempty" export:"true"`
+ MaxVersion string `description:"Defines the maximum TLS version to use when contacting backend servers." json:"maxVersion,omitempty" toml:"maxVersion,omitempty" yaml:"maxVersion,omitempty" export:"true"`
MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used. If negative, disables connection reuse." json:"maxIdleConnsPerHost,omitempty" toml:"maxIdleConnsPerHost,omitempty" yaml:"maxIdleConnsPerHost,omitempty" export:"true"`
ForwardingTimeouts *ForwardingTimeouts `description:"Defines the timeouts for requests forwarded to the backend servers." json:"forwardingTimeouts,omitempty" toml:"forwardingTimeouts,omitempty" yaml:"forwardingTimeouts,omitempty" export:"true"`
DisableHTTP2 bool `description:"Disables HTTP/2 for connections with backend servers." json:"disableHTTP2,omitempty" toml:"disableHTTP2,omitempty" yaml:"disableHTTP2,omitempty" export:"true"`
diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go
index 94f95ff1e..e4bd3ae1e 100644
--- a/pkg/config/dynamic/zz_generated.deepcopy.go
+++ b/pkg/config/dynamic/zz_generated.deepcopy.go
@@ -1611,6 +1611,11 @@ func (in *ServersTransport) DeepCopyInto(out *ServersTransport) {
*out = make(tls.Certificates, len(*in))
copy(*out, *in)
}
+ if in.CipherSuites != nil {
+ in, out := &in.CipherSuites, &out.CipherSuites
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
if in.ForwardingTimeouts != nil {
in, out := &in.ForwardingTimeouts, &out.ForwardingTimeouts
*out = new(ForwardingTimeouts)
diff --git a/pkg/provider/kubernetes/crd/fixtures/with_servers_transport.yml b/pkg/provider/kubernetes/crd/fixtures/with_servers_transport.yml
index 496c5af30..5b1fee8f0 100644
--- a/pkg/provider/kubernetes/crd/fixtures/with_servers_transport.yml
+++ b/pkg/provider/kubernetes/crd/fixtures/with_servers_transport.yml
@@ -169,6 +169,11 @@ spec:
- spiffe://foo/buz
- spiffe://bar/biz
trustDomain: spiffe://lol
+ cipherSuites:
+ - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
+ - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
+ minVersion: VersionTLS11
+ maxVersion: VersionTLS12
---
apiVersion: traefik.io/v1alpha1
diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go
index 92b45a124..181724942 100644
--- a/pkg/provider/kubernetes/crd/kubernetes.go
+++ b/pkg/provider/kubernetes/crd/kubernetes.go
@@ -409,6 +409,49 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
})
}
+ var cipherSuites []string
+ if serversTransport.Spec.CipherSuites != nil {
+ for _, cipher := range serversTransport.Spec.CipherSuites {
+ if _, exists := tls.CipherSuites[cipher]; exists {
+ cipherSuites = append(cipherSuites, cipher)
+ } else {
+ logger.Error().Msgf("cipher suite not supported: %s, falling back to default CipherSuite.", cipher)
+ cipherSuites = nil
+ break
+ }
+ }
+ }
+
+ var minVersion string
+ var minVersionID uint16
+ if serversTransport.Spec.MinVersion != "" {
+ if id, exists := tls.MinVersion[serversTransport.Spec.MinVersion]; exists {
+ minVersion = serversTransport.Spec.MinVersion
+ minVersionID = id
+ } else {
+ logger.Error().Msgf("invalid TLS minimum version: %s", serversTransport.Spec.MinVersion)
+ }
+ }
+
+ var maxVersion string
+ var maxVersionID uint16
+ if serversTransport.Spec.MaxVersion != "" {
+ if id, exists := tls.MaxVersion[serversTransport.Spec.MaxVersion]; exists {
+ maxVersion = serversTransport.Spec.MaxVersion
+ maxVersionID = id
+ } else {
+ logger.Error().Msgf("invalid TLS maximum version: %s", serversTransport.Spec.MaxVersion)
+ }
+ }
+
+ if serversTransport.Spec.MinVersion != "" && serversTransport.Spec.MaxVersion != "" {
+ if minVersionID >= maxVersionID {
+ log.Error().Msgf("CipherSuite MinVersion, %s, above or equal to the MaxVersion, %s. Falling back to default MaxVersion and MinVersion", serversTransport.Spec.MinVersion, serversTransport.Spec.MaxVersion)
+ minVersion = "VersionTLS12"
+ maxVersion = ""
+ }
+ }
+
forwardingTimeout := &dynamic.ForwardingTimeouts{}
forwardingTimeout.SetDefaults()
@@ -455,6 +498,9 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
InsecureSkipVerify: serversTransport.Spec.InsecureSkipVerify,
RootCAs: rootCAs,
Certificates: certs,
+ CipherSuites: cipherSuites,
+ MinVersion: minVersion,
+ MaxVersion: maxVersion,
DisableHTTP2: serversTransport.Spec.DisableHTTP2,
MaxIdleConnsPerHost: serversTransport.Spec.MaxIdleConnsPerHost,
ForwardingTimeouts: forwardingTimeout,
diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go
index eac9b5fc1..758844bbb 100644
--- a/pkg/provider/kubernetes/crd/kubernetes_test.go
+++ b/pkg/provider/kubernetes/crd/kubernetes_test.go
@@ -4818,6 +4818,9 @@ func TestLoadIngressRoutes(t *testing.T) {
{CertFile: "TESTCERT2", KeyFile: "TESTKEY2"},
{CertFile: "TESTCERT3", KeyFile: "TESTKEY3"},
},
+ CipherSuites: []string{"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"},
+ MinVersion: "VersionTLS11",
+ MaxVersion: "VersionTLS12",
MaxIdleConnsPerHost: 42,
DisableHTTP2: true,
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransport.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransport.go
index 8cfbb92be..541710ef9 100644
--- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransport.go
+++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransport.go
@@ -38,6 +38,12 @@ type ServersTransportSpec struct {
RootCAsSecrets []string `json:"rootCAsSecrets,omitempty"`
// CertificatesSecrets defines a list of secret storing client certificates for mTLS.
CertificatesSecrets []string `json:"certificatesSecrets,omitempty"`
+ // CipherSuites defines the cipher suites to use when contacting backend servers.
+ CipherSuites []string `json:"cipherSuites,omitempty"`
+ // MinVersion defines the minimum TLS version to use when contacting backend servers.
+ MinVersion string `json:"minVersion,omitempty"`
+ // MaxVersion defines the maximum TLS version to use when contacting backend servers.
+ MaxVersion string `json:"maxVersion,omitempty"`
// MaxIdleConnsPerHost controls the maximum idle (keep-alive) to keep per-host.
// +kubebuilder:validation:Minimum=-1
MaxIdleConnsPerHost int `json:"maxIdleConnsPerHost,omitempty"`
diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go
index 3137d46ec..6e50ab3dd 100644
--- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go
@@ -1470,6 +1470,11 @@ func (in *ServersTransportSpec) DeepCopyInto(out *ServersTransportSpec) {
*out = make([]string, len(*in))
copy(*out, *in)
}
+ if in.CipherSuites != nil {
+ in, out := &in.CipherSuites, &out.CipherSuites
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
if in.ForwardingTimeouts != nil {
in, out := &in.ForwardingTimeouts, &out.ForwardingTimeouts
*out = new(ForwardingTimeouts)
diff --git a/pkg/server/service/transport.go b/pkg/server/service/transport.go
index ecce54010..87e50688d 100644
--- a/pkg/server/service/transport.go
+++ b/pkg/server/service/transport.go
@@ -169,16 +169,58 @@ func (t *TransportManager) createTLSConfig(cfg *dynamic.ServersTransport) (*tls.
config = tlsconfig.MTLSClientConfig(t.spiffeX509Source, t.spiffeX509Source, spiffeAuthorizer)
}
- if cfg.InsecureSkipVerify || len(cfg.RootCAs) > 0 || len(cfg.ServerName) > 0 || len(cfg.Certificates) > 0 || cfg.PeerCertURI != "" {
+ if cfg.InsecureSkipVerify || len(cfg.RootCAs) > 0 || len(cfg.ServerName) > 0 || len(cfg.Certificates) > 0 || cfg.PeerCertURI != "" || len(cfg.CipherSuites) > 0 || cfg.MaxVersion != "" || cfg.MinVersion != "" {
if config != nil {
return nil, errors.New("TLS and SPIFFE configuration cannot be defined at the same time")
}
+ cipherSuites := make([]uint16, 0)
+ if cfg.CipherSuites != nil {
+ for _, cipher := range cfg.CipherSuites {
+ if cipherID, exists := traefiktls.CipherSuites[cipher]; exists {
+ cipherSuites = append(cipherSuites, cipherID)
+ } else {
+ log.Error().Msgf("Invalid cipher: %v, falling back to default CipherSuite.", cipher)
+ cipherSuites = nil
+ break
+ }
+ }
+ }
+
+ var minVersion uint16
+ if cfg.MinVersion != "" {
+ if value, exists := traefiktls.MinVersion[cfg.MinVersion]; exists {
+ minVersion = value
+ } else {
+ log.Error().Msgf("Invalid TLS minimum version: %s", cfg.MinVersion)
+ }
+ }
+
+ var maxVersion uint16
+ if cfg.MaxVersion != "" {
+ if value, exists := traefiktls.MaxVersion[cfg.MaxVersion]; exists {
+ maxVersion = value
+ } else {
+ log.Error().Msgf("Invalid TLS maximum version: %s", cfg.MaxVersion)
+ }
+ }
+
+ if cfg.MinVersion != "" && cfg.MaxVersion != "" {
+ if minVersion >= maxVersion {
+ log.Error().Msgf("CipherSuite MinVersion, %s, above or equal to the MaxVersion, %s. Falling back to default MaxVersion and MinVersion", cfg.MinVersion, cfg.MaxVersion)
+ minVersion = tls.VersionTLS12
+ maxVersion = 0
+ }
+ }
+
config = &tls.Config{
ServerName: cfg.ServerName,
InsecureSkipVerify: cfg.InsecureSkipVerify,
RootCAs: createRootCACertPool(cfg.RootCAs),
Certificates: cfg.Certificates.GetCertificates(),
+ CipherSuites: cipherSuites,
+ MinVersion: minVersion,
+ MaxVersion: maxVersion,
}
if cfg.PeerCertURI != "" {
diff --git a/pkg/server/service/transport_test.go b/pkg/server/service/transport_test.go
index 0fd3a8ab8..2510cd74f 100644
--- a/pkg/server/service/transport_test.go
+++ b/pkg/server/service/transport_test.go
@@ -1,6 +1,7 @@
package service
import (
+ "bytes"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
@@ -11,10 +12,12 @@ import (
"net/http"
"net/http/httptest"
"net/url"
+ "os"
"sync/atomic"
"testing"
"time"
+ "github.com/rs/zerolog/log"
"github.com/spiffe/go-spiffe/v2/bundle/x509bundle"
"github.com/spiffe/go-spiffe/v2/spiffeid"
"github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig"
@@ -183,6 +186,346 @@ func TestKeepConnectionWhenSameConfiguration(t *testing.T) {
assert.EqualValues(t, 2, count)
}
+func TestValidCipherSuites(t *testing.T) {
+ srv := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+ rw.WriteHeader(http.StatusOK)
+ }))
+
+ cert, err := tls.X509KeyPair(LocalhostCert, LocalhostKey)
+ require.NoError(t, err)
+
+ srv.TLS = &tls.Config{
+ Certificates: []tls.Certificate{cert},
+ CipherSuites: []uint16{
+ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ },
+ }
+ srv.StartTLS()
+
+ transportManager := NewTransportManager(nil)
+
+ dynamicConf := map[string]*dynamic.ServersTransport{
+ "test": {
+ ServerName: "example.com",
+ RootCAs: []types.FileOrContent{types.FileOrContent(LocalhostCert)},
+ CipherSuites: []string{"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"},
+ },
+ }
+
+ transportManager.Update(dynamicConf)
+ require.NoError(t, err)
+ tr, err := transportManager.GetRoundTripper("test")
+ require.NoError(t, err)
+ client := http.Client{Transport: tr}
+ resp, err := client.Get(srv.URL)
+ require.NoError(t, err)
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+}
+
+func TestValidTLSVersions(t *testing.T) {
+ srv := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+ rw.WriteHeader(http.StatusOK)
+ }))
+
+ cert, err := tls.X509KeyPair(LocalhostCert, LocalhostKey)
+ require.NoError(t, err)
+
+ srv.TLS = &tls.Config{
+ Certificates: []tls.Certificate{cert},
+ MaxVersion: tls.VersionTLS12,
+ MinVersion: tls.VersionTLS11,
+ CipherSuites: []uint16{
+ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ },
+ }
+ srv.StartTLS()
+
+ transportManager := NewTransportManager(nil)
+
+ dynamicConf := map[string]*dynamic.ServersTransport{
+ "test": {
+ ServerName: "example.com",
+ RootCAs: []types.FileOrContent{types.FileOrContent(LocalhostCert)},
+ MaxVersion: "VersionTLS12",
+ MinVersion: "VersionTLS11",
+ CipherSuites: []string{"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"},
+ },
+ }
+
+ transportManager.Update(dynamicConf)
+ require.NoError(t, err)
+ tr, err := transportManager.GetRoundTripper("test")
+ require.NoError(t, err)
+ client := http.Client{Transport: tr}
+ resp, err := client.Get(srv.URL)
+ require.NoError(t, err)
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+}
+
+func TestInvalidMaxTLSVersions(t *testing.T) {
+ // Init log buffer to capture zerolog output
+ var logBuffer bytes.Buffer
+ // Capture zerolog output
+ log.Logger = log.Output(&logBuffer)
+ // Restore original logger after test
+ defer func() {
+ log.Logger = log.Output(os.Stderr)
+ }()
+
+ // Define a function to run the test logic and gather logs
+ logtest := func() {
+ srv := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+ rw.WriteHeader(http.StatusOK)
+ }))
+
+ cert, err := tls.X509KeyPair(LocalhostCert, LocalhostKey)
+ require.NoError(t, err)
+
+ srv.TLS = &tls.Config{
+ Certificates: []tls.Certificate{cert},
+ MaxVersion: tls.VersionTLS12,
+ CipherSuites: []uint16{
+ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ },
+ }
+ srv.StartTLS()
+
+ transportManager := NewTransportManager(nil)
+
+ dynamicConf := map[string]*dynamic.ServersTransport{
+ "test": {
+ ServerName: "example.com",
+ RootCAs: []types.FileOrContent{types.FileOrContent(LocalhostCert)},
+ MaxVersion: "VersionTLS16",
+ CipherSuites: []string{"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"},
+ },
+ }
+
+ transportManager.Update(dynamicConf)
+ tr, err := transportManager.GetRoundTripper("test")
+ require.NoError(t, err)
+ client := http.Client{Transport: tr}
+ resp, err := client.Get(srv.URL)
+ require.NoError(t, err)
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ }
+
+ // Run the test
+ logtest()
+ // Set logs in variable as string
+ logged := logBuffer.String()
+ // Check logs content expected error message
+ assert.Contains(t, logged, "Invalid TLS maximum version: VersionTLS16")
+}
+
+func TestInvalidMinTLSVersions(t *testing.T) {
+ // Init log buffer to capture zerolog output
+ var logBuffer bytes.Buffer
+ // Capture zerolog output
+ log.Logger = log.Output(&logBuffer)
+ // Restore original logger after test
+ defer func() {
+ log.Logger = log.Output(os.Stderr)
+ }()
+
+ // Define a function to run the test logic and gather logs
+ logtest := func() {
+ srv := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+ rw.WriteHeader(http.StatusOK)
+ }))
+
+ cert, err := tls.X509KeyPair(LocalhostCert, LocalhostKey)
+ require.NoError(t, err)
+
+ srv.TLS = &tls.Config{
+ Certificates: []tls.Certificate{cert},
+ MinVersion: tls.VersionTLS11,
+ CipherSuites: []uint16{
+ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ },
+ }
+ srv.StartTLS()
+
+ transportManager := NewTransportManager(nil)
+
+ dynamicConf := map[string]*dynamic.ServersTransport{
+ "test": {
+ ServerName: "example.com",
+ RootCAs: []types.FileOrContent{types.FileOrContent(LocalhostCert)},
+ MinVersion: "VersionTLS09",
+ CipherSuites: []string{"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"},
+ },
+ }
+
+ transportManager.Update(dynamicConf)
+ tr, err := transportManager.GetRoundTripper("test")
+ require.NoError(t, err)
+ client := http.Client{Transport: tr}
+ resp, err := client.Get(srv.URL)
+ require.NoError(t, err)
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ }
+
+ // Run the test
+ logtest()
+ // Set logs in variable as string
+ logged := logBuffer.String()
+ // Check logs content expected error message
+ assert.Contains(t, logged, "Invalid TLS minimum version: VersionTLS09")
+}
+
+func TestInvalidCipherSuites(t *testing.T) {
+ // Init log buffer to capture zerolog output
+ var logBuffer bytes.Buffer
+ // Capture zerolog output
+ log.Logger = log.Output(&logBuffer)
+ // Restore original logger after test
+ defer func() {
+ log.Logger = log.Output(os.Stderr)
+ }()
+
+ // Define a function to run the test logic and gather logs
+ logtest := func() {
+ srv := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+ rw.WriteHeader(http.StatusOK)
+ }))
+
+ cert, err := tls.X509KeyPair(LocalhostCert, LocalhostKey)
+ require.NoError(t, err)
+
+ srv.TLS = &tls.Config{
+ Certificates: []tls.Certificate{cert},
+ MaxVersion: tls.VersionTLS12,
+ CipherSuites: []uint16{
+ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ },
+ }
+ srv.StartTLS()
+
+ transportManager := NewTransportManager(nil)
+
+ dynamicConf := map[string]*dynamic.ServersTransport{
+ "test": {
+ ServerName: "example.com",
+ RootCAs: []types.FileOrContent{types.FileOrContent(LocalhostCert)},
+ MaxVersion: "VersionTLS12",
+ CipherSuites: []string{"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA385", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"},
+ },
+ }
+
+ transportManager.Update(dynamicConf)
+ tr, err := transportManager.GetRoundTripper("test")
+ require.NoError(t, err)
+ client := http.Client{Transport: tr}
+ resp, err := client.Get(srv.URL)
+ require.NoError(t, err)
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ }
+
+ // Run the test
+ logtest()
+ // Set logs in variable as string
+ logged := logBuffer.String()
+ // Check logs content expected error message
+ assert.Contains(t, logged, "Invalid cipher: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA385, falling back to default CipherSuite.")
+}
+
+func TestMinMaxCipherSuites(t *testing.T) {
+ // Init log buffer to capture zerolog output
+ var logBuffer bytes.Buffer
+ // Capture zerolog output
+ log.Logger = log.Output(&logBuffer)
+ // Restore original logger after test
+ defer func() {
+ log.Logger = log.Output(os.Stderr)
+ }()
+
+ // Define a function to run the test logic and gather logs
+ logtest := func() {
+ srv := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+ rw.WriteHeader(http.StatusOK)
+ }))
+
+ cert, err := tls.X509KeyPair(LocalhostCert, LocalhostKey)
+ require.NoError(t, err)
+
+ srv.TLS = &tls.Config{
+ Certificates: []tls.Certificate{cert},
+ MinVersion: tls.VersionTLS12,
+ CipherSuites: []uint16{
+ tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+ },
+ }
+ srv.StartTLS()
+
+ transportManager := NewTransportManager(nil)
+
+ dynamicConf := map[string]*dynamic.ServersTransport{
+ "test": {
+ ServerName: "example.com",
+ RootCAs: []types.FileOrContent{types.FileOrContent(LocalhostCert)},
+ MinVersion: "VersionTLS12",
+ MaxVersion: "VersionTLS10",
+ CipherSuites: []string{"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA"},
+ },
+ }
+
+ transportManager.Update(dynamicConf)
+ tr, err := transportManager.GetRoundTripper("test")
+ require.NoError(t, err)
+ client := http.Client{Transport: tr}
+ resp, err := client.Get(srv.URL)
+ require.NoError(t, err)
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ }
+
+ // Run the test
+ logtest()
+ // Set logs in variable as string
+ logged := logBuffer.String()
+ // Check logs content expected error message
+ assert.Contains(t, logged, "CipherSuite MinVersion, VersionTLS12, above or equal to the MaxVersion, VersionTLS10. Falling back to default MaxVersion and MinVersion")
+}
+
+func TestEmptyCipherSuites(t *testing.T) {
+ srv := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+ rw.WriteHeader(http.StatusOK)
+ }))
+
+ cert, err := tls.X509KeyPair(LocalhostCert, LocalhostKey)
+ require.NoError(t, err)
+
+ srv.TLS = &tls.Config{
+ Certificates: []tls.Certificate{cert},
+ MaxVersion: tls.VersionTLS12,
+ MinVersion: tls.VersionTLS11,
+ CipherSuites: []uint16{
+ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ },
+ }
+ srv.StartTLS()
+
+ transportManager := NewTransportManager(nil)
+
+ dynamicConf := map[string]*dynamic.ServersTransport{
+ "test": {
+ ServerName: "example.com",
+ RootCAs: []types.FileOrContent{types.FileOrContent(LocalhostCert)},
+ },
+ }
+
+ transportManager.Update(dynamicConf)
+ tr, err := transportManager.GetRoundTripper("test")
+ require.NoError(t, err)
+ client := http.Client{Transport: tr}
+ _, err = client.Get(srv.URL)
+ require.Error(t, err)
+
+ assert.ErrorContains(t, err, "remote error: tls: handshake failure")
+}
+
func TestMTLS(t *testing.T) {
srv := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)