Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ability to specify multiple services top be opened with the --console flag #827

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 84 additions & 74 deletions pkg/assume/assume.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,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.StringSlice("service") != nil || assumeFlags.Bool("url") || assumeFlags.String("browser-profile") != "")

// this makes it easy for users to copy the actual command and avoid needing to lookup profiles
if !cfg.DisableUsageTips && showRerunCommand {
Expand All @@ -330,11 +330,15 @@ func AssumeCommand(c *cli.Context) error {
if getConsoleURL {
con := console.AWS{
Profile: profile.Name,
Service: assumeFlags.String("service"),
Region: region,
Destination: assumeFlags.String("console-destination"),
}

services := assumeFlags.StringSlice("service")
con.Service = append(con.Service, services...)

region := assumeFlags.String("region")
con.Region = region

creds, err := profile.AssumeConsole(c.Context, configOpts)
if err != nil && strings.HasPrefix(err.Error(), "no access") {
clio.Debugw("received a No Access error", "error", err)
Expand Down Expand Up @@ -406,92 +410,98 @@ func AssumeCommand(c *cli.Context) error {
containerProfile = assumeFlags.String("browser-profile")
}

consoleURL, err := con.URL(creds)
consoleURLs, err := con.URLs(creds)
if err != nil {
return err
}

if cfg.DefaultBrowser == browser.FirefoxKey || cfg.DefaultBrowser == browser.WaterfoxKey || cfg.DefaultBrowser == browser.FirefoxStdoutKey || cfg.DefaultBrowser == browser.FirefoxDevEditionKey || cfg.DefaultBrowser == browser.FirefoxNightlyKey {
// transform the URL into the Firefox Tab Container format.
consoleURL = fmt.Sprintf("ext+granted-containers:name=%s&url=%s&color=%s&icon=%s", containerProfile, url.QueryEscape(consoleURL), profile.CustomGrantedProperty("color"), profile.CustomGrantedProperty("icon"))
}
clio.Debugf(`number of console urls created`, "amount", len(consoleURLs))
for _, consoleURL := range consoleURLs {

justPrintURL := assumeFlags.Bool("url") || cfg.DefaultBrowser == browser.StdoutKey || cfg.DefaultBrowser == browser.FirefoxStdoutKey
if justPrintURL {
// return the url via stdout through the CLI wrapper script and return early.
fmt.Print(assumeprint.SafeOutput(consoleURL))
return nil
}

browserPath := cfg.CustomBrowserPath
if browserPath == "" && cfg.AWSConsoleBrowserLaunchTemplate == nil {
return errors.New("default browser not configured. run `granted browser set` to configure")
}

var l Launcher
switch cfg.DefaultBrowser {
case browser.ChromeKey, browser.BraveKey, browser.EdgeKey, browser.ChromiumKey, browser.VivaldiKey:
l = launcher.ChromeProfile{
BrowserType: cfg.DefaultBrowser,
ExecutablePath: browserPath,
}
case browser.FirefoxKey, browser.WaterfoxKey:
l = launcher.Firefox{
ExecutablePath: browserPath,
if cfg.DefaultBrowser == browser.FirefoxKey || cfg.DefaultBrowser == browser.WaterfoxKey || cfg.DefaultBrowser == browser.FirefoxStdoutKey || cfg.DefaultBrowser == browser.FirefoxDevEditionKey || cfg.DefaultBrowser == browser.FirefoxNightlyKey {
// transform the URL into the Firefox Tab Container format.
consoleURL = fmt.Sprintf("ext+granted-containers:name=%s&url=%s&color=%s&icon=%s", containerProfile, url.QueryEscape(consoleURL), profile.CustomGrantedProperty("color"), profile.CustomGrantedProperty("icon"))
}
case browser.SafariKey:
l = launcher.Safari{}
case browser.ArcKey:
l = launcher.Arc{}
case browser.FirefoxDevEditionKey:
l = launcher.FirefoxDevEdition{
ExecutablePath: browserPath,
}
case browser.FirefoxNightlyKey:
l = launcher.FirefoxNightly{
ExecutablePath: browserPath,

justPrintURL := assumeFlags.Bool("url") || cfg.DefaultBrowser == browser.StdoutKey || cfg.DefaultBrowser == browser.FirefoxStdoutKey
if justPrintURL {
// return the url via stdout through the CLI wrapper script and return early.
fmt.Print(assumeprint.SafeOutput(consoleURL))
return nil
}
case browser.CustomKey:
l, err = launcher.CustomFromLaunchTemplate(cfg.AWSConsoleBrowserLaunchTemplate, c.StringSlice("browser-launch-template-arg"))
if err == launcher.ErrLaunchTemplateNotConfigured {
return errors.New("error configuring custom browser, ensure that [AWSConsoleBrowserLaunchTemplate] is specified in your Granted config file")

browserPath := cfg.CustomBrowserPath
if browserPath == "" && cfg.AWSConsoleBrowserLaunchTemplate == nil {
return errors.New("default browser not configured. run `granted browser set` to configure")
}
if err != nil {
return err

var l Launcher
switch cfg.DefaultBrowser {
case browser.ChromeKey, browser.BraveKey, browser.EdgeKey, browser.ChromiumKey, browser.VivaldiKey:
l = launcher.ChromeProfile{
BrowserType: cfg.DefaultBrowser,
ExecutablePath: browserPath,
}
case browser.FirefoxKey, browser.WaterfoxKey:
l = launcher.Firefox{
ExecutablePath: browserPath,
}
case browser.SafariKey:
l = launcher.Safari{}
case browser.ArcKey:
l = launcher.Arc{}
case browser.FirefoxDevEditionKey:
l = launcher.FirefoxDevEdition{
ExecutablePath: browserPath,
}
case browser.FirefoxNightlyKey:
l = launcher.FirefoxNightly{
ExecutablePath: browserPath,
}
case browser.CustomKey:
l, err = launcher.CustomFromLaunchTemplate(cfg.AWSConsoleBrowserLaunchTemplate, c.StringSlice("browser-launch-template-arg"))
if err == launcher.ErrLaunchTemplateNotConfigured {
return errors.New("error configuring custom browser, ensure that [AWSConsoleBrowserLaunchTemplate] is specified in your Granted config file")
}
if err != nil {
return err
}
default:
l = launcher.Open{}
}
default:
l = launcher.Open{}
}

printFlagUsage(con.Region, con.Service)
clio.Infof("Opening a console for %s in your browser...", profile.Name)
servicesString := strings.Join(services, " ")
printFlagUsage(con.Region, servicesString)
clio.Debugf("Opening a console for %s in your browser...", profile.Name)

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

var startErr error
if l.UseForkProcess() {
clio.Debugf("running command using forkprocess: %s", args)
cmd, err := forkprocess.New(args...)
args, err := l.LaunchCommand(consoleURL, containerProfile)
if err != nil {
return err
return fmt.Errorf("error building browser launch command: %w", 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", 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),
)
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", 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),
)
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/assume/entrypoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func GlobalFlags() []cli.Flag {
&cli.BoolFlag{Name: "export-sso-token", Aliases: []string{"es"}, Usage: "Export sso token to ~/.aws/sso/cache"},
&cli.BoolFlag{Name: "unset", Aliases: []string{"un"}, Usage: "Unset all environment variables configured by Assume"},
&cli.BoolFlag{Name: "url", Aliases: []string{"u"}, Usage: "Get an active console session url"},
&cli.StringFlag{Name: "service", Aliases: []string{"s"}, Usage: "Like --c, but opens to a specified service"},
&cli.StringSliceFlag{Name: "service", Aliases: []string{"s"}, Usage: "Like --c, but opens to a specified service"},
&cli.StringFlag{Name: "region", Aliases: []string{"r"}, Usage: "region to launch the console or export to the terminal"},
&cli.StringFlag{Name: "console-destination", Aliases: []string{"cd"}, Usage: "Open a web console at this destination"},
&cli.StringSliceFlag{Name: "pass-through", Aliases: []string{"pt"}, Usage: "Pass args to proxy assumer"},
Expand Down
40 changes: 34 additions & 6 deletions pkg/console/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
type AWS struct {
Profile string
Region string
Service string
Service []string
Destination string
}

Expand All @@ -28,11 +28,39 @@ type awsSession struct {
SessionToken string `json:"sessionToken"`
}

func (a AWS) URLs(creds aws.Credentials) ([]string, error) {

urls := []string{}

//if region and service were not specified create a single default url and return
if a.Region == "" && a.Service == nil {
url, err := a.URL(creds, "", "")
if err != nil {
return nil, err
}
urls = append(urls, url)
return urls, nil
}

//if one or more services were specified in the assume command then create multiple urls to be opened up in the browser
if len(a.Service) > 0 {
for _, service := range a.Service {
url, err := a.URL(creds, a.Region, service)
if err != nil {
return nil, err
}
urls = append(urls, url)
}
}

return urls, nil
}

// URL retrieves an authorised access URL for the AWS console. The URL includes a security token which is retrieved
// by exchanging AWS session credentials using the AWS federation endpoint.
//
// see: https://docs.aws.amazon.com/IAM/latest/UserGuide/example_sts_Scenario_ConstructFederatedUrl_section.html
func (a AWS) URL(creds aws.Credentials) (string, error) {
func (a AWS) URL(creds aws.Credentials, region string, service string) (string, error) {
sess := awsSession{
SessionID: creds.AccessKeyID,
SessionKey: creds.SecretAccessKey,
Expand All @@ -43,12 +71,12 @@ func (a AWS) URL(creds aws.Credentials) (string, error) {
return "", err
}

partition := GetPartitionFromRegion(a.Region)
partition := GetPartitionFromRegion(region)
clio.Debugf("Partition is detected as %s for region %s...\n", partition, a.Region)

u := url.URL{
Scheme: "https",
Host: partition.RegionalHostString(a.Region),
Host: partition.RegionalHostString(region),
Path: "/federation",
}
q := u.Query()
Expand All @@ -75,11 +103,11 @@ func (a AWS) URL(creds aws.Credentials) (string, error) {

u = url.URL{
Scheme: "https",
Host: partition.RegionalHostString(a.Region),
Host: partition.RegionalHostString(region),
Path: "/federation",
}

dest, err := makeDestinationURL(a.Service, a.Region, a.Destination)
dest, err := makeDestinationURL(service, region, a.Destination)

if err != nil {
return "", err
Expand Down
Loading