logo
Secure Remote Access with AWS Client VPN Endpoints using CDK

Secure Remote Access with AWS Client VPN Endpoints using CDK

AWS Client VPN endpoint provides a secure solution for connecting remote users to private/internal AWS resources with ease.

Published Feb 9, 2024

The AWS Cloud Development Kit (CDK) allows you to define cloud infrastructure using familiar programming languages. First you need to install the CDK dependencies and create a fresh CDK stack if you don't already have one. More details on AWS documentation.
Before creating the CDK stack for AWS client VPN endpoint, if you don't have a user authentication service such as Azure AD or SAML, you can use mutual authentication type where we will be creating the certificates and the keys ourselves. We can use easy-rsa CLI tool to create the CA, server certificate and key, client certificate and key. Please follow this detailed step by step documentation by AWS and finally you will have the ARN of the ACM certificate.
Now we have the server certificate ARN and below CDK stack will create the required components for AWS client VPN endpoint. Please replace the CDK stack in the lib directory with below code,
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';

export class AwsVpnStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

//Create a VPC with Public and Private subnets
const demoVpc = new ec2.Vpc(this, 'DemoVPC', {
vpcName: 'Demo-VPC',
cidr: '10.0.0.0/16',
maxAzs: 2,
subnetConfiguration: [
{
cidrMask: 24,
name: 'private-subnet',
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
{
cidrMask: 24,
name: 'public-subnet',
subnetType: ec2.SubnetType.PUBLIC,
},
],
});

const vpnCertificateArn = ''; //Paste the correct server certificate ARN

//Create a SG to allow client connections
const vpnSG = new ec2.SecurityGroup(this, 'DemoSG', {
securityGroupName: 'demo-vpn-sg',
vpc: demoVpc,
description: 'Allow Client Connections',
allowAllOutbound: true
});

vpnSG.addIngressRule(vpnSG, ec2.Port.allTraffic(), 'Allow All Traffic within SG');

//Create the Client VPN Endpoint with Mutual Authentication
const cfnClientVpnEndpoint = new ec2.CfnClientVpnEndpoint(this, 'CfnClientVpnEndpoint', {
authenticationOptions: [{
type: 'certificate-authentication',
mutualAuthentication: {
clientRootCertificateChainArn: vpnCertificateArn,
},
}],
clientCidrBlock: '172.1.0.0/16',
connectionLogOptions: {
enabled: false,
},
serverCertificateArn: vpnCertificateArn,
description: 'VPN for connecting Private subnets',
securityGroupIds: [vpnSG.securityGroupId],
sessionTimeoutHours: 12,
tagSpecifications: [{
resourceType: 'client-vpn-endpoint',
tags: [{
key: 'Name',
value: 'demo-vpn-endpoint',
}],
}],
dnsServers: ['10.0.0.2', '10.0.0.3'],
splitTunnel: true,
vpcId: demoVpc.vpcId
});

//Add Authorization Rules to grant access to VPC
const demoVpnAuthorizationRule = new ec2.CfnClientVpnAuthorizationRule(this, 'DemoVpnAuthorizationRule', {
clientVpnEndpointId: cfnClientVpnEndpoint.ref,
targetNetworkCidr: demoVpc.vpcCidrBlock,
authorizeAllGroups: true
});

//Add Network associations to configure Private subnet routes and associations
for (const subnet of demoVpc.privateSubnets) {
new ec2.CfnClientVpnTargetNetworkAssociation(this, 'NetworkAssociation_' + subnet, {
clientVpnEndpointId: cfnClientVpnEndpoint.ref,
subnetId: subnet.subnetId,
});
}
}
}
After this stack is created, it should be deployed. This will create the Endpoint and necessary infrastructure. If you already have a VPC, remove the VPC code block and add correct VPC details in the references.
cdk deploy
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
[WARNING] aws-cdk-lib.aws_ec2.VpcProps#cidr is deprecated.
Use ipAddresses instead
This API will be removed in the next major release.

✨ Synthesis time: 2.4s

AwsVpnStack: start: Building 7f18a11296f35510ee16538afec983ed6312e12afbf81b777089a9f8e34e2474:current_account-current_region
AwsVpnStack: success: Built 7f18a11296f35510ee16538afec983ed6312e12afbf81b777089a9f8e34e2474:current_account-current_region
AwsVpnStack: start: Publishing 7f18a11296f35510ee16538afec983ed6312e12afbf81b777089a9f8e34e2474:current_account-current_region
AwsVpnStack: start: Building 952a8ea181abf7e1aff71fde65f7d912aeaf26bb47181981a26bf92f55b991b4:current_account-current_region
AwsVpnStack: success: Built 952a8ea181abf7e1aff71fde65f7d912aeaf26bb47181981a26bf92f55b991b4:current_account-current_region
AwsVpnStack: start: Publishing 952a8ea181abf7e1aff71fde65f7d912aeaf26bb47181981a26bf92f55b991b4:current_account-current_region
AwsVpnStack: success: Published 7f18a11296f35510ee16538afec983ed6312e12afbf81b777089a9f8e34e2474:current_account-current_region
AwsVpnStack: success: Published 952a8ea181abf7e1aff71fde65f7d912aeaf26bb47181981a26bf92f55b991b4:current_account-current_region
This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

IAM Statement Changes
┌───┬──────────────────────────────────────────────────┬────────┬──────────────────────────────────────────────────┬───────────────────────────────────────────────────┬───────────┐
│ │ Resource │ Effect │ Action │ Principal │ Condition │
├───┼──────────────────────────────────────────────────┼────────┼──────────────────────────────────────────────────┼───────────────────────────────────────────────────┼───────────┤
│ + │ ${Custom::VpcRestrictDefaultSGCustomResourceProv │ Allow │ sts:AssumeRole │ Service:lambda.amazonaws.com │ │
│ │ ider/Role.Arn} │ │ │ │ │
├───┼──────────────────────────────────────────────────┼────────┼──────────────────────────────────────────────────┼───────────────────────────────────────────────────┼───────────┤
│ + │ arn:${AWS::Partition}:ec2:${AWS::Region}:${AWS:: │ Allow │ ec2:AuthorizeSecurityGroupEgress │ AWS:${Custom::VpcRestrictDefaultSGCustomResourceP │ │
│ │ AccountId}:security-group/${DemoVPC2409DB3F.Defa │ │ ec2:AuthorizeSecurityGroupIngress │ rovider/Role} │ │
│ │ ultSecurityGroup} │ │ ec2:RevokeSecurityGroupEgress │ │ │
│ │ │ │ ec2:RevokeSecurityGroupIngress │ │ │
└───┴──────────────────────────────────────────────────┴────────┴──────────────────────────────────────────────────┴───────────────────────────────────────────────────┴───────────┘
IAM Policy Changes
┌───┬────────────────────────────────────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────┐
│ │ Resource │ Managed Policy ARN │
├───┼────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${Custom::VpcRestrictDefaultSGCustomResourceProvider/Role} │ {"Fn::Sub":"arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"} │
└───┴────────────────────────────────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────┘
Security Group Changes
┌───┬───────────────────┬─────┬────────────┬───────────────────┐
│ │ Group │ Dir │ Protocol │ Peer │
├───┼───────────────────┼─────┼────────────┼───────────────────┤
│ + │ ${DemoSG.GroupId} │ In │ Everything │ ${DemoSG.GroupId} │
│ + │ ${DemoSG.GroupId} │ Out │ Everything │ Everyone (IPv4) │
└───┴───────────────────┴─────┴────────────┴───────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Do you wish to deploy these changes (y/n)? y
AwsVpnStack: deploying... [1/1]
AwsVpnStack: creating CloudFormation changeset...

✅ AwsVpnStack

✨ Deployment time: 571.78s

Stack ARN:
arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/AwsVpnStack/xxxxxxxxxxxxxxxx

✨ Total time: 574.18s
To connect to this VPN endpoint you need to download AWS VPN Client application and add the correct VPN profile. Before adding a new profile you need to have the VPN configuration file which you can download either from the service portal of your vpn endpoint or directly from AWS console. Once you download the .ovpn file and if you used mutual authentication method, you must add client certificate and key in to the same .ovpn file. To complete that please follow this detailed document by AWS. After the configuration file is ready, create a profile and connect to the VPN.
AWS VPN Client
AWS VPN Client
Now you should be able to access any internal service within your VPC(Make sure to configure proper Security Group Rules).
Connected to EC2 with a Private IP
Connected to EC2 with a Private IP
If you have multiple VPCs, you do not need to create multiple Client VPN endpoints. Instead it is possible to create a AWS Transit gateway and connect the VPCs as TGW attachments, then configure proper authorization rules and subnet associations in the existing client VPN endpoint.
In conclusion, AWS Client VPN endpoints offer a secure and scalable solution for enabling remote access to your AWS resources. Leveraging the AWS CDK in TypeScript makes the process of creating and managing these endpoints straightforward and efficient, allowing you to focus on providing seamless connectivity for your remote users.