Add severity field to controls in JSON output

Co-authored-by: matthyx <20683409+matthyx@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-12-03 16:31:57 +00:00
parent 9bc29032e1
commit 742e3bb67f
5 changed files with 163 additions and 1 deletions

View File

@@ -120,7 +120,11 @@ func printConfigurationsScanning(opaSessionObj *cautils.OPASessionObj, imageScan
opaSessionObj.Report.SummaryDetails.Vulnerabilities.Images = imageScanSummary.Images
}
r, err := json.Marshal(FinalizeResults(opaSessionObj))
// Convert to PostureReportWithSeverity to add severity field to controls
finalizedReport := FinalizeResults(opaSessionObj)
reportWithSeverity := ConvertToPostureReportWithSeverity(finalizedReport)
r, err := json.Marshal(reportWithSeverity)
_, err = jp.writer.Write(r)
return err

View File

@@ -7,6 +7,7 @@ import (
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
"github.com/stretchr/testify/assert"
)
@@ -192,3 +193,85 @@ func TestConvertToReportSummary(t *testing.T) {
assert.Equal(t, want, got)
}
func TestEnrichControlsWithSeverity(t *testing.T) {
tests := []struct {
name string
scoreFactor float32
wantSeverity string
}{
{
name: "Critical severity",
scoreFactor: 9.0,
wantSeverity: "Critical",
},
{
name: "High severity",
scoreFactor: 8.0,
wantSeverity: "High",
},
{
name: "Medium severity",
scoreFactor: 6.0,
wantSeverity: "Medium",
},
{
name: "Low severity",
scoreFactor: 3.0,
wantSeverity: "Low",
},
{
name: "Unknown severity",
scoreFactor: 0.0,
wantSeverity: "Unknown",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
controls := reportsummary.ControlSummaries{
"C-0001": reportsummary.ControlSummary{
ControlID: "C-0001",
Name: "Test Control",
ScoreFactor: tt.scoreFactor,
},
}
enrichedControls := enrichControlsWithSeverity(controls)
assert.Equal(t, 1, len(enrichedControls))
assert.Equal(t, tt.wantSeverity, enrichedControls["C-0001"].Severity)
assert.Equal(t, "Test Control", enrichedControls["C-0001"].Name)
assert.Equal(t, tt.scoreFactor, enrichedControls["C-0001"].ScoreFactor)
})
}
}
func TestConvertToPostureReportWithSeverity(t *testing.T) {
// Create a mock PostureReport with controls having different severity levels
mockReport := reportsummary.MockSummaryDetails()
// Get the controls from mock data
controls := mockReport.Controls
// Create a minimal PostureReport
report := &reporthandlingv2.PostureReport{
SummaryDetails: *mockReport,
}
// Convert to PostureReportWithSeverity
reportWithSeverity := ConvertToPostureReportWithSeverity(report)
// Verify controls have severity field
assert.NotNil(t, reportWithSeverity)
assert.NotNil(t, reportWithSeverity.SummaryDetails.Controls)
// Verify each control in the original report has a corresponding enriched control with severity
for controlID, control := range controls {
enrichedControl, exists := reportWithSeverity.SummaryDetails.Controls[controlID]
assert.True(t, exists, "Control %s should exist in enriched controls", controlID)
assert.NotEmpty(t, enrichedControl.Severity, "Severity should not be empty for control %s", controlID)
assert.Equal(t, control.ControlID, enrichedControl.ControlID, "Control ID should match")
assert.Equal(t, control.ScoreFactor, enrichedControl.ScoreFactor, "ScoreFactor should match")
}
}

View File

@@ -8,6 +8,7 @@ import (
"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"
@@ -16,6 +17,80 @@ import (
const indicator = "†"
// ControlSummaryWithSeverity wraps ControlSummary to add severity field for JSON output
type ControlSummaryWithSeverity struct {
reportsummary.ControlSummary
Severity string `json:"severity"`
}
// 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 []resourcesresults.Result `json:"results,omitempty"`
Metadata reporthandlingv2.Metadata `json:"metadata,omitempty"`
}
// 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
}
// ConvertToPostureReportWithSeverity converts PostureReport to PostureReportWithSeverity
func ConvertToPostureReportWithSeverity(report *reporthandlingv2.PostureReport) *PostureReportWithSeverity {
enrichedControls := enrichControlsWithSeverity(report.SummaryDetails.Controls)
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: report.Results,
Metadata: report.Metadata,
}
}
// FinalizeResults finalize the results objects by copying data from map to lists
func FinalizeResults(data *cautils.OPASessionObj) *reporthandlingv2.PostureReport {
report := reporthandlingv2.PostureReport{