github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/commands/app.go (about) 1 package commands 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io" 8 "os" 9 "sort" 10 "strings" 11 "time" 12 13 "github.com/spf13/cobra" 14 "github.com/spf13/viper" 15 "golang.org/x/xerrors" 16 17 awsScanner "github.com/aquasecurity/trivy-aws/pkg/scanner" 18 awscommands "github.com/devseccon/trivy/pkg/cloud/aws/commands" 19 "github.com/devseccon/trivy/pkg/commands/artifact" 20 "github.com/devseccon/trivy/pkg/commands/convert" 21 "github.com/devseccon/trivy/pkg/commands/server" 22 "github.com/devseccon/trivy/pkg/fanal/analyzer" 23 "github.com/devseccon/trivy/pkg/flag" 24 k8scommands "github.com/devseccon/trivy/pkg/k8s/commands" 25 "github.com/devseccon/trivy/pkg/log" 26 "github.com/devseccon/trivy/pkg/module" 27 "github.com/devseccon/trivy/pkg/plugin" 28 "github.com/devseccon/trivy/pkg/types" 29 "github.com/devseccon/trivy/pkg/version" 30 xstrings "github.com/devseccon/trivy/pkg/x/strings" 31 ) 32 33 const ( 34 usageTemplate = `Usage:{{if .Runnable}} 35 {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} 36 {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} 37 38 Aliases: 39 {{.NameAndAliases}}{{end}}{{if .HasExample}} 40 41 Examples: 42 {{.Example}}{{end}}{{if .HasAvailableSubCommands}} 43 44 Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} 45 {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} 46 47 %s 48 49 Global Flags: 50 {{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} 51 52 Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} 53 {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} 54 55 Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} 56 ` 57 58 groupScanning = "scanning" 59 groupManagement = "management" 60 groupUtility = "utility" 61 groupPlugin = "plugin" 62 ) 63 64 // NewApp is the factory method to return Trivy CLI 65 func NewApp() *cobra.Command { 66 globalFlags := flag.NewGlobalFlagGroup() 67 rootCmd := NewRootCommand(globalFlags) 68 rootCmd.AddGroup( 69 &cobra.Group{ 70 ID: groupScanning, 71 Title: "Scanning Commands", 72 }, 73 &cobra.Group{ 74 ID: groupManagement, 75 Title: "Management Commands", 76 }, 77 &cobra.Group{ 78 ID: groupUtility, 79 Title: "Utility Commands", 80 }, 81 ) 82 rootCmd.SetCompletionCommandGroupID(groupUtility) 83 rootCmd.SetHelpCommandGroupID(groupUtility) 84 rootCmd.AddCommand( 85 NewImageCommand(globalFlags), 86 NewFilesystemCommand(globalFlags), 87 NewRootfsCommand(globalFlags), 88 NewRepositoryCommand(globalFlags), 89 NewClientCommand(globalFlags), 90 NewServerCommand(globalFlags), 91 NewConfigCommand(globalFlags), 92 NewConvertCommand(globalFlags), 93 NewPluginCommand(), 94 NewModuleCommand(globalFlags), 95 NewKubernetesCommand(globalFlags), 96 NewSBOMCommand(globalFlags), 97 NewVersionCommand(globalFlags), 98 NewAWSCommand(globalFlags), 99 NewVMCommand(globalFlags), 100 ) 101 102 if plugins := loadPluginCommands(); len(plugins) > 0 { 103 rootCmd.AddGroup(&cobra.Group{ 104 ID: groupPlugin, 105 Title: "Plugin Commands", 106 }) 107 rootCmd.AddCommand(plugins...) 108 } 109 110 return rootCmd 111 } 112 113 func loadPluginCommands() []*cobra.Command { 114 var commands []*cobra.Command 115 plugins, err := plugin.LoadAll() 116 if err != nil { 117 log.Logger.Debugf("no plugins were loaded") 118 return nil 119 } 120 for _, p := range plugins { 121 p := p 122 cmd := &cobra.Command{ 123 Use: fmt.Sprintf("%s [flags]", p.Name), 124 Short: p.Usage, 125 GroupID: groupPlugin, 126 RunE: func(cmd *cobra.Command, args []string) error { 127 if err = p.Run(cmd.Context(), args); err != nil { 128 return xerrors.Errorf("plugin error: %w", err) 129 } 130 return nil 131 }, 132 DisableFlagParsing: true, 133 } 134 commands = append(commands, cmd) 135 } 136 return commands 137 } 138 139 func initConfig(configFile string) error { 140 // Read from config 141 viper.SetConfigFile(configFile) 142 viper.SetConfigType("yaml") 143 if err := viper.ReadInConfig(); err != nil { 144 if errors.Is(err, os.ErrNotExist) { 145 log.Logger.Debugf("config file %q not found", configFile) 146 return nil 147 } 148 return xerrors.Errorf("config file %q loading error: %s", configFile, err) 149 } 150 log.Logger.Infof("Loaded %s", configFile) 151 return nil 152 } 153 154 func NewRootCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { 155 var versionFormat string 156 cmd := &cobra.Command{ 157 Use: "trivy [global flags] command [flags] target", 158 Short: "Unified security scanner", 159 Long: "Scanner for vulnerabilities in container images, file systems, and Git repositories, as well as for configuration issues and hard-coded secrets", 160 Example: ` # Scan a container image 161 $ trivy image python:3.4-alpine 162 163 # Scan a container image from a tar archive 164 $ trivy image --input ruby-3.1.tar 165 166 # Scan local filesystem 167 $ trivy fs . 168 169 # Run in server mode 170 $ trivy server`, 171 Args: cobra.NoArgs, 172 PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 173 // Set the Trivy version here so that we can override version printer. 174 cmd.Version = version.AppVersion() 175 176 // viper.BindPFlag cannot be called in init(). 177 // cf. https://github.com/spf13/cobra/issues/875 178 // https://github.com/spf13/viper/issues/233 179 if err := globalFlags.Bind(cmd); err != nil { 180 return xerrors.Errorf("flag bind error: %w", err) 181 } 182 183 // The config path is needed for config initialization. 184 // It needs to be obtained before ToOptions(). 185 configPath := viper.GetString(flag.ConfigFileFlag.ConfigName) 186 187 // Configure environment variables and config file 188 // It cannot be called in init() because it must be called after viper.BindPFlags. 189 if err := initConfig(configPath); err != nil { 190 return err 191 } 192 193 globalOptions := globalFlags.ToOptions() 194 195 // Initialize logger 196 if err := log.InitLogger(globalOptions.Debug, globalOptions.Quiet); err != nil { 197 return err 198 } 199 200 return nil 201 }, 202 RunE: func(cmd *cobra.Command, args []string) error { 203 globalOptions := globalFlags.ToOptions() 204 if globalOptions.ShowVersion { 205 // Customize version output 206 return showVersion(globalOptions.CacheDir, versionFormat, cmd.OutOrStdout()) 207 } else { 208 return cmd.Help() 209 } 210 }, 211 } 212 213 // Add version format flag, only json is supported 214 cmd.Flags().StringVarP(&versionFormat, flag.FormatFlag.Name, flag.FormatFlag.Shorthand, "", "version format (json)") 215 216 globalFlags.AddFlags(cmd) 217 218 return cmd 219 } 220 221 func NewImageCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { 222 scanFlagGroup := flag.NewScanFlagGroup() 223 scanFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' 224 225 reportFlagGroup := flag.NewReportFlagGroup() 226 report := flag.ReportFormatFlag 227 report.Default = "summary" // override the default value as the summary is preferred for the compliance report 228 report.Usage = "specify a format for the compliance report." // "--report" works only with "--compliance" 229 reportFlagGroup.ReportFormat = &report 230 231 compliance := flag.ComplianceFlag 232 compliance.Values = []string{types.ComplianceDockerCIS} 233 reportFlagGroup.Compliance = &compliance // override usage as the accepted values differ for each subcommand. 234 235 misconfFlagGroup := flag.NewMisconfFlagGroup() 236 misconfFlagGroup.CloudformationParamVars = nil // disable '--cf-params' 237 misconfFlagGroup.TerraformTFVars = nil // disable '--tf-vars' 238 239 imageFlags := &flag.Flags{ 240 CacheFlagGroup: flag.NewCacheFlagGroup(), 241 DBFlagGroup: flag.NewDBFlagGroup(), 242 ImageFlagGroup: flag.NewImageFlagGroup(), // container image specific 243 LicenseFlagGroup: flag.NewLicenseFlagGroup(), 244 MisconfFlagGroup: misconfFlagGroup, 245 ModuleFlagGroup: flag.NewModuleFlagGroup(), 246 RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode 247 RegistryFlagGroup: flag.NewRegistryFlagGroup(), 248 RegoFlagGroup: flag.NewRegoFlagGroup(), 249 ReportFlagGroup: reportFlagGroup, 250 ScanFlagGroup: scanFlagGroup, 251 SecretFlagGroup: flag.NewSecretFlagGroup(), 252 VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), 253 } 254 255 cmd := &cobra.Command{ 256 Use: "image [flags] IMAGE_NAME", 257 Aliases: []string{"i"}, 258 GroupID: groupScanning, 259 Short: "Scan a container image", 260 Example: ` # Scan a container image 261 $ trivy image python:3.4-alpine 262 263 # Scan a container image from a tar archive 264 $ trivy image --input ruby-3.1.tar 265 266 # Filter by severities 267 $ trivy image --severity HIGH,CRITICAL alpine:3.15 268 269 # Ignore unfixed/unpatched vulnerabilities 270 $ trivy image --ignore-unfixed alpine:3.15 271 272 # Scan a container image in client mode 273 $ trivy image --server http://127.0.0.1:4954 alpine:latest 274 275 # Generate json result 276 $ trivy image --format json --output result.json alpine:3.15 277 278 # Generate a report in the CycloneDX format 279 $ trivy image --format cyclonedx --output result.cdx alpine:3.15`, 280 281 // 'Args' cannot be used since it is called before PreRunE and viper is not configured yet. 282 // cmd.Args -> cannot validate args here 283 // cmd.PreRunE -> configure viper && validate args 284 // cmd.RunE -> run the command 285 PreRunE: func(cmd *cobra.Command, args []string) error { 286 // viper.BindPFlag cannot be called in init(), so it is called in PreRunE. 287 // cf. https://github.com/spf13/cobra/issues/875 288 // https://github.com/spf13/viper/issues/233 289 if err := imageFlags.Bind(cmd); err != nil { 290 return xerrors.Errorf("flag bind error: %w", err) 291 } 292 return validateArgs(cmd, args) 293 }, 294 RunE: func(cmd *cobra.Command, args []string) error { 295 options, err := imageFlags.ToOptions(args, globalFlags) 296 if err != nil { 297 return xerrors.Errorf("flag error: %w", err) 298 } 299 return artifact.Run(cmd.Context(), options, artifact.TargetContainerImage) 300 }, 301 SilenceErrors: true, 302 SilenceUsage: true, 303 } 304 305 imageFlags.AddFlags(cmd) 306 cmd.SetFlagErrorFunc(flagErrorFunc) 307 cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, imageFlags.Usages(cmd))) 308 309 return cmd 310 } 311 312 func NewFilesystemCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { 313 reportFlagGroup := flag.NewReportFlagGroup() 314 reportFormat := flag.ReportFormatFlag 315 reportFormat.Usage = "specify a compliance report format for the output" // @TODO: support --report summary for non compliance reports 316 reportFlagGroup.ReportFormat = &reportFormat 317 reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' 318 319 fsFlags := &flag.Flags{ 320 CacheFlagGroup: flag.NewCacheFlagGroup(), 321 DBFlagGroup: flag.NewDBFlagGroup(), 322 LicenseFlagGroup: flag.NewLicenseFlagGroup(), 323 MisconfFlagGroup: flag.NewMisconfFlagGroup(), 324 ModuleFlagGroup: flag.NewModuleFlagGroup(), 325 RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode 326 RegistryFlagGroup: flag.NewRegistryFlagGroup(), 327 RegoFlagGroup: flag.NewRegoFlagGroup(), 328 ReportFlagGroup: reportFlagGroup, 329 ScanFlagGroup: flag.NewScanFlagGroup(), 330 SecretFlagGroup: flag.NewSecretFlagGroup(), 331 VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), 332 } 333 334 cmd := &cobra.Command{ 335 Use: "filesystem [flags] PATH", 336 Aliases: []string{"fs"}, 337 GroupID: groupScanning, 338 Short: "Scan local filesystem", 339 Example: ` # Scan a local project including language-specific files 340 $ trivy fs /path/to/your_project 341 342 # Scan a single file 343 $ trivy fs ./trivy-ci-test/Pipfile.lock`, 344 PreRunE: func(cmd *cobra.Command, args []string) error { 345 if err := fsFlags.Bind(cmd); err != nil { 346 return xerrors.Errorf("flag bind error: %w", err) 347 } 348 return validateArgs(cmd, args) 349 }, 350 RunE: func(cmd *cobra.Command, args []string) error { 351 if err := fsFlags.Bind(cmd); err != nil { 352 return xerrors.Errorf("flag bind error: %w", err) 353 } 354 options, err := fsFlags.ToOptions(args, globalFlags) 355 if err != nil { 356 return xerrors.Errorf("flag error: %w", err) 357 } 358 return artifact.Run(cmd.Context(), options, artifact.TargetFilesystem) 359 }, 360 SilenceErrors: true, 361 SilenceUsage: true, 362 } 363 364 cmd.SetFlagErrorFunc(flagErrorFunc) 365 fsFlags.AddFlags(cmd) 366 cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, fsFlags.Usages(cmd))) 367 368 return cmd 369 } 370 371 func NewRootfsCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { 372 rootfsFlags := &flag.Flags{ 373 CacheFlagGroup: flag.NewCacheFlagGroup(), 374 DBFlagGroup: flag.NewDBFlagGroup(), 375 LicenseFlagGroup: flag.NewLicenseFlagGroup(), 376 MisconfFlagGroup: flag.NewMisconfFlagGroup(), 377 ModuleFlagGroup: flag.NewModuleFlagGroup(), 378 RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode 379 RegistryFlagGroup: flag.NewRegistryFlagGroup(), 380 RegoFlagGroup: flag.NewRegoFlagGroup(), 381 ReportFlagGroup: flag.NewReportFlagGroup(), 382 ScanFlagGroup: flag.NewScanFlagGroup(), 383 SecretFlagGroup: flag.NewSecretFlagGroup(), 384 VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), 385 } 386 rootfsFlags.ReportFlagGroup.ReportFormat = nil // TODO: support --report summary 387 rootfsFlags.ReportFlagGroup.Compliance = nil // disable '--compliance' 388 rootfsFlags.ReportFlagGroup.ReportFormat = nil // disable '--report' 389 rootfsFlags.ScanFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' 390 391 cmd := &cobra.Command{ 392 Use: "rootfs [flags] ROOTDIR", 393 Short: "Scan rootfs", 394 GroupID: groupScanning, 395 Example: ` # Scan unpacked filesystem 396 $ docker export $(docker create alpine:3.10.2) | tar -C /tmp/rootfs -xvf - 397 $ trivy rootfs /tmp/rootfs 398 399 # Scan from inside a container 400 $ docker run --rm -it alpine:3.11 401 / # curl -sfL https://raw.githubusercontent.com/devseccon/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin 402 / # trivy rootfs /`, 403 PreRunE: func(cmd *cobra.Command, args []string) error { 404 if err := rootfsFlags.Bind(cmd); err != nil { 405 return xerrors.Errorf("flag bind error: %w", err) 406 } 407 return validateArgs(cmd, args) 408 }, 409 RunE: func(cmd *cobra.Command, args []string) error { 410 if err := rootfsFlags.Bind(cmd); err != nil { 411 return xerrors.Errorf("flag bind error: %w", err) 412 } 413 options, err := rootfsFlags.ToOptions(args, globalFlags) 414 if err != nil { 415 return xerrors.Errorf("flag error: %w", err) 416 } 417 return artifact.Run(cmd.Context(), options, artifact.TargetRootfs) 418 }, 419 SilenceErrors: true, 420 SilenceUsage: true, 421 } 422 cmd.SetFlagErrorFunc(flagErrorFunc) 423 rootfsFlags.AddFlags(cmd) 424 cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, rootfsFlags.Usages(cmd))) 425 426 return cmd 427 } 428 429 func NewRepositoryCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { 430 repoFlags := &flag.Flags{ 431 CacheFlagGroup: flag.NewCacheFlagGroup(), 432 DBFlagGroup: flag.NewDBFlagGroup(), 433 LicenseFlagGroup: flag.NewLicenseFlagGroup(), 434 MisconfFlagGroup: flag.NewMisconfFlagGroup(), 435 ModuleFlagGroup: flag.NewModuleFlagGroup(), 436 RegistryFlagGroup: flag.NewRegistryFlagGroup(), 437 RegoFlagGroup: flag.NewRegoFlagGroup(), 438 RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode 439 ReportFlagGroup: flag.NewReportFlagGroup(), 440 ScanFlagGroup: flag.NewScanFlagGroup(), 441 SecretFlagGroup: flag.NewSecretFlagGroup(), 442 VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), 443 RepoFlagGroup: flag.NewRepoFlagGroup(), 444 } 445 repoFlags.ReportFlagGroup.ReportFormat = nil // TODO: support --report summary 446 repoFlags.ReportFlagGroup.Compliance = nil // disable '--compliance' 447 repoFlags.ReportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' 448 449 cmd := &cobra.Command{ 450 Use: "repository [flags] (REPO_PATH | REPO_URL)", 451 Aliases: []string{"repo"}, 452 GroupID: groupScanning, 453 Short: "Scan a repository", 454 Example: ` # Scan your remote git repository 455 $ trivy repo https://github.com/knqyf263/trivy-ci-test 456 # Scan your local git repository 457 $ trivy repo /path/to/your/repository`, 458 PreRunE: func(cmd *cobra.Command, args []string) error { 459 if err := repoFlags.Bind(cmd); err != nil { 460 return xerrors.Errorf("flag bind error: %w", err) 461 } 462 return validateArgs(cmd, args) 463 }, 464 RunE: func(cmd *cobra.Command, args []string) error { 465 if err := repoFlags.Bind(cmd); err != nil { 466 return xerrors.Errorf("flag bind error: %w", err) 467 } 468 options, err := repoFlags.ToOptions(args, globalFlags) 469 if err != nil { 470 return xerrors.Errorf("flag error: %w", err) 471 } 472 return artifact.Run(cmd.Context(), options, artifact.TargetRepository) 473 }, 474 SilenceErrors: true, 475 SilenceUsage: true, 476 } 477 cmd.SetFlagErrorFunc(flagErrorFunc) 478 repoFlags.AddFlags(cmd) 479 cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, repoFlags.Usages(cmd))) 480 481 return cmd 482 } 483 484 func NewConvertCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { 485 convertFlags := &flag.Flags{ 486 ScanFlagGroup: &flag.ScanFlagGroup{}, 487 ReportFlagGroup: flag.NewReportFlagGroup(), 488 } 489 cmd := &cobra.Command{ 490 Use: "convert [flags] RESULT_JSON", 491 Aliases: []string{"conv"}, 492 GroupID: groupUtility, 493 Short: "Convert Trivy JSON report into a different format", 494 Example: ` # report conversion 495 $ trivy image --format json --output result.json --list-all-pkgs debian:11 496 $ trivy convert --format cyclonedx --output result.cdx result.json 497 `, 498 PreRunE: func(cmd *cobra.Command, args []string) error { 499 if err := convertFlags.Bind(cmd); err != nil { 500 return xerrors.Errorf("flag bind error: %w", err) 501 } 502 return validateArgs(cmd, args) 503 }, 504 RunE: func(cmd *cobra.Command, args []string) error { 505 if err := convertFlags.Bind(cmd); err != nil { 506 return xerrors.Errorf("flag bind error: %w", err) 507 } 508 opts, err := convertFlags.ToOptions(args, globalFlags) 509 if err != nil { 510 return xerrors.Errorf("flag error: %w", err) 511 } 512 513 return convert.Run(cmd.Context(), opts) 514 }, 515 SilenceErrors: true, 516 SilenceUsage: true, 517 } 518 cmd.SetFlagErrorFunc(flagErrorFunc) 519 convertFlags.AddFlags(cmd) 520 cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, convertFlags.Usages(cmd))) 521 522 return cmd 523 } 524 525 // NewClientCommand returns the 'client' subcommand that is deprecated 526 func NewClientCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { 527 remoteFlags := flag.NewClientFlags() 528 remoteAddr := flag.Flag{ 529 Name: "remote", 530 ConfigName: "server.addr", 531 Shorthand: "", 532 Default: "http://localhost:4954", 533 Usage: "server address", 534 } 535 remoteFlags.ServerAddr = &remoteAddr // disable '--server' and enable '--remote' instead. 536 537 clientFlags := &flag.Flags{ 538 CacheFlagGroup: flag.NewCacheFlagGroup(), 539 DBFlagGroup: flag.NewDBFlagGroup(), 540 MisconfFlagGroup: flag.NewMisconfFlagGroup(), 541 RegistryFlagGroup: flag.NewRegistryFlagGroup(), 542 RegoFlagGroup: flag.NewRegoFlagGroup(), 543 RemoteFlagGroup: remoteFlags, 544 ReportFlagGroup: flag.NewReportFlagGroup(), 545 ScanFlagGroup: flag.NewScanFlagGroup(), 546 VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), 547 } 548 549 cmd := &cobra.Command{ 550 Use: "client [flags] IMAGE_NAME", 551 Aliases: []string{"c"}, 552 Hidden: true, // 'client' command is deprecated 553 PreRunE: func(cmd *cobra.Command, args []string) error { 554 if err := clientFlags.Bind(cmd); err != nil { 555 return xerrors.Errorf("flag bind error: %w", err) 556 } 557 return validateArgs(cmd, args) 558 }, 559 RunE: func(cmd *cobra.Command, args []string) error { 560 log.Logger.Warn("'client' subcommand is deprecated now. See https://github.com/devseccon/trivy/discussions/2119") 561 562 if err := clientFlags.Bind(cmd); err != nil { 563 return xerrors.Errorf("flag bind error: %w", err) 564 } 565 options, err := clientFlags.ToOptions(args, globalFlags) 566 if err != nil { 567 return xerrors.Errorf("flag error: %w", err) 568 } 569 return artifact.Run(cmd.Context(), options, artifact.TargetContainerImage) 570 }, 571 SilenceErrors: true, 572 SilenceUsage: true, 573 } 574 cmd.SetFlagErrorFunc(flagErrorFunc) 575 clientFlags.AddFlags(cmd) 576 cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, clientFlags.Usages(cmd))) 577 578 return cmd 579 } 580 581 func NewServerCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { 582 serverFlags := &flag.Flags{ 583 CacheFlagGroup: flag.NewCacheFlagGroup(), 584 DBFlagGroup: flag.NewDBFlagGroup(), 585 ModuleFlagGroup: flag.NewModuleFlagGroup(), 586 RemoteFlagGroup: flag.NewServerFlags(), 587 RegistryFlagGroup: flag.NewRegistryFlagGroup(), 588 } 589 590 // java-db only works on client side. 591 serverFlags.DBFlagGroup.DownloadJavaDBOnly = nil // disable '--download-java-db-only' 592 serverFlags.DBFlagGroup.SkipJavaDBUpdate = nil // disable '--skip-java-db-update' 593 serverFlags.DBFlagGroup.JavaDBRepository = nil // disable '--java-db-repository' 594 595 cmd := &cobra.Command{ 596 Use: "server [flags]", 597 Aliases: []string{"s"}, 598 GroupID: groupUtility, 599 Short: "Server mode", 600 Example: ` # Run a server 601 $ trivy server 602 603 # Listen on 0.0.0.0:10000 604 $ trivy server --listen 0.0.0.0:10000 605 `, 606 Args: cobra.ExactArgs(0), 607 RunE: func(cmd *cobra.Command, args []string) error { 608 if err := serverFlags.Bind(cmd); err != nil { 609 return xerrors.Errorf("flag bind error: %w", err) 610 } 611 options, err := serverFlags.ToOptions(args, globalFlags) 612 if err != nil { 613 return xerrors.Errorf("flag error: %w", err) 614 } 615 return server.Run(cmd.Context(), options) 616 }, 617 SilenceErrors: true, 618 SilenceUsage: true, 619 } 620 cmd.SetFlagErrorFunc(flagErrorFunc) 621 serverFlags.AddFlags(cmd) 622 cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, serverFlags.Usages(cmd))) 623 624 return cmd 625 } 626 627 func NewConfigCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { 628 reportFlagGroup := flag.NewReportFlagGroup() 629 reportFlagGroup.DependencyTree = nil // disable '--dependency-tree' 630 reportFlagGroup.ListAllPkgs = nil // disable '--list-all-pkgs' 631 reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' 632 reportFormat := flag.ReportFormatFlag 633 reportFormat.Usage = "specify a compliance report format for the output" // @TODO: support --report summary for non compliance reports 634 reportFlagGroup.ReportFormat = &reportFormat 635 636 scanFlags := &flag.ScanFlagGroup{ 637 // Enable only '--skip-dirs' and '--skip-files' and disable other flags 638 SkipDirs: &flag.SkipDirsFlag, 639 SkipFiles: &flag.SkipFilesFlag, 640 FilePatterns: &flag.FilePatternsFlag, 641 } 642 643 configFlags := &flag.Flags{ 644 CacheFlagGroup: flag.NewCacheFlagGroup(), 645 MisconfFlagGroup: flag.NewMisconfFlagGroup(), 646 ModuleFlagGroup: flag.NewModuleFlagGroup(), 647 RegistryFlagGroup: flag.NewRegistryFlagGroup(), 648 RegoFlagGroup: flag.NewRegoFlagGroup(), 649 K8sFlagGroup: &flag.K8sFlagGroup{ 650 // disable unneeded flags 651 K8sVersion: &flag.K8sVersionFlag, 652 }, 653 ReportFlagGroup: reportFlagGroup, 654 ScanFlagGroup: scanFlags, 655 } 656 657 cmd := &cobra.Command{ 658 Use: "config [flags] DIR", 659 Aliases: []string{"conf"}, 660 GroupID: groupScanning, 661 Short: "Scan config files for misconfigurations", 662 PreRunE: func(cmd *cobra.Command, args []string) error { 663 if err := configFlags.Bind(cmd); err != nil { 664 return xerrors.Errorf("flag bind error: %w", err) 665 } 666 return validateArgs(cmd, args) 667 }, 668 RunE: func(cmd *cobra.Command, args []string) error { 669 if err := configFlags.Bind(cmd); err != nil { 670 return xerrors.Errorf("flag bind error: %w", err) 671 } 672 options, err := configFlags.ToOptions(args, globalFlags) 673 if err != nil { 674 return xerrors.Errorf("flag error: %w", err) 675 } 676 677 // Disable OS and language analyzers 678 options.DisabledAnalyzers = append(analyzer.TypeOSes, analyzer.TypeLanguages...) 679 680 // Scan only for misconfigurations 681 options.Scanners = types.Scanners{types.MisconfigScanner} 682 683 return artifact.Run(cmd.Context(), options, artifact.TargetFilesystem) 684 }, 685 SilenceErrors: true, 686 SilenceUsage: true, 687 } 688 cmd.SetFlagErrorFunc(flagErrorFunc) 689 configFlags.AddFlags(cmd) 690 cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, configFlags.Usages(cmd))) 691 692 return cmd 693 } 694 695 func NewPluginCommand() *cobra.Command { 696 cmd := &cobra.Command{ 697 Use: "plugin subcommand", 698 Aliases: []string{"p"}, 699 GroupID: groupManagement, 700 Short: "Manage plugins", 701 SilenceErrors: true, 702 SilenceUsage: true, 703 } 704 cmd.AddCommand( 705 &cobra.Command{ 706 Use: "install URL | FILE_PATH", 707 Aliases: []string{"i"}, 708 Short: "Install a plugin", 709 SilenceErrors: true, 710 DisableFlagsInUseLine: true, 711 Args: cobra.ExactArgs(1), 712 RunE: func(cmd *cobra.Command, args []string) error { 713 if _, err := plugin.Install(cmd.Context(), args[0], true); err != nil { 714 return xerrors.Errorf("plugin install error: %w", err) 715 } 716 return nil 717 }, 718 }, 719 &cobra.Command{ 720 Use: "uninstall PLUGIN_NAME", 721 Aliases: []string{"u"}, 722 SilenceErrors: true, 723 DisableFlagsInUseLine: true, 724 Short: "Uninstall a plugin", 725 Args: cobra.ExactArgs(1), 726 RunE: func(_ *cobra.Command, args []string) error { 727 if err := plugin.Uninstall(args[0]); err != nil { 728 return xerrors.Errorf("plugin uninstall error: %w", err) 729 } 730 return nil 731 }, 732 }, 733 &cobra.Command{ 734 Use: "list", 735 Aliases: []string{"l"}, 736 SilenceErrors: true, 737 DisableFlagsInUseLine: true, 738 Short: "List installed plugin", 739 Args: cobra.NoArgs, 740 RunE: func(cmd *cobra.Command, args []string) error { 741 info, err := plugin.List() 742 if err != nil { 743 return xerrors.Errorf("plugin list display error: %w", err) 744 } 745 if _, err := fmt.Fprint(os.Stdout, info); err != nil { 746 return xerrors.Errorf("print error: %w", err) 747 } 748 return nil 749 }, 750 }, 751 &cobra.Command{ 752 Use: "info PLUGIN_NAME", 753 Short: "Show information about the specified plugin", 754 SilenceErrors: true, 755 DisableFlagsInUseLine: true, 756 Args: cobra.ExactArgs(1), 757 RunE: func(_ *cobra.Command, args []string) error { 758 info, err := plugin.Information(args[0]) 759 if err != nil { 760 return xerrors.Errorf("plugin information display error: %w", err) 761 } 762 if _, err := fmt.Fprint(os.Stdout, info); err != nil { 763 return xerrors.Errorf("print error: %w", err) 764 } 765 return nil 766 }, 767 }, 768 &cobra.Command{ 769 Use: "run URL | FILE_PATH", 770 Aliases: []string{"r"}, 771 SilenceErrors: true, 772 DisableFlagsInUseLine: true, 773 Short: "Run a plugin on the fly", 774 Args: cobra.MinimumNArgs(1), 775 RunE: func(cmd *cobra.Command, args []string) error { 776 return plugin.RunWithArgs(cmd.Context(), args[0], args[1:]) 777 }, 778 }, 779 &cobra.Command{ 780 Use: "update PLUGIN_NAME", 781 Short: "Update an existing plugin", 782 SilenceErrors: true, 783 DisableFlagsInUseLine: true, 784 Args: cobra.ExactArgs(1), 785 RunE: func(_ *cobra.Command, args []string) error { 786 if err := plugin.Update(args[0]); err != nil { 787 return xerrors.Errorf("plugin update error: %w", err) 788 } 789 return nil 790 }, 791 }, 792 ) 793 cmd.SetFlagErrorFunc(flagErrorFunc) 794 return cmd 795 } 796 797 func NewModuleCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { 798 moduleFlags := &flag.Flags{ 799 ModuleFlagGroup: flag.NewModuleFlagGroup(), 800 } 801 802 cmd := &cobra.Command{ 803 Use: "module subcommand", 804 Aliases: []string{"m"}, 805 GroupID: groupManagement, 806 Short: "Manage modules", 807 SilenceErrors: true, 808 SilenceUsage: true, 809 } 810 811 // Add subcommands 812 cmd.AddCommand( 813 &cobra.Command{ 814 Use: "install [flags] REPOSITORY", 815 Aliases: []string{"i"}, 816 Short: "Install a module", 817 Args: cobra.ExactArgs(1), 818 PreRunE: func(cmd *cobra.Command, args []string) error { 819 if err := moduleFlags.Bind(cmd); err != nil { 820 return xerrors.Errorf("flag bind error: %w", err) 821 } 822 return nil 823 }, 824 RunE: func(cmd *cobra.Command, args []string) error { 825 if len(args) != 1 { 826 return cmd.Help() 827 } 828 829 repo := args[0] 830 opts, err := moduleFlags.ToOptions(args, globalFlags) 831 if err != nil { 832 return xerrors.Errorf("flag error: %w", err) 833 } 834 return module.Install(cmd.Context(), opts.ModuleDir, repo, opts.Quiet, opts.RegistryOpts()) 835 }, 836 }, 837 &cobra.Command{ 838 Use: "uninstall [flags] REPOSITORY", 839 Aliases: []string{"u"}, 840 Short: "Uninstall a module", 841 Args: cobra.ExactArgs(1), 842 PreRunE: func(cmd *cobra.Command, args []string) error { 843 if err := moduleFlags.Bind(cmd); err != nil { 844 return xerrors.Errorf("flag bind error: %w", err) 845 } 846 return nil 847 }, 848 RunE: func(cmd *cobra.Command, args []string) error { 849 if len(args) != 1 { 850 return cmd.Help() 851 } 852 853 repo := args[0] 854 opts, err := moduleFlags.ToOptions(args, globalFlags) 855 if err != nil { 856 return xerrors.Errorf("flag error: %w", err) 857 } 858 return module.Uninstall(cmd.Context(), opts.ModuleDir, repo) 859 }, 860 }, 861 ) 862 moduleFlags.AddFlags(cmd) 863 cmd.SetFlagErrorFunc(flagErrorFunc) 864 return cmd 865 } 866 867 func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { 868 scanFlags := flag.NewScanFlagGroup() 869 scanners := flag.ScannersFlag 870 // overwrite the default scanners 871 scanners.Values = xstrings.ToStringSlice(types.Scanners{ 872 types.VulnerabilityScanner, 873 types.MisconfigScanner, 874 types.SecretScanner, 875 types.RBACScanner, 876 }) 877 scanners.Default = scanners.Values 878 scanFlags.Scanners = &scanners 879 scanFlags.IncludeDevDeps = nil // disable '--include-dev-deps' 880 881 // required only SourceFlag 882 imageFlags := &flag.ImageFlagGroup{ImageSources: &flag.SourceFlag} 883 884 reportFlagGroup := flag.NewReportFlagGroup() 885 compliance := flag.ComplianceFlag 886 compliance.Values = []string{ 887 types.ComplianceK8sNsa, 888 types.ComplianceK8sCIS, 889 types.ComplianceK8sPSSBaseline, 890 types.ComplianceK8sPSSRestricted, 891 } 892 reportFlagGroup.Compliance = &compliance // override usage as the accepted values differ for each subcommand. 893 reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' 894 895 formatFlag := flag.FormatFlag 896 formatFlag.Values = xstrings.ToStringSlice([]types.Format{ 897 types.FormatTable, 898 types.FormatJSON, 899 types.FormatCycloneDX, 900 }) 901 reportFlagGroup.Format = &formatFlag 902 903 misconfFlagGroup := flag.NewMisconfFlagGroup() 904 misconfFlagGroup.CloudformationParamVars = nil // disable '--cf-params' 905 misconfFlagGroup.TerraformTFVars = nil // disable '--tf-vars' 906 907 k8sFlags := &flag.Flags{ 908 CacheFlagGroup: flag.NewCacheFlagGroup(), 909 DBFlagGroup: flag.NewDBFlagGroup(), 910 ImageFlagGroup: imageFlags, 911 K8sFlagGroup: flag.NewK8sFlagGroup(), // kubernetes-specific flags 912 MisconfFlagGroup: misconfFlagGroup, 913 RegoFlagGroup: flag.NewRegoFlagGroup(), 914 ReportFlagGroup: reportFlagGroup, 915 ScanFlagGroup: scanFlags, 916 SecretFlagGroup: flag.NewSecretFlagGroup(), 917 RegistryFlagGroup: flag.NewRegistryFlagGroup(), 918 VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), 919 } 920 cmd := &cobra.Command{ 921 Use: "kubernetes [flags] { cluster | all | specific resources like kubectl. eg: pods, pod/NAME }", 922 Aliases: []string{"k8s"}, 923 GroupID: groupScanning, 924 Short: "[EXPERIMENTAL] Scan kubernetes cluster", 925 Example: ` # cluster scanning 926 $ trivy k8s --report summary cluster 927 928 # namespace scanning: 929 $ trivy k8s -n kube-system --report summary all 930 931 # resources scanning: 932 $ trivy k8s --report=summary deploy 933 $ trivy k8s --namespace=kube-system --report=summary deploy,configmaps 934 935 # resource scanning: 936 $ trivy k8s deployment/orion 937 `, 938 PreRunE: func(cmd *cobra.Command, args []string) error { 939 if err := k8sFlags.Bind(cmd); err != nil { 940 return xerrors.Errorf("flag bind error: %w", err) 941 } 942 return validateArgs(cmd, args) 943 }, 944 RunE: func(cmd *cobra.Command, args []string) error { 945 if err := k8sFlags.Bind(cmd); err != nil { 946 return xerrors.Errorf("flag bind error: %w", err) 947 } 948 opts, err := k8sFlags.ToOptions(args, globalFlags) 949 if err != nil { 950 return xerrors.Errorf("flag error: %w", err) 951 } 952 953 return k8scommands.Run(cmd.Context(), args, opts) 954 }, 955 SilenceErrors: true, 956 SilenceUsage: true, 957 } 958 cmd.SetFlagErrorFunc(flagErrorFunc) 959 k8sFlags.AddFlags(cmd) 960 cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, k8sFlags.Usages(cmd))) 961 962 return cmd 963 } 964 965 func NewAWSCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { 966 reportFlagGroup := flag.NewReportFlagGroup() 967 compliance := flag.ComplianceFlag 968 compliance.Values = []string{ 969 types.ComplianceAWSCIS12, 970 types.ComplianceAWSCIS14, 971 } 972 reportFlagGroup.Compliance = &compliance // override usage as the accepted values differ for each subcommand. 973 reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' 974 975 awsFlags := &flag.Flags{ 976 AWSFlagGroup: flag.NewAWSFlagGroup(), 977 CloudFlagGroup: flag.NewCloudFlagGroup(), 978 MisconfFlagGroup: flag.NewMisconfFlagGroup(), 979 RegoFlagGroup: flag.NewRegoFlagGroup(), 980 ReportFlagGroup: reportFlagGroup, 981 } 982 983 services := awsScanner.AllSupportedServices() 984 sort.Strings(services) 985 986 cmd := &cobra.Command{ 987 Use: "aws [flags]", 988 Aliases: []string{}, 989 GroupID: groupScanning, 990 Args: cobra.ExactArgs(0), 991 Short: "[EXPERIMENTAL] Scan AWS account", 992 Long: fmt.Sprintf(`Scan an AWS account for misconfigurations. Trivy uses the same authentication methods as the AWS CLI. See https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html 993 994 The following services are supported: 995 996 - %s 997 `, strings.Join(services, "\n- ")), 998 Example: ` # basic scanning 999 $ trivy aws --region us-east-1 1000 1001 # limit scan to a single service: 1002 $ trivy aws --region us-east-1 --service s3 1003 1004 # limit scan to multiple services: 1005 $ trivy aws --region us-east-1 --service s3 --service ec2 1006 1007 # force refresh of cache for fresh results 1008 $ trivy aws --region us-east-1 --update-cache 1009 `, 1010 PreRunE: func(cmd *cobra.Command, args []string) error { 1011 if err := awsFlags.Bind(cmd); err != nil { 1012 return xerrors.Errorf("flag bind error: %w", err) 1013 } 1014 return nil 1015 }, 1016 RunE: func(cmd *cobra.Command, args []string) error { 1017 opts, err := awsFlags.ToOptions(args, globalFlags) 1018 if err != nil { 1019 return xerrors.Errorf("flag error: %w", err) 1020 } 1021 if opts.Timeout < time.Hour { 1022 opts.Timeout = time.Hour 1023 log.Logger.Debug("Timeout is set to less than 1 hour - upgrading to 1 hour for this command.") 1024 } 1025 return awscommands.Run(cmd.Context(), opts) 1026 }, 1027 SilenceErrors: true, 1028 SilenceUsage: true, 1029 } 1030 cmd.SetFlagErrorFunc(flagErrorFunc) 1031 awsFlags.AddFlags(cmd) 1032 cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, awsFlags.Usages(cmd))) 1033 1034 return cmd 1035 } 1036 1037 func NewVMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { 1038 vmFlags := &flag.Flags{ 1039 CacheFlagGroup: flag.NewCacheFlagGroup(), 1040 DBFlagGroup: flag.NewDBFlagGroup(), 1041 MisconfFlagGroup: flag.NewMisconfFlagGroup(), 1042 ModuleFlagGroup: flag.NewModuleFlagGroup(), 1043 RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode 1044 ReportFlagGroup: flag.NewReportFlagGroup(), 1045 ScanFlagGroup: flag.NewScanFlagGroup(), 1046 SecretFlagGroup: flag.NewSecretFlagGroup(), 1047 VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), 1048 AWSFlagGroup: &flag.AWSFlagGroup{ 1049 Region: &flag.Flag{ 1050 Name: "aws-region", 1051 ConfigName: "aws.region", 1052 Default: "", 1053 Usage: "AWS region to scan", 1054 }, 1055 }, 1056 } 1057 vmFlags.ReportFlagGroup.ReportFormat = nil // disable '--report' 1058 vmFlags.ScanFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' 1059 vmFlags.MisconfFlagGroup.CloudformationParamVars = nil // disable '--cf-params' 1060 vmFlags.MisconfFlagGroup.TerraformTFVars = nil // disable '--tf-vars' 1061 1062 cmd := &cobra.Command{ 1063 Use: "vm [flags] VM_IMAGE", 1064 Aliases: []string{}, 1065 GroupID: groupScanning, 1066 Short: "[EXPERIMENTAL] Scan a virtual machine image", 1067 Example: ` # Scan your AWS AMI 1068 $ trivy vm --scanners vuln ami:${your_ami_id} 1069 1070 # Scan your AWS EBS snapshot 1071 $ trivy vm ebs:${your_ebs_snapshot_id} 1072 `, 1073 PreRunE: func(cmd *cobra.Command, args []string) error { 1074 if err := vmFlags.Bind(cmd); err != nil { 1075 return xerrors.Errorf("flag bind error: %w", err) 1076 } 1077 return validateArgs(cmd, args) 1078 }, 1079 RunE: func(cmd *cobra.Command, args []string) error { 1080 if err := vmFlags.Bind(cmd); err != nil { 1081 return xerrors.Errorf("flag bind error: %w", err) 1082 } 1083 options, err := vmFlags.ToOptions(args, globalFlags) 1084 if err != nil { 1085 return xerrors.Errorf("flag error: %w", err) 1086 } 1087 if options.Timeout < time.Minute*30 { 1088 options.Timeout = time.Minute * 30 1089 log.Logger.Debug("Timeout is set to less than 30 min - upgrading to 30 min for this command.") 1090 } 1091 return artifact.Run(cmd.Context(), options, artifact.TargetVM) 1092 }, 1093 SilenceErrors: true, 1094 SilenceUsage: true, 1095 } 1096 cmd.SetFlagErrorFunc(flagErrorFunc) 1097 vmFlags.AddFlags(cmd) 1098 cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, vmFlags.Usages(cmd))) 1099 1100 return cmd 1101 } 1102 1103 func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { 1104 reportFlagGroup := flag.NewReportFlagGroup() 1105 reportFlagGroup.DependencyTree = nil // disable '--dependency-tree' 1106 reportFlagGroup.ReportFormat = nil // TODO: support --report summary 1107 1108 scanFlagGroup := flag.NewScanFlagGroup() 1109 scanFlagGroup.Scanners = nil // disable '--scanners' as it always scans for vulnerabilities 1110 scanFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' 1111 scanFlagGroup.Parallel = nil // disable '--parallel' 1112 1113 sbomFlags := &flag.Flags{ 1114 CacheFlagGroup: flag.NewCacheFlagGroup(), 1115 DBFlagGroup: flag.NewDBFlagGroup(), 1116 RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode 1117 ReportFlagGroup: reportFlagGroup, 1118 ScanFlagGroup: scanFlagGroup, 1119 SBOMFlagGroup: flag.NewSBOMFlagGroup(), 1120 VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), 1121 } 1122 1123 cmd := &cobra.Command{ 1124 Use: "sbom [flags] SBOM_PATH", 1125 Short: "Scan SBOM for vulnerabilities", 1126 GroupID: groupScanning, 1127 Example: ` # Scan CycloneDX and show the result in tables 1128 $ trivy sbom /path/to/report.cdx 1129 1130 # Scan CycloneDX-type attestation and show the result in tables 1131 $ trivy sbom /path/to/report.cdx.intoto.jsonl 1132 `, 1133 PreRunE: func(cmd *cobra.Command, args []string) error { 1134 if err := sbomFlags.Bind(cmd); err != nil { 1135 return xerrors.Errorf("flag bind error: %w", err) 1136 } 1137 return validateArgs(cmd, args) 1138 }, 1139 RunE: func(cmd *cobra.Command, args []string) error { 1140 if err := sbomFlags.Bind(cmd); err != nil { 1141 return xerrors.Errorf("flag bind error: %w", err) 1142 } 1143 options, err := sbomFlags.ToOptions(args, globalFlags) 1144 if err != nil { 1145 return xerrors.Errorf("flag error: %w", err) 1146 } 1147 1148 // Scan vulnerabilities 1149 options.Scanners = types.Scanners{types.VulnerabilityScanner} 1150 1151 return artifact.Run(cmd.Context(), options, artifact.TargetSBOM) 1152 }, 1153 SilenceErrors: true, 1154 SilenceUsage: true, 1155 } 1156 cmd.SetFlagErrorFunc(flagErrorFunc) 1157 sbomFlags.AddFlags(cmd) 1158 cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, sbomFlags.Usages(cmd))) 1159 1160 return cmd 1161 } 1162 1163 func NewVersionCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { 1164 var versionFormat string 1165 cmd := &cobra.Command{ 1166 Use: "version [flags]", 1167 Short: "Print the version", 1168 GroupID: groupUtility, 1169 Args: cobra.NoArgs, 1170 RunE: func(cmd *cobra.Command, args []string) error { 1171 options := globalFlags.ToOptions() 1172 return showVersion(options.CacheDir, versionFormat, cmd.OutOrStdout()) 1173 }, 1174 SilenceErrors: true, 1175 SilenceUsage: true, 1176 } 1177 cmd.SetFlagErrorFunc(flagErrorFunc) 1178 1179 // Add version format flag, only json is supported 1180 cmd.Flags().StringVarP(&versionFormat, flag.FormatFlag.Name, flag.FormatFlag.Shorthand, "", "version format (json)") 1181 1182 return cmd 1183 } 1184 1185 func showVersion(cacheDir, outputFormat string, w io.Writer) error { 1186 versionInfo := version.NewVersionInfo(cacheDir) 1187 switch outputFormat { 1188 case "json": 1189 if err := json.NewEncoder(w).Encode(versionInfo); err != nil { 1190 return xerrors.Errorf("json encode error: %w", err) 1191 } 1192 default: 1193 fmt.Fprint(w, versionInfo.String()) 1194 } 1195 return nil 1196 } 1197 1198 func validateArgs(cmd *cobra.Command, args []string) error { 1199 // '--clear-cache', '--download-db-only', '--download-java-db-only', '--reset' and '--generate-default-config' don't conduct the subsequent scanning 1200 if viper.GetBool(flag.ClearCacheFlag.ConfigName) || viper.GetBool(flag.DownloadDBOnlyFlag.ConfigName) || 1201 viper.GetBool(flag.ResetFlag.ConfigName) || viper.GetBool(flag.GenerateDefaultConfigFlag.ConfigName) || 1202 viper.GetBool(flag.DownloadJavaDBOnlyFlag.ConfigName) || viper.GetBool(flag.ResetPolicyBundleFlag.ConfigName) { 1203 return nil 1204 } 1205 1206 if len(args) == 0 && viper.GetString(flag.InputFlag.ConfigName) == "" { 1207 if err := cmd.Help(); err != nil { 1208 return err 1209 } 1210 1211 if f := cmd.Flags().Lookup(flag.InputFlag.ConfigName); f != nil { 1212 return xerrors.New(`Require at least 1 argument or --input option`) 1213 } 1214 return xerrors.New(`Require at least 1 argument`) 1215 } else if cmd.Name() != "kubernetes" && len(args) > 1 { 1216 if err := cmd.Help(); err != nil { 1217 return err 1218 } 1219 return xerrors.New(`multiple targets cannot be specified`) 1220 } 1221 1222 return nil 1223 } 1224 1225 // show help on using the command when an invalid flag is encountered 1226 func flagErrorFunc(command *cobra.Command, err error) error { 1227 if err := command.Help(); err != nil { 1228 return err 1229 } 1230 command.Println() // add empty line after list of flags 1231 return err 1232 }