Documentation ¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
View Source
var AddCredentialsCommand = cli.Command{ Name: "add", Usage: "Add IAM credentials to secure storage", ArgsUsage: "[<profile>]", Action: func(c *cli.Context) error { profileName := c.Args().First() if profileName == "" { in := survey.Input{Message: "Profile Name:"} err := testable.AskOne(&in, &profileName, survey.WithValidator(survey.MinLength(1))) if err != nil { return err } } profiles, err := cfaws.LoadProfiles() if err != nil { return err } if profiles.HasProfile(profileName) { return fmt.Errorf("a profile with name %s already exists, you can import an existing profile using '%s credentials import %s", profileName, build.GrantedBinaryName(), profileName) } credentials, err := promptCredentials() if err != nil { return err } secureIAMCredentialStorage := securestorage.NewSecureIAMCredentialStorage() err = secureIAMCredentialStorage.StoreCredentials(profileName, credentials) if err != nil { return err } err = updateOrCreateProfileWithCredentialProcess(profileName) if err != nil { return err } fmt.Printf("Saved %s to secure storage\n", profileName) return nil }, }
View Source
var CacheCommand = cli.Command{ Name: "cache", Usage: "Manage your cached credentials that are stored in secure storage", Subcommands: []*cli.Command{&ClearCommand, &ListCommand}, }
View Source
var ClearCommand = cli.Command{ Name: "clear", Usage: "Clear cached credential from the secure storage", Flags: []cli.Flag{ &cli.StringFlag{Name: "storage", Usage: "Specify the storage type"}, &cli.StringFlag{Name: "profile", Usage: "Specify the profile name of the credential which should be cleared"}, }, Action: func(c *cli.Context) error { withStdio := survey.WithStdio(os.Stdin, os.Stderr, os.Stderr) selection := c.String("storage") if selection == "" { in := survey.Select{ Message: "Select which secure storage would you like to clear cache from", Options: []string{"aws-iam-credentials", "sso-token", "session-credentials"}, } clio.NewLine() err := testable.AskOne(&in, &selection, withStdio) if err != nil { return err } } storageToNameMap := map[string]securestorage.SecureStorage{ "aws-iam-credentials": securestorage.NewSecureIAMCredentialStorage().SecureStorage, "sso-token": securestorage.NewSecureSSOTokenStorage().SecureStorage, "session-credentials": securestorage.NewSecureSessionCredentialStorage().SecureStorage, } selectedStorage := storageToNameMap[selection] keys, err := selectedStorage.ListKeys() if err != nil { return err } if len(keys) == 0 { clio.Warnf("You do not have any cached credentials for %s storage", selection) return nil } selectedProfile := c.String("profile") if selectedProfile == "" { prompt := &survey.Select{ Message: "Select the profile name you want to clear cache for", Options: keys, } err = survey.AskOne(prompt, &selectedProfile) if err != nil { return err } } err = selectedStorage.Clear(selectedProfile) if err != nil { return err } clio.Successf("successfully cleared the cached credentials for '%s'", selectedProfile) return nil }, }
View Source
var ClearSSOTokensCommand = cli.Command{ Name: "clear", Usage: "Remove a selected token from the keyring", Flags: []cli.Flag{ &cli.BoolFlag{Name: "all", Aliases: []string{"a"}, Usage: "Remove all saved tokens from keyring"}, }, Action: func(c *cli.Context) error { if c.Bool("all") { err := clearAllTokens() if err != nil { return err } clio.Success("Cleared all saved tokens") return nil } var selection string if c.Args().First() != "" { selection = c.Args().First() } startUrlMap, err := MapTokens(c.Context) if err != nil { return err } if selection == "" { var max int for k := range startUrlMap { if len(k) > max { max = len(k) } } selectionsMap := make(map[string]string) tokenList := []string{} for k, profiles := range startUrlMap { stringKey := fmt.Sprintf("%-*s (%s)", max, k, strings.Join(profiles, ", ")) tokenList = append(tokenList, stringKey) selectionsMap[stringKey] = k } withStdio := survey.WithStdio(os.Stdin, os.Stderr, os.Stderr) in := survey.Select{ Message: "Select a token to remove from keyring", Options: tokenList, } clio.NewLine() var out string err = testable.AskOne(&in, &out, withStdio) if err != nil { return err } selection = selectionsMap[out] } secureSSOTokenStorage := securestorage.NewSecureSSOTokenStorage() err = secureSSOTokenStorage.SecureStorage.Clear(selection) if err != nil { return err } clio.Successf("Cleared %s", selection) return nil }, }
View Source
var CompletionCommand = cli.Command{ Name: "completion", Usage: "Add autocomplete to your granted cli installation", Flags: flags, Action: func(c *cli.Context) (err error) { shell := c.String("shell") switch shell { case "fish": err = installFishCompletions(c) case "zsh": err = installZSHCompletions(c) case "bash": err = installBashCompletions(c) default: clio.Info("To install completions for other shells, please see our docs: https://docs.commonfate.io/granted/configuration#autocompletion") } return err }, Description: "Install completions for fish, zsh, or bash. To install completions for other shells, please see our docs:\nhttps://docs.commonfate.io/granted/configuration#autocompletion\n", }
View Source
var ConsoleCommand = cli.Command{ Name: "console", Usage: "Generate an AWS console URL using credentials in the environment", Flags: []cli.Flag{ &cli.StringFlag{Name: "service"}, &cli.StringFlag{Name: "region", EnvVars: []string{"AWS_REGION"}}, &cli.StringFlag{Name: "destination", Usage: "The destination URL for the console"}, &cli.BoolFlag{Name: "url", Usage: "Return the URL to stdout instead of launching the browser"}, &cli.BoolFlag{Name: "firefox", Usage: "Generate the Firefox container URL"}, &cli.StringFlag{Name: "color", Usage: "When the firefox flag is true, this specifies the color of the container tab"}, &cli.StringFlag{Name: "icon", Usage: "When firefox flag is true, this specifies the icon of the container tab"}, &cli.StringFlag{Name: "container-name", Usage: "When firefox flag is true, this specifies the name of the container of the container tab.", Value: "aws"}, }, Action: func(c *cli.Context) error { ctx := c.Context credentials := cfaws.GetEnvCredentials(ctx) con := console.AWS{ Service: c.String("service"), Region: c.String("region"), Destination: c.String("destination"), } consoleURL, err := con.URL(credentials) if err != nil { return err } cfg, err := config.Load() if err != nil { return err } if c.Bool("firefox") || cfg.DefaultBrowser == browser.FirefoxKey || cfg.DefaultBrowser == browser.FirefoxStdoutKey { consoleURL = fmt.Sprintf("ext+granted-containers:name=%s&url=%s&color=%s&icon=%s", c.String("container-name"), url.QueryEscape(consoleURL), c.String("color"), c.String("icon")) } justPrintURL := c.Bool("url") || cfg.DefaultBrowser == browser.StdoutKey || cfg.DefaultBrowser == browser.FirefoxStdoutKey if justPrintURL { fmt.Print(consoleURL) return nil } var l assume.Launcher if cfg.CustomBrowserPath == "" && cfg.DefaultBrowser != "" { l = launcher.Open{} } else if cfg.CustomBrowserPath == "" { return errors.New("default browser not configured. run `granted browser set` to configure") } else { switch cfg.DefaultBrowser { case browser.ChromeKey: l = launcher.ChromeProfile{ ExecutablePath: cfg.CustomBrowserPath, } case browser.BraveKey: l = launcher.ChromeProfile{ ExecutablePath: cfg.CustomBrowserPath, } case browser.EdgeKey: l = launcher.ChromeProfile{ ExecutablePath: cfg.CustomBrowserPath, } case browser.ChromiumKey: l = launcher.ChromeProfile{ ExecutablePath: cfg.CustomBrowserPath, } case browser.FirefoxKey: l = launcher.Firefox{ ExecutablePath: cfg.CustomBrowserPath, } case browser.SafariKey: l = launcher.Safari{} default: l = launcher.Open{} } } args := l.LaunchCommand(consoleURL, con.Profile) var startErr error if l.UseForkProcess() { clio.Debugf("running command using forkprocess: %s", args) cmd, err := forkprocess.New(args...) if err != nil { return err } startErr = cmd.Start() } else { clio.Debugf("running command without forkprocess: %s", args) cmd := exec.Command(args[0], args[1:]...) startErr = cmd.Start() } if startErr != nil { return clierr.New(fmt.Sprintf("Granted was unable to open a browser session automatically due to the following error: %s", err.Error()), clierr.Info("You can open the browser session manually using the following url:"), clierr.Info(consoleURL), ) } return nil }, }
View Source
var CredentialProcess = cli.Command{ Name: "credential-process", Usage: "Exports AWS session credentials for use with AWS CLI credential_process", Flags: []cli.Flag{ &cli.StringFlag{Name: "profile", Required: true}, &cli.StringFlag{Name: "url"}, &cli.DurationFlag{Name: "window", Value: 15 * time.Minute}, &cli.BoolFlag{Name: "auto-login", Usage: "automatically open the configured browser to log in if needed"}, }, Action: func(c *cli.Context) error { cfg, err := config.Load() if err != nil { return err } profileName := c.String("profile") autoLogin := c.Bool("auto-login") secureSessionCredentialStorage := securestorage.NewSecureSessionCredentialStorage() clio.Debugw("running credential process with config", "profile", profileName, "url", c.String("url"), "window", c.Duration("window"), "disableCredentialProcessCache", cfg.DisableCredentialProcessCache) useCache := !cfg.DisableCredentialProcessCache if useCache { cachedCreds, err := secureSessionCredentialStorage.GetCredentials(profileName) if err != nil { clio.Debugw("error loading cached credentials", "error", err) } else if cachedCreds == nil { clio.Debugw("refreshing credentials", "reason", "cachedCreds was nil") } else if cachedCreds.CanExpire && cachedCreds.Expires.Add(-c.Duration("window")).Before(time.Now()) { clio.Debugw("refreshing credentials", "reason", "credentials are expired") } else { clio.Debugw("credentials found in cache", "expires", cachedCreds.Expires.String(), "canExpire", cachedCreds.CanExpire, "timeNow", time.Now().String(), "refreshIfBeforeNow", cachedCreds.Expires.Add(-c.Duration("window")).String()) return printCredentials(*cachedCreds) } } if !useCache { clio.Debugw("refreshing credentials", "reason", "credential process cache is disabled via config") } profiles, err := cfaws.LoadProfiles() if err != nil { return err } profile, err := profiles.LoadInitialisedProfile(c.Context, profileName) if err != nil { return err } duration := time.Hour if profile.AWSConfig.RoleDurationSeconds != nil { duration = *profile.AWSConfig.RoleDurationSeconds } credentials, err := profile.AssumeTerminal(c.Context, cfaws.ConfigOpts{Duration: duration, UsingCredentialProcess: true, CredentialProcessAutoLogin: autoLogin}) if err != nil { return err } if !cfg.DisableCredentialProcessCache { clio.Debugw("storing refreshed credentials in credential process cache", "expires", credentials.Expires.String(), "canExpire", credentials.CanExpire, "timeNow", time.Now().String()) if err := secureSessionCredentialStorage.StoreCredentials(profileName, credentials); err != nil { return err } } return printCredentials(credentials) }, }
View Source
var CredentialsCommand = cli.Command{ Name: "credentials", Usage: "Manage secure IAM credentials", Subcommands: []*cli.Command{&AddCredentialsCommand, &ImportCredentialsCommand, &UpdateCredentialsCommand, &ListCredentialsCommand, &RemoveCredentialsCommand, &ExportCredentialsCommand, &RotateCredentialsCommand, &ImportCredFromEnvCommand}, }
View Source
var DefaultBrowserCommand = cli.Command{ Name: "browser", Usage: "View the web browser that Granted uses to open cloud consoles", Subcommands: []*cli.Command{&SetBrowserCommand, &SetSSOBrowserCommand}, Action: func(c *cli.Context) error { conf, err := config.Load() if err != nil { return err } clio.Infof("Granted is using %s. To change this run `granted browser set`", conf.DefaultBrowser) return nil }, }
View Source
var ExportCredentialsCommand = cli.Command{ Name: "export-plaintext", Usage: "Export credentials from the secure storage to ~/.aws/credentials file in plaintext", ArgsUsage: "[<profile>]", Flags: []cli.Flag{ &cli.BoolFlag{Name: "all", Aliases: []string{"a"}, Usage: "export all credentials from secure storage in plaintext"}, }, Action: func(c *cli.Context) error { secureIAMCredentialStorage := securestorage.NewSecureIAMCredentialStorage() profileName := c.Args().First() secureProfileKeys, err := secureIAMCredentialStorage.SecureStorage.ListKeys() if err != nil { return err } var profileNames []string if c.Bool("all") { profileNames = append(profileNames, secureProfileKeys...) } else { if profileName == "" && len(secureProfileKeys) == 0 { fmt.Println("No credentials in secure storage") return nil } if profileName == "" { in := survey.Select{Message: "Profile Name:", Options: secureProfileKeys} err = testable.AskOne(&in, &profileName) if err != nil { return err } } profileNames = append(profileNames, profileName) } for _, profileName := range profileNames { credentials, err := secureIAMCredentialStorage.GetCredentials(profileName) if err != nil { return err } credentialsFilePath := cfaws.GetAWSCredentialsPath() credentialsFile, err := ini.LoadSources(ini.LoadOptions{ AllowNonUniqueSections: false, SkipUnrecognizableLines: false, AllowNestedValues: true, }, credentialsFilePath) if err != nil { return err } section, err := credentialsFile.NewSection(profileName) if err != nil { return err } err = section.ReflectFrom(&struct { AWSAccessKeyID string `ini:"aws_access_key_id"` AWSSecretAccessKey string `ini:"aws_secret_access_key"` AWSSessionToken string `ini:"aws_session_token,omitempty"` }{ AWSAccessKeyID: credentials.AccessKeyID, AWSSecretAccessKey: credentials.SecretAccessKey, AWSSessionToken: credentials.SessionToken, }) if err != nil { return err } err = credentialsFile.SaveTo(credentialsFilePath) if err != nil { return err } configPath := cfaws.GetAWSConfigPath() configFile, err := ini.LoadSources(ini.LoadOptions{ AllowNonUniqueSections: false, SkipUnrecognizableLines: false, AllowNestedValues: true, }, configPath) if err != nil { return err } sectionName := "profile " + profileName if section, _ := configFile.GetSection(sectionName); section != nil { if section.HasKey("credential_process") { if len(section.Keys()) > 1 { section.DeleteKey("credential_process") } else { configFile.DeleteSection(sectionName) } err = configFile.SaveTo(configPath) if err != nil { return err } } } fmt.Printf("Exported %s in plaintext from secure storage to %s\n", profileName, credentialsFilePath) fmt.Printf("The %s credentials have not been removed from secure storage. If you'd like to delete them, you can run '%s credentials remove %s'\n", profileName, build.GrantedBinaryName(), profileName) } return nil }, }
View Source
var GenerateCommand = cli.Command{ Name: "generate", Usage: "Prints an AWS configuration file to stdout with profiles from accounts and roles available in AWS SSO", UsageText: "granted [global options] sso generate [command options] [sso-start-url]", Flags: []cli.Flag{ &cli.StringFlag{Name: "config", Usage: "Specify the SSO config section in the Granted config file ([SSO.name])", Value: "default"}, &cli.StringFlag{Name: "prefix", Usage: "Specify a prefix for all generated profile names"}, &cli.StringFlag{Name: "sso-region", Usage: "Specify the SSO region"}, &cli.StringSliceFlag{Name: "source", Usage: "The sources to load AWS profiles from (valid values are: 'aws-sso', 'commonfate')", Value: cli.NewStringSlice("aws-sso")}, &cli.BoolFlag{Name: "no-credential-process", Usage: "Generate profiles without the Granted credential-process integration"}, &cli.StringFlag{Name: "profile-template", Usage: "Specify profile name template", Value: awsconfigfile.DefaultProfileNameTemplate}}, Action: func(c *cli.Context) error { ctx := c.Context fullCommand := fmt.Sprintf("%s %s", c.App.Name, c.Command.FullName()) cfg, err := grantedconfig.Load() if err != nil { clio.Errorf("Error reading default config (~/.granted/config)") return nil } cfgSSO := cfg.SSO[c.String("config")] startURL := coalesceString(c.Args().First(), cfgSSO.StartURL) if startURL == "" { return clierr.New(fmt.Sprintf("Usage: %s [sso-start-url]", fullCommand), clierr.Infof("For example, %s https://example.awsapps.com/start", fullCommand)) } ssoRegion := coalesceString(c.String("sso-region"), cfgSSO.SSORegion) if ssoRegion == "" { clio.Errorf("Please specify the --sso-region flag: '%s --sso-region us-east-1 %s'", fullCommand, startURL) return nil } // Since `profile-template` has a default value, need to check IsSet instead of having a value var profileNameTemplate string if c.IsSet("profile-template") { profileNameTemplate = c.String("profile-template") } else { profileNameTemplate = coalesceString(cfgSSO.ProfileTemplate, c.String("profile-template")) } prefix := coalesceString(c.String("prefix"), cfgSSO.Prefix) noCredentialProcess := c.Bool("no-credential-process") || cfgSSO.NoCredentialProcess g := awsconfigfile.Generator{ Config: ini.Empty(), ProfileNameTemplate: profileNameTemplate, NoCredentialProcess: noCredentialProcess, Prefix: prefix, } for _, s := range c.StringSlice("source") { switch s { case "aws-sso": g.AddSource(AWSSSOSource{SSORegion: ssoRegion, StartURL: startURL}) case "commonfate", "common-fate", "cf": ps, err := getCFProfileSource(c, ssoRegion, startURL) if err != nil { return err } g.AddSource(ps) default: return fmt.Errorf("unknown profile source %s: allowed sources are aws-sso, commonfate", s) } } err = g.Generate(ctx) if err != nil { return err } _, err = g.Config.WriteTo(os.Stdout) if err != nil { return err } return nil }, }
in dev: go run ./cmd/granted/main.go sso generate --sso-region ap-southeast-2 url
View Source
var ImportCredFromEnvCommand = cli.Command{ Name: "import-from-env", Usage: "Create a new AWS config profile with IAM credentials imported from environment. You must have $AWS_ACCESS_KEY_ID and $AWS_SECRET_ACCESS_KEY set in your environment", Flags: []cli.Flag{ &cli.StringFlag{Name: "profile", Required: true}, }, Action: func(c *cli.Context) error { ctx := c.Context accessKeyFromEnv, accessKeyFromEnvExists := os.LookupEnv("AWS_ACCESS_KEY_ID") secretAccessKeyFromEnv, secretAccessKeyFromEnvExists := os.LookupEnv("AWS_SECRET_ACCESS_KEY") if accessKeyFromEnvExists && secretAccessKeyFromEnvExists { profileName := c.String("profile") profiles, err := cfaws.LoadProfiles() if err != nil { return err } if profiles.HasProfile(profileName) { return fmt.Errorf("profile with name '%s' already exist", profileName) } credentials, err := credentials.NewStaticCredentialsProvider(accessKeyFromEnv, secretAccessKeyFromEnv, "").Retrieve(ctx) if err != nil { return err } secureIAMCredentialStorage := securestorage.NewSecureIAMCredentialStorage() err = secureIAMCredentialStorage.StoreCredentials(profileName, credentials) if err != nil { return err } credentialsFilePath := cfaws.GetAWSCredentialsPath() credentialsFile, err := ini.LoadSources(ini.LoadOptions{ AllowNonUniqueSections: false, SkipUnrecognizableLines: false, AllowNestedValues: true, }, credentialsFilePath) if err != nil { return err } section, err := credentialsFile.NewSection(profileName) if err != nil { return err } err = section.ReflectFrom(&struct { AWSAccessKeyID string `ini:"aws_access_key_id"` AWSSecretAccessKey string `ini:"aws_secret_access_key"` }{ AWSAccessKeyID: accessKeyFromEnv, AWSSecretAccessKey: secretAccessKeyFromEnv, }) if err != nil { return err } err = credentialsFile.SaveTo(credentialsFilePath) if err != nil { return err } err = updateOrCreateProfileWithCredentialProcess(profileName) if err != nil { return err } clio.Successf("successfully created new profile %s", profileName) return nil } clio.Error("you don't have variables $AWS_ACCESS_KEY_ID and $AWS_SECRET_ACCESS_KEY set in your environment.") clio.Info("If you instead want to import plain-text credentials from ~/.aws/credentials to secure storage then run 'granted credentials import'") return nil }, }
View Source
var ImportCredentialsCommand = cli.Command{ Name: "import", Usage: "Import plaintext IAM user credentials from AWS credentials file into secure storage", ArgsUsage: "[<profile>]", Flags: []cli.Flag{ &cli.BoolFlag{Name: "overwrite", Usage: "Overwrite an existing profile saved in secure storage with values from the AWS credentials file"}, }, Action: func(c *cli.Context) error { profileName := c.Args().First() profiles, err := cfaws.LoadProfiles() if err != nil { return err } if profileName == "" { in := survey.Select{Message: "Profile Name:", Options: profiles.ProfileNames} err := testable.AskOne(&in, &profileName, survey.WithValidator(func(ans interface{}) error { option := ans.(core.OptionAnswer) return validateProfileForImport(c.Context, profiles, option.Value, c.Bool("overwrite")) })) if err != nil { return err } } else { err = validateProfileForImport(c.Context, profiles, profileName, c.Bool("overwrite")) if err != nil { return err } } profile, err := profiles.LoadInitialisedProfile(c.Context, profileName) if err != nil { return err } secureIAMCredentialStorage := securestorage.NewSecureIAMCredentialStorage() err = secureIAMCredentialStorage.StoreCredentials(profileName, profile.AWSConfig.Credentials) if err != nil { return err } err = updateOrCreateProfileWithCredentialProcess(profileName) if err != nil { return err } credentialsFilePath := cfaws.GetAWSCredentialsPath() credentialsFile, err := ini.LoadSources(ini.LoadOptions{ AllowNonUniqueSections: false, SkipUnrecognizableLines: false, AllowNestedValues: true, }, credentialsFilePath) if err != nil { return err } items, err := credentialsFile.GetSection(profileName) if err != nil { return err } configPath := cfaws.GetAWSConfigPath() configFile, err := ini.LoadSources(ini.LoadOptions{ AllowNonUniqueSections: false, SkipUnrecognizableLines: false, AllowNestedValues: true, }, configPath) if err != nil { return err } sectionName := "profile " + profileName for _, key := range items.Keys() { if !(key.Name() == "aws_access_key_id" || key.Name() == "aws_secret_access_key" || key.Name() == "aws_session_token") { section, err := configFile.GetSection(sectionName) if err != nil { return err } if !section.HasKey(key.Name()) { _, err = section.NewKey(key.Name(), key.Value()) if err != nil { return err } } } } err = configFile.SaveTo(configPath) if err != nil { return err } credentialsFile.DeleteSection(profileName) err = credentialsFile.SaveTo(credentialsFilePath) if err != nil { return err } fmt.Printf("Saved %s to secure storage\n", profileName) return nil }, }
View Source
var ListCommand = cli.Command{ Name: "list", Usage: "List currently cached credentials and secure storage type", Action: func(c *cli.Context) error { storageToNameMap := map[string]securestorage.SecureStorage{ "aws-iam-credentials": securestorage.NewSecureIAMCredentialStorage().SecureStorage, "sso-token": securestorage.NewSecureSSOTokenStorage().SecureStorage, "session-credentials": securestorage.NewSecureSessionCredentialStorage().SecureStorage, } tw := tabwriter.NewWriter(os.Stderr, 10, 1, 5, ' ', 0) headers := strings.Join([]string{"STORAGE TYPE", "KEY"}, "\t") fmt.Fprintln(tw, headers) for storageName, v := range storageToNameMap { keys, err := v.ListKeys() if err != nil { return err } for _, key := range keys { tabbed := strings.Join([]string{storageName, key}, "\t") fmt.Fprintln(tw, tabbed) } } tw.Flush() return nil }, }
View Source
var ListCredentialsCommand = cli.Command{ Name: "list", Usage: "Lists the profile names for credentials in secure storage", Action: func(c *cli.Context) error { secureIAMCredentialStorage := securestorage.NewSecureIAMCredentialStorage() profiles, err := secureIAMCredentialStorage.SecureStorage.List() if err != nil { return err } if len(profiles) == 0 { clio.Info("No IAM user credentials stored in secure storage") return nil } for _, profile := range profiles { fmt.Printf("%s\n", profile.Key) } return nil }, }
View Source
var ListSSOTokensCommand = cli.Command{ Name: "list", Usage: "Lists all access tokens saved in the keyring", Action: func(ctx *cli.Context) error { startUrlMap, err := MapTokens(ctx.Context) if err != nil { return err } var max int for k := range startUrlMap { if len(k) > max { max = len(k) } } secureSSOTokenStorage := securestorage.NewSecureSSOTokenStorage() keys, err := secureSSOTokenStorage.SecureStorage.ListKeys() if err != nil { return err } for _, key := range keys { clio.Logf("%-*s (%s)", max, key, strings.Join(startUrlMap[key], ", ")) } return nil }, }
View Source
var LoginCommand = cli.Command{ Name: "login", Usage: "Log in via AWS SSO interactive credential process", Flags: []cli.Flag{ &cli.StringFlag{Name: "sso-region", Usage: "Specify the SSO region"}, &cli.StringFlag{Name: "sso-start-url", Usage: "Specify the SSO start url"}, &cli.StringSliceFlag{Name: "sso-scope", Usage: "Specify the SSO scopes"}, }, Action: func(c *cli.Context) error { ctx := c.Context ssoStartUrl := c.String("sso-start-url") if ssoStartUrl == "" { in1 := survey.Input{Message: "SSO Start URL"} err := testable.AskOne(&in1, &ssoStartUrl) if err != nil { return err } } ssoRegion := c.String("sso-region") if ssoRegion == "" { resp, err := http.Get(ssoStartUrl) if err != nil { return err } defer resp.Body.Close() re := regexp.MustCompile(`<meta\s+name="region"\s+content="(.*?)"/>`) body, err := io.ReadAll(resp.Body) if err != nil { return err } match := re.FindStringSubmatch(string(body)) if len(match) == 2 { ssoRegion = match[1] } if ssoRegion == "" { in2 := survey.Input{Message: "Region"} err := testable.AskOne(&in2, &ssoRegion) if err != nil { return err } } } ssoScopes := c.StringSlice("sso-scope") cfg := aws.NewConfig() cfg.Region = ssoRegion secureSSOTokenStorage := securestorage.NewSecureSSOTokenStorage() newSSOToken, err := idclogin.Login(ctx, *cfg, ssoStartUrl, ssoScopes) if err != nil { return err } secureSSOTokenStorage.StoreSSOToken(ssoStartUrl, *newSSOToken) clio.Successf("Successfully logged into Start URL: %s", ssoStartUrl) return nil }, }
View Source
var PopulateCommand = cli.Command{ Name: "populate", Usage: "Populate your local AWS configuration file with profiles from accounts and roles available in AWS SSO", UsageText: "granted [global options] sso populate [command options] [sso-start-url]", Flags: []cli.Flag{ &cli.StringFlag{Name: "config", Usage: "Specify the SSO config section ([SSO.name])", Value: "default"}, &cli.StringFlag{Name: "prefix", Usage: "Specify a prefix for all generated profile names"}, &cli.StringFlag{Name: "sso-region", Usage: "Specify the SSO region"}, &cli.StringSliceFlag{Name: "sso-scope", Usage: "Specify the SSO scopes"}, &cli.StringSliceFlag{Name: "source", Usage: "The sources to load AWS profiles from", Value: cli.NewStringSlice("aws-sso")}, &cli.BoolFlag{Name: "prune", Usage: "Remove any generated profiles with the 'common_fate_generated_from' key which no longer exist"}, &cli.StringFlag{Name: "profile-template", Usage: "Specify profile name template", Value: awsconfigfile.DefaultProfileNameTemplate}, &cli.BoolFlag{Name: "no-credential-process", Usage: "Generate profiles without the Granted credential-process integration"}, }, Action: func(c *cli.Context) error { ctx := c.Context fullCommand := fmt.Sprintf("%s %s", c.App.Name, c.Command.FullName()) cfg, err := grantedconfig.Load() if err != nil { clio.Errorf("Error reading default config (~/.granted/config)") return nil } cfgSSO := cfg.SSO[c.String("config")] startURL := coalesceString(c.Args().First(), cfgSSO.StartURL) if startURL == "" { return clierr.New(fmt.Sprintf("Usage: %s [sso-start-url]", fullCommand), clierr.Infof("For example, %s https://example.awsapps.com/start", fullCommand)) } ssoRegion := coalesceString(c.String("sso-region"), cfgSSO.SSORegion) if ssoRegion == "" { clio.Errorf("Please specify the --sso-region flag: '%s --sso-region us-east-1 %s'", fullCommand, startURL) return nil } // Since `profile-template` has a default value, need to check IsSet instead of having a value var profileNameTemplate string if c.IsSet("profile-template") { profileNameTemplate = c.String("profile-template") } else { profileNameTemplate = coalesceString(cfgSSO.ProfileTemplate, c.String("profile-template")) } prefix := coalesceString(c.String("prefix"), cfgSSO.Prefix) noCredentialProcess := c.Bool("no-credential-process") || cfgSSO.NoCredentialProcess configFilename := cfaws.GetAWSConfigPath() config, err := ini.LoadSources(ini.LoadOptions{ AllowNonUniqueSections: false, SkipUnrecognizableLines: false, AllowNestedValues: true, }, configFilename) if err != nil { if !os.IsNotExist(err) { return err } config = ini.Empty() } var pruneStartURLs []string if c.Bool("prune") { pruneStartURLs = []string{startURL} } g := awsconfigfile.Generator{ Config: config, ProfileNameTemplate: profileNameTemplate, NoCredentialProcess: noCredentialProcess, Prefix: prefix, PruneStartURLs: pruneStartURLs, } for _, s := range c.StringSlice("source") { switch s { case "aws-sso": g.AddSource(AWSSSOSource{SSORegion: ssoRegion, StartURL: startURL, SSOScopes: c.StringSlice("sso-scope")}) case "commonfate", "common-fate", "cf": ps, err := getCFProfileSource(c, ssoRegion, startURL) if err != nil { return err } g.AddSource(ps) default: return fmt.Errorf("unknown profile source %s: allowed sources are aws-sso, commonfate", s) } } err = g.Generate(ctx) if err != nil { return err } err = config.SaveTo(configFilename) if err != nil { return err } return nil }, }
View Source
var RemoveCredentialsCommand = cli.Command{ Name: "remove", Usage: "Remove credentials from secure storage and an associated profile if it exists in the AWS config file", ArgsUsage: "[<profile>]", Flags: []cli.Flag{ &cli.BoolFlag{Name: "all", Aliases: []string{"a"}, Usage: "Remove all credentials from secure storage and an associated profile if it exists in the AWS config file"}, }, Action: func(c *cli.Context) error { secureIAMCredentialStorage := securestorage.NewSecureIAMCredentialStorage() configPath := cfaws.GetAWSConfigPath() configFile, err := ini.LoadSources(ini.LoadOptions{ AllowNonUniqueSections: false, SkipUnrecognizableLines: false, AllowNestedValues: true, }, configPath) if err != nil { return err } profileName := c.Args().First() secureProfileKeys, err := secureIAMCredentialStorage.SecureStorage.ListKeys() if err != nil { return err } var profileNames []string if c.Bool("all") { profileNames = append(profileNames, secureProfileKeys...) } else { if profileName == "" && len(secureProfileKeys) == 0 { fmt.Println("No credentials in secure storage") return nil } if profileName == "" { in := survey.Select{Message: "Profile Name:", Options: secureProfileKeys} err = testable.AskOne(&in, &profileName) if err != nil { return err } } profileNames = append(profileNames, profileName) } fmt.Printf(`Removing credentials from secure storage will cause them to be permanently deleted. To avoid losing your credentials you may first want to export them to plaintext using 'granted credentials export-plaintext <profile name>' This command will remove a profile with the same name from the AWS config file if it has a 'credential_process = granted credential-process --profile=<profile name>' If you have already used 'granted credentials export-plaintext <profile name>' to export the credentials, the profile will not be removed by this command. `) var confirm bool s := &survey.Confirm{ Message: "Are you sure you want to remove these credentials and profile from your AWS config?", Default: true, } err = survey.AskOne(s, &confirm) if err != nil { return err } if !confirm { fmt.Printf("Cancelled clearing credentials\n") return nil } for _, profileName := range profileNames { fmt.Printf("Removing %s credentials from secure storage\n", profileName) err = secureIAMCredentialStorage.SecureStorage.Clear(profileName) if err != nil { return err } sectionName := "profile " + profileName if section, _ := configFile.GetSection(sectionName); section != nil { if key, _ := section.GetKey("credential_process"); key != nil { if strings.HasPrefix(key.Value(), fmt.Sprintf("%s credential-process", build.GrantedBinaryName())) { fmt.Printf("Removing profile %s AWS config file\n", profileName) configFile.DeleteSection(sectionName) } } } } err = configFile.SaveTo(configPath) if err != nil { return err } fmt.Printf("Cleared credentials from secure storage\n") return nil }, }
View Source
var RotateCredentialsCommand = cli.Command{ Name: "rotate", Usage: "Generates new access key for the profile in AWS, and updates the profile", Flags: []cli.Flag{ &cli.StringFlag{Name: "profile", Usage: "If provided, generates new access key for the specified profile"}, &cli.BoolFlag{Name: "delete", Usage: "delete the previous active key"}, }, Action: func(c *cli.Context) error { profileName := c.String("profile") secureIAMCredentialStorage := securestorage.NewSecureIAMCredentialStorage() if profileName == "" { profileNames, err := secureIAMCredentialStorage.SecureStorage.ListKeys() if err != nil { return err } if len(profileNames) == 0 { fmt.Println("No credentials in secure storage") return nil } in := survey.Select{Message: "Profile Name:", Options: profileNames} err = testable.AskOne(&in, &profileName) if err != nil { return err } } has, err := secureIAMCredentialStorage.SecureStorage.HasKey(profileName) if err != nil { return err } if !has { return fmt.Errorf("no credentials exist for %s in secure storage. If you wanted to add a new profile, run '%s credentials add'", profileName, build.GrantedBinaryName()) } var t aws.Credentials err = secureIAMCredentialStorage.SecureStorage.Retrieve(profileName, &t) if err != nil { return err } opts := []func(*config.LoadOptions) error{ config.WithSharedConfigProfile(profileName), } cfg, err := config.LoadDefaultConfig(c.Context, opts...) if err != nil { return err } iamClient := iam.NewFromConfig(cfg) res, err := iamClient.CreateAccessKey(c.Context, &iam.CreateAccessKeyInput{}) if err != nil { return err } err = secureIAMCredentialStorage.StoreCredentials(profileName, aws.Credentials{AccessKeyID: *res.AccessKey.AccessKeyId, SecretAccessKey: *res.AccessKey.SecretAccessKey}) if err != nil { return err } _, err = iamClient.UpdateAccessKey(c.Context, &iam.UpdateAccessKeyInput{AccessKeyId: &t.AccessKeyID, Status: "Inactive"}) if err != nil { return err } if c.Bool("delete") { _, err = iamClient.DeleteAccessKey(c.Context, &iam.DeleteAccessKeyInput{AccessKeyId: &t.AccessKeyID}) if err != nil { return err } } clio.Successf("Access Key of '%s' profile has been successfully rotated and updated in secure storage\n", profileName) return nil }, }
View Source
var SSOCommand = cli.Command{ Name: "sso", Usage: "Manage your local AWS configuration file from information available in AWS SSO", Subcommands: []*cli.Command{&GenerateCommand, &PopulateCommand, &LoginCommand}, }
View Source
var SSOTokensCommand = cli.Command{ Name: "sso-tokens", Usage: "Manage AWS SSO tokens", Subcommands: []*cli.Command{&ListSSOTokensCommand, &ClearSSOTokensCommand, &TokenExpiryCommand}, Action: ListSSOTokensCommand.Action, }
View Source
var SetBrowserCommand = cli.Command{ Name: "set", Usage: "Change the web browser that Granted uses to open cloud consoles", Flags: []cli.Flag{&cli.StringFlag{Name: "browser", Aliases: []string{"b"}, Usage: "Specify a default browser without prompts, e.g `-b firefox`, `-b chrome`"}, &cli.StringFlag{Name: "path", Aliases: []string{"p"}, Usage: "Specify a path to the browser without prompts, requires -browser to be provided"}}, Action: func(c *cli.Context) (err error) { outcome := c.String("browser") path := c.String("path") if outcome == "" { if path != "" { clio.Info("-path flag must be used with -browser flag, provided path will be ignored") } outcome, err = browser.HandleManualBrowserSelection() if err != nil { return err } } return browser.ConfigureBrowserSelection(outcome, path) }, }
View Source
var SetSSOBrowserCommand = cli.Command{ Name: "set-sso", Usage: "Change the web browser that Granted uses to sso flows", Flags: []cli.Flag{&cli.StringFlag{Name: "browser", Aliases: []string{"b"}, Usage: "Specify a default browser without prompts, e.g `-b firefox`, `-b chrome`"}, &cli.StringFlag{Name: "path", Aliases: []string{"p"}, Usage: "Specify a path to the browser without prompts, requires -browser to be provided"}}, Action: func(c *cli.Context) (err error) { outcome := c.String("browser") path := c.String("path") conf, err := config.Load() if err != nil { return err } var browserPath string if outcome == "" { if path != "" { clio.Info("-path flag must be used with -browser flag, provided path will be ignored") } customBrowserPath, err := browser.AskAndGetBrowserPath() if err != nil { return err } browserPath = customBrowserPath } conf.CustomSSOBrowserPath = browserPath err = conf.Save() if err != nil { return err } clio.Successf("Granted will default to using %s for SSO flows.", browserPath) return nil }, }
View Source
var TokenCommand = cli.Command{ Name: "token", Usage: "Deprecated: Use 'sso-tokens' instead", Action: func(ctx *cli.Context) error { fmt.Println("The 'token' command has been deprecated and will be removed in a future release, it has been renamed to 'sso-tokens'") return SSOTokensCommand.Run(ctx) }, }
TokenCommand has been deprecated in favour of 'sso-tokens' @TODO: remove this when suitable after deprecation
View Source
var TokenExpiryCommand = cli.Command{ Name: "expiry", Usage: "Lists expiry status for all access tokens saved in the keyring", Flags: []cli.Flag{&cli.StringFlag{Name: "url", Usage: "If provided, prints the expiry of the token for the specific SSO URL"}, &cli.BoolFlag{Name: "json", Usage: "If provided, prints the expiry of the tokens in JSON"}}, Action: func(c *cli.Context) error { url := c.String("url") ctx := c.Context secureSSOTokenStorage := securestorage.NewSecureSSOTokenStorage() if url != "" { token := secureSSOTokenStorage.GetValidSSOToken(ctx, url) var expiry string if token == nil { return errors.New("SSO token is expired") } expiry = token.Expiry.Local().Format(time.RFC3339) fmt.Println(expiry) return nil } startUrlMap, err := MapTokens(ctx) if err != nil { return err } var max int for k := range startUrlMap { if len(k) > max { max = len(k) } } keys, err := secureSSOTokenStorage.SecureStorage.ListKeys() if err != nil { return err } jsonflag := c.Bool("json") type sso_expiry struct { StartURLs string `json:"start_urls"` ExpiresAt string `json:"expires_at"` IsExpired bool `json:"is_expired"` } var jsonDataArray []sso_expiry for _, key := range keys { token := secureSSOTokenStorage.GetValidSSOToken(ctx, key) var expiry string if token == nil { expiry = "EXPIRED" } else { expiry = token.Expiry.Local().Format(time.RFC3339) } if jsonflag { sso_expiry_data := sso_expiry{ StartURLs: key, ExpiresAt: expiry, IsExpired: expiry == "EXPIRED", } jsonDataArray = append(jsonDataArray, sso_expiry_data) } else { clio.Logf("%-*s (%s) expires at: %s", max, key, strings.Join(startUrlMap[key], ", "), expiry) } } if jsonflag { jsonData, err := json.Marshal(jsonDataArray) if err != nil { return err } fmt.Println(string(jsonData)) } return nil }, }
View Source
var UninstallCommand = cli.Command{ Name: "uninstall", Usage: "Remove all Granted configuration", Action: func(c *cli.Context) error { withStdio := survey.WithStdio(os.Stdin, os.Stderr, os.Stderr) in := &survey.Confirm{ Message: "Are you sure you want to remove your Granted config?", Default: true, } var confirm bool err := survey.AskOne(in, &confirm, withStdio) if err != nil { return err } if confirm { err = alias.UninstallDefaultShellAlias() if err != nil { clio.Error(err.Error()) } grantedFolder, err := config.GrantedConfigFolder() if err != nil { return err } err = os.RemoveAll(grantedFolder) if err != nil { return err } clio.Successf("Removed Granted config folder %s\n", grantedFolder) } return nil }, }
View Source
var UpdateCredentialsCommand = cli.Command{ Name: "update", Usage: "Update existing credentials in secure storage", ArgsUsage: "[<profile>]", Action: func(c *cli.Context) error { profileName := c.Args().First() secureIAMCredentialStorage := securestorage.NewSecureIAMCredentialStorage() if profileName == "" { profileNames, err := secureIAMCredentialStorage.SecureStorage.ListKeys() if err != nil { return err } if profileName == "" && len(profileNames) == 0 { fmt.Println("No credentials in secure storage") return nil } in := survey.Select{Message: "Profile Name:", Options: profileNames} err = testable.AskOne(&in, &profileName) if err != nil { return err } } has, err := secureIAMCredentialStorage.SecureStorage.HasKey(profileName) if err != nil { return err } if !has { return fmt.Errorf("no credentials exist for %s in secure storage. If you wanted to add a new profile, run '%s credentials add'", profileName, build.GrantedBinaryName()) } credentials, err := promptCredentials() if err != nil { return err } err = secureIAMCredentialStorage.StoreCredentials(profileName, credentials) if err != nil { return err } fmt.Printf("Updated %s in secure storage\n", profileName) return nil }, }
Functions ¶
Types ¶
type AWSSSOSource ¶ added in v0.8.0
func (AWSSSOSource) GetProfiles ¶ added in v0.8.0
func (s AWSSSOSource) GetProfiles(ctx context.Context) ([]awsconfigfile.SSOProfile, error)
type AutoCompleteTemplateData ¶ added in v0.2.1
type AutoCompleteTemplateData struct {
Program string
}
Source Files ¶
Directories ¶
Path | Synopsis |
---|---|
Package awsmerge contains logic to merge multiple AWS config files together.
|
Package awsmerge contains logic to merge multiple AWS config files together. |
Package exp holds experimental commands.
|
Package exp holds experimental commands. |
Click to show internal directories.
Click to hide internal directories.