AWS Client VPN with Manually Provisioned Certificates using Terraform
This is a brief explanation of how to use Terraform to setup an AWS CVPN (Client VPN) where the certificates (for VPN authentication) are manually provisioned by yourself and then uploaded into ACM (AWS Certificate Manager).
The advantage of using a manually provisioned approach is that the cost is significantly lower than a managed approach that utilises AWS Private CA (Certificate Authority). Previously, the cost of operating AWS Private CA for us was $800 / Month. Apart from the initial manual steps required to generate the certificate chain, the main disadvantages of this approach over a fully managed approach are that: (1) you also have to store your certificates and keys outside of the cloud in a secure location, and (2) you have to remember to renew certificates before they expire and then replace the expiring certificates with their newer counterparts in ACM.
If you are looking for a fully managed approach instead, you can find this in a previous blog article that I wrote - AWS Client VPN with Managed Certificates using Terraform.
The approach taken in this article was informed by the Timeular blog post: Creating an AWS Client VPN with Terraform. Unfortunately, I could not get their approach to work correctly, and so the approach detailed in my article is an adaption/extension of their approach, but I want to thank them as my work builds upon theirs.
Chain of Trust
We will create a number of certificates that will form a chain of trust. Each subordinate certificate is signed by its parent certificate. Our chain of trust will look like:
- Root CA Certificate.
- Intermediate CA Certificate - this CA certificate is used as an intermediary between the Root CA and our other CAs. This makes it easier to revoke subordinate CA certificates should we need to. This certificate is signed by the Root CA Certificate.
- CVPN CA Certificate - this CA certificate is used for issuing and signing certificates that are used by the CVPN Server and its clients (e.g. users) to connect to the CVPN Server. This certificate is signed by the Intermediate CA Certificate.
- CVPN Server Certificate - this endpoint certificate is for the CVPN server itself in AWS. This certificate is signed by the CVPN CA Certificate.
- CVPN Client Certificate - we create one of these endpoint certificates for each client (e.g. user) that wishes to connect to the CPVN Server. These certificates will also be signed by the CVPN CA Certificate.
The purpose of having a separate Root CA, Intermediate CA, and CVPN CA is that we have delegated trust from the Root to the CVPN via the Intermediate. We can therefore easily isolate the CVPN if needed and manage CVPN certificate issuance and revocation completely separately to the Root. In future it may be desirable to create further delegated CAs from the Intermediate CA for other purposes.
Creating the Certificates
We will manually create all of the CA certificates and endpoint certificates. Whilst various tools are available for this purpose, for convenience, the first time I undertook this I used the GUI tool XCA (which is available for macOS, Linux, and Windows). In this article I will show both the settings required for XCA, and also provide the equivalent OpenSSL commands. After the Certificates are generated we will deploy them and the Client VPN with Terraform further below.
Note: For the OpenSSL commands, I tested against OpenSSL version 3.0.11.
Root CA
In XCA you need to navigate to the Certificates
tab, and then press the New Certificate
button. This presents a dialog with a number of tabs, that should be configured with the following information and settings, after which a new Root CA certificate will be created in the XCA database for you.
- Type:
x509 Certificate
- Source:
- Signing: Create a self signed certificate
- Signature algorithm:
SHA 512
- Template for new certificate:
[default] Empty template
- Subject:
- Internal Name:
My Private Root CA
- Distinguished Name:
- countryName:
GB
- stateOrProvinceName:
My State
- localityName:
My Locality
- organizationName:
My Company
- organizationalUnitName:
DevOps
- commonName:
root.ca.cert.private.mydomain.com
- emailAddress:
devops@mydomain.com
- countryName:
- Private Key:
- Name:
My Private Root CA
- Type:
RSA
- Size:
4096 bit
- Name:
- Internal Name:
- Extensions:
- x509v3 Basic Constraints:
- Type:
Certification Authority
- Type:
- Key Identifier:
x509v3 Subject Key Identifier
x509v3 Authority Key Identifier
- Validity: (10 Years)
- Not Before:
2023-09-01 00:00 GMT
- Not After:
2033-08-31 23:59 GMT
- Not Before:
- x509v3 Basic Constraints:
- Key usage:
- x509v3 Key Usage:
Certificate Sign
CRL Sign
- x509v3 Key Usage:
If you prefer to generate the Root CA with OpenSSL then you can use the following commands:
Intermediate CA
To generate the subordinate Intermediate CA in XCA you need to make sure you have the My Private Root CA
certificate selected in the Certificates
tab, and then press the New Certificate
button. This presents a dialog with a number of tabs, that should be configured with the following information and settings, after which a new Intermediate CA certificate will be created (as a subordinate of the Root CA) in the XCA database for you.
- Type:
x509 Certificate
- Source:
- Signing:
My Private Root CA
- Signature algorithm:
SHA 512
- Template for new certificate:
[default] Empty template
- Signing:
- Subject:
- Internal Name:
My Private Intermediate CA
- Distinguished Name:
- countryName:
GB
- stateOrProvinceName:
My State
- localityName:
My Locality
- organizationName:
My Company
- organizationalUnitName:
DevOps
- commonName:
intermediate.ca.cert.private.mydomain.com
- emailAddress:
devops@mydomain.com
- countryName:
- Private Key:
- Name:
My Private Intermediate CA
- Type:
RSA
- Size:
4096 bit
- Name:
- Internal Name:
- Extensions:
- x509v3 Basic Constraints:
- Type:
Certification Authority
- Type:
- Key Identifier:
x509v3 Subject Key Identifier
x509v3 Authority Key Identifier
- Validity: (5 Years)
- Not Before:
2023-09-01 00:00 GMT
- Not After:
2028-08-31 23:59 GMT
- Not Before:
- x509v3 Basic Constraints:
- Key usage:
- x509v3 Key Usage:
Certificate Sign
CRL Sign
- x509v3 Key Usage:
If you prefer to generate the Intermediate CA with OpenSSL then you can use the following commands:
CVPN CA
To generate the subordinate CVPN CA in XCA you need to make sure you have the My Private Intermediate CA
certificate selected in the Certificates
tab, and then press the New Certificate
button. This presents a dialog with a number of tabs, that should be configured with the following information and settings, after which a new CVPN CA certificate will be created (as a subordinate of the Intermediate CA) in the XCA database for you.
- Type:
x509 Certificate
- Source:
- Signing:
My Private Intermediate CA
- Signature algorithm:
SHA 512
- Template for new certificate:
[default] Empty template
- Signing:
- Subject:
- Internal Name:
My Private CVPN CA
- Distinguished Name:
- countryName:
GB
- stateOrProvinceName:
My State
- localityName:
My Locality
- organizationName:
My Company
- organizationalUnitName:
DevOps
- commonName:
cvpn.ca.cert.private.mydomain.com
- emailAddress:
devops@mydomain.com
- countryName:
- Private Key:
- Name:
My Private CVPN CA
- Type:
RSA
- Size:
2048 bit
- Name:
- Internal Name:
- Extensions:
- x509v3 Basic Constraints:
- Type:
Certification Authority
- Type:
- Key Identifier:
x509v3 Subject Key Identifier
x509v3 Authority Key Identifier
- Validity: (3 Years)
- Not Before:
2023-09-01 00:00 GMT
- Not After:
2026-08-31 23:59 GMT
- Not Before:
- x509v3 Basic Constraints:
- Key usage:
- x509v3 Key Usage:
Certificate Sign
CRL Sign
- x509v3 Key Usage:
Note that for the CVPN CA, the Private Key size is only 2048 bits and not 4096 bits (as used by the Root and Intermediate CA); this is due to a limitation with the AWS Client VPN only supporting a maximum key size of 2048 bits.
If you prefer to generate the CVPN CA with OpenSSL then you can use the following commands:
CVPN Server Certificate
To generate the endpoint CVPN Server certificate in XCA you need to make sure you have the My Private CVPN CA
certificate selected in the Certificates
tab, and then press the New Certificate
button. This presents a dialog with a number of tabs, that should be configured with the following information and settings, after which a new CVPN Server certificate will be created (as a subordinate of the CVPN CA) in the XCA database for you.
- Type:
x509 Certificate
- Source:
- Signing:
My Private CVPN CA
- Signature algorithm:
SHA 512
- Template for new certificate:
[default] Empty template
- Signing:
- Subject:
- Internal Name:
My Private CVPN - Server
- Distinguished Name:
- countryName:
GB
- stateOrProvinceName:
My State
- localityName:
My Locality
- organizationName:
My Company
- organizationalUnitName:
DevOps
- commonName:
cvpn-server.cvpn.cert.private.mydomain.com
- emailAddress:
devops@mydomain.com
- countryName:
- Private Key:
- Name:
My Private CVPN - Server
- Type:
RSA
- Size:
2048 bit
- Name:
- Internal Name:
- Extensions:
- x509v3 Basic Constraints:
- Type:
End Entity
- Type:
- Key Identifier:
x509v3 Subject Key Identifier
x509v3 Authority Key Identifier
- Validity: (3 Years)
- Not Before:
2023-09-01 00:00 GMT
- Not After:
2026-08-31 23:59 GMT
- Not Before:
- x509v3 Basic Constraints:
- Key usage:
- x509v3 Key Usage:
Digital Signature
- x509v3 Extended Key Usage:
TLS Web Server Authentication
- x509v3 Key Usage:
If you prefer to generate the CVPN Server certificate with OpenSSL then you can use the following commands:
CVPN Client Certificate(s)
To generate the endpoint CVPN Client certificate(s) that will enable a client (e.g. user) to connect to the CVPN Server, you may use the following as a template and repeat it for as many clients as you need; just replace <<username>>
with the username of the user, etc.
In XCA you need to make sure you have the My Private CVPN CA
certificate selected in the Certificates
tab, and then press the New Certificate
button. This presents a dialog with a number of tabs, that should be configured with the following information and settings, after which a new CVPN Client certificate will be created (as a subordinate of the CVPN CA) in the XCA database for you.
- Type:
x509 Certificate
- Source:
- Signing:
My Private CVPN CA
- Signature algorithm:
SHA 512
- Template for new certificate:
[default] Empty template
- Signing:
- Subject:
- Internal Name:
My Private CVPN - User - <<username>>
- Distinguished Name:
- countryName:
<<countryCode>>
- stateOrProvinceName:
<<county>>
- localityName:
<<city>>
- organizationName:
<<organisation>>
- organizationalUnitName:
<<department>>
- commonName:
<<username>>.cvpn.cert.private.mydomain.com
- emailAddress:
<<email>>
- countryName:
- Private Key:
- Name:
My Private CVPN - User - <<username>>
- Type:
RSA
- Size:
2048 bit
- Name:
- Internal Name:
- Extensions:
- x509v3 Basic Constraints:
- Type:
End Entity
- Type:
- Key Identifier:
x509v3 Subject Key Identifier
x509v3 Authority Key Identifier
- Validity: (1 Year)
- Not Before:
2023-09-01 00:00 GMT
- Not After:
2024-08-31 23:59 GMT
- Not Before:
- x509v3 Basic Constraints:
- Key usage:
- x509v3 Key Usage:
Digital Signature
- x509v3 Extended Key Usage:
TLS Web Client Authentication
- x509v3 Key Usage:
If you prefer to generate the CVPN Client certificate for the user with OpenSSL then you can use the following commands:
Deploying the Certificates in Terraform
Now that we have generated CA certificates and endpoint certificates, we can now deploy them to ACM (AWS Certificate Manager) by using Terraform.
Note that as the client (e.g. user) certificates are signed by the CVPN CA (i.e. the same CA as used by the Client VPN and the Client VPN Server certificate), they do not need to be uploaded into AWS ACM.
Creating the Client VPN in Terraform
Now that we have the CA certificates and endpoint certificates in place, we can create and configure the AWS Client VPN by using Terraform.
The following placeholders in the code above need to be filled out with your own Terraform AWS configuration values:
<YOUR VPC ID>
- the AWS ID of your VPC in which you wish to deploy the Client VPN.<ID OF YOUR IPv4 SUBNET WHERE THE CLIENT VPN WILL TERMINATE>
- the AWS ID of the IPv4 Subnet within your VPC where you wish VPN traffic to arrive to.<YOUR IPv4 SUBNET WHERE THE CLIENT VPN WILL TERMINATE>
- the IPv4 Subnet CIDR address within your VPC where you wish VPN traffic to arrive to.<YOUR IPv6 SUBNET WHERE THE CLIENT VPN WILL TERMINATE>
- the IPv6 Subnet CIDR address within your VPC where you wish VPN traffic to arrive to.
Connecting to the Client VPN
Once you have the above setup you can download a skeleton OpenVPN configuration file from the AWS Dashboard.
The OpenVPN configuration file provided by AWS will be incomplete, and will something look like this:
You will need to modify it to add:
- A
<cert>
section containing the certificate for one of the CVPN Client certificate(s) that you generated above with XCA or OpenSSL. - A
<key>
section containing the private key for the CVPN Client certificate that you are using.
You should then have a complete OpenVPN configuration file that looks similar to this:
You may now use your favourite OpenVPN client tool, with the above OpenVPN configuration file to connect to your new Client VPN; I personally use Tunnelblick. Enjoy!