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
This commit is contained in:
Evans Mungai
2022-11-29 17:47:52 +00:00
committed by GitHub
parent c4c66633e5
commit bfb77ad601
17 changed files with 1039 additions and 17 deletions

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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

View File

@@ -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())
}
}

108
pkg/collect/redis_test.go Normal file
View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

21
testdata/db/ca.pem vendored Normal file
View File

@@ -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-----

27
testdata/db/client-key.pem vendored Normal file
View File

@@ -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-----

22
testdata/db/client.pem vendored Normal file
View File

@@ -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-----