From bfb77ad601023770905ce318c36353214de60b4a Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Tue, 29 Nov 2022 17:47:52 +0000 Subject: [PATCH] feat(collectors): Add TLS parameters to the redis collector (#870) feat(collectors): Add mTLS parameters to the redis collector For a redis collector spec targeting a redis server configured to accept (m)TLS connections we need to pass in the necessary TLS parameters in order to successfully connect to the server. Both preflight and support bundle specs use this collector. This change allows us to pass in the necessary TLS parameters via inlined TLS configuration or via a secret reference. Fixes #746 --- config/crds/troubleshoot.sh_collectors.yaml | 63 ++++++ config/crds/troubleshoot.sh_preflights.yaml | 63 ++++++ .../crds/troubleshoot.sh_supportbundles.yaml | 63 ++++++ examples/preflight/redis.yaml | 7 +- ...mysql-collector.yaml => db-collector.yaml} | 5 +- .../troubleshoot/v1beta2/collector_shared.go | 18 +- .../v1beta2/zz_generated.deepcopy.go | 40 ++++ pkg/collect/redis.go | 56 +++++- pkg/collect/redis_test.go | 108 ++++++++++ pkg/collect/util.go | 87 ++++++++ pkg/collect/util_test.go | 188 ++++++++++++++++++ schemas/collector-troubleshoot-v1beta2.json | 96 +++++++++ schemas/preflight-troubleshoot-v1beta2.json | 96 +++++++++ .../supportbundle-troubleshoot-v1beta2.json | 96 +++++++++ testdata/db/ca.pem | 21 ++ testdata/db/client-key.pem | 27 +++ testdata/db/client.pem | 22 ++ 17 files changed, 1039 insertions(+), 17 deletions(-) rename examples/support-bundle/{mysql-collector.yaml => db-collector.yaml} (78%) create mode 100644 pkg/collect/redis_test.go create mode 100644 testdata/db/ca.pem create mode 100644 testdata/db/client-key.pem create mode 100644 testdata/db/client.pem diff --git a/config/crds/troubleshoot.sh_collectors.yaml b/config/crds/troubleshoot.sh_collectors.yaml index 488b1fc3..146dd71b 100644 --- a/config/crds/troubleshoot.sh_collectors.yaml +++ b/config/crds/troubleshoot.sh_collectors.yaml @@ -367,6 +367,27 @@ spec: items: type: string type: array + tls: + properties: + cacert: + type: string + clientCert: + type: string + clientKey: + type: string + secret: + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + skipVerify: + type: boolean + type: object uri: type: string required: @@ -382,6 +403,27 @@ spec: items: type: string type: array + tls: + properties: + cacert: + type: string + clientCert: + type: string + clientKey: + type: string + secret: + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + skipVerify: + type: boolean + type: object uri: type: string required: @@ -397,6 +439,27 @@ spec: items: type: string type: array + tls: + properties: + cacert: + type: string + clientCert: + type: string + clientKey: + type: string + secret: + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + skipVerify: + type: boolean + type: object uri: type: string required: diff --git a/config/crds/troubleshoot.sh_preflights.yaml b/config/crds/troubleshoot.sh_preflights.yaml index 93328fe8..59809d4c 100644 --- a/config/crds/troubleshoot.sh_preflights.yaml +++ b/config/crds/troubleshoot.sh_preflights.yaml @@ -1692,6 +1692,27 @@ spec: items: type: string type: array + tls: + properties: + cacert: + type: string + clientCert: + type: string + clientKey: + type: string + secret: + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + skipVerify: + type: boolean + type: object uri: type: string required: @@ -1707,6 +1728,27 @@ spec: items: type: string type: array + tls: + properties: + cacert: + type: string + clientCert: + type: string + clientKey: + type: string + secret: + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + skipVerify: + type: boolean + type: object uri: type: string required: @@ -1722,6 +1764,27 @@ spec: items: type: string type: array + tls: + properties: + cacert: + type: string + clientCert: + type: string + clientKey: + type: string + secret: + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + skipVerify: + type: boolean + type: object uri: type: string required: diff --git a/config/crds/troubleshoot.sh_supportbundles.yaml b/config/crds/troubleshoot.sh_supportbundles.yaml index 00f30dcf..256c7367 100644 --- a/config/crds/troubleshoot.sh_supportbundles.yaml +++ b/config/crds/troubleshoot.sh_supportbundles.yaml @@ -1723,6 +1723,27 @@ spec: items: type: string type: array + tls: + properties: + cacert: + type: string + clientCert: + type: string + clientKey: + type: string + secret: + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + skipVerify: + type: boolean + type: object uri: type: string required: @@ -1738,6 +1759,27 @@ spec: items: type: string type: array + tls: + properties: + cacert: + type: string + clientCert: + type: string + clientKey: + type: string + secret: + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + skipVerify: + type: boolean + type: object uri: type: string required: @@ -1753,6 +1795,27 @@ spec: items: type: string type: array + tls: + properties: + cacert: + type: string + clientCert: + type: string + clientKey: + type: string + secret: + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + skipVerify: + type: boolean + type: object uri: type: string required: diff --git a/examples/preflight/redis.yaml b/examples/preflight/redis.yaml index ece1e156..34c9fd30 100644 --- a/examples/preflight/redis.yaml +++ b/examples/preflight/redis.yaml @@ -6,7 +6,9 @@ spec: collectors: - redis: collectorName: my-redis - uri: rediss://default:password@hostname:6379 + uri: rediss://default:replicated@server:6380 + tls: + skipVerify: true analyzers: - redis: checkName: Must be redis 5.x or later @@ -14,10 +16,9 @@ spec: outcomes: - fail: when: "connected == false" - message: Cannot connect to postgres server + message: Cannot connect to redis server - fail: when: "version < 5.0.0" message: The redis server must be at least version 5 - pass: message: The redis connection checks out - \ No newline at end of file diff --git a/examples/support-bundle/mysql-collector.yaml b/examples/support-bundle/db-collector.yaml similarity index 78% rename from examples/support-bundle/mysql-collector.yaml rename to examples/support-bundle/db-collector.yaml index e590878e..e47ac851 100644 --- a/examples/support-bundle/mysql-collector.yaml +++ b/examples/support-bundle/db-collector.yaml @@ -1,7 +1,7 @@ apiVersion: troubleshoot.sh/v1beta2 kind: SupportBundle metadata: - name: mysql + name: dbs-collector spec: collectors: - mysql: @@ -15,3 +15,6 @@ spec: - innodb_large_prefix - innodb_strict_mode - log_bin_trust_function_creators + - redis: + collectorName: my-redis + uri: rediss://default:replicated@server:6380 diff --git a/pkg/apis/troubleshoot/v1beta2/collector_shared.go b/pkg/apis/troubleshoot/v1beta2/collector_shared.go index 7aa532ad..a21a0851 100644 --- a/pkg/apis/troubleshoot/v1beta2/collector_shared.go +++ b/pkg/apis/troubleshoot/v1beta2/collector_shared.go @@ -168,8 +168,22 @@ type Put struct { type Database struct { CollectorMeta `json:",inline" yaml:",inline"` - URI string `json:"uri" yaml:"uri"` - Parameters []string `json:"parameters,omitempty"` + URI string `json:"uri" yaml:"uri"` + Parameters []string `json:"parameters,omitempty"` + TLS *TLSParams `json:"tls,omitempty" yaml:"tls,omitempty"` +} + +type TLSParams struct { + SkipVerify bool `json:"skipVerify,omitempty" yaml:"skipVerify,omitempty"` + Secret *TLSSecret `json:"secret,omitempty" yaml:"secret,omitempty"` + CACert string `json:"cacert,omitempty" yaml:"cacert,omitempty"` + ClientCert string `json:"clientCert,omitempty" yaml:"clientCert,omitempty"` + ClientKey string `json:"clientKey,omitempty" yaml:"clientKey,omitempty"` +} + +type TLSSecret struct { + Name string `json:"name" yaml:"name"` + Namespace string `json:"namespace" yaml:"namespace"` } type Collectd struct { diff --git a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go index 17ca9bca..e937abc5 100644 --- a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go +++ b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go @@ -1081,6 +1081,11 @@ func (in *Database) DeepCopyInto(out *Database) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(TLSParams) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Database. @@ -4102,6 +4107,41 @@ func (in *TCPPortStatusAnalyze) DeepCopy() *TCPPortStatusAnalyze { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSParams) DeepCopyInto(out *TLSParams) { + *out = *in + if in.Secret != nil { + in, out := &in.Secret, &out.Secret + *out = new(TLSSecret) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSParams. +func (in *TLSParams) DeepCopy() *TLSParams { + if in == nil { + return nil + } + out := new(TLSParams) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSSecret) DeepCopyInto(out *TLSSecret) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSSecret. +func (in *TLSSecret) DeepCopy() *TLSSecret { + if in == nil { + return nil + } + out := new(TLSSecret) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TextAnalyze) DeepCopyInto(out *TextAnalyze) { *out = *in diff --git a/pkg/collect/redis.go b/pkg/collect/redis.go index 07fb466e..2a0d11a7 100644 --- a/pkg/collect/redis.go +++ b/pkg/collect/redis.go @@ -12,6 +12,7 @@ import ( troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" + "k8s.io/klog/v2" ) type CollectRedis struct { @@ -32,14 +33,55 @@ func (c *CollectRedis) IsExcluded() (bool, error) { return isExcluded(c.Collector.Exclude) } +func (c *CollectRedis) createClient() (*redis.Client, error) { + opt, err := redis.ParseURL(c.Collector.URI) + if err != nil { + return nil, err + } + + if c.Collector.TLS != nil { + klog.V(2).Infof("Connecting to redis in mutual TLS") + return c.createMTLSClient(opt) + } + + klog.V(2).Infof("Connecting to redis in plain text") + return redis.NewClient(opt), nil +} + +func (c *CollectRedis) createMTLSClient(opt *redis.Options) (*redis.Client, error) { + tlsCfg, err := createTLSConfig(c.Context, c.Client, c.Collector.TLS) + if err != nil { + return nil, err + } + + opt.TLSConfig = tlsCfg + + return redis.NewClient(opt), nil +} + +func extractServerVersion(info string) string { + lines := strings.Split(info, "\n") + for _, line := range lines { + lineParts := strings.Split(strings.TrimSpace(line), ":") + if len(lineParts) == 2 { + if lineParts[0] == "redis_version" { + return strings.TrimSpace(lineParts[1]) + } + } + } + + return "" +} + func (c *CollectRedis) Collect(progressChan chan<- interface{}) (CollectorResult, error) { databaseConnection := DatabaseConnection{} - opt, err := redis.ParseURL(c.Collector.URI) + client, err := c.createClient() if err != nil { databaseConnection.Error = err.Error() } else { - client := redis.NewClient(opt) + defer client.Close() + stringResult := client.Info("server") if stringResult.Err() != nil { @@ -49,15 +91,7 @@ func (c *CollectRedis) Collect(progressChan chan<- interface{}) (CollectorResult databaseConnection.IsConnected = stringResult.Err() == nil if databaseConnection.Error == "" { - lines := strings.Split(stringResult.Val(), "\n") - for _, line := range lines { - lineParts := strings.Split(line, ":") - if len(lineParts) == 2 { - if lineParts[0] == "redis_version" { - databaseConnection.Version = strings.TrimSpace(lineParts[1]) - } - } - } + databaseConnection.Version = extractServerVersion(stringResult.Val()) } } diff --git a/pkg/collect/redis_test.go b/pkg/collect/redis_test.go new file mode 100644 index 00000000..dcbb9f46 --- /dev/null +++ b/pkg/collect/redis_test.go @@ -0,0 +1,108 @@ +package collect + +import ( + "testing" + + v1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + testclient "k8s.io/client-go/kubernetes/fake" +) + +func Test_extractServerVersion(t *testing.T) { + tests := []struct { + name string + info string + want string + }{ + { + name: "extracts version successfully", + info: ` + # Server + redis_version:7.0.5 + redis_git_sha1:00000000 + redis_git_dirty:0 + redis_build_id:eb3578384289228a + `, + want: "7.0.5", + }, + { + name: "extracts version but fails", + info: "", + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := extractServerVersion(tt.info) + assert.Equalf(t, tt.want, got, "extractServerVersion() = %v, want %v", got, tt.want) + }) + } +} + +func TestCollectRedis_createPlainTextClient(t *testing.T) { + tests := []struct { + name string + uri string + hasError bool + }{ + { + name: "valid uri creates redis client successfully", + uri: "redis://localhost:6379", + }, + { + name: "empty uri fails to create client with error", + uri: "", + hasError: true, + }, + { + name: "invalid redis protocol fails to create client with error", + uri: "http://localhost:6379", + hasError: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &CollectRedis{ + Collector: &v1beta2.Database{ + URI: tt.uri, + }, + } + + client, err := c.createClient() + assert.Equal(t, err != nil, tt.hasError) + if err == nil { + require.NotNil(t, client) + assert.Equal(t, client.Options().Addr, "localhost:6379") + } else { + t.Log(err) + assert.Nil(t, client) + } + }) + } +} + +func TestCollectRedis_createTLSClient(t *testing.T) { + k8sClient := testclient.NewSimpleClientset() + + c := &CollectRedis{ + Client: k8sClient, + Collector: &v1beta2.Database{ + URI: "redis://localhost:6379", + TLS: &v1beta2.TLSParams{ + CACert: getTestFixture(t, "db/ca.pem"), + ClientCert: getTestFixture(t, "db/client.pem"), + ClientKey: getTestFixture(t, "db/client-key.pem"), + }, + }, + } + + client, err := c.createClient() + assert.NoError(t, err) + assert.NotNil(t, client) + opt := client.Options() + assert.Equal(t, opt.Addr, "localhost:6379") + assert.NotNil(t, opt.TLSConfig.Certificates) + assert.NotNil(t, opt.TLSConfig.RootCAs) + assert.False(t, opt.TLSConfig.InsecureSkipVerify) +} diff --git a/pkg/collect/util.go b/pkg/collect/util.go index b6e0c161..09d38c07 100644 --- a/pkg/collect/util.go +++ b/pkg/collect/util.go @@ -3,6 +3,8 @@ package collect import ( "bytes" "context" + "crypto/tls" + "crypto/x509" "encoding/json" "fmt" "io" @@ -10,6 +12,7 @@ import ( "regexp" "strings" + "github.com/pkg/errors" troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -140,3 +143,87 @@ func listNodesInSelector(ctx context.Context, client *kubernetes.Clientset, sele return nodes.Items, nil } + +func createTLSConfig(ctx context.Context, client kubernetes.Interface, params *troubleshootv1beta2.TLSParams) (*tls.Config, error) { + rootCA, err := x509.SystemCertPool() + if err != nil { + rootCA = x509.NewCertPool() + } + + tlsCfg := &tls.Config{} + + if params.SkipVerify { + tlsCfg.InsecureSkipVerify = true + return tlsCfg, nil + } + + var caCert, clientCert, clientKey string + if params.Secret != nil { + caCert, clientCert, clientKey, err = getTLSParamsFromSecret(ctx, client, params.Secret) + if err != nil { + return nil, err + } + } else { + caCert = params.CACert + clientCert = params.ClientCert + clientKey = params.ClientKey + } + + if ok := rootCA.AppendCertsFromPEM([]byte(caCert)); !ok { + return nil, fmt.Errorf("failed to append CA cert to root CA bundle") + } + tlsCfg.RootCAs = rootCA + + if clientCert == "" && clientKey == "" { + return tlsCfg, nil + } + + certPair, err := tls.X509KeyPair([]byte(clientCert), []byte(clientKey)) + if err != nil { + return nil, err + } + + tlsCfg.Certificates = []tls.Certificate{certPair} + + return tlsCfg, nil +} + +func getTLSParamsFromSecret(ctx context.Context, client kubernetes.Interface, secretParams *troubleshootv1beta2.TLSSecret) (string, string, string, error) { + var caCert, clientCert, clientKey string + secret, err := client.CoreV1().Secrets(secretParams.Namespace).Get(ctx, secretParams.Name, metav1.GetOptions{}) + if err != nil { + return "", "", "", errors.Wrap(err, "failed to get secret") + } + + if val, ok := secret.StringData["cacert"]; ok { + caCert = val + } else { + return "", "", "", fmt.Errorf("failed to find 'cacert' key for CA cert data in secret") + } + + var foundClientCert, foundClientKey bool + if val, ok := secret.StringData["clientCert"]; ok { + clientCert = val + foundClientCert = true + } + + if val, ok := secret.StringData["clientKey"]; ok { + clientKey = val + foundClientKey = true + } + + if !foundClientCert && !foundClientKey { + // Cert only configuration + return caCert, "", "", nil + } + + if !foundClientKey { + return "", "", "", fmt.Errorf("failed to find 'clientKey' for client key data in secret") + } + + if !foundClientCert { + return "", "", "", fmt.Errorf("failed to find 'clientCert' for client cert data in secret") + } + + return caCert, clientCert, clientKey, nil +} diff --git a/pkg/collect/util_test.go b/pkg/collect/util_test.go index 64fe40e1..24fd7c72 100644 --- a/pkg/collect/util_test.go +++ b/pkg/collect/util_test.go @@ -1,10 +1,20 @@ package collect import ( + "context" + "math/rand" + "os" + "path/filepath" "testing" + "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + testclient "k8s.io/client-go/kubernetes/fake" ) func Test_selectorToString(t *testing.T) { @@ -107,3 +117,181 @@ func Test_DeterministicIDForCollector(t *testing.T) { }) } } + +func Test_createTLSConfig(t *testing.T) { + k8sClient := testclient.NewSimpleClientset() + + tests := []struct { + name string + tlsParams v1beta2.TLSParams + caCertOnly bool + hasError bool + }{ + { + name: "complete tls params creates config successfully", + tlsParams: v1beta2.TLSParams{ + CACert: getTestFixture(t, "db/ca.pem"), + ClientCert: getTestFixture(t, "db/client.pem"), + ClientKey: getTestFixture(t, "db/client-key.pem"), + }, + }, + { + name: "complete tls params in secret creates config successfully", + tlsParams: v1beta2.TLSParams{ + Secret: createTLSSecret(t, k8sClient, map[string]string{ + "cacert": getTestFixture(t, "db/ca.pem"), + "clientCert": getTestFixture(t, "db/client.pem"), + "clientKey": getTestFixture(t, "db/client-key.pem"), + }), + }, + }, + { + name: "tls params with skip verify creates config successfully", + tlsParams: v1beta2.TLSParams{ + SkipVerify: true, + }, + }, + { + name: "tls params with CA cert only in secret creates config successfully", + tlsParams: v1beta2.TLSParams{ + Secret: createTLSSecret(t, k8sClient, map[string]string{ + "cacert": getTestFixture(t, "db/ca.pem"), + }), + }, + caCertOnly: true, + }, + { + name: "tls params with CA cert only creates config successfully", + tlsParams: v1beta2.TLSParams{ + CACert: getTestFixture(t, "db/ca.pem"), + }, + caCertOnly: true, + }, + { + name: "empty TLS parameters fails to create config with error", + hasError: true, + }, + { + name: "missing CA cert fails to create config with error", + tlsParams: v1beta2.TLSParams{ + ClientCert: getTestFixture(t, "db/client.pem"), + ClientKey: getTestFixture(t, "db/client-key.pem"), + }, + hasError: true, + }, + { + name: "missing client cert fails to create config with error", + tlsParams: v1beta2.TLSParams{ + CACert: getTestFixture(t, "db/ca.pem"), + ClientKey: getTestFixture(t, "db/client-key.pem"), + }, + hasError: true, + }, + { + name: "missing client key fails to create config with error", + tlsParams: v1beta2.TLSParams{ + CACert: getTestFixture(t, "db/ca.pem"), + ClientCert: getTestFixture(t, "db/client.pem"), + }, + hasError: true, + }, + { + name: "missing CA cert in secret fails to create config with error", + tlsParams: v1beta2.TLSParams{ + Secret: createTLSSecret(t, k8sClient, map[string]string{ + "clientCert": getTestFixture(t, "db/client.pem"), + "clientKey": getTestFixture(t, "db/client-key.pem"), + }), + }, + hasError: true, + }, + { + name: "missing client cert in secret fails to create config with error", + tlsParams: v1beta2.TLSParams{ + Secret: createTLSSecret(t, k8sClient, map[string]string{ + "cacert": getTestFixture(t, "db/ca.pem"), + "clientKey": getTestFixture(t, "db/client-key.pem"), + }), + }, + hasError: true, + }, + { + name: "missing client key in secret fails to create config with error", + tlsParams: v1beta2.TLSParams{ + Secret: createTLSSecret(t, k8sClient, map[string]string{ + "cacert": getTestFixture(t, "db/ca.pem"), + "clientCert": getTestFixture(t, "db/client.pem"), + }), + }, + hasError: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tlsCfg, err := createTLSConfig(context.Background(), k8sClient, &tt.tlsParams) + assert.Equalf(t, err != nil, tt.hasError, "createTLSConfig() error = %v, wantErr %v", err, tt.hasError) + + if err == nil { + require.NotNil(t, tlsCfg) + + if tt.tlsParams.SkipVerify { + assert.True(t, tlsCfg.InsecureSkipVerify) + assert.Nil(t, tlsCfg.RootCAs) + assert.Nil(t, tlsCfg.Certificates) + } else { + // TLS parameter objects are opaque. Just check if they were created. + // There is no trivial way to inspect their metadata. Trust me :) + assert.NotNil(t, tlsCfg.RootCAs) + assert.Equal(t, tt.caCertOnly, tlsCfg.Certificates == nil) + assert.False(t, tlsCfg.InsecureSkipVerify) + } + } else { + t.Log(err) + assert.Nil(t, tlsCfg) + } + }) + } +} + +func randStringRunes(n int) string { + runes := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890") + b := make([]rune, n) + for i := range b { + b[i] = runes[rand.Intn(len(runes))] + } + return string(b) +} + +// createTLSSecret create a secret in a fake client +func createTLSSecret(t *testing.T, client kubernetes.Interface, secretData map[string]string) *v1beta2.TLSSecret { + t.Helper() + + // Generate unique names cause we reuse the same client + secretName := "secret-name-" + randStringRunes(20) + namespace := "namespace-" + randStringRunes(20) + + _, err := client.CoreV1().Secrets(namespace).Create( + context.Background(), + &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + }, + StringData: secretData, + }, + metav1.CreateOptions{}, + ) + require.NoError(t, err) + + return &v1beta2.TLSSecret{ + Namespace: namespace, + Name: secretName, + } +} + +func getTestFixture(t *testing.T, path string) string { + t.Helper() + p := filepath.Join("../../testdata", path) + b, err := os.ReadFile(p) + require.NoError(t, err) + return string(b) +} diff --git a/schemas/collector-troubleshoot-v1beta2.json b/schemas/collector-troubleshoot-v1beta2.json index 36344a78..444ffdef 100644 --- a/schemas/collector-troubleshoot-v1beta2.json +++ b/schemas/collector-troubleshoot-v1beta2.json @@ -521,6 +521,38 @@ "type": "string" } }, + "tls": { + "type": "object", + "properties": { + "cacert": { + "type": "string" + }, + "clientCert": { + "type": "string" + }, + "clientKey": { + "type": "string" + }, + "secret": { + "type": "object", + "required": [ + "name", + "namespace" + ], + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + } + }, + "skipVerify": { + "type": "boolean" + } + } + }, "uri": { "type": "string" } @@ -544,6 +576,38 @@ "type": "string" } }, + "tls": { + "type": "object", + "properties": { + "cacert": { + "type": "string" + }, + "clientCert": { + "type": "string" + }, + "clientKey": { + "type": "string" + }, + "secret": { + "type": "object", + "required": [ + "name", + "namespace" + ], + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + } + }, + "skipVerify": { + "type": "boolean" + } + } + }, "uri": { "type": "string" } @@ -567,6 +631,38 @@ "type": "string" } }, + "tls": { + "type": "object", + "properties": { + "cacert": { + "type": "string" + }, + "clientCert": { + "type": "string" + }, + "clientKey": { + "type": "string" + }, + "secret": { + "type": "object", + "required": [ + "name", + "namespace" + ], + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + } + }, + "skipVerify": { + "type": "boolean" + } + } + }, "uri": { "type": "string" } diff --git a/schemas/preflight-troubleshoot-v1beta2.json b/schemas/preflight-troubleshoot-v1beta2.json index b5e90186..d9dbe315 100644 --- a/schemas/preflight-troubleshoot-v1beta2.json +++ b/schemas/preflight-troubleshoot-v1beta2.json @@ -2564,6 +2564,38 @@ "type": "string" } }, + "tls": { + "type": "object", + "properties": { + "cacert": { + "type": "string" + }, + "clientCert": { + "type": "string" + }, + "clientKey": { + "type": "string" + }, + "secret": { + "type": "object", + "required": [ + "name", + "namespace" + ], + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + } + }, + "skipVerify": { + "type": "boolean" + } + } + }, "uri": { "type": "string" } @@ -2587,6 +2619,38 @@ "type": "string" } }, + "tls": { + "type": "object", + "properties": { + "cacert": { + "type": "string" + }, + "clientCert": { + "type": "string" + }, + "clientKey": { + "type": "string" + }, + "secret": { + "type": "object", + "required": [ + "name", + "namespace" + ], + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + } + }, + "skipVerify": { + "type": "boolean" + } + } + }, "uri": { "type": "string" } @@ -2610,6 +2674,38 @@ "type": "string" } }, + "tls": { + "type": "object", + "properties": { + "cacert": { + "type": "string" + }, + "clientCert": { + "type": "string" + }, + "clientKey": { + "type": "string" + }, + "secret": { + "type": "object", + "required": [ + "name", + "namespace" + ], + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + } + }, + "skipVerify": { + "type": "boolean" + } + } + }, "uri": { "type": "string" } diff --git a/schemas/supportbundle-troubleshoot-v1beta2.json b/schemas/supportbundle-troubleshoot-v1beta2.json index 4793f6f8..e1ffba71 100644 --- a/schemas/supportbundle-troubleshoot-v1beta2.json +++ b/schemas/supportbundle-troubleshoot-v1beta2.json @@ -2610,6 +2610,38 @@ "type": "string" } }, + "tls": { + "type": "object", + "properties": { + "cacert": { + "type": "string" + }, + "clientCert": { + "type": "string" + }, + "clientKey": { + "type": "string" + }, + "secret": { + "type": "object", + "required": [ + "name", + "namespace" + ], + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + } + }, + "skipVerify": { + "type": "boolean" + } + } + }, "uri": { "type": "string" } @@ -2633,6 +2665,38 @@ "type": "string" } }, + "tls": { + "type": "object", + "properties": { + "cacert": { + "type": "string" + }, + "clientCert": { + "type": "string" + }, + "clientKey": { + "type": "string" + }, + "secret": { + "type": "object", + "required": [ + "name", + "namespace" + ], + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + } + }, + "skipVerify": { + "type": "boolean" + } + } + }, "uri": { "type": "string" } @@ -2656,6 +2720,38 @@ "type": "string" } }, + "tls": { + "type": "object", + "properties": { + "cacert": { + "type": "string" + }, + "clientCert": { + "type": "string" + }, + "clientKey": { + "type": "string" + }, + "secret": { + "type": "object", + "required": [ + "name", + "namespace" + ], + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + } + }, + "skipVerify": { + "type": "boolean" + } + } + }, "uri": { "type": "string" } diff --git a/testdata/db/ca.pem b/testdata/db/ca.pem new file mode 100644 index 00000000..fbe84051 --- /dev/null +++ b/testdata/db/ca.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDYDCCAkigAwIBAgIUSM5DVlWGz84qTEXTOyQGCbwMfUgwDQYJKoZIhvcNAQEL +BQAwRzELMAkGA1UEBhMCVUsxFDASBgNVBAgTC094Zm9yZHNoaXJlMQ8wDQYDVQQH +EwZEaWRjb3QxETAPBgNVBAMTCERhdGFiYXNlMCAXDTIyMTEyMzEyMTYwMFoYDzIx +MjIxMDMwMTIxNjAwWjBHMQswCQYDVQQGEwJVSzEUMBIGA1UECBMLT3hmb3Jkc2hp +cmUxDzANBgNVBAcTBkRpZGNvdDERMA8GA1UEAxMIRGF0YWJhc2UwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDP27FhXvn3PNULwygCWlpAUQThnIp/GQBO +dA4P3bZRKQg3bSVcAd9oPaab8yTW8H/50lvR7EoZRA+06+XjkQ2eOrndvM2FML4h +q5c6IOMII60ARuvJTa+gPbQMP3fy7NN3DoacNd6I5NnvHQBGSVmhT7wYLkzq0rmT +98WwbPjPk0m+bncN94TTF3tQryqiQ8AuyL+HGaQS5NExB6Rp25jr0GXy+B5K4FbI +gg4Qv8xkpkhNn02vZFTPQage+cm6FoZ6ntN7uBdk4MJYEKXUana6hRH6fKvx3bS6 +T3LTXGlb1T8DX6yC5aI3c1BV3VeEfaqo1F0O2zIQdfKFMlRpG/8PAgMBAAGjQjBA +MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQHbBoB +7NHvhVEhrqGARbkW6fa1KDANBgkqhkiG9w0BAQsFAAOCAQEABtGMXguhuiTR6d7c +hS1AfoCKQp8tJSLmwy3uu8rhAMjRTxMNUHwfKjRz6WBU/YgrI7gi/BCkRR4wPqVM +95QtfE5MeSB8esFaDl3V4YdGjxwO6xfnRm3fre9S+2VGXiLFWjtMnV5F7a6k9lPr +HZceK0OzHObu4v/3p35BGq5KXfGy0K4AovmuG1nQ+okylu5FAZKIxjHkXze3h39O +UrdRr1sgWaVI+//aG7u14IQGZcN/8COk5akzwUgBQnS/v8IbjgX8f/seVtbEb0+q +WLWLjHpx5iYAD/Y7hCquO9aGBoDkbFbG/DBmVp/XMF4xFLeYMyA/pZlfPbgsVZgB +El37MQ== +-----END CERTIFICATE----- diff --git a/testdata/db/client-key.pem b/testdata/db/client-key.pem new file mode 100644 index 00000000..32f47637 --- /dev/null +++ b/testdata/db/client-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA2FXj0wxictJb2z2mXhEfQ6e1JBXQz5I3CGgFPNccyKC1Giq4 +rDJgPB13OmyCzQKI2D/VjI5ovOe84H/OBwXqOHrMnQ1NUuhohDsLn6eeW4TS0TmI +0bxs/LovY170LNWCo/W2Zry+h9R8lWBl0cFfHZhO2QTgdfSHUG+uF8BXHS6Tmj9T +asaGCKZQ+i3ahKM1PagauYShRnWeT4KTCeNcrKmbLhiOUMYaUYjIZ2dMDncnyOSN +t44NlaudSICpZzoSYAY0aMPwSedyiiT/1KCPHPPwfeZ5Y846aRmZuhmkj4XIdWJC +LYNCsflw6Jbe36CQTcpbvJYSzpszreB1argItQIDAQABAoIBAQCthZ8azok83jwt +i7KhKwyvwJpL2m/FvtsDlCP3W/fLeRQqa+/6tuF39o6GvzjfZdQL2I/akPOz6hEW +5YyFF5p6OQ1cP6oxAResUIwPQGyFrfFHPuXejUvCHzkZdLVYLN3/03u9xlDxNF/F +lS+2Ymt/moZMDlxYYI4U16+Ff5KgZRxukUZtmmvhktLBZaeFdPxLZyF/DC2V4fuu +3Me00H1Cc0oGpK/YSaKDQPk2GeXw2j0FWElNQJ1Qk1WtP1STG+c+YllyjlEtt2B6 +OEnT2Q1bcJn1Jf6zjNUmuI/DwW4UXw6SFvytI2PrdzM1iJwCKbfCr/7BJ5le/2YQ +KAKPggj1AoGBAN2x9a0gGL62cCrU+mRVKVMRMJjGmS7NyhISS7La3EF11eYMwuPn +Fi4M74gAvpxBKuSz883Oac99HdSp8conLNlaYGko4Lxtej9bqZ4Ds6J+Wi0dfxhH +EwN7+4+FLs+59F7REDeDHFT0PQInfkxEE8NegYpjc0zGhuwhOJo77/KvAoGBAPnP +ninEXFrpH66lEKqEhdwnC2KJZNBICmcGBUquqzqe9YC6x3rAIb12kmvtsW2ZyE0J +viv7im5t81iE+eBEESprpDoEglDSl2R3uj+Vmtq+jEG+cXr71323546da9F/0Rt7 +oQ2j/OxziVphLYaKeinbUtfH252kVp90ZKMlcqPbAoGBALSjhKMEPCVpUgwUbdHC +8mEU6JwTdk2EsNn4vmWFn2JXOWqxiztX1+K9947BzI5VKzYeytEvF1hRgjT+JF2r +fge8dEI7ZKMkMS/e6F4wdfY5LhEEr8KAZyW3qEpRIstvoYaAos4YrsUqHWvb8jUC +Y3gVUUgJsLEmMlnE1/IUVHdRAoGAdscyA9f6tbsstkSD5R/36dKixipfxezLDWs+ +buEYR1o3jwrAOGxMXqKXQjwVZfB93atpQc5rZtBeqVMo4Gpc4xelUXS9ZaKMG1gW +NY9zvthlJglgOcKvFnav4g6Vqlok2fVxgWPcqazRvSiBlfwX0/kVrcW4dX8jNNQJ +2cy/cosCgYEAhaO+zyrwdSE7gJcYAUcr/FY+aiwCrfkHy+3+oAdBSVmYqY3LCLMq +c+2WOH7Sabo3WFAUg3EqY+O87Wj+b6HWCuPYYqUDnXZwjfWbC4Xd+3RnG90F4HdS +8zLP0S/cLvCqNc1P+QWQ57ZhHEUJhiD4nCTDS1YYXF6HqPwBGr8POH0= +-----END RSA PRIVATE KEY----- diff --git a/testdata/db/client.pem b/testdata/db/client.pem new file mode 100644 index 00000000..0178da29 --- /dev/null +++ b/testdata/db/client.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDsDCCApigAwIBAgIUHnMIIZgJwDbjXw6aHnXJL5nUhuQwDQYJKoZIhvcNAQEL +BQAwRzELMAkGA1UEBhMCVUsxFDASBgNVBAgTC094Zm9yZHNoaXJlMQ8wDQYDVQQH +EwZEaWRjb3QxETAPBgNVBAMTCERhdGFiYXNlMCAXDTIyMTEyMzEyMTYwMFoYDzIx +MjIxMDMwMTIxNjAwWjBFMQswCQYDVQQGEwJVSzEUMBIGA1UECBMLT3hmb3Jkc2hp +cmUxDzANBgNVBAcTBkRpZGNvdDEPMA0GA1UEAxMGY2xpZW50MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2FXj0wxictJb2z2mXhEfQ6e1JBXQz5I3CGgF +PNccyKC1Giq4rDJgPB13OmyCzQKI2D/VjI5ovOe84H/OBwXqOHrMnQ1NUuhohDsL +n6eeW4TS0TmI0bxs/LovY170LNWCo/W2Zry+h9R8lWBl0cFfHZhO2QTgdfSHUG+u +F8BXHS6Tmj9TasaGCKZQ+i3ahKM1PagauYShRnWeT4KTCeNcrKmbLhiOUMYaUYjI +Z2dMDncnyOSNt44NlaudSICpZzoSYAY0aMPwSedyiiT/1KCPHPPwfeZ5Y846aRmZ +uhmkj4XIdWJCLYNCsflw6Jbe36CQTcpbvJYSzpszreB1argItQIDAQABo4GTMIGQ +MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw +DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUjdiaa0Yfd1H/QmJMhvJU7ZvU2i8wHwYD +VR0jBBgwFoAUB2waAezR74VRIa6hgEW5Fun2tSgwEQYDVR0RBAowCIIGY2xpZW50 +MA0GCSqGSIb3DQEBCwUAA4IBAQC66ONbumXIUfNbs4YLABQSHhk3UWaXo8GcW8W0 +pqi6WnN98Pzbo3rN6HFYgt09LIm0bzRAQDWqkG/D2revc1XI3bVtpQu4rfOl3LOO +1JHTgObLQNOpYnp3Bzbx7GQPWoGl06tNNQf20V1BGFQtRDpXGZZ3hROezc6AxC9S +xk9u2voc3sqImzOZm4seEIlH0bBh6jGu7Sx78GpmTEUuvPJoPf/jfQKQHXlucsIm ++K5BPPnjs3yNH1g3A8pJh58SQq6Bazm+pUfuA5VbVOoxS0VeHGnCPDpLh3Wu/HLc +BPXv6bGMK44ZXfMEiGQji7LMfPhM+94TBa7SoqrD+mUVyiBc +-----END CERTIFICATE-----