Add option to set cluster TLS cipher suites. (#3228)

* Add option to set cluster TLS cipher suites.

Fixes #3227
This commit is contained in:
Jeff Mitchell 2017-08-30 16:28:23 -04:00 committed by GitHub
parent d31ce49771
commit 3edb337a00
10 changed files with 76 additions and 8 deletions

View File

@ -42,7 +42,9 @@ type Config struct {
DefaultLeaseTTL time.Duration `hcl:"-"`
DefaultLeaseTTLRaw interface{} `hcl:"default_lease_ttl"`
ClusterName string `hcl:"cluster_name"`
ClusterName string `hcl:"cluster_name"`
ClusterCipherSuites string `hcl:"cluster_cipher_suites"`
PluginDirectory string `hcl:"plugin_directory"`
}
@ -276,6 +278,11 @@ func (c *Config) Merge(c2 *Config) *Config {
result.ClusterName = c2.ClusterName
}
result.ClusterCipherSuites = c.ClusterCipherSuites
if c2.ClusterCipherSuites != "" {
result.ClusterCipherSuites = c2.ClusterCipherSuites
}
result.EnableUI = c.EnableUI
if c2.EnableUI {
result.EnableUI = c2.EnableUI
@ -376,6 +383,7 @@ func ParseConfig(d string, logger log.Logger) (*Config, error) {
"default_lease_ttl",
"max_lease_ttl",
"cluster_name",
"cluster_cipher_suites",
"plugin_directory",
}
if err := checkHCLKeys(list, valid); err != nil {

View File

@ -99,6 +99,8 @@ func TestLoadConfigFile_json(t *testing.T) {
DisableClustering: true,
},
ClusterCipherSuites: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
Telemetry: &Telemetry{
StatsiteAddr: "baz",
StatsdAddr: "",

View File

@ -4,6 +4,7 @@
"address": "127.0.0.1:443"
}
}],
"cluster_cipher_suites": "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"storage": {
"consul": {
"foo": "bar",

View File

@ -23,6 +23,7 @@ func ParseCiphers(cipherStr string) ([]uint16, error) {
"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_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
"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,
@ -32,10 +33,14 @@ func ParseCiphers(cipherStr string) ([]uint16, error) {
"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_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
"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_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
}
for _, cipher := range ciphers {
if v, ok := cipherMap[cipher]; ok {

View File

@ -7,12 +7,12 @@ import (
)
func TestParseCiphers(t *testing.T) {
testOk := "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_256_GCM_SHA384"
testOk := "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305"
v, err := ParseCiphers(testOk)
if err != nil {
t.Fatal(err)
}
if len(v) != 12 {
if len(v) != 17 {
t.Fatal("missed ciphers after parse")
}

View File

@ -398,7 +398,7 @@ func (c *Core) ClusterTLSConfig() (*tls.Config, error) {
//c.logger.Trace("core: performing server config lookup")
for _, v := range clientHello.SupportedProtos {
switch v {
case "h2", "req_fw_sb-act_v1":
case "h2", requestForwardingALPN:
default:
return nil, fmt.Errorf("unknown ALPN proto %s", v)
}
@ -414,6 +414,7 @@ func (c *Core) ClusterTLSConfig() (*tls.Config, error) {
RootCAs: caPool,
ClientCAs: caPool,
NextProtos: clientHello.SupportedProtos,
CipherSuites: c.clusterCipherSuites,
}
switch {
@ -438,6 +439,7 @@ func (c *Core) ClusterTLSConfig() (*tls.Config, error) {
GetClientCertificate: clientLookup,
GetConfigForClient: serverConfigLookup,
MinVersion: tls.VersionTLS12,
CipherSuites: c.clusterCipherSuites,
}
var localCert bytes.Buffer

View File

@ -383,3 +383,37 @@ func testCluster_ForwardRequests(t *testing.T, c *TestClusterCore, rootToken, re
}
}
}
func TestCluster_CustomCipherSuites(t *testing.T) {
cluster := NewTestCluster(t, &CoreConfig{
ClusterCipherSuites: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
}, nil)
cluster.Start()
defer cluster.Cleanup()
core := cluster.Cores[0]
// Wait for core to become active
TestWaitActive(t, core.Core)
tlsConf, err := core.Core.ClusterTLSConfig()
if err != nil {
t.Fatal(err)
}
conn, err := tls.Dial("tcp", fmt.Sprintf("%s:%d", core.Listeners[0].Address.IP.String(), core.Listeners[0].Address.Port+105), tlsConf)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
err = conn.Handshake()
if err != nil {
t.Fatal(err)
}
if conn.ConnectionState().CipherSuite != tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 {
var availCiphers string
for _, cipher := range core.clusterCipherSuites {
availCiphers += fmt.Sprintf("%x ", cipher)
}
t.Fatalf("got bad negotiated cipher %x, core-set suites are %s", conn.ConnectionState().CipherSuite, availCiphers)
}
}

View File

@ -30,6 +30,7 @@ import (
"github.com/hashicorp/vault/helper/logformat"
"github.com/hashicorp/vault/helper/mlock"
"github.com/hashicorp/vault/helper/reload"
"github.com/hashicorp/vault/helper/tlsutil"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/physical"
"github.com/hashicorp/vault/shamir"
@ -285,6 +286,8 @@ type Core struct {
//
// Name
clusterName string
// Specific cipher suites to use for clustering, if any
clusterCipherSuites []uint16
// Used to modify cluster parameters
clusterParamsLock sync.RWMutex
// The private key stored in the barrier used for establishing
@ -395,6 +398,8 @@ type CoreConfig struct {
ClusterName string `json:"cluster_name" structs:"cluster_name" mapstructure:"cluster_name"`
ClusterCipherSuites string `json:"cluster_cipher_suites" structs:"cluster_cipher_suites" mapstructure:"cluster_cipher_suites"`
EnableUI bool `json:"ui" structs:"ui" mapstructure:"ui"`
PluginDirectory string `json:"plugin_directory" structs:"plugin_directory" mapstructure:"plugin_directory"`
@ -459,6 +464,14 @@ func NewCore(conf *CoreConfig) (*Core, error) {
enableMlock: !conf.DisableMlock,
}
if conf.ClusterCipherSuites != "" {
suites, err := tlsutil.ParseCiphers(conf.ClusterCipherSuites)
if err != nil {
return nil, errwrap.Wrapf("error parsing cluster cipher suites: {{err}}", err)
}
c.clusterCipherSuites = suites
}
c.corsConfig = &CORSConfig{core: c}
// Load CORS config and provide a value for the core field.

View File

@ -22,6 +22,7 @@ import (
const (
clusterListenerAcceptDeadline = 500 * time.Millisecond
heartbeatInterval = 30 * time.Second
requestForwardingALPN = "req_fw_sb-act_v1"
)
// Starts the listeners and servers necessary to handle forwarded requests
@ -45,7 +46,7 @@ func (c *Core) startForwarding() error {
}
// The server supports all of the possible protos
tlsConfig.NextProtos = []string{"h2", "req_fw_sb-act_v1"}
tlsConfig.NextProtos = []string{"h2", requestForwardingALPN}
// Create our RPC server and register the request handler server
c.clusterParamsLock.Lock()
@ -144,13 +145,13 @@ func (c *Core) startForwarding() error {
}
switch tlsConn.ConnectionState().NegotiatedProtocol {
case "req_fw_sb-act_v1":
case requestForwardingALPN:
if !ha {
conn.Close()
continue
}
c.logger.Trace("core: got req_fw_sb-act_v1 connection")
c.logger.Trace("core: got request forwarding connection")
go fws.ServeConn(conn, &http2.ServeConnOpts{
Handler: c.rpcServer,
})
@ -227,7 +228,7 @@ func (c *Core) refreshRequestForwardingConnection(clusterAddr string) error {
// the TLS state.
ctx, cancelFunc := context.WithCancel(context.Background())
c.rpcClientConn, err = grpc.DialContext(ctx, clusterURL.Host,
grpc.WithDialer(c.getGRPCDialer("req_fw_sb-act_v1", "", nil)),
grpc.WithDialer(c.getGRPCDialer(requestForwardingALPN, "", nil)),
grpc.WithInsecure(), // it's not, we handle it in the dialer
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: 2 * heartbeatInterval,

View File

@ -1105,6 +1105,8 @@ func NewTestCluster(t testing.T, base *CoreConfig, opts *TestClusterOptions) *Te
coreConfig.Logger = base.Logger
}
coreConfig.ClusterCipherSuites = base.ClusterCipherSuites
coreConfig.DisableCache = base.DisableCache
coreConfig.DevToken = base.DevToken