Security Group Examples in AWS CDK - Complete Guide

avatar
Borislav Hadzhiev

Last updated: Jan 26, 2024
11 min

banner

# Table of Contents

  1. Creating a Security Group in AWS CDK
  2. Importing an Existing Security Group in AWS CDK
  3. Remove a Security Group Inbound Rule with AWS CLI
  4. Remove a Security Group Outbound Rule with AWS CLI

# Creating a Security Group in AWS CDK

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.

By default security groups provisioned with CDK allow all outbound (egress) traffic and deny all incoming (ingress) traffic.

We are going to look at multiple examples of creating security groups and editing their inbound and outbound rules.

The code for this article is available on GitHub

Let's start by creating a VPC and a security group for a web server.

lib/cdk-starter-stack.ts
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', { ipAddresses: ec2.IpAddresses.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 sample:

  1. 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.

  2. 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 in
  • allowAllOutbound - whether the security group should allow all outbound traffic. By default allowAllOutbound is set to true
  • description - a short description of the security group
  1. To allow inbound traffic we used the addIngressRule method on an instance of the SecurityGroup class. The parameters we passed to the method are:
  • peer - the Source in a security group inbound rule
  • connection - the Port, Protocol and Type in a security group inbound rule
  • description - a short description of the security group rule
  1. These are the inbound rules we added to our security group:
TypeProtocolPortSource
SSHTCP220.0.0.0/0
HTTPTCP800.0.0.0/0
HTTPSTCP4430.0.0.0/0
All ICMPICMPALL123.123.0.0/16

Let's provision the VPC and the security group:

shell
npx aws-cdk deploy

After the resources have been deployed, we can see that the inbound security group rules have been applied:

inbound security group rules

The outbound rules allow all traffic because we've set the allowAllOutbound to true, which is also the default value:

outbound security group rules

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.

The code for this article is available on GitHub
lib/cdk-starter-stack.ts
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 sample:

  1. We created a backend server security group.
  2. We used the allowFrom method on an instance of the Connections class to allow inbound connections on port 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

  1. We created a database server security group and used the allowFrom method to allow traffic on port 3306 from the security group of the backend server.

The inbound rules for the backendServerSG look as follows:

TypeProtocolPortSource
Custom TCPTCP8000webserverSG-id

The inbound rules for the dbserverSG look as follows:

TypeProtocolPortSource
MYSQLTCP3306backendServerSG-id

The outbound rules are still the default of all traffic allowed. We will edit the outbound rules of a security group later in the article.

Let's run the deploy command:

shell
npx aws-cdk deploy

After deploying, 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 web server security group:

backend server 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:

database server security group

Next, we are going to take a look at how we can edit the default outbound rules of a security group.

# Updating the Outbound Rules of Security Groups in AWS CDK

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.

The code for this article is available on GitHub

Let's create a security group and customize its outbound traffic rules.

lib/cdk-starter-stack.ts
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.

  1. We created a security group, but this time we set the 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 ignored.
  2. We used the addEgressRule method on an instance of the SecurityGroup class.

The outbound rules we added to the security group look as follows:

TypeProtocolPortDestination
MYSQLTCP330610.0.0.0/16
HTTPTCP800.0.0.0/0

Let's deploy the changes:

shell
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:

security group updated outbound rules

Note that we haven't added any inbound rules to the security group, so it has none:

security group no inbound rules

# Clean up

To delete the resources we have provisioned, issue the destroy command:

shell
npx aws-cdk destroy

# Table of Contents

  1. Importing an Existing Security Group in AWS CDK
  2. Remove a Security Group Inbound Rule with AWS CLI
  3. Remove a Security Group Outbound Rule with AWS CLI

# Importing an Existing Security Group in AWS CDK

In order to import an existing security group into a CDK stack, we have to use the fromSecurityGroupId static method on the SecurityGroup class.

The code for this article is available on GitHub

Let's look at an example of importing a security group in a CDK stack:

lib/cdk-starter-stack.ts
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); // ๐Ÿ‘‡ import security group by ID const importedSecurityGroup = ec2.SecurityGroup.fromSecurityGroupId( this, 'imported-security-group', 'YOUR-SG-ID', {allowAllOutbound: true, mutable: true}, ); console.log('security group id ๐Ÿ‘‰', importedSecurityGroup.securityGroupId); } }

Let's go over the code snippet.

  1. We imported a security group into our CDK stack by using the fromSecurityGroupId static method on the SecurityGroup class

  2. The fromSecurityGroupId method takes the following parameters:

  • scope - the scope the method is invoked in

  • id - the construct identifier (must be unique in the scope)

  • securityGroupId - the id of the security group

  • securityGroupImportOptions - a configuration object for the imported security group

    The allowAllOutbound property is set to true by default and specifies that the security group allows all outbound traffic. The fromSecurityGroupId method assumes that the imported security group allows all outbound traffic, so it doesn't modify any of the egress rules. If we wanted to modify the outbound rules of the imported security group, we would have to set allowAllOutbound to false.

    The mutable property is also set to true by default. Setting the property to true allows us to add rules to the imported security group. We can only add inbound rules unless allowAllOutbound is set to false.

# Adding Inbound rules to an Imported Security Group in CDK

In order to add an inbound rule to an imported security group in CDK, we have to:

  1. Set the mutable property to true when importing the security group. The mutable prop is set to true by default, so we can omit passing it altogether
  2. Use the addIngressRule method on the imported security group
The code for this article is available on GitHub
lib/cdk-starter-stack.ts
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 // ๐Ÿ‘‡ `mutable` is `true`, so we can add ingress rules importedSecurityGroup.addIngressRule( ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'allow SSH access from anywhere', ); } }

We used the addIngressRule method to add the following inbound rule to the imported security group:

TypeProtocolPortSource
SSHTCP220.0.0.0/0

If I run the npx aws-cdk deploy command with an existing security group ID, we can see that the inbound rule gets applied:

imported security group inbound

Note that if we were to destroy the CDK stack, the inbound rule would get deleted and removed from the security group.

# Adding Outbound rules to an Imported Security Group in CDK

In order to add an outbound rule to an imported security group in CDK, we have to:

  1. Set the allowAllOutbound property to false and the mutable property to true
lib/cdk-starter-stack.ts
const importedSecurityGroup = ec2.SecurityGroup.fromSecurityGroupId( this, 'imported-security-group', 'sg-0364cc5f9a979e9a6', {allowAllOutbound: false, mutable: true}, );
  1. Use the addEgressRule method on the imported security group
The code for this article is available on GitHub

Let's look at an example, where we add an egress rule to an imported security group:

lib/cdk-starter-stack.ts
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 // ๐Ÿ‘‡ `mutable` is `true`, so we can add ingress rules importedSecurityGroup.addEgressRule( ec2.Peer.ipv4('10.0.0.0/16'), ec2.Port.tcp(3306), 'allow outgoing traffic on port 3306', ); } }

We used the addEgressRule method on the imported security group to add the following outbound rule:

TypeProtocolPortDestination
MYSQLTCP330610.0.0.0/16

If I deploy the new egress rule, we can see that the outbound rules of the imported security group get updated:

imported security group outbound

If we take a look at the resources the CloudFormation stack has provisioned, we can see the ingress and egress security group rules:

cloudformation security group rules

Deleting the stack would remove all of the ingress and egress rules we added to the imported security group.

# Table of Contents

  1. Remove a Security Group Inbound Rule with AWS CLI
  2. Remove a Security Group Outbound Rule with AWS CLI

# Remove a Security Group Inbound Rule with AWS CLI

To remove an inbound rule from a security group, we need to:

  1. Get the security group ID
  2. Run the revoke-security-group-ingress command, passing in details that identify the rule to be removed

To get the security group ID, open your AWS console, or if you've named the security groups in that specific region uniquely, run the describe-security-groups command:

shell
aws ec2 describe-security-groups --query 'SecurityGroups[*].{sgName:GroupName,sgId:GroupId,vpcId:VpcId}'

The command returns a list of the security group names, IDs and VPC IDs:

describe security groups

Next, run the revoke-security-group-ingress command passing in the details that identify the rule to be removed:

shell
aws ec2 revoke-security-group-ingress --group-id sg-ABC123 --protocol tcp --port 80 --cidr 0.0.0.0/0

remove inbound rule

We removed an inbound rule that allows HTTP traffic on port 80 from anywhere.

If the rule has a protocol or port of All, use the -1 value for those parameters in your call to the revoke-security-group-ingress command.

To verify that the rule has been removed, run the describe-security-groups command:

shell
aws ec2 describe-security-groups --group-ids sg-ABC123
You might have seen that in the AWS CLI docs, the first example uses the--group-name parameter to identify the security group. Note that you can only use the --group-name parameter when the security group is in your default VPC in that region.

If the security group you're trying to remove a rule from isn't in your default VPC, you must use the --group-id parameter.

If the rule is not found in the security group, the AWS CLI throws an error: "The specified rule does not exist in this security group".

If your inbound rule specifies a port range or you want to remove multiple inbound rules, use the --ip-permissions parameter in the call to revoke-security-group-ingress:

shell
aws ec2 revoke-security-group-ingress --group-id sg-ABC123 --ip-permissions "[{\"IpProtocol\": \"tcp\", \"FromPort\": 80, \"ToPort\": 90, \"IpRanges\": [{\"CidrIp\": \"0.0.0.0/0\"}]}]"

remove inbound rule ip permissions parameter

We removed an inbound rule that allows TCP traffic from anywhere to a port range of 80-90.

The syntax with the --ip-permission parameter is quite tricky, make sure you escape the double quotes in the json input.

If you need to remove multiple inbound rules with a single command pass multiple objects in the list of the --ip-permission parameter.

# Remove a Security Group Outbound Rule with AWS CLI

To remove a security group outbound rule with the AWS CLI, run the revoke-security-group-egress command, passing in parameters that identify the rule you're trying to remove.

shell
aws ec2 revoke-security-group-egress --group-id sg-ABC123 --protocol icmp --port -1 --cidr 0.0.0.0/0

remove outbound rule

The command above removes an outbound rule that allows icmp traffic on all ports to everywhere.

Note that we've specified All ports by passing in the -1 value to the --port parameter.

To verify that the rule has been removed, run the describe-security-groups command:

shell
aws ec2 describe-security-groups --group-ids sg-ABC123

If your outbound rule specifies a port range or you want to remove multiple outbound rules, use the --ip-permissions parameter in the call to revoke-security-group-egress:

shell
aws ec2 revoke-security-group-egress --group-id sg-ABC123 --ip-permissions "[{\"IpProtocol\": \"tcp\", \"FromPort\": 80, \"ToPort\": 90, \"IpRanges\": [{\"CidrIp\": \"0.0.0.0/0\"}]}]"

remove outbound rule with ip permissions parameter

In the command above, we removed an outbound rule that allows TCP traffic on ports 80-90 to everywhere.

To remove multiple inbound rules in a single call to revoke-security-group-egress, pass multiple objects in the list of the --ip-permissions parameter.

# Additional Resources

You can learn more about the related topics by checking out the following tutorials:

I wrote a book in which I share everything I know about how to become a better, more efficient programmer.
book cover
You can use the search field on my Home Page to filter through all of my articles.

Copyright ยฉ 2024 Borislav Hadzhiev