mirror of
https://github.com/project-zot/zot.git
synced 2024-12-30 22:34:13 -05: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:
parent
752b9e87c1
commit
5039128723
20 changed files with 2555 additions and 11 deletions
|
@ -168,4 +168,6 @@ var (
|
||||||
ErrAPINotSupported = errors.New("registry at the given address doesn't implement the correct API")
|
ErrAPINotSupported = errors.New("registry at the given address doesn't implement the correct API")
|
||||||
ErrURLNotFound = errors.New("url not found")
|
ErrURLNotFound = errors.New("url not found")
|
||||||
ErrInvalidSearchQuery = errors.New("invalid search query")
|
ErrInvalidSearchQuery = errors.New("invalid search query")
|
||||||
|
ErrImageNotFound = errors.New("image not found")
|
||||||
|
ErrAmbiguousInput = errors.New("input is not specific enough")
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
//nolint:dupl
|
||||||
func TestServerCVEResponse(t *testing.T) {
|
func TestServerCVEResponse(t *testing.T) {
|
||||||
port := test.GetFreePort()
|
port := test.GetFreePort()
|
||||||
|
|
|
@ -30,6 +30,7 @@ func NewCVECommand(searchService SearchService) *cobra.Command {
|
||||||
cvesCmd.AddCommand(NewCveForImageCommand(searchService))
|
cvesCmd.AddCommand(NewCveForImageCommand(searchService))
|
||||||
cvesCmd.AddCommand(NewImagesByCVEIDCommand(searchService))
|
cvesCmd.AddCommand(NewImagesByCVEIDCommand(searchService))
|
||||||
cvesCmd.AddCommand(NewFixedTagsCommand(searchService))
|
cvesCmd.AddCommand(NewFixedTagsCommand(searchService))
|
||||||
|
cvesCmd.AddCommand(NewCVEDiffCommand(searchService))
|
||||||
|
|
||||||
return cvesCmd
|
return cvesCmd
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,3 +140,139 @@ func NewFixedTagsCommand(searchService SearchService) *cobra.Command {
|
||||||
|
|
||||||
return fixedTagsCmd
|
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"
|
DebugFlag = "debug"
|
||||||
SearchedCVEID = "cve-id"
|
SearchedCVEID = "cve-id"
|
||||||
SortByFlag = "sort-by"
|
SortByFlag = "sort-by"
|
||||||
|
PlatformFlag = "platform"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -25,6 +25,12 @@ func CVEResultForImage() GQLType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CVEDiffResult() GQLType {
|
||||||
|
return GQLType{
|
||||||
|
Name: "CVEDiffResult",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func PaginatedImagesResult() GQLType {
|
func PaginatedImagesResult() GQLType {
|
||||||
return GQLType{
|
return GQLType{
|
||||||
Name: "PaginatedImagesResult",
|
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 {
|
func ImageListForDigestQuery() GQLQuery {
|
||||||
return GQLQuery{
|
return GQLQuery{
|
||||||
Name: "ImageListForDigest",
|
Name: "ImageListForDigest",
|
||||||
|
|
|
@ -1077,6 +1077,20 @@ type mockService struct {
|
||||||
getFixedTagsForCVEGQLFn func(ctx context.Context, config SearchConfig, username, password,
|
getFixedTagsForCVEGQLFn func(ctx context.Context, config SearchConfig, username, password,
|
||||||
imageName, cveID string,
|
imageName, cveID string,
|
||||||
) (*common.ImageListWithCVEFixedResponse, error)
|
) (*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,
|
func (service mockService) getRepos(ctx context.Context, config SearchConfig, username,
|
||||||
|
|
|
@ -267,6 +267,52 @@ func SearchCVEForImageGQL(config SearchConfig, image, searchedCveID string) erro
|
||||||
return nil
|
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 {
|
func SearchImagesByCVEIDGQL(config SearchConfig, repo, cveid string) error {
|
||||||
username, password := getUsernameAndPassword(config.User)
|
username, password := getUsernameAndPassword(config.User)
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
|
@ -48,6 +48,9 @@ type SearchService interface { //nolint:interfacebloat
|
||||||
baseImage string) (*common.BaseImageListResponse, error)
|
baseImage string) (*common.BaseImageListResponse, error)
|
||||||
getReferrersGQL(ctx context.Context, config SearchConfig, username, password string,
|
getReferrersGQL(ctx context.Context, config SearchConfig, username, password string,
|
||||||
repo, digest string) (*common.ReferrersResp, error)
|
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,
|
globalSearchGQL(ctx context.Context, config SearchConfig, username, password string,
|
||||||
query string) (*common.GlobalSearch, error)
|
query string) (*common.GlobalSearch, error)
|
||||||
|
|
||||||
|
@ -146,6 +149,46 @@ func (service searchService) getReferrersGQL(ctx context.Context, config SearchC
|
||||||
return result, nil
|
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,
|
func (service searchService) globalSearchGQL(ctx context.Context, config SearchConfig, username, password string,
|
||||||
query string,
|
query string,
|
||||||
) (*common.GlobalSearch, error) {
|
) (*common.GlobalSearch, error) {
|
||||||
|
@ -746,6 +789,22 @@ type cve struct {
|
||||||
PackageList []packageList `json:"PackageList"`
|
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
|
//nolint:tagliatelle // graphQL schema
|
||||||
type cveListForImage struct {
|
type cveListForImage struct {
|
||||||
Tag string `json:"Tag"`
|
Tag string `json:"Tag"`
|
||||||
|
@ -755,7 +814,7 @@ type cveListForImage struct {
|
||||||
|
|
||||||
//nolint:tagliatelle // graphQL schema
|
//nolint:tagliatelle // graphQL schema
|
||||||
type cveData struct {
|
type cveData struct {
|
||||||
CVEListForImage cveListForImage `json:"CVEListForImage"`
|
CVEListForImage cveListForImage `json:"cveListForImage"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cve cveResult) string(format string) (string, error) {
|
func (cve cveResult) string(format string) (string, error) {
|
||||||
|
|
|
@ -112,6 +112,17 @@ type HistoryDescription struct {
|
||||||
EmptyLayer bool `json:"emptyLayer"`
|
EmptyLayer bool `json:"emptyLayer"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OsArch struct {
|
||||||
|
Os, Arch string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageIdentifier struct {
|
||||||
|
Repo string
|
||||||
|
Tag string
|
||||||
|
Digest string
|
||||||
|
Platform OsArch
|
||||||
|
}
|
||||||
|
|
||||||
type Referrer struct {
|
type Referrer struct {
|
||||||
MediaType string `json:"mediatype"`
|
MediaType string `json:"mediatype"`
|
||||||
ArtifactType string `json:"artifacttype"`
|
ArtifactType string `json:"artifacttype"`
|
||||||
|
|
|
@ -21,8 +21,10 @@ import (
|
||||||
type CveInfo interface {
|
type CveInfo interface {
|
||||||
GetImageListForCVE(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error)
|
GetImageListForCVE(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error)
|
||||||
GetImageListWithCVEFixed(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error)
|
GetImageListWithCVEFixed(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error)
|
||||||
GetCVEListForImage(ctx context.Context, repo, tag string, searchedCVE string, excludedCVE string, severity string,
|
GetCVEListForImage(ctx context.Context, repo, tag string, searchedCVE, excludedCVE string, severity string,
|
||||||
pageinput cvemodel.PageInput) ([]cvemodel.CVE, cvemodel.ImageCVESummary, zcommon.PageInfo, error)
|
pageinput cvemodel.PageInput) ([]cvemodel.CVE, cvemodel.ImageCVESummary, zcommon.PageInfo, error)
|
||||||
|
GetCVEDiffListForImages(ctx context.Context, minuend, subtrahend, searchedCVE, excludedCVE string,
|
||||||
|
pageInput cvemodel.PageInput) ([]cvemodel.CVE, cvemodel.ImageCVESummary, zcommon.PageInfo, error)
|
||||||
GetCVESummaryForImageMedia(ctx context.Context, repo, digestStr, mediaType string) (cvemodel.ImageCVESummary, error)
|
GetCVESummaryForImageMedia(ctx context.Context, repo, digestStr, mediaType string) (cvemodel.ImageCVESummary, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,8 +331,8 @@ func getConfigAndDigest(metaDB mTypes.MetaDB, manifestDigestStr string) (ispec.I
|
||||||
return manifestData.Manifests[0].Config, manifestDigest, err
|
return manifestData.Manifests[0].Config, manifestDigest, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterCVEList(
|
func filterCVEMap(cveMap map[string]cvemodel.CVE, searchedCVE, excludedCVE, severity string,
|
||||||
cveMap map[string]cvemodel.CVE, searchedCVE, excludedCVE, severity string, pageFinder *CvePageFinder,
|
pageFinder *CvePageFinder,
|
||||||
) {
|
) {
|
||||||
searchedCVE = strings.ToUpper(searchedCVE)
|
searchedCVE = strings.ToUpper(searchedCVE)
|
||||||
|
|
||||||
|
@ -349,8 +351,26 @@ func filterCVEList(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filterCVEList(cveList []cvemodel.CVE, searchedCVE, excludedCVE, severity string, pageFinder *CvePageFinder) {
|
||||||
|
searchedCVE = strings.ToUpper(searchedCVE)
|
||||||
|
|
||||||
|
for _, cve := range cveList {
|
||||||
|
if severity != "" && (cvemodel.CompareSeverities(cve.Severity, severity) != 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if excludedCVE != "" && cve.ContainsStr(excludedCVE) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if cve.ContainsStr(searchedCVE) {
|
||||||
|
pageFinder.Add(cve)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (cveinfo BaseCveInfo) GetCVEListForImage(ctx context.Context, repo, ref string, searchedCVE string,
|
func (cveinfo BaseCveInfo) GetCVEListForImage(ctx context.Context, repo, ref string, searchedCVE string,
|
||||||
excludedCVE string, severity string, pageInput cvemodel.PageInput,
|
excludedCVE, severity string, pageInput cvemodel.PageInput,
|
||||||
) (
|
) (
|
||||||
[]cvemodel.CVE, cvemodel.ImageCVESummary, zcommon.PageInfo, error,
|
[]cvemodel.CVE, cvemodel.ImageCVESummary, zcommon.PageInfo, error,
|
||||||
) {
|
) {
|
||||||
|
@ -379,13 +399,98 @@ func (cveinfo BaseCveInfo) GetCVEListForImage(ctx context.Context, repo, ref str
|
||||||
return []cvemodel.CVE{}, imageCVESummary, zcommon.PageInfo{}, err
|
return []cvemodel.CVE{}, imageCVESummary, zcommon.PageInfo{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
filterCVEList(cveMap, searchedCVE, excludedCVE, severity, pageFinder)
|
filterCVEMap(cveMap, searchedCVE, excludedCVE, severity, pageFinder)
|
||||||
|
|
||||||
cveList, pageInfo := pageFinder.Page()
|
cveList, pageInfo := pageFinder.Page()
|
||||||
|
|
||||||
return cveList, imageCVESummary, pageInfo, nil
|
return cveList, imageCVESummary, pageInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cveinfo BaseCveInfo) GetCVEDiffListForImages(ctx context.Context, minuend, subtrahend, searchedCVE string,
|
||||||
|
excludedCVE string, pageInput cvemodel.PageInput,
|
||||||
|
) ([]cvemodel.CVE, cvemodel.ImageCVESummary, zcommon.PageInfo, error) {
|
||||||
|
minuendRepo, minuendRef, _ := zcommon.GetImageDirAndReference(minuend)
|
||||||
|
subtrahendRepo, subtrahendRef, _ := zcommon.GetImageDirAndReference(subtrahend)
|
||||||
|
|
||||||
|
// get the CVEs of image and comparedImage
|
||||||
|
minuendCVEList, _, _, err := cveinfo.GetCVEListForImage(ctx, minuendRepo, minuendRef, searchedCVE, excludedCVE,
|
||||||
|
"", cvemodel.PageInput{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, cvemodel.ImageCVESummary{}, zcommon.PageInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
subtrahendCVEList, _, _, err := cveinfo.GetCVEListForImage(ctx, subtrahendRepo, subtrahendRef,
|
||||||
|
searchedCVE, excludedCVE, "", cvemodel.PageInput{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, cvemodel.ImageCVESummary{}, zcommon.PageInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
subtrahendCVEMap := map[string]cvemodel.CVE{}
|
||||||
|
|
||||||
|
for _, cve := range subtrahendCVEList {
|
||||||
|
cve := cve
|
||||||
|
subtrahendCVEMap[cve.ID] = cve
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
count int
|
||||||
|
unknownCount int
|
||||||
|
lowCount int
|
||||||
|
mediumCount int
|
||||||
|
highCount int
|
||||||
|
criticalCount int
|
||||||
|
maxSeverity string
|
||||||
|
|
||||||
|
diffCVEs = []cvemodel.CVE{}
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := range minuendCVEList {
|
||||||
|
if _, ok := subtrahendCVEMap[minuendCVEList[i].ID]; !ok {
|
||||||
|
diffCVEs = append(diffCVEs, minuendCVEList[i])
|
||||||
|
|
||||||
|
switch minuendCVEList[i].Severity {
|
||||||
|
case cvemodel.SeverityUnknown:
|
||||||
|
unknownCount++
|
||||||
|
case cvemodel.SeverityLow:
|
||||||
|
lowCount++
|
||||||
|
case cvemodel.SeverityMedium:
|
||||||
|
mediumCount++
|
||||||
|
case cvemodel.SeverityHigh:
|
||||||
|
highCount++
|
||||||
|
case cvemodel.SeverityCritical:
|
||||||
|
criticalCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
if cvemodel.CompareSeverities(maxSeverity, minuendCVEList[i].Severity) > 0 {
|
||||||
|
maxSeverity = minuendCVEList[i].Severity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pageFinder, err := NewCvePageFinder(pageInput.Limit, pageInput.Offset, pageInput.SortBy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, cvemodel.ImageCVESummary{}, zcommon.PageInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
filterCVEList(diffCVEs, "", "", "", pageFinder)
|
||||||
|
|
||||||
|
cveList, pageInfo := pageFinder.Page()
|
||||||
|
|
||||||
|
count = unknownCount + lowCount + mediumCount + highCount + criticalCount
|
||||||
|
|
||||||
|
diffCVESummary := cvemodel.ImageCVESummary{
|
||||||
|
Count: count,
|
||||||
|
UnknownCount: unknownCount,
|
||||||
|
LowCount: lowCount,
|
||||||
|
MediumCount: mediumCount,
|
||||||
|
HighCount: highCount,
|
||||||
|
CriticalCount: criticalCount,
|
||||||
|
MaxSeverity: maxSeverity,
|
||||||
|
}
|
||||||
|
|
||||||
|
return cveList, diffCVESummary, pageInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (cveinfo BaseCveInfo) GetCVESummaryForImageMedia(ctx context.Context, repo, digestStr, mediaType string,
|
func (cveinfo BaseCveInfo) GetCVESummaryForImageMedia(ctx context.Context, repo, digestStr, mediaType string,
|
||||||
) (cvemodel.ImageCVESummary, error) {
|
) (cvemodel.ImageCVESummary, error) {
|
||||||
// There are several cases, expected returned values below:
|
// There are several cases, expected returned values below:
|
||||||
|
|
|
@ -1277,6 +1277,11 @@ func TestCVEStruct(t *testing.T) { //nolint:gocyclo
|
||||||
So(cveSummary.CriticalCount, ShouldEqual, 2)
|
So(cveSummary.CriticalCount, ShouldEqual, 2)
|
||||||
So(cveSummary.MaxSeverity, ShouldEqual, "CRITICAL")
|
So(cveSummary.MaxSeverity, ShouldEqual, "CRITICAL")
|
||||||
|
|
||||||
|
_, _, _, err = cveInfo.GetCVEDiffListForImages(ctx, "repo8:1.0.0", "repo1@"+image13Digest, "", "", pageInput)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
_, _, _, err = cveInfo.GetCVEDiffListForImages(ctx, "repo8:1.0.0", "repo1:0.1.0", "", "", pageInput)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
// Image is multiarch
|
// Image is multiarch
|
||||||
cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repoMultiarch, "tagIndex", "", "", "", pageInput)
|
cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repoMultiarch, "tagIndex", "", "", "", pageInput)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
@ -1625,6 +1630,35 @@ func TestCVEStruct(t *testing.T) { //nolint:gocyclo
|
||||||
|
|
||||||
_, err = cveInfo.GetImageListForCVE(ctx, repoMultiarch, "CVE1")
|
_, err = cveInfo.GetImageListForCVE(ctx, repoMultiarch, "CVE1")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
cveInfo = cveinfo.BaseCveInfo{Log: log, Scanner: mocks.CveScannerMock{
|
||||||
|
IsImageFormatScannableFn: func(repo, reference string) (bool, error) {
|
||||||
|
return true, nil
|
||||||
|
},
|
||||||
|
ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) {
|
||||||
|
return nil, zerr.ErrTypeAssertionFailed
|
||||||
|
},
|
||||||
|
}, MetaDB: metaDB}
|
||||||
|
_, _, _, err = cveInfo.GetCVEDiffListForImages(ctx, "repo8:1.0.0", "repo1:0.1.0", "", "", pageInput)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
try := 0
|
||||||
|
cveInfo = cveinfo.BaseCveInfo{Log: log, Scanner: mocks.CveScannerMock{
|
||||||
|
IsImageFormatScannableFn: func(repo, reference string) (bool, error) {
|
||||||
|
return true, nil
|
||||||
|
},
|
||||||
|
ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) {
|
||||||
|
if try == 1 {
|
||||||
|
return nil, zerr.ErrTypeAssertionFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
try++
|
||||||
|
|
||||||
|
return make(map[string]cvemodel.CVE), nil
|
||||||
|
},
|
||||||
|
}, MetaDB: metaDB}
|
||||||
|
_, _, _, err = cveInfo.GetCVEDiffListForImages(ctx, "repo8:1.0.0", "repo6:0.1.0", "", "", pageInput)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -35,6 +35,20 @@ type Cve struct {
|
||||||
PackageList []*PackageInfo `json:"PackageList,omitempty"`
|
PackageList []*PackageInfo `json:"PackageList,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Contains the diff results of subtracting Subtrahend's CVEs from Minuend's CVEs
|
||||||
|
type CVEDiffResult struct {
|
||||||
|
// Minuend is the image from which CVE's we subtract
|
||||||
|
Minuend *ImageIdentifier `json:"Minuend"`
|
||||||
|
// Subtrahend is the image which CVE's are subtracted
|
||||||
|
Subtrahend *ImageIdentifier `json:"Subtrahend"`
|
||||||
|
// List of CVE objects which are present in minuend but not in subtrahend
|
||||||
|
CVEList []*Cve `json:"CVEList,omitempty"`
|
||||||
|
// Summary of the findings for this image
|
||||||
|
Summary *ImageVulnerabilitySummary `json:"Summary,omitempty"`
|
||||||
|
// The CVE pagination information, see PageInfo object for more details
|
||||||
|
Page *PageInfo `json:"Page,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// Contains the tag of the image and a list of CVEs
|
// Contains the tag of the image and a list of CVEs
|
||||||
type CVEResultForImage struct {
|
type CVEResultForImage struct {
|
||||||
// Tag affected by the CVEs
|
// Tag affected by the CVEs
|
||||||
|
@ -92,6 +106,30 @@ type HistoryDescription struct {
|
||||||
EmptyLayer *bool `json:"EmptyLayer,omitempty"`
|
EmptyLayer *bool `json:"EmptyLayer,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ImageIdentifier
|
||||||
|
type ImageIdentifier struct {
|
||||||
|
// Repo name of the image
|
||||||
|
Repo string `json:"Repo"`
|
||||||
|
// The tag of the image
|
||||||
|
Tag string `json:"Tag"`
|
||||||
|
// The digest of the image
|
||||||
|
Digest *string `json:"Digest,omitempty"`
|
||||||
|
// The platform of the image
|
||||||
|
Platform *Platform `json:"Platform,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImageInput
|
||||||
|
type ImageInput struct {
|
||||||
|
// Repo name of the image
|
||||||
|
Repo string `json:"Repo"`
|
||||||
|
// The tag of the image
|
||||||
|
Tag string `json:"Tag"`
|
||||||
|
// The digest of the image
|
||||||
|
Digest *string `json:"Digest,omitempty"`
|
||||||
|
// The platform of the image
|
||||||
|
Platform *PlatformInput `json:"Platform,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// Details about a specific image, it is used by queries returning a list of images
|
// Details about a specific image, it is used by queries returning a list of images
|
||||||
// We define an image as a pairing or a repository and a tag belonging to that repository
|
// We define an image as a pairing or a repository and a tag belonging to that repository
|
||||||
type ImageSummary struct {
|
type ImageSummary struct {
|
||||||
|
@ -264,6 +302,14 @@ type Platform struct {
|
||||||
Arch *string `json:"Arch,omitempty"`
|
Arch *string `json:"Arch,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PlatformInput contains the Os and the Arch of the input image
|
||||||
|
type PlatformInput struct {
|
||||||
|
// The os of the image
|
||||||
|
Os *string `json:"Os,omitempty"`
|
||||||
|
// The arch of the image
|
||||||
|
Arch *string `json:"Arch,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// Queries supported by the zot server
|
// Queries supported by the zot server
|
||||||
type Query struct {
|
type Query struct {
|
||||||
}
|
}
|
||||||
|
|
|
@ -279,6 +279,260 @@ func getCVEListForImage(
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCVEDiffListForImages(
|
||||||
|
ctx context.Context, //nolint:unparam // may be used in the future to filter by permissions
|
||||||
|
minuend gql_generated.ImageInput,
|
||||||
|
subtrahend gql_generated.ImageInput,
|
||||||
|
metaDB mTypes.MetaDB,
|
||||||
|
cveInfo cveinfo.CveInfo,
|
||||||
|
requestedPage *gql_generated.PageInput,
|
||||||
|
searchedCVE string,
|
||||||
|
excludedCVE string,
|
||||||
|
log log.Logger, //nolint:unparam // may be used by devs for debugging
|
||||||
|
) (*gql_generated.CVEDiffResult, error) {
|
||||||
|
minuend, err := resolveImageData(ctx, minuend, metaDB)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resultMinuend := getImageIdentifier(minuend)
|
||||||
|
resultSubtrahend := gql_generated.ImageIdentifier{}
|
||||||
|
|
||||||
|
if subtrahend.Repo != "" {
|
||||||
|
subtrahend, err = resolveImageData(ctx, subtrahend, metaDB)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resultSubtrahend = getImageIdentifier(subtrahend)
|
||||||
|
} else {
|
||||||
|
// search for base images
|
||||||
|
// get minuend image meta
|
||||||
|
minuendSummary, err := metaDB.GetImageMeta(godigest.Digest(deref(minuend.Digest, "")))
|
||||||
|
if err != nil {
|
||||||
|
return &gql_generated.CVEDiffResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the base images for the minuend
|
||||||
|
minuendBaseImages, err := metaDB.FilterTags(ctx, mTypes.AcceptOnlyRepo(minuend.Repo),
|
||||||
|
filterBaseImagesForMeta(minuendSummary))
|
||||||
|
if err != nil {
|
||||||
|
return &gql_generated.CVEDiffResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the best base image as subtrahend
|
||||||
|
// get the one with most layers in common
|
||||||
|
imgLayers := map[string]struct{}{}
|
||||||
|
|
||||||
|
for _, layer := range minuendSummary.Manifests[0].Manifest.Layers {
|
||||||
|
imgLayers[layer.Digest.String()] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
bestMatchingScore := 0
|
||||||
|
|
||||||
|
for _, baseImage := range minuendBaseImages {
|
||||||
|
for _, baseManifest := range baseImage.Manifests {
|
||||||
|
currentMatchingScore := 0
|
||||||
|
|
||||||
|
for _, layer := range baseManifest.Manifest.Layers {
|
||||||
|
if _, ok := imgLayers[layer.Digest.String()]; ok {
|
||||||
|
currentMatchingScore++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentMatchingScore > bestMatchingScore {
|
||||||
|
bestMatchingScore = currentMatchingScore
|
||||||
|
|
||||||
|
resultSubtrahend = gql_generated.ImageIdentifier{
|
||||||
|
Repo: baseImage.Repo,
|
||||||
|
Tag: baseImage.Tag,
|
||||||
|
Digest: ref(baseImage.Manifests[0].Digest.String()),
|
||||||
|
Platform: &gql_generated.Platform{
|
||||||
|
Os: ref(baseImage.Manifests[0].Config.OS),
|
||||||
|
Arch: ref(getArch(baseImage.Manifests[0].Config.Platform)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
subtrahend.Repo = baseImage.Repo
|
||||||
|
subtrahend.Tag = baseImage.Tag
|
||||||
|
subtrahend.Digest = ref(baseImage.Manifests[0].Digest.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
minuendRepoRef := minuend.Repo + "@" + deref(minuend.Digest, "")
|
||||||
|
subtrahendRepoRef := subtrahend.Repo + "@" + deref(subtrahend.Digest, "")
|
||||||
|
page := dderef(requestedPage)
|
||||||
|
|
||||||
|
diffCVEs, diffSummary, _, err := cveInfo.GetCVEDiffListForImages(ctx, minuendRepoRef, subtrahendRepoRef, searchedCVE,
|
||||||
|
excludedCVE, cvemodel.PageInput{
|
||||||
|
Limit: deref(page.Limit, 0),
|
||||||
|
Offset: deref(page.Offset, 0),
|
||||||
|
SortBy: cvemodel.SortCriteria(deref(page.SortBy, gql_generated.SortCriteriaSeverity)),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cveids := []*gql_generated.Cve{}
|
||||||
|
|
||||||
|
for _, cveDetail := range diffCVEs {
|
||||||
|
vulID := cveDetail.ID
|
||||||
|
desc := cveDetail.Description
|
||||||
|
title := cveDetail.Title
|
||||||
|
severity := cveDetail.Severity
|
||||||
|
referenceURL := cveDetail.Reference
|
||||||
|
|
||||||
|
pkgList := make([]*gql_generated.PackageInfo, 0)
|
||||||
|
|
||||||
|
for _, pkg := range cveDetail.PackageList {
|
||||||
|
pkg := pkg
|
||||||
|
|
||||||
|
pkgList = append(pkgList,
|
||||||
|
&gql_generated.PackageInfo{
|
||||||
|
Name: &pkg.Name,
|
||||||
|
InstalledVersion: &pkg.InstalledVersion,
|
||||||
|
FixedVersion: &pkg.FixedVersion,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
cveids = append(cveids,
|
||||||
|
&gql_generated.Cve{
|
||||||
|
ID: &vulID,
|
||||||
|
Title: &title,
|
||||||
|
Description: &desc,
|
||||||
|
Severity: &severity,
|
||||||
|
Reference: &referenceURL,
|
||||||
|
PackageList: pkgList,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gql_generated.CVEDiffResult{
|
||||||
|
Minuend: &resultMinuend,
|
||||||
|
Subtrahend: &resultSubtrahend,
|
||||||
|
Summary: &gql_generated.ImageVulnerabilitySummary{
|
||||||
|
Count: &diffSummary.Count,
|
||||||
|
UnknownCount: &diffSummary.UnknownCount,
|
||||||
|
LowCount: &diffSummary.LowCount,
|
||||||
|
MediumCount: &diffSummary.MediumCount,
|
||||||
|
HighCount: &diffSummary.HighCount,
|
||||||
|
CriticalCount: &diffSummary.CriticalCount,
|
||||||
|
MaxSeverity: &diffSummary.MaxSeverity,
|
||||||
|
},
|
||||||
|
CVEList: cveids,
|
||||||
|
Page: &gql_generated.PageInfo{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getImageIdentifier(img gql_generated.ImageInput) gql_generated.ImageIdentifier {
|
||||||
|
return gql_generated.ImageIdentifier{
|
||||||
|
Repo: img.Repo,
|
||||||
|
Tag: img.Tag,
|
||||||
|
Digest: img.Digest,
|
||||||
|
Platform: getIdentifierPlatform(img.Platform),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIdentifierPlatform(platform *gql_generated.PlatformInput) *gql_generated.Platform {
|
||||||
|
if platform == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gql_generated.Platform{
|
||||||
|
Os: platform.Os,
|
||||||
|
Arch: platform.Arch,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rename idea: identify image from input.
|
||||||
|
func resolveImageData(ctx context.Context, imageInput gql_generated.ImageInput, metaDB mTypes.MetaDB,
|
||||||
|
) (gql_generated.ImageInput, error) {
|
||||||
|
if imageInput.Repo == "" {
|
||||||
|
return gql_generated.ImageInput{}, zerr.ErrEmptyRepoName
|
||||||
|
}
|
||||||
|
|
||||||
|
if imageInput.Tag == "" {
|
||||||
|
return gql_generated.ImageInput{}, zerr.ErrEmptyTag
|
||||||
|
}
|
||||||
|
|
||||||
|
// try checking if the tag is a simple image first
|
||||||
|
repoMeta, err := metaDB.GetRepoMeta(ctx, imageInput.Repo)
|
||||||
|
if err != nil {
|
||||||
|
return gql_generated.ImageInput{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
descriptor, ok := repoMeta.Tags[imageInput.Tag]
|
||||||
|
if !ok {
|
||||||
|
return gql_generated.ImageInput{}, zerr.ErrImageNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
switch descriptor.MediaType {
|
||||||
|
case ispec.MediaTypeImageManifest:
|
||||||
|
imageInput.Digest = ref(descriptor.Digest)
|
||||||
|
|
||||||
|
return imageInput, nil
|
||||||
|
case ispec.MediaTypeImageIndex:
|
||||||
|
if dderef(imageInput.Digest) == "" && !isPlatformSpecified(imageInput.Platform) {
|
||||||
|
return gql_generated.ImageInput{},
|
||||||
|
fmt.Errorf("%w: platform or specific manifest digest needed", zerr.ErrAmbiguousInput)
|
||||||
|
}
|
||||||
|
|
||||||
|
imageMeta, err := metaDB.GetImageMeta(godigest.Digest(descriptor.Digest))
|
||||||
|
if err != nil {
|
||||||
|
return gql_generated.ImageInput{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, manifest := range imageMeta.Manifests {
|
||||||
|
if manifest.Digest.String() == dderef(imageInput.Digest) ||
|
||||||
|
isMatchingPlatform(manifest.Config.Platform, dderef(imageInput.Platform)) {
|
||||||
|
imageInput.Digest = ref(manifest.Digest.String())
|
||||||
|
imageInput.Platform = &gql_generated.PlatformInput{
|
||||||
|
Os: ref(manifest.Config.OS),
|
||||||
|
Arch: ref(getArch(manifest.Config.Platform)),
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageInput, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageInput, zerr.ErrImageNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageInput, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPlatformSpecified(platformInput *gql_generated.PlatformInput) bool {
|
||||||
|
if platformInput == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if dderef(platformInput.Os) == "" || dderef(platformInput.Arch) == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func isMatchingPlatform(platform ispec.Platform, platformInput gql_generated.PlatformInput) bool {
|
||||||
|
if platform.OS != deref(platformInput.Os, "") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
arch := getArch(platform)
|
||||||
|
|
||||||
|
return arch == deref(platformInput.Arch, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getArch(platform ispec.Platform) string {
|
||||||
|
arch := platform.Architecture
|
||||||
|
if arch != "" && platform.Variant != "" {
|
||||||
|
arch += "/" + platform.Variant
|
||||||
|
}
|
||||||
|
|
||||||
|
return arch
|
||||||
|
}
|
||||||
|
|
||||||
func FilterByTagInfo(tagsInfo []cvemodel.TagInfo) mTypes.FilterFunc {
|
func FilterByTagInfo(tagsInfo []cvemodel.TagInfo) mTypes.FilterFunc {
|
||||||
return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool {
|
return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool {
|
||||||
manifestDigest := imageMeta.Manifests[0].Digest.String()
|
manifestDigest := imageMeta.Manifests[0].Digest.String()
|
||||||
|
@ -1001,6 +1255,47 @@ func filterBaseImages(image *gql_generated.ImageSummary) mTypes.FilterFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filterBaseImagesForMeta(image mTypes.ImageMeta) mTypes.FilterFunc {
|
||||||
|
return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool {
|
||||||
|
var addImageToList bool
|
||||||
|
|
||||||
|
manifest := imageMeta.Manifests[0]
|
||||||
|
|
||||||
|
for i := range image.Manifests {
|
||||||
|
manifestDigest := manifest.Digest.String()
|
||||||
|
if manifestDigest == image.Manifests[i].Digest.String() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
addImageToList = true
|
||||||
|
|
||||||
|
for _, l := range manifest.Manifest.Layers {
|
||||||
|
foundLayer := false
|
||||||
|
|
||||||
|
for _, k := range image.Manifests[i].Manifest.Layers {
|
||||||
|
if l.Digest.String() == k.Digest.String() {
|
||||||
|
foundLayer = true
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundLayer {
|
||||||
|
addImageToList = false
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if addImageToList {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func validateGlobalSearchInput(query string, filter *gql_generated.Filter,
|
func validateGlobalSearchInput(query string, filter *gql_generated.Filter,
|
||||||
requestedPage *gql_generated.PageInput,
|
requestedPage *gql_generated.PageInput,
|
||||||
) error {
|
) error {
|
||||||
|
@ -1187,6 +1482,17 @@ func (p timeSlice) Swap(i, j int) {
|
||||||
p[i], p[j] = p[j], p[i]
|
p[i], p[j] = p[j], p[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dderef is a default deref.
|
||||||
|
func dderef[T any](pointer *T) T {
|
||||||
|
var defValue T
|
||||||
|
|
||||||
|
if pointer != nil {
|
||||||
|
return *pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
return defValue
|
||||||
|
}
|
||||||
|
|
||||||
func deref[T any](pointer *T, defaultVal T) T {
|
func deref[T any](pointer *T, defaultVal T) T {
|
||||||
if pointer != nil {
|
if pointer != nil {
|
||||||
return *pointer
|
return *pointer
|
||||||
|
@ -1195,6 +1501,12 @@ func deref[T any](pointer *T, defaultVal T) T {
|
||||||
return defaultVal
|
return defaultVal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ref[T any](input T) *T {
|
||||||
|
ref := input
|
||||||
|
|
||||||
|
return &ref
|
||||||
|
}
|
||||||
|
|
||||||
func getImageList(ctx context.Context, repo string, metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo,
|
func getImageList(ctx context.Context, repo string, metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo,
|
||||||
requestedPage *gql_generated.PageInput, log log.Logger, //nolint:unparam
|
requestedPage *gql_generated.PageInput, log log.Logger, //nolint:unparam
|
||||||
) (*gql_generated.PaginatedImagesResult, error) {
|
) (*gql_generated.PaginatedImagesResult, error) {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
|
||||||
|
zerr "zotregistry.dev/zot/errors"
|
||||||
"zotregistry.dev/zot/pkg/common"
|
"zotregistry.dev/zot/pkg/common"
|
||||||
"zotregistry.dev/zot/pkg/extensions/search/convert"
|
"zotregistry.dev/zot/pkg/extensions/search/convert"
|
||||||
cveinfo "zotregistry.dev/zot/pkg/extensions/search/cve"
|
cveinfo "zotregistry.dev/zot/pkg/extensions/search/cve"
|
||||||
|
@ -730,6 +731,50 @@ func TestQueryResolverErrors(t *testing.T) {
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("CVEDiffListForImages nill cveinfo", func() {
|
||||||
|
resolverConfig := NewResolver(
|
||||||
|
log,
|
||||||
|
storage.StoreController{
|
||||||
|
DefaultStore: mocks.MockedImageStore{},
|
||||||
|
},
|
||||||
|
mocks.MetaDBMock{
|
||||||
|
GetMultipleRepoMetaFn: func(ctx context.Context, filter func(repoMeta mTypes.RepoMeta) bool,
|
||||||
|
) ([]mTypes.RepoMeta, error) {
|
||||||
|
return []mTypes.RepoMeta{}, ErrTestError
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
qr := queryResolver{resolverConfig}
|
||||||
|
|
||||||
|
_, err := qr.CVEDiffListForImages(ctx, gql_generated.ImageInput{}, gql_generated.ImageInput{},
|
||||||
|
&gql_generated.PageInput{}, nil, nil)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("CVEDiffListForImages error", func() {
|
||||||
|
resolverConfig := NewResolver(
|
||||||
|
log,
|
||||||
|
storage.StoreController{
|
||||||
|
DefaultStore: mocks.MockedImageStore{},
|
||||||
|
},
|
||||||
|
mocks.MetaDBMock{
|
||||||
|
GetMultipleRepoMetaFn: func(ctx context.Context, filter func(repoMeta mTypes.RepoMeta) bool,
|
||||||
|
) ([]mTypes.RepoMeta, error) {
|
||||||
|
return []mTypes.RepoMeta{}, ErrTestError
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mocks.CveInfoMock{},
|
||||||
|
)
|
||||||
|
|
||||||
|
qr := queryResolver{resolverConfig}
|
||||||
|
|
||||||
|
_, err := qr.CVEDiffListForImages(ctx, gql_generated.ImageInput{}, gql_generated.ImageInput{},
|
||||||
|
&gql_generated.PageInput{}, nil, nil)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
Convey("ImageListForCve error in GetMultipleRepoMeta", func() {
|
Convey("ImageListForCve error in GetMultipleRepoMeta", func() {
|
||||||
resolverConfig := NewResolver(
|
resolverConfig := NewResolver(
|
||||||
log,
|
log,
|
||||||
|
@ -1883,6 +1928,272 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
|
||||||
)
|
)
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("CVE Diff between images", t, func() {
|
||||||
|
// image := "image:tag"
|
||||||
|
// baseImage := "base:basetag"
|
||||||
|
ctx := context.Background()
|
||||||
|
pageInput := &gql_generated.PageInput{
|
||||||
|
SortBy: ref(gql_generated.SortCriteriaAlphabeticAsc),
|
||||||
|
}
|
||||||
|
|
||||||
|
boltDriver, err := boltdb.GetBoltDriver(boltdb.DBParameters{RootDir: t.TempDir()})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
metaDB, err := boltdb.New(boltDriver, log)
|
||||||
|
if 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, _, _ := common.GetRepoReference(image)
|
||||||
|
|
||||||
|
if common.IsDigest(ref) {
|
||||||
|
return getCveResults(ref), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
repoMeta, _ := 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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cveInfo := &cveinfo.BaseCveInfo{
|
||||||
|
Log: log,
|
||||||
|
Scanner: scanner,
|
||||||
|
MetaDB: metaDB,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, err = ociutils.InitializeTestMetaDB(ctx, 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"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
minuend := gql_generated.ImageInput{Repo: "repo", Tag: "image"}
|
||||||
|
subtrahend := gql_generated.ImageInput{Repo: "repo", Tag: "image"}
|
||||||
|
diffResult, err := getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(len(diffResult.CVEList), ShouldEqual, 0)
|
||||||
|
|
||||||
|
minuend = gql_generated.ImageInput{Repo: "repo", Tag: "image"}
|
||||||
|
subtrahend = gql_generated.ImageInput{}
|
||||||
|
diffResult, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(len(diffResult.CVEList), ShouldEqual, 1)
|
||||||
|
|
||||||
|
minuend = gql_generated.ImageInput{Repo: "repo", Tag: "base-image"}
|
||||||
|
subtrahend = gql_generated.ImageInput{Repo: "repo", Tag: "image"}
|
||||||
|
diffResult, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(len(diffResult.CVEList), ShouldEqual, 0)
|
||||||
|
|
||||||
|
minuend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-img", Platform: &gql_generated.PlatformInput{}}
|
||||||
|
subtrahend = gql_generated.ImageInput{}
|
||||||
|
_, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
minuend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-img", Platform: &gql_generated.PlatformInput{
|
||||||
|
Os: ref("testOs"),
|
||||||
|
Arch: ref("testArch"),
|
||||||
|
}}
|
||||||
|
subtrahend = gql_generated.ImageInput{}
|
||||||
|
diffResult, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(len(diffResult.CVEList), ShouldEqual, 1)
|
||||||
|
So(diffResult.Subtrahend.Repo, ShouldEqual, "repo-multi")
|
||||||
|
So(diffResult.Subtrahend.Tag, ShouldEqual, "multi-base")
|
||||||
|
So(dderef(diffResult.Subtrahend.Platform.Os), ShouldResemble, "testOs")
|
||||||
|
So(dderef(diffResult.Subtrahend.Platform.Arch), ShouldResemble, "testArch")
|
||||||
|
|
||||||
|
minuend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-img", Platform: &gql_generated.PlatformInput{
|
||||||
|
Os: ref("testOs"),
|
||||||
|
Arch: ref("testArch"),
|
||||||
|
}}
|
||||||
|
subtrahend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-base", Platform: &gql_generated.PlatformInput{
|
||||||
|
Os: ref("testOs"),
|
||||||
|
Arch: ref("testArch"),
|
||||||
|
}}
|
||||||
|
diffResult, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(len(diffResult.CVEList), ShouldEqual, 1)
|
||||||
|
|
||||||
|
minuend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-img", Platform: &gql_generated.PlatformInput{
|
||||||
|
Os: ref("testOs"),
|
||||||
|
Arch: ref("testArch"),
|
||||||
|
}}
|
||||||
|
subtrahend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-base", Platform: &gql_generated.PlatformInput{}}
|
||||||
|
_, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("CVE Diff Errors", t, func() {
|
||||||
|
ctx := context.Background()
|
||||||
|
metaDB := mocks.MetaDBMock{}
|
||||||
|
cveInfo := mocks.CveInfoMock{}
|
||||||
|
emptyImage := gql_generated.ImageInput{}
|
||||||
|
|
||||||
|
Convey("minuend is empty", func() {
|
||||||
|
_, err := getCVEDiffListForImages(ctx, emptyImage, emptyImage, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("no ", func() {
|
||||||
|
minuend := gql_generated.ImageInput{Repo: "repo", Tag: "bad-tag"}
|
||||||
|
_, err := getCVEDiffListForImages(ctx, minuend, emptyImage, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("getImageSummary for subtrahend errors", func() {
|
||||||
|
metaDB.GetRepoMetaFn = func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
|
||||||
|
return mTypes.RepoMeta{}, ErrTestError
|
||||||
|
}
|
||||||
|
minuend := gql_generated.ImageInput{Repo: "test", Tag: "img"}
|
||||||
|
_, err := getCVEDiffListForImages(ctx, minuend, emptyImage, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
metaDB.GetRepoMetaFn = func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
|
||||||
|
return mTypes.RepoMeta{}, zerr.ErrRepoMetaNotFound
|
||||||
|
}
|
||||||
|
minuend = gql_generated.ImageInput{Repo: "test", Tag: "img"}
|
||||||
|
_, err = getCVEDiffListForImages(ctx, minuend, emptyImage, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("FilterTags for subtrahend errors", func() {
|
||||||
|
metaDB.FilterTagsFn = func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc,
|
||||||
|
) ([]mTypes.FullImageMeta, error) {
|
||||||
|
return nil, ErrTestError
|
||||||
|
}
|
||||||
|
minuend := gql_generated.ImageInput{Repo: "test", Tag: "img"}
|
||||||
|
_, err = getCVEDiffListForImages(ctx, minuend, emptyImage, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("GetCVEDiffListForImages errors", func() {
|
||||||
|
cveInfo.GetCVEDiffListForImagesFn = func(ctx context.Context, minuend, subtrahend, searchedCVE, excluded string,
|
||||||
|
pageInput cvemodel.PageInput,
|
||||||
|
) ([]cvemodel.CVE, cvemodel.ImageCVESummary, common.PageInfo, error) {
|
||||||
|
return nil, cvemodel.ImageCVESummary{}, common.PageInfo{}, ErrTestError
|
||||||
|
}
|
||||||
|
minuend := gql_generated.ImageInput{Repo: "test", Tag: "img"}
|
||||||
|
subtrahend := gql_generated.ImageInput{Repo: "sub", Tag: "img"}
|
||||||
|
_, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMockedDerivedImageList(t *testing.T) {
|
func TestMockedDerivedImageList(t *testing.T) {
|
||||||
|
@ -2352,10 +2663,67 @@ func TestExpandedRepoInfoErrors(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func ref[T any](input T) *T {
|
func TestUtils(t *testing.T) {
|
||||||
ref := input
|
Convey("utils", t, func() {
|
||||||
|
Convey("", func() {
|
||||||
|
So(isMatchingPlatform(ispec.Platform{OS: "test"}, gql_generated.PlatformInput{Os: ref("t")}), ShouldBeFalse)
|
||||||
|
So(getArch(ispec.Platform{OS: "t", Architecture: "e", Variant: "st"}), ShouldResemble, "e/st")
|
||||||
|
})
|
||||||
|
|
||||||
return &ref
|
Convey("checkImageInput", func() {
|
||||||
|
_, err := resolveImageData(context.Background(), gql_generated.ImageInput{Repo: "test"}, mocks.MetaDBMock{})
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("checkImageInput can't find index data", func() {
|
||||||
|
_, err := resolveImageData(context.Background(), gql_generated.ImageInput{
|
||||||
|
Repo: "test", Tag: "test", Digest: ref("dig"),
|
||||||
|
},
|
||||||
|
mocks.MetaDBMock{
|
||||||
|
GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
|
||||||
|
return mTypes.RepoMeta{Tags: map[string]mTypes.Descriptor{
|
||||||
|
"test": {MediaType: ispec.MediaTypeImageIndex},
|
||||||
|
}}, nil
|
||||||
|
},
|
||||||
|
GetImageMetaFn: func(digest godigest.Digest) (mTypes.ImageMeta, error) {
|
||||||
|
return mTypes.ImageMeta{}, ErrTestError
|
||||||
|
},
|
||||||
|
})
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
Convey("checkImageInput image meta not found", func() {
|
||||||
|
_, err := resolveImageData(context.Background(), gql_generated.ImageInput{
|
||||||
|
Repo: "test", Tag: "test", Digest: ref("dig"),
|
||||||
|
},
|
||||||
|
mocks.MetaDBMock{
|
||||||
|
GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
|
||||||
|
return mTypes.RepoMeta{Tags: map[string]mTypes.Descriptor{
|
||||||
|
"test": {MediaType: ispec.MediaTypeImageIndex},
|
||||||
|
}}, nil
|
||||||
|
},
|
||||||
|
GetImageMetaFn: func(digest godigest.Digest) (mTypes.ImageMeta, error) {
|
||||||
|
return mTypes.ImageMeta{}, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
Convey("checkImageInput image meta bad media type", func() {
|
||||||
|
_, err := resolveImageData(context.Background(), gql_generated.ImageInput{
|
||||||
|
Repo: "test", Tag: "test", Digest: ref("dig"),
|
||||||
|
},
|
||||||
|
mocks.MetaDBMock{
|
||||||
|
GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
|
||||||
|
return mTypes.RepoMeta{Tags: map[string]mTypes.Descriptor{
|
||||||
|
"test": {MediaType: "bad-type"},
|
||||||
|
}}, nil
|
||||||
|
},
|
||||||
|
GetImageMetaFn: func(digest godigest.Digest) (mTypes.ImageMeta, error) {
|
||||||
|
return mTypes.ImageMeta{}, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func getGQLPageInput(limit int, offset int) *gql_generated.PageInput {
|
func getGQLPageInput(limit int, offset int) *gql_generated.PageInput {
|
||||||
|
|
|
@ -33,6 +33,54 @@ type CVEResultForImage {
|
||||||
Page: PageInfo
|
Page: PageInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
ImageIdentifier
|
||||||
|
"""
|
||||||
|
type ImageIdentifier {
|
||||||
|
"""
|
||||||
|
Repo name of the image
|
||||||
|
"""
|
||||||
|
Repo: String!
|
||||||
|
"""
|
||||||
|
The tag of the image
|
||||||
|
"""
|
||||||
|
Tag: String!
|
||||||
|
"""
|
||||||
|
The digest of the image
|
||||||
|
"""
|
||||||
|
Digest: String
|
||||||
|
"""
|
||||||
|
The platform of the image
|
||||||
|
"""
|
||||||
|
Platform: Platform
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Contains the diff results of subtracting Subtrahend's CVEs from Minuend's CVEs
|
||||||
|
"""
|
||||||
|
type CVEDiffResult {
|
||||||
|
"""
|
||||||
|
Minuend is the image from which CVE's we subtract
|
||||||
|
"""
|
||||||
|
Minuend: ImageIdentifier!
|
||||||
|
"""
|
||||||
|
Subtrahend is the image which CVE's are subtracted
|
||||||
|
"""
|
||||||
|
Subtrahend: ImageIdentifier!
|
||||||
|
"""
|
||||||
|
List of CVE objects which are present in minuend but not in subtrahend
|
||||||
|
"""
|
||||||
|
CVEList: [CVE]
|
||||||
|
"""
|
||||||
|
Summary of the findings for this image
|
||||||
|
"""
|
||||||
|
Summary: ImageVulnerabilitySummary
|
||||||
|
"""
|
||||||
|
The CVE pagination information, see PageInfo object for more details
|
||||||
|
"""
|
||||||
|
Page: PageInfo
|
||||||
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Contains various details about the CVE (Common Vulnerabilities and Exposures)
|
Contains various details about the CVE (Common Vulnerabilities and Exposures)
|
||||||
and a list of PackageInfo about the affected packages
|
and a list of PackageInfo about the affected packages
|
||||||
|
@ -567,6 +615,42 @@ input PageInput {
|
||||||
sortBy: SortCriteria
|
sortBy: SortCriteria
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
PlatformInput contains the Os and the Arch of the input image
|
||||||
|
"""
|
||||||
|
input PlatformInput {
|
||||||
|
"""
|
||||||
|
The os of the image
|
||||||
|
"""
|
||||||
|
Os: String
|
||||||
|
"""
|
||||||
|
The arch of the image
|
||||||
|
"""
|
||||||
|
Arch: String
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
ImageInput
|
||||||
|
"""
|
||||||
|
input ImageInput {
|
||||||
|
"""
|
||||||
|
Repo name of the image
|
||||||
|
"""
|
||||||
|
Repo: String!
|
||||||
|
"""
|
||||||
|
The tag of the image
|
||||||
|
"""
|
||||||
|
Tag: String!
|
||||||
|
"""
|
||||||
|
The digest of the image
|
||||||
|
"""
|
||||||
|
Digest: String
|
||||||
|
"""
|
||||||
|
The platform of the image
|
||||||
|
"""
|
||||||
|
Platform: PlatformInput
|
||||||
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Paginated list of RepoSummary objects
|
Paginated list of RepoSummary objects
|
||||||
"""
|
"""
|
||||||
|
@ -645,6 +729,22 @@ type Query {
|
||||||
severity: String
|
severity: String
|
||||||
): CVEResultForImage!
|
): CVEResultForImage!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Returns a list with CVE's that are present in `image` but not in `comparedImage`
|
||||||
|
"""
|
||||||
|
CVEDiffListForImages(
|
||||||
|
"Image name in format `repository:tag` or `repository@digest`"
|
||||||
|
minuend: ImageInput!,
|
||||||
|
"Compared image name in format `repository:tag` or `repository@digest`"
|
||||||
|
subtrahend: ImageInput!,
|
||||||
|
"Sets the parameters of the requested page"
|
||||||
|
requestedPage: PageInput
|
||||||
|
"Search term for specific CVE by title/id"
|
||||||
|
searchedCVE: String
|
||||||
|
"Search term that must not be present in the returned results"
|
||||||
|
excludedCVE: String
|
||||||
|
): CVEDiffResult!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Returns a list of images vulnerable to the CVE of the specified ID
|
Returns a list of images vulnerable to the CVE of the specified ID
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -23,6 +23,16 @@ func (r *queryResolver) CVEListForImage(ctx context.Context, image string, reque
|
||||||
return getCVEListForImage(ctx, image, r.cveInfo, requestedPage, deref(searchedCve, ""), deref(excludedCve, ""), deref(severity, ""), r.log)
|
return getCVEListForImage(ctx, image, r.cveInfo, requestedPage, deref(searchedCve, ""), deref(excludedCve, ""), deref(severity, ""), r.log)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CVEDiffListForImages is the resolver for the CVEDiffListForImages field.
|
||||||
|
func (r *queryResolver) CVEDiffListForImages(ctx context.Context, minuend gql_generated.ImageInput, subtrahend gql_generated.ImageInput, requestedPage *gql_generated.PageInput, searchedCve *string, excludedCve *string) (*gql_generated.CVEDiffResult, error) {
|
||||||
|
if r.cveInfo == nil {
|
||||||
|
return &gql_generated.CVEDiffResult{}, zerr.ErrCVESearchDisabled
|
||||||
|
}
|
||||||
|
|
||||||
|
return getCVEDiffListForImages(ctx, minuend, subtrahend, r.metaDB, r.cveInfo, requestedPage,
|
||||||
|
dderef(searchedCve), dderef(excludedCve), r.log)
|
||||||
|
}
|
||||||
|
|
||||||
// ImageListForCve is the resolver for the ImageListForCVE field.
|
// ImageListForCve is the resolver for the ImageListForCVE field.
|
||||||
func (r *queryResolver) ImageListForCve(ctx context.Context, id string, filter *gql_generated.Filter, requestedPage *gql_generated.PageInput) (*gql_generated.PaginatedImagesResult, error) {
|
func (r *queryResolver) ImageListForCve(ctx context.Context, id string, filter *gql_generated.Filter, requestedPage *gql_generated.PageInput) (*gql_generated.PaginatedImagesResult, error) {
|
||||||
if r.cveInfo == nil {
|
if r.cveInfo == nil {
|
||||||
|
|
|
@ -38,6 +38,10 @@ func AcceptAllRepoTag(repo, tag string) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AcceptOnlyRepo(repo string) func(repo, tag string) bool {
|
||||||
|
return func(r, t string) bool { return repo == r }
|
||||||
|
}
|
||||||
|
|
||||||
func AcceptAllImageMeta(repoMeta RepoMeta, imageMeta ImageMeta) bool {
|
func AcceptAllImageMeta(repoMeta RepoMeta, imageMeta ImageMeta) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,29 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type CveInfoMock struct {
|
type CveInfoMock struct {
|
||||||
GetImageListForCVEFn func(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error)
|
GetImageListForCVEFn func(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error)
|
||||||
|
|
||||||
GetImageListWithCVEFixedFn func(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error)
|
GetImageListWithCVEFixedFn func(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error)
|
||||||
GetCVEListForImageFn func(ctx context.Context, repo, reference, searchedCVE, excludedCVE, severity string,
|
|
||||||
|
GetCVEListForImageFn func(ctx context.Context, repo, reference, searchedCVE, excludedCVE, severity string,
|
||||||
pageInput cvemodel.PageInput) ([]cvemodel.CVE, cvemodel.ImageCVESummary, common.PageInfo, error)
|
pageInput cvemodel.PageInput) ([]cvemodel.CVE, cvemodel.ImageCVESummary, common.PageInfo, error)
|
||||||
|
|
||||||
GetCVESummaryForImageMediaFn func(ctx context.Context, repo string, digest, mediaType string,
|
GetCVESummaryForImageMediaFn func(ctx context.Context, repo string, digest, mediaType string,
|
||||||
) (cvemodel.ImageCVESummary, error)
|
) (cvemodel.ImageCVESummary, error)
|
||||||
|
|
||||||
|
GetCVEDiffListForImagesFn func(ctx context.Context, minuend, subtrahend, searchedCVE string,
|
||||||
|
excludedCVE string, pageInput cvemodel.PageInput,
|
||||||
|
) ([]cvemodel.CVE, cvemodel.ImageCVESummary, common.PageInfo, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cveInfo CveInfoMock) GetCVEDiffListForImages(ctx context.Context, minuend, subtrahend, searchedCVE string,
|
||||||
|
excludedCVE string, pageInput cvemodel.PageInput,
|
||||||
|
) ([]cvemodel.CVE, cvemodel.ImageCVESummary, common.PageInfo, error) {
|
||||||
|
if cveInfo.GetCVEDiffListForImagesFn != nil {
|
||||||
|
return cveInfo.GetCVEDiffListForImagesFn(ctx, minuend, subtrahend, searchedCVE, excludedCVE, pageInput)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []cvemodel.CVE{}, cvemodel.ImageCVESummary{}, common.PageInfo{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cveInfo CveInfoMock) GetImageListForCVE(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error) {
|
func (cveInfo CveInfoMock) GetImageListForCVE(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error) {
|
||||||
|
|
Loading…
Reference in a new issue