mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-24 03:58:12 -08:00
fix(k8s): summary report when when only vulns exit (#2146)
* fix(k8s): summary report when when only vulns exit Signed-off-by: Jose Donizetti <jdbjunior@gmail.com> * fix(k8s): return error for not supported report Signed-off-by: Jose Donizetti <jdbjunior@gmail.com> * test(k8s): add tests for report Failed() Signed-off-by: Jose Donizetti <jdbjunior@gmail.com> * refactor: improve error message Signed-off-by: Jose Donizetti <jdbjunior@gmail.com>
This commit is contained in:
@@ -24,7 +24,7 @@ func (jw JSONWriter) Write(report Report) error {
|
||||
case summaryReport:
|
||||
output, err = json.MarshalIndent(report.consolidate(), "", " ")
|
||||
default:
|
||||
err = fmt.Errorf("report %s not supported", jw.Report)
|
||||
return xerrors.Errorf(`report %q not supported. Use "summary" or "all"`, jw.Report)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -3,7 +3,9 @@ package k8s
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
ftypes "github.com/aquasecurity/fanal/types"
|
||||
@@ -57,6 +59,10 @@ type Resource struct {
|
||||
Report types.Report `json:"-"`
|
||||
}
|
||||
|
||||
func (r Resource) fullname() string {
|
||||
return strings.ToLower(fmt.Sprintf("%s/%s/%s", r.Namespace, r.Kind, r.Name))
|
||||
}
|
||||
|
||||
// Failed returns whether the k8s report includes any vulnerabilities or misconfigurations
|
||||
func (r Report) Failed() bool {
|
||||
for _, r := range r.Vulnerabilities {
|
||||
@@ -80,25 +86,32 @@ func (r Report) consolidate() ConsolidatedReport {
|
||||
ClusterName: r.ClusterName,
|
||||
}
|
||||
|
||||
index := make(map[string]Resource)
|
||||
|
||||
for _, m := range r.Misconfigurations {
|
||||
found := false
|
||||
for _, v := range r.Vulnerabilities {
|
||||
if v.Kind == m.Kind && v.Name == m.Name && v.Namespace == m.Namespace {
|
||||
consolidated.Findings = append(consolidated.Findings, Resource{
|
||||
Namespace: v.Namespace,
|
||||
Kind: v.Kind,
|
||||
Name: v.Name,
|
||||
Results: append(v.Results, m.Results...),
|
||||
Error: v.Error,
|
||||
})
|
||||
found = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
consolidated.Findings = append(consolidated.Findings, m)
|
||||
}
|
||||
index[m.fullname()] = m
|
||||
}
|
||||
|
||||
for _, v := range r.Vulnerabilities {
|
||||
key := v.fullname()
|
||||
|
||||
if r, ok := index[key]; ok {
|
||||
index[key] = Resource{
|
||||
Namespace: r.Namespace,
|
||||
Kind: r.Kind,
|
||||
Name: r.Name,
|
||||
Results: append(r.Results, v.Results...),
|
||||
Error: r.Error,
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
index[key] = v
|
||||
}
|
||||
|
||||
consolidated.Findings = maps.Values(index)
|
||||
|
||||
return consolidated
|
||||
}
|
||||
|
||||
@@ -120,7 +133,7 @@ func write(report Report, option Option) error {
|
||||
Severities: option.Severities,
|
||||
}
|
||||
default:
|
||||
return xerrors.Errorf("unknown format: %v", option.Format)
|
||||
return xerrors.Errorf(`unknown format %q. Use "json" or "table"`, option.Format)
|
||||
}
|
||||
|
||||
return writer.Write(report)
|
||||
|
||||
173
pkg/k8s/report_test.go
Normal file
173
pkg/k8s/report_test.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
deployOrionWithMisconfigs = Resource{
|
||||
Namespace: "default",
|
||||
Kind: "Deploy",
|
||||
Name: "orion",
|
||||
Results: types.Results{
|
||||
{Misconfigurations: []types.DetectedMisconfiguration{{ID: "ID100", Status: types.StatusFailure}}},
|
||||
},
|
||||
}
|
||||
|
||||
deployOrionWithVulns = Resource{
|
||||
Namespace: "default",
|
||||
Kind: "Deploy",
|
||||
Name: "orion",
|
||||
Results: types.Results{
|
||||
{Vulnerabilities: []types.DetectedVulnerability{{VulnerabilityID: "CVE-2020-8888"}}},
|
||||
},
|
||||
}
|
||||
|
||||
deployOrionWithBothVulnsAndMisconfigs = Resource{
|
||||
Namespace: "default",
|
||||
Kind: "Deploy",
|
||||
Name: "orion",
|
||||
Results: types.Results{
|
||||
{Misconfigurations: []types.DetectedMisconfiguration{{ID: "ID100", Status: types.StatusFailure}}},
|
||||
{Vulnerabilities: []types.DetectedVulnerability{{VulnerabilityID: "CVE-2020-8888"}}},
|
||||
},
|
||||
}
|
||||
|
||||
cronjobHelloWithVulns = Resource{
|
||||
Namespace: "default",
|
||||
Kind: "Cronjob",
|
||||
Name: "hello",
|
||||
Results: types.Results{
|
||||
{Vulnerabilities: []types.DetectedVulnerability{{VulnerabilityID: "CVE-2020-9999"}}},
|
||||
},
|
||||
}
|
||||
|
||||
podPrometheusWithMisconfigs = Resource{
|
||||
Namespace: "default",
|
||||
Kind: "Pod",
|
||||
Name: "prometheus",
|
||||
Results: types.Results{
|
||||
{Misconfigurations: []types.DetectedMisconfiguration{{ID: "ID100"}}},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestReport_consolidate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
report Report
|
||||
expectedFindings map[string]Resource
|
||||
}{
|
||||
{
|
||||
name: "report with both misconfigs and vulnerabilities",
|
||||
report: Report{
|
||||
Vulnerabilities: []Resource{deployOrionWithVulns, cronjobHelloWithVulns},
|
||||
Misconfigurations: []Resource{deployOrionWithMisconfigs, podPrometheusWithMisconfigs},
|
||||
},
|
||||
expectedFindings: map[string]Resource{
|
||||
"default/deploy/orion": deployOrionWithBothVulnsAndMisconfigs,
|
||||
"default/cronjob/hello": cronjobHelloWithVulns,
|
||||
"default/pod/prometheus": podPrometheusWithMisconfigs,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "report with only misconfigurations",
|
||||
report: Report{
|
||||
Misconfigurations: []Resource{deployOrionWithMisconfigs, podPrometheusWithMisconfigs},
|
||||
},
|
||||
expectedFindings: map[string]Resource{
|
||||
"default/deploy/orion": deployOrionWithMisconfigs,
|
||||
"default/pod/prometheus": podPrometheusWithMisconfigs,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "report with only vulnerabilities",
|
||||
report: Report{
|
||||
Vulnerabilities: []Resource{deployOrionWithVulns, cronjobHelloWithVulns},
|
||||
},
|
||||
expectedFindings: map[string]Resource{
|
||||
"default/deploy/orion": deployOrionWithVulns,
|
||||
"default/cronjob/hello": cronjobHelloWithVulns,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
consolidateReport := tt.report.consolidate()
|
||||
for _, f := range consolidateReport.Findings {
|
||||
key := f.fullname()
|
||||
|
||||
expected, found := tt.expectedFindings[key]
|
||||
if !found {
|
||||
t.Errorf("key not found: %s", key)
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, f)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResource_fullname(t *testing.T) {
|
||||
tests := []struct {
|
||||
expected string
|
||||
resource Resource
|
||||
}{
|
||||
{"default/deploy/orion", deployOrionWithBothVulnsAndMisconfigs},
|
||||
{"default/deploy/orion", deployOrionWithMisconfigs},
|
||||
{"default/cronjob/hello", cronjobHelloWithVulns},
|
||||
{"default/pod/prometheus", podPrometheusWithMisconfigs},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.expected, func(t *testing.T) {
|
||||
assert.Equal(t, tt.expected, tt.resource.fullname())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceFailed(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
report Report
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "report with both misconfigs and vulnerabilities",
|
||||
report: Report{
|
||||
Vulnerabilities: []Resource{deployOrionWithVulns, cronjobHelloWithVulns},
|
||||
Misconfigurations: []Resource{deployOrionWithMisconfigs, podPrometheusWithMisconfigs},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "report with only misconfigurations",
|
||||
report: Report{
|
||||
Misconfigurations: []Resource{deployOrionWithMisconfigs, podPrometheusWithMisconfigs},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "report with only vulnerabilities",
|
||||
report: Report{
|
||||
Vulnerabilities: []Resource{deployOrionWithVulns, cronjobHelloWithVulns},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "report without vulnerabilities and misconfigurations",
|
||||
report: Report{},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.expected, tt.report.Failed())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,9 @@ import (
|
||||
"io"
|
||||
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
pkgReport "github.com/aquasecurity/trivy/pkg/report"
|
||||
)
|
||||
|
||||
@@ -36,6 +39,9 @@ func (tw TableWriter) Write(report Report) error {
|
||||
case summaryReport:
|
||||
writer := NewSummaryWriter(tw.Output, tw.Severities)
|
||||
return writer.Write(report)
|
||||
default:
|
||||
return xerrors.Errorf(`report %q not supported. Use "summary" or "all"`, tw.Report)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user