AWS with Terraform Tutorial: AWS Security Groups (9)
How to Create, and Manage AWS Security Groups with Terraform #
How to configure and use the Terraform aws_security_group and aws_security_group_rule resource blocks to create and manage AWS Security Groups and secure the infrastructure.
Welcome to our tutorial series where we dive into cloud infrastructure deployment using Terraform or OpenTofu on AWS. In this series, the fundamentals are shown, guiding you through the process of minimizing resource usage and simplifying the deployment complexities associated with cloud infrastructure.
This comprehensive OpenTofu and Terraform tutorial guides you step-by-step through creating infrastructure in AWS using Terraform.
Infrastructure as Code (IaC) helps maintain consistency, enables version control, enhances collaboration among teams, allows for easier replication of environments, streamlines the deployment and management of infrastructure boosting efficiency, and reducing errors in managing complex systems.
Prerequisites #
Read the previous sections of the tutorial, listed in the series index at the end of this page.
AWS Security Groups #
An AWS Security Group acts as a virtual firewall for AWS instances and services controlling inbound and outbound traffic. It is a set of rules that filter traffic based on protocols, ports, and origins or destinations.
Security Groups are composed of Security Rules that define the inbound or outbound flows. In AWS, Security Groups define what is authorized, by default everything else is denied.
The diagram shows the elements of a Security Group:
Ingress or Inbound Rules #
Ingress Rules, also called Inbound Rules apply to traffic that enters the instance, e.g. traffic generated on the Internet that reaches an instance (instance being the destination).
Components of an Inbound or Ingress Security Rule are:
Type
All types of traffic, TCP, UDP, or ICMP (e.g. ping)
Port or Port Range
The destination port can be a single port or a range. A single port is specified with its number (e.g. 80) and a range with a start port number, a dash, and the end port number (e.g. 1024-1040). The port number is only required for TCP and UDP protocols.
Source
The origin is the instance or network generating the traffic, it can be specified in many different ways:
- IPv4 CIDR notation (e.g. 75.2.98.97/32)
- IPv6 CIDR notation (e.g. 2001:db6::/32)
- IPv4 Anywhere (0.0.0.0/0)
- IPv6 Anywhere (::/0)
- An AWS Prefix (a set of one or more CIDR blocks). See Group CIDR blocks using managed prefix lists.
- A Security Group: This is the recommended way to specify sources inside the VPC. The source is any instance that has a group with this name attached.
Destination
Specifies the resource the incoming network traffic is trying to reach. The destination in an inbound rule is not specified as it is derived from the instances that have the Security Group attached. E.g. If the security group is attached to instance-web-server-01 and instance-web-server-02, then the destination becomes any of those instances.
Description
A text explaining intended use that acts as documentation
Tags
A list of tags and values classifying the rule.
Egress or Outbound Rules #
Egress Rules, also called Outbound Rules apply to traffic that leaves the instance, e.g. traffic generated in the instance that is sent to another instance or destination (e.g. from an Instance to the Internet).
When you create a new Security Group in a VPC, AWS sets up an allow all egress rule by default. However, when using Terraform, this default rule is removed.
Components of an Egress or Outbound Security Rule are:
Type
All types of traffic, TCP, UDP, or ICMP (e.g. ping)
Port or Port Range
The destination port can be a single port or a range. A single port is specified with its number (e.g. 80) and a range with a start port number, a dash, and the end port number (e.g. 1024-1040). The port number is only required for TCP and UDP protocols.
Source
Specifies the resource that is generating the outgoing network traffic. The source in an outbound rule is not specified as it is derived from the instances that have the Security Group attached. E.g. If the security group is attached to instance-web-server-01, then the source becomes this instance (eg. this instance is the source for traffic going to a database instance in a private subnet).
Destination
The destination is the instance or network where the traffic is going, it can be specified in many different ways:
- IPv4 CIDR notation (e.g. 75.2.98.97/32)
- IPv6 CIDR notation (e.g. 2001:db6::/32)
- IPv4 Anywhere (0.0.0.0/0)
- IPv6 Anywhere (::/0)
- An AWS Prefix (a set of one or more CIDR blocks). See Group CIDR blocks using managed prefix lists.
- A Security Group: This is the recommended way to specify sources inside the VPC. The source is any instance that has a group with this name attached.
Description
A text explaining intended use that acts as documentation
Tags
A list of tags and values classifying the rule.
Connectivity Diagrams #
Security should be taken as a priority when designing any Infrastructure. A secure instance will have by default all the incoming traffic denied and only the needed ports open and if possible only to traffic originating from known sources. E.g. A database server that stores data for a web application should have all inbound traffic denied except the one originating from the application that uses the data (a web server probably).
A diagram with the services, listening ports, and intended connections helps design a secure infrastructure.
The diagram shows two instances that will be added in the next sections and their connectivity requirements:
EC2 Instance: ditwl-ec-front-end-001 #
A front-end instance, located in the ditwl-sn-za-pro-pub-00 Public Subnet. Instances in public subnets are usually exposed to the Internet, this one will expose port 80 and run an HTTP server.
Connectivity requirements:
- Outbound access (from the instance) to the Internet: the instance should be able to access the Internet to download updates.
- Inbound access (to the instance) from the Internet to the Instance TCP port 80.
- Outbound access (from the instance) to the TCP port 8080 on the back-end server
ditwl-ec-back-end-123instance.
EC2 Instance: ditwl-ec-back-end-123 #
A back-end instance, located in the ditwl-sn-za-pro-pri-02 Private Subnet. Instances in private subnets are not exposed to the Internet, this one will expose port 8080 and run an HTTP server (e.g. an API).
Connectivity requirements:
- Outbound access (from the instance) to the Internet: the instance should be able to access the Internet to download updates. It will use the NAT Gateway, although it doesn’t have an impact on the Security Rules.
- Inbound access from the instance
ditwl-ec-front-end-001to the TCP port 8080.
Security Groups Needed #
To have a secure infrastructure, each group of common services (eg. cluster of web servers running the same application) requires a specific security group, additionally a security group with common rules for all the instances can be added to open ssh and observability access from selected sources.
0.0.0.0 -> Assigned / tcp 22
0.0.0.0 -> Assigned / icmp -1 (all)
Assigned -> 0.0.0.0 / all
0.0.0.0 -> Assigned / tcp 80
ditwl-sg-front-end -> Assigned / tcp 8080
Security Group ditwl-sg-base-ec2
This is a security group with common rules that should be applied to all instances. It allows inbound connectivity to port TCP 22 from the Internet and also ICMP.
The recommended security practice is to limit SSH and ICMP access to a set of known IP addresses corresponding to the management network.
See How to programmatically use your public Internet IP address in Terraform? for a better approach.
Ingress Security Rule ditwl-sr-internet-to-ec2-ssh
Allows inbound connectivity to port TCP 22 from the Internet. Allowing SSH from any address is a security risk as the instances could be compromised. The recommended practice is to limit SSH access to a set of known IP addresses corresponding to the management network or use EC2 Instance Connect or AWS Systems Manager Session Manager, which need no inbound rules.
Ingress ditwl-sr-internet-to-ec2-icmp
Allows inbound ICMP from the Internet, that again is not a recommended practice and should be limited to a set of known IP addresses corresponding to the management network.
ditwl-sr-all-outbund
Allows all outbound connectivity from the instance, including access to the Internet.
Security Group ditwl-sg-front-end
This security group defines the access rules for the Front End Instance.
Ingress ditwl-sr-internet-to-front-end-http
Allow access from the Internet to TCP port 80 (for a web server).
Security Group ditwl-sg-back-end
This security group defines the access rules for the Back-End Instance, allowing access only from the Front-End instance.
Ingress ditwl-sr-front-end-to-api
Allow access from instances having the ditwl-sg-front-end security group assigned to TCP port 8080 (for a web server offering an API).
How to create an AWS Security Group with Terraform #
Previous sections of this AWS with Terraform guide have covered basic information about Terraform and AWS and have configured and used the AWS Terraform provider to create a VPC, four subnets, one Internet Gateway, two NAT Gateways, and three Routing Tables.
It is time to create Security Groups that will allow traffic between different resources and to the Internet.
Keep adding Terraform blocks to the terraform-aws-tutorial.tf created in previous sections.
Terraform Code for Security Groups #
Security Group creation is done with aws_security_group and aws_security_group_rule Terraform Blocks.
Security Groups need to be assigned to Instances using the Instance creation block aws_instance property vpc_security_group_ids. See the EC2 Instance creation section for the assignment.
Add the following block to the file terraform-aws-tutorial.tf.
For Security Group ditwl-sg-base-ec2:
# Create a "base" Security Group for EC2 instances
resource "aws_security_group" "ditwl-sg-base-ec2" {
name = "ditwl-sg-base-ec2"
vpc_id = aws_vpc.ditlw-vpc.id
description = "Base security Group for EC2 instances"
}
# DANGEROUS!!
# Allow access from the Internet to port 22 (SSH) in the Public EC2 instances
resource "aws_security_group_rule" "ditwl-sr-internet-to-ec2-ssh" {
security_group_id = aws_security_group.ditwl-sg-base-ec2.id
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # Internet
description = "Allow access from the Internet to port 22 (SSH)"
}
# Allow access from the Internet for ICMP protocol (e.g. ping) to the EC2 instances
resource "aws_security_group_rule" "ditwl-sr-internet-to-ec2-icmp" {
security_group_id = aws_security_group.ditwl-sg-base-ec2.id
type = "ingress"
from_port = -1
to_port = -1
protocol = "icmp"
cidr_blocks = ["0.0.0.0/0"] # Internet
description = "Allow access from the Internet for ICMP protocol"
}
# Allow all outbound traffic to the Internet
resource "aws_security_group_rule" "ditwl-sr-all-outbund" {
security_group_id = aws_security_group.ditwl-sg-base-ec2.id
type = "egress"
from_port = "0"
to_port = "0"
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
description = "Allow all outbound traffic to Internet"
}
For Security Group ditwl-sg-front-end:
# Create a Security Group for the Front end Server
resource "aws_security_group" "ditwl-sg-front-end" {
name = "ditwl-sg-front-end"
vpc_id = aws_vpc.ditlw-vpc.id
description = "Front end Server Security"
}
# Allow access from the Internet to port 80 HTTP in the EC2 instances
resource "aws_security_group_rule" "ditwl-sr-internet-to-front-end-http" {
security_group_id = aws_security_group.ditwl-sg-front-end.id
type = "ingress"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # Internet
description = "Access from the Internet to port 80 in the EC2 instances"
}
For Security Group ditwl-sg-back-end:
# Create a Security Group for the Back-end Server
resource "aws_security_group" "ditwl-sg-back-end" {
name = "ditwl-sg-back-end"
vpc_id = aws_vpc.ditlw-vpc.id
description = "Back-end Server Security"
}
# Allow access from the front-end to port 8080 in the back-end API
resource "aws_security_group_rule" "ditwl-sr-front-end-to-api" {
security_group_id = aws_security_group.ditwl-sg-back-end.id
type = "ingress"
from_port = 8080
to_port = 8080
protocol = "tcp"
source_security_group_id = aws_security_group.ditwl-sg-front-end.id
description = "Allow access from the front-end to port 8080 in the back-end API"
}
Run the Terraform Plan #
Open a command line shell in the directory where the file terraform-aws-tutorial.tf is located, and run the Terraform or OpenTofu plan and apply commands.
Plan using OpenTofu #
Run tofu plan to generate and review the execution plan. Check each line and value to make sure that it corresponds to the desired change.
Terraform will refresh the state comparing it with the Cloud resources and produce a plan for the resources that need to be created, updated, or destroyed.
In the past section of the tutorial, some resources were commented out to reduce the infrastructure cost during development, as the security Groups don’t have any dependency yet with the commented code, there is no need to uncomment.
Run the Terraform plan:
Apply the changes using OpenTofu #
Run tofu apply to generate and apply the execution plan. OpenTofu will generate a new plan and ask for confirmation before applying the changes. Review again the changes and answer yes or no to apply the changes in AWS.
Review the result, it should be 12 (resource) added, 0 changed, 0 destroyed.
Security Groups Cost #
There is no cost for creating or having Security Groups in AWS. See also AWS Security Groups’ Best Practices.
Common Questions About AWS Security Groups #
Can the source be an IP address for instance inbound? #
It can be, although it is not recommended as the common practice is to use DHCP for private IP address assignment. If the instance IP address changes after a reboot then all security rules will need to be updated and if the old IP address gets assigned to another instance it can create a security risk.
How does Security Group differ from Network ACL? #
A security group is stateful and operates at the instance (network interface) level; a network ACL is stateless and works at the subnet level.
What’s the default rule for a new Security Group? #
All inbound traffic is denied and, in the console, all outbound traffic is allowed (a security group created with Terraform has no outbound rules until they are defined).
Can I assign multiple Security Groups to an instance? #
Yes. By default you can associate up to five security groups with a network interface (the quota can be increased), and the rules of all of them are combined.
Do Security Groups support deny rules? #
No, security groups only have allow rules: whatever is not allowed is denied. To deny a specific source explicitly, use a network ACL.
Next Steps #
Continue with AWS Key Pairs, or see the full series index.