As an AWS best practice, you want to build security into your solution at every level, and that includes networking. In the majority of cases this means keeping your resources in a private subnet, only accessible through VPN or your corporate network.
I recently ran into this challenge working with Managed Workflows for Apache Airflow (MWAA), a game-changer for orchestrating data workflows. Being security conscious,we wanted to set our MWAA environment to private access, . This is where a VPN (Virtual Private Network) setup becomes invaluable, ensuring that your team can access the MWAA environment safely and efficiently.
In this post, we'll explore the manual steps to set up a VPN client endpoint and then see how Terraform can automate and streamline this process.
Prerequisites
- AWS CLI
- Server and Client certificate uploaded to ACM
- Terragrunt/ Terraform
- MWAA environment with private access
We assume you already have ACM certificates generated and uploaded to ACM (Amazon Certificate Manager). If not, you can refer to and follow the steps in this tutorial
Generate and add Server and Client certificates to ACM - Authentication
Once you have generated the certificates upload them to ACM using below AWS CLI commands
Server certificate
aws acm import-certificate --certificate fileb://server.crt --private-key fileb://server.key --certificate-chain fileb://ca.crt –region <your_region>
Client certificate
aws acm import-certificate --certificate fileb://client1.domain.tld.crt --private-key fileb://client1.domain.tld.key --certificate-chain fileb://ca.crt –region <your_region>
Setup of VPN access, the hard way
You can set up VPN connection access to your private MWAA environment manually following this tutorial - AWS Client VPN, and as you can see there are so many resources that need to be created and configured. The whole process of doing this feels long and tedious.
Terraform to the Rescue
While these manual steps are straightforward, they can be time-consuming and error-prone, especially when you need to repeat them across multiple environments. Terraform can automate this entire process, ensuring consistency and saving time.
Terraform, an open-source infrastructure as a code (IaC) tool, allows you to define and provision your AWS infrastructure using a simple, declarative configuration language. You can create a simple terraform script that handles the creation and configuring of the AWS resources for client VPN setup just by running one simple command from your CLI!!!
THE Terraform script
Below we provide a simple way of structuring your terraform script ( main.tf
) to achieve this.
Let’s break down the script and see how each part contributes to the setup.
1. Set up AWS provider
Tell Terraform to use AWS as the provider and specify which AWS region where the resources will be created. The var.aws_region
is the terraform variable defined to make it easy to switch the regions as needed.
provider "aws" {
region = var.aws_region
}
2. Cloudformation stack for Client VPN endpoint
Here, we’re using a CloudFormation stack to set up the VPN client endpoint. This stack includes all the necessary resources and configurations, such as security group IDs, VPC ID, and subnet ID. By using CloudFormation, we can manage the resources as a single unit, making updates and maintenance straightforward. We leverage the terraform-defined resource aws_cloudformation_stack
to configure it on the AWS region specified.
resource "aws_cloudformation_stack" "vpn_client_stack" {
name = "VPNClientStack"
template_body = <<TEMPLATE
AWSTemplateFormatVersion: '2010-09-09'
Description: AWS VPN Client Setup
Parameters:
SecurityGroupId:
Type: String
Description: The ID of the existing security group to use
VpcId:
Type: String
Description: The ID of the VPC where the VPN will be created
SubnetId:
Type: String
Description: The ID of the subnet to associate with the VPN
Resources:
VpnClientEndpoint:
Type: AWS::EC2::ClientVpnEndpoint
Properties:
AuthenticationOptions:
- Type: certificate-authentication
MutualAuthentication:
ClientRootCertificateChainArn: "${data.aws_acm_certificate.client.arn}"
ClientCidrBlock: "${var.client_cidr}"
ConnectionLogOptions:
Enabled: false
Description: "Client VPN Endpoint"
ServerCertificateArn: "${data.aws_acm_certificate.server.arn}"
VpcId: !Ref VpcId
VpnPort: 443
TransportProtocol: udp
TagSpecifications:
- ResourceType: "client-vpn-endpoint"
Tags:
- Key: Name
Value: Test-Client-VPN
SplitTunnel: true
SecurityGroupIds:
- Ref: SecurityGroupId
Outputs:
VpnClientEndpointId:
Description: "VPN Client Endpoint ID"
Value: !Ref VpnClientEndpoint
TEMPLATE
parameters = {
SecurityGroupId = var.security_group_id
VpcId = var.vpc_id
SubnetId = var.private_subnet_ids[0]
}
depends_on = [
data.aws_acm_certificate.server,
data.aws_acm_certificate.client
]
}
This snippet assumes that you have already uploaded the certificates to ACM, and fetches the client and server certificates ARNs - data.aws_acm_certificate.server, data.aws_acm_certificate.client
.
3. Associating subnet to VPN endpoint
This resource associates the VPN client endpoint with a specified subnet, defining the network range that can be accessed via the VPN. If you are setting up the VPN endpoint for a private resource, ( in our case a an MWAA environment), make sure that the private subnet you are passing here is the one that your resource is in.
resource "aws_ec2_client_vpn_network_association" "client_vpn_association_private" {
client_vpn_endpoint_id = aws_cloudformation_stack.vpn_client_stack.outputs["VpnClientEndpointId"]
subnet_id = var.private_subnet_id
}
4. Adding authorization rule
Authorization rules determine who can access the VPN. In this script, all traffic within the VPC CIDR block is authorized, and access is granted to all user groups. Make sure to provide the VPC CIDR block associated with the MWAA environment.
resource "aws_ec2_client_vpn_authorization_rule" "authorization_rule" {
client_vpn_endpoint_id = aws_cloudformation_stack.vpn_client_stack.outputs["VpnClientEndpointId"]
target_network_cidr = var.vpc_cidr
authorize_all_groups = true
}
Steps to run the script
We discussed the core content of the terraform script, feel free to add any missing information like variables, data resources, and output blocks appropriately and package them neatly in separate files in a project folder or within the same script. See below sections for sample references.
Once you have everything ready and in place, we are ready to test our script!
1. Initialize Your Terraform Workspace
Navigate to the directory containing your main.tf
file and run the following command to initialize your Terraform workspace:
terraform init
2. Configure Your Variables
Create a variables.tf file to define the variables used in your script, such as aws_region, security_group_id, vpc_id, private_subnet_ids, and client_cidr. Follow the sample below:
variable "aws_region" {
description = "The AWS region to deploy the resources"
type = string
}
variable "security_group_id" {
description = "The ID of the security group"
type = string
}
variable "vpc_id" {
description = "The ID of the VPC"
type = string
}
variable "private_subnet_ids" {
description = "List of private subnet IDs"
type = list(string)
}
variable "client_cidr" {
description = "The CIDR block for the VPN client"
type = string
}
Now populate the variable values by creating a terraform.tfvars
aws_region = "us-east-1"
security_group_id = "<security_group_id>"
vpc_id = "<vpc_id>"
private_subnet_ids = ["<private_subnet_id>"]
client_cidr = "10.0.0.0/16" # Adjust CIDR block as needed
3.Plan and apply
Run the following commands in the command prompt
terraform plan
terraform apply
Review the outputs of the plan
command to ensure the configuration, and the apply
command will create the resources on AWS.
Testing the setup
Now you have deployed the script that created the client VPN endpoint. To test if the endpoint is created properly, first ensure that the endpoint is created successfully.
Login to your AWS account, navigate to the VPC dashboard, and select "Client VPN Endpoints", you should see the client endpoint "Test-Client-VPN".
Download the configuration file of the client VPN endpoint by clicking on "Download Client Configuration".
- In the configuration.ovpn file, add the below block (paste above
reneg-sec 0
):
<cert>
Contents of client certificate (.crt) file
</cert>
<key>
Contents of private key (.key) file
</key>
- Copy the contents of your client certificate and client private key files in their respective tags in the above block. Save and close the file.
- Connecting to the VPN using the AWS VPN client app: You can download the AWS Client VPN from here AWS Provided Client Connect
Open the AWS VPN client application on your machine
Select File -> Manage Profiles
- Click on Add Profile, give a name to your connection, select the configuration file you downloaded from AWS Client VPN, and click Add Profile.
- Select your profile name from the drop-down and click on Connect - establishes the connection through the VPN
Cleaning up! Deleting the client VPN endpoint
To delete the client VPN setup and all the resources associated with it, you can do it by simply running the below command
terraform destroy
It is as simple as this, now you can set up a client VPN connection to your AWS resources securely.
Happy Coding!! :)