06.05, Katowice AWS Summit Poland
23 min readPart 3/4

Connecting Amazon VPCs via AWS Site-to-Site VPN

A practical example of pinging Amazon VPCs connected via AWS Site-to-Site VPN, built and deployed using the AWS Cloud Development Kit.



Thanks to AWS Site-to-Site VPN you can establish a secure connection over insecure infrastructure (e.g. the public internet) between a VPC and your on-premises data center. With such a connection in place, you can communicate with your EC2 instances as if they were within your existing corporate network.

It is much cheaper and quicker to establish this option rather than AWS Direct Connect, although it is less reliable in terms of performance and has a lower cap for bandwidth. It’s not uncommon to have both in place, with AWS Site-to-Site VPN being the backup option.

Furthermore, if you have multiple data centers, each with an AWS Site-to-Site VPN connection to a central Virtual Private Gateway (that’s the logical construct on which the VPN connection is terminated), you can provide secure communications between those in a hub-and-spoke model using something that AWS advertises as VPN CloudHub.

As far as billing is concerned, you pay for each hour during which the VPN connection is in the available state (thus, to avoid unnecessary costs you should terminate it whenever it’s not in use), plus the usual data egress fees.

Implementation and validation

As mentioned in the overview section, in most cases you’d use AWS Site-to-Site VPN to connect your on-premises data center to AWS. However, for the sake of simplicity, we’re going to connect two VPCs together using the Software VPN-to-AWS Managed VPN approach.

A rudimentary diagram of the complete solution

We’ll reuse the VpcStack and InstanceStack classes that we’ve created in part 1, so the only class we’re missing is the one for the Customer Gateway Device (CGD):

ping-me-cdk-example/lib/instance.ts ts
import * as cdk from '@aws-cdk/core';import * as ec2 from '@aws-cdk/aws-ec2';import * as iam from '@aws-cdk/aws-iam'; // <- this module is not available from the start; remember to import it: `npm install @aws-cdk/aws-iam`interface CustomerGatewayDeviceProps extends cdk.StackProps {  vpc: ec2.Vpc; // <- the VPC in which the Customer Gateway Device will be created}export class CustomerGatewayDeviceStack extends cdk.Stack {  constructor(scope: cdk.Construct, id: string, props: CustomerGatewayDeviceProps) {    super(scope, id, props);    // Prepare the user data to be applied to the Windows EC2 instance on its initial boot-up    const userData = ec2.UserData.forWindows();    userData.addCommands(      // The below PowerShell commands were taken from here: https://github.com/ACloudGuru-Resources/course-aws-certified-advanced-networking-specialty/blob/3a687ba5c70d507a53743037b8f1c5a52d05d357/SteveResources/OnPremNet.yaml#L126      '<powershell>',      '# Disable Internet Explorer Enhanced Security Configuration for Administrators',      'Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}" -Name "IsInstalled" -Value 0 -Force',      'Stop-Process -Name iexplore -ErrorAction SilentlyContinue',      '# Begin configuration for VPN services',      'Set-NetAdapterAdvancedProperty -DisplayName "IPv4 Checksum Offload" -DisplayValue "Disabled"',      'Set-NetAdapterAdvancedProperty -DisplayName "TCP Checksum Offload (IPv4)" -DisplayValue "Disabled"',      'Set-NetAdapterAdvancedProperty -DisplayName "UDP Checksum Offload (IPv4)" -DisplayValue "Disabled"',      'Invoke-WebRequest https://steveatacg.s3-us-west-1.amazonaws.com/advnetspec/Win2019VPNServerConfig.xml -OutFile c:\config.xml',      'Install-WindowsFeature -ConfigurationFilePath c:\config.xml -computername $env:COMPUTERNAME -Restart',      'Install-RemoteAccess -VpnType VpnS2S',      '</powershell>',    )    // Create an IAM role allowing the instance to be managed by SSM    const ssmRoleForEc2 = new iam.Role(this, 'SsmRoleForEc2', {      assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),      managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore')],    });    // Create an EC2 instance to serve as the software VPN    const cgd = new ec2.Instance(this, 'CGW', {      vpc: props.vpc,      instanceType: new ec2.InstanceType('t2.micro'),      machineImage: ec2.MachineImage.fromSSMParameter('/aws/service/ami-windows-latest/Windows_Server-2019-English-Full-Base', ec2.OperatingSystemType.WINDOWS, userData), // <- use the latest Amazon Machine Image for Windows Server 2019      vpcSubnets: {        subnetType: ec2.SubnetType.PUBLIC, // <- place the instance in a public subnet      },      role: ssmRoleForEc2, // <- use the created earlier IAM role as the instance profile      sourceDestCheck: false, // <- make sure the source/destination check is turned off    });    // Output the instance's public IP    new cdk.CfnOutput(this, 'CustomerGatewayDevicePublicIp', {      value: cgd.instancePublicIp,    });  }}

Since the @aws-cdk/aws-iam module was not imported during the cdk initialization, let’s install it now:

  ping-me-cdk-example$ npm install @aws-cdk/aws-iam@1.74.0npm WARN ping-me-cdk-example@0.1.0 No repository field.npm WARN ping-me-cdk-example@0.1.0 No license field.+ @aws-cdk/aws-iam@1.74.0updated 1 package and audited 847 packages in 5.085s27 packages are looking for funding  run `npm fund` for detailsfound 0 vulnerabilities

We explicitly chose version 1.74.0 (the exact same as for our @aws-cdk/core and @aws-cdk/aws-ec2 modules) to avoid the possibility of seeing the Argument of type 'this' is not assignable to parameter of type 'Construct' error.

Since we got all the pieces of the puzzle, let’s start putting them together.

First, we’ll initialize the VPN source VPC (that’s the one that mocks an on-prem network) and place a Customer Gateway Device (a software VPN in our case) in it:

ping-me-cdk-example/bin/ping-me-cdk-example.ts ts
import * as cdk from '@aws-cdk/core';import { VpcStack } from '../lib/vpc';import { InstanceStack } from '../lib/instance';import { PeeringStack } from '../lib/peering';import { CustomerGatewayDeviceStack } from '../lib/cgd'; // <- added in part 2const 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/** * CODE FROM "Ping Me! (Part 1: VPC Peering Using CDK)" WAS REMOVED FOR VISIBILITY */// Create a VPN source VPCconst vpcVpnSource = new VpcStack(app, 'VpcVpnSourceStack', {  vpcSetup: {    cidrs: ['10.0.2.0/24'],    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 a Customer Gateway Device in the VPN source VPCnew CustomerGatewayDeviceStack(app, 'CustomerGatewayDeviceStack', {  vpc: vpcVpnSource.createdVpcs[0],});

Now, we can deploy these two stacks. And since VpcVpnSourceStack is a dependency of CustomerGatewayDeviceStack we can just call the latter in our command and still both are going to be built out:

  ping-me-cdk-example$ cdk deploy CustomerGatewayDeviceStack --require-approval neverIncluding dependency stacks: VpcVpnSourceStackVpcVpnSourceStackVpcVpnSourceStack: deploying...VpcVpnSourceStack: creating CloudFormation changeset...[██████████████████████████████████████████████████████████] (16/16)   VpcVpnSourceStackOutputs:VpcVpnSourceStack.ExportsOutputFnGetAttVpc07C831B30CidrBlockB8164F9E = 10.0.2.0/24VpcVpnSourceStack.ExportsOutputRefVpc07C831B304FE08623 = vpc-0d8d8118923b40b85VpcVpnSourceStack.ExportsOutputRefVpc0publicSubnet1SubnetB977A71E8C9155C7 = subnet-037467ea871b103c1Stack ARN:arn:aws:cloudformation:eu-west-1:REDACTED:stack/VpcVpnSourceStack/f9d46c70-3005-11eb-8c3a-06fab0925578CustomerGatewayDeviceStackCustomerGatewayDeviceStack: deploying...CustomerGatewayDeviceStack: creating CloudFormation changeset...[██████████████████████████████████████████████████████████] (6/6)   CustomerGatewayDeviceStackOutputs:CustomerGatewayDeviceStack.CustomerGatewayDevicePublicIp = 52.51.199.29 # <- COPY THE PUBLIC IP OF THE CUSTOMER GATEWAY DEVICE (CGD)Stack ARN:arn:aws:cloudformation:eu-west-1:REDACTED:stack/CustomerGatewayDeviceStack/665a19d0-3006-11eb-9492-0a91d0eaa80f

Let’s grab the public IP of the CGD from the above output, or by using AWS CLI (like so: aws cloudformation describe-stacks --stack-name CustomerGatewayDeviceStack --query "Stacks[].Outputs[].OutputValue") and move on to creating a VPN destination VPC, with a Site-to-Site VPN connection to the VPN source VPC already in place, and an EC2 instance that’ll serve as our ping destination.

ping-me-cdk-example/bin/ping-me-cdk-example.ts ts
/** * ALL PRECEDING CODE WAS REMOVED FOR VISIBILITY */const vpcVpnDestination = new VpcStack(app, 'VpcVpnDestinationStack', {  vpcSetup: {    cidrs: ['10.0.3.0/24'],    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)    vpnConnections: {      toOnPrem: {        ip: '52.51.199.29', // <- grab this from the outputs of CustomerGatewayDeviceStack, e.g.: aws cloudformation describe-stacks --stack-name CustomerGatewayDeviceStack --query "Stacks[].Outputs[].OutputValue"        staticRoutes: [          vpcVpnSource.createdVpcs[0].vpcCidrBlock,        ]      }    }  },});new InstanceStack(app, 'InstanceVpnDestinationStack', {  vpcs: vpcVpnDestination.createdVpcs,});
  ping-me-cdk-example$ cdk deploy InstanceVpnDestinationStack --require-approval neverIncluding dependency stacks: VpcVpnDestinationStack, VpcVpnSourceStackVpcVpnSourceStackVpcVpnSourceStack: deploying...   VpcVpnSourceStack (no changes)Outputs:VpcVpnSourceStack.ExportsOutputFnGetAttVpc07C831B30CidrBlockB8164F9E = 10.0.2.0/24VpcVpnSourceStack.ExportsOutputRefVpc07C831B304FE08623 = vpc-0d8d8118923b40b85VpcVpnSourceStack.ExportsOutputRefVpc0publicSubnet1SubnetB977A71E8C9155C7 = subnet-037467ea871b103c1Stack ARN:arn:aws:cloudformation:eu-west-1:REDACTED:stack/VpcVpnSourceStack/f9d46c70-3005-11eb-8c3a-06fab0925578VpcVpnDestinationStackVpcVpnDestinationStack: deploying...VpcVpnDestinationStack: creating CloudFormation changeset...[██████████████████████████████████████████████████████████] (22/22)   VpcVpnDestinationStackOutputs:VpcVpnDestinationStack.ExportsOutputFnGetAttVpc07C831B30DefaultSecurityGroup52C351BF = sg-0e750a0ff54ed08aaVpcVpnDestinationStack.ExportsOutputRefVpc0privateSubnet1SubnetD6383522ACB05B9B = subnet-0149bc2a09ea2bc34Stack ARN:arn:aws:cloudformation:eu-west-1:REDACTED:stack/VpcVpnDestinationStack/af2f9e90-3007-11eb-93e5-0a4cff3749d5InstanceVpnDestinationStackInstanceVpnDestinationStack: deploying...InstanceVpnDestinationStack: creating CloudFormation changeset...[██████████████████████████████████████████████████████████] (6/6)   InstanceVpnDestinationStackOutputs:InstanceVpnDestinationStack.Instance0BastionHostId1959CA92 = i-02e376354bcb4b094InstanceVpnDestinationStack.Instance0PrivateIp = 10.0.3.49Stack ARN:arn:aws:cloudformation:eu-west-1:REDACTED:stack/InstanceVpnDestinationStack/c2a92a30-3008-11eb-a26f-0639b3b50e04

The necessary infrastructure is standing, but we still need to configure the CGD. To accomplish that we’ll head over to the AWS Console and download a config file that was generated for us:

AWS download VPN configuration
AWS Console -> VPC -> Site-to-Site VPN Connections -> select our new connection -> click on "Download Configuration"
AWS choose the type of the Customer Gateway Device

In this text file we’re interested in the lines 111 to 129 containing two scripts we’ll need to execute on the CGD. Those should look something like this:

! Script for Tunnel 1:netsh advfirewall consec add rule Name="vgw-0e89a3295d6b100c2 Tunnel 1" ^Enable=Yes Profile=any Type=Static Mode=Tunnel ^LocalTunnelEndpoint=[Windows_Server_Private_IP_address] ^RemoteTunnelEndpoint=34.253.60.42 Endpoint1=[Your_Static_Route_IP_Prefix] ^Endpoint2=[Your_VPC_CIDR_Block] Protocol=Any Action=RequireInClearOut ^Auth1=ComputerPSK Auth1PSK=iKV4pvEVAxVk6wKeVma38F8ZDxVfPhNA ^QMSecMethods=ESP:SHA1-AES128+60min+100000kb ^ExemptIPsecProtectedConnections=No ApplyAuthz=No QMPFS=dhgroup2! Script for Tunnel 2:netsh advfirewall consec add rule Name="vgw-0e89a3295d6b100c2 Tunnel 2" ^Enable=Yes Profile=any Type=Static Mode=Tunnel ^LocalTunnelEndpoint=[Windows_Server_Private_IP_address] ^RemoteTunnelEndpoint=52.50.166.48 Endpoint1=[Your_Static_Route_IP_Prefix] ^Endpoint2=[Your_VPC_CIDR_Block] Protocol=Any Action=RequireInClearOut ^Auth1=ComputerPSK Auth1PSK=PAoYUDzM_V93hRcJxvVkMCV6VjpWFtKt ^QMSecMethods=ESP:SHA1-AES128+60min+100000kb ^ExemptIPsecProtectedConnections=No ApplyAuthz=No QMPFS=dhgroup2

In there:

netsh advfirewall consec add rule Name=\"vgw-06fab417114297dd1 Tunnel 1\" Enable=Yes Profile=any Type=Static Mode=Tunnel LocalTunnelEndpoint=10.0.2.18 RemoteTunnelEndpoint=34.249.222.228 Endpoint1=10.0.2.0/24 Endpoint2=10.0.3.0/24 Protocol=Any Action=RequireInClearOut Auth1=ComputerPSK Auth1PSK=F_BjSjPg8ctE6XeX183INrtAPxkaktXm QMSecMethods=ESP:SHA1-AES128+60min+100000kb ExemptIPsecProtectedConnections=No ApplyAuthz=No QMPFS=dhgroup2

We’ll run them on the Windows instance remotely using SSM.

If you’re following along, be sure to swap the ID of the Windows instance (i-03dd90e66a0e6a145) for the appropriate value before running the below:

  ping-me-cdk-example$ aws ssm send-command --document-name "AWS-RunPowerShellScript" \--document-version "1" \--targets '[{"Key":"InstanceIds","Values":["i-03dd90e66a0e6a145"]}]' \--parameters '{"workingDirectory":[""],"executionTimeout":["3600"],"commands":["netsh advfirewall consec add rule Name=\"vgw-06fab417114297dd1 Tunnel 1\" Enable=Yes Profile=any Type=Static Mode=Tunnel LocalTunnelEndpoint=10.0.2.18 RemoteTunnelEndpoint=34.249.222.228 Endpoint1=10.0.2.0/24 Endpoint2=10.0.3.0/24 Protocol=Any Action=RequireInClearOut Auth1=ComputerPSK Auth1PSK=F_BjSjPg8ctE6XeX183INrtAPxkaktXm QMSecMethods=ESP:SHA1-AES128+60min+100000kb ExemptIPsecProtectedConnections=No ApplyAuthz=No QMPFS=dhgroup2"]}' \--timeout-seconds 600 \--max-concurrency "50" \--max-errors "0"{    "Command": {        "CommandId": "ab32f5e8-78c9-4e5b-95d4-944e02814682",        "DocumentName": "AWS-RunPowerShellScript",        "DocumentVersion": "1",        "Comment": "",        "ExpiresAfter": "2020-11-26T19:27:48.114000+01:00",        "Parameters": {            "commands": ["netsh advfirewall consec add rule Name=\"vgw-06fab417114297dd1 Tunnel 1\" Enable=Yes Profile=any Type=Static Mode=Tunnel LocalTunnelEndpoint=10.0.2.18 RemoteTunnelEndpoint=34.249.222.228 Endpoint1=10.0.2.0/24 Endpoint2=10.0.3.0/24 Protocol=Any Action=RequireInClearOut Auth1=ComputerPSK Auth1PSK=F_BjSjPg8ctE6XeX183INrtAPxkaktXm QMSecMethods=ESP:SHA1-AES128+60min+100000kb ExemptIPsecProtectedConnections=No ApplyAuthz=No QMPFS=dhgroup2"],            "executionTimeout": ["3600"],            "workingDirectory": [""]        },        "InstanceIds": [],        "Targets": [            {                "Key": "InstanceIds",                "Values": [                    "i-03dd90e66a0e6a145"                ]            }        ],        "RequestedDateTime": "2020-11-26T18:17:48.114000+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    }}

Now, let’s check whether that succeeded using AWS CLI’s aws ssm get-command-invocation command.

Again, if you’re following along, be sure to swap the command ID (ab32f5e8-78c9-4e5b-95d4-944e02814682) and the ID of the Windows instance (i-03dd90e66a0e6a145) for appropriate values before running the below:

  ping-me-cdk-example$ aws ssm get-command-invocation --command-id ab32f5e8-78c9-4e5b-95d4-944e02814682 --instance-id i-03dd90e66a0e6a145{    "CommandId": "ab32f5e8-78c9-4e5b-95d4-944e02814682",    "InstanceId": "i-03dd90e66a0e6a145",    "Comment": "",    "DocumentName": "AWS-RunPowerShellScript",    "DocumentVersion": "1",    "PluginName": "aws:runPowerShellScript",    "ResponseCode": 0,    "ExecutionStartDateTime": "2020-11-26T17:17:49.795Z",    "ExecutionElapsedTime": "PT1.96S",    "ExecutionEndDateTime": "2020-11-26T17:17:50.795Z",    "Status": "Success",    "StatusDetails": "Success",    "StandardOutputContent": "Ok.\r\n\r\n",    "StandardOutputUrl": "",    "StandardErrorContent": "",    "StandardErrorUrl": "",    "CloudWatchOutputConfig": {        "CloudWatchLogGroupName": "",        "CloudWatchOutputEnabled": false    }}

All’s A-OKAY, so let’s repeat these two steps for the second script:

  ping-me-cdk-example$ aws ssm send-command --document-name "AWS-RunPowerShellScript" \--document-version "1" \--targets '[{"Key":"InstanceIds","Values":["i-03dd90e66a0e6a145"]}]' \--parameters '{"workingDirectory":[""],"executionTimeout":["3600"],"commands":["netsh advfirewall consec add rule Name=\"vgw-06fab417114297dd1 Tunnel 2\" Enable=Yes Profile=any Type=Static Mode=Tunnel LocalTunnelEndpoint=10.0.2.18 RemoteTunnelEndpoint=54.77.85.166 Endpoint1=10.0.2.0/24 Endpoint2=10.0.3.0/24 Protocol=Any Action=RequireInClearOut Auth1=ComputerPSK Auth1PSK=sPGbITqCkW.PSrwuhlycnn6CgFFbjS0w QMSecMethods=ESP:SHA1-AES128+60min+100000kb ExemptIPsecProtectedConnections=No ApplyAuthz=No QMPFS=dhgroup2"]}' \--timeout-seconds 600 \--max-concurrency "50" \--max-errors "0"{    "Command": {        "CommandId": "dd7bb695-51e2-41eb-a898-eec8fc4562f4",        "DocumentName": "AWS-RunPowerShellScript",        "DocumentVersion": "1",        "Comment": "",        "ExpiresAfter": "2020-11-26T19:30:10.955000+01:00",        "Parameters": {            "commands": [                "netsh advfirewall consec add rule Name=\"vgw-06fab417114297dd1 Tunnel 2\" Enable=Yes Profile=any Type=Static Mode=Tunnel LocalTunnelEndpoint=10.0.2.18 RemoteTunnelEndpoint=54.77.85.166 Endpoint1=10.0.2.0/24 Endpoint2=10.0.3.0/24 Protocol=Any Action=RequireInClearOut Auth1=ComputerPSK Auth1PSK=sPGbITqCkW.PSrwuhlycnn6CgFFbjS0w QMSecMethods=ESP:SHA1-AES128+60min+100000kb ExemptIPsecProtectedConnections=No ApplyAuthz=No QMPFS=dhgroup2"            ],            "executionTimeout": [                "3600"            ],            "workingDirectory": [                ""            ]        },        "InstanceIds": [],        "Targets": [            {                "Key": "InstanceIds",                "Values": [                    "i-03dd90e66a0e6a145"                ]            }        ],        "RequestedDateTime": "2020-11-26T18:20:10.955000+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    }}
  ping-me-cdk-example$ aws ssm get-command-invocation --command-id dd7bb695-51e2-41eb-a898-eec8fc4562f4 --instance-id i-03dd90e66a0e6a145{    "CommandId": "dd7bb695-51e2-41eb-a898-eec8fc4562f4",    "InstanceId": "i-03dd90e66a0e6a145",    "Comment": "",    "DocumentName": "AWS-RunPowerShellScript",    "DocumentVersion": "1",    "PluginName": "aws:runPowerShellScript",    "ResponseCode": 0,    "ExecutionStartDateTime": "2020-11-26T17:20:11.557Z",    "ExecutionElapsedTime": "PT0.647S",    "ExecutionEndDateTime": "2020-11-26T17:20:11.557Z",    "Status": "Success",    "StatusDetails": "Success",    "StandardOutputContent": "Ok.\r\n\r\n",    "StandardOutputUrl": "",    "StandardErrorContent": "",    "StandardErrorUrl": "",    "CloudWatchOutputConfig": {        "CloudWatchLogGroupName": "",        "CloudWatchOutputEnabled": false    }}

Another A-OKAY, so let’s ping the EC2 instance in the VPN destination VPC from the Windows instance in the VPN source VPC to make sure the Site-to-Site VPN connection is working as expected.

Again, if you’re following along, be sure to swap the ID of the Windows instance (i-03dd90e66a0e6a145) and the private IP of the Linux instance for appropriate values before running the below:

aws ssm send-command --document-name "AWS-RunPowerShellScript" --document-version "1" --targets '[{"Key":"InstanceIds","Values":["i-03dd90e66a0e6a145"]}]' --parameters '{"workingDirectory":[""],"executionTimeout":["3600"],"commands":["ping 10.0.3.49"]}' --timeout-seconds 600 --max-concurrency "50" --max-errors "0"{    "Command": {        "CommandId": "09785d32-8b7e-4f24-9581-90aed774aa7d",        "DocumentName": "AWS-RunPowerShellScript",        "DocumentVersion": "1",        "Comment": "",        "ExpiresAfter": "2020-11-26T20:16:40.640000+01:00",        "Parameters": {            "commands": ["ping 10.0.3.49"],            "executionTimeout": ["3600"],            "workingDirectory": [""]        },        "InstanceIds": [],        "Targets": [            {                "Key": "InstanceIds",                "Values": [                    "i-03dd90e66a0e6a145"                ]            }        ],        "RequestedDateTime": "2020-11-26T19:06:40.640000+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    }}

Let’s see the result’s output:

  ping-me-cdk-example$ aws ssm get-command-invocation --command-id 09785d32-8b7e-4f24-9581-90aed774aa7d --instance-id i-03dd90e66a0e6a145{    "CommandId": "09785d32-8b7e-4f24-9581-90aed774aa7d",    "InstanceId": "i-03dd90e66a0e6a145",    "Comment": "",    "DocumentName": "AWS-RunPowerShellScript",    "DocumentVersion": "1",    "PluginName": "aws:runPowerShellScript",    "ResponseCode": 0,    "ExecutionStartDateTime": "2020-11-26T18:06:41.265Z",    "ExecutionElapsedTime": "PT7.529S",    "ExecutionEndDateTime": "2020-11-26T18:06:48.265Z",    "Status": "Success",    "StatusDetails": "Success",    "StandardOutputContent": "\r\nPinging 10.0.3.49 with 32 bytes of data:\r\nRequest timed out.\r\nReply from 10.0.3.49: bytes=32 time=1ms TTL=254\r\nReply from 10.0.3.49: bytes=32 time<1ms TTL=254\r\nReply from 10.0.3.49: bytes=32 time<1ms TTL=254\r\n\r\nPing statistics for 10.0.3.49:\r\n    Packets: Sent = 4, Received = 3, Lost = 1 (25% loss),\r\nApproximate round trip times in milli-seconds:\r\n    Minimum = 0ms, Maximum = 1ms, Average = 0ms\r\n",    "StandardOutputUrl": "",    "StandardErrorContent": "",    "StandardErrorUrl": "",    "CloudWatchOutputConfig": {        "CloudWatchLogGroupName": "",        "CloudWatchOutputEnabled": false    }}

The first ping was lost, the other three came back. It’s perfectly valid behavior since by default the customer gateway device must bring up the VPN tunnels by generating traffic into AWS.

Wait, tunnels? Yep, AWS Site-to-site VPN creates two tunnels from the customer gateway device, and they operate in an active/passive failover configuration. You can check their status in the AWS Console:

AWS, the state of the VPN tunnels before the ping
Before the ping
AWS, the state of the VPN tunnels after the ping
After the ping

Cleanup

For the sake of our wallets, let’s promptly destroy the current infrastructure before moving on. When prompted, type y for yes:

➜  ping-me-cdk-example$ cdk destroy --allAre you sure you want to delete: InstanceVpnDestinationStack, VpcVpnDestinationStack, PeeringStack, InstancePeersStack, CustomerGatewayDeviceStack, VpcVpnSourceStack, VpcPeersStack (y/n)? yInstanceVpnDestinationStack: destroying...19:15:58 | DELETE_IN_PROGRESS   | AWS::CloudFormation::Stack | InstanceVpnDestinationStack ✅  InstanceVpnDestinationStack: destroyedVpcVpnDestinationStack: destroying...19:16:55 | DELETE_IN_PROGRESS   | AWS::CloudFormation::Stack            | VpcVpnDestinationStack19:18:39 | DELETE_IN_PROGRESS   | AWS::EC2::InternetGateway             | Vpc0/IGW19:18:39 | DELETE_IN_PROGRESS   | AWS::EC2::VPC                         | Vpc0 ✅  VpcVpnDestinationStack: destroyedPeeringStack: destroying... ✅  PeeringStack: destroyedInstancePeersStack: destroying... ✅  InstancePeersStack: destroyedCustomerGatewayDeviceStack: destroying...19:19:00 | DELETE_IN_PROGRESS   | AWS::CloudFormation::Stack | CustomerGatewayDeviceStack19:19:52 | DELETE_IN_PROGRESS   | AWS::IAM::Role            | SsmRoleForEc2 ✅  CustomerGatewayDeviceStack: destroyedVpcVpnSourceStack: destroying...19:19:58 | DELETE_IN_PROGRESS   | AWS::CloudFormation::Stack            | VpcVpnSourceStack ✅  VpcVpnSourceStack: destroyedVpcPeersStack: destroying... ✅  VpcPeersStack: destroyed

We’ve already pinged our VPCs over VPC Peering and just a moment ago we’ve seen AWS Site-to-Site VPN in action. Hence, all that’s left is to take AWS Transit Gateway for a spin. Please remember that all the code is available on GitHub.

Let's talk about your project

We'd love to answer your questions and help you thrive in the cloud.