Skip to content
This repository has been archived by the owner on Jul 31, 2023. It is now read-only.

Commit

Permalink
Use account names as profile names (#4)
Browse files Browse the repository at this point in the history
* Use account names as profile names

* Update README.md

* Fix lint error
  • Loading branch information
bemica authored Mar 16, 2021
1 parent 1c3ab5a commit 85f8f6d
Show file tree
Hide file tree
Showing 7 changed files with 24,669 additions and 19 deletions.
19 changes: 12 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@
aws-cfg-generator is a CLI tool to generate configs for AWS helper tools based on an IAM user's permissions.

To use this tool you need AWS credentials for an IAM user. This IAM user also needs sufficient permissions to read their
own permission sets and group memberships.
own permission sets and group memberships.

## Profile names

In order to name profiles correctly, aws-cfg-generator will attempt to call `organizations.ListAccounts` and match that
with account IDs in the roles the user has access to. If the user has permissions for a role not in the same AWS
organization the profile will be named by the account ID instead. Similarly, if the user lacks permissions to list the
organization's accounts, the profiles will be named by account IDs as well,

## Supported tools

Expand All @@ -25,12 +32,12 @@ aws-vault for every profile you're explicitly allowed to assume. Run `cat ~/.aws
should look something like this:

```
[profile 123456789098]
[profile account-name]
role_arn=arn:aws:iam::123456789098:role/role-name
source_profile=default
include_profile=default
[profile 098765432123]
[profile another-account-name]
role_arn=arn:aws:iam::098765432123:role/role-name-two
source_profile=default
include_profile=default
Expand All @@ -49,12 +56,12 @@ Run `aws-vault exec default -- ./aws-cfg-generator switch-roles` to write your c
it into your aws-extend-switch-roles settings page. The generated config should look something like this:

```
[123456789098]
[account-name]
aws_account_id = 123456789098
role_name = example-role
color = 00ff7f
[098765432123]
[another-account-name]
aws_account_id = 098765432123
role_name = example-role-two
color = 00ff7f
Expand All @@ -68,13 +75,11 @@ color = 00ff7f

## Known-limitations

- No human-readable profile names
- Only recognizes policies that are attached to groups
- Can only recognize explicit permissions (i.e. it doesn't work when the `Resource` is not a role ARN)

## Planned features

- Human-readable names configurable via a provided mapping from AWS account IDs to names
- Discover roles that are attached or inlined directly on the user

## Contributions
Expand Down
68 changes: 56 additions & 12 deletions aws-cfg-generator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/aws/aws-sdk-go/service/organizations"
"github.com/aws/aws-sdk-go/service/sts"
)

Expand Down Expand Up @@ -71,7 +72,8 @@ type SwitchRolesCmd struct {

type configCreator struct {
iamClient *iam.IAM
generateProfile func(role string)
accountMap map[string]string
generateProfile func(accountMap map[string]string, role string)
}

type PolicyDoc struct {
Expand All @@ -84,19 +86,26 @@ func getUser(userArn *string) *string {
return &arnParts[1]
}

func generateSwitchRolesProfile(role, color string) {
func generateSwitchRolesProfile(accountMap map[string]string, role, color string) {
if !arn.IsARN(role) {
return
}

roleArn, _ := arn.Parse(role)

var profileName string
if name, ok := accountMap[roleArn.AccountID]; ok {
profileName = name
} else {
profileName = roleArn.AccountID
}

roleSplit := strings.Split(roleArn.Resource, "/")

fmt.Printf(switchRolesTemplate, roleArn.AccountID, roleArn.AccountID, roleSplit[1], color)
fmt.Printf(switchRolesTemplate, profileName, roleArn.AccountID, roleSplit[1], color)
}

func generateVaultProfile(role, region, sourceProfile string) {
func generateVaultProfile(accountMap map[string]string, role, region, sourceProfile string) {
// skip creating this profile if the role isn't a valid ARN (e.g. `*`)
if !arn.IsARN(role) {
return
Expand All @@ -106,9 +115,16 @@ func generateVaultProfile(role, region, sourceProfile string) {

t := template.Must(template.New("vaultText").Parse(vaultTemplate))

var profileName string
if name, ok := accountMap[roleArn.AccountID]; ok {
profileName = name
} else {
profileName = roleArn.AccountID
}

var b bytes.Buffer
err := t.Execute(&b, VaultModel{
ProfileName: roleArn.AccountID,
ProfileName: profileName,
Region: region,
SourceProfile: sourceProfile,
RoleArn: role,
Expand Down Expand Up @@ -158,14 +174,14 @@ func (cc *configCreator) statementHelper(polDoc PolicyDoc) {
}

if resStr, ok := statement["Resource"].(string); ok {
cc.generateProfile(resStr)
cc.generateProfile(cc.accountMap, resStr)
continue
}

if resArr, ok := statement["Resource"].([]interface{}); ok {
for _, res := range resArr {
if resStr, ok := res.(string); ok {
cc.generateProfile(resStr)
cc.generateProfile(cc.accountMap, resStr)
}
}
}
Expand Down Expand Up @@ -228,20 +244,46 @@ func (cc *configCreator) generateCfgsForGroup(group *iam.Group) {
}
}

func getAccountNames(orgClient *organizations.Organizations) map[string]string {
accIDToName := map[string]string{}

lai := &organizations.ListAccountsInput{}

for {
lao, err := orgClient.ListAccounts(lai)
if err != nil {
// ignore error so script can be used without these permissions
break
}

for _, acc := range lao.Accounts {
accIDToName[*acc.Id] = *acc.Name
}

if lao.NextToken == nil {
break
}

lai.NextToken = lao.NextToken
}

return accIDToName
}

func main() {
var cli CLI
ctx := kong.Parse(&cli)

var generatorFunc func(role string)
var generatorFunc func(accountMap map[string]string, role string)

switch ctx.Command() {
case "vault":
generatorFunc = func(role string) {
generateVaultProfile(role, cli.Vault.Region, cli.Vault.SourceProfile)
generatorFunc = func(accountMap map[string]string, role string) {
generateVaultProfile(accountMap, role, cli.Vault.Region, cli.Vault.SourceProfile)
}
case "switch-roles":
generatorFunc = func(role string) {
generateSwitchRolesProfile(role, cli.SwitchRoles.Color)
generatorFunc = func(accountMap map[string]string, role string) {
generateSwitchRolesProfile(accountMap, role, cli.SwitchRoles.Color)
}
default:
panic(fmt.Errorf("unsupported command '%s'", ctx.Command()))
Expand All @@ -258,9 +300,11 @@ func main() {
user := getUser(gcio.Arn)

iamClient := iam.New(sess)
accMap := getAccountNames(organizations.New(sess))

cfgCreator := &configCreator{
iamClient: iamClient,
accountMap: accMap,
generateProfile: generatorFunc,
}

Expand Down
Loading

0 comments on commit 85f8f6d

Please sign in to comment.