diff --git a/pkg/config/dynamic/http_config.go b/pkg/config/dynamic/http_config.go index d91f60340..06ea40e02 100644 --- a/pkg/config/dynamic/http_config.go +++ b/pkg/config/dynamic/http_config.go @@ -334,6 +334,7 @@ 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 `json:"cipherSuites,omitempty" toml:"cipherSuites,omitempty" yaml:"cipherSuites,omitempty" export:"true"` MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used" 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/provider/kubernetes/crd/traefikio/v1alpha1/serverstransport.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransport.go index 5c7773895..b836fa191 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransport.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransport.go @@ -38,6 +38,8 @@ 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 a list of cipher to use to contact backends + CipherSuites []string `json:"cipherSuites,omitempty" toml:"cipherSuites,omitempty" yaml:"cipherSuites,omitempty" export:"true"` // MaxIdleConnsPerHost controls the maximum idle (keep-alive) to keep per-host. // +kubebuilder:validation:Minimum=0 MaxIdleConnsPerHost int `json:"maxIdleConnsPerHost,omitempty"` diff --git a/pkg/server/service/transport.go b/pkg/server/service/transport.go index ecce54010..5166dffe6 100644 --- a/pkg/server/service/transport.go +++ b/pkg/server/service/transport.go @@ -174,11 +174,25 @@ func (t *TransportManager) createTLSConfig(cfg *dynamic.ServersTransport) (*tls. return nil, errors.New("TLS and SPIFFE configuration cannot be defined at the same time") } + // map and validate the CipherSuite passed in the configuration + if cfg.CipherSuites != nil { + config.CipherSuites = make([]uint16, 0) + for _, cipher := range cfg.CipherSuites { + if cipherID, exists := traefiktls.CipherSuites[cipher]; exists { + config.CipherSuites = append(config.CipherSuites, cipherID) + } else { + // CipherSuite listed in the configuration does not exist in our list + return nil, fmt.Errorf("invalid CipherSuite: %s", cipher) + } + } + } + config = &tls.Config{ ServerName: cfg.ServerName, InsecureSkipVerify: cfg.InsecureSkipVerify, RootCAs: createRootCACertPool(cfg.RootCAs), Certificates: cfg.Certificates.GetCertificates(), + CipherSuites: config.CipherSuites, } if cfg.PeerCertURI != "" { diff --git a/pkg/server/service/transport_test.go b/pkg/server/service/transport_test.go index 0fd3a8ab8..8a8cc3231 100644 --- a/pkg/server/service/transport_test.go +++ b/pkg/server/service/transport_test.go @@ -118,6 +118,8 @@ PtvuNc5EImfSkuPBYLBslNxtjbBvAYgacEdY+gRhn2TeIUApnND58lCWsKbNHLFZ ajIPbTY+Fe9OTOFTN48ujXNn -----END PRIVATE KEY-----`) +var ciphers = []string{"TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA256"} + func TestKeepConnectionWhenSameConfiguration(t *testing.T) { srv := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusOK) @@ -183,6 +185,49 @@ func TestKeepConnectionWhenSameConfiguration(t *testing.T) { assert.EqualValues(t, 2, count) } +func TestCipherSuites(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}} + srv.StartTLS() + + transportManager := NewTransportManager(nil) + + dynamicConf := map[string]*dynamic.ServersTransport{ + "test": { + ServerName: "example.com", + // For TLS + RootCAs: []types.FileOrContent{types.FileOrContent(LocalhostCert)}, + + // For mTLS + Certificates: traefiktls.Certificates{ + traefiktls.Certificate{ + CertFile: types.FileOrContent(mTLSCert), + KeyFile: types.FileOrContent(mTLSKey), + }, + }, + CipherSuites: ciphers, + }, + } + + 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) +} + func TestMTLS(t *testing.T) { srv := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusOK)