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(¤tPassword, "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 }