AWS Terraform IAM User Management

AWS Terraform IAM User Management

Source: Dev.to

Why Use Terraform for IAM User Management? ## Architecture Overview ## Reading User Data from CSV ## Creating IAM Users Dynamically ## Key points: ## Enabling Console Access Securely ## Creating IAM Groups ## Dynamic Group Membership Assignment ## Education Group ## Managers Group (based on job title) ## Engineers Group ## Enforcing MFA for All Users ## Attaching MFA Policy to Groups ## Attaching Permissions to Groups ## AWS Account Verification ## Security and Best Practices ## Conclusion ## Embedded Video Tutorial Managing IAM users manually in AWS can quickly become complex, error-prone, and difficult to scale. As teams grow, you need a repeatable, auditable, and secure way to manage users, groups, permissions, and security controls like MFA. In this blog, we’ll explore how to implement AWS IAM user management using Terraform, driven by a CSV file as the single source of truth. Using Terraform for IAM offers several advantages: Instead of clicking through the AWS Console, we define everything declaratively. The CSV file becomes the single source of truth for identity data. We begin by loading user data from a CSV file. Terraform converts each row into a map, allowing us to loop over users dynamically. This makes adding or removing users as simple as editing the CSV. IAM users are created using for_each, ensuring scalability and consistency. To allow AWS Console access, we create login profiles. This enforces a password reset on first login, aligning with security best practices. Groups are used to manage permissions collectively. This allows permissions to be managed at the group level instead of per user. Users are automatically assigned to groups based on their attributes. This approach eliminates manual group assignment and ensures accuracy. Terraform cannot create MFA devices, but it can enforce MFA usage through IAM policies. This policy denies all AWS actions unless MFA is enabled. Once attached, all users in these groups must enable MFA to access AWS. Permissions are granted using AWS managed policies. This is useful for outputs, debugging, and ensuring Terraform is operating in the correct AWS account. For production environments, AWS IAM Identity Center (SSO) is recommended instead of IAM users. This Terraform-based IAM user management solution demonstrates how identity can be treated as code, not configuration. By combining CSV-driven data, dynamic group membership, MFA enforcement, and least-privilege access, you get a scalable and secure IAM architecture suitable for real-world environments. This approach not only simplifies administration but also aligns closely with modern DevOps and security best practices. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK: locals { users = csvdecode(file("users.csv")) // It gives list of maps for the data } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: locals { users = csvdecode(file("users.csv")) // It gives list of maps for the data } CODE_BLOCK: locals { users = csvdecode(file("users.csv")) // It gives list of maps for the data } COMMAND_BLOCK: 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 Email = each.value.email Phone = each.value.phone } } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: 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 Email = each.value.email Phone = each.value.phone } } COMMAND_BLOCK: 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 Email = each.value.email Phone = each.value.phone } } CODE_BLOCK: 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 ] } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: 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 ] } } CODE_BLOCK: 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 ] } } CODE_BLOCK: resource "aws_iam_group" "education" { name = "Education" path = "/groups/" } resource "aws_iam_group" "managers" { name = "Managers" path = "/groups/" } resource "aws_iam_group" "engineers" { name = "Engineers" path = "/groups/" } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: resource "aws_iam_group" "education" { name = "Education" path = "/groups/" } resource "aws_iam_group" "managers" { name = "Managers" path = "/groups/" } resource "aws_iam_group" "engineers" { name = "Engineers" path = "/groups/" } CODE_BLOCK: resource "aws_iam_group" "education" { name = "Education" path = "/groups/" } resource "aws_iam_group" "managers" { name = "Managers" path = "/groups/" } resource "aws_iam_group" "engineers" { name = "Engineers" path = "/groups/" } CODE_BLOCK: 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" ] } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: 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" ] } CODE_BLOCK: 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" ] } CODE_BLOCK: 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)) ] } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: 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)) ] } CODE_BLOCK: 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)) ] } CODE_BLOCK: resource "aws_iam_group_membership" "engineers_members" { name = "engineers-group-membership" group = aws_iam_group.engineers.name users = [ for user in aws_iam_user.users : user.name if user.tags.Department == "Engineering" ] } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: resource "aws_iam_group_membership" "engineers_members" { name = "engineers-group-membership" group = aws_iam_group.engineers.name users = [ for user in aws_iam_user.users : user.name if user.tags.Department == "Engineering" ] } CODE_BLOCK: resource "aws_iam_group_membership" "engineers_members" { name = "engineers-group-membership" group = aws_iam_group.engineers.name users = [ for user in aws_iam_user.users : user.name if user.tags.Department == "Engineering" ] } CODE_BLOCK: resource "aws_iam_policy" "require_mfa" { name = "Require-MFA" description = "Deny access unless MFA is enabled" policy = jsonencode({ Version = "2012-10-17" Statement = [ { Sid = "DenyAllExceptMFA" Effect = "Deny" Action = "*" Resource = "*" Condition = { BoolIfExists = { "aws:MultiFactorAuthPresent" = "false" } } } ] }) } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: resource "aws_iam_policy" "require_mfa" { name = "Require-MFA" description = "Deny access unless MFA is enabled" policy = jsonencode({ Version = "2012-10-17" Statement = [ { Sid = "DenyAllExceptMFA" Effect = "Deny" Action = "*" Resource = "*" Condition = { BoolIfExists = { "aws:MultiFactorAuthPresent" = "false" } } } ] }) } CODE_BLOCK: resource "aws_iam_policy" "require_mfa" { name = "Require-MFA" description = "Deny access unless MFA is enabled" policy = jsonencode({ Version = "2012-10-17" Statement = [ { Sid = "DenyAllExceptMFA" Effect = "Deny" Action = "*" Resource = "*" Condition = { BoolIfExists = { "aws:MultiFactorAuthPresent" = "false" } } } ] }) } CODE_BLOCK: resource "aws_iam_group_policy_attachment" "mfa_enforcement" { for_each = { education = aws_iam_group.education.name managers = aws_iam_group.managers.name engineers = aws_iam_group.engineers.name } group = each.value policy_arn = aws_iam_policy.require_mfa.arn } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: resource "aws_iam_group_policy_attachment" "mfa_enforcement" { for_each = { education = aws_iam_group.education.name managers = aws_iam_group.managers.name engineers = aws_iam_group.engineers.name } group = each.value policy_arn = aws_iam_policy.require_mfa.arn } CODE_BLOCK: resource "aws_iam_group_policy_attachment" "mfa_enforcement" { for_each = { education = aws_iam_group.education.name managers = aws_iam_group.managers.name engineers = aws_iam_group.engineers.name } group = each.value policy_arn = aws_iam_policy.require_mfa.arn } CODE_BLOCK: resource "aws_iam_group_policy_attachment" "education_readonly" { group = aws_iam_group.education.name policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess" } resource "aws_iam_group_policy_attachment" "managers_admin" { group = aws_iam_group.managers.name policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess" } resource "aws_iam_group_policy_attachment" "engineers_poweruser" { group = aws_iam_group.engineers.name policy_arn = "arn:aws:iam::aws:policy/PowerUserAccess" } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: resource "aws_iam_group_policy_attachment" "education_readonly" { group = aws_iam_group.education.name policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess" } resource "aws_iam_group_policy_attachment" "managers_admin" { group = aws_iam_group.managers.name policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess" } resource "aws_iam_group_policy_attachment" "engineers_poweruser" { group = aws_iam_group.engineers.name policy_arn = "arn:aws:iam::aws:policy/PowerUserAccess" } CODE_BLOCK: resource "aws_iam_group_policy_attachment" "education_readonly" { group = aws_iam_group.education.name policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess" } resource "aws_iam_group_policy_attachment" "managers_admin" { group = aws_iam_group.managers.name policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess" } resource "aws_iam_group_policy_attachment" "engineers_poweruser" { group = aws_iam_group.engineers.name policy_arn = "arn:aws:iam::aws:policy/PowerUserAccess" } CODE_BLOCK: data "aws_caller_identity" "current" {} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: data "aws_caller_identity" "current" {} CODE_BLOCK: data "aws_caller_identity" "current" {} - Centralized and version-controlled user management - Easy onboarding and offboarding of users - Consistent security policies across teams - Reduced manual errors - Idempotent and auditable changes - IAM users created dynamically from a CSV file - IAM groups for Education, Managers, and Engineers - Automatic group membership based on user attributes - Console access with forced password reset - MFA enforcement across all groups - Group-based permission management - Usernames are generated automatically (first initial + last name) - Tags store rich metadata for filtering, auditing, and policies - No hardcoding of users - Education users have read-only access - Managers have full administrative access - Engineers can manage resources but not IAM - MFA enforced across all users - Permissions applied via groups, not users - Metadata stored as tags for auditing - CSV-driven user lifecycle management - Infrastructure fully reproducible and auditable