From c4bd3dcff65e5fe281b6d14cda341d4db49bc995 Mon Sep 17 00:00:00 2001 From: Roland Erk Date: Fri, 24 Jan 2025 15:26:42 +0100 Subject: [PATCH 1/3] For the webhook notification from dtrack, apply the finding only to the project that contains the component mentioned in the notification. Signed-off-by: Roland Erk --- internal/api/handlers.go | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 0b4746f..0f7d33c 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -29,9 +29,33 @@ func handleDTNotification(dtClient *dtrack.Client, auditChan chan<- any, auditor switch subject := n.Subject.(type) { case *notification.NewVulnerabilitySubject: for i := range subject.AffectedProjects { + + project := resolveProject(subject.AffectedProjects[i], dtClient, logger) + component := resolveComponent(subject.Component, dtClient, logger) + + components, err := dtrack.FetchAll(func(po dtrack.PageOptions) (dtrack.Page[dtrack.Component], error) { + return dtClient.Component.GetAll(context.Background(), project.UUID, po) + }) + if err != nil { + logger.Error().Err(err).Str("project", project.UUID.String()).Msg("failed to query components") + continue + } + + found := false + for _ , comp := range components { + if comp.UUID == component.UUID { + found = true + break + } + } + + if !found { + continue + } + finding := audit.Finding{ - Component: resolveComponent(subject.Component, dtClient, logger), - Project: resolveProject(subject.AffectedProjects[i], dtClient, logger), + Component: component, + Project: project, Vulnerability: resolveVulnerability(subject.Vulnerability, dtClient, logger), } From 0a8e67c944d4ea2b4429258152ba81af9ebfa94a Mon Sep 17 00:00:00 2001 From: Roland Erk Date: Mon, 27 Jan 2025 12:48:09 +0100 Subject: [PATCH 2/3] Since the requests were not completed in time and further webhooks then failed, set the processing into its on goroutine and lock it so the concurrent accesses don't DoS dtrack Signed-off-by: Roland Erk --- internal/api/handlers.go | 96 ++++++++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 39 deletions(-) diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 0f7d33c..8116b94 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -6,8 +6,9 @@ import ( "fmt" "net/http" "strings" + "sync" - "github.com/DependencyTrack/client-go" + dtrack "github.com/DependencyTrack/client-go" "github.com/DependencyTrack/client-go/notification" "github.com/rs/zerolog" @@ -28,44 +29,9 @@ func handleDTNotification(dtClient *dtrack.Client, auditChan chan<- any, auditor switch subject := n.Subject.(type) { case *notification.NewVulnerabilitySubject: - for i := range subject.AffectedProjects { - - project := resolveProject(subject.AffectedProjects[i], dtClient, logger) - component := resolveComponent(subject.Component, dtClient, logger) - - components, err := dtrack.FetchAll(func(po dtrack.PageOptions) (dtrack.Page[dtrack.Component], error) { - return dtClient.Component.GetAll(context.Background(), project.UUID, po) - }) - if err != nil { - logger.Error().Err(err).Str("project", project.UUID.String()).Msg("failed to query components") - continue - } - - found := false - for _ , comp := range components { - if comp.UUID == component.UUID { - found = true - break - } - } - - if !found { - continue - } - - finding := audit.Finding{ - Component: component, - Project: project, - Vulnerability: resolveVulnerability(subject.Vulnerability, dtClient, logger), - } - - analysisReq, auditErr := auditor.AuditFinding(context.Background(), finding) - if auditErr == nil && analysisReq != (dtrack.AnalysisRequest{}) { - auditChan <- analysisReq - } else if auditErr != nil { - logger.Error().Err(auditErr).Object("finding", finding).Msg("failed to audit finding") - } - } + logger.Info().Str("content", n.Content).Msg("Received notification") + go handleVulnerability(*subject, dtClient, auditChan, auditor, logger) + case *notification.PolicyViolationSubject: violation := audit.Violation{ Component: resolveComponent(subject.Component, dtClient, logger), @@ -92,6 +58,58 @@ func handleDTNotification(dtClient *dtrack.Client, auditChan chan<- any, auditor } } +var lock sync.Mutex + +func handleVulnerability(subject notification.NewVulnerabilitySubject, dtClient *dtrack.Client, auditChan chan<- any, auditor audit.Auditor, logger zerolog.Logger) { + lock.Lock() + + component := resolveComponent(subject.Component, dtClient, logger) + + logger.Info().Str("component", component.UUID.String()).Msg("Handling notification started") + for i := range subject.AffectedProjects { + + project := resolveProject(subject.AffectedProjects[i], dtClient, logger) + + components, err := dtrack.FetchAll(func(po dtrack.PageOptions) (dtrack.Page[dtrack.Component], error) { + return dtClient.Component.GetAll(context.Background(), project.UUID, po) + }) + if err != nil { + logger.Error().Err(err).Str("project", project.UUID.String()).Msg("failed to query components") + continue + } + + found := false + for _ , comp := range components { + if comp.UUID == component.UUID { + found = true + logger.Info().Str("project", project.UUID.String()).Msg("Found component in project") + break + } + } + + if !found { + continue + } + + finding := audit.Finding{ + Component: component, + Project: project, + Vulnerability: resolveVulnerability(subject.Vulnerability, dtClient, logger), + } + + analysisReq, auditErr := auditor.AuditFinding(context.Background(), finding) + if auditErr == nil && analysisReq != (dtrack.AnalysisRequest{}) { + auditChan <- analysisReq + } else if auditErr != nil { + logger.Error().Err(auditErr).Object("finding", finding).Msg("failed to audit finding") + } + } + logger.Info().Str("component", component.UUID.String()).Msg("Handling notification done") + + lock.Unlock() +} + + func handleOPAStatus(statusChan chan<- opa.Status) http.HandlerFunc { return func(rw http.ResponseWriter, r *http.Request) { logger := getRequestLogger(r) From a6513cc7ee0bef5566b6115fc0d79796f499cd8f Mon Sep 17 00:00:00 2001 From: Roland Erk Date: Tue, 28 Jan 2025 13:09:05 +0100 Subject: [PATCH 3/3] Simplify finding the correct project for the component from webhook call Signed-off-by: Roland Erk --- internal/api/handlers.go | 57 ++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 8116b94..fc77836 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -66,43 +66,20 @@ func handleVulnerability(subject notification.NewVulnerabilitySubject, dtClient component := resolveComponent(subject.Component, dtClient, logger) logger.Info().Str("component", component.UUID.String()).Msg("Handling notification started") - for i := range subject.AffectedProjects { - project := resolveProject(subject.AffectedProjects[i], dtClient, logger) + project := resolveProjectFromComponent(*component.Project, dtClient, logger) - components, err := dtrack.FetchAll(func(po dtrack.PageOptions) (dtrack.Page[dtrack.Component], error) { - return dtClient.Component.GetAll(context.Background(), project.UUID, po) - }) - if err != nil { - logger.Error().Err(err).Str("project", project.UUID.String()).Msg("failed to query components") - continue - } - - found := false - for _ , comp := range components { - if comp.UUID == component.UUID { - found = true - logger.Info().Str("project", project.UUID.String()).Msg("Found component in project") - break - } - } - - if !found { - continue - } - - finding := audit.Finding{ - Component: component, - Project: project, - Vulnerability: resolveVulnerability(subject.Vulnerability, dtClient, logger), - } + finding := audit.Finding{ + Component: component, + Project: project, + Vulnerability: resolveVulnerability(subject.Vulnerability, dtClient, logger), + } - analysisReq, auditErr := auditor.AuditFinding(context.Background(), finding) - if auditErr == nil && analysisReq != (dtrack.AnalysisRequest{}) { - auditChan <- analysisReq - } else if auditErr != nil { - logger.Error().Err(auditErr).Object("finding", finding).Msg("failed to audit finding") - } + analysisReq, auditErr := auditor.AuditFinding(context.Background(), finding) + if auditErr == nil && analysisReq != (dtrack.AnalysisRequest{}) { + auditChan <- analysisReq + } else if auditErr != nil { + logger.Error().Err(auditErr).Object("finding", finding).Msg("failed to audit finding") } logger.Info().Str("component", component.UUID.String()).Msg("Handling notification done") @@ -144,6 +121,18 @@ func resolveComponent(input notification.Component, dtClient *dtrack.Client, log return } +func resolveProjectFromComponent(input dtrack.Project, dtClient *dtrack.Client, logger zerolog.Logger) (project dtrack.Project) { + project, err := dtClient.Project.Get(context.Background(), input.UUID) + if err != nil { + logger.Error().Err(err). + Str("project", input.UUID.String()). + Msg("failed to fetch project, proceeding with project from component instead") + project = input + } + + return +} + func resolveProject(input notification.Project, dtClient *dtrack.Client, logger zerolog.Logger) (project dtrack.Project) { project, err := dtClient.Project.Get(context.Background(), input.UUID) if err != nil {