mirror of https://github.com/docker/docs.git
Merge pull request #528 from docker/delegation-api
Break down client API for delegations
This commit is contained in:
commit
6acb6a1802
|
@ -88,6 +88,7 @@ type TufDelegation struct {
|
||||||
RemoveKeys []string `json:"remove_keys,omitempty"`
|
RemoveKeys []string `json:"remove_keys,omitempty"`
|
||||||
AddPaths []string `json:"add_paths,omitempty"`
|
AddPaths []string `json:"add_paths,omitempty"`
|
||||||
RemovePaths []string `json:"remove_paths,omitempty"`
|
RemovePaths []string `json:"remove_paths,omitempty"`
|
||||||
|
ClearAllPaths bool `json:"clear_paths,omitempty"`
|
||||||
AddPathHashPrefixes []string `json:"add_prefixes,omitempty"`
|
AddPathHashPrefixes []string `json:"add_prefixes,omitempty"`
|
||||||
RemovePathHashPrefixes []string `json:"remove_prefixes,omitempty"`
|
RemovePathHashPrefixes []string `json:"remove_prefixes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
164
client/client.go
164
client/client.go
|
@ -24,7 +24,6 @@ import (
|
||||||
"github.com/docker/notary/tuf/keys"
|
"github.com/docker/notary/tuf/keys"
|
||||||
"github.com/docker/notary/tuf/signed"
|
"github.com/docker/notary/tuf/signed"
|
||||||
"github.com/docker/notary/tuf/store"
|
"github.com/docker/notary/tuf/store"
|
||||||
"github.com/docker/notary/tuf/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -302,96 +301,6 @@ func addChange(cl *changelist.FileChangelist, c changelist.Change, roles ...stri
|
||||||
return nil
|
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
// AddTarget creates new changelist entries to add a target to the given roles
|
||||||
// in the repository when the changelist gets applied at publish time.
|
// in the repository when the changelist gets applied at publish time.
|
||||||
// If roles are unspecified, the default role is "targets".
|
// If roles are unspecified, the default role is "targets".
|
||||||
|
@ -529,79 +438,6 @@ func (r *NotaryRepository) GetChangelist() (changelist.Changelist, error) {
|
||||||
return cl, nil
|
return cl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDelegationRoles returns the keys and roles of the repository's delegations
|
|
||||||
// Also converts key IDs to canonical key IDs to keep consistent with signing prompts
|
|
||||||
func (r *NotaryRepository) GetDelegationRoles() ([]*data.Role, error) {
|
|
||||||
// Update state of the repo to latest
|
|
||||||
if _, err := r.Update(false); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// All top level delegations (ex: targets/level1) are stored exclusively in targets.json
|
|
||||||
targets, ok := r.tufRepo.Targets[data.CanonicalTargetsRole]
|
|
||||||
if !ok {
|
|
||||||
return nil, store.ErrMetaNotFound{Resource: data.CanonicalTargetsRole}
|
|
||||||
}
|
|
||||||
|
|
||||||
// make a copy of top-level Delegations and only show canonical key IDs
|
|
||||||
allDelegations, err := translateDelegationsToCanonicalIDs(targets.Signed.Delegations)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// make a copy for traversing nested delegations
|
|
||||||
delegationsList := make([]*data.Role, len(allDelegations))
|
|
||||||
copy(delegationsList, allDelegations)
|
|
||||||
|
|
||||||
// Now traverse to lower level delegations (ex: targets/level1/level2)
|
|
||||||
for len(delegationsList) > 0 {
|
|
||||||
// Pop off first delegation to traverse
|
|
||||||
delegation := delegationsList[0]
|
|
||||||
delegationsList = delegationsList[1:]
|
|
||||||
|
|
||||||
// Get metadata
|
|
||||||
delegationMeta, ok := r.tufRepo.Targets[delegation.Name]
|
|
||||||
// If we get an error, don't try to traverse further into this subtree because it doesn't exist or is malformed
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// For the return list, update with a copy that includes canonicalKeyIDs
|
|
||||||
canonicalDelegations, err := translateDelegationsToCanonicalIDs(delegationMeta.Signed.Delegations)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
allDelegations = append(allDelegations, canonicalDelegations...)
|
|
||||||
// Add nested delegations to the exploration list
|
|
||||||
delegationsList = append(delegationsList, delegationMeta.Signed.Delegations.Roles...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert all key IDs to canonical IDs:
|
|
||||||
return allDelegations, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func translateDelegationsToCanonicalIDs(delegationInfo data.Delegations) ([]*data.Role, error) {
|
|
||||||
canonicalDelegations := make([]*data.Role, len(delegationInfo.Roles))
|
|
||||||
copy(canonicalDelegations, delegationInfo.Roles)
|
|
||||||
delegationKeys := delegationInfo.Keys
|
|
||||||
for i, delegation := range canonicalDelegations {
|
|
||||||
canonicalKeyIDs := []string{}
|
|
||||||
for _, keyID := range delegation.KeyIDs {
|
|
||||||
pubKey, ok := delegationKeys[keyID]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("Could not translate canonical key IDs for %s", delegation.Name)
|
|
||||||
}
|
|
||||||
canonicalKeyID, err := utils.CanonicalKeyID(pubKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Could not translate canonical key IDs for %s: %v", delegation.Name, err)
|
|
||||||
}
|
|
||||||
canonicalKeyIDs = append(canonicalKeyIDs, canonicalKeyID)
|
|
||||||
}
|
|
||||||
canonicalDelegations[i].KeyIDs = canonicalKeyIDs
|
|
||||||
}
|
|
||||||
return canonicalDelegations, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoleWithSignatures is a Role with its associated signatures
|
// RoleWithSignatures is a Role with its associated signatures
|
||||||
type RoleWithSignatures struct {
|
type RoleWithSignatures struct {
|
||||||
Signatures []data.Signature
|
Signatures []data.Signature
|
||||||
|
|
|
@ -1910,10 +1910,10 @@ func testPublishDelegations(t *testing.T, clearCache, x509Keys bool) {
|
||||||
// targets/a, because these should execute in order
|
// targets/a, because these should execute in order
|
||||||
for _, delgName := range []string{"targets/a", "targets/a/b", "targets/c"} {
|
for _, delgName := range []string{"targets/a", "targets/a/b", "targets/c"} {
|
||||||
assert.NoError(t,
|
assert.NoError(t,
|
||||||
repo1.AddDelegation(delgName, 1, []data.PublicKey{delgKey}, []string{""}),
|
repo1.AddDelegation(delgName, []data.PublicKey{delgKey}, []string{""}),
|
||||||
"error creating delegation")
|
"error creating delegation")
|
||||||
}
|
}
|
||||||
assert.Len(t, getChanges(t, repo1), 3, "wrong number of changelist files found")
|
assert.Len(t, getChanges(t, repo1), 6, "wrong number of changelist files found")
|
||||||
|
|
||||||
var rec *passRoleRecorder
|
var rec *passRoleRecorder
|
||||||
if clearCache {
|
if clearCache {
|
||||||
|
@ -1932,11 +1932,11 @@ func testPublishDelegations(t *testing.T, clearCache, x509Keys bool) {
|
||||||
|
|
||||||
// this should not publish, because targets/z doesn't exist
|
// this should not publish, because targets/z doesn't exist
|
||||||
assert.NoError(t,
|
assert.NoError(t,
|
||||||
repo1.AddDelegation("targets/z/y", 1, []data.PublicKey{delgKey}, []string{""}),
|
repo1.AddDelegation("targets/z/y", []data.PublicKey{delgKey}, []string{""}),
|
||||||
"error creating delegation")
|
"error creating delegation")
|
||||||
assert.Len(t, getChanges(t, repo1), 1, "wrong number of changelist files found")
|
assert.Len(t, getChanges(t, repo1), 2, "wrong number of changelist files found")
|
||||||
assert.Error(t, repo1.Publish())
|
assert.Error(t, repo1.Publish())
|
||||||
assert.Len(t, getChanges(t, repo1), 1, "wrong number of changelist files found")
|
assert.Len(t, getChanges(t, repo1), 2, "wrong number of changelist files found")
|
||||||
|
|
||||||
if clearCache {
|
if clearCache {
|
||||||
rec.assertAsked(t, nil)
|
rec.assertAsked(t, nil)
|
||||||
|
@ -2035,7 +2035,7 @@ func testPublishTargetsDelgationScopeNoFallbackIfNoKeys(t *testing.T, clearCache
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure that the role exists
|
// ensure that the role exists
|
||||||
assert.NoError(t, repo.AddDelegation("targets/a", 1, []data.PublicKey{aPubKey}, []string{""}))
|
assert.NoError(t, repo.AddDelegation("targets/a", []data.PublicKey{aPubKey}, []string{""}))
|
||||||
assert.NoError(t, repo.Publish())
|
assert.NoError(t, repo.Publish())
|
||||||
|
|
||||||
if clearCache {
|
if clearCache {
|
||||||
|
@ -2076,7 +2076,7 @@ func TestPublishTargetsDelgationSuccessLocallyHasRoles(t *testing.T) {
|
||||||
for _, delgName := range []string{"targets/a", "targets/a/b"} {
|
for _, delgName := range []string{"targets/a", "targets/a/b"} {
|
||||||
delgKey := createKey(t, repo, delgName, false)
|
delgKey := createKey(t, repo, delgName, false)
|
||||||
assert.NoError(t,
|
assert.NoError(t,
|
||||||
repo.AddDelegation(delgName, 1, []data.PublicKey{delgKey}, []string{""}),
|
repo.AddDelegation(delgName, []data.PublicKey{delgKey}, []string{""}),
|
||||||
"error creating delegation")
|
"error creating delegation")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2106,7 +2106,7 @@ func TestPublishTargetsDelgationNoTargetsKeyNeeded(t *testing.T) {
|
||||||
for _, delgName := range []string{"targets/a", "targets/a/b"} {
|
for _, delgName := range []string{"targets/a", "targets/a/b"} {
|
||||||
delgKey := createKey(t, repo, delgName, false)
|
delgKey := createKey(t, repo, delgName, false)
|
||||||
assert.NoError(t,
|
assert.NoError(t,
|
||||||
repo.AddDelegation(delgName, 1, []data.PublicKey{delgKey}, []string{""}),
|
repo.AddDelegation(delgName, []data.PublicKey{delgKey}, []string{""}),
|
||||||
"error creating delegation")
|
"error creating delegation")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2170,10 +2170,10 @@ func TestPublishTargetsDelgationSuccessNeedsToDownloadRoles(t *testing.T) {
|
||||||
|
|
||||||
// owner creates delegations, adds the delegated key to them, and publishes them
|
// owner creates delegations, adds the delegated key to them, and publishes them
|
||||||
assert.NoError(t,
|
assert.NoError(t,
|
||||||
ownerRepo.AddDelegation("targets/a", 1, []data.PublicKey{aKey}, []string{""}),
|
ownerRepo.AddDelegation("targets/a", []data.PublicKey{aKey}, []string{""}),
|
||||||
"error creating delegation")
|
"error creating delegation")
|
||||||
assert.NoError(t,
|
assert.NoError(t,
|
||||||
ownerRepo.AddDelegation("targets/a/b", 1, []data.PublicKey{bKey}, []string{""}),
|
ownerRepo.AddDelegation("targets/a/b", []data.PublicKey{bKey}, []string{""}),
|
||||||
"error creating delegation")
|
"error creating delegation")
|
||||||
|
|
||||||
assert.NoError(t, ownerRepo.Publish())
|
assert.NoError(t, ownerRepo.Publish())
|
||||||
|
@ -2212,7 +2212,7 @@ func TestPublishTargetsDelgationFromTwoRepos(t *testing.T) {
|
||||||
|
|
||||||
// delegation includes both keys
|
// delegation includes both keys
|
||||||
assert.NoError(t,
|
assert.NoError(t,
|
||||||
repo1.AddDelegation("targets/a", 1, []data.PublicKey{key1, key2}, []string{""}),
|
repo1.AddDelegation("targets/a", []data.PublicKey{key1, key2}, []string{""}),
|
||||||
"error creating delegation")
|
"error creating delegation")
|
||||||
|
|
||||||
assert.NoError(t, repo1.Publish())
|
assert.NoError(t, repo1.Publish())
|
||||||
|
@ -2282,7 +2282,7 @@ func TestPublishRemoveDelgationKeyFromDelegationRole(t *testing.T) {
|
||||||
|
|
||||||
// owner creates delegation, adds the delegated key to it, and publishes it
|
// owner creates delegation, adds the delegated key to it, and publishes it
|
||||||
assert.NoError(t,
|
assert.NoError(t,
|
||||||
ownerRepo.AddDelegation("targets/a", 1, []data.PublicKey{aKey}, []string{""}),
|
ownerRepo.AddDelegation("targets/a", []data.PublicKey{aKey}, []string{""}),
|
||||||
"error creating delegation")
|
"error creating delegation")
|
||||||
assert.NoError(t, ownerRepo.Publish())
|
assert.NoError(t, ownerRepo.Publish())
|
||||||
|
|
||||||
|
@ -2340,7 +2340,7 @@ func TestPublishRemoveDelgation(t *testing.T) {
|
||||||
|
|
||||||
// owner creates delegation, adds the delegated key to it, and publishes it
|
// owner creates delegation, adds the delegated key to it, and publishes it
|
||||||
assert.NoError(t,
|
assert.NoError(t,
|
||||||
ownerRepo.AddDelegation("targets/a", 1, []data.PublicKey{aKey}, []string{""}),
|
ownerRepo.AddDelegation("targets/a", []data.PublicKey{aKey}, []string{""}),
|
||||||
"error creating delegation")
|
"error creating delegation")
|
||||||
assert.NoError(t, ownerRepo.Publish())
|
assert.NoError(t, ownerRepo.Publish())
|
||||||
|
|
||||||
|
@ -2351,7 +2351,7 @@ func TestPublishRemoveDelgation(t *testing.T) {
|
||||||
// owner removes delegation
|
// owner removes delegation
|
||||||
aKeyCanonicalID, err := utils.CanonicalKeyID(aKey)
|
aKeyCanonicalID, err := utils.CanonicalKeyID(aKey)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NoError(t, ownerRepo.RemoveDelegation("targets/a", []string{aKeyCanonicalID}, []string{}, false))
|
assert.NoError(t, ownerRepo.RemoveDelegationKeys("targets/a", []string{aKeyCanonicalID}))
|
||||||
assert.NoError(t, ownerRepo.Publish())
|
assert.NoError(t, ownerRepo.Publish())
|
||||||
|
|
||||||
// delegated repo can now no longer publish to delegated role
|
// delegated repo can now no longer publish to delegated role
|
||||||
|
@ -2374,7 +2374,7 @@ func TestPublishSucceedsDespiteDelegationCorrupt(t *testing.T) {
|
||||||
assert.NoError(t, err, "error creating delegation key")
|
assert.NoError(t, err, "error creating delegation key")
|
||||||
|
|
||||||
assert.NoError(t,
|
assert.NoError(t,
|
||||||
repo.AddDelegation("targets/a", 1, []data.PublicKey{delgKey}, []string{""}),
|
repo.AddDelegation("targets/a", []data.PublicKey{delgKey}, []string{""}),
|
||||||
"error creating delegation")
|
"error creating delegation")
|
||||||
|
|
||||||
testPublishBadMetadata(t, "targets/a", repo, false, true)
|
testPublishBadMetadata(t, "targets/a", repo, false, true)
|
||||||
|
@ -2609,22 +2609,25 @@ func TestAddDelegationChangefileValid(t *testing.T) {
|
||||||
targetPubKey := repo.CryptoService.GetKey(targetKeyIds[0])
|
targetPubKey := repo.CryptoService.GetKey(targetKeyIds[0])
|
||||||
assert.NotNil(t, targetPubKey)
|
assert.NotNil(t, targetPubKey)
|
||||||
|
|
||||||
err := repo.AddDelegation("root", 1, []data.PublicKey{targetPubKey}, []string{""})
|
err := repo.AddDelegation("root", []data.PublicKey{targetPubKey}, []string{""})
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.IsType(t, data.ErrInvalidRole{}, err)
|
assert.IsType(t, data.ErrInvalidRole{}, err)
|
||||||
assert.Empty(t, getChanges(t, repo))
|
assert.Empty(t, getChanges(t, repo))
|
||||||
|
|
||||||
// to show that adding does not care about the hierarchy
|
// to show that adding does not care about the hierarchy
|
||||||
err = repo.AddDelegation("targets/a/b/c", 1, []data.PublicKey{targetPubKey}, []string{""})
|
err = repo.AddDelegation("targets/a/b/c", []data.PublicKey{targetPubKey}, []string{""})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// ensure that the changefiles is correct
|
// ensure that the changefiles is correct
|
||||||
changes := getChanges(t, repo)
|
changes := getChanges(t, repo)
|
||||||
assert.Len(t, changes, 1)
|
assert.Len(t, changes, 2)
|
||||||
assert.Equal(t, changelist.ActionCreate, changes[0].Action())
|
assert.Equal(t, changelist.ActionCreate, changes[0].Action())
|
||||||
assert.Equal(t, "targets/a/b/c", changes[0].Scope())
|
assert.Equal(t, "targets/a/b/c", changes[0].Scope())
|
||||||
assert.Equal(t, changelist.TypeTargetsDelegation, changes[0].Type())
|
assert.Equal(t, changelist.TypeTargetsDelegation, changes[0].Type())
|
||||||
assert.Equal(t, "", changes[0].Path())
|
assert.Equal(t, changelist.ActionCreate, changes[1].Action())
|
||||||
|
assert.Equal(t, "targets/a/b/c", changes[1].Scope())
|
||||||
|
assert.Equal(t, changelist.TypeTargetsDelegation, changes[1].Type())
|
||||||
|
assert.Equal(t, "", changes[1].Path())
|
||||||
assert.NotEmpty(t, changes[0].Content())
|
assert.NotEmpty(t, changes[0].Content())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2645,10 +2648,10 @@ func TestAddDelegationChangefileApplicable(t *testing.T) {
|
||||||
assert.NotNil(t, targetPubKey)
|
assert.NotNil(t, targetPubKey)
|
||||||
|
|
||||||
// this hierarchy has to be right to be applied
|
// this hierarchy has to be right to be applied
|
||||||
err := repo.AddDelegation("targets/a", 1, []data.PublicKey{targetPubKey}, []string{""})
|
err := repo.AddDelegation("targets/a", []data.PublicKey{targetPubKey}, []string{""})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
changes := getChanges(t, repo)
|
changes := getChanges(t, repo)
|
||||||
assert.Len(t, changes, 1)
|
assert.Len(t, changes, 2)
|
||||||
|
|
||||||
// ensure that it can be applied correctly
|
// ensure that it can be applied correctly
|
||||||
err = applyTargetsChange(repo.tufRepo, changes[0])
|
err = applyTargetsChange(repo.tufRepo, changes[0])
|
||||||
|
@ -2676,7 +2679,7 @@ func TestAddDelegationErrorWritingChanges(t *testing.T) {
|
||||||
targetPubKey := repo.CryptoService.GetKey(targetKeyIds[0])
|
targetPubKey := repo.CryptoService.GetKey(targetKeyIds[0])
|
||||||
assert.NotNil(t, targetPubKey)
|
assert.NotNil(t, targetPubKey)
|
||||||
|
|
||||||
return repo.AddDelegation("targets/a", 1, []data.PublicKey{targetPubKey}, []string{""})
|
return repo.AddDelegation("targets/a", []data.PublicKey{targetPubKey}, []string{""})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2693,14 +2696,14 @@ func TestRemoveDelegationChangefileValid(t *testing.T) {
|
||||||
rootPubKey := repo.CryptoService.GetKey(rootKeyID)
|
rootPubKey := repo.CryptoService.GetKey(rootKeyID)
|
||||||
assert.NotNil(t, rootPubKey)
|
assert.NotNil(t, rootPubKey)
|
||||||
|
|
||||||
err := repo.RemoveDelegation("root", []string{rootKeyID}, []string{}, false)
|
err := repo.RemoveDelegationKeys("root", []string{rootKeyID})
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.IsType(t, data.ErrInvalidRole{}, err)
|
assert.IsType(t, data.ErrInvalidRole{}, err)
|
||||||
assert.Empty(t, getChanges(t, repo))
|
assert.Empty(t, getChanges(t, repo))
|
||||||
|
|
||||||
// to demonstrate that so long as the delegation name is valid, the
|
// to demonstrate that so long as the delegation name is valid, the
|
||||||
// existence of the delegation doesn't matter
|
// existence of the delegation doesn't matter
|
||||||
assert.NoError(t, repo.RemoveDelegation("targets/a/b/c", []string{rootKeyID}, []string{}, false))
|
assert.NoError(t, repo.RemoveDelegationKeys("targets/a/b/c", []string{rootKeyID}))
|
||||||
|
|
||||||
// ensure that the changefile is correct
|
// ensure that the changefile is correct
|
||||||
changes := getChanges(t, repo)
|
changes := getChanges(t, repo)
|
||||||
|
@ -2711,7 +2714,7 @@ func TestRemoveDelegationChangefileValid(t *testing.T) {
|
||||||
assert.Equal(t, "", changes[0].Path())
|
assert.Equal(t, "", changes[0].Path())
|
||||||
}
|
}
|
||||||
|
|
||||||
// The changefile produced by RemoveDelegation, when applied, actually removes
|
// The changefile produced by RemoveDelegationKeys, when applied, actually removes
|
||||||
// the delegation from the repo (assuming the repo exists - tests for
|
// the delegation from the repo (assuming the repo exists - tests for
|
||||||
// change application validation are in helpers_test.go)
|
// change application validation are in helpers_test.go)
|
||||||
func TestRemoveDelegationChangefileApplicable(t *testing.T) {
|
func TestRemoveDelegationChangefileApplicable(t *testing.T) {
|
||||||
|
@ -2725,10 +2728,11 @@ func TestRemoveDelegationChangefileApplicable(t *testing.T) {
|
||||||
assert.NotNil(t, rootPubKey)
|
assert.NotNil(t, rootPubKey)
|
||||||
|
|
||||||
// add a delegation first so it can be removed
|
// add a delegation first so it can be removed
|
||||||
assert.NoError(t, repo.AddDelegation("targets/a", 1, []data.PublicKey{rootPubKey}, []string{""}))
|
assert.NoError(t, repo.AddDelegation("targets/a", []data.PublicKey{rootPubKey}, []string{""}))
|
||||||
changes := getChanges(t, repo)
|
changes := getChanges(t, repo)
|
||||||
assert.Len(t, changes, 1)
|
assert.Len(t, changes, 2)
|
||||||
assert.NoError(t, applyTargetsChange(repo.tufRepo, changes[0]))
|
assert.NoError(t, applyTargetsChange(repo.tufRepo, changes[0]))
|
||||||
|
assert.NoError(t, applyTargetsChange(repo.tufRepo, changes[1]))
|
||||||
|
|
||||||
targetRole := repo.tufRepo.Targets[data.CanonicalTargetsRole]
|
targetRole := repo.tufRepo.Targets[data.CanonicalTargetsRole]
|
||||||
assert.Len(t, targetRole.Signed.Delegations.Roles, 1)
|
assert.Len(t, targetRole.Signed.Delegations.Roles, 1)
|
||||||
|
@ -2737,21 +2741,137 @@ func TestRemoveDelegationChangefileApplicable(t *testing.T) {
|
||||||
// now remove it
|
// now remove it
|
||||||
rootKeyCanonicalID, err := utils.CanonicalKeyID(rootPubKey)
|
rootKeyCanonicalID, err := utils.CanonicalKeyID(rootPubKey)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NoError(t, repo.RemoveDelegation("targets/a", []string{rootKeyCanonicalID}, []string{}, false))
|
assert.NoError(t, repo.RemoveDelegationKeys("targets/a", []string{rootKeyCanonicalID}))
|
||||||
changes = getChanges(t, repo)
|
changes = getChanges(t, repo)
|
||||||
assert.Len(t, changes, 2)
|
assert.Len(t, changes, 3)
|
||||||
assert.NoError(t, applyTargetsChange(repo.tufRepo, changes[1]))
|
assert.NoError(t, applyTargetsChange(repo.tufRepo, changes[2]))
|
||||||
|
|
||||||
targetRole = repo.tufRepo.Targets[data.CanonicalTargetsRole]
|
targetRole = repo.tufRepo.Targets[data.CanonicalTargetsRole]
|
||||||
assert.Empty(t, targetRole.Signed.Delegations.Roles)
|
assert.Empty(t, targetRole.Signed.Delegations.Roles)
|
||||||
assert.Empty(t, targetRole.Signed.Delegations.Keys)
|
assert.Empty(t, targetRole.Signed.Delegations.Keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The changefile with the ClearAllPaths key set, when applied, actually removes
|
||||||
|
// all paths from the specified delegation in the repo (assuming the repo and delegation exist)
|
||||||
|
func TestClearAllPathsDelegationChangefileApplicable(t *testing.T) {
|
||||||
|
gun := "docker.com/notary"
|
||||||
|
ts, _, _ := simpleTestServer(t)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
repo, rootKeyID := initializeRepo(t, data.ECDSAKey, gun, ts.URL, false)
|
||||||
|
defer os.RemoveAll(repo.baseDir)
|
||||||
|
rootPubKey := repo.CryptoService.GetKey(rootKeyID)
|
||||||
|
assert.NotNil(t, rootPubKey)
|
||||||
|
|
||||||
|
// add a delegation first so it can be removed
|
||||||
|
assert.NoError(t, repo.AddDelegation("targets/a", []data.PublicKey{rootPubKey}, []string{"abc,123,xyz,path"}))
|
||||||
|
changes := getChanges(t, repo)
|
||||||
|
assert.Len(t, changes, 2)
|
||||||
|
assert.NoError(t, applyTargetsChange(repo.tufRepo, changes[0]))
|
||||||
|
assert.NoError(t, applyTargetsChange(repo.tufRepo, changes[1]))
|
||||||
|
|
||||||
|
// now clear paths it
|
||||||
|
assert.NoError(t, repo.ClearDelegationPaths("targets/a"))
|
||||||
|
changes = getChanges(t, repo)
|
||||||
|
assert.Len(t, changes, 3)
|
||||||
|
assert.NoError(t, applyTargetsChange(repo.tufRepo, changes[2]))
|
||||||
|
|
||||||
|
delgRoles := repo.tufRepo.Targets[data.CanonicalTargetsRole].Signed.Delegations.Roles
|
||||||
|
assert.Len(t, delgRoles, 1)
|
||||||
|
assert.Len(t, delgRoles[0].Paths, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFullAddDelegationChangefileApplicable generates a single changelist with AddKeys and AddPaths set,
|
||||||
|
// (in the old style of AddDelegation) and tests that all of its changes are reflected on publish
|
||||||
|
func TestFullAddDelegationChangefileApplicable(t *testing.T) {
|
||||||
|
gun := "docker.com/notary"
|
||||||
|
ts, _, _ := simpleTestServer(t)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
repo, rootKeyID := initializeRepo(t, data.ECDSAKey, gun, ts.URL, false)
|
||||||
|
defer os.RemoveAll(repo.baseDir)
|
||||||
|
rootPubKey := repo.CryptoService.GetKey(rootKeyID)
|
||||||
|
assert.NotNil(t, rootPubKey)
|
||||||
|
|
||||||
|
key2, err := repo.CryptoService.Create("user", data.ECDSAKey)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
delegationName := "targets/a"
|
||||||
|
|
||||||
|
// manually create the changelist object to load multiple keys
|
||||||
|
tdJSON, err := json.Marshal(&changelist.TufDelegation{
|
||||||
|
NewThreshold: notary.MinThreshold,
|
||||||
|
AddKeys: data.KeyList([]data.PublicKey{rootPubKey, key2}),
|
||||||
|
AddPaths: []string{"abc", "123", "xyz"},
|
||||||
|
})
|
||||||
|
change := newCreateDelegationChange(delegationName, tdJSON)
|
||||||
|
cl, err := changelist.NewFileChangelist(filepath.Join(repo.tufRepoPath, "changelist"))
|
||||||
|
addChange(cl, change, delegationName)
|
||||||
|
|
||||||
|
changes := getChanges(t, repo)
|
||||||
|
assert.Len(t, changes, 1)
|
||||||
|
assert.NoError(t, applyTargetsChange(repo.tufRepo, changes[0]))
|
||||||
|
|
||||||
|
delgRoles := repo.tufRepo.Targets[data.CanonicalTargetsRole].Signed.Delegations.Roles
|
||||||
|
assert.Len(t, delgRoles, 1)
|
||||||
|
assert.Len(t, delgRoles[0].Paths, 3)
|
||||||
|
assert.Len(t, delgRoles[0].KeyIDs, 2)
|
||||||
|
assert.Equal(t, delgRoles[0].Name, delegationName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFullRemoveDelegationChangefileApplicable generates a single changelist with RemoveKeys and RemovePaths set,
|
||||||
|
// (in the old style of RemoveDelegation) and tests that all of its changes are reflected on publish
|
||||||
|
func TestFullRemoveDelegationChangefileApplicable(t *testing.T) {
|
||||||
|
gun := "docker.com/notary"
|
||||||
|
ts, _, _ := simpleTestServer(t)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
repo, rootKeyID := initializeRepo(t, data.ECDSAKey, gun, ts.URL, false)
|
||||||
|
defer os.RemoveAll(repo.baseDir)
|
||||||
|
rootPubKey := repo.CryptoService.GetKey(rootKeyID)
|
||||||
|
assert.NotNil(t, rootPubKey)
|
||||||
|
|
||||||
|
key2, err := repo.CryptoService.Create("user", data.ECDSAKey)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
key2CanonicalID, err := utils.CanonicalKeyID(key2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
delegationName := "targets/a"
|
||||||
|
|
||||||
|
assert.NoError(t, repo.AddDelegation(delegationName, []data.PublicKey{rootPubKey, key2}, []string{"abc", "123"}))
|
||||||
|
changes := getChanges(t, repo)
|
||||||
|
assert.Len(t, changes, 2)
|
||||||
|
assert.NoError(t, applyTargetsChange(repo.tufRepo, changes[0]))
|
||||||
|
assert.NoError(t, applyTargetsChange(repo.tufRepo, changes[1]))
|
||||||
|
|
||||||
|
targetRole := repo.tufRepo.Targets[data.CanonicalTargetsRole]
|
||||||
|
assert.Len(t, targetRole.Signed.Delegations.Roles, 1)
|
||||||
|
assert.Len(t, targetRole.Signed.Delegations.Keys, 2)
|
||||||
|
|
||||||
|
// manually create the changelist object to load multiple keys
|
||||||
|
tdJSON, err := json.Marshal(&changelist.TufDelegation{
|
||||||
|
RemoveKeys: []string{key2CanonicalID},
|
||||||
|
RemovePaths: []string{"abc", "123"},
|
||||||
|
})
|
||||||
|
change := newUpdateDelegationChange(delegationName, tdJSON)
|
||||||
|
cl, err := changelist.NewFileChangelist(filepath.Join(repo.tufRepoPath, "changelist"))
|
||||||
|
addChange(cl, change, delegationName)
|
||||||
|
|
||||||
|
changes = getChanges(t, repo)
|
||||||
|
assert.Len(t, changes, 3)
|
||||||
|
assert.NoError(t, applyTargetsChange(repo.tufRepo, changes[2]))
|
||||||
|
|
||||||
|
delgRoles := repo.tufRepo.Targets[data.CanonicalTargetsRole].Signed.Delegations.Roles
|
||||||
|
assert.Len(t, delgRoles, 1)
|
||||||
|
assert.Len(t, delgRoles[0].Paths, 0)
|
||||||
|
assert.Len(t, delgRoles[0].KeyIDs, 1)
|
||||||
|
}
|
||||||
|
|
||||||
// TestRemoveDelegationErrorWritingChanges expects errors writing a change to
|
// TestRemoveDelegationErrorWritingChanges expects errors writing a change to
|
||||||
// file to be propagated.
|
// file to be propagated.
|
||||||
func TestRemoveDelegationErrorWritingChanges(t *testing.T) {
|
func TestRemoveDelegationErrorWritingChanges(t *testing.T) {
|
||||||
testErrorWritingChangefiles(t, func(repo *NotaryRepository) error {
|
testErrorWritingChangefiles(t, func(repo *NotaryRepository) error {
|
||||||
return repo.RemoveDelegation("targets/a", []string{""}, []string{}, false)
|
return repo.RemoveDelegationKeysAndPaths("targets/a", []string{""}, []string{})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2859,10 +2979,10 @@ func testPublishTargetsDelgationCanUseUserKeyWithArbitraryRole(t *testing.T, x50
|
||||||
|
|
||||||
// owner creates delegations, adds the delegated key to them, and publishes them
|
// owner creates delegations, adds the delegated key to them, and publishes them
|
||||||
assert.NoError(t,
|
assert.NoError(t,
|
||||||
ownerRepo.AddDelegation("targets/a", 1, []data.PublicKey{aKey}, []string{""}),
|
ownerRepo.AddDelegation("targets/a", []data.PublicKey{aKey}, []string{""}),
|
||||||
"error creating delegation")
|
"error creating delegation")
|
||||||
assert.NoError(t,
|
assert.NoError(t,
|
||||||
ownerRepo.AddDelegation("targets/a/b", 1, []data.PublicKey{bKey}, []string{""}),
|
ownerRepo.AddDelegation("targets/a/b", []data.PublicKey{bKey}, []string{""}),
|
||||||
"error creating delegation")
|
"error creating delegation")
|
||||||
|
|
||||||
assert.NoError(t, ownerRepo.Publish())
|
assert.NoError(t, ownerRepo.Publish())
|
||||||
|
@ -3016,7 +3136,7 @@ func TestListRoles(t *testing.T) {
|
||||||
// Create a delegation on the top level
|
// Create a delegation on the top level
|
||||||
aKey := createKey(t, repo, "user", true)
|
aKey := createKey(t, repo, "user", true)
|
||||||
assert.NoError(t,
|
assert.NoError(t,
|
||||||
repo.AddDelegation("targets/a", 1, []data.PublicKey{aKey}, []string{""}),
|
repo.AddDelegation("targets/a", []data.PublicKey{aKey}, []string{""}),
|
||||||
"error creating delegation")
|
"error creating delegation")
|
||||||
|
|
||||||
assert.NoError(t, repo.Publish())
|
assert.NoError(t, repo.Publish())
|
||||||
|
@ -3053,7 +3173,7 @@ func TestListRoles(t *testing.T) {
|
||||||
// Create another delegation, one level further
|
// Create another delegation, one level further
|
||||||
bKey := createKey(t, repo, "user", true)
|
bKey := createKey(t, repo, "user", true)
|
||||||
assert.NoError(t,
|
assert.NoError(t,
|
||||||
repo.AddDelegation("targets/a/b", 1, []data.PublicKey{bKey}, []string{""}),
|
repo.AddDelegation("targets/a/b", []data.PublicKey{bKey}, []string{""}),
|
||||||
"error creating delegation")
|
"error creating delegation")
|
||||||
|
|
||||||
assert.NoError(t, repo.Publish())
|
assert.NoError(t, repo.Publish())
|
||||||
|
|
|
@ -0,0 +1,310 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/notary"
|
||||||
|
"github.com/docker/notary/client/changelist"
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/docker/notary/tuf/store"
|
||||||
|
"github.com/docker/notary/tuf/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddDelegation creates changelist entries to add provided delegation public keys and paths.
|
||||||
|
// This method composes AddDelegationRoleAndKeys and AddDelegationPaths (each creates one changelist if called).
|
||||||
|
func (r *NotaryRepository) AddDelegation(name string, delegationKeys []data.PublicKey, paths []string) error {
|
||||||
|
if len(delegationKeys) > 0 {
|
||||||
|
err := r.AddDelegationRoleAndKeys(name, delegationKeys)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(paths) > 0 {
|
||||||
|
err := r.AddDelegationPaths(name, paths)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveDelegationKeysAndPaths creates changelist entries to remove provided delegation key IDs and paths.
|
||||||
|
// This method composes RemoveDelegationPaths and RemoveDelegationKeys (each creates one changelist if called).
|
||||||
|
func (r *NotaryRepository) RemoveDelegationKeysAndPaths(name string, keyIDs, paths []string) error {
|
||||||
|
if len(paths) > 0 {
|
||||||
|
err := r.RemoveDelegationPaths(name, paths)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(keyIDs) > 0 {
|
||||||
|
err := r.RemoveDelegationKeys(name, keyIDs)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveDelegationPaths creates a changelist entry to remove provided paths from an existing delegation.
|
||||||
|
func (r *NotaryRepository) RemoveDelegationPaths(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(`Removing %s paths from delegation "%s"\n`, paths, name)
|
||||||
|
|
||||||
|
tdJSON, err := json.Marshal(&changelist.TufDelegation{
|
||||||
|
RemovePaths: paths,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
template := newUpdateDelegationChange(name, tdJSON)
|
||||||
|
return addChange(cl, template, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func (r *NotaryRepository) RemoveDelegationKeys(name string, keyIDs []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 %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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUpdateDelegationChange(name string, content []byte) *changelist.TufChange {
|
||||||
|
return changelist.NewTufChange(
|
||||||
|
changelist.ActionUpdate,
|
||||||
|
name,
|
||||||
|
changelist.TypeTargetsDelegation,
|
||||||
|
"", // no path for delegations
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCreateDelegationChange(name string, content []byte) *changelist.TufChange {
|
||||||
|
return changelist.NewTufChange(
|
||||||
|
changelist.ActionCreate,
|
||||||
|
name,
|
||||||
|
changelist.TypeTargetsDelegation,
|
||||||
|
"", // no path for delegations
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDeleteDelegationChange(name string, content []byte) *changelist.TufChange {
|
||||||
|
return changelist.NewTufChange(
|
||||||
|
changelist.ActionDelete,
|
||||||
|
name,
|
||||||
|
changelist.TypeTargetsDelegation,
|
||||||
|
"", // no path for delegations
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDelegationRoles returns the keys and roles of the repository's delegations
|
||||||
|
// Also converts key IDs to canonical key IDs to keep consistent with signing prompts
|
||||||
|
func (r *NotaryRepository) GetDelegationRoles() ([]*data.Role, error) {
|
||||||
|
// Update state of the repo to latest
|
||||||
|
if _, err := r.Update(false); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// All top level delegations (ex: targets/level1) are stored exclusively in targets.json
|
||||||
|
targets, ok := r.tufRepo.Targets[data.CanonicalTargetsRole]
|
||||||
|
if !ok {
|
||||||
|
return nil, store.ErrMetaNotFound{Resource: data.CanonicalTargetsRole}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make a copy of top-level Delegations and only show canonical key IDs
|
||||||
|
allDelegations, err := translateDelegationsToCanonicalIDs(targets.Signed.Delegations)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// make a copy for traversing nested delegations
|
||||||
|
delegationsList := make([]*data.Role, len(allDelegations))
|
||||||
|
copy(delegationsList, allDelegations)
|
||||||
|
|
||||||
|
// Now traverse to lower level delegations (ex: targets/level1/level2)
|
||||||
|
for len(delegationsList) > 0 {
|
||||||
|
// Pop off first delegation to traverse
|
||||||
|
delegation := delegationsList[0]
|
||||||
|
delegationsList = delegationsList[1:]
|
||||||
|
|
||||||
|
// Get metadata
|
||||||
|
delegationMeta, ok := r.tufRepo.Targets[delegation.Name]
|
||||||
|
// If we get an error, don't try to traverse further into this subtree because it doesn't exist or is malformed
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For the return list, update with a copy that includes canonicalKeyIDs
|
||||||
|
canonicalDelegations, err := translateDelegationsToCanonicalIDs(delegationMeta.Signed.Delegations)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
allDelegations = append(allDelegations, canonicalDelegations...)
|
||||||
|
// Add nested delegations to the exploration list
|
||||||
|
delegationsList = append(delegationsList, delegationMeta.Signed.Delegations.Roles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert all key IDs to canonical IDs:
|
||||||
|
return allDelegations, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func translateDelegationsToCanonicalIDs(delegationInfo data.Delegations) ([]*data.Role, error) {
|
||||||
|
canonicalDelegations := make([]*data.Role, len(delegationInfo.Roles))
|
||||||
|
copy(canonicalDelegations, delegationInfo.Roles)
|
||||||
|
delegationKeys := delegationInfo.Keys
|
||||||
|
for i, delegation := range canonicalDelegations {
|
||||||
|
canonicalKeyIDs := []string{}
|
||||||
|
for _, keyID := range delegation.KeyIDs {
|
||||||
|
pubKey, ok := delegationKeys[keyID]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("Could not translate canonical key IDs for %s", delegation.Name)
|
||||||
|
}
|
||||||
|
canonicalKeyID, err := utils.CanonicalKeyID(pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not translate canonical key IDs for %s: %v", delegation.Name, err)
|
||||||
|
}
|
||||||
|
canonicalKeyIDs = append(canonicalKeyIDs, canonicalKeyID)
|
||||||
|
}
|
||||||
|
canonicalDelegations[i].KeyIDs = canonicalKeyIDs
|
||||||
|
}
|
||||||
|
return canonicalDelegations, nil
|
||||||
|
}
|
|
@ -136,8 +136,14 @@ func changeTargetsDelegation(repo *tuf.Repo, c changelist.Change) error {
|
||||||
if err := r.AddPathHashPrefixes(td.AddPathHashPrefixes); err != nil {
|
if err := r.AddPathHashPrefixes(td.AddPathHashPrefixes); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear all paths if we're given the flag, else remove specified paths
|
||||||
|
if td.ClearAllPaths {
|
||||||
|
r.RemovePaths(r.Paths)
|
||||||
|
} else {
|
||||||
|
r.RemovePaths(td.RemovePaths)
|
||||||
|
}
|
||||||
r.RemoveKeys(removeTUFKeyIDs)
|
r.RemoveKeys(removeTUFKeyIDs)
|
||||||
r.RemovePaths(td.RemovePaths)
|
|
||||||
r.RemovePathHashPrefixes(td.RemovePathHashPrefixes)
|
r.RemovePathHashPrefixes(td.RemovePathHashPrefixes)
|
||||||
return repo.UpdateDelegations(r, td.AddKeys)
|
return repo.UpdateDelegations(r, td.AddKeys)
|
||||||
case changelist.ActionDelete:
|
case changelist.ActionDelete:
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/docker/notary"
|
|
||||||
notaryclient "github.com/docker/notary/client"
|
notaryclient "github.com/docker/notary/client"
|
||||||
"github.com/docker/notary/passphrase"
|
"github.com/docker/notary/passphrase"
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
|
@ -139,13 +138,19 @@ func (d *delegationCommander) delegationRemove(cmd *cobra.Command, args []string
|
||||||
} else {
|
} else {
|
||||||
cmd.Println("Confirmed `yes` from flag")
|
cmd.Println("Confirmed `yes` from flag")
|
||||||
}
|
}
|
||||||
|
// Delete the entire delegation
|
||||||
|
err = nRepo.RemoveDelegationRole(role)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to remove delegation: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Remove any keys or paths that we passed in
|
||||||
|
err = nRepo.RemoveDelegationKeysAndPaths(role, keyIDs, paths)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to remove delegation: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the delegation from the repository
|
|
||||||
err = nRepo.RemoveDelegation(role, keyIDs, paths, removeAll)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to remove delegation: %v", err)
|
|
||||||
}
|
|
||||||
cmd.Println("")
|
cmd.Println("")
|
||||||
if removeAll {
|
if removeAll {
|
||||||
cmd.Printf("Forced removal (including all keys and paths) of delegation role %s to repository \"%s\" staged for next publish.\n", role, gun)
|
cmd.Printf("Forced removal (including all keys and paths) of delegation role %s to repository \"%s\" staged for next publish.\n", role, gun)
|
||||||
|
@ -197,9 +202,7 @@ func (d *delegationCommander) delegationAdd(cmd *cobra.Command, args []string) e
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the delegation to the repository
|
// Add the delegation to the repository
|
||||||
// Sets threshold to 1 since we only added one key - thresholds are not currently fully supported, though
|
err = nRepo.AddDelegation(role, pubKeys, paths)
|
||||||
// one can use additional client-side validation to check for signatures from a quorum of varying delegation roles
|
|
||||||
err = nRepo.AddDelegation(role, notary.MinThreshold, pubKeys, paths)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create delegation: %v", err)
|
return fmt.Errorf("failed to create delegation: %v", err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue