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 }