A concise way of creating AWS Security Groups (using Terraform)


Security Group Overview

A security group is a virtual firewall which can be assigned to an instance running in an AWS Virtual Private Cloud(VPC).

Inbound and Outbound Rules

For ingress (inbound) rules the source can be the same security group (self), another security group, an IPv4 or IPv6 CIDR block, a single IPv4 or IPv6 address, or a prefix list ID.

For egress(outbound rules only) the destination can be the same security group (self), another security group, an IPv4 or IPv6 CIDR block, a single IPv4 or IPv6 address, or a prefix list ID.

Defining multiple ingress and egress rules using Terraform can become quite verbose.

For one ingress rule you would have to write

ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [ "" ]
    description = "Allow HTTPS"

Having multiple security groups with many ingress and egress rules can make it difficult to analyze and update your configurations, especially when these rules are distributed across multiple files.

What if you could sum up the rule above as

"443,443,tcp,,Allow HTTPS"

Here is an example describing how self(same security group), source security group, and CIDR block rules work.

These are some of the rules created for the 3 security groups which are part of the example below - where I’ll try to demonstrate a (hopefully) more concise method of creating security groups using a module I wrote.

Create an example VPC and Security Groups

The security group module code (v1.2.6) used in this example.

git clone https://github.com/serbangilvitu/terraform-examples.git
cd terraform-examples/aws/vpc-sg/
git checkout v1.0.3
terraform init
terraform apply
Plan: 8 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

This example has created a VPC with 1 subnet and 3 security groups.

You might have noticed I have not passed any argument to terraform apply, as the values are being read from the *.auto.tfvars files

SG A - Allow traffic from self and IPv4 CIDR block

The values used for the rules of this security group are

sg_a_ingress_from_self_list = [ "80,80,tcp,Allow HTTP" ]

sg_a_ingress_cidr_list = [ 
    "443,443,tcp,,Allow HTTPS", 
    "22,22,tcp,,Allow SSH"

sg_a_egress_cidr_list = [ 
    "0,0,-1,,Allow any" 

One ingress or egress rule can be defined in one line, and all the rules of the security group can be swiftly analyzed.

The code for this security group contains arguments which could have been skipped, as they are not being used, and they will default to an empty list [].

The only argument which doesn’t default to an empty list is egress_cidr_list which defaults to [ "0,0,-1," ].

The reason I’ve added so many arguments in this security groups was to present all scenarios supported by the module.

module "sg_a" {
  source = "git::https://github.com/serbangilvitu/terraform-modules.git//aws/security-group?ref=v1.2.6"
  name_prefix = "${var.stack_name}-a"
  description = "Example a"
  vpc_id = module.vpc.id

  ingress_cidr_list = var.sg_a_ingress_cidr_list
  ingress_from_self_list = var.sg_a_ingress_from_self_list
  ingress_ipv6_cidr_list = var.sg_a_ingress_ipv6_cidr_list
  ingress_prefix_list_ids = var.sg_a_ingress_prefix_list_ids
  ingress_source_sg_list = var.sg_a_ingress_source_sg_list

  egress_cidr_list = var.sg_a_egress_cidr_list
  egress_from_self_list = var.sg_a_egress_from_self_list
  egress_ipv6_cidr_list = var.sg_a_egress_ipv6_cidr_list
  egress_prefix_list_ids = var.sg_a_egress_prefix_list_ids
  egress_source_sg_list = var.sg_a_egress_source_sg_list

  stack_name = var.stack_name
  additional_tags = var.additional_tags

And this is the result

SG B - An even more concise version of SG A

SG B was created by copying SG A’s code, then removing the arguments which are not being used (and will use the module’s default values as mentioned above).

Here’s the code

module "sg_b" {
  source = "git::https://github.com/serbangilvitu/terraform-modules.git//aws/security-group?ref=v1.2.6"
  name_prefix = "${var.stack_name}-b"
  description = "Example b"
  vpc_id = module.vpc.id

  ingress_from_self_list = var.sg_b_ingress_from_self_list
  ingress_cidr_list = var.sg_b_ingress_cidr_list

  stack_name = var.stack_name
  additional_tags = var.additional_tags

The values can be found here.

The following security group was created:

SG C - Allow traffic from SG A and SG B

For SG C we’re allowing traffic to and from SG A and SG B

module "sg_c" {
  source = "git::https://github.com/serbangilvitu/terraform-modules.git//aws/security-group?ref=v1.2.6"
  name_prefix = "${var.stack_name}-c"
  description = "Example c"
  vpc_id = module.vpc.id

  ingress_source_sg_list = [ 
    join(",", ["0", "0", "-1", module.sg_a.id, "Allow all traffic from a"]),
    join(",", ["443", "443", "tcp", module.sg_b.id, "Allow HTTPS traffic from b"])
  egress_source_sg_list = [ 
    join(",", ["0", "0", "-1", module.sg_a.id, "Allow all traffic to a"]),
    join(",", ["0", "0", "-1", module.sg_b.id, "Allow all traffic to b"])
  egress_cidr_list = []
  stack_name = var.stack_name
  additional_tags = var.additional_tags

More arguments

There are some arguments I did not include like ingress_ipv6_cidr_list, ingress_prefix_list_ids - those should will work in the same way.