This repository contains an AWS Lambda function and complete infrastructure-as-code (AWS SAM) for controlling a personal OpenVPN server on AWS. It's part of a larger project created during a sabbatical in Taipei to build a complete VPN solution with iOS app control.
Note: The initial Lambda function was generated with ChatGPT/Claude assistance in February 2025, with API Gateway setup left as manual steps. In January 2026, Claude Code generated the complete AWS SAM deployment solution, transforming this from a "some assembly required" project into a fully deployable infrastructure-as-code package.
This API provides a secure interface to control an EC2 instance running OpenVPN through:
- REST API endpoints using API Gateway
- Lambda function for EC2 control
- API key authentication with usage plans
- Rate limiting and throttling
- CloudWatch monitoring
The API is designed to work with:
- VPN Infrastructure created via Terraform
- VPNControl iOS App for remote management
iOS App (VPNControl)
│
▼
┌─────────────────────┐
│ API Gateway │
│ ┌───────────────┐ │
│ │ API Key Auth │ │
│ │ Rate Limiting│ │
│ └───────────────┘ │
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ Lambda Function │
│ (handler.py) │
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ EC2 Instance │
│ (OpenVPN Server) │
└─────────────────────┘
| Method | Endpoint | Description |
|---|---|---|
| POST | /vpn |
Start or stop the VPN |
| GET | /vpn/status |
Get instance status |
start: Start the VPN instancestop: Stop the VPN instancestatus: Get current instance state
- AWS CLI configured with credentials
- AWS SAM CLI
- An EC2 instance ID for your VPN server
- Python 3.11+ (for local development)
# Set your EC2 instance ID and deploy (with optional custom domain)
EC2_INSTANCE_ID=i-1234567890abcdef0 CustomDomainName=toggle-vpn.your-domain.com make deployOr use the guided deployment for interactive setup:
make deploy-guidedNote: The
CustomDomainNameparameter is optional. If provided, it creates a base path mapping to an existing API Gateway custom domain.
make get-api-keymake outputs# Build the application
make build
# Validate the SAM template
make validate
# Deploy with custom settings
EC2_INSTANCE_ID=i-xxx EC2_REGION=us-west-2 STAGE=prod make deploy
# View deployment outputs
make outputs
# Tail Lambda logs
make logs
# Delete the stack
make delete# Build
sam build
# Deploy
sam deploy \
--stack-name vpn-control-api \
--capabilities CAPABILITY_IAM \
--resolve-s3 \
--parameter-overrides \
EC2InstanceId=i-1234567890abcdef0 \
EC2Region=us-west-2 \
StageName=prod| Parameter | Default | Description |
|---|---|---|
| EC2InstanceId | (required) | EC2 instance ID to control |
| EC2Region | us-west-2 | AWS region of the EC2 instance |
| StageName | prod | API Gateway stage name |
| ThrottleRateLimit | 10 | Max requests per second |
| ThrottleBurstLimit | 20 | Max concurrent requests |
| QuotaLimit | 1000 | Max requests per month |
| CustomDomainName | (empty) | Custom domain name (e.g., toggle-vpn.your-domain.com) |
curl -X POST "https://your-api-id.execute-api.us-west-2.amazonaws.com/prod/vpn" \
-H "x-api-key: your-api-key" \
-H "Content-Type: application/json" \
-d '{"action": "start"}'curl -X GET "https://your-api-id.execute-api.us-west-2.amazonaws.com/prod/vpn/status" \
-H "x-api-key: your-api-key"curl -X POST "https://your-api-id.execute-api.us-west-2.amazonaws.com/prod/vpn" \
-H "x-api-key: your-api-key" \
-H "Content-Type: application/json" \
-d '{"action": "stop"}'{
"message": "Instance i-1234567890abcdef0 is starting.",
"instanceId": "i-1234567890abcdef0",
"action": "start"
}{
"message": "Instance i-1234567890abcdef0 is currently running.",
"instanceId": "i-1234567890abcdef0",
"state": "running",
"action": "status"
}{
"error": "Invalid action",
"message": "Use 'start', 'stop', or 'status'.",
"validActions": ["start", "stop", "status"]
}# Create local environment configuration
cp env.json.example env.json
# Edit env.json with your EC2 instance ID# Start local API Gateway
make local
# Test a specific event
sam local invoke VPNControlFunction --event events/status.json --env-vars env.json- API Key Authentication: All endpoints require a valid API key via
x-api-keyheader - Usage Plans: Rate limiting (10 req/sec) and monthly quotas (1000 req/month)
- IAM Least Privilege: Lambda has minimal EC2 permissions for the specific instance
- CORS Configuration: Configured for cross-origin requests
- CloudWatch Logging: All requests and errors are logged
- Never commit API keys to source control
- Rotate API keys periodically via the AWS Console
- Restrict instance scope - IAM policy limits actions to your specific EC2 instance
- Enable CloudWatch alarms for unusual activity
- Consider VPC endpoints for enhanced network security
Lambda function logs are available at:
/aws/lambda/{stack-name}-vpn-control
API Gateway logs (when enabled):
/aws/apigateway/{stack-name}
make logs- API Gateway 4xx/5xx error rates
- Lambda execution duration and errors
- Throttling and API key usage
vpn-control-api/
├── handler.py # Lambda function code
├── template.yaml # SAM/CloudFormation template
├── samconfig.toml # SAM CLI configuration
├── Makefile # Build and deployment commands
├── requirements.txt # Python dependencies
├── env.json.example # Local development config template
├── events/ # Test events for local development
│ ├── start.json
│ ├── stop.json
│ └── status.json
└── README.md
The Lambda function's EC2_ID environment variable is not set. Redeploy with the correct EC2InstanceId parameter.
Verify your EC2 instance ID format matches i- followed by 8-17 alphanumeric characters.
- Check that you're including the
x-api-keyheader - Verify the API key is correct and associated with the usage plan
- Check if you've exceeded rate limits or quota
The default timeout is 30 seconds. If EC2 operations are slow, consider increasing the timeout in template.yaml.
- Fork the repository
- Create a feature branch
- Submit a pull request
This project is licensed under the MIT License - see the LICENSE file for details.
- AWS for the serverless platform
- ChatGPT and Claude for initial Lambda function code generation (February 2025)
- Claude Code for complete AWS SAM infrastructure-as-code solution (January 2026)