Add tests for updating replacing corrupted local cache

Signed-off-by: Ying Li <ying.li@docker.com>
This commit is contained in:
Ying Li 2016-01-07 16:57:44 -08:00
parent b4d3ac881d
commit 4f8d28ad7f
1 changed files with 246 additions and 0 deletions

View File

@ -1,12 +1,23 @@
package client
import (
"os"
"path"
"testing"
"time"
"fmt"
"io/ioutil"
"net/http"
"os"
"testing"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/signed"
"github.com/docker/notary/tuf/store"
json "github.com/jfrazelle/go/canonical/json"
"github.com/stretchr/testify/require"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/store"
"github.com/stretchr/testify/require"
@ -95,3 +106,238 @@ func testUpdateWithLocalCacheRemoteMissingMetadata(t *testing.T, forWrite bool)
require.True(t, ok)
require.Equal(t, data.CanonicalTimestampRole, metaNotFound.Resource)
}
type messUpMetadata func(t *testing.T, cs signed.CryptoService, ms store.MetadataStore, role string)
// corrupts metadata into something that is no longer valid JSON
func invalidJSONMetadata(t *testing.T, _ signed.CryptoService, ms store.MetadataStore, role string) {
require.NoError(t, ms.SetMeta(role, []byte("nope")))
}
// corrupts the metadata into something that is valid JSON, but not unmarshalable at all
func allUnmarshallableMetadata(t *testing.T, _ signed.CryptoService, ms store.MetadataStore, role string) {
metaBytes, err := json.MarshalCanonical(data.Signed{})
require.NoError(t, err)
require.NoError(t, ms.SetMeta(role, metaBytes))
}
// messes up the metadata in such a way that the hash is no longer valid
func invalidateMetadataHash(t *testing.T, _ signed.CryptoService, ms store.MetadataStore, role string) {
b, err := ms.GetMeta(role, maxSize)
require.NoError(t, err)
var unmarshalled map[string]interface{}
require.NoError(t, json.Unmarshal(b, &unmarshalled))
signed, ok := unmarshalled["signed"].(map[string]interface{})
require.True(t, ok)
signed["boogeyman"] = "exists"
metaBytes, err := json.MarshalCanonical(unmarshalled)
require.NoError(t, err)
require.NoError(t, ms.SetMeta(role, metaBytes))
}
// deletes the metadata
func deleteMetadata(t *testing.T, _ signed.CryptoService, ms store.MetadataStore, role string) {
require.NoError(t, ms.DeleteMeta(role))
}
func serializeMetadata(t *testing.T, s *data.Signed, cs signed.CryptoService, role string) []byte {
// delete the existing signatures
s.Signatures = []data.Signature{}
pubKeys := cs.ListKeys(role)
require.Len(t, pubKeys, 1, "no keys for %s", role)
pubKey := cs.GetKey(pubKeys[0])
require.NotNil(t, pubKey, "unable to get %s key %s", role, pubKeys[0])
require.NoError(t, signed.Sign(cs, s, pubKey))
metaBytes, err := json.MarshalCanonical(s)
require.NoError(t, err)
return metaBytes
}
// signs the metadata with the wrong key
func invalidateMetadataSig(t *testing.T, _ signed.CryptoService, ms store.MetadataStore, role string) {
b, err := ms.GetMeta(role, maxSize)
require.NoError(t, err)
signedThing := data.Signed{}
require.NoError(t, json.Unmarshal(b, &signedThing), "error unmarshalling data for %s", role)
// create an invalid key, but not in the existing CryptoService
cs := signed.NewEd25519()
_, err = cs.Create("root", data.ED25519Key)
require.NoError(t, err)
metaBytes := serializeMetadata(t, &signedThing, cs, "root")
require.NoError(t, ms.SetMeta(role, metaBytes))
}
func signedMetaFromStore(t *testing.T, ms store.MetadataStore, role string) data.SignedMeta {
b, err := ms.GetMeta(role, maxSize)
require.NoError(t, err)
signedMeta := data.SignedMeta{}
require.NoError(t, json.Unmarshal(b, &signedMeta), "error unmarshalling data for %s", role)
return signedMeta
}
func signedMetaToSigned(t *testing.T, signedMeta data.SignedMeta) data.Signed {
s, err := json.MarshalCanonical(signedMeta.Signed)
require.NoError(t, err)
signed := json.RawMessage{}
require.NoError(t, signed.UnmarshalJSON(s))
return data.Signed{Signed: signed}
}
// corrupt the metadata in such a way that it is JSON parsable, and correctly signed, but will not
// unmarshal correctly because it has the wrong type
func corruptSignedMetadata(t *testing.T, cs signed.CryptoService, ms store.MetadataStore, role string) {
if role != data.CanonicalTimestampRole || len(cs.ListKeys(role)) > 0 {
signedMeta := signedMetaFromStore(t, ms, role)
signedMeta.Signed.Type = "nonexistent"
signedThing := signedMetaToSigned(t, signedMeta)
metaBytes := serializeMetadata(t, &signedThing, cs, role)
require.NoError(t, ms.SetMeta(role, metaBytes))
}
}
// decrements the metadata version, which would make it invalid - don't do anything if we don't
// have the timestamp key
func decrementMetadataVersion(t *testing.T, cs signed.CryptoService, ms store.MetadataStore, role string) {
if role != data.CanonicalTimestampRole || len(cs.ListKeys(role)) > 0 {
signedMeta := signedMetaFromStore(t, ms, role)
signedMeta.Signed.Version--
signedThing := signedMetaToSigned(t, signedMeta)
metaBytes := serializeMetadata(t, &signedThing, cs, role)
require.NoError(t, ms.SetMeta(role, metaBytes))
}
}
// expire the metadata, which would make it invalid - don't do anything if we don't have the
// timestamp key
func expireMetadata(t *testing.T, cs signed.CryptoService, ms store.MetadataStore, role string) {
if role != data.CanonicalTimestampRole || len(cs.ListKeys(role)) > 0 {
signedMeta := signedMetaFromStore(t, ms, role)
signedMeta.Signed.Expires = time.Now().AddDate(-1, -1, -1)
signedThing := signedMetaToSigned(t, signedMeta)
metaBytes := serializeMetadata(t, &signedThing, cs, role)
require.NoError(t, ms.SetMeta(role, metaBytes))
}
}
// increments a threshold for a metadata role - invalidates the metadata for which the threshold
// is increased, since there is only 1 signature for each
func incrementThreshold(t *testing.T, cs signed.CryptoService, ms store.MetadataStore, role string) {
roleSpecifyingThreshold := data.CanonicalRootRole
if data.IsDelegation(role) {
roleSpecifyingThreshold = path.Dir(role)
}
b, err := ms.GetMeta(roleSpecifyingThreshold, maxSize)
require.NoError(t, err)
signedThing := &data.Signed{}
require.NoError(t, json.Unmarshal(b, signedThing), "error unmarshalling data for %s",
roleSpecifyingThreshold)
if roleSpecifyingThreshold == data.CanonicalRootRole {
signedRoot, err := data.RootFromSigned(signedThing)
require.NoError(t, err)
signedRoot.Signed.Roles[role].Threshold++
signedThing, err = signedRoot.ToSigned()
require.NoError(t, err)
} else {
signedTargets, err := data.TargetsFromSigned(signedThing)
require.NoError(t, err)
for _, roleObject := range signedTargets.Signed.Delegations.Roles {
if roleObject.Name == role {
roleObject.Threshold++
break
}
}
signedThing, err = signedTargets.ToSigned()
require.NoError(t, err)
}
metaBytes := serializeMetadata(t, signedThing, cs, roleSpecifyingThreshold)
require.NoError(t, ms.SetMeta(roleSpecifyingThreshold, metaBytes))
}
// If a repo has corrupt metadata, an update will replace all the metadata
func TestUpdateReplacesCorruptOrMissingMetadata(t *testing.T) {
// create repo with 2 level delegations
ts := fullTestServer(t)
defer ts.Close()
repo, _ := initializeRepo(t, data.ECDSAKey, "docker.com/notary", ts.URL, false)
defer os.RemoveAll(repo.baseDir)
delegatedRoles := []string{"targets/a", "targets/a/b"}
for _, delgName := range delegatedRoles {
delgKey, err := repo.CryptoService.Create(delgName, data.ECDSAKey)
require.NoError(t, err, "error creating delegation key")
require.NoError(t,
repo.AddDelegation(delgName, 1, []data.PublicKey{delgKey}, []string{""}),
"error creating delegation")
}
// add a target so the second level delegation is created
addTarget(t, repo, "first", "../fixtures/root-ca.crt", "targets/a/b")
require.NoError(t, repo.Publish())
_, err := repo.Update() // ensure we have all metadata to start with
require.NoError(t, err)
// corrupt any number of roles - an update should fix all of them
roles := []string{
data.CanonicalTimestampRole,
data.CanonicalSnapshotRole,
"targets/a",
"targets/a/b",
data.CanonicalTargetsRole,
data.CanonicalRootRole,
}
// store original metadata
origMeta := make(map[string][]byte)
for _, role := range roles {
b, err := repo.fileStore.GetMeta(role, maxSize)
require.NoError(t, err)
require.NotNil(t, b)
origMeta[role] = b
}
// mess up metadata in different ways, update the repo, and assert that the metadata is fixed.
waysToMessUp := map[string]messUpMetadata{
"corrupted/invalid JSON": invalidJSONMetadata,
"metadata has invalid hash": invalidateMetadataHash,
"missing metadata": deleteMetadata,
"metadata signed by wrong key": invalidateMetadataSig,
"expired metadata": expireMetadata,
"insufficient signatures": incrementThreshold,
// decremented version just tests that updates do not need to increment
// by 1, only increment at all
"version much lower": decrementMetadataVersion,
}
for i := range roles {
for text, messItUp := range waysToMessUp {
for _, role := range roles[:i+1] {
messItUp(t, repo.CryptoService, repo.fileStore, role)
}
_, err := repo.Update()
require.NoError(t, err)
for role, origBytes := range origMeta {
b, err := repo.fileStore.GetMeta(role, maxSize)
require.NoError(t, err, "problem getting metadata for %s", role)
require.Equal(t, origBytes, b, "%s for %s expected to recover after update", text, role)
}
}
}
}