forked from notaryproject/notary
-
Notifications
You must be signed in to change notification settings - Fork 0
/
delegations.go
372 lines (317 loc) · 10.8 KB
/
delegations.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
package main
import (
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/docker/notary"
notaryclient "github.com/docker/notary/client"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/utils"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var cmdDelegationTemplate = usageTemplate{
Use: "delegation",
Short: "Operates on delegations.",
Long: `Operations on TUF delegations.`,
}
var cmdDelegationListTemplate = usageTemplate{
Use: "list [ GUN ]",
Short: "Lists delegations for the Global Unique Name.",
Long: "Lists all delegations known to notary for a specific Global Unique Name.",
}
var cmdDelegationRemoveTemplate = usageTemplate{
Use: "remove [ GUN ] [ Role ] <KeyID 1> ...",
Short: "Remove KeyID(s) from the specified Role delegation.",
Long: "Remove KeyID(s) from the specified Role delegation in a specific Global Unique Name.",
}
var cmdDelegationPurgeKeysTemplate = usageTemplate{
Use: "purge [ GUN ]",
Short: "Remove KeyID(s) from all delegation roles in the given GUN.",
Long: "Remove KeyID(s) from all delegation roles in the given GUN, for which the signing keys are available. Warnings will be printed for delegations that cannot be updated.",
}
var cmdDelegationAddTemplate = usageTemplate{
Use: "add [ GUN ] [ Role ] <X509 file path 1> ...",
Short: "Add a keys to delegation using the provided public key X509 certificates.",
Long: "Add a keys to delegation using the provided public key PEM encoded X509 certificates in a specific Global Unique Name.",
}
type delegationCommander struct {
// these need to be set
configGetter func() (*viper.Viper, error)
retriever notary.PassRetriever
paths []string
allPaths, removeAll, forceYes bool
keyIDs []string
autoPublish bool
}
func (d *delegationCommander) GetCommand() *cobra.Command {
cmd := cmdDelegationTemplate.ToCommand(nil)
cmd.AddCommand(cmdDelegationListTemplate.ToCommand(d.delegationsList))
cmdPurgeDelgKeys := cmdDelegationPurgeKeysTemplate.ToCommand(d.delegationPurgeKeys)
cmdPurgeDelgKeys.Flags().StringSliceVar(&d.keyIDs, "key", nil, "Delegation key IDs to be removed from the GUN")
cmdPurgeDelgKeys.Flags().BoolVarP(&d.autoPublish, "publish", "p", false, htAutoPublish)
cmd.AddCommand(cmdPurgeDelgKeys)
cmdRemDelg := cmdDelegationRemoveTemplate.ToCommand(d.delegationRemove)
cmdRemDelg.Flags().StringSliceVar(&d.paths, "paths", nil, "List of paths to remove")
cmdRemDelg.Flags().BoolVarP(&d.forceYes, "yes", "y", false, "Answer yes to the removal question (no confirmation)")
cmdRemDelg.Flags().BoolVar(&d.allPaths, "all-paths", false, "Remove all paths from this delegation")
cmdRemDelg.Flags().BoolVarP(&d.autoPublish, "publish", "p", false, htAutoPublish)
cmd.AddCommand(cmdRemDelg)
cmdAddDelg := cmdDelegationAddTemplate.ToCommand(d.delegationAdd)
cmdAddDelg.Flags().StringSliceVar(&d.paths, "paths", nil, "List of paths to add")
cmdAddDelg.Flags().BoolVar(&d.allPaths, "all-paths", false, "Add all paths to this delegation")
cmdAddDelg.Flags().BoolVarP(&d.autoPublish, "publish", "p", false, htAutoPublish)
cmd.AddCommand(cmdAddDelg)
return cmd
}
func (d *delegationCommander) delegationPurgeKeys(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
cmd.Usage()
return fmt.Errorf("Please provide a single Global Unique Name as an argument to remove")
}
if len(d.keyIDs) == 0 {
cmd.Usage()
return fmt.Errorf("Please provide at least one key ID to be removed using the --key flag")
}
gun := args[0]
config, err := d.configGetter()
if err != nil {
return err
}
trustPin, err := getTrustPinning(config)
if err != nil {
return err
}
nRepo, err := notaryclient.NewNotaryRepository(
config.GetString("trust_dir"),
gun,
getRemoteTrustServer(config),
nil,
d.retriever,
trustPin,
)
if err != nil {
return err
}
err = nRepo.RemoveDelegationKeys("targets/*", d.keyIDs)
if err != nil {
return fmt.Errorf("failed to remove keys from delegations: %v", err)
}
fmt.Printf(
"Removal of the following keys from all delegations in %s staged for next publish:\n\t- %s\n",
gun,
strings.Join(d.keyIDs, "\n\t- "),
)
return maybeAutoPublish(cmd, d.autoPublish, gun, config, d.retriever)
}
// delegationsList lists all the delegations for a particular GUN
func (d *delegationCommander) delegationsList(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
cmd.Usage()
return fmt.Errorf(
"Please provide a Global Unique Name as an argument to list")
}
config, err := d.configGetter()
if err != nil {
return err
}
gun := args[0]
rt, err := getTransport(config, gun, readOnly)
if err != nil {
return err
}
trustPin, err := getTrustPinning(config)
if err != nil {
return err
}
// initialize repo with transport to get latest state of the world before listing delegations
nRepo, err := notaryclient.NewNotaryRepository(
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, d.retriever, trustPin)
if err != nil {
return err
}
delegationRoles, err := nRepo.GetDelegationRoles()
if err != nil {
return fmt.Errorf("Error retrieving delegation roles for repository %s: %v", gun, err)
}
cmd.Println("")
prettyPrintRoles(delegationRoles, cmd.Out(), "delegations")
cmd.Println("")
return nil
}
// delegationRemove removes a public key from a specific role in a GUN
func (d *delegationCommander) delegationRemove(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
cmd.Usage()
return fmt.Errorf("must specify the Global Unique Name and the role of the delegation along with optional keyIDs and/or a list of paths to remove")
}
config, err := d.configGetter()
if err != nil {
return err
}
gun := args[0]
role := args[1]
// Check if role is valid delegation name before requiring any user input
if !data.IsDelegation(role) {
return fmt.Errorf("invalid delegation name %s", role)
}
// If we're only given the gun and the role, attempt to remove all data for this delegation
if len(args) == 2 && d.paths == nil && !d.allPaths {
d.removeAll = true
}
keyIDs := []string{}
if len(args) > 2 {
keyIDs = args[2:]
}
// If the user passes --all-paths, don't use any of the passed in --paths
if d.allPaths {
d.paths = nil
}
trustPin, err := getTrustPinning(config)
if err != nil {
return err
}
// no online operations are performed by add so the transport argument
// should be nil
nRepo, err := notaryclient.NewNotaryRepository(
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, d.retriever, trustPin)
if err != nil {
return err
}
if d.removeAll {
cmd.Println("\nAre you sure you want to remove all data for this delegation? (yes/no)")
// Ask for confirmation before force removing delegation
if !d.forceYes {
confirmed := askConfirm(os.Stdin)
if !confirmed {
fatalf("Aborting action.")
}
} else {
cmd.Println("Confirmed `yes` from flag")
}
// Delete the entire delegation
err = nRepo.RemoveDelegationRole(role)
if err != nil {
return fmt.Errorf("failed to remove delegation: %v", err)
}
} else {
if d.allPaths {
err = nRepo.ClearDelegationPaths(role)
if err != nil {
return fmt.Errorf("failed to remove delegation: %v", err)
}
}
// Remove any keys or paths that we passed in
err = nRepo.RemoveDelegationKeysAndPaths(role, keyIDs, d.paths)
if err != nil {
return fmt.Errorf("failed to remove delegation: %v", err)
}
}
cmd.Println("")
if d.removeAll {
cmd.Printf("Forced removal (including all keys and paths) of delegation role %s to repository \"%s\" staged for next publish.\n", role, gun)
} else {
removingItems := ""
if len(keyIDs) > 0 {
removingItems = removingItems + fmt.Sprintf("with keys %s, ", keyIDs)
}
if d.allPaths {
removingItems = removingItems + "with all paths, "
}
if d.paths != nil {
removingItems = removingItems + fmt.Sprintf(
"with paths [%s], ",
strings.Join(prettyPaths(d.paths), "\n"),
)
}
cmd.Printf("Removal of delegation role %s %sto repository \"%s\" staged for next publish.\n", role, removingItems, gun)
}
cmd.Println("")
return maybeAutoPublish(cmd, d.autoPublish, gun, config, d.retriever)
}
// delegationAdd creates a new delegation by adding a public key from a certificate to a specific role in a GUN
func (d *delegationCommander) delegationAdd(cmd *cobra.Command, args []string) error {
// We must have at least the gun and role name, and at least one key or path (or the --all-paths flag) to add
if len(args) < 2 || len(args) < 3 && d.paths == nil && !d.allPaths {
cmd.Usage()
return fmt.Errorf("must specify the Global Unique Name and the role of the delegation along with the public key certificate paths and/or a list of paths to add")
}
config, err := d.configGetter()
if err != nil {
return err
}
gun := args[0]
role := args[1]
pubKeys := []data.PublicKey{}
if len(args) > 2 {
pubKeyPaths := args[2:]
for _, pubKeyPath := range pubKeyPaths {
// Read public key bytes from PEM file
pubKeyBytes, err := ioutil.ReadFile(pubKeyPath)
if err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("file for public key does not exist: %s", pubKeyPath)
}
return fmt.Errorf("unable to read public key from file: %s", pubKeyPath)
}
// Parse PEM bytes into type PublicKey
pubKey, err := utils.ParsePEMPublicKey(pubKeyBytes)
if err != nil {
return fmt.Errorf("unable to parse valid public key certificate from PEM file %s: %v", pubKeyPath, err)
}
pubKeys = append(pubKeys, pubKey)
}
}
for _, path := range d.paths {
if path == "" {
d.allPaths = true
break
}
}
// If the user passes --all-paths (or gave the "" path in --paths), give the "" path
if d.allPaths {
d.paths = []string{""}
}
trustPin, err := getTrustPinning(config)
if err != nil {
return err
}
// no online operations are performed by add so the transport argument
// should be nil
nRepo, err := notaryclient.NewNotaryRepository(
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, d.retriever, trustPin)
if err != nil {
return err
}
// Add the delegation to the repository
err = nRepo.AddDelegation(role, pubKeys, d.paths)
if err != nil {
return fmt.Errorf("failed to create delegation: %v", err)
}
// Make keyID slice for better CLI print
pubKeyIDs := []string{}
for _, pubKey := range pubKeys {
pubKeyID, err := utils.CanonicalKeyID(pubKey)
if err != nil {
return err
}
pubKeyIDs = append(pubKeyIDs, pubKeyID)
}
cmd.Println("")
addingItems := ""
if len(pubKeyIDs) > 0 {
addingItems = addingItems + fmt.Sprintf("with keys %s, ", pubKeyIDs)
}
if d.paths != nil || d.allPaths {
addingItems = addingItems + fmt.Sprintf(
"with paths [%s], ",
strings.Join(prettyPaths(d.paths), "\n"),
)
}
cmd.Printf(
"Addition of delegation role %s %sto repository \"%s\" staged for next publish.\n",
role, addingItems, gun)
cmd.Println("")
return maybeAutoPublish(cmd, d.autoPublish, gun, config, d.retriever)
}