From 20b270853ca156af89430b01b5adf1ba36544c15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20B=C3=BClow=20Knudsen?= <12843299+JonasBK@users.noreply.github.com> Date: Mon, 12 Feb 2024 19:40:10 +0100 Subject: [PATCH] fix: ESC3 enrollment agent restrictions logic (#405) --- .../src/analysis/ad/adcs_integration_test.go | 65 +++++ cmd/api/src/test/integration/harnesses.go | 62 ++++ .../integration/harnesses/esc3harness3.json | 273 ++++++++++++++++++ .../integration/harnesses/esc3harness3.svg | 1 + packages/go/analysis/ad/esc3.go | 30 +- 5 files changed, 421 insertions(+), 10 deletions(-) create mode 100644 cmd/api/src/test/integration/harnesses/esc3harness3.json create mode 100644 cmd/api/src/test/integration/harnesses/esc3harness3.svg diff --git a/cmd/api/src/analysis/ad/adcs_integration_test.go b/cmd/api/src/analysis/ad/adcs_integration_test.go index a6b105a359..c0d3446fc0 100644 --- a/cmd/api/src/analysis/ad/adcs_integration_test.go +++ b/cmd/api/src/analysis/ad/adcs_integration_test.go @@ -583,6 +583,71 @@ func TestADCSESC3(t *testing.T) { return nil }) }) + + testContext.DatabaseTestWithSetup(func(harness *integration.HarnessDetails) error { + harness.ESC3Harness3.Setup(testContext) + return nil + }, func(harness integration.HarnessDetails, db graph.Database) { + operation := analysis.NewPostRelationshipOperation(context.Background(), db, "ADCS Post Process Test - ESC3") + + 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) + require.Nil(t, err) + + cache := ad2.NewADCSCache() + cache.BuildCache(context.Background(), db, enterpriseCertAuthorities, certTemplates) + + for _, domain := range domains { + innerDomain := domain + + operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { + if enterpriseCAs, err := ad2.FetchEnterpriseCAsTrustedForNTAuthToDomain(tx, innerDomain); err != nil { + return err + } else { + for _, enterpriseCA := range enterpriseCAs { + if cache.DoesCAChainProperlyToDomain(enterpriseCA, innerDomain) { + if err := ad2.PostADCSESC3(ctx, tx, outC, groupExpansions, enterpriseCA, innerDomain, cache); err != nil { + t.Logf("failed post processing for %s: %v", ad.ADCSESC3.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.ADCSESC3) + })); err != nil { + t.Fatalf("error fetching esc3 edges in integration test; %v", err) + } else { + assert.Equal(t, 1, len(results)) + + require.True(t, results.Contains(harness.ESC3Harness3.Group1)) + } + + if edge, err := tx.Relationships().Filterf(func() graph.Criteria { + return query.Kind(query.Relationship(), ad.ADCSESC3) + }).First(); err != nil { + t.Fatalf("error fetching esc3 edges in integration test; %v", err) + } else { + comp, err := ad2.GetADCSESC3EdgeComposition(context.Background(), db, edge) + assert.Nil(t, err) + assert.Equal(t, 7, len(comp.AllNodes())) + assert.False(t, comp.AllNodes().Contains(harness.ESC3Harness3.User2)) + } + return nil + }) + }) } func TestADCSESC9a(t *testing.T) { diff --git a/cmd/api/src/test/integration/harnesses.go b/cmd/api/src/test/integration/harnesses.go index a1e90414ec..b077622f82 100644 --- a/cmd/api/src/test/integration/harnesses.go +++ b/cmd/api/src/test/integration/harnesses.go @@ -1930,6 +1930,67 @@ func (s *ESC3Harness2) Setup(c *GraphTestContext) { c.UpdateNode(s.EnterpriseCA1) } +type ESC3Harness3 struct { + CertTemplate1 *graph.Node + CertTemplate2 *graph.Node + Domain *graph.Node + EnterpriseCA1 *graph.Node + Group1 *graph.Node + NTAuthStore *graph.Node + RootCA *graph.Node + User2 *graph.Node +} + +func (s *ESC3Harness3) Setup(c *GraphTestContext) { + sid := RandomDomainSID() + emptyEkus := make([]string, 0) + s.User2 = c.NewActiveDirectoryUser("User2", sid) + s.Group1 = c.NewActiveDirectoryGroup("Group1", sid) + s.CertTemplate1 = c.NewActiveDirectoryCertTemplate("CertTemplate1", sid, CertTemplateData{ + RequiresManagerApproval: false, + AuthenticationEnabled: true, + EnrolleeSuppliesSubject: false, + SubjectAltRequireUPN: false, + SubjectAltRequireSPN: false, + NoSecurityExtension: false, + SchemaVersion: 2, + AuthorizedSignatures: 0, + EKUS: emptyEkus, + ApplicationPolicies: emptyEkus, + }) + s.CertTemplate2 = c.NewActiveDirectoryCertTemplate("CertTemplate2", sid, CertTemplateData{ + RequiresManagerApproval: false, + AuthenticationEnabled: true, + EnrolleeSuppliesSubject: false, + SubjectAltRequireUPN: true, + SubjectAltRequireSPN: false, + NoSecurityExtension: false, + SchemaVersion: 1, + AuthorizedSignatures: 0, + EKUS: emptyEkus, + ApplicationPolicies: emptyEkus, + }) + s.EnterpriseCA1 = c.NewActiveDirectoryEnterpriseCA("EnterpriseCA1", sid) + s.NTAuthStore = c.NewActiveDirectoryNTAuthStore("NTAuthStore", sid) + s.RootCA = c.NewActiveDirectoryRootCA("RootCA", sid) + s.Domain = c.NewActiveDirectoryDomain("ESC3-1Domain", sid, false, true) + + c.NewRelationship(s.User2, s.Group1, ad.MemberOf) + c.NewRelationship(s.Group1, s.CertTemplate1, ad.Enroll) + c.NewRelationship(s.Group1, s.EnterpriseCA1, ad.Enroll) + c.NewRelationship(s.Group1, s.CertTemplate2, ad.AllExtendedRights) + c.NewRelationship(s.CertTemplate1, s.EnterpriseCA1, ad.PublishedTo) + c.NewRelationship(s.CertTemplate1, s.CertTemplate2, ad.EnrollOnBehalfOf) + c.NewRelationship(s.CertTemplate2, s.EnterpriseCA1, ad.PublishedTo) + c.NewRelationship(s.EnterpriseCA1, s.NTAuthStore, ad.TrustedForNTAuth) + c.NewRelationship(s.EnterpriseCA1, s.RootCA, ad.IssuedSignedBy) + c.NewRelationship(s.NTAuthStore, s.Domain, ad.NTAuthStoreFor) + c.NewRelationship(s.RootCA, s.Domain, ad.RootCAFor) + + s.EnterpriseCA1.Properties.Set(ad.EnrollmentAgentRestrictionsCollected.String(), false) + c.UpdateNode(s.EnterpriseCA1) +} + type ESC9aPrincipalHarness struct { CertTemplate *graph.Node DC *graph.Node @@ -5487,6 +5548,7 @@ type HarnessDetails struct { AZInboundControlHarness AZInboundControlHarness ESC3Harness1 ESC3Harness1 ESC3Harness2 ESC3Harness2 + ESC3Harness3 ESC3Harness3 ESC6aHarnessPrincipalEdges ESC6aHarnessPrincipalEdges ESC6aHarnessECA ESC6aHarnessECA ESC6aHarnessTemplate1 ESC6aHarnessTemplate1 diff --git a/cmd/api/src/test/integration/harnesses/esc3harness3.json b/cmd/api/src/test/integration/harnesses/esc3harness3.json new file mode 100644 index 0000000000..c0226145b7 --- /dev/null +++ b/cmd/api/src/test/integration/harnesses/esc3harness3.json @@ -0,0 +1,273 @@ +{ + "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": "n0", + "position": { + "x": 1881.2280990212867, + "y": 170.6883448055827 + }, + "caption": "Domain", + "labels": [], + "properties": {}, + "style": { + "node-color": "#68ccca" + } + }, + { + "id": "n1", + "position": { + "x": 1434.5135272197335, + "y": 50 + }, + "caption": "NTAuthStore", + "labels": [], + "properties": {}, + "style": { + "node-color": "#653294", + "caption-color": "#ffffff" + } + }, + { + "id": "n2", + "position": { + "x": 1434.5135272197335, + "y": 335.88693405944036 + }, + "caption": "RootCA", + "labels": [], + "properties": {}, + "style": { + "node-color": "#653294", + "caption-color": "#ffffff" + } + }, + { + "id": "n3", + "position": { + "x": 1119.4632817169047, + "y": 170.6883448055827 + }, + "caption": "EnterpriseCA1", + "labels": [], + "properties": { + "EnrollmentAgentRestrictionsCollected": "False" + }, + "style": { + "node-color": "#194d33", + "caption-color": "#ffffff" + } + }, + { + "id": "n5", + "position": { + "x": 688.2215061752007, + "y": 335.88693405944036 + }, + "caption": "CertTemplate2", + "labels": [], + "properties": { + "AuthenticationEnabled": "True", + "RequiresManagerApproval": "False", + "SubjectAltRequireUPN": "True" + }, + "style": { + "node-color": "#fda1ff" + } + }, + { + "id": "n6", + "position": { + "x": 688.221506175201, + "y": 50 + }, + "caption": "CertTemplate1", + "labels": [], + "properties": { + "RequiresManagerApproval": "False", + "AuthorizedSignatures": "0", + "SchemaVersion": "2" + }, + "style": { + "node-color": "#fda1ff" + } + }, + { + "id": "n7", + "position": { + "x": 357.69846441252275, + "y": 170.6883448055827 + }, + "caption": "Group1", + "labels": [], + "properties": {}, + "style": { + "node-color": "#ffffff", + "border-color": "#d33115" + } + }, + { + "id": "n8", + "position": { + "x": 75, + "y": 50 + }, + "caption": "User2", + "labels": [], + "properties": {}, + "style": { + "border-color": "#009ce0" + } + } + ], + "relationships": [ + { + "id": "n0", + "fromId": "n2", + "toId": "n0", + "type": "RootCAFor", + "properties": {}, + "style": { + "arrow-color": "#000000" + } + }, + { + "id": "n1", + "fromId": "n3", + "toId": "n2", + "type": "IssuedSignedBy", + "properties": {}, + "style": { + "arrow-color": "#000000" + } + }, + { + "id": "n2", + "fromId": "n1", + "toId": "n0", + "type": "NTAuthStoreFor", + "properties": {}, + "style": { + "arrow-color": "#000000" + } + }, + { + "id": "n3", + "fromId": "n3", + "toId": "n1", + "type": "TrustedForNTAuth", + "properties": {}, + "style": { + "arrow-color": "#000000" + } + }, + { + "id": "n4", + "fromId": "n5", + "toId": "n3", + "type": "PublishedTo", + "properties": {}, + "style": {} + }, + { + "id": "n5", + "fromId": "n6", + "toId": "n3", + "type": "PublishedTo", + "properties": {}, + "style": {} + }, + { + "id": "n6", + "fromId": "n6", + "toId": "n5", + "type": "EnrollOnBehalfOf", + "properties": {}, + "style": {} + }, + { + "id": "n7", + "fromId": "n7", + "toId": "n3", + "type": "Enroll", + "properties": {}, + "style": {} + }, + { + "id": "n9", + "fromId": "n8", + "toId": "n7", + "type": "MemberOf", + "properties": {}, + "style": {} + }, + { + "id": "n10", + "fromId": "n7", + "toId": "n5", + "type": "AllExtendedRights", + "properties": {}, + "style": {} + }, + { + "id": "n11", + "fromId": "n7", + "toId": "n6", + "type": "Enroll", + "properties": {}, + "style": {} + } + ] +} \ No newline at end of file diff --git a/cmd/api/src/test/integration/harnesses/esc3harness3.svg b/cmd/api/src/test/integration/harnesses/esc3harness3.svg new file mode 100644 index 0000000000..cdc713de65 --- /dev/null +++ b/cmd/api/src/test/integration/harnesses/esc3harness3.svg @@ -0,0 +1 @@ +RootCAForIssuedSignedByNTAuthStoreForTrustedForNTAuthPublishedToPublishedToEnrollOnBehalfOfEnrollMemberOfAllExtendedRightsEnrollDomainNTAuthStoreRootCAEnterpriseCA1EnrollmentAgentRestrictionsCollected:FalseCertTemplate2AuthenticationEnabled:TrueRequiresManagerApproval:FalseSubjectAltRequireUPN:TrueCertTemplate1RequiresManagerApproval:FalseAuthorizedSignatures:0SchemaVersion:2Group1User2 \ No newline at end of file diff --git a/packages/go/analysis/ad/esc3.go b/packages/go/analysis/ad/esc3.go index 5cf761ac3e..8500d6faee 100644 --- a/packages/go/analysis/ad/esc3.go +++ b/packages/go/analysis/ad/esc3.go @@ -41,9 +41,17 @@ func PostADCSESC3(ctx context.Context, tx graph.Transaction, outC chan<- analysi return nil } else if collected, err := eca2.Properties.Get(ad.EnrollmentAgentRestrictionsCollected.String()).Bool(); err != nil { return fmt.Errorf("error getting enrollmentagentcollected for eca2 %d: %w", eca2.ID, err) - } else if hasRestrictions, err := eca2.Properties.Get(ad.HasEnrollmentAgentRestrictions.String()).Bool(); err != nil { - return fmt.Errorf("error getting hasenrollmentagentrestrictions for ca %d: %w", eca2.ID, err) } else { + // Assuming no enrollement agent restrictions if not collected + eARestrictions := false + if collected { + if hasRestrictions, err := eca2.Properties.Get(ad.HasEnrollmentAgentRestrictions.String()).Bool(); err != nil { + return fmt.Errorf("error getting hasenrollmentagentrestrictions for ca %d: %w", eca2.ID, err) + } else { + eARestrictions = hasRestrictions + } + } + for _, certTemplateTwo := range publishedCertTemplates { if !isEndCertTemplateValidESC3(certTemplateTwo) { continue @@ -69,7 +77,7 @@ func PostADCSESC3(ctx context.Context, tx graph.Transaction, outC chan<- analysi log.Errorf("error getting cas for cert template %d: %v", certTemplateOne.ID, err) } else if publishedECAs.Len() == 0 { continue - } else if collected && hasRestrictions { + } else if eARestrictions { if delegatedAgents, err := fetchFirstDegreeNodes(tx, certTemplateTwo, ad.DelegatedEnrollmentAgent); err != nil { log.Errorf("error getting delegated agents for cert template %d: %v", certTemplateTwo.ID, err) } else { @@ -478,13 +486,15 @@ func GetADCSESC3EdgeComposition(ctx context.Context, db graph.Database, edge *gr if collected, err := eca2.Properties.Get(ad.EnrollmentAgentRestrictionsCollected.String()).Bool(); err != nil { log.Errorf("error getting enrollmentagentcollected for eca2 %d: %v", eca2.ID, err) - } else if hasRestrictions, err := eca2.Properties.Get(ad.HasEnrollmentAgentRestrictions.String()).Bool(); err != nil { - log.Errorf("error getting hasenrollmentagentrestrictions for ca %d: %v", eca2.ID, err) - } else if collected && hasRestrictions { - if p6, err := getDelegatedEnrollmentAgentPath(ctx, startNode, ct2, db); err != nil { - log.Infof("Error getting p6 for composition: %v", err) - } else { - paths.AddPathSet(p6) + } else if collected { + if hasRestrictions, err := eca2.Properties.Get(ad.HasEnrollmentAgentRestrictions.String()).Bool(); err != nil { + log.Errorf("error getting hasenrollmentagentrestrictions for ca %d: %v", eca2.ID, err) + } else if hasRestrictions { + if p6, err := getDelegatedEnrollmentAgentPath(ctx, startNode, ct2, db); err != nil { + log.Infof("Error getting p6 for composition: %v", err) + } else { + paths.AddPathSet(p6) + } } } }