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 }