diff --git a/services/compliance/api/benchmark.go b/services/compliance/api/benchmark.go index 327f5cb37..501c4f5bc 100644 --- a/services/compliance/api/benchmark.go +++ b/services/compliance/api/benchmark.go @@ -225,6 +225,19 @@ type GetFrameworkListRequest struct { PerPage *int64 `json:"per_page"` } + +type GetFrameworkSummaryListRequest struct { + TitleRegex *string `json:"title_regex"` + IntegrationTypes []string `json:"integration_types"` + IsBaseline *bool `json:"is_baseline"` + SortBy string `json:"sort_by"` + Cursor *int64 `json:"cursor"` + PerPage *int64 `json:"per_page"` +} + + + + type GetBenchmarkListMetadata struct { ID string `json:"id"` Title string `json:"title"` @@ -241,6 +254,8 @@ type GetBenchmarkListMetadata struct { UpdatedAt time.Time `json:"updated_at"` } + + type GetBenchmarkListItem struct { Benchmark GetBenchmarkListMetadata `json:"benchmark"` IncidentCount int `json:"incident_count"` @@ -251,6 +266,32 @@ type GetBenchmarkListResponse struct { TotalCount int `json:"total_count"` } +type GetBenchmarkListSummaryMetadata struct { + ID string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + IntegrationType []string `json:"connectors"` + Enabled bool `json:"enabled"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + ComplianceScore float64 `json:"compliance_score"` + SeveritySummaryByControl BenchmarkControlsSeverityStatusV2 `json:"severity_summary_by_control"` + SeveritySummaryByResource BenchmarkResourcesSeverityStatusV2 `json:"severity_summary_by_resource"` + SeveritySummaryByIncidents types.SeverityResultV2 `json:"severity_summary_by_incidents"` + CostImpact *float64 `json:"cost_impact"` + ComplianceResultsSummary ComplianceStatusSummaryV2 `json:"compliance_results_summary"` + IssuesCount int `json:"issues_count"` + LastEvaluatedAt *time.Time `json:"last_evaluated_at"` + LastJobStatus string `json:"last_job_status"` +} + + + +type GetBenchmarkSummaryListResponse struct { + Items []GetBenchmarkListSummaryMetadata `json:"items"` + TotalCount int `json:"total_count"` +} + type GetBenchmarkAssignmentsResponse struct { Items []GetBenchmarkAssignmentsItem `json:"items"` Status BenchmarkAssignmentStatus `json:"status"` diff --git a/services/compliance/http_routes.go b/services/compliance/http_routes.go index d389010c8..7717d1d12 100644 --- a/services/compliance/http_routes.go +++ b/services/compliance/http_routes.go @@ -98,6 +98,7 @@ func (h *HttpHandler) Register(e *echo.Echo) { v3.GET("/policies/:policy_id", httpserver2.AuthorizeHandler(h.GetPolicy, authApi.ViewerRole)) v3.POST("/benchmarks", httpserver2.AuthorizeHandler(h.ListBenchmarksFiltered, authApi.ViewerRole)) + v3.POST("/benchmarks/summary", httpserver2.AuthorizeHandler(h.GetBenchmarksSummary, authApi.ViewerRole)) v3.GET("/benchmarks/filters", httpserver2.AuthorizeHandler(h.ListBenchmarksFilters, authApi.ViewerRole)) v3.POST("/benchmark/:benchmark_id", httpserver2.AuthorizeHandler(h.GetBenchmarkDetails, authApi.ViewerRole)) v3.GET("/benchmark/:benchmark_id/assignments", httpserver2.AuthorizeHandler(h.GetBenchmarkAssignments, authApi.ViewerRole)) @@ -3109,6 +3110,247 @@ func (h *HttpHandler) ListBenchmarksFiltered(echoCtx echo.Context) error { return echoCtx.JSON(http.StatusOK, response) } + + + + +// ListBenchmarksFiltered godoc +// +// @Summary List benchmarks filtered by integrations and other filters +// @Security BearerToken +// @Tags compliance +// @Accept json +// @Produce json +// @Param request body api.GetFrameworkListRequest true "Request Body" +// @Success 200 {object} []api.GetBenchmarkListResponse +// @Router /compliance/api/v3/benchmarks/summary [post] +func (h *HttpHandler) GetBenchmarksSummary(echoCtx echo.Context) error { + ctx := echoCtx.Request().Context() + clientCtx := &httpclient.Context{UserRole: authApi.AdminRole} + + var req api.GetFrameworkSummaryListRequest + if err := bindValidate(echoCtx, &req); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + + isRoot := true + assigned :=true + + + benchmarkAssignmentsCount, err := h.db.GetBenchmarkAssignmentsCount() + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + benchmarkAssignmentsCountMap := make(map[string]int) + for _, ba := range benchmarkAssignmentsCount { + benchmarkAssignmentsCountMap[ba.BenchmarkId] = ba.Count + } + integrationsCountByType := make(map[string]int) + integrationsResp, err := h.integrationClient.ListIntegrations(clientCtx, nil) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + for _, s := range integrationsResp.Integrations { + if _, ok := integrationsCountByType[s.IntegrationType.String()]; ok { + integrationsCountByType[s.IntegrationType.String()]++ + } else { + integrationsCountByType[s.IntegrationType.String()] = 1 + } + } + + + + + benchmarks, err := h.db.ListBenchmarksFiltered(ctx, req.TitleRegex, isRoot, nil, nil, &assigned, req.IsBaseline, nil) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + + var items []api.GetBenchmarkListSummaryMetadata + + for _, b := range benchmarks { + + + metadata := db.BenchmarkMetadata{} + + if len(b.Metadata.Bytes) > 0 { + err := json.Unmarshal(b.Metadata.Bytes, &metadata) + if err != nil { + h.logger.Error("failed to unmarshal metadata", zap.Error(err)) + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + } + + benchmarkDetails := api.GetBenchmarkListSummaryMetadata{ + ID: b.ID, + Title: b.Title, + Description: b.Description, + Enabled: b.Enabled, + CreatedAt: b.CreatedAt, + UpdatedAt: b.UpdatedAt, + } + + if b.IntegrationType != nil { + benchmarkDetails.IntegrationType = b.IntegrationType + } + + items = append(items, benchmarkDetails) + } + + totalCount := len(items) + + switch strings.ToLower(req.SortBy) { + + case "title": + sort.Slice(items, func(i, j int) bool { + return items[i].Title < items[j].Title + }) + } + + if req.PerPage != nil { + if req.Cursor == nil { + items = utils.Paginate(1, *req.PerPage, items) + } else { + items = utils.Paginate(*req.Cursor, *req.PerPage, items) + } + } + // finding summary of paginated benchmarks + var new_items []api.GetBenchmarkListSummaryMetadata + + for _, benchmark := range items { + + controls, err := h.db.ListControlsByBenchmarkID(ctx, benchmark.ID) + if err != nil { + h.logger.Error("failed to get controls", zap.Error(err)) + return err + } + controlsMap := make(map[string]*db.Control) + for _, control := range controls { + control := control + controlsMap[strings.ToLower(control.ID)] = &control + } + timeAt := time.Now() + + summariesAtTime, err := es.ListBenchmarkSummariesAtTime(ctx, h.logger, h.client, + []string{benchmark.ID}, nil, nil, + timeAt, true) + if err != nil { + return err + } + + passedResourcesResult, err := es.GetPerBenchmarkResourceSeverityResult(ctx, h.logger, h.client, []string{benchmark.ID}, nil, nil, nil, opengovernanceTypes.GetPassedComplianceStatuses()) + if err != nil { + h.logger.Error("failed to fetch per benchmark resource severity result for passed", zap.Error(err)) + return err + } + + allResourcesResult, err := es.GetPerBenchmarkResourceSeverityResult(ctx, h.logger, h.client, []string{benchmark.ID}, nil, nil, nil, nil) + if err != nil { + h.logger.Error("failed to fetch per benchmark resource severity result for all", zap.Error(err)) + return err + } + + summaryAtTime := summariesAtTime[benchmark.ID] + + csResult := api.ComplianceStatusSummaryV2{} + sResult := opengovernanceTypes.SeverityResultV2{} + controlSeverityResult := api.BenchmarkControlsSeverityStatusV2{} + var costImpact *float64 + addToResults := func(resultGroup types.ResultGroup) { + csResult.AddESComplianceStatusMap(resultGroup.Result.QueryResult) + sResult.AddResultMap(resultGroup.Result.SeverityResult) + costImpact = utils.PAdd(costImpact, resultGroup.Result.CostImpact) + for controlId, controlResult := range resultGroup.Controls { + control := controlsMap[strings.ToLower(controlId)] + controlSeverityResult = addToControlSeverityResultV2(controlSeverityResult, control, controlResult) + } + } + + addToResults(summaryAtTime.Integrations.BenchmarkResult) + + lastJob, err := h.schedulerClient.GetLatestComplianceJobForBenchmark(&httpclient.Context{UserRole: authApi.AdminRole}, benchmark.ID) + if err != nil { + h.logger.Error("failed to get latest compliance job for benchmark", zap.Error(err), zap.String("benchmarkID", benchmark.ID)) + return err + } + + var lastJobStatus string + if lastJob != nil { + lastJobStatus = string(lastJob.Status) + } + + resourcesSeverityResult := api.BenchmarkResourcesSeverityStatusV2{} + allResources := allResourcesResult[benchmark.ID] + resourcesSeverityResult.Total.TotalCount = allResources.TotalCount + resourcesSeverityResult.Critical.TotalCount = allResources.CriticalCount + resourcesSeverityResult.High.TotalCount = allResources.HighCount + resourcesSeverityResult.Medium.TotalCount = allResources.MediumCount + resourcesSeverityResult.Low.TotalCount = allResources.LowCount + resourcesSeverityResult.None.TotalCount = allResources.NoneCount + passedResource := passedResourcesResult[benchmark.ID] + resourcesSeverityResult.Total.PassedCount = passedResource.TotalCount + resourcesSeverityResult.Critical.PassedCount = passedResource.CriticalCount + resourcesSeverityResult.High.PassedCount = passedResource.HighCount + resourcesSeverityResult.Medium.PassedCount = passedResource.MediumCount + resourcesSeverityResult.Low.PassedCount = passedResource.LowCount + resourcesSeverityResult.None.PassedCount = passedResource.NoneCount + + resourcesSeverityResult.Total.FailedCount = allResources.TotalCount - passedResource.TotalCount + resourcesSeverityResult.Critical.FailedCount = allResources.CriticalCount - passedResource.CriticalCount + resourcesSeverityResult.High.FailedCount = allResources.HighCount - passedResource.HighCount + resourcesSeverityResult.Medium.FailedCount = allResources.MediumCount - passedResource.MediumCount + resourcesSeverityResult.Low.FailedCount = allResources.LowCount - passedResource.LowCount + resourcesSeverityResult.None.FailedCount = allResources.NoneCount - passedResource.NoneCount + + + + var complianceScore float64 + if controlSeverityResult.Total.TotalCount > 0 { + complianceScore = float64(controlSeverityResult.Total.PassedCount) / float64(controlSeverityResult.Total.TotalCount) + } else { + complianceScore = 0 + } + + + new_items = append(new_items, api.GetBenchmarkListSummaryMetadata{ + ComplianceScore: complianceScore, + SeveritySummaryByControl: controlSeverityResult, + SeveritySummaryByResource: resourcesSeverityResult, + SeveritySummaryByIncidents: sResult, + CostImpact: costImpact, + ComplianceResultsSummary: csResult, + IssuesCount: csResult.FailedCount, + LastEvaluatedAt: utils.GetPointer(time.Unix(summaryAtTime.EvaluatedAtEpoch, 0)), + LastJobStatus: lastJobStatus, + ID: benchmark.ID, + Title: benchmark.Title, + Description: benchmark.Description, + Enabled: benchmark.Enabled, + CreatedAt: benchmark.CreatedAt, + UpdatedAt: benchmark.UpdatedAt, + IntegrationType: benchmark.IntegrationType, + }) + + + } + + response := api.GetBenchmarkSummaryListResponse{ + Items: new_items, + TotalCount: totalCount, + } + + return echoCtx.JSON(http.StatusOK, response) +} + + + + + + + + + + // GetBenchmarkDetails godoc // // @Summary Get Benchmark Details by BenchmarkID @@ -4136,6 +4378,8 @@ func (h *HttpHandler) ComplianceSummaryOfBenchmark(echoCtx echo.Context) error { return echoCtx.JSON(http.StatusOK, response) } + + // ListControlsFilters godoc // // @Summary List possible values for each filter in List Controls