Recently a client decided to set up an AWS Hardware VPN to their site. The simplest way to research this seemed to be to set up a test VPN to my own router – a MikroTik 951G-2HnD running RouterOS 6.30.2. Here’s how I did it.
First up, my thanks to those on the Australian MikroTik users mailing list who provided assistance, and to Mike Everest of Duxtel for his advice. Another valuable resource was this blog entry by Arnt Gulbrandsen.
This article is not about the AWS end; the Amazon documentation is very complete. This article is about how to set up the MikroTik end.
You will need…
As part of setting up the AWS end (a VPC with a Hardware VPN) you will have supplied AWS with the outside IP address of your MikroTik router. This needs to be a publicly accessible IP address, a so-called “static IP address”.
AWS will have provided you with some configuration details – these are downloadable from AWS after you have set up the AWS end of your VPN. You can download configuration details for various router platforms (Cisco, Juniper etc) – I used the “generic” instructions.
Here are the mappings from the AWS details to variables used in the configuration statements in this article:
$AWSOUTSIDE1the “Virtual Private Gateway Outside address” for Tunnel #1
$AWSINSIDE1the “Virtual Private Gateway Inside address” for Tunnel #1
$SECRET1the “Pre-Shared Key” for Tunnel #1
$AWSOUTSIDE2the “Virtual Private Gateway Outside address” for Tunnel #2
$AWSINSIDE2the “Virtual Private Gateway Inside address” for Tunnel #2
$SECRET2the “Pre-Shared Key” for Tunnel #2
$LOCALOUTSIDEthe Customer Gateway Outside address
Here is a sample set of MikroTik scripting statements to set these variables appropriately (obviously don’t use these actual values!):
:global LOCALOUTSIDE "184.108.40.206" :global LOCALNET "192.168.1.0/24" :global REMOTENET "10.0.1.0/24" :global AWSINSIDE1 "169.254.33.100" :global AWSOUTSIDE1 "220.127.116.11" :global SECRET1 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx" :global AWSINSIDE2 "169.254.35.100" :global AWSOUTSIDE2 "18.104.22.168" :global SECRET2 "yyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
Notice the absence of
$LOCALINSIDE2 to match the AWS-supplied “Customer gateway Inside” addresses. They are not needed in a MikroTik configuration.
For routing and NAT bypass, you will also need these two items, not provided in the AWS configuration instructions:
$LOCALNETyour local network (including prefix length!)
$REMOTENETthe network inside the VPC (including prefix length!)
A single IPsec proposal can be used for both tunnels; it needs to look like this:
/ip ipsec proposal add name=AWS \ add auth-algorithms=sha1 \ enc-algorithms=aes-128-cbc \ lifetime=8m \ pfs-group=modp1024
Obviously if AWS changes their parameters you will need to adjust these to match.
By the way, if you are doing this seriously or expect to terminate more than one AWS tunnel set on the same router, it would be a good idea to include the VPN ID in the identifiers or comments you use when creating peers, policies, proposals, routes and so on.
Then you will need two peer statements, one for each of the AWS Virtual Private Gateway Outside Addresses:
/ip ipsec peer add comment="AWS Peer 1" \ address=$AWSOUTSIDE1 \ local-address=$LOCALOUTSIDE \ secret=$SECRET1 \ dpd-interval=10s dpd-maximum-failures=3 \ enc-algorithm=aes-128 lifetime=8h \ nat-traversal=no
/ip ipsec peer add comment="AWS Peer 2" \ address=$AWSOUTSIDE2 \ local-address=$LOCALOUTSIDE \ secret=$SECRET2 \ dpd-interval=10s dpd-maximum-failures=3 \ enc-algorithm=aes-128 lifetime=8h \ nat-traversal=no
And you will need four IPsec policies, two for each VPN. One of the two covers traffic from your local network to the VPC network; the other covers traffic from your inside address to the AWS inside address:
/ip ipsec policy add comment="AWS Tunnel 1" \ proposal=AWS \ src-address=0.0.0.0/0 dst-address=$REMOTENET sa-src-address=$LOCALOUTSIDE sa-dst-address=$AWSOUTSIDE1 \ tunnel=yes
/ip ipsec policy add comment="AWS Tunnel 1" \ proposal=AWS \ src-address=0.0.0.0/0 dst-address=$AWSINSIDE1 \ sa-src-address=#$LOCALOUTSIDE sa-dst-address=$AWSOUTSIDE1 \ tunnel=yes
/ip ipsec policy add comment="AWS Tunnel 2" \ proposal=AWS \ src-address=0.0.0.0/0 dst-address=$REMOTENET sa-src-address=$LOCALOUTSIDE sa-dst-address=$AWSOUTSIDE2 \ tunnel=yes
/ip ipsec policy add comment="AWS Tunnel 2" \ proposal=AWS \ src-address=0.0.0.0/0 dst-address=$AWSINSIDE2 \ sa-src-address=#$LOCAL_OUT sa-dst-address=$AWSOUTSIDE2 \ tunnel=yes
If you have more than one remote network, you will need to specify a supernet (a network that contains all your remote networks) or add additional policies to cover the other remote networks. Or all the ones you want to access from your local network, anyway.
If your outside interface is doing NAT, you will need to exempt traffic between the local and remote networks. Add these lines before any masquerade NAT statements:
/ip firewall nat add chain=srcnat \ action=accept \ dst-address=$REMOTENET src-address=$LOCALNET
/ip firewall nat add chain=srcnat \ action=accept \ dst-address=$LOCALNET src-address=$REMOTENET
If you have more than one network at either end, you will need to add additional configuration to exempt them from NAT too.
You will need to allow IKE (IPsec’s key negotiation protocol) through your firewall, as well as IPsec-encrypted traffic. Put the following four lines somewhere suitable in your filter rules. If you are not sure where to put them, insert them in front of all other rules.
/ip firewall filter add comment="AWS Tunnel 1" \ chain=input \ protocol=ipsec-esp \ src-address=$AWSOUTSIDE1 dst-address=$LOCALOUTSIDE
/ip firewall filter add comment="AWS Tunnel 1" \ chain=input \ protocol=udp \ src-address=$AWSOUTSIDE1 src-port=500 \ dst-address=$LOCALOUTSIDE dst-port=500
/ip firewall filter add comment="AWS Tunnel 2" \ chain=input \ protocol=ipsec-esp \ src-address=$AWSOUTSIDE2 dst-address=$LOCALOUTSIDE
/ip firewall filter add comment="AWS Tunnel 2" \ chain=input \ protocol=udp \ src-address=$AWSOUTSIDE2 src-port=500 \ dst-address=$LOCALOUTSIDE dst-port=500
If you want to control what traffic is allowed to flow between your VPC and your local network, add suitable firewall filter statements.
Finally, you need to tell your MikroTik how to route traffic to your VPC:
/ip route add distance=1 dst-address=$REMOTENET gateway=$AWSINSIDE1 add distance=1 dst-address=$REMOTENET gateway=$AWSINSIDE2
Yes – there are two routes for the same destination, because the VPC can be reached over either tunnel. If you have more than one network in your VPC, add suitable route statements for them too. The key point is that each route must be via an AWS Virtual Gateway Inside address.
Using the AWS console, check the tunnel status of your VPN. You should see that one of the tunnels is UP, the other DOWN. See “A MikroTik limitation” below for why this is so, and a possible workaround.
If a tunnel is UP, you should also be able to see valid security associations for it. Use this command:
/ip ipsec installed-sa print
You can test your new VPN by sending a ping to the AWS inside address of whichever tunnel is up.
If you have an instance running inside your VPN, you should be able to ping its IP address too. If your VPC is only routing to specific networks at your end of the VPN, you will need to specify an address in one of those networks as the source address of your ping. And of course, it has to be an address on the router!
If one of your tunnels is UP, but you can’t seem to get a ping through, double check the filters on the router at your end and the security groups and ACLs that apply to the VPC at the AWS end.
Using the command above, look at the installed security associations, particularly the “current-bytes” value. If current-bytes is increasing, then the SA is being used to transfer data; the tunnel is working, and the problem is probably filters.
A note on virtual interfaces
Some router platforms implement IPsec on virtual interfaces. The AWS documentation talks about “tunnel interfaces”. On the MikroTik these are not necessary – the local “inside” addresses do not need to be assigned to any interface. They are referenced only in route statements and IPsec policy statements.
If you have only one interface to the Internet, and the router’s default route points over that interface, then packets to the AWS inside addresses will be routed over that interface and be handled by the relevant IPsec policy.
If you have more than one Internet-connected interface on your router, or if the single Internet-connected interface is not the default route, you will need to route the AWS inside addresses out the correct interface.
A MikrotTik limitation
In practice, one of your tunnels will always be “invalid”, because MikroTik has a limitation (maybe even a bug) in that regard – it will not permit two IPsec policies to cover the same traffic, and flags one as “invalid” if you try.
If you followed the above instruction and now look at your configured policies, you will see that two of them are marked “Invalid”. That’s OK – one or the other tunnel will work fine. If you want to be in control, you can disable one pair of policies yourself rather than leave it up to chance.
Since first writing this article I’ve come up with a sort of a workaround. For the MikroTik to invalidate a policy, the policy has to cover the identical address range as another. I tricked it by setting up the second policy as a pair of policies, each covering half the desired address range.
For example, if your policy was supposed to send 192.168.100.0/24 over two VPNs, you could have one policy for that range and VPN1, then two more policies, for 192.168.100.0/25 and 192.168.10.128/25, for VPN2.
I’ve tried this and it does work, though you will not be able to use the network address of the second subnet nor the broadcast address of the first. And I have not tried it with BGP, only with static routes.
Using a supernet works too. For example if you have 192.168.100.0/24 at the AWS end, you could make the policy on one of the VPNs for 192.168.100.0/24, and the other for 192.168.100.0/23. Of course, the supernet must not overlap any local networks! If you have the address range available, using a supernet is better than using two subnets, because you don’t lose two addresses out of the middle of the range, and don’t therefore have to do anything special at the AWS end.
[Note: Updated 3/12/2016 to correct syntax errors and add some clarifications.]