github.com/pachyderm/pachyderm@v1.13.4/src/server/auth/cmds/cmds.go (about)

     1  package cmds
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"os"
     7  	"strings"
     8  	"text/template"
     9  	"time"
    10  
    11  	"github.com/pachyderm/pachyderm/src/client"
    12  	"github.com/pachyderm/pachyderm/src/client/auth"
    13  	"github.com/pachyderm/pachyderm/src/client/pkg/config"
    14  	"github.com/pachyderm/pachyderm/src/client/pkg/errors"
    15  	"github.com/pachyderm/pachyderm/src/client/pkg/grpcutil"
    16  	"github.com/pachyderm/pachyderm/src/server/pkg/cmdutil"
    17  	"github.com/pkg/browser"
    18  
    19  	"github.com/spf13/cobra"
    20  )
    21  
    22  var githubAuthLink = `https://github.com/login/oauth/authorize?client_id=d3481e92b4f09ea74ff8&redirect_uri=https%3A%2F%2Fpachyderm.io%2Flogin-hook%2Fdisplay-token.html`
    23  
    24  func githubLogin() (string, error) {
    25  	fmt.Println("(1) Please paste this link into a browser:\n\n" +
    26  		githubAuthLink + "\n\n" +
    27  		"(You will be directed to GitHub and asked to authorize Pachyderm's " +
    28  		"login app on GitHub. If you accept, you will be given a token to " +
    29  		"paste here, which will give you an externally verified account in " +
    30  		"this Pachyderm cluster)\n\n(2) Please paste the token you receive " +
    31  		"from GitHub here:")
    32  	token, err := cmdutil.ReadPassword("")
    33  	if err != nil {
    34  		return "", errors.Wrapf(err, "error reading token")
    35  	}
    36  	return strings.TrimSpace(token), nil // drop trailing newline
    37  }
    38  
    39  func requestOIDCLogin(c *client.APIClient) (string, error) {
    40  	var authURL string
    41  	loginInfo, err := c.GetOIDCLogin(c.Ctx(), &auth.GetOIDCLoginRequest{})
    42  	if err != nil {
    43  		return "", err
    44  	}
    45  	authURL = loginInfo.LoginURL
    46  	state := loginInfo.State
    47  
    48  	// print the prepared URL and promp the user to click on it
    49  	fmt.Println("You will momentarily be directed to your IdP and asked to authorize Pachyderm's " +
    50  		"login app on your IdP.\n\nPaste the following URL into a browser if not automatically redirected:\n\n" +
    51  		authURL + "\n\n" +
    52  		"")
    53  
    54  	err = browser.OpenURL(authURL)
    55  	if err != nil {
    56  		return "", err
    57  	}
    58  
    59  	return state, nil
    60  }
    61  
    62  // ActivateCmd returns a cobra.Command to activate Pachyderm's auth system
    63  func ActivateCmd() *cobra.Command {
    64  	var initialAdmin string
    65  	activate := &cobra.Command{
    66  		Short: "Activate Pachyderm's auth system",
    67  		Long: `
    68  Activate Pachyderm's auth system, and restrict access to existing data to the
    69  user running the command (or the argument to --initial-admin), who will be the
    70  first cluster admin`[1:],
    71  		Run: cmdutil.Run(func(args []string) error {
    72  			var token string
    73  			var err error
    74  
    75  			if !strings.HasPrefix(initialAdmin, auth.RobotPrefix) {
    76  				token, err = githubLogin()
    77  				if err != nil {
    78  					return err
    79  				}
    80  			}
    81  			// Exchange GitHub token for Pachyderm token
    82  			c, err := client.NewOnUserMachine("user")
    83  			if err != nil {
    84  				return errors.Wrapf(err, "could not connect")
    85  			}
    86  			defer c.Close()
    87  
    88  			fmt.Println("Retrieving Pachyderm token...")
    89  
    90  			resp, err := c.Activate(c.Ctx(),
    91  				&auth.ActivateRequest{
    92  					GitHubToken: token,
    93  					Subject:     initialAdmin,
    94  				})
    95  			if err != nil {
    96  				return errors.Wrapf(grpcutil.ScrubGRPC(err), "error activating Pachyderm auth")
    97  			}
    98  
    99  			if err := config.WritePachTokenToConfig(resp.PachToken); err != nil {
   100  				return err
   101  			}
   102  			if strings.HasPrefix(initialAdmin, auth.RobotPrefix) {
   103  				fmt.Println("WARNING: DO NOT LOSE THE ROBOT TOKEN BELOW WITHOUT " +
   104  					"ADDING OTHER ADMINS.\nIF YOU DO, YOU WILL BE PERMANENTLY LOCKED OUT " +
   105  					"OF YOUR CLUSTER!")
   106  				fmt.Printf("Pachyderm token for \"%s\":\n%s\n", initialAdmin, resp.PachToken)
   107  			}
   108  			return nil
   109  		}),
   110  	}
   111  	activate.PersistentFlags().StringVar(&initialAdmin, "initial-admin", "", `
   112  The subject (robot user or github user) who
   113  will be the first cluster admin; the user running 'activate' will identify as
   114  this user once auth is active.  If you set 'initial-admin' to a robot
   115  user, pachctl will print that robot user's Pachyderm token; this token is
   116  effectively a root token, and if it's lost you will be locked out of your
   117  cluster`[1:])
   118  	return cmdutil.CreateAlias(activate, "auth activate")
   119  }
   120  
   121  // DeactivateCmd returns a cobra.Command to delete all ACLs, tokens, and admins,
   122  // deactivating Pachyderm's auth system
   123  func DeactivateCmd() *cobra.Command {
   124  	deactivate := &cobra.Command{
   125  		Short: "Delete all ACLs, tokens, and admins, and deactivate Pachyderm auth",
   126  		Long: "Deactivate Pachyderm's auth system, which will delete ALL auth " +
   127  			"tokens, ACLs and admins, and expose all data in the cluster to any " +
   128  			"user with cluster access. Use with caution.",
   129  		Run: cmdutil.Run(func(args []string) error {
   130  			fmt.Println("Are you sure you want to delete ALL auth information " +
   131  				"(ACLs, tokens, and admins) in this cluster, and expose ALL data? yN")
   132  			confirm, err := bufio.NewReader(os.Stdin).ReadString('\n')
   133  			if err != nil {
   134  				return err
   135  			}
   136  			if !strings.Contains("yY", confirm[:1]) {
   137  				return errors.Errorf("operation aborted")
   138  			}
   139  			c, err := client.NewOnUserMachine("user")
   140  			if err != nil {
   141  				return errors.Wrapf(err, "could not connect")
   142  			}
   143  			defer c.Close()
   144  			_, err = c.Deactivate(c.Ctx(), &auth.DeactivateRequest{})
   145  			return grpcutil.ScrubGRPC(err)
   146  		}),
   147  	}
   148  	return cmdutil.CreateAlias(deactivate, "auth deactivate")
   149  }
   150  
   151  // LoginCmd returns a cobra.Command to login to a Pachyderm cluster with your
   152  // GitHub account. Any resources that have been restricted to the email address
   153  // registered with your GitHub account will subsequently be accessible.
   154  func LoginCmd() *cobra.Command {
   155  	var useOTP bool
   156  	login := &cobra.Command{
   157  		Short: "Log in to Pachyderm",
   158  		Long: "Login to Pachyderm. Any resources that have been restricted to " +
   159  			"the account you have with your ID provider (e.g. GitHub, Okta) " +
   160  			"account will subsequently be accessible.",
   161  		Run: cmdutil.Run(func([]string) error {
   162  			c, err := client.NewOnUserMachine("user")
   163  			if err != nil {
   164  				return errors.Wrapf(err, "could not connect")
   165  			}
   166  			defer c.Close()
   167  
   168  			// Issue authentication request to Pachyderm and get response
   169  			var resp *auth.AuthenticateResponse
   170  			var authErr error
   171  			if useOTP {
   172  				// Exhange short-lived Pachyderm auth code for long-lived Pachyderm token
   173  				code, err := cmdutil.ReadPassword("One-Time Password:")
   174  				if err != nil {
   175  					return errors.Wrapf(err, "error reading One-Time Password")
   176  				}
   177  				code = strings.TrimSpace(code) // drop trailing newline
   178  				resp, authErr = c.Authenticate(
   179  					c.Ctx(),
   180  					&auth.AuthenticateRequest{OneTimePassword: code})
   181  			} else if state, err := requestOIDCLogin(c); err == nil {
   182  				// Exchange OIDC token for Pachyderm token
   183  				fmt.Println("Retrieving Pachyderm token...")
   184  				resp, authErr = c.Authenticate(
   185  					c.Ctx(),
   186  					&auth.AuthenticateRequest{OIDCState: state})
   187  				if authErr != nil && !auth.IsErrPartiallyActivated(authErr) {
   188  					return errors.Wrapf(grpcutil.ScrubGRPC(authErr),
   189  						"authorization failed (OIDC state token: %q; Pachyderm logs may "+
   190  							"contain more information)",
   191  						// Print state token as it's logged, for easy searching
   192  						fmt.Sprintf("%s.../%d", state[:len(state)/2], len(state)))
   193  				}
   194  			} else {
   195  				// Exchange GitHub token for Pachyderm token
   196  				token, err := githubLogin()
   197  				if err != nil {
   198  					return err
   199  				}
   200  				fmt.Println("Retrieving Pachyderm token...")
   201  				resp, authErr = c.Authenticate(
   202  					c.Ctx(),
   203  					&auth.AuthenticateRequest{GitHubToken: token})
   204  			}
   205  
   206  			// Write new Pachyderm token to config
   207  			if auth.IsErrPartiallyActivated(authErr) {
   208  				return errors.Wrapf(authErr, "Pachyderm auth is partially activated "+
   209  					"(if pachyderm is stuck in this state, you can revert by running "+
   210  					"'pachctl auth deactivate' or retry by running 'pachctl auth "+
   211  					"activate' again)")
   212  			} else if authErr != nil {
   213  				return errors.Wrapf(grpcutil.ScrubGRPC(authErr), "error authenticating with Pachyderm cluster")
   214  			}
   215  			return config.WritePachTokenToConfig(resp.PachToken)
   216  		}),
   217  	}
   218  	login.PersistentFlags().BoolVarP(&useOTP, "one-time-password", "o", false,
   219  		"If set, authenticate with a Dash-provided One-Time Password, rather than "+
   220  			"via GitHub")
   221  	return cmdutil.CreateAlias(login, "auth login")
   222  }
   223  
   224  // LogoutCmd returns a cobra.Command that deletes your local Pachyderm
   225  // credential, logging you out of your cluster. Note that this is not necessary
   226  // to do before logging in as another user, but is useful for testing.
   227  func LogoutCmd() *cobra.Command {
   228  	logout := &cobra.Command{
   229  		Short: "Log out of Pachyderm by deleting your local credential",
   230  		Long: "Log out of Pachyderm by deleting your local credential. Note that " +
   231  			"it's not necessary to log out before logging in with another account " +
   232  			"(simply run 'pachctl auth login' twice) but 'logout' can be useful on " +
   233  			"shared workstations.",
   234  		Run: cmdutil.Run(func([]string) error {
   235  			cfg, err := config.Read(false, false)
   236  			if err != nil {
   237  				return errors.Wrapf(err, "error reading Pachyderm config (for cluster address)")
   238  			}
   239  			_, context, err := cfg.ActiveContext(true)
   240  			if err != nil {
   241  				return errors.Wrapf(err, "error getting the active context")
   242  			}
   243  			context.SessionToken = ""
   244  			return cfg.Write()
   245  		}),
   246  	}
   247  	return cmdutil.CreateAlias(logout, "auth logout")
   248  }
   249  
   250  // WhoamiCmd returns a cobra.Command that deletes your local Pachyderm
   251  // credential, logging you out of your cluster. Note that this is not necessary
   252  // to do before logging in as another user, but is useful for testing.
   253  func WhoamiCmd() *cobra.Command {
   254  	whoami := &cobra.Command{
   255  		Short: "Print your Pachyderm identity",
   256  		Long:  "Print your Pachyderm identity.",
   257  		Run: cmdutil.Run(func([]string) error {
   258  			c, err := client.NewOnUserMachine("user")
   259  			if err != nil {
   260  				return errors.Wrapf(err, "could not connect")
   261  			}
   262  			defer c.Close()
   263  			resp, err := c.WhoAmI(c.Ctx(), &auth.WhoAmIRequest{})
   264  			if err != nil {
   265  				return errors.Wrapf(grpcutil.ScrubGRPC(err), "error")
   266  			}
   267  			fmt.Printf("You are \"%s\"\n", resp.Username)
   268  			if resp.TTL > 0 {
   269  				fmt.Printf("session expires: %v\n", time.Now().Add(time.Duration(resp.TTL)*time.Second).Format(time.RFC822))
   270  			}
   271  			if resp.IsAdmin {
   272  				fmt.Println("You are an administrator of this Pachyderm cluster")
   273  			}
   274  			return nil
   275  		}),
   276  	}
   277  	return cmdutil.CreateAlias(whoami, "auth whoami")
   278  }
   279  
   280  // CheckCmd returns a cobra command that sends an "Authorize" RPC to Pachd, to
   281  // determine whether the specified user has access to the specified repo.
   282  func CheckCmd() *cobra.Command {
   283  	check := &cobra.Command{
   284  		Use:   "{{alias}} (none|reader|writer|owner) <repo>",
   285  		Short: "Check whether you have reader/writer/etc-level access to 'repo'",
   286  		Long: "Check whether you have reader/writer/etc-level access to 'repo'. " +
   287  			"For example, 'pachctl auth check reader private-data' prints \"true\" " +
   288  			"if the you have at least \"reader\" access to the repo " +
   289  			"\"private-data\" (you could be a reader, writer, or owner). Unlike " +
   290  			"`pachctl auth get`, you do not need to have access to 'repo' to " +
   291  			"discover your own access level.",
   292  		Run: cmdutil.RunFixedArgs(2, func(args []string) error {
   293  			scope, err := auth.ParseScope(args[0])
   294  			if err != nil {
   295  				return err
   296  			}
   297  			repo := args[1]
   298  			c, err := client.NewOnUserMachine("user")
   299  			if err != nil {
   300  				return errors.Wrapf(err, "could not connect")
   301  			}
   302  			defer c.Close()
   303  			resp, err := c.Authorize(c.Ctx(), &auth.AuthorizeRequest{
   304  				Repo:  repo,
   305  				Scope: scope,
   306  			})
   307  			if err != nil {
   308  				return grpcutil.ScrubGRPC(err)
   309  			}
   310  			fmt.Printf("%t\n", resp.Authorized)
   311  			return nil
   312  		}),
   313  	}
   314  	return cmdutil.CreateAlias(check, "auth check")
   315  }
   316  
   317  // GetCmd returns a cobra command that gets either the ACL for a Pachyderm
   318  // repo or another user's scope of access to that repo
   319  func GetCmd() *cobra.Command {
   320  	get := &cobra.Command{
   321  		Use:   "{{alias}} [<username>] <repo>",
   322  		Short: "Get the ACL for 'repo' or the access that 'username' has to 'repo'",
   323  		Long: "Get the ACL for 'repo' or the access that 'username' has to " +
   324  			"'repo'. For example, 'pachctl auth get github-alice private-data' " +
   325  			"prints \"reader\", \"writer\", \"owner\", or \"none\", depending on " +
   326  			"the privileges that \"github-alice\" has in \"repo\". Currently all " +
   327  			"Pachyderm authentication uses GitHub OAuth, so 'username' must be a " +
   328  			"GitHub username",
   329  		Run: cmdutil.RunBoundedArgs(1, 2, func(args []string) error {
   330  			c, err := client.NewOnUserMachine("user")
   331  			if err != nil {
   332  				return errors.Wrapf(err, "could not connect")
   333  			}
   334  			defer c.Close()
   335  			if len(args) == 1 {
   336  				// Get ACL for a repo
   337  				repo := args[0]
   338  				resp, err := c.GetACL(c.Ctx(), &auth.GetACLRequest{
   339  					Repo: repo,
   340  				})
   341  				if err != nil {
   342  					return grpcutil.ScrubGRPC(err)
   343  				}
   344  				t := template.Must(template.New("ACLEntries").Parse(
   345  					"{{range .}}{{.Username }}: {{.Scope}}\n{{end}}"))
   346  				return t.Execute(os.Stdout, resp.Entries)
   347  			}
   348  			// Get User's scope on an acl
   349  			username, repo := args[0], args[1]
   350  			resp, err := c.GetScope(c.Ctx(), &auth.GetScopeRequest{
   351  				Repos:    []string{repo},
   352  				Username: username,
   353  			})
   354  			if err != nil {
   355  				return grpcutil.ScrubGRPC(err)
   356  			}
   357  			fmt.Println(resp.Scopes[0].String())
   358  			return nil
   359  		}),
   360  	}
   361  	return cmdutil.CreateAlias(get, "auth get")
   362  }
   363  
   364  // SetScopeCmd returns a cobra command that lets a user set the level of access
   365  // that another user has to a repo
   366  func SetScopeCmd() *cobra.Command {
   367  	setScope := &cobra.Command{
   368  		Use:   "{{alias}} <username> (none|reader|writer|owner) <repo>",
   369  		Short: "Set the scope of access that 'username' has to 'repo'",
   370  		Long: "Set the scope of access that 'username' has to 'repo'. For " +
   371  			"example, 'pachctl auth set github-alice none private-data' prevents " +
   372  			"\"github-alice\" from interacting with the \"private-data\" repo in any " +
   373  			"way (the default). Similarly, 'pachctl auth set github-alice reader " +
   374  			"private-data' would let \"github-alice\" read from \"private-data\" but " +
   375  			"not create commits (writer) or modify the repo's access permissions " +
   376  			"(owner). Currently all Pachyderm authentication uses GitHub OAuth, so " +
   377  			"'username' must be a GitHub username",
   378  		Run: cmdutil.RunFixedArgs(3, func(args []string) error {
   379  			scope, err := auth.ParseScope(args[1])
   380  			if err != nil {
   381  				return err
   382  			}
   383  			username, repo := args[0], args[2]
   384  			c, err := client.NewOnUserMachine("user")
   385  			if err != nil {
   386  				return errors.Wrapf(err, "could not connect")
   387  			}
   388  			defer c.Close()
   389  			_, err = c.SetScope(c.Ctx(), &auth.SetScopeRequest{
   390  				Repo:     repo,
   391  				Scope:    scope,
   392  				Username: username,
   393  			})
   394  			return grpcutil.ScrubGRPC(err)
   395  		}),
   396  	}
   397  	return cmdutil.CreateAlias(setScope, "auth set")
   398  }
   399  
   400  // ListAdminsCmd returns a cobra command that lists the current cluster admins
   401  func ListAdminsCmd() *cobra.Command {
   402  	listAdmins := &cobra.Command{
   403  		Short: "List the current cluster admins",
   404  		Long:  "List the current cluster admins",
   405  		Run: cmdutil.Run(func([]string) error {
   406  			c, err := client.NewOnUserMachine("user")
   407  			if err != nil {
   408  				return err
   409  			}
   410  			defer c.Close()
   411  			resp, err := c.GetAdmins(c.Ctx(), &auth.GetAdminsRequest{})
   412  			if err != nil {
   413  				return grpcutil.ScrubGRPC(err)
   414  			}
   415  			for _, user := range resp.Admins {
   416  				fmt.Println(user)
   417  			}
   418  			return nil
   419  		}),
   420  	}
   421  	return cmdutil.CreateAlias(listAdmins, "auth list-admins")
   422  }
   423  
   424  // ModifyAdminsCmd returns a cobra command that modifies the set of current
   425  // cluster admins
   426  func ModifyAdminsCmd() *cobra.Command {
   427  	var add []string
   428  	var remove []string
   429  	modifyAdmins := &cobra.Command{
   430  		Short: "Modify the current cluster admins",
   431  		Long: "Modify the current cluster admins. --add accepts a comma-" +
   432  			"separated list of users to grant admin status, and --remove accepts a " +
   433  			"comma-separated list of users to revoke admin status",
   434  		Run: cmdutil.Run(func([]string) error {
   435  			c, err := client.NewOnUserMachine("user")
   436  			if err != nil {
   437  				return err
   438  			}
   439  			defer c.Close()
   440  			_, err = c.ModifyAdmins(c.Ctx(), &auth.ModifyAdminsRequest{
   441  				Add:    add,
   442  				Remove: remove,
   443  			})
   444  			if auth.IsErrPartiallyActivated(err) {
   445  				return errors.Wrapf(err, "Errored, if pachyderm is stuck in this state, you "+
   446  					"can revert by running 'pachctl auth deactivate' or retry by "+
   447  					"running 'pachctl auth activate' again")
   448  			}
   449  			return grpcutil.ScrubGRPC(err)
   450  		}),
   451  	}
   452  	modifyAdmins.PersistentFlags().StringSliceVar(&add, "add", []string{},
   453  		"Comma-separated list of users to grant admin status")
   454  	modifyAdmins.PersistentFlags().StringSliceVar(&remove, "remove", []string{},
   455  		"Comma-separated list of users revoke admin status")
   456  	return cmdutil.CreateAlias(modifyAdmins, "auth modify-admins")
   457  }
   458  
   459  // GetAuthTokenCmd returns a cobra command that lets a user get a pachyderm
   460  // token on behalf of themselves or another user
   461  func GetAuthTokenCmd() *cobra.Command {
   462  	var quiet bool
   463  	var ttl string
   464  	getAuthToken := &cobra.Command{
   465  		Use: "{{alias}} [username]",
   466  		Short: "Get an auth token that authenticates the holder as \"username\", " +
   467  			"or the currently signed-in user, if no 'username' is provided",
   468  		Long: "Get an auth token that authenticates the holder as \"username\"; " +
   469  			"or the currently signed-in user, if no 'username' is provided. Only " +
   470  			"cluster admins can obtain an auth token on behalf of another user.",
   471  		Run: cmdutil.RunBoundedArgs(0, 1, func(args []string) error {
   472  			c, err := client.NewOnUserMachine("user")
   473  			if err != nil {
   474  				return errors.Wrapf(err, "could not connect")
   475  			}
   476  			defer c.Close()
   477  
   478  			req := &auth.GetAuthTokenRequest{}
   479  			if ttl != "" {
   480  				d, err := time.ParseDuration(ttl)
   481  				if err != nil {
   482  					return errors.Wrapf(err, "could not parse duration %q", ttl)
   483  				}
   484  				req.TTL = int64(d.Seconds())
   485  			}
   486  			if len(args) == 1 {
   487  				req.Subject = args[0]
   488  			}
   489  			resp, err := c.GetAuthToken(c.Ctx(), req)
   490  			if err != nil {
   491  				return grpcutil.ScrubGRPC(err)
   492  			}
   493  			if quiet {
   494  				fmt.Println(resp.Token)
   495  			} else {
   496  				fmt.Printf("New credentials:\n  Subject: %s\n  Token: %s\n", resp.Subject, resp.Token)
   497  			}
   498  			return nil
   499  		}),
   500  	}
   501  	getAuthToken.PersistentFlags().BoolVarP(&quiet, "quiet", "q", false, "if "+
   502  		"set, only print the resulting token (if successful). This is useful for "+
   503  		"scripting, as the output can be piped to use-auth-token")
   504  	getAuthToken.PersistentFlags().StringVar(&ttl, "ttl", "", "if set, the "+
   505  		"resulting auth token will have the given lifetime (or the lifetime"+
   506  		"of the caller's current session, whichever is shorter). This flag should "+
   507  		"be a golang duration (e.g. \"30s\" or \"1h2m3s\"). If unset, tokens will "+
   508  		"have a lifetime of 30 days.")
   509  	return cmdutil.CreateAlias(getAuthToken, "auth get-auth-token")
   510  }
   511  
   512  // UseAuthTokenCmd returns a cobra command that lets a user get a pachyderm
   513  // token on behalf of themselves or another user
   514  func UseAuthTokenCmd() *cobra.Command {
   515  	useAuthToken := &cobra.Command{
   516  		Short: "Read a Pachyderm auth token from stdin, and write it to the " +
   517  			"current user's Pachyderm config file",
   518  		Long: "Read a Pachyderm auth token from stdin, and write it to the " +
   519  			"current user's Pachyderm config file",
   520  		Run: cmdutil.RunFixedArgs(0, func(args []string) error {
   521  			fmt.Println("Please paste your Pachyderm auth token:")
   522  			token, err := cmdutil.ReadPassword("")
   523  			if err != nil {
   524  				return errors.Wrapf(err, "error reading token")
   525  			}
   526  			config.WritePachTokenToConfig(strings.TrimSpace(token)) // drop trailing newline
   527  			return nil
   528  		}),
   529  	}
   530  	return cmdutil.CreateAlias(useAuthToken, "auth use-auth-token")
   531  }
   532  
   533  // GetOneTimePasswordCmd returns a cobra command that lets a user get an OTP.
   534  func GetOneTimePasswordCmd() *cobra.Command {
   535  	var ttl string
   536  	getOneTimePassword := &cobra.Command{
   537  		Use: "{{alias}} <username>",
   538  		Short: "Get a one-time password that authenticates the holder as " +
   539  			"\"username\", or the currently signed in user if no 'username' is " +
   540  			"specified",
   541  		Long: "Get a one-time password that authenticates the holder as " +
   542  			"\"username\", or the currently signed in user if no 'username' is " +
   543  			"specified. Only cluster admins may obtain a one-time password on " +
   544  			"behalf of another user.",
   545  		Run: cmdutil.RunBoundedArgs(0, 1, func(args []string) error {
   546  			c, err := client.NewOnUserMachine("user")
   547  			if err != nil {
   548  				return errors.Wrapf(err, "could not connect")
   549  			}
   550  			defer c.Close()
   551  
   552  			req := &auth.GetOneTimePasswordRequest{}
   553  			if ttl != "" {
   554  				d, err := time.ParseDuration(ttl)
   555  				if err != nil {
   556  					return errors.Wrapf(err, "could not parse duration %q", ttl)
   557  				}
   558  				req.TTL = int64(d.Seconds())
   559  			}
   560  			if len(args) == 1 {
   561  				req.Subject = args[0]
   562  			}
   563  			resp, err := c.GetOneTimePassword(c.Ctx(), req)
   564  			if err != nil {
   565  				return grpcutil.ScrubGRPC(err)
   566  			}
   567  			fmt.Println(resp.Code)
   568  			return nil
   569  		}),
   570  	}
   571  	getOneTimePassword.PersistentFlags().StringVar(&ttl, "ttl", "", "if set, "+
   572  		"the resulting one-time password will have the given lifetime (or the "+
   573  		"lifetime of the caller's current session, whichever is shorter). This "+
   574  		"flag should be a golang duration (e.g. \"30s\" or \"1h2m3s\"). If unset, "+
   575  		"one-time passwords will have a lifetime of 5 minutes")
   576  	return cmdutil.CreateAlias(getOneTimePassword, "auth get-otp")
   577  }
   578  
   579  // Cmds returns a list of cobra commands for authenticating and authorizing
   580  // users in an auth-enabled Pachyderm cluster.
   581  func Cmds() []*cobra.Command {
   582  	var commands []*cobra.Command
   583  
   584  	auth := &cobra.Command{
   585  		Short: "Auth commands manage access to data in a Pachyderm cluster",
   586  		Long:  "Auth commands manage access to data in a Pachyderm cluster",
   587  	}
   588  
   589  	commands = append(commands, cmdutil.CreateAlias(auth, "auth"))
   590  	commands = append(commands, ActivateCmd())
   591  	commands = append(commands, DeactivateCmd())
   592  	commands = append(commands, LoginCmd())
   593  	commands = append(commands, LogoutCmd())
   594  	commands = append(commands, WhoamiCmd())
   595  	commands = append(commands, CheckCmd())
   596  	commands = append(commands, SetScopeCmd())
   597  	commands = append(commands, GetCmd())
   598  	commands = append(commands, ListAdminsCmd())
   599  	commands = append(commands, ModifyAdminsCmd())
   600  	commands = append(commands, GetAuthTokenCmd())
   601  	commands = append(commands, UseAuthTokenCmd())
   602  	commands = append(commands, GetConfigCmd())
   603  	commands = append(commands, SetConfigCmd())
   604  	commands = append(commands, GetOneTimePasswordCmd())
   605  
   606  	return commands
   607  }