go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/resources/discovery/container_registry/registry.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package container_registry 5 6 import ( 7 "crypto/tls" 8 "fmt" 9 "net" 10 "net/http" 11 "net/url" 12 "time" 13 14 "github.com/google/go-containerregistry/pkg/authn" 15 16 "github.com/cockroachdb/errors" 17 "github.com/google/go-containerregistry/pkg/name" 18 "github.com/google/go-containerregistry/pkg/v1/remote" 19 "github.com/rs/zerolog/log" 20 "go.mondoo.com/cnquery/providers-sdk/v1/inventory" 21 "go.mondoo.com/cnquery/providers-sdk/v1/vault" 22 "go.mondoo.com/cnquery/providers/os/connection/container/auth" 23 "go.mondoo.com/cnquery/providers/os/connection/container/image" 24 "go.mondoo.com/cnquery/providers/os/id/containerid" 25 ) 26 27 func NewContainerRegistryResolver() *DockerRegistryImages { 28 return &DockerRegistryImages{} 29 } 30 31 type DockerRegistryImages struct { 32 Insecure bool 33 DisableKeychainAuth bool 34 } 35 36 func (a *DockerRegistryImages) remoteOptions() []remote.Option { 37 options := []remote.Option{} 38 39 // does not work with bearer auth, therefore it need to be disabled when other remote auth options are used 40 // TODO: we should implement this a bit differently 41 if a.DisableKeychainAuth == false { 42 options = append(options, remote.WithAuthFromKeychain(authn.DefaultKeychain)) 43 } 44 45 if a.Insecure { 46 // NOTE: config to get remote running with an insecure registry, we need to override the TLSClientConfig 47 tr := &http.Transport{ 48 Proxy: http.ProxyFromEnvironment, 49 DialContext: (&net.Dialer{ 50 Timeout: 30 * time.Second, 51 KeepAlive: 30 * time.Second, 52 DualStack: true, 53 }).DialContext, 54 ForceAttemptHTTP2: true, 55 MaxIdleConns: 100, 56 IdleConnTimeout: 90 * time.Second, 57 TLSHandshakeTimeout: 10 * time.Second, 58 ExpectContinueTimeout: 1 * time.Second, 59 TLSClientConfig: &tls.Config{ 60 InsecureSkipVerify: true, 61 }, 62 } 63 options = append(options, remote.WithTransport(tr)) 64 } 65 66 return options 67 } 68 69 func (a *DockerRegistryImages) Repositories(reg name.Registry) ([]string, error) { 70 n := 100 71 last := "" 72 var res []string 73 for { 74 page, err := remote.CatalogPage(reg, last, n, a.remoteOptions()...) 75 if err != nil { 76 return nil, err 77 } 78 79 if len(page) > 0 { 80 last = page[len(page)-1] 81 res = append(res, page...) 82 } 83 84 if len(page) < n { 85 break 86 } 87 } 88 89 return res, nil 90 } 91 92 // ListRegistry tries to iterate over all repositores in one registry 93 // eg. 1234567.dkr.ecr.us-east-1.amazonaws.com 94 func (a *DockerRegistryImages) ListRegistry(registry string) ([]*inventory.Asset, error) { 95 reg, err := name.NewRegistry(registry) 96 if err != nil { 97 return nil, errors.Wrap(err, "resolve registry") 98 } 99 100 repos, err := a.Repositories(reg) 101 if err != nil { 102 return nil, err 103 } 104 105 assets := []*inventory.Asset{} 106 for i := range repos { 107 repoName := reg.RegistryStr() + "/" + repos[i] 108 log.Debug().Str("repository", repoName).Msg("discovered repository") 109 110 // iterate over all repository digests 111 repoImages, err := a.ListRepository(repoName) 112 if err != nil { 113 return nil, err 114 } 115 assets = append(assets, repoImages...) 116 } 117 118 return assets, nil 119 } 120 121 // ListRepository tries to fetch all details about a specific repository 122 // index.docker.io/mondoo 123 // index.docker.io/mondoo/client 124 // harbor.lunalectric.com/library 125 // harbor.lunalectric.com/library/ubuntu 126 func (a *DockerRegistryImages) ListRepository(repoName string) ([]*inventory.Asset, error) { 127 assets := []*inventory.Asset{} 128 129 repo, err := name.NewRepository(repoName) 130 if err != nil { 131 return nil, err 132 } 133 134 // fetch tags 135 tags, err := remote.List(repo, a.remoteOptions()...) 136 if err != nil { 137 return nil, handleUnauthorizedError(err, repo.Name()) 138 } 139 140 foundAssets := map[string]*inventory.Asset{} 141 for i := range tags { 142 repoWithTag := repo.Name() + ":" + tags[i] 143 144 ref, err := name.ParseReference(repoWithTag) 145 if err != nil { 146 return nil, fmt.Errorf("parsing reference %q: %v", repoWithTag, err) 147 } 148 149 a, err := a.toAsset(ref, nil) 150 if err != nil { 151 return nil, err 152 } 153 if foundAsset, ok := foundAssets[a.PlatformIds[0]]; ok { 154 // only add tags to the first asset 155 foundAsset.Labels["docker.io/tags"] = foundAsset.Labels["docker.io/tags"] + "," + a.Labels["docker.io/tags"] 156 log.Debug().Str("tags", foundAsset.Labels["docker.io/tags"]).Str("image", foundAsset.Name).Msg("found additional tags for image") 157 continue 158 } 159 foundAssets[a.PlatformIds[0]] = a 160 } 161 162 // flatten map 163 for k := range foundAssets { 164 assets = append(assets, foundAssets[k]) 165 } 166 return assets, nil 167 } 168 169 // ListImages either takes a registry or a repository and tries to fetch as many images as possible 170 func (a *DockerRegistryImages) ListImages(repoName string) ([]*inventory.Asset, error) { 171 url, err := url.Parse("//" + repoName) 172 if err != nil { 173 return nil, fmt.Errorf("registries must be valid RFC 3986 URI authorities: %s", repoName) 174 } 175 176 if url.Host == repoName { 177 // fetch registry information 178 return a.ListRegistry(repoName) 179 } else { 180 // fetch repo information 181 return a.ListRepository(repoName) 182 } 183 } 184 185 func (a *DockerRegistryImages) GetImage(ref name.Reference, creds []*vault.Credential, opts ...remote.Option) (*inventory.Asset, error) { 186 return a.toAsset(ref, creds, opts...) 187 } 188 189 func (a *DockerRegistryImages) toAsset(ref name.Reference, creds []*vault.Credential, opts ...remote.Option) (*inventory.Asset, error) { 190 desc, err := image.GetImageDescriptor(ref, auth.AuthOption(creds)...) 191 if err != nil { 192 return nil, handleUnauthorizedError(err, ref.Name()) 193 } 194 imgDigest := desc.Digest.String() 195 repoName := ref.Context().Name() 196 imgTag := ref.Context().Tag(ref.Identifier()).TagStr() 197 name := repoName + "@" + containerid.ShortContainerImageID(imgDigest) 198 imageUrl := repoName + "@" + imgDigest 199 asset := &inventory.Asset{ 200 PlatformIds: []string{containerid.MondooContainerImageID(imgDigest)}, 201 Name: name, 202 Connections: []*inventory.Config{ 203 { 204 Type: "container-registry", 205 Host: imageUrl, 206 Credentials: creds, 207 }, 208 }, 209 State: inventory.State_STATE_ONLINE, 210 Labels: make(map[string]string), 211 } 212 213 // store digest and tag 214 asset.Labels["docker.io/digest"] = imgDigest 215 asset.Labels["docker.io/tags"] = imgTag 216 log.Debug().Strs("platform-ids", asset.PlatformIds).Msg("asset platform ids") 217 return asset, nil 218 }