PT-2299 - collect openssl x509 certificate information for each secret

- Implemented the feature
- Added test case
This commit is contained in:
Sveta Smirnova
2024-05-10 00:05:47 +03:00
parent 782a3c0b1b
commit e1390c4a52
4 changed files with 275 additions and 6 deletions

View File

@@ -6,6 +6,7 @@ import (
"compress/gzip"
"encoding/base64"
"encoding/json"
"html/template"
"log"
"os"
"os/exec"
@@ -18,6 +19,13 @@ import (
corev1 "k8s.io/api/core/v1"
)
// sslSecret struct for dumping certificates
type sslSecret struct {
secret string
resource string
dataNames []string
}
// Dumper struct is for dumping cluster
type Dumper struct {
cmd string
@@ -31,6 +39,7 @@ type Dumper struct {
mode int64
crType string
forwardport string
sslSecrets []sslSecret
}
var resourcesRe = regexp.MustCompile(`(\w+)\.(\w+).percona\.com`)
@@ -113,8 +122,36 @@ func New(location, namespace, resource string, kubeconfig string, forwardport st
"perconaservermongodbs",
)
}
sslSecrets := make([]sslSecret, 0)
filePaths := make([]string, 0)
if resourceType(resource) == "pxc" {
switch resource {
case "pg":
sslSecrets = append(sslSecrets,
sslSecret{
secret: "{{ .Name }}-ssl-ca",
resource: "perconapgclusters",
dataNames: []string{"ca.crt"},
},
sslSecret{
secret: "pgo.tls",
resource: "perconapgclusters",
dataNames: []string{"tls.crt"},
},
)
case "pgv2":
sslSecrets = append(sslSecrets,
sslSecret{
secret: "{{ .Name }}-cluster-cert",
resource: "pg",
dataNames: []string{"ca.crt", "tls.crt"},
},
sslSecret{
secret: "pgo-root-cacert",
resource: "pg",
dataNames: []string{"root.crt"},
},
)
case "pxc":
filePaths = append(filePaths,
"var/lib/mysql/mysqld-error.log",
"var/lib/mysql/innobackup.backup.log",
@@ -126,8 +163,32 @@ func New(location, namespace, resource string, kubeconfig string, forwardport st
"var/lib/mysql/auto.cnf",
)
d.fileContainer = "logs"
sslSecrets = append(sslSecrets,
sslSecret{
secret: "{{ .Name }}-ssl",
resource: "pxc",
dataNames: []string{"ca.crt", "tls.crt"},
},
)
case "ps":
sslSecrets = append(sslSecrets,
sslSecret{
secret: "{{ .Name }}-ssl",
resource: "ps",
dataNames: []string{"ca.crt", "tls.crt"},
},
)
case "psmdb":
sslSecrets = append(sslSecrets,
sslSecret{
secret: "{{ .Name }}-ssl",
resource: "psmdb",
dataNames: []string{"ca.crt", "tls.crt"},
},
)
}
d.resources = resources
d.sslSecrets = sslSecrets
d.crType = resource
d.filePaths = filePaths
return d
@@ -256,7 +317,7 @@ func (d *Dumper) DumpCluster() error {
crName = pod.Labels["app.kubernetes.io/instance"]
}
// Get summary
output, err = d.getPodSummary(resourceType(d.crType), pod.Name, crName, ns.Name, tw)
output, err = d.getPodSummary(resourceType(d.crType), pod.Name, crName, ns.Name)
if err != nil {
d.logError(err.Error(), d.crType, pod.Name)
err = addToArchive(location, d.mode, []byte(err.Error()), tw)
@@ -289,6 +350,13 @@ func (d *Dumper) DumpCluster() error {
log.Printf("Error: get %s resource: %v", resource, err)
}
}
for _, s := range d.sslSecrets {
err = d.getSSLCertificates(s, ns.Name, tw)
if err != nil {
log.Printf("Error: get SSL certificates in %s: %v", s.secret, err)
}
}
}
err = d.getResource("nodes", "", false, tw)
@@ -390,7 +458,7 @@ func (d *Dumper) getIndividualFiles(namespace string, podName, path, location st
return addToArchive(location+"/"+path, d.mode, output, tw)
}
func (d *Dumper) getPodSummary(resource, podName, crName string, namespace string, tw *tar.Writer) ([]byte, error) {
func (d *Dumper) getPodSummary(resource, podName, crName string, namespace string) ([]byte, error) {
var (
summCmdName string
ports string
@@ -476,7 +544,7 @@ func (d *Dumper) getPodSummary(resource, podName, crName string, namespace strin
cmd.Stderr = &errb
err := cmd.Run()
if err != nil {
return nil, errors.Errorf("error: %v\nstderr: %sstdout: %s", err, errb.String(), outb.String())
return nil, errors.Wrapf(err, "stderr: %s\nstdout: %s", errb.String(), outb.String())
}
return outb.Bytes(), nil
}
@@ -508,6 +576,81 @@ func (d *Dumper) getDataFromSecret(secretName, dataName string, namespace string
return string(pass), nil
}
func (d *Dumper) getSSLDataFromSecret(secretName, dataName string, namespace string) (string, error) {
data, err := d.runCmd("get", "secrets/"+secretName, "-o", "go-template='{{ index .data \""+dataName+"\" | base64decode }}'", "-n", namespace)
if err != nil {
return "", errors.Wrap(err, "run get secret cmd")
}
return string(data), nil
}
func (d *Dumper) getSSLCertificates(secret sslSecret, namespace string, tw *tar.Writer) error {
cr := struct {
Items []struct {
Metadata struct {
Name string `json:"name"`
} `json:"metadata"`
} `json:"items"`
}{}
output, err := d.runCmd("get", secret.resource, "-o", "json", "-n", namespace)
if err != nil {
return errors.Wrap(err, "get "+secret.resource)
}
err = json.Unmarshal(output, &cr)
if err != nil {
return errors.Wrapf(err, "unmarshal %s cr", secret.resource)
}
if len(cr.Items) > 1 {
return errors.Wrap(err, "Unexpected structure for resource "+secret.resource)
}
for _, item := range cr.Items {
location := d.location
if len(namespace) > 0 {
location = filepath.Join(d.location, namespace)
}
var nb bytes.Buffer
t := template.Must(template.New("secret").Parse(secret.secret))
t.Execute(&nb, item.Metadata)
name := nb.String()
location = filepath.Join(location, name)
result := make([]byte, 0)
for _, dn := range secret.dataNames {
result = append(result, dn+"\n"...)
data, err := d.getSSLDataFromSecret(name, dn, namespace)
if err != nil {
errors.Wrapf(err, "Error getting certificate %s from secret %s", dn, name)
}
var outb, errb bytes.Buffer
cmd := exec.Command("sh", "-c", "echo "+data+" | openssl x509 -noout -text")
cmd.Stdout = &outb
cmd.Stderr = &errb
err = cmd.Run()
if err != nil {
errors.Wrapf(err, "stderr: %s\nstdout: %s", errb.String(), outb.String())
}
result = append(result, outb.Bytes()...)
}
err = addToArchive(location, d.mode, result, tw)
if err != nil {
return errors.Wrapf(err, "Cannot add certificates in the secret %s into resulting archive", name)
}
}
return nil
}
func resourceType(s string) string {
if s == "pxc" || strings.HasPrefix(s, "pxc/") {
return "pxc"

View File

@@ -214,6 +214,123 @@ func TestResourceOption(t *testing.T) {
}
}
/*
PT-2299 - collect openssl x509 certificate information for each secret
*/
func TestSSLResourceOption(t *testing.T) {
tests := []struct {
name string
resource string
cmds [][]string // slice of commands to execute
want []string // slice of expected results
kubeconfig string
}{
{
name: "auto pxc",
resource: "auto",
cmds: [][]string{
{"tar", "--to-command", "grep -m 1 -o ca.crt", "-xzf", "cluster-dump.tar.gz", "--wildcards", "cluster-dump/*/*ssl"},
{"tar", "--to-command", "grep -m 1 -o Certificate", "-xzf", "cluster-dump.tar.gz", "--wildcards", "cluster-dump/*/*ssl"},
{"tar", "--to-command", "grep -m 1 -o tls.crt", "-xzf", "cluster-dump.tar.gz", "--wildcards", "cluster-dump/*/*ssl"},
},
want: []string{
"ca.crt",
"Certificate",
"tls.crt",
},
kubeconfig: os.Getenv("KUBECONFIG_PXC"),
},
{
name: "auto ps",
resource: "auto",
cmds: [][]string{
{"tar", "--to-command", "grep -m 1 -o ca.crt", "-xzf", "cluster-dump.tar.gz", "--wildcards", "cluster-dump/*/*ssl"},
{"tar", "--to-command", "grep -m 1 -o Certificate", "-xzf", "cluster-dump.tar.gz", "--wildcards", "cluster-dump/*/*ssl"},
{"tar", "--to-command", "grep -m 1 -o tls.crt", "-xzf", "cluster-dump.tar.gz", "--wildcards", "cluster-dump/*/*ssl"},
},
want: []string{
"ca.crt",
"Certificate",
"tls.crt",
},
kubeconfig: os.Getenv("KUBECONFIG_PS"),
},
{
name: "auto psmdb",
resource: "auto",
cmds: [][]string{
{"tar", "--to-command", "grep -m 1 -o ca.crt", "-xzf", "cluster-dump.tar.gz", "--wildcards", "cluster-dump/*/*ssl"},
{"tar", "--to-command", "grep -m 1 -o Certificate", "-xzf", "cluster-dump.tar.gz", "--wildcards", "cluster-dump/*/*ssl"},
{"tar", "--to-command", "grep -m 1 -o tls.crt", "-xzf", "cluster-dump.tar.gz", "--wildcards", "cluster-dump/*/*ssl"},
},
want: []string{
"ca.crt",
"Certificate",
"tls.crt",
},
kubeconfig: os.Getenv("KUBECONFIG_PSMDB"),
},
{
name: "auto pg",
resource: "auto",
cmds: [][]string{
{"tar", "--to-command", "grep -m 1 -o ca.crt", "-xzf", "cluster-dump.tar.gz", "--wildcards", "cluster-dump/*/*ssl-ca"},
{"tar", "--to-command", "grep -m 1 -o Certificate", "-xzf", "cluster-dump.tar.gz", "--wildcards", "cluster-dump/*/*ssl-ca"},
{"tar", "--to-command", "grep -m 1 -o tls.crt", "-xzf", "cluster-dump.tar.gz", "--wildcards", "cluster-dump/*/pgo.tls"},
{"tar", "--to-command", "grep -m 1 -o Certificate", "-xzf", "cluster-dump.tar.gz", "--wildcards", "cluster-dump/*/pgo.tls"},
},
want: []string{
"ca.crt",
"Certificate",
"tls.crt",
"Certificate",
},
kubeconfig: os.Getenv("KUBECONFIG_PG"),
},
{
name: "auto pgv2",
resource: "auto",
cmds: [][]string{
{"tar", "--to-command", "grep -m 1 -o ca.crt", "-xzf", "cluster-dump.tar.gz", "--wildcards", "cluster-dump/*/*cluster-cert"},
{"tar", "--to-command", "grep -m 1 -o Certificate", "-xzf", "cluster-dump.tar.gz", "--wildcards", "cluster-dump/*/*cluster-cert"},
{"tar", "--to-command", "grep -m 1 -o tls.crt", "-xzf", "cluster-dump.tar.gz", "--wildcards", "cluster-dump/*/*cluster-cert"},
{"tar", "--to-command", "grep -m 1 -o root.crt", "-xzf", "cluster-dump.tar.gz", "--wildcards", "cluster-dump/*/pgo-root-cacert"},
{"tar", "--to-command", "grep -m 1 -o Certificate", "-xzf", "cluster-dump.tar.gz", "--wildcards", "cluster-dump/*/pgo-root-cacert"},
},
want: []string{
"ca.crt",
"Certificate",
"tls.crt",
"root.crt",
"Certificate",
},
kubeconfig: os.Getenv("KUBECONFIG_PG2"),
},
}
for _, test := range tests {
cmd := exec.Command("../../../bin/pt-k8s-debug-collector", "--kubeconfig", test.kubeconfig, "--forwardport", os.Getenv("FORWARDPORT"), "--resource", test.resource)
if err := cmd.Run(); err != nil {
t.Errorf("error executing pt-k8s-debug-collector: %s", err.Error())
}
defer func() {
cmd = exec.Command("rm", "-f", "cluster-dump.tar.gz")
if err := cmd.Run(); err != nil {
t.Errorf("error cleaning up test data: %s", err.Error())
}
}()
for ind, testcmd := range test.cmds {
out, err := exec.Command(testcmd[0], testcmd[1:]...).Output()
if err != nil {
t.Errorf("test %s, error running command %s:\n%s\n\nCommand output:\n%s", test.name, testcmd, err.Error(), out)
}
if strings.TrimRight(bytes.NewBuffer(out).String(), "\n") != test.want[ind] {
t.Errorf("test %s, output is not as expected\nOutput: %s\nWanted: %s", test.name, out, test.want)
}
}
}
}
/*
Option --version
*/