diff --git a/cmd/api/src/analysis/ad/post.go b/cmd/api/src/analysis/ad/post.go index 972dee5e8c..1c61b4f987 100644 --- a/cmd/api/src/analysis/ad/post.go +++ b/cmd/api/src/analysis/ad/post.go @@ -18,6 +18,7 @@ package ad import ( "context" + "github.com/specterops/bloodhound/graphschema/azure" "github.com/specterops/bloodhound/analysis" adAnalysis "github.com/specterops/bloodhound/analysis/ad" @@ -27,7 +28,7 @@ import ( func Post(ctx context.Context, db graph.Database, adcsEnabled bool, citrixEnabled bool) (*analysis.AtomicPostProcessingStats, error) { aggregateStats := analysis.NewAtomicPostProcessingStats() - if stats, err := analysis.DeleteTransitEdges(ctx, db, ad.Entity, ad.Entity, adAnalysis.PostProcessedRelationships()...); err != nil { + if stats, err := analysis.DeleteTransitEdges(ctx, db, graph.Kinds{ad.Entity, azure.Entity}, adAnalysis.PostProcessedRelationships()...); err != nil { return &aggregateStats, err } else if groupExpansions, err := adAnalysis.ExpandAllRDPLocalGroups(ctx, db); err != nil { return &aggregateStats, err diff --git a/cmd/api/src/analysis/analysis_integration_test.go b/cmd/api/src/analysis/analysis_integration_test.go index e1f7d3a7fa..a765ee2a39 100644 --- a/cmd/api/src/analysis/analysis_integration_test.go +++ b/cmd/api/src/analysis/analysis_integration_test.go @@ -48,7 +48,7 @@ func TestFetchRDPEnsureNoDescent(t *testing.T) { // We should expect all groups that have the RIL incoming privilege to the computer require.Equal(t, 1, int(rdpEnabledEntityIDBitmap.Cardinality())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDPB.RDPDomainUsersGroup.ID.Uint32())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDPB.RDPDomainUsersGroup.ID.Uint64())) return nil })) @@ -72,21 +72,21 @@ func TestFetchRemoteDesktopUsersBitmapForComputer(t *testing.T) { // We should expect all entities that have the RIL incoming privilege to the computer require.Equal(t, 7, int(rdpEnabledEntityIDBitmap.Cardinality())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DillonUser.ID.Uint32())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.IrshadUser.ID.Uint32())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.UliUser.ID.Uint32())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.EliUser.ID.Uint32())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupA.ID.Uint32())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupB.ID.Uint32())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.RohanUser.ID.Uint32())) - - require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupC.ID.Uint32())) - require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupD.ID.Uint32())) - require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupE.ID.Uint32())) - require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.RDPDomainUsersGroup.ID.Uint32())) - require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.AlyxUser.ID.Uint32())) - require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.AndyUser.ID.Uint32())) - require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.JohnUser.ID.Uint32())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DillonUser.ID.Uint64())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.IrshadUser.ID.Uint64())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.UliUser.ID.Uint64())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.EliUser.ID.Uint64())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupA.ID.Uint64())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupB.ID.Uint64())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.RohanUser.ID.Uint64())) + + require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupC.ID.Uint64())) + require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupD.ID.Uint64())) + require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupE.ID.Uint64())) + require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.RDPDomainUsersGroup.ID.Uint64())) + require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.AlyxUser.ID.Uint64())) + require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.AndyUser.ID.Uint64())) + require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.JohnUser.ID.Uint64())) return nil })) @@ -96,21 +96,21 @@ func TestFetchRemoteDesktopUsersBitmapForComputer(t *testing.T) { rdpEnabledEntityIDBitmap, err := analysis.FetchRemoteDesktopUsersBitmapForComputer(tx, harness.RDP.Computer.ID, groupExpansions, false) require.Nil(t, err) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.IrshadUser.ID.Uint32())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.UliUser.ID.Uint32())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.EliUser.ID.Uint32())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupA.ID.Uint32())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupB.ID.Uint32())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupC.ID.Uint32())) - - require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupD.ID.Uint32())) - require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupE.ID.Uint32())) - require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.RDPDomainUsersGroup.ID.Uint32())) - require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.AlyxUser.ID.Uint32())) - require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DillonUser.ID.Uint32())) - require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.AndyUser.ID.Uint32())) - require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.RohanUser.ID.Uint32())) - require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.JohnUser.ID.Uint32())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.IrshadUser.ID.Uint64())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.UliUser.ID.Uint64())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.EliUser.ID.Uint64())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupA.ID.Uint64())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupB.ID.Uint64())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupC.ID.Uint64())) + + require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupD.ID.Uint64())) + require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupE.ID.Uint64())) + require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.RDPDomainUsersGroup.ID.Uint64())) + require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.AlyxUser.ID.Uint64())) + require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DillonUser.ID.Uint64())) + require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.AndyUser.ID.Uint64())) + require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.RohanUser.ID.Uint64())) + require.False(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.JohnUser.ID.Uint64())) return nil })) @@ -132,12 +132,12 @@ func TestFetchRemoteDesktopUsersBitmapForComputer(t *testing.T) { require.Equal(t, 6, int(rdpEnabledEntityIDBitmap.Cardinality())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupC.ID.Uint32())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.IrshadUser.ID.Uint32())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.UliUser.ID.Uint32())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupB.ID.Uint32())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.EliUser.ID.Uint32())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupA.ID.Uint32())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupC.ID.Uint64())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.IrshadUser.ID.Uint64())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.UliUser.ID.Uint64())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupB.ID.Uint64())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.EliUser.ID.Uint64())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDP.DomainGroupA.ID.Uint64())) return nil })) @@ -161,10 +161,10 @@ func TestFetchRDPEntityBitmapForComputer(t *testing.T) { // We should expect the intersection of members of `Direct Access Users`, with entities that have the RIL privilege to the computer require.Equal(t, 4, int(rdpEnabledEntityIDBitmap.Cardinality())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDPHarnessWithCitrix.UliUser.ID.Uint32())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDPHarnessWithCitrix.IrshadUser.ID.Uint32())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDPHarnessWithCitrix.DillonUser.ID.Uint32())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDPHarnessWithCitrix.RohanUser.ID.Uint32())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDPHarnessWithCitrix.UliUser.ID.Uint64())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDPHarnessWithCitrix.IrshadUser.ID.Uint64())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDPHarnessWithCitrix.DillonUser.ID.Uint64())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDPHarnessWithCitrix.RohanUser.ID.Uint64())) return nil })) @@ -186,9 +186,9 @@ func TestFetchRDPEntityBitmapForComputer(t *testing.T) { // We should expect the intersection of members of `Direct Access Users,` with entities that are first degree members of the `Remote Desktop Users` group require.Equal(t, 3, int(rdpEnabledEntityIDBitmap.Cardinality())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDPHarnessWithCitrix.DomainGroupC.ID.Uint32())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDPHarnessWithCitrix.IrshadUser.ID.Uint32())) - require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDPHarnessWithCitrix.UliUser.ID.Uint32())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDPHarnessWithCitrix.DomainGroupC.ID.Uint64())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDPHarnessWithCitrix.IrshadUser.ID.Uint64())) + require.True(t, rdpEnabledEntityIDBitmap.Contains(harness.RDPHarnessWithCitrix.UliUser.ID.Uint64())) return nil })) diff --git a/cmd/api/src/analysis/azure/post.go b/cmd/api/src/analysis/azure/post.go index f3f30f031a..cb6ab40129 100644 --- a/cmd/api/src/analysis/azure/post.go +++ b/cmd/api/src/analysis/azure/post.go @@ -18,6 +18,7 @@ package azure import ( "context" + "github.com/specterops/bloodhound/graphschema/ad" "github.com/specterops/bloodhound/analysis" azureAnalysis "github.com/specterops/bloodhound/analysis/azure" @@ -28,7 +29,7 @@ import ( func Post(ctx context.Context, db graph.Database) (*analysis.AtomicPostProcessingStats, error) { aggregateStats := analysis.NewAtomicPostProcessingStats() - if stats, err := analysis.DeleteTransitEdges(ctx, db, azure.Entity, azure.Entity, azureAnalysis.AzurePostProcessedRelationships()...); err != nil { + if stats, err := analysis.DeleteTransitEdges(ctx, db, graph.Kinds{ad.Entity, azure.Entity}, azureAnalysis.AzurePostProcessedRelationships()...); err != nil { return &aggregateStats, err } else if userRoleStats, err := azureAnalysis.UserRoleAssignments(ctx, db); err != nil { return &aggregateStats, err diff --git a/cmd/api/src/analysis/hybrid/hybrid_integration_test.go b/cmd/api/src/analysis/hybrid/hybrid_integration_test.go index d44eba889d..448f51226b 100644 --- a/cmd/api/src/analysis/hybrid/hybrid_integration_test.go +++ b/cmd/api/src/analysis/hybrid/hybrid_integration_test.go @@ -14,6 +14,9 @@ // // SPDX-License-Identifier: Apache-2.0 +//go:build integration +// +build integration + package hybrid import ( diff --git a/cmd/api/src/analysis/membership_integration_test.go b/cmd/api/src/analysis/membership_integration_test.go index 6ab9ff3890..6a4b05076f 100644 --- a/cmd/api/src/analysis/membership_integration_test.go +++ b/cmd/api/src/analysis/membership_integration_test.go @@ -84,10 +84,10 @@ func TestResolveAllGroupMemberships(t *testing.T) { test.RequireNilErr(t, err) - require.Equal(t, 3, int(memberships.Cardinality(harness.RDP.DomainGroupA.ID.Uint32()).Cardinality())) - require.Equal(t, 1, int(memberships.Cardinality(harness.RDP.DomainGroupB.ID.Uint32()).Cardinality())) - require.Equal(t, 1, int(memberships.Cardinality(harness.RDP.DomainGroupC.ID.Uint32()).Cardinality())) - require.Equal(t, 1, int(memberships.Cardinality(harness.RDP.DomainGroupD.ID.Uint32()).Cardinality())) - require.Equal(t, 2, int(memberships.Cardinality(harness.RDP.DomainGroupE.ID.Uint32()).Cardinality())) + require.Equal(t, 3, int(memberships.Cardinality(harness.RDP.DomainGroupA.ID.Uint64()).Cardinality())) + require.Equal(t, 1, int(memberships.Cardinality(harness.RDP.DomainGroupB.ID.Uint64()).Cardinality())) + require.Equal(t, 1, int(memberships.Cardinality(harness.RDP.DomainGroupC.ID.Uint64()).Cardinality())) + require.Equal(t, 1, int(memberships.Cardinality(harness.RDP.DomainGroupD.ID.Uint64()).Cardinality())) + require.Equal(t, 2, int(memberships.Cardinality(harness.RDP.DomainGroupE.ID.Uint64()).Cardinality())) }) } diff --git a/cmd/api/src/analysis/post_integration_test.go b/cmd/api/src/analysis/post_integration_test.go index c0d633b784..39490b1d67 100644 --- a/cmd/api/src/analysis/post_integration_test.go +++ b/cmd/api/src/analysis/post_integration_test.go @@ -89,7 +89,7 @@ func TestCrossProduct(t *testing.T) { require.Nil(t, err) domainsid, _ := harness.ShortcutHarness.Group3.Properties.Get(ad.DomainSID.String()).String() results := ad2.CalculateCrossProductNodeSets(tx, domainsid, groupExpansions, firstSet, secondSet) - require.Truef(t, results.Contains(harness.ShortcutHarness.Group3.ID.Uint32()), "missing id %d", harness.ShortcutHarness.Group3.ID.Uint32()) + require.Truef(t, results.Contains(harness.ShortcutHarness.Group3.ID.Uint64()), "missing id %d", harness.ShortcutHarness.Group3.ID.Uint64()) }) } @@ -105,7 +105,7 @@ func TestCrossProductAuthUsers(t *testing.T) { require.Nil(t, err) domainsid, _ := harness.ShortcutHarnessAuthUsers.Group3.Properties.Get(ad.DomainSID.String()).String() results := ad2.CalculateCrossProductNodeSets(tx, domainsid, groupExpansions, firstSet, secondSet) - require.True(t, results.Contains(harness.ShortcutHarnessAuthUsers.Group2.ID.Uint32())) + require.True(t, results.Contains(harness.ShortcutHarnessAuthUsers.Group2.ID.Uint64())) }) } @@ -121,7 +121,7 @@ func TestCrossProductEveryone(t *testing.T) { require.Nil(t, err) domainsid, _ := harness.ShortcutHarnessEveryone.Group3.Properties.Get(ad.DomainSID.String()).String() results := ad2.CalculateCrossProductNodeSets(tx, domainsid, groupExpansions, firstSet, secondSet) - require.True(t, results.Contains(harness.ShortcutHarnessEveryone.Group2.ID.Uint32())) + require.True(t, results.Contains(harness.ShortcutHarnessEveryone.Group2.ID.Uint64())) }) } @@ -137,7 +137,7 @@ func TestCrossProductEveryone2(t *testing.T) { require.Nil(t, err) domainsid, _ := harness.ShortcutHarnessEveryone2.Group3.Properties.Get(ad.DomainSID.String()).String() results := ad2.CalculateCrossProductNodeSets(tx, domainsid, groupExpansions, firstSet, secondSet) - require.True(t, results.Contains(harness.ShortcutHarnessEveryone2.Group1.ID.Uint32())) - require.True(t, results.Contains(harness.ShortcutHarnessEveryone2.Group2.ID.Uint32())) + require.True(t, results.Contains(harness.ShortcutHarnessEveryone2.Group1.ID.Uint64())) + require.True(t, results.Contains(harness.ShortcutHarnessEveryone2.Group2.ID.Uint64())) }) } diff --git a/cmd/api/src/database/datapipestatus_test.go b/cmd/api/src/database/datapipestatus_integration_test.go similarity index 97% rename from cmd/api/src/database/datapipestatus_test.go rename to cmd/api/src/database/datapipestatus_integration_test.go index 5ce3c71d67..4223e51896 100644 --- a/cmd/api/src/database/datapipestatus_test.go +++ b/cmd/api/src/database/datapipestatus_integration_test.go @@ -14,6 +14,9 @@ // // SPDX-License-Identifier: Apache-2.0 +//go:build integration +// +build integration + package database_test import ( diff --git a/cmd/api/src/database/parameters_test.go b/cmd/api/src/database/parameters_test.go index 2ed510b77b..93a8ae4743 100644 --- a/cmd/api/src/database/parameters_test.go +++ b/cmd/api/src/database/parameters_test.go @@ -14,6 +14,9 @@ // // SPDX-License-Identifier: Apache-2.0 +//go:build integration +// +build integration + package database_test import ( diff --git a/cmd/api/src/model/appcfg/parameter_test.go b/cmd/api/src/model/appcfg/parameter_test.go index 0abd42440e..105363ad68 100644 --- a/cmd/api/src/model/appcfg/parameter_test.go +++ b/cmd/api/src/model/appcfg/parameter_test.go @@ -14,6 +14,9 @@ // // SPDX-License-Identifier: Apache-2.0 +//go:build integration +// +build integration + package appcfg_test import ( diff --git a/packages/go/analysis/ad/ad.go b/packages/go/analysis/ad/ad.go index 5efc4609f7..b4d0b2e7a4 100644 --- a/packages/go/analysis/ad/ad.go +++ b/packages/go/analysis/ad/ad.go @@ -300,26 +300,26 @@ func createOrUpdateWellKnownLink(tx graph.Transaction, startNode *graph.Node, en // CalculateCrossProductNodeSets finds the intersection of the given sets of nodes. // See CalculateCrossProductNodeSetsDoc.md for explaination of the specialGroups (Authenticated Users and Everyone) and why we treat them the way we do -func CalculateCrossProductNodeSets(tx graph.Transaction, domainsid string, groupExpansions impact.PathAggregator, nodeSlices ...[]*graph.Node) cardinality.Duplex[uint32] { +func CalculateCrossProductNodeSets(tx graph.Transaction, domainsid string, groupExpansions impact.PathAggregator, nodeSlices ...[]*graph.Node) cardinality.Duplex[uint64] { if len(nodeSlices) < 2 { log.Errorf("Cross products require at least 2 nodesets") - return cardinality.NewBitmap32() + return cardinality.NewBitmap64() } //The intention is that the node sets being passed into this function contain all the first degree principals for control var ( //Temporary storage for first degree and unrolled sets without auth users/everyone - firstDegreeSets []cardinality.Duplex[uint32] - unrolledSets []cardinality.Duplex[uint32] + firstDegreeSets []cardinality.Duplex[uint64] + unrolledSets []cardinality.Duplex[uint64] //This is the set we use as a reference set to check against checkset - unrolledRefSet = cardinality.NewBitmap32() + unrolledRefSet = cardinality.NewBitmap64() //This is the set we use to aggregate multiple sets together it should have all the valid principals from all other sets at this point - checkSet = cardinality.NewBitmap32() + checkSet = cardinality.NewBitmap64() //This is our set of entities that have the complete cross product of permissions - resultEntities = cardinality.NewBitmap32() + resultEntities = cardinality.NewBitmap64() ) //Get the IDs of the Auth. Users and Everyone groups @@ -332,18 +332,18 @@ func CalculateCrossProductNodeSets(tx graph.Transaction, domainsid string, group //Unroll all nodesets for _, nodeSlice := range nodeSlices { var ( - firstDegreeSet = cardinality.NewBitmap32() - unrolledSet = cardinality.NewBitmap32() + firstDegreeSet = cardinality.NewBitmap64() + unrolledSet = cardinality.NewBitmap64() ) for _, entity := range nodeSlice { - entityID := entity.ID.Uint32() + entityID := entity.ID.Uint64() firstDegreeSet.Add(entityID) unrolledSet.Add(entityID) if entity.Kinds.ContainsOneOf(ad.Group, ad.LocalGroup) { - unrolledSet.Or(groupExpansions.Cardinality(entity.ID.Uint32()).(cardinality.Duplex[uint32])) + unrolledSet.Or(groupExpansions.Cardinality(entity.ID.Uint64())) } } @@ -351,7 +351,7 @@ func CalculateCrossProductNodeSets(tx graph.Transaction, domainsid string, group hasSpecialGroup := false for _, specialGroup := range specialGroups { - if unrolledSet.Contains(specialGroup.ID.Uint32()) { + if unrolledSet.Contains(specialGroup.ID.Uint64()) { hasSpecialGroup = true break } @@ -367,7 +367,7 @@ func CalculateCrossProductNodeSets(tx graph.Transaction, domainsid string, group if len(firstDegreeSets) == 0 { for _, nodeSet := range nodeSlices { for _, entity := range nodeSet { - resultEntities.Add(entity.ID.Uint32()) + resultEntities.Add(entity.ID.Uint64()) } } @@ -384,7 +384,7 @@ func CalculateCrossProductNodeSets(tx graph.Transaction, domainsid string, group } //Check first degree principals in our reference set (firstDegreeSets[0]) first - firstDegreeSets[0].Each(func(id uint32) bool { + firstDegreeSets[0].Each(func(id uint64) bool { if checkSet.Contains(id) { resultEntities.Add(id) } else { @@ -396,8 +396,8 @@ func CalculateCrossProductNodeSets(tx graph.Transaction, domainsid string, group //Find all the groups in our secondary targets and map them to their cardinality in our expansions //Saving off to a map to prevent multiple lookups on the expansions - tempMap := map[uint32]uint64{} - unrolledRefSet.Each(func(id uint32) bool { + tempMap := map[uint64]uint64{} + unrolledRefSet.Each(func(id uint64) bool { //If group expansions contains this ID and its cardinality is > 0, it's a group/localgroup idCardinality := groupExpansions.Cardinality(id).Cardinality() if idCardinality > 0 { @@ -408,7 +408,7 @@ func CalculateCrossProductNodeSets(tx graph.Transaction, domainsid string, group }) //Save the map keys to a new slice, this represents our list of groups in the expansion - keys := make([]uint32, 0, len(tempMap)) + keys := make([]uint64, 0, len(tempMap)) for key := range tempMap { keys = append(keys, key) @@ -437,7 +437,7 @@ func CalculateCrossProductNodeSets(tx graph.Transaction, domainsid string, group } } - unrolledRefSet.Each(func(remainder uint32) bool { + unrolledRefSet.Each(func(remainder uint64) bool { if checkSet.Contains(remainder) { resultEntities.Add(remainder) } diff --git a/packages/go/analysis/ad/adcscache.go b/packages/go/analysis/ad/adcscache.go index 1edf9a1241..1bf53e6256 100644 --- a/packages/go/analysis/ad/adcscache.go +++ b/packages/go/analysis/ad/adcscache.go @@ -31,33 +31,33 @@ type ADCSCache struct { mu *sync.RWMutex // To discourage direct access without getting a read lock, these are private - authStoreForChainValid map[graph.ID]cardinality.Duplex[uint32] - rootCAForChainValid map[graph.ID]cardinality.Duplex[uint32] - expandedCertTemplateControllers map[graph.ID]cardinality.Duplex[uint32] + authStoreForChainValid map[graph.ID]cardinality.Duplex[uint64] + rootCAForChainValid map[graph.ID]cardinality.Duplex[uint64] + expandedCertTemplateControllers map[graph.ID]cardinality.Duplex[uint64] certTemplateHasSpecialEnrollers map[graph.ID]bool // whether Auth. Users or Everyone has enrollment rights on templates enterpriseCAHasSpecialEnrollers map[graph.ID]bool // whether Auth. Users or Everyone has enrollment rights on enterprise CAs certTemplateEnrollers map[graph.ID][]*graph.Node // principals that have enrollment on a cert template via `enroll`, `generic all`, `all extended rights` edges certTemplateControllers map[graph.ID][]*graph.Node // principals that have privileges on a cert template via `owner`, `generic all`, `write dacl`, `write owner` edges enterpriseCAEnrollers map[graph.ID][]*graph.Node // principals that have enrollment rights on an enterprise ca via `enroll` edge publishedTemplateCache map[graph.ID][]*graph.Node // cert templates that are published to an enterprise ca - hasUPNCertMappingInForest cardinality.Duplex[uint32] // domains where at least one DC in the forest has Schannel UPN cert mapping enabled - hasWeakCertBindingInForest cardinality.Duplex[uint32] // domains where at least one DC in the forest has Kerberos weak cert binding enabled + hasUPNCertMappingInForest cardinality.Duplex[uint64] // domains where at least one DC in the forest has Schannel UPN cert mapping enabled + hasWeakCertBindingInForest cardinality.Duplex[uint64] // domains where at least one DC in the forest has Kerberos weak cert binding enabled } func NewADCSCache() ADCSCache { return ADCSCache{ mu: &sync.RWMutex{}, - authStoreForChainValid: make(map[graph.ID]cardinality.Duplex[uint32]), - rootCAForChainValid: make(map[graph.ID]cardinality.Duplex[uint32]), - expandedCertTemplateControllers: make(map[graph.ID]cardinality.Duplex[uint32]), + authStoreForChainValid: make(map[graph.ID]cardinality.Duplex[uint64]), + rootCAForChainValid: make(map[graph.ID]cardinality.Duplex[uint64]), + expandedCertTemplateControllers: make(map[graph.ID]cardinality.Duplex[uint64]), certTemplateHasSpecialEnrollers: make(map[graph.ID]bool), enterpriseCAHasSpecialEnrollers: make(map[graph.ID]bool), certTemplateEnrollers: make(map[graph.ID][]*graph.Node), certTemplateControllers: make(map[graph.ID][]*graph.Node), enterpriseCAEnrollers: make(map[graph.ID][]*graph.Node), publishedTemplateCache: make(map[graph.ID][]*graph.Node), - hasUPNCertMappingInForest: cardinality.NewBitmap32(), - hasWeakCertBindingInForest: cardinality.NewBitmap32(), + hasUPNCertMappingInForest: cardinality.NewBitmap64(), + hasWeakCertBindingInForest: cardinality.NewBitmap64(), } } @@ -121,8 +121,8 @@ func (s *ADCSCache) BuildCache(ctx context.Context, db graph.Database, enterpris } else if authStoreForNodes, err := FetchEnterpriseCAsTrustedForNTAuthToDomain(tx, domain); err != nil { log.Errorf("Error getting cas via authstorefor for domain %d: %v", domain.ID, err) } else { - s.authStoreForChainValid[domain.ID] = cardinality.NodeSetToDuplex(authStoreForNodes) - s.rootCAForChainValid[domain.ID] = cardinality.NodeSetToDuplex(rootCaForNodes) + s.authStoreForChainValid[domain.ID] = graph.NodeSetToDuplex(authStoreForNodes) + s.rootCAForChainValid[domain.ID] = graph.NodeSetToDuplex(rootCaForNodes) } // Check for weak cert config on DCs @@ -130,13 +130,13 @@ func (s *ADCSCache) BuildCache(ctx context.Context, db graph.Database, enterpris log.Warnf("Error checking hasUPNCertMappingInForest for domain %d: %v", domain.ID, err) return nil } else if upnMapping { - s.hasUPNCertMappingInForest.Add(domain.ID.Uint32()) + s.hasUPNCertMappingInForest.Add(domain.ID.Uint64()) } if weakCertBinding, err := hasWeakCertBindingInForest(tx, domain); err != nil { log.Warnf("Error checking hasWeakCertBindingInForest for domain %d: %v", domain.ID, err) return nil } else if weakCertBinding { - s.hasWeakCertBindingInForest.Add(domain.ID.Uint32()) + s.hasWeakCertBindingInForest.Add(domain.ID.Uint64()) } } @@ -151,7 +151,7 @@ func (s *ADCSCache) BuildCache(ctx context.Context, db graph.Database, enterpris func (s *ADCSCache) DoesCAChainProperlyToDomain(enterpriseCA, domain *graph.Node) bool { var domainID = domain.ID - var caID = enterpriseCA.ID.Uint32() + var caID = enterpriseCA.ID.Uint64() s.mu.RLock() defer s.mu.RUnlock() @@ -164,23 +164,23 @@ func (s *ADCSCache) DoesCAChainProperlyToDomain(enterpriseCA, domain *graph.Node } } -func (s *ADCSCache) GetExpandedCertTemplateControllers(id graph.ID) cardinality.Duplex[uint32] { +func (s *ADCSCache) GetExpandedCertTemplateControllers(id graph.ID) cardinality.Duplex[uint64] { s.mu.RLock() defer s.mu.RUnlock() if expandedCertTemplateControllers, ok := s.expandedCertTemplateControllers[id]; !ok { - return cardinality.NewBitmap32() + return cardinality.NewBitmap64() } else { return expandedCertTemplateControllers } } -func (s *ADCSCache) SetExpandedCertTemplateControllers(certId graph.ID, principalId uint32) { +func (s *ADCSCache) SetExpandedCertTemplateControllers(certId graph.ID, principalId uint64) { s.mu.Lock() defer s.mu.Unlock() if _, ok := s.expandedCertTemplateControllers[certId]; !ok { - s.expandedCertTemplateControllers[certId] = cardinality.NewBitmap32With(principalId) + s.expandedCertTemplateControllers[certId] = cardinality.NewBitmap64With(principalId) } else { s.expandedCertTemplateControllers[certId].Add(principalId) } @@ -228,14 +228,14 @@ func (s *ADCSCache) GetPublishedTemplateCache(id graph.ID) []*graph.Node { return s.publishedTemplateCache[id] } -func (s *ADCSCache) HasUPNCertMappingInForest(id uint32) bool { +func (s *ADCSCache) HasUPNCertMappingInForest(id uint64) bool { s.mu.RLock() defer s.mu.RUnlock() return s.hasUPNCertMappingInForest.Contains(id) } -func (s *ADCSCache) HasWeakCertBindingInForest(id uint32) bool { +func (s *ADCSCache) HasWeakCertBindingInForest(id uint64) bool { s.mu.RLock() defer s.mu.RUnlock() diff --git a/packages/go/analysis/ad/esc1.go b/packages/go/analysis/ad/esc1.go index 9338b9a5e5..92eb90bed1 100644 --- a/packages/go/analysis/ad/esc1.go +++ b/packages/go/analysis/ad/esc1.go @@ -33,7 +33,7 @@ import ( ) func PostADCSESC1(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob, expandedGroups impact.PathAggregator, enterpriseCA, domain *graph.Node, cache ADCSCache) error { - results := cardinality.NewBitmap32() + results := cardinality.NewBitmap64() if publishedCertTemplates := cache.GetPublishedTemplateCache(enterpriseCA.ID); len(publishedCertTemplates) == 0 { return nil } else { @@ -53,7 +53,7 @@ func PostADCSESC1(ctx context.Context, tx graph.Transaction, outC chan<- analysi } } - results.Each(func(value uint32) bool { + results.Each(func(value uint64) bool { channels.Submit(ctx, outC, analysis.CreatePostRelationshipJob{ FromID: graph.ID(value), ToID: domain.ID, @@ -130,14 +130,14 @@ func ADCSESC1Path1Pattern(domainID graph.ID) traversal.PatternContinuation { )) } -func ADCSESC1Path2Pattern(domainID graph.ID, enterpriseCAs cardinality.Duplex[uint32]) traversal.PatternContinuation { +func ADCSESC1Path2Pattern(domainID graph.ID, enterpriseCAs cardinality.Duplex[uint64]) traversal.PatternContinuation { return traversal.NewPattern().OutboundWithDepth(0, 0, query.And( query.Kind(query.Relationship(), ad.MemberOf), query.Kind(query.End(), ad.Group), )). Outbound(query.And( query.KindIn(query.Relationship(), ad.Enroll), - query.InIDs(query.EndID(), cardinality.DuplexToGraphIDs(enterpriseCAs)...), + query.InIDs(query.EndID(), graph.DuplexToGraphIDs(enterpriseCAs)...), )). Outbound(query.And( query.KindIn(query.Relationship(), ad.TrustedForNTAuth), @@ -181,8 +181,8 @@ func GetADCSESC1EdgeComposition(ctx context.Context, db graph.Database, edge *gr traversalInst = traversal.New(db, analysis.MaximumDatabaseParallelWorkers) paths = graph.PathSet{} candidateSegments = map[graph.ID][]*graph.PathSegment{} - path1EnterpriseCAs = cardinality.NewBitmap32() - path2EnterpriseCAs = cardinality.NewBitmap32() + path1EnterpriseCAs = cardinality.NewBitmap64() + path2EnterpriseCAs = cardinality.NewBitmap64() lock = &sync.Mutex{} ) @@ -231,7 +231,7 @@ func GetADCSESC1EdgeComposition(ctx context.Context, db graph.Database, edge *gr lock.Lock() candidateSegments[enterpriseCANode.ID] = append(candidateSegments[enterpriseCANode.ID], terminal) - path1EnterpriseCAs.Add(enterpriseCANode.ID.Uint32()) + path1EnterpriseCAs.Add(enterpriseCANode.ID.Uint64()) lock.Unlock() return nil @@ -253,7 +253,7 @@ func GetADCSESC1EdgeComposition(ctx context.Context, db graph.Database, edge *gr lock.Lock() candidateSegments[enterpriseCANode.ID] = append(candidateSegments[enterpriseCANode.ID], terminal) - path2EnterpriseCAs.Add(enterpriseCANode.ID.Uint32()) + path2EnterpriseCAs.Add(enterpriseCANode.ID.Uint64()) lock.Unlock() return nil @@ -267,7 +267,7 @@ func GetADCSESC1EdgeComposition(ctx context.Context, db graph.Database, edge *gr path1EnterpriseCAs.And(path2EnterpriseCAs) // Render paths from the segments - path1EnterpriseCAs.Each(func(value uint32) bool { + path1EnterpriseCAs.Each(func(value uint64) bool { for _, segment := range candidateSegments[graph.ID(value)] { paths.AddPath(segment.Path()) } diff --git a/packages/go/analysis/ad/esc10.go b/packages/go/analysis/ad/esc10.go index 553aa96c5a..d62863dd2a 100644 --- a/packages/go/analysis/ad/esc10.go +++ b/packages/go/analysis/ad/esc10.go @@ -34,14 +34,14 @@ import ( ) func PostADCSESC10a(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob, groupExpansions impact.PathAggregator, eca, domain *graph.Node, cache ADCSCache) error { - if ok := cache.HasUPNCertMappingInForest(domain.ID.Uint32()); !ok { + if ok := cache.HasUPNCertMappingInForest(domain.ID.Uint64()); !ok { return nil } else if publishedCertTemplates := cache.GetPublishedTemplateCache(eca.ID); len(publishedCertTemplates) == 0 { return nil } else if ecaEnrollers := cache.GetEnterpriseCAEnrollers(eca.ID); len(ecaEnrollers) == 0 { return nil } else { - results := cardinality.NewBitmap32() + results := cardinality.NewBitmap64() for _, template := range publishedCertTemplates { if valid, err := isCertTemplateValidForESC10(template, false); err != nil { @@ -62,12 +62,12 @@ func PostADCSESC10a(ctx context.Context, tx graph.Transaction, outC chan<- analy log.Warnf("Error getting start nodes for esc10a attacker nodes: %v", err) continue } else { - results.Or(cardinality.NodeIDsToDuplex(attackers)) + results.Or(graph.NodeIDsToDuplex(attackers)) } } } - results.Each(func(value uint32) bool { + results.Each(func(value uint64) bool { channels.Submit(ctx, outC, analysis.CreatePostRelationshipJob{ FromID: graph.ID(value), ToID: domain.ID, @@ -80,14 +80,14 @@ func PostADCSESC10a(ctx context.Context, tx graph.Transaction, outC chan<- analy } func PostADCSESC10b(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob, groupExpansions impact.PathAggregator, enterpriseCA, domain *graph.Node, cache ADCSCache) error { - if ok := cache.HasUPNCertMappingInForest(domain.ID.Uint32()); !ok { + if ok := cache.HasUPNCertMappingInForest(domain.ID.Uint64()); !ok { return nil } else if publishedCertTemplates := cache.GetPublishedTemplateCache(enterpriseCA.ID); len(publishedCertTemplates) == 0 { return nil } else if ecaEnrollers := cache.GetEnterpriseCAEnrollers(enterpriseCA.ID); len(ecaEnrollers) == 0 { return nil } else { - results := cardinality.NewBitmap32() + results := cardinality.NewBitmap64() for _, template := range publishedCertTemplates { if valid, err := isCertTemplateValidForESC10(template, true); err != nil { @@ -105,12 +105,12 @@ func PostADCSESC10b(ctx context.Context, tx graph.Transaction, outC chan<- analy log.Warnf("Error getting start nodes for esc10b attacker nodes: %v", err) continue } else { - results.Or(cardinality.NodeIDsToDuplex(attackers)) + results.Or(graph.NodeIDsToDuplex(attackers)) } } } - results.Each(func(value uint32) bool { + results.Each(func(value uint64) bool { channels.Submit(ctx, outC, analysis.CreatePostRelationshipJob{ FromID: graph.ID(value), ToID: domain.ID, diff --git a/packages/go/analysis/ad/esc13.go b/packages/go/analysis/ad/esc13.go index cfd72994d7..e1c83fd542 100644 --- a/packages/go/analysis/ad/esc13.go +++ b/packages/go/analysis/ad/esc13.go @@ -21,7 +21,6 @@ import ( "errors" "sync" - "github.com/RoaringBitmap/roaring" "github.com/specterops/bloodhound/analysis" "github.com/specterops/bloodhound/analysis/impact" "github.com/specterops/bloodhound/dawgs/cardinality" @@ -61,7 +60,7 @@ func PostADCSESC13(ctx context.Context, tx graph.Transaction, outC chan<- analys } else { for _, group := range groupNodes.Slice() { if groupIsContainedOrTrusted(tx, group, domain) { - filtered.Each(func(value uint32) bool { + filtered.Each(func(value uint64) bool { channels.Submit(ctx, outC, analysis.CreatePostRelationshipJob{ FromID: graph.ID(value), ToID: group.ID, @@ -82,12 +81,12 @@ func PostADCSESC13(ctx context.Context, tx graph.Transaction, outC chan<- analys func groupIsContainedOrTrusted(tx graph.Transaction, group, domain *graph.Node) bool { var ( matchFound = false - visitedBitmap = roaring.New() + visitedBitmap = cardinality.NewBitmap64() traversalPlan = ops.TraversalPlan{ Root: group, Direction: graph.DirectionInbound, ExpansionFilter: func(segment *graph.PathSegment) bool { - return visitedBitmap.CheckedAdd(segment.Node.ID.Uint32()) + return visitedBitmap.CheckedAdd(segment.Node.ID.Uint64()) }, BranchQuery: func() graph.Criteria { return query.KindIn(query.Relationship(), ad.Contains, ad.TrustedBy) @@ -199,13 +198,13 @@ func GetADCSESC13EdgeComposition(ctx context.Context, db graph.Database, edge *g traversalInst = traversal.New(db, analysis.MaximumDatabaseParallelWorkers) paths = graph.PathSet{} path1CandidateSegments = map[graph.ID][]*graph.PathSegment{} - path1EnterpriseCAs = cardinality.NewBitmap32() - path1DomainNodes = cardinality.NewBitmap32() - path1CertTemplates = cardinality.NewBitmap32() + path1EnterpriseCAs = cardinality.NewBitmap64() + path1DomainNodes = cardinality.NewBitmap64() + path1CertTemplates = cardinality.NewBitmap64() path2CandidateSegments = map[graph.ID][]*graph.PathSegment{} path3CandidateSegments = map[graph.ID][]*graph.PathSegment{} path4CandidateSegments = map[graph.ID][]*graph.PathSegment{} - path2EnterpriseCAs = cardinality.NewBitmap32() + path2EnterpriseCAs = cardinality.NewBitmap64() lock = &sync.Mutex{} ) @@ -258,9 +257,9 @@ func GetADCSESC13EdgeComposition(ctx context.Context, db graph.Database, edge *g lock.Lock() path1CandidateSegments[enterpriseCANode.ID] = append(path1CandidateSegments[enterpriseCANode.ID], terminal) - path1EnterpriseCAs.Add(enterpriseCANode.ID.Uint32()) - path1DomainNodes.Add(domainNode.ID.Uint32()) - path1CertTemplates.Add(certTemplate.ID.Uint32()) + path1EnterpriseCAs.Add(enterpriseCANode.ID.Uint64()) + path1DomainNodes.Add(domainNode.ID.Uint64()) + path1CertTemplates.Add(certTemplate.ID.Uint64()) lock.Unlock() return nil }), @@ -273,7 +272,7 @@ func GetADCSESC13EdgeComposition(ctx context.Context, db graph.Database, edge *g for _, n := range startNodes.Slice() { if err := traversalInst.BreadthFirst(ctx, traversal.Plan{ Root: n, - Driver: adcsESC13Path2Pattern(cardinality.DuplexToGraphIDs(path1EnterpriseCAs), cardinality.DuplexToGraphIDs(path1DomainNodes)).Do(func(terminal *graph.PathSegment) error { + Driver: adcsESC13Path2Pattern(graph.DuplexToGraphIDs(path1EnterpriseCAs), graph.DuplexToGraphIDs(path1DomainNodes)).Do(func(terminal *graph.PathSegment) error { var enterpriseCANode *graph.Node terminal.WalkReverse(func(nextSegment *graph.PathSegment) bool { if nextSegment.Node.Kinds.ContainsOneOf(ad.EnterpriseCA) { @@ -283,7 +282,7 @@ func GetADCSESC13EdgeComposition(ctx context.Context, db graph.Database, edge *g }) lock.Lock() path2CandidateSegments[enterpriseCANode.ID] = append(path2CandidateSegments[enterpriseCANode.ID], terminal) - path2EnterpriseCAs.Add(enterpriseCANode.ID.Uint32()) + path2EnterpriseCAs.Add(enterpriseCANode.ID.Uint64()) lock.Unlock() return nil @@ -296,7 +295,7 @@ func GetADCSESC13EdgeComposition(ctx context.Context, db graph.Database, edge *g //Manifest P3 keyed to cert template nodes if err := traversalInst.BreadthFirst(ctx, traversal.Plan{ Root: endNode, - Driver: adcsESC13Path3Pattern(cardinality.DuplexToGraphIDs(path1CertTemplates)).Do(func(terminal *graph.PathSegment) error { + Driver: adcsESC13Path3Pattern(graph.DuplexToGraphIDs(path1CertTemplates)).Do(func(terminal *graph.PathSegment) error { certTemplate := terminal.Search(func(nextSegment *graph.PathSegment) bool { return nextSegment.Node.Kinds.ContainsOneOf(ad.CertTemplate) }) diff --git a/packages/go/analysis/ad/esc3.go b/packages/go/analysis/ad/esc3.go index 626ad3744c..2406e0081b 100644 --- a/packages/go/analysis/ad/esc3.go +++ b/packages/go/analysis/ad/esc3.go @@ -36,7 +36,7 @@ import ( ) func PostADCSESC3(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob, groupExpansions impact.PathAggregator, eca2, domain *graph.Node, cache ADCSCache) error { - results := cardinality.NewBitmap32() + results := cardinality.NewBitmap64() if domainsid, err := domain.Properties.Get(ad.DomainSID.String()).String(); err != nil { log.Warnf("Error getting domain SID for domain %d: %v", domain.ID, err) return nil @@ -130,7 +130,7 @@ func PostADCSESC3(ctx context.Context, tx graph.Transaction, outC chan<- analysi } } - results.Each(func(value uint32) bool { + results.Each(func(value uint64) bool { channels.Submit(ctx, outC, analysis.CreatePostRelationshipJob{ FromID: graph.ID(value), ToID: domain.ID, @@ -421,11 +421,11 @@ func GetADCSESC3EdgeComposition(ctx context.Context, db graph.Database, edge *gr path6_7CandidateSegments = map[graph.ID][]*graph.PathSegment{} path8CandidateSegments = map[graph.ID][]*graph.PathSegment{} lock = &sync.Mutex{} - path1CertTemplates = cardinality.NewBitmap32() - path2CertTemplates = cardinality.NewBitmap32() - enterpriseCANodes = cardinality.NewBitmap32() + path1CertTemplates = cardinality.NewBitmap64() + path2CertTemplates = cardinality.NewBitmap64() + enterpriseCANodes = cardinality.NewBitmap64() enterpriseCASegments = map[graph.ID][]*graph.PathSegment{} - path2CandidateTemplates = cardinality.NewBitmap32() + path2CandidateTemplates = cardinality.NewBitmap64() enrollOnBehalfOfPaths graph.PathSet ) @@ -469,7 +469,7 @@ func GetADCSESC3EdgeComposition(ctx context.Context, db graph.Database, edge *gr lock.Lock() enterpriseCASegments[enterpriseCANode.ID] = append(enterpriseCASegments[enterpriseCANode.ID], terminal) - enterpriseCANodes.Add(enterpriseCANode.ID.Uint32()) + enterpriseCANodes.Add(enterpriseCANode.ID.Uint64()) lock.Unlock() return nil @@ -494,7 +494,7 @@ func GetADCSESC3EdgeComposition(ctx context.Context, db graph.Database, edge *gr // Check that CT is valid for user start nodes userStartNode := startNode.Kinds.ContainsOneOf(ad.User) if !userStartNode || certTemplateValidForUserVictim(certTemplateNode) { - path1CertTemplates.Add(certTemplateNode.ID.Uint32()) + path1CertTemplates.Add(certTemplateNode.ID.Uint64()) } lock.Unlock() @@ -508,7 +508,7 @@ func GetADCSESC3EdgeComposition(ctx context.Context, db graph.Database, edge *gr if err := db.ReadTransaction(ctx, func(tx graph.Transaction) error { if p, err := ops.FetchPathSet(tx.Relationships().Filter( query.And( - query.InIDs(query.StartID(), cardinality.DuplexToGraphIDs(path1CertTemplates)...), + query.InIDs(query.StartID(), graph.DuplexToGraphIDs(path1CertTemplates)...), query.KindIn(query.Relationship(), ad.EnrollOnBehalfOf), query.KindIn(query.End(), ad.CertTemplate)), )); err != nil { @@ -522,7 +522,7 @@ func GetADCSESC3EdgeComposition(ctx context.Context, db graph.Database, edge *gr } for _, path := range enrollOnBehalfOfPaths { - path2CandidateTemplates.Add(path.Terminal().ID.Uint32()) + path2CandidateTemplates.Add(path.Terminal().ID.Uint64()) } //Use our enterprise ca + candidate templates as filters for the third query (P2) @@ -536,7 +536,7 @@ func GetADCSESC3EdgeComposition(ctx context.Context, db graph.Database, edge *gr lock.Lock() path2CandidateSegments[certTemplateNode.ID] = append(path2CandidateSegments[certTemplateNode.ID], terminal) - path2CertTemplates.Add(certTemplateNode.ID.Uint32()) + path2CertTemplates.Add(certTemplateNode.ID.Uint64()) lock.Unlock() return nil @@ -597,11 +597,11 @@ func GetADCSESC3EdgeComposition(ctx context.Context, db graph.Database, edge *gr ct1 := p3.Root() ct2 := p3.Terminal() - if !path1CertTemplates.Contains(ct1.ID.Uint32()) { + if !path1CertTemplates.Contains(ct1.ID.Uint64()) { continue } - if !path2CertTemplates.Contains(ct2.ID.Uint32()) { + if !path2CertTemplates.Contains(ct2.ID.Uint64()) { continue } @@ -610,12 +610,12 @@ func GetADCSESC3EdgeComposition(ctx context.Context, db graph.Database, edge *gr for _, p1 := range p1paths { eca1 := p1.Search(func(nextSegment *graph.PathSegment) bool { - return nextSegment.Node.Kinds.ContainsOneOf(ad.EnterpriseCA) && enterpriseCANodes.Contains(nextSegment.Node.ID.Uint32()) + return nextSegment.Node.Kinds.ContainsOneOf(ad.EnterpriseCA) && enterpriseCANodes.Contains(nextSegment.Node.ID.Uint64()) }) for _, p2 := range p2paths { eca2 := p2.Search(func(nextSegment *graph.PathSegment) bool { - return nextSegment.Node.Kinds.ContainsOneOf(ad.EnterpriseCA) && enterpriseCANodes.Contains(nextSegment.Node.ID.Uint32()) + return nextSegment.Node.Kinds.ContainsOneOf(ad.EnterpriseCA) && enterpriseCANodes.Contains(nextSegment.Node.ID.Uint64()) }) // Verify P6 and P7 paths exists @@ -673,7 +673,7 @@ func GetADCSESC3EdgeComposition(ctx context.Context, db graph.Database, edge *gr return paths, nil } -func ADCSESC3Path1Pattern(enterpriseCAs cardinality.Duplex[uint32]) traversal.PatternContinuation { +func ADCSESC3Path1Pattern(enterpriseCAs cardinality.Duplex[uint64]) traversal.PatternContinuation { return traversal.NewPattern().OutboundWithDepth(0, 0, query.And( query.Kind(query.Relationship(), ad.MemberOf), query.Kind(query.End(), ad.Group), @@ -694,12 +694,12 @@ func ADCSESC3Path1Pattern(enterpriseCAs cardinality.Duplex[uint32]) traversal.Pa )). Outbound(query.And( query.KindIn(query.Relationship(), ad.PublishedTo), - query.InIDs(query.End(), cardinality.DuplexToGraphIDs(enterpriseCAs)...), + query.InIDs(query.End(), graph.DuplexToGraphIDs(enterpriseCAs)...), query.Kind(query.End(), ad.EnterpriseCA), )) } -func ADCSESC3Path2Pattern(domainId graph.ID, enterpriseCAs, candidateTemplates cardinality.Duplex[uint32]) traversal.PatternContinuation { +func ADCSESC3Path2Pattern(domainId graph.ID, enterpriseCAs, candidateTemplates cardinality.Duplex[uint64]) traversal.PatternContinuation { return traversal.NewPattern().OutboundWithDepth(0, 0, query.And( query.Kind(query.Relationship(), ad.MemberOf), query.Kind(query.End(), ad.Group), @@ -709,12 +709,12 @@ func ADCSESC3Path2Pattern(domainId graph.ID, enterpriseCAs, candidateTemplates c query.KindIn(query.End(), ad.CertTemplate), query.Equals(query.EndProperty(ad.AuthenticationEnabled.String()), true), query.Equals(query.EndProperty(ad.RequiresManagerApproval.String()), false), - query.InIDs(query.EndID(), cardinality.DuplexToGraphIDs(candidateTemplates)...), + query.InIDs(query.EndID(), graph.DuplexToGraphIDs(candidateTemplates)...), )). Outbound(query.And( query.KindIn(query.Relationship(), ad.PublishedTo), query.KindIn(query.End(), ad.EnterpriseCA), - query.InIDs(query.End(), cardinality.DuplexToGraphIDs(enterpriseCAs)...))). + query.InIDs(query.End(), graph.DuplexToGraphIDs(enterpriseCAs)...))). Outbound(query.And( query.KindIn(query.Relationship(), ad.TrustedForNTAuth), query.Kind(query.End(), ad.NTAuthStore), @@ -752,13 +752,13 @@ func ADCSESC3Path6_7Pattern(domainId graph.ID) traversal.PatternContinuation { )) } -func ADCSESC3Path8Pattern(candidateTemplates cardinality.Duplex[uint32]) traversal.PatternContinuation { +func ADCSESC3Path8Pattern(candidateTemplates cardinality.Duplex[uint64]) traversal.PatternContinuation { return traversal.NewPattern().OutboundWithDepth(0, 0, query.And( query.Kind(query.Relationship(), ad.MemberOf), query.Kind(query.End(), ad.Group), )). Outbound(query.And( query.KindIn(query.Relationship(), ad.DelegatedEnrollmentAgent), - query.InIDs(query.EndID(), cardinality.DuplexToGraphIDs(candidateTemplates)...), + query.InIDs(query.EndID(), graph.DuplexToGraphIDs(candidateTemplates)...), )) } diff --git a/packages/go/analysis/ad/esc4.go b/packages/go/analysis/ad/esc4.go index dddbcd13f1..93fae1f28c 100644 --- a/packages/go/analysis/ad/esc4.go +++ b/packages/go/analysis/ad/esc4.go @@ -34,7 +34,7 @@ import ( func PostADCSESC4(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob, groupExpansions impact.PathAggregator, enterpriseCA, domain *graph.Node, cache ADCSCache) error { // 1. - principals := cardinality.NewBitmap32() + principals := cardinality.NewBitmap64() publishedTemplates := cache.GetPublishedTemplateCache(enterpriseCA.ID) domainsid, err := domain.Properties.Get(ad.DomainSID.String()).String() if err != nil { @@ -129,7 +129,7 @@ func PostADCSESC4(ctx context.Context, tx graph.Transaction, outC chan<- analysi } } - principals.Each(func(value uint32) bool { + principals.Each(func(value uint64) bool { channels.Submit(ctx, outC, analysis.CreatePostRelationshipJob{ FromID: graph.ID(value), ToID: domain.ID, @@ -229,15 +229,15 @@ func findPathsToDomainThroughCertTemplateWithGenericAll( db graph.Database, startNodes graph.NodeSet, domainID graph.ID, - enrollAndNTAuthECAs cardinality.Duplex[uint32], -) (map[graph.ID][]*graph.PathSegment, cardinality.Duplex[uint32], error) { + enrollAndNTAuthECAs cardinality.Duplex[uint64], +) (map[graph.ID][]*graph.PathSegment, cardinality.Duplex[uint64], error) { var ( traversalInst = traversal.New(db, analysis.MaximumDatabaseParallelWorkers) lock = &sync.Mutex{} certTemplateSegments = map[graph.ID][]*graph.PathSegment{} - certTemplates = cardinality.NewBitmap32() + certTemplates = cardinality.NewBitmap64() ) // p1: use the enterpriseCA nodes to gather the set of cert templates with an inbound `GenericAll` @@ -260,7 +260,7 @@ func findPathsToDomainThroughCertTemplateWithGenericAll( lock.Lock() certTemplateSegments[certTemplate.ID] = append(certTemplateSegments[certTemplate.ID], terminal) - certTemplates.Add(certTemplate.ID.Uint32()) + certTemplates.Add(certTemplate.ID.Uint64()) lock.Unlock() @@ -280,8 +280,8 @@ func findPathsToDomainThroughCertTemplateWithGenericWrite( db graph.Database, startNodes graph.NodeSet, domainID graph.ID, - enrollAndNTAuthECAs cardinality.Duplex[uint32], -) (map[graph.ID][]*graph.PathSegment, cardinality.Duplex[uint32], error) { + enrollAndNTAuthECAs cardinality.Duplex[uint64], +) (map[graph.ID][]*graph.PathSegment, cardinality.Duplex[uint64], error) { var ( inboundEdgeToCertTemplate = ad.GenericWrite @@ -297,8 +297,8 @@ func findPathsToDomainThroughCertTemplateWithWritePKINameFlag( db graph.Database, startNodes graph.NodeSet, domainID graph.ID, - enrollAndNTAuthECAs cardinality.Duplex[uint32], -) (map[graph.ID][]*graph.PathSegment, cardinality.Duplex[uint32], error) { + enrollAndNTAuthECAs cardinality.Duplex[uint64], +) (map[graph.ID][]*graph.PathSegment, cardinality.Duplex[uint64], error) { var ( inboundEdgeToCertTemplate = ad.WritePKINameFlag @@ -322,8 +322,8 @@ func findPathsToDomainThroughCertTemplateWithWritePKIEnrollmentFlag( db graph.Database, startNodes graph.NodeSet, domainID graph.ID, - enrollAndNTAuthECAs cardinality.Duplex[uint32], -) (map[graph.ID][]*graph.PathSegment, cardinality.Duplex[uint32], error) { + enrollAndNTAuthECAs cardinality.Duplex[uint64], +) (map[graph.ID][]*graph.PathSegment, cardinality.Duplex[uint64], error) { var ( inboundEdgeToCertTemplate = ad.WritePKIEnrollmentFlag @@ -346,17 +346,17 @@ func traversalToDomainThroughCertTemplate( db graph.Database, startNodes graph.NodeSet, domainID graph.ID, - enrollAndNTAuthECAs cardinality.Duplex[uint32], + enrollAndNTAuthECAs cardinality.Duplex[uint64], inboundEdgeToCertTemplate graph.Kind, criteriaForCertTemplate graph.Criteria, -) (map[graph.ID][]*graph.PathSegment, cardinality.Duplex[uint32], error) { +) (map[graph.ID][]*graph.PathSegment, cardinality.Duplex[uint64], error) { var ( traversalInst = traversal.New(db, analysis.MaximumDatabaseParallelWorkers) lock = &sync.Mutex{} certTemplateSegments = map[graph.ID][]*graph.PathSegment{} - certTemplates = cardinality.NewBitmap32() + certTemplates = cardinality.NewBitmap64() ) for _, n := range startNodes.Slice() { @@ -373,7 +373,7 @@ func traversalToDomainThroughCertTemplate( lock.Lock() certTemplateSegments[certTemplate.ID] = append(certTemplateSegments[certTemplate.ID], terminal) - certTemplates.Add(certTemplate.ID.Uint32()) + certTemplates.Add(certTemplate.ID.Uint64()) lock.Unlock() return nil @@ -415,15 +415,15 @@ func findPathsToDomainThroughCertTemplateWithBothPKIFlags( db graph.Database, startNodes graph.NodeSet, domainID graph.ID, - enrollAndNTAuthECAs cardinality.Duplex[uint32], -) (map[graph.ID][]*graph.PathSegment, cardinality.Duplex[uint32], error) { + enrollAndNTAuthECAs cardinality.Duplex[uint64], +) (map[graph.ID][]*graph.PathSegment, cardinality.Duplex[uint64], error) { var ( traversalInst = traversal.New(db, analysis.MaximumDatabaseParallelWorkers) lock = &sync.Mutex{} certTemplateSegments = map[graph.ID][]*graph.PathSegment{} - certTemplates = cardinality.NewBitmap32() + certTemplates = cardinality.NewBitmap64() ) // p9: use the enterpriseCA nodes to gather the set of cert templates with an inbound `WritePKIEnrollmentFlag` @@ -441,7 +441,7 @@ func findPathsToDomainThroughCertTemplateWithBothPKIFlags( lock.Lock() certTemplateSegments[certTemplate.ID] = append(certTemplateSegments[certTemplate.ID], terminal) - certTemplates.Add(certTemplate.ID.Uint32()) + certTemplates.Add(certTemplate.ID.Uint64()) lock.Unlock() return nil @@ -510,7 +510,7 @@ func findPathToDomainThroughEnterpriseCAsTrustedForNTAuth( domainID graph.ID, ) ( map[graph.ID][]*graph.PathSegment, - cardinality.Duplex[uint32], + cardinality.Duplex[uint64], error, ) { var ( @@ -518,7 +518,7 @@ func findPathToDomainThroughEnterpriseCAsTrustedForNTAuth( lock = &sync.Mutex{} enrollAndNTAuthECASegments = map[graph.ID][]*graph.PathSegment{} - enrollAndNTAuthECAs = cardinality.NewBitmap32() + enrollAndNTAuthECAs = cardinality.NewBitmap64() ) for _, n := range startNodes.Slice() { @@ -533,7 +533,7 @@ func findPathToDomainThroughEnterpriseCAsTrustedForNTAuth( }) lock.Lock() - enrollAndNTAuthECAs.Add(enterpriseCA.ID.Uint32()) + enrollAndNTAuthECAs.Add(enterpriseCA.ID.Uint64()) enrollAndNTAuthECASegments[enterpriseCA.ID] = append(enrollAndNTAuthECASegments[enterpriseCA.ID], terminal) lock.Unlock() @@ -598,12 +598,12 @@ func GetADCSESC4EdgeComposition(ctx context.Context, db graph.Database, edge *gr endNode *graph.Node startNodes = graph.NodeSet{} - enrollAndNTAuthECAs cardinality.Duplex[uint32] + enrollAndNTAuthECAs cardinality.Duplex[uint64] domainID = edge.EndID paths = graph.PathSet{} enrollAndNTAuthECASegments = map[graph.ID][]*graph.PathSegment{} - finalECAs = cardinality.NewBitmap32() + finalECAs = cardinality.NewBitmap64() ) if err := db.ReadTransaction(ctx, func(tx graph.Transaction) error { @@ -648,7 +648,7 @@ func GetADCSESC4EdgeComposition(ctx context.Context, db graph.Database, edge *gr return nil, err } else { certTemplateIDs.Each( - func(value uint32) bool { + func(value uint64) bool { // add the paths which satisfy p1-p2 requirements for _, segment := range pathsToDomain[graph.ID(value)] { paths.AddPath(segment.Path()) @@ -656,7 +656,7 @@ func GetADCSESC4EdgeComposition(ctx context.Context, db graph.Database, edge *gr // add the ECA where the template is published (first ECA in the path in case of multi-tier hierarchy) to final list of ECAs segment.Path().Walk(func(start, end *graph.Node, relationship *graph.Relationship) bool { if end.Kinds.ContainsOneOf(ad.EnterpriseCA) { - finalECAs.Add(end.ID.Uint32()) + finalECAs.Add(end.ID.Uint64()) return false } return true @@ -673,7 +673,7 @@ func GetADCSESC4EdgeComposition(ctx context.Context, db graph.Database, edge *gr return nil, err } else { certTemplateIDs.Each( - func(value uint32) bool { + func(value uint64) bool { // add the paths which satisfy p3, p4, and p5 requirements for _, segment := range pathsToDomain[graph.ID(value)] { paths.AddPath(segment.Path()) @@ -681,7 +681,7 @@ func GetADCSESC4EdgeComposition(ctx context.Context, db graph.Database, edge *gr // add the ECA where the template is published (first ECA in the path in case of multi-tier hierarchy) to final list of ECAs segment.Path().Walk(func(start, end *graph.Node, relationship *graph.Relationship) bool { if end.Kinds.ContainsOneOf(ad.EnterpriseCA) { - finalECAs.Add(end.ID.Uint32()) + finalECAs.Add(end.ID.Uint64()) return false } return true @@ -698,7 +698,7 @@ func GetADCSESC4EdgeComposition(ctx context.Context, db graph.Database, edge *gr return nil, err } else { certTemplateIDs.Each( - func(value uint32) bool { + func(value uint64) bool { // add the paths which satisfy p6, p7, and p8 requirements for _, segment := range pathsToDomain[graph.ID(value)] { paths.AddPath(segment.Path()) @@ -706,7 +706,7 @@ func GetADCSESC4EdgeComposition(ctx context.Context, db graph.Database, edge *gr // add the ECA where the template is published (first ECA in the path in case of multi-tier hierarchy) to final list of ECAs segment.Path().Walk(func(start, end *graph.Node, relationship *graph.Relationship) bool { if end.Kinds.ContainsOneOf(ad.EnterpriseCA) { - finalECAs.Add(end.ID.Uint32()) + finalECAs.Add(end.ID.Uint64()) return false } return true @@ -723,7 +723,7 @@ func GetADCSESC4EdgeComposition(ctx context.Context, db graph.Database, edge *gr return nil, err } else { certTemplateIDs.Each( - func(value uint32) bool { + func(value uint64) bool { // add the paths which satisfy p9, p10, and p11 requirements for _, segment := range pathsToDomain[graph.ID(value)] { paths.AddPath(segment.Path()) @@ -731,7 +731,7 @@ func GetADCSESC4EdgeComposition(ctx context.Context, db graph.Database, edge *gr // add the ECA where the template is published (first ECA in the path in case of multi-tier hierarchy) to final list of ECAs segment.Path().Walk(func(start, end *graph.Node, relationship *graph.Relationship) bool { if end.Kinds.ContainsOneOf(ad.EnterpriseCA) { - finalECAs.Add(end.ID.Uint32()) + finalECAs.Add(end.ID.Uint64()) return false } return true @@ -748,7 +748,7 @@ func GetADCSESC4EdgeComposition(ctx context.Context, db graph.Database, edge *gr return nil, err } else { certTemplateIDs.Each( - func(value uint32) bool { + func(value uint64) bool { // add the paths which satisfy p12, p13, p14, p15 requirements for _, segment := range pathsToDomain[graph.ID(value)] { paths.AddPath(segment.Path()) @@ -756,7 +756,7 @@ func GetADCSESC4EdgeComposition(ctx context.Context, db graph.Database, edge *gr // add the ECA where the template is published (first ECA in the path in case of multi-tier hierarchy) to final list of ECAs segment.Path().Walk(func(start, end *graph.Node, relationship *graph.Relationship) bool { if end.Kinds.ContainsOneOf(ad.EnterpriseCA) { - finalECAs.Add(end.ID.Uint32()) + finalECAs.Add(end.ID.Uint64()) return false } return true @@ -771,7 +771,7 @@ func GetADCSESC4EdgeComposition(ctx context.Context, db graph.Database, edge *gr // if we have found paths already, materialize the paths we found from the enterprise CAs -> NTAuthStore -> Domain if paths.Len() > 0 { finalECAs.Each( - func(value uint32) bool { + func(value uint64) bool { for _, segment := range enrollAndNTAuthECASegments[graph.ID(value)] { paths.AddPath(segment.Path()) } @@ -807,7 +807,7 @@ func ntAuthStoreToDomainTraversal(domainId graph.ID) traversal.PatternContinuati } // This traversal goes from principal -> domain via a cert template that has an inbound edge(s) corresponding to whatever `priveleges` are provided -func certTemplateWithPrivelegesToDomainTraversal(priveleges graph.Kinds, domainID graph.ID, enrollAndNTAuthECAs cardinality.Duplex[uint32]) traversal.PatternContinuation { +func certTemplateWithPrivelegesToDomainTraversal(priveleges graph.Kinds, domainID graph.ID, enrollAndNTAuthECAs cardinality.Duplex[uint64]) traversal.PatternContinuation { return traversal.NewPattern(). OutboundWithDepth(0, 0, query.And( @@ -821,7 +821,7 @@ func certTemplateWithPrivelegesToDomainTraversal(priveleges graph.Kinds, domainI )). Outbound(query.And( query.KindIn(query.Relationship(), ad.PublishedTo), - query.InIDs(query.End(), cardinality.DuplexToGraphIDs(enrollAndNTAuthECAs)...), + query.InIDs(query.End(), graph.DuplexToGraphIDs(enrollAndNTAuthECAs)...), query.Kind(query.End(), ad.EnterpriseCA), )). OutboundWithDepth(0, 0, query.And( @@ -839,7 +839,7 @@ func certTemplateWithPrivelegesToDomainTraversal(priveleges graph.Kinds, domainI )) } -func certTemplateWithEnrollmentRightsTraversal(certTemplates cardinality.Duplex[uint32], criteria graph.Criteria) traversal.PatternContinuation { +func certTemplateWithEnrollmentRightsTraversal(certTemplates cardinality.Duplex[uint64], criteria graph.Criteria) traversal.PatternContinuation { // start with outbound group membership of any depth initialTraversal := traversal.NewPattern(). OutboundWithDepth(0, 0, @@ -852,19 +852,19 @@ func certTemplateWithEnrollmentRightsTraversal(certTemplates cardinality.Duplex[ return initialTraversal.Outbound( query.And( query.KindIn(query.Relationship(), ad.Enroll, ad.AllExtendedRights), - query.InIDs(query.End(), cardinality.DuplexToGraphIDs(certTemplates)...), + query.InIDs(query.End(), graph.DuplexToGraphIDs(certTemplates)...), )) } else { return initialTraversal.Outbound( query.And( query.KindIn(query.Relationship(), ad.Enroll, ad.AllExtendedRights), - query.InIDs(query.End(), cardinality.DuplexToGraphIDs(certTemplates)...), + query.InIDs(query.End(), graph.DuplexToGraphIDs(certTemplates)...), criteria, )) } } -func certTemplateWithPKINameFlagTraversal(certTemplates cardinality.Duplex[uint32]) traversal.PatternContinuation { +func certTemplateWithPKINameFlagTraversal(certTemplates cardinality.Duplex[uint64]) traversal.PatternContinuation { return traversal.NewPattern(). OutboundWithDepth(0, 0, query.And( @@ -874,7 +874,7 @@ func certTemplateWithPKINameFlagTraversal(certTemplates cardinality.Duplex[uint3 Outbound( query.And( query.KindIn(query.Relationship(), ad.WritePKINameFlag), - query.InIDs(query.End(), cardinality.DuplexToGraphIDs(certTemplates)...), + query.InIDs(query.End(), graph.DuplexToGraphIDs(certTemplates)...), // ct.authenticationenabled == true query.Equals(query.EndProperty(ad.AuthenticationEnabled.String()), true), query.Or( diff --git a/packages/go/analysis/ad/esc6.go b/packages/go/analysis/ad/esc6.go index 5a7f188bcb..eec4116359 100644 --- a/packages/go/analysis/ad/esc6.go +++ b/packages/go/analysis/ad/esc6.go @@ -46,7 +46,7 @@ func PostADCSESC6a(ctx context.Context, tx graph.Transaction, outC chan<- analys return nil } else { var ( - tempResults = cardinality.NewBitmap32() + tempResults = cardinality.NewBitmap64() validCertTemplates []*graph.Node enterpriseCAEnrollers = cache.GetEnterpriseCAEnrollers(enterpriseCA.ID) ) @@ -67,7 +67,7 @@ func PostADCSESC6a(ctx context.Context, tx graph.Transaction, outC chan<- analys } filterTempResultsForESC6(tx, domainsid, tempResults, groupExpansions, validCertTemplates, cache).Each( - func(value uint32) bool { + func(value uint64) bool { return channels.Submit(ctx, outC, analysis.CreatePostRelationshipJob{ FromID: graph.ID(value), ToID: domain.ID, @@ -86,13 +86,13 @@ func PostADCSESC6b(ctx context.Context, tx graph.Transaction, outC chan<- analys return err } else if !isUserSpecifiesSanEnabled { return nil - } else if ok := cache.HasUPNCertMappingInForest(domain.ID.Uint32()); !ok { + } else if ok := cache.HasUPNCertMappingInForest(domain.ID.Uint64()); !ok { return nil } else if publishedCertTemplates := cache.GetPublishedTemplateCache(enterpriseCA.ID); len(publishedCertTemplates) == 0 { return nil } else { var ( - tempResults = cardinality.NewBitmap32() + tempResults = cardinality.NewBitmap64() validCertTemplates []*graph.Node enterpriseCAEnrollers = cache.GetEnterpriseCAEnrollers(enterpriseCA.ID) ) @@ -120,7 +120,7 @@ func PostADCSESC6b(ctx context.Context, tx graph.Transaction, outC chan<- analys } filterTempResultsForESC6(tx, domainsid, tempResults, groupExpansions, validCertTemplates, cache).Each( - func(value uint32) bool { + func(value uint64) bool { return channels.Submit(ctx, outC, analysis.CreatePostRelationshipJob{ FromID: graph.ID(value), ToID: domain.ID, @@ -131,10 +131,10 @@ func PostADCSESC6b(ctx context.Context, tx graph.Transaction, outC chan<- analys return nil } -func filterTempResultsForESC6(tx graph.Transaction, domainsid string, tempResults cardinality.Duplex[uint32], groupExpansions impact.PathAggregator, validCertTemplates []*graph.Node, cache ADCSCache) cardinality.Duplex[uint32] { - principalsEnabledForESC6 := cardinality.NewBitmap32() +func filterTempResultsForESC6(tx graph.Transaction, domainsid string, tempResults cardinality.Duplex[uint64], groupExpansions impact.PathAggregator, validCertTemplates []*graph.Node, cache ADCSCache) cardinality.Duplex[uint64] { + principalsEnabledForESC6 := cardinality.NewBitmap64() - tempResults.Each(func(value uint32) bool { + tempResults.Each(func(value uint64) bool { sourceID := graph.ID(value) if resultNode, err := tx.Nodes().Filter(query.Equals(query.NodeID(), sourceID)).First(); err != nil { @@ -165,7 +165,7 @@ func filterTempResultsForESC6(tx graph.Transaction, domainsid string, tempResult } func principalControlsCertTemplate(tx graph.Transaction, domainsid string, principal, certTemplate *graph.Node, groupExpansions impact.PathAggregator, cache ADCSCache) bool { - principalID := principal.ID.Uint32() + principalID := principal.ID.Uint64() if expandedCertTemplateControllers := cache.GetExpandedCertTemplateControllers(certTemplate.ID); expandedCertTemplateControllers.Contains(principalID) { return true @@ -257,8 +257,8 @@ func GetADCSESC6EdgeComposition(ctx context.Context, db graph.Database, edge *gr paths = graph.PathSet{} path1Segments = map[graph.ID][]*graph.PathSegment{} path2Segments = []*graph.PathSegment{} - path1EnterpriseCAs = cardinality.NewBitmap32() - finalEnterpriseCAs = cardinality.NewBitmap32() + path1EnterpriseCAs = cardinality.NewBitmap64() + finalEnterpriseCAs = cardinality.NewBitmap64() ) if err := db.ReadTransaction(ctx, func(tx graph.Transaction) error { @@ -303,7 +303,7 @@ func GetADCSESC6EdgeComposition(ctx context.Context, db graph.Database, edge *gr }) lock.Lock() - path1EnterpriseCAs.Add(enterpriseCA.ID.Uint32()) + path1EnterpriseCAs.Add(enterpriseCA.ID.Uint64()) path1Segments[enterpriseCA.ID] = append(path1Segments[enterpriseCA.ID], terminal) lock.Unlock() @@ -351,7 +351,7 @@ func GetADCSESC6EdgeComposition(ctx context.Context, db graph.Database, edge *gr // add the ECA where the template is published (first ECA in the path in case of multi-tier hierarchy) to final list of ECAs terminal.Path().Walk(func(start, end *graph.Node, relationship *graph.Relationship) bool { if end.Kinds.ContainsOneOf(ad.EnterpriseCA) { - finalEnterpriseCAs.Add(end.ID.Uint32()) + finalEnterpriseCAs.Add(end.ID.Uint64()) return false } return true @@ -364,7 +364,7 @@ func GetADCSESC6EdgeComposition(ctx context.Context, db graph.Database, edge *gr } if paths.Len() > 0 { - finalEnterpriseCAs.Each(func(value uint32) bool { + finalEnterpriseCAs.Each(func(value uint64) bool { for _, segment := range path1Segments[graph.ID(value)] { paths.AddPath(segment.Path()) } @@ -446,7 +446,7 @@ func ADCSESC6Path2Pattern(domainId graph.ID) traversal.PatternContinuation { )) } -func ADCSESC6Path3Pattern(domainId graph.ID, enterpriseCAs cardinality.Duplex[uint32], edgeKind graph.Kind) traversal.PatternContinuation { +func ADCSESC6Path3Pattern(domainId graph.ID, enterpriseCAs cardinality.Duplex[uint64], edgeKind graph.Kind) traversal.PatternContinuation { return traversal.NewPattern(). OutboundWithDepth(0, 0, query.And( query.Kind(query.Relationship(), ad.MemberOf), @@ -459,7 +459,7 @@ func ADCSESC6Path3Pattern(domainId graph.ID, enterpriseCAs cardinality.Duplex[ui )). Outbound(query.And( query.KindIn(query.Relationship(), ad.PublishedTo), - query.InIDs(query.End(), cardinality.DuplexToGraphIDs(enterpriseCAs)...), + query.InIDs(query.End(), graph.DuplexToGraphIDs(enterpriseCAs)...), query.Kind(query.End(), ad.EnterpriseCA), )). OutboundWithDepth(0, 0, query.And( diff --git a/packages/go/analysis/ad/esc9.go b/packages/go/analysis/ad/esc9.go index bebfca7707..2ca4c3218f 100644 --- a/packages/go/analysis/ad/esc9.go +++ b/packages/go/analysis/ad/esc9.go @@ -33,9 +33,9 @@ import ( ) func PostADCSESC9a(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob, groupExpansions impact.PathAggregator, eca, domain *graph.Node, cache ADCSCache) error { - results := cardinality.NewBitmap32() + results := cardinality.NewBitmap64() - if ok := cache.HasWeakCertBindingInForest(domain.ID.Uint32()); !ok { + if ok := cache.HasWeakCertBindingInForest(domain.ID.Uint64()); !ok { return nil } else if publishedCertTemplates := cache.GetPublishedTemplateCache(eca.ID); len(publishedCertTemplates) == 0 { return nil @@ -61,12 +61,12 @@ func PostADCSESC9a(ctx context.Context, tx graph.Transaction, outC chan<- analys log.Warnf("Error getting start nodes for esc9a attacker nodes: %v", err) continue } else { - results.Or(cardinality.NodeIDsToDuplex(attackers)) + results.Or(graph.NodeIDsToDuplex(attackers)) } } } - results.Each(func(value uint32) bool { + results.Each(func(value uint64) bool { return channels.Submit(ctx, outC, analysis.CreatePostRelationshipJob{ FromID: graph.ID(value), ToID: domain.ID, @@ -79,9 +79,9 @@ func PostADCSESC9a(ctx context.Context, tx graph.Transaction, outC chan<- analys } func PostADCSESC9b(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob, groupExpansions impact.PathAggregator, eca, domain *graph.Node, cache ADCSCache) error { - results := cardinality.NewBitmap32() + results := cardinality.NewBitmap64() - if ok := cache.HasWeakCertBindingInForest(domain.ID.Uint32()); !ok { + if ok := cache.HasWeakCertBindingInForest(domain.ID.Uint64()); !ok { return nil } else if publishedCertTemplates := cache.GetPublishedTemplateCache(eca.ID); len(publishedCertTemplates) == 0 { return nil @@ -104,12 +104,12 @@ func PostADCSESC9b(ctx context.Context, tx graph.Transaction, outC chan<- analys log.Warnf("Error getting start nodes for esc9a attacker nodes: %v", err) continue } else { - results.Or(cardinality.NodeIDsToDuplex(attackers)) + results.Or(graph.NodeIDsToDuplex(attackers)) } } } - results.Each(func(value uint32) bool { + results.Each(func(value uint64) bool { return channels.Submit(ctx, outC, analysis.CreatePostRelationshipJob{ FromID: graph.ID(value), ToID: domain.ID, diff --git a/packages/go/analysis/ad/esc_shared.go b/packages/go/analysis/ad/esc_shared.go index 24f6340995..0bc7cb97aa 100644 --- a/packages/go/analysis/ad/esc_shared.go +++ b/packages/go/analysis/ad/esc_shared.go @@ -298,12 +298,12 @@ func findNodesByCertThumbprint(certThumbprint string, tx graph.Transaction, kind })) } -func expandNodeSliceToBitmapWithoutGroups(nodes []*graph.Node, groupExpansions impact.PathAggregator) cardinality.Duplex[uint32] { - var bitmap = cardinality.NewBitmap32() +func expandNodeSliceToBitmapWithoutGroups(nodes []*graph.Node, groupExpansions impact.PathAggregator) cardinality.Duplex[uint64] { + var bitmap = cardinality.NewBitmap64() for _, controller := range nodes { if controller.Kinds.ContainsOneOf(ad.Group) { - groupExpansions.Cardinality(controller.ID.Uint32()).(cardinality.Duplex[uint32]).Each(func(id uint32) bool { + groupExpansions.Cardinality(controller.ID.Uint64()).(cardinality.Duplex[uint64]).Each(func(id uint64) bool { //Check group expansions against each id, if cardinality is 0 than its not a group if groupExpansions.Cardinality(id).Cardinality() == 0 { bitmap.Add(id) @@ -311,7 +311,7 @@ func expandNodeSliceToBitmapWithoutGroups(nodes []*graph.Node, groupExpansions i return true }) } else { - bitmap.Add(controller.ID.Uint32()) + bitmap.Add(controller.ID.Uint64()) } } @@ -359,29 +359,29 @@ func certTemplateValidForUserVictim(certTemplate *graph.Node) bool { } } -func filterUserDNSResults(tx graph.Transaction, bitmap cardinality.Duplex[uint32], certTemplate *graph.Node) (cardinality.Duplex[uint32], error) { +func filterUserDNSResults(tx graph.Transaction, bitmap cardinality.Duplex[uint64], certTemplate *graph.Node) (cardinality.Duplex[uint64], error) { if userNodes, err := ops.FetchNodeSet(tx.Nodes().Filterf(func() graph.Criteria { return query.And( query.KindIn(query.Node(), ad.User), - query.InIDs(query.NodeID(), cardinality.DuplexToGraphIDs(bitmap)...), + query.InIDs(query.NodeID(), graph.DuplexToGraphIDs(bitmap)...), ) })); err != nil { if !graph.IsErrNotFound(err) { return nil, err } } else if len(userNodes) > 0 && !certTemplateValidForUserVictim(certTemplate) { - bitmap.Xor(cardinality.NodeSetToDuplex(userNodes)) + bitmap.Xor(graph.NodeSetToDuplex(userNodes)) } return bitmap, nil } -func getVictimBitmap(groupExpansions impact.PathAggregator, certTemplateControllers, ecaControllers []*graph.Node, specialGroupHasTemplateEnroll, specialGroupHasECAEnroll bool) cardinality.Duplex[uint32] { +func getVictimBitmap(groupExpansions impact.PathAggregator, certTemplateControllers, ecaControllers []*graph.Node, specialGroupHasTemplateEnroll, specialGroupHasECAEnroll bool) cardinality.Duplex[uint64] { // Expand controllers for the eca + template completely because we don't do group shortcutting here var ( templateBitmap = expandNodeSliceToBitmapWithoutGroups(certTemplateControllers, groupExpansions) ecaBitmap = expandNodeSliceToBitmapWithoutGroups(ecaControllers, groupExpansions) - victimBitmap = cardinality.NewBitmap32() + victimBitmap = cardinality.NewBitmap64() ) // If no special group has enroll neither the template or eca then return the common nodes among the enrollers diff --git a/packages/go/analysis/ad/membership.go b/packages/go/analysis/ad/membership.go index fa3ca52789..181c3457bf 100644 --- a/packages/go/analysis/ad/membership.go +++ b/packages/go/analysis/ad/membership.go @@ -38,10 +38,10 @@ func ResolveAllGroupMemberships(ctx context.Context, db graph.Database, addition adGroupIDs []graph.ID searchCriteria = []graph.Criteria{query.KindIn(query.Relationship(), ad.MemberOf, ad.MemberOfLocalGroup)} - traversalMap = cardinality.ThreadSafeDuplex(cardinality.NewBitmap32()) + traversalMap = cardinality.ThreadSafeDuplex(cardinality.NewBitmap64()) traversalInst = traversal.NewIDTraversal(db, analysis.MaximumDatabaseParallelWorkers) - memberships = impact.NewThreadSafeAggregator(impact.NewIDA(func() cardinality.Provider[uint32] { - return cardinality.NewBitmap32() + memberships = impact.NewThreadSafeAggregator(impact.NewIDA(func() cardinality.Provider[uint64] { + return cardinality.NewBitmap64() })) ) @@ -65,7 +65,7 @@ func ResolveAllGroupMemberships(ctx context.Context, db graph.Database, addition log.Infof("Collected %d groups to resolve", len(adGroupIDs)) for _, adGroupID := range adGroupIDs { - if traversalMap.Contains(adGroupID.Uint32()) { + if traversalMap.Contains(adGroupID.Uint64()) { continue } @@ -80,7 +80,7 @@ func ResolveAllGroupMemberships(ctx context.Context, db graph.Database, addition if err := nextQuery.FetchTriples( func(cursor graph.Cursor[graph.RelationshipTripleResult]) error { for nextTriple := range cursor.Chan() { - if traversalMap.CheckedAdd(nextTriple.StartID.Uint32()) { + if traversalMap.CheckedAdd(nextTriple.StartID.Uint64()) { nextSegments = append(nextSegments, segment.Descend(nextTriple.StartID, nextTriple.ID)) } else { memberships.AddShortcut(segment.Descend(nextTriple.StartID, nextTriple.ID)) @@ -141,16 +141,16 @@ func newTraversalQuery(tx graph.Transaction, segment *graph.IDSegment, direction return tx.Relationships().Filter(query.And(traversalCriteria...)), nil } -func NodeDuplexByKinds(ctx context.Context, db graph.Database, nodes cardinality.Duplex[uint32]) (*cardinality.ThreadSafeKindBitmap, error) { - nodesByKind := cardinality.NewThreadSafeKindBitmap() +func NodeDuplexByKinds(ctx context.Context, db graph.Database, nodes cardinality.Duplex[uint64]) (*graph.ThreadSafeKindBitmap, error) { + nodesByKind := graph.NewThreadSafeKindBitmap() return nodesByKind, db.ReadTransaction(ctx, func(tx graph.Transaction) error { return tx.Nodes().Filter( - query.InIDs(query.NodeID(), graph.Uint32SliceToIDs(nodes.Slice())...), + query.InIDs(query.NodeID(), graph.Uint64SliceToIDs(nodes.Slice())...), ).FetchKinds(func(cursor graph.Cursor[graph.KindsResult]) error { for nextResult := range cursor.Chan() { for _, kind := range nextResult.Kinds { - nodesByKind.Add(kind, nextResult.ID.Uint32()) + nodesByKind.Add(kind, nextResult.ID.Uint64()) } } @@ -159,8 +159,8 @@ func NodeDuplexByKinds(ctx context.Context, db graph.Database, nodes cardinality }) } -func FetchPathMembers(ctx context.Context, db graph.Database, root graph.ID, direction graph.Direction, queryCriteria ...graph.Criteria) (cardinality.Duplex[uint32], error) { - traversalMap := cardinality.ThreadSafeDuplex(cardinality.NewBitmap32()) +func FetchPathMembers(ctx context.Context, db graph.Database, root graph.ID, direction graph.Direction, queryCriteria ...graph.Criteria) (cardinality.Duplex[uint64], error) { + traversalMap := cardinality.ThreadSafeDuplex(cardinality.NewBitmap64()) return traversalMap, traversal.NewIDTraversal(db, analysis.MaximumDatabaseParallelWorkers).BreadthFirst(ctx, traversal.IDPlan{ Root: root, @@ -174,7 +174,7 @@ func FetchPathMembers(ctx context.Context, db graph.Database, root graph.ID, dir for nextTriple := range cursor.Chan() { if nextID, err := direction.PickReverseID(nextTriple.StartID, nextTriple.EndID); err != nil { return err - } else if traversalMap.CheckedAdd(nextID.Uint32()) { + } else if traversalMap.CheckedAdd(nextID.Uint64()) { nextSegments = append(nextSegments, segment.Descend(nextID, nextTriple.ID)) } } diff --git a/packages/go/analysis/ad/post.go b/packages/go/analysis/ad/post.go index 64edf74f27..69f1a4a21b 100644 --- a/packages/go/analysis/ad/post.go +++ b/packages/go/analysis/ad/post.go @@ -58,6 +58,7 @@ func PostProcessedRelationships() []graph.Kind { ad.ADCSESC9a, ad.ADCSESC13, ad.EnrollOnBehalfOf, + ad.SyncedToEntraUser, } } @@ -77,7 +78,7 @@ func PostSyncLAPSPassword(ctx context.Context, db graph.Database, groupExpansion return err } else { for _, computer := range computers { - lapsSyncers.Each(func(value uint32) bool { + lapsSyncers.Each(func(value uint64) bool { channels.Submit(ctx, outC, analysis.CreatePostRelationshipJob{ FromID: graph.ID(value), ToID: computer, @@ -110,7 +111,7 @@ func PostDCSync(ctx context.Context, db graph.Database, groupExpansions impact.P } else if dcSyncers.Cardinality() == 0 { return nil } else { - dcSyncers.Each(func(value uint32) bool { + dcSyncers.Each(func(value uint64) bool { channels.Submit(ctx, outC, analysis.CreatePostRelationshipJob{ FromID: graph.ID(value), ToID: innerDomain.ID, @@ -177,7 +178,7 @@ func fetchCollectedDomainNodes(ctx context.Context, db graph.Database) ([]*graph }) } -func getLAPSSyncers(tx graph.Transaction, domain *graph.Node, groupExpansions impact.PathAggregator) (cardinality.Duplex[uint32], error) { +func getLAPSSyncers(tx graph.Transaction, domain *graph.Node, groupExpansions impact.PathAggregator) (cardinality.Duplex[uint64], error) { var ( getChangesQuery = analysis.FromEntityToEntityWithRelationshipKind(tx, domain, ad.GetChanges) getChangesFilteredQuery = analysis.FromEntityToEntityWithRelationshipKind(tx, domain, ad.GetChangesInFilteredSet) @@ -197,7 +198,7 @@ func getLAPSSyncers(tx graph.Transaction, domain *graph.Node, groupExpansions im } } -func getDCSyncers(tx graph.Transaction, domain *graph.Node, groupExpansions impact.PathAggregator) (cardinality.Duplex[uint32], error) { +func getDCSyncers(tx graph.Transaction, domain *graph.Node, groupExpansions impact.PathAggregator) (cardinality.Duplex[uint64], error) { var ( getChangesQuery = analysis.FromEntityToEntityWithRelationshipKind(tx, domain, ad.GetChanges) getChangesAllQuery = analysis.FromEntityToEntityWithRelationshipKind(tx, domain, ad.GetChangesAll) @@ -469,15 +470,15 @@ func HasRemoteInteractiveLogonPrivilege(tx graph.Transaction, groupId, computerI return true } -func FetchLocalGroupBitmapForComputer(tx graph.Transaction, computer graph.ID, suffix string) (cardinality.Duplex[uint32], error) { +func FetchLocalGroupBitmapForComputer(tx graph.Transaction, computer graph.ID, suffix string) (cardinality.Duplex[uint64], error) { if members, err := FetchLocalGroupMembership(tx, computer, suffix); err != nil { if graph.IsErrNotFound(err) { - return cardinality.NewBitmap32(), nil + return cardinality.NewBitmap64(), nil } return nil, err } else { - return cardinality.NodeSetToDuplex(members), nil + return graph.NodeSetToDuplex(members), nil } } @@ -492,9 +493,9 @@ func ExpandAllRDPLocalGroups(ctx context.Context, db graph.Database) (impact.Pat )) } -func FetchCanRDPEntityBitmapForComputer(tx graph.Transaction, computer graph.ID, localGroupExpansions impact.PathAggregator, enforceURA bool, citrixEnabled bool) (cardinality.Duplex[uint32], error) { +func FetchCanRDPEntityBitmapForComputer(tx graph.Transaction, computer graph.ID, localGroupExpansions impact.PathAggregator, enforceURA bool, citrixEnabled bool) (cardinality.Duplex[uint64], error) { if remoteDesktopUsers, err := FetchRemoteDesktopUsersBitmapForComputer(tx, computer, localGroupExpansions, enforceURA); err != nil { - return cardinality.NewBitmap32(), err + return cardinality.NewBitmap64(), err } else if remoteDesktopUsers.Cardinality() == 0 || !citrixEnabled { return remoteDesktopUsers, nil } else { @@ -504,10 +505,10 @@ func FetchCanRDPEntityBitmapForComputer(tx graph.Transaction, computer graph.ID, // "Direct Access Users" is a group that Citrix creates. If the group does not exist, then the computer does not have Citrix installed and post-processing logic can continue by enumerating the "Remote Desktop Users" AD group. return remoteDesktopUsers, nil } - return cardinality.NewBitmap32(), err + return cardinality.NewBitmap64(), err } else { - if dauGroupMembers, ok := localGroupExpansions.Cardinality(directAccessUsersGroup.ID.Uint32()).(cardinality.Duplex[uint32]); !ok { - return cardinality.NewBitmap32(), errors.New("type assertion failed in FetchCanRDPEntityBitmapForComputer") + if dauGroupMembers, ok := localGroupExpansions.Cardinality(directAccessUsersGroup.ID.Uint64()).(cardinality.Duplex[uint64]); !ok { + return cardinality.NewBitmap64(), errors.New("type assertion failed in FetchCanRDPEntityBitmapForComputer") } else { dauGroupMembers.And(remoteDesktopUsers) return dauGroupMembers, nil @@ -517,10 +518,10 @@ func FetchCanRDPEntityBitmapForComputer(tx graph.Transaction, computer graph.ID, } // returns a bitmap containing the ID's of all entities that have RDP privileges to the specified computer via membership to the "Remote Desktop Users" AD group -func FetchRemoteDesktopUsersBitmapForComputer(tx graph.Transaction, computer graph.ID, localGroupExpansions impact.PathAggregator, enforceURA bool) (cardinality.Duplex[uint32], error) { +func FetchRemoteDesktopUsersBitmapForComputer(tx graph.Transaction, computer graph.ID, localGroupExpansions impact.PathAggregator, enforceURA bool) (cardinality.Duplex[uint64], error) { if rdpLocalGroup, err := FetchComputerLocalGroupBySIDSuffix(tx, computer, RDPGroupSuffix); err != nil { if graph.IsErrNotFound(err) { - return cardinality.NewBitmap32(), nil + return cardinality.NewBitmap64(), nil } return nil, err @@ -547,11 +548,11 @@ func ComputerHasURACollection(tx graph.Transaction, computerID graph.ID) bool { } } -func ProcessRDPWithUra(tx graph.Transaction, rdpLocalGroup *graph.Node, computer graph.ID, localGroupExpansions impact.PathAggregator) (cardinality.Duplex[uint32], error) { - rdpLocalGroupMembers := localGroupExpansions.Cardinality(rdpLocalGroup.ID.Uint32()).(cardinality.Duplex[uint32]) +func ProcessRDPWithUra(tx graph.Transaction, rdpLocalGroup *graph.Node, computer graph.ID, localGroupExpansions impact.PathAggregator) (cardinality.Duplex[uint64], error) { + rdpLocalGroupMembers := localGroupExpansions.Cardinality(rdpLocalGroup.ID.Uint64()).(cardinality.Duplex[uint64]) // Shortcut opportunity: see if the RDP group has RIL privilege. If it does, get the first degree members and return those ids, since everything in RDP group has CanRDP privs. No reason to look any further if HasRemoteInteractiveLogonPrivilege(tx, rdpLocalGroup.ID, computer) { - firstDegreeMembers := cardinality.NewBitmap32() + firstDegreeMembers := cardinality.NewBitmap64() return firstDegreeMembers, tx.Relationships().Filter( query.And( @@ -561,7 +562,7 @@ func ProcessRDPWithUra(tx graph.Transaction, rdpLocalGroup *graph.Node, computer ), ).FetchTriples(func(cursor graph.Cursor[graph.RelationshipTripleResult]) error { for result := range cursor.Chan() { - firstDegreeMembers.Add(result.StartID.Uint32()) + firstDegreeMembers.Add(result.StartID.Uint64()) } return cursor.Error() }) @@ -569,17 +570,17 @@ func ProcessRDPWithUra(tx graph.Transaction, rdpLocalGroup *graph.Node, computer return nil, err } else { var ( - rdpEntities = cardinality.NewBitmap32() - secondaryTargets = cardinality.NewBitmap32() + rdpEntities = cardinality.NewBitmap64() + secondaryTargets = cardinality.NewBitmap64() ) // Attempt 2: look at each RIL entity directly and see if it has membership to the RDP group. If not, and it's a group, expand its membership for further processing for _, entity := range baseRilEntities { - if rdpLocalGroupMembers.Contains(entity.ID.Uint32()) { + if rdpLocalGroupMembers.Contains(entity.ID.Uint64()) { // If we have membership to the RDP group, then this is a valid CanRDP entity - rdpEntities.Add(entity.ID.Uint32()) + rdpEntities.Add(entity.ID.Uint64()) } else if entity.Kinds.ContainsOneOf(ad.Group, ad.LocalGroup) { - secondaryTargets.Or(localGroupExpansions.Cardinality(entity.ID.Uint32()).(cardinality.Duplex[uint32])) + secondaryTargets.Or(localGroupExpansions.Cardinality(entity.ID.Uint64()).(cardinality.Duplex[uint64])) } } diff --git a/packages/go/analysis/ad/queries.go b/packages/go/analysis/ad/queries.go index e6132e3b4b..edcfb01225 100644 --- a/packages/go/analysis/ad/queries.go +++ b/packages/go/analysis/ad/queries.go @@ -1417,7 +1417,7 @@ func FetchLocalGroupCompleteness(tx graph.Transaction, domainSIDs ...string) (fl var ( activeComputerCount = float64(computers.Len()) activeComputerCountWithAdmins = float64(0) - computerBmp = cardinality.NewBitmap32() + computerBmp = cardinality.NewBitmap64() ) if err := tx.Relationships().Filterf(func() graph.Criteria { @@ -1427,7 +1427,7 @@ func FetchLocalGroupCompleteness(tx graph.Transaction, domainSIDs ...string) (fl ) }).Fetch(func(cursor graph.Cursor[*graph.Relationship]) error { for rel := range cursor.Chan() { - computerBmp.Add(rel.EndID.Uint32()) + computerBmp.Add(rel.EndID.Uint64()) } return nil @@ -1699,12 +1699,12 @@ func fetchFirstDegreeNodes(tx graph.Transaction, targetNode *graph.Node, relKind )) } -func FetchAttackersForEscalations9and10(tx graph.Transaction, victimBitmap cardinality.Duplex[uint32], scenarioB bool) ([]graph.ID, error) { +func FetchAttackersForEscalations9and10(tx graph.Transaction, victimBitmap cardinality.Duplex[uint64], scenarioB bool) ([]graph.ID, error) { if attackers, err := ops.FetchStartNodeIDs(tx.Relationships().Filterf(func() graph.Criteria { criteria := query.And( query.KindIn(query.Start(), ad.Group, ad.User, ad.Computer), query.KindIn(query.Relationship(), ad.GenericAll, ad.GenericWrite, ad.Owns, ad.WriteOwner, ad.WriteDACL), - query.InIDs(query.EndID(), cardinality.DuplexToGraphIDs(victimBitmap)...), + query.InIDs(query.EndID(), graph.DuplexToGraphIDs(victimBitmap)...), ) if scenarioB { return query.And(criteria, query.KindIn(query.End(), ad.Computer)) diff --git a/packages/go/analysis/azure/post.go b/packages/go/analysis/azure/post.go index 3d662d58a0..2b05643a66 100644 --- a/packages/go/analysis/azure/post.go +++ b/packages/go/analysis/azure/post.go @@ -19,9 +19,9 @@ package azure import ( "context" "fmt" + "github.com/specterops/bloodhound/dawgs/cardinality" "strings" - "github.com/RoaringBitmap/roaring" "github.com/specterops/bloodhound/analysis" "github.com/specterops/bloodhound/dawgs/graph" "github.com/specterops/bloodhound/dawgs/ops" @@ -131,6 +131,7 @@ func AzurePostProcessedRelationships() []graph.Kind { azure.AZMGAddSecret, azure.AZMGGrantAppRoles, azure.AZMGGrantRole, + azure.SyncedToADUser, } } @@ -739,19 +740,19 @@ func resetPassword(_ context.Context, _ graph.Database, operation analysis.StatT if targets, err := resetPasswordEndNodeBitmapForRole(role, roleAssignments); err != nil { return fmt.Errorf("unable to continue processing azresetpassword for tenant node %d: %w", tenant.ID, err) } else { - iter := targets.Iterator() - for iter.HasNext() { + targets.Each(func(nextID uint64) bool { nextJob := analysis.CreatePostRelationshipJob{ FromID: role.ID, - ToID: graph.ID(iter.Next()), + ToID: graph.ID(nextID), Kind: azure.ResetPassword, } if !channels.Submit(ctx, outC, nextJob) { - return nil + return false } - } + return true + }) } } } @@ -759,13 +760,13 @@ func resetPassword(_ context.Context, _ graph.Database, operation analysis.StatT }) } -func resetPasswordEndNodeBitmapForRole(role *graph.Node, roleAssignments RoleAssignments) (*roaring.Bitmap, error) { +func resetPasswordEndNodeBitmapForRole(role *graph.Node, roleAssignments RoleAssignments) (cardinality.Duplex[uint64], error) { if roleTemplateIDProp := role.Properties.Get(azure.RoleTemplateID.String()); roleTemplateIDProp.IsNil() { return nil, fmt.Errorf("role node %d is missing property %s", role.ID, azure.RoleTemplateID) } else if roleTemplateID, err := roleTemplateIDProp.String(); err != nil { return nil, fmt.Errorf("role node %d property %s is not a string", role.ID, azure.RoleTemplateID) } else { - result := roaring.New() + result := cardinality.NewBitmap64() switch roleTemplateID { case azure.CompanyAdministratorRole, azure.PrivilegedAuthenticationAdministratorRole, azure.PartnerTier2SupportRole: result.Or(roleAssignments.Users()) @@ -792,57 +793,59 @@ func resetPasswordEndNodeBitmapForRole(role *graph.Node, roleAssignments RoleAss func globalAdmins(roleAssignments RoleAssignments, tenant *graph.Node, operation analysis.StatTrackedOperation[analysis.CreatePostRelationshipJob]) { operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { - iter := roleAssignments.PrincipalsWithRole(azure.CompanyAdministratorRole).Iterator() - for iter.HasNext() { + roleAssignments.PrincipalsWithRole(azure.CompanyAdministratorRole).Each(func(nextID uint64) bool { nextJob := analysis.CreatePostRelationshipJob{ - FromID: graph.ID(iter.Next()), + FromID: graph.ID(nextID), ToID: tenant.ID, Kind: azure.GlobalAdmin, } if !channels.Submit(ctx, outC, nextJob) { - return nil + return false } - } + return true + }) + return nil }) } func privilegedRoleAdmins(roleAssignments RoleAssignments, tenant *graph.Node, operation analysis.StatTrackedOperation[analysis.CreatePostRelationshipJob]) { operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { - iter := roleAssignments.PrincipalsWithRole(azure.PrivilegedRoleAdministratorRole).Iterator() - for iter.HasNext() { + roleAssignments.PrincipalsWithRole(azure.PrivilegedRoleAdministratorRole).Each(func(nextID uint64) bool { nextJob := analysis.CreatePostRelationshipJob{ - FromID: graph.ID(iter.Next()), + FromID: graph.ID(nextID), ToID: tenant.ID, Kind: azure.PrivilegedRoleAdmin, } if !channels.Submit(ctx, outC, nextJob) { - return nil + return false } - } + return true + }) + return nil }) } func privilegedAuthAdmins(roleAssignments RoleAssignments, tenant *graph.Node, operation analysis.StatTrackedOperation[analysis.CreatePostRelationshipJob]) { operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { - iter := roleAssignments.PrincipalsWithRole(azure.PrivilegedAuthenticationAdministratorRole).Iterator() - for iter.HasNext() { + roleAssignments.PrincipalsWithRole(azure.PrivilegedAuthenticationAdministratorRole).Each(func(nextID uint64) bool { nextJob := analysis.CreatePostRelationshipJob{ - FromID: graph.ID(iter.Next()), + FromID: graph.ID(nextID), ToID: tenant.ID, Kind: azure.PrivilegedAuthAdmin, } if !channels.Submit(ctx, outC, nextJob) { - return nil + return false } - } + return true + }) return nil }) @@ -855,19 +858,20 @@ func addMembers(roleAssignments RoleAssignments, operation analysis.StatTrackedO innerGroupID := tenantGroupID innerGroup := tenantGroup operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { - iter := roleAssignments.UsersWithRole(AddMemberAllGroupsTargetRoles()...).Iterator() - for iter.HasNext() { + roleAssignments.UsersWithRole(AddMemberAllGroupsTargetRoles()...).Each(func(nextID uint64) bool { nextJob := analysis.CreatePostRelationshipJob{ - FromID: graph.ID(iter.Next()), + FromID: graph.ID(nextID), ToID: innerGroupID, Kind: azure.AddMembers, } if !channels.Submit(ctx, outC, nextJob) { - return nil + return false } - } + return true + }) + return nil }) @@ -879,19 +883,19 @@ func addMembers(roleAssignments RoleAssignments, operation analysis.StatTrackedO return err } } else if !isRoleAssignable { - iter := roleAssignments.UsersWithRole(AddMemberGroupNotRoleAssignableTargetRoles()...).Iterator() - for iter.HasNext() { + roleAssignments.UsersWithRole(AddMemberGroupNotRoleAssignableTargetRoles()...).Each(func(nextID uint64) bool { nextJob := analysis.CreatePostRelationshipJob{ - FromID: graph.ID(iter.Next()), + FromID: graph.ID(nextID), ToID: innerGroupID, Kind: azure.AddMembers, } if !channels.Submit(ctx, outC, nextJob) { - return nil + return false } - } + return true + }) } return nil diff --git a/packages/go/analysis/azure/post_test.go b/packages/go/analysis/azure/post_test.go index 3c3c0cd98f..040f9b8834 100644 --- a/packages/go/analysis/azure/post_test.go +++ b/packages/go/analysis/azure/post_test.go @@ -18,9 +18,9 @@ package azure_test import ( "context" + "github.com/specterops/bloodhound/dawgs/cardinality" "testing" - "github.com/RoaringBitmap/roaring" "github.com/bloodhoundad/azurehound/v2/constants" "github.com/specterops/bloodhound/dawgs/graph" graph_mocks "github.com/specterops/bloodhound/dawgs/graph/mocks" @@ -43,16 +43,16 @@ var ( // setupRoleAssignments is used to create a testable RoleAssignments struct. It is used in all RoleAssignments tests // and may require adjusting tests if modified func setupRoleAssignments() azure.RoleAssignments { - roleMap := map[string]*roaring.Bitmap{ - constants.GlobalAdministratorRoleID: roaring.New(), - constants.ReportsReaderRoleID: roaring.New(), - constants.HelpdeskAdministratorRoleID: roaring.New(), - constants.PartnerTier1SupportRoleID: roaring.New(), + roleMap := map[string]cardinality.Duplex[uint64]{ + constants.GlobalAdministratorRoleID: cardinality.NewBitmap64(), + constants.ReportsReaderRoleID: cardinality.NewBitmap64(), + constants.HelpdeskAdministratorRoleID: cardinality.NewBitmap64(), + constants.PartnerTier1SupportRoleID: cardinality.NewBitmap64(), } - roleMap[constants.GlobalAdministratorRoleID].Add(uint32(user.ID)) - roleMap[constants.ReportsReaderRoleID].Add(uint32(group.ID)) - roleMap[constants.HelpdeskAdministratorRoleID].Add(uint32(group.ID)) - roleMap[constants.PartnerTier1SupportRoleID].Add(uint32(app.ID)) + roleMap[constants.GlobalAdministratorRoleID].Add(uint64(user.ID)) + roleMap[constants.ReportsReaderRoleID].Add(uint64(group.ID)) + roleMap[constants.HelpdeskAdministratorRoleID].Add(uint64(group.ID)) + roleMap[constants.PartnerTier1SupportRoleID].Add(uint64(app.ID)) return azure.RoleAssignments{ // user2 has no roles! this is intentional @@ -72,16 +72,16 @@ func TestRoleAssignments_NodeHasRole(t *testing.T) { func TestRoleAssignments_UsersWithoutRoles(t *testing.T) { assignments := setupRoleAssignments() - assert.False(t, assignments.UsersWithoutRoles().Contains(uint32(user.ID))) - assert.True(t, assignments.UsersWithoutRoles().Contains(uint32(user2.ID))) + assert.False(t, assignments.UsersWithoutRoles().Contains(uint64(user.ID))) + assert.True(t, assignments.UsersWithoutRoles().Contains(uint64(user2.ID))) } func TestRoleAssignments_NodesWithRole(t *testing.T) { assignments := setupRoleAssignments() - assert.True(t, assignments.PrincipalsWithRole(constants.ReportsReaderRoleID, constants.GlobalAdministratorRoleID).Contains(uint32(user.ID))) - assert.True(t, assignments.PrincipalsWithRole(constants.ReportsReaderRoleID, constants.GlobalAdministratorRoleID).Contains(uint32(group.ID))) - assert.True(t, assignments.PrincipalsWithRole(constants.ReportsReaderRoleID, constants.HelpdeskAdministratorRoleID).Contains(uint32(group.ID))) - assert.False(t, assignments.PrincipalsWithRole(constants.ReportsReaderRoleID).Contains(uint32(user.ID))) + assert.True(t, assignments.PrincipalsWithRole(constants.ReportsReaderRoleID, constants.GlobalAdministratorRoleID).Contains(uint64(user.ID))) + assert.True(t, assignments.PrincipalsWithRole(constants.ReportsReaderRoleID, constants.GlobalAdministratorRoleID).Contains(uint64(group.ID))) + assert.True(t, assignments.PrincipalsWithRole(constants.ReportsReaderRoleID, constants.HelpdeskAdministratorRoleID).Contains(uint64(group.ID))) + assert.False(t, assignments.PrincipalsWithRole(constants.ReportsReaderRoleID).Contains(uint64(user.ID))) } func TestRoleAssignments_NodesWithRolesExclusive(t *testing.T) { diff --git a/packages/go/analysis/azure/role.go b/packages/go/analysis/azure/role.go index 49a3a58767..e467ddb489 100644 --- a/packages/go/analysis/azure/role.go +++ b/packages/go/analysis/azure/role.go @@ -19,9 +19,9 @@ package azure import ( "context" "fmt" + "github.com/specterops/bloodhound/dawgs/cardinality" "slices" - "github.com/RoaringBitmap/roaring" "github.com/specterops/bloodhound/dawgs/graph" "github.com/specterops/bloodhound/dawgs/ops" "github.com/specterops/bloodhound/dawgs/query" @@ -88,33 +88,34 @@ func (s RoleAssignmentMap) HasRole(id graph.ID, roleTemplateIDs ...string) bool type RoleAssignments struct { Principals graph.NodeKindSet - RoleMap map[string]*roaring.Bitmap + RoleMap map[string]cardinality.Duplex[uint64] } -func (s RoleAssignments) GetNodeKindSet(bm *roaring.Bitmap) graph.NodeKindSet { - var ( - result = graph.NewNodeKindSet() - iter = bm.Iterator() - ) - for iter.HasNext() { - node := s.Principals.GetNode(graph.ID(iter.Next())) +func (s RoleAssignments) GetNodeKindSet(bm cardinality.Duplex[uint64]) graph.NodeKindSet { + result := graph.NewNodeKindSet() + + bm.Each(func(nextID uint64) bool { + node := s.Principals.GetNode(graph.ID(nextID)) result.Add(node) - } + + return true + }) + return result } -func (s RoleAssignments) GetNodeSet(bm *roaring.Bitmap) graph.NodeSet { +func (s RoleAssignments) GetNodeSet(bm cardinality.Duplex[uint64]) graph.NodeSet { return s.GetNodeKindSet(bm).AllNodes() } -func (s RoleAssignments) Users() *roaring.Bitmap { +func (s RoleAssignments) Users() cardinality.Duplex[uint64] { return s.Principals.Get(azure.User).IDBitmap() } -func (s RoleAssignments) UsersWithAnyRole() *roaring.Bitmap { +func (s RoleAssignments) UsersWithAnyRole() cardinality.Duplex[uint64] { users := s.Users() - principalsWithRoles := roaring.New() + principalsWithRoles := cardinality.NewBitmap64() for _, bitmap := range s.RoleMap { principalsWithRoles.Or(bitmap) } @@ -122,27 +123,27 @@ func (s RoleAssignments) UsersWithAnyRole() *roaring.Bitmap { return principalsWithRoles } -func (s RoleAssignments) UsersWithoutRoles() *roaring.Bitmap { +func (s RoleAssignments) UsersWithoutRoles() cardinality.Duplex[uint64] { result := s.Users() result.AndNot(s.UsersWithAnyRole()) return result } -func (s RoleAssignments) UsersWithRole(roleTemplateIDs ...string) *roaring.Bitmap { +func (s RoleAssignments) UsersWithRole(roleTemplateIDs ...string) cardinality.Duplex[uint64] { result := s.PrincipalsWithRole(roleTemplateIDs...) result.And(s.Users()) return result } -func (s RoleAssignments) UsersWithRolesExclusive(roleTemplateIDs ...string) *roaring.Bitmap { +func (s RoleAssignments) UsersWithRolesExclusive(roleTemplateIDs ...string) cardinality.Duplex[uint64] { result := s.PrincipalsWithRolesExclusive(roleTemplateIDs...) result.And(s.Users()) return result } // PrincipalsWithRole returns a roaring bitmap of principals that have been assigned one or more of the matching roles from list of role template IDs -func (s RoleAssignments) PrincipalsWithRole(roleTemplateIDs ...string) *roaring.Bitmap { - result := roaring.New() +func (s RoleAssignments) PrincipalsWithRole(roleTemplateIDs ...string) cardinality.Duplex[uint64] { + result := cardinality.NewBitmap64() for _, roleTemplateID := range roleTemplateIDs { if bitmap, ok := s.RoleMap[roleTemplateID]; ok { result.Or(bitmap) @@ -152,10 +153,10 @@ func (s RoleAssignments) PrincipalsWithRole(roleTemplateIDs ...string) *roaring. } // PrincipalsWithRole returns a roaring bitmap of principals that have been assigned one or more of the matching roles from list of role template IDs but excluding principals with non-matching roles -func (s RoleAssignments) PrincipalsWithRolesExclusive(roleTemplateIDs ...string) *roaring.Bitmap { +func (s RoleAssignments) PrincipalsWithRolesExclusive(roleTemplateIDs ...string) cardinality.Duplex[uint64] { var ( - result = roaring.New() - excludedPrincipals = roaring.New() + result = cardinality.NewBitmap64() + excludedPrincipals = cardinality.NewBitmap64() ) for roleID, bitmap := range s.RoleMap { if slices.Contains(roleTemplateIDs, roleID) { @@ -177,7 +178,7 @@ func (s RoleAssignments) NodesWithRolesExclusive(roleTemplateIDs ...string) grap func (s RoleAssignments) NodeHasRole(id graph.ID, roleTemplateIDs ...string) bool { for _, roleID := range roleTemplateIDs { if bm, ok := s.RoleMap[roleID]; ok { - if bm.Contains(uint32(id)) { + if bm.Contains(id.Uint64()) { return true } } @@ -193,7 +194,7 @@ func initTenantRoleAssignments(tx graph.Transaction, tenant *graph.Node) (RoleAs } else { return RoleAssignments{ Principals: roleMembers.KindSet(), - RoleMap: make(map[string]*roaring.Bitmap), + RoleMap: make(map[string]cardinality.Duplex[uint64]), }, nil } } diff --git a/packages/go/analysis/impact/aggregator.go b/packages/go/analysis/impact/aggregator.go index 75300f34d8..f5f6586d82 100644 --- a/packages/go/analysis/impact/aggregator.go +++ b/packages/go/analysis/impact/aggregator.go @@ -32,27 +32,27 @@ import ( // of nodes by calling the cardinality functions of the aggregator. Resolution is accomplished using a recursive // depth-first strategy. type Aggregator struct { - resolved cardinality.Duplex[uint32] - cardinalities *graph.IndexedSlice[uint32, cardinality.Provider[uint32]] - dependencies map[uint32]cardinality.Duplex[uint32] - newCardinalityProvider cardinality.ProviderConstructor[uint32] + resolved cardinality.Duplex[uint64] + cardinalities *graph.IndexedSlice[uint64, cardinality.Provider[uint64]] + dependencies map[uint64]cardinality.Duplex[uint64] + newCardinalityProvider cardinality.ProviderConstructor[uint64] } -func NewAggregator(newCardinalityProvider cardinality.ProviderConstructor[uint32]) Aggregator { +func NewAggregator(newCardinalityProvider cardinality.ProviderConstructor[uint64]) Aggregator { return Aggregator{ - cardinalities: graph.NewIndexedSlice[uint32, cardinality.Provider[uint32]](), - dependencies: map[uint32]cardinality.Duplex[uint32]{}, - resolved: cardinality.NewBitmap32(), + cardinalities: graph.NewIndexedSlice[uint64, cardinality.Provider[uint64]](), + dependencies: map[uint64]cardinality.Duplex[uint64]{}, + resolved: cardinality.NewBitmap64(), newCardinalityProvider: newCardinalityProvider, } } // pushDependency adds a new dependency for the given target. -func (s Aggregator) pushDependency(target, dependency uint32) { +func (s Aggregator) pushDependency(target, dependency uint64) { if dependencies, hasDependencies := s.dependencies[target]; hasDependencies { dependencies.Add(dependency) } else { - newDependencies := cardinality.NewBitmap32() + newDependencies := cardinality.NewBitmap64() newDependencies.Add(dependency) s.dependencies[target] = newDependencies @@ -61,7 +61,7 @@ func (s Aggregator) pushDependency(target, dependency uint32) { // popDependencies will take the simplex cardinality provider reference for the given target, remove it from the // containing map in the aggregator and then return it -func (s Aggregator) popDependencies(targetUint32ID uint32) []uint32 { +func (s Aggregator) popDependencies(targetUint32ID uint64) []uint64 { dependencies, hasDependencies := s.dependencies[targetUint32ID] delete(s.dependencies, targetUint32ID) @@ -72,38 +72,38 @@ func (s Aggregator) popDependencies(targetUint32ID uint32) []uint32 { return nil } -func (s Aggregator) getImpact(targetUint32ID uint32) cardinality.Provider[uint32] { +func (s Aggregator) getImpact(targetUint32ID uint64) cardinality.Provider[uint64] { return s.cardinalities.GetOr(targetUint32ID, s.newCardinalityProvider) } // resolution is a cursor type that tracks the resolution of a node's impact type resolution struct { - // target is the uint32 ID of the node being resolved - target uint32 + // target is the uint64 ID of the node being resolved + target uint64 // impact stores the cardinality of the target's impact - impact cardinality.Provider[uint32] + impact cardinality.Provider[uint64] // completions are cardinality providers that will have this resolution's impact merged into them - completions []cardinality.Provider[uint32] + completions []cardinality.Provider[uint64] - // dependencies contains a slice of uint32 node IDs that this resolution depends on - dependencies []uint32 + // dependencies contains a slice of uint64 node IDs that this resolution depends on + dependencies []uint64 } -// resolve takes the target uint32 ID of a node and calculates the cardinality of nodes that have a path that traverse +// resolve takes the target uint64 ID of a node and calculates the cardinality of nodes that have a path that traverse // it -func (s Aggregator) resolve(targetUint32ID uint32) cardinality.Provider[uint32] { +func (s Aggregator) resolve(targetUint32ID uint64) cardinality.Provider[uint64] { var ( targetImpact = s.getImpact(targetUint32ID) - resolutions = map[uint32]*resolution{ + resolutions = map[uint64]*resolution{ targetUint32ID: { target: targetUint32ID, impact: targetImpact, dependencies: s.popDependencies(targetUint32ID), }, } - stack = []uint32{targetUint32ID} + stack = []uint64{targetUint32ID} ) for len(stack) > 0 { @@ -128,7 +128,7 @@ func (s Aggregator) resolve(targetUint32ID uint32) cardinality.Provider[uint32] resolutions[nextDependency] = &resolution{ target: nextDependency, impact: s.getImpact(nextDependency), - completions: []cardinality.Provider[uint32]{next.impact}, + completions: []cardinality.Provider[uint64]{next.impact}, dependencies: s.popDependencies(nextDependency), } } @@ -157,7 +157,7 @@ func (s Aggregator) resolve(targetUint32ID uint32) cardinality.Provider[uint32] return targetImpact } -func (s Aggregator) Cardinality(targets ...uint32) cardinality.Provider[uint32] { +func (s Aggregator) Cardinality(targets ...uint64) cardinality.Provider[uint64] { log.Debugf("Calculating pathMembers cardinality for %d targets", len(targets)) defer log.Measure(log.LevelDebug, "Calculated pathMembers cardinality for %d targets", len(targets))() @@ -175,29 +175,29 @@ func (s Aggregator) Cardinality(targets ...uint32) cardinality.Provider[uint32] } func (s Aggregator) AddPath(path *graph.PathSegment, impactKinds graph.Kinds) { - var impactingNodes []uint32 + var impactingNodes []uint64 if path.Node.Kinds.ContainsOneOf(impactKinds...) { - impactingNodes = append(impactingNodes, path.Node.ID.Uint32()) + impactingNodes = append(impactingNodes, path.Node.ID.Uint64()) } for cursor := path.Trunk; cursor != nil; cursor = cursor.Trunk { // Only pull the pathMembers from the map if we have nodes that should be counted for this cursor if len(impactingNodes) > 0 { - s.getImpact(cursor.Node.ID.Uint32()).Add(impactingNodes...) + s.getImpact(cursor.Node.ID.Uint64()).Add(impactingNodes...) } // Only roll up cardinalities for nodes that belong to the set of impacting kinds if cursor.Node.Kinds.ContainsOneOf(impactKinds...) { - impactingNodes = append(impactingNodes, cursor.Node.ID.Uint32()) + impactingNodes = append(impactingNodes, cursor.Node.ID.Uint64()) } } } func (s Aggregator) AddShortcut(path *graph.PathSegment, impactKinds graph.Kinds) { var ( - terminalUint32ID = path.Node.ID.Uint32() - impactingNodes []uint32 + terminalUint32ID = path.Node.ID.Uint64() + impactingNodes []uint64 ) // Only add the terminal to the impacting nodes if it's a type that imparts impact - this does not remove the @@ -207,7 +207,7 @@ func (s Aggregator) AddShortcut(path *graph.PathSegment, impactKinds graph.Kinds } for cursor := path.Trunk; cursor != nil; cursor = cursor.Trunk { - cursorNodeUint32ID := cursor.Node.ID.Uint32() + cursorNodeUint32ID := cursor.Node.ID.Uint64() // Add the terminal shortcut as a dependency to each ascending node s.pushDependency(cursorNodeUint32ID, terminalUint32ID) @@ -219,11 +219,11 @@ func (s Aggregator) AddShortcut(path *graph.PathSegment, impactKinds graph.Kinds // Only roll up cardinalities for nodes that belong to the set of impacting kinds if cursor.Node.Kinds.ContainsOneOf(impactKinds...) { - impactingNodes = append(impactingNodes, cursor.Node.ID.Uint32()) + impactingNodes = append(impactingNodes, cursor.Node.ID.Uint64()) } } } -func (s Aggregator) Resolved() cardinality.Duplex[uint32] { +func (s Aggregator) Resolved() cardinality.Duplex[uint64] { return s.resolved } diff --git a/packages/go/analysis/impact/aggregator_test.go b/packages/go/analysis/impact/aggregator_test.go index 9bcd5ed121..487d72b2f2 100644 --- a/packages/go/analysis/impact/aggregator_test.go +++ b/packages/go/analysis/impact/aggregator_test.go @@ -60,8 +60,8 @@ func node(nodeKinds ...graph.Kind) *graph.Node { return graph.NewNode(getNextID(), nil, nodeKinds...) } -func requireImpact(t *testing.T, agg impact.Aggregator, nodeID uint32, containedNodes ...uint32) { - nodeImpact := agg.Cardinality(nodeID).(cardinality.Duplex[uint32]) +func requireImpact(t *testing.T, agg impact.Aggregator, nodeID uint64, containedNodes ...uint64) { + nodeImpact := agg.Cardinality(nodeID).(cardinality.Duplex[uint64]) if int(nodeImpact.Cardinality()) != len(containedNodes) { t.Fatalf("Expected node %d to contain %d impacting nodes but saw %d: %v", int(nodeID), len(containedNodes), int(nodeImpact.Cardinality()), nodeImpact.Slice()) @@ -93,8 +93,8 @@ func TestAggregator_NonImpactingShortcut(t *testing.T) { node2Segment = descend(rootSegment, node2) node1ToNode2Shortcut = descend(node2Segment, node1) - agg = impact.NewAggregator(func() cardinality.Provider[uint32] { - return cardinality.NewBitmap32() + agg = impact.NewAggregator(func() cardinality.Provider[uint64] { + return cardinality.NewBitmap64() }) ) @@ -152,8 +152,8 @@ func TestAggregator_Impact(t *testing.T) { node11to10Terminal = descend(node11Segment, node10) // Make sure to use an exact cardinality container (bitset in this case) - agg = impact.NewAggregator(func() cardinality.Provider[uint32] { - return cardinality.NewBitmap32() + agg = impact.NewAggregator(func() cardinality.Provider[uint64] { + return cardinality.NewBitmap64() }) ) diff --git a/packages/go/analysis/impact/id_aggregator.go b/packages/go/analysis/impact/id_aggregator.go index 1213fc0a4d..d9abf5259d 100644 --- a/packages/go/analysis/impact/id_aggregator.go +++ b/packages/go/analysis/impact/id_aggregator.go @@ -25,8 +25,8 @@ import ( ) type PathAggregator interface { - Cardinality(targets ...uint32) cardinality.Provider[uint32] - Contains(target uint32) bool + Cardinality(targets ...uint64) cardinality.Provider[uint64] + Contains(target uint64) bool AddPath(path *graph.IDSegment) AddShortcut(path *graph.IDSegment) } @@ -36,14 +36,14 @@ type ThreadSafeAggregator struct { lock *sync.RWMutex } -func (s ThreadSafeAggregator) Contains(target uint32) bool { +func (s ThreadSafeAggregator) Contains(target uint64) bool { s.lock.RLock() defer s.lock.RUnlock() return s.aggregator.Contains(target) } -func (s ThreadSafeAggregator) Cardinality(targets ...uint32) cardinality.Provider[uint32] { +func (s ThreadSafeAggregator) Cardinality(targets ...uint64) cardinality.Provider[uint64] { s.lock.Lock() defer s.lock.Unlock() @@ -81,31 +81,31 @@ func NewThreadSafeAggregator(aggregator PathAggregator) PathAggregator { // of nodes by calling the cardinality functions of the aggregator. Resolution is accomplished using a recursive // depth-first strategy. type IDA struct { - resolved cardinality.Duplex[uint32] - cardinalities *graph.IndexedSlice[uint32, cardinality.Provider[uint32]] - dependencies map[uint32]cardinality.Duplex[uint32] - newCardinalityProvider cardinality.ProviderConstructor[uint32] + resolved cardinality.Duplex[uint64] + cardinalities *graph.IndexedSlice[uint64, cardinality.Provider[uint64]] + dependencies map[uint64]cardinality.Duplex[uint64] + newCardinalityProvider cardinality.ProviderConstructor[uint64] } -func (s IDA) Contains(target uint32) bool { +func (s IDA) Contains(target uint64) bool { return s.cardinalities.Has(target) } -func NewIDA(newCardinalityProvider cardinality.ProviderConstructor[uint32]) IDA { +func NewIDA(newCardinalityProvider cardinality.ProviderConstructor[uint64]) IDA { return IDA{ - cardinalities: graph.NewIndexedSlice[uint32, cardinality.Provider[uint32]](), - dependencies: map[uint32]cardinality.Duplex[uint32]{}, - resolved: cardinality.NewBitmap32(), + cardinalities: graph.NewIndexedSlice[uint64, cardinality.Provider[uint64]](), + dependencies: map[uint64]cardinality.Duplex[uint64]{}, + resolved: cardinality.NewBitmap64(), newCardinalityProvider: newCardinalityProvider, } } // pushDependency adds a new dependency for the given target. -func (s IDA) pushDependency(target, dependency uint32) { +func (s IDA) pushDependency(target, dependency uint64) { if dependencies, hasDependencies := s.dependencies[target]; hasDependencies { dependencies.Add(dependency) } else { - newDependencies := cardinality.NewBitmap32() + newDependencies := cardinality.NewBitmap64() newDependencies.Add(dependency) s.dependencies[target] = newDependencies @@ -114,7 +114,7 @@ func (s IDA) pushDependency(target, dependency uint32) { // popDependencies will take the simplex cardinality provider reference for the given target, remove it from the // containing map in the aggregator and then return it -func (s IDA) popDependencies(targetUint32ID uint32) []uint32 { +func (s IDA) popDependencies(targetUint32ID uint64) []uint64 { dependencies, hasDependencies := s.dependencies[targetUint32ID] delete(s.dependencies, targetUint32ID) @@ -125,38 +125,38 @@ func (s IDA) popDependencies(targetUint32ID uint32) []uint32 { return nil } -func (s IDA) membership(targetUint32ID uint32) cardinality.Provider[uint32] { +func (s IDA) membership(targetUint32ID uint64) cardinality.Provider[uint64] { return s.cardinalities.GetOr(targetUint32ID, s.newCardinalityProvider) } // idaRes is a cursor type that tracks the resolution of a node's pathMembers type idaRes struct { - // target is the uint32 ID of the node being resolved - target uint32 + // target is the uint64 ID of the node being resolved + target uint64 // pathMembers stores the cardinality of the target's path membership - pathMembers cardinality.Provider[uint32] + pathMembers cardinality.Provider[uint64] // completions are cardinality providers that will have this resolution's pathMembers merged into them - completions []cardinality.Provider[uint32] + completions []cardinality.Provider[uint64] - // dependencies contains a slice of uint32 node IDs that this resolution depends on - dependencies []uint32 + // dependencies contains a slice of uint64 node IDs that this resolution depends on + dependencies []uint64 } -// resolve takes the target uint32 ID of a node and calculates the cardinality of nodes that have a path that traverse +// resolve takes the target uint64 ID of a node and calculates the cardinality of nodes that have a path that traverse // it -func (s IDA) resolve(targetUint32ID uint32) cardinality.Provider[uint32] { +func (s IDA) resolve(targetUint32ID uint64) cardinality.Provider[uint64] { var ( targetImpact = s.membership(targetUint32ID) - resolutions = map[uint32]*idaRes{ + resolutions = map[uint64]*idaRes{ targetUint32ID: { target: targetUint32ID, pathMembers: targetImpact, dependencies: s.popDependencies(targetUint32ID), }, } - stack = []uint32{targetUint32ID} + stack = []uint64{targetUint32ID} ) for len(stack) > 0 { @@ -181,7 +181,7 @@ func (s IDA) resolve(targetUint32ID uint32) cardinality.Provider[uint32] { resolutions[nextDependency] = &idaRes{ target: nextDependency, pathMembers: s.membership(nextDependency), - completions: []cardinality.Provider[uint32]{next.pathMembers}, + completions: []cardinality.Provider[uint64]{next.pathMembers}, dependencies: s.popDependencies(nextDependency), } } @@ -210,7 +210,7 @@ func (s IDA) resolve(targetUint32ID uint32) cardinality.Provider[uint32] { return targetImpact } -func (s IDA) Cardinality(targets ...uint32) cardinality.Provider[uint32] { +func (s IDA) Cardinality(targets ...uint64) cardinality.Provider[uint64] { log.Debugf("Calculating pathMembers cardinality for %d targets", len(targets)) defer log.Measure(log.LevelDebug, "Calculated pathMembers cardinality for %d targets", len(targets))() @@ -228,25 +228,25 @@ func (s IDA) Cardinality(targets ...uint32) cardinality.Provider[uint32] { } func (s IDA) AddPath(path *graph.IDSegment) { - pathMembers := []uint32{path.Node.Uint32()} + pathMembers := []uint64{path.Node.Uint64()} for cursor := path.Trunk; cursor != nil; cursor = cursor.Trunk { - cursorNodeUint32ID := cursor.Node.Uint32() + cursorNodeUint32ID := cursor.Node.Uint64() // Roll up cardinalities for nodes that belong to the path s.membership(cursorNodeUint32ID).Add(pathMembers...) - pathMembers = append(pathMembers, cursor.Node.Uint32()) + pathMembers = append(pathMembers, cursor.Node.Uint64()) } } func (s IDA) AddShortcut(path *graph.IDSegment) { var ( - terminalUint32ID = path.Node.Uint32() - pathMembers = []uint32{terminalUint32ID} + terminalUint32ID = path.Node.Uint64() + pathMembers = []uint64{terminalUint32ID} ) for cursor := path.Trunk; cursor != nil; cursor = cursor.Trunk { - cursorNodeUint32ID := cursor.Node.Uint32() + cursorNodeUint32ID := cursor.Node.Uint64() // The terminal node of this path was not fully traversed, so push it as a dependency of all ascending nodes // above it @@ -258,6 +258,6 @@ func (s IDA) AddShortcut(path *graph.IDSegment) { } } -func (s IDA) Resolved() cardinality.Duplex[uint32] { +func (s IDA) Resolved() cardinality.Duplex[uint64] { return s.resolved } diff --git a/packages/go/analysis/impact/id_aggregrator_test.go b/packages/go/analysis/impact/id_aggregrator_test.go index 344770c545..f72e5f3e0d 100644 --- a/packages/go/analysis/impact/id_aggregrator_test.go +++ b/packages/go/analysis/impact/id_aggregrator_test.go @@ -70,8 +70,8 @@ func TestAggregator_Cardinality(t *testing.T) { node11to10Terminal = idDescend(node11Segment, node10) // Make sure to use an exact cardinality container (bitset in this case) - agg = impact.NewIDA(func() cardinality.Provider[uint32] { - return cardinality.NewBitmap32() + agg = impact.NewIDA(func() cardinality.Provider[uint64] { + return cardinality.NewBitmap64() }) ) @@ -84,7 +84,7 @@ func TestAggregator_Cardinality(t *testing.T) { agg.AddShortcut(node7to3Shortcut) agg.AddShortcut(node8to10Shortcut) - nodeImpact := agg.Cardinality(2).(cardinality.Duplex[uint32]) + nodeImpact := agg.Cardinality(2).(cardinality.Duplex[uint64]) assert.Equal(t, 4, int(agg.Resolved().Cardinality())) @@ -104,7 +104,7 @@ func TestAggregator_Cardinality(t *testing.T) { require.True(t, nodeImpact.Contains(6)) require.True(t, nodeImpact.Contains(8)) - nodeImpact = agg.Cardinality(1).(cardinality.Duplex[uint32]) + nodeImpact = agg.Cardinality(1).(cardinality.Duplex[uint64]) require.Equal(t, 5, int(agg.Resolved().Cardinality())) @@ -124,7 +124,7 @@ func TestAggregator_Cardinality(t *testing.T) { require.True(t, nodeImpact.Contains(9)) require.True(t, nodeImpact.Contains(10)) - nodeImpact = agg.Cardinality(11).(cardinality.Duplex[uint32]) + nodeImpact = agg.Cardinality(11).(cardinality.Duplex[uint64]) require.Equal(t, 7, int(agg.Resolved().Cardinality())) @@ -148,7 +148,7 @@ func TestAggregator_Cardinality(t *testing.T) { require.True(t, nodeImpact.Contains(10)) // Validate cached resolutions are correct - nodeImpact = agg.Cardinality(2).(cardinality.Duplex[uint32]) + nodeImpact = agg.Cardinality(2).(cardinality.Duplex[uint64]) require.Equal(t, 8, int(nodeImpact.Cardinality())) diff --git a/packages/go/analysis/post.go b/packages/go/analysis/post.go index d0c012236f..16f0a8de79 100644 --- a/packages/go/analysis/post.go +++ b/packages/go/analysis/post.go @@ -120,7 +120,7 @@ type DeleteRelationshipJob struct { ID graph.ID } -func DeleteTransitEdges(ctx context.Context, db graph.Database, fromKind, toKind graph.Kind, targetRelationships ...graph.Kind) (*AtomicPostProcessingStats, error) { +func DeleteTransitEdges(ctx context.Context, db graph.Database, baseKinds graph.Kinds, targetRelationships ...graph.Kind) (*AtomicPostProcessingStats, error) { defer log.Measure(log.LevelInfo, "Finished deleting transit edges")() var ( @@ -134,9 +134,9 @@ func DeleteTransitEdges(ctx context.Context, db graph.Database, fromKind, toKind if err := db.ReadTransaction(ctx, func(tx graph.Transaction) error { fetchedRelationshipIDs, err := ops.FetchRelationshipIDs(tx.Relationships().Filterf(func() graph.Criteria { return query.And( - query.Kind(query.Start(), fromKind), + query.KindIn(query.Start(), baseKinds...), query.Kind(query.Relationship(), closureKindCopy), - query.Kind(query.End(), toKind), + query.KindIn(query.End(), baseKinds...), ) })) diff --git a/packages/go/cypher/models/pgsql/pgtypes.go b/packages/go/cypher/models/pgsql/pgtypes.go index 0a991ab61a..e19537cc07 100644 --- a/packages/go/cypher/models/pgsql/pgtypes.go +++ b/packages/go/cypher/models/pgsql/pgtypes.go @@ -272,10 +272,10 @@ var CompositeTypes = []DataType{NodeComposite, NodeCompositeArray, EdgeComposite func NegotiateValue(value any) (any, error) { switch typedValue := value.(type) { case graph.ID: - return typedValue.Uint32(), nil + return typedValue.Uint64(), nil case []graph.ID: - return graph.IDsToUint32Slice(typedValue), nil + return graph.IDsToUint64Slice(typedValue), nil default: return value, nil diff --git a/packages/go/dawgs/cardinality/cardinality.go b/packages/go/dawgs/cardinality/cardinality.go index 6e8d321580..4760cf3f5f 100644 --- a/packages/go/dawgs/cardinality/cardinality.go +++ b/packages/go/dawgs/cardinality/cardinality.go @@ -16,10 +16,6 @@ package cardinality -import ( - "github.com/specterops/bloodhound/dawgs/graph" -) - type ProviderConstructor[T uint32 | uint64] func() Provider[T] type SimplexConstructor[T uint32 | uint64] func() Simplex[T] type DuplexConstructor[T uint32 | uint64] func() Duplex[T] @@ -69,6 +65,7 @@ type Duplex[T uint32 | uint64] interface { Xor(other Provider[T]) And(other Provider[T]) + AndNot(other Provider[T]) Remove(value T) Slice() []T Contains(value T) bool @@ -76,38 +73,3 @@ type Duplex[T uint32 | uint64] interface { CheckedAdd(value T) bool Clone() Duplex[T] } - -// DuplexToGraphIDs takes a Duplex provider and returns a slice of graph IDs. -func DuplexToGraphIDs[T uint32 | uint64](provider Duplex[T]) []graph.ID { - ids := make([]graph.ID, 0, provider.Cardinality()) - - provider.Each(func(value T) bool { - ids = append(ids, graph.ID(value)) - return true - }) - - return ids - -} - -// NodeSetToDuplex takes a graph NodeSet and returns a Duplex provider that contains all node IDs. -func NodeSetToDuplex(nodes graph.NodeSet) Duplex[uint32] { - duplex := NewBitmap32() - - for nodeID := range nodes { - duplex.Add(nodeID.Uint32()) - } - - return duplex -} - -// NodeSetToDuplex takes a graph NodeSet and returns a Duplex provider that contains all node IDs. -func NodeIDsToDuplex(nodeIDs []graph.ID) Duplex[uint32] { - duplex := NewBitmap32() - - for _, nodeID := range nodeIDs { - duplex.Add(nodeID.Uint32()) - } - - return duplex -} diff --git a/packages/go/dawgs/cardinality/cardinality_test.go b/packages/go/dawgs/cardinality/cardinality_test.go index fa4a544175..6a80ceff5d 100644 --- a/packages/go/dawgs/cardinality/cardinality_test.go +++ b/packages/go/dawgs/cardinality/cardinality_test.go @@ -25,17 +25,17 @@ import ( ) func TestDuplexToGraphIDs(t *testing.T) { - uint32IDs := []uint32{1, 2, 3, 4, 5} - duplex := cardinality.NewBitmap32() - duplex.Add(uint32IDs...) + uintIDs := []uint64{1, 2, 3, 4, 5} + duplex := cardinality.NewBitmap64() + duplex.Add(uintIDs...) - ids := cardinality.DuplexToGraphIDs(duplex) + ids := graph.DuplexToGraphIDs(duplex) - for _, uint32ID := range uint32IDs { + for _, uintID := range uintIDs { found := false for _, id := range ids { - if id.Uint32() == uint32ID { + if id.Uint64() == uintID { found = true break } @@ -55,7 +55,7 @@ func TestNodeSetToDuplex(t *testing.T) { }, } - duplex := cardinality.NodeSetToDuplex(nodes) + duplex := graph.NodeSetToDuplex(nodes) require.True(t, duplex.Contains(1)) require.True(t, duplex.Contains(2)) diff --git a/packages/go/dawgs/cardinality/lock.go b/packages/go/dawgs/cardinality/lock.go index d59faad715..06a16ec95c 100644 --- a/packages/go/dawgs/cardinality/lock.go +++ b/packages/go/dawgs/cardinality/lock.go @@ -46,6 +46,13 @@ func (s threadSafeDuplex[T]) Add(values ...T) { s.provider.Add(values...) } +func (s threadSafeDuplex[T]) AndNot(other Provider[T]) { + s.lock.Lock() + defer s.lock.Unlock() + + s.provider.AndNot(other) +} + func (s threadSafeDuplex[T]) Remove(value T) { s.lock.Lock() defer s.lock.Unlock() diff --git a/packages/go/dawgs/cardinality/roaring32.go b/packages/go/dawgs/cardinality/roaring32.go index edbefde20a..ba1186798b 100644 --- a/packages/go/dawgs/cardinality/roaring32.go +++ b/packages/go/dawgs/cardinality/roaring32.go @@ -142,3 +142,19 @@ func (s bitmap32) Clone() Duplex[uint32] { bitmap: s.bitmap.Clone(), } } + +func (s bitmap32) AndNot(provider Provider[uint32]) { + switch typedProvider := provider.(type) { + case bitmap32: + s.bitmap.AndNot(typedProvider.bitmap) + + case Duplex[uint32]: + s.Each(func(nextValue uint32) bool { + if typedProvider.Contains(nextValue) { + s.Remove(nextValue) + } + + return true + }) + } +} diff --git a/packages/go/dawgs/cardinality/roaring64.go b/packages/go/dawgs/cardinality/roaring64.go index f559975d9e..6b0fb5ef30 100644 --- a/packages/go/dawgs/cardinality/roaring64.go +++ b/packages/go/dawgs/cardinality/roaring64.go @@ -42,6 +42,13 @@ func NewBitmap64() Duplex[uint64] { } } +func NewBitmap64With(values ...uint64) Duplex[uint64] { + duplex := NewBitmap64() + duplex.Add(values...) + + return duplex +} + func (s bitmap64) Clear() { s.bitmap.Clear() } @@ -133,3 +140,19 @@ func (s bitmap64) Clone() Duplex[uint64] { bitmap: s.bitmap.Clone(), } } + +func (s bitmap64) AndNot(provider Provider[uint64]) { + switch typedProvider := provider.(type) { + case bitmap64: + s.bitmap.AndNot(typedProvider.bitmap) + + case Duplex[uint64]: + s.Each(func(nextValue uint64) bool { + if typedProvider.Contains(nextValue) { + s.Remove(nextValue) + } + + return true + }) + } +} diff --git a/packages/go/dawgs/drivers/pg/batch.go b/packages/go/dawgs/drivers/pg/batch.go index d8a4bf8440..bed59eac00 100644 --- a/packages/go/dawgs/drivers/pg/batch.go +++ b/packages/go/dawgs/drivers/pg/batch.go @@ -151,7 +151,7 @@ func (s *batch) flushNodeCreateBuffer() error { func (s *batch) flushNodeCreateBufferWithIDs() error { var ( numCreates = len(s.nodeCreateBuffer) - nodeIDs = make([]uint32, numCreates) + nodeIDs = make([]uint64, numCreates) kindIDSlices = make([]string, numCreates) kindIDEncoder = Int2ArrayEncoder{ buffer: &bytes.Buffer{}, @@ -160,7 +160,7 @@ func (s *batch) flushNodeCreateBufferWithIDs() error { ) for idx, nextNode := range s.nodeCreateBuffer { - nodeIDs[idx] = nextNode.ID.Uint32() + nodeIDs[idx] = nextNode.ID.Uint64() if mappedKindIDs, missingKinds := s.schemaManager.MapKinds(nextNode.Kinds); len(missingKinds) > 0 { return fmt.Errorf("unable to map kinds %v", missingKinds) @@ -402,22 +402,22 @@ func (s *batch) tryFlushRelationshipUpdateByBuffer() error { } type relationshipCreateBatch struct { - startIDs []uint32 - endIDs []uint32 + startIDs []uint64 + endIDs []uint64 edgeKindIDs []int16 edgePropertyBags []pgtype.JSONB } func newRelationshipCreateBatch(size int) *relationshipCreateBatch { return &relationshipCreateBatch{ - startIDs: make([]uint32, 0, size), - endIDs: make([]uint32, 0, size), + startIDs: make([]uint64, 0, size), + endIDs: make([]uint64, 0, size), edgeKindIDs: make([]int16, 0, size), edgePropertyBags: make([]pgtype.JSONB, 0, size), } } -func (s *relationshipCreateBatch) Add(startID, endID uint32, edgeKindID int16) { +func (s *relationshipCreateBatch) Add(startID, endID uint64, edgeKindID int16) { s.startIDs = append(s.startIDs, startID) s.edgeKindIDs = append(s.edgeKindIDs, edgeKindID) s.endIDs = append(s.endIDs, endID) @@ -436,17 +436,17 @@ func (s *relationshipCreateBatch) EncodeProperties(edgePropertiesBatch []*graph. } type relationshipCreateBatchBuilder struct { - keyToEdgeID map[string]uint32 + keyToEdgeID map[string]uint64 relationshipUpdateBatch *relationshipCreateBatch - edgePropertiesIndex map[uint32]int + edgePropertiesIndex map[uint64]int edgePropertiesBatch []*graph.Properties } func newRelationshipCreateBatchBuilder(size int) *relationshipCreateBatchBuilder { return &relationshipCreateBatchBuilder{ - keyToEdgeID: map[string]uint32{}, + keyToEdgeID: map[string]uint64{}, relationshipUpdateBatch: newRelationshipCreateBatch(size), - edgePropertiesIndex: map[uint32]int{}, + edgePropertiesIndex: map[uint64]int{}, } } @@ -467,9 +467,9 @@ func (s *relationshipCreateBatchBuilder) Add(kindMapper KindMapper, edge *graph. s.edgePropertiesBatch[existingPropertiesIdx].Merge(edge.Properties) } else { var ( - startID = edge.StartID.Uint32() - edgeID = edge.ID.Uint32() - endID = edge.EndID.Uint32() + startID = edge.StartID.Uint64() + edgeID = edge.ID.Uint64() + endID = edge.EndID.Uint64() edgeProperties = edge.Properties.Clone() ) diff --git a/packages/go/dawgs/drivers/pg/query/sql/schema_up.sql b/packages/go/dawgs/drivers/pg/query/sql/schema_up.sql index 10436ad04c..efe2c700ab 100644 --- a/packages/go/dawgs/drivers/pg/query/sql/schema_up.sql +++ b/packages/go/dawgs/drivers/pg/query/sql/schema_up.sql @@ -71,7 +71,7 @@ create extension if not exists intarray; -- corresponding table partitions for the node and edge tables. create table if not exists graph ( - id serial, + id bigserial, name varchar(256) not null, primary key (id), unique (name) @@ -106,7 +106,7 @@ $$; -- contain a disjunction of up to 8 kinds for creating clique subsets without requiring edges. create table if not exists node ( - id serial not null, + id bigserial not null, graph_id integer not null, kind_ids smallint[8] not null, properties jsonb not null, @@ -147,12 +147,12 @@ $$; -- The edge table is a partitioned table view that partitions over the graph ID that each edge belongs to. create table if not exists edge ( - id serial not null, - graph_id integer not null, - start_id integer not null, - end_id integer not null, - kind_id smallint not null, - properties jsonb not null, + id bigserial not null, + graph_id integer not null, + start_id bigint not null, + end_id bigint not null, + kind_id smallint not null, + properties jsonb not null, primary key (id, graph_id), foreign key (graph_id) references graph (id) on delete cascade, diff --git a/packages/go/dawgs/graph/graph.go b/packages/go/dawgs/graph/graph.go index b54af86575..34bdb02565 100644 --- a/packages/go/dawgs/graph/graph.go +++ b/packages/go/dawgs/graph/graph.go @@ -128,7 +128,7 @@ func (s ID) Int64() int64 { } func (s ID) Sizeof() size.Size { - return size.Size(unsafe.Sizeof(s.Uint32())) + return size.Size(unsafe.Sizeof(s)) } // String formats the int64 value of the ID as a string. diff --git a/packages/go/dawgs/cardinality/graph.go b/packages/go/dawgs/graph/mtypes.go similarity index 69% rename from packages/go/dawgs/cardinality/graph.go rename to packages/go/dawgs/graph/mtypes.go index ee4001ea1d..ed921e809b 100644 --- a/packages/go/dawgs/cardinality/graph.go +++ b/packages/go/dawgs/graph/mtypes.go @@ -14,18 +14,17 @@ // // SPDX-License-Identifier: Apache-2.0 -package cardinality +package graph import ( + "github.com/specterops/bloodhound/dawgs/cardinality" "sync" - - "github.com/specterops/bloodhound/dawgs/graph" ) -type KindBitmaps map[string]Duplex[uint32] +type KindBitmaps map[string]cardinality.Duplex[uint64] -func (s KindBitmaps) Get(kinds ...graph.Kind) Duplex[uint32] { - bitmap := NewBitmap32() +func (s KindBitmaps) Get(kinds ...Kind) cardinality.Duplex[uint64] { + bitmap := cardinality.NewBitmap64() if len(kinds) == 0 { for _, kindBitmap := range s { @@ -42,14 +41,14 @@ func (s KindBitmaps) Get(kinds ...graph.Kind) Duplex[uint32] { return bitmap } -func (s KindBitmaps) Count(kinds ...graph.Kind) uint64 { +func (s KindBitmaps) Count(kinds ...Kind) uint64 { return s.Get(kinds...).Cardinality() } func (s KindBitmaps) Or(bitmaps KindBitmaps) { for kindStr, leftBitmap := range bitmaps { if rightBitmap, hasRightBitmap := s[kindStr]; !hasRightBitmap { - newRightBitmap := NewBitmap32() + newRightBitmap := cardinality.NewBitmap64() newRightBitmap.Or(leftBitmap) s[kindStr] = newRightBitmap @@ -59,7 +58,7 @@ func (s KindBitmaps) Or(bitmaps KindBitmaps) { } } -func (s KindBitmaps) AddSets(nodeSets ...graph.NodeSet) { +func (s KindBitmaps) AddSets(nodeSets ...NodeSet) { for _, nodeSet := range nodeSets { for _, node := range nodeSet { s.AddIDToKinds(node.ID, node.Kinds) @@ -67,8 +66,8 @@ func (s KindBitmaps) AddSets(nodeSets ...graph.NodeSet) { } } -func (s KindBitmaps) OrAll() Duplex[uint32] { - all := NewBitmap32() +func (s KindBitmaps) OrAll() cardinality.Duplex[uint64] { + all := cardinality.NewBitmap64() for _, bitmap := range s { all.Or(bitmap) @@ -77,9 +76,9 @@ func (s KindBitmaps) OrAll() Duplex[uint32] { return all } -func (s KindBitmaps) Contains(node *graph.Node) bool { +func (s KindBitmaps) Contains(node *Node) bool { for _, bitmap := range s { - if bitmap.Contains(node.ID.Uint32()) { + if bitmap.Contains(node.ID.Uint64()) { return true } } @@ -87,11 +86,11 @@ func (s KindBitmaps) Contains(node *graph.Node) bool { return false } -func (s KindBitmaps) AddDuplexToKind(ids Duplex[uint32], kind graph.Kind) { +func (s KindBitmaps) AddDuplexToKind(ids cardinality.Duplex[uint64], kind Kind) { kindStr := kind.String() if bitmap, hasBitmap := s[kindStr]; !hasBitmap { - newBitmap := NewBitmap32() + newBitmap := cardinality.NewBitmap64() newBitmap.Or(ids) s[kindStr] = newBitmap @@ -100,14 +99,14 @@ func (s KindBitmaps) AddDuplexToKind(ids Duplex[uint32], kind graph.Kind) { } } -func (s KindBitmaps) AddIDToKind(id graph.ID, kind graph.Kind) { +func (s KindBitmaps) AddIDToKind(id ID, kind Kind) { var ( - nodeID = id.Uint32() + nodeID = id.Uint64() kindStr = kind.String() ) if bitmap, hasBitmap := s[kindStr]; !hasBitmap { - newBitmap := NewBitmap32() + newBitmap := cardinality.NewBitmap64() newBitmap.Add(nodeID) s[kindStr] = newBitmap @@ -116,14 +115,14 @@ func (s KindBitmaps) AddIDToKind(id graph.ID, kind graph.Kind) { } } -func (s KindBitmaps) AddIDToKinds(id graph.ID, kinds graph.Kinds) { - nodeID := id.Uint32() +func (s KindBitmaps) AddIDToKinds(id ID, kinds Kinds) { + nodeID := id.Uint64() for _, kind := range kinds { kindStr := kind.String() if bitmap, hasBitmap := s[kindStr]; !hasBitmap { - newBitmap := NewBitmap32() + newBitmap := cardinality.NewBitmap64() newBitmap.Add(nodeID) s[kindStr] = newBitmap @@ -133,7 +132,7 @@ func (s KindBitmaps) AddIDToKinds(id graph.ID, kinds graph.Kinds) { } } -func (s KindBitmaps) AddNodes(nodes ...*graph.Node) { +func (s KindBitmaps) AddNodes(nodes ...*Node) { for _, node := range nodes { s.AddIDToKinds(node.ID, node.Kinds) } @@ -146,20 +145,20 @@ type ThreadSafeKindBitmap struct { func NewThreadSafeKindBitmap() *ThreadSafeKindBitmap { return &ThreadSafeKindBitmap{ - bitmaps: map[string]Duplex[uint32]{}, + bitmaps: map[string]cardinality.Duplex[uint64]{}, rwLock: &sync.RWMutex{}, } } -func (s ThreadSafeKindBitmap) Count(kinds ...graph.Kind) uint64 { +func (s ThreadSafeKindBitmap) Count(kinds ...Kind) uint64 { return s.Get(kinds...).Cardinality() } -func (s ThreadSafeKindBitmap) Get(kinds ...graph.Kind) Duplex[uint32] { +func (s ThreadSafeKindBitmap) Get(kinds ...Kind) cardinality.Duplex[uint64] { s.rwLock.RLock() defer s.rwLock.RUnlock() - bitmap := NewBitmap32() + bitmap := cardinality.NewBitmap64() if len(kinds) == 0 { for _, kindBitmap := range s.bitmaps { @@ -176,7 +175,7 @@ func (s ThreadSafeKindBitmap) Get(kinds ...graph.Kind) Duplex[uint32] { return bitmap } -func (s ThreadSafeKindBitmap) Cardinality(kinds ...graph.Kind) uint64 { +func (s ThreadSafeKindBitmap) Cardinality(kinds ...Kind) uint64 { return s.Get(kinds...).Cardinality() } @@ -192,7 +191,7 @@ func (s ThreadSafeKindBitmap) Clone() *ThreadSafeKindBitmap { return clone } -func (s ThreadSafeKindBitmap) Contains(kind graph.Kind, value uint32) bool { +func (s ThreadSafeKindBitmap) Contains(kind Kind, value uint64) bool { s.rwLock.RLock() defer s.rwLock.RUnlock() @@ -203,28 +202,28 @@ func (s ThreadSafeKindBitmap) Contains(kind graph.Kind, value uint32) bool { return false } -func (s ThreadSafeKindBitmap) Add(kind graph.Kind, value uint32) { +func (s ThreadSafeKindBitmap) Add(kind Kind, value uint64) { s.rwLock.Lock() defer s.rwLock.Unlock() if kindBitmap, hasKind := s.bitmaps[kind.String()]; hasKind { kindBitmap.Add(value) } else { - kindBitmap = NewBitmap32() + kindBitmap = cardinality.NewBitmap64() kindBitmap.Add(value) s.bitmaps[kind.String()] = kindBitmap } } -func (s ThreadSafeKindBitmap) CheckedAdd(kind graph.Kind, value uint32) bool { +func (s ThreadSafeKindBitmap) CheckedAdd(kind Kind, value uint64) bool { s.rwLock.Lock() defer s.rwLock.Unlock() if kindBitmap, hasKind := s.bitmaps[kind.String()]; hasKind { return kindBitmap.CheckedAdd(value) } else { - kindBitmap = NewBitmap32() + kindBitmap = cardinality.NewBitmap64() kindBitmap.Add(value) s.bitmaps[kind.String()] = kindBitmap diff --git a/packages/go/dawgs/graph/node.go b/packages/go/dawgs/graph/node.go index c417810f11..9812084409 100644 --- a/packages/go/dawgs/graph/node.go +++ b/packages/go/dawgs/graph/node.go @@ -18,10 +18,10 @@ package graph import ( "encoding/json" + "github.com/specterops/bloodhound/dawgs/cardinality" "math" "sync" - "github.com/RoaringBitmap/roaring" "github.com/RoaringBitmap/roaring/roaring64" "github.com/specterops/bloodhound/dawgs/util/size" ) @@ -238,12 +238,12 @@ func (s NodeSet) IDs() []ID { return idList } -// IDBitmap returns a new roaring64.Bitmap instance containing all Node ID values in this NodeSet. -func (s NodeSet) IDBitmap() *roaring.Bitmap { - bitmap := roaring.New() +// IDBitmap returns a new bitmap instance containing all Node ID values in this NodeSet. +func (s NodeSet) IDBitmap() cardinality.Duplex[uint64] { + bitmap := cardinality.NewBitmap64() for id := range s { - bitmap.Add(id.Uint32()) + bitmap.Add(id.Uint64()) } return bitmap @@ -399,7 +399,7 @@ func (s ThreadSafeNodeSet) IDs() []ID { } // IDBitmap returns a new roaring64.Bitmap instance containing all Node ID values in this NodeSet. -func (s ThreadSafeNodeSet) IDBitmap() *roaring.Bitmap { +func (s ThreadSafeNodeSet) IDBitmap() cardinality.Duplex[uint64] { s.rwLock.RLock() defer s.rwLock.RUnlock() @@ -416,6 +416,16 @@ func Uint32SliceToIDs(raw []uint32) []ID { return ids } +func Uint64SliceToIDs(raw []uint64) []ID { + ids := make([]ID, len(raw)) + + for idx, rawID := range raw { + ids[idx] = ID(rawID) + } + + return ids +} + func IDsToUint32Slice(ids []ID) []uint32 { rawIDs := make([]uint32, len(ids)) @@ -426,6 +436,16 @@ func IDsToUint32Slice(ids []ID) []uint32 { return rawIDs } +func IDsToUint64Slice(ids []ID) []uint32 { + rawIDs := make([]uint32, len(ids)) + + for idx, id := range ids { + rawIDs[idx] = id.Uint32() + } + + return rawIDs +} + // NewNodeSet returns a new NodeSet from the given Node slice. func NewNodeSet(nodes ...*Node) NodeSet { newSet := NodeSet{} diff --git a/packages/go/dawgs/graph/types.go b/packages/go/dawgs/graph/types.go index 9cb456f925..189cc20d63 100644 --- a/packages/go/dawgs/graph/types.go +++ b/packages/go/dawgs/graph/types.go @@ -16,7 +16,10 @@ package graph -import "github.com/specterops/bloodhound/dawgs/util/size" +import ( + "github.com/specterops/bloodhound/dawgs/cardinality" + "github.com/specterops/bloodhound/dawgs/util/size" +) // IndexedSlice is a structure maps a comparable key to a value that implements size.Sizable. type IndexedSlice[K comparable, V any] struct { @@ -164,3 +167,38 @@ func (s *IndexedSlice[K, V]) Each(delegate func(key K, value V) bool) { } } } + +// DuplexToGraphIDs takes a Duplex provider and returns a slice of graph IDs. +func DuplexToGraphIDs[T uint32 | uint64](provider cardinality.Duplex[T]) []ID { + ids := make([]ID, 0, provider.Cardinality()) + + provider.Each(func(value T) bool { + ids = append(ids, ID(value)) + return true + }) + + return ids + +} + +// NodeSetToDuplex takes a graph NodeSet and returns a Duplex provider that contains all node IDs. +func NodeSetToDuplex(nodes NodeSet) cardinality.Duplex[uint64] { + duplex := cardinality.NewBitmap64() + + for nodeID := range nodes { + duplex.Add(nodeID.Uint64()) + } + + return duplex +} + +// NodeSetToDuplex takes a graph NodeSet and returns a Duplex provider that contains all node IDs. +func NodeIDsToDuplex(nodeIDs []ID) cardinality.Duplex[uint64] { + duplex := cardinality.NewBitmap64() + + for _, nodeID := range nodeIDs { + duplex.Add(nodeID.Uint64()) + } + + return duplex +} diff --git a/packages/go/dawgs/traversal/traversal.go b/packages/go/dawgs/traversal/traversal.go index 4366f36284..db6acc36ba 100644 --- a/packages/go/dawgs/traversal/traversal.go +++ b/packages/go/dawgs/traversal/traversal.go @@ -467,7 +467,7 @@ type SegmentVisitor = func(next *graph.PathSegment) // UniquePathSegmentFilter is a SegmentFilter constructor that will allow a traversal to all unique paths. This is done // by tracking edge IDs traversed in a bitmap. func UniquePathSegmentFilter(delegate SegmentFilter) SegmentFilter { - traversalBitmap := cardinality.ThreadSafeDuplex(cardinality.NewBitmap32()) + traversalBitmap := cardinality.ThreadSafeDuplex(cardinality.NewBitmap64()) return func(next *graph.PathSegment) bool { // Bail on cycles @@ -476,7 +476,7 @@ func UniquePathSegmentFilter(delegate SegmentFilter) SegmentFilter { } // Return if we've seen this edge before - if !traversalBitmap.CheckedAdd(next.Edge.ID.Uint32()) { + if !traversalBitmap.CheckedAdd(next.Edge.ID.Uint64()) { return false } @@ -568,20 +568,20 @@ func LightweightDriver(direction graph.Direction, cache graphcache.Cache, criter return nil, err } else { // Reconcile the start and end nodes of the fetched relationships with the graph cache - nodesToFetch := cardinality.NewBitmap32() + nodesToFetch := cardinality.NewBitmap64() for _, nextRelationship := range relationships { if nextID, err := direction.PickReverse(nextRelationship); err != nil { return nil, err } else { - nodesToFetch.Add(nextID.Uint32()) + nodesToFetch.Add(nextID.Uint64()) } } // Shallow fetching the nodes achieves the same result as shallowFetchRelationships(...) but with the added // benefit of interacting with the graph cache. Any nodes not already in the cache are fetched just-in-time // from the database and stored back in the cache for later. - if cachedNodes, err := graphcache.ShallowFetchNodesByID(tx, cache, cardinality.DuplexToGraphIDs(nodesToFetch)); err != nil { + if cachedNodes, err := graphcache.ShallowFetchNodesByID(tx, cache, graph.DuplexToGraphIDs(nodesToFetch)); err != nil { return nil, err } else { cachedNodeSet := graph.NewNodeSet(cachedNodes...) diff --git a/packages/go/dawgs/util/channels/pipe_test.go b/packages/go/dawgs/util/channels/pipe_test.go index 33d6cc405f..941d374e44 100644 --- a/packages/go/dawgs/util/channels/pipe_test.go +++ b/packages/go/dawgs/util/channels/pipe_test.go @@ -91,14 +91,14 @@ func TestBufferedPipe_DumpOnContextCancel(t *testing.T) { func TestBufferedPipe_HappyPath(t *testing.T) { var ( ctx, done = context.WithTimeout(context.Background(), time.Second*5) - writerC, readerC = channels.BufferedPipe[uint32](ctx) + writerC, readerC = channels.BufferedPipe[uint64](ctx) ) // Ensure that the context done function is always called defer done() // Submit the values first to demonstrate buffering - for i := uint32(0); i < numValuesToSend; i++ { + for i := uint64(0); i < numValuesToSend; i++ { require.True(t, channels.Submit(ctx, writerC, i)) } @@ -107,7 +107,7 @@ func TestBufferedPipe_HappyPath(t *testing.T) { var ( workerWG = &sync.WaitGroup{} - seen = cardinality.ThreadSafeDuplex(cardinality.NewBitmap32()) + seen = cardinality.ThreadSafeDuplex(cardinality.NewBitmap64()) ) for workerID := 0; workerID < 10; workerID++ { @@ -128,7 +128,7 @@ func TestBufferedPipe_HappyPath(t *testing.T) { workerWG.Wait() - for i := uint32(0); i < numValuesToSend; i++ { + for i := uint64(0); i < numValuesToSend; i++ { require.True(t, seen.Contains(i)) } }