Skip to content

Commit

Permalink
command to purge all keys from all delegations below a starting point
Browse files Browse the repository at this point in the history
Signed-off-by: David Lawrence <david.lawrence@docker.com> (github: endophage)
  • Loading branch information
David Lawrence committed Jul 20, 2016
1 parent 0abcc1b commit fca6cf4
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 6 deletions.
2 changes: 1 addition & 1 deletion client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ func addChange(cl *changelist.FileChangelist, c changelist.Change, roles ...stri
for _, role := range roles {
// Ensure we can only add targets to the CanonicalTargetsRole,
// or a Delegation role (which is <CanonicalTargetsRole>/something else)
if role != data.CanonicalTargetsRole && !data.IsDelegation(role) {
if role != data.CanonicalTargetsRole && !data.IsDelegation(role) && !data.IsWildDelegation(role) {
return data.ErrInvalidRole{
Role: role,
Reason: "cannot add targets to this role",
Expand Down
4 changes: 3 additions & 1 deletion client/delegations.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,11 @@ func (r *NotaryRepository) RemoveDelegationPaths(name string, paths []string) er
// 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.
// It can also delete a key from all delegations under a parent using a name
// with a wildcard at the end.
func (r *NotaryRepository) RemoveDelegationKeys(name string, keyIDs []string) error {

if !data.IsDelegation(name) {
if !data.IsDelegation(name) && !data.IsWildDelegation(name) {
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
}

Expand Down
4 changes: 4 additions & 0 deletions client/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ func changeTargetsDelegation(repo *tuf.Repo, c changelist.Change) error {
if err != nil {
return err
}
if data.IsWildDelegation(c.Scope()) {
return repo.PurgeDelegationKeys(c.Scope(), td.RemoveKeys)
}

delgRole, err := repo.GetDelegationRole(c.Scope())
if err != nil {
return err
Expand Down
58 changes: 58 additions & 0 deletions cmd/notary/delegations.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ var cmdDelegationRemoveTemplate = usageTemplate{
Long: "Remove KeyID(s) from the specified Role delegation in a specific Global Unique Name.",
}

var cmdDelegationRemoveKeysTemplate = usageTemplate{
Use: "purge [ GUN ]",
Short: "Remove KeyID(s) from all delegations it is found in.",
Long: "Remove KeyID(s) from all delegations it is found in, for which the signing keys are available. Warnings will be printed for delegations that cannot be updated.",
}

var cmdDelegationAddTemplate = usageTemplate{
Use: "add [ GUN ] [ Role ] <X509 file path 1> ...",
Short: "Add a keys to delegation using the provided public key X509 certificates.",
Expand All @@ -45,12 +51,17 @@ type delegationCommander struct {

paths []string
allPaths, removeAll, forceYes bool
keyIDs []string
}

func (d *delegationCommander) GetCommand() *cobra.Command {
cmd := cmdDelegationTemplate.ToCommand(nil)
cmd.AddCommand(cmdDelegationListTemplate.ToCommand(d.delegationsList))

cmdRemDelgKeys := cmdDelegationRemoveKeysTemplate.ToCommand(d.delegationRemoveKeys)
cmdRemDelgKeys.Flags().StringSliceVar(&d.keyIDs, "key", nil, "Delegation keys to be removed from the GUN")
cmd.AddCommand(cmdRemDelgKeys)

cmdRemDelg := cmdDelegationRemoveTemplate.ToCommand(d.delegationRemove)
cmdRemDelg.Flags().StringSliceVar(&d.paths, "paths", nil, "List of paths to remove")
cmdRemDelg.Flags().BoolVarP(&d.forceYes, "yes", "y", false, "Answer yes to the removal question (no confirmation)")
Expand All @@ -64,6 +75,53 @@ func (d *delegationCommander) GetCommand() *cobra.Command {
return cmd
}

func (d *delegationCommander) delegationRemoveKeys(cmd *cobra.Command, args []string) error {
if len(args) > 1 {
cmd.Usage()
return fmt.Errorf("Please provide a Global Unique Name as an argument to remove")
}

if len(d.keyIDs) == 0 {
cmd.Usage()
return fmt.Errorf("Please provide at least one key ID to be removed using the --key flag")
}

gun := args[0]

config, err := d.configGetter()
if err != nil {
return err
}

trustPin, err := getTrustPinning(config)
if err != nil {
return err
}

nRepo, err := notaryclient.NewNotaryRepository(
config.GetString("trust_dir"),
gun,
getRemoteTrustServer(config),
nil,
d.retriever,
trustPin,
)
if err != nil {
return err
}

err = nRepo.RemoveDelegationKeys("targets/*", d.keyIDs)
if err != nil {
return fmt.Errorf("failed to remove delegation: %v", err)
}
fmt.Printf(
"Removal of the following keys from all delegations in %s staged for next publish:\n\t- %s\n",
gun,
strings.Join(d.keyIDs, "\n\t- "),
)
return nil
}

// delegationsList lists all the delegations for a particular GUN
func (d *delegationCommander) delegationsList(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
Expand Down
11 changes: 11 additions & 0 deletions tuf/data/roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"

"github.com/Sirupsen/logrus"
"path/filepath"
)

// Canonical base role names
Expand Down Expand Up @@ -96,6 +97,16 @@ func IsBaseRole(role string) bool {
return false
}

// IsWildDelegation determines if a role represents a valid wildcard delegation
// path, i.e. targets/*, targets/foo/*.
// The wildcard may only appear as the final part of the delegation and must
// be a whole segment, i.e. targets/foo* is not a valid wildcard delegation.
func IsWildDelegation(role string) bool {
base := filepath.Dir(role)
splat := filepath.Base(role)
return splat == "*" && (IsDelegation(base) || base == CanonicalTargetsRole)
}

// BaseRole is an internal representation of a root/targets/snapshot/timestamp role, with its public keys included
type BaseRole struct {
Keys map[string]PublicKey
Expand Down
54 changes: 50 additions & 4 deletions tuf/tuf.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,11 +392,57 @@ func (tr *Repo) UpdateDelegationKeys(roleName string, addKeys data.KeyList, remo
// Walk to the parent of this delegation, since that is where its role metadata exists
// We do not have to verify that the walker reached its desired role in this scenario
// since we've already done another walk to the parent role in VerifyCanSign, and potentially made a targets file
err := tr.WalkTargets("", parent, delegationUpdateVisitor(roleName, addKeys, removeKeys, []string{}, []string{}, false, newThreshold))
if err != nil {
return err
return tr.WalkTargets("", parent, delegationUpdateVisitor(roleName, addKeys, removeKeys, []string{}, []string{}, false, newThreshold))
}

// PurgeDelegationKeys removes the provided canonical key IDs from all delegations
// present in the subtree rooted at role. The role argument must be provided in a wildcard
// format, i.e. targets/* would remove the key from all delegations in the repo
func (tr *Repo) PurgeDelegationKeys(role string, removeKeys []string) error {
if !data.IsWildDelegation(role) {
return data.ErrInvalidRole{
Role: role,
Reason: "only wildcard roles can be used in a purge",
}
}
return nil

removeIDs := make(map[string]struct{})
for _, id := range removeKeys {
removeIDs[id] = struct{}{}
}

start := path.Dir(role)
tufIDToCanon := make(map[string]string)

purgeKeys := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
var (
deleteCandidates []string
err error
)
for id, key := range tgt.Signed.Delegations.Keys {
var (
canonID string
ok bool
)
if canonID, ok = tufIDToCanon[id]; !ok {
canonID, err = utils.CanonicalKeyID(key)
if err != nil {
// TODO: should we return here or just log?
return err
}
tufIDToCanon[id] = canonID
}
if _, ok := removeIDs[canonID]; ok {
delete(tgt.Signed.Delegations.Keys, id)
deleteCandidates = append(deleteCandidates, id)
}
}
for _, role := range tgt.Signed.Delegations.Roles {
role.RemoveKeys(deleteCandidates)
}
return nil
}
return tr.WalkTargets("", start, purgeKeys)
}

// UpdateDelegationPaths updates the appropriate delegation's paths.
Expand Down

0 comments on commit fca6cf4

Please sign in to comment.