The interaction between Terraform configuration language and AWS Identity and Access Management produces a repeatable workflow for defining, validating, and applying permissions. Terraform offers a dedicated data source called awsiampolicydocument that provides a native HCL way to define IAM policies. Unlike using raw JSON or jsonencode, this data source gives you compile-time validation, the ability to merge policy documents, and a more declarative syntax. This guide covers everything you need to know about using awsiampolicydocument effectively.
The awsiampolicy_document data source generates an IAM policy document in JSON format from HCL blocks. The generation occurs during the Terraform plan phase and produces the JSON structure that AWS APIs expect. The real-world consequence for a team operating across multiple AWS accounts is that policy definitions remain readable in HCL while the eventual artifact remains a valid JSON document. The declarative syntax reduces the friction of copying JSON from AWS documentation into Terraform and allows policy authors to express intent using HCL blocks rather than string literals. The contextual layer connecting this to the broader Terraform workflow is that the data source does not create infrastructure by itself. The data source must be referenced by a resource that consumes the .json attribute, otherwise the plan shows no changes.
The core principle of using a data resource in Terraform is that data resources read information and have no effect on the infrastructure. This implies that we must use this data resource as a reference somewhere in order to actually create the generated policy. The impact for an operator is a two-step pattern: first declare the policy document, then declare the resource that consumes it. Without the second step, terraform plan will run as usual and no changes will be reflected. The plan output will be empty because the data resource is inert.
awsiampolicy_document Data Source Fundamentals
The data source is defined with the name awsiampolicy_document. A minimal declaration contains a statement block with sid, actions, and resources.
data "aws_iam_policy_document" "s3_write_only_policy_document" {
statement {
sid = "1"
actions = [
"s3:PutObject",
]
resources = ["*"]
}
}
Running terraform plan at this stage produces no infrastructure changes. The data resource exists only to produce a value. The value is accessed via data.awsiampolicydocument.s3writeonlypolicy_document.json. The contextual layer is that this pattern isolates policy logic from resource logic, which allows the same policy document to be reused across multiple resources.
The data source provides compile-time validation. Errors in action names, statement structure, or missing required attributes are caught before apply. This contrasts with raw JSON where a typo can pass Terraform validation and fail only at AWS API time. The impact is reduced deployment failures and faster feedback during code review.
The ability to merge policy documents is cited as a capability of the data source. Merging allows composition of base policies with environment-specific additions without manual JSON concatenation. The real-world consequence is smaller, maintainable modules that assemble permissions from reusable fragments.
Creating Standalone Policies with awsiampolicy
A standalone IAM policy is created with the awsiampolicy resource. The resource requires a name and a policy attribute that contains JSON. The typical pattern connects the two resources:
resource "aws_iam_policy" "s3_write_only_policy" {
name = "S3WriteOnlyPolicy"
policy = data.aws_iam_policy_document.s3_write_only_policy_document.json
}
After this reference is added, terraform plan shows a creation action. The plan output for this example includes:
```
awsiampolicy.s3writeonly_policy will be created
- resource "awsiampolicy" "s3writeonly_policy" {
- arn = (known after apply)
- id = (known after apply)
- name = "S3WriteOnlyPolicy"
- path = "/"
- policy = jsonencode(
{ - Statement = [
- {
- Action = "s3:PutObject"
- Effect = "Allow"
- Resource = "*"
- Sid = "1"
},
] - Version = "2012-10-17"
}
) - policy_id = (known after apply)
- tags_all = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
```
The plan shows the policy attribute rendered via jsonencode. The Version is 2012-10-17. The Effect is Allow. The Action is s3:PutObject. The Resource is *. The Sid is 1. The path defaults to /. The arn, id, policy_id are known after apply. The impact for the operator is visibility into the exact JSON that will be sent to AWS before any change is made.
Running terraform apply creates the S3WriteOnlyPolicy standalone policy. Standalone policies exist independently of identities and can be attached to roles, users, or groups later. The contextual layer is that standalone policies enable centralized permission management and easier auditing.
IAM policies lie at the heart of AWS access management. Essentially, they are a set of permissions that can be attached to an AWS identity or resource to manage its access. Terraform allows you to define, create, and manage AWS IAM policies programmatically, ensuring consistency and automation across environments.
Inline Policies and Identity Attachment
Inline policies are created and attached to a user using the awsiamuser_policy resource. When you delete the identity, you also delete the inline policy. This coupling is important for lifecycle planning.
The example uses HEREDOC syntax to represent the policy:
resource "aws_iam_user_policy" "s3_list_only_policy" {
name = "S3ListOnlyPolicy"
user = aws_iam_user.spacelift_user.name
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"s3:ListAllMyBuckets"
],
"Resource": "*"
}]
}
EOF
}
The HEREDOC syntax allows us to use the JSON policy directly in Terraform without any modifications. The note in the reference material emphasizes that despite using HEREDOC syntax to represent the policy, Terraform internally uses the jsonencode function, which is visible in the plan.
The plan output for the inline policy shows:
```
awsiamuserpolicy.s3listonlypolicy will be created
- resource "awsiamuserpolicy.s3listonlypolicy" {
- id = (known after apply)
- name = "S3ListOnlyPolicy"
- policy = jsonencode(
{ - Statement = [
- {
- Action = [
- "s3:ListAllMyBuckets",
] - Effect = "Allow"
- Resource = "*"
},
] - Version = "2012-10-17"
}
) - user = "spacelift-user"
}
Plan: 1 to add, 0 to change, 0 to destroy.
```
The impact for teams is that inline policies travel with the user. Deleting the user removes the policy automatically, which simplifies cleanup but reduces reusability. The contextual layer is that identity-based policies created with awsiamuserpolicy differ from standalone policies created with awsiam_policy. The former is tightly bound to an identity, the latter is reusable.
Policy Versioning and Lifecycle Management
Terraform's state management combined with AWS IAM policy versioning provides a comprehensive solution for policy lifecycle management. The reference material demonstrates a versioned policy resource:
resource "aws_iam_policy" "versioned_policy" {
name = "application-access-policy-v${var.policy_version}"
description = "Versioned policy for application access - Version ${var.policy_version}"
policy = data.aws_iam_policy_document.application_access.json
lifecycle {
create_before_destroy = true
}
tags = {
Version = var.policy_version
Environment = var.environment
LastUpdated = timestamp()
}
}
Implement policy versioning by maintaining separate policy resources with version identifiers, enabling quick rollbacks through Terraform state manipulation. This strategy ensures that authentication and authorization changes can be rapidly reverted if issues arise, maintaining system availability while preserving audit trails for compliance requirements.
The name incorporates var.policyversion. The description includes the version. The lifecycle block sets createbefore_destroy = true. The tags include Version, Environment, and LastUpdated = timestamp().
The real-world consequence is that each change creates a new policy object rather than updating an existing one in place. Rollback becomes a state change that points back to a previous version identifier. The contextual layer is that this pattern works with the awsiampolicy_document data source, because the policy attribute is a reference to .json, so changes to the document automatically flow to a new versioned resource.
Module-Based Policy Definition
A community module for AWS IAM Policy is available for Terraform v0.12 and above. It uses Dynamic Nested Blocks, which are not supported by earlier versions of Terraform. It aims to create both awsiampolicy resource and awsiampolicy_document data blocks.
Usage of this module is straightforward. It accepts most of the inputs of resource awsiampolicy. Instead of policy input, it takes a required argument statements, which is a list of maps similar to statement from awsiampolicy_document data source.
The basic example:
module "grafana_policy" {
source = "grodzik/iam_policy/aws"
description = "Provides read-only access for grafana user"
name = "grafana-ro-access"
path = "/"
statements = [
{
sid = "GrafanaEC2AccessRO"
actions = [
"ec2:DescribeTags",
"ec2:DescribeRegions",
"ec2:DescribeInstances",
]
effect = "Allow"
resources = ["*"]
}
]
}
The code above will create both awsiampolicy resource and awsiampolicy_document data resource. The advantage of this is that whole definition in a single block.
The impact for teams is reduced boilerplate. The module hides the data source and resource wiring. The contextual layer is that the module expects statements as a list of maps, mirroring the statement block structure of awsiampolicy_document, which preserves HCL readability while automating the underlying resource creation.
Choosing a Representation Method
Terraform gives you four ways to define an AWS IAM policy: the HEREDOC syntax, the jsonencode() function, the file() function, and the awsiampolicy_document data source. All four produce the same JSON document AWS expects, so the choice comes down to readability, validation, and IDE support.
The table below summarizes the representation options referenced in the material.
| Representation Method | Mechanism | Validation Timing | Readability |
| HEREDOC syntax | policy = <
| file function | policy = file("policy.json") | Apply time | External file |
| awsiampolicydocument data source | policy = data.awsiampolicydocument.xxx.json | Compile time | High with HCL blocks |
The awsiampolicy_document data resource approach is recommended because it allows Terraform to validate any errors without having to apply the changes, which is not possible in other cases.
In general, you can represent policies in any way you like. However, the data resource approach provides compile-time validation, declarative syntax, and the ability to merge documents. The impact for a development team is fewer failed applies and clearer error messages during plan.
The reference material notes that below are two policies, one with the HEREDOC syntax and one with the jsonencode function, to illustrate practical differences in validation behavior.
Validation and Plan Behavior
The plan command is central to the workflow. After defining only the data source, terraform plan shows no changes. After referencing the data source in a resource, terraform plan shows the resource to be created with the rendered JSON.
The plan output shows policy = jsonencode({...}). This indicates that Terraform normalizes various input methods to JSON before sending to AWS. The visibility of the rendered JSON in the plan is important for security review.
The data source generates an IAM policy document in JSON format from HCL blocks. The generation is deterministic. Changing any attribute in the statement block changes the resulting JSON hash, which triggers Terraform to update the dependent resource.
The contextual layer is that the separation between data and resource allows teams to test policy changes in isolation. The data source can be validated independently, and the resource can be applied only when the policy is approved.
Best Practices and Operational Notes
Policies determine what an identity or resource is allowed to do on AWS based on the permission set. The material lists the topics covered in the tutorial: What are IAM policies? Different ways of representing IAM Policies in Terraform. Prerequisites. Creating identity-based IAM policies using Terraform. Creating an inline policy using Terraform. Creating standalone IAM policies using Terraform. How to choose a way to represent an IAM policy in Terraform. AWS-managed policies. Creating resource-based policies using Terraform. Best practices for managing IAM policies in Terraform.
The practical implication of following the data source pattern is consistency across environments. The same HCL can be used in development, staging, and production with variable substitution for names, tags, and conditions.
Dynamic Policy Generation with terraform awsiampolicy_document Conditions is mentioned for complex enterprise environments often requiring dynamic policy generation based on runtime conditions and organizational hierarchies. The data source supports condition blocks that translate to JSON Condition elements.
The contextual layer connecting all sections is that Terraform's state management provides a single source of truth for IAM policy lifecycle, while the awsiampolicy_document data source provides a safe, validated way to author policies in HCL.
Conclusion
The combination of awsiampolicydocument data source and awsiampolicy resource forms the core Terraform pattern for AWS IAM policy management. The data source produces JSON from HCL with compile-time validation and merge capability. The resource consumes the JSON and creates a standalone policy in AWS. Inline policies attach directly to identities via awsiamuserpolicy, with lifecycle coupling to the identity. Versioned policies use naming conventions, lifecycle createbeforedestroy, and tags to enable rollback. Module wrappers encapsulate the data source and resource in a single block. Representation choice among HEREDOC, jsonencode, file, and data source hinges on validation timing and readability, with the data source recommended for its early error detection. The plan output reveals the rendered JSON, allowing review before apply. The overall workflow supports consistent, automated, and auditable IAM policy management across environments.