
AWS Identity and Access Management (IAM) decides who can do what in your account, so getting it right matters for security. Doing it by hand works until it doesn't. Once you have more than a handful of roles, clicking through the console leads to drift, copy-paste mistakes, and roles nobody remembers creating. Terraform lets you define those roles as code instead, so the configuration is the same every time you apply it.
This post walks through creating AWS IAM roles with Terraform: the resources you'll use, a worked EC2 example, and some practices worth following. Terraform handles the automation well on its own. What it doesn't solve is governance once you have many configurations and several teams touching them, and that's where a platform around Terraform starts to pay off.
Defining IAM roles in Terraform pays off in a few concrete ways once you get past the first role or two:
plan command lets you review what's about to change, so you catch misconfigurations before they go live.Terraform's AWS provider offers specific resources for managing IAM roles and policies:
| Resource/Data Source Name | Primary Purpose | Key Arguments/Attributes Example |
|---|---|---|
aws_iam_role |
Defines an IAM role. | name, assume_role_policy |
aws_iam_policy_document (data) |
Generates a JSON IAM policy document. | statement { actions, effect, resources, principals, condition } |
aws_iam_policy |
Creates a customer-managed IAM policy. | name, policy (JSON) |
aws_iam_role_policy_attachment |
Attaches a managed policy (AWS or customer) to an IAM role. | role, policy_arn |
aws_iam_role_policy |
Creates an inline policy directly attached to an IAM role. | role, policy (JSON) |
aws_iam_role (inline_policy block) |
Exclusively manages inline policies for a role. | inline_policy { name, policy } |
These resources work together to define who can assume a role (trust policy) and what actions the assumed role can perform (permissions policies).
Here's how to create an IAM role.
The trust policy specifies which principals (e.g., AWS services, users, other accounts) can assume the role. The aws_iam_policy_document data source is ideal for this.
data "aws_iam_policy_document" "ec2_assume_role_policy" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"] # Standard action for assuming a role
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"] # Allows EC2 service to assume this role
}
}
}
resource "aws_iam_role" "my_ec2_role" {
name = "MyApplicationEC2Role"
assume_role_policy = data.aws_iam_policy_document.ec2_assume_role_policy.json
description = "IAM role for my application's EC2 instances"
tags = {
Environment = "production"
ManagedBy = "Terraform"
}
}This code defines a role that can be assumed by EC2 instances.
Permissions policies define what the role can do. You can use AWS managed policies or create custom ones. For custom policies, again, aws_iam_policy_document helps define the permissions, and aws_iam_policy creates the managed policy.
Example: Custom S3 Read-Only Policy
data "aws_iam_policy_document" "s3_read_only_permissions" {
statement {
effect = "Allow"
actions = [
"s3:GetObject",
"s3:ListBucket"
]
resources = [
"arn:aws:s3:::my-application-bucket",
"arn:aws:s3:::my-application-bucket/*"
]
}
}
resource "aws_iam_policy" "s3_read_only_policy" {
name = "MyApplicationS3ReadOnly"
description = "Grants read-only access to a specific S3 bucket"
policy = data.aws_iam_policy_document.s3_read_only_permissions.json
}Use aws_iam_role_policy_attachment to link your defined policies to the role.
resource "aws_iam_role_policy_attachment" "attach_s3_read_only" {
role = aws_iam_role.my_ec2_role.name
policy_arn = aws_iam_policy.s3_read_only_policy.arn
}
# Attaching an AWS managed policy for SSM access
resource "aws_iam_role_policy_attachment" "attach_ssm_core" {
role = aws_iam_role.my_ec2_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}For EC2 instances to use an IAM role, an instance profile is required.
resource "aws_iam_instance_profile" "my_ec2_profile" {
name = "MyApplicationEC2Profile"
role = aws_iam_role.my_ec2_role.name
}This instance profile would then be associated with your EC2 instances at launch.
Terraform automates the IAM work itself. Running it across a large organization, or dozens of AWS accounts, raises a different set of problems:
Platforms like Scalr handle these scaling problems. Scalr runs Terraform operations with hierarchical environment management, role-based access control (RBAC) for Terraform runs, integration with policy-as-code frameworks such as Open Policy Agent (OPA) for governance, and centralized auditing. Those features make it easier for teams to collaborate, stay compliant, and keep track of infrastructure like AWS IAM roles.
Managing AWS IAM roles with Terraform gives you a cloud setup that is more consistent and easier to audit. Once your roles and policies live in code, the same review and rollback workflow you already use for the rest of your infrastructure applies to them too.
The hard part shows up later, as more teams and accounts come into the picture. That's the point where a dedicated Terraform automation platform earns a look: it adds the access controls, policy enforcement, and audit history you need to run IAM at scale without losing track of who changed what.
