diff --git a/command/agent/agent.go b/command/agent/agent.go index 6f31717827..f33cf34536 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -429,6 +429,7 @@ func (a *Agent) consulConfig() *consul.Config { base.KeyFile = a.config.KeyFile base.ServerName = a.config.ServerName base.Domain = a.config.Domain + base.TLSMinVersion = a.config.TLSMinVersion // Setup the ServerUp callback base.ServerUp = a.state.ConsulServerUp diff --git a/command/agent/config.go b/command/agent/config.go index 4ea3c1fbca..ac72e023d9 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -429,6 +429,9 @@ type Config struct { // provide matches the certificate ServerName string `mapstructure:"server_name"` + // TLSMinVersion is used to set the minimum TLS version used for TLS connections. + TLSMinVersion string `mapstructure:"tls_min_version"` + // 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. @@ -771,6 +774,8 @@ func DefaultConfig() *Config { ACLEnforceVersion8: Bool(false), RetryInterval: 30 * time.Second, RetryIntervalWan: 30 * time.Second, + + TLSMinVersion: "tls10", } } @@ -1407,6 +1412,9 @@ func MergeConfig(a, b *Config) *Config { if b.ServerName != "" { result.ServerName = b.ServerName } + if b.TLSMinVersion != "" { + result.TLSMinVersion = b.TLSMinVersion + } if b.Checks != nil { result.Checks = append(result.Checks, b.Checks...) } diff --git a/command/agent/config_test.go b/command/agent/config_test.go index efa9165db5..4c9addc484 100644 --- a/command/agent/config_test.go +++ b/command/agent/config_test.go @@ -332,7 +332,7 @@ func TestDecodeConfig(t *testing.T) { } // TLS - input = `{"verify_incoming": true, "verify_outgoing": true, "verify_server_hostname": true}` + input = `{"verify_incoming": true, "verify_outgoing": true, "verify_server_hostname": true, "tls_min_version": "tls12"}` config, err = DecodeConfig(bytes.NewReader([]byte(input))) if err != nil { t.Fatalf("err: %s", err) @@ -350,6 +350,10 @@ func TestDecodeConfig(t *testing.T) { t.Fatalf("bad: %#v", config) } + if config.TLSMinVersion != "tls12" { + 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"}` config, err = DecodeConfig(bytes.NewReader([]byte(input))) @@ -1621,6 +1625,7 @@ func TestMergeConfig(t *testing.T) { CAFile: "test/ca.pem", CertFile: "test/cert.pem", KeyFile: "test/key.pem", + TLSMinVersion: "tls12", Checks: []*CheckDefinition{nil}, Services: []*ServiceDefinition{nil}, StartJoin: []string{"1.1.1.1"}, diff --git a/command/agent/http.go b/command/agent/http.go index b228b9c0e3..dc70693690 100644 --- a/command/agent/http.go +++ b/command/agent/http.go @@ -62,7 +62,9 @@ func NewHTTPServers(agent *Agent, config *Config, logOutput io.Writer) ([]*HTTPS CertFile: config.CertFile, KeyFile: config.KeyFile, NodeName: config.NodeName, - ServerName: config.ServerName} + ServerName: config.ServerName, + TLSMinVersion: config.TLSMinVersion, + } tlsConfig, err := tlsConf.IncomingTLSConfig() if err != nil { diff --git a/consul/config.go b/consul/config.go index ae8e1e3155..a11f73f454 100644 --- a/consul/config.go +++ b/consul/config.go @@ -144,6 +144,9 @@ type Config struct { // provide matches the certificate ServerName string + // TLSMinVersion is used to set the minimum TLS version used for TLS connections. + TLSMinVersion string + // 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 @@ -341,6 +344,8 @@ func DefaultConfig() *Config { // bit longer to try to cover that period. This should be more // than enough when running in the high performance mode. RPCHoldTimeout: 7 * time.Second, + + TLSMinVersion: "tls10", } // Increase our reap interval to 3 days instead of 24h. @@ -394,6 +399,7 @@ func (c *Config) tlsConfig() *tlsutil.Config { NodeName: c.NodeName, ServerName: c.ServerName, Domain: c.Domain, + TLSMinVersion: c.TLSMinVersion, } return tlsConf } diff --git a/tlsutil/config.go b/tlsutil/config.go index 57c9867483..ad9ee088ea 100644 --- a/tlsutil/config.go +++ b/tlsutil/config.go @@ -19,6 +19,13 @@ type DCWrapper func(dc string, conn net.Conn) (net.Conn, error) // a constant value. This is usually done by currying DCWrapper. type Wrapper func(conn net.Conn) (net.Conn, error) +// TLSLookup maps the tls_min_version configuration to the internal value +var TLSLookup = map[string]uint16{ + "tls10": tls.VersionTLS10, + "tls11": tls.VersionTLS11, + "tls12": tls.VersionTLS12, +} + // Config used to create tls.Config type Config struct { // VerifyIncoming is used to verify the authenticity of incoming connections. @@ -61,6 +68,9 @@ type Config struct { // Domain is the Consul TLD being used. Defaults to "consul." Domain string + + // TLSMinVersion is the minimum accepted TLS version that can be used. + TLSMinVersion string } // AppendCA opens and parses the CA file and adds the certificates to @@ -140,6 +150,15 @@ func (c *Config) OutgoingTLSConfig() (*tls.Config, error) { tlsConfig.Certificates = []tls.Certificate{*cert} } + // Check if a minimum TLS version was set + if c.TLSMinVersion != "" { + tlsvers, ok := TLSLookup[c.TLSMinVersion] + if !ok { + return nil, fmt.Errorf("TLSMinVersion: value %s not supported, please specify one of [tls10,tls11,tls12]", c.TLSMinVersion) + } + tlsConfig.MinVersion = tlsvers + } + return tlsConfig, nil } @@ -310,5 +329,14 @@ func (c *Config) IncomingTLSConfig() (*tls.Config, error) { return nil, fmt.Errorf("VerifyIncoming set, and no Cert/Key pair provided!") } } + + // Check if a minimum TLS version was set + if c.TLSMinVersion != "" { + tlsvers, ok := TLSLookup[c.TLSMinVersion] + if !ok { + return nil, fmt.Errorf("TLSMinVersion: value %s not supported, please specify one of [tls10,tls11,tls12]", c.TLSMinVersion) + } + tlsConfig.MinVersion = tlsvers + } return tlsConfig, nil } diff --git a/tlsutil/config_test.go b/tlsutil/config_test.go index 2bf413672b..5d9af0ffca 100644 --- a/tlsutil/config_test.go +++ b/tlsutil/config_test.go @@ -183,6 +183,27 @@ func TestConfig_OutgoingTLS_WithKeyPair(t *testing.T) { } } +func TestConfig_OutgoingTLS_TLSMinVersion(t *testing.T) { + tlsVersions := []string{"tls10", "tls11", "tls12"} + for _, version := range tlsVersions { + conf := &Config{ + VerifyOutgoing: true, + CAFile: "../test/ca/root.cer", + TLSMinVersion: version, + } + tls, err := conf.OutgoingTLSConfig() + if err != nil { + t.Fatalf("err: %v", err) + } + if tls == nil { + t.Fatalf("expected config") + } + if tls.MinVersion != TLSLookup[version] { + t.Fatalf("expected tls min version: %v, %v", tls.MinVersion, TLSLookup[version]) + } + } +} + func startTLSServer(config *Config) (net.Conn, chan error) { errc := make(chan error, 1) @@ -450,3 +471,26 @@ func TestConfig_IncomingTLS_NoVerify(t *testing.T) { t.Fatalf("unexpected client cert") } } + +func TestConfig_IncomingTLS_TLSMinVersion(t *testing.T) { + tlsVersions := []string{"tls10", "tls11", "tls12"} + for _, version := range tlsVersions { + conf := &Config{ + VerifyIncoming: true, + CAFile: "../test/ca/root.cer", + CertFile: "../test/key/ourdomain.cer", + KeyFile: "../test/key/ourdomain.key", + TLSMinVersion: version, + } + tls, err := conf.IncomingTLSConfig() + if err != nil { + t.Fatalf("err: %v", err) + } + if tls == nil { + t.Fatalf("expected config") + } + if tls.MinVersion != TLSLookup[version] { + t.Fatalf("expected tls min version: %v, %v", tls.MinVersion, TLSLookup[version]) + } + } +} diff --git a/website/source/docs/agent/options.html.markdown b/website/source/docs/agent/options.html.markdown index cf475c8301..153fc7f4db 100644 --- a/website/source/docs/agent/options.html.markdown +++ b/website/source/docs/agent/options.html.markdown @@ -958,6 +958,11 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass [`enable_syslog`](#enable_syslog) is provided, this controls to which facility messages are sent. By default, `LOCAL0` will be used. +* `tls_min_version` Added in Consul + 0.7.4, this specifies the minimum supported version of TLS. Accepted values are "tls10", "tls11" + or "tls12". This defaults to "tls10". WARNING: TLS 1.1 and lower are generally considered less + secure; avoid using these if possible. This will be changed to default to "tls12" in Consul 0.8.0. + * `translate_wan_addrs` If set to true, Consul will prefer a node's configured WAN address when servicing DNS and HTTP requests for a node in a remote datacenter. This allows the node to