Skip to content

Terraform Guide AWS

Vitalii Kanivets edited this page Aug 10, 2023 · 3 revisions

Terraform is an IaC tool used to create and change your virtual infrastructure. Custodian team uses Terraform for testing policies. Creation of a Terraform code will require you to follow the procedure and general rules introduced below.


Table of Contents

  1. Pre-requisites
    1.1. Create folders
    1.2 Green/Red folder structure
  2. Rules for Terraform code creation
    2.1. Split terraform resources by service into separate files
    2.2. Content of "provider.tf" file
    2.3. Content of "variables.tf" file
    2.4. Content of "terraform.tfvars" file
    2.5. A "resource" block name
    2.6. Value of the argument "Name"
    2.7. Tags
    2.8. Instances AMI
    2.9. Random password generation
  3. Terraform language style conventions
  4. AWS provider versioning
  5. Terraform basic tutorials

Pre-requisites

Create folders

Before you start writing a code, you need to create the necessary folders.

Perform the following steps:

1. In the local branch, in the folder "terraform", create a general folder with the name of the policy, e.g., "ecc-aws-000-example_policy".

2. In this folder, create three other folders:

  • Folder with "green" infrastructure – name: "green"
  • Folder with a JSON file with permissions for a user to run Custodian policy – name: "iam"
  • Folder with "red" infrastructure – name: “red”

This should look something like this:

image

ℹ️ Note: In some cases, red or green infrastructure building is not possible.

 Green/Red folder structure

Green and red infrastructure folders have the same structure:

  • Resources that will be created;
  • Default variables;
  • Terraform vars (terraform.tfvars);
  • Provider configuration for connecting with AWS account.

For example:

image

Rules for Terraform code creation

Split terraform resources by service into separate files

If you have a script with multiple resources, it is recommended to create separate files for the main of them.
For example, it will be easier for understanding to have separate files containing information about the network (vpc.tf), IAM configuration (iam.tf), etc.

image

Content of "provider.tf" file

(More information about provider versioning in AWS provider versioning)

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4"
    }
  }
}

provider "aws"{
  profile = var.profile
  region = var.default-region

  default_tags {
    tags = {
      CustodiaRule     = "ecc-aws-103-instance_without_termination_protection"
      ComplianceStatus = "Green"
    }
  }
}

Content of "variables.tf" file

variable "default-region" {
  type        = string
  description = "Default region for resources will be created" 
}

variable "profile" {
  type        = string
  description = "Profile name configured before running apply"  
}

Content of "terraform.tfvars" file

profile        = "c7n"
default-region = "us-east-1"

ℹ️ Note: All regions and locations are used as an example and can be changed depending on your case.

A "resource" block name

The "resource" block name should be assigned according to the recommendations below:

  • If the resource module creates a single resource of a specific service, the resource name should be this :
resource "aws_acm_certificate" "this" {
  # ... remaining arguments omitted
}
  • If you have multiple resources of a specific service, then more descriptive names should be assigned: private, public, or main :
resource "aws_subnet" "public" {
  # ... remaining arguments omitted
}
  • If you have multiple resources but with the same or similar configurations, their functionality should be described in a comment before the resource name. You can also name them bucket1, bucket2:
# resource description
resource "aws_iam_role" "role1" {
  # ... remaining arguments omitted
}

Value of the argument "Name"

Value of the argument "Name" must include:

  • Rule ID;
  • Resource description;
  • Infrastructure identification: "green" or "red".

For example:

resource "aws_iam_user" "this" {
  name          = "002_user_green"
  path          = "/"
  force_destroy = true
}

In some cases, a "name" cannot include a symbol "_" or be started with numbers and should look as follows:

resource "resource" "this" {
  bucket = "resource-000-green"
}

resource "resource" "this" {
  name = "000resourcegreen"
   # ... remaining arguments omitted
}

Tags

Tags should contain the rule name and compliance status - "green" or "red":

resource "resource" "this" {
  name = "000_resource_green"

  # ... remaining arguments omitted

  tags = {
   CustodiaRule     = "ecc-aws-000-example_policy"
   Description      = "..."  
   ComplianceStatus = "Green"
  }
}

ℹ️ Note: The line containing description is optional 

Tags, as well as meta-arguments "depends_on" and "lifecycle", should be separated by a single empty line:

resource "resource" "this" {
  name = "000_resource_green"

  # ... remaining arguments omitted

  tags = {
   CustodianRule    = "ecc-aws-000-example_policy"
   Description      = "..."
   ComplianceStatus = "Green"
  }

 depends_on = {
      ...
  }

  lifecycle = {
      ...
  }
}

Instances AMI

Instances AMI should be specified as follows:

resource "aws_instance" "this" {
  ami = data.aws_ami.this.id

# ... remaining arguments omitted

}  
data "aws_ami" "this" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm\*"]
  }
}

Random password generation

All passwords (IDs where possible) should be randomly generated:

resource "random_password" "this" {
  length           = 12
  special          = true
  number           = true
  override_special = "!#$%\*()-_=+[]{}:?"
}

resource "aws_db_instance" "this" {

  # ... remaining arguments omitted

  db_name              = "rds105green"
  username             = "root"
  password             = random_password.this.result
}

Terraform language style conventions

The terraform fmt command is used to rewrite Terraform configuration files to a canonical format and style. This command applies a subset of Terraform language style conventions, along with other minor adjustments for readability.

The canonical format may change in minor ways between Terraform versions, so after upgrading Terraform, it is recommended to proactively run terraform fmt on modules.

AWS provider versioning

AWS Provider has MAJOR versions (1, 2, 3, 4) and MINOR (4.1, 4.2.3, etc.). The latest major version of AWS provider should be used in "provider.tf".

If terraform stopped working with the update of the MINOR version for example: MAJOR version - 4 is used at this moment and terraform stopped working after the release of the new minor version, for example, 4.5. In this case, provider version should be changed to the old minor version on which it still works, for example (4.1.0).

In case of a new MAJOR version release, all terraform files must be checked and updated to the new MAJOR version of the provider.

⚠️ In order to keep track of the latest versions of AWS Provider, please use the following link CHANGELOG, pay attention to the BREAKING CHANGES section.

Terraform basic tutorials