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_attachment as the canonical resource for binding managed policies to IAM roles, rather than inline policy blocks or the managed_policy_arns argument 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →