mirror of
https://github.com/BishopFox/cloudfox.git
synced 2026-11-08 17:07:29 -10:00
Compare commits
43
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db7091f33b | ||
|
|
465f08b530 | ||
|
|
8da636e294 | ||
|
|
6c7c26a53f | ||
|
|
2086a72275 | ||
|
|
ab6806d641 | ||
|
|
3e3608f774 | ||
|
|
7941a2c712 | ||
|
|
67feb23ad2 | ||
|
|
14bcd43158 | ||
|
|
0d3acc1006 | ||
|
|
f371bc7021 | ||
|
|
272bc0ad11 | ||
|
|
3cf38defdd | ||
|
|
6725aaaf45 | ||
|
|
b0fe4a290f | ||
|
|
20881183d1 | ||
|
|
8d989ccaf9 | ||
|
|
a91e0a891e | ||
|
|
386f3cb059 | ||
|
|
effb6a490e | ||
|
|
f6f78efd42 | ||
|
|
924fd3d89e | ||
|
|
60dbe425a5 | ||
|
|
bffa980d22 | ||
|
|
6665a9a5f0 | ||
|
|
50445263d7 | ||
|
|
ec422dcc6c | ||
|
|
416c93fdf1 | ||
|
|
9bd96cfb66 | ||
|
|
b38bf98072 | ||
|
|
7e209883fb | ||
|
|
991decf2d9 | ||
|
|
14132b3958 | ||
|
|
325b6ce892 | ||
|
|
b7cca44509 | ||
|
|
c2100cd927 | ||
|
|
71a60a6e67 | ||
|
|
04db8765a9 | ||
|
|
e5de75bd07 | ||
|
|
5b5a69a605 | ||
|
|
783524ba9c | ||
|
|
7410dab4f6 |
No files matched your search
@@ -48,8 +48,8 @@ For the full documentation please refer to our [wiki](https://github.com/BishopF
|
||||
|
||||
| Provider| CloudFox Commands |
|
||||
| - | - |
|
||||
| AWS | 33 |
|
||||
| Azure | 4 |
|
||||
| AWS | 30 |
|
||||
| Azure | 6 |
|
||||
| GCP | Support Planned |
|
||||
| Kubernetes | Support Planned |
|
||||
|
||||
@@ -149,11 +149,14 @@ Additional policy notes (as of 09/2022):
|
||||
| Azure | [inventory](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#inventory) | Display an inventory table of all resources per location. |
|
||||
| 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 |
|
||||
| Azure | [vms](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#vms) | Enumerates useful information for Compute instances in all available resource groups and subscriptions |
|
||||
|
||||
# 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)
|
||||
|
||||
+78
-74
@@ -7,107 +7,101 @@ 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"
|
||||
)
|
||||
|
||||
func AzInventoryCommand(AzTenantID, AzSubscriptionID, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error {
|
||||
type AzInventoryModule struct {
|
||||
AzClient *internal.AzureClient
|
||||
Log *internal.Logger
|
||||
}
|
||||
|
||||
|
||||
func (m *AzInventoryModule) AzInventoryCommand() error {
|
||||
o := internal.OutputClient{
|
||||
Verbosity: AzVerbosity,
|
||||
CallingModule: globals.AZ_INVENTORY_MODULE_NAME,
|
||||
Verbosity: m.AzClient.AzVerbosity,
|
||||
CallingModule: "inventory",
|
||||
Table: internal.TableClient{
|
||||
Wrap: AzWrapTable,
|
||||
Wrap: m.AzClient.AzWrapTable,
|
||||
},
|
||||
}
|
||||
|
||||
if AzTenantID != "" && AzSubscriptionID == "" {
|
||||
if len(m.AzClient.AzTenants) > 0 {
|
||||
// cloudfox azure inventory --tenant [TENANT_ID | PRIMARY_DOMAIN]
|
||||
tenantInfo := populateTenant(AzTenantID)
|
||||
for _, AzTenant := range m.AzClient.AzTenants {
|
||||
|
||||
if AzMergedTable {
|
||||
// set up table vars
|
||||
var header []string
|
||||
var body [][]string
|
||||
if m.AzClient.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,
|
||||
},
|
||||
o := internal.OutputClient{
|
||||
Verbosity: m.AzClient.AzVerbosity,
|
||||
CallingModule: "inventory",
|
||||
Table: internal.TableClient{
|
||||
Wrap: m.AzClient.AzWrapTable,
|
||||
},
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
//populate the table data
|
||||
header, body, err := m.getInventoryInfoPerTenant(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(o.CallingModule)})
|
||||
|
||||
if body != nil {
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
}
|
||||
} else {
|
||||
for _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) {
|
||||
m.runInventoryCommandForSingleSubscription(*AzTenant.DefaultDomain, &AzSubscription)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
} else {
|
||||
// ./cloudfox azure inventory --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME]
|
||||
for tenantSlug, AzSubscriptions := range m.AzClient.AzSubscriptionsAlt {
|
||||
for _, AzSubscription := range AzSubscriptions {
|
||||
m.runInventoryCommandForSingleSubscription(tenantSlug, AzSubscription)
|
||||
}
|
||||
}
|
||||
|
||||
} else if AzTenantID == "" && AzSubscriptionID != "" {
|
||||
|
||||
// ./cloudfox azure inventory --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME]
|
||||
runInventoryCommandForSingleSubscription(AzSubscriptionID, 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
|
||||
}
|
||||
|
||||
func runInventoryCommandForSingleSubscription(AzSubscription string, AzOutputDirectory string, AzVerbosity int, AzWrapTable bool, Version string) error {
|
||||
func (m *AzInventoryModule) runInventoryCommandForSingleSubscription(tenantSlug string, AzSubscription *subscriptions.Subscription) 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
|
||||
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)
|
||||
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] Gathering inventory for subscription %s\n",
|
||||
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(o.CallingModule),
|
||||
fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID))
|
||||
m.Log.Infof(nil, "Gathering inventory for subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID)
|
||||
|
||||
// populate the table data
|
||||
header, body, err = getInventoryInfoPerSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID)
|
||||
header, body, err = m.getInventoryInfoPerSubscription(tenantSlug, AzSubscription)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -126,8 +120,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(tenantSlug string, AzSubscription *subscriptions.Subscription) ([]string, [][]string, error) {
|
||||
resources, err := m.getResources(*AzSubscription.TenantID, *AzSubscription.SubscriptionID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -139,14 +133,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"}
|
||||
@@ -173,14 +177,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
|
||||
}
|
||||
@@ -223,7 +227,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
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
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/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"
|
||||
)
|
||||
|
||||
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, "")
|
||||
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
|
||||
} 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)
|
||||
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 {
|
||||
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)
|
||||
targetSubnetsAlt[addressPrefix] = []string{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := os.Create(lootFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
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
|
||||
Log *internal.Logger
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
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/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 {
|
||||
|
||||
m.getNSGData = m.getNSGLinksData
|
||||
|
||||
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))
|
||||
for _, AzTenant := range m.AzClient.AzTenants {
|
||||
for _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) {
|
||||
m.runNSGLinksCommandForSingleSubcription(*AzTenant.DefaultDomain, &AzSubscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for tenantSlug, AzSubscriptions := range m.AzClient.AzSubscriptionsAlt {
|
||||
for _, AzSubscription := range AzSubscriptions {
|
||||
m.runNSGLinksCommandForSingleSubcription(tenantSlug, AzSubscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
err = m.getNSGInfoPerSubscription(tenantSlug, AzSubscription)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
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 := 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,
|
||||
},
|
||||
}
|
||||
|
||||
var resource azure.Resource
|
||||
resource, err = azure.ParseResourceID(*networkSecurityGroup.ID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
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
|
||||
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{
|
||||
*networkSecurityGroup.Name,
|
||||
"Subnet",
|
||||
ptr.ToString(subnet.Name),
|
||||
strings.Join(addressPrefixes[:], "\n"),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
if networkSecurityGroup.NetworkInterfaces != nil {
|
||||
for _, networkInterface := range *networkSecurityGroup.NetworkInterfaces {
|
||||
tableBody = append(tableBody,
|
||||
[]string{
|
||||
*networkSecurityGroup.Name,
|
||||
"NIC",
|
||||
ptr.ToString(networkInterface.Name),
|
||||
strings.Join(internal.GetIPAddressesFromInterface(&networkInterface)[:], "\n"),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// set up table vars
|
||||
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("%s-%s", globals.AZ_NSG_LINKS_MODULE_NAME, *networkSecurityGroup.Name)})
|
||||
|
||||
if tableBody != nil {
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func stringAndArrayToString(value *string, values *[]string, separator string) string {
|
||||
var final string
|
||||
if value != nil {
|
||||
final = *value
|
||||
} else {
|
||||
final = ""
|
||||
}
|
||||
if len(*values) > 0 {
|
||||
if len(final) > 0 {
|
||||
final = fmt.Sprintf("%s%s%s", final, separator, strings.Join((*values)[:], separator))
|
||||
} else {
|
||||
final = strings.Join((*values)[:], separator)
|
||||
}
|
||||
}
|
||||
return final
|
||||
}
|
||||
|
||||
|
||||
func getSourceFromSecurityGroupRule(rule *network.SecurityRule) string {
|
||||
var final string
|
||||
final = stringAndArrayToString(rule.SecurityRulePropertiesFormat.SourceAddressPrefix, rule.SecurityRulePropertiesFormat.SourceAddressPrefixes, "\n")
|
||||
if rule.SecurityRulePropertiesFormat.SourceApplicationSecurityGroups != nil {
|
||||
for _, app := range *rule.SecurityRulePropertiesFormat.SourceApplicationSecurityGroups {
|
||||
resource, _ := azure.ParseResourceID(ptr.ToString(app.ID))
|
||||
final = fmt.Sprintf("%s\n%s", final, resource.ResourceName)
|
||||
}
|
||||
}
|
||||
return final
|
||||
}
|
||||
|
||||
func getDestinationFromSecurityGroupRule(rule *network.SecurityRule) string {
|
||||
var final string
|
||||
final = stringAndArrayToString(rule.SecurityRulePropertiesFormat.DestinationAddressPrefix, rule.SecurityRulePropertiesFormat.DestinationAddressPrefixes, "\n")
|
||||
if rule.SecurityRulePropertiesFormat.DestinationApplicationSecurityGroups != nil {
|
||||
for _, app := range *rule.SecurityRulePropertiesFormat.DestinationApplicationSecurityGroups {
|
||||
resource, _ := azure.ParseResourceID(ptr.ToString(app.ID))
|
||||
final = fmt.Sprintf("%s\n%s", final, resource.ResourceName)
|
||||
}
|
||||
}
|
||||
return final
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
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/Azure/go-autorest/autorest/azure"
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions"
|
||||
)
|
||||
|
||||
|
||||
func (m *AzNSGModule) AzNSGRulesCommand() error {
|
||||
|
||||
m.getNSGData = m.getNSGRulesData
|
||||
|
||||
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))
|
||||
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 {
|
||||
for tenantSlug, AzSubscriptions := range m.AzClient.AzSubscriptionsAlt {
|
||||
for _, AzSubscription := range AzSubscriptions {
|
||||
m.runNSGRulesCommandForSingleSubcription(tenantSlug, AzSubscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
err = m.getNSGInfoPerSubscription(tenantSlug, AzSubscription)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
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_RULES_MODULE_NAME,
|
||||
Table: internal.TableClient{
|
||||
Wrap: m.AzClient.AzWrapTable,
|
||||
},
|
||||
}
|
||||
|
||||
var resource azure.Resource
|
||||
resource, err = azure.ParseResourceID(*networkSecurityGroup.ID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
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{
|
||||
*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, "\n"),
|
||||
m.colorRule(fmt.Sprintf("%v", securityRule.SecurityRulePropertiesFormat.Access)),
|
||||
ptr.ToString(securityRule.Description),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// set up table vars
|
||||
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("%s-%s", globals.AZ_NSG_RULES_MODULE_NAME, *networkSecurityGroup.Name)})
|
||||
|
||||
if tableBody != nil {
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
+74
-69
@@ -16,82 +16,80 @@ import (
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
"github.com/fatih/color"
|
||||
"github.com/kyokomi/emoji"
|
||||
)
|
||||
|
||||
func AzRBACCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error {
|
||||
type AzRBACModule struct {
|
||||
AzClient *internal.AzureClient
|
||||
Log *internal.Logger
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
var AzSubscriptionInfo SubsriptionInfo
|
||||
|
||||
if AzTenantID != "" && AzSubscription == "" {
|
||||
if len(m.AzClient.AzTenants) > 0 {
|
||||
// cloudfox azure rbac --tenant [TENANT_ID | PRIMARY_DOMAIN]
|
||||
for _, AzTenant := range m.AzClient.AzTenants {
|
||||
|
||||
var err error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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")
|
||||
|
||||
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
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles,
|
||||
internal.TableFile{
|
||||
Header: header,
|
||||
Body: body,
|
||||
Name: fmt.Sprintf(globals.AZ_RBAC_MODULE_NAME)})
|
||||
|
||||
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")
|
||||
|
||||
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)))
|
||||
|
||||
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)})
|
||||
|
||||
} else if AzTenantID == "" && AzSubscription != "" {
|
||||
// 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.")
|
||||
// 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)
|
||||
|
||||
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{
|
||||
Header: header,
|
||||
Body: body,
|
||||
Name: fmt.Sprintf(globals.AZ_RBAC_MODULE_NAME)})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -110,7 +108,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() {
|
||||
@@ -126,6 +124,7 @@ type CloudFoxRBACclient struct {
|
||||
roleAssignments []authorization.RoleAssignment
|
||||
roleDefinitions []authorization.RoleDefinition
|
||||
AADUsers []graphrbac.User
|
||||
RBACModule *AzRBACModule
|
||||
}
|
||||
|
||||
func (c *CloudFoxRBACclient) initialize(tenantID string, subscriptionIDs []string) error {
|
||||
@@ -134,19 +133,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)
|
||||
}
|
||||
@@ -171,7 +170,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
|
||||
})
|
||||
|
||||
@@ -216,9 +215,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() {
|
||||
@@ -234,7 +235,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)
|
||||
@@ -248,10 +249,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)
|
||||
}
|
||||
@@ -269,9 +270,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() {
|
||||
@@ -287,7 +290,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 {
|
||||
@@ -300,16 +303,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)
|
||||
}
|
||||
@@ -346,9 +349,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() {
|
||||
@@ -363,7 +368,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 {
|
||||
@@ -382,8 +387,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)
|
||||
}
|
||||
|
||||
+1
-1
@@ -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)) {
|
||||
|
||||
+95
-85
@@ -9,98 +9,99 @@ 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"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
"github.com/fatih/color"
|
||||
"github.com/kyokomi/emoji"
|
||||
)
|
||||
|
||||
// Color functions
|
||||
var cyan = color.New(color.FgCyan).SprintFunc()
|
||||
|
||||
func AzStorageCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error {
|
||||
type AzStorageModule struct {
|
||||
AzClient *internal.AzureClient
|
||||
Log *internal.Logger
|
||||
}
|
||||
|
||||
func (m *AzStorageModule) AzStorageCommand() error {
|
||||
|
||||
var publicBlobURLs []string
|
||||
|
||||
if AzTenantID != "" && AzSubscription == "" {
|
||||
if len(m.AzClient.AzTenants) > 0 {
|
||||
// cloudfox azure storage --tenant [TENANT_ID | PRIMARY_DOMAIN]
|
||||
tenantInfo := populateTenant(AzTenantID)
|
||||
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,
|
||||
CallingModule: globals.AZ_STORAGE_MODULE_NAME,
|
||||
Table: internal.TableClient{
|
||||
Wrap: AzWrapTable,
|
||||
},
|
||||
}
|
||||
// set up table vars
|
||||
var header []string
|
||||
var body [][]string
|
||||
// setup logging client
|
||||
o := internal.OutputClient{
|
||||
Verbosity: m.AzClient.AzVerbosity,
|
||||
CallingModule: globals.AZ_STORAGE_MODULE_NAME,
|
||||
Table: internal.TableClient{
|
||||
Wrap: m.AzClient.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)))
|
||||
m.Log.Infof([]string{}, "Enumerating storage accounts for tenant %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(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(tenantInfo.ID))
|
||||
header, body, publicBlobURLs, err = m.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
|
||||
}
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles,
|
||||
internal.TableFile{
|
||||
Header: header,
|
||||
Body: body,
|
||||
Name: fmt.Sprintf(globals.AZ_STORAGE_MODULE_NAME)})
|
||||
|
||||
} else {
|
||||
if body != nil {
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
}
|
||||
if publicBlobURLs != nil {
|
||||
err := m.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 _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) {
|
||||
m.runStorageCommandForSingleSubscription(*AzTenant.DefaultDomain, &AzSubscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} 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 tenantSlug, AzSubscriptions := range m.AzClient.AzSubscriptionsAlt {
|
||||
for _, AzSubscription := range AzSubscriptions {
|
||||
m.runStorageCommandForSingleSubscription(tenantSlug, AzSubscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runStorageCommandForSingleSubcription(AzSubscription string, AzOutputDirectory string, AzVerbosity int, AzWrapTable bool, Version string) error {
|
||||
func (m *AzStorageModule) runStorageCommandForSingleSubscription(tenantSlug string, AzSubscription *subscriptions.Subscription) 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,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -109,19 +110,11 @@ func runStorageCommandForSingleSubcription(AzSubscription string, AzOutputDirect
|
||||
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(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 storage accounts for subscription %s\n",
|
||||
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", 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)
|
||||
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
|
||||
}
|
||||
@@ -136,7 +129,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
|
||||
}
|
||||
@@ -145,7 +138,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")
|
||||
|
||||
@@ -171,14 +164,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 {
|
||||
@@ -188,7 +181,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
|
||||
@@ -196,7 +189,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
|
||||
}
|
||||
@@ -205,20 +198,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
|
||||
|
||||
@@ -240,20 +247,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() {
|
||||
@@ -265,7 +275,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)
|
||||
@@ -284,7 +294,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},
|
||||
@@ -305,11 +315,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
|
||||
@@ -320,19 +330,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{
|
||||
@@ -352,7 +362,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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+130
-107
@@ -16,106 +16,108 @@ 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 AzVMsCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error {
|
||||
type AzVMsModule struct {
|
||||
AzClient *internal.AzureClient
|
||||
Log *internal.Logger
|
||||
}
|
||||
|
||||
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
|
||||
func (m *AzVMsModule) AzVMsCommand() error {
|
||||
|
||||
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 {
|
||||
|
||||
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 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 := internal.OutputClient{
|
||||
Verbosity: m.AzClient.AzVerbosity,
|
||||
CallingModule: globals.AZ_VMS_MODULE_NAME,
|
||||
Table: internal.TableClient{
|
||||
Wrap: m.AzClient.AzWrapTable,
|
||||
},
|
||||
}
|
||||
|
||||
if len(m.AzClient.AzTenants) > 0 {
|
||||
// cloudfox azure inventory --tenant [TENANT_ID | PRIMARY_DOMAIN]
|
||||
for _, AzTenant := range m.AzClient.AzTenants {
|
||||
|
||||
if m.AzClient.AzMergedTable {
|
||||
// set up table vars
|
||||
var header []string
|
||||
var body [][]string
|
||||
var userData string
|
||||
|
||||
o := internal.OutputClient{
|
||||
Verbosity: m.AzClient.AzVerbosity,
|
||||
CallingModule: globals.AZ_INVENTORY_MODULE_NAME,
|
||||
Table: internal.TableClient{
|
||||
Wrap: m.AzClient.AzWrapTable,
|
||||
},
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
//populate the table data
|
||||
header, body, userData = m.getVMsPerTenantID(ptr.ToString(AzTenant.TenantID))
|
||||
|
||||
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(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,
|
||||
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 _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) {
|
||||
m.runVMsCommandForSingleSubscription(*AzTenant.DefaultDomain, &AzSubscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ./cloudfox azure inventory --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME]
|
||||
for tenantSlug, AzSubscriptions := range m.AzClient.AzSubscriptionsAlt {
|
||||
for _, AzSubscription := range AzSubscriptions {
|
||||
m.runVMsCommandForSingleSubscription(tenantSlug, AzSubscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runVMsCommandForSingleSubscription(AzSubscription string, AzOutputDirectory string, AzVerbosity int, AzWrapTable bool, Version string) error {
|
||||
func (m *AzVMsModule) runVMsCommandForSingleSubscription(tenantSlug string, AzSubscription *subscriptions.Subscription) 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
|
||||
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)
|
||||
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:", 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 = getVMsPerSubscriptionID(AzSubscriptionInfo.ID)
|
||||
header, body, userData = m.getVMsPerSubscriptionID(tenantSlug, AzSubscription)
|
||||
|
||||
o.Table.TableFiles = append(o.Table.TableFiles,
|
||||
internal.TableFile{
|
||||
@@ -125,7 +127,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, tenantSlug, *AzSubscription.DisplayName, "loot")
|
||||
o.Loot.LootFiles = append(o.Loot.LootFiles,
|
||||
internal.LootFile{
|
||||
Contents: userData,
|
||||
@@ -143,59 +145,65 @@ 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
|
||||
var err error
|
||||
|
||||
for _, s := range GetSubscriptionsPerTenantID(AzTenantID) {
|
||||
for _, rg := range getResourceGroups(ptr.ToString(s.SubscriptionID)) {
|
||||
resultsHeader, b, userData, err = getComputeRelevantData(s, rg)
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return resultsHeader, resultsBody, userDataCombined
|
||||
}
|
||||
|
||||
func 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 = 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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -250,9 +258,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
|
||||
|
||||
@@ -277,8 +288,16 @@ func getComputeVmInfo(subscriptionID string, resourceGroup string, vmName string
|
||||
}
|
||||
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 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)
|
||||
@@ -306,7 +325,7 @@ func getIPs(subscriptionID string, resourceGroup string, vm compute.VirtualMachi
|
||||
|
||||
if vm.VirtualMachineProperties.NetworkProfile.NetworkInterfaces != nil {
|
||||
for _, nicReference := range *vm.VirtualMachineProperties.NetworkProfile.NetworkInterfaces {
|
||||
nic, err := getNICdetails(subscriptionID, resourceGroup, nicReference)
|
||||
nic, err := getNICdetails(subscriptionID, resourceGroup, nicReference, "")
|
||||
if err != nil {
|
||||
return []string{err.Error()}, []string{err.Error()}
|
||||
}
|
||||
@@ -330,13 +349,15 @@ func getIPs(subscriptionID string, resourceGroup string, vm compute.VirtualMachi
|
||||
return privateIPs, publicIPs
|
||||
}
|
||||
|
||||
var getNICdetails = getNICdetailsOriginal
|
||||
func getNICdetails(subscriptionID string, resourceGroup string, nicReference compute.NetworkInterfaceReference, expand string) (network.Interface, error) {
|
||||
return getNICdetailsOriginal(subscriptionID, resourceGroup, nicReference, expand)
|
||||
}
|
||||
|
||||
func 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)
|
||||
}
|
||||
@@ -344,7 +365,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)
|
||||
@@ -364,7 +385,9 @@ func mockedGetNICdetails(subscriptionID, resourceGroup string, nicReference comp
|
||||
return network.Interface{}, fmt.Errorf("NICnotFound_%s", ptr.ToString(nicReference.ID))
|
||||
}
|
||||
|
||||
var getPublicIP = getPublicIPOriginal
|
||||
func getPublicIP(subscriptionID string, resourceGroup string, ip network.InterfaceIPConfiguration) (*string, error) {
|
||||
return getPublicIPOriginal(subscriptionID, resourceGroup, ip)
|
||||
}
|
||||
|
||||
func getPublicIPOriginal(subscriptionID string, resourceGroup string, ip network.InterfaceIPConfiguration) (*string, error) {
|
||||
client := internal.GetPublicIPClient(subscriptionID)
|
||||
@@ -380,7 +403,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)
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+19
-15
@@ -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{
|
||||
@@ -46,7 +50,7 @@ func AzWhoamiCommand(AzOutputDirectory, version string, AzWrapTable bool, AzVerb
|
||||
} 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,
|
||||
@@ -64,10 +68,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 +94,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 +112,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 +167,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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
+203
-45
@@ -1,27 +1,46 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/BishopFox/cloudfox/azure"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
az "github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/spf13/cobra"
|
||||
"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
|
||||
// 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"},
|
||||
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()
|
||||
},
|
||||
@@ -34,12 +53,8 @@ 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,
|
||||
PersistentPreRun: func (cmd *cobra.Command, args []string) {},
|
||||
}
|
||||
AzInventoryCommand = &cobra.Command{
|
||||
Use: "inventory",
|
||||
@@ -52,12 +67,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",
|
||||
@@ -70,13 +80,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",
|
||||
@@ -88,12 +92,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",
|
||||
@@ -106,26 +105,182 @@ 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",
|
||||
Aliases: []string{},
|
||||
Short: "Enumerates azure Network Security Group rules",
|
||||
Long: `
|
||||
Enumerate Network Security Groups rules for a specific tenant:
|
||||
./cloudfox az nsg-rukes --tenant TENANT_ID
|
||||
|
||||
Enumerate Network Security Groups rules for a specific subscription:
|
||||
./cloudfox az nsg-rules --subscription SUBSCRIPTION_ID
|
||||
|
||||
Enumerate rules for a specific Network Security Group:
|
||||
./cloudfox az nsg-rules --nsg NSG_ID
|
||||
`,
|
||||
Run: runAzNSGRulesCommand,
|
||||
}
|
||||
AzNSGLinksCommand = &cobra.Command{
|
||||
Use: "nsg-links",
|
||||
Aliases: []string{},
|
||||
Short: "Enumerates azure Network Security 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: 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) {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
m.Log.Fatal([]string{"links"}, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func runAzNetScanCommand(cmd *cobra.Command, args []string) {
|
||||
m := azure.AzNetScanModule{
|
||||
AzClient: AzClient,
|
||||
Log: internal.NewLogger("netscan"),
|
||||
}
|
||||
|
||||
err := m.AzNetScanCommand(AzSourceIPv4)
|
||||
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)
|
||||
nSubscriptions := len(AzClient.AzSubscriptions)
|
||||
nRGs := len(AzClient.AzRGs)
|
||||
nResources := len(AzClient.AzResources)
|
||||
nTotal := nTenants + nSubscriptions + nRGs + nResources
|
||||
if nTotal == 0 {
|
||||
AzClient.Log.Fatal(nil, "No valid target supplied, stopping")
|
||||
}
|
||||
}
|
||||
|
||||
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 ")
|
||||
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().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.")
|
||||
|
||||
@@ -134,6 +289,9 @@ func init() {
|
||||
AzRBACCommand,
|
||||
AzVMsCommand,
|
||||
AzStorageCommand,
|
||||
AzInventoryCommand)
|
||||
AzNSGRulesCommand,
|
||||
AzNSGLinksCommand,
|
||||
AzInventoryCommand,
|
||||
AzNetScanCommand)
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
1.13.0-prerelease
|
||||
@@ -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_LINKS_MODULE_NAME = "nsg-links"
|
||||
const AZ_NSG_RULES_MODULE_NAME = "nsg-rules"
|
||||
|
||||
// Microsoft endpoints
|
||||
const AZ_RESOURCE_MANAGER_ENDPOINT = "https://management.azure.com/"
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
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 version string
|
||||
@@ -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,146 @@ 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/spf13/cobra"
|
||||
)
|
||||
|
||||
type AzureClient struct {
|
||||
AzVerbosity int
|
||||
AzWrapTable bool
|
||||
AzMergedTable bool
|
||||
|
||||
Version string
|
||||
AzOutputFormat string
|
||||
AzOutputDirectory string
|
||||
|
||||
AzTenants []*subscriptions.TenantIDDescription
|
||||
AzSubscriptions []*subscriptions.Subscription
|
||||
AzSubscriptionsAlt map[string][]*subscriptions.Subscription
|
||||
AzRGs []*resources.Group
|
||||
AzResources []*azure.Resource
|
||||
|
||||
Log *Logger
|
||||
}
|
||||
|
||||
func (a *AzureClient) init (AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs []string){
|
||||
availableSubscriptions := GetSubscriptions()
|
||||
availableTenants := GetTenants()
|
||||
|
||||
// resource identifiers were submitted on the CLI, running modules on them only
|
||||
if len(AzResourceRefs) > 0 {
|
||||
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
|
||||
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 {
|
||||
a.Log.Errorf(nil, "Invalid resource identifier : %s", 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
|
||||
}
|
||||
}
|
||||
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
|
||||
a.Log.Info(nil, "Azure subscriptions submitted, skipping submitted tenants")
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
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)
|
||||
for _, AzSubscriptionRef := range AzSubscriptionRefs {
|
||||
for _, subscription := range availableSubscriptions {
|
||||
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
|
||||
}
|
||||
}
|
||||
a.Log.Warnf(nil, "Subscription %s not accessible with active CLI credentials, removing from targets", 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
|
||||
}
|
||||
}
|
||||
a.Log.Warnf(nil, "Tenant %s not accessible with active CLI credentials, removing from targets", AzTenantRef)
|
||||
FOUND_TENANT:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
client.AzVerbosity = AzVerbosity
|
||||
client.AzOutputFormat = AzOutputFormat
|
||||
client.AzOutputDirectory = AzOutputDirectory
|
||||
client.init(AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs)
|
||||
return client
|
||||
}
|
||||
|
||||
|
||||
func getAuthorizer(endpoint string) (autorest.Authorizer, error) {
|
||||
auth, err := auth.NewAuthorizerFromCLIWithResource(endpoint)
|
||||
if err != nil {
|
||||
@@ -126,6 +263,39 @@ 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 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)
|
||||
@@ -171,3 +341,57 @@ 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
+109
@@ -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,108 @@ 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)
|
||||
panic(nil)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
Reference in new issue
Block a user