Releasing a Terraform module for an ephemeral, SSO-secured AWS Client VPN
Today I'm releasing a Terraform module that spins up an ephemeral, SSO-secured AWS Client VPN endpoint. It costs nothing when nobody's using it, your team logs in with the same corporate identity they already have, and you flip it on from a little web page when you need it.
I built it because I kept running into the same handful of networking problems and I was tired of solving them with always-on boxes and shared secrets (or private PKIs).
The problems it solves
1. Goodbye bastion host
The classic way to reach a private RDS instance, an ElastiCache cluster, or an internal load balancer is to keep a bastion around and SSH through it. That means a box to patch, keys to rotate, and a standing attack surface that sits there whether anyone uses it or not.
Once you're on this VPN you're inside the VPC. You talk to private resources directly, no jump host in the middle, no public database endpoint. When you disconnect, there's nothing left running for someone to poke at.
2. A fixed egress IP out of your VPC
Plenty of third-party APIs still gate access with an IP allow-list. If your traffic leaves AWS from whatever ephemeral address a Lambda or a container happened to get, you can't satisfy that.
With a full tunnel, everything you send exits through your VPC's NAT gateway, so it all appears to come from that one Elastic IP. You give the vendor a single address to allow-list and you're done.
3. Egress from any AWS region
Because you choose which region the VPC lives in, you choose where your traffic appears to come from. That's genuinely useful when you're testing how a service behaves for users in another part of the world, or when you need to reach something that geofilters by country. Stand the module up in eu-west-3 and you look French; stand it up in us-east-1 and you look American. Same setup, different exit door.
How to use it
How it works
The moving parts fit together like this. You open a small single-page app served from CloudFront and S3. You sign in through the Cognito hosted UI, which federates via SAML to IAM Identity Center, so it's your real company login, not a password someone shared in Slack two years ago. Cognito hands back a JWT.
That token lets you call an API Gateway endpoint, which is guarded by a Cognito authorizer. Behind it sits a small Lambda that does one job: when you ask it to turn the VPN on, it associates a subnet with the Client VPN endpoint and adds the default route so your traffic can get out. When you turn it off, it tears the association back down.
The reason this is cheap is the billing model. AWS charges you for the Client VPN endpoint per associated subnet per hour, not for the endpoint merely existing. So the endpoint stays up permanently at no cost, and the one thing that actually bills, the subnet association, only exists while you're using it. An EventBridge rule runs every 15 minutes and checks whether anyone is still connected. If the last reading shows nobody home, it disassociates automatically. Forget to switch it off and it switches itself off for you.
Here's the portal you actually interact with:
You sign in, hit start, wait for the status to go green, then connect your client. When you're done you hit stop, or you just walk away and let the idle check handle it.
Example module configuration
The module is on the Terraform Registry. A minimal call looks like this:
module "vpn" {
source = "psantus/ephemeral-vpn/aws"
version = "~> 1.0"
region = "eu-west-3"
project_name = "acme-vpn"
app_title = "ACME VPN"
# Bring your own VPC with a NAT gateway and a private subnet
# that routes 0.0.0.0/0 through it.
vpc_id = "vpc-0123456789abcdef0"
target_subnet_id = "subnet-0123456789abcdef0"
# SSO via IAM Identity Center, federated through Cognito.
auth_mode = "federated"
# Metadata XML from your three IDC custom SAML apps
# (VPN client, self-service portal, Cognito).
client_saml_metadata_file = "${path.module}/idc/client.xml"
portal_saml_metadata_file = "${path.module}/idc/portal.xml"
cognito_saml_metadata_file = "${path.module}/idc/cognito.xml"
cognito_domain_prefix = "acme-vpn"
# Optional: a pretty URL for the web app.
dns_hosted_zone_name = "example.com"
dns_name = "vpn"
}
You bring a VPC that already has a NAT gateway and a private subnet, plus three IAM Identity Center SAML apps (one for the VPN client, one for the self-service portal, one for Cognito). Drop in their metadata files and apply. The server certificate is generated for you by Terraform, so there's no openssl dance and no private key sitting on your laptop.
Try it
The module is public and ready to use:
- Terraform Registry:
psantus/ephemeral-vpn/aws - Source: https://github.com/psantus/terraform-aws-ephemeral-vpn
If it saves you from standing up yet another bastion, or from explaining to a vendor why your source IP keeps changing, don't hesitate buy me a coffee ;).
Give it a spin!


Top comments (0)