mirror of
https://github.com/project-zot/zot.git
synced 2026-06-16 20:38:08 +08:00
feat(cve): cli cve diff (#2242)
* feat(gql): add new query for diff of cves for 2 images Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com> * feat(cli): add cli for cve diff Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com> --------- Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
This commit is contained in:
@@ -198,6 +198,276 @@ func TestNegativeServerResponse(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestCVEDiffList(t *testing.T) {
|
||||
port := test.GetFreePort()
|
||||
url := test.GetBaseURL(port)
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
|
||||
dir := t.TempDir()
|
||||
|
||||
conf.Storage.RootDirectory = dir
|
||||
trivyConfig := &extconf.TrivyConfig{
|
||||
DBRepository: "ghcr.io/project-zot/trivy-db",
|
||||
}
|
||||
cveConfig := &extconf.CVEConfig{
|
||||
UpdateInterval: 2,
|
||||
Trivy: trivyConfig,
|
||||
}
|
||||
defaultVal := true
|
||||
searchConfig := &extconf.SearchConfig{
|
||||
BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
|
||||
CVE: cveConfig,
|
||||
}
|
||||
conf.Extensions = &extconf.ExtensionConfig{
|
||||
Search: searchConfig,
|
||||
}
|
||||
|
||||
logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
logPath := logFile.Name()
|
||||
defer os.Remove(logPath)
|
||||
|
||||
writers := io.MultiWriter(os.Stdout, logFile)
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
ctlr.Log.Logger = ctlr.Log.Output(writers)
|
||||
|
||||
if err := ctlr.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
layer1 := []byte{10, 20, 30}
|
||||
layer2 := []byte{11, 21, 31}
|
||||
layer3 := []byte{12, 22, 23}
|
||||
|
||||
otherImage := CreateImageWith().LayerBlobs([][]byte{
|
||||
layer1,
|
||||
}).DefaultConfig().Build()
|
||||
|
||||
baseImage := CreateImageWith().LayerBlobs([][]byte{
|
||||
layer1,
|
||||
layer2,
|
||||
}).PlatformConfig("testArch", "testOs").Build()
|
||||
|
||||
image := CreateImageWith().LayerBlobs([][]byte{
|
||||
layer1,
|
||||
layer2,
|
||||
layer3,
|
||||
}).PlatformConfig("testArch", "testOs").Build()
|
||||
|
||||
multiArchBase := CreateMultiarchWith().Images([]Image{baseImage, CreateRandomImage(), CreateRandomImage()}).
|
||||
Build()
|
||||
multiArchImage := CreateMultiarchWith().Images([]Image{image, CreateRandomImage(), CreateRandomImage()}).
|
||||
Build()
|
||||
|
||||
getCveResults := func(digestStr string) map[string]cvemodel.CVE {
|
||||
switch digestStr {
|
||||
case image.DigestStr():
|
||||
return map[string]cvemodel.CVE{
|
||||
"CVE1": {
|
||||
ID: "CVE1",
|
||||
Severity: "HIGH",
|
||||
Title: "Title CVE1",
|
||||
Description: "Description CVE1",
|
||||
PackageList: []cvemodel.Package{{}},
|
||||
},
|
||||
"CVE2": {
|
||||
ID: "CVE2",
|
||||
Severity: "MEDIUM",
|
||||
Title: "Title CVE2",
|
||||
Description: "Description CVE2",
|
||||
PackageList: []cvemodel.Package{{}},
|
||||
},
|
||||
"CVE3": {
|
||||
ID: "CVE3",
|
||||
Severity: "LOW",
|
||||
Title: "Title CVE3",
|
||||
Description: "Description CVE3",
|
||||
PackageList: []cvemodel.Package{{}},
|
||||
},
|
||||
}
|
||||
case baseImage.DigestStr():
|
||||
return map[string]cvemodel.CVE{
|
||||
"CVE1": {
|
||||
ID: "CVE1",
|
||||
Severity: "HIGH",
|
||||
Title: "Title CVE1",
|
||||
Description: "Description CVE1",
|
||||
PackageList: []cvemodel.Package{{}},
|
||||
},
|
||||
"CVE2": {
|
||||
ID: "CVE2",
|
||||
Severity: "MEDIUM",
|
||||
Title: "Title CVE2",
|
||||
Description: "Description CVE2",
|
||||
PackageList: []cvemodel.Package{{}},
|
||||
},
|
||||
}
|
||||
case otherImage.DigestStr():
|
||||
return map[string]cvemodel.CVE{
|
||||
"CVE1": {
|
||||
ID: "CVE1",
|
||||
Severity: "HIGH",
|
||||
Title: "Title CVE1",
|
||||
Description: "Description CVE1",
|
||||
PackageList: []cvemodel.Package{{}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// By default the image has no vulnerabilities
|
||||
return map[string]cvemodel.CVE{}
|
||||
}
|
||||
|
||||
// MetaDB loaded with initial data, now mock the scanner
|
||||
// Setup test CVE data in mock scanner
|
||||
scanner := mocks.CveScannerMock{
|
||||
ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) {
|
||||
repo, ref, _, _ := zcommon.GetRepoReference(image)
|
||||
|
||||
if zcommon.IsDigest(ref) {
|
||||
return getCveResults(ref), nil
|
||||
}
|
||||
|
||||
repoMeta, _ := ctlr.MetaDB.GetRepoMeta(ctx, repo)
|
||||
|
||||
if _, ok := repoMeta.Tags[ref]; !ok {
|
||||
panic("unexpected tag '" + ref + "', test might be wrong")
|
||||
}
|
||||
|
||||
return getCveResults(repoMeta.Tags[ref].Digest), nil
|
||||
},
|
||||
GetCachedResultFn: func(digestStr string) map[string]cvemodel.CVE {
|
||||
return getCveResults(digestStr)
|
||||
},
|
||||
IsResultCachedFn: func(digestStr string) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
ctlr.CveScanner = scanner
|
||||
|
||||
go func() {
|
||||
if err := ctlr.Run(); !errors.Is(err, http.ErrServerClosed) {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
defer ctlr.Shutdown()
|
||||
|
||||
test.WaitTillServerReady(url)
|
||||
|
||||
ctx := context.Background()
|
||||
_, err = ociutils.InitializeTestMetaDB(ctx, ctlr.MetaDB,
|
||||
ociutils.Repo{
|
||||
Name: "repo",
|
||||
Images: []ociutils.RepoImage{
|
||||
{Image: otherImage, Reference: "other-image"},
|
||||
{Image: baseImage, Reference: "base-image"},
|
||||
{Image: image, Reference: "image"},
|
||||
},
|
||||
},
|
||||
ociutils.Repo{
|
||||
Name: "repo-multi",
|
||||
MultiArchImages: []ociutils.RepoMultiArchImage{
|
||||
{MultiarchImage: CreateRandomMultiarch(), Reference: "multi-rand"},
|
||||
{MultiarchImage: multiArchBase, Reference: "multi-base"},
|
||||
{MultiarchImage: multiArchImage, Reference: "multi-img"},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
Convey("Test CVE by image name - GQL - positive", t, func() {
|
||||
args := []string{"diff", "repo:image", "repo:base-image", "--config", "cvetest"}
|
||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
|
||||
defer os.Remove(configPath)
|
||||
cveCmd := client.NewCVECommand(client.NewSearchService())
|
||||
buff := bytes.NewBufferString("")
|
||||
cveCmd.SetOut(buff)
|
||||
cveCmd.SetErr(buff)
|
||||
cveCmd.SetArgs(args)
|
||||
err = cveCmd.Execute()
|
||||
fmt.Println(buff.String())
|
||||
space := regexp.MustCompile(`\s+`)
|
||||
str := space.ReplaceAllString(buff.String(), " ")
|
||||
str = strings.TrimSpace(str)
|
||||
So(str, ShouldContainSubstring, "CVE3")
|
||||
So(str, ShouldNotContainSubstring, "CVE1")
|
||||
So(str, ShouldNotContainSubstring, "CVE2")
|
||||
})
|
||||
|
||||
Convey("Errors", t, func() {
|
||||
// args := []string{"diff", "repo:image", "repo:base-image", "--config", "cvetest"}
|
||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
|
||||
defer os.Remove(configPath)
|
||||
cveCmd := client.NewCVECommand(client.NewSearchService())
|
||||
|
||||
Convey("Set wrong number of params", func() {
|
||||
args := []string{"diff", "repo:image", "--config", "cvetest"}
|
||||
cveCmd.SetArgs(args)
|
||||
So(cveCmd.Execute(), ShouldNotBeNil)
|
||||
})
|
||||
Convey("First input is not a repo:tag", func() {
|
||||
args := []string{"diff", "bad-input", "repo:base-image", "--config", "cvetest"}
|
||||
cveCmd.SetArgs(args)
|
||||
So(cveCmd.Execute(), ShouldNotBeNil)
|
||||
})
|
||||
Convey("Second input is arch but not enough args", func() {
|
||||
args := []string{"diff", "repo:base-image", "linux/amd64", "--config", "cvetest"}
|
||||
cveCmd.SetArgs(args)
|
||||
So(cveCmd.Execute(), ShouldNotBeNil)
|
||||
})
|
||||
Convey("Second input is arch 3rd is repo:tag", func() {
|
||||
args := []string{"diff", "repo:base-image", "linux/amd64", "repo:base-image", "--config", "cvetest"}
|
||||
cveCmd.SetArgs(args)
|
||||
So(cveCmd.Execute(), ShouldBeNil)
|
||||
})
|
||||
Convey("Second input is repo:tag 3rd is repo:tag", func() {
|
||||
args := []string{"diff", "repo:base-image", "repo:base-image", "repo:base-image", "--config", "cvetest"}
|
||||
cveCmd.SetArgs(args)
|
||||
So(cveCmd.Execute(), ShouldNotBeNil)
|
||||
})
|
||||
Convey("Second input is arch 3rd is arch as well", func() {
|
||||
args := []string{"diff", "repo:base-image", "linux/amd64", "linux/amd64", "--config", "cvetest"}
|
||||
cveCmd.SetArgs(args)
|
||||
So(cveCmd.Execute(), ShouldNotBeNil)
|
||||
})
|
||||
Convey("Second input is repo:tag 3rd is arch", func() {
|
||||
args := []string{"diff", "repo:base-image", "repo:base-image", "linux/amd64", "--config", "cvetest"}
|
||||
cveCmd.SetArgs(args)
|
||||
So(cveCmd.Execute(), ShouldBeNil)
|
||||
})
|
||||
Convey("Second input is repo:tag 3rd is arch, 4th is repo:tag", func() {
|
||||
args := []string{
|
||||
"diff", "repo:base-image", "repo:base-image", "linux/amd64", "repo:base-image",
|
||||
"--config", "cvetest",
|
||||
}
|
||||
|
||||
cveCmd.SetArgs(args)
|
||||
So(cveCmd.Execute(), ShouldNotBeNil)
|
||||
})
|
||||
Convey("Second input is arch 3rd is repo:tag, 4th is arch", func() {
|
||||
args := []string{"diff", "repo:base-image", "linux/amd64", "repo:base-image", "linux/amd64", "--config", "cvetest"}
|
||||
cveCmd.SetArgs(args)
|
||||
So(cveCmd.Execute(), ShouldBeNil)
|
||||
})
|
||||
Convey("input is with digest ref", func() {
|
||||
args := []string{"diff", "repo@sha256:123123", "--config", "cvetest"}
|
||||
cveCmd.SetArgs(args)
|
||||
So(cveCmd.Execute(), ShouldNotBeNil)
|
||||
})
|
||||
Convey("input is with just repo no ref", func() {
|
||||
args := []string{"diff", "repo", "--config", "cvetest"}
|
||||
cveCmd.SetArgs(args)
|
||||
So(cveCmd.Execute(), ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
//nolint:dupl
|
||||
func TestServerCVEResponse(t *testing.T) {
|
||||
port := test.GetFreePort()
|
||||
|
||||
@@ -30,6 +30,7 @@ func NewCVECommand(searchService SearchService) *cobra.Command {
|
||||
cvesCmd.AddCommand(NewCveForImageCommand(searchService))
|
||||
cvesCmd.AddCommand(NewImagesByCVEIDCommand(searchService))
|
||||
cvesCmd.AddCommand(NewFixedTagsCommand(searchService))
|
||||
cvesCmd.AddCommand(NewCVEDiffCommand(searchService))
|
||||
|
||||
return cvesCmd
|
||||
}
|
||||
|
||||
@@ -140,3 +140,139 @@ func NewFixedTagsCommand(searchService SearchService) *cobra.Command {
|
||||
|
||||
return fixedTagsCmd
|
||||
}
|
||||
|
||||
func NewCVEDiffCommand(searchService SearchService) *cobra.Command {
|
||||
var (
|
||||
minuendStr, minuendArch string
|
||||
subtrahendStr, subtrahendArch string
|
||||
)
|
||||
imagesByCVEIDCmd := &cobra.Command{
|
||||
Use: "diff [minuend] ([minuend-platform]) [subtrahend] ([subtrahend-platform])",
|
||||
Short: "List the CVE's present in minuend that are not present in subtrahend",
|
||||
Long: `List the CVE's present in minuend that are not present in subtrahend`,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
const (
|
||||
twoArgs = 2
|
||||
threeArgs = 3
|
||||
fourArgs = 4
|
||||
)
|
||||
|
||||
if err := cobra.RangeArgs(twoArgs, fourArgs)(cmd, args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !isRepoTag(args[0]) {
|
||||
return fmt.Errorf("%w: first parameter should be a repo:tag", zerr.ErrInvalidArgs)
|
||||
}
|
||||
|
||||
minuendStr = args[0]
|
||||
|
||||
if isRepoTag(args[1]) {
|
||||
subtrahendStr = args[1]
|
||||
} else {
|
||||
minuendArch = args[1]
|
||||
|
||||
if len(args) == twoArgs {
|
||||
return fmt.Errorf("%w: not enough arguments, specified only 1 image with arch", zerr.ErrInvalidArgs)
|
||||
}
|
||||
}
|
||||
|
||||
if len(args) == twoArgs {
|
||||
return nil
|
||||
}
|
||||
|
||||
if isRepoTag(args[2]) {
|
||||
if subtrahendStr == "" {
|
||||
subtrahendStr = args[2]
|
||||
} else {
|
||||
return fmt.Errorf("%w: too many repo:tag inputs", zerr.ErrInvalidArgs)
|
||||
}
|
||||
} else {
|
||||
if subtrahendStr == "" {
|
||||
return fmt.Errorf("%w: 3rd argument should be a repo:tag", zerr.ErrInvalidArgs)
|
||||
} else {
|
||||
subtrahendArch = args[2]
|
||||
}
|
||||
}
|
||||
|
||||
if len(args) == threeArgs {
|
||||
return nil
|
||||
}
|
||||
|
||||
if isRepoTag(args[3]) {
|
||||
return fmt.Errorf("%w: 4th argument should not be a repo:tag but an arch", zerr.ErrInvalidArgs)
|
||||
} else {
|
||||
subtrahendArch = args[3]
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
searchConfig, err := GetSearchConfigFromFlags(cmd, searchService)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = CheckExtEndPointQuery(searchConfig, CVEDiffListForImagesQuery())
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: '%s'", err, CVEDiffListForImagesQuery().Name)
|
||||
}
|
||||
|
||||
// parse the args and determine the input
|
||||
minuend := getImageIdentifier(minuendStr, minuendArch)
|
||||
subtrahend := getImageIdentifier(subtrahendStr, subtrahendArch)
|
||||
|
||||
return SearchCVEDiffList(searchConfig, minuend, subtrahend)
|
||||
},
|
||||
}
|
||||
|
||||
return imagesByCVEIDCmd
|
||||
}
|
||||
|
||||
func isRepoTag(arg string) bool {
|
||||
_, _, _, err := zcommon.GetRepoReference(arg) //nolint:dogsled
|
||||
|
||||
return err == nil
|
||||
}
|
||||
|
||||
type osArch struct {
|
||||
Os string
|
||||
Arch string
|
||||
}
|
||||
|
||||
type ImageIdentifier struct {
|
||||
Repo string `json:"repo"`
|
||||
Tag string `json:"tag"`
|
||||
Digest string `json:"digest"`
|
||||
Platform *osArch `json:"platform"`
|
||||
}
|
||||
|
||||
func getImageIdentifier(repoTagStr, platformStr string) ImageIdentifier {
|
||||
var tag, digest string
|
||||
|
||||
repo, ref, isTag, err := zcommon.GetRepoReference(repoTagStr)
|
||||
if err != nil {
|
||||
return ImageIdentifier{}
|
||||
}
|
||||
|
||||
if isTag {
|
||||
tag = ref
|
||||
} else {
|
||||
digest = ref
|
||||
}
|
||||
|
||||
// check if the following input is a repo:tag or repo@digest, if not then it's a platform
|
||||
var platform *osArch
|
||||
|
||||
if platformStr != "" {
|
||||
os, arch, _ := strings.Cut(platformStr, "/")
|
||||
platform = &osArch{Os: os, Arch: arch}
|
||||
}
|
||||
|
||||
return ImageIdentifier{
|
||||
Repo: repo,
|
||||
Tag: tag,
|
||||
Digest: digest,
|
||||
Platform: platform,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ const (
|
||||
DebugFlag = "debug"
|
||||
SearchedCVEID = "cve-id"
|
||||
SortByFlag = "sort-by"
|
||||
PlatformFlag = "platform"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -25,6 +25,12 @@ func CVEResultForImage() GQLType {
|
||||
}
|
||||
}
|
||||
|
||||
func CVEDiffResult() GQLType {
|
||||
return GQLType{
|
||||
Name: "CVEDiffResult",
|
||||
}
|
||||
}
|
||||
|
||||
func PaginatedImagesResult() GQLType {
|
||||
return GQLType{
|
||||
Name: "PaginatedImagesResult",
|
||||
@@ -51,6 +57,14 @@ func ImageListQuery() GQLQuery {
|
||||
}
|
||||
}
|
||||
|
||||
func CVEDiffListForImagesQuery() GQLQuery {
|
||||
return GQLQuery{
|
||||
Name: "CVEDiffListForImages",
|
||||
Args: []string{"minuend", "subtrahend", "requestedPage", "searchedCVE", "excludedCVE"},
|
||||
ReturnType: CVEDiffResult(),
|
||||
}
|
||||
}
|
||||
|
||||
func ImageListForDigestQuery() GQLQuery {
|
||||
return GQLQuery{
|
||||
Name: "ImageListForDigest",
|
||||
|
||||
@@ -1077,6 +1077,20 @@ type mockService struct {
|
||||
getFixedTagsForCVEGQLFn func(ctx context.Context, config SearchConfig, username, password,
|
||||
imageName, cveID string,
|
||||
) (*common.ImageListWithCVEFixedResponse, error)
|
||||
|
||||
getCVEDiffListGQLFn func(ctx context.Context, config SearchConfig, username, password string,
|
||||
minuend, subtrahend ImageIdentifier,
|
||||
) (*cveDiffListResp, error)
|
||||
}
|
||||
|
||||
func (service mockService) getCVEDiffListGQL(ctx context.Context, config SearchConfig, username, password string,
|
||||
minuend, subtrahend ImageIdentifier,
|
||||
) (*cveDiffListResp, error) {
|
||||
if service.getCVEDiffListGQLFn != nil {
|
||||
return service.getCVEDiffListGQLFn(ctx, config, username, password, minuend, subtrahend)
|
||||
}
|
||||
|
||||
return &cveDiffListResp{}, nil
|
||||
}
|
||||
|
||||
func (service mockService) getRepos(ctx context.Context, config SearchConfig, username,
|
||||
|
||||
@@ -267,6 +267,52 @@ func SearchCVEForImageGQL(config SearchConfig, image, searchedCveID string) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
func SearchCVEDiffList(config SearchConfig, minuend, subtrahend ImageIdentifier) error {
|
||||
username, password := getUsernameAndPassword(config.User)
|
||||
|
||||
response, err := config.SearchService.getCVEDiffListGQL(context.Background(), config, username, password,
|
||||
minuend, subtrahend)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cveDiffResult := response.Data.CveDiffResult
|
||||
|
||||
result := cveResult{
|
||||
Data: cveData{
|
||||
CVEListForImage: cveListForImage{
|
||||
Tag: cveDiffResult.Minuend.Tag,
|
||||
CVEList: cveDiffResult.CVEList,
|
||||
Summary: cveDiffResult.Summary,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var builder strings.Builder
|
||||
|
||||
if config.OutputFormat == defaultOutputFormat || config.OutputFormat == "" {
|
||||
imageCVESummary := result.Data.CVEListForImage.Summary
|
||||
|
||||
statsStr := fmt.Sprintf("CRITICAL %d, HIGH %d, MEDIUM %d, LOW %d, UNKNOWN %d, TOTAL %d\n\n",
|
||||
imageCVESummary.CriticalCount, imageCVESummary.HighCount, imageCVESummary.MediumCount,
|
||||
imageCVESummary.LowCount, imageCVESummary.UnknownCount, imageCVESummary.Count)
|
||||
|
||||
fmt.Fprint(config.ResultWriter, statsStr)
|
||||
|
||||
printCVETableHeader(&builder)
|
||||
fmt.Fprint(config.ResultWriter, builder.String())
|
||||
}
|
||||
|
||||
out, err := result.string(config.OutputFormat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprint(config.ResultWriter, out)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SearchImagesByCVEIDGQL(config SearchConfig, repo, cveid string) error {
|
||||
username, password := getUsernameAndPassword(config.User)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
@@ -48,6 +48,9 @@ type SearchService interface { //nolint:interfacebloat
|
||||
baseImage string) (*common.BaseImageListResponse, error)
|
||||
getReferrersGQL(ctx context.Context, config SearchConfig, username, password string,
|
||||
repo, digest string) (*common.ReferrersResp, error)
|
||||
getCVEDiffListGQL(ctx context.Context, config SearchConfig, username, password string,
|
||||
minuend, subtrahend ImageIdentifier,
|
||||
) (*cveDiffListResp, error)
|
||||
globalSearchGQL(ctx context.Context, config SearchConfig, username, password string,
|
||||
query string) (*common.GlobalSearch, error)
|
||||
|
||||
@@ -146,6 +149,46 @@ func (service searchService) getReferrersGQL(ctx context.Context, config SearchC
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (service searchService) getCVEDiffListGQL(ctx context.Context, config SearchConfig, username, password string,
|
||||
minuend, subtrahend ImageIdentifier,
|
||||
) (*cveDiffListResp, error) {
|
||||
minuendInput := getImageInput(minuend)
|
||||
subtrahendInput := getImageInput(subtrahend)
|
||||
query := fmt.Sprintf(`
|
||||
{
|
||||
CVEDiffListForImages( minuend: %s, subtrahend: %s ) {
|
||||
Minuend {Repo Tag}
|
||||
Subtrahend {Repo Tag}
|
||||
CVEList {
|
||||
Id Title Description Severity Reference
|
||||
PackageList {Name InstalledVersion FixedVersion}
|
||||
}
|
||||
Summary {
|
||||
Count UnknownCount LowCount MediumCount HighCount CriticalCount
|
||||
}
|
||||
Page {TotalCount ItemCount}
|
||||
}
|
||||
}`, minuendInput, subtrahendInput)
|
||||
|
||||
result := &cveDiffListResp{}
|
||||
|
||||
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
||||
if errResult := checkResultGraphQLQuery(ctx, err, result.Errors); errResult != nil {
|
||||
return nil, errResult
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func getImageInput(img ImageIdentifier) string {
|
||||
platform := ""
|
||||
if img.Platform != nil {
|
||||
platform = fmt.Sprintf(`, Platform: {Os: "%s", Arch: "%s"}`, img.Platform.Os, img.Platform.Arch)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`{Repo: "%s", Tag: "%s", Digest: "%s"%s}`, img.Repo, img.Tag, img.Digest, platform)
|
||||
}
|
||||
|
||||
func (service searchService) globalSearchGQL(ctx context.Context, config SearchConfig, username, password string,
|
||||
query string,
|
||||
) (*common.GlobalSearch, error) {
|
||||
@@ -746,6 +789,22 @@ type cve struct {
|
||||
PackageList []packageList `json:"PackageList"`
|
||||
}
|
||||
|
||||
type cveDiffListResp struct {
|
||||
Data cveDiffResultsForImages `json:"data"`
|
||||
Errors []common.ErrorGQL `json:"errors"`
|
||||
}
|
||||
|
||||
type cveDiffResultsForImages struct {
|
||||
CveDiffResult cveDiffResult `json:"cveDiffListForImages"`
|
||||
}
|
||||
|
||||
type cveDiffResult struct {
|
||||
Minuend ImageIdentifier `json:"minuend"`
|
||||
Subtrahend ImageIdentifier `json:"subtrahend"`
|
||||
CVEList []cve `json:"cveList"`
|
||||
Summary common.ImageVulnerabilitySummary `json:"summary"`
|
||||
}
|
||||
|
||||
//nolint:tagliatelle // graphQL schema
|
||||
type cveListForImage struct {
|
||||
Tag string `json:"Tag"`
|
||||
@@ -755,7 +814,7 @@ type cveListForImage struct {
|
||||
|
||||
//nolint:tagliatelle // graphQL schema
|
||||
type cveData struct {
|
||||
CVEListForImage cveListForImage `json:"CVEListForImage"`
|
||||
CVEListForImage cveListForImage `json:"cveListForImage"`
|
||||
}
|
||||
|
||||
func (cve cveResult) string(format string) (string, error) {
|
||||
|
||||
Reference in New Issue
Block a user