Handle managed_policy_arns in aws_iam_roles

main
Elie 2021-06-17 17:25:53 +02:00
parent 4ac335e7a1
commit 8dcb5da73d
No known key found for this signature in database
GPG Key ID: 399AF69092C727B6
9 changed files with 377 additions and 2 deletions

View File

@ -97,6 +97,7 @@ func (d DriftCTL) Run() (*analyser.Analysis, error) {
middlewares.NewAwsSqsQueuePolicyExpander(d.resourceFactory, d.resourceSchemaRepository),
middlewares.NewAwsDefaultSqsQueuePolicy(),
middlewares.NewAwsSNSTopicPolicyExpander(d.resourceFactory, d.resourceSchemaRepository),
middlewares.NewAwsRoleManagedPolicyExpander(d.resourceFactory),
)
if !d.strictMode {

View File

@ -79,9 +79,10 @@ func runTest(t *testing.T, cases TestCases) {
remoteSupplier := &resource.MockSupplier{}
remoteSupplier.On("Resources").Return(c.remoteResources, nil)
resourceFactory := &terraform.MockResourceFactory{}
var resourceFactory resource.ResourceFactory = terraform.NewTerraformResourceFactory(repo)
if c.mocks != nil {
resourceFactory = &terraform.MockResourceFactory{}
c.mocks(resourceFactory, repo)
}
@ -1544,6 +1545,73 @@ func TestDriftctlRun_Middlewares(t *testing.T) {
return &pkg.ScanOptions{Filter: f}
}(t),
},
{
name: "test aws role managed policy expander",
stateResources: []resource.Resource{
&resource.AbstractResource{
Id: "role_with_managed_policy_attr",
Type: aws.AwsIamRoleResourceType,
Attrs: &resource.Attributes{
"name": "role_with_managed_policy_attr",
"managed_policy_arns": []interface{}{
"arn1",
"arn2",
},
},
},
&resource.AbstractResource{
Id: "role_with_empty_managed_policy_attribute",
Type: aws.AwsIamRoleResourceType,
Attrs: &resource.Attributes{
"managed_policy_arns": []interface{}{},
},
},
&resource.AbstractResource{
Id: "role_without_managed_policy_attribute",
Type: aws.AwsIamRoleResourceType,
Attrs: &resource.Attributes{},
},
},
remoteResources: []resource.Resource{
&resource.AbstractResource{
Id: "role_with_managed_policy_attr",
Type: aws.AwsIamRoleResourceType,
Attrs: &resource.Attributes{
"name": "role_with_managed_policy_attr",
},
},
&resource.AbstractResource{
Id: "role_with_managed_policy_attr-arn1",
Type: aws.AwsIamPolicyAttachmentResourceType,
Attrs: &resource.Attributes{
"policy_arn": "arn1",
"roles": []interface{}{"role_with_managed_policy_attr"},
},
},
&resource.AbstractResource{
Id: "role_with_managed_policy_attr-arn2",
Type: aws.AwsIamPolicyAttachmentResourceType,
Attrs: &resource.Attributes{
"policy_arn": "arn2",
"roles": []interface{}{"role_with_managed_policy_attr"},
},
},
&resource.AbstractResource{
Id: "role_with_empty_managed_policy_attribute",
Type: aws.AwsIamRoleResourceType,
Attrs: &resource.Attributes{},
},
&resource.AbstractResource{
Id: "role_without_managed_policy_attribute",
Type: aws.AwsIamRoleResourceType,
Attrs: &resource.Attributes{},
},
},
assert: func(result *test.ScanResult, err error) {
result.AssertInfrastructureIsInSync()
result.AssertManagedCount(5)
},
},
}
runTest(t, cases)

View File

@ -28,7 +28,12 @@ func (m AwsDefaults) awsIamRoleDefaults(remoteResources []resource.Resource) []r
continue
}
if match := strings.HasPrefix((*remoteResource.(*resource.AbstractResource).Attrs)["path"].(string), defaultIamRolePathPrefix); match {
path := remoteResource.Attributes().GetString("path")
if path == nil {
continue
}
if match := strings.HasPrefix(*path, defaultIamRolePathPrefix); match {
resourcesToIgnore = append(resourcesToIgnore, remoteResource)
}
}

View File

@ -0,0 +1,86 @@
package middlewares
import (
"fmt"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/resource/aws"
"github.com/sirupsen/logrus"
)
// The role of this middleware is to expand policy contained in `managed_policy_arns` to dedicated `aws_iam_policy_attachment`
// resources. Note that we do not use `aws_iam_role_policy_attachment` or `aws_iam_user_policy_attachment`
// Once theses resources created, we remove the old `managed_policy_arns` field to avoid false positive drifts
type AwsRoleManagedPolicyExpander struct {
resourceFactory resource.ResourceFactory
}
func NewAwsRoleManagedPolicyExpander(resourceFactory resource.ResourceFactory) *AwsRoleManagedPolicyExpander {
return &AwsRoleManagedPolicyExpander{resourceFactory: resourceFactory}
}
func (a AwsRoleManagedPolicyExpander) Execute(remoteResources, resourcesFromState *[]resource.Resource) error {
newList := make([]resource.Resource, 0)
for _, res := range *remoteResources {
// Ignore all resources other than iam_role
if res.TerraformType() != aws.AwsIamRoleResourceType {
newList = append(newList, res)
continue
}
res.Attributes().SafeDelete([]string{"managed_policy_arns"})
newList = append(newList, res)
}
*remoteResources = newList
newList = make([]resource.Resource, 0)
for _, res := range *resourcesFromState {
// Ignore all resources other than iam_role
if res.TerraformType() != aws.AwsIamRoleResourceType {
newList = append(newList, res)
continue
}
managedPolicyArns := res.Attributes().GetSlice("managed_policy_arns")
// if managed_policy_arns does not exist or is empty ignore resource
if managedPolicyArns == nil {
newList = append(newList, res)
continue
}
// Remove empty slices to match remote read results
if len(managedPolicyArns) == 0 {
res.Attributes().SafeDelete([]string{"managed_policy_arns"})
newList = append(newList, res)
continue
}
roleName := res.Attributes().GetString("name")
for _, arn := range managedPolicyArns {
arn := arn.(string)
policyAttachmentData := resource.Attributes{
"policy_arn": arn,
"users": []interface{}{},
"groups": []interface{}{},
"roles": []interface{}{*roleName},
}
logrus.WithFields(logrus.Fields{
"role": *roleName,
"policy_arn": arn,
}).Debug("Expanded managed_policy_arns from role")
newList = append(newList, a.resourceFactory.CreateAbstractResource(aws.AwsIamPolicyAttachmentResourceType, fmt.Sprintf("%s-%s", *roleName, arn), policyAttachmentData))
}
res.Attributes().SafeDelete([]string{"managed_policy_arns"})
newList = append(newList, res)
}
*resourcesFromState = newList
return nil
}

View File

@ -0,0 +1,60 @@
package aws_test
import (
"testing"
"github.com/cloudskiff/driftctl/test"
"github.com/cloudskiff/driftctl/test/acceptance"
)
func TestAcc_Aws_IamRole(t *testing.T) {
acceptance.Run(t, acceptance.AccTestCase{
TerraformVersion: "0.14.9",
Paths: []string{"./testdata/acc/aws_iam_role"},
Args: []string{"scan", "--filter", "Type=='aws_iam_role'"},
Checks: []acceptance.AccCheck{
{
Env: map[string]string{
"AWS_REGION": "us-east-1",
},
Check: func(result *test.ScanResult, stdout string, err error) {
if err != nil {
t.Fatal(err)
}
result.AssertDriftCountTotal(0)
result.AssertDeletedCount(0)
result.AssertManagedCount(1)
},
},
},
})
}
func TestAcc_Aws_IamRole_WithManaged(t *testing.T) {
acceptance.Run(t, acceptance.AccTestCase{
TerraformVersion: "0.14.9",
Paths: []string{"./testdata/acc/aws_iam_role_with_managed_policies"},
Args: []string{
"scan",
"--filter",
"Type=='aws_iam_role' || Type=='aws_iam_policy_attachment'",
"--tf-provider-version",
"3.45.0",
},
Checks: []acceptance.AccCheck{
{
Env: map[string]string{
"AWS_REGION": "us-east-1",
},
Check: func(result *test.ScanResult, stdout string, err error) {
if err != nil {
t.Fatal(err)
}
result.AssertDriftCountTotal(0)
result.AssertDeletedCount(0)
result.AssertManagedCount(2)
},
},
},
})
}

View File

@ -0,0 +1,21 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/aws" {
version = "3.45.0"
constraints = "3.45.0"
hashes = [
"h1:LKU/xfna87/p+hl5yTTW3dvOqWJp5JEM+Dt3nnvSDvA=",
"zh:0fdbb3af75ff55807466533f97eb314556ec41a908a543d7cafb06546930f7c6",
"zh:20656895744fa0f4607096b9681c77b2385f450b1577f9151d3070818378a724",
"zh:390f316d00f25a5e45ef5410961fd05bf673068c1b701dc752d11df6d8e741d7",
"zh:3da70f9de241d5f66ea9994ef1e0beddfdb005fa2d2ef6712392f57c5d2e4844",
"zh:65de63cc0f97c85c28a19db560c546aa25f4f403dbf4783ac53c3918044cf180",
"zh:6fc52072e5a66a5d0510aaa2b373a2697895f51398613c68619d8c0c95fc75f5",
"zh:7c1da61092bd1206a020e3ee340ab11be8a4f9bb74e925ca1229ea5267fb3a62",
"zh:94e533d86ce3c08e7102dcabe34ba32ae7fd7819fd0aedef28f48d29e635eae2",
"zh:a3180d4826662e19e71cf20e925a2be8613a51f2f3f7b6d2643ac1418b976d58",
"zh:c783df364928c77fd4dec5419533b125bebe2d50212c4ad609f83b701c2d981a",
"zh:e1279bde388cb675d324584d965c6d22c3ec6890b13de76a50910a3bcd84ed64",
]
}

View File

@ -0,0 +1,58 @@
provider "aws" {
region = "us-east-1"
}
terraform {
required_providers {
aws = {
version = "3.45.0"
}
}
}
resource "aws_iam_role" "b" {
name = "test_role"
# Terraform's "jsonencode" function converts a
# Terraform expression result to valid JSON syntax.
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
Service = "ec2.amazonaws.com"
}
},
]
})
}
resource "aws_iam_policy" "b" {
name = "b"
path = "/"
description = "bbb"
# Terraform's "jsonencode" function converts a
# Terraform expression result to valid JSON syntax.
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"ec2:Describe*",
]
Effect = "Allow"
Resource = "*"
},
]
})
}
resource "aws_iam_role_policy_attachment" "attach-1" {
role = aws_iam_role.b.name
policy_arn = aws_iam_policy.b.arn
}

View File

@ -0,0 +1,21 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/aws" {
version = "3.45.0"
constraints = "3.45.0"
hashes = [
"h1:LKU/xfna87/p+hl5yTTW3dvOqWJp5JEM+Dt3nnvSDvA=",
"zh:0fdbb3af75ff55807466533f97eb314556ec41a908a543d7cafb06546930f7c6",
"zh:20656895744fa0f4607096b9681c77b2385f450b1577f9151d3070818378a724",
"zh:390f316d00f25a5e45ef5410961fd05bf673068c1b701dc752d11df6d8e741d7",
"zh:3da70f9de241d5f66ea9994ef1e0beddfdb005fa2d2ef6712392f57c5d2e4844",
"zh:65de63cc0f97c85c28a19db560c546aa25f4f403dbf4783ac53c3918044cf180",
"zh:6fc52072e5a66a5d0510aaa2b373a2697895f51398613c68619d8c0c95fc75f5",
"zh:7c1da61092bd1206a020e3ee340ab11be8a4f9bb74e925ca1229ea5267fb3a62",
"zh:94e533d86ce3c08e7102dcabe34ba32ae7fd7819fd0aedef28f48d29e635eae2",
"zh:a3180d4826662e19e71cf20e925a2be8613a51f2f3f7b6d2643ac1418b976d58",
"zh:c783df364928c77fd4dec5419533b125bebe2d50212c4ad609f83b701c2d981a",
"zh:e1279bde388cb675d324584d965c6d22c3ec6890b13de76a50910a3bcd84ed64",
]
}

View File

@ -0,0 +1,55 @@
provider "aws" {
region = "us-east-1"
}
terraform {
required_providers {
aws = {
version = "3.45.0"
}
}
}
resource "aws_iam_role" "b" {
name = "test_role"
managed_policy_arns = [aws_iam_policy.b.arn]
# Terraform's "jsonencode" function converts a
# Terraform expression result to valid JSON syntax.
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
Service = "ec2.amazonaws.com"
}
},
]
})
}
resource "aws_iam_policy" "b" {
name = "b"
path = "/"
description = "bbb"
# Terraform's "jsonencode" function converts a
# Terraform expression result to valid JSON syntax.
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"ec2:Describe*",
]
Effect = "Allow"
Resource = "*"
},
]
})
}