diff --git a/README.md b/README.md index b98fc8331..d1a8d9ed9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@
- +
A tool for cleaning your AWS account
diff --git a/go.mod b/go.mod index f558b1f10..5bbd0b389 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/golang/mock v1.4.0 github.com/gruntwork-io/terratest v0.24.2 github.com/hashicorp/terraform v0.12.24 - github.com/jckuester/awsls v0.0.0-20200523195606-04f54b8ca1c6 + github.com/jckuester/awsls v0.0.0-20200524112109-93c2a4665746 github.com/jckuester/terradozer v0.0.0-20200523195146-e66de6fa55f3 github.com/onsi/gomega v1.9.0 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 814314fc1..194edf3b9 100644 --- a/go.sum +++ b/go.sum @@ -353,6 +353,8 @@ github.com/jckuester/awsls v0.0.0-20200523105025-fe25c60a9fba h1:fbk5OV5nnwtkElu github.com/jckuester/awsls v0.0.0-20200523105025-fe25c60a9fba/go.mod h1:6dqF/j6Ar6b10DmHSV0CRYgKP2jwN8VUsHoFT3szFfA= github.com/jckuester/awsls v0.0.0-20200523195606-04f54b8ca1c6 h1:Rbcyj5lTyRRE4QgkJVncOgLbn6ehT6Yp5SdjCprr6FA= github.com/jckuester/awsls v0.0.0-20200523195606-04f54b8ca1c6/go.mod h1:hbj1nD8zXLBoZffzkV7WXUmMGarGPKkFeke90U9eehs= +github.com/jckuester/awsls v0.0.0-20200524112109-93c2a4665746 h1:CCO3Lc/ofOzEwH5WIZpndhoixKwNQRNVPKczVT4IS9w= +github.com/jckuester/awsls v0.0.0-20200524112109-93c2a4665746/go.mod h1:hbj1nD8zXLBoZffzkV7WXUmMGarGPKkFeke90U9eehs= github.com/jckuester/terradozer v0.0.0-20200505071321-36ef87ab4394 h1:7LmuH4Cm81Qsi0trqbnOmZ75xemFDhmzin0OoNL7aM4= github.com/jckuester/terradozer v0.0.0-20200505071321-36ef87ab4394/go.mod h1:KYrRcPbIiXgcRp7hG9fOBdAyvDnUx2aA9cjTqvKa4cI= github.com/jckuester/terradozer v0.0.0-20200522202131-ed33ac929141 h1:BowqT+JEJsqjPRWcTH7EOEEJuJlhKoh80MKZouM3pkE= diff --git a/pkg/resource/list.go b/pkg/resource/list.go index 6af722989..b5459b3d5 100644 --- a/pkg/resource/list.go +++ b/pkg/resource/list.go @@ -57,14 +57,19 @@ func List(filter *Filter, client *AWS, awsClient *awsls.Client, switch rType { case "aws_iam_user": - policyAttachments := getAttachedUserPolicies(filteredRes, client, provider) - print(policyAttachments, outputType) + attachedPolicies := getAttachedUserPolicies(filteredRes, client, provider) + print(attachedPolicies, outputType) inlinePolicies := getInlineUserPolicies(filteredRes, client, provider) print(inlinePolicies, outputType) - filteredRes = append(filteredRes, policyAttachments...) + filteredRes = append(filteredRes, attachedPolicies...) filteredRes = append(filteredRes, inlinePolicies...) + case "aws_iam_policy": + policyAttachments := getPolicyAttachments(filteredRes, provider) + print(policyAttachments, outputType) + + filteredRes = append(filteredRes, policyAttachments...) } for _, r := range filteredRes { @@ -142,7 +147,38 @@ func getInlineUserPolicies(users []awsls.Resource, client *AWS, result = append(result, r) } + } + + return result +} + +func getPolicyAttachments(policies []awsls.Resource, provider *provider.TerraformProvider) []awsls.Resource { + var result []awsls.Resource + + for _, policy := range policies { + arn, err := awslsRes.GetAttribute("arn", &policy) + if err != nil { + fmt.Fprint(os.Stderr, color.RedString("Error: %s\n", err)) + continue + } + + r := awsls.Resource{ + Type: "aws_iam_policy_attachment", + // Note: ID is only set for pretty printing (could be also left empty) + ID: policy.ID, + } + + r.Resource = terradozerRes.New(r.Type, r.ID, map[string]cty.Value{ + "policy_arn": cty.StringVal(arn), + }, provider) + + err = r.UpdateState() + if err != nil { + fmt.Fprint(os.Stderr, color.RedString("Error: %s\n", err)) + continue + } + result = append(result, r) } return result diff --git a/pkg/resource/select.go b/pkg/resource/select.go index 418362b71..ee1d392db 100644 --- a/pkg/resource/select.go +++ b/pkg/resource/select.go @@ -7,7 +7,6 @@ import ( "github.com/apex/log" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/efs" - "github.com/aws/aws-sdk-go/service/iam" "github.com/aws/aws-sdk-go/service/kms" awsls "github.com/jckuester/awsls/aws" "github.com/zclconf/go-cty/cty" @@ -35,8 +34,6 @@ func (f Filter) Apply(resType string, res []awsls.Resource, raw interface{}, aws switch resType { case EfsFileSystem: return f.efsFileSystemFilter(res, raw, aws) - case "aws_iam_policy": - return f.iamPolicyFilter(res, raw, aws) case KmsKey: return f.kmsKeysFilter(res, aws) case KmsAlias: @@ -118,54 +115,6 @@ func (f Filter) efsFileSystemFilter(res []awsls.Resource, raw interface{}, c *AW return result } -func (f Filter) iamPolicyFilter(res []awsls.Resource, raw interface{}, c *AWS) []awsls.Resource { - var result []awsls.Resource - - for _, r := range res { - if f.Match(r) { - es, err := c.ListEntitiesForPolicy(&iam.ListEntitiesForPolicyInput{ - PolicyArn: &r.ID, - }) - if err != nil { - log.Fatal(err.Error()) - } - - var roles []string - var users []string - var groups []string - - for _, u := range es.PolicyUsers { - users = append(users, *u.UserName) - } - for _, g := range es.PolicyGroups { - groups = append(groups, *g.GroupName) - } - for _, r := range es.PolicyRoles { - roles = append(roles, *r.RoleName) - } - - result = append(result, awsls.Resource{ - Type: "aws_iam_policy_attachment", - ID: "none", - /* - Attrs: map[string]string{ - "policy_arn": r.ID, - "name": *raw.([]*iam.Policy)[i].PolicyName, - "users": strings.Join(users, "."), - "roles": strings.Join(roles, "."), - "groups": strings.Join(groups, "."), - }, - - */ - }) - result = append(result, r) - } - } - // policy attachments are not resources - // what happens here, is that policy is detached from groups, users and roles - return result -} - func (f Filter) kmsKeysFilter(res []awsls.Resource, c *AWS) []awsls.Resource { var result []awsls.Resource diff --git a/test/iam_policy_test.go b/test/iam_policy_test.go new file mode 100644 index 000000000..056c263ea --- /dev/null +++ b/test/iam_policy_test.go @@ -0,0 +1,72 @@ +package test + +import ( + "fmt" + "os" + "testing" + + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/iam" + "github.com/gruntwork-io/terratest/modules/terraform" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAcc_IamPolicy_DeleteByID(t *testing.T) { + if testing.Short() { + t.Skip("Skipping acceptance test.") + } + + env := InitEnv(t) + + terraformDir := "./test-fixtures/iam-policy" + + terraformOptions := getTerraformOptions(terraformDir, env) + + defer terraform.Destroy(t, terraformOptions) + + terraform.InitAndApply(t, terraformOptions) + + arn := terraform.Output(t, terraformOptions, "arn") + assertIamPolicyExists(t, env, arn) + + id := terraform.Output(t, terraformOptions, "id") + writeConfigID(t, terraformDir, "aws_iam_policy", id) + + defer os.Remove(terraformDir + "/config.yml") + + logBuffer, err := runBinary(t, terraformDir, "YES\n") + require.NoError(t, err) + + assertIamPolicyDeleted(t, env, arn) + + fmt.Println(logBuffer) +} + +func assertIamPolicyExists(t *testing.T, env EnvVars, arn string) { + assert.True(t, iamPolicyExists(t, env, arn)) +} + +func assertIamPolicyDeleted(t *testing.T, env EnvVars, arn string) { + assert.False(t, iamPolicyExists(t, env, arn)) +} + +func iamPolicyExists(t *testing.T, env EnvVars, arn string) bool { + opts := &iam.GetPolicyInput{ + PolicyArn: &arn, + } + + _, err := env.AWSClient.IAMAPI.GetPolicy(opts) + if err != nil { + ec2err, ok := err.(awserr.Error) + if !ok { + t.Fatal() + } + if ec2err.Code() == "NoSuchEntity" { + return false + } + t.Fatal(err) + } + + return true +} diff --git a/test/test-fixtures/iam-policy/main.tf b/test/test-fixtures/iam-policy/main.tf new file mode 100644 index 000000000..50fb584ca --- /dev/null +++ b/test/test-fixtures/iam-policy/main.tf @@ -0,0 +1,76 @@ +provider "aws" { + version = "~> 2.0" + + profile = var.profile + region = var.region +} + +terraform { + # The configuration for this backend will be filled in by Terragrunt + backend "s3" { + } +} + +resource "aws_iam_user" "test" { + name = "awsweeper-test-acc" + + tags = { + awsweeper = "test-acc" + } +} + +resource "aws_iam_role" "test" { + name = "awsweeper-test-acc" + + assume_role_policy = <