Shammi Shailaj
Shammi Shailaj

Reputation: 475

Create and attach IAM role to EC2 using terraform

I have a terraform script to create an instance and attach a role to it.

Everything is getting created as per expectation.

The issue I am facing is that the IAM role is getting created and the policy is being attached to the role but role is not getting attached to the instance. Please help.

I have the following terraform script:

provider "aws" {
  region = "ap-southeast-1"
}

terraform {
  backend "s3" {
    bucket = "example.com"
    key    = "terraform/aws/ec2/xxxNNN/terraform.tfstate"
    region = "ap-southeast-1"
  }
}

resource "aws_iam_policy" "xxx_nodes_role_policy" {
  name        = "xxx_nodes_role_policy"
  description = "IAM Policy for XXX nodes"
  policy      = "${file("xxx_nodes_role_policy.json")}"
}

resource "aws_iam_role" "ec2_role_for_xxx_nodes" {
  name               = "ec2_role_for_xxx_nodes"
  assume_role_policy = "${file("ec2_assumerolepolicy.json")}"
}

resource "aws_iam_role_policy_attachment" "xxx_nodes_role_policy_attachment" {
  role       = "${aws_iam_role.ec2_role_for_xxx_nodes.name}"
  policy_arn = "${aws_iam_policy.xxx_nodes_role_policy.arn}"
}

resource "aws_iam_instance_profile" "xxx_instance_profile" {
  name  = "xxx_instance_profile"
  role = "${aws_iam_role.ec2_role_for_xxx_nodes.name}"
}

variable "sgids" {
  type = list(string)
  default = [ "sg-XXX", "sg-XXX" ]
}

resource "aws_instance" "xxxNNN" {
  ami           = "ami-063e3af9d2cc7fe94"
  instance_type = "r5.large"
  iam_instance_profile = "${aws_iam_instance_profile.xxx_instance_profile.name}"

  availability_zone = "ap-southeast-1a"
  key_name = "KKK"
  vpc_security_group_ids = var.sgids
  subnet_id = "subnet-XXX"
  associate_public_ip_address = false
  user_data = "${file("set-up.sh")}"

  root_block_device {
    volume_type = "gp2"
    volume_size = "200"
    delete_on_termination = true
  }

  tags = {
    Name = "XXXXXXXXXXX/XXXNNN"
  }

  lifecycle {
    prevent_destroy = true
  }
}

resource "aws_eip" "XXXNNN" {
  vpc = true
  instance = "${aws_instance.xxxNNN.id}"

  tags = {
    Name = "XXXNNN"
  }

  lifecycle {
    prevent_destroy = true
  }
}

The contents of xxx_nodes_role_policy.json are as follows:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:DescribeLogGroups",
                "logs:DescribeLogStreams",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:ap-southeast-1:0000:log-group:*",
                "arn:aws:logs:ap-southeast-1:0000:log-group:production:*"
            ]
        }
    ]
}

The contents of ec2_assumerolepolicy.json are as follows:

{
 "Version": "2012-10-17",
 "Statement": [
   {
     "Action": "sts:AssumeRole",
     "Principal": {
       "Service": "ec2.amazonaws.com"
     },
     "Effect": "Allow",
     "Sid": ""
   }
 ]
}

Everything is getting created as per expectation.

The issue I am facing is that the IAM role is getting created and the policy is being attached to the role but role is not getting attached to the instance. Please help.

EDIT: Adding terraform apply output below:

aws_iam_policy.xxx_nodes_role_policy: Refreshing state... [id=arn:aws:iam::XXX:policy/xxx_nodes_role_policy]
aws_iam_role.ec2_role_for_xxx_nodes: Refreshing state... [id=ec2_role_for_xxx_nodes]
data.aws_iam_policy_document.instance-assume-role-policy: Refreshing state...
aws_iam_role_policy_attachment.xxx_nodes_role_policy_attachment: Refreshing state... [id=ec2_role_for_xxx_nodes-20200717102807715700000001]
aws_iam_instance_profile.xxx_instance_profile: Refreshing state... [id=xxx_instance_profile]
aws_instance.xxxNNN: Refreshing state... [id=i-XXX]
aws_eip.XXXNNN: Refreshing state... [id=eipalloc-XXX]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_eip.XXXNNN will be created
  + resource "aws_eip" "XXXNNN" {
      + allocation_id     = (known after apply)
      + association_id    = (known after apply)
      + customer_owned_ip = (known after apply)
      + domain            = (known after apply)
      + id                = (known after apply)
      + instance          = (known after apply)
      + network_interface = (known after apply)
      + private_dns       = (known after apply)
      + private_ip        = (known after apply)
      + public_dns        = (known after apply)
      + public_ip         = (known after apply)
      + public_ipv4_pool  = (known after apply)
      + tags              = {
          + "Name" = "XXXNNN"
        }
      + vpc               = true
    }

  # aws_iam_policy.xxx_nodes_role_policy will be created
  + resource "aws_iam_policy" "xxx_nodes_role_policy" {
      + arn         = (known after apply)
      + description = "IAM Policy for XXX nodes"
      + id          = (known after apply)
      + name        = "xxx_nodes_role_policy"
      + path        = "/"
      + policy      = jsonencode(
            {
              + Statement = [
                  + {
                      + Action   = [
                          + "logs:CreateLogStream",
                          + "logs:DescribeLogGroups",
                          + "logs:DescribeLogStreams",
                          + "logs:PutLogEvents",
                        ]
                      + Effect   = "Allow"
                      + Resource = [
                          + "arn:aws:logs:ap-southeast-1:XXX:log-group:*",
                          + "arn:aws:logs:ap-southeast-1:XXX:log-group:production:*",
                        ]
                      + Sid      = "VisualEditor0"
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
    }

  # aws_iam_role.ec2_role_for_xxx_nodes will be created
  + resource "aws_iam_role" "ec2_role_for_xxx_nodes" {
      + arn                   = (known after apply)
      + assume_role_policy    = jsonencode(
            {
              + Statement = [
                  + {
                      + Action    = "sts:AssumeRole"
                      + Effect    = "Allow"
                      + Principal = {
                          + Service = "ec2.amazonaws.com"
                        }
                      + Sid       = ""
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + create_date           = (known after apply)
      + force_detach_policies = false
      + id                    = (known after apply)
      + max_session_duration  = 3600
      + name                  = "ec2_role_for_xxx_nodes"
      + path                  = "/"
      + unique_id             = (known after apply)
    }

  # aws_iam_role_policy_attachment.xxx_nodes_role_policy_attachment will be created
  + resource "aws_iam_role_policy_attachment" "xxx_nodes_role_policy_attachment" {
      + id         = (known after apply)
      + policy_arn = (known after apply)
      + role       = "ec2_role_for_xxx_nodes"
    }

  # aws_instance.xxxNNN will be created
  + resource "aws_instance" "xxxNNN" {
      + ami                          = "ami-063e3af9d2cc7fe94"
      + arn                          = (known after apply)
      + associate_public_ip_address  = false
      + availability_zone            = "aws-region"
      + cpu_core_count               = (known after apply)
      + cpu_threads_per_core         = (known after apply)
      + get_password_data            = false
      + host_id                      = (known after apply)
      + iam_instance_profile         = "xxx_instance_profile"
      + id                           = (known after apply)
      + instance_state               = (known after apply)
      + instance_type                = "r5.large"
      + ipv6_address_count           = (known after apply)
      + ipv6_addresses               = (known after apply)
      + key_name                     = "key.name"
      + network_interface_id         = (known after apply)
      + outpost_arn                  = (known after apply)
      + password_data                = (known after apply)
      + placement_group              = (known after apply)
      + primary_network_interface_id = (known after apply)
      + private_dns                  = (known after apply)
      + private_ip                   = (known after apply)
      + public_dns                   = (known after apply)
      + public_ip                    = (known after apply)
      + security_groups              = (known after apply)
      + source_dest_check            = true
      + subnet_id                    = "subnet-XXX"
      + tags                         = {
          + "Name" = "XXXNNN"
        }
      + tenancy                      = (known after apply)
      + user_data                    = "060b3d9c8929ff0f18bdd9fa151f5d982c256a78"
      + volume_tags                  = (known after apply)
      + vpc_security_group_ids       = [
          + "sg-XXX",
          + "sg-XXX",
        ]

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      + metadata_options {
          + http_endpoint               = (known after apply)
          + http_put_response_hop_limit = (known after apply)
          + http_tokens                 = (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)
        }

      + root_block_device {
          + delete_on_termination = true
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = 200
          + volume_type           = "gp2"
        }
    }

Plan: 5 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_policy.xxx_nodes_role_policy: Creating...
aws_iam_role.ec2_role_for_xxx_nodes: Creating...
aws_instance.xxxNNN: Creating...
aws_iam_role.ec2_role_for_xxx_nodes: Creation complete after 3s [id=ec2_role_for_xxx_nodes]
aws_iam_policy.xxx_nodes_role_policy: Creation complete after 4s [id=arn:aws:iam::XXX:policy/xxx_nodes_role_policy]
aws_iam_role_policy_attachment.xxx_nodes_role_policy_attachment: Creating...
aws_iam_role_policy_attachment.xxx_nodes_role_policy_attachment: Creation complete after 2s [id=ec2_role_for_xxx_nodes-20200717110045184300000001]
aws_instance.xxxNNN: Still creating... [10s elapsed]
aws_instance.xxxNNN: Still creating... [20s elapsed]
aws_instance.xxxNNN: Creation complete after 22s [id=i-XXX]
aws_eip.XXXNNN: Creating...
aws_eip.XXXNNN: Creation complete after 3s [id=eipalloc-XXX]

Apply complete! Resources: 5 added, 0 changed, 0 destroyed.

Upvotes: 2

Views: 7194

Answers (1)

Shammi Shailaj
Shammi Shailaj

Reputation: 475

I was able to solve this by myself with a discussion with other folks.

RCA (Root Cause Analysis)

So, the issue was that the first time terraform apply was run, an instance profile was created but not probably for reasons of inadequate IAM permissions, the IAM role could not be attached to the IAM instance profile. On all subsequent executions of terraform apply, terraform was using the already existing instance profile (probably because I had not given the terraform user destroy permissions).

How I discovered this

It was a simple thing that was being overlooked. If you count the number of resources in my terraform script in the question, you will see that the number of resources defined are 6 whereas in the output, terraform always responds with:

Apply complete! Resources: 5 added, 0 changed, 0 destroyed.

This is how I figured that probably the instance profile was never being deleted.

Removing the instance profile from IAM via AWS CLI and then re-doing a terraform apply fixed this.

For IAM permissions you may refer to: https://iam.cloudonaut.io/reference/iam.html

Do a find on page on this URL and search for InstanceProfile and add the permissions needed to your terraform user. I added all of them.

Upvotes: 2

Related Questions