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