granted

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 15, 2022 License: MIT Imports: 36 Imported by: 0

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 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]
		}

		err = clearToken(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://granted.dev/autocompletion")
		}
		return err
	},

	Description: "Install completions for fish, zsh, or bash. To install completions for other shells, please see our docs:\nhttps://granted.dev/autocompletion\n",
}
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"}},
	Action: func(c *cli.Context) error {

		profileName := c.String("profile")
		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
		}

		creds, err := profile.AssumeTerminal(c.Context, cfaws.ConfigOpts{Duration: duration})
		if err != nil {
			return err
		}

		out := awsCredsStdOut{
			Version:         1,
			AccessKeyID:     creds.AccessKeyID,
			SecretAccessKey: creds.SecretAccessKey,
			SessionToken:    creds.SessionToken,
		}
		if creds.CanExpire {
			out.Expiration = creds.Expires.Format(time.RFC3339)
		}

		jsonOut, err := json.Marshal(out)
		if err != nil {
			return errors.Wrap(err, "marshalling session credentials")
		}

		fmt.Println(string(jsonOut))
		return nil
	},
}
View Source
var CredentialsCommand = cli.Command{
	Name:        "credentials",
	Usage:       "Manage secure IAM credentials",
	Subcommands: []*cli.Command{&AddCredentialsCommand, &ImportCredentialsCommand, &UpdateCredentialsCommand, &ListCredentialsCommand, &RemoveCredentialsCommand, &ExportCredentialsCommand},
}
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 := config.DefaultSharedCredentialsFilename()
			credentialsFile, err := ini.LoadSources(ini.LoadOptions{
				AllowNonUniqueSections:  false,
				SkipUnrecognizableLines: false,
			}, 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 := config.DefaultSharedConfigFilename()
			configFile, err := ini.LoadSources(ini.LoadOptions{
				AllowNonUniqueSections:  false,
				SkipUnrecognizableLines: false,
			}, 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: "prefix", Usage: "Specify a prefix for all generated profile names"}, &cli.StringFlag{Name: "region", Usage: "Specify the SSO region", DefaultText: "us-east-1"}},
	Action: func(c *cli.Context) error {
		options, err := parseCliOptions(c)
		if err != nil {
			return err
		}
		ssoProfiles, err := listSSOProfiles(c.Context, ListSSOProfilesInput{
			StartUrl:  options.StartUrl,
			SSORegion: options.SSORegion,
		})
		if err != nil {
			return err
		}
		config := ini.Empty()
		err = mergeSSOProfiles(config, options.Prefix, ssoProfiles)
		if err != nil {
			return err
		}
		_, err = config.WriteTo(os.Stdout)
		return err
	},
}
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 := config.DefaultSharedCredentialsFilename()
		credentialsFile, err := ini.LoadSources(ini.LoadOptions{
			AllowNonUniqueSections:  false,
			SkipUnrecognizableLines: false,
		}, credentialsFilePath)
		if err != nil {
			return err
		}

		items, err := credentialsFile.GetSection(profileName)
		if err != nil {
			return err
		}

		configPath := config.DefaultSharedConfigFilename()
		configFile, err := ini.LoadSources(ini.LoadOptions{
			AllowNonUniqueSections:  false,
			SkipUnrecognizableLines: false,
		}, 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 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 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: "prefix", Usage: "Specify a prefix for all generated profile names"}, &cli.StringFlag{Name: "region", Usage: "Specify the SSO region", DefaultText: "us-east-1"}},
	Action: func(c *cli.Context) error {
		options, err := parseCliOptions(c)
		if err != nil {
			return err
		}

		ssoProfiles, err := listSSOProfiles(c.Context, ListSSOProfilesInput{
			StartUrl:  options.StartUrl,
			SSORegion: options.SSORegion,
		})
		if err != nil {
			return err
		}

		configFilename := config.DefaultSharedConfigFilename()

		config, err := ini.LoadSources(ini.LoadOptions{
			AllowNonUniqueSections:  false,
			SkipUnrecognizableLines: false,
		}, configFilename)
		if err != nil {
			if !os.IsNotExist(err) {
				return err
			}
			config = ini.Empty()
		}
		if err := mergeSSOProfiles(config, options.Prefix, ssoProfiles); 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 := config.DefaultSharedConfigFilename()
		configFile, err := ini.LoadSources(ini.LoadOptions{
			AllowNonUniqueSections:  false,
			SkipUnrecognizableLines: false,
		}, 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 SSOCommand = cli.Command{
	Name:        "sso",
	Usage:       "Manage your local AWS configuration file from information available in AWS SSO",
	Subcommands: []*cli.Command{&GenerateCommand, &PopulateCommand},
}
View Source
var SSOTokensCommand = cli.Command{
	Name:        "sso-tokens",
	Usage:       "Manage AWS SSO tokens",
	Subcommands: []*cli.Command{&ListSSOTokensCommand, &ClearSSOTokensCommand},
	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 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

func GetCliApp

func GetCliApp() *cli.App

func MapTokens added in v0.1.8

func MapTokens(ctx context.Context) (map[string][]string, error)

Types

type AutoCompleteTemplateData added in v0.2.1

type AutoCompleteTemplateData struct {
	Program string
}

type ListSSOProfilesInput added in v0.3.0

type ListSSOProfilesInput struct {
	SSORegion string
	StartUrl  string
}

type SSOCommonOptions added in v0.3.0

type SSOCommonOptions struct {
	Prefix    string
	StartUrl  string
	SSORegion string
}

type SSOProfile added in v0.3.0

type SSOProfile struct {
	// SSO details
	StartUrl  string
	SSORegion string
	// Account and role details
	AccountId   string
	AccountName string
	RoleName    string
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL