diff --git a/cmd/api/src/analysis/ad/adcs_integration_test.go b/cmd/api/src/analysis/ad/adcs_integration_test.go
index 6406891ffd..d3c4188b44 100644
--- a/cmd/api/src/analysis/ad/adcs_integration_test.go
+++ b/cmd/api/src/analysis/ad/adcs_integration_test.go
@@ -21,8 +21,8 @@ package ad_test
import (
"context"
-
"github.com/specterops/bloodhound/analysis"
+
ad2 "github.com/specterops/bloodhound/analysis/ad"
"github.com/specterops/bloodhound/dawgs/ops"
@@ -177,3 +177,89 @@ func TestGoldenCert(t *testing.T) {
})
}
+
+func TestEnrollOnBehalfOf(t *testing.T) {
+ testContext := integration.NewGraphTestContext(t)
+ testContext.DatabaseTestWithSetup(func(harness *integration.HarnessDetails) {
+ harness.EnrollOnBehalfOfHarnessOne.Setup(testContext)
+ }, func(harness integration.HarnessDetails, db graph.Database) error {
+ certTemplates, err := ad2.FetchNodesByKind(context.Background(), db, ad.CertTemplate)
+ v1Templates := make([]*graph.Node, 0)
+ v2Templates := make([]*graph.Node, 0)
+ for _, template := range certTemplates {
+ if version, err := template.Properties.Get(ad.SchemaVersion.String()).Float64(); err != nil {
+ continue
+ } else if version == 1 {
+ v1Templates = append(v1Templates, template)
+ } else if version >= 2 {
+ v2Templates = append(v2Templates, template)
+ }
+ }
+ require.Nil(t, err)
+ db.ReadTransaction(context.Background(), func(tx graph.Transaction) error {
+ results, err := ad2.EnrollOnBehalfOfVersionOne(tx, v1Templates, certTemplates)
+ require.Nil(t, err)
+
+ require.Len(t, results, 2)
+
+ require.Contains(t, results, analysis.CreatePostRelationshipJob{
+ FromID: harness.EnrollOnBehalfOfHarnessOne.CertTemplate11.ID,
+ ToID: harness.EnrollOnBehalfOfHarnessOne.CertTemplate12.ID,
+ Kind: ad.EnrollOnBehalfOf,
+ })
+
+ require.Contains(t, results, analysis.CreatePostRelationshipJob{
+ FromID: harness.EnrollOnBehalfOfHarnessOne.CertTemplate13.ID,
+ ToID: harness.EnrollOnBehalfOfHarnessOne.CertTemplate12.ID,
+ Kind: ad.EnrollOnBehalfOf,
+ })
+
+ return nil
+ })
+
+ return nil
+ })
+
+ testContext.DatabaseTestWithSetup(func(harness *integration.HarnessDetails) {
+ harness.EnrollOnBehalfOfHarnessTwo.Setup(testContext)
+ }, func(harness integration.HarnessDetails, db graph.Database) error {
+ certTemplates, err := ad2.FetchNodesByKind(context.Background(), db, ad.CertTemplate)
+ v1Templates := make([]*graph.Node, 0)
+ v2Templates := make([]*graph.Node, 0)
+ for _, template := range certTemplates {
+ if version, err := template.Properties.Get(ad.SchemaVersion.String()).Float64(); err != nil {
+ continue
+ } else if version == 1 {
+ v1Templates = append(v1Templates, template)
+ } else if version >= 2 {
+ v2Templates = append(v2Templates, template)
+ }
+ }
+ require.Nil(t, err)
+ db.ReadTransaction(context.Background(), func(tx graph.Transaction) error {
+ results, err := ad2.EnrollOnBehalfOfVersionTwo(tx, v2Templates, certTemplates)
+ require.Nil(t, err)
+
+ require.Len(t, results, 1)
+ require.Contains(t, results, analysis.CreatePostRelationshipJob{
+ FromID: harness.EnrollOnBehalfOfHarnessTwo.CertTemplate21.ID,
+ ToID: harness.EnrollOnBehalfOfHarnessTwo.CertTemplate23.ID,
+ Kind: ad.EnrollOnBehalfOf,
+ })
+
+ results, err = ad2.EnrollOnBehalfOfSelfControl(tx, v1Templates)
+ require.Nil(t, err)
+
+ require.Len(t, results, 1)
+ require.Contains(t, results, analysis.CreatePostRelationshipJob{
+ FromID: harness.EnrollOnBehalfOfHarnessTwo.CertTemplate25.ID,
+ ToID: harness.EnrollOnBehalfOfHarnessTwo.CertTemplate25.ID,
+ Kind: ad.EnrollOnBehalfOf,
+ })
+
+ return nil
+ })
+
+ return nil
+ })
+}
diff --git a/cmd/api/src/test/integration/graph.go b/cmd/api/src/test/integration/graph.go
index 141bfaefe2..7bbfd8bf3f 100644
--- a/cmd/api/src/test/integration/graph.go
+++ b/cmd/api/src/test/integration/graph.go
@@ -510,7 +510,7 @@ func (s *GraphTestContext) NewActiveDirectoryRootCA(name, domainSID string) *gra
}), ad.Entity, ad.RootCA)
}
-func (s *GraphTestContext) NewActiveDirectoryCertTemplate(name, domainSID string, requiresManagerApproval, authenticationEnabled, enrolleeSupplieSubject bool, schemaVersion, authorizedSignatures int) *graph.Node {
+func (s *GraphTestContext) NewActiveDirectoryCertTemplate(name, domainSID string, requiresManagerApproval, authenticationEnabled, enrolleeSupplieSubject, subjectAltRequireUpn bool, schemaVersion, authorizedSignatures int, ekus, applicationPolicies []string) *graph.Node {
return s.NewNode(graph.AsProperties(graph.PropertyMap{
common.Name: name,
common.ObjectID: must.NewUUIDv4().String(),
@@ -520,6 +520,9 @@ func (s *GraphTestContext) NewActiveDirectoryCertTemplate(name, domainSID string
ad.EnrolleeSuppliesSubject: enrolleeSupplieSubject,
ad.SchemaVersion: float64(schemaVersion),
ad.AuthorizedSignatures: float64(authorizedSignatures),
+ ad.EKUs: ekus,
+ ad.ApplicationPolicies: applicationPolicies,
+ ad.SubjectAltRequireUPN: subjectAltRequireUpn,
}), ad.Entity, ad.CertTemplate)
}
diff --git a/cmd/api/src/test/integration/harnesses.go b/cmd/api/src/test/integration/harnesses.go
index 53d0b52983..8766d89976 100644
--- a/cmd/api/src/test/integration/harnesses.go
+++ b/cmd/api/src/test/integration/harnesses.go
@@ -1134,12 +1134,13 @@ type ADCSESC1Harness struct {
}
func (s *ADCSESC1Harness) Setup(graphTestContext *GraphTestContext) {
+ emptyEkus := make([]string, 0)
sid := RandomDomainSID()
s.Domain1 = graphTestContext.NewActiveDirectoryDomain("domain 1", sid, false, true)
s.AuthStore1 = graphTestContext.NewActiveDirectoryNTAuthStore("ntauthstore 1", sid)
s.EnterpriseCA1 = graphTestContext.NewActiveDirectoryEnterpriseCA("eca 1", sid)
s.RootCA1 = graphTestContext.NewActiveDirectoryRootCA("rca 1", sid)
- s.CertTemplate1 = graphTestContext.NewActiveDirectoryCertTemplate("certtemplate 1", sid, false, true, true, 1, 0)
+ s.CertTemplate1 = graphTestContext.NewActiveDirectoryCertTemplate("certtemplate 1", sid, false, true, true, false, 1, 0, emptyEkus, emptyEkus)
s.Group11 = graphTestContext.NewActiveDirectoryGroup("group1-1", sid)
s.Group12 = graphTestContext.NewActiveDirectoryGroup("group1-2", sid)
s.Group13 = graphTestContext.NewActiveDirectoryGroup("group1-3", sid)
@@ -1175,7 +1176,7 @@ func (s *ADCSESC1Harness) Setup(graphTestContext *GraphTestContext) {
s.EnterpriseCA22 = graphTestContext.NewActiveDirectoryEnterpriseCA("eca2-2", sid)
s.Group21 = graphTestContext.NewActiveDirectoryGroup("group2-1", sid)
s.Group22 = graphTestContext.NewActiveDirectoryGroup("group2-2", sid)
- s.CertTemplate2 = graphTestContext.NewActiveDirectoryCertTemplate("certtemplate 2", sid, false, true, true, 1, 0)
+ s.CertTemplate2 = graphTestContext.NewActiveDirectoryCertTemplate("certtemplate 2", sid, false, true, true, false, 1, 0, emptyEkus, emptyEkus)
graphTestContext.NewRelationship(s.RootCA2, s.Domain2, ad.RootCAFor)
graphTestContext.NewRelationship(s.AuthStore2, s.Domain2, ad.NTAuthStoreFor)
@@ -1197,7 +1198,7 @@ func (s *ADCSESC1Harness) Setup(graphTestContext *GraphTestContext) {
s.EnterpriseCA32 = graphTestContext.NewActiveDirectoryEnterpriseCA("eca3-2", sid)
s.Group31 = graphTestContext.NewActiveDirectoryGroup("group3-1", sid)
s.Group32 = graphTestContext.NewActiveDirectoryGroup("group3-2", sid)
- s.CertTemplate3 = graphTestContext.NewActiveDirectoryCertTemplate("certtemplate 3", sid, false, true, true, 1, 0)
+ s.CertTemplate3 = graphTestContext.NewActiveDirectoryCertTemplate("certtemplate 3", sid, false, true, true, false, 1, 0, emptyEkus, emptyEkus)
graphTestContext.NewRelationship(s.RootCA3, s.Domain3, ad.RootCAFor)
graphTestContext.NewRelationship(s.AuthStore3, s.Domain3, ad.NTAuthStoreFor)
@@ -1222,12 +1223,12 @@ func (s *ADCSESC1Harness) Setup(graphTestContext *GraphTestContext) {
s.Group44 = graphTestContext.NewActiveDirectoryGroup("group4-4", sid)
s.Group45 = graphTestContext.NewActiveDirectoryGroup("group4-5", sid)
s.Group46 = graphTestContext.NewActiveDirectoryGroup("group4-6", sid)
- s.CertTemplate41 = graphTestContext.NewActiveDirectoryCertTemplate("certtemplate 4-1", sid, false, true, true, 2, 1)
- s.CertTemplate42 = graphTestContext.NewActiveDirectoryCertTemplate("certtemplate 4-2", sid, false, true, true, 2, 0)
- s.CertTemplate43 = graphTestContext.NewActiveDirectoryCertTemplate("certtemplate 4-3", sid, false, true, true, 1, 0)
- s.CertTemplate44 = graphTestContext.NewActiveDirectoryCertTemplate("certtemplate 4-4", sid, true, true, true, 1, 0)
- s.CertTemplate45 = graphTestContext.NewActiveDirectoryCertTemplate("certtemplate 4-5", sid, false, false, true, 1, 0)
- s.CertTemplate46 = graphTestContext.NewActiveDirectoryCertTemplate("certtemplate 4-6", sid, false, true, false, 1, 0)
+ s.CertTemplate41 = graphTestContext.NewActiveDirectoryCertTemplate("certtemplate 4-1", sid, false, true, true, false, 2, 1, emptyEkus, emptyEkus)
+ s.CertTemplate42 = graphTestContext.NewActiveDirectoryCertTemplate("certtemplate 4-2", sid, false, true, true, false, 2, 0, emptyEkus, emptyEkus)
+ s.CertTemplate43 = graphTestContext.NewActiveDirectoryCertTemplate("certtemplate 4-3", sid, false, true, true, false, 1, 0, emptyEkus, emptyEkus)
+ s.CertTemplate44 = graphTestContext.NewActiveDirectoryCertTemplate("certtemplate 4-4", sid, true, true, true, false, 1, 0, emptyEkus, emptyEkus)
+ s.CertTemplate45 = graphTestContext.NewActiveDirectoryCertTemplate("certtemplate 4-5", sid, false, false, true, false, 1, 0, emptyEkus, emptyEkus)
+ s.CertTemplate46 = graphTestContext.NewActiveDirectoryCertTemplate("certtemplate 4-6", sid, false, true, false, true, 1, 0, emptyEkus, emptyEkus)
graphTestContext.NewRelationship(s.AuthStore4, s.Domain4, ad.NTAuthStoreFor)
graphTestContext.NewRelationship(s.RootCA4, s.Domain4, ad.RootCAFor)
@@ -1251,7 +1252,76 @@ func (s *ADCSESC1Harness) Setup(graphTestContext *GraphTestContext) {
graphTestContext.NewRelationship(s.CertTemplate44, s.EnterpriseCA4, ad.PublishedTo)
graphTestContext.NewRelationship(s.CertTemplate45, s.EnterpriseCA4, ad.PublishedTo)
graphTestContext.NewRelationship(s.CertTemplate46, s.EnterpriseCA4, ad.PublishedTo)
+}
+
+type EnrollOnBehalfOfHarnessTwo struct {
+ Domain2 *graph.Node
+ AuthStore2 *graph.Node
+ RootCA2 *graph.Node
+ EnterpriseCA2 *graph.Node
+ CertTemplate21 *graph.Node
+ CertTemplate22 *graph.Node
+ CertTemplate23 *graph.Node
+ CertTemplate24 *graph.Node
+ CertTemplate25 *graph.Node
+}
+func (s *EnrollOnBehalfOfHarnessTwo) Setup(gt *GraphTestContext) {
+ certRequestAgentEKU := make([]string, 0)
+ certRequestAgentEKU = append(certRequestAgentEKU, adAnalysis.EkuCertRequestAgent)
+ emptyAppPolicies := make([]string, 0)
+ sid := RandomDomainSID()
+ s.Domain2 = gt.NewActiveDirectoryDomain("domain2", sid, false, true)
+ s.AuthStore2 = gt.NewActiveDirectoryNTAuthStore("authstore2", sid)
+ s.RootCA2 = gt.NewActiveDirectoryRootCA("rca2", sid)
+ s.EnterpriseCA2 = gt.NewActiveDirectoryEnterpriseCA("eca2", sid)
+ s.CertTemplate21 = gt.NewActiveDirectoryCertTemplate("certtemplate2-1", sid, false, false, false, false, 1, 0, certRequestAgentEKU, emptyAppPolicies)
+ s.CertTemplate22 = gt.NewActiveDirectoryCertTemplate("certtemplate2-2", sid, false, false, false, false, 1, 0, []string{adAnalysis.EkuCertRequestAgent, adAnalysis.EkuAnyPurpose}, emptyAppPolicies)
+ s.CertTemplate23 = gt.NewActiveDirectoryCertTemplate("certtemplate2-3", sid, false, false, false, false, 2, 1, certRequestAgentEKU, []string{adAnalysis.EkuCertRequestAgent})
+ s.CertTemplate24 = gt.NewActiveDirectoryCertTemplate("certtemplate2-4", sid, false, false, false, false, 2, 1, []string{}, emptyAppPolicies)
+ s.CertTemplate25 = gt.NewActiveDirectoryCertTemplate("certtemplate2-5", sid, false, false, false, true, 1, 1, []string{}, emptyAppPolicies)
+
+ gt.NewRelationship(s.AuthStore2, s.Domain2, ad.NTAuthStoreFor)
+ gt.NewRelationship(s.RootCA2, s.Domain2, ad.RootCAFor)
+ gt.NewRelationship(s.EnterpriseCA2, s.AuthStore2, ad.TrustedForNTAuth)
+ gt.NewRelationship(s.EnterpriseCA2, s.RootCA2, ad.EnterpriseCAFor)
+ gt.NewRelationship(s.CertTemplate21, s.EnterpriseCA2, ad.PublishedTo)
+ gt.NewRelationship(s.CertTemplate22, s.EnterpriseCA2, ad.PublishedTo)
+ gt.NewRelationship(s.CertTemplate23, s.EnterpriseCA2, ad.PublishedTo)
+ gt.NewRelationship(s.CertTemplate24, s.EnterpriseCA2, ad.PublishedTo)
+ gt.NewRelationship(s.CertTemplate25, s.EnterpriseCA2, ad.PublishedTo)
+}
+
+type EnrollOnBehalfOfHarnessOne struct {
+ Domain1 *graph.Node
+ AuthStore1 *graph.Node
+ RootCA1 *graph.Node
+ EnterpriseCA1 *graph.Node
+ CertTemplate11 *graph.Node
+ CertTemplate12 *graph.Node
+ CertTemplate13 *graph.Node
+}
+
+func (s *EnrollOnBehalfOfHarnessOne) Setup(gt *GraphTestContext) {
+ sid := RandomDomainSID()
+ anyPurposeEkus := make([]string, 0)
+ anyPurposeEkus = append(anyPurposeEkus, adAnalysis.EkuAnyPurpose)
+ emptyAppPolicies := make([]string, 0)
+ s.Domain1 = gt.NewActiveDirectoryDomain("domain1", sid, false, true)
+ s.AuthStore1 = gt.NewActiveDirectoryNTAuthStore("authstore1", sid)
+ s.RootCA1 = gt.NewActiveDirectoryRootCA("rca1", sid)
+ s.EnterpriseCA1 = gt.NewActiveDirectoryEnterpriseCA("eca1", sid)
+ s.CertTemplate11 = gt.NewActiveDirectoryCertTemplate("certtemplate1-1", sid, false, false, false, false, 2, 0, anyPurposeEkus, emptyAppPolicies)
+ s.CertTemplate12 = gt.NewActiveDirectoryCertTemplate("certtemplate1-2", sid, false, false, false, false, 1, 0, anyPurposeEkus, emptyAppPolicies)
+ s.CertTemplate13 = gt.NewActiveDirectoryCertTemplate("certtemplate1-3", sid, false, false, false, false, 2, 0, anyPurposeEkus, emptyAppPolicies)
+
+ gt.NewRelationship(s.AuthStore1, s.Domain1, ad.NTAuthStoreFor)
+ gt.NewRelationship(s.RootCA1, s.Domain1, ad.RootCAFor)
+ gt.NewRelationship(s.EnterpriseCA1, s.AuthStore1, ad.TrustedForNTAuth)
+ gt.NewRelationship(s.EnterpriseCA1, s.RootCA1, ad.EnterpriseCAFor)
+ gt.NewRelationship(s.CertTemplate11, s.EnterpriseCA1, ad.PublishedTo)
+ gt.NewRelationship(s.CertTemplate12, s.EnterpriseCA1, ad.PublishedTo)
+ gt.NewRelationship(s.CertTemplate13, s.EnterpriseCA1, ad.PublishedTo)
}
type ADCSGoldenCertHarness struct {
@@ -1414,6 +1484,8 @@ type HarnessDetails struct {
SearchHarness SearchHarness
ShortcutHarness ShortcutHarness
ADCSESC1Harness ADCSESC1Harness
+ EnrollOnBehalfOfHarnessOne EnrollOnBehalfOfHarnessOne
+ EnrollOnBehalfOfHarnessTwo EnrollOnBehalfOfHarnessTwo
ADCSGoldenCertHarness ADCSGoldenCertHarness
NumCollectedActiveDirectoryDomains int
AZInboundControlHarness AZInboundControlHarness
diff --git a/cmd/api/src/test/integration/harnesses/enrollonbehalfof-1.json b/cmd/api/src/test/integration/harnesses/enrollonbehalfof-1.json
new file mode 100644
index 0000000000..61d33b1cec
--- /dev/null
+++ b/cmd/api/src/test/integration/harnesses/enrollonbehalfof-1.json
@@ -0,0 +1,235 @@
+{
+ "style": {
+ "font-family": "sans-serif",
+ "background-color": "#ffffff",
+ "background-image": "",
+ "background-size": "100%",
+ "node-color": "#ffffff",
+ "border-width": 4,
+ "border-color": "#000000",
+ "radius": 50,
+ "node-padding": 5,
+ "node-margin": 2,
+ "outside-position": "auto",
+ "node-icon-image": "",
+ "node-background-image": "",
+ "icon-position": "inside",
+ "icon-size": 64,
+ "caption-position": "inside",
+ "caption-max-width": 200,
+ "caption-color": "#000000",
+ "caption-font-size": 50,
+ "caption-font-weight": "normal",
+ "label-position": "inside",
+ "label-display": "pill",
+ "label-color": "#000000",
+ "label-background-color": "#ffffff",
+ "label-border-color": "#000000",
+ "label-border-width": 4,
+ "label-font-size": 40,
+ "label-padding": 5,
+ "label-margin": 4,
+ "directionality": "directed",
+ "detail-position": "inline",
+ "detail-orientation": "parallel",
+ "arrow-width": 5,
+ "arrow-color": "#000000",
+ "margin-start": 5,
+ "margin-end": 5,
+ "margin-peer": 20,
+ "attachment-start": "normal",
+ "attachment-end": "normal",
+ "relationship-icon-image": "",
+ "type-color": "#000000",
+ "type-background-color": "#ffffff",
+ "type-border-color": "#000000",
+ "type-border-width": 0,
+ "type-font-size": 16,
+ "type-padding": 5,
+ "property-position": "outside",
+ "property-alignment": "colon",
+ "property-color": "#000000",
+ "property-font-size": 16,
+ "property-font-weight": "normal"
+ },
+ "nodes": [
+ {
+ "id": "n1",
+ "position": {
+ "x": 729.9551990267428,
+ "y": -4
+ },
+ "caption": "Domain1",
+ "labels": [],
+ "properties": {},
+ "style": {
+ "node-color": "#68ccca"
+ }
+ },
+ {
+ "id": "n2",
+ "position": {
+ "x": 129,
+ "y": 273.97628342478527
+ },
+ "caption": "CertTemplate1-1",
+ "labels": [],
+ "properties": {
+ "schemaversion": "2",
+ "ekus": "[\"2.5.29.37.0\"]"
+ },
+ "style": {
+ "node-color": "#fda1ff"
+ }
+ },
+ {
+ "id": "n3",
+ "position": {
+ "x": 487.6313891898351,
+ "y": -4
+ },
+ "caption": "NTAuthStore1",
+ "labels": [],
+ "properties": {},
+ "style": {
+ "node-color": "#7b64ff"
+ }
+ },
+ {
+ "id": "n4",
+ "position": {
+ "x": 487.6313891898351,
+ "y": 273.97628342478527
+ },
+ "caption": "EnterpriseCA1",
+ "labels": [],
+ "properties": {},
+ "style": {
+ "node-color": "#b0bc00"
+ }
+ },
+ {
+ "id": "n5",
+ "position": {
+ "x": 230.03558347937087,
+ "y": 551.9525668495705
+ },
+ "caption": "CertTemplate1-2",
+ "labels": [],
+ "properties": {
+ "schemaversion": "1",
+ "ekus": "[\"2.5.29.37.0\"]"
+ },
+ "style": {
+ "node-color": "#fda1ff"
+ }
+ },
+ {
+ "id": "n6",
+ "position": {
+ "x": 508.01086036954564,
+ "y": 551.2045130499298
+ },
+ "caption": "CertTemplate1-3",
+ "labels": [],
+ "properties": {
+ "schemaversion": "2",
+ "ekus": "[\"2.5.29.37.0\"]"
+ },
+ "style": {
+ "node-color": "#fda1ff"
+ }
+ },
+ {
+ "id": "n7",
+ "position": {
+ "x": 729.9551990267428,
+ "y": 273.97628342478527
+ },
+ "caption": "RootCA1",
+ "labels": [],
+ "properties": {},
+ "style": {
+ "node-color": "#e27300"
+ }
+ }
+ ],
+ "relationships": [
+ {
+ "id": "n0",
+ "fromId": "n3",
+ "toId": "n1",
+ "type": "NTAuthStoreFor",
+ "properties": {},
+ "style": {}
+ },
+ {
+ "id": "n1",
+ "fromId": "n4",
+ "toId": "n3",
+ "type": "TrustedForNTAuth",
+ "properties": {},
+ "style": {}
+ },
+ {
+ "id": "n2",
+ "fromId": "n2",
+ "toId": "n4",
+ "type": "PublishedTo",
+ "properties": {},
+ "style": {}
+ },
+ {
+ "id": "n3",
+ "fromId": "n5",
+ "toId": "n4",
+ "type": "PublishedTo",
+ "properties": {},
+ "style": {}
+ },
+ {
+ "id": "n4",
+ "fromId": "n6",
+ "toId": "n4",
+ "type": "PublishedTo",
+ "properties": {},
+ "style": {}
+ },
+ {
+ "id": "n5",
+ "fromId": "n7",
+ "toId": "n1",
+ "type": "RootCAFor",
+ "properties": {},
+ "style": {}
+ },
+ {
+ "id": "n6",
+ "fromId": "n4",
+ "toId": "n7",
+ "type": "EnterpriseCAFor",
+ "properties": {},
+ "style": {}
+ },
+ {
+ "id": "n7",
+ "fromId": "n2",
+ "toId": "n5",
+ "type": "EnrollOnBehalfOf",
+ "properties": {},
+ "style": {
+ "arrow-color": "#68ccca"
+ }
+ },
+ {
+ "id": "n8",
+ "fromId": "n6",
+ "toId": "n5",
+ "type": "EnrollOnBehalfOf",
+ "properties": {},
+ "style": {
+ "arrow-color": "#68ccca"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/cmd/api/src/test/integration/harnesses/enrollonbehalfof-1.svg b/cmd/api/src/test/integration/harnesses/enrollonbehalfof-1.svg
new file mode 100644
index 0000000000..d075e118df
--- /dev/null
+++ b/cmd/api/src/test/integration/harnesses/enrollonbehalfof-1.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/cmd/api/src/test/integration/harnesses/enrollonbehalfof-2.json b/cmd/api/src/test/integration/harnesses/enrollonbehalfof-2.json
new file mode 100644
index 0000000000..5ea093bf1c
--- /dev/null
+++ b/cmd/api/src/test/integration/harnesses/enrollonbehalfof-2.json
@@ -0,0 +1,287 @@
+{
+ "nodes": [
+ {
+ "id": "n0",
+ "position": {
+ "x": -569.1685177598522,
+ "y": -1021.0927494329366
+ },
+ "caption": "Domain2",
+ "labels": [],
+ "properties": {},
+ "style": {
+ "node-color": "#68ccca"
+ }
+ },
+ {
+ "id": "n2",
+ "position": {
+ "x": -811.4923275967599,
+ "y": -1021.0927494329366
+ },
+ "caption": "NTAuthStore2",
+ "labels": [],
+ "properties": {},
+ "style": {
+ "node-color": "#7b64ff"
+ }
+ },
+ {
+ "id": "n5",
+ "position": {
+ "x": -811.4923275967599,
+ "y": -743.1164660081513
+ },
+ "caption": "EnterpriseCA2",
+ "labels": [],
+ "properties": {},
+ "style": {
+ "node-color": "#b0bc00"
+ }
+ },
+ {
+ "id": "n13",
+ "position": {
+ "x": -569.1685177598522,
+ "y": -743.1164660081513
+ },
+ "caption": "RootCA2",
+ "labels": [],
+ "properties": {},
+ "style": {
+ "node-color": "#e27300"
+ }
+ },
+ {
+ "id": "n14",
+ "position": {
+ "x": -1151.5140057279425,
+ "y": -743.1164660081513
+ },
+ "caption": "CertTemplate2-1",
+ "labels": [],
+ "properties": {
+ "ekus": "[\"1.3.6.1.4.1.311.20.2.1\"]"
+ },
+ "style": {
+ "node-color": "#fda1ff"
+ }
+ },
+ {
+ "id": "n15",
+ "position": {
+ "x": -1151.5140057279425,
+ "y": -546.8048586088046
+ },
+ "caption": "CertTemplate2-2",
+ "labels": [],
+ "properties": {
+ "ekus": "[\"1.3.6.1.4.1.311.20.2.1\", \"2.5.29.37.0\"]"
+ },
+ "style": {
+ "node-color": "#fda1ff"
+ }
+ },
+ {
+ "id": "n16",
+ "position": {
+ "x": -981.5031666623508,
+ "y": -448.6490549091318
+ },
+ "caption": "CertTemplate2-3",
+ "labels": [],
+ "properties": {
+ "ekus": "[]",
+ "schemaversion": "2",
+ "authorizedsignatures": "1",
+ "applicationpolicies": "[\"1.3.6.1.4.1.311.20.2.1\"]"
+ },
+ "style": {
+ "node-color": "#fda1ff"
+ }
+ },
+ {
+ "id": "n17",
+ "position": {
+ "x": -695.4923275967599,
+ "y": -448.6490549091318
+ },
+ "caption": "CertTemplate2-4",
+ "labels": [],
+ "properties": {
+ "ekus": "[]",
+ "schemaversion": "2",
+ "authorizedsignatures": "1",
+ "applicationpolicies": "[]"
+ },
+ "style": {
+ "node-color": "#fda1ff"
+ }
+ },
+ {
+ "id": "n18",
+ "position": {
+ "x": -517.02491649774,
+ "y": -448.6490549091315
+ },
+ "caption": "CertTemplate2-5",
+ "labels": [],
+ "properties": {
+ "ekus": "[]",
+ "schemaversion": "1",
+ "subjectaltrequiresupn": "true"
+ },
+ "style": {
+ "node-color": "#fda1ff"
+ }
+ }
+ ],
+ "relationships": [
+ {
+ "id": "n0",
+ "fromId": "n2",
+ "toId": "n0",
+ "type": "NTAuthStoreFor",
+ "properties": {},
+ "style": {}
+ },
+ {
+ "id": "n1",
+ "fromId": "n5",
+ "toId": "n2",
+ "type": "TrustedForNTAuth",
+ "properties": {},
+ "style": {}
+ },
+ {
+ "id": "n9",
+ "fromId": "n13",
+ "toId": "n0",
+ "type": "RootCAFor",
+ "properties": {},
+ "style": {}
+ },
+ {
+ "id": "n10",
+ "fromId": "n5",
+ "toId": "n13",
+ "type": "EnterpriseCAFor",
+ "properties": {},
+ "style": {}
+ },
+ {
+ "id": "n11",
+ "fromId": "n14",
+ "toId": "n5",
+ "type": "PublishedTo",
+ "properties": {},
+ "style": {}
+ },
+ {
+ "id": "n12",
+ "fromId": "n15",
+ "toId": "n5",
+ "type": "PublishedTo",
+ "properties": {},
+ "style": {}
+ },
+ {
+ "id": "n13",
+ "fromId": "n16",
+ "toId": "n5",
+ "type": "PublishedTo",
+ "properties": {},
+ "style": {}
+ },
+ {
+ "id": "n14",
+ "fromId": "n17",
+ "toId": "n5",
+ "type": "PublishedTo",
+ "properties": {},
+ "style": {}
+ },
+ {
+ "id": "n15",
+ "type": "EnrollOnBehalfOf",
+ "style": {
+ "arrow-color": "#a4dd00"
+ },
+ "properties": {},
+ "toId": "n16",
+ "fromId": "n14"
+ },
+ {
+ "id": "n16",
+ "fromId": "n18",
+ "toId": "n5",
+ "type": "PublishedTo",
+ "properties": {},
+ "style": {}
+ },
+ {
+ "id": "n17",
+ "fromId": "n18",
+ "toId": "n18",
+ "type": "EnrollOnBehalfOf",
+ "properties": {},
+ "style": {
+ "type-color": "#4d4d4d",
+ "arrow-color": "#a4dd00"
+ }
+ }
+ ],
+ "style": {
+ "font-family": "sans-serif",
+ "background-color": "#ffffff",
+ "background-image": "",
+ "background-size": "100%",
+ "node-color": "#ffffff",
+ "border-width": 4,
+ "border-color": "#000000",
+ "radius": 50,
+ "node-padding": 5,
+ "node-margin": 2,
+ "outside-position": "auto",
+ "node-icon-image": "",
+ "node-background-image": "",
+ "icon-position": "inside",
+ "icon-size": 64,
+ "caption-position": "inside",
+ "caption-max-width": 200,
+ "caption-color": "#000000",
+ "caption-font-size": 50,
+ "caption-font-weight": "normal",
+ "label-position": "inside",
+ "label-display": "pill",
+ "label-color": "#000000",
+ "label-background-color": "#ffffff",
+ "label-border-color": "#000000",
+ "label-border-width": 4,
+ "label-font-size": 40,
+ "label-padding": 5,
+ "label-margin": 4,
+ "directionality": "directed",
+ "detail-position": "inline",
+ "detail-orientation": "parallel",
+ "arrow-width": 5,
+ "arrow-color": "#000000",
+ "margin-start": 5,
+ "margin-end": 5,
+ "margin-peer": 20,
+ "attachment-start": "normal",
+ "attachment-end": "normal",
+ "relationship-icon-image": "",
+ "type-color": "#000000",
+ "type-background-color": "#ffffff",
+ "type-border-color": "#000000",
+ "type-border-width": 0,
+ "type-font-size": 16,
+ "type-padding": 5,
+ "property-position": "outside",
+ "property-alignment": "colon",
+ "property-color": "#000000",
+ "property-font-size": 16,
+ "property-font-weight": "normal"
+ }
+}
\ No newline at end of file
diff --git a/cmd/api/src/test/integration/harnesses/enrollonbehalfof-2.svg b/cmd/api/src/test/integration/harnesses/enrollonbehalfof-2.svg
new file mode 100644
index 0000000000..75b89d04f0
--- /dev/null
+++ b/cmd/api/src/test/integration/harnesses/enrollonbehalfof-2.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/go/analysis/ad/adcs.go b/packages/go/analysis/ad/adcs.go
index 063a95af04..6aad2beb57 100644
--- a/packages/go/analysis/ad/adcs.go
+++ b/packages/go/analysis/ad/adcs.go
@@ -154,12 +154,12 @@ func validatePublishedCertTemplateForEsc1(properties PublishedCertTemplateValida
}
}
-func PostEnrollOnBehalfOf(certTemplates []graph.Node, operation analysis.StatTrackedOperation[analysis.CreatePostRelationshipJob]) error {
- versionOneTemplates := make([]graph.Node, 0)
- versionTwoTemplates := make([]graph.Node, 0)
+func PostEnrollOnBehalfOf(certTemplates []*graph.Node, operation analysis.StatTrackedOperation[analysis.CreatePostRelationshipJob]) error {
+ versionOneTemplates := make([]*graph.Node, 0)
+ versionTwoTemplates := make([]*graph.Node, 0)
for _, node := range certTemplates {
- if version, err := node.Properties.Get(ad.SchemaVersion.String()).Int(); err != nil {
+ if version, err := node.Properties.Get(ad.SchemaVersion.String()).Float64(); err != nil {
log.Errorf("Error getting schema version for cert template %d: %v", node.ID, err)
} else {
if version == 1 {
@@ -173,21 +173,52 @@ func PostEnrollOnBehalfOf(certTemplates []graph.Node, operation analysis.StatTra
}
operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error {
- return enrollOnBehalfOfVersionTwo(tx, versionTwoTemplates, certTemplates, outC)
+ if results, err := EnrollOnBehalfOfVersionTwo(tx, versionTwoTemplates, certTemplates); err != nil {
+ return err
+ } else {
+ for _, result := range results {
+ if !channels.Submit(ctx, outC, result) {
+ return nil
+ }
+ }
+
+ return nil
+ }
})
operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error {
- return enrollOnBehalfOfVersionOne(tx, versionOneTemplates, certTemplates, outC)
+ if results, err := EnrollOnBehalfOfVersionOne(tx, versionOneTemplates, certTemplates); err != nil {
+ return err
+ } else {
+ for _, result := range results {
+ if !channels.Submit(ctx, outC, result) {
+ return nil
+ }
+ }
+
+ return nil
+ }
})
operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error {
- return enrollOnBehalfOfSelfControl(tx, versionOneTemplates, outC)
+ if results, err := EnrollOnBehalfOfSelfControl(tx, versionOneTemplates); err != nil {
+ return err
+ } else {
+ for _, result := range results {
+ if !channels.Submit(ctx, outC, result) {
+ return nil
+ }
+ }
+
+ return nil
+ }
})
return nil
}
-func enrollOnBehalfOfVersionTwo(tx graph.Transaction, versionTwoCertTemplates, allCertTemplates []graph.Node, outC chan<- analysis.CreatePostRelationshipJob) error {
+func EnrollOnBehalfOfVersionTwo(tx graph.Transaction, versionTwoCertTemplates, allCertTemplates []*graph.Node) ([]analysis.CreatePostRelationshipJob, error) {
+ results := make([]analysis.CreatePostRelationshipJob, 0)
for _, certTemplateOne := range allCertTemplates {
if hasBadEku, err := certTemplateHasEku(certTemplateOne, EkuAnyPurpose); err != nil {
log.Errorf("error getting ekus for cert template %d: %w", certTemplateOne.ID, err)
@@ -205,7 +236,9 @@ func enrollOnBehalfOfVersionTwo(tx graph.Transaction, versionTwoCertTemplates, a
continue
} else {
for _, certTemplateTwo := range versionTwoCertTemplates {
- if authorizedSignatures, err := certTemplateTwo.Properties.Get(ad.AuthorizedSignatures.String()).Int(); err != nil {
+ if certTemplateOne.ID == certTemplateTwo.ID {
+ continue
+ } else if authorizedSignatures, err := certTemplateTwo.Properties.Get(ad.AuthorizedSignatures.String()).Float64(); err != nil {
log.Errorf("Error getting authorized signatures for cert template %d: %w", certTemplateTwo.ID, err)
} else if authorizedSignatures < 1 {
continue
@@ -218,23 +251,27 @@ func enrollOnBehalfOfVersionTwo(tx graph.Transaction, versionTwoCertTemplates, a
} else if !isLinked {
continue
} else {
- outC <- analysis.CreatePostRelationshipJob{
+ results = append(results, analysis.CreatePostRelationshipJob{
FromID: certTemplateOne.ID,
ToID: certTemplateTwo.ID,
Kind: ad.EnrollOnBehalfOf,
- }
+ })
}
}
}
}
- return nil
+ return results, nil
}
-func enrollOnBehalfOfVersionOne(tx graph.Transaction, versionOneCertTemplates []graph.Node, allCertTemplates []graph.Node, outC chan<- analysis.CreatePostRelationshipJob) error {
+func EnrollOnBehalfOfVersionOne(tx graph.Transaction, versionOneCertTemplates []*graph.Node, allCertTemplates []*graph.Node) ([]analysis.CreatePostRelationshipJob, error) {
+ results := make([]analysis.CreatePostRelationshipJob, 0)
+
for _, certTemplateOne := range allCertTemplates {
//prefilter as much as we can first
- if hasEku, err := certTemplateHasEkuOrAll(certTemplateOne, EkuCertRequestAgent, EkuAnyPurpose); err != nil {
+ if slices.Contains(versionOneCertTemplates, certTemplateOne) {
+ continue
+ } else if hasEku, err := certTemplateHasEkuOrAll(certTemplateOne, EkuCertRequestAgent, EkuAnyPurpose); err != nil {
log.Errorf("Error checking ekus for certtemplate %d: %w", certTemplateOne.ID, err)
} else if !hasEku {
continue
@@ -253,30 +290,31 @@ func enrollOnBehalfOfVersionOne(tx graph.Transaction, versionOneCertTemplates []
} else if !hasPath {
continue
} else {
- outC <- analysis.CreatePostRelationshipJob{
- FromID: 0,
- ToID: 0,
- Kind: nil,
- }
+ results = append(results, analysis.CreatePostRelationshipJob{
+ FromID: certTemplateOne.ID,
+ ToID: certTemplateTwo.ID,
+ Kind: ad.EnrollOnBehalfOf,
+ })
}
}
}
}
- return nil
+ return results, nil
}
-func getDomainForCertTemplate(tx graph.Transaction, certTemplate graph.Node) (graph.Node, error) {
+func getDomainForCertTemplate(tx graph.Transaction, certTemplate *graph.Node) (*graph.Node, error) {
if domainSid, err := certTemplate.Properties.Get(ad.DomainSID.String()).String(); err != nil {
- return graph.Node{}, err
+ return &graph.Node{}, err
} else if domainNode, err := analysis.FetchNodeByObjectID(tx, domainSid); err != nil {
- return graph.Node{}, err
+ return &graph.Node{}, err
} else {
- return *domainNode, nil
+ return domainNode, nil
}
}
-func enrollOnBehalfOfSelfControl(tx graph.Transaction, versionOneCertTemplates []graph.Node, outC chan<- analysis.CreatePostRelationshipJob) error {
+func EnrollOnBehalfOfSelfControl(tx graph.Transaction, versionOneCertTemplates []*graph.Node) ([]analysis.CreatePostRelationshipJob, error) {
+ results := make([]analysis.CreatePostRelationshipJob, 0)
for _, certTemplate := range versionOneCertTemplates {
if hasEku, err := certTemplateHasEkuOrAll(certTemplate, EkuAnyPurpose); err != nil {
log.Errorf("Error checking ekus for certtemplate %d: %w", certTemplate.ID, err)
@@ -293,18 +331,18 @@ func enrollOnBehalfOfSelfControl(tx graph.Transaction, versionOneCertTemplates [
} else if !doesLink {
continue
} else {
- outC <- analysis.CreatePostRelationshipJob{
+ results = append(results, analysis.CreatePostRelationshipJob{
FromID: certTemplate.ID,
ToID: certTemplate.ID,
Kind: ad.EnrollOnBehalfOf,
- }
+ })
}
}
- return nil
+ return results, nil
}
-func certTemplateHasEkuOrAll(certTemplate graph.Node, targetEkus ...string) (bool, error) {
+func certTemplateHasEkuOrAll(certTemplate *graph.Node, targetEkus ...string) (bool, error) {
if ekus, err := certTemplate.Properties.Get(ad.EKUs.String()).StringSlice(); err != nil {
return false, err
} else if len(ekus) == 0 {
@@ -322,7 +360,7 @@ func certTemplateHasEkuOrAll(certTemplate graph.Node, targetEkus ...string) (boo
}
}
-func certTemplateHasEku(certTemplate graph.Node, targetEkus ...string) (bool, error) {
+func certTemplateHasEku(certTemplate *graph.Node, targetEkus ...string) (bool, error) {
if ekus, err := certTemplate.Properties.Get(ad.EKUs.String()).StringSlice(); err != nil {
return false, err
} else {
@@ -349,7 +387,7 @@ func PostADCS(ctx context.Context, db graph.Database, groupExpansions impact.Pat
return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching cert template nodes: %w", err)
} else if domains, err := FetchNodesByKind(ctx, db, ad.Domain); err != nil {
return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching domain nodes: %w", err)
- } else if preProcessStats, err := postADCSPreProcess(ctx, db, enterpriseCertAuthorities, rootCertAuthorities); err != nil {
+ } else if preProcessStats, err := postADCSPreProcess(ctx, db, enterpriseCertAuthorities, rootCertAuthorities, certTemplates); err != nil {
return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed adcs pre-processing: %w", err)
} else {
operation.Stats.Merge(preProcessStats)
@@ -386,7 +424,7 @@ func PostADCS(ctx context.Context, db graph.Database, groupExpansions impact.Pat
}
}
-func postADCSPreProcess(ctx context.Context, db graph.Database, enterpriseCertAuthorities, rootCertAuthorities []*graph.Node) (*analysis.AtomicPostProcessingStats, error) {
+func postADCSPreProcess(ctx context.Context, db graph.Database, enterpriseCertAuthorities, rootCertAuthorities, certTemplates []*graph.Node) (*analysis.AtomicPostProcessingStats, error) {
operation := analysis.NewPostRelationshipOperation(ctx, db, "ADCS Post Processing - No Dependencies")
if err := PostTrustedForNTAuth(ctx, db, operation); err != nil {
@@ -395,6 +433,8 @@ func postADCSPreProcess(ctx context.Context, db graph.Database, enterpriseCertAu
return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed post processing for %s: %w", ad.IssuedSignedBy.String(), err)
} else if err := PostEnterpriseCAFor(ctx, db, operation, enterpriseCertAuthorities); err != nil {
return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed post processing for %s: %w", ad.EnterpriseCAFor.String(), err)
+ } else if err := PostEnrollOnBehalfOf(certTemplates, operation); err != nil {
+ return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed post processing for %s: %w", ad.EnrollOnBehalfOf.String(), err)
} else {
return &operation.Stats, operation.Done()
}
diff --git a/packages/go/analysis/ad/queries.go b/packages/go/analysis/ad/queries.go
index c093fe28a7..5c5933cd8f 100644
--- a/packages/go/analysis/ad/queries.go
+++ b/packages/go/analysis/ad/queries.go
@@ -1439,7 +1439,7 @@ func FetchEnterpriseCAsTrustedForNTAuthPathToDomain(tx graph.Transaction, domain
})
}
-func DoesCertTemplateLinkToDomain(tx graph.Transaction, certTemplate graph.Node, domainNode graph.Node) (bool, error) {
+func DoesCertTemplateLinkToDomain(tx graph.Transaction, certTemplate, domainNode *graph.Node) (bool, error) {
if pathSet, err := FetchCertTemplatePathToDomain(tx, certTemplate, domainNode); err != nil {
return false, err
} else {
@@ -1447,7 +1447,7 @@ func DoesCertTemplateLinkToDomain(tx graph.Transaction, certTemplate graph.Node,
}
}
-func FetchCertTemplatePathToDomain(tx graph.Transaction, certTemplate graph.Node, domain graph.Node) (graph.PathSet, error) {
+func FetchCertTemplatePathToDomain(tx graph.Transaction, certTemplate, domain *graph.Node) (graph.PathSet, error) {
var (
paths = graph.NewPathSet()
)