How to Create AWS IAM users with Terraform & OpenTofu
Programmatically creating AWS users using IaC tools like Terraform & OpenTofu #
This how-to shows using Terraform and OpenTofu to create AWS IAM Users and their corresponding access keys to allow programmatic calls to AWS from the AWS CLI, AWS Tools for PowerShell, AWS SDKs, or direct AWS API calls. It uses a PGP Key Pair to encrypt/decrypt the Secret access key.
Prerequisites #
You need:
- A working Terraform or OpenTofu installation. See How to Install OpenTofu,
- A Key Pair generated with the PrettyGood Privacy (PGP)
gpgtool. See how to install and use gpg.
Terraform Plan to Create Users #
Create a Terraform infrastructure definition file.
The resource "aws_iam_access_key" is used to create the Access Key. It tells Terraform to encrypt the value using the public PGP key in base64 format stored in the location specified inside the file path (e.g. ~/keys/itwonderlab.com/ditwl_infradmin_gpg_b64_public.key).
The output of resource "aws_iam_access_key" is stored in two Terraform output variables:
- project_1_admin_access_key: Access key
- project_1_admin_encrypted_secret_access_key: Encrypted Secret access key
# Copyright (C) 2018 - 2023 IT Wonder Lab (https://www.itwonderlab.com)
#
# This software may be modified and distributed under the terms
# of the MIT license. See the LICENSE file for details.
# -------------------------------- WARNING --------------------------------
# IT Wonder Lab's best practices for infrastructure include modularizing
# Terraform/OpenTofu configuration.
# In this example, we define everything in a single file.
# See other tutorials for best practices at itwonderlab.com
# -------------------------------- WARNING --------------------------------
#Define Terrraform Providers and Backend
terraform {
required_version = "> 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
# Stores the state as a given key in a given bucket on Amazon S3.
backend "s3" {
bucket = "ditwonderlab"
key = "ditwl_infradmin/aws-terraform-user/terraform"
profile = "ditwl_infradmin"
region = "us-east-1"
shared_credentials_files = ["~/.aws/credentials"]
}
}
# Provider AWS
provider "aws" {
shared_credentials_files = ["~/.aws/credentials"]
profile = "ditwl_infradmin"
region = "us-east-1"
}
# USERS & ACCESS KEYS
resource "aws_iam_user" "project_1_admin" {
name = "ditwl_user_project_1_admin"
path = "/ditwl/tutorial/"
tags = {
"Name" = "ditwl_user_project_1_admin"
}
}
# Create an Access Key for the user and encrypt its value using PGP public key
resource "aws_iam_access_key" "project_1_admin" {
user = aws_iam_user.project_1_admin.name
pgp_key = file("~/keys/itwonderlab.com/ditwl_infradmin_gpg_b64_public.key")
}
# Store the Access key in an Output variables
output "project_1_admin_access_key" {
value = aws_iam_access_key.project_1_admin.id
}
# Store the Encrypted Secret access key in an Output variables
output "project_1_admin_encrypted_secret_access_key" {
value = aws_iam_access_key.project_1_admin.encrypted_secret_access_key
}
Init, Plan, and Apply the Terraform infrastructure definition #
Init, Plan, and Apply the configuration
$ terraform init
...
$ terraform plan
...
$ terraform apply
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_iam_access_key.project_1_admin will be created
+ resource "aws_iam_access_key" "project_1_admin" {
+ create_date = (known after apply)
+ encrypted_secret = (known after apply)
+ encrypted_ses_smtp_password_v4 = (known after apply)
+ id = (known after apply)
+ key_fingerprint = (known after apply)
+ pgp_key = <<-EOT
mQGNBGVThvgBDAC35weEx21DrMjKCE/bODUnzewNpOgMsqWWOUmnYSMrPDVhoWvQ5jt/4YIAlqLy
....
YstYpiGx7Bh1gjnVlXPBWyuYy8LKzGAvdYccT5sM7irYbJOf6sIrQ623A6o6eWxeA/9j+erW/icK
srxxj4Hr+T4jplnBiyVB4U4/EFEZTc5ofl4qkbtnCP+P58MFxydByJKQy7BK7IpSvw==
EOT
+ secret = (sensitive value)
+ ses_smtp_password_v4 = (sensitive value)
+ status = "Active"
+ user = "ditwl_user_project_1_admin"
}
# aws_iam_user.project_1_admin will be created
+ resource "aws_iam_user" "project_1_admin" {
+ arn = (known after apply)
+ force_destroy = false
+ id = (known after apply)
+ name = "ditwl_user_project_1_admin"
+ path = "/ditwl/tutorial/"
+ tags = {
+ "Name" = "ditwl_user_project_1_admin"
}
+ tags_all = {
+ "Name" = "ditwl_user_project_1_admin"
}
+ unique_id = (known after apply)
}
Plan: 2 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ project_1_admin_access_key = (known after apply)
+ project_1_admin_encrypted_secret_access_key = (known after apply)
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
aws_iam_user.project_1_admin: Creating...
aws_iam_user.project_1_admin: Creation complete after 2s [id=ditwl_user_project_1_admin]
aws_iam_access_key.project_1_admin: Creating...
aws_iam_access_key.project_1_admin: Creation complete after 0s [id=AKIAW....YUX5]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
project_1_admin_access_key = "AKIAW....YUX5"
project_1_admin_encrypted_secret_access_key = "wcDMAzjqv6YGlVfzAQv+LYk0siFvDoOM...0Zuq92r3+3kn8Ww=="$ tofu init
...
$ tofu plan
...
$ tofu apply
OpenTofu used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
OpenTofu will perform the following actions:
# aws_iam_access_key.project_1_admin will be created
+ resource "aws_iam_access_key" "project_1_admin" {
+ create_date = (known after apply)
+ encrypted_secret = (known after apply)
+ encrypted_ses_smtp_password_v4 = (known after apply)
+ id = (known after apply)
+ key_fingerprint = (known after apply)
+ pgp_key = <<-EOT
mQGNBGVThvgBDAC35weEx21DrMjKCE/bODUnzewNpOgMsqWWOUmnYSMrPDVhoWvQ5jt/4YIAlqLy
....
YstYpiGx7Bh1gjnVlXPBWyuYy8LKzGAvdYccT5sM7irYbJOf6sIrQ623A6o6eWxeA/9j+erW/icK
srxxj4Hr+T4jplnBiyVB4U4/EFEZTc5ofl4qkbtnCP+P58MFxydByJKQy7BK7IpSvw==
EOT
+ secret = (sensitive value)
+ ses_smtp_password_v4 = (sensitive value)
+ status = "Active"
+ user = "ditwl_user_project_1_admin"
}
# aws_iam_user.project_1_admin will be created
+ resource "aws_iam_user" "project_1_admin" {
+ arn = (known after apply)
+ force_destroy = false
+ id = (known after apply)
+ name = "ditwl_user_project_1_admin"
+ path = "/ditwl/tutorial/"
+ tags = {
+ "Name" = "ditwl_user_project_1_admin"
}
+ tags_all = {
+ "Name" = "ditwl_user_project_1_admin"
}
+ unique_id = (known after apply)
}
Plan: 2 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ project_1_admin_access_key = (known after apply)
+ project_1_admin_encrypted_secret_access_key = (known after apply)
Do you want to perform these actions?
OpenTofu will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
aws_iam_user.project_1_admin: Creating...
aws_iam_user.project_1_admin: Creation complete after 1s [id=ditwl_user_project_1_admin]
aws_iam_access_key.project_1_admin: Creating...
aws_iam_access_key.project_1_admin: Creation complete after 0s [id=AKIAW....YUX5]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
project_1_admin_access_key = "AKIAW....YUX5"
project_1_admin_encrypted_secret_access_key = "wcDMAzjqv6YGlVfzAQv+LYk0siFvDoOM...0Zuq92r3+3kn8Ww=="Extract the Private Secret Access Key #
Use the Terraform / OpenTofu output command to access the encrypted access key output project_1_admin_encrypted_secret_access_key variable, decode from base64, and decrypt using the gpg tool. The decrypted Secret Access Key is stored in the file project_1_admin_encrypted_secret
$ terraform output -raw project_1_admin_encrypted_secret_access_key | base64 --decode | gpg --decrypt --output project_1_admin_encrypted_secret