// AddDelegationRoleAndKeys creates a changelist entry to add provided delegation public keys. // This method is the simplest way to create a new delegation, because the delegation must have at least // one key upon creation to be valid since we will reject the changelist while validating the threshold. func (r *NotaryRepository) AddDelegationRoleAndKeys(name string, delegationKeys []data.PublicKey) error { if !data.IsDelegation(name) { return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} } cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { return err } defer cl.Close() logrus.Debugf(`Adding delegation "%s" with threshold %d, and %d keys\n`, name, notary.MinThreshold, len(delegationKeys)) // Defaulting to threshold of 1, since we don't allow for larger thresholds at the moment. tdJSON, err := json.Marshal(&changelist.TufDelegation{ NewThreshold: notary.MinThreshold, AddKeys: data.KeyList(delegationKeys), }) if err != nil { return err } template := newCreateDelegationChange(name, tdJSON) return addChange(cl, template, name) }
// Witness creates change objects to witness (i.e. re-sign) the given // roles on the next publish. One change is created per role func (r *NotaryRepository) Witness(roles ...string) ([]string, error) { cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { return nil, err } defer cl.Close() successful := make([]string, 0, len(roles)) for _, role := range roles { // scope is role c := changelist.NewTUFChange( changelist.ActionUpdate, role, changelist.TypeWitness, "", nil, ) err = cl.Add(c) if err != nil { break } successful = append(successful, role) } return successful, err }
func (r *NotaryRepository) rootFileKeyChange(role, action string, key data.PublicKey) error { cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { return err } defer cl.Close() kl := make(data.KeyList, 0, 1) kl = append(kl, key) meta := changelist.TufRootData{ RoleName: role, Keys: kl, } metaJSON, err := json.Marshal(meta) if err != nil { return err } c := changelist.NewTufChange( action, changelist.ScopeRoot, changelist.TypeRootRole, role, metaJSON, ) err = cl.Add(c) if err != nil { return err } return nil }
// AddDelegation creates a new changelist entry to add a delegation to the repository // when the changelist gets applied at publish time. This does not do any validation // other than checking the name of the delegation to add - all that will happen // at publish time. func (r *NotaryRepository) AddDelegation(name string, threshold int, delegationKeys []data.PublicKey, paths []string) error { if !data.IsDelegation(name) { return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} } cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { return err } defer cl.Close() logrus.Debugf(`Adding delegation "%s" with threshold %d, and %d keys\n`, name, threshold, len(delegationKeys)) tdJSON, err := json.Marshal(&changelist.TufDelegation{ NewThreshold: threshold, AddKeys: data.KeyList(delegationKeys), AddPaths: paths, }) if err != nil { return err } template := changelist.NewTufChange( changelist.ActionCreate, name, changelist.TypeTargetsDelegation, "", // no path tdJSON, ) return addChange(cl, template, name) }
func (r *NotaryRepository) rootFileKeyChange(role, action string, key data.PublicKey) error { cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { return err } defer cl.Close() k, ok := key.(*data.TUFKey) if !ok { return errors.New("Invalid key type found during rotation.") } meta := changelist.TufRootData{ RoleName: role, Keys: []data.TUFKey{*k}, } metaJSON, err := json.Marshal(meta) if err != nil { return err } c := changelist.NewTufChange( action, changelist.ScopeRoot, changelist.TypeRootRole, role, metaJSON, ) err = cl.Add(c) if err != nil { return err } return nil }
func testListTarget(t *testing.T, rootType string) { // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir) assert.NoError(t, err, "failed to create a temporary directory: %s", err) gun := "docker.com/notary" ts, mux := simpleTestServer(t) defer ts.Close() repo, _ := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL) // tests need to manually boostrap timestamp as client doesn't generate it err = repo.tufRepo.InitTimestamp() assert.NoError(t, err, "error creating repository: %s", err) latestTarget := addTarget(t, repo, "latest", "../fixtures/intermediate-ca.crt") currentTarget := addTarget(t, repo, "current", "../fixtures/intermediate-ca.crt") // Apply the changelist. Normally, this would be done by Publish // load the changelist for this repo cl, err := changelist.NewFileChangelist( filepath.Join(tempBaseDir, "tuf", filepath.FromSlash(gun), "changelist")) assert.NoError(t, err, "could not open changelist") // apply the changelist to the repo err = applyChangelist(repo.tufRepo, cl) assert.NoError(t, err, "could not apply changelist") fakeServerData(t, repo, mux) targets, err := repo.ListTargets() assert.NoError(t, err) // Should be two targets assert.Len(t, targets, 2, "unexpected number of targets returned by ListTargets") if targets[0].Name == "latest" { assert.Equal(t, latestTarget, targets[0], "latest target does not match") assert.Equal(t, currentTarget, targets[1], "current target does not match") } else if targets[0].Name == "current" { assert.Equal(t, currentTarget, targets[0], "current target does not match") assert.Equal(t, latestTarget, targets[1], "latest target does not match") } else { t.Fatalf("unexpected target name: %s", targets[0].Name) } // Also test GetTargetByName newLatestTarget, err := repo.GetTargetByName("latest") assert.NoError(t, err) assert.Equal(t, latestTarget, newLatestTarget, "latest target does not match") newCurrentTarget, err := repo.GetTargetByName("current") assert.NoError(t, err) assert.Equal(t, currentTarget, newCurrentTarget, "current target does not match") }
// GetChangelist returns the list of the repository's unpublished changes func (r *NotaryRepository) GetChangelist() (changelist.Changelist, error) { changelistDir := filepath.Join(r.tufRepoPath, "changelist") cl, err := changelist.NewFileChangelist(changelistDir) if err != nil { logrus.Debug("Error initializing changelist") return nil, err } return cl, nil }
// RemoveTarget creates new changelist entries to remove a target from the given // roles in the repository when the changelist gets applied at publish time. // If roles are unspecified, the default role is "target". func (r *NotaryRepository) RemoveTarget(targetName string, roles ...string) error { cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { return err } logrus.Debugf("Removing target \"%s\"", targetName) template := changelist.NewTufChange(changelist.ActionDelete, "", changelist.TypeTargetsTarget, targetName, nil) return addChange(cl, template, roles...) }
// RemoveTarget creates a new changelist entry to remove a target from the repository // when the changelist gets applied at publish time func (r *NotaryRepository) RemoveTarget(targetName string) error { cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { return err } logrus.Debugf("Removing target \"%s\"", targetName) c := changelist.NewTufChange(changelist.ActionDelete, changelist.ScopeTargets, "target", targetName, nil) err = cl.Add(c) if err != nil { return err } return nil }
// RemoveDelegationRole creates a changelist to remove all paths and keys from a role, and delete the role in its entirety. func (r *NotaryRepository) RemoveDelegationRole(name string) error { if !data.IsDelegation(name) { return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} } cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { return err } defer cl.Close() logrus.Debugf(`Removing delegation "%s"\n`, name) template := newDeleteDelegationChange(name, nil) return addChange(cl, template, name) }
// RemoveDelegation creates a new changelist entry to remove a delegation from // the repository when the changelist gets applied at publish time. // This does not validate that the delegation exists, since one might exist // after applying all changes. func (r *NotaryRepository) RemoveDelegation(name string, keyIDs, paths []string, removeAll bool) error { if !data.IsDelegation(name) { return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} } cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { return err } defer cl.Close() logrus.Debugf(`Removing delegation "%s"\n`, name) var template *changelist.TufChange // We use the Delete action only for force removal, Update is used for removing individual keys and paths if removeAll { template = changelist.NewTufChange( changelist.ActionDelete, name, changelist.TypeTargetsDelegation, "", // no path nil, // deleting role, no data needed ) } else { tdJSON, err := json.Marshal(&changelist.TufDelegation{ RemoveKeys: keyIDs, RemovePaths: paths, }) if err != nil { return err } template = changelist.NewTufChange( changelist.ActionUpdate, name, changelist.TypeTargetsDelegation, "", // no path tdJSON, ) } return addChange(cl, template, name) }
// AddTarget creates new changelist entries to add a target to the given roles // in the repository when the changelist gets appied at publish time. // If roles are unspecified, the default role is "targets". func (r *NotaryRepository) AddTarget(target *Target, roles ...string) error { cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { return err } defer cl.Close() logrus.Debugf("Adding target \"%s\" with sha256 \"%x\" and size %d bytes.\n", target.Name, target.Hashes["sha256"], target.Length) meta := data.FileMeta{Length: target.Length, Hashes: target.Hashes} metaJSON, err := json.Marshal(meta) if err != nil { return err } template := changelist.NewTufChange( changelist.ActionCreate, "", changelist.TypeTargetsTarget, target.Name, metaJSON) return addChange(cl, template, roles...) }
// AddTarget adds a new target to the repository, forcing a timestamps check from TUF func (r *NotaryRepository) AddTarget(target *Target) error { cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { return err } logrus.Debugf("Adding target \"%s\" with sha256 \"%x\" and size %d bytes.\n", target.Name, target.Hashes["sha256"], target.Length) meta := data.FileMeta{Length: target.Length, Hashes: target.Hashes} metaJSON, err := json.Marshal(meta) if err != nil { return err } c := changelist.NewTufChange(changelist.ActionCreate, "targets", "target", target.Name, metaJSON) err = cl.Add(c) if err != nil { return err } return cl.Close() }
// RemoveDelegationKeys creates a changelist entry to remove provided keys from an existing delegation. // When this changelist is applied, if the specified keys are the only keys left in the role, // the role itself will be deleted in its entirety. // It can also delete a key from all delegations under a parent using a name // with a wildcard at the end. func (r *NotaryRepository) RemoveDelegationKeys(name string, keyIDs []string) error { if !data.IsDelegation(name) && !data.IsWildDelegation(name) { return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} } cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { return err } defer cl.Close() logrus.Debugf(`Removing %s keys from delegation "%s"\n`, keyIDs, name) tdJSON, err := json.Marshal(&changelist.TUFDelegation{ RemoveKeys: keyIDs, }) if err != nil { return err } template := newUpdateDelegationChange(name, tdJSON) return addChange(cl, template, name) }
// ClearDelegationPaths creates a changelist entry to remove all paths from an existing delegation. func (r *NotaryRepository) ClearDelegationPaths(name string) error { if !data.IsDelegation(name) { return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} } cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { return err } defer cl.Close() logrus.Debugf(`Removing all paths from delegation "%s"\n`, name) tdJSON, err := json.Marshal(&changelist.TufDelegation{ ClearAllPaths: true, }) if err != nil { return err } template := newUpdateDelegationChange(name, tdJSON) return addChange(cl, template, name) }
// AddDelegationPaths creates a changelist entry to add provided paths to an existing delegation. // This method cannot create a new delegation itself because the role must meet the key threshold upon creation. func (r *NotaryRepository) AddDelegationPaths(name string, paths []string) error { if !data.IsDelegation(name) { return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} } cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { return err } defer cl.Close() logrus.Debugf(`Adding %s paths to delegation %s\n`, paths, name) tdJSON, err := json.Marshal(&changelist.TufDelegation{ AddPaths: paths, }) if err != nil { return err } template := newCreateDelegationChange(name, tdJSON) return addChange(cl, template, name) }
// Publish pushes the local changes in signed material to the remote notary-server // Conceptually it performs an operation similar to a `git rebase` func (r *NotaryRepository) Publish() error { var updateRoot bool var root *data.Signed // attempt to initialize the repo from the remote store c, err := r.bootstrapClient() if err != nil { if _, ok := err.(store.ErrMetaNotFound); ok { // if the remote store return a 404 (translated into ErrMetaNotFound), // the repo hasn't been initialized yet. Attempt to load it from disk. err := r.bootstrapRepo() if err != nil { // Repo hasn't been initialized, It must be initialized before // it can be published. Return an error and let caller determine // what it wants to do. logrus.Debug(err.Error()) logrus.Debug("Repository not initialized during Publish") return &ErrRepoNotInitialized{} } // We had local data but the server doesn't know about the repo yet, // ensure we will push the initial root file root, err = r.tufRepo.Root.ToSigned() if err != nil { return err } updateRoot = true } else { // The remote store returned an error other than 404. We're // unable to determine if the repo has been initialized or not. logrus.Error("Could not publish Repository: ", err.Error()) return err } } else { // If we were successfully able to bootstrap the client (which only pulls // root.json), update it the rest of the tuf metadata in preparation for // applying the changelist. err = c.Update() if err != nil { if err, ok := err.(signed.ErrExpired); ok { return ErrExpired{err} } return err } } // load the changelist for this repo changelistDir := filepath.Join(r.tufRepoPath, "changelist") cl, err := changelist.NewFileChangelist(changelistDir) if err != nil { logrus.Debug("Error initializing changelist") return err } // apply the changelist to the repo err = applyChangelist(r.tufRepo, cl) if err != nil { logrus.Debug("Error applying changelist") return err } // check if our root file is nearing expiry. Resign if it is. if nearExpiry(r.tufRepo.Root) || r.tufRepo.Root.Dirty { if err != nil { return err } rootKeyID := r.tufRepo.Root.Signed.Roles["root"].KeyIDs[0] rootCryptoService, err := r.KeyStoreManager.GetRootCryptoService(rootKeyID) if err != nil { return err } root, err = r.tufRepo.SignRoot(data.DefaultExpires("root"), rootCryptoService.CryptoService) if err != nil { return err } updateRoot = true } // we will always resign targets and snapshots targets, err := r.tufRepo.SignTargets("targets", data.DefaultExpires("targets"), nil) if err != nil { return err } snapshot, err := r.tufRepo.SignSnapshot(data.DefaultExpires("snapshot"), nil) if err != nil { return err } remote, err := getRemoteStore(r.baseURL, r.gun, r.roundTrip) if err != nil { return err } // ensure we can marshal all the json before sending anything to remote targetsJSON, err := json.Marshal(targets) if err != nil { return err } snapshotJSON, err := json.Marshal(snapshot) if err != nil { return err } update := make(map[string][]byte) // if we need to update the root, marshal it and push the update to remote if updateRoot { rootJSON, err := json.Marshal(root) if err != nil { return err } update["root"] = rootJSON } update["targets"] = targetsJSON update["snapshot"] = snapshotJSON err = remote.SetMultiMeta(update) if err != nil { return err } err = cl.Clear("") if err != nil { // This is not a critical problem when only a single host is pushing // but will cause weird behaviour if changelist cleanup is failing // and there are multiple hosts writing to the repo. logrus.Warn("Unable to clear changelist. You may want to manually delete the folder ", changelistDir) } return nil }
// Publish pushes the local changes in signed material to the remote notary-server // Conceptually it performs an operation similar to a `git rebase` func (r *NotaryRepository) Publish(getPass passwordRetriever) error { var updateRoot bool var root *data.Signed // attempt to initialize the repo from the remote store c, err := r.bootstrapClient() if err != nil { if _, ok := err.(*store.ErrMetaNotFound); ok { // if the remote store return a 404 (translated into ErrMetaNotFound), // the repo hasn't been initialized yet. Attempt to load it from disk. err := r.bootstrapRepo() if err != nil { // Repo hasn't been initialized, It must be initialized before // it can be published. Return an error and let caller determine // what it wants to do. logrus.Debug("Repository not initialized during Publish") return &ErrRepoNotInitialized{} } // We had local data but the server doesn't know about the repo yet, // ensure we will push the initial root file root, err = r.tufRepo.Root.ToSigned() if err != nil { return err } updateRoot = true } else { // The remote store returned an error other than 404. We're // unable to determine if the repo has been initialized or not. logrus.Error("Could not publish Repository: ", err.Error()) return err } } else { // If we were successfully able to bootstrap the client (which only pulls // root.json), update it the rest of the tuf metadata in preparation for // applying the changelist. err = c.Update() if err != nil { return err } } // load the changelist for this repo cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { logrus.Debug("Error initializing changelist") return err } // apply the changelist to the repo err = applyChangelist(r.tufRepo, cl) if err != nil { logrus.Debug("Error applying changelist") return err } // check if our root file is nearing expiry. Resign if it is. if nearExpiry(r.tufRepo.Root) || r.tufRepo.Root.Dirty { passphrase, err := getPass() if err != nil { return err } rootKeyID := r.tufRepo.Root.Signed.Roles["root"].KeyIDs[0] rootSigner, err := r.GetRootSigner(rootKeyID, passphrase) if err != nil { return err } root, err = r.tufRepo.SignRoot(data.DefaultExpires("root"), rootSigner.signer) if err != nil { return err } updateRoot = true } // we will always resign targets and snapshots targets, err := r.tufRepo.SignTargets("targets", data.DefaultExpires("targets"), nil) if err != nil { return err } snapshot, err := r.tufRepo.SignSnapshot(data.DefaultExpires("snapshot"), nil) if err != nil { return err } remote, err := getRemoteStore(r.baseURL, r.Gun) if err != nil { return err } // ensure we can marshal all the json before sending anything to remote targetsJSON, err := json.Marshal(targets) if err != nil { return err } snapshotJSON, err := json.Marshal(snapshot) if err != nil { return err } // if we need to update the root, marshal it and push the update to remote if updateRoot { rootJSON, err := json.Marshal(root) if err != nil { return err } err = remote.SetMeta("root", rootJSON) if err != nil { return err } } err = remote.SetMeta("targets", targetsJSON) if err != nil { return err } err = remote.SetMeta("snapshot", snapshotJSON) if err != nil { return err } return nil }
func testAddListTarget(t *testing.T, rootType data.KeyAlgorithm) { // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir) assert.NoError(t, err, "failed to create a temporary directory: %s", err) gun := "docker.com/notary" ts, mux := createTestServer(t) defer ts.Close() repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, passphraseRetriever) assert.NoError(t, err, "error creating repository: %s", err) rootKeyID, err := repo.KeyStoreManager.GenRootKey(rootType.String()) assert.NoError(t, err, "error generating root key: %s", err) rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID) assert.NoError(t, err, "error retreiving root key: %s", err) err = repo.Initialize(rootCryptoService) assert.NoError(t, err, "error creating repository: %s", err) // tests need to manually boostrap timestamp as client doesn't generate it err = repo.tufRepo.InitTimestamp() assert.NoError(t, err, "error creating repository: %s", err) // Add fixtures/intermediate-ca.crt as a target. There's no particular reason // for using this file except that it happens to be available as // a fixture. latestTarget, err := NewTarget("latest", "../fixtures/intermediate-ca.crt") assert.NoError(t, err, "error creating target") err = repo.AddTarget(latestTarget) assert.NoError(t, err, "error adding target") // Look for the changelist file changelistDirPath := filepath.Join(tempBaseDir, "tuf", filepath.FromSlash(gun), "changelist") changelistDir, err := os.Open(changelistDirPath) assert.NoError(t, err, "could not open changelist directory") fileInfos, err := changelistDir.Readdir(0) assert.NoError(t, err, "could not read changelist directory") // Should only be one file in the directory assert.Len(t, fileInfos, 1, "wrong number of changelist files found") clName := fileInfos[0].Name() raw, err := ioutil.ReadFile(filepath.Join(changelistDirPath, clName)) assert.NoError(t, err, "could not read changelist file %s", clName) c := &changelist.TufChange{} err = json.Unmarshal(raw, c) assert.NoError(t, err, "could not unmarshal changelist file %s", clName) assert.EqualValues(t, changelist.ActionCreate, c.Actn) assert.Equal(t, "targets", c.Role) assert.Equal(t, "target", c.ChangeType) assert.Equal(t, "latest", c.ChangePath) assert.NotEmpty(t, c.Data) changelistDir.Close() // Create a second target currentTarget, err := NewTarget("current", "../fixtures/intermediate-ca.crt") assert.NoError(t, err, "error creating target") err = repo.AddTarget(currentTarget) assert.NoError(t, err, "error adding target") changelistDir, err = os.Open(changelistDirPath) assert.NoError(t, err, "could not open changelist directory") // There should now be a second file in the directory fileInfos, err = changelistDir.Readdir(0) assert.NoError(t, err, "could not read changelist directory") assert.Len(t, fileInfos, 2, "wrong number of changelist files found") newFileFound := false for _, fileInfo := range fileInfos { if fileInfo.Name() != clName { clName2 := fileInfo.Name() raw, err := ioutil.ReadFile(filepath.Join(changelistDirPath, clName2)) assert.NoError(t, err, "could not read changelist file %s", clName2) c := &changelist.TufChange{} err = json.Unmarshal(raw, c) assert.NoError(t, err, "could not unmarshal changelist file %s", clName2) assert.EqualValues(t, changelist.ActionCreate, c.Actn) assert.Equal(t, "targets", c.Role) assert.Equal(t, "target", c.ChangeType) assert.Equal(t, "current", c.ChangePath) assert.NotEmpty(t, c.Data) newFileFound = true break } } assert.True(t, newFileFound, "second changelist file not found") changelistDir.Close() // Now test ListTargets. In preparation, we need to expose some signed // metadata files on the internal HTTP server. // Apply the changelist. Normally, this would be done by Publish // load the changelist for this repo cl, err := changelist.NewFileChangelist(filepath.Join(tempBaseDir, "tuf", filepath.FromSlash(gun), "changelist")) assert.NoError(t, err, "could not open changelist") // apply the changelist to the repo err = applyChangelist(repo.tufRepo, cl) assert.NoError(t, err, "could not apply changelist") var tempKey data.TUFKey json.Unmarshal([]byte(timestampECDSAKeyJSON), &tempKey) repo.KeyStoreManager.NonRootKeyStore().AddKey(filepath.Join(filepath.FromSlash(gun), tempKey.ID()), "nonroot", &tempKey) // Because ListTargets will clear this savedTUFRepo := repo.tufRepo rootJSONFile := filepath.Join(tempBaseDir, "tuf", filepath.FromSlash(gun), "metadata", "root.json") rootFileBytes, err := ioutil.ReadFile(rootJSONFile) signedTargets, err := savedTUFRepo.SignTargets("targets", data.DefaultExpires("targets"), nil) assert.NoError(t, err) signedSnapshot, err := savedTUFRepo.SignSnapshot(data.DefaultExpires("snapshot"), nil) assert.NoError(t, err) signedTimestamp, err := savedTUFRepo.SignTimestamp(data.DefaultExpires("timestamp"), nil) assert.NoError(t, err) mux.HandleFunc("/v2/docker.com/notary/_trust/tuf/root.json", func(w http.ResponseWriter, r *http.Request) { assert.NoError(t, err) fmt.Fprint(w, string(rootFileBytes)) }) mux.HandleFunc("/v2/docker.com/notary/_trust/tuf/timestamp.json", func(w http.ResponseWriter, r *http.Request) { timestampJSON, _ := json.Marshal(signedTimestamp) fmt.Fprint(w, string(timestampJSON)) }) mux.HandleFunc("/v2/docker.com/notary/_trust/tuf/snapshot.json", func(w http.ResponseWriter, r *http.Request) { snapshotJSON, _ := json.Marshal(signedSnapshot) fmt.Fprint(w, string(snapshotJSON)) }) mux.HandleFunc("/v2/docker.com/notary/_trust/tuf/targets.json", func(w http.ResponseWriter, r *http.Request) { targetsJSON, _ := json.Marshal(signedTargets) fmt.Fprint(w, string(targetsJSON)) }) targets, err := repo.ListTargets() assert.NoError(t, err) // Should be two targets assert.Len(t, targets, 2, "unexpected number of targets returned by ListTargets") if targets[0].Name == "latest" { assert.Equal(t, latestTarget, targets[0], "latest target does not match") assert.Equal(t, currentTarget, targets[1], "current target does not match") } else if targets[0].Name == "current" { assert.Equal(t, currentTarget, targets[0], "current target does not match") assert.Equal(t, latestTarget, targets[1], "latest target does not match") } else { t.Fatalf("unexpected target name: %s", targets[0].Name) } // Also test GetTargetByName newLatestTarget, err := repo.GetTargetByName("latest") assert.NoError(t, err) assert.Equal(t, latestTarget, newLatestTarget, "latest target does not match") newCurrentTarget, err := repo.GetTargetByName("current") assert.NoError(t, err) assert.Equal(t, currentTarget, newCurrentTarget, "current target does not match") }