Skip to content

Commit

Permalink
feat: esc6a post (#322)
Browse files Browse the repository at this point in the history
* feat: esc6a post

* chore: add integration tests

* chore: wip

* chore: remove recursion o.o

* chore: refactor into smaller funcs

* chore: replace hardcoded strings with schema defined property values now that they are in main

* chore: clean up test logging and specify which nodes are not expected

* chore: explicitly handle property access errors and general cleanup

* fix: update tests to conform to changes associated with pg merge into main

* fix: cross product with each cert template controller instead of the whole collection

* chore: align harness file names, remove unnecessary type casting

* chore: remove unnecessary type casting

* chore: cleanup principalControlsCerTemplate logic

* fix: user dnsvalidity even if email set, early return in emailvaliditycheck,  return error on filterTempResultsForESC6a
  • Loading branch information
urangel authored Jan 24, 2024
1 parent 7544299 commit 0399f59
Show file tree
Hide file tree
Showing 16 changed files with 4,116 additions and 90 deletions.
233 changes: 233 additions & 0 deletions cmd/api/src/analysis/ad/adcs_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (

"github.com/specterops/bloodhound/dawgs/graph"
"github.com/specterops/bloodhound/graphschema/ad"
"github.com/specterops/bloodhound/graphschema/common"
"github.com/specterops/bloodhound/src/test/integration"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -586,3 +587,235 @@ func TestADCSESC3(t *testing.T) {
})
})
}

func TestADCSESC6a(t *testing.T) {
testContext := integration.NewGraphTestContext(t, graphschema.DefaultGraphSchema())

testContext.DatabaseTestWithSetup(func(harness *integration.HarnessDetails) error {
harness.ESC6aHarnessPrincipalEdges.Setup(testContext)
return nil
}, func(harness integration.HarnessDetails, db graph.Database) {
operation := analysis.NewPostRelationshipOperation(context.Background(), db, "ADCS Post Process Test - ESC6a")

groupExpansions, err := ad2.ExpandAllRDPLocalGroups(context.Background(), db)
require.Nil(t, err)
enterpriseCertAuthorities, err := ad2.FetchNodesByKind(context.Background(), db, ad.EnterpriseCA)
require.Nil(t, err)
certTemplates, err := ad2.FetchNodesByKind(context.Background(), db, ad.CertTemplate)
require.Nil(t, err)
domains, err := ad2.FetchNodesByKind(context.Background(), db, ad.Domain)

cache := ad2.NewADCSCache()
cache.BuildCache(context.Background(), db, enterpriseCertAuthorities, certTemplates)

for _, domain := range domains {
innerDomain := domain

for _, enterpriseCA := range enterpriseCertAuthorities {
if cache.DoesCAChainProperlyToDomain(enterpriseCA, innerDomain) {
innerEnterpriseCA := enterpriseCA

operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error {
if err := ad2.PostADCSESC6a(ctx, tx, outC, groupExpansions, innerEnterpriseCA, innerDomain, cache); err != nil {
t.Logf("failed post processing for %s: %v", ad.ADCSESC6a.String(), err)
} else {
return nil
}

return nil
})
}
}

}
operation.Done()

db.ReadTransaction(context.Background(), func(tx graph.Transaction) error {
if results, err := ops.FetchStartNodes(tx.Relationships().Filterf(func() graph.Criteria {
return query.Kind(query.Relationship(), ad.ADCSESC6a)
})); err != nil {
t.Fatalf("error fetching esc6a edges in integration test; %v", err)
} else {
require.Equal(t, 2, len(results))

require.True(t, results.Contains(harness.ESC6aHarnessPrincipalEdges.Group1))
require.True(t, results.Contains(harness.ESC6aHarnessPrincipalEdges.Group2))

}
return nil
})
})

testContext.DatabaseTestWithSetup(func(harness *integration.HarnessDetails) error {
harness.ESC6aHarnessECA.Setup(testContext)
return nil
}, func(harness integration.HarnessDetails, db graph.Database) {
operation := analysis.NewPostRelationshipOperation(context.Background(), db, "ADCS Post Process Test - ESC6a")

groupExpansions, err := ad2.ExpandAllRDPLocalGroups(context.Background(), db)
require.Nil(t, err)
enterpriseCertAuthorities, err := ad2.FetchNodesByKind(context.Background(), db, ad.EnterpriseCA)
require.Nil(t, err)
certTemplates, err := ad2.FetchNodesByKind(context.Background(), db, ad.CertTemplate)
require.Nil(t, err)
domains, err := ad2.FetchNodesByKind(context.Background(), db, ad.Domain)

cache := ad2.NewADCSCache()
cache.BuildCache(context.Background(), db, enterpriseCertAuthorities, certTemplates)

for _, domain := range domains {
innerDomain := domain

for _, enterpriseCA := range enterpriseCertAuthorities {
if cache.DoesCAChainProperlyToDomain(enterpriseCA, innerDomain) {
innerEnterpriseCA := enterpriseCA

operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error {
if err := ad2.PostADCSESC6a(ctx, tx, outC, groupExpansions, innerEnterpriseCA, innerDomain, cache); err != nil {
t.Logf("failed post processing for %s: %v", ad.ADCSESC6a.String(), err)
} else {
return nil
}

return nil
})
}
}

}
operation.Done()

db.ReadTransaction(context.Background(), func(tx graph.Transaction) error {
if results, err := ops.FetchStartNodes(tx.Relationships().Filterf(func() graph.Criteria {
return query.Kind(query.Relationship(), ad.ADCSESC6a)
})); err != nil {
t.Fatalf("error fetching esc6a edges in integration test; %v", err)
} else {
require.Equal(t, 1, len(results))
name, _ := results.Pick().Properties.Get(common.Name.String()).String()
require.Equal(t, name, "Group0")
}

return nil
})
})

testContext.DatabaseTestWithSetup(func(harness *integration.HarnessDetails) error {
harness.ESC6aHarnessTemplate1.Setup(testContext)
return nil
}, func(harness integration.HarnessDetails, db graph.Database) {
operation := analysis.NewPostRelationshipOperation(context.Background(), db, "ADCS Post Process Test - ESC6a")

groupExpansions, err := ad2.ExpandAllRDPLocalGroups(context.Background(), db)
require.Nil(t, err)
enterpriseCertAuthorities, err := ad2.FetchNodesByKind(context.Background(), db, ad.EnterpriseCA)
require.Nil(t, err)
certTemplates, err := ad2.FetchNodesByKind(context.Background(), db, ad.CertTemplate)
require.Nil(t, err)
domains, err := ad2.FetchNodesByKind(context.Background(), db, ad.Domain)

cache := ad2.NewADCSCache()
cache.BuildCache(context.Background(), db, enterpriseCertAuthorities, certTemplates)

for _, domain := range domains {
innerDomain := domain

for _, enterpriseCA := range enterpriseCertAuthorities {
if cache.DoesCAChainProperlyToDomain(enterpriseCA, innerDomain) {
innerEnterpriseCA := enterpriseCA

operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error {
if err := ad2.PostADCSESC6a(ctx, tx, outC, groupExpansions, innerEnterpriseCA, innerDomain, cache); err != nil {
t.Logf("failed post processing for %s: %v", ad.ADCSESC6a.String(), err)
} else {
return nil
}

return nil
})
}
}

}
operation.Done()

db.ReadTransaction(context.Background(), func(tx graph.Transaction) error {
if results, err := ops.FetchStartNodes(tx.Relationships().Filterf(func() graph.Criteria {
return query.Kind(query.Relationship(), ad.ADCSESC6a)
})); err != nil {
t.Fatalf("error fetching esc6a edges in integration test; %v", err)
} else {
require.Equal(t, 2, len(results))
names := []string{}
for _, result := range results.Slice() {
name, _ := result.Properties.Get(common.Name.String()).String()
names = append(names, name)
}
require.Contains(t, names, "Group1", "Group2")
}

return nil
})
})

testContext.DatabaseTestWithSetup(func(harness *integration.HarnessDetails) error {
harness.ESC6aHarnessTemplate2.Setup(testContext)
return nil
}, func(harness integration.HarnessDetails, db graph.Database) {
operation := analysis.NewPostRelationshipOperation(context.Background(), db, "ADCS Post Process Test - ESC6a")

groupExpansions, err := ad2.ExpandAllRDPLocalGroups(context.Background(), db)
require.Nil(t, err)
enterpriseCertAuthorities, err := ad2.FetchNodesByKind(context.Background(), db, ad.EnterpriseCA)
require.Nil(t, err)
certTemplates, err := ad2.FetchNodesByKind(context.Background(), db, ad.CertTemplate)
require.Nil(t, err)
domains, err := ad2.FetchNodesByKind(context.Background(), db, ad.Domain)

cache := ad2.NewADCSCache()
cache.BuildCache(context.Background(), db, enterpriseCertAuthorities, certTemplates)

for _, domain := range domains {
innerDomain := domain

for _, enterpriseCA := range enterpriseCertAuthorities {
if cache.DoesCAChainProperlyToDomain(enterpriseCA, innerDomain) {
innerEnterpriseCA := enterpriseCA

operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error {
if err := ad2.PostADCSESC6a(ctx, tx, outC, groupExpansions, innerEnterpriseCA, innerDomain, cache); err != nil {
t.Logf("failed post processing for %s: %v", ad.ADCSESC6a.String(), err)
} else {
return nil
}

return nil
})
}
}

}
operation.Done()

db.ReadTransaction(context.Background(), func(tx graph.Transaction) error {
if results, err := ops.FetchStartNodes(tx.Relationships().Filterf(func() graph.Criteria {
return query.Kind(query.Relationship(), ad.ADCSESC6a)
})); err != nil {
t.Fatalf("error fetching esc6a edges in integration test; %v", err)
} else {
names := []string{}
for _, result := range results.Slice() {
name, _ := result.Properties.Get(common.Name.String()).String()
names = append(names, name)
}
require.Equal(t, 14, len(results))
require.NotContains(t, names, "User2")
require.NotContains(t, names, "User3")
require.NotContains(t, names, "User5")
require.NotContains(t, names, "User7")
require.NotContains(t, names, "Computer6")
}
return nil
})
})
}
3 changes: 2 additions & 1 deletion cmd/api/src/test/integration/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,14 +414,15 @@ func (s *GraphTestContext) NewActiveDirectoryRootCAWithThumbprint(name, domainSI
}), ad.Entity, ad.RootCA)
}

func (s *GraphTestContext) NewActiveDirectoryCertTemplate(name, domainSID string, requiresManagerApproval, authenticationEnabled, enrolleeSupplieSubject, subjectAltRequireUpn bool, schemaVersion, authorizedSignatures int, ekus, applicationPolicies []string) *graph.Node {
func (s *GraphTestContext) NewActiveDirectoryCertTemplate(name, domainSID string, requiresManagerApproval, authenticationEnabled, enrolleeSupplieSubject, subjectAltRequireUpn, noSecurityExtension bool, schemaVersion, authorizedSignatures int, ekus, applicationPolicies []string) *graph.Node {
return s.NewNode(graph.AsProperties(graph.PropertyMap{
common.Name: name,
common.ObjectID: must.NewUUIDv4().String(),
ad.DomainSID: domainSID,
ad.RequiresManagerApproval: requiresManagerApproval,
ad.AuthenticationEnabled: authenticationEnabled,
ad.EnrolleeSuppliesSubject: enrolleeSupplieSubject,
ad.NoSecurityExtension: noSecurityExtension,
ad.SchemaVersion: float64(schemaVersion),
ad.AuthorizedSignatures: float64(authorizedSignatures),
ad.EKUs: ekus,
Expand Down
Loading

0 comments on commit 0399f59

Please sign in to comment.