mirror of
https://github.com/project-zot/zot.git
synced 2026-06-19 22:27:58 +08:00
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:
@@ -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().
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user