As someone without a permanent base, I needed a secure and flexible cloud infrastructure that allowed me to spawn powerful machines when needed. To achieve this, I built an isolated network on AWS.
I began by creating a Terraform module that provisions the infrastructure needed, such as
- VPCs
- Subnets
- Routing tables
- EC2 instances
While the module is tailored to AWS, I plan to keep the variable names consistent to other modules that re-create the setup for different cloud platforms, such as Exoscale.
The isolated network is centered around an EC2 instance, which acts as a router between a public VPC and a private VPC, similar to an at-home router. The EC2 instance has two ENI adapters, one attached to the public VPC and the other attached to the private VPC. The EC2 instance is running VyOS, which I configured using Ansible and the local-exec
provisioner in Terraform upon creation.
data "aws_ami" "vyos" {
most_recent = true
filter {
name = "name"
values = ["VyOS 1.4.0-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["679593333241"]
}
resource "aws_instance" "vyos" {
ami = data.aws_ami.vyos.id
availability_zone = data.aws_availability_zones.region.names[0]
instance_type = "t3.small"
network_interface {
network_interface_id = aws_network_interface.public.id
device_index = 0
}
network_interface {
network_interface_id = aws_network_interface.local.id
device_index = 1
}
provisioner "local-exec" {
command = "ansible-playbook -i \"${aws_eip.public.public_ip},\" <path_to_playbook>"
}
}
The public VPC has an internet gateway attached to it, and all instances in the public VPC have internet access. The router instance is the only instance that resides in the public VPC. Both VPCs have a subnet within a single availability zone (AZ), as a single EC2 instance cannot span two AZs.
resource "aws_internet_gateway" "gw" {
}
resource "aws_internet_gateway_attachment" "gw" {
internet_gateway_id = aws_internet_gateway.gw.id
vpc_id = aws_vpc.public.id
}
Each VPC has a routing table to correctly route traffic. The public VPC routes all traffic towards the internet gateway, while the private VPC routes all traffic within the subnet to each other.
resource "aws_route_table" "public" {
vpc_id = aws_vpc.public.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.gw.id
}
}
resource "aws_route_table" "internal" {
vpc_id = aws_vpc.internal.id
}
resource "aws_route_table_association" "internal" {
subnet_id = aws_subnet.internal.id
route_table_id = aws_route_table.internal.id
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
I connect to my isolated network primarily through my OpenWRT-based router using WireGuard. I also use the WireGuard client on my Mac or phone to connect to the cluster when I’m outside. Keep an eye out for my posts detailing how I deploy VyOS on AWS and configure OpenWRT to connect to WireGuard.
I attached an Elastic IP to the router instance, which lets me destroy and re-build the instance without issue. This is useful when I don’t need the network running, like when I’m flying, or when I’m actively improving the instance.
resource "aws_eip" "public" {
domain = "vpc"
}
resource "aws_eip_association" "public" {
network_interface_id = aws_network_interface.public.id
allocation_id = aws_eip.public.id
}
If I need to access any other AWS resource, I add a VPC Endpoint for that resource directly to the private VPC. For example, I use S3FS to mount S3 storage directly on the instance and DynamoDB for building JSONL files for machine learning tasks.
Use Cases
I create a Windows instance inside the subnet when I need to do remote work that involves downloading when I’m outdoors. I also create larger instances for working with AI/Machine Learning models when my Mac isn’t able to load them or when I don’t have storage at a given time.
Multi-Region Setup
To transfer the setup to another region, I simply change the region variable in my Terraform module, and it magically appears in the new region.