github.com/wolfi-dev/wolfictl@v0.16.11/pkg/cli/advisory_list.go (about)

     1  package cli
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strings"
     7  
     8  	"github.com/spf13/cobra"
     9  	v2 "github.com/wolfi-dev/wolfictl/pkg/configs/advisory/v2"
    10  	rwos "github.com/wolfi-dev/wolfictl/pkg/configs/rwfs/os"
    11  	"github.com/wolfi-dev/wolfictl/pkg/distro"
    12  )
    13  
    14  func cmdAdvisoryList() *cobra.Command {
    15  	p := &listParams{}
    16  	cmd := &cobra.Command{
    17  		Use:     "list",
    18  		Aliases: []string{"ls"},
    19  		Short:   "List advisories for specific packages, vulnerabilities, or the entire data set",
    20  		Long: `List advisories for specific packages, vulnerabilities, or the entire data set.
    21  
    22  The 'list' (or 'ls') command prints a list of advisories based on the given 
    23  selection criteria. By default, all advisories in the current advisory data set 
    24  will be listed.
    25  
    26  FILTERING
    27  
    28  You can list advisories for a single package:
    29  
    30  	wolfictl adv ls -p glibc
    31  
    32  You can list all advisories for a given vulnerability ID across all packages:
    33  
    34  	wolfictl adv ls -V CVE-2023-38545
    35  
    36  You can show only advisories that are considered not to be "resolved":
    37  
    38  	wolfictl adv ls --unresolved
    39  
    40  And you can combine the above flags as needed.
    41  
    42  HISTORY
    43  
    44  Using the --history flag, you can list advisory events instead of just 
    45  advisories' latest states. This is useful for viewing a summary of an 
    46  investigation over time for a given package/vulnerability match.'
    47  `,
    48  		SilenceErrors: true,
    49  		Args:          cobra.NoArgs,
    50  		RunE: func(cmd *cobra.Command, _ []string) error {
    51  			advisoriesRepoDir := resolveAdvisoriesDirInput(p.advisoriesRepoDir)
    52  			if advisoriesRepoDir == "" {
    53  				if p.doNotDetectDistro {
    54  					return fmt.Errorf("no advisories repo dir specified")
    55  				}
    56  
    57  				d, err := distro.Detect()
    58  				if err != nil {
    59  					return fmt.Errorf("no advisories repo dir specified, and distro auto-detection failed: %w", err)
    60  				}
    61  
    62  				advisoriesRepoDir = d.Local.AdvisoriesRepo.Dir
    63  				_, _ = fmt.Fprint(os.Stderr, renderDetectedDistro(d))
    64  			}
    65  
    66  			advisoriesFsys := rwos.DirFS(advisoriesRepoDir)
    67  			advisoryCfgs, err := v2.NewIndex(cmd.Context(), advisoriesFsys)
    68  			if err != nil {
    69  				return err
    70  			}
    71  
    72  			var cfgs []v2.Document
    73  			if pkg := p.packageName; pkg != "" {
    74  				cfgs = advisoryCfgs.Select().WhereName(pkg).Configurations()
    75  			} else {
    76  				cfgs = advisoryCfgs.Select().Configurations()
    77  			}
    78  
    79  			var output string
    80  
    81  			for _, cfg := range cfgs {
    82  				for _, adv := range cfg.Advisories {
    83  					if len(adv.Events) == 0 {
    84  						// nothing to show
    85  						continue
    86  					}
    87  
    88  					if p.vuln != "" && !adv.DescribesVulnerability(p.vuln) {
    89  						// user specified a particular different vulnerability
    90  						continue
    91  					}
    92  
    93  					if p.unresolved && adv.Resolved() {
    94  						// user only wants to see unresolved advisories
    95  						continue
    96  					}
    97  
    98  					vulnID := adv.ID
    99  					if len(adv.Aliases) > 0 {
   100  						vulnID += fmt.Sprintf(" (%s)", strings.Join(adv.Aliases, ", "))
   101  					}
   102  
   103  					if p.history {
   104  						// user wants to see the full history
   105  						sorted := adv.SortedEvents()
   106  						for _, event := range sorted {
   107  							statusDescription := renderListItem(event)
   108  							timestamp := event.Timestamp
   109  
   110  							output += fmt.Sprintf(
   111  								"%s: %s: %s @ %s\n",
   112  								cfg.Package.Name,
   113  								vulnID,
   114  								statusDescription,
   115  								timestamp,
   116  							)
   117  						}
   118  
   119  						continue
   120  					}
   121  
   122  					statusDescription := renderListItem(adv.Latest())
   123  					output += fmt.Sprintf("%s: %s: %s\n", cfg.Package.Name, vulnID, statusDescription)
   124  				}
   125  			}
   126  
   127  			fmt.Print(output)
   128  			return nil
   129  		},
   130  	}
   131  
   132  	p.addFlagsTo(cmd)
   133  	return cmd
   134  }
   135  
   136  type listParams struct {
   137  	doNotDetectDistro bool
   138  
   139  	advisoriesRepoDir string
   140  
   141  	packageName string
   142  	vuln        string
   143  	history     bool
   144  	unresolved  bool
   145  }
   146  
   147  func (p *listParams) addFlagsTo(cmd *cobra.Command) {
   148  	addNoDistroDetectionFlag(&p.doNotDetectDistro, cmd)
   149  
   150  	addAdvisoriesDirFlag(&p.advisoriesRepoDir, cmd)
   151  
   152  	addPackageFlag(&p.packageName, cmd)
   153  	addVulnFlag(&p.vuln, cmd)
   154  
   155  	cmd.Flags().BoolVar(&p.history, "history", false, "show full history for advisories")
   156  	cmd.Flags().BoolVar(&p.unresolved, "unresolved", false, "only show advisories considered to be unresolved")
   157  }
   158  
   159  func renderListItem(event v2.Event) string {
   160  	switch t := event.Type; t {
   161  	case v2.EventTypeAnalysisNotPlanned,
   162  		v2.EventTypeFixNotPlanned,
   163  		v2.EventTypePendingUpstreamFix:
   164  		return t
   165  
   166  	case v2.EventTypeDetection:
   167  		expanded := ""
   168  		if data, ok := event.Data.(v2.Detection); ok && data.Type != "" {
   169  			switch data.Type {
   170  			case v2.DetectionTypeManual:
   171  				expanded = "manual"
   172  
   173  			case v2.DetectionTypeNVDAPI:
   174  				if data, ok := data.Data.(v2.DetectionNVDAPI); ok {
   175  					expanded = fmt.Sprintf("nvdapi: %s", data.CPEFound)
   176  				}
   177  			}
   178  		}
   179  		return fmt.Sprintf("%s (%s)", t, expanded)
   180  
   181  	case v2.EventTypeTruePositiveDetermination:
   182  		expanded := ""
   183  		if data, ok := event.Data.(v2.TruePositiveDetermination); ok && data.Note != "" {
   184  			expanded = data.Note
   185  		}
   186  		return fmt.Sprintf("%s (%s)", t, expanded)
   187  
   188  	case v2.EventTypeFixed:
   189  		expanded := ""
   190  		if data, ok := event.Data.(v2.Fixed); ok && data.FixedVersion != "" {
   191  			expanded = data.FixedVersion
   192  		}
   193  		return fmt.Sprintf("%s (%s)", t, expanded)
   194  
   195  	case v2.EventTypeFalsePositiveDetermination:
   196  		expanded := ""
   197  		if data, ok := event.Data.(v2.FalsePositiveDetermination); ok && data.Type != "" {
   198  			expanded = data.Type
   199  		}
   200  		return fmt.Sprintf("%s (%s)", t, expanded)
   201  	}
   202  
   203  	return "INVALID EVENT TYPE"
   204  }