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

Add TLS cipher suite options and CA path support #2963

Merged
merged 3 commits into from
Apr 27, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions command/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,11 +453,14 @@ func (a *Agent) consulConfig() *consul.Config {
base.VerifyOutgoing = a.config.VerifyOutgoing
base.VerifyServerHostname = a.config.VerifyServerHostname
base.CAFile = a.config.CAFile
base.CAPath = a.config.CAPath
base.CertFile = a.config.CertFile
base.KeyFile = a.config.KeyFile
base.ServerName = a.config.ServerName
base.Domain = a.config.Domain
base.TLSMinVersion = a.config.TLSMinVersion
base.TLSCipherSuites = a.config.TLSCipherSuites
base.TLSPreferServerCipherSuites = a.config.TLSPreferServerCipherSuites

// Setup the ServerUp callback
base.ServerUp = a.state.ConsulServerUp
Expand Down
30 changes: 30 additions & 0 deletions command/agent/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/hashicorp/consul/types"
"github.com/hashicorp/consul/watch"
"github.com/mitchellh/mapstructure"
"github.com/hashicorp/consul/tlsutil"
Copy link
Contributor

Choose a reason for hiding this comment

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

Use goimports for sorting imports correctly

)

// Ports is used to simplify the configuration by
Expand Down Expand Up @@ -469,6 +470,10 @@ type Config struct {
// or VerifyOutgoing to verify the TLS connection.
CAFile string `mapstructure:"ca_file"`

// CAPath is a path to a directory of certificate authority files. This is used with
// VerifyIncoming or VerifyOutgoing to verify the TLS connection.
CAPath string `mapstructure:"ca_path"`

// CertFile is used to provide a TLS certificate that is used for serving TLS connections.
// Must be provided to serve TLS connections.
CertFile string `mapstructure:"cert_file"`
Expand All @@ -484,6 +489,14 @@ type Config struct {
// TLSMinVersion is used to set the minimum TLS version used for TLS connections.
TLSMinVersion string `mapstructure:"tls_min_version"`

// TLSCipherSuites is used to specify the list of supported ciphersuites.
TLSCipherSuites []uint16 `mapstructure:"-" json:"-"`
TLSCipherSuitesRaw string `mapstructure:"tls_cipher_suites"`

// TLSPreferServerCipherSuites specifies whether to prefer the server's ciphersuite
// over the client ciphersuites.
TLSPreferServerCipherSuites bool `mapstructure:"tls_prefer_server_cipher_suites"`

// StartJoin is a list of addresses to attempt to join when the
// agent starts. If Serf is unable to communicate with any of these
// addresses, then the agent will error and exit.
Expand Down Expand Up @@ -1178,6 +1191,14 @@ func DecodeConfig(r io.Reader) (*Config, error) {
return nil, fmt.Errorf("Performance.RaftMultiplier must be <= %d", consul.MaxRaftMultiplier)
}

if raw := result.TLSCipherSuitesRaw; raw != "" {
ciphers, err := tlsutil.ParseCiphers(raw)
if err != nil {
return nil, fmt.Errorf("TLSCipherSuites invalid: %v", err)
}
result.TLSCipherSuites = ciphers
}

return &result, nil
}

Expand Down Expand Up @@ -1517,6 +1538,9 @@ func MergeConfig(a, b *Config) *Config {
if b.CAFile != "" {
result.CAFile = b.CAFile
}
if b.CAPath != "" {
result.CAPath = b.CAPath
}
if b.CertFile != "" {
result.CertFile = b.CertFile
}
Expand All @@ -1529,6 +1553,12 @@ func MergeConfig(a, b *Config) *Config {
if b.TLSMinVersion != "" {
result.TLSMinVersion = b.TLSMinVersion
}
if len(b.TLSCipherSuites) != 0 {
result.TLSCipherSuites = append(result.TLSCipherSuites, b.TLSCipherSuites...)
}
if b.TLSPreferServerCipherSuites {
result.TLSPreferServerCipherSuites = true
}
if b.Checks != nil {
result.Checks = append(result.Checks, b.Checks...)
}
Expand Down
17 changes: 15 additions & 2 deletions command/agent/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"time"

Copy link
Contributor

Choose a reason for hiding this comment

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

remove blank line and goimports

"github.com/hashicorp/consul/lib"
"crypto/tls"
Copy link
Contributor

Choose a reason for hiding this comment

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

import sorting

)

func TestConfigEncryptBytes(t *testing.T) {
Expand Down Expand Up @@ -354,7 +355,8 @@ func TestDecodeConfig(t *testing.T) {
}

// TLS
input = `{"verify_incoming": true, "verify_outgoing": true, "verify_server_hostname": true, "tls_min_version": "tls12"}`
input = `{"verify_incoming": true, "verify_outgoing": true, "verify_server_hostname": true, "tls_min_version": "tls12",
"tls_cipher_suites": "TLS_RSA_WITH_AES_256_CBC_SHA", "tls_prefer_server_cipher_suites": true}`
config, err = DecodeConfig(bytes.NewReader([]byte(input)))
if err != nil {
t.Fatalf("err: %s", err)
Expand All @@ -376,8 +378,16 @@ func TestDecodeConfig(t *testing.T) {
t.Fatalf("bad: %#v", config)
}

if len(config.TLSCipherSuites) != 1 || config.TLSCipherSuites[0] != tls.TLS_RSA_WITH_AES_256_CBC_SHA {
t.Fatalf("bad: %#v", config)
}

if !config.TLSPreferServerCipherSuites {
t.Fatalf("bad: %#v", config)
}

// TLS keys
input = `{"ca_file": "my/ca/file", "cert_file": "my.cert", "key_file": "key.pem", "server_name": "example.com"}`
input = `{"ca_file": "my/ca/file", "ca_path":"my/ca/path", "cert_file": "my.cert", "key_file": "key.pem", "server_name": "example.com"}`
config, err = DecodeConfig(bytes.NewReader([]byte(input)))
if err != nil {
t.Fatalf("err: %s", err)
Expand All @@ -386,6 +396,9 @@ func TestDecodeConfig(t *testing.T) {
if config.CAFile != "my/ca/file" {
t.Fatalf("bad: %#v", config)
}
if config.CAPath != "my/ca/path" {
t.Fatalf("bad: %#v", config)
}
if config.CertFile != "my.cert" {
t.Fatalf("bad: %#v", config)
}
Expand Down
19 changes: 11 additions & 8 deletions command/agent/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,17 @@ func NewHTTPServers(agent *Agent, config *Config, logOutput io.Writer) ([]*HTTPS
}

tlsConf := &tlsutil.Config{
VerifyIncoming: config.VerifyIncoming,
VerifyOutgoing: config.VerifyOutgoing,
CAFile: config.CAFile,
CertFile: config.CertFile,
KeyFile: config.KeyFile,
NodeName: config.NodeName,
ServerName: config.ServerName,
TLSMinVersion: config.TLSMinVersion,
VerifyIncoming: config.VerifyIncoming,
VerifyOutgoing: config.VerifyOutgoing,
CAFile: config.CAFile,
CAPath: config.CAPath,
CertFile: config.CertFile,
KeyFile: config.KeyFile,
NodeName: config.NodeName,
ServerName: config.ServerName,
TLSMinVersion: config.TLSMinVersion,
CipherSuites: config.TLSCipherSuites,
PreferServerCipherSuites: config.TLSPreferServerCipherSuites,
}

tlsConfig, err := tlsConf.IncomingTLSConfig()
Expand Down
33 changes: 23 additions & 10 deletions consul/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ type Config struct {
// or VerifyOutgoing to verify the TLS connection.
CAFile string

// CAPath is a path to a directory of certificate authority files. This is used with
// VerifyIncoming or VerifyOutgoing to verify the TLS connection.
CAPath string

// CertFile is used to provide a TLS certificate that is used for serving TLS connections.
// Must be provided to serve TLS connections.
CertFile string
Expand All @@ -157,6 +161,13 @@ type Config struct {
// TLSMinVersion is used to set the minimum TLS version used for TLS connections.
TLSMinVersion string

// TLSCipherSuites is used to specify the list of supported ciphersuites.
TLSCipherSuites []uint16

// TLSPreferServerCipherSuites specifies whether to prefer the server's ciphersuite
// over the client ciphersuites.
TLSPreferServerCipherSuites bool

// RejoinAfterLeave controls our interaction with Serf.
// When set to false (default), a leave causes a Consul to not rejoin
// the cluster until an explicit join is received. If this is set to
Expand Down Expand Up @@ -421,16 +432,18 @@ func (c *Config) ScaleRaft(raftMultRaw uint) {
// tlsConfig maps this config into a tlsutil config.
func (c *Config) tlsConfig() *tlsutil.Config {
tlsConf := &tlsutil.Config{
VerifyIncoming: c.VerifyIncoming,
VerifyOutgoing: c.VerifyOutgoing,
VerifyServerHostname: c.VerifyServerHostname,
CAFile: c.CAFile,
CertFile: c.CertFile,
KeyFile: c.KeyFile,
NodeName: c.NodeName,
ServerName: c.ServerName,
Domain: c.Domain,
TLSMinVersion: c.TLSMinVersion,
VerifyIncoming: c.VerifyIncoming,
VerifyOutgoing: c.VerifyOutgoing,
VerifyServerHostname: c.VerifyServerHostname,
CAFile: c.CAFile,
CAPath: c.CAPath,
CertFile: c.CertFile,
KeyFile: c.KeyFile,
NodeName: c.NodeName,
ServerName: c.ServerName,
Domain: c.Domain,
TLSMinVersion: c.TLSMinVersion,
PreferServerCipherSuites: c.TLSPreferServerCipherSuites,
}
return tlsConf
}
Expand Down
97 changes: 88 additions & 9 deletions tlsutil/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"net"
"strings"
"time"

"github.com/hashicorp/go-rootcerts"
)

// DCWrapper is a function that is used to wrap a non-TLS connection
Expand Down Expand Up @@ -51,6 +53,10 @@ type Config struct {
// or VerifyOutgoing to verify the TLS connection.
CAFile string

// CAPath is a path to a directory containing certificate authority files. This is used
// with VerifyIncoming or VerifyOutgoing to verify the TLS connection.
CAPath string

// CertFile is used to provide a TLS certificate that is used for serving TLS connections.
// Must be provided to serve TLS connections.
CertFile string
Expand All @@ -71,6 +77,13 @@ type Config struct {

// TLSMinVersion is the minimum accepted TLS version that can be used.
TLSMinVersion string

// CipherSuites is the list of TLS cipher suites to use.
CipherSuites []uint16

// PreferServerCipherSuites specifies whether to prefer the server's ciphersuite
// over the client ciphersuites.
PreferServerCipherSuites bool
}

// AppendCA opens and parses the CA file and adds the certificates to
Expand Down Expand Up @@ -130,15 +143,24 @@ func (c *Config) OutgoingTLSConfig() (*tls.Config, error) {
tlsConfig.ServerName = "VerifyServerHostname"
tlsConfig.InsecureSkipVerify = false
}
if len(c.CipherSuites) != 0 {
tlsConfig.CipherSuites = c.CipherSuites
}
if c.PreferServerCipherSuites {
tlsConfig.PreferServerCipherSuites = true
}

// Ensure we have a CA if VerifyOutgoing is set
if c.VerifyOutgoing && c.CAFile == "" {
if c.VerifyOutgoing && c.CAFile == "" && c.CAPath == "" {
return nil, fmt.Errorf("VerifyOutgoing set, and no CA certificate provided!")
}

// Parse the CA cert if any
err := c.AppendCA(tlsConfig.RootCAs)
if err != nil {
// Parse the CA certs if any
rootConfig := &rootcerts.Config{
CAFile: c.CAFile,
CAPath: c.CAPath,
}
if err := rootcerts.ConfigureTLS(tlsConfig, rootConfig); err != nil {
return nil, err
}

Expand Down Expand Up @@ -305,10 +327,27 @@ func (c *Config) IncomingTLSConfig() (*tls.Config, error) {
tlsConfig.ServerName = c.NodeName
}

// Parse the CA cert if any
err := c.AppendCA(tlsConfig.ClientCAs)
if err != nil {
return nil, err
// Set the cipher suites
if len(c.CipherSuites) != 0 {
tlsConfig.CipherSuites = c.CipherSuites
}
if c.PreferServerCipherSuites {
tlsConfig.PreferServerCipherSuites = true
}

// Parse the CA certs if any
if c.CAFile != "" {
pool, err := rootcerts.LoadCAFile(c.CAFile)
if err != nil {
return nil, err
}
tlsConfig.ClientCAs = pool
} else if c.CAPath != "" {
pool, err := rootcerts.LoadCAPath(c.CAPath)
if err != nil {
return nil, err
}
tlsConfig.ClientCAs = pool
}

// Add cert/key
Expand All @@ -322,7 +361,7 @@ func (c *Config) IncomingTLSConfig() (*tls.Config, error) {
// Check if we require verification
if c.VerifyIncoming {
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
if c.CAFile == "" {
if c.CAFile == "" && c.CAPath == "" {
return nil, fmt.Errorf("VerifyIncoming set, and no CA certificate provided!")
}
if cert == nil {
Expand All @@ -340,3 +379,43 @@ 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)
if cipherStr == "" {
return []uint16{}, nil
}
ciphers := strings.Split(cipherStr, ",")

cipherMap := 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,
}
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
}
Copy link
Contributor

Choose a reason for hiding this comment

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

gofmt this file

Loading