Improve logic

Signed-off-by: Maartje Eyskens <maartje@eyskens.me>
This commit is contained in:
Maartje Eyskens 2020-11-25 16:52:43 +01:00
parent bedb95a0a4
commit f671c811cf
2 changed files with 228 additions and 224 deletions

View File

@ -18,12 +18,16 @@ package secret
import (
"context"
"crypto/x509"
"errors"
"fmt"
corev1 "k8s.io/api/core/v1"
"net/url"
"strings"
"time"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/kubernetes"
@ -31,8 +35,48 @@ import (
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
"github.com/jetstack/cert-manager/pkg/util/pki"
)
const validForTemplate = `Valid for:
DNS Names: %s
URIs: %s
IP Addresses: %s
Email Addresses: %s
Usages: %s`
const validityPeriodTemplate = `Validity period:
Not Before: %s
Not After: %s`
const issuedByTemplate = `Issued By:
Common Name %s
Organization %s
OrganizationalUnit %s
Country: %s`
const issuedForTemplate = `Issued For:
Common Name %s
Organization %s
OrganizationalUnit %s
Country: %s`
const certificateTemplate = `Certificate:
Signing Algorithm: %s
Public Key Algorithm: %s
Serial Number: %s
Fingerprints: %s
Is a CA certificate: %v
CRL: %s
OCSP: %s`
const debuggingTemplate = `Debugging:
Trusted by this computer: %s
CRL Status: %s
OCSP Status: %s`
var (
long = templates.LongDesc(i18n.T(`
Get details about a kubernetes.io/tls typed secret`))
@ -122,11 +166,166 @@ func (o *Options) Run(args []string) error {
}
// TODO: use cmmeta
output, err := DescribeCertificate(secret.Data[corev1.TLSCertKey], secret.Data["ca.crt"])
if err != nil {
return fmt.Errorf("error when describing Secret %q: %w\n", args[0], err)
certData := secret.Data[corev1.TLSCertKey]
certs, err2 := splitPEMs(certData)
if err2 != nil {
return err2
}
fmt.Println(output)
if len(certs) < 1 {
return errors.New("no PEM data found in secret")
}
// we only want to inspect the leaf
x509Cert, err := pki.DecodeX509CertificateBytes(certs[0])
if err != nil {
return fmt.Errorf("error when parsing 'tls.crt': %w", err)
}
intermediates := [][]byte(nil)
if len(certs) > 1 {
intermediates = certs[1:]
}
out := []string{
describeValidFor(x509Cert),
describeValidityPeriod(x509Cert),
describeIssuedBy(x509Cert),
describeIssuedFor(x509Cert),
describeCertificate(x509Cert),
describeDebugging(x509Cert, intermediates, secret.Data[cmmeta.TLSCAKey]),
}
fmt.Println(strings.Join(out, "\n\n"))
return nil
}
func describeValidFor(cert *x509.Certificate) string {
return fmt.Sprintf(validForTemplate,
printSlice(cert.DNSNames),
printSlice(pki.URLsToString(cert.URIs)),
printSlice(pki.IPAddressesToString(cert.IPAddresses)),
printSlice(cert.EmailAddresses),
printKeyUsage(pki.BuildCertManagerKeyUsages(cert.KeyUsage, cert.ExtKeyUsage)),
)
}
func describeValidityPeriod(cert *x509.Certificate) string {
return fmt.Sprintf(validityPeriodTemplate,
cert.NotBefore.Format(time.RFC1123),
cert.NotAfter.Format(time.RFC1123),
)
}
func describeIssuedBy(cert *x509.Certificate) string {
return fmt.Sprintf(issuedByTemplate,
printOrNone(cert.Issuer.CommonName),
printSliceOrOne(cert.Issuer.Organization),
printSliceOrOne(cert.Issuer.OrganizationalUnit),
printSliceOrOne(cert.Issuer.Country),
)
}
func describeIssuedFor(cert *x509.Certificate) string {
return fmt.Sprintf(issuedForTemplate,
printOrNone(cert.Subject.CommonName),
printSliceOrOne(cert.Subject.Organization),
printSliceOrOne(cert.Subject.OrganizationalUnit),
printSliceOrOne(cert.Subject.Country),
)
}
func describeCertificate(cert *x509.Certificate) string {
return fmt.Sprintf(certificateTemplate,
cert.SignatureAlgorithm.String(),
cert.PublicKeyAlgorithm.String(),
cert.SerialNumber.String(),
fingerprintCert(cert),
cert.IsCA,
printSliceOrOne(cert.CRLDistributionPoints),
printSliceOrOne(cert.OCSPServer),
)
}
func describeDebugging(cert *x509.Certificate, intermediates [][]byte, ca []byte) string {
return fmt.Sprintf(debuggingTemplate,
describeTrusted(cert, intermediates),
describeCRL(cert),
describeOCSP(cert, intermediates, ca),
)
}
func describeCRL(cert *x509.Certificate) string {
if len(cert.CRLDistributionPoints) < 1 {
return "No CRL endpoints set"
}
hasChecked := false
for _, crlURL := range cert.CRLDistributionPoints {
u, err := url.Parse(crlURL)
if err != nil {
continue // not a valid URL
}
if u.Scheme != "ldap" && u.Scheme != "https" {
continue
}
valid, err := checkCRLValidCert(cert, crlURL)
if err != nil {
return fmt.Sprintf("Cannot check CRL: %s", err.Error())
}
if !valid {
return fmt.Sprintf("Revoked by %s", crlURL)
}
}
if !hasChecked {
return "No CRL endpoints we support found"
}
return "Valid"
}
func describeOCSP(cert *x509.Certificate, intermediates [][]byte, ca []byte) string {
if len(ca) > 1 {
intermediates = append([][]byte{ca}, intermediates...)
}
if len(intermediates) < 1 {
return "Cannot check OCSP, does not have a CA or intermediate certificate provided"
}
issuerCert, err := pki.DecodeX509CertificateBytes(intermediates[len(intermediates)-1])
if err != nil {
return fmt.Sprintf("Cannot parse intermediate certificate: %s", err.Error())
}
valid, err := checkOCSPValidCert(cert, issuerCert)
if err != nil {
return fmt.Sprintf("Cannot check OCSP: %s", err.Error())
}
if !valid {
return "Marked as revoked"
}
return "valid"
}
func describeTrusted(cert *x509.Certificate, intermediates [][]byte) string {
systemPool, err := x509.SystemCertPool()
for _, intermediate := range intermediates {
systemPool.AppendCertsFromPEM(intermediate)
}
if err != nil {
return fmt.Sprintf("error loading system CA trusts: %s", err.Error())
}
_, err = cert.Verify(x509.VerifyOptions{
Roots: systemPool,
CurrentTime: time.Now(),
})
if err == nil {
return "yes"
}
return fmt.Sprintf("no: %s", err.Error())
}

View File

@ -12,230 +12,13 @@ import (
"net/http"
"net/url"
"strings"
"time"
"golang.org/x/crypto/ocsp"
cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
"github.com/jetstack/cert-manager/pkg/util/pki"
)
const validForTemplate = `Valid for:
DNS Names: %s
URIs: %s
IP Addresses: %s
Email Addresses: %s
Usages: %s`
const validityPeriodTemplate = `Validity period:
Not Before: %s
Not After: %s`
const issuedByTemplate = `Issued By:
Common Name %s
Organization %s
OrganizationalUnit %s
Country: %s`
const issuedForTemplate = `Issued For:
Common Name %s
Organization %s
OrganizationalUnit %s
Country: %s`
const certificateTemplate = `Certificate:
Signing Algorithm: %s
Public Key Algorithm: %s
Serial Number: %s
Fingerprints: %s
Is a CA certificate: %v
CRL: %s
OCSP: %s`
const debuggingTemplate = `Debugging:
Trusted by this computer: %s
CRL Status: %s
OCSP Status: %s`
// DescribeCertificate retutring describing a PEM encoded X.509 certificate
func DescribeCertificate(certData []byte, ca []byte) (string, error) {
certs := [][]byte(nil)
for {
block, rest := pem.Decode(certData)
if block == nil {
break // got no more certs to decode
}
// ignore private key data
if block.Type == "CERTIFICATE" {
buf := bytes.NewBuffer(nil)
err := pem.Encode(buf, block)
if err != nil {
return "", fmt.Errorf("error when reencoding PEM: %s", err)
}
certs = append(certs, buf.Bytes())
}
certData = rest
}
if len(certs) < 1 {
return "", errors.New("no PEM data found in secret")
}
// we only want to inspect the leaf
x509Cert, err := pki.DecodeX509CertificateBytes(certs[0])
if err != nil {
return "", fmt.Errorf("error when parsing 'tls.crt': %w", err)
}
intermediates := [][]byte(nil)
if len(certs) > 1 {
intermediates = certs[1:]
}
out := []string{
describeValidFor(x509Cert),
describeValidityPeriod(x509Cert),
describeIssuedBy(x509Cert),
describeIssuedFor(x509Cert),
describeCertificate(x509Cert),
describeDebugging(x509Cert, intermediates, ca),
}
return strings.Join(out, "\n\n"), nil
}
func describeValidFor(cert *x509.Certificate) string {
return fmt.Sprintf(validForTemplate,
printSlice(cert.DNSNames),
printSlice(pki.URLsToString(cert.URIs)),
printSlice(pki.IPAddressesToString(cert.IPAddresses)),
printSlice(cert.EmailAddresses),
printKeyUsage(pki.BuildCertManagerKeyUsages(cert.KeyUsage, cert.ExtKeyUsage)),
)
}
func describeValidityPeriod(cert *x509.Certificate) string {
return fmt.Sprintf(validityPeriodTemplate,
cert.NotBefore.Format(time.RFC1123),
cert.NotAfter.Format(time.RFC1123),
)
}
func describeIssuedBy(cert *x509.Certificate) string {
return fmt.Sprintf(issuedByTemplate,
printOrNone(cert.Issuer.CommonName),
printSliceOrOne(cert.Issuer.Organization),
printSliceOrOne(cert.Issuer.OrganizationalUnit),
printSliceOrOne(cert.Issuer.Country),
)
}
func describeIssuedFor(cert *x509.Certificate) string {
return fmt.Sprintf(issuedForTemplate,
printOrNone(cert.Subject.CommonName),
printSliceOrOne(cert.Subject.Organization),
printSliceOrOne(cert.Subject.OrganizationalUnit),
printSliceOrOne(cert.Subject.Country),
)
}
func describeCertificate(cert *x509.Certificate) string {
return fmt.Sprintf(certificateTemplate,
cert.SignatureAlgorithm.String(),
cert.PublicKeyAlgorithm.String(),
cert.SerialNumber.String(),
fingerprint(cert),
cert.IsCA,
printSliceOrOne(cert.CRLDistributionPoints),
printSliceOrOne(cert.OCSPServer),
)
}
func describeDebugging(cert *x509.Certificate, intermediates [][]byte, ca []byte) string {
return fmt.Sprintf(debuggingTemplate,
describeTrusted(cert, intermediates),
describeCRL(cert),
describeOCSP(cert, intermediates, ca),
)
}
func describeCRL(cert *x509.Certificate) string {
if len(cert.CRLDistributionPoints) < 1 {
return "No CRL endpoints set"
}
hasChecked := false
for _, crlURL := range cert.CRLDistributionPoints {
u, err := url.Parse(crlURL)
if err != nil {
continue // not a valid URL
}
if u.Scheme != "ldap" && u.Scheme != "https" {
continue
}
valid, err := checkCRLValidCert(cert, crlURL)
if err != nil {
return fmt.Sprintf("Cannot check CRL: %s", err.Error())
}
if !valid {
return fmt.Sprintf("Revoked by %s", crlURL)
}
}
if !hasChecked {
return "No CRL endpoints we support found"
}
return "Valid"
}
func describeOCSP(cert *x509.Certificate, intermediates [][]byte, ca []byte) string {
if len(ca) > 1 {
intermediates = append([][]byte{ca}, intermediates...)
}
if len(intermediates) < 1 {
return "Cannot check OCSP, does not have a CA or intermediate certificate provided"
}
issuerCert, err := pki.DecodeX509CertificateBytes(intermediates[len(intermediates)-1])
if err != nil {
return fmt.Sprintf("Cannot parse intermediate certificate: %s", err.Error())
}
valid, err := checkOCSPValidCert(cert, issuerCert)
if err != nil {
return fmt.Sprintf("Cannot check OCSP: %s", err.Error())
}
if !valid {
return "Marked as revoked"
}
return "valid"
}
func describeTrusted(cert *x509.Certificate, intermediates [][]byte) string {
systemPool, err := x509.SystemCertPool()
for _, intermediate := range intermediates {
systemPool.AppendCertsFromPEM(intermediate)
}
if err != nil {
return fmt.Sprintf("error loading system CA trusts: %s", err.Error())
}
_, err = cert.Verify(x509.VerifyOptions{
Roots: systemPool,
CurrentTime: time.Now(),
})
if err == nil {
return "yes"
}
return fmt.Sprintf("no: %s", err.Error())
}
func fingerprint(cert *x509.Certificate) string {
func fingerprintCert(cert *x509.Certificate) string {
fingerprint := sha256.Sum256(cert.Raw)
var buf bytes.Buffer
@ -360,3 +143,25 @@ func printKeyUsage(in []cmapi.KeyUsage) string {
return "\n\t\t- " + strings.Trim(strings.Join(usageStrings, "\n\t\t- "), " ")
}
func splitPEMs(certData []byte) ([][]byte, error) {
certs := [][]byte(nil)
for {
block, rest := pem.Decode(certData)
if block == nil {
break // got no more certs to decode
}
// ignore private key data
if block.Type == "CERTIFICATE" {
buf := bytes.NewBuffer(nil)
err := pem.Encode(buf, block)
if err != nil {
return nil, fmt.Errorf("error when reencoding PEM: %s", err)
}
certs = append(certs, buf.Bytes())
}
certData = rest
}
return certs, nil
}