From 2d68517b83921756cfb570ca3f6c8f63944bab58 Mon Sep 17 00:00:00 2001 From: Christopher Bradford Date: Fri, 23 Oct 2020 11:06:44 -0400 Subject: [PATCH] Support for specifying SSL certificates for Cassandra client Updated tests Regenerated documentation Added CHANGELOG entry Signed-off-by: Christopher Bradford Regenerate documentation and update certificate key file to private key file Signed-off-by: Christopher Bradford --- CHANGELOG.md | 1 + docs/configuration/config-file-reference.md | 8 ++++ pkg/chunk/cassandra/storage_client.go | 28 +++++++++++-- pkg/chunk/cassandra/storage_client_test.go | 42 +++++++++++++++++++ .../cassandra/testdata/example.com-key.pem | 27 ++++++++++++ .../cassandra/testdata/example.com.ca.pem | 25 +++++++++++ pkg/chunk/cassandra/testdata/example.com.pem | 27 ++++++++++++ 7 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 pkg/chunk/cassandra/testdata/example.com-key.pem create mode 100644 pkg/chunk/cassandra/testdata/example.com.ca.pem create mode 100644 pkg/chunk/cassandra/testdata/example.com.pem diff --git a/CHANGELOG.md b/CHANGELOG.md index 63c8c05b18d..a265438ef64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ * [FEATURE] Shuffle sharding: added support for shuffle-sharding ingesters on the read path. When ingesters shuffle-sharding is enabled and `-querier.shuffle-sharding-ingesters-lookback-period` is set, queriers will fetch in-memory series from the minimum set of required ingesters, selecting only ingesters which may have received series since 'now - lookback period'. #3252 * [FEATURE] Query-frontend: added `compression` config to support results cache with compression. #3217 * [FEATURE] Added support for applying Prometheus relabel configs on series received by the distributor. A `metric_relabel_configs` field has been added to the per-tenant limits configuration. #3329 +* [FEATURE] Support for Cassandra client SSL certificates. #3384 * [ENHANCEMENT] Ruler: Introduces two new limits `-ruler.max-rules-per-rule-group` and `-ruler.max-rule-groups-per-tenant` to control the number of rules per rule group and the total number of rule groups for a given user. They are disabled by default. #3366 * [ENHANCEMENT] Allow to specify multiple comma-separated Cortex services to `-target` CLI option (or its respective YAML config option). For example, `-target=all,compactor` can be used to start Cortex single-binary with compactor as well. #3275 * [ENHANCEMENT] Expose additional HTTP configs for the S3 backend client. New flag are listed below: #3244 diff --git a/docs/configuration/config-file-reference.md b/docs/configuration/config-file-reference.md index 316abb37a15..b9dc7c3e5bc 100644 --- a/docs/configuration/config-file-reference.md +++ b/docs/configuration/config-file-reference.md @@ -1980,6 +1980,14 @@ cassandra: # CLI flag: -cassandra.ca-path [CA_path: | default = ""] + # Path to certificate file used by TLS. + # CLI flag: -cassandra.tls-cert-path + [tls_cert_path: | default = ""] + + # Path to private key file used by TLS. + # CLI flag: -cassandra.tls-key-path + [tls_key_path: | default = ""] + # Enable password authentication when connecting to cassandra. # CLI flag: -cassandra.auth [auth: | default = false] diff --git a/pkg/chunk/cassandra/storage_client.go b/pkg/chunk/cassandra/storage_client.go index a509c5bfb6e..1e638f6091f 100644 --- a/pkg/chunk/cassandra/storage_client.go +++ b/pkg/chunk/cassandra/storage_client.go @@ -34,6 +34,8 @@ type Config struct { SSL bool `yaml:"SSL"` HostVerification bool `yaml:"host_verification"` CAPath string `yaml:"CA_path"` + CertPath string `yaml:"tls_cert_path"` + KeyPath string `yaml:"tls_key_path"` Auth bool `yaml:"auth"` Username string `yaml:"username"` Password flagext.Secret `yaml:"password"` @@ -62,6 +64,8 @@ func (cfg *Config) RegisterFlags(f *flag.FlagSet) { f.BoolVar(&cfg.SSL, "cassandra.ssl", false, "Use SSL when connecting to cassandra instances.") f.BoolVar(&cfg.HostVerification, "cassandra.host-verification", true, "Require SSL certificate validation.") f.StringVar(&cfg.CAPath, "cassandra.ca-path", "", "Path to certificate file to verify the peer.") + f.StringVar(&cfg.CertPath, "cassandra.tls-cert-path", "", "Path to certificate file used by TLS.") + f.StringVar(&cfg.KeyPath, "cassandra.tls-key-path", "", "Path to private key file used by TLS.") f.BoolVar(&cfg.Auth, "cassandra.auth", false, "Enable password authentication when connecting to cassandra.") f.StringVar(&cfg.Username, "cassandra.username", "", "Username to use when connecting to cassandra.") f.Var(&cfg.Password, "cassandra.password", "Password to use when connecting to cassandra.") @@ -86,6 +90,12 @@ func (cfg *Config) Validate() error { if cfg.SSL && cfg.HostVerification && len(strings.Split(cfg.Addresses, ",")) != 1 { return errors.Errorf("Host verification is only possible for a single host.") } + if cfg.SSL && cfg.CertPath != "" && cfg.KeyPath == "" { + return errors.Errorf("TLS certificate specified, but private key configuration is missing.") + } + if cfg.SSL && cfg.KeyPath != "" && cfg.CertPath == "" { + return errors.Errorf("TLS private key specified, but certificate configuration is missing.") + } return nil } @@ -144,17 +154,29 @@ func (cfg *Config) setClusterConfig(cluster *gocql.ClusterConfig) error { cluster.DisableInitialHostLookup = cfg.DisableInitialHostLookup if cfg.SSL { + tlsConfig := &tls.Config{} + + if cfg.CertPath != "" { + cert, err := tls.LoadX509KeyPair(cfg.CertPath, cfg.KeyPath) + if err != nil { + return errors.Wrap(err, "Unable to load TLS certificate and private key") + } + + tlsConfig.Certificates = []tls.Certificate{cert} + } + if cfg.HostVerification { + tlsConfig.ServerName = strings.Split(cfg.Addresses, ",")[0] + cluster.SslOpts = &gocql.SslOptions{ CaPath: cfg.CAPath, EnableHostVerification: true, - Config: &tls.Config{ - ServerName: strings.Split(cfg.Addresses, ",")[0], - }, + Config: tlsConfig, } } else { cluster.SslOpts = &gocql.SslOptions{ EnableHostVerification: false, + Config: tlsConfig, } } } diff --git a/pkg/chunk/cassandra/storage_client_test.go b/pkg/chunk/cassandra/storage_client_test.go index 0ef25791fba..7fd6867fe27 100644 --- a/pkg/chunk/cassandra/storage_client_test.go +++ b/pkg/chunk/cassandra/storage_client_test.go @@ -75,6 +75,48 @@ func TestConfig_setClusterConfig_authWithPasswordAndPasswordFile(t *testing.T) { assert.Error(t, cfg.Validate()) } +func TestConfig_setClusterConfig_clientSSL(t *testing.T) { + cfg := defaultConfig() + cfg.SSL = true + cfg.CAPath = "testdata/example.com.ca.pem" + cfg.CertPath = "testdata/example.com.pem" + cfg.KeyPath = "testdata/example.com-key.pem" + require.NoError(t, cfg.Validate()) + + cqlCfg := gocql.NewCluster() + err := cfg.setClusterConfig(cqlCfg) + require.NoError(t, err) + assert.NotNil(t, cqlCfg.SslOpts) + assert.Len(t, cqlCfg.SslOpts.Certificates, 1) +} + +func TestConfig_setClusterConfig_clientSSLWithOnlyCertificatePath(t *testing.T) { + cfg := defaultConfig() + cfg.SSL = true + cfg.CAPath = "testdata/example.com.ca.pem" + cfg.CertPath = "testdata/example.com.pem" + assert.Error(t, cfg.Validate(), "TLS certificate specified, but private key configuration is missing.") +} + +func TestConfig_setClusterConfig_clientSSLWithOnlyKeyPath(t *testing.T) { + cfg := defaultConfig() + cfg.SSL = true + cfg.CAPath = "testdata/example.com.ca.pem" + cfg.KeyPath = "testdata/example.com-key.pem" + assert.Error(t, cfg.Validate(), "TLS private key specified, but certificate configuration is missing.") +} + +func TestConfig_setClusterConfig_clientSSLWithInvalidParameters(t *testing.T) { + cfg := defaultConfig() + cfg.SSL = true + cfg.CAPath = "testdata/example.com.ca.pem" + cfg.CertPath = "testdata/example.com-key.pem" + cfg.KeyPath = "testdata/example.com.pem" + + cluster := gocql.NewCluster() + assert.Error(t, cfg.setClusterConfig(cluster), "Unable to load TLS certificate and private key.") +} + func TestConfig_setClusterConfig_consistency(t *testing.T) { tests := map[string]struct { cfg Config diff --git a/pkg/chunk/cassandra/testdata/example.com-key.pem b/pkg/chunk/cassandra/testdata/example.com-key.pem new file mode 100644 index 00000000000..cef0fa6cc54 --- /dev/null +++ b/pkg/chunk/cassandra/testdata/example.com-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAp7T84Nzd5y54a0qEFNeCXIDoz4LdfQqB2MloSl6aicKFRX7Y +rTPDgwul9trau0dtDi7goT7GoyesRRJBsAqmUEaX9YjgRbWCiiKr47SxQJRn0Hom +sNgCfIgCJ+Ej8no87JaxM7gklqoXrWK2KeG+NoC7jO2hrlBomkTkjgjU466kYpXM +8z2+p3Jt3JHNJYTnOl36s/Iaw0ugmEPOJpLrsdnt2Irs5J2dmbMbeDJ5+yaE2c8B +YSzAnLh5GQZO+5G7Ez0SXs1VJ2DALXybLuLD9pEZpAt40TK9jNwU5dfTYZ6yEeyn +jw6qqJMcxVEYRru7x4ArOP7Uvs0qlOLDxJdD+QIDAQABAoIBAFWbj9KBLE0Mba/n +E9FHyWXK8Aytcr6XlHzDIxeDf0N/JmS5QYX4fH7yfT+rrCgZZ9PrngLxdphmcgu4 +LAvfA9LKlltiCYnMA9zbof7Uh/69Qtkq4YE4YtyK2P7ecGkgeOUUb1RFVXgLT5bU +YsSyVVShFhv0WaoPpbXKIRlX7MRj188laiYQnhLJ8gZeutk9A3wkaPsRp2s1ME9R +oyUDs9OAEJOXB7wEqGLmKXqxSqzTDSoC/7wbjQut8G63/ri+EYwp5ukRP886cbep +hVUDt8bzoratw+IFsGt+fBe9MGNbAMvva7x0yb/7gdEf505+c1h3Q19CLbPHFIJv +hEcbhAECgYEAwkZp26SQANi7zDVoGqS7PXIWs2TL2g9Y2vO5g0MTvbno/0OPo3+e +GOtFW4rK2k6SoK3RtdOdmobJcxKf2G4W7ncdM6X1Rgxfgqyt58qfXSL/TlS3wfH2 +vDFQDyLEofZ64vSMEKinSkv5kY/FCCtw8q8hCgiqmpwY51PFlI/52bkCgYEA3P2h +LmRYGImkw8eiKm4eAFYXdVlxzsytCF3s2cCI3GavoGyMwxayYUkEKRA7bcup1pK+ +RdIRaaVkNcFICrX4N9zaEqSkBEQkQ+ZQN0hW9HM3XKPR7VPQEusRCbV5hMNgI3tY +irAZXgeOzS61Rl+o3Ta3SQ2jDcCoK31xzE1s3EECgYAYYb/tWfTctlazZUyAc4Yw +Sv5AW3keD+kF6aqxp5x1pjxwtOj1CxIrbHOS7pNQ3KWYVthH6pwQBbSIpaC8B+0G +1poqnjxvIyRlgQh+W7aueLL0ALvjMlvV+JZkn+dvsEBx9WESwifksi5LL3D5+oG9 +Y29RFA9dQhP6DFByubMQuQKBgAyIogykik6R9/NWrj7j0fXI7DmuogLNnv67fQR4 +pAqEFG/v2Cf0cJeN8Zt2nThD9dUCq6IAIRax17YoyTI6UeKxNvkZt2e6iagENwZ7 +ptrkcf5iGDTyrPl1tZisX0EFZ717cHElPbsUiKfgf02HfWdWhByzlkzgYWleCwdA +WO1BAoGAMJA/dYgbEachKXl9nZ9HvqAtc30ioYG0eq5Zx9iYZWqhb1vqGJXS2M2t +Onmw/+2pnvPIM1uP66G5VG0rcVL7jdJ0pibCgw+L9IHpDwCAuVGn1Cuzcty+NiAy +qLkQAgPOMjwL9zJYrn6LAOVchnIbpuoaGNG14BdZWeh25kozk/k= +-----END RSA PRIVATE KEY----- diff --git a/pkg/chunk/cassandra/testdata/example.com.ca.pem b/pkg/chunk/cassandra/testdata/example.com.ca.pem new file mode 100644 index 00000000000..2880d2c27de --- /dev/null +++ b/pkg/chunk/cassandra/testdata/example.com.ca.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIUDpfGq/7zYQRROCP65MxUy+LYewkwDQYJKoZIhvcNAQEL +BQAwgbAxGDAWBgNVBAYTD0V4YW1wbGUgQ291bnRyeTEWMBQGA1UECBMNRXhhbXBs +ZSBTdGF0ZTEZMBcGA1UEBxMQRXhhbXBsZSBMb2NhbGl0eTEdMBsGA1UEChMURXhh +bXBsZSBPcmdhbml6YXRpb24xIjAgBgNVBAsTGUV4YW1wbGUgT3JnYW5pemF0aW9u +IFVuaXQxHjAcBgNVBAMTFVNhbXBsZSBTZWxmLVNpZ25lZCBDQTAeFw0yMDEwMjMx +NTUxMDBaFw0yNTEwMjIxNTUxMDBaMIGwMRgwFgYDVQQGEw9FeGFtcGxlIENvdW50 +cnkxFjAUBgNVBAgTDUV4YW1wbGUgU3RhdGUxGTAXBgNVBAcTEEV4YW1wbGUgTG9j +YWxpdHkxHTAbBgNVBAoTFEV4YW1wbGUgT3JnYW5pemF0aW9uMSIwIAYDVQQLExlF +eGFtcGxlIE9yZ2FuaXphdGlvbiBVbml0MR4wHAYDVQQDExVTYW1wbGUgU2VsZi1T +aWduZWQgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCXiANIcYCd +AaniIG59ZEIhgT2tx4PpsjPc07sx4B8cPAhYVPlpom2lSfI9lj14i37vEBFEsuwD +Dk7xz6MGIAsLbmw8eEAbUyMaKeCfUZDVwWWW/OQo/riCc1Vw85nKAUQ7YA4TyvpO +ORf5UAhLx0/ZhJsoAOgEOGa+S7NZjNCwADvhOzE0j5oDCblv3+EeiB8zAYzAG1xm +onkVsS09mfmiE2V5mA6zAn60E9Ssfx4hJbxFNAvTlv+im37uumipKEGr/gRCcnFp +QcgamxjCbmD+XNjJ35u0/r/mXHeghvRNl+2ARl3XngKclFeNwhgm7DWJsx66bbGy +Hv2YQb9xxTNbAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBSuamXDGCosE/xvsAWBVbSshGQ65DANBgkqhkiG9w0BAQsF +AAOCAQEAd21HOCRsQENKxOsMbsYyla1lyWPjTTnn+c4IbgfcKA7lkf8ESFa7ChVq +Q2z8mTAxblvnGy1bHaeFw1vy0hIYnV1rizsb+3nBN5oBZQtG6Rmc9iL5MhIaHprB +bHqx/9zuCwH2jzSMcIYGUbfJcC5+W67P/zpX5rKkCqiyu81Unw5GAmcawaU6600b +Dtx1YEWWLjwnBXXQp+4udHHCChsq5SFwJuWZ13+KNIrrD0QqcXn4lGtEWLD7/Anl +BFGcQJpHq5x17Of1pxOqEogXTk44+cMKNDRr05mH2xc6nZqvZlUkdusl34XLC9Lo +kIim0bc2p5dHjfAeBS7HSkXuwYzeQA== +-----END CERTIFICATE----- diff --git a/pkg/chunk/cassandra/testdata/example.com.pem b/pkg/chunk/cassandra/testdata/example.com.pem new file mode 100644 index 00000000000..6fee105b19d --- /dev/null +++ b/pkg/chunk/cassandra/testdata/example.com.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEfzCCA2egAwIBAgIUJr0Q3zi9e99mQtYS9nK2jniXnZkwDQYJKoZIhvcNAQEL +BQAwgbAxGDAWBgNVBAYTD0V4YW1wbGUgQ291bnRyeTEWMBQGA1UECBMNRXhhbXBs +ZSBTdGF0ZTEZMBcGA1UEBxMQRXhhbXBsZSBMb2NhbGl0eTEdMBsGA1UEChMURXhh +bXBsZSBPcmdhbml6YXRpb24xIjAgBgNVBAsTGUV4YW1wbGUgT3JnYW5pemF0aW9u +IFVuaXQxHjAcBgNVBAMTFVNhbXBsZSBTZWxmLVNpZ25lZCBDQTAeFw0yMDEwMjMx +NTUyMDBaFw0yMTEwMjMxNTUyMDBaMIGmMRgwFgYDVQQGEw9FeGFtcGxlIENvdW50 +cnkxFjAUBgNVBAgTDUV4YW1wbGUgU3RhdGUxGTAXBgNVBAcTEEV4YW1wbGUgTG9j +YWxpdHkxHTAbBgNVBAoTFEV4YW1wbGUgT3JnYW5pemF0aW9uMSIwIAYDVQQLExlF +eGFtcGxlIE9yZ2FuaXphdGlvbiBVbml0MRQwEgYDVQQDEwtleGFtcGxlLmNvbTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKe0/ODc3ecueGtKhBTXglyA +6M+C3X0KgdjJaEpemonChUV+2K0zw4MLpfba2rtHbQ4u4KE+xqMnrEUSQbAKplBG +l/WI4EW1gooiq+O0sUCUZ9B6JrDYAnyIAifhI/J6POyWsTO4JJaqF61itinhvjaA +u4ztoa5QaJpE5I4I1OOupGKVzPM9vqdybdyRzSWE5zpd+rPyGsNLoJhDziaS67HZ +7diK7OSdnZmzG3gyefsmhNnPAWEswJy4eRkGTvuRuxM9El7NVSdgwC18my7iw/aR +GaQLeNEyvYzcFOXX02GeshHsp48OqqiTHMVRGEa7u8eAKzj+1L7NKpTiw8SXQ/kC +AwEAAaOBmDCBlTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEG +CCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFNfuWV6Y129JoLnf12l+ +/OHgSyXEMB8GA1UdIwQYMBaAFK5qZcMYKiwT/G+wBYFVtKyEZDrkMBYGA1UdEQQP +MA2CC2V4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQBoIy1KhsjfHMwO1yAZ +ECiyoPr7cfqPFSteAEk7CxqgdCW2ZsZJnKHdZYFhBfc7cEGTHss9gANEgOogzgEV +ZOny0fINK3+GVuDlvm9DEB/r5sXm9zWoS5qvbtZ58lUlgOXDSlQ5gsMxOSFl7Gp5 +GAKxuGIvF8bFejQP8u1f5YBxSPgHhg+vzyHJ2vmOw4r+dxpMG5B/NDpbOxQNpNmw +GTOETPbQh01Y/D6jI4HGZ90c6C9dP0+Tc1PScfaE3uOqa3GEToahXzLDLCW4sQFS +PYma10nXk29DfjYtaYYFMmpZnemhZG8E61OXe7fLn5/DLt1a4lZRfoEoiGAaKqQd +glLB +-----END CERTIFICATE-----