zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/cli/client/cves_sub_cmd.go (about)

     1  //go:build search
     2  // +build search
     3  
     4  package client
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/spf13/cobra"
    11  
    12  	zerr "zotregistry.dev/zot/errors"
    13  	zcommon "zotregistry.dev/zot/pkg/common"
    14  )
    15  
    16  const (
    17  	maxRetries = 20
    18  )
    19  
    20  func NewCveForImageCommand(searchService SearchService) *cobra.Command {
    21  	var (
    22  		searchedCVEID   string
    23  		cveListSortFlag = CVEListSortFlag(SortBySeverity)
    24  	)
    25  
    26  	cveForImageCmd := &cobra.Command{
    27  		Use:   "list [repo:tag]|[repo@digest]",
    28  		Short: "List CVEs by REPO:TAG or REPO@DIGEST",
    29  		Long:  `List CVEs by REPO:TAG or REPO@DIGEST`,
    30  		Args:  OneImageWithRefArg,
    31  		RunE: func(cmd *cobra.Command, args []string) error {
    32  			searchConfig, err := GetSearchConfigFromFlags(cmd, searchService)
    33  			if err != nil {
    34  				return err
    35  			}
    36  
    37  			err = CheckExtEndPointQuery(searchConfig, CVEListForImageQuery())
    38  			if err != nil {
    39  				return fmt.Errorf("%w: '%s'", err, CVEListForImageQuery().Name)
    40  			}
    41  
    42  			image := args[0]
    43  
    44  			return SearchCVEForImageGQL(searchConfig, image, searchedCVEID)
    45  		},
    46  	}
    47  
    48  	cveForImageCmd.Flags().StringVar(&searchedCVEID, SearchedCVEID, "", "Search for a specific CVE by name/id")
    49  	cveForImageCmd.Flags().Var(&cveListSortFlag, SortByFlag,
    50  		fmt.Sprintf("Options for sorting the output: [%s]", CVEListSortOptionsStr()))
    51  
    52  	return cveForImageCmd
    53  }
    54  
    55  func NewImagesByCVEIDCommand(searchService SearchService) *cobra.Command {
    56  	var (
    57  		repo              string
    58  		imageListSortFlag = ImageListSortFlag(SortByAlphabeticAsc)
    59  	)
    60  
    61  	imagesByCVEIDCmd := &cobra.Command{
    62  		Use:   "affected [cveId]",
    63  		Short: "List images affected by a CVE",
    64  		Long:  `List images affected by a CVE`,
    65  		Args: func(cmd *cobra.Command, args []string) error {
    66  			if err := cobra.ExactArgs(1)(cmd, args); err != nil {
    67  				return err
    68  			}
    69  
    70  			if !strings.HasPrefix(args[0], "CVE") {
    71  				return fmt.Errorf("%w: expected a cveid 'CVE-...' got '%s'", zerr.ErrInvalidCLIParameter, args[0])
    72  			}
    73  
    74  			return nil
    75  		},
    76  		RunE: func(cmd *cobra.Command, args []string) error {
    77  			searchConfig, err := GetSearchConfigFromFlags(cmd, searchService)
    78  			if err != nil {
    79  				return err
    80  			}
    81  
    82  			err = CheckExtEndPointQuery(searchConfig, ImageListForCVEQuery())
    83  			if err != nil {
    84  				return fmt.Errorf("%w: '%s'", err, ImageListForCVEQuery().Name)
    85  			}
    86  
    87  			searchedCVEID := args[0]
    88  
    89  			return SearchImagesByCVEIDGQL(searchConfig, repo, searchedCVEID)
    90  		},
    91  	}
    92  
    93  	imagesByCVEIDCmd.Flags().StringVar(&repo, "repo", "", "Search for a specific CVE by name/id")
    94  	imagesByCVEIDCmd.Flags().Var(&imageListSortFlag, SortByFlag,
    95  		fmt.Sprintf("Options for sorting the output: [%s]", ImageListSortOptionsStr()))
    96  
    97  	return imagesByCVEIDCmd
    98  }
    99  
   100  func NewFixedTagsCommand(searchService SearchService) *cobra.Command {
   101  	imageListSortFlag := ImageListSortFlag(SortByAlphabeticAsc)
   102  
   103  	fixedTagsCmd := &cobra.Command{
   104  		Use:   "fixed [repo] [cveId]",
   105  		Short: "List tags where a CVE is fixed",
   106  		Long:  `List tags where a CVE is fixed`,
   107  		Args: func(cmd *cobra.Command, args []string) error {
   108  			const argCount = 2
   109  
   110  			if err := cobra.ExactArgs(argCount)(cmd, args); err != nil {
   111  				return err
   112  			}
   113  
   114  			if !zcommon.CheckIsCorrectRepoNameFormat(args[0]) {
   115  				return fmt.Errorf("%w: expected a valid repo name for first argument '%s'", zerr.ErrInvalidCLIParameter, args[0])
   116  			}
   117  
   118  			return nil
   119  		},
   120  		RunE: func(cmd *cobra.Command, args []string) error {
   121  			searchConfig, err := GetSearchConfigFromFlags(cmd, searchService)
   122  			if err != nil {
   123  				return err
   124  			}
   125  
   126  			err = CheckExtEndPointQuery(searchConfig, ImageListWithCVEFixedQuery())
   127  			if err != nil {
   128  				return fmt.Errorf("%w: '%s'", err, ImageListWithCVEFixedQuery().Name)
   129  			}
   130  
   131  			repo := args[0]
   132  			searchedCVEID := args[1]
   133  
   134  			return SearchFixedTagsGQL(searchConfig, repo, searchedCVEID)
   135  		},
   136  	}
   137  
   138  	fixedTagsCmd.Flags().Var(&imageListSortFlag, SortByFlag,
   139  		fmt.Sprintf("Options for sorting the output: [%s]", ImageListSortOptionsStr()))
   140  
   141  	return fixedTagsCmd
   142  }
   143  
   144  func NewCVEDiffCommand(searchService SearchService) *cobra.Command {
   145  	var (
   146  		minuendStr, minuendArch       string
   147  		subtrahendStr, subtrahendArch string
   148  	)
   149  	imagesByCVEIDCmd := &cobra.Command{
   150  		Use:   "diff [minuend] ([minuend-platform]) [subtrahend] ([subtrahend-platform])",
   151  		Short: "List the CVE's present in minuend that are not present in subtrahend",
   152  		Long:  `List the CVE's present in minuend that are not present in subtrahend`,
   153  		Args: func(cmd *cobra.Command, args []string) error {
   154  			const (
   155  				twoArgs   = 2
   156  				threeArgs = 3
   157  				fourArgs  = 4
   158  			)
   159  
   160  			if err := cobra.RangeArgs(twoArgs, fourArgs)(cmd, args); err != nil {
   161  				return err
   162  			}
   163  
   164  			if !isRepoTag(args[0]) {
   165  				return fmt.Errorf("%w: first parameter should be a repo:tag", zerr.ErrInvalidArgs)
   166  			}
   167  
   168  			minuendStr = args[0]
   169  
   170  			if isRepoTag(args[1]) {
   171  				subtrahendStr = args[1]
   172  			} else {
   173  				minuendArch = args[1]
   174  
   175  				if len(args) == twoArgs {
   176  					return fmt.Errorf("%w: not enough arguments, specified only 1 image with arch", zerr.ErrInvalidArgs)
   177  				}
   178  			}
   179  
   180  			if len(args) == twoArgs {
   181  				return nil
   182  			}
   183  
   184  			if isRepoTag(args[2]) {
   185  				if subtrahendStr == "" {
   186  					subtrahendStr = args[2]
   187  				} else {
   188  					return fmt.Errorf("%w: too many repo:tag inputs", zerr.ErrInvalidArgs)
   189  				}
   190  			} else {
   191  				if subtrahendStr == "" {
   192  					return fmt.Errorf("%w: 3rd argument should be a repo:tag", zerr.ErrInvalidArgs)
   193  				} else {
   194  					subtrahendArch = args[2]
   195  				}
   196  			}
   197  
   198  			if len(args) == threeArgs {
   199  				return nil
   200  			}
   201  
   202  			if isRepoTag(args[3]) {
   203  				return fmt.Errorf("%w: 4th argument should not be a repo:tag but an arch", zerr.ErrInvalidArgs)
   204  			} else {
   205  				subtrahendArch = args[3]
   206  			}
   207  
   208  			return nil
   209  		},
   210  		RunE: func(cmd *cobra.Command, args []string) error {
   211  			searchConfig, err := GetSearchConfigFromFlags(cmd, searchService)
   212  			if err != nil {
   213  				return err
   214  			}
   215  
   216  			err = CheckExtEndPointQuery(searchConfig, CVEDiffListForImagesQuery())
   217  			if err != nil {
   218  				return fmt.Errorf("%w: '%s'", err, CVEDiffListForImagesQuery().Name)
   219  			}
   220  
   221  			// parse the args and determine the input
   222  			minuend := getImageIdentifier(minuendStr, minuendArch)
   223  			subtrahend := getImageIdentifier(subtrahendStr, subtrahendArch)
   224  
   225  			return SearchCVEDiffList(searchConfig, minuend, subtrahend)
   226  		},
   227  	}
   228  
   229  	return imagesByCVEIDCmd
   230  }
   231  
   232  func isRepoTag(arg string) bool {
   233  	_, _, _, err := zcommon.GetRepoReference(arg) //nolint:dogsled
   234  
   235  	return err == nil
   236  }
   237  
   238  type osArch struct {
   239  	Os   string
   240  	Arch string
   241  }
   242  
   243  type ImageIdentifier struct {
   244  	Repo     string  `json:"repo"`
   245  	Tag      string  `json:"tag"`
   246  	Digest   string  `json:"digest"`
   247  	Platform *osArch `json:"platform"`
   248  }
   249  
   250  func getImageIdentifier(repoTagStr, platformStr string) ImageIdentifier {
   251  	var tag, digest string
   252  
   253  	repo, ref, isTag, err := zcommon.GetRepoReference(repoTagStr)
   254  	if err != nil {
   255  		return ImageIdentifier{}
   256  	}
   257  
   258  	if isTag {
   259  		tag = ref
   260  	} else {
   261  		digest = ref
   262  	}
   263  
   264  	// check if the following input is a repo:tag or repo@digest, if not then it's a platform
   265  	var platform *osArch
   266  
   267  	if platformStr != "" {
   268  		os, arch, _ := strings.Cut(platformStr, "/")
   269  		platform = &osArch{Os: os, Arch: arch}
   270  	}
   271  
   272  	return ImageIdentifier{
   273  		Repo:     repo,
   274  		Tag:      tag,
   275  		Digest:   digest,
   276  		Platform: platform,
   277  	}
   278  }