blob: ca26c74e6375e6e189d94e216c1ae9dc59860022 [file] [log] [blame]
Jiri Simsa5293dcb2014-05-10 09:56:38 -07001package main
2
3import (
4 "bytes"
Jiri Simsa5293dcb2014-05-10 09:56:38 -07005 "fmt"
6 "io"
7 "os"
Ankurde0edf72014-05-30 15:42:10 -07008 "os/user"
Jiri Simsa5293dcb2014-05-10 09:56:38 -07009 "time"
10
Jiri Simsa519c5072014-09-17 21:37:57 -070011 "veyron.io/veyron/veyron/lib/cmdline"
12 "veyron.io/veyron/veyron/services/identity"
13 "veyron.io/veyron/veyron/services/identity/util"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070014
Jiri Simsa519c5072014-09-17 21:37:57 -070015 "veyron.io/veyron/veyron2"
16 "veyron.io/veyron/veyron2/rt"
17 "veyron.io/veyron/veyron2/security"
18 "veyron.io/veyron/veyron2/vdl/vdlutil"
19 "veyron.io/veyron/veyron2/vlog"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070020)
21
22var (
Asim Shankar61071792014-07-22 13:03:18 -070023 // Flags for the "bless" command
24 flagBlessWith string
25 flagBlessFor time.Duration
26
27 // Flags for the "seekblessing" command
28 flagSeekBlessingFor string
29 flagSeekBlessingOAuthClientID string
30 flagSeekBlessingFrom string
31
32 cmdPrint = &cmdline.Command{
33 Name: "print",
34 Short: "Print out information about the provided identity",
35 Long: `
36Print dumps out information about the identity encoded in the provided file,
37or if no filename is provided, then the identity that would be used by binaries
38started in the same environment.
39`,
40 ArgsName: "[<file>]",
41 ArgsLong: `
42<file> is the path to a file containing a base64-encoded, VOM encoded identity,
43typically obtained from this tool. - is used for STDIN and an empty string
44implies the identity encoded in the environment.
45`,
46 Run: func(cmd *cmdline.Command, args []string) error {
47 if len(args) > 1 {
48 return fmt.Errorf("require at most one argument, <file>, provided %d", len(args))
49 }
50 id := rt.R().Identity()
51 if len(args) == 1 {
52 if err := decode(args[0], &id); err != nil {
53 return err
54 }
55 }
56 fmt.Println("Name : ", id.PublicID())
57 fmt.Printf("Go Type : %T\n", id)
Asim Shankar1c5b94a2014-09-05 16:36:12 -070058 fmt.Printf("PublicKey: %v\n", id.PublicID().PublicKey())
Asim Shankar61071792014-07-22 13:03:18 -070059 fmt.Println("Any caveats in the identity are not printed")
60 return nil
61 },
62 }
63
64 cmdGenerate = &cmdline.Command{
65 Name: "generate",
66 Short: "Generate an identity with a newly minted private key",
67 Long: `
68Generate a new private key and create an identity that binds <name> to
69this key.
70
71Since the generated identity has a newly minted key, it will be typically
72unusable at other veyron services as those services have placed no trust
73in this key. In such cases, you likely want to seek a blessing for this
74generated identity using the 'bless' command.
75`,
76 ArgsName: "[<name>]",
77 ArgsLong: `
78<name> is the name to bind the newly minted private key to. If not specified,
79a name will be generated based on the hostname of the machine and the name of
80the user running this command.
81`,
82 Run: func(cmd *cmdline.Command, args []string) error {
83 r := rt.R()
84 var name string
85 switch len(args) {
86 case 0:
87 name = defaultIdentityName()
88 case 1:
89 name = args[0]
90 default:
91 return fmt.Errorf("require at most one argument, provided %d", len(args))
92 }
93 id, err := r.NewIdentity(name)
94 if err != nil {
Jiri Simsa519c5072014-09-17 21:37:57 -070095 return fmt.Errorf("NewIdentity(%q) failed: %v", name, err)
Asim Shankar61071792014-07-22 13:03:18 -070096 }
97 output, err := util.Base64VomEncode(id)
98 if err != nil {
99 return fmt.Errorf("failed to encode identity: %v", err)
100 }
101 fmt.Println(output)
102 return nil
103 },
104 }
105
106 cmdBless = &cmdline.Command{
107 Name: "bless",
108 Short: "Bless another identity with your own",
109 Long: `
110Bless uses the identity of the tool (either from an environment variable or
111explicitly specified using --with) to bless another identity encoded in a
112file (or STDIN). No caveats are applied to this blessing other than expiration,
113which is specified with --for.
114
115The output consists of a base64-vom encoded security.PrivateID or security.PublicID,
116depending on what was provided as input.
117
118For example, if the tool has an identity veyron/user/device, then
119bless /tmp/blessee batman
120will generate a blessing with the name veyron/user/device/batman
121
122The identity of the tool can be specified with the --with flag:
123bless --with /tmp/id /tmp/blessee batman
124`,
125 ArgsName: "<file> <name> [--with=<file>]",
126 ArgsLong: `
127<file> is the name of the file containing a base64-vom encoded security.PublicID
128or security.PrivateID
129
130<name> is the name to use for the blessing.
131`,
132 Run: func(cmd *cmdline.Command, args []string) error {
133 if len(args) != 2 {
134 return fmt.Errorf("expected exactly two arguments (<file> and <name>), got %d", len(args))
135 }
136 blesser := rt.R().Identity()
137 if len(flagBlessWith) > 0 {
138 if err := decode(flagBlessWith, &blesser); err != nil {
139 return err
140 }
141 }
142 name := args[1]
143 var blessee security.PublicID
144 var private security.PrivateID
Asim Shankar76f431a2014-07-23 10:03:20 -0700145 encoded, err := read(args[0])
146 if err != nil {
147 return err
148 }
149 if util.Base64VomDecode(encoded, &blessee); err != nil || blessee == nil {
150 if err := util.Base64VomDecode(encoded, &private); err != nil || private == nil {
151 return fmt.Errorf("failed to extract security.PublicID or security.PrivateID: (%v, %v)", private, err)
Asim Shankar61071792014-07-22 13:03:18 -0700152 }
153 blessee = private.PublicID()
154 }
155 blessed, err := blesser.Bless(blessee, name, flagBlessFor, nil)
156 if err != nil {
157 return err
158 }
159 var object interface{} = blessed
160 if private != nil {
161 object, err = private.Derive(blessed)
162 if err != nil {
163 return err
164 }
165 }
166 output, err := util.Base64VomEncode(object)
167 if err != nil {
168 return err
169 }
170 fmt.Println(output)
171 return nil
172 },
173 }
174
175 cmdSeekBlessing = &cmdline.Command{
176 Name: "seekblessing",
177 Short: "Seek a blessing from the default veyron identity provider",
178 Long: `
179Seeks a blessing from a default, hardcoded Veyron identity provider which
180requires the caller to first authenticate with Google using OAuth. Simply
181run the command to see what happens.
182
183The blessing is sought for the identity that this tool is using. An alternative
184can be provided with the --for flag.
185`,
186 Run: func(cmd *cmdline.Command, args []string) error {
187 r := rt.R()
188 id := r.Identity()
189
190 if len(flagSeekBlessingFor) > 0 {
191 if err := decode(flagSeekBlessingFor, &id); err != nil {
192 return err
193 }
194 var err error
195 if r, err = rt.New(veyron2.RuntimeID(id)); err != nil {
196 return err
197 }
198 }
199
200 blessedChan := make(chan string)
201 defer close(blessedChan)
202 authcodeChan, err := getOAuthAuthorizationCodeFromGoogle(flagSeekBlessingOAuthClientID, blessedChan)
203 if err != nil {
204 return fmt.Errorf("failed to get authorization code from Google: %v", err)
205 }
206 redirectURL := <-authcodeChan
207 authcode := <-authcodeChan
208
Matt Rosencrantz137b8d22014-08-18 09:56:15 -0700209 ctx, cancel := r.NewContext().WithTimeout(time.Minute)
210 defer cancel()
211
Asim Shankar61071792014-07-22 13:03:18 -0700212 wait := time.Second
213 const maxWait = 20 * time.Second
214 var reply vdlutil.Any
215 for {
216 blesser, err := identity.BindOAuthBlesser(flagSeekBlessingFrom, r.Client())
217 if err == nil {
Matt Rosencrantz137b8d22014-08-18 09:56:15 -0700218
219 reply, err = blesser.BlessUsingAuthorizationCode(ctx, authcode, redirectURL)
Asim Shankar61071792014-07-22 13:03:18 -0700220 }
221 if err != nil {
222 vlog.Infof("Failed to get blessing from %q: %v, will try again in %v", flagSeekBlessingFrom, err, wait)
223 time.Sleep(wait)
Asim Shankar1c3b1812014-07-31 18:54:51 -0700224 if wait = wait + 2*time.Second; wait > maxWait {
Asim Shankar61071792014-07-22 13:03:18 -0700225 wait = maxWait
226 }
227 continue
228 }
229 blessed, ok := reply.(security.PublicID)
230 if !ok {
231 return fmt.Errorf("received %T, want security.PublicID", reply)
232 }
233 if id, err = id.Derive(blessed); err != nil {
234 return fmt.Errorf("received incompatible blessing from %q: %v", flagSeekBlessingFrom, err)
235 }
236 output, err := util.Base64VomEncode(id)
237 if err != nil {
238 return fmt.Errorf("failed to encode blessing: %v", err)
239 }
240 fmt.Println(output)
241 blessedChan <- fmt.Sprint(blessed)
242 // Wait for getOAuthAuthenticationCodeFromGoogle to clean up:
243 <-authcodeChan
244 return nil
245 }
246 },
247 }
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700248)
249
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700250func main() {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700251 rt.Init()
Asim Shankar61071792014-07-22 13:03:18 -0700252 cmdBless.Flags.StringVar(&flagBlessWith, "with", "", "Path to file containing identity to bless with (or - for STDIN)")
253 cmdBless.Flags.DurationVar(&flagBlessFor, "for", 365*24*time.Hour, "Expiry time of blessing (defaults to 1 year)")
254 cmdSeekBlessing.Flags.StringVar(&flagSeekBlessingFor, "for", "", "Path to file containing identity to bless (or - for STDIN)")
255 cmdSeekBlessing.Flags.StringVar(&flagSeekBlessingOAuthClientID, "clientid", "761523829214-4ms7bae18ef47j6590u9ncs19ffuo7b3.apps.googleusercontent.com", "OAuth client ID used to make OAuth request for an authorization code")
256 cmdSeekBlessing.Flags.StringVar(&flagSeekBlessingFrom, "from", "/proxy.envyor.com:8101/identity/veyron-test/google", "Object name of Veyron service running the identity.OAuthBlesser service to seek blessings from")
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700257
Asim Shankar61071792014-07-22 13:03:18 -0700258 (&cmdline.Command{
259 Name: "identity",
260 Short: "Create an manage veyron identities",
261 Long: `
262The identity tool helps create and manage keys and blessings that are used for
263identification in veyron.
264`,
265 Children: []*cmdline.Command{cmdPrint, cmdGenerate, cmdBless, cmdSeekBlessing},
266 }).Main()
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700267}
268
Asim Shankar76f431a2014-07-23 10:03:20 -0700269func read(fname string) (string, error) {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700270 if len(fname) == 0 {
Asim Shankar76f431a2014-07-23 10:03:20 -0700271 return "", nil
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700272 }
Asim Shankar76f431a2014-07-23 10:03:20 -0700273 f := os.Stdin
274 if fname != "-" {
275 var err error
276 if f, err = os.Open(fname); err != nil {
277 return "", fmt.Errorf("failed to open %q: %v", fname, err)
278 }
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700279 }
280 defer f.Close()
281 var buf bytes.Buffer
282 if _, err := io.Copy(&buf, f); err != nil {
Asim Shankar76f431a2014-07-23 10:03:20 -0700283 return "", fmt.Errorf("failed to read %q: %v", fname, err)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700284 }
Asim Shankar76f431a2014-07-23 10:03:20 -0700285 return buf.String(), nil
286}
287
288func decode(fname string, val interface{}) error {
289 str, err := read(fname)
290 if err != nil {
291 return err
292 }
293 if err := util.Base64VomDecode(str, val); err != nil || val == nil {
Asim Shankar61071792014-07-22 13:03:18 -0700294 return fmt.Errorf("failed to decode %q: %v", fname, err)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700295 }
Asim Shankar61071792014-07-22 13:03:18 -0700296 return nil
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700297}
Ankurde0edf72014-05-30 15:42:10 -0700298
299func defaultIdentityName() string {
300 var name string
301 if user, _ := user.Current(); user != nil && len(user.Username) > 0 {
302 name = user.Username
303 } else {
304 name = "anonymous"
305 }
306 if host, _ := os.Hostname(); len(host) > 0 {
307 name = name + "@" + host
308 }
309 return name
310}