In this article we will look at how to set up an AWS Client VPN Endpoint to give users access to our VPC. This article assumes you have some knowledge on how to write and deploy Terraform code with AWS. If you don't have Terraform with AWS already up and running, please have a look at Terraform with AWS.
Use case
There are many reasons to use a VPN. Let's says your running EC2 instances on AWS and would like to only allow inbound traffic from certain employees in your company. Many companies solves this problem by whitelisting the employee's IP address. This does not work when the employee's IP change. IP whitelisting also prevents the ability to place your EC2 instance in a private subnet. In our use case, we have multiple employee's that we want to give access to our EC2 instance. For security reasons, this EC2 instance will also be placed in a private subnet, to prevent any outside traffic from access our instance. Note, this article is not restricted to only EC2 instances but will also apply to any other AWS service.
AWS VPN
AWS Client VPN Endpoint, is an AWS service that enables clients to connect to a VPN session. This will allow the client to access services within our VPC, privately. AWS Client VPN Endpoint also allows for split tunneling. Split tunneling will split the clients traffic based on where it is going. This means that any traffic between the client and the VPC will be private through the VPN tunnel, and other traffic will be routed through the open web.
Generate certificates
For this tutorial, we will be using OpenVPN easy-rsa to generate our client and server certificate and keys.
First, we need to clone the easy-rsa Github repository.
git clone https://github.com/OpenVPN/easy-rsa.git
Next, lets move into the easyrsa3 directory.
cd easy-rsa/easyrsa3
Then we initialize a new PKI environment.
./easyrsa init-pki
To build a new certificate authority (CA), run this command.
./easyrsa build-ca nopass
Now we need to generate server and client certificates and keys. Run the following command to generate server certificates and keys.
./easyrsa build-server-full server nopass
...and lastly, the client certificate and key.
./easyrsa build-client-full client1.domain.tld nopass
To make things a little easier for ourselves, lets move all the needed files out in a another directory.
mkdir ~/my-vpn-files/
cp pki/ca.crt ~/my-vpn-files/
cp pki/issued/server.crt ~/my-vpn-files/
cp pki/private/server.key ~/my-vpn-files/
cp pki/issued/client1.domain.tld.crt ~/my-vpn-files
cp pki/private/client1.domain.tld.key ~/my-vpn-files/
cd ~/my-vpn-files/
Now that we have all our needed files, let's look at our Terraform code.
Terraform VPN
We'll start by adding the files we generated in the last step to AWS ACM to make them easily accessible later on. Create a file, acm.tf, and insert the following.
resource "aws_acm_certificate" "server_vpn_cert" {
certificate_body = var.server_cert
private_key = var.server_private_key
certificate_chain = var.ca_cert
}
resource "aws_acm_certificate" "client_vpn_cert" {
certificate_body = var.client_cert
private_key = var.client_private_key
certificate_chain = var.ca_cert
}
In the first resource we add the server files and then the client files in the second resource. You can also choose to put these certificates and keys in a separate variables.tf file. If your unfamiliar with variables, click here to learn more about Terraform variables. You could also just paste the content of each file directly into the code. Here we set the following arguments.
Resource "server_vpn_cert"
- certificate_body, is the server.crt file we generated in the previous step.
- private_key, is the server.key file we generated in the previous step.
- certificate_chain, is the ca.crt file we generated in the previous step.
Resource "client_vpn_cert"
The Client VPN Endpoint needs one pair of client certificates and keys. This step is not necessary when we create more clients later on.
- certificate_body, is the client1.domain.tld.crt file we generated in the previous step.
- private_key, is the client1.domain.tld.key file we generated in the previous step.
- certificate_chain, is the ca.crt file we generated in the previous step.
Next, let's create our security group. Create a file, security_groups.tf, and insert the following.
resource "aws_security_group" "vpn_secgroup" {
name = "vpn-sg"
vpc_id = module.vpc.vpc_id
description = "Allow inbound traffic from port 443, to the VPN"
ingress {
protocol = "tcp"
from_port = 443
to_port = 443
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
egress {
protocol = "-1"
from_port = 0
to_port = 0
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
}
Since security groups is not directly related to AWS Client VPN Endpoint, I won't go in to detail how they work. All you need to know is that this security group allows inbound traffic on port 443.
Now, we can create our client VPN endpoint. Start by creating a file, vpn.tf, and insert the following.
resource "aws_ec2_client_vpn_endpoint" "my_client_vpn" {
description = "My client vpn"
server_certificate_arn = aws_acm_certificate.server_vpn_cert.arn
client_cidr_block = "10.100.0.0/22"
vpc_id = module.vpc.vpc_id
security_group_ids = [aws_security_group.vpn_secgroup.id]
split_tunnel = true
# Client authentication
authentication_options {
type = "certificate-authentication"
root_certificate_chain_arn = aws_acm_certificate.client_vpn_cert.arn
}
connection_log_options {
enabled = false
}
depends_on = [
aws_acm_certificate.server_vpn_cert,
aws_acm_certificate.client_vpn_cert
]
}
We set the follow arguments.
- description, is the description for our Client VPN Endpoint.
- server_certificate_arn, is the ACM certificate ARN containing the server certificate we generated earlier.
- client_cidr_block, is the IPv4 CIDR range we want our clients to have. This cannot overlap with the CIDR of the VPC.
- vpc_id, is the ID of our VPC.
- security_groups_ids, is a list of security groups we want attached to our VPN Endpoint. Here we set the security group ID we created in the previous step.
- split_tunnel, is whether or not we want split tunneling enabled. We set this to true.
# Client authentication #
This contains information about how we want to authenticate our clients accessing our VPN.
- type, is set to certificate-authentication since we want to authenticate clients using the generated files we created earlier.
- root_certificate_chain_arn, is the ARN of the client certificate we generated earlier.
# Connection logs option #
- enabled, is set to false since we don't want to log information about the client connection.
Lastly, we need to add some network configurations to our VPN endpoint. Insert the following in vpn.tf.
### Network Association ###
resource "aws_ec2_client_vpn_network_association" "client_vpn_association_private" {
client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.my_client_vpn.id
subnet_id = tolist(module.vpc.private_subnets)[0]
}
resource "aws_ec2_client_vpn_network_association" "client_vpn_association_public" {
client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.my_client_vpn.id
subnet_id = tolist(module.vpc.public_subnets)[1]
}
### Authorization ###
resource "aws_ec2_client_vpn_authorization_rule" "authorization_rule" {
client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.my_client_vpn.id
target_network_cidr = "10.0.0.0/16"
authorize_all_groups = true
}
Here we set the following arguments.
# Network Association #
- client_vpn_endpoint_id, is the ID of the client VPN endpoint we created earlier.
- subnet_id, is the ID of the subnet we want to associate with the client connection. If you are not using a VPC module, you might need to adjust this to fit whatever setup you have.
# Authorization #
- client_vpn_endpoint_id, is the ID of the client VPN endpoint we created earlier.
- target_network_cidr, here we set the CIDR for our VPC.
- authorize_all_groups, is set to "true" since we want to allow all groups access to our VPN. I'll explain in the next step how we control access to our VPN per-person based.
Now, we should be able to run terraform plan and apply.
Next step is to go into AWS Console and view our VPN Endpoint. In the top right corner, press "download client configuration". We need this to connect to our VPN. Open the file and insert the following as such.
<cert>
* insert client1.domain.tld.cert we generated earlier.
</cert>
<key>
* insert client1.domain.tld.key we generated earlier.
</key>
Note: Make sure these are placed above reneg-sec 0 in the client configuration we downloaded.
The final step is connecting to our VPN. We need to download AWS VPN client. Other VPN clients should also work, but using AWS own VPN client is recommended. Open AWS VPN Client, press manage profiles -> add profile and press connect. Now, you should be connected to our Client VPN Endpoint.
Note: To access your EC2 instance, you need to allow inbound traffic from the Client VPN security group we created earlier. Remember to use the private IP address of the EC2 instance, since we are connecting to it from inside our VPC.
Add more clients
If we want to add more clients to our Client VPN Endpoint, all we need to do is go back to our OpenVPN easy-rsa repo and run:
./easyrsa build-client-full <new-client-name> nopass
This should create a new client certificate and key which we'll add to a client configuration file like we did in the previous step. We create a new client configuration file for each client we want to give access to our VPN. This way we can control who gets access to our VPC through our Client VPN Endpoint.
Summary
In this article, we have looked at what AWS Client VPN Endpoint is and why we should use it. We've gone through the steps of generating certificates and keys for both our VPN server and our clients. Lastly, we looked at how we provision out a Client VPN Endpoint, using Terraform.