From 7410dab4f6f6cc3447d3a6ce46c531bab60ece4f Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Sat, 28 Oct 2023 17:43:11 -0700 Subject: [PATCH 01/40] Added Azure nsg-links command to dump NSG attachments --- azure/nsg_links.go | 233 +++++++++++++++++++++++++++++++++++++++++++++ cli/azure.go | 49 ++++++++++ globals/azure.go | 2 + internal/azure.go | 39 ++++++++ 4 files changed, 323 insertions(+) create mode 100644 azure/nsg_links.go diff --git a/azure/nsg_links.go b/azure/nsg_links.go new file mode 100644 index 00000000..a484dcc5 --- /dev/null +++ b/azure/nsg_links.go @@ -0,0 +1,233 @@ +package azure + +import ( + "context" + "strings" + "fmt" + "path/filepath" + "github.com/BishopFox/cloudfox/globals" + "github.com/BishopFox/cloudfox/internal" + "github.com/aws/smithy-go/ptr" + "github.com/fatih/color" + "github.com/kyokomi/emoji" + "github.com/Azure/azure-sdk-for-go/profiles/latest/network/mgmt/network" + "github.com/Azure/go-autorest/autorest/azure" +) + + +func AzNSGLinksCommand(AzTenantID string, AzSubscription string, AzResourceIDs []string, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { + + + if AzTenantID != "" && AzSubscription == "" { + // cloudfox azure nsg-links --tenant [TENANT_ID | PRIMARY_DOMAIN] + tenantInfo := populateTenant(AzTenantID) + + if AzMergedTable { + + // set up table vars + var header []string + var body [][]string + // setup logging client + o := internal.OutputClient{ + Verbosity: AzVerbosity, + CallingModule: globals.AZ_NSG_MODULE_NAME, + Table: internal.TableClient{ + Wrap: AzWrapTable, + }, + } + + var err error + + fmt.Printf("[%s][%s] Enumerating Network Security Group links for tenant %s\n", + color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_NSGLINKS_MODULE_NAME), + fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID))) + + o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain) + o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level") + + header, body, err = getNSGInfoPerTenant(ptr.ToString(tenantInfo.ID)) + + if err != nil { + return err + } + o.Table.TableFiles = append(o.Table.TableFiles, + internal.TableFile{ + Header: header, + Body: body, + Name: fmt.Sprintf(globals.AZ_NSG_MODULE_NAME)}) + + if body != nil { + o.WriteFullOutput(o.Table.TableFiles, nil) + } + } else { + + for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(tenantInfo.ID)) { + runNSGCommandForSingleSubcription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + } + } + + } else if AzTenantID == "" && AzSubscription != "" { + //cloudfox azure nsg-links --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME] + runNSGCommandForSingleSubcription(AzSubscription, AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + + } else { + // Error: please make a valid flag selection + fmt.Println("Please enter a valid input with a valid flag. Use --help for info.") + } + + return nil +} + +func runNSGCommandForSingleSubcription(AzSubscription string, AzOutputDirectory string, AzVerbosity int, AzWrapTable bool, Version string) error { + var err error + // setup logging client + o := internal.OutputClient{ + Verbosity: AzVerbosity, + CallingModule: globals.AZ_NSGLINKS_MODULE_NAME, + Table: internal.TableClient{ + Wrap: AzWrapTable, + }, + } + + // set up table vars + var header []string + var body [][]string + + tenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription)) + tenantInfo := populateTenant(tenantID) + AzSubscriptionInfo := PopulateSubsriptionType(AzSubscription) + o.PrefixIdentifier = AzSubscriptionInfo.Name + o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name) + + fmt.Printf( + "[%s][%s] Enumerating Network Security Groups links for subscription %s\n", + color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), + color.CyanString(globals.AZ_NSGLINKS_MODULE_NAME), + fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID)) + //AzTenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription)) + header, body, err = getNSGInfoPerSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID) + if err != nil { + return err + } + + o.Table.TableFiles = append(o.Table.TableFiles, + internal.TableFile{ + Header: header, + Body: body, + Name: fmt.Sprintf(globals.AZ_NSGLINKS_MODULE_NAME)}) + if body != nil { + o.WriteFullOutput(o.Table.TableFiles, nil) + + } + return nil + +} + + +func getNSGInfoPerTenant(AzTenantID string) ([]string, [][]string, error) { + var err error + var header []string + var body, b [][]string + + for _, s := range GetSubscriptionsPerTenantID(AzTenantID) { + header, b, err = getNSGData(ptr.ToString(s.SubscriptionID)) + if err != nil { + return nil, nil, err + } else { + body = append(body, b...) + } + } + return header, body, nil +} + +func getNSGInfoPerSubscription(AzTenantID, AzSubscriptionID string) ([]string, [][]string, error) { + var err error + var header []string + var body [][]string + + for _, s := range GetSubscriptions() { + if ptr.ToString(s.SubscriptionID) == AzSubscriptionID { + header, body, err = getNSGData(ptr.ToString(s.SubscriptionID)) + if err != nil { + return nil, nil, err + } + } + } + return header, body, nil +} +func value(ptr *string) string { + if ptr != nil { + return *ptr + } else { + return "AAAAA" + } +} + +func getNSGData(subscriptionID string) ([]string, [][]string, error) { + tableHeader := []string{"Subscription Name", "Network Security Group", "Link Type", "Linked Name", "Link Target"} + var tableBody [][]string + networkSecurityGroups, err := getNSG(subscriptionID) + nsgClient := internal.GetNSGClient(subscriptionID) + //subnetsClient := internal.GetSubnetsClient(subscriptionID) + if err != nil { + return tableHeader, tableBody, err + } + for _, networkSecurityGroup := range *networkSecurityGroups { + var resource azure.Resource + resource, err = azure.ParseResourceID(*networkSecurityGroup.ID) + if err != nil { + continue + } + networkSecurityGroup, _ = nsgClient.Get(context.TODO(), resource.ResourceGroup, resource.ResourceName, "Subnets,NetworkInterfaces") + fmt.Println(*networkSecurityGroup.ID) + if networkSecurityGroup.Subnets != nil { + for _, subnet := range *networkSecurityGroup.Subnets { + var addressPrefixes []string + if subnet.AddressPrefixes != nil { + for _, prefix := range *subnet.AddressPrefixes { + addressPrefixes = append(addressPrefixes, prefix) + } + } + if subnet.AddressPrefix != nil { + addressPrefixes = append(addressPrefixes, *subnet.AddressPrefix) + } + tableBody = append(tableBody, + []string{ + subscriptionID, + *networkSecurityGroup.Name, + "Subnet", + value(subnet.Name), + strings.Join(addressPrefixes[:], "\n"), + }, + ) + } + } + if networkSecurityGroup.NetworkInterfaces != nil { + for _, networkInterface := range *networkSecurityGroup.NetworkInterfaces { + tableBody = append(tableBody, + []string{ + subscriptionID, + *networkSecurityGroup.Name, + "NIC", + value(networkInterface.Name), + strings.Join(internal.GetIPAddressesFromInterface(&networkInterface)[:], "\n"), + }, + ) + } + } + } + return tableHeader, tableBody, nil +} + + +func getNSG(subscriptionID string) (*[]network.SecurityGroup, error) { + nsgClient := internal.GetNSGClient(subscriptionID) + var networkSecurityGroups []network.SecurityGroup + for page, err := nsgClient.ListAll(context.TODO()); page.NotDone(); page.Next() { + if err != nil { + return nil, fmt.Errorf("could not get network security groups for subscription") + } + networkSecurityGroups = append(networkSecurityGroups, page.Values()...) + } + return &networkSecurityGroups, nil +} diff --git a/cli/azure.go b/cli/azure.go index b98bd55f..230fa675 100644 --- a/cli/azure.go +++ b/cli/azure.go @@ -17,6 +17,8 @@ var ( AzWrapTable bool AzMergedTable bool + AzResourceIDs []string + AzCommands = &cobra.Command{ Use: "azure", Aliases: []string{"az"}, @@ -113,6 +115,50 @@ Enumerate storage accounts for a specific subscription: } }, } +/* + AzNSGCommand = &cobra.Command{ + Use: "nsg", + Aliases: []string{}, + Short: "Enumerates azure Network Securiy Groups (NSG)", + Long: ` +Enumerate Network Security Groups rules for a specific tenant: +./cloudfox az nsg --tenant TENANT_ID + +Enumerate Network Security Groups rules for a specific subscription: +./cloudfox az nsg --subscription SUBSCRIPTION_ID + +Enumerate rules for a specific Network Security Group: +./cloudfox az nsg --nsg NSG_ID +`, + Run: func(cmd *cobra.Command, args []string) { + err := azure.AzNSGCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + if err != nil { + log.Fatal(err) + } + }, + } +*/ + AzNSGLinksCommand = &cobra.Command{ + Use: "nsg-links", + Aliases: []string{}, + Short: "Enumerates azure Network Securiy Groups links", + Long: ` +Enumerate Network Security Groups links for a specific tenant: +./cloudfox az nsg-links --tenant TENANT_ID + +Enumerate Network Security Groups links for a specific subscription: +./cloudfox az nsg-links --subscription SUBSCRIPTION_ID + +Enumerate links for a specific Network Security Group: +./cloudfox az nsg-links --nsg NSG_ID +`, + Run: func(cmd *cobra.Command, args []string) { + err := azure.AzNSGLinksCommand(AzTenantID, AzSubscription, AzResourceIDs, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + if err != nil { + log.Fatal(err) + } + }, + } ) func init() { @@ -126,6 +172,7 @@ func init() { AzCommands.PersistentFlags().StringVarP(&AzTenantID, "tenant", "t", "", "Tenant name") AzCommands.PersistentFlags().StringVarP(&AzSubscription, "subscription", "s", "", "Subscription ID or Name") AzCommands.PersistentFlags().StringVarP(&AzRGName, "resource-group", "g", "", "Resource Group name") + AzCommands.PersistentFlags().StringSliceVarP(&AzResourceIDs, "resource-id", "r", []string{}, "Resource ID (/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName})") AzCommands.PersistentFlags().BoolVarP(&AzWrapTable, "wrap", "w", false, "Wrap table to fit in terminal (complicates grepping)") AzCommands.PersistentFlags().BoolVarP(&AzMergedTable, "merged-table", "m", false, "Writes a single table for all subscriptions in the tenant. Default writes a table per subscription.") @@ -134,6 +181,8 @@ func init() { AzRBACCommand, AzVMsCommand, AzStorageCommand, +// AzNSGCommand, + AzNSGLinksCommand, AzInventoryCommand) } diff --git a/globals/azure.go b/globals/azure.go index 7dfa5706..e6d22c02 100644 --- a/globals/azure.go +++ b/globals/azure.go @@ -21,6 +21,8 @@ const AZ_INVENTORY_MODULE_NAME = "inventory" const AZ_VMS_MODULE_NAME = "vms" const AZ_RBAC_MODULE_NAME = "rbac" const AZ_STORAGE_MODULE_NAME = "storage" +const AZ_NSG_MODULE_NAME = "nsg" +const AZ_NSGLINKS_MODULE_NAME = "nsglinks" // Microsoft endpoints const AZ_RESOURCE_MANAGER_ENDPOINT = "https://management.azure.com/" diff --git a/internal/azure.go b/internal/azure.go index b75d13e8..eaed8ab4 100644 --- a/internal/azure.go +++ b/internal/azure.go @@ -126,6 +126,28 @@ func GetNICClient(subscriptionID string) network.InterfacesClient { return client } +func GetNSGClient(subscriptionID string) network.SecurityGroupsClient { + client := network.NewSecurityGroupsClient(subscriptionID) + authorizer, err := getAuthorizer(globals.AZ_RESOURCE_MANAGER_ENDPOINT) + if err != nil { + log.Fatalf("failed to get nsg client: %s", err) + } + client.Authorizer = authorizer + client.AddToUserAgent(globals.CLOUDFOX_USER_AGENT) + return client +} + +func GetSubnetsClient(subscriptionID string) network.SubnetsClient { + client := network.NewSubnetsClient(subscriptionID) + authorizer, err := getAuthorizer(globals.AZ_RESOURCE_MANAGER_ENDPOINT) + if err != nil { + log.Fatalf("failed to get subnets client: %s", err) + } + client.Authorizer = authorizer + client.AddToUserAgent(globals.CLOUDFOX_USER_AGENT) + return client +} + func GetPublicIPClient(subscriptionID string) network.PublicIPAddressesClient { client := network.NewPublicIPAddressesClient(subscriptionID) authorizer, err := getAuthorizer(globals.AZ_RESOURCE_MANAGER_ENDPOINT) @@ -171,3 +193,20 @@ func GetARMresourcesClient(tenantID, subscriptionID string) *armresources.Client } return client } + +func GetIPAddressesFromInterface(iface *network.Interface) []string{ + var ipAddresses []string + for _, ipConfiguration := range *iface.InterfacePropertiesFormat.IPConfigurations { + if ipConfiguration.InterfaceIPConfigurationPropertiesFormat.PrivateIPAddress != nil { + ipAddresses = append(ipAddresses, *ipConfiguration.InterfaceIPConfigurationPropertiesFormat.PrivateIPAddress) + } + if ipConfiguration.InterfaceIPConfigurationPropertiesFormat.PublicIPAddress != nil { + if ipConfiguration.InterfaceIPConfigurationPropertiesFormat.PublicIPAddress.PublicIPAddressPropertiesFormat != nil { + if ipConfiguration.InterfaceIPConfigurationPropertiesFormat.PublicIPAddress.PublicIPAddressPropertiesFormat.IPAddress != nil { + ipAddresses = append(ipAddresses, *ipConfiguration.InterfaceIPConfigurationPropertiesFormat.PublicIPAddress.PublicIPAddressPropertiesFormat.IPAddress) + } + } + } + } + return ipAddresses +} From 783524ba9c975e78cc2e36b8871815e1dc5ecfda Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Sat, 28 Oct 2023 17:45:00 -0700 Subject: [PATCH 02/40] Added skeleton for Azure nsg-rules command --- azure/nsg_links.go | 12 ++++---- azure/nsg_rules.go | 76 ++++++++++++++++++++++++++++++++++++++++++++++ cli/azure.go | 18 +++++------ globals/azure.go | 4 +-- 4 files changed, 92 insertions(+), 18 deletions(-) create mode 100644 azure/nsg_rules.go diff --git a/azure/nsg_links.go b/azure/nsg_links.go index a484dcc5..b241dd8e 100644 --- a/azure/nsg_links.go +++ b/azure/nsg_links.go @@ -30,7 +30,7 @@ func AzNSGLinksCommand(AzTenantID string, AzSubscription string, AzResourceIDs [ // setup logging client o := internal.OutputClient{ Verbosity: AzVerbosity, - CallingModule: globals.AZ_NSG_MODULE_NAME, + CallingModule: globals.AZ_NSG_LINKS_MODULE_NAME, Table: internal.TableClient{ Wrap: AzWrapTable, }, @@ -39,7 +39,7 @@ func AzNSGLinksCommand(AzTenantID string, AzSubscription string, AzResourceIDs [ var err error fmt.Printf("[%s][%s] Enumerating Network Security Group links for tenant %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_NSGLINKS_MODULE_NAME), + color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_NSG_LINKS_MODULE_NAME), fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID))) o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain) @@ -54,7 +54,7 @@ func AzNSGLinksCommand(AzTenantID string, AzSubscription string, AzResourceIDs [ internal.TableFile{ Header: header, Body: body, - Name: fmt.Sprintf(globals.AZ_NSG_MODULE_NAME)}) + Name: fmt.Sprintf(globals.AZ_NSG_LINKS_MODULE_NAME)}) if body != nil { o.WriteFullOutput(o.Table.TableFiles, nil) @@ -83,7 +83,7 @@ func runNSGCommandForSingleSubcription(AzSubscription string, AzOutputDirectory // setup logging client o := internal.OutputClient{ Verbosity: AzVerbosity, - CallingModule: globals.AZ_NSGLINKS_MODULE_NAME, + CallingModule: globals.AZ_NSG_LINKS_MODULE_NAME, Table: internal.TableClient{ Wrap: AzWrapTable, }, @@ -102,7 +102,7 @@ func runNSGCommandForSingleSubcription(AzSubscription string, AzOutputDirectory fmt.Printf( "[%s][%s] Enumerating Network Security Groups links for subscription %s\n", color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), - color.CyanString(globals.AZ_NSGLINKS_MODULE_NAME), + color.CyanString(globals.AZ_NSG_LINKS_MODULE_NAME), fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID)) //AzTenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription)) header, body, err = getNSGInfoPerSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID) @@ -114,7 +114,7 @@ func runNSGCommandForSingleSubcription(AzSubscription string, AzOutputDirectory internal.TableFile{ Header: header, Body: body, - Name: fmt.Sprintf(globals.AZ_NSGLINKS_MODULE_NAME)}) + Name: fmt.Sprintf(globals.AZ_NSG_LINKS_MODULE_NAME)}) if body != nil { o.WriteFullOutput(o.Table.TableFiles, nil) diff --git a/azure/nsg_rules.go b/azure/nsg_rules.go new file mode 100644 index 00000000..e81d223b --- /dev/null +++ b/azure/nsg_rules.go @@ -0,0 +1,76 @@ +package azure + +import ( + "fmt" + "path/filepath" + "github.com/BishopFox/cloudfox/globals" + "github.com/BishopFox/cloudfox/internal" + "github.com/aws/smithy-go/ptr" + "github.com/fatih/color" + "github.com/kyokomi/emoji" +) + + +func AzNSGRulesCommand(AzTenantID string, AzSubscription string, AzResourceIDs []string, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { + + + if AzTenantID != "" && AzSubscription == "" { + // cloudfox azure nsg-rules --tenant [TENANT_ID | PRIMARY_DOMAIN] + tenantInfo := populateTenant(AzTenantID) + + if AzMergedTable { + + // set up table vars + var header []string + var body [][]string + // setup logging client + o := internal.OutputClient{ + Verbosity: AzVerbosity, + CallingModule: globals.AZ_NSG_RULES_MODULE_NAME, + Table: internal.TableClient{ + Wrap: AzWrapTable, + }, + } + + var err error + + fmt.Printf("[%s][%s] Enumerating Network Security Group rules for tenant %s\n", + color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_NSG_RULES_MODULE_NAME), + fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID))) + + o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain) + o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level") + + header, body, err = getNSGInfoPerTenant(ptr.ToString(tenantInfo.ID)) + + if err != nil { + return err + } + o.Table.TableFiles = append(o.Table.TableFiles, + internal.TableFile{ + Header: header, + Body: body, + Name: fmt.Sprintf(globals.AZ_NSG_RULES_MODULE_NAME)}) + + if body != nil { + o.WriteFullOutput(o.Table.TableFiles, nil) + } + } else { + + for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(tenantInfo.ID)) { + //runNSGCommandForSingleSubcription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + fmt.Println(s) + } + } + + } else if AzTenantID == "" && AzSubscription != "" { + //cloudfox azure nsg-rules --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME] + runNSGCommandForSingleSubcription(AzSubscription, AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + + } else { + // Error: please make a valid flag selection + fmt.Println("Please enter a valid input with a valid flag. Use --help for info.") + } + + return nil +} diff --git a/cli/azure.go b/cli/azure.go index 230fa675..fe708205 100644 --- a/cli/azure.go +++ b/cli/azure.go @@ -115,29 +115,27 @@ Enumerate storage accounts for a specific subscription: } }, } -/* - AzNSGCommand = &cobra.Command{ - Use: "nsg", + AzNSGRulesCommand = &cobra.Command{ + Use: "nsg-rules", Aliases: []string{}, - Short: "Enumerates azure Network Securiy Groups (NSG)", + Short: "Enumerates azure Network Securiy Group rules", Long: ` Enumerate Network Security Groups rules for a specific tenant: -./cloudfox az nsg --tenant TENANT_ID +./cloudfox az nsg-rukes --tenant TENANT_ID Enumerate Network Security Groups rules for a specific subscription: -./cloudfox az nsg --subscription SUBSCRIPTION_ID +./cloudfox az nsg-rules --subscription SUBSCRIPTION_ID Enumerate rules for a specific Network Security Group: -./cloudfox az nsg --nsg NSG_ID +./cloudfox az nsg-rules --nsg NSG_ID `, Run: func(cmd *cobra.Command, args []string) { - err := azure.AzNSGCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + err := azure.AzNSGRulesCommand(AzTenantID, AzSubscription, AzResourceIDs, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) if err != nil { log.Fatal(err) } }, } -*/ AzNSGLinksCommand = &cobra.Command{ Use: "nsg-links", Aliases: []string{}, @@ -181,7 +179,7 @@ func init() { AzRBACCommand, AzVMsCommand, AzStorageCommand, -// AzNSGCommand, + AzNSGRulesCommand, AzNSGLinksCommand, AzInventoryCommand) diff --git a/globals/azure.go b/globals/azure.go index e6d22c02..1c42d2ae 100644 --- a/globals/azure.go +++ b/globals/azure.go @@ -21,8 +21,8 @@ const AZ_INVENTORY_MODULE_NAME = "inventory" const AZ_VMS_MODULE_NAME = "vms" const AZ_RBAC_MODULE_NAME = "rbac" const AZ_STORAGE_MODULE_NAME = "storage" -const AZ_NSG_MODULE_NAME = "nsg" -const AZ_NSGLINKS_MODULE_NAME = "nsglinks" +const AZ_NSG_LINKS_MODULE_NAME = "nsg-links" +const AZ_NSG_RULES_MODULE_NAME = "nsg-rules" // Microsoft endpoints const AZ_RESOURCE_MANAGER_ENDPOINT = "https://management.azure.com/" From 5b5a69a6054e411b22f035b567785078c0e8c19c Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Sat, 28 Oct 2023 18:17:27 -0700 Subject: [PATCH 03/40] Migrated AWS CLI structure into Azure CLI --- cli/azure.go | 100 +++++++++++++++++++++++++++++---------------------- 1 file changed, 57 insertions(+), 43 deletions(-) diff --git a/cli/azure.go b/cli/azure.go index fe708205..d9eeec92 100644 --- a/cli/azure.go +++ b/cli/azure.go @@ -36,12 +36,7 @@ var ( Long: ` Display Available Azure CLI Sessions: ./cloudfox az whoami`, - Run: func(cmd *cobra.Command, args []string) { - err := azure.AzWhoamiCommand(AzOutputDirectory, cmd.Root().Version, AzWrapTable, AzVerbosity, AzWhoamiListRGsAlso) - if err != nil { - log.Fatal(err) - } - }, + Run: runAzWhoamiCommand, } AzInventoryCommand = &cobra.Command{ Use: "inventory", @@ -54,12 +49,7 @@ Enumerate inventory for a specific tenant: Enumerate inventory for a specific subscription: ./cloudfox az inventory --subscription SUBSCRIPTION_ID `, - Run: func(cmd *cobra.Command, args []string) { - err := azure.AzInventoryCommand(AzTenantID, AzSubscription, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) - if err != nil { - log.Fatal(err) - } - }, + Run: runAzInventoryCommand, } AzRBACCommand = &cobra.Command{ Use: "rbac", @@ -72,13 +62,7 @@ Enumerate role assignments for a specific tenant: Enumerate role assignments for a specific subscription: ./cloudfox az rbac --subscription SUBSCRIPTION_ID `, - Run: func(cmd *cobra.Command, args []string) { - - err := azure.AzRBACCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) - if err != nil { - log.Fatal(err) - } - }, + Run: runAzRBACCommand, } AzVMsCommand = &cobra.Command{ Use: "vms", @@ -90,12 +74,7 @@ Enumerate VMs for a specific tenant: Enumerate VMs for a specific subscription: ./cloudfox az vms --subscription SUBSCRIPTION_ID`, - Run: func(cmd *cobra.Command, args []string) { - err := azure.AzVMsCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) - if err != nil { - log.Fatal(err) - } - }, + Run: runAzVMsCommand, } AzStorageCommand = &cobra.Command{ Use: "storage", @@ -108,12 +87,7 @@ Enumerate storage accounts for a specific tenant: Enumerate storage accounts for a specific subscription: ./cloudfox az storage --subscription SUBSCRIPTION_ID `, - Run: func(cmd *cobra.Command, args []string) { - err := azure.AzStorageCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) - if err != nil { - log.Fatal(err) - } - }, + Run: runAzStorageCommand, } AzNSGRulesCommand = &cobra.Command{ Use: "nsg-rules", @@ -129,12 +103,7 @@ Enumerate Network Security Groups rules for a specific subscription: Enumerate rules for a specific Network Security Group: ./cloudfox az nsg-rules --nsg NSG_ID `, - Run: func(cmd *cobra.Command, args []string) { - err := azure.AzNSGRulesCommand(AzTenantID, AzSubscription, AzResourceIDs, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) - if err != nil { - log.Fatal(err) - } - }, + Run: runAzNSGRulesCommand, } AzNSGLinksCommand = &cobra.Command{ Use: "nsg-links", @@ -150,15 +119,60 @@ Enumerate Network Security Groups links for a specific subscription: Enumerate links for a specific Network Security Group: ./cloudfox az nsg-links --nsg NSG_ID `, - Run: func(cmd *cobra.Command, args []string) { - err := azure.AzNSGLinksCommand(AzTenantID, AzSubscription, AzResourceIDs, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) - if err != nil { - log.Fatal(err) - } - }, + Run: runAzNSGLinksCommand, } ) +func runAzWhoamiCommand (cmd *cobra.Command, args []string) { + err := azure.AzWhoamiCommand(AzOutputDirectory, cmd.Root().Version, AzWrapTable, AzVerbosity, AzWhoamiListRGsAlso) + if err != nil { + log.Fatal(err) + } +} + +func runAzInventoryCommand (cmd *cobra.Command, args []string) { + err := azure.AzInventoryCommand(AzTenantID, AzSubscription, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + if err != nil { + log.Fatal(err) + } +} + +func runAzRBACCommand (cmd *cobra.Command, args []string) { + err := azure.AzRBACCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + if err != nil { + log.Fatal(err) + } +} + +func runAzVMsCommand (cmd *cobra.Command, args []string) { + err := azure.AzVMsCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + if err != nil { + log.Fatal(err) + } +} + +func runAzStorageCommand (cmd *cobra.Command, args []string) { + err := azure.AzStorageCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + if err != nil { + log.Fatal(err) + } +} + +func runAzNSGRulesCommand(cmd *cobra.Command, args []string) { + err := azure.AzNSGRulesCommand(AzTenantID, AzSubscription, AzResourceIDs, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + if err != nil { + log.Fatal(err) + } +} + +func runAzNSGLinksCommand(cmd *cobra.Command, args []string) { + err := azure.AzNSGLinksCommand(AzTenantID, AzSubscription, AzResourceIDs, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + if err != nil { + log.Fatal(err) + } +} + + func init() { AzWhoamiCommand.Flags().BoolVarP(&AzWhoamiListRGsAlso, "list-rgs", "l", false, "Drill down to the resource group level") From e5de75bd07ed4789f6ebb9e8d88ba05aacb7d657 Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Sun, 29 Oct 2023 00:01:24 -0700 Subject: [PATCH 04/40] Started to prepare new resource selection logic for the Azure subcommand --- azure/shared.go | 2 +- azure/storage_test.go | 4 +- azure/vms.go | 4 +- azure/vms_test.go | 2 +- azure/whoami.go | 14 ++--- azure/whoami_test.go | 4 +- cli/azure.go | 137 ++++++++++++++++++++++++++++++++++++++---- 7 files changed, 140 insertions(+), 27 deletions(-) diff --git a/azure/shared.go b/azure/shared.go index 24a17e64..c642c6f6 100644 --- a/azure/shared.go +++ b/azure/shared.go @@ -116,7 +116,7 @@ func GetDefaultDomainFromTenantID(tenantID string) (string, error) { func populateTenant(tenantID string) TenantInfo { - for _, t := range getTenants() { + for _, t := range GetTenants() { if ptr.ToString(t.TenantID) == tenantID || ptr.ToString(t.DefaultDomain) == tenantID { var subscriptions []SubsriptionInfo for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(t.ID)) { diff --git a/azure/storage_test.go b/azure/storage_test.go index eb16fcd1..babd4279 100644 --- a/azure/storage_test.go +++ b/azure/storage_test.go @@ -69,9 +69,9 @@ func TestAzStorageCommand(t *testing.T) { } internal.MockFileSystem(true) // Mocked functions to simulate Azure calls and responses - getTenants = mockedGetTenants + GetTenants = mockedGetTenants GetSubscriptions = mockedGetSubscriptions - getResourceGroups = mockedGetResourceGroups + GetResourceGroups = mockedGetResourceGroups getStorageAccounts = mockedGetStorageAccounts for _, s := range subtests { diff --git a/azure/vms.go b/azure/vms.go index 1b4c54ef..c0b320a0 100644 --- a/azure/vms.go +++ b/azure/vms.go @@ -150,7 +150,7 @@ func getVMsPerTenantID(AzTenantID string) ([]string, [][]string, string) { var err error for _, s := range GetSubscriptionsPerTenantID(AzTenantID) { - for _, rg := range getResourceGroups(ptr.ToString(s.SubscriptionID)) { + for _, rg := range GetResourceGroups(ptr.ToString(s.SubscriptionID)) { resultsHeader, b, userData, err = getComputeRelevantData(s, rg) if err != nil { fmt.Printf("[%s] Could not enumerate VMs for resource group %s in subscription %s\n", color.CyanString(globals.AZ_VMS_MODULE_NAME), ptr.ToString(rg.Name), ptr.ToString(s.SubscriptionID)) @@ -172,7 +172,7 @@ func getVMsPerSubscriptionID(AzSubscriptionID string) ([]string, [][]string, str for _, s := range GetSubscriptions() { if ptr.ToString(s.SubscriptionID) == AzSubscriptionID { - for _, rg := range getResourceGroups(ptr.ToString(s.SubscriptionID)) { + for _, rg := range GetResourceGroups(ptr.ToString(s.SubscriptionID)) { resultsHeader, b, userData, err = getComputeRelevantData(s, rg) if err != nil { fmt.Printf("[%s] Could not enumerate VMs for resource group %s in subscription %s\n", color.CyanString(globals.AZ_VMS_MODULE_NAME), ptr.ToString(rg.Name), ptr.ToString(s.SubscriptionID)) diff --git a/azure/vms_test.go b/azure/vms_test.go index 3733926d..66a645c6 100644 --- a/azure/vms_test.go +++ b/azure/vms_test.go @@ -77,7 +77,7 @@ func TestAzVMsCommand(t *testing.T) { // Mocked functions to simulate Azure calls and responses GetSubscriptions = mockedGetSubscriptions - getResourceGroups = mockedGetResourceGroups + GetResourceGroups = mockedGetResourceGroups getComputeVMsPerResourceGroup = mockedGetComputeVMsPerResourceGroup getNICdetails = mockedGetNICdetails getPublicIP = mockedGetPublicIP diff --git a/azure/whoami.go b/azure/whoami.go index 9e9c8f3f..9e9ebcfa 100644 --- a/azure/whoami.go +++ b/azure/whoami.go @@ -64,10 +64,10 @@ func getWhoamiRelevantDataPerRG() ([]string, [][]string) { tableHead := []string{"Tenant ID", "Tentant Primary Domain", "Subscription ID", "Subscription Name", "RG Name", "Region"} var tableBody [][]string - for _, t := range getTenants() { + for _, t := range GetTenants() { for _, s := range GetSubscriptions() { if ptr.ToString(t.TenantID) == ptr.ToString(s.TenantID) { - for _, rg := range getResourceGroups(ptr.ToString(s.SubscriptionID)) { + for _, rg := range GetResourceGroups(ptr.ToString(s.SubscriptionID)) { tableBody = append( tableBody, []string{ @@ -90,7 +90,7 @@ func getWhoamiRelevantDataSubsOnly() ([]string, [][]string) { tableHead := []string{"Tenant ID", "Tenant Primary Domain", "Subscription ID", "Subscription Name"} var tableBody [][]string - for _, t := range getTenants() { + for _, t := range GetTenants() { for _, s := range GetSubscriptions() { if ptr.ToString(t.TenantID) == ptr.ToString(s.TenantID) { tableBody = append( @@ -108,9 +108,9 @@ func getWhoamiRelevantDataSubsOnly() ([]string, [][]string) { return tableHead, tableBody } -var getTenants = getTenantsOriginal +var GetTenants = GetTenantsOriginal -func getTenantsOriginal() []subscriptions.TenantIDDescription { +func GetTenantsOriginal() []subscriptions.TenantIDDescription { tenantsClient := internal.GetTenantsClient() var results []subscriptions.TenantIDDescription for page, err := tenantsClient.List(context.TODO()); page.NotDone(); err = page.Next() { @@ -163,9 +163,9 @@ func mockedGetSubscriptions() []subscriptions.Subscription { return results } -var getResourceGroups = getResourceGroupsOriginal +var GetResourceGroups = GetResourceGroupsOriginal -func getResourceGroupsOriginal(subscriptionID string) []resources.Group { +func GetResourceGroupsOriginal(subscriptionID string) []resources.Group { var results []resources.Group rgClient := internal.GetResourceGroupsClient(subscriptionID) diff --git a/azure/whoami_test.go b/azure/whoami_test.go index 4395cf69..0ab6efdc 100644 --- a/azure/whoami_test.go +++ b/azure/whoami_test.go @@ -14,9 +14,9 @@ func TestAzWhoamiCommand(t *testing.T) { fmt.Println("[test case] Azure Whoami Command") // Mocked functions to simulate Azure calls and responses - getTenants = mockedGetTenants + GetTenants = mockedGetTenants GetSubscriptions = mockedGetSubscriptions - getResourceGroups = mockedGetResourceGroups + GetResourceGroups = mockedGetResourceGroups // Test case parameters internal.MockFileSystem(true) diff --git a/cli/azure.go b/cli/azure.go index d9eeec92..738aa67d 100644 --- a/cli/azure.go +++ b/cli/azure.go @@ -2,28 +2,42 @@ package cli import ( "log" + "fmt" "github.com/BishopFox/cloudfox/azure" + az "github.com/Azure/go-autorest/autorest/azure" "github.com/spf13/cobra" + "github.com/kyokomi/emoji" + "github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions" + "github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/resources" ) var ( - AzTenantID string - AzSubscription string - AzRGName string - AzOutputFormat string - AzOutputDirectory string - AzVerbosity int - AzWrapTable bool - AzMergedTable bool + AzTenantID string + AzSubscription string + AzRGName string + AzOutputFormat string + AzOutputDirectory string + AzVerbosity int + AzWrapTable bool + AzMergedTable bool - AzResourceIDs []string + AzTenantRefs []string + AzSubscriptionRefs []string + AzRGRefs []string + AzResourceRefs []string + + AzTenants []*subscriptions.TenantIDDescription + AzSubscriptions []*subscriptions.Subscription + AzRGs []*resources.Group + AzResources []*az.Resource AzCommands = &cobra.Command{ Use: "azure", Aliases: []string{"az"}, Long: `See "Available Commands" for Azure Modules below`, Short: "See \"Available Commands\" for Azure Modules below", + PersistentPreRun: azurePreRun, Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, @@ -159,19 +173,113 @@ func runAzStorageCommand (cmd *cobra.Command, args []string) { } func runAzNSGRulesCommand(cmd *cobra.Command, args []string) { - err := azure.AzNSGRulesCommand(AzTenantID, AzSubscription, AzResourceIDs, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + err := azure.AzNSGRulesCommand(AzTenantID, AzSubscription, AzResourceRefs, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) if err != nil { log.Fatal(err) } } func runAzNSGLinksCommand(cmd *cobra.Command, args []string) { - err := azure.AzNSGLinksCommand(AzTenantID, AzSubscription, AzResourceIDs, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + err := azure.AzNSGLinksCommand(AzTenantID, AzSubscription, AzResourceRefs, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) if err != nil { log.Fatal(err) } } +func azurePreRun(cmd *cobra.Command, args []string) { + availableSubscriptions := azure.GetSubscriptions() + availableTenants := azure.GetTenants() + // resource identifiers were submitted on the CLI, running modules on them only + if len(AzResourceRefs) > 0 { + fmt.Printf("[%s] Azure resource identifiers submitted, skipping submitted tenants and subscriptions\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version))) + // remove any other resource scope filter + AzTenants = nil + AzSubscriptions = nil + AzRGs = nil + + var ( + err error + resource az.Resource + ) + // check if the submitted resource can be reached with existing credentials + // this does not guarantees that the resource will be found since only the prefix of the resource ID + // is checked against the available ones + for _, azResourceRef := range AzResourceRefs { + resource, err = az.ParseResourceID(azResourceRef) + if err != nil { + fmt.Printf("[%s] Invalid resource identifier : %s\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), azResourceRef) + continue + } + for _, subscription := range availableSubscriptions { + if resource.SubscriptionID == *subscription.SubscriptionID { + // add the resource to the final list of targets + AzResources = append(AzResources, &resource) + // also add the associated subscription since you cannot query a resource alone + AzSubscriptions = append(AzSubscriptions, &subscription) + goto FOUND_RESOURCE + } + } + fmt.Printf("[%s] No active credentials valid for resource %s, removing from target list\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), azResourceRef) + FOUND_RESOURCE: + } + return + } + // resource groups were submitted on the CLI, running modules on them only + if len(AzRGRefs) > 0 { + fmt.Printf("[%s] Azure subscriptions submitted, skipping submitted tenants\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version))) + for _, AzRGRef := range AzRGRefs { + for _, subscription := range availableSubscriptions { + for _, rg := range azure.GetResourceGroups(*subscription.SubscriptionID) { + if *rg.ID == AzRGRef { + AzRGs = append(AzRGs, &rg) + goto FOUND_RG + } else if *rg.Name == AzRGRef { + AzRGs = append(AzRGs, &rg) + goto FOUND_RG + } + } + } + fmt.Printf("[%s] Resource Group %s not accessible with active CLI credentials, removing from targetst\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), AzRGRef) + FOUND_RG: + } + } + // subscriptions were submitted on the CLI, running modules on them only + if len(AzSubscriptionRefs) > 0 { + fmt.Printf("[%s] Azure subscriptions submitted, skipping submitted tenants\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version))) + // remove any other resource scope filter + AzTenants = nil + for _, AzSubscriptionRef := range AzSubscriptionRefs { + for _, subscription := range availableSubscriptions { + if *subscription.ID == AzSubscriptionRef { + AzSubscriptions = append(AzSubscriptions, &subscription) + goto FOUND_SUB + } else if *subscription.DisplayName == AzSubscriptionRef { + AzSubscriptions = append(AzSubscriptions, &subscription) + goto FOUND_SUB + } + } + fmt.Printf("[%s] Subscription %s not accessible with active CLI credentials, removing from targetst\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), AzSubscriptionRef) + FOUND_SUB: + } + return + } + // tenants were submitted on the CLI, running modules on them only + if len(AzTenantRefs) > 0 { + for _, AzTenantRef := range AzTenantRefs { + for _, tenant := range availableTenants { + if *tenant.ID == AzTenantRef { + AzTenants = append(AzTenants, &tenant) + goto FOUND_TENANT + } else if *tenant.DefaultDomain == AzTenantRef { + AzTenants = append(AzTenants, &tenant) + goto FOUND_TENANT + } + } + fmt.Printf("[%s] Tenant %s not accessible with active CLI credentials, removing from targetst\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), AzTenantRef) + FOUND_TENANT: + } + } +} func init() { @@ -184,7 +292,12 @@ func init() { AzCommands.PersistentFlags().StringVarP(&AzTenantID, "tenant", "t", "", "Tenant name") AzCommands.PersistentFlags().StringVarP(&AzSubscription, "subscription", "s", "", "Subscription ID or Name") AzCommands.PersistentFlags().StringVarP(&AzRGName, "resource-group", "g", "", "Resource Group name") - AzCommands.PersistentFlags().StringSliceVarP(&AzResourceIDs, "resource-id", "r", []string{}, "Resource ID (/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName})") + + AzCommands.PersistentFlags().StringSliceVar(&AzTenantRefs, "tenants", []string{}, "Tenant ID or name, repeatable") + AzCommands.PersistentFlags().StringSliceVar(&AzSubscriptionRefs, "subs", []string{}, "Subscription ID or name, repeatable") + AzCommands.PersistentFlags().StringSliceVar(&AzRGRefs, "rgs", []string{}, "Resource Group name or ID, repeatable") + AzCommands.PersistentFlags().StringSliceVar(&AzResourceRefs, "resource-id", []string{}, "Resource ID (/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}), repeatable") + AzCommands.PersistentFlags().BoolVarP(&AzWrapTable, "wrap", "w", false, "Wrap table to fit in terminal (complicates grepping)") AzCommands.PersistentFlags().BoolVarP(&AzMergedTable, "merged-table", "m", false, "Writes a single table for all subscriptions in the tenant. Default writes a table per subscription.") From 04db8765a9494e6ea7e45a7cfa2a514d8911ab63 Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Sun, 29 Oct 2023 01:12:29 -0700 Subject: [PATCH 05/40] Implemented AzureClient class that autopopulates for all subcommands, moved some code to internal package --- azure/inventory.go | 101 +++++++++++++++--------------- azure/nsg_links.go | 5 ++ cli/azure.go | 105 ++++--------------------------- internal/azure.go | 151 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 218 insertions(+), 144 deletions(-) diff --git a/azure/inventory.go b/azure/inventory.go index 1fe9789c..1e4ba01c 100644 --- a/azure/inventory.go +++ b/azure/inventory.go @@ -7,6 +7,7 @@ import ( "sort" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" + "github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions" "github.com/BishopFox/cloudfox/globals" "github.com/BishopFox/cloudfox/internal" "github.com/aws/smithy-go/ptr" @@ -14,7 +15,7 @@ import ( "github.com/kyokomi/emoji" ) -func AzInventoryCommand(AzTenantID, AzSubscriptionID, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { +func AzInventoryCommand(AzTenants []*subscriptions.TenantIDDescription, AzSubscriptions []*subscriptions.Subscription, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { o := internal.OutputClient{ Verbosity: AzVerbosity, CallingModule: globals.AZ_INVENTORY_MODULE_NAME, @@ -23,60 +24,58 @@ func AzInventoryCommand(AzTenantID, AzSubscriptionID, AzOutputDirectory, Version }, } - if AzTenantID != "" && AzSubscriptionID == "" { + if len(AzTenants) > 0 { // cloudfox azure inventory --tenant [TENANT_ID | PRIMARY_DOMAIN] - tenantInfo := populateTenant(AzTenantID) - - if AzMergedTable { - // set up table vars - var header []string - var body [][]string - - o := internal.OutputClient{ - Verbosity: AzVerbosity, - CallingModule: globals.AZ_INVENTORY_MODULE_NAME, - Table: internal.TableClient{ - Wrap: AzWrapTable, - }, - } - - fmt.Printf( - "[%s][%s] Gathering inventory for subscription %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(o.CallingModule), - fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID))) - - o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain) - o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level") - - //populate the table data - header, body, err := getInventoryInfoPerTenant(ptr.ToString(tenantInfo.ID)) - if err != nil { - return err - } - o.Table.TableFiles = append(o.Table.TableFiles, - internal.TableFile{ - Header: header, - Body: body, - Name: fmt.Sprintf(o.CallingModule)}) - - if body != nil { - o.WriteFullOutput(o.Table.TableFiles, nil) - } - } else { - - for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(tenantInfo.ID)) { - runInventoryCommandForSingleSubscription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + for _, AzTenant := range AzTenants { + tenantInfo := populateTenant(*AzTenant.ID) + + if AzMergedTable { + // set up table vars + var header []string + var body [][]string + + o := internal.OutputClient{ + Verbosity: AzVerbosity, + CallingModule: globals.AZ_INVENTORY_MODULE_NAME, + Table: internal.TableClient{ + Wrap: AzWrapTable, + }, + } + + fmt.Printf( + "[%s][%s] Gathering inventory for tenant %s\n", + color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(o.CallingModule), + fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID))) + + o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain) + o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level") + + //populate the table data + header, body, err := getInventoryInfoPerTenant(ptr.ToString(tenantInfo.ID)) + if err != nil { + return err + } + o.Table.TableFiles = append(o.Table.TableFiles, + internal.TableFile{ + Header: header, + Body: body, + Name: fmt.Sprintf(o.CallingModule)}) + + if body != nil { + o.WriteFullOutput(o.Table.TableFiles, nil) + } + } else { + for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(tenantInfo.ID)) { + runInventoryCommandForSingleSubscription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + } } - } - - } else if AzTenantID == "" && AzSubscriptionID != "" { - + } + } else { // ./cloudfox azure inventory --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME] - runInventoryCommandForSingleSubscription(AzSubscriptionID, AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + for _, AzSubscription := range AzSubscriptions { + runInventoryCommandForSingleSubscription(*AzSubscription.SubscriptionID, AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + } - } else { - // Error: please make a valid flag selection - fmt.Println("Please enter a valid input with a valid flag. Use --help for info.") } o.WriteFullOutput(o.Table.TableFiles, nil) return nil diff --git a/azure/nsg_links.go b/azure/nsg_links.go index b241dd8e..023a3d0d 100644 --- a/azure/nsg_links.go +++ b/azure/nsg_links.go @@ -15,6 +15,11 @@ import ( ) +type NSGLinksModule struct { + NSGClient *network.SecurityGroupsClient + +} + func AzNSGLinksCommand(AzTenantID string, AzSubscription string, AzResourceIDs []string, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { diff --git a/cli/azure.go b/cli/azure.go index 738aa67d..8109bf3f 100644 --- a/cli/azure.go +++ b/cli/azure.go @@ -2,9 +2,9 @@ package cli import ( "log" - "fmt" "github.com/BishopFox/cloudfox/azure" + "github.com/BishopFox/cloudfox/internal" az "github.com/Azure/go-autorest/autorest/azure" "github.com/spf13/cobra" "github.com/kyokomi/emoji" @@ -27,6 +27,8 @@ var ( AzRGRefs []string AzResourceRefs []string + + AzClient *internal.AzureClient AzTenants []*subscriptions.TenantIDDescription AzSubscriptions []*subscriptions.Subscription AzRGs []*resources.Group @@ -145,7 +147,7 @@ func runAzWhoamiCommand (cmd *cobra.Command, args []string) { } func runAzInventoryCommand (cmd *cobra.Command, args []string) { - err := azure.AzInventoryCommand(AzTenantID, AzSubscription, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + err := azure.AzInventoryCommand(AzTenants, AzSubscriptions, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) if err != nil { log.Fatal(err) } @@ -187,97 +189,14 @@ func runAzNSGLinksCommand(cmd *cobra.Command, args []string) { } func azurePreRun(cmd *cobra.Command, args []string) { - availableSubscriptions := azure.GetSubscriptions() - availableTenants := azure.GetTenants() - // resource identifiers were submitted on the CLI, running modules on them only - if len(AzResourceRefs) > 0 { - fmt.Printf("[%s] Azure resource identifiers submitted, skipping submitted tenants and subscriptions\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version))) - // remove any other resource scope filter - AzTenants = nil - AzSubscriptions = nil - AzRGs = nil - - var ( - err error - resource az.Resource - ) - // check if the submitted resource can be reached with existing credentials - // this does not guarantees that the resource will be found since only the prefix of the resource ID - // is checked against the available ones - for _, azResourceRef := range AzResourceRefs { - resource, err = az.ParseResourceID(azResourceRef) - if err != nil { - fmt.Printf("[%s] Invalid resource identifier : %s\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), azResourceRef) - continue - } - for _, subscription := range availableSubscriptions { - if resource.SubscriptionID == *subscription.SubscriptionID { - // add the resource to the final list of targets - AzResources = append(AzResources, &resource) - // also add the associated subscription since you cannot query a resource alone - AzSubscriptions = append(AzSubscriptions, &subscription) - goto FOUND_RESOURCE - } - } - fmt.Printf("[%s] No active credentials valid for resource %s, removing from target list\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), azResourceRef) - FOUND_RESOURCE: - } - return - } - // resource groups were submitted on the CLI, running modules on them only - if len(AzRGRefs) > 0 { - fmt.Printf("[%s] Azure subscriptions submitted, skipping submitted tenants\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version))) - for _, AzRGRef := range AzRGRefs { - for _, subscription := range availableSubscriptions { - for _, rg := range azure.GetResourceGroups(*subscription.SubscriptionID) { - if *rg.ID == AzRGRef { - AzRGs = append(AzRGs, &rg) - goto FOUND_RG - } else if *rg.Name == AzRGRef { - AzRGs = append(AzRGs, &rg) - goto FOUND_RG - } - } - } - fmt.Printf("[%s] Resource Group %s not accessible with active CLI credentials, removing from targetst\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), AzRGRef) - FOUND_RG: - } - } - // subscriptions were submitted on the CLI, running modules on them only - if len(AzSubscriptionRefs) > 0 { - fmt.Printf("[%s] Azure subscriptions submitted, skipping submitted tenants\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version))) - // remove any other resource scope filter - AzTenants = nil - for _, AzSubscriptionRef := range AzSubscriptionRefs { - for _, subscription := range availableSubscriptions { - if *subscription.ID == AzSubscriptionRef { - AzSubscriptions = append(AzSubscriptions, &subscription) - goto FOUND_SUB - } else if *subscription.DisplayName == AzSubscriptionRef { - AzSubscriptions = append(AzSubscriptions, &subscription) - goto FOUND_SUB - } - } - fmt.Printf("[%s] Subscription %s not accessible with active CLI credentials, removing from targetst\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), AzSubscriptionRef) - FOUND_SUB: - } - return - } - // tenants were submitted on the CLI, running modules on them only - if len(AzTenantRefs) > 0 { - for _, AzTenantRef := range AzTenantRefs { - for _, tenant := range availableTenants { - if *tenant.ID == AzTenantRef { - AzTenants = append(AzTenants, &tenant) - goto FOUND_TENANT - } else if *tenant.DefaultDomain == AzTenantRef { - AzTenants = append(AzTenants, &tenant) - goto FOUND_TENANT - } - } - fmt.Printf("[%s] Tenant %s not accessible with active CLI credentials, removing from targetst\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), AzTenantRef) - FOUND_TENANT: - } + AzClient = internal.NewAzureClient(AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs, cmd) + nTenants := len(AzClient.AzTenants) + nSubscriptions := len(AzClient.AzSubscriptions) + nRGs := len(AzClient.AzRGs) + nResources := len(AzClient.AzResources) + nTotal := nTenants + nSubscriptions + nRGs + nResources + if nTotal == 0 { + log.Fatalf("[%s] No valid target supplied, stopping\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version))) } } diff --git a/internal/azure.go b/internal/azure.go index eaed8ab4..212235cc 100644 --- a/internal/azure.go +++ b/internal/azure.go @@ -3,6 +3,7 @@ package internal import ( "fmt" "log" + "context" "github.com/Azure/azure-sdk-for-go/profiles/latest/authorization/mgmt/authorization" "github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute" @@ -15,10 +16,123 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/azure/auth" "github.com/BishopFox/cloudfox/globals" + "github.com/kyokomi/emoji" + "github.com/spf13/cobra" ) +type AzureClient struct { + AzTenants []*subscriptions.TenantIDDescription + AzSubscriptions []*subscriptions.Subscription + AzRGs []*resources.Group + AzResources []*azure.Resource +} + +func (a *AzureClient) init (AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs []string, cmd *cobra.Command){ + availableSubscriptions := GetSubscriptions() + availableTenants := GetTenants() + // resource identifiers were submitted on the CLI, running modules on them only + if len(AzResourceRefs) > 0 { + fmt.Printf("[%s] Azure resource identifiers submitted, skipping submitted tenants and subscriptions\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version))) + // remove any other resource scope filter + a.AzTenants = nil + a.AzSubscriptions = nil + a.AzRGs = nil + + var ( + err error + resource azure.Resource + ) + // check if the submitted resource can be reached with existing credentials + // this does not guarantees that the resource will be found since only the prefix of the resource ID + // is checked against the available ones + for _, azResourceRef := range AzResourceRefs { + resource, err = azure.ParseResourceID(azResourceRef) + if err != nil { + fmt.Printf("[%s] Invalid resource identifier : %s\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), azResourceRef) + continue + } + for _, subscription := range availableSubscriptions { + if resource.SubscriptionID == *subscription.SubscriptionID { + // add the resource to the final list of targets + a.AzResources = append(a.AzResources, &resource) + // also add the associated subscription since you cannot query a resource alone + a.AzSubscriptions = append(a.AzSubscriptions, &subscription) + goto FOUND_RESOURCE + } + } + fmt.Printf("[%s] No active credentials valid for resource %s, removing from target list\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), azResourceRef) + FOUND_RESOURCE: + } + } else if len(AzRGRefs) > 0 { + // resource groups were submitted on the CLI, running modules on them only + fmt.Printf("[%s] Azure subscriptions submitted, skipping submitted tenants\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version))) + + a.AzTenants = nil + a.AzSubscriptions = nil + + for _, AzRGRef := range AzRGRefs { + for _, subscription := range availableSubscriptions { + for _, rg := range GetResourceGroups(*subscription.SubscriptionID) { + if *rg.ID == AzRGRef { + a.AzRGs = append(a.AzRGs, &rg) + a.AzSubscriptions = append(a.AzSubscriptions, &subscription) + goto FOUND_RG + } else if *rg.Name == AzRGRef { + a.AzRGs = append(a.AzRGs, &rg) + a.AzSubscriptions = append(a.AzSubscriptions, &subscription) + goto FOUND_RG + } + } + } + fmt.Printf("[%s] Resource Group %s not accessible with active CLI credentials, removing from targetst\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), AzRGRef) + FOUND_RG: + } + } else if len(AzSubscriptionRefs) > 0 { + // subscriptions were submitted on the CLI, running modules on them only + fmt.Printf("[%s] Azure subscriptions submitted, skipping submitted tenants\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version))) + // remove any other resource scope filter + a.AzTenants = nil + for _, AzSubscriptionRef := range AzSubscriptionRefs { + for _, subscription := range availableSubscriptions { + if *subscription.SubscriptionID == AzSubscriptionRef { + a.AzSubscriptions = append(a.AzSubscriptions, &subscription) + goto FOUND_SUB + } else if *subscription.DisplayName == AzSubscriptionRef { + a.AzSubscriptions = append(a.AzSubscriptions, &subscription) + goto FOUND_SUB + } + } + fmt.Printf("[%s] Subscription %s not accessible with active CLI credentials, removing from targetst\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), AzSubscriptionRef) + FOUND_SUB: + } + } else if len(AzTenantRefs) > 0 { + // tenants were submitted on the CLI, running modules on them only + for _, AzTenantRef := range AzTenantRefs { + for _, tenant := range availableTenants { + if *tenant.ID == AzTenantRef { + a.AzTenants = append(a.AzTenants, &tenant) + goto FOUND_TENANT + } else if *tenant.DefaultDomain == AzTenantRef { + a.AzTenants = append(a.AzTenants, &tenant) + goto FOUND_TENANT + } + } + fmt.Printf("[%s] Tenant %s not accessible with active CLI credentials, removing from targetst\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), AzTenantRef) + FOUND_TENANT: + } + } +} + +func NewAzureClient(AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs []string, cmd *cobra.Command) *AzureClient { + client := new(AzureClient) + client.init(AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs, cmd) + return client +} + + func getAuthorizer(endpoint string) (autorest.Authorizer, error) { auth, err := auth.NewAuthorizerFromCLIWithResource(endpoint) if err != nil { @@ -210,3 +324,40 @@ func GetIPAddressesFromInterface(iface *network.Interface) []string{ } return ipAddresses } + +func GetSubscriptions() []subscriptions.Subscription { + var results []subscriptions.Subscription + subsClient := GetSubscriptionsClient() + for page, err := subsClient.List(context.TODO()); page.NotDone(); err = page.Next() { + if err != nil { + log.Fatal("could not get subscriptions for active session") + } + results = append(results, page.Values()...) + } + return results +} + +func GetTenants() []subscriptions.TenantIDDescription { + tenantsClient := GetTenantsClient() + var results []subscriptions.TenantIDDescription + for page, err := tenantsClient.List(context.TODO()); page.NotDone(); err = page.Next() { + if err != nil { + log.Fatal("could not get tenants for active session") + } + results = append(results, page.Values()...) + } + return results +} + +func GetResourceGroups(subscriptionID string) []resources.Group { + var results []resources.Group + rgClient := GetResourceGroupsClient(subscriptionID) + + for page, err := rgClient.List(context.TODO(), "", nil); page.NotDone(); err = page.Next() { + if err != nil { + log.Fatalf("error reading resource groups for subscription %s", subscriptionID) + } + results = append(results, page.Values()...) + } + return results +} From 71a60a6e6798cb1248767a2818ed30b34bafc308 Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Sun, 29 Oct 2023 01:16:42 -0700 Subject: [PATCH 06/40] Migrated Azure Inventory command to new format --- azure/inventory.go | 9 ++++----- cli/azure.go | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/azure/inventory.go b/azure/inventory.go index 1e4ba01c..3c32cdf8 100644 --- a/azure/inventory.go +++ b/azure/inventory.go @@ -7,7 +7,6 @@ import ( "sort" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" - "github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions" "github.com/BishopFox/cloudfox/globals" "github.com/BishopFox/cloudfox/internal" "github.com/aws/smithy-go/ptr" @@ -15,7 +14,7 @@ import ( "github.com/kyokomi/emoji" ) -func AzInventoryCommand(AzTenants []*subscriptions.TenantIDDescription, AzSubscriptions []*subscriptions.Subscription, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { +func AzInventoryCommand(AzClient *internal.AzureClient, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { o := internal.OutputClient{ Verbosity: AzVerbosity, CallingModule: globals.AZ_INVENTORY_MODULE_NAME, @@ -24,9 +23,9 @@ func AzInventoryCommand(AzTenants []*subscriptions.TenantIDDescription, AzSubscr }, } - if len(AzTenants) > 0 { + if len(AzClient.AzTenants) > 0 { // cloudfox azure inventory --tenant [TENANT_ID | PRIMARY_DOMAIN] - for _, AzTenant := range AzTenants { + for _, AzTenant := range AzClient.AzTenants { tenantInfo := populateTenant(*AzTenant.ID) if AzMergedTable { @@ -72,7 +71,7 @@ func AzInventoryCommand(AzTenants []*subscriptions.TenantIDDescription, AzSubscr } } else { // ./cloudfox azure inventory --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME] - for _, AzSubscription := range AzSubscriptions { + for _, AzSubscription := range AzClient.AzSubscriptions { runInventoryCommandForSingleSubscription(*AzSubscription.SubscriptionID, AzOutputDirectory, AzVerbosity, AzWrapTable, Version) } diff --git a/cli/azure.go b/cli/azure.go index 8109bf3f..ac79f912 100644 --- a/cli/azure.go +++ b/cli/azure.go @@ -147,7 +147,7 @@ func runAzWhoamiCommand (cmd *cobra.Command, args []string) { } func runAzInventoryCommand (cmd *cobra.Command, args []string) { - err := azure.AzInventoryCommand(AzTenants, AzSubscriptions, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + err := azure.AzInventoryCommand(AzClient, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) if err != nil { log.Fatal(err) } From c2100cd927e4af521c53cc7f656f01c60f649add Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Sun, 29 Oct 2023 01:40:04 -0700 Subject: [PATCH 07/40] Migrated Azure VMs command to new format and fixed tenant reference bug in Inventory --- azure/inventory.go | 2 +- azure/vms.go | 127 ++++++++++++++++++++++++--------------------- 2 files changed, 68 insertions(+), 61 deletions(-) diff --git a/azure/inventory.go b/azure/inventory.go index 3c32cdf8..198f076f 100644 --- a/azure/inventory.go +++ b/azure/inventory.go @@ -26,7 +26,7 @@ func AzInventoryCommand(AzClient *internal.AzureClient, AzOutputDirectory, Versi if len(AzClient.AzTenants) > 0 { // cloudfox azure inventory --tenant [TENANT_ID | PRIMARY_DOMAIN] for _, AzTenant := range AzClient.AzTenants { - tenantInfo := populateTenant(*AzTenant.ID) + tenantInfo := populateTenant(*AzTenant.TenantID) if AzMergedTable { // set up table vars diff --git a/azure/vms.go b/azure/vms.go index c0b320a0..8940fec5 100644 --- a/azure/vms.go +++ b/azure/vms.go @@ -20,73 +20,80 @@ import ( "github.com/kyokomi/emoji" ) -func AzVMsCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { - - if AzTenantID != "" && AzSubscription == "" { - // cloudfox azure vms --tenant [TENANT_ID | PRIMARY_DOMAIN] - tenantInfo := populateTenant(AzTenantID) - - if AzMergedTable { - // set up table vars - var header []string - var body [][]string - var userData string - - o := internal.OutputClient{ - Verbosity: AzVerbosity, - CallingModule: globals.AZ_VMS_MODULE_NAME, - Table: internal.TableClient{ - Wrap: AzWrapTable, - }, - } - fmt.Printf("[%s][%s] Enumerating VMs for tenant %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_VMS_MODULE_NAME), - fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID))) - - o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain) - o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level") - - // populate the table data - header, body, userData = getVMsPerTenantID(ptr.ToString(tenantInfo.ID)) - - o.Table.TableFiles = append(o.Table.TableFiles, - internal.TableFile{ - Header: header, - Body: body, - Name: fmt.Sprintf(globals.AZ_VMS_MODULE_NAME)}) - - if body != nil { - if userData != "" { - o.Loot.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "loot") - o.Loot.LootFiles = append(o.Loot.LootFiles, - internal.LootFile{ - Contents: userData, - Name: "virtualmachines-user-data"}) - o.WriteFullOutput(o.Table.TableFiles, o.Loot.LootFiles) - fmt.Println() - } else { +func AzVMsCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { - o.WriteFullOutput(o.Table.TableFiles, nil) - fmt.Println() - } + o := internal.OutputClient{ + Verbosity: AzVerbosity, + CallingModule: globals.AZ_VMS_MODULE_NAME, + Table: internal.TableClient{ + Wrap: AzWrapTable, + }, + } - } - } else { + if len(AzClient.AzTenants) > 0 { + // cloudfox azure inventory --tenant [TENANT_ID | PRIMARY_DOMAIN] + for _, AzTenant := range AzClient.AzTenants { + tenantInfo := populateTenant(*AzTenant.TenantID) + + if AzMergedTable { + // set up table vars + var header []string + var body [][]string + var userData string + + o := internal.OutputClient{ + Verbosity: AzVerbosity, + CallingModule: globals.AZ_INVENTORY_MODULE_NAME, + Table: internal.TableClient{ + Wrap: AzWrapTable, + }, + } - for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(tenantInfo.ID)) { - runVMsCommandForSingleSubscription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + fmt.Printf( + "[%s][%s] Enumerating VMs for tenant %s\n", + color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(o.CallingModule), + fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID))) + + o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain) + o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level") + + //populate the table data + header, body, userData = getVMsPerTenantID(ptr.ToString(tenantInfo.ID)) + + o.Table.TableFiles = append(o.Table.TableFiles, + internal.TableFile{ + Header: header, + Body: body, + Name: fmt.Sprintf(globals.AZ_VMS_MODULE_NAME)}) + + if body != nil { + if userData != "" { + o.Loot.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "loot") + o.Loot.LootFiles = append(o.Loot.LootFiles, + internal.LootFile{ + Contents: userData, + Name: "virtualmachines-user-data"}) + o.WriteFullOutput(o.Table.TableFiles, o.Loot.LootFiles) + fmt.Println() + } else { + o.WriteFullOutput(o.Table.TableFiles, nil) + fmt.Println() + } + } + } else { + for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(tenantInfo.ID)) { + runVMsCommandForSingleSubscription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + } } + } + } else { + // ./cloudfox azure inventory --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME] + for _, AzSubscription := range AzClient.AzSubscriptions { + runVMsCommandForSingleSubscription(*AzSubscription.SubscriptionID, AzOutputDirectory, AzVerbosity, AzWrapTable, Version) } - } else if AzTenantID == "" && AzSubscription != "" { - // cloudfox azure vms --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME] - runVMsCommandForSingleSubscription(AzSubscription, AzOutputDirectory, AzVerbosity, AzWrapTable, Version) - - } else { - // Error: please make a valid flag selection - fmt.Println("Please enter a valid input with a valid flag. Use --help for info.") } - + o.WriteFullOutput(o.Table.TableFiles, nil) return nil } From b7cca44509dd047f1417938f7c166dadb42f31a2 Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Sun, 29 Oct 2023 01:50:13 -0700 Subject: [PATCH 08/40] Migrated Azure RBAC subcommand to new format --- azure/rbac.go | 79 +++++++++++++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/azure/rbac.go b/azure/rbac.go index f4eb80f0..cf4c738b 100644 --- a/azure/rbac.go +++ b/azure/rbac.go @@ -19,7 +19,7 @@ import ( "github.com/kyokomi/emoji" ) -func AzRBACCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { +func AzRBACCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { // setup logging client o := internal.OutputClient{ Verbosity: AzVerbosity, @@ -34,53 +34,50 @@ func AzRBACCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory var header []string var body [][]string - var AzSubscriptionInfo SubsriptionInfo - if AzTenantID != "" && AzSubscription == "" { + if len(AzClient.AzTenants) > 0 { // cloudfox azure rbac --tenant [TENANT_ID | PRIMARY_DOMAIN] + for _, AzTenant := range AzClient.AzTenants { - var err error - tenantInfo := populateTenant(AzTenantID) - if err != nil { - return err - } - o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain) - o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level") + var err error + if err != nil { + return err + } + o.PrefixIdentifier = *AzTenant.DefaultDomain + o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, *AzTenant.DefaultDomain, "1-tenant-level") - fmt.Printf("[%s][%s] Enumerating RBAC permissions for tenant %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME), - fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID))) + fmt.Printf("[%s][%s] Enumerating RBAC permissions for tenant %s\n", + color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME), + fmt.Sprintf("%s (%s)", *AzTenant.DefaultDomain, *AzTenant.TenantID)) - header, body, err = getRBACperTenant(ptr.ToString(tenantInfo.ID), c) - if err != nil { - return err - } - o.Table.TableFiles = append(o.Table.TableFiles, - internal.TableFile{ - Header: header, - Body: body, - Name: fmt.Sprintf(globals.AZ_RBAC_MODULE_NAME)}) + header, body, err = getRBACperTenant(ptr.ToString(AzTenant.ID), c) + if err != nil { + return err + } + o.Table.TableFiles = append(o.Table.TableFiles, + internal.TableFile{ + Header: header, + Body: body, + Name: fmt.Sprintf(globals.AZ_RBAC_MODULE_NAME)}) - } else if AzTenantID == "" && AzSubscription != "" { + } + } else{ // cloudfox azure rbac --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME] - tenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription)) - tenantInfo := populateTenant(tenantID) - AzSubscriptionInfo = PopulateSubsriptionType(AzSubscription) - o.PrefixIdentifier = AzSubscriptionInfo.Name - o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name) - - fmt.Printf("[%s][%s] Enumerating RBAC permissions for subscription %s\n", color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME), - fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID)) - header, body = getRBACperSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID, c) - o.Table.TableFiles = append(o.Table.TableFiles, - internal.TableFile{ - Header: header, - Body: body, - Name: fmt.Sprintf(globals.AZ_RBAC_MODULE_NAME)}) - - } else { - // Error: please make a valid flag selection - fmt.Println("Please enter a valid input with a valid flag. Use --help for info.") + for _, AzSubscription := range AzClient.AzSubscriptions { + tenantInfo := populateTenant(*AzSubscription.TenantID) + o.PrefixIdentifier = ptr.ToString(AzSubscription.DisplayName) + o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), *AzSubscription.DisplayName) + + fmt.Printf("[%s][%s] Enumerating RBAC permissions for subscription %s\n", color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME), + fmt.Sprintf("%s (%s)", ptr.ToString(AzSubscription.DisplayName), *AzSubscription.SubscriptionID)) + header, body = getRBACperSubscription(ptr.ToString(tenantInfo.ID), *AzSubscription.SubscriptionID, c) + o.Table.TableFiles = append(o.Table.TableFiles, + internal.TableFile{ + Header: header, + Body: body, + Name: fmt.Sprintf(globals.AZ_RBAC_MODULE_NAME)}) + } + } if body != nil { From 325b6ce892cf20257c1270c164c669c574bbe53d Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Sun, 29 Oct 2023 02:00:12 -0700 Subject: [PATCH 09/40] Migrated Azure NSG commands to new format --- azure/nsg_links.go | 88 +++++++++++++++++------------------- azure/nsg_rules.go | 109 ++++++++++++++++++++++----------------------- cli/azure.go | 8 ++-- 3 files changed, 98 insertions(+), 107 deletions(-) diff --git a/azure/nsg_links.go b/azure/nsg_links.go index 023a3d0d..4433d16d 100644 --- a/azure/nsg_links.go +++ b/azure/nsg_links.go @@ -20,66 +20,60 @@ type NSGLinksModule struct { } -func AzNSGLinksCommand(AzTenantID string, AzSubscription string, AzResourceIDs []string, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { +func AzNSGLinksCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { - if AzTenantID != "" && AzSubscription == "" { - // cloudfox azure nsg-links --tenant [TENANT_ID | PRIMARY_DOMAIN] - tenantInfo := populateTenant(AzTenantID) + if len(AzClient.AzTenants) > 0 { + for _, AzTenant := range AzClient.AzTenants { - if AzMergedTable { + if AzMergedTable { - // set up table vars - var header []string - var body [][]string - // setup logging client - o := internal.OutputClient{ - Verbosity: AzVerbosity, - CallingModule: globals.AZ_NSG_LINKS_MODULE_NAME, - Table: internal.TableClient{ - Wrap: AzWrapTable, - }, - } - - var err error + // set up table vars + var header []string + var body [][]string + // setup logging client + o := internal.OutputClient{ + Verbosity: AzVerbosity, + CallingModule: globals.AZ_NSG_LINKS_MODULE_NAME, + Table: internal.TableClient{ + Wrap: AzWrapTable, + }, + } - fmt.Printf("[%s][%s] Enumerating Network Security Group links for tenant %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_NSG_LINKS_MODULE_NAME), - fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID))) + var err error - o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain) - o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level") + fmt.Printf("[%s][%s] Enumerating Network Security Group links for tenant %s\n", + color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_NSG_LINKS_MODULE_NAME), + fmt.Sprintf("%s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))) - header, body, err = getNSGInfoPerTenant(ptr.ToString(tenantInfo.ID)) + o.PrefixIdentifier = ptr.ToString(AzTenant.DefaultDomain) + o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level") - if err != nil { - return err - } - o.Table.TableFiles = append(o.Table.TableFiles, - internal.TableFile{ - Header: header, - Body: body, - Name: fmt.Sprintf(globals.AZ_NSG_LINKS_MODULE_NAME)}) - - if body != nil { - o.WriteFullOutput(o.Table.TableFiles, nil) - } - } else { + header, body, err = getNSGInfoPerTenant(ptr.ToString(AzTenant.TenantID)) - for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(tenantInfo.ID)) { - runNSGCommandForSingleSubcription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + if err != nil { + return err + } + o.Table.TableFiles = append(o.Table.TableFiles, + internal.TableFile{ + Header: header, + Body: body, + Name: fmt.Sprintf(globals.AZ_NSG_LINKS_MODULE_NAME)}) + + if body != nil { + o.WriteFullOutput(o.Table.TableFiles, nil) + } + } else { + for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) { + runNSGCommandForSingleSubcription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + } } } - - } else if AzTenantID == "" && AzSubscription != "" { - //cloudfox azure nsg-links --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME] - runNSGCommandForSingleSubcription(AzSubscription, AzOutputDirectory, AzVerbosity, AzWrapTable, Version) - } else { - // Error: please make a valid flag selection - fmt.Println("Please enter a valid input with a valid flag. Use --help for info.") + for _, AzSubscription := range AzClient.AzSubscriptions { + runNSGCommandForSingleSubcription(*AzSubscription.SubscriptionID, AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + } } - return nil } diff --git a/azure/nsg_rules.go b/azure/nsg_rules.go index e81d223b..9a2ff078 100644 --- a/azure/nsg_rules.go +++ b/azure/nsg_rules.go @@ -11,65 +11,62 @@ import ( ) -func AzNSGRulesCommand(AzTenantID string, AzSubscription string, AzResourceIDs []string, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { - - - if AzTenantID != "" && AzSubscription == "" { - // cloudfox azure nsg-rules --tenant [TENANT_ID | PRIMARY_DOMAIN] - tenantInfo := populateTenant(AzTenantID) - - if AzMergedTable { - - // set up table vars - var header []string - var body [][]string - // setup logging client - o := internal.OutputClient{ - Verbosity: AzVerbosity, - CallingModule: globals.AZ_NSG_RULES_MODULE_NAME, - Table: internal.TableClient{ - Wrap: AzWrapTable, - }, - } - - var err error - - fmt.Printf("[%s][%s] Enumerating Network Security Group rules for tenant %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_NSG_RULES_MODULE_NAME), - fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID))) - - o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain) - o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level") - - header, body, err = getNSGInfoPerTenant(ptr.ToString(tenantInfo.ID)) - - if err != nil { - return err - } - o.Table.TableFiles = append(o.Table.TableFiles, - internal.TableFile{ - Header: header, - Body: body, - Name: fmt.Sprintf(globals.AZ_NSG_RULES_MODULE_NAME)}) - - if body != nil { - o.WriteFullOutput(o.Table.TableFiles, nil) - } - } else { - - for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(tenantInfo.ID)) { - //runNSGCommandForSingleSubcription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version) - fmt.Println(s) +func AzNSGRulesCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { + + + if len(AzClient.AzTenants) > 0 { + for _, AzTenant := range AzClient.AzTenants { + // cloudfox azure nsg-rules --tenant [TENANT_ID | PRIMARY_DOMAIN] + + if AzMergedTable { + + // set up table vars + var header []string + var body [][]string + // setup logging client + o := internal.OutputClient{ + Verbosity: AzVerbosity, + CallingModule: globals.AZ_NSG_RULES_MODULE_NAME, + Table: internal.TableClient{ + Wrap: AzWrapTable, + }, + } + + var err error + + fmt.Printf("[%s][%s] Enumerating Network Security Group rules for tenant %s\n", + color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_NSG_RULES_MODULE_NAME), + fmt.Sprintf("%s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))) + + o.PrefixIdentifier = ptr.ToString(AzTenant.DefaultDomain) + o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level") + + header, body, err = getNSGInfoPerTenant(ptr.ToString(AzTenant.TenantID)) + + if err != nil { + return err + } + o.Table.TableFiles = append(o.Table.TableFiles, + internal.TableFile{ + Header: header, + Body: body, + Name: fmt.Sprintf(globals.AZ_NSG_RULES_MODULE_NAME)}) + + if body != nil { + o.WriteFullOutput(o.Table.TableFiles, nil) + } + } else { + + for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) { + //runNSGCommandForSingleSubcription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + fmt.Println(s) + } } } - - } else if AzTenantID == "" && AzSubscription != "" { - //cloudfox azure nsg-rules --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME] - runNSGCommandForSingleSubcription(AzSubscription, AzOutputDirectory, AzVerbosity, AzWrapTable, Version) - } else { - // Error: please make a valid flag selection - fmt.Println("Please enter a valid input with a valid flag. Use --help for info.") + for _, AzSubscription := range AzClient.AzSubscriptions { + runNSGCommandForSingleSubcription(*AzSubscription.SubscriptionID, AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + } } return nil diff --git a/cli/azure.go b/cli/azure.go index ac79f912..f92acffd 100644 --- a/cli/azure.go +++ b/cli/azure.go @@ -154,14 +154,14 @@ func runAzInventoryCommand (cmd *cobra.Command, args []string) { } func runAzRBACCommand (cmd *cobra.Command, args []string) { - err := azure.AzRBACCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + err := azure.AzRBACCommand(AzClient, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) if err != nil { log.Fatal(err) } } func runAzVMsCommand (cmd *cobra.Command, args []string) { - err := azure.AzVMsCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + err := azure.AzVMsCommand(AzClient, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) if err != nil { log.Fatal(err) } @@ -175,14 +175,14 @@ func runAzStorageCommand (cmd *cobra.Command, args []string) { } func runAzNSGRulesCommand(cmd *cobra.Command, args []string) { - err := azure.AzNSGRulesCommand(AzTenantID, AzSubscription, AzResourceRefs, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + err := azure.AzNSGRulesCommand(AzClient, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) if err != nil { log.Fatal(err) } } func runAzNSGLinksCommand(cmd *cobra.Command, args []string) { - err := azure.AzNSGLinksCommand(AzTenantID, AzSubscription, AzResourceRefs, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + err := azure.AzNSGLinksCommand(AzClient, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) if err != nil { log.Fatal(err) } From 14132b39584f10d9d4e4a92bb824aaad35c7a60c Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Sun, 29 Oct 2023 02:04:47 -0700 Subject: [PATCH 10/40] Migrated Azure storage command to new format --- azure/storage.go | 97 +++++++++++++++++++++++------------------------- cli/azure.go | 2 +- 2 files changed, 47 insertions(+), 52 deletions(-) diff --git a/azure/storage.go b/azure/storage.go index 3febe71c..4d943bcc 100644 --- a/azure/storage.go +++ b/azure/storage.go @@ -22,74 +22,69 @@ import ( // Color functions var cyan = color.New(color.FgCyan).SprintFunc() -func AzStorageCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { +func AzStorageCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { var publicBlobURLs []string - if AzTenantID != "" && AzSubscription == "" { + if len(AzClient.AzTenants) > 0 { // cloudfox azure storage --tenant [TENANT_ID | PRIMARY_DOMAIN] - tenantInfo := populateTenant(AzTenantID) - - if AzMergedTable { - - // set up table vars - var header []string - var body [][]string - // setup logging client - o := internal.OutputClient{ - Verbosity: AzVerbosity, - CallingModule: globals.AZ_STORAGE_MODULE_NAME, - Table: internal.TableClient{ - Wrap: AzWrapTable, - }, - } + for _, AzTenant := range AzClient.AzTenants { + + if AzMergedTable { + + // set up table vars + var header []string + var body [][]string + // setup logging client + o := internal.OutputClient{ + Verbosity: AzVerbosity, + CallingModule: globals.AZ_STORAGE_MODULE_NAME, + Table: internal.TableClient{ + Wrap: AzWrapTable, + }, + } - var err error + var err error - fmt.Printf("[%s][%s] Enumerating storage accounts for tenant %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME), - fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID))) + fmt.Printf("[%s][%s] Enumerating storage accounts for tenant %s\n", + color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME), + fmt.Sprintf("%s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))) - o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain) - o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level") + o.PrefixIdentifier = ptr.ToString(AzTenant.DefaultDomain) + o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level") - header, body, publicBlobURLs, err = getStorageInfoPerTenant(ptr.ToString(tenantInfo.ID)) + header, body, publicBlobURLs, err = getStorageInfoPerTenant(ptr.ToString(AzTenant.TenantID)) - if err != nil { - return err - } - o.Table.TableFiles = append(o.Table.TableFiles, - internal.TableFile{ - Header: header, - Body: body, - Name: fmt.Sprintf(globals.AZ_STORAGE_MODULE_NAME)}) - - if body != nil { - o.WriteFullOutput(o.Table.TableFiles, nil) - } - if publicBlobURLs != nil { - err := writeBlobURLslootFile(globals.AZ_STORAGE_MODULE_NAME, o.PrefixIdentifier, o.Table.DirectoryName, publicBlobURLs) if err != nil { return err } - } - - } else { + o.Table.TableFiles = append(o.Table.TableFiles, + internal.TableFile{ + Header: header, + Body: body, + Name: fmt.Sprintf(globals.AZ_STORAGE_MODULE_NAME)}) + + if body != nil { + o.WriteFullOutput(o.Table.TableFiles, nil) + } + if publicBlobURLs != nil { + err := writeBlobURLslootFile(globals.AZ_STORAGE_MODULE_NAME, o.PrefixIdentifier, o.Table.DirectoryName, publicBlobURLs) + if err != nil { + return err + } + } - for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(tenantInfo.ID)) { - runStorageCommandForSingleSubcription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + } else { + for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) { + runStorageCommandForSingleSubcription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + } } } - - } else if AzTenantID == "" && AzSubscription != "" { - //cloudfox azure storage --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME] - runStorageCommandForSingleSubcription(AzSubscription, AzOutputDirectory, AzVerbosity, AzWrapTable, Version) - } else { - // Error: please make a valid flag selection - fmt.Println("Please enter a valid input with a valid flag. Use --help for info.") + for _, AzSubscription := range AzClient.AzSubscriptions { + runStorageCommandForSingleSubcription(*AzSubscription.SubscriptionID, AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + } } - return nil } diff --git a/cli/azure.go b/cli/azure.go index f92acffd..6b0a8542 100644 --- a/cli/azure.go +++ b/cli/azure.go @@ -168,7 +168,7 @@ func runAzVMsCommand (cmd *cobra.Command, args []string) { } func runAzStorageCommand (cmd *cobra.Command, args []string) { - err := azure.AzStorageCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + err := azure.AzStorageCommand(AzClient, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) if err != nil { log.Fatal(err) } From 991decf2d900675f10688391bcc67596a45f26fb Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Sun, 29 Oct 2023 02:08:20 -0700 Subject: [PATCH 11/40] Code cleanup in Azure modules --- azure/inventory.go | 11 +++++------ azure/vms.go | 13 ++++++------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/azure/inventory.go b/azure/inventory.go index 198f076f..1bf23011 100644 --- a/azure/inventory.go +++ b/azure/inventory.go @@ -26,7 +26,6 @@ func AzInventoryCommand(AzClient *internal.AzureClient, AzOutputDirectory, Versi if len(AzClient.AzTenants) > 0 { // cloudfox azure inventory --tenant [TENANT_ID | PRIMARY_DOMAIN] for _, AzTenant := range AzClient.AzTenants { - tenantInfo := populateTenant(*AzTenant.TenantID) if AzMergedTable { // set up table vars @@ -44,13 +43,13 @@ func AzInventoryCommand(AzClient *internal.AzureClient, AzOutputDirectory, Versi fmt.Printf( "[%s][%s] Gathering inventory for tenant %s\n", color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(o.CallingModule), - fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID))) + fmt.Sprintf("%s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))) - o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain) - o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level") + o.PrefixIdentifier = ptr.ToString(AzTenant.DefaultDomain) + o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level") //populate the table data - header, body, err := getInventoryInfoPerTenant(ptr.ToString(tenantInfo.ID)) + header, body, err := getInventoryInfoPerTenant(ptr.ToString(AzTenant.TenantID)) if err != nil { return err } @@ -64,7 +63,7 @@ func AzInventoryCommand(AzClient *internal.AzureClient, AzOutputDirectory, Versi o.WriteFullOutput(o.Table.TableFiles, nil) } } else { - for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(tenantInfo.ID)) { + for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.ID)) { runInventoryCommandForSingleSubscription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version) } } diff --git a/azure/vms.go b/azure/vms.go index 8940fec5..a352db50 100644 --- a/azure/vms.go +++ b/azure/vms.go @@ -33,7 +33,6 @@ func AzVMsCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDirect if len(AzClient.AzTenants) > 0 { // cloudfox azure inventory --tenant [TENANT_ID | PRIMARY_DOMAIN] for _, AzTenant := range AzClient.AzTenants { - tenantInfo := populateTenant(*AzTenant.TenantID) if AzMergedTable { // set up table vars @@ -52,13 +51,13 @@ func AzVMsCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDirect fmt.Printf( "[%s][%s] Enumerating VMs for tenant %s\n", color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(o.CallingModule), - fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID))) + fmt.Sprintf("%s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))) - o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain) - o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level") + o.PrefixIdentifier = ptr.ToString(AzTenant.DefaultDomain) + o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level") //populate the table data - header, body, userData = getVMsPerTenantID(ptr.ToString(tenantInfo.ID)) + header, body, userData = getVMsPerTenantID(ptr.ToString(AzTenant.TenantID)) o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{ @@ -68,7 +67,7 @@ func AzVMsCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDirect if body != nil { if userData != "" { - o.Loot.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "loot") + o.Loot.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "loot") o.Loot.LootFiles = append(o.Loot.LootFiles, internal.LootFile{ Contents: userData, @@ -81,7 +80,7 @@ func AzVMsCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDirect } } } else { - for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(tenantInfo.ID)) { + for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.ID)) { runVMsCommandForSingleSubscription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version) } } From 7e209883fb01abaf22f8331cf772786cfe0d0cd3 Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Mon, 30 Oct 2023 12:25:18 -0700 Subject: [PATCH 12/40] Fix wrong reference to resource ID in Azure RBAC and VMs modules --- azure/rbac.go | 2 +- azure/vms.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/azure/rbac.go b/azure/rbac.go index cf4c738b..d4cd40b7 100644 --- a/azure/rbac.go +++ b/azure/rbac.go @@ -50,7 +50,7 @@ func AzRBACCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDirec color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME), fmt.Sprintf("%s (%s)", *AzTenant.DefaultDomain, *AzTenant.TenantID)) - header, body, err = getRBACperTenant(ptr.ToString(AzTenant.ID), c) + header, body, err = getRBACperTenant(ptr.ToString(AzTenant.TenantID), c) if err != nil { return err } diff --git a/azure/vms.go b/azure/vms.go index a352db50..e37b86d0 100644 --- a/azure/vms.go +++ b/azure/vms.go @@ -80,7 +80,7 @@ func AzVMsCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDirect } } } else { - for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.ID)) { + for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) { runVMsCommandForSingleSubscription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version) } } From b38bf98072ce4f1706a8ecff10d9a57293910d85 Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Mon, 30 Oct 2023 12:49:21 -0700 Subject: [PATCH 13/40] Migrated the Azure Inventory command to a fully modular structure, implemented RG filtering --- azure/inventory.go | 70 ++++++++++++++++++++++++++++------------------ cli/azure.go | 7 +++-- internal/azure.go | 17 ++++++++++- 3 files changed, 64 insertions(+), 30 deletions(-) diff --git a/azure/inventory.go b/azure/inventory.go index 1bf23011..52190598 100644 --- a/azure/inventory.go +++ b/azure/inventory.go @@ -9,47 +9,53 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" "github.com/BishopFox/cloudfox/globals" "github.com/BishopFox/cloudfox/internal" + "github.com/Azure/go-autorest/autorest/azure" "github.com/aws/smithy-go/ptr" "github.com/fatih/color" "github.com/kyokomi/emoji" ) -func AzInventoryCommand(AzClient *internal.AzureClient, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { +type AzInventoryModule struct { + AzClient *internal.AzureClient +} + + +func (m *AzInventoryModule) AzInventoryCommand() error { o := internal.OutputClient{ - Verbosity: AzVerbosity, + Verbosity: m.AzClient.AzVerbosity, CallingModule: globals.AZ_INVENTORY_MODULE_NAME, Table: internal.TableClient{ - Wrap: AzWrapTable, + Wrap: m.AzClient.AzWrapTable, }, } - if len(AzClient.AzTenants) > 0 { + if len(m.AzClient.AzTenants) > 0 { // cloudfox azure inventory --tenant [TENANT_ID | PRIMARY_DOMAIN] - for _, AzTenant := range AzClient.AzTenants { + for _, AzTenant := range m.AzClient.AzTenants { - if AzMergedTable { + if m.AzClient.AzMergedTable { // set up table vars var header []string var body [][]string o := internal.OutputClient{ - Verbosity: AzVerbosity, + Verbosity: m.AzClient.AzVerbosity, CallingModule: globals.AZ_INVENTORY_MODULE_NAME, Table: internal.TableClient{ - Wrap: AzWrapTable, + Wrap: m.AzClient.AzWrapTable, }, } fmt.Printf( "[%s][%s] Gathering inventory for tenant %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(o.CallingModule), + color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), color.CyanString(o.CallingModule), fmt.Sprintf("%s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))) o.PrefixIdentifier = ptr.ToString(AzTenant.DefaultDomain) - o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level") + o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level") //populate the table data - header, body, err := getInventoryInfoPerTenant(ptr.ToString(AzTenant.TenantID)) + header, body, err := m.getInventoryInfoPerTenant(ptr.ToString(AzTenant.TenantID)) if err != nil { return err } @@ -63,15 +69,15 @@ func AzInventoryCommand(AzClient *internal.AzureClient, AzOutputDirectory, Versi o.WriteFullOutput(o.Table.TableFiles, nil) } } else { - for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.ID)) { - runInventoryCommandForSingleSubscription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) { + m.runInventoryCommandForSingleSubscription(ptr.ToString(s.SubscriptionID)) } } } } else { // ./cloudfox azure inventory --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME] - for _, AzSubscription := range AzClient.AzSubscriptions { - runInventoryCommandForSingleSubscription(*AzSubscription.SubscriptionID, AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + for _, AzSubscription := range m.AzClient.AzSubscriptions { + m.runInventoryCommandForSingleSubscription(*AzSubscription.SubscriptionID) } } @@ -79,16 +85,16 @@ func AzInventoryCommand(AzClient *internal.AzureClient, AzOutputDirectory, Versi return nil } -func runInventoryCommandForSingleSubscription(AzSubscription string, AzOutputDirectory string, AzVerbosity int, AzWrapTable bool, Version string) error { +func (m *AzInventoryModule) runInventoryCommandForSingleSubscription(AzSubscription string) error { // set up table vars var header []string var body [][]string var err error o := internal.OutputClient{ - Verbosity: AzVerbosity, + Verbosity: m.AzClient.AzVerbosity, CallingModule: globals.AZ_INVENTORY_MODULE_NAME, Table: internal.TableClient{ - Wrap: AzWrapTable, + Wrap: m.AzClient.AzWrapTable, }, } var AzSubscriptionInfo SubsriptionInfo @@ -96,15 +102,15 @@ func runInventoryCommandForSingleSubscription(AzSubscription string, AzOutputDir tenantInfo := populateTenant(tenantID) AzSubscriptionInfo = PopulateSubsriptionType(AzSubscription) o.PrefixIdentifier = AzSubscriptionInfo.Name - o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name) + o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name) fmt.Printf( "[%s][%s] Gathering inventory for subscription %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(o.CallingModule), + color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), color.CyanString(o.CallingModule), fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID)) // populate the table data - header, body, err = getInventoryInfoPerSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID) + header, body, err = m.getInventoryInfoPerSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID) if err != nil { return err } @@ -123,8 +129,8 @@ func runInventoryCommandForSingleSubscription(AzSubscription string, AzOutputDir return nil } -func getInventoryInfoPerSubscription(tenantID, subscriptionID string) ([]string, [][]string, error) { - resources, err := getResources(tenantID, subscriptionID) +func (m *AzInventoryModule) getInventoryInfoPerSubscription(tenantID, subscriptionID string) ([]string, [][]string, error) { + resources, err := m.getResources(tenantID, subscriptionID) if err != nil { return nil, nil, err } @@ -136,14 +142,24 @@ func getInventoryInfoPerSubscription(tenantID, subscriptionID string) ([]string, for _, resource := range resources { resourceType := ptr.ToString(resource.Type) resourceLocation := ptr.ToString(resource.Location) - _, ok := inventory[resourceType] + if len(m.AzClient.AzRGs) > 0 { + for _, AzRG := range m.AzClient.AzRGs { + metaResource, _ := azure.ParseResourceID(*resource.ID) + if metaResource.ResourceGroup == *AzRG.Name { + goto ADD_RESOURCE + } + } + goto SKIP_RESOURCE + } + ADD_RESOURCE: if !ok { inventory[resourceType] = make(map[string]int) } inventory[resourceType][resourceLocation]++ resourceTypes[resourceType] = true resourceLocations[resourceLocation] = true + SKIP_RESOURCE: } header := []string{"Resource Type"} @@ -170,14 +186,14 @@ func getInventoryInfoPerSubscription(tenantID, subscriptionID string) ([]string, return header, body, nil } -func getInventoryInfoPerTenant(tenantID string) ([]string, [][]string, error) { +func (m *AzInventoryModule) getInventoryInfoPerTenant(tenantID string) ([]string, [][]string, error) { inventory := make(map[string]map[string]int) resourceTypes := make(map[string]bool) resourceLocations := make(map[string]bool) for _, s := range GetSubscriptionsPerTenantID(tenantID) { - resources, err := getResources(tenantID, ptr.ToString(s.SubscriptionID)) + resources, err := m.getResources(tenantID, ptr.ToString(s.SubscriptionID)) if err != nil { return nil, nil, err } @@ -220,7 +236,7 @@ func getInventoryInfoPerTenant(tenantID string) ([]string, [][]string, error) { return header, body, nil } -func getResources(tenantID, subscriptionID string) ([]*armresources.GenericResourceExpanded, error) { +func (m *AzInventoryModule) getResources(tenantID, subscriptionID string) ([]*armresources.GenericResourceExpanded, error) { client := internal.GetARMresourcesClient(tenantID, subscriptionID) var resources []*armresources.GenericResourceExpanded diff --git a/cli/azure.go b/cli/azure.go index 6b0a8542..8c94be9e 100644 --- a/cli/azure.go +++ b/cli/azure.go @@ -147,7 +147,10 @@ func runAzWhoamiCommand (cmd *cobra.Command, args []string) { } func runAzInventoryCommand (cmd *cobra.Command, args []string) { - err := azure.AzInventoryCommand(AzClient, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + m := azure.AzInventoryModule{ + AzClient: AzClient, + } + err := m.AzInventoryCommand() if err != nil { log.Fatal(err) } @@ -189,7 +192,7 @@ func runAzNSGLinksCommand(cmd *cobra.Command, args []string) { } func azurePreRun(cmd *cobra.Command, args []string) { - AzClient = internal.NewAzureClient(AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs, cmd) + AzClient = internal.NewAzureClient(AzVerbosity, AzWrapTable, AzMergedTable, AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs, cmd, AzOutputFormat, AzOutputDirectory) nTenants := len(AzClient.AzTenants) nSubscriptions := len(AzClient.AzSubscriptions) nRGs := len(AzClient.AzRGs) diff --git a/internal/azure.go b/internal/azure.go index 212235cc..b13916ad 100644 --- a/internal/azure.go +++ b/internal/azure.go @@ -22,8 +22,17 @@ import ( "github.com/kyokomi/emoji" "github.com/spf13/cobra" ) +// func NewAzureClient(AzVerbosity int, AzWrapTable, AzMergedTable bool, AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs []string, cmd *cobra.Command) *AzureClient { type AzureClient struct { + AzVerbosity int + AzWrapTable bool + AzMergedTable bool + + Version string + AzOutputFormat string + AzOutputDirectory string + AzTenants []*subscriptions.TenantIDDescription AzSubscriptions []*subscriptions.Subscription AzRGs []*resources.Group @@ -126,8 +135,14 @@ func (a *AzureClient) init (AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResour } } -func NewAzureClient(AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs []string, cmd *cobra.Command) *AzureClient { +func NewAzureClient(AzVerbosity int, AzWrapTable, AzMergedTable bool, AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs []string, cmd *cobra.Command, AzOutputFormat, AzOutputDirectory string) *AzureClient { client := new(AzureClient) + client.Version = cmd.Root().Version + client.AzWrapTable = AzWrapTable + client.AzMergedTable = AzMergedTable + client.AzVerbosity = AzVerbosity + client.AzOutputFormat = AzOutputFormat + client.AzOutputDirectory = AzOutputDirectory client.init(AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs, cmd) return client } From 9bd96cfb66179f96050208ec6f77e06293b38e35 Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Mon, 30 Oct 2023 13:16:44 -0700 Subject: [PATCH 14/40] Skip PreRun when running Azure Whoami module --- cli/azure.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cli/azure.go b/cli/azure.go index 8c94be9e..4fcceca7 100644 --- a/cli/azure.go +++ b/cli/azure.go @@ -12,6 +12,8 @@ import ( "github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/resources" ) +func void(cmd *cobra.Command, args []string) {} + var ( AzTenantID string AzSubscription string @@ -53,6 +55,7 @@ var ( Display Available Azure CLI Sessions: ./cloudfox az whoami`, Run: runAzWhoamiCommand, + PersistentPreRun: void, } AzInventoryCommand = &cobra.Command{ Use: "inventory", From 416c93fdf112511fa3e74b1c4312a8a3036d337c Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Mon, 30 Oct 2023 13:34:38 -0700 Subject: [PATCH 15/40] Skip PreRun when running Azure Whoami module, but better --- cli/azure.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cli/azure.go b/cli/azure.go index 4fcceca7..bb84a398 100644 --- a/cli/azure.go +++ b/cli/azure.go @@ -12,8 +12,6 @@ import ( "github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/resources" ) -func void(cmd *cobra.Command, args []string) {} - var ( AzTenantID string AzSubscription string @@ -55,7 +53,7 @@ var ( Display Available Azure CLI Sessions: ./cloudfox az whoami`, Run: runAzWhoamiCommand, - PersistentPreRun: void, + PersistentPreRun: func (cmd *cobra.Command, args []string) {}, } AzInventoryCommand = &cobra.Command{ Use: "inventory", From ec422dcc6c302e6cbb42f06858d066f201f761b4 Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Mon, 30 Oct 2023 15:12:24 -0700 Subject: [PATCH 16/40] Migrated the Azure VMs command to a fully modular structure, implemented RG filtering --- azure/vms.go | 110 ++++++++++++++++++++++++++++++--------------------- cli/azure.go | 5 ++- 2 files changed, 70 insertions(+), 45 deletions(-) diff --git a/azure/vms.go b/azure/vms.go index e37b86d0..4b7449e4 100644 --- a/azure/vms.go +++ b/azure/vms.go @@ -20,44 +20,49 @@ import ( "github.com/kyokomi/emoji" ) -func AzVMsCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { +type AzVMsModule struct { + AzClient *internal.AzureClient +} + + +func (m *AzVMsModule) AzVMsCommand() error { o := internal.OutputClient{ - Verbosity: AzVerbosity, + Verbosity: m.AzClient.AzVerbosity, CallingModule: globals.AZ_VMS_MODULE_NAME, Table: internal.TableClient{ - Wrap: AzWrapTable, + Wrap: m.AzClient.AzWrapTable, }, } - if len(AzClient.AzTenants) > 0 { + if len(m.AzClient.AzTenants) > 0 { // cloudfox azure inventory --tenant [TENANT_ID | PRIMARY_DOMAIN] - for _, AzTenant := range AzClient.AzTenants { + for _, AzTenant := range m.AzClient.AzTenants { - if AzMergedTable { + if m.AzClient.AzMergedTable { // set up table vars var header []string var body [][]string var userData string o := internal.OutputClient{ - Verbosity: AzVerbosity, + Verbosity: m.AzClient.AzVerbosity, CallingModule: globals.AZ_INVENTORY_MODULE_NAME, Table: internal.TableClient{ - Wrap: AzWrapTable, + Wrap: m.AzClient.AzWrapTable, }, } fmt.Printf( "[%s][%s] Enumerating VMs for tenant %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(o.CallingModule), + color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), color.CyanString(o.CallingModule), fmt.Sprintf("%s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))) o.PrefixIdentifier = ptr.ToString(AzTenant.DefaultDomain) - o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level") + o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level") //populate the table data - header, body, userData = getVMsPerTenantID(ptr.ToString(AzTenant.TenantID)) + header, body, userData = m.getVMsPerTenantID(ptr.ToString(AzTenant.TenantID)) o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{ @@ -67,7 +72,7 @@ func AzVMsCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDirect if body != nil { if userData != "" { - o.Loot.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "loot") + o.Loot.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "loot") o.Loot.LootFiles = append(o.Loot.LootFiles, internal.LootFile{ Contents: userData, @@ -81,14 +86,14 @@ func AzVMsCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDirect } } else { for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) { - runVMsCommandForSingleSubscription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + m.runVMsCommandForSingleSubscription(ptr.ToString(s.SubscriptionID)) } } } } else { // ./cloudfox azure inventory --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME] - for _, AzSubscription := range AzClient.AzSubscriptions { - runVMsCommandForSingleSubscription(*AzSubscription.SubscriptionID, AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + for _, AzSubscription := range m.AzClient.AzSubscriptions { + m.runVMsCommandForSingleSubscription(*AzSubscription.SubscriptionID) } } @@ -96,17 +101,17 @@ func AzVMsCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDirect return nil } -func runVMsCommandForSingleSubscription(AzSubscription string, AzOutputDirectory string, AzVerbosity int, AzWrapTable bool, Version string) error { +func (m *AzVMsModule) runVMsCommandForSingleSubscription(AzSubscription string) error { // set up table vars var header []string var body [][]string var userData string o := internal.OutputClient{ - Verbosity: AzVerbosity, + Verbosity: m.AzClient.AzVerbosity, CallingModule: globals.AZ_VMS_MODULE_NAME, Table: internal.TableClient{ - Wrap: AzWrapTable, + Wrap: m.AzClient.AzWrapTable, }, } var AzSubscriptionInfo SubsriptionInfo @@ -114,14 +119,14 @@ func runVMsCommandForSingleSubscription(AzSubscription string, AzOutputDirectory tenantInfo := populateTenant(tenantID) AzSubscriptionInfo = PopulateSubsriptionType(AzSubscription) o.PrefixIdentifier = AzSubscriptionInfo.Name - o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name) + o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name) fmt.Printf("[%s][%s] Enumerating VMs for subscription %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_VMS_MODULE_NAME), + color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), color.CyanString(globals.AZ_VMS_MODULE_NAME), fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID)) // populate the table data - header, body, userData = getVMsPerSubscriptionID(AzSubscriptionInfo.ID) + header, body, userData = m.getVMsPerSubscriptionID(AzSubscriptionInfo.ID) o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{ @@ -131,7 +136,7 @@ func runVMsCommandForSingleSubscription(AzSubscription string, AzOutputDirectory if body != nil { if userData != "" { - o.Loot.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name, "loot") + o.Loot.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name, "loot") o.Loot.LootFiles = append(o.Loot.LootFiles, internal.LootFile{ Contents: userData, @@ -149,7 +154,7 @@ func runVMsCommandForSingleSubscription(AzSubscription string, AzOutputDirectory return nil } -func getVMsPerTenantID(AzTenantID string) ([]string, [][]string, string) { +func (m *AzVMsModule) getVMsPerTenantID(AzTenantID string) ([]string, [][]string, string) { var resultsHeader []string var resultsBody, b [][]string var userDataCombined, userData string @@ -157,20 +162,19 @@ func getVMsPerTenantID(AzTenantID string) ([]string, [][]string, string) { for _, s := range GetSubscriptionsPerTenantID(AzTenantID) { for _, rg := range GetResourceGroups(ptr.ToString(s.SubscriptionID)) { - resultsHeader, b, userData, err = getComputeRelevantData(s, rg) + resultsHeader, b, userData, err = m.getComputeRelevantData(s, rg) if err != nil { fmt.Printf("[%s] Could not enumerate VMs for resource group %s in subscription %s\n", color.CyanString(globals.AZ_VMS_MODULE_NAME), ptr.ToString(rg.Name), ptr.ToString(s.SubscriptionID)) } else { resultsBody = append(resultsBody, b...) userDataCombined += userData } - } } return resultsHeader, resultsBody, userDataCombined } -func getVMsPerSubscriptionID(AzSubscriptionID string) ([]string, [][]string, string) { +func (m *AzVMsModule) getVMsPerSubscriptionID(AzSubscriptionID string) ([]string, [][]string, string) { var resultsHeader []string var resultsBody, b [][]string var userDataCombined, userData string @@ -179,7 +183,7 @@ func getVMsPerSubscriptionID(AzSubscriptionID string) ([]string, [][]string, str for _, s := range GetSubscriptions() { if ptr.ToString(s.SubscriptionID) == AzSubscriptionID { for _, rg := range GetResourceGroups(ptr.ToString(s.SubscriptionID)) { - resultsHeader, b, userData, err = getComputeRelevantData(s, rg) + resultsHeader, b, userData, err = m.getComputeRelevantData(s, rg) if err != nil { fmt.Printf("[%s] Could not enumerate VMs for resource group %s in subscription %s\n", color.CyanString(globals.AZ_VMS_MODULE_NAME), ptr.ToString(rg.Name), ptr.ToString(s.SubscriptionID)) } else { @@ -192,16 +196,27 @@ func getVMsPerSubscriptionID(AzSubscriptionID string) ([]string, [][]string, str return resultsHeader, resultsBody, userDataCombined } -func getComputeRelevantData(sub subscriptions.Subscription, rg resources.Group) ([]string, [][]string, string, error) { +func (m *AzVMsModule) getComputeRelevantData(sub subscriptions.Subscription, rg resources.Group) ([]string, [][]string, string, error) { header := []string{"Subscription Name", "VM Name", "VM Location", "Private IPs", "Public IPs", "Admin Username", "Resource Group Name"} var body [][]string var userDataString string + // User has requested specific resource groups, filtering + if len(m.AzClient.AzRGs) > 0 { + for _, AzRG := range m.AzClient.AzRGs { + if *rg.Name == *AzRG.Name { + goto ADD_RESOURCE + } + } + return header, body, userDataString, nil + } + ADD_RESOURCE: + subscriptionID := ptr.ToString(sub.SubscriptionID) subscriptionName := ptr.ToString(sub.DisplayName) resourceGroupName := ptr.ToString(rg.Name) - vms, err := getComputeVMsPerResourceGroup(subscriptionID, resourceGroupName) + vms, err := m.getComputeVMsPerResourceGroup(subscriptionID, resourceGroupName) if err != nil { return nil, nil, "", fmt.Errorf("error fetching vms for resource group %s: %s", resourceGroupName, err) } @@ -211,9 +226,9 @@ func getComputeRelevantData(sub subscriptions.Subscription, rg resources.Group) if vm.VirtualMachineProperties != nil && vm.OsProfile != nil { adminUsername = ptr.ToString(vm.OsProfile.AdminUsername) } - privateIPs, publicIPs := getIPs(ptr.ToString(sub.SubscriptionID), ptr.ToString(rg.Name), vm) + privateIPs, publicIPs := m.getIPs(ptr.ToString(sub.SubscriptionID), ptr.ToString(rg.Name), vm) // get userdata - vmDetails, err := getComputeVmInfo(subscriptionID, resourceGroupName, ptr.ToString(vm.Name)) + vmDetails, err := m.getComputeVmInfo(subscriptionID, resourceGroupName, ptr.ToString(vm.Name)) if err != nil { fmt.Println("error fetching vm details for vm: ", ptr.ToString(vm.Name)) } @@ -256,9 +271,12 @@ func getComputeRelevantData(sub subscriptions.Subscription, rg resources.Group) return header, body, userDataString, nil } -var getComputeVMsPerResourceGroup = getComputeVMsPerResourceGroupOriginal -func getComputeVMsPerResourceGroupOriginal(subscriptionID string, resourceGroup string) ([]compute.VirtualMachine, error) { +func (m *AzVMsModule) getComputeVMsPerResourceGroup(subscriptionID string, resourceGroup string) ([]compute.VirtualMachine, error) { + return m.getComputeVMsPerResourceGroupOriginal(subscriptionID, resourceGroup) +} + +func (m *AzVMsModule) getComputeVMsPerResourceGroupOriginal(subscriptionID string, resourceGroup string) ([]compute.VirtualMachine, error) { computeClient := internal.GetVirtualMachinesClient(subscriptionID) var vms []compute.VirtualMachine @@ -275,7 +293,7 @@ func getComputeVMsPerResourceGroupOriginal(subscriptionID string, resourceGroup } // get vms with user-data view -func getComputeVmInfo(subscriptionID string, resourceGroup string, vmName string) (compute.VirtualMachine, error) { +func (m *AzVMsModule) getComputeVmInfo(subscriptionID string, resourceGroup string, vmName string) (compute.VirtualMachine, error) { computeClient := internal.GetVirtualMachinesClient(subscriptionID) vm, err := computeClient.Get(context.Background(), resourceGroup, vmName, compute.InstanceViewTypesUserData) if err != nil { @@ -284,7 +302,7 @@ func getComputeVmInfo(subscriptionID string, resourceGroup string, vmName string return vm, nil } -func mockedGetComputeVMsPerResourceGroup(subscriptionID, resourceGroup string) ([]compute.VirtualMachine, error) { +func (m *AzVMsModule) mockedGetComputeVMsPerResourceGroup(subscriptionID, resourceGroup string) ([]compute.VirtualMachine, error) { testFile, err := os.ReadFile(globals.VMS_TEST_FILE) if err != nil { return nil, fmt.Errorf("could not read file %s", globals.VMS_TEST_FILE) @@ -307,12 +325,12 @@ func mockedGetComputeVMsPerResourceGroup(subscriptionID, resourceGroup string) ( return results, nil } -func getIPs(subscriptionID string, resourceGroup string, vm compute.VirtualMachine) ([]string, []string) { +func (m *AzVMsModule) getIPs(subscriptionID string, resourceGroup string, vm compute.VirtualMachine) ([]string, []string) { var privateIPs, publicIPs []string if vm.VirtualMachineProperties.NetworkProfile.NetworkInterfaces != nil { for _, nicReference := range *vm.VirtualMachineProperties.NetworkProfile.NetworkInterfaces { - nic, err := getNICdetails(subscriptionID, resourceGroup, nicReference) + nic, err := m.getNICdetails(subscriptionID, resourceGroup, nicReference) if err != nil { return []string{err.Error()}, []string{err.Error()} } @@ -323,7 +341,7 @@ func getIPs(subscriptionID string, resourceGroup string, vm compute.VirtualMachi ptr.ToString( ip.InterfaceIPConfigurationPropertiesFormat.PrivateIPAddress)) - publicIP, err := getPublicIP(subscriptionID, resourceGroup, ip) + publicIP, err := m.getPublicIP(subscriptionID, resourceGroup, ip) if err != nil { publicIPs = append(publicIPs, err.Error()) } else { @@ -336,9 +354,11 @@ func getIPs(subscriptionID string, resourceGroup string, vm compute.VirtualMachi return privateIPs, publicIPs } -var getNICdetails = getNICdetailsOriginal +func (m *AzVMsModule) getNICdetails(subscriptionID string, resourceGroup string, nicReference compute.NetworkInterfaceReference) (network.Interface, error) { + return m.getNICdetailsOriginal(subscriptionID, resourceGroup, nicReference) +} -func getNICdetailsOriginal(subscriptionID string, resourceGroup string, nicReference compute.NetworkInterfaceReference) (network.Interface, error) { +func (m *AzVMsModule) getNICdetailsOriginal(subscriptionID string, resourceGroup string, nicReference compute.NetworkInterfaceReference) (network.Interface, error) { client := internal.GetNICClient(subscriptionID) NICName := strings.Split(ptr.ToString(nicReference.ID), "/")[len(strings.Split(ptr.ToString(nicReference.ID), "/"))-1] @@ -350,7 +370,7 @@ func getNICdetailsOriginal(subscriptionID string, resourceGroup string, nicRefer return nic, nil } -func mockedGetNICdetails(subscriptionID, resourceGroup string, nicReference compute.NetworkInterfaceReference) (network.Interface, error) { +func (m *AzVMsModule) mockedGetNICdetails(subscriptionID, resourceGroup string, nicReference compute.NetworkInterfaceReference) (network.Interface, error) { testFile, err := os.ReadFile(globals.NICS_TEST_FILE) if err != nil { return network.Interface{}, fmt.Errorf("NICnotFound_%s", globals.NICS_TEST_FILE) @@ -370,9 +390,11 @@ func mockedGetNICdetails(subscriptionID, resourceGroup string, nicReference comp return network.Interface{}, fmt.Errorf("NICnotFound_%s", ptr.ToString(nicReference.ID)) } -var getPublicIP = getPublicIPOriginal +func (m *AzVMsModule) getPublicIP(subscriptionID string, resourceGroup string, ip network.InterfaceIPConfiguration) (*string, error) { + return m.getPublicIPOriginal(subscriptionID, resourceGroup, ip) +} -func getPublicIPOriginal(subscriptionID string, resourceGroup string, ip network.InterfaceIPConfiguration) (*string, error) { +func (m *AzVMsModule) getPublicIPOriginal(subscriptionID string, resourceGroup string, ip network.InterfaceIPConfiguration) (*string, error) { client := internal.GetPublicIPClient(subscriptionID) if ip.InterfaceIPConfigurationPropertiesFormat.PublicIPAddress == nil { return nil, fmt.Errorf("NoPublicIP") @@ -386,7 +408,7 @@ func getPublicIPOriginal(subscriptionID string, resourceGroup string, ip network return publicIPExpanded.PublicIPAddressPropertiesFormat.IPAddress, nil } -func mockedGetPublicIP(subscriptionID, resourceGroup string, ip network.InterfaceIPConfiguration) (*string, error) { +func (m *AzVMsModule) mockedGetPublicIP(subscriptionID, resourceGroup string, ip network.InterfaceIPConfiguration) (*string, error) { f, err := os.ReadFile(globals.PUBLIC_IPS_TEST_FILE) if err != nil { return nil, fmt.Errorf("IPNotFound_%s", globals.PUBLIC_IPS_TEST_FILE) diff --git a/cli/azure.go b/cli/azure.go index bb84a398..09905278 100644 --- a/cli/azure.go +++ b/cli/azure.go @@ -165,7 +165,10 @@ func runAzRBACCommand (cmd *cobra.Command, args []string) { } func runAzVMsCommand (cmd *cobra.Command, args []string) { - err := azure.AzVMsCommand(AzClient, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + m := azure.AzVMsModule{ + AzClient: AzClient, + } + err := m.AzVMsCommand() if err != nil { log.Fatal(err) } From 50445263d74c280a84e3653ce7c530c50f475dde Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Mon, 30 Oct 2023 15:36:40 -0700 Subject: [PATCH 17/40] Migrated the Azure storage command to a fully modular structure, implemented RG filtering --- azure/storage.go | 102 ++++++++++++++++++++++++++++------------------- cli/azure.go | 5 ++- 2 files changed, 66 insertions(+), 41 deletions(-) diff --git a/azure/storage.go b/azure/storage.go index 4d943bcc..e99da3f6 100644 --- a/azure/storage.go +++ b/azure/storage.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage" + "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" "github.com/BishopFox/cloudfox/globals" @@ -22,38 +23,42 @@ import ( // Color functions var cyan = color.New(color.FgCyan).SprintFunc() -func AzStorageCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { +type AzStorageModule struct { + AzClient *internal.AzureClient +} + +func (m *AzStorageModule) AzStorageCommand() error { var publicBlobURLs []string - if len(AzClient.AzTenants) > 0 { + if len(m.AzClient.AzTenants) > 0 { // cloudfox azure storage --tenant [TENANT_ID | PRIMARY_DOMAIN] - for _, AzTenant := range AzClient.AzTenants { + for _, AzTenant := range m.AzClient.AzTenants { - if AzMergedTable { + if m.AzClient.AzMergedTable { // set up table vars var header []string var body [][]string // setup logging client o := internal.OutputClient{ - Verbosity: AzVerbosity, + Verbosity: m.AzClient.AzVerbosity, CallingModule: globals.AZ_STORAGE_MODULE_NAME, Table: internal.TableClient{ - Wrap: AzWrapTable, + Wrap: m.AzClient.AzWrapTable, }, } var err error fmt.Printf("[%s][%s] Enumerating storage accounts for tenant %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME), + color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME), fmt.Sprintf("%s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))) o.PrefixIdentifier = ptr.ToString(AzTenant.DefaultDomain) - o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level") + o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level") - header, body, publicBlobURLs, err = getStorageInfoPerTenant(ptr.ToString(AzTenant.TenantID)) + header, body, publicBlobURLs, err = m.getStorageInfoPerTenant(ptr.ToString(AzTenant.TenantID)) if err != nil { return err @@ -68,7 +73,7 @@ func AzStorageCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDi o.WriteFullOutput(o.Table.TableFiles, nil) } if publicBlobURLs != nil { - err := writeBlobURLslootFile(globals.AZ_STORAGE_MODULE_NAME, o.PrefixIdentifier, o.Table.DirectoryName, publicBlobURLs) + err := m.writeBlobURLslootFile(globals.AZ_STORAGE_MODULE_NAME, o.PrefixIdentifier, o.Table.DirectoryName, publicBlobURLs) if err != nil { return err } @@ -76,26 +81,26 @@ func AzStorageCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDi } else { for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) { - runStorageCommandForSingleSubcription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + m.runStorageCommandForSingleSubcription(ptr.ToString(s.SubscriptionID)) } } } } else { - for _, AzSubscription := range AzClient.AzSubscriptions { - runStorageCommandForSingleSubcription(*AzSubscription.SubscriptionID, AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + for _, AzSubscription := range m.AzClient.AzSubscriptions { + m.runStorageCommandForSingleSubcription(*AzSubscription.SubscriptionID) } } return nil } -func runStorageCommandForSingleSubcription(AzSubscription string, AzOutputDirectory string, AzVerbosity int, AzWrapTable bool, Version string) error { +func (m *AzStorageModule) runStorageCommandForSingleSubcription(AzSubscription string) error { var err error // setup logging client o := internal.OutputClient{ - Verbosity: AzVerbosity, + Verbosity: m.AzClient.AzVerbosity, CallingModule: globals.AZ_STORAGE_MODULE_NAME, Table: internal.TableClient{ - Wrap: AzWrapTable, + Wrap: m.AzClient.AzWrapTable, }, } @@ -108,15 +113,15 @@ func runStorageCommandForSingleSubcription(AzSubscription string, AzOutputDirect tenantInfo := populateTenant(tenantID) AzSubscriptionInfo := PopulateSubsriptionType(AzSubscription) o.PrefixIdentifier = AzSubscriptionInfo.Name - o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name) + o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name) fmt.Printf( "[%s][%s] Enumerating storage accounts for subscription %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), + color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), color.CyanString(globals.AZ_STORAGE_MODULE_NAME), fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID)) //AzTenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription)) - header, body, publicBlobURLs, err = getStorageInfoPerSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID) + header, body, publicBlobURLs, err = m.getStorageInfoPerSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID) if err != nil { return err } @@ -131,7 +136,7 @@ func runStorageCommandForSingleSubcription(AzSubscription string, AzOutputDirect } if publicBlobURLs != nil { - err := writeBlobURLslootFile(globals.AZ_STORAGE_MODULE_NAME, o.PrefixIdentifier, o.Table.DirectoryName, publicBlobURLs) + err := m.writeBlobURLslootFile(globals.AZ_STORAGE_MODULE_NAME, o.PrefixIdentifier, o.Table.DirectoryName, publicBlobURLs) if err != nil { return err } @@ -140,7 +145,7 @@ func runStorageCommandForSingleSubcription(AzSubscription string, AzOutputDirect } -func writeBlobURLslootFile(callingModule, controlMessagePrefix, outputDirectory string, publicBlobURLs []string) error { +func (m *AzStorageModule) writeBlobURLslootFile(callingModule, controlMessagePrefix, outputDirectory string, publicBlobURLs []string) error { lootDirectory := filepath.Join(outputDirectory, "loot") lootFilePath := filepath.Join(lootDirectory, "public-blob-urls.txt") @@ -166,14 +171,14 @@ func writeBlobURLslootFile(callingModule, controlMessagePrefix, outputDirectory return nil } -func getStorageInfoPerTenant(AzTenantID string) ([]string, [][]string, []string, error) { +func (m *AzStorageModule) getStorageInfoPerTenant(AzTenantID string) ([]string, [][]string, []string, error) { var err error var header []string var body, b [][]string var publicBlobURLs []string for _, s := range GetSubscriptionsPerTenantID(AzTenantID) { - header, b, publicBlobURLs, err = getRelevantStorageAccountData(AzTenantID, ptr.ToString(s.SubscriptionID)) + header, b, publicBlobURLs, err = m.getRelevantStorageAccountData(AzTenantID, ptr.ToString(s.SubscriptionID)) if err != nil { return nil, nil, nil, err } else { @@ -183,7 +188,7 @@ func getStorageInfoPerTenant(AzTenantID string) ([]string, [][]string, []string, return header, body, publicBlobURLs, nil } -func getStorageInfoPerSubscription(AzTenantID, AzSubscriptionID string) ([]string, [][]string, []string, error) { +func (m *AzStorageModule) getStorageInfoPerSubscription(AzTenantID, AzSubscriptionID string) ([]string, [][]string, []string, error) { var err error var header []string var body [][]string @@ -191,7 +196,7 @@ func getStorageInfoPerSubscription(AzTenantID, AzSubscriptionID string) ([]strin for _, s := range GetSubscriptions() { if ptr.ToString(s.SubscriptionID) == AzSubscriptionID { - header, body, publicBlobURLs, err = getRelevantStorageAccountData(AzTenantID, ptr.ToString(s.SubscriptionID)) + header, body, publicBlobURLs, err = m.getRelevantStorageAccountData(AzTenantID, ptr.ToString(s.SubscriptionID)) if err != nil { return nil, nil, nil, err } @@ -200,20 +205,34 @@ func getStorageInfoPerSubscription(AzTenantID, AzSubscriptionID string) ([]strin return header, body, publicBlobURLs, nil } -func getRelevantStorageAccountData(tenantID, subscriptionID string) ([]string, [][]string, []string, error) { +func (m *AzStorageModule) getRelevantStorageAccountData(tenantID, subscriptionID string) ([]string, [][]string, []string, error) { tableHeader := []string{"Subscription Name", "Storage Account Name", "Container Name", "Access Status"} var tableBody [][]string var publicBlobURLs []string - storageAccounts, err := getStorageAccounts(subscriptionID) + var urls []string + var containers map[string]string + storageAccounts, err := m.getStorageAccounts(subscriptionID) if err != nil { return nil, nil, nil, err } for _, sa := range storageAccounts { blobClient, err := internal.GetStorageAccountBlobClient(tenantID, ptr.ToString(sa.Name)) + // if resource group were submitted, do filtering + if len(m.AzClient.AzRGs) > 0 { + fmt.Println(*sa.ID) + for _, AzRG := range m.AzClient.AzRGs { + metaResource, _ := azure.ParseResourceID(*sa.ID) + if metaResource.ResourceGroup == *AzRG.Name { + goto ADD_RESOURCE + } + } + goto SKIP_RESOURCE + } + ADD_RESOURCE: if err != nil { return nil, nil, nil, err } - containers, err := getStorageAccountContainers(blobClient) + containers, err = m.getStorageAccountContainers(blobClient) if err != nil { // rather than return an error, we'll just add a row to the table highlighting the storage account name and that we couldn't get the containers @@ -235,20 +254,23 @@ func getRelevantStorageAccountData(tenantID, subscriptionID string) ([]string, [ containerName, accessType}) } - urls, err := getPublicBlobURLs(blobClient, ptr.ToString(sa.Name), containers) + urls, err = m.getPublicBlobURLs(blobClient, ptr.ToString(sa.Name), containers) if err == nil { continue //return nil, nil, nil, err } publicBlobURLs = append(publicBlobURLs, urls...) + SKIP_RESOURCE: } return tableHeader, tableBody, publicBlobURLs, nil } -var getStorageAccounts = getStorageAccountsOriginal +func (m *AzStorageModule) getStorageAccounts(subscriptionID string) ([]storage.Account, error) { + return m.getStorageAccountsOriginal(subscriptionID) +} -func getStorageAccountsOriginal(subscriptionID string) ([]storage.Account, error) { +func (m *AzStorageModule) getStorageAccountsOriginal(subscriptionID string) ([]storage.Account, error) { storageClient := internal.GetStorageClient(subscriptionID) var storageAccounts []storage.Account for page, err := storageClient.List(context.TODO()); page.NotDone(); page.Next() { @@ -260,7 +282,7 @@ func getStorageAccountsOriginal(subscriptionID string) ([]storage.Account, error return storageAccounts, nil } -func mockedGetStorageAccounts(subscriptionID string) ([]storage.Account, error) { +func (m *AzStorageModule) mockedGetStorageAccounts(subscriptionID string) ([]storage.Account, error) { testFile, err := os.ReadFile(globals.STORAGE_ACCOUNTS_TEST_FILE) if err != nil { return nil, fmt.Errorf("could not open storage accounts test file %s", globals.STORAGE_ACCOUNTS_TEST_FILE) @@ -279,7 +301,7 @@ func mockedGetStorageAccounts(subscriptionID string) ([]storage.Account, error) return storageAccountsResults, nil } -func getStorageAccountContainers(client *azblob.Client) (map[string]string, error) { +func (m *AzStorageModule) getStorageAccountContainers(client *azblob.Client) (map[string]string, error) { containers := make(map[string]string) pager := client.NewListContainersPager(&azblob.ListContainersOptions{ Include: azblob.ListContainersInclude{Metadata: true, Deleted: true}, @@ -300,11 +322,11 @@ func getStorageAccountContainers(client *azblob.Client) (map[string]string, erro return containers, nil } -func getPublicBlobURLs(client *azblob.Client, storageAccountName string, containers map[string]string) ([]string, error) { +func (m *AzStorageModule) getPublicBlobURLs(client *azblob.Client, storageAccountName string, containers map[string]string) ([]string, error) { var publicBlobURLs []string for containerName, accessType := range containers { if accessType == "public" { - url, err := getPublicBlobURLsForContainer(client, storageAccountName, containerName) + url, err := m.getPublicBlobURLsForContainer(client, storageAccountName, containerName) if err != nil { //return nil, err continue @@ -315,19 +337,19 @@ func getPublicBlobURLs(client *azblob.Client, storageAccountName string, contain return publicBlobURLs, nil } -func getPublicBlobURLsForContainer(client *azblob.Client, storageAccountName, containerName string) ([]string, error) { - blobNames, err := getAllBlobsForContainer(client, containerName) +func (m *AzStorageModule) getPublicBlobURLsForContainer(client *azblob.Client, storageAccountName, containerName string) ([]string, error) { + blobNames, err := m.getAllBlobsForContainer(client, containerName) if err != nil { return nil, err } - publicBlobURLs, err := validatePublicBlobURLs(storageAccountName, containerName, blobNames) + publicBlobURLs, err := m.validatePublicBlobURLs(storageAccountName, containerName, blobNames) if err != nil { return nil, err } return publicBlobURLs, nil } -func getAllBlobsForContainer(blobClient *azblob.Client, containerName string) ([]string, error) { +func (m *AzStorageModule) getAllBlobsForContainer(blobClient *azblob.Client, containerName string) ([]string, error) { var blobNames []string pager := blobClient.NewListBlobsFlatPager(containerName, &azblob.ListBlobsFlatOptions{ @@ -347,7 +369,7 @@ func getAllBlobsForContainer(blobClient *azblob.Client, containerName string) ([ return blobNames, nil } -func validatePublicBlobURLs(storageAccountName, containerName string, blobNames []string) ([]string, error) { +func (m *AzStorageModule) validatePublicBlobURLs(storageAccountName, containerName string, blobNames []string) ([]string, error) { var publicBlobURLs []string if blobNames == nil { diff --git a/cli/azure.go b/cli/azure.go index 09905278..8dd5a96c 100644 --- a/cli/azure.go +++ b/cli/azure.go @@ -175,7 +175,10 @@ func runAzVMsCommand (cmd *cobra.Command, args []string) { } func runAzStorageCommand (cmd *cobra.Command, args []string) { - err := azure.AzStorageCommand(AzClient, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + m := azure.AzStorageModule{ + AzClient: AzClient, + } + err := m.AzStorageCommand() if err != nil { log.Fatal(err) } From 6665a9a5f05840ef29bd845585d84997b3019c9c Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Mon, 30 Oct 2023 16:00:18 -0700 Subject: [PATCH 18/40] Migrated the Azure RBAC command to a fully modular structure --- azure/rbac.go | 82 +++++++++++++++++++++++++++++---------------------- cli/azure.go | 5 +++- 2 files changed, 51 insertions(+), 36 deletions(-) diff --git a/azure/rbac.go b/azure/rbac.go index d4cd40b7..3e1c8669 100644 --- a/azure/rbac.go +++ b/azure/rbac.go @@ -19,38 +19,43 @@ import ( "github.com/kyokomi/emoji" ) -func AzRBACCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { +type AzRBACModule struct { + AzClient *internal.AzureClient +} + +func (m *AzRBACModule) AzRBACCommand() error { // setup logging client o := internal.OutputClient{ - Verbosity: AzVerbosity, + Verbosity: m.AzClient.AzVerbosity, CallingModule: globals.AZ_RBAC_MODULE_NAME, Table: internal.TableClient{ - Wrap: AzWrapTable, + Wrap: m.AzClient.AzWrapTable, }, } // initiate command specific client var c CloudFoxRBACclient + c.RBACModule = m // set up table vars var header []string var body [][]string - if len(AzClient.AzTenants) > 0 { + if len(m.AzClient.AzTenants) > 0 { // cloudfox azure rbac --tenant [TENANT_ID | PRIMARY_DOMAIN] - for _, AzTenant := range AzClient.AzTenants { + for _, AzTenant := range m.AzClient.AzTenants { var err error if err != nil { return err } o.PrefixIdentifier = *AzTenant.DefaultDomain - o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, *AzTenant.DefaultDomain, "1-tenant-level") + o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, *AzTenant.DefaultDomain, "1-tenant-level") fmt.Printf("[%s][%s] Enumerating RBAC permissions for tenant %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME), + color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME), fmt.Sprintf("%s (%s)", *AzTenant.DefaultDomain, *AzTenant.TenantID)) - header, body, err = getRBACperTenant(ptr.ToString(AzTenant.TenantID), c) + header, body, err = m.getRBACperTenant(ptr.ToString(AzTenant.TenantID), c) if err != nil { return err } @@ -63,14 +68,14 @@ func AzRBACCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDirec } } else{ // cloudfox azure rbac --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME] - for _, AzSubscription := range AzClient.AzSubscriptions { + for _, AzSubscription := range m.AzClient.AzSubscriptions { tenantInfo := populateTenant(*AzSubscription.TenantID) o.PrefixIdentifier = ptr.ToString(AzSubscription.DisplayName) - o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), *AzSubscription.DisplayName) + o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), *AzSubscription.DisplayName) - fmt.Printf("[%s][%s] Enumerating RBAC permissions for subscription %s\n", color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME), + fmt.Printf("[%s][%s] Enumerating RBAC permissions for subscription %s\n", color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME), fmt.Sprintf("%s (%s)", ptr.ToString(AzSubscription.DisplayName), *AzSubscription.SubscriptionID)) - header, body = getRBACperSubscription(ptr.ToString(tenantInfo.ID), *AzSubscription.SubscriptionID, c) + header, body = m.getRBACperSubscription(ptr.ToString(tenantInfo.ID), *AzSubscription.SubscriptionID, c) o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{ Header: header, @@ -81,14 +86,14 @@ func AzRBACCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDirec } if body != nil { - //internal.OutputSelector(AzVerbosity, AzOutputFormat, header, body, outputDirectory, fileNameWithoutExtension, globals.AZ_RBAC_MODULE_NAME, AzWrapTable, controlMessagePrefix) + //internal.OutputSelector(m.AzClient.AzVerbosity, AzOutputFormat, header, body, outputDirectory, fileNameWithoutExtension, globals.AZ_RBAC_MODULE_NAME, AzWrapTable, controlMessagePrefix) o.WriteFullOutput(o.Table.TableFiles, nil) } return nil } -func getRBACperTenant(AzTenantID string, c CloudFoxRBACclient) ([]string, [][]string, error) { +func (m *AzRBACModule) getRBACperTenant(AzTenantID string, c CloudFoxRBACclient) ([]string, [][]string, error) { var selectedSubs, resultsHeader []string var resultsBody, b [][]string for _, s := range GetSubscriptions() { @@ -107,7 +112,7 @@ func getRBACperTenant(AzTenantID string, c CloudFoxRBACclient) ([]string, [][]st return resultsHeader, resultsBody, nil } -func getRBACperSubscription(AzTenantID, AzSubscriptionID string, c CloudFoxRBACclient) ([]string, [][]string) { +func (m *AzRBACModule) getRBACperSubscription(AzTenantID, AzSubscriptionID string, c CloudFoxRBACclient) ([]string, [][]string) { var resultsHeader []string var resultsBody [][]string for _, s := range GetSubscriptions() { @@ -123,6 +128,7 @@ type CloudFoxRBACclient struct { roleAssignments []authorization.RoleAssignment roleDefinitions []authorization.RoleDefinition AADUsers []graphrbac.User + RBACModule *AzRBACModule } func (c *CloudFoxRBACclient) initialize(tenantID string, subscriptionIDs []string) error { @@ -131,19 +137,19 @@ func (c *CloudFoxRBACclient) initialize(tenantID string, subscriptionIDs []strin c.roleAssignments = nil c.roleDefinitions = nil - c.AADUsers, err = getAzureADUsers(tenantID) + c.AADUsers, err = c.RBACModule.getAzureADUsers(tenantID) if err != nil { return fmt.Errorf("[%s] failed to get users for tenant %s: %s", color.New(color.FgCyan).Sprint(globals.AZ_RBAC_MODULE_NAME), tenantID, err) } for _, subID := range subscriptionIDs { - rd, err := getRoleDefinitions(subID) + rd, err := c.RBACModule.getRoleDefinitions(subID) if err != nil { fmt.Printf("[%s] failed to get role definitions for subscription %s: %s. Skipping it.\n", color.New(color.FgCyan).Sprint(globals.AZ_RBAC_MODULE_NAME), subID, err) } c.roleDefinitions = append(c.roleDefinitions, rd...) - ra, err := getRoleAssignments(subID) + ra, err := c.RBACModule.getRoleAssignments(subID) if err != nil { fmt.Printf("[%s] failed to get role assignments for subscription %s: %s. Skipping it.\n", color.New(color.FgCyan).Sprint(globals.AZ_RBAC_MODULE_NAME), subID, err) } @@ -168,7 +174,7 @@ func (c *CloudFoxRBACclient) GetRelevantRBACData(tenantID, subscriptionID string } // Sort the results by userDisplayName using slice.Sort sortedResults := results - sort.Slice(sortedResults, func(i, j int) bool { + sort.Slice(sortedResults, func (i, j int) bool { return sortedResults[i].userDisplayName < sortedResults[j].userDisplayName }) @@ -213,9 +219,11 @@ type RoleAssignmentRelevantData struct { roleName string } -var getAzureADUsers = getAzureADUsersOriginal +func (m *AzRBACModule) getAzureADUsers(tenantID string) ([]graphrbac.User, error) { + return m.getAzureADUsersOriginal(tenantID) +} -func getAzureADUsersOriginal(tenantID string) ([]graphrbac.User, error) { +func (m *AzRBACModule) getAzureADUsersOriginal(tenantID string) ([]graphrbac.User, error) { var users []graphrbac.User client := internal.GetAADUsersClient(tenantID) for page, err := client.List(context.TODO(), "", ""); page.NotDone(); page.Next() { @@ -231,7 +239,7 @@ func getAzureADUsersOriginal(tenantID string) ([]graphrbac.User, error) { return users, nil } -func mockedGetAzureADUsers(tenantID string) ([]graphrbac.User, error) { +func (m *AzRBACModule) mockedGetAzureADUsers(tenantID string) ([]graphrbac.User, error) { var users AzureADUsersTestFile file, err := os.ReadFile(globals.AAD_USERS_TEST_FILE) @@ -245,10 +253,10 @@ func mockedGetAzureADUsers(tenantID string) ([]graphrbac.User, error) { return users.AzureADUsers, nil } -func generateAzureADUsersTestFIle(tenantID string) { +func (m *AzRBACModule) generateAzureADUsersTestFIle(tenantID string) { // The READ-ONLY ObjectID attribute needs to be included manually in the test file // ObjectID *string `json:"objectId,omitempty"` - users, err := getAzureADUsers(tenantID) + users, err := m.getAzureADUsers(tenantID) if err != nil { log.Fatalf("could not enumerate users for tenant %s", tenantID) } @@ -266,9 +274,11 @@ type AzureADUsersTestFile struct { AzureADUsers []graphrbac.User `json:"azureADUsers"` } -var getRoleDefinitions = getRoleDefinitionsOriginal +func (m *AzRBACModule) getRoleDefinitions(subscriptionID string) ([]authorization.RoleDefinition, error) { + return m.getRoleDefinitionsOriginal(subscriptionID) +} -func getRoleDefinitionsOriginal(subscriptionID string) ([]authorization.RoleDefinition, error) { +func (m *AzRBACModule) getRoleDefinitionsOriginal(subscriptionID string) ([]authorization.RoleDefinition, error) { client := internal.GetRoleDefinitionsClient(subscriptionID) var roleDefinitions []authorization.RoleDefinition for page, err := client.List(context.TODO(), "", ""); page.NotDone(); page.Next() { @@ -284,7 +294,7 @@ func getRoleDefinitionsOriginal(subscriptionID string) ([]authorization.RoleDefi return roleDefinitions, nil } -func mockedGetRoleDefinitions(subscriptionID string) ([]authorization.RoleDefinition, error) { +func (m *AzRBACModule) mockedGetRoleDefinitions(subscriptionID string) ([]authorization.RoleDefinition, error) { var roleDefinitions RoleDefinitionTestFile file, err := os.ReadFile(globals.ROLE_DEFINITIONS_TEST_FILE) if err != nil { @@ -297,16 +307,16 @@ func mockedGetRoleDefinitions(subscriptionID string) ([]authorization.RoleDefini return roleDefinitions.RoleDefinitions, nil } -func generateRoleDefinitionsTestFile(subscriptionID string) { +func (m *AzRBACModule) generateRoleDefinitionsTestFile(subscriptionID string) { // The READ-ONLY ID attribute needs to be included manually in the test file. // This attribute is the unique identifier for the role. // ID *string `json:"id,omitempty"`. - roleDefinitions, err := getRoleDefinitions(subscriptionID) + roleDefinitions, err := m.getRoleDefinitions(subscriptionID) if err != nil { log.Fatal(err) } - roleAssignments, err := getRoleAssignments(subscriptionID) + roleAssignments, err := m.getRoleAssignments(subscriptionID) if err != nil { log.Fatal(err) } @@ -343,9 +353,11 @@ type RoleDefinitionTestFile struct { RoleDefinitions []authorization.RoleDefinition `json:"roleDefinitions"` } -var getRoleAssignments = getRoleAssignmentsOriginal +func (m *AzRBACModule) getRoleAssignments(subscriptionID string) ([]authorization.RoleAssignment, error) { + return m.getRoleAssignmentsOriginal(subscriptionID) +} -func getRoleAssignmentsOriginal(subscriptionID string) ([]authorization.RoleAssignment, error) { +func (m *AzRBACModule) getRoleAssignmentsOriginal(subscriptionID string) ([]authorization.RoleAssignment, error) { var roleAssignments []authorization.RoleAssignment client := internal.GetRoleAssignmentsClient(subscriptionID) for page, err := client.List(context.TODO(), ""); page.NotDone(); page.Next() { @@ -360,7 +372,7 @@ func getRoleAssignmentsOriginal(subscriptionID string) ([]authorization.RoleAssi return roleAssignments, nil } -func mockedGetRoleAssignments(subscriptionID string) ([]authorization.RoleAssignment, error) { +func (m *AzRBACModule) mockedGetRoleAssignments(subscriptionID string) ([]authorization.RoleAssignment, error) { var allRoleAssignments, roleAssignmentsResults []authorization.RoleAssignment file, err := os.ReadFile(globals.ROLE_ASSIGNMENTS_TEST_FILE) if err != nil { @@ -379,8 +391,8 @@ func mockedGetRoleAssignments(subscriptionID string) ([]authorization.RoleAssign return roleAssignmentsResults, nil } -func generateRoleAssignmentsTestFile(subscriptionID string) { - ra, err := getRoleAssignments(subscriptionID) +func (m *AzRBACModule) generateRoleAssignmentsTestFile(subscriptionID string) { + ra, err := m.getRoleAssignments(subscriptionID) if err != nil { log.Fatalf("could not generate role assignments for subscription %s", subscriptionID) } diff --git a/cli/azure.go b/cli/azure.go index 8dd5a96c..15f3622c 100644 --- a/cli/azure.go +++ b/cli/azure.go @@ -158,7 +158,10 @@ func runAzInventoryCommand (cmd *cobra.Command, args []string) { } func runAzRBACCommand (cmd *cobra.Command, args []string) { - err := azure.AzRBACCommand(AzClient, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + m := azure.AzRBACModule{ + AzClient: AzClient, + } + err := m.AzRBACCommand() if err != nil { log.Fatal(err) } From bffa980d22554ee76ac9a2f28e2be8482e761d3d Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Mon, 30 Oct 2023 21:01:08 -0700 Subject: [PATCH 19/40] Added the two new Azure nsg-links and nsg-rules commands, added a map[string][]subscription populated prior to run any module --- azure/nsg.go | 50 ++++++++++ azure/nsg_links.go | 227 +++++++++++++++++---------------------------- azure/nsg_rules.go | 136 ++++++++++++++++++--------- cli/azure.go | 10 +- internal/azure.go | 14 ++- 5 files changed, 245 insertions(+), 192 deletions(-) create mode 100644 azure/nsg.go diff --git a/azure/nsg.go b/azure/nsg.go new file mode 100644 index 00000000..5b415493 --- /dev/null +++ b/azure/nsg.go @@ -0,0 +1,50 @@ +package azure + +import ( + "fmt" + "context" + "github.com/BishopFox/cloudfox/internal" + "github.com/Azure/azure-sdk-for-go/profiles/latest/network/mgmt/network" + "github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions" +) + +type AzNSGModule struct { + AzClient *internal.AzureClient + getNSGData func(string, *subscriptions.Subscription) error +} + + +func (m *AzNSGModule) AzNSGCommand(data string) error { + + if data == "links" { + m.AzNSGLinksCommand() + } else if data == "rules" { + m.AzNSGRulesCommand() + } else { + return fmt.Errorf("Invalid data selection for NSG") + } + + return nil +} + +func (m *AzNSGModule) getNSGInfoPerSubscription(tenantSlug string, AzSubscription *subscriptions.Subscription) error { + var err error + + err = m.getNSGData(tenantSlug, AzSubscription) + if err != nil { + return err + } + return nil +} + +func (m *AzNSGModule) getNSG(subscriptionID string) (*[]network.SecurityGroup, error) { + nsgClient := internal.GetNSGClient(subscriptionID) + var networkSecurityGroups []network.SecurityGroup + for page, err := nsgClient.ListAll(context.TODO()); page.NotDone(); page.Next() { + if err != nil { + return nil, fmt.Errorf("could not get network security groups for subscription") + } + networkSecurityGroups = append(networkSecurityGroups, page.Values()...) + } + return &networkSecurityGroups, nil +} diff --git a/azure/nsg_links.go b/azure/nsg_links.go index 4433d16d..67a223c4 100644 --- a/azure/nsg_links.go +++ b/azure/nsg_links.go @@ -11,174 +11,80 @@ import ( "github.com/fatih/color" "github.com/kyokomi/emoji" "github.com/Azure/azure-sdk-for-go/profiles/latest/network/mgmt/network" + "github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions" "github.com/Azure/go-autorest/autorest/azure" ) +func (m *AzNSGModule) AzNSGLinksCommand() error { -type NSGLinksModule struct { - NSGClient *network.SecurityGroupsClient + m.getNSGData = m.getNSGLinksData -} - -func AzNSGLinksCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { - - - if len(AzClient.AzTenants) > 0 { - for _, AzTenant := range AzClient.AzTenants { - - if AzMergedTable { - - // set up table vars - var header []string - var body [][]string - // setup logging client - o := internal.OutputClient{ - Verbosity: AzVerbosity, - CallingModule: globals.AZ_NSG_LINKS_MODULE_NAME, - Table: internal.TableClient{ - Wrap: AzWrapTable, - }, - } - - var err error - - fmt.Printf("[%s][%s] Enumerating Network Security Group links for tenant %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_NSG_LINKS_MODULE_NAME), - fmt.Sprintf("%s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))) - - o.PrefixIdentifier = ptr.ToString(AzTenant.DefaultDomain) - o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level") - - header, body, err = getNSGInfoPerTenant(ptr.ToString(AzTenant.TenantID)) - - if err != nil { - return err - } - o.Table.TableFiles = append(o.Table.TableFiles, - internal.TableFile{ - Header: header, - Body: body, - Name: fmt.Sprintf(globals.AZ_NSG_LINKS_MODULE_NAME)}) - - if body != nil { - o.WriteFullOutput(o.Table.TableFiles, nil) - } - } else { - for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) { - runNSGCommandForSingleSubcription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + if len(m.AzClient.AzTenants) > 0 { + for _, AzTenant := range m.AzClient.AzTenants { + fmt.Printf("[%s][%s] Enumerating Network Security Group links for tenant %s\n", + color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), color.CyanString(globals.AZ_NSG_LINKS_MODULE_NAME), + fmt.Sprintf("%s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))) + for _, AzTenant := range m.AzClient.AzTenants { + for _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) { + m.runNSGLinksCommandForSingleSubcription(*AzTenant.DefaultDomain, &AzSubscription) } } } } else { - for _, AzSubscription := range AzClient.AzSubscriptions { - runNSGCommandForSingleSubcription(*AzSubscription.SubscriptionID, AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + for tenantSlug, AzSubscriptions := range m.AzClient.AzSubscriptionsAlt { + for _, AzSubscription := range AzSubscriptions { + m.runNSGLinksCommandForSingleSubcription(tenantSlug, AzSubscription) + } } } return nil } -func runNSGCommandForSingleSubcription(AzSubscription string, AzOutputDirectory string, AzVerbosity int, AzWrapTable bool, Version string) error { +func (m *AzNSGModule) runNSGLinksCommandForSingleSubcription(tenantSlug string, AzSubscription *subscriptions.Subscription) error { var err error - // setup logging client - o := internal.OutputClient{ - Verbosity: AzVerbosity, - CallingModule: globals.AZ_NSG_LINKS_MODULE_NAME, - Table: internal.TableClient{ - Wrap: AzWrapTable, - }, - } - - // set up table vars - var header []string - var body [][]string - - tenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription)) - tenantInfo := populateTenant(tenantID) - AzSubscriptionInfo := PopulateSubsriptionType(AzSubscription) - o.PrefixIdentifier = AzSubscriptionInfo.Name - o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name) fmt.Printf( "[%s][%s] Enumerating Network Security Groups links for subscription %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), + color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), color.CyanString(globals.AZ_NSG_LINKS_MODULE_NAME), - fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID)) + fmt.Sprintf("%s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID)) //AzTenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription)) - header, body, err = getNSGInfoPerSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID) + err = m.getNSGInfoPerSubscription(tenantSlug, AzSubscription) if err != nil { return err } - o.Table.TableFiles = append(o.Table.TableFiles, - internal.TableFile{ - Header: header, - Body: body, - Name: fmt.Sprintf(globals.AZ_NSG_LINKS_MODULE_NAME)}) - if body != nil { - o.WriteFullOutput(o.Table.TableFiles, nil) - - } return nil - } -func getNSGInfoPerTenant(AzTenantID string) ([]string, [][]string, error) { - var err error - var header []string - var body, b [][]string - - for _, s := range GetSubscriptionsPerTenantID(AzTenantID) { - header, b, err = getNSGData(ptr.ToString(s.SubscriptionID)) - if err != nil { - return nil, nil, err - } else { - body = append(body, b...) - } - } - return header, body, nil -} -func getNSGInfoPerSubscription(AzTenantID, AzSubscriptionID string) ([]string, [][]string, error) { - var err error - var header []string - var body [][]string - - for _, s := range GetSubscriptions() { - if ptr.ToString(s.SubscriptionID) == AzSubscriptionID { - header, body, err = getNSGData(ptr.ToString(s.SubscriptionID)) - if err != nil { - return nil, nil, err - } - } - } - return header, body, nil -} -func value(ptr *string) string { - if ptr != nil { - return *ptr - } else { - return "AAAAA" - } -} - -func getNSGData(subscriptionID string) ([]string, [][]string, error) { - tableHeader := []string{"Subscription Name", "Network Security Group", "Link Type", "Linked Name", "Link Target"} +func (m *AzNSGModule) getNSGLinksData(tenantSlug string, AzSubscription *subscriptions.Subscription) error { + tableHeader := []string{"Network Security Group", "Link Type", "Linked Name", "Link Target"} var tableBody [][]string - networkSecurityGroups, err := getNSG(subscriptionID) - nsgClient := internal.GetNSGClient(subscriptionID) + networkSecurityGroups, err := m.getNSG(*AzSubscription.SubscriptionID) + nsgClient := internal.GetNSGClient(*AzSubscription.SubscriptionID) //subnetsClient := internal.GetSubnetsClient(subscriptionID) if err != nil { - return tableHeader, tableBody, err + return err } for _, networkSecurityGroup := range *networkSecurityGroups { + tableBody = nil + // setup logging client + o := internal.OutputClient{ + Verbosity: m.AzClient.AzVerbosity, + CallingModule: globals.AZ_NSG_LINKS_MODULE_NAME, + Table: internal.TableClient{ + Wrap: m.AzClient.AzWrapTable, + }, + } + var resource azure.Resource resource, err = azure.ParseResourceID(*networkSecurityGroup.ID) if err != nil { continue } networkSecurityGroup, _ = nsgClient.Get(context.TODO(), resource.ResourceGroup, resource.ResourceName, "Subnets,NetworkInterfaces") - fmt.Println(*networkSecurityGroup.ID) if networkSecurityGroup.Subnets != nil { for _, subnet := range *networkSecurityGroup.Subnets { var addressPrefixes []string @@ -192,10 +98,9 @@ func getNSGData(subscriptionID string) ([]string, [][]string, error) { } tableBody = append(tableBody, []string{ - subscriptionID, *networkSecurityGroup.Name, "Subnet", - value(subnet.Name), + ptr.ToString(subnet.Name), strings.Join(addressPrefixes[:], "\n"), }, ) @@ -205,28 +110,68 @@ func getNSGData(subscriptionID string) ([]string, [][]string, error) { for _, networkInterface := range *networkSecurityGroup.NetworkInterfaces { tableBody = append(tableBody, []string{ - subscriptionID, *networkSecurityGroup.Name, "NIC", - value(networkInterface.Name), + ptr.ToString(networkInterface.Name), strings.Join(internal.GetIPAddressesFromInterface(&networkInterface)[:], "\n"), }, ) } } + + // set up table vars + o.PrefixIdentifier = fmt.Sprintf("%s %s", *AzSubscription.DisplayName, *networkSecurityGroup.Name) + o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName, *networkSecurityGroup.Name) + o.Table.TableFiles = append(o.Table.TableFiles, + internal.TableFile{ + Header: tableHeader, + Body: tableBody, + Name: fmt.Sprintf(globals.AZ_NSG_LINKS_MODULE_NAME)}) + + if tableBody != nil { + o.WriteFullOutput(o.Table.TableFiles, nil) + } } - return tableHeader, tableBody, nil + return nil } -func getNSG(subscriptionID string) (*[]network.SecurityGroup, error) { - nsgClient := internal.GetNSGClient(subscriptionID) - var networkSecurityGroups []network.SecurityGroup - for page, err := nsgClient.ListAll(context.TODO()); page.NotDone(); page.Next() { - if err != nil { - return nil, fmt.Errorf("could not get network security groups for subscription") +func stringAndArrayToString(value *string, values *[]string) string { + var final string + if value != nil { + final = *value + } else { + final = "" + } + if len(*values) > 0 { + if len(final) > 0 { + final = fmt.Sprintf("%s\n%s", final, strings.Join((*values)[:], "\n")) + } else { + final = strings.Join((*values)[:], "\n") + } + } + return final +} + + +func getSourceFromSecurityGroupRule(rule *network.SecurityRule) string { + var final string + final = stringAndArrayToString(rule.SecurityRulePropertiesFormat.SourceAddressPrefix, rule.SecurityRulePropertiesFormat.SourceAddressPrefixes) + if rule.SecurityRulePropertiesFormat.SourceApplicationSecurityGroups != nil { + for _, app := range *rule.SecurityRulePropertiesFormat.SourceApplicationSecurityGroups { + final = fmt.Sprintf("%s\n%s", final, *app.Name) + } + } + return final +} + +func getDestinationFromSecurityGroupRule(rule *network.SecurityRule) string { + var final string + final = stringAndArrayToString(rule.SecurityRulePropertiesFormat.DestinationAddressPrefix, rule.SecurityRulePropertiesFormat.DestinationAddressPrefixes) + if rule.SecurityRulePropertiesFormat.DestinationApplicationSecurityGroups != nil { + for _, app := range *rule.SecurityRulePropertiesFormat.DestinationApplicationSecurityGroups { + final = fmt.Sprintf("%s\n%s", final, *app.Name) } - networkSecurityGroups = append(networkSecurityGroups, page.Values()...) } - return &networkSecurityGroups, nil + return final } diff --git a/azure/nsg_rules.go b/azure/nsg_rules.go index 9a2ff078..7a447859 100644 --- a/azure/nsg_rules.go +++ b/azure/nsg_rules.go @@ -3,71 +3,117 @@ package azure import ( "fmt" "path/filepath" + "context" "github.com/BishopFox/cloudfox/globals" "github.com/BishopFox/cloudfox/internal" "github.com/aws/smithy-go/ptr" "github.com/fatih/color" "github.com/kyokomi/emoji" + "github.com/Azure/go-autorest/autorest/azure" + "github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions" ) -func AzNSGRulesCommand(AzClient *internal.AzureClient, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error { +func (m *AzNSGModule) AzNSGRulesCommand() error { + m.getNSGData = m.getNSGRulesData - if len(AzClient.AzTenants) > 0 { - for _, AzTenant := range AzClient.AzTenants { - // cloudfox azure nsg-rules --tenant [TENANT_ID | PRIMARY_DOMAIN] - - if AzMergedTable { - - // set up table vars - var header []string - var body [][]string - // setup logging client - o := internal.OutputClient{ - Verbosity: AzVerbosity, - CallingModule: globals.AZ_NSG_RULES_MODULE_NAME, - Table: internal.TableClient{ - Wrap: AzWrapTable, - }, + if len(m.AzClient.AzTenants) > 0 { + for _, AzTenant := range m.AzClient.AzTenants { + fmt.Printf("[%s][%s] Enumerating Network Security Group rules for tenant %s\n", + color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), color.CyanString(globals.AZ_NSG_RULES_MODULE_NAME), + fmt.Sprintf("%s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))) + for _, AzTenant := range m.AzClient.AzTenants { + // cloudfox azure nsg-rules --tenant [TENANT_ID | PRIMARY_DOMAIN] + for _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) { + m.runNSGRulesCommandForSingleSubcription(*AzTenant.DefaultDomain, &AzSubscription) } + } + } + } else { + fmt.Println(m.AzClient.AzSubscriptionsAlt) + for tenantSlug, AzSubscriptions := range m.AzClient.AzSubscriptionsAlt { + for _, AzSubscription := range AzSubscriptions { + m.runNSGRulesCommandForSingleSubcription(tenantSlug, AzSubscription) + } + } + } - var err error + return nil +} - fmt.Printf("[%s][%s] Enumerating Network Security Group rules for tenant %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_NSG_RULES_MODULE_NAME), - fmt.Sprintf("%s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))) +func (m *AzNSGModule) runNSGRulesCommandForSingleSubcription(tenantSlug string, AzSubscription *subscriptions.Subscription) error { + var err error - o.PrefixIdentifier = ptr.ToString(AzTenant.DefaultDomain) - o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level") + fmt.Printf( + "[%s][%s] Enumerating Network Security Groups rules for subscription %s\n", + color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), + color.CyanString(globals.AZ_NSG_LINKS_MODULE_NAME), + fmt.Sprintf("%s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID)) + //AzTenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription)) + err = m.getNSGInfoPerSubscription(tenantSlug, AzSubscription) + if err != nil { + return err + } - header, body, err = getNSGInfoPerTenant(ptr.ToString(AzTenant.TenantID)) + return nil - if err != nil { - return err - } - o.Table.TableFiles = append(o.Table.TableFiles, - internal.TableFile{ - Header: header, - Body: body, - Name: fmt.Sprintf(globals.AZ_NSG_RULES_MODULE_NAME)}) +} - if body != nil { - o.WriteFullOutput(o.Table.TableFiles, nil) - } - } else { +func (m *AzNSGModule) getNSGRulesData(tenantSlug string, AzSubscription *subscriptions.Subscription) error { + tableHeader := []string{"Name", "Rule Type", "Protocol", "Source", "Destination", "Destination port", "Action", "Description"} + var tableBody [][]string + networkSecurityGroups, err := m.getNSG(*AzSubscription.SubscriptionID) + nsgClient := internal.GetNSGClient(*AzSubscription.SubscriptionID) + //subnetsClient := internal.GetSubnetsClient(subscriptionID) + if err != nil { + return err + } + for _, networkSecurityGroup := range *networkSecurityGroups { + tableBody = nil + // setup logging client + o := internal.OutputClient{ + Verbosity: m.AzClient.AzVerbosity, + CallingModule: globals.AZ_NSG_LINKS_MODULE_NAME, + Table: internal.TableClient{ + Wrap: m.AzClient.AzWrapTable, + }, + } - for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) { - //runNSGCommandForSingleSubcription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version) - fmt.Println(s) - } - } + var resource azure.Resource + resource, err = azure.ParseResourceID(*networkSecurityGroup.ID) + if err != nil { + continue } - } else { - for _, AzSubscription := range AzClient.AzSubscriptions { - runNSGCommandForSingleSubcription(*AzSubscription.SubscriptionID, AzOutputDirectory, AzVerbosity, AzWrapTable, Version) + networkSecurityGroup, _ = nsgClient.Get(context.TODO(), resource.ResourceGroup, resource.ResourceName, "") + for _, securityRule := range *networkSecurityGroup.SecurityRules { + tableBody = append(tableBody, + []string{ + *securityRule.Name, + fmt.Sprintf("%v", securityRule.SecurityRulePropertiesFormat.Direction), + fmt.Sprintf("%v", securityRule.SecurityRulePropertiesFormat.Protocol), + getSourceFromSecurityGroupRule(&securityRule), + getDestinationFromSecurityGroupRule(&securityRule), + stringAndArrayToString(securityRule.SecurityRulePropertiesFormat.DestinationPortRange, + securityRule.SecurityRulePropertiesFormat.DestinationPortRanges), + fmt.Sprintf("%v", securityRule.SecurityRulePropertiesFormat.Access), + ptr.ToString(securityRule.Description), + }, + ) } - } + // set up table vars + o.PrefixIdentifier = fmt.Sprintf("%s %s", *AzSubscription.DisplayName, *networkSecurityGroup.Name) + o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName, *networkSecurityGroup.Name) + o.Table.TableFiles = append(o.Table.TableFiles, + internal.TableFile{ + Header: tableHeader, + Body: tableBody, + Name: fmt.Sprintf(globals.AZ_NSG_LINKS_MODULE_NAME)}) + + if tableBody != nil { + o.WriteFullOutput(o.Table.TableFiles, nil) + } + } return nil } diff --git a/cli/azure.go b/cli/azure.go index 15f3622c..188d3f77 100644 --- a/cli/azure.go +++ b/cli/azure.go @@ -188,14 +188,20 @@ func runAzStorageCommand (cmd *cobra.Command, args []string) { } func runAzNSGRulesCommand(cmd *cobra.Command, args []string) { - err := azure.AzNSGRulesCommand(AzClient, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + m := azure.AzNSGModule{ + AzClient: AzClient, + } + err := m.AzNSGCommand("rules") if err != nil { log.Fatal(err) } } func runAzNSGLinksCommand(cmd *cobra.Command, args []string) { - err := azure.AzNSGLinksCommand(AzClient, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable) + m := azure.AzNSGModule{ + AzClient: AzClient, + } + err := m.AzNSGCommand("links") if err != nil { log.Fatal(err) } diff --git a/internal/azure.go b/internal/azure.go index b13916ad..56e51d3b 100644 --- a/internal/azure.go +++ b/internal/azure.go @@ -35,6 +35,7 @@ type AzureClient struct { AzTenants []*subscriptions.TenantIDDescription AzSubscriptions []*subscriptions.Subscription + AzSubscriptionsAlt map[string][]*subscriptions.Subscription AzRGs []*resources.Group AzResources []*azure.Resource } @@ -42,6 +43,7 @@ type AzureClient struct { func (a *AzureClient) init (AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs []string, cmd *cobra.Command){ availableSubscriptions := GetSubscriptions() availableTenants := GetTenants() + // resource identifiers were submitted on the CLI, running modules on them only if len(AzResourceRefs) > 0 { fmt.Printf("[%s] Azure resource identifiers submitted, skipping submitted tenants and subscriptions\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version))) @@ -104,13 +106,17 @@ func (a *AzureClient) init (AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResour fmt.Printf("[%s] Azure subscriptions submitted, skipping submitted tenants\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version))) // remove any other resource scope filter a.AzTenants = nil + a.AzSubscriptionsAlt = make(map[string][]*subscriptions.Subscription, 0) for _, AzSubscriptionRef := range AzSubscriptionRefs { for _, subscription := range availableSubscriptions { - if *subscription.SubscriptionID == AzSubscriptionRef { - a.AzSubscriptions = append(a.AzSubscriptions, &subscription) - goto FOUND_SUB - } else if *subscription.DisplayName == AzSubscriptionRef { + if (*subscription.SubscriptionID == AzSubscriptionRef) || (*subscription.DisplayName == AzSubscriptionRef) { a.AzSubscriptions = append(a.AzSubscriptions, &subscription) + for _, AzTenant := range availableTenants { + if *subscription.TenantID == *AzTenant.TenantID { + a.AzSubscriptionsAlt[*AzTenant.DefaultDomain] = append(a.AzSubscriptionsAlt[*AzTenant.DefaultDomain], &subscription) + break + } + } goto FOUND_SUB } } From 60dbe425a532f5882ad9955ae435182ab451b124 Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Mon, 30 Oct 2023 21:16:36 -0700 Subject: [PATCH 20/40] Cosmetic fixes in the NSG modules --- azure/nsg_links.go | 6 +++--- azure/nsg_rules.go | 12 ++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/azure/nsg_links.go b/azure/nsg_links.go index 67a223c4..e982ba53 100644 --- a/azure/nsg_links.go +++ b/azure/nsg_links.go @@ -120,13 +120,13 @@ func (m *AzNSGModule) getNSGLinksData(tenantSlug string, AzSubscription *subscri } // set up table vars - o.PrefixIdentifier = fmt.Sprintf("%s %s", *AzSubscription.DisplayName, *networkSecurityGroup.Name) - o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName, *networkSecurityGroup.Name) + o.PrefixIdentifier = fmt.Sprintf("%s", *AzSubscription.DisplayName) + o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName) o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{ Header: tableHeader, Body: tableBody, - Name: fmt.Sprintf(globals.AZ_NSG_LINKS_MODULE_NAME)}) + Name: fmt.Sprintf("%s-%s", globals.AZ_NSG_LINKS_MODULE_NAME, *networkSecurityGroup.Name)}) if tableBody != nil { o.WriteFullOutput(o.Table.TableFiles, nil) diff --git a/azure/nsg_rules.go b/azure/nsg_rules.go index 7a447859..2f5766e7 100644 --- a/azure/nsg_rules.go +++ b/azure/nsg_rules.go @@ -31,7 +31,6 @@ func (m *AzNSGModule) AzNSGRulesCommand() error { } } } else { - fmt.Println(m.AzClient.AzSubscriptionsAlt) for tenantSlug, AzSubscriptions := range m.AzClient.AzSubscriptionsAlt { for _, AzSubscription := range AzSubscriptions { m.runNSGRulesCommandForSingleSubcription(tenantSlug, AzSubscription) @@ -85,6 +84,11 @@ func (m *AzNSGModule) getNSGRulesData(tenantSlug string, AzSubscription *subscri if err != nil { continue } + fmt.Printf( + "[%s][%s] Enumerating rules for NSG %s\n", + color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), + color.CyanString(globals.AZ_NSG_LINKS_MODULE_NAME), + fmt.Sprintf(*networkSecurityGroup.Name)) networkSecurityGroup, _ = nsgClient.Get(context.TODO(), resource.ResourceGroup, resource.ResourceName, "") for _, securityRule := range *networkSecurityGroup.SecurityRules { tableBody = append(tableBody, @@ -103,13 +107,13 @@ func (m *AzNSGModule) getNSGRulesData(tenantSlug string, AzSubscription *subscri } // set up table vars - o.PrefixIdentifier = fmt.Sprintf("%s %s", *AzSubscription.DisplayName, *networkSecurityGroup.Name) - o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName, *networkSecurityGroup.Name) + o.PrefixIdentifier = fmt.Sprintf("%s", *AzSubscription.DisplayName) + o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName) o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{ Header: tableHeader, Body: tableBody, - Name: fmt.Sprintf(globals.AZ_NSG_LINKS_MODULE_NAME)}) + Name: fmt.Sprintf("%s-%s", globals.AZ_NSG_RULES_MODULE_NAME, *networkSecurityGroup.Name)}) if tableBody != nil { o.WriteFullOutput(o.Table.TableFiles, nil) From f6f78efd42de5aba6cb1fd2a213a3ea1200198df Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Mon, 30 Oct 2023 22:47:29 -0700 Subject: [PATCH 21/40] Moved version information to its own file --- VERSION | 1 + globals/VERSION | 1 + globals/utils.go | 6 ++++++ main.go | 3 ++- 4 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 VERSION create mode 120000 globals/VERSION diff --git a/VERSION b/VERSION new file mode 100644 index 00000000..d5fe5285 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.13.0-prerelease diff --git a/globals/VERSION b/globals/VERSION new file mode 120000 index 00000000..6ff19de4 --- /dev/null +++ b/globals/VERSION @@ -0,0 +1 @@ +../VERSION \ No newline at end of file diff --git a/globals/utils.go b/globals/utils.go index ee913aa5..ff5f5bd2 100644 --- a/globals/utils.go +++ b/globals/utils.go @@ -1,6 +1,12 @@ package globals +import ( + _ "embed" +) + const CLOUDFOX_USER_AGENT = "cloudfox" const CLOUDFOX_LOG_FILE_DIR_NAME = ".cloudfox" const CLOUDFOX_BASE_DIRECTORY = "cloudfox-output" const LOOT_DIRECTORY_NAME = "loot" +//go:embed VERSION +var CLOUDFOX_VERSION string diff --git a/main.go b/main.go index ae6b6da8..f4772aea 100644 --- a/main.go +++ b/main.go @@ -4,13 +4,14 @@ import ( "os" "github.com/BishopFox/cloudfox/cli" + "github.com/BishopFox/cloudfox/globals" "github.com/spf13/cobra" ) var ( rootCmd = &cobra.Command{ Use: os.Args[0], - Version: "1.13.0-prerelease", + Version: globals.CLOUDFOX_VERSION, } ) From effb6a490e41899a606682176936646ddbc3535e Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Tue, 31 Oct 2023 16:36:56 -0700 Subject: [PATCH 22/40] Moved version information to a file and leverage go:embed --- globals/VERSION | 2 +- globals/utils.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) mode change 120000 => 100644 globals/VERSION diff --git a/globals/VERSION b/globals/VERSION deleted file mode 120000 index 6ff19de4..00000000 --- a/globals/VERSION +++ /dev/null @@ -1 +0,0 @@ -../VERSION \ No newline at end of file diff --git a/globals/VERSION b/globals/VERSION new file mode 100644 index 00000000..d5fe5285 --- /dev/null +++ b/globals/VERSION @@ -0,0 +1 @@ +1.13.0-prerelease diff --git a/globals/utils.go b/globals/utils.go index ff5f5bd2..3dc79228 100644 --- a/globals/utils.go +++ b/globals/utils.go @@ -2,11 +2,13 @@ package globals import ( _ "embed" + "strings" ) const CLOUDFOX_USER_AGENT = "cloudfox" const CLOUDFOX_LOG_FILE_DIR_NAME = ".cloudfox" const CLOUDFOX_BASE_DIRECTORY = "cloudfox-output" const LOOT_DIRECTORY_NAME = "loot" +var CLOUDFOX_VERSION string = strings.TrimSpace(version) //go:embed VERSION -var CLOUDFOX_VERSION string +var version string From 386f3cb0592718b2e0d19f73a8d77c18db59d7bc Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Tue, 31 Oct 2023 16:37:22 -0700 Subject: [PATCH 23/40] Implemented custom logger usable by any module and cloud provider --- internal/log.go | 108 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/internal/log.go b/internal/log.go index b27d3660..90e46d13 100644 --- a/internal/log.go +++ b/internal/log.go @@ -2,6 +2,7 @@ package internal import ( "log" + "fmt" "os" "os/user" "path/filepath" @@ -9,6 +10,9 @@ import ( "github.com/BishopFox/cloudfox/globals" "github.com/aws/smithy-go/ptr" "github.com/jedib0t/go-pretty/text" + "github.com/kyokomi/emoji" + "github.com/fatih/color" + "github.com/sirupsen/logrus" ) func init() { @@ -30,3 +34,107 @@ func GetLogDirPath() *string { } return ptr.String(dir) } + +type Logger struct { + version string + module string + txtLog *logrus.Logger + cyan func(...interface{}) string + red func(...interface{}) string + green func(...interface{}) string + yellow func(...interface{}) string +} + +func NewLogger(module string) *Logger { + lootLogger := logrus.New() + logDirPath := GetLogDirPath() + logFile, err := os.OpenFile(filepath.Join(*logDirPath, "cloudfox.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + panic("Could not initiate logger") + } + lootLogger.Out = logFile + lootLogger.SetLevel(logrus.InfoLevel) + var logger = Logger{ + version: globals.CLOUDFOX_VERSION, + txtLog: lootLogger, + module: module, + cyan: color.New(color.FgCyan).SprintFunc(), + red: color.New(color.FgRed).SprintFunc(), + green:color.New(color.FgGreen).SprintFunc(), + yellow: color.New(color.FgYellow).SprintFunc(), + } + return &logger +} + + +func (l *Logger) Print(categories []string, color func(...interface{}) string, text string) { + formatString := text + formatArgs := []any{} + l.Printf(categories, color, formatString, formatArgs...) +} + +func (l *Logger) Printf(categories []string, color func(...interface{}) string, format string, params ...any) { + formatString := "" + formatArgs := []any{} + blocks := []string{} + blocks = append(blocks, categories...) + for _, block := range blocks { + formatString += "[%s]" + formatArgs = append(formatArgs, color(block)) + } + for _, param := range params { + formatArgs = append(formatArgs, color(param)) + } + formatString += " " + format + "\n" + + fmt.Printf(formatString, formatArgs...) +} + +func (l *Logger) Announce(categories []string, text string) { + l.Print(append([]string{emoji.Sprintf(":fox:cloudfox v%s :fox:", l.version), l.module}, categories...), l.cyan, text) +} + +func (l *Logger) Announcef(categories []string, format string, params ...any) { + l.Printf(append([]string{emoji.Sprintf(":fox:cloudfox v%s :fox:", l.version), l.module}, categories...), l.cyan, format, params...) +} + +func (l *Logger) Info(categories []string, text string) { + l.Print(append([]string{l.module}, categories...), l.cyan, text) +} + +func (l *Logger) Infof(categories []string, format string, params ...any) { + l.Printf(append([]string{l.module}, categories...), l.cyan, format, params...) +} + +func (l *Logger) Success(categories []string, text string) { + l.Print(append([]string{l.module}, categories...), l.green, text) +} + +func (l *Logger) Successf(categories []string, format string, params ...any) { + l.Printf(append([]string{l.module}, categories...), l.green, format, params...) +} + +func (l *Logger) Warn(categories []string, text string) { + l.Print(append([]string{l.module}, categories...), l.yellow, text) +} + +func (l *Logger) Warnf(categories []string, format string, params ...any) { + l.Printf(append([]string{l.module}, categories...), l.yellow, format, params...) +} + +func (l *Logger) Error(categories []string, text string) { + l.Print(append([]string{l.module}, categories...), l.red, text) +} + +func (l *Logger) Errorf(categories []string, format string, params ...any) { + l.Printf(append([]string{l.module}, categories...), l.red, format, params...) +} + +func (l *Logger) Fatal(categories []string, text string) { + l.Print(append([]string{emoji.Sprintf(":fox:cloudfox v%s :fox:", l.version), l.module}, categories...), l.red, text) +} + +func (l *Logger) Fatalf(categories []string, format string, params ...any) { + l.Printf(append([]string{emoji.Sprintf(":fox:cloudfox v%s :fox:", l.version), l.module}, categories...), l.red, format, params...) + panic(nil) +} From a91e0a891ee4b7fff5beee14848203881a46034c Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Tue, 31 Oct 2023 16:38:05 -0700 Subject: [PATCH 24/40] Migrated Azure storage module to use the new logger, as well as the Azure root module --- VERSION | 1 - azure/storage.go | 15 +++++---------- cli/azure.go | 3 +-- internal/azure.go | 22 ++++++++++++---------- 4 files changed, 18 insertions(+), 23 deletions(-) delete mode 100644 VERSION diff --git a/VERSION b/VERSION deleted file mode 100644 index d5fe5285..00000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -1.13.0-prerelease diff --git a/azure/storage.go b/azure/storage.go index e99da3f6..5ed51df9 100644 --- a/azure/storage.go +++ b/azure/storage.go @@ -17,7 +17,6 @@ import ( "github.com/BishopFox/cloudfox/internal" "github.com/aws/smithy-go/ptr" "github.com/fatih/color" - "github.com/kyokomi/emoji" ) // Color functions @@ -25,10 +24,13 @@ var cyan = color.New(color.FgCyan).SprintFunc() type AzStorageModule struct { AzClient *internal.AzureClient + log *internal.Logger } func (m *AzStorageModule) AzStorageCommand() error { + m.log = internal.NewLogger("storage") + var publicBlobURLs []string if len(m.AzClient.AzTenants) > 0 { @@ -51,9 +53,7 @@ func (m *AzStorageModule) AzStorageCommand() error { var err error - fmt.Printf("[%s][%s] Enumerating storage accounts for tenant %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME), - fmt.Sprintf("%s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))) + m.log.Infof([]string{}, "Enumerating storage accounts for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID)) o.PrefixIdentifier = ptr.ToString(AzTenant.DefaultDomain) o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level") @@ -115,12 +115,7 @@ func (m *AzStorageModule) runStorageCommandForSingleSubcription(AzSubscription s o.PrefixIdentifier = AzSubscriptionInfo.Name o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name) - fmt.Printf( - "[%s][%s] Enumerating storage accounts for subscription %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), - color.CyanString(globals.AZ_STORAGE_MODULE_NAME), - fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID)) - //AzTenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription)) + m.log.Infof([]string{}, "Enumerating storage accounts for subscription %s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID) header, body, publicBlobURLs, err = m.getStorageInfoPerSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID) if err != nil { return err diff --git a/cli/azure.go b/cli/azure.go index 188d3f77..aab7ab1f 100644 --- a/cli/azure.go +++ b/cli/azure.go @@ -7,7 +7,6 @@ import ( "github.com/BishopFox/cloudfox/internal" az "github.com/Azure/go-autorest/autorest/azure" "github.com/spf13/cobra" - "github.com/kyokomi/emoji" "github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions" "github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/resources" ) @@ -215,7 +214,7 @@ func azurePreRun(cmd *cobra.Command, args []string) { nResources := len(AzClient.AzResources) nTotal := nTenants + nSubscriptions + nRGs + nResources if nTotal == 0 { - log.Fatalf("[%s] No valid target supplied, stopping\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version))) + AzClient.Log.Fatal(nil, "No valid target supplied, stopping") } } diff --git a/internal/azure.go b/internal/azure.go index 56e51d3b..37c5bb65 100644 --- a/internal/azure.go +++ b/internal/azure.go @@ -19,10 +19,8 @@ import ( "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/azure/auth" "github.com/BishopFox/cloudfox/globals" - "github.com/kyokomi/emoji" "github.com/spf13/cobra" ) -// func NewAzureClient(AzVerbosity int, AzWrapTable, AzMergedTable bool, AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs []string, cmd *cobra.Command) *AzureClient { type AzureClient struct { AzVerbosity int @@ -38,6 +36,8 @@ type AzureClient struct { AzSubscriptionsAlt map[string][]*subscriptions.Subscription AzRGs []*resources.Group AzResources []*azure.Resource + + Log *Logger } func (a *AzureClient) init (AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs []string, cmd *cobra.Command){ @@ -46,7 +46,7 @@ func (a *AzureClient) init (AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResour // resource identifiers were submitted on the CLI, running modules on them only if len(AzResourceRefs) > 0 { - fmt.Printf("[%s] Azure resource identifiers submitted, skipping submitted tenants and subscriptions\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version))) + a.Log.Info(nil, "Azure resource identifiers submitted, skipping submitted tenants and subscriptions") // remove any other resource scope filter a.AzTenants = nil a.AzSubscriptions = nil @@ -62,7 +62,7 @@ func (a *AzureClient) init (AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResour for _, azResourceRef := range AzResourceRefs { resource, err = azure.ParseResourceID(azResourceRef) if err != nil { - fmt.Printf("[%s] Invalid resource identifier : %s\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), azResourceRef) + a.Log.Errorf(nil, "Invalid resource identifier : %s", azResourceRef) continue } for _, subscription := range availableSubscriptions { @@ -74,12 +74,12 @@ func (a *AzureClient) init (AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResour goto FOUND_RESOURCE } } - fmt.Printf("[%s] No active credentials valid for resource %s, removing from target list\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), azResourceRef) + a.Log.Errorf(nil, "No active credentials valid for resource %s, removing from target list", azResourceRef) FOUND_RESOURCE: } } else if len(AzRGRefs) > 0 { // resource groups were submitted on the CLI, running modules on them only - fmt.Printf("[%s] Azure subscriptions submitted, skipping submitted tenants\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version))) + a.Log.Info(nil, "Azure subscriptions submitted, skipping submitted tenants") a.AzTenants = nil a.AzSubscriptions = nil @@ -98,12 +98,12 @@ func (a *AzureClient) init (AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResour } } } - fmt.Printf("[%s] Resource Group %s not accessible with active CLI credentials, removing from targetst\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), AzRGRef) + a.Log.Warnf(nil, "Resource Group %s not accessible with active CLI credentials, removing from targets", AzRGRef) FOUND_RG: } } else if len(AzSubscriptionRefs) > 0 { // subscriptions were submitted on the CLI, running modules on them only - fmt.Printf("[%s] Azure subscriptions submitted, skipping submitted tenants\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version))) + a.Log.Info(nil, "Azure subscriptions submitted, skipping submitted tenants") // remove any other resource scope filter a.AzTenants = nil a.AzSubscriptionsAlt = make(map[string][]*subscriptions.Subscription, 0) @@ -120,7 +120,7 @@ func (a *AzureClient) init (AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResour goto FOUND_SUB } } - fmt.Printf("[%s] Subscription %s not accessible with active CLI credentials, removing from targetst\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), AzSubscriptionRef) + a.Log.Warnf(nil, "Subscription %s not accessible with active CLI credentials, removing from targets", AzSubscriptionRef) FOUND_SUB: } } else if len(AzTenantRefs) > 0 { @@ -135,7 +135,7 @@ func (a *AzureClient) init (AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResour goto FOUND_TENANT } } - fmt.Printf("[%s] Tenant %s not accessible with active CLI credentials, removing from targetst\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), AzTenantRef) + a.Log.Warnf(nil, "Tenant %s not accessible with active CLI credentials, removing from targets", AzTenantRef) FOUND_TENANT: } } @@ -143,6 +143,8 @@ func (a *AzureClient) init (AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResour func NewAzureClient(AzVerbosity int, AzWrapTable, AzMergedTable bool, AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs []string, cmd *cobra.Command, AzOutputFormat, AzOutputDirectory string) *AzureClient { client := new(AzureClient) + client.Log = NewLogger("azure") + client.Log.Announce(nil, "Searching existing credentials and validating submitted targets...") client.Version = cmd.Root().Version client.AzWrapTable = AzWrapTable client.AzMergedTable = AzMergedTable From 8d989ccaf9f5542ada7d43a8a08318a7e1539224 Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Tue, 31 Oct 2023 16:57:35 -0700 Subject: [PATCH 25/40] Migrature Azure inventory module to new logger and eliminated legacy tenant and subscription ID manipulation --- azure/inventory.go | 48 +++++++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/azure/inventory.go b/azure/inventory.go index 52190598..b5d3b7d4 100644 --- a/azure/inventory.go +++ b/azure/inventory.go @@ -7,23 +7,24 @@ import ( "sort" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" + "github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions" "github.com/BishopFox/cloudfox/globals" "github.com/BishopFox/cloudfox/internal" "github.com/Azure/go-autorest/autorest/azure" "github.com/aws/smithy-go/ptr" - "github.com/fatih/color" - "github.com/kyokomi/emoji" ) type AzInventoryModule struct { AzClient *internal.AzureClient + log *internal.Logger } func (m *AzInventoryModule) AzInventoryCommand() error { + m.log = internal.NewLogger("inventory") o := internal.OutputClient{ Verbosity: m.AzClient.AzVerbosity, - CallingModule: globals.AZ_INVENTORY_MODULE_NAME, + CallingModule: "inventory", Table: internal.TableClient{ Wrap: m.AzClient.AzWrapTable, }, @@ -40,16 +41,13 @@ func (m *AzInventoryModule) AzInventoryCommand() error { o := internal.OutputClient{ Verbosity: m.AzClient.AzVerbosity, - CallingModule: globals.AZ_INVENTORY_MODULE_NAME, + CallingModule: "inventory", Table: internal.TableClient{ Wrap: m.AzClient.AzWrapTable, }, } - fmt.Printf( - "[%s][%s] Gathering inventory for tenant %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), color.CyanString(o.CallingModule), - fmt.Sprintf("%s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))) + m.log.Infof(nil, "Gathering inventory for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID)) o.PrefixIdentifier = ptr.ToString(AzTenant.DefaultDomain) o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level") @@ -69,23 +67,24 @@ func (m *AzInventoryModule) AzInventoryCommand() error { o.WriteFullOutput(o.Table.TableFiles, nil) } } else { - for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) { - m.runInventoryCommandForSingleSubscription(ptr.ToString(s.SubscriptionID)) + for _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) { + m.runInventoryCommandForSingleSubscription(*AzTenant.DefaultDomain, &AzSubscription) } } } } else { // ./cloudfox azure inventory --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME] - for _, AzSubscription := range m.AzClient.AzSubscriptions { - m.runInventoryCommandForSingleSubscription(*AzSubscription.SubscriptionID) + for tenantSlug, AzSubscriptions := range m.AzClient.AzSubscriptionsAlt { + for _, AzSubscription := range AzSubscriptions { + m.runInventoryCommandForSingleSubscription(tenantSlug, AzSubscription) + } } - } o.WriteFullOutput(o.Table.TableFiles, nil) return nil } -func (m *AzInventoryModule) runInventoryCommandForSingleSubscription(AzSubscription string) error { +func (m *AzInventoryModule) runInventoryCommandForSingleSubscription(tenantSlug string, AzSubscription *subscriptions.Subscription) error { // set up table vars var header []string var body [][]string @@ -97,20 +96,13 @@ func (m *AzInventoryModule) runInventoryCommandForSingleSubscription(AzSubscript Wrap: m.AzClient.AzWrapTable, }, } - var AzSubscriptionInfo SubsriptionInfo - tenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription)) - tenantInfo := populateTenant(tenantID) - AzSubscriptionInfo = PopulateSubsriptionType(AzSubscription) - o.PrefixIdentifier = AzSubscriptionInfo.Name - o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name) - - fmt.Printf( - "[%s][%s] Gathering inventory for subscription %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), color.CyanString(o.CallingModule), - fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID)) + o.PrefixIdentifier = *AzSubscription.DisplayName + o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName) + + m.log.Infof(nil, "Gathering inventory for subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID) // populate the table data - header, body, err = m.getInventoryInfoPerSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID) + header, body, err = m.getInventoryInfoPerSubscription(tenantSlug, AzSubscription) if err != nil { return err } @@ -129,8 +121,8 @@ func (m *AzInventoryModule) runInventoryCommandForSingleSubscription(AzSubscript return nil } -func (m *AzInventoryModule) getInventoryInfoPerSubscription(tenantID, subscriptionID string) ([]string, [][]string, error) { - resources, err := m.getResources(tenantID, subscriptionID) +func (m *AzInventoryModule) getInventoryInfoPerSubscription(tenantSlug string, AzSubscription *subscriptions.Subscription) ([]string, [][]string, error) { + resources, err := m.getResources(*AzSubscription.TenantID, *AzSubscription.SubscriptionID) if err != nil { return nil, nil, err } From 20881183d19488a795b7a7f1968cabed8efca3fa Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Tue, 31 Oct 2023 17:15:21 -0700 Subject: [PATCH 26/40] Migrated Azure NSG modules to new logger and eliminated legacy tenant and subscription ID manipulation --- azure/nsg.go | 2 ++ azure/nsg_links.go | 22 +++++++++------------- azure/nsg_rules.go | 38 ++++++++++++++++++++------------------ internal/log.go | 40 ++++++++++++++++++++-------------------- 4 files changed, 51 insertions(+), 51 deletions(-) diff --git a/azure/nsg.go b/azure/nsg.go index 5b415493..ca626086 100644 --- a/azure/nsg.go +++ b/azure/nsg.go @@ -11,10 +11,12 @@ import ( type AzNSGModule struct { AzClient *internal.AzureClient getNSGData func(string, *subscriptions.Subscription) error + log *internal.Logger } func (m *AzNSGModule) AzNSGCommand(data string) error { + m.log = internal.NewLogger("nsg") if data == "links" { m.AzNSGLinksCommand() diff --git a/azure/nsg_links.go b/azure/nsg_links.go index e982ba53..5d62b3e8 100644 --- a/azure/nsg_links.go +++ b/azure/nsg_links.go @@ -8,8 +8,6 @@ import ( "github.com/BishopFox/cloudfox/globals" "github.com/BishopFox/cloudfox/internal" "github.com/aws/smithy-go/ptr" - "github.com/fatih/color" - "github.com/kyokomi/emoji" "github.com/Azure/azure-sdk-for-go/profiles/latest/network/mgmt/network" "github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions" "github.com/Azure/go-autorest/autorest/azure" @@ -21,9 +19,7 @@ func (m *AzNSGModule) AzNSGLinksCommand() error { if len(m.AzClient.AzTenants) > 0 { for _, AzTenant := range m.AzClient.AzTenants { - fmt.Printf("[%s][%s] Enumerating Network Security Group links for tenant %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), color.CyanString(globals.AZ_NSG_LINKS_MODULE_NAME), - fmt.Sprintf("%s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))) + m.log.Infof([]string{"links"}, "Enumerating Network Security Group links for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID)) for _, AzTenant := range m.AzClient.AzTenants { for _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) { m.runNSGLinksCommandForSingleSubcription(*AzTenant.DefaultDomain, &AzSubscription) @@ -43,12 +39,7 @@ func (m *AzNSGModule) AzNSGLinksCommand() error { func (m *AzNSGModule) runNSGLinksCommandForSingleSubcription(tenantSlug string, AzSubscription *subscriptions.Subscription) error { var err error - fmt.Printf( - "[%s][%s] Enumerating Network Security Groups links for subscription %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), - color.CyanString(globals.AZ_NSG_LINKS_MODULE_NAME), - fmt.Sprintf("%s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID)) - //AzTenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription)) + m.log.Infof([]string{"links"}, "Enumerating Network Security Groups links for subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID) err = m.getNSGInfoPerSubscription(tenantSlug, AzSubscription) if err != nil { return err @@ -58,7 +49,6 @@ func (m *AzNSGModule) runNSGLinksCommandForSingleSubcription(tenantSlug string, } - func (m *AzNSGModule) getNSGLinksData(tenantSlug string, AzSubscription *subscriptions.Subscription) error { tableHeader := []string{"Network Security Group", "Link Type", "Linked Name", "Link Target"} var tableBody [][]string @@ -84,7 +74,13 @@ func (m *AzNSGModule) getNSGLinksData(tenantSlug string, AzSubscription *subscri if err != nil { continue } - networkSecurityGroup, _ = nsgClient.Get(context.TODO(), resource.ResourceGroup, resource.ResourceName, "Subnets,NetworkInterfaces") + networkSecurityGroup, err = nsgClient.Get(context.TODO(), resource.ResourceGroup, resource.ResourceName, "Subnets,NetworkInterfaces") + if err != nil { + m.log.Warnf([]string{"links"}, "Failed to enumerate links for NSG %s", *networkSecurityGroup.Name) + continue + } else { + m.log.Infof([]string{"links"}, "Enumerating rules for NSG %s", *networkSecurityGroup.Name) + } if networkSecurityGroup.Subnets != nil { for _, subnet := range *networkSecurityGroup.Subnets { var addressPrefixes []string diff --git a/azure/nsg_rules.go b/azure/nsg_rules.go index 2f5766e7..1fc74858 100644 --- a/azure/nsg_rules.go +++ b/azure/nsg_rules.go @@ -7,8 +7,6 @@ import ( "github.com/BishopFox/cloudfox/globals" "github.com/BishopFox/cloudfox/internal" "github.com/aws/smithy-go/ptr" - "github.com/fatih/color" - "github.com/kyokomi/emoji" "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions" ) @@ -20,9 +18,7 @@ func (m *AzNSGModule) AzNSGRulesCommand() error { if len(m.AzClient.AzTenants) > 0 { for _, AzTenant := range m.AzClient.AzTenants { - fmt.Printf("[%s][%s] Enumerating Network Security Group rules for tenant %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), color.CyanString(globals.AZ_NSG_RULES_MODULE_NAME), - fmt.Sprintf("%s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))) + m.log.Infof([]string{"rules"}, "Enumerating Network Security Group rules for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID)) for _, AzTenant := range m.AzClient.AzTenants { // cloudfox azure nsg-rules --tenant [TENANT_ID | PRIMARY_DOMAIN] for _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) { @@ -44,12 +40,7 @@ func (m *AzNSGModule) AzNSGRulesCommand() error { func (m *AzNSGModule) runNSGRulesCommandForSingleSubcription(tenantSlug string, AzSubscription *subscriptions.Subscription) error { var err error - fmt.Printf( - "[%s][%s] Enumerating Network Security Groups rules for subscription %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), - color.CyanString(globals.AZ_NSG_LINKS_MODULE_NAME), - fmt.Sprintf("%s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID)) - //AzTenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription)) + m.log.Infof([]string{"rules"}, "Enumerating Network Security Groups rules for subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID) err = m.getNSGInfoPerSubscription(tenantSlug, AzSubscription) if err != nil { return err @@ -84,12 +75,13 @@ func (m *AzNSGModule) getNSGRulesData(tenantSlug string, AzSubscription *subscri if err != nil { continue } - fmt.Printf( - "[%s][%s] Enumerating rules for NSG %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), - color.CyanString(globals.AZ_NSG_LINKS_MODULE_NAME), - fmt.Sprintf(*networkSecurityGroup.Name)) - networkSecurityGroup, _ = nsgClient.Get(context.TODO(), resource.ResourceGroup, resource.ResourceName, "") + networkSecurityGroup, err = nsgClient.Get(context.TODO(), resource.ResourceGroup, resource.ResourceName, "") + if err != nil { + m.log.Warnf([]string{"rules"}, "Failed to enumerate rules for NSG %s", *networkSecurityGroup.Name) + continue + } else { + m.log.Infof([]string{"rules"}, "Enumerating rules for NSG %s", *networkSecurityGroup.Name) + } for _, securityRule := range *networkSecurityGroup.SecurityRules { tableBody = append(tableBody, []string{ @@ -100,7 +92,7 @@ func (m *AzNSGModule) getNSGRulesData(tenantSlug string, AzSubscription *subscri getDestinationFromSecurityGroupRule(&securityRule), stringAndArrayToString(securityRule.SecurityRulePropertiesFormat.DestinationPortRange, securityRule.SecurityRulePropertiesFormat.DestinationPortRanges), - fmt.Sprintf("%v", securityRule.SecurityRulePropertiesFormat.Access), + m.colorRule(fmt.Sprintf("%v", securityRule.SecurityRulePropertiesFormat.Access)), ptr.ToString(securityRule.Description), }, ) @@ -121,3 +113,13 @@ func (m *AzNSGModule) getNSGRulesData(tenantSlug string, AzSubscription *subscri } return nil } + +func (m *AzNSGModule) colorRule(action string) string { + if action == "Allow" { + return m.log.Green(action) + } else if action == "Deny" { + return m.log.Red(action) + } else { + return action + } +} diff --git a/internal/log.go b/internal/log.go index 90e46d13..8261677f 100644 --- a/internal/log.go +++ b/internal/log.go @@ -39,10 +39,10 @@ type Logger struct { version string module string txtLog *logrus.Logger - cyan func(...interface{}) string - red func(...interface{}) string - green func(...interface{}) string - yellow func(...interface{}) string + Cyan func(...interface{}) string + Red func(...interface{}) string + Green func(...interface{}) string + Yellow func(...interface{}) string } func NewLogger(module string) *Logger { @@ -58,10 +58,10 @@ func NewLogger(module string) *Logger { version: globals.CLOUDFOX_VERSION, txtLog: lootLogger, module: module, - cyan: color.New(color.FgCyan).SprintFunc(), - red: color.New(color.FgRed).SprintFunc(), - green:color.New(color.FgGreen).SprintFunc(), - yellow: color.New(color.FgYellow).SprintFunc(), + Cyan: color.New(color.FgCyan).SprintFunc(), + Red: color.New(color.FgRed).SprintFunc(), + Green:color.New(color.FgGreen).SprintFunc(), + Yellow: color.New(color.FgYellow).SprintFunc(), } return &logger } @@ -91,50 +91,50 @@ func (l *Logger) Printf(categories []string, color func(...interface{}) string, } func (l *Logger) Announce(categories []string, text string) { - l.Print(append([]string{emoji.Sprintf(":fox:cloudfox v%s :fox:", l.version), l.module}, categories...), l.cyan, text) + l.Print(append([]string{emoji.Sprintf(":fox:cloudfox v%s :fox:", l.version), l.module}, categories...), l.Cyan, text) } func (l *Logger) Announcef(categories []string, format string, params ...any) { - l.Printf(append([]string{emoji.Sprintf(":fox:cloudfox v%s :fox:", l.version), l.module}, categories...), l.cyan, format, params...) + l.Printf(append([]string{emoji.Sprintf(":fox:cloudfox v%s :fox:", l.version), l.module}, categories...), l.Cyan, format, params...) } func (l *Logger) Info(categories []string, text string) { - l.Print(append([]string{l.module}, categories...), l.cyan, text) + l.Print(append([]string{l.module}, categories...), l.Cyan, text) } func (l *Logger) Infof(categories []string, format string, params ...any) { - l.Printf(append([]string{l.module}, categories...), l.cyan, format, params...) + l.Printf(append([]string{l.module}, categories...), l.Cyan, format, params...) } func (l *Logger) Success(categories []string, text string) { - l.Print(append([]string{l.module}, categories...), l.green, text) + l.Print(append([]string{l.module}, categories...), l.Green, text) } func (l *Logger) Successf(categories []string, format string, params ...any) { - l.Printf(append([]string{l.module}, categories...), l.green, format, params...) + l.Printf(append([]string{l.module}, categories...), l.Green, format, params...) } func (l *Logger) Warn(categories []string, text string) { - l.Print(append([]string{l.module}, categories...), l.yellow, text) + l.Print(append([]string{l.module}, categories...), l.Yellow, text) } func (l *Logger) Warnf(categories []string, format string, params ...any) { - l.Printf(append([]string{l.module}, categories...), l.yellow, format, params...) + l.Printf(append([]string{l.module}, categories...), l.Yellow, format, params...) } func (l *Logger) Error(categories []string, text string) { - l.Print(append([]string{l.module}, categories...), l.red, text) + l.Print(append([]string{l.module}, categories...), l.Red, text) } func (l *Logger) Errorf(categories []string, format string, params ...any) { - l.Printf(append([]string{l.module}, categories...), l.red, format, params...) + l.Printf(append([]string{l.module}, categories...), l.Red, format, params...) } func (l *Logger) Fatal(categories []string, text string) { - l.Print(append([]string{emoji.Sprintf(":fox:cloudfox v%s :fox:", l.version), l.module}, categories...), l.red, text) + l.Print(append([]string{emoji.Sprintf(":fox:cloudfox v%s :fox:", l.version), l.module}, categories...), l.Red, text) } func (l *Logger) Fatalf(categories []string, format string, params ...any) { - l.Printf(append([]string{emoji.Sprintf(":fox:cloudfox v%s :fox:", l.version), l.module}, categories...), l.red, format, params...) + l.Printf(append([]string{emoji.Sprintf(":fox:cloudfox v%s :fox:", l.version), l.module}, categories...), l.Red, format, params...) panic(nil) } From b0fe4a290f3b1bf0a58862ce4b59375fbd47f357 Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Tue, 31 Oct 2023 17:59:02 -0700 Subject: [PATCH 27/40] Migrated all Azure modules to new logging system, disabled legacy subscription and tenant iteration mechanisms --- azure/inventory.go | 7 +++--- azure/nsg.go | 3 +-- azure/nsg_links.go | 8 +++---- azure/nsg_rules.go | 12 +++++----- azure/rbac.go | 12 ++++------ azure/storage.go | 8 +++---- azure/vms.go | 59 ++++++++++++++++++---------------------------- azure/whoami.go | 18 ++++++++------ cli/azure.go | 38 +++++++++++++++++++++-------- 9 files changed, 83 insertions(+), 82 deletions(-) diff --git a/azure/inventory.go b/azure/inventory.go index b5d3b7d4..642775ea 100644 --- a/azure/inventory.go +++ b/azure/inventory.go @@ -16,12 +16,11 @@ import ( type AzInventoryModule struct { AzClient *internal.AzureClient - log *internal.Logger + Log *internal.Logger } func (m *AzInventoryModule) AzInventoryCommand() error { - m.log = internal.NewLogger("inventory") o := internal.OutputClient{ Verbosity: m.AzClient.AzVerbosity, CallingModule: "inventory", @@ -47,7 +46,7 @@ func (m *AzInventoryModule) AzInventoryCommand() error { }, } - m.log.Infof(nil, "Gathering inventory for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID)) + m.Log.Infof(nil, "Gathering inventory for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID)) o.PrefixIdentifier = ptr.ToString(AzTenant.DefaultDomain) o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level") @@ -99,7 +98,7 @@ func (m *AzInventoryModule) runInventoryCommandForSingleSubscription(tenantSlug o.PrefixIdentifier = *AzSubscription.DisplayName o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName) - m.log.Infof(nil, "Gathering inventory for subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID) + m.Log.Infof(nil, "Gathering inventory for subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID) // populate the table data header, body, err = m.getInventoryInfoPerSubscription(tenantSlug, AzSubscription) diff --git a/azure/nsg.go b/azure/nsg.go index ca626086..ff32c29e 100644 --- a/azure/nsg.go +++ b/azure/nsg.go @@ -11,12 +11,11 @@ import ( type AzNSGModule struct { AzClient *internal.AzureClient getNSGData func(string, *subscriptions.Subscription) error - log *internal.Logger + Log *internal.Logger } func (m *AzNSGModule) AzNSGCommand(data string) error { - m.log = internal.NewLogger("nsg") if data == "links" { m.AzNSGLinksCommand() diff --git a/azure/nsg_links.go b/azure/nsg_links.go index 5d62b3e8..fcccbc6e 100644 --- a/azure/nsg_links.go +++ b/azure/nsg_links.go @@ -19,7 +19,7 @@ func (m *AzNSGModule) AzNSGLinksCommand() error { if len(m.AzClient.AzTenants) > 0 { for _, AzTenant := range m.AzClient.AzTenants { - m.log.Infof([]string{"links"}, "Enumerating Network Security Group links for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID)) + m.Log.Infof([]string{"links"}, "Enumerating Network Security Group links for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID)) for _, AzTenant := range m.AzClient.AzTenants { for _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) { m.runNSGLinksCommandForSingleSubcription(*AzTenant.DefaultDomain, &AzSubscription) @@ -39,7 +39,7 @@ func (m *AzNSGModule) AzNSGLinksCommand() error { func (m *AzNSGModule) runNSGLinksCommandForSingleSubcription(tenantSlug string, AzSubscription *subscriptions.Subscription) error { var err error - m.log.Infof([]string{"links"}, "Enumerating Network Security Groups links for subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID) + m.Log.Infof([]string{"links"}, "Enumerating Network Security Groups links for subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID) err = m.getNSGInfoPerSubscription(tenantSlug, AzSubscription) if err != nil { return err @@ -76,10 +76,10 @@ func (m *AzNSGModule) getNSGLinksData(tenantSlug string, AzSubscription *subscri } networkSecurityGroup, err = nsgClient.Get(context.TODO(), resource.ResourceGroup, resource.ResourceName, "Subnets,NetworkInterfaces") if err != nil { - m.log.Warnf([]string{"links"}, "Failed to enumerate links for NSG %s", *networkSecurityGroup.Name) + m.Log.Warnf([]string{"links"}, "Failed to enumerate links for NSG %s", *networkSecurityGroup.Name) continue } else { - m.log.Infof([]string{"links"}, "Enumerating rules for NSG %s", *networkSecurityGroup.Name) + m.Log.Infof([]string{"links"}, "Enumerating rules for NSG %s", *networkSecurityGroup.Name) } if networkSecurityGroup.Subnets != nil { for _, subnet := range *networkSecurityGroup.Subnets { diff --git a/azure/nsg_rules.go b/azure/nsg_rules.go index 1fc74858..62978aa7 100644 --- a/azure/nsg_rules.go +++ b/azure/nsg_rules.go @@ -18,7 +18,7 @@ func (m *AzNSGModule) AzNSGRulesCommand() error { if len(m.AzClient.AzTenants) > 0 { for _, AzTenant := range m.AzClient.AzTenants { - m.log.Infof([]string{"rules"}, "Enumerating Network Security Group rules for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID)) + m.Log.Infof([]string{"rules"}, "Enumerating Network Security Group rules for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID)) for _, AzTenant := range m.AzClient.AzTenants { // cloudfox azure nsg-rules --tenant [TENANT_ID | PRIMARY_DOMAIN] for _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) { @@ -40,7 +40,7 @@ func (m *AzNSGModule) AzNSGRulesCommand() error { func (m *AzNSGModule) runNSGRulesCommandForSingleSubcription(tenantSlug string, AzSubscription *subscriptions.Subscription) error { var err error - m.log.Infof([]string{"rules"}, "Enumerating Network Security Groups rules for subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID) + m.Log.Infof([]string{"rules"}, "Enumerating Network Security Groups rules for subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID) err = m.getNSGInfoPerSubscription(tenantSlug, AzSubscription) if err != nil { return err @@ -77,10 +77,10 @@ func (m *AzNSGModule) getNSGRulesData(tenantSlug string, AzSubscription *subscri } networkSecurityGroup, err = nsgClient.Get(context.TODO(), resource.ResourceGroup, resource.ResourceName, "") if err != nil { - m.log.Warnf([]string{"rules"}, "Failed to enumerate rules for NSG %s", *networkSecurityGroup.Name) + m.Log.Warnf([]string{"rules"}, "Failed to enumerate rules for NSG %s", *networkSecurityGroup.Name) continue } else { - m.log.Infof([]string{"rules"}, "Enumerating rules for NSG %s", *networkSecurityGroup.Name) + m.Log.Infof([]string{"rules"}, "Enumerating rules for NSG %s", *networkSecurityGroup.Name) } for _, securityRule := range *networkSecurityGroup.SecurityRules { tableBody = append(tableBody, @@ -116,9 +116,9 @@ func (m *AzNSGModule) getNSGRulesData(tenantSlug string, AzSubscription *subscri func (m *AzNSGModule) colorRule(action string) string { if action == "Allow" { - return m.log.Green(action) + return m.Log.Green(action) } else if action == "Deny" { - return m.log.Red(action) + return m.Log.Red(action) } else { return action } diff --git a/azure/rbac.go b/azure/rbac.go index 3e1c8669..40464bb0 100644 --- a/azure/rbac.go +++ b/azure/rbac.go @@ -16,11 +16,11 @@ import ( "github.com/BishopFox/cloudfox/internal" "github.com/aws/smithy-go/ptr" "github.com/fatih/color" - "github.com/kyokomi/emoji" ) type AzRBACModule struct { AzClient *internal.AzureClient + Log *internal.Logger } func (m *AzRBACModule) AzRBACCommand() error { @@ -51,10 +51,7 @@ func (m *AzRBACModule) AzRBACCommand() error { o.PrefixIdentifier = *AzTenant.DefaultDomain o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, *AzTenant.DefaultDomain, "1-tenant-level") - fmt.Printf("[%s][%s] Enumerating RBAC permissions for tenant %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME), - fmt.Sprintf("%s (%s)", *AzTenant.DefaultDomain, *AzTenant.TenantID)) - + m.Log.Infof(nil, "Enumerating RBAC permissions for tenant %s (%s)", *AzTenant.DefaultDomain, *AzTenant.TenantID) header, body, err = m.getRBACperTenant(ptr.ToString(AzTenant.TenantID), c) if err != nil { return err @@ -66,15 +63,14 @@ func (m *AzRBACModule) AzRBACCommand() error { Name: fmt.Sprintf(globals.AZ_RBAC_MODULE_NAME)}) } - } else{ + } else { // cloudfox azure rbac --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME] for _, AzSubscription := range m.AzClient.AzSubscriptions { tenantInfo := populateTenant(*AzSubscription.TenantID) o.PrefixIdentifier = ptr.ToString(AzSubscription.DisplayName) o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), *AzSubscription.DisplayName) - fmt.Printf("[%s][%s] Enumerating RBAC permissions for subscription %s\n", color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME), - fmt.Sprintf("%s (%s)", ptr.ToString(AzSubscription.DisplayName), *AzSubscription.SubscriptionID)) + m.Log.Infof(nil, "Enumerating RBAC permissions for subscription %s (%s)", ptr.ToString(AzSubscription.DisplayName), *AzSubscription.SubscriptionID) header, body = m.getRBACperSubscription(ptr.ToString(tenantInfo.ID), *AzSubscription.SubscriptionID, c) o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{ diff --git a/azure/storage.go b/azure/storage.go index 5ed51df9..e4cc564e 100644 --- a/azure/storage.go +++ b/azure/storage.go @@ -24,13 +24,11 @@ var cyan = color.New(color.FgCyan).SprintFunc() type AzStorageModule struct { AzClient *internal.AzureClient - log *internal.Logger + Log *internal.Logger } func (m *AzStorageModule) AzStorageCommand() error { - m.log = internal.NewLogger("storage") - var publicBlobURLs []string if len(m.AzClient.AzTenants) > 0 { @@ -53,7 +51,7 @@ func (m *AzStorageModule) AzStorageCommand() error { var err error - m.log.Infof([]string{}, "Enumerating storage accounts for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID)) + m.Log.Infof([]string{}, "Enumerating storage accounts for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID)) o.PrefixIdentifier = ptr.ToString(AzTenant.DefaultDomain) o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level") @@ -115,7 +113,7 @@ func (m *AzStorageModule) runStorageCommandForSingleSubcription(AzSubscription s o.PrefixIdentifier = AzSubscriptionInfo.Name o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name) - m.log.Infof([]string{}, "Enumerating storage accounts for subscription %s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID) + m.Log.Infof([]string{}, "Enumerating storage accounts for subscription %s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID) header, body, publicBlobURLs, err = m.getStorageInfoPerSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID) if err != nil { return err diff --git a/azure/vms.go b/azure/vms.go index 4b7449e4..db5828e1 100644 --- a/azure/vms.go +++ b/azure/vms.go @@ -16,12 +16,11 @@ import ( "github.com/BishopFox/cloudfox/globals" "github.com/BishopFox/cloudfox/internal" "github.com/aws/smithy-go/ptr" - "github.com/fatih/color" - "github.com/kyokomi/emoji" ) type AzVMsModule struct { AzClient *internal.AzureClient + Log *internal.Logger } @@ -53,10 +52,7 @@ func (m *AzVMsModule) AzVMsCommand() error { }, } - fmt.Printf( - "[%s][%s] Enumerating VMs for tenant %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), color.CyanString(o.CallingModule), - fmt.Sprintf("%s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))) + m.Log.Infof(nil, "Enumerating VMs for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID)) o.PrefixIdentifier = ptr.ToString(AzTenant.DefaultDomain) o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level") @@ -85,23 +81,24 @@ func (m *AzVMsModule) AzVMsCommand() error { } } } else { - for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) { - m.runVMsCommandForSingleSubscription(ptr.ToString(s.SubscriptionID)) + for _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) { + m.runVMsCommandForSingleSubscription(*AzTenant.DefaultDomain, &AzSubscription) } } } } else { // ./cloudfox azure inventory --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME] - for _, AzSubscription := range m.AzClient.AzSubscriptions { - m.runVMsCommandForSingleSubscription(*AzSubscription.SubscriptionID) + for tenantSlug, AzSubscriptions := range m.AzClient.AzSubscriptionsAlt { + for _, AzSubscription := range AzSubscriptions { + m.runVMsCommandForSingleSubscription(tenantSlug, AzSubscription) + } } - } o.WriteFullOutput(o.Table.TableFiles, nil) return nil } -func (m *AzVMsModule) runVMsCommandForSingleSubscription(AzSubscription string) error { +func (m *AzVMsModule) runVMsCommandForSingleSubscription(tenantSlug string, AzSubscription *subscriptions.Subscription) error { // set up table vars var header []string var body [][]string @@ -114,19 +111,13 @@ func (m *AzVMsModule) runVMsCommandForSingleSubscription(AzSubscription string) Wrap: m.AzClient.AzWrapTable, }, } - var AzSubscriptionInfo SubsriptionInfo - tenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription)) - tenantInfo := populateTenant(tenantID) - AzSubscriptionInfo = PopulateSubsriptionType(AzSubscription) - o.PrefixIdentifier = AzSubscriptionInfo.Name - o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name) + o.PrefixIdentifier = *AzSubscription.DisplayName + o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName) - fmt.Printf("[%s][%s] Enumerating VMs for subscription %s\n", - color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", m.AzClient.Version)), color.CyanString(globals.AZ_VMS_MODULE_NAME), - fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID)) + m.Log.Infof(nil, "Enumerating VMs in subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID) // populate the table data - header, body, userData = m.getVMsPerSubscriptionID(AzSubscriptionInfo.ID) + header, body, userData = m.getVMsPerSubscriptionID(tenantSlug, AzSubscription) o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{ @@ -136,7 +127,7 @@ func (m *AzVMsModule) runVMsCommandForSingleSubscription(AzSubscription string) if body != nil { if userData != "" { - o.Loot.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name, "loot") + o.Loot.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName, "loot") o.Loot.LootFiles = append(o.Loot.LootFiles, internal.LootFile{ Contents: userData, @@ -164,7 +155,7 @@ func (m *AzVMsModule) getVMsPerTenantID(AzTenantID string) ([]string, [][]string for _, rg := range GetResourceGroups(ptr.ToString(s.SubscriptionID)) { resultsHeader, b, userData, err = m.getComputeRelevantData(s, rg) if err != nil { - fmt.Printf("[%s] Could not enumerate VMs for resource group %s in subscription %s\n", color.CyanString(globals.AZ_VMS_MODULE_NAME), ptr.ToString(rg.Name), ptr.ToString(s.SubscriptionID)) + m.Log.Warnf(nil, "Could not enumerate VMs for resource group %s in subscription %s", ptr.ToString(rg.Name), ptr.ToString(s.SubscriptionID)) } else { resultsBody = append(resultsBody, b...) userDataCombined += userData @@ -174,23 +165,19 @@ func (m *AzVMsModule) getVMsPerTenantID(AzTenantID string) ([]string, [][]string return resultsHeader, resultsBody, userDataCombined } -func (m *AzVMsModule) getVMsPerSubscriptionID(AzSubscriptionID string) ([]string, [][]string, string) { +func (m *AzVMsModule) getVMsPerSubscriptionID(tenantSlug string, AzSubscription *subscriptions.Subscription) ([]string, [][]string, string) { var resultsHeader []string var resultsBody, b [][]string var userDataCombined, userData string var err error - for _, s := range GetSubscriptions() { - if ptr.ToString(s.SubscriptionID) == AzSubscriptionID { - for _, rg := range GetResourceGroups(ptr.ToString(s.SubscriptionID)) { - resultsHeader, b, userData, err = m.getComputeRelevantData(s, rg) - if err != nil { - fmt.Printf("[%s] Could not enumerate VMs for resource group %s in subscription %s\n", color.CyanString(globals.AZ_VMS_MODULE_NAME), ptr.ToString(rg.Name), ptr.ToString(s.SubscriptionID)) - } else { - resultsBody = append(resultsBody, b...) - userDataCombined += userData - } - } + for _, rg := range GetResourceGroups(ptr.ToString(AzSubscription.SubscriptionID)) { + resultsHeader, b, userData, err = m.getComputeRelevantData(*AzSubscription, rg) + if err != nil { + m.Log.Warnf(nil, "Could not enumerate VMs for resource group %s in subscription %s", ptr.ToString(rg.Name), ptr.ToString(AzSubscription.SubscriptionID)) + } else { + resultsBody = append(resultsBody, b...) + userDataCombined += userData } } return resultsHeader, resultsBody, userDataCombined diff --git a/azure/whoami.go b/azure/whoami.go index 9e9ebcfa..38b33925 100644 --- a/azure/whoami.go +++ b/azure/whoami.go @@ -15,27 +15,31 @@ import ( "github.com/BishopFox/cloudfox/globals" "github.com/BishopFox/cloudfox/internal" "github.com/aws/smithy-go/ptr" - "github.com/fatih/color" - "github.com/kyokomi/emoji" ) -func AzWhoamiCommand(AzOutputDirectory, version string, AzWrapTable bool, AzVerbosity int, AzWhoamiListRGsAlso bool) error { +type AzWhoamiModule struct { + AzClient *internal.AzureClient + Log *internal.Logger +} + +func (m *AzWhoamiModule) AzWhoamiCommand(AzWhoamiListRGsAlso bool) error { o := internal.OutputClient{ - Verbosity: AzVerbosity, + Verbosity: m.AzClient.AzVerbosity, CallingModule: globals.AZ_WHOAMI_MODULE_NAME, Table: internal.TableClient{ - Wrap: AzWrapTable, + Wrap: m.AzClient.AzWrapTable, }, } - fmt.Printf("[%s][%s] Enumerating Azure CLI sessions...\n", color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", version)), color.CyanString(globals.AZ_WHOAMI_MODULE_NAME)) + + m.Log.Info(nil, "Enumerating Azure CLI sessions...") var header []string var body [][]string o.PrefixIdentifier = "N/A" if !AzWhoamiListRGsAlso { // cloudfox azure whoami header, body = getWhoamiRelevantDataSubsOnly() - o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, "whoami-data") + o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, "whoami-data") // append timestamp to filename (time from epoch) o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{ diff --git a/cli/azure.go b/cli/azure.go index aab7ab1f..329b738f 100644 --- a/cli/azure.go +++ b/cli/azure.go @@ -1,8 +1,6 @@ package cli import ( - "log" - "github.com/BishopFox/cloudfox/azure" "github.com/BishopFox/cloudfox/internal" az "github.com/Azure/go-autorest/autorest/azure" @@ -140,69 +138,89 @@ Enumerate links for a specific Network Security Group: ) func runAzWhoamiCommand (cmd *cobra.Command, args []string) { - err := azure.AzWhoamiCommand(AzOutputDirectory, cmd.Root().Version, AzWrapTable, AzVerbosity, AzWhoamiListRGsAlso) + AzClient = new(internal.AzureClient) + AzClient.Log = internal.NewLogger("azure") + AzClient.Log.Announce(nil, "Analyzing local Azure credentials") + AzClient.Version = cmd.Root().Version + AzClient.AzWrapTable = AzWrapTable + AzClient.AzMergedTable = AzMergedTable + AzClient.AzVerbosity = AzVerbosity + AzClient.AzOutputFormat = AzOutputFormat + AzClient.AzOutputDirectory = AzOutputDirectory + + m := azure.AzWhoamiModule{ + AzClient: AzClient, + Log: internal.NewLogger("whoami"), + } + err := m.AzWhoamiCommand(AzWhoamiListRGsAlso) if err != nil { - log.Fatal(err) + m.Log.Fatal(nil, err.Error()) } } func runAzInventoryCommand (cmd *cobra.Command, args []string) { m := azure.AzInventoryModule{ AzClient: AzClient, + Log: internal.NewLogger("inventory"), } err := m.AzInventoryCommand() if err != nil { - log.Fatal(err) + m.Log.Fatal(nil, err.Error()) } } func runAzRBACCommand (cmd *cobra.Command, args []string) { m := azure.AzRBACModule{ AzClient: AzClient, + Log: internal.NewLogger("rbac"), } err := m.AzRBACCommand() if err != nil { - log.Fatal(err) + m.Log.Fatal(nil, err.Error()) } } func runAzVMsCommand (cmd *cobra.Command, args []string) { m := azure.AzVMsModule{ AzClient: AzClient, + Log: internal.NewLogger("vms"), } err := m.AzVMsCommand() if err != nil { - log.Fatal(err) + m.Log.Fatal(nil, err.Error()) } } func runAzStorageCommand (cmd *cobra.Command, args []string) { m := azure.AzStorageModule{ AzClient: AzClient, + Log: internal.NewLogger("storage"), } err := m.AzStorageCommand() if err != nil { - log.Fatal(err) + m.Log.Fatal(nil, err.Error()) } } func runAzNSGRulesCommand(cmd *cobra.Command, args []string) { m := azure.AzNSGModule{ AzClient: AzClient, + Log: internal.NewLogger("nsg"), } err := m.AzNSGCommand("rules") if err != nil { - log.Fatal(err) + m.Log.Fatal([]string{"rules"}, err.Error()) } } func runAzNSGLinksCommand(cmd *cobra.Command, args []string) { m := azure.AzNSGModule{ AzClient: AzClient, + Log: internal.NewLogger("nsg"), } err := m.AzNSGCommand("links") if err != nil { - log.Fatal(err) + m.Log.Fatal([]string{"links"}, err.Error()) } } From 6725aaaf454e5723981a4eb44425dbdc3ea85cde Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Tue, 31 Oct 2023 18:38:47 -0700 Subject: [PATCH 28/40] Update README with new Azure commands --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 20b07558..2741f94f 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ For the full documentation please refer to our [wiki](https://github.com/BishopF | Provider| CloudFox Commands | | - | - | | AWS | 30 | -| Azure | 4 | +| Azure | 6 | | GCP | Support Planned | | Kubernetes | Support Planned | @@ -128,10 +128,13 @@ Additional policy notes (as of 09/2022): | Azure | [instances](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#instances) | Enumerates useful information for Compute instances in all available resource groups and subscriptions | | Azure | [rbac](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#rbac) | Lists Azure RBAC role assignments at subscription or tenant level | | Azure | [storage](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#storage) | The storage command is still under development. Currently it only displays limited data about the storage accounts | +| Azure | [nsg-links](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#nsg-links) | Lists subnets and network interfaces attached to Network Security Groups | +| Azure | [nsg-rules](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#nsg-rules) | Lists rules in Network Security Groups | # Authors * [Carlos Vendramini](https://github.com/carlosvendramini-bf) * [Seth Art (@sethsec](https://twitter.com/sethsec)) +* [Bastien Faure](https://github.com/BastienFaure) # Contributing [Wiki - How to Contribute](https://github.com/BishopFox/cloudfox/wiki#how-to-contribute) From 3cf38defdd9fe5adb8c8d3c3a168d6f3b1240e7f Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Tue, 31 Oct 2023 18:44:59 -0700 Subject: [PATCH 29/40] Fixed wrong output directory for Azure whoami module --- azure/whoami.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/whoami.go b/azure/whoami.go index 38b33925..d7c3e615 100644 --- a/azure/whoami.go +++ b/azure/whoami.go @@ -50,7 +50,7 @@ func (m *AzWhoamiModule) AzWhoamiCommand(AzWhoamiListRGsAlso bool) error { } else { // cloudfox azure whoami --list-rgs header, body = getWhoamiRelevantDataPerRG() - o.Table.DirectoryName = filepath.Join(ptr.ToString(internal.GetLogDirPath()), globals.AZ_DIR_BASE, "whoami-data") + o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, "whoami-data") o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{ Header: header, From 272bc0ad119e0f5be7a95b9996f3319b00241be1 Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Tue, 31 Oct 2023 18:57:34 -0700 Subject: [PATCH 30/40] Removed additional legacy code from the Azure Storage module --- azure/storage.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/azure/storage.go b/azure/storage.go index e4cc564e..c6779916 100644 --- a/azure/storage.go +++ b/azure/storage.go @@ -9,6 +9,7 @@ import ( "path/filepath" "strings" + "github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions" "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage" "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" @@ -78,20 +79,22 @@ func (m *AzStorageModule) AzStorageCommand() error { } } else { - for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) { - m.runStorageCommandForSingleSubcription(ptr.ToString(s.SubscriptionID)) + for _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) { + m.runStorageCommandForSingleSubscription(*AzTenant.DefaultDomain, &AzSubscription) } } } } else { - for _, AzSubscription := range m.AzClient.AzSubscriptions { - m.runStorageCommandForSingleSubcription(*AzSubscription.SubscriptionID) + for tenantSlug, AzSubscriptions := range m.AzClient.AzSubscriptionsAlt { + for _, AzSubscription := range AzSubscriptions { + m.runStorageCommandForSingleSubscription(tenantSlug, AzSubscription) + } } } return nil } -func (m *AzStorageModule) runStorageCommandForSingleSubcription(AzSubscription string) error { +func (m *AzStorageModule) runStorageCommandForSingleSubscription(tenantSlug string, AzSubscription *subscriptions.Subscription) error { var err error // setup logging client o := internal.OutputClient{ @@ -107,14 +110,11 @@ func (m *AzStorageModule) runStorageCommandForSingleSubcription(AzSubscription s var body [][]string var publicBlobURLs []string - tenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription)) - tenantInfo := populateTenant(tenantID) - AzSubscriptionInfo := PopulateSubsriptionType(AzSubscription) - o.PrefixIdentifier = AzSubscriptionInfo.Name - o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name) + o.PrefixIdentifier = *AzSubscription.DisplayName + o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName) - m.Log.Infof([]string{}, "Enumerating storage accounts for subscription %s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID) - header, body, publicBlobURLs, err = m.getStorageInfoPerSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID) + m.Log.Infof([]string{}, "Enumerating storage accounts for subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID) + header, body, publicBlobURLs, err = m.getStorageInfoPerSubscription(*AzSubscription.TenantID, *AzSubscription.SubscriptionID) if err != nil { return err } From f371bc7021ea8715bcbcfc2a539487d9a1e0ba88 Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Tue, 31 Oct 2023 19:03:14 -0700 Subject: [PATCH 31/40] Removed legacy Azure CLI flags and introduced repeatable ones --- cli/azure.go | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/cli/azure.go b/cli/azure.go index 329b738f..6834b331 100644 --- a/cli/azure.go +++ b/cli/azure.go @@ -12,7 +12,6 @@ import ( var ( AzTenantID string AzSubscription string - AzRGName string AzOutputFormat string AzOutputDirectory string AzVerbosity int @@ -224,6 +223,17 @@ func runAzNSGLinksCommand(cmd *cobra.Command, args []string) { } } +func runAzNetScanCommand(cmd *cobra.Command, args []string) { + m := azure.AzNetScanModule{ + AzClient: AzClient, + Log: internal.NewLogger("netscan"), + } + err := m.AzNetScanCommand() + if err != nil { + m.Log.Fatal(nil, err.Error()) + } +} + func azurePreRun(cmd *cobra.Command, args []string) { AzClient = internal.NewAzureClient(AzVerbosity, AzWrapTable, AzMergedTable, AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs, cmd, AzOutputFormat, AzOutputDirectory) nTenants := len(AzClient.AzTenants) @@ -244,14 +254,11 @@ func init() { AzCommands.PersistentFlags().StringVarP(&AzOutputFormat, "output", "o", "all", "[\"table\" | \"csv\" | \"all\" ]") AzCommands.PersistentFlags().StringVar(&AzOutputDirectory, "outdir", defaultOutputDir, "Output Directory ") AzCommands.PersistentFlags().IntVarP(&AzVerbosity, "verbosity", "v", 2, "1 = Print control messages only\n2 = Print control messages, module output\n3 = Print control messages, module output, and loot file output\n") - AzCommands.PersistentFlags().StringVarP(&AzTenantID, "tenant", "t", "", "Tenant name") - AzCommands.PersistentFlags().StringVarP(&AzSubscription, "subscription", "s", "", "Subscription ID or Name") - AzCommands.PersistentFlags().StringVarP(&AzRGName, "resource-group", "g", "", "Resource Group name") - AzCommands.PersistentFlags().StringSliceVar(&AzTenantRefs, "tenants", []string{}, "Tenant ID or name, repeatable") - AzCommands.PersistentFlags().StringSliceVar(&AzSubscriptionRefs, "subs", []string{}, "Subscription ID or name, repeatable") - AzCommands.PersistentFlags().StringSliceVar(&AzRGRefs, "rgs", []string{}, "Resource Group name or ID, repeatable") - AzCommands.PersistentFlags().StringSliceVar(&AzResourceRefs, "resource-id", []string{}, "Resource ID (/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}), repeatable") + AzCommands.PersistentFlags().StringSliceVarP(&AzTenantRefs, "tenant", "t", []string{}, "Tenant ID or name, repeatable") + AzCommands.PersistentFlags().StringSliceVarP(&AzSubscriptionRefs, "subscription", "s", []string{}, "Subscription ID or name, repeatable") + AzCommands.PersistentFlags().StringSliceVarP(&AzRGRefs, "resource-group", "r", []string{}, "Resource Group name or ID, repeatable") + AzCommands.PersistentFlags().StringSliceVarP(&AzResourceRefs, "resource-id", "i", []string{}, "Resource ID (/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}), repeatable") AzCommands.PersistentFlags().BoolVarP(&AzWrapTable, "wrap", "w", false, "Wrap table to fit in terminal (complicates grepping)") AzCommands.PersistentFlags().BoolVarP(&AzMergedTable, "merged-table", "m", false, "Writes a single table for all subscriptions in the tenant. Default writes a table per subscription.") From 0d3acc10066ebcbd1ce12cfb5755070cc78d497e Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Tue, 31 Oct 2023 21:43:48 -0700 Subject: [PATCH 32/40] Codespell fix --- cli/azure.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/azure.go b/cli/azure.go index 6834b331..39815dd3 100644 --- a/cli/azure.go +++ b/cli/azure.go @@ -105,7 +105,7 @@ Enumerate storage accounts for a specific subscription: AzNSGRulesCommand = &cobra.Command{ Use: "nsg-rules", Aliases: []string{}, - Short: "Enumerates azure Network Securiy Group rules", + Short: "Enumerates azure Network Security Group rules", Long: ` Enumerate Network Security Groups rules for a specific tenant: ./cloudfox az nsg-rukes --tenant TENANT_ID @@ -121,7 +121,7 @@ Enumerate rules for a specific Network Security Group: AzNSGLinksCommand = &cobra.Command{ Use: "nsg-links", Aliases: []string{}, - Short: "Enumerates azure Network Securiy Groups links", + Short: "Enumerates azure Network Security Groups links", Long: ` Enumerate Network Security Groups links for a specific tenant: ./cloudfox az nsg-links --tenant TENANT_ID From 14bcd431580ae70630a824e35b7f9a8fa1c1df18 Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Wed, 1 Nov 2023 10:24:07 -0700 Subject: [PATCH 33/40] Address potential nil pointer deref, wrong module name in NSG rules --- azure/nsg_links.go | 14 +++++++------- azure/nsg_rules.go | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/azure/nsg_links.go b/azure/nsg_links.go index fcccbc6e..327218fe 100644 --- a/azure/nsg_links.go +++ b/azure/nsg_links.go @@ -132,7 +132,7 @@ func (m *AzNSGModule) getNSGLinksData(tenantSlug string, AzSubscription *subscri } -func stringAndArrayToString(value *string, values *[]string) string { +func stringAndArrayToString(value *string, values *[]string, separator string) string { var final string if value != nil { final = *value @@ -141,9 +141,9 @@ func stringAndArrayToString(value *string, values *[]string) string { } if len(*values) > 0 { if len(final) > 0 { - final = fmt.Sprintf("%s\n%s", final, strings.Join((*values)[:], "\n")) + final = fmt.Sprintf("%s%s%s", final, separator, strings.Join((*values)[:], separator)) } else { - final = strings.Join((*values)[:], "\n") + final = strings.Join((*values)[:], separator) } } return final @@ -152,10 +152,10 @@ func stringAndArrayToString(value *string, values *[]string) string { func getSourceFromSecurityGroupRule(rule *network.SecurityRule) string { var final string - final = stringAndArrayToString(rule.SecurityRulePropertiesFormat.SourceAddressPrefix, rule.SecurityRulePropertiesFormat.SourceAddressPrefixes) + final = stringAndArrayToString(rule.SecurityRulePropertiesFormat.SourceAddressPrefix, rule.SecurityRulePropertiesFormat.SourceAddressPrefixes, "\n") if rule.SecurityRulePropertiesFormat.SourceApplicationSecurityGroups != nil { for _, app := range *rule.SecurityRulePropertiesFormat.SourceApplicationSecurityGroups { - final = fmt.Sprintf("%s\n%s", final, *app.Name) + final = fmt.Sprintf("%s\n%s", final, ptr.ToString(app.Name)) } } return final @@ -163,10 +163,10 @@ func getSourceFromSecurityGroupRule(rule *network.SecurityRule) string { func getDestinationFromSecurityGroupRule(rule *network.SecurityRule) string { var final string - final = stringAndArrayToString(rule.SecurityRulePropertiesFormat.DestinationAddressPrefix, rule.SecurityRulePropertiesFormat.DestinationAddressPrefixes) + final = stringAndArrayToString(rule.SecurityRulePropertiesFormat.DestinationAddressPrefix, rule.SecurityRulePropertiesFormat.DestinationAddressPrefixes, "\n") if rule.SecurityRulePropertiesFormat.DestinationApplicationSecurityGroups != nil { for _, app := range *rule.SecurityRulePropertiesFormat.DestinationApplicationSecurityGroups { - final = fmt.Sprintf("%s\n%s", final, *app.Name) + final = fmt.Sprintf("%s\n%s", final, ptr.ToString(app.Name)) } } return final diff --git a/azure/nsg_rules.go b/azure/nsg_rules.go index 62978aa7..d7d5d493 100644 --- a/azure/nsg_rules.go +++ b/azure/nsg_rules.go @@ -64,7 +64,7 @@ func (m *AzNSGModule) getNSGRulesData(tenantSlug string, AzSubscription *subscri // setup logging client o := internal.OutputClient{ Verbosity: m.AzClient.AzVerbosity, - CallingModule: globals.AZ_NSG_LINKS_MODULE_NAME, + CallingModule: globals.AZ_NSG_RULES_MODULE_NAME, Table: internal.TableClient{ Wrap: m.AzClient.AzWrapTable, }, @@ -91,7 +91,7 @@ func (m *AzNSGModule) getNSGRulesData(tenantSlug string, AzSubscription *subscri getSourceFromSecurityGroupRule(&securityRule), getDestinationFromSecurityGroupRule(&securityRule), stringAndArrayToString(securityRule.SecurityRulePropertiesFormat.DestinationPortRange, - securityRule.SecurityRulePropertiesFormat.DestinationPortRanges), + securityRule.SecurityRulePropertiesFormat.DestinationPortRanges, "\n"), m.colorRule(fmt.Sprintf("%v", securityRule.SecurityRulePropertiesFormat.Access)), ptr.ToString(securityRule.Description), }, From 67feb23ad23cfc1dd8e5bd543435893aa216e3d5 Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Wed, 1 Nov 2023 10:24:50 -0700 Subject: [PATCH 34/40] Allowed 'expand' param to get passed to NIC getter --- azure/vms.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/azure/vms.go b/azure/vms.go index db5828e1..d82144d2 100644 --- a/azure/vms.go +++ b/azure/vms.go @@ -317,7 +317,7 @@ func (m *AzVMsModule) getIPs(subscriptionID string, resourceGroup string, vm com if vm.VirtualMachineProperties.NetworkProfile.NetworkInterfaces != nil { for _, nicReference := range *vm.VirtualMachineProperties.NetworkProfile.NetworkInterfaces { - nic, err := m.getNICdetails(subscriptionID, resourceGroup, nicReference) + nic, err := getNICdetails(subscriptionID, resourceGroup, nicReference, "") if err != nil { return []string{err.Error()}, []string{err.Error()} } @@ -341,15 +341,15 @@ func (m *AzVMsModule) getIPs(subscriptionID string, resourceGroup string, vm com return privateIPs, publicIPs } -func (m *AzVMsModule) getNICdetails(subscriptionID string, resourceGroup string, nicReference compute.NetworkInterfaceReference) (network.Interface, error) { - return m.getNICdetailsOriginal(subscriptionID, resourceGroup, nicReference) +func getNICdetails(subscriptionID string, resourceGroup string, nicReference compute.NetworkInterfaceReference, expand string) (network.Interface, error) { + return getNICdetailsOriginal(subscriptionID, resourceGroup, nicReference, expand) } -func (m *AzVMsModule) getNICdetailsOriginal(subscriptionID string, resourceGroup string, nicReference compute.NetworkInterfaceReference) (network.Interface, error) { +func getNICdetailsOriginal(subscriptionID string, resourceGroup string, nicReference compute.NetworkInterfaceReference, expand string) (network.Interface, error) { client := internal.GetNICClient(subscriptionID) NICName := strings.Split(ptr.ToString(nicReference.ID), "/")[len(strings.Split(ptr.ToString(nicReference.ID), "/"))-1] - nic, err := client.Get(context.TODO(), resourceGroup, NICName, "") + nic, err := client.Get(context.TODO(), resourceGroup, NICName, expand) if err != nil { return network.Interface{}, fmt.Errorf("NICnotFound_%s", NICName) } From 7941a2c712ff5ae770cf073e84152944c1a90829 Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Wed, 1 Nov 2023 10:25:33 -0700 Subject: [PATCH 35/40] Missing panic call in the logger --- internal/log.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/log.go b/internal/log.go index 8261677f..368ac4a3 100644 --- a/internal/log.go +++ b/internal/log.go @@ -132,6 +132,7 @@ func (l *Logger) Errorf(categories []string, format string, params ...any) { func (l *Logger) Fatal(categories []string, text string) { l.Print(append([]string{emoji.Sprintf(":fox:cloudfox v%s :fox:", l.version), l.module}, categories...), l.Red, text) + panic(nil) } func (l *Logger) Fatalf(categories []string, format string, params ...any) { From ab6806d641af0a98dd03bef426d6aa7e71ae0c62 Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Sun, 5 Nov 2023 22:13:34 -0800 Subject: [PATCH 36/40] Added draft Azure netscan command --- azure/netscan.go | 139 ++++++++++++++++++++++++++++++++++++++++++++++ cli/azure.go | 30 ++++++++-- internal/azure.go | 15 ++++- 3 files changed, 178 insertions(+), 6 deletions(-) create mode 100644 azure/netscan.go diff --git a/azure/netscan.go b/azure/netscan.go new file mode 100644 index 00000000..dffabccd --- /dev/null +++ b/azure/netscan.go @@ -0,0 +1,139 @@ +package azure +// https://learn.microsoft.com/en-us/azure/virtual-network/network-security-group-how-it-works +// https://learn.microsoft.com/en-us/azure/virtual-network/application-security-groups +// https://learn.microsoft.com/en-us/azure/network-watcher/diagnose-vm-network-traffic-filtering-problem?toc=%2Fazure%2Fvirtual-network%2Ftoc.json +import ( + "github.com/BishopFox/cloudfox/internal" + "github.com/Azure/azure-sdk-for-go/profiles/latest/network/mgmt/network" + "github.com/Azure/go-autorest/autorest/azure" + "strings" + "context" + "fmt" +) + +type AzNetScanModule struct { + AzClient *internal.AzureClient + Log *internal.Logger +} + + +func (m *AzNetScanModule) AzNetScanCommand(sourceIP string) error { + + o := internal.OutputClient{ + Verbosity: m.AzClient.AzVerbosity, + CallingModule: "netscan", + Table: internal.TableClient{ + Wrap: m.AzClient.AzWrapTable, + }, + } + + if len(m.AzClient.AzResources) == 0 && sourceIP == "" { + return fmt.Errorf("No resource ID or source IP address supplied") + } + if sourceIP != "" { + m.Log.Warnf(nil, "Searching resources attached to IP %s, this might take a while", sourceIP) + } else { + for _, AzResource := range m.AzClient.AzResources { + if AzResource.ResourceType == "virtualMachines" { + m.Log.Infof(nil, "Starting network analysis for Virtual Machine %s", AzResource.ResourceName) + m.processVM(AzResource.SubscriptionID, AzResource.ResourceGroup, AzResource.ResourceName) + } else { + m.Log.Errorf(nil, "Resource type %s / %s not implemented", AzResource.Provider, AzResource.ResourceType) + continue + } + } + } + + o.WriteFullOutput(o.Table.TableFiles, nil) + return nil +} + + +func (m *AzNetScanModule) processVM(subscriptionID string, resourceGroupName string, VMName string) { + computeClient := internal.GetVirtualMachinesClient(subscriptionID) + vm, err := computeClient.Get(context.TODO(), resourceGroupName, VMName, "") + if err != nil { + m.Log.Errorf([]string{VMName}, "Could not fetch virtual machine details") + return + } else { + if vm.VirtualMachineProperties.NetworkProfile.NetworkInterfaces != nil { + for _, nicReference := range *vm.VirtualMachineProperties.NetworkProfile.NetworkInterfaces { + nic, err := getNICdetails(subscriptionID, resourceGroupName, nicReference, "networkSecurityGroup") + if err != nil { + m.Log.Warnf([]string{VMName}, "Could not get details of network interface %s", *nicReference.ID) + continue + } + if nic.InterfacePropertiesFormat.IPConfigurations == nil { + m.Log.Warn([]string{VMName, *nic.Name}, "Interface has no IP address configured") + continue + } + if nic.InterfacePropertiesFormat.NetworkSecurityGroup != nil { + m.Log.Infof([]string{VMName, *nic.Name}, "Linked to NSG %s", *nic.InterfacePropertiesFormat.NetworkSecurityGroup.Name) + } + //var subnet *network.Subnet + for _, ip := range *nic.InterfacePropertiesFormat.IPConfigurations { + if ip.InterfaceIPConfigurationPropertiesFormat.PrivateIPAddress == nil { + m.Log.Warn([]string{VMName, *nic.Name}, "Empty IP configuration") + continue + } + ipConfig := ip.InterfaceIPConfigurationPropertiesFormat + m.Log.Infof([]string{VMName, *nic.Name}, "Interface assigned with address %s", *ipConfig.PrivateIPAddress) + subnet := *ipConfig.Subnet + subnetRGName := strings.Split(*subnet.ID, "/")[4] + VNETName := strings.Split(*subnet.ID, "/")[8] + subnetName := strings.Split(*subnet.ID, "/")[10] + subnet, err := getSubnetDetails(subscriptionID, subnetRGName, VNETName, subnetName, "networkSecurityGroup") + if err != nil { + m.Log.Warnf([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress}, "Unable to get subnet %s details", subnetName) + continue + } + if subnet.SubnetPropertiesFormat.NetworkSecurityGroup != nil { + m.Log.Infof([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress}, "Linked to NSG %s through subnet %s", *subnet.SubnetPropertiesFormat.NetworkSecurityGroup.Name, *subnet.Name) + } + m.Log.Infof([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress}, "Subnet %s, in Virtual Network %s", subnetName, VNETName) + vnet, err := getVNET(subscriptionID, subnetRGName, VNETName, "") + if err != nil { + m.Log.Warnf([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress, *subnet.Name}, "Unable to get Virtual Network %s details", VNETName) + continue + } + m.Log.Infof([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress}, "Virtual Network %s has %s subnets as potential targets", VNETName, len(*vnet.VirtualNetworkPropertiesFormat.Subnets)) + for _, subnet := range *vnet.VirtualNetworkPropertiesFormat.Subnets { + m.Log.Infof([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress}, "Potential target : %s", *subnet.SubnetPropertiesFormat.AddressPrefix) + } + for _, peering := range *vnet.VirtualNetworkPropertiesFormat.VirtualNetworkPeerings { + remoteVnetID := *peering.VirtualNetworkPeeringPropertiesFormat.RemoteVirtualNetwork.ID + remoteVnetResource, _ := azure.ParseResourceID(remoteVnetID) + if !*peering.VirtualNetworkPeeringPropertiesFormat.AllowForwardedTraffic { + m.Log.Warnf([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress, *subnet.Name}, "Peering to Virtual Network %s is disabled", remoteVnetResource.ResourceName) + } else { + m.Log.Successf([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress, *subnet.Name}, "Peering to Virtual Network %s is active : %s", + remoteVnetResource.ResourceName, stringAndArrayToString(nil, peering.VirtualNetworkPeeringPropertiesFormat.RemoteAddressSpace.AddressPrefixes, " ")) + } + } + + } + } + } else { + m.Log.Errorf(nil, "Virtual Machine %s has no network interface", VMName) + return + } + } +} + +func getSubnetDetails(subscriptionID, resourceGroup, VNETName, subnetName, expand string) (network.Subnet, error) { + client := internal.GetSubnetsClient(subscriptionID) + subnet, err := client.Get(context.TODO(), resourceGroup, VNETName, subnetName, expand) + if err != nil { + return network.Subnet{}, err + } + return subnet, nil +} + +func getVNET(subscriptionID, resourceGroup, VNETName, expand string) (network.VirtualNetwork, error) { + client := internal.GetVNETsClient(subscriptionID) + subnet, err := client.Get(context.TODO(), resourceGroup, VNETName, expand) + if err != nil { + return network.VirtualNetwork{}, err + } + return subnet, nil +} diff --git a/cli/azure.go b/cli/azure.go index 39815dd3..11c89250 100644 --- a/cli/azure.go +++ b/cli/azure.go @@ -10,26 +10,31 @@ import ( ) var ( - AzTenantID string - AzSubscription string + // output options AzOutputFormat string AzOutputDirectory string AzVerbosity int AzWrapTable bool AzMergedTable bool + // resource selectors AzTenantRefs []string AzSubscriptionRefs []string AzRGRefs []string AzResourceRefs []string + // Shared resources for all modules AzClient *internal.AzureClient AzTenants []*subscriptions.TenantIDDescription AzSubscriptions []*subscriptions.Subscription AzRGs []*resources.Group AzResources []*az.Resource + // command specific variables + // netscan ipv4 + AzSourceIPv4 string + AzCommands = &cobra.Command{ Use: "azure", Aliases: []string{"az"}, @@ -134,6 +139,19 @@ Enumerate links for a specific Network Security Group: `, Run: runAzNSGLinksCommand, } + AzNetScanCommand = &cobra.Command{ + Use: "netscan", + Aliases: []string{}, + Short: "Enumerates internal network targets", + Long: ` +Enumerate targets with a specific resource as origin: +./cloudfox az nsg-links --resource-id /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName} + +Enumerate targets with a specific IP address as origin +./cloudfox az nsg-links --ipv4 A.B.C.D +`, + Run: runAzNetScanCommand, + } ) func runAzWhoamiCommand (cmd *cobra.Command, args []string) { @@ -228,7 +246,8 @@ func runAzNetScanCommand(cmd *cobra.Command, args []string) { AzClient: AzClient, Log: internal.NewLogger("netscan"), } - err := m.AzNetScanCommand() + + err := m.AzNetScanCommand(AzSourceIPv4) if err != nil { m.Log.Fatal(nil, err.Error()) } @@ -250,6 +269,8 @@ func init() { AzWhoamiCommand.Flags().BoolVarP(&AzWhoamiListRGsAlso, "list-rgs", "l", false, "Drill down to the resource group level") + AzNetScanCommand.Flags().StringVar(&AzSourceIPv4, "ipv4", "", "The Source IP address to run the analysis from") + // Global flags AzCommands.PersistentFlags().StringVarP(&AzOutputFormat, "output", "o", "all", "[\"table\" | \"csv\" | \"all\" ]") AzCommands.PersistentFlags().StringVar(&AzOutputDirectory, "outdir", defaultOutputDir, "Output Directory ") @@ -270,6 +291,7 @@ func init() { AzStorageCommand, AzNSGRulesCommand, AzNSGLinksCommand, - AzInventoryCommand) + AzInventoryCommand, + AzNetScanCommand) } diff --git a/internal/azure.go b/internal/azure.go index 37c5bb65..eb0b147b 100644 --- a/internal/azure.go +++ b/internal/azure.go @@ -40,7 +40,7 @@ type AzureClient struct { Log *Logger } -func (a *AzureClient) init (AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs []string, cmd *cobra.Command){ +func (a *AzureClient) init (AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs []string){ availableSubscriptions := GetSubscriptions() availableTenants := GetTenants() @@ -151,7 +151,7 @@ func NewAzureClient(AzVerbosity int, AzWrapTable, AzMergedTable bool, AzTenantRe client.AzVerbosity = AzVerbosity client.AzOutputFormat = AzOutputFormat client.AzOutputDirectory = AzOutputDirectory - client.init(AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs, cmd) + client.init(AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs) return client } @@ -285,6 +285,17 @@ func GetSubnetsClient(subscriptionID string) network.SubnetsClient { return client } +func GetVNETsClient(subscriptionID string) network.VirtualNetworksClient { + client := network.NewVirtualNetworksClient(subscriptionID) + authorizer, err := getAuthorizer(globals.AZ_RESOURCE_MANAGER_ENDPOINT) + if err != nil { + log.Fatalf("failed to get subnets client: %s", err) + } + client.Authorizer = authorizer + client.AddToUserAgent(globals.CLOUDFOX_USER_AGENT) + return client +} + func GetPublicIPClient(subscriptionID string) network.PublicIPAddressesClient { client := network.NewPublicIPAddressesClient(subscriptionID) authorizer, err := getAuthorizer(globals.AZ_RESOURCE_MANAGER_ENDPOINT) From 2086a72275732b834741a63e4f83fe87bcd29565 Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Sun, 5 Nov 2023 22:32:17 -0800 Subject: [PATCH 37/40] Azure netscan: write scan file into loot directory --- azure/netscan.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/azure/netscan.go b/azure/netscan.go index dffabccd..ebec1c58 100644 --- a/azure/netscan.go +++ b/azure/netscan.go @@ -8,7 +8,9 @@ import ( "github.com/Azure/go-autorest/autorest/azure" "strings" "context" + "path/filepath" "fmt" + "os" ) type AzNetScanModule struct { @@ -52,6 +54,7 @@ func (m *AzNetScanModule) AzNetScanCommand(sourceIP string) error { func (m *AzNetScanModule) processVM(subscriptionID string, resourceGroupName string, VMName string) { computeClient := internal.GetVirtualMachinesClient(subscriptionID) vm, err := computeClient.Get(context.TODO(), resourceGroupName, VMName, "") + targetSubnets := []string{} if err != nil { m.Log.Errorf([]string{VMName}, "Could not fetch virtual machine details") return @@ -99,6 +102,7 @@ func (m *AzNetScanModule) processVM(subscriptionID string, resourceGroupName str m.Log.Infof([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress}, "Virtual Network %s has %s subnets as potential targets", VNETName, len(*vnet.VirtualNetworkPropertiesFormat.Subnets)) for _, subnet := range *vnet.VirtualNetworkPropertiesFormat.Subnets { m.Log.Infof([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress}, "Potential target : %s", *subnet.SubnetPropertiesFormat.AddressPrefix) + targetSubnets = append(targetSubnets, *subnet.SubnetPropertiesFormat.AddressPrefix) } for _, peering := range *vnet.VirtualNetworkPropertiesFormat.VirtualNetworkPeerings { remoteVnetID := *peering.VirtualNetworkPeeringPropertiesFormat.RemoteVirtualNetwork.ID @@ -108,11 +112,15 @@ func (m *AzNetScanModule) processVM(subscriptionID string, resourceGroupName str } else { m.Log.Successf([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress, *subnet.Name}, "Peering to Virtual Network %s is active : %s", remoteVnetResource.ResourceName, stringAndArrayToString(nil, peering.VirtualNetworkPeeringPropertiesFormat.RemoteAddressSpace.AddressPrefixes, " ")) + for _, addressPrefix := range *peering.VirtualNetworkPeeringPropertiesFormat.RemoteAddressSpace.AddressPrefixes { + targetSubnets = append(targetSubnets, addressPrefix) + } } } } } + m.writeScanFile(VMName, targetSubnets) } else { m.Log.Errorf(nil, "Virtual Machine %s has no network interface", VMName) return @@ -120,6 +128,30 @@ func (m *AzNetScanModule) processVM(subscriptionID string, resourceGroupName str } } +func (m *AzNetScanModule) writeScanFile(vm string, subnets []string) error { + lootDirectory := filepath.Join(m.AzClient.AzOutputDirectory, "loot") + lootFilePath := filepath.Join(lootDirectory, fmt.Sprintf("scan-from-%s.txt", vm)) + err := os.MkdirAll(lootDirectory, os.ModePerm) + if err != nil { + return err + } + + file, err := os.Create(lootFilePath) + if err != nil { + return err + } + defer file.Close() + + for _, subnet := range subnets { + _, err := file.WriteString(fmt.Sprintf("nmap -p- --max-retries 1 -T4 -Pn -sV -sC -oA %s %s\n", strings.Replace(subnet, "/", "_", -1), subnet)) + if err != nil { + return err + } + } + m.Log.Successf(nil, "Scan file written to %s", lootFilePath) + return nil +} + func getSubnetDetails(subscriptionID, resourceGroup, VNETName, subnetName, expand string) (network.Subnet, error) { client := internal.GetSubnetsClient(subscriptionID) subnet, err := client.Get(context.TODO(), resourceGroup, VNETName, subnetName, expand) From 6c7c26a53fde40e44a5a84e103651edc8a69b9a4 Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Wed, 8 Nov 2023 16:19:44 -0800 Subject: [PATCH 38/40] Properly display ASG in NSG rules --- azure/nsg_links.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/azure/nsg_links.go b/azure/nsg_links.go index 327218fe..20b022ea 100644 --- a/azure/nsg_links.go +++ b/azure/nsg_links.go @@ -155,7 +155,8 @@ func getSourceFromSecurityGroupRule(rule *network.SecurityRule) string { final = stringAndArrayToString(rule.SecurityRulePropertiesFormat.SourceAddressPrefix, rule.SecurityRulePropertiesFormat.SourceAddressPrefixes, "\n") if rule.SecurityRulePropertiesFormat.SourceApplicationSecurityGroups != nil { for _, app := range *rule.SecurityRulePropertiesFormat.SourceApplicationSecurityGroups { - final = fmt.Sprintf("%s\n%s", final, ptr.ToString(app.Name)) + resource, _ := azure.ParseResourceID(ptr.ToString(app.ID)) + final = fmt.Sprintf("%s\n%s", final, resource.ResourceName) } } return final @@ -166,7 +167,8 @@ func getDestinationFromSecurityGroupRule(rule *network.SecurityRule) string { final = stringAndArrayToString(rule.SecurityRulePropertiesFormat.DestinationAddressPrefix, rule.SecurityRulePropertiesFormat.DestinationAddressPrefixes, "\n") if rule.SecurityRulePropertiesFormat.DestinationApplicationSecurityGroups != nil { for _, app := range *rule.SecurityRulePropertiesFormat.DestinationApplicationSecurityGroups { - final = fmt.Sprintf("%s\n%s", final, ptr.ToString(app.Name)) + resource, _ := azure.ParseResourceID(ptr.ToString(app.ID)) + final = fmt.Sprintf("%s\n%s", final, resource.ResourceName) } } return final From 8da636e294f4b6a3c4333274f93ba3c8e9d8c3a5 Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Wed, 8 Nov 2023 16:20:33 -0800 Subject: [PATCH 39/40] Detach a few VM functions from VMs module and make them shared --- azure/vms.go | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/azure/vms.go b/azure/vms.go index d82144d2..a572ab6d 100644 --- a/azure/vms.go +++ b/azure/vms.go @@ -213,9 +213,9 @@ func (m *AzVMsModule) getComputeRelevantData(sub subscriptions.Subscription, rg if vm.VirtualMachineProperties != nil && vm.OsProfile != nil { adminUsername = ptr.ToString(vm.OsProfile.AdminUsername) } - privateIPs, publicIPs := m.getIPs(ptr.ToString(sub.SubscriptionID), ptr.ToString(rg.Name), vm) + privateIPs, publicIPs := getIPs(ptr.ToString(sub.SubscriptionID), ptr.ToString(rg.Name), vm) // get userdata - vmDetails, err := m.getComputeVmInfo(subscriptionID, resourceGroupName, ptr.ToString(vm.Name)) + vmDetails, err := getComputeVmInfo(subscriptionID, resourceGroupName, ptr.ToString(vm.Name)) if err != nil { fmt.Println("error fetching vm details for vm: ", ptr.ToString(vm.Name)) } @@ -280,7 +280,7 @@ func (m *AzVMsModule) getComputeVMsPerResourceGroupOriginal(subscriptionID strin } // get vms with user-data view -func (m *AzVMsModule) getComputeVmInfo(subscriptionID string, resourceGroup string, vmName string) (compute.VirtualMachine, error) { +func getComputeVmInfo(subscriptionID string, resourceGroup string, vmName string) (compute.VirtualMachine, error) { computeClient := internal.GetVirtualMachinesClient(subscriptionID) vm, err := computeClient.Get(context.Background(), resourceGroup, vmName, compute.InstanceViewTypesUserData) if err != nil { @@ -288,6 +288,14 @@ func (m *AzVMsModule) getComputeVmInfo(subscriptionID string, resourceGroup stri } return vm, nil } +func getComputeVmInstanceView(subscriptionID string, resourceGroup string, vmName string) (compute.VirtualMachine, error) { + computeClient := internal.GetVirtualMachinesClient(subscriptionID) + vm, err := computeClient.Get(context.Background(), resourceGroup, vmName, compute.InstanceViewTypesInstanceView) + if err != nil { + return compute.VirtualMachine{}, fmt.Errorf("could not get vm %s. %s", vmName, err) + } + return vm, nil +} func (m *AzVMsModule) mockedGetComputeVMsPerResourceGroup(subscriptionID, resourceGroup string) ([]compute.VirtualMachine, error) { testFile, err := os.ReadFile(globals.VMS_TEST_FILE) @@ -312,7 +320,7 @@ func (m *AzVMsModule) mockedGetComputeVMsPerResourceGroup(subscriptionID, resour return results, nil } -func (m *AzVMsModule) getIPs(subscriptionID string, resourceGroup string, vm compute.VirtualMachine) ([]string, []string) { +func getIPs(subscriptionID string, resourceGroup string, vm compute.VirtualMachine) ([]string, []string) { var privateIPs, publicIPs []string if vm.VirtualMachineProperties.NetworkProfile.NetworkInterfaces != nil { @@ -328,7 +336,7 @@ func (m *AzVMsModule) getIPs(subscriptionID string, resourceGroup string, vm com ptr.ToString( ip.InterfaceIPConfigurationPropertiesFormat.PrivateIPAddress)) - publicIP, err := m.getPublicIP(subscriptionID, resourceGroup, ip) + publicIP, err := getPublicIP(subscriptionID, resourceGroup, ip) if err != nil { publicIPs = append(publicIPs, err.Error()) } else { @@ -377,11 +385,11 @@ func (m *AzVMsModule) mockedGetNICdetails(subscriptionID, resourceGroup string, return network.Interface{}, fmt.Errorf("NICnotFound_%s", ptr.ToString(nicReference.ID)) } -func (m *AzVMsModule) getPublicIP(subscriptionID string, resourceGroup string, ip network.InterfaceIPConfiguration) (*string, error) { - return m.getPublicIPOriginal(subscriptionID, resourceGroup, ip) +func getPublicIP(subscriptionID string, resourceGroup string, ip network.InterfaceIPConfiguration) (*string, error) { + return getPublicIPOriginal(subscriptionID, resourceGroup, ip) } -func (m *AzVMsModule) getPublicIPOriginal(subscriptionID string, resourceGroup string, ip network.InterfaceIPConfiguration) (*string, error) { +func getPublicIPOriginal(subscriptionID string, resourceGroup string, ip network.InterfaceIPConfiguration) (*string, error) { client := internal.GetPublicIPClient(subscriptionID) if ip.InterfaceIPConfigurationPropertiesFormat.PublicIPAddress == nil { return nil, fmt.Errorf("NoPublicIP") From 465f08b53068f87a91d5900dcfcd3ac067f15f30 Mon Sep 17 00:00:00 2001 From: Bastien Faure Date: Wed, 8 Nov 2023 16:22:53 -0800 Subject: [PATCH 40/40] Azure netscan: reduce list of network targets by filtering running vms, included in target subnets --- azure/netscan.go | 85 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/azure/netscan.go b/azure/netscan.go index ebec1c58..f1ff84eb 100644 --- a/azure/netscan.go +++ b/azure/netscan.go @@ -5,11 +5,14 @@ package azure import ( "github.com/BishopFox/cloudfox/internal" "github.com/Azure/azure-sdk-for-go/profiles/latest/network/mgmt/network" + "github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute" "github.com/Azure/go-autorest/autorest/azure" "strings" + "github.com/aws/smithy-go/ptr" "context" "path/filepath" "fmt" + "net" "os" ) @@ -55,6 +58,9 @@ func (m *AzNetScanModule) processVM(subscriptionID string, resourceGroupName str computeClient := internal.GetVirtualMachinesClient(subscriptionID) vm, err := computeClient.Get(context.TODO(), resourceGroupName, VMName, "") targetSubnets := []string{} + targetSubnetsAlt := make(map[string][]string) + vmSubscriptionIDs := make(map[string]bool) + vmSubscriptionIDs[subscriptionID] = true if err != nil { m.Log.Errorf([]string{VMName}, "Could not fetch virtual machine details") return @@ -103,10 +109,12 @@ func (m *AzNetScanModule) processVM(subscriptionID string, resourceGroupName str for _, subnet := range *vnet.VirtualNetworkPropertiesFormat.Subnets { m.Log.Infof([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress}, "Potential target : %s", *subnet.SubnetPropertiesFormat.AddressPrefix) targetSubnets = append(targetSubnets, *subnet.SubnetPropertiesFormat.AddressPrefix) + targetSubnetsAlt[*subnet.SubnetPropertiesFormat.AddressPrefix] = []string{} } for _, peering := range *vnet.VirtualNetworkPropertiesFormat.VirtualNetworkPeerings { remoteVnetID := *peering.VirtualNetworkPeeringPropertiesFormat.RemoteVirtualNetwork.ID remoteVnetResource, _ := azure.ParseResourceID(remoteVnetID) + vmSubscriptionIDs[remoteVnetResource.SubscriptionID] = true if !*peering.VirtualNetworkPeeringPropertiesFormat.AllowForwardedTraffic { m.Log.Warnf([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress, *subnet.Name}, "Peering to Virtual Network %s is disabled", remoteVnetResource.ResourceName) } else { @@ -114,13 +122,53 @@ func (m *AzNetScanModule) processVM(subscriptionID string, resourceGroupName str remoteVnetResource.ResourceName, stringAndArrayToString(nil, peering.VirtualNetworkPeeringPropertiesFormat.RemoteAddressSpace.AddressPrefixes, " ")) for _, addressPrefix := range *peering.VirtualNetworkPeeringPropertiesFormat.RemoteAddressSpace.AddressPrefixes { targetSubnets = append(targetSubnets, addressPrefix) + targetSubnetsAlt[addressPrefix] = []string{} } } } } } - m.writeScanFile(VMName, targetSubnets) + m.Log.Info([]string{VMName}, "Narrowing down potential target subnets with running compute instances...") + var vms []compute.VirtualMachine + for subscriptionID, _ := range vmSubscriptionIDs { + subscriptionVMs, err := getComputeVMsPerSubscription(subscriptionID) + if err != nil { + m.Log.Warnf([]string{VMName}, "Unable to enumerate VMs in subscription %s", subscriptionID) + continue + } + vms = append(vms, subscriptionVMs...) + } + for i, vm := range vms { + fmt.Printf("Processing machine %d%%\r", 100*i/len(vms)) + vmResource, _ := azure.ParseResourceID(*vm.ID) + vmDetails, err := getComputeVmInstanceView(subscriptionID, vmResource.ResourceGroup, ptr.ToString(vm.Name)) + if err != nil { + m.Log.Warnf([]string{VMName}, "Unable to fetch VM %s details", ptr.ToString(vm.Name)) + continue + } + if vmDetails.VirtualMachineProperties != nil && vmDetails.VirtualMachineProperties.InstanceView != nil { + for _, status := range *vmDetails.VirtualMachineProperties.InstanceView.Statuses { + if *status.Code == "PowerState/running" { + privateIPs, _ := getIPs(subscriptionID, vmResource.ResourceGroup, vm) + for targetSubnet, _ := range targetSubnetsAlt { + _, ipnet, _ := net.ParseCIDR(targetSubnet) + for _, privateIP := range privateIPs { + ip := net.ParseIP(privateIP) + if ipnet.Contains(ip) { + targetSubnetsAlt[targetSubnet] = append(targetSubnetsAlt[targetSubnet], privateIP) + } + } + } + break + } + } + } + } + for targetSubnet, targets := range targetSubnetsAlt { + m.Log.Infof([]string{VMName}, "%s subnet has %s addresses", targetSubnet, len(targets)) + } + m.writeScanFile(VMName, targetSubnetsAlt) } else { m.Log.Errorf(nil, "Virtual Machine %s has no network interface", VMName) return @@ -128,7 +176,27 @@ func (m *AzNetScanModule) processVM(subscriptionID string, resourceGroupName str } } -func (m *AzNetScanModule) writeScanFile(vm string, subnets []string) error { +func (m *AzNetScanModule) filterVM(vm compute.VirtualMachine) { + +} +func getComputeVMsPerSubscription(subscriptionID string) ([]compute.VirtualMachine, error) { + computeClient := internal.GetVirtualMachinesClient(subscriptionID) + var vms []compute.VirtualMachine + + for _, rg := range GetResourceGroups(subscriptionID) { + for page, err := computeClient.List(context.TODO(), ptr.ToString(rg.Name), ""); page.NotDone(); page.Next() { + if err != nil { + return nil, fmt.Errorf("could not enumerate resource group %s. %s", rg, err) + } else { + + vms = append(vms, page.Values()...) + } + } + } + return vms, nil +} + +func (m *AzNetScanModule) writeScanFile(vm string, targetSubnetsAlt map[string][]string) error { lootDirectory := filepath.Join(m.AzClient.AzOutputDirectory, "loot") lootFilePath := filepath.Join(lootDirectory, fmt.Sprintf("scan-from-%s.txt", vm)) err := os.MkdirAll(lootDirectory, os.ModePerm) @@ -142,11 +210,20 @@ func (m *AzNetScanModule) writeScanFile(vm string, subnets []string) error { } defer file.Close() - for _, subnet := range subnets { - _, err := file.WriteString(fmt.Sprintf("nmap -p- --max-retries 1 -T4 -Pn -sV -sC -oA %s %s\n", strings.Replace(subnet, "/", "_", -1), subnet)) + for targetSubnet, targets := range targetSubnetsAlt { + cleanSubnet := strings.Replace(targetSubnet, "/", "_", -1) + _, err := file.WriteString(fmt.Sprintf("nmap -p- --max-retries 1 -T4 -Pn -sV -sC -oA %s -iL %s.lst\n", cleanSubnet, cleanSubnet)) + if err != nil { + return err + } + targetsFile, err := os.Create(filepath.Join(lootDirectory, fmt.Sprintf("%s.lst", cleanSubnet))) if err != nil { return err } + defer targetsFile.Close() + for _, target := range targets { + targetsFile.WriteString(fmt.Sprintf("%s\n", target)) + } } m.Log.Successf(nil, "Scan file written to %s", lootFilePath) return nil