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 }