Automating Secure VPN Setup on AWS with Terraform

Deeksha Gunde - Aug 21 - - Dev Community

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

  1. AWS CLI
  2. Server and Client certificate uploaded to ACM
  3. Terragrunt/ Terraform
  4. 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> 
Enter fullscreen mode Exit fullscreen mode

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> 
Enter fullscreen mode Exit fullscreen mode

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
}

Enter fullscreen mode Exit fullscreen mode

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
  ]
}

Enter fullscreen mode Exit fullscreen mode

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
}

Enter fullscreen mode Exit fullscreen mode

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
}

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
}

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

3.Plan and apply

Run the following commands in the command prompt

terraform plan
terraform apply
Enter fullscreen mode Exit fullscreen mode

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.

  1. Login to your AWS account, navigate to the VPC dashboard, and select "Client VPN Endpoints", you should see the client endpoint "Test-Client-VPN".

  2. 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>
Enter fullscreen mode Exit fullscreen mode
  • 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.
  1. 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

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.

Add profile

  • Select your profile name from the drop-down and click on Connect - establishes the connection through the VPN

Test connection

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
Enter fullscreen mode Exit fullscreen mode

It is as simple as this, now you can set up a client VPN connection to your AWS resources securely.

Happy Coding!! :)

. .