From 4ee5d7a5433a6b46343d4527fd9dc1cfced9f94e Mon Sep 17 00:00:00 2001 From: Peter Rifel Date: Mon, 21 Dec 2020 20:22:40 -0600 Subject: [PATCH] Add tagging support for AWS IAM Roles --- cloudmock/aws/mockiam/iamrole.go | 1 + pkg/model/iam.go | 1 + upup/pkg/fi/cloudup/awstasks/iamrole.go | 37 +++++++++++++++++++++++-- upup/pkg/fi/cloudup/awstasks/tags.go | 29 +++++++++++++++++++ 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/cloudmock/aws/mockiam/iamrole.go b/cloudmock/aws/mockiam/iamrole.go index e546de4685..39a70786a3 100644 --- a/cloudmock/aws/mockiam/iamrole.go +++ b/cloudmock/aws/mockiam/iamrole.go @@ -64,6 +64,7 @@ func (m *MockIAM) CreateRole(request *iam.CreateRoleInput) (*iam.CreateRoleOutpu }, RoleName: request.RoleName, RoleId: &roleID, + Tags: request.Tags, } if m.Roles == nil { diff --git a/pkg/model/iam.go b/pkg/model/iam.go index 7023095123..e54fdf5d13 100644 --- a/pkg/model/iam.go +++ b/pkg/model/iam.go @@ -136,6 +136,7 @@ func (b *IAMModelBuilder) buildIAMRole(role iam.Subject, iamName string, c *fi.M Lifecycle: b.Lifecycle, RolePolicyDocument: rolePolicy, + Tags: b.CloudTags(iamName, false), } if isServiceAccount { diff --git a/upup/pkg/fi/cloudup/awstasks/iamrole.go b/upup/pkg/fi/cloudup/awstasks/iamrole.go index 8fcead88de..e00200b01c 100644 --- a/upup/pkg/fi/cloudup/awstasks/iamrole.go +++ b/upup/pkg/fi/cloudup/awstasks/iamrole.go @@ -44,6 +44,8 @@ type IAMRole struct { RolePolicyDocument fi.Resource // "inline" IAM policy PermissionsBoundary *string + Tags map[string]string + // ExportWithId will expose the name & ARN for reuse as part of a larger system. Only supported by terraform currently. ExportWithID *string } @@ -110,6 +112,7 @@ func (e *IAMRole) Find(c *fi.Context) (*IAMRole, error) { actual.RolePolicyDocument = fi.NewStringResource(actualPolicy) } + actual.Tags = mapIAMTagsToMap(r.Tags) klog.V(2).Infof("found matching IAMRole %q", aws.StringValue(actual.ID)) e.ID = actual.ID @@ -150,6 +153,7 @@ func (_ *IAMRole) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *IAMRole) error request := &iam.CreateRoleInput{} request.AssumeRolePolicyDocument = aws.String(policy) request.RoleName = e.Name + request.Tags = mapToIAMTags(e.Tags) if e.PermissionsBoundary != nil { request.PermissionsBoundary = e.PermissionsBoundary @@ -215,7 +219,32 @@ func (_ *IAMRole) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *IAMRole) error return fmt.Errorf("error updating IAMRole: %v", err) } } - + } + if changes.Tags != nil { + if len(a.Tags) > 0 { + existingTagKeys := make([]*string, 0) + for k := range a.Tags { + existingTagKeys = append(existingTagKeys, &k) + } + untagRequest := &iam.UntagRoleInput{ + RoleName: e.Name, + TagKeys: existingTagKeys, + } + _, err = t.Cloud.IAM().UntagRole(untagRequest) + if err != nil { + return fmt.Errorf("error untagging IAMRole: %v", err) + } + } + if len(e.Tags) > 0 { + tagRequest := &iam.TagRoleInput{ + RoleName: e.Name, + Tags: mapToIAMTags(e.Tags), + } + _, err = t.Cloud.IAM().TagRole(tagRequest) + if err != nil { + return fmt.Errorf("error tagging IAMRole: %v", err) + } + } } } @@ -227,6 +256,7 @@ type terraformIAMRole struct { Name *string `json:"name" cty:"name"` AssumeRolePolicy *terraform.Literal `json:"assume_role_policy" cty:"assume_role_policy"` PermissionsBoundary *string `json:"permissions_boundary,omitempty" cty:"permissions_boundary"` + Tags map[string]string `json:"tags,omitempty" cty:"tags"` } func (_ *IAMRole) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *IAMRole) error { @@ -238,6 +268,7 @@ func (_ *IAMRole) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *I tf := &terraformIAMRole{ Name: e.Name, AssumeRolePolicy: policy, + Tags: e.Tags, } if e.PermissionsBoundary != nil { @@ -259,7 +290,8 @@ func (e *IAMRole) TerraformLink() *terraform.Literal { type cloudformationIAMRole struct { RoleName *string `json:"RoleName"` AssumeRolePolicyDocument map[string]interface{} - PermissionsBoundary *string `json:"PermissionsBoundary,omitempty"` + PermissionsBoundary *string `json:"PermissionsBoundary,omitempty"` + Tags []cloudformationTag `json:"Tags,omitempty"` } func (_ *IAMRole) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *IAMRole) error { @@ -277,6 +309,7 @@ func (_ *IAMRole) RenderCloudformation(t *cloudformation.CloudformationTarget, a cf := &cloudformationIAMRole{ RoleName: e.Name, AssumeRolePolicyDocument: data, + Tags: buildCloudformationTags(e.Tags), } if e.PermissionsBoundary != nil { diff --git a/upup/pkg/fi/cloudup/awstasks/tags.go b/upup/pkg/fi/cloudup/awstasks/tags.go index d63f72ec45..3627c92c01 100644 --- a/upup/pkg/fi/cloudup/awstasks/tags.go +++ b/upup/pkg/fi/cloudup/awstasks/tags.go @@ -21,6 +21,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/iam" ) func mapEC2TagsToMap(tags []*ec2.Tag) map[string]string { @@ -37,6 +38,34 @@ func mapEC2TagsToMap(tags []*ec2.Tag) map[string]string { return m } +func mapIAMTagsToMap(tags []*iam.Tag) map[string]string { + if tags == nil { + return nil + } + m := make(map[string]string) + for _, t := range tags { + if strings.HasPrefix(aws.StringValue(t.Key), "aws:cloudformation:") { + continue + } + m[aws.StringValue(t.Key)] = aws.StringValue(t.Value) + } + return m +} + +func mapToIAMTags(tags map[string]string) []*iam.Tag { + if tags == nil { + return nil + } + m := make([]*iam.Tag, 0) + for k, v := range tags { + m = append(m, &iam.Tag{ + Key: aws.String(k), + Value: aws.String(v), + }) + } + return m +} + func findNameTag(tags []*ec2.Tag) *string { for _, tag := range tags { if aws.StringValue(tag.Key) == "Name" {