From 9872026235b094ada8ef017cae9db2a1c0cac399 Mon Sep 17 00:00:00 2001 From: Chris Marchesi Date: Mon, 30 May 2016 07:55:33 -0700 Subject: [PATCH] provider/aws: Various IAM policy normalizations for IAM data source * Various string slices are sorted and truncated to strings if they only contain one element. * Sids are now included if they are empty. This is to ensure what is sent to AWS matches what comes back, to prevent recurring diffs even when the policy has changed. --- .../data_source_aws_iam_policy_document.go | 17 ++++-- ...ata_source_aws_iam_policy_document_test.go | 45 +++++---------- builtin/providers/aws/iam_policy_model.go | 55 +++++++++++++------ 3 files changed, 65 insertions(+), 52 deletions(-) diff --git a/builtin/providers/aws/data_source_aws_iam_policy_document.go b/builtin/providers/aws/data_source_aws_iam_policy_document.go index 8d5051f77135..5bea111eec60 100644 --- a/builtin/providers/aws/data_source_aws_iam_policy_document.go +++ b/builtin/providers/aws/data_source_aws_iam_policy_document.go @@ -150,12 +150,19 @@ func dataSourceAwsIamPolicyDocumentRead(d *schema.ResourceData, meta interface{} return nil } -func dataSourceAwsIamPolicyDocumentReplaceVarsInList(in []string) []string { - out := make([]string, len(in)) - for i, item := range in { - out[i] = dataSourceAwsIamPolicyDocumentVarReplacer.Replace(item) +func dataSourceAwsIamPolicyDocumentReplaceVarsInList(in interface{}) interface{} { + switch v := in.(type) { + case string: + return dataSourceAwsIamPolicyDocumentVarReplacer.Replace(v) + case []string: + out := make([]string, len(v)) + for i, item := range v { + out[i] = dataSourceAwsIamPolicyDocumentVarReplacer.Replace(item) + } + return out + default: + panic("dataSourceAwsIamPolicyDocumentReplaceVarsInList: input not string nor []string") } - return out } func dataSourceAwsIamPolicyDocumentMakeConditions(in []interface{}) IAMPolicyStatementConditionSet { diff --git a/builtin/providers/aws/data_source_aws_iam_policy_document_test.go b/builtin/providers/aws/data_source_aws_iam_policy_document_test.go index 8a2210265f9a..a50a8ae29ec3 100644 --- a/builtin/providers/aws/data_source_aws_iam_policy_document_test.go +++ b/builtin/providers/aws/data_source_aws_iam_policy_document_test.go @@ -75,7 +75,6 @@ data "aws_iam_policy_document" "test" { test = "StringLike" variable = "s3:prefix" values = [ - "", "home/", "home/&{aws:username}/", ] @@ -118,59 +117,45 @@ var testAccAWSIAMPolicyDocumentExpectedJSON = `{ "Sid": "1", "Effect": "Allow", "Action": [ - "s3:GetBucketLocation", - "s3:ListAllMyBuckets" + "s3:ListAllMyBuckets", + "s3:GetBucketLocation" ], - "Resource": [ - "arn:aws:s3:::*" - ] + "Resource": "arn:aws:s3:::*" }, { + "Sid": "", "Effect": "Allow", - "Action": [ - "s3:ListBucket" - ], - "Resource": [ - "arn:aws:s3:::foo" - ], + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::foo", "NotPrincipal": { - "AWS": [ - "arn:blahblah:example" - ] + "AWS": "arn:blahblah:example" }, "Condition": { "StringLike": { "s3:prefix": [ - "", - "home/", - "home/${aws:username}/" + "home/${aws:username}/", + "home/" ] } } }, { + "Sid": "", "Effect": "Allow", - "Action": [ - "s3:*" - ], + "Action": "s3:*", "Resource": [ "arn:aws:s3:::foo/home/${aws:username}/*", "arn:aws:s3:::foo/home/${aws:username}" ], "Principal": { - "AWS": [ - "arn:blahblah:example" - ] + "AWS": "arn:blahblah:example" } }, { + "Sid": "", "Effect": "Deny", - "NotAction": [ - "s3:*" - ], - "NotResource": [ - "arn:aws:s3:::*" - ] + "NotAction": "s3:*", + "NotResource": "arn:aws:s3:::*" } ] }` diff --git a/builtin/providers/aws/iam_policy_model.go b/builtin/providers/aws/iam_policy_model.go index 56ffc9d5cf0f..59192fbf11c0 100644 --- a/builtin/providers/aws/iam_policy_model.go +++ b/builtin/providers/aws/iam_policy_model.go @@ -2,6 +2,7 @@ package aws import ( "encoding/json" + "sort" ) type IAMPolicyDoc struct { @@ -11,12 +12,12 @@ type IAMPolicyDoc struct { } type IAMPolicyStatement struct { - Sid string `json:",omitempty"` + Sid string Effect string `json:",omitempty"` - Actions []string `json:"Action,omitempty"` - NotActions []string `json:"NotAction,omitempty"` - Resources []string `json:"Resource,omitempty"` - NotResources []string `json:"NotResource,omitempty"` + Actions interface{} `json:"Action,omitempty"` + NotActions interface{} `json:"NotAction,omitempty"` + Resources interface{} `json:"Resource,omitempty"` + NotResources interface{} `json:"NotResource,omitempty"` Principals IAMPolicyStatementPrincipalSet `json:"Principal,omitempty"` NotPrincipals IAMPolicyStatementPrincipalSet `json:"NotPrincipal,omitempty"` Conditions IAMPolicyStatementConditionSet `json:"Condition,omitempty"` @@ -24,51 +25,71 @@ type IAMPolicyStatement struct { type IAMPolicyStatementPrincipal struct { Type string - Identifiers []string + Identifiers interface{} } type IAMPolicyStatementCondition struct { Test string Variable string - Values []string + Values interface{} } type IAMPolicyStatementPrincipalSet []IAMPolicyStatementPrincipal type IAMPolicyStatementConditionSet []IAMPolicyStatementCondition func (ps IAMPolicyStatementPrincipalSet) MarshalJSON() ([]byte, error) { - raw := map[string][]string{} + raw := map[string]interface{}{} for _, p := range ps { - if _, ok := raw[p.Type]; !ok { - raw[p.Type] = make([]string, 0, len(p.Identifiers)) + switch i := p.Identifiers.(type) { + case []string: + if _, ok := raw[p.Type]; !ok { + raw[p.Type] = make([]string, 0, len(i)) + } + sort.Sort(sort.Reverse(sort.StringSlice(i))) + raw[p.Type] = append(raw[p.Type].([]string), i...) + case string: + raw[p.Type] = i + default: + panic("Unsupported data type for IAMPolicyStatementPrincipalSet") } - raw[p.Type] = append(raw[p.Type], p.Identifiers...) } return json.Marshal(&raw) } func (cs IAMPolicyStatementConditionSet) MarshalJSON() ([]byte, error) { - raw := map[string]map[string][]string{} + raw := map[string]map[string]interface{}{} for _, c := range cs { if _, ok := raw[c.Test]; !ok { - raw[c.Test] = map[string][]string{} + raw[c.Test] = map[string]interface{}{} } - if _, ok := raw[c.Test][c.Variable]; !ok { - raw[c.Test][c.Variable] = make([]string, 0, len(c.Values)) + switch i := c.Values.(type) { + case []string: + if _, ok := raw[c.Test][c.Variable]; !ok { + raw[c.Test][c.Variable] = make([]string, 0, len(i)) + } + sort.Sort(sort.Reverse(sort.StringSlice(i))) + raw[c.Test][c.Variable] = append(raw[c.Test][c.Variable].([]string), i...) + case string: + raw[c.Test][c.Variable] = i + default: + panic("Unsupported data type for IAMPolicyStatementConditionSet") } - raw[c.Test][c.Variable] = append(raw[c.Test][c.Variable], c.Values...) } return json.Marshal(&raw) } -func iamPolicyDecodeConfigStringList(lI []interface{}) []string { +func iamPolicyDecodeConfigStringList(lI []interface{}) interface{} { + if len(lI) == 1 { + return lI[0].(string) + } ret := make([]string, len(lI)) for i, vI := range lI { ret[i] = vI.(string) } + sort.Sort(sort.Reverse(sort.StringSlice(ret))) return ret }