HOME
/
BLOG
Dynamic VPC Peering Part 1

Dynamic VPC Peering Part 1

Fri, Aug 28, 202010 min read

Category: AWS

This is an AWS based solution, utilizing CloudFormation, AWS SDK, node.js, lambdas, and advanced networking.

Let's assume that we’ve got a master application that should be accessible only to selected client applications and a client application that will be deployed dynamically in many instances. Each application will be deployed on separate VPC and AWS accounts. It's possible to establish a peering connection between two (or more) VPCs on the fly.

Our plan is to:

  • create a publicly accessible API endpoint and DB registry of existing paired client apps,

  • put a custom resource in the client-side CloudFormation stack that will call the endpoint and pull the initial setup for the new VPC and VPC Peering

  • the public endpoint will adjust the VPC Peer Role on the master account to allow the client app to establish a VPC Peering connection,  

  • the outputs will be used as parameters when creating VPC-related resources and register our newly forming client application,

  • the client CloudFormation template will create a VPC, Subnets, and VPC Peering resource and, when done, will notify the public endpoint of success,

  • the public endpoint will create routing table entries to accommodate traffic from the new source.

With the route table online, we should be able to access our master app from the freshly deployed client app. At each step, we can implement health checks that will force the client app to roll back if an error occurs.

I hope you will find all the details below useful; it requires a bit of coding and a moderate AWS skillset.

Case study: 

The command center data application needed the highest-level security-driven solution to run client-side applications on a separate AWS account, deployed from the AWS service catalog. An essential requirement was ensuring that deploying the client-side app wouldn’t compromise the security of the AWS account. Therefore, no communication was allowed to travel through the public internet. The client app will dynamically join the command center VPC through online requests (REST API/Lambda).

What is VPC Peering?

VPC Peering is a networking association of two VPCs to route traffic among them privately. A VPC Peering connection can be set up between VPCs on different AWS accounts and/or regions. VPCs are communicated by private IPs based on two subnets, where we need to put a route table that points to the IP address range of the peer VPC and makes sure that the CIDR blocks of two VPCs are not overlapping.

How to prevent overlapping IPs?

We have to enumerate our networks n=0...N; n=0 will be our command center app; n > 1 will be reserved for client applications.

This is how we calculate the address space for the VPC:

const int2ip = (ipInt) => ( (ipInt>>>24) + '.' + (ipInt>>16 & 255) +'.' + (ipInt>>8 & 255) +'.' + (ipInt & 255) )

For example:

  • a. n=0 10.0.0.0/22

  • b. n=1 10.0.4.0/22

  • c. n=2 10.0.8.0/22 

  • d. n=300 10.4.176.0/22

In each VPC we create 4 subnets, s=0,1,2,3. Public Subnets s=0, s=1 and Private Subnets s=2 & s=3. The subnet range will look like this: 

(n, s) => int2ip(((1<<10) * n) + (1<<8) * s + (10<<24)); for example:

  • a. n=0, s=1 10.0.1.0/24

  • b. n=300, s=2 10.4.178.0/24

Each Subnet will have 255 addresses and each VPC has a total of 1024 addresses (512 public and 512 private).

With that settled, we can move forward.

Part 1 - API: Registering the Client App

To use that addressing easily in the client app CloudFormation stack, we need to pass it through the public endpoint that will be available via API Gateway.

The endpoint needs to register the VPC peering request, assign a unique ID (n), and return some parameters (examples below are for n=1)

  1. Namespace ID(n), to be used later for verifying the setup

  2. VPNCidrBlock, like 10.0.4.0/22

  3. SubnetACidrBlock, like 10.0.4.0/24

  4. SubnetBCidrBlock, like 10.0.5.0/24

  5. SubnetCCidrBlock, like 10.0.6.0/24

  6. SubnetDCidrBlock, like 10.0.7.0/24

  7. PeerVpcId, (VPC ID of our command center app) for later use

  8. PeerOwnerId (VPC ACCOUNT ID) for later use

  9. PeerRoleArn (VPC Peering role) for later use

Samples below are in typescript, using great and well-documented "aws-sdk" with great typing.

https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/installing-jssdk.html

npm install aws-sdk --save

We gonna import few bits from that package

import { CreateRouteRequest } from "aws-sdk/clients/ec2";
import { IAM, ECR, EC2 } from "aws-sdk";

If you are using AWS Fargate or an EC2 instance to run the endpoint with SDK, you don't need to sign in. 

Our API needs to know the ID of the VPC that we want to connect to, its role, and the AWS account ID (VPC_ID, VPC_ACCOUNT_ID, VPC_PEER_ROLE).

For this sample, I'm introducing them as environmental variables.

Also, the API needs to find a unique, lowest, unused namespaceId, and keep existing and working IDs in the DB.

Step 1: Calculate Networks

function calculateNetworks(namespaceId: number) {
  const int2ip = (ipInt: number) =>
    `${ipInt >>> 24}.${(ipInt >> 16) & 255}.${(ipInt >> 8) & 255}.${ipInt & 255}`;
  const subnetIp = (subnetId: number) =>
    int2ip((1 << 10) * namespaceId + (1 << 8) * subnetId + (10 << 24));
  return {
    namespaceId,
    VPNCidrBlock: int2ip((1 << 10) * namespaceId + (10 << 24)) + "/22",
    SubnetACidrBlock: subnetIp(0) + "/24",
    SubnetBCidrBlock: subnetIp(1) + "/24",
    SubnetCCidrBlock: subnetIp(2) + "/24",
    SubnetDCidrBlock: subnetIp(3) + "/24",
    PeerVpcId: process.env.VPC_ID,
    PeerOwnerId: process.env.VPC_ACCOUNT_ID,
    PeerRole: process.env.VPC_PEER_ROLE,
  };
}

The code will construct the base object that we can pass to the client app’s CloudFormation template as params, but before that, we need to:

Step 2: VPC Peering of the Client App 

In this request to the API, we need to catch the requester AWS Account ID, which is the ID we will use to adjust the role policy.

To create that role, we will use the CloudFormation template in a command center stack, like:

VPCpeerRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Principal:
              AWS: 	
                - "ID_OF_ACCOUNTS_THAT_CAN_CREATE_VPC_PEERING"
            Action:
              - sts:AssumeRole
            Effect: Allow
      Path: /
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: 'ec2:AcceptVpcPeeringConnection'
                Resource: '*'

That's our VPC_PEER_ROLE. We will expand it by adding an additional AWS account ID, and the stack will drift so it won't be updated by default. You can keep the above template in the main stack.

We can expand the role on our API on the fly like this:

function addAccountToRolePolicy(awsAccountId: number) {
const iam = new IAM();
const role = await iam
      .getRole({
        RoleName: process.env.VPC_PEER_ROLE,
      })
      .promise();
const policy = JSON.parse(
        unescape(vpcPeerRole.Role.AssumeRolePolicyDocument as string) || "{}"
      );
roleArn = vpcPeerRole.Role.Arn;
      if (!policy.Statement[0].Principal.AWS.includes(awsAccountId)) {
        policy.Statement[0].Principal.AWS.push(awsAccountId);
      }
const updateAssumeRolePolicy = await iam
        .updateAssumeRolePolicy({
          RoleName: process.env.VPC_PEER_ROLE,
          PolicyDocument: JSON.stringify(policy),
        }).promise();
}

GREAT!!! Now our endpoint has granted namespace, address range, and permitted the account to assume an AWS peering role. 

Now let's see how to utilize these params.

Soon, PART 2: Creating a Client App 

  • Piotr Sierankiewicz
    Piotr Sierankiewicz

    DevOps

Get the latest technology insights on our blog

Recent posts