Skip to content

Commit

Permalink
Publish only the key rotation changes after a remote key rotation
Browse files Browse the repository at this point in the history
Signed-off-by: Ying Li <ying.li@docker.com>
  • Loading branch information
cyli committed Mar 16, 2016
1 parent 4e5e2f3 commit fa5edc4
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 45 deletions.
5 changes: 1 addition & 4 deletions client/backwards_compatibility_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"testing"
"time"

"github.com/docker/notary/client/changelist"
"github.com/docker/notary/passphrase"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/store"
Expand Down Expand Up @@ -108,10 +107,8 @@ func Test0Dot1RepoFormat(t *testing.T) {
require.NoError(t, repo.fileStore.RemoveMeta(data.CanonicalTimestampRole))

// rotate the timestamp key, since the server doesn't have that one
timestampPubKey, err := getRemoteKey(ts.URL, gun, data.CanonicalTimestampRole, http.DefaultTransport)
err = repo.RotateKey(data.CanonicalTimestampRole, true)
require.NoError(t, err)
require.NoError(
t, repo.rootFileKeyChange(data.CanonicalTimestampRole, changelist.ActionCreate, timestampPubKey))

require.NoError(t, repo.Publish())

Expand Down
73 changes: 37 additions & 36 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,25 @@ func (r *NotaryRepository) ListRoles() ([]RoleWithSignatures, error) {
// 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 {
cl, err := r.GetChangelist()
if err != nil {
return err
}
if err = r.publish(cl); err != nil {
return err
}
if err = cl.Clear(""); 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 ", filepath.Join(r.tufRepoPath, "changelist"))
}
return nil
}

// publish pushes the changes in the given changelist to the remote notary-server
// Conceptually it performs an operation similar to a `git rebase`
func (r *NotaryRepository) publish(cl changelist.Changelist) error {
var initialPublish bool
// update first before publishing
_, err := r.Update(true)
Expand Down Expand Up @@ -558,10 +577,6 @@ func (r *NotaryRepository) Publish() error {
return err
}
}
cl, err := r.GetChangelist()
if err != nil {
return err
}
// apply the changelist to the repo
err = applyChangelist(r.tufRepo, cl)
if err != nil {
Expand Down Expand Up @@ -632,18 +647,7 @@ func (r *NotaryRepository) Publish() error {
return err
}

err = remote.SetMultiMeta(updatedFiles)
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 ", filepath.Join(r.tufRepoPath, "changelist"))
}
return nil
return remote.SetMultiMeta(updatedFiles)
}

// bootstrapRepo loads the repository from the local file system. This attempts
Expand Down Expand Up @@ -885,32 +889,33 @@ func (r *NotaryRepository) RotateKey(role string, serverManagesKey bool) error {
return fmt.Errorf("notary does not currently permit rotating the %s key", role)
}

var (
pubKey data.PublicKey
err error
errFmtString string
)
if serverManagesKey {
pubKey, err = getRemoteKey(r.baseURL, r.gun, role, r.roundTrip)
errFmtString = "unable to rotate remote key: %s"
} else {
pubKey, err = r.CryptoService.Create(role, data.ECDSAKey)
errFmtString = "unable to generate key: %s"
pubKey, err := getRemoteKey(r.baseURL, r.gun, role, r.roundTrip)
if err != nil {
return fmt.Errorf("unable to rotate remote key: %s", err)
}
cl := changelist.NewMemChangelist()
if err := r.rootFileKeyChange(cl, role, changelist.ActionCreate, pubKey); err != nil {
return err
}
return r.publish(cl)
}

pubKey, err := r.CryptoService.Create(role, data.ECDSAKey)
if err != nil {
return fmt.Errorf(errFmtString, err)
return fmt.Errorf("unable to generate key: %s", err)
}

return r.rootFileKeyChange(role, changelist.ActionCreate, pubKey)
}

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()

return r.rootFileKeyChange(cl, role, changelist.ActionCreate, pubKey)
}

func (r *NotaryRepository) rootFileKeyChange(cl changelist.Changelist, role, action string, key data.PublicKey) error {
kl := make(data.KeyList, 0, 1)
kl = append(kl, key)
meta := changelist.TufRootData{
Expand All @@ -929,11 +934,7 @@ func (r *NotaryRepository) rootFileKeyChange(role, action string, key data.Publi
role,
metaJSON,
)
err = cl.Add(c)
if err != nil {
return err
}
return nil
return cl.Add(c)
}

// DeleteTrustData removes the trust data stored for this repo in the TUF cache and certificate store on the client side
Expand Down
41 changes: 41 additions & 0 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2580,6 +2580,47 @@ func TestRotateKeyInvalidRole(t *testing.T) {
}
}

// Initialize repo to have local signing of snapshots. Rotate the key to have
// the server manage the snapshot key. Assert that this publishes the key change
// but not any other changes.
func TestRemoteRotationOnlyPublishesKeyChanges(t *testing.T) {
ts := fullTestServer(t)
defer ts.Close()

repo1, _ := initializeRepo(t, data.ECDSAKey, "docker.com/notary", ts.URL, false)
defer os.RemoveAll(repo1.baseDir)

oldSnapshotRole, err := repo1.tufRepo.GetBaseRole(data.CanonicalSnapshotRole)
assert.NoError(t, err)

// Add and make sure it's not published when the key is rotated
addTarget(t, repo1, "latest", "../fixtures/intermediate-ca.crt")
cl, err := repo1.GetChangelist()
assert.NoError(t, err)
assert.Len(t, cl.List(), 1)

assert.NoError(t, repo1.RotateKey(data.CanonicalSnapshotRole, true))
assert.Len(t, cl.List(), 1, "rotating key published other changes")

// ensure that the key rotation was public by pulling from a new repo
repo2, _ := newRepoToTestRepo(t, repo1, true)
defer os.RemoveAll(repo2.baseDir)
tgts, err := repo2.ListTargets()
assert.NoError(t, err)
assert.Len(t, tgts, 0, "No targets should have been published")

newSnapshotRole, err := repo2.tufRepo.GetBaseRole(data.CanonicalSnapshotRole)
assert.NoError(t, err)

// assert that the snapshot key has changed
assert.Len(t, oldSnapshotRole.Keys, 1)
assert.Len(t, newSnapshotRole.Keys, 1)
for k := range oldSnapshotRole.Keys {
_, ok := newSnapshotRole.Keys[k]
assert.False(t, ok)
}
}

// If remotely rotating key fails, the failure is propagated
func TestRemoteRotationError(t *testing.T) {
ts, _, _ := simpleTestServer(t)
Expand Down
6 changes: 1 addition & 5 deletions cmd/notary/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -430,11 +430,7 @@ func (k *keyCommander) keysRotate(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
err = nRepo.RotateKey(rotateKeyRole, k.rotateKeyServerManaged)
if err == nil && k.rotateKeyServerManaged {
err = nRepo.Publish()
}
return err
return nRepo.RotateKey(rotateKeyRole, k.rotateKeyServerManaged)
}

func removeKeyInteractively(keyStores []trustmanager.KeyStore, keyID string,
Expand Down

0 comments on commit fa5edc4

Please sign in to comment.