From 090a00e71736bf39af70eded21ac4d016b1f6baf Mon Sep 17 00:00:00 2001 From: chenk Date: Tue, 9 May 2023 16:52:02 +0300 Subject: [PATCH] feat: unified k8s scan resources (#4188) --- pkg/k8s/commands/run.go | 5 +- pkg/k8s/report/report.go | 94 +++++++++++++++++------------------ pkg/k8s/report/report_test.go | 68 ++++++++++++------------- pkg/k8s/report/table.go | 10 +--- pkg/k8s/scanner/scanner.go | 14 +++--- 5 files changed, 87 insertions(+), 104 deletions(-) diff --git a/pkg/k8s/commands/run.go b/pkg/k8s/commands/run.go index 1dc3afd70f..e50a84c5fd 100644 --- a/pkg/k8s/commands/run.go +++ b/pkg/k8s/commands/run.go @@ -95,10 +95,7 @@ func (r *runner) run(ctx context.Context, artifacts []*artifacts.Artifact) error if r.flagOpts.Compliance.Spec.ID != "" { var scanResults []types.Results - for _, rss := range rpt.Vulnerabilities { - scanResults = append(scanResults, rss.Results) - } - for _, rss := range rpt.Misconfigurations { + for _, rss := range rpt.Resources { scanResults = append(scanResults, rss.Results) } complianceReport, err := cr.BuildComplianceReport(scanResults, r.flagOpts.Compliance) diff --git a/pkg/k8s/report/report.go b/pkg/k8s/report/report.go index daf3ffd9f3..ad1f53609a 100644 --- a/pkg/k8s/report/report.go +++ b/pkg/k8s/report/report.go @@ -40,11 +40,10 @@ type Option struct { // Report represents a kubernetes scan report type Report struct { - SchemaVersion int `json:",omitempty"` - ClusterName string - Vulnerabilities []Resource `json:",omitempty"` - Misconfigurations []Resource `json:",omitempty"` - name string + SchemaVersion int `json:",omitempty"` + ClusterName string + Resources []Resource `json:",omitempty"` + name string } // ConsolidatedReport represents a kubernetes scan report with consolidated findings @@ -74,18 +73,11 @@ func (r Resource) fullname() string { // Failed returns whether the k8s report includes any vulnerabilities or misconfigurations func (r Report) Failed() bool { - for _, v := range r.Vulnerabilities { + for _, v := range r.Resources { if v.Results.Failed() { return true } } - - for _, m := range r.Misconfigurations { - if m.Results.Failed() { - return true - } - } - return false } @@ -96,12 +88,16 @@ func (r Report) consolidate() ConsolidatedReport { } index := make(map[string]Resource) - - for _, m := range r.Misconfigurations { - index[m.fullname()] = m + vulnerabilities := make([]Resource, 0) + for _, m := range r.Resources { + if vulnerabilitiesOrSecretResource(m) { + vulnerabilities = append(vulnerabilities, m) + } else { + index[m.fullname()] = m + } } - for _, v := range r.Vulnerabilities { + for _, v := range vulnerabilities { key := v.fullname() if res, ok := index[key]; ok { @@ -181,13 +177,19 @@ func separateMisconfigReports(k8sReport Report, scanners types.Scanners, compone workloadMisconfig := make([]Resource, 0) infraMisconfig := make([]Resource, 0) rbacAssessment := make([]Resource, 0) + workloadVulnerabilities := make([]Resource, 0) + workloadResource := make([]Resource, 0) + for _, resource := range k8sReport.Resources { + if vulnerabilitiesOrSecretResource(resource) { + workloadVulnerabilities = append(workloadVulnerabilities, resource) + continue + } - for _, misConfig := range k8sReport.Misconfigurations { switch { - case scanners.Enabled(types.RBACScanner) && rbacResource(misConfig): - rbacAssessment = append(rbacAssessment, misConfig) - case infraResource(misConfig): - workload, infra := splitInfraAndWorkloadResources(misConfig) + case scanners.Enabled(types.RBACScanner) && rbacResource(resource): + rbacAssessment = append(rbacAssessment, resource) + case infraResource(resource): + workload, infra := splitInfraAndWorkloadResources(resource) if slices.Contains(components, infraComponent) { infraMisconfig = append(infraMisconfig, infra) @@ -197,27 +199,27 @@ func separateMisconfigReports(k8sReport Report, scanners types.Scanners, compone workloadMisconfig = append(workloadMisconfig, workload) } - case scanners.Enabled(types.MisconfigScanner) && !rbacResource(misConfig): + case scanners.Enabled(types.MisconfigScanner) && !rbacResource(resource): if slices.Contains(components, workloadComponent) { - workloadMisconfig = append(workloadMisconfig, misConfig) + workloadMisconfig = append(workloadMisconfig, resource) } } } r := make([]reports, 0) - + workloadResource = append(workloadResource, workloadVulnerabilities...) + workloadResource = append(workloadResource, workloadMisconfig...) if shouldAddWorkloadReport(scanners) { workloadReport := Report{ - SchemaVersion: 0, - ClusterName: k8sReport.ClusterName, - Misconfigurations: workloadMisconfig, - Vulnerabilities: k8sReport.Vulnerabilities, - name: "Workload Assessment", + SchemaVersion: 0, + ClusterName: k8sReport.ClusterName, + Resources: workloadResource, + name: "Workload Assessment", } if (slices.Contains(components, workloadComponent) && len(workloadMisconfig) > 0) || - len(k8sReport.Vulnerabilities) > 0 { + len(workloadVulnerabilities) > 0 { r = append(r, reports{ report: workloadReport, columns: WorkloadColumns(), @@ -228,10 +230,10 @@ func separateMisconfigReports(k8sReport Report, scanners types.Scanners, compone if scanners.Enabled(types.RBACScanner) && len(rbacAssessment) > 0 { r = append(r, reports{ report: Report{ - SchemaVersion: 0, - ClusterName: k8sReport.ClusterName, - Misconfigurations: rbacAssessment, - name: "RBAC Assessment", + SchemaVersion: 0, + ClusterName: k8sReport.ClusterName, + Resources: rbacAssessment, + name: "RBAC Assessment", }, columns: RoleColumns(), }) @@ -243,10 +245,10 @@ func separateMisconfigReports(k8sReport Report, scanners types.Scanners, compone r = append(r, reports{ report: Report{ - SchemaVersion: 0, - ClusterName: k8sReport.ClusterName, - Misconfigurations: infraMisconfig, - name: "Infra Assessment", + SchemaVersion: 0, + ClusterName: k8sReport.ClusterName, + Resources: infraMisconfig, + name: "Infra Assessment", }, columns: InfraColumns(), }) @@ -292,15 +294,9 @@ func CreateResource(artifact *artifacts.Artifact, report types.Report, err error } func (r Report) printErrors() { - for _, resource := range r.Vulnerabilities { + for _, resource := range r.Resources { if resource.Error != "" { - log.Logger.Errorf("Error during vulnerabilities scan: %s", resource.Error) - } - } - - for _, resource := range r.Misconfigurations { - if resource.Error != "" { - log.Logger.Errorf("Error during misconfiguration scan: %s", resource.Error) + log.Logger.Errorf("Error during vulnerabilities or misconfiguration scan: %s", resource.Error) } } } @@ -366,3 +362,7 @@ func copyResult(r types.Result, misconfigs []types.DetectedMisconfiguration) typ func shouldAddWorkloadReport(scanners types.Scanners) bool { return scanners.AnyEnabled(types.MisconfigScanner, types.VulnerabilityScanner, types.SecretScanner) } + +func vulnerabilitiesOrSecretResource(resource Resource) bool { + return len(resource.Results) > 0 && (len(resource.Results[0].Vulnerabilities) > 0 || len(resource.Results[0].Secrets) > 0) +} diff --git a/pkg/k8s/report/report_test.go b/pkg/k8s/report/report_test.go index c9e2f50947..7039c99c6c 100644 --- a/pkg/k8s/report/report_test.go +++ b/pkg/k8s/report/report_test.go @@ -283,11 +283,9 @@ func TestReport_consolidate(t *testing.T) { { name: "report with both misconfigs and vulnerabilities", report: Report{ - Vulnerabilities: []Resource{ + Resources: []Resource{ deployOrionWithVulns, cronjobHelloWithVulns, - }, - Misconfigurations: []Resource{ deployOrionWithMisconfigs, podPrometheusWithMisconfigs, }, @@ -301,7 +299,7 @@ func TestReport_consolidate(t *testing.T) { { name: "report with only misconfigurations", report: Report{ - Misconfigurations: []Resource{ + Resources: []Resource{ deployOrionWithMisconfigs, podPrometheusWithMisconfigs, }, @@ -314,7 +312,7 @@ func TestReport_consolidate(t *testing.T) { { name: "report with only vulnerabilities", report: Report{ - Vulnerabilities: []Resource{ + Resources: []Resource{ deployOrionWithVulns, cronjobHelloWithVulns, }, @@ -382,11 +380,9 @@ func TestResourceFailed(t *testing.T) { { name: "report with both misconfigs and vulnerabilities", report: Report{ - Vulnerabilities: []Resource{ + Resources: []Resource{ deployOrionWithVulns, cronjobHelloWithVulns, - }, - Misconfigurations: []Resource{ deployOrionWithMisconfigs, podPrometheusWithMisconfigs, }, @@ -396,7 +392,7 @@ func TestResourceFailed(t *testing.T) { { name: "report with only misconfigurations", report: Report{ - Misconfigurations: []Resource{ + Resources: []Resource{ deployOrionWithMisconfigs, podPrometheusWithMisconfigs, }, @@ -406,7 +402,7 @@ func TestResourceFailed(t *testing.T) { { name: "report with only vulnerabilities", report: Report{ - Vulnerabilities: []Resource{ + Resources: []Resource{ deployOrionWithVulns, cronjobHelloWithVulns, }, @@ -464,7 +460,7 @@ func Test_rbacResource(t *testing.T) { func Test_separateMisconfigReports(t *testing.T) { k8sReport := Report{ - Misconfigurations: []Resource{ + Resources: []Resource{ {Kind: "Role"}, {Kind: "Deployment"}, {Kind: "StatefulSet"}, @@ -500,14 +496,14 @@ func Test_separateMisconfigReports(t *testing.T) { expectedReports: []Report{ // the order matter for the test { - Misconfigurations: []Resource{ + Resources: []Resource{ {Kind: "Deployment"}, {Kind: "StatefulSet"}, {Kind: "Pod"}, }, }, - {Misconfigurations: []Resource{{Kind: "Role"}}}, - {Misconfigurations: []Resource{{Kind: "Pod"}}}, + {Resources: []Resource{{Kind: "Role"}}}, + {Resources: []Resource{{Kind: "Pod"}}}, }, }, { @@ -521,13 +517,13 @@ func Test_separateMisconfigReports(t *testing.T) { expectedReports: []Report{ // the order matter for the test { - Misconfigurations: []Resource{ + Resources: []Resource{ {Kind: "Deployment"}, {Kind: "StatefulSet"}, {Kind: "Pod"}, }, }, - {Misconfigurations: []Resource{{Kind: "Pod"}}}, + {Resources: []Resource{{Kind: "Pod"}}}, }, }, { @@ -535,7 +531,7 @@ func Test_separateMisconfigReports(t *testing.T) { k8sReport: k8sReport, scanners: types.Scanners{types.RBACScanner}, expectedReports: []Report{ - {Misconfigurations: []Resource{{Kind: "Role"}}}, + {Resources: []Resource{{Kind: "Role"}}}, }, }, { @@ -545,7 +541,7 @@ func Test_separateMisconfigReports(t *testing.T) { components: []string{workloadComponent}, expectedReports: []Report{ { - Misconfigurations: []Resource{ + Resources: []Resource{ {Kind: "Deployment"}, {Kind: "StatefulSet"}, {Kind: "Pod"}, @@ -559,7 +555,7 @@ func Test_separateMisconfigReports(t *testing.T) { scanners: types.Scanners{types.MisconfigScanner}, components: []string{infraComponent}, expectedReports: []Report{ - {Misconfigurations: []Resource{{Kind: "Pod"}}}, + {Resources: []Resource{{Kind: "Pod"}}}, }, }, @@ -572,9 +568,9 @@ func Test_separateMisconfigReports(t *testing.T) { assert.Equal(t, len(tt.expectedReports), len(reports)) for i := range reports { - assert.Equal(t, len(tt.expectedReports[i].Misconfigurations), len(reports[i].report.Misconfigurations)) - for j, m := range tt.expectedReports[i].Misconfigurations { - assert.Equal(t, m.Kind, reports[i].report.Misconfigurations[j].Kind) + assert.Equal(t, len(tt.expectedReports[i].Resources), len(reports[i].report.Resources)) + for j, m := range tt.expectedReports[i].Resources { + assert.Equal(t, m.Kind, reports[i].report.Resources[j].Kind) } } }) @@ -602,8 +598,8 @@ func TestReportWrite_Summary(t *testing.T) { { name: "Only config, all serverities", report: Report{ - ClusterName: "test", - Misconfigurations: []Resource{deployOrionWithMisconfigs}, + ClusterName: "test", + Resources: []Resource{deployOrionWithMisconfigs}, }, scanners: types.Scanners{types.MisconfigScanner}, components: []string{workloadComponent}, @@ -624,8 +620,8 @@ Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN`, { name: "Only vuln, all serverities", report: Report{ - ClusterName: "test", - Vulnerabilities: []Resource{deployOrionWithVulns}, + ClusterName: "test", + Resources: []Resource{deployOrionWithVulns}, }, scanners: types.Scanners{types.VulnerabilityScanner}, severities: allSeverities, @@ -645,8 +641,8 @@ Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN`, { name: "Only rbac, all serverities", report: Report{ - ClusterName: "test", - Misconfigurations: []Resource{roleWithMisconfig}, + ClusterName: "test", + Resources: []Resource{roleWithMisconfig}, }, scanners: types.Scanners{types.RBACScanner}, severities: allSeverities, @@ -666,8 +662,8 @@ Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN`, { name: "Only secret, all serverities", report: Report{ - ClusterName: "test", - Vulnerabilities: []Resource{deployLuaWithSecrets}, + ClusterName: "test", + Resources: []Resource{deployLuaWithSecrets}, }, scanners: types.Scanners{types.SecretScanner}, severities: allSeverities, @@ -687,8 +683,8 @@ Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN`, { name: "apiserver, only infra and serverities", report: Report{ - ClusterName: "test", - Misconfigurations: []Resource{apiseverPodWithMisconfigAndInfra}, + ClusterName: "test", + Resources: []Resource{apiseverPodWithMisconfigAndInfra}, }, scanners: types.Scanners{types.MisconfigScanner}, components: []string{infraComponent}, @@ -709,8 +705,8 @@ Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN`, { name: "apiserver, vuln,config,secret and serverities", report: Report{ - ClusterName: "test", - Misconfigurations: []Resource{apiseverPodWithMisconfigAndInfra}, + ClusterName: "test", + Resources: []Resource{apiseverPodWithMisconfigAndInfra}, }, scanners: types.Scanners{ types.VulnerabilityScanner, @@ -735,8 +731,8 @@ Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN`, { name: "apiserver, all scanners and serverities", report: Report{ - ClusterName: "test", - Misconfigurations: []Resource{apiseverPodWithMisconfigAndInfra}, + ClusterName: "test", + Resources: []Resource{apiseverPodWithMisconfigAndInfra}, }, scanners: types.Scanners{ types.MisconfigScanner, diff --git a/pkg/k8s/report/table.go b/pkg/k8s/report/table.go index bb89725ac5..7e856f6979 100644 --- a/pkg/k8s/report/table.go +++ b/pkg/k8s/report/table.go @@ -43,15 +43,7 @@ func (tw TableWriter) Write(report Report) error { switch tw.Report { case allReport: t := pkgReport.Writer{Output: tw.Output, Severities: tw.Severities, ShowMessageOnce: &sync.Once{}} - for _, r := range report.Vulnerabilities { - if r.Report.Results.Failed() { - err := t.Write(r.Report) - if err != nil { - return err - } - } - } - for _, r := range report.Misconfigurations { + for _, r := range report.Resources { if r.Report.Results.Failed() { err := t.Write(r.Report) if err != nil { diff --git a/pkg/k8s/scanner/scanner.go b/pkg/k8s/scanner/scanner.go index 28b354d9cc..07f6b8f8da 100644 --- a/pkg/k8s/scanner/scanner.go +++ b/pkg/k8s/scanner/scanner.go @@ -47,7 +47,7 @@ func (s *Scanner) Scan(ctx context.Context, artifactsData []*artifacts.Artifact) log.Fatal(xerrors.Errorf("can't enable logger error: %w", err)) } }() - var vulns, misconfigs []report.Resource + var resources []report.Resource type scanResult struct { vulns []report.Resource @@ -74,8 +74,8 @@ func (s *Scanner) Scan(ctx context.Context, artifactsData []*artifacts.Artifact) } onResult := func(result scanResult) error { - vulns = append(vulns, result.vulns...) - misconfigs = append(misconfigs, result.misconfig) + resources = append(resources, result.vulns...) + resources = append(resources, result.misconfig) return nil } @@ -84,12 +84,10 @@ func (s *Scanner) Scan(ctx context.Context, artifactsData []*artifacts.Artifact) if err != nil { return report.Report{}, err } - return report.Report{ - SchemaVersion: 0, - ClusterName: s.cluster, - Vulnerabilities: vulns, - Misconfigurations: misconfigs, + SchemaVersion: 0, + ClusterName: s.cluster, + Resources: resources, }, nil }