From 058ea6cc58e86528e08e5b1c847ca4357545eeec Mon Sep 17 00:00:00 2001 From: meyerjrr <33018450+meyerjrr@users.noreply.github.com> Date: Tue, 24 Sep 2024 16:44:16 +1000 Subject: [PATCH] implement custom templated launch for sso browser option (#750) * implement custom templated launch for sso browser option * fix typo * update custom browser logic to handle custom cases for google chrome --- pkg/assume/entrypoint.go | 1 + pkg/config/config.go | 7 ++++++- pkg/granted/console.go | 2 +- pkg/idclogin/run.go | 44 +++++++++++++++++++++++++++++++++++++++- pkg/launcher/custom.go | 24 +++++++++++++++++++++- 5 files changed, 74 insertions(+), 4 deletions(-) diff --git a/pkg/assume/entrypoint.go b/pkg/assume/entrypoint.go index 32f86660..c85c2377 100644 --- a/pkg/assume/entrypoint.go +++ b/pkg/assume/entrypoint.go @@ -116,6 +116,7 @@ func GetCliApp() *cli.App { if err != nil { return err } + if !hasSetup { browserName, err := browser.HandleBrowserWizard(c) if err != nil { diff --git a/pkg/config/config.go b/pkg/config/config.go index 7c4f8ac0..f1933dbd 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -52,6 +52,11 @@ type Config struct { // and CustomBrowserPath fields. AWSConsoleBrowserLaunchTemplate *BrowserLaunchTemplate `toml:",omitempty"` + // SSOBrowserLaunchTemplate is an optional launch template to use + // for opening the SSO auth flow. If specified it overrides the DefaultBrowser + // and CustomSSOBrowserPath fields. + SSOBrowserLaunchTemplate *BrowserLaunchTemplate `toml:",omitempty"` + Keyring *KeyringConfig `toml:",omitempty"` Ordering string ExportCredentialSuffix string @@ -290,7 +295,7 @@ func Load() (*Config, error) { _, err = toml.NewDecoder(file).Decode(&c) if err != nil { // if there is an error just reset the file - return &c, nil + return nil, err } return &c, nil } diff --git a/pkg/granted/console.go b/pkg/granted/console.go index 60ab88c7..9fb89953 100644 --- a/pkg/granted/console.go +++ b/pkg/granted/console.go @@ -128,7 +128,7 @@ var ConsoleCommand = cli.Command{ } 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()), + return clierr.New(fmt.Sprintf("Granted was unable to open a browser session automatically due to the following error: %s", startErr.Error()), // allow them to try open the url manually clierr.Info("You can open the browser session manually using the following url:"), clierr.Info(consoleURL), diff --git a/pkg/idclogin/run.go b/pkg/idclogin/run.go index 0e2e92f0..eedf6b8f 100644 --- a/pkg/idclogin/run.go +++ b/pkg/idclogin/run.go @@ -2,13 +2,18 @@ package idclogin import ( "context" + "errors" + "fmt" "os/exec" "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ssooidc" "github.com/common-fate/clio" + "github.com/common-fate/clio/clierr" grantedConfig "github.com/common-fate/granted/pkg/config" + "github.com/common-fate/granted/pkg/forkprocess" + "github.com/common-fate/granted/pkg/launcher" "github.com/common-fate/granted/pkg/securestorage" "github.com/pkg/browser" ) @@ -54,7 +59,44 @@ func Login(ctx context.Context, cfg aws.Config, startUrl string, scopes []string return nil, err } - if config.CustomSSOBrowserPath != "" { + if config.SSOBrowserLaunchTemplate != nil { + l, err := launcher.CustomFromLaunchTemplate(config.SSOBrowserLaunchTemplate, []string{}) + if err == launcher.ErrLaunchTemplateNotConfigured { + return nil, errors.New("error configuring custom browser, ensure that [SSOBrowserLaunchTemplate] is specified in your Granted config file") + } + if err != nil { + return nil, err + } + + // now build the actual command to run - e.g. 'firefox --new-tab ' + args, err := l.LaunchCommand(url, "") + if err != nil { + return nil, fmt.Errorf("error building browser launch command: %w", err) + } + + var startErr error + if l.UseForkProcess() { + clio.Debugf("running command using forkprocess: %s", args) + cmd, err := forkprocess.New(args...) + if err != nil { + return nil, 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 nil, clierr.New(fmt.Sprintf("Granted was unable to open a browser session automatically due to the following error: %s", startErr.Error()), + // allow them to try open the url manually + clierr.Info("You can open the browser session manually using the following url:"), + clierr.Info(url), + ) + } + + } else if config.CustomSSOBrowserPath != "" { cmd := exec.Command(config.CustomSSOBrowserPath, url) err = cmd.Start() if err != nil { diff --git a/pkg/launcher/custom.go b/pkg/launcher/custom.go index 18c30ccb..67c2bbf6 100644 --- a/pkg/launcher/custom.go +++ b/pkg/launcher/custom.go @@ -3,6 +3,7 @@ package launcher import ( "errors" "fmt" + "regexp" "strings" "text/template" @@ -52,10 +53,31 @@ func (l Custom) LaunchCommand(url string, profile string) ([]string, error) { return nil, fmt.Errorf("executing command template (check that your browser launch template is valid in your Granted config): %w", err) } - commandParts := strings.Fields(renderedCommand.String()) + commandParts := splitCommand(renderedCommand.String()) return commandParts, nil } +// splits each component of the command. Anything within quotes will be handled as one component of the command +// eg open -a "Google Chrome" returns ["open", "-a", "Google Chrome", ""] +func splitCommand(command string) []string { + + re := regexp.MustCompile(`"([^"]+)"|(\S+)`) + matches := re.FindAllStringSubmatch(command, -1) + + var result []string + for _, match := range matches { + + if match[1] != "" { + result = append(result, match[1]) + } else { + + result = append(result, match[2]) + } + } + + return result +} + func (l Custom) UseForkProcess() bool { return l.ForkProcess } var ErrLaunchTemplateNotConfigured = errors.New("launch template is not configured")