Skip to content

Commit

Permalink
allow configurable cipher suites
Browse files Browse the repository at this point in the history
disallow 3DES and RC4 ciphers

add documentation for tls_cipher_suites
  • Loading branch information
chelseakomlo committed May 8, 2018
1 parent f92d364 commit eb587d6
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 10 deletions.
9 changes: 7 additions & 2 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,11 +399,16 @@ func (c *Client) init() error {
func (c *Client) reloadTLSConnections(newConfig *nconfig.TLSConfig) error {
var tlsWrap tlsutil.RegionWrapper
if newConfig != nil && newConfig.EnableRPC {
tw, err := tlsutil.NewTLSConfiguration(newConfig).OutgoingTLSWrapper()
tw, err := tlsutil.NewTLSConfiguration(newConfig)
if err != nil {
return err
}
tlsWrap = tw

twWrap, err := tw.OutgoingTLSWrapper()
if err != nil {
return err
}
tlsWrap = twWrap
}

// Store the new tls wrapper.
Expand Down
6 changes: 6 additions & 0 deletions command/agent/config_parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/hashicorp/hcl"
"github.com/hashicorp/hcl/hcl/ast"
"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/helper/tlsutil"
"github.com/hashicorp/nomad/nomad/structs/config"
"github.com/mitchellh/mapstructure"
)
Expand Down Expand Up @@ -760,6 +761,7 @@ func parseTLSConfig(result **config.TLSConfig, list *ast.ObjectList) error {
"cert_file",
"key_file",
"verify_https_client",
"tls_cipher_suites",
}

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

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

*result = &tlsConfig
return nil
}
Expand Down
69 changes: 65 additions & 4 deletions helper/tlsutil/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"net"
"strings"
"time"

"github.com/hashicorp/nomad/nomad/structs/config"
Expand Down Expand Up @@ -65,9 +66,18 @@ type Config struct {

// KeyLoader dynamically reloads TLS configuration.
KeyLoader *config.KeyLoader

// CipherSuites have a default safe configuration, or operators can override
// these values for acceptable safe alternatives.
CipherSuites []uint16
}

func NewTLSConfiguration(newConf *config.TLSConfig) *Config {
func NewTLSConfiguration(newConf *config.TLSConfig) (*Config, error) {
ciphers, err := ParseCiphers(newConf.TLSCipherSuites)
if err != nil {
return nil, err
}

return &Config{
VerifyIncoming: true,
VerifyOutgoing: true,
Expand All @@ -76,7 +86,8 @@ func NewTLSConfiguration(newConf *config.TLSConfig) *Config {
CertFile: newConf.CertFile,
KeyFile: newConf.KeyFile,
KeyLoader: newConf.GetKeyLoader(),
}
CipherSuites: ciphers,
}, nil
}

// AppendCA opens and parses the CA file and adds the certificates to
Expand Down Expand Up @@ -132,6 +143,7 @@ func (c *Config) OutgoingTLSConfig() (*tls.Config, error) {
tlsConfig := &tls.Config{
RootCAs: x509.NewCertPool(),
InsecureSkipVerify: true,
CipherSuites: c.CipherSuites,
}
if c.VerifyServerHostname {
tlsConfig.InsecureSkipVerify = false
Expand Down Expand Up @@ -248,8 +260,9 @@ func WrapTLSClient(conn net.Conn, tlsConfig *tls.Config) (net.Conn, error) {
func (c *Config) IncomingTLSConfig() (*tls.Config, error) {
// Create the tlsConfig
tlsConfig := &tls.Config{
ClientCAs: x509.NewCertPool(),
ClientAuth: tls.NoClientCert,
ClientCAs: x509.NewCertPool(),
ClientAuth: tls.NoClientCert,
CipherSuites: c.CipherSuites,
}

// Parse the CA cert if any
Expand Down Expand Up @@ -279,3 +292,51 @@ func (c *Config) IncomingTLSConfig() (*tls.Config, error) {

return tlsConfig, nil
}

// ParseCiphers parse ciphersuites from the comma-separated string into recognized slice
func ParseCiphers(cipherStr string) ([]uint16, error) {
suites := []uint16{}

cipherStr = strings.TrimSpace(cipherStr)

var ciphers []string
if cipherStr == "" {
// Set good 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",
}

} 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)
}
}

return suites, nil
}
83 changes: 83 additions & 0 deletions helper/tlsutil/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ package tlsutil
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"io/ioutil"
"net"
"strings"
"testing"

"github.com/hashicorp/nomad/nomad/structs/config"
"github.com/hashicorp/yamux"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

const (
Expand Down Expand Up @@ -412,3 +415,83 @@ func TestConfig_IncomingTLS_NoVerify(t *testing.T) {
t.Fatalf("unexpected client cert")
}
}

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

validCiphers := strings.Join([]string{
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"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_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_128_CBC_SHA256",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA",
}, ",")

expectedCiphers := []uint16{
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
}

parsedCiphers, err := ParseCiphers(validCiphers)
require.Nil(err)
require.Equal(parsedCiphers, expectedCiphers)
}

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

expectedCiphers := []uint16{
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
}

parsedCiphers, err := ParseCiphers("")
require.Nil(err)
require.Equal(parsedCiphers, expectedCiphers)
}

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

invalidCiphers := []string{"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_RSA_WITH_RC4_128_SHA",
"TLS_ECDHE_RSA_WITH_RC4_128_SHA",
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
}

for _, cipher := range invalidCiphers {
parsedCiphers, err := ParseCiphers(cipher)
require.NotNil(err)
require.Equal(fmt.Sprintf("unsupported cipher %q", cipher), err.Error())
require.Equal(0, len(parsedCiphers))
}
}
6 changes: 5 additions & 1 deletion nomad/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,11 @@ func (s *Server) reloadTLSConnections(newTLSConfig *config.TLSConfig) error {
return fmt.Errorf("can't reload uninitialized RPC listener")
}

tlsConf := tlsutil.NewTLSConfiguration(newTLSConfig)
tlsConf, err := tlsutil.NewTLSConfiguration(newTLSConfig)
if err != nil {
return err
}

incomingTLS, tlsWrap, err := getTLSConf(newTLSConfig.EnableRPC, tlsConf)
if err != nil {
s.logger.Printf("[ERR] nomad: unable to reset TLS context %s", err)
Expand Down
4 changes: 4 additions & 0 deletions nomad/structs/config/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ type TLSConfig struct {
// Checksum is a MD5 hash of the certificate CA File, Certificate file, and
// key file.
Checksum string

// TLSCipherSuites are operator-defined ciphers to be used in Nomad TLS
// connections
TLSCipherSuites string `mapstructure:"tls_cipher_suites"`
}

type KeyLoader struct {
Expand Down
7 changes: 4 additions & 3 deletions nomad/structs/config/tls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,10 @@ func TestTLS_Copy(t *testing.T) {
fookey = "../../../helper/tlsutil/testdata/nomad-foo-key.pem"
)
a := &TLSConfig{
CAFile: cafile,
CertFile: foocert,
KeyFile: fookey,
CAFile: cafile,
CertFile: foocert,
KeyFile: fookey,
TLSCipherSuites: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
}
a.SetChecksum()

Expand Down
6 changes: 6 additions & 0 deletions website/source/docs/agent/configuration/tls.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ the [Agent's Gossip and RPC Encryption](/docs/agent/encryption.html).
cluster is being upgraded to TLS, and removed after the migration is
complete. This allows the agent to accept both TLS and plaintext traffic.

- `tls_cipher_suites` - Specifies the TLS cipher suites that will be used by
the agent. Known insecure ciphers are disabled (3DES and RC4). By default,
an agent is configured to use TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, and
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384.

- `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 eb587d6

Please sign in to comment.