How to Create & Manage IAM Users with Terraform

terraform

🚀 Level Up Your Infrastructure Skills

You focus on building. We’ll keep you updated. Get curated infrastructure insights that help you make smarter decisions.

Managing AWS Identity and Access Management (IAM) users can be complex, especially at scale. Terraform simplifies this by enabling infrastructure as code, which allows you to create, update, and manage IAM users, groups, and policies efficiently and consistently. 

In this guide, we’ll walk through how to create and manage IAM users with Terraform step by step.

What is an IAM user?

An IAM user is an identity created in AWS Identity and Access Management (IAM) to represent a person or application that interacts with AWS services. It includes long-term credentials like a username, password, and access keys. IAM users are used to manage fine-grained permissions and resource-level access within a single AWS account. 

Unlike roles, IAM users are tied to specific credentials rather than being assumed temporarily.

In most modern setups, you should use IAM roles and temporary credentials for automation, and reserve IAM users only for humans or special legacy integrations.

How to create an IAM user with Terraform

Here is a practical way to create an AWS IAM user with Terraform:

Step 1. Prepare your working directory and provider

Create an empty directory, initialize a Terraform project, and add the AWS provider with a pinned version and set a default region. 

Keep credentials out of code and rely on your usual AWS auth method, such as a shared profile, SSO, or environment variables.

terraform {
  required_version = ">= 1.6.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "eu-central-1"
}

Run terraform init to download the provider.

You can also configure alias providers if you manage multiple AWS accounts or regions.

Step 2. Model the IAM user as code

Start with the user resource. Use tags to record ownership and purpose. 

Choose a clear, immutable name and prefer machine-readable tags so future automation can discover the user.

resource "aws_iam_user" "ci_bot" {
  name = "ci-bot"
  tags = {
    Owner   = "platform"
    Purpose = "ci"
  }
}

At this point, the user exists with no permissions and no credentials. That is intentional since it avoids accidental privilege.

Step 3. Attach permissions through groups or policies

You can attach permissions by adding the user to a group or by binding policies directly. Groups make long-term maintenance simpler because you can move members in or out without rewriting policies. 

Create a group, attach a managed policy, then add the user to the group:

resource "aws_iam_group" "ci_group" {
  name = "ci-group"
}

resource "aws_iam_group_policy_attachment" "ci_group_poweruser" {
  group      = aws_iam_group.ci_group.name
  policy_arn = "arn:aws:iam::aws:policy/PowerUserAccess"
}

resource "aws_iam_user_group_membership" "ci_membership" {
  user   = aws_iam_user.ci_bot.name
  groups = [aws_iam_group.ci_group.name]
}

For least privilege, you would replace the broad managed policy with a custom inline policy that specifies only the actions and resources that the automation needs.

resource "aws_iam_policy" "ci_policy" {
  name        = "ci-policy"
  description = "Least privilege for CI tasks"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "sts:GetCallerIdentity",
          "ecr:GetAuthorizationToken",
          "ecr:BatchCheckLayerAvailability",
          "ecr:BatchGetImage",
          "ecr:GetDownloadUrlForLayer"
        ]
        Resource = "*"
      }
    ]
  })
}

resource "aws_iam_group_policy_attachment" "ci_group_custom" {
  group      = aws_iam_group.ci_group.name
  policy_arn = aws_iam_policy.ci_policy.arn
}

Use groups for shared roles and rotate policies through them, rather than attaching policies directly to users.

Step 4. Decide how the user authenticates

There are two main paths:

  • Human users need a console password
  • Programmatic users need access keys

Do not create both unless there is a strong reason.

Programmatic access

Create an access key in Terraform only if you can store the secret securely at apply time.

Never commit the secret to version control and do not print it to logs. Use a PGP key to encrypt the secret into state so it is not stored in plaintext.

resource "aws_iam_access_key" "ci_bot" {
  user    = aws_iam_user.ci_bot.name
  pgp_key = "keybase:your-keybase-username"
}

After apply, Terraform emits an encrypted secret field that you can decrypt locally with your PGP key.

If you cannot use PGP, consider creating the access key out of band and importing only the user into Terraform to avoid secret exposure.

AWS allows only two access keys per user, so plan rotation carefully. Also, prefer temporary credentials via roles and STS for automation instead of long-lived keys.

Console access for a human

Create a login profile and enforce a password reset on first login. Pair this with MFA in your account baseline.

resource "aws_iam_user_login_profile" "human_profile" {
  user                    = aws_iam_user.ci_bot.name
  password_reset_required = true
  pgp_key                 = "keybase:your-keybase-username"
}

Note: The aws_iam_user_login_profile resource only sets the password when first created and can’t update or import profiles later.

Always enforce MFA for console users — Terraform doesn’t manage that directly, so handle it through AWS account settings or separate automation.

Step 5. Plan, apply, and verify

Run terraform plan to view the proposed changes. Apply them and verify the result.

  • Check the user exists with aws iam get-user --user-name ci-bot
  • Confirm group membership and attached policies
  • If you created access keys, decrypt the secret and test aws sts get-caller-identity using that key and the correct region
  • If this is a console user, test login and ensure MFA is required

Step 6. Keep state and secrets safe

Terraform state will contain IAM ARNs and possibly encrypted secrets.

  • Store state in a remote backend that supports locking and encryption at rest
  • S3 with server-side encryption and DynamoDB locking is the common choice
  • Restrict who can read state because metadata about permissions is sensitive, even if secrets are encrypted
  • Consider Spacelift or HashiCorp Cloud Platform (HCP) for secure remote state with built-in encryption and access control
  • Mark sensitive outputs using sensitive = true in your Terraform outputs

Best practices for managing IAM users with Terraform

Creating IAM users with Terraform works best when you treat it like product work. Use consistent patterns, plan for the full lifecycle, and avoid the shortcuts that cause drift.

  • Treat IAM users as a product in Terraform, using a small reusable module that enforces consistent naming, tags, and guardrails, plus a clear toggle for human vs programmatic access
  • Prefer group-attached policies, and avoid long-lived user-attached policies unless absolutely necessary
  • Tag users as automation data, for example Owner, Purpose, and Expiry, then use those tags in reviews and cleanup jobs
  • Plan rotation with two active keys, create the new key, update all consumers, confirm traffic cutover, then remove the old key, and on departure disable credentials first, keep the user for an observation window, and delete after logs show no activity; document cadence and schedule it
  • Protect the ecosystem by never outputting secrets, storing remote state with locking and encryption, importing any out-of-band IAM resources to avoid drift, and validating permissions regularly with IAM Access Analyzer
  • Add an account-wide permissions boundary or Service Control Policy (SCP) to limit the blast radius of human error
  • Avoid wildcard (“*”) permissions whenever possible
  • Federate human access via AWS IAM Identity Center (SSO) or external identity providers where possible, rather than creating many IAM users

Key points

Implementing IAM users with Terraform promotes consistency, least-privilege access, and version-controlled security policies. 

However, always prefer short-lived credentials (roles and temporary tokens) where possible — IAM users should be the exception, not the norm.

Terraform is really powerful, but to achieve an end-to-end secure GitOps approach, you need to use a product that can run your Terraform workflows. Spacelift takes managing Terraform to the next level by giving you access to a powerful CI/CD workflow and unlocking features such as:

  • Policies (based on Open Policy Agent)
  • Multi-IaC workflows
  • Self-service infrastructure
  • Integrations with any third-party tools

If you want to learn more about Spacelift, create a free account today or book a demo with one of our engineers.

Note: New versions of Terraform are placed under the BUSL license, but everything created before version 1.5.x stays open-source. OpenTofu is an open-source version of Terraform that expands on Terraform’s existing concepts and offerings. It is a viable alternative to HashiCorp’s Terraform, being forked from Terraform version 1.5.6.

Automate Terraform deployments with Spacelift

Automate your infrastructure provisioning and build more complex workflows based on Terraform using policy as code, programmatic configuration, context sharing, drift detection, resource visualization, and many more.

Learn more

Terraform Commands Cheat Sheet

Grab our ultimate cheat sheet PDF
for all the Terraform commands
and concepts you need.

Share your data and download the cheat sheet