func newServerSwizzler(t *testing.T) (map[string][]byte, *testutils.MetadataSwizzler) { serverMeta, cs, err := testutils.NewRepoMetadata("docker.com/notary", metadataDelegations...) require.NoError(t, err) serverSwizzler := testutils.NewMetadataSwizzler("docker.com/notary", serverMeta, cs) require.NoError(t, err) return serverMeta, serverSwizzler }
// we just want sample metadata for a role - so we can build cached metadata // and use it once. func getSampleMeta(t *testing.T) (map[string][]byte, string) { gun := "docker.com/notary" delgNames := []string{"targets/a", "targets/a/b", "targets/a/b/force_parent_metadata"} if _cachedMeta == nil { meta, _, err := testutils.NewRepoMetadata(gun, delgNames...) require.NoError(t, err) _cachedMeta = meta } return _cachedMeta, gun }
func TestRepoPrefixDoesNotMatch(t *testing.T) { gun := "docker.io/notary" meta, cs, err := testutils.NewRepoMetadata(gun) require.NoError(t, err) s := storage.NewMemStorage() ctx := context.WithValue(context.Background(), notary.CtxKeyMetaStore, s) ctx = context.WithValue(ctx, notary.CtxKeyKeyAlgo, data.ED25519Key) snChecksumBytes := sha256.Sum256(meta[data.CanonicalSnapshotRole]) // successful gets handler := RootHandler(ctx, nil, cs, nil, nil, []string{"nope"}) ts := httptest.NewServer(handler) url := fmt.Sprintf("%s/v2/%s/_trust/tuf/", ts.URL, gun) uploader, err := store.NewHTTPStore(url, "", "json", "key", http.DefaultTransport) require.NoError(t, err) require.Error(t, uploader.SetMulti(meta)) // update the storage so we don't fail just because the metadata is missing for _, roleName := range data.BaseRoles { require.NoError(t, s.UpdateCurrent(gun, storage.MetaUpdate{ Role: roleName, Data: meta[roleName], Version: 1, })) } _, err = uploader.GetSized(data.CanonicalSnapshotRole, notary.MaxDownloadSize) require.Error(t, err) _, err = uploader.GetSized( tufutils.ConsistentName(data.CanonicalSnapshotRole, snChecksumBytes[:]), notary.MaxDownloadSize) require.Error(t, err) _, err = uploader.GetKey(data.CanonicalTimestampRole) require.Error(t, err) // the httpstore doesn't actually delete all, so we do it manually req, err := http.NewRequest("DELETE", url, nil) require.NoError(t, err) res, err := http.DefaultTransport.RoundTrip(req) require.NoError(t, err) defer res.Body.Close() require.Equal(t, http.StatusNotFound, res.StatusCode) }
// If the parent is not in the store, then the parent must be in the update else // validation fails. func TestValidateTargetsParentInUpdate(t *testing.T) { gun := "docker.com/notary" delgName := "targets/level1" metadata, _, err := testutils.NewRepoMetadata(gun, delgName, path.Join(delgName, "other")) require.NoError(t, err) store := storage.NewMemStorage() // load the root into the builder, else we can't load anything else builder := tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{}) require.NoError(t, builder.Load(data.CanonicalRootRole, metadata[data.CanonicalRootRole], 0, false)) targetsUpdate := storage.MetaUpdate{ Role: data.CanonicalTargetsRole, Version: 1, Data: []byte("Invalid metadata"), } delgUpdate := storage.MetaUpdate{ Role: delgName, Version: 1, Data: metadata[delgName], } upload := map[string]storage.MetaUpdate{ "targets/level1": delgUpdate, data.CanonicalTargetsRole: targetsUpdate, } // parent update not readable - fail _, err = loadAndValidateTargets(gun, builder, upload, store) require.Error(t, err) require.IsType(t, validation.ErrBadTargets{}, err) // because we sort the roles, the list of returned updates // will contain shallower roles first, in this case "targets", // and then "targets/level1" targetsUpdate.Data = metadata[data.CanonicalTargetsRole] upload[data.CanonicalTargetsRole] = targetsUpdate updates, err := loadAndValidateTargets(gun, builder, upload, store) require.NoError(t, err) require.Equal(t, []storage.MetaUpdate{targetsUpdate, delgUpdate}, updates) }
func TestGenerateSnapshotNoKey(t *testing.T) { gun := "docker.com/notary" metadata, cs, err := testutils.NewRepoMetadata(gun) require.NoError(t, err) store := storage.NewMemStorage() // delete snapshot key in the cryptoservice for _, keyID := range cs.ListKeys(data.CanonicalSnapshotRole) { require.NoError(t, cs.RemoveKey(keyID)) } builder := tuf.NewRepoBuilder(gun, cs, trustpinning.TrustPinConfig{}) // only load root and targets require.NoError(t, builder.Load(data.CanonicalRootRole, metadata[data.CanonicalRootRole], 0, false)) require.NoError(t, builder.Load(data.CanonicalTargetsRole, metadata[data.CanonicalTargetsRole], 0, false)) _, err = generateSnapshot(gun, builder, store) require.Error(t, err) require.IsType(t, validation.ErrBadHierarchy{}, err) }
// Update can succeed even if we cannot write any metadata to the repo (assuming // no data in the repo) func TestUpdateSucceedsEvenIfCannotWriteNewRepo(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode") } serverMeta, _, err := testutils.NewRepoMetadata("docker.com/notary", metadataDelegations...) require.NoError(t, err) ts := readOnlyServer(t, store.NewMemoryStore(serverMeta), http.StatusNotFound, "docker.com/notary") defer ts.Close() for role := range serverMeta { repo := newBlankRepo(t, ts.URL) repo.fileStore = &unwritableStore{MetadataStore: repo.fileStore, roleToNotWrite: role} _, err := repo.Update(false) if role == data.CanonicalRootRole { require.Error(t, err) // because checkRoot loads root from cache to check hashes continue } else { require.NoError(t, err) } for r, expected := range serverMeta { actual, err := repo.fileStore.GetMeta(r, -1) if r == role { require.Error(t, err) require.IsType(t, store.ErrMetaNotFound{}, err, "expected no data because unable to write for %s", role) } else { require.NoError(t, err, "problem getting repo metadata for %s", r) require.True(t, bytes.Equal(expected, actual), "%s: expected to update since only %s was unwritable", r, role) } } os.RemoveAll(repo.baseDir) } }
// ### Target validation with delegations tests func TestLoadTargetsLoadsNothingIfNoUpdates(t *testing.T) { gun := "docker.com/notary" metadata, _, err := testutils.NewRepoMetadata(gun) require.NoError(t, err) // load the root into the builder, else we can't load anything else builder := tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{}) require.NoError(t, builder.Load(data.CanonicalRootRole, metadata[data.CanonicalRootRole], 0, false)) store := storage.NewMemStorage() store.UpdateCurrent(gun, storage.MetaUpdate{ Role: data.CanonicalTargetsRole, Version: 1, Data: metadata[data.CanonicalTargetsRole], }) // if no updates, nothing is loaded targetsToUpdate, err := loadAndValidateTargets(gun, builder, nil, store) require.Empty(t, targetsToUpdate) require.NoError(t, err) require.False(t, builder.IsLoaded(data.CanonicalTargetsRole)) }
func TestRepoPrefixMatches(t *testing.T) { gun := "docker.io/notary" meta, cs, err := testutils.NewRepoMetadata(gun) require.NoError(t, err) ctx := context.WithValue(context.Background(), notary.CtxKeyMetaStore, storage.NewMemStorage()) ctx = context.WithValue(ctx, notary.CtxKeyKeyAlgo, data.ED25519Key) snChecksumBytes := sha256.Sum256(meta[data.CanonicalSnapshotRole]) // successful gets handler := RootHandler(ctx, nil, cs, nil, nil, []string{"docker.io"}) ts := httptest.NewServer(handler) url := fmt.Sprintf("%s/v2/%s/_trust/tuf/", ts.URL, gun) uploader, err := store.NewHTTPStore(url, "", "json", "key", http.DefaultTransport) require.NoError(t, err) // uploading is cool require.NoError(t, uploader.SetMulti(meta)) // getting is cool _, err = uploader.GetSized(data.CanonicalSnapshotRole, notary.MaxDownloadSize) require.NoError(t, err) _, err = uploader.GetSized( tufutils.ConsistentName(data.CanonicalSnapshotRole, snChecksumBytes[:]), notary.MaxDownloadSize) require.NoError(t, err) _, err = uploader.GetKey(data.CanonicalTimestampRole) require.NoError(t, err) // the httpstore doesn't actually delete all, so we do it manually req, err := http.NewRequest("DELETE", url, nil) require.NoError(t, err) res, err := http.DefaultTransport.RoundTrip(req) require.NoError(t, err) defer res.Body.Close() require.Equal(t, http.StatusOK, res.StatusCode) }
// When a delegation role appears in the update and the parent does not, the // parent is loaded from the DB if it can func TestValidateTargetsRequiresStoredParent(t *testing.T) { gun := "docker.com/notary" delgName := "targets/level1" metadata, _, err := testutils.NewRepoMetadata(gun, delgName, path.Join(delgName, "other")) require.NoError(t, err) // load the root into the builder, else we can't load anything else builder := tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{}) require.NoError(t, builder.Load(data.CanonicalRootRole, metadata[data.CanonicalRootRole], 0, false)) delUpdate := storage.MetaUpdate{ Role: delgName, Version: 1, Data: metadata[delgName], } upload := map[string]storage.MetaUpdate{delgName: delUpdate} store := storage.NewMemStorage() // if the DB has no "targets" role _, err = loadAndValidateTargets(gun, builder, upload, store) require.Error(t, err) require.IsType(t, validation.ErrBadTargets{}, err) // ensure the "targets" (the parent) is in the "db" store.UpdateCurrent(gun, storage.MetaUpdate{ Role: data.CanonicalTargetsRole, Version: 1, Data: metadata[data.CanonicalTargetsRole], }) updates, err := loadAndValidateTargets(gun, builder, upload, store) require.NoError(t, err) require.Len(t, updates, 1) require.Equal(t, delgName, updates[0].Role) require.Equal(t, metadata[delgName], updates[0].Data) }
// This just checks the URL routing is working correctly and cache headers are set correctly. // More detailed tests for this path including negative // tests are located in /server/handlers/ func TestGetCurrentRole(t *testing.T) { store := storage.NewMemStorage() metadata, _, err := testutils.NewRepoMetadata("gun") require.NoError(t, err) // need both the snapshot and the timestamp, because when getting the current // timestamp the server checks to see if it's out of date (there's a new snapshot) // and if so, generates a new one store.UpdateCurrent("gun", storage.MetaUpdate{ Role: data.CanonicalSnapshotRole, Version: 1, Data: metadata[data.CanonicalSnapshotRole], }) store.UpdateCurrent("gun", storage.MetaUpdate{ Role: data.CanonicalTimestampRole, Version: 1, Data: metadata[data.CanonicalTimestampRole], }) ctx := context.WithValue( context.Background(), notary.CtxKeyMetaStore, store) ctx = context.WithValue(ctx, notary.CtxKeyKeyAlgo, data.ED25519Key) ccc := utils.NewCacheControlConfig(10, false) handler := RootHandler(ctx, nil, signed.NewEd25519(), ccc, ccc, nil) serv := httptest.NewServer(handler) defer serv.Close() res, err := http.Get(fmt.Sprintf( "%s/v2/gun/_trust/tuf/%s.json", serv.URL, data.CanonicalTimestampRole, )) require.NoError(t, err) require.Equal(t, http.StatusOK, res.StatusCode) verifyGetResponse(t, res, metadata[data.CanonicalTimestampRole]) }
// If a repo has corrupt metadata (in that the hash doesn't match the snapshot) or // missing metadata, an update will replace all of it func TestUpdateReplacesCorruptOrMissingMetadata(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode") } serverMeta, cs, err := testutils.NewRepoMetadata("docker.com/notary", metadataDelegations...) require.NoError(t, err) ts := readOnlyServer(t, store.NewMemoryStore(serverMeta), http.StatusNotFound, "docker.com/notary") defer ts.Close() repo := newBlankRepo(t, ts.URL) defer os.RemoveAll(repo.baseDir) _, err = repo.Update(false) // ensure we have all metadata to start with require.NoError(t, err) // we want to swizzle the local cache, not the server, so create a new one repoSwizzler := testutils.NewMetadataSwizzler("docker.com/notary", serverMeta, cs) repoSwizzler.MetadataCache = repo.fileStore for _, role := range repoSwizzler.Roles { for _, expt := range waysToMessUpLocalMetadata { text, messItUp := expt.desc, expt.swizzle for _, forWrite := range []bool{true, false} { require.NoError(t, messItUp(repoSwizzler, role), "could not fuzz %s (%s)", role, text) _, err := repo.Update(forWrite) require.NoError(t, err) for r, expected := range serverMeta { actual, err := repo.fileStore.GetMeta(r, -1) require.NoError(t, err, "problem getting repo metadata for %s", role) require.True(t, bytes.Equal(expected, actual), "%s for %s: expected to recover after update", text, role) } } } } }