github.com/argoproj/argo-cd/v3@v3.2.1/cmd/argocd/commands/account.go (about)

     1  package commands
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"os"
     8  	"strconv"
     9  	"strings"
    10  	"text/tabwriter"
    11  	"time"
    12  
    13  	timeutil "github.com/argoproj/pkg/v2/time"
    14  	log "github.com/sirupsen/logrus"
    15  	"github.com/spf13/cobra"
    16  	"golang.org/x/term"
    17  	"sigs.k8s.io/yaml"
    18  
    19  	"github.com/argoproj/argo-cd/v3/util/rbac"
    20  
    21  	"github.com/argoproj/argo-cd/v3/cmd/argocd/commands/headless"
    22  	"github.com/argoproj/argo-cd/v3/cmd/argocd/commands/utils"
    23  	argocdclient "github.com/argoproj/argo-cd/v3/pkg/apiclient"
    24  	accountpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/account"
    25  	"github.com/argoproj/argo-cd/v3/pkg/apiclient/session"
    26  	"github.com/argoproj/argo-cd/v3/util/cli"
    27  	"github.com/argoproj/argo-cd/v3/util/errors"
    28  	utilio "github.com/argoproj/argo-cd/v3/util/io"
    29  	"github.com/argoproj/argo-cd/v3/util/localconfig"
    30  	sessionutil "github.com/argoproj/argo-cd/v3/util/session"
    31  	"github.com/argoproj/argo-cd/v3/util/templates"
    32  )
    33  
    34  func NewAccountCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
    35  	command := &cobra.Command{
    36  		Use:   "account",
    37  		Short: "Manage account settings",
    38  		Example: templates.Examples(`
    39  			# List accounts
    40  			argocd account list
    41  
    42  			# Update the current user's password
    43  			argocd account update-password
    44  
    45  			# Can I sync any app?
    46  			argocd account can-i sync applications '*'
    47  
    48  			# Get User information
    49  			argocd account get-user-info
    50  		`),
    51  		Run: func(c *cobra.Command, args []string) {
    52  			c.HelpFunc()(c, args)
    53  			os.Exit(1)
    54  		},
    55  	}
    56  	command.AddCommand(NewAccountUpdatePasswordCommand(clientOpts))
    57  	command.AddCommand(NewAccountGetUserInfoCommand(clientOpts))
    58  	command.AddCommand(NewAccountCanICommand(clientOpts))
    59  	command.AddCommand(NewAccountListCommand(clientOpts))
    60  	command.AddCommand(NewAccountGenerateTokenCommand(clientOpts))
    61  	command.AddCommand(NewAccountGetCommand(clientOpts))
    62  	command.AddCommand(NewAccountDeleteTokenCommand(clientOpts))
    63  	command.AddCommand(NewBcryptCmd())
    64  	return command
    65  }
    66  
    67  func NewAccountUpdatePasswordCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
    68  	var (
    69  		account         string
    70  		currentPassword string
    71  		newPassword     string
    72  	)
    73  	command := &cobra.Command{
    74  		Use:   "update-password",
    75  		Short: "Update an account's password",
    76  		Long: `
    77  This command can be used to update the password of the currently logged on
    78  user, or an arbitrary local user account when the currently logged on user
    79  has appropriate RBAC permissions to change other accounts.
    80  `,
    81  		Example: `
    82  	# Update the current user's password
    83  	argocd account update-password
    84  
    85  	# Update the password for user foobar
    86  	argocd account update-password --account foobar
    87  `,
    88  		Run: func(c *cobra.Command, args []string) {
    89  			ctx := c.Context()
    90  
    91  			if len(args) != 0 {
    92  				c.HelpFunc()(c, args)
    93  				os.Exit(1)
    94  			}
    95  			acdClient := headless.NewClientOrDie(clientOpts, c)
    96  			conn, usrIf := acdClient.NewAccountClientOrDie()
    97  			defer utilio.Close(conn)
    98  
    99  			userInfo := getCurrentAccount(ctx, acdClient)
   100  
   101  			if userInfo.Iss == sessionutil.SessionManagerClaimsIssuer && currentPassword == "" {
   102  				fmt.Printf("*** Enter password of currently logged in user (%s): ", userInfo.Username)
   103  				password, err := term.ReadPassword(int(os.Stdin.Fd()))
   104  				errors.CheckError(err)
   105  				currentPassword = string(password)
   106  				fmt.Print("\n")
   107  			}
   108  
   109  			if account == "" {
   110  				account = userInfo.Username
   111  			}
   112  
   113  			if newPassword == "" {
   114  				var err error
   115  				newPassword, err = cli.ReadAndConfirmPassword(account)
   116  				errors.CheckError(err)
   117  			}
   118  
   119  			updatePasswordRequest := accountpkg.UpdatePasswordRequest{
   120  				NewPassword:     newPassword,
   121  				CurrentPassword: currentPassword,
   122  				Name:            account,
   123  			}
   124  
   125  			_, err := usrIf.UpdatePassword(ctx, &updatePasswordRequest)
   126  			errors.CheckError(err)
   127  			fmt.Printf("Password updated\n")
   128  
   129  			if account == "" || account == userInfo.Username {
   130  				// Get a new JWT token after updating the password
   131  				localCfg, err := localconfig.ReadLocalConfig(clientOpts.ConfigPath)
   132  				errors.CheckError(err)
   133  				configCtx, err := localCfg.ResolveContext(clientOpts.Context)
   134  				errors.CheckError(err)
   135  				claims, err := configCtx.User.Claims()
   136  				errors.CheckError(err)
   137  				tokenString := passwordLogin(ctx, acdClient, localconfig.GetUsername(claims.Subject), newPassword)
   138  				localCfg.UpsertUser(localconfig.User{
   139  					Name:      localCfg.CurrentContext,
   140  					AuthToken: tokenString,
   141  				})
   142  				err = localconfig.WriteLocalConfig(*localCfg, clientOpts.ConfigPath)
   143  				errors.CheckError(err)
   144  				fmt.Printf("Context '%s' updated\n", localCfg.CurrentContext)
   145  			}
   146  		},
   147  	}
   148  
   149  	command.Flags().StringVar(&currentPassword, "current-password", "", "Password of the currently logged on user")
   150  	command.Flags().StringVar(&newPassword, "new-password", "", "New password you want to update to")
   151  	command.Flags().StringVar(&account, "account", "", "An account name that should be updated. Defaults to current user account")
   152  	return command
   153  }
   154  
   155  func NewAccountGetUserInfoCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   156  	var output string
   157  	command := &cobra.Command{
   158  		Use:     "get-user-info",
   159  		Short:   "Get user info",
   160  		Aliases: []string{"whoami"},
   161  		Example: templates.Examples(`
   162  			# Get User information for the currently logged-in user (see 'argocd login')
   163  			argocd account get-user-info
   164  
   165  			# Get User information in yaml format
   166  			argocd account get-user-info -o yaml
   167  		`),
   168  		Run: func(c *cobra.Command, args []string) {
   169  			ctx := c.Context()
   170  
   171  			if len(args) != 0 {
   172  				c.HelpFunc()(c, args)
   173  				os.Exit(1)
   174  			}
   175  
   176  			conn, client := headless.NewClientOrDie(clientOpts, c).NewSessionClientOrDie()
   177  			defer utilio.Close(conn)
   178  
   179  			response, err := client.GetUserInfo(ctx, &session.GetUserInfoRequest{})
   180  			errors.CheckError(err)
   181  
   182  			switch output {
   183  			case "yaml":
   184  				yamlBytes, err := yaml.Marshal(response)
   185  				errors.CheckError(err)
   186  				fmt.Println(string(yamlBytes))
   187  			case "json":
   188  				jsonBytes, err := json.MarshalIndent(response, "", "  ")
   189  				errors.CheckError(err)
   190  				fmt.Println(string(jsonBytes))
   191  			case "":
   192  				fmt.Printf("Logged In: %v\n", response.LoggedIn)
   193  				if response.LoggedIn {
   194  					fmt.Printf("Username: %s\n", response.Username)
   195  					fmt.Printf("Issuer: %s\n", response.Iss)
   196  					fmt.Printf("Groups: %v\n", strings.Join(response.Groups, ","))
   197  				}
   198  			default:
   199  				log.Fatalf("Unknown output format: %s", output)
   200  			}
   201  		},
   202  	}
   203  	command.Flags().StringVarP(&output, "output", "o", "", "Output format. One of: yaml, json")
   204  	return command
   205  }
   206  
   207  func NewAccountCanICommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   208  	return &cobra.Command{
   209  		Use:   "can-i ACTION RESOURCE SUBRESOURCE",
   210  		Short: "Can I",
   211  		Example: fmt.Sprintf(`
   212  # Can I sync any app?
   213  argocd account can-i sync applications '*'
   214  
   215  # Can I update a project?
   216  argocd account can-i update projects 'default'
   217  
   218  # Can I create a cluster?
   219  argocd account can-i create clusters '*'
   220  
   221  Actions: %v
   222  Resources: %v
   223  `, rbac.Actions, rbac.Resources),
   224  		Run: func(c *cobra.Command, args []string) {
   225  			ctx := c.Context()
   226  
   227  			if len(args) != 3 {
   228  				c.HelpFunc()(c, args)
   229  				os.Exit(1)
   230  			}
   231  
   232  			conn, client := headless.NewClientOrDie(clientOpts, c).NewAccountClientOrDie()
   233  			defer utilio.Close(conn)
   234  
   235  			response, err := client.CanI(ctx, &accountpkg.CanIRequest{
   236  				Action:      args[0],
   237  				Resource:    args[1],
   238  				Subresource: args[2],
   239  			})
   240  			errors.CheckError(err)
   241  			fmt.Println(response.Value)
   242  		},
   243  	}
   244  }
   245  
   246  func printAccountNames(accounts []*accountpkg.Account) {
   247  	for _, p := range accounts {
   248  		fmt.Println(p.Name)
   249  	}
   250  }
   251  
   252  func printAccountsTable(items []*accountpkg.Account) {
   253  	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
   254  	fmt.Fprintf(w, "NAME\tENABLED\tCAPABILITIES\n")
   255  	for _, a := range items {
   256  		fmt.Fprintf(w, "%s\t%v\t%s\n", a.Name, a.Enabled, strings.Join(a.Capabilities, ", "))
   257  	}
   258  	_ = w.Flush()
   259  }
   260  
   261  func NewAccountListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   262  	var output string
   263  	cmd := &cobra.Command{
   264  		Use:     "list",
   265  		Short:   "List accounts",
   266  		Example: "argocd account list",
   267  		Run: func(c *cobra.Command, _ []string) {
   268  			ctx := c.Context()
   269  
   270  			conn, client := headless.NewClientOrDie(clientOpts, c).NewAccountClientOrDie()
   271  			defer utilio.Close(conn)
   272  
   273  			response, err := client.ListAccounts(ctx, &accountpkg.ListAccountRequest{})
   274  
   275  			errors.CheckError(err)
   276  			switch output {
   277  			case "yaml", "json":
   278  				err := PrintResourceList(response.Items, output, false)
   279  				errors.CheckError(err)
   280  			case "name":
   281  				printAccountNames(response.Items)
   282  			case "wide", "":
   283  				printAccountsTable(response.Items)
   284  			default:
   285  				errors.CheckError(fmt.Errorf("unknown output format: %s", output))
   286  			}
   287  		},
   288  	}
   289  	cmd.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|name")
   290  	return cmd
   291  }
   292  
   293  func getCurrentAccount(ctx context.Context, clientset argocdclient.Client) session.GetUserInfoResponse {
   294  	conn, client := clientset.NewSessionClientOrDie()
   295  	defer utilio.Close(conn)
   296  	userInfo, err := client.GetUserInfo(ctx, &session.GetUserInfoRequest{})
   297  	errors.CheckError(err)
   298  	return *userInfo
   299  }
   300  
   301  func NewAccountGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   302  	var (
   303  		output  string
   304  		account string
   305  	)
   306  	cmd := &cobra.Command{
   307  		Use:   "get",
   308  		Short: "Get account details",
   309  		Example: `# Get the currently logged in account details
   310  argocd account get
   311  
   312  # Get details for an account by name
   313  argocd account get --account <account-name>`,
   314  		Run: func(c *cobra.Command, _ []string) {
   315  			ctx := c.Context()
   316  
   317  			clientset := headless.NewClientOrDie(clientOpts, c)
   318  
   319  			if account == "" {
   320  				account = getCurrentAccount(ctx, clientset).Username
   321  			}
   322  
   323  			conn, client := clientset.NewAccountClientOrDie()
   324  			defer utilio.Close(conn)
   325  
   326  			acc, err := client.GetAccount(ctx, &accountpkg.GetAccountRequest{Name: account})
   327  
   328  			errors.CheckError(err)
   329  			switch output {
   330  			case "yaml", "json":
   331  				err := PrintResourceList(acc, output, true)
   332  				errors.CheckError(err)
   333  			case "name":
   334  				fmt.Println(acc.Name)
   335  			case "wide", "":
   336  				printAccountDetails(acc)
   337  			default:
   338  				errors.CheckError(fmt.Errorf("unknown output format: %s", output))
   339  			}
   340  		},
   341  	}
   342  	cmd.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|name")
   343  	cmd.Flags().StringVarP(&account, "account", "a", "", "Account name. Defaults to the current account.")
   344  	return cmd
   345  }
   346  
   347  func printAccountDetails(acc *accountpkg.Account) {
   348  	fmt.Printf(printOpFmtStr, "Name:", acc.Name)
   349  	fmt.Printf(printOpFmtStr, "Enabled:", strconv.FormatBool(acc.Enabled))
   350  	fmt.Printf(printOpFmtStr, "Capabilities:", strings.Join(acc.Capabilities, ", "))
   351  	fmt.Println("\nTokens:")
   352  	if len(acc.Tokens) == 0 {
   353  		fmt.Println("NONE")
   354  	} else {
   355  		w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
   356  		fmt.Fprintf(w, "ID\tISSUED AT\tEXPIRING AT\n")
   357  		for _, t := range acc.Tokens {
   358  			expiresAtFormatted := "never"
   359  			if t.ExpiresAt > 0 {
   360  				expiresAt := time.Unix(t.ExpiresAt, 0)
   361  				expiresAtFormatted = expiresAt.Format(time.RFC3339)
   362  				if expiresAt.Before(time.Now()) {
   363  					expiresAtFormatted = expiresAtFormatted + " (expired)"
   364  				}
   365  			}
   366  
   367  			fmt.Fprintf(w, "%s\t%s\t%s\n", t.Id, time.Unix(t.IssuedAt, 0).Format(time.RFC3339), expiresAtFormatted)
   368  		}
   369  		_ = w.Flush()
   370  	}
   371  }
   372  
   373  func NewAccountGenerateTokenCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   374  	var (
   375  		account   string
   376  		expiresIn string
   377  		id        string
   378  	)
   379  	cmd := &cobra.Command{
   380  		Use:   "generate-token",
   381  		Short: "Generate account token",
   382  		Example: `# Generate token for the currently logged in account
   383  argocd account generate-token
   384  
   385  # Generate token for the account with the specified name
   386  argocd account generate-token --account <account-name>`,
   387  		Run: func(c *cobra.Command, _ []string) {
   388  			ctx := c.Context()
   389  
   390  			clientset := headless.NewClientOrDie(clientOpts, c)
   391  			conn, client := clientset.NewAccountClientOrDie()
   392  			defer utilio.Close(conn)
   393  			if account == "" {
   394  				account = getCurrentAccount(ctx, clientset).Username
   395  			}
   396  			expiresIn, err := timeutil.ParseDuration(expiresIn)
   397  			errors.CheckError(err)
   398  			response, err := client.CreateToken(ctx, &accountpkg.CreateTokenRequest{
   399  				Name:      account,
   400  				ExpiresIn: int64(expiresIn.Seconds()),
   401  				Id:        id,
   402  			})
   403  			errors.CheckError(err)
   404  			fmt.Println(response.Token)
   405  		},
   406  	}
   407  	cmd.Flags().StringVarP(&account, "account", "a", "", "Account name. Defaults to the current account.")
   408  	cmd.Flags().StringVarP(&expiresIn, "expires-in", "e", "0s", "Duration before the token will expire. (Default: No expiration)")
   409  	cmd.Flags().StringVar(&id, "id", "", "Optional token id. Fall back to uuid if not value specified.")
   410  	return cmd
   411  }
   412  
   413  func NewAccountDeleteTokenCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   414  	var account string
   415  	cmd := &cobra.Command{
   416  		Use:   "delete-token",
   417  		Short: "Deletes account token",
   418  		Example: `# Delete token of the currently logged in account
   419  argocd account delete-token ID
   420  
   421  # Delete token of the account with the specified name
   422  argocd account delete-token --account <account-name> ID`,
   423  		Run: func(c *cobra.Command, args []string) {
   424  			ctx := c.Context()
   425  
   426  			if len(args) != 1 {
   427  				c.HelpFunc()(c, args)
   428  				os.Exit(1)
   429  			}
   430  			id := args[0]
   431  
   432  			clientset := headless.NewClientOrDie(clientOpts, c)
   433  			conn, client := clientset.NewAccountClientOrDie()
   434  			defer utilio.Close(conn)
   435  			if account == "" {
   436  				account = getCurrentAccount(ctx, clientset).Username
   437  			}
   438  			promptUtil := utils.NewPrompt(clientOpts.PromptsEnabled)
   439  			canDelete := promptUtil.Confirm(fmt.Sprintf("Are you sure you want to delete '%s' token? [y/n]", id))
   440  			if canDelete {
   441  				_, err := client.DeleteToken(ctx, &accountpkg.DeleteTokenRequest{Name: account, Id: id})
   442  				errors.CheckError(err)
   443  			} else {
   444  				fmt.Printf("The command to delete '%s' was cancelled.\n", id)
   445  			}
   446  		},
   447  	}
   448  	cmd.Flags().StringVarP(&account, "account", "a", "", "Account name. Defaults to the current account.")
   449  	return cmd
   450  }