blob: 62b4f61df86aec044a4d57c5402fc642004efe90 [file] [log] [blame]
Ankurcf6a89f2014-10-06 18:33:03 -07001package main
2
3import (
4 "bytes"
Asim Shankarf11b1bc2014-11-12 17:18:45 -08005 "crypto/rand"
6 "crypto/subtle"
Asim Shankarb3a82ba2014-10-29 11:41:27 -07007 "encoding/base64"
Ankurcf6a89f2014-10-06 18:33:03 -07008 "errors"
9 "fmt"
10 "io"
11 "os"
12 "os/user"
13 "time"
14
Jiri Simsabc26d692014-11-19 18:30:55 -080015 "veyron.io/lib/cmdline"
16 profile "veyron.io/veyron/veyron/profiles/static"
17 vsecurity "veyron.io/veyron/veyron/security"
18 "veyron.io/veyron/veyron/services/identity"
Asim Shankarf11b1bc2014-11-12 17:18:45 -080019 "veyron.io/veyron/veyron2"
20 "veyron.io/veyron/veyron2/ipc"
21 "veyron.io/veyron/veyron2/naming"
Ankurcf6a89f2014-10-06 18:33:03 -070022 "veyron.io/veyron/veyron2/rt"
23 "veyron.io/veyron/veyron2/security"
Asim Shankarb3a82ba2014-10-29 11:41:27 -070024 "veyron.io/veyron/veyron2/vom"
Ankurcf6a89f2014-10-06 18:33:03 -070025)
26
27var (
28 // Flags for the "blessself" command
Asim Shankar66c52f92014-10-15 23:39:10 -070029 flagBlessSelfFor time.Duration
Ankurcf6a89f2014-10-06 18:33:03 -070030
Asim Shankar66c52f92014-10-15 23:39:10 -070031 // Flags for the "bless" command
Asim Shankarf11b1bc2014-11-12 17:18:45 -080032 flagBlessFor time.Duration
33 flagBlessWith string
34 flagBlessRemoteKey string
35 flagBlessRemoteToken string
Asim Shankar66c52f92014-10-15 23:39:10 -070036
37 // Flags for the "seekblessings" command
38 flagSeekBlessingsFrom string
39 flagSeekBlessingsSetDefault bool
40 flagSeekBlessingsForPeer string
41
42 // Flags common to many commands
Ankurc24ff422014-12-16 17:59:26 -080043 flagAddToRoots bool
44 flagCreateOverwrite bool
Ankur1d46f552014-10-09 12:13:31 -070045
Ankure548f392014-12-08 18:42:41 -080046 // Flags for the "recvblessings" command
47 flagRecvBlessingsSetDefault bool
48 flagRecvBlessingsForPeer string
49
Ankur1615a7d2014-10-09 11:58:02 -070050 cmdDump = &cmdline.Command{
51 Name: "dump",
52 Short: "Dump out information about the principal",
53 Long: `
Asim Shankar66c52f92014-10-15 23:39:10 -070054Prints out information about the principal specified by the environment
Asim Shankar1789b8a2014-10-31 17:31:41 -070055that this tool is running in.
Ankur1615a7d2014-10-09 11:58:02 -070056`,
57 Run: func(cmd *cmdline.Command, args []string) error {
Matt Rosencrantz574d5e12014-11-26 10:01:37 -080058 runtime, err := rt.New()
59 if err != nil {
60 panic(err)
61 }
62 defer runtime.Cleanup()
63
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -080064 p := runtime.Principal()
Ankur1615a7d2014-10-09 11:58:02 -070065 fmt.Printf("Public key : %v\n", p.PublicKey())
Ankur1615a7d2014-10-09 11:58:02 -070066 fmt.Println("---------------- BlessingStore ----------------")
67 fmt.Printf("%v", p.BlessingStore().DebugString())
Ankur1615a7d2014-10-09 11:58:02 -070068 fmt.Println("---------------- BlessingRoots ----------------")
69 fmt.Printf("%v", p.Roots().DebugString())
70 return nil
71 },
72 }
73
Asim Shankar66c52f92014-10-15 23:39:10 -070074 cmdDumpBlessings = &cmdline.Command{
75 Name: "dumpblessings",
76 Short: "Dump out information about the provided blessings",
Ankurcf6a89f2014-10-06 18:33:03 -070077 Long: `
Asim Shankar66c52f92014-10-15 23:39:10 -070078Prints out information about the blessings (typically obtained from this tool)
Ankurcf6a89f2014-10-06 18:33:03 -070079encoded in the provided file.
80`,
81 ArgsName: "<file>",
82 ArgsLong: `
Asim Shankar66c52f92014-10-15 23:39:10 -070083<file> is the path to a file containing blessings typically obtained from
Ankurcf6a89f2014-10-06 18:33:03 -070084this tool. - is used for STDIN.
85`,
86 Run: func(cmd *cmdline.Command, args []string) error {
87 if len(args) != 1 {
88 return fmt.Errorf("requires exactly one argument, <file>, provided %d", len(args))
89 }
90 blessings, err := decodeBlessings(args[0])
91 if err != nil {
92 return fmt.Errorf("failed to decode provided blessings: %v", err)
93 }
Asim Shankar66c52f92014-10-15 23:39:10 -070094 wire := security.MarshalBlessings(blessings)
95 fmt.Printf("Blessings : %v\n", blessings)
96 fmt.Printf("PublicKey : %v\n", blessings.PublicKey())
Asim Shankardf88a2e2014-10-21 17:20:28 -070097 fmt.Printf("Certificate chains : %d\n", len(wire.CertificateChains))
Asim Shankar66c52f92014-10-15 23:39:10 -070098 for idx, chain := range wire.CertificateChains {
Asim Shankardf88a2e2014-10-21 17:20:28 -070099 fmt.Printf("Chain #%d (%d certificates). Root certificate public key: %v\n", idx, len(chain), rootkey(chain))
100 for certidx, cert := range chain {
101 fmt.Printf(" Certificate #%d: %v with ", certidx, cert.Extension)
102 switch n := len(cert.Caveats); n {
103 case 1:
104 fmt.Printf("1 caveat")
105 default:
106 fmt.Printf("%d caveats", n)
107 }
108 fmt.Println("")
109 for cavidx, cav := range cert.Caveats {
110 fmt.Printf(" (%d) %v\n", cavidx, &cav)
111 }
Asim Shankar66c52f92014-10-15 23:39:10 -0700112 }
Asim Shankar66c52f92014-10-15 23:39:10 -0700113 }
Ankurcf6a89f2014-10-06 18:33:03 -0700114 return nil
115 },
116 }
117
118 cmdBlessSelf = &cmdline.Command{
119 Name: "blessself",
120 Short: "Generate a self-signed blessing",
121 Long: `
Asim Shankar1789b8a2014-10-31 17:31:41 -0700122Returns a blessing with name <name> and self-signed by the principal specified
123by the environment that this tool is running in. Optionally, the blessing can
124be restricted with an expiry caveat specified using the --for flag.
Ankurcf6a89f2014-10-06 18:33:03 -0700125`,
126 ArgsName: "[<name>]",
127 ArgsLong: `
128<name> is the name used to create the self-signed blessing. If not
129specified, a name will be generated based on the hostname of the
130machine and the name of the user running this command.
131`,
132 Run: func(cmd *cmdline.Command, args []string) error {
Ankurcf6a89f2014-10-06 18:33:03 -0700133 var name string
134 switch len(args) {
135 case 0:
136 name = defaultBlessingName()
137 case 1:
138 name = args[0]
139 default:
140 return fmt.Errorf("requires at most one argument, provided %d", len(args))
141 }
142
143 var caveats []security.Caveat
Asim Shankar66c52f92014-10-15 23:39:10 -0700144 if flagBlessSelfFor != 0 {
145 cav, err := security.ExpiryCaveat(time.Now().Add(flagBlessSelfFor))
Ankurcf6a89f2014-10-06 18:33:03 -0700146 if err != nil {
Ankur8d304122014-10-07 10:43:43 -0700147 return fmt.Errorf("failed to create expiration Caveat: %v", err)
Ankurcf6a89f2014-10-06 18:33:03 -0700148 }
149 caveats = append(caveats, cav)
150 }
Matt Rosencrantz574d5e12014-11-26 10:01:37 -0800151
152 runtime, err := rt.New()
153 if err != nil {
154 panic(err)
155 }
156 defer runtime.Cleanup()
157
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -0800158 blessing, err := runtime.Principal().BlessSelf(name, caveats...)
Ankurcf6a89f2014-10-06 18:33:03 -0700159 if err != nil {
160 return fmt.Errorf("failed to create self-signed blessing for name %q: %v", name, err)
161 }
162
163 return dumpBlessings(blessing)
164 },
165 }
166
Asim Shankar66c52f92014-10-15 23:39:10 -0700167 cmdBless = &cmdline.Command{
168 Name: "bless",
169 Short: "Bless another principal",
170 Long: `
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800171Bless another principal.
Asim Shankar66c52f92014-10-15 23:39:10 -0700172
Ankurc24ff422014-12-16 17:59:26 -0800173The blesser is obtained from the runtime this tool is using. The blessing that
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800174will be extended is the default one from the blesser's store, or specified by
175the --with flag. Caveats on the blessing are controlled via the --for flag.
Asim Shankar66c52f92014-10-15 23:39:10 -0700176
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800177For example, let's say a principal "alice" wants to bless another principal "bob"
178as "alice/friend", the invocation would be:
179 VEYRON_CREDENTIALS=<path to alice> principal bless <path to bob> friend
180and this will dump the blessing to STDOUT.
181
182With the --remote_key and --remote_token flags, this command can be used to
183bless a principal on a remote machine as well. In this case, the blessing is
184not dumped to STDOUT but sent to the remote end. Use 'principal help
185recvblessings' for more details on that.
186`,
Asim Shankar66c52f92014-10-15 23:39:10 -0700187 ArgsName: "<principal to bless> <extension>",
188 ArgsLong: `
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800189<principal to bless> represents the principal to be blessed (i.e., whose public
190key will be provided with a name). This can be either:
191(a) The directory containing credentials for that principal,
192OR
193(b) The filename (- for STDIN) containing any other blessings of that
194 principal,
195OR
196(c) The object name produced by the 'recvblessings' command of this tool
197 running on behalf of another principal (if the --remote_key and
198 --remote_token flags are specified).
Asim Shankar66c52f92014-10-15 23:39:10 -0700199
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800200<extension> is the string extension that will be applied to create the
201blessing.
Asim Shankar66c52f92014-10-15 23:39:10 -0700202 `,
203 Run: func(cmd *cmdline.Command, args []string) error {
204 if len(args) != 2 {
205 return fmt.Errorf("require exactly two arguments, provided %d", len(args))
206 }
Matt Rosencrantz574d5e12014-11-26 10:01:37 -0800207
208 runtime, err := rt.New()
209 if err != nil {
210 panic(err)
211 }
212 defer runtime.Cleanup()
213
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -0800214 p := runtime.Principal()
Asim Shankar66c52f92014-10-15 23:39:10 -0700215
216 var with security.Blessings
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800217 var caveats []security.Caveat
Asim Shankar66c52f92014-10-15 23:39:10 -0700218 if len(flagBlessWith) > 0 {
219 if with, err = decodeBlessings(flagBlessWith); err != nil {
220 return fmt.Errorf("failed to read blessings from --with=%q: %v", flagBlessWith, err)
221 }
222 } else {
223 with = p.BlessingStore().Default()
224 }
225
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800226 if c, err := security.ExpiryCaveat(time.Now().Add(flagBlessFor)); err != nil {
227 return fmt.Errorf("failed to create ExpiryCaveat: %v", err)
228 } else {
229 caveats = append(caveats, c)
230 }
231 // TODO(ashankar,ataly,suharshs): Work out how to add additional caveats, like maybe
232 // revocation, method etc.
Asim Shankar66c52f92014-10-15 23:39:10 -0700233 tobless, extension := args[0], args[1]
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800234 if (len(flagBlessRemoteKey) == 0) != (len(flagBlessRemoteToken) == 0) {
235 return fmt.Errorf("either both --remote_key and --remote_token should be set, or neither should")
236 }
237 if len(flagBlessRemoteKey) > 0 {
238 // Send blessings to a "server" started by a "recvblessings" command
239 granter := &granter{p, with, extension, caveats, flagBlessRemoteKey}
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -0800240 return sendBlessings(runtime, tobless, granter, flagBlessRemoteToken)
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800241 }
242 // Blessing a principal whose key is available locally.
243 var key security.PublicKey
Asim Shankar66c52f92014-10-15 23:39:10 -0700244 if finfo, err := os.Stat(tobless); err == nil && finfo.IsDir() {
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800245 // TODO(suharshs,ashankar,ataly): How should we make an encrypted pk... or is that up to the agent?
Suharsh Sivakumaraca1c322014-10-21 11:27:32 -0700246 other, err := vsecurity.LoadPersistentPrincipal(tobless, nil)
Asim Shankar66c52f92014-10-15 23:39:10 -0700247 if err != nil {
Suharsh Sivakumaraca1c322014-10-21 11:27:32 -0700248 if other, err = vsecurity.CreatePersistentPrincipal(tobless, nil); err != nil {
249 return fmt.Errorf("failed to read principal in directory %q: %v", tobless, err)
250 }
Asim Shankar66c52f92014-10-15 23:39:10 -0700251 }
252 key = other.PublicKey()
253 } else if other, err := decodeBlessings(tobless); err != nil {
254 return fmt.Errorf("failed to decode blessings in %q: %v", tobless, err)
255 } else {
256 key = other.PublicKey()
257 }
258
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800259 blessings, err := p.Bless(key, with, extension, caveats[0], caveats[1:]...)
Asim Shankar66c52f92014-10-15 23:39:10 -0700260 if err != nil {
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800261 return fmt.Errorf("Bless(%v, %v, %q, ...) failed: %v", key, with, extension, err)
Asim Shankar66c52f92014-10-15 23:39:10 -0700262 }
263 return dumpBlessings(blessings)
264 },
265 }
266
267 cmdStoreForPeer = &cmdline.Command{
268 Name: "forpeer",
Ankurcf6a89f2014-10-06 18:33:03 -0700269 Short: "Return blessings marked for the provided peer",
270 Long: `
271Returns blessings that are marked for the provided peer in the
Asim Shankar1789b8a2014-10-31 17:31:41 -0700272BlessingStore specified by the environment that this tool is
273running in.
Ankurcf6a89f2014-10-06 18:33:03 -0700274`,
275 ArgsName: "[<peer_1> ... <peer_k>]",
276 ArgsLong: `
277<peer_1> ... <peer_k> are the (human-readable string) blessings bound
278to the peer. The returned blessings are marked with a pattern that is
279matched by at least one of these. If no arguments are specified,
280store.forpeer returns the blessings that are marked for all peers (i.e.,
281blessings set on the store with the "..." pattern).
282`,
283 Run: func(cmd *cmdline.Command, args []string) error {
Matt Rosencrantz574d5e12014-11-26 10:01:37 -0800284 runtime, err := rt.New()
285 if err != nil {
286 panic(err)
287 }
288 defer runtime.Cleanup()
289
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -0800290 return dumpBlessings(runtime.Principal().BlessingStore().ForPeer(args...))
Ankurcf6a89f2014-10-06 18:33:03 -0700291 },
292 }
293
Asim Shankar66c52f92014-10-15 23:39:10 -0700294 cmdStoreDefault = &cmdline.Command{
295 Name: "default",
Ankurcf6a89f2014-10-06 18:33:03 -0700296 Short: "Return blessings marked as default",
297 Long: `
Asim Shankar1789b8a2014-10-31 17:31:41 -0700298Returns blessings that are marked as default in the BlessingStore specified by
299the environment that this tool is running in.
Ankurcf6a89f2014-10-06 18:33:03 -0700300`,
301 Run: func(cmd *cmdline.Command, args []string) error {
Matt Rosencrantz574d5e12014-11-26 10:01:37 -0800302 runtime, err := rt.New()
303 if err != nil {
304 panic(err)
305 }
306 defer runtime.Cleanup()
307
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -0800308 return dumpBlessings(runtime.Principal().BlessingStore().Default())
Ankurcf6a89f2014-10-06 18:33:03 -0700309 },
310 }
Ankur8d304122014-10-07 10:43:43 -0700311
Asim Shankar66c52f92014-10-15 23:39:10 -0700312 cmdStoreSet = &cmdline.Command{
313 Name: "set",
Ankur8d304122014-10-07 10:43:43 -0700314 Short: "Set provided blessings for peer",
315 Long: `
Asim Shankar1789b8a2014-10-31 17:31:41 -0700316Marks the provided blessings to be shared with the provided peers on the
317BlessingStore specified by the environment that this tool is running in.
Ankur8d304122014-10-07 10:43:43 -0700318
319'set b pattern' marks the intention to reveal b to peers who
320present blessings of their own matching 'pattern'.
321
322'set nil pattern' can be used to remove the blessings previously
323associated with the pattern (by a prior 'set' command).
324
325It is an error to call 'store.set' with blessings whose public
326key does not match the public key of this principal specified
327by the environment.
328`,
329 ArgsName: "<file> <pattern>",
330 ArgsLong: `
331<file> is the path to a file containing a blessing typically obtained
332from this tool. - is used for STDIN.
333
334<pattern> is the BlessingPattern used to identify peers with whom this
335blessing can be shared with.
336`,
337 Run: func(cmd *cmdline.Command, args []string) error {
338 if len(args) != 2 {
Asim Shankar66c52f92014-10-15 23:39:10 -0700339 return fmt.Errorf("requires exactly two arguments <file>, <pattern>, provided %d", len(args))
Ankur8d304122014-10-07 10:43:43 -0700340 }
341 blessings, err := decodeBlessings(args[0])
342 if err != nil {
343 return fmt.Errorf("failed to decode provided blessings: %v", err)
344 }
345 pattern := security.BlessingPattern(args[1])
Matt Rosencrantz574d5e12014-11-26 10:01:37 -0800346
347 runtime, err := rt.New()
348 if err != nil {
349 panic(err)
350 }
351 defer runtime.Cleanup()
352
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -0800353 p := runtime.Principal()
Asim Shankar66c52f92014-10-15 23:39:10 -0700354 if _, err := p.BlessingStore().Set(blessings, pattern); err != nil {
Ankur8d304122014-10-07 10:43:43 -0700355 return fmt.Errorf("failed to set blessings %v for peers %v: %v", blessings, pattern, err)
356 }
Asim Shankar66c52f92014-10-15 23:39:10 -0700357 if flagAddToRoots {
358 if err := p.AddToRoots(blessings); err != nil {
359 return fmt.Errorf("AddToRoots failed: %v", err)
360 }
361 }
Ankur8d304122014-10-07 10:43:43 -0700362 return nil
363 },
364 }
365
Mike Burrows367515e2014-12-02 11:30:18 -0800366 cmdStoreAddToRoots = &cmdline.Command{
367 Name: "addtoroots",
368 Short: "Add provided blessings to root set",
369 Long: `
370Adds the provided blessings to the set of trusted roots for this principal.
371
372'addtoroots b' adds blessings b to the trusted root set.
373
374For example, to make the principal in credentials directory A trust the
375root of the default blessing in credentials directory B:
376 principal -veyron.credentials=B bless A some_extension |
377 principal -veyron.credentials=A store addtoroots -
378
379The extension 'some_extension' has no effect in the command above.
380`,
381 ArgsName: "<file>",
382 ArgsLong: `
383<file> is the path to a file containing a blessing typically obtained
384from this tool. - is used for STDIN.
385`,
386 Run: func(cmd *cmdline.Command, args []string) error {
387 if len(args) != 1 {
388 return fmt.Errorf("requires exactly one argument <file>, provided %d", len(args))
389 }
390 blessings, err := decodeBlessings(args[0])
391 if err != nil {
392 return fmt.Errorf("failed to decode provided blessings: %v", err)
393 }
394
395 runtime, err := rt.New()
396 if err != nil {
397 panic(err)
398 }
399 defer runtime.Cleanup()
400
401 p := runtime.Principal()
402 if err := p.AddToRoots(blessings); err != nil {
403 return fmt.Errorf("AddToRoots failed: %v", err)
404 }
405 return nil
406 },
407 }
408
Asim Shankar66c52f92014-10-15 23:39:10 -0700409 cmdStoreSetDefault = &cmdline.Command{
410 Name: "setdefault",
Ankur8d304122014-10-07 10:43:43 -0700411 Short: "Set provided blessings as default",
412 Long: `
Asim Shankar1789b8a2014-10-31 17:31:41 -0700413Sets the provided blessings as default in the BlessingStore specified by the
414environment that this tool is running in.
Ankur8d304122014-10-07 10:43:43 -0700415
Asim Shankar1789b8a2014-10-31 17:31:41 -0700416It is an error to call 'store.setdefault' with blessings whose public key does
417not match the public key of the principal specified by the environment.
Ankur8d304122014-10-07 10:43:43 -0700418`,
419 ArgsName: "<file>",
420 ArgsLong: `
421<file> is the path to a file containing a blessing typically obtained from
422this tool. - is used for STDIN.
423`,
424 Run: func(cmd *cmdline.Command, args []string) error {
425 if len(args) != 1 {
Asim Shankar66c52f92014-10-15 23:39:10 -0700426 return fmt.Errorf("requires exactly one argument, <file>, provided %d", len(args))
Ankur8d304122014-10-07 10:43:43 -0700427 }
428 blessings, err := decodeBlessings(args[0])
429 if err != nil {
430 return fmt.Errorf("failed to decode provided blessings: %v", err)
431 }
Matt Rosencrantz574d5e12014-11-26 10:01:37 -0800432
433 runtime, err := rt.New()
434 if err != nil {
435 panic(err)
436 }
437 defer runtime.Cleanup()
438
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -0800439 p := runtime.Principal()
Asim Shankar1789b8a2014-10-31 17:31:41 -0700440 if err := p.BlessingStore().SetDefault(blessings); err != nil {
Ankur8d304122014-10-07 10:43:43 -0700441 return fmt.Errorf("failed to set blessings %v as default: %v", blessings, err)
442 }
Asim Shankar66c52f92014-10-15 23:39:10 -0700443 if flagAddToRoots {
444 if err := p.AddToRoots(blessings); err != nil {
445 return fmt.Errorf("AddToRoots failed: %v", err)
446 }
447 }
448 return nil
449 },
450 }
451
452 cmdCreate = &cmdline.Command{
453 Name: "create",
454 Short: "Create a new principal and persist it into a directory",
455 Long: `
Ankur4704f5f2014-10-23 12:40:54 -0700456Creates a new principal with a single self-blessed blessing and writes it out
Ankurc24ff422014-12-16 17:59:26 -0800457to the provided directory. The same directory can then be used to set the
458VEYRON_CREDENTIALS environment variable for other veyron applications.
Ankur4704f5f2014-10-23 12:40:54 -0700459
460The operation fails if the directory already contains a principal. In this case
Ankurc24ff422014-12-16 17:59:26 -0800461the --overwrite flag can be provided to clear the directory and write out the
gauthamtb7bb39b2014-11-10 11:40:41 -0800462new principal.
Ankur4704f5f2014-10-23 12:40:54 -0700463`,
Asim Shankar66c52f92014-10-15 23:39:10 -0700464 ArgsName: "<directory> <blessing>",
465 ArgsLong: `
Ankurc24ff422014-12-16 17:59:26 -0800466 <directory> is the directory to which the new principal will be persisted.
Asim Shankar66c52f92014-10-15 23:39:10 -0700467 <blessing> is the self-blessed blessing that the principal will be setup to use by default.
468 `,
469 Run: func(cmd *cmdline.Command, args []string) error {
470 if len(args) != 2 {
471 return fmt.Errorf("requires exactly two arguments: <directory> and <blessing>, provided %d", len(args))
472 }
473 dir, name := args[0], args[1]
Suharsh Sivakumaraca1c322014-10-21 11:27:32 -0700474 // TODO(suharshs,ashankar,ataly): How should we make an ecrypted pk... or is that up to the agent?
Ankur4704f5f2014-10-23 12:40:54 -0700475 if flagCreateOverwrite {
Ankurc24ff422014-12-16 17:59:26 -0800476 if err := os.RemoveAll(dir); err != nil {
gauthamtb7bb39b2014-11-10 11:40:41 -0800477 return err
478 }
Ankur4704f5f2014-10-23 12:40:54 -0700479 }
Ankurc24ff422014-12-16 17:59:26 -0800480 p, err := vsecurity.CreatePersistentPrincipal(dir, nil)
Suharsh Sivakumaraca1c322014-10-21 11:27:32 -0700481 if err != nil {
482 return err
Asim Shankar66c52f92014-10-15 23:39:10 -0700483 }
484 blessings, err := p.BlessSelf(name)
485 if err != nil {
486 return fmt.Errorf("BlessSelf(%q) failed: %v", name, err)
487 }
Ankurc24ff422014-12-16 17:59:26 -0800488 if err := vsecurity.SetDefaultBlessings(p, blessings); err != nil {
489 return fmt.Errorf("could not set blessings %v as default: %v", blessings, err)
Asim Shankar66c52f92014-10-15 23:39:10 -0700490 }
Ankurc24ff422014-12-16 17:59:26 -0800491 return nil
492 },
493 }
494
495 cmdFork = &cmdline.Command{
496 Name: "fork",
497 Short: "Fork a new principal from the principal that this tool is running as and persist it into a directory",
498 Long: `
499Creates a new principal with a blessing from the principal specified by the
500environment that this tool is running in, and writes it out to the provided
501directory. The blessing that will be extended is the default one from the
502blesser's store, or specified by the --with flag. Caveats on the blessing
503are controlled via the --for flag. The blessing is marked as default and
504shareable with all peers on the new principal's blessing store.
505
506The operation fails if the directory already contains a principal. In this case
507the --overwrite flag can be provided to clear the directory and write out the
508forked principal.
509`,
510 ArgsName: "<directory> <extension>",
511 ArgsLong: `
512 <directory> is the directory to which the forked principal will be persisted.
513 <extension> is the extension under which the forked principal is blessed.
514 `,
515 Run: func(cmd *cmdline.Command, args []string) error {
516 if len(args) != 2 {
517 return fmt.Errorf("requires exactly two arguments: <directory> and <extension>, provided %d", len(args))
Asim Shankar66c52f92014-10-15 23:39:10 -0700518 }
Ankurc24ff422014-12-16 17:59:26 -0800519 dir, extension := args[0], args[1]
520
521 if flagCreateOverwrite {
522 if err := os.RemoveAll(dir); err != nil {
523 return err
524 }
525 }
526 p, err := vsecurity.CreatePersistentPrincipal(dir, nil)
527 if err != nil {
528 return err
529 }
530
531 runtime, err := rt.New()
532 if err != nil {
533 return err
534 }
535 defer runtime.Cleanup()
536
537 var (
538 with security.Blessings
539 caveats []security.Caveat
540 )
541 if len(flagBlessWith) > 0 {
542 if with, err = decodeBlessings(flagBlessWith); err != nil {
543 return fmt.Errorf("failed to read blessings from --with=%q: %v", flagBlessWith, err)
544 }
545 } else {
546 with = runtime.Principal().BlessingStore().Default()
547 }
548 if c, err := security.ExpiryCaveat(time.Now().Add(flagBlessFor)); err != nil {
549 return fmt.Errorf("failed to create ExpiryCaveat: %v", err)
550 } else {
551 caveats = append(caveats, c)
552 }
553 // TODO(ashankar,ataly,suharshs): Work out how to add additional caveats, like maybe
554 // revocation, method etc.
555
556 key := p.PublicKey()
557 blessings, err := runtime.Principal().Bless(key, with, extension, caveats[0], caveats[1:]...)
558 if err != nil {
559 return fmt.Errorf("Bless(%v, %v, %q, ...) failed: %v", key, with, extension, err)
560 }
561 if err := vsecurity.SetDefaultBlessings(p, blessings); err != nil {
562 return fmt.Errorf("could not set blessings %v as default: %v", blessings, err)
Asim Shankar66c52f92014-10-15 23:39:10 -0700563 }
Ankur8d304122014-10-07 10:43:43 -0700564 return nil
565 },
566 }
Ankur1d46f552014-10-09 12:13:31 -0700567
568 cmdSeekBlessings = &cmdline.Command{
569 Name: "seekblessings",
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800570 Short: "Seek blessings from a web-based Veyron blessing service",
Ankur1d46f552014-10-09 12:13:31 -0700571 Long: `
572Seeks blessings from a web-based Veyron blesser which
573requires the caller to first authenticate with Google using OAuth. Simply
574run the command to see what happens.
575
Asim Shankar1789b8a2014-10-31 17:31:41 -0700576The blessings are sought for the principal specified by the environment that
577this tool is running in.
Ankur1d46f552014-10-09 12:13:31 -0700578
Ankure548f392014-12-08 18:42:41 -0800579The blessings obtained are set as default, unless the --set_default flag is
580set to true, and are also set for sharing with all peers, unless a more
Ankur1d46f552014-10-09 12:13:31 -0700581specific peer pattern is provided using the --for_peer flag.
582`,
583 Run: func(cmd *cmdline.Command, args []string) error {
Asim Shankar08ab3892014-10-16 12:04:07 -0700584 // Initialize the runtime first so that any local errors are reported
585 // before the HTTP roundtrips for obtaining the macaroon begin.
Matt Rosencrantz574d5e12014-11-26 10:01:37 -0800586 runtime, err := rt.New()
587 if err != nil {
588 panic(err)
589 }
590 defer runtime.Cleanup()
591
Ankur1d46f552014-10-09 12:13:31 -0700592 blessedChan := make(chan string)
593 defer close(blessedChan)
Asim Shankar66c52f92014-10-15 23:39:10 -0700594 macaroonChan, err := getMacaroonForBlessRPC(flagSeekBlessingsFrom, blessedChan)
Ankur1d46f552014-10-09 12:13:31 -0700595 if err != nil {
596 return fmt.Errorf("failed to get macaroon from Veyron blesser: %v", err)
597 }
598 macaroon := <-macaroonChan
599 service := <-macaroonChan
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -0800600 ctx, cancel := runtime.NewContext().WithTimeout(time.Minute)
Ankur1d46f552014-10-09 12:13:31 -0700601 defer cancel()
602
Asim Shankarb3a82ba2014-10-29 11:41:27 -0700603 var reply security.WireBlessings
Todd Wang702385a2014-11-07 01:54:08 -0800604 reply, err = identity.MacaroonBlesserClient(service).Bless(ctx, macaroon)
Ankur1d46f552014-10-09 12:13:31 -0700605 if err != nil {
606 return fmt.Errorf("failed to get blessing from %q: %v", service, err)
607 }
Asim Shankarb3a82ba2014-10-29 11:41:27 -0700608 blessings, err := security.NewBlessings(reply)
Ankur1d46f552014-10-09 12:13:31 -0700609 if err != nil {
Asim Shankarb3a82ba2014-10-29 11:41:27 -0700610 return fmt.Errorf("failed to construct Blessings object from response: %v", err)
Ankur1d46f552014-10-09 12:13:31 -0700611 }
612 blessedChan <- fmt.Sprint(blessings)
613 // Wait for getTokenForBlessRPC to clean up:
614 <-macaroonChan
615
Asim Shankar66c52f92014-10-15 23:39:10 -0700616 if flagSeekBlessingsSetDefault {
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -0800617 if err := runtime.Principal().BlessingStore().SetDefault(blessings); err != nil {
Ankur1d46f552014-10-09 12:13:31 -0700618 return fmt.Errorf("failed to set blessings %v as default: %v", blessings, err)
619 }
620 }
Asim Shankar66c52f92014-10-15 23:39:10 -0700621 if pattern := security.BlessingPattern(flagSeekBlessingsForPeer); len(pattern) > 0 {
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -0800622 if _, err := runtime.Principal().BlessingStore().Set(blessings, pattern); err != nil {
Asim Shankar66c52f92014-10-15 23:39:10 -0700623 return fmt.Errorf("failed to set blessings %v for peers %v: %v", blessings, pattern, err)
624 }
625 }
626 if flagAddToRoots {
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -0800627 if err := runtime.Principal().AddToRoots(blessings); err != nil {
Asim Shankar66c52f92014-10-15 23:39:10 -0700628 return fmt.Errorf("AddToRoots failed: %v", err)
629 }
Ankur1d46f552014-10-09 12:13:31 -0700630 }
631 return dumpBlessings(blessings)
632 },
633 }
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800634
635 cmdRecvBlessings = &cmdline.Command{
636 Name: "recvblessings",
637 Short: "Receive blessings sent by another principal and use them as the default",
638 Long: `
639Allow another principal (likely a remote process) to bless this one.
640
641This command sets up the invoker (this process) to wait for a blessing
642from another invocation of this tool (remote process) and prints out the
643command to be run as the remote principal.
644
Ankure548f392014-12-08 18:42:41 -0800645The received blessings are set as default, unless the --set_default flag is
646set to true, and are also set for sharing with all peers, unless a more
647specific peer pattern is provided using the --for_peer flag.
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800648
649TODO(ashankar,cnicolaou): Make this next paragraph possible! Requires
650the ability to obtain the proxied endpoint.
651
652Typically, this command should require no arguments.
653However, if the sender and receiver are on different network domains, it may
654make sense to use the --veyron.proxy flag:
Robin Thellend8fea01c2014-12-11 13:48:10 -0800655 principal --veyron.proxy=proxy recvblessings
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800656
657The command to be run at the sender is of the form:
658 principal bless --remote_key=KEY --remote_token=TOKEN ADDRESS
659
660The --remote_key flag is used to by the sender to "authenticate" the receiver,
661ensuring it blesses the intended recipient and not any attacker that may have
662taken over the address.
663
664The --remote_token flag is used by the sender to authenticate itself to the
665receiver. This helps ensure that the receiver rejects blessings from senders
666who just happened to guess the network address of the 'recvblessings'
667invocation.
668`,
669 Run: func(cmd *cmdline.Command, args []string) error {
670 if len(args) != 0 {
671 return fmt.Errorf("command accepts no arguments")
672 }
Matt Rosencrantz574d5e12014-11-26 10:01:37 -0800673
674 runtime, err := rt.New()
675 if err != nil {
676 panic(err)
677 }
678 defer runtime.Cleanup()
679
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -0800680 server, err := runtime.NewServer()
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800681 if err != nil {
682 return fmt.Errorf("failed to create server to listen for blessings: %v", err)
683 }
684 defer server.Stop()
Cosmos Nicolaou28dabfc2014-12-15 22:51:07 -0800685 eps, err := server.Listen(profile.ListenSpec)
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800686 if err != nil {
687 return fmt.Errorf("failed to setup listening: %v", err)
688 }
689 var token [24]byte
690 if _, err := rand.Read(token[:]); err != nil {
691 return fmt.Errorf("unable to generate token: %v", err)
692 }
693 service := &recvBlessingsService{
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -0800694 principal: runtime.Principal(),
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800695 token: base64.URLEncoding.EncodeToString(token[:]),
696 notify: make(chan error),
697 }
698 if err := server.Serve("", service, allowAnyone{}); err != nil {
699 return fmt.Errorf("failed to setup service: %v", err)
700 }
701 // Proposed name:
702 extension := fmt.Sprintf("extension%d", int(token[0])<<16|int(token[1])<<8|int(token[2]))
703 fmt.Println("Run the following command on behalf of the principal that will send blessings:")
704 fmt.Println("You may want to adjust flags affecting the caveats on this blessing, for example using")
705 fmt.Println("the --for flag, or change the extension to something more meaningful")
706 fmt.Println()
Cosmos Nicolaou28dabfc2014-12-15 22:51:07 -0800707 fmt.Printf("principal bless --remote_key=%v --remote_token=%v %v %v\n", runtime.Principal().PublicKey(), service.token, naming.JoinAddressName(eps[0].String(), ""), extension)
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800708 fmt.Println()
709 fmt.Println("...waiting for sender..")
710 return <-service.notify
711 },
712 }
Ankurcf6a89f2014-10-06 18:33:03 -0700713)
714
715func main() {
Asim Shankar66c52f92014-10-15 23:39:10 -0700716 cmdBlessSelf.Flags.DurationVar(&flagBlessSelfFor, "for", 0, "Duration of blessing validity (zero means no that the blessing is always valid)")
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800717
Asim Shankar66c52f92014-10-15 23:39:10 -0700718 cmdBless.Flags.DurationVar(&flagBlessFor, "for", time.Minute, "Duration of blessing validity")
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800719 cmdBless.Flags.StringVar(&flagBlessWith, "with", "", "Path to file containing blessing to extend")
720 cmdBless.Flags.StringVar(&flagBlessRemoteKey, "remote_key", "", "Public key of the remote principal to bless (obtained from the 'recvblessings' command run by the remote principal")
721 cmdBless.Flags.StringVar(&flagBlessRemoteToken, "remote_token", "", "Token provided by principal running the 'recvblessings' command")
722
Robin Thellend8fea01c2014-12-11 13:48:10 -0800723 cmdSeekBlessings.Flags.StringVar(&flagSeekBlessingsFrom, "from", "https://auth.dev.v.io:8125/google", "URL to use to begin the seek blessings process")
Asim Shankar66c52f92014-10-15 23:39:10 -0700724 cmdSeekBlessings.Flags.BoolVar(&flagSeekBlessingsSetDefault, "set_default", true, "If true, the blessings obtained will be set as the default blessing in the store")
725 cmdSeekBlessings.Flags.StringVar(&flagSeekBlessingsForPeer, "for_peer", string(security.AllPrincipals), "If non-empty, the blessings obtained will be marked for peers matching this pattern in the store")
726 cmdSeekBlessings.Flags.BoolVar(&flagAddToRoots, "add_to_roots", true, "If true, the root certificate of the blessing will be added to the principal's set of recognized root certificates")
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800727
Asim Shankar66c52f92014-10-15 23:39:10 -0700728 cmdStoreSet.Flags.BoolVar(&flagAddToRoots, "add_to_roots", true, "If true, the root certificate of the blessing will be added to the principal's set of recognized root certificates")
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800729
Asim Shankar66c52f92014-10-15 23:39:10 -0700730 cmdStoreSetDefault.Flags.BoolVar(&flagAddToRoots, "add_to_roots", true, "If true, the root certificate of the blessing will be added to the principal's set of recognized root certificates")
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800731
Ankur4704f5f2014-10-23 12:40:54 -0700732 cmdCreate.Flags.BoolVar(&flagCreateOverwrite, "overwrite", false, "If true, any existing principal data in the directory will be overwritten")
Ankurc24ff422014-12-16 17:59:26 -0800733 cmdFork.Flags.BoolVar(&flagCreateOverwrite, "overwrite", false, "If true, any existing principal data in the directory will be overwritten")
734 cmdFork.Flags.DurationVar(&flagBlessFor, "for", time.Minute, "Duration of blessing validity")
735 cmdFork.Flags.StringVar(&flagBlessWith, "with", "", "Path to file containing blessing to extend")
Asim Shankar66c52f92014-10-15 23:39:10 -0700736
Ankure548f392014-12-08 18:42:41 -0800737 cmdRecvBlessings.Flags.BoolVar(&flagRecvBlessingsSetDefault, "set_default", true, "If true, the blessings received will be set as the default blessing in the store")
738 cmdRecvBlessings.Flags.StringVar(&flagRecvBlessingsForPeer, "for_peer", string(security.AllPrincipals), "If non-empty, the blessings received will be marked for peers matching this pattern in the store")
739
Asim Shankar66c52f92014-10-15 23:39:10 -0700740 cmdStore := &cmdline.Command{
741 Name: "store",
742 Short: "Manipulate and inspect the principal's blessing store",
743 Long: `
744Commands to manipulate and inspect the blessing store of the principal.
745
746All blessings are printed to stdout using base64-VOM-encoding
747`,
Mike Burrows367515e2014-12-02 11:30:18 -0800748 Children: []*cmdline.Command{cmdStoreDefault, cmdStoreSetDefault, cmdStoreForPeer, cmdStoreSet, cmdStoreAddToRoots},
Ankurcf6a89f2014-10-06 18:33:03 -0700749 }
Ankurcf6a89f2014-10-06 18:33:03 -0700750
751 (&cmdline.Command{
752 Name: "principal",
753 Short: "Create and manage veyron principals",
754 Long: `
755The principal tool helps create and manage blessings and the set of trusted
756roots bound to a principal.
757
758All objects are printed using base64-VOM-encoding.
759`,
Ankurc24ff422014-12-16 17:59:26 -0800760 Children: []*cmdline.Command{cmdCreate, cmdFork, cmdSeekBlessings, cmdRecvBlessings, cmdDump, cmdDumpBlessings, cmdBlessSelf, cmdBless, cmdStore},
Ankurcf6a89f2014-10-06 18:33:03 -0700761 }).Main()
762}
763
Ankurcf6a89f2014-10-06 18:33:03 -0700764func decodeBlessings(fname string) (security.Blessings, error) {
765 var wire security.WireBlessings
766 if err := decode(fname, &wire); err != nil {
767 return nil, err
768 }
769 return security.NewBlessings(wire)
770}
771
772func dumpBlessings(blessings security.Blessings) error {
773 if blessings == nil {
774 return errors.New("no blessings found")
775 }
Ankuree0aa812014-11-14 10:56:52 -0800776 str, err := base64VomEncode(security.MarshalBlessings(blessings))
Ankurcf6a89f2014-10-06 18:33:03 -0700777 if err != nil {
778 return fmt.Errorf("base64-VOM encoding failed: %v", err)
779 }
780 fmt.Println(str)
781 return nil
782}
783
784func read(fname string) (string, error) {
785 if len(fname) == 0 {
786 return "", nil
787 }
788 f := os.Stdin
789 if fname != "-" {
790 var err error
791 if f, err = os.Open(fname); err != nil {
792 return "", fmt.Errorf("failed to open %q: %v", fname, err)
793 }
794 }
795 defer f.Close()
796 var buf bytes.Buffer
797 if _, err := io.Copy(&buf, f); err != nil {
798 return "", fmt.Errorf("failed to read %q: %v", fname, err)
799 }
800 return buf.String(), nil
801}
802
803func decode(fname string, val interface{}) error {
804 str, err := read(fname)
805 if err != nil {
806 return err
807 }
Asim Shankarb3a82ba2014-10-29 11:41:27 -0700808 if err := base64VomDecode(str, val); err != nil || val == nil {
Ankurcf6a89f2014-10-06 18:33:03 -0700809 return fmt.Errorf("failed to decode %q: %v", fname, err)
810 }
811 return nil
812}
813
814func defaultBlessingName() string {
815 var name string
816 if user, _ := user.Current(); user != nil && len(user.Username) > 0 {
817 name = user.Username
818 } else {
819 name = "anonymous"
820 }
821 if host, _ := os.Hostname(); len(host) > 0 {
822 name = name + "@" + host
823 }
824 return name
825}
Asim Shankardf88a2e2014-10-21 17:20:28 -0700826
827func rootkey(chain []security.Certificate) string {
828 if len(chain) == 0 {
829 return "<empty certificate chain>"
830 }
831 key, err := security.UnmarshalPublicKey(chain[0].PublicKey)
832 if err != nil {
833 return fmt.Sprintf("<invalid PublicKey: %v>", err)
834 }
835 return fmt.Sprintf("%v", key)
836}
Asim Shankarb3a82ba2014-10-29 11:41:27 -0700837
838func base64VomEncode(i interface{}) (string, error) {
839 buf := &bytes.Buffer{}
840 closer := base64.NewEncoder(base64.URLEncoding, buf)
841 if err := vom.NewEncoder(closer).Encode(i); err != nil {
842 return "", err
843 }
844 // Must close the base64 encoder to flush out any partially written
845 // blocks.
846 if err := closer.Close(); err != nil {
847 return "", err
848 }
849 return buf.String(), nil
850}
851
852func base64VomDecode(s string, i interface{}) error {
853 b, err := base64.URLEncoding.DecodeString(s)
854 if err != nil {
855 return err
856 }
857 return vom.NewDecoder(bytes.NewBuffer(b)).Decode(i)
858}
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800859
860type recvBlessingsService struct {
861 principal security.Principal
862 notify chan error
863 token string
864}
865
866func (r *recvBlessingsService) Grant(call ipc.ServerCall, token string) error {
867 b := call.Blessings()
868 if b == nil {
869 return fmt.Errorf("no blessings granted by sender")
870 }
871 if len(token) != len(r.token) {
872 // A timing attack can be used to figure out the length
873 // of the token, but then again, so can looking at the
874 // source code. So, it's okay.
875 return fmt.Errorf("blessings received from unexpected sender")
876 }
877 if subtle.ConstantTimeCompare([]byte(token), []byte(r.token)) != 1 {
878 return fmt.Errorf("blessings received from unexpected sender")
879 }
Ankure548f392014-12-08 18:42:41 -0800880 if flagRecvBlessingsSetDefault {
881 if err := r.principal.BlessingStore().SetDefault(b); err != nil {
882 return fmt.Errorf("failed to set blessings %v as default: %v", b, err)
883 }
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800884 }
Ankure548f392014-12-08 18:42:41 -0800885 if pattern := security.BlessingPattern(flagRecvBlessingsForPeer); len(pattern) > 0 {
886 if _, err := r.principal.BlessingStore().Set(b, pattern); err != nil {
887 return fmt.Errorf("failed to set blessings %v for peers %v: %v", b, pattern, err)
888 }
Asim Shankarf11b1bc2014-11-12 17:18:45 -0800889 }
890 if flagAddToRoots {
891 if err := r.principal.AddToRoots(b); err != nil {
892 return fmt.Errorf("failed to add blessings to recognized roots: %v", err)
893 }
894 }
895 fmt.Println("Received blessings:", b)
896 r.notify <- nil
897 return nil
898}
899
900type allowAnyone struct{}
901
902func (allowAnyone) Authorize(security.Context) error { return nil }
903
904type granter struct {
905 p security.Principal
906 with security.Blessings
907 extension string
908 caveats []security.Caveat
909 serverKey string
910}
911
912func (g *granter) Grant(server security.Blessings) (security.Blessings, error) {
913 if got := fmt.Sprintf("%v", server.PublicKey()); got != g.serverKey {
914 // If the granter returns an error, the IPC framework should
915 // abort the RPC before sending the request to the server.
916 // Thus, there is no concern about leaking the token to an
917 // imposter server.
918 return nil, fmt.Errorf("key mismatch: Remote end has public key %v, want %v", got, g.serverKey)
919 }
920 return g.p.Bless(server.PublicKey(), g.with, g.extension, g.caveats[0], g.caveats[1:]...)
921}
922func (*granter) IPCCallOpt() {}
923
924func sendBlessings(r veyron2.Runtime, object string, granter *granter, remoteToken string) error {
925 call, err := r.Client().StartCall(r.NewContext(), object, "Grant", []interface{}{remoteToken}, granter)
926 if err != nil {
927 return fmt.Errorf("failed to start RPC to %q: %v", object, err)
928 }
929 if ierr := call.Finish(&err); ierr != nil {
930 return fmt.Errorf("failed to finish RPC to %q: %v", object, ierr)
931 }
932 return err
933}