Skip to content

Commit

Permalink
provider/aws: Various IAM policy normalizations for IAM data source
Browse files Browse the repository at this point in the history
 * 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.
  • Loading branch information
Chris Marchesi committed May 31, 2016
1 parent f162d91 commit cfe2d43
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 52 deletions.
17 changes: 12 additions & 5 deletions builtin/providers/aws/data_source_aws_iam_policy_document.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,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 {
Expand Down
46 changes: 16 additions & 30 deletions builtin/providers/aws/data_source_aws_iam_policy_document_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ data "aws_iam_policy_document" "test" {
test = "StringLike"
variable = "s3:prefix"
values = [
"",
"home/",
"home/&{aws:username}/",
]
Expand Down Expand Up @@ -112,61 +111,48 @@ var testAccAWSIAMPolicyDocumentExpectedJSON = `{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"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:::*"
}
]
}`
55 changes: 38 additions & 17 deletions builtin/providers/aws/iam_policy_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package aws

import (
"encoding/json"
"sort"
)

type IAMPolicyDoc struct {
Expand All @@ -11,64 +12,84 @@ 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"`
}

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
}

0 comments on commit cfe2d43

Please sign in to comment.