mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-23 15:37:50 -08:00
fix(report): close the file (#4842)
* fix(report): close the file * refactor: add the format type * fix: return errors in version printing * fix: lint issues * fix: do not fail on bogus cache dir --------- Co-authored-by: DmitriyLewen <dmitriy.lewen@smartforce.io>
This commit is contained in:
@@ -5,10 +5,9 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/defsec/pkg/errs"
|
||||
@@ -17,10 +16,8 @@ import (
|
||||
"github.com/aquasecurity/trivy/pkg/cloud/aws/scanner"
|
||||
"github.com/aquasecurity/trivy/pkg/cloud/report"
|
||||
"github.com/aquasecurity/trivy/pkg/commands/operation"
|
||||
cr "github.com/aquasecurity/trivy/pkg/compliance/report"
|
||||
"github.com/aquasecurity/trivy/pkg/flag"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
var allSupportedServicesFunc = awsScanner.AllSupportedServices
|
||||
@@ -166,24 +163,6 @@ func Run(ctx context.Context, opt flag.Options) error {
|
||||
}
|
||||
|
||||
log.Logger.Debug("Writing report to output...")
|
||||
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, 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.ReportFormat,
|
||||
Output: opt.Output,
|
||||
})
|
||||
}
|
||||
|
||||
res := results.GetFailed()
|
||||
if opt.MisconfOptions.IncludeNonFailures {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -1243,8 +1242,8 @@ Summary Report for compliance: my-custom-spec
|
||||
}()
|
||||
}
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
test.options.Output = buffer
|
||||
output := filepath.Join(t.TempDir(), "output")
|
||||
test.options.Output = output
|
||||
test.options.Debug = true
|
||||
test.options.GlobalOptions.Timeout = time.Minute
|
||||
if test.options.Format == "" {
|
||||
@@ -1283,10 +1282,13 @@ Summary Report for compliance: my-custom-spec
|
||||
err := Run(context.Background(), test.options)
|
||||
if test.expectErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.want, buffer.String())
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
|
||||
b, err := os.ReadFile(output)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.want, string(b))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,16 @@ package report
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/defsec/pkg/scan"
|
||||
"github.com/aquasecurity/tml"
|
||||
cr "github.com/aquasecurity/trivy/pkg/compliance/report"
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/flag"
|
||||
pkgReport "github.com/aquasecurity/trivy/pkg/report"
|
||||
@@ -55,6 +59,15 @@ func (r *Report) Failed() bool {
|
||||
|
||||
// Write writes the results in the give format
|
||||
func Write(rep *Report, opt flag.Options, fromCache bool) error {
|
||||
output, err := opt.OutputWriter()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to create output file: %w", err)
|
||||
}
|
||||
defer output.Close()
|
||||
|
||||
if opt.Compliance.Spec.ID != "" {
|
||||
return writeCompliance(rep, opt, output)
|
||||
}
|
||||
|
||||
var filtered []types.Result
|
||||
|
||||
@@ -91,7 +104,7 @@ func Write(rep *Report, opt flag.Options, fromCache bool) error {
|
||||
|
||||
// ensure color/formatting is disabled for pipes/non-pty
|
||||
var useANSI bool
|
||||
if opt.Output == os.Stdout {
|
||||
if opt.Output == "" {
|
||||
if o, err := os.Stdout.Stat(); err == nil {
|
||||
useANSI = (o.Mode() & os.ModeCharDevice) == os.ModeCharDevice
|
||||
}
|
||||
@@ -102,33 +115,44 @@ func Write(rep *Report, opt flag.Options, fromCache bool) error {
|
||||
|
||||
switch {
|
||||
case len(opt.Services) == 1 && opt.ARN == "":
|
||||
if err := writeResourceTable(rep, filtered, opt.Output, opt.Services[0]); err != nil {
|
||||
if err := writeResourceTable(rep, filtered, output, opt.Services[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
case len(opt.Services) == 1 && opt.ARN != "":
|
||||
if err := writeResultsForARN(rep, filtered, opt.Output, opt.Services[0], opt.ARN, opt.Severities); err != nil {
|
||||
if err := writeResultsForARN(rep, filtered, output, opt.Services[0], opt.ARN, opt.Severities); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
if err := writeServiceTable(rep, filtered, opt.Output); err != nil {
|
||||
if err := writeServiceTable(rep, filtered, output); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// render cache info
|
||||
if fromCache {
|
||||
_ = tml.Fprintf(opt.Output, "\n<blue>This scan report was loaded from cached results. If you'd like to run a fresh scan, use --update-cache.</blue>\n")
|
||||
_ = tml.Fprintf(output, "\n<blue>This scan report was loaded from cached results. If you'd like to run a fresh scan, use --update-cache.</blue>\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
default:
|
||||
return pkgReport.Write(base, pkgReport.Option{
|
||||
return pkgReport.Write(base, opt)
|
||||
}
|
||||
}
|
||||
|
||||
func writeCompliance(rep *Report, opt flag.Options, output io.Writer) error {
|
||||
var crr []types.Results
|
||||
for _, r := range rep.Results {
|
||||
crr = append(crr, r.Results)
|
||||
}
|
||||
|
||||
complianceReport, err := cr.BuildComplianceReport(crr, opt.Compliance)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("compliance report build error: %w", err)
|
||||
}
|
||||
|
||||
return cr.Write(complianceReport, cr.Option{
|
||||
Format: opt.Format,
|
||||
Output: opt.Output,
|
||||
Severities: opt.Severities,
|
||||
OutputTemplate: opt.Template,
|
||||
IncludeNonFailures: opt.IncludeNonFailures,
|
||||
Trace: opt.Trace,
|
||||
Report: opt.ReportFormat,
|
||||
Output: output,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -109,15 +110,18 @@ No problems detected.
|
||||
tt.options.AWSOptions.Services,
|
||||
)
|
||||
|
||||
buffer := bytes.NewBuffer([]byte{})
|
||||
tt.options.Output = buffer
|
||||
output := filepath.Join(t.TempDir(), "output")
|
||||
tt.options.Output = output
|
||||
require.NoError(t, Write(report, tt.options, tt.fromCache))
|
||||
|
||||
assert.Equal(t, "AWS", report.Provider)
|
||||
assert.Equal(t, tt.options.AWSOptions.Account, report.AccountID)
|
||||
assert.Equal(t, tt.options.AWSOptions.Region, report.Region)
|
||||
assert.ElementsMatch(t, tt.options.AWSOptions.Services, report.ServicesInScope)
|
||||
assert.Equal(t, tt.expected, buffer.String())
|
||||
|
||||
b, err := os.ReadFile(output)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, string(b))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -68,15 +69,18 @@ See https://avd.aquasec.com/misconfig/avd-aws-9999
|
||||
tt.options.AWSOptions.Services,
|
||||
)
|
||||
|
||||
buffer := bytes.NewBuffer([]byte{})
|
||||
tt.options.Output = buffer
|
||||
output := filepath.Join(t.TempDir(), "output")
|
||||
tt.options.Output = output
|
||||
require.NoError(t, Write(report, tt.options, tt.fromCache))
|
||||
|
||||
b, err := os.ReadFile(output)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "AWS", report.Provider)
|
||||
assert.Equal(t, tt.options.AWSOptions.Account, report.AccountID)
|
||||
assert.Equal(t, tt.options.AWSOptions.Region, report.Region)
|
||||
assert.ElementsMatch(t, tt.options.AWSOptions.Services, report.ServicesInScope)
|
||||
assert.Equal(t, tt.expected, strings.ReplaceAll(buffer.String(), "\r\n", "\n"))
|
||||
assert.Equal(t, tt.expected, strings.ReplaceAll(string(b), "\r\n", "\n"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/aquasecurity/trivy-db/pkg/types"
|
||||
@@ -320,19 +321,22 @@ Scan Overview for AWS Account
|
||||
tt.options.AWSOptions.Services,
|
||||
)
|
||||
|
||||
buffer := bytes.NewBuffer([]byte{})
|
||||
tt.options.Output = buffer
|
||||
output := filepath.Join(t.TempDir(), "output")
|
||||
tt.options.Output = output
|
||||
require.NoError(t, Write(report, tt.options, tt.fromCache))
|
||||
|
||||
assert.Equal(t, "AWS", report.Provider)
|
||||
assert.Equal(t, tt.options.AWSOptions.Account, report.AccountID)
|
||||
assert.Equal(t, tt.options.AWSOptions.Region, report.Region)
|
||||
assert.ElementsMatch(t, tt.options.AWSOptions.Services, report.ServicesInScope)
|
||||
|
||||
b, err := os.ReadFile(output)
|
||||
require.NoError(t, err)
|
||||
if tt.options.Format == "json" {
|
||||
// json output can be formatted/ordered differently - we just care that the data matches
|
||||
assert.JSONEq(t, tt.expected, buffer.String())
|
||||
assert.JSONEq(t, tt.expected, string(b))
|
||||
} else {
|
||||
assert.Equal(t, tt.expected, buffer.String())
|
||||
assert.Equal(t, tt.expected, string(b))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ import (
|
||||
"github.com/aquasecurity/trivy/pkg/module"
|
||||
"github.com/aquasecurity/trivy/pkg/plugin"
|
||||
"github.com/aquasecurity/trivy/pkg/policy"
|
||||
r "github.com/aquasecurity/trivy/pkg/report"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
xstrings "github.com/aquasecurity/trivy/pkg/x/strings"
|
||||
)
|
||||
|
||||
// VersionInfo holds the trivy DB version Info
|
||||
@@ -71,15 +71,6 @@ Use "{{.CommandPath}} [command] --help" for more information about a command.{{e
|
||||
groupPlugin = "plugin"
|
||||
)
|
||||
|
||||
var (
|
||||
outputWriter io.Writer = os.Stdout
|
||||
)
|
||||
|
||||
// SetOut overrides the destination for messages
|
||||
func SetOut(out io.Writer) {
|
||||
outputWriter = out
|
||||
}
|
||||
|
||||
// NewApp is the factory method to return Trivy CLI
|
||||
func NewApp(version string) *cobra.Command {
|
||||
globalFlags := flag.NewGlobalFlagGroup()
|
||||
@@ -189,8 +180,6 @@ func NewRootCommand(version string, globalFlags *flag.GlobalFlagGroup) *cobra.Co
|
||||
$ trivy server`,
|
||||
Args: cobra.NoArgs,
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
cmd.SetOut(outputWriter)
|
||||
|
||||
// Set the Trivy version here so that we can override version printer.
|
||||
cmd.Version = version
|
||||
|
||||
@@ -224,11 +213,10 @@ func NewRootCommand(version string, globalFlags *flag.GlobalFlagGroup) *cobra.Co
|
||||
globalOptions := globalFlags.ToOptions()
|
||||
if globalOptions.ShowVersion {
|
||||
// Customize version output
|
||||
showVersion(globalOptions.CacheDir, versionFormat, version, outputWriter)
|
||||
return showVersion(globalOptions.CacheDir, versionFormat, version, cmd.OutOrStdout())
|
||||
} else {
|
||||
return cmd.Help()
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -310,7 +298,7 @@ func NewImageCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
return validateArgs(cmd, args)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options, err := imageFlags.ToOptions(cmd.Version, args, globalFlags, outputWriter)
|
||||
options, err := imageFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -369,7 +357,7 @@ func NewFilesystemCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
if err := fsFlags.Bind(cmd); err != nil {
|
||||
return xerrors.Errorf("flag bind error: %w", err)
|
||||
}
|
||||
options, err := fsFlags.ToOptions(cmd.Version, args, globalFlags, outputWriter)
|
||||
options, err := fsFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -428,7 +416,7 @@ func NewRootfsCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
if err := rootfsFlags.Bind(cmd); err != nil {
|
||||
return xerrors.Errorf("flag bind error: %w", err)
|
||||
}
|
||||
options, err := rootfsFlags.ToOptions(cmd.Version, args, globalFlags, outputWriter)
|
||||
options, err := rootfsFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -481,7 +469,7 @@ func NewRepositoryCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
if err := repoFlags.Bind(cmd); err != nil {
|
||||
return xerrors.Errorf("flag bind error: %w", err)
|
||||
}
|
||||
options, err := repoFlags.ToOptions(cmd.Version, args, globalFlags, outputWriter)
|
||||
options, err := repoFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -521,7 +509,7 @@ func NewConvertCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
if err := convertFlags.Bind(cmd); err != nil {
|
||||
return xerrors.Errorf("flag bind error: %w", err)
|
||||
}
|
||||
opts, err := convertFlags.ToOptions(cmd.Version, args, globalFlags, outputWriter)
|
||||
opts, err := convertFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -578,7 +566,7 @@ func NewClientCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
if err := clientFlags.Bind(cmd); err != nil {
|
||||
return xerrors.Errorf("flag bind error: %w", err)
|
||||
}
|
||||
options, err := clientFlags.ToOptions(cmd.Version, args, globalFlags, outputWriter)
|
||||
options, err := clientFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -619,7 +607,7 @@ func NewServerCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
if err := serverFlags.Bind(cmd); err != nil {
|
||||
return xerrors.Errorf("flag bind error: %w", err)
|
||||
}
|
||||
options, err := serverFlags.ToOptions(cmd.Version, args, globalFlags, outputWriter)
|
||||
options, err := serverFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -681,7 +669,7 @@ func NewConfigCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
if err := configFlags.Bind(cmd); err != nil {
|
||||
return xerrors.Errorf("flag bind error: %w", err)
|
||||
}
|
||||
options, err := configFlags.ToOptions(cmd.Version, args, globalFlags, outputWriter)
|
||||
options, err := configFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -839,7 +827,7 @@ func NewModuleCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
}
|
||||
|
||||
repo := args[0]
|
||||
opts, err := moduleFlags.ToOptions(cmd.Version, args, globalFlags, outputWriter)
|
||||
opts, err := moduleFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -863,7 +851,7 @@ func NewModuleCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
}
|
||||
|
||||
repo := args[0]
|
||||
opts, err := moduleFlags.ToOptions(cmd.Version, args, globalFlags, outputWriter)
|
||||
opts, err := moduleFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -904,11 +892,11 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol'
|
||||
|
||||
formatFlag := flag.FormatFlag
|
||||
formatFlag.Values = []string{
|
||||
r.FormatTable,
|
||||
r.FormatJSON,
|
||||
r.FormatCycloneDX,
|
||||
}
|
||||
formatFlag.Values = xstrings.ToStringSlice([]types.Format{
|
||||
types.FormatTable,
|
||||
types.FormatJSON,
|
||||
types.FormatCycloneDX,
|
||||
})
|
||||
reportFlagGroup.Format = &formatFlag
|
||||
|
||||
k8sFlags := &flag.Flags{
|
||||
@@ -952,7 +940,7 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
if err := k8sFlags.Bind(cmd); err != nil {
|
||||
return xerrors.Errorf("flag bind error: %w", err)
|
||||
}
|
||||
opts, err := k8sFlags.ToOptions(cmd.Version, args, globalFlags, outputWriter)
|
||||
opts, err := k8sFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -972,7 +960,10 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
func NewAWSCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
reportFlagGroup := flag.NewReportFlagGroup()
|
||||
compliance := flag.ComplianceFlag
|
||||
compliance.Values = []string{types.ComplianceAWSCIS12, types.ComplianceAWSCIS14}
|
||||
compliance.Values = []string{
|
||||
types.ComplianceAWSCIS12,
|
||||
types.ComplianceAWSCIS14,
|
||||
}
|
||||
reportFlagGroup.Compliance = &compliance // override usage as the accepted values differ for each subcommand.
|
||||
reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol'
|
||||
|
||||
@@ -1016,7 +1007,7 @@ The following services are supported:
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts, err := awsFlags.ToOptions(cmd.Version, args, globalFlags, outputWriter)
|
||||
opts, err := awsFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -1080,7 +1071,7 @@ func NewVMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
if err := vmFlags.Bind(cmd); err != nil {
|
||||
return xerrors.Errorf("flag bind error: %w", err)
|
||||
}
|
||||
options, err := vmFlags.ToOptions(cmd.Version, args, globalFlags, outputWriter)
|
||||
options, err := vmFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -1139,7 +1130,7 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
if err := sbomFlags.Bind(cmd); err != nil {
|
||||
return xerrors.Errorf("flag bind error: %w", err)
|
||||
}
|
||||
options, err := sbomFlags.ToOptions(cmd.Version, args, globalFlags, outputWriter)
|
||||
options, err := sbomFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -1168,9 +1159,7 @@ func NewVersionCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options := globalFlags.ToOptions()
|
||||
showVersion(options.CacheDir, versionFormat, cmd.Version, outputWriter)
|
||||
|
||||
return nil
|
||||
return showVersion(options.CacheDir, versionFormat, cmd.Version, cmd.OutOrStdout())
|
||||
},
|
||||
SilenceErrors: true,
|
||||
SilenceUsage: true,
|
||||
@@ -1183,12 +1172,15 @@ func NewVersionCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func showVersion(cacheDir, outputFormat, version string, outputWriter io.Writer) {
|
||||
func showVersion(cacheDir, outputFormat, version string, w io.Writer) error {
|
||||
var dbMeta *metadata.Metadata
|
||||
var javadbMeta *metadata.Metadata
|
||||
|
||||
mc := metadata.NewClient(cacheDir)
|
||||
meta, _ := mc.Get() // nolint: errcheck
|
||||
meta, err := mc.Get()
|
||||
if err != nil {
|
||||
log.Logger.Debugw("Failed to get DB metadata", "error", err)
|
||||
}
|
||||
if !meta.UpdatedAt.IsZero() && !meta.NextUpdate.IsZero() && meta.Version != 0 {
|
||||
dbMeta = &metadata.Metadata{
|
||||
Version: meta.Version,
|
||||
@@ -1199,7 +1191,10 @@ func showVersion(cacheDir, outputFormat, version string, outputWriter io.Writer)
|
||||
}
|
||||
|
||||
mcJava := javadb.NewMetadata(filepath.Join(cacheDir, "java-db"))
|
||||
metaJava, _ := mcJava.Get() // nolint: errcheck
|
||||
metaJava, err := mcJava.Get()
|
||||
if err != nil {
|
||||
log.Logger.Debugw("Failed to get Java DB metadata", "error", err)
|
||||
}
|
||||
if !metaJava.UpdatedAt.IsZero() && !metaJava.NextUpdate.IsZero() && metaJava.Version != 0 {
|
||||
javadbMeta = &metadata.Metadata{
|
||||
Version: metaJava.Version,
|
||||
@@ -1212,18 +1207,23 @@ func showVersion(cacheDir, outputFormat, version string, outputWriter io.Writer)
|
||||
var pbMeta *policy.Metadata
|
||||
pc, err := policy.NewClient(cacheDir, false)
|
||||
if pc != nil && err == nil {
|
||||
pbMeta, _ = pc.GetMetadata()
|
||||
pbMeta, err = pc.GetMetadata()
|
||||
if err != nil {
|
||||
log.Logger.Debugw("Failed to get policy metadata", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
switch outputFormat {
|
||||
case "json":
|
||||
b, _ := json.Marshal(VersionInfo{
|
||||
err = json.NewEncoder(w).Encode(VersionInfo{
|
||||
Version: version,
|
||||
VulnerabilityDB: dbMeta,
|
||||
JavaDB: javadbMeta,
|
||||
PolicyBundle: pbMeta,
|
||||
})
|
||||
fmt.Fprintln(outputWriter, string(b))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("json encode error: %w", err)
|
||||
}
|
||||
default:
|
||||
output := fmt.Sprintf("Version: %s\n", version)
|
||||
if dbMeta != nil {
|
||||
@@ -1250,8 +1250,9 @@ func showVersion(cacheDir, outputFormat, version string, outputWriter io.Writer)
|
||||
DownloadedAt: %s
|
||||
`, pbMeta.Digest, pbMeta.DownloadedAt.UTC())
|
||||
}
|
||||
fmt.Fprintf(outputWriter, output)
|
||||
fmt.Fprintf(w, output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateArgs(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/flag"
|
||||
"github.com/aquasecurity/trivy/pkg/report"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
func Test_showVersion(t *testing.T) {
|
||||
@@ -158,7 +158,7 @@ Policy Bundle:
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got := new(bytes.Buffer)
|
||||
app := NewApp("test")
|
||||
SetOut(got)
|
||||
app.SetOut(got)
|
||||
app.SetArgs(test.arguments)
|
||||
|
||||
err := app.Execute()
|
||||
@@ -170,7 +170,7 @@ Policy Bundle:
|
||||
|
||||
func TestFlags(t *testing.T) {
|
||||
type want struct {
|
||||
format string
|
||||
format types.Format
|
||||
severities []dbTypes.Severity
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -185,7 +185,7 @@ func TestFlags(t *testing.T) {
|
||||
"test",
|
||||
},
|
||||
want: want{
|
||||
format: report.FormatTable,
|
||||
format: types.FormatTable,
|
||||
severities: []dbTypes.Severity{
|
||||
dbTypes.SeverityUnknown,
|
||||
dbTypes.SeverityLow,
|
||||
@@ -203,7 +203,7 @@ func TestFlags(t *testing.T) {
|
||||
"LOW,MEDIUM",
|
||||
},
|
||||
want: want{
|
||||
format: report.FormatTable,
|
||||
format: types.FormatTable,
|
||||
severities: []dbTypes.Severity{
|
||||
dbTypes.SeverityLow,
|
||||
dbTypes.SeverityMedium,
|
||||
@@ -220,7 +220,7 @@ func TestFlags(t *testing.T) {
|
||||
"HIGH",
|
||||
},
|
||||
want: want{
|
||||
format: report.FormatTable,
|
||||
format: types.FormatTable,
|
||||
severities: []dbTypes.Severity{
|
||||
dbTypes.SeverityLow,
|
||||
dbTypes.SeverityHigh,
|
||||
@@ -237,7 +237,7 @@ func TestFlags(t *testing.T) {
|
||||
"CRITICAL",
|
||||
},
|
||||
want: want{
|
||||
format: report.FormatJSON,
|
||||
format: types.FormatJSON,
|
||||
severities: []dbTypes.Severity{
|
||||
dbTypes.SeverityCritical,
|
||||
},
|
||||
@@ -259,7 +259,7 @@ func TestFlags(t *testing.T) {
|
||||
globalFlags := flag.NewGlobalFlagGroup()
|
||||
rootCmd := NewRootCommand("dev", globalFlags)
|
||||
rootCmd.SetErr(io.Discard)
|
||||
SetOut(io.Discard)
|
||||
rootCmd.SetOut(io.Discard)
|
||||
|
||||
flags := &flag.Flags{
|
||||
ReportFlagGroup: flag.NewReportFlagGroup(),
|
||||
@@ -270,7 +270,7 @@ func TestFlags(t *testing.T) {
|
||||
// Bind
|
||||
require.NoError(t, flags.Bind(cmd))
|
||||
|
||||
options, err := flags.ToOptions("dev", args, globalFlags, nil)
|
||||
options, err := flags.ToOptions("dev", args, globalFlags)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.want.format, options.Format)
|
||||
|
||||
@@ -282,7 +282,7 @@ func (r *runner) Filter(ctx context.Context, opts flag.Options, report types.Rep
|
||||
}
|
||||
|
||||
func (r *runner) Report(opts flag.Options, report types.Report) error {
|
||||
if err := pkgReport.Write(report, opts.ReportOpts()); err != nil {
|
||||
if err := pkgReport.Write(report, opts); err != nil {
|
||||
return xerrors.Errorf("unable to write results: %w", err)
|
||||
}
|
||||
|
||||
@@ -325,7 +325,7 @@ func (r *runner) initJavaDB(opts flag.Options) error {
|
||||
|
||||
// If vulnerability scanning and SBOM generation are disabled, it doesn't need to download the Java database.
|
||||
if !opts.Scanners.Enabled(types.VulnerabilityScanner) &&
|
||||
!slices.Contains(pkgReport.SupportedSBOMFormats, opts.Format) {
|
||||
!slices.Contains(types.SupportedSBOMFormats, opts.Format) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -497,7 +497,7 @@ func disabledAnalyzers(opts flag.Options) []analyzer.Type {
|
||||
// But we don't create client if vulnerability analysis is disabled and SBOM format is not used
|
||||
// We need to disable jar analyzer to avoid errors
|
||||
// TODO disable all languages that don't contain license information for this case
|
||||
if !opts.Scanners.Enabled(types.VulnerabilityScanner) && !slices.Contains(pkgReport.SupportedSBOMFormats, opts.Format) {
|
||||
if !opts.Scanners.Enabled(types.VulnerabilityScanner) && !slices.Contains(types.SupportedSBOMFormats, opts.Format) {
|
||||
analyzers = append(analyzers, analyzer.TypeJar)
|
||||
}
|
||||
|
||||
@@ -612,7 +612,7 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi
|
||||
|
||||
// SPDX needs to calculate digests for package files
|
||||
var fileChecksum bool
|
||||
if opts.Format == pkgReport.FormatSPDXJSON || opts.Format == pkgReport.FormatSPDX {
|
||||
if opts.Format == types.FormatSPDXJSON || opts.Format == types.FormatSPDX {
|
||||
fileChecksum = true
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ func Run(ctx context.Context, opts flag.Options) (err error) {
|
||||
}
|
||||
|
||||
log.Logger.Debug("Writing report to output...")
|
||||
if err = report.Write(r, opts.ReportOpts()); err != nil {
|
||||
if err = report.Write(r, opts); err != nil {
|
||||
return xerrors.Errorf("unable to write results: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,13 +15,10 @@ import (
|
||||
const (
|
||||
allReport = "all"
|
||||
summaryReport = "summary"
|
||||
|
||||
tableFormat = "table"
|
||||
jsonFormat = "json"
|
||||
)
|
||||
|
||||
type Option struct {
|
||||
Format string
|
||||
Format types.Format
|
||||
Report string
|
||||
Output io.Writer
|
||||
Severities []dbTypes.Severity
|
||||
@@ -70,10 +67,10 @@ type Writer interface {
|
||||
// Write writes the results in the give format
|
||||
func Write(report *ComplianceReport, option Option) error {
|
||||
switch option.Format {
|
||||
case jsonFormat:
|
||||
case types.FormatJSON:
|
||||
jwriter := JSONWriter{Output: option.Output, Report: option.Report}
|
||||
return jwriter.Write(report)
|
||||
case tableFormat:
|
||||
case types.FormatTable:
|
||||
if !report.empty() {
|
||||
complianceWriter := &TableWriter{
|
||||
Output: option.Output,
|
||||
|
||||
@@ -10,14 +10,14 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/mapfs"
|
||||
"github.com/aquasecurity/trivy/pkg/syncx"
|
||||
"github.com/aquasecurity/trivy/pkg/x/sync"
|
||||
)
|
||||
|
||||
// CompositeFS contains multiple filesystems for post-analyzers
|
||||
type CompositeFS struct {
|
||||
group AnalyzerGroup
|
||||
dir string
|
||||
files *syncx.Map[Type, *mapfs.FS]
|
||||
files *sync.Map[Type, *mapfs.FS]
|
||||
}
|
||||
|
||||
func NewCompositeFS(group AnalyzerGroup) (*CompositeFS, error) {
|
||||
@@ -29,7 +29,7 @@ func NewCompositeFS(group AnalyzerGroup) (*CompositeFS, error) {
|
||||
return &CompositeFS{
|
||||
group: group,
|
||||
dir: tmpDir,
|
||||
files: new(syncx.Map[Type, *mapfs.FS]),
|
||||
files: new(sync.Map[Type, *mapfs.FS]),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package types
|
||||
|
||||
import (
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -106,9 +105,3 @@ type Credential struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func (runtimes ImageSources) StringSlice() []string {
|
||||
return lo.Map(runtimes, func(r ImageSource, _ int) string {
|
||||
return string(r)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
xstrings "github.com/aquasecurity/trivy/pkg/x/strings"
|
||||
)
|
||||
|
||||
// e.g. config yaml
|
||||
@@ -18,10 +19,10 @@ var (
|
||||
Name: "image-config-scanners",
|
||||
ConfigName: "image.image-config-scanners",
|
||||
Default: []string{},
|
||||
Values: types.Scanners{
|
||||
Values: xstrings.ToStringSlice(types.Scanners{
|
||||
types.MisconfigScanner,
|
||||
types.SecretScanner,
|
||||
}.StringSlice(),
|
||||
}),
|
||||
Usage: "comma-separated list of what security issues to detect on container image configurations",
|
||||
}
|
||||
ScanRemovedPkgsFlag = Flag{
|
||||
@@ -51,8 +52,8 @@ var (
|
||||
SourceFlag = Flag{
|
||||
Name: "image-src",
|
||||
ConfigName: "image.source",
|
||||
Default: ftypes.AllImageSources.StringSlice(),
|
||||
Values: ftypes.AllImageSources.StringSlice(),
|
||||
Default: xstrings.ToStringSlice(ftypes.AllImageSources),
|
||||
Values: xstrings.ToStringSlice(ftypes.AllImageSources),
|
||||
Usage: "image source(s) to use, in priority order",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/cast"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -18,14 +17,12 @@ import (
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/report"
|
||||
"github.com/aquasecurity/trivy/pkg/result"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
xio "github.com/aquasecurity/trivy/pkg/x/io"
|
||||
xstrings "github.com/aquasecurity/trivy/pkg/x/strings"
|
||||
)
|
||||
|
||||
type String interface {
|
||||
~string
|
||||
}
|
||||
|
||||
type Flag struct {
|
||||
// Name is for CLI flag and environment variable.
|
||||
// If this field is empty, it will be available only in config file.
|
||||
@@ -120,18 +117,18 @@ type Options struct {
|
||||
|
||||
// Align takes consistency of options
|
||||
func (o *Options) Align() {
|
||||
if o.Format == report.FormatSPDX || o.Format == report.FormatSPDXJSON {
|
||||
if o.Format == types.FormatSPDX || o.Format == types.FormatSPDXJSON {
|
||||
log.Logger.Info(`"--format spdx" and "--format spdx-json" disable security scanning`)
|
||||
o.Scanners = nil
|
||||
}
|
||||
|
||||
// Vulnerability scanning is disabled by default for CycloneDX.
|
||||
if o.Format == report.FormatCycloneDX && !viper.IsSet(ScannersFlag.ConfigName) && len(o.K8sOptions.Components) == 0 { // remove K8sOptions.Components validation check when vuln scan is supported for k8s report with cycloneDX
|
||||
if o.Format == types.FormatCycloneDX && !viper.IsSet(ScannersFlag.ConfigName) && len(o.K8sOptions.Components) == 0 { // remove K8sOptions.Components validation check when vuln scan is supported for k8s report with cycloneDX
|
||||
log.Logger.Info(`"--format cyclonedx" disables security scanning. Specify "--scanners vuln" explicitly if you want to include vulnerabilities in the CycloneDX report.`)
|
||||
o.Scanners = nil
|
||||
}
|
||||
|
||||
if o.Format == report.FormatCycloneDX && len(o.K8sOptions.Components) > 0 {
|
||||
if o.Format == types.FormatCycloneDX && len(o.K8sOptions.Components) > 0 {
|
||||
log.Logger.Info(`"k8s with --format cyclonedx" disable security scanning`)
|
||||
o.Scanners = nil
|
||||
}
|
||||
@@ -161,19 +158,17 @@ func (o *Options) FilterOpts() result.FilterOption {
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Options) ReportOpts() report.Option {
|
||||
return report.Option{
|
||||
AppVersion: o.AppVersion,
|
||||
Format: o.Format,
|
||||
Output: o.Output,
|
||||
Tree: o.DependencyTree,
|
||||
Severities: o.Severities,
|
||||
OutputTemplate: o.Template,
|
||||
IncludeNonFailures: o.IncludeNonFailures,
|
||||
Trace: o.Trace,
|
||||
Report: o.ReportFormat,
|
||||
Compliance: o.Compliance,
|
||||
// OutputWriter returns an output writer.
|
||||
// If the output file is not specified, it returns os.Stdout.
|
||||
func (o *Options) OutputWriter() (io.WriteCloser, error) {
|
||||
if o.Output != "" {
|
||||
f, err := os.Create(o.Output)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to create output file: %w", err)
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
return xio.NopCloser(os.Stdout), nil
|
||||
}
|
||||
|
||||
func addFlag(cmd *cobra.Command, flag *Flag) {
|
||||
@@ -268,6 +263,11 @@ func getString(flag *Flag) string {
|
||||
return cast.ToString(getValue(flag))
|
||||
}
|
||||
|
||||
func getUnderlyingString[T xstrings.String](flag *Flag) T {
|
||||
s := getString(flag)
|
||||
return T(s)
|
||||
}
|
||||
|
||||
func getStringSlice(flag *Flag) []string {
|
||||
// viper always returns a string for ENV
|
||||
// https://github.com/spf13/viper/blob/419fd86e49ef061d0d33f4d1d56d5e2a480df5bb/viper.go#L545-L553
|
||||
@@ -283,14 +283,12 @@ func getStringSlice(flag *Flag) []string {
|
||||
return v
|
||||
}
|
||||
|
||||
func getUnderlyingStringSlice[T String](flag *Flag) []T {
|
||||
func getUnderlyingStringSlice[T xstrings.String](flag *Flag) []T {
|
||||
ss := getStringSlice(flag)
|
||||
if len(ss) == 0 {
|
||||
return nil
|
||||
}
|
||||
return lo.Map(ss, func(s string, _ int) T {
|
||||
return T(s)
|
||||
})
|
||||
return xstrings.ToTSlice[T](ss)
|
||||
}
|
||||
|
||||
func getInt(flag *Flag) int {
|
||||
@@ -441,7 +439,7 @@ func (f *Flags) Bind(cmd *cobra.Command) error {
|
||||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
func (f *Flags) ToOptions(appVersion string, args []string, globalFlags *GlobalFlagGroup, output io.Writer) (Options, error) {
|
||||
func (f *Flags) ToOptions(appVersion string, args []string, globalFlags *GlobalFlagGroup) (Options, error) {
|
||||
var err error
|
||||
opts := Options{
|
||||
AppVersion: appVersion,
|
||||
@@ -522,7 +520,7 @@ func (f *Flags) ToOptions(appVersion string, args []string, globalFlags *GlobalF
|
||||
}
|
||||
|
||||
if f.ReportFlagGroup != nil {
|
||||
opts.ReportOptions, err = f.ReportFlagGroup.ToOptions(output)
|
||||
opts.ReportOptions, err = f.ReportFlagGroup.ToOptions()
|
||||
if err != nil {
|
||||
return Options{}, xerrors.Errorf("report flag error: %w", err)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package flag
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
@@ -12,9 +10,9 @@ import (
|
||||
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"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
xstrings "github.com/aquasecurity/trivy/pkg/x/strings"
|
||||
)
|
||||
|
||||
// e.g. config yaml:
|
||||
@@ -27,8 +25,8 @@ var (
|
||||
Name: "format",
|
||||
ConfigName: "format",
|
||||
Shorthand: "f",
|
||||
Default: report.FormatTable,
|
||||
Values: report.SupportedFormats,
|
||||
Default: string(types.FormatTable),
|
||||
Values: xstrings.ToStringSlice(types.SupportedFormats),
|
||||
Usage: "format",
|
||||
}
|
||||
ReportFormatFlag = Flag{
|
||||
@@ -122,7 +120,7 @@ type ReportFlagGroup struct {
|
||||
}
|
||||
|
||||
type ReportOptions struct {
|
||||
Format string
|
||||
Format types.Format
|
||||
ReportFormat string
|
||||
Template string
|
||||
DependencyTree bool
|
||||
@@ -131,7 +129,7 @@ type ReportOptions struct {
|
||||
ExitCode int
|
||||
ExitOnEOL int
|
||||
IgnorePolicy string
|
||||
Output io.Writer
|
||||
Output string
|
||||
Severities []dbTypes.Severity
|
||||
Compliance spec.ComplianceSpec
|
||||
}
|
||||
@@ -174,12 +172,11 @@ func (f *ReportFlagGroup) Flags() []*Flag {
|
||||
}
|
||||
}
|
||||
|
||||
func (f *ReportFlagGroup) ToOptions(out io.Writer) (ReportOptions, error) {
|
||||
format := getString(f.Format)
|
||||
func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) {
|
||||
format := getUnderlyingString[types.Format](f.Format)
|
||||
template := getString(f.Template)
|
||||
dependencyTree := getBool(f.DependencyTree)
|
||||
listAllPkgs := getBool(f.ListAllPkgs)
|
||||
output := getString(f.Output)
|
||||
|
||||
if template != "" {
|
||||
if format == "" {
|
||||
@@ -188,14 +185,14 @@ func (f *ReportFlagGroup) ToOptions(out io.Writer) (ReportOptions, error) {
|
||||
log.Logger.Warnf("'--template' is ignored because '--format %s' is specified. Use '--template' option with '--format template' option.", format)
|
||||
}
|
||||
} else {
|
||||
if format == report.FormatTemplate {
|
||||
if format == types.FormatTemplate {
|
||||
log.Logger.Warn("'--format template' is ignored because '--template' is not specified. Specify '--template' option when you use '--format template'.")
|
||||
}
|
||||
}
|
||||
|
||||
// "--list-all-pkgs" option is unavailable with "--format table".
|
||||
// If user specifies "--list-all-pkgs" with "--format table", we should warn it.
|
||||
if listAllPkgs && format == report.FormatTable {
|
||||
if listAllPkgs && format == types.FormatTable {
|
||||
log.Logger.Warn(`"--list-all-pkgs" cannot be used with "--format table". Try "--format json" or other formats.`)
|
||||
}
|
||||
|
||||
@@ -204,7 +201,7 @@ func (f *ReportFlagGroup) ToOptions(out io.Writer) (ReportOptions, error) {
|
||||
log.Logger.Infof(`"--dependency-tree" only shows the dependents of vulnerable packages. ` +
|
||||
`Note that it is the reverse of the usual dependency tree, which shows the packages that depend on the vulnerable package. ` +
|
||||
`It supports limited package managers. Please see the document for the detail.`)
|
||||
if format != report.FormatTable {
|
||||
if format != types.FormatTable {
|
||||
log.Logger.Warn(`"--dependency-tree" can be used only with "--format table".`)
|
||||
}
|
||||
}
|
||||
@@ -214,13 +211,6 @@ func (f *ReportFlagGroup) ToOptions(out io.Writer) (ReportOptions, error) {
|
||||
listAllPkgs = true
|
||||
}
|
||||
|
||||
if output != "" {
|
||||
var err error
|
||||
if out, err = os.Create(output); err != nil {
|
||||
return ReportOptions{}, xerrors.Errorf("failed to create an output file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
cs, err := loadComplianceTypes(getString(f.Compliance))
|
||||
if err != nil {
|
||||
return ReportOptions{}, xerrors.Errorf("unable to load compliance spec: %w", err)
|
||||
@@ -236,14 +226,14 @@ func (f *ReportFlagGroup) ToOptions(out io.Writer) (ReportOptions, error) {
|
||||
ExitCode: getInt(f.ExitCode),
|
||||
ExitOnEOL: getInt(f.ExitOnEOL),
|
||||
IgnorePolicy: getString(f.IgnorePolicy),
|
||||
Output: out,
|
||||
Output: getString(f.Output),
|
||||
Severities: toSeverity(getStringSlice(f.Severity)),
|
||||
Compliance: cs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
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.SupportedCompliances, compliance) && !strings.HasPrefix(compliance, "@") {
|
||||
return spec.ComplianceSpec{}, xerrors.Errorf("unknown compliance : %v", compliance)
|
||||
}
|
||||
|
||||
@@ -255,13 +245,13 @@ func loadComplianceTypes(compliance string) (spec.ComplianceSpec, error) {
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
func (f *ReportFlagGroup) forceListAllPkgs(format string, listAllPkgs, dependencyTree bool) bool {
|
||||
if slices.Contains(report.SupportedSBOMFormats, format) && !listAllPkgs {
|
||||
log.Logger.Debugf("%q automatically enables '--list-all-pkgs'.", report.SupportedSBOMFormats)
|
||||
func (f *ReportFlagGroup) forceListAllPkgs(format types.Format, listAllPkgs, dependencyTree bool) bool {
|
||||
if slices.Contains(types.SupportedSBOMFormats, format) && !listAllPkgs {
|
||||
log.Logger.Debugf("%q automatically enables '--list-all-pkgs'.", types.SupportedSBOMFormats)
|
||||
return true
|
||||
}
|
||||
// We need this flag to insert dependency locations into Sarif('Package' struct contains 'Locations')
|
||||
if format == report.FormatSarif && !listAllPkgs {
|
||||
if format == types.FormatSarif && !listAllPkgs {
|
||||
log.Logger.Debugf("Sarif format automatically enables '--list-all-pkgs' to get locations")
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
package flag_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
defsecTypes "github.com/aquasecurity/defsec/pkg/types"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zaptest/observer"
|
||||
|
||||
defsecTypes "github.com/aquasecurity/defsec/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/log"
|
||||
"github.com/aquasecurity/trivy/pkg/report"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
func TestReportFlagGroup_ToOptions(t *testing.T) {
|
||||
type fields struct {
|
||||
format string
|
||||
format types.Format
|
||||
template string
|
||||
dependencyTree bool
|
||||
listAllPkgs bool
|
||||
@@ -44,9 +42,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
|
||||
{
|
||||
name: "happy default (without flags)",
|
||||
fields: fields{},
|
||||
want: flag.ReportOptions{
|
||||
Output: os.Stdout,
|
||||
},
|
||||
want: flag.ReportOptions{},
|
||||
},
|
||||
{
|
||||
name: "happy path with an cyclonedx",
|
||||
@@ -56,9 +52,8 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
|
||||
listAllPkgs: true,
|
||||
},
|
||||
want: flag.ReportOptions{
|
||||
Output: os.Stdout,
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
||||
Format: report.FormatCycloneDX,
|
||||
Format: types.FormatCycloneDX,
|
||||
ListAllPkgs: true,
|
||||
},
|
||||
},
|
||||
@@ -76,11 +71,10 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
|
||||
`Severities: ["CRITICAL"]`,
|
||||
},
|
||||
want: flag.ReportOptions{
|
||||
Output: os.Stdout,
|
||||
Severities: []dbTypes.Severity{
|
||||
dbTypes.SeverityCritical,
|
||||
},
|
||||
Format: report.FormatCycloneDX,
|
||||
Format: types.FormatCycloneDX,
|
||||
ListAllPkgs: true,
|
||||
},
|
||||
},
|
||||
@@ -94,7 +88,6 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
|
||||
"'--template' is ignored because '--format template' is not specified. Use '--template' option with '--format template' option.",
|
||||
},
|
||||
want: flag.ReportOptions{
|
||||
Output: os.Stdout,
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
|
||||
Template: "@contrib/gitlab.tpl",
|
||||
},
|
||||
@@ -110,7 +103,6 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
|
||||
"'--template' is ignored because '--format json' is specified. Use '--template' option with '--format template' option.",
|
||||
},
|
||||
want: flag.ReportOptions{
|
||||
Output: os.Stdout,
|
||||
Format: "json",
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
|
||||
Template: "@contrib/gitlab.tpl",
|
||||
@@ -126,7 +118,6 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
|
||||
"'--format template' is ignored because '--template' is not specified. Specify '--template' option when you use '--format template'.",
|
||||
},
|
||||
want: flag.ReportOptions{
|
||||
Output: os.Stdout,
|
||||
Format: "template",
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
|
||||
},
|
||||
@@ -143,7 +134,6 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
|
||||
},
|
||||
want: flag.ReportOptions{
|
||||
Format: "table",
|
||||
Output: os.Stdout,
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
|
||||
ListAllPkgs: true,
|
||||
},
|
||||
@@ -155,7 +145,6 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
|
||||
severities: dbTypes.SeverityLow.String(),
|
||||
},
|
||||
want: flag.ReportOptions{
|
||||
Output: os.Stdout,
|
||||
Compliance: spec.ComplianceSpec{
|
||||
Spec: defsecTypes.Spec{
|
||||
ID: "0001",
|
||||
@@ -188,7 +177,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
|
||||
core, obs := observer.New(level)
|
||||
log.Logger = zap.New(core).Sugar()
|
||||
|
||||
viper.Set(flag.FormatFlag.ConfigName, tt.fields.format)
|
||||
viper.Set(flag.FormatFlag.ConfigName, string(tt.fields.format))
|
||||
viper.Set(flag.TemplateFlag.ConfigName, tt.fields.template)
|
||||
viper.Set(flag.DependencyTreeFlag.ConfigName, tt.fields.dependencyTree)
|
||||
viper.Set(flag.ListAllPkgsFlag.ConfigName, tt.fields.listAllPkgs)
|
||||
@@ -216,7 +205,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
|
||||
Compliance: &flag.ComplianceFlag,
|
||||
}
|
||||
|
||||
got, err := f.ToOptions(os.Stdout)
|
||||
got, err := f.ToOptions()
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, tt.want, got, "ToOptions()")
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package flag
|
||||
|
||||
import (
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
xstrings "github.com/aquasecurity/trivy/pkg/x/strings"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -26,16 +27,16 @@ var (
|
||||
ScannersFlag = Flag{
|
||||
Name: "scanners",
|
||||
ConfigName: "scan.scanners",
|
||||
Default: types.Scanners{
|
||||
Default: xstrings.ToStringSlice(types.Scanners{
|
||||
types.VulnerabilityScanner,
|
||||
types.SecretScanner,
|
||||
}.StringSlice(),
|
||||
Values: types.Scanners{
|
||||
}),
|
||||
Values: xstrings.ToStringSlice(types.Scanners{
|
||||
types.VulnerabilityScanner,
|
||||
types.MisconfigScanner,
|
||||
types.SecretScanner,
|
||||
types.LicenseScanner,
|
||||
}.StringSlice(),
|
||||
}),
|
||||
Aliases: []Alias{
|
||||
{
|
||||
Name: "security-checks",
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/aquasecurity/trivy-kubernetes/pkg/trivyk8s"
|
||||
"github.com/aquasecurity/trivy/pkg/flag"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/report"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
@@ -23,12 +22,12 @@ func clusterRun(ctx context.Context, opts flag.Options, cluster k8s.Cluster) err
|
||||
var artifacts []*artifacts.Artifact
|
||||
var err error
|
||||
switch opts.Format {
|
||||
case report.FormatCycloneDX:
|
||||
case types.FormatCycloneDX:
|
||||
artifacts, err = trivyk8s.New(cluster, log.Logger).ListBomInfo(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get k8s artifacts with node info error: %w", err)
|
||||
}
|
||||
case report.FormatJSON, report.FormatTable:
|
||||
case types.FormatJSON, types.FormatTable:
|
||||
if opts.Scanners.AnyEnabled(types.MisconfigScanner) && slices.Contains(opts.Components, "infra") {
|
||||
artifacts, err = trivyk8s.New(cluster, log.Logger).ListArtifactAndNodeInfo(ctx, opts.NodeCollectorNamespace, opts.ExcludeNodes, opts.Tolerations...)
|
||||
if err != nil {
|
||||
|
||||
@@ -95,6 +95,12 @@ func (r *runner) run(ctx context.Context, artifacts []*artifacts.Artifact) error
|
||||
return xerrors.Errorf("k8s scan error: %w", err)
|
||||
}
|
||||
|
||||
output, err := r.flagOpts.OutputWriter()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to create output file: %w", err)
|
||||
}
|
||||
defer output.Close()
|
||||
|
||||
if r.flagOpts.Compliance.Spec.ID != "" {
|
||||
var scanResults []types.Results
|
||||
for _, rss := range rpt.Resources {
|
||||
@@ -107,14 +113,14 @@ func (r *runner) run(ctx context.Context, artifacts []*artifacts.Artifact) error
|
||||
return cr.Write(complianceReport, cr.Option{
|
||||
Format: r.flagOpts.Format,
|
||||
Report: r.flagOpts.ReportFormat,
|
||||
Output: r.flagOpts.Output,
|
||||
Output: output,
|
||||
})
|
||||
}
|
||||
|
||||
if err := k8sRep.Write(rpt, report.Option{
|
||||
Format: r.flagOpts.Format,
|
||||
Report: r.flagOpts.ReportFormat,
|
||||
Output: r.flagOpts.Output,
|
||||
Output: output,
|
||||
Severities: r.flagOpts.Severities,
|
||||
Components: r.flagOpts.Components,
|
||||
Scanners: r.flagOpts.ScanOptions.Scanners,
|
||||
|
||||
@@ -25,7 +25,7 @@ const (
|
||||
)
|
||||
|
||||
type Option struct {
|
||||
Format string
|
||||
Format types.Format
|
||||
Report string
|
||||
Output io.Writer
|
||||
Severities []dbTypes.Severity
|
||||
|
||||
@@ -7,16 +7,13 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
cdx "github.com/CycloneDX/cyclonedx-go"
|
||||
ms "github.com/mitchellh/mapstructure"
|
||||
"github.com/package-url/packageurl-go"
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/go-version/pkg/version"
|
||||
|
||||
cdx "github.com/CycloneDX/cyclonedx-go"
|
||||
|
||||
"github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
|
||||
"github.com/aquasecurity/trivy-kubernetes/pkg/bom"
|
||||
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
|
||||
@@ -27,7 +24,6 @@ import (
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/parallel"
|
||||
"github.com/aquasecurity/trivy/pkg/purl"
|
||||
rep "github.com/aquasecurity/trivy/pkg/report"
|
||||
cyc "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx"
|
||||
"github.com/aquasecurity/trivy/pkg/sbom/cyclonedx/core"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/local"
|
||||
@@ -74,7 +70,7 @@ func (s *Scanner) Scan(ctx context.Context, artifactsData []*artifacts.Artifact)
|
||||
}
|
||||
}()
|
||||
|
||||
if s.opts.Format == rep.FormatCycloneDX {
|
||||
if s.opts.Format == types.FormatCycloneDX {
|
||||
rootComponent, err := clusterInfoToReportResources(artifactsData, s.cluster)
|
||||
if err != nil {
|
||||
return report.Report{}, err
|
||||
|
||||
@@ -3,12 +3,11 @@ package k8s
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/k8s/report"
|
||||
|
||||
cdx "github.com/CycloneDX/cyclonedx-go"
|
||||
|
||||
rp "github.com/aquasecurity/trivy/pkg/report"
|
||||
"github.com/aquasecurity/trivy/pkg/k8s/report"
|
||||
"github.com/aquasecurity/trivy/pkg/report/table"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
type Writer interface {
|
||||
@@ -20,13 +19,13 @@ func Write(k8sreport report.Report, option report.Option) error {
|
||||
k8sreport.PrintErrors()
|
||||
|
||||
switch option.Format {
|
||||
case rp.FormatJSON:
|
||||
case types.FormatJSON:
|
||||
jwriter := report.JSONWriter{
|
||||
Output: option.Output,
|
||||
Report: option.Report,
|
||||
}
|
||||
return jwriter.Write(k8sreport)
|
||||
case rp.FormatTable:
|
||||
case types.FormatTable:
|
||||
separatedReports := report.SeparateMisconfigReports(k8sreport, option.Scanners, option.Components)
|
||||
|
||||
if option.Report == report.SummaryReport {
|
||||
@@ -48,7 +47,7 @@ func Write(k8sreport report.Report, option report.Option) error {
|
||||
}
|
||||
|
||||
return nil
|
||||
case rp.FormatCycloneDX:
|
||||
case types.FormatCycloneDX:
|
||||
w := report.NewCycloneDXWriter(option.Output, cdx.BOMFileFormatJSON, option.APIVersion)
|
||||
return w.Write(k8sreport.RootComponent)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/syncx"
|
||||
xsync "github.com/aquasecurity/trivy/pkg/x/sync"
|
||||
)
|
||||
|
||||
var separator = "/"
|
||||
@@ -24,7 +24,7 @@ type file struct {
|
||||
underlyingPath string // underlying file path
|
||||
data []byte // virtual file, only either of 'path' or 'data' has a value.
|
||||
stat fileStat
|
||||
files syncx.Map[string, *file]
|
||||
files xsync.Map[string, *file]
|
||||
}
|
||||
|
||||
func (f *file) isVirtual() bool {
|
||||
@@ -187,7 +187,7 @@ func (f *file) MkdirAll(path string, perm fs.FileMode) error {
|
||||
modTime: time.Now(),
|
||||
mode: perm,
|
||||
},
|
||||
files: syncx.Map[string, *file]{},
|
||||
files: xsync.Map[string, *file]{},
|
||||
}
|
||||
|
||||
// Create the directory when the key is not present
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/syncx"
|
||||
xsync "github.com/aquasecurity/trivy/pkg/x/sync"
|
||||
)
|
||||
|
||||
type allFS interface {
|
||||
@@ -56,7 +56,7 @@ func New(opts ...Option) *FS {
|
||||
modTime: time.Now(),
|
||||
mode: 0o0700 | fs.ModeDir,
|
||||
},
|
||||
files: syncx.Map[string, *file]{},
|
||||
files: xsync.Map[string, *file]{},
|
||||
},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/report"
|
||||
"github.com/aquasecurity/trivy/pkg/report/github"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
@@ -136,22 +135,19 @@ func TestWriter_Write(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
jw := github.Writer{}
|
||||
written := bytes.Buffer{}
|
||||
jw.Output = &written
|
||||
written := bytes.NewBuffer(nil)
|
||||
w := github.Writer{
|
||||
Output: written,
|
||||
}
|
||||
|
||||
inputResults := tt.report
|
||||
|
||||
err := report.Write(inputResults, report.Option{
|
||||
Format: "github",
|
||||
Output: &written,
|
||||
})
|
||||
err := w.Write(inputResults)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var got github.DependencySnapshot
|
||||
err = json.Unmarshal(written.Bytes(), &got)
|
||||
assert.NoError(t, err, "invalid github written")
|
||||
|
||||
assert.Equal(t, tt.want, got.Manifests, tt.name)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -66,9 +66,10 @@ func TestReportWriter_JSON(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
jw := report.JSONWriter{}
|
||||
jsonWritten := bytes.Buffer{}
|
||||
jw.Output = &jsonWritten
|
||||
jsonWritten := bytes.NewBuffer(nil)
|
||||
jw := report.JSONWriter{
|
||||
Output: jsonWritten,
|
||||
}
|
||||
|
||||
inputResults := types.Report{
|
||||
SchemaVersion: 2,
|
||||
@@ -81,10 +82,7 @@ func TestReportWriter_JSON(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
err := report.Write(inputResults, report.Option{
|
||||
Format: "json",
|
||||
Output: &jsonWritten,
|
||||
})
|
||||
err := jw.Write(inputResults)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var got types.Report
|
||||
|
||||
@@ -456,11 +456,11 @@ func TestReportWriter_Sarif(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
sarifWritten := bytes.Buffer{}
|
||||
err := report.Write(tt.input, report.Option{
|
||||
Format: "sarif",
|
||||
Output: &sarifWritten,
|
||||
})
|
||||
sarifWritten := bytes.NewBuffer(nil)
|
||||
w := report.SarifWriter{
|
||||
Output: sarifWritten,
|
||||
}
|
||||
err := w.Write(tt.input)
|
||||
assert.NoError(t, err)
|
||||
|
||||
result := &sarif.Report{}
|
||||
|
||||
@@ -15,11 +15,11 @@ import (
|
||||
type Writer struct {
|
||||
output io.Writer
|
||||
version string
|
||||
format string
|
||||
format types.Format
|
||||
marshaler *spdx.Marshaler
|
||||
}
|
||||
|
||||
func NewWriter(output io.Writer, version string, spdxFormat string) Writer {
|
||||
func NewWriter(output io.Writer, version string, spdxFormat types.Format) Writer {
|
||||
return Writer{
|
||||
output: output,
|
||||
version: version,
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/report"
|
||||
"github.com/aquasecurity/trivy/pkg/report/table"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
@@ -339,8 +339,7 @@ package-lock.json
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tableWritten := bytes.Buffer{}
|
||||
err := report.Write(types.Report{Results: tc.results}, report.Option{
|
||||
Format: report.FormatTable,
|
||||
writer := table.Writer{
|
||||
Output: &tableWritten,
|
||||
Tree: true,
|
||||
IncludeNonFailures: tc.includeNonFailures,
|
||||
@@ -348,7 +347,8 @@ package-lock.json
|
||||
dbTypes.SeverityHigh,
|
||||
dbTypes.SeverityMedium,
|
||||
},
|
||||
})
|
||||
}
|
||||
err := writer.Write(types.Report{Results: tc.results})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedOutput, tableWritten.String(), tc.name)
|
||||
})
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/clock"
|
||||
@@ -177,11 +178,9 @@ func TestReportWriter_Template(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
err := report.Write(inputReport, report.Option{
|
||||
Format: "template",
|
||||
Output: &got,
|
||||
OutputTemplate: tc.template,
|
||||
})
|
||||
w, err := report.NewTemplateWriter(&got, tc.template)
|
||||
require.NoError(t, err)
|
||||
err = w.Write(inputReport)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expected, got.String())
|
||||
})
|
||||
|
||||
@@ -7,9 +7,8 @@ import (
|
||||
|
||||
"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/flag"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/report/cyclonedx"
|
||||
"github.com/aquasecurity/trivy/pkg/report/github"
|
||||
@@ -21,114 +20,67 @@ import (
|
||||
|
||||
const (
|
||||
SchemaVersion = 2
|
||||
|
||||
FormatTable = "table"
|
||||
FormatJSON = "json"
|
||||
FormatTemplate = "template"
|
||||
FormatSarif = "sarif"
|
||||
FormatCycloneDX = "cyclonedx"
|
||||
FormatSPDX = "spdx"
|
||||
FormatSPDXJSON = "spdx-json"
|
||||
FormatGitHub = "github"
|
||||
FormatCosignVuln = "cosign-vuln"
|
||||
)
|
||||
|
||||
var (
|
||||
SupportedFormats = []string{
|
||||
FormatTable,
|
||||
FormatJSON,
|
||||
FormatTemplate,
|
||||
FormatSarif,
|
||||
FormatCycloneDX,
|
||||
FormatSPDX,
|
||||
FormatSPDXJSON,
|
||||
FormatGitHub,
|
||||
FormatCosignVuln,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
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
|
||||
Trace bool
|
||||
|
||||
// For licenses
|
||||
LicenseRiskThreshold int
|
||||
IgnoredLicenses []string
|
||||
}
|
||||
|
||||
// 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 flag.Options) error {
|
||||
output, err := option.OutputWriter()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to create a file: %w", err)
|
||||
}
|
||||
defer output.Close()
|
||||
|
||||
// Compliance report
|
||||
if option.Compliance.Spec.ID != "" {
|
||||
return complianceWrite(report, option)
|
||||
return complianceWrite(report, option, output)
|
||||
}
|
||||
|
||||
var writer Writer
|
||||
switch option.Format {
|
||||
case FormatTable:
|
||||
case types.FormatTable:
|
||||
writer = &table.Writer{
|
||||
Output: option.Output,
|
||||
Output: output,
|
||||
Severities: option.Severities,
|
||||
Tree: option.Tree,
|
||||
Tree: option.DependencyTree,
|
||||
ShowMessageOnce: &sync.Once{},
|
||||
IncludeNonFailures: option.IncludeNonFailures,
|
||||
Trace: option.Trace,
|
||||
LicenseRiskThreshold: option.LicenseRiskThreshold,
|
||||
IgnoredLicenses: option.IgnoredLicenses,
|
||||
}
|
||||
case FormatJSON:
|
||||
writer = &JSONWriter{Output: option.Output}
|
||||
case FormatGitHub:
|
||||
case types.FormatJSON:
|
||||
writer = &JSONWriter{Output: output}
|
||||
case types.FormatGitHub:
|
||||
writer = &github.Writer{
|
||||
Output: option.Output,
|
||||
Output: output,
|
||||
Version: option.AppVersion,
|
||||
}
|
||||
case FormatCycloneDX:
|
||||
case types.FormatCycloneDX:
|
||||
// TODO: support xml format option with cyclonedx writer
|
||||
writer = cyclonedx.NewWriter(option.Output, option.AppVersion)
|
||||
case FormatSPDX, FormatSPDXJSON:
|
||||
writer = spdx.NewWriter(option.Output, option.AppVersion, option.Format)
|
||||
case FormatTemplate:
|
||||
writer = cyclonedx.NewWriter(output, option.AppVersion)
|
||||
case types.FormatSPDX, types.FormatSPDXJSON:
|
||||
writer = spdx.NewWriter(output, option.AppVersion, option.Format)
|
||||
case types.FormatTemplate:
|
||||
// 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.Template, "@") && strings.HasSuffix(option.Template, "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,
|
||||
Output: output,
|
||||
Version: option.AppVersion,
|
||||
}
|
||||
break
|
||||
}
|
||||
var err error
|
||||
if writer, err = NewTemplateWriter(option.Output, option.OutputTemplate); err != nil {
|
||||
if writer, err = NewTemplateWriter(output, option.Template); err != nil {
|
||||
return xerrors.Errorf("failed to initialize template writer: %w", err)
|
||||
}
|
||||
case FormatSarif:
|
||||
case types.FormatSarif:
|
||||
writer = &SarifWriter{
|
||||
Output: option.Output,
|
||||
Output: output,
|
||||
Version: option.AppVersion,
|
||||
}
|
||||
case FormatCosignVuln:
|
||||
writer = predicate.NewVulnWriter(option.Output, option.AppVersion)
|
||||
case types.FormatCosignVuln:
|
||||
writer = predicate.NewVulnWriter(output, option.AppVersion)
|
||||
default:
|
||||
return xerrors.Errorf("unknown format: %v", option.Format)
|
||||
}
|
||||
@@ -139,15 +91,15 @@ func Write(report types.Report, option Option) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func complianceWrite(report types.Report, opt Option) error {
|
||||
func complianceWrite(report types.Report, opt flag.Options, output io.Writer) 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,
|
||||
Report: opt.ReportFormat,
|
||||
Output: output,
|
||||
Severities: opt.Severities,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
r "github.com/aquasecurity/trivy/pkg/rpc"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
xstrings "github.com/aquasecurity/trivy/pkg/x/strings"
|
||||
rpc "github.com/aquasecurity/trivy/rpc/scanner"
|
||||
)
|
||||
|
||||
@@ -82,7 +83,7 @@ func (s Scanner) Scan(ctx context.Context, target, artifactKey string, blobKeys
|
||||
BlobIds: blobKeys,
|
||||
Options: &rpc.ScanOptions{
|
||||
VulnType: opts.VulnType,
|
||||
Scanners: opts.Scanners.StringSlice(),
|
||||
Scanners: xstrings.ToStringSlice(opts.Scanners),
|
||||
ListAllPackages: opts.ListAllPackages,
|
||||
LicenseCategories: licenseCategories,
|
||||
IncludeDevDeps: opts.IncludeDevDeps,
|
||||
|
||||
@@ -8,16 +8,6 @@ import (
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
)
|
||||
|
||||
var Compliances = []string{
|
||||
ComplianceK8sNsa,
|
||||
ComplianceK8sCIS,
|
||||
ComplianceK8sPSSBaseline,
|
||||
ComplianceK8sPSSRestricted,
|
||||
ComplianceAWSCIS12,
|
||||
ComplianceAWSCIS14,
|
||||
ComplianceDockerCIS,
|
||||
}
|
||||
|
||||
// Report represents a scan result
|
||||
type Report struct {
|
||||
SchemaVersion int `json:",omitempty"`
|
||||
@@ -48,6 +38,7 @@ type Results []Result
|
||||
|
||||
type ResultClass string
|
||||
type Compliance = string
|
||||
type Format string
|
||||
|
||||
const (
|
||||
ClassOSPkg = "os-pkgs" // For detected packages and vulnerabilities in OS packages
|
||||
@@ -65,6 +56,45 @@ const (
|
||||
ComplianceAWSCIS12 = Compliance("aws-cis-1.2")
|
||||
ComplianceAWSCIS14 = Compliance("aws-cis-1.4")
|
||||
ComplianceDockerCIS = Compliance("docker-cis")
|
||||
|
||||
FormatTable Format = "table"
|
||||
FormatJSON Format = "json"
|
||||
FormatTemplate Format = "template"
|
||||
FormatSarif Format = "sarif"
|
||||
FormatCycloneDX Format = "cyclonedx"
|
||||
FormatSPDX Format = "spdx"
|
||||
FormatSPDXJSON Format = "spdx-json"
|
||||
FormatGitHub Format = "github"
|
||||
FormatCosignVuln Format = "cosign-vuln"
|
||||
)
|
||||
|
||||
var (
|
||||
SupportedFormats = []Format{
|
||||
FormatTable,
|
||||
FormatJSON,
|
||||
FormatTemplate,
|
||||
FormatSarif,
|
||||
FormatCycloneDX,
|
||||
FormatSPDX,
|
||||
FormatSPDXJSON,
|
||||
FormatGitHub,
|
||||
FormatCosignVuln,
|
||||
}
|
||||
SupportedSBOMFormats = []Format{
|
||||
FormatCycloneDX,
|
||||
FormatSPDX,
|
||||
FormatSPDXJSON,
|
||||
FormatGitHub,
|
||||
}
|
||||
SupportedCompliances = []string{
|
||||
ComplianceK8sNsa,
|
||||
ComplianceK8sCIS,
|
||||
ComplianceK8sPSSBaseline,
|
||||
ComplianceK8sPSSRestricted,
|
||||
ComplianceAWSCIS12,
|
||||
ComplianceAWSCIS14,
|
||||
ComplianceDockerCIS,
|
||||
}
|
||||
)
|
||||
|
||||
// Result holds a target and detected vulnerabilities
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
@@ -84,9 +83,3 @@ func (scanners Scanners) AnyEnabled(ss ...Scanner) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (scanners Scanners) StringSlice() []string {
|
||||
return lo.Map(scanners, func(s Scanner, _ int) string {
|
||||
return string(s)
|
||||
})
|
||||
}
|
||||
|
||||
15
pkg/x/io/io.go
Normal file
15
pkg/x/io/io.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package io
|
||||
|
||||
import "io"
|
||||
|
||||
// NopCloser returns a WriteCloser with a no-op Close method wrapping
|
||||
// the provided Writer w.
|
||||
func NopCloser(w io.Writer) io.WriteCloser {
|
||||
return nopCloser{w}
|
||||
}
|
||||
|
||||
type nopCloser struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (nopCloser) Close() error { return nil }
|
||||
19
pkg/x/strings/strings.go
Normal file
19
pkg/x/strings/strings.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package strings
|
||||
|
||||
import "github.com/samber/lo"
|
||||
|
||||
type String interface {
|
||||
~string
|
||||
}
|
||||
|
||||
func ToStringSlice[T String](ss []T) []string {
|
||||
return lo.Map(ss, func(s T, _ int) string {
|
||||
return string(s)
|
||||
})
|
||||
}
|
||||
|
||||
func ToTSlice[T String](ss []string) []T {
|
||||
return lo.Map(ss, func(s string, _ int) T {
|
||||
return T(s)
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package syncx
|
||||
package sync
|
||||
|
||||
import "sync"
|
||||
|
||||
Reference in New Issue
Block a user