Overview
VPC Peering is a networking connection that you can establish between two VPCs to allow instances on either end to communicate with each other, using their private IPs (both IPv4 and IPv6 are supported), in exactly the same way as if they were inside one VPC. Traffic never leaves the AWS backbone network, thus avoiding the dirty pipes of the Internet. The connection itself is free of charge. However, you have to pay for the data transferred between the VPCs. Intra-region (between VPCs within the same region), inter-region (between VPCs in different regions), and cross-account (between VPCs belonging to different AWS accounts) peering are all possible. Of course, in either case, the CIDR ranges of the peered VPCs mustn’t overlap with each other, e.g. peering VPC A with the CIDR range of 10.0.0.0/16 and VPC B with the CIDR range of 10.0.1.0/24 would not be possible as IP addresses from 10.0.1.0 to 10.0.1.255 exist in both VPCs. One more gotcha is that transitive peering is also disallowed. Hence, if you got VPC A peered to VPC B and VPC B peered to VPC C, you wouldn’t be able to reach VPC C from VPC A through VPC B. Instead, you’d need to peer VPC A directly with VPC C. With three VPCs it shouldn’t be such a hard thing to accomplish (and then to maintain), but imagine having hundreds of VPCs… To achieve full mesh topology in that scenario, you’d need a Transit Gateway. But I’m getting a little ahead of myself.
Implementation

VpcStack
class:➜ ping-me-cdk-example$ touch lib/vpc.ts
ping-me-cdk-example/lib/vpc.ts
import * as cdk from '@aws-cdk/core'; import * as ec2 from '@aws-cdk/aws-ec2'; // <--- this module is not available from the start; remember to import it: `npm install @aws-cdk/aws-ec2` interface VpcProps extends cdk.StackProps { vpcSetup: { cidrs: string[], // <--- each VPC will need a list of CIDRs maxAzs?: number, // <--- optionally the number of Availability Zones can be provided; defaults to 2 in our particular case vpnConnections?: { // <--- if dealing with Site-to-Site VPN, the VPN connection details can be provided [id: string]: ec2.VpnConnectionOptions; }, }; } export class VpcStack extends cdk.Stack { readonly createdVpcs: ec2.Vpc[]; // <-- create a class property for exposing the list of VPC objects constructor(scope: cdk.Construct, id: string, props: VpcProps) { super(scope, id, props); const createdVpcs: ec2.Vpc[] = []; // for each of the provided CIDR ranges, create a VPC with two /27 subnets (one public and one private) per AZ props.vpcSetup.cidrs.forEach((cidr, index) => { createdVpcs.push(new ec2.Vpc(this, 'Vpc' + index, { cidr, maxAzs: props.vpcSetup.maxAzs, subnetConfiguration: [ { cidrMask: 27, name: 'public', subnetType: ec2.SubnetType.PUBLIC, }, { cidrMask: 27, name: 'private', subnetType: ec2.SubnetType.PRIVATE, }, ], vpnConnections: props.vpcSetup.vpnConnections, })); }); // For each VPC's default security group, allow inbound ICMP (ping) requests from anywhere createdVpcs.forEach((vpc, index) => { ec2.SecurityGroup.fromSecurityGroupId(this, 'DefaultSecurityGroup' + index, vpc.vpcDefaultSecurityGroup) .addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.icmpPing(), 'Allow ping from anywhere'); }); this.createdVpcs = createdVpcs; // <-- expose the list of created VPC objects so that they can be used by different stacks } }
➜ ping-me-cdk-example$ npm install @aws-cdk/aws-e[email protected] npm WARN @aws-cdk/[email protected] requires a peer of @aws-cdk/[email protected] but none is installed. You must install peer dependencies yourself. npm WARN @aws-cdk/[email protected] requires a peer of @aws-cdk/[email protected] but none is installed. You must install peer dependencies yourself. npm WARN @aws-cdk/[email protected] requires a peer of @aws-cdk/[email protected] but none is installed. You must install peer dependencies yourself. npm WARN @aws-cdk/[email protected] requires a peer of @aws-cdk/[email protected] but none is installed. You must install peer dependencies yourself. npm WARN @aws-cdk/[email protected] requires a peer of @aws-cdk/[email protected] but none is installed. You must install peer dependencies yourself. npm WARN @aws-cdk/[email protected] requires a peer of @aws-cdk/[email protected] but none is installed. You must install peer dependencies yourself. npm WARN @aws-cdk/[email protected] requires a peer of @aws-cdk/[email protected] but none is installed. You must install peer dependencies yourself. npm WARN @aws-cdk/[email protected] requires a peer of @aws-cdk/[email protected] but none is installed. You must install peer dependencies yourself. npm WARN @aws-cdk/[email protected] requires a peer of @aws-cdk/[email protected] but none is installed. You must install peer dependencies yourself. npm WARN @aws-cdk/[email protected] requires a peer of @aws-cdk/[email protected] but none is installed. You must install peer dependencies yourself. npm WARN @aws-cdk/[email protected] requires a peer of @aws-cdk/[email protected] but none is installed. You must install peer dependencies yourself. npm WARN @aws-cdk/[email protected] requires a peer of @aws-cdk/[email protected] but none is installed. You must install peer dependencies yourself. npm WARN @aws-cdk/[email protected] requires a peer of @aws-cdk/[email protected] but none is installed. You must install peer dependencies yourself. npm WARN @aws-cdk/[email protected] requires a peer of @aws-cdk/[email protected] but none is installed. You must install peer dependencies yourself. npm WARN @aws-cdk/[email protected] requires a peer of @aws-cdk/[email protected] but none is installed. You must install peer dependencies yourself. npm WARN @aws-cdk/[email protected] requires a peer of @aws-cdk/[email protected] but none is installed. You must install peer dependencies yourself. npm WARN @aws-cdk/[email protected] requires a peer of @aws-cdk/[email protected] but none is installed. You must install peer dependencies yourself. npm WARN [email protected] No repository field. npm WARN [email protected] No license field. + @aws-cdk/[email protected] added 190 packages from 9 contributors and audited 932 packages in 10.624s 27 packages are looking for funding run `npm fund` for details found 0 vulnerabilities
1.73.0
version on purpose (the exact same one we used for our @aws-cdk/core module) to avoid the possibility of seeing the Argument of type ‘this’ is not assignable to parameter of type ‘Construct’ error.VpcStack
class in ping-me-cdk-example/bin/ping-me-cdk-example.ts
:ping-me-cdk-example/bin/ping-me-cdk-example.ts
import * as cdk from '@aws-cdk/core'; import { VpcStack } from '../lib/vpc'; const app = new cdk.App(); // <--- you can read more about the App construct here: https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.App.html const vpcPeers = new VpcStack(app, 'VpcPeersStack', { vpcSetup: { cidrs: ['10.0.0.0/24', '10.0.1.0/24'], // <--- two non-overlapping CIDR ranges for our two VPCs maxAzs: 1, // <--- to keep the costs down, we'll stick to 1 availability zone per VPC (obviously, not something you'd want to do in production) }, });
npm run build
command every time that happens, we’ll run the below:➜ ping-me-cdk-example$ npm run watch [19:50:46] Starting compilation in watch mode... [19:50:51] Found 0 errors. Watching for file changes. # KEEP THIS RUNNING!
➜ ping-me-cdk-example$ cdk synth Resources: Vpc07C831B30: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/24 EnableDnsHostnames: true EnableDnsSupport: true InstanceTenancy: default Tags: - Key: Name Value: VpcPeersStack/Vpc0 Metadata: aws:cdk:path: VpcPeersStack/Vpc0/Resource Vpc0publicSubnet1SubnetB977A71E: Type: AWS::EC2::Subnet Properties: CidrBlock: 10.0.0.0/27 VpcId: Ref: Vpc07C831B30 AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: "" MapPublicIpOnLaunch: true Tags: - Key: aws-cdk:subnet-name Value: public - Key: aws-cdk:subnet-type Value: Public - Key: Name Value: VpcPeersStack/Vpc0/publicSubnet1 Metadata: aws:cdk:path: VpcPeersStack/Vpc0/publicSubnet1/Subnet Vpc0publicSubnet1RouteTable2012E33A: Type: AWS::EC2::RouteTable Properties: VpcId: Ref: Vpc07C831B30 Tags: - Key: Name Value: VpcPeersStack/Vpc0/publicSubnet1 Metadata: aws:cdk:path: VpcPeersStack/Vpc0/publicSubnet1/RouteTable Vpc0publicSubnet1RouteTableAssociation0E1C3D4B: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: Vpc0publicSubnet1RouteTable2012E33A SubnetId: Ref: Vpc0publicSubnet1SubnetB977A71E Metadata: aws:cdk:path: VpcPeersStack/Vpc0/publicSubnet1/RouteTableAssociation Vpc0publicSubnet1DefaultRouteC03283FF: Type: AWS::EC2::Route Properties: RouteTableId: Ref: Vpc0publicSubnet1RouteTable2012E33A DestinationCidrBlock: 0.0.0.0/0 GatewayId: Ref: Vpc0IGW3080DF7F DependsOn: - Vpc0VPCGW9FBA9469 Metadata: aws:cdk:path: VpcPeersStack/Vpc0/publicSubnet1/DefaultRoute Vpc0publicSubnet1EIP16FED7DC: Type: AWS::EC2::EIP Properties: Domain: vpc Tags: - Key: Name Value: VpcPeersStack/Vpc0/publicSubnet1 Metadata: aws:cdk:path: VpcPeersStack/Vpc0/publicSubnet1/EIP Vpc0publicSubnet1NATGateway40294DF4: Type: AWS::EC2::NatGateway Properties: AllocationId: Fn::GetAtt: - Vpc0publicSubnet1EIP16FED7DC - AllocationId SubnetId: Ref: Vpc0publicSubnet1SubnetB977A71E Tags: - Key: Name Value: VpcPeersStack/Vpc0/publicSubnet1 Metadata: aws:cdk:path: VpcPeersStack/Vpc0/publicSubnet1/NATGateway Vpc0privateSubnet1SubnetD6383522: Type: AWS::EC2::Subnet Properties: CidrBlock: 10.0.0.32/27 VpcId: Ref: Vpc07C831B30 AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: "" MapPublicIpOnLaunch: false Tags: - Key: aws-cdk:subnet-name Value: private - Key: aws-cdk:subnet-type Value: Private - Key: Name Value: VpcPeersStack/Vpc0/privateSubnet1 Metadata: aws:cdk:path: VpcPeersStack/Vpc0/privateSubnet1/Subnet Vpc0privateSubnet1RouteTableB5C6777D: Type: AWS::EC2::RouteTable Properties: VpcId: Ref: Vpc07C831B30 Tags: - Key: Name Value: VpcPeersStack/Vpc0/privateSubnet1 Metadata: aws:cdk:path: VpcPeersStack/Vpc0/privateSubnet1/RouteTable Vpc0privateSubnet1RouteTableAssociationC17661A1: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: Vpc0privateSubnet1RouteTableB5C6777D SubnetId: Ref: Vpc0privateSubnet1SubnetD6383522 Metadata: aws:cdk:path: VpcPeersStack/Vpc0/privateSubnet1/RouteTableAssociation Vpc0privateSubnet1DefaultRoute1EA0AEFE: Type: AWS::EC2::Route Properties: RouteTableId: Ref: Vpc0privateSubnet1RouteTableB5C6777D DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: Ref: Vpc0publicSubnet1NATGateway40294DF4 Metadata: aws:cdk:path: VpcPeersStack/Vpc0/privateSubnet1/DefaultRoute Vpc0IGW3080DF7F: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: VpcPeersStack/Vpc0 Metadata: aws:cdk:path: VpcPeersStack/Vpc0/IGW Vpc0VPCGW9FBA9469: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: Ref: Vpc07C831B30 InternetGatewayId: Ref: Vpc0IGW3080DF7F Metadata: aws:cdk:path: VpcPeersStack/Vpc0/VPCGW Vpc1C211860B: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.1.0/24 EnableDnsHostnames: true EnableDnsSupport: true InstanceTenancy: default Tags: - Key: Name Value: VpcPeersStack/Vpc1 Metadata: aws:cdk:path: VpcPeersStack/Vpc1/Resource Vpc1publicSubnet1SubnetB43EFACE: Type: AWS::EC2::Subnet Properties: CidrBlock: 10.0.1.0/27 VpcId: Ref: Vpc1C211860B AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: "" MapPublicIpOnLaunch: true Tags: - Key: aws-cdk:subnet-name Value: public - Key: aws-cdk:subnet-type Value: Public - Key: Name Value: VpcPeersStack/Vpc1/publicSubnet1 Metadata: aws:cdk:path: VpcPeersStack/Vpc1/publicSubnet1/Subnet Vpc1publicSubnet1RouteTable1C630681: Type: AWS::EC2::RouteTable Properties: VpcId: Ref: Vpc1C211860B Tags: - Key: Name Value: VpcPeersStack/Vpc1/publicSubnet1 Metadata: aws:cdk:path: VpcPeersStack/Vpc1/publicSubnet1/RouteTable Vpc1publicSubnet1RouteTableAssociation4DA13984: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: Vpc1publicSubnet1RouteTable1C630681 SubnetId: Ref: Vpc1publicSubnet1SubnetB43EFACE Metadata: aws:cdk:path: VpcPeersStack/Vpc1/publicSubnet1/RouteTableAssociation Vpc1publicSubnet1DefaultRouteB4C85D62: Type: AWS::EC2::Route Properties: RouteTableId: Ref: Vpc1publicSubnet1RouteTable1C630681 DestinationCidrBlock: 0.0.0.0/0 GatewayId: Ref: Vpc1IGW15AE5E6B DependsOn: - Vpc1VPCGW4C1BD07A Metadata: aws:cdk:path: VpcPeersStack/Vpc1/publicSubnet1/DefaultRoute Vpc1publicSubnet1EIP5F1D9658: Type: AWS::EC2::EIP Properties: Domain: vpc Tags: - Key: Name Value: VpcPeersStack/Vpc1/publicSubnet1 Metadata: aws:cdk:path: VpcPeersStack/Vpc1/publicSubnet1/EIP Vpc1publicSubnet1NATGateway06106699: Type: AWS::EC2::NatGateway Properties: AllocationId: Fn::GetAtt: - Vpc1publicSubnet1EIP5F1D9658 - AllocationId SubnetId: Ref: Vpc1publicSubnet1SubnetB43EFACE Tags: - Key: Name Value: VpcPeersStack/Vpc1/publicSubnet1 Metadata: aws:cdk:path: VpcPeersStack/Vpc1/publicSubnet1/NATGateway Vpc1privateSubnet1Subnet41967AFD: Type: AWS::EC2::Subnet Properties: CidrBlock: 10.0.1.32/27 VpcId: Ref: Vpc1C211860B AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: "" MapPublicIpOnLaunch: false Tags: - Key: aws-cdk:subnet-name Value: private - Key: aws-cdk:subnet-type Value: Private - Key: Name Value: VpcPeersStack/Vpc1/privateSubnet1 Metadata: aws:cdk:path: VpcPeersStack/Vpc1/privateSubnet1/Subnet Vpc1privateSubnet1RouteTable339A93B3: Type: AWS::EC2::RouteTable Properties: VpcId: Ref: Vpc1C211860B Tags: - Key: Name Value: VpcPeersStack/Vpc1/privateSubnet1 Metadata: aws:cdk:path: VpcPeersStack/Vpc1/privateSubnet1/RouteTable Vpc1privateSubnet1RouteTableAssociation4FB53340: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: Vpc1privateSubnet1RouteTable339A93B3 SubnetId: Ref: Vpc1privateSubnet1Subnet41967AFD Metadata: aws:cdk:path: VpcPeersStack/Vpc1/privateSubnet1/RouteTableAssociation Vpc1privateSubnet1DefaultRoute4ACBA7B3: Type: AWS::EC2::Route Properties: RouteTableId: Ref: Vpc1privateSubnet1RouteTable339A93B3 DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: Ref: Vpc1publicSubnet1NATGateway06106699 Metadata: aws:cdk:path: VpcPeersStack/Vpc1/privateSubnet1/DefaultRoute Vpc1IGW15AE5E6B: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: VpcPeersStack/Vpc1 Metadata: aws:cdk:path: VpcPeersStack/Vpc1/IGW Vpc1VPCGW4C1BD07A: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: Ref: Vpc1C211860B InternetGatewayId: Ref: Vpc1IGW15AE5E6B Metadata: aws:cdk:path: VpcPeersStack/Vpc1/VPCGW DefaultSecurityGroup0from00000ICMPType829E2C81F: Type: AWS::EC2::SecurityGroupIngress Properties: IpProtocol: icmp CidrIp: 0.0.0.0/0 Description: Allow ping from anywhere FromPort: 8 GroupId: Fn::GetAtt: - Vpc07C831B30 - DefaultSecurityGroup ToPort: -1 Metadata: aws:cdk:path: VpcPeersStack/DefaultSecurityGroup0/from 0.0.0.0_0:ICMP Type 8 DefaultSecurityGroup1from00000ICMPType8D69AB703: Type: AWS::EC2::SecurityGroupIngress Properties: IpProtocol: icmp CidrIp: 0.0.0.0/0 Description: Allow ping from anywhere FromPort: 8 GroupId: Fn::GetAtt: - Vpc1C211860B - DefaultSecurityGroup ToPort: -1 Metadata: aws:cdk:path: VpcPeersStack/DefaultSecurityGroup1/from 0.0.0.0_0:ICMP Type 8 CDKMetadata: Type: AWS::CDK::Metadata Properties: Modules: aws-cdk=1.74.0,@aws-cdk/assets=1.74.0,@aws-cdk/aws-cloudwatch=1.74.0,@aws-cdk/aws-ec2=1.74.0,@aws-cdk/aws-events=1.74.0,@aws-cdk/aws-iam=1.74.0,@aws-cdk/aws-kms=1.74.0,@aws-cdk/aws-logs=1.74.0,@aws-cdk/aws-s3=1.74.0,@aws-cdk/aws-s3-assets=1.74.0,@aws-cdk/aws-ssm=1.74.0,@aws-cdk/cloud-assembly-schema=1.74.0,@aws-cdk/core=1.74.0,@aws-cdk/cx-api=1.74.0,@aws-cdk/region-info=1.74.0,jsii-runtime=node.js/v14.14.0 Metadata: aws:cdk:path: VpcPeersStack/CDKMetadata/Default Condition: CDKMetadataAvailable Conditions: CDKMetadataAvailable: Fn::Or: - Fn::Or: - Fn::Equals: - Ref: AWS::Region - ap-east-1 - Fn::Equals: - Ref: AWS::Region - ap-northeast-1 - Fn::Equals: - Ref: AWS::Region - ap-northeast-2 - Fn::Equals: - Ref: AWS::Region - ap-south-1 - Fn::Equals: - Ref: AWS::Region - ap-southeast-1 - Fn::Equals: - Ref: AWS::Region - ap-southeast-2 - Fn::Equals: - Ref: AWS::Region - ca-central-1 - Fn::Equals: - Ref: AWS::Region - cn-north-1 - Fn::Equals: - Ref: AWS::Region - cn-northwest-1 - Fn::Equals: - Ref: AWS::Region - eu-central-1 - Fn::Or: - Fn::Equals: - Ref: AWS::Region - eu-north-1 - Fn::Equals: - Ref: AWS::Region - eu-west-1 - Fn::Equals: - Ref: AWS::Region - eu-west-2 - Fn::Equals: - Ref: AWS::Region - eu-west-3 - Fn::Equals: - Ref: AWS::Region - me-south-1 - Fn::Equals: - Ref: AWS::Region - sa-east-1 - Fn::Equals: - Ref: AWS::Region - us-east-1 - Fn::Equals: - Ref: AWS::Region - us-east-2 - Fn::Equals: - Ref: AWS::Region - us-west-1 - Fn::Equals: - Ref: AWS::Region - us-west-2
cdk diff
command to see what changes can be applied:➜ ping-me-cdk-example$ cdk diff Stack VpcPeersStack Security Group Changes ┌───┬──────────────────────────────┬─────┬───────────┬─────────────────┐ │ │ Group │ Dir │ Protocol │ Peer │ ├───┼──────────────────────────────┼─────┼───────────┼─────────────────┤ │ + │ ${Vpc0.DefaultSecurityGroup} │ In │ ICMP 8--1 │ Everyone (IPv4) │ ├───┼──────────────────────────────┼─────┼───────────┼─────────────────┤ │ + │ ${Vpc1.DefaultSecurityGroup} │ In │ ICMP 8--1 │ Everyone (IPv4) │ └───┴──────────────────────────────┴─────┴───────────┴─────────────────┘ (NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299) Conditions [+] Condition CDKMetadata/Condition CDKMetadataAvailable: {"Fn::Or":[{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ca-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-northwest-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-central-1"]}]},{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-3"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"me-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"sa-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-2"]}]}]} Resources [+] AWS::EC2::VPC Vpc0 Vpc07C831B30 [+] AWS::EC2::Subnet Vpc0/publicSubnet1/Subnet Vpc0publicSubnet1SubnetB977A71E [+] AWS::EC2::RouteTable Vpc0/publicSubnet1/RouteTable Vpc0publicSubnet1RouteTable2012E33A [+] AWS::EC2::SubnetRouteTableAssociation Vpc0/publicSubnet1/RouteTableAssociation Vpc0publicSubnet1RouteTableAssociation0E1C3D4B [+] AWS::EC2::Route Vpc0/publicSubnet1/DefaultRoute Vpc0publicSubnet1DefaultRouteC03283FF [+] AWS::EC2::EIP Vpc0/publicSubnet1/EIP Vpc0publicSubnet1EIP16FED7DC [+] AWS::EC2::NatGateway Vpc0/publicSubnet1/NATGateway Vpc0publicSubnet1NATGateway40294DF4 [+] AWS::EC2::Subnet Vpc0/privateSubnet1/Subnet Vpc0privateSubnet1SubnetD6383522 [+] AWS::EC2::RouteTable Vpc0/privateSubnet1/RouteTable Vpc0privateSubnet1RouteTableB5C6777D [+] AWS::EC2::SubnetRouteTableAssociation Vpc0/privateSubnet1/RouteTableAssociation Vpc0privateSubnet1RouteTableAssociationC17661A1 [+] AWS::EC2::Route Vpc0/privateSubnet1/DefaultRoute Vpc0privateSubnet1DefaultRoute1EA0AEFE [+] AWS::EC2::InternetGateway Vpc0/IGW Vpc0IGW3080DF7F [+] AWS::EC2::VPCGatewayAttachment Vpc0/VPCGW Vpc0VPCGW9FBA9469 [+] AWS::EC2::VPC Vpc1 Vpc1C211860B [+] AWS::EC2::Subnet Vpc1/publicSubnet1/Subnet Vpc1publicSubnet1SubnetB43EFACE [+] AWS::EC2::RouteTable Vpc1/publicSubnet1/RouteTable Vpc1publicSubnet1RouteTable1C630681 [+] AWS::EC2::SubnetRouteTableAssociation Vpc1/publicSubnet1/RouteTableAssociation Vpc1publicSubnet1RouteTableAssociation4DA13984 [+] AWS::EC2::Route Vpc1/publicSubnet1/DefaultRoute Vpc1publicSubnet1DefaultRouteB4C85D62 [+] AWS::EC2::EIP Vpc1/publicSubnet1/EIP Vpc1publicSubnet1EIP5F1D9658 [+] AWS::EC2::NatGateway Vpc1/publicSubnet1/NATGateway Vpc1publicSubnet1NATGateway06106699 [+] AWS::EC2::Subnet Vpc1/privateSubnet1/Subnet Vpc1privateSubnet1Subnet41967AFD [+] AWS::EC2::RouteTable Vpc1/privateSubnet1/RouteTable Vpc1privateSubnet1RouteTable339A93B3 [+] AWS::EC2::SubnetRouteTableAssociation Vpc1/privateSubnet1/RouteTableAssociation Vpc1privateSubnet1RouteTableAssociation4FB53340 [+] AWS::EC2::Route Vpc1/privateSubnet1/DefaultRoute Vpc1privateSubnet1DefaultRoute4ACBA7B3 [+] AWS::EC2::InternetGateway Vpc1/IGW Vpc1IGW15AE5E6B [+] AWS::EC2::VPCGatewayAttachment Vpc1/VPCGW Vpc1VPCGW4C1BD07A [+] AWS::EC2::SecurityGroupIngress DefaultSecurityGroup0/from 0.0.0.0_0:ICMP Type 8 DefaultSecurityGroup0from00000ICMPType829E2C81F [+] AWS::EC2::SecurityGroupIngress DefaultSecurityGroup1/from 0.0.0.0_0:ICMP Type 8 DefaultSecurityGroup1from00000ICMPType8D69AB703
➜ ping-me-cdk-example$ cdk deploy --require-approval never VpcPeersStack: deploying... VpcPeersStack: creating CloudFormation changeset... [██████████████████████████████████████████████████████████] (30/30) ✅ VpcPeersStack Stack ARN: arn:aws:cloudformation:eu-west-1:REDACTED:stack/VpcPeersStack/1057bae0-2cc0-11eb-8cd5-0a517997c0b3
--require-approval
flag to never
to avoid manually confirming the creation of the Allow ping from anywhere
rules, which were deemed as potentially insecure by the CDK. We got the VPCs. Now, on to the EC2s and the peering connection itself:➜ ping-me-cdk-example$ touch lib/instance.ts
ping-me-cdk-example/lib/instance.ts
import * as cdk from '@aws-cdk/core'; import * as ec2 from '@aws-cdk/aws-ec2'; interface InstanceProps extends cdk.StackProps { vpcs: ec2.Vpc[]; // <--- a list of VPC objects required for the creation of the EC2 instance(s) } export class InstanceStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props: InstanceProps) { super(scope, id, props); // For each supplied VPC, create a Linux-based EC2 instance in the private subnet and attach the VPC's default security group to it props.vpcs.forEach((vpc, index) => { const instanceName = `Instance${index}`; const instanceResource = new ec2.BastionHostLinux(this, instanceName, { vpc, instanceName, securityGroup: ec2.SecurityGroup.fromSecurityGroupId(this, instanceName + 'SecurityGroup', vpc.vpcDefaultSecurityGroup), }); // Output the instance's private IP new cdk.CfnOutput(this, instanceName + 'PrivateIp', { value: instanceResource.instancePrivateIp, }); }); } }
➜ ping-me-cdk-example$ touch lib/peering.ts
ping-me-cdk-example/lib/peering.ts
import * as cdk from '@aws-cdk/core'; import * as ec2 from '@aws-cdk/aws-ec2'; interface PeeringProps extends cdk.StackProps { vpcs: [ec2.Vpc, ec2.Vpc]; // <--- a fixed-length array (a tuple type in TypeScript parlance) consisting of two VPC objects between which the peering connection will be made } export class PeeringStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props: PeeringProps) { super(scope, id, props); // Create the peering connection const peer = new ec2.CfnVPCPeeringConnection(this, 'Peer', { vpcId: props.vpcs[0].vpcId, peerVpcId: props.vpcs[1].vpcId }); // Add route from the private subnet of the first VPC to the second VPC over the peering connection // NB the below was taken from: https://stackoverflow.com/questions/62525195/adding-entry-to-route-table-with-cdk-typescript-when-its-private-subnet-alread props.vpcs[0].privateSubnets.forEach(({ routeTable: { routeTableId } }, index) => { new ec2.CfnRoute(this, 'RouteFromPrivateSubnetOfVpc1ToVpc2' + index, { destinationCidrBlock: props.vpcs[1].vpcCidrBlock, routeTableId, vpcPeeringConnectionId: peer.ref, }); }); // Add route from the private subnet of the second VPC to the first VPC over the peering connection props.vpcs[1].privateSubnets.forEach(({ routeTable: { routeTableId } }, index) => { new ec2.CfnRoute(this, 'RouteFromPrivateSubnetOfVpc2ToVpc1' + index, { destinationCidrBlock: props.vpcs[0].vpcCidrBlock, routeTableId, vpcPeeringConnectionId: peer.ref, }); }); } }
ping-me-cdk-example/bin/ping-me-cdk-example.ts
file to initialize our newly created classes:ping-me-cdk-example/bin/ping-me-cdk-example.ts
import * as cdk from '@aws-cdk/core'; import { VpcStack } from '../lib/vpc'; import { InstanceStack } from '../lib/instance'; import { PeeringStack } from '../lib/peering'; const app = new cdk.App(); // <--- you can read more about the App construct here: https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.App.html const vpcPeers = new VpcStack(app, 'VpcPeersStack', { vpcSetup: { cidrs: ['10.0.0.0/24', '10.0.1.0/24'], // <--- two non-overlapping CIDR ranges for our two VPCs maxAzs: 1, // <--- to keep the costs down, we'll stick to 1 availability zone per VPC (obviously, not something you'd want to do in production) }, }); // Create two EC2 instances, one in each VPC new InstanceStack(app, 'InstancePeersStack', { vpcs: vpcPeers.createdVpcs, }); // Establish a VPC Peering connection between the two VPCs new PeeringStack(app, 'PeeringStack', { vpcs: [vpcPeers.createdVpcs[0], vpcPeers.createdVpcs[1]], });
➜ ping-me-cdk-example$ cdk deploy --all --require-approval never VpcPeersStack VpcPeersStack: deploying... ✅ VpcPeersStack (no changes) Outputs: VpcPeersStack.ExportsOutputFnGetAttVpc07C831B30CidrBlockB8164F9E = 10.0.0.0/24 VpcPeersStack.ExportsOutputFnGetAttVpc07C831B30DefaultSecurityGroup52C351BF = sg-0dd8a9cd265dc8acb VpcPeersStack.ExportsOutputFnGetAttVpc1C211860BCidrBlock933A5AA8 = 10.0.1.0/24 VpcPeersStack.ExportsOutputFnGetAttVpc1C211860BDefaultSecurityGroup87C47BC2 = sg-0496c16092cdd8311 VpcPeersStack.ExportsOutputRefVpc07C831B304FE08623 = vpc-07277da5218b90290 VpcPeersStack.ExportsOutputRefVpc0privateSubnet1RouteTableB5C6777D52F53FE8 = rtb-005f39777bccd74f4 VpcPeersStack.ExportsOutputRefVpc0privateSubnet1SubnetD6383522ACB05B9B = subnet-0a018df57060948a4 VpcPeersStack.ExportsOutputRefVpc1C211860B64169B74 = vpc-0c5433d68b3f2f67c VpcPeersStack.ExportsOutputRefVpc1privateSubnet1RouteTable339A93B3DFC75FCA = rtb-02ca74736f4f0ea17 VpcPeersStack.ExportsOutputRefVpc1privateSubnet1Subnet41967AFDFF883DAB = subnet-048b1e861592d392c Stack ARN: arn:aws:cloudformation:eu-west-1:REDACTED:stack/VpcPeersStack/d91b71b0-2dbf-11eb-8c69-06b222f0b0a4 InstancePeersStack InstancePeersStack: deploying... InstancePeersStack: creating CloudFormation changeset... [██████████████████████████████████████████████████████████] (10/10) ✅ InstancePeersStack Outputs: InstancePeersStack.Instance0BastionHostId1959CA92 = i-0ca24549d1646cccd # <--- COPY THE ID OF YOUR SOURCE EC2 INSTANCE! InstancePeersStack.Instance0PrivateIp = 10.0.0.36 InstancePeersStack.Instance1BastionHostIdEF2AA144 = i-0fec2bdd51392974d InstancePeersStack.Instance1PrivateIp = 10.0.1.59 # <--- COPY THE PRIVATE IP OF YOUR DESTINATION EC2 INSTANCE! Stack ARN: arn:aws:cloudformation:eu-west-1:REDACTED:stack/InstancePeersStack/9e40f500-2dc0-11eb-aab0-0a253e5a178e PeeringStack PeeringStack: deploying... PeeringStack: creating CloudFormation changeset... [██████████████████████████████████████████████████████████] (5/5) ✅ PeeringStack Stack ARN: arn:aws:cloudformation:eu-west-1:REDACTED:stack/PeeringStack/15e96f10-2dc1-11eb-ae91-0643678755c5
Validation
i-0ca24549d1646cccd
) and the private IP of the destination EC2 instance (10.0.1.59
) for appropriate values before running the below:➜ ping-me-cdk-example$ aws ssm send-command \ --document-name "AWS-RunShellScript" \ --document-version "1" \ --targets '[{"Key":"InstanceIds","Values":["i-0ca24549d1646cccd"]}]' \ --parameters '{"workingDirectory":[""],"executionTimeout":["3600"],"commands":["ping 10.0.1.59 -c 3"]}' \ --timeout-seconds 600 \ --max-concurrency "50" \ --max-errors "0" { "Command": { "CommandId": "e2171883-d9d1-478c-9ad2-2c7c51ca6c2e", "DocumentName": "AWS-RunShellScript", "DocumentVersion": "1", "Comment": "", "ExpiresAfter": "2020-11-23T22:04:17.410000+01:00", "Parameters": { "commands": [ "ping 10.0.1.59 -c 3" ], "executionTimeout": [ "3600" ], "workingDirectory": [ "" ] }, "InstanceIds": [], "Targets": [ { "Key": "InstanceIds", "Values": [ "i-0ca24549d1646cccd" ] } ], "RequestedDateTime": "2020-11-23T20:54:17.410000+01:00", "Status": "Pending", "StatusDetails": "Pending", "OutputS3BucketName": "", "OutputS3KeyPrefix": "", "MaxConcurrency": "50", "MaxErrors": "0", "TargetCount": 0, "CompletedCount": 0, "ErrorCount": 0, "DeliveryTimedOutCount": 0, "ServiceRole": "", "NotificationConfig": { "NotificationArn": "", "NotificationEvents": [], "NotificationType": "" }, "CloudWatchOutputConfig": { "CloudWatchLogGroupName": "", "CloudWatchOutputEnabled": false }, "TimeoutSeconds": 600 } }
e2171883-d9d1-478c-9ad2-2c7c51ca6c2e
) and the ID of the source EC2 instance (i-0ca24549d1646cccd
) for appropriate values before running the below:➜ ping-me-cdk-example$ aws ssm get-command-invocation --command-id e2171883-d9d1-478c-9ad2-2c7c51ca6c2e --instance-id i-0ca24549d1646cccd { "CommandId": "e2171883-d9d1-478c-9ad2-2c7c51ca6c2e", "InstanceId": "i-0ca24549d1646cccd", "Comment": "", "DocumentName": "AWS-RunShellScript", "DocumentVersion": "1", "PluginName": "aws:runShellScript", "ResponseCode": 0, "ExecutionStartDateTime": "2020-11-23T19:54:17.876Z", "ExecutionElapsedTime": "PT2.032S", "ExecutionEndDateTime": "2020-11-23T19:54:19.876Z", "Status": "Success", "StatusDetails": "Success", "StandardOutputContent": "PING 10.0.1.59 (10.0.1.59) 56(84) bytes of data.\n64 bytes from 10.0.1.59: icmp_seq=1 ttl=255 time=0.140 ms\n64 bytes from 10.0.1.59: icmp_seq=2 ttl=255 time=0.152 ms\n64 bytes from 10.0.1.59: icmp_seq=3 ttl=255 time=0.138 ms\n\n--- 10.0.1.59 ping statistics ---\n3 packets transmitted, 3 received, 0% packet loss, time 2025ms\nrtt min/avg/max/mdev = 0.138/0.143/0.152/0.011 ms\n", "StandardOutputUrl": "", "StandardErrorContent": "", "StandardErrorUrl": "", "CloudWatchOutputConfig": { "CloudWatchLogGroupName": "", "CloudWatchOutputEnabled": false } }
3 packets transmitted, 3 received, 0% packet loss
, Woop Woop!
Cleanup
y
for yes:➜ ping-me-cdk-example$ cdk destroy --all Are you sure you want to delete: PeeringStack, InstancePeersStack, VpcPeersStack (y/n)? y PeeringStack: destroying... 21:15:12 | DELETE_IN_PROGRESS | AWS::CloudFormation::Stack | PeeringStack ✅ PeeringStack: destroyed InstancePeersStack: destroying... 21:16:03 | DELETE_IN_PROGRESS | AWS::CloudFormation::Stack | InstancePeersStack ✅ InstancePeersStack: destroyed VpcPeersStack: destroying... 21:17:01 | DELETE_IN_PROGRESS | AWS::CloudFormation::Stack | VpcPeersStack 21:18:55 | DELETE_IN_PROGRESS | AWS::EC2::InternetGateway | Vpc1/IGW 21:18:55 | DELETE_IN_PROGRESS | AWS::EC2::VPC | Vpc1 ✅ VpcPeersStack: destroyed