From 41c23b2f04acc2c2c1d3b183caf8c64d4b1698e1 Mon Sep 17 00:00:00 2001 From: Chris Marchesi Date: Tue, 9 Aug 2016 17:06:38 -0700 Subject: [PATCH] provider/aws: Various IAM policy normalizations for IAM data source (#6956) * 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 8d5051f771..5bea111eec 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 8a2210265f..a50a8ae29e 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 56ffc9d5cf..59192fbf11 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 }