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 }