Using Terraform as an Infrastructure as Code (IaC) on AWS

What is IaC?

DevOps is the combination of cultural philosophies, practices, and tools that increase an organization’s ability to deliver applications and services at high velocity; evolving and improving products at a faster pace than organizations using traditional software development and infrastructure management processes [1]. It is a software development method, which can bring software development and operational activities together.

The Infrastructure as Code (IaC) is a type of “IT infrastructure provisioning process” where systems are automatically built, managed and provisioned through code execution rather than scripting or a manual process. If your infrastructure is treated as code, you can easily manage your infrastructure just as your application code.

What is Terraform?

AWS CloudFormation and Terraform are leaders in IaC, which greatly eases the process of provisioning IT resources in the AWS cloud. Among these two, Amazon was the first cloud vendor to offer IaC through CloudFormation, which was launched in 2011. Though AWS CloudFormation is a popular hit in IaC, there was a need to have a cloud independent IaC platform. Terraform has basically fulfilled this task by allowing users to define infrastructure for a variety of cloud vendors (AWS, Azure, Google Cloud, DigitalOcean, etc). It is an open source tool with the features of a simple declarative programming language with the ability to deploy and manage cloud infrastructure using CLI commands.

Using Terraform on AWS - An Example

The below examples illustrate how we can leverage Terraform to execute the following tasks in AWS.

  • Creating an AWS IAM role using Terraform
  • Creating an IAM policy using Terraform
  • Attaching the policy to the role using Terraform
  • Creating the IAM instance profile using Terraform
  • Assigning the IAM role to an EC2 instance on the fly using Terraform

1. Creating an AWS IAM role using Terraform:

This is where the IAM role creation will be done. This assume_role_policy parameter is a must to be given within the resource block. There are other optional parameters as well, such as name, path, description, etc.

The Terraform script:

resource "aws_iam_role" "ec2_s3_access_role" {
                  name           	= "s3-role"
                  assume_role_policy = "${file("assumerolepolicy.json")}"
                }

The resource block above constructs a resource of the stated TYPE (i.e. the initial parameter “aws_iam_role”) and NAME (i.e. the second parameter “ec2_s3_access_role”). The integration of the type and name must be distinctive. Within the block (the { }) is the configuration for the resource.

A resource component in Terraform, constructs a resource, of the given TYPE (first parameter) and NAME (second parameter) when defining a resource. As an example, let’s consider the below script:

resource "aws_iam_instance_profile" "test_profile" {                             
                  name  = "test_profile"                        
                  roles = ["${aws_iam_role.ec2_s3_access_role.name}"]
                  }

In the above block, aws_iam_instance_profile is the TYPE and test_profile is the NAME. The combination of the type and name must be unique.

The assume_role_policy parameter in the above resource block allows an entity permission to assume the role.

The assume role policy:

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

2. Creating an AWS IAM policy using Terraform:

This is where we need to define the required policy (i.e. permissions) according to the necessity. For example, allowing the IAM role to access all the S3 buckets within the region. Providing the policy is a required parameter. Apart from this, there are other parameters as well, such as arn, path, id, etc.

The Terraform script:

 resource "aws_iam_policy" "policy" {
                name    	= "test-policy"
                description = "A test policy"
                policy  	= "${file("policys3bucket.json")}"
              }

The policy parameter in the above block requires an IAM policy in a JSON format. What the following policy does is that it allows the IAM role to access all the S3 buckets and perform any action (i.e. list buckets, put objects, delete objects, etc.) on those buckets.

The IAM policy:

 {
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Effect": "Allow",
                        "Action": "s3:*",
                        "Resource": "*"
                    }
                ]
              }

3. Attaching the policy to the role using Terraform:

This is where we’ll be attaching the policy, which we wrote above, to the role we created in the first step.

The Terraform script:

  resource "aws_iam_policy_attachment" "test-attach" {
                name   	= "test-attachment"
                roles  	= ["${aws_iam_role.ec2_s3_access_role.name}"]
                policy_arn = "${aws_iam_policy.policy.arn}"
              }

The aws_iam_policy_attachment in the above resource block is used to attach a Managed IAM Policy to user(s), role(s), and/or group(s). But in our case, it was a role. The value for the roles parameter has been accessed from the resource block, which we created in step 1.

Value of the role = ${aws_iam_role.ec2_s3_access_role.name}

Explanation:

  • aws_iam_role is the type of the resource block which we created in step 1.
  • ec2_s3_access_role is the name of the variable which we defined.
  • name is a property of that resource block.

The same thing applies to the value for policy_arn.

4. Creating the IAM instance profile using Terraform:

This is the resource that must be used to tag the IAM role to the EC2 instance. When we are creating the resource block for an EC2 instance, in order for us to assign the role to that instance, it expects the aws_iam_instance_profile to be given as a parameter.

The Terraform script:

 resource "aws_iam_instance_profile" "test_profile" {
                name  = "test_profile"
                roles = ["${aws_iam_role.ec2_s3_access_role.name}"]
              }

The value for the roles parameter has been accessed from the resource block, which we created in step 1.

5. Assigning the IAM role, to an EC2 instance on the fly using Terraform:

Here we will be creating a basic free tier EC2 instance and attaching the IAM instance profile, which we created above in the step 4.

The Terraform script:

 resource "aws_instance" "my-test-instance" {
                ami         	= "${lookup(var.AmiLinux, var.region)}"
                instance_type   = "t2.micro"
                iam_instance_profile = "${aws_iam_instance_profile.test_profile.name}"
              
                tags {
                Name = "test-instance"
                }
              }
              

The tags parameter is defined to identify and differentiate the EC2 instance from the others. It simply represents a mapping. The value of ami is being retrieved from the predefined variables, which are defined on a different Terraform script as shown below:

variable "region" {
                  default = "us-east-1"
                }
                variable "AmiLinux" {
                  type = "map"
                  default = {
                  us-east-1 = "ami-b73b63a0" # Virginia
                  }
                  description = "have only added one region"
                }

The following commands should be executed from the terminal, in its respective order, within the directory where the scripts are being saved.

  • Initialize a new or an existing Terraform configuration terraform init
  • Generate and show an execution plan from the resources we’re trying to provision terraform plan
  • Validate the Terraform files terraform validate
  • Build or change the infrastructure terraform apply
  • The complete list of commands are available here.

Resources

The complete source code is available here for grabs:
www.github.com/Kulasangar/terraform-demo

References

DevOps Model defined - www.aws.amazon.com/devops/what-is-devops
Terraform official website -www.terraform.io

Kulasangar Gowrisangar

Senior Software Engineer