Post

Automating AWS Client VPN creation with Terraform and Scripts

Abstract

In previous Post Latest AWS Managed Client VPN Connection, we checked how managed AWS VPN Client allows much faster and secure bootstrap OpenVPN server compared to DIY EC2-based instance setup. And there are some manual time-consuming steps that require human interaction. Now we will define and automate these steps to make experience with bootstrapping AWS Client VPN even more smoother.

Target Architecture Diagram

infra.png

Identifying Time-consuming Tasks to Automate

Here is a list of Tasks that are potential candidates for automation:

  • interaction with AWS console wizard while creating VPN Client
  • Certificates and Keys generation
  • Manual Download of OpenVPN configuration
  • Manual Patch of OpenVPN configuration

Automation Step 1 (Automate Certs generation):

Let’s define following script that will automatically check out the latest OpenVPN easy-rsa and generate root CA, clients Cert and Key, servers Cert and Key.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env bash

# 1. generate certs
git clone https://github.com/OpenVPN/easy-rsa.git
cd easy-rsa/easyrsa3
./easyrsa init-pki
./easyrsa init-pki
./easyrsa build-ca nopass
./easyrsa build-server-full server nopass
./easyrsa build-client-full client1.domain.tld nopass

# 2. copy certificates
mkdir ../../certs_$1/
cp pki/ca.crt ../../certs_$1/
cp pki/issued/server.crt ../../certs_$1/
cp pki/private/server.key ../../certs_$1/
cp pki/issued/client1.domain.tld.crt ../../certs_$1/
cp pki/private/client1.domain.tld.key ../../certs_$1/

It supports a parameter with aws region name:

1
./gen_certs.sh us-west-1

The output of certs generation script is following files created in folder named with region postfix:

1
2
3
4
5
6
7
8
 ls -l certs_us-west-1
total 56
-rw-------  1   staff  1172 Sep  11 20:10 ca.crt
-rw-------  1   staff  4460 Sep  11 20:10 client1.domain.tld.crt
-rw-------  1   staff  1704 Sep  11 20:10 client1.domain.tld.key
-rw-------  1   staff  4547 Sep  11 20:10 server.crt
-rw-------  1   staff  1704 Sep  11 20:10 server.key

Automation Step 2 (Automating AWS resources creation with Terraform):

Let’s start with main network topology creation:

  • VPC
  • SN
  • InternetGateway
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
resource "aws_vpc" "main" {
  cidr_block = var.vpc_CIDR

  enable_dns_hostnames = true
  enable_dns_support   = true
  instance_tenancy     = "default"

  tags = local.global_tags
}

resource "aws_default_security_group" "default" {
  vpc_id = aws_vpc.main.id

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = local.global_tags
}

resource "aws_subnet" "sn_az" {

  availability_zone = local.availability_zone

  vpc_id                  = aws_vpc.main.id
  map_public_ip_on_launch = false

  cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 5, 1)

  tags = local.global_tags
}

resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.main.id

  tags = local.global_tags
}

Following Terraform resources will upload certificates to AWS ACM:

  • ACM Client Certificate
  • ACM Server Certificate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
resource "aws_acm_certificate" "vpn_server" {
  private_key       = file("${var.cert_dir}_${local.region}/server.key")
  certificate_body  = file("${var.cert_dir}_${local.region}/server.crt")
  certificate_chain = file("${var.cert_dir}_${local.region}/ca.crt")

  tags = local.global_tags

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_acm_certificate" "vpn_client" {
  private_key       = file("${var.cert_dir}_${local.region}/client1.domain.tld.key")
  certificate_body  = file("${var.cert_dir}_${local.region}/client1.domain.tld.crt")
  certificate_chain = file("${var.cert_dir}_${local.region}/ca.crt")

  tags = local.global_tags
}
  • Client VPN
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    resource "aws_ec2_client_vpn_endpoint" "vpn" {
    description            = "Client VPN example"
    client_cidr_block      = var.vpn_client_CIDR
    split_tunnel           = var.split_tunnel
    server_certificate_arn = aws_acm_certificate.vpn_server.arn
    
    authentication_options {
      type                       = "certificate-authentication"
      root_certificate_chain_arn = aws_acm_certificate.vpn_client.arn
    }
    
    dns_servers = var.dns_servers
    
    connection_log_options {
      enabled = false
    }
    
    tags = local.global_tags
    }
    
  • SN association
1
2
3
4
5
6
7
8
9
resource "aws_ec2_client_vpn_network_association" "vpn_subnets" {
  client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.vpn.id
  subnet_id              = aws_subnet.sn_az.id
  security_groups        = [aws_security_group.vpn_access.id]

  lifecycle {
    ignore_changes = [subnet_id]
  }
}

Automation Step 3 (Automate OpenVPN config download):

This will automatically download *.opvn configuration file to local machine with no need to click pages and navigate in AWS console.

1
2
3
4
5
6
7
8
9
10
11
resource "null_resource" "export-client-config" {
  provisioner "local-exec" {
    command = "aws --region ${local.region} ec2 export-client-vpn-client-configuration --client-vpn-endpoint-id ${aws_ec2_client_vpn_endpoint.vpn.id} --output text>${path.root}/${local.region}_openvpn-client-config.ovpn"
  }

  depends_on = [
    aws_ec2_client_vpn_endpoint.vpn,
    aws_ec2_client_vpn_route.vpn_routes,
    aws_ec2_client_vpn_network_association.vpn_subnets,
  ]
}

Automation Step 4 (Automatic Patching OpenVPN configuration with proper values):

Since downloaded AWS OpenVPN configuration can not apply immediately, and it requires manual modification this is also a great candidate for automation. Following script does all heavy lifting and adds cert, key and modifies server_name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env bash

# Contents of client certificate (.crt) file
echo ""  >> $1_openvpn-client-config.ovpn
echo ""  >> $1_openvpn-client-config.ovpn
echo '<cert>' >> $1_openvpn-client-config.ovpn

cat certs_$1/client1.domain.tld.crt >> $1_openvpn-client-config.ovpn

echo '</cert>' >> $1_openvpn-client-config.ovpn
echo ""  >> $1_openvpn-client-config.ovpn

# Contents of private key (.key) file
echo ""  >> $1_openvpn-client-config.ovpn
echo '<key>' >> $1_openvpn-client-config.ovpn

cat certs_$1/client1.domain.tld.key >> $1_openvpn-client-config.ovpn

echo '</key>' >> $1_openvpn-client-config.ovpn
echo ""  >> $1_openvpn-client-config.ovpn

# Append server to DNS name
sed -i '' 's/cvpn-endpoint/server.cvpn-endpoint/g' $1_openvpn-client-config.ovpn

Automation Step 5 (Adding OpenVPN patching as terraform resource):

1
2
3
4
5
6
7
8
9
10
resource "null_resource" "patch_vpn_config_locally" {
  provisioner "local-exec" {
    command = "${path.root}/patch_certs.sh ${local.region}"
  }

  depends_on = [
    aws_ec2_client_vpn_authorization_rule.vpn_auth_rule,
    null_resource.export-client-config
  ]
}

Results

Now creation of AWS managed Client VPN is fully automated, just run the following commands, and you are ready to connect to you VPN Endpoint.

1
2
./gen_certs.sh us-east-1
terraform apply

AWS VPN is created and in your local folder you will find us-west-1_openvpn-client-config.ovpn config file ready to be uploaded into OpenVPN Connect.

This post is licensed under CC BY 4.0 by the author.