go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/_motor/discovery/k8s/list_images.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package k8s
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"strings"
    10  
    11  	"github.com/docker/cli/cli/config/configfile"
    12  	"github.com/google/go-containerregistry/pkg/name"
    13  
    14  	"go.mondoo.com/cnquery/motor/discovery/container_registry"
    15  	"go.mondoo.com/cnquery/motor/vault"
    16  	"go.mondoo.com/cnquery/types"
    17  
    18  	"github.com/pkg/errors"
    19  	"github.com/rs/zerolog/log"
    20  	"go.mondoo.com/cnquery/motor/asset"
    21  	"go.mondoo.com/cnquery/motor/providers/k8s"
    22  	v1 "k8s.io/api/core/v1"
    23  )
    24  
    25  // ListPodImages lits all container images for the pods in the cluster. Only unique container images are returned.
    26  // Uniqueness is determined based on the container digests.
    27  func ListPodImages(p k8s.KubernetesProvider, nsFilter NamespaceFilterOpts, od *k8s.PlatformIdOwnershipDirectory) ([]*asset.Asset, error) {
    28  	namespaces, err := p.Namespaces()
    29  	if err != nil {
    30  		return nil, errors.Wrap(err, "could not list kubernetes namespaces")
    31  	}
    32  
    33  	// Grab the unique container images in the cluster.
    34  	runningImages := make(map[string]ContainerImage)
    35  	credsStore := NewCredsStore(p)
    36  	for i := range namespaces {
    37  		namespace := namespaces[i]
    38  		skip, err := skipNamespace(namespace, nsFilter)
    39  		if err != nil {
    40  			log.Error().Err(err).Str("namespace", namespace.Name).Msg("error checking whether namespace should be included or excluded")
    41  			return nil, err
    42  		}
    43  		if skip {
    44  			log.Debug().Str("namespace", namespace.Name).Msg("namespace not included")
    45  			continue
    46  		}
    47  
    48  		pods, err := p.Pods(namespace)
    49  		if err != nil {
    50  			return nil, errors.Wrap(err, "failed to list pods")
    51  		}
    52  
    53  		for j := range pods {
    54  			od.Add(pods[j])
    55  			podImages := UniqueImagesForPod(*pods[j], credsStore)
    56  			runningImages = types.MergeMaps(runningImages, podImages)
    57  		}
    58  	}
    59  
    60  	// Convert the container images to assets.
    61  	assets := make(map[string]*asset.Asset)
    62  	for _, i := range runningImages {
    63  		a, err := newPodImageAsset(i)
    64  		if err != nil {
    65  			log.Error().Err(err).Msg("failed to convert container image to asset")
    66  			continue
    67  		}
    68  
    69  		// It is still possible to have unique images at this point. There might be
    70  		// multiple image tags that actually point to the same digest. If we are scanning
    71  		// a manifest, where there is no container status, we can only know that the 2 images
    72  		// are identical after we resolve them with the container registry.
    73  		assets[a.Labels["docker.io/digest"]] = a
    74  		log.Debug().Str("name", a.Name).Str("image", a.Connections[0].Host).Msg("resolved pod")
    75  	}
    76  
    77  	return types.MapValuesToSlice(assets), nil
    78  }
    79  
    80  func newPodImageAsset(i ContainerImage) (*asset.Asset, error) {
    81  	ccresolver := container_registry.NewContainerRegistryResolver()
    82  
    83  	ref, err := name.ParseReference(i.resolvedImage, name.WeakValidation)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  
    88  	a, err := ccresolver.GetImage(ref, nil)
    89  	// If there was an error getting the image, try to resolve it using image pull secrets.
    90  	// It might be that the container is coming from a private repo.
    91  	if err != nil {
    92  		for _, secret := range i.pullSecrets {
    93  			if cfg, ok := secret.Data[v1.DockerConfigJsonKey]; ok {
    94  				creds, err := toCredential(cfg)
    95  				if err != nil {
    96  					continue
    97  				}
    98  
    99  				a, err = ccresolver.GetImage(ref, creds)
   100  				if err == nil {
   101  					break
   102  				}
   103  			}
   104  		}
   105  	}
   106  
   107  	// If at this point we still have no asset it means that neither public scan worked, nor
   108  	// a scan using pull secrets.
   109  	if a == nil {
   110  		return nil, fmt.Errorf("could not resolve image %s. %v", i.resolvedImage, err)
   111  	}
   112  
   113  	// parse image name to extract tags
   114  	tagName := ""
   115  	if len(i.image) > 0 {
   116  		tag, err := name.NewTag(i.image, name.WeakValidation)
   117  		if err == nil {
   118  			tagName = tag.Name()
   119  		}
   120  	}
   121  	if a.Labels == nil {
   122  		a.Labels = map[string]string{}
   123  	}
   124  	a.Labels["docker.io/tags"] = tagName
   125  	return a, nil
   126  }
   127  
   128  func isIncluded(value string, included []string) bool {
   129  	if len(included) == 0 {
   130  		return true
   131  	}
   132  
   133  	for _, ex := range included {
   134  		if strings.EqualFold(ex, value) {
   135  			return true
   136  		}
   137  	}
   138  
   139  	return false
   140  }
   141  
   142  func toCredential(cfg []byte) ([]*vault.Credential, error) {
   143  	cf := configfile.ConfigFile{}
   144  	if err := json.Unmarshal(cfg, &cf); err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	var creds []*vault.Credential
   149  	for _, v := range cf.AuthConfigs {
   150  		c := &vault.Credential{
   151  			User: v.Username,
   152  		}
   153  
   154  		if v.Password != "" {
   155  			c.Type = vault.CredentialType_password
   156  			c.Secret = []byte(v.Password)
   157  		} else if v.RegistryToken != "" {
   158  			c.Type = vault.CredentialType_bearer
   159  			c.Secret = []byte(v.RegistryToken)
   160  		}
   161  		creds = append(creds, c)
   162  	}
   163  	return creds, nil
   164  }