Skip to main content

Command Palette

Search for a command to run...

Day 16: AWS IAM User Management with Terraform – CSV-Driven Onboarding, RBAC, Password Security, PGP Encryption, and SSO Best Practices

Updated
12 min read
Day 16: AWS IAM User Management with Terraform – CSV-Driven Onboarding, RBAC, Password Security, PGP Encryption, and SSO Best Practices
P
Welcome! I’m Prajwal P. I stand at the intersection of technology and efficiency, exploring the dynamic world of DevOps ⚙️. From mastering Cloud infrastructure to orchestrating containers, I am passionate about automating the complex to create the simple. Join me as I document my learning curve, share technical insights, and navigate the ever-evolving landscape of software deployment.

Introduction

Managing AWS IAM users manually becomes difficult as organizations grow. Creating users one by one, assigning permissions manually, and maintaining access controls can quickly become error-prone.

In this project, we'll automate AWS IAM user onboarding using Terraform.

We'll:

  • Create IAM users from a CSV file

  • Automatically generate usernames

  • Create IAM login profiles

  • Organize users into IAM groups

  • Implement Role-Based Access Control (RBAC)

  • Attach custom IAM policies to groups

  • Understand AWS password handling

  • Learn why Terraform cannot retrieve AWS-generated passwords

  • Explore PGP encryption

  • Discuss MFA onboarding

  • Compare IAM Users vs AWS IAM Identity Center (SSO)

By the end of this project, you'll understand both Terraform fundamentals and real-world IAM security practices.


Project Architecture

users.csv
    ↓
csvdecode()
    ↓
Terraform
    ↓
IAM Users
    ↓
User Tags
    ↓
Group Membership Logic
    ↓
IAM Groups
    ↓
IAM Policies
    ↓
RBAC

Project Structure

day16/
├── backend.tf
├── providers.tf
├── main.tf
├── groups.tf
├── policies.tf
└── users.csv

Configure Remote State

backend.tf

terraform {
  backend "s3" {
    bucket       = "terraform-state-1766811759"
    key          = "day14ls/terraform.tfstate"
    region       = "us-east-1"
    use_lockfile = true
    encrypt      = true
  }
}

Explanation

This stores Terraform state remotely in Amazon S3.

Benefits:

Team Collaboration
State Persistence
Versioning
State Locking
Encryption

Understanding Each Parameter

bucket

bucket = "terraform-state-1766811759"

S3 bucket where state is stored.


key

key = "day14ls/terraform.tfstate"

Path of the state file inside S3.


use_lockfile

use_lockfile = true

Prevents multiple people from running Terraform simultaneously.


encrypt

encrypt = true

Encrypts Terraform state in S3.


Configure Provider

providers.tf

terraform {
  required_version = ">= 1.6.0"

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

provider "aws" {
  region = "us-east-1"
}

Explanation

required_version

Ensures Terraform version compatibility.


required_providers

Downloads AWS provider plugin.


provider "aws"

Configures AWS region.


Reading Data From CSV

users.csv

first_name,last_name,email,phone,department,job_title
Michael,Scott,michael.scott@dundermifflin.com,1111111111,Education,Regional Manager
Jim,Halpert,jim.halpert@dundermifflin.com,3333333333,Engineering,Software Engineer
Angela,Martin,angela.martin@dundermifflin.com,5558190243,Engineering,Software Engineer

This simulates an HR export file.


AWS Account Information

data "aws_caller_identity" "current" {}

What is a Data Source?

Data sources read existing AWS information.

Example:

data "aws_caller_identity" "current" {}

retrieves:

AWS Account ID
User ARN
User ID

Output:

output "account_id" {
  value = data.aws_caller_identity.current.account_id
}

Reading CSV Data

locals {
  users = csvdecode(file("users.csv"))
}

This is one of the most important parts of the project.


file()

file("users.csv")

Reads file contents.

Example:

first_name,last_name
Jim,Halpert

returns:

Raw Text

csvdecode()

csvdecode(file("users.csv"))

Converts CSV into Terraform objects.

Example:

first_name,last_name
Jim,Halpert

becomes:

[
  {
    first_name = "Jim"
    last_name  = "Halpert"
  }
]

Creating 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
    Email       = each.value.email
    Phone       = each.value.phone
  }
}

Understanding for_each

Instead of:

resource "aws_iam_user" "jim" {}
resource "aws_iam_user" "pam" {}
resource "aws_iam_user" "dwight" {}

Terraform loops through every CSV record.


Understanding for Expression

for user in local.users : user.first_name => user

Transforms:

Michael Scott
Jim Halpert
Pam Beesly

into:

{
  Michael = {...}
  Jim     = {...}
  Pam     = {...}
}

Understanding substr()

substr(each.value.first_name, 0, 1)

Example:

Michael

returns:

M

Understanding lower()

lower("MScott")

returns:

mscott

Result:

Michael Scott → mscott
Jim Halpert → jhalpert
Angela Martin → amartin

User Tags

Every user receives:

tags = {
  DisplayName = ...
  Department  = ...
  JobTitle    = ...
  Email       = ...
  Phone       = ...
}

These tags drive our RBAC logic.


Creating Login Profiles

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_length,
      password_reset_required,
    ]
  }
}

Important Password Discussion

Many people expect:

Terraform
↓
Password
↓
Output

But AWS does NOT work this way.

Terraform requests:

Create Login Profile

AWS internally:

Generates Password
Stores Password
Creates Login Profile

Terraform never receives plaintext password.


Why AWS Doesn't Reveal Passwords

If AWS exposed passwords:

Terraform State
CI/CD Logs
Console Output
S3 Backend

could all contain user credentials.

This would be a massive security risk.


How To Verify Login Profiles

aws iam get-login-profile --user-name mscott

Output:

{
  "LoginProfile": {
    "UserName": "mscott",
    "PasswordResetRequired": true
  }
}

Notice:

Password is NOT returned.

IAM Groups

groups.tf

resource "aws_iam_group" "education" {
  name = "Education"
}

resource "aws_iam_group" "managers" {
  name = "Managers"
}

resource "aws_iam_group" "engineers" {
  name = "Engineers"
}

Dynamic Group Membership

Education Group:

users = [
  for user in aws_iam_user.users :
  user.name
  if user.tags.Department == "Education"
]

Managers Group:

users = [
  for user in aws_iam_user.users :
  user.name
  if contains(keys(user.tags), "JobTitle")
  && can(regex("Manager|CEO", user.tags.JobTitle))
]

Understanding keys()

keys(user.tags)

Returns:

[
  "DisplayName",
  "Department",
  "JobTitle",
  "Email",
  "Phone"
]

Understanding contains()

contains(keys(user.tags), "JobTitle")

Checks if JobTitle exists.

Returns:

true

or

false

Understanding regex()

regex("Manager|CEO", user.tags.JobTitle)

Matches:

Regional Manager
CEO
CEO of Sabre

Does not match:

Software Engineer
Accountant

Engineers Group

user.tags.Department == "Engineering"

This automatically places:

Jim Halpert
Angela Martin
Oscar Martinez

into Engineers.


Custom IAM Policies

Education Policy:

s3:Get*
s3:List*

Read-only S3 access.


Managers Policy:

ec2:Describe*

Read-only EC2 visibility.


Engineers Policy:

s3:*

Full S3 access.


Policy Attachments

resource "aws_iam_group_policy_attachment"

Connects:

Group
↓
Policy

Example:

Engineers
↓
EngineerS3FullAccess

In this project, we successfully automated IAM user onboarding using Terraform.

We built:

  • IAM Users from a CSV file

  • IAM Groups

  • Dynamic Group Membership

  • Role-Based Access Control (RBAC)

  • IAM Policies

  • Policy Attachments

At first, I wanted to go one step further and automatically generate passwords, store them in AWS Secrets Manager, and distribute them to users. However, while exploring Terraform and AWS IAM more deeply, I discovered an important security design decision made by AWS.

In this section, let's understand how AWS handles passwords, why Terraform cannot retrieve them, how PGP encryption solves the problem, and why most modern organizations prefer AWS IAM Identity Center (SSO).


How IAM User Password Creation Works

When we create an IAM user login profile:

resource "aws_iam_user_login_profile" "users" {
  for_each = aws_iam_user.users

  user                    = each.value.name
  password_reset_required = true
}

Notice something interesting.

There is no:

password = "MyPassword123!"

argument.

Terraform only tells AWS:

Create console access for this user.

AWS then performs the following steps:

Terraform
    ↓
Create Login Profile
    ↓
AWS Generates Random Password
    ↓
AWS Stores Password Internally
    ↓
Console Access Enabled

The actual password is generated inside AWS.

Terraform never sees the plaintext password.


Where Is The Password Stored?

The password becomes part of the IAM Login Profile.

You can verify the login profile exists:

aws iam get-login-profile --user-name mscott

Example output:

{
  "LoginProfile": {
    "UserName": "mscott",
    "CreateDate": "2026-06-15T10:00:00Z",
    "PasswordResetRequired": true
  }
}

Notice that AWS returns:

  • Username

  • Creation date

  • Password reset requirement

But AWS never returns:

{
  "password": "TempPassword123!"
}

The password cannot be retrieved.


Why Doesn't AWS Simply Return The Password?

Imagine if AWS returned:

output "password" {
  value = aws_iam_user_login_profile.users.password
}

Now the password would appear in:

  • Terraform outputs

  • CI/CD logs

  • Jenkins logs

  • GitLab logs

  • GitHub Actions logs

  • Terraform state files

  • Remote S3 backends

For example:

{
  "username": "mscott",
  "password": "TempPassword123!"
}

This would be a massive security risk.

Anyone with access to Terraform state could steal every user's password.

AWS intentionally prevents this.


Terraform State File Security Problem

Suppose AWS allowed Terraform to store passwords.

Your state file might contain:

{
  "resources": [
    {
      "username": "mscott",
      "password": "TempPassword123!"
    }
  ]
}

Now anyone with access to:

terraform.tfstate

could see every user's password.

In our project we are storing state remotely in S3.

That means passwords would also be stored in:

S3 Backend

which creates another security risk.

This is one of the main reasons AWS does not expose plaintext passwords.


How PGP Encryption Solves The Problem

Instead of returning the password directly, AWS supports PGP encryption.

PGP stands for:

Pretty Good Privacy

The idea is simple.

You create:

Public Key
Private Key

Think of them as:

Public Key  = Lock
Private Key = Key

Step 1 – Generate PGP Keys

You create a key pair:

Public Key
Private Key

The public key can be shared safely.

The private key must remain secret.


Step 2 – Give AWS Your Public Key

Terraform configuration:

resource "aws_iam_user_login_profile" "users" {
  for_each = aws_iam_user.users

  user                    = each.value.name
  password_reset_required = true

  pgp_key = file("public-key.asc")
}

Now AWS has your public key.


Step 3 – AWS Generates Password

AWS creates:

TempPassword123!

Step 4 – AWS Encrypts Password

Using your public key:

TempPassword123!
      ↓
PGP Encryption
      ↓
wcBMA4DJskdfjsdf...

Step 5 – Terraform Receives Encrypted Password

Terraform receives:

encrypted_password

not:

password

Example output:

wcBMA4DJskdfjsdf...

This encrypted value is safe to store.

Even if someone steals:

terraform.tfstate

they still cannot see the password.


Step 6 – Decrypt Using Private Key

Only the owner of the private key can decrypt:

gpg --decrypt password.gpg

Result:

TempPassword123!

Advantages Of PGP Encryption

Protects Terraform State

Without PGP:

State File
    ↓
Plaintext Passwords

With PGP:

State File
    ↓
Encrypted Passwords

Protects CI/CD Logs

Without PGP:

GitLab Logs
    ↓
Password Exposure

With PGP:

GitLab Logs
    ↓
Encrypted Data Only

Protects Remote Backends

Even if someone gains access to:

S3 State Bucket

they cannot read the passwords.


Why Not Store Passwords In Secrets Manager?

Initially I planned to:

Generate Password
    ↓
Store In Secrets Manager

However, I discovered a limitation.

AWS generates the IAM login password internally.

Terraform never receives the plaintext password.

Therefore:

AWS Login Password

cannot automatically be placed into:

AWS Secrets Manager

unless you use the PGP workflow.

Without PGP, Terraform simply does not know the actual password.


MFA In Production Environments

A common enterprise workflow looks like:

Create IAM User
        ↓
Provide Temporary Password
        ↓
User Logs In
        ↓
User Changes Password
        ↓
User Configures MFA
        ↓
MFA Enforcement Policy Applied

This avoids locking users out before they have a chance to enroll in MFA.


How SSO Changes Everything

Modern organizations rarely create IAM users manually.

Instead they use:

Microsoft Entra ID
Okta
Google Workspace

with AWS IAM Identity Center.

Architecture:

Employee
    ↓
Corporate Identity Provider
    ↓
AWS IAM Identity Center
    ↓
AWS Account Access

Traditional IAM User Flow

Create User
    ↓
Generate Password
    ↓
Distribute Password
    ↓
Change Password
    ↓
Enable MFA

Lots of operational overhead.


AWS SSO Flow

HR Creates Employee
        ↓
Employee Added To Entra ID
        ↓
Employee Logs In Using Corporate Password
        ↓
AWS Trusts Entra ID
        ↓
Access Granted

No password generation.

No password distribution.

No PGP.

No login profile management.

No Secrets Manager for user passwords.


Why SSO Is Considered Superior

Benefits:

Single Sign-On
Centralized Authentication
Centralized MFA
No Password Distribution
Simplified User Lifecycle
Better Security
Better Auditability

This is why most modern enterprises prefer AWS IAM Identity Center over managing hundreds of IAM users.


Final Thoughts

While building this project, I learned that creating IAM users is only part of the identity management story.

AWS intentionally prevents Terraform from accessing plaintext passwords because storing them in state files, logs, or remote backends would create significant security risks.

PGP encryption provides a secure way to retrieve generated passwords exactly once while keeping Terraform state protected.

However, modern organizations increasingly avoid this entire challenge by adopting AWS IAM Identity Center (SSO), allowing users to authenticate using their corporate identity provider and centralized MFA.

Understanding both approaches—traditional IAM users and modern SSO—is essential for anyone working with AWS and Terraform.

https://youtu.be/33dWo4esH1U

Terraform on AWS

Part 1 of 14

30 Days of Terraform on AWS is a hands-on series that takes you from IaC fundamentals to production-ready AWS architectures. Learn Terraform basics, AWS provisioning, mini projects, and advanced topics like EKS, CI/CD, GitOps, and Terraform Cloud.

Up next

Day-15 Building a Full-Mesh Multi-Region VPC Peering Architecture Using Terraform on AWS

Project Source Code The complete Terraform code used in this project is available on GitHub: 🔗 https://github.com/Prajwal8651/terraform-practice/tree/main/lessons/day15 Feel free to clone the reposit