335 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			335 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
| package storage
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"regexp"
 | |
| 	"strings"
 | |
| 	"testing"
 | |
| 
 | |
| 	"github.com/distribution/distribution/v3"
 | |
| 	"github.com/distribution/distribution/v3/manifest"
 | |
| 	"github.com/distribution/distribution/v3/manifest/ocischema"
 | |
| 	"github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
 | |
| 	"github.com/opencontainers/go-digest"
 | |
| 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 | |
| )
 | |
| 
 | |
| func TestVerifyOCIManifestNonDistributableLayer(t *testing.T) {
 | |
| 	ctx := context.Background()
 | |
| 	inmemoryDriver := inmemory.New()
 | |
| 	registry := createRegistry(t, inmemoryDriver,
 | |
| 		ManifestURLsAllowRegexp(regexp.MustCompile("^https?://foo")),
 | |
| 		ManifestURLsDenyRegexp(regexp.MustCompile("^https?://foo/nope")))
 | |
| 	repo := makeRepository(t, registry, "test")
 | |
| 	manifestService := makeManifestService(t, repo)
 | |
| 
 | |
| 	config, err := repo.Blobs(ctx).Put(ctx, v1.MediaTypeImageConfig, nil)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	layer, err := repo.Blobs(ctx).Put(ctx, v1.MediaTypeImageLayerGzip, nil)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	nonDistributableLayer := distribution.Descriptor{
 | |
| 		Digest:    "sha256:463435349086340864309863409683460843608348608934092322395278926a",
 | |
| 		Size:      6323,
 | |
| 		MediaType: v1.MediaTypeImageLayerNonDistributableGzip,
 | |
| 	}
 | |
| 
 | |
| 	emptyLayer := distribution.Descriptor{
 | |
| 		Digest: "",
 | |
| 	}
 | |
| 
 | |
| 	emptyGzipLayer := distribution.Descriptor{
 | |
| 		Digest:    "",
 | |
| 		MediaType: v1.MediaTypeImageLayerGzip,
 | |
| 	}
 | |
| 
 | |
| 	template := ocischema.Manifest{
 | |
| 		Versioned: manifest.Versioned{
 | |
| 			SchemaVersion: 2,
 | |
| 			MediaType:     v1.MediaTypeImageManifest,
 | |
| 		},
 | |
| 		Config: config,
 | |
| 	}
 | |
| 
 | |
| 	type testcase struct {
 | |
| 		BaseLayer distribution.Descriptor
 | |
| 		URLs      []string
 | |
| 		Err       error
 | |
| 	}
 | |
| 
 | |
| 	cases := []testcase{
 | |
| 		{
 | |
| 			nonDistributableLayer,
 | |
| 			nil,
 | |
| 			distribution.ErrManifestBlobUnknown{Digest: nonDistributableLayer.Digest},
 | |
| 		},
 | |
| 		{
 | |
| 			layer,
 | |
| 			[]string{"http://foo/bar"},
 | |
| 			nil,
 | |
| 		},
 | |
| 		{
 | |
| 			nonDistributableLayer,
 | |
| 			[]string{"file:///local/file"},
 | |
| 			errInvalidURL,
 | |
| 		},
 | |
| 		{
 | |
| 			nonDistributableLayer,
 | |
| 			[]string{"http://foo/bar#baz"},
 | |
| 			errInvalidURL,
 | |
| 		},
 | |
| 		{
 | |
| 			nonDistributableLayer,
 | |
| 			[]string{""},
 | |
| 			errInvalidURL,
 | |
| 		},
 | |
| 		{
 | |
| 			nonDistributableLayer,
 | |
| 			[]string{"https://foo/bar", ""},
 | |
| 			errInvalidURL,
 | |
| 		},
 | |
| 		{
 | |
| 			nonDistributableLayer,
 | |
| 			[]string{"", "https://foo/bar"},
 | |
| 			errInvalidURL,
 | |
| 		},
 | |
| 		{
 | |
| 			nonDistributableLayer,
 | |
| 			[]string{"http://nope/bar"},
 | |
| 			errInvalidURL,
 | |
| 		},
 | |
| 		{
 | |
| 			nonDistributableLayer,
 | |
| 			[]string{"http://foo/nope"},
 | |
| 			errInvalidURL,
 | |
| 		},
 | |
| 		{
 | |
| 			nonDistributableLayer,
 | |
| 			[]string{"http://foo/bar"},
 | |
| 			nil,
 | |
| 		},
 | |
| 		{
 | |
| 			nonDistributableLayer,
 | |
| 			[]string{"https://foo/bar"},
 | |
| 			nil,
 | |
| 		},
 | |
| 		{
 | |
| 			emptyLayer,
 | |
| 			[]string{"https://foo/empty"},
 | |
| 			digest.ErrDigestInvalidFormat,
 | |
| 		},
 | |
| 		{
 | |
| 			emptyLayer,
 | |
| 			[]string{},
 | |
| 			digest.ErrDigestInvalidFormat,
 | |
| 		},
 | |
| 		{
 | |
| 			emptyGzipLayer,
 | |
| 			[]string{"https://foo/empty"},
 | |
| 			digest.ErrDigestInvalidFormat,
 | |
| 		},
 | |
| 		{
 | |
| 			emptyGzipLayer,
 | |
| 			[]string{},
 | |
| 			digest.ErrDigestInvalidFormat,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, c := range cases {
 | |
| 		m := template
 | |
| 		l := c.BaseLayer
 | |
| 		l.URLs = c.URLs
 | |
| 		m.Layers = []distribution.Descriptor{l}
 | |
| 		dm, err := ocischema.FromStruct(m)
 | |
| 		if err != nil {
 | |
| 			t.Error(err)
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		_, err = manifestService.Put(ctx, dm)
 | |
| 		if verr, ok := err.(distribution.ErrManifestVerification); ok {
 | |
| 			// Extract the first error
 | |
| 			if len(verr) == 2 {
 | |
| 				if _, ok = verr[1].(distribution.ErrManifestBlobUnknown); ok {
 | |
| 					err = verr[0]
 | |
| 				}
 | |
| 			} else if len(verr) == 1 {
 | |
| 				err = verr[0]
 | |
| 			}
 | |
| 		}
 | |
| 		if err != c.Err {
 | |
| 			t.Errorf("%#v: expected %v, got %v", l, c.Err, err)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestVerifyOCIManifestBlobLayerAndConfig(t *testing.T) {
 | |
| 	ctx := context.Background()
 | |
| 	inmemoryDriver := inmemory.New()
 | |
| 	registry := createRegistry(t, inmemoryDriver,
 | |
| 		ManifestURLsAllowRegexp(regexp.MustCompile("^https?://foo")),
 | |
| 		ManifestURLsDenyRegexp(regexp.MustCompile("^https?://foo/nope")))
 | |
| 
 | |
| 	repo := makeRepository(t, registry, strings.ToLower(t.Name()))
 | |
| 	manifestService := makeManifestService(t, repo)
 | |
| 
 | |
| 	config, err := repo.Blobs(ctx).Put(ctx, v1.MediaTypeImageConfig, nil)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	layer, err := repo.Blobs(ctx).Put(ctx, v1.MediaTypeImageLayerGzip, nil)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	template := ocischema.Manifest{
 | |
| 		Versioned: manifest.Versioned{
 | |
| 			SchemaVersion: 2,
 | |
| 			MediaType:     v1.MediaTypeImageManifest,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	checkFn := func(m ocischema.Manifest, rerr error) {
 | |
| 		dm, err := ocischema.FromStruct(m)
 | |
| 		if err != nil {
 | |
| 			t.Error(err)
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		_, err = manifestService.Put(ctx, dm)
 | |
| 		if verr, ok := err.(distribution.ErrManifestVerification); ok {
 | |
| 			// Extract the first error
 | |
| 			if len(verr) == 2 {
 | |
| 				if _, ok = verr[1].(distribution.ErrManifestBlobUnknown); ok {
 | |
| 					err = verr[0]
 | |
| 				}
 | |
| 			} else if len(verr) == 1 {
 | |
| 				err = verr[0]
 | |
| 			}
 | |
| 		}
 | |
| 		if err != rerr {
 | |
| 			t.Errorf("%#v: expected %v, got %v", m, rerr, err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	type testcase struct {
 | |
| 		Desc distribution.Descriptor
 | |
| 		URLs []string
 | |
| 		Err  error
 | |
| 	}
 | |
| 
 | |
| 	layercases := []testcase{
 | |
| 		// empty media type
 | |
| 		{
 | |
| 			distribution.Descriptor{},
 | |
| 			[]string{"http://foo/bar"},
 | |
| 			digest.ErrDigestInvalidFormat,
 | |
| 		},
 | |
| 		{
 | |
| 			distribution.Descriptor{},
 | |
| 			nil,
 | |
| 			digest.ErrDigestInvalidFormat,
 | |
| 		},
 | |
| 		// unknown media type, but blob is present
 | |
| 		{
 | |
| 			distribution.Descriptor{
 | |
| 				Digest: layer.Digest,
 | |
| 			},
 | |
| 			nil,
 | |
| 			nil,
 | |
| 		},
 | |
| 		{
 | |
| 			distribution.Descriptor{
 | |
| 				Digest: layer.Digest,
 | |
| 			},
 | |
| 			[]string{"http://foo/bar"},
 | |
| 			nil,
 | |
| 		},
 | |
| 		// gzip layer, but invalid digest
 | |
| 		{
 | |
| 			distribution.Descriptor{
 | |
| 				MediaType: v1.MediaTypeImageLayerGzip,
 | |
| 			},
 | |
| 			nil,
 | |
| 			digest.ErrDigestInvalidFormat,
 | |
| 		},
 | |
| 		{
 | |
| 			distribution.Descriptor{
 | |
| 				MediaType: v1.MediaTypeImageLayerGzip,
 | |
| 			},
 | |
| 			[]string{"https://foo/bar"},
 | |
| 			digest.ErrDigestInvalidFormat,
 | |
| 		},
 | |
| 		{
 | |
| 			distribution.Descriptor{
 | |
| 				MediaType: v1.MediaTypeImageLayerGzip,
 | |
| 				Digest:    digest.Digest("invalid"),
 | |
| 			},
 | |
| 			nil,
 | |
| 			digest.ErrDigestInvalidFormat,
 | |
| 		},
 | |
| 		// normal uploaded gzip layer
 | |
| 		{
 | |
| 			layer,
 | |
| 			nil,
 | |
| 			nil,
 | |
| 		},
 | |
| 		{
 | |
| 			layer,
 | |
| 			[]string{"https://foo/bar"},
 | |
| 			nil,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, c := range layercases {
 | |
| 		m := template
 | |
| 		m.Config = config
 | |
| 
 | |
| 		l := c.Desc
 | |
| 		l.URLs = c.URLs
 | |
| 
 | |
| 		m.Layers = []distribution.Descriptor{l}
 | |
| 
 | |
| 		checkFn(m, c.Err)
 | |
| 	}
 | |
| 
 | |
| 	configcases := []testcase{
 | |
| 		// valid config
 | |
| 		{
 | |
| 			config,
 | |
| 			nil,
 | |
| 			nil,
 | |
| 		},
 | |
| 		// invalid digest
 | |
| 		{
 | |
| 			distribution.Descriptor{
 | |
| 				MediaType: v1.MediaTypeImageConfig,
 | |
| 			},
 | |
| 			[]string{"https://foo/bar"},
 | |
| 			digest.ErrDigestInvalidFormat,
 | |
| 		},
 | |
| 		{
 | |
| 			distribution.Descriptor{
 | |
| 				MediaType: v1.MediaTypeImageConfig,
 | |
| 				Digest:    digest.Digest("invalid"),
 | |
| 			},
 | |
| 			nil,
 | |
| 			digest.ErrDigestInvalidFormat,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, c := range configcases {
 | |
| 		m := template
 | |
| 		m.Config = c.Desc
 | |
| 		m.Config.URLs = c.URLs
 | |
| 
 | |
| 		checkFn(m, c.Err)
 | |
| 	}
 | |
| }
 |