Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NO-JIRA: return multiple certificates when scanning secrets #1751

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 116 additions & 32 deletions pkg/certs/cert-inspection/certgraphanalysis/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
9 changes: 5 additions & 4 deletions pkg/certs/cert-inspection/certgraphanalysis/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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{
Expand Down
57 changes: 52 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,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"
)

Expand All @@ -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)
Expand Down Expand Up @@ -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))
Expand All @@ -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")
}
Expand All @@ -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{}
Expand All @@ -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)

Expand Down