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

Add config support and Profile to Roles #225

Merged
merged 1 commit into from
Jan 3, 2022
Merged
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
PROJECT_VERSION := 1.6.1
PROJECT_VERSION := 1.7.0
DOCKER_REPO := synfinatic
PROJECT_NAME := aws-sso

Expand Down
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@
* [Quick Setup](#quick-setup)
* [Security](#security)
* [Commands](#commands)
* [cache](#cache)
* [console](#console)
* [config](#config)
* [eval](#eval)
* [exec](#exec)
* [flush](#flush)
* [list](#list)
* [process](#process)
* [tags](#tags)
* [time](#time)
* [install-autocomplete](#install-autocomplete)
* [Configuration](docs/config.md)
* [Environment Varables](#environment-varables)
* [Release History](#release-history)
Expand Down Expand Up @@ -130,6 +141,7 @@ been granted access!

* [cache](#cache) -- Force refresh of AWS SSO role information
* [console](#console) -- Open AWS Console in a browser with the selected role
* [config](#config) -- Update your `~/.aws/config` file with the AWS profiles in AWS SSO
* [eval](#eval) -- Print shell environment variables for use in your shell
* [exec](#exec) -- Exec a command with the selected role
* [flush](#flush) -- Force delete of cached AWS SSO credentials
Expand Down Expand Up @@ -179,6 +191,33 @@ Priority is given to:
* `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_SESSION_TOKEN` environment variables
* Prompt user interactively

### config

Modifies the `~/.aws/config` file to contain a profile for every role accessible
via AWS SSO CLI.

Flags:

* `--print` -- Print profile entries instead of modifying config file
* `--output` -- Set the default output format.
Must be one of `json`, `yaml`, `yaml-stream`, `text`, `table`. Default is `json`.

This generates a series of [named profile entries](
https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) in the
`~/.aws/config` file which allows you to easily use any AWS SSO role just by setting
the `$AWS_PROFILE` environment variable. By default, each profile is named according
to the [ProfileFormat](docs/config.md#profileformat) config option or overridden by
the user defined [Profile](docs/config.md#profile) option on a role by role basis.

**Note:** You should run this command any time your list of AWS roles changes.

**Note:** It is important that you do _NOT_ remove the `# BEGIN_AWS_SSO_CLI` and
`# END_AWS_SSO_CLI` lines from your config file! These markers are used to track
which profiles are managed by AWS SSO CLI.

**Note:** This command does not honor the `--sso` option as it operates on all
of the configured AWS SSO instances in the `~/.aws-sso/config.yaml` file.

### eval

Generate a series of `export VARIABLE=VALUE` lines suitable for sourcing into your
Expand Down Expand Up @@ -319,12 +358,21 @@ By default the following key/values are available as tags to your roles:
* `History` -- Tag tracking if this role was recently used. See `HistoryLimit`
in config.

### time

Print a string containing the number of hours and minutes that the current
AWS Role's STS credentials are valid for in the format of `HHhMMm`

### install-autocomplete

Configures your appropriate shell configuration file to add auto-complete
functionality for commands, flags and options. Must restart your shell
for this to take effect.

Modifies the following file based on your shell:
* `~/.bash_profile` -- bash
* `~/.zshrc` -- zsh

## Environment Varables

### Honored Variables
Expand Down
91 changes: 91 additions & 0 deletions cmd/config_cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package main

/*
* AWS SSO CLI
* Copyright (c) 2021-2022 Aaron Turner <synfinatic at gmail dot com>
*
* This program is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or with the authors permission any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import (
"fmt"
"os"
"text/template"

log "github.com/sirupsen/logrus"
)

const (
// CONFIG_PREFIX = "# BEGIN_AWS_SSO_CLI"
// CONFIG_SUFFIX = "# END_AWS_SSO_CLI"
CONFIG_TEMPLATE = `
# BEGIN_AWS_SSO_CLI
{{ range . }}
[profile {{ .Profile }}]
credential_process = {{ .BinaryPath }} -u open -S "{{ .Sso }}" process --arn {{ .Arn }}
output={{ .Output }}
{{end}}
# END_AWS_SSO_CLI
`
)

type ProfileConfig struct {
Sso string
Arn string
Profile string
Output string
BinaryPath string
}

type ConfigCmd struct {
Print bool `kong:"help='Print profile entries instead of modifying config file'"`
Output string `kong:"help='Output format [json|yaml|yaml-stream|text|table]',default='json',enum='json,yaml,yaml-stream,text,table'"`
}

func (cc *ConfigCmd) Run(ctx *RunContext) error {
set := ctx.Settings
binaryPath, _ := os.Executable()

// Find all the roles across all of the SSO instances
profiles := []ProfileConfig{}
for ssoName, s := range set.Cache.SSO {
for _, role := range s.Roles.GetAllRoles() {
profile, err := role.ProfileName(ctx.Settings)
if err != nil {
log.Errorf("Unable to generate profile name for %s: %s", role.Arn, err.Error())
}
profiles = append(profiles, ProfileConfig{
Sso: ssoName,
Arn: role.Arn,
Profile: profile,
Output: ctx.Cli.Config.Output,
BinaryPath: binaryPath,
})
}
}

templ, err := template.New("profile").Parse(CONFIG_TEMPLATE)
if err != nil {
return err
}
if ctx.Cli.Config.Print {
if err := templ.Execute(os.Stdout, profiles); err != nil {
return err
}
} else {
return fmt.Errorf("Writing to ~/.aws/config is not yet supported")
}

return nil
}
61 changes: 5 additions & 56 deletions cmd/exec_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,12 @@ package main
*/

import (
"bytes"
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"text/template"

"github.com/c-bata/go-prompt"
"github.com/davecgh/go-spew/spew"
log "github.com/sirupsen/logrus"
"github.com/synfinatic/aws-sso-cli/sso"
"github.com/synfinatic/aws-sso-cli/utils"
Expand Down Expand Up @@ -105,32 +101,6 @@ func (cc *ExecCmd) Run(ctx *RunContext) error {
return nil
}

const (
AwsSsoProfileTemplate = "{{AccountIdStr .AccountId}}:{{.RoleName}}"
)

func emptyString(str string) bool {
return str == ""
}

func firstItem(items ...string) string {
for _, v := range items {
if v != "" {
return v
}
}
return ""
}

func accountIdToStr(id int64) string {
i, _ := utils.AccountIdToString(id)
return i
}

func stringsJoin(x string, items ...string) string {
return strings.Join(items, x)
}

// Executes Cmd+Args in the context of the AWS Role creds
func execCmd(ctx *RunContext, awssso *sso.AWSSSO, accountid int64, role string) error {
region := ctx.Settings.GetDefaultRegion(ctx.Cli.Exec.AccountId, ctx.Cli.Exec.Role, ctx.Cli.Exec.NoRegion)
Expand Down Expand Up @@ -158,6 +128,7 @@ func execCmd(ctx *RunContext, awssso *sso.AWSSSO, accountid int64, role string)
}

func execShellEnvs(ctx *RunContext, awssso *sso.AWSSSO, accountid int64, role, region string) map[string]string {
var err error
credsPtr := GetRoleCredentials(ctx, awssso, accountid, role)
creds := *credsPtr

Expand All @@ -180,39 +151,17 @@ func execShellEnvs(ctx *RunContext, awssso *sso.AWSSSO, accountid int64, role, r
shellVars["AWS_SSO_DEFAULT_REGION"] = ""
}

var profileFormat string = AwsSsoProfileTemplate

funcMap := template.FuncMap{
"AccountIdStr": accountIdToStr,
"EmptyString": emptyString,
"FirstItem": firstItem,
"StringsJoin": stringsJoin,
}

if ctx.Settings.ProfileFormat != "" {
profileFormat = ctx.Settings.ProfileFormat
}

// Set the AWS_SSO_PROFILE env var using our template
var templ *template.Template
cache := ctx.Settings.Cache.GetSSO()
if roleInfo, err := cache.Roles.GetRole(accountid, role); err != nil {
var roleInfo *sso.AWSRoleFlat
if roleInfo, err = cache.Roles.GetRole(accountid, role); err != nil {
// this error should never happen
log.Errorf("Unable to find role in cache. Unable to set AWS_SSO_PROFILE")
} else {
templ, err = template.New("main").Funcs(funcMap).Parse(profileFormat)
shellVars["AWS_SSO_PROFILE"], err = roleInfo.ProfileName(ctx.Settings)
if err != nil {
log.Errorf("Invalid ProfileFormat '%s': %s -- using default", ctx.Settings.ProfileFormat, err)
templ, _ = template.New("main").Funcs(funcMap).Parse(AwsSsoProfileTemplate)
}

buf := new(bytes.Buffer)
log.Tracef("RoleInfo: %s", spew.Sdump(roleInfo))
log.Tracef("Template: %s", spew.Sdump(templ))
if err := templ.Execute(buf, roleInfo); err != nil {
log.WithError(err).Errorf("Unable to generate AWS_SSO_PROFILE")
log.Errorf("Unable to generate AWS_SSO_PROFILE: %s", err.Error())
}
shellVars["AWS_SSO_PROFILE"] = buf.String()
}

return shellVars
Expand Down
7 changes: 6 additions & 1 deletion cmd/list_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ var allListFields = map[string]string{
"RoleName": "AWS Role Name",
"SSO": "AWS SSO Instance Name",
"Via": "Role Chain Via",
// "Profile": "AWS_PROFILE",
"Profile": "AWS_SSO_PROFILE / AWS_PROFILE",
}

type ListCmd struct {
Expand Down Expand Up @@ -118,6 +118,11 @@ func printRoles(ctx *RunContext, fields []string) {
roleFlat.ExpiresStr = exp
}
}
// update Profile
p, err := roleFlat.ProfileName(ctx.Settings)
if err == nil {
roleFlat.Profile = p
}
roleFlat.Id = idx
idx += 1
tr = append(tr, *roleFlat)
Expand Down
1 change: 1 addition & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ type CLI struct {

// Commands
Cache CacheCmd `kong:"cmd,help='Force reload of cached AWS SSO role info and config.yaml'"`
Config ConfigCmd `kong:"cmd,help='Update ~/.aws/config with AWS SSO profiles'"`
Console ConsoleCmd `kong:"cmd,help='Open AWS Console using specificed AWS Role/profile'"`
Default DefaultCmd `kong:"cmd,hidden,default='1'"` // list command without args
Eval EvalCmd `kong:"cmd,help='Print AWS Environment vars for use with eval $(aws-sso eval ...)'"`
Expand Down
12 changes: 6 additions & 6 deletions cmd/process_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@ package main
import (
"encoding/json"
"fmt"
"time"

//log "github.com/sirupsen/logrus"
// log "github.com/sirupsen/logrus"
"github.com/synfinatic/aws-sso-cli/sso"
"github.com/synfinatic/aws-sso-cli/storage"
"github.com/synfinatic/aws-sso-cli/utils"
Expand Down Expand Up @@ -71,12 +70,13 @@ type CredentialProcessOutput struct {
}

func NewCredentialsProcessOutput(creds *storage.RoleCredentials) *CredentialProcessOutput {
x := *creds
c := CredentialProcessOutput{
Version: 1,
AccessKeyId: (*creds).AccessKeyId,
SecretAccessKey: (*creds).SecretAccessKey,
SessionToken: (*creds).SessionToken,
Expiration: time.Unix((*creds).ExpireEpoch(), 0).Format(time.RFC3339),
AccessKeyId: x.AccessKeyId,
SecretAccessKey: x.SecretAccessKey,
SessionToken: x.SessionToken,
Expiration: x.ExpireISO8601(),
}
return &c
}
Expand Down
36 changes: 26 additions & 10 deletions docs/FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ If the above are true, then AWS SSO will define both:
* `$AWS_DEFAULT_REGION`
* `$AWS_SSO_DEFAULT_REGION`

to the default region as defined by `config.yaml`. If the user changes
to the default region as def)ined by `config.yaml`. If the user changes
roles and the two variables are set to the same region, then AWS SSO will
update the region. If the user ever overrides the `$AWS_DEFAULT_REGION`
value or deletes the `$AWS_SSO_DEFAULT_REGION` then AWS SSO will no longer
Expand All @@ -65,18 +65,34 @@ manage the variable.

### How to configure ProfileFormat

`aws-sso` makes it easy to modify your shell `$PROMPT` to include information
about what AWS Account/Role you have currently assumed by defining the `$AWS_SSO_PROFILE`
environment variable. By default, `ProfileFormat` is set to
`{{ AccountIdStr .AccountId }}:{{ .RoleName }}` which will generate a value like
`02345678901:MyRoleName`.
`aws-sso` uses the `ProfileFormat` configuration option for two different purposes:

1. Makes it easy to modify your shell `$PROMPT` to include information
about what AWS Account/Role you have currently assumed by defining the
`$AWS_SSO_PROFILE` environment variable.
2. Makes it easy to select a role via the `$AWS_PROFILE` environment variable
when you use the [config](../README.md#config) command.

By default, `ProfileFormat` is set to `{{ AccountIdStr .AccountId }}:{{ .RoleName }}`
which will generate a value like `02345678901:MyRoleName`.

Some examples:

* `{{ FirstItem .AccountName .AccountAlias }}` -- If there is an Account Name set in the config.yaml use that,
otherwise use the Account Alias defined by the AWS administrator.
* `{{ AccountIdStr .AccountId }}` -- Pad the AccountId with leading zeros if it is < 12 digits long
* `{{ FirstItem .AccountName .AccountAlias }}` -- If there is an Account Name
set in the config.yaml print that, otherwise print the Account Alias defined
by the AWS administrator.
* `{{ AccountIdStr .AccountId }}` -- Pad the AccountId with leading zeros if it
is < 12 digits long
* `{{ .AccountId }}` -- Print the AccountId as a regular number
* `{{ StringsJoin ":" .AccountAlias .RoleName}} -- Another way of writing `{{ .AccountAlias }}:{{ .RoleName }}`
* `{{ StringsJoin ":" .AccountAlias .RoleName }} -- Another way of writing
`{{ .AccountAlias }}:{{ .RoleName }}`
* `{{ StringReplace " " "_" .AccountAlias }}` -- Replace any spaces (` `) in the
AccountAlias with an underscore (`_`).
* `{{ FirstItem .AccountName .AccountAlias | StringReplace " " "_" }}:{{ .RoleName }}` --
Use the Account Name if set, otherwise use the Account Alias and replace any spaces
with an underscore and then append a colon, followed by the role name.

For a full list of available variables, [see here](config.md#profileformat).

To see a list of values across your roles for a given variable, you can use
the [list](../README.md#list) command.
Loading