diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 00000000..916a2331 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,12 @@ +linters: + enable: + - asciicheck + - ineffassign + - gocyclo + - dupl + # - funlen + - gofmt + - gosec + - misspell + - whitespace + # - unparam diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b8cbaf3..18fc23bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## [Unreleased] * Add report card and make improvements to code style #124 + * Add auto-complete support #12 + * Add golangci-lint support & config file ## [v1.2.3] - 2021-11-13 diff --git a/Makefile b/Makefile index 29d3a421..b8b3740d 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ endif BUILDINFOSDET ?= PROGRAM_ARGS ?= -PROJECT_VERSION := 1.2.3 +PROJECT_VERSION := 1.3.0 DOCKER_REPO := synfinatic PROJECT_NAME := aws-sso ifeq ($(PROJECT_TAG),) diff --git a/README.md b/README.md index 6e46aa0d..24a56bff 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ -# AWS SSO CLI ![Tests](https://github.com/synfinatic/aws-sso-cli/workflows/Tests/badge.svg) [![Report Card](https://goreportcard.com/badge/github.com/synfinatic/aws-sso-cli)](https://goreportcard.com/report/github.com/synfinatic/aws-sso-cli) +# AWS SSO CLI +![Tests](https://github.com/synfinatic/aws-sso-cli/workflows/Tests/badge.svg) +[![Report Card](https://goreportcard.com/badge/github.com/synfinatic/aws-sso-cli)](https://goreportcard.com/report/github.com/synfinatic/aws-sso-cli) +[![GitHub license](https://img.shields.io/badge/license-GPLv3-blue.svg)](https://raw.githubusercontent.com/synfinatic/aws-sso-cli/main/LICENSE) ## About @@ -89,12 +92,12 @@ been granted access! * `tags` -- List manually created tags for each role * `time` -- Print how much time remains for currently selected role * `version` -- Print the version of aws-sso + * `install-autocomplete` -- Install auto-complete functionality into your shell ### Common Flags * `--help`, `-h` -- Builtin and context sensitive help * `--browser `, `-b` -- Override default browser to open AWS SSO URL (`$AWS_SSO_BROWSER`) - * `--cache ` -- Specify alternative cache file (`$AWS_SSO_CACHE`) * `--config ` -- Specify alternative config file (`$AWS_SSO_CONFIG`) * `--level `, `-L` -- Change default log level: [error|warn|info|debug|trace] * `--lines` -- Print file number with logs @@ -221,6 +224,12 @@ 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. +### 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. + ### Environment Variables The following environment variables are honored by `exec` and `console`: diff --git a/cmd/complete.go b/cmd/complete.go new file mode 100644 index 00000000..80ef93b3 --- /dev/null +++ b/cmd/complete.go @@ -0,0 +1,135 @@ +package main + +/* + * AWS SSO CLI + * Copyright (c) 2021 Aaron Turner + * + * 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 . + */ + +import ( + "io/ioutil" + + "github.com/goccy/go-yaml" + "github.com/posener/complete" + "github.com/synfinatic/aws-sso-cli/sso" + "github.com/synfinatic/aws-sso-cli/utils" +) + +type Predictor struct { + configFile string + accountids []string + roles []string + arns []string +} + +// AvailableAwsRegions lists all the AWS regions that AWS provides +// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-available-regions +var AvailableAwsRegions []string = []string{ + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + "af-south-1", + "ap-east-1", + "ap-south-1", + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-southeast-1", + "ap-southeast-2", + "ap-northeast-1", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-south-1", + "eu-west-3", + "eu-north-1", + "me-south-1", + "sa-east-1", +} + +// NewPredictor loads our cache file (if exists) and loads the values +func NewPredictor(cacheFile, configFile string) *Predictor { + p := Predictor{ + configFile: configFile, + } + c, err := sso.OpenCache(cacheFile, &sso.Settings{}) + if err != nil { + return &p + } + + uniqueRoles := map[string]bool{} + + for i, a := range c.Roles.Accounts { + id, _ := utils.AccountIdToString(i) + p.accountids = append(p.accountids, id) + for role, r := range a.Roles { + p.arns = append(p.arns, r.Arn) + uniqueRoles[role] = true + } + } + + for k := range uniqueRoles { + p.roles = append(p.roles, k) + } + + return &p +} + +// FieldListComplete returns a completor for the `list` fields +func (p *Predictor) FieldListComplete() complete.Predictor { + set := []string{} + for f := range allListFields { + set = append(set, f) + } + + return complete.PredictSet(set...) +} + +// AccountComplete returns a list of all the valid AWS Accounts we have in the cache +func (p *Predictor) AccountComplete() complete.Predictor { + return complete.PredictSet(p.accountids...) +} + +// RoleComplete returns a list of all the valid AWS Roles we have in the cache +func (p *Predictor) RoleComplete() complete.Predictor { + return complete.PredictSet(p.roles...) +} + +// ArnComplete returns a list of all the valid AWS Role ARNs we have in the cache +func (p *Predictor) ArnComplete() complete.Predictor { + return complete.PredictSet(p.arns...) +} + +// RegionsComplete returns a list of all the valid AWS Regions +func (p *Predictor) RegionComplete() complete.Predictor { + return complete.PredictSet(AvailableAwsRegions...) +} + +// SsoComplete returns a list of the valid AWS SSO Instances +func (p *Predictor) SsoComplete() complete.Predictor { + ssos := []string{} + s := sso.Settings{} + + if config, err := ioutil.ReadFile(p.configFile); err == nil { + if err = yaml.Unmarshal(config, &s); err == nil { + for sso := range s.SSO { + ssos = append(ssos, sso) + } + } + } + return complete.PredictSet(ssos...) +} diff --git a/cmd/console_cmd.go b/cmd/console_cmd.go index af3a17c3..1fed6430 100644 --- a/cmd/console_cmd.go +++ b/cmd/console_cmd.go @@ -37,12 +37,12 @@ import ( const AWS_FEDERATED_URL = "https://signin.aws.amazon.com/federation" type ConsoleCmd struct { - Region string `kong:"optional,name='region',help='AWS Region',env='AWS_DEFAULT_REGION'"` + Region string `kong:"optional,name='region',help='AWS Region',env='AWS_DEFAULT_REGION',predictor='region'"` AccessKeyId string `kong:"optional,env='AWS_ACCESS_KEY_ID',hidden"` - AccountId int64 `kong:"optional,name='account',short='A',help='AWS AccountID of role to assume',env='AWS_SSO_ACCOUNTID'"` - Arn string `kong:"optional,short='a',help='ARN of role to assume',env='AWS_SSO_ROLE_ARN'"` + AccountId int64 `kong:"optional,name='account',short='A',help='AWS AccountID of role to assume',env='AWS_SSO_ACCOUNTID',predictor='accountId'"` + Arn string `kong:"optional,short='a',help='ARN of role to assume',env='AWS_SSO_ROLE_ARN',predictor='arn'"` Duration int64 `kong:"optional,short='d',help='AWS Session duration in minutes (default 60)',default=60,env='AWS_SSO_DURATION'"` - Role string `kong:"optional,short='R',help='Name of AWS Role to assume',env='AWS_SSO_ROLE'"` + Role string `kong:"optional,short='R',help='Name of AWS Role to assume',env='AWS_SSO_ROLE',predictor='role'"` SecretAccessKey string `kong:"optional,env='AWS_SECRET_ACCESS_KEY',hidden"` SessionToken string `kong:"optional,env='AWS_SESSION_TOKEN',hidden"` UseEnv bool `kong:"optional,short='e',help='Use existing ENV vars to generate URL'"` diff --git a/cmd/exec_cmd.go b/cmd/exec_cmd.go index 97bf581b..f8f66ae1 100644 --- a/cmd/exec_cmd.go +++ b/cmd/exec_cmd.go @@ -36,11 +36,11 @@ import ( type ExecCmd struct { // AWS Params - Region string `kong:"optional,name='region',help='AWS Region',env='AWS_DEFAULT_REGION'"` + Region string `kong:"optional,name='region',help='AWS Region',env='AWS_DEFAULT_REGION',predictor='region'"` Duration int64 `kong:"optional,name='duration',short='d',help='AWS Session duration in minutes (default 60)',default=60,env='AWS_SSO_DURATION'"` - Arn string `kong:"optional,name='arn',short='a',help='ARN of role to assume',env='AWS_SSO_ROLE_ARN'"` - AccountId int64 `kong:"optional,name='account',short='A',help='AWS AccountID of role to assume',env='AWS_SSO_ACCOUNTID'"` - Role string `kong:"optional,name='role',short='R',help='Name of AWS Role to assume',env='AWS_SSO_ROLE'"` + Arn string `kong:"optional,name='arn',short='a',help='ARN of role to assume',env='AWS_SSO_ROLE_ARN',predictor='arn'"` + AccountId int64 `kong:"optional,name='account',short='A',help='AWS AccountID of role to assume',env='AWS_SSO_ACCOUNTID',predictor='accountId'"` + Role string `kong:"optional,name='role',short='R',help='Name of AWS Role to assume',env='AWS_SSO_ROLE',predictor='role'"` // Exec Params Cmd string `kong:"arg,optional,name='command',help='Command to execute',env='SHELL'"` @@ -132,7 +132,7 @@ func execCmd(ctx *RunContext, awssso *sso.AWSSSO, accountid int64, role, region } // ready our command and connect everything up - cmd := exec.Command(ctx.Cli.Exec.Cmd, ctx.Cli.Exec.Args...) + cmd := exec.Command(ctx.Cli.Exec.Cmd, ctx.Cli.Exec.Args...) // #nosec cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout cmd.Stdin = os.Stdin diff --git a/cmd/list_cmd.go b/cmd/list_cmd.go index 8fe8320e..9a63d02a 100644 --- a/cmd/list_cmd.go +++ b/cmd/list_cmd.go @@ -45,8 +45,8 @@ var allListFields = map[string]string{ } type ListCmd struct { - Fields []string `kong:"optional,arg,help='Fields to display',env='AWS_SSO_FIELDS'"` ListFields bool `kong:"optional,name='list-fields',short='f',help='List available fields'"` + Fields []string `kong:"optional,arg,help='Fields to display',env='AWS_SSO_FIELDS',predictor='fieldList'"` } // what should this actually do? diff --git a/cmd/main.go b/cmd/main.go index 4336970b..bc8a3344 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -20,13 +20,16 @@ package main import ( "fmt" + "os" "github.com/alecthomas/kong" - // "github.com/davecgh/go-spew/spew" + // "github.com/davecgh/go-spew/spew" + "github.com/posener/complete" 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" + "github.com/willabides/kongplete" ) // These variables are defined in the Makefile @@ -81,24 +84,24 @@ var DEFAULT_CONFIG map[string]interface{} = map[string]interface{}{ type CLI struct { // Common Arguments Browser string `kong:"optional,short='b',help='Path to browser to open URLs with',env='AWS_SSO_BROWSER'"` - CacheFile string `kong:"optional,name='cache',default='${INSECURE_CACHE_FILE}',help='Insecure cache file',env='AWS_SSO_CACHE'"` ConfigFile string `kong:"optional,name='config',default='${CONFIG_FILE}',help='Config file',env='AWS_SSO_CONFIG'"` Lines bool `kong:"optional,help='Print line number in logs'"` LogLevel string `kong:"optional,short='L',name='level',enum='error,warn,info,debug,trace,',help='Logging level [error|warn|info|debug|trace]'"` UrlAction string `kong:"optional,short='u',enum='open,print,clip,',help='How to handle URLs [open|print|clip]'"` - SSO string `kong:"optional,short='S',help='AWS SSO Instance',env='AWS_SSO'"` + SSO string `kong:"optional,short='S',help='AWS SSO Instance',env='AWS_SSO',predictor='sso'"` STSRefresh bool `kong:"optional,help='Force refresh of STS Token Credentials'"` // Commands - Cache CacheCmd `kong:"cmd,help='Force reload of cached AWS SSO role info and config.yaml'"` - Console ConsoleCmd `kong:"cmd,help='Open AWS Console using specificed AWS Role/profile'"` - Exec ExecCmd `kong:"cmd,help='Execute command using specified AWS Role/Profile'"` - Flush FlushCmd `kong:"cmd,help='Flush AWS SSO/STS credentials from cache'"` - List ListCmd `kong:"cmd,help='List all accounts / role (default command)',default='1'"` - Renew RenewCmd `kong:"cmd,help='Print renewed AWS credentials for your shell'"` - Tags TagsCmd `kong:"cmd,help='List tags'"` - Time TimeCmd `kong:"cmd,help='Print out much time before STS Token expires'"` - Version VersionCmd `kong:"cmd,help='Print version and exit'"` + Cache CacheCmd `kong:"cmd,help='Force reload of cached AWS SSO role info and config.yaml'"` + Console ConsoleCmd `kong:"cmd,help='Open AWS Console using specificed AWS Role/profile'"` + Exec ExecCmd `kong:"cmd,help='Execute command using specified AWS Role/Profile'"` + Flush FlushCmd `kong:"cmd,help='Flush AWS SSO/STS credentials from cache'"` + List ListCmd `kong:"cmd,help='List all accounts / role (default command)',default='1'"` + Renew RenewCmd `kong:"cmd,help='Print renewed AWS credentials for your shell'"` + Tags TagsCmd `kong:"cmd,help='List tags'"` + Time TimeCmd `kong:"cmd,help='Print out much time before STS Token expires'"` + Version VersionCmd `kong:"cmd,help='Print version and exit'"` + InstallCompletions kongplete.InstallCompletions `kong:"cmd,help='Install shell completions'"` } func main() { @@ -113,9 +116,9 @@ func main() { // Load the config file cli.ConfigFile = utils.GetHomePath(cli.ConfigFile) - cli.CacheFile = utils.GetHomePath(cli.CacheFile) + cacheFile := utils.GetHomePath(INSECURE_CACHE_FILE) - if run_ctx.Settings, err = sso.LoadSettings(cli.ConfigFile, cli.CacheFile, DEFAULT_CONFIG, override); err != nil { + if run_ctx.Settings, err = sso.LoadSettings(cli.ConfigFile, cacheFile, DEFAULT_CONFIG, override); err != nil { log.Fatalf("%s", err.Error()) } @@ -147,33 +150,46 @@ func main() { // parseArgs parses our CLI arguments func parseArgs(cli *CLI) (*kong.Context, sso.OverrideSettings) { - op := kong.Description("Securely manage temporary AWS API Credentials issued via AWS SSO") // need to pass in the variables for defaults vars := kong.Vars{ - "CONFIG_DIR": CONFIG_DIR, - "CONFIG_FILE": CONFIG_FILE, - "DEFAULT_STORE": DEFAULT_STORE, - "INSECURE_CACHE_FILE": INSECURE_CACHE_FILE, - "JSON_STORE_FILE": JSON_STORE_FILE, + "CONFIG_DIR": CONFIG_DIR, + "CONFIG_FILE": CONFIG_FILE, + "DEFAULT_STORE": DEFAULT_STORE, + "JSON_STORE_FILE": JSON_STORE_FILE, } - ctx := kong.Parse(cli, op, vars) - override := sso.OverrideSettings{} + parser := kong.Must( + cli, + kong.Name("aws-sso"), + kong.Description("Securely manage temporary AWS API Credentials issued via AWS SSO"), + kong.UsageOnError(), + vars, + ) - if cli.UrlAction != "" { - override.UrlAction = cli.UrlAction - } - if cli.Browser != "" { - override.Browser = cli.Browser - } - if cli.SSO != "" { - override.DefaultSSO = cli.SSO - } - if cli.LogLevel != "" { - override.LogLevel = cli.LogLevel - } - if cli.Lines { - override.LogLines = true + p := NewPredictor(utils.GetHomePath(INSECURE_CACHE_FILE), utils.GetHomePath(CONFIG_FILE)) + + kongplete.Complete(parser, + kongplete.WithPredictors( + map[string]complete.Predictor{ + "accountId": p.AccountComplete(), + "arn": p.ArnComplete(), + "fieldList": p.FieldListComplete(), + "region": p.RegionComplete(), + "role": p.RoleComplete(), + "sso": p.SsoComplete(), + }, + ), + ) + + ctx, err := parser.Parse(os.Args[1:]) + parser.FatalIfErrorf(err) + + override := sso.OverrideSettings{ + UrlAction: cli.UrlAction, + Browser: cli.Browser, + DefaultSSO: cli.SSO, + LogLevel: cli.LogLevel, + LogLines: cli.Lines, } log.SetFormatter(&log.TextFormatter{ diff --git a/cmd/select.go b/cmd/select.go index f13208ba..8ca74805 100644 --- a/cmd/select.go +++ b/cmd/select.go @@ -208,7 +208,6 @@ func completeTags(roleTags *sso.RoleTags, allTags *sso.TagsList, accountPrimaryT default: desc = fmt.Sprintf("%d roles", roleCnt) - } suggestions = append(suggestions, prompt.Suggest{ Text: value, diff --git a/go.mod b/go.mod index a85dbf7c..b39bbf11 100644 --- a/go.mod +++ b/go.mod @@ -14,10 +14,12 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/goccy/go-yaml v1.9.4 github.com/knadh/koanf v0.16.0 + github.com/posener/complete v1.2.3 github.com/sirupsen/logrus v1.7.0 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 github.com/stretchr/testify v1.7.0 github.com/synfinatic/gotable v0.0.1 + github.com/willabides/kongplete v0.2.0 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // see: https://github.com/sirupsen/logrus/issues/1275 diff --git a/go.sum b/go.sum index 3a2b778e..3f6bae70 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,7 @@ github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN github.com/99designs/keyring v1.1.6 h1:kVDC2uCgVwecxCk+9zoCt2uEL6dt+dfVzMvGgnVcIuM= github.com/99designs/keyring v1.1.6/go.mod h1:16e0ds7LGQQcT59QqkTg72Hh5ShM51Byv5PEmW6uoRU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/alecthomas/kong v0.2.2/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= github.com/alecthomas/kong v0.2.17 h1:URDISCI96MIgcIlQyoCAlhOmrSw6pZScBNkctg8r0W0= github.com/alecthomas/kong v0.2.17/go.mod h1:ka3VZ8GZNPXv9Ov+j4YNLkI8mTuhXyr/0ktSlqIydQQ= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -50,12 +51,14 @@ github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= @@ -124,6 +127,7 @@ github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144T github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk= @@ -131,6 +135,8 @@ github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= @@ -150,6 +156,8 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/synfinatic/gotable v0.0.1 h1:bNVmjRnJCJnnzUjs8+WEUiZcpYNuu9C4m4j9VfFa2PA= github.com/synfinatic/gotable v0.0.1/go.mod h1:kWXD1bxZY6tyu6tWK3CIbGAOrtF7teg0ZQotUMDvoMw= +github.com/willabides/kongplete v0.2.0 h1:C6wYVn+IPyA8rAGRGLLkuxhhSQTEECX4t8u3gi+fuD0= +github.com/willabides/kongplete v0.2.0/go.mod h1:kFVw+PkQsqkV7O4tfIBo6iJ9qY94PJC8sPfMgFG5AdM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/sso/awssso.go b/sso/awssso.go index b5d0d661..6528f6f0 100644 --- a/sso/awssso.go +++ b/sso/awssso.go @@ -408,12 +408,15 @@ func (as *AWSSSO) GetAccounts() ([]AccountInfo, error) { }) } } - return as.Accounts, nil + return as.Accounts, nil } func (as *AWSSSO) GetRoleCredentials(accountId int64, role string) (storage.RoleCredentials, error) { - aId := fmt.Sprintf("%012d", accountId) + aId, err := utils.AccountIdToString(accountId) + if err != nil { + return storage.RoleCredentials{}, err + } input := sso.GetRoleCredentialsInput{ AccessToken: aws.String(as.Token.AccessToken), @@ -433,6 +436,7 @@ func (as *AWSSSO) GetRoleCredentials(accountId int64, role string) (storage.Role SessionToken: aws.StringValue(output.RoleCredentials.SessionToken), Expiration: aws.Int64Value(output.RoleCredentials.Expiration), } + return ret, nil } diff --git a/sso/cache.go b/sso/cache.go index a8a2cd09..bad6853b 100644 --- a/sso/cache.go +++ b/sso/cache.go @@ -57,8 +57,7 @@ func OpenCache(f string, s *Settings) (*Cache, error) { if f != "" { cacheBytes, err = ioutil.ReadFile(f) if err != nil { - log.WithError(err).Errorf("Unable to open CacheStore: %s", f) - return &cache, nil // return empty struct + return &cache, err // return empty struct } err = json.Unmarshal(cacheBytes, &cache) } diff --git a/sso/cache_test.go b/sso/cache_test.go index c98b1e8e..2c0e67a5 100644 --- a/sso/cache_test.go +++ b/sso/cache_test.go @@ -103,7 +103,6 @@ func (suite *CacheTestSuite) TestGetAllRoles() { assert.Equal(t, 4, len(aroles)) aroles = suite.cache.Roles.GetAccountRoles(258234615182) assert.Equal(t, 7, len(aroles)) - } func (suite *CacheTestSuite) TestGetAllTags() { diff --git a/sso/role_tags_test.go b/sso/role_tags_test.go index 52f5e2d5..0de5cb7c 100644 --- a/sso/role_tags_test.go +++ b/sso/role_tags_test.go @@ -119,7 +119,6 @@ func (suite *RoleTagsTestSuite) TestGetMatchCount() { ret := test.RoleTags.GetMatchCount(*test.Query) assert.Equal(t, test.Result, ret, testName) } - } func (suite *RoleTagsTestSuite) TestGetRoleTags() { diff --git a/sso/settings.go b/sso/settings.go index abf3cdea..66aef272 100644 --- a/sso/settings.go +++ b/sso/settings.go @@ -167,7 +167,7 @@ func LoadSettings(configFile, cacheFile string, defaults map[string]interface{}, // load the cache var err error if s.Cache, err = OpenCache(s.cacheFile, s); err != nil { - return s, err + log.Warnf("%s", err.Error()) } return s, nil diff --git a/storage/keyring.go b/storage/keyring.go index 7be4bd2a..32ee04a9 100644 --- a/storage/keyring.go +++ b/storage/keyring.go @@ -36,7 +36,7 @@ const ( KEYRING_ID = "aws-sso-cli" REGISTER_CLIENT_DATA_PREFIX = "client-data" CREATE_TOKEN_RESPONSE_PREFIX = "token-response" - ENV_SSO_FILE_PASSWORD = "AWS_SSO_FILE_PASSPHRASE" + ENV_SSO_FILE_PASSWORD = "AWS_SSO_FILE_PASSPHRASE" // #nosec ) // Implements SecureStorage diff --git a/utils/utils.go b/utils/utils.go index 2ab4052b..f0c11c08 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -111,7 +111,11 @@ func ParseRoleARN(arn string) (int64, string, error) { // Creates an AWS ARN for a role func MakeRoleARN(account int64, name string) string { - return fmt.Sprintf("arn:aws:iam:%012d:role/%s", account, name) + a, err := AccountIdToString(account) + if err != nil { + log.Fatalf("%s", err.Error()) + } + return fmt.Sprintf("arn:aws:iam:%s:role/%s", a, name) } // ensures the given directory exists for the filename @@ -162,3 +166,11 @@ func TimeRemain(expires int64, space bool) (string, error) { // Just return the number of MMm or HHhMMm return s, nil } + +// Converts an AWS AccountId to a string +func AccountIdToString(a int64) (string, error) { + if a < 0 { + return "", fmt.Errorf("Invalid AWS AccountId: %d", a) + } + return fmt.Sprintf("%012d", a), nil +} diff --git a/utils/utils_test.go b/utils/utils_test.go index b1b5d2dc..2026005d 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -94,3 +94,25 @@ func (suite *UtilsTestSuite) TestGetHomePath() { x := filepath.Join(home, "foo/bar") assert.Equal(t, x, GetHomePath("~/foo/bar")) } + +func (suite *UtilsTestSuite) TestAccountToString() { + t := suite.T() + + a, err := AccountIdToString(0) + assert.Nil(t, err) + assert.Equal(t, "000000000000", a) + + a, err = AccountIdToString(11111) + assert.Nil(t, err) + assert.Equal(t, "000000011111", a) + + a, err = AccountIdToString(999999999999) + assert.Nil(t, err) + assert.Equal(t, "999999999999", a) + + _, err = AccountIdToString(-1) + assert.NotNil(t, err) + + _, err = AccountIdToString(-19999) + assert.NotNil(t, err) +}