mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-02-14 10:19:54 +00:00
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:
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
108
pkg/collect/redis_test.go
Normal 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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
21
testdata/db/ca.pem
vendored
Normal 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
27
testdata/db/client-key.pem
vendored
Normal 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
22
testdata/db/client.pem
vendored
Normal 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-----
|
||||
Reference in New Issue
Block a user