Borislav Hadzhiev
Fri Apr 15 2022·6 min read
Photo by Lakshay Bhardwaj
Updated - Fri Apr 15 2022
Security groups are virtual firewalls - they control the traffic that goes in and out of our EC2 instances.
They allow us to define inbound and outbound rules. Inbound traffic is traffic that comes into the EC2 instance, whereas Outbound traffic is traffic that goes out of the EC2 instance.
In this article we are going to look at multiple examples of creating security groups and editing their inbound and outbound rules.
Let's start by creating a VPC and a security group for a web server:
import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as cdk from 'aws-cdk-lib'; export class CdkStarterStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); const vpc = new ec2.Vpc(this, 'my-cdk-vpc', { cidr: '10.0.0.0/16', natGateways: 0, maxAzs: 3, subnetConfiguration: [ { name: 'public-subnet-1', subnetType: ec2.SubnetType.PUBLIC, cidrMask: 24, }, ], }); // 👇 Create a SG for a web server const webserverSG = new ec2.SecurityGroup(this, 'web-server-sg', { vpc, allowAllOutbound: true, description: 'security group for a web server', }); webserverSG.addIngressRule( ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'allow SSH access from anywhere', ); webserverSG.addIngressRule( ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'allow HTTP traffic from anywhere', ); webserverSG.addIngressRule( ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'allow HTTPS traffic from anywhere', ); webserverSG.addIngressRule( ec2.Peer.ipv4('123.123.123.123/16'), ec2.Port.allIcmp(), 'allow ICMP traffic from a specific IP range', ); } }
Let's go over what we did in the code snippet.
We created a VPC, by instantiating and configuring the Vpc
class. We
set the natGateways
prop to 0
to avoid getting charged unnecessarily.
If you want to read more about creating VPCs, I've written another article on creating VPCs in CDK.
We created a security group for a web server. The props we passed when instantiating the SecurityGroup class are:
vpc
- the VPC, the security group will be created inallowAllOutbound
- whether the security group should allow all outbound
traffic. By default allowAllOutbound
is set to true
description
- a short description of the security groupSecurityGroup
class. The parameters we passed
to the method are:peer
- the Source
in a security group inbound ruleconnection
- the Port
, Protocol
and Type
in a security group inbound
ruledescription
- a short description of the security group ruleType | Protocol | Port | Source |
---|---|---|---|
SSH | TCP | 22 | 0.0.0.0/0 |
HTTP | TCP | 80 | 0.0.0.0/0 |
HTTPS | TCP | 443 | 0.0.0.0/0 |
All ICMP | ICMP | ALL | 123.123.0.0/16 |
Let's provision the VPC and the security group:
npx aws-cdk deploy
After the resources have been deployed, we can see that the inbound security group rules have been applied:
The outbound rules allow all traffic because we've set the allowAllOutbound
to true
, which is also the default value:
Let's add 2 more security groups - 1 for a backend server, and 1 for a database server.
The security group of the backend server will only allow requests on port
8000
, made from instances in the webserverSG
security group.
Whereas the security group for the database server will only allow requests on
port 3306
, made from instances in the backendServerSG
security group.
import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as cdk from 'aws-cdk-lib'; export class CdkStarterStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // ... rest // 👇 Create a SG for a backend server const backendServerSG = new ec2.SecurityGroup(this, 'backend-server-sg', { vpc, allowAllOutbound: true, description: 'security group for a backend server', }); backendServerSG.connections.allowFrom( new ec2.Connections({ securityGroups: [webserverSG], }), ec2.Port.tcp(8000), 'allow traffic on port 8000 from the webserver security group', ); // 👇 Create a SG for a database server const dbserverSG = new ec2.SecurityGroup(this, 'database-server-sg', { vpc, allowAllOutbound: true, description: 'security group for a database server', }); dbserverSG.connections.allowFrom( new ec2.Connections({ securityGroups: [backendServerSG], }), ec2.Port.tcp(3306), 'allow traffic on port 3306 from the backend server security group', ); } }
Let's go over what we did in the code snippet.
8000
from instances in the
webserverSG
security group. The allowFrom
method takes the following 3
props:connectable
- an object that has connection options, in our case a security
group
connection
- the Port
, Protocol
and Type
in a security group
rule
description
- a short description of the security group rule
allowFrom
method
to allow traffic on port 3306
from the security group of the backend serverThe inbound rules for the backendServerSG
look like:
Type | Protocol | Port | Source |
---|---|---|---|
Custom TCP | TCP | 8000 | webserverSG-id |
The inbound rules for the dbserverSG
look like:
Type | Protocol | Port | Source |
---|---|---|---|
MYSQL | TCP | 3306 | backendServerSG-id |
The outbound rules are still the default of all traffic is allowed. We will edit the outbound rules of a security group later in the article.
Let's run the deploy
command:
npx aws-cdk deploy
After a deployment, the inbound rules of the backend server security group show
that it only allows traffic on port 8000
from requests made from instances
within the webserver security group:
The security group of the database server shows that it only allows traffic on
port 3306
from requests made from instances within the backend server security
group:
Next, we are going to take a look at how we can edit the default outbound rules of a security group.
In order to edit the outbound rules of a security group in CDK, we have to set
the allowAllOutbound
prop to false
, when instantiating the SecurityGroup
class.
Let's create a security group and customize its outbound traffic rules.
import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as cdk from 'aws-cdk-lib'; export class CdkStarterStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // ... rest // 👇 create a SG with custom Outbound rules const customOutboundSG = new ec2.SecurityGroup(this, 'custom-outbound-sg', { vpc, allowAllOutbound: false, description: 'a security group with custom outbound rules', }); customOutboundSG.addEgressRule( ec2.Peer.ipv4('10.0.0.0/16'), ec2.Port.tcp(3306), 'allow outgoing traffic on port 3306', ); customOutboundSG.addEgressRule( ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'allow outgoing traffic on port 80', ); } }
Let's go over the code snippet.
allowAllOutbound
prop
to false
. This is necessary if we are going to edit the outbound rules for
a security group, otherwise our egress rules would just get ignoredaddEgressRule
method on an instance of the SecurityGroup
classThe outbound rules we added to the security group look like:
Type | Protocol | Port | Destination |
---|---|---|---|
MYSQL | TCP | 3306 | 10.0.0.0/16 |
HTTP | TCP | 80 | 0.0.0.0/0 |
Let's deploy the changes:
npx aws-cdk deploy
If we look at the VPC management console, we can see that the outbound rules have been applied to the security group:
Note that we haven't added any inbound rules to the security group, so it has none:
To delete the resources we have provisioned, issue the destroy
command:
npx aws-cdk destroy