github.com/thajeztah/cli@v0.0.0-20240223162942-dc6bfac81a8b/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(cmd.Context(), 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(ctx context.Context, dockerCLI command.Cli, options signerRemoveOptions) error { 43 var errRepos []string 44 for _, repo := range options.repos { 45 fmt.Fprintf(dockerCLI.Out(), "Removing signer \"%s\" from %s...\n", options.signer, repo) 46 if _, err := removeSingleSigner(ctx, dockerCLI, repo, options.signer, options.forceYes); err != nil { 47 fmt.Fprintln(dockerCLI.Err(), err.Error()+"\n") 48 errRepos = append(errRepos, repo) 49 } 50 } 51 if len(errRepos) > 0 { 52 return errors.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, errors.New("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 func removeSingleSigner(ctx context.Context, dockerCLI command.Cli, repoName, signerName string, forceYes bool) (bool, error) { 82 imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, image.AuthResolver(dockerCLI), repoName) 83 if err != nil { 84 return false, err 85 } 86 87 signerDelegation := data.RoleName("targets/" + signerName) 88 if signerDelegation == releasesRoleTUFName { 89 return false, errors.Errorf("releases is a reserved keyword and cannot be removed") 90 } 91 notaryRepo, err := dockerCLI.NotaryClient(imgRefAndAuth, trust.ActionsPushAndPull) 92 if err != nil { 93 return false, trust.NotaryError(imgRefAndAuth.Reference().Name(), err) 94 } 95 delegationRoles, err := notaryRepo.GetDelegationRoles() 96 if err != nil { 97 return false, errors.Wrapf(err, "error retrieving signers for %s", repoName) 98 } 99 var role data.Role 100 for _, delRole := range delegationRoles { 101 if delRole.Name == signerDelegation { 102 role = delRole 103 break 104 } 105 } 106 if role.Name == "" { 107 return false, errors.Errorf("no signer %s for repository %s", signerName, repoName) 108 } 109 allRoles, err := notaryRepo.ListRoles() 110 if err != nil { 111 return false, err 112 } 113 if ok, err := isLastSignerForReleases(role, allRoles); ok && !forceYes { 114 removeSigner := command.PromptForConfirmation(os.Stdin, dockerCLI.Out(), fmt.Sprintf("The signer \"%s\" signed the last released version of %s. "+ 115 "Removing this signer will make %s unpullable. "+ 116 "Are you sure you want to continue?", 117 signerName, repoName, repoName, 118 )) 119 120 if !removeSigner { 121 fmt.Fprintf(dockerCLI.Out(), "\nAborting action.\n") 122 return false, nil 123 } 124 } else if err != nil { 125 return false, err 126 } 127 if err = notaryRepo.RemoveDelegationKeys(releasesRoleTUFName, role.KeyIDs); err != nil { 128 return false, err 129 } 130 if err = notaryRepo.RemoveDelegationRole(signerDelegation); err != nil { 131 return false, err 132 } 133 134 if err = notaryRepo.Publish(); err != nil { 135 return false, err 136 } 137 138 fmt.Fprintf(dockerCLI.Out(), "Successfully removed %s from %s\n\n", signerName, repoName) 139 140 return true, nil 141 }