From 08a8cccb0a7d04aed500a30bdc2d93f2d0df2512 Mon Sep 17 00:00:00 2001 From: Victor Garcia Date: Wed, 12 Jul 2017 23:49:41 -0700 Subject: [PATCH] Adding support for custom TLS ciphers in api server and kubelet Kubernetes-commit: d7dbc96c70d480f0b81cd83ae3abd34b69c1e70d --- pkg/server/options/serving.go | 16 ++++ pkg/util/flag/BUILD | 2 + pkg/util/flag/ciphersuites_flag.go | 64 +++++++++++++++ pkg/util/flag/ciphersuites_flag_test.go | 100 ++++++++++++++++++++++++ 4 files changed, 182 insertions(+) create mode 100644 pkg/util/flag/ciphersuites_flag.go create mode 100644 pkg/util/flag/ciphersuites_flag_test.go diff --git a/pkg/server/options/serving.go b/pkg/server/options/serving.go index 43042fde9..9606fa0cb 100644 --- a/pkg/server/options/serving.go +++ b/pkg/server/options/serving.go @@ -51,6 +51,9 @@ type SecureServingOptions struct { ServerCert GeneratableKeyCert // SNICertKeys are named CertKeys for serving secure traffic with SNI support. SNICertKeys []utilflag.NamedCertKey + // CipherSuites is the list of allowed cipher suites for the server. + // Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). + CipherSuites []string } type CertKey struct { @@ -134,6 +137,11 @@ func (s *SecureServingOptions) AddFlags(fs *pflag.FlagSet) { "Controllers. This must be a valid PEM-encoded CA bundle. Altneratively, the certificate authority "+ "can be appended to the certificate provided by --tls-cert-file.") + fs.StringSliceVar(&s.CipherSuites, "tls-cipher-suites", s.CipherSuites, + "Comma-separated list of cipher suites for the server. "+ + "Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). "+ + "If omitted, the default Go cipher suites will be used") + fs.Var(utilflag.NewNamedCertKeyArray(&s.SNICertKeys), "tls-sni-cert-key", ""+ "A pair of x509 certificate and private key file paths, optionally suffixed with a list of "+ "domain patterns which are fully qualified domain names, possibly with prefixed wildcard "+ @@ -233,6 +241,14 @@ func (s *SecureServingOptions) applyServingInfoTo(c *server.Config) error { } } + if len(s.CipherSuites) != 0 { + cipherSuites, err := utilflag.TLSCipherSuites(s.CipherSuites) + if err != nil { + return err + } + secureServingInfo.CipherSuites = cipherSuites + } + // load SNI certs namedTLSCerts := make([]server.NamedTLSCert, 0, len(s.SNICertKeys)) for _, nck := range s.SNICertKeys { diff --git a/pkg/util/flag/BUILD b/pkg/util/flag/BUILD index 7bf545631..25bf080aa 100644 --- a/pkg/util/flag/BUILD +++ b/pkg/util/flag/BUILD @@ -9,6 +9,7 @@ load( go_test( name = "go_default_test", srcs = [ + "ciphersuites_flag_test.go", "colon_separated_multimap_string_string_test.go", "langle_separated_map_string_string_test.go", "map_string_bool_test.go", @@ -23,6 +24,7 @@ go_test( go_library( name = "go_default_library", srcs = [ + "ciphersuites_flag.go", "colon_separated_multimap_string_string.go", "configuration_map.go", "flags.go", diff --git a/pkg/util/flag/ciphersuites_flag.go b/pkg/util/flag/ciphersuites_flag.go new file mode 100644 index 000000000..c2ce311e4 --- /dev/null +++ b/pkg/util/flag/ciphersuites_flag.go @@ -0,0 +1,64 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package flag + +import ( + "crypto/tls" + "fmt" +) + +// ciphers maps strings into tls package cipher constants in +// https://golang.org/pkg/crypto/tls/#pkg-constants +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, + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, +} + +func TLSCipherSuites(cipherNames []string) ([]uint16, error) { + if len(cipherNames) == 0 { + return nil, nil + } + ciphersIntSlice := make([]uint16, 0) + for _, cipher := range cipherNames { + intValue, ok := ciphers[cipher] + if !ok { + return nil, fmt.Errorf("Cipher suite %s not supported or doesn't exist", cipher) + } + ciphersIntSlice = append(ciphersIntSlice, intValue) + } + return ciphersIntSlice, nil +} diff --git a/pkg/util/flag/ciphersuites_flag_test.go b/pkg/util/flag/ciphersuites_flag_test.go new file mode 100644 index 000000000..b050238a3 --- /dev/null +++ b/pkg/util/flag/ciphersuites_flag_test.go @@ -0,0 +1,100 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package flag + +import ( + "crypto/tls" + "fmt" + "go/importer" + "reflect" + "strings" + "testing" +) + +func TestStrToUInt16(t *testing.T) { + tests := []struct { + flag []string + expected []uint16 + expected_error bool + }{ + { + // Happy case + flag: []string{"TLS_RSA_WITH_RC4_128_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_RC4_128_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"}, + expected: []uint16{tls.TLS_RSA_WITH_RC4_128_SHA, tls.TLS_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}, + expected_error: false, + }, + { + // One flag only + flag: []string{"TLS_RSA_WITH_RC4_128_SHA"}, + expected: []uint16{tls.TLS_RSA_WITH_RC4_128_SHA}, + expected_error: false, + }, + { + // Empty flag + flag: []string{}, + expected: nil, + expected_error: false, + }, + { + // Duplicated flag + flag: []string{"TLS_RSA_WITH_RC4_128_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_RC4_128_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_RC4_128_SHA"}, + expected: []uint16{tls.TLS_RSA_WITH_RC4_128_SHA, tls.TLS_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_RC4_128_SHA}, + expected_error: false, + }, + { + // Invalid flag + flag: []string{"foo"}, + expected: nil, + expected_error: true, + }, + } + + for i, test := range tests { + uIntFlags, err := TLSCipherSuites(test.flag) + if reflect.DeepEqual(uIntFlags, test.expected) == false { + t.Errorf("%d: expected %+v, got %+v", i, test.expected, uIntFlags) + } + if test.expected_error && err == nil { + t.Errorf("%d: expecting error, got %+v", i, err) + } + } +} + +func TestConstantMaps(t *testing.T) { + pkg, err := importer.Default().Import("crypto/tls") + if err != nil { + fmt.Printf("error: %s\n", err.Error()) + return + } + discoveredCiphers := map[string]bool{} + for _, declName := range pkg.Scope().Names() { + 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) + } + } +}