1
0
mirror of https://github.com/docker/docs.git synced 2026-11-05 00:14:17 +03:00

Merge pull request #572 from docker/per-metadata-type-mutation

Per metadata type mutation tests
This commit is contained in:
Ying Li
2016-02-22 16:34:33 -08:00
4 changed files with 438 additions and 8 deletions

View File

@@ -811,6 +811,36 @@ var waysToMessUpServer = []swizzleExpectations{
}},
}
var _waysToMessUpServerRoot []swizzleExpectations
// We also want to remove a every role from root once, or remove the role's keys.
// This function generates once and caches the result for later re-use.
func waysToMessUpServerRoot() []swizzleExpectations {
if _waysToMessUpServerRoot == nil {
_waysToMessUpServerRoot = waysToMessUpServer
for _, roleName := range data.BaseRoles {
_waysToMessUpServerRoot = append(_waysToMessUpServerRoot,
swizzleExpectations{
desc: fmt.Sprintf("no %s keys", roleName),
expectErrs: []interface{}{
&certs.ErrValidationFail{}, signed.ErrRoleThreshold{}},
swizzle: func(s *testutils.MetadataSwizzler, role string) error {
return s.MutateRoot(func(r *data.Root) {
r.Roles[roleName].KeyIDs = []string{}
})
}},
swizzleExpectations{
desc: fmt.Sprintf("no %s role", roleName),
expectErrs: []interface{}{data.ErrInvalidRole{}},
swizzle: func(s *testutils.MetadataSwizzler, role string) error {
return s.MutateRoot(func(r *data.Root) { delete(r.Roles, roleName) })
}},
)
}
}
return _waysToMessUpServerRoot
}
// If there's no local cache, we go immediately to check the remote server for
// root, and if it invalid (corrupted), we cannot update. This happens
// with and without a force check (update for write).
@@ -818,7 +848,8 @@ func TestUpdateRootRemoteCorruptedNoLocalCache(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
for _, testData := range waysToMessUpServer {
for _, testData := range waysToMessUpServerRoot() {
if testData.desc == "insufficient signatures" {
// Currently if we download the root during the bootstrap phase,
// we don't check for enough signatures to meet the threshold. We
@@ -842,7 +873,11 @@ func TestUpdateRootRemoteCorruptedNoLocalCache(t *testing.T) {
// because the fact that the timestamp hasn't changed should mean that we don't
// have to re-download the root.
func TestUpdateRootRemoteCorruptedCanUseLocalCache(t *testing.T) {
for _, testData := range waysToMessUpServer {
if testing.Short() {
t.Skip("skipping test in short mode")
}
for _, testData := range waysToMessUpServerRoot() {
testUpdateRemoteCorruptValidChecksum(t, updateOpts{
localCache: true,
forWrite: false,
@@ -862,7 +897,7 @@ func TestUpdateRootRemoteCorruptedCannotUseLocalCache(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
for _, testData := range waysToMessUpServer {
for _, testData := range waysToMessUpServerRoot() {
testUpdateRemoteCorruptValidChecksum(t, updateOpts{
serverHasNewData: true,
localCache: true,
@@ -878,6 +913,69 @@ func TestUpdateRootRemoteCorruptedCannotUseLocalCache(t *testing.T) {
}
}
func waysToMessUpServerNonRootPerRole(t *testing.T) map[string][]swizzleExpectations {
perRoleSwizzling := make(map[string][]swizzleExpectations)
for _, missing := range []string{data.CanonicalRootRole, data.CanonicalTargetsRole} {
perRoleSwizzling[data.CanonicalSnapshotRole] = append(
perRoleSwizzling[data.CanonicalSnapshotRole],
swizzleExpectations{
desc: fmt.Sprintf("snapshot missing root meta checksum"),
expectErrs: []interface{}{client.ErrMissingMeta{}},
swizzle: func(s *testutils.MetadataSwizzler, role string) error {
return s.MutateSnapshot(func(sn *data.Snapshot) {
delete(sn.Meta, missing)
})
},
})
}
perRoleSwizzling[data.CanonicalTargetsRole] = []swizzleExpectations{{
desc: fmt.Sprintf("target missing delegations data"),
expectErrs: []interface{}{client.ErrChecksumMismatch{}},
swizzle: func(s *testutils.MetadataSwizzler, role string) error {
return s.MutateTargets(func(tg *data.Targets) {
tg.Delegations.Roles = tg.Delegations.Roles[1:]
})
},
}}
perRoleSwizzling[data.CanonicalTimestampRole] = []swizzleExpectations{{
desc: fmt.Sprintf("timestamp missing snapshot meta checksum"),
expectErrs: []interface{}{client.ErrMissingMeta{}},
swizzle: func(s *testutils.MetadataSwizzler, role string) error {
return s.MutateTimestamp(func(ts *data.Timestamp) {
delete(ts.Meta, data.CanonicalSnapshotRole)
})
},
}}
perRoleSwizzling["targets/a"] = []swizzleExpectations{{
desc: fmt.Sprintf("delegation has invalid role"),
expectErrs: []interface{}{client.ErrMissingMeta{}},
swizzle: func(s *testutils.MetadataSwizzler, role string) error {
return s.MutateTargets(func(tg *data.Targets) {
var keyIDs []string
for k := range tg.Delegations.Keys {
keyIDs = append(keyIDs, k)
}
// add the keys from root too
rootMeta, err := s.MetadataCache.GetMeta(data.CanonicalRootRole, -1)
require.NoError(t, err)
signedRoot := &data.SignedRoot{}
require.NoError(t, json.Unmarshal(rootMeta, signedRoot))
for k := range signedRoot.Signed.Keys {
keyIDs = append(keyIDs, k)
}
// add an invalid role (root) to delegation
tg.Delegations.Roles = append(tg.Delegations.Roles,
&data.Role{RootRole: data.RootRole{KeyIDs: keyIDs, Threshold: 1},
Name: data.CanonicalRootRole})
})
},
}}
return perRoleSwizzling
}
// If there's no local cache, we just download from the server and if anything
// is corrupt, we cannot update.
func TestUpdateNonRootRemoteCorruptedNoLocalCache(t *testing.T) {
@@ -894,6 +992,32 @@ func TestUpdateNonRootRemoteCorruptedNoLocalCache(t *testing.T) {
}, testData, true)
}
}
for role, expectations := range waysToMessUpServerNonRootPerRole(t) {
for _, testData := range expectations {
switch role {
case data.CanonicalSnapshotRole:
testUpdateRemoteCorruptValidChecksum(t, updateOpts{
role: role,
}, testData, true)
case data.CanonicalTargetsRole:
// if there are no delegation target roles, we're fine, we just don't
// download them
testUpdateRemoteCorruptValidChecksum(t, updateOpts{
role: role,
}, testData, false)
case data.CanonicalTimestampRole:
testUpdateRemoteCorruptValidChecksum(t, updateOpts{
role: role,
}, testData, true)
case "targets/a":
// TODO: Bug where invalid (non-delegation) roles in delegations
// are accepted
testUpdateRemoteCorruptValidChecksum(t, updateOpts{
role: role,
}, testData, false)
}
}
}
}
// Having a local cache, if the server has the same data (timestamp has not changed),
@@ -916,6 +1040,37 @@ func TestUpdateNonRootRemoteCorruptedCanUseLocalCache(t *testing.T) {
}, testData, false)
}
}
for role, expectations := range waysToMessUpServerNonRootPerRole(t) {
for _, testData := range expectations {
switch role {
case data.CanonicalSnapshotRole:
testUpdateRemoteCorruptValidChecksum(t, updateOpts{
localCache: true,
role: role,
}, testData, false)
case data.CanonicalTargetsRole:
testUpdateRemoteCorruptValidChecksum(t, updateOpts{
localCache: true,
role: role,
}, testData, false)
case data.CanonicalTimestampRole:
// TODO: Bug: this should succeed, because it's an invalid timestamp metadata
// and hence should fall back on the the local cached timestamp. It's not
// succeeding now because the failure occurs on downloading the snapshot,
// not during the validation of the timestamp.
testUpdateRemoteCorruptValidChecksum(t, updateOpts{
localCache: true,
role: role,
}, testData, true)
case "targets/a":
testUpdateRemoteCorruptValidChecksum(t, updateOpts{
localCache: true,
role: role,
}, testData, false)
}
}
}
}
// Having a local cache, if the server has new same data should fail in all cases
@@ -944,6 +1099,41 @@ func TestUpdateNonRootRemoteCorruptedCannotUseLocalCache(t *testing.T) {
}, testData, shouldFail)
}
}
for role, expectations := range waysToMessUpServerNonRootPerRole(t) {
for _, testData := range expectations {
switch role {
case data.CanonicalSnapshotRole:
testUpdateRemoteCorruptValidChecksum(t, updateOpts{
serverHasNewData: true,
localCache: true,
role: role,
}, testData, true)
case data.CanonicalTargetsRole:
// if there are no delegation target roles, we're fine, we just don't
// download them
testUpdateRemoteCorruptValidChecksum(t, updateOpts{
serverHasNewData: true,
localCache: true,
role: role,
}, testData, false)
case data.CanonicalTimestampRole:
testUpdateRemoteCorruptValidChecksum(t, updateOpts{
serverHasNewData: true,
localCache: true,
role: role,
}, testData, true)
case "targets/a":
// TODO: Bug where invalid (non-delegation) roles in delegations
// are accepted
testUpdateRemoteCorruptValidChecksum(t, updateOpts{
serverHasNewData: true,
localCache: true,
role: role,
}, testData, false)
}
}
}
}
func testUpdateRemoteCorruptValidChecksum(t *testing.T, opts updateOpts, expt swizzleExpectations, shouldErr bool) {
@@ -977,10 +1167,16 @@ func testUpdateRemoteCorruptValidChecksum(t *testing.T, opts updateOpts, expt sw
isSignatureSwizzle := expt.desc == "invalid signatures" || expt.desc == "meta signed by wrong key"
// just try to do these - if they fail (probably because they've been swizzled), that's fine
if opts.role != data.CanonicalSnapshotRole || !isSignatureSwizzle {
serverSwizzler.UpdateSnapshotHashes()
// if we are purposely editing out some snapshot metadata, don't re-generate
if !strings.HasPrefix(expt.desc, "snapshot missing") {
serverSwizzler.UpdateSnapshotHashes()
}
}
if opts.role != data.CanonicalTimestampRole || !isSignatureSwizzle {
serverSwizzler.UpdateTimestampHash()
// if we are purposely editing out some timestamp metadata, don't re-generate
if !strings.HasPrefix(expt.desc, "timestamp missing") {
serverSwizzler.UpdateTimestampHash()
}
}
}
_, err := repo.Update(opts.forWrite)

View File

@@ -128,8 +128,10 @@ func (c *Client) downloadRoot() error {
var size int64 = -1
var expectedSha256 []byte
if c.local.Snapshot != nil {
size = c.local.Snapshot.Signed.Meta[role].Length
expectedSha256 = c.local.Snapshot.Signed.Meta[role].Hashes["sha256"]
if prevRootMeta, ok := c.local.Snapshot.Signed.Meta[role]; ok {
size = prevRootMeta.Length
expectedSha256 = prevRootMeta.Hashes["sha256"]
}
}
// if we're bootstrapping we may not have a cached root, an

View File

@@ -9,6 +9,7 @@ import (
"github.com/docker/notary/cryptoservice"
"github.com/docker/notary/passphrase"
"github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/signed"
"github.com/docker/notary/tuf/store"
@@ -41,7 +42,11 @@ func getPubKeys(cs signed.CryptoService, s *data.Signed, role string) ([]data.Pu
if err := json.Unmarshal(s.Signed, root); err != nil {
return nil, err
}
for _, pubKeyID := range root.Roles[data.CanonicalRootRole].KeyIDs {
rootRole, ok := root.Roles[data.CanonicalRootRole]
if !ok || rootRole == nil {
return nil, tuf.ErrNotLoaded{}
}
for _, pubKeyID := range rootRole.KeyIDs {
pubKeys = append(pubKeys, root.Keys[pubKeyID])
}
} else {
@@ -532,3 +537,137 @@ func (m *MetadataSwizzler) UpdateTimestampHash() error {
}
return m.MetadataCache.SetMeta(data.CanonicalTimestampRole, metaBytes)
}
// MutateRoot takes a function that mutates the root metadata - once done, it
// serializes the root again
func (m *MetadataSwizzler) MutateRoot(mutate func(*data.Root)) error {
signedThing, err := signedFromStore(m.MetadataCache, data.CanonicalRootRole)
if err != nil {
return err
}
var root data.Root
if err := json.Unmarshal(signedThing.Signed, &root); err != nil {
return err
}
// get the original keys, in case the mutation messes with the signing keys
oldPubKeys, err := getPubKeys(m.CryptoService, signedThing, data.CanonicalRootRole)
if err != nil {
return err
}
mutate(&root)
signedThing, err = data.SignedRoot{Signed: root, Signatures: signedThing.Signatures}.ToSigned()
if err != nil {
return err
}
pubKeys, err := getPubKeys(m.CryptoService, signedThing, data.CanonicalRootRole)
if err != nil || len(pubKeys) == 0 { // we have to sign it somehow - might as well use the old keys
pubKeys = oldPubKeys
}
metaBytes, err := serializeMetadata(m.CryptoService, signedThing, data.CanonicalRootRole, pubKeys...)
if err != nil {
return err
}
return m.MetadataCache.SetMeta(data.CanonicalRootRole, metaBytes)
}
// MutateTimestamp takes a function that mutates the timestamp metadata - once done, it
// serializes the timestamp again
func (m *MetadataSwizzler) MutateTimestamp(mutate func(*data.Timestamp)) error {
signedThing, err := signedFromStore(m.MetadataCache, data.CanonicalTimestampRole)
if err != nil {
return err
}
var timestamp data.Timestamp
if err := json.Unmarshal(signedThing.Signed, &timestamp); err != nil {
return err
}
mutate(&timestamp)
signedThing, err = data.SignedTimestamp{Signed: timestamp, Signatures: signedThing.Signatures}.ToSigned()
if err != nil {
return err
}
pubKeys, err := getPubKeys(m.CryptoService, signedThing, data.CanonicalTimestampRole)
if err != nil {
return err
}
metaBytes, err := serializeMetadata(m.CryptoService, signedThing, data.CanonicalTimestampRole, pubKeys...)
if err != nil {
return err
}
return m.MetadataCache.SetMeta(data.CanonicalTimestampRole, metaBytes)
}
// MutateSnapshot takes a function that mutates the snapshot metadata - once done, it
// serializes the snapshot again
func (m *MetadataSwizzler) MutateSnapshot(mutate func(*data.Snapshot)) error {
signedThing, err := signedFromStore(m.MetadataCache, data.CanonicalSnapshotRole)
if err != nil {
return err
}
var snapshot data.Snapshot
if err := json.Unmarshal(signedThing.Signed, &snapshot); err != nil {
return err
}
mutate(&snapshot)
signedThing, err = data.SignedSnapshot{Signed: snapshot, Signatures: signedThing.Signatures}.ToSigned()
if err != nil {
return err
}
pubKeys, err := getPubKeys(m.CryptoService, signedThing, data.CanonicalSnapshotRole)
if err != nil {
return err
}
metaBytes, err := serializeMetadata(m.CryptoService, signedThing, data.CanonicalSnapshotRole, pubKeys...)
if err != nil {
return err
}
return m.MetadataCache.SetMeta(data.CanonicalSnapshotRole, metaBytes)
}
// MutateTargets takes a function that mutates the targets metadata - once done, it
// serializes the targets again
func (m *MetadataSwizzler) MutateTargets(mutate func(*data.Targets)) error {
signedThing, err := signedFromStore(m.MetadataCache, data.CanonicalTargetsRole)
if err != nil {
return err
}
var targets data.Targets
if err := json.Unmarshal(signedThing.Signed, &targets); err != nil {
return err
}
mutate(&targets)
signedThing, err = data.SignedTargets{Signed: targets, Signatures: signedThing.Signatures}.ToSigned()
if err != nil {
return err
}
pubKeys, err := getPubKeys(m.CryptoService, signedThing, data.CanonicalTargetsRole)
if err != nil {
return err
}
metaBytes, err := serializeMetadata(m.CryptoService, signedThing, data.CanonicalTargetsRole, pubKeys...)
if err != nil {
return err
}
return m.MetadataCache.SetMeta(data.CanonicalTargetsRole, metaBytes)
}

View File

@@ -15,6 +15,7 @@ import (
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/signed"
"github.com/docker/notary/tuf/store"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -577,3 +578,95 @@ func TestMissingSigningKey(t *testing.T) {
require.IsType(t, ErrNoKeyForRole{}, f.UpdateSnapshotHashes())
require.IsType(t, ErrNoKeyForRole{}, f.UpdateTimestampHash())
}
// This mutates the root
func TestSwizzlerMutateRoot(t *testing.T) {
f, origMeta := createNewSwizzler(t)
assert.NoError(t, f.MutateRoot(func(r *data.Root) { r.Roles["hello"] = nil }))
for role, metaBytes := range origMeta {
newMeta, err := f.MetadataCache.GetMeta(role, -1)
require.NoError(t, err)
if role != data.CanonicalRootRole {
require.True(t, bytes.Equal(metaBytes, newMeta), "bytes have changed for role %s", role)
} else {
require.False(t, bytes.Equal(metaBytes, newMeta))
origSigned, newSigned := &data.SignedRoot{}, &data.SignedRoot{}
require.NoError(t, json.Unmarshal(metaBytes, origSigned))
require.NoError(t, json.Unmarshal(newMeta, newSigned))
require.Len(t, origSigned.Signed.Roles, 4)
require.Len(t, newSigned.Signed.Roles, 5)
}
}
}
// This mutates the timestamp
func TestSwizzlerMutateTimestamp(t *testing.T) {
f, origMeta := createNewSwizzler(t)
assert.NoError(t, f.MutateTimestamp(func(t *data.Timestamp) { t.Meta["hello"] = data.FileMeta{} }))
for role, metaBytes := range origMeta {
newMeta, err := f.MetadataCache.GetMeta(role, -1)
require.NoError(t, err)
if role != data.CanonicalTimestampRole {
require.True(t, bytes.Equal(metaBytes, newMeta), "bytes have changed for role %s", role)
} else {
require.False(t, bytes.Equal(metaBytes, newMeta))
origSigned, newSigned := &data.SignedTimestamp{}, &data.SignedTimestamp{}
require.NoError(t, json.Unmarshal(metaBytes, origSigned))
require.NoError(t, json.Unmarshal(newMeta, newSigned))
require.Len(t, origSigned.Signed.Meta, 1)
require.Len(t, newSigned.Signed.Meta, 2)
}
}
}
// This mutates the snapshot
func TestSwizzlerMutateSnapshot(t *testing.T) {
f, origMeta := createNewSwizzler(t)
assert.NoError(t, f.MutateSnapshot(func(s *data.Snapshot) { s.Meta["hello"] = data.FileMeta{} }))
for role, metaBytes := range origMeta {
newMeta, err := f.MetadataCache.GetMeta(role, -1)
require.NoError(t, err)
if role != data.CanonicalSnapshotRole {
require.True(t, bytes.Equal(metaBytes, newMeta), "bytes have changed for role %s", role)
} else {
require.False(t, bytes.Equal(metaBytes, newMeta))
origSigned, newSigned := &data.SignedSnapshot{}, &data.SignedSnapshot{}
require.NoError(t, json.Unmarshal(metaBytes, origSigned))
require.NoError(t, json.Unmarshal(newMeta, newSigned))
require.Len(t, origSigned.Signed.Meta, 4)
require.Len(t, newSigned.Signed.Meta, 5)
}
}
}
// This mutates the targets
func TestSwizzlerMutateTargets(t *testing.T) {
f, origMeta := createNewSwizzler(t)
assert.NoError(t, f.MutateTargets(func(t *data.Targets) { t.Targets["hello"] = data.FileMeta{} }))
for role, metaBytes := range origMeta {
newMeta, err := f.MetadataCache.GetMeta(role, -1)
require.NoError(t, err)
if role != data.CanonicalTargetsRole {
require.True(t, bytes.Equal(metaBytes, newMeta), "bytes have changed for role %s", role)
} else {
require.False(t, bytes.Equal(metaBytes, newMeta))
origSigned, newSigned := &data.SignedTargets{}, &data.SignedTargets{}
require.NoError(t, json.Unmarshal(metaBytes, origSigned))
require.NoError(t, json.Unmarshal(newMeta, newSigned))
require.Len(t, origSigned.Signed.Targets, 0)
require.Len(t, newSigned.Signed.Targets, 1)
}
}
}