diff --git a/aws/data_source_aws_iam_policy_document.go b/aws/data_source_aws_iam_policy_document.go index 6376bd32ac1..cb14494f7d9 100644 --- a/aws/data_source_aws_iam_policy_document.go +++ b/aws/data_source_aws_iam_policy_document.go @@ -26,10 +26,18 @@ func dataSourceAwsIamPolicyDocument() *schema.Resource { Read: dataSourceAwsIamPolicyDocumentRead, Schema: map[string]*schema.Schema{ + "override_json": { + Type: schema.TypeString, + Optional: true, + }, "policy_id": { Type: schema.TypeString, Optional: true, }, + "source_json": { + Type: schema.TypeString, + Optional: true, + }, "statement": { Type: schema.TypeList, Required: true, @@ -85,10 +93,6 @@ func dataSourceAwsIamPolicyDocument() *schema.Resource { }, }, }, - "source_json": { - Type: schema.TypeString, - Optional: true, - }, "json": { Type: schema.TypeString, Computed: true, @@ -159,6 +163,24 @@ func dataSourceAwsIamPolicyDocumentRead(d *schema.ResourceData, meta interface{} doc.Statements = append(doc.Statements, stmts...) + // merge in our override_json + if overrideJson, hasOverrideJson := d.GetOk("override_json"); hasOverrideJson { + overrideDoc := &IAMPolicyDoc{} + if err := json.Unmarshal([]byte(overrideJson.(string)), overrideDoc); err != nil { + return err + } + + if len(overrideDoc.Id) > 0 { + doc.Id = overrideDoc.Id + } + + if len(overrideDoc.Statements) > 0 { + doc.Statements = append(doc.Statements, overrideDoc.Statements...) + } + } + + doc.DeDupSids() + jsonDoc, err := json.MarshalIndent(doc, "", " ") if err != nil { // should never happen if the above code is correct diff --git a/aws/data_source_aws_iam_policy_document_test.go b/aws/data_source_aws_iam_policy_document_test.go index a38d55164b1..bd7367f62bc 100644 --- a/aws/data_source_aws_iam_policy_document_test.go +++ b/aws/data_source_aws_iam_policy_document_test.go @@ -56,6 +56,40 @@ func TestAccAWSDataSourceIAMPolicyDocument_source(t *testing.T) { }) } +func TestAccAWSDataSourceIAMPolicyDocument_sourceConflicting(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccAWSIAMPolicyDocumentSourceConflictingConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckStateValue("data.aws_iam_policy_document.test_source_conflicting", "json", + testAccAWSIAMPolicyDocumentSourceConflictingExpectedJSON, + ), + ), + }, + }, + }) +} + +func TestAccAWSDataSourceIAMPolicyDocument_override(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccAWSIAMPolicyDocumentOverrideConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckStateValue("data.aws_iam_policy_document.test_override", "json", + testAccAWSIAMPolicyDocumentOverrideExpectedJSON, + ), + ), + }, + }, + }) +} + func testAccCheckStateValue(id, name, value string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[id] @@ -395,3 +429,84 @@ var testAccAWSIAMPolicyDocumentSourceBlankExpectedJSON = `{ } ] }` + +var testAccAWSIAMPolicyDocumentSourceConflictingConfig = ` +data "aws_iam_policy_document" "test_source" { + statement { + sid = "SourceJSONTestConflicting" + actions = ["iam:*"] + resources = ["*"] + } +} + +data "aws_iam_policy_document" "test_source_conflicting" { + source_json = "${data.aws_iam_policy_document.test_source.json}" + + statement { + sid = "SourceJSONTestConflicting" + actions = ["*"] + resources = ["*"] + } +} +` + +var testAccAWSIAMPolicyDocumentSourceConflictingExpectedJSON = `{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "SourceJSONTestConflicting", + "Effect": "Allow", + "Action": "*", + "Resource": "*" + } + ] +}` + +var testAccAWSIAMPolicyDocumentOverrideConfig = ` +data "aws_iam_policy_document" "override" { + statement { + sid = "SidToOverwrite" + + actions = ["s3:*"] + resources = ["*"] + } +} + +data "aws_iam_policy_document" "test_override" { + override_json = "${data.aws_iam_policy_document.override.json}" + + statement { + actions = ["ec2:*"] + resources = ["*"] + } + + statement { + sid = "SidToOverwrite" + + actions = ["s3:*"] + + resources = [ + "arn:aws:s3:::somebucket", + "arn:aws:s3:::somebucket/*", + ] + } +} +` + +var testAccAWSIAMPolicyDocumentOverrideExpectedJSON = `{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "", + "Effect": "Allow", + "Action": "ec2:*", + "Resource": "*" + }, + { + "Sid": "SidToOverwrite", + "Effect": "Allow", + "Action": "s3:*", + "Resource": "*" + } + ] +}` diff --git a/aws/iam_policy_model.go b/aws/iam_policy_model.go index 861f2847aba..aa3db834ec9 100644 --- a/aws/iam_policy_model.go +++ b/aws/iam_policy_model.go @@ -38,6 +38,22 @@ type IAMPolicyStatementCondition struct { type IAMPolicyStatementPrincipalSet []IAMPolicyStatementPrincipal type IAMPolicyStatementConditionSet []IAMPolicyStatementCondition +func (self *IAMPolicyDoc) DeDupSids() { + // de-dupe the statements by traversing backwards and removing duplicate Sids + sidsSeen := map[string]bool{} + l := len(self.Statements) - 1 + for i := range self.Statements { + if sid := self.Statements[l-i].Sid; len(sid) > 0 { + if sidsSeen[sid] { + // we've seen this sid already so remove the duplicate + self.Statements = append(self.Statements[:l-i], self.Statements[l-i+1:]...) + } + // mark this sid seen + sidsSeen[sid] = true + } + } +} + func (ps IAMPolicyStatementPrincipalSet) MarshalJSON() ([]byte, error) { raw := map[string]interface{}{} diff --git a/website/docs/d/iam_policy_document.html.markdown b/website/docs/d/iam_policy_document.html.markdown index 7264ac3d73f..6f3d48781c1 100644 --- a/website/docs/d/iam_policy_document.html.markdown +++ b/website/docs/d/iam_policy_document.html.markdown @@ -78,9 +78,14 @@ valid to use literal JSON strings within your configuration, or to use the The following arguments are supported: * `policy_id` (Optional) - An ID for the policy document. -* `source_json` (Optional) - An IAM policy document to import and add the - statements from. Use this to, for example, customize resource policies in - modules. +* `source_json` (Optional) - An IAM policy document to import as a base for the + current policy document. Statements with non-blank `sid`s in the current + policy document will overwrite statements with the same `sid` in the source + json. Statements without an `sid` cannot be overwritten. +* `override_json` (Optional) - An IAM policy document to import and override the + current policy document. Statements with non-blank `sid`s in the override + document will overwrite statements with the same `sid` in the current document. + Statements without an `sid` cannot be overwritten. * `statement` (Required) - A nested configuration block (described below) configuring one *statement* to be included in the policy document. @@ -169,3 +174,116 @@ data "aws_iam_policy_document" "event_stream_bucket_role_assume_role_policy" { } } ``` + +## Example with Source and Override + +Showing how you can use `source_json` and `override_json` + +```hcl +data "aws_iam_policy_document" "source" { + statement { + actions = ["ec2:*"] + resources = ["*"] + } + + statement { + sid = "SidToOverwrite" + + actions = ["s3:*"] + resources = ["*"] + } +} + +data "aws_iam_policy_document" "source_json_example" { + source_json = "${data.aws_iam_policy_document.source.json}" + + statement { + sid = "SidToOverwrite" + + actions = ["s3:*"] + + resources = [ + "arn:aws:s3:::somebucket", + "arn:aws:s3:::somebucket/*", + ] + } +} + +data "aws_iam_policy_document" "override" { + statement { + sid = "SidToOverwrite" + + actions = ["s3:*"] + resources = ["*"] + } +} + +data "aws_iam_policy_document" "override_json_example" { + override_json = "${data.aws_iam_policy_document.override.json}" + + statement { + actions = ["ec2:*"] + resources = ["*"] + } + + statement { + sid = "SidToOverwrite" + + actions = ["s3:*"] + + resources = [ + "arn:aws:s3:::somebucket", + "arn:aws:s3:::somebucket/*", + ] + } +} +``` + +`data.aws_iam_policy_document.source_json_example.json` will evaluate to: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "", + "Effect": "Allow", + "Action": "ec2:*", + "Resource": "*" + }, + { + "Sid": "SidToOverwrite", + "Effect": "Allow", + "Action": "s3:*", + "Resource": [ + "arn:aws:s3:::somebucket/*", + "arn:aws:s3:::somebucket" + ] + } + ] +} +``` + +`data.aws_iam_policy_document.override_json_example.json` will evaluate to: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "", + "Effect": "Allow", + "Action": "ec2:*", + "Resource": "*" + }, + { + "Sid": "SidToOverwrite", + "Effect": "Allow", + "Action": "s3:*", + "Resource": "*" + } + ] +} +``` + +You can also combine `source_json` and `override_json` in the same document.