Skip to content

Commit

Permalink
Add support for using Firefox containers with console
Browse files Browse the repository at this point in the history
Normally you can be logged into a single role via the AWS console
which is super annoying.  By using a Firefox plugin, we can use
unique containers for each role and avoid any error messages
from AWS. :)

Fixes: #336
  • Loading branch information
synfinatic committed May 1, 2022
1 parent 8754f5d commit d474947
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 36 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### New Features

* Add support for Firefox Containers #336

### Bug Fixes

* `console` command now works when AWS_PROFILE #332
Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ then this won't work for you.
## What does AWS SSO CLI do?

AWS SSO CLI makes it easy to manage your shell environment variables allowing
you to access the AWS API using CLI tools. Unlike the official AWS tooling,
the `aws-sso` command does not require defining named profiles in your
you to access the AWS API & web console using CLI tools. Unlike the official
AWS tooling, the `aws-sso` command does not require defining named profiles in your
`~/.aws/config` (or anywhere else for that matter) for each and every role you
wish to assume and use.

Expand All @@ -63,6 +63,11 @@ metadata (tags) and exports the necessary [AWS STS Token credentials](
https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html#using-temp-creds-sdk-cli)
to your shell environment.

As part of the goal of improving the end-user experience with AWS SSO, it also
supports using multiple AWS Web Console sessions with Firefox and the [Open URL in
Container](https://addons.mozilla.org/en-US/firefox/addon/open-url-in-container/)
plugin and many other quality of life improvements!

## Demo

Here's a quick demo showing how to select a role to assume in interactive mode
Expand Down
26 changes: 20 additions & 6 deletions cmd/console_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"net/url"
"os/user"
"regexp"
"strings"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
Expand Down Expand Up @@ -155,7 +156,7 @@ func consoleViaEnvVars(ctx *RunContext, duration int32) error {
SecretAccessKey: ctx.Cli.Console.SecretAccessKey,
SessionToken: ctx.Cli.Console.SessionToken,
}
return openConsoleAccessKey(ctx, &creds, duration, region)
return openConsoleAccessKey(ctx, &creds, duration, region, accountid, role)
}

func consoleViaSDK(ctx *RunContext, duration int32) error {
Expand Down Expand Up @@ -200,7 +201,8 @@ func consoleViaSDK(ctx *RunContext, duration int32) error {
SessionToken: aws.ToString(token.Credentials.SessionToken),
}

return openConsoleAccessKey(ctx, &creds, duration, region)
return openConsoleAccessKey(ctx, &creds, duration, region,
rFlat.AccountId, rFlat.RoleName)
}

func consolePrompt(ctx *RunContext) error {
Expand Down Expand Up @@ -263,11 +265,12 @@ func openConsole(ctx *RunContext, awssso *sso.AWSSSO, accountid int64, role stri

creds := GetRoleCredentials(ctx, awssso, accountid, role)

return openConsoleAccessKey(ctx, creds, duration, region)
return openConsoleAccessKey(ctx, creds, duration, region, accountid, role)
}

// openConsoleAccessKey opens the Frederated Console access URL
func openConsoleAccessKey(ctx *RunContext, creds *storage.RoleCredentials, duration int32, region string) error {
func openConsoleAccessKey(ctx *RunContext, creds *storage.RoleCredentials,
duration int32, region string, accountId int64, role string) error {
signin := SigninTokenUrlParams{
SessionDuration: duration * 60,
Session: SessionUrlParams{
Expand Down Expand Up @@ -309,10 +312,21 @@ func openConsoleAccessKey(ctx *RunContext, creds *storage.RoleCredentials, durat
Destination: fmt.Sprintf("https://console.aws.amazon.com/console/home?region=%s", region),
SigninToken: loginResponse.SigninToken,
}
url := login.GetUrl()
awsUrl := login.GetUrl()

if ctx.Settings.FirefoxOpenInContainer {
rFlat, _ := ctx.Settings.Cache.GetRole(utils.MakeRoleARN(accountId, role))
profile, err := rFlat.ProfileName(ctx.Settings)
if err != nil && strings.Contains(profile, "&") {
profile = fmt.Sprintf("%d:%s", accountId, role)
}

// docs don't say it, but you have to URL escape the url value
awsUrl = fmt.Sprintf("ext+container:name=%s&url=%s", profile, url.QueryEscape(awsUrl))
}

urlOpener := utils.NewHandleUrl(ctx.Settings.UrlAction, ctx.Settings.Browser, ctx.Settings.UrlExecCommand)
return urlOpener.Open(url,
return urlOpener.Open(awsUrl,
"Please open the following URL in your browser:\n\n", "\n\n")
}

Expand Down
1 change: 1 addition & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ var DEFAULT_CONFIG map[string]interface{} = map[string]interface{}{
"UrlExecCommand": "",
"LogLevel": "warn",
"DefaultSSO": "Default",
"FirefoxOpenContainer": false,
}

type CLI struct {
Expand Down
50 changes: 44 additions & 6 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ UrlActionExec:
- <arg 1>
- <arg N>
- "%s"
FirefoxOpenInContainer: [False|True]
ConsoleDuration: <minutes>

LogLevel: [error|warn|info|debug|trace]
Expand Down Expand Up @@ -165,7 +166,7 @@ If you only have a single AWS SSO instance, then it doesn't really matter what y
but if you have two or more, than `Default` is automatically selected unless you manually
specify it here, on the CLI (`--sso`), or via the `AWS_SSO` environment variable.

## Browser / UrlAction / UrlActionExec
## Browser / UrlAction / UrlActionExec / FirefoxOpenInContainer

`UrlAction` gives you control over how AWS SSO and AWS Console URLs are opened in a browser:

Expand All @@ -178,14 +179,42 @@ specify it here, on the CLI (`--sso`), or via the `AWS_SSO` environment variable
If `Browser` is not set, then your default browser will be used and that
your browser needs to support JavaScript for the AWS SSO user interface.

`UrlActionExec` allows you to execute arbitrary commands to handle the URL. The command and arguments
should be specified as a list, with the URL to open specified as the format string `%s`. Only one instance
of `%s` is allowed. Note that YAML requires quotes around strings which start with a [reserved indicator](
https://yaml.org/spec/1.2-old/spec.html#id2774228) like `%`.
`UrlActionExec` is used with `UrlAction=exec` and allows you to execute arbitrary
commands to handle the URL. The command and arguments should be specified as a list,
with the URL to open specified as the format string `%s`. Only one instance
of `%s` is allowed. Note that YAML requires quotes around strings which start
with a [reserved indicator]( https://yaml.org/spec/1.2-old/spec.html#id2774228) like `%`.

Example:
`FirefoxOpenInContainer` is used with `UrlAction=exec` and [Firefox](
https://getfirefox.com) with the [Firefox Open URL in Container](
https://addons.mozilla.org/en-US/firefox/addon/open-url-in-container/) plugin. This causes
the generated URL to be of the format:

```
ext+container:name=<ProfileName>&url=<AWS Console URL>
```

The result is that each account/role has a dedicated Firefox container named after the _ProfileName_
so that you can be logged in across multiple AWS accounts/roles without getting
an error from AWS.

**Note:** If your `ProfileFormat` generates a _ProfileName_ with an `&`, then
`{{ .AccountId }}:{{ .RoleName }}` will be used as the container name instead.

**Note for MacOS users:** This feature does not work with the `open` command, so you should
specify `/Applications/Firefox.app/Contents/MacOS/firefox` as the command to execute.


Examples:

```yaml
# Open the AWS Console in your default browser
UrlAction=open
```

```yaml
# Open the AWS Console using Brave on MacOS
UrlAction=exec
UrlActionExec:
- open
- -a
Expand All @@ -194,6 +223,15 @@ UrlActionExec:
- "%s"
```

```
# Open the AWS Console in a Firefox container on MacOS
UrlAction=exec
FirefoxOpenInContainer: True
UrlActionExec:
- /Applications/Firefox.app/Contents/MacOS/firefox
- "%s"
```
## LogLevel / LogLines
By default, the `LogLevel` is 'warn'. You can override it here or via `--log-level` with one
Expand Down
45 changes: 23 additions & 22 deletions sso/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,28 +42,29 @@ const (
)

type Settings struct {
configFile string // name of this file
cacheFile string // name of cache file; always passed in via CLI args
Cache *Cache `yaml:"-"` // our cache data
SSO map[string]*SSOConfig `koanf:"SSOConfig" yaml:"SSOConfig,omitempty"`
DefaultSSO string `koanf:"DefaultSSO" yaml:"DefaultSSO,omitempty"` // specify default SSO by key
SecureStore string `koanf:"SecureStore" yaml:"SecureStore,omitempty"` // json or keyring
DefaultRegion string `koanf:"DefaultRegion" yaml:"DefaultRegion,omitempty"`
ConsoleDuration int32 `koanf:"ConsoleDuration" yaml:"ConsoleDuration,omitempty"`
JsonStore string `koanf:"JsonStore" yaml:"JsonStore,omitempty"`
UrlAction string `koanf:"UrlAction" yaml:"UrlAction,omitempty"`
Browser string `koanf:"Browser" yaml:"Browser,omitempty"`
UrlExecCommand interface{} `koanf:"UrlExecCommand" yaml:"UrlExecCommand,omitempty"` // string or list
ProfileFormat string `koanf:"ProfileFormat" yaml:"ProfileFormat,omitempty"`
AccountPrimaryTag []string `koanf:"AccountPrimaryTag" yaml:"AccountPrimaryTag,omitempty"`
PromptColors PromptColors `koanf:"PromptColors" yaml:"PromptColors,omitempty"` // go-prompt colors
LogLevel string `koanf:"LogLevel" yaml:"LogLevel,omitempty"`
LogLines bool `koanf:"LogLines" yaml:"LogLines,omitempty"`
HistoryLimit int64 `koanf:"HistoryLimit" yaml:"HistoryLimit,omitempty"`
HistoryMinutes int64 `koanf:"HistoryMinutes" yaml:"HistoryMinutes,omitempty"`
ListFields []string `koanf:"ListFields" yaml:"ListFields,omitempty"`
ConfigVariables map[string]interface{} `koanf:"ConfigVariables" yaml:"ConfigVariables,omitempty"`
EnvVarTags []string `koanf:"EnvVarTags" yaml:"EnvVarTags,omitempty"`
configFile string // name of this file
cacheFile string // name of cache file; always passed in via CLI args
Cache *Cache `yaml:"-"` // our cache data
SSO map[string]*SSOConfig `koanf:"SSOConfig" yaml:"SSOConfig,omitempty"`
DefaultSSO string `koanf:"DefaultSSO" yaml:"DefaultSSO,omitempty"` // specify default SSO by key
SecureStore string `koanf:"SecureStore" yaml:"SecureStore,omitempty"` // json or keyring
DefaultRegion string `koanf:"DefaultRegion" yaml:"DefaultRegion,omitempty"`
ConsoleDuration int32 `koanf:"ConsoleDuration" yaml:"ConsoleDuration,omitempty"`
JsonStore string `koanf:"JsonStore" yaml:"JsonStore,omitempty"`
UrlAction string `koanf:"UrlAction" yaml:"UrlAction,omitempty"`
Browser string `koanf:"Browser" yaml:"Browser,omitempty"`
UrlExecCommand interface{} `koanf:"UrlExecCommand" yaml:"UrlExecCommand,omitempty"` // string or list
ProfileFormat string `koanf:"ProfileFormat" yaml:"ProfileFormat,omitempty"`
AccountPrimaryTag []string `koanf:"AccountPrimaryTag" yaml:"AccountPrimaryTag,omitempty"`
PromptColors PromptColors `koanf:"PromptColors" yaml:"PromptColors,omitempty"` // go-prompt colors
LogLevel string `koanf:"LogLevel" yaml:"LogLevel,omitempty"`
LogLines bool `koanf:"LogLines" yaml:"LogLines,omitempty"`
HistoryLimit int64 `koanf:"HistoryLimit" yaml:"HistoryLimit,omitempty"`
HistoryMinutes int64 `koanf:"HistoryMinutes" yaml:"HistoryMinutes,omitempty"`
ListFields []string `koanf:"ListFields" yaml:"ListFields,omitempty"`
ConfigVariables map[string]interface{} `koanf:"ConfigVariables" yaml:"ConfigVariables,omitempty"`
EnvVarTags []string `koanf:"EnvVarTags" yaml:"EnvVarTags,omitempty"`
FirefoxOpenInContainer bool `koanf:"FirefoxOpenInContainer" yaml:"FirefoxOpenInContainer"`
}

type SSOConfig struct {
Expand Down

0 comments on commit d474947

Please sign in to comment.