go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/provider/platform.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package provider
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  
    10  	"github.com/rs/zerolog/log"
    11  	"go.mondoo.com/cnquery/providers-sdk/v1/inventory"
    12  	"go.mondoo.com/cnquery/providers/os/connection/shared"
    13  	"go.mondoo.com/cnquery/providers/os/detector"
    14  	"go.mondoo.com/cnquery/providers/os/id/awsec2"
    15  	"go.mondoo.com/cnquery/providers/os/id/awsecs"
    16  	"go.mondoo.com/cnquery/providers/os/id/clouddetect"
    17  	"go.mondoo.com/cnquery/providers/os/id/hostname"
    18  	"go.mondoo.com/cnquery/providers/os/id/ids"
    19  	"go.mondoo.com/cnquery/providers/os/id/machineid"
    20  	"go.mondoo.com/cnquery/providers/os/id/sshhostkey"
    21  )
    22  
    23  type PlatformFingerprint struct {
    24  	PlatformIDs   []string
    25  	Name          string
    26  	Runtime       string
    27  	Kind          string
    28  	RelatedAssets []PlatformFingerprint
    29  }
    30  
    31  type PlatformInfo struct {
    32  	IDs                []string
    33  	Name               string
    34  	RelatedPlatformIDs []string
    35  }
    36  
    37  func IdentifyPlatform(conn shared.Connection, p *inventory.Platform, idDetectors []string) (*PlatformFingerprint, error) {
    38  	var ok bool
    39  	if p == nil {
    40  		p, ok = detector.DetectOS(conn)
    41  		if !ok {
    42  			return nil, errors.New("cannot detect os")
    43  		}
    44  	}
    45  
    46  	var fingerprint PlatformFingerprint
    47  	var ids []string
    48  	var relatedIds []string
    49  
    50  	for i := range idDetectors {
    51  		idDetector := idDetectors[i]
    52  		platformInfo, err := GatherPlatformInfo(conn, p, idDetector)
    53  		if err != nil {
    54  			// we only err if we found zero platform ids, if we try multiple, a fail of an individual one is okay
    55  			log.Debug().Err(err).Str("detector", string(idDetector)).Msg("could not determine platform info")
    56  			continue
    57  		}
    58  		if len(platformInfo.IDs) > 0 {
    59  			ids = append(ids, platformInfo.IDs...)
    60  		}
    61  		if len(platformInfo.RelatedPlatformIDs) > 0 {
    62  			relatedIds = append(relatedIds, platformInfo.RelatedPlatformIDs...)
    63  		}
    64  
    65  		if len(platformInfo.Name) > 0 {
    66  			fingerprint.Name = platformInfo.Name
    67  		} else {
    68  			// check if we get a name for the asset, eg. aws instance id
    69  			for _, id := range platformInfo.IDs {
    70  				name := GatherNameForPlatformId(id)
    71  				if name != "" {
    72  					fingerprint.Name = name
    73  				}
    74  			}
    75  		}
    76  		// check whether we can extract runtime and kind information
    77  		for _, id := range platformInfo.IDs {
    78  			runtime, kind := ExtractPlatformAndKindFromPlatformId(id)
    79  			if runtime != "" {
    80  				fingerprint.Runtime = runtime
    81  				fingerprint.Kind = kind
    82  			}
    83  		}
    84  	}
    85  
    86  	// if we found zero platform ids something went wrong
    87  	if len(ids) == 0 {
    88  		return nil, errors.New("could not determine a platform identifier")
    89  	}
    90  
    91  	fingerprint.PlatformIDs = ids
    92  	for _, v := range relatedIds {
    93  		fingerprint.RelatedAssets = append(fingerprint.RelatedAssets, PlatformFingerprint{
    94  			PlatformIDs: []string{v},
    95  			Name:        GatherNameForPlatformId(v),
    96  		})
    97  	}
    98  
    99  	log.Debug().Interface("id-detector", idDetectors).Strs("platform-ids", ids).Msg("detected platform ids")
   100  	return &fingerprint, nil
   101  }
   102  
   103  func GatherNameForPlatformId(id string) string {
   104  	if awsec2.IsValidMondooInstanceId(id) {
   105  		structId, _ := awsec2.ParseMondooInstanceID(id)
   106  		return structId.Id
   107  	} else if accountID, err := awsec2.ParseMondooAccountID(id); err == nil {
   108  		return fmt.Sprintf("AWS Account %s", accountID)
   109  	}
   110  	return ""
   111  }
   112  
   113  func ExtractPlatformAndKindFromPlatformId(id string) (string, string) {
   114  	if awsec2.ParseEc2PlatformID(id) != nil {
   115  		return "aws-ec2", "virtual-machine"
   116  	} else if awsec2.IsValidMondooAccountId(id) {
   117  		return "aws", "api"
   118  	} else if awsecs.IsValidMondooECSContainerId(id) {
   119  		return "aws-ecs", "container"
   120  	}
   121  	return "", ""
   122  }
   123  
   124  func GatherPlatformInfo(conn shared.Connection, pf *inventory.Platform, idDetector string) (*PlatformInfo, error) {
   125  	var identifier string
   126  	switch {
   127  	case idDetector == ids.IdDetector_Hostname:
   128  		// NOTE: we need to be careful with hostname's since they are not required to be unique
   129  		hostname, ok := hostname.Hostname(conn, pf)
   130  		if ok && len(hostname) > 0 {
   131  			identifier = "//platformid.api.mondoo.app/hostname/" + hostname
   132  			return &PlatformInfo{
   133  				IDs:                []string{identifier},
   134  				Name:               hostname,
   135  				RelatedPlatformIDs: []string{},
   136  			}, nil
   137  		}
   138  		return &PlatformInfo{}, nil
   139  	case idDetector == ids.IdDetector_MachineID:
   140  		guid, hostErr := machineid.MachineId(conn, pf)
   141  		if hostErr == nil && len(guid) > 0 {
   142  			identifier = "//platformid.api.mondoo.app/machineid/" + guid
   143  			return &PlatformInfo{
   144  				IDs:                []string{identifier},
   145  				Name:               "",
   146  				RelatedPlatformIDs: []string{},
   147  			}, hostErr
   148  		}
   149  		return &PlatformInfo{}, nil
   150  	case idDetector == ids.IdDetector_AwsEcs:
   151  		metadata, err := awsecs.Resolve(conn, pf)
   152  		if err != nil {
   153  			return nil, err
   154  		}
   155  		ident, err := metadata.Identify()
   156  		if err != nil {
   157  			return nil, err
   158  		}
   159  		if len(ident.PlatformIds) != 0 {
   160  			return &PlatformInfo{
   161  				IDs:                ident.PlatformIds,
   162  				Name:               ident.Name,
   163  				RelatedPlatformIDs: []string{ident.AccountPlatformID},
   164  			}, nil
   165  		}
   166  		return &PlatformInfo{}, nil
   167  	case idDetector == ids.IdDetector_CloudDetect:
   168  		identifier, name, relatedIdentifiers := clouddetect.Detect(conn, pf)
   169  		if identifier != "" {
   170  			return &PlatformInfo{
   171  				IDs:                []string{identifier},
   172  				Name:               name,
   173  				RelatedPlatformIDs: relatedIdentifiers,
   174  			}, nil
   175  		}
   176  		return &PlatformInfo{}, nil
   177  	case idDetector == ids.IdDetector_SshHostkey:
   178  		identifier, err := sshhostkey.Detect(conn, pf)
   179  		if err != nil {
   180  			return nil, err
   181  		}
   182  		return &PlatformInfo{
   183  			IDs:                identifier,
   184  			Name:               "",
   185  			RelatedPlatformIDs: []string{},
   186  		}, nil
   187  	default:
   188  		return nil, errors.New(fmt.Sprintf("the provided id-detector is not supported: %s", idDetector))
   189  	}
   190  }