Importing existing AWS IAM resources to Terraform
Tags: terraform, aws, iam, devops
Published: 2024-04-25 07:34:40

## Introduction
My project team created some AWS resources manually, but they wanted to bring this environment to another account. So here's Terraform. Using `terraform import` command, you can import existing resources to have Terraform manage.
In a nutshell, it's about updating tfstate file to reflect existing resources by this command, then reflecting the resources to main.tf (or other .tf files) to remove the differences between Terraform configuration and the real environment.

## Environment
- AWS
- Terraform v1.7.5
- Terraform Backend set to S3

## Requirements
- Basic knowledge of AWS
- Basic knowledge of Terraform
- IAM credentials that allow creating resources.

## Terraform Directory design
Since we had different environment such as production, test, development, the Terraform directory is designed as below.
Also, we wanted to re-use codes, so we divided modules as below. VPC and IAM modules were developed.
There is iam.tf in /environment/test directory. This is the tf file to create IAM resource, developed in this post. This will be copied to the other environments as well.

.
├── environments
│   ├── dev
│   │   ├── main.tf
│   │   ├── terraform.tf
│   │   ├── terraform.tfvars
│   │   └── variables.tf
│   ├── test
│   │   ├── iam.tf
│   │   ├── main.tf
│   │   ├── output.tf
│   │   ├── terraform.tf
│   │   ├── terraform.tfvars
│   │   └── variables.tf
│   ├── prod
│   │   ├── main.tf
│   │   ├── terraform.tf
│   │   ├── terraform.tfvars
│   │   └── variables.tf
├── modules
   ├── vpc
   │   ├── main.tf
   │   ├── output.tf
   │   └── variables.tf
   └── iam
       ├── policy
       │   ├── main.tf
       │   ├── output.tf
       │   ├── policies
       │   │   └── xxx_allow_policy.json
       │   ├── terraform.tf
       │   └── variables.tf
       └── role
           ├── main.tf
           ├── output.tf
           ├── roles
           │   ├── ec2.json
           │   ├── lambda.json
           │   └── s3.json
           ├── terraform.tf
           └── variables.tf


## Import
### Add resource block to .tf file
First, you need to add empty resource blocks for your imported resources in main.tf.
In this case, I've added a new file, iam.tf acting as main.tf when running `terraform apply` command, and main.tf for IAM module called from iam.tf. 
iam.tf is separated from main.tf in the environment directory for some operational reasons.
Below are the snippets.

```
# iam.tf in environment directory
module "iam_policy" {
  source = "../../modules/iam/policy"
}

module "iam_role" {
  source = "../../modules/iam/role"
}
```

You don't have to add arguments such as name or policy inside the block for now.

```
# main.tf in IAM Policy module directory
resource "aws_iam_policy" "test_policy" {
}
```

```
# main.tf in IAM Role module directory
resource "aws_iam_role" "test_role" {
}
```

### Import IAM Policy
Here is the command and the result example.

```
$ terraform import module.iam_policy.aws_iam_policy.test_policy arn:aws:iam::1234567890:policy/test-policy
xxx...
Import successful!
The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.
```

### Import IAM Role
Here is the command and the result example.

```
$ terraform import module.iam_role.aws_iam_role.test_role Test-Role
xxx...
Import successful!
The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.
```

### Check the tfstate file
You should see that your imported resource is added in the tfstate file like below.

```
{
  "module": "module.iam_policy",
  "mode": "managed",
  "type": "aws_iam_policy",
  "name": "test_policy",
  "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
  "instances": [
    {
      "schema_version": 0,
      "attributes": {
        xxxx
      },
      "sensitive_attributes": [],
      "private": "xxx"
    }
  ]
}
```

### Update main.tf in IAM Policy and Role module directory.

Now using the information added in the tfstate file, you need to add arguments in the resource blocks like below.
If you use JSON file for policies, you need to place them accordingly.

```
# main.tf in IAM Policy module directory
resource "aws_iam_policy" "test_policy" {
  name = "test-policy"
  policy = file("${path.module}/policies/xxx_allow_policy.json")
}
```
```
# main.tf in IAM Role module directory
resource "aws_iam_role" "Test_Role" {
  name   = "Test-Role"
  assume_role_policy = file("${path.module}/roles/s3.json")
  managed_policy_arns = ["arn:aws:iam::1234567890:policy/test-policy"]
}
```

### Plan to check differences
Let's check if there are differences between Terraform configuration (imported ones) and the real resources in AWS. If you find differences (a kind of conflicts you might say), you need to resolve them. In this case, there are 6 differences as below. If the change was subtle, such as adding tags, you might apply this configuration change to the real resources, or you might modify other settings to resolve.

```
$ terraform plan
xxx...
Plan: 0 to add, 6 to change, 0 to destroy.

```

## Note
1. If you failed to add resource blocks before importing, you should see an error as below.

```
Error: resource address "aws_iam_policy.test_policy" does not exist in the configuration.

Before importing this resource, please create its configuration in the root module. For example:

resource "aws_iam_policy" "test_policy" {
  # (resource arguments)
}
```

2. The arguments for importing IAM Policy and IAM Role are different. The former is ARN and the latter is Name. I wonder why indeed. If you used ARN by mistake to import IAM Role, the command would return Invalid error as below, which can be confusing.

```
│ Error: reading IAM Role (arn:aws:iam::1234567890:role/test-role): operation error IAM: GetRole, https response error StatusCode: 400, RequestID: 12345678-aaaa-bb12-cc34-1234567890, api error ValidationError: The specified value for roleName is invalid. It must contain only alphanumeric characters and/or the following: +=,.@_-
```
READ THE DOCUMENT!

3. If you imported wrong resources, you can remove them from Terraform state quickly as below. (This won't delete the real resources in AWS, no worry.)

Let's check if this would remove as you intend with dry-run.

```
$ terraform state rm --dry-run module.wrongmodule.aws_iam_policy.wrong_policy
Would remove module.wrongmodule.aws_iam_policy.wrong_policy
```

Then let's remove.

```
$ terraform state rm module.wrongmodule.aws_iam_policy.wrong_policy
Removed module.wrongmodule.aws_iam_policy.wrong_policy
Successfully removed 1 resource instance(s).
```

## Conclusion
OK, I've imported all the IAM resources to Terraform configuration with some try&error. It's really important to read Terraform official document in detail..
Hoping this post would help.

## References
- https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy_attachment
- https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment
- https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy
- https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role