// adds a TUF Change template to the given roles func addChange(cl *changelist.FileChangelist, c changelist.Change, roles ...string) error { if len(roles) == 0 { roles = []string{data.CanonicalTargetsRole} } var changes []changelist.Change 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) && !data.IsWildDelegation(role) { return data.ErrInvalidRole{ Role: role, Reason: "cannot add targets to this role", } } changes = append(changes, changelist.NewTUFChange( c.Action(), role, c.Type(), c.Path(), c.Content(), )) } for _, c := range changes { if err := cl.Add(c); err != nil { return err } } return nil }
func applyChangelist(repo *tuf.Repo, invalid *tuf.Repo, cl changelist.Changelist) error { it, err := cl.NewIterator() if err != nil { return err } index := 0 for it.HasNext() { c, err := it.Next() if err != nil { return err } isDel := data.IsDelegation(c.Scope()) || data.IsWildDelegation(c.Scope()) switch { case c.Scope() == changelist.ScopeTargets || isDel: err = applyTargetsChange(repo, invalid, c) case c.Scope() == changelist.ScopeRoot: err = applyRootChange(repo, c) default: return fmt.Errorf("scope not supported: %s", c.Scope()) } if err != nil { logrus.Debugf("error attempting to apply change #%d: %s, on scope: %s path: %s type: %s", index, c.Action(), c.Scope(), c.Path(), c.Type()) return err } index++ } logrus.Debugf("applied %d change(s)", index) return nil }
func changeTargetsDelegation(repo *tuf.Repo, c changelist.Change) error { switch c.Action() { case changelist.ActionCreate: td := changelist.TUFDelegation{} err := json.Unmarshal(c.Content(), &td) if err != nil { return err } // Try to create brand new role or update one // First add the keys, then the paths. We can only add keys and paths in this scenario err = repo.UpdateDelegationKeys(c.Scope(), td.AddKeys, []string{}, td.NewThreshold) if err != nil { return err } return repo.UpdateDelegationPaths(c.Scope(), td.AddPaths, []string{}, false) case changelist.ActionUpdate: td := changelist.TUFDelegation{} err := json.Unmarshal(c.Content(), &td) 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 } // We need to translate the keys from canonical ID to TUF ID for compatibility canonicalToTUFID := make(map[string]string) for tufID, pubKey := range delgRole.Keys { canonicalID, err := utils.CanonicalKeyID(pubKey) if err != nil { return err } canonicalToTUFID[canonicalID] = tufID } removeTUFKeyIDs := []string{} for _, canonID := range td.RemoveKeys { removeTUFKeyIDs = append(removeTUFKeyIDs, canonicalToTUFID[canonID]) } err = repo.UpdateDelegationKeys(c.Scope(), td.AddKeys, removeTUFKeyIDs, td.NewThreshold) if err != nil { return err } return repo.UpdateDelegationPaths(c.Scope(), td.AddPaths, td.RemovePaths, td.ClearAllPaths) case changelist.ActionDelete: return repo.DeleteDelegation(c.Scope()) default: return fmt.Errorf("unsupported action against delegations: %s", c.Action()) } }
// 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) && !data.IsWildDelegation(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) }
// 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", } } 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 { return err } tufIDToCanon[id] = canonID } if _, ok := removeIDs[canonID]; ok { deleteCandidates = append(deleteCandidates, id) } } if len(deleteCandidates) == 0 { // none of the interesting keys were present. We're done with this role return nil } // now we know there are changes, check if we'll be able to sign them in if err := tr.VerifyCanSign(validRole.Name); err != nil { logrus.Warnf( "role %s contains keys being purged but you do not have the necessary keys present to sign it; keys will not be purged from %s or its immediate children", validRole.Name, validRole.Name, ) return nil } // we know we can sign in the changes, delete the keys for _, id := range deleteCandidates { delete(tgt.Signed.Delegations.Keys, id) } // delete candidate keys from all roles. for _, role := range tgt.Signed.Delegations.Roles { role.RemoveKeys(deleteCandidates) if len(role.KeyIDs) < role.Threshold { logrus.Warnf("role %s has fewer keys than its threshold of %d; it will not be usable until keys are added to it", role.Name, role.Threshold) } } tgt.Dirty = true return nil } return tr.WalkTargets("", start, purgeKeys) }