Simplifying Developer Access with Client VPN

Posted August 10, 2024 by Trevor Roberts Jr ‐ 5 min read

"Hey! Can you add my IP address to the security group, real quick?" If you have ever had this question interrupt your day...several times a day, read on to see how you can solve it with Client VPN...

Introduction

AWS Client VPN is a managed OpenVPN offering to simplify secure developer access to resources running in your VPC. If your developers are not comfortable using AWS Systems Manager Session Manager or configuration management tools like Ansible or the AWS Systems Manager Run Command, Client VPN can be provide a secure alternative access experience they are more familiar with.

How does it work?

Figure 01: Client VPN Architecture
Figure 01: Client VPN Architecture

When you deploy Client VPN, you select the subnet that you want to associate the service with. This process places a Client VPN Elastic Network Interface (ENI) in that subnet and adds the subnet to Client VPN route table that is pushed down to developer machines when they establish the VPN connection.

Your developers do not have access to that subnet automatically, though. Remember that AWS services design for security by default! You must also create an authorization rule to grant access to that subnet. Your next question may be, "are you allowed to authorize multiple subnets?" Of course you can! Just be aware that if you want to authorize subnets that are in other VPCs, you must meet the following conditions:

  1. The associated subnet must have connectivity to the subnets you wish to connect to via VPC Peering or Transit Gateway (TGW)
  2. The associated subnet's route table must be updated with the routes to those networks via the peering connection or the TGW.
  3. You must update the Client VPN route table with the routes to the target subnets as well.

While that sounds like a lot of setup, it is pretty to automate with your infrastructure as code (IaC) tool of choice. I'm using, you guessed it, Pulumi for this example.

Let's Automate with Pulumi

Before we begin, you need to have an authentication mechanism determined and setup already for your developers to connect to Client VPN. The service supports using either certificates or an IdP that supports SAML. In this blog, I use IAM identity Center.

In the code samples below, I highlight the resources that are specific to Client VPN, but the entire Pulumi stack source code, including a test instance deployment to ping, can be found on GitHub:

// Create the Client VPN Endpoint
		vpnEndpoint, err := ec2clientvpn.NewEndpoint(ctx, "vpnEndpoint", &ec2clientvpn.EndpointArgs{
			VpcId:                vpc.ID(),
			SecurityGroupIds:     pulumi.StringArray{vpnSecurityGroup.ID()},
			ClientCidrBlock:      pulumi.String(clientCidrBlock),
			DnsServers:           pulumi.StringArray{pulumi.String("172.16.0.2")},
			ServerCertificateArn: pulumi.String(serverCertificateArn),
			ConnectionLogOptions: &ec2clientvpn.EndpointConnectionLogOptionsArgs{
				Enabled:            pulumi.Bool(true),
				CloudwatchLogGroup: logGroup.Name,
			},
			AuthenticationOptions: ec2clientvpn.EndpointAuthenticationOptionArray{
				&ec2clientvpn.EndpointAuthenticationOptionArgs{
					Type:                       pulumi.String("federated-authentication"),
					SamlProviderArn:            pulumi.String(samlProviderArn),
					SelfServiceSamlProviderArn: pulumi.String(selfServiceSamlProviderArn),
				},
			},
			SplitTunnel: pulumi.Bool(true),
			Tags: pulumi.StringMap{
				"Name": pulumi.String("AWS SSO Client VPN"),
			},
		})
		if err != nil {
			return err
		}

This resource deploys the Client VPN endpoint. You provide VPC and subnet information that the Client VPN uses to provide connectivity to devleopers. Here are other interesting parameters to be aware of:

  1. SecurityGroupIds - Assign one or more security groups (SG) to the Client VPN Endpoint. One component of providing developers access to internal resources is by allowing traffic from any of the SGs assigned to Client VPN. This gets you out of the business of constantly updating allowlists for SSH access from developers' IP addresses!
  2. ClientCidrBlock - A /22 address space from which Client VPN assigns IP addresses to developer machines that make a VPN connection. Note that this address space cannot be in use in any VPC you plan to eventually peer with your associated VPC.
  3. DnsServers - Either specify the VPC DNS IP address (as I did), or, if you use Route 53 Resolvers or your own DNS server, specify those IP addresses here.
  4. ConnectionLogOptions - Specify the CloudWatch Log Group to keep track of connections. Remember, an audit trail is a great security practice!
  5. AuthenticationOptions - The authentication mechanism developers will use to connect. Again, I am using IAM Identity Center.
  6. SplitTunnel - Determine whether all traffic (false) or only traffic intended for VPC resources (true) will traverse the Client VPN connection.
		_, err = ec2clientvpn.NewNetworkAssociation(ctx, "vpnSubnetAssociation", &ec2clientvpn.NetworkAssociationArgs{
			ClientVpnEndpointId: vpnEndpoint.ID(),
			SubnetId:            privateComputeSubnetsDict["a"].ID(),
		})
		if err != nil {
			return err
		}

This resource tells Client VPN which subnet in the VPC to associate with to provision its ENI. This process can take a few minutes. So, when you pulumi up, that's a good time to fill up your water bottle.

		_, err = ec2clientvpn.NewAuthorizationRule(ctx, "vpnNetworkAuthorization", &ec2clientvpn.AuthorizationRuleArgs{
			ClientVpnEndpointId: vpnEndpoint.ID(),
			TargetNetworkCidr: privateComputeSubnetsDict["a"].CidrBlock.ApplyT(func(cidrBlock *string) string {
				if cidrBlock == nil {
					panic("CIDR Block is nil")
				}
				return *cidrBlock
			}).(pulumi.StringOutput),
			AuthorizeAllGroups: pulumi.Bool(true),
		})
		if err != nil {
			return err
		}

Finally, this resource tells Client VPN which subnet CIDRs developers are allowed to access. You can have granularity down to the group ID/name in the SAML assertion from your IdP by specifying it with an AccessGroupId parameter. I opted to allow all my users in all my groups to access this authorized CIDR with the AuthorizeAllGroups parameter.

Wrapping Things Up...

In this blog post, we discussed how to configure Client VPN so that developers can securely connect to the AWS resources in your VPC without regularly managing allowlists. A follow-up action you can take after is to use your preferred configuration management tool to add your Client VPN security group(s) to the security group inbound rules for AWS resources that developers are authorized to access.

If you found this article useful, let me know on BlueSky or on LinkedIn!