package printer import ( v5 "github.com/anchore/grype/grype/db/v5" "github.com/anchore/grype/grype/match" "github.com/kubescape/k8s-interface/workloadinterface" "github.com/kubescape/kubescape/v3/core/cautils" "github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter" "github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils" "github.com/kubescape/opa-utils/reporthandling" "github.com/kubescape/opa-utils/reporthandling/apis" "github.com/kubescape/opa-utils/reporthandling/results/v1/prioritization" "github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary" "github.com/kubescape/opa-utils/reporthandling/results/v1/resourcesresults" reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2" ) const indicator = "†" // ControlSummaryWithSeverity wraps ControlSummary to add severity field for JSON output type ControlSummaryWithSeverity struct { reportsummary.ControlSummary Severity string `json:"severity"` } // ResourceAssociatedControlWithSeverity wraps ResourceAssociatedControl to add severity field type ResourceAssociatedControlWithSeverity struct { resourcesresults.ResourceAssociatedControl Severity string `json:"severity"` } // ResultWithSeverity wraps Result to include severity in associated controls type ResultWithSeverity struct { ResourceID string `json:"resourceID"` AssociatedControls []ResourceAssociatedControlWithSeverity `json:"controls,omitempty"` PrioritizedResource *prioritization.PrioritizedResource `json:"prioritizedResource,omitempty"` } // SummaryDetailsWithSeverity wraps SummaryDetails to include enriched controls type SummaryDetailsWithSeverity struct { Controls map[string]ControlSummaryWithSeverity `json:"controls,omitempty"` Status apis.ScanningStatus `json:"status"` Frameworks []reportsummary.FrameworkSummary `json:"frameworks"` ResourcesSeverityCounters reportsummary.SeverityCounters `json:"resourcesSeverityCounters,omitempty"` ControlsSeverityCounters reportsummary.SeverityCounters `json:"controlsSeverityCounters,omitempty"` StatusCounters reportsummary.StatusCounters `json:"ResourceCounters"` Vulnerabilities reportsummary.VulnerabilitySummary `json:"vulnerabilities,omitempty"` Score float32 `json:"score"` ComplianceScore float32 `json:"complianceScore"` } // PostureReportWithSeverity wraps PostureReport to include severity in controls type PostureReportWithSeverity struct { ReportGenerationTime string `json:"generationTime"` ClusterAPIServerInfo interface{} `json:"clusterAPIServerInfo"` ClusterCloudProvider string `json:"clusterCloudProvider"` CustomerGUID string `json:"customerGUID"` ClusterName string `json:"clusterName"` SummaryDetails SummaryDetailsWithSeverity `json:"summaryDetails,omitempty"` Resources []reporthandling.Resource `json:"resources,omitempty"` Attributes []reportsummary.PostureAttributes `json:"attributes"` Results []ResultWithSeverity `json:"results,omitempty"` Metadata reporthandlingv2.Metadata `json:"metadata,omitempty"` ResourceLabels map[string]map[string]string `json:"resourceLabels,omitempty"` // map[resourceID]map[labelKey]labelValue - extracted labels from workloads } // enrichControlsWithSeverity adds severity field to controls based on scoreFactor func enrichControlsWithSeverity(controls reportsummary.ControlSummaries) map[string]ControlSummaryWithSeverity { enrichedControls := make(map[string]ControlSummaryWithSeverity) for controlID, control := range controls { enrichedControl := ControlSummaryWithSeverity{ ControlSummary: control, Severity: apis.ControlSeverityToString(control.GetScoreFactor()), } enrichedControls[controlID] = enrichedControl } return enrichedControls } // enrichResultsWithSeverity adds severity field to controls in results func enrichResultsWithSeverity(results []resourcesresults.Result, controlSummaries reportsummary.ControlSummaries) []ResultWithSeverity { enrichedResults := make([]ResultWithSeverity, len(results)) for i, result := range results { enrichedControls := make([]ResourceAssociatedControlWithSeverity, len(result.AssociatedControls)) for j, control := range result.AssociatedControls { // Get the severity from the control summary severity := "Unknown" if controlSummary, exists := controlSummaries[control.GetID()]; exists { severity = apis.ControlSeverityToString(controlSummary.GetScoreFactor()) } enrichedControls[j] = ResourceAssociatedControlWithSeverity{ ResourceAssociatedControl: control, Severity: severity, } } enrichedResults[i] = ResultWithSeverity{ ResourceID: result.ResourceID, AssociatedControls: enrichedControls, PrioritizedResource: result.PrioritizedResource, } } return enrichedResults } // ConvertToPostureReportWithSeverity converts PostureReport to PostureReportWithSeverity func ConvertToPostureReportWithSeverity(report *reporthandlingv2.PostureReport) *PostureReportWithSeverity { return ConvertToPostureReportWithSeverityAndLabels(report, nil, nil) } // ConvertToPostureReportWithSeverityAndLabels converts PostureReport to PostureReportWithSeverity // and extracts specified labels from workloads func ConvertToPostureReportWithSeverityAndLabels(report *reporthandlingv2.PostureReport, labelsToCopy []string, allResources map[string]workloadinterface.IMetadata) *PostureReportWithSeverity { if report == nil { return nil } enrichedControls := enrichControlsWithSeverity(report.SummaryDetails.Controls) enrichedResults := enrichResultsWithSeverity(report.Results, report.SummaryDetails.Controls) // Extract labels from resources if labelsToCopy is specified var resourceLabels map[string]map[string]string if len(labelsToCopy) > 0 && allResources != nil { resourceLabels = extractResourceLabels(allResources, labelsToCopy) } return &PostureReportWithSeverity{ ReportGenerationTime: report.ReportGenerationTime.Format("2006-01-02T15:04:05Z07:00"), ClusterAPIServerInfo: report.ClusterAPIServerInfo, ClusterCloudProvider: report.ClusterCloudProvider, CustomerGUID: report.CustomerGUID, ClusterName: report.ClusterName, SummaryDetails: SummaryDetailsWithSeverity{ Controls: enrichedControls, Status: report.SummaryDetails.Status, Frameworks: report.SummaryDetails.Frameworks, ResourcesSeverityCounters: report.SummaryDetails.ResourcesSeverityCounters, ControlsSeverityCounters: report.SummaryDetails.ControlsSeverityCounters, StatusCounters: report.SummaryDetails.StatusCounters, Vulnerabilities: report.SummaryDetails.Vulnerabilities, Score: report.SummaryDetails.Score, ComplianceScore: report.SummaryDetails.ComplianceScore, }, Resources: report.Resources, Attributes: report.Attributes, Results: enrichedResults, Metadata: report.Metadata, ResourceLabels: resourceLabels, } } // extractResourceLabels extracts specified labels from all resources func extractResourceLabels(allResources map[string]workloadinterface.IMetadata, labelsToCopy []string) map[string]map[string]string { resourceLabels := make(map[string]map[string]string) for resourceID, resource := range allResources { // IMetadata doesn't have GetLabels, need to cast to IBasicWorkload basicWorkload, ok := resource.(workloadinterface.IBasicWorkload) if !ok { continue } labels := basicWorkload.GetLabels() if labels == nil { continue } extractedLabels := make(map[string]string) for _, labelKey := range labelsToCopy { if value, exists := labels[labelKey]; exists { extractedLabels[labelKey] = value } } // Only add to result if at least one label was found if len(extractedLabels) > 0 { resourceLabels[resourceID] = extractedLabels } } return resourceLabels } // FinalizeResults finalize the results objects by copying data from map to lists func FinalizeResults(data *cautils.OPASessionObj) *reporthandlingv2.PostureReport { report := reporthandlingv2.PostureReport{ SummaryDetails: data.Report.SummaryDetails, Metadata: *data.Metadata, ClusterAPIServerInfo: data.Report.ClusterAPIServerInfo, ReportGenerationTime: data.Report.ReportGenerationTime, Attributes: data.Report.Attributes, ClusterName: data.Report.ClusterName, CustomerGUID: data.Report.CustomerGUID, ClusterCloudProvider: data.Report.ClusterCloudProvider, } report.Results = make([]resourcesresults.Result, len(data.ResourcesResult)) finalizeResults(report.Results, data.ResourcesResult, data.ResourcesPrioritized) if !data.OmitRawResources { report.Resources = finalizeResources(report.Results, data.AllResources, data.ResourceSource) } return &report } func finalizeResults(results []resourcesresults.Result, resourcesResult map[string]resourcesresults.Result, prioritizedResources map[string]prioritization.PrioritizedResource) { index := 0 for resourceID := range resourcesResult { results[index] = resourcesResult[resourceID] // Add prioritization information to the result if v, exist := prioritizedResources[resourceID]; exist { results[index].PrioritizedResource = &v } index++ } } type infoStars struct { stars string info string } func mapInfoToPrintInfo(controls reportsummary.ControlSummaries) []infoStars { infoToPrintInfo := []infoStars{} infoToPrintInfoMap := map[string]interface{}{} starCount := indicator for _, control := range controls { if control.GetStatus().IsSkipped() && control.GetStatus().Info() != "" { if _, ok := infoToPrintInfoMap[control.GetStatus().Info()]; !ok { infoToPrintInfo = append(infoToPrintInfo, infoStars{ info: control.GetStatus().Info(), stars: starCount, }) starCount += indicator infoToPrintInfoMap[control.GetStatus().Info()] = nil } } } return infoToPrintInfo } func finalizeResources(results []resourcesresults.Result, allResources map[string]workloadinterface.IMetadata, resourcesSource map[string]reporthandling.Source) []reporthandling.Resource { resources := make([]reporthandling.Resource, 0) for i := range results { if obj, ok := allResources[results[i].ResourceID]; ok { resource := *reporthandling.NewResourceIMetadata(obj) if r, ok := resourcesSource[results[i].ResourceID]; ok { resource.SetSource(&r) } resources = append(resources, resource) } } return resources } func setSeverityToSummaryMap(cves []imageprinter.CVE, mapSeverityToSummary map[string]*imageprinter.SeveritySummary) { for _, cve := range cves { if _, ok := mapSeverityToSummary[cve.Severity]; !ok { mapSeverityToSummary[cve.Severity] = &imageprinter.SeveritySummary{} } mapSeverityToSummary[cve.Severity].NumberOfCVEs += 1 if cve.FixedState == string(v5.FixedState) { mapSeverityToSummary[cve.Severity].NumberOfFixableCVEs = mapSeverityToSummary[cve.Severity].NumberOfFixableCVEs + 1 } } } func setPkgNameToScoreMap(matches match.Matches, pkgScores map[string]*imageprinter.PackageScore) { for _, m := range matches.Sorted() { // key is pkg name + version to avoid version conflicts key := m.Package.Name + m.Package.Version if _, ok := pkgScores[key]; !ok { pkgScores[key] = &imageprinter.PackageScore{ Version: m.Package.Version, Name: m.Package.Name, MapSeverityToCVEsNumber: make(map[string]int, 0), } } if _, ok := pkgScores[key].MapSeverityToCVEsNumber[m.Vulnerability.Metadata.Severity]; !ok { pkgScores[key].MapSeverityToCVEsNumber[m.Vulnerability.Metadata.Severity] = 1 } else { pkgScores[key].MapSeverityToCVEsNumber[m.Vulnerability.Metadata.Severity] += 1 } pkgScores[key].Score += utils.ImageSeverityToInt(m.Vulnerability.Metadata.Severity) } } func extractCVEs(matches match.Matches, image string) []imageprinter.CVE { var CVEs []imageprinter.CVE for _, m := range matches.Sorted() { cve := imageprinter.CVE{ ID: m.Vulnerability.Metadata.ID, Severity: m.Vulnerability.Metadata.Severity, Package: m.Package.Name, Version: m.Package.Version, FixVersions: m.Vulnerability.Fix.Versions, FixedState: m.Vulnerability.Fix.State.String(), Image: image, } CVEs = append(CVEs, cve) } return CVEs }