internal/inventory/gcpfetcher/fetcher_assets.go (311 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package gcpfetcher import ( "context" "strings" "github.com/mitchellh/mapstructure" "github.com/samber/lo" "google.golang.org/protobuf/types/known/structpb" "github.com/elastic/cloudbeat/internal/infra/clog" "github.com/elastic/cloudbeat/internal/inventory" gcpinventory "github.com/elastic/cloudbeat/internal/resources/providers/gcplib/inventory" ) type ( assetsInventory struct { logger *clog.Logger provider inventoryProvider } inventoryProvider interface { ListAllAssetTypesByName(ctx context.Context, assets []string) ([]*gcpinventory.ExtendedGcpAsset, error) } ResourcesClassification struct { assetType string classification inventory.AssetClassification } ) var ResourcesToFetch = []ResourcesClassification{ {gcpinventory.CrmOrgAssetType, inventory.AssetClassificationGcpOrganization}, {gcpinventory.CrmFolderAssetType, inventory.AssetClassificationGcpFolder}, {gcpinventory.CrmProjectAssetType, inventory.AssetClassificationGcpProject}, {gcpinventory.ComputeInstanceAssetType, inventory.AssetClassificationGcpInstance}, {gcpinventory.ComputeFirewallAssetType, inventory.AssetClassificationGcpFirewall}, {gcpinventory.StorageBucketAssetType, inventory.AssetClassificationGcpBucket}, {gcpinventory.ComputeSubnetworkAssetType, inventory.AssetClassificationGcpSubnet}, {gcpinventory.IamServiceAccountAssetType, inventory.AssetClassificationGcpServiceAccount}, {gcpinventory.IamServiceAccountKeyAssetType, inventory.AssetClassificationGcpServiceAccountKey}, {gcpinventory.GkeClusterAssetType, inventory.AssetClassificationGcpGkeCluster}, {gcpinventory.ComputeForwardingRuleAssetType, inventory.AssetClassificationGcpForwardingRule}, {gcpinventory.CloudFunctionAssetType, inventory.AssetClassificationGcpCloudFunction}, {gcpinventory.CloudRunService, inventory.AssetClassificationGcpCloudRunService}, {gcpinventory.IamRoleAssetType, inventory.AssetClassificationGcpIamRole}, {gcpinventory.ComputeNetworkAssetType, inventory.AssetClassificationGcpNetwork}, } func newAssetsInventoryFetcher(logger *clog.Logger, provider inventoryProvider) inventory.AssetFetcher { return &assetsInventory{ logger: logger, provider: provider, } } func (f *assetsInventory) Fetch(ctx context.Context, assetChan chan<- inventory.AssetEvent) { for _, r := range ResourcesToFetch { f.fetch(ctx, assetChan, r.assetType, r.classification) } } func (f *assetsInventory) fetch(ctx context.Context, assetChan chan<- inventory.AssetEvent, assetType string, classification inventory.AssetClassification) { f.logger.Infof("Fetching %s", assetType) defer f.logger.Infof("Fetching %s - Finished", assetType) gcpAssets, err := f.provider.ListAllAssetTypesByName(ctx, []string{assetType}) if err != nil { f.logger.Errorf("Could not fetch %s: %v", assetType, err) return } for _, item := range gcpAssets { assetChan <- getAssetEvent(classification, item) } } func getAssetEvent(classification inventory.AssetClassification, item *gcpinventory.ExtendedGcpAsset) inventory.AssetEvent { // Common enrichers enrichers := []inventory.AssetEnricher{ inventory.WithRawAsset(item), inventory.WithLabels(getAssetLabels(item)), inventory.WithTags(getAssetTags(item)), inventory.WithRelatedAssetIds( findRelatedAssetIds(classification.Type, item), ), // Any asset type enrichers also setting Cloud fields will need to re-add these fields below inventory.WithCloud(inventory.Cloud{ Provider: inventory.GcpCloudProvider, AccountID: item.CloudAccount.AccountId, AccountName: item.CloudAccount.AccountName, ProjectID: item.CloudAccount.OrganisationId, ProjectName: item.CloudAccount.OrganizationName, ServiceName: item.AssetType, }), } // Asset type specific enrichers if hasResourceData(item) { fields := item.GetResource().GetData().GetFields() if enricher, ok := assetEnrichers[item.AssetType]; ok { enrichers = append(enrichers, enricher(item, fields)...) } } return inventory.NewAssetEvent( classification, item.Name, item.Name, enrichers..., ) } func findRelatedAssetIds(t inventory.AssetType, item *gcpinventory.ExtendedGcpAsset) []string { ids := []string{} ids = append(ids, item.Ancestors...) if item.Resource != nil { ids = append(ids, item.Resource.Parent) } ids = append(ids, findRelatedAssetIdsForType(t, item)...) ids = lo.Compact(ids) ids = lo.Uniq(ids) return ids } func findRelatedAssetIdsForType(t inventory.AssetType, item *gcpinventory.ExtendedGcpAsset) []string { ids := []string{} var fields map[string]*structpb.Value if item.Resource != nil && item.Resource.Data != nil { fields = item.GetResource().GetData().GetFields() } switch t { case inventory.AssetClassificationGcpInstance.Type: if v, ok := fields["networkInterfaces"]; ok { for _, networkInterface := range v.GetListValue().GetValues() { networkInterfaceFields := networkInterface.GetStructValue().GetFields() ids = appendIfExists(ids, networkInterfaceFields, "network") ids = appendIfExists(ids, networkInterfaceFields, "subnetwork") } } if v, ok := fields["serviceAccounts"]; ok { for _, serviceAccount := range v.GetListValue().GetValues() { serviceAccountFields := serviceAccount.GetStructValue().GetFields() ids = appendIfExists(ids, serviceAccountFields, "email") } } if v, ok := fields["disks"]; ok { for _, disk := range v.GetListValue().GetValues() { diskFields := disk.GetStructValue().GetFields() ids = appendIfExists(ids, diskFields, "source") } } ids = appendIfExists(ids, fields, "machineType") ids = appendIfExists(ids, fields, "zone") case inventory.AssetClassificationGcpFirewall.Type, inventory.AssetClassificationGcpSubnet.Type: ids = appendIfExists(ids, fields, "network") case inventory.AssetClassificationGcpProject.Type, inventory.AssetClassificationGcpBucket.Type: if item.IamPolicy == nil { break } for _, binding := range item.IamPolicy.Bindings { ids = append(ids, binding.Role) ids = append(ids, binding.Members...) } default: return ids } return ids } func appendIfExists(slice []string, fields map[string]*structpb.Value, key string) []string { value, ok := fields[key] if !ok { return slice } return append(slice, value.GetStringValue()) } func hasResourceData(item *gcpinventory.ExtendedGcpAsset) bool { return item.Resource != nil && item.Resource.Data != nil } func getAssetTags(item *gcpinventory.ExtendedGcpAsset) []string { if !hasResourceData(item) { return nil } tagsObj, ok := item.GetResource().GetData().GetFields()["tags"] if !ok { return nil } structValue := tagsObj.GetStructValue() if structValue == nil { return nil } items, ok := structValue.GetFields()["items"] if !ok { return nil } tagValues := items.GetListValue().GetValues() tags := make([]string, len(tagValues)) for i, tag := range tagValues { tags[i] = tag.GetStringValue() } return tags } func getAssetLabels(item *gcpinventory.ExtendedGcpAsset) map[string]string { if !hasResourceData(item) { return nil } labels, ok := item.GetResource().GetData().GetFields()["labels"] if !ok { return nil } labelsMap := make(map[string]string) if err := mapstructure.Decode(labels.GetStructValue().AsMap(), &labelsMap); err != nil { return nil } return labelsMap } var assetEnrichers = map[string]func(item *gcpinventory.ExtendedGcpAsset, fields map[string]*structpb.Value) []inventory.AssetEnricher{ gcpinventory.IamRoleAssetType: noopEnricher, gcpinventory.CrmFolderAssetType: noopEnricher, gcpinventory.CrmProjectAssetType: noopEnricher, gcpinventory.StorageBucketAssetType: noopEnricher, gcpinventory.IamServiceAccountKeyAssetType: noopEnricher, gcpinventory.CloudRunService: noopEnricher, gcpinventory.CrmOrgAssetType: enrichOrganization, gcpinventory.ComputeInstanceAssetType: enrichComputeInstance, gcpinventory.ComputeFirewallAssetType: enrichFirewall, gcpinventory.ComputeSubnetworkAssetType: enrichSubnetwork, gcpinventory.IamServiceAccountAssetType: enrichServiceAccount, gcpinventory.GkeClusterAssetType: enrichGkeCluster, gcpinventory.ComputeForwardingRuleAssetType: enrichForwardingRule, gcpinventory.CloudFunctionAssetType: enrichCloudFunction, gcpinventory.ComputeNetworkAssetType: enrichNetwork, } func enrichOrganization(_ *gcpinventory.ExtendedGcpAsset, fields map[string]*structpb.Value) []inventory.AssetEnricher { return []inventory.AssetEnricher{ inventory.WithOrganization(inventory.Organization{ Name: getStringValue("displayName", fields), }), } } func enrichComputeInstance(item *gcpinventory.ExtendedGcpAsset, fields map[string]*structpb.Value) []inventory.AssetEnricher { return []inventory.AssetEnricher{ inventory.WithCloud(inventory.Cloud{ // This will override the default Cloud fields, so we re-add the common ones Provider: inventory.GcpCloudProvider, AccountID: item.CloudAccount.AccountId, AccountName: item.CloudAccount.AccountName, ProjectID: item.CloudAccount.OrganisationId, ProjectName: item.CloudAccount.OrganizationName, ServiceName: item.AssetType, InstanceID: getStringValue("id", fields), InstanceName: getStringValue("name", fields), MachineType: getStringValue("machineType", fields), AvailabilityZone: getStringValue("zone", fields), }), inventory.WithHost(inventory.Host{ ID: getStringValue("id", fields), }), } } func enrichFirewall(_ *gcpinventory.ExtendedGcpAsset, fields map[string]*structpb.Value) []inventory.AssetEnricher { return []inventory.AssetEnricher{ inventory.WithNetwork(inventory.Network{ Name: getStringValue("name", fields), Direction: getStringValue("direction", fields), }), } } func enrichSubnetwork(_ *gcpinventory.ExtendedGcpAsset, fields map[string]*structpb.Value) []inventory.AssetEnricher { return []inventory.AssetEnricher{ inventory.WithNetwork(inventory.Network{ Name: getStringValue("name", fields), Type: strings.ToLower(getStringValue("stackType", fields)), }), } } func enrichServiceAccount(_ *gcpinventory.ExtendedGcpAsset, fields map[string]*structpb.Value) []inventory.AssetEnricher { return []inventory.AssetEnricher{ inventory.WithUser(inventory.User{ Email: getStringValue("email", fields), Name: getStringValue("displayName", fields), }), } } func enrichGkeCluster(_ *gcpinventory.ExtendedGcpAsset, fields map[string]*structpb.Value) []inventory.AssetEnricher { return []inventory.AssetEnricher{ inventory.WithOrchestrator(inventory.Orchestrator{ Type: "kubernetes", ClusterName: getStringValue("name", fields), ClusterID: getStringValue("id", fields), }), } } func enrichForwardingRule(_ *gcpinventory.ExtendedGcpAsset, fields map[string]*structpb.Value) []inventory.AssetEnricher { return []inventory.AssetEnricher{ inventory.WithCloud(inventory.Cloud{ Region: getStringValue("region", fields), }), } } func enrichCloudFunction(_ *gcpinventory.ExtendedGcpAsset, fields map[string]*structpb.Value) []inventory.AssetEnricher { var revision string if serviceConfig, ok := fields["serviceConfig"]; ok { serviceConfigFields := serviceConfig.GetStructValue().GetFields() revision = getStringValue("revision", serviceConfigFields) } return []inventory.AssetEnricher{ inventory.WithURL(inventory.URL{ Full: getStringValue("url", fields), }), inventory.WithFass(inventory.Fass{ Name: getStringValue("name", fields), Version: revision, }), } } func enrichNetwork(_ *gcpinventory.ExtendedGcpAsset, fields map[string]*structpb.Value) []inventory.AssetEnricher { return []inventory.AssetEnricher{ inventory.WithNetwork(inventory.Network{ Name: getStringValue("name", fields), }), } } func noopEnricher(_ *gcpinventory.ExtendedGcpAsset, _ map[string]*structpb.Value) []inventory.AssetEnricher { return []inventory.AssetEnricher{} } func getStringValue(key string, f map[string]*structpb.Value) string { if value, ok := f[key]; ok { return value.GetStringValue() } return "" }