2021-10-05 04:12:22 -05:00
|
|
|
package storage_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
godigest "github.com/opencontainers/go-digest"
|
|
|
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
|
|
. "github.com/smartystreets/goconvey/convey"
|
|
|
|
"zotregistry.io/zot/pkg/extensions/monitoring"
|
|
|
|
"zotregistry.io/zot/pkg/log"
|
|
|
|
"zotregistry.io/zot/pkg/storage"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
repoName = "test"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestCheckAllBlobsIntegrity(t *testing.T) {
|
2022-03-07 03:55:12 -05:00
|
|
|
dir := t.TempDir()
|
2021-10-05 04:12:22 -05:00
|
|
|
|
|
|
|
log := log.NewLogger("debug", "")
|
|
|
|
|
|
|
|
metrics := monitoring.NewMetricsServer(false, log)
|
|
|
|
|
2022-06-24 08:08:47 -05:00
|
|
|
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay,
|
|
|
|
true, true, log, metrics, nil)
|
2021-10-05 04:12:22 -05:00
|
|
|
|
|
|
|
Convey("Scrub only one repo", t, func(c C) {
|
|
|
|
// initialize repo
|
2022-03-07 03:55:12 -05:00
|
|
|
err := imgStore.InitRepo(repoName)
|
2021-10-05 04:12:22 -05:00
|
|
|
So(err, ShouldBeNil)
|
2021-12-13 14:23:31 -05:00
|
|
|
ok := imgStore.DirExists(path.Join(imgStore.RootDir(), repoName))
|
2021-10-05 04:12:22 -05:00
|
|
|
So(ok, ShouldBeTrue)
|
|
|
|
storeController := storage.StoreController{}
|
2021-12-13 14:23:31 -05:00
|
|
|
storeController.DefaultStore = imgStore
|
|
|
|
So(storeController.GetImageStore(repoName), ShouldResemble, imgStore)
|
2021-10-05 04:12:22 -05:00
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
storeCtlr := storage.StoreController{}
|
|
|
|
storeCtlr.DefaultStore = imgStore
|
2021-10-05 04:12:22 -05:00
|
|
|
|
|
|
|
const tag = "1.0"
|
|
|
|
|
|
|
|
var manifest string
|
|
|
|
var config string
|
|
|
|
var layer string
|
|
|
|
|
|
|
|
// create layer digest
|
|
|
|
body := []byte("this is a blob")
|
|
|
|
buf := bytes.NewBuffer(body)
|
2021-12-13 14:23:31 -05:00
|
|
|
buflen := buf.Len()
|
|
|
|
digest := godigest.FromBytes(body)
|
|
|
|
upload, n, err := imgStore.FullBlobUpload(repoName, buf, digest.String())
|
2021-10-05 04:12:22 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(n, ShouldEqual, len(body))
|
2021-12-13 14:23:31 -05:00
|
|
|
So(upload, ShouldNotBeEmpty)
|
|
|
|
layer = digest.String()
|
2021-10-05 04:12:22 -05:00
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
// create config digest
|
2021-10-05 04:12:22 -05:00
|
|
|
created := time.Now().Format("2006-01-02T15:04:05Z")
|
|
|
|
configBody := []byte(fmt.Sprintf(`{
|
|
|
|
"created": "%v",
|
|
|
|
"architecture": "amd64",
|
|
|
|
"os": "linux",
|
|
|
|
"rootfs": {
|
|
|
|
"type": "layers",
|
|
|
|
"diff_ids": [
|
|
|
|
"",
|
|
|
|
""
|
|
|
|
]
|
|
|
|
},
|
|
|
|
"history": [
|
|
|
|
{
|
|
|
|
"created": "%v",
|
|
|
|
"created_by": ""
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"created": "%v",
|
|
|
|
"created_by": "",
|
|
|
|
"empty_layer": true
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}`, created, created, created))
|
|
|
|
configBuf := bytes.NewBuffer(configBody)
|
|
|
|
configLen := configBuf.Len()
|
|
|
|
configDigest := godigest.FromBytes(configBody)
|
2021-12-13 14:23:31 -05:00
|
|
|
uConfig, nConfig, err := imgStore.FullBlobUpload(repoName, configBuf, configDigest.String())
|
2021-10-05 04:12:22 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(nConfig, ShouldEqual, len(configBody))
|
|
|
|
So(uConfig, ShouldNotBeEmpty)
|
|
|
|
config = configDigest.String()
|
|
|
|
|
|
|
|
// create manifest and add it to the repository
|
|
|
|
annotationsMap := make(map[string]string)
|
|
|
|
annotationsMap[ispec.AnnotationRefName] = tag
|
2021-12-13 14:23:31 -05:00
|
|
|
mnfst := ispec.Manifest{
|
2021-10-05 04:12:22 -05:00
|
|
|
Config: ispec.Descriptor{
|
|
|
|
MediaType: "application/vnd.oci.image.config.v1+json",
|
|
|
|
Digest: configDigest,
|
|
|
|
Size: int64(configLen),
|
|
|
|
},
|
|
|
|
Layers: []ispec.Descriptor{
|
|
|
|
{
|
|
|
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
2021-12-13 14:23:31 -05:00
|
|
|
Digest: digest,
|
|
|
|
Size: int64(buflen),
|
2021-10-05 04:12:22 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
Annotations: annotationsMap,
|
|
|
|
}
|
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
mnfst.SchemaVersion = 2
|
2022-06-24 08:08:47 -05:00
|
|
|
mbytes, err := json.Marshal(mnfst)
|
2022-03-21 12:37:23 -05:00
|
|
|
So(err, ShouldBeNil)
|
2021-10-05 04:12:22 -05:00
|
|
|
|
2022-06-24 08:08:47 -05:00
|
|
|
manifest, err = imgStore.PutImageManifest(repoName, tag, ispec.MediaTypeImageManifest,
|
|
|
|
mbytes)
|
2021-10-05 04:12:22 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
Convey("Blobs integrity not affected", func() {
|
|
|
|
buff := bytes.NewBufferString("")
|
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
res, err := storeCtlr.CheckAllBlobsIntegrity()
|
2021-10-05 04:12:22 -05:00
|
|
|
res.PrintScrubResults(buff)
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
space := regexp.MustCompile(`\s+`)
|
|
|
|
str := space.ReplaceAllString(buff.String(), " ")
|
|
|
|
actual := strings.TrimSpace(str)
|
|
|
|
So(actual, ShouldContainSubstring, "IMAGE NAME TAG STATUS ERROR")
|
|
|
|
So(actual, ShouldContainSubstring, "test 1.0 ok")
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Manifest integrity affected", func() {
|
|
|
|
// get content of manifest file
|
2021-12-13 14:23:31 -05:00
|
|
|
content, _, _, err := imgStore.GetImageManifest(repoName, manifest)
|
2021-10-05 04:12:22 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
// delete content of manifest file
|
|
|
|
manifest = strings.ReplaceAll(manifest, "sha256:", "")
|
2021-12-13 14:23:31 -05:00
|
|
|
manifestFile := path.Join(imgStore.RootDir(), repoName, "/blobs/sha256", manifest)
|
2021-10-05 04:12:22 -05:00
|
|
|
err = os.Truncate(manifestFile, 0)
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
buff := bytes.NewBufferString("")
|
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
res, err := storeCtlr.CheckAllBlobsIntegrity()
|
2021-10-05 04:12:22 -05:00
|
|
|
res.PrintScrubResults(buff)
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
space := regexp.MustCompile(`\s+`)
|
|
|
|
str := space.ReplaceAllString(buff.String(), " ")
|
|
|
|
actual := strings.TrimSpace(str)
|
|
|
|
So(actual, ShouldContainSubstring, "IMAGE NAME TAG STATUS ERROR")
|
|
|
|
// verify error message
|
|
|
|
So(actual, ShouldContainSubstring, "test 1.0 affected parse application/vnd.oci.image.manifest.v1+json")
|
|
|
|
|
|
|
|
// put manifest content back to file
|
2022-09-02 07:56:02 -05:00
|
|
|
err = os.WriteFile(manifestFile, content, 0o600)
|
2021-10-05 04:12:22 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Config integrity affected", func() {
|
|
|
|
// get content of config file
|
2021-12-13 14:23:31 -05:00
|
|
|
content, err := imgStore.GetBlobContent(repoName, config)
|
2021-10-05 04:12:22 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
// delete content of config file
|
|
|
|
config = strings.ReplaceAll(config, "sha256:", "")
|
2021-12-13 14:23:31 -05:00
|
|
|
configFile := path.Join(imgStore.RootDir(), repoName, "/blobs/sha256", config)
|
2021-10-05 04:12:22 -05:00
|
|
|
err = os.Truncate(configFile, 0)
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
buff := bytes.NewBufferString("")
|
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
res, err := storeCtlr.CheckAllBlobsIntegrity()
|
2021-10-05 04:12:22 -05:00
|
|
|
res.PrintScrubResults(buff)
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
space := regexp.MustCompile(`\s+`)
|
|
|
|
str := space.ReplaceAllString(buff.String(), " ")
|
|
|
|
actual := strings.TrimSpace(str)
|
|
|
|
So(actual, ShouldContainSubstring, "IMAGE NAME TAG STATUS ERROR")
|
|
|
|
So(actual, ShouldContainSubstring, "test 1.0 affected stat: parse application/vnd.oci.image.config.v1+json")
|
|
|
|
|
|
|
|
// put config content back to file
|
2022-09-02 07:56:02 -05:00
|
|
|
err = os.WriteFile(configFile, content, 0o600)
|
2021-10-05 04:12:22 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Layers integrity affected", func() {
|
|
|
|
// get content of layer
|
2021-12-13 14:23:31 -05:00
|
|
|
content, err := imgStore.GetBlobContent(repoName, layer)
|
2021-10-05 04:12:22 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
// delete content of layer file
|
|
|
|
layer = strings.ReplaceAll(layer, "sha256:", "")
|
2021-12-13 14:23:31 -05:00
|
|
|
layerFile := path.Join(imgStore.RootDir(), repoName, "/blobs/sha256", layer)
|
2021-10-05 04:12:22 -05:00
|
|
|
err = os.Truncate(layerFile, 0)
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
buff := bytes.NewBufferString("")
|
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
res, err := storeCtlr.CheckAllBlobsIntegrity()
|
2021-10-05 04:12:22 -05:00
|
|
|
res.PrintScrubResults(buff)
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
space := regexp.MustCompile(`\s+`)
|
|
|
|
str := space.ReplaceAllString(buff.String(), " ")
|
|
|
|
actual := strings.TrimSpace(str)
|
|
|
|
So(actual, ShouldContainSubstring, "IMAGE NAME TAG STATUS ERROR")
|
|
|
|
So(actual, ShouldContainSubstring, "test 1.0 affected blob: bad blob digest")
|
|
|
|
|
|
|
|
// put layer content back to file
|
2022-09-02 07:56:02 -05:00
|
|
|
err = os.WriteFile(layerFile, content, 0o600)
|
2021-10-05 04:12:22 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Layer not found", func() {
|
|
|
|
// delete layer file
|
|
|
|
layer = strings.ReplaceAll(layer, "sha256:", "")
|
2021-12-13 14:23:31 -05:00
|
|
|
layerFile := path.Join(imgStore.RootDir(), repoName, "/blobs/sha256", layer)
|
2021-10-05 04:12:22 -05:00
|
|
|
err = os.Remove(layerFile)
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
buff := bytes.NewBufferString("")
|
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
res, err := storeCtlr.CheckAllBlobsIntegrity()
|
2021-10-05 04:12:22 -05:00
|
|
|
res.PrintScrubResults(buff)
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
space := regexp.MustCompile(`\s+`)
|
|
|
|
str := space.ReplaceAllString(buff.String(), " ")
|
|
|
|
actual := strings.TrimSpace(str)
|
|
|
|
So(actual, ShouldContainSubstring, "IMAGE NAME TAG STATUS ERROR")
|
|
|
|
So(actual, ShouldContainSubstring, "test 1.0 affected blob: not found")
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|