diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c6bf2c8..65a7eae2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,10 @@ ### New Features * Setup now prompts for `LogLevel` * Suppress bogus warning when saving Role credentials in `wincred` store #183 - * Fix `eval` command on Windows using `export` instead of `set` #183 + * Add support for role chaining using `Via` tag #38 ### Bugs Fixes + * Fix issue with missing colon in parsed/generated Role ARNs for missing AWS region #192 * Incorrect `--level` value now correctly tells user the correct name of the flag * `exec` command now uses `cmd.exe` when no command is specified diff --git a/cmd/flush_cmd.go b/cmd/flush_cmd.go index c7f449ce..60cd88db 100644 --- a/cmd/flush_cmd.go +++ b/cmd/flush_cmd.go @@ -35,7 +35,7 @@ func (cc *FlushCmd) Run(ctx *RunContext) error { if err != nil { return err } - awssso := sso.NewAWSSSO(s.SSORegion, s.StartUrl, &ctx.Store) + awssso := sso.NewAWSSSO(s, &ctx.Store) // Deleting the token response invalidates all our STS tokens err = ctx.Store.DeleteCreateTokenResponse(awssso.StoreKey()) diff --git a/cmd/main.go b/cmd/main.go index 16da0979..fa97caf2 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -295,7 +295,7 @@ func doAuth(ctx *RunContext) *sso.AWSSSO { if err != nil { log.Fatalf("%s", err.Error()) } - AwsSSO := sso.NewAWSSSO(s.SSORegion, s.StartUrl, &ctx.Store) + AwsSSO := sso.NewAWSSSO(s, &ctx.Store) err = AwsSSO.Authenticate(ctx.Settings.UrlAction, ctx.Settings.Browser) if err != nil { log.WithError(err).Fatalf("Unable to authenticate") diff --git a/cmd/select.go b/cmd/select.go index a785e0e8..6b9314e2 100644 --- a/cmd/select.go +++ b/cmd/select.go @@ -75,7 +75,7 @@ func (tc *TagsCompleter) Complete(d prompt.Document) []prompt.Suggest { } // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_iam-quotas.html -var isRoleARN *regexp.Regexp = regexp.MustCompile(`^arn:aws:iam:\d+:role/[a-zA-Z0-9\+=,\.@_-]+$`) +var isRoleARN *regexp.Regexp = regexp.MustCompile(`^arn:aws:iam::\d+:role/[a-zA-Z0-9\+=,\.@_-]+$`) var NoSpaceAtEnd *regexp.Regexp = regexp.MustCompile(`\s+$`) func (tc *TagsCompleter) Executor(args string) { diff --git a/cmd/tags_cmd.go b/cmd/tags_cmd.go index 0ccf5a34..ec779d17 100644 --- a/cmd/tags_cmd.go +++ b/cmd/tags_cmd.go @@ -35,7 +35,7 @@ func (cc *TagsCmd) Run(ctx *RunContext) error { set := ctx.Settings if ctx.Cli.Tags.ForceUpdate { s := set.SSO[ctx.Cli.SSO] - awssso := sso.NewAWSSSO(s.SSORegion, s.StartUrl, &ctx.Store) + awssso := sso.NewAWSSSO(s, &ctx.Store) err := awssso.Authenticate(ctx.Settings.UrlAction, ctx.Settings.Browser) if err != nil { log.WithError(err).Fatalf("Unable to authenticate") diff --git a/sso/awssso.go b/sso/awssso.go index 6528f6f0..6ddefb92 100644 --- a/sso/awssso.go +++ b/sso/awssso.go @@ -26,9 +26,12 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/sso" "github.com/aws/aws-sdk-go/service/ssooidc" + "github.com/aws/aws-sdk-go/service/sts" + "github.com/davecgh/go-spew/spew" log "github.com/sirupsen/logrus" "github.com/synfinatic/aws-sso-cli/storage" "github.com/synfinatic/aws-sso-cli/utils" @@ -48,12 +51,13 @@ type AWSSSO struct { Token storage.CreateTokenResponse `json:"TokenResponse"` Accounts []AccountInfo `json:"Accounts"` Roles map[string][]RoleInfo `json:"Roles"` + SSOConfig *SSOConfig `json:"SSOConfig"` } -func NewAWSSSO(ssoRegion, startUrl string, store *storage.SecureStorage) *AWSSSO { +func NewAWSSSO(s *SSOConfig, store *storage.SecureStorage) *AWSSSO { mySession := session.Must(session.NewSession()) - oidcSession := ssooidc.New(mySession, aws.NewConfig().WithRegion(ssoRegion)) - ssoSession := sso.New(mySession, aws.NewConfig().WithRegion(ssoRegion)) + oidcSession := ssooidc.New(mySession, aws.NewConfig().WithRegion(s.SSORegion)) + ssoSession := sso.New(mySession, aws.NewConfig().WithRegion(s.SSORegion)) as := AWSSSO{ sso: *ssoSession, @@ -61,9 +65,10 @@ func NewAWSSSO(ssoRegion, startUrl string, store *storage.SecureStorage) *AWSSSO store: *store, ClientName: awsSSOClientName, ClientType: awsSSOClientType, - SsoRegion: ssoRegion, - StartUrl: startUrl, + SsoRegion: s.SSORegion, + StartUrl: s.StartUrl, Roles: map[string][]RoleInfo{}, + SSOConfig: s, } return &as } @@ -293,6 +298,7 @@ type RoleInfo struct { Region string `yaml:"Region" json:"Region" header:"Region"` SSORegion string `header:"SSORegion"` StartUrl string `header:"StartUrl"` + Via string `header:"Via"` } func (ri RoleInfo) GetHeader(fieldName string) (string, error) { @@ -301,7 +307,8 @@ func (ri RoleInfo) GetHeader(fieldName string) (string, error) { } func (ri RoleInfo) RoleArn() string { - return fmt.Sprintf("arn:aws:iam:%s:role/%s", ri.AccountId, ri.RoleName) + a, _ := strconv.ParseInt(ri.AccountId, 10, 64) + return utils.MakeRoleARN(a, ri.RoleName) } func (as *AWSSSO) GetRoles(account AccountInfo) ([]RoleInfo, error) { @@ -321,6 +328,17 @@ func (as *AWSSSO) GetRoles(account AccountInfo) ([]RoleInfo, error) { return as.Roles[account.AccountId], err } for i, r := range output.RoleList { + var via string + + aId, err := strconv.ParseInt(account.AccountId, 10, 64) + if err != nil { + return as.Roles[account.AccountId], fmt.Errorf("Unable to parse accountid %s: %s", + account.AccountId, err.Error()) + } + ssoRole, err := as.SSOConfig.GetRole(aId, aws.StringValue(r.RoleName)) + if err != nil && len(ssoRole.Via) > 0 { + via = ssoRole.Via + } as.Roles[account.AccountId] = append(as.Roles[account.AccountId], RoleInfo{ Id: i, AccountId: aws.StringValue(r.AccountId), @@ -329,6 +347,7 @@ func (as *AWSSSO) GetRoles(account AccountInfo) ([]RoleInfo, error) { EmailAddress: account.EmailAddress, SSORegion: as.SsoRegion, StartUrl: as.StartUrl, + Via: via, }) } for aws.StringValue(output.NextToken) != "" { @@ -412,31 +431,94 @@ func (as *AWSSSO) GetAccounts() ([]AccountInfo, error) { return as.Accounts, nil } +// GetRoleCredentials recursively does any sts:AssumeRole calls as necessary for role-chaining +// through `Via` and returns the final set of RoleCredentials for the requested role func (as *AWSSSO) GetRoleCredentials(accountId int64, role string) (storage.RoleCredentials, error) { aId, err := utils.AccountIdToString(accountId) if err != nil { return storage.RoleCredentials{}, err } - input := sso.GetRoleCredentialsInput{ - AccessToken: aws.String(as.Token.AccessToken), - AccountId: aws.String(aId), - RoleName: aws.String(role), + configRole, err := as.SSOConfig.GetRole(accountId, role) + if err != nil && configRole.Via == "" { + log.Debugf("Getting %s:%s directly", aId, role) + // This are the actual role creds requested through AWS SSO + input := sso.GetRoleCredentialsInput{ + AccessToken: aws.String(as.Token.AccessToken), + AccountId: aws.String(aId), + RoleName: aws.String(role), + } + output, err := as.sso.GetRoleCredentials(&input) + if err != nil { + return storage.RoleCredentials{}, err + } + + ret := storage.RoleCredentials{ + AccountId: accountId, + RoleName: role, + AccessKeyId: aws.StringValue(output.RoleCredentials.AccessKeyId), + SecretAccessKey: aws.StringValue(output.RoleCredentials.SecretAccessKey), + SessionToken: aws.StringValue(output.RoleCredentials.SessionToken), + Expiration: aws.Int64Value(output.RoleCredentials.Expiration), + } + + return ret, nil + } + + // Need to recursively call sts:AssumeRole in order to retrieve the STS creds for + // the requested role + // role has a Via + log.Debugf("Getting %s:%s via %s", aId, role, configRole.Via) + viaAccountId, viaRole, err := utils.ParseRoleARN(configRole.Via) + if err != nil { + return storage.RoleCredentials{}, fmt.Errorf("Invalid Via %s: %s", configRole.Via, err.Error()) } - output, err := as.sso.GetRoleCredentials(&input) + + // recurse + creds, err := as.GetRoleCredentials(viaAccountId, viaRole) if err != nil { return storage.RoleCredentials{}, err } + sessionCreds := credentials.NewStaticCredentials( + creds.AccessKeyId, + creds.SecretAccessKey, + creds.SessionToken, + ) + mySession := session.Must(session.NewSessionWithOptions(session.Options{ + Config: aws.Config{ + Region: aws.String(as.SsoRegion), + Credentials: sessionCreds, + }, + })) + stsSession := sts.New(mySession) + + input := sts.AssumeRoleInput{ + // DurationSeconds: aws.Int64(900), + RoleArn: aws.String(utils.MakeRoleARN(accountId, role)), + RoleSessionName: aws.String(fmt.Sprintf("Via_%s_%s", aId, role)), + } + if configRole.ExternalId != "" { + // Optional vlaue: https://docs.aws.amazon.com/sdk-for-go/api/service/sts/#AssumeRoleInput + input.ExternalId = aws.String(configRole.ExternalId) + } + if configRole.SourceIdentity != "" { + input.SourceIdentity = aws.String(configRole.SourceIdentity) + } + + output, err := stsSession.AssumeRole(&input) + if err != nil { + return storage.RoleCredentials{}, err + } + log.Debugf("%s", spew.Sdump(output)) ret := storage.RoleCredentials{ AccountId: accountId, RoleName: role, - AccessKeyId: aws.StringValue(output.RoleCredentials.AccessKeyId), - SecretAccessKey: aws.StringValue(output.RoleCredentials.SecretAccessKey), - SessionToken: aws.StringValue(output.RoleCredentials.SessionToken), - Expiration: aws.Int64Value(output.RoleCredentials.Expiration), + AccessKeyId: aws.StringValue(output.Credentials.AccessKeyId), + SecretAccessKey: aws.StringValue(output.Credentials.SecretAccessKey), + SessionToken: aws.StringValue(output.Credentials.SessionToken), + Expiration: aws.TimeValue(output.Credentials.Expiration).Unix(), } - return ret, nil } diff --git a/sso/cache_test.go b/sso/cache_test.go index 141944ed..736ed737 100644 --- a/sso/cache_test.go +++ b/sso/cache_test.go @@ -9,9 +9,9 @@ import ( const ( TEST_CACHE_FILE = "./testdata/cache.json" - TEST_ROLE_ARN = "arn:aws:iam:707513610766:role/AWSAdministratorAccess" - INVALID_ACCOUNT_ARN = "arn:aws:iam:707513618766:role/AWSAdministratorAccess" - INVALID_ROLE_ARN = "arn:aws:iam:707513610766:role/AdministratorAccess" + TEST_ROLE_ARN = "arn:aws:iam::707513610766:role/AWSAdministratorAccess" + INVALID_ACCOUNT_ARN = "arn:aws:iam::707513618766:role/AWSAdministratorAccess" + INVALID_ROLE_ARN = "arn:aws:iam::707513610766:role/AdministratorAccess" ) type CacheTestSuite struct { diff --git a/sso/settings.go b/sso/settings.go index b2b2351d..43427be7 100644 --- a/sso/settings.go +++ b/sso/settings.go @@ -81,12 +81,14 @@ type SSOAccount struct { } type SSORole struct { - account *SSOAccount // pointer back up - ARN string `yaml:"ARN"` - Profile string `koanf:"Profile" yaml:"Profile,omitempty"` - Tags map[string]string `koanf:"Tags" yaml:"Tags,omitempty"` - DefaultRegion string `koanf:"DefaultRegion" yaml:"DefaultRegion,omitempty"` - Via string `koanf:"Via" yaml:"Via,omitempty"` + account *SSOAccount // pointer back up + ARN string `yaml:"ARN"` + Profile string `koanf:"Profile" yaml:"Profile,omitempty"` + Tags map[string]string `koanf:"Tags" yaml:"Tags,omitempty"` + DefaultRegion string `koanf:"DefaultRegion" yaml:"DefaultRegion,omitempty"` + Via string `koanf:"Via" yaml:"Via,omitempty"` + ExternalId string `koanf:"ExternalId" yaml:"ExternalId,omitempty"` + SourceIdentity string `koanf:"SourceIdentity" yaml:"SourceIdentity,omitempty"` } // GetDefaultRegion scans the config settings file to pick the most local DefaultRegion from the tree @@ -367,6 +369,17 @@ func (s *SSOConfig) GetRoleMatches(tags map[string]string) []*SSORole { return match } +// GetRole returns the matching role if it exists +func (s *SSOConfig) GetRole(accountId int64, role string) (*SSORole, error) { + if a, ok := s.Accounts[accountId]; ok { + if r, ok := a.Roles[role]; ok { + return r, nil + } + } + a, _ := utils.AccountIdToString(accountId) + return &SSORole{}, fmt.Errorf("Unable to find %s:%s", a, role) +} + // HasRole returns true/false if the given Account has the provided arn func (a *SSOAccount) HasRole(arn string) bool { hasRole := false @@ -440,19 +453,19 @@ func (r *SSORole) GetRoleName() string { // GetAccountId returns the accountId portion of the ARN or empty string on error func (r *SSORole) GetAccountId() string { - s := strings.Split(r.ARN, ":") - if len(s) < 4 { - log.Errorf("Role.ARN is missing the account field: '%v'\n%v", r.ARN, *r) + a, err := utils.AccountIdToString(r.GetAccountId64()) + if err != nil { + log.WithError(err).Errorf("Unable to parse AccountId '%s'", a) return "" } - return s[3] + return a } // GetAccountId64 returns the accountId portion of the ARN func (r *SSORole) GetAccountId64() int64 { - i, err := strconv.ParseInt(r.GetAccountId(), 10, 64) + a, _, err := utils.ParseRoleARN(r.ARN) if err != nil { - log.WithError(err).Panicf("Unable to decode account id for %s", r.ARN) + log.WithError(err).Panicf("Unable to parse %s", r.ARN) } - return i + return a } diff --git a/sso/settings_test.go b/sso/settings_test.go index 6016487b..6572dff9 100644 --- a/sso/settings_test.go +++ b/sso/settings_test.go @@ -33,9 +33,9 @@ const ( ) var TEST_GET_ROLE_ARN []string = []string{ - "arn:aws:iam:258234615182:role/AWSAdministratorAccess", - "arn:aws:iam:258234615182:role/LimitedAccess", - "arn:aws:iam:833365043586:role/AWSAdministratorAccess", + "arn:aws:iam::258234615182:role/AWSAdministratorAccess", + "arn:aws:iam::258234615182:role/LimitedAccess", + "arn:aws:iam::833365043586:role/AWSAdministratorAccess", } type SettingsTestSuite struct { diff --git a/sso/testdata/cache.json b/sso/testdata/cache.json index 74134199..76259bf6 100644 --- a/sso/testdata/cache.json +++ b/sso/testdata/cache.json @@ -12,7 +12,7 @@ }, "Roles": { "AWSAdministratorAccess": { - "Arn": "arn:aws:iam:258234615182:role/AWSAdministratorAccess", + "Arn": "arn:aws:iam::258234615182:role/AWSAdministratorAccess", "Tags": { "AccountAlias": "OurCompany Control Tower Playground", "AccountID": "258234615182", @@ -25,7 +25,7 @@ } }, "AWSOrganizationsFullAccess": { - "Arn": "arn:aws:iam:258234615182:role/AWSOrganizationsFullAccess", + "Arn": "arn:aws:iam::258234615182:role/AWSOrganizationsFullAccess", "Tags": { "AccountAlias": "OurCompany Control Tower Playground", "AccountID": "258234615182", @@ -35,7 +35,7 @@ } }, "AWSPowerUserAccess": { - "Arn": "arn:aws:iam:258234615182:role/AWSPowerUserAccess", + "Arn": "arn:aws:iam::258234615182:role/AWSPowerUserAccess", "Tags": { "AccountAlias": "OurCompany Control Tower Playground", "AccountID": "258234615182", @@ -45,7 +45,7 @@ } }, "AWSReadOnlyAccess": { - "Arn": "arn:aws:iam:258234615182:role/AWSReadOnlyAccess", + "Arn": "arn:aws:iam::258234615182:role/AWSReadOnlyAccess", "Tags": { "AccountAlias": "OurCompany Control Tower Playground", "AccountID": "258234615182", @@ -55,7 +55,7 @@ } }, "AWSServiceCatalogEndUserAccess": { - "Arn": "arn:aws:iam:258234615182:role/AWSServiceCatalogEndUserAccess", + "Arn": "arn:aws:iam::258234615182:role/AWSServiceCatalogEndUserAccess", "Tags": { "AccountAlias": "OurCompany Control Tower Playground", "AccountID": "258234615182", @@ -65,7 +65,7 @@ } }, "DoNothingRole": { - "Arn": "arn:aws:iam:258234615182:role/DoNothingRole", + "Arn": "arn:aws:iam::258234615182:role/DoNothingRole", "Tags": { "AccountAlias": "OurCompany Control Tower Playground", "AccountID": "258234615182", @@ -75,7 +75,7 @@ } }, "SSO-ProductionDevelopersRO": { - "Arn": "arn:aws:iam:258234615182:role/SSO-ProductionDevelopersRO", + "Arn": "arn:aws:iam::258234615182:role/SSO-ProductionDevelopersRO", "Tags": { "AccountAlias": "OurCompany Control Tower Playground", "AccountID": "258234615182", @@ -95,7 +95,7 @@ }, "Roles": { "AWSAdministratorAccess": { - "Arn": "arn:aws:iam:833365043586:role/AWSAdministratorAccess", + "Arn": "arn:aws:iam::833365043586:role/AWSAdministratorAccess", "Tags": { "AccountAlias": "Log archive", "AccountID": "833365043586", @@ -105,7 +105,7 @@ } }, "AWSPowerUserAccess": { - "Arn": "arn:aws:iam:833365043586:role/AWSPowerUserAccess", + "Arn": "arn:aws:iam::833365043586:role/AWSPowerUserAccess", "Tags": { "AccountAlias": "Log archive", "AccountID": "833365043586", @@ -115,7 +115,7 @@ } }, "AWSReadOnlyAccess": { - "Arn": "arn:aws:iam:833365043586:role/AWSReadOnlyAccess", + "Arn": "arn:aws:iam::833365043586:role/AWSReadOnlyAccess", "Tags": { "AccountAlias": "Log archive", "AccountID": "833365043586", @@ -125,7 +125,7 @@ } }, "SSO-ProductionDevelopersRO": { - "Arn": "arn:aws:iam:833365043586:role/SSO-ProductionDevelopersRO", + "Arn": "arn:aws:iam::833365043586:role/SSO-ProductionDevelopersRO", "Tags": { "AccountAlias": "Log archive", "AccountID": "833365043586", @@ -146,7 +146,7 @@ }, "Roles": { "AWSAdministratorAccess": { - "Arn": "arn:aws:iam:502470824893:role/AWSAdministratorAccess", + "Arn": "arn:aws:iam::502470824893:role/AWSAdministratorAccess", "Tags": { "AccountAlias": "Audit", "AccountID": "502470824893", @@ -156,7 +156,7 @@ } }, "AWSPowerUserAccess": { - "Arn": "arn:aws:iam:502470824893:role/AWSPowerUserAccess", + "Arn": "arn:aws:iam::502470824893:role/AWSPowerUserAccess", "Tags": { "AccountAlias": "Audit", "AccountID": "502470824893", @@ -166,7 +166,7 @@ } }, "AWSReadOnlyAccess": { - "Arn": "arn:aws:iam:502470824893:role/AWSReadOnlyAccess", + "Arn": "arn:aws:iam::502470824893:role/AWSReadOnlyAccess", "Tags": { "AccountAlias": "Audit", "AccountID": "502470824893", @@ -176,7 +176,7 @@ } }, "SSO-ProductionDevelopersRO": { - "Arn": "arn:aws:iam:502470824893:role/SSO-ProductionDevelopersRO", + "Arn": "arn:aws:iam::502470824893:role/SSO-ProductionDevelopersRO", "Tags": { "AccountAlias": "Audit", "AccountID": "502470824893", @@ -196,7 +196,7 @@ }, "Roles": { "AWSAdministratorAccess": { - "Arn": "arn:aws:iam:707513610766:role/AWSAdministratorAccess", + "Arn": "arn:aws:iam::707513610766:role/AWSAdministratorAccess", "Tags": { "AccountAlias": "control-tower-dev-sub1-aws", "AccountID": "707513610766", @@ -206,7 +206,7 @@ } }, "AWSPowerUserAccess": { - "Arn": "arn:aws:iam:707513610766:role/AWSPowerUserAccess", + "Arn": "arn:aws:iam::707513610766:role/AWSPowerUserAccess", "Tags": { "AccountAlias": "control-tower-dev-sub1-aws", "AccountID": "707513610766", @@ -216,7 +216,7 @@ } }, "AWSReadOnlyAccess": { - "Arn": "arn:aws:iam:707513610766:role/AWSReadOnlyAccess", + "Arn": "arn:aws:iam::707513610766:role/AWSReadOnlyAccess", "Tags": { "AccountAlias": "control-tower-dev-sub1-aws", "AccountID": "707513610766", @@ -226,7 +226,7 @@ } }, "SSO-ProductionDevelopersRO": { - "Arn": "arn:aws:iam:707513610766:role/SSO-ProductionDevelopersRO", + "Arn": "arn:aws:iam::707513610766:role/SSO-ProductionDevelopersRO", "Tags": { "AccountAlias": "control-tower-dev-sub1-aws", "AccountID": "707513610766", diff --git a/storage/json_store_test.go b/storage/json_store_test.go index 43d34327..f0ea593f 100644 --- a/storage/json_store_test.go +++ b/storage/json_store_test.go @@ -96,7 +96,7 @@ func (s *JsonStoreTestSuite) TestRegisterClientData() { func (s *JsonStoreTestSuite) TestRoleCredentials() { t := s.T() rc := RoleCredentials{} - arn := "arn:aws:iam:012344553243:role/AWSAdministratorAccess" + arn := "arn:aws:iam::012344553243:role/AWSAdministratorAccess" err := s.json.GetRoleCredentials("foobar", &rc) assert.NotNil(t, err) diff --git a/storage/storage_test.go b/storage/storage_test.go index ad60531a..b16aefec 100644 --- a/storage/storage_test.go +++ b/storage/storage_test.go @@ -81,7 +81,7 @@ func TestRoleArn(t *testing.T) { AccountId: 12344553243, RoleName: "foobar", } - assert.Equal(t, "arn:aws:iam:012344553243:role/foobar", x.RoleArn()) + assert.Equal(t, "arn:aws:iam::012344553243:role/foobar", x.RoleArn()) assert.Equal(t, "012344553243", x.AccountIdStr()) } diff --git a/storage/testdata/store.json b/storage/testdata/store.json index d5e58deb..72238d28 100644 --- a/storage/testdata/store.json +++ b/storage/testdata/store.json @@ -18,7 +18,7 @@ } }, "RoleCredentials": { - "arn:aws:iam:012344553243:role/AWSAdministratorAccess": { + "arn:aws:iam::012344553243:role/AWSAdministratorAccess": { "roleName": "AWSAdministratorAccess", "accountId": 12344553243, "accessKeyId": "not a real access key id", diff --git a/utils/utils.go b/utils/utils.go index 6b6b0edf..e5a4a8d3 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -89,10 +89,10 @@ func ParseRoleARN(arn string) (int64, string, error) { // short account:Role format accountid = s[0] role = s[1] - } else if len(s) == 5 { - // long format for arn:aws:iam:XXXXXXXXXX:role/YYYYYYYY - accountid = s[3] - s = strings.Split(s[4], "/") + } else if len(s) == 6 { + // long format for arn:aws:iam::XXXXXXXXXX:role/YYYYYYYY + accountid = s[4] + s = strings.Split(s[5], "/") if len(s) != 2 { return 0, "", fmt.Errorf("Unable to parse ARN: %s", arn) } @@ -114,7 +114,7 @@ func MakeRoleARN(account int64, name string) string { if err != nil { log.Fatalf("%s", err.Error()) } - return fmt.Sprintf("arn:aws:iam:%s:role/%s", a, name) + return fmt.Sprintf("arn:aws:iam::%s:role/%s", a, name) } // ensures the given directory exists for the filename diff --git a/utils/utils_test.go b/utils/utils_test.go index 7acd8ee2..1b8ca5ee 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -39,7 +39,7 @@ func TestUtilsSuite(t *testing.T) { func (suite *UtilsTestSuite) TestParseRoleARN() { t := suite.T() - a, r, err := ParseRoleARN("arn:aws:iam:11111:role/Foo") + a, r, err := ParseRoleARN("arn:aws:iam::11111:role/Foo") assert.Equal(t, int64(11111), a) assert.Equal(t, "Foo", r) assert.Nil(t, err) @@ -50,28 +50,28 @@ func (suite *UtilsTestSuite) TestParseRoleARN() { _, _, err = ParseRoleARN("arnFoo") assert.NotNil(t, err) - _, _, err = ParseRoleARN("arn:aws:iam:a:role/Foo") + _, _, err = ParseRoleARN("arn:aws:iam::a:role/Foo") assert.NotNil(t, err) - _, _, err = ParseRoleARN("arn:aws:iam:000000011111:role") + _, _, err = ParseRoleARN("arn:aws:iam::000000011111:role") assert.NotNil(t, err) _, _, err = ParseRoleARN("aws:iam:000000011111:role/Foo") assert.NotNil(t, err) - _, _, err = ParseRoleARN("invalid:arn:aws:iam:000000011111:role/Foo") + _, _, err = ParseRoleARN("invalid:arn:aws:iam::000000011111:role/Foo") assert.NotNil(t, err) - _, _, err = ParseRoleARN("arn:aws:iam:000000011111:role/Foo/Bar") + _, _, err = ParseRoleARN("arn:aws:iam::000000011111:role/Foo/Bar") assert.NotNil(t, err) } func (suite *UtilsTestSuite) TestMakeRoleARN() { t := suite.T() - assert.Equal(t, "arn:aws:iam:000000011111:role/Foo", MakeRoleARN(11111, "Foo")) - assert.Equal(t, "arn:aws:iam:000000711111:role/Foo", MakeRoleARN(711111, "Foo")) - assert.Equal(t, "arn:aws:iam:000000000000:role/", MakeRoleARN(0, "")) + assert.Equal(t, "arn:aws:iam::000000011111:role/Foo", MakeRoleARN(11111, "Foo")) + assert.Equal(t, "arn:aws:iam::000000711111:role/Foo", MakeRoleARN(711111, "Foo")) + assert.Equal(t, "arn:aws:iam::000000000000:role/", MakeRoleARN(0, "")) } func (suite *UtilsTestSuite) TestEnsureDirExists() {