Today marks the Day 16 of 30 Days of AWS Terraform challenge Initiative by Piyush Sachdeva. Today, we will be mastering a mini-project on AWS main services such as IAM: bulk-creating AWS IAM users from a CSV file, assigning them dynamically to groups based on their attributes (like department and job title), and enabling secure console login.
Architecture:
As we all know that we will use AWS IAM resource for creating Users and managing their access. For any large organizations, IAM is a key service as it helps in creating the users with adequate permissions and grouping them into the respective groups so that no authorized access into other services of AWS will take place.
In this demo, I will show how to manage AWS IAM users, groups, and group memberships using Terraform. It's an AWS equivalent of Azure AD user management, demonstrating Infrastructure as Code (IaC) best practices. This project will solidify your understanding of Terraform's powerful iteration constructs: for_each, for expressions, and conditional filtering.
What This Demo Does
- Retrieves AWS Account Information - Gets your current AWS Account ID
- Reads User Data from CSV - Loads user information from a CSV file
- Creates IAM Users - Automatically creates IAM users with proper naming conventions
- Sets Up Login Profiles - Configures console access with password reset requirement
- Creates IAM Groups - Sets up organizational groups (Education, Managers, Engineers)
- Manages Group Memberships - Automatically assigns users to appropriate groups.
Mini Project Overview
Goal: Bulk create 26 IAM users (based on a users.csv).
Key Features:
Dynamic user assignment to groups (education, engineers, managers).
Enable console login with mandatory temporary passwords.
Utilize an S3 remote backend for state management.
Username Format: First initial + Last name (e.g., Michael Scott -> mscott).
What Gets Created:
- 26 IAM Users with console access
- 3 IAM Groups (Education, Managers, Engineers)
- Group Memberships based on user attributes
- User Tags with metadata (DisplayName, Department, JobTitle)
Project SetUP:
Below is the project structure for this mini-project:
day16/
├── backend.tf # S3 backend configuration for state
├── provider.tf # AWS provider configuration
├── versions.tf # Terraform version and required providers
├── main.tf # Main user creation logic
├── groups.tf # IAM groups and membership management
├── users.csv # User data source
1. CSV Processing:
The csvdecode() function is the magic here. It takes the CSV file and transforms it into a list of maps, which is a perfect data structure for Terraform to iterate over.
In the CSV file we will have data coming in the format:
first_name,last_name,department,job_title
Michael,Scott,Education,Regional Manager
Dwight,Schrute,Sales,Assistant to the Regional Manager
So to make that into our usable format, We will convert that into a list of maps so that we can get whatever value based on that key.
locals {
users = csvdecode(file("users.csv"))
}
Make sure you have the users.csv located in the same folder.
2. IAM User Creation:
This is the main block where we will write main code for creation of IAM users.
resource "aws_iam_user" "users" {
for_each = { for user in local.users : user.first_name => user }
name = lower("${substr(each.value.first_name, 0, 1)}${each.value.last_name}")
path = "/users/"
tags = {
"DisplayName" = "${each.value.first_name} ${each.value.last_name}"
"Department" = each.value.department
"JobTitle" = each.value.job_title
}
}
Users are created with a username format: {first_initial}{lastname} (e.g., mscott)
This is where the for_each pattern shines. We transform the list of maps from our CSV into a map of objects keyed by the desired username.
for_each Key: The key is dynamically generated using lower() and substr() to enforce our first initial+lastname format (e.g., mscott).
We have used 2 Terraform functions named lower and substr here:
Substr is used to cut short first_name into the first letter followed by last_name and then using the lower function to make the entire string lowercase.
Tags: We propagate the user attributes (department, job_title) into AWS tags. This is critical for the next step: dynamic group assignment.
3. Console Login Profiles:
We need to provide console access and enforce a password change on first login for security.
resource "aws_iam_user_login_profile" "users" {
for_each = aws_iam_user.users
user = each.value.name
password_reset_required = true
lifecycle {
ignore_changes = [password_reset_required, password_length]
}
}
Login profiles are created for console access with password reset required.
Again we will use for_each block to iterate all the users from the aws_iam_users block.
We have used lifecycle meta argument here because it Prevents Terraform from destroying/recreating the resource every time the user changes their password.
4: Create Groups and Memberships:
This is the most advanced section, showcasing conditional logic to manage group membership.
Create IAM Groups:
resource "aws_iam_group" "education" {
name = "Education"
path = "/groups/"
}
resource "aws_iam_group_membership" "education_members" {
name = "education-group-membership"
group = aws_iam_group.education.name
users = [
for user in aws_iam_user.users : user.name
if user.tags.Department == "Education"
]
}
Like the similar above block for education, we can create separate blocks for engineering, manager.
In the above block, we could see we have created aws_iam_group block to create groups with the name and path.
In aws_iam_group_membership, we will associate the users with the required groups based on the Department value. We have used a for expression with an if clause to filter the list of all created users, assigning them to a specific group based on their tags.
Manager Group Logic (Advanced):
For the managers group, you might need more complex logic and error handling:
resource "aws_iam_group_membership" "managers_members" {
name = "managers-group-membership"
group = aws_iam_group.managers.name
users = [
for user in aws_iam_user.users : user.name if contains(keys(user.tags), "JobTitle") && can(regex("Manager|CEO", user.tags.JobTitle))
]
}
The 'can()' function is excellent for safely checking if an attribute exists for Members include those with job_title "manager" OR "CEO".
In Simple, it searches for key named "JobTitle" and it tries to find the names like Manager or CEO inside the Job Title and assigns them to Manager group.
Execution Commands:
# Initialize terraform
terraform init
# Review the changes (check for 58 resources created)
terraform plan
# Apply the configuration
terraform apply
With this configuration, you will provision 58 resources in total:
(26{Users} + 26{Login Profiles} + 3{Groups} + 3 {Group Memberships}$)
Plan: 58 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ account_id = "716145636736"
+ user_names = [
+ "Michael Scott",
+ "Dwight Schrute",
+ "Jim Halpert",
+ "Pam Beesly",
+ "Ryan Howard",
+ "Andy Bernard",
+ "Robert California",
+ "Stanley Hudson",
+ "Kevin Malone",
+ "Angela Martin",
+ "Oscar Martinez",
+ "Phyllis Vance",
+ "Toby Flenderson",
+ "Kelly Kapoor",
+ "Darryl Philbin",
+ "Creed Bratton",
+ "Meredith Palmer",
+ "Erin Hannon",
+ "Gabe Lewis",
+ "Jan Levinson",
+ "David Wallace",
+ "Holly Flax",
+ "Charles Miner",
+ "Jo Bennett",
+ "Clark Green",
+ "Pete Miller",
]
+ user_passwords = (sensitive value)
You know before terraform apply, only 2 IAM users were there.
terraform apply
.
.
.
Apply complete! Resources: 58 added, 0 changed, 0 destroyed.
Outputs:
account_id = "716145636736"
user_names = [
"Michael Scott",
"Dwight Schrute",
"Jim Halpert",
"Pam Beesly",
"Ryan Howard",
"Andy Bernard",
"Robert California",
"Stanley Hudson",
"Kevin Malone",
"Angela Martin",
"Oscar Martinez",
"Phyllis Vance",
"Toby Flenderson",
"Kelly Kapoor",
"Darryl Philbin",
"Creed Bratton",
"Meredith Palmer",
"Erin Hannon",
"Gabe Lewis",
"Jan Levinson",
"David Wallace",
"Holly Flax",
"Charles Miner",
"Jo Bennett",
"Clark Green",
"Pete Miller",
]
user_passwords = <sensitive>
Now you could see that the 26 new users have been created as per the terraform code.
Summary:
- Ingesting external data using csvdecode().
- Dynamic resource creation using for_each.
- Enabling secure console access for users.
- Using tags to propagate data for later use.
- Creating dynamic group membership lists using for/if expressions.
- Configuring a resilient resource using lifecycle ignore_changes.
Don't forgot to destroy all the resources you have created using the terraform destroy command.
Conclusion:
AWS IAM User Management with Terraform offers a robust and scalable solution for handling access control within your AWS environment. By embracing Infrastructure as Code (IaC) principles, organizations can move beyond manual configurations and achieve a more secure, efficient, and auditable management of user identities and permissions.
This concludes the Day 16 of 30 Days of AWS Terraform challeneg. See you in the next blog.
Below is the Youtube Video for Reference:




Top comments (0)