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