Site to Site VPN creates a private tunnel with a remote destination for transferring data securely. AWS provides managed VPN solution but if you want to configure NAT on top of it, you need to provision additional instance and make it NAT compatible. AWS provides a documentation on how to configure NAT for managed VPN

However, if the VPN solution is not managed then you can follow this tutorial to setup NAT traversing on your custom VPN CIDR. For this, I will use Openswan, an IPSec VPN for Linux.

We will use NAT to eliminate CIDR overlapping with a remote destination and alter the source IP.

Topology

<img class="aligncenter wp-image-895 size-full" src="/uploads/2021/08/SiteToSiteVPN-rev4.png" alt="" width="813" height="406" srcset="/uploads/2021/08/SiteToSiteVPN-rev4.png 813w, /uploads/2021/08/SiteToSiteVPN-rev4-768x384.png 768w" sizes="(max-width: 813px) 100vw, 813px" />

Overview

Suppose in remote/customer end, the CIDR block is 192.168.0.0/16. First, we will connect with that CIDR block from our AWS side.

In AWS, create a new VPC with CIDR 10.0.0.0/16.

<img class="aligncenter size-full wp-image-878" src="/uploads/2021/08/site-to-site-1.png" alt="" width="1029" height="47" srcset="/uploads/2021/08/site-to-site-1.png 1029w, /uploads/2021/08/site-to-site-1-768x35.png 768w" sizes="(max-width: 1029px) 100vw, 1029px" />

Create an internet gateway (IGW) & attach it with that VPC.

<img class="aligncenter size-full wp-image-879" src="/uploads/2021/08/site-to-site-2.png" alt="" width="972" height="44" srcset="/uploads/2021/08/site-to-site-2.png 972w, /uploads/2021/08/site-to-site-2-768x35.png 768w" sizes="(max-width: 972px) 100vw, 972px" />

Create two subnets: 10.100.0.0/24 & 10.100.1.0/24. One for the general instances and another one dedicated to NAT instance.

<img class="aligncenter size-full wp-image-880" src="/uploads/2021/08/site-to-site-3.png" alt="" width="976" height="59" srcset="/uploads/2021/08/site-to-site-3.png 976w, /uploads/2021/08/site-to-site-3-768x46.png 768w" sizes="(max-width: 976px) 100vw, 976px" />

Now provision two EC2 instances in those subnets. I used Amazon Linux 2 as AMI and t2.micro as instance type. Also attach elastic IP addresses in both instances. Make sure to disable source and destination check of the instance that you provisioned in 10.100.1.0/24 subnet because we will use that instance as NAT.

As for the security group of NAT instance, allow UDP port 4500 & 500 and TCP 4500 from remote public IP.

Now create two route tables. For the first route table, associate the subnet 10.100.0.0/24

<img class="aligncenter wp-image-899 size-full" src="/uploads/2021/08/site-to-site-6-1.png" alt="" width="1058" height="398" srcset="/uploads/2021/08/site-to-site-6-1.png 1058w, /uploads/2021/08/site-to-site-6-1-768x289.png 768w" sizes="(max-width: 1058px) 100vw, 1058px" />

Create the following routes. Note: Here eni-* is the network interface id of the NAT instance and destination is the remote private subnet. So all incoming traffic from 192.168.0.0/16 subnet will be transferred to the NAT instance for manipulating the packets.

<img class="aligncenter wp-image-898 size-full" src="/uploads/2021/08/site-to-site-6a.png" alt="" width="1089" height="422" srcset="/uploads/2021/08/site-to-site-6a.png 1089w, /uploads/2021/08/site-to-site-6a-768x298.png 768w" sizes="(max-width: 1089px) 100vw, 1089px" />

Again create another route table for 10.100.1.0/24 which will use for NAT instance. Associate the subnets and add the routes:

<img class="aligncenter size-full wp-image-887" src="/uploads/2021/08/site-to-site-6.png" alt="" width="1058" height="398" srcset="/uploads/2021/08/site-to-site-6.png 1058w, /uploads/2021/08/site-to-site-6-768x289.png 768w" sizes="(max-width: 1058px) 100vw, 1058px" />

<img class="aligncenter size-full wp-image-886" src="/uploads/2021/08/site-to-site-7.png" alt="" width="1065" height="425" srcset="/uploads/2021/08/site-to-site-7.png 1065w, /uploads/2021/08/site-to-site-7-768x306.png 768w" sizes="(max-width: 1065px) 100vw, 1065px" />

Topology setup is done.

Now from remote end, gather the following info:

  • Remote public IP (Exm: 55.66.77.88)
  • Remote private subnet (Exm: 192.168.0.0/16)
  • Pre shared key (Exm: xKDJG/Ay28XdokbtC0gIMzDLdJJo)

Setting up VPN in instance

SSH into the NAT instance. Install Openswan and iptables packages

sudo yum install openswan -y
sudo yum install iptables-services -y
sudo systemctl enable iptables
sudo systemctl start iptables
sudo iptables --flush
sudo service iptables save

Modify the kernel parameters in order to allow IP forwarding and disable redirects. Edit /etc/sysctl.conf file and add below lines:

net.ipv4.ip_forward = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0

net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.eth0.send_redirects = 0

net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.eth0.accept_redirects = 0

Save and exit. Reload the rules.

sudo sysctl -p /etc/sysctl.conf

Edit /etc/ipsec.conf file and uncomment the following line if not already

include /etc/ipsec.d/*.conf

Now for configuring tunnel, always consider your side as left/east and remote end as right/west. Create a file in /etc/ipsec.d/aws-vpn.conf and paste the following:

conn Tunnel1
    authby=secret
    auto=start
    left=%defaultroute
    leftnexthop=%defaultroute
    leftid=&lt;Public-IP-of-this-NAT-Instance&gt;
    right=&lt;Public-IP-of-remote-gateway&gt;
    type=tunnel
    ikelifetime=8h
    keylife=1h
    phase2alg=aes128-sha1;modp1024
    ike=aes128-sha1;modp1024
    keyingtries=%forever
    keyexchange=ike
    leftsubnet=&lt;Public-IP-of-this-NAT-Instance&gt;/32
    rightsubnet=&lt;Private-subnet-of-remote-side&gt;
    dpddelay=10
    dpdtimeout=30
    dpdaction=restart_by_peer

At line 6, leftid=EIP of NAT instance and for line 7, place the public IP of remote gateway.

Also at line 15, place the EIP of the NAT instance and in line 16, place the private subnet of remote end. Save and exit.

Now for authentication, create a file in /etc/ipsec.d/aws-vpn.secrets, put the below line:

remote-public-IP NAT-EIP: PSK "pre-shared-key"
## Example
55.66.77.88 11.22.33.44: PSK "xKDJG/Ay28XdokbtC0gIMzDLdJJo"

Before starting the tunnel, share your source IP to the remote end and tell them to add that IP as rightsubnet._ In our example, we use 1.2.3.4 as source IP. It can be any public IP address. Start the tunnel afterwards.

sudo systemctl enable ipsec.service
sudo systemctl start ipsec
sudo systemctl status ipsec

<img class="aligncenter size-full wp-image-890" src="/uploads/2021/08/site-to-site-8.png" alt="" width="887" height="178" srcset="/uploads/2021/08/site-to-site-8.png 887w, /uploads/2021/08/site-to-site-8-768x154.png 768w" sizes="(max-width: 887px) 100vw, 887px" />

Tunnel1 is up.

Configuring NAT traversal

I want to send traffic from NAT instance to remote as if it’s coming from public IP 1.2.3.4. Of course it can be any IP of your choice.

In NAT instance, add an iptables rule. Configure the remote private subnet as destination and the source IP of your choice.

sudo iptables -t nat -A POSTROUTING -d 192.168.0.0/16 -j SNAT --to-source 1.2.3.4
sudo service iptables save

Now we will test the setup with tcpdump tool. For this, run below command in NAT instance.

sudo yum install tcpdump -y
sudo tcpdump -i any icmp -n

SSH into another instance which you provisioned in subnet 10.100.0.0/24. Ping to the remote private IP of the host from that instance. Assuming the remote private IP is 192.168.1.161. Ping result should success and you can see the tcpdump result in NAT instance.

[ec2-user@ip-10-100-1-102 ~]# sudo tcpdump -i any icmp -n
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
13:33:49.373891 IP 10.100.0.182 &gt; 192.168.1.161: ICMP echo request, id 4273, seq 591, length 64
13:33:49.374867 IP 192.168.1.161 &gt; 1.2.3.4: ICMP echo reply, id 4273, seq 591, length 64

As you can see, traffic coming from 192.168.1.161 is being translated to 1.2.3.4

Also, if tcpdump command run in remote end, the following output will be generated.

[ec2-user@ip-192-168-1-161 ~]# sudo tcpdump icmp -n
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
13:43:20.255914 IP 1.2.3.4 &gt; 192.168.1.161: ICMP echo request, id 4273, seq 1161, length 64
13:43:21.256418 IP 1.2.3.4 &gt; 192.168.1.161: ICMP echo request, id 4273, seq 1162, length 64
13:43:21.257498 IP 1.2.3.4 &gt; 192.168.1.161: ICMP echo request, id 4273, seq 1162, length 64
13:43:21.259818 IP 1.2.3.4 &gt; 192.168.1.161: ICMP echo request, id 4273, seq 1162, length 64

Traffics are indeed coming from source 1.2.3.4.