Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1.5] Plumb and allow customizing ciphers and tls version #13198

Merged
merged 2 commits into from
Mar 3, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pkg/cmd/server/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions pkg/cmd/server/api/v1/swagger_doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions pkg/cmd/server/api/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions pkg/cmd/server/api/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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
}

Expand Down
186 changes: 136 additions & 50 deletions pkg/cmd/server/crypto/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
42 changes: 42 additions & 0 deletions pkg/cmd/server/crypto/crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
3 changes: 3 additions & 0 deletions pkg/cmd/server/kubernetes/master_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you dropped a line here

genericConfig.SecureServingInfo.MinTLSVersion = crypto.TLSVersionOrDie(options.ServingInfo.MinTLSVersion)

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)
Expand Down
2 changes: 2 additions & 0 deletions pkg/cmd/server/kubernetes/node_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions pkg/cmd/server/origin/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 2 additions & 0 deletions pkg/cmd/server/origin/master.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading