Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iam: Improve policy diff handling #28836

Merged
merged 4 commits into from
Jan 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .changelog/28836.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
```release-note:bug
resource/aws_iam_group_policy: Improve refresh to avoid unnecessary diffs in `policy`
```

```release-note:bug
resource/aws_iam_policy: Improve refresh to avoid unnecessary diffs in `policy`, `tags`
```

```release-note:bug
resource/aws_iam_role: Improve refresh to avoid unnecessary diffs in `inline_policy.*.policy`, `tags`
```

```release-note:bug
resource/aws_iam_role_policy: Improve refresh to avoid unnecessary diffs in `policy`
```

```release-note:bug
resource/aws_iam_user_policy: Improve refresh to avoid unnecessary diffs in `policy`
```
14 changes: 11 additions & 3 deletions internal/service/iam/group_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ func ResourceGroupPolicy() *schema.Resource {
ValidateFunc: verify.ValidIAMPolicyJSON,
DiffSuppressFunc: verify.SuppressEquivalentPolicyDiffs,
DiffSuppressOnRefresh: true,
StateFunc: func(v interface{}) string {
json, _ := verify.LegacyPolicyNormalize(v)
return json
},
},
"name": {
Type: schema.TypeString,
Expand All @@ -62,9 +66,14 @@ func ResourceGroupPolicy() *schema.Resource {
func resourceGroupPolicyPut(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).IAMConn()

policyDoc, err := verify.LegacyPolicyNormalize(d.Get("policy").(string))
if err != nil {
return fmt.Errorf("policy (%s) is invalid JSON: %w", policyDoc, err)
}

request := &iam.PutGroupPolicyInput{
GroupName: aws.String(d.Get("group").(string)),
PolicyDocument: aws.String(d.Get("policy").(string)),
PolicyDocument: aws.String(policyDoc),
}

var policyName string
Expand Down Expand Up @@ -139,8 +148,7 @@ func resourceGroupPolicyRead(d *schema.ResourceData, meta interface{}) error {
return err
}

policyToSet, err := verify.SecondJSONUnlessEquivalent(d.Get("policy").(string), policy)

policyToSet, err := verify.LegacyPolicyToSet(d.Get("policy").(string), policy)
if err != nil {
return fmt.Errorf("while setting policy (%s), encountered: %w", policyToSet, err)
}
Expand Down
4 changes: 2 additions & 2 deletions internal/service/iam/group_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,12 +339,12 @@ resource "aws_iam_group_policy" "test" {

policy = <<EOF
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": %[2]q,
"Resource": "*"
}
},
"Version": "2012-10-17"
}
EOF
}
Expand Down
15 changes: 5 additions & 10 deletions internal/service/iam/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ func ResourcePolicy() *schema.Resource {
ValidateFunc: verify.ValidIAMPolicyJSON,
DiffSuppressFunc: verify.SuppressEquivalentPolicyDiffs,
DiffSuppressOnRefresh: true,
StateFunc: func(v interface{}) string {
json, _ := structure.NormalizeJsonString(v)
return json
},
},
"name": {
Type: schema.TypeString,
Expand Down Expand Up @@ -97,7 +101,6 @@ func resourcePolicyCreate(d *schema.ResourceData, meta interface{}) error {
}

policy, err := structure.NormalizeJsonString(d.Get("policy").(string))

if err != nil {
return fmt.Errorf("policy (%s) is invalid JSON: %w", policy, err)
}
Expand Down Expand Up @@ -259,18 +262,11 @@ func resourcePolicyRead(d *schema.ResourceData, meta interface{}) error {
}
}

policyToSet, err := verify.SecondJSONUnlessEquivalent(d.Get("policy").(string), policyDocument)

policyToSet, err := verify.PolicyToSet(d.Get("policy").(string), policyDocument)
if err != nil {
return fmt.Errorf("while setting policy (%s), encountered: %w", policyToSet, err)
}

policyToSet, err = structure.NormalizeJsonString(policyToSet)

if err != nil {
return fmt.Errorf("policy (%s) is invalid JSON: %w", policyToSet, err)
}

d.Set("policy", policyToSet)

return nil
Expand All @@ -285,7 +281,6 @@ func resourcePolicyUpdate(d *schema.ResourceData, meta interface{}) error {
}

policy, err := structure.NormalizeJsonString(d.Get("policy").(string))

if err != nil {
return fmt.Errorf("policy (%s) is invalid JSON: %w", policy, err)
}
Expand Down
158 changes: 158 additions & 0 deletions internal/service/iam/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,94 @@ func TestAccIAMPolicy_policy(t *testing.T) {
})
}

// https://github.com/hashicorp/terraform-provider-aws/issues/28833
func TestAccIAMPolicy_diffs(t *testing.T) {
var out iam.GetPolicyOutput
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_iam_policy.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ErrorCheck: acctest.ErrorCheck(t, iam.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckPolicyDestroy,
Steps: []resource.TestStep{
{
Config: testAccPolicyConfig_diffs(rName, ""),
Check: resource.ComposeTestCheckFunc(
testAccCheckPolicyExists(resourceName, &out),
resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccPolicyConfig_diffs(rName, ""),
Check: resource.ComposeTestCheckFunc(
testAccCheckPolicyExists(resourceName, &out),
resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
),
},
{
Config: testAccPolicyConfig_diffs(rName, ""),
PlanOnly: true,
},
{
Config: testAccPolicyConfig_diffs(rName, ""),
PlanOnly: true,
},
{
Config: testAccPolicyConfig_diffs(rName, ""),
PlanOnly: true,
},
{
Config: testAccPolicyConfig_diffs(rName, ""),
PlanOnly: true,
},
{
Config: testAccPolicyConfig_diffs(rName, "tags = {}"),
Check: resource.ComposeTestCheckFunc(
testAccCheckPolicyExists(resourceName, &out),
resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
),
},
{
Config: testAccPolicyConfig_diffs(rName, "tags = {}"),
Check: resource.ComposeTestCheckFunc(
testAccCheckPolicyExists(resourceName, &out),
resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
),
},
{
Config: testAccPolicyConfig_diffs(rName, "tags = {}"),
PlanOnly: true,
},
{
Config: testAccPolicyConfig_diffs(rName, "tags = {}"),
PlanOnly: true,
},
{
Config: testAccPolicyConfig_diffs(rName, "tags = {}"),
PlanOnly: true,
},
{
Config: testAccPolicyConfig_diffs(rName, "tags = {}"),
PlanOnly: true,
},
{
Config: testAccPolicyConfig_diffs(rName, "tags = {}"),
Check: resource.ComposeTestCheckFunc(
testAccCheckPolicyExists(resourceName, &out),
resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
),
},
},
})
}

func testAccCheckPolicyExists(resource string, res *iam.GetPolicyOutput) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resource]
Expand Down Expand Up @@ -448,3 +536,73 @@ EOF
}
`, rName, tagKey1, tagValue1, tagKey2, tagValue2)
}

func testAccPolicyConfig_diffs(rName string, tags string) string {
return fmt.Sprintf(`
resource "aws_iam_policy" "test" {
name = %[1]q

policy = jsonencode({
Id = %[1]q
Version = "2012-10-17"
Statement = [{
Sid = "60c9d11f"
Effect = "Allow"
Action = [
"kms:Describe*",
"kms:Get*",
"kms:List*",
"kms:CreateKey",
"kms:DescribeKey",
"kms:ScheduleKeyDeletion",
"kms:TagResource",
"kms:UntagResource",
"s3:ListMultipartUploadParts",
"s3:ListBucketVersions",
"s3:ListBucketMultipartUploads",
"s3:ListBucket",
"s3:GetReplicationConfiguration",
"s3:GetObjectVersionTorrent",
"s3:GetObjectVersionTagging",
"s3:GetObjectVersionForReplication",
"s3:GetObjectVersionAcl",
"s3:GetObjectVersion",
"s3:GetObjectTorrent",
"s3:GetObjectTagging",
"s3:GetObjectRetention",
"s3:GetObjectLegalHold",
"s3:GetObjectAcl",
"s3:GetObject",
"s3:GetMetricsConfiguration",
"s3:GetLifecycleConfiguration",
"s3:GetJobTagging",
"s3:GetInventoryConfiguration",
"s3:GetEncryptionConfiguration",
"s3:GetBucketWebsite",
"s3:GetBucketVersioning",
"s3:GetBucketTagging",
"s3:GetBucketRequestPayment",
"s3:GetBucketPublicAccessBlock",
"s3:GetBucketPolicyStatus",
"s3:GetBucketPolicy",
"s3:GetBucketOwnershipControls",
"s3:GetBucketObjectLockConfiguration",
"s3:GetBucketNotification",
"s3:GetBucketLogging",
"s3:GetBucketLocation",
"s3:GetBucketCORS",
"s3:GetBucketAcl",
"s3:GetAnalyticsConfiguration",
"s3:GetAccessPointPolicyStatus",
"s3:GetAccessPointPolicy",
"s3:GetAccelerateConfiguration",
"s3:DescribeJob",
]
Resource = "*"
}]
})

%[2]s
}
`, rName, tags)
}
26 changes: 12 additions & 14 deletions internal/service/iam/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@ func ResourceRole() *schema.Resource {
Read: resourceRoleRead,
Update: resourceRoleUpdate,
Delete: resourceRoleDelete,

Importer: &schema.ResourceImporter{
State: resourceRoleImport,
},

Schema: map[string]*schema.Schema{
"arn": {
Type: schema.TypeString,
Expand Down Expand Up @@ -93,6 +95,10 @@ func ResourceRole() *schema.Resource {
ValidateFunc: verify.ValidIAMPolicyJSON,
DiffSuppressFunc: verify.SuppressEquivalentPolicyDiffs,
DiffSuppressOnRefresh: true,
StateFunc: func(v interface{}) string {
json, _ := verify.LegacyPolicyNormalize(v)
return json
},
},
},
},
Expand Down Expand Up @@ -170,7 +176,6 @@ func resourceRoleCreate(d *schema.ResourceData, meta interface{}) error {
tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{})))

assumeRolePolicy, err := structure.NormalizeJsonString(d.Get("assume_role_policy").(string))

if err != nil {
return fmt.Errorf("assume_role_policy (%s) is invalid JSON: %w", assumeRolePolicy, err)
}
Expand Down Expand Up @@ -287,13 +292,11 @@ func resourceRoleRead(d *schema.ResourceData, meta interface{}) error {
d.Set("unique_id", role.RoleId)

assumeRolePolicy, err := url.QueryUnescape(aws.StringValue(role.AssumeRolePolicyDocument))

if err != nil {
return err
}

policyToSet, err := verify.PolicyToSet(d.Get("assume_role_policy").(string), assumeRolePolicy)

if err != nil {
return err
}
Expand Down Expand Up @@ -341,7 +344,6 @@ func resourceRoleUpdate(d *schema.ResourceData, meta interface{}) error {

if d.HasChange("assume_role_policy") {
assumeRolePolicy, err := structure.NormalizeJsonString(d.Get("assume_role_policy").(string))

if err != nil {
return fmt.Errorf("assume_role_policy (%s) is invalid JSON: %w", assumeRolePolicy, err)
}
Expand Down Expand Up @@ -857,24 +859,20 @@ func readRoleInlinePolicies(roleName string, meta interface{}) ([]*iam.PutRolePo
return nil, err
}

p, err := verify.LegacyPolicyNormalize(policy)
if err != nil {
return nil, fmt.Errorf("policy (%s) is invalid JSON: %w", p, err)
}

apiObject := &iam.PutRolePolicyInput{
RoleName: aws.String(roleName),
PolicyDocument: aws.String(policy),
PolicyDocument: aws.String(p),
PolicyName: policyName,
}

apiObjects = append(apiObjects, apiObject)
}

/*
if len(apiObjects) == 0 {
apiObjects = append(apiObjects, &iam.PutRolePolicyInput{
PolicyDocument: aws.String(""),
PolicyName: aws.String(""),
})
}
*/

return apiObjects, nil
}

Expand Down
Loading