Skip to content

Commit

Permalink
add support for configurable TLS minimum version
Browse files Browse the repository at this point in the history
  • Loading branch information
chelseakomlo committed May 9, 2018
1 parent 0f46208 commit 509180e
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 30 deletions.
5 changes: 5 additions & 0 deletions command/agent/config_parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,7 @@ func parseTLSConfig(result **config.TLSConfig, list *ast.ObjectList) error {
"key_file",
"verify_https_client",
"tls_cipher_suites",
"tls_min_version",
}

if err := helper.CheckHCLKeys(listVal, valid); err != nil {
Expand All @@ -782,6 +783,10 @@ func parseTLSConfig(result **config.TLSConfig, list *ast.ObjectList) error {
return err
}

if _, err := tlsutil.ParseMinVersion(tlsConfig.TLSMinVersion); err != nil {
return err
}

*result = &tlsConfig
return nil
}
Expand Down
93 changes: 64 additions & 29 deletions helper/tlsutil/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,40 @@ import (
"github.com/hashicorp/nomad/nomad/structs/config"
)

// supportedTLSVersions are the current TLS versions that Nomad supports
var supportedTLSVersions = map[string]uint16{
"tls10": tls.VersionTLS10,
"tls11": tls.VersionTLS11,
"tls12": tls.VersionTLS12,
}

// supportedTLSCiphers are the complete list of TLS ciphers supported by Nomad
var supportedTLSCiphers = map[string]uint16{
"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,
"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_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_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_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_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_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
"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,
}

// defaultTLSCiphers are the TLS Ciphers that are supported by default
var defaultTLSCiphers = []string{"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
}

// RegionSpecificWrapper is used to invoke a static Region and turns a
// RegionWrapper into a Wrapper type.
func RegionSpecificWrapper(region string, tlsWrap RegionWrapper) Wrapper {
Expand Down Expand Up @@ -70,6 +104,9 @@ type Config struct {
// CipherSuites have a default safe configuration, or operators can override
// these values for acceptable safe alternatives.
CipherSuites []uint16

// MinVersion contains the minimum SSL/TLS version that is accepted.
MinVersion uint16
}

func NewTLSConfiguration(newConf *config.TLSConfig) (*Config, error) {
Expand All @@ -78,6 +115,11 @@ func NewTLSConfiguration(newConf *config.TLSConfig) (*Config, error) {
return nil, err
}

minVersion, err := ParseMinVersion(newConf.TLSMinVersion)
if err != nil {
return nil, err
}

return &Config{
VerifyIncoming: true,
VerifyOutgoing: true,
Expand All @@ -87,6 +129,7 @@ func NewTLSConfiguration(newConf *config.TLSConfig) (*Config, error) {
KeyFile: newConf.KeyFile,
KeyLoader: newConf.GetKeyLoader(),
CipherSuites: ciphers,
MinVersion: minVersion,
}, nil
}

Expand Down Expand Up @@ -144,6 +187,7 @@ func (c *Config) OutgoingTLSConfig() (*tls.Config, error) {
RootCAs: x509.NewCertPool(),
InsecureSkipVerify: true,
CipherSuites: c.CipherSuites,
MinVersion: c.MinVersion,
}
if c.VerifyServerHostname {
tlsConfig.InsecureSkipVerify = false
Expand Down Expand Up @@ -263,6 +307,7 @@ func (c *Config) IncomingTLSConfig() (*tls.Config, error) {
ClientCAs: x509.NewCertPool(),
ClientAuth: tls.NoClientCert,
CipherSuites: c.CipherSuites,
MinVersion: c.MinVersion,
}

// Parse the CA cert if any
Expand Down Expand Up @@ -302,42 +347,32 @@ func ParseCiphers(cipherStr string) ([]uint16, error) {

var ciphers []string
if cipherStr == "" {
// Set strong default values
ciphers = []string{"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
}
ciphers = defaultTLSCiphers

} else {
ciphers = strings.Split(cipherStr, ",")
}

cipherMap := map[string]uint16{
"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,
"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_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_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_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_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_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
"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,
}
for _, cipher := range ciphers {
if v, ok := cipherMap[cipher]; ok {
suites = append(suites, v)
} else {
return suites, fmt.Errorf("unsupported cipher %q", cipher)
c, ok := supportedTLSCiphers[cipher]
if !ok {
return suites, fmt.Errorf("unsupported TLS cipher %q", cipher)
}
suites = append(suites, c)
}

return suites, nil
}

// ParseMinVersion parses the specified minimum TLS version for the Nomad agent
func ParseMinVersion(version string) (uint16, error) {
if version == "" {
return supportedTLSVersions["tls12"], nil
}

vers, ok := supportedTLSVersions[version]
if !ok {
return 0, fmt.Errorf("unsupported TLS version %q", version)
}

return vers, nil
}
38 changes: 37 additions & 1 deletion helper/tlsutil/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,43 @@ func TestConfig_ParseCiphers_Invalid(t *testing.T) {
for _, cipher := range invalidCiphers {
parsedCiphers, err := ParseCiphers(cipher)
require.NotNil(err)
require.Equal(fmt.Sprintf("unsupported cipher %q", cipher), err.Error())
require.Equal(fmt.Sprintf("unsupported TLS cipher %q", cipher), err.Error())
require.Equal(0, len(parsedCiphers))
}
}

func TestConfig_ParseMinVersion_Valid(t *testing.T) {
require := require.New(t)

validVersions := []string{"tls10",
"tls11",
"tls12",
}

expected := map[string]uint16{
"tls10": tls.VersionTLS10,
"tls11": tls.VersionTLS11,
"tls12": tls.VersionTLS12,
}

for _, version := range validVersions {
parsedVersion, err := ParseMinVersion(version)
require.Nil(err)
require.Equal(expected[version], parsedVersion)
}
}

func TestConfig_ParseMinVersion_Invalid(t *testing.T) {
require := require.New(t)

invalidVersions := []string{"tls13",
"tls15",
}

for _, version := range invalidVersions {
parsedVersion, err := ParseMinVersion(version)
require.NotNil(err)
require.Equal(fmt.Sprintf("unsupported TLS version %q", version), err.Error())
require.Equal(uint16(0), parsedVersion)
}
}
7 changes: 7 additions & 0 deletions nomad/structs/config/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ type TLSConfig struct {
// TLSCipherSuites are operator-defined ciphers to be used in Nomad TLS
// connections
TLSCipherSuites string `mapstructure:"tls_cipher_suites"`

// TLSMinVersion is used to set the minimum TLS version used for TLS
// connections. Should be either "tls10", "tls11", or "tls12".
TLSMinVersion string `mapstructure:"tls_min_version"`
}

type KeyLoader struct {
Expand Down Expand Up @@ -151,6 +155,9 @@ func (t *TLSConfig) Copy() *TLSConfig {
new.RPCUpgradeMode = t.RPCUpgradeMode
new.VerifyHTTPSClient = t.VerifyHTTPSClient

new.TLSCipherSuites = t.TLSCipherSuites
new.TLSMinVersion = t.TLSMinVersion

new.SetChecksum()

return new
Expand Down
3 changes: 3 additions & 0 deletions website/source/docs/agent/configuration/tls.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ the [Agent's Gossip and RPC Encryption](/docs/agent/encryption.html).
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, and
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384.

- `tls_min_version` - Specifies the minimum supported version of TLS. Accepted
values are "tls10", "tls11", "tls12". Defaults to TLS 1.2.

- `verify_https_client` `(bool: false)` - Specifies agents should require
client certificates for all incoming HTTPS requests. The client certificates
must be signed by the same CA as Nomad.
Expand Down

0 comments on commit 509180e

Please sign in to comment.