DEV Community

Cover image for 13.Attach IAM Role with Inline Policy Using Terraform
Thu Kha Kyawe
Thu Kha Kyawe

Posted on

13.Attach IAM Role with Inline Policy Using Terraform

Lab Information

The Nautilus DevOps team is setting up IAM-based access control for internal AWS resources. They need to create an IAM Role and an IAM Policy using Terraform and attach the policy to the role.

Create an IAM Role named devops-role.

Create an IAM Policy named devops-policy that allows listing EC2 instances.

Attach the policy to the role

Create the main.tf file (do not create a separate .tf file) to provision a Role, policy and attach it.

Use the variables.tf file with the following:
    KKE_ROLE_NAME: name of the role.
    KKE_POLICY_NAME: name of the policy.

Use terraform.tfvarsfile to input the role and policy names.

Use outputs.tf file to output the following:
    kke_iam_role_name: name of the role created.
    kke_iam_policy_name: name of the policy ceated.
Enter fullscreen mode Exit fullscreen mode

Lab Solutions

1️⃣ variables.tf

variable "KKE_ROLE_NAME" {
  type = string
}

variable "KKE_POLICY_NAME" {
  type = string
}
Enter fullscreen mode Exit fullscreen mode

2️⃣ terraform.tfvars

KKE_ROLE_NAME   = "devops-role"
KKE_POLICY_NAME = "devops-policy"
Enter fullscreen mode Exit fullscreen mode

3️⃣ main.tf

# IAM Role
resource "aws_iam_role" "devops_role" {
  name = var.KKE_ROLE_NAME

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Principal = {
        Service = "ec2.amazonaws.com"
      }
      Action = "sts:AssumeRole"
    }]
  })
}

# IAM Policy (allow listing EC2 instances)
resource "aws_iam_policy" "devops_policy" {
  name = var.KKE_POLICY_NAME

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect   = "Allow"
      Action   = [
        "ec2:DescribeInstances"
      ]
      Resource = "*"
    }]
  })
}

# Attach policy to role
resource "aws_iam_role_policy_attachment" "attach_policy" {
  role       = aws_iam_role.devops_role.name
  policy_arn = aws_iam_policy.devops_policy.arn
}
Enter fullscreen mode Exit fullscreen mode

4️⃣ outputs.tf

output "kke_iam_role_name" {
  value = aws_iam_role.devops_role.name
}

output "kke_iam_policy_name" {
  value = aws_iam_policy.devops_policy.name
}
Enter fullscreen mode Exit fullscreen mode

5️⃣ Terraform Commands (Run in Order)
terraform init
terraform validate
terraform apply

Type:

yes

✅ Expected Output

bob@iac-server ~/terraform via 💠 default ➜  terraform apply

Terraform used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_iam_policy.devops_policy will be created
  + resource "aws_iam_policy" "devops_policy" {
      + arn              = (known after apply)
      + attachment_count = (known after apply)
      + id               = (known after apply)
      + name             = "devops-policy"
      + name_prefix      = (known after apply)
      + path             = "/"
      + policy           = jsonencode(
            {
              + Statement = [
                  + {
                      + Action   = [
                          + "ec2:DescribeInstances",
                        ]
                      + Effect   = "Allow"
                      + Resource = "*"
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + policy_id        = (known after apply)
      + tags_all         = (known after apply)
    }

  # aws_iam_role.devops_role will be created
  + resource "aws_iam_role" "devops_role" {
      + arn                   = (known after apply)
      + assume_role_policy    = jsonencode(
            {
              + Statement = [
                  + {
                      + Action    = "sts:AssumeRole"
                      + Effect    = "Allow"
                      + Principal = {
                          + Service = "ec2.amazonaws.com"
                        }
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + create_date           = (known after apply)
      + force_detach_policies = false
      + id                    = (known after apply)
      + managed_policy_arns   = (known after apply)
      + max_session_duration  = 3600
      + name                  = "devops-role"
      + name_prefix           = (known after apply)
      + path                  = "/"
      + tags_all              = (known after apply)
      + unique_id             = (known after apply)

      + inline_policy (known after apply)
    }

  # aws_iam_role_policy_attachment.attach_policy will be created
  + resource "aws_iam_role_policy_attachment" "attach_policy" {
      + id         = (known after apply)
      + policy_arn = (known after apply)
      + role       = "devops-role"
    }

Plan: 3 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + kke_iam_policy_name = "devops-policy"
  + kke_iam_role_name   = "devops-role"

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_iam_policy.devops_policy: Creating...
aws_iam_role.devops_role: Creating...
aws_iam_policy.devops_policy: Creation complete after 1s [id=arn:aws:iam::000000000000:policy/devops-policy]
aws_iam_role.devops_role: Creation complete after 1s [id=devops-role]
aws_iam_role_policy_attachment.attach_policy: Creating...
aws_iam_role_policy_attachment.attach_policy: Creation complete after 0s [id=devops-role-20260128104355055200000001]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

kke_iam_policy_name = "devops-policy"
kke_iam_role_name = "devops-role"
Enter fullscreen mode Exit fullscreen mode

🧠 Step-by-Step Explanation (Simple & Clear)

Let’s explain what you built and why.

🔹 What problem is this lab solving?

The team wants:

Controlled access to AWS

No hard-coded credentials

Permissions defined centrally

IAM Roles + Policies solve this cleanly.

🔹 IAM Role vs IAM Policy (important)

Think of it like this:

Policy = 📜 What actions are allowed

Role = 🪪 Who can use those actions

Attachment = 🔗 Connects them

A role without a policy → useless
A policy without a role → unused

You need both.

🔹 Step 1: IAM Role
aws_iam_role

This role:

Can be assumed by EC2 (ec2.amazonaws.com)

Does nothing by itself yet

The assume_role_policy answers:

“Who is allowed to use this role?”

🔹 Step 2: IAM Policy
aws_iam_policy

This policy allows:

"ec2:DescribeInstances"

Meaning:

List EC2 instances

Read-only access

No create / delete / modify

This is least privilege ✅

🔹 Step 3: Attach Policy to Role
aws_iam_role_policy_attachment

This is the glue:

Role now inherits permissions

AWS enforces them automatically

🔹 What happens during terraform apply?

1️⃣ Terraform creates IAM role
2️⃣ Terraform creates IAM policy
3️⃣ Terraform attaches policy to role
4️⃣ AWS enforces permissions
5️⃣ Terraform outputs names

🧠 Easy Memory Model

Role = 👤 identity

Policy = 🔑 permissions

Attachment = 🔗 link

🚨 Common Mistakes

❌ Forgetting assume role policy
❌ Using wrong EC2 action
❌ Hardcoding names
❌ Missing attachment
❌ Output name mismatch


Resources & Next Steps
📦 Full Code Repository: KodeKloud Learning Labs
📖 More Deep Dives: Whispering Cloud Insights - Read other technical articles
💬 Join Discussion: DEV Community - Share your thoughts and questions
💼 Let's Connect: LinkedIn - I'd love to connect with you

Credits
• All labs are from: KodeKloud
• I sincerely appreciate your provision of these valuable resources.

Top comments (0)