Danny Nunez

6 minutes read

AWS Client VPN With Terraform

Often times we want to work on a project utilizing AWS, but we do not want it exposed to the world. There are several options for this and most require a lot of work. One option is setup a vpn in the cloud, no thanks. Or perhaps we could setup a bastion server with a public ip and utilize ssh tunneling, again no thanks. I prefer to use AWS’s Client VPN

AWS’s Client VPN allows you to quickly and easy setup private access to your aws resources through a managed vpn service. You will be able to access your Client VPN from anywhere using an OpenVPN-based VPN client. AWS offers a client to use for Mac or Windows OS see here.

Please be aware that a Client VPN is not free and charges apply per connection and each connection will be billed for the full hour. Charges also apply for any data out. You can get more pricing information here . Please do not run the terraform code if you do not want to pay for AWS resources. You have been warned.

Dependencies

There are several requirements that need to be installed or configured prior to continuing with this tutorial.

Skills Required:

  • General Understanding on AWS
  • Comfortable with using the command line interface
  • General Understanding of Linux
  • Ability to install applications

Create Certs needed for Mutual Authentication

AWS’s Client VPN uses certificates to perform authentication between the client and the server. We will need to create our own using easyrsa.

First Lets Initialize pki. CD in the terraform directory and run the following command.

../easy-rsa/easyrsa3/easyrsa init-pki

Now lets build the CA. This command will present a prompt to enter in the Common Name for the Certificate.

../easy-rsa/easyrsa3/easyrsa build-ca nopass

Enter in your Common Name like so.

$ ../easy-rsa/easyrsa3/easyrsa build-ca nopass
Using SSL: openssl LibreSSL 2.2.7
Generating RSA private key, 2048 bit long modulus
....................+++
.................................................+++
e is 65537 (0x10001)
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Common Name (eg: your user, host, or server name) [Easy-RSA CA]:MyCommonName

Generate the server certificate and key.

../easy-rsa/easyrsa3/easyrsa build-server-full server nopass

Generate the client certificate and key.

../easy-rsa/easyrsa3/easyrsa build-client-full client1.domain.tld nopass

Setting up Terragrunt and Creating the basic AWS resources.

First we will need to create our Terragrunt HCL file. CD into the terraform directory and create a terragrunt.hcl file with the following contents. Update the bucket name to something unique. S3 bucket names are required to be unique globally. Terragrunt will automatically create both the S3 bucket and dynamoDB table needed to manage the state of your infrastructure for more information on this see here.

inputs = {
  environment = "dev"
  region = "us-east-1"
  key_name  = "dev"
  profile   = "default"
}

remote_state {
  backend = "s3"
  config = {
    bucket = "{place a unique name here}-terraform-state"
    key = "terraform/${path_relative_to_include()}/terraform.tfstate"
    region = "us-east-1"
    encrypt = true
    dynamodb_table = "terraform-state"
  }
}

Your Terraform directory contents should now look as follows.

├── create.yaml
├── destroy.yaml
├── docker
│   ├── docker.tf
│   ├── instance_profile.tf
│   ├── main.tf
│   ├── security-group.tf
│   ├── terragrunt.hcl
│   └── varibles.tf
├── key-pair
│   ├── main.tf
│   ├── terragrunt.hcl
│   └── varibles.tf
├── pki
│   ├── lots of files here
├── terragrunt.hcl
└── vpc
    ├── main.tf
    ├── subnets.tf
    ├── terragrunt.hcl
    ├── varibles.tf
    └── vpc.tf

Now we are going to run the ansible playbook to create the the following resources.

  • Server Certificate
  • Client Certificate
  • VPC
  • Private Subnet
  • Public Subnet
  • Public Route Table
  • Route Table Association
  • Elastic IP Address
  • Internet Gateway
  • Nat Gateway
  • Network acl

CD into the terraform directory and run the following commands.

ansible-playbook import-certs.yaml
ansible-playbook create.yaml

Great now we get to the good stuff. It is now time to create the Client VPN. CD in your terraform/vpc directory and create a file named client-vpn-endpoint.tf.

touch client-vpn-endpoint.tf

Your terraform/vpc directory should now look as follows:

├── client-vpn-endpoint.tf
├── main.tf
├── subnets.tf
├── terragrunt.hcl
├── varibles.tf
└── vpc.tf

Open the client-vpn-endpoint.tf file and add the following content. This will Create the AWS Client VPN Endpoint.

data "aws_acm_certificate" "server" {
  domain = "server"
}

data "aws_acm_certificate" "client" {
  domain = "client1.domain.tld"
}

resource "aws_ec2_client_vpn_endpoint" "dev" {

  description            = "clientvpn-endpoint"
  server_certificate_arn = data.aws_acm_certificate.server.arn
  client_cidr_block      = "172.20.0.0/16"
  split_tunnel           = true

  dns_servers = ["75.75.75.75", "76.76.76.76"]

  authentication_options {
    type                       = "certificate-authentication"
    root_certificate_chain_arn = data.aws_acm_certificate.client.arn
  }

  connection_log_options {
    enabled = false
  }

}

resource "aws_ec2_client_vpn_network_association" "this" {
  client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.dev.id
  subnet_id              = aws_subnet.public_a.id
}

resource "null_resource" "client_vpn_ingress" {
  depends_on = [aws_ec2_client_vpn_endpoint.dev]
  provisioner "local-exec" {
    when    = create
    command = "aws ec2 authorize-client-vpn-ingress --client-vpn-endpoint-id ${aws_ec2_client_vpn_endpoint.dev.id} --target-network-cidr 0.0.0.0/0 --authorize-all-groups"
  }
  lifecycle {
    create_before_destroy = true
  }
}

resource "null_resource" "export_endpoint_configuration" {
  depends_on = [null_resource.client_vpn_ingress]
  provisioner "local-exec" {
    when    = create
    command = "aws ec2 export-client-vpn-client-configuration --client-vpn-endpoint-id ${aws_ec2_client_vpn_endpoint.dev.id}  --output text > ../certs/client-config.ovpn"
  }
  lifecycle {
    create_before_destroy = true
  }
}

resource "null_resource" "append_client_certificate" {
  depends_on = [null_resource.export_endpoint_configuration]
  provisioner "local-exec" {
    when    = create
    command = "echo 'cert ${replace(path.cwd,"vpc","certs")}/client1.domain.tld.crt'  >> ../certs/client-config.ovpn"
  }
  lifecycle {
    create_before_destroy = true
  }
}

resource "null_resource" "append_client_key" {
  depends_on = [null_resource.export_endpoint_configuration]
  provisioner "local-exec" {
    when    = create
    command = "echo 'key ${replace(path.cwd,"vpc","certs")}/client1.domain.tld.key'  >> ../certs/client-config.ovpn"
  }
  lifecycle {
    create_before_destroy = true
  }
}

Now that is added, save your file and run the following command in your terminal.

terragrunt apply

Now lets setup our profile with AWS Client VPN. Open the AWS Client VPN application than

Click File > Manage Profiles

aws client vpn manage profile

Click add Profile

aws client vpn manage profile

Link the profile to the terraform/certs/client-config.ovp file

aws client vpn manage profile

Now connect to your VPN. Once connected ssh into your ec2 instance.

ssh ec2-user@10.200.217.138
The authenticity of host '10.200.217.138 (10.200.217.138)' can't be established.
ECDSA key fingerprint is SHA256:CHluwEG/6+0OdOkaIVCIyMv3qqYPf6EtNXjO1Vm+69E.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.200.217.138' (ECDSA) to the list of known hosts.

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/

Great you are now connected.

Clean up

Now it is time to destroy our resources to avoid unwanted charges.

  1. Disconnect from the Client VPN
  2. Run the destroy ansible playbook in the terraform directory
  3. Delete the Certificates in the AWS Console located at https://console.aws.amazon.com/acm/home?region=us-east-1#/
  4. Delete the S3 bucket and its contents created by Terragrunt
  5. Delete the DynamoDB Table created by Terragrunt

Recent posts

Categories