mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 10:37:56 +00:00 
			
		
		
		
	Add option to set cluster TLS cipher suites. (#3228)
* Add option to set cluster TLS cipher suites. Fixes #3227
This commit is contained in:
		| @@ -42,7 +42,9 @@ type Config struct { | |||||||
| 	DefaultLeaseTTL    time.Duration `hcl:"-"` | 	DefaultLeaseTTL    time.Duration `hcl:"-"` | ||||||
| 	DefaultLeaseTTLRaw interface{}   `hcl:"default_lease_ttl"` | 	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"` | 	PluginDirectory string `hcl:"plugin_directory"` | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -276,6 +278,11 @@ func (c *Config) Merge(c2 *Config) *Config { | |||||||
| 		result.ClusterName = c2.ClusterName | 		result.ClusterName = c2.ClusterName | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	result.ClusterCipherSuites = c.ClusterCipherSuites | ||||||
|  | 	if c2.ClusterCipherSuites != "" { | ||||||
|  | 		result.ClusterCipherSuites = c2.ClusterCipherSuites | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	result.EnableUI = c.EnableUI | 	result.EnableUI = c.EnableUI | ||||||
| 	if c2.EnableUI { | 	if c2.EnableUI { | ||||||
| 		result.EnableUI = c2.EnableUI | 		result.EnableUI = c2.EnableUI | ||||||
| @@ -376,6 +383,7 @@ func ParseConfig(d string, logger log.Logger) (*Config, error) { | |||||||
| 		"default_lease_ttl", | 		"default_lease_ttl", | ||||||
| 		"max_lease_ttl", | 		"max_lease_ttl", | ||||||
| 		"cluster_name", | 		"cluster_name", | ||||||
|  | 		"cluster_cipher_suites", | ||||||
| 		"plugin_directory", | 		"plugin_directory", | ||||||
| 	} | 	} | ||||||
| 	if err := checkHCLKeys(list, valid); err != nil { | 	if err := checkHCLKeys(list, valid); err != nil { | ||||||
|   | |||||||
| @@ -99,6 +99,8 @@ func TestLoadConfigFile_json(t *testing.T) { | |||||||
| 			DisableClustering: true, | 			DisableClustering: true, | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | 		ClusterCipherSuites: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", | ||||||
|  |  | ||||||
| 		Telemetry: &Telemetry{ | 		Telemetry: &Telemetry{ | ||||||
| 			StatsiteAddr:                       "baz", | 			StatsiteAddr:                       "baz", | ||||||
| 			StatsdAddr:                         "", | 			StatsdAddr:                         "", | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ | |||||||
| 			"address": "127.0.0.1:443" | 			"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": { | 	"storage": { | ||||||
| 		"consul": { | 		"consul": { | ||||||
| 			"foo": "bar", | 			"foo": "bar", | ||||||
|   | |||||||
| @@ -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_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_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_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_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_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_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_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_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_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_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_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_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_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 { | 	for _, cipher := range ciphers { | ||||||
| 		if v, ok := cipherMap[cipher]; ok { | 		if v, ok := cipherMap[cipher]; ok { | ||||||
| @@ -7,12 +7,12 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestParseCiphers(t *testing.T) { | 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) | 	v, err := ParseCiphers(testOk) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	if len(v) != 12 { | 	if len(v) != 17 { | ||||||
| 		t.Fatal("missed ciphers after parse") | 		t.Fatal("missed ciphers after parse") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -398,7 +398,7 @@ func (c *Core) ClusterTLSConfig() (*tls.Config, error) { | |||||||
| 		//c.logger.Trace("core: performing server config lookup") | 		//c.logger.Trace("core: performing server config lookup") | ||||||
| 		for _, v := range clientHello.SupportedProtos { | 		for _, v := range clientHello.SupportedProtos { | ||||||
| 			switch v { | 			switch v { | ||||||
| 			case "h2", "req_fw_sb-act_v1": | 			case "h2", requestForwardingALPN: | ||||||
| 			default: | 			default: | ||||||
| 				return nil, fmt.Errorf("unknown ALPN proto %s", v) | 				return nil, fmt.Errorf("unknown ALPN proto %s", v) | ||||||
| 			} | 			} | ||||||
| @@ -414,6 +414,7 @@ func (c *Core) ClusterTLSConfig() (*tls.Config, error) { | |||||||
| 			RootCAs:              caPool, | 			RootCAs:              caPool, | ||||||
| 			ClientCAs:            caPool, | 			ClientCAs:            caPool, | ||||||
| 			NextProtos:           clientHello.SupportedProtos, | 			NextProtos:           clientHello.SupportedProtos, | ||||||
|  | 			CipherSuites:         c.clusterCipherSuites, | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		switch { | 		switch { | ||||||
| @@ -438,6 +439,7 @@ func (c *Core) ClusterTLSConfig() (*tls.Config, error) { | |||||||
| 		GetClientCertificate: clientLookup, | 		GetClientCertificate: clientLookup, | ||||||
| 		GetConfigForClient:   serverConfigLookup, | 		GetConfigForClient:   serverConfigLookup, | ||||||
| 		MinVersion:           tls.VersionTLS12, | 		MinVersion:           tls.VersionTLS12, | ||||||
|  | 		CipherSuites:         c.clusterCipherSuites, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var localCert bytes.Buffer | 	var localCert bytes.Buffer | ||||||
|   | |||||||
| @@ -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) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -30,6 +30,7 @@ import ( | |||||||
| 	"github.com/hashicorp/vault/helper/logformat" | 	"github.com/hashicorp/vault/helper/logformat" | ||||||
| 	"github.com/hashicorp/vault/helper/mlock" | 	"github.com/hashicorp/vault/helper/mlock" | ||||||
| 	"github.com/hashicorp/vault/helper/reload" | 	"github.com/hashicorp/vault/helper/reload" | ||||||
|  | 	"github.com/hashicorp/vault/helper/tlsutil" | ||||||
| 	"github.com/hashicorp/vault/logical" | 	"github.com/hashicorp/vault/logical" | ||||||
| 	"github.com/hashicorp/vault/physical" | 	"github.com/hashicorp/vault/physical" | ||||||
| 	"github.com/hashicorp/vault/shamir" | 	"github.com/hashicorp/vault/shamir" | ||||||
| @@ -285,6 +286,8 @@ type Core struct { | |||||||
| 	// | 	// | ||||||
| 	// Name | 	// Name | ||||||
| 	clusterName string | 	clusterName string | ||||||
|  | 	// Specific cipher suites to use for clustering, if any | ||||||
|  | 	clusterCipherSuites []uint16 | ||||||
| 	// Used to modify cluster parameters | 	// Used to modify cluster parameters | ||||||
| 	clusterParamsLock sync.RWMutex | 	clusterParamsLock sync.RWMutex | ||||||
| 	// The private key stored in the barrier used for establishing | 	// 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"` | 	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"` | 	EnableUI bool `json:"ui" structs:"ui" mapstructure:"ui"` | ||||||
|  |  | ||||||
| 	PluginDirectory string `json:"plugin_directory" structs:"plugin_directory" mapstructure:"plugin_directory"` | 	PluginDirectory string `json:"plugin_directory" structs:"plugin_directory" mapstructure:"plugin_directory"` | ||||||
| @@ -459,6 +464,14 @@ func NewCore(conf *CoreConfig) (*Core, error) { | |||||||
| 		enableMlock:                      !conf.DisableMlock, | 		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} | 	c.corsConfig = &CORSConfig{core: c} | ||||||
| 	// Load CORS config and provide a value for the core field. | 	// Load CORS config and provide a value for the core field. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ import ( | |||||||
| const ( | const ( | ||||||
| 	clusterListenerAcceptDeadline = 500 * time.Millisecond | 	clusterListenerAcceptDeadline = 500 * time.Millisecond | ||||||
| 	heartbeatInterval             = 30 * time.Second | 	heartbeatInterval             = 30 * time.Second | ||||||
|  | 	requestForwardingALPN         = "req_fw_sb-act_v1" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Starts the listeners and servers necessary to handle forwarded requests | // 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 | 	// 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 | 	// Create our RPC server and register the request handler server | ||||||
| 	c.clusterParamsLock.Lock() | 	c.clusterParamsLock.Lock() | ||||||
| @@ -144,13 +145,13 @@ func (c *Core) startForwarding() error { | |||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				switch tlsConn.ConnectionState().NegotiatedProtocol { | 				switch tlsConn.ConnectionState().NegotiatedProtocol { | ||||||
| 				case "req_fw_sb-act_v1": | 				case requestForwardingALPN: | ||||||
| 					if !ha { | 					if !ha { | ||||||
| 						conn.Close() | 						conn.Close() | ||||||
| 						continue | 						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{ | 					go fws.ServeConn(conn, &http2.ServeConnOpts{ | ||||||
| 						Handler: c.rpcServer, | 						Handler: c.rpcServer, | ||||||
| 					}) | 					}) | ||||||
| @@ -227,7 +228,7 @@ func (c *Core) refreshRequestForwardingConnection(clusterAddr string) error { | |||||||
| 	// the TLS state. | 	// the TLS state. | ||||||
| 	ctx, cancelFunc := context.WithCancel(context.Background()) | 	ctx, cancelFunc := context.WithCancel(context.Background()) | ||||||
| 	c.rpcClientConn, err = grpc.DialContext(ctx, clusterURL.Host, | 	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.WithInsecure(), // it's not, we handle it in the dialer | ||||||
| 		grpc.WithKeepaliveParams(keepalive.ClientParameters{ | 		grpc.WithKeepaliveParams(keepalive.ClientParameters{ | ||||||
| 			Time: 2 * heartbeatInterval, | 			Time: 2 * heartbeatInterval, | ||||||
|   | |||||||
| @@ -1105,6 +1105,8 @@ func NewTestCluster(t testing.T, base *CoreConfig, opts *TestClusterOptions) *Te | |||||||
| 			coreConfig.Logger = base.Logger | 			coreConfig.Logger = base.Logger | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		coreConfig.ClusterCipherSuites = base.ClusterCipherSuites | ||||||
|  |  | ||||||
| 		coreConfig.DisableCache = base.DisableCache | 		coreConfig.DisableCache = base.DisableCache | ||||||
|  |  | ||||||
| 		coreConfig.DevToken = base.DevToken | 		coreConfig.DevToken = base.DevToken | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Jeff Mitchell
					Jeff Mitchell