Skip to content

Commit

Permalink
#2672: d/aws_iam_policy_document add support for source_json
Browse files Browse the repository at this point in the history
  • Loading branch information
devonbleak committed Feb 4, 2018
1 parent c88732a commit 399264e
Show file tree
Hide file tree
Showing 4 changed files with 276 additions and 3 deletions.
17 changes: 14 additions & 3 deletions aws/data_source_aws_iam_policy_document.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ func dataSourceAwsIamPolicyDocument() *schema.Resource {
},
},
},
"source_json": {
Type: schema.TypeString,
Optional: true,
},
"json": {
Type: schema.TypeString,
Computed: true,
Expand All @@ -94,17 +98,22 @@ func dataSourceAwsIamPolicyDocument() *schema.Resource {
}

func dataSourceAwsIamPolicyDocumentRead(d *schema.ResourceData, meta interface{}) error {
doc := &IAMPolicyDoc{
Version: "2012-10-17",
doc := &IAMPolicyDoc{}

if sourceJson, hasSourceJson := d.GetOk("source_json"); hasSourceJson {
if err := json.Unmarshal([]byte(sourceJson.(string)), doc); err != nil {
return err
}
}

doc.Version = "2012-10-17"

if policyId, hasPolicyId := d.GetOk("policy_id"); hasPolicyId {
doc.Id = policyId.(string)
}

var cfgStmts = d.Get("statement").([]interface{})
stmts := make([]*IAMPolicyStatement, len(cfgStmts))
doc.Statements = stmts
for i, stmtI := range cfgStmts {
cfgStmt := stmtI.(map[string]interface{})
stmt := &IAMPolicyStatement{
Expand Down Expand Up @@ -148,6 +157,8 @@ func dataSourceAwsIamPolicyDocumentRead(d *schema.ResourceData, meta interface{}
stmts[i] = stmt
}

doc.Statements = append(doc.Statements, stmts...)

jsonDoc, err := json.MarshalIndent(doc, "", " ")
if err != nil {
// should never happen if the above code is correct
Expand Down
208 changes: 208 additions & 0 deletions aws/data_source_aws_iam_policy_document_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,34 @@ func TestAccAWSDataSourceIAMPolicyDocument_basic(t *testing.T) {
})
}

func TestAccAWSDataSourceIAMPolicyDocument_source(t *testing.T) {
// This really ought to be able to be a unit test rather than an
// acceptance test, but just instantiating the AWS provider requires
// some AWS API calls, and so this needs valid AWS credentials to work.
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccAWSIAMPolicyDocumentSourceConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckStateValue("data.aws_iam_policy_document.test_source", "json",
testAccAWSIAMPolicyDocumentSourceExpectedJSON,
),
),
},
{
Config: testAccAWSIAMPolicyDocumentSourceBlankConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckStateValue("data.aws_iam_policy_document.test_source_blank", "json",
testAccAWSIAMPolicyDocumentSourceBlankExpectedJSON,
),
),
},
},
})
}

func testAccCheckStateValue(id, name, value string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[id]
Expand Down Expand Up @@ -187,3 +215,183 @@ var testAccAWSIAMPolicyDocumentExpectedJSON = `{
}
]
}`

var testAccAWSIAMPolicyDocumentSourceConfig = `
data "aws_iam_policy_document" "test" {
policy_id = "policy_id"
statement {
sid = "1"
actions = [
"s3:ListAllMyBuckets",
"s3:GetBucketLocation",
]
resources = [
"arn:aws:s3:::*",
]
}
statement {
actions = [
"s3:ListBucket",
]
resources = [
"arn:aws:s3:::foo",
]
condition {
test = "StringLike"
variable = "s3:prefix"
values = [
"home/",
"home/&{aws:username}/",
]
}
not_principals {
type = "AWS"
identifiers = ["arn:blahblah:example"]
}
}
statement {
actions = [
"s3:*",
]
resources = [
"arn:aws:s3:::foo/home/&{aws:username}",
"arn:aws:s3:::foo/home/&{aws:username}/*",
]
principals {
type = "AWS"
identifiers = ["arn:blahblah:example"]
}
}
statement {
effect = "Deny"
not_actions = ["s3:*"]
not_resources = ["arn:aws:s3:::*"]
}
# Normalization of wildcard principals
statement {
effect = "Allow"
actions = ["kinesis:*"]
principals {
type = "AWS"
identifiers = ["*"]
}
}
statement {
effect = "Allow"
actions = ["firehose:*"]
principals {
type = "*"
identifiers = ["*"]
}
}
}
data "aws_iam_policy_document" "test_source" {
source_json = "${data.aws_iam_policy_document.test.json}"
statement {
sid = "SourceJSONTest1"
actions = ["*"]
resources = ["*"]
}
}
`

var testAccAWSIAMPolicyDocumentSourceExpectedJSON = `{
"Version": "2012-10-17",
"Id": "policy_id",
"Statement": [
{
"Sid": "1",
"Effect": "Allow",
"Action": [
"s3:ListAllMyBuckets",
"s3:GetBucketLocation"
],
"Resource": "arn:aws:s3:::*"
},
{
"Sid": "",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::foo",
"NotPrincipal": {
"AWS": "arn:blahblah:example"
},
"Condition": {
"StringLike": {
"s3:prefix": [
"home/${aws:username}/",
"home/"
]
}
}
},
{
"Sid": "",
"Effect": "Allow",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::foo/home/${aws:username}/*",
"arn:aws:s3:::foo/home/${aws:username}"
],
"Principal": {
"AWS": "arn:blahblah:example"
}
},
{
"Sid": "",
"Effect": "Deny",
"NotAction": "s3:*",
"NotResource": "arn:aws:s3:::*"
},
{
"Sid": "",
"Effect": "Allow",
"Action": "kinesis:*",
"Principal": "*"
},
{
"Sid": "",
"Effect": "Allow",
"Action": "firehose:*",
"Principal": "*"
},
{
"Sid": "SourceJSONTest1",
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}`

var testAccAWSIAMPolicyDocumentSourceBlankConfig = `
data "aws_iam_policy_document" "test_source_blank" {
source_json = ""
statement {
sid = "SourceJSONTest2"
actions = ["*"]
resources = ["*"]
}
}
`

var testAccAWSIAMPolicyDocumentSourceBlankExpectedJSON = `{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SourceJSONTest2",
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}`
51 changes: 51 additions & 0 deletions 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"
"fmt"
"sort"
)

Expand Down Expand Up @@ -75,6 +76,29 @@ func (ps IAMPolicyStatementPrincipalSet) MarshalJSON() ([]byte, error) {
return json.Marshal(&raw)
}

func (ps *IAMPolicyStatementPrincipalSet) UnmarshalJSON(b []byte) error {
var out IAMPolicyStatementPrincipalSet

var data interface{}
if err := json.Unmarshal(b, &data); err != nil {
return err
}

switch t := data.(type) {
case string:
out = append(out, IAMPolicyStatementPrincipal{Type: "*", Identifiers: []string{"*"}})
case map[string]interface{}:
for key, value := range data.(map[string]interface{}) {
out = append(out, IAMPolicyStatementPrincipal{Type: key, Identifiers: value})
}
default:
return fmt.Errorf("Unsupported data type %s for IAMPolicyStatementPrincipalSet", t)
}

*ps = out
return nil
}

func (cs IAMPolicyStatementConditionSet) MarshalJSON() ([]byte, error) {
raw := map[string]map[string]interface{}{}

Expand All @@ -99,6 +123,33 @@ func (cs IAMPolicyStatementConditionSet) MarshalJSON() ([]byte, error) {
return json.Marshal(&raw)
}

func (cs *IAMPolicyStatementConditionSet) UnmarshalJSON(b []byte) error {
var out IAMPolicyStatementConditionSet

var data map[string]map[string]interface{}
if err := json.Unmarshal(b, &data); err != nil {
return err
}

for test_key, test_value := range data {
for var_key, var_values := range test_value {
switch var_values.(type) {
case string:
out = append(out, IAMPolicyStatementCondition{Test: test_key, Variable: var_key, Values: []string{var_values.(string)}})
case []interface{}:
values := []string{}
for _, v := range var_values.([]interface{}) {
values = append(values, v.(string))
}
out = append(out, IAMPolicyStatementCondition{Test: test_key, Variable: var_key, Values: values})
}
}
}

*cs = out
return nil
}

func iamPolicyDecodeConfigStringList(lI []interface{}) interface{} {
if len(lI) == 1 {
return lI[0].(string)
Expand Down
3 changes: 3 additions & 0 deletions website/docs/d/iam_policy_document.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ 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.
* `statement` (Required) - A nested configuration block (described below)
configuring one *statement* to be included in the policy document.

Expand Down

0 comments on commit 399264e

Please sign in to comment.