Schedule your AWS Client VPN Endpoint (and reduce costs)

Marc Logemann
AWS Factory
Published in
6 min readJun 9, 2020

The AWS Client VPN Endpoint is more on the expensive side and since there is no easy way to activate or deactivate it, i will show you how to automate creation and destruction of this service without even the need to change the VPN Client config.

The problem

The Client VPN Endpoint is a service which comes in handy when you have databases or other services in your private AWS VPC subnets which you need to administrate from time to time. The monetary issue is, that even if you dont need this VPN that often, you are paying 24/7 for it. When clients connect to the VPN, there is of course a surcharge for the minutes being connected. So its a two layered pricing: service availability on the one, client connection time on the other hand. And unlike EC2, where you can just “stop” a server without losing the configuration of the server, the VPN can only be deleted in wich case its gone forever including all those configs like certs. rights or even the endpoint URL.

The overall solution

First, you need to automate the construction of the VPN Endpoint. I wrote a blog which describes just that with the help of CDK. Then you need two Lambdas, one which creates or destroys the VPN stack based on the input parameter and another who is doing some DNS magic for the newly created VPN Endpoint. Remember that each time a new VPN is bootstrapped, it gets a new URL and for that we need a CNAME in Route53, so that your client VPN configurations will still work. So lets get to work….

The needed lambdas as javascript code

This will be the base of our automation. We will create a stack that will define the two needed Lambda functions. We will ship the javascript lambdas as part of our CDK project, which itself is written in typescript.

The following Lambda is responsible for creating (starting) and destroying (stopping) the VpnStack. Be aware that you need to add the Cloudformation Template for the VpnStack into the getVpnCfnTemplate() function which you can easily generate by executing the following command on the shell:

cdk synth vpnStack

Of course assumed that you have a CDK class which creates a vpnStack as shown in my previous blog mentioned above. You can find the synthesized template in the cdk.out folder of your CDK project.

You should save this JS file (startStopVpn.js) in the $CDK_ROOT/content/lambda/cron/ folder. But lets shortly discuss this script:

It receives a basic text parameter in the event object depending which Rule has triggered the script (more on Rules later on). Advantage is that we dont need two different scripts for starting and stopping. Apart from that, this code is pretty simple. It just uses the createStack() and deleteStack() methods of the Cloudformation AWS-SDK. The magic is in the getVpnCfnTemplate() function anyway, which you need to paste in.

The next script purpose is to change (or add) the CName entry of your target hostedZone in Route53. It assumes that you dont have other VPN Endpoints in your account, hence the direct access of data.ClientVpnEndpoints[0]. Change that part if you have a different setting.

You should also save this JS file (changeCName.js) in the $CDK_ROOT/content/lambda/cron/ folder.

The code is pretty simple as well. We ask the EC2 part of the SDK for all the VPN Client Endpoints and use the URL of the first one for the value of the CNAME. Then you will see an event object which has two attributes: cname and dnsZoneId. Those will be provided by the lambda Stack later on.

The CDK LambdaStack as typescript code

Now that we have the scripts wich will turned into lambdas, we need the infrastructure code. We will make some assumptions here which you will most likely need to modify.

  1. we provide a Route53Stack to the constructor, which has a public instance variable swypComZone which holds a reference to my Route53 which i want to operate on. If you created the Route53 zone in the AWS GUI, just use this in your code, instead of Route53Stack:
    HostedZone.fromLookup(this, ‘MyZone’, { domainName: ‘yourdomain.com’ });
  2. All those Schedule.cron() constructor parameters which control the start and stop time of the VPN Endpoint can easily be changed.

So lets talk briefly about this Stack. At the end of the code, we create the Lambda Functions. For that we reference the existing scripts in the CDK folder mentioned above. Furthermore we add some PolicyStatements so that those Lambdas have enough rights. I am sure those can be narrowed down to comply with the “least privileges” principle, but i was too lazy ;-)

The createStartupCronLambda() and createShutdownCronLambda() functions just associate the already defined startStopVpn lambda with Rules which were triggered by the displayed cron expressions. In this case, the VPN should be started at 6am UCT and stopped at 6pm UCT.

The last one is the createCNameChangeCron() is a bit different though. In general we also associate a lambda, the changeCName script, with a cron. This cron starts the CName lambda 10 minutes after the Vpn creation script started and executes it every 10 minutes during that one hour. As you might guess, thats a poor-mans workaround for not having an event which indicates that the VPNStack is up-and-running which would allow me to fire a lambda based on that event. Remember, commands like createStack() do the real work in the background because these things are long running tasks. Anyways, with this cron, we might have a slight delay after VPNStack creation and the CNAME update, but i think we can live with that.

But thats about the lambdaStack. Its a teamplay game of two different JS lambdas which are cron scheduled but lets finalize this thing and dig deeper into the URL issue with the VPN Endpoint.

Why do i need this CNAME ?

When you create a VPN Endpoint, AWS provides you with a URL (DNS name) for that created service.

This URL is part of the downloadable OpenVPN config file for your clients. To be exact, it’s the remote parameter inside the file. Now imagine you created the VPN and supplied your clients with the OpenVPN config file to import into their VPN client software. It will work for exactly one day because the next day, the VPN Endpoint will have a new URL because we recreated the whole service in the morning. So the clients cant connect anymore. The solution is a URL which doesnt change and thats what the CName lambda does for us. It creates a CNAME like *.vpn.yourdomain.com with the value of the current VPN Url like *.cvpn-endpoint-XXXXXXXXXXXXXX.prod.clientvpn.eu-central-1.amazonaws.com.

Now you might think why do we have asterisks in the CNAME. When you look into the OpenVPN config file AWS provides, you will see a parameter called remote-random-hostname. This parameter prefixes the remote URL with a random number to prevent DNS caching. So in the original AWS provided OpenVPN file the URL behind the remote parameter cvpn-endpoint-XXXXXXXXXXXXXX.prod.clientvpn.eu-central-1.amazonaws.com turns into RANDOM.cvpn-endpoint-XXXXXXXXXXXXXX.prod.clientvpn.eu-central-1.amazonaws.com when the real connect happens (the RANDOM string should signal any letter combinaton as prefix).

Now the task for you is to modify the Client VPN config file with regards to the remote parameter. Just replace the AWS Url with vpn.yourdomain.com. During connection time, the VPN client will call the URL RANDOM.vpn.yourdomain.com and your CNAME translates all RANDOM.vpn.yourdomain.com into RANDOM.cvpn-endpoint-…. URL So on the AWS DNS side, there is the same wildcard present and thats why you see this wildcard in the DNS Name in the screenshot. Normally you don’t see that much wildcards when a service wants to provide you with a connection URL right?

To finalize this post: With this stack you can save some bucks because now the VPN will only be available when your clients are at least awake. In an emergency you can easily trigger the Lambdas via the GUI. In case your workforce is global with all timezones involved, this wont help you but then you most likely dont need to save 80 USD ;-)

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.