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  }