diff --git a/deepfence_server/apiDocs/operation.go b/deepfence_server/apiDocs/operation.go index 600bbb6aa7..24c8adfc69 100644 --- a/deepfence_server/apiDocs/operation.go +++ b/deepfence_server/apiDocs/operation.go @@ -176,6 +176,10 @@ func (d *OpenAPIDocs) AddLookupOperations() { d.AddOperation("getCloudCompliances", http.MethodPost, "/deepfence/lookup/cloud-compliances", "Retrieve Cloud Compliances data", "Retrieve all the data associated with cloud-compliances", http.StatusOK, []string{tagLookup}, bearerToken, new(LookupFilter), new([]CloudCompliance)) + + d.AddOperation("getComplianceControls", http.MethodPost, "/deepfence/lookup/compliance-controls", + "Retrieve Cloud Compliances Control data", "Retrieve all the data associated with cloud compliance controls", + http.StatusOK, []string{tagLookup}, bearerToken, new(LookupFilter), new([]CloudComplianceControl)) } func (d *OpenAPIDocs) AddSearchOperations() { @@ -612,10 +616,10 @@ func (d *OpenAPIDocs) AddScansOperations() { // compliance and cloud-compliance results count grouped by control_id d.AddOperation("groupResultsCompliance", http.MethodPost, "/deepfence/scan/results/count/group/compliance", "Count Compliance Results by Control ID", "Count Compliance Results grouped by Control ID", - http.StatusOK, []string{tagCompliance}, bearerToken, new(ComplinaceScanResultsGroupReq), new(ComplinaceScanResultsGroupResp)) + http.StatusOK, []string{tagCompliance}, bearerToken, new(ComplinaceScanResultsGroupReq), new(ComplianceScanResultsGroupResp)) d.AddOperation("groupResultsCloudCompliance", http.MethodPost, "/deepfence/scan/results/count/group/cloud-compliance", "Count Cloud Compliance Results by Control ID", "Count Cloud Compliance Results grouped by Control ID", - http.StatusOK, []string{tagCompliance}, bearerToken, new(ComplinaceScanResultsGroupReq), new(ComplinaceScanResultsGroupResp)) + http.StatusOK, []string{tagCompliance}, bearerToken, new(ComplinaceScanResultsGroupReq), new(ComplianceScanResultsGroupResp)) d.AddOperation("getAllNodesInScanResults", http.MethodPost, "/deepfence/scan/nodes-in-result", "Get all nodes in given scan result ids", "Get all nodes in given scan result ids", diff --git a/deepfence_server/handler/lookup_reports.go b/deepfence_server/handler/lookup_reports.go index cbbc4af34f..6e9fc60bd4 100644 --- a/deepfence_server/handler/lookup_reports.go +++ b/deepfence_server/handler/lookup_reports.go @@ -125,3 +125,7 @@ func (h *Handler) GetCompliances(w http.ResponseWriter, r *http.Request) { func (h *Handler) GetCloudCompliances(w http.ResponseWriter, r *http.Request) { getGeneric[model.CloudCompliance](h, w, r, reporters_lookup.GetCloudComplianceReport) } + +func (h *Handler) GetCloudComplianceControl(w http.ResponseWriter, r *http.Request) { + getGeneric[model.CloudComplianceControl](h, w, r, reporters_lookup.GetCloudComplianceControl) +} diff --git a/deepfence_server/handler/scan_reports.go b/deepfence_server/handler/scan_reports.go index d1480be6f8..d294d27445 100644 --- a/deepfence_server/handler/scan_reports.go +++ b/deepfence_server/handler/scan_reports.go @@ -1266,10 +1266,10 @@ func (h *Handler) CountComplianceScanResultsGroupHandler(w http.ResponseWriter, h.respondError(err, w) } - results := map[string]model.ComplinaceScanResultControlGroup{} + results := map[string]model.ComplianceScanResultControlGroup{} for _, rec := range recs { - r := model.ComplinaceScanResultControlGroup{ + r := model.ComplianceScanResultControlGroup{ Counts: groupArrayToMap(rec.Values[1].([]interface{})), BenchmarkTypes: cast.ToStringSlice(rec.Values[2].(string)), Title: rec.Values[3].(string), @@ -1278,7 +1278,7 @@ func (h *Handler) CountComplianceScanResultsGroupHandler(w http.ResponseWriter, } err = httpext.JSON(w, http.StatusOK, - model.ComplinaceScanResultsGroupResp{Groups: results}) + model.ComplianceScanResultsGroupResp{Groups: results}) if err != nil { log.Error().Msgf("%v", err) } @@ -1319,7 +1319,7 @@ func (h *Handler) CountCloudComplianceScanResultsGroupHandler(w http.ResponseWri session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead}) defer session.Close(ctx) - tx, err := session.BeginTransaction(ctx, neo4j.WithTxTimeout(120*time.Second)) + tx, err := session.BeginTransaction(ctx, neo4j.WithTxTimeout(30*time.Second)) if err != nil { log.Error().Msgf("%v", err) h.respondError(err, w) @@ -1352,10 +1352,10 @@ func (h *Handler) CountCloudComplianceScanResultsGroupHandler(w http.ResponseWri h.respondError(err, w) } - results := map[string]model.ComplinaceScanResultControlGroup{} + results := map[string]model.ComplianceScanResultControlGroup{} for _, rec := range recs { - r := model.ComplinaceScanResultControlGroup{ + r := model.ComplianceScanResultControlGroup{ Counts: groupArrayToMap(rec.Values[1].([]interface{})), BenchmarkTypes: cast.ToStringSlice(rec.Values[2].([]interface{})), Title: rec.Values[3].(string), @@ -1364,7 +1364,7 @@ func (h *Handler) CountCloudComplianceScanResultsGroupHandler(w http.ResponseWri } err = httpext.JSON(w, http.StatusOK, - model.ComplinaceScanResultsGroupResp{Groups: results}) + model.ComplianceScanResultsGroupResp{Groups: results}) if err != nil { log.Error().Msgf("%v", err) } diff --git a/deepfence_server/model/scans.go b/deepfence_server/model/scans.go index 97a9680ac3..816cc31523 100644 --- a/deepfence_server/model/scans.go +++ b/deepfence_server/model/scans.go @@ -603,6 +603,40 @@ func (CloudCompliance) GetJSONCategory() string { return "severity" } +type CloudComplianceControl struct { + ControlID string `json:"control_id"` + Documentation string `json:"documentation"` + Active bool `json:"active"` + Description string `json:"description"` + CloudProvider string `json:"cloud_provider"` + Title string `json:"title"` + Executable bool `json:"executable"` + CategoryHierarchyShort string `json:"category_hierarchy_short"` + CategoryHierarchy []string `json:"category_hierarchy"` + Service string `json:"service"` + ParentControlHierarchy []string `json:"parent_control_hierarchy"` + ComplianceType string `json:"compliance_type"` + Disabled bool `json:"disabled"` + Category string `json:"category"` + NodeID string `json:"node_id"` +} + +func (CloudComplianceControl) NodeType() string { + return "CloudComplianceControl" +} + +func (CloudComplianceControl) ExtendedField() string { + return "" +} + +func (v CloudComplianceControl) GetCategory() string { + return v.ComplianceType +} + +func (CloudComplianceControl) GetJSONCategory() string { + return "compliance_type" +} + type ScanReportFieldsResponse struct { Vulnerability []string `json:"vulnerability"` Secret []string `json:"secret"` @@ -615,11 +649,11 @@ type ComplinaceScanResultsGroupReq struct { FieldsFilter reporters.FieldsFilters `json:"fields_filter" required:"true"` } -type ComplinaceScanResultsGroupResp struct { - Groups map[string]ComplinaceScanResultControlGroup `json:"groups"` +type ComplianceScanResultsGroupResp struct { + Groups map[string]ComplianceScanResultControlGroup `json:"groups"` } -type ComplinaceScanResultControlGroup struct { +type ComplianceScanResultControlGroup struct { Title string `json:"title,omitempty"` Counts map[string]int64 `json:"counts,omitempty"` BenchmarkTypes []string `json:"benchmark_types,omitempty"` diff --git a/deepfence_server/reporters/lookup/lookup.go b/deepfence_server/reporters/lookup/lookup.go index 0b866be713..c2cacb90de 100644 --- a/deepfence_server/reporters/lookup/lookup.go +++ b/deepfence_server/reporters/lookup/lookup.go @@ -410,6 +410,86 @@ func getGenericDirectNodeReport[T reporters.Cypherable](ctx context.Context, fil return res, nil } +// whereIDLabel is not used if nodeID's in filter are not present +func getGenericNodeReport[T reporters.Cypherable](ctx context.Context, whereIDLabel string, filter LookupFilter) ([]T, error) { + + ctx, span := telemetry.NewSpan(ctx, "lookup", "get-generic-node-report") + defer span.End() + + res := []T{} + var dummy T + + driver, err := directory.Neo4jClient(ctx) + if err != nil { + return res, err + } + + session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead}) + defer session.Close(ctx) + + tx, err := session.BeginTransaction(ctx, neo4j.WithTxTimeout(30*time.Second)) + if err != nil { + return res, err + } + defer tx.Close(ctx) + + var r neo4j.ResultWithContext + var query string + if len(filter.NodeIds) == 0 { + query = ` + MATCH (n:` + dummy.NodeType() + `) + RETURN ` + reporters.FieldFilterCypher("n", filter.InFieldFilter) + + filter.Window.FetchWindow2CypherQuery() + } else { + query = ` + MATCH (n:` + dummy.NodeType() + `) + WHERE n.` + whereIDLabel + ` IN $ids + RETURN ` + reporters.FieldFilterCypher("n", filter.InFieldFilter) + + filter.Window.FetchWindow2CypherQuery() + } + + log.Debug().Msgf("query: %s", query) + + r, err = tx.Run(ctx, query, map[string]interface{}{"ids": filter.NodeIds}) + if err != nil { + return res, err + } + + recs, err := r.Collect(ctx) + + if err != nil { + return res, err + } + + for _, rec := range recs { + var nodeMap map[string]interface{} + if len(filter.InFieldFilter) == 0 { + data, has := rec.Get("n") + if !has { + log.Warn().Msgf("Missing neo4j entry") + continue + } + da, ok := data.(dbtype.Node) + if !ok { + log.Warn().Msgf("Missing neo4j entry") + continue + } + nodeMap = da.Props + } else { + nodeMap = map[string]interface{}{} + for i := range filter.InFieldFilter { + nodeMap[filter.InFieldFilter[i]] = rec.Values[i] + } + } + + var node T + utils.FromMap(nodeMap, &node) + res = append(res, node) + } + + return res, nil +} + func getNodeConnections[T reporters.Cypherable](ctx context.Context, ids []string) ([]model.ConnectionQueryResp, []model.ConnectionQueryResp, error) { inbound := []model.ConnectionQueryResp{} outbound := []model.ConnectionQueryResp{} @@ -626,3 +706,7 @@ func GetComplianceReport(ctx context.Context, filter LookupFilter) ([]model.Comp func GetCloudComplianceReport(ctx context.Context, filter LookupFilter) ([]model.CloudCompliance, error) { return getGenericDirectNodeReport[model.CloudCompliance](ctx, filter) } + +func GetCloudComplianceControl(ctx context.Context, filter LookupFilter) ([]model.CloudComplianceControl, error) { + return getGenericNodeReport[model.CloudComplianceControl](ctx, "control_id", filter) +} diff --git a/deepfence_server/router/router.go b/deepfence_server/router/router.go index 2196cec20f..e0fe2098cf 100644 --- a/deepfence_server/router/router.go +++ b/deepfence_server/router/router.go @@ -258,6 +258,7 @@ func SetupRoutes(r *chi.Mux, serverPort string, serveOpenapiDocs bool, ingestC c r.Post("/malwares", dfHandler.GetMalwares) r.Post("/compliances", dfHandler.GetCompliances) r.Post("/cloud-compliances", dfHandler.GetCloudCompliances) + r.Post("/compliance-controls", dfHandler.GetCloudComplianceControl) }) r.Route("/complete", func(r chi.Router) {