From ef6979c2800b700f6c810421fb92493a1b88cad2 Mon Sep 17 00:00:00 2001 From: David Gamba Date: Sat, 23 Mar 2024 10:37:32 -0600 Subject: [PATCH] kdecode: Add -pem flag to decode secret contents as a PEM certificate --- kdecode/README.adoc | 58 ++++++++++++++++++++++- kdecode/main.go | 111 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 165 insertions(+), 4 deletions(-) diff --git a/kdecode/README.adoc b/kdecode/README.adoc index 8e4be99..a6b12ab 100644 --- a/kdecode/README.adoc +++ b/kdecode/README.adoc @@ -2,7 +2,63 @@ Decodes K8s secret's data block. -Cluster selection can be done using the `--cluster` flag to avoid having to change the current context when comparing multiple clusters. +Change namespace with the `--namespace` or `-n` flag. + +Cluster selection can be done using the `--cluster` or `-c` flag to avoid having to change the current context when comparing multiple clusters. + +You can limit the output to a single key within the secret with the `--key` or `-k` flag. + +Additionally, if the secret is a PEM encoded certificate, you can the `--pem` flag to decode the certificate and print the details. + +== Examples + +.Decode a secret as a PEM certificate and pass the namespace, the cluster and filter to a single key +---- +kdecode argo-server-tls -n argocd -c prod-cluster -k tls.crt -pem +tls.crt= +Certificate 0 + Issuer: CN=R3,O=Let's Encrypt,C=US + Subject: CN=cd.argo.example.com + NotBefore: 2024-03-22 18:03:13 +0000 UTC + NotAfter: 2024-06-20 18:03:12 +0000 UTC + SignatureAlgorithm: SHA256-RSA + PublicKeyAlgorithm: RSA + SerialNumber: 111111111111111111111111111111111111111111 + KeyUsage: + OCSPServer: [http://r3.o.lencr.org] + IssuingCertificateURL: [http://r3.i.lencr.org/] + DNSNames: [cd.argo.example.com] +Certificate 1 + Issuer: CN=ISRG Root X1,O=Internet Security Research Group,C=US + Subject: CN=R3,O=Let's Encrypt,C=US + NotBefore: 2020-09-04 00:00:00 +0000 UTC + NotAfter: 2025-09-15 16:00:00 +0000 UTC + SignatureAlgorithm: SHA256-RSA + PublicKeyAlgorithm: RSA + SerialNumber: 222222222222222222222222222222222222222 + KeyUsage: + IssuingCertificateURL: [http://x1.i.lencr.org/] + CRLDistributionPoints: [http://x1.c.lencr.org/] +Certificate 2 + Issuer: CN=DST Root CA X3,O=Digital Signature Trust Co. + Subject: CN=ISRG Root X1,O=Internet Security Research Group,C=US + NotBefore: 2021-01-20 19:14:03 +0000 UTC + NotAfter: 2024-09-30 18:14:03 +0000 UTC + SignatureAlgorithm: SHA256-RSA + PublicKeyAlgorithm: RSA + SerialNumber: 33333333333333333333333333333333333333 + KeyUsage: + IssuingCertificateURL: [http://apps.identrust.com/roots/dstrootcax3.p7c] + CRLDistributionPoints: [http://crl.identrust.com/DSTROOTCAX3CRL.crl] +---- + +.Decode a secret as a PEM certificate and pass the namespace, use the current context and filter to a single key +---- +kdecode argo-server-tls -n argocd -k tls.crt -pem # current context +tls.crt= +Certificate 0 + ... +---- == Installation diff --git a/kdecode/main.go b/kdecode/main.go index d3cced1..a572179 100644 --- a/kdecode/main.go +++ b/kdecode/main.go @@ -2,6 +2,8 @@ package main import ( "context" + "crypto/x509" + "encoding/pem" "errors" "fmt" "io" @@ -26,8 +28,9 @@ func program(args []string) int { If a secret is not given, lists all secrets in the current namespace. Source: https://github.com/DavidGamba/dgtools`) - opt.Bool("quiet", false, opt.GetEnv("QUIET")) opt.SetCommandFn(Run) + opt.Bool("quiet", false, opt.GetEnv("QUIET")) + opt.Bool("pem", false, opt.Description("Parse the secret as a PEM encoded certificate")) opt.String("namespace", "") opt.String("key", "", opt.Description("limit output to the given key")) opt.String("cluster", "", opt.Description(`Allows targeting a different cluster from the current context. @@ -64,6 +67,7 @@ func Run(ctx context.Context, opt *getoptions.GetOpt, args []string) error { namespace := opt.Value("namespace").(string) cluster := opt.Value("cluster").(string) key := opt.Value("key").(string) + pem := opt.Value("pem").(bool) secret := "" if len(args) >= 1 { secret = args[0] @@ -99,14 +103,115 @@ func Run(ctx context.Context, opt *getoptions.GetOpt, args []string) error { } for k, v := range k8sSecret.Data { if (key != "" && k == key) || key == "" { - fmt.Printf("%s=%s\n", k, string(v)) + fmt.Printf("%s=", k) + if pem { + info, err := ParseCert(string(v)) + if err != nil { + fmt.Printf("%s\n", string(v)) + Logger.Printf("%s\n", err) + } else { + fmt.Printf("\n%s\n", info) + } + } else { + fmt.Printf("%s=%s\n", k, string(v)) + } } } for k, v := range k8sSecret.StringData { if (key != "" && k == key) || key == "" { - fmt.Printf("%s=%s\n", k, v) + fmt.Printf("%s=", k) + if pem { + info, err := ParseCert(string(v)) + if err != nil { + fmt.Printf("%s\n", string(v)) + Logger.Printf("%s\n", err) + } else { + fmt.Printf("\n%s\n", info) + } + } else { + fmt.Printf("%s=%s\n", k, v) + } } } return nil } + +func ParseCert(data string) (string, error) { + info := "" + blocks := []*pem.Block{} + rest := []byte(data) + for { + block, newRest := pem.Decode(rest) + if block == nil { + return info, fmt.Errorf("failed to parse certificate PEM") + } + blocks = append(blocks, block) + if len(newRest) == 0 { + break + } + rest = newRest + } + + for i, block := range blocks { + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return info, fmt.Errorf("failed to parse certificate: %w", err) + } + info += fmt.Sprintf("Certificate %d\n", i) + info += fmt.Sprintf("\tIssuer: %s\n", cert.Issuer) + info += fmt.Sprintf("\tSubject: %s\n", cert.Subject) + info += fmt.Sprintf("\tNotBefore: %s\n", cert.NotBefore) + info += fmt.Sprintf("\tNotAfter: %s\n", cert.NotAfter) + info += fmt.Sprintf("\tSignatureAlgorithm: %s\n", cert.SignatureAlgorithm) + info += fmt.Sprintf("\tPublicKeyAlgorithm: %s\n", cert.PublicKeyAlgorithm) + info += fmt.Sprintf("\tSerialNumber: %s\n", cert.SerialNumber) + info += fmt.Sprintf("\tKeyUsage: %s\n", KeyUsage(cert.KeyUsage)) + if len(cert.OCSPServer) > 0 { + info += fmt.Sprintf("\tOCSPServer: %v\n", cert.OCSPServer) + } + if len(cert.IssuingCertificateURL) > 0 { + info += fmt.Sprintf("\tIssuingCertificateURL: %v\n", cert.IssuingCertificateURL) + } + if len(cert.DNSNames) > 0 { + info += fmt.Sprintf("\tDNSNames: %v\n", cert.DNSNames) + } + if len(cert.EmailAddresses) > 0 { + info += fmt.Sprintf("\tEmailAddresses: %v\n", cert.EmailAddresses) + } + if len(cert.IPAddresses) > 0 { + info += fmt.Sprintf("\tIPAddresses: %v\n", cert.IPAddresses) + } + if len(cert.URIs) > 0 { + info += fmt.Sprintf("\tURIs: %v\n", cert.URIs) + } + if len(cert.CRLDistributionPoints) > 0 { + info += fmt.Sprintf("\tCRLDistributionPoints: %v\n", cert.CRLDistributionPoints) + } + } + return info, nil +} + +func KeyUsage(k x509.KeyUsage) string { + switch k { + case x509.KeyUsageDigitalSignature: + return "DigitalSignature" + case x509.KeyUsageContentCommitment: + return "ContentCommitment" + case x509.KeyUsageKeyEncipherment: + return "KeyEncipherment" + case x509.KeyUsageDataEncipherment: + return "DataEncipherment" + case x509.KeyUsageKeyAgreement: + return "KeyAgreement" + case x509.KeyUsageCertSign: + return "CertSign" + case x509.KeyUsageCRLSign: + return "CRLSign" + case x509.KeyUsageEncipherOnly: + return "EncipherOnly" + case x509.KeyUsageDecipherOnly: + return "DecipherOnly" + } + return "" +}