# AWS IAM Role Policy Attachment Terraform: Idiomatic Patterns for Error-Free IAM Management

> Master AWS IAM role policy attachment with Terraform. Learn idiomatic patterns for error-free IAM management using the aws provider, ensuring reliable policy binding and drift detection.

- Repository: [HashiCorp/terraform](https://github.com/hashicorp/terraform)
- Tags: best-practices
- Published: 2026-02-16

---

**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`](https://github.com/hashicorp/terraform/blob/main/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`](https://github.com/hashicorp/terraform/blob/main/internal/service/iam/resource_aws_iam_role_policy_attachment.go).

```hcl

# 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.

```hcl
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.

```hcl
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.

```hcl
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`](https://github.com/hashicorp/terraform/blob/main/internal/service/iam/resource_aws_iam_role_policy_attachment.go).

```bash
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`](https://github.com/hashicorp/terraform/blob/main/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.