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.
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.
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-identityusing 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 = truein your Terraform outputs
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
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.
