Skip to content

Commit

Permalink
Bugfix st-aws_iam_policy on attaching long IAM policy. (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
styumyum authored Nov 15, 2023
1 parent a3fc3a7 commit e9f6985
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 38 deletions.
111 changes: 75 additions & 36 deletions aws/resource_iam_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package aws
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
"strconv"
Expand Down Expand Up @@ -54,7 +53,11 @@ func (r *iamPolicyResource) Metadata(_ context.Context, req resource.MetadataReq

func (r *iamPolicyResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Provides a RAM Policy resource that manages policy content exceeding character limits by splitting it into smaller segments. These segments are combined to form a complete policy attached to the user.",
Description: "Provides a RAM Policy resource that manages policy content " +
"exceeding character limits by splitting it into smaller segments. " +
"These segments are combined to form a complete policy attached to the user. " +
"However, the policy like `ReadOnlyAccess` that exceed the maximum length " +
"of a policy, they will be attached directly to the user.",
Attributes: map[string]schema.Attribute{
"attached_policies": schema.ListAttribute{
Description: "The RAM policies to attach to the user.",
Expand Down Expand Up @@ -363,13 +366,13 @@ func (r *iamPolicyResource) ImportState(ctx context.Context, req resource.Import
}

func (r *iamPolicyResource) createPolicy(ctx context.Context, plan *iamPolicyResourceModel) (policiesList []attr.Value, err error) {
formattedPolicy, err := r.getPolicyDocument(ctx, plan)
combinedPolicyStatements, notCombinedPolicies, err := r.getPolicyDocument(ctx, plan)
if err != nil {
return nil, err
}

createPolicy := func() error {
for i, policy := range formattedPolicy {
for i, policy := range combinedPolicyStatements {
policyName := plan.UserName.ValueString() + "-" + strconv.Itoa(i+1)

createPolicyRequest := &awsIamClient.CreatePolicyInput{
Expand All @@ -385,7 +388,7 @@ func (r *iamPolicyResource) createPolicy(ctx context.Context, plan *iamPolicyRes
return nil
}

for i, policies := range formattedPolicy {
for i, policy := range combinedPolicyStatements {
policyName := plan.UserName.ValueString() + "-" + strconv.Itoa(i+1)

policyObj := types.ObjectValueMust(
Expand All @@ -395,7 +398,25 @@ func (r *iamPolicyResource) createPolicy(ctx context.Context, plan *iamPolicyRes
},
map[string]attr.Value{
"policy_name": types.StringValue(policyName),
"policy_document": types.StringValue(policies),
"policy_document": types.StringValue(policy),
},
)

policiesList = append(policiesList, policyObj)
}

// These policies will be attached directly to the user since splitting the
// policy "statement" will be hitting the limitation of "maximum number of
// attached policies" easily.
for _, policy := range notCombinedPolicies {
policyObj := types.ObjectValueMust(
map[string]attr.Type{
"policy_name": types.StringType,
"policy_document": types.StringType,
},
map[string]attr.Value{
"policy_name": types.StringValue(policy.policyName),
"policy_document": types.StringValue(policy.policyDocument),
},
)

Expand Down Expand Up @@ -534,12 +555,16 @@ func (r *iamPolicyResource) removePolicy(ctx context.Context, state *iamPolicyRe
return nil
}

func (r *iamPolicyResource) getPolicyDocument(ctx context.Context, plan *iamPolicyResourceModel) (finalPolicyDocument []string, err error) {
type simplePolicy struct {
policyName string
policyDocument string
}

func (r *iamPolicyResource) getPolicyDocument(ctx context.Context, plan *iamPolicyResourceModel) (finalPolicyDocument []string, excludedPolicy []simplePolicy, err error) {
policyName := ""
currentLength := 0
currentPolicyDocument := ""
appendedPolicyDocument := make([]string, 0)
finalPolicyDocument = make([]string, 0)

var getPolicyResponse *awsIamClient.GetPolicyVersionOutput

Expand Down Expand Up @@ -568,41 +593,55 @@ func (r *iamPolicyResource) getPolicyDocument(ctx context.Context, plan *iamPoli
if getPolicyResponse.PolicyVersion != nil {
tempPolicyDocument, err := url.QueryUnescape(*getPolicyResponse.PolicyVersion.Document)
if err != nil {
return nil, err
return nil, nil, err
}

var data map[string]interface{}
if err := json.Unmarshal([]byte(tempPolicyDocument), &data); err != nil {
return nil, err
skipCombinePolicy := false
// If the policy itself have more than 6144 characters, then skip the combine
// policy part since splitting the policy "statement" will be hitting the
// limitation of "maximum number of attached policies" easily.
if len(tempPolicyDocument) > maxLength {
excludedPolicy = append(excludedPolicy, simplePolicy{
policyName: policyName,
policyDocument: tempPolicyDocument,
})
skipCombinePolicy = true
}

statementArr := data["Statement"].([]interface{})
statementBytes, err := json.MarshalIndent(statementArr, "", " ")
if err != nil {
return nil, err
}
if !skipCombinePolicy {
var data map[string]interface{}
if err := json.Unmarshal([]byte(tempPolicyDocument), &data); err != nil {
return nil, nil, err
}

removeSpaces := strings.ReplaceAll(string(statementBytes), " ", "")
replacer := strings.NewReplacer("\n", "")
removeParagraphs := replacer.Replace(removeSpaces)
statementArr := data["Statement"].([]interface{})
statementBytes, err := json.MarshalIndent(statementArr, "", " ")
if err != nil {
return nil, nil, err
}

finalStatement := strings.Trim(removeParagraphs, "[]")
removeSpaces := strings.ReplaceAll(string(statementBytes), " ", "")
replacer := strings.NewReplacer("\n", "")
removeParagraphs := replacer.Replace(removeSpaces)

currentLength += len(finalStatement)
finalStatement := strings.Trim(removeParagraphs, "[]")

// Before further proceeding the current policy, we need to add a number of 30 to simulate the total length of completed policy to check whether it is already execeeded the max character length of 6144.
// Number of 30 indicates the character length of neccessary policy keyword such as "Version" and "Statement" and some JSON symbols ({}, [])
if (currentLength + 30) > maxLength {
lastCommaIndex := strings.LastIndex(currentPolicyDocument, ",")
if lastCommaIndex >= 0 {
currentPolicyDocument = currentPolicyDocument[:lastCommaIndex] + currentPolicyDocument[lastCommaIndex+1:]
}
currentLength += len(finalStatement)

appendedPolicyDocument = append(appendedPolicyDocument, currentPolicyDocument)
currentPolicyDocument = finalStatement + ","
currentLength = len(finalStatement)
} else {
currentPolicyDocument += finalStatement + ","
// Before further proceeding the current policy, we need to add a number of 30 to simulate the total length of completed policy to check whether it is already execeeded the max character length of 6144.
// Number of 30 indicates the character length of neccessary policy keyword such as "Version" and "Statement" and some JSON symbols ({}, [])
if (currentLength + 30) > maxLength {
lastCommaIndex := strings.LastIndex(currentPolicyDocument, ",")
if lastCommaIndex >= 0 {
currentPolicyDocument = currentPolicyDocument[:lastCommaIndex] + currentPolicyDocument[lastCommaIndex+1:]
}

appendedPolicyDocument = append(appendedPolicyDocument, currentPolicyDocument)
currentPolicyDocument = finalStatement + ","
currentLength = len(finalStatement)
} else {
currentPolicyDocument += finalStatement + ","
}
}

if i == len(plan.AttachedPolicies.Elements())-1 && (currentLength+30) <= maxLength {
Expand All @@ -615,7 +654,7 @@ func (r *iamPolicyResource) getPolicyDocument(ctx context.Context, plan *iamPoli
}
}
} else {
return nil, errors.New(fmt.Sprintf("The %v policy not found.", policyName))
return nil, nil, fmt.Errorf("The %v policy not found.", policyName)
}
}

Expand All @@ -625,7 +664,7 @@ func (r *iamPolicyResource) getPolicyDocument(ctx context.Context, plan *iamPoli
}
}

return finalPolicyDocument, nil
return finalPolicyDocument, excludedPolicy, nil
}

func (r *iamPolicyResource) attachPolicyToUser(ctx context.Context, state *iamPolicyResourceModel) (err error) {
Expand Down
4 changes: 2 additions & 2 deletions docs/resources/iam_policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
page_title: "st-aws_iam_policy Resource - st-aws"
subcategory: ""
description: |-
Provides a RAM Policy resource that manages policy content exceeding character limits by splitting it into smaller segments. These segments are combined to form a complete policy attached to the user.
Provides a RAM Policy resource that manages policy content exceeding character limits by splitting it into smaller segments. These segments are combined to form a complete policy attached to the user. However, the policy like `ReadOnlyAccess` that exceed the maximum length of a policy, they will be attached directly to the user.
---

# st-aws_iam_policy (Resource)

Provides a RAM Policy resource that manages policy content exceeding character limits by splitting it into smaller segments. These segments are combined to form a complete policy attached to the user.
Provides a RAM Policy resource that manages policy content exceeding character limits by splitting it into smaller segments. These segments are combined to form a complete policy attached to the user. However, the policy like `ReadOnlyAccess` that exceed the maximum length of a policy, they will be attached directly to the user.

## Example Usage

Expand Down

0 comments on commit e9f6985

Please sign in to comment.