diff --git a/scrapers/azure/azure.go b/scrapers/azure/azure.go index 3a0e2cd1..c6d0c4c6 100644 --- a/scrapers/azure/azure.go +++ b/scrapers/azure/azure.go @@ -19,10 +19,14 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/subscription/armsubscription" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/trafficmanager/armtrafficmanager" "github.com/flanksource/commons/logger" + "github.com/flanksource/duty/models" + "github.com/flanksource/config-db/api" v1 "github.com/flanksource/config-db/api/v1" ) +const ConfigTypePrefix = "Azure::" + type Scraper struct { ctx context.Context cred *azidentity.ClientSecretCredential @@ -102,6 +106,45 @@ func (azure Scraper) Scrape(ctx api.ScrapeContext) v1.ScrapeResults { results = append(results, azure.fetchAdvisorAnalysis()...) } + // Establish relationship of all resources to the corresponding subscription & resource group + for i, r := range results { + if r.ID == "" { + continue + } + + var relateSubscription, relateResourceGroup bool + switch r.Type { + case ConfigTypePrefix + "SUBSCRIPTION": + continue + + case ConfigTypePrefix + "MICROSOFT.RESOURCES/RESOURCEGROUPS": + relateSubscription = true + + default: + relateSubscription = true + relateResourceGroup = true + } + + if relateSubscription { + results[i].RelationshipResults = append(results[i].RelationshipResults, v1.RelationshipResult{ + ConfigExternalID: v1.ExternalID{ExternalID: []string{r.ID}, ConfigType: r.Type}, + RelatedExternalID: v1.ExternalID{ExternalID: []string{"/subscriptions/" + azure.config.SubscriptionID}, ConfigType: ConfigTypePrefix + "SUBSCRIPTION"}, + Relationship: "Subscription" + strings.TrimPrefix(r.Type, ConfigTypePrefix), + }) + } + + if relateResourceGroup && extractResourceGroup(r.ID) != "" { + results[i].RelationshipResults = append(results[i].RelationshipResults, v1.RelationshipResult{ + ConfigExternalID: v1.ExternalID{ExternalID: []string{r.ID}, ConfigType: r.Type}, + RelatedExternalID: v1.ExternalID{ + ExternalID: []string{fmt.Sprintf("/subscriptions/%s/resourcegroups/%s", azure.config.SubscriptionID, extractResourceGroup(r.ID))}, + ConfigType: ConfigTypePrefix + "MICROSOFT.RESOURCES/RESOURCEGROUPS", + }, + Relationship: "Resourcegroup" + strings.TrimPrefix(r.Type, ConfigTypePrefix), + }) + } + } + return results } @@ -313,11 +356,12 @@ func (azure Scraper) fetchVirtualMachines() v1.ScrapeResults { ID: getARMID(v.ID), Name: deref(v.Name), Config: v, - ConfigClass: "VirtualMachine", + ConfigClass: models.ConfigClassVirtualMachine, Type: getARMType(v.Type), }) } } + return results } @@ -628,3 +672,20 @@ func getARMType(rType *string) string { // This is required to match config analysis with the config item. return "Azure::" + strings.ToUpper(deref(rType)) } + +func extractResourceGroup(resourceID string) string { + resourceID = strings.Trim(resourceID, " ") + resourceID = strings.TrimPrefix(resourceID, "/") + + segments := strings.Split(resourceID, "/") + if len(segments) < 4 { + return "" + } + + if segments[2] != "resourcegroups" { + return "" + } + + // The resource group is the third segment + return segments[3] +} diff --git a/scrapers/azure/azure_test.go b/scrapers/azure/azure_test.go new file mode 100644 index 00000000..89e6c5c9 --- /dev/null +++ b/scrapers/azure/azure_test.go @@ -0,0 +1,29 @@ +package azure + +import "testing" + +func TestExtractResourceGroup(t *testing.T) { + tests := []struct { + input string + expectedOutput string + }{ + // Valid input cases + {"/subscriptions/0cd017bb-aa54-5121-b21f-ecf8daee0624/resourcegroups/mc_demo_demo_francecentral", "mc_demo_demo_francecentral"}, + {"/subscriptions/0cd017bb-aa54-5121-b21f-ecf8daee0624/resourcegroups/crossplane", "crossplane"}, + {"/subscriptions/0cd017bb-aa54-5121-b21f-ecf8daee0624/resourcegroups/crossplane/providers/microsoft.containerservice/managedclusters/workload-prod-eu-01", "crossplane"}, + {"/subscriptions/0cd017bb-aa54-5121-b21f-ecf8daee0624/resourcegroups/internal-prod/providers/microsoft.storage/storageaccounts/flanksourcebackups", "internal-prod"}, + {"/subscriptions/0cd017bb-aa54-5121-b21f-ecf8daee0624/resourcegroups/mc_crossplane_crossplane-master_northeurope/providers/microsoft.network/loadbalancers/kubernetes", "mc_crossplane_crossplane-master_northeurope"}, + + // Invalid input cases + {"", ""}, + {"/subscriptions/123", ""}, + {"/subscriptions/456/notresourcegroup/test", ""}, + } + + for _, test := range tests { + result := extractResourceGroup(test.input) + if result != test.expectedOutput { + t.Errorf("Input: %s, Expected: %s, Got: %s", test.input, test.expectedOutput, result) + } + } +}