# AWS IAM User Management | Terraform & Boto3 Scripts

## Overview

Finding the balance between ensuring the security of user identities while providing a self-service user experience takes continuous effort. With the constantly evolving cloud landscape and the security around it, IAM user administration can become a burdensome task. In this post, I'll provide the steps to an approach that attempts to ensure the security of AWS IAM users while streamlining administrative overhead and maintaining a self-service user experience by using a combination of Terraform code and Python Boto3 scripts.

## Code Functionality

The solution below is comprised of both Terraform code and Python boto3 scripts creating an efficient method for managing IAM users and their associated login profiles, MFA devices and access keys.

### Terraform Code

* Manages IAM user identities, email address tags and optional custom tags (i.e. Access Key descriptions)
    
* `aws_iam_user` resource block is used with a for\_each meta-argument which loops through the map of users assigned to the `user_info` variable
    
* `local-exec` provisioner is used to call external executable scripts which are uniquely defined based on the creation and destruction of an IAM user
    
* `create-time` provisioner creates the login profile and generates a temporary login password during user creation
    
* `destroy-time` provisioner destroys the login profile, MFA devices and access keys during user destruction
    

[![](https://cdn.hashnode.com/res/hashnode/image/upload/v1695215428174/b7a2cdcb-9676-415f-9901-dc856315311f.png align="center")](https://github.com/jksprattler/aws-security/blob/main/terraform/iam/users.tf)

### Python Boto3 Scripts

* Automatically invoked by the `local-exec` provisioner upon `terraform apply` and are unique based on the definition set in the `when` meta-argument
    
* These scripts allow users to manage their MFA devices and access keys rather than having these resources checked into the terraform state and managed by an IAM admin
    
* Allows IAM admins to run a single command to create or destroy a user rather than running additional commands to invoke scripts to create or destroy user resources such as the login profile, MFA devices and access keys
    

### Code Resources

*Link to the Terraform code:* [https://github.com/jksprattler/aws-security/blob/main/terraform/iam/users.tf](https://github.com/jksprattler/aws-security/blob/main/terraform/iam/users.tf)

*Link to the local-exec provisioner boto3 script that* ***creates*** *the login profile and generates the temp password for the new user:* [https://github.com/jksprattler/aws-security/blob/main/scripts/aws\_iam\_user\_password\_reset.py](https://github.com/jksprattler/aws-security/blob/main/scripts/aws_iam_user_password_reset.py)

*Link to the local-exec provisioner boto3 script that* ***destroys*** *the login profile, MFA devices and access keys for a removed user:* [https://github.com/jksprattler/aws-security/blob/main/scripts/aws\_iam\_user\_cleanup.py](https://github.com/jksprattler/aws-security/blob/main/scripts/aws_iam_user_cleanup.py)

*Link to my blog post on IAM User Password Expiry Notice which is why I have the email tags required in this post on IAM user management:* [https://blog.jennasrunbooks.com/aws-lambda-function-iam-user-password-expiry-notice-ses-boto3-terraform](https://blog.jennasrunbooks.com/aws-lambda-function-iam-user-password-expiry-notice-ses-boto3-terraform)

## Usage

### Create IAM User

Creating a new AWS IAM user with the provided terraform configuration requires updating the `user_info` variable definition by adding a new map object assignment containing the new username and the required corresponding `email` address key-value tag. The optional `user_tags` can be disregarded for now - these are only needed if the user decides to apply a description to their access key ID. The need to update these optional tags would be indicated by a future terraform plan output where the key-value tags are missing from the terraform state.

Below is some sample terraform output from creating a new IAM user called "userB":

```yaml
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_user.this["userB"] will be created
  + resource "aws_iam_user" "this" {
      + arn           = (known after apply)
      + force_destroy = false
      + id            = (known after apply)
      + name          = "userB"
      + path          = "/"
      + tags          = {
          + "email"            = "userB@jennasrunbooks.com"
          + "environment-type" = "lab"
          + "provisioner"      = "terraform"
          + "repo"             = "aws-security"
          + "resource-owner"   = "aws-landing-zone@jennasrunbooks.com"
        }
      + tags_all      = {
          + "email"            = "userB@jennasrunbooks.com"
          + "environment-type" = "lab"
          + "provisioner"      = "terraform"
          + "repo"             = "aws-security"
          + "resource-owner"   = "aws-landing-zone@jennasrunbooks.com"
        }
      + unique_id     = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

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.this["userB"]: Creating...
aws_iam_user.this["userB"]: Provisioning with 'local-exec'...
aws_iam_user.this["userB"] (local-exec): Executing: ["/bin/sh" "-c" "python ../../scripts/aws/aws_iam_user_password_reset.py profile -u userB"]
aws_iam_user.this["userB"] (local-exec): New login profile has been created for: userB
aws_iam_user.this["userB"] (local-exec): Login with temp password:
aws_iam_user.this["userB"] (local-exec): ,)y3}"fXafTHj=Acei';
aws_iam_user.this["userB"] (local-exec): Password reset will be enforced upon initial login
aws_iam_user.this["userB"]: Creation complete after 1s [id=userB]
Releasing state lock. This may take a few moments...

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
```

As seen in the above output, the `local-exec` provisioner provides the temporary password to the login profile for the new user based on this line:

`aws_iam_user.this["userB"] (local-exec): ,)y3}"fXafTHj=Acei';`

This can now be directly provided to the new user so they can log into the AWS Console UI where they'll be forced to reset their password and then proceed to set up their MFA devices and Access keys on their own.

### Destroy IAM User

Destroying an AWS IAM user is as simple as deleting their map object assignment values from the `user_info` variable. Additionally, you'd want to remove the user from any IAM groups however, this post is focused on IAM user management using the `aws_iam_user` terraform resource block.

Below is some sample terraform output from destroying the "userB" IAM user created in the previous section:

```yaml
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # aws_iam_user.this["userB"] will be destroyed
  # (because key ["userB"] is not in for_each map)
  - resource "aws_iam_user" "this" {
      - arn           = "arn:aws:iam::<accountID>:user/userB" -> null
      - force_destroy = false -> null
      - id            = "userB" -> null
      - name          = "userB" -> null
      - path          = "/" -> null
      - tags          = {
          - "AKIASRJ6UGTM2MP47QO6" = "testkey2"
          - "AKIASRJ6UGTMV3JU6CU2" = "testkey1"
          - "email"                = "userB@jennasrunbooks.com"
          - "environment-type"     = "lab"
          - "provisioner"          = "terraform"
          - "repo"                 = "aws-security"
          - "resource-owner"       = "aws-landing-zone@jennasrunbooks.com"
        } -> null
      - tags_all      = {
          - "AKIASRJ6UGTM2MP47QO6" = "testkey2"
          - "AKIASRJ6UGTMV3JU6CU2" = "testkey1"
          - "email"                = "userB@jennasrunbooks.com"
          - "environment-type"     = "lab"
          - "provisioner"          = "terraform"
          - "repo"                 = "aws-security"
          - "resource-owner"       = "aws-landing-zone@jennasrunbooks.com"
        } -> null
      - unique_id     = "AIDASRJ6UGTMQUHFQ77G4" -> null
    }

Plan: 0 to add, 0 to change, 1 to destroy.

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.this["userB"]: Destroying... [id=userB]
aws_iam_user.this["userB"]: Provisioning with 'local-exec'...
aws_iam_user.this["userB"] (local-exec): Executing: ["/bin/sh" "-c" "python ../../scripts/aws/aws_iam_user_cleanup.py userB"]
aws_iam_user.this["userB"] (local-exec): Deleting login profile for userB
aws_iam_user.this["userB"] (local-exec): Deleting MFA device for userB: arn:aws:iam::<accountID>:u2f/user/testuser/testfido-I5GXZJQHZL3FXDIXX4
aws_iam_user.this["userB"] (local-exec): Deleted access key: AKIASRJ6UGTMV3JU6CU2 for user: userB
aws_iam_user.this["userB"] (local-exec): Deleted access key: AKIASRJ6UGTM2MP47QO6 for user: userB
aws_iam_user.this["userB"]: Destruction complete after 2s
Releasing state lock. This may take a few moments...

Apply complete! Resources: 0 added, 0 changed, 1 destroyed.
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">The <code>force_destroy</code> option when set to true in the <a target="_blank" rel="noopener noreferrer nofollow" href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user#force_destroy" style="pointer-events: none">aws_iam_user </a>resource block states: <em>When destroying this user, destroy even if it has non-Terraform-managed IAM access keys, login profile or MFA devices. Without force_destroy a user with non-Terraform-managed access keys and login profile will fail to be destroyed. </em>I tried testing this option by destroying a user with an associated login profile, MFA device and access key while running AWS provider version <code>5.16.2</code> and terraform version <code>1.5.7</code> and it failed with the following error:</div>
</div>

```yaml
╷
│ Error: deleting IAM User (blahblah): DeleteConflict: Cannot delete entity, must delete login profile first.
│       status code: 409, request id: ca2c4e6e-620a-4082-9077-04af49b29bcb
│
```

## Conclusion

By integrating Terraform and Python Boto3 scripts, the above approach attempts to provide an efficient solution for managing AWS IAM users while maintaining a secure yet self-service environment for the end user. Administrative overhead should be reduced since MFA device and access key management remain in the hands of the user and outside of the terraform state. As long as you have good documentation and your users are savvy enough managing MFA devices and access keys should not be an issue for them. The Terraform code also follows the DRY principle to reduce repetitive lines of code with a single `aws_iam_user` resource block used.
