Create a AWS VPN Client Endpoint with CDK

Marc Logemann
AWS Factory
Published in
5 min readJun 6, 2020
Photo by KS KYUNG on Unsplash

Creating a VPN in AWS is quite a common task if you hide some of your services in private Subnets and need admin access to it. Unfortunately there is no full featured Conctruct for that in CDK yet. But there are the Cfn* lower Level Resources to achieve what we want.

I wont get into detail how to install or setup CDK. I’ve done some of that in one of my previous articles which you can see or just use the official AWS getting started documentation.

The example VpnStack

I will post the whole Stack called VpnStack below and then will explain the details. It’s not much to understand anyway.

Creating the CfnClientVpnEndpoint

As can be easily seen, we use Cfn* Resources all over the place because there is no higher level abstraction in CDK yet.

We start by creating the CfnClientVpnEndpoint and decide to use certificateAuthentication which is based on asymmetric keys. For that to work we also need to supply a serverCertificate and a clientRootCertificateChainArn. We use a hard coded ARN to obtain our server and client certificate from the AWS Certificate Manager, which we manually put there before. The creation of the certificates is best documented here. Remember it’s better to create a client certificate for every client who wants to connect to the VPN. The documentation only creates one cert in Step 5. If you have done all the outlined steps, you just need go to the Certificate Manager in the AWS web console and obtain the ARN. If you created multiple client certs, you can just reference one out of those to define the clientRootCertificateChainArn.

To have a proper name in the AWS GUI for the VPN, we need to tag the resource and that’s what we do in tagSpecifications. When you think why is there no “name” attribute as top level property… you are right. I dont know either. BTW, its not possible to add the tag via Tag.add(endpoint, ‘Name’, ‘My super VPN’);

Another section is about logging exposed by the attribute connectionLogOptions. I created a logGroup and a stream right at the start of the stack code and applied a retention of one month to it. Remember that you pay for all those logs in CloudWatch, so its always a good idea to have an expiration time.

The last three attributes i will discuss here are splitTunnel, dnsServers and clientCidrBlock. SpitTunnel means that if true, traffic to all other IP destinations other than the pushed route (normally all the internet traffic from your client) will *not* be handled by the VPN interface. In my case i wanted to have all the traffic go through the VPN, so i turned it off. Because of that, i am also able to define public DNS Server in the dnsServers attribute, because my client will sill be able to resolve those Google DNS server while inside the VPN. There are of course other ways to do it. You could for instance use internal DNS servers of your private subnet, but i wont go into that. Lastly the clientCidrBlock defines the IP range clients will get when connecting. If you are working for a pretty big corporation, make it large enough so that enough of the employees can connect simultaneously.

Setup targetNetworkAssociations, AuthRules and Routes

First we create two NetworkAssociations for our private Subnets of the VPN. For that we just iterate through our private subnets and apply the association. You will notice that we “save” those associations in a ConcreteDependable object. This will be important later on.

Then we create a VpnAuthorizationRule which is quite non-permissive as you an easily see. There is also the possibility to add SecurityGroups to the VPN to create more fine grained permissions but for now we leave it quite open.

The last thing we need to do is to create Routes that allow us to reach more than just our subnet IP range. Because routes need to be applied for each subnet, we iterate over those again and apply the 0.0.0.0/0 route. Remember the splitTunnel thing. If you have splitTunnel to true, there is no need to push those routes but then you need to change also the dnsServers value.

You also see a weird “node.addDependency(dependables)” call at the end. Cloudformation somehow doesn’t respect the rule that you cant create a Route without having created a NetworkAssociation before. So with the addDependency() call, we inform Cloudformation that it should first create the CfnClientVpnTargetNetworkAssociation before doing the Routes. Remember, just because in your code it comes first, doesn’t mean that Cloudformation process it in the same order.

Photo by AbsolutVision on Unsplash

Final notes

First, big thanks to Kirill Merkushev whose blog got me starting with the idea of automating my VPN Endpoint in AWS. The outlined VPN Stack is kind of an iteration of his work.

In case you wonder what props.vpc is… in my whole setup i create a VpcStack first and put the created VPC in a properties object which i pass around so that other stacks get access to the VPC. You could also pass the whole stack around or just create the VPC network inside the VpnStack. Its up to you.

Of course the whole point to have this as part of your CDK project is to automate its creation or destruction. So at the end you can just do cdk deploy vpnStack on the shell and a few minutes later your VPN is up and running. I am thinking about creating a script which destroys the VPN at the end of the day and create it again with the start of another day but this would involve creating a dynamic CNAME for the endpoint URL which would change every day then which is bad for your client configuration.

Speaking of client configuration for VPN clients like tunnelblick, its not enough to download the client config via AWS VPN Console because of missing cert sections in the configuration file. Just keep in mind that you need to add the <cert> and <key> sections by yourself and put the generated certs in there.

Update: There is also kind of a sequel to this topic which focuses on automating the creation and destruction of a VPN to reduce costs related to that service. See more on: https://medium.com/aws-factory/schedule-your-aws-client-vpn-endpoint-and-reduce-costs-f68d8729bade

If you want to know more about well architected cloud applications on Amazon AWS, how to prototype a complete SaaS application or starting your next mobile app with Flutter, feel free to head over to https://okaycloud.de for more infos or reach me at the usual places in the internet.

--

--

Marc Logemann
AWS Factory

Entrepreneur & CTO - (AWS) Software Architect, likes Typescript, Java and Flutter, located in the Cloud, Berlin and Osnabrück.