Skip to content

Commit

Permalink
service/iam: Policy tagging support and add policy_id attribute (#18276)
Browse files Browse the repository at this point in the history
Output from acceptance testing in AWS Commercial:

```
--- PASS: TestAccAWSDataSourceIAMPolicy_basic (20.45s)

--- PASS: TestAccAWSIAMPolicy_basic (22.11s)
--- PASS: TestAccAWSIAMPolicy_description (21.92s)
--- PASS: TestAccAWSIAMPolicy_disappears (18.51s)
--- PASS: TestAccAWSIAMPolicy_namePrefix (22.36s)
--- PASS: TestAccAWSIAMPolicy_path (22.27s)
--- PASS: TestAccAWSIAMPolicy_policy (29.66s)
--- PASS: TestAccAWSIAMPolicy_tags (36.52s)
```

Output from acceptance testing in AWS GovCloud (US):

```
--- PASS: TestAccAWSDataSourceIAMPolicy_basic (25.35s)

--- PASS: TestAccAWSIAMPolicy_basic (27.37s)
--- PASS: TestAccAWSIAMPolicy_description (27.37s)
--- PASS: TestAccAWSIAMPolicy_disappears (21.46s)
--- PASS: TestAccAWSIAMPolicy_namePrefix (27.86s)
--- PASS: TestAccAWSIAMPolicy_path (27.19s)
--- PASS: TestAccAWSIAMPolicy_policy (41.21s)
--- PASS: TestAccAWSIAMPolicy_tags (53.53s)
```
  • Loading branch information
DrFaust92 authored Mar 26, 2021
1 parent 0994f68 commit 3a08c22
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 65 deletions.
11 changes: 11 additions & 0 deletions .changelog/18276.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```release-note:enhancement
resource/aws_iam_policy: Add tagging support
```

```release-note:enhancement
resource/aws_iam_policy: Add `policy_id` attribute
```

```release-note:enhancement
data-source/aws_iam_policy: Add `policy_id` and `tags` attributes
```
5 changes: 5 additions & 0 deletions aws/data_source_aws_iam_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ func dataSourceAwsIAMPolicy() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"policy_id": {
Type: schema.TypeString,
Computed: true,
},
"tags": tagsSchemaComputed(),
},
}
}
Expand Down
19 changes: 11 additions & 8 deletions aws/data_source_aws_iam_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import (
)

func TestAccAWSDataSourceIAMPolicy_basic(t *testing.T) {
resourceName := "data.aws_iam_policy.test"
datasourceName := "data.aws_iam_policy.test"
resourceName := "aws_iam_policy.test"
policyName := fmt.Sprintf("test-policy-%s", acctest.RandString(10))

resource.ParallelTest(t, resource.TestCase{
Expand All @@ -21,11 +22,13 @@ func TestAccAWSDataSourceIAMPolicy_basic(t *testing.T) {
{
Config: testAccAwsDataSourceIamPolicyConfig(policyName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "name", policyName),
resource.TestCheckResourceAttr(resourceName, "description", "My test policy"),
resource.TestCheckResourceAttr(resourceName, "path", "/"),
resource.TestCheckResourceAttrSet(resourceName, "policy"),
resource.TestCheckResourceAttrPair(resourceName, "arn", "aws_iam_policy.test_policy", "arn"),
resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, "name"),
resource.TestCheckResourceAttrPair(datasourceName, "description", resourceName, "description"),
resource.TestCheckResourceAttrPair(datasourceName, "path", resourceName, "path"),
resource.TestCheckResourceAttrPair(datasourceName, "policy", resourceName, "policy"),
resource.TestCheckResourceAttrPair(datasourceName, "policy_id", resourceName, "policy_id"),
resource.TestCheckResourceAttrPair(datasourceName, "arn", resourceName, "arn"),
resource.TestCheckResourceAttrPair(datasourceName, "tags", resourceName, "tags"),
),
},
},
Expand All @@ -35,7 +38,7 @@ func TestAccAWSDataSourceIAMPolicy_basic(t *testing.T) {

func testAccAwsDataSourceIamPolicyConfig(policyName string) string {
return fmt.Sprintf(`
resource "aws_iam_policy" "test_policy" {
resource "aws_iam_policy" "test" {
name = "%s"
path = "/"
description = "My test policy"
Expand All @@ -57,7 +60,7 @@ EOF
}
data "aws_iam_policy" "test" {
arn = aws_iam_policy.test_policy.arn
arn = aws_iam_policy.test.arn
}
`, policyName)
}
35 changes: 35 additions & 0 deletions aws/internal/keyvaluetags/iam_tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,41 @@ func IamOpenIDConnectProviderUpdateTags(conn *iam.IAM, identifier string, oldTag
return nil
}

// IamPolicyUpdateTags updates IAM Policy tags.
// The identifier is the Policy ARN.
func IamPolicyUpdateTags(conn *iam.IAM, identifier string, oldTagsMap interface{}, newTagsMap interface{}) error {
oldTags := New(oldTagsMap)
newTags := New(newTagsMap)

if removedTags := oldTags.Removed(newTags); len(removedTags) > 0 {
input := &iam.UntagPolicyInput{
PolicyArn: aws.String(identifier),
TagKeys: aws.StringSlice(removedTags.Keys()),
}

_, err := conn.UntagPolicy(input)

if err != nil {
return fmt.Errorf("error untagging resource (%s): %w", identifier, err)
}
}

if updatedTags := oldTags.Updated(newTags); len(updatedTags) > 0 {
input := &iam.TagPolicyInput{
PolicyArn: aws.String(identifier),
Tags: updatedTags.IgnoreAws().IamTags(),
}

_, err := conn.TagPolicy(input)

if err != nil {
return fmt.Errorf("error tagging resource (%s): %w", identifier, err)
}
}

return nil
}

// IamSAMLProviderUpdateTags updates IAM SAML Provider tags.
// The identifier is the SAML Provider ARN.
func IamSAMLProviderUpdateTags(conn *iam.IAM, identifier string, oldTagsMap interface{}, newTagsMap interface{}) error {
Expand Down
109 changes: 67 additions & 42 deletions aws/resource_aws_iam_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/waiter"
)

Expand Down Expand Up @@ -67,12 +68,17 @@ func resourceAwsIamPolicy() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"policy_id": {
Type: schema.TypeString,
Computed: true,
},
"tags": tagsSchema(),
},
}
}

func resourceAwsIamPolicyCreate(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
conn := meta.(*AWSClient).iamconn

var name string
if v, ok := d.GetOk("name"); ok {
Expand All @@ -88,11 +94,12 @@ func resourceAwsIamPolicyCreate(d *schema.ResourceData, meta interface{}) error
Path: aws.String(d.Get("path").(string)),
PolicyDocument: aws.String(d.Get("policy").(string)),
PolicyName: aws.String(name),
Tags: keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws().IamTags(),
}

response, err := iamconn.CreatePolicy(request)
response, err := conn.CreatePolicy(request)
if err != nil {
return fmt.Errorf("Error creating IAM policy %s: %s", name, err)
return fmt.Errorf("Error creating IAM policy %s: %w", name, err)
}

d.SetId(aws.StringValue(response.Policy.Arn))
Expand All @@ -101,7 +108,8 @@ func resourceAwsIamPolicyCreate(d *schema.ResourceData, meta interface{}) error
}

func resourceAwsIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
conn := meta.(*AWSClient).iamconn
ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig

getPolicyRequest := &iam.GetPolicyInput{
PolicyArn: aws.String(d.Id()),
Expand All @@ -112,7 +120,7 @@ func resourceAwsIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
var getPolicyResponse *iam.GetPolicyOutput
err := resource.Retry(waiter.PropagationTimeout, func() *resource.RetryError {
var err error
getPolicyResponse, err = iamconn.GetPolicy(getPolicyRequest)
getPolicyResponse, err = conn.GetPolicy(getPolicyRequest)

if d.IsNewResource() && isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") {
return resource.RetryableError(err)
Expand All @@ -125,7 +133,7 @@ func resourceAwsIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
return nil
})
if isResourceTimeoutError(err) {
getPolicyResponse, err = iamconn.GetPolicy(getPolicyRequest)
getPolicyResponse, err = conn.GetPolicy(getPolicyRequest)
}
if isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") {
log.Printf("[WARN] IAM Policy (%s) not found, removing from state", d.Id())
Expand All @@ -134,7 +142,7 @@ func resourceAwsIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
}

if err != nil {
return fmt.Errorf("Error reading IAM policy %s: %s", d.Id(), err)
return fmt.Errorf("Error reading IAM policy %s: %w", d.Id(), err)
}

if getPolicyResponse == nil || getPolicyResponse.Policy == nil {
Expand All @@ -143,24 +151,30 @@ func resourceAwsIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
return nil
}

d.Set("arn", getPolicyResponse.Policy.Arn)
d.Set("description", getPolicyResponse.Policy.Description)
d.Set("name", getPolicyResponse.Policy.PolicyName)
d.Set("path", getPolicyResponse.Policy.Path)
policyRes := getPolicyResponse.Policy
d.Set("arn", policyRes.Arn)
d.Set("description", policyRes.Description)
d.Set("name", policyRes.PolicyName)
d.Set("path", policyRes.Path)
d.Set("policy_id", policyRes.PolicyId)

if err := d.Set("tags", keyvaluetags.IamKeyValueTags(policyRes.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil {
return fmt.Errorf("error setting tags: %w", err)
}

// Retrieve policy

getPolicyVersionRequest := &iam.GetPolicyVersionInput{
PolicyArn: aws.String(d.Id()),
VersionId: getPolicyResponse.Policy.DefaultVersionId,
VersionId: policyRes.DefaultVersionId,
}
log.Printf("[DEBUG] Getting IAM Policy Version: %s", getPolicyVersionRequest)

// Handle IAM eventual consistency
var getPolicyVersionResponse *iam.GetPolicyVersionOutput
err = resource.Retry(waiter.PropagationTimeout, func() *resource.RetryError {
var err error
getPolicyVersionResponse, err = iamconn.GetPolicyVersion(getPolicyVersionRequest)
getPolicyVersionResponse, err = conn.GetPolicyVersion(getPolicyVersionRequest)

if isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") {
return resource.RetryableError(err)
Expand All @@ -173,7 +187,7 @@ func resourceAwsIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
return nil
})
if isResourceTimeoutError(err) {
getPolicyVersionResponse, err = iamconn.GetPolicyVersion(getPolicyVersionRequest)
getPolicyVersionResponse, err = conn.GetPolicyVersion(getPolicyVersionRequest)
}
if isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") {
log.Printf("[WARN] IAM Policy (%s) not found, removing from state", d.Id())
Expand All @@ -182,15 +196,15 @@ func resourceAwsIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
}

if err != nil {
return fmt.Errorf("Error reading IAM policy version %s: %s", d.Id(), err)
return fmt.Errorf("Error reading IAM policy version %s: %w", d.Id(), err)
}

policy := ""
if getPolicyVersionResponse != nil && getPolicyVersionResponse.PolicyVersion != nil {
var err error
policy, err = url.QueryUnescape(aws.StringValue(getPolicyVersionResponse.PolicyVersion.Document))
if err != nil {
return fmt.Errorf("error parsing policy: %s", err)
return fmt.Errorf("error parsing policy: %w", err)
}
}

Expand All @@ -200,41 +214,52 @@ func resourceAwsIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
}

func resourceAwsIamPolicyUpdate(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
conn := meta.(*AWSClient).iamconn

if err := iamPolicyPruneVersions(d.Id(), iamconn); err != nil {
return err
}
if d.HasChangeExcept("tags") {

request := &iam.CreatePolicyVersionInput{
PolicyArn: aws.String(d.Id()),
PolicyDocument: aws.String(d.Get("policy").(string)),
SetAsDefault: aws.Bool(true),
if err := iamPolicyPruneVersions(d.Id(), conn); err != nil {
return err
}

request := &iam.CreatePolicyVersionInput{
PolicyArn: aws.String(d.Id()),
PolicyDocument: aws.String(d.Get("policy").(string)),
SetAsDefault: aws.Bool(true),
}

if _, err := conn.CreatePolicyVersion(request); err != nil {
return fmt.Errorf("Error updating IAM policy %s: %w", d.Id(), err)
}
}

if _, err := iamconn.CreatePolicyVersion(request); err != nil {
return fmt.Errorf("Error updating IAM policy %s: %s", d.Id(), err)
if d.HasChange("tags") {
o, n := d.GetChange("tags")

if err := keyvaluetags.IamPolicyUpdateTags(conn, d.Id(), o, n); err != nil {
return fmt.Errorf("error updating tags for IAM Policy (%s): %w", d.Id(), err)
}
}

return resourceAwsIamPolicyRead(d, meta)
}

func resourceAwsIamPolicyDelete(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
conn := meta.(*AWSClient).iamconn

if err := iamPolicyDeleteNondefaultVersions(d.Id(), iamconn); err != nil {
if err := iamPolicyDeleteNondefaultVersions(d.Id(), conn); err != nil {
return err
}

request := &iam.DeletePolicyInput{
PolicyArn: aws.String(d.Id()),
}

if _, err := iamconn.DeletePolicy(request); err != nil {
if _, err := conn.DeletePolicy(request); err != nil {
if isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") {
return nil
}
return fmt.Errorf("Error deleting IAM policy %s: %s", d.Id(), err)
return fmt.Errorf("Error deleting IAM policy %s: %w", d.Id(), err)
}

return nil
Expand All @@ -247,8 +272,8 @@ func resourceAwsIamPolicyDelete(d *schema.ResourceData, meta interface{}) error
//
// The default version is never deleted.

func iamPolicyPruneVersions(arn string, iamconn *iam.IAM) error {
versions, err := iamPolicyListVersions(arn, iamconn)
func iamPolicyPruneVersions(arn string, conn *iam.IAM) error {
versions, err := iamPolicyListVersions(arn, conn)
if err != nil {
return err
}
Expand All @@ -268,12 +293,12 @@ func iamPolicyPruneVersions(arn string, iamconn *iam.IAM) error {
}
}

err1 := iamPolicyDeleteVersion(arn, *oldestVersion.VersionId, iamconn)
err1 := iamPolicyDeleteVersion(arn, aws.StringValue(oldestVersion.VersionId), conn)
return err1
}

func iamPolicyDeleteNondefaultVersions(arn string, iamconn *iam.IAM) error {
versions, err := iamPolicyListVersions(arn, iamconn)
func iamPolicyDeleteNondefaultVersions(arn string, conn *iam.IAM) error {
versions, err := iamPolicyListVersions(arn, conn)
if err != nil {
return err
}
Expand All @@ -282,35 +307,35 @@ func iamPolicyDeleteNondefaultVersions(arn string, iamconn *iam.IAM) error {
if *version.IsDefaultVersion {
continue
}
if err := iamPolicyDeleteVersion(arn, *version.VersionId, iamconn); err != nil {
if err := iamPolicyDeleteVersion(arn, aws.StringValue(version.VersionId), conn); err != nil {
return err
}
}

return nil
}

func iamPolicyDeleteVersion(arn, versionID string, iamconn *iam.IAM) error {
func iamPolicyDeleteVersion(arn, versionID string, conn *iam.IAM) error {
request := &iam.DeletePolicyVersionInput{
PolicyArn: aws.String(arn),
VersionId: aws.String(versionID),
}

_, err := iamconn.DeletePolicyVersion(request)
_, err := conn.DeletePolicyVersion(request)
if err != nil {
return fmt.Errorf("Error deleting version %s from IAM policy %s: %s", versionID, arn, err)
return fmt.Errorf("Error deleting version %s from IAM policy %s: %w", versionID, arn, err)
}
return nil
}

func iamPolicyListVersions(arn string, iamconn *iam.IAM) ([]*iam.PolicyVersion, error) {
func iamPolicyListVersions(arn string, conn *iam.IAM) ([]*iam.PolicyVersion, error) {
request := &iam.ListPolicyVersionsInput{
PolicyArn: aws.String(arn),
}

response, err := iamconn.ListPolicyVersions(request)
response, err := conn.ListPolicyVersions(request)
if err != nil {
return nil, fmt.Errorf("Error listing versions for IAM policy %s: %s", arn, err)
return nil, fmt.Errorf("Error listing versions for IAM policy %s: %w", arn, err)
}
return response.Versions, nil
}
Loading

0 comments on commit 3a08c22

Please sign in to comment.