mirror of
https://github.com/percona/percona-toolkit.git
synced 2025-09-19 02:05:23 +00:00
PT-2299 - collect openssl x509 certificate information for each secret
- Implemented the feature - Added test case
This commit is contained in:
@@ -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"
|
||||
|
@@ -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
|
||||
*/
|
||||
|
Reference in New Issue
Block a user