Do not create the delegation metadata when the delegation is created.

Only create it when a target is added to it, or other delegations
are added to it, or when getting a child delegation.

Signed-off-by: Ying Li <ying.li@docker.com>
This commit is contained in:
Ying Li 2015-12-18 00:49:52 -08:00
parent f1761afc25
commit 7592a029ef
4 changed files with 320 additions and 30 deletions

View File

@ -764,6 +764,44 @@ func TestApplyTargetsDelegationParentDoesntExist(t *testing.T) {
assert.IsType(t, data.ErrInvalidRole{}, err)
}
// If there is no delegation target, ApplyTargets creates it
func TestApplyChangelistCreatesDelegation(t *testing.T) {
_, repo, cs := testutils.EmptyRepo()
newKey, err := cs.Create("targets/level1", data.ED25519Key)
assert.NoError(t, err)
r, err := data.NewRole("targets/level1", 1, []string{newKey.ID()}, nil, nil)
assert.NoError(t, err)
repo.UpdateDelegations(r, []data.PublicKey{newKey})
delete(repo.Targets, "targets/level1")
hash := sha256.Sum256([]byte{})
f := &data.FileMeta{
Length: 1,
Hashes: map[string][]byte{
"sha256": hash[:],
},
}
fjson, err := json.Marshal(f)
assert.NoError(t, err)
cl := changelist.NewMemChangelist()
assert.NoError(t, cl.Add(&changelist.TufChange{
Actn: changelist.ActionCreate,
Role: "targets/level1",
ChangeType: "target",
ChangePath: "latest",
Data: fjson,
}))
assert.NoError(t, applyChangelist(repo, cl))
_, ok := repo.Targets["targets/level1"]
assert.True(t, ok, "Failed to create the delegation target")
_, ok = repo.Targets["targets/level1"].Signed.Targets["latest"]
assert.True(t, ok, "Failed to write change to delegation target")
}
// Each change applies only to the role specified
func TestApplyChangelistTargetsToMultipleRoles(t *testing.T) {
_, repo, cs := testutils.EmptyRepo()
@ -808,7 +846,8 @@ func TestApplyChangelistTargetsToMultipleRoles(t *testing.T) {
assert.NoError(t, applyChangelist(repo, cl))
_, ok := repo.Targets["targets/level1"].Signed.Targets["latest"]
assert.True(t, ok)
assert.Empty(t, repo.Targets["targets/level2"].Signed.Targets)
_, ok = repo.Targets["targets/level2"]
assert.False(t, ok, "no change to targets/level2, so metadata not created")
}
// ApplyTargets falls back to role that exists when adding or deleting a change

View File

@ -836,7 +836,11 @@ func TestValidateTargetsLoadParent(t *testing.T) {
r, err := data.NewRole("targets/level1", 1, []string{k.ID()}, []string{""}, nil)
assert.NoError(t, err)
baseRepo.UpdateDelegations(r, []data.PublicKey{k})
err = baseRepo.UpdateDelegations(r, []data.PublicKey{k})
assert.NoError(t, err)
// no targets file is created for the new delegations, so force one
baseRepo.InitTargets("targets/level1")
// we're not going to validate things loaded from storage, so no need
// to sign the base targets, just Marshal it and set it into storage
@ -885,6 +889,9 @@ func TestValidateTargetsParentInUpdate(t *testing.T) {
baseRepo.UpdateDelegations(r, []data.PublicKey{k})
// no targets file is created for the new delegations, so force one
baseRepo.InitTargets("targets/level1")
targets, err := baseRepo.SignTargets("targets", data.DefaultExpires(data.CanonicalTargetsRole))
tgtsJSON, err := json.Marshal(targets)
@ -939,6 +946,9 @@ func TestValidateTargetsParentNotFound(t *testing.T) {
baseRepo.UpdateDelegations(r, []data.PublicKey{k})
// no targets file is created for the new delegations, so force one
baseRepo.InitTargets("targets/level1")
// generate the update object we're doing to use to call loadAndValidateTargets
del, err := baseRepo.SignTargets("targets/level1", data.DefaultExpires(data.CanonicalTargetsRole))
assert.NoError(t, err)

View File

@ -164,11 +164,20 @@ func (tr *Repo) GetDelegation(role string) (*data.Role, error) {
if !r.IsDelegation() {
return nil, data.ErrInvalidRole{Role: role, Reason: "not a valid delegated role"}
}
parent := filepath.Dir(role)
p, ok := tr.Targets[parent]
if !ok {
// check the parent role
if parentRole := tr.keysDB.GetRole(parent); parentRole == nil {
return nil, data.ErrInvalidRole{Role: role, Reason: "parent role not found"}
}
// check the parent role's metadata
p, ok := tr.Targets[parent]
if !ok { // the parent targetfile may not exist yet, so it can't be in the list
return nil, data.ErrNoSuchRole{Role: role}
}
foundAt := utils.FindRoleIndex(p.Signed.Delegations.Roles, role)
if foundAt < 0 {
return nil, data.ErrNoSuchRole{Role: role}
@ -185,10 +194,19 @@ func (tr *Repo) UpdateDelegations(role *data.Role, keys []data.PublicKey) error
return data.ErrInvalidRole{Role: role.Name, Reason: "not a valid delegated role"}
}
parent := filepath.Dir(role.Name)
p, ok := tr.Targets[parent]
if !ok {
// check the parent role
if parentRole := tr.keysDB.GetRole(parent); parentRole == nil {
return data.ErrInvalidRole{Role: role.Name, Reason: "parent role not found"}
}
// check the parent role's metadata
p, ok := tr.Targets[parent]
if !ok { // the parent targetfile may not exist yet - if not, then create it
p = data.NewTargets()
tr.Targets[parent] = p
}
for _, k := range keys {
if !utils.StrSliceContains(role.KeyIDs, k.ID()) {
role.KeyIDs = append(role.KeyIDs, k.ID())
@ -214,11 +232,11 @@ func (tr *Repo) UpdateDelegations(role *data.Role, keys []data.PublicKey) error
// We've made a change to parent. Set it to dirty
p.Dirty = true
roleTargets := data.NewTargets() // NewTargets always marked Dirty
tr.Targets[role.Name] = roleTargets
// We don't actually want to create the new delegation metadata yet.
// When we add a delegation, it may only be signable by a key we don't have
// (hence we are delegating signing).
tr.keysDB.AddRole(role)
utils.RemoveUnusedKeys(p)
return nil
@ -476,18 +494,26 @@ func (tr Repo) FindTarget(path string) *data.FileMeta {
}
// AddTargets will attempt to add the given targets specifically to
// the directed role. If the user does not have the signing keys for the role
// the function will return an error and the full slice of targets.
// the directed role. If the metadata for the role doesn't exist yet,
// AddTargets will create one.
func (tr *Repo) AddTargets(role string, targets data.Files) (data.Files, error) {
t, ok := tr.Targets[role]
if !ok {
return targets, data.ErrInvalidRole{Role: role, Reason: "does not exist"}
// check the role exists
r := tr.keysDB.GetRole(role)
if r == nil {
return nil, data.ErrInvalidRole{Role: role, Reason: "does not exist"}
}
// check the role's metadata
t, ok := tr.Targets[role]
if !ok { // the targetfile may not exist yet - if not, then create it
t = data.NewTargets()
tr.Targets[role] = t
}
invalid := make(data.Files)
for path, target := range targets {
pathDigest := sha256.Sum256([]byte(path))
pathHex := hex.EncodeToString(pathDigest[:])
r := tr.keysDB.GetRole(role)
if role == data.ValidRoles["targets"] || (r.CheckPaths(path) || r.CheckPrefixes(pathHex)) {
t.Signed.Targets[path] = target
} else {

View File

@ -1,6 +1,7 @@
package tuf
import (
"crypto/sha256"
"encoding/json"
"io/ioutil"
"os"
@ -155,7 +156,12 @@ func TestUpdateDelegations(t *testing.T) {
err = repo.UpdateDelegations(role, data.KeyList{testKey})
assert.NoError(t, err)
r := repo.Targets[data.CanonicalTargetsRole]
// no empty metadata is created for this role
_, ok := repo.Targets["targets/test"]
assert.False(t, ok, "no empty targets file should be created for deepest delegation")
r, ok := repo.Targets[data.CanonicalTargetsRole]
assert.True(t, ok)
assert.Len(t, r.Signed.Delegations.Roles, 1)
assert.Len(t, r.Signed.Delegations.Keys, 1)
keyIDs := r.Signed.Delegations.Roles[0].KeyIDs
@ -170,13 +176,20 @@ func TestUpdateDelegations(t *testing.T) {
err = repo.UpdateDelegations(roleDeep, data.KeyList{testDeepKey})
assert.NoError(t, err)
r = repo.Targets["targets/test"]
// this metadata didn't exist before, but creating targets/test/deep created
// the targets/test metadata
r, ok = repo.Targets["targets/test"]
assert.True(t, ok)
assert.Len(t, r.Signed.Delegations.Roles, 1)
assert.Len(t, r.Signed.Delegations.Keys, 1)
keyIDs = r.Signed.Delegations.Roles[0].KeyIDs
assert.Len(t, keyIDs, 1)
assert.Equal(t, testDeepKey.ID(), keyIDs[0])
assert.True(t, r.Dirty)
// no empty delegation metadata is created for targets/test/deep
_, ok = repo.Targets["targets/test/deep"]
assert.False(t, ok, "no empty targets file should be created for deepest delegation")
}
func TestUpdateDelegationsParentMissing(t *testing.T) {
@ -193,8 +206,13 @@ func TestUpdateDelegationsParentMissing(t *testing.T) {
assert.Error(t, err)
assert.IsType(t, data.ErrInvalidRole{}, err)
r := repo.Targets[data.CanonicalTargetsRole]
r, ok := repo.Targets[data.CanonicalTargetsRole]
assert.True(t, ok)
assert.Len(t, r.Signed.Delegations.Roles, 0)
// no delegation metadata created for non-existent parent
_, ok = repo.Targets["targets/test"]
assert.False(t, ok, "no targets file should be created for nonexistent parent delegation")
}
func TestUpdateDelegationsInvalidRole(t *testing.T) {
@ -214,8 +232,13 @@ func TestUpdateDelegationsInvalidRole(t *testing.T) {
assert.Error(t, err)
assert.IsType(t, data.ErrInvalidRole{}, err)
r := repo.Targets[data.CanonicalTargetsRole]
r, ok := repo.Targets[data.CanonicalTargetsRole]
assert.True(t, ok)
assert.Len(t, r.Signed.Delegations.Roles, 0)
// no delegation metadata created for invalid delgation
_, ok = repo.Targets["root"]
assert.False(t, ok, "no targets file should be created since delegation failed")
}
func TestUpdateDelegationsRoleMissingKey(t *testing.T) {
@ -233,13 +256,18 @@ func TestUpdateDelegationsRoleMissingKey(t *testing.T) {
err = repo.UpdateDelegations(role, data.KeyList{roleKey})
assert.NoError(t, err)
r := repo.Targets[data.CanonicalTargetsRole]
r, ok := repo.Targets[data.CanonicalTargetsRole]
assert.True(t, ok)
assert.Len(t, r.Signed.Delegations.Roles, 1)
assert.Len(t, r.Signed.Delegations.Keys, 1)
keyIDs := r.Signed.Delegations.Roles[0].KeyIDs
assert.Len(t, keyIDs, 1)
assert.Equal(t, roleKey.ID(), keyIDs[0])
assert.True(t, r.Dirty)
// no empty delegation metadata created for new delegation
_, ok = repo.Targets["targets/role"]
assert.False(t, ok, "no targets file should be created for empty delegation")
}
func TestUpdateDelegationsNotEnoughKeys(t *testing.T) {
@ -253,10 +281,13 @@ func TestUpdateDelegationsNotEnoughKeys(t *testing.T) {
role, err := data.NewRole("targets/role", 2, []string{}, []string{""}, []string{})
assert.NoError(t, err)
// key should get added to role as part of updating the delegation
err = repo.UpdateDelegations(role, data.KeyList{roleKey})
assert.Error(t, err)
assert.IsType(t, data.ErrInvalidRole{}, err)
// no delegation metadata created for failed delegation
_, ok := repo.Targets["targets/role"]
assert.False(t, ok, "no targets file should be created since delegation failed")
}
func TestUpdateDelegationsReplaceRole(t *testing.T) {
@ -272,13 +303,22 @@ func TestUpdateDelegationsReplaceRole(t *testing.T) {
err = repo.UpdateDelegations(role, data.KeyList{testKey})
assert.NoError(t, err)
r := repo.Targets[data.CanonicalTargetsRole]
r, ok := repo.Targets[data.CanonicalTargetsRole]
assert.True(t, ok)
assert.Len(t, r.Signed.Delegations.Roles, 1)
assert.Len(t, r.Signed.Delegations.Keys, 1)
keyIDs := r.Signed.Delegations.Roles[0].KeyIDs
assert.Len(t, keyIDs, 1)
assert.Equal(t, testKey.ID(), keyIDs[0])
// no empty delegation metadata created for new delegation
_, ok = repo.Targets["targets/test"]
assert.False(t, ok, "no targets file should be created for empty delegation")
// create one now to assert that replacing the delegation doesn't delete the
// metadata
repo.Targets["targets/test"] = data.NewTargets()
// create another role with the same name and ensure it replaces the
// previous role
testKey2, err := ed25519.Create("targets/test", data.ED25519Key)
@ -289,13 +329,18 @@ func TestUpdateDelegationsReplaceRole(t *testing.T) {
err = repo.UpdateDelegations(role2, data.KeyList{testKey2})
assert.NoError(t, err)
r = repo.Targets["targets"]
r, ok = repo.Targets["targets"]
assert.True(t, ok)
assert.Len(t, r.Signed.Delegations.Roles, 1)
assert.Len(t, r.Signed.Delegations.Keys, 1)
keyIDs = r.Signed.Delegations.Roles[0].KeyIDs
assert.Len(t, keyIDs, 1)
assert.Equal(t, testKey2.ID(), keyIDs[0])
assert.True(t, r.Dirty)
// delegation was not deleted
_, ok = repo.Targets["targets/test"]
assert.True(t, ok, "targets file should still be here")
}
func TestUpdateDelegationsAddKeyToRole(t *testing.T) {
@ -311,7 +356,8 @@ func TestUpdateDelegationsAddKeyToRole(t *testing.T) {
err = repo.UpdateDelegations(role, data.KeyList{testKey})
assert.NoError(t, err)
r := repo.Targets[data.CanonicalTargetsRole]
r, ok := repo.Targets[data.CanonicalTargetsRole]
assert.True(t, ok)
assert.Len(t, r.Signed.Delegations.Roles, 1)
assert.Len(t, r.Signed.Delegations.Keys, 1)
keyIDs := r.Signed.Delegations.Roles[0].KeyIDs
@ -324,7 +370,8 @@ func TestUpdateDelegationsAddKeyToRole(t *testing.T) {
err = repo.UpdateDelegations(role, data.KeyList{testKey2})
assert.NoError(t, err)
r = repo.Targets["targets"]
r, ok = repo.Targets["targets"]
assert.True(t, ok)
assert.Len(t, r.Signed.Delegations.Roles, 1)
assert.Len(t, r.Signed.Delegations.Keys, 2)
keyIDs = r.Signed.Delegations.Roles[0].KeyIDs
@ -348,17 +395,26 @@ func TestDeleteDelegations(t *testing.T) {
err = repo.UpdateDelegations(role, data.KeyList{testKey})
assert.NoError(t, err)
r := repo.Targets[data.CanonicalTargetsRole]
r, ok := repo.Targets[data.CanonicalTargetsRole]
assert.True(t, ok)
assert.Len(t, r.Signed.Delegations.Roles, 1)
assert.Len(t, r.Signed.Delegations.Keys, 1)
keyIDs := r.Signed.Delegations.Roles[0].KeyIDs
assert.Len(t, keyIDs, 1)
assert.Equal(t, testKey.ID(), keyIDs[0])
// ensure that the metadata is there
repo.Targets["targets/test"] = data.NewTargets()
err = repo.DeleteDelegation(*role)
assert.Len(t, r.Signed.Delegations.Roles, 0)
assert.Len(t, r.Signed.Delegations.Keys, 0)
assert.True(t, r.Dirty)
// metadata should be deleted
_, ok = repo.Targets["targets/test"]
assert.False(t, ok)
}
func TestDeleteDelegationsRoleNotExist(t *testing.T) {
@ -376,7 +432,8 @@ func TestDeleteDelegationsRoleNotExist(t *testing.T) {
err = repo.DeleteDelegation(*role)
assert.NoError(t, err)
r := repo.Targets[data.CanonicalTargetsRole]
r, ok := repo.Targets[data.CanonicalTargetsRole]
assert.True(t, ok)
assert.Len(t, r.Signed.Delegations.Roles, 0)
assert.Len(t, r.Signed.Delegations.Keys, 0)
assert.False(t, r.Dirty)
@ -396,7 +453,8 @@ func TestDeleteDelegationsInvalidRole(t *testing.T) {
assert.Error(t, err)
assert.IsType(t, data.ErrInvalidRole{}, err)
r := repo.Targets[data.CanonicalTargetsRole]
r, ok := repo.Targets[data.CanonicalTargetsRole]
assert.True(t, ok)
assert.Len(t, r.Signed.Delegations.Roles, 0)
}
@ -412,7 +470,8 @@ func TestDeleteDelegationsParentMissing(t *testing.T) {
assert.Error(t, err)
assert.IsType(t, data.ErrInvalidRole{}, err)
r := repo.Targets[data.CanonicalTargetsRole]
r, ok := repo.Targets[data.CanonicalTargetsRole]
assert.True(t, ok)
assert.Len(t, r.Signed.Delegations.Roles, 0)
}
@ -444,12 +503,85 @@ func TestDeleteDelegationsMidSliceRole(t *testing.T) {
err = repo.DeleteDelegation(*role2)
assert.NoError(t, err)
r := repo.Targets[data.CanonicalTargetsRole]
r, ok := repo.Targets[data.CanonicalTargetsRole]
assert.True(t, ok)
assert.Len(t, r.Signed.Delegations.Roles, 2)
assert.Len(t, r.Signed.Delegations.Keys, 1)
assert.True(t, r.Dirty)
}
// If the parent exists, the metadata exists, and the delegation is in it,
// returns the role that was found
func TestGetDelegationRoleAndMetadataExistDelegationExists(t *testing.T) {
ed25519 := signed.NewEd25519()
keyDB := keys.NewDB()
repo := initRepo(t, ed25519, keyDB)
testKey, err := ed25519.Create("meh", data.ED25519Key)
assert.NoError(t, err)
role, err := data.NewRole(
"targets/level1", 1, []string{testKey.ID()}, []string{}, []string{})
assert.NoError(t, err)
assert.NoError(t, repo.UpdateDelegations(role, data.KeyList{testKey}))
role, err = data.NewRole(
"targets/level1/level2", 1, []string{testKey.ID()}, []string{}, []string{})
assert.NoError(t, err)
assert.NoError(t, repo.UpdateDelegations(role, data.KeyList{testKey}))
gottenRole, err := repo.GetDelegation("targets/level1/level2")
assert.NoError(t, err)
assert.Equal(t, role, gottenRole)
}
// If the parent exists, the metadata exists, and the delegation isn't in it,
// returns an ErrNoSuchRole
func TestGetDelegationRoleAndMetadataExistDelegationDoesntExists(t *testing.T) {
ed25519 := signed.NewEd25519()
keyDB := keys.NewDB()
repo := initRepo(t, ed25519, keyDB)
testKey, err := ed25519.Create("meh", data.ED25519Key)
assert.NoError(t, err)
role, err := data.NewRole(
"targets/level1", 1, []string{testKey.ID()}, []string{}, []string{})
assert.NoError(t, err)
assert.NoError(t, repo.UpdateDelegations(role, data.KeyList{testKey}))
// ensure metadata exists
repo.Targets["targets/level1"] = data.NewTargets()
_, err = repo.GetDelegation("targets/level1/level2")
assert.Error(t, err)
assert.IsType(t, data.ErrNoSuchRole{}, err)
}
// If the parent exists but the metadata doesn't exist, returns an ErrNoSuchRole
func TestGetDelegationRoleAndMetadataDoesntExists(t *testing.T) {
ed25519 := signed.NewEd25519()
keyDB := keys.NewDB()
repo := initRepo(t, ed25519, keyDB)
testKey, err := ed25519.Create("meh", data.ED25519Key)
assert.NoError(t, err)
role, err := data.NewRole(
"targets/level1", 1, []string{testKey.ID()}, []string{}, []string{})
assert.NoError(t, err)
assert.NoError(t, repo.UpdateDelegations(role, data.KeyList{testKey}))
// no empty delegation metadata created for new delegation
_, ok := repo.Targets["targets/test"]
assert.False(t, ok, "no targets file should be created for empty delegation")
_, err = repo.GetDelegation("targets/level1/level2")
assert.Error(t, err)
assert.IsType(t, data.ErrNoSuchRole{}, err)
}
// If the parent role doesn't exist, GetDelegation fails with an ErrInvalidRole
func TestGetDelegationParentMissing(t *testing.T) {
ed25519 := signed.NewEd25519()
keyDB := keys.NewDB()
@ -459,3 +591,86 @@ func TestGetDelegationParentMissing(t *testing.T) {
assert.Error(t, err)
assert.IsType(t, data.ErrInvalidRole{}, err)
}
// Adding targets to a role that exists and has metadata (like targets)
// correctly adds the target
func TestAddTargetsRoleAndMetadataExist(t *testing.T) {
ed25519 := signed.NewEd25519()
keyDB := keys.NewDB()
repo := initRepo(t, ed25519, keyDB)
hash := sha256.Sum256([]byte{})
f := data.FileMeta{
Length: 1,
Hashes: map[string][]byte{
"sha256": hash[:],
},
}
_, err := repo.AddTargets(data.CanonicalTargetsRole, data.Files{"f": f})
assert.NoError(t, err)
r, ok := repo.Targets[data.CanonicalTargetsRole]
assert.True(t, ok)
targetsF, ok := r.Signed.Targets["f"]
assert.True(t, ok)
assert.Equal(t, f, targetsF)
}
// Adding targets to a role that exists and has not metadata first creates the
// metadata and then correctly adds the target
func TestAddTargetsRoleExistsAndMetadataDoesntExist(t *testing.T) {
hash := sha256.Sum256([]byte{})
f := data.FileMeta{
Length: 1,
Hashes: map[string][]byte{
"sha256": hash[:],
},
}
ed25519 := signed.NewEd25519()
keyDB := keys.NewDB()
repo := initRepo(t, ed25519, keyDB)
testKey, err := ed25519.Create("targets/test", data.ED25519Key)
assert.NoError(t, err)
role, err := data.NewRole(
"targets/test", 1, []string{testKey.ID()}, []string{"test"}, []string{})
assert.NoError(t, err)
err = repo.UpdateDelegations(role, data.KeyList{testKey})
assert.NoError(t, err)
// no empty metadata is created for this role
_, ok := repo.Targets["targets/test"]
assert.False(t, ok, "no empty targets file should be created")
// adding the targets to the role should create the metadata though
_, err = repo.AddTargets("targets/test", data.Files{"f": f})
assert.NoError(t, err)
r, ok := repo.Targets["targets/test"]
assert.True(t, ok)
targetsF, ok := r.Signed.Targets["f"]
assert.True(t, ok)
assert.Equal(t, f, targetsF)
}
// Adding targets to a role that doesn't exist fails
func TestAddTargetsRoleDoesntExist(t *testing.T) {
hash := sha256.Sum256([]byte{})
f := data.FileMeta{
Length: 1,
Hashes: map[string][]byte{
"sha256": hash[:],
},
}
ed25519 := signed.NewEd25519()
keyDB := keys.NewDB()
repo := initRepo(t, ed25519, keyDB)
_, err := repo.AddTargets("targets/test", data.Files{"f": f})
assert.Error(t, err)
assert.IsType(t, data.ErrInvalidRole{}, err)
}