diff --git a/cmd/kubelet/app/options/options.go b/cmd/kubelet/app/options/options.go index b2aca7f219a..3ce0b652884 100644 --- a/cmd/kubelet/app/options/options.go +++ b/cmd/kubelet/app/options/options.go @@ -464,11 +464,13 @@ func AddKubeletConfigFlags(mainfs *pflag.FlagSet, c *kubeletconfig.KubeletConfig fs.StringVar(&c.TLSPrivateKeyFile, "tls-private-key-file", c.TLSPrivateKeyFile, "File containing x509 private key matching --tls-cert-file.") fs.BoolVar(&c.ServerTLSBootstrap, "rotate-server-certificates", c.ServerTLSBootstrap, "Auto-request and rotate the kubelet serving certificates by requesting new certificates from the kube-apiserver when the certificate expiration approaches. Requires the RotateKubeletServerCertificate feature gate to be enabled, and approval of the submitted CertificateSigningRequest objects.") - tlsCipherPossibleValues := cliflag.TLSCipherPossibleValues() + tlsCipherPreferredValues := cliflag.PreferredTLSCipherNames() + tlsCipherInsecureValues := cliflag.InsecureTLSCipherNames() fs.StringSliceVar(&c.TLSCipherSuites, "tls-cipher-suites", c.TLSCipherSuites, "Comma-separated list of cipher suites for the server. "+ - "If omitted, the default Go cipher suites will be used. "+ - "Possible values: "+strings.Join(tlsCipherPossibleValues, ",")) + "If omitted, the default Go cipher suites will be used. \n"+ + "Preferred values: "+strings.Join(tlsCipherPreferredValues, ", ")+". \n"+ + "Insecure values: "+strings.Join(tlsCipherInsecureValues, ", ")+".") tlsPossibleVersions := cliflag.TLSPossibleVersions() fs.StringVar(&c.TLSMinVersion, "tls-min-version", c.TLSMinVersion, "Minimum TLS version supported. "+ diff --git a/cmd/kubelet/app/server.go b/cmd/kubelet/app/server.go index ad95c83949e..3639ce18b1f 100644 --- a/cmd/kubelet/app/server.go +++ b/cmd/kubelet/app/server.go @@ -60,6 +60,7 @@ import ( "k8s.io/client-go/util/connrotation" "k8s.io/client-go/util/keyutil" cloudprovider "k8s.io/cloud-provider" + "k8s.io/component-base/cli/flag" cliflag "k8s.io/component-base/cli/flag" "k8s.io/component-base/configz" "k8s.io/component-base/featuregate" @@ -1010,6 +1011,17 @@ func InitializeTLS(kf *options.KubeletFlags, kc *kubeletconfiginternal.KubeletCo return nil, err } + if len(tlsCipherSuites) > 0 { + insecureCiphers := flag.InsecureTLSCiphers() + for i := 0; i < len(tlsCipherSuites); i++ { + for cipherName, cipherID := range insecureCiphers { + if tlsCipherSuites[i] == cipherID { + klog.Warningf("Use of insecure cipher '%s' detected.", cipherName) + } + } + } + } + minTLSVersion, err := cliflag.TLSVersion(kc.TLSMinVersion) if err != nil { return nil, err diff --git a/staging/src/k8s.io/apiserver/pkg/server/BUILD b/staging/src/k8s.io/apiserver/pkg/server/BUILD index 9b6b5a4999f..1f1a706a5fc 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/server/BUILD @@ -117,6 +117,7 @@ go_library( "//staging/src/k8s.io/apiserver/pkg/util/openapi:go_default_library", "//staging/src/k8s.io/client-go/informers:go_default_library", "//staging/src/k8s.io/client-go/rest:go_default_library", + "//staging/src/k8s.io/component-base/cli/flag:go_default_library", "//staging/src/k8s.io/component-base/logs:go_default_library", "//vendor/github.com/coreos/go-systemd/daemon:go_default_library", "//vendor/github.com/emicklei/go-restful:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/serving.go b/staging/src/k8s.io/apiserver/pkg/server/options/serving.go index 8a791cf72ea..0dcbbb738fd 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/serving.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/serving.go @@ -171,11 +171,13 @@ func (s *SecureServingOptions) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&s.ServerCert.CertKey.KeyFile, "tls-private-key-file", s.ServerCert.CertKey.KeyFile, "File containing the default x509 private key matching --tls-cert-file.") - tlsCipherPossibleValues := cliflag.TLSCipherPossibleValues() + tlsCipherPreferredValues := cliflag.PreferredTLSCipherNames() + tlsCipherInsecureValues := cliflag.InsecureTLSCipherNames() fs.StringSliceVar(&s.CipherSuites, "tls-cipher-suites", s.CipherSuites, "Comma-separated list of cipher suites for the server. "+ - "If omitted, the default Go cipher suites will be use. "+ - "Possible values: "+strings.Join(tlsCipherPossibleValues, ",")) + "If omitted, the default Go cipher suites will be used. \n"+ + "Preferred values: "+strings.Join(tlsCipherPreferredValues, ", ")+". \n"+ + "Insecure values: "+strings.Join(tlsCipherInsecureValues, ", ")+".") tlsPossibleVersions := cliflag.TLSPossibleVersions() fs.StringVar(&s.MinTLSVersion, "tls-min-version", s.MinTLSVersion, diff --git a/staging/src/k8s.io/apiserver/pkg/server/secure_serving.go b/staging/src/k8s.io/apiserver/pkg/server/secure_serving.go index 1f92a58d635..b5b108ce732 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/secure_serving.go +++ b/staging/src/k8s.io/apiserver/pkg/server/secure_serving.go @@ -25,6 +25,7 @@ import ( "time" "golang.org/x/net/http2" + "k8s.io/component-base/cli/flag" "k8s.io/klog/v2" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -56,6 +57,14 @@ func (s *SecureServingInfo) tlsConfig(stopCh <-chan struct{}) (*tls.Config, erro } if len(s.CipherSuites) > 0 { tlsConfig.CipherSuites = s.CipherSuites + insecureCiphers := flag.InsecureTLSCiphers() + for i := 0; i < len(s.CipherSuites); i++ { + for cipherName, cipherID := range insecureCiphers { + if s.CipherSuites[i] == cipherID { + klog.Warningf("Use of insecure cipher '%s' detected.", cipherName) + } + } + } } if s.ClientCA != nil { diff --git a/staging/src/k8s.io/component-base/cli/flag/ciphersuites_flag.go b/staging/src/k8s.io/component-base/cli/flag/ciphersuites_flag.go index a84484cf239..a52faf91975 100644 --- a/staging/src/k8s.io/component-base/cli/flag/ciphersuites_flag.go +++ b/staging/src/k8s.io/component-base/cli/flag/ciphersuites_flag.go @@ -25,23 +25,18 @@ import ( // ciphers maps strings into tls package cipher constants in // https://golang.org/pkg/crypto/tls/#pkg-constants +// to be replaced by tls.CipherSuites() when the project migrates to go1.14. 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_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256, "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_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, "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, @@ -53,7 +48,38 @@ var ciphers = map[string]uint16{ "TLS_AES_256_GCM_SHA384": tls.TLS_AES_256_GCM_SHA384, } -func TLSCipherPossibleValues() []string { +// to be replaced by tls.InsecureCipherSuites() when the project migrates to go1.14. +var insecureCiphers = map[string]uint16{ + "TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA, + "TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256, + "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, + "TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, +} + +// InsecureTLSCiphers returns the cipher suites implemented by crypto/tls which have +// security issues. +func InsecureTLSCiphers() map[string]uint16 { + cipherKeys := make(map[string]uint16, len(insecureCiphers)) + for k, v := range insecureCiphers { + cipherKeys[k] = v + } + return cipherKeys +} + +// InsecureTLSCipherNames returns a list of cipher suite names implemented by crypto/tls +// which have security issues. +func InsecureTLSCipherNames() []string { + cipherKeys := sets.NewString() + for key := range insecureCiphers { + cipherKeys.Insert(key) + } + return cipherKeys.List() +} + +// PreferredTLSCipherNames returns a list of cipher suite names implemented by crypto/tls. +func PreferredTLSCipherNames() []string { cipherKeys := sets.NewString() for key := range ciphers { cipherKeys.Insert(key) @@ -61,13 +87,37 @@ func TLSCipherPossibleValues() []string { return cipherKeys.List() } +func allCiphers() map[string]uint16 { + acceptedCiphers := make(map[string]uint16, len(ciphers)+len(insecureCiphers)) + for k, v := range ciphers { + acceptedCiphers[k] = v + } + for k, v := range insecureCiphers { + acceptedCiphers[k] = v + } + return acceptedCiphers +} + +// TLSCipherPossibleValues returns all acceptable cipher suite names. +// This is a combination of both InsecureTLSCipherNames() and PreferredTLSCipherNames(). +func TLSCipherPossibleValues() []string { + cipherKeys := sets.NewString() + acceptedCiphers := allCiphers() + for key := range acceptedCiphers { + cipherKeys.Insert(key) + } + return cipherKeys.List() +} + +// TLSCipherSuites returns a list of cipher suite IDs from the cipher suite names passed. func TLSCipherSuites(cipherNames []string) ([]uint16, error) { if len(cipherNames) == 0 { return nil, nil } ciphersIntSlice := make([]uint16, 0) + possibleCiphers := allCiphers() for _, cipher := range cipherNames { - intValue, ok := ciphers[cipher] + intValue, ok := possibleCiphers[cipher] if !ok { return nil, fmt.Errorf("Cipher suite %s not supported or doesn't exist", cipher) } @@ -83,6 +133,7 @@ var versions = map[string]uint16{ "VersionTLS13": tls.VersionTLS13, } +// TLSPossibleVersions returns all acceptable values for TLS Version. func TLSPossibleVersions() []string { versionsKeys := sets.NewString() for key := range versions { @@ -91,6 +142,7 @@ func TLSPossibleVersions() []string { return versionsKeys.List() } +// TLSVersion returns the TLS Version ID for the version name passed. func TLSVersion(versionName string) (uint16, error) { if len(versionName) == 0 { return DefaultTLSVersion(), nil @@ -101,6 +153,7 @@ func TLSVersion(versionName string) (uint16, error) { return 0, fmt.Errorf("unknown tls version %q", versionName) } +// DefaultTLSVersion defines the default TLS Version. 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 diff --git a/staging/src/k8s.io/component-base/cli/flag/ciphersuites_flag_test.go b/staging/src/k8s.io/component-base/cli/flag/ciphersuites_flag_test.go index f7fc7ab6c12..94963aaadd5 100644 --- a/staging/src/k8s.io/component-base/cli/flag/ciphersuites_flag_test.go +++ b/staging/src/k8s.io/component-base/cli/flag/ciphersuites_flag_test.go @@ -86,18 +86,18 @@ func TestConstantMaps(t *testing.T) { if strings.HasPrefix(declName, "VersionTLS") { discoveredVersions[declName] = true } - if strings.HasPrefix(declName, "TLS_RSA_") || strings.HasPrefix(declName, "TLS_ECDHE_") || - strings.HasPrefix(declName, "TLS_AES_") || strings.HasPrefix(declName, "TLS_CHACHA20_") { + if strings.HasPrefix(declName, "TLS_") && !strings.HasPrefix(declName, "TLS_FALLBACK_") { discoveredCiphers[declName] = true } } + acceptedCiphers := allCiphers() for k := range discoveredCiphers { - if _, ok := ciphers[k]; !ok { + if _, ok := acceptedCiphers[k]; !ok { t.Errorf("discovered cipher tls.%s not in ciphers map", k) } } - for k := range ciphers { + for k := range acceptedCiphers { if _, ok := discoveredCiphers[k]; !ok { t.Errorf("ciphers map has %s not in tls package", k) }