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