Creating reusable infrastructure with Terraform modules

2022-12-16

🎯 Objectives

  • Create reusable infrastructure with Terraform modules
  • Use Terraform dynamic block to create repeatable nested blocks.
  • Use Terraform for_each to iterate over a data structure to configure modules with each item in turn.
  • Use Terraform Maps/Objects data type, a collection of string keys and string values.

Prerequisite

  • An AWS account
  • Terraform installation.

To illustrate, we will create a WAF Regional Rule Resource for use with Application Load Balancer.

Export AWS credentials and other environment variables

1
2
3
4

export AWS_ACCESS_KEY_ID="ASIAV....."
export AWS_SECRET_ACCESS_KEY="XtRzQJ20fGFAil....."
export REGION=<preferred_aws_region>

Create the following file and add the following code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat > providers.tf <<EOF

terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "4.22.0"
}
}
}

provider "aws" {
region = $REGION
}
EOF

Create the module

  • A module is a mini Terraform project that can contain all of the same constructs as Terraform resources, data blocks,locals, etc.
  • Modules allow you to define a reusable block of Terraform code and have many instances of it.

Create a new folder waf_modules, create 2 files main.tf and variable.tf
In the code below, the iterator argument is a temporary variable that stores the value per iteration while looping a list or a map object in Terraform.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# main.tf
resource "aws_wafregional_ipset" "ipset" {
name = var.ipset_name

dynamic "ip_set_descriptor" {
for_each = var.cidrs_blocks
iterator = port
content {
type = "IPV4"
value = port.value

}
}
}


resource "aws_wafregional_rule" "wafrule" {
name = var.regional_rule_name
metric_name = var.regional_rule_metric_name


dynamic "predicate" {
for_each = var.regional_rules
iterator = rules
content {
data_id = aws_wafregional_ipset.ipset.id
negated = rules.value.negated
type = rules.value.type

}
}
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# variable.tf
variable "ipset_name" {
type = string
description = "name of ipset rule"
}

variable "regional_rule_name" {
type = string
description = "name of regional rule"
}
variable "regional_rule_metric_name" {
type = string
description = "name of regional rule metric "
}

variable "cidrs_blocks" {

type = list(string)
description = "list of cidr ports for ipset rule"

}
variable "regional_rules" {
type = map(object({
negated = bool
type = string
}))

}


Modules in action

Create a main.tf and env.tfvars in the root directory

  • We will create 2 individual resources for the ipset and regional rules
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# main.tf
variable "waf_vars" {
type = map(any)
}

module "waf_resources" {
source = "./waf_modules"
for_each = var.waf_vars
ipset_name = each.value.ipset_name
cidrs_blocks = each.value.cidrs_blocks
regional_rule_metric_name = each.value.regional_rule_metric_name
regional_rule_name = each.value.regional_rule_name
regional_rules = each.value.regional_rules

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# env.tfvars

waf_vars = {
first_resource = {
regional_rule_metric_name = "first_rule_name"
regional_rule_name = "first_rule_metric_name"
ipset_name = "first_ipset_name"
cidrs_blocks = ["10.0.0.0/8", "10.0.0.0/3"]
regional_rules = {
"rule_one" = {
negated = false
type = "ByteMatch"
},
"rule_two" = {
negated = false
type = "GeoMatch"
}

}

},
second_resource = {
regional_rule_metric_name = "second_rule_name"
regional_rule_name = "second_metric_name"
ipset_name = "second_ipset_name"
cidrs_blocks = ["10.0.2.0/8", "10.0.3.0/3"]
regional_rules = {
"rule_one" = {
negated = false
type = "RegexMatch"
},
"rule_two" = {
negated = false
type = "IPMatch"
}

}

}


}


Apply Changes

1
2
terraform init
terraform plan -var-file=env.tfvars

Hello 👋, If you enjoyed this article, please consider subscribing to my email newsletter. Subscribe 📭