github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/flag/report_flags.go (about)

     1  package flag
     2  
     3  import (
     4  	"strings"
     5  
     6  	"github.com/samber/lo"
     7  	"golang.org/x/exp/slices"
     8  	"golang.org/x/xerrors"
     9  
    10  	dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
    11  	"github.com/devseccon/trivy/pkg/compliance/spec"
    12  	"github.com/devseccon/trivy/pkg/log"
    13  	"github.com/devseccon/trivy/pkg/result"
    14  	"github.com/devseccon/trivy/pkg/types"
    15  	xstrings "github.com/devseccon/trivy/pkg/x/strings"
    16  )
    17  
    18  // e.g. config yaml:
    19  //
    20  //	format: table
    21  //	dependency-tree: true
    22  //	severity: HIGH,CRITICAL
    23  var (
    24  	FormatFlag = Flag{
    25  		Name:       "format",
    26  		ConfigName: "format",
    27  		Shorthand:  "f",
    28  		Default:    string(types.FormatTable),
    29  		Values:     xstrings.ToStringSlice(types.SupportedFormats),
    30  		Usage:      "format",
    31  	}
    32  	ReportFormatFlag = Flag{
    33  		Name:       "report",
    34  		ConfigName: "report",
    35  		Default:    "all",
    36  		Values:     []string{"all", "summary"},
    37  		Usage:      "specify a report format for the output",
    38  	}
    39  	TemplateFlag = Flag{
    40  		Name:       "template",
    41  		ConfigName: "template",
    42  		Shorthand:  "t",
    43  		Default:    "",
    44  		Usage:      "output template",
    45  	}
    46  	DependencyTreeFlag = Flag{
    47  		Name:       "dependency-tree",
    48  		ConfigName: "dependency-tree",
    49  		Default:    false,
    50  		Usage:      "[EXPERIMENTAL] show dependency origin tree of vulnerable packages",
    51  	}
    52  	ListAllPkgsFlag = Flag{
    53  		Name:       "list-all-pkgs",
    54  		ConfigName: "list-all-pkgs",
    55  		Default:    false,
    56  		Usage:      "enabling the option will output all packages regardless of vulnerability",
    57  	}
    58  	IgnoreFileFlag = Flag{
    59  		Name:       "ignorefile",
    60  		ConfigName: "ignorefile",
    61  		Default:    result.DefaultIgnoreFile,
    62  		Usage:      "specify .trivyignore file",
    63  	}
    64  	IgnorePolicyFlag = Flag{
    65  		Name:       "ignore-policy",
    66  		ConfigName: "ignore-policy",
    67  		Default:    "",
    68  		Usage:      "specify the Rego file path to evaluate each vulnerability",
    69  	}
    70  	ExitCodeFlag = Flag{
    71  		Name:       "exit-code",
    72  		ConfigName: "exit-code",
    73  		Default:    0,
    74  		Usage:      "specify exit code when any security issues are found",
    75  	}
    76  	ExitOnEOLFlag = Flag{
    77  		Name:       "exit-on-eol",
    78  		ConfigName: "exit-on-eol",
    79  		Default:    0,
    80  		Usage:      "exit with the specified code when the OS reaches end of service/life",
    81  	}
    82  	OutputFlag = Flag{
    83  		Name:       "output",
    84  		ConfigName: "output",
    85  		Shorthand:  "o",
    86  		Default:    "",
    87  		Usage:      "output file name",
    88  	}
    89  	SeverityFlag = Flag{
    90  		Name:       "severity",
    91  		ConfigName: "severity",
    92  		Shorthand:  "s",
    93  		Default:    dbTypes.SeverityNames,
    94  		Values:     dbTypes.SeverityNames,
    95  		Usage:      "severities of security issues to be displayed",
    96  	}
    97  	ComplianceFlag = Flag{
    98  		Name:       "compliance",
    99  		ConfigName: "scan.compliance",
   100  		Default:    "",
   101  		Usage:      "compliance report to generate",
   102  	}
   103  )
   104  
   105  // ReportFlagGroup composes common printer flag structs
   106  // used for commands requiring reporting logic.
   107  type ReportFlagGroup struct {
   108  	Format         *Flag
   109  	ReportFormat   *Flag
   110  	Template       *Flag
   111  	DependencyTree *Flag
   112  	ListAllPkgs    *Flag
   113  	IgnoreFile     *Flag
   114  	IgnorePolicy   *Flag
   115  	ExitCode       *Flag
   116  	ExitOnEOL      *Flag
   117  	Output         *Flag
   118  	Severity       *Flag
   119  	Compliance     *Flag
   120  }
   121  
   122  type ReportOptions struct {
   123  	Format         types.Format
   124  	ReportFormat   string
   125  	Template       string
   126  	DependencyTree bool
   127  	ListAllPkgs    bool
   128  	IgnoreFile     string
   129  	ExitCode       int
   130  	ExitOnEOL      int
   131  	IgnorePolicy   string
   132  	Output         string
   133  	Severities     []dbTypes.Severity
   134  	Compliance     spec.ComplianceSpec
   135  }
   136  
   137  func NewReportFlagGroup() *ReportFlagGroup {
   138  	return &ReportFlagGroup{
   139  		Format:         &FormatFlag,
   140  		ReportFormat:   &ReportFormatFlag,
   141  		Template:       &TemplateFlag,
   142  		DependencyTree: &DependencyTreeFlag,
   143  		ListAllPkgs:    &ListAllPkgsFlag,
   144  		IgnoreFile:     &IgnoreFileFlag,
   145  		IgnorePolicy:   &IgnorePolicyFlag,
   146  		ExitCode:       &ExitCodeFlag,
   147  		ExitOnEOL:      &ExitOnEOLFlag,
   148  		Output:         &OutputFlag,
   149  		Severity:       &SeverityFlag,
   150  		Compliance:     &ComplianceFlag,
   151  	}
   152  }
   153  
   154  func (f *ReportFlagGroup) Name() string {
   155  	return "Report"
   156  }
   157  
   158  func (f *ReportFlagGroup) Flags() []*Flag {
   159  	return []*Flag{
   160  		f.Format,
   161  		f.ReportFormat,
   162  		f.Template,
   163  		f.DependencyTree,
   164  		f.ListAllPkgs,
   165  		f.IgnoreFile,
   166  		f.IgnorePolicy,
   167  		f.ExitCode,
   168  		f.ExitOnEOL,
   169  		f.Output,
   170  		f.Severity,
   171  		f.Compliance,
   172  	}
   173  }
   174  
   175  func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) {
   176  	format := getUnderlyingString[types.Format](f.Format)
   177  	template := getString(f.Template)
   178  	dependencyTree := getBool(f.DependencyTree)
   179  	listAllPkgs := getBool(f.ListAllPkgs)
   180  
   181  	if template != "" {
   182  		if format == "" {
   183  			log.Logger.Warn("'--template' is ignored because '--format template' is not specified. Use '--template' option with '--format template' option.")
   184  		} else if format != "template" {
   185  			log.Logger.Warnf("'--template' is ignored because '--format %s' is specified. Use '--template' option with '--format template' option.", format)
   186  		}
   187  	} else {
   188  		if format == types.FormatTemplate {
   189  			log.Logger.Warn("'--format template' is ignored because '--template' is not specified. Specify '--template' option when you use '--format template'.")
   190  		}
   191  	}
   192  
   193  	// "--list-all-pkgs" option is unavailable with "--format table".
   194  	// If user specifies "--list-all-pkgs" with "--format table", we should warn it.
   195  	if listAllPkgs && format == types.FormatTable {
   196  		log.Logger.Warn(`"--list-all-pkgs" cannot be used with "--format table". Try "--format json" or other formats.`)
   197  	}
   198  
   199  	// "--dependency-tree" option is available only with "--format table".
   200  	if dependencyTree {
   201  		log.Logger.Infof(`"--dependency-tree" only shows the dependents of vulnerable packages. ` +
   202  			`Note that it is the reverse of the usual dependency tree, which shows the packages that depend on the vulnerable package. ` +
   203  			`It supports limited package managers. Please see the document for the detail.`)
   204  		if format != types.FormatTable {
   205  			log.Logger.Warn(`"--dependency-tree" can be used only with "--format table".`)
   206  		}
   207  	}
   208  
   209  	// Enable '--list-all-pkgs' if needed
   210  	if f.forceListAllPkgs(format, listAllPkgs, dependencyTree) {
   211  		listAllPkgs = true
   212  	}
   213  
   214  	cs, err := loadComplianceTypes(getString(f.Compliance))
   215  	if err != nil {
   216  		return ReportOptions{}, xerrors.Errorf("unable to load compliance spec: %w", err)
   217  	}
   218  
   219  	return ReportOptions{
   220  		Format:         format,
   221  		ReportFormat:   getString(f.ReportFormat),
   222  		Template:       template,
   223  		DependencyTree: dependencyTree,
   224  		ListAllPkgs:    listAllPkgs,
   225  		IgnoreFile:     getString(f.IgnoreFile),
   226  		ExitCode:       getInt(f.ExitCode),
   227  		ExitOnEOL:      getInt(f.ExitOnEOL),
   228  		IgnorePolicy:   getString(f.IgnorePolicy),
   229  		Output:         getString(f.Output),
   230  		Severities:     toSeverity(getStringSlice(f.Severity)),
   231  		Compliance:     cs,
   232  	}, nil
   233  }
   234  
   235  func loadComplianceTypes(compliance string) (spec.ComplianceSpec, error) {
   236  	if len(compliance) > 0 && !slices.Contains(types.SupportedCompliances, compliance) && !strings.HasPrefix(compliance, "@") {
   237  		return spec.ComplianceSpec{}, xerrors.Errorf("unknown compliance : %v", compliance)
   238  	}
   239  
   240  	cs, err := spec.GetComplianceSpec(compliance)
   241  	if err != nil {
   242  		return spec.ComplianceSpec{}, xerrors.Errorf("spec loading from file system error: %w", err)
   243  	}
   244  
   245  	return cs, nil
   246  }
   247  
   248  func (f *ReportFlagGroup) forceListAllPkgs(format types.Format, listAllPkgs, dependencyTree bool) bool {
   249  	if slices.Contains(types.SupportedSBOMFormats, format) && !listAllPkgs {
   250  		log.Logger.Debugf("%q automatically enables '--list-all-pkgs'.", types.SupportedSBOMFormats)
   251  		return true
   252  	}
   253  	// We need this flag to insert dependency locations into Sarif('Package' struct contains 'Locations')
   254  	if format == types.FormatSarif && !listAllPkgs {
   255  		log.Logger.Debugf("Sarif format automatically enables '--list-all-pkgs' to get locations")
   256  		return true
   257  	}
   258  	if dependencyTree && !listAllPkgs {
   259  		log.Logger.Debugf("'--dependency-tree' enables '--list-all-pkgs'.")
   260  		return true
   261  	}
   262  	return false
   263  }
   264  
   265  func toSeverity(severity []string) []dbTypes.Severity {
   266  	if len(severity) == 0 {
   267  		return nil
   268  	}
   269  	severities := lo.Map(severity, func(s string, _ int) dbTypes.Severity {
   270  		// Note that there is no need to check the error here
   271  		// since the severity value is already validated in the flag parser.
   272  		sev, _ := dbTypes.NewSeverity(s)
   273  		return sev
   274  	})
   275  	log.Logger.Debugf("Severities: %q", severities)
   276  	return severities
   277  }