refactor(scrub): replace umoci logic in scrub implementation (#1845)

- implement scrub also for S3 storage by replacing umoci
- change scrub implementation for ImageIndex
- take the `Subject` into consideration when running scrub
- remove test code relying on the umoci library. Since we started
relying on images in test/data, and we create our own images using
go code we can obtain digests by other means. (cherry picked from commit 489d4e2d23c1b4e48799283f8281024bbef6123f)

Signed-off-by: Andrei Aaron <aaaron@luxoft.com>
Signed-off-by: Andreea-Lupu <andreealupu1470@yahoo.com>
This commit is contained in:
Andreea Lupu
2023-09-26 21:02:11 +03:00
committed by GitHub
parent 510b7a2e16
commit 92e382ce39
12 changed files with 469 additions and 411 deletions
+5 -5
View File
@@ -4350,7 +4350,7 @@ func TestInvalidCases(t *testing.T) {
panic(err)
}
digest := test.GetTestBlobDigest("zot-cve-test", "config").String()
digest := godigest.FromString("dummy").String()
name := "zot-c-test"
client := resty.New()
@@ -4449,7 +4449,8 @@ func TestCrossRepoMount(t *testing.T) {
ctlr.Config.Storage.RemoteCache = false
ctlr.Config.Storage.Dedupe = false
err := test.WriteImageToFileSystem(CreateDefaultImage(), "zot-cve-test", "test", storage.StoreController{
image := CreateDefaultImage()
err := test.WriteImageToFileSystem(image, "zot-cve-test", "test", storage.StoreController{
DefaultStore: test.GetDefaultImageStore(dir, ctlr.Log),
})
So(err, ShouldBeNil)
@@ -4459,8 +4460,7 @@ func TestCrossRepoMount(t *testing.T) {
params := make(map[string]string)
var manifestDigest godigest.Digest
manifestDigest, _, _ = test.GetOciLayoutDigests(path.Join(dir, "zot-cve-test"))
manifestDigest := image.ManifestDescriptor.Digest
dgst := manifestDigest
name := "zot-cve-test"
@@ -4487,7 +4487,7 @@ func TestCrossRepoMount(t *testing.T) {
baseURL, constants.RoutePrefix, constants.Blobs, constants.Uploads))
incorrectParams := make(map[string]string)
incorrectParams["mount"] = test.GetTestBlobDigest("zot-cve-test", "manifest").String()
incorrectParams["mount"] = godigest.FromString("dummy").String()
incorrectParams["from"] = "zot-x-test"
postResponse, err = client.R().
+8 -14
View File
@@ -28,25 +28,19 @@ func EnableScrubExtension(config *config.Config, log log.Logger, storeController
log.Warn().Msg("Scrub interval set to too-short interval < 2h, changing scrub duration to 2 hours and continuing.") //nolint:lll // gofumpt conflicts with lll
}
// is local imagestore (because of umoci dependency which works only locally)
if config.Storage.StorageDriver == nil {
generator := &taskGenerator{
imgStore: storeController.DefaultStore,
log: log,
}
sch.SubmitGenerator(generator, config.Extensions.Scrub.Interval, scheduler.LowPriority)
generator := &taskGenerator{
imgStore: storeController.DefaultStore,
log: log,
}
sch.SubmitGenerator(generator, config.Extensions.Scrub.Interval, scheduler.LowPriority)
if config.Storage.SubPaths != nil {
for route := range config.Storage.SubPaths {
// is local imagestore (because of umoci dependency which works only locally)
if config.Storage.SubPaths[route].StorageDriver == nil {
generator := &taskGenerator{
imgStore: storeController.SubStore[route],
log: log,
}
sch.SubmitGenerator(generator, config.Extensions.Scrub.Interval, scheduler.LowPriority)
generator := &taskGenerator{
imgStore: storeController.SubStore[route],
log: log,
}
sch.SubmitGenerator(generator, config.Extensions.Scrub.Interval, scheduler.LowPriority)
}
}
} else {
+1
View File
@@ -39,6 +39,7 @@ func RunScrubRepo(ctx context.Context, imgStore storageTypes.ImageStore, repo st
Str("image", result.ImageName).
Str("tag", result.Tag).
Str("status", result.Status).
Str("affected blob", result.AffectedBlob).
Str("error", result.Error).
Msg("scrub: blobs/manifest affected")
}
+20 -17
View File
@@ -381,19 +381,21 @@ func TestRepoListWithNewestImage(t *testing.T) {
ctlrManager.StartAndWait(port)
defer ctlrManager.StopServer()
config, layers, manifest, err := GetImageComponents(100)
config, layers, _, err := GetImageComponents(100)
So(err, ShouldBeNil)
err = UploadImage(Image{Manifest: manifest, Config: config, Layers: layers}, baseURL, "zot-cve-test", "0.0.1")
uploadedImage := CreateImageWith().LayerBlobs(layers).ImageConfig(config).Build()
err = UploadImage(uploadedImage, baseURL, "zot-cve-test", "0.0.1")
So(err, ShouldBeNil)
err = UploadImage(Image{Manifest: manifest, Config: config, Layers: layers}, baseURL, "a/zot-cve-test", "0.0.1")
err = UploadImage(uploadedImage, baseURL, "a/zot-cve-test", "0.0.1")
So(err, ShouldBeNil)
err = UploadImage(Image{Manifest: manifest, Config: config, Layers: layers}, baseURL, "zot-test", "0.0.1")
err = UploadImage(uploadedImage, baseURL, "zot-test", "0.0.1")
So(err, ShouldBeNil)
err = UploadImage(Image{Manifest: manifest, Config: config, Layers: layers}, baseURL, "a/zot-test", "0.0.1")
err = UploadImage(uploadedImage, baseURL, "a/zot-test", "0.0.1")
So(err, ShouldBeNil)
resp, err := resty.R().Get(baseURL + "/v2/")
@@ -590,9 +592,8 @@ func TestRepoListWithNewestImage(t *testing.T) {
panic(err)
}
var manifestDigest godigest.Digest
var configDigest godigest.Digest
manifestDigest, configDigest, _ = GetOciLayoutDigests(path.Join(subRootDir, "a/zot-test"))
manifestDigest := uploadedImage.ManifestDescriptor.Digest
configDigest := uploadedImage.ConfigDescriptor.Digest
// Delete config blob and try.
err = os.Remove(path.Join(subRootDir, "a/zot-test/blobs/sha256", configDigest.Encoded()))
@@ -1269,22 +1270,25 @@ func TestExpandedRepoInfo(t *testing.T) {
ctlrManager.StartAndWait(port)
defer ctlrManager.StopServer()
config, layers, manifest, err := GetImageComponents(100)
config, layers, _, err := GetImageComponents(100)
So(err, ShouldBeNil)
manifest.Annotations = make(map[string]string)
manifest.Annotations["org.opencontainers.image.vendor"] = "zot"
annotations := make(map[string]string)
annotations["org.opencontainers.image.vendor"] = "zot"
err = UploadImage(Image{Manifest: manifest, Config: config, Layers: layers}, baseURL, "zot-cve-test", "0.0.1")
uploadedImage := CreateImageWith().LayerBlobs(layers).ImageConfig(config).
Annotations(annotations).Build()
err = UploadImage(uploadedImage, baseURL, "zot-cve-test", "0.0.1")
So(err, ShouldBeNil)
err = UploadImage(Image{Manifest: manifest, Config: config, Layers: layers}, baseURL, "a/zot-cve-test", "0.0.1")
err = UploadImage(uploadedImage, baseURL, "a/zot-cve-test", "0.0.1")
So(err, ShouldBeNil)
err = UploadImage(Image{Manifest: manifest, Config: config, Layers: layers}, baseURL, "zot-test", "0.0.1")
err = UploadImage(uploadedImage, baseURL, "zot-test", "0.0.1")
So(err, ShouldBeNil)
err = UploadImage(Image{Manifest: manifest, Config: config, Layers: layers}, baseURL, "a/zot-test", "0.0.1")
err = UploadImage(uploadedImage, baseURL, "a/zot-test", "0.0.1")
So(err, ShouldBeNil)
log := log.NewLogger("debug", "")
@@ -1455,8 +1459,7 @@ func TestExpandedRepoInfo(t *testing.T) {
}
So(found, ShouldEqual, true)
var manifestDigest godigest.Digest
manifestDigest, _, _ = GetOciLayoutDigests(path.Join(rootDir, "zot-test"))
manifestDigest := uploadedImage.ManifestDescriptor.Digest
err = os.Remove(path.Join(rootDir, "zot-test/blobs/sha256", manifestDigest.Encoded()))
So(err, ShouldBeNil)
+178 -100
View File
@@ -5,16 +5,12 @@ import (
"encoding/json"
"fmt"
"io"
"os"
"path"
"strings"
"time"
"github.com/olekukonko/tablewriter"
godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opencontainers/umoci"
"github.com/opencontainers/umoci/oci/casext"
"zotregistry.io/zot/errors"
storageTypes "zotregistry.io/zot/pkg/storage/types"
@@ -24,19 +20,22 @@ const (
colImageNameIndex = iota
colTagIndex
colStatusIndex
colAffectedBlobIndex
colErrorIndex
imageNameWidth = 32
tagWidth = 24
statusWidth = 8
errorWidth = 8
imageNameWidth = 32
tagWidth = 24
statusWidth = 8
affectedBlobWidth = 24
errorWidth = 8
)
type ScrubImageResult struct {
ImageName string `json:"imageName"`
Tag string `json:"tag"`
Status string `json:"status"`
Error string `json:"error"`
ImageName string `json:"imageName"`
Tag string `json:"tag"`
Status string `json:"status"`
AffectedBlob string `json:"affectedBlob"`
Error string `json:"error"`
}
type ScrubResults struct {
@@ -92,153 +91,228 @@ func CheckRepo(ctx context.Context, imageName string, imgStore storageTypes.Imag
return results, ctx.Err()
}
dir := path.Join(imgStore.RootDir(), imageName)
if !imgStore.DirExists(dir) {
return results, errors.ErrRepoNotFound
}
oci, err := umoci.OpenLayout(dir)
if err != nil {
return results, err
}
defer oci.Close()
var lockLatency time.Time
imgStore.RLock(&lockLatency)
defer imgStore.RUnlock(&lockLatency)
buf, err := os.ReadFile(path.Join(dir, "index.json"))
// check image structure / layout
ok, err := imgStore.ValidateRepo(imageName)
if err != nil {
return results, err
}
if !ok {
return results, errors.ErrRepoBadLayout
}
// check "index.json" content
indexContent, err := imgStore.GetIndexContent(imageName)
if err != nil {
return results, err
}
var index ispec.Index
if err := json.Unmarshal(buf, &index); err != nil {
if err := json.Unmarshal(indexContent, &index); err != nil {
return results, errors.ErrRepoNotFound
}
listOfManifests := []ispec.Descriptor{}
scrubbedManifests := make(map[godigest.Digest]ScrubImageResult)
for _, manifest := range index.Manifests {
if manifest.MediaType == ispec.MediaTypeImageIndex {
buf, err := os.ReadFile(path.Join(dir, "blobs", manifest.Digest.Algorithm().String(), manifest.Digest.Encoded()))
if err != nil {
tagName := manifest.Annotations[ispec.AnnotationRefName]
imgRes := getResult(imageName, tagName, errors.ErrBadBlobDigest)
results = append(results, imgRes)
continue
}
var idx ispec.Index
if err := json.Unmarshal(buf, &idx); err != nil {
tagName := manifest.Annotations[ispec.AnnotationRefName]
imgRes := getResult(imageName, tagName, errors.ErrBadBlobDigest)
results = append(results, imgRes)
continue
}
listOfManifests = append(listOfManifests, idx.Manifests...)
} else if manifest.MediaType == ispec.MediaTypeImageManifest {
listOfManifests = append(listOfManifests, manifest)
}
}
for _, m := range listOfManifests {
tag := m.Annotations[ispec.AnnotationRefName]
imageResult := CheckIntegrity(ctx, imageName, tag, oci, m, dir)
results = append(results, imageResult)
tag := manifest.Annotations[ispec.AnnotationRefName]
scrubManifest(ctx, manifest, imgStore, imageName, tag, scrubbedManifests)
results = append(results, scrubbedManifests[manifest.Digest])
}
return results, nil
}
func CheckIntegrity(ctx context.Context, imageName, tagName string, oci casext.Engine, manifest ispec.Descriptor, dir string) ScrubImageResult { //nolint: lll
func scrubManifest(
ctx context.Context, manifest ispec.Descriptor, imgStore storageTypes.ImageStore, imageName, tag string,
scrubbedManifests map[godigest.Digest]ScrubImageResult,
) {
res, ok := scrubbedManifests[manifest.Digest]
if ok {
scrubbedManifests[manifest.Digest] = newScrubImageResult(imageName, tag, res.Status,
res.AffectedBlob, res.Error)
return
}
switch manifest.MediaType {
case ispec.MediaTypeImageIndex:
buf, err := imgStore.GetBlobContent(imageName, manifest.Digest)
if err != nil {
imgRes := getResult(imageName, tag, manifest.Digest, errors.ErrBadBlobDigest)
scrubbedManifests[manifest.Digest] = imgRes
return
}
var idx ispec.Index
if err := json.Unmarshal(buf, &idx); err != nil {
imgRes := getResult(imageName, tag, manifest.Digest, errors.ErrBadBlobDigest)
scrubbedManifests[manifest.Digest] = imgRes
return
}
// check all manifests
for _, m := range idx.Manifests {
scrubManifest(ctx, m, imgStore, imageName, tag, scrubbedManifests)
// if the manifest is affected then this index is also affected
if scrubbedManifests[m.Digest].Error != "" {
mRes := scrubbedManifests[m.Digest]
scrubbedManifests[manifest.Digest] = newScrubImageResult(imageName, tag, mRes.Status,
mRes.AffectedBlob, mRes.Error)
return
}
}
// at this point, before starting to check de subject we can consider the index is ok
scrubbedManifests[manifest.Digest] = getResult(imageName, tag, "", nil)
// check subject if exists
if idx.Subject != nil {
scrubManifest(ctx, *idx.Subject, imgStore, imageName, tag, scrubbedManifests)
subjectRes := scrubbedManifests[idx.Subject.Digest]
scrubbedManifests[manifest.Digest] = newScrubImageResult(imageName, tag, subjectRes.Status,
subjectRes.AffectedBlob, subjectRes.Error)
}
case ispec.MediaTypeImageManifest:
imgRes := CheckIntegrity(ctx, imageName, tag, manifest, imgStore)
scrubbedManifests[manifest.Digest] = imgRes
// if integrity ok then check subject if exists
if imgRes.Error == "" {
manifestContent, _ := imgStore.GetBlobContent(imageName, manifest.Digest)
var man ispec.Manifest
_ = json.Unmarshal(manifestContent, &man)
if man.Subject != nil {
scrubManifest(ctx, *man.Subject, imgStore, imageName, tag, scrubbedManifests)
subjectRes := scrubbedManifests[man.Subject.Digest]
scrubbedManifests[manifest.Digest] = newScrubImageResult(imageName, tag, subjectRes.Status,
subjectRes.AffectedBlob, subjectRes.Error)
}
}
default:
scrubbedManifests[manifest.Digest] = getResult(imageName, tag, manifest.Digest, errors.ErrBadManifest)
}
}
func CheckIntegrity(
ctx context.Context, imageName, tagName string, manifest ispec.Descriptor, imgStore storageTypes.ImageStore,
) ScrubImageResult {
// check manifest and config
if _, err := umoci.Stat(ctx, oci, manifest); err != nil {
return getResult(imageName, tagName, err)
if affectedBlob, err := CheckManifestAndConfig(imageName, manifest, imgStore); err != nil {
return getResult(imageName, tagName, affectedBlob, err)
}
// check layers
return CheckLayers(ctx, imageName, tagName, dir, manifest)
return CheckLayers(ctx, imageName, tagName, manifest, imgStore)
}
func CheckLayers(ctx context.Context, imageName, tagName, dir string, manifest ispec.Descriptor) ScrubImageResult {
func CheckManifestAndConfig(
imageName string, manifestDesc ispec.Descriptor, imgStore storageTypes.ImageStore,
) (godigest.Digest, error) {
if manifestDesc.MediaType != ispec.MediaTypeImageManifest {
return manifestDesc.Digest, errors.ErrBadManifest
}
manifestContent, err := imgStore.GetBlobContent(imageName, manifestDesc.Digest)
if err != nil {
return manifestDesc.Digest, err
}
var manifest ispec.Manifest
err = json.Unmarshal(manifestContent, &manifest)
if err != nil {
return manifestDesc.Digest, errors.ErrBadManifest
}
configContent, err := imgStore.GetBlobContent(imageName, manifest.Config.Digest)
if err != nil {
return manifest.Config.Digest, err
}
var config ispec.Image
err = json.Unmarshal(configContent, &config)
if err != nil {
return manifest.Config.Digest, errors.ErrBadConfig
}
return "", nil
}
func CheckLayers(
ctx context.Context, imageName, tagName string, manifest ispec.Descriptor, imgStore storageTypes.ImageStore,
) ScrubImageResult {
imageRes := ScrubImageResult{}
buf, err := os.ReadFile(path.Join(dir, "blobs", manifest.Digest.Algorithm().String(), manifest.Digest.Encoded()))
buf, err := imgStore.GetBlobContent(imageName, manifest.Digest)
if err != nil {
imageRes = getResult(imageName, tagName, err)
imageRes = getResult(imageName, tagName, manifest.Digest, err)
return imageRes
}
var man ispec.Manifest
if err := json.Unmarshal(buf, &man); err != nil {
imageRes = getResult(imageName, tagName, err)
imageRes = getResult(imageName, tagName, manifest.Digest, errors.ErrBadManifest)
return imageRes
}
for _, layer := range man.Layers {
layerPath := path.Join(dir, "blobs", layer.Digest.Algorithm().String(), layer.Digest.Encoded())
_, err = os.Stat(layerPath)
layerContent, err := imgStore.GetBlobContent(imageName, layer.Digest)
if err != nil {
imageRes = getResult(imageName, tagName, errors.ErrBlobNotFound)
imageRes = getResult(imageName, tagName, layer.Digest, err)
break
}
layerFh, err := os.Open(layerPath)
if err != nil {
imageRes = getResult(imageName, tagName, errors.ErrBlobNotFound)
break
}
computedDigest, err := godigest.FromReader(layerFh)
layerFh.Close()
if err != nil {
imageRes = getResult(imageName, tagName, errors.ErrBadBlobDigest)
break
}
computedDigest := godigest.FromBytes(layerContent)
if computedDigest != layer.Digest {
imageRes = getResult(imageName, tagName, errors.ErrBadBlobDigest)
imageRes = getResult(imageName, tagName, layer.Digest, errors.ErrBadBlobDigest)
break
}
imageRes = getResult(imageName, tagName, nil)
imageRes = getResult(imageName, tagName, "", nil)
}
return imageRes
}
func getResult(imageName, tag string, err error) ScrubImageResult {
var status string
var errField string
func getResult(imageName, tag string, affectedBlobDigest godigest.Digest, err error) ScrubImageResult {
if err != nil {
status = "affected"
errField = err.Error()
} else {
status = "ok"
errField = ""
return newScrubImageResult(imageName, tag, "affected", affectedBlobDigest.Encoded(), err.Error())
}
return newScrubImageResult(imageName, tag, "ok", "", "")
}
func newScrubImageResult(imageName, tag, status, affectedBlob, err string) ScrubImageResult {
return ScrubImageResult{
ImageName: imageName,
Tag: tag,
Status: status,
Error: errField,
ImageName: imageName,
Tag: tag,
Status: status,
AffectedBlob: affectedBlob,
Error: err,
}
}
@@ -259,12 +333,13 @@ func getScrubTableWriter(writer io.Writer) *tablewriter.Table {
table.SetColMinWidth(colImageNameIndex, imageNameWidth)
table.SetColMinWidth(colTagIndex, tagWidth)
table.SetColMinWidth(colStatusIndex, statusWidth)
table.SetColMinWidth(colErrorIndex, affectedBlobWidth)
table.SetColMinWidth(colErrorIndex, errorWidth)
return table
}
const tableCols = 4
const tableCols = 5
func printScrubTableHeader(writer io.Writer) {
table := getScrubTableWriter(writer)
@@ -274,6 +349,7 @@ func printScrubTableHeader(writer io.Writer) {
row[colImageNameIndex] = "REPOSITORY"
row[colTagIndex] = "TAG"
row[colStatusIndex] = "STATUS"
row[colAffectedBlobIndex] = "AFFECTED BLOB"
row[colErrorIndex] = "ERROR"
table.Append(row)
@@ -287,6 +363,7 @@ func printImageResult(imageResult ScrubImageResult) string {
table.SetColMinWidth(colImageNameIndex, imageNameWidth)
table.SetColMinWidth(colTagIndex, tagWidth)
table.SetColMinWidth(colStatusIndex, statusWidth)
table.SetColMinWidth(colAffectedBlobIndex, affectedBlobWidth)
table.SetColMinWidth(colErrorIndex, errorWidth)
row := make([]string, tableCols)
@@ -294,6 +371,7 @@ func printImageResult(imageResult ScrubImageResult) string {
row[colImageNameIndex] = imageResult.ImageName
row[colTagIndex] = imageResult.Tag
row[colStatusIndex] = imageResult.Status
row[colAffectedBlobIndex] = imageResult.AffectedBlob
row[colErrorIndex] = imageResult.Error
table.Append(row)
+252 -65
View File
@@ -4,23 +4,31 @@ import (
"bytes"
"context"
"encoding/json"
"os"
"errors"
"fmt"
"path"
"regexp"
"strings"
"testing"
"github.com/docker/distribution/registry/storage/driver"
guuid "github.com/gofrs/uuid"
godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
. "github.com/smartystreets/goconvey/convey"
zerr "zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/extensions/monitoring"
"zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/storage"
"zotregistry.io/zot/pkg/storage/cache"
common "zotregistry.io/zot/pkg/storage/common"
"zotregistry.io/zot/pkg/storage/local"
"zotregistry.io/zot/pkg/storage/s3"
storageTypes "zotregistry.io/zot/pkg/storage/types"
"zotregistry.io/zot/pkg/test"
. "zotregistry.io/zot/pkg/test/image-utils"
"zotregistry.io/zot/pkg/test/mocks"
)
const (
@@ -28,31 +36,60 @@ const (
tag = "1.0"
)
func TestCheckAllBlobsIntegrity(t *testing.T) {
dir := t.TempDir()
var errUnexpectedError = errors.New("unexpected err")
log := log.NewLogger("debug", "")
func TestLocalCheckAllBlobsIntegrity(t *testing.T) {
Convey("test with local storage", t, func() {
tdir := t.TempDir()
log := log.NewLogger("debug", "")
metrics := monitoring.NewMetricsServer(false, log)
cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{
RootDir: tdir,
Name: "cache",
UseRelPaths: true,
}, log)
driver := local.New(true)
imgStore := local.NewImageStore(tdir, true, true, log, metrics, nil, cacheDriver)
metrics := monitoring.NewMetricsServer(false, log)
cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{
RootDir: dir,
Name: "cache",
UseRelPaths: true,
}, log)
imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver)
RunCheckAllBlobsIntegrityTests(t, imgStore, driver, log)
})
}
Convey("Scrub only one repo", t, func(c C) {
func TestS3CheckAllBlobsIntegrity(t *testing.T) {
skipIt(t)
Convey("test with S3 storage", t, func() {
uuid, err := guuid.NewV4()
if err != nil {
panic(err)
}
testDir := path.Join("/oci-repo-test", uuid.String())
tdir := t.TempDir()
log := log.NewLogger("debug", "")
var store driver.StorageDriver
store, imgStore, _ := createObjectsStore(testDir, tdir)
defer cleanupStorage(store, testDir)
driver := s3.New(store)
RunCheckAllBlobsIntegrityTests(t, imgStore, driver, log)
})
}
func RunCheckAllBlobsIntegrityTests( //nolint: thelper
t *testing.T, imgStore storageTypes.ImageStore, driver storageTypes.Driver, log log.Logger,
) {
Convey("Scrub only one repo", func() {
// initialize repo
err := imgStore.InitRepo(repoName)
So(err, ShouldBeNil)
ok := imgStore.DirExists(path.Join(imgStore.RootDir(), repoName))
So(ok, ShouldBeTrue)
storeController := storage.StoreController{}
storeController.DefaultStore = imgStore
So(storeController.GetImageStore(repoName), ShouldResemble, imgStore)
storeCtlr := storage.StoreController{}
storeCtlr.DefaultStore = imgStore
So(storeCtlr.GetImageStore(repoName), ShouldResemble, imgStore)
config, layers, manifest, err := test.GetImageComponents(1000) //nolint:staticcheck
So(err, ShouldBeNil)
@@ -84,7 +121,7 @@ func TestCheckAllBlobsIntegrity(t *testing.T) {
space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS ERROR")
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS AFFECTED BLOB ERROR")
So(actual, ShouldContainSubstring, "test 1.0 ok")
})
@@ -96,9 +133,15 @@ func TestCheckAllBlobsIntegrity(t *testing.T) {
// delete content of manifest file
manifestDig := manifestDigest.Encoded()
manifestFile := path.Join(imgStore.RootDir(), repoName, "/blobs/sha256", manifestDig)
err = os.Truncate(manifestFile, 0)
err = driver.Delete(manifestFile)
So(err, ShouldBeNil)
defer func() {
// put manifest content back to file
_, err = driver.WriteFile(manifestFile, content)
So(err, ShouldBeNil)
}()
buff := bytes.NewBufferString("")
res, err := storeCtlr.CheckAllBlobsIntegrity(context.Background())
@@ -108,9 +151,9 @@ func TestCheckAllBlobsIntegrity(t *testing.T) {
space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS ERROR")
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS AFFECTED BLOB ERROR")
// verify error message
So(actual, ShouldContainSubstring, "test 1.0 affected parse application/vnd.oci.image.manifest.v1+json")
So(actual, ShouldContainSubstring, fmt.Sprintf("test 1.0 affected %s blob: not found", manifestDig))
index, err := common.GetIndex(imgStore, repoName, log)
So(err, ShouldBeNil)
@@ -118,14 +161,34 @@ func TestCheckAllBlobsIntegrity(t *testing.T) {
So(len(index.Manifests), ShouldEqual, 1)
manifestDescriptor := index.Manifests[0]
repoDir := path.Join(dir, repoName)
imageRes := storage.CheckLayers(context.Background(), repoName, tag, repoDir, manifestDescriptor)
imageRes := storage.CheckLayers(context.Background(), repoName, tag, manifestDescriptor, imgStore)
So(imageRes.Status, ShouldEqual, "affected")
So(imageRes.Error, ShouldEqual, "unexpected end of JSON input")
So(imageRes.Error, ShouldEqual, "blob: not found")
// put manifest content back to file
err = os.WriteFile(manifestFile, content, 0o600)
_, err = driver.WriteFile(manifestFile, []byte("invalid content"))
So(err, ShouldBeNil)
buff = bytes.NewBufferString("")
res, err = storeCtlr.CheckAllBlobsIntegrity(context.Background())
res.PrintScrubResults(buff)
So(err, ShouldBeNil)
str = space.ReplaceAllString(buff.String(), " ")
actual = strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS AFFECTED BLOB ERROR")
// verify error message
So(actual, ShouldContainSubstring, fmt.Sprintf("test 1.0 affected %s manifest: invalid contents", manifestDig))
index, err = common.GetIndex(imgStore, repoName, log)
So(err, ShouldBeNil)
So(len(index.Manifests), ShouldEqual, 1)
manifestDescriptor = index.Manifests[0]
imageRes = storage.CheckLayers(context.Background(), repoName, tag, manifestDescriptor, imgStore)
So(imageRes.Status, ShouldEqual, "affected")
So(imageRes.Error, ShouldEqual, "manifest: invalid contents")
})
Convey("Config integrity affected", func() {
@@ -136,9 +199,15 @@ func TestCheckAllBlobsIntegrity(t *testing.T) {
// delete content of config file
configDig := configDigest.Encoded()
configFile := path.Join(imgStore.RootDir(), repoName, "/blobs/sha256", configDig)
err = os.Truncate(configFile, 0)
err = driver.Delete(configFile)
So(err, ShouldBeNil)
defer func() {
// put config content back to file
_, err = driver.WriteFile(configFile, content)
So(err, ShouldBeNil)
}()
buff := bytes.NewBufferString("")
res, err := storeCtlr.CheckAllBlobsIntegrity(context.Background())
@@ -148,12 +217,22 @@ func TestCheckAllBlobsIntegrity(t *testing.T) {
space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS ERROR")
So(actual, ShouldContainSubstring, "test 1.0 affected stat: parse application/vnd.oci.image.config.v1+json")
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS AFFECTED BLOB ERROR")
So(actual, ShouldContainSubstring, fmt.Sprintf("test 1.0 affected %s blob: not found", configDig))
// put config content back to file
err = os.WriteFile(configFile, content, 0o600)
_, err = driver.WriteFile(configFile, []byte("invalid content"))
So(err, ShouldBeNil)
buff = bytes.NewBufferString("")
res, err = storeCtlr.CheckAllBlobsIntegrity(context.Background())
res.PrintScrubResults(buff)
So(err, ShouldBeNil)
str = space.ReplaceAllString(buff.String(), " ")
actual = strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS AFFECTED BLOB ERROR")
So(actual, ShouldContainSubstring, fmt.Sprintf("test 1.0 affected %s config: invalid config", configDig))
})
Convey("Layers integrity affected", func() {
@@ -164,9 +243,15 @@ func TestCheckAllBlobsIntegrity(t *testing.T) {
// delete content of layer file
layerDig := layerDigest.Encoded()
layerFile := path.Join(imgStore.RootDir(), repoName, "/blobs/sha256", layerDig)
err = os.Truncate(layerFile, 0)
_, err = driver.WriteFile(layerFile, []byte(" "))
So(err, ShouldBeNil)
defer func() {
// put layer content back to file
_, err = driver.WriteFile(layerFile, content)
So(err, ShouldBeNil)
}()
buff := bytes.NewBufferString("")
res, err := storeCtlr.CheckAllBlobsIntegrity(context.Background())
@@ -176,37 +261,36 @@ func TestCheckAllBlobsIntegrity(t *testing.T) {
space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS ERROR")
So(actual, ShouldContainSubstring, "test 1.0 affected blob: bad blob digest")
// put layer content back to file
err = os.WriteFile(layerFile, content, 0o600)
So(err, ShouldBeNil)
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS AFFECTED BLOB ERROR")
So(actual, ShouldContainSubstring, fmt.Sprintf("test 1.0 affected %s blob: bad blob digest", layerDig))
})
Convey("Layer not found", func() {
// get content of layer
content, err := imgStore.GetBlobContent(repoName, layerDigest)
So(err, ShouldBeNil)
// change layer file permissions
layerDig := layerDigest.Encoded()
repoDir := path.Join(dir, repoName)
repoDir := path.Join(imgStore.RootDir(), repoName)
layerFile := path.Join(repoDir, "/blobs/sha256", layerDig)
err = os.Chmod(layerFile, 0x0200)
err = driver.Delete(layerFile)
So(err, ShouldBeNil)
defer func() {
_, err := driver.WriteFile(layerFile, content)
So(err, ShouldBeNil)
}()
index, err := common.GetIndex(imgStore, repoName, log)
So(err, ShouldBeNil)
So(len(index.Manifests), ShouldEqual, 1)
manifestDescriptor := index.Manifests[0]
imageRes := storage.CheckLayers(context.Background(), repoName, tag, repoDir, manifestDescriptor)
imageRes := storage.CheckLayers(context.Background(), repoName, tag, manifestDescriptor, imgStore)
So(imageRes.Status, ShouldEqual, "affected")
So(imageRes.Error, ShouldEqual, "blob: not found")
err = os.Chmod(layerFile, 0x0600)
So(err, ShouldBeNil)
// delete layer file
err = os.Remove(layerFile)
So(err, ShouldBeNil)
buff := bytes.NewBufferString("")
@@ -217,8 +301,8 @@ func TestCheckAllBlobsIntegrity(t *testing.T) {
space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS ERROR")
So(actual, ShouldContainSubstring, "test 1.0 affected blob: not found")
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS AFFECTED BLOB ERROR")
So(actual, ShouldContainSubstring, fmt.Sprintf("test 1.0 affected %s blob: not found", layerDig))
})
Convey("Scrub index", func() {
@@ -244,8 +328,15 @@ func TestCheckAllBlobsIntegrity(t *testing.T) {
_, _, err = imgStore.FullBlobUpload(repoName, newManifestReader, newManifestDigest)
So(err, ShouldBeNil)
idx, err := common.GetIndex(imgStore, repoName, log)
So(err, ShouldBeNil)
manifestDescriptor, ok := common.GetManifestDescByReference(idx, manifestDigest.String())
So(ok, ShouldBeTrue)
var index ispec.Index
index.SchemaVersion = 2
index.Subject = &manifestDescriptor
index.Manifests = []ispec.Descriptor{
{
MediaType: ispec.MediaTypeImageManifest,
@@ -268,13 +359,14 @@ func TestCheckAllBlobsIntegrity(t *testing.T) {
space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS ERROR")
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS AFFECTED BLOB ERROR")
So(actual, ShouldContainSubstring, "test 1.0 ok")
So(actual, ShouldContainSubstring, "test ok")
// test scrub index - errors
indexFile := path.Join(imgStore.RootDir(), repoName, "/blobs/sha256", indexDigest.Encoded())
err = os.Chmod(indexFile, 0o000)
// delete content of manifest file
manifestFile := path.Join(imgStore.RootDir(), repoName, "/blobs/sha256", newManifestDigest.Encoded())
err = driver.Delete(manifestFile)
So(err, ShouldBeNil)
buff = bytes.NewBufferString("")
@@ -285,13 +377,11 @@ func TestCheckAllBlobsIntegrity(t *testing.T) {
str = space.ReplaceAllString(buff.String(), " ")
actual = strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS ERROR")
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS AFFECTED BLOB ERROR")
So(actual, ShouldContainSubstring, "test affected")
err = os.Chmod(indexFile, 0o600)
So(err, ShouldBeNil)
err = os.Truncate(indexFile, 0)
indexFile := path.Join(imgStore.RootDir(), repoName, "/blobs/sha256", indexDigest.Encoded())
err = driver.Delete(indexFile)
So(err, ShouldBeNil)
buff = bytes.NewBufferString("")
@@ -302,7 +392,48 @@ func TestCheckAllBlobsIntegrity(t *testing.T) {
str = space.ReplaceAllString(buff.String(), " ")
actual = strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS ERROR")
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS AFFECTED BLOB ERROR")
So(actual, ShouldContainSubstring, "test affected")
index.Manifests[0].MediaType = "invalid"
indexBlob, err = json.Marshal(index)
So(err, ShouldBeNil)
_, err = driver.WriteFile(indexFile, indexBlob)
So(err, ShouldBeNil)
buff = bytes.NewBufferString("")
res, err = storeCtlr.CheckAllBlobsIntegrity(context.Background())
res.PrintScrubResults(buff)
So(err, ShouldBeNil)
_, err = storage.CheckManifestAndConfig(repoName, index.Manifests[0], imgStore)
So(err, ShouldNotBeNil)
So(err, ShouldEqual, zerr.ErrBadManifest)
str = space.ReplaceAllString(buff.String(), " ")
actual = strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS AFFECTED BLOB ERROR")
So(actual, ShouldContainSubstring, "test affected")
_, err = driver.WriteFile(indexFile, []byte("invalid cotent"))
So(err, ShouldBeNil)
defer func() {
err := driver.Delete(indexFile)
So(err, ShouldBeNil)
}()
buff = bytes.NewBufferString("")
res, err = storeCtlr.CheckAllBlobsIntegrity(context.Background())
res.PrintScrubResults(buff)
So(err, ShouldBeNil)
str = space.ReplaceAllString(buff.String(), " ")
actual = strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS AFFECTED BLOB ERROR")
So(actual, ShouldContainSubstring, "test affected")
})
@@ -310,7 +441,7 @@ func TestCheckAllBlobsIntegrity(t *testing.T) {
// delete manifest file
manifestDig := manifestDigest.Encoded()
manifestFile := path.Join(imgStore.RootDir(), repoName, "/blobs/sha256", manifestDig)
err = os.Remove(manifestFile)
err = driver.Delete(manifestFile)
So(err, ShouldBeNil)
buff := bytes.NewBufferString("")
@@ -322,20 +453,76 @@ func TestCheckAllBlobsIntegrity(t *testing.T) {
space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS ERROR")
So(actual, ShouldContainSubstring, "test 1.0 affected")
So(actual, ShouldContainSubstring, "no such file or directory")
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS AFFECTED BLOB ERROR")
So(actual, ShouldContainSubstring, fmt.Sprintf("test 1.0 affected %s blob: not found", manifestDig))
index, err := common.GetIndex(imgStore, repoName, log)
So(err, ShouldBeNil)
So(len(index.Manifests), ShouldEqual, 2)
So(len(index.Manifests), ShouldEqual, 1)
manifestDescriptor := index.Manifests[0]
repoDir := path.Join(dir, repoName)
imageRes := storage.CheckLayers(context.Background(), repoName, tag, repoDir, manifestDescriptor)
imageRes := storage.CheckLayers(context.Background(), repoName, tag, manifestDescriptor, imgStore)
So(imageRes.Status, ShouldEqual, "affected")
So(imageRes.Error, ShouldContainSubstring, "no such file or directory")
So(imageRes.Error, ShouldContainSubstring, "blob: not found")
})
Convey("use the result of an already scrubed manifest which is the subject of the current manifest", func() {
index, err := common.GetIndex(imgStore, repoName, log)
So(err, ShouldBeNil)
manifestDescriptor, ok := common.GetManifestDescByReference(index, manifestDigest.String())
So(ok, ShouldBeTrue)
err = test.WriteImageToFileSystem(CreateDefaultImageWith().Subject(&manifestDescriptor).Build(),
repoName, "0.0.1", storeCtlr)
So(err, ShouldBeNil)
buff := bytes.NewBufferString("")
res, err := storeCtlr.CheckAllBlobsIntegrity(context.Background())
res.PrintScrubResults(buff)
So(err, ShouldBeNil)
space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "REPOSITORY TAG STATUS AFFECTED BLOB ERROR")
So(actual, ShouldContainSubstring, "test 1.0 ok")
So(actual, ShouldContainSubstring, "test 0.0.1 ok")
})
})
Convey("test errors", func() {
mockedImgStore := mocks.MockedImageStore{
GetRepositoriesFn: func() ([]string, error) {
return []string{repoName}, nil
},
ValidateRepoFn: func(name string) (bool, error) {
return false, nil
},
}
storeController := storage.StoreController{}
storeController.DefaultStore = mockedImgStore
_, err := storeController.CheckAllBlobsIntegrity(context.Background())
So(err, ShouldNotBeNil)
So(err, ShouldEqual, zerr.ErrRepoBadLayout)
mockedImgStore = mocks.MockedImageStore{
GetRepositoriesFn: func() ([]string, error) {
return []string{repoName}, nil
},
GetIndexContentFn: func(repo string) ([]byte, error) {
return []byte{}, errUnexpectedError
},
}
storeController.DefaultStore = mockedImgStore
_, err = storeController.CheckAllBlobsIntegrity(context.Background())
So(err, ShouldNotBeNil)
So(err, ShouldEqual, errUnexpectedError)
})
}
-75
View File
@@ -35,7 +35,6 @@ import (
godigest "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opencontainers/umoci"
"github.com/phayes/freeport"
"github.com/project-zot/mockoidc"
"github.com/sigstore/cosign/v2/cmd/cosign/cli/generate"
@@ -72,29 +71,6 @@ var (
var NotationPathLock = new(sync.Mutex) //nolint: gochecknoglobals
// which: manifest, config, layer
func GetTestBlobDigest(image, which string) godigest.Digest {
prePath := "../test/data"
for _, err := os.Stat(prePath); err != nil; _, err = os.Stat(prePath) {
prePath = "../" + prePath
}
imgPath := path.Join(prePath, image)
manifest, config, layer := GetOciLayoutDigests(imgPath)
switch which {
case "manifest":
return manifest
case "config":
return config
case "layer":
return layer
}
return ""
}
func GetFreePort() string {
port, err := freeport.GetFreePort()
if err != nil {
@@ -434,57 +410,6 @@ func GetRandomImageConfig() ([]byte, godigest.Digest) {
return configBlobContent, configBlobDigestRaw
}
func GetOciLayoutDigests(imagePath string) (godigest.Digest, godigest.Digest, godigest.Digest) {
var (
manifestDigest godigest.Digest
configDigest godigest.Digest
layerDigest godigest.Digest
)
oci, err := umoci.OpenLayout(imagePath)
if err != nil {
panic(fmt.Errorf("error opening layout at '%s' : %w", imagePath, err))
}
defer oci.Close()
ctxUmoci := context.Background()
index, err := oci.GetIndex(ctxUmoci)
if err != nil {
panic(err)
}
for _, manifest := range index.Manifests {
manifestDigest = manifest.Digest
manifestBlob, err := oci.GetBlob(ctxUmoci, manifest.Digest)
if err != nil {
panic(err)
}
manifestBuf, err := io.ReadAll(manifestBlob)
if err != nil {
panic(err)
}
var manifest ispec.Manifest
err = json.Unmarshal(manifestBuf, &manifest)
if err != nil {
panic(err)
}
configDigest = manifest.Config.Digest
for _, layer := range manifest.Layers {
layerDigest = layer.Digest
}
}
return manifestDigest, configDigest, layerDigest
}
// Deprecated: Should use the new functions starting with "Create".
func GetImageComponents(layerSize int) (ispec.Image, [][]byte, ispec.Manifest, error) {
config := ispec.Image{
-59
View File
@@ -22,7 +22,6 @@ import (
"zotregistry.io/zot/pkg/api"
"zotregistry.io/zot/pkg/api/config"
"zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/storage"
"zotregistry.io/zot/pkg/test"
. "zotregistry.io/zot/pkg/test/image-utils"
@@ -125,64 +124,6 @@ func TestCopyFiles(t *testing.T) {
})
}
func TestGetOciLayoutDigests(t *testing.T) {
dir := t.TempDir()
Convey("image path is wrong", t, func() {
So(func() { _, _, _ = test.GetOciLayoutDigests("inexistent-image") }, ShouldPanic)
})
Convey("no permissions when getting index", t, func() {
storageCtlr := test.GetDefaultStoreController(dir, log.NewLogger("debug", ""))
image := CreateDefaultImage()
err := test.WriteImageToFileSystem(image, "test-index", "0.0.1", storageCtlr)
So(err, ShouldBeNil)
err = os.Chmod(path.Join(dir, "test-index", "index.json"), 0o000)
if err != nil {
panic(err)
}
So(func() { _, _, _ = test.GetOciLayoutDigests(path.Join(dir, "test-index")) }, ShouldPanic)
err = os.Chmod(path.Join(dir, "test-index", "index.json"), 0o755)
if err != nil {
panic(err)
}
})
Convey("can't access manifest digest", t, func() {
storageCtlr := test.GetDefaultStoreController(dir, log.NewLogger("debug", ""))
image := CreateDefaultImage()
err := test.WriteImageToFileSystem(image, "test-manifest", "0.0.1", storageCtlr)
So(err, ShouldBeNil)
buf, err := os.ReadFile(path.Join(dir, "test-manifest", "index.json"))
if err != nil {
panic(err)
}
var index ispec.Index
if err := json.Unmarshal(buf, &index); err != nil {
panic(err)
}
err = os.Chmod(path.Join(dir, "test-manifest", "blobs/sha256", index.Manifests[0].Digest.Encoded()), 0o000)
if err != nil {
panic(err)
}
So(func() { _, _, _ = test.GetOciLayoutDigests(path.Join(dir, "test-manifest")) }, ShouldPanic)
err = os.Chmod(path.Join(dir, "test-manifest", "blobs/sha256", index.Manifests[0].Digest.Encoded()), 0o755)
if err != nil {
panic(err)
}
})
}
func TestGetImageComponents(t *testing.T) {
Convey("Inject failures for unreachable lines", t, func() {
injected := inject.InjectFailure(0)
+4 -3
View File
@@ -33,8 +33,6 @@ import (
var ErrTestError = fmt.Errorf("testError")
func TestBaseOciLayoutUtils(t *testing.T) {
manifestDigest := GetTestBlobDigest("zot-test", "config").String()
Convey("GetImageManifestSize fail", t, func() {
mockStoreController := mocks.MockedImageStore{
GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
@@ -64,6 +62,9 @@ func TestBaseOciLayoutUtils(t *testing.T) {
})
Convey("GetImageConfigSize: config GetBlobContent fail", t, func() {
image := CreateRandomImage()
manifestDigest := image.ConfigDescriptor.Digest.String()
mockStoreController := mocks.MockedImageStore{
GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
if digest.String() == manifestDigest {
@@ -83,7 +84,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
"layers": [
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "` + GetTestBlobDigest("zot-test", "layer").String() + `",
"digest": "` + image.Manifest.Layers[0].Digest.String() + `",
"size": 76097157
}
]