From b12f4886efc04828b2714390a4027b16e5094926 Mon Sep 17 00:00:00 2001 From: Michael Penick Date: Wed, 24 Aug 2022 09:56:28 -0400 Subject: [PATCH 1/4] Add proxy to cluster TLS support (`--cluster-ca-file`) Adds basic support for proxy to cluster TLS. Use `--cluster-ca-file` to enable. To use a client certificate use `--cluster-cert-file` and `--cluster-key-file`. This is useful for Cassandra clusters that use TLS. --- proxy/proxy_test.go | 2 +- proxy/run.go | 52 ++++++++++++++++++++++++++++++++++++------- proxycore/endpoint.go | 6 +++-- 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/proxy/proxy_test.go b/proxy/proxy_test.go index 3e7a2ac..0ba0022 100644 --- a/proxy/proxy_test.go +++ b/proxy/proxy_test.go @@ -341,7 +341,7 @@ func setupProxyTestWithConfig(ctx context.Context, numNodes int, cfg *proxyTestC tester.proxy = NewProxy(ctx, Config{ Version: primitive.ProtocolVersion4, - Resolver: proxycore.NewResolverWithDefaultPort([]string{clusterAddr}, clusterPort), + Resolver: proxycore.NewResolverWithDefaultPort([]string{clusterAddr}, clusterPort, nil), ReconnectPolicy: proxycore.NewReconnectPolicyWithDelays(200*time.Millisecond, time.Second), NumConns: 2, HeartBeatInterval: 30 * time.Second, diff --git a/proxy/run.go b/proxy/run.go index e67f58d..575261e 100644 --- a/proxy/run.go +++ b/proxy/run.go @@ -17,6 +17,7 @@ package proxy import ( "context" "crypto/tls" + "crypto/x509" "encoding/json" "errors" "fmt" @@ -63,6 +64,9 @@ type runConfig struct { NumConns int `yaml:"num-conns" help:"Number of connection to create to each node of the backend cluster" default:"1" env:"NUM_CONNS"` ProxyCertFile string `yaml:"proxy-cert-file" help:"Path to a PEM encoded certificate file with its intermediate certificate chain. This is used to encrypt traffic for proxy clients" env:"PROXY_CERT_FILE"` ProxyKeyFile string `yaml:"proxy-key-file" help:"Path to a PEM encoded private key file. This is used to encrypt traffic for proxy clients" env:"PROXY_KEY_FILE"` + ClusterCAFile string `yaml:"cluster-ca-file" help:"Path to a PEM encoded file with CA certificates and their intermediate certificate chains. This is used to encrypt traffic between the proxy and the backend cluster" env:"CLUSTER_CA_FILE"` + ClusterCertFile string `yaml:"cluster-cert-file" help:"Path to a PEM encoded client certificate file with its intermediate certificate chain. This is used for mutual TLS when connecting to the backend cluster" env:"CLUSTER_CERT_FILE"` + ClusterKeyFile string `yaml:"cluster-key-file" help:"Path to a PEM encoded client private key file. This is used for mutual TLS when connecting to the backend cluster" env:"CLUSTER_KEY_FILE"` RpcAddress string `yaml:"rpc-address" help:"Address to advertise in the 'system.local' table for 'rpc_address'. It must be set if configuring peer proxies" env:"RPC_ADDRESS"` DataCenter string `yaml:"data-center" help:"Data center to use in system tables" env:"DATA_CENTER"` Tokens []string `yaml:"tokens" help:"Tokens to use in the system tables. It's not recommended" env:"TOKENS"` @@ -117,7 +121,31 @@ func Run(ctx context.Context, args []string) int { cfg.Username = "token" cfg.Password = cfg.AstraToken } else if len(cfg.ContactPoints) > 0 { - resolver = proxycore.NewResolverWithDefaultPort(cfg.ContactPoints, cfg.Port) + var tlsConfig *tls.Config + + if len(cfg.ClusterCAFile) > 0 { // Use proxy to cluster TLS + caCert, err := os.ReadFile(cfg.ClusterCAFile) + + if err != nil { + cliCtx.Fatalf("unable to load cluster CA file %s: %v", cfg.ClusterCertFile, err) + } + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + + tlsConfig = &tls.Config{ + RootCAs: caCertPool, + } + + if len(cfg.ClusterCertFile) > 0 || len(cfg.ClusterKeyFile) > 0 { + cert, err := loadCertificate(cfg.ClusterCertFile, cfg.ClusterKeyFile) + if err != nil { + cliCtx.Fatalf("problem loading cluster TLS client certificate pair: %v", err) + } + tlsConfig.Certificates = []tls.Certificate{cert} + } + } + + resolver = proxycore.NewResolverWithDefaultPort(cfg.ContactPoints, cfg.Port, tlsConfig) } else { cliCtx.Errorf("must provide either bundle path, token, or contact points") return 1 @@ -342,14 +370,22 @@ func (c *runConfig) listenAndServe(p *Proxy, mux *http.ServeMux, ctx context.Con return err } -func resolveAndListen(address, cert, key string) (net.Listener, error) { - if len(cert) > 0 || len(key) > 0 { - if len(cert) == 0 || len(key) == 0 { - return nil, errors.New("both certificate and private key are required for TLS") - } - cert, err := tls.LoadX509KeyPair(cert, key) +func loadCertificate(certFile, keyFile string) (tls.Certificate, error) { + if len(certFile) == 0 || len(keyFile) == 0 { + return tls.Certificate{}, errors.New("both certificate and private key are required for TLS") + } + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return tls.Certificate{}, err + } + return cert, nil +} + +func resolveAndListen(address, certFile, keyFile string) (net.Listener, error) { + if len(certFile) > 0 || len(keyFile) > 0 { + cert, err := loadCertificate(certFile, keyFile) if err != nil { - return nil, fmt.Errorf("unable to load TLS certificate pair: %v", err) + return nil, fmt.Errorf("problem loading proxy TLS certificate pair: %v", err) } return tls.Listen("tcp", address, &tls.Config{Certificates: []tls.Certificate{cert}}) } else { diff --git a/proxycore/endpoint.go b/proxycore/endpoint.go index aff0e1d..a0fc506 100644 --- a/proxycore/endpoint.go +++ b/proxycore/endpoint.go @@ -67,6 +67,7 @@ type EndpointResolver interface { type defaultEndpointResolver struct { contactPoints []string defaultPort string + tlsConfig *tls.Config } func NewEndpoint(addr string) Endpoint { @@ -78,13 +79,14 @@ func NewEndpointTLS(addr string, cfg *tls.Config) Endpoint { } func NewResolver(contactPoints ...string) EndpointResolver { - return NewResolverWithDefaultPort(contactPoints, 9042) + return NewResolverWithDefaultPort(contactPoints, 9042, nil) } -func NewResolverWithDefaultPort(contactPoints []string, defaultPort int) EndpointResolver { +func NewResolverWithDefaultPort(contactPoints []string, defaultPort int, tlsConfig *tls.Config) EndpointResolver { return &defaultEndpointResolver{ contactPoints: contactPoints, defaultPort: strconv.Itoa(defaultPort), + tlsConfig: tlsConfig, } } From 7fd1dbc2ea47e09f73aafbf3595c8108bc5c849c Mon Sep 17 00:00:00 2001 From: Michael Penick Date: Wed, 24 Aug 2022 10:05:39 -0400 Subject: [PATCH 2/4] Use ioutil.ReadFile() for go 1.15.x --- proxy/run.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/run.go b/proxy/run.go index 575261e..935036f 100644 --- a/proxy/run.go +++ b/proxy/run.go @@ -124,7 +124,7 @@ func Run(ctx context.Context, args []string) int { var tlsConfig *tls.Config if len(cfg.ClusterCAFile) > 0 { // Use proxy to cluster TLS - caCert, err := os.ReadFile(cfg.ClusterCAFile) + caCert, err := ioutil.ReadFile(cfg.ClusterCAFile) if err != nil { cliCtx.Fatalf("unable to load cluster CA file %s: %v", cfg.ClusterCertFile, err) From 46cc3fb5b5471d445c8eb08fbfc9ef45dd760849 Mon Sep 17 00:00:00 2001 From: Pravin Bhat Date: Wed, 21 Jun 2023 10:10:11 -0400 Subject: [PATCH 3/4] Patch to resolve the endpoint correctly --- proxy/run.go | 2 +- proxycore/cluster.go | 6 ++++++ proxycore/endpoint.go | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/proxy/run.go b/proxy/run.go index 935036f..8afee0f 100644 --- a/proxy/run.go +++ b/proxy/run.go @@ -127,7 +127,7 @@ func Run(ctx context.Context, args []string) int { caCert, err := ioutil.ReadFile(cfg.ClusterCAFile) if err != nil { - cliCtx.Fatalf("unable to load cluster CA file %s: %v", cfg.ClusterCertFile, err) + cliCtx.Fatalf("unable to load cluster CA file %s: %v", cfg.ClusterCAFile, err) } caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) diff --git a/proxycore/cluster.go b/proxycore/cluster.go index 79275ba..84fca4a 100644 --- a/proxycore/cluster.go +++ b/proxycore/cluster.go @@ -264,6 +264,12 @@ func (c *Cluster) mergeHosts(hosts []*Host) error { c.logger.Info("adding host to the cluster", zap.Stringer("host", host)) c.sendEvent(&AddEvent{host}) } + + endpoints, err := c.config.Resolver.Resolve(c.ctx) + if err != nil { + return err + } + host.Endpoint = endpoints[len(endpoints)-1] } for _, host := range existing { diff --git a/proxycore/endpoint.go b/proxycore/endpoint.go index a0fc506..bd36d0d 100644 --- a/proxycore/endpoint.go +++ b/proxycore/endpoint.go @@ -108,6 +108,7 @@ func (r *defaultEndpointResolver) Resolve(ctx context.Context) ([]Endpoint, erro for _, addr := range addrs { endpoints = append(endpoints, &defaultEndpoint{ addr: net.JoinHostPort(addr, port), + tlsConfig: r.tlsConfig, }) } } From df190aab8f5b6b4b1f3da58d7eeab409853c8087 Mon Sep 17 00:00:00 2001 From: Pravin Bhat Date: Fri, 23 Jun 2023 11:18:55 -0400 Subject: [PATCH 4/4] Skip server name validation --- proxy/run.go | 1 + 1 file changed, 1 insertion(+) diff --git a/proxy/run.go b/proxy/run.go index 8afee0f..01b46c8 100644 --- a/proxy/run.go +++ b/proxy/run.go @@ -134,6 +134,7 @@ func Run(ctx context.Context, args []string) int { tlsConfig = &tls.Config{ RootCAs: caCertPool, + InsecureSkipVerify: true, // Skip server name validation } if len(cfg.ClusterCertFile) > 0 || len(cfg.ClusterKeyFile) > 0 {