feat(cve): implemented trivy image scan for multiarch images (#1510)

Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
This commit is contained in:
LaurentiuNiculae
2023-07-06 11:36:26 +03:00
committed by GitHub
parent 96d9d318df
commit 0a04b2a4ed
32 changed files with 1617 additions and 370 deletions
+137 -8
View File
@@ -20,6 +20,7 @@ import (
"os"
"path"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
@@ -45,6 +46,7 @@ import (
"oras.land/oras-go/v2/registry/remote"
"oras.land/oras-go/v2/registry/remote/auth"
zerr "zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/meta/repodb"
"zotregistry.io/zot/pkg/storage"
"zotregistry.io/zot/pkg/test/inject"
@@ -56,6 +58,8 @@ const (
SleepTime = 100 * time.Millisecond
)
var vulnerableLayer []byte //nolint: gochecknoglobals
var NotationPathLock = new(sync.Mutex) //nolint: gochecknoglobals
// which: manifest, config, layer
@@ -604,15 +608,8 @@ func GetRandomImageComponents(layerSize int) (ispec.Image, [][]byte, ispec.Manif
configDigest := godigest.FromBytes(configBlob)
layer := make([]byte, layerSize)
_, err = rand.Read(layer)
if err != nil {
return ispec.Image{}, [][]byte{}, ispec.Manifest{}, err
}
layers := [][]byte{
layer,
GetRandomLayer(layerSize),
}
schemaVersion := 2
@@ -639,6 +636,138 @@ func GetRandomImageComponents(layerSize int) (ispec.Image, [][]byte, ispec.Manif
return config, layers, manifest, nil
}
// These are the 2 vulnerabilities found for the returned image by the GetVulnImage function.
const (
Vulnerability1ID = "CVE-2023-2650"
Vulnerability2ID = "CVE-2023-1255"
)
func GetVulnImage(ref string) (Image, error) {
const skipStackFrame = 2
vulnerableLayer, err := GetLayerWithVulnerability(skipStackFrame)
if err != nil {
return Image{}, err
}
vulnerableConfig := ispec.Image{
Platform: ispec.Platform{
Architecture: "amd64",
OS: "linux",
},
Config: ispec.ImageConfig{
Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
Cmd: []string{"/bin/sh"},
},
RootFS: ispec.RootFS{
Type: "layers",
DiffIDs: []godigest.Digest{"sha256:f1417ff83b319fbdae6dd9cd6d8c9c88002dcd75ecf6ec201c8c6894681cf2b5"},
},
}
img, err := GetImageWithComponents(
vulnerableConfig,
[][]byte{
vulnerableLayer,
})
if err != nil {
return Image{}, err
}
img.Reference = ref
return img, err
}
func GetVulnImageWithConfig(ref string, config ispec.Image) (Image, error) {
const skipStackFrame = 2
vulnerableLayer, err := GetLayerWithVulnerability(skipStackFrame)
if err != nil {
return Image{}, err
}
vulnerableConfig := ispec.Image{
Platform: config.Platform,
Config: config.Config,
RootFS: ispec.RootFS{
Type: "layers",
DiffIDs: []godigest.Digest{"sha256:f1417ff83b319fbdae6dd9cd6d8c9c88002dcd75ecf6ec201c8c6894681cf2b5"},
},
Created: config.Created,
History: config.History,
}
img, err := GetImageWithComponents(
vulnerableConfig,
[][]byte{
vulnerableLayer,
})
if err != nil {
return Image{}, err
}
img.Reference = ref
return img, err
}
func GetLayerWithVulnerability(skip int) ([]byte, error) {
if vulnerableLayer != nil {
return vulnerableLayer, nil
}
_, b, _, ok := runtime.Caller(skip)
if !ok {
return []byte{}, zerr.ErrCallerInfo
}
absoluteCallerpath := filepath.Dir(b)
fmt.Println(absoluteCallerpath)
// we know pkg folder inside zot must exist, and since all tests are called from within pkg we'll use it as reference
relCallerPath := absoluteCallerpath[strings.LastIndex(absoluteCallerpath, "pkg"):]
relCallerSlice := strings.Split(relCallerPath, string(os.PathSeparator))
fmt.Println(relCallerPath, relCallerSlice)
// we'll calculate how many folder we should go back to reach the root of the zot folder relative
// to the callers position
backPathSlice := make([]string, len(relCallerSlice))
for i := 0; i < len(backPathSlice); i++ {
backPathSlice[i] = ".."
}
backPath := filepath.Join(backPathSlice...)
// this is the path of the blob relative to the root of the zot folder
vulnBlobPath := "test/data/alpine/blobs/sha256/f56be85fc22e46face30e2c3de3f7fe7c15f8fd7c4e5add29d7f64b87abdaa09"
var err error
x, _ := filepath.Abs(filepath.Join(backPath, vulnBlobPath))
_ = x
vulnerableLayer, err = os.ReadFile(filepath.Join(backPath, vulnBlobPath)) //nolint: lll
if err != nil {
return nil, err
}
return vulnerableLayer, nil
}
func GetRandomLayer(size int) []byte {
layer := make([]byte, size)
_, err := rand.Read(layer)
if err != nil {
return layer
}
return layer
}
func GetRandomImage(reference string) (Image, error) {
const layerSize = 20
+25 -6
View File
@@ -2,17 +2,18 @@ package mocks
import (
"zotregistry.io/zot/pkg/common"
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
)
type CveInfoMock struct {
GetImageListForCVEFn func(repo, cveID string) ([]cvemodel.TagInfo, error)
GetImageListWithCVEFixedFn func(repo, cveID string) ([]cvemodel.TagInfo, error)
GetCVEListForImageFn func(repo string, reference string, searchedCVE string, pageInput cveinfo.PageInput,
GetCVEListForImageFn func(repo string, reference string, searchedCVE string, pageInput cvemodel.PageInput,
) ([]cvemodel.CVE, common.PageInfo, error)
GetCVESummaryForImageFn func(repo string, reference string,
) (cveinfo.ImageCVESummary, error)
) (cvemodel.ImageCVESummary, error)
GetCVESummaryForImageMediaFn func(repo string, digest, mediaType string,
) (cvemodel.ImageCVESummary, error)
CompareSeveritiesFn func(severity1, severity2 string) int
UpdateDBFn func() error
}
@@ -34,7 +35,7 @@ func (cveInfo CveInfoMock) GetImageListWithCVEFixed(repo, cveID string) ([]cvemo
}
func (cveInfo CveInfoMock) GetCVEListForImage(repo string, reference string,
searchedCVE string, pageInput cveinfo.PageInput,
searchedCVE string, pageInput cvemodel.PageInput,
) (
[]cvemodel.CVE,
common.PageInfo,
@@ -48,12 +49,21 @@ func (cveInfo CveInfoMock) GetCVEListForImage(repo string, reference string,
}
func (cveInfo CveInfoMock) GetCVESummaryForImage(repo string, reference string,
) (cveinfo.ImageCVESummary, error) {
) (cvemodel.ImageCVESummary, error) {
if cveInfo.GetCVESummaryForImageFn != nil {
return cveInfo.GetCVESummaryForImageFn(repo, reference)
}
return cveinfo.ImageCVESummary{}, nil
return cvemodel.ImageCVESummary{}, nil
}
func (cveInfo CveInfoMock) GetCVESummaryForImageMedia(repo, digest, mediaType string,
) (cvemodel.ImageCVESummary, error) {
if cveInfo.GetCVESummaryForImageMediaFn != nil {
return cveInfo.GetCVESummaryForImageMediaFn(repo, digest, mediaType)
}
return cvemodel.ImageCVESummary{}, nil
}
func (cveInfo CveInfoMock) CompareSeverities(severity1, severity2 string) int {
@@ -74,6 +84,7 @@ func (cveInfo CveInfoMock) UpdateDB() error {
type CveScannerMock struct {
IsImageFormatScannableFn func(repo string, reference string) (bool, error)
IsImageMediaScannableFn func(repo string, digest, mediaType string) (bool, error)
ScanImageFn func(image string) (map[string]cvemodel.CVE, error)
CompareSeveritiesFn func(severity1, severity2 string) int
UpdateDBFn func() error
@@ -87,6 +98,14 @@ func (scanner CveScannerMock) IsImageFormatScannable(repo string, reference stri
return true, nil
}
func (scanner CveScannerMock) IsImageMediaScannable(repo string, digest, mediaType string) (bool, error) {
if scanner.IsImageMediaScannableFn != nil {
return scanner.IsImageMediaScannableFn(repo, digest, mediaType)
}
return true, nil
}
func (scanner CveScannerMock) ScanImage(image string) (map[string]cvemodel.CVE, error) {
if scanner.ScanImageFn != nil {
return scanner.ScanImageFn(image)
+1 -1
View File
@@ -216,7 +216,7 @@ func (olu BaseOciLayoutUtils) GetImageTagsWithTimestamp(repo string) ([]cvemodel
tagsInfo = append(tagsInfo,
cvemodel.TagInfo{
Name: val,
Tag: val,
Timestamp: timeStamp,
Descriptor: cvemodel.Descriptor{
Digest: digest,
+5 -5
View File
@@ -450,7 +450,7 @@ func TestTagsInfo(t *testing.T) {
allTags := make([]cvemodel.TagInfo, 0)
firstTag := cvemodel.TagInfo{
Name: "1.0.0",
Tag: "1.0.0",
Descriptor: cvemodel.Descriptor{
Digest: "sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb",
MediaType: ispec.MediaTypeImageManifest,
@@ -458,7 +458,7 @@ func TestTagsInfo(t *testing.T) {
Timestamp: time.Now(),
}
secondTag := cvemodel.TagInfo{
Name: "1.0.1",
Tag: "1.0.1",
Descriptor: cvemodel.Descriptor{
Digest: "sha256:eca04f027f414362596f2632746d8a179362170b9ac9af772011fedcc3877ebb",
MediaType: ispec.MediaTypeImageManifest,
@@ -466,7 +466,7 @@ func TestTagsInfo(t *testing.T) {
Timestamp: time.Now(),
}
thirdTag := cvemodel.TagInfo{
Name: "1.0.2",
Tag: "1.0.2",
Descriptor: cvemodel.Descriptor{
Digest: "sha256:eca04f027f414362596f2632746d8a170362170b9ac9af772011fedcc3877ebb",
MediaType: ispec.MediaTypeImageManifest,
@@ -474,7 +474,7 @@ func TestTagsInfo(t *testing.T) {
Timestamp: time.Now(),
}
fourthTag := cvemodel.TagInfo{
Name: "1.0.3",
Tag: "1.0.3",
Descriptor: cvemodel.Descriptor{
Digest: "sha256:eca04f027f414362596f2632746d8a171362170b9ac9af772011fedcc3877ebb",
MediaType: ispec.MediaTypeImageManifest,
@@ -485,6 +485,6 @@ func TestTagsInfo(t *testing.T) {
allTags = append(allTags, firstTag, secondTag, thirdTag, fourthTag)
latestTag := ocilayout.GetLatestTag(allTags)
So(latestTag.Name, ShouldEqual, "1.0.3")
So(latestTag.Tag, ShouldEqual, "1.0.3")
})
}