Skip to content

Check if aws_iam_policy sets policy value from reference#1058

Open
fionn wants to merge 2 commits intoterraform-linters:masterfrom
fionn:use-policy-document
Open

Check if aws_iam_policy sets policy value from reference#1058
fionn wants to merge 2 commits intoterraform-linters:masterfrom
fionn:use-policy-document

Conversation

@fionn
Copy link
Copy Markdown

@fionn fionn commented Mar 14, 2026

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.

The motivation is that I occasionally come across incorrect (or not obviously correct) jsonencode(...) inputs in code review, which this would catch automatically.

If this seems reasonable, I'll do the same for aws_iam_role's assume_role_policy argument.

@fionn fionn force-pushed the use-policy-document branch from 87396cb to 9c40d79 Compare March 14, 2026 14:42
@wata727
Copy link
Copy Markdown
Member

wata727 commented Mar 15, 2026

but it also permits locals, resources and data sources of any type.

I'm not sure how useful this rule is when we're allowing variables. For example, this is also valid when using an jsonencode(...) output with an incorrect input via a variable.

@fionn
Copy link
Copy Markdown
Author

fionn commented Mar 15, 2026

Yeah, it's not perfect, because it doesn't recursively check the source. I did consider checking if the source is a local and then checking if that was set from a reference or not, but it seemed non-trivial.

(I also originally considered checking that the policy is a data.aws_iam_policy_document.*.foo object immediately, but that seems overly conservative.)

My argument for why it's acceptable as-is is that if there's some indirection, the problem sort of lies elsewhere? Not sure it holds up, let me know if you think we need to check all the way down.

@fionn fionn force-pushed the use-policy-document branch from 9c40d79 to 801c9d8 Compare March 15, 2026 09:13
@wata727
Copy link
Copy Markdown
Member

wata727 commented Mar 15, 2026

The important question is whether this rule is also useful to other users. If the warning can be avoided by using local variables, there may be no incentive to enable this rule.

However, I agree that recursive checking is too complex. As a compromise, one possible criterion might be whether the expression can be resolved to a static string, since the data attribute is resolved as an unknown value in TFLint.

That said, considering that cases where external files are referenced using functions such as file or templatefile are perfectly valid, I'm not sure if this rule is always useful. You'll probably have to accept the inconvenience of not being able to use external files by enabling it.

@fionn
Copy link
Copy Markdown
Author

fionn commented Mar 15, 2026

whether the expression can be resolved to a static string

I think this would work well.

That said, considering that cases where external files are referenced using functions such as file or templatefile are perfectly valid, I'm not sure if this rule is always useful. You'll probably have to accept the inconvenience of not being able to use external files by enabling it.

When I started this I initially wanted to only permit data.aws_iam_policy_documents, but feel it is too aggressive for a general purpose rule that others will use. I think it's a bit tricky now because the scope of what should and should not be allowed is a little fuzzy.

I think we probably do want to permit loading from files in general, since there could be some process producing well-formed policy documents in JSON format that would be valid to read from. That said, I personally don't want to permit reading from files in any Terraform I review, every time I've come across this it would have been better to use a data.aws_iam_policy_document. So it comes down to what we think is the more generally useful behaviour.

How about, emit an issue if:

  • we resolve to a static string or,
  • we resolve a function call that is jsonencode?

That way we permit references and loading from files and so on.

(Totally happy to emit an issue on resolving any function call also, which would catch loading from files, if you think that's not too aggressive.)

@bendrucker
Copy link
Copy Markdown
Member

Going any further than matching on a string or object literal seems too far.

When I started this I initially wanted to only permit data.aws_iam_policy_document

In the vast majority of cases this is preferable but this is probably only achievable with AI code review and not static analysis. A file() is valid for some policy you download or otherwise generate from some external tool. Assuming you want to vendor that result rather than fetch it on every Terraform run. Eg Datadog's setup used to involve copying this policy (now they have an API that exposes required permissions):

https://docs.datadoghq.com/integrations/guide/aws-manual-setup/?tab=roledelegation#aws-integration-iam-policy

There's also every variation of getting a policy from a remote service: data.aws_s3_bucket_object, data.http_get, data.tfe_outputs, etc., all potentially valid in the right context.

@bendrucker
Copy link
Copy Markdown
Member

we resolve a function call that is jsonencode?

I would say it should go further, emit if jsonencode called on an object literal. If you jsonencode(var.policy) because you take a policy input as a typed object, that is valid.

@wata727
Copy link
Copy Markdown
Member

wata727 commented Mar 20, 2026

There are probably two options:

  • This rule enforces that the policy is always a value that cannot be statically resolved, such as a data source. This means that an issue will be emitted if a known value is obtained with EvaluateExpr.
    • In this case, you can prevent examples that use jsonencode to assemble JSON, but since it is not possible to reference external files using file, the rule needs to be disabled in examples like Datadog.
  • This rule enforces that the policy is always a value that cannot be statically resolved, however, it makes exceptions for certain functions such as file and templatefile.

I would say it should go further, emit if jsonencode called on an object literal. If you jsonencode(var.policy) because you take a policy input as a typed object, that is valid.

Hmm, I'm not sure about this. It seems to me that there are no situations where it would be more reasonable to maintain the input type of the policy variable without using data.aws_iam_policy_document.

fionn added 2 commits March 28, 2026 16:05
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.
@fionn fionn force-pushed the use-policy-document branch from 801c9d8 to 46fb8ea Compare March 28, 2026 08:14
@fionn
Copy link
Copy Markdown
Author

fionn commented Mar 28, 2026

As far as I can tell, EvaluateExpr produces an error on both data source attributes and calls to functions like jsonencode. Maybe I'm misunderstanding, but it seems it's not suitable to discriminate between these cases here.

So I opted to try out the second option. With this, we could also consider making the list of permitted functions configurable.

}

// Check if the value is a reference and if so permit it.
if _, ok := attribute.Expr.(*hclsyntax.ScopeTraversalExpr); ok {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSON syntax is not HCL native syntax, so we should not assume it is hclsyntax expression here.
You can determine whether an expression can be resolved statically by evaluating it as cty.Value.
https://pkg.go.dev/github.com/terraform-linters/tflint-plugin-sdk@v0.24.0/tflint#Runner


## How To Fix

Define an `aws_iam_policy_document` data source and assign its `json` attribute to `policy`.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add the fixed code shown in the above examples.

@@ -0,0 +1,94 @@
# aws_iam_policy_use_policy_reference

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`. This includes function calls, except those that read from files (`file` and `templatefile`).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good idea to include examples demonstrating how certain functions or unknown variables are handled exceptionally, and to keep the introductory explanation as concise as possible.

rule "aws_iam_policy_use_policy_reference" {
enabled = true
}
```
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there are no configs specific to this rule, this section can be deleted.


// Check checks if policy is not referencing an object reference
func (r *AwsIAMPolicyUsePolicyReference) Check(runner tflint.Runner) error {
issueMessage := "The policy does not reference an object"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to make the message a little clearer. I also would like to avoid overly long messages consisting of multiple sentences, but as it is, it's probably not clear what "object" refers to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants