forked from pachyderm/pachyderm
/
cmds.go
393 lines (374 loc) · 13.4 KB
/
cmds.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
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
package cmds
import (
"bufio"
"fmt"
"os"
"strings"
"text/template"
"github.com/pachyderm/pachyderm/src/client"
"github.com/pachyderm/pachyderm/src/client/auth"
"github.com/pachyderm/pachyderm/src/client/pkg/config"
"github.com/pachyderm/pachyderm/src/client/pkg/grpcutil"
"github.com/pachyderm/pachyderm/src/server/pkg/cmdutil"
"github.com/spf13/cobra"
)
var githubAuthLink = `https://github.com/login/oauth/authorize?client_id=d3481e92b4f09ea74ff8&redirect_uri=https%3A%2F%2Fpachyderm.io%2Flogin-hook%2Fdisplay-token.html`
func githubLogin() (string, error) {
fmt.Println("(1) Please paste this link into a browser:\n\n" +
githubAuthLink + "\n\n" +
"(You will be directed to GitHub and asked to authorize Pachyderm's " +
"login app on Github. If you accept, you will be given a token to " +
"paste here, which will give you an externally verified account in " +
"this Pachyderm cluster)\n\n(2) Please paste the token you receive " +
"from GitHub here:")
token, err := bufio.NewReader(os.Stdin).ReadString('\n')
if err != nil {
return "", fmt.Errorf("error reading token: %v", err)
}
return strings.TrimSpace(token), nil // drop trailing newline
}
func writePachTokenToCfg(token string) error {
cfg, err := config.Read()
if err != nil {
return fmt.Errorf("error reading Pachyderm config (for cluster "+
"address): %v", err)
}
if cfg.V1 == nil {
cfg.V1 = &config.ConfigV1{}
}
cfg.V1.SessionToken = token
return cfg.Write()
}
// ActivateCmd returns a cobra.Command to activate Pachyderm's auth system
func ActivateCmd() *cobra.Command {
var username string
activate := &cobra.Command{
Use: "activate",
Short: "Activate Pachyderm's auth system",
Long: "Activate Pachyderm's auth system, and restrict access to existing data to cluster admins",
Run: cmdutil.Run(func(args []string) error {
token, err := githubLogin()
if err != nil {
return err
}
fmt.Println("Retrieving Pachyderm token...")
// Exchange GitHub token for Pachyderm token
c, err := client.NewOnUserMachine(true, "user")
if err != nil {
return fmt.Errorf("could not connect: %v", err)
}
resp, err := c.Activate(
c.Ctx(),
&auth.ActivateRequest{GithubUsername: username, GithubToken: token})
if err != nil {
return fmt.Errorf("error activating Pachyderm auth: %v",
grpcutil.ScrubGRPC(err))
}
return writePachTokenToCfg(resp.PachToken)
}),
}
activate.PersistentFlags().StringVarP(&username, "user", "u", "", "GitHub "+
"username of the user activating auth. If set, the GitHub authorization "+
"code will be used to verify possession of this account. If unset, the "+
"username will be inferred from the GitHub authorization code")
return activate
}
// DeactivateCmd returns a cobra.Command to delete all ACLs, tokens, and admins,
// deactivating Pachyderm's auth system
func DeactivateCmd() *cobra.Command {
deactivate := &cobra.Command{
Use: "deactivate",
Short: "Delete all ACLs, tokens, and admins, and deactivate Pachyderm auth",
Long: "Deactivate Pachyderm's auth system, which will delete ALL auth " +
"tokens, ACLs and admins, and expose all data in the cluster to any " +
"user with cluster access. Use with caution.",
Run: cmdutil.Run(func(args []string) error {
fmt.Println("Are you sure you want to delete ALL auth information " +
"(ACLs, tokens, and admins) in this cluster, and expose ALL data? yN")
confirm, err := bufio.NewReader(os.Stdin).ReadString('\n')
if !strings.Contains("yY", confirm[:1]) {
return fmt.Errorf("operation aborted")
}
c, err := client.NewOnUserMachine(true, "user")
if err != nil {
return fmt.Errorf("could not connect: %v", err)
}
_, err = c.Deactivate(c.Ctx(), &auth.DeactivateRequest{})
return grpcutil.ScrubGRPC(err)
}),
}
return deactivate
}
// LoginCmd returns a cobra.Command to login to a Pachyderm cluster with your
// GitHub account. Any resources that have been restricted to the email address
// registered with your GitHub account will subsequently be accessible.
func LoginCmd() *cobra.Command {
var username string
login := &cobra.Command{
Use: "login",
Short: "Login to Pachyderm with your GitHub account",
Long: "Login to Pachyderm with your GitHub account. Any resources that " +
"have been restricted to the email address registered with your GitHub " +
"account will subsequently be accessible.",
Run: cmdutil.Run(func([]string) error {
token, err := githubLogin()
if err != nil {
return err
}
fmt.Println("Retrieving Pachyderm token...")
// Exchange GitHub token for Pachyderm token
c, err := client.NewOnUserMachine(true, "user")
if err != nil {
return fmt.Errorf("could not connect: %v", err)
}
resp, err := c.Authenticate(
c.Ctx(),
&auth.AuthenticateRequest{GithubUsername: username, GithubToken: token})
if err != nil {
return fmt.Errorf("error authenticating with Pachyderm cluster: %v",
grpcutil.ScrubGRPC(err))
}
return writePachTokenToCfg(resp.PachToken)
}),
}
login.PersistentFlags().StringVarP(&username, "user", "u", "", "GitHub "+
"username of the user logging in. If set, the GitHub authorization code "+
"will be used to verify possession of this account.If unset, the username "+
"will be inferred from the github authorization code")
return login
}
// LogoutCmd returns a cobra.Command that deletes your local Pachyderm
// credential, logging you out of your cluster. Note that this is not necessary
// to do before logging in as another user, but is useful for testing.
func LogoutCmd() *cobra.Command {
logout := &cobra.Command{
Use: "logout",
Short: "Log out of Pachyderm by deleting your local credential",
Long: "Log out of Pachyderm by deleting your local credential. Note that " +
"it's not necessary to log out before logging in with another account " +
"(simply run 'pachctl auth login' twice) but 'logout' can be useful on " +
"shared workstations.",
Run: cmdutil.Run(func([]string) error {
cfg, err := config.Read()
if err != nil {
return fmt.Errorf("error reading Pachyderm config (for cluster "+
"address): %v", err)
}
if cfg.V1 == nil {
return nil
}
cfg.V1.SessionToken = ""
return cfg.Write()
}),
}
return logout
}
// WhoamiCmd returns a cobra.Command that deletes your local Pachyderm
// credential, logging you out of your cluster. Note that this is not necessary
// to do before logging in as another user, but is useful for testing.
func WhoamiCmd() *cobra.Command {
whoami := &cobra.Command{
Use: "whoami",
Short: "Print your Pachyderm identity",
Long: "Print your Pachyderm identity.",
Run: cmdutil.Run(func([]string) error {
c, err := client.NewOnUserMachine(true, "user")
if err != nil {
return fmt.Errorf("could not connect: %v", err)
}
resp, err := c.WhoAmI(c.Ctx(), &auth.WhoAmIRequest{})
if err != nil {
return fmt.Errorf("error: %v", grpcutil.ScrubGRPC(err))
}
fmt.Printf("You are \"%s\"\n", resp.Username)
return nil
}),
}
return whoami
}
// CheckCmd returns a cobra command that sends an "Authorize" RPC to Pachd, to
// determine whether the specified user has access to the specified repo.
func CheckCmd() *cobra.Command {
check := &cobra.Command{
Use: "check (none|reader|writer|owner) repo",
Short: "Check whether you have reader/writer/etc-level access to 'repo'",
Long: "Check whether you have reader/writer/etc-level access to 'repo'. " +
"For example, 'pachctl auth check reader private-data' prints \"true\" " +
"if the you have at least \"reader\" access to the repo " +
"\"private-data\" (you could be a reader, writer, or owner). Unlike " +
"`pachctl get-acl`, you do not need to have access to 'repo' to " +
"discover your own acess level.",
Run: cmdutil.RunFixedArgs(2, func(args []string) error {
scope, err := auth.ParseScope(args[0])
if err != nil {
return err
}
repo := args[1]
c, err := client.NewOnUserMachine(true, "user")
if err != nil {
return fmt.Errorf("could not connect: %v", err)
}
resp, err := c.Authorize(c.Ctx(), &auth.AuthorizeRequest{
Repo: repo,
Scope: scope,
})
if err != nil {
return grpcutil.ScrubGRPC(err)
}
fmt.Printf("%t\n", resp.Authorized)
return nil
}),
}
return check
}
// GetCmd returns a cobra command that gets either the ACL for a Pachyderm
// repo or another user's scope of access to that repo
func GetCmd() *cobra.Command {
setScope := &cobra.Command{
Use: "get [username] repo",
Short: "Get the ACL for 'repo' or the access that 'username' has to 'repo'",
Long: "Get the ACL for 'repo' or the access that 'username' has to " +
"'repo'. For example, 'pachctl auth get github-alice private-data' " +
"prints \"reader\", \"writer\", \"owner\", or \"none\", depending on " +
"the privileges that \"github-alice\" has in \"repo\". Currently all " +
"Pachyderm authentication uses GitHub OAuth, so 'username' must be a " +
"GitHub username",
Run: cmdutil.RunBoundedArgs(1, 2, func(args []string) error {
c, err := client.NewOnUserMachine(true, "user")
if err != nil {
return fmt.Errorf("could not connect: %v", err)
}
if len(args) == 1 {
// Get ACL for a repo
repo := args[0]
resp, err := c.GetACL(c.Ctx(), &auth.GetACLRequest{
Repo: repo,
})
if err != nil {
return grpcutil.ScrubGRPC(err)
}
t := template.Must(template.New("ACLEntries").Parse(
"{{range .}}{{.Username }}: {{.Scope}}\n{{end}}"))
return t.Execute(os.Stdout, resp.Entries)
}
// Get User's scope on an acl
username, repo := args[0], args[1]
resp, err := c.GetScope(c.Ctx(), &auth.GetScopeRequest{
Repos: []string{repo},
Username: username,
})
if err != nil {
return grpcutil.ScrubGRPC(err)
}
fmt.Println(resp.Scopes[0].String())
return nil
}),
}
return setScope
}
// SetScopeCmd returns a cobra command that lets a user set the level of access
// that another user has to a repo
func SetScopeCmd() *cobra.Command {
setScope := &cobra.Command{
Use: "set username (none|reader|writer|owner) repo",
Short: "Set the scope of access that 'username' has to 'repo'",
Long: "Set the scope of access that 'username' has to 'repo'. For " +
"example, 'pachctl auth set github-alice none private-data' prevents " +
"\"github-alice\" from interacting with the \"private-data\" repo in any " +
"way (the default). Similarly, 'pachctl auth set github-alice reader " +
"private-data' would let \"github-alice\" read from \"private-data\" but " +
"not create commits (writer) or modify the repo's access permissions " +
"(owner). Currently all Pachyderm authentication uses GitHub OAuth, so " +
"'username' must be a GitHub username",
Run: cmdutil.RunFixedArgs(3, func(args []string) error {
scope, err := auth.ParseScope(args[1])
if err != nil {
return err
}
username, repo := args[0], args[2]
c, err := client.NewOnUserMachine(true, "user")
if err != nil {
return fmt.Errorf("could not connect: %v", err)
}
_, err = c.SetScope(c.Ctx(), &auth.SetScopeRequest{
Repo: repo,
Scope: scope,
Username: username,
})
return grpcutil.ScrubGRPC(err)
}),
}
return setScope
}
// ListAdminsCmd returns a cobra command that lists the current cluster admins
func ListAdminsCmd() *cobra.Command {
listAdmins := &cobra.Command{
Use: "list-admins",
Short: "List the current cluster admins",
Long: "List the current cluster admins",
Run: cmdutil.Run(func([]string) error {
c, err := client.NewOnUserMachine(true, "user")
if err != nil {
return err
}
resp, err := c.GetAdmins(c.Ctx(), &auth.GetAdminsRequest{})
if err != nil {
return grpcutil.ScrubGRPC(err)
}
for _, user := range resp.Admins {
fmt.Println(user)
}
return nil
}),
}
return listAdmins
}
// ModifyAdminsCmd returns a cobra command that modifies the set of current
// cluster admins
func ModifyAdminsCmd() *cobra.Command {
var add []string
var remove []string
modifyAdmins := &cobra.Command{
Use: "modify-admins",
Short: "Modify the current cluster admins",
Long: "Modify the current cluster admins. --add accepts a comma-" +
"separated list of users to grant admin status, and --remove accepts a " +
"comma-separated list of users to revoke admin status",
Run: cmdutil.Run(func([]string) error {
c, err := client.NewOnUserMachine(true, "user")
if err != nil {
return err
}
_, err = c.ModifyAdmins(c.Ctx(), &auth.ModifyAdminsRequest{
Add: add,
Remove: remove,
})
return grpcutil.ScrubGRPC(err)
}),
}
modifyAdmins.PersistentFlags().StringSliceVar(&add, "add", []string{},
"Comma-separated list of users to grant admin status")
modifyAdmins.PersistentFlags().StringSliceVar(&remove, "remove", []string{},
"Comma-separated list of users revoke admin status")
return modifyAdmins
}
// Cmds returns a list of cobra commands for authenticating and authorizing
// users in an auth-enabled Pachyderm cluster.
func Cmds() []*cobra.Command {
auth := &cobra.Command{
Use: "auth",
Short: "Auth commands manage access to data in a Pachyderm cluster",
Long: "Auth commands manage access to data in a Pachyderm cluster",
}
auth.AddCommand(ActivateCmd())
auth.AddCommand(DeactivateCmd())
auth.AddCommand(LoginCmd())
auth.AddCommand(LogoutCmd())
auth.AddCommand(WhoamiCmd())
auth.AddCommand(CheckCmd())
auth.AddCommand(SetScopeCmd())
auth.AddCommand(GetCmd())
auth.AddCommand(ListAdminsCmd())
auth.AddCommand(ModifyAdminsCmd())
return []*cobra.Command{auth}
}