HOW-TO: Implement VPC Peering between 2 VPC’s in the same AWS account using CloudFormation

October 11, 2017 Liz Duke

Introduction

While investigating new solutions I was spinning up POC’s and decided that instead of either making a new jumphost every time or adding manually the access to my existing jumphost I just wanted the new VPC to come up with my minimum set of requirements. One of these requirements was peering the new VPC to an existing VPC with some services I wanted to re-use.

Background

We use what is a “hub-and-spoke” design for our Virtual Private Clouds (VPC’s) – where the hub is a centralized Management Services VPC and the spokes lead out to peered Product VPC’s which are usually for each environment. This allows us to not have to duplicate our shared Management Services such as monitoring, jumphosts, build servers etc. This is shown as the “partially meshed” example in the following page:

https://aws.amazon.com/answers/networking/aws-single-region-multi-vpc-connectivity.

To test out various options for automation without impacting existing services I am leveraging a sandbox account which allows me to test out my code without a concern of impacting existing customer accounts.

Requirement

If a new VPC is created in a region, it needs to be peered with an existing Management Services VPC. The route tables of both VPC’s must be updated to direct relevant traffic between the VPC’s.

For this to work, the VPC’s that are to be peered with the Management Services VPC must not have overlapping address ranges with either the Management Services VPC or any of the other VPC’s the Management Services VPC is peered to.

Is there an example I can just use?

I started by looking for examples of how to do this and although there is a nice AWS documentation example to enable peering between 2 different AWS accounts using Lambda, the peering between 2 VPC’s in the same account did not have an example. I used this guide http://2ndwatch.com/blog/vpc-peering-via-cloudformation/ to work out what I needed to do and expanded it for my needs.
I have a CloudFormation template file that builds up my standard VPC with a public and private subnet and a NAT gateway for outbound traffic. As my Management Services VPC already exists and has some services I want to use (such as a jumphost) then I want to peer my new VPC to that existing management services VPC.

This is an overview of the 2 VPC’s that I will be connecting. The Management Services VPC already exists and the Product VPC is the one I am creating as “Standard VPC”.


Management Services VPC (MyServiceVPC).

The existing Management/Services VPC will be referenced in the new template by using parameters. At first this errored for me as I was referencing the route table as Type “Route”, but that is not allowed as a parameter type. To reference a route table from another stack I used the type of “String” and entered them in the template directly. I later replaced the Default Values as I wanted to anonymise the template so that it could be shared. You will need to either have these values as outputs from your previous service VPC creation (in which case they are referenced differently) or look up the values and input them here as the “Default” or on creation of the stack.


 "MyServiceVPC": {
 "Description": "VPC with some useful service tools",
 "Type": "AWS::EC2::VPC::Id",
 "Default": "vpc-xxxxx"
 },

"PrivateRouteTableServiceVPC": {
 "Description": "Route Table for Private Subnet in Service VPC",
 "Type": "String",
 "Default": "rtb-xxxxx"
 },

"PublicRouteTableServiceVPC": {
 "Description": "Route Table for Public Subnet in Service VPC",
 "Type": "String",
 "Default": "rtb-xxxxx"
 },

 

Product VPC (StandardVPC).

This is the new VPC I am creating which I want to be peered with my Management  Services VPC so that I can use my jumphost and any other services I may need, such as LDAP, monitoring etc.
I will specify several resources to create here, they will define entries to my routing tables in both VPCs. The account creating the VPC – which is making the peering request – must have sufficient permission to both create the request and approve it. I’m doing this in my sandbox account where I am for all intents and purposes all-powerful :-).

If you are not using your own permissions to do this you will have to run this update as a user who has this level of access or allow a role to be assumed that gives the same access.

"myVPCPeeringConnection": {
"Type": "AWS::EC2::VPCPeeringConnection",
"Properties": {
"VpcId": {
"Ref": "StandardVPC"
},
"PeerVpcId": {
"Ref": "MyServiceVPC"
}
}
},
"PeeringRoute1StandardtoServicePrivate": {
"Type": "AWS::EC2::Route",
"Properties": {
"DestinationCidrBlock":{
"Ref": "ServiceVPCCIDR"
},
"RouteTableId": {
"Ref": "PrivateRouteTable"
},
"VpcPeeringConnectionId": {
"Ref": "myVPCPeeringConnection"
}
}
},
"PeeringRoute2ServicePrivatetoStandard": {
"Type": "AWS::EC2::Route",
"Properties": {
"DestinationCidrBlock": {
"Ref": "PrivateSubnetCIDR"
},
"RouteTableId": {
"Ref": "PrivateRouteTableServiceVPC"
},
"VpcPeeringConnectionId": {
"Ref": "myVPCPeeringConnection"
}
}
},
"PeeringRoute3ServicePublictoStandard": {
"Type": "AWS::EC2::Route",
"Properties": {
"DestinationCidrBlock": {
"Ref": "PrivateSubnetCIDR"
},
"RouteTableId": {
"Ref": "PublicRouteTableServiceVPC"
},
"VpcPeeringConnectionId": {
"Ref": "myVPCPeeringConnection"
}
}
},

I am peering the private subnet of my new VPC to the public and private subnet of the Management Services VPC. I am doing this as my jumphost is in the public subnet of my Services VPC with access restricted to our Hide IP’s. I also have an LDAP server in the private subnet of my Services VPC that may need to connect to hosts in my new VPC. As I’m not creating anything else in my public subnet of the new VPC that needs to connect to the Management VPC I didn’t need to add additional routes. If you do want to access instances in the public subnet of your new VPC you just need to add more resources as you need the routes adding both ways (or your traffic doesn’t know how to return via the peering connection). This is shown below :-


"PeeringRoute4StandardtoServicePublic": {
"Type": "AWS::EC2::Route",
"Properties": {
"DestinationCidrBlock":{
"Ref": "ServiceVPCCIDR"
},
"RouteTableId": {
"Ref": "PublicRouteTable"
},
"VpcPeeringConnectionId": {
"Ref": "myVPCPeeringConnection"
}
}
},
"PeeringRoute5ServicePublictoStandard": {
"Type": "AWS::EC2::Route",
"Properties": {
"DestinationCidrBlock": {
"Ref": "PublicSubnetCIDR"
},
"RouteTableId": {
"Ref": "PrivateRouteTableServiceVPC"
},
"VpcPeeringConnectionId": {
"Ref": "myVPCPeeringConnection"
}
}
},
"PeeringRoute6ServicePublictoStandard": {
"Type": "AWS::EC2::Route",
"Properties": {
"DestinationCidrBlock": {
"Ref": "PublicSubnetCIDR"
},
"RouteTableId": {
"Ref": "PublicRouteTableServiceVPC"
},
"VpcPeeringConnectionId": {
"Ref": "myVPCPeeringConnection"
}
}
},

You can see from the code blocks that I need to let my new VPC know which requests to IP’s need to go via the Peering connection.
The peering request itself is quite straightforward and just references both VPC’s. Now when the VPC is created, the peering request is also created and approved. The relevant routes are added to the routing tables of the management/services VPC and the new VPC.
I have parameterized the values for the VPC CIDR as well as the Public and Private Subnets and the AZ so that they can be modified for each new VPC.

"VPCCIDRBlock": {
"Description": " The IP address range used by the VPC.",
"Type": "String",
"MinLength": "9",
"MaxLength": "18",
"Default": "10.0.0.0/22",
"AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
"ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
},
"PublicSubnetCIDR": {
"Description": " Valid Subnet within the VPC CIDR Range to be used for Public Subnet",
"Type": "String",
"MinLength": "9",
"MaxLength": "18",
"Default": "10.0.2.0/24",
"AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
"ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
},
"PrivateSubnetCIDR": {
"Description": " Valid Subnet within the VPC CIDR Range to be used for Private Subnet",
"Type": "String",
"MinLength": "9",
"MaxLength": "18",
"Default": "10.0.3.0/24",
"AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
"ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
},

"AZ":{
"Description":"The AZ for Public and Private Subnets",
"Type":"AWS::EC2::AvailabilityZone::Name"
},

Again you could add multiple AZ’s here – I just wanted to keep it straightforward. Now when I use the template to create a new VPC it creates the peering connection as it comes up and I can connect directly from my jumphost in the Management Services VPC to the host(s) in the Product VPC.

 

Is there a way I can just grab a CloudFormation template that includes these samples?

Well yes there is – after spending some time and creating various templates I decided I would store them on github and make them publicly accessible. The first template I have uploaded will create a new VPC with a public and private subnet and a NAT gateway. You will also get a public facing ELB with a security group that allows traffic from the ELB to instances. There is also a web instance security group which will contain rules to allow both web access from the ELB using it’s security group and ssh access from a jump-host that you specify in the peered VPC public subnet. So here you go…

https://github.com/lizduke/cloudformationexamples.git

Use git to clone the repo or download the file as a zip.

I hope you will find this template useful and let me know what you think in the comments box below.

Next time I will focus on stacking templates. If you want to be notified, just sign up to the blog to get all new posts sent straight to your inbox!

 

Note: Irdeto provides a range of software security products and services based off of our Cloakware Software Protection suite of tools and technologies.  All of the solutions, including Cloakware’s Secure Environment, adopt a multi-layered, self-protecting, approach to software security.

 

Previous Article
The Perimeter is a lie – The Container Layer (part 3)
The Perimeter is a lie – The Container Layer (part 3)

Following on from previous posts (part 1, part 2) I wanted to drill down a bit more into the components fro...

Next Article
Shedding light on CAP theorem for the pragmatic
Shedding light on CAP theorem for the pragmatic

In part 1 of this series of blog posts, we talked about how the choice between NoSQL and SQL databases is b...