Skip to content

Commit c1b56aa

Browse files
committed
add support for configurable TLS minimum version
1 parent 0f46208 commit c1b56aa

File tree

5 files changed

+115
-29
lines changed

5 files changed

+115
-29
lines changed

command/agent/config_parse.go

+5
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,7 @@ func parseTLSConfig(result **config.TLSConfig, list *ast.ObjectList) error {
762762
"key_file",
763763
"verify_https_client",
764764
"tls_cipher_suites",
765+
"tls_min_version",
765766
}
766767

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

786+
if _, err := tlsutil.ParseMinVersion(tlsConfig.TLSMinVersion); err != nil {
787+
return err
788+
}
789+
785790
*result = &tlsConfig
786791
return nil
787792
}

helper/tlsutil/config.go

+64-29
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,40 @@ import (
1212
"github.com/hashicorp/nomad/nomad/structs/config"
1313
)
1414

15+
// supportedTLSVersions are the current TLS versions that Nomad supports
16+
var supportedTLSVersions = map[string]uint16{
17+
"tls10": tls.VersionTLS10,
18+
"tls11": tls.VersionTLS11,
19+
"tls12": tls.VersionTLS12,
20+
}
21+
22+
// supportedTLSCiphers are the complete list of TLS ciphers supported by Nomad
23+
var supportedTLSCiphers = map[string]uint16{
24+
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
25+
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
26+
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
27+
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
28+
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
29+
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
30+
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
31+
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
32+
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
33+
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
34+
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
35+
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
36+
"TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
37+
"TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
38+
"TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
39+
"TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
40+
"TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
41+
}
42+
43+
// defaultTLSCiphers are the TLS Ciphers that are supported by default
44+
var defaultTLSCiphers = []string{"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
45+
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
46+
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
47+
}
48+
1549
// RegionSpecificWrapper is used to invoke a static Region and turns a
1650
// RegionWrapper into a Wrapper type.
1751
func RegionSpecificWrapper(region string, tlsWrap RegionWrapper) Wrapper {
@@ -70,6 +104,9 @@ type Config struct {
70104
// CipherSuites have a default safe configuration, or operators can override
71105
// these values for acceptable safe alternatives.
72106
CipherSuites []uint16
107+
108+
// MinVersion contains the minimum SSL/TLS version that is accepted.
109+
MinVersion uint16
73110
}
74111

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

118+
minVersion, err := ParseMinVersion(newConf.TLSMinVersion)
119+
if err != nil {
120+
return nil, err
121+
}
122+
81123
return &Config{
82124
VerifyIncoming: true,
83125
VerifyOutgoing: true,
@@ -87,6 +129,7 @@ func NewTLSConfiguration(newConf *config.TLSConfig) (*Config, error) {
87129
KeyFile: newConf.KeyFile,
88130
KeyLoader: newConf.GetKeyLoader(),
89131
CipherSuites: ciphers,
132+
MinVersion: minVersion,
90133
}, nil
91134
}
92135

@@ -144,6 +187,7 @@ func (c *Config) OutgoingTLSConfig() (*tls.Config, error) {
144187
RootCAs: x509.NewCertPool(),
145188
InsecureSkipVerify: true,
146189
CipherSuites: c.CipherSuites,
190+
MinVersion: c.MinVersion,
147191
}
148192
if c.VerifyServerHostname {
149193
tlsConfig.InsecureSkipVerify = false
@@ -263,6 +307,7 @@ func (c *Config) IncomingTLSConfig() (*tls.Config, error) {
263307
ClientCAs: x509.NewCertPool(),
264308
ClientAuth: tls.NoClientCert,
265309
CipherSuites: c.CipherSuites,
310+
MinVersion: c.MinVersion,
266311
}
267312

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

303348
var ciphers []string
304349
if cipherStr == "" {
305-
// Set strong default values
306-
ciphers = []string{"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
307-
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
308-
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
309-
}
350+
ciphers = defaultTLSCiphers
310351

311352
} else {
312353
ciphers = strings.Split(cipherStr, ",")
313354
}
314-
315-
cipherMap := map[string]uint16{
316-
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
317-
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
318-
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
319-
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
320-
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
321-
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
322-
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
323-
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
324-
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
325-
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
326-
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
327-
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
328-
"TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
329-
"TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
330-
"TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
331-
"TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
332-
"TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
333-
}
334355
for _, cipher := range ciphers {
335-
if v, ok := cipherMap[cipher]; ok {
336-
suites = append(suites, v)
337-
} else {
338-
return suites, fmt.Errorf("unsupported cipher %q", cipher)
356+
c, ok := supportedTLSCiphers[cipher]
357+
if !ok {
358+
return suites, fmt.Errorf("unsupported TLS cipher %q", cipher)
339359
}
360+
suites = append(suites, c)
340361
}
341362

342363
return suites, nil
343364
}
365+
366+
// ParseMinVersion parses the specified minimum TLS version for the Nomad agent
367+
func ParseMinVersion(version string) (uint16, error) {
368+
if version == "" {
369+
return supportedTLSVersions["tls12"], nil
370+
}
371+
372+
vers, ok := supportedTLSVersions[version]
373+
if !ok {
374+
return 0, fmt.Errorf("unsupported TLS version %q", version)
375+
}
376+
377+
return vers, nil
378+
}

helper/tlsutil/config_test.go

+36
Original file line numberDiff line numberDiff line change
@@ -495,3 +495,39 @@ func TestConfig_ParseCiphers_Invalid(t *testing.T) {
495495
require.Equal(0, len(parsedCiphers))
496496
}
497497
}
498+
499+
func TestConfig_ParseMinVersion_Valid(t *testing.T) {
500+
require := require.New(t)
501+
502+
validVersions := []string{"tls10",
503+
"tls11",
504+
"tls12",
505+
}
506+
507+
expected := map[string]uint16{
508+
"tls10": tls.VersionTLS10,
509+
"tls11": tls.VersionTLS11,
510+
"tls12": tls.VersionTLS12,
511+
}
512+
513+
for _, version := range validVersions {
514+
parsedVersion, err := ParseMinVersion(version)
515+
require.Nil(err)
516+
require.Equal(expected[version], parsedVersion)
517+
}
518+
}
519+
520+
func TestConfig_ParseMinVersion_Invalid(t *testing.T) {
521+
require := require.New(t)
522+
523+
invalidVersions := []string{"tls13",
524+
"tls15",
525+
}
526+
527+
for _, version := range invalidVersions {
528+
parsedVersion, err := ParseMinVersion(version)
529+
require.NotNil(err)
530+
require.Equal(fmt.Sprintf("unsupported TLS version %q", version), err.Error())
531+
require.Equal(uint16(0), parsedVersion)
532+
}
533+
}

nomad/structs/config/tls.go

+7
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ type TLSConfig struct {
5959
// TLSCipherSuites are operator-defined ciphers to be used in Nomad TLS
6060
// connections
6161
TLSCipherSuites string `mapstructure:"tls_cipher_suites"`
62+
63+
// TLSMinVersion is used to set the minimum TLS version used for TLS
64+
// connections. Should be either "tls10", "tls11", or "tls12".
65+
TLSMinVersion string `mapstructure:"tls_min_version"`
6266
}
6367

6468
type KeyLoader struct {
@@ -151,6 +155,9 @@ func (t *TLSConfig) Copy() *TLSConfig {
151155
new.RPCUpgradeMode = t.RPCUpgradeMode
152156
new.VerifyHTTPSClient = t.VerifyHTTPSClient
153157

158+
new.TLSCipherSuites = t.TLSCipherSuites
159+
new.TLSMinVersion = t.TLSMinVersion
160+
154161
new.SetChecksum()
155162

156163
return new

website/source/docs/agent/configuration/tls.html.md

+3
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ the [Agent's Gossip and RPC Encryption](/docs/agent/encryption.html).
6464
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, and
6565
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384.
6666

67+
- `tls_min_version` - Specifies the minimum supported version of TLS. Accepted
68+
values are "tls10", "tls11", "tls12". Defaults to TLS 1.2.
69+
6770
- `verify_https_client` `(bool: false)` - Specifies agents should require
6871
client certificates for all incoming HTTPS requests. The client certificates
6972
must be signed by the same CA as Nomad.

0 commit comments

Comments
 (0)