Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

General improvements and Azure rework #60

Open
wants to merge 43 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
7410dab
Added Azure nsg-links command to dump NSG attachments
bishopfaure Oct 29, 2023
783524b
Added skeleton for Azure nsg-rules command
bishopfaure Oct 29, 2023
5b5a69a
Migrated AWS CLI structure into Azure CLI
bishopfaure Oct 29, 2023
e5de75b
Started to prepare new resource selection logic for the Azure subcommand
bishopfaure Oct 29, 2023
04db876
Implemented AzureClient class that autopopulates for all subcommands,…
bishopfaure Oct 29, 2023
71a60a6
Migrated Azure Inventory command to new format
bishopfaure Oct 29, 2023
c2100cd
Migrated Azure VMs command to new format and fixed tenant reference b…
bishopfaure Oct 29, 2023
b7cca44
Migrated Azure RBAC subcommand to new format
bishopfaure Oct 29, 2023
325b6ce
Migrated Azure NSG commands to new format
bishopfaure Oct 29, 2023
14132b3
Migrated Azure storage command to new format
bishopfaure Oct 29, 2023
991decf
Code cleanup in Azure modules
bishopfaure Oct 29, 2023
7e20988
Fix wrong reference to resource ID in Azure RBAC and VMs modules
bishopfaure Oct 30, 2023
b38bf98
Migrated the Azure Inventory command to a fully modular structure, im…
bishopfaure Oct 30, 2023
9bd96cf
Skip PreRun when running Azure Whoami module
bishopfaure Oct 30, 2023
416c93f
Skip PreRun when running Azure Whoami module, but better
bishopfaure Oct 30, 2023
ec422dc
Migrated the Azure VMs command to a fully modular structure, implemen…
bishopfaure Oct 30, 2023
5044526
Migrated the Azure storage command to a fully modular structure, impl…
bishopfaure Oct 30, 2023
6665a9a
Migrated the Azure RBAC command to a fully modular structure
bishopfaure Oct 30, 2023
bffa980
Added the two new Azure nsg-links and nsg-rules commands, added a map…
bishopfaure Oct 31, 2023
60dbe42
Cosmetic fixes in the NSG modules
bishopfaure Oct 31, 2023
924fd3d
Merge remote-tracking branch 'origin/main' into general-improvements_…
bishopfaure Oct 31, 2023
f6f78ef
Moved version information to its own file
bishopfaure Oct 31, 2023
effb6a4
Moved version information to a file and leverage go:embed
bishopfaure Oct 31, 2023
386f3cb
Implemented custom logger usable by any module and cloud provider
bishopfaure Oct 31, 2023
a91e0a8
Migrated Azure storage module to use the new logger, as well as the A…
bishopfaure Oct 31, 2023
8d989cc
Migrature Azure inventory module to new logger and eliminated legacy …
bishopfaure Oct 31, 2023
2088118
Migrated Azure NSG modules to new logger and eliminated legacy tenant…
bishopfaure Nov 1, 2023
b0fe4a2
Migrated all Azure modules to new logging system, disabled legacy sub…
bishopfaure Nov 1, 2023
6725aaa
Update README with new Azure commands
bishopfaure Nov 1, 2023
3cf38de
Fixed wrong output directory for Azure whoami module
bishopfaure Nov 1, 2023
272bc0a
Removed additional legacy code from the Azure Storage module
bishopfaure Nov 1, 2023
f371bc7
Removed legacy Azure CLI flags and introduced repeatable ones
bishopfaure Nov 1, 2023
0d3acc1
Codespell fix
bishopfaure Nov 1, 2023
14bcd43
Address potential nil pointer deref, wrong module name in NSG rules
bishopfaure Nov 1, 2023
67feb23
Allowed 'expand' param to get passed to NIC getter
bishopfaure Nov 1, 2023
7941a2c
Missing panic call in the logger
bishopfaure Nov 1, 2023
3e3608f
Merge branch 'general-improvements_azure-commands' of ssh://bf.github…
bishopfaure Nov 1, 2023
ab6806d
Added draft Azure netscan command
bishopfaure Nov 6, 2023
2086a72
Azure netscan: write scan file into loot directory
bishopfaure Nov 6, 2023
6c7c26a
Properly display ASG in NSG rules
bishopfaure Nov 9, 2023
8da636e
Detach a few VM functions from VMs module and make them shared
bishopfaure Nov 9, 2023
465f08b
Azure netscan: reduce list of network targets by filtering running vm…
bishopfaure Nov 9, 2023
db7091f
Merge branch 'main' into general-improvements_azure-commands
dbravo-bishopfox Jan 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

Expand Down Expand Up @@ -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)
Expand Down
158 changes: 81 additions & 77 deletions azure/inventory.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

if AzMergedTable {
// set up table vars
var header []string
var body [][]string

o := internal.OutputClient{
Verbosity: AzVerbosity,
CallingModule: globals.AZ_INVENTORY_MODULE_NAME,
Table: internal.TableClient{
Wrap: AzWrapTable,
},
}

fmt.Printf(
"[%s][%s] Gathering inventory for subscription %s\n",
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(o.CallingModule),
fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID)))

o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain)
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level")

//populate the table data
header, body, err := getInventoryInfoPerTenant(ptr.ToString(tenantInfo.ID))
if err != nil {
return err
}
o.Table.TableFiles = append(o.Table.TableFiles,
internal.TableFile{
Header: header,
Body: body,
Name: fmt.Sprintf(o.CallingModule)})

if body != nil {
o.WriteFullOutput(o.Table.TableFiles, nil)
for _, AzTenant := range m.AzClient.AzTenants {

if m.AzClient.AzMergedTable {
// set up table vars
var header []string
var body [][]string

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)
}
}
} 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)

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))
o.PrefixIdentifier = *AzSubscription.DisplayName
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName)

m.Log.Infof(nil, "Gathering inventory for subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID)

// populate the table data
header, body, err = getInventoryInfoPerSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID)
header, body, err = m.getInventoryInfoPerSubscription(tenantSlug, AzSubscription)
if err != nil {
return err
}
Expand All @@ -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
}
Expand All @@ -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"}
Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand Down
Loading