diff --git a/docs/content/https/tls.md b/docs/content/https/tls.md index b06a4b22c..d291800da 100644 --- a/docs/content/https/tls.md +++ b/docs/content/https/tls.md @@ -274,6 +274,46 @@ spec: With TLS 1.3, the cipher suites are not configurable (all supported cipher suites are safe in this case). +### Curve Preferences + +This option allows to set the preferred elliptic curves in a specific order. + +The names of the curves defined by [`crypto`](https://godoc.org/crypto/tls#CurveID) (e.g. `CurveP521`) and the [RFC defined names](https://tools.ietf.org/html/rfc8446#section-4.2.7) (e. g. `secp521r1`) can be used. + +See [CurveID](https://godoc.org/crypto/tls#CurveID) for more information. + +```toml tab="File (TOML)" +# Dynamic configuration + +[tls.options] + [tls.options.default] + curvePreferences = ["CurveP521", "CurveP384"] +``` + +```yaml tab="File (YAML)" +# Dynamic configuration + +tls: + options: + default: + curvePreferences: + - CurveP521 + - CurveP384 +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: default + namespace: default + +spec: + curvePreferences: + - CurveP521 + - CurveP384 +``` + ### Strict SNI Checking With strict SNI checking, Traefik won't allow connections from clients connections diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml index 934b5f695..37c16d0f8 100644 --- a/docs/content/reference/dynamic-configuration/file.toml +++ b/docs/content/reference/dynamic-configuration/file.toml @@ -321,6 +321,7 @@ maxVersion = "foobar" cipherSuites = ["foobar", "foobar"] sniStrict = true + curvePreferences = ["foobar", "foobar"] [tls.options.Options0.clientAuth] caFiles = ["foobar", "foobar"] clientAuthType = "foobar" @@ -329,6 +330,7 @@ maxVersion = "foobar" cipherSuites = ["foobar", "foobar"] sniStrict = true + curvePreferences = ["foobar", "foobar"] [tls.options.Options1.clientAuth] caFiles = ["foobar", "foobar"] clientAuthType = "foobar" diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml index c2eca8db9..49bd65e93 100644 --- a/docs/content/reference/dynamic-configuration/file.yaml +++ b/docs/content/reference/dynamic-configuration/file.yaml @@ -353,6 +353,9 @@ tls: cipherSuites: - foobar - foobar + curvePreferences: + - foobar + - foobar clientAuth: caFiles: - foobar @@ -365,6 +368,9 @@ tls: cipherSuites: - foobar - foobar + curvePreferences: + - foobar + - foobar clientAuth: caFiles: - foobar diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index e9164f2f0..7392c22ad 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -481,9 +481,10 @@ func buildTLSOptions(ctx context.Context, client Client) map[string]tls.Options } tlsOptions[makeID(tlsOption.Namespace, tlsOption.Name)] = tls.Options{ - MinVersion: tlsOption.Spec.MinVersion, - MaxVersion: tlsOption.Spec.MaxVersion, - CipherSuites: tlsOption.Spec.CipherSuites, + MinVersion: tlsOption.Spec.MinVersion, + MaxVersion: tlsOption.Spec.MaxVersion, + CipherSuites: tlsOption.Spec.CipherSuites, + CurvePreferences: tlsOption.Spec.CurvePreferences, ClientAuth: tls.ClientAuth{ CAFiles: clientCAs, ClientAuthType: tlsOption.Spec.ClientAuth.ClientAuthType, diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsoption.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsoption.go index a63165e80..36f7f89f3 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsoption.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsoption.go @@ -19,11 +19,12 @@ type TLSOption struct { // TLSOptionSpec configures TLS for an entry point type TLSOptionSpec struct { - MinVersion string `json:"minVersion,omitempty"` - MaxVersion string `json:"maxVersion,omitempty"` - CipherSuites []string `json:"cipherSuites,omitempty"` - ClientAuth ClientAuth `json:"clientAuth,omitempty"` - SniStrict bool `json:"sniStrict,omitempty"` + MinVersion string `json:"minVersion,omitempty"` + MaxVersion string `json:"maxVersion,omitempty"` + CipherSuites []string `json:"cipherSuites,omitempty"` + CurvePreferences []string `json:"curvePreferences,omitempty"` + ClientAuth ClientAuth `json:"clientAuth,omitempty"` + SniStrict bool `json:"sniStrict,omitempty"` } // +k8s:deepcopy-gen=true diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go index 8f254d622..4b1e0918a 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go @@ -803,6 +803,11 @@ func (in *TLSOptionSpec) DeepCopyInto(out *TLSOptionSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.CurvePreferences != nil { + in, out := &in.CurvePreferences, &out.CurvePreferences + *out = make([]string, len(*in)) + copy(*out, *in) + } in.ClientAuth.DeepCopyInto(&out.ClientAuth) return } diff --git a/pkg/tls/certificate.go b/pkg/tls/certificate.go index a2b27d26c..a7d6d4f27 100644 --- a/pkg/tls/certificate.go +++ b/pkg/tls/certificate.go @@ -60,6 +60,20 @@ var ( "TLS_CHACHA20_POLY1305_SHA256": tls.TLS_CHACHA20_POLY1305_SHA256, "TLS_FALLBACK_SCSV": tls.TLS_FALLBACK_SCSV, } + + // CurveIDs is a Map of TLS elliptic curves from crypto/tls + // Available CurveIDs defined at https://godoc.org/crypto/tls#CurveID, + // also allowing rfc names defined at https://tools.ietf.org/html/rfc8446#section-4.2.7 + CurveIDs = map[string]tls.CurveID{ + `secp256r1`: tls.CurveP256, + `CurveP256`: tls.CurveP256, + `secp384r1`: tls.CurveP384, + `CurveP384`: tls.CurveP384, + `secp521r1`: tls.CurveP521, + `CurveP521`: tls.CurveP521, + `x25519`: tls.X25519, + `X25519`: tls.X25519, + } ) // Certificate holds a SSL cert/key pair diff --git a/pkg/tls/tls.go b/pkg/tls/tls.go index 476277a8e..36342dc39 100644 --- a/pkg/tls/tls.go +++ b/pkg/tls/tls.go @@ -16,11 +16,12 @@ 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"` - ClientAuth ClientAuth `json:"clientAuth,omitempty" toml:"clientAuth,omitempty" yaml:"clientAuth,omitempty"` - SniStrict bool `json:"sniStrict,omitempty" toml:"sniStrict,omitempty" yaml:"sniStrict,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"` + CurvePreferences []string `json:"curvePreferences,omitempty" toml:"curvePreferences,omitempty" yaml:"curvePreferences,omitempty"` + ClientAuth ClientAuth `json:"clientAuth,omitempty" toml:"clientAuth,omitempty" yaml:"clientAuth,omitempty"` + SniStrict bool `json:"sniStrict,omitempty" toml:"sniStrict,omitempty" yaml:"sniStrict,omitempty" export:"true"` } // +k8s:deepcopy-gen=true diff --git a/pkg/tls/tlsmanager.go b/pkg/tls/tlsmanager.go index d71145c2e..e69fdb2df 100644 --- a/pkg/tls/tlsmanager.go +++ b/pkg/tls/tlsmanager.go @@ -211,7 +211,7 @@ func buildTLSConfig(tlsOption Options) (*tls.Config, error) { } } - // Set the minimum TLS version if set in the config TOML + // Set the minimum TLS version if set in the config if minConst, exists := MinVersion[tlsOption.MinVersion]; exists { conf.PreferServerCipherSuites = true conf.MinVersion = minConst @@ -223,7 +223,7 @@ func buildTLSConfig(tlsOption Options) (*tls.Config, error) { conf.MaxVersion = maxConst } - // Set the list of CipherSuites if set in the config TOML + // Set the list of CipherSuites if set in the config if tlsOption.CipherSuites != nil { // if our list of CipherSuites is defined in the entryPoint config, we can re-initialize the suites list as empty conf.CipherSuites = make([]uint16, 0) @@ -237,6 +237,20 @@ func buildTLSConfig(tlsOption Options) (*tls.Config, error) { } } + // Set the list of CurvePreferences/CurveIDs if set in the config + if tlsOption.CurvePreferences != nil { + conf.CurvePreferences = make([]tls.CurveID, 0) + // if our list of CurvePreferences/CurveIDs is defined in the config, we can re-initialize the list as empty + for _, curve := range tlsOption.CurvePreferences { + if curveID, exists := CurveIDs[curve]; exists { + conf.CurvePreferences = append(conf.CurvePreferences, curveID) + } else { + // CurveID listed in the toml does not exist in our listed + return nil, fmt.Errorf("invalid CurveID in curvePreferences: %s", curve) + } + } + } + return conf, nil } diff --git a/pkg/tls/zz_generated.deepcopy.go b/pkg/tls/zz_generated.deepcopy.go index e259da2ca..4d841fbaa 100644 --- a/pkg/tls/zz_generated.deepcopy.go +++ b/pkg/tls/zz_generated.deepcopy.go @@ -79,6 +79,11 @@ func (in *Options) DeepCopyInto(out *Options) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.CurvePreferences != nil { + in, out := &in.CurvePreferences, &out.CurvePreferences + *out = make([]string, len(*in)) + copy(*out, *in) + } in.ClientAuth.DeepCopyInto(&out.ClientAuth) return }