mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-12 07:40:48 -08:00
refactor(k8s): custom reports (#3076)
This commit is contained in:
2
go.mod
2
go.mod
@@ -355,7 +355,7 @@ require (
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/neurosnap/sentences.v1 v1.0.6 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gotest.tools v2.2.0+incompatible
|
||||
gotest.tools/v3 v3.2.0 // indirect
|
||||
helm.sh/helm/v3 v3.10.0 // indirect
|
||||
|
||||
@@ -18,15 +18,17 @@ func (jw JSONWriter) Write(report *ComplianceReport) error {
|
||||
var output []byte
|
||||
var err error
|
||||
|
||||
var v interface{}
|
||||
switch jw.Report {
|
||||
case allReport:
|
||||
output, err = json.MarshalIndent(report, "", " ")
|
||||
v = report
|
||||
case summaryReport:
|
||||
output, err = json.MarshalIndent(BuildSummary(report), "", " ")
|
||||
v = BuildSummary(report)
|
||||
default:
|
||||
return xerrors.Errorf(`report %q not supported. Use "summary" or "all"`, jw.Report)
|
||||
}
|
||||
|
||||
output, err = json.MarshalIndent(v, "", " ")
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to marshal json: %w", err)
|
||||
}
|
||||
|
||||
@@ -1,46 +1,82 @@
|
||||
package report
|
||||
package report_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/compliance/report"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJSONReport(t *testing.T) {
|
||||
func TestJSONWriter_Write(t *testing.T) {
|
||||
input := &report.ComplianceReport{
|
||||
ID: "1234",
|
||||
Title: "NSA",
|
||||
RelatedResources: []string{"https://example.com"},
|
||||
Results: []*report.ControlCheckResult{
|
||||
{
|
||||
ID: "1.0",
|
||||
Name: "Non-root containers",
|
||||
Severity: "MEDIUM",
|
||||
Results: types.Results{
|
||||
{
|
||||
Misconfigurations: []types.DetectedMisconfiguration{
|
||||
{AVDID: "AVD-KSV012", Status: types.StatusFailure},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "1.1",
|
||||
Name: "Immutable container file systems",
|
||||
Severity: "LOW",
|
||||
Results: types.Results{
|
||||
{
|
||||
Misconfigurations: []types.DetectedMisconfiguration{
|
||||
{AVDID: "AVD-KSV013", Status: types.StatusFailure},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
specPath string
|
||||
resultPath string
|
||||
reportType string
|
||||
wantJsonReportPath string
|
||||
name string
|
||||
reportType string
|
||||
input *report.ComplianceReport
|
||||
want string
|
||||
}{
|
||||
{name: "build summary json output report", specPath: "./testdata/config_spec.yaml", reportType: "summary", resultPath: "./testdata/results_config.json", wantJsonReportPath: "./testdata/json_summary.json"},
|
||||
{name: "build full json output report", specPath: "./testdata/config_spec.yaml", reportType: "all", resultPath: "./testdata/results_config.json", wantJsonReportPath: "./testdata/json_view.json"},
|
||||
{
|
||||
name: "build summary json output report",
|
||||
reportType: "summary",
|
||||
input: input,
|
||||
want: filepath.Join("testdata", "summary.json"),
|
||||
},
|
||||
{
|
||||
name: "build full json output report",
|
||||
reportType: "all",
|
||||
input: input,
|
||||
want: filepath.Join("testdata", "all.json"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
specfile, err := os.ReadFile(tt.specPath)
|
||||
assert.NoError(t, err)
|
||||
var res types.Results
|
||||
resultByte, err := os.ReadFile(tt.resultPath)
|
||||
err = json.Unmarshal(resultByte, &res)
|
||||
assert.NoError(t, err)
|
||||
complianceResults, err := BuildComplianceReport([]types.Results{res}, string(specfile))
|
||||
assert.NoError(t, err)
|
||||
ioWriter := new(bytes.Buffer)
|
||||
tr := JSONWriter{Report: tt.reportType, Output: ioWriter}
|
||||
err = tr.Write(complianceResults)
|
||||
assert.NoError(t, err)
|
||||
bt, err := io.ReadAll(ioWriter)
|
||||
assert.NoError(t, err)
|
||||
r, err := os.ReadFile(tt.wantJsonReportPath)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, string(bt), string(r))
|
||||
buf := new(bytes.Buffer)
|
||||
tr := report.JSONWriter{Report: tt.reportType, Output: buf}
|
||||
err := tr.Write(tt.input)
|
||||
require.NoError(t, err)
|
||||
|
||||
want, err := os.ReadFile(tt.want)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.JSONEq(t, string(want), buf.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"io"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/compliance/spec"
|
||||
@@ -29,37 +28,37 @@ type Option struct {
|
||||
|
||||
// ComplianceReport represents a kubernetes scan report
|
||||
type ComplianceReport struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Version string `json:"severity"`
|
||||
RelatedResources []string `json:"relatedResources"`
|
||||
Results []*ControlCheckResult `json:"results"`
|
||||
ID string
|
||||
Title string
|
||||
Description string
|
||||
Version string
|
||||
RelatedResources []string
|
||||
Results []*ControlCheckResult
|
||||
}
|
||||
|
||||
type ControlCheckResult struct {
|
||||
ControlCheckID string `json:"id"`
|
||||
ControlName string `json:"name"`
|
||||
ControlDescription string `json:"description"`
|
||||
DefaultStatus spec.ControlStatus `json:"defaultStatus,omitempty"`
|
||||
ControlSeverity string `json:"severity"`
|
||||
Results types.Results `json:"results"`
|
||||
ID string
|
||||
Name string
|
||||
Description string
|
||||
DefaultStatus spec.ControlStatus `json:",omitempty"`
|
||||
Severity string
|
||||
Results types.Results
|
||||
}
|
||||
|
||||
// ConsolidatedReport represents a kubernetes scan report with consolidated findings
|
||||
// SummaryReport represents a kubernetes scan report with consolidated findings
|
||||
type SummaryReport struct {
|
||||
SchemaVersion int `json:",omitempty"`
|
||||
ReportID string
|
||||
ReportTitle string
|
||||
ID string
|
||||
Title string
|
||||
SummaryControls []ControlCheckSummary `json:",omitempty"`
|
||||
}
|
||||
|
||||
type ControlCheckSummary struct {
|
||||
ControlCheckID string `json:"id"`
|
||||
ControlName string `json:"name"`
|
||||
ControlSeverity string `json:"severity"`
|
||||
TotalPass float32 `json:"totalPass"`
|
||||
TotalFail float32 `json:"totalFail"`
|
||||
ID string
|
||||
Name string
|
||||
Severity string
|
||||
TotalPass float32
|
||||
TotalFail float32
|
||||
}
|
||||
|
||||
// Writer defines the result write operation
|
||||
@@ -99,16 +98,18 @@ func (r ComplianceReport) empty() bool {
|
||||
func buildControlCheckResults(checksMap map[string]types.Results, controls []spec.Control) []*ControlCheckResult {
|
||||
complianceResults := make([]*ControlCheckResult, 0)
|
||||
for _, control := range controls {
|
||||
cr := ControlCheckResult{}
|
||||
cr.ControlName = control.Name
|
||||
cr.ControlCheckID = control.ID
|
||||
cr.ControlDescription = control.Description
|
||||
cr.ControlSeverity = string(control.Severity)
|
||||
cr.DefaultStatus = control.DefaultStatus
|
||||
var results types.Results
|
||||
for _, c := range control.Checks {
|
||||
cr.Results = append(cr.Results, checksMap[c.ID]...)
|
||||
results = append(results, checksMap[c.ID]...)
|
||||
}
|
||||
complianceResults = append(complianceResults, &cr)
|
||||
complianceResults = append(complianceResults, &ControlCheckResult{
|
||||
Name: control.Name,
|
||||
ID: control.ID,
|
||||
Description: control.Description,
|
||||
Severity: string(control.Severity),
|
||||
DefaultStatus: control.DefaultStatus,
|
||||
Results: results,
|
||||
})
|
||||
}
|
||||
return complianceResults
|
||||
}
|
||||
@@ -126,20 +127,9 @@ func buildComplianceReportResults(checksMap map[string]types.Results, spec spec.
|
||||
}
|
||||
}
|
||||
|
||||
func BuildComplianceReport(scanResults []types.Results, complianceSpec string) (*ComplianceReport, error) {
|
||||
// load compliance spec
|
||||
cs := spec.ComplianceSpec{}
|
||||
err := yaml.Unmarshal([]byte(complianceSpec), &cs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// validate scanners types (vuln and config) define in spec supported
|
||||
err = spec.ValidateScanners(cs.Spec.Controls)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func BuildComplianceReport(scanResults []types.Results, cs spec.ComplianceSpec) (*ComplianceReport, error) {
|
||||
// aggregate checks by ID
|
||||
aggregateChecksByID := spec.AggregateAllChecksBySpecID(scanResults, cs.Spec.Controls)
|
||||
aggregateChecksByID := spec.AggregateAllChecksBySpecID(scanResults, cs)
|
||||
|
||||
// build compliance report results
|
||||
return buildComplianceReportResults(aggregateChecksByID, cs.Spec), nil
|
||||
|
||||
@@ -1,84 +1,242 @@
|
||||
package report
|
||||
package report_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
|
||||
"github.com/aquasecurity/trivy/pkg/compliance/report"
|
||||
"github.com/aquasecurity/trivy/pkg/compliance/spec"
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
func TestReport(t *testing.T) {
|
||||
func TestBuildComplianceReport(t *testing.T) {
|
||||
type args struct {
|
||||
scanResults []types.Results
|
||||
cs spec.ComplianceSpec
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
specPath string
|
||||
resultPath string
|
||||
Option Option
|
||||
wantSummaryReportPath string
|
||||
expectError bool
|
||||
name string
|
||||
args args
|
||||
want *report.ComplianceReport
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{name: "build table report summary", Option: Option{Report: "summary", Format: "table"}, specPath: "./testdata/config_spec.yaml", resultPath: "./testdata/results_config.json", wantSummaryReportPath: "./testdata/table_summary.txt"},
|
||||
{name: "build table report full", Option: Option{Report: "all", Format: "table"}, specPath: "./testdata/config_spec.yaml", resultPath: "./testdata/results_config.json", wantSummaryReportPath: "./testdata/table.txt"},
|
||||
{name: "build json report summary", Option: Option{Report: "summary", Format: "json"}, specPath: "./testdata/config_spec.yaml", resultPath: "./testdata/results_config.json", wantSummaryReportPath: "./testdata/json_summary.json"},
|
||||
{name: "build json report full", Option: Option{Report: "all", Format: "json"}, specPath: "./testdata/config_spec.yaml", resultPath: "./testdata/results_config.json", wantSummaryReportPath: "./testdata/json_view.json"},
|
||||
{name: "build report bad format", Option: Option{Report: "all", Format: "aaa"}, specPath: "./testdata/config_spec.yaml", resultPath: "./testdata/results_config.json", wantSummaryReportPath: "./testdata/json_view.json", expectError: true},
|
||||
{
|
||||
name: "happy",
|
||||
args: args{
|
||||
scanResults: []types.Results{
|
||||
{
|
||||
{
|
||||
Target: "Deployment/metrics-server",
|
||||
Class: types.ClassConfig,
|
||||
Type: ftypes.Kubernetes,
|
||||
MisconfSummary: &types.MisconfSummary{
|
||||
Successes: 1,
|
||||
Failures: 0,
|
||||
Exceptions: 0,
|
||||
},
|
||||
Misconfigurations: []types.DetectedMisconfiguration{
|
||||
{
|
||||
Type: "Kubernetes Security Check",
|
||||
ID: "KSV001",
|
||||
AVDID: "AVD-KSV-0001",
|
||||
Title: "Process can elevate its own privileges",
|
||||
Description: "A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node.",
|
||||
Message: "Container 'metrics-server' of Deployment 'metrics-server' should set 'securityContext.allowPrivilegeEscalation' to false",
|
||||
Namespace: "builtin.kubernetes.KSV001",
|
||||
Query: "data.builtin.kubernetes.KSV001.deny",
|
||||
Resolution: "Set 'set containers[].securityContext.allowPrivilegeEscalation' to 'false'.",
|
||||
Severity: dbTypes.SeverityMedium.String(),
|
||||
PrimaryURL: "https://avd.aquasec.com/misconfig/ksv001",
|
||||
References: []string{
|
||||
"https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted",
|
||||
"https://avd.aquasec.com/misconfig/ksv001",
|
||||
},
|
||||
Status: types.StatusPassed,
|
||||
},
|
||||
{
|
||||
Type: "Kubernetes Security Check",
|
||||
ID: "KSV002",
|
||||
AVDID: "AVD-KSV-9999",
|
||||
Status: types.StatusFailure,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
Target: "rancher/metrics-server:v0.3.6 (debian 9.9)",
|
||||
Class: types.ClassOSPkg,
|
||||
Type: "debian",
|
||||
Vulnerabilities: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "DLA-2424-1",
|
||||
VendorIDs: []string{"DLA-2424-1"},
|
||||
PkgName: "tzdata",
|
||||
InstalledVersion: "2019a-0+deb9u1",
|
||||
FixedVersion: "2020d-0+deb9u1",
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
|
||||
},
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: vulnerability.Debian,
|
||||
Name: "Debian Security Tracker",
|
||||
URL: "https://salsa.debian.org/security-tracker-team/security-tracker",
|
||||
},
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "tzdata - new upstream version",
|
||||
Severity: dbTypes.SeverityUnknown.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cs: spec.ComplianceSpec{
|
||||
Spec: spec.Spec{
|
||||
ID: "1234",
|
||||
Title: "NSA",
|
||||
Description: "National Security Agency - Kubernetes Hardening Guidance",
|
||||
Version: "1.0",
|
||||
RelatedResources: []string{
|
||||
"https://example.com",
|
||||
},
|
||||
Controls: []spec.Control{
|
||||
{
|
||||
ID: "1.0",
|
||||
Name: "Non-root containers",
|
||||
Description: "Check that container is not running as root",
|
||||
Severity: "MEDIUM",
|
||||
Checks: []spec.SpecCheck{
|
||||
{ID: "AVD-KSV-0001"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "1.1",
|
||||
Name: "Immutable container file systems",
|
||||
Description: "Check that container root file system is immutable",
|
||||
Severity: "LOW",
|
||||
Checks: []spec.SpecCheck{
|
||||
{ID: "AVD-KSV-0002"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "1.2",
|
||||
Name: "tzdata - new upstream version",
|
||||
Description: "Bad tzdata package",
|
||||
Severity: "CRITICAL",
|
||||
Checks: []spec.SpecCheck{
|
||||
{ID: "DLA-2424-1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &report.ComplianceReport{
|
||||
ID: "1234",
|
||||
Title: "NSA",
|
||||
Description: "National Security Agency - Kubernetes Hardening Guidance",
|
||||
Version: "1.0",
|
||||
RelatedResources: []string{
|
||||
"https://example.com",
|
||||
},
|
||||
Results: []*report.ControlCheckResult{
|
||||
{
|
||||
ID: "1.0",
|
||||
Name: "Non-root containers",
|
||||
Description: "Check that container is not running as root",
|
||||
Severity: "MEDIUM",
|
||||
Results: types.Results{
|
||||
{
|
||||
Target: "Deployment/metrics-server",
|
||||
Class: types.ClassConfig,
|
||||
Type: ftypes.Kubernetes,
|
||||
MisconfSummary: &types.MisconfSummary{
|
||||
Successes: 1,
|
||||
Failures: 0,
|
||||
Exceptions: 0,
|
||||
},
|
||||
Misconfigurations: []types.DetectedMisconfiguration{
|
||||
{
|
||||
Type: "Kubernetes Security Check",
|
||||
ID: "KSV001",
|
||||
AVDID: "AVD-KSV-0001",
|
||||
Title: "Process can elevate its own privileges",
|
||||
Description: "A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node.",
|
||||
Message: "Container 'metrics-server' of Deployment 'metrics-server' should set 'securityContext.allowPrivilegeEscalation' to false",
|
||||
Namespace: "builtin.kubernetes.KSV001",
|
||||
Query: "data.builtin.kubernetes.KSV001.deny",
|
||||
Resolution: "Set 'set containers[].securityContext.allowPrivilegeEscalation' to 'false'.",
|
||||
Severity: dbTypes.SeverityMedium.String(),
|
||||
PrimaryURL: "https://avd.aquasec.com/misconfig/ksv001",
|
||||
References: []string{
|
||||
"https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted",
|
||||
"https://avd.aquasec.com/misconfig/ksv001",
|
||||
},
|
||||
Status: types.StatusPassed,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "1.1",
|
||||
Name: "Immutable container file systems",
|
||||
Description: "Check that container root file system is immutable",
|
||||
Severity: "LOW",
|
||||
Results: nil,
|
||||
},
|
||||
{
|
||||
ID: "1.2",
|
||||
Name: "tzdata - new upstream version",
|
||||
Description: "Bad tzdata package",
|
||||
Severity: "CRITICAL",
|
||||
Results: types.Results{
|
||||
{
|
||||
Target: "rancher/metrics-server:v0.3.6 (debian 9.9)",
|
||||
Class: types.ClassOSPkg,
|
||||
Type: "debian",
|
||||
Vulnerabilities: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "DLA-2424-1",
|
||||
VendorIDs: []string{"DLA-2424-1"},
|
||||
PkgName: "tzdata",
|
||||
InstalledVersion: "2019a-0+deb9u1",
|
||||
FixedVersion: "2020d-0+deb9u1",
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
|
||||
},
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: vulnerability.Debian,
|
||||
Name: "Debian Security Tracker",
|
||||
URL: "https://salsa.debian.org/security-tracker-team/security-tracker",
|
||||
},
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "tzdata - new upstream version",
|
||||
Severity: dbTypes.SeverityUnknown.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
specfile, err := os.ReadFile(tt.specPath)
|
||||
assert.NoError(t, err)
|
||||
var res types.Results
|
||||
resultByte, err := os.ReadFile(tt.resultPath)
|
||||
err = json.Unmarshal(resultByte, &res)
|
||||
assert.NoError(t, err)
|
||||
complianceResults, err := BuildComplianceReport([]types.Results{res}, string(specfile))
|
||||
ioWriter := new(bytes.Buffer)
|
||||
tt.Option.Output = ioWriter
|
||||
err = Write(complianceResults, tt.Option)
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
bt, err := io.ReadAll(ioWriter)
|
||||
assert.NoError(t, err)
|
||||
r, err := os.ReadFile(tt.wantSummaryReportPath)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, string(bt), string(r))
|
||||
got, err := report.BuildComplianceReport(tt.args.scanResults, tt.args.cs)
|
||||
if !tt.wantErr(t, err, fmt.Sprintf("BuildComplianceReport(%v, %v)", tt.args.scanResults, tt.args.cs)) {
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildComplianceReportResults(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
specPath string
|
||||
resultPath string
|
||||
complianceReportPath string
|
||||
}{
|
||||
{name: "build report test config and vuln", specPath: "./testdata/config_vuln_spec.yaml", resultPath: "./testdata/results_vul_config.json", complianceReportPath: "./testdata/vuln_config_compliance.json"}}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
specFile, err := os.ReadFile(tt.specPath)
|
||||
assert.NoError(t, err)
|
||||
var res types.Results
|
||||
c, err := os.ReadFile(tt.resultPath)
|
||||
err = json.Unmarshal(c, &res)
|
||||
assert.NoError(t, err)
|
||||
pp, err := BuildComplianceReport([]types.Results{res}, string(specFile))
|
||||
assert.NoError(t, err)
|
||||
complianceReport, err := os.ReadFile(tt.complianceReportPath)
|
||||
assert.NoError(t, err)
|
||||
var cp ComplianceReport
|
||||
err = json.Unmarshal(complianceReport, &cp)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, reflect.DeepEqual(&cp, pp))
|
||||
|
||||
assert.Equalf(t, tt.want, got, "BuildComplianceReport(%v, %v)", tt.args.scanResults, tt.args.cs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,17 @@ import (
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/compliance/spec"
|
||||
pkgReport "github.com/aquasecurity/trivy/pkg/report/table"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
func BuildSummary(cr *ComplianceReport) *SummaryReport {
|
||||
ccma := make([]ControlCheckSummary, 0)
|
||||
var ccma []ControlCheckSummary
|
||||
for _, control := range cr.Results {
|
||||
ccm := ControlCheckSummary{ControlCheckID: control.ControlCheckID, ControlName: control.ControlName, ControlSeverity: control.ControlSeverity}
|
||||
ccm := ControlCheckSummary{
|
||||
ID: control.ID,
|
||||
Name: control.Name,
|
||||
Severity: control.Severity,
|
||||
}
|
||||
if len(control.Results) == 0 { // this validation is mainly for vuln type
|
||||
if control.DefaultStatus == spec.PassStatus {
|
||||
ccm.TotalPass = 1
|
||||
@@ -25,27 +30,23 @@ func BuildSummary(cr *ComplianceReport) *SummaryReport {
|
||||
continue
|
||||
}
|
||||
for _, check := range control.Results {
|
||||
for _, cr := range check.Misconfigurations {
|
||||
if cr.CheckPass() {
|
||||
ccm.TotalPass++
|
||||
continue
|
||||
}
|
||||
ccm.TotalFail++
|
||||
}
|
||||
for _, cr := range check.Vulnerabilities {
|
||||
if cr.CheckPass() {
|
||||
for _, m := range check.Misconfigurations {
|
||||
if m.Status == types.StatusPassed {
|
||||
ccm.TotalPass++
|
||||
continue
|
||||
}
|
||||
ccm.TotalFail++
|
||||
}
|
||||
// Detected vulnerabilities are always failure.
|
||||
ccm.TotalFail += float32(len(check.Vulnerabilities))
|
||||
}
|
||||
ccma = append(ccma, ccm)
|
||||
|
||||
}
|
||||
return &SummaryReport{ReportID: cr.ID,
|
||||
ReportTitle: cr.Title,
|
||||
SummaryControls: ccma}
|
||||
return &SummaryReport{
|
||||
ID: cr.ID,
|
||||
Title: cr.Title,
|
||||
SummaryControls: ccma,
|
||||
}
|
||||
}
|
||||
|
||||
type SummaryWriter struct {
|
||||
@@ -69,7 +70,6 @@ func NewSummaryWriter(output io.Writer, requiredSevs []dbTypes.Severity, columnH
|
||||
|
||||
// Write writes the results in a summarized table format
|
||||
func (s SummaryWriter) Write(report *ComplianceReport) error {
|
||||
|
||||
if _, err := fmt.Fprintln(s.Output); err != nil {
|
||||
return xerrors.Errorf("failed to write summary report: %w", err)
|
||||
}
|
||||
@@ -102,7 +102,7 @@ func (s SummaryWriter) Write(report *ComplianceReport) error {
|
||||
|
||||
func (s SummaryWriter) generateSummary(summaryControls ControlCheckSummary) []string {
|
||||
percentage := calculatePercentage(summaryControls.TotalFail, summaryControls.TotalPass)
|
||||
return []string{summaryControls.ControlCheckID, summaryControls.ControlSeverity, summaryControls.ControlName, percentage}
|
||||
return []string{summaryControls.ID, summaryControls.Severity, summaryControls.Name, percentage}
|
||||
}
|
||||
|
||||
func calculatePercentage(totalFail float32, totalPass float32) string {
|
||||
|
||||
@@ -1,62 +1,158 @@
|
||||
package report
|
||||
package report_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/compliance/report"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBuildSummary(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
specPath string
|
||||
resultPath string
|
||||
wantSummaryReportPath string
|
||||
name string
|
||||
reportType string
|
||||
input *report.ComplianceReport
|
||||
want *report.SummaryReport
|
||||
}{
|
||||
{name: "build report summary config only", specPath: "./testdata/config_spec.yaml", resultPath: "./testdata/results_config.json", wantSummaryReportPath: "./testdata/report_summary.json"},
|
||||
{name: "build report summary config and vuln", specPath: "./testdata/config_vuln_spec.yaml", resultPath: "./testdata/results_vul_config.json", wantSummaryReportPath: "./testdata/vuln_config_summary.json"}}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
specfile, err := os.ReadFile(tt.specPath)
|
||||
assert.NoError(t, err)
|
||||
var res types.Results
|
||||
c, err := os.ReadFile(tt.resultPath)
|
||||
err = json.Unmarshal(c, &res)
|
||||
assert.NoError(t, err)
|
||||
complianceResults, err := BuildComplianceReport([]types.Results{res}, string(specfile))
|
||||
tk := BuildSummary(complianceResults)
|
||||
o, err := json.Marshal(tk)
|
||||
assert.NoError(t, err)
|
||||
r, err := os.ReadFile(tt.wantSummaryReportPath)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(string(o)), string(r))
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculatePercentage(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pass float32
|
||||
fail float32
|
||||
want string
|
||||
}{
|
||||
{name: "calcuale percentage pass bigger then fail", pass: 10.0, fail: 5.0, want: "66.67%"},
|
||||
{name: "calcuale percentage pass smaller then fail", pass: 5.0, fail: 10.0, want: "33.33%"},
|
||||
{name: "calcuale percentage pass zero and fail zero", pass: 0.0, fail: 0.0, want: "0.00%"},
|
||||
{
|
||||
name: "build report summary config only",
|
||||
reportType: "summary",
|
||||
input: &report.ComplianceReport{
|
||||
ID: "1234",
|
||||
Title: "NSA",
|
||||
RelatedResources: []string{"https://example.com"},
|
||||
Results: []*report.ControlCheckResult{
|
||||
{
|
||||
ID: "1.0",
|
||||
Name: "Non-root containers",
|
||||
Severity: "MEDIUM",
|
||||
Results: types.Results{
|
||||
{
|
||||
Misconfigurations: []types.DetectedMisconfiguration{
|
||||
{AVDID: "AVD-KSV012", Status: types.StatusFailure},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "1.1",
|
||||
Name: "Immutable container file systems",
|
||||
Severity: "LOW",
|
||||
Results: types.Results{
|
||||
{
|
||||
Misconfigurations: []types.DetectedMisconfiguration{
|
||||
{AVDID: "AVD-KSV013", Status: types.StatusFailure},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &report.SummaryReport{
|
||||
SchemaVersion: 0,
|
||||
ID: "1234",
|
||||
Title: "NSA",
|
||||
SummaryControls: []report.ControlCheckSummary{
|
||||
{
|
||||
ID: "1.0",
|
||||
Name: "Non-root containers",
|
||||
Severity: "MEDIUM",
|
||||
TotalPass: 0,
|
||||
TotalFail: 1,
|
||||
},
|
||||
{
|
||||
ID: "1.1",
|
||||
Name: "Immutable container file systems",
|
||||
Severity: "LOW",
|
||||
TotalPass: 0,
|
||||
TotalFail: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "build full json output report",
|
||||
reportType: "all",
|
||||
input: &report.ComplianceReport{
|
||||
ID: "1234",
|
||||
Title: "NSA",
|
||||
RelatedResources: []string{"https://example.com"},
|
||||
Results: []*report.ControlCheckResult{
|
||||
{
|
||||
ID: "1.0",
|
||||
Name: "Non-root containers",
|
||||
Severity: "MEDIUM",
|
||||
Results: types.Results{
|
||||
{
|
||||
Misconfigurations: []types.DetectedMisconfiguration{
|
||||
{AVDID: "AVD-KSV012", Status: types.StatusFailure},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "1.1",
|
||||
Name: "Immutable container file systems",
|
||||
Severity: "LOW",
|
||||
Results: types.Results{
|
||||
{
|
||||
Misconfigurations: []types.DetectedMisconfiguration{
|
||||
{AVDID: "AVD-KSV013", Status: types.StatusFailure},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "1.2",
|
||||
Name: "tzdata - new upstream version",
|
||||
Severity: "LOW",
|
||||
Results: types.Results{
|
||||
{
|
||||
Vulnerabilities: []types.DetectedVulnerability{
|
||||
{VulnerabilityID: "CVE-9999-0001"},
|
||||
{VulnerabilityID: "CVE-9999-0002"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &report.SummaryReport{
|
||||
SchemaVersion: 0,
|
||||
ID: "1234",
|
||||
Title: "NSA",
|
||||
SummaryControls: []report.ControlCheckSummary{
|
||||
{
|
||||
ID: "1.0",
|
||||
Name: "Non-root containers",
|
||||
Severity: "MEDIUM",
|
||||
TotalPass: 0,
|
||||
TotalFail: 1,
|
||||
},
|
||||
{
|
||||
ID: "1.1",
|
||||
Name: "Immutable container file systems",
|
||||
Severity: "LOW",
|
||||
TotalPass: 0,
|
||||
TotalFail: 1,
|
||||
},
|
||||
{
|
||||
ID: "1.2",
|
||||
Name: "tzdata - new upstream version",
|
||||
Severity: "LOW",
|
||||
TotalPass: 0,
|
||||
TotalFail: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := calculatePercentage(tt.fail, tt.pass)
|
||||
assert.Equal(t, got, tt.want)
|
||||
|
||||
got := report.BuildSummary(tt.input)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ const (
|
||||
ComplianceColumn = "Compliance"
|
||||
)
|
||||
|
||||
func (tw TableWriter) Columns() []string {
|
||||
func (tw TableWriter) columns() []string {
|
||||
return []string{ControlIDColumn, SeverityColumn, ControlNameColumn, ComplianceColumn}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ func (tw TableWriter) Write(report *ComplianceReport) error {
|
||||
}
|
||||
}
|
||||
case summaryReport:
|
||||
writer := NewSummaryWriter(tw.Output, tw.Severities, tw.Columns())
|
||||
writer := NewSummaryWriter(tw.Output, tw.Severities, tw.columns())
|
||||
return writer.Write(report)
|
||||
default:
|
||||
return xerrors.Errorf(`report %q not supported. Use "summary" or "all"`, tw.Report)
|
||||
|
||||
@@ -1,52 +1,75 @@
|
||||
package report
|
||||
package report_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/compliance/report"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
func TestTableReport(t *testing.T) {
|
||||
func TestTableWriter_Write(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
specPath string
|
||||
resultPath string
|
||||
reportType string
|
||||
wantSummaryReportPath string
|
||||
name string
|
||||
reportType string
|
||||
input *report.ComplianceReport
|
||||
want string
|
||||
}{
|
||||
{name: "build table report summary config only", specPath: "./testdata/config_spec.yaml", reportType: "summary", resultPath: "./testdata/results_config.json", wantSummaryReportPath: "./testdata/table_summary.txt"},
|
||||
{name: "build table report summary config and vuln", specPath: "./testdata/config_vuln_spec.yaml", reportType: "summary", resultPath: "./testdata/results_vul_config.json", wantSummaryReportPath: "./testdata/vuln_conf_table_summary.txt"},
|
||||
{name: "build table report config only", specPath: "./testdata/config_spec.yaml", reportType: "all", resultPath: "./testdata/results_config.json", wantSummaryReportPath: "./testdata/table.txt"},
|
||||
{name: "build table report config and vuln", specPath: "./testdata/config_vuln_spec.yaml", reportType: "all", resultPath: "./testdata/results_vul_config.json", wantSummaryReportPath: "./testdata/vuln_conf_table.txt"},
|
||||
{
|
||||
name: "build summary table",
|
||||
reportType: "summary",
|
||||
input: &report.ComplianceReport{
|
||||
ID: "1234",
|
||||
Title: "NSA",
|
||||
RelatedResources: []string{"https://example.com"},
|
||||
Results: []*report.ControlCheckResult{
|
||||
{
|
||||
ID: "1.0",
|
||||
Name: "Non-root containers",
|
||||
Severity: "MEDIUM",
|
||||
Results: types.Results{
|
||||
{
|
||||
Misconfigurations: []types.DetectedMisconfiguration{
|
||||
{AVDID: "AVD-KSV012", Status: types.StatusFailure},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "1.1",
|
||||
Name: "Immutable container file systems",
|
||||
Severity: "LOW",
|
||||
Results: types.Results{
|
||||
{
|
||||
Misconfigurations: []types.DetectedMisconfiguration{
|
||||
{AVDID: "AVD-KSV013", Status: types.StatusFailure},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: filepath.Join("testdata", "table_summary.txt"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
specfile, err := os.ReadFile(tt.specPath)
|
||||
assert.NoError(t, err)
|
||||
var res types.Results
|
||||
resultByte, err := os.ReadFile(tt.resultPath)
|
||||
err = json.Unmarshal(resultByte, &res)
|
||||
assert.NoError(t, err)
|
||||
complianceResults, err := BuildComplianceReport([]types.Results{res}, string(specfile))
|
||||
ioWriter := new(bytes.Buffer)
|
||||
tr := TableWriter{Report: tt.reportType, Output: ioWriter}
|
||||
err = tr.Write(complianceResults)
|
||||
assert.NoError(t, err)
|
||||
bt, err := io.ReadAll(ioWriter)
|
||||
assert.NoError(t, err)
|
||||
r, err := os.ReadFile(tt.wantSummaryReportPath)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, string(bt), string(r))
|
||||
buf := new(bytes.Buffer)
|
||||
tr := report.TableWriter{Report: tt.reportType, Output: buf}
|
||||
err := tr.Write(tt.input)
|
||||
require.NoError(t, err)
|
||||
|
||||
want, err := os.ReadFile(tt.want)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, string(want), buf.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestColumn(t *testing.T) {
|
||||
tr := TableWriter{}
|
||||
assert.Equal(t, tr.Columns(), []string{ControlIDColumn, SeverityColumn, ControlNameColumn, ComplianceColumn})
|
||||
}
|
||||
|
||||
57
pkg/compliance/report/testdata/all.json
vendored
Normal file
57
pkg/compliance/report/testdata/all.json
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"ID": "1234",
|
||||
"Title": "NSA",
|
||||
"Description": "",
|
||||
"Version": "",
|
||||
"RelatedResources": [
|
||||
"https://example.com"
|
||||
],
|
||||
"Results": [
|
||||
{
|
||||
"ID": "1.0",
|
||||
"Name": "Non-root containers",
|
||||
"Description": "",
|
||||
"Severity": "MEDIUM",
|
||||
"Results": [
|
||||
{
|
||||
"Target": "",
|
||||
"Misconfigurations": [
|
||||
{
|
||||
"AVDID": "AVD-KSV012",
|
||||
"Status": "FAIL",
|
||||
"Layer": {},
|
||||
"CauseMetadata": {
|
||||
"Code": {
|
||||
"Lines": null
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "1.1",
|
||||
"Name": "Immutable container file systems",
|
||||
"Description": "",
|
||||
"Severity": "LOW",
|
||||
"Results": [
|
||||
{
|
||||
"Target": "",
|
||||
"Misconfigurations": [
|
||||
{
|
||||
"AVDID": "AVD-KSV013",
|
||||
"Status": "FAIL",
|
||||
"Layer": {},
|
||||
"CauseMetadata": {
|
||||
"Code": {
|
||||
"Lines": null
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
23
pkg/compliance/report/testdata/config_spec.yaml
vendored
23
pkg/compliance/report/testdata/config_spec.yaml
vendored
@@ -1,23 +0,0 @@
|
||||
---
|
||||
spec:
|
||||
id: "1234"
|
||||
title: nsa
|
||||
description: National Security Agency - Kubernetes Hardening Guidance
|
||||
relatedResources :
|
||||
- http://related-resource/
|
||||
version: "1.0"
|
||||
controls:
|
||||
- name: Non-root containers
|
||||
description: 'Check that container is not running as root'
|
||||
id: '1.0'
|
||||
checks:
|
||||
- id: AVD-KSV-0001
|
||||
severity: 'MEDIUM'
|
||||
- name: Immutable container file systems
|
||||
description: 'Check that container root file system is immutable'
|
||||
id: '1.1'
|
||||
checks:
|
||||
- id: AVD-KSV-0003
|
||||
severity: 'LOW'
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
---
|
||||
spec:
|
||||
id: "1234"
|
||||
title: nsa
|
||||
description: National Security Agency - Kubernetes Hardening Guidance
|
||||
relatedResources :
|
||||
- http://related-resource/
|
||||
version: "1.0"
|
||||
controls:
|
||||
- name: Non-root containers
|
||||
description: 'Check that container is not running as root'
|
||||
id: '1.0'
|
||||
checks:
|
||||
- id: AVD-KSV-0001
|
||||
severity: 'MEDIUM'
|
||||
- name: Immutable container file systems
|
||||
description: 'Check that container root file system is immutable'
|
||||
id: '1.1'
|
||||
checks:
|
||||
- id: AVD-KSV-0003
|
||||
severity: 'LOW'
|
||||
- name: tzdata - new upstream version
|
||||
description: 'Bad tzdata package'
|
||||
id: '1.2'
|
||||
checks:
|
||||
- id: DLA-2424-1
|
||||
severity: 'CRITICAL'
|
||||
|
||||
|
||||
20
pkg/compliance/report/testdata/json_summary.json
vendored
20
pkg/compliance/report/testdata/json_summary.json
vendored
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"ReportID": "1234",
|
||||
"ReportTitle": "nsa",
|
||||
"SummaryControls": [
|
||||
{
|
||||
"id": "1.0",
|
||||
"name": "Non-root containers",
|
||||
"severity": "MEDIUM",
|
||||
"totalPass": 0,
|
||||
"totalFail": 1
|
||||
},
|
||||
{
|
||||
"id": "1.1",
|
||||
"name": "Immutable container file systems",
|
||||
"severity": "LOW",
|
||||
"totalPass": 0,
|
||||
"totalFail": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
271
pkg/compliance/report/testdata/json_view.json
vendored
271
pkg/compliance/report/testdata/json_view.json
vendored
@@ -1,271 +0,0 @@
|
||||
{
|
||||
"id": "1234",
|
||||
"title": "nsa",
|
||||
"description": "National Security Agency - Kubernetes Hardening Guidance",
|
||||
"severity": "1.0",
|
||||
"relatedResources": [
|
||||
"http://related-resource/"
|
||||
],
|
||||
"results": [
|
||||
{
|
||||
"id": "1.0",
|
||||
"name": "Non-root containers",
|
||||
"description": "Check that container is not running as root",
|
||||
"severity": "MEDIUM",
|
||||
"results": [
|
||||
{
|
||||
"Target": "Deployment/metrics-server",
|
||||
"Class": "config",
|
||||
"Type": "kubernetes",
|
||||
"MisconfSummary": {
|
||||
"Successes": 1,
|
||||
"Failures": 0,
|
||||
"Exceptions": 0
|
||||
},
|
||||
"Misconfigurations": [
|
||||
{
|
||||
"Type": "Kubernetes Security Check",
|
||||
"ID": "KSV001",
|
||||
"AVDID": "AVD-KSV-0001",
|
||||
"Title": "Process can elevate its own privileges",
|
||||
"Description": "A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node.",
|
||||
"Message": "Container 'metrics-server' of Deployment 'metrics-server' should set 'securityContext.allowPrivilegeEscalation' to false",
|
||||
"Namespace": "builtin.kubernetes.KSV001",
|
||||
"Query": "data.builtin.kubernetes.KSV001.deny",
|
||||
"Resolution": "Set 'set containers[].securityContext.allowPrivilegeEscalation' to 'false'.",
|
||||
"Severity": "MEDIUM",
|
||||
"PrimaryURL": "https://avd.aquasec.com/misconfig/ksv001",
|
||||
"References": [
|
||||
"https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted",
|
||||
"https://avd.aquasec.com/misconfig/ksv001"
|
||||
],
|
||||
"Status": "FAIL",
|
||||
"Layer": {},
|
||||
"CauseMetadata": {
|
||||
"Provider": "Kubernetes",
|
||||
"Service": "general",
|
||||
"StartLine": 132,
|
||||
"EndLine": 140,
|
||||
"Code": {
|
||||
"Lines": [
|
||||
{
|
||||
"Number": 132,
|
||||
"Content": " - image: rancher/metrics-server:v0.3.6",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": true,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 133,
|
||||
"Content": " imagePullPolicy: IfNotPresent",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 134,
|
||||
"Content": " name: metrics-server",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 135,
|
||||
"Content": " resources: {}",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 136,
|
||||
"Content": " terminationMessagePath: /dev/termination-log",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 137,
|
||||
"Content": " terminationMessagePolicy: File",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 138,
|
||||
"Content": " volumeMounts:",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 139,
|
||||
"Content": " - mountPath: /tmp",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 140,
|
||||
"Content": " name: tmp-dir",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "1.1",
|
||||
"name": "Immutable container file systems",
|
||||
"description": "Check that container root file system is immutable",
|
||||
"severity": "LOW",
|
||||
"results": [
|
||||
{
|
||||
"Target": "Deployment/metrics-server",
|
||||
"Class": "config",
|
||||
"Type": "kubernetes",
|
||||
"MisconfSummary": {
|
||||
"Successes": 1,
|
||||
"Failures": 0,
|
||||
"Exceptions": 0
|
||||
},
|
||||
"Misconfigurations": [
|
||||
{
|
||||
"Type": "Kubernetes Security Check",
|
||||
"ID": "KSV003",
|
||||
"AVDID": "AVD-KSV-0003",
|
||||
"Title": "Default capabilities not dropped",
|
||||
"Description": "The container should drop all default capabilities and add only those that are needed for its execution.",
|
||||
"Message": "Container 'metrics-server' of Deployment 'metrics-server' should add 'ALL' to 'securityContext.capabilities.drop'",
|
||||
"Namespace": "builtin.kubernetes.KSV003",
|
||||
"Query": "data.builtin.kubernetes.KSV003.deny",
|
||||
"Resolution": "Add 'ALL' to containers[].securityContext.capabilities.drop.",
|
||||
"Severity": "LOW",
|
||||
"PrimaryURL": "https://avd.aquasec.com/misconfig/ksv003",
|
||||
"References": [
|
||||
"https://kubesec.io/basics/containers-securitycontext-capabilities-drop-index-all/",
|
||||
"https://avd.aquasec.com/misconfig/ksv003"
|
||||
],
|
||||
"Status": "FAIL",
|
||||
"Layer": {},
|
||||
"CauseMetadata": {
|
||||
"Provider": "Kubernetes",
|
||||
"Service": "general",
|
||||
"StartLine": 132,
|
||||
"EndLine": 140,
|
||||
"Code": {
|
||||
"Lines": [
|
||||
{
|
||||
"Number": 132,
|
||||
"Content": " - image: rancher/metrics-server:v0.3.6",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": true,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 133,
|
||||
"Content": " imagePullPolicy: IfNotPresent",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 134,
|
||||
"Content": " name: metrics-server",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 135,
|
||||
"Content": " resources: {}",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 136,
|
||||
"Content": " terminationMessagePath: /dev/termination-log",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 137,
|
||||
"Content": " terminationMessagePolicy: File",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 138,
|
||||
"Content": " volumeMounts:",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 139,
|
||||
"Content": " - mountPath: /tmp",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 140,
|
||||
"Content": " name: tmp-dir",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{"ReportID":"1234","ReportTitle":"nsa","SummaryControls":[{"id":"1.0","name":"Non-root containers","severity":"MEDIUM","totalPass":0,"totalFail":1},{"id":"1.1","name":"Immutable container file systems","severity":"LOW","totalPass":0,"totalFail":1}]}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,150 +0,0 @@
|
||||
[
|
||||
{
|
||||
"Target": "Deployment/metrics-server",
|
||||
"Class": "config",
|
||||
"Type": "kubernetes",
|
||||
"MisconfSummary": {
|
||||
"Successes": 66,
|
||||
"Failures": 13,
|
||||
"Exceptions": 0
|
||||
},
|
||||
"Misconfigurations": [
|
||||
{
|
||||
"Type": "Kubernetes Security Check",
|
||||
"ID": "KSV001",
|
||||
"AVDID": "AVD-KSV-0001",
|
||||
"Title": "Process can elevate its own privileges",
|
||||
"Description": "A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node.",
|
||||
"Message": "Container 'metrics-server' of Deployment 'metrics-server' should set 'securityContext.allowPrivilegeEscalation' to false",
|
||||
"Namespace": "builtin.kubernetes.KSV001",
|
||||
"Query": "data.builtin.kubernetes.KSV001.deny",
|
||||
"Resolution": "Set 'set containers[].securityContext.allowPrivilegeEscalation' to 'false'.",
|
||||
"Severity": "MEDIUM",
|
||||
"PrimaryURL": "https://avd.aquasec.com/misconfig/ksv001",
|
||||
"References": [
|
||||
"https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted",
|
||||
"https://avd.aquasec.com/misconfig/ksv001"
|
||||
],
|
||||
"Status": "PASS",
|
||||
"Layer": {},
|
||||
"CauseMetadata": {
|
||||
"Provider": "Kubernetes",
|
||||
"Service": "general",
|
||||
"StartLine": 132,
|
||||
"EndLine": 140,
|
||||
"Code": {
|
||||
"Lines": [
|
||||
{
|
||||
"Number": 132,
|
||||
"Content": " - image: rancher/metrics-server:v0.3.6",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": true,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 133,
|
||||
"Content": " imagePullPolicy: IfNotPresent",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 134,
|
||||
"Content": " name: metrics-server",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 135,
|
||||
"Content": " resources: {}",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 136,
|
||||
"Content": " terminationMessagePath: /dev/termination-log",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 137,
|
||||
"Content": " terminationMessagePolicy: File",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 138,
|
||||
"Content": " volumeMounts:",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 139,
|
||||
"Content": " - mountPath: /tmp",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 140,
|
||||
"Content": " name: tmp-dir",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "rancher/metrics-server:v0.3.6 (debian 9.9)",
|
||||
"Class": "os-pkgs",
|
||||
"Type": "debian",
|
||||
"Vulnerabilities": [
|
||||
{
|
||||
"VulnerabilityID": "DLA-2424-1",
|
||||
"VendorIDs": [
|
||||
"DLA-2424-1"
|
||||
],
|
||||
"PkgName": "tzdata",
|
||||
"InstalledVersion": "2019a-0+deb9u1",
|
||||
"FixedVersion": "2020d-0+deb9u1",
|
||||
"Layer": {
|
||||
"DiffID": "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02"
|
||||
},
|
||||
"DataSource": {
|
||||
"ID": "debian",
|
||||
"Name": "Debian Security Tracker",
|
||||
"URL": "https://salsa.debian.org/security-tracker-team/security-tracker"
|
||||
},
|
||||
"Title": "tzdata - new upstream version",
|
||||
"Severity": "UNKNOWN"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
20
pkg/compliance/report/testdata/summary.json
vendored
Normal file
20
pkg/compliance/report/testdata/summary.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"ID": "1234",
|
||||
"Title": "NSA",
|
||||
"SummaryControls": [
|
||||
{
|
||||
"ID": "1.0",
|
||||
"Name": "Non-root containers",
|
||||
"Severity": "MEDIUM",
|
||||
"TotalPass": 0,
|
||||
"TotalFail": 1
|
||||
},
|
||||
{
|
||||
"ID": "1.1",
|
||||
"Name": "Immutable container file systems",
|
||||
"Severity": "LOW",
|
||||
"TotalPass": 0,
|
||||
"TotalFail": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
Summary Report for compliance: nsa
|
||||
Summary Report for compliance: NSA
|
||||
┌─────┬──────────┬──────────────────────────────────┬────────────┐
|
||||
│ ID │ Severity │ Control Name │ Compliance │
|
||||
├─────┼──────────┼──────────────────────────────────┼────────────┤
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
|
||||
Deployment/metrics-server (kubernetes)
|
||||
======================================
|
||||
Tests: 1 (SUCCESSES: 1, FAILURES: 0, EXCEPTIONS: 0)
|
||||
Failures: 0 ()
|
||||
|
||||
MEDIUM: Container 'metrics-server' of Deployment 'metrics-server' should set 'securityContext.allowPrivilegeEscalation' to false
|
||||
════════════════════════════════════════
|
||||
A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node.
|
||||
|
||||
See https://avd.aquasec.com/misconfig/ksv001
|
||||
────────────────────────────────────────
|
||||
Deployment/metrics-server:132-140
|
||||
────────────────────────────────────────
|
||||
132 ┌ - image: rancher/metrics-server:v0.3.6
|
||||
133 │ imagePullPolicy: IfNotPresent
|
||||
134 │ name: metrics-server
|
||||
135 │ resources: {}
|
||||
136 │ terminationMessagePath: /dev/termination-log
|
||||
137 │ terminationMessagePolicy: File
|
||||
138 │ volumeMounts:
|
||||
139 │ - mountPath: /tmp
|
||||
140 └ name: tmp-dir
|
||||
────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
rancher/metrics-server:v0.3.6 (debian 9.9)
|
||||
==========================================
|
||||
Total: 0 ()
|
||||
|
||||
┌─────────┬───────────────┬──────────┬───────────────────┬────────────────┬───────────────────────────────┐
|
||||
│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │
|
||||
├─────────┼───────────────┼──────────┼───────────────────┼────────────────┼───────────────────────────────┤
|
||||
│ tzdata │ DLA-2424-1 │ UNKNOWN │ 2019a-0+deb9u1 │ 2020d-0+deb9u1 │ tzdata - new upstream version │
|
||||
└─────────┴───────────────┴──────────┴───────────────────┴────────────────┴───────────────────────────────┘
|
||||
@@ -1,11 +0,0 @@
|
||||
|
||||
Summary Report for compliance: nsa
|
||||
┌─────┬──────────┬──────────────────────────────────┬────────────┐
|
||||
│ ID │ Severity │ Control Name │ Compliance │
|
||||
├─────┼──────────┼──────────────────────────────────┼────────────┤
|
||||
│ 1.0 │ MEDIUM │ Non-root containers │ 100.00% │
|
||||
│ 1.1 │ LOW │ Immutable container file systems │ 0.00% │
|
||||
│ 1.2 │ CRITICAL │ tzdata - new upstream version │ 0.00% │
|
||||
└─────┴──────────┴──────────────────────────────────┴────────────┘
|
||||
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
{
|
||||
"id": "1234",
|
||||
"title": "nsa",
|
||||
"description": "National Security Agency - Kubernetes Hardening Guidance",
|
||||
"severity": "1.0",
|
||||
"relatedResources": [
|
||||
"http://related-resource/"
|
||||
],
|
||||
"results": [
|
||||
{
|
||||
"id": "1.0",
|
||||
"name": "Non-root containers",
|
||||
"description": "Check that container is not running as root",
|
||||
"severity": "MEDIUM",
|
||||
"results": [
|
||||
{
|
||||
"Target": "Deployment/metrics-server",
|
||||
"Class": "config",
|
||||
"Type": "kubernetes",
|
||||
"MisconfSummary": {
|
||||
"Successes": 1,
|
||||
"Failures": 0,
|
||||
"Exceptions": 0
|
||||
},
|
||||
"Misconfigurations": [
|
||||
{
|
||||
"Type": "Kubernetes Security Check",
|
||||
"ID": "KSV001",
|
||||
"AVDID": "AVD-KSV-0001",
|
||||
"Title": "Process can elevate its own privileges",
|
||||
"Description": "A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node.",
|
||||
"Message": "Container 'metrics-server' of Deployment 'metrics-server' should set 'securityContext.allowPrivilegeEscalation' to false",
|
||||
"Namespace": "builtin.kubernetes.KSV001",
|
||||
"Query": "data.builtin.kubernetes.KSV001.deny",
|
||||
"Resolution": "Set 'set containers[].securityContext.allowPrivilegeEscalation' to 'false'.",
|
||||
"Severity": "MEDIUM",
|
||||
"PrimaryURL": "https://avd.aquasec.com/misconfig/ksv001",
|
||||
"References": [
|
||||
"https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted",
|
||||
"https://avd.aquasec.com/misconfig/ksv001"
|
||||
],
|
||||
"Status": "PASS",
|
||||
"Layer": {},
|
||||
"CauseMetadata": {
|
||||
"Provider": "Kubernetes",
|
||||
"Service": "general",
|
||||
"StartLine": 132,
|
||||
"EndLine": 140,
|
||||
"Code": {
|
||||
"Lines": [
|
||||
{
|
||||
"Number": 132,
|
||||
"Content": " - image: rancher/metrics-server:v0.3.6",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": true,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 133,
|
||||
"Content": " imagePullPolicy: IfNotPresent",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 134,
|
||||
"Content": " name: metrics-server",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 135,
|
||||
"Content": " resources: {}",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 136,
|
||||
"Content": " terminationMessagePath: /dev/termination-log",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 137,
|
||||
"Content": " terminationMessagePolicy: File",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 138,
|
||||
"Content": " volumeMounts:",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 139,
|
||||
"Content": " - mountPath: /tmp",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": false
|
||||
},
|
||||
{
|
||||
"Number": 140,
|
||||
"Content": " name: tmp-dir",
|
||||
"IsCause": true,
|
||||
"Annotation": "",
|
||||
"Truncated": false,
|
||||
"FirstCause": false,
|
||||
"LastCause": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "1.1",
|
||||
"name": "Immutable container file systems",
|
||||
"description": "Check that container root file system is immutable",
|
||||
"severity": "LOW",
|
||||
"results": null
|
||||
},
|
||||
{
|
||||
"id": "1.2",
|
||||
"name": "tzdata - new upstream version",
|
||||
"description": "Bad tzdata package",
|
||||
"severity": "CRITICAL",
|
||||
"results": [
|
||||
{
|
||||
"Target": "rancher/metrics-server:v0.3.6 (debian 9.9)",
|
||||
"Class": "os-pkgs",
|
||||
"Type": "debian",
|
||||
"Vulnerabilities": [
|
||||
{
|
||||
"VulnerabilityID": "DLA-2424-1",
|
||||
"VendorIDs": [
|
||||
"DLA-2424-1"
|
||||
],
|
||||
"PkgName": "tzdata",
|
||||
"InstalledVersion": "2019a-0+deb9u1",
|
||||
"FixedVersion": "2020d-0+deb9u1",
|
||||
"Layer": {
|
||||
"DiffID": "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02"
|
||||
},
|
||||
"DataSource": {
|
||||
"ID": "debian",
|
||||
"Name": "Debian Security Tracker",
|
||||
"URL": "https://salsa.debian.org/security-tracker-team/security-tracker"
|
||||
},
|
||||
"Title": "tzdata - new upstream version",
|
||||
"Severity": "UNKNOWN"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{"ReportID":"1234","ReportTitle":"nsa","SummaryControls":[{"id":"1.0","name":"Non-root containers","severity":"MEDIUM","totalPass":1,"totalFail":0},{"id":"1.1","name":"Immutable container file systems","severity":"LOW","totalPass":0,"totalFail":0},{"id":"1.2","name":"tzdata - new upstream version","severity":"CRITICAL","totalPass":0,"totalFail":1}]}
|
||||
@@ -1,17 +1,16 @@
|
||||
package spec
|
||||
|
||||
type Severity string
|
||||
import (
|
||||
"strings"
|
||||
|
||||
const (
|
||||
SeverityCritical Severity = "CRITICAL"
|
||||
SeverityHigh Severity = "HIGH"
|
||||
SeverityMedium Severity = "MEDIUM"
|
||||
SeverityLow Severity = "LOW"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
SeverityNone Severity = "NONE"
|
||||
SeverityUnknown Severity = "UNKNOWN"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
type Severity string
|
||||
|
||||
// ComplianceSpec represent the compliance specification
|
||||
type ComplianceSpec struct {
|
||||
Spec Spec `yaml:"spec"`
|
||||
@@ -58,3 +57,42 @@ const (
|
||||
PassStatus ControlStatus = "PASS"
|
||||
WarnStatus ControlStatus = "WARN"
|
||||
)
|
||||
|
||||
// SecurityChecks reads spec control and determines the scanners by check ID prefix
|
||||
func (cs *ComplianceSpec) SecurityChecks() ([]types.SecurityCheck, error) {
|
||||
scannerTypes := map[types.SecurityCheck]struct{}{}
|
||||
for _, control := range cs.Spec.Controls {
|
||||
for _, check := range control.Checks {
|
||||
scannerType := securityCheckByCheckID(check.ID)
|
||||
if scannerType == types.SecurityCheckUnknown {
|
||||
return nil, xerrors.Errorf("unsupported check ID: %s", check.ID)
|
||||
}
|
||||
scannerTypes[scannerType] = struct{}{}
|
||||
}
|
||||
}
|
||||
return maps.Keys(scannerTypes), nil
|
||||
}
|
||||
|
||||
// CheckIDs return list of compliance check IDs
|
||||
func (cs *ComplianceSpec) CheckIDs() map[types.SecurityCheck][]string {
|
||||
checkIDsMap := map[types.SecurityCheck][]string{}
|
||||
for _, control := range cs.Spec.Controls {
|
||||
for _, check := range control.Checks {
|
||||
scannerType := securityCheckByCheckID(check.ID)
|
||||
checkIDsMap[scannerType] = append(checkIDsMap[scannerType], check.ID)
|
||||
}
|
||||
}
|
||||
return checkIDsMap
|
||||
}
|
||||
|
||||
func securityCheckByCheckID(checkID string) types.SecurityCheck {
|
||||
checkID = strings.ToLower(checkID)
|
||||
switch {
|
||||
case strings.HasPrefix(checkID, "cve-") || strings.HasPrefix(checkID, "dla-"):
|
||||
return types.SecurityCheckVulnerability
|
||||
case strings.HasPrefix(checkID, "avd-"):
|
||||
return types.SecurityCheckConfig
|
||||
default:
|
||||
return types.SecurityCheckUnknown
|
||||
}
|
||||
}
|
||||
|
||||
236
pkg/compliance/spec/compliance_test.go
Normal file
236
pkg/compliance/spec/compliance_test.go
Normal file
@@ -0,0 +1,236 @@
|
||||
package spec_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/compliance/spec"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
func TestComplianceSpec_SecurityChecks(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
spec spec.Spec
|
||||
want []types.SecurityCheck
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "get config scanner type by check id prefix",
|
||||
spec: spec.Spec{
|
||||
ID: "1234",
|
||||
Title: "NSA",
|
||||
Description: "National Security Agency - Kubernetes Hardening Guidance",
|
||||
RelatedResources: []string{
|
||||
"https://example.com",
|
||||
},
|
||||
Version: "1.0",
|
||||
Controls: []spec.Control{
|
||||
{
|
||||
Name: "Non-root containers",
|
||||
Description: "Check that container is not running as root",
|
||||
ID: "1.0",
|
||||
Checks: []spec.SpecCheck{
|
||||
{ID: "AVD-KSV012"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Check that encryption resource has been set",
|
||||
Description: "Control checks whether encryption resource has been set",
|
||||
ID: "1.1",
|
||||
Checks: []spec.SpecCheck{
|
||||
{ID: "AVD-1.2.31"},
|
||||
{ID: "AVD-1.2.32"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []types.SecurityCheck{types.SecurityCheckConfig},
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "get config and vuln scanners types by check id prefix",
|
||||
spec: spec.Spec{
|
||||
ID: "1234",
|
||||
Title: "NSA",
|
||||
Description: "National Security Agency - Kubernetes Hardening Guidance",
|
||||
RelatedResources: []string{
|
||||
"https://example.com",
|
||||
},
|
||||
Version: "1.0",
|
||||
Controls: []spec.Control{
|
||||
{
|
||||
Name: "Non-root containers",
|
||||
Description: "Check that container is not running as root",
|
||||
ID: "1.0",
|
||||
Checks: []spec.SpecCheck{
|
||||
{ID: "AVD-KSV012"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Check that encryption resource has been set",
|
||||
Description: "Control checks whether encryption resource has been set",
|
||||
ID: "1.1",
|
||||
Checks: []spec.SpecCheck{
|
||||
{ID: "AVD-1.2.31"},
|
||||
{ID: "AVD-1.2.32"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Ensure no critical vulnerabilities",
|
||||
Description: "Control checks whether critical vulnerabilities are not found",
|
||||
ID: "7.0",
|
||||
Checks: []spec.SpecCheck{
|
||||
{ID: "CVE-9999-9999"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []types.SecurityCheck{types.SecurityCheckConfig, types.SecurityCheckVulnerability},
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "unknown prefix",
|
||||
spec: spec.Spec{
|
||||
ID: "1234",
|
||||
Title: "NSA",
|
||||
Description: "National Security Agency - Kubernetes Hardening Guidance",
|
||||
RelatedResources: []string{
|
||||
"https://example.com",
|
||||
},
|
||||
Version: "1.0",
|
||||
Controls: []spec.Control{
|
||||
{
|
||||
Name: "Unknown",
|
||||
ID: "1.0",
|
||||
Checks: []spec.SpecCheck{
|
||||
{ID: "UNKNOWN-001"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: assert.Error,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cs := &spec.ComplianceSpec{
|
||||
Spec: tt.spec,
|
||||
}
|
||||
got, err := cs.SecurityChecks()
|
||||
if !tt.wantErr(t, err, fmt.Sprintf("SecurityChecks()")) {
|
||||
return
|
||||
}
|
||||
sort.Strings(got) // for consistency
|
||||
assert.Equalf(t, tt.want, got, "SecurityChecks()")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestComplianceSpec_CheckIDs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
spec spec.Spec
|
||||
want map[types.SecurityCheck][]string
|
||||
}{
|
||||
{
|
||||
name: "get config scanner type by check id prefix",
|
||||
spec: spec.Spec{
|
||||
ID: "1234",
|
||||
Title: "NSA",
|
||||
Description: "National Security Agency - Kubernetes Hardening Guidance",
|
||||
RelatedResources: []string{
|
||||
"https://example.com",
|
||||
},
|
||||
Version: "1.0",
|
||||
Controls: []spec.Control{
|
||||
{
|
||||
Name: "Non-root containers",
|
||||
Description: "Check that container is not running as root",
|
||||
ID: "1.0",
|
||||
Checks: []spec.SpecCheck{
|
||||
{ID: "AVD-KSV012"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Check that encryption resource has been set",
|
||||
Description: "Control checks whether encryption resource has been set",
|
||||
ID: "1.1",
|
||||
Checks: []spec.SpecCheck{
|
||||
{ID: "AVD-1.2.31"},
|
||||
{ID: "AVD-1.2.32"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: map[types.SecurityCheck][]string{
|
||||
types.SecurityCheckConfig: {
|
||||
"AVD-KSV012",
|
||||
"AVD-1.2.31",
|
||||
"AVD-1.2.32",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get config and vuln scanners types by check id prefix",
|
||||
spec: spec.Spec{
|
||||
ID: "1234",
|
||||
Title: "NSA",
|
||||
Description: "National Security Agency - Kubernetes Hardening Guidance",
|
||||
RelatedResources: []string{
|
||||
"https://example.com",
|
||||
},
|
||||
Version: "1.0",
|
||||
Controls: []spec.Control{
|
||||
{
|
||||
Name: "Non-root containers",
|
||||
Description: "Check that container is not running as root",
|
||||
ID: "1.0",
|
||||
Checks: []spec.SpecCheck{
|
||||
{ID: "AVD-KSV012"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Check that encryption resource has been set",
|
||||
Description: "Control checks whether encryption resource has been set",
|
||||
ID: "1.1",
|
||||
Checks: []spec.SpecCheck{
|
||||
{ID: "AVD-1.2.31"},
|
||||
{ID: "AVD-1.2.32"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Ensure no critical vulnerabilities",
|
||||
Description: "Control checks whether critical vulnerabilities are not found",
|
||||
ID: "7.0",
|
||||
Checks: []spec.SpecCheck{
|
||||
{ID: "CVE-9999-9999"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: map[types.SecurityCheck][]string{
|
||||
types.SecurityCheckConfig: {
|
||||
"AVD-KSV012",
|
||||
"AVD-1.2.31",
|
||||
"AVD-1.2.32",
|
||||
},
|
||||
types.SecurityCheckVulnerability: {
|
||||
"CVE-9999-9999",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cs := &spec.ComplianceSpec{
|
||||
Spec: tt.spec,
|
||||
}
|
||||
got := cs.CheckIDs()
|
||||
assert.Equalf(t, tt.want, got, "CheckIDs()")
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package spec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
// GetScannerTypes read spec control and detremine the scanners by check ID prefix
|
||||
func GetScannerTypes(complianceSpec string) ([]types.SecurityCheck, error) {
|
||||
cs := ComplianceSpec{}
|
||||
err := yaml.Unmarshal([]byte(complianceSpec), &cs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scannerTypes := make([]types.SecurityCheck, 0)
|
||||
for _, control := range cs.Spec.Controls {
|
||||
for _, check := range control.Checks {
|
||||
scannerType := scannersByCheckIDPrefix(check.ID)
|
||||
if !slices.Contains(scannerTypes, scannerType) {
|
||||
scannerTypes = append(scannerTypes, scannerType)
|
||||
}
|
||||
}
|
||||
}
|
||||
return scannerTypes, nil
|
||||
}
|
||||
|
||||
// ValidateScanners validate that scanner types are supported
|
||||
func ValidateScanners(controls []Control) error {
|
||||
for _, control := range controls {
|
||||
for _, check := range control.Checks {
|
||||
scannerType := scannersByCheckIDPrefix(check.ID)
|
||||
if !slices.Contains(types.SecurityChecks, scannerType) {
|
||||
return fmt.Errorf("scanner type %v is not supported", scannerType)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ScannerCheckIDs return list of compliance check IDs
|
||||
func ScannerCheckIDs(controls []Control) map[string][]string {
|
||||
scannerChecksMap := make(map[string][]string)
|
||||
for _, control := range controls {
|
||||
for _, check := range control.Checks {
|
||||
scannerType := scannersByCheckIDPrefix(check.ID)
|
||||
if _, ok := scannerChecksMap[scannerType]; !ok {
|
||||
scannerChecksMap[scannerType] = make([]string, 0)
|
||||
}
|
||||
if !slices.Contains(scannerChecksMap[scannerType], check.ID) {
|
||||
scannerChecksMap[scannerType] = append(scannerChecksMap[scannerType], check.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
return scannerChecksMap
|
||||
}
|
||||
|
||||
func scannersByCheckIDPrefix(checkID string) string {
|
||||
switch {
|
||||
case strings.HasPrefix(strings.ToLower(checkID), "cve-") || strings.HasPrefix(strings.ToLower(checkID), "dla-"):
|
||||
return "vuln"
|
||||
case strings.HasPrefix(strings.ToLower(checkID), "avd-"):
|
||||
return "config"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package spec_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/compliance/spec"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func TestGetScannerTypes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
specPath string
|
||||
want []types.SecurityCheck
|
||||
}{
|
||||
{name: "get config scanner type by check id prefix", specPath: "./testdata/spec.yaml", want: []types.SecurityCheck{types.SecurityCheckConfig}},
|
||||
{name: "get config and vuln scanners types by check id prefix", specPath: "./testdata/multi_scanner_spec.yaml", want: []types.SecurityCheck{types.SecurityCheckConfig, types.SecurityCheckVulnerability}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, err := os.ReadFile(tt.specPath)
|
||||
assert.NoError(t, err)
|
||||
got, err := spec.GetScannerTypes(string(b))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, got, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckIDs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
specPath string
|
||||
wantConfig int
|
||||
}{
|
||||
{name: "get map of scannerType:checkIds array", specPath: "./testdata/spec.yaml", wantConfig: 29},
|
||||
{name: "get map of scannerType:checkIds array when dup ids", specPath: "./testdata/spec_dup_id.yaml", wantConfig: 1},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cr, err := ReadSpecFile(tt.specPath)
|
||||
assert.NoError(t, err)
|
||||
got := spec.ScannerCheckIDs(cr.Spec.Controls)
|
||||
assert.Equal(t, len(got["config"]), tt.wantConfig)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateScanners(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
specPath string
|
||||
expectError bool
|
||||
}{
|
||||
//{name: "spec with valid scanner", specPath: "./testdata/spec.yaml", expectError: false},
|
||||
{name: "spec with non valid scanner", specPath: "./testdata/bad_scanner_spec.yaml", expectError: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
compliance, err := ReadSpecFile(tt.specPath)
|
||||
assert.NoError(t, err)
|
||||
err = spec.ValidateScanners(compliance.Spec.Controls)
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func ReadSpecFile(specFilePath string) (*spec.ComplianceSpec, error) {
|
||||
b, err := os.ReadFile(specFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cr := spec.ComplianceSpec{}
|
||||
err = yaml.Unmarshal(b, &cr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cr, nil
|
||||
}
|
||||
@@ -1,60 +1,39 @@
|
||||
package spec
|
||||
|
||||
import (
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
// TrivyCheck represent checks models : vulnerability , misconfiguration and secret
|
||||
type TrivyCheck interface {
|
||||
GetID() string
|
||||
CheckType() string
|
||||
CheckPass() bool
|
||||
}
|
||||
|
||||
// Mapper represent scan checks to spec check ids mapper
|
||||
type Mapper[T TrivyCheck] interface {
|
||||
FilterScanResultsBySpecCheckIds(trivyChecks []T, scannerCheckIDs map[string][]string) []T
|
||||
MapSpecCheckIDToFilteredResults(trivyChecks []TrivyCheck, target string, class types.ResultClass, typeN string, scannerCheckIDs map[string][]string) map[string]types.Results
|
||||
}
|
||||
|
||||
type mapper[T TrivyCheck] struct {
|
||||
}
|
||||
|
||||
// NewMapper instansiate new Mapper for specific scanner type
|
||||
func NewMapper[T TrivyCheck]() Mapper[T] {
|
||||
return &mapper[T]{}
|
||||
}
|
||||
|
||||
// FilterScanResultsBySpecCheckIds create a array of filtered security checks by spec checks ids
|
||||
func (m mapper[T]) FilterScanResultsBySpecCheckIds(trivyChecks []T, scannerCheckIDs map[string][]string) []T {
|
||||
filteredSecurityCheck := make([]T, 0)
|
||||
for _, tc := range trivyChecks {
|
||||
for _, id := range scannerCheckIDs[tc.CheckType()] {
|
||||
if tc.GetID() == id {
|
||||
filteredSecurityCheck = append(filteredSecurityCheck, tc)
|
||||
}
|
||||
}
|
||||
}
|
||||
return filteredSecurityCheck
|
||||
}
|
||||
|
||||
// MapSpecCheckIDToFilteredResults map spec check id to filterred scan results
|
||||
func (m mapper[T]) MapSpecCheckIDToFilteredResults(trivyChecks []TrivyCheck, target string, class types.ResultClass, typeN string, scannerCheckIDs map[string][]string) map[string]types.Results {
|
||||
// MapSpecCheckIDToFilteredResults map spec check id to filtered scan results
|
||||
func MapSpecCheckIDToFilteredResults(result types.Result, checkIDs map[types.SecurityCheck][]string) map[string]types.Results {
|
||||
mapCheckByID := make(map[string]types.Results)
|
||||
for _, tc := range trivyChecks {
|
||||
if _, ok := mapCheckByID[tc.GetID()]; !ok {
|
||||
mapCheckByID[tc.GetID()] = make(types.Results, 0)
|
||||
for _, vuln := range result.Vulnerabilities {
|
||||
// Skip irrelevant check IDs
|
||||
if !slices.Contains(checkIDs[types.SecurityCheckVulnerability], vuln.GetID()) {
|
||||
continue
|
||||
}
|
||||
for _, id := range scannerCheckIDs[tc.CheckType()] {
|
||||
if tc.GetID() == id {
|
||||
switch val := tc.(type) {
|
||||
case types.DetectedMisconfiguration:
|
||||
mapCheckByID[tc.GetID()] = append(mapCheckByID[tc.GetID()], types.Result{Target: target, Class: class, Type: typeN, MisconfSummary: misconfigSummary(val), Misconfigurations: []types.DetectedMisconfiguration{val}})
|
||||
case types.DetectedVulnerability:
|
||||
mapCheckByID[tc.GetID()] = append(mapCheckByID[tc.GetID()], types.Result{Target: target, Class: class, Type: typeN, Vulnerabilities: []types.DetectedVulnerability{val}})
|
||||
}
|
||||
}
|
||||
mapCheckByID[vuln.GetID()] = append(mapCheckByID[vuln.GetID()], types.Result{
|
||||
Target: result.Target,
|
||||
Class: result.Class,
|
||||
Type: result.Type,
|
||||
Vulnerabilities: []types.DetectedVulnerability{vuln},
|
||||
})
|
||||
}
|
||||
for _, m := range result.Misconfigurations {
|
||||
// Skip irrelevant check IDs
|
||||
if !slices.Contains(checkIDs[types.SecurityCheckConfig], m.GetID()) {
|
||||
continue
|
||||
}
|
||||
|
||||
mapCheckByID[m.GetID()] = append(mapCheckByID[m.GetID()], types.Result{
|
||||
Target: result.Target,
|
||||
Class: result.Class,
|
||||
Type: result.Type,
|
||||
MisconfSummary: misconfigSummary(m),
|
||||
Misconfigurations: []types.DetectedMisconfiguration{m},
|
||||
})
|
||||
}
|
||||
return mapCheckByID
|
||||
}
|
||||
@@ -65,66 +44,24 @@ func misconfigSummary(misconfig types.DetectedMisconfiguration) *types.MisconfSu
|
||||
case types.StatusPassed:
|
||||
rms.Successes = 1
|
||||
case types.StatusFailure:
|
||||
rms.Successes = 1
|
||||
rms.Failures = 1
|
||||
case types.StatusException:
|
||||
rms.Exceptions = 1
|
||||
}
|
||||
return &rms
|
||||
}
|
||||
|
||||
// FilterResults filter miconfiguration and vulnerabilities results by spec scanner check Ids
|
||||
func FilterResults(results types.Results, scannerCheckIDs map[string][]string) types.Results {
|
||||
filteredResults := make(types.Results, 0)
|
||||
for _, result := range results {
|
||||
if len(result.Misconfigurations) > 0 {
|
||||
filteredMisconfig := NewMapper[types.DetectedMisconfiguration]().FilterScanResultsBySpecCheckIds(result.Misconfigurations, scannerCheckIDs)
|
||||
result.Misconfigurations = filteredMisconfig
|
||||
}
|
||||
if len(result.Vulnerabilities) > 0 {
|
||||
filteredVuln := NewMapper[types.DetectedVulnerability]().FilterScanResultsBySpecCheckIds(result.Vulnerabilities, scannerCheckIDs)
|
||||
result.Vulnerabilities = filteredVuln
|
||||
}
|
||||
filteredResults = append(filteredResults, result)
|
||||
}
|
||||
return filteredResults
|
||||
}
|
||||
|
||||
// AggregateAllChecksBySpecID aggregate all scan results and map it to spec ids
|
||||
func AggregateAllChecksBySpecID(multiResults []types.Results, controls []Control) map[string]types.Results {
|
||||
scannerCheckIDs := ScannerCheckIDs(controls)
|
||||
// AggregateAllChecksBySpecID aggregates all scan results and map it to spec ids
|
||||
func AggregateAllChecksBySpecID(multiResults []types.Results, cs ComplianceSpec) map[string]types.Results {
|
||||
checkIDs := cs.CheckIDs()
|
||||
complianceArr := make(map[string]types.Results, 0)
|
||||
for _, resResult := range multiResults {
|
||||
filteredResults := FilterResults(resResult, scannerCheckIDs)
|
||||
for _, result := range filteredResults {
|
||||
if len(result.Misconfigurations) > 0 {
|
||||
cMapper := NewMapper[types.DetectedMisconfiguration]()
|
||||
misconfigMap := cMapper.MapSpecCheckIDToFilteredResults(getTrivyChecks(result.Misconfigurations), result.Target, result.Class, result.Type, scannerCheckIDs)
|
||||
for id, checks := range misconfigMap {
|
||||
if _, ok := misconfigMap[id]; !ok {
|
||||
complianceArr[id] = make(types.Results, 0)
|
||||
}
|
||||
complianceArr[id] = append(complianceArr[id], checks...)
|
||||
}
|
||||
}
|
||||
if len(result.Vulnerabilities) > 0 {
|
||||
vMapper := NewMapper[types.DetectedMisconfiguration]()
|
||||
vulnsMap := vMapper.MapSpecCheckIDToFilteredResults(getTrivyChecks(result.Vulnerabilities), result.Target, result.Class, result.Type, scannerCheckIDs)
|
||||
for id, checks := range vulnsMap {
|
||||
if _, ok := vulnsMap[id]; !ok {
|
||||
complianceArr[id] = make(types.Results, 0)
|
||||
}
|
||||
complianceArr[id] = append(complianceArr[id], checks...)
|
||||
}
|
||||
for _, result := range resResult {
|
||||
m := MapSpecCheckIDToFilteredResults(result, checkIDs)
|
||||
for id, checks := range m {
|
||||
complianceArr[id] = append(complianceArr[id], checks...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return complianceArr
|
||||
}
|
||||
|
||||
func getTrivyChecks[T TrivyCheck](checks []T) []TrivyCheck {
|
||||
tc := make([]TrivyCheck, 0)
|
||||
for _, check := range checks {
|
||||
tc = append(tc, check)
|
||||
}
|
||||
return tc
|
||||
}
|
||||
|
||||
@@ -3,83 +3,98 @@ package spec_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/compliance/spec"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/compliance/spec"
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
func TestFilterScanResultsBySpecCheckIds(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
specPath string
|
||||
trivyCheck spec.TrivyCheck
|
||||
wantMapping map[string][]spec.TrivyCheck
|
||||
}{
|
||||
{name: "filter results by check ids for config", specPath: "./testdata/mapping_spec.yaml", trivyCheck: types.DetectedMisconfiguration{AVDID: "KSV012"},
|
||||
wantMapping: map[string][]spec.TrivyCheck{"KSV012": {types.DetectedMisconfiguration{AVDID: "KSV012"}}}},
|
||||
{name: "filter results by check ids for vulns", specPath: "./testdata/mapping_spec.yaml", trivyCheck: types.DetectedVulnerability{VulnerabilityID: "KSV014"},
|
||||
wantMapping: map[string][]spec.TrivyCheck{"KSV014": {types.DetectedVulnerability{VulnerabilityID: "KSV014"}}}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cr, err := ReadSpecFile(tt.specPath)
|
||||
assert.NoError(t, err)
|
||||
scannerIDMap := spec.ScannerCheckIDs(cr.Spec.Controls)
|
||||
m := spec.NewMapper[spec.TrivyCheck]()
|
||||
filteredResults := m.FilterScanResultsBySpecCheckIds([]spec.TrivyCheck{tt.trivyCheck}, scannerIDMap)
|
||||
for _, v := range filteredResults {
|
||||
assert.Equal(t, len(tt.wantMapping[v.GetID()]), 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterResults(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
specPath string
|
||||
results types.Results
|
||||
wantFiltered types.Results
|
||||
}{
|
||||
{name: "filter results by check ids define in spec", specPath: "./testdata/mapping_spec.yaml",
|
||||
results: types.Results{{Misconfigurations: []types.DetectedMisconfiguration{{AVDID: "AVD-KSV012"}, {AVDID: "AVD-KSV017"}}, Vulnerabilities: []types.DetectedVulnerability{{VulnerabilityID: "CVE-KSV014"}}}},
|
||||
wantFiltered: types.Results{{Misconfigurations: []types.DetectedMisconfiguration{{AVDID: "AVD-KSV012"}}, Vulnerabilities: []types.DetectedVulnerability{{VulnerabilityID: "CVE-KSV014"}}}}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cr, err := ReadSpecFile(tt.specPath)
|
||||
assert.NoError(t, err)
|
||||
scannerIDMap := spec.ScannerCheckIDs(cr.Spec.Controls)
|
||||
r := spec.FilterResults(tt.results, scannerIDMap)
|
||||
assert.Equal(t, r, tt.wantFiltered)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapSpecCheckIDtoFilteredResults(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
specPath string
|
||||
trivyCheck spec.TrivyCheck
|
||||
wantMapping map[string]types.Results
|
||||
}{
|
||||
{name: "map Check by ID config", specPath: "./testdata/mapping_spec.yaml", trivyCheck: types.DetectedMisconfiguration{AVDID: "AVD-KSV012"},
|
||||
wantMapping: map[string]types.Results{"AVD-KSV012": {types.Result{Target: "target", MisconfSummary: &types.MisconfSummary{Successes: 0, Failures: 0, Exceptions: 0}, Class: "class", Type: "typeN", Misconfigurations: []types.DetectedMisconfiguration{{AVDID: "AVD-KSV012"}}}}},
|
||||
func TestMapSpecCheckIDToFilteredResults(t *testing.T) {
|
||||
checkIDs := map[types.SecurityCheck][]string{
|
||||
types.SecurityCheckConfig: {
|
||||
"AVD-KSV012",
|
||||
"AVD-1.2.31",
|
||||
"AVD-1.2.32",
|
||||
},
|
||||
types.SecurityCheckVulnerability: {
|
||||
"CVE-9999-9999",
|
||||
},
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
checkIDs map[types.SecurityCheck][]string
|
||||
result types.Result
|
||||
want map[string]types.Results
|
||||
}{
|
||||
{
|
||||
name: "misconfiguration",
|
||||
checkIDs: checkIDs,
|
||||
result: types.Result{
|
||||
Target: "target",
|
||||
Class: types.ClassConfig,
|
||||
Type: ftypes.Kubernetes,
|
||||
Misconfigurations: []types.DetectedMisconfiguration{
|
||||
{AVDID: "AVD-KSV012", Status: types.StatusFailure},
|
||||
{AVDID: "AVD-KSV013", Status: types.StatusFailure},
|
||||
{AVDID: "AVD-1.2.31", Status: types.StatusFailure},
|
||||
},
|
||||
},
|
||||
want: map[string]types.Results{
|
||||
"AVD-KSV012": {
|
||||
{
|
||||
Target: "target",
|
||||
Class: types.ClassConfig,
|
||||
Type: ftypes.Kubernetes,
|
||||
MisconfSummary: &types.MisconfSummary{Successes: 0, Failures: 1, Exceptions: 0},
|
||||
Misconfigurations: []types.DetectedMisconfiguration{
|
||||
{AVDID: "AVD-KSV012", Status: types.StatusFailure},
|
||||
},
|
||||
},
|
||||
},
|
||||
"AVD-1.2.31": {
|
||||
{
|
||||
Target: "target",
|
||||
Class: types.ClassConfig,
|
||||
Type: ftypes.Kubernetes,
|
||||
MisconfSummary: &types.MisconfSummary{Successes: 0, Failures: 1, Exceptions: 0},
|
||||
Misconfigurations: []types.DetectedMisconfiguration{
|
||||
{AVDID: "AVD-1.2.31", Status: types.StatusFailure},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "vulnerability",
|
||||
checkIDs: checkIDs,
|
||||
result: types.Result{
|
||||
Target: "target",
|
||||
Class: types.ClassLangPkg,
|
||||
Type: ftypes.GoModule,
|
||||
Vulnerabilities: []types.DetectedVulnerability{
|
||||
{VulnerabilityID: "CVE-9999-0001"},
|
||||
{VulnerabilityID: "CVE-9999-9999"},
|
||||
},
|
||||
},
|
||||
want: map[string]types.Results{
|
||||
"CVE-9999-9999": {
|
||||
{
|
||||
Target: "target",
|
||||
Class: types.ClassLangPkg,
|
||||
Type: ftypes.GoModule,
|
||||
Vulnerabilities: []types.DetectedVulnerability{
|
||||
{VulnerabilityID: "CVE-9999-9999"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cr, err := ReadSpecFile(tt.specPath)
|
||||
assert.NoError(t, err)
|
||||
scannerIDMap := spec.ScannerCheckIDs(cr.Spec.Controls)
|
||||
m := spec.NewMapper[spec.TrivyCheck]()
|
||||
mapResults := m.MapSpecCheckIDToFilteredResults([]spec.TrivyCheck{tt.trivyCheck}, "target", "class", "typeN", scannerIDMap)
|
||||
for key, val := range tt.wantMapping {
|
||||
assert.Equal(t, mapResults[key], val)
|
||||
}
|
||||
got := spec.MapSpecCheckIDToFilteredResults(tt.result, tt.checkIDs)
|
||||
assert.Equalf(t, tt.want, got, "CheckIDs()")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
spec:
|
||||
id: "1234"
|
||||
title: nsa
|
||||
description: National Security Agency - Kubernetes Hardening Guidance
|
||||
version: "1.0"
|
||||
related_resources :
|
||||
- http://related-resource/
|
||||
controls:
|
||||
- name: Pod and/or namespace Selectors usage
|
||||
description: 'Control check validate the pod and/or namespace Selectors usage'
|
||||
id: '2.0'
|
||||
defaultStatus: 'FAIL'
|
||||
checks:
|
||||
- id: AVD-KSV038
|
||||
severity: 'MEDIUM'
|
||||
- name: Use CNI plugin that supports NetworkPolicy API
|
||||
description: 'Control check whether check cni plugin installed'
|
||||
id: '3.0'
|
||||
checks:
|
||||
- id: 5.3.1
|
||||
severity: 'CRITICAL'
|
||||
File diff suppressed because one or more lines are too long
23
pkg/compliance/spec/testdata/config_spec.yaml
vendored
23
pkg/compliance/spec/testdata/config_spec.yaml
vendored
@@ -1,23 +0,0 @@
|
||||
---
|
||||
spec:
|
||||
id: "1234"
|
||||
title: nsa
|
||||
description: National Security Agency - Kubernetes Hardening Guidance
|
||||
relatedResources :
|
||||
- http://related-resource/
|
||||
version: "1.0"
|
||||
controls:
|
||||
- name: Non-root containers
|
||||
description: 'Check that container is not running as root'
|
||||
id: '1.0'
|
||||
checks:
|
||||
- id: AVD-KSV-0001
|
||||
severity: 'MEDIUM'
|
||||
- name: Immutable container file systems
|
||||
description: 'Check that container root file system is immutable'
|
||||
id: '1.1'
|
||||
checks:
|
||||
- id: AVD-KSV-0003
|
||||
severity: 'LOW'
|
||||
|
||||
|
||||
23
pkg/compliance/spec/testdata/mapping_spec.yaml
vendored
23
pkg/compliance/spec/testdata/mapping_spec.yaml
vendored
@@ -1,23 +0,0 @@
|
||||
---
|
||||
spec:
|
||||
id: "1234"
|
||||
title: nsa
|
||||
description: National Security Agency - Kubernetes Hardening Guidance
|
||||
relatedResources :
|
||||
- http://related-resource/
|
||||
version: "1.0"
|
||||
controls:
|
||||
- name: Non-root containers
|
||||
description: 'Check that container is not running as root'
|
||||
id: '1.0'
|
||||
checks:
|
||||
- id: AVD-KSV012
|
||||
severity: 'MEDIUM'
|
||||
- name: Immutable container file systems
|
||||
description: 'Check that container root file system is immutable'
|
||||
id: '1.1'
|
||||
checks:
|
||||
- id: CVE-KSV014
|
||||
severity: 'LOW'
|
||||
|
||||
|
||||
178
pkg/compliance/spec/testdata/multi_scanner_spec.yaml
vendored
178
pkg/compliance/spec/testdata/multi_scanner_spec.yaml
vendored
@@ -1,178 +0,0 @@
|
||||
---
|
||||
spec:
|
||||
name: "1234"
|
||||
title: nsa
|
||||
description: National Security Agency - Kubernetes Hardening Guidance
|
||||
related_resources :
|
||||
- http://related-resource/
|
||||
version: "1.0"
|
||||
controls:
|
||||
- name: Non-root containers
|
||||
description: 'Check that container is not running as root'
|
||||
id: '1.0'
|
||||
checks:
|
||||
- id: AVD-KSV012
|
||||
severity: 'MEDIUM'
|
||||
- name: Immutable container file systems
|
||||
description: 'Check that container root file system is immutable'
|
||||
id: '1.1'
|
||||
checks:
|
||||
- id: AVD-KSV014
|
||||
severity: 'LOW'
|
||||
- name: Preventing privileged containers
|
||||
description: 'Controls whether Pods can run privileged containers'
|
||||
id: '1.2'
|
||||
checks:
|
||||
- id: AVD-KSV017
|
||||
severity: 'HIGH'
|
||||
- name: Share containers process namespaces
|
||||
description: 'Controls whether containers can share process namespaces'
|
||||
id: '1.3'
|
||||
checks:
|
||||
- id: AVD-KSV008
|
||||
severity: 'HIGH'
|
||||
- name: Share host process namespaces
|
||||
description: 'Controls whether share host process namespaces'
|
||||
id: '1.4'
|
||||
checks:
|
||||
- id: AVD-KSV009
|
||||
severity: 'HIGH'
|
||||
- name: Use the host network
|
||||
description: 'Controls whether containers can use the host network'
|
||||
id: '1.5'
|
||||
checks:
|
||||
- id: AVD-KSV010
|
||||
severity: 'HIGH'
|
||||
- name: Run with root privileges or with root group membership
|
||||
description: 'Controls whether container applications can run with root privileges or with root group membership'
|
||||
id: '1.6'
|
||||
checks:
|
||||
- id: AVD-KSV029
|
||||
severity: 'LOW'
|
||||
- name: Restricts escalation to root privileges
|
||||
description: 'Control check restrictions escalation to root privileges'
|
||||
id: '1.7'
|
||||
checks:
|
||||
- id: AVD-KSV001
|
||||
severity: 'MEDIUM'
|
||||
- name: Sets the SELinux context of the container
|
||||
description: 'Control checks if pod sets the SELinux context of the container'
|
||||
id: '1.8'
|
||||
checks:
|
||||
- id: AVD-KSV002
|
||||
severity: 'MEDIUM'
|
||||
- name: Restrict a container's access to resources with AppArmor
|
||||
description: 'Control checks the restriction of containers access to resources with AppArmor'
|
||||
id: '1.9'
|
||||
checks:
|
||||
- id: AVD-KSV030
|
||||
severity: 'MEDIUM'
|
||||
- name: Sets the seccomp profile used to sandbox containers.
|
||||
description: 'Control checks the sets the seccomp profile used to sandbox containers'
|
||||
id: '1.10'
|
||||
checks:
|
||||
- id: AVD-KSV030
|
||||
severity: 'LOW'
|
||||
- name: Protecting Pod service account tokens
|
||||
description: 'Control check whether disable secret token been mount ,automountServiceAccountToken: false'
|
||||
id: '1.11'
|
||||
checks:
|
||||
- id: AVD-KSV036
|
||||
severity: 'MEDIUM'
|
||||
- name: Namespace kube-system should not be used by users
|
||||
description: 'Control check whether Namespace kube-system is not be used by users'
|
||||
id: '1.12'
|
||||
defaultStatus: 'FAIL'
|
||||
checks:
|
||||
- id: AVD-KSV037
|
||||
severity: 'MEDIUM'
|
||||
- name: Pod and/or namespace Selectors usage
|
||||
description: 'Control check validate the pod and/or namespace Selectors usage'
|
||||
id: '2.0'
|
||||
defaultStatus: 'FAIL'
|
||||
checks:
|
||||
- id: AVD-KSV038
|
||||
severity: 'MEDIUM'
|
||||
- name: Use CNI plugin that supports NetworkPolicy API
|
||||
description: 'Control check whether check cni plugin installed'
|
||||
id: '3.0'
|
||||
checks:
|
||||
- id: AVD-5.3.1
|
||||
severity: 'CRITICAL'
|
||||
- name: Use ResourceQuota policies to limit resources
|
||||
description: 'Control check the use of ResourceQuota policy to limit aggregate resource usage within namespace'
|
||||
id: '4.0'
|
||||
defaultStatus: 'FAIL'
|
||||
checks:
|
||||
- id: "AVD-KSV040"
|
||||
severity: 'MEDIUM'
|
||||
- name: Use LimitRange policies to limit resources
|
||||
description: 'Control check the use of LimitRange policy limit resource usage for namespaces or nodes'
|
||||
id: '4.1'
|
||||
defaultStatus: 'FAIL'
|
||||
checks:
|
||||
- id: "AVD-KSV039"
|
||||
severity: 'MEDIUM'
|
||||
- name: Control plan disable insecure port
|
||||
description: 'Control check whether control plan disable insecure port'
|
||||
id: '5.0'
|
||||
checks:
|
||||
- id: AVD-1.2.19
|
||||
severity: 'CRITICAL'
|
||||
- name: Encrypt etcd communication
|
||||
description: 'Control check whether etcd communication is encrypted'
|
||||
id: '5.1'
|
||||
checks:
|
||||
- id: CVE-2.1
|
||||
severity: 'CRITICAL'
|
||||
- name: Ensure kube config file permission
|
||||
description: 'Control check whether kube config file permissions'
|
||||
id: '6.0'
|
||||
checks:
|
||||
- id: AVD-4.1.3
|
||||
- id: AVD-4.1.4
|
||||
severity: 'CRITICAL'
|
||||
- name: Check that encryption resource has been set
|
||||
description: 'Control checks whether encryption resource has been set'
|
||||
id: '6.1'
|
||||
checks:
|
||||
- id: AVD-1.2.31
|
||||
- id: AVD-1.2.32
|
||||
severity: 'CRITICAL'
|
||||
- name: Check encryption provider
|
||||
description: 'Control checks whether encryption provider has been set'
|
||||
id: '6.2'
|
||||
checks:
|
||||
- id: AVD-1.2.3
|
||||
severity: 'CRITICAL'
|
||||
- name: Make sure anonymous-auth is unset
|
||||
description: 'Control checks whether anonymous-auth is unset'
|
||||
id: '7.0'
|
||||
checks:
|
||||
- id: AVD-1.2.1
|
||||
severity: 'CRITICAL'
|
||||
- name: Make sure -authorization-mode=RBAC
|
||||
description: 'Control check whether RBAC permission is in use'
|
||||
id: '7.1'
|
||||
checks:
|
||||
- id: AVD-1.2.7
|
||||
- id: AVD-1.2.8
|
||||
severity: 'CRITICAL'
|
||||
- name: Audit policy is configure
|
||||
description: 'Control check whether audit policy is configure'
|
||||
id: '8.0'
|
||||
checks:
|
||||
- id: AVD-3.2.1
|
||||
severity: 'HIGH'
|
||||
- name: Audit log path is configure
|
||||
description: 'Control check whether audit log path is configure'
|
||||
id: '8.1'
|
||||
checks:
|
||||
- id: AVD-1.2.22
|
||||
severity: 'MEDIUM'
|
||||
- name: Audit log aging
|
||||
description: 'Control check whether audit log aging is configure'
|
||||
id: '8.2'
|
||||
checks:
|
||||
- id: AVD-1.2.23
|
||||
severity: 'MEDIUM'
|
||||
File diff suppressed because one or more lines are too long
178
pkg/compliance/spec/testdata/spec.yaml
vendored
178
pkg/compliance/spec/testdata/spec.yaml
vendored
@@ -1,178 +0,0 @@
|
||||
---
|
||||
spec:
|
||||
id: "1234"
|
||||
title: nsa
|
||||
description: National Security Agency - Kubernetes Hardening Guidance
|
||||
related_resources :
|
||||
- http://related-resource/
|
||||
version: "1.0"
|
||||
controls:
|
||||
- name: Non-root containers
|
||||
description: 'Check that container is not running as root'
|
||||
id: '1.0'
|
||||
checks:
|
||||
- id: AVD-KSV012
|
||||
severity: 'MEDIUM'
|
||||
- name: Immutable container file systems
|
||||
description: 'Check that container root file system is immutable'
|
||||
id: '1.1'
|
||||
checks:
|
||||
- id: AVD-KSV014
|
||||
severity: 'LOW'
|
||||
- name: Preventing privileged containers
|
||||
description: 'Controls whether Pods can run privileged containers'
|
||||
id: '1.2'
|
||||
checks:
|
||||
- id: AVD-KSV017
|
||||
severity: 'HIGH'
|
||||
- name: Share containers process namespaces
|
||||
description: 'Controls whether containers can share process namespaces'
|
||||
id: '1.3'
|
||||
checks:
|
||||
- id: AVD-KSV008
|
||||
severity: 'HIGH'
|
||||
- name: Share host process namespaces
|
||||
description: 'Controls whether share host process namespaces'
|
||||
id: '1.4'
|
||||
checks:
|
||||
- id: AVD-KSV009
|
||||
severity: 'HIGH'
|
||||
- name: Use the host network
|
||||
description: 'Controls whether containers can use the host network'
|
||||
id: '1.5'
|
||||
checks:
|
||||
- id: AVD-KSV010
|
||||
severity: 'HIGH'
|
||||
- name: Run with root privileges or with root group membership
|
||||
description: 'Controls whether container applications can run with root privileges or with root group membership'
|
||||
id: '1.6'
|
||||
checks:
|
||||
- id: AVD-KSV029
|
||||
severity: 'LOW'
|
||||
- name: Restricts escalation to root privileges
|
||||
description: 'Control check restrictions escalation to root privileges'
|
||||
id: '1.7'
|
||||
checks:
|
||||
- id: AVD-KSV001
|
||||
severity: 'MEDIUM'
|
||||
- name: Sets the SELinux context of the container
|
||||
description: 'Control checks if pod sets the SELinux context of the container'
|
||||
id: '1.8'
|
||||
checks:
|
||||
- id: AVD-KSV002
|
||||
severity: 'MEDIUM'
|
||||
- name: Restrict a container's access to resources with AppArmor
|
||||
description: 'Control checks the restriction of containers access to resources with AppArmor'
|
||||
id: '1.9'
|
||||
checks:
|
||||
- id: AVD-KSV030
|
||||
severity: 'MEDIUM'
|
||||
- name: Sets the seccomp profile used to sandbox containers.
|
||||
description: 'Control checks the sets the seccomp profile used to sandbox containers'
|
||||
id: '1.10'
|
||||
checks:
|
||||
- id: AVD-KSV030
|
||||
severity: 'LOW'
|
||||
- name: Protecting Pod service account tokens
|
||||
description: 'Control check whether disable secret token been mount ,automountServiceAccountToken: false'
|
||||
id: '1.11'
|
||||
checks:
|
||||
- id: AVD-KSV036
|
||||
severity: 'MEDIUM'
|
||||
- name: Namespace kube-system should not be used by users
|
||||
description: 'Control check whether Namespace kube-system is not be used by users'
|
||||
id: '1.12'
|
||||
defaultStatus: 'FAIL'
|
||||
checks:
|
||||
- id: AVD-KSV037
|
||||
severity: 'MEDIUM'
|
||||
- name: Pod and/or namespace Selectors usage
|
||||
description: 'Control check validate the pod and/or namespace Selectors usage'
|
||||
id: '2.0'
|
||||
defaultStatus: 'FAIL'
|
||||
checks:
|
||||
- id: AVD-KSV038
|
||||
severity: 'MEDIUM'
|
||||
- name: Use CNI plugin that supports NetworkPolicy API
|
||||
description: 'Control check whether check cni plugin installed'
|
||||
id: '3.0'
|
||||
checks:
|
||||
- id: AVD-5.3.1
|
||||
severity: 'CRITICAL'
|
||||
- name: Use ResourceQuota policies to limit resources
|
||||
description: 'Control check the use of ResourceQuota policy to limit aggregate resource usage within namespace'
|
||||
id: '4.0'
|
||||
defaultStatus: 'FAIL'
|
||||
checks:
|
||||
- id: "AVD-KSV040"
|
||||
severity: 'MEDIUM'
|
||||
- name: Use LimitRange policies to limit resources
|
||||
description: 'Control check the use of LimitRange policy limit resource usage for namespaces or nodes'
|
||||
id: '4.1'
|
||||
defaultStatus: 'FAIL'
|
||||
checks:
|
||||
- id: "AVD-KSV039"
|
||||
severity: 'MEDIUM'
|
||||
- name: Control plan disable insecure port
|
||||
description: 'Control check whether control plan disable insecure port'
|
||||
id: '5.0'
|
||||
checks:
|
||||
- id: AVD-1.2.19
|
||||
severity: 'CRITICAL'
|
||||
- name: Encrypt etcd communication
|
||||
description: 'Control check whether etcd communication is encrypted'
|
||||
id: '5.1'
|
||||
checks:
|
||||
- id: 'AVD-2.1'
|
||||
severity: 'CRITICAL'
|
||||
- name: Ensure kube config file permission
|
||||
description: 'Control check whether kube config file permissions'
|
||||
id: '6.0'
|
||||
checks:
|
||||
- id: AVD-4.1.3
|
||||
- id: AVD-4.1.4
|
||||
severity: 'CRITICAL'
|
||||
- name: Check that encryption resource has been set
|
||||
description: 'Control checks whether encryption resource has been set'
|
||||
id: '6.1'
|
||||
checks:
|
||||
- id: AVD-1.2.31
|
||||
- id: AVD-1.2.32
|
||||
severity: 'CRITICAL'
|
||||
- name: Check encryption provider
|
||||
description: 'Control checks whether encryption provider has been set'
|
||||
id: '6.2'
|
||||
checks:
|
||||
- id: AVD-1.2.3
|
||||
severity: 'CRITICAL'
|
||||
- name: Make sure anonymous-auth is unset
|
||||
description: 'Control checks whether anonymous-auth is unset'
|
||||
id: '7.0'
|
||||
checks:
|
||||
- id: AVD-1.2.1
|
||||
severity: 'CRITICAL'
|
||||
- name: Make sure -authorization-mode=RBAC
|
||||
description: 'Control check whether RBAC permission is in use'
|
||||
id: '7.1'
|
||||
checks:
|
||||
- id: AVD-1.2.7
|
||||
- id: AVD-1.2.8
|
||||
severity: 'CRITICAL'
|
||||
- name: Audit policy is configure
|
||||
description: 'Control check whether audit policy is configure'
|
||||
id: '8.0'
|
||||
checks:
|
||||
- id: AVD-3.2.1
|
||||
severity: 'HIGH'
|
||||
- name: Audit log path is configure
|
||||
description: 'Control check whether audit log path is configure'
|
||||
id: '8.1'
|
||||
checks:
|
||||
- id: AVD-1.2.22
|
||||
severity: 'MEDIUM'
|
||||
- name: Audit log aging
|
||||
description: 'Control check whether audit log aging is configure'
|
||||
id: '8.2'
|
||||
checks:
|
||||
- id: AVD-1.2.23
|
||||
severity: 'MEDIUM'
|
||||
22
pkg/compliance/spec/testdata/spec_dup_id.yaml
vendored
22
pkg/compliance/spec/testdata/spec_dup_id.yaml
vendored
@@ -1,22 +0,0 @@
|
||||
---
|
||||
spec:
|
||||
id: "1234"
|
||||
title: nsa
|
||||
description: National Security Agency - Kubernetes Hardening Guidance
|
||||
related_resources :
|
||||
- http://related-resource/
|
||||
version: "1.0"
|
||||
controls:
|
||||
- name: Non-root containers
|
||||
description: 'Check that container is not running as root'
|
||||
id: '1.0'
|
||||
checks:
|
||||
- id: AVD-KSV012
|
||||
severity: 'MEDIUM'
|
||||
- name: Immutable container file systems
|
||||
description: 'Check that container root file system is immutable'
|
||||
id: '1.1'
|
||||
checks:
|
||||
- id: AVD-KSV012
|
||||
severity: 'LOW'
|
||||
|
||||
@@ -5,11 +5,8 @@ import (
|
||||
"errors"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/flag"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
sp "github.com/aquasecurity/defsec/pkg/spec"
|
||||
"github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
|
||||
@@ -17,9 +14,11 @@ import (
|
||||
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
|
||||
cr "github.com/aquasecurity/trivy/pkg/compliance/report"
|
||||
"github.com/aquasecurity/trivy/pkg/compliance/spec"
|
||||
"github.com/aquasecurity/trivy/pkg/flag"
|
||||
"github.com/aquasecurity/trivy/pkg/k8s/report"
|
||||
"github.com/aquasecurity/trivy/pkg/k8s/scanner"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -81,15 +80,20 @@ func (r *runner) run(ctx context.Context, artifacts []*artifacts.Artifact) error
|
||||
}()
|
||||
|
||||
s := scanner.NewScanner(r.cluster, runner, r.flagOpts)
|
||||
var complianceSpec string
|
||||
|
||||
var complianceSpec spec.ComplianceSpec
|
||||
// set scanners types by spec
|
||||
if len(r.flagOpts.ReportOptions.Compliance) > 0 {
|
||||
complianceSpec = sp.NewSpecLoader().GetSpecByName(r.flagOpts.ReportOptions.Compliance)
|
||||
scannerTypes, err := spec.GetScannerTypes(complianceSpec)
|
||||
if err != nil {
|
||||
return err
|
||||
if r.flagOpts.ReportOptions.Compliance != "" {
|
||||
cs := sp.NewSpecLoader().GetSpecByName(r.flagOpts.ReportOptions.Compliance)
|
||||
if err = yaml.Unmarshal([]byte(cs), &complianceSpec); err != nil {
|
||||
return xerrors.Errorf("yaml unmarshal error: %w", err)
|
||||
}
|
||||
r.flagOpts.ScanOptions.SecurityChecks = scannerTypes
|
||||
|
||||
securityChecks, err := complianceSpec.SecurityChecks()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("security check error: %w", err)
|
||||
}
|
||||
r.flagOpts.ScanOptions.SecurityChecks = securityChecks
|
||||
}
|
||||
|
||||
rpt, err := s.Scan(ctx, artifacts)
|
||||
@@ -98,17 +102,16 @@ func (r *runner) run(ctx context.Context, artifacts []*artifacts.Artifact) error
|
||||
}
|
||||
|
||||
if len(r.flagOpts.ReportOptions.Compliance) > 0 {
|
||||
scanResults := make([]types.Results, 0)
|
||||
|
||||
for _, rss := range rpt.Misconfigurations {
|
||||
var scanResults []types.Results
|
||||
for _, rss := range rpt.Vulnerabilities {
|
||||
scanResults = append(scanResults, rss.Results)
|
||||
}
|
||||
for _, rss := range rpt.Vulnerabilities {
|
||||
for _, rss := range rpt.Misconfigurations {
|
||||
scanResults = append(scanResults, rss.Results)
|
||||
}
|
||||
complianceReport, err := cr.BuildComplianceReport(scanResults, complianceSpec)
|
||||
if err != nil {
|
||||
return err
|
||||
return xerrors.Errorf("compliance report build error: %w", err)
|
||||
}
|
||||
return cr.Write(complianceReport, cr.Option{
|
||||
Format: r.flagOpts.Format,
|
||||
|
||||
@@ -39,16 +39,6 @@ const (
|
||||
)
|
||||
|
||||
// GetID retrun misconfig ID
|
||||
func (mc DetectedMisconfiguration) GetID() string {
|
||||
func (mc *DetectedMisconfiguration) GetID() string {
|
||||
return mc.AVDID
|
||||
}
|
||||
|
||||
// CheckType retrun misconfig check type
|
||||
func (mc DetectedMisconfiguration) CheckType() string {
|
||||
return "config"
|
||||
}
|
||||
|
||||
// CheckType retrun misconfig check pass
|
||||
func (mc DetectedMisconfiguration) CheckPass() bool {
|
||||
return mc.Status == StatusPassed
|
||||
}
|
||||
|
||||
@@ -29,6 +29,11 @@ type DetectedVulnerability struct {
|
||||
types.Vulnerability
|
||||
}
|
||||
|
||||
// GetID retrun Vulnerability ID
|
||||
func (vuln *DetectedVulnerability) GetID() string {
|
||||
return vuln.VulnerabilityID
|
||||
}
|
||||
|
||||
// BySeverity implements sort.Interface based on the Severity field.
|
||||
type BySeverity []DetectedVulnerability
|
||||
|
||||
@@ -53,18 +58,3 @@ func (v BySeverity) Less(i, j int) bool {
|
||||
|
||||
// Swap swaps 2 vulnerability
|
||||
func (v BySeverity) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
|
||||
|
||||
// GetID retrun Vulnerability ID
|
||||
func (vuln DetectedVulnerability) GetID() string {
|
||||
return vuln.VulnerabilityID
|
||||
}
|
||||
|
||||
// CheckType retrun vulnerabilies check type
|
||||
func (mc DetectedVulnerability) CheckType() string {
|
||||
return "vuln"
|
||||
}
|
||||
|
||||
// CheckType retrun vulnerabilies check pass
|
||||
func (mc DetectedVulnerability) CheckPass() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user