Skip to content

Commit

Permalink
Implement slow but accurate cloudformation filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
vektah committed May 5, 2020
1 parent 8469cff commit 4336513
Show file tree
Hide file tree
Showing 10 changed files with 241 additions and 51 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ Exec all aws commands? (y/N) y
> aws iam attach-user-policy --user-name billy.blogs --policy-arn arn:aws:iam::aws:policy/ReadOnly
```

## Accurate cloudformation matching

By default, iamy will use a simple heuristic (does it end with an ID, eg -ABCDEF1234) to determine if a given resource is managed by cloudformation.

This behaviour is good enough for some cases, but if you want slower but more accurate matching pass `--accurate-cfn`
to enumerate all cloudformation stacks and resources to determine exactly which resources are managed.

## Inspiration and similar tools
- https://github.com/percolate/iamer
Expand Down
8 changes: 2 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
module github.com/99designs/iamy

go 1.14

require (
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 // indirect
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 // indirect
github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1 // indirect
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect
github.com/aws/aws-sdk-go v1.16.23
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.7.0
github.com/ghodss/yaml v1.0.0
github.com/kr/pretty v0.1.0 // indirect
github.com/kr/pty v1.1.3 // indirect
github.com/mattn/go-colorable v0.0.9 // indirect
github.com/mattn/go-isatty v0.0.4 // indirect
github.com/pkg/errors v0.8.1
github.com/sergi/go-diff v1.0.0 // indirect
github.com/stretchr/objx v0.1.1 // indirect
github.com/stretchr/testify v1.3.0 // indirect
golang.org/x/net v0.0.0-20190119204137-ed066c81e75e // indirect
golang.org/x/sys v0.0.0-20190122071731-054c452bb702 // indirect
Expand Down
28 changes: 2 additions & 26 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1 h1:GDQdwm/gAcJcLAKQQZGOJ4knlw+7rfEQQcmwTbt4p5E=
github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/aws/aws-sdk-go v0.0.0-20190104231327-923b7b1b0525 h1:qc2jx43HwaJSlY5UO/2cphRqZJElpLJlvJfJ4DkAqK0=
github.com/aws/aws-sdk-go v0.0.0-20190104231327-923b7b1b0525/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.16.23 h1:MwBOBeez0XEFVh6DCc888X+nHVBCjUDLnnWXSGGWUgM=
github.com/aws/aws-sdk-go v1.16.23/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v0.0.0-20170523202404-62e9147c64a1 h1:UisrPJO/eDu/ceQR8WPTVgTAyL8XnaKKWIEy7GTZhS0=
github.com/fatih/color v0.0.0-20170523202404-62e9147c64a1/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
Expand All @@ -26,44 +17,29 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-colorable v0.0.8 h1:KatiXbcoFpoKmM5pL0yhug+tx/POfZO+0aVsuGhUhgo=
github.com/mattn/go-colorable v0.0.8/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.2 h1:F+DnWktyadxnOrohKLNUC9/GjFii5RJgY4GFG6ilggw=
github.com/mattn/go-isatty v0.0.2/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17 h1:chPfVn+gpAM5CTpTyVU9j8J+xgRGwmoDlNDLjKnJiYo=
github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/net v0.0.0-20190119204137-ed066c81e75e h1:MDa3fSUp6MdYHouVmCCNz/zaH2a6CRcxY3VhT/K3C5Q=
golang.org/x/net v0.0.0-20190119204137-ed066c81e75e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sys v0.0.0-20170615053224-fb4cac33e319 h1:VOzr22cZ/j39L4pIzz1fEBJewkELEkgJuuLZHCdzFRg=
golang.org/x/sys v0.0.0-20170615053224-fb4cac33e319/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190122071731-054c452bb702 h1:Lk4tbZFnlyPgV+sLgTw5yGfzrlOn9kx4vSombi2FFlY=
golang.org/x/sys v0.0.0-20190122071731-054c452bb702/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/alecthomas/kingpin.v2 v2.2.4 h1:CC8tJ/xljioKrK6ii3IeWVXU4Tw7VB+LbjZBJaBxN50=
gopkg.in/alecthomas/kingpin.v2 v2.2.4/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.0.0-20170407172122-cd8b52f8269e h1:o/mfNjxpTLivuKEfxzzwrJ8PmulH2wEp7t713uMwKAA=
gopkg.in/yaml.v2 v2.0.0-20170407172122-cd8b52f8269e/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
6 changes: 4 additions & 2 deletions iamy.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func main() {
pull = kingpin.Command("pull", "Syncs IAM users, groups and policies from the active AWS account to files")
pullDir = pull.Flag("dir", "The directory to dump yaml files to").Default(defaultDir).Short('d').String()
canDelete = pull.Flag("delete", "Delete extraneous files from destination dir").Bool()
lookupCfn = pull.Flag("accurate-cfn", "Fetch all known resource names from cloudformation to get exact filtering").Bool()
push = kingpin.Command("push", "Syncs IAM users, groups and policies from files to the active AWS account")
pushDir = push.Flag("dir", "The directory to load yaml files from").Default(defaultDir).Short('d').ExistingDir()
)
Expand Down Expand Up @@ -68,8 +69,9 @@ func main() {

case pull.FullCommand():
PullCommand(ui, PullCommandInput{
Dir: *pullDir,
CanDelete: *canDelete,
Dir: *pullDir,
CanDelete: *canDelete,
HeuristicCfnMatching: !*lookupCfn,
})
}
}
Expand Down
33 changes: 23 additions & 10 deletions iamy/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package iamy
import (
"fmt"
"log"
"regexp"
"strings"
"sync"

Expand All @@ -12,18 +11,18 @@ import (
"github.com/pkg/errors"
)

var cfnResourceRegexp = regexp.MustCompile(`-[A-Z0-9]{10,20}$`)

// AwsFetcher fetches account data from AWS
type AwsFetcher struct {
// As Policy and Role descriptions are immutable, we can skip fetching them
// when pushing to AWS
SkipFetchingPolicyAndRoleDescriptions bool
HeuristicCfnMatching bool

Debug *log.Logger

iam *iamClient
s3 *s3Client
cfn *cfnClient
account *Account
data AccountData

Expand All @@ -37,6 +36,8 @@ func (a *AwsFetcher) init() error {
s := awsSession()
a.iam = newIamClient(s)
a.s3 = newS3Client(s)
a.cfn = newCfnClient(s)

if a.account, err = a.getAccount(); err != nil {
return err
}
Expand All @@ -53,6 +54,13 @@ func (a *AwsFetcher) Fetch() (*AccountData, error) {
return nil, errors.Wrap(err, "Error in init")
}

if !a.HeuristicCfnMatching {
log.Println("Fetching CFN data")
if err := a.cfn.PopulateMangedResourceData(); err != nil {
return nil, errors.Wrap(err, "Error fetching CFN data")
}
}

var wg sync.WaitGroup
var iamErr, s3Err error

Expand Down Expand Up @@ -91,6 +99,10 @@ func (a *AwsFetcher) fetchS3Data() error {
if b.policyJson == "" {
continue
}
if ok, err := a.isSkippableManagedResource(CfnS3Bucket, b.name); ok {
log.Printf(err)
continue
}

policyDoc, err := NewPolicyDocumentFromJson(b.policyJson)
if err != nil {
Expand All @@ -107,6 +119,7 @@ func (a *AwsFetcher) fetchS3Data() error {

return nil
}

func (a *AwsFetcher) fetchIamData() error {
var populateIamDataErr error
var populateInstanceProfileErr error
Expand Down Expand Up @@ -196,7 +209,7 @@ func (a *AwsFetcher) marshalRoleDescriptionAsync(roleName string, target *string

func (a *AwsFetcher) populateInstanceProfileData(resp *iam.ListInstanceProfilesOutput) error {
for _, profileResp := range resp.InstanceProfiles {
if ok, err := isSkippableManagedResource(*profileResp.InstanceProfileName); ok {
if ok, err := a.isSkippableManagedResource(CfnInstanceProfile, *profileResp.InstanceProfileName); ok {
log.Printf(err)
continue
}
Expand All @@ -216,7 +229,7 @@ func (a *AwsFetcher) populateInstanceProfileData(resp *iam.ListInstanceProfilesO

func (a *AwsFetcher) populateIamData(resp *iam.GetAccountAuthorizationDetailsOutput) error {
for _, userResp := range resp.UserDetailList {
if ok, err := isSkippableManagedResource(*userResp.UserName); ok {
if ok, err := a.isSkippableManagedResource(CfnIamUser, *userResp.UserName); ok {
log.Printf(err)
continue
}
Expand Down Expand Up @@ -246,7 +259,7 @@ func (a *AwsFetcher) populateIamData(resp *iam.GetAccountAuthorizationDetailsOut
}

for _, groupResp := range resp.GroupDetailList {
if ok, err := isSkippableManagedResource(*groupResp.GroupName); ok {
if ok, err := a.isSkippableManagedResource(CfnIamGroup, *groupResp.GroupName); ok {
log.Printf(err)
continue
}
Expand All @@ -267,7 +280,7 @@ func (a *AwsFetcher) populateIamData(resp *iam.GetAccountAuthorizationDetailsOut
}

for _, roleResp := range resp.RoleDetailList {
if ok, err := isSkippableManagedResource(*roleResp.RoleName); ok {
if ok, err := a.isSkippableManagedResource(CfnIamRole, *roleResp.RoleName); ok {
log.Printf(err)
continue
}
Expand Down Expand Up @@ -297,7 +310,7 @@ func (a *AwsFetcher) populateIamData(resp *iam.GetAccountAuthorizationDetailsOut
}

for _, policyResp := range resp.Policies {
if ok, err := isSkippableManagedResource(*policyResp.PolicyName); ok {
if ok, err := a.isSkippableManagedResource(CfnIamPolicy, *policyResp.PolicyName); ok {
log.Printf(err)
continue
}
Expand Down Expand Up @@ -390,8 +403,8 @@ func (a *AwsFetcher) getAccount() (*Account, error) {
//
// Returns a boolean of whether it can be skipped and a string of the
// reasoning why it was skipped.
func isSkippableManagedResource(resourceIdentifier string) (bool, string) {
if cfnResourceRegexp.MatchString(resourceIdentifier) {
func (a *AwsFetcher) isSkippableManagedResource(cfnType CfnResourceType, resourceIdentifier string) (bool, string) {
if a.cfn.IsManagedResource(cfnType, resourceIdentifier) {
return true, fmt.Sprintf("CloudFormation generated resource %s", resourceIdentifier)
}

Expand Down
6 changes: 4 additions & 2 deletions iamy/aws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ func TestIsSkippableManagedResource(t *testing.T) {
"myalias-123/iam/instance-profile/example.yaml",
}

f := AwsFetcher{cfn: &cfnClient{}}

for _, name := range skippables {
t.Run(name, func(t *testing.T) {

skipped, err := isSkippableManagedResource(name)
skipped, err := f.isSkippableManagedResource(CfnIamRole, name)
if skipped == false {
t.Errorf("expected %s to be skipped but got false", name)
}
Expand All @@ -34,7 +36,7 @@ func TestIsSkippableManagedResource(t *testing.T) {
for _, name := range nonSkippables {
t.Run(name, func(t *testing.T) {

skipped, err := isSkippableManagedResource(name)
skipped, err := f.isSkippableManagedResource(CfnIamRole, name)
if skipped == true {
t.Errorf("expected %s to not be skipped but got true", name)
}
Expand Down
Loading

0 comments on commit 4336513

Please sign in to comment.