Networking & Content Delivery

Building an egress VPC with AWS Transit Gateway and the AWS CDK

Introduction

With the introduction of AWS Transit Gateway, it is easier for customers to manage connectivity between many VPCs. Further simplification can be achieved by routing all outbound traffic through one shared egress VPC. This allows re-use of NAT Gateways and can improve overall network design and operational efficiency.

This ‘egress VPC’ design pattern is introduced in great detail by my colleagues Vinod Madabushi and Perry Wald in a related blog article. This post builds upon their work and demonstrates how to implement their solution with fewer than 150 lines of code.

I use the AWS Cloud Development Kit (CDK), an open source software development framework to model and provision cloud application resources. By using the CDK, you significantly reduce the complexity and amount of code needed to automate deployment of resources as infrastructure as code.

In addition, I use AWS Systems Manager Session Manager for accessing fully private EC2 instances in isolated VPCs without the need for bastion hosts.

Solution overview

The architecture diagram that follows shows the egress VPC pattern and elements created by the template. The private VPC contains a single EC2 instance. It lacks a direct route to the internet and has no public subnets or internet gateway. Instead, traffic destined for the internet is routed to the Transit Gateway.

The second VPC contains two pairs of public and private subnets, an internet gateway and two NAT gateways. Both VPCs are attached to the Transit Gateway, which allows north-south connectivity. All relevant routes are depicted in tables connected by dotted lines.

Walkthrough

The following tutorial familiarizes you with the AWS Cloud Development Kit. You learn how to automate deployment of individual resources or complete solutions on AWS. In three easy steps you complete the following tasks:

  • Installing CDK and reviewing a demo repository
  • Deploying the example environment into your own AWS account
  • Familiarizing yourself with the egress VPC pattern and the associated constructs and routing
  • Securely accessing the shell of a fully private EC2 instance via AWS Systems Manager Session Manager

The complete demo is available here on GitHub. Let’s get you started!

Step 1: Prerequisites

For this walkthrough, you need the following:

Step 2: Checkout and deploy the sample stack

  1. Make sure that you completed the prerequisite above and cloned the CDK example by running the following command in a local directory:

git clone git@github.com:aws-samples/aws-transit-gateway-egress-vpc-pattern.git

  1. Open the repository in your preferred local editor and inspect lib/egress_vpc-tg-demo-stack.ts

The example file is written in TypeScript and contains all objects necessary to create the demo environment. It uses CDK higher-level constructors for creating a VPC with multiple subnets, route tables, and NAT gateways with just a few lines of code.

const egressVPC = new ec2.Vpc(this, 'Egress VPC', {
      cidr: "10.0.1.0/26",
      //natGateways: 1, add this to limit number of deployed NAT gateways
      subnetConfiguration: [{
          cidrMask: 28,
          name: 'Public - EgressVPC SubNet',
          subnetType: SubnetType.PUBLIC,
        },
        {
          cidrMask: 28,
          name: 'Private - EgressVPC SubNet',
          subnetType: SubnetType.PRIVATE,
        },
      ]
    });
    const privateVPC = new ec2.Vpc(this, 'Private VPC', {
      cidr: "10.0.2.0/26",
      maxAzs: 1,
      enableDnsHostnames: true,
      enableDnsSupport: true,
      subnetConfiguration: [{
        cidrMask: 28,
        name: 'Isolated Subnet - privateVPC',
        subnetType: SubnetType.ISOLATED,
      }],
    });
  1. Run npm install to include dependencies
  2. Run npm run build once to compile to JavaScript. Alternatively, if you want to keep experimenting, open a separate terminal window running npm run watch to start continuous compilation to JavaScript in watch mode
  3. Execute cdk synth and check out the synthesized AWS CloudFormation YAML syntax that is used for deployment of the stack. Due to the higher-level programming languages used in CDK and constructor libraries, the CDK code is more compact and more powerful than conventional markup. For instance, you can easily loop through all subnets of the egressVPC and add routes, such as:
    for (let subnet of egressVPC.publicSubnets) {
      new CfnRoute(this, subnet.node.uniqueId, {
        routeTableId: subnet.routeTable.routeTableId,
        destinationCidrBlock: privateVPC.vpcCidrBlock,
        transitGatewayId: TransitGateway.ref,
      }).addDependsOn(TransitGatewayAttachmentEgress);
    };

    for (let subnet of privateVPC.isolatedSubnets) {
      new CfnRoute(this, subnet.node.uniqueId, {
        routeTableId: subnet.routeTable.routeTableId,
        destinationCidrBlock: "0.0.0.0/0",
        transitGatewayId: TransitGateway.ref,
      }).addDependsOn(TransitGatewayAttachmentPrivate);
    };
  1. Now deploy the stack simply by running cdk deploy and observe the progress in your terminal window.

Step 3: Review results and test routing

  1. Once stack creation is complete, open the AWS Management Console, select AWS Systems Manager, choose the instance created with the stack, and select “Start Session.”

  1. Execute any command to test internet connectivity, for example ping amazon.com

Good to know and explore

  • The image used to create the EC2 instance is selected based on a higher-level constructor, AmazonLinuxImage, and the AMI ID is automatically retrieved via the SSM Parameter Store. This means that the stack is deployed to multiple Regions and automatically retrieves the latest managed image of the selected OS. By default CDK uses your default AWS CLI configuration, however multiple environments can easily be integrated via CDK native functionality.
  • AWS Systems Manager Agent (SSM Agent) is included in the AMI and requires two managed policies to work. They are attached to the instance’s role.
      assumedBy: new ServicePrincipal('ec2.amazonaws.com'),
      Managed policies: [
        ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'),
        ManagedPolicy.fromAwsManagedPolicyName('CloudWatchAgentServerPolicy'),
      ]
    });
  • The CDK template includes commented out examples of adding VPC Endpoint for Systems Manager and illustrates the use of mandatory, as well as optional endpoints.

Cleaning up

To avoid incurring future charges, delete the resources by simply running cdk destroy and confirm deletion.

Conclusion

This post demonstrated how you can implement an egress VPC pattern in CDK with fewer than 150 lines of code. The template is portable between AWS Regions and easily adjustable to different architectural considerations by using higher-level constructors and convenience functions.

The use of AWS Systems Manager illustrates how builders can reduce costs and complexity and improve overall security posture of their architecture. This approach can remove the need for dedicated bastion hosts, while still retaining full access to deployed instances.

If you have any questions or suggestions, please leave a comment.

Ivan Zaytsev

Ivan Zaytsev

Ivan Zaytsev is a Solutions Architect at Amazon Web Services with a passion for IoT, Cloud, Mobile, and most things digital.

Blog: Using AWS Client VPN to securely access AWS and on-premises resources
Learn about AWS VPN services
Watch re:Invent 2019: Connectivity to AWS and hybrid AWS network architectures