github.com/npaton/distribution@v2.3.1-rc.0+incompatible/registry/storage/manifeststore_test.go (about)

     1  package storage
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"reflect"
     7  	"testing"
     8  
     9  	"github.com/docker/distribution"
    10  	"github.com/docker/distribution/context"
    11  	"github.com/docker/distribution/digest"
    12  	"github.com/docker/distribution/manifest"
    13  	"github.com/docker/distribution/manifest/schema1"
    14  	"github.com/docker/distribution/reference"
    15  	"github.com/docker/distribution/registry/storage/cache/memory"
    16  	"github.com/docker/distribution/registry/storage/driver"
    17  	"github.com/docker/distribution/registry/storage/driver/inmemory"
    18  	"github.com/docker/distribution/testutil"
    19  	"github.com/docker/libtrust"
    20  )
    21  
    22  type manifestStoreTestEnv struct {
    23  	ctx        context.Context
    24  	driver     driver.StorageDriver
    25  	registry   distribution.Namespace
    26  	repository distribution.Repository
    27  	name       reference.Named
    28  	tag        string
    29  }
    30  
    31  func newManifestStoreTestEnv(t *testing.T, name reference.Named, tag string, options ...RegistryOption) *manifestStoreTestEnv {
    32  	ctx := context.Background()
    33  	driver := inmemory.New()
    34  	registry, err := NewRegistry(ctx, driver, options...)
    35  	if err != nil {
    36  		t.Fatalf("error creating registry: %v", err)
    37  	}
    38  
    39  	repo, err := registry.Repository(ctx, name)
    40  	if err != nil {
    41  		t.Fatalf("unexpected error getting repo: %v", err)
    42  	}
    43  
    44  	return &manifestStoreTestEnv{
    45  		ctx:        ctx,
    46  		driver:     driver,
    47  		registry:   registry,
    48  		repository: repo,
    49  		name:       name,
    50  		tag:        tag,
    51  	}
    52  }
    53  
    54  func TestManifestStorage(t *testing.T) {
    55  	testManifestStorage(t, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableDelete, EnableRedirect)
    56  }
    57  
    58  func TestManifestStorageDisabledSignatures(t *testing.T) {
    59  	k, err := libtrust.GenerateECP256PrivateKey()
    60  	if err != nil {
    61  		t.Fatal(err)
    62  	}
    63  	testManifestStorage(t, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableDelete, EnableRedirect, DisableSchema1Signatures, Schema1SigningKey(k))
    64  }
    65  
    66  func testManifestStorage(t *testing.T, options ...RegistryOption) {
    67  	repoName, _ := reference.ParseNamed("foo/bar")
    68  	env := newManifestStoreTestEnv(t, repoName, "thetag", options...)
    69  	ctx := context.Background()
    70  	ms, err := env.repository.Manifests(ctx)
    71  	if err != nil {
    72  		t.Fatal(err)
    73  	}
    74  	equalSignatures := env.registry.(*registry).schema1SignaturesEnabled
    75  
    76  	m := schema1.Manifest{
    77  		Versioned: manifest.Versioned{
    78  			SchemaVersion: 1,
    79  		},
    80  		Name: env.name.Name(),
    81  		Tag:  env.tag,
    82  	}
    83  
    84  	// Build up some test layers and add them to the manifest, saving the
    85  	// readseekers for upload later.
    86  	testLayers := map[digest.Digest]io.ReadSeeker{}
    87  	for i := 0; i < 2; i++ {
    88  		rs, ds, err := testutil.CreateRandomTarFile()
    89  		if err != nil {
    90  			t.Fatalf("unexpected error generating test layer file")
    91  		}
    92  		dgst := digest.Digest(ds)
    93  
    94  		testLayers[digest.Digest(dgst)] = rs
    95  		m.FSLayers = append(m.FSLayers, schema1.FSLayer{
    96  			BlobSum: dgst,
    97  		})
    98  		m.History = append(m.History, schema1.History{
    99  			V1Compatibility: "",
   100  		})
   101  
   102  	}
   103  
   104  	pk, err := libtrust.GenerateECP256PrivateKey()
   105  	if err != nil {
   106  		t.Fatalf("unexpected error generating private key: %v", err)
   107  	}
   108  
   109  	sm, merr := schema1.Sign(&m, pk)
   110  	if merr != nil {
   111  		t.Fatalf("error signing manifest: %v", err)
   112  	}
   113  
   114  	_, err = ms.Put(ctx, sm)
   115  	if err == nil {
   116  		t.Fatalf("expected errors putting manifest with full verification")
   117  	}
   118  
   119  	switch err := err.(type) {
   120  	case distribution.ErrManifestVerification:
   121  		if len(err) != 2 {
   122  			t.Fatalf("expected 2 verification errors: %#v", err)
   123  		}
   124  
   125  		for _, err := range err {
   126  			if _, ok := err.(distribution.ErrManifestBlobUnknown); !ok {
   127  				t.Fatalf("unexpected error type: %v", err)
   128  			}
   129  		}
   130  	default:
   131  		t.Fatalf("unexpected error verifying manifest: %v", err)
   132  	}
   133  
   134  	// Now, upload the layers that were missing!
   135  	for dgst, rs := range testLayers {
   136  		wr, err := env.repository.Blobs(env.ctx).Create(env.ctx)
   137  		if err != nil {
   138  			t.Fatalf("unexpected error creating test upload: %v", err)
   139  		}
   140  
   141  		if _, err := io.Copy(wr, rs); err != nil {
   142  			t.Fatalf("unexpected error copying to upload: %v", err)
   143  		}
   144  
   145  		if _, err := wr.Commit(env.ctx, distribution.Descriptor{Digest: dgst}); err != nil {
   146  			t.Fatalf("unexpected error finishing upload: %v", err)
   147  		}
   148  	}
   149  
   150  	var manifestDigest digest.Digest
   151  	if manifestDigest, err = ms.Put(ctx, sm); err != nil {
   152  		t.Fatalf("unexpected error putting manifest: %v", err)
   153  	}
   154  
   155  	exists, err := ms.Exists(ctx, manifestDigest)
   156  	if err != nil {
   157  		t.Fatalf("unexpected error checking manifest existence: %#v", err)
   158  	}
   159  
   160  	if !exists {
   161  		t.Fatalf("manifest should exist")
   162  	}
   163  
   164  	fromStore, err := ms.Get(ctx, manifestDigest)
   165  	if err != nil {
   166  		t.Fatalf("unexpected error fetching manifest: %v", err)
   167  	}
   168  
   169  	fetchedManifest, ok := fromStore.(*schema1.SignedManifest)
   170  	if !ok {
   171  		t.Fatalf("unexpected manifest type from signedstore")
   172  	}
   173  
   174  	if !bytes.Equal(fetchedManifest.Canonical, sm.Canonical) {
   175  		t.Fatalf("fetched payload does not match original payload: %q != %q", fetchedManifest.Canonical, sm.Canonical)
   176  	}
   177  
   178  	if equalSignatures {
   179  		if !reflect.DeepEqual(fetchedManifest, sm) {
   180  			t.Fatalf("fetched manifest not equal: %#v != %#v", fetchedManifest.Manifest, sm.Manifest)
   181  		}
   182  	}
   183  
   184  	_, pl, err := fetchedManifest.Payload()
   185  	if err != nil {
   186  		t.Fatalf("error getting payload %#v", err)
   187  	}
   188  
   189  	fetchedJWS, err := libtrust.ParsePrettySignature(pl, "signatures")
   190  	if err != nil {
   191  		t.Fatalf("unexpected error parsing jws: %v", err)
   192  	}
   193  
   194  	payload, err := fetchedJWS.Payload()
   195  	if err != nil {
   196  		t.Fatalf("unexpected error extracting payload: %v", err)
   197  	}
   198  
   199  	// Now that we have a payload, take a moment to check that the manifest is
   200  	// return by the payload digest.
   201  
   202  	dgst := digest.FromBytes(payload)
   203  	exists, err = ms.Exists(ctx, dgst)
   204  	if err != nil {
   205  		t.Fatalf("error checking manifest existence by digest: %v", err)
   206  	}
   207  
   208  	if !exists {
   209  		t.Fatalf("manifest %s should exist", dgst)
   210  	}
   211  
   212  	fetchedByDigest, err := ms.Get(ctx, dgst)
   213  	if err != nil {
   214  		t.Fatalf("unexpected error fetching manifest by digest: %v", err)
   215  	}
   216  
   217  	byDigestManifest, ok := fetchedByDigest.(*schema1.SignedManifest)
   218  	if !ok {
   219  		t.Fatalf("unexpected manifest type from signedstore")
   220  	}
   221  
   222  	if !bytes.Equal(byDigestManifest.Canonical, fetchedManifest.Canonical) {
   223  		t.Fatalf("fetched manifest not equal: %q != %q", byDigestManifest.Canonical, fetchedManifest.Canonical)
   224  	}
   225  
   226  	if equalSignatures {
   227  		if !reflect.DeepEqual(fetchedByDigest, fetchedManifest) {
   228  			t.Fatalf("fetched manifest not equal: %#v != %#v", fetchedByDigest, fetchedManifest)
   229  		}
   230  	}
   231  
   232  	sigs, err := fetchedJWS.Signatures()
   233  	if err != nil {
   234  		t.Fatalf("unable to extract signatures: %v", err)
   235  	}
   236  
   237  	if len(sigs) != 1 {
   238  		t.Fatalf("unexpected number of signatures: %d != %d", len(sigs), 1)
   239  	}
   240  
   241  	// Now, push the same manifest with a different key
   242  	pk2, err := libtrust.GenerateECP256PrivateKey()
   243  	if err != nil {
   244  		t.Fatalf("unexpected error generating private key: %v", err)
   245  	}
   246  
   247  	sm2, err := schema1.Sign(&m, pk2)
   248  	if err != nil {
   249  		t.Fatalf("unexpected error signing manifest: %v", err)
   250  	}
   251  	_, pl, err = sm2.Payload()
   252  	if err != nil {
   253  		t.Fatalf("error getting payload %#v", err)
   254  	}
   255  
   256  	jws2, err := libtrust.ParsePrettySignature(pl, "signatures")
   257  	if err != nil {
   258  		t.Fatalf("error parsing signature: %v", err)
   259  	}
   260  
   261  	sigs2, err := jws2.Signatures()
   262  	if err != nil {
   263  		t.Fatalf("unable to extract signatures: %v", err)
   264  	}
   265  
   266  	if len(sigs2) != 1 {
   267  		t.Fatalf("unexpected number of signatures: %d != %d", len(sigs2), 1)
   268  	}
   269  
   270  	if manifestDigest, err = ms.Put(ctx, sm2); err != nil {
   271  		t.Fatalf("unexpected error putting manifest: %v", err)
   272  	}
   273  
   274  	fromStore, err = ms.Get(ctx, manifestDigest)
   275  	if err != nil {
   276  		t.Fatalf("unexpected error fetching manifest: %v", err)
   277  	}
   278  
   279  	fetched, ok := fromStore.(*schema1.SignedManifest)
   280  	if !ok {
   281  		t.Fatalf("unexpected type from signed manifeststore : %T", fetched)
   282  	}
   283  
   284  	if _, err := schema1.Verify(fetched); err != nil {
   285  		t.Fatalf("unexpected error verifying manifest: %v", err)
   286  	}
   287  
   288  	// Assemble our payload and two signatures to get what we expect!
   289  	expectedJWS, err := libtrust.NewJSONSignature(payload, sigs[0], sigs2[0])
   290  	if err != nil {
   291  		t.Fatalf("unexpected error merging jws: %v", err)
   292  	}
   293  
   294  	expectedSigs, err := expectedJWS.Signatures()
   295  	if err != nil {
   296  		t.Fatalf("unexpected error getting expected signatures: %v", err)
   297  	}
   298  
   299  	_, pl, err = fetched.Payload()
   300  	if err != nil {
   301  		t.Fatalf("error getting payload %#v", err)
   302  	}
   303  
   304  	receivedJWS, err := libtrust.ParsePrettySignature(pl, "signatures")
   305  	if err != nil {
   306  		t.Fatalf("unexpected error parsing jws: %v", err)
   307  	}
   308  
   309  	receivedPayload, err := receivedJWS.Payload()
   310  	if err != nil {
   311  		t.Fatalf("unexpected error extracting received payload: %v", err)
   312  	}
   313  
   314  	if !bytes.Equal(receivedPayload, payload) {
   315  		t.Fatalf("payloads are not equal")
   316  	}
   317  
   318  	if equalSignatures {
   319  		receivedSigs, err := receivedJWS.Signatures()
   320  		if err != nil {
   321  			t.Fatalf("error getting signatures: %v", err)
   322  		}
   323  
   324  		for i, sig := range receivedSigs {
   325  			if !bytes.Equal(sig, expectedSigs[i]) {
   326  				t.Fatalf("mismatched signatures from remote: %v != %v", string(sig), string(expectedSigs[i]))
   327  			}
   328  		}
   329  	}
   330  
   331  	// Test deleting manifests
   332  	err = ms.Delete(ctx, dgst)
   333  	if err != nil {
   334  		t.Fatalf("unexpected an error deleting manifest by digest: %v", err)
   335  	}
   336  
   337  	exists, err = ms.Exists(ctx, dgst)
   338  	if err != nil {
   339  		t.Fatalf("Error querying manifest existence")
   340  	}
   341  	if exists {
   342  		t.Errorf("Deleted manifest should not exist")
   343  	}
   344  
   345  	deletedManifest, err := ms.Get(ctx, dgst)
   346  	if err == nil {
   347  		t.Errorf("Unexpected success getting deleted manifest")
   348  	}
   349  	switch err.(type) {
   350  	case distribution.ErrManifestUnknownRevision:
   351  		break
   352  	default:
   353  		t.Errorf("Unexpected error getting deleted manifest: %s", reflect.ValueOf(err).Type())
   354  	}
   355  
   356  	if deletedManifest != nil {
   357  		t.Errorf("Deleted manifest get returned non-nil")
   358  	}
   359  
   360  	// Re-upload should restore manifest to a good state
   361  	_, err = ms.Put(ctx, sm)
   362  	if err != nil {
   363  		t.Errorf("Error re-uploading deleted manifest")
   364  	}
   365  
   366  	exists, err = ms.Exists(ctx, dgst)
   367  	if err != nil {
   368  		t.Fatalf("Error querying manifest existence")
   369  	}
   370  	if !exists {
   371  		t.Errorf("Restored manifest should exist")
   372  	}
   373  
   374  	deletedManifest, err = ms.Get(ctx, dgst)
   375  	if err != nil {
   376  		t.Errorf("Unexpected error getting manifest")
   377  	}
   378  	if deletedManifest == nil {
   379  		t.Errorf("Deleted manifest get returned non-nil")
   380  	}
   381  
   382  	r, err := NewRegistry(ctx, env.driver, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableRedirect)
   383  	if err != nil {
   384  		t.Fatalf("error creating registry: %v", err)
   385  	}
   386  	repo, err := r.Repository(ctx, env.name)
   387  	if err != nil {
   388  		t.Fatalf("unexpected error getting repo: %v", err)
   389  	}
   390  	ms, err = repo.Manifests(ctx)
   391  	if err != nil {
   392  		t.Fatal(err)
   393  	}
   394  	err = ms.Delete(ctx, dgst)
   395  	if err == nil {
   396  		t.Errorf("Unexpected success deleting while disabled")
   397  	}
   398  }
   399  
   400  // TestLinkPathFuncs ensures that the link path functions behavior are locked
   401  // down and implemented as expected.
   402  func TestLinkPathFuncs(t *testing.T) {
   403  	for _, testcase := range []struct {
   404  		repo       string
   405  		digest     digest.Digest
   406  		linkPathFn linkPathFunc
   407  		expected   string
   408  	}{
   409  		{
   410  			repo:       "foo/bar",
   411  			digest:     "sha256:deadbeaf98fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
   412  			linkPathFn: blobLinkPath,
   413  			expected:   "/docker/registry/v2/repositories/foo/bar/_layers/sha256/deadbeaf98fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/link",
   414  		},
   415  		{
   416  			repo:       "foo/bar",
   417  			digest:     "sha256:deadbeaf98fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
   418  			linkPathFn: manifestRevisionLinkPath,
   419  			expected:   "/docker/registry/v2/repositories/foo/bar/_manifests/revisions/sha256/deadbeaf98fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/link",
   420  		},
   421  	} {
   422  		p, err := testcase.linkPathFn(testcase.repo, testcase.digest)
   423  		if err != nil {
   424  			t.Fatalf("unexpected error calling linkPathFn(pm, %q, %q): %v", testcase.repo, testcase.digest, err)
   425  		}
   426  
   427  		if p != testcase.expected {
   428  			t.Fatalf("incorrect path returned: %q != %q", p, testcase.expected)
   429  		}
   430  	}
   431  
   432  }