diff --git a/pkg/assume/assume.go b/pkg/assume/assume.go index d14c2d04..e4649c12 100644 --- a/pkg/assume/assume.go +++ b/pkg/assume/assume.go @@ -280,10 +280,11 @@ func AssumeCommand(c *cli.Context) error { } configOpts := cfaws.ConfigOpts{ - Duration: time.Hour, - MFATokenCode: assumeFlags.String("mfa-token"), - Args: assumeFlags.StringSlice("pass-through"), - DisableCache: assumeFlags.Bool("no-cache"), + Duration: time.Hour, + MFATokenCode: assumeFlags.String("mfa-token"), + Args: assumeFlags.StringSlice("pass-through"), + DisableCache: assumeFlags.Bool("no-cache"), + SSOBrowserProfile: assumeFlags.String("sso-browser-profile"), } // attempt to get session duration from profile @@ -307,7 +308,7 @@ func AssumeCommand(c *cli.Context) error { // if getConsoleURL is true, we'll use the AWS federated login to retrieve a URL to access the console. // depending on how Granted is configured, this is then printed to the terminal or a browser is launched at the URL automatically. - getConsoleURL := !assumeFlags.Bool("env") && ((assumeFlags.Bool("console") || assumeFlags.String("console-destination") != "") || assumeFlags.Bool("active-role") || assumeFlags.String("service") != "" || assumeFlags.Bool("url") || assumeFlags.String("browser-profile") != "") + getConsoleURL := !assumeFlags.Bool("env") && ((assumeFlags.Bool("console") || assumeFlags.String("console-destination") != "") || assumeFlags.Bool("active-role") || assumeFlags.String("service") != "" || assumeFlags.Bool("url")) // this makes it easy for users to copy the actual command and avoid needing to lookup profiles if !cfg.DisableUsageTips && showRerunCommand { diff --git a/pkg/assume/entrypoint.go b/pkg/assume/entrypoint.go index 4f22154d..7c623f0c 100644 --- a/pkg/assume/entrypoint.go +++ b/pkg/assume/entrypoint.go @@ -45,7 +45,8 @@ func GlobalFlags() []cli.Flag { &cli.StringFlag{Name: "sso-region", Usage: "Use this in conjunction with --sso, the sso-region"}, &cli.StringFlag{Name: "account-id", Usage: "Use this in conjunction with --sso, the account-id"}, &cli.StringFlag{Name: "role-name", Usage: "Use this in conjunction with --sso, the role-name"}, - &cli.StringFlag{Name: "browser-profile", Aliases: []string{"bp"}, Usage: "Use a pre-existing profile in your browser"}, + &cli.StringFlag{Name: "browser-profile", Aliases: []string{"bp"}, Usage: "Use a pre-existing profile in your browser", EnvVars: []string{"GRANTED_BROWSER_PROFILE"}}, + &cli.StringFlag{Name: "sso-browser-profile", Usage: "Use a pre-existing profile in your browser for SSO login", EnvVars: []string{"GRANTED_SSO_BROWSER_PROFILE"}}, &cli.StringFlag{Name: "mfa-token", Usage: "Provide your current MFA token for the role you are assuming to skip being prompted"}, &cli.StringFlag{Name: "save-to", Usage: "Use this in conjunction with --sso, the profile name to save the role to in your AWS config file"}, &cli.BoolFlag{Name: "export-all-env-vars", Aliases: []string{"x"}, Usage: "Exports all available credentials to the terminal when used with a profile configured for credential-process. Without this flag, only the AWS_PROFILE will be configured"}, diff --git a/pkg/cfaws/assumer_aws_sso.go b/pkg/cfaws/assumer_aws_sso.go index 35a065aa..1d853547 100644 --- a/pkg/cfaws/assumer_aws_sso.go +++ b/pkg/cfaws/assumer_aws_sso.go @@ -192,7 +192,7 @@ func (c *Profile) SSOLogin(ctx context.Context, configOpts ConfigOpts) (aws.Cred if cachedToken == nil && plainTextToken == nil { newCfg := aws.NewConfig() newCfg.Region = rootProfile.SSORegion() - newSSOToken, err := idclogin.Login(ctx, *newCfg, rootProfile.SSOStartURL(), rootProfile.SSOScopes()) + newSSOToken, err := idclogin.Login(ctx, *newCfg, rootProfile.SSOStartURL(), rootProfile.SSOScopes(), configOpts.SSOBrowserProfile) if err != nil { return aws.Credentials{}, err } diff --git a/pkg/cfaws/profiles.go b/pkg/cfaws/profiles.go index 140db9e9..fa2c6ee4 100644 --- a/pkg/cfaws/profiles.go +++ b/pkg/cfaws/profiles.go @@ -27,6 +27,7 @@ type ConfigOpts struct { ShouldRetryAssuming *bool MFATokenCode string DisableCache bool + SSOBrowserProfile string } type Profile struct { diff --git a/pkg/granted/sso.go b/pkg/granted/sso.go index 0af49e28..e3b153f4 100644 --- a/pkg/granted/sso.go +++ b/pkg/granted/sso.go @@ -55,7 +55,9 @@ var GenerateCommand = cli.Command{ &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')", 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}}, + &cli.StringFlag{Name: "profile-template", Usage: "Specify profile name template", Value: awsconfigfile.DefaultProfileNameTemplate}, + &cli.StringFlag{Name: "sso-browser-profile", Usage: "Use a pre-existing profile in your browser for SSO login", EnvVars: []string{"GRANTED_SSO_BROWSER_PROFILE"}}, + }, Action: func(c *cli.Context) error { ctx := c.Context fullCommand := fmt.Sprintf("%s %s", c.App.Name, c.Command.FullName()) // e.g. 'granted sso populate' @@ -100,10 +102,11 @@ var GenerateCommand = cli.Command{ Prefix: prefix, } + ssoBrowserProfile := c.String("sso-browser-profile") for _, s := range c.StringSlice("source") { switch s { case "aws-sso": - g.AddSource(AWSSSOSource{SSORegion: ssoRegion, StartURL: startURL}) + g.AddSource(AWSSSOSource{SSORegion: ssoRegion, StartURL: startURL, SSOBrowserProfile: ssoBrowserProfile}) case "commonfate", "common-fate", "cf": return fmt.Errorf("the common fate profile source is no longer supported: https://www.commonfate.io/blog/winding-down") default: @@ -138,6 +141,7 @@ var PopulateCommand = cli.Command{ &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"}, + &cli.StringFlag{Name: "sso-browser-profile", Usage: "Use a pre-existing profile in your browser for SSO login", EnvVars: []string{"GRANTED_SSO_BROWSER_PROFILE"}}, }, Action: func(c *cli.Context) error { ctx := c.Context @@ -214,10 +218,11 @@ var PopulateCommand = cli.Command{ PruneStartURLs: pruneStartURLs, } + ssoBrowserProfile := c.String("sso-browser-profile") for _, s := range c.StringSlice("source") { switch s { case "aws-sso": - g.AddSource(AWSSSOSource{SSORegion: ssoRegion, StartURL: startURL, SSOScopes: c.StringSlice("sso-scope")}) + g.AddSource(AWSSSOSource{SSORegion: ssoRegion, StartURL: startURL, SSOScopes: c.StringSlice("sso-scope"), SSOBrowserProfile: ssoBrowserProfile}) case "commonfate", "common-fate", "cf": return fmt.Errorf("the common fate profile source is no longer supported: https://www.commonfate.io/blog/winding-down") default: @@ -245,6 +250,7 @@ var LoginCommand = cli.Command{ &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"}, + &cli.StringFlag{Name: "sso-browser-profile", Usage: "Use a pre-existing profile in your browser for SSO login", EnvVars: []string{"GRANTED_SSO_BROWSER_PROFILE"}}, }, Action: func(c *cli.Context) error { ctx := c.Context @@ -291,13 +297,14 @@ var LoginCommand = cli.Command{ } ssoScopes := c.StringSlice("sso-scope") + ssoBrowserProfile := c.String("sso-browser-profile") cfg := aws.NewConfig() cfg.Region = ssoRegion secureSSOTokenStorage := securestorage.NewSecureSSOTokenStorage() - newSSOToken, err := idclogin.Login(ctx, *cfg, ssoStartUrl, ssoScopes) + newSSOToken, err := idclogin.Login(ctx, *cfg, ssoStartUrl, ssoScopes, ssoBrowserProfile) if err != nil { return err } @@ -311,9 +318,10 @@ var LoginCommand = cli.Command{ } type AWSSSOSource struct { - SSORegion string - StartURL string - SSOScopes []string + SSORegion string + StartURL string + SSOScopes []string + SSOBrowserProfile string } func (s AWSSSOSource) GetProfiles(ctx context.Context) ([]awsconfigfile.SSOProfile, error) { @@ -348,7 +356,7 @@ func (s AWSSSOSource) GetProfiles(ctx context.Context) ([]awsconfigfile.SSOProfi if ssoTokenFromSecureCache == nil && ssoTokenFromPlainText == nil { // otherwise, login with SSO - ssoTokenFromSecureCache, err = idclogin.Login(ctx, cfg, s.StartURL, s.SSOScopes) + ssoTokenFromSecureCache, err = idclogin.Login(ctx, cfg, s.StartURL, s.SSOScopes, s.SSOBrowserProfile) if err != nil { return nil, err } diff --git a/pkg/idclogin/run.go b/pkg/idclogin/run.go index 9dfab4e6..1328c4d2 100644 --- a/pkg/idclogin/run.go +++ b/pkg/idclogin/run.go @@ -19,7 +19,7 @@ import ( ) // Login contains all the steps to complete a device code flow to retrieve an SSO token -func Login(ctx context.Context, cfg aws.Config, startUrl string, scopes []string) (*securestorage.SSOToken, error) { +func Login(ctx context.Context, cfg aws.Config, startUrl string, scopes []string, browserProfile string) (*securestorage.SSOToken, error) { ssooidcClient := ssooidc.NewFromConfig(cfg) // If scopes aren't provided, default to the legacy non-refreshable configuration @@ -79,7 +79,7 @@ func Login(ctx context.Context, cfg aws.Config, startUrl string, scopes []string } // now build the actual command to run - e.g. 'firefox --new-tab ' - args, err := l.LaunchCommand(url, "") + args, err := l.LaunchCommand(url, browserProfile) if err != nil { return nil, fmt.Errorf("error building browser launch command: %w", err) } diff --git a/pkg/launcher/custom_test.go b/pkg/launcher/custom_test.go index 1cf9efe8..6adf1f32 100644 --- a/pkg/launcher/custom_test.go +++ b/pkg/launcher/custom_test.go @@ -35,6 +35,17 @@ func TestCustom_LaunchCommand(t *testing.T) { }, want: []string{"/usr/bin/firefox", "--url", "https://commonfate.io", "--profile", "example"}, }, + { + name: "empty_profile", + fields: fields{ + Command: "/usr/bin/firefox --url {{.URL}} --profile {{.Profile}}", + }, + args: args{ + url: "https://commonfate.io", + profile: "", + }, + want: []string{"/usr/bin/firefox", "--url", "https://commonfate.io", "--profile", ""}, + }, { name: "with_args", fields: fields{