Creating an AWS Client VPN with Terraform
Hosting infrastructure with cloud providers like AWS can be a good opportunity to use managed services to save manpower and time. To access your infrastructure in a secure way VPN seems to be a good way to do it.
In this short tutorial, we will have a look at how to configure a VPN Client Endpoint with terraform in a more “complex” scenario. In our scenario, we are setting up (at least preparing) multiple VPN Endpoints to access infrastructures by different people.
Examples for those infrastructures could be a development and a production environment which are completely separated from each other. As an authentication mechanism, we are choosing to have client certificates. This enables us to act without any additional infrastructure and every person is still managable on its own. So every person is receiving their own certificate which can be individually revoked if neccessary.
So from a certificate perspective, we want to have one TLS certificate per VPN tunnel and n client certificates. Additionally we:
- want to have a regularly rotated TLS Certificate
- don’t want to rotate the TLS Certificate manually
- want to have one PKI for our company and not per VPN Tunnel
With that definition let’s get ready to set everything up.
If we are talking about working with certificates in an AWS environment you won’t be able to avoid the AWS Certificate Manager (ACM) where all certificates are placed. Because we want to have rotated TLS certificates anyway we will use this service to also create those for us. In terraform this would look like the following:
With this snippet, we are creating a TLS certificate that will be managed by AWS. Because AWS doesn’t know if we are the owner of that domain, it has to validate it at some point. In our case, we have chosen a DNS validation, where we are providing a DNS entry that AWS is trying to find.
You also might wonder where the `local.global_tags` is coming from. All those snippets are part of a standalone example to set up a client VPN endpoint. This means that within this example all required resources like an own VPC, with subnets and tags are created. Everything is available on GitHub where you can look at the complete setup. For now we are putting this basic setup aside to focus on the VPN endpoint.
Before we are allowed to use the certificate we have to wait until the validation is finished. For that we are going to use the `aws_acm_certficiate_validation` block. By using the validation block instead of the certificate block as a dependency within other terraform resources we make sure that we are only using certificates that are correctly created.
With those two snippets we have taken care of the whole TLS part for our upcoming VPN tunnel. What is left on the certificate side are our client certificates.
If you have already a PKI in place you can of course use that. In our example we will use a tool called XCA which is a nice little tool for managing a PKI.
We are going to create the following certificate structure:
As you can see in the picture we are having a certificate chain of four certificates. Three of them are certificate authorities (CA) meaning that they are allowed to sign other certificates. The fourth one is a client certificate which a user can use to authenticate via a VPN Tunnel.
All CAs have four X509v3 extensions set:
- Basic Constraint: CA:TRUE
- Subject Key Identifier
- Authority Key Identifier
- Key Usage: Certificate Sign, CRL Sign
The client certificate has a few other X509v3 extension options set:
- Basic Constraint: CA:FALSE
- Subject Key Identifier
- Authority Key Identifier
- Key Usage: Digital Signature
- Extended Key Usage: TLS Web Client Authentication
If we have created all the certificates we need to export them to make use of them. On the one hand, we want to export the VPN Client certificate as PKCS#12 file and on the other hand, we want to export the VPN CA (private and public key) and the certificate chain (the public keys) of the root and intermediate certificate.
After exporting all certificates we have to add the VPN CA to the ACM in the following way:
As you can see in this snippet, we are uploading the VPN CA certificate and it’s certificate chain to the ACM.
Finally we have prepared everything to create our VPN Endpoint.
Because we already have prepared and exported all certificates we can now start to create our client VPN endpoint:
In this block, we are defining the client VPN endpoint, which IP Addresses should be used to establish a VPN connection. Additionally we have assigned our certificates for TLS (server_certificate_arn) and authentication. We also have enabled split_tunnel which means that traffic which isn’t meant to reach something within our tunnel won’t be routed into our VPC.
What isn’t shown in the client vpn snippet are some default values which are good to know. First of all the default transport protocol is UDP and the default port which is getting opened is the port 443. Because we want to access AWS resources via our VPN we also haven’t defined a DNS server, so the default DNS server of the VPC will be taken.
As with most of the resources of AWS out-of-the-box, our VPN endpoint isn’t accessible yet. To make it available we have to add a security rule which allows us to access the VPN endpoint on the defined port with the defined protocol:
We are only restricting incoming traffic to the defined port and protocol but outgoing everything is allowed.
After handling the access to the VPN endpoint, the next step is connecting our VPN endpoint to our VPC – to be more precise to one or more subnets of our VPC.
The example definition associates all defined subnets with one association rule for each of it.
Because of an issue within the terraform AWS provider on each update the VPN network association will be removed and recreated from scratch (and this takes a while). For that reason, we are ignoring all changes on the subnet_id attribute.
Last but not least we also have to create an authorization rule which allows our clients to access resources. Because we are using certificate-based authentication we are not able to create more granular rules yet:
With this last snippet we have finished the whole terraform setup and we can now execute it with:
That’s it! Now you should have created a VPN endpoint within AWS.
The whole code for this example can be found here.
Connecting to our VPN endpoint
To connect to a VPN endpoint you have to use an OpenVPN compatible VPN client – in our case, we will use the OpenVPN CLI Client – and a corresponding configuration to access our endpoint.
We can download a basic version of the VPN client configuration directly from AWS. For doing so we can use either the AWS CLI or download it via the web console (VPNC > Client VPN Endpoints > Download Client Configuration).
After downloading the configuration we have to adapt it:
- While writing this article the certificate section of the client configuration is out-of-the-box broken, meaning that it is adding an additional certificate that should not be in there. To validate if your client configuration is messed up you have to take a look at the <ca> section and count the available certificates in it. If the counted number is four then you must delete the third certificate.
- Regardless if you have to fix the <ca> section our client ca certificates have to be added. So in our example, we must append the certificates of our exported certificate authorities placed in the files ca-chain.crt and client-vpn-ca.crt.
Now we are ready to go to test our vpn connection:
Voila, now you should be connected to the client vpn endpoint.
This concludes our journey to create a client VPN endpoint with terraform on AWS. I hope this short walkthrough saves you some time and gives you a rough idea of how you can set up a client VPN endpoint with terraform.