mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-23 07:29:00 -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/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/neurosnap/sentences.v1 v1.0.6 // indirect
|
gopkg.in/neurosnap/sentences.v1 v1.0.6 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // 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 v2.2.0+incompatible
|
||||||
gotest.tools/v3 v3.2.0 // indirect
|
gotest.tools/v3 v3.2.0 // indirect
|
||||||
helm.sh/helm/v3 v3.10.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 output []byte
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
var v interface{}
|
||||||
switch jw.Report {
|
switch jw.Report {
|
||||||
case allReport:
|
case allReport:
|
||||||
output, err = json.MarshalIndent(report, "", " ")
|
v = report
|
||||||
case summaryReport:
|
case summaryReport:
|
||||||
output, err = json.MarshalIndent(BuildSummary(report), "", " ")
|
v = BuildSummary(report)
|
||||||
default:
|
default:
|
||||||
return xerrors.Errorf(`report %q not supported. Use "summary" or "all"`, jw.Report)
|
return xerrors.Errorf(`report %q not supported. Use "summary" or "all"`, jw.Report)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output, err = json.MarshalIndent(v, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("failed to marshal json: %w", err)
|
return xerrors.Errorf("failed to marshal json: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,82 @@
|
|||||||
package report
|
package report_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"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/aquasecurity/trivy/pkg/types"
|
||||||
"github.com/stretchr/testify/assert"
|
"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 {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
specPath string
|
reportType string
|
||||||
resultPath string
|
input *report.ComplianceReport
|
||||||
reportType string
|
want string
|
||||||
wantJsonReportPath 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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
specfile, err := os.ReadFile(tt.specPath)
|
buf := new(bytes.Buffer)
|
||||||
assert.NoError(t, err)
|
tr := report.JSONWriter{Report: tt.reportType, Output: buf}
|
||||||
var res types.Results
|
err := tr.Write(tt.input)
|
||||||
resultByte, err := os.ReadFile(tt.resultPath)
|
require.NoError(t, err)
|
||||||
err = json.Unmarshal(resultByte, &res)
|
|
||||||
assert.NoError(t, err)
|
want, err := os.ReadFile(tt.want)
|
||||||
complianceResults, err := BuildComplianceReport([]types.Results{res}, string(specfile))
|
require.NoError(t, err)
|
||||||
assert.NoError(t, err)
|
|
||||||
ioWriter := new(bytes.Buffer)
|
assert.JSONEq(t, string(want), buf.String())
|
||||||
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))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
|
|
||||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
"github.com/aquasecurity/trivy/pkg/compliance/spec"
|
"github.com/aquasecurity/trivy/pkg/compliance/spec"
|
||||||
@@ -29,37 +28,37 @@ type Option struct {
|
|||||||
|
|
||||||
// ComplianceReport represents a kubernetes scan report
|
// ComplianceReport represents a kubernetes scan report
|
||||||
type ComplianceReport struct {
|
type ComplianceReport struct {
|
||||||
ID string `json:"id"`
|
ID string
|
||||||
Title string `json:"title"`
|
Title string
|
||||||
Description string `json:"description"`
|
Description string
|
||||||
Version string `json:"severity"`
|
Version string
|
||||||
RelatedResources []string `json:"relatedResources"`
|
RelatedResources []string
|
||||||
Results []*ControlCheckResult `json:"results"`
|
Results []*ControlCheckResult
|
||||||
}
|
}
|
||||||
|
|
||||||
type ControlCheckResult struct {
|
type ControlCheckResult struct {
|
||||||
ControlCheckID string `json:"id"`
|
ID string
|
||||||
ControlName string `json:"name"`
|
Name string
|
||||||
ControlDescription string `json:"description"`
|
Description string
|
||||||
DefaultStatus spec.ControlStatus `json:"defaultStatus,omitempty"`
|
DefaultStatus spec.ControlStatus `json:",omitempty"`
|
||||||
ControlSeverity string `json:"severity"`
|
Severity string
|
||||||
Results types.Results `json:"results"`
|
Results types.Results
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConsolidatedReport represents a kubernetes scan report with consolidated findings
|
// SummaryReport represents a kubernetes scan report with consolidated findings
|
||||||
type SummaryReport struct {
|
type SummaryReport struct {
|
||||||
SchemaVersion int `json:",omitempty"`
|
SchemaVersion int `json:",omitempty"`
|
||||||
ReportID string
|
ID string
|
||||||
ReportTitle string
|
Title string
|
||||||
SummaryControls []ControlCheckSummary `json:",omitempty"`
|
SummaryControls []ControlCheckSummary `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ControlCheckSummary struct {
|
type ControlCheckSummary struct {
|
||||||
ControlCheckID string `json:"id"`
|
ID string
|
||||||
ControlName string `json:"name"`
|
Name string
|
||||||
ControlSeverity string `json:"severity"`
|
Severity string
|
||||||
TotalPass float32 `json:"totalPass"`
|
TotalPass float32
|
||||||
TotalFail float32 `json:"totalFail"`
|
TotalFail float32
|
||||||
}
|
}
|
||||||
|
|
||||||
// Writer defines the result write operation
|
// 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 {
|
func buildControlCheckResults(checksMap map[string]types.Results, controls []spec.Control) []*ControlCheckResult {
|
||||||
complianceResults := make([]*ControlCheckResult, 0)
|
complianceResults := make([]*ControlCheckResult, 0)
|
||||||
for _, control := range controls {
|
for _, control := range controls {
|
||||||
cr := ControlCheckResult{}
|
var results types.Results
|
||||||
cr.ControlName = control.Name
|
|
||||||
cr.ControlCheckID = control.ID
|
|
||||||
cr.ControlDescription = control.Description
|
|
||||||
cr.ControlSeverity = string(control.Severity)
|
|
||||||
cr.DefaultStatus = control.DefaultStatus
|
|
||||||
for _, c := range control.Checks {
|
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
|
return complianceResults
|
||||||
}
|
}
|
||||||
@@ -126,20 +127,9 @@ func buildComplianceReportResults(checksMap map[string]types.Results, spec spec.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildComplianceReport(scanResults []types.Results, complianceSpec string) (*ComplianceReport, error) {
|
func BuildComplianceReport(scanResults []types.Results, cs spec.ComplianceSpec) (*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
|
|
||||||
}
|
|
||||||
// aggregate checks by ID
|
// aggregate checks by ID
|
||||||
aggregateChecksByID := spec.AggregateAllChecksBySpecID(scanResults, cs.Spec.Controls)
|
aggregateChecksByID := spec.AggregateAllChecksBySpecID(scanResults, cs)
|
||||||
|
|
||||||
// build compliance report results
|
// build compliance report results
|
||||||
return buildComplianceReportResults(aggregateChecksByID, cs.Spec), nil
|
return buildComplianceReportResults(aggregateChecksByID, cs.Spec), nil
|
||||||
|
|||||||
@@ -1,84 +1,242 @@
|
|||||||
package report
|
package report_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"fmt"
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"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 {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
specPath string
|
args args
|
||||||
resultPath string
|
want *report.ComplianceReport
|
||||||
Option Option
|
wantErr assert.ErrorAssertionFunc
|
||||||
wantSummaryReportPath string
|
|
||||||
expectError bool
|
|
||||||
}{
|
}{
|
||||||
{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: "happy",
|
||||||
{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"},
|
args: args{
|
||||||
{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"},
|
scanResults: []types.Results{
|
||||||
{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},
|
{
|
||||||
|
{
|
||||||
|
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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
specfile, err := os.ReadFile(tt.specPath)
|
got, err := report.BuildComplianceReport(tt.args.scanResults, tt.args.cs)
|
||||||
assert.NoError(t, err)
|
if !tt.wantErr(t, err, fmt.Sprintf("BuildComplianceReport(%v, %v)", tt.args.scanResults, tt.args.cs)) {
|
||||||
var res types.Results
|
return
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
})
|
assert.Equalf(t, tt.want, got, "BuildComplianceReport(%v, %v)", tt.args.scanResults, tt.args.cs)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,17 @@ import (
|
|||||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
"github.com/aquasecurity/trivy/pkg/compliance/spec"
|
"github.com/aquasecurity/trivy/pkg/compliance/spec"
|
||||||
pkgReport "github.com/aquasecurity/trivy/pkg/report/table"
|
pkgReport "github.com/aquasecurity/trivy/pkg/report/table"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func BuildSummary(cr *ComplianceReport) *SummaryReport {
|
func BuildSummary(cr *ComplianceReport) *SummaryReport {
|
||||||
ccma := make([]ControlCheckSummary, 0)
|
var ccma []ControlCheckSummary
|
||||||
for _, control := range cr.Results {
|
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 len(control.Results) == 0 { // this validation is mainly for vuln type
|
||||||
if control.DefaultStatus == spec.PassStatus {
|
if control.DefaultStatus == spec.PassStatus {
|
||||||
ccm.TotalPass = 1
|
ccm.TotalPass = 1
|
||||||
@@ -25,27 +30,23 @@ func BuildSummary(cr *ComplianceReport) *SummaryReport {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, check := range control.Results {
|
for _, check := range control.Results {
|
||||||
for _, cr := range check.Misconfigurations {
|
for _, m := range check.Misconfigurations {
|
||||||
if cr.CheckPass() {
|
if m.Status == types.StatusPassed {
|
||||||
ccm.TotalPass++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ccm.TotalFail++
|
|
||||||
}
|
|
||||||
for _, cr := range check.Vulnerabilities {
|
|
||||||
if cr.CheckPass() {
|
|
||||||
ccm.TotalPass++
|
ccm.TotalPass++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ccm.TotalFail++
|
ccm.TotalFail++
|
||||||
}
|
}
|
||||||
|
// Detected vulnerabilities are always failure.
|
||||||
|
ccm.TotalFail += float32(len(check.Vulnerabilities))
|
||||||
}
|
}
|
||||||
ccma = append(ccma, ccm)
|
ccma = append(ccma, ccm)
|
||||||
|
|
||||||
}
|
}
|
||||||
return &SummaryReport{ReportID: cr.ID,
|
return &SummaryReport{
|
||||||
ReportTitle: cr.Title,
|
ID: cr.ID,
|
||||||
SummaryControls: ccma}
|
Title: cr.Title,
|
||||||
|
SummaryControls: ccma,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type SummaryWriter struct {
|
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
|
// Write writes the results in a summarized table format
|
||||||
func (s SummaryWriter) Write(report *ComplianceReport) error {
|
func (s SummaryWriter) Write(report *ComplianceReport) error {
|
||||||
|
|
||||||
if _, err := fmt.Fprintln(s.Output); err != nil {
|
if _, err := fmt.Fprintln(s.Output); err != nil {
|
||||||
return xerrors.Errorf("failed to write summary report: %w", err)
|
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 {
|
func (s SummaryWriter) generateSummary(summaryControls ControlCheckSummary) []string {
|
||||||
percentage := calculatePercentage(summaryControls.TotalFail, summaryControls.TotalPass)
|
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 {
|
func calculatePercentage(totalFail float32, totalPass float32) string {
|
||||||
|
|||||||
@@ -1,62 +1,158 @@
|
|||||||
package report
|
package report_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy/pkg/compliance/report"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuildSummary(t *testing.T) {
|
func TestBuildSummary(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
specPath string
|
reportType string
|
||||||
resultPath string
|
input *report.ComplianceReport
|
||||||
wantSummaryReportPath string
|
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"}}
|
name: "build report summary config only",
|
||||||
|
reportType: "summary",
|
||||||
for _, tt := range tests {
|
input: &report.ComplianceReport{
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
ID: "1234",
|
||||||
specfile, err := os.ReadFile(tt.specPath)
|
Title: "NSA",
|
||||||
assert.NoError(t, err)
|
RelatedResources: []string{"https://example.com"},
|
||||||
var res types.Results
|
Results: []*report.ControlCheckResult{
|
||||||
c, err := os.ReadFile(tt.resultPath)
|
{
|
||||||
err = json.Unmarshal(c, &res)
|
ID: "1.0",
|
||||||
assert.NoError(t, err)
|
Name: "Non-root containers",
|
||||||
complianceResults, err := BuildComplianceReport([]types.Results{res}, string(specfile))
|
Severity: "MEDIUM",
|
||||||
tk := BuildSummary(complianceResults)
|
Results: types.Results{
|
||||||
o, err := json.Marshal(tk)
|
{
|
||||||
assert.NoError(t, err)
|
Misconfigurations: []types.DetectedMisconfiguration{
|
||||||
r, err := os.ReadFile(tt.wantSummaryReportPath)
|
{AVDID: "AVD-KSV012", Status: types.StatusFailure},
|
||||||
assert.NoError(t, err)
|
},
|
||||||
assert.Equal(t, strings.TrimSpace(string(o)), string(r))
|
},
|
||||||
|
},
|
||||||
})
|
},
|
||||||
}
|
{
|
||||||
}
|
ID: "1.1",
|
||||||
|
Name: "Immutable container file systems",
|
||||||
func TestCalculatePercentage(t *testing.T) {
|
Severity: "LOW",
|
||||||
tests := []struct {
|
Results: types.Results{
|
||||||
name string
|
{
|
||||||
pass float32
|
Misconfigurations: []types.DetectedMisconfiguration{
|
||||||
fail float32
|
{AVDID: "AVD-KSV013", Status: types.StatusFailure},
|
||||||
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%"},
|
},
|
||||||
|
},
|
||||||
|
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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got := calculatePercentage(tt.fail, tt.pass)
|
got := report.BuildSummary(tt.input)
|
||||||
assert.Equal(t, got, tt.want)
|
assert.Equal(t, tt.want, got)
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const (
|
|||||||
ComplianceColumn = "Compliance"
|
ComplianceColumn = "Compliance"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (tw TableWriter) Columns() []string {
|
func (tw TableWriter) columns() []string {
|
||||||
return []string{ControlIDColumn, SeverityColumn, ControlNameColumn, ComplianceColumn}
|
return []string{ControlIDColumn, SeverityColumn, ControlNameColumn, ComplianceColumn}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ func (tw TableWriter) Write(report *ComplianceReport) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case summaryReport:
|
case summaryReport:
|
||||||
writer := NewSummaryWriter(tw.Output, tw.Severities, tw.Columns())
|
writer := NewSummaryWriter(tw.Output, tw.Severities, tw.columns())
|
||||||
return writer.Write(report)
|
return writer.Write(report)
|
||||||
default:
|
default:
|
||||||
return xerrors.Errorf(`report %q not supported. Use "summary" or "all"`, tw.Report)
|
return xerrors.Errorf(`report %q not supported. Use "summary" or "all"`, tw.Report)
|
||||||
|
|||||||
@@ -1,52 +1,75 @@
|
|||||||
package report
|
package report_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"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 {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
specPath string
|
reportType string
|
||||||
resultPath string
|
input *report.ComplianceReport
|
||||||
reportType string
|
want string
|
||||||
wantSummaryReportPath 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 summary table",
|
||||||
{name: "build table report config only", specPath: "./testdata/config_spec.yaml", reportType: "all", resultPath: "./testdata/results_config.json", wantSummaryReportPath: "./testdata/table.txt"},
|
reportType: "summary",
|
||||||
{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"},
|
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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
specfile, err := os.ReadFile(tt.specPath)
|
buf := new(bytes.Buffer)
|
||||||
assert.NoError(t, err)
|
tr := report.TableWriter{Report: tt.reportType, Output: buf}
|
||||||
var res types.Results
|
err := tr.Write(tt.input)
|
||||||
resultByte, err := os.ReadFile(tt.resultPath)
|
require.NoError(t, err)
|
||||||
err = json.Unmarshal(resultByte, &res)
|
|
||||||
assert.NoError(t, err)
|
want, err := os.ReadFile(tt.want)
|
||||||
complianceResults, err := BuildComplianceReport([]types.Results{res}, string(specfile))
|
require.NoError(t, err)
|
||||||
ioWriter := new(bytes.Buffer)
|
|
||||||
tr := TableWriter{Report: tt.reportType, Output: ioWriter}
|
assert.Equal(t, string(want), buf.String())
|
||||||
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))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 │
|
│ 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
|
package spec
|
||||||
|
|
||||||
type Severity string
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
const (
|
"golang.org/x/exp/maps"
|
||||||
SeverityCritical Severity = "CRITICAL"
|
"golang.org/x/xerrors"
|
||||||
SeverityHigh Severity = "HIGH"
|
|
||||||
SeverityMedium Severity = "MEDIUM"
|
|
||||||
SeverityLow Severity = "LOW"
|
|
||||||
|
|
||||||
SeverityNone Severity = "NONE"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
SeverityUnknown Severity = "UNKNOWN"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Severity string
|
||||||
|
|
||||||
// ComplianceSpec represent the compliance specification
|
// ComplianceSpec represent the compliance specification
|
||||||
type ComplianceSpec struct {
|
type ComplianceSpec struct {
|
||||||
Spec Spec `yaml:"spec"`
|
Spec Spec `yaml:"spec"`
|
||||||
@@ -58,3 +57,42 @@ const (
|
|||||||
PassStatus ControlStatus = "PASS"
|
PassStatus ControlStatus = "PASS"
|
||||||
WarnStatus ControlStatus = "WARN"
|
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
|
package spec
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TrivyCheck represent checks models : vulnerability , misconfiguration and secret
|
// MapSpecCheckIDToFilteredResults map spec check id to filtered scan results
|
||||||
type TrivyCheck interface {
|
func MapSpecCheckIDToFilteredResults(result types.Result, checkIDs map[types.SecurityCheck][]string) map[string]types.Results {
|
||||||
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 {
|
|
||||||
mapCheckByID := make(map[string]types.Results)
|
mapCheckByID := make(map[string]types.Results)
|
||||||
for _, tc := range trivyChecks {
|
for _, vuln := range result.Vulnerabilities {
|
||||||
if _, ok := mapCheckByID[tc.GetID()]; !ok {
|
// Skip irrelevant check IDs
|
||||||
mapCheckByID[tc.GetID()] = make(types.Results, 0)
|
if !slices.Contains(checkIDs[types.SecurityCheckVulnerability], vuln.GetID()) {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
for _, id := range scannerCheckIDs[tc.CheckType()] {
|
mapCheckByID[vuln.GetID()] = append(mapCheckByID[vuln.GetID()], types.Result{
|
||||||
if tc.GetID() == id {
|
Target: result.Target,
|
||||||
switch val := tc.(type) {
|
Class: result.Class,
|
||||||
case types.DetectedMisconfiguration:
|
Type: result.Type,
|
||||||
mapCheckByID[tc.GetID()] = append(mapCheckByID[tc.GetID()], types.Result{Target: target, Class: class, Type: typeN, MisconfSummary: misconfigSummary(val), Misconfigurations: []types.DetectedMisconfiguration{val}})
|
Vulnerabilities: []types.DetectedVulnerability{vuln},
|
||||||
case types.DetectedVulnerability:
|
})
|
||||||
mapCheckByID[tc.GetID()] = append(mapCheckByID[tc.GetID()], types.Result{Target: target, Class: class, Type: typeN, Vulnerabilities: []types.DetectedVulnerability{val}})
|
}
|
||||||
}
|
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
|
return mapCheckByID
|
||||||
}
|
}
|
||||||
@@ -65,66 +44,24 @@ func misconfigSummary(misconfig types.DetectedMisconfiguration) *types.MisconfSu
|
|||||||
case types.StatusPassed:
|
case types.StatusPassed:
|
||||||
rms.Successes = 1
|
rms.Successes = 1
|
||||||
case types.StatusFailure:
|
case types.StatusFailure:
|
||||||
rms.Successes = 1
|
rms.Failures = 1
|
||||||
case types.StatusException:
|
case types.StatusException:
|
||||||
rms.Exceptions = 1
|
rms.Exceptions = 1
|
||||||
}
|
}
|
||||||
return &rms
|
return &rms
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilterResults filter miconfiguration and vulnerabilities results by spec scanner check Ids
|
// AggregateAllChecksBySpecID aggregates all scan results and map it to spec ids
|
||||||
func FilterResults(results types.Results, scannerCheckIDs map[string][]string) types.Results {
|
func AggregateAllChecksBySpecID(multiResults []types.Results, cs ComplianceSpec) map[string]types.Results {
|
||||||
filteredResults := make(types.Results, 0)
|
checkIDs := cs.CheckIDs()
|
||||||
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)
|
|
||||||
complianceArr := make(map[string]types.Results, 0)
|
complianceArr := make(map[string]types.Results, 0)
|
||||||
for _, resResult := range multiResults {
|
for _, resResult := range multiResults {
|
||||||
filteredResults := FilterResults(resResult, scannerCheckIDs)
|
for _, result := range resResult {
|
||||||
for _, result := range filteredResults {
|
m := MapSpecCheckIDToFilteredResults(result, checkIDs)
|
||||||
if len(result.Misconfigurations) > 0 {
|
for id, checks := range m {
|
||||||
cMapper := NewMapper[types.DetectedMisconfiguration]()
|
complianceArr[id] = append(complianceArr[id], checks...)
|
||||||
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...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return complianceArr
|
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 (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/compliance/spec"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"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) {
|
func TestMapSpecCheckIDToFilteredResults(t *testing.T) {
|
||||||
tests := []struct {
|
checkIDs := map[types.SecurityCheck][]string{
|
||||||
name string
|
types.SecurityCheckConfig: {
|
||||||
specPath string
|
"AVD-KSV012",
|
||||||
trivyCheck spec.TrivyCheck
|
"AVD-1.2.31",
|
||||||
wantMapping map[string][]spec.TrivyCheck
|
"AVD-1.2.32",
|
||||||
}{
|
},
|
||||||
{name: "filter results by check ids for config", specPath: "./testdata/mapping_spec.yaml", trivyCheck: types.DetectedMisconfiguration{AVDID: "KSV012"},
|
types.SecurityCheckVulnerability: {
|
||||||
wantMapping: map[string][]spec.TrivyCheck{"KSV012": {types.DetectedMisconfiguration{AVDID: "KSV012"}}}},
|
"CVE-9999-9999",
|
||||||
{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"}}}},
|
}
|
||||||
}
|
tests := []struct {
|
||||||
|
name string
|
||||||
for _, tt := range tests {
|
checkIDs map[types.SecurityCheck][]string
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
result types.Result
|
||||||
cr, err := ReadSpecFile(tt.specPath)
|
want map[string]types.Results
|
||||||
assert.NoError(t, err)
|
}{
|
||||||
scannerIDMap := spec.ScannerCheckIDs(cr.Spec.Controls)
|
{
|
||||||
m := spec.NewMapper[spec.TrivyCheck]()
|
name: "misconfiguration",
|
||||||
filteredResults := m.FilterScanResultsBySpecCheckIds([]spec.TrivyCheck{tt.trivyCheck}, scannerIDMap)
|
checkIDs: checkIDs,
|
||||||
for _, v := range filteredResults {
|
result: types.Result{
|
||||||
assert.Equal(t, len(tt.wantMapping[v.GetID()]), 1)
|
Target: "target",
|
||||||
}
|
Class: types.ClassConfig,
|
||||||
})
|
Type: ftypes.Kubernetes,
|
||||||
}
|
Misconfigurations: []types.DetectedMisconfiguration{
|
||||||
}
|
{AVDID: "AVD-KSV012", Status: types.StatusFailure},
|
||||||
|
{AVDID: "AVD-KSV013", Status: types.StatusFailure},
|
||||||
func TestFilterResults(t *testing.T) {
|
{AVDID: "AVD-1.2.31", Status: types.StatusFailure},
|
||||||
tests := []struct {
|
},
|
||||||
name string
|
},
|
||||||
specPath string
|
want: map[string]types.Results{
|
||||||
results types.Results
|
"AVD-KSV012": {
|
||||||
wantFiltered types.Results
|
{
|
||||||
}{
|
Target: "target",
|
||||||
{name: "filter results by check ids define in spec", specPath: "./testdata/mapping_spec.yaml",
|
Class: types.ClassConfig,
|
||||||
results: types.Results{{Misconfigurations: []types.DetectedMisconfiguration{{AVDID: "AVD-KSV012"}, {AVDID: "AVD-KSV017"}}, Vulnerabilities: []types.DetectedVulnerability{{VulnerabilityID: "CVE-KSV014"}}}},
|
Type: ftypes.Kubernetes,
|
||||||
wantFiltered: types.Results{{Misconfigurations: []types.DetectedMisconfiguration{{AVDID: "AVD-KSV012"}}, Vulnerabilities: []types.DetectedVulnerability{{VulnerabilityID: "CVE-KSV014"}}}}},
|
MisconfSummary: &types.MisconfSummary{Successes: 0, Failures: 1, Exceptions: 0},
|
||||||
}
|
Misconfigurations: []types.DetectedMisconfiguration{
|
||||||
|
{AVDID: "AVD-KSV012", Status: types.StatusFailure},
|
||||||
for _, tt := range tests {
|
},
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
},
|
||||||
cr, err := ReadSpecFile(tt.specPath)
|
},
|
||||||
assert.NoError(t, err)
|
"AVD-1.2.31": {
|
||||||
scannerIDMap := spec.ScannerCheckIDs(cr.Spec.Controls)
|
{
|
||||||
r := spec.FilterResults(tt.results, scannerIDMap)
|
Target: "target",
|
||||||
assert.Equal(t, r, tt.wantFiltered)
|
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},
|
||||||
func TestMapSpecCheckIDtoFilteredResults(t *testing.T) {
|
},
|
||||||
tests := []struct {
|
},
|
||||||
name string
|
},
|
||||||
specPath string
|
},
|
||||||
trivyCheck spec.TrivyCheck
|
},
|
||||||
wantMapping map[string]types.Results
|
{
|
||||||
}{
|
name: "vulnerability",
|
||||||
{name: "map Check by ID config", specPath: "./testdata/mapping_spec.yaml", trivyCheck: types.DetectedMisconfiguration{AVDID: "AVD-KSV012"},
|
checkIDs: checkIDs,
|
||||||
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"}}}}},
|
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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
cr, err := ReadSpecFile(tt.specPath)
|
got := spec.MapSpecCheckIDToFilteredResults(tt.result, tt.checkIDs)
|
||||||
assert.NoError(t, err)
|
assert.Equalf(t, tt.want, got, "CheckIDs()")
|
||||||
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)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
"errors"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/flag"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
|
||||||
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
sp "github.com/aquasecurity/defsec/pkg/spec"
|
sp "github.com/aquasecurity/defsec/pkg/spec"
|
||||||
"github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
|
"github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
|
||||||
@@ -17,9 +14,11 @@ import (
|
|||||||
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
|
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
|
||||||
cr "github.com/aquasecurity/trivy/pkg/compliance/report"
|
cr "github.com/aquasecurity/trivy/pkg/compliance/report"
|
||||||
"github.com/aquasecurity/trivy/pkg/compliance/spec"
|
"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/report"
|
||||||
"github.com/aquasecurity/trivy/pkg/k8s/scanner"
|
"github.com/aquasecurity/trivy/pkg/k8s/scanner"
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -81,15 +80,20 @@ func (r *runner) run(ctx context.Context, artifacts []*artifacts.Artifact) error
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
s := scanner.NewScanner(r.cluster, runner, r.flagOpts)
|
s := scanner.NewScanner(r.cluster, runner, r.flagOpts)
|
||||||
var complianceSpec string
|
|
||||||
|
var complianceSpec spec.ComplianceSpec
|
||||||
// set scanners types by spec
|
// set scanners types by spec
|
||||||
if len(r.flagOpts.ReportOptions.Compliance) > 0 {
|
if r.flagOpts.ReportOptions.Compliance != "" {
|
||||||
complianceSpec = sp.NewSpecLoader().GetSpecByName(r.flagOpts.ReportOptions.Compliance)
|
cs := sp.NewSpecLoader().GetSpecByName(r.flagOpts.ReportOptions.Compliance)
|
||||||
scannerTypes, err := spec.GetScannerTypes(complianceSpec)
|
if err = yaml.Unmarshal([]byte(cs), &complianceSpec); err != nil {
|
||||||
if err != nil {
|
return xerrors.Errorf("yaml unmarshal error: %w", err)
|
||||||
return 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)
|
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 {
|
if len(r.flagOpts.ReportOptions.Compliance) > 0 {
|
||||||
scanResults := make([]types.Results, 0)
|
var scanResults []types.Results
|
||||||
|
for _, rss := range rpt.Vulnerabilities {
|
||||||
for _, rss := range rpt.Misconfigurations {
|
|
||||||
scanResults = append(scanResults, rss.Results)
|
scanResults = append(scanResults, rss.Results)
|
||||||
}
|
}
|
||||||
for _, rss := range rpt.Vulnerabilities {
|
for _, rss := range rpt.Misconfigurations {
|
||||||
scanResults = append(scanResults, rss.Results)
|
scanResults = append(scanResults, rss.Results)
|
||||||
}
|
}
|
||||||
complianceReport, err := cr.BuildComplianceReport(scanResults, complianceSpec)
|
complianceReport, err := cr.BuildComplianceReport(scanResults, complianceSpec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return xerrors.Errorf("compliance report build error: %w", err)
|
||||||
}
|
}
|
||||||
return cr.Write(complianceReport, cr.Option{
|
return cr.Write(complianceReport, cr.Option{
|
||||||
Format: r.flagOpts.Format,
|
Format: r.flagOpts.Format,
|
||||||
|
|||||||
@@ -39,16 +39,6 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// GetID retrun misconfig ID
|
// GetID retrun misconfig ID
|
||||||
func (mc DetectedMisconfiguration) GetID() string {
|
func (mc *DetectedMisconfiguration) GetID() string {
|
||||||
return mc.AVDID
|
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
|
types.Vulnerability
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetID retrun Vulnerability ID
|
||||||
|
func (vuln *DetectedVulnerability) GetID() string {
|
||||||
|
return vuln.VulnerabilityID
|
||||||
|
}
|
||||||
|
|
||||||
// BySeverity implements sort.Interface based on the Severity field.
|
// BySeverity implements sort.Interface based on the Severity field.
|
||||||
type BySeverity []DetectedVulnerability
|
type BySeverity []DetectedVulnerability
|
||||||
|
|
||||||
@@ -53,18 +58,3 @@ func (v BySeverity) Less(i, j int) bool {
|
|||||||
|
|
||||||
// Swap swaps 2 vulnerability
|
// Swap swaps 2 vulnerability
|
||||||
func (v BySeverity) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
|
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