mirror of
https://github.com/project-zot/zot.git
synced 2025-03-18 02:22:53 -05:00
feat(cve): the cve related calls to use repodb and add pagination on image results (#1118)
Signed-off-by: Andrei Aaron <aaaron@luxoft.com>
This commit is contained in:
parent
f0d947809b
commit
3caa0f3253
10 changed files with 1226 additions and 307 deletions
|
@ -16,7 +16,7 @@ import (
|
|||
)
|
||||
|
||||
type CveInfo interface {
|
||||
GetImageListForCVE(repo, cveID string) ([]ImageInfoByCVE, error)
|
||||
GetImageListForCVE(repo, cveID string) ([]common.TagInfo, error)
|
||||
GetImageListWithCVEFixed(repo, cveID string) ([]common.TagInfo, error)
|
||||
GetCVEListForImage(image string) (map[string]cvemodel.CVE, error)
|
||||
GetCVESummaryForImage(image string) (ImageCVESummary, error)
|
||||
|
@ -30,12 +30,6 @@ type Scanner interface {
|
|||
UpdateDB() error
|
||||
}
|
||||
|
||||
type ImageInfoByCVE struct {
|
||||
Tag string
|
||||
Digest godigest.Digest
|
||||
Manifest ispec.Manifest
|
||||
}
|
||||
|
||||
type ImageCVESummary struct {
|
||||
Count int
|
||||
MaxSeverity string
|
||||
|
@ -59,8 +53,8 @@ func NewCVEInfo(storeController storage.StoreController, repoDB repodb.RepoDB,
|
|||
}
|
||||
}
|
||||
|
||||
func (cveinfo BaseCveInfo) GetImageListForCVE(repo, cveID string) ([]ImageInfoByCVE, error) {
|
||||
imgList := make([]ImageInfoByCVE, 0)
|
||||
func (cveinfo BaseCveInfo) GetImageListForCVE(repo, cveID string) ([]common.TagInfo, error) {
|
||||
imgList := make([]common.TagInfo, 0)
|
||||
|
||||
repoMeta, err := cveinfo.RepoDB.GetRepoMeta(repo)
|
||||
if err != nil {
|
||||
|
@ -110,10 +104,9 @@ func (cveinfo BaseCveInfo) GetImageListForCVE(repo, cveID string) ([]ImageInfoBy
|
|||
|
||||
for id := range cveMap {
|
||||
if id == cveID {
|
||||
imgList = append(imgList, ImageInfoByCVE{
|
||||
Tag: tag,
|
||||
Digest: manifestDigest,
|
||||
Manifest: manifestContent,
|
||||
imgList = append(imgList, common.TagInfo{
|
||||
Name: tag,
|
||||
Digest: manifestDigest,
|
||||
})
|
||||
|
||||
break
|
||||
|
|
|
@ -414,10 +414,9 @@ func TestCVESearchDisabled(t *testing.T) {
|
|||
ctrlManager.StartAndWait(port)
|
||||
|
||||
// Wait for trivy db to download
|
||||
_, err = ReadLogFileAndSearchString(logPath, "DB update completed, next update scheduled", 90*time.Second)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
found, err := ReadLogFileAndSearchString(logPath, "CVE config not provided, skipping CVE update", 90*time.Second)
|
||||
So(err, ShouldBeNil)
|
||||
So(found, ShouldBeTrue)
|
||||
|
||||
defer ctrlManager.StopServer()
|
||||
|
||||
|
@ -798,6 +797,11 @@ func TestCVEStruct(t *testing.T) {
|
|||
So(err, ShouldBeNil)
|
||||
|
||||
manifestBlob11, err := json.Marshal(ispec.Manifest{
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: ispec.MediaTypeImageConfig,
|
||||
Size: 0,
|
||||
Digest: godigest.FromBytes(configBlob11),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: ispec.MediaTypeImageLayerGzip,
|
||||
|
@ -805,9 +809,6 @@ func TestCVEStruct(t *testing.T) {
|
|||
Digest: godigest.NewDigestFromEncoded(godigest.SHA256, "digest"),
|
||||
},
|
||||
},
|
||||
Config: ispec.Descriptor{
|
||||
Digest: godigest.FromBytes(configBlob11),
|
||||
},
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
|
@ -832,6 +833,11 @@ func TestCVEStruct(t *testing.T) {
|
|||
So(err, ShouldBeNil)
|
||||
|
||||
manifestBlob12, err := json.Marshal(ispec.Manifest{
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: ispec.MediaTypeImageConfig,
|
||||
Size: 0,
|
||||
Digest: godigest.FromBytes(configBlob12),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: ispec.MediaTypeImageLayerGzip,
|
||||
|
@ -839,9 +845,6 @@ func TestCVEStruct(t *testing.T) {
|
|||
Digest: godigest.NewDigestFromEncoded(godigest.SHA256, "digest"),
|
||||
},
|
||||
},
|
||||
Config: ispec.Descriptor{
|
||||
Digest: godigest.FromBytes(configBlob12),
|
||||
},
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
|
@ -866,6 +869,11 @@ func TestCVEStruct(t *testing.T) {
|
|||
So(err, ShouldBeNil)
|
||||
|
||||
manifestBlob13, err := json.Marshal(ispec.Manifest{
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: ispec.MediaTypeImageConfig,
|
||||
Size: 0,
|
||||
Digest: godigest.FromBytes(configBlob13),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: ispec.MediaTypeImageLayerGzip,
|
||||
|
@ -873,9 +881,6 @@ func TestCVEStruct(t *testing.T) {
|
|||
Digest: godigest.NewDigestFromEncoded(godigest.SHA256, "digest"),
|
||||
},
|
||||
},
|
||||
Config: ispec.Descriptor{
|
||||
Digest: godigest.FromBytes(configBlob13),
|
||||
},
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
|
@ -898,6 +903,11 @@ func TestCVEStruct(t *testing.T) {
|
|||
So(err, ShouldBeNil)
|
||||
|
||||
manifestBlob14, err := json.Marshal(ispec.Manifest{
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: ispec.MediaTypeImageConfig,
|
||||
Size: 0,
|
||||
Digest: godigest.FromBytes(configBlob14),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: ispec.MediaTypeImageLayerGzip,
|
||||
|
@ -905,9 +915,6 @@ func TestCVEStruct(t *testing.T) {
|
|||
Digest: godigest.NewDigestFromEncoded(godigest.SHA256, "digest"),
|
||||
},
|
||||
},
|
||||
Config: ispec.Descriptor{
|
||||
Digest: godigest.FromBytes(configBlob14),
|
||||
},
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
|
@ -931,6 +938,11 @@ func TestCVEStruct(t *testing.T) {
|
|||
So(err, ShouldBeNil)
|
||||
|
||||
manifestBlob61, err := json.Marshal(ispec.Manifest{
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: ispec.MediaTypeImageConfig,
|
||||
Size: 0,
|
||||
Digest: godigest.FromBytes(configBlob61),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: ispec.MediaTypeImageLayerGzip,
|
||||
|
@ -938,9 +950,6 @@ func TestCVEStruct(t *testing.T) {
|
|||
Digest: godigest.NewDigestFromEncoded(godigest.SHA256, "digest"),
|
||||
},
|
||||
},
|
||||
Config: ispec.Descriptor{
|
||||
Digest: godigest.FromBytes(configBlob61),
|
||||
},
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
|
@ -964,6 +973,11 @@ func TestCVEStruct(t *testing.T) {
|
|||
So(err, ShouldBeNil)
|
||||
|
||||
manifestBlob21, err := json.Marshal(ispec.Manifest{
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: ispec.MediaTypeImageConfig,
|
||||
Size: 0,
|
||||
Digest: godigest.FromBytes(configBlob21),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: ispec.MediaTypeImageLayerNonDistributableGzip,
|
||||
|
@ -971,9 +985,6 @@ func TestCVEStruct(t *testing.T) {
|
|||
Digest: godigest.NewDigestFromEncoded(godigest.SHA256, "digest"),
|
||||
},
|
||||
},
|
||||
Config: ispec.Descriptor{
|
||||
Digest: godigest.FromBytes(configBlob21),
|
||||
},
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
|
@ -1312,55 +1323,49 @@ func TestCVEStruct(t *testing.T) {
|
|||
t.Log("Test GetImageListForCVE")
|
||||
|
||||
// Image is found
|
||||
imageInfoByCveList, err := cveInfo.GetImageListForCVE("repo1", "CVE1")
|
||||
tagList, err = cveInfo.GetImageListForCVE("repo1", "CVE1")
|
||||
So(err, ShouldBeNil)
|
||||
So(len(imageInfoByCveList), ShouldEqual, 3)
|
||||
So(len(tagList), ShouldEqual, 3)
|
||||
expectedTags = []string{"0.1.0", "1.0.0", "1.0.1"}
|
||||
So(expectedTags, ShouldContain, imageInfoByCveList[0].Tag)
|
||||
So(expectedTags, ShouldContain, imageInfoByCveList[1].Tag)
|
||||
So(expectedTags, ShouldContain, imageInfoByCveList[2].Tag)
|
||||
So(expectedTags, ShouldContain, tagList[0].Name)
|
||||
So(expectedTags, ShouldContain, tagList[1].Name)
|
||||
So(expectedTags, ShouldContain, tagList[2].Name)
|
||||
|
||||
imageInfoByCveList, err = cveInfo.GetImageListForCVE("repo1", "CVE2")
|
||||
tagList, err = cveInfo.GetImageListForCVE("repo1", "CVE2")
|
||||
So(err, ShouldBeNil)
|
||||
So(len(imageInfoByCveList), ShouldEqual, 1)
|
||||
So(imageInfoByCveList[0].Tag, ShouldEqual, "1.0.0")
|
||||
So(len(tagList), ShouldEqual, 1)
|
||||
So(tagList[0].Name, ShouldEqual, "1.0.0")
|
||||
|
||||
imageInfoByCveList, err = cveInfo.GetImageListForCVE("repo1", "CVE3")
|
||||
tagList, err = cveInfo.GetImageListForCVE("repo1", "CVE3")
|
||||
So(err, ShouldBeNil)
|
||||
So(len(imageInfoByCveList), ShouldEqual, 3)
|
||||
So(len(tagList), ShouldEqual, 3)
|
||||
expectedTags = []string{"1.0.0", "1.0.1", "1.1.0"}
|
||||
So(expectedTags, ShouldContain, imageInfoByCveList[0].Tag)
|
||||
So(expectedTags, ShouldContain, imageInfoByCveList[1].Tag)
|
||||
So(expectedTags, ShouldContain, imageInfoByCveList[2].Tag)
|
||||
So(expectedTags, ShouldContain, tagList[0].Name)
|
||||
So(expectedTags, ShouldContain, tagList[1].Name)
|
||||
So(expectedTags, ShouldContain, tagList[2].Name)
|
||||
|
||||
// Image/repo doesn't have the CVE at all
|
||||
imageInfoByCveList, err = cveInfo.GetImageListForCVE("repo6", "CVE1")
|
||||
tagList, err = cveInfo.GetImageListForCVE("repo6", "CVE1")
|
||||
So(err, ShouldBeNil)
|
||||
So(len(imageInfoByCveList), ShouldEqual, 0)
|
||||
So(len(tagList), ShouldEqual, 0)
|
||||
|
||||
// Image is not scannable
|
||||
imageInfoByCveList, err = cveInfo.GetImageListForCVE("repo2", "CVE100")
|
||||
tagList, err = cveInfo.GetImageListForCVE("repo2", "CVE100")
|
||||
// Image is not considered affected with CVE as scan is not possible
|
||||
// but do not return an error
|
||||
So(err, ShouldBeNil)
|
||||
So(len(imageInfoByCveList), ShouldEqual, 0)
|
||||
So(len(tagList), ShouldEqual, 0)
|
||||
|
||||
// Tag is not found, but we should not error
|
||||
imageInfoByCveList, err = cveInfo.GetImageListForCVE("repo3", "CVE101")
|
||||
tagList, err = cveInfo.GetImageListForCVE("repo3", "CVE101")
|
||||
So(err, ShouldBeNil)
|
||||
So(len(imageInfoByCveList), ShouldEqual, 0)
|
||||
|
||||
// Manifest is not found, assume it is affetected by the CVE
|
||||
// But we don't have enough of it's data to actually return it
|
||||
imageInfoByCveList, err = cveInfo.GetImageListForCVE("repo5", "CVE101")
|
||||
So(err, ShouldEqual, zerr.ErrManifestMetaNotFound)
|
||||
So(len(imageInfoByCveList), ShouldEqual, 0)
|
||||
So(len(tagList), ShouldEqual, 0)
|
||||
|
||||
// Repo is not found, assume it is affetected by the CVE
|
||||
// But we don't have enough of it's data to actually return it
|
||||
imageInfoByCveList, err = cveInfo.GetImageListForCVE("repo100", "CVE100")
|
||||
tagList, err = cveInfo.GetImageListForCVE("repo100", "CVE100")
|
||||
So(err, ShouldEqual, zerr.ErrRepoMetaNotFound)
|
||||
So(len(imageInfoByCveList), ShouldEqual, 0)
|
||||
So(len(tagList), ShouldEqual, 0)
|
||||
|
||||
t.Log("Test errors while scanning")
|
||||
|
||||
|
@ -1388,10 +1393,10 @@ func TestCVEStruct(t *testing.T) {
|
|||
So(err, ShouldBeNil)
|
||||
So(len(tagList), ShouldEqual, 0)
|
||||
|
||||
imageInfoByCveList, err = cveInfo.GetImageListForCVE("repo1", "CVE1")
|
||||
tagList, err = cveInfo.GetImageListForCVE("repo1", "CVE1")
|
||||
// Image is not considered affected with CVE as scan is not possible
|
||||
// but do not return an error
|
||||
So(err, ShouldBeNil)
|
||||
So(len(imageInfoByCveList), ShouldEqual, 0)
|
||||
So(len(tagList), ShouldEqual, 0)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -212,7 +212,7 @@ func (scanner Scanner) IsImageFormatScannable(image string) (bool, error) {
|
|||
for _, imageLayer := range manifestContent.Layers {
|
||||
switch imageLayer.MediaType {
|
||||
case ispec.MediaTypeImageLayerGzip, ispec.MediaTypeImageLayer, string(regTypes.DockerLayer):
|
||||
return true, nil
|
||||
continue
|
||||
default:
|
||||
scanner.log.Debug().Str("image", image).
|
||||
Msgf("image media type %s not supported for scanning", imageLayer.MediaType)
|
||||
|
@ -221,7 +221,7 @@ func (scanner Scanner) IsImageFormatScannable(image string) (bool, error) {
|
|||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (scanner Scanner) ScanImage(image string) (map[string]cvemodel.CVE, error) {
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
@ -209,6 +210,193 @@ func TestTrivyLibraryErrors(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestImageScannable(t *testing.T) {
|
||||
rootDir := t.TempDir()
|
||||
|
||||
repoDB, err := bolt.NewBoltDBWrapper(bolt.DBParameters{
|
||||
RootDir: rootDir,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Create test data for the following cases
|
||||
// - Error: RepoMeta not found in DB
|
||||
// - Error: Tag not found in DB
|
||||
// - Error: Digest in RepoMeta is invalid
|
||||
// - Error: ManifestData not found in repodb
|
||||
// - Error: ManifestData cannot be unmarshalled
|
||||
// - Error: ManifestData contains unscannable layer type
|
||||
// - Valid Scannable image
|
||||
|
||||
// Create repodb data for scannable image
|
||||
timeStamp := time.Date(2008, 1, 1, 12, 0, 0, 0, time.UTC)
|
||||
|
||||
validConfigBlob, err := json.Marshal(ispec.Image{
|
||||
Created: &timeStamp,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
validManifestBlob, err := json.Marshal(ispec.Manifest{
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: ispec.MediaTypeImageConfig,
|
||||
Size: 0,
|
||||
Digest: godigest.FromBytes(validConfigBlob),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: ispec.MediaTypeImageLayerGzip,
|
||||
Size: 0,
|
||||
Digest: godigest.NewDigestFromEncoded(godigest.SHA256, "digest"),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
validRepoMeta := repodb.ManifestData{
|
||||
ManifestBlob: validManifestBlob,
|
||||
ConfigBlob: validConfigBlob,
|
||||
}
|
||||
|
||||
digestValidManifest := godigest.FromBytes(validManifestBlob)
|
||||
|
||||
err = repoDB.SetManifestData(digestValidManifest, validRepoMeta)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = repoDB.SetRepoTag("repo1", "valid", digestValidManifest, ispec.MediaTypeImageManifest)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Create RepoDB data for manifest with unscannable layers
|
||||
manifestBlobUnscannableLayer, err := json.Marshal(ispec.Manifest{
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: ispec.MediaTypeImageConfig,
|
||||
Size: 0,
|
||||
Digest: godigest.FromBytes(validConfigBlob),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: "unscannable_media_type",
|
||||
Size: 0,
|
||||
Digest: godigest.NewDigestFromEncoded(godigest.SHA256, "digest"),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
repoMetaUnscannableLayer := repodb.ManifestData{
|
||||
ManifestBlob: manifestBlobUnscannableLayer,
|
||||
ConfigBlob: validConfigBlob,
|
||||
}
|
||||
|
||||
digestManifestUnscannableLayer := godigest.FromBytes(manifestBlobUnscannableLayer)
|
||||
|
||||
err = repoDB.SetManifestData(digestManifestUnscannableLayer, repoMetaUnscannableLayer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = repoDB.SetRepoTag("repo1", "unscannable-layer", digestManifestUnscannableLayer, ispec.MediaTypeImageManifest)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Create RepoDB data for unmarshable manifest
|
||||
unmarshableManifestBlob := []byte("Some string")
|
||||
repoMetaUnmarshable := repodb.ManifestData{
|
||||
ManifestBlob: unmarshableManifestBlob,
|
||||
ConfigBlob: validConfigBlob,
|
||||
}
|
||||
|
||||
digestUnmarshableManifest := godigest.FromBytes(unmarshableManifestBlob)
|
||||
|
||||
err = repoDB.SetManifestData(digestUnmarshableManifest, repoMetaUnmarshable)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = repoDB.SetRepoTag("repo1", "unmarshable", digestUnmarshableManifest, ispec.MediaTypeImageManifest)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Manifest meta cannot be found
|
||||
digestMissingManifest := godigest.FromBytes([]byte("Some other string"))
|
||||
|
||||
err = repoDB.SetRepoTag("repo1", "missing", digestMissingManifest, ispec.MediaTypeImageManifest)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// RepoMeta contains invalid digest
|
||||
err = repoDB.SetRepoTag("repo1", "invalid-digest", "invalid", ispec.MediaTypeImageManifest)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Continue with initializing the objects the scanner depends on
|
||||
log := log.NewLogger("debug", "")
|
||||
metrics := monitoring.NewMetricsServer(false, log)
|
||||
|
||||
store := local.NewImageStore(rootDir, false, storage.DefaultGCDelay, false, false, log, metrics, nil, nil)
|
||||
|
||||
storeController := storage.StoreController{}
|
||||
storeController.DefaultStore = store
|
||||
|
||||
scanner := NewScanner(storeController, repoDB, "ghcr.io/project-zot/trivy-db", log)
|
||||
|
||||
Convey("Valid image should be scannable", t, func() {
|
||||
result, err := scanner.IsImageFormatScannable("repo1:valid")
|
||||
So(err, ShouldBeNil)
|
||||
So(result, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("Image with layers of unsupported types should be unscannable", t, func() {
|
||||
result, err := scanner.IsImageFormatScannable("repo1:unscannable-layer")
|
||||
So(err, ShouldNotBeNil)
|
||||
So(result, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("Image with unmarshable manifests should be unscannable", t, func() {
|
||||
result, err := scanner.IsImageFormatScannable("repo1:unmarshable")
|
||||
So(err, ShouldNotBeNil)
|
||||
So(result, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("Image with missing manifest meta should be unscannable", t, func() {
|
||||
result, err := scanner.IsImageFormatScannable("repo1:missing")
|
||||
So(err, ShouldNotBeNil)
|
||||
So(result, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("Image with invalid manifest digest should be unscannable", t, func() {
|
||||
result, err := scanner.IsImageFormatScannable("repo1:invalid-digest")
|
||||
So(err, ShouldNotBeNil)
|
||||
So(result, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("Image with unknown tag should be unscannable", t, func() {
|
||||
result, err := scanner.IsImageFormatScannable("repo1:unknown-tag")
|
||||
So(err, ShouldNotBeNil)
|
||||
So(result, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("Image with unknown repo should be unscannable", t, func() {
|
||||
result, err := scanner.IsImageFormatScannable("unknown-repo:sometag")
|
||||
So(err, ShouldNotBeNil)
|
||||
So(result, ShouldBeFalse)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDefaultTrivyDBUrl(t *testing.T) {
|
||||
Convey("Test trivy DB download from default location", t, func() {
|
||||
// Create temporary directory
|
||||
|
|
|
@ -150,9 +150,9 @@ type ComplexityRoot struct {
|
|||
GlobalSearch func(childComplexity int, query string, filter *Filter, requestedPage *PageInput) int
|
||||
Image func(childComplexity int, image string) int
|
||||
ImageList func(childComplexity int, repo string) int
|
||||
ImageListForCve func(childComplexity int, id string) int
|
||||
ImageListForCve func(childComplexity int, id string, requestedPage *PageInput) int
|
||||
ImageListForDigest func(childComplexity int, id string, requestedPage *PageInput) int
|
||||
ImageListWithCVEFixed func(childComplexity int, id string, image string) int
|
||||
ImageListWithCVEFixed func(childComplexity int, id string, image string, requestedPage *PageInput) int
|
||||
Referrers func(childComplexity int, repo string, digest string, typeArg string) int
|
||||
RepoListWithNewestImage func(childComplexity int, requestedPage *PageInput) int
|
||||
}
|
||||
|
@ -187,8 +187,8 @@ type ComplexityRoot struct {
|
|||
|
||||
type QueryResolver interface {
|
||||
CVEListForImage(ctx context.Context, image string) (*CVEResultForImage, error)
|
||||
ImageListForCve(ctx context.Context, id string) ([]*ImageSummary, error)
|
||||
ImageListWithCVEFixed(ctx context.Context, id string, image string) ([]*ImageSummary, error)
|
||||
ImageListForCve(ctx context.Context, id string, requestedPage *PageInput) ([]*ImageSummary, error)
|
||||
ImageListWithCVEFixed(ctx context.Context, id string, image string, requestedPage *PageInput) ([]*ImageSummary, error)
|
||||
ImageListForDigest(ctx context.Context, id string, requestedPage *PageInput) ([]*ImageSummary, error)
|
||||
RepoListWithNewestImage(ctx context.Context, requestedPage *PageInput) (*PaginatedReposResult, error)
|
||||
ImageList(ctx context.Context, repo string) ([]*ImageSummary, error)
|
||||
|
@ -708,7 +708,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Query.ImageListForCve(childComplexity, args["id"].(string)), true
|
||||
return e.complexity.Query.ImageListForCve(childComplexity, args["id"].(string), args["requestedPage"].(*PageInput)), true
|
||||
|
||||
case "Query.ImageListForDigest":
|
||||
if e.complexity.Query.ImageListForDigest == nil {
|
||||
|
@ -732,7 +732,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Query.ImageListWithCVEFixed(childComplexity, args["id"].(string), args["image"].(string)), true
|
||||
return e.complexity.Query.ImageListWithCVEFixed(childComplexity, args["id"].(string), args["image"].(string), args["requestedPage"].(*PageInput)), true
|
||||
|
||||
case "Query.Referrers":
|
||||
if e.complexity.Query.Referrers == nil {
|
||||
|
@ -1148,12 +1148,12 @@ type Query {
|
|||
"""
|
||||
Returns a list of images vulnerable to the CVE of the specified ID
|
||||
"""
|
||||
ImageListForCVE(id: String!): [ImageSummary!]
|
||||
ImageListForCVE(id: String!, requestedPage: PageInput): [ImageSummary!]
|
||||
|
||||
"""
|
||||
Returns a list of images that are no longer vulnerable to the CVE of the specified ID, from the specified image (repo)
|
||||
"""
|
||||
ImageListWithCVEFixed(id: String!, image: String!): [ImageSummary!]
|
||||
ImageListWithCVEFixed(id: String!, image: String!, requestedPage: PageInput): [ImageSummary!]
|
||||
|
||||
"""
|
||||
Returns a list of images which contain the specified digest
|
||||
|
@ -1314,6 +1314,15 @@ func (ec *executionContext) field_Query_ImageListForCVE_args(ctx context.Context
|
|||
}
|
||||
}
|
||||
args["id"] = arg0
|
||||
var arg1 *PageInput
|
||||
if tmp, ok := rawArgs["requestedPage"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("requestedPage"))
|
||||
arg1, err = ec.unmarshalOPageInput2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPageInput(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["requestedPage"] = arg1
|
||||
return args, nil
|
||||
}
|
||||
|
||||
|
@ -1362,6 +1371,15 @@ func (ec *executionContext) field_Query_ImageListWithCVEFixed_args(ctx context.C
|
|||
}
|
||||
}
|
||||
args["image"] = arg1
|
||||
var arg2 *PageInput
|
||||
if tmp, ok := rawArgs["requestedPage"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("requestedPage"))
|
||||
arg2, err = ec.unmarshalOPageInput2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPageInput(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["requestedPage"] = arg2
|
||||
return args, nil
|
||||
}
|
||||
|
||||
|
@ -4148,7 +4166,7 @@ func (ec *executionContext) _Query_ImageListForCVE(ctx context.Context, field gr
|
|||
}()
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return ec.resolvers.Query().ImageListForCve(rctx, fc.Args["id"].(string))
|
||||
return ec.resolvers.Query().ImageListForCve(rctx, fc.Args["id"].(string), fc.Args["requestedPage"].(*PageInput))
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
|
@ -4244,7 +4262,7 @@ func (ec *executionContext) _Query_ImageListWithCVEFixed(ctx context.Context, fi
|
|||
}()
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return ec.resolvers.Query().ImageListWithCVEFixed(rctx, fc.Args["id"].(string), fc.Args["image"].(string))
|
||||
return ec.resolvers.Query().ImageListWithCVEFixed(rctx, fc.Args["id"].(string), fc.Args["image"].(string), fc.Args["requestedPage"].(*PageInput))
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
|
|
|
@ -191,6 +191,199 @@ func getImageSummary(ctx context.Context, repo, tag string, repoDB repodb.RepoDB
|
|||
return imageSummaries[0], nil
|
||||
}
|
||||
|
||||
func getCVEListForImage(
|
||||
ctx context.Context, //nolint:unparam // may be used in the future to filter by permissions
|
||||
image string,
|
||||
cveInfo cveinfo.CveInfo,
|
||||
log log.Logger, //nolint:unparam // may be used by devs for debugging
|
||||
) (*gql_generated.CVEResultForImage, error) {
|
||||
_, copyImgTag := common.GetImageDirAndTag(image)
|
||||
|
||||
if copyImgTag == "" {
|
||||
return &gql_generated.CVEResultForImage{}, gqlerror.Errorf("no reference provided")
|
||||
}
|
||||
|
||||
cveidMap, err := cveInfo.GetCVEListForImage(image)
|
||||
if err != nil {
|
||||
return &gql_generated.CVEResultForImage{}, err
|
||||
}
|
||||
|
||||
cveids := []*gql_generated.Cve{}
|
||||
|
||||
for id, cveDetail := range cveidMap {
|
||||
vulID := id
|
||||
desc := cveDetail.Description
|
||||
title := cveDetail.Title
|
||||
severity := cveDetail.Severity
|
||||
|
||||
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,
|
||||
PackageList: pkgList,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return &gql_generated.CVEResultForImage{Tag: ©ImgTag, CVEList: cveids}, nil
|
||||
}
|
||||
|
||||
func FilterByTagInfo(tagsInfo []common.TagInfo) repodb.FilterFunc {
|
||||
return func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
||||
manifestDigest := godigest.FromBytes(manifestMeta.ManifestBlob).String()
|
||||
|
||||
for _, tagInfo := range tagsInfo {
|
||||
if tagInfo.Digest.String() == manifestDigest {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func getImageListForCVE(
|
||||
ctx context.Context,
|
||||
cveID string,
|
||||
cveInfo cveinfo.CveInfo,
|
||||
requestedPage *gql_generated.PageInput,
|
||||
repoDB repodb.RepoDB,
|
||||
log log.Logger,
|
||||
) ([]*gql_generated.ImageSummary, error) {
|
||||
// Obtain all repos and tags
|
||||
// Infinite page to make sure we scan all repos in advance, before filtering results
|
||||
// The CVE scan logic is called from here, not in the actual filter,
|
||||
// this is because we shouldn't keep the DB locked while we wait on scan results
|
||||
reposMeta, err := repoDB.GetMultipleRepoMeta(ctx,
|
||||
func(repoMeta repodb.RepoMetadata) bool { return true },
|
||||
repodb.PageInput{Limit: 0, Offset: 0, SortBy: repodb.SortCriteria(gql_generated.SortCriteriaUpdateTime)},
|
||||
)
|
||||
if err != nil {
|
||||
return []*gql_generated.ImageSummary{}, err
|
||||
}
|
||||
|
||||
affectedImages := []common.TagInfo{}
|
||||
|
||||
for _, repoMeta := range reposMeta {
|
||||
repo := repoMeta.Name
|
||||
|
||||
log.Info().Str("repo", repo).Str("CVE", cveID).Msg("extracting list of tags affected by CVE")
|
||||
|
||||
tagsInfo, err := cveInfo.GetImageListForCVE(repo, cveID)
|
||||
if err != nil {
|
||||
log.Error().Str("repo", repo).Str("CVE", cveID).Err(err).
|
||||
Msg("error getting image list for CVE from repo")
|
||||
|
||||
return []*gql_generated.ImageSummary{}, err
|
||||
}
|
||||
|
||||
affectedImages = append(affectedImages, tagsInfo...)
|
||||
}
|
||||
|
||||
imageList := make([]*gql_generated.ImageSummary, 0)
|
||||
|
||||
// We're not interested in other vulnerabilities
|
||||
skip := convert.SkipQGLField{Vulnerabilities: true}
|
||||
|
||||
if requestedPage == nil {
|
||||
requestedPage = &gql_generated.PageInput{}
|
||||
}
|
||||
|
||||
// Actual page requested by user
|
||||
pageInput := repodb.PageInput{
|
||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
||||
SortBy: repodb.SortCriteria(
|
||||
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
|
||||
),
|
||||
}
|
||||
|
||||
// get all repos
|
||||
reposMeta, manifestMetaMap, err := repoDB.FilterTags(ctx, FilterByTagInfo(affectedImages), pageInput)
|
||||
if err != nil {
|
||||
return []*gql_generated.ImageSummary{}, err
|
||||
}
|
||||
|
||||
for _, repoMeta := range reposMeta {
|
||||
imageSummaries := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, skip, cveInfo)
|
||||
|
||||
imageList = append(imageList, imageSummaries...)
|
||||
}
|
||||
|
||||
return imageList, nil
|
||||
}
|
||||
|
||||
func getImageListWithCVEFixed(
|
||||
ctx context.Context,
|
||||
cveID string,
|
||||
repo string,
|
||||
cveInfo cveinfo.CveInfo,
|
||||
requestedPage *gql_generated.PageInput,
|
||||
repoDB repodb.RepoDB,
|
||||
log log.Logger,
|
||||
) ([]*gql_generated.ImageSummary, error) {
|
||||
imageList := make([]*gql_generated.ImageSummary, 0)
|
||||
|
||||
log.Info().Str("repo", repo).Str("CVE", cveID).Msg("extracting list of tags where CVE is fixed")
|
||||
|
||||
tagsInfo, err := cveInfo.GetImageListWithCVEFixed(repo, cveID)
|
||||
if err != nil {
|
||||
log.Error().Str("repo", repo).Str("CVE", cveID).Err(err).
|
||||
Msg("error getting image list with CVE fixed from repo")
|
||||
|
||||
return imageList, err
|
||||
}
|
||||
|
||||
// We're not interested in other vulnerabilities
|
||||
skip := convert.SkipQGLField{Vulnerabilities: true}
|
||||
|
||||
if requestedPage == nil {
|
||||
requestedPage = &gql_generated.PageInput{}
|
||||
}
|
||||
|
||||
// Actual page requested by user
|
||||
pageInput := repodb.PageInput{
|
||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
||||
SortBy: repodb.SortCriteria(
|
||||
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
|
||||
),
|
||||
}
|
||||
|
||||
// get all repos
|
||||
reposMeta, manifestMetaMap, err := repoDB.FilterTags(ctx, FilterByTagInfo(tagsInfo), pageInput)
|
||||
if err != nil {
|
||||
return []*gql_generated.ImageSummary{}, err
|
||||
}
|
||||
|
||||
for _, repoMeta := range reposMeta {
|
||||
if repoMeta.Name != repo {
|
||||
continue
|
||||
}
|
||||
|
||||
imageSummaries := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, skip, cveInfo)
|
||||
imageList = append(imageList, imageSummaries...)
|
||||
}
|
||||
|
||||
return imageList, nil
|
||||
}
|
||||
|
||||
func repoListWithNewestImage(
|
||||
ctx context.Context,
|
||||
cveInfo cveinfo.CveInfo,
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -15,9 +16,11 @@ import (
|
|||
|
||||
"zotregistry.io/zot/pkg/extensions/search/common"
|
||||
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
|
||||
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
|
||||
"zotregistry.io/zot/pkg/extensions/search/gql_generated"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/meta/repodb"
|
||||
bolt "zotregistry.io/zot/pkg/meta/repodb/boltdb-wrapper"
|
||||
localCtx "zotregistry.io/zot/pkg/requestcontext"
|
||||
"zotregistry.io/zot/pkg/storage"
|
||||
"zotregistry.io/zot/pkg/test/mocks"
|
||||
|
@ -1178,17 +1181,19 @@ func TestQueryResolverErrors(t *testing.T) {
|
|||
log := log.NewLogger("debug", "")
|
||||
ctx := context.Background()
|
||||
|
||||
Convey("ImageListForCve olu.GetRepositories() errors", func() {
|
||||
Convey("ImageListForCve error in GetMultipleRepoMeta", func() {
|
||||
resolverConfig := NewResolver(
|
||||
log,
|
||||
storage.StoreController{
|
||||
DefaultStore: mocks.MockedImageStore{
|
||||
GetRepositoriesFn: func() ([]string, error) {
|
||||
return nil, ErrTestError
|
||||
},
|
||||
DefaultStore: mocks.MockedImageStore{},
|
||||
},
|
||||
mocks.RepoDBMock{
|
||||
GetMultipleRepoMetaFn: func(ctx context.Context, filter func(repoMeta repodb.RepoMetadata) bool,
|
||||
requestedPage repodb.PageInput,
|
||||
) ([]repodb.RepoMetadata, error) {
|
||||
return []repodb.RepoMetadata{}, ErrTestError
|
||||
},
|
||||
},
|
||||
mocks.RepoDBMock{},
|
||||
mocks.CveInfoMock{},
|
||||
)
|
||||
|
||||
|
@ -1196,66 +1201,59 @@ func TestQueryResolverErrors(t *testing.T) {
|
|||
resolverConfig,
|
||||
}
|
||||
|
||||
_, err := qr.ImageListForCve(ctx, "id")
|
||||
_, err := qr.ImageListForCve(ctx, "cve1", &gql_generated.PageInput{})
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("ImageListForCve cveInfo.GetImageListForCVE() errors", func() {
|
||||
Convey("ImageListForCve error in FilterTags", func() {
|
||||
resolverConfig := NewResolver(
|
||||
log,
|
||||
storage.StoreController{
|
||||
DefaultStore: mocks.MockedImageStore{
|
||||
GetRepositoriesFn: func() ([]string, error) {
|
||||
return []string{"repo"}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
mocks.RepoDBMock{},
|
||||
mocks.CveInfoMock{
|
||||
GetImageListForCVEFn: func(repo, cveID string) ([]cveinfo.ImageInfoByCVE, error) {
|
||||
return nil, ErrTestError
|
||||
DefaultStore: mocks.MockedImageStore{},
|
||||
},
|
||||
mocks.RepoDBMock{
|
||||
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
||||
requestedPage repodb.PageInput,
|
||||
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error) {
|
||||
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, ErrTestError
|
||||
},
|
||||
},
|
||||
mocks.CveInfoMock{},
|
||||
)
|
||||
|
||||
qr := queryResolver{
|
||||
resolverConfig,
|
||||
}
|
||||
|
||||
_, err := qr.ImageListForCve(ctx, "a")
|
||||
_, err := qr.ImageListForCve(ctx, "cve1", &gql_generated.PageInput{})
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("ImageListForCve olu.GetImageConfigInfo() errors", func() {
|
||||
Convey("ImageListWithCVEFixed error in FilterTags", func() {
|
||||
resolverConfig := NewResolver(
|
||||
log,
|
||||
storage.StoreController{
|
||||
DefaultStore: mocks.MockedImageStore{
|
||||
GetRepositoriesFn: func() ([]string, error) {
|
||||
return []string{"repo"}, nil
|
||||
},
|
||||
GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
|
||||
return nil, ErrTestError
|
||||
},
|
||||
},
|
||||
},
|
||||
mocks.RepoDBMock{},
|
||||
mocks.CveInfoMock{
|
||||
GetImageListForCVEFn: func(repo, cveID string) ([]cveinfo.ImageInfoByCVE, error) {
|
||||
return []cveinfo.ImageInfoByCVE{{}}, nil
|
||||
DefaultStore: mocks.MockedImageStore{},
|
||||
},
|
||||
mocks.RepoDBMock{
|
||||
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
||||
requestedPage repodb.PageInput,
|
||||
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error) {
|
||||
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, ErrTestError
|
||||
},
|
||||
},
|
||||
mocks.CveInfoMock{},
|
||||
)
|
||||
|
||||
qr := queryResolver{
|
||||
resolverConfig,
|
||||
}
|
||||
|
||||
_, err := qr.ImageListForCve(ctx, "a")
|
||||
_, err := qr.ImageListWithCVEFixed(ctx, "cve1", "image", &gql_generated.PageInput{})
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("RepoListWithNewestImage repoListWithNewestImage errors", func() {
|
||||
Convey("RepoListWithNewestImage repoListWithNewestImage() errors mocked StoreController", func() {
|
||||
resolverConfig := NewResolver(
|
||||
log,
|
||||
storage.StoreController{
|
||||
|
@ -1279,68 +1277,7 @@ func TestQueryResolverErrors(t *testing.T) {
|
|||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("ImageListWithCVEFixed olu.GetImageBlobManifest() errors", func() {
|
||||
resolverConfig := NewResolver(
|
||||
log,
|
||||
storage.StoreController{
|
||||
DefaultStore: mocks.MockedImageStore{
|
||||
GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
|
||||
return nil, ErrTestError
|
||||
},
|
||||
},
|
||||
},
|
||||
mocks.RepoDBMock{},
|
||||
mocks.CveInfoMock{
|
||||
GetImageListWithCVEFixedFn: func(repo, cveID string) ([]common.TagInfo, error) {
|
||||
return []common.TagInfo{{}}, nil
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
qr := queryResolver{
|
||||
resolverConfig,
|
||||
}
|
||||
|
||||
_, err := qr.ImageListWithCVEFixed(ctx, "a", "d")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("ImageListWithCVEFixed olu.GetImageConfigInfo() errors", func() {
|
||||
getBlobContentCallCounter := 0
|
||||
|
||||
resolverConfig := NewResolver(
|
||||
log,
|
||||
storage.StoreController{
|
||||
DefaultStore: mocks.MockedImageStore{
|
||||
GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
|
||||
if getBlobContentCallCounter == 1 {
|
||||
getBlobContentCallCounter++
|
||||
|
||||
return nil, ErrTestError
|
||||
}
|
||||
getBlobContentCallCounter++
|
||||
|
||||
return []byte("{}"), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
mocks.RepoDBMock{},
|
||||
mocks.CveInfoMock{
|
||||
GetImageListWithCVEFixedFn: func(repo, cveID string) ([]common.TagInfo, error) {
|
||||
return []common.TagInfo{{}}, nil
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
qr := queryResolver{
|
||||
resolverConfig,
|
||||
}
|
||||
|
||||
_, err := qr.ImageListWithCVEFixed(ctx, "a", "d")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("RepoListWithNewestImage repoListWithNewestImage() errors", func() {
|
||||
Convey("RepoListWithNewestImage repoListWithNewestImage() errors valid StoreController", func() {
|
||||
resolverConfig := NewResolver(
|
||||
log,
|
||||
storage.StoreController{},
|
||||
|
@ -1470,5 +1407,712 @@ func TestQueryResolverErrors(t *testing.T) {
|
|||
_, err := qr.BaseImageList(ctx, "repo:tag")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("GetReferrers error", func() {
|
||||
resolverConfig := NewResolver(
|
||||
log,
|
||||
storage.StoreController{
|
||||
DefaultStore: mocks.MockedImageStore{
|
||||
GetReferrersFn: func(repo string, digest godigest.Digest, artifactType string) (ispec.Index, error) {
|
||||
return ispec.Index{}, ErrTestError
|
||||
},
|
||||
},
|
||||
},
|
||||
mocks.RepoDBMock{},
|
||||
mocks.CveInfoMock{},
|
||||
)
|
||||
|
||||
qr := queryResolver{
|
||||
resolverConfig,
|
||||
}
|
||||
|
||||
_, err := qr.Referrers(ctx, "repo", "", "")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
|
||||
repoDB, err := bolt.NewBoltDBWrapper(bolt.DBParameters{
|
||||
RootDir: t.TempDir(),
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Create repodb data for scannable image with vulnerabilities
|
||||
// Create manifets metadata first
|
||||
timeStamp1 := time.Date(2008, 1, 1, 12, 0, 0, 0, time.UTC)
|
||||
|
||||
configBlob1, err := json.Marshal(ispec.Image{
|
||||
Created: &timeStamp1,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
manifestBlob1, err := json.Marshal(ispec.Manifest{
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: ispec.MediaTypeImageConfig,
|
||||
Size: 0,
|
||||
Digest: godigest.FromBytes(configBlob1),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: ispec.MediaTypeImageLayerGzip,
|
||||
Size: 0,
|
||||
Digest: godigest.NewDigestFromEncoded(godigest.SHA256, "digest"),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
repoMeta1 := repodb.ManifestData{
|
||||
ManifestBlob: manifestBlob1,
|
||||
ConfigBlob: configBlob1,
|
||||
}
|
||||
|
||||
digest1 := godigest.FromBytes(manifestBlob1)
|
||||
|
||||
err = repoDB.SetManifestData(digest1, repoMeta1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
timeStamp2 := time.Date(2009, 1, 1, 12, 0, 0, 0, time.UTC)
|
||||
|
||||
configBlob2, err := json.Marshal(ispec.Image{
|
||||
Created: &timeStamp2,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
manifestBlob2, err := json.Marshal(ispec.Manifest{
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: ispec.MediaTypeImageConfig,
|
||||
Size: 0,
|
||||
Digest: godigest.FromBytes(configBlob2),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: ispec.MediaTypeImageLayerGzip,
|
||||
Size: 0,
|
||||
Digest: godigest.NewDigestFromEncoded(godigest.SHA256, "digest"),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
repoMeta2 := repodb.ManifestData{
|
||||
ManifestBlob: manifestBlob2,
|
||||
ConfigBlob: configBlob2,
|
||||
}
|
||||
|
||||
digest2 := godigest.FromBytes(manifestBlob2)
|
||||
|
||||
err = repoDB.SetManifestData(digest2, repoMeta2)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
timeStamp3 := time.Date(2010, 1, 1, 12, 0, 0, 0, time.UTC)
|
||||
|
||||
configBlob3, err := json.Marshal(ispec.Image{
|
||||
Created: &timeStamp3,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
manifestBlob3, err := json.Marshal(ispec.Manifest{
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: ispec.MediaTypeImageConfig,
|
||||
Size: 0,
|
||||
Digest: godigest.FromBytes(configBlob3),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: ispec.MediaTypeImageLayerGzip,
|
||||
Size: 0,
|
||||
Digest: godigest.NewDigestFromEncoded(godigest.SHA256, "digest"),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
repoMeta3 := repodb.ManifestData{
|
||||
ManifestBlob: manifestBlob3,
|
||||
ConfigBlob: configBlob3,
|
||||
}
|
||||
|
||||
digest3 := godigest.FromBytes(manifestBlob3)
|
||||
|
||||
err = repoDB.SetManifestData(digest3, repoMeta3)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Create the repo metadata using previously defined manifests
|
||||
tagsMap := map[string]godigest.Digest{}
|
||||
tagsMap["repo1:1.0.0"] = digest1
|
||||
tagsMap["repo1:1.0.1"] = digest2
|
||||
tagsMap["repo1:1.1.0"] = digest3
|
||||
tagsMap["repo1:latest"] = digest3
|
||||
tagsMap["repo2:2.0.0"] = digest1
|
||||
tagsMap["repo2:2.0.1"] = digest2
|
||||
tagsMap["repo2:2.1.0"] = digest3
|
||||
tagsMap["repo2:latest"] = digest3
|
||||
tagsMap["repo3:3.0.1"] = digest2
|
||||
tagsMap["repo3:3.1.0"] = digest3
|
||||
tagsMap["repo3:latest"] = digest3
|
||||
|
||||
for image, digest := range tagsMap {
|
||||
repo, tag := common.GetImageDirAndTag(image)
|
||||
|
||||
err := repoDB.SetRepoTag(repo, tag, digest, ispec.MediaTypeImageManifest)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the repo metadata using previously defined manifests
|
||||
|
||||
// RepoDB loaded with initial data, mock the scanner
|
||||
severities := map[string]int{
|
||||
"UNKNOWN": 0,
|
||||
"LOW": 1,
|
||||
"MEDIUM": 2,
|
||||
"HIGH": 3,
|
||||
"CRITICAL": 4,
|
||||
}
|
||||
|
||||
// Setup test CVE data in mock scanner
|
||||
scanner := mocks.CveScannerMock{
|
||||
ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) {
|
||||
digest, ok := tagsMap[image]
|
||||
if !ok {
|
||||
return map[string]cvemodel.CVE{}, nil
|
||||
}
|
||||
|
||||
if digest.String() == digest1.String() {
|
||||
return map[string]cvemodel.CVE{
|
||||
"CVE1": {
|
||||
ID: "CVE1",
|
||||
Severity: "HIGH",
|
||||
Title: "Title CVE1",
|
||||
Description: "Description CVE1",
|
||||
},
|
||||
"CVE2": {
|
||||
ID: "CVE2",
|
||||
Severity: "MEDIM",
|
||||
Title: "Title CVE2",
|
||||
Description: "Description CVE2",
|
||||
},
|
||||
"CVE3": {
|
||||
ID: "CVE3",
|
||||
Severity: "LOW",
|
||||
Title: "Title CVE3",
|
||||
Description: "Description CVE3",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
if digest.String() == digest2.String() {
|
||||
return map[string]cvemodel.CVE{
|
||||
"CVE2": {
|
||||
ID: "CVE2",
|
||||
Severity: "MEDIUM",
|
||||
Title: "Title CVE2",
|
||||
Description: "Description CVE2",
|
||||
},
|
||||
"CVE3": {
|
||||
ID: "CVE3",
|
||||
Severity: "LOW",
|
||||
Title: "Title CVE3",
|
||||
Description: "Description CVE3",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
if digest.String() == digest3.String() {
|
||||
return map[string]cvemodel.CVE{
|
||||
"CVE3": {
|
||||
ID: "CVE3",
|
||||
Severity: "LOW",
|
||||
Title: "Title CVE3",
|
||||
Description: "Description CVE3",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// By default the image has no vulnerabilities
|
||||
return map[string]cvemodel.CVE{}, nil
|
||||
},
|
||||
CompareSeveritiesFn: func(severity1, severity2 string) int {
|
||||
return severities[severity2] - severities[severity1]
|
||||
},
|
||||
}
|
||||
|
||||
log := log.NewLogger("debug", "")
|
||||
|
||||
cveInfo := &cveinfo.BaseCveInfo{
|
||||
Log: log,
|
||||
Scanner: scanner,
|
||||
RepoDB: repoDB,
|
||||
}
|
||||
|
||||
Convey("Get CVE list for image ", t, func() {
|
||||
Convey("Unpaginated request to get all CVEs in an image", func() {
|
||||
// CVE pagination will be implemented later
|
||||
|
||||
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
|
||||
graphql.DefaultRecover)
|
||||
|
||||
cveResult, err := getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, log)
|
||||
So(err, ShouldBeNil)
|
||||
So(*cveResult.Tag, ShouldEqual, "1.0.0")
|
||||
|
||||
expectedCves := []string{"CVE1", "CVE2", "CVE3"}
|
||||
So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
|
||||
|
||||
for _, cve := range cveResult.CVEList {
|
||||
So(expectedCves, ShouldContain, *cve.ID)
|
||||
}
|
||||
|
||||
cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.1", cveInfo, log)
|
||||
So(err, ShouldBeNil)
|
||||
So(*cveResult.Tag, ShouldEqual, "1.0.1")
|
||||
|
||||
expectedCves = []string{"CVE2", "CVE3"}
|
||||
So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
|
||||
|
||||
for _, cve := range cveResult.CVEList {
|
||||
So(expectedCves, ShouldContain, *cve.ID)
|
||||
}
|
||||
|
||||
cveResult, err = getCVEListForImage(responseContext, "repo1:1.1.0", cveInfo, log)
|
||||
So(err, ShouldBeNil)
|
||||
So(*cveResult.Tag, ShouldEqual, "1.1.0")
|
||||
|
||||
expectedCves = []string{"CVE3"}
|
||||
So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
|
||||
|
||||
for _, cve := range cveResult.CVEList {
|
||||
So(expectedCves, ShouldContain, *cve.ID)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Get a list of images affected by a particular CVE ", t, func() {
|
||||
Convey("Unpaginated request", func() {
|
||||
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
|
||||
graphql.DefaultRecover)
|
||||
|
||||
images, err := getImageListForCVE(responseContext, "CVE1", cveInfo, nil, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages := []string{
|
||||
"repo1:1.0.0",
|
||||
"repo2:2.0.0",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
|
||||
images, err = getImageListForCVE(responseContext, "CVE2", cveInfo, nil, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages = []string{
|
||||
"repo1:1.0.0", "repo1:1.0.1",
|
||||
"repo2:2.0.0", "repo2:2.0.1",
|
||||
"repo3:3.0.1",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
|
||||
images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, nil, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages = []string{
|
||||
"repo1:1.0.0", "repo1:1.0.1", "repo1:1.1.0", "repo1:latest",
|
||||
"repo2:2.0.0", "repo2:2.0.1", "repo2:2.1.0", "repo2:latest",
|
||||
"repo3:3.0.1", "repo3:3.1.0", "repo3:latest",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("Paginated requests", func() {
|
||||
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
|
||||
graphql.DefaultRecover,
|
||||
)
|
||||
|
||||
pageInput := getPageInput(1, 0)
|
||||
|
||||
images, err := getImageListForCVE(responseContext, "CVE1", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages := []string{
|
||||
"repo1:1.0.0",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
|
||||
pageInput = getPageInput(1, 1)
|
||||
|
||||
images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages = []string{
|
||||
"repo2:2.0.0",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
|
||||
pageInput = getPageInput(1, 2)
|
||||
|
||||
images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(images), ShouldEqual, 0)
|
||||
|
||||
pageInput = getPageInput(1, 5)
|
||||
|
||||
images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(images), ShouldEqual, 0)
|
||||
|
||||
pageInput = getPageInput(2, 0)
|
||||
|
||||
images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages = []string{
|
||||
"repo1:1.0.0",
|
||||
"repo2:2.0.0",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
|
||||
pageInput = getPageInput(5, 0)
|
||||
|
||||
images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages = []string{
|
||||
"repo1:1.0.0",
|
||||
"repo2:2.0.0",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
|
||||
pageInput = getPageInput(5, 1)
|
||||
|
||||
images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages = []string{
|
||||
"repo2:2.0.0",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
|
||||
pageInput = getPageInput(5, 2)
|
||||
|
||||
images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(images), ShouldEqual, 0)
|
||||
|
||||
pageInput = getPageInput(5, 5)
|
||||
|
||||
images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(images), ShouldEqual, 0)
|
||||
|
||||
pageInput = getPageInput(5, 0)
|
||||
|
||||
images, err = getImageListForCVE(responseContext, "CVE2", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages = []string{
|
||||
"repo1:1.0.0", "repo1:1.0.1",
|
||||
"repo2:2.0.0", "repo2:2.0.1",
|
||||
"repo3:3.0.1",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
|
||||
pageInput = getPageInput(5, 3)
|
||||
|
||||
images, err = getImageListForCVE(responseContext, "CVE2", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages = []string{
|
||||
"repo2:2.0.1",
|
||||
"repo3:3.0.1",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
|
||||
pageInput = getPageInput(5, 0)
|
||||
|
||||
images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages = []string{
|
||||
"repo1:1.0.0", "repo1:1.0.1", "repo1:1.1.0", "repo1:latest",
|
||||
"repo2:2.0.0",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
|
||||
pageInput = getPageInput(5, 5)
|
||||
|
||||
images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages = []string{
|
||||
"repo2:2.0.1", "repo2:2.1.0", "repo2:latest",
|
||||
"repo3:3.0.1", "repo3:3.1.0",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
|
||||
pageInput = getPageInput(5, 10)
|
||||
|
||||
images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages = []string{
|
||||
"repo3:latest",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Get a list of images where a particular CVE is fixed", t, func() {
|
||||
Convey("Unpaginated request", func() {
|
||||
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
|
||||
graphql.DefaultRecover)
|
||||
|
||||
images, err := getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages := []string{
|
||||
"repo1:1.0.1", "repo1:1.1.0", "repo1:latest",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
|
||||
images, err = getImageListWithCVEFixed(responseContext, "CVE2", "repo1", cveInfo, nil, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages = []string{
|
||||
"repo1:1.1.0", "repo1:latest",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
|
||||
images, err = getImageListWithCVEFixed(responseContext, "CVE3", "repo1", cveInfo, nil, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(images), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Paginated requests", func() {
|
||||
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
|
||||
graphql.DefaultRecover,
|
||||
)
|
||||
|
||||
pageInput := getPageInput(1, 0)
|
||||
|
||||
images, err := getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages := []string{
|
||||
"repo1:1.0.1",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
|
||||
pageInput = getPageInput(1, 1)
|
||||
|
||||
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages = []string{
|
||||
"repo1:1.1.0",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
|
||||
pageInput = getPageInput(1, 2)
|
||||
|
||||
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages = []string{
|
||||
"repo1:latest",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
|
||||
pageInput = getPageInput(1, 3)
|
||||
|
||||
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(images), ShouldEqual, 0)
|
||||
|
||||
pageInput = getPageInput(1, 10)
|
||||
|
||||
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(images), ShouldEqual, 0)
|
||||
|
||||
pageInput = getPageInput(2, 0)
|
||||
|
||||
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages = []string{
|
||||
"repo1:1.0.1", "repo1:1.1.0",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
|
||||
pageInput = getPageInput(2, 1)
|
||||
|
||||
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages = []string{
|
||||
"repo1:1.1.0", "repo1:latest",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
|
||||
pageInput = getPageInput(2, 2)
|
||||
|
||||
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages = []string{
|
||||
"repo1:latest",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
|
||||
pageInput = getPageInput(5, 0)
|
||||
|
||||
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages = []string{
|
||||
"repo1:1.0.1", "repo1:1.1.0", "repo1:latest",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
|
||||
pageInput = getPageInput(5, 0)
|
||||
|
||||
images, err = getImageListWithCVEFixed(responseContext, "CVE2", "repo1", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
expectedImages = []string{
|
||||
"repo1:1.1.0", "repo1:latest",
|
||||
}
|
||||
So(len(images), ShouldEqual, len(expectedImages))
|
||||
|
||||
for _, image := range images {
|
||||
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
|
||||
}
|
||||
|
||||
pageInput = getPageInput(5, 2)
|
||||
|
||||
images, err = getImageListWithCVEFixed(responseContext, "CVE2", "repo1", cveInfo, pageInput, repoDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(images), ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func getPageInput(limit int, offset int) *gql_generated.PageInput {
|
||||
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
|
||||
|
||||
return &gql_generated.PageInput{
|
||||
Limit: &limit,
|
||||
Offset: &offset,
|
||||
SortBy: &sortCriteria,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -207,12 +207,12 @@ type Query {
|
|||
"""
|
||||
Returns a list of images vulnerable to the CVE of the specified ID
|
||||
"""
|
||||
ImageListForCVE(id: String!): [ImageSummary!]
|
||||
ImageListForCVE(id: String!, requestedPage: PageInput): [ImageSummary!]
|
||||
|
||||
"""
|
||||
Returns a list of images that are no longer vulnerable to the CVE of the specified ID, from the specified image (repo)
|
||||
"""
|
||||
ImageListWithCVEFixed(id: String!, image: String!): [ImageSummary!]
|
||||
ImageListWithCVEFixed(id: String!, image: String!, requestedPage: PageInput): [ImageSummary!]
|
||||
|
||||
"""
|
||||
Returns a list of images which contain the specified digest
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/extensions/search/common"
|
||||
"zotregistry.io/zot/pkg/extensions/search/convert"
|
||||
"zotregistry.io/zot/pkg/extensions/search/gql_generated"
|
||||
)
|
||||
|
||||
|
@ -20,146 +19,25 @@ func (r *queryResolver) CVEListForImage(ctx context.Context, image string) (*gql
|
|||
return &gql_generated.CVEResultForImage{}, zerr.ErrCVESearchDisabled
|
||||
}
|
||||
|
||||
_, copyImgTag := common.GetImageDirAndTag(image)
|
||||
|
||||
if copyImgTag == "" {
|
||||
return &gql_generated.CVEResultForImage{}, gqlerror.Errorf("no reference provided")
|
||||
}
|
||||
|
||||
cveidMap, err := r.cveInfo.GetCVEListForImage(image)
|
||||
if err != nil {
|
||||
return &gql_generated.CVEResultForImage{}, err
|
||||
}
|
||||
|
||||
cveids := []*gql_generated.Cve{}
|
||||
|
||||
for id, cveDetail := range cveidMap {
|
||||
vulID := id
|
||||
desc := cveDetail.Description
|
||||
title := cveDetail.Title
|
||||
severity := cveDetail.Severity
|
||||
|
||||
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,
|
||||
PackageList: pkgList,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return &gql_generated.CVEResultForImage{Tag: ©ImgTag, CVEList: cveids}, nil
|
||||
return getCVEListForImage(ctx, image, r.cveInfo, r.log)
|
||||
}
|
||||
|
||||
// ImageListForCve is the resolver for the ImageListForCVE field.
|
||||
func (r *queryResolver) ImageListForCve(ctx context.Context, id string) ([]*gql_generated.ImageSummary, error) {
|
||||
olu := common.NewBaseOciLayoutUtils(r.storeController, r.log)
|
||||
affectedImages := []*gql_generated.ImageSummary{}
|
||||
|
||||
func (r *queryResolver) ImageListForCve(ctx context.Context, id string, requestedPage *gql_generated.PageInput) ([]*gql_generated.ImageSummary, error) {
|
||||
if r.cveInfo == nil {
|
||||
return affectedImages, zerr.ErrCVESearchDisabled
|
||||
return []*gql_generated.ImageSummary{}, zerr.ErrCVESearchDisabled
|
||||
}
|
||||
|
||||
r.log.Info().Msg("extracting repositories")
|
||||
repoList, err := olu.GetRepositories()
|
||||
if err != nil { //nolint: wsl
|
||||
r.log.Error().Err(err).Msg("unable to search repositories")
|
||||
|
||||
return affectedImages, err
|
||||
}
|
||||
|
||||
r.log.Info().Msg("scanning each repository")
|
||||
|
||||
for _, repo := range repoList {
|
||||
r.log.Info().Str("repo", repo).Msg("extracting list of tags available in image repo")
|
||||
|
||||
imageListByCVE, err := r.cveInfo.GetImageListForCVE(repo, id)
|
||||
if err != nil {
|
||||
r.log.Error().Str("repo", repo).Str("CVE", id).Err(err).
|
||||
Msg("error getting image list for CVE from repo")
|
||||
|
||||
return affectedImages, err
|
||||
}
|
||||
|
||||
for _, imageByCVE := range imageListByCVE {
|
||||
imageConfig, err := olu.GetImageConfigInfo(repo, imageByCVE.Digest)
|
||||
if err != nil {
|
||||
return affectedImages, err
|
||||
}
|
||||
|
||||
isSigned := olu.CheckManifestSignature(repo, imageByCVE.Digest)
|
||||
imageInfo := convert.BuildImageInfo(
|
||||
repo, imageByCVE.Tag,
|
||||
imageByCVE.Digest,
|
||||
imageByCVE.Manifest,
|
||||
imageConfig,
|
||||
isSigned,
|
||||
)
|
||||
|
||||
affectedImages = append(
|
||||
affectedImages,
|
||||
imageInfo,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return affectedImages, nil
|
||||
return getImageListForCVE(ctx, id, r.cveInfo, requestedPage, r.repoDB, r.log)
|
||||
}
|
||||
|
||||
// ImageListWithCVEFixed is the resolver for the ImageListWithCVEFixed field.
|
||||
func (r *queryResolver) ImageListWithCVEFixed(ctx context.Context, id string, image string) ([]*gql_generated.ImageSummary, error) {
|
||||
olu := common.NewBaseOciLayoutUtils(r.storeController, r.log)
|
||||
|
||||
unaffectedImages := []*gql_generated.ImageSummary{}
|
||||
|
||||
func (r *queryResolver) ImageListWithCVEFixed(ctx context.Context, id string, image string, requestedPage *gql_generated.PageInput) ([]*gql_generated.ImageSummary, error) {
|
||||
if r.cveInfo == nil {
|
||||
return unaffectedImages, zerr.ErrCVESearchDisabled
|
||||
return []*gql_generated.ImageSummary{}, zerr.ErrCVESearchDisabled
|
||||
}
|
||||
|
||||
tagsInfo, err := r.cveInfo.GetImageListWithCVEFixed(image, id)
|
||||
if err != nil {
|
||||
return unaffectedImages, err
|
||||
}
|
||||
|
||||
for _, tag := range tagsInfo {
|
||||
digest := tag.Digest
|
||||
|
||||
manifest, err := olu.GetImageBlobManifest(image, digest)
|
||||
if err != nil {
|
||||
r.log.Error().Err(err).Str("repo", image).Str("digest", tag.Digest.String()).
|
||||
Msg("extension api: error reading manifest")
|
||||
|
||||
return unaffectedImages, err
|
||||
}
|
||||
|
||||
imageConfig, err := olu.GetImageConfigInfo(image, digest)
|
||||
if err != nil {
|
||||
return []*gql_generated.ImageSummary{}, err
|
||||
}
|
||||
|
||||
isSigned := olu.CheckManifestSignature(image, digest)
|
||||
imageInfo := convert.BuildImageInfo(image, tag.Name, digest, manifest, imageConfig, isSigned)
|
||||
|
||||
unaffectedImages = append(unaffectedImages, imageInfo)
|
||||
}
|
||||
|
||||
return unaffectedImages, nil
|
||||
return getImageListWithCVEFixed(ctx, id, image, r.cveInfo, requestedPage, r.repoDB, r.log)
|
||||
}
|
||||
|
||||
// ImageListForDigest is the resolver for the ImageListForDigest field.
|
||||
|
|
|
@ -7,19 +7,19 @@ import (
|
|||
)
|
||||
|
||||
type CveInfoMock struct {
|
||||
GetImageListForCVEFn func(repo, cveID string) ([]cveinfo.ImageInfoByCVE, error)
|
||||
GetImageListForCVEFn func(repo, cveID string) ([]common.TagInfo, error)
|
||||
GetImageListWithCVEFixedFn func(repo, cveID string) ([]common.TagInfo, error)
|
||||
GetCVEListForImageFn func(image string) (map[string]cvemodel.CVE, error)
|
||||
GetCVESummaryForImageFn func(image string) (cveinfo.ImageCVESummary, error)
|
||||
UpdateDBFn func() error
|
||||
}
|
||||
|
||||
func (cveInfo CveInfoMock) GetImageListForCVE(repo, cveID string) ([]cveinfo.ImageInfoByCVE, error) {
|
||||
func (cveInfo CveInfoMock) GetImageListForCVE(repo, cveID string) ([]common.TagInfo, error) {
|
||||
if cveInfo.GetImageListForCVEFn != nil {
|
||||
return cveInfo.GetImageListForCVEFn(repo, cveID)
|
||||
}
|
||||
|
||||
return []cveinfo.ImageInfoByCVE{}, nil
|
||||
return []common.TagInfo{}, nil
|
||||
}
|
||||
|
||||
func (cveInfo CveInfoMock) GetImageListWithCVEFixed(repo, cveID string) ([]common.TagInfo, error) {
|
||||
|
|
Loading…
Add table
Reference in a new issue