AWS IAM Role Policy Attachment Terraform: Idiomatic Patterns for Error-Free IAM Management
Use the aws_iam_role_policy_attachment resource to bind managed IAM policies to roles, ensuring idempotent attachments, explicit dependency graphs, and built-in drift detection that inline policy blocks cannot guarantee.
Managing AWS IAM role policy attachments in Terraform requires a specific resource type to avoid common pitfalls like phantom dependencies or accidental permission revocation. The HashiCorp Terraform AWS provider implements this through a dedicated attachment resource that separates the lifecycle of the role from its policy bindings. This article demonstrates the canonical patterns for error-free IAM management using the provider’s validation logic and lifecycle controls.
Why Use aws_iam_role_policy_attachment in Terraform?
The aws_iam_role_policy_attachment resource exists in internal/service/iam/resource_aws_iam_role_policy_attachment.go and provides a first-class Terraform resource for the AWS AttachRolePolicy API action. Unlike embedding policy ARNs directly inside an aws_iam_role resource’s managed_policy_arns argument (which forces replacement of the entire role on change), the dedicated attachment resource offers granular control.
Idempotent Attachment and Drift Detection
Terraform records each attachment as a distinct state object with its own ID (formatted as <role_name>/<policy_arn>). This allows the provider to detect drift accurately: if an attachment is removed manually via the AWS Console, the next terraform plan will flag the missing resource and propose recreation. The provider’s CRUD logic in the source implementation validates the ARN format and role existence during the planning phase, surfacing errors before any infrastructure changes occur.
Explicit Dependencies and Lifecycle Safety
The resource requires explicit role and policy_arn arguments, which Terraform automatically converts into dependency graph edges. This guarantees that the IAM role and policy exist before Terraform attempts the attachment. For critical permissions, you can leverage lifecycle meta-arguments:
prevent_destroy: Blocks accidental destruction of the attachment.create_before_destroy: Ensures a new attachment is created before an old one is destroyed during policy updates, preventing permission gaps.
Idiomatic Implementation Pattern
The canonical pattern involves three discrete resources: the role, the policy (or data source for AWS-managed policies), and the attachment. This separation follows the single-responsibility principle and aligns with the provider’s schema design in internal/service/iam/resource_aws_iam_role_policy_attachment.go.
# 1. Define the IAM role with its trust policy
resource "aws_iam_role" "example" {
name = "example-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "ec2.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
# 2. Define a customer-managed policy
resource "aws_iam_policy" "example" {
name = "example-policy"
policy = data.aws_iam_policy_document.example.json
}
data "aws_iam_policy_document" "example" {
statement {
actions = ["s3:ListBucket"]
resources = ["arn:aws:s3:::example-bucket"]
}
}
# 3. Attach the policy to the role using the idiomatic resource
resource "aws_iam_role_policy_attachment" "example" {
role = aws_iam_role.example.name
policy_arn = aws_iam_policy.example.arn
}
Practical Code Examples for AWS IAM Role Policy Attachment in Terraform
The following patterns cover common operational scenarios, from leveraging AWS-managed policies to safeguarding critical attachments against accidental destruction.
Attaching AWS-Managed Policies
For AWS-managed policies (such as ReadOnlyAccess), reference the ARN directly without creating a separate aws_iam_policy resource. This reduces state file size and avoids managing policy documents for AWS-controlled permissions.
resource "aws_iam_role" "lambda_exec" {
name = "lambda-exec-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
resource "aws_iam_role_policy_attachment" "lambda_basic" {
role = aws_iam_role.lambda_exec.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
Attaching Multiple Policies with for_each
When a role requires multiple policy attachments, use for_each to create distinct attachment resources for each ARN. This prevents issues where a single resource manages multiple attachments, which complicates partial updates and drift detection.
locals {
policy_arns = [
"arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess",
"arn:aws:iam::aws:policy/CloudWatchLogsFullAccess"
]
}
resource "aws_iam_role_policy_attachment" "multiple" {
for_each = toset(local.policy_arns)
role = aws_iam_role.lambda_exec.name
policy_arn = each.value
}
Protecting Critical Permissions with prevent_destroy
For roles governing production-critical access (e.g., security audit roles or break-glass access), add a lifecycle block to prevent accidental destruction of the attachment. This ensures that terraform destroy or resource removal from configuration will fail rather than revoke critical permissions.
resource "aws_iam_role_policy_attachment" "critical" {
role = aws_iam_role.security_audit.name
policy_arn = "arn:aws:iam::aws:policy/SecurityAudit"
lifecycle {
prevent_destroy = true
}
}
Importing Existing Attachments
If an attachment was created outside Terraform (e.g., via the AWS CLI or Console), import it into state using the resource ID format <role_name>/<policy_arn>. This aligns with the parsing logic in internal/service/iam/resource_aws_iam_role_policy_attachment.go.
terraform import aws_iam_role_policy_attachment.example example-role/arn:aws:iam::123456789012:policy/example-policy
After import, Terraform manages the attachment lifecycle, enabling drift detection and controlled updates through subsequent applies.
Summary
- Use
aws_iam_role_policy_attachmentas the canonical resource for binding managed policies to IAM roles, rather than inline policy blocks or themanaged_policy_arnsargument on the role resource. - Separate concerns by defining the role, policy, and attachment as distinct resources to leverage Terraform’s dependency graph and lifecycle controls.
- Validate early by relying on the AWS provider’s built-in ARN validation and existence checks in
internal/service/iam/resource_aws_iam_role_policy_attachment.go. - Protect critical access using
lifecycle { prevent_destroy = true }to avoid accidental permission revocation. - Import existing state using the
<role_name>/<policy_arn>ID format to bring manually created attachments under Terraform management.
Frequently Asked Questions
What is the difference between aws_iam_role_policy and aws_iam_role_policy_attachment?
aws_iam_role_policy creates an inline policy embedded directly within the IAM role, meaning the policy document is part of the role resource itself and cannot be shared across roles. In contrast, aws_iam_role_policy_attachment binds a managed policy (either AWS-managed or customer-managed) to a role by ARN, allowing the same policy to be attached to multiple roles and managed independently. The attachment resource is the idiomatic choice for reusable, auditable permission boundaries.
Can I attach multiple policies to a single IAM role using aws_iam_role_policy_attachment?
Yes, but you must create separate attachment resources for each policy, or use a for_each loop to generate multiple attachment resources from a set of policy ARNs. You cannot attach multiple policies using a single aws_iam_role_policy_attachment resource because the resource schema defines policy_arn as a single string, not a list. Using for_each ensures each attachment has a unique Terraform resource address, enabling granular lifecycle management and precise drift detection for each policy binding.
How do I prevent Terraform from accidentally destroying a critical IAM role policy attachment?
Add a lifecycle block with prevent_destroy = true to the aws_iam_role_policy_attachment resource. This meta-argument causes Terraform to reject any plan that would destroy the attachment, including terraform destroy operations or configuration changes that remove the resource. This is essential for "break-glass" access roles, security audit permissions, or production-critical service-linked roles where accidental detachment would cause immediate outages or security lockouts.
What is the correct format for importing an existing IAM role policy attachment into Terraform?
Use the import command with the resource ID formatted as <role_name>/<policy_arn>, where role_name is the IAM role’s friendly name and policy_arn is the full Amazon Resource Name of the attached policy. For example: terraform import aws_iam_role_policy_attachment.example MyRole/arn:aws:iam::aws:policy/ReadOnlyAccess. This format matches the composite ID parsing logic in the provider’s resource implementation and allows Terraform to correctly map the existing AWS attachment to the resource state.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →