github.com/justincormack/cli@v0.0.0-20201215022714-831ebeae9675/cli/command/trust/signer_remove.go (about) 1 package trust 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "strings" 8 9 "github.com/docker/cli/cli" 10 "github.com/docker/cli/cli/command" 11 "github.com/docker/cli/cli/command/image" 12 "github.com/docker/cli/cli/trust" 13 "github.com/pkg/errors" 14 "github.com/spf13/cobra" 15 "github.com/theupdateframework/notary/client" 16 "github.com/theupdateframework/notary/tuf/data" 17 ) 18 19 type signerRemoveOptions struct { 20 signer string 21 repos []string 22 forceYes bool 23 } 24 25 func newSignerRemoveCommand(dockerCli command.Cli) *cobra.Command { 26 options := signerRemoveOptions{} 27 cmd := &cobra.Command{ 28 Use: "remove [OPTIONS] NAME REPOSITORY [REPOSITORY...]", 29 Short: "Remove a signer", 30 Args: cli.RequiresMinArgs(2), 31 RunE: func(cmd *cobra.Command, args []string) error { 32 options.signer = args[0] 33 options.repos = args[1:] 34 return removeSigner(dockerCli, options) 35 }, 36 } 37 flags := cmd.Flags() 38 flags.BoolVarP(&options.forceYes, "force", "f", false, "Do not prompt for confirmation before removing the most recent signer") 39 return cmd 40 } 41 42 func removeSigner(cli command.Cli, options signerRemoveOptions) error { 43 var errRepos []string 44 for _, repo := range options.repos { 45 fmt.Fprintf(cli.Out(), "Removing signer \"%s\" from %s...\n", options.signer, repo) 46 if _, err := removeSingleSigner(cli, repo, options.signer, options.forceYes); err != nil { 47 fmt.Fprintln(cli.Err(), err.Error()+"\n") 48 errRepos = append(errRepos, repo) 49 } 50 } 51 if len(errRepos) > 0 { 52 return fmt.Errorf("Error removing signer from: %s", strings.Join(errRepos, ", ")) 53 } 54 return nil 55 } 56 57 func isLastSignerForReleases(roleWithSig data.Role, allRoles []client.RoleWithSignatures) (bool, error) { 58 var releasesRoleWithSigs client.RoleWithSignatures 59 for _, role := range allRoles { 60 if role.Name == releasesRoleTUFName { 61 releasesRoleWithSigs = role 62 break 63 } 64 } 65 counter := len(releasesRoleWithSigs.Signatures) 66 if counter == 0 { 67 return false, fmt.Errorf("All signed tags are currently revoked, use docker trust sign to fix") 68 } 69 for _, signature := range releasesRoleWithSigs.Signatures { 70 for _, key := range roleWithSig.KeyIDs { 71 if signature.KeyID == key { 72 counter-- 73 } 74 } 75 } 76 return counter < releasesRoleWithSigs.Threshold, nil 77 } 78 79 // removeSingleSigner attempts to remove a single signer and returns whether signer removal happened. 80 // The signer not being removed doesn't necessarily raise an error e.g. user choosing "No" when prompted for confirmation. 81 // nolint: unparam 82 func removeSingleSigner(cli command.Cli, repoName, signerName string, forceYes bool) (bool, error) { 83 ctx := context.Background() 84 imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, nil, image.AuthResolver(cli), repoName) 85 if err != nil { 86 return false, err 87 } 88 89 signerDelegation := data.RoleName("targets/" + signerName) 90 if signerDelegation == releasesRoleTUFName { 91 return false, fmt.Errorf("releases is a reserved keyword and cannot be removed") 92 } 93 notaryRepo, err := cli.NotaryClient(imgRefAndAuth, trust.ActionsPushAndPull) 94 if err != nil { 95 return false, trust.NotaryError(imgRefAndAuth.Reference().Name(), err) 96 } 97 delegationRoles, err := notaryRepo.GetDelegationRoles() 98 if err != nil { 99 return false, errors.Wrapf(err, "error retrieving signers for %s", repoName) 100 } 101 var role data.Role 102 for _, delRole := range delegationRoles { 103 if delRole.Name == signerDelegation { 104 role = delRole 105 break 106 } 107 } 108 if role.Name == "" { 109 return false, fmt.Errorf("No signer %s for repository %s", signerName, repoName) 110 } 111 allRoles, err := notaryRepo.ListRoles() 112 if err != nil { 113 return false, err 114 } 115 if ok, err := isLastSignerForReleases(role, allRoles); ok && !forceYes { 116 removeSigner := command.PromptForConfirmation(os.Stdin, cli.Out(), fmt.Sprintf("The signer \"%s\" signed the last released version of %s. "+ 117 "Removing this signer will make %s unpullable. "+ 118 "Are you sure you want to continue?", 119 signerName, repoName, repoName, 120 )) 121 122 if !removeSigner { 123 fmt.Fprintf(cli.Out(), "\nAborting action.\n") 124 return false, nil 125 } 126 } else if err != nil { 127 return false, err 128 } 129 if err = notaryRepo.RemoveDelegationKeys(releasesRoleTUFName, role.KeyIDs); err != nil { 130 return false, err 131 } 132 if err = notaryRepo.RemoveDelegationRole(signerDelegation); err != nil { 133 return false, err 134 } 135 136 if err = notaryRepo.Publish(); err != nil { 137 return false, err 138 } 139 140 fmt.Fprintf(cli.Out(), "Successfully removed %s from %s\n\n", signerName, repoName) 141 142 return true, nil 143 }