Tech experiments, travel adventures, and code explorations by Robert Taylor
by Robert Taylor
While on sabbatical in Taipei, Taiwan, I embarked on a project to create my own VPN service using AWS. The goal was to build a complete system that would allow me to appear in a different location while traveling, with the ability to easily control the VPN from my iPhone. What made this project particularly interesting was that nearly all of the code was generated using AI tools like ChatGPT and Claude.
The project consists of three main components:
Let’s dive into each component and see how they were built.
The VPN infrastructure is defined using Terraform, which allows for infrastructure as code (IaC). The configuration creates:
Here’s the core of the Terraform configuration:
resource "aws_instance" "vpn_instance" {
ami = "ami-0b16505c55f9802f9" # Amazon Linux 2
instance_type = "t4g.micro"
subnet_id = aws_subnet.vpn_subnet.id
vpc_security_group_ids = [aws_security_group.vpn_sg.id]
key_name = var.ssh_key_name
user_data = <<-EOF
#!/bin/bash
yum update -y
amazon-linux-extras enable epel
yum install -y openvpn easy-rsa
curl -O https://raw.githubusercontent.com/Angristan/OpenVPN-install/master/openvpn-install.sh
chmod +x openvpn-install.sh
AUTO_INSTALL=y ./openvpn-install.sh
EOF
tags = {
Name = "VPN-Instance"
}
}
To control the VPN instance, I created a serverless REST API using AWS API Gateway and Lambda. The API provides three main functions:
The Lambda function handling these operations is written in Python:
def lambda_handler(event, context):
action = ""
if "queryStringParameters" in event and event["queryStringParameters"] is not None:
action = event["queryStringParameters"].get("action", "").lower()
elif "action" in event and event["action"] is not None:
action = event.get("action", "").lower()
if action not in ["start", "stop", "status"]:
return {
"statusCode": 400,
"body": json.dumps({"message": "Invalid action. Use 'start', 'stop', or 'status'."})
}
try:
if action == "start":
ec2.start_instances(InstanceIds=[INSTANCE_ID])
message = f"Instance {INSTANCE_ID} is starting."
# ... rest of the handler code
The API is secured using API keys and includes CORS support for the iOS app.
The iOS app was built using SwiftUI and follows the MVVM (Model-View-ViewModel) architecture. It provides a clean interface for managing the VPN service with features like:
Here’s a look at the main view model that manages the VPN state:
@MainActor
class VPNViewModel: ObservableObject {
@Published var vpnState: VPNState = .stopped
@Published var isLoading = false
@Published var statusMessage = "Checking status..."
func toggleVPN() async {
isLoading = true
do {
let action: VPNAction = vpnState == .stopped ? .start : .stop
let response = try await vpnClient.controlVPN(action: action)
// ... handle response
} catch {
errorMessage = error.localizedDescription
}
isLoading = false
}
}
The app uses modern iOS features like:
One of the most interesting aspects of this project was the extensive use of AI tools for code generation. Here’s how AI assisted in each component:
The AI tools were particularly effective at:
Throughout this project, several key insights emerged:
AWS serverless architecture provides a cost-effective way to manage infrastructure
SwiftUI and MVVM create a maintainable and responsive mobile application
Some potential enhancements for the project:
This project demonstrates how modern cloud services, mobile development, and AI tools can come together to create a practical solution. The combination of AWS services, Terraform, and iOS development provides a flexible and secure way to manage a personal VPN service, while AI tools significantly accelerated the development process.
The complete source code, GenAI chat transcripts, and documentation for this project are available on GitHub at github.com/TerrorTunnels.
tags: aws - terraform - ios - swiftui - vpn - serverless