diff --git a/command/server/config.go b/command/server/config.go index dae8e5353..d1f7c23e6 100644 --- a/command/server/config.go +++ b/command/server/config.go @@ -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 { diff --git a/command/server/config_test.go b/command/server/config_test.go index c19cecd22..49cf93a51 100644 --- a/command/server/config_test.go +++ b/command/server/config_test.go @@ -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: "", diff --git a/command/server/test-fixtures/config.hcl.json b/command/server/test-fixtures/config.hcl.json index 4bcb129dc..5723eb2b8 100644 --- a/command/server/test-fixtures/config.hcl.json +++ b/command/server/test-fixtures/config.hcl.json @@ -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", diff --git a/helper/tlsutil/tls.go b/helper/tlsutil/tlsutil.go similarity index 83% rename from helper/tlsutil/tls.go rename to helper/tlsutil/tlsutil.go index 5cbd0604d..08b3ebd0c 100644 --- a/helper/tlsutil/tls.go +++ b/helper/tlsutil/tlsutil.go @@ -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 { diff --git a/helper/tlsutil/tlsutil_test.go b/helper/tlsutil/tlsutil_test.go index a8e9e77ab..79aac9ba6 100644 --- a/helper/tlsutil/tlsutil_test.go +++ b/helper/tlsutil/tlsutil_test.go @@ -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") } diff --git a/vault/cluster.go b/vault/cluster.go index e435e8870..beca4b964 100644 --- a/vault/cluster.go +++ b/vault/cluster.go @@ -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 diff --git a/vault/cluster_test.go b/vault/cluster_test.go index a2711217b..9bc5b69ca 100644 --- a/vault/cluster_test.go +++ b/vault/cluster_test.go @@ -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) + } +} diff --git a/vault/core.go b/vault/core.go index 26453f7e0..e63e29aaf 100644 --- a/vault/core.go +++ b/vault/core.go @@ -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. diff --git a/vault/request_forwarding.go b/vault/request_forwarding.go index 7d764b7a7..0433fec7e 100644 --- a/vault/request_forwarding.go +++ b/vault/request_forwarding.go @@ -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, diff --git a/vault/testing.go b/vault/testing.go index 4dfa1cf81..3c6a2713c 100644 --- a/vault/testing.go +++ b/vault/testing.go @@ -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