Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
4 changes: 4 additions & 0 deletions cmd/list-management-groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,14 @@ func listManagementGroups(ctx context.Context, client client.AzureClient) <-chan
defer panicrecovery.PanicRecovery()
defer close(out)
count := 0
homeTenantId := client.TenantInfo().TenantId
for item := range client.ListAzureManagementGroups(ctx, "") {
if item.Error != nil {
log.Info("warning: unable to process azure management groups; either the organization has no management groups or azurehound does not have the reader role on the root management group.")
return
} else if item.Ok.Properties.TenantId != homeTenantId {
log.V(2).Info("skipping management group from foreign tenant", "name", item.Ok.Name, "tenantId", item.Ok.Properties.TenantId, "homeTenantId", homeTenantId)
continue
} else if len(config.AzMgmtGroupId.Value().([]string)) == 0 || contains(config.AzMgmtGroupId.Value().([]string), item.Ok.Name) {
log.V(2).Info("found management group", "name", item.Ok.Name)
count++
Expand Down
91 changes: 89 additions & 2 deletions cmd/list-management-groups_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

"github.com/bloodhoundad/azurehound/v2/client"
"github.com/bloodhoundad/azurehound/v2/client/mocks"
"github.com/bloodhoundad/azurehound/v2/models"
"github.com/bloodhoundad/azurehound/v2/models/azure"
"go.uber.org/mock/gomock"
)
Expand All @@ -37,21 +38,27 @@ func TestListManagementGroups(t *testing.T) {
defer ctrl.Finish()
ctx := context.Background()

homeTenantId := "home-tenant-id"
mockClient := mocks.NewMockAzureClient(ctrl)
mockChannel := make(chan client.AzureResult[azure.ManagementGroup])
mockError := fmt.Errorf("I'm an error")
mockClient.EXPECT().TenantInfo().Return(azure.Tenant{TenantId: homeTenantId}).AnyTimes()
mockClient.EXPECT().ListAzureManagementGroups(gomock.Any(), gomock.Any()).Return(mockChannel)

go func() {
defer close(mockChannel)
mockChannel <- client.AzureResult[azure.ManagementGroup]{
Ok: azure.ManagementGroup{},
Ok: azure.ManagementGroup{
Properties: azure.ManagementGroupProperties{TenantId: homeTenantId},
},
}
mockChannel <- client.AzureResult[azure.ManagementGroup]{
Error: mockError,
}
mockChannel <- client.AzureResult[azure.ManagementGroup]{
Ok: azure.ManagementGroup{},
Ok: azure.ManagementGroup{
Properties: azure.ManagementGroupProperties{TenantId: homeTenantId},
},
}
}()

Expand All @@ -65,3 +72,83 @@ func TestListManagementGroups(t *testing.T) {
t.Error("expected channel to close from an error result but it did not")
}
}

func TestListManagementGroups_FiltersForeignTenants(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()

const (
homeTenantId = "home-tenant-aaaa-bbbb-cccc"
foreignTenantId = "foreign-tenant-dddd-eeee-ffff"
)

mockClient := mocks.NewMockAzureClient(ctrl)
mockChannel := make(chan client.AzureResult[azure.ManagementGroup])
mockClient.EXPECT().TenantInfo().Return(azure.Tenant{TenantId: homeTenantId}).AnyTimes()
mockClient.EXPECT().ListAzureManagementGroups(gomock.Any(), gomock.Any()).Return(mockChannel)

go func() {
defer close(mockChannel)
// Management group belonging to the home tenant — should be collected
mockChannel <- client.AzureResult[azure.ManagementGroup]{
Ok: azure.ManagementGroup{
Entity: azure.Entity{Id: "/providers/Microsoft.Management/managementGroups/HomeMG"},
Name: "HomeMG",
Properties: azure.ManagementGroupProperties{
TenantId: homeTenantId,
DisplayName: "Home Management Group",
},
},
}
// Management group belonging to a foreign tenant — should be filtered out
mockChannel <- client.AzureResult[azure.ManagementGroup]{
Ok: azure.ManagementGroup{
Entity: azure.Entity{Id: "/providers/Microsoft.Management/managementGroups/ForeignMG"},
Name: "ForeignMG",
Properties: azure.ManagementGroupProperties{
TenantId: foreignTenantId,
DisplayName: "Foreign Management Group",
},
},
}
// Another home tenant management group — should be collected
mockChannel <- client.AzureResult[azure.ManagementGroup]{
Ok: azure.ManagementGroup{
Entity: azure.Entity{Id: "/providers/Microsoft.Management/managementGroups/HomeMG2"},
Name: "HomeMG2",
Properties: azure.ManagementGroupProperties{
TenantId: homeTenantId,
DisplayName: "Home Management Group 2",
},
},
}
}()

channel := listManagementGroups(ctx, mockClient)

var results []models.ManagementGroup
for item := range channel {
wrapper := item.(AzureWrapper)
mg := wrapper.Data.(models.ManagementGroup)
results = append(results, mg)
}

if len(results) != 2 {
t.Fatalf("expected 2 management groups (home tenant only), got %d", len(results))
}

for _, mg := range results {
if mg.TenantId != homeTenantId {
t.Errorf("expected all management groups to have tenantId %q, got %q (name: %s)",
homeTenantId, mg.TenantId, mg.Name)
}
}

if results[0].Name != "HomeMG" {
t.Errorf("expected first result to be HomeMG, got %s", results[0].Name)
}
if results[1].Name != "HomeMG2" {
t.Errorf("expected second result to be HomeMG2, got %s", results[1].Name)
}
}
Loading