mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-23 07:29:00 -08:00
feat(image): add support for Docker CIS Benchmark (#3496)
Co-authored-by: chenk <hen.keinan@gmail.com>
This commit is contained in:
@@ -9,10 +9,12 @@ Trivy’s 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):
|
Compliance report is currently supported in the following targets (trivy sub-commands):
|
||||||
|
|
||||||
|
- `trivy image`
|
||||||
- `trivy aws`
|
- `trivy aws`
|
||||||
- `trivy k8s`
|
- `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
|
### Options
|
||||||
|
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -8,7 +8,7 @@ require (
|
|||||||
github.com/NYTimes/gziphandler v1.1.1
|
github.com/NYTimes/gziphandler v1.1.1
|
||||||
github.com/alicebob/miniredis/v2 v2.23.0
|
github.com/alicebob/miniredis/v2 v2.23.0
|
||||||
github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986
|
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-dep-parser v0.0.0-20230123091557-6a4a819ab8da
|
||||||
github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce
|
github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce
|
||||||
github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798
|
github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -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/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 h1:2a30xLN2sUZcMXl50hg+PJCIDdJgIvIbVcKqLJ/ZrtM=
|
||||||
github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986/go.mod h1:NT+jyeCzXk6vXR5MTkdn4z64TgGfE5HMLC8qfj5unl8=
|
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.9-0.20230130071923-197035f687cb h1:bq0WzKdisulgz3EVXVGQlL8uOIn5i25BYegJgLBvxJM=
|
||||||
github.com/aquasecurity/defsec v0.82.8/go.mod h1:f/acz2sBQzfTcnaPxSjVnkRhCQ9hUbC6qwQCaHQwrFc=
|
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 h1:dQ9R41jz6tAZCw6NNJQCkdNYfEzRUxK1ZmuChldWcbc=
|
||||||
github.com/aquasecurity/go-dep-parser v0.0.0-20230123091557-6a4a819ab8da/go.mod h1:Cj+0xDZFgXGQin2fo40aH2a9srUKMOCFPw50NHsfVEk=
|
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=
|
github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce h1:QgBRgJvtEOBtUXilDb1MLi1p1MWoyFDXAu5DEUl5nwM=
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go-v2/config"
|
"github.com/aws/aws-sdk-go-v2/config"
|
||||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/defsec/pkg/errs"
|
"github.com/aquasecurity/defsec/pkg/errs"
|
||||||
awsScanner "github.com/aquasecurity/defsec/pkg/scanners/cloud/aws"
|
awsScanner "github.com/aquasecurity/defsec/pkg/scanners/cloud/aws"
|
||||||
@@ -18,7 +17,6 @@ import (
|
|||||||
"github.com/aquasecurity/trivy/pkg/cloud/report"
|
"github.com/aquasecurity/trivy/pkg/cloud/report"
|
||||||
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/flag"
|
"github.com/aquasecurity/trivy/pkg/flag"
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"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...")
|
log.Logger.Debug("Writing report to output...")
|
||||||
if len(opt.Compliance) > 0 {
|
if opt.Compliance.Spec.ID != "" {
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
convertedResults := report.ConvertResults(results, cloud.ProviderAWS, opt.Services)
|
convertedResults := report.ConvertResults(results, cloud.ProviderAWS, opt.Services)
|
||||||
var crr []types.Results
|
var crr []types.Results
|
||||||
for _, r := range convertedResults {
|
for _, r := range convertedResults {
|
||||||
crr = append(crr, r.Results)
|
crr = append(crr, r.Results)
|
||||||
}
|
}
|
||||||
|
|
||||||
complianceReport, err := cr.BuildComplianceReport(crr, complianceSpec)
|
complianceReport, err := cr.BuildComplianceReport(crr, opt.Compliance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("compliance report build error: %w", err)
|
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{
|
return cr.Write(complianceReport, cr.Option{
|
||||||
Format: opt.Format,
|
Format: opt.Format,
|
||||||
Report: opt.ReportFormat,
|
Report: opt.ReportFormat,
|
||||||
Output: opt.Output})
|
Output: opt.Output,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
r := report.New(cloud.ProviderAWS, opt.Account, opt.Region, results.GetFailed(), opt.Services)
|
r := report.New(cloud.ProviderAWS, opt.Account, opt.Region, results.GetFailed(), opt.Services)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
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/flag"
|
"github.com/aquasecurity/trivy/pkg/flag"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -646,7 +647,30 @@ deny[res] {
|
|||||||
CloudOptions: flag.CloudOptions{
|
CloudOptions: flag.CloudOptions{
|
||||||
MaxCacheAge: time.Hour * 24 * 365 * 100,
|
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,
|
cacheContent: exampleS3Cache,
|
||||||
want: `
|
want: `
|
||||||
@@ -656,26 +680,8 @@ Summary Report for compliance: my-custom-spec
|
|||||||
├─────┼──────────┼───────────────────────┼────────┼────────┤
|
├─────┼──────────┼───────────────────────┼────────┼────────┤
|
||||||
│ 1.1 │ HIGH │ Unencrypted S3 bucket │ FAIL │ 1 │
|
│ 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 {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
|||||||
@@ -85,8 +85,8 @@ func (s *AWSScanner) Scan(ctx context.Context, option flag.Options) (scan.Result
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(option.Compliance) > 0 {
|
if option.Compliance.Spec.ID != "" {
|
||||||
scannerOpts = append(scannerOpts, options.ScannerWithSpec(option.Compliance))
|
scannerOpts = append(scannerOpts, options.ScannerWithSpec(option.Compliance.Spec.ID))
|
||||||
} else {
|
} else {
|
||||||
scannerOpts = append(scannerOpts, options.ScannerWithFrameworks(
|
scannerOpts = append(scannerOpts, options.ScannerWithFrameworks(
|
||||||
framework.Default,
|
framework.Default,
|
||||||
|
|||||||
@@ -209,8 +209,15 @@ func NewRootCommand(version string, globalFlags *flag.GlobalFlagGroup) *cobra.Co
|
|||||||
|
|
||||||
func NewImageCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
func NewImageCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||||
reportFlagGroup := flag.NewReportFlagGroup()
|
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{
|
imageFlags := &flag.Flags{
|
||||||
CacheFlagGroup: flag.NewCacheFlagGroup(),
|
CacheFlagGroup: flag.NewCacheFlagGroup(),
|
||||||
|
|||||||
@@ -288,6 +288,8 @@ func (r *runner) Report(opts flag.Options, report types.Report) error {
|
|||||||
OutputTemplate: opts.Template,
|
OutputTemplate: opts.Template,
|
||||||
IncludeNonFailures: opts.IncludeNonFailures,
|
IncludeNonFailures: opts.IncludeNonFailures,
|
||||||
Trace: opts.Trace,
|
Trace: opts.Trace,
|
||||||
|
Report: opts.ReportFormat,
|
||||||
|
Compliance: opts.Compliance,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return xerrors.Errorf("unable to write results: %w", err)
|
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 cancel()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if xerrors.Is(err, context.DeadlineExceeded) {
|
if errors.Is(err, context.DeadlineExceeded) {
|
||||||
log.Logger.Warn("Increase --timeout value")
|
log.Logger.Warn("Increase --timeout value")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -476,6 +478,22 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi
|
|||||||
target = opts.Input
|
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{
|
scanOptions := types.ScanOptions{
|
||||||
VulnType: opts.VulnType,
|
VulnType: opts.VulnType,
|
||||||
Scanners: opts.Scanners,
|
Scanners: opts.Scanners,
|
||||||
|
|||||||
@@ -6,13 +6,10 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"k8s.io/utils/pointer"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/table"
|
"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 {
|
func BuildSummary(cr *ComplianceReport) *SummaryReport {
|
||||||
@@ -24,7 +21,7 @@ func BuildSummary(cr *ComplianceReport) *SummaryReport {
|
|||||||
Severity: control.Severity,
|
Severity: control.Severity,
|
||||||
}
|
}
|
||||||
if !strings.Contains(control.Name, "Manual") {
|
if !strings.Contains(control.Name, "Manual") {
|
||||||
ccm.TotalFail = pointer.Int(len(control.Results))
|
ccm.TotalFail = lo.ToPtr(len(control.Results))
|
||||||
}
|
}
|
||||||
ccma = append(ccma, ccm)
|
ccma = append(ccma, ccm)
|
||||||
}
|
}
|
||||||
@@ -37,20 +34,11 @@ func BuildSummary(cr *ComplianceReport) *SummaryReport {
|
|||||||
|
|
||||||
type SummaryWriter struct {
|
type SummaryWriter struct {
|
||||||
Output io.Writer
|
Output io.Writer
|
||||||
Severities []string
|
|
||||||
SeverityHeadings []string
|
|
||||||
ColumnsHeading []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSummaryWriter(output io.Writer, requiredSevs []dbTypes.Severity, columnHeading []string) SummaryWriter {
|
func NewSummaryWriter(output io.Writer) SummaryWriter {
|
||||||
var severities []string
|
|
||||||
var severityHeadings []string
|
|
||||||
severities, severityHeadings = getRequiredSeverities(requiredSevs)
|
|
||||||
return SummaryWriter{
|
return SummaryWriter{
|
||||||
Output: output,
|
Output: output,
|
||||||
Severities: severities,
|
|
||||||
SeverityHeadings: severityHeadings,
|
|
||||||
ColumnsHeading: columnHeading,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,29 +54,32 @@ func (s SummaryWriter) Write(report *ComplianceReport) error {
|
|||||||
sr := BuildSummary(report)
|
sr := BuildSummary(report)
|
||||||
t := table.New(s.Output)
|
t := table.New(s.Output)
|
||||||
t.SetRowLines(false)
|
t.SetRowLines(false)
|
||||||
configureHeader(s, t, s.ColumnsHeading)
|
configureHeader(t, s.columns())
|
||||||
|
|
||||||
for _, summaryControl := range sr.SummaryControls {
|
for _, summaryControl := range sr.SummaryControls {
|
||||||
rowParts := make([]string, 0)
|
rowParts := s.generateSummary(summaryControl)
|
||||||
rowParts = append(rowParts, s.generateSummary(summaryControl)...)
|
|
||||||
t.AddRow(rowParts...)
|
t.AddRow(rowParts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Render()
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s SummaryWriter) columns() []string {
|
||||||
|
return []string{
|
||||||
|
ControlIDColumn,
|
||||||
|
SeverityColumn,
|
||||||
|
ControlNameColumn,
|
||||||
|
StatusColumn,
|
||||||
|
IssuesColumn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s SummaryWriter) generateSummary(summaryControls ControlCheckSummary) []string {
|
func (s SummaryWriter) generateSummary(summaryControls ControlCheckSummary) []string {
|
||||||
var numOfIssues string
|
// "-" means manual checks
|
||||||
var status string
|
numOfIssues := "-"
|
||||||
|
status := "-"
|
||||||
if summaryControls.TotalFail != nil {
|
if summaryControls.TotalFail != nil {
|
||||||
if *summaryControls.TotalFail == 0 {
|
if *summaryControls.TotalFail == 0 {
|
||||||
status = "PASS"
|
status = "PASS"
|
||||||
@@ -97,40 +88,24 @@ func (s SummaryWriter) generateSummary(summaryControls ControlCheckSummary) []st
|
|||||||
}
|
}
|
||||||
numOfIssues = strconv.Itoa(*summaryControls.TotalFail)
|
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) {
|
func configureHeader(t *table.Table, columnHeading []string) {
|
||||||
requiredSevOrder := []dbTypes.Severity{dbTypes.SeverityCritical,
|
headerAlignment := []table.Alignment{
|
||||||
dbTypes.SeverityHigh, dbTypes.SeverityMedium,
|
table.AlignLeft,
|
||||||
dbTypes.SeverityLow, dbTypes.SeverityUnknown}
|
table.AlignCenter,
|
||||||
var severities []string
|
table.AlignLeft,
|
||||||
var severityHeadings []string
|
table.AlignCenter,
|
||||||
for _, sev := range requiredSevOrder {
|
table.AlignCenter,
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
t.SetHeaders(columnHeading...)
|
t.SetHeaders(columnHeading...)
|
||||||
t.SetAlignment(headerAlignment...)
|
t.SetAlignment(headerAlignment...)
|
||||||
t.SetAutoMergeHeaders(true)
|
t.SetAutoMergeHeaders(true)
|
||||||
t.SetHeaderColSpans(0, colSpan...)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,14 +26,14 @@ const (
|
|||||||
IssuesColumn = "Issues"
|
IssuesColumn = "Issues"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (tw TableWriter) columns() []string {
|
|
||||||
return []string{ControlIDColumn, SeverityColumn, ControlNameColumn, StatusColumn, IssuesColumn}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tw TableWriter) Write(report *ComplianceReport) error {
|
func (tw TableWriter) Write(report *ComplianceReport) error {
|
||||||
switch tw.Report {
|
switch tw.Report {
|
||||||
case allReport:
|
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 {
|
for _, cr := range report.Results {
|
||||||
r := types.Report{Results: cr.Results}
|
r := types.Report{Results: cr.Results}
|
||||||
err := t.Write(r)
|
err := t.Write(r)
|
||||||
@@ -42,7 +42,7 @@ func (tw TableWriter) Write(report *ComplianceReport) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case summaryReport:
|
case summaryReport:
|
||||||
writer := NewSummaryWriter(tw.Output, tw.Severities, tw.columns())
|
writer := NewSummaryWriter(tw.Output)
|
||||||
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)
|
||||||
|
|||||||
@@ -6,5 +6,3 @@ Summary Report for compliance: NSA
|
|||||||
│ 1.0 │ MEDIUM │ Non-root containers │ FAIL │ 1 │
|
│ 1.0 │ MEDIUM │ Non-root containers │ FAIL │ 1 │
|
||||||
│ 1.1 │ LOW │ Immutable container file systems │ FAIL │ 1 │
|
│ 1.1 │ LOW │ Immutable container file systems │ FAIL │ 1 │
|
||||||
└─────┴──────────┴──────────────────────────────────┴────────┴────────┘
|
└─────┴──────────┴──────────────────────────────────┴────────┴────────┘
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
"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/pkg/types"
|
"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
|
// 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, "@") {
|
if strings.HasPrefix(specNameOrPath, "@") {
|
||||||
buf, err := os.ReadFile(strings.TrimPrefix(specNameOrPath, "@"))
|
b, err = os.ReadFile(strings.TrimPrefix(specNameOrPath, "@"))
|
||||||
if err != nil {
|
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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
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/log"
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
"github.com/aquasecurity/trivy/pkg/report"
|
"github.com/aquasecurity/trivy/pkg/report"
|
||||||
"github.com/aquasecurity/trivy/pkg/result"
|
"github.com/aquasecurity/trivy/pkg/result"
|
||||||
@@ -120,7 +121,7 @@ type ReportOptions struct {
|
|||||||
IgnorePolicy string
|
IgnorePolicy string
|
||||||
Output io.Writer
|
Output io.Writer
|
||||||
Severities []dbTypes.Severity
|
Severities []dbTypes.Severity
|
||||||
Compliance string
|
Compliance spec.ComplianceSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReportFlagGroup() *ReportFlagGroup {
|
func NewReportFlagGroup() *ReportFlagGroup {
|
||||||
@@ -144,8 +145,19 @@ func (f *ReportFlagGroup) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *ReportFlagGroup) Flags() []*Flag {
|
func (f *ReportFlagGroup) Flags() []*Flag {
|
||||||
return []*Flag{f.Format, f.ReportFormat, f.Template, f.DependencyTree, f.ListAllPkgs, f.IgnoreFile,
|
return []*Flag{
|
||||||
f.IgnorePolicy, f.ExitCode, f.Output, f.Severity, f.Compliance}
|
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) {
|
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 {
|
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{
|
return ReportOptions{
|
||||||
@@ -215,15 +227,21 @@ func (f *ReportFlagGroup) ToOptions(out io.Writer) (ReportOptions, error) {
|
|||||||
IgnorePolicy: getString(f.IgnorePolicy),
|
IgnorePolicy: getString(f.IgnorePolicy),
|
||||||
Output: out,
|
Output: out,
|
||||||
Severities: splitSeverity(getStringSlice(f.Severity)),
|
Severities: splitSeverity(getStringSlice(f.Severity)),
|
||||||
Compliance: complianceTypes,
|
Compliance: cs,
|
||||||
}, nil
|
}, 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, "@") {
|
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 {
|
func (f *ReportFlagGroup) forceListAllPkgs(format string, listAllPkgs, dependencyTree bool) bool {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"go.uber.org/zap/zaptest/observer"
|
"go.uber.org/zap/zaptest/observer"
|
||||||
|
|
||||||
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/flag"
|
"github.com/aquasecurity/trivy/pkg/flag"
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
"github.com/aquasecurity/trivy/pkg/report"
|
"github.com/aquasecurity/trivy/pkg/report"
|
||||||
@@ -174,12 +175,30 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "happy path with compliance",
|
name: "happy path with compliance",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
compliane: "k8s-nsa",
|
compliane: "@testdata/example-spec.yaml",
|
||||||
severities: "low",
|
severities: "low",
|
||||||
},
|
},
|
||||||
want: flag.ReportOptions{
|
want: flag.ReportOptions{
|
||||||
Output: os.Stdout,
|
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},
|
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
13
pkg/flag/testdata/example-spec.yaml
vendored
Normal file
13
pkg/flag/testdata/example-spec.yaml
vendored
Normal 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
|
||||||
@@ -6,13 +6,11 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
|
"github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
|
||||||
"github.com/aquasecurity/trivy-kubernetes/pkg/k8s"
|
"github.com/aquasecurity/trivy-kubernetes/pkg/k8s"
|
||||||
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/flag"
|
"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"
|
||||||
@@ -83,17 +81,9 @@ 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 spec.ComplianceSpec
|
|
||||||
// set scanners types by spec
|
// set scanners types by spec
|
||||||
if r.flagOpts.ReportOptions.Compliance != "" {
|
if r.flagOpts.Compliance.Spec.ID != "" {
|
||||||
cs, err := spec.GetComplianceSpec(r.flagOpts.ReportOptions.Compliance)
|
scanners, err := r.flagOpts.Compliance.Scanners()
|
||||||
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 err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("scanner error: %w", err)
|
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)
|
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
|
var scanResults []types.Results
|
||||||
for _, rss := range rpt.Vulnerabilities {
|
for _, rss := range rpt.Vulnerabilities {
|
||||||
scanResults = append(scanResults, rss.Results)
|
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 {
|
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, r.flagOpts.Compliance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("compliance report build error: %w", err)
|
return xerrors.Errorf("compliance report build error: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,16 +5,17 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/report/predicate"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/report/table"
|
|
||||||
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
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/log"
|
||||||
"github.com/aquasecurity/trivy/pkg/report/cyclonedx"
|
"github.com/aquasecurity/trivy/pkg/report/cyclonedx"
|
||||||
"github.com/aquasecurity/trivy/pkg/report/github"
|
"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/spdx"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/report/table"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,21 +34,38 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
SupportedFormats = []string{FormatTable, FormatJSON, FormatTemplate, FormatSarif, FormatCycloneDX, FormatSPDX, FormatSPDXJSON, FormatGitHub, FormatCosignVuln}
|
SupportedFormats = []string{
|
||||||
|
FormatTable,
|
||||||
|
FormatJSON,
|
||||||
|
FormatTemplate,
|
||||||
|
FormatSarif,
|
||||||
|
FormatCycloneDX,
|
||||||
|
FormatSPDX,
|
||||||
|
FormatSPDXJSON,
|
||||||
|
FormatGitHub,
|
||||||
|
FormatCosignVuln,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
SupportedSBOMFormats = []string{FormatCycloneDX, FormatSPDX, FormatSPDXJSON, FormatGitHub}
|
SupportedSBOMFormats = []string{
|
||||||
|
FormatCycloneDX,
|
||||||
|
FormatSPDX,
|
||||||
|
FormatSPDXJSON,
|
||||||
|
FormatGitHub,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type Option struct {
|
type Option struct {
|
||||||
AppVersion string
|
AppVersion string
|
||||||
|
|
||||||
Format string
|
Format string
|
||||||
|
Report string
|
||||||
Output io.Writer
|
Output io.Writer
|
||||||
Tree bool
|
Tree bool
|
||||||
Severities []dbTypes.Severity
|
Severities []dbTypes.Severity
|
||||||
OutputTemplate string
|
OutputTemplate string
|
||||||
|
Compliance spec.ComplianceSpec
|
||||||
|
|
||||||
// For misconfigurations
|
// For misconfigurations
|
||||||
IncludeNonFailures bool
|
IncludeNonFailures bool
|
||||||
@@ -60,6 +78,11 @@ type Option struct {
|
|||||||
|
|
||||||
// Write writes the result to output, format as passed in argument
|
// Write writes the result to output, format as passed in argument
|
||||||
func Write(report types.Report, option Option) error {
|
func Write(report types.Report, option Option) error {
|
||||||
|
// Compliance report
|
||||||
|
if option.Compliance.Spec.ID != "" {
|
||||||
|
return complianceWrite(report, option)
|
||||||
|
}
|
||||||
|
|
||||||
var writer Writer
|
var writer Writer
|
||||||
switch option.Format {
|
switch option.Format {
|
||||||
case FormatTable:
|
case FormatTable:
|
||||||
@@ -76,7 +99,10 @@ func Write(report types.Report, option Option) error {
|
|||||||
case FormatJSON:
|
case FormatJSON:
|
||||||
writer = &JSONWriter{Output: option.Output}
|
writer = &JSONWriter{Output: option.Output}
|
||||||
case FormatGitHub:
|
case FormatGitHub:
|
||||||
writer = &github.Writer{Output: option.Output, Version: option.AppVersion}
|
writer = &github.Writer{
|
||||||
|
Output: option.Output,
|
||||||
|
Version: option.AppVersion,
|
||||||
|
}
|
||||||
case FormatCycloneDX:
|
case FormatCycloneDX:
|
||||||
// TODO: support xml format option with cyclonedx writer
|
// TODO: support xml format option with cyclonedx writer
|
||||||
writer = cyclonedx.NewWriter(option.Output, option.AppVersion)
|
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.
|
// We keep `sarif.tpl` template working for backward compatibility for a while.
|
||||||
if strings.HasPrefix(option.OutputTemplate, "@") && strings.HasSuffix(option.OutputTemplate, "sarif.tpl") {
|
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")
|
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
|
break
|
||||||
}
|
}
|
||||||
var err error
|
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)
|
return xerrors.Errorf("failed to initialize template writer: %w", err)
|
||||||
}
|
}
|
||||||
case FormatSarif:
|
case FormatSarif:
|
||||||
writer = SarifWriter{Output: option.Output, Version: option.AppVersion}
|
writer = SarifWriter{
|
||||||
|
Output: option.Output,
|
||||||
|
Version: option.AppVersion,
|
||||||
|
}
|
||||||
case FormatCosignVuln:
|
case FormatCosignVuln:
|
||||||
writer = predicate.NewVulnWriter(option.Output, option.AppVersion)
|
writer = predicate.NewVulnWriter(option.Output, option.AppVersion)
|
||||||
default:
|
default:
|
||||||
@@ -107,6 +139,19 @@ func Write(report types.Report, option Option) error {
|
|||||||
return nil
|
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
|
// Writer defines the result write operation
|
||||||
type Writer interface {
|
type Writer interface {
|
||||||
Write(types.Report) error
|
Write(types.Report) error
|
||||||
|
|||||||
@@ -8,7 +8,13 @@ import (
|
|||||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
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
|
// Report represents a scan result
|
||||||
type Report struct {
|
type Report struct {
|
||||||
@@ -55,6 +61,7 @@ const (
|
|||||||
ComplianceK8sCIS = Compliance("k8s-cis")
|
ComplianceK8sCIS = Compliance("k8s-cis")
|
||||||
ComplianceAWSCIS12 = Compliance("aws-cis-1.2")
|
ComplianceAWSCIS12 = Compliance("aws-cis-1.2")
|
||||||
ComplianceAWSCIS14 = Compliance("aws-cis-1.4")
|
ComplianceAWSCIS14 = Compliance("aws-cis-1.4")
|
||||||
|
ComplianceDockerCIS = Compliance("docker-cis")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Result holds a target and detected vulnerabilities
|
// Result holds a target and detected vulnerabilities
|
||||||
|
|||||||
Reference in New Issue
Block a user