mirror of
https://github.com/kubescape/kubescape.git
synced 2026-04-15 06:58:11 +00:00
308 lines
13 KiB
Go
308 lines
13 KiB
Go
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
|
|
}
|