Skip to content

Commit 9c40d79

Browse files
committed
Check if aws_iam_policy sets policy value from reference
The aws_iam_policy resource's policy argument takes a string that must match the structure of a JSON-formatted IAM policy document. Often this is done by assigning to the output of jsonencode which is passed some HCL -- however, this doesn't guarantee that the string is well-formed, only that it is JSON, and can result in errors at apply-time. The spirit of this check is to ensure that policy values are from references. In practice, it's anticipated that these will be data.aws_iam_policy_document.foo.json objects, but it also permits locals, resources and data sources of any type.
1 parent e255af1 commit 9c40d79

File tree

6 files changed

+263
-0
lines changed

6 files changed

+263
-0
lines changed

docs/rules/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ These rules enforce best practices and naming conventions:
7070
|[aws_iam_policy_attachment_exclusive_attachment](aws_iam_policy_attachment_exclusive_attachment.md)|Consider alternative resources to `aws_iam_policy_attachment`||
7171
|[aws_iam_policy_document_gov_friendly_arns](aws_iam_policy_document_gov_friendly_arns.md)|Ensure `iam_policy_document` data sources do not contain `arn:aws:` ARN's||
7272
|[aws_iam_policy_gov_friendly_arns](aws_iam_policy_gov_friendly_arns.md)|Ensure `iam_policy` resources do not contain `arn:aws:` ARN's||
73+
|[aws_iam_policy_use_policy_reference](aws_iam_policy_use_policy_reference.md)|Check that `iam_policy` sources its `policy` from a reference||
7374
|[aws_iam_role_deprecated_policy_attributes](aws_iam_role_deprecated_policy_attributes.md)|Disallow using deprecated policy attributes of `aws_iam_role`||
7475
|[aws_iam_role_policy_gov_friendly_arns](aws_iam_role_policy_gov_friendly_arns.md)|Ensure `iam_role_policy` resources do not contain `arn:aws:` ARN's||
7576
|[aws_instance_previous_type](aws_instance_previous_type.md)|Disallow using previous generation instance types||

docs/rules/README.md.tmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ These rules enforce best practices and naming conventions:
7070
|[aws_iam_policy_attachment_exclusive_attachment](aws_iam_policy_attachment_exclusive_attachment.md)|Consider alternative resources to `aws_iam_policy_attachment`||
7171
|[aws_iam_policy_document_gov_friendly_arns](aws_iam_policy_document_gov_friendly_arns.md)|Ensure `iam_policy_document` data sources do not contain `arn:aws:` ARN's||
7272
|[aws_iam_policy_gov_friendly_arns](aws_iam_policy_gov_friendly_arns.md)|Ensure `iam_policy` resources do not contain `arn:aws:` ARN's||
73+
|[aws_iam_policy_use_policy_reference](aws_iam_policy_use_policy_reference.md)|Check that `iam_policy` sources its `policy` from a reference||
7374
|[aws_iam_role_deprecated_policy_attributes](aws_iam_role_deprecated_policy_attributes.md)|Disallow using deprecated policy attributes of `aws_iam_role`||
7475
|[aws_iam_role_policy_gov_friendly_arns](aws_iam_role_policy_gov_friendly_arns.md)|Ensure `iam_role_policy` resources do not contain `arn:aws:` ARN's||
7576
|[aws_instance_previous_type](aws_instance_previous_type.md)|Disallow using previous generation instance types||
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# aws_iam_policy_use_policy_reference
2+
3+
Check the source of the value for `policy` in an `aws_iam_policy` to catch values not generated from resources or data sources such as `data.aws_iam_policy_document`.
4+
5+
## Configuration
6+
7+
```hcl
8+
rule "aws_iam_policy_use_policy_reference" {
9+
enabled = true
10+
}
11+
```
12+
13+
## Example
14+
15+
```hcl
16+
resource "aws_iam_policy" "raw_json" {
17+
name = "test_policy"
18+
19+
policy = <<-EOF
20+
{
21+
"Version": "2012-10-17",
22+
"Statement": [
23+
{
24+
"Action": ["ec2:Describe*"],
25+
"Effect": "Allow",
26+
"Resource": "arn:aws:s3:::<bucketname>/*"
27+
}
28+
]
29+
}
30+
EOF
31+
}
32+
33+
resource "aws_iam_policy" "jsonencode" {
34+
name = "test_policy"
35+
36+
policy = jsonencode({
37+
Version = "2012-10-17"
38+
Statement = [
39+
{
40+
Action = [
41+
"ec2:Describe*",
42+
]
43+
Effect = "Allow"
44+
Resource = "*"
45+
},
46+
]
47+
})
48+
}
49+
```
50+
51+
```
52+
$ tflint
53+
2 issue(s) found:
54+
55+
Notice: The policy does not reference an object (aws_iam_policy_use_policy_reference)
56+
57+
on main.tf line 4:
58+
4: policy = <<-EOF
59+
5: {
60+
6: "Version": "2012-10-17",
61+
7: "Statement": [
62+
8: {
63+
9: "Action": ["ec2:Describe*"],
64+
10: "Effect": "Allow",
65+
11: "Resource": "arn:aws:s3:::<bucketname>/*"
66+
12: }
67+
13: ]
68+
14: }
69+
15: EOF
70+
71+
Notice: The policy does not reference an object (aws_iam_policy_use_policy_reference)
72+
73+
on main.tf line 21:
74+
21: policy = jsonencode({
75+
22: Version = "2012-10-17"
76+
23: Statement = [
77+
24: {
78+
25: Action = [
79+
26: "ec2:Describe*",
80+
27: ]
81+
28: Effect = "Allow"
82+
29: Resource = "*"
83+
30: },
84+
31: ]
85+
32: })
86+
```
87+
88+
## Why
89+
90+
The `policy` argument takes a string, which must be well-formed. Specifically, it must be a JSON-formatted IAM policy document. By passing HCL to `jsonencode` we can ensure that it is JSON-formatted, but not that it is the correct structure for an IAM policy document. This check aims to make it simpler to assert correctness before applying.
91+
92+
## How To Fix
93+
94+
Define an `aws_iam_policy_document` data source and assign its `json` attribute to `policy`.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package rules
2+
3+
import (
4+
// hcl "github.com/hashicorp/hcl/v2"
5+
"github.com/hashicorp/hcl/v2/hclsyntax"
6+
7+
"github.com/terraform-linters/tflint-plugin-sdk/hclext"
8+
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
9+
"github.com/terraform-linters/tflint-ruleset-aws/project"
10+
)
11+
12+
// AwsIAMPolicyUsePolicyReference checks if aws_iam_policy doesn't assign to
13+
// policy from a reference
14+
type AwsIAMPolicyUsePolicyReference struct {
15+
tflint.DefaultRule
16+
17+
resourceType string
18+
attributeName string
19+
}
20+
21+
// NewAwsIAMPolicyUsePolicyReferenceRule returns new rule with default attributes
22+
func NewAwsIAMPolicyUsePolicyReferenceRule() *AwsIAMPolicyUsePolicyReference {
23+
return &AwsIAMPolicyUsePolicyReference{
24+
resourceType: "aws_iam_policy",
25+
attributeName: "policy",
26+
}
27+
}
28+
29+
// Name returns the rule name
30+
func (r *AwsIAMPolicyUsePolicyReference) Name() string {
31+
return "aws_iam_policy_use_policy_reference"
32+
}
33+
34+
// Enabled returns whether the rule is enabled by default
35+
func (r *AwsIAMPolicyUsePolicyReference) Enabled() bool {
36+
return false
37+
}
38+
39+
// Severity returns the rule severity
40+
func (r *AwsIAMPolicyUsePolicyReference) Severity() tflint.Severity {
41+
return tflint.NOTICE
42+
}
43+
44+
// Link returns the rule reference link
45+
func (r *AwsIAMPolicyUsePolicyReference) Link() string {
46+
return project.ReferenceLink(r.Name())
47+
}
48+
49+
// Check checks if policy is not referencing an object reference
50+
func (r *AwsIAMPolicyUsePolicyReference) Check(runner tflint.Runner) error {
51+
issueMessage := "The policy does not reference an object"
52+
53+
resources, err := runner.GetResourceContent(r.resourceType, &hclext.BodySchema{
54+
Attributes: []hclext.AttributeSchema{{Name: r.attributeName}},
55+
}, nil)
56+
if err != nil {
57+
return err
58+
}
59+
60+
for _, resource := range resources.Blocks {
61+
attribute, exists := resource.Body.Attributes[r.attributeName]
62+
if !exists {
63+
continue
64+
}
65+
66+
// Check if the value is a reference and if so permit it.
67+
if _, ok := attribute.Expr.(*hclsyntax.ScopeTraversalExpr); ok {
68+
continue
69+
}
70+
71+
if err := runner.EmitIssue(r, issueMessage, attribute.Range); err != nil {
72+
return err
73+
}
74+
}
75+
76+
return nil
77+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package rules
2+
3+
import (
4+
"testing"
5+
6+
hcl "github.com/hashicorp/hcl/v2"
7+
"github.com/terraform-linters/tflint-plugin-sdk/helper"
8+
)
9+
10+
func Test_AwsIAMPolicyUsePolicyDocument(t *testing.T) {
11+
cases := []struct {
12+
Name string
13+
Content string
14+
Expected helper.Issues
15+
}{
16+
{
17+
Name: "policy uses raw JSON",
18+
Content: `
19+
resource "aws_iam_policy" "policy" {
20+
name = "test_policy"
21+
policy = <<EOF
22+
{
23+
"Version": "2012-10-17",
24+
"Statement": [
25+
{
26+
"Effect": "Allow",
27+
"Resource": "arn:aws:s3:::<bucketname>/*""
28+
}
29+
]
30+
}
31+
EOF
32+
}
33+
`,
34+
Expected: helper.Issues{
35+
{
36+
Rule: NewAwsIAMPolicyUsePolicyReferenceRule(),
37+
Message: "The policy does not reference an object",
38+
Range: hcl.Range{
39+
Filename: "resource.tf",
40+
Start: hcl.Pos{Line: 4, Column: 2},
41+
End: hcl.Pos{Line: 14, Column: 4},
42+
},
43+
},
44+
},
45+
},
46+
{
47+
Name: "policy uses jsonencode",
48+
Content: `
49+
resource "aws_iam_policy" "policy" {
50+
name = "test_policy"
51+
policy = jsonencode()
52+
}
53+
`,
54+
Expected: helper.Issues{
55+
{
56+
Rule: NewAwsIAMPolicyUsePolicyReferenceRule(),
57+
Message: "The policy does not reference an object",
58+
Range: hcl.Range{
59+
Filename: "resource.tf",
60+
Start: hcl.Pos{Line: 4, Column: 2},
61+
End: hcl.Pos{Line: 4, Column: 23},
62+
},
63+
},
64+
},
65+
},
66+
{
67+
Name: "policy uses data.aws_iam_policy_document",
68+
Content: `
69+
resource "aws_iam_policy" "policy" {
70+
name = "test_policy"
71+
policy = data.aws_iam_policy_document.document.json # (not shown)
72+
}
73+
`,
74+
Expected: helper.Issues{},
75+
},
76+
}
77+
78+
rule := NewAwsIAMPolicyUsePolicyReferenceRule()
79+
80+
for _, tc := range cases {
81+
runner := helper.TestRunner(t, map[string]string{"resource.tf": tc.Content})
82+
83+
if err := rule.Check(runner); err != nil {
84+
t.Fatalf("Unexpected error occurred: %s", err)
85+
}
86+
87+
helper.AssertIssues(t, tc.Expected, runner.Issues)
88+
}
89+
}

rules/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ var manualRules = []tflint.Rule{
1919
NewAwsIAMPolicyDocumentGovFriendlyArnsRule(),
2020
NewAwsIAMPolicyGovFriendlyArnsRule(),
2121
NewAwsIAMRolePolicyGovFriendlyArnsRule(),
22+
NewAwsIAMPolicyUsePolicyReferenceRule(),
2223
NewAwsInstancePreviousTypeRule(),
2324
NewAwsMqBrokerInvalidEngineTypeRule(),
2425
NewAwsMqConfigurationInvalidEngineTypeRule(),

0 commit comments

Comments
 (0)