Skip to content

Commit fadca60

Browse files
authored
feat!: add GRANTED_BROWSER_PROFILE env var support for browser profile override (#914)
* feat!: add GRANTED_BROWSER_PROFILE env var support for browser profile override Add support for GRANTED_BROWSER_PROFILE environment variable to override the browser profile used when opening AWS console with -c/-s flags. Changes: - Add EnvVars to browser-profile flag to automatically read from GRANTED_BROWSER_PROFILE environment variable - Remove browser-profile from getConsoleURL condition to prevent env var from inadvertently triggering console URL generation - Browser profile from env var is only used when actually launching browser (after all safety checks) BREAKING CHANGE: The --browser-profile flag no longer triggers browser opening by itself. Users must now explicitly use -c or -s flags along with --browser-profile to open a browser. Previously, passing --browser-profile alone would open a browser window, but now it only sets which browser profile to use when a browser is opened via other flags. This prevents the GRANTED_BROWSER_PROFILE environment variable from inadvertently opening browsers. This allows users to set a default browser profile via environment variable that will be used whenever opening the console, while still allowing --browser-profile flag to take precedence. Fixes #886 * feat: add GRANTED_SSO_BROWSER_PROFILE env var support for SSO browser profile Add support for GRANTED_SSO_BROWSER_PROFILE environment variable to override the browser profile used when launching SSO login flows. When using SSOBrowserLaunchTemplate, the environment variable will be used to specify which browser profile to use for SSO authentication flows. Usage: export GRANTED_SSO_BROWSER_PROFILE="MySSOProfile" assume <profile> # SSO login will use "MySSOProfile" as the browser profile * feat: add --sso-browser-profile flag and GRANTED_SSO_BROWSER_PROFILE env var support - Add --sso-browser-profile flag to assume, granted sso login, generate, and populate commands - Add SSOBrowserProfile field to ConfigOpts to pass browser profile through SSO login flow - Remove redundant environment variable check from idclogin.Login (handled by flag EnvVars) - Add test case for empty browser profile in launcher tests - Support GRANTED_SSO_BROWSER_PROFILE environment variable via flag EnvVars
1 parent 5e655aa commit fadca60

7 files changed

Lines changed: 39 additions & 17 deletions

File tree

pkg/assume/assume.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -280,10 +280,11 @@ func AssumeCommand(c *cli.Context) error {
280280
}
281281

282282
configOpts := cfaws.ConfigOpts{
283-
Duration: time.Hour,
284-
MFATokenCode: assumeFlags.String("mfa-token"),
285-
Args: assumeFlags.StringSlice("pass-through"),
286-
DisableCache: assumeFlags.Bool("no-cache"),
283+
Duration: time.Hour,
284+
MFATokenCode: assumeFlags.String("mfa-token"),
285+
Args: assumeFlags.StringSlice("pass-through"),
286+
DisableCache: assumeFlags.Bool("no-cache"),
287+
SSOBrowserProfile: assumeFlags.String("sso-browser-profile"),
287288
}
288289

289290
// attempt to get session duration from profile
@@ -307,7 +308,7 @@ func AssumeCommand(c *cli.Context) error {
307308

308309
// if getConsoleURL is true, we'll use the AWS federated login to retrieve a URL to access the console.
309310
// depending on how Granted is configured, this is then printed to the terminal or a browser is launched at the URL automatically.
310-
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") != "")
311+
getConsoleURL := !assumeFlags.Bool("env") && ((assumeFlags.Bool("console") || assumeFlags.String("console-destination") != "") || assumeFlags.Bool("active-role") || assumeFlags.String("service") != "" || assumeFlags.Bool("url"))
311312

312313
// this makes it easy for users to copy the actual command and avoid needing to lookup profiles
313314
if !cfg.DisableUsageTips && showRerunCommand {

pkg/assume/entrypoint.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ func GlobalFlags() []cli.Flag {
4545
&cli.StringFlag{Name: "sso-region", Usage: "Use this in conjunction with --sso, the sso-region"},
4646
&cli.StringFlag{Name: "account-id", Usage: "Use this in conjunction with --sso, the account-id"},
4747
&cli.StringFlag{Name: "role-name", Usage: "Use this in conjunction with --sso, the role-name"},
48-
&cli.StringFlag{Name: "browser-profile", Aliases: []string{"bp"}, Usage: "Use a pre-existing profile in your browser"},
48+
&cli.StringFlag{Name: "browser-profile", Aliases: []string{"bp"}, Usage: "Use a pre-existing profile in your browser", EnvVars: []string{"GRANTED_BROWSER_PROFILE"}},
49+
&cli.StringFlag{Name: "sso-browser-profile", Usage: "Use a pre-existing profile in your browser for SSO login", EnvVars: []string{"GRANTED_SSO_BROWSER_PROFILE"}},
4950
&cli.StringFlag{Name: "mfa-token", Usage: "Provide your current MFA token for the role you are assuming to skip being prompted"},
5051
&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"},
5152
&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"},

pkg/cfaws/assumer_aws_sso.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ func (c *Profile) SSOLogin(ctx context.Context, configOpts ConfigOpts) (aws.Cred
192192
if cachedToken == nil && plainTextToken == nil {
193193
newCfg := aws.NewConfig()
194194
newCfg.Region = rootProfile.SSORegion()
195-
newSSOToken, err := idclogin.Login(ctx, *newCfg, rootProfile.SSOStartURL(), rootProfile.SSOScopes())
195+
newSSOToken, err := idclogin.Login(ctx, *newCfg, rootProfile.SSOStartURL(), rootProfile.SSOScopes(), configOpts.SSOBrowserProfile)
196196
if err != nil {
197197
return aws.Credentials{}, err
198198
}

pkg/cfaws/profiles.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type ConfigOpts struct {
2727
ShouldRetryAssuming *bool
2828
MFATokenCode string
2929
DisableCache bool
30+
SSOBrowserProfile string
3031
}
3132

3233
type Profile struct {

pkg/granted/sso.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ var GenerateCommand = cli.Command{
5555
&cli.StringFlag{Name: "sso-region", Usage: "Specify the SSO region"},
5656
&cli.StringSliceFlag{Name: "source", Usage: "The sources to load AWS profiles from (valid values are: 'aws-sso')", Value: cli.NewStringSlice("aws-sso")},
5757
&cli.BoolFlag{Name: "no-credential-process", Usage: "Generate profiles without the Granted credential-process integration"},
58-
&cli.StringFlag{Name: "profile-template", Usage: "Specify profile name template", Value: awsconfigfile.DefaultProfileNameTemplate}},
58+
&cli.StringFlag{Name: "profile-template", Usage: "Specify profile name template", Value: awsconfigfile.DefaultProfileNameTemplate},
59+
&cli.StringFlag{Name: "sso-browser-profile", Usage: "Use a pre-existing profile in your browser for SSO login", EnvVars: []string{"GRANTED_SSO_BROWSER_PROFILE"}},
60+
},
5961
Action: func(c *cli.Context) error {
6062
ctx := c.Context
6163
fullCommand := fmt.Sprintf("%s %s", c.App.Name, c.Command.FullName()) // e.g. 'granted sso populate'
@@ -100,10 +102,11 @@ var GenerateCommand = cli.Command{
100102
Prefix: prefix,
101103
}
102104

105+
ssoBrowserProfile := c.String("sso-browser-profile")
103106
for _, s := range c.StringSlice("source") {
104107
switch s {
105108
case "aws-sso":
106-
g.AddSource(AWSSSOSource{SSORegion: ssoRegion, StartURL: startURL})
109+
g.AddSource(AWSSSOSource{SSORegion: ssoRegion, StartURL: startURL, SSOBrowserProfile: ssoBrowserProfile})
107110
case "commonfate", "common-fate", "cf":
108111
return fmt.Errorf("the common fate profile source is no longer supported: https://www.commonfate.io/blog/winding-down")
109112
default:
@@ -138,6 +141,7 @@ var PopulateCommand = cli.Command{
138141
&cli.BoolFlag{Name: "prune", Usage: "Remove any generated profiles with the 'common_fate_generated_from' key which no longer exist"},
139142
&cli.StringFlag{Name: "profile-template", Usage: "Specify profile name template", Value: awsconfigfile.DefaultProfileNameTemplate},
140143
&cli.BoolFlag{Name: "no-credential-process", Usage: "Generate profiles without the Granted credential-process integration"},
144+
&cli.StringFlag{Name: "sso-browser-profile", Usage: "Use a pre-existing profile in your browser for SSO login", EnvVars: []string{"GRANTED_SSO_BROWSER_PROFILE"}},
141145
},
142146
Action: func(c *cli.Context) error {
143147
ctx := c.Context
@@ -214,10 +218,11 @@ var PopulateCommand = cli.Command{
214218
PruneStartURLs: pruneStartURLs,
215219
}
216220

221+
ssoBrowserProfile := c.String("sso-browser-profile")
217222
for _, s := range c.StringSlice("source") {
218223
switch s {
219224
case "aws-sso":
220-
g.AddSource(AWSSSOSource{SSORegion: ssoRegion, StartURL: startURL, SSOScopes: c.StringSlice("sso-scope")})
225+
g.AddSource(AWSSSOSource{SSORegion: ssoRegion, StartURL: startURL, SSOScopes: c.StringSlice("sso-scope"), SSOBrowserProfile: ssoBrowserProfile})
221226
case "commonfate", "common-fate", "cf":
222227
return fmt.Errorf("the common fate profile source is no longer supported: https://www.commonfate.io/blog/winding-down")
223228
default:
@@ -245,6 +250,7 @@ var LoginCommand = cli.Command{
245250
&cli.StringFlag{Name: "sso-region", Usage: "Specify the SSO region"},
246251
&cli.StringFlag{Name: "sso-start-url", Usage: "Specify the SSO start url"},
247252
&cli.StringSliceFlag{Name: "sso-scope", Usage: "Specify the SSO scopes"},
253+
&cli.StringFlag{Name: "sso-browser-profile", Usage: "Use a pre-existing profile in your browser for SSO login", EnvVars: []string{"GRANTED_SSO_BROWSER_PROFILE"}},
248254
},
249255
Action: func(c *cli.Context) error {
250256
ctx := c.Context
@@ -291,13 +297,14 @@ var LoginCommand = cli.Command{
291297
}
292298

293299
ssoScopes := c.StringSlice("sso-scope")
300+
ssoBrowserProfile := c.String("sso-browser-profile")
294301

295302
cfg := aws.NewConfig()
296303
cfg.Region = ssoRegion
297304

298305
secureSSOTokenStorage := securestorage.NewSecureSSOTokenStorage()
299306

300-
newSSOToken, err := idclogin.Login(ctx, *cfg, ssoStartUrl, ssoScopes)
307+
newSSOToken, err := idclogin.Login(ctx, *cfg, ssoStartUrl, ssoScopes, ssoBrowserProfile)
301308
if err != nil {
302309
return err
303310
}
@@ -311,9 +318,10 @@ var LoginCommand = cli.Command{
311318
}
312319

313320
type AWSSSOSource struct {
314-
SSORegion string
315-
StartURL string
316-
SSOScopes []string
321+
SSORegion string
322+
StartURL string
323+
SSOScopes []string
324+
SSOBrowserProfile string
317325
}
318326

319327
func (s AWSSSOSource) GetProfiles(ctx context.Context) ([]awsconfigfile.SSOProfile, error) {
@@ -348,7 +356,7 @@ func (s AWSSSOSource) GetProfiles(ctx context.Context) ([]awsconfigfile.SSOProfi
348356

349357
if ssoTokenFromSecureCache == nil && ssoTokenFromPlainText == nil {
350358
// otherwise, login with SSO
351-
ssoTokenFromSecureCache, err = idclogin.Login(ctx, cfg, s.StartURL, s.SSOScopes)
359+
ssoTokenFromSecureCache, err = idclogin.Login(ctx, cfg, s.StartURL, s.SSOScopes, s.SSOBrowserProfile)
352360
if err != nil {
353361
return nil, err
354362
}

pkg/idclogin/run.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020
)
2121

2222
// Login contains all the steps to complete a device code flow to retrieve an SSO token
23-
func Login(ctx context.Context, cfg aws.Config, startUrl string, scopes []string) (*securestorage.SSOToken, error) {
23+
func Login(ctx context.Context, cfg aws.Config, startUrl string, scopes []string, browserProfile string) (*securestorage.SSOToken, error) {
2424
ssooidcClient := ssooidc.NewFromConfig(cfg)
2525

2626
// If scopes aren't provided, default to the legacy non-refreshable configuration
@@ -80,7 +80,7 @@ func Login(ctx context.Context, cfg aws.Config, startUrl string, scopes []string
8080
}
8181

8282
// now build the actual command to run - e.g. 'firefox --new-tab <URL>'
83-
args, err := l.LaunchCommand(url, "")
83+
args, err := l.LaunchCommand(url, browserProfile)
8484
if err != nil {
8585
return nil, fmt.Errorf("error building browser launch command: %w", err)
8686
}

pkg/launcher/custom_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@ func TestCustom_LaunchCommand(t *testing.T) {
3535
},
3636
want: []string{"/usr/bin/firefox", "--url", "https://commonfate.io", "--profile", "example"},
3737
},
38+
{
39+
name: "empty_profile",
40+
fields: fields{
41+
Command: "/usr/bin/firefox --url {{.URL}} --profile {{.Profile}}",
42+
},
43+
args: args{
44+
url: "https://commonfate.io",
45+
profile: "",
46+
},
47+
want: []string{"/usr/bin/firefox", "--url", "https://commonfate.io", "--profile", ""},
48+
},
3849
{
3950
name: "with_args",
4051
fields: fields{

0 commit comments

Comments
 (0)