feat(image): add support for Docker CIS Benchmark (#3496)

Co-authored-by: chenk <hen.keinan@gmail.com>
This commit is contained in:
Teppei Fukuda
2023-01-31 07:31:59 +02:00
committed by GitHub
parent 6eec9ac0a4
commit cb5af0b33b
18 changed files with 253 additions and 155 deletions

View File

@@ -9,10 +9,12 @@ Trivys compliance flag lets you curate a specific set of checks into a report
Compliance report is currently supported in the following targets (trivy sub-commands):
- `trivy image`
- `trivy aws`
- `trivy k8s`
Add the `--compliance` flag to the command line, and set it's value to desired report. For example: `trivy k8s cluster --compliance k8s-nsa` (see below for built-in and custom reports)
Add the `--compliance` flag to the command line, and set it's value to desired report.
For example: `trivy k8s cluster --compliance k8s-nsa` (see below for built-in and custom reports)
### Options

2
go.mod
View File

@@ -8,7 +8,7 @@ require (
github.com/NYTimes/gziphandler v1.1.1
github.com/alicebob/miniredis/v2 v2.23.0
github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986
github.com/aquasecurity/defsec v0.82.8
github.com/aquasecurity/defsec v0.82.9-0.20230130071923-197035f687cb
github.com/aquasecurity/go-dep-parser v0.0.0-20230123091557-6a4a819ab8da
github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce
github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798

4
go.sum
View File

@@ -193,8 +193,8 @@ github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 h1:2a30xLN2sUZcMXl50hg+PJCIDdJgIvIbVcKqLJ/ZrtM=
github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986/go.mod h1:NT+jyeCzXk6vXR5MTkdn4z64TgGfE5HMLC8qfj5unl8=
github.com/aquasecurity/defsec v0.82.8 h1:qQmpbIYXHYykTBqcwjYV5LEGU2tnbn2Pbz9P3CY10xg=
github.com/aquasecurity/defsec v0.82.8/go.mod h1:f/acz2sBQzfTcnaPxSjVnkRhCQ9hUbC6qwQCaHQwrFc=
github.com/aquasecurity/defsec v0.82.9-0.20230130071923-197035f687cb h1:bq0WzKdisulgz3EVXVGQlL8uOIn5i25BYegJgLBvxJM=
github.com/aquasecurity/defsec v0.82.9-0.20230130071923-197035f687cb/go.mod h1:f/acz2sBQzfTcnaPxSjVnkRhCQ9hUbC6qwQCaHQwrFc=
github.com/aquasecurity/go-dep-parser v0.0.0-20230123091557-6a4a819ab8da h1:dQ9R41jz6tAZCw6NNJQCkdNYfEzRUxK1ZmuChldWcbc=
github.com/aquasecurity/go-dep-parser v0.0.0-20230123091557-6a4a819ab8da/go.mod h1:Cj+0xDZFgXGQin2fo40aH2a9srUKMOCFPw50NHsfVEk=
github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce h1:QgBRgJvtEOBtUXilDb1MLi1p1MWoyFDXAu5DEUl5nwM=

View File

@@ -9,7 +9,6 @@ import (
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/sts"
"golang.org/x/xerrors"
"gopkg.in/yaml.v3"
"github.com/aquasecurity/defsec/pkg/errs"
awsScanner "github.com/aquasecurity/defsec/pkg/scanners/cloud/aws"
@@ -18,7 +17,6 @@ import (
"github.com/aquasecurity/trivy/pkg/cloud/report"
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
cr "github.com/aquasecurity/trivy/pkg/compliance/report"
"github.com/aquasecurity/trivy/pkg/compliance/spec"
"github.com/aquasecurity/trivy/pkg/flag"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/types"
@@ -125,23 +123,14 @@ func Run(ctx context.Context, opt flag.Options) error {
}
log.Logger.Debug("Writing report to output...")
if len(opt.Compliance) > 0 {
var complianceSpec spec.ComplianceSpec
cs, err := spec.GetComplianceSpec(opt.Compliance)
if err != nil {
return xerrors.Errorf("spec loading from file system error: %w", err)
}
if err = yaml.Unmarshal(cs, &complianceSpec); err != nil {
return xerrors.Errorf("yaml unmarshal error: %w", err)
}
if opt.Compliance.Spec.ID != "" {
convertedResults := report.ConvertResults(results, cloud.ProviderAWS, opt.Services)
var crr []types.Results
for _, r := range convertedResults {
crr = append(crr, r.Results)
}
complianceReport, err := cr.BuildComplianceReport(crr, complianceSpec)
complianceReport, err := cr.BuildComplianceReport(crr, opt.Compliance)
if err != nil {
return xerrors.Errorf("compliance report build error: %w", err)
}
@@ -149,7 +138,8 @@ func Run(ctx context.Context, opt flag.Options) error {
return cr.Write(complianceReport, cr.Option{
Format: opt.Format,
Report: opt.ReportFormat,
Output: opt.Output})
Output: opt.Output,
})
}
r := report.New(cloud.ProviderAWS, opt.Account, opt.Region, results.GetFailed(), opt.Services)

View File

@@ -9,6 +9,7 @@ import (
"time"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/compliance/spec"
"github.com/aquasecurity/trivy/pkg/flag"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -646,7 +647,30 @@ deny[res] {
CloudOptions: flag.CloudOptions{
MaxCacheAge: time.Hour * 24 * 365 * 100,
},
ReportOptions: flag.ReportOptions{Compliance: "@./testdata/example-spec.yaml", Format: "table", ReportFormat: "summary"},
ReportOptions: flag.ReportOptions{
Compliance: spec.ComplianceSpec{
Spec: spec.Spec{
// TODO: refactor defsec so that the parsed spec can be passed
ID: "@testdata/example-spec.yaml",
Title: "my-custom-spec",
Description: "My fancy spec",
Version: "1.2",
Controls: []spec.Control{
{
ID: "1.1",
Name: "Unencrypted S3 bucket",
Description: "S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised.",
Checks: []spec.SpecCheck{
{ID: "AVD-AWS-0088"},
},
Severity: "HIGH",
},
},
},
},
Format: "table",
ReportFormat: "summary",
},
},
cacheContent: exampleS3Cache,
want: `
@@ -656,26 +680,8 @@ Summary Report for compliance: my-custom-spec
├─────┼──────────┼───────────────────────┼────────┼────────┤
│ 1.1 │ HIGH │ Unencrypted S3 bucket │ FAIL │ 1 │
└─────┴──────────┴───────────────────────┴────────┴────────┘
`,
},
{
name: "error loading compliance report",
expectErr: true,
options: flag.Options{
AWSOptions: flag.AWSOptions{
Region: "us-east-1",
Services: []string{"s3"},
Account: "12345678",
},
CloudOptions: flag.CloudOptions{
MaxCacheAge: time.Hour * 24 * 365 * 100,
},
ReportOptions: flag.ReportOptions{Compliance: "@./testdata/nosuchspec.yaml", Format: "table", ReportFormat: "summary"},
},
cacheContent: exampleS3Cache,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {

View File

@@ -85,8 +85,8 @@ func (s *AWSScanner) Scan(ctx context.Context, option flag.Options) (scan.Result
)
}
if len(option.Compliance) > 0 {
scannerOpts = append(scannerOpts, options.ScannerWithSpec(option.Compliance))
if option.Compliance.Spec.ID != "" {
scannerOpts = append(scannerOpts, options.ScannerWithSpec(option.Compliance.Spec.ID))
} else {
scannerOpts = append(scannerOpts, options.ScannerWithFrameworks(
framework.Default,

View File

@@ -209,8 +209,15 @@ func NewRootCommand(version string, globalFlags *flag.GlobalFlagGroup) *cobra.Co
func NewImageCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
reportFlagGroup := flag.NewReportFlagGroup()
reportFlagGroup.ReportFormat = nil // TODO: support --report summary
reportFlagGroup.Compliance = nil // disable '--compliance'
report := flag.ReportFormatFlag
report.Value = "summary" // override the default value as the summary is preferred for the compliance report
report.Usage = "specify a format for the compliance report." // "--report" works only with "--compliance"
reportFlagGroup.ReportFormat = &report
compliance := flag.ComplianceFlag
compliance.Usage += fmt.Sprintf(" (%s)", types.ComplianceDockerCIS)
reportFlagGroup.Compliance = &compliance // override usage as the accepted values differ for each subcommand.
imageFlags := &flag.Flags{
CacheFlagGroup: flag.NewCacheFlagGroup(),

View File

@@ -288,6 +288,8 @@ func (r *runner) Report(opts flag.Options, report types.Report) error {
OutputTemplate: opts.Template,
IncludeNonFailures: opts.IncludeNonFailures,
Trace: opts.Trace,
Report: opts.ReportFormat,
Compliance: opts.Compliance,
}); err != nil {
return xerrors.Errorf("unable to write results: %w", err)
}
@@ -365,7 +367,7 @@ func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err err
defer cancel()
defer func() {
if xerrors.Is(err, context.DeadlineExceeded) {
if errors.Is(err, context.DeadlineExceeded) {
log.Logger.Warn("Increase --timeout value")
}
}()
@@ -476,6 +478,22 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi
target = opts.Input
}
if opts.Compliance.Spec.ID != "" {
// set scanners types by spec
scanners, err := opts.Compliance.Scanners()
if err != nil {
return ScannerConfig{}, types.ScanOptions{}, xerrors.Errorf("scanner error: %w", err)
}
opts.Scanners = scanners
opts.ImageConfigScanners = nil
// TODO: define image-config-scanners in the spec
if opts.Compliance.Spec.ID == "docker-cis" {
opts.Scanners = nil
opts.ImageConfigScanners = scanners
}
}
scanOptions := types.ScanOptions{
VulnType: opts.VulnType,
Scanners: opts.Scanners,

View File

@@ -6,13 +6,10 @@ import (
"strconv"
"strings"
"github.com/samber/lo"
"golang.org/x/xerrors"
"k8s.io/utils/pointer"
"github.com/aquasecurity/table"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
pkgReport "github.com/aquasecurity/trivy/pkg/report/table"
)
func BuildSummary(cr *ComplianceReport) *SummaryReport {
@@ -24,7 +21,7 @@ func BuildSummary(cr *ComplianceReport) *SummaryReport {
Severity: control.Severity,
}
if !strings.Contains(control.Name, "Manual") {
ccm.TotalFail = pointer.Int(len(control.Results))
ccm.TotalFail = lo.ToPtr(len(control.Results))
}
ccma = append(ccma, ccm)
}
@@ -37,20 +34,11 @@ func BuildSummary(cr *ComplianceReport) *SummaryReport {
type SummaryWriter struct {
Output io.Writer
Severities []string
SeverityHeadings []string
ColumnsHeading []string
}
func NewSummaryWriter(output io.Writer, requiredSevs []dbTypes.Severity, columnHeading []string) SummaryWriter {
var severities []string
var severityHeadings []string
severities, severityHeadings = getRequiredSeverities(requiredSevs)
func NewSummaryWriter(output io.Writer) SummaryWriter {
return SummaryWriter{
Output: output,
Severities: severities,
SeverityHeadings: severityHeadings,
ColumnsHeading: columnHeading,
}
}
@@ -66,29 +54,32 @@ func (s SummaryWriter) Write(report *ComplianceReport) error {
sr := BuildSummary(report)
t := table.New(s.Output)
t.SetRowLines(false)
configureHeader(s, t, s.ColumnsHeading)
configureHeader(t, s.columns())
for _, summaryControl := range sr.SummaryControls {
rowParts := make([]string, 0)
rowParts = append(rowParts, s.generateSummary(summaryControl)...)
rowParts := s.generateSummary(summaryControl)
t.AddRow(rowParts...)
}
t.Render()
keyParts := make([]string, 0)
for _, s := range s.Severities {
keyParts = append(keyParts, fmt.Sprintf("%s=%s", s[:1], pkgReport.ColorizeSeverity(s, s)))
}
_, _ = fmt.Fprintln(s.Output, strings.Join(keyParts, " "))
_, _ = fmt.Fprintln(s.Output)
return nil
}
func (s SummaryWriter) columns() []string {
return []string{
ControlIDColumn,
SeverityColumn,
ControlNameColumn,
StatusColumn,
IssuesColumn,
}
}
func (s SummaryWriter) generateSummary(summaryControls ControlCheckSummary) []string {
var numOfIssues string
var status string
// "-" means manual checks
numOfIssues := "-"
status := "-"
if summaryControls.TotalFail != nil {
if *summaryControls.TotalFail == 0 {
status = "PASS"
@@ -97,40 +88,24 @@ func (s SummaryWriter) generateSummary(summaryControls ControlCheckSummary) []st
}
numOfIssues = strconv.Itoa(*summaryControls.TotalFail)
}
return []string{summaryControls.ID, summaryControls.Severity, summaryControls.Name, status, numOfIssues}
return []string{
summaryControls.ID,
summaryControls.Severity,
summaryControls.Name,
status,
numOfIssues,
}
}
func getRequiredSeverities(requiredSevs []dbTypes.Severity) ([]string, []string) {
requiredSevOrder := []dbTypes.Severity{dbTypes.SeverityCritical,
dbTypes.SeverityHigh, dbTypes.SeverityMedium,
dbTypes.SeverityLow, dbTypes.SeverityUnknown}
var severities []string
var severityHeadings []string
for _, sev := range requiredSevOrder {
for _, p := range requiredSevs {
if p == sev {
severities = append(severities, sev.String())
severityHeadings = append(severityHeadings, strings.ToUpper(sev.String()[:1]))
continue
}
}
}
return severities, severityHeadings
}
func configureHeader(s SummaryWriter, t *table.Table, columnHeading []string) {
sevCount := len(s.Severities)
headerRow := []string{columnHeading[0], columnHeading[1]}
count := len(columnHeading) - len(headerRow)
colSpan := []int{1, 1}
headerAlignment := []table.Alignment{table.AlignLeft, table.AlignLeft}
for i := 0; i < count; i++ {
headerRow = append(headerRow, s.SeverityHeadings...)
colSpan = append(colSpan, sevCount)
headerAlignment = append(headerAlignment, table.AlignCenter)
func configureHeader(t *table.Table, columnHeading []string) {
headerAlignment := []table.Alignment{
table.AlignLeft,
table.AlignCenter,
table.AlignLeft,
table.AlignCenter,
table.AlignCenter,
}
t.SetHeaders(columnHeading...)
t.SetAlignment(headerAlignment...)
t.SetAutoMergeHeaders(true)
t.SetHeaderColSpans(0, colSpan...)
}

View File

@@ -26,14 +26,14 @@ const (
IssuesColumn = "Issues"
)
func (tw TableWriter) columns() []string {
return []string{ControlIDColumn, SeverityColumn, ControlNameColumn, StatusColumn, IssuesColumn}
}
func (tw TableWriter) Write(report *ComplianceReport) error {
switch tw.Report {
case allReport:
t := pkgReport.Writer{Output: tw.Output, Severities: tw.Severities, ShowMessageOnce: &sync.Once{}}
t := pkgReport.Writer{
Output: tw.Output,
Severities: tw.Severities,
ShowMessageOnce: &sync.Once{},
}
for _, cr := range report.Results {
r := types.Report{Results: cr.Results}
err := t.Write(r)
@@ -42,7 +42,7 @@ func (tw TableWriter) Write(report *ComplianceReport) error {
}
}
case summaryReport:
writer := NewSummaryWriter(tw.Output, tw.Severities, tw.columns())
writer := NewSummaryWriter(tw.Output)
return writer.Write(report)
default:
return xerrors.Errorf(`report %q not supported. Use "summary" or "all"`, tw.Report)

View File

@@ -6,5 +6,3 @@ Summary Report for compliance: NSA
│ 1.0 │ MEDIUM │ Non-root containers │ FAIL │ 1 │
│ 1.1 │ LOW │ Immutable container file systems │ FAIL │ 1 │
└─────┴──────────┴──────────────────────────────────┴────────┴────────┘

View File

@@ -7,6 +7,7 @@ import (
"golang.org/x/exp/maps"
"golang.org/x/xerrors"
"gopkg.in/yaml.v3"
sp "github.com/aquasecurity/defsec/pkg/spec"
"github.com/aquasecurity/trivy/pkg/types"
@@ -101,14 +102,23 @@ func scannerByCheckID(checkID string) types.Scanner {
}
// GetComplianceSpec accepct compliance flag name/path and return builtin or file system loaded spec
func GetComplianceSpec(specNameOrPath string) ([]byte, error) {
func GetComplianceSpec(specNameOrPath string) (ComplianceSpec, error) {
var b []byte
var err error
if strings.HasPrefix(specNameOrPath, "@") {
buf, err := os.ReadFile(strings.TrimPrefix(specNameOrPath, "@"))
b, err = os.ReadFile(strings.TrimPrefix(specNameOrPath, "@"))
if err != nil {
return []byte{}, fmt.Errorf("error retrieving compliance spec from path: %w", err)
return ComplianceSpec{}, fmt.Errorf("error retrieving compliance spec from path: %w", err)
}
return buf, nil
} else {
// TODO: GetSpecByName() should return []byte
b = []byte(sp.NewSpecLoader().GetSpecByName(specNameOrPath))
}
return []byte(sp.NewSpecLoader().GetSpecByName(specNameOrPath)), nil
var complianceSpec ComplianceSpec
if err = yaml.Unmarshal(b, &complianceSpec); err != nil {
return ComplianceSpec{}, xerrors.Errorf("spec yaml decode error: %w", err)
}
return complianceSpec, nil
}

View File

@@ -9,6 +9,7 @@ import (
"golang.org/x/xerrors"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/compliance/spec"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/report"
"github.com/aquasecurity/trivy/pkg/result"
@@ -120,7 +121,7 @@ type ReportOptions struct {
IgnorePolicy string
Output io.Writer
Severities []dbTypes.Severity
Compliance string
Compliance spec.ComplianceSpec
}
func NewReportFlagGroup() *ReportFlagGroup {
@@ -144,8 +145,19 @@ func (f *ReportFlagGroup) Name() string {
}
func (f *ReportFlagGroup) Flags() []*Flag {
return []*Flag{f.Format, f.ReportFormat, f.Template, f.DependencyTree, f.ListAllPkgs, f.IgnoreFile,
f.IgnorePolicy, f.ExitCode, f.Output, f.Severity, f.Compliance}
return []*Flag{
f.Format,
f.ReportFormat,
f.Template,
f.DependencyTree,
f.ListAllPkgs,
f.IgnoreFile,
f.IgnorePolicy,
f.ExitCode,
f.Output,
f.Severity,
f.Compliance,
}
}
func (f *ReportFlagGroup) ToOptions(out io.Writer) (ReportOptions, error) {
@@ -199,9 +211,9 @@ func (f *ReportFlagGroup) ToOptions(out io.Writer) (ReportOptions, error) {
}
}
complianceTypes, err := parseComplianceTypes(getString(f.Compliance))
cs, err := loadComplianceTypes(getString(f.Compliance))
if err != nil {
return ReportOptions{}, xerrors.Errorf("unable to parse compliance types: %w", err)
return ReportOptions{}, xerrors.Errorf("unable to load compliance spec: %w", err)
}
return ReportOptions{
@@ -215,15 +227,21 @@ func (f *ReportFlagGroup) ToOptions(out io.Writer) (ReportOptions, error) {
IgnorePolicy: getString(f.IgnorePolicy),
Output: out,
Severities: splitSeverity(getStringSlice(f.Severity)),
Compliance: complianceTypes,
Compliance: cs,
}, nil
}
func parseComplianceTypes(compliance string) (string, error) {
func loadComplianceTypes(compliance string) (spec.ComplianceSpec, error) {
if len(compliance) > 0 && !slices.Contains(types.Compliances, compliance) && !strings.HasPrefix(compliance, "@") {
return "", xerrors.Errorf("unknown compliance : %v", compliance)
return spec.ComplianceSpec{}, xerrors.Errorf("unknown compliance : %v", compliance)
}
return compliance, nil
cs, err := spec.GetComplianceSpec(compliance)
if err != nil {
return spec.ComplianceSpec{}, xerrors.Errorf("spec loading from file system error: %w", err)
}
return cs, nil
}
func (f *ReportFlagGroup) forceListAllPkgs(format string, listAllPkgs, dependencyTree bool) bool {

View File

@@ -10,6 +10,7 @@ import (
"go.uber.org/zap/zaptest/observer"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/compliance/spec"
"github.com/aquasecurity/trivy/pkg/flag"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/report"
@@ -174,12 +175,30 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
{
name: "happy path with compliance",
fields: fields{
compliane: "k8s-nsa",
compliane: "@testdata/example-spec.yaml",
severities: "low",
},
want: flag.ReportOptions{
Output: os.Stdout,
Compliance: "k8s-nsa",
Compliance: spec.ComplianceSpec{
Spec: spec.Spec{
ID: "0001",
Title: "my-custom-spec",
Description: "My fancy spec",
Version: "1.2",
Controls: []spec.Control{
{
ID: "1.1",
Name: "Unencrypted S3 bucket",
Description: "S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised.",
Checks: []spec.SpecCheck{
{ID: "AVD-AWS-0088"},
},
Severity: "HIGH",
},
},
},
},
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
},
},

13
pkg/flag/testdata/example-spec.yaml vendored Normal file
View File

@@ -0,0 +1,13 @@
spec:
id: "0001"
title: my-custom-spec
description: My fancy spec
version: "1.2"
controls:
- id: "1.1"
name: Unencrypted S3 bucket
description: |-
S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised.
checks:
- id: AVD-AWS-0088
severity: HIGH

View File

@@ -6,13 +6,11 @@ import (
"github.com/spf13/viper"
"golang.org/x/xerrors"
"gopkg.in/yaml.v3"
"github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
"github.com/aquasecurity/trivy-kubernetes/pkg/k8s"
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
cr "github.com/aquasecurity/trivy/pkg/compliance/report"
"github.com/aquasecurity/trivy/pkg/compliance/spec"
"github.com/aquasecurity/trivy/pkg/flag"
"github.com/aquasecurity/trivy/pkg/k8s/report"
"github.com/aquasecurity/trivy/pkg/k8s/scanner"
@@ -83,17 +81,9 @@ func (r *runner) run(ctx context.Context, artifacts []*artifacts.Artifact) error
s := scanner.NewScanner(r.cluster, runner, r.flagOpts)
var complianceSpec spec.ComplianceSpec
// set scanners types by spec
if r.flagOpts.ReportOptions.Compliance != "" {
cs, err := spec.GetComplianceSpec(r.flagOpts.ReportOptions.Compliance)
if err != nil {
return xerrors.Errorf("spec loading from file system error: %w", err)
}
if err = yaml.Unmarshal(cs, &complianceSpec); err != nil {
return xerrors.Errorf("yaml unmarshal error: %w", err)
}
scanners, err := complianceSpec.Scanners()
if r.flagOpts.Compliance.Spec.ID != "" {
scanners, err := r.flagOpts.Compliance.Scanners()
if err != nil {
return xerrors.Errorf("scanner error: %w", err)
}
@@ -105,7 +95,7 @@ func (r *runner) run(ctx context.Context, artifacts []*artifacts.Artifact) error
return xerrors.Errorf("k8s scan error: %w", err)
}
if len(r.flagOpts.ReportOptions.Compliance) > 0 {
if r.flagOpts.Compliance.Spec.ID != "" {
var scanResults []types.Results
for _, rss := range rpt.Vulnerabilities {
scanResults = append(scanResults, rss.Results)
@@ -113,7 +103,7 @@ func (r *runner) run(ctx context.Context, artifacts []*artifacts.Artifact) error
for _, rss := range rpt.Misconfigurations {
scanResults = append(scanResults, rss.Results)
}
complianceReport, err := cr.BuildComplianceReport(scanResults, complianceSpec)
complianceReport, err := cr.BuildComplianceReport(scanResults, r.flagOpts.Compliance)
if err != nil {
return xerrors.Errorf("compliance report build error: %w", err)
}

View File

@@ -5,16 +5,17 @@ import (
"strings"
"sync"
"github.com/aquasecurity/trivy/pkg/report/predicate"
"github.com/aquasecurity/trivy/pkg/report/table"
"golang.org/x/xerrors"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
cr "github.com/aquasecurity/trivy/pkg/compliance/report"
"github.com/aquasecurity/trivy/pkg/compliance/spec"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/report/cyclonedx"
"github.com/aquasecurity/trivy/pkg/report/github"
"github.com/aquasecurity/trivy/pkg/report/predicate"
"github.com/aquasecurity/trivy/pkg/report/spdx"
"github.com/aquasecurity/trivy/pkg/report/table"
"github.com/aquasecurity/trivy/pkg/types"
)
@@ -33,21 +34,38 @@ const (
)
var (
SupportedFormats = []string{FormatTable, FormatJSON, FormatTemplate, FormatSarif, FormatCycloneDX, FormatSPDX, FormatSPDXJSON, FormatGitHub, FormatCosignVuln}
SupportedFormats = []string{
FormatTable,
FormatJSON,
FormatTemplate,
FormatSarif,
FormatCycloneDX,
FormatSPDX,
FormatSPDXJSON,
FormatGitHub,
FormatCosignVuln,
}
)
var (
SupportedSBOMFormats = []string{FormatCycloneDX, FormatSPDX, FormatSPDXJSON, FormatGitHub}
SupportedSBOMFormats = []string{
FormatCycloneDX,
FormatSPDX,
FormatSPDXJSON,
FormatGitHub,
}
)
type Option struct {
AppVersion string
Format string
Report string
Output io.Writer
Tree bool
Severities []dbTypes.Severity
OutputTemplate string
Compliance spec.ComplianceSpec
// For misconfigurations
IncludeNonFailures bool
@@ -60,6 +78,11 @@ type Option struct {
// Write writes the result to output, format as passed in argument
func Write(report types.Report, option Option) error {
// Compliance report
if option.Compliance.Spec.ID != "" {
return complianceWrite(report, option)
}
var writer Writer
switch option.Format {
case FormatTable:
@@ -76,7 +99,10 @@ func Write(report types.Report, option Option) error {
case FormatJSON:
writer = &JSONWriter{Output: option.Output}
case FormatGitHub:
writer = &github.Writer{Output: option.Output, Version: option.AppVersion}
writer = &github.Writer{
Output: option.Output,
Version: option.AppVersion,
}
case FormatCycloneDX:
// TODO: support xml format option with cyclonedx writer
writer = cyclonedx.NewWriter(option.Output, option.AppVersion)
@@ -86,7 +112,10 @@ func Write(report types.Report, option Option) error {
// We keep `sarif.tpl` template working for backward compatibility for a while.
if strings.HasPrefix(option.OutputTemplate, "@") && strings.HasSuffix(option.OutputTemplate, "sarif.tpl") {
log.Logger.Warn("Using `--template sarif.tpl` is deprecated. Please migrate to `--format sarif`. See https://github.com/aquasecurity/trivy/discussions/1571")
writer = SarifWriter{Output: option.Output, Version: option.AppVersion}
writer = SarifWriter{
Output: option.Output,
Version: option.AppVersion,
}
break
}
var err error
@@ -94,7 +123,10 @@ func Write(report types.Report, option Option) error {
return xerrors.Errorf("failed to initialize template writer: %w", err)
}
case FormatSarif:
writer = SarifWriter{Output: option.Output, Version: option.AppVersion}
writer = SarifWriter{
Output: option.Output,
Version: option.AppVersion,
}
case FormatCosignVuln:
writer = predicate.NewVulnWriter(option.Output, option.AppVersion)
default:
@@ -107,6 +139,19 @@ func Write(report types.Report, option Option) error {
return nil
}
func complianceWrite(report types.Report, opt Option) error {
complianceReport, err := cr.BuildComplianceReport([]types.Results{report.Results}, opt.Compliance)
if err != nil {
return xerrors.Errorf("compliance report build error: %w", err)
}
return cr.Write(complianceReport, cr.Option{
Format: opt.Format,
Report: opt.Report,
Output: opt.Output,
Severities: opt.Severities,
})
}
// Writer defines the result write operation
type Writer interface {
Write(types.Report) error

View File

@@ -8,7 +8,13 @@ import (
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
var Compliances = []string{ComplianceK8sNsa, ComplianceK8sCIS, ComplianceAWSCIS12, ComplianceAWSCIS14}
var Compliances = []string{
ComplianceK8sNsa,
ComplianceK8sCIS,
ComplianceAWSCIS12,
ComplianceAWSCIS14,
ComplianceDockerCIS,
}
// Report represents a scan result
type Report struct {
@@ -55,6 +61,7 @@ const (
ComplianceK8sCIS = Compliance("k8s-cis")
ComplianceAWSCIS12 = Compliance("aws-cis-1.2")
ComplianceAWSCIS14 = Compliance("aws-cis-1.4")
ComplianceDockerCIS = Compliance("docker-cis")
)
// Result holds a target and detected vulnerabilities