AWS Security Group with Terraform (2024)

5 min read

·

Dec 25, 2022

--

AWS Security Group with Terraform (2)

In this article, I will cover the different ways to define security groups in AWS in Terraform. Though it sounds like a straightforward topic, many times, team will end up exposing more traffic than they should, such as using AWS default egress rules. I will cover when you should use CIDR range and when you should use Security Group ID. I will cover my interpretation of secure yet dynamic security group.

Security Group contains a collection of security group rules that defines the set of traffic that is allowed inbound or outbound, defined as ingress and egress respectively.

Each security group rules are made up of 3 parts:

  • Type of protocol (UDP/TCP)
  • Port Number
  • CIDR Range (IPV4/IPV6) or Security Group ID

Any traffic not specified will be blocked. Security group rules are stateful, hence, you do not need to handle for ephemeral port. By default, if no egress rules defined, AWS will append a All Traffic 0.0.0.0/0. Many times, people will neglect the need to also implement secure egress rules.

Typically, you will need to define 2 sets of rules, one for the consumer (egress), one for the provider (ingress). This is common when the resources are “within your control”.

For instance, EC2 -> MySQL scenario, you should have these 2 set of rules

  • EC2 Egress TCP 3306 to MySQL
  • MySQL Ingress TCP 3306 to EC2

However, there are scenarios where you only have 1 set of rules, such as accessing endpoints that is “out of your control”, such as static IP from external endpoint.

  • EC2 Egress TCP 443 to External Static Endpoint

What is the differences between these 2 scenario? In the former case, you have access to add the rules to these 2 set of resources. They can be within the same VPC, or even peered VPC. In the latter case, you do not have the access to the External endpoint, hence, you can only add 1 set of rules.

Simple enough? Lets dive even deeper than that!

In the latter scenario, the only way is to use CIDR range when adding the Egress rule. Reason being, you have no way to point to the other endpoint’s security_group_id.

However, for former scenario, you will have the 2 options, CIDR Range and Security Group ID. I will rank following approaches from least secure to the most secure method:

  1. Anywhere
  2. VPC CIDR
  3. Subnet CIDR
  4. Static IP
  5. Security Group ID

If you notice, in the same ordering, it is also the reverse order of easiest to hardest to implement.

  • Anywhere is easiest to implement as it covers all IP range by setting 0.0.0.0/0
  • VPC CIDR is a broad IP range that covers all IP within the VPC, which you can query via data_source. However, note that VPC supports multiple VPC CIDR ranges
  • Subnet CIDR becomes more slightly more complicated as AWS advocates services to be running across 3 AZs. Hence, you will need to cover all 3 IP ranges
  • Static IP like most likely to be implemented as a terraform variable. But Static IP are more as flexible as there are chances where the “static” IP change out of the blue.

Why is security_group_id is the hardest to implement? For the case of CIDR range, it does not reference the rules to a resource. However, it does for security_group_id. Then it becomes more complicated as it becomes cyclical dependency.

Using the earlier example EC2 -> RDS. EC2 will need to refer to RDS security_group_id, and RDS will need to refer to EC2 security_group_id. In most terraform modules for security group, it does not allow you to do that as there will result in a cyclical dependency.

The perks of using security_group_id is that you don’t need to be bothered by IP address at all. You have one security group specifically for EC2 for egress to RDS and you have one for for ingress to EC2. Only thing that you need to do is to add the security group to the resource and it will gain the access that it should.

One may argue, this sounds too good to be true but it may be prone to be abused. If an attacker who managed to gain access, they just need to attach the necessary security group to resources and immediately gain the access. Whereas CIDR range approach, it MAY require them to modify the security group rule to open wider range. My take is that using security_group_id is not the problem, but granting the attacker the wrong IAM policy is. Truth to the matter, any attacker with administrator permission can do anything they want, hence, it won’t defer in terms of whichever approach.

If you are enticed by the benefit of security_group_id, how can we then tackle the cyclical dependency issue?

The way to handle it is to use the native resource which is security_group and security_group_rule. While you can put all rules within security_group resource, to achieve what we want is to have 2 separately resources. Reason is that you need both security_group resources to be created before you can references each other.

resource "aws_security_group" "rds-sg" {
name = "sgrp-rds"
description = "Security group for RDS"
vpc_id = var.vpc_id
}

resource "aws_security_group_rule" "rds-sg" {
type = "ingress"
description = "Security Group for RDS"
from_port = var.port
to_port = var.port
protocol = "tcp"
source_security_group_id = aws_security_group.rds-sg-for-app.id
security_group_id = aws_security_group.rds-sg.id
}

resource "aws_security_group" "rds-sg-for-app" {
name = "sgrp-app-to-rds"
description = "Security Group for App to RDS"
vpc_id = var.vpc_id
}

resource "aws_security_group_rule" "rds-sg-for-app" {
type = "egress"
description = "Security Group for App to RDS"
from_port = var.port
to_port = var.port
protocol = "tcp"
source_security_group_id = aws_security_group.rds-sg.id
security_group_id = aws_security_group.rds-sg-for-app.id
}

So for above scenario, assuming I have a RDS Terraform module, I will have these 4 sets of resources. This will create 2 security groups that opens for each other.

Then for my EC2 instances, I will just need to bind the aws_security_group.rds-sg-for-app security group ID. That can be done via data.terraform_remote_state. As simple as that, you have a very secure and dynamic security setup.

You can also point security group id to itself, that is using the self attribute. That is more relevant when you have a setup needs to communicate within itself such as synchronising states.

You can also point your security group rules to prefix_list_id. This allows you to point to AWS regional endpoints instead of opening to Anywhere.

In my opinion, we should use security_group_id for security group rules whenever possible as it gives more secure and flexible setup without bothering about IP address. You can do the same for AWS PrivateLink setting egress to your PrivateLink security group.

CIDR range give you a quick and dirty way to implement and test connectivity, but it shouldn’t be the final setup for production environment unless you have no alternative way to do so.

AWS Security Group with Terraform (2024)
Top Articles
Latest Posts
Article information

Author: Tuan Roob DDS

Last Updated:

Views: 6315

Rating: 4.1 / 5 (62 voted)

Reviews: 93% of readers found this page helpful

Author information

Name: Tuan Roob DDS

Birthday: 1999-11-20

Address: Suite 592 642 Pfannerstill Island, South Keila, LA 74970-3076

Phone: +9617721773649

Job: Marketing Producer

Hobby: Skydiving, Flag Football, Knitting, Running, Lego building, Hunting, Juggling

Introduction: My name is Tuan Roob DDS, I am a friendly, good, energetic, faithful, fantastic, gentle, enchanting person who loves writing and wants to share my knowledge and understanding with you.