Security Group Examples in AWS CDK - Complete Guide

avatar

Borislav Hadzhiev

Tue May 04 20216 min read

A complete example of how to create a Security Group in AWS CDK, and edit its inbound and outbound rules.

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.

In this article 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/aws-ec2';
import * as cdk from '@aws-cdk/core';

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.

  1. we created a VPC, by instantiating and configuring the Vpc class. We have 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've 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 have used the addIngressRule method on an instance of the SecurityGroup class. The parameters we've
    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 have 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 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/aws-ec2';
import * as cdk from '@aws-cdk/core';

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.

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

TypeProtocolPortSource
Custom TCPTCP8000webserverSG-id

The inbound rules for the dbserverSG look like:

TypeProtocolPortSource
MYSQLTCP3306backendServerSG-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 execute a deployment:

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

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/aws-ec2';
import * as cdk from '@aws-cdk/core';

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 have added to the security group look like:

TypeProtocolPortDestination
MYSQLTCP330610.0.0.0/16
HTTPTCP800.0.0.0/0

Let's deploy the changes:

shell
npx 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, execute the destroy command:

shell
npx cdk destroy

Further Reading #

Join my newsletter

I'll send you 1 email a week with links to all of the articles I've written that week

Buy Me A Coffee