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

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package docker_engine
     5  
     6  import (
     7  	"context"
     8  	"os"
     9  
    10  	"github.com/cockroachdb/errors"
    11  	"github.com/google/go-containerregistry/pkg/name"
    12  	"github.com/rs/zerolog/log"
    13  	"go.mondoo.com/cnquery/providers-sdk/v1/inventory"
    14  	"go.mondoo.com/cnquery/providers-sdk/v1/vault"
    15  	"go.mondoo.com/cnquery/providers/os/resources/discovery/container_registry"
    16  	"go.mondoo.com/cnquery/utils/stringx"
    17  )
    18  
    19  const (
    20  	DiscoveryContainerRunning = "container"
    21  	DiscoveryContainerImages  = "container-images"
    22  )
    23  
    24  type Resolver struct{}
    25  
    26  func (r *Resolver) Name() string {
    27  	return "Docker Resolver"
    28  }
    29  
    30  func (r *Resolver) AvailableDiscoveryTargets() []string {
    31  	return []string{"auto", "all", DiscoveryContainerRunning, DiscoveryContainerImages}
    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  	if conf == nil {
    37  		return nil, errors.New("no provider configuration found")
    38  	}
    39  
    40  	// check if we have a tar as input
    41  	// detect if the tar is a container image format -> container image
    42  	// or a container snapshot format -> container snapshot
    43  	if conf.Type == "tar" {
    44  
    45  		if conf.Options == nil || conf.Options["file"] == "" {
    46  			return nil, errors.New("could not find the tar file")
    47  		}
    48  
    49  		filename := conf.Options["file"]
    50  
    51  		// check if we are pointing to a local tar file
    52  		_, err := os.Stat(filename)
    53  		if err != nil {
    54  			return nil, errors.New("could not find the tar file: " + filename)
    55  		}
    56  
    57  		// Tar container can be an image or a snapshot
    58  		resolvedAsset := &inventory.Asset{
    59  			Name:        filename,
    60  			Connections: []*inventory.Config{conf},
    61  			Platform: &inventory.Platform{
    62  				Kind:    "container-image",
    63  				Runtime: "docker-image",
    64  			},
    65  			State: inventory.State_STATE_ONLINE,
    66  		}
    67  
    68  		// determine platform identifier
    69  		identifier, err := platformID(filename)
    70  		if err != nil {
    71  			return nil, err
    72  		}
    73  
    74  		resolvedAsset.PlatformIds = []string{identifier}
    75  
    76  		return []*inventory.Asset{resolvedAsset}, nil
    77  	}
    78  
    79  	ded, dockerEngErr := NewDockerEngineDiscovery()
    80  	// we do not fail here, since we pull the image from upstream if its is an image without the need for docker
    81  
    82  	if conf.Type == "docker-container" {
    83  		if dockerEngErr != nil {
    84  			return nil, errors.Wrap(dockerEngErr, "cannot connect to docker engine to fetch the container")
    85  		}
    86  		resolvedAsset, err := r.container(ctx, root, conf, ded)
    87  		if err != nil {
    88  			return nil, err
    89  		}
    90  
    91  		return []*inventory.Asset{resolvedAsset}, nil
    92  	}
    93  
    94  	if conf.Type == "docker-image" {
    95  		// NOTE, we ignore dockerEngErr here since we fallback to pulling the images directly
    96  		// resolvedAssets, err := r.images(ctx, root, conf, ded, credsResolver, sfn)
    97  		resolvedAssets, err := r.images(ctx, root, conf, ded, credsResolver)
    98  		if err != nil {
    99  			return nil, err
   100  		}
   101  		return resolvedAssets, nil
   102  	}
   103  
   104  	// check if we should do a discovery
   105  	if conf.Host == "" {
   106  		return DiscoverDockerEngineAssets(conf)
   107  	}
   108  
   109  	// if we are here, the user has not specified the direct target, we need to search for it
   110  	// could be an image id/name, container id/name or a short reference to an image in docker engine
   111  	// 1. check if we have a container id
   112  	//    check if the container is running -> docker engine
   113  	//    check if the container is stopped -> container snapshot
   114  	// 3. check if we have an image id -> container image
   115  	// 4. check if we have a descriptor for a registry -> container image
   116  	log.Debug().Str("docker", conf.Host).Msg("try to resolve the container or image source")
   117  
   118  	if dockerEngErr == nil {
   119  		containerAsset, err := r.container(ctx, root, conf, ded)
   120  		if err == nil {
   121  			return []*inventory.Asset{containerAsset}, nil
   122  		}
   123  	}
   124  
   125  	// containerImageAssets, err := r.images(ctx, root, conf, ded, credsResolver, sfn)
   126  	containerImageAssets, err := r.images(ctx, root, conf, ded, credsResolver)
   127  	if err == nil {
   128  		return containerImageAssets, nil
   129  	}
   130  
   131  	// if we reached here, we assume we have a name of an image or container from a registry
   132  	return nil, errors.Wrap(err, "could not find the container reference")
   133  }
   134  
   135  func (k *Resolver) container(ctx context.Context, root *inventory.Asset, conf *inventory.Config, ded *dockerEngineDiscovery) (*inventory.Asset, error) {
   136  	ci, err := ded.ContainerInfo(conf.Host)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	conf.Type = "docker-container"
   142  
   143  	// TODO: how do we know we're not connecting to docker over
   144  	// the network and LOCAL_OS is correct
   145  	relatedAssets := []*inventory.Asset{
   146  		{
   147  			Connections: []*inventory.Config{
   148  				{
   149  					Type: "local",
   150  				},
   151  			},
   152  		},
   153  	}
   154  
   155  	return &inventory.Asset{
   156  		Name:        ci.Name,
   157  		Connections: []*inventory.Config{conf},
   158  		PlatformIds: []string{ci.PlatformID},
   159  		Platform: &inventory.Platform{
   160  			Kind:    "container",
   161  			Runtime: "docker-container",
   162  		},
   163  		State:         inventory.State_STATE_ONLINE,
   164  		Labels:        ci.Labels,
   165  		RelatedAssets: relatedAssets,
   166  	}, nil
   167  }
   168  
   169  // func (k *Resolver) images(ctx context.Context, root *inventory.Asset, conf *inventory.Config, ded *dockerEngineDiscovery, credsResolver vault.Resolver, sfn common.QuerySecretFn) ([]*inventory.Asset, error) {
   170  func (k *Resolver) images(ctx context.Context, root *inventory.Asset, conf *inventory.Config, ded *dockerEngineDiscovery, credsResolver vault.Resolver) ([]*inventory.Asset, error) {
   171  	// if we have a docker engine available, try to fetch it from there
   172  	if ded != nil {
   173  		ii, err := ded.ImageInfo(conf.Host)
   174  		if err == nil {
   175  			conf.Type = "docker-image"
   176  			return []*inventory.Asset{{
   177  				Name:        ii.Name,
   178  				Connections: []*inventory.Config{conf},
   179  				PlatformIds: []string{ii.PlatformID},
   180  				Platform: &inventory.Platform{
   181  					Kind:    "container-image",
   182  					Runtime: "docker-image",
   183  				},
   184  				State:  inventory.State_STATE_ONLINE,
   185  				Labels: ii.Labels,
   186  			}}, nil
   187  		}
   188  
   189  	}
   190  
   191  	// otherwise try to fetch the image from upstream
   192  	log.Debug().Msg("try to download the image from docker registry")
   193  	_, err := name.ParseReference(conf.Host, name.WeakValidation)
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  
   198  	// switch to container registry resolver since docker is not installed
   199  	rr := container_registry.Resolver{
   200  		NoStrictValidation: true,
   201  	}
   202  	// return rr.Resolve(ctx, root, conf, credsResolver, sfn)
   203  	return rr.Resolve(ctx, root, conf, credsResolver)
   204  }
   205  
   206  func DiscoverDockerEngineAssets(conf *inventory.Config) ([]*inventory.Asset, error) {
   207  	log.Debug().Msg("start discovery for docker engine")
   208  	// we use generic `container` and `container-images` options to avoid the requirement for the user to know if
   209  	// the system is using docker or podman locally
   210  	assetList := []*inventory.Asset{}
   211  
   212  	if conf.Discover == nil {
   213  		return assetList, nil
   214  	}
   215  
   216  	// discover running container: container
   217  	if stringx.Contains(conf.Discover.Targets, "all") || stringx.Contains(conf.Discover.Targets, DiscoveryContainerRunning) {
   218  		ded, err := NewDockerEngineDiscovery()
   219  		if err != nil {
   220  			return nil, err
   221  		}
   222  
   223  		containerAssets, err := ded.ListContainer()
   224  		if err != nil {
   225  			return nil, err
   226  		}
   227  
   228  		log.Info().Int("container", len(containerAssets)).Msg("running container search completed")
   229  		assetList = append(assetList, containerAssets...)
   230  	}
   231  
   232  	// discover container images: container-images
   233  	if stringx.Contains(conf.Discover.Targets, "all") || stringx.Contains(conf.Discover.Targets, DiscoveryContainerImages) {
   234  		ded, err := NewDockerEngineDiscovery()
   235  		if err != nil {
   236  			return nil, err
   237  		}
   238  
   239  		containerImageAssets, err := ded.ListImages()
   240  		if err != nil {
   241  			return nil, err
   242  		}
   243  		log.Info().Int("images", len(containerImageAssets)).Msg("running container images search completed")
   244  		assetList = append(assetList, containerImageAssets...)
   245  	}
   246  	return assetList, nil
   247  }