diff --git a/pkg/certs/cert-inspection/certgraphanalysis/analyzer.go b/pkg/certs/cert-inspection/certgraphanalysis/analyzer.go index 38eebdc482..464f761904 100644 --- a/pkg/certs/cert-inspection/certgraphanalysis/analyzer.go +++ b/pkg/certs/cert-inspection/certgraphanalysis/analyzer.go @@ -6,40 +6,34 @@ import ( "github.com/openshift/library-go/pkg/certs/cert-inspection/certgraphapi" certificatesv1 "k8s.io/api/certificates/v1" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "k8s.io/client-go/util/cert" ) -func InspectSecret(obj *corev1.Secret) (*certgraphapi.CertKeyPair, error) { - resourceString := fmt.Sprintf("secrets/%s[%s]", obj.Name, obj.Namespace) +func InspectSecret(obj *corev1.Secret) ([]*certgraphapi.CertKeyPair, error) { tlsCrt, isTLS := obj.Data["tls.crt"] - if !isTLS { - return nil, nil - } - //fmt.Printf("%s - tls (%v)\n", resourceString, obj.CreationTimestamp.UTC()) - if len(tlsCrt) == 0 { - return nil, fmt.Errorf("%s MISSING tls.crt content\n", resourceString) - } - - certificates, err := cert.ParseCertsPEM([]byte(tlsCrt)) - if err != nil { - return nil, err - } - for _, certificate := range certificates { - detail, err := toCertKeyPair(certificate) - if err != nil { - return nil, err + if !isTLS || len(tlsCrt) == 0 { + if detail, err := InspectSecretAsKubeConfig(obj); err == nil { + return detail, nil } - detail = addSecretLocation(detail, obj.Namespace, obj.Name) - return detail, nil + return nil, nil } - return nil, fmt.Errorf("didn't see that coming") + return extractCertKeyPairsFromBytes("secret", &obj.ObjectMeta, tlsCrt) } -func inspectCSR(resourceString, objName string, certificate []byte) (*certgraphapi.CertKeyPair, error) { +func extractCertKeyPairsFromBytes(resourceType string, obj *metav1.ObjectMeta, certificate []byte) ([]*certgraphapi.CertKeyPair, error) { + resourceString := "" + if obj != nil { + resourceString = fmt.Sprintf("%s/%s[%s]", resourceType, obj.Name, obj.Namespace) + } if len(certificate) == 0 { return nil, fmt.Errorf("%s MISSING issued certificate\n", resourceString) } + certKeyPairDetails := []*certgraphapi.CertKeyPair{} certificates, err := cert.ParseCertsPEM([]byte(certificate)) if err != nil { return nil, err @@ -49,34 +43,124 @@ func inspectCSR(resourceString, objName string, certificate []byte) (*certgrapha if err != nil { return nil, err } - return detail, nil + if resourceType == "secret" && obj != nil { + detail = addSecretLocation(detail, obj.Namespace, obj.Name) + } + certKeyPairDetails = append(certKeyPairDetails, detail) } - return nil, fmt.Errorf("didn't see that coming") + return certKeyPairDetails, nil } -func InspectCSR(obj *certificatesv1.CertificateSigningRequest) (*certgraphapi.CertKeyPair, error) { - resourceString := fmt.Sprintf("csr/%s[%s]", obj.Name, obj.Namespace) - return inspectCSR(resourceString, obj.Name, obj.Status.Certificate) +func InspectCSR(obj *certificatesv1.CertificateSigningRequest) ([]*certgraphapi.CertKeyPair, error) { + return extractCertKeyPairsFromBytes("csr", &obj.ObjectMeta, obj.Status.Certificate) } func InspectConfigMap(obj *corev1.ConfigMap) (*certgraphapi.CertificateAuthorityBundle, error) { - caBundle, ok := obj.Data["ca-bundle.crt"] - if !ok { - return nil, nil + if details, err := InspectConfigMapAsKubeConfig(obj); err == nil { + return details, nil } - if len(caBundle) == 0 { + + caBundle, ok := obj.Data["ca-bundle.crt"] + if !ok || len(caBundle) == 0 { return nil, nil } certificates, err := cert.ParseCertsPEM([]byte(caBundle)) if err != nil { - return nil, err + return nil, nil } caBundleDetail, err := toCABundle(certificates) if err != nil { 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) (*clientcmdapi.Config, error) { + if obj == nil { + return nil, fmt.Errorf("empty object") + } + for _, v := range obj.Data { + clientConfig, err := clientcmd.NewClientConfigFromBytes(v) + if err != nil { + continue + } + if kubeConfig, err := clientConfig.RawConfig(); err == nil && kubeConfig.CurrentContext != "" { + return &kubeConfig, 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, nil + } + 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 GetCertKeyPairsFromKubeConfig(authInfo *clientcmdapi.AuthInfo, obj *metav1.ObjectMeta) ([]*certgraphapi.CertKeyPair, error) { + if authInfo == nil { + return nil, fmt.Errorf("empty authinfo") + } + return extractCertKeyPairsFromBytes("secret", obj, authInfo.ClientCertificateData) +} + +func InspectConfigMapAsKubeConfig(obj *corev1.ConfigMap) (*certgraphapi.CertificateAuthorityBundle, error) { + kubeConfig, err := extractKubeConfigFromConfigMap(obj) + if err != nil { + return nil, err + } + if kubeConfig == nil { + return nil, fmt.Errorf("empty kubeconfig") + } + + 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 + } + if kubeConfig == nil { + return nil, fmt.Errorf("empty kubeconfig") + } + certKeyPairInfos := []*certgraphapi.CertKeyPair{} + for _, v := range kubeConfig.AuthInfos { + certKeyPairInfo, err := GetCertKeyPairsFromKubeConfig(v, &obj.ObjectMeta) + if err != nil { + continue + } + certKeyPairInfos = append(certKeyPairInfos, certKeyPairInfo...) + } + return certKeyPairInfos, nil +} diff --git a/pkg/certs/cert-inspection/certgraphanalysis/collector.go b/pkg/certs/cert-inspection/certgraphanalysis/collector.go index 6d13dc5d5b..2dd3eed0a5 100644 --- a/pkg/certs/cert-inspection/certgraphanalysis/collector.go +++ b/pkg/certs/cert-inspection/certgraphanalysis/collector.go @@ -113,7 +113,6 @@ func gatherFilteredCerts(ctx context.Context, kubeClient kubernetes.Interface, a continue } options.rewriteCABundle(configMap.ObjectMeta, details) - caBundles = append(caBundles, details) inClusterResourceData.CertificateAuthorityBundles = append(inClusterResourceData.CertificateAuthorityBundles, @@ -149,12 +148,14 @@ func gatherFilteredCerts(ctx context.Context, kubeClient kubernetes.Interface, a errs = append(errs, err) continue } - if details == nil { + if len(details) == 0 { continue } - options.rewriteCertKeyPair(secret.ObjectMeta, details) - certs = append(certs, details) + for i := range details { + options.rewriteCertKeyPair(secret.ObjectMeta, details[i]) + } + certs = append(certs, details...) inClusterResourceData.CertKeyPairs = append(inClusterResourceData.CertKeyPairs, certgraphapi.PKIRegistryInClusterCertKeyPair{ SecretLocation: certgraphapi.InClusterSecretLocation{ diff --git a/pkg/certs/cert-inspection/certgraphanalysis/disk_certs.go b/pkg/certs/cert-inspection/certgraphanalysis/disk_certs.go index bc4fc19ba4..4f2f135f5c 100644 --- a/pkg/certs/cert-inspection/certgraphanalysis/disk_certs.go +++ b/pkg/certs/cert-inspection/certgraphanalysis/disk_certs.go @@ -14,6 +14,7 @@ import ( "github.com/openshift/library-go/pkg/certs/cert-inspection/certgraphapi" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/cert" ) @@ -38,7 +39,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) @@ -174,13 +175,36 @@ 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 { + details := []*certgraphapi.CertKeyPair{} + if rawConfig, err := kubeConfig.RawConfig(); err == nil { + for _, authInfo := range rawConfig.AuthInfos { + if certKeyPairs, err := GetCertKeyPairsFromKubeConfig(authInfo, nil); err == nil { + for i := range certKeyPairs { + certKeyPairs[i].Spec.OnDiskLocations = []certgraphapi.OnDiskCertKeyPairLocation{ + { + Cert: certgraphapi.OnDiskLocation{ + Path: path, + }, + Key: certgraphapi.OnDiskLocation{ + Path: path, + }, + }} + } + details = append(details, certKeyPairs...) + } + } + return details, nil + } + } // Parse all blocks for len(bytes) > 0 { fmt.Fprintf(os.Stdout, "Parsing block with length %d \n", len(bytes)) @@ -200,16 +224,31 @@ 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 { + if clientConfig, err := kubeConfig.ClientConfig(); err == nil { + fmt.Fprintf(os.Stdout, "Found a valid kubeconfig\n") + if detail, err := GetCAFromKubeConfig(clientConfig, "", ""); 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") } @@ -228,6 +267,14 @@ func parseFileAsCA(path, dir string) (*certgraphapi.CertificateAuthorityBundle, return detail, nil } +func parseFileAsKubeConfig(path string) (clientcmd.OverridingClientConfig, error) { + bytes, err := os.ReadFile(path) + if err != nil { + return nil, err + } + return clientcmd.NewClientConfigFromBytes(bytes) +} + func gatherCABundlesFromDisk(ctx context.Context, dir string, options certGenerationOptionList) ([]*certgraphapi.CertificateAuthorityBundle, []*certgraphapi.OnDiskLocationWithMetadata, error) { ret := []*certgraphapi.CertificateAuthorityBundle{} metadataList := []*certgraphapi.OnDiskLocationWithMetadata{} @@ -246,7 +293,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)