From 3ffe55ddd0a299d53eb2e92bf54ac2218d1b0f83 Mon Sep 17 00:00:00 2001 From: Harshvardhan Karn Date: Thu, 21 Sep 2023 13:08:11 +0000 Subject: [PATCH 1/2] feat: (backend): API to list scan fields and add custom fields in Jira --- deepfence_server/apiDocs/operation.go | 5 ++ deepfence_server/handler/scan_reports.go | 56 +++++++++++++++++++ deepfence_server/model/scans.go | 7 +++ deepfence_server/pkg/integration/jira/jira.go | 29 ++++++++++ .../pkg/integration/jira/types.go | 17 +++--- deepfence_server/router/router.go | 2 + 6 files changed, 108 insertions(+), 8 deletions(-) diff --git a/deepfence_server/apiDocs/operation.go b/deepfence_server/apiDocs/operation.go index 43932f9877..51e920a0d0 100644 --- a/deepfence_server/apiDocs/operation.go +++ b/deepfence_server/apiDocs/operation.go @@ -472,6 +472,11 @@ func (d *OpenApiDocs) AddIngestersOperations() { } func (d *OpenApiDocs) AddScansOperations() { + // List scan result fields + d.AddOperation("getScanReportFields", http.MethodGet, "/deepfence/scan/results/fields", + "Get Scan Report Fields", "Get all the fields available in all the scan reports", + http.StatusOK, []string{tagVulnerability, tagSecretScan, tagCompliance, tagMalwareScan}, bearerToken, nil, new(ScanReportFieldsResponse)) + // Start scan d.AddOperation("startVulnerabilityScan", http.MethodPost, "/deepfence/scan/start/vulnerability", "Start Vulnerability Scan", "Start Vulnerability Scan on agent or registry", diff --git a/deepfence_server/handler/scan_reports.go b/deepfence_server/handler/scan_reports.go index 2c0c910a55..58490f0271 100644 --- a/deepfence_server/handler/scan_reports.go +++ b/deepfence_server/handler/scan_reports.go @@ -10,6 +10,7 @@ import ( "net/http" "net/url" "path" + "reflect" "strconv" "strings" "time" @@ -951,6 +952,61 @@ func (h *Handler) listScansHandler(w http.ResponseWriter, r *http.Request, scan_ httpext.JSON(w, http.StatusOK, infos) } +func (h *Handler) GetScanReportFields(w http.ResponseWriter, r *http.Request) { + // iterate over empty struct "model.Vulnerability" fields + // and push the field json tag name to an array + vulnerabilityFields := []string{} + vulnerability := model.Vulnerability{} + vulnerabilityType := reflect.TypeOf(vulnerability) + for i := 0; i < vulnerabilityType.NumField(); i++ { + fieldString := vulnerabilityType.Field(i).Tag.Get("json") + fields := strings.Split(fieldString, ",") + vulnerabilityFields = append(vulnerabilityFields, fields[0]) + } + + // iterate over empty struct "model.Secret" fields + // and push the field json tag name to an array + secretFields := []string{} + secret := model.Secret{} + secretType := reflect.TypeOf(secret) + for i := 0; i < secretType.NumField(); i++ { + fieldString := vulnerabilityType.Field(i).Tag.Get("json") + fields := strings.Split(fieldString, ",") + secretFields = append(secretFields, fields[0]) + } + + // iterate over empty struct "model.Compliance" fields + // and push the field json tag name to an array + complianceFields := []string{} + compliance := model.Compliance{} + complianceType := reflect.TypeOf(compliance) + for i := 0; i < complianceType.NumField(); i++ { + fieldString := vulnerabilityType.Field(i).Tag.Get("json") + fields := strings.Split(fieldString, ",") + complianceFields = append(complianceFields, fields[0]) + } + + // iterate over empty struct "model.Malware" fields + // and push the field json tag name to an array + malwareFields := []string{} + malware := model.Malware{} + malwareType := reflect.TypeOf(malware) + for i := 0; i < malwareType.NumField(); i++ { + fieldString := vulnerabilityType.Field(i).Tag.Get("json") + fields := strings.Split(fieldString, ",") + malwareFields = append(malwareFields, fields[0]) + } + + response := model.ScanReportFieldsResponse{ + Vulnerability: vulnerabilityFields, + Secret: secretFields, + Compliance: complianceFields, + Malware: malwareFields, + } + + httpext.JSON(w, http.StatusOK, response) +} + func (h *Handler) ListVulnerabilityScanResultsHandler(w http.ResponseWriter, r *http.Request) { entries, common, err := listScanResultsHandler[model.Vulnerability](w, r, utils.NEO4J_VULNERABILITY_SCAN) if err != nil { diff --git a/deepfence_server/model/scans.go b/deepfence_server/model/scans.go index e5207de2d8..f24ea54d2b 100644 --- a/deepfence_server/model/scans.go +++ b/deepfence_server/model/scans.go @@ -574,3 +574,10 @@ func (v CloudCompliance) GetCategory() string { func (CloudCompliance) GetJsonCategory() string { return "severity" } + +type ScanReportFieldsResponse struct { + Vulnerability []string `json:"vulnerability"` + Secret []string `json:"secret"` + Malware []string `json:"malware"` + Compliance []string `json:"compliance"` +} diff --git a/deepfence_server/pkg/integration/jira/jira.go b/deepfence_server/pkg/integration/jira/jira.go index a4c4dd5af5..5630e869fc 100644 --- a/deepfence_server/pkg/integration/jira/jira.go +++ b/deepfence_server/pkg/integration/jira/jira.go @@ -79,6 +79,35 @@ func (j Jira) SendNotification(ctx context.Context, message string, extras map[s } log.Info().Msgf("jira issue created id %s link %s", issue.ID, issue.Self) + // parse message in case of custom fields + var msgWithCustomFields []map[string]interface{} + if len(j.Config.CustomFields) > 0 { + var msg []map[string]interface{} + err = json.Unmarshal([]byte(message), &msg) + if err != nil { + log.Error().Msgf(err.Error()) + return err + } + + for _, m := range msg { + customFields := make(map[string]interface{}) + for _, f := range j.Config.CustomFields { + if value, ok := m[f]; ok { + customFields[f] = value + } + } + msgWithCustomFields = append(msgWithCustomFields, customFields) + } + + finalByte, err := json.MarshalIndent(msgWithCustomFields, "", " ") + if err != nil { + log.Error().Msgf(err.Error()) + return err + } + + message = string(finalByte) + } + attachment, resp, err := client.Issue.PostAttachment(issue.ID, strings.NewReader(message), "scan-results.json") if err != nil { log.Error().Msgf(err.Error()) diff --git a/deepfence_server/pkg/integration/jira/types.go b/deepfence_server/pkg/integration/jira/types.go index 663de0fa3d..8a3f8dcd69 100644 --- a/deepfence_server/pkg/integration/jira/types.go +++ b/deepfence_server/pkg/integration/jira/types.go @@ -14,14 +14,15 @@ type Jira struct { } type Config struct { - JiraSiteUrl string `json:"jiraSiteUrl" validate:"required,url" required:"true"` - Username string `json:"username" validate:"required,min=1" required:"true"` - Password string `json:"password" validate:"omitempty,min=8,max=100,jira_auth_key"` - JiraProjectKey string `json:"jiraProjectKey" validate:"required,min=1" required:"true"` - JiraAssignee string `json:"jiraAssignee" validate:"omitempty,min=1"` - IssueType string `json:"issueType" validate:"required,min=1" required:"true"` - IsAuthToken bool `json:"isAuthToken" validate:"required" required:"true"` - APIToken string `json:"api_token" validate:"omitempty,min=32,max=300,jira_auth_key"` + JiraSiteUrl string `json:"jiraSiteUrl" validate:"required,url" required:"true"` + Username string `json:"username" validate:"required,min=1" required:"true"` + Password string `json:"password" validate:"omitempty,min=8,max=100,jira_auth_key"` + JiraProjectKey string `json:"jiraProjectKey" validate:"required,min=1" required:"true"` + JiraAssignee string `json:"jiraAssignee" validate:"omitempty,min=1"` + IssueType string `json:"issueType" validate:"required,min=1" required:"true"` + IsAuthToken bool `json:"isAuthToken" validate:"required" required:"true"` + APIToken string `json:"api_token" validate:"omitempty,min=32,max=300,jira_auth_key"` + CustomFields []string `json:"custom_fields"` } func (j Jira) ValidateConfig(validate *validator.Validate) error { diff --git a/deepfence_server/router/router.go b/deepfence_server/router/router.go index 281239d906..74de11970a 100644 --- a/deepfence_server/router/router.go +++ b/deepfence_server/router/router.go @@ -367,6 +367,8 @@ func SetupRoutes(r *chi.Mux, serverPort string, serveOpenapiDocs bool, ingestC c r.Post("/cloud-compliance", dfHandler.AuthHandler(ResourceScanReport, PermissionRead, dfHandler.ListCloudComplianceScansHandler)) }) r.Route("/scan/results", func(r chi.Router) { + r.Get("/fields", dfHandler.AuthHandler(ResourceScanReport, PermissionRead, dfHandler.GetScanReportFields)) + r.Post("/vulnerability", dfHandler.AuthHandler(ResourceScanReport, PermissionRead, dfHandler.ListVulnerabilityScanResultsHandler)) r.Route("/secret", func(r chi.Router) { From 2c829b33b4a8839d449a72b91141d2dcc1ff7312 Mon Sep 17 00:00:00 2001 From: Harshvardhan Karn Date: Thu, 21 Sep 2023 13:17:08 +0000 Subject: [PATCH 2/2] put /deepfence/scan/results/fields under common in swagger --- deepfence_server/apiDocs/operation.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deepfence_server/apiDocs/operation.go b/deepfence_server/apiDocs/operation.go index 51e920a0d0..4409d54c2a 100644 --- a/deepfence_server/apiDocs/operation.go +++ b/deepfence_server/apiDocs/operation.go @@ -475,8 +475,7 @@ func (d *OpenApiDocs) AddScansOperations() { // List scan result fields d.AddOperation("getScanReportFields", http.MethodGet, "/deepfence/scan/results/fields", "Get Scan Report Fields", "Get all the fields available in all the scan reports", - http.StatusOK, []string{tagVulnerability, tagSecretScan, tagCompliance, tagMalwareScan}, bearerToken, nil, new(ScanReportFieldsResponse)) - + http.StatusOK, []string{tagCommon}, bearerToken, nil, new(ScanReportFieldsResponse)) // Start scan d.AddOperation("startVulnerabilityScan", http.MethodPost, "/deepfence/scan/start/vulnerability", "Start Vulnerability Scan", "Start Vulnerability Scan on agent or registry",