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
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.