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

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package discovery
     5  
     6  // The discovery package is responsible to determine all assets reachable. E.g. If you provide an AWS
     7  // connection, multiple assets like EC2, ECR images as well as EKS clusters can be determined automatically
     8  //
     9  // This package implements all the resolution steps and returns a fully resolved list of assets that mondoo
    10  // can connect to.
    11  //
    12  // As part of the discovery process, secrets need to be determined. This package is designed to have know
    13  // no knowledge about inventory or vault packages. It defines two `common.CredentialFn` and `common.QuerySecretFn`
    14  // to retrieve the required information. The inventory manager injects the correct functions upon initialization
    15  
    16  import (
    17  	"context"
    18  	"strings"
    19  
    20  	"github.com/cockroachdb/errors"
    21  	"github.com/rs/zerolog/log"
    22  	"go.mondoo.com/cnquery/motor/discovery/common"
    23  	inventory "go.mondoo.com/cnquery/motor/inventory/v1"
    24  	"go.mondoo.com/cnquery/motor/providers"
    25  	pr "go.mondoo.com/cnquery/motor/providers/resolver"
    26  	"go.mondoo.com/cnquery/motor/vault"
    27  	"go.mondoo.com/cnquery/stringx"
    28  )
    29  
    30  type Resolver interface {
    31  	Name() string
    32  	Resolve(ctx context.Context, root *inventory.Asset, t *inventory.Config, credsResolver vault.Resolver, sfn common.QuerySecretFn,
    33  		userIdDetectors ...providers.PlatformIdDetector) ([]*inventory.Asset, error)
    34  	AvailableDiscoveryTargets() []string
    35  }
    36  
    37  var resolver map[string]Resolver
    38  
    39  func init() {
    40  	resolver = map[string]Resolver{}
    41  }
    42  
    43  // InitCtx initializes the context to support all resolvers
    44  func InitCtx(ctx context.Context) context.Context {
    45  	initCtx := ctx
    46  	for _, r := range resolver {
    47  		if ctxInitializer, ok := r.(common.ContextInitializer); ok {
    48  			initCtx = ctxInitializer.InitCtx(initCtx)
    49  		}
    50  	}
    51  	return initCtx
    52  }
    53  
    54  func ResolveAsset(ctx context.Context, root *inventory.Asset, credsResolver vault.Resolver, sfn common.QuerySecretFn) ([]*inventory.Asset, error) {
    55  	resolved := []*inventory.Asset{}
    56  
    57  	// if the asset is missing a secret, we try to add this for the asset
    58  	common.EnrichAssetWithSecrets(root, sfn)
    59  
    60  	assetFallbackName := func(a *inventory.Asset, c *inventory.Config) {
    61  		// set the asset name to the config name. This is only required for error cases where the discovery
    62  		// is not successful
    63  		if a.Name == "" {
    64  			a.Name = c.Host
    65  		}
    66  	}
    67  
    68  	for i := range root.Connections {
    69  		pCfg := root.Connections[i]
    70  
    71  		resolverId := pCfg.Type
    72  		r, ok := resolver[resolverId]
    73  		if !ok {
    74  			assetFallbackName(root, pCfg)
    75  			return nil, errors.New("cannot discover backend: " + resolverId)
    76  		}
    77  		log.Debug().Str("resolver-id", resolverId).Str("resolver", r.Name()).Msg("run resolver")
    78  
    79  		// check that all discovery options are supported and show a user warning
    80  		availableTargets := r.AvailableDiscoveryTargets()
    81  		if pCfg.Discover != nil {
    82  			for i := range pCfg.Discover.Targets {
    83  				target := pCfg.Discover.Targets[i]
    84  				if !stringx.Contains(availableTargets, target) {
    85  					log.Warn().Str("resolver", r.Name()).Msgf("resolver does not support discovery target '%s', the following are supported: %s", target, strings.Join(availableTargets, ","))
    86  				}
    87  			}
    88  		}
    89  
    90  		userIdDetectors := providers.ToPlatformIdDetectors(root.IdDetector)
    91  
    92  		// resolve assets
    93  		resolvedAssets, err := r.Resolve(ctx, root, pCfg, credsResolver, sfn, userIdDetectors...)
    94  		if err != nil {
    95  			assetFallbackName(root, pCfg)
    96  			return nil, err
    97  		}
    98  
    99  		for ai := range resolvedAssets {
   100  			assetObj := resolvedAssets[ai]
   101  
   102  			// copy over id detector overwrite
   103  			assetObj.IdDetector = root.IdDetector
   104  
   105  			// copy over labels from root
   106  			if assetObj.Labels == nil {
   107  				assetObj.Labels = map[string]string{}
   108  			}
   109  
   110  			for k, v := range root.Labels {
   111  				assetObj.Labels[k] = v
   112  			}
   113  
   114  			// copy over annotations from root
   115  			if assetObj.Annotations == nil {
   116  				assetObj.Annotations = map[string]string{}
   117  			}
   118  
   119  			for k, v := range root.Annotations {
   120  				assetObj.Annotations[k] = v
   121  			}
   122  			assetObj.Category = root.Category
   123  
   124  			// copy over managedBy from root
   125  			assetObj.ManagedBy = root.GetManagedBy()
   126  
   127  			// if the user set the asset name via flag, --asset-name,
   128  			// that value should override the discovered one
   129  			if root.Name != "" {
   130  				assetObj.Name = root.Name
   131  			}
   132  			resolved = append(resolved, assetObj)
   133  		}
   134  	}
   135  	return resolved, nil
   136  }
   137  
   138  type ResolvedAssets struct {
   139  	Assets        []*inventory.Asset
   140  	RelatedAssets []*inventory.Asset
   141  	Errors        map[*inventory.Asset]error
   142  }
   143  
   144  func ResolveAssets(ctx context.Context, rootAssets []*inventory.Asset, credsResolver vault.Resolver, sfn common.QuerySecretFn) ResolvedAssets {
   145  	resolved := []*inventory.Asset{}
   146  	resolvedMap := map[string]struct{}{}
   147  	errors := map[*inventory.Asset]error{}
   148  	relatedAssets := []*inventory.Asset{}
   149  	platformIdToAssetMap := map[string]*inventory.Asset{}
   150  
   151  	for i := range rootAssets {
   152  		asset := rootAssets[i]
   153  
   154  		resolverAssets, err := ResolveAsset(ctx, asset, credsResolver, sfn)
   155  		if err != nil {
   156  			errors[asset] = err
   157  			continue
   158  		}
   159  
   160  		for _, resolvedAsset := range resolverAssets {
   161  			for _, platformId := range resolvedAsset.PlatformIds {
   162  				if platformId != "" {
   163  					platformIdToAssetMap[platformId] = asset
   164  					resolvedMap[platformId] = struct{}{}
   165  				}
   166  			}
   167  
   168  			for _, a := range resolvedAsset.RelatedAssets {
   169  				relatedAssets = append(relatedAssets, a)
   170  			}
   171  		}
   172  
   173  		resolved = append(resolved, resolverAssets...)
   174  	}
   175  
   176  	resolveRelatedAssets(ctx, relatedAssets, platformIdToAssetMap, credsResolver)
   177  
   178  	neededRelatedAssets := []*inventory.Asset{}
   179  	for _, a := range relatedAssets {
   180  		found := false
   181  		for _, platformId := range a.PlatformIds {
   182  			if _, ok := resolvedMap[platformId]; ok {
   183  				found = true
   184  				break
   185  			}
   186  		}
   187  		if found {
   188  			continue
   189  		}
   190  		neededRelatedAssets = append(neededRelatedAssets, a)
   191  	}
   192  
   193  	return ResolvedAssets{
   194  		Assets:        resolved,
   195  		RelatedAssets: neededRelatedAssets,
   196  		Errors:        errors,
   197  	}
   198  }
   199  
   200  func resolveRelatedAssets(ctx context.Context, relatedAssets []*inventory.Asset, platformIdToAssetMap map[string]*inventory.Asset, credsResolver vault.Resolver) {
   201  	for _, assetObj := range relatedAssets {
   202  		if len(assetObj.PlatformIds) > 0 {
   203  			for _, platformId := range assetObj.PlatformIds {
   204  				platformIdToAssetMap[platformId] = assetObj
   205  			}
   206  			continue
   207  		}
   208  		if len(assetObj.Connections) > 0 {
   209  			tc := assetObj.Connections[0]
   210  			if tc.PlatformId != "" {
   211  				assetObj.PlatformIds = []string{tc.PlatformId}
   212  				platformIdToAssetMap[tc.PlatformId] = assetObj
   213  				continue
   214  			}
   215  
   216  			func() {
   217  				m, err := pr.NewMotorConnection(ctx, tc, credsResolver)
   218  				if err != nil {
   219  					log.Warn().Err(err).Msg("could not connect to related asset")
   220  					return
   221  				}
   222  				defer m.Close()
   223  				// p, err := m.Platform()
   224  				if err != nil {
   225  					log.Warn().Err(err).Msg("could not get related asset platform")
   226  					return
   227  				}
   228  
   229  				panic("REDO")
   230  				// fingerprint, err := motorid.IdentifyPlatform(m.Provider, p, m.Provider.PlatformIdDetectors())
   231  				// if err != nil {
   232  				// 	return
   233  				// }
   234  
   235  				// if fingerprint.Runtime != "" {
   236  				// 	p.Runtime = fingerprint.Runtime
   237  				// }
   238  
   239  				// if fingerprint.Kind != providers.Kind_KIND_UNKNOWN {
   240  				// 	p.Kind = fingerprint.Kind
   241  				// }
   242  
   243  				// assetObj.State = asset.State_STATE_ONLINE
   244  				// assetObj.Name = fingerprint.Name
   245  				// assetObj.PlatformIds = fingerprint.PlatformIDs
   246  				// assetObj.Platform = p
   247  
   248  				// for _, v := range fingerprint.PlatformIDs {
   249  				// 	platformIdToAssetMap[v] = assetObj
   250  				// }
   251  			}()
   252  		}
   253  	}
   254  }