Skip to content

Commit

Permalink
refactor(scrub): replace umoci logic in scrub implementation
Browse files Browse the repository at this point in the history
- implement scrub also for S3 storage by replacing umoci
- change scrub implementation for ImageIndex
- take the `Subject` into consideration when running scrub

Signed-off-by: Andreea-Lupu <[email protected]>
  • Loading branch information
Andreea-Lupu committed Sep 22, 2023
1 parent 4e04be4 commit b8f4d67
Show file tree
Hide file tree
Showing 4 changed files with 275 additions and 130 deletions.
1 change: 1 addition & 0 deletions errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ var (
ErrCliBadConfig = errors.New("cli: bad config")
ErrRepoNotFound = errors.New("repository: not found")
ErrRepoBadVersion = errors.New("repository: unsupported layout version")
ErrRepoBadLayout = errors.New("repository: invaid layout")
ErrManifestNotFound = errors.New("manifest: not found")
ErrBadManifest = errors.New("manifest: invalid contents")
ErrUploadNotFound = errors.New("uploads: not found")
Expand Down
22 changes: 8 additions & 14 deletions pkg/extensions/extension_scrub.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
201 changes: 134 additions & 67 deletions pkg/storage/scrub.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -92,84 +88,171 @@ 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
}
var lockLatency time.Time

imgStore.RLock(&lockLatency)
defer imgStore.RUnlock(&lockLatency)

oci, err := umoci.OpenLayout(dir)
// check image structure / layout
ok, err := imgStore.ValidateRepo(imageName)
if err != nil {
return results, err
}

defer oci.Close()

var lockLatency time.Time

imgStore.RLock(&lockLatency)
defer imgStore.RUnlock(&lockLatency)
if !ok {
return results, errors.ErrRepoBadLayout
}

buf, err := os.ReadFile(path.Join(dir, "index.json"))
// 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{}
listOfManifests := 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
}
tag := manifest.Annotations[ispec.AnnotationRefName]
imageResult := getManifestResult(ctx, manifest, imgStore, imageName, tag, listOfManifests)
results = append(results, imageResult)
}

return results, nil
}

func getManifestResult(
ctx context.Context, manifest ispec.Descriptor, imgStore storageTypes.ImageStore, imageName, tag string,
listOfManifests map[godigest.Digest]ScrubImageResult,
) ScrubImageResult {
res, ok := listOfManifests[manifest.Digest]
if ok {
return ScrubImageResult{
ImageName: imageName,
Tag: tag,
Status: res.Status,
Error: res.Error,
}
}

switch manifest.MediaType {
case ispec.MediaTypeImageIndex:
buf, err := imgStore.GetBlobContent(imageName, manifest.Digest)
if err != nil {
imgRes := getResult(imageName, tag, errors.ErrBadBlobDigest)
listOfManifests[manifest.Digest] = imgRes

return imgRes
}

var idx ispec.Index
if err := json.Unmarshal(buf, &idx); err != nil {
imgRes := getResult(imageName, tag, errors.ErrBadBlobDigest)
listOfManifests[manifest.Digest] = imgRes

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)
return imgRes
}

continue
for _, m := range idx.Manifests {
imgRes := getManifestResult(ctx, m, imgStore, imageName, tag, listOfManifests)
listOfManifests[manifest.Digest] = imgRes

if imgRes.Error != "" {
return imgRes
}
}

listOfManifests = append(listOfManifests, idx.Manifests...)
} else if manifest.MediaType == ispec.MediaTypeImageManifest {
listOfManifests = append(listOfManifests, manifest)
okResult := getResult(imageName, tag, nil)

listOfManifests[manifest.Digest] = okResult

// check subject if exists
if idx.Subject != nil {
imgRes := getManifestResult(ctx, *idx.Subject, imgStore, imageName, tag, listOfManifests)
listOfManifests[manifest.Digest] = imgRes

return imgRes
}
}

for _, m := range listOfManifests {
tag := m.Annotations[ispec.AnnotationRefName]
imageResult := CheckIntegrity(ctx, imageName, tag, oci, m, dir)
results = append(results, imageResult)
}
return okResult
case ispec.MediaTypeImageManifest:
imgRes := CheckIntegrity(ctx, imageName, tag, manifest, imgStore)
listOfManifests[manifest.Digest] = imgRes

return results, nil
// if integrity ok then check subject if exists
if imgRes.Error == "" {
manifestContent, _ := imgStore.GetBlobContent(imageName, manifest.Digest)

var m ispec.Manifest

_ = json.Unmarshal(manifestContent, &m)

if m.Subject != nil {
imgRes = getManifestResult(ctx, *m.Subject, imgStore, imageName, tag, listOfManifests)
listOfManifests[manifest.Digest] = imgRes

return imgRes
}
}

return imgRes
default:
return getResult(imageName, tag, errors.ErrBadManifest)
}
}

func CheckIntegrity(ctx context.Context, imageName, tagName string, oci casext.Engine, manifest ispec.Descriptor, dir string) ScrubImageResult { //nolint: lll
func CheckIntegrity(ctx context.Context, imageName, tagName string, manifest ispec.Descriptor, imgStore storageTypes.ImageStore) ScrubImageResult { //nolint: lll
// check manifest and config
if _, err := umoci.Stat(ctx, oci, manifest); err != nil {
if err := CheckManifestAndConfig(imageName, manifest, imgStore); err != nil {
return getResult(imageName, tagName, err)
}

// check layers
return CheckLayers(ctx, imageName, tagName, dir, manifest)
return CheckLayers(ctx, imageName, tagName, manifest, imgStore)
}

func CheckManifestAndConfig(imageName string, manifestDesc ispec.Descriptor, imgStore storageTypes.ImageStore) error {
if manifestDesc.MediaType != ispec.MediaTypeImageManifest {
return errors.ErrBadManifest
}

manifestContent, err := imgStore.GetBlobContent(imageName, manifestDesc.Digest)
if err != nil {
return err
}

var manifest ispec.Manifest

err = json.Unmarshal(manifestContent, &manifest)
if err != nil {
return errors.ErrBadManifest
}

configContent, err := imgStore.GetBlobContent(imageName, manifest.Config.Digest)
if err != nil {
return err
}

var config ispec.Image

err = json.Unmarshal(configContent, &config)
if err != nil {
return errors.ErrBadConfig
}

return nil
}

func CheckLayers(ctx context.Context, imageName, tagName, dir string, manifest ispec.Descriptor) ScrubImageResult {
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)

Expand All @@ -178,36 +261,20 @@ func CheckLayers(ctx context.Context, imageName, tagName, dir string, manifest i

var man ispec.Manifest
if err := json.Unmarshal(buf, &man); err != nil {
imageRes = getResult(imageName, tagName, err)
imageRes = getResult(imageName, tagName, 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)

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)
Expand Down
Loading

0 comments on commit b8f4d67

Please sign in to comment.