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  }