Skip to content

Commit

Permalink
cert-inspection: parse secret/configmap/files as kubeconfigs
Browse files Browse the repository at this point in the history
Extract CA and certs used in kubeconfigs as TLS artifacts too
  • Loading branch information
vrutkovs committed Jun 10, 2024
1 parent aed018c commit d2c7ae1
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 5 deletions.
94 changes: 94 additions & 0 deletions pkg/certs/cert-inspection/certgraphanalysis/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@ import (
"github.com/openshift/library-go/pkg/certs/cert-inspection/certgraphapi"
certificatesv1 "k8s.io/api/certificates/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/cert"
)

func InspectSecret(obj *corev1.Secret) (*certgraphapi.CertKeyPair, error) {
resourceString := fmt.Sprintf("secrets/%s[%s]", obj.Name, obj.Namespace)
tlsCrt, isTLS := obj.Data["tls.crt"]
if !isTLS {
if detail, err := InspectSecretAsKubeConfig(obj); err == nil {
return detail, nil
}
return nil, nil
}
//fmt.Printf("%s - tls (%v)\n", resourceString, obj.CreationTimestamp.UTC())
Expand Down Expand Up @@ -62,6 +67,9 @@ func InspectCSR(obj *certificatesv1.CertificateSigningRequest) (*certgraphapi.Ce
func InspectConfigMap(obj *corev1.ConfigMap) (*certgraphapi.CertificateAuthorityBundle, error) {
caBundle, ok := obj.Data["ca-bundle.crt"]
if !ok {
if detail, err := InspectConfigMapAsKubeConfig(obj); err == nil {
return detail, nil
}
return nil, nil
}
if len(caBundle) == 0 {
Expand All @@ -77,6 +85,92 @@ func InspectConfigMap(obj *corev1.ConfigMap) (*certgraphapi.CertificateAuthority
return nil, err
}
caBundleDetail = addConfigMapLocation(caBundleDetail, obj.Namespace, obj.Name)
return caBundleDetail, nil
}

func extractKubeConfigFromConfigMap(obj *corev1.ConfigMap) (*rest.Config, error) {
if obj == nil {
return nil, fmt.Errorf("empty object")
}
for _, v := range obj.Data {
kubeConfig, err := clientcmd.NewClientConfigFromBytes([]byte(v))
if err != nil {
continue
}
if clientConfig, err := kubeConfig.ClientConfig(); err == nil {
return clientConfig, nil
}
}
return nil, nil
}

func extractKubeConfigFromSecret(obj *corev1.Secret) (*rest.Config, error) {
if obj == nil {
return nil, fmt.Errorf("empty object")
}
for _, v := range obj.Data {
kubeConfig, err := clientcmd.NewClientConfigFromBytes(v)
if err != nil {
continue
}
if clientConfig, err := kubeConfig.ClientConfig(); err == nil {
return clientConfig, nil
}
}
return nil, nil
}

func GetCAFromKubeConfig(kubeConfig *rest.Config, namespace, name string) (*certgraphapi.CertificateAuthorityBundle, error) {
if kubeConfig == nil {
return nil, fmt.Errorf("empty kubeconfig")
}
certificates, err := cert.ParseCertsPEM(kubeConfig.CAData)
if err != nil {
return nil, err
}
caBundleDetail, err := toCABundle(certificates)
if err != nil {
return nil, err
}
if len(namespace) > 0 && len(name) > 0 {
caBundleDetail = addConfigMapLocation(caBundleDetail, namespace, name)
}
return caBundleDetail, nil
}

func GetCertKeyPairFromKubeConfig(kubeConfig *rest.Config, namespace, name string) (*certgraphapi.CertKeyPair, error) {
if kubeConfig == nil {
return nil, fmt.Errorf("empty kubeconfig")
}
certificates, err := cert.ParseCertsPEM(kubeConfig.CertData)
if err != nil {
return nil, err
}
for _, certificate := range certificates {
detail, err := toCertKeyPair(certificate)
if err != nil {
return nil, err
}
if len(namespace) > 0 && len(name) > 0 {
detail = addSecretLocation(detail, namespace, name)
}
return detail, nil
}
return nil, fmt.Errorf("didn't see that coming")
}

func InspectConfigMapAsKubeConfig(obj *corev1.ConfigMap) (*certgraphapi.CertificateAuthorityBundle, error) {
kubeConfig, err := extractKubeConfigFromConfigMap(obj)
if err != nil {
return nil, err
}
return GetCAFromKubeConfig(kubeConfig, obj.Namespace, obj.Name)
}

func InspectSecretAsKubeConfig(obj *corev1.Secret) (*certgraphapi.CertKeyPair, error) {
kubeConfig, err := extractKubeConfigFromSecret(obj)
if err != nil {
return nil, err
}
return GetCertKeyPairFromKubeConfig(kubeConfig, obj.Namespace, obj.Name)
}
53 changes: 48 additions & 5 deletions pkg/certs/cert-inspection/certgraphanalysis/disk_certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (

"github.com/openshift/library-go/pkg/certs/cert-inspection/certgraphapi"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/cert"
)

Expand All @@ -38,7 +40,7 @@ func gatherSecretsFromDisk(ctx context.Context, dir string, options certGenerati
}

fmt.Fprintf(os.Stdout, "Checking if %s is a certificate or secret key.\n", path)
if details, err := parseFileAsTLSArtifact(path, dir); err == nil && details != nil {
if details, err := parseFileAsCertKeyPair(path); err == nil && details != nil {
for i, detail := range details {
fmt.Fprintf(os.Stdout, "Found certkeypair #%d in %s.\n", i+1, path)
options.rewriteCertKeyPair(metav1.ObjectMeta{}, detail)
Expand Down Expand Up @@ -174,13 +176,30 @@ func parseBlockAsECDSAPrivateKey(key *ecdsa.PrivateKey, path string) *certgrapha
}
}

func parseFileAsTLSArtifact(path, dir string) ([]*certgraphapi.CertKeyPair, error) {
func parseFileAsCertKeyPair(path string) ([]*certgraphapi.CertKeyPair, error) {
var details []*certgraphapi.CertKeyPair

bytes, err := os.ReadFile(path)
if err != nil {
return nil, err
}
// Parse as kubeconfig
if kubeConfig, err := parseFileAsKubeConfig(path); err == nil {
if detail, err := GetCertKeyPairFromKubeConfig(kubeConfig, "", ""); err == nil {
fmt.Fprintf(os.Stdout, "Found a valid kubeconfig\n")
detail.Spec.OnDiskLocations = []certgraphapi.OnDiskCertKeyPairLocation{
{
Cert: certgraphapi.OnDiskLocation{
Path: path,
},
Key: certgraphapi.OnDiskLocation{
Path: path,
},
}}
details = append(details, detail)
return details, nil
}
}
// Parse all blocks
for len(bytes) > 0 {
fmt.Fprintf(os.Stdout, "Parsing block with length %d \n", len(bytes))
Expand All @@ -200,16 +219,28 @@ func parseFileAsTLSArtifact(path, dir string) ([]*certgraphapi.CertKeyPair, erro
return details, nil
}

func parseFileAsCA(path, dir string) (*certgraphapi.CertificateAuthorityBundle, error) {
func parseFileAsCA(path string) (*certgraphapi.CertificateAuthorityBundle, error) {
bytes, err := os.ReadFile(path)
if err != nil {
return nil, err
}
// Parse as kubeconfig
if kubeConfig, err := parseFileAsKubeConfig(path); err == nil {
fmt.Fprintf(os.Stdout, "Found a valid kubeconfig\n")
if detail, err := GetCAFromKubeConfig(kubeConfig, "", ""); err == nil {
detail.Spec.OnDiskLocations = []certgraphapi.OnDiskLocation{
{
Path: path,
},
}
return detail, nil
}
}
certificates, err := cert.ParseCertsPEM(bytes)
if err != nil {
return nil, nil
}
// Check that the first certificat is a CA
// Check that the first certificate is a CA
if len(certificates) == 0 {
return nil, fmt.Errorf("no certificates found")
}
Expand All @@ -228,6 +259,18 @@ func parseFileAsCA(path, dir string) (*certgraphapi.CertificateAuthorityBundle,
return detail, nil
}

func parseFileAsKubeConfig(path string) (*rest.Config, error) {
bytes, err := os.ReadFile(path)
if err != nil {
return nil, err
}
kubeConfig, err := clientcmd.NewClientConfigFromBytes(bytes)
if err != nil {
return nil, err
}
return kubeConfig.ClientConfig()
}

func gatherCABundlesFromDisk(ctx context.Context, dir string, options certGenerationOptionList) ([]*certgraphapi.CertificateAuthorityBundle, []*certgraphapi.OnDiskLocationWithMetadata, error) {
ret := []*certgraphapi.CertificateAuthorityBundle{}
metadataList := []*certgraphapi.OnDiskLocationWithMetadata{}
Expand All @@ -246,7 +289,7 @@ func gatherCABundlesFromDisk(ctx context.Context, dir string, options certGenera
return nil
}
fmt.Fprintf(os.Stdout, "Checking if %s is a CA bundle.\n", path)
if detail, err := parseFileAsCA(path, dir); err == nil && detail != nil {
if detail, err := parseFileAsCA(path); err == nil && detail != nil {
fmt.Fprintf(os.Stdout, "Found CA bundle in %s.\n", path)
options.rewriteCABundle(metav1.ObjectMeta{}, detail)

Expand Down

0 comments on commit d2c7ae1

Please sign in to comment.