Security in Node.js Applications: Hardening the AWS EC2 deployment

There is an issue left over from Chapter 12, Deploying a Docker Swarm to AWS EC2 with Terraform, which is the security group configuration for the EC2 instances. We configured the EC2 instances with permissive security groups, and it is better for them to be strictly defined. We rightly described that, at the time, as not the best practice, and promised to fix the issue later. This is where we do so.

In AWS, remember that a security group describes a firewall that allows or disallows traffic based on the IP port and IP address. This tool exists so we can decrease the potential attack surface miscreants have to gain illicit access to our systems.

For the ec2-public-sg security group, edit ec2-public.tf and change it to this:

resource “aws_security_group” “ec2-public-sg” {

name = “${var.project_name}-public-sg”

description = “allow inbound access to the EC2 instance”

vpc_id = aws_vpc.notes.id

ingress {

description = “SSH”

protocol = “TCP”

from_port = 22

to_port = 22

cidr_blocks = [ “0.0.0.0/0” ]

}

ingress {

description = “HTTP”

protocol = “TCP”

from_port = 80

to_port = 80

cidr_blocks = [ “0.0.0.0/0” ]

}

ingress {

description = “HTTPS”

protocol = “TCP”

from_port = 443

to_port = 443

cidr_blocks = [ “0.0.0.0/0” ]

}

ingress {

description = “Redis”

protocol = “TCP”

from_port = 6379

to_port = 6379

cidr_blocks = [ aws_vpc.notes.cidr_block ]

}

ingress {

description = “Docker swarm management”

from_port = 2377

to_port = 2377

protocol = “tcp”

cidr_blocks = [ aws_vpc.notes.cidr_block ]

}

ingress {

description = “Docker container network discovery”

from_port = 7946

to_port = 7946

protocol = “tcp”

cidr_blocks = [ aws_vpc.notes.cidr_block ]

}

ingress {

description = “Docker container network discovery”

from_port = 7946

to_port = 7946 protocol = “udp”

cidr_blocks = [ aws_vpc.notes.cidr_block ]

}

ingress {

description = “Docker overlay network”

from_port = 4789

to_port = 4789

protocol = “udp”

cidr_blocks = [ aws_vpc.notes.cidr_block ]

}

egress {

description = “Docker swarm (udp)”

from_port = 0

to_port = 0

protocol = “udp”

cidr_blocks = [ aws_vpc.notes.cidr_block ]

}

egress {

protocol = “-1”

from_port = 0

to_port = 0

cidr_blocks = [ “0.0.0.0/0” ]

}

}

This declares many specific network ports used for specific protocols. Each rule names the protocol in the description attribute. The protocol attribute says whether it is a UDP or TCP protocol. Remember that TCP is a stream-oriented protocol that ensures packets are delivered, and UDP, by contrast, is a packet- oriented protocol that does not ensure delivery. Each has characteristics making them suitable for different purposes.

Something missing is an ingress rule for port 3306, the MySQL port. That’s because the notes-public server will not host a MySQL server based on the placement constraints.

Another thing to note is which rules allow traffic from public IP addresses, and which limit traffic to IP addresses inside the VPC. Many of these ports are used in support of the Docker swarm, and therefore do not need to communicate anywhere but other hosts on the VPC.

An issue to ponder is whether the SSH port should be left open to the entire internet. If you, or your team, only SSH into the VPC from a specific network, such as an office network, then this setting could list that network. And because the cidr_blocks attribute takes an array, it’s possible to configure a list of networks, such as a company with several offices each with their own office network.

In ec2-private.tf, we must make a similar change to ec2-private-sg:

resource “aws_security_group” “ec2-private-sg” {

name = “${var.project_name}-private-sg”

description = “allow inbound access to the EC2 instance”

vpc_id = aws_vpc.notes.id

ingress {

description = “SSH”

protocol = “TCP”

from_port = 22

to_port = 22

cidr_blocks = [ aws_vpc.notes.cidr_block ]

}

ingress {

description = “HTTP”

protocol = “TCP”

from_port = 80

to_port = 80

cidr_blocks = [ aws_vpc.notes.cidr_block ]

}

ingress {

description = “MySQL”

protocol = “TCP”

from_port = 3306

to_port = 3306

cidr_blocks = [ aws_vpc.notes.cidr_block ]

}

ingress {

description = “Redis”

protocol = “TCP”

from_port = 6379

to_port = 6379

cidr_blocks = [ aws_vpc.notes.cidr_block ]

}

ingress {

description = “Docker swarm management”

from_port = 2377

to_port = 2377

protocol = “tcp”

cidr_blocks = [ aws_vpc.notes.cidr_block ]

}

ingress {

description = “Docker container network discovery”

from_port = 7946

to_port = 7946

protocol = “tcp”

cidr_blocks = [ aws_vpc.notes.cidr_block ]

}

ingress {

description = “Docker container network discovery”

from_port = 7946

to_port = 7946

protocol = “udp”

cidr_blocks = [ aws_vpc.notes.cidr_block ]

}

ingress {

description = “Docker overlay network”

from_port = 4789

to_port = 4789

protocol = “udp”

cidr_blocks = [ aws_vpc.notes.cidr_block ]

}

egress {

description = “Docker swarm (udp)”

from_port = 0

to_port = 0

protocol = “udp”

cidr_blocks = [ aws_vpc.notes.cidr_block ]

}

egress {

protocol = “-1”

from_port = 0

to_port = 0

cidr_blocks = [ “0.0.0.0/0” ]

}

}

This is largely the same but for some specific differences. First, because the private EC2 instances can have MySQL databases, we have declared a rule for port 3306. Second, all but one of the rules restrict traffic to IP addresses inside the VPC.

Between these two security group definitions, we have strictly limited the attack surface of the EC2 instances. This will throw certain barriers in the path of any miscreants attempting to intrude on the Notes service.

While we’ve implemented several security best practices for the Notes service, there is always more that can be done. In the next section, we’ll discuss where to learn more.

Source: Herron David (2020), Node.js Web Development: Server-side web development made easy with Node 14 using practical examples, Packt Publishing.

Leave a Reply

Your email address will not be published. Required fields are marked *