diff --git a/pkg/cmd/server/api/types.go b/pkg/cmd/server/api/types.go index 0b94b5834d28..99151e8379fe 100644 --- a/pkg/cmd/server/api/types.go +++ b/pkg/cmd/server/api/types.go @@ -559,6 +559,12 @@ type ServingInfo struct { ClientCA string // NamedCertificates is a list of certificates to use to secure requests to specific hostnames NamedCertificates []NamedCertificate + // MinTLSVersion is the minimum TLS version supported. + // Values must match version names from https://golang.org/pkg/crypto/tls/#pkg-constants + MinTLSVersion string + // CipherSuites contains an overridden list of ciphers for the server to support. + // Values must match cipher suite IDs from https://golang.org/pkg/crypto/tls/#pkg-constants + CipherSuites []string } // NamedCertificate specifies a certificate/key, and the names it should be served for diff --git a/pkg/cmd/server/api/v1/swagger_doc.go b/pkg/cmd/server/api/v1/swagger_doc.go index 1138f84da2b2..370aff0e949f 100644 --- a/pkg/cmd/server/api/v1/swagger_doc.go +++ b/pkg/cmd/server/api/v1/swagger_doc.go @@ -765,6 +765,8 @@ var map_ServingInfo = map[string]string{ "bindNetwork": "BindNetwork is the type of network to bind to - defaults to \"tcp4\", accepts \"tcp\", \"tcp4\", and \"tcp6\"", "clientCA": "ClientCA is the certificate bundle for all the signers that you'll recognize for incoming client certificates", "namedCertificates": "NamedCertificates is a list of certificates to use to secure requests to specific hostnames", + "minTLSVersion": "MinTLSVersion is the minimum TLS version supported. Values must match version names from https://golang.org/pkg/crypto/tls/#pkg-constants", + "cipherSuites": "CipherSuites contains an overridden list of ciphers for the server to support. Values must match cipher suite IDs from https://golang.org/pkg/crypto/tls/#pkg-constants", } func (ServingInfo) SwaggerDoc() map[string]string { diff --git a/pkg/cmd/server/api/v1/types.go b/pkg/cmd/server/api/v1/types.go index dd8d1888ec84..bf3f9a26937c 100644 --- a/pkg/cmd/server/api/v1/types.go +++ b/pkg/cmd/server/api/v1/types.go @@ -511,6 +511,12 @@ type ServingInfo struct { ClientCA string `json:"clientCA"` // NamedCertificates is a list of certificates to use to secure requests to specific hostnames NamedCertificates []NamedCertificate `json:"namedCertificates"` + // MinTLSVersion is the minimum TLS version supported. + // Values must match version names from https://golang.org/pkg/crypto/tls/#pkg-constants + MinTLSVersion string `json:"minTLSVersion,omitempty"` + // CipherSuites contains an overridden list of ciphers for the server to support. + // Values must match cipher suite IDs from https://golang.org/pkg/crypto/tls/#pkg-constants + CipherSuites []string `json:"cipherSuites,omitempty"` } // NamedCertificate specifies a certificate/key, and the names it should be served for diff --git a/pkg/cmd/server/api/validation/validation.go b/pkg/cmd/server/api/validation/validation.go index dae31e4bfdf1..34833d016b3a 100644 --- a/pkg/cmd/server/api/validation/validation.go +++ b/pkg/cmd/server/api/validation/validation.go @@ -21,6 +21,7 @@ import ( "k8s.io/kubernetes/pkg/util/validation/field" "github.com/openshift/origin/pkg/cmd/server/api" + "github.com/openshift/origin/pkg/cmd/server/crypto" cmdutil "github.com/openshift/origin/pkg/cmd/util" cmdflags "github.com/openshift/origin/pkg/cmd/util/flags" ) @@ -132,6 +133,15 @@ func ValidateServingInfo(info api.ServingInfo, fldPath *field.Path) ValidationRe } } + if _, err := crypto.TLSVersion(info.MinTLSVersion); err != nil { + validationResults.AddErrors(field.NotSupported(fldPath.Child("minTLSVersion"), info.MinTLSVersion, crypto.ValidTLSVersions())) + } + for i, cipher := range info.CipherSuites { + if _, err := crypto.CipherSuite(cipher); err != nil { + validationResults.AddErrors(field.NotSupported(fldPath.Child("cipherSuites").Index(i), cipher, crypto.ValidCipherSuites())) + } + } + return validationResults } diff --git a/pkg/cmd/server/crypto/crypto.go b/pkg/cmd/server/crypto/crypto.go index a0807d514ad5..ce69d7ba3810 100644 --- a/pkg/cmd/server/crypto/crypto.go +++ b/pkg/cmd/server/crypto/crypto.go @@ -27,64 +27,150 @@ import ( "k8s.io/kubernetes/pkg/auth/user" "k8s.io/kubernetes/pkg/util/sets" + "sort" + "github.com/openshift/origin/pkg/auth/authenticator/request/x509request" cmdutil "github.com/openshift/origin/pkg/cmd/util" ) -// SecureTLSConfig enforces the default minimum security settings for the -// cluster. -// TODO: allow override -func SecureTLSConfig(config *tls.Config) *tls.Config { - // Recommendations from https://wiki.mozilla.org/Security/Server_Side_TLS +var versions = map[string]uint16{ + "VersionTLS10": tls.VersionTLS10, + "VersionTLS11": tls.VersionTLS11, + "VersionTLS12": tls.VersionTLS12, +} + +func TLSVersion(versionName string) (uint16, error) { + if len(versionName) == 0 { + return DefaultTLSVersion(), nil + } + if version, ok := versions[versionName]; ok { + return version, nil + } + return 0, fmt.Errorf("unknown tls version %q", versionName) +} +func TLSVersionOrDie(versionName string) uint16 { + version, err := TLSVersion(versionName) + if err != nil { + panic(err) + } + return version +} +func ValidTLSVersions() []string { + validVersions := []string{} + for k := range versions { + validVersions = append(validVersions, k) + } + sort.Strings(validVersions) + return validVersions +} +func DefaultTLSVersion() uint16 { // Can't use SSLv3 because of POODLE and BEAST // Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher // Can't use TLSv1.1 because of RC4 cipher usage - config.MinVersion = tls.VersionTLS12 - // In a legacy environment, allow cipher control to be disabled. - if len(os.Getenv("OPENSHIFT_ALLOW_DANGEROUS_TLS_CIPHER_SUITES")) == 0 { - config.PreferServerCipherSuites = true - config.CipherSuites = []uint16{ - // Ciphers below are selected and ordered based on the recommended "Intermediate compatibility" suite - // Compare with available ciphers when bumping Go versions - // - // Available ciphers from last comparison (go 1.6): - // TLS_RSA_WITH_RC4_128_SHA - no - // TLS_RSA_WITH_3DES_EDE_CBC_SHA - // TLS_RSA_WITH_AES_128_CBC_SHA - // TLS_RSA_WITH_AES_256_CBC_SHA - // TLS_RSA_WITH_AES_128_GCM_SHA256 - // TLS_RSA_WITH_AES_256_GCM_SHA384 - // TLS_ECDHE_ECDSA_WITH_RC4_128_SHA - no - // TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA - // TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA - // TLS_ECDHE_RSA_WITH_RC4_128_SHA - no - // TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA - // TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA - // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA - // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - // TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, - tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - // the next two are in the intermediate suite, but go1.6 http2 complains when they are included at the recommended index - // fixed in https://github.com/golang/go/commit/b5aae1a2845f157a2565b856fb2d7773a0f7af25 in go1.7 - // tls.TLS_RSA_WITH_AES_128_GCM_SHA256, - // tls.TLS_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_RSA_WITH_AES_128_CBC_SHA, - tls.TLS_RSA_WITH_AES_256_CBC_SHA, - tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, - tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + return tls.VersionTLS12 +} + +var ciphers = map[string]uint16{ + "TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA, + "TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + "TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA, + "TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA, + "TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256, + "TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384, + "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + "TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, + "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, +} + +func CipherSuite(cipherName string) (uint16, error) { + if cipher, ok := ciphers[cipherName]; ok { + return cipher, nil + } + return 0, fmt.Errorf("unknown cipher name %q", cipherName) +} + +func CipherSuitesOrDie(cipherNames []string) []uint16 { + if len(cipherNames) == 0 { + return DefaultCiphers() + } + cipherValues := []uint16{} + for _, cipherName := range cipherNames { + cipher, err := CipherSuite(cipherName) + if err != nil { + panic(err) } - } else { - glog.Warningf("Potentially insecure TLS cipher suites are allowed in client connections because environment variable OPENSHIFT_ALLOW_DANGEROUS_TLS_CIPHER_SUITES is set") + cipherValues = append(cipherValues, cipher) + } + return cipherValues +} +func ValidCipherSuites() []string { + validCipherSuites := []string{} + for k := range ciphers { + validCipherSuites = append(validCipherSuites, k) + } + sort.Strings(validCipherSuites) + return validCipherSuites +} +func DefaultCiphers() []uint16 { + return []uint16{ + // Ciphers below are selected and ordered based on the recommended "Intermediate compatibility" suite + // Compare with available ciphers when bumping Go versions + // + // Available ciphers from last comparison (go 1.6): + // TLS_RSA_WITH_RC4_128_SHA - no + // TLS_RSA_WITH_3DES_EDE_CBC_SHA + // TLS_RSA_WITH_AES_128_CBC_SHA + // TLS_RSA_WITH_AES_256_CBC_SHA + // TLS_RSA_WITH_AES_128_GCM_SHA256 + // TLS_RSA_WITH_AES_256_GCM_SHA384 + // TLS_ECDHE_ECDSA_WITH_RC4_128_SHA - no + // TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA + // TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA + // TLS_ECDHE_RSA_WITH_RC4_128_SHA - no + // TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA + // TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + // TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + // the next two are in the intermediate suite, but go1.6 http2 complains when they are included at the recommended index + // fixed in https://github.com/golang/go/commit/b5aae1a2845f157a2565b856fb2d7773a0f7af25 in go1.7 + // tls.TLS_RSA_WITH_AES_128_GCM_SHA256, + // tls.TLS_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_RSA_WITH_AES_128_CBC_SHA, + tls.TLS_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + } +} + +// SecureTLSConfig enforces the default minimum security settings for the cluster. +func SecureTLSConfig(config *tls.Config) *tls.Config { + if config.MinVersion == 0 { + config.MinVersion = DefaultTLSVersion() + } + + config.PreferServerCipherSuites = true + if len(config.CipherSuites) == 0 { + config.CipherSuites = DefaultCiphers() } return config } diff --git a/pkg/cmd/server/crypto/crypto_test.go b/pkg/cmd/server/crypto/crypto_test.go index 04d2e421ec82..842c75eea55d 100644 --- a/pkg/cmd/server/crypto/crypto_test.go +++ b/pkg/cmd/server/crypto/crypto_test.go @@ -5,12 +5,54 @@ import ( "crypto/x509" "crypto/x509/pkix" "fmt" + "go/importer" + "strings" "testing" "time" ) const certificateLifetime = 365 * 2 +func TestConstantMaps(t *testing.T) { + pkg, err := importer.Default().Import("crypto/tls") + if err != nil { + fmt.Printf("error: %s\n", err.Error()) + return + } + discoveredVersions := map[string]bool{} + discoveredCiphers := map[string]bool{} + for _, declName := range pkg.Scope().Names() { + if strings.HasPrefix(declName, "VersionTLS") { + discoveredVersions[declName] = true + } + if strings.HasPrefix(declName, "TLS_RSA_") || strings.HasPrefix(declName, "TLS_ECDHE_") { + discoveredCiphers[declName] = true + } + } + + for k := range discoveredCiphers { + if _, ok := ciphers[k]; !ok { + t.Errorf("discovered cipher tls.%s not in ciphers map", k) + } + } + for k := range ciphers { + if _, ok := discoveredCiphers[k]; !ok { + t.Errorf("ciphers map has %s not in tls package", k) + } + } + + for k := range discoveredVersions { + if _, ok := versions[k]; !ok { + t.Errorf("discovered version tls.%s not in version map", k) + } + } + for k := range versions { + if _, ok := discoveredVersions[k]; !ok { + t.Errorf("versions map has %s not in tls package", k) + } + } +} + func TestCrypto(t *testing.T) { roots := x509.NewCertPool() intermediates := x509.NewCertPool() diff --git a/pkg/cmd/server/kubernetes/master_config.go b/pkg/cmd/server/kubernetes/master_config.go index cadb1c697074..82ad2a114e0d 100644 --- a/pkg/cmd/server/kubernetes/master_config.go +++ b/pkg/cmd/server/kubernetes/master_config.go @@ -50,6 +50,7 @@ import ( "github.com/openshift/origin/pkg/api" "github.com/openshift/origin/pkg/cmd/flagtypes" configapi "github.com/openshift/origin/pkg/cmd/server/api" + "github.com/openshift/origin/pkg/cmd/server/crypto" "github.com/openshift/origin/pkg/cmd/server/election" cmdflags "github.com/openshift/origin/pkg/cmd/util/flags" "github.com/openshift/origin/pkg/controller/shared" @@ -298,6 +299,8 @@ func BuildKubernetesMasterConfig(options configapi.MasterConfig, requestContextM genericConfig.LegacyAPIGroupPrefixes = LegacyAPIGroupPrefixes genericConfig.SecureServingInfo.BindAddress = options.ServingInfo.BindAddress genericConfig.SecureServingInfo.BindNetwork = options.ServingInfo.BindNetwork + genericConfig.SecureServingInfo.MinTLSVersion = crypto.TLSVersionOrDie(options.ServingInfo.MinTLSVersion) + genericConfig.SecureServingInfo.CipherSuites = crypto.CipherSuitesOrDie(options.ServingInfo.CipherSuites) oAuthClientCertCAs, err := configapi.GetOAuthClientCertCAs(options) if err != nil { glog.Fatalf("Error setting up OAuth2 client certificates: %v", err) diff --git a/pkg/cmd/server/kubernetes/node_config.go b/pkg/cmd/server/kubernetes/node_config.go index ec90fff6e573..863d0887c177 100644 --- a/pkg/cmd/server/kubernetes/node_config.go +++ b/pkg/cmd/server/kubernetes/node_config.go @@ -275,6 +275,8 @@ func BuildKubernetesNodeConfig(options configapi.NodeConfig, enableProxy, enable // Do not use NameToCertificate, since that requires certificates be included in the server's tlsConfig.Certificates list, // which we do not control when running with http.Server#ListenAndServeTLS GetCertificate: cmdutil.GetCertificateFunc(extraCerts), + MinVersion: crypto.TLSVersionOrDie(options.ServingInfo.MinTLSVersion), + CipherSuites: crypto.CipherSuitesOrDie(options.ServingInfo.CipherSuites), }), CertFile: options.ServingInfo.ServerCert.CertFile, KeyFile: options.ServingInfo.ServerCert.KeyFile, diff --git a/pkg/cmd/server/origin/asset.go b/pkg/cmd/server/origin/asset.go index ce4c3d257c9f..7a24e9fb7241 100644 --- a/pkg/cmd/server/origin/asset.go +++ b/pkg/cmd/server/origin/asset.go @@ -73,6 +73,8 @@ func (c *AssetConfig) Run() { server.TLSConfig = crypto.SecureTLSConfig(&tls.Config{ // Set SNI certificate func GetCertificate: cmdutil.GetCertificateFunc(extraCerts), + MinVersion: crypto.TLSVersionOrDie(c.Options.ServingInfo.MinTLSVersion), + CipherSuites: crypto.CipherSuitesOrDie(c.Options.ServingInfo.CipherSuites), }) glog.Infof("Web console listening at https://%s", c.Options.ServingInfo.BindAddress) glog.Fatal(cmdutil.ListenAndServeTLS(server, c.Options.ServingInfo.BindNetwork, c.Options.ServingInfo.ServerCert.CertFile, c.Options.ServingInfo.ServerCert.KeyFile)) diff --git a/pkg/cmd/server/origin/master.go b/pkg/cmd/server/origin/master.go index ee4068308e10..82971df8718a 100644 --- a/pkg/cmd/server/origin/master.go +++ b/pkg/cmd/server/origin/master.go @@ -396,6 +396,8 @@ func (c *MasterConfig) serve(handler http.Handler, messages []string) { ClientCAs: c.ClientCAs, // Set SNI certificate func GetCertificate: cmdutil.GetCertificateFunc(extraCerts), + MinVersion: crypto.TLSVersionOrDie(c.Options.ServingInfo.MinTLSVersion), + CipherSuites: crypto.CipherSuitesOrDie(c.Options.ServingInfo.CipherSuites), }) glog.Fatal(cmdutil.ListenAndServeTLS(server, c.Options.ServingInfo.BindNetwork, c.Options.ServingInfo.ServerCert.CertFile, c.Options.ServingInfo.ServerCert.KeyFile)) } else { diff --git a/test/integration/tls_test.go b/test/integration/tls_test.go new file mode 100644 index 000000000000..c6979dc834d4 --- /dev/null +++ b/test/integration/tls_test.go @@ -0,0 +1,170 @@ +package integration + +import ( + "crypto/tls" + "testing" + + "github.com/openshift/origin/pkg/cmd/server/crypto" + testutil "github.com/openshift/origin/test/util" + testserver "github.com/openshift/origin/test/util/server" +) + +func TestTLSDefaults(t *testing.T) { + testutil.RequireEtcd(t) + defer testutil.DumpEtcdOnFailure(t) + master, node, components, err := testserver.DefaultAllInOneOptions() + if err != nil { + t.Fatal(err) + } + _, err = testserver.StartConfiguredAllInOne(master, node, components) + if err != nil { + t.Fatal(err) + } + + // Verify we fail with TLS versions less than the default, and work with TLS versions >= the default + for _, tlsVersionName := range crypto.ValidTLSVersions() { + tlsVersion := crypto.TLSVersionOrDie(tlsVersionName) + expectSuccess := tlsVersion >= crypto.DefaultTLSVersion() + config := &tls.Config{MinVersion: tlsVersion, MaxVersion: tlsVersion, InsecureSkipVerify: true} + + { + conn, err := tls.Dial(master.ServingInfo.BindNetwork, master.ServingInfo.BindAddress, config) + if err == nil { + conn.Close() + } + if success := err == nil; success != expectSuccess { + t.Errorf("Expected success %v, got %v with TLS version %s dialing master", expectSuccess, success, tlsVersionName) + } + } + { + conn, err := tls.Dial(node.ServingInfo.BindNetwork, node.ServingInfo.BindAddress, config) + if err == nil { + conn.Close() + } + if success := err == nil; success != expectSuccess { + t.Errorf("Expected success %v, got %v with TLS version %s dialing node", expectSuccess, success, tlsVersionName) + } + } + } + + // Verify the only ciphers we work with are in the default set. + // Not all default ciphers will succeed because they depend on the serving cert type. + defaultCiphers := map[uint16]bool{} + for _, defaultCipher := range crypto.DefaultCiphers() { + defaultCiphers[defaultCipher] = true + } + for _, cipherName := range crypto.ValidCipherSuites() { + cipher, err := crypto.CipherSuite(cipherName) + if err != nil { + t.Fatal(err) + } + expectFailure := !defaultCiphers[cipher] + config := &tls.Config{CipherSuites: []uint16{cipher}, InsecureSkipVerify: true} + + { + conn, err := tls.Dial(master.ServingInfo.BindNetwork, master.ServingInfo.BindAddress, config) + if err == nil { + conn.Close() + if expectFailure { + t.Errorf("Expected failure on cipher %s, got success dialing master", cipherName) + } + } + } + { + conn, err := tls.Dial(node.ServingInfo.BindNetwork, node.ServingInfo.BindAddress, config) + if err == nil { + conn.Close() + if expectFailure { + t.Errorf("Expected failure on cipher %s, got success dialing node", cipherName) + } + } + } + } +} + +func TestTLSOverrides(t *testing.T) { + testutil.RequireEtcd(t) + defer testutil.DumpEtcdOnFailure(t) + master, node, components, err := testserver.DefaultAllInOneOptions() + if err != nil { + t.Fatal(err) + } + + // Pick these ciphers because the first is http2 compatible, and the second works with TLS10 + master.ServingInfo.MinTLSVersion = "VersionTLS10" + master.ServingInfo.CipherSuites = []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA"} + node.ServingInfo.MinTLSVersion = "VersionTLS10" + node.ServingInfo.CipherSuites = []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA"} + + _, err = testserver.StartConfiguredAllInOne(master, node, components) + if err != nil { + t.Fatal(err) + } + + // Verify we work with all TLS versions + for _, tlsVersionName := range crypto.ValidTLSVersions() { + tlsVersion := crypto.TLSVersionOrDie(tlsVersionName) + expectSuccess := true + config := &tls.Config{MinVersion: tlsVersion, MaxVersion: tlsVersion, InsecureSkipVerify: true} + + { + conn, err := tls.Dial(master.ServingInfo.BindNetwork, master.ServingInfo.BindAddress, config) + if err == nil { + conn.Close() + } + if success := err == nil; success != expectSuccess { + t.Errorf("Expected success %v, got %v with TLS version %s dialing master", expectSuccess, success, tlsVersionName) + } + } + { + conn, err := tls.Dial(node.ServingInfo.BindNetwork, node.ServingInfo.BindAddress, config) + if err == nil { + conn.Close() + } + if success := err == nil; success != expectSuccess { + t.Errorf("Expected success %v, got %v with TLS version %s dialing node", expectSuccess, success, tlsVersionName) + } + } + } + + // Verify the only ciphers we work with are the ones we chose + defaultCiphers := map[uint16]bool{} + for _, defaultCipher := range crypto.DefaultCiphers() { + defaultCiphers[defaultCipher] = true + } + for _, cipherName := range crypto.ValidCipherSuites() { + cipher, err := crypto.CipherSuite(cipherName) + if err != nil { + t.Fatal(err) + } + expectFailure := true + switch cipher { + case tls.TLS_RSA_WITH_AES_128_CBC_SHA: + expectFailure = false + case tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + expectFailure = false + default: + expectFailure = true + } + config := &tls.Config{CipherSuites: []uint16{cipher}, InsecureSkipVerify: true} + + { + conn, err := tls.Dial(master.ServingInfo.BindNetwork, master.ServingInfo.BindAddress, config) + if err == nil { + conn.Close() + if expectFailure { + t.Errorf("Expected failure on cipher %s, got success dialing master", cipherName) + } + } + } + { + conn, err := tls.Dial(node.ServingInfo.BindNetwork, node.ServingInfo.BindAddress, config) + if err == nil { + conn.Close() + if expectFailure { + t.Errorf("Expected failure on cipher %s, got success dialing node", cipherName) + } + } + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/genericapiserver/config.go b/vendor/k8s.io/kubernetes/pkg/genericapiserver/config.go index 9726b5dbfa24..497fd0b2791b 100644 --- a/vendor/k8s.io/kubernetes/pkg/genericapiserver/config.go +++ b/vendor/k8s.io/kubernetes/pkg/genericapiserver/config.go @@ -168,6 +168,12 @@ type SecureServingInfo struct { SNICerts []NamedCertKey // ClientCA is the certificate bundle for all the signers that you'll recognize for incoming client certificates ClientCA *x509.CertPool + // MinTLSVersion optionally overrides the minimum TLS version supported. + // If 0, the default is used. + MinTLSVersion uint16 + // CipherSuites optionally overrides the list of cipher suites for the server. + // If empty, the default is used. + CipherSuites []uint16 } type CertKey struct { diff --git a/vendor/k8s.io/kubernetes/pkg/genericapiserver/serve.go b/vendor/k8s.io/kubernetes/pkg/genericapiserver/serve.go index dd995e7f34f3..840f4fdef59b 100644 --- a/vendor/k8s.io/kubernetes/pkg/genericapiserver/serve.go +++ b/vendor/k8s.io/kubernetes/pkg/genericapiserver/serve.go @@ -61,6 +61,13 @@ func (s *GenericAPIServer) serveSecurely(stopCh <-chan struct{}) error { }, } + if s.SecureServingInfo.MinTLSVersion > 0 { + secureServer.TLSConfig.MinVersion = s.SecureServingInfo.MinTLSVersion + } + if len(s.SecureServingInfo.CipherSuites) > 0 { + secureServer.TLSConfig.CipherSuites = s.SecureServingInfo.CipherSuites + } + if len(s.SecureServingInfo.ServerCert.CertFile) != 0 || len(s.SecureServingInfo.ServerCert.KeyFile) != 0 { secureServer.TLSConfig.Certificates = make([]tls.Certificate, 1) secureServer.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(s.SecureServingInfo.ServerCert.CertFile, s.SecureServingInfo.ServerCert.KeyFile)