go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/resources/discovery/container_registry/resolver.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package container_registry
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  
    10  	"github.com/google/go-containerregistry/pkg/authn"
    11  	"github.com/google/go-containerregistry/pkg/name"
    12  	"github.com/google/go-containerregistry/pkg/v1/remote"
    13  	"github.com/rs/zerolog/log"
    14  	"go.mondoo.com/cnquery/logger"
    15  	"go.mondoo.com/cnquery/providers-sdk/v1/inventory"
    16  	"go.mondoo.com/cnquery/providers-sdk/v1/vault"
    17  )
    18  
    19  type Resolver struct {
    20  	// NoStrictValidation deactivates the strict validation for container registry resolutions
    21  	// cr://index.docker.io/mondoo/client would be converted index.docker.io/mondoo/client:latest
    22  	// It is not the default behavior but is used by the docker resolver to resolve images
    23  	NoStrictValidation bool
    24  }
    25  
    26  func (r *Resolver) Name() string {
    27  	return "Container Registry Discover"
    28  }
    29  
    30  func (r *Resolver) AvailableDiscoveryTargets() []string {
    31  	return []string{"auto", "all"}
    32  }
    33  
    34  // func (r *Resolver) Resolve(ctx context.Context, root *inventory.Asset, conf *inventory.Config, credsResolver vault.Resolver, sfn common.QuerySecretFn, userIdDetectors ...providers.PlatformIdDetector) ([]*inventory.Asset, error) {
    35  func (r *Resolver) Resolve(ctx context.Context, root *inventory.Asset, conf *inventory.Config, credsResolver vault.Resolver) ([]*inventory.Asset, error) {
    36  	resolved := []*inventory.Asset{}
    37  
    38  	imageFetcher := NewContainerRegistryResolver()
    39  	// to support self-signed certs
    40  	imageFetcher.Insecure = conf.Insecure
    41  
    42  	// check if the reference is an image
    43  	// NOTE: we use strict validation here otherwise urls like cr://index.docker.io/mondoo/client are converted
    44  	// to index.docker.io/mondoo/client:latest
    45  	opts := name.StrictValidation
    46  	if r.NoStrictValidation {
    47  		opts = name.WeakValidation
    48  	}
    49  
    50  	ref, err := name.ParseReference(conf.Host, opts)
    51  	if err == nil {
    52  		log.Debug().Str("image", conf.Host).Msg("detected container image in container registry")
    53  
    54  		remoteOpts := AuthOption(conf.Credentials, credsResolver)
    55  		// we need to disable default keychain auth if an auth method was found
    56  		if len(remoteOpts) > 0 {
    57  			imageFetcher.DisableKeychainAuth = true
    58  		}
    59  
    60  		a, err := imageFetcher.GetImage(ref, conf.Credentials, remoteOpts...)
    61  		if err != nil {
    62  			return nil, err
    63  		}
    64  		// keep already set options, i.e. image paths
    65  		if conf.Options != nil && a.Connections[0].Options == nil {
    66  			a.Connections[0].Options = conf.Options
    67  		}
    68  
    69  		if conf.Insecure {
    70  			for i := range a.Connections {
    71  				c := a.Connections[i]
    72  				c.Insecure = conf.Insecure
    73  				c.Credentials = conf.Credentials
    74  			}
    75  		}
    76  
    77  		return []*inventory.Asset{a}, nil
    78  	}
    79  
    80  	// okay, no image, lets check the repository
    81  	repository := conf.Host
    82  	log.Info().Str("registry", repository).Msg("fetch meta information from container registry")
    83  
    84  	assetList, err := imageFetcher.ListImages(repository)
    85  	if err != nil {
    86  		log.Error().Err(err).Msg("could not fetch container images")
    87  		return nil, err
    88  	}
    89  
    90  	for i := range assetList {
    91  		a := assetList[i]
    92  		log.Info().Str("name", a.Name).Str("image", a.Connections[0].Host+assetList[i].Connections[0].Path).Msg("resolved image")
    93  
    94  		if conf.Insecure {
    95  			for i := range a.Connections {
    96  				c := a.Connections[i]
    97  				c.Insecure = conf.Insecure
    98  			}
    99  		}
   100  		resolved = append(resolved, a)
   101  	}
   102  
   103  	if len(resolved) == 0 {
   104  		return nil, errors.New("could not find repository:" + repository)
   105  	}
   106  
   107  	return resolved, nil
   108  }
   109  
   110  func AuthOption(credentials []*vault.Credential, credsResolver vault.Resolver) []remote.Option {
   111  	remoteOpts := []remote.Option{}
   112  	for i := range credentials {
   113  		cred := credentials[i]
   114  
   115  		// NOTE: normally the motor connection is resolving the credentials but here we need the credential earlier
   116  		// we probably want to write some mql resources to support the query of registries itself
   117  		resolvedCredential, err := credsResolver.GetCredential(cred)
   118  		if err != nil {
   119  			log.Warn().Err(err).Msg("could not resolve credential")
   120  		}
   121  		switch resolvedCredential.Type {
   122  		case vault.CredentialType_password:
   123  			log.Debug().Msg("add password authentication")
   124  			cfg := authn.AuthConfig{
   125  				Username: resolvedCredential.User,
   126  				Password: string(resolvedCredential.Secret),
   127  			}
   128  			remoteOpts = append(remoteOpts, remote.WithAuth(authn.FromConfig(cfg)))
   129  		case vault.CredentialType_bearer:
   130  			log.Debug().Str("token", string(resolvedCredential.Secret)).Msg("add bearer authentication")
   131  			cfg := authn.AuthConfig{
   132  				Username:      resolvedCredential.User,
   133  				RegistryToken: string(resolvedCredential.Secret),
   134  			}
   135  			remoteOpts = append(remoteOpts, remote.WithAuth(authn.FromConfig(cfg)))
   136  		default:
   137  			log.Warn().Msg("unknown credentials for container image")
   138  			logger.DebugJSON(credentials)
   139  		}
   140  	}
   141  	return remoteOpts
   142  }