diff --git a/terraform/VPN-SETUP-GUIDE.md b/terraform/VPN-SETUP-GUIDE.md new file mode 100644 index 000000000..45f0aa5ce --- /dev/null +++ b/terraform/VPN-SETUP-GUIDE.md @@ -0,0 +1,563 @@ +# AWS Client VPN Setup Guide for Code-Server + +This guide explains how to set up AWS Client VPN to securely access your code-server deployment. With VPN enabled, users must connect to the VPN before accessing code-server, adding an extra layer of security. + +## Table of Contents + +- [Overview](#overview) +- [Why Use VPN](#why-use-vpn) +- [Architecture](#architecture) +- [Prerequisites](#prerequisites) +- [Setup Steps](#setup-steps) + - [1. Generate VPN Certificates](#1-generate-vpn-certificates) + - [2. Configure Terraform](#2-configure-terraform) + - [3. Deploy VPN](#3-deploy-vpn) + - [4. Export Client Configuration](#4-export-client-configuration) + - [5. Distribute to Users](#5-distribute-to-users) +- [Client Setup](#client-setup) +- [Testing VPN Connection](#testing-vpn-connection) +- [Advanced Configuration](#advanced-configuration) +- [Troubleshooting](#troubleshooting) +- [Cost Considerations](#cost-considerations) + +## Overview + +AWS Client VPN is a managed client-based VPN service that enables you to securely access your AWS resources from any location using an OpenVPN-based VPN client. + +### Key Features + +- **Certificate-based Authentication**: Secure authentication using X.509 certificates +- **Split Tunneling**: Only route VPC traffic through VPN (internet traffic goes direct) +- **Session Logging**: CloudWatch logs for all VPN connections +- **Multi-platform Support**: Works on Windows, macOS, Linux, iOS, and Android +- **High Availability**: AWS managed service with automatic failover + +## Why Use VPN + +### Security Benefits + +1. **Network-Level Access Control**: VPN required before accessing code-server +2. **Hide Internal Infrastructure**: ALB and resources stay completely private +3. **Additional Authentication Layer**: Certificates + OAuth2/SAML = multi-factor +4. **Audit Trail**: All VPN connections logged in CloudWatch +5. **IP Whitelisting Alternative**: No need to manage IP allowlists + +### Use Cases + +- **Fully Private Deployment**: No public endpoints at all +- **Compliance Requirements**: Meet regulatory requirements for private access +- **Remote Team Access**: Secure access for distributed teams +- **Development Environments**: Keep dev/staging completely isolated + +## Architecture + +### With VPN Enabled + +``` +User Device + ↓ +AWS Client VPN Endpoint (Certificate Auth) + ↓ +Private Subnets + ↓ +Internal ALB → OAuth2 Proxy → Code-Server + ↓ +Private EC2/EKS Resources +``` + +### VPN Network Flow + +1. User connects to VPN with client certificate +2. VPN assigns IP from client CIDR (172.16.0.0/22 by default) +3. VPN routes VPC traffic (10.0.0.0/16) through tunnel +4. User accesses internal ALB at private IP +5. OAuth2 Proxy provides SAML/OIDC authentication +6. Code-Server accessible only via VPN + +## Prerequisites + +- OpenSSL installed (for certificate generation) +- AWS CLI configured with appropriate permissions +- Terraform >= 1.0 +- IAM permissions to: + - Create VPN endpoints + - Import certificates to ACM + - Create CloudWatch log groups + - Modify security groups + +## Setup Steps + +### 1. Generate VPN Certificates + +VPN requires server and client certificates for authentication. + +```bash +cd terraform/scripts + +# Generate certificates (will upload to ACM automatically) +./generate-vpn-certificates.sh [cert-dir] [region] [common-name] + +# Example: +./generate-vpn-certificates.sh ./vpn-certs us-east-1 code-server-vpn +``` + +This script: +1. Creates a Certificate Authority (CA) +2. Generates server certificate +3. Generates client certificate +4. Uploads both to AWS Certificate Manager +5. Outputs certificate ARNs for Terraform + +**Output Files:** +- `ca.key` - CA private key (keep secure!) +- `ca.crt` - CA certificate +- `server.key` - Server private key (keep secure!) +- `server.crt` - Server certificate +- `client.key` - Client private key (distribute to users) +- `client.crt` - Client certificate (distribute to users) +- `certificate-arns.txt` - ARNs for Terraform +- `terraform-vars.txt` - Terraform configuration snippet + +**Security Note:** +- Store `ca.key` and `server.key` securely (never share these!) +- Back up all certificates securely +- Distribute `client.key` and `client.crt` to authorized users only + +### 2. Configure Terraform + +Edit your `terraform.tfvars` file: + +```hcl +# EC2 or EKS deployment + +# Enable VPN +enable_vpn = true + +# Certificate ARNs from step 1 +vpn_server_certificate_arn = "arn:aws:acm:us-east-1:123456789012:certificate/xxxxx" +vpn_client_certificate_arn = "arn:aws:acm:us-east-1:123456789012:certificate/yyyyy" + +# VPN Configuration +vpn_client_cidr_block = "172.16.0.0/22" # Must not overlap with VPC CIDR +vpn_split_tunnel = true # Recommended +vpn_transport_protocol = "udp" # udp (faster) or tcp +vpn_port = 443 # 443 or 1194 +vpn_session_timeout_hours = 24 # 8-24 hours +vpn_client_login_banner = "Welcome to Code-Server VPN. Authorized access only." + +# Make ALB internal (required for VPN-only access) +internal_alb = true +``` + +### 3. Deploy VPN + +Deploy the infrastructure with VPN enabled: + +```bash +# EC2 Deployment +cd deployments/ec2 +terraform apply + +# EKS Deployment +cd deployments/eks +terraform apply +``` + +Terraform will create: +- Client VPN Endpoint +- VPN Network Associations (in private subnets) +- Authorization Rules (allow access to VPC CIDR) +- Security Group for VPN +- CloudWatch Log Group for VPN connections + +**Deployment Time:** ~5-10 minutes for VPN endpoint to become available + +### 4. Export Client Configuration + +After deployment completes, export the VPN client configuration: + +```bash +cd terraform/scripts + +# Auto-detect VPN endpoint from Terraform state +./export-vpn-config.sh + +# Or specify manually +./export-vpn-config.sh [region] [output-dir] [cert-dir] + +# Example: +./export-vpn-config.sh cvpn-endpoint-0123456789abcdef0 us-east-1 ./vpn-config ./vpn-certs +``` + +This script: +1. Downloads VPN configuration from AWS +2. Embeds client certificate and key +3. Creates platform-ready `.ovpn` file + +**Output:** `code-server-vpn.ovpn` - ready to distribute to users + +### 5. Distribute to Users + +**Securely** distribute these files to authorized users: +- `code-server-vpn.ovpn` - VPN configuration file + +**Distribution Methods:** +- Encrypted email +- Secure file sharing (e.g., 1Password, encrypted USB) +- MDM system for corporate devices +- Secure internal portal + +## Client Setup + +### macOS + +1. **Install Tunnelblick** (free, open-source) + ```bash + brew install --cask tunnelblick + # Or download from https://tunnelblick.net/ + ``` + +2. **Import Configuration** + - Double-click `code-server-vpn.ovpn` + - Tunnelblick will import it automatically + +3. **Connect** + - Click Tunnelblick icon in menu bar + - Select "Connect code-server-vpn" + - VPN should connect within a few seconds + +### Windows + +1. **Install OpenVPN Connect** + - Download from https://openvpn.net/client-connect-vpn-for-windows/ + - Install and launch + +2. **Import Configuration** + - File → Import Profile + - Select `code-server-vpn.ovpn` + +3. **Connect** + - Click "Connect" on the profile + - Wait for connection to establish + +### Linux + +1. **Install OpenVPN** + ```bash + # Debian/Ubuntu + sudo apt-get update + sudo apt-get install openvpn + + # RHEL/CentOS + sudo yum install openvpn + + # Arch + sudo pacman -S openvpn + ``` + +2. **Connect** + ```bash + sudo openvpn --config code-server-vpn.ovpn + ``` + +3. **Run as Service** (optional) + ```bash + sudo cp code-server-vpn.ovpn /etc/openvpn/client/code-server.conf + sudo systemctl start openvpn-client@code-server + sudo systemctl enable openvpn-client@code-server + ``` + +### iOS + +1. **Install OpenVPN Connect** + - Download from App Store + +2. **Transfer Configuration** + - Email to yourself, or + - Use AirDrop, or + - Upload to iCloud/Dropbox + +3. **Import and Connect** + - Open `.ovpn` file + - Import to OpenVPN Connect + - Tap "Connect" + +### Android + +1. **Install OpenVPN for Android** + - Download from Google Play Store + +2. **Transfer Configuration** + - Email to yourself, or + - Upload to Google Drive/Dropbox + +3. **Import and Connect** + - Open `.ovpn` file + - Import to OpenVPN + - Tap "Connect" + +## Testing VPN Connection + +### 1. Connect to VPN + +Connect using your platform's VPN client (see Client Setup above). + +### 2. Verify VPN Connection + +**macOS/Linux:** +```bash +# Check VPN interface +ifconfig | grep tun + +# Check VPN IP assignment +ifconfig tun0 # or tun1, etc. + +# Should show IP in 172.16.0.0/22 range +``` + +**Windows:** +```powershell +# Check VPN adapter +ipconfig | findstr "172.16" +``` + +### 3. Test Access to Code-Server + +Get the internal ALB DNS name: +```bash +# From Terraform output +terraform output alb_dns_name # EC2 +# Or +kubectl get ingress -n code-server # EKS +``` + +Access code-server: +```bash +# EC2 +curl -I http:// + +# EKS +curl -I http:// +``` + +**Expected Result:** You should get a redirect to OAuth2 login or code-server login page. + +### 4. Check CloudWatch Logs + +View VPN connection logs: +```bash +aws logs tail /aws/vpn/ --follow +``` + +You should see connection events with your IP address. + +## Advanced Configuration + +### Multiple Client Certificates + +To create additional client certificates for different users: + +```bash +# Generate new client cert +openssl genrsa -out client2.key 2048 +openssl req -new -key client2.key -out client2.csr +openssl x509 -req -days 3650 -in client2.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client2.crt + +# Create user-specific .ovpn file +./export-vpn-config.sh us-east-1 ./user2-config ./ +# Manually replace client cert/key in the .ovpn file +``` + +### SAML/SSO Authentication + +For enterprise deployments, use SAML-based authentication instead of certificates: + +```hcl +# terraform.tfvars +vpn_authentication_type = "federated-authentication" +enable_saml_authentication = true +saml_provider_arn = "arn:aws:iam::123456789012:saml-provider/YourSAMLProvider" +``` + +This combines VPN (network access) with SAML (user authentication). + +### Split Tunnel Configuration + +**Enabled (default - recommended):** +```hcl +vpn_split_tunnel = true +``` +- Only VPC traffic (10.0.0.0/16) routed through VPN +- Internet traffic goes directly +- Better performance for users + +**Disabled (full tunnel):** +```hcl +vpn_split_tunnel = false +``` +- All traffic routed through VPN +- More secure but slower +- Required for some compliance scenarios + +### Custom DNS Servers + +Route DNS queries through VPN: + +```hcl +vpn_dns_servers = ["10.0.0.2"] # VPC DNS resolver +``` + +### Session Timeout + +Configure VPN session duration: + +```hcl +vpn_session_timeout_hours = 12 # 8-24 hours +``` + +Users will be disconnected after this period and must reconnect. + +## Troubleshooting + +### Connection Fails + +**Check Certificate Validity:** +```bash +openssl x509 -in client.crt -text -noout +# Verify "Not After" date +``` + +**Check VPN Endpoint Status:** +```bash +aws ec2 describe-client-vpn-endpoints \ + --client-vpn-endpoint-ids +``` + +**Check CloudWatch Logs:** +```bash +aws logs tail /aws/vpn/ --follow +``` + +### Cannot Access Code-Server After Connecting + +**1. Verify VPN IP Assignment:** +```bash +ifconfig | grep 172.16 # macOS/Linux +ipconfig | findstr "172.16" # Windows +``` + +**2. Check Authorization Rules:** +```bash +aws ec2 describe-client-vpn-authorization-rules \ + --client-vpn-endpoint-id +``` + +**3. Check Security Groups:** +```bash +# Verify VPN security group allows traffic to ALB +aws ec2 describe-security-groups --group-ids +``` + +**4. Test DNS Resolution:** +```bash +nslookup +dig +``` + +### Split Tunnel Not Working + +Check routing table after VPN connection: + +**macOS/Linux:** +```bash +netstat -rn | grep tun +# Should only see VPC CIDR (10.0.0.0/16) routes +``` + +**Windows:** +```powershell +route print | findstr "172.16" +``` + +### Certificate Expired + +Certificates are valid for 10 years by default. To renew: + +1. Generate new certificates (see Step 1) +2. Update certificate ARNs in Terraform +3. Run `terraform apply` +4. Export new client configuration +5. Distribute to users + +## Cost Considerations + +### AWS Client VPN Pricing (as of 2024) + +**Endpoint Association:** +- $0.10 per hour per subnet association +- For 3 subnets (multi-AZ): ~$216/month + +**Connection Time:** +- $0.05 per hour per connection +- For 10 active users (8 hrs/day): ~$88/month + +**Total Estimated Cost:** +- Base: ~$216/month (always running) +- Variable: ~$0.40 per user per day + +**Cost Optimization:** + +1. **Single Subnet Association** (not recommended for production): + ```hcl + subnet_ids = [module.vpc.private_subnet_ids[0]] # Only one AZ + ``` + Saves: ~$140/month (but loses HA) + +2. **Scheduled VPN** (for dev environments): + - Use Lambda to disable VPN outside business hours + - Potential savings: ~50-70% + +3. **Alternative: Direct Connect or Site-to-Site VPN:** + - For office connectivity: Site-to-Site VPN ($0.05/hr = ~$36/month) + - For large teams: Direct Connect (higher setup, lower per-GB cost) + +## Security Best Practices + +1. **Certificate Management:** + - Store CA private key in HSM or secure vault + - Rotate certificates annually + - Revoke certificates for departed users + +2. **Monitoring:** + - Set up CloudWatch Alarms for unusual connection patterns + - Review VPN logs regularly + - Alert on failed authentication attempts + +3. **Network Segmentation:** + - Use separate subnets for VPN clients if needed + - Apply additional security groups for VPN traffic + - Implement network ACLs for defense in depth + +4. **Multi-Factor Authentication:** + - Combine VPN (certificate) + OAuth2/SAML for true MFA + - Optionally add SAML to VPN itself for triple-factor + +5. **Access Control:** + - Use separate client certificates per user for audit trail + - Implement IP-based restrictions if needed + - Regular access reviews + +## Additional Resources + +- [AWS Client VPN Documentation](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/what-is.html) +- [AWS Client VPN Pricing](https://aws.amazon.com/vpn/pricing/) +- [OpenVPN Documentation](https://openvpn.net/community-resources/) +- [Tunnelblick Documentation](https://tunnelblick.net/documents.html) + +## Support + +For issues or questions: +- Check [Troubleshooting](#troubleshooting) section +- Review CloudWatch Logs +- Check AWS VPN endpoint status +- Consult main [README.md](README.md) diff --git a/terraform/deployments/ec2/main.tf b/terraform/deployments/ec2/main.tf index 5e7ad96c4..a0b9c1dc5 100644 --- a/terraform/deployments/ec2/main.tf +++ b/terraform/deployments/ec2/main.tf @@ -115,3 +115,26 @@ module "code_server_ec2" { tags = local.common_tags } + +# VPN Module (Optional) +module "vpn" { + count = var.enable_vpn ? 1 : 0 + source = "../../modules/vpn" + + name_prefix = local.name_prefix + vpc_id = module.vpc.vpc_id + vpc_cidr = module.vpc.vpc_cidr + subnet_ids = module.vpc.private_subnet_ids + server_certificate_arn = var.vpn_server_certificate_arn + client_certificate_arn = var.vpn_client_certificate_arn + client_cidr_block = var.vpn_client_cidr_block + split_tunnel = var.vpn_split_tunnel + authentication_type = var.vpn_authentication_type + dns_servers = var.vpn_dns_servers + transport_protocol = var.vpn_transport_protocol + vpn_port = var.vpn_port + session_timeout_hours = var.vpn_session_timeout_hours + client_login_banner = var.vpn_client_login_banner + + tags = local.common_tags +} diff --git a/terraform/deployments/ec2/outputs.tf b/terraform/deployments/ec2/outputs.tf index 4fdfa9902..e7e98d8b5 100644 --- a/terraform/deployments/ec2/outputs.tf +++ b/terraform/deployments/ec2/outputs.tf @@ -40,6 +40,21 @@ output "kms_key_arn" { value = module.security.kms_key_arn } +output "vpn_endpoint_id" { + description = "ID of the VPN endpoint (if enabled)" + value = var.enable_vpn ? module.vpn[0].vpn_endpoint_id : null +} + +output "vpn_endpoint_dns_name" { + description = "DNS name of the VPN endpoint (if enabled)" + value = var.enable_vpn ? module.vpn[0].vpn_endpoint_dns_name : null +} + +output "vpn_client_cidr_block" { + description = "CIDR block for VPN clients (if enabled)" + value = var.enable_vpn ? var.vpn_client_cidr_block : null +} + output "next_steps" { description = "Next steps to complete the setup" value = <<-EOT @@ -73,5 +88,6 @@ output "next_steps" { - ALB is ${var.internal_alb ? "internal (private network only)" : "public"} - Data is encrypted at rest using KMS - VPC Flow Logs are enabled for monitoring + ${var.enable_vpn ? "\n VPN Configuration:\n - VPN Endpoint: ${module.vpn[0].vpn_endpoint_dns_name}\n - To export VPN configuration: ../../scripts/export-vpn-config.sh ${module.vpn[0].vpn_endpoint_id} ${var.aws_region}\n - VPN clients will receive IPs from: ${var.vpn_client_cidr_block}" : ""} EOT } diff --git a/terraform/deployments/ec2/terraform.tfvars.example b/terraform/deployments/ec2/terraform.tfvars.example index 88af8886a..7a7927a63 100644 --- a/terraform/deployments/ec2/terraform.tfvars.example +++ b/terraform/deployments/ec2/terraform.tfvars.example @@ -45,3 +45,16 @@ oauth2_allowed_emails = [ # "user1@example.com", # "user2@example.com" ] + +# VPN Configuration (Optional - for enhanced security) +# First run: ../../scripts/generate-vpn-certificates.sh +enable_vpn = false # Set to true to enable VPN +vpn_server_certificate_arn = "" # ARN from generate-vpn-certificates.sh +vpn_client_certificate_arn = "" # ARN from generate-vpn-certificates.sh +vpn_client_cidr_block = "172.16.0.0/22" # Must not overlap with VPC +vpn_split_tunnel = true # Only route VPC traffic through VPN +vpn_authentication_type = "certificate-authentication" +vpn_transport_protocol = "udp" # udp (faster) or tcp (more reliable) +vpn_port = 443 # 443 or 1194 +vpn_session_timeout_hours = 24 # 8-24 hours +vpn_client_login_banner = "Welcome to Code-Server VPN. Authorized access only." diff --git a/terraform/deployments/ec2/variables.tf b/terraform/deployments/ec2/variables.tf index cddf70ce9..9ad969030 100644 --- a/terraform/deployments/ec2/variables.tf +++ b/terraform/deployments/ec2/variables.tf @@ -145,3 +145,70 @@ variable "oauth2_allowed_emails" { type = list(string) default = [] } + +# VPN Configuration +variable "enable_vpn" { + description = "Enable AWS Client VPN for secure access" + type = bool + default = false +} + +variable "vpn_server_certificate_arn" { + description = "ARN of server certificate in ACM for VPN" + type = string + default = "" +} + +variable "vpn_client_certificate_arn" { + description = "ARN of client root certificate in ACM for VPN" + type = string + default = "" +} + +variable "vpn_client_cidr_block" { + description = "CIDR block for VPN clients (must not overlap with VPC)" + type = string + default = "172.16.0.0/22" +} + +variable "vpn_split_tunnel" { + description = "Enable split tunnel (only route VPC traffic through VPN)" + type = bool + default = true +} + +variable "vpn_authentication_type" { + description = "VPN authentication type (certificate-authentication recommended)" + type = string + default = "certificate-authentication" +} + +variable "vpn_dns_servers" { + description = "DNS servers for VPN clients (leave empty to use VPC DNS)" + type = list(string) + default = [] +} + +variable "vpn_transport_protocol" { + description = "VPN transport protocol (udp recommended for better performance)" + type = string + default = "udp" +} + +variable "vpn_port" { + description = "VPN port (443 or 1194)" + type = number + default = 443 +} + +variable "vpn_session_timeout_hours" { + description = "VPN session timeout in hours (8-24)" + type = number + default = 24 +} + +variable "vpn_client_login_banner" { + description = "Banner text to display on VPN client login" + type = string + default = "Welcome to Code-Server VPN. Authorized access only." +} diff --git a/terraform/deployments/eks/main.tf b/terraform/deployments/eks/main.tf index 9e4fb3a17..3a49e013f 100644 --- a/terraform/deployments/eks/main.tf +++ b/terraform/deployments/eks/main.tf @@ -243,3 +243,26 @@ resource "kubernetes_storage_class" "gp3" { depends_on = [module.eks] } + +# VPN Module (Optional) +module "vpn" { + count = var.enable_vpn ? 1 : 0 + source = "../../modules/vpn" + + name_prefix = local.name_prefix + vpc_id = module.vpc.vpc_id + vpc_cidr = module.vpc.vpc_cidr + subnet_ids = module.vpc.private_subnet_ids + server_certificate_arn = var.vpn_server_certificate_arn + client_certificate_arn = var.vpn_client_certificate_arn + client_cidr_block = var.vpn_client_cidr_block + split_tunnel = var.vpn_split_tunnel + authentication_type = var.vpn_authentication_type + dns_servers = var.vpn_dns_servers + transport_protocol = var.vpn_transport_protocol + vpn_port = var.vpn_port + session_timeout_hours = var.vpn_session_timeout_hours + client_login_banner = var.vpn_client_login_banner + + tags = local.common_tags +} diff --git a/terraform/deployments/eks/outputs.tf b/terraform/deployments/eks/outputs.tf index 92e4f93af..80b708663 100644 --- a/terraform/deployments/eks/outputs.tf +++ b/terraform/deployments/eks/outputs.tf @@ -40,6 +40,21 @@ output "kms_key_arn" { value = module.security.kms_key_arn } +output "vpn_endpoint_id" { + description = "ID of the VPN endpoint (if enabled)" + value = var.enable_vpn ? module.vpn[0].vpn_endpoint_id : null +} + +output "vpn_endpoint_dns_name" { + description = "DNS name of the VPN endpoint (if enabled)" + value = var.enable_vpn ? module.vpn[0].vpn_endpoint_dns_name : null +} + +output "vpn_client_cidr_block" { + description = "CIDR block for VPN clients (if enabled)" + value = var.enable_vpn ? var.vpn_client_cidr_block : null +} + output "configure_kubectl" { description = "Command to configure kubectl" value = "aws eks update-kubeconfig --region ${var.aws_region} --name ${module.eks.cluster_id}" @@ -101,6 +116,7 @@ output "next_steps" { - Data is encrypted at rest using KMS - VPC Flow Logs are enabled for monitoring - IRSA (IAM Roles for Service Accounts) is enabled + ${var.enable_vpn ? "\n VPN Configuration:\n - VPN Endpoint: ${module.vpn[0].vpn_endpoint_dns_name}\n - To export VPN configuration: ../../scripts/export-vpn-config.sh ${module.vpn[0].vpn_endpoint_id} ${var.aws_region}\n - VPN clients will receive IPs from: ${var.vpn_client_cidr_block}" : ""} Useful Commands: - Scale nodes: kubectl scale deployment code-server -n code-server --replicas=3 diff --git a/terraform/deployments/eks/terraform.tfvars.example b/terraform/deployments/eks/terraform.tfvars.example index 80273bec8..20b20441b 100644 --- a/terraform/deployments/eks/terraform.tfvars.example +++ b/terraform/deployments/eks/terraform.tfvars.example @@ -36,3 +36,16 @@ oauth2_client_secret = "your-client-secret-from-idp" # Generate cookie secret with: # python -c 'import os,base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())' oauth2_cookie_secret = "generate-random-secret-here" + +# VPN Configuration (Optional - for enhanced security) +# First run: ../../scripts/generate-vpn-certificates.sh +enable_vpn = false # Set to true to enable VPN +vpn_server_certificate_arn = "" # ARN from generate-vpn-certificates.sh +vpn_client_certificate_arn = "" # ARN from generate-vpn-certificates.sh +vpn_client_cidr_block = "172.16.0.0/22" # Must not overlap with VPC +vpn_split_tunnel = true # Only route VPC traffic through VPN +vpn_authentication_type = "certificate-authentication" +vpn_transport_protocol = "udp" # udp (faster) or tcp (more reliable) +vpn_port = 443 # 443 or 1194 +vpn_session_timeout_hours = 24 # 8-24 hours +vpn_client_login_banner = "Welcome to Code-Server VPN. Authorized access only." diff --git a/terraform/deployments/eks/variables.tf b/terraform/deployments/eks/variables.tf index 6adc6a5ce..5d955c48c 100644 --- a/terraform/deployments/eks/variables.tf +++ b/terraform/deployments/eks/variables.tf @@ -137,3 +137,70 @@ variable "oauth2_cookie_secret" { sensitive = true default = "" } + +# VPN Configuration +variable "enable_vpn" { + description = "Enable AWS Client VPN for secure access" + type = bool + default = false +} + +variable "vpn_server_certificate_arn" { + description = "ARN of server certificate in ACM for VPN" + type = string + default = "" +} + +variable "vpn_client_certificate_arn" { + description = "ARN of client root certificate in ACM for VPN" + type = string + default = "" +} + +variable "vpn_client_cidr_block" { + description = "CIDR block for VPN clients (must not overlap with VPC)" + type = string + default = "172.16.0.0/22" +} + +variable "vpn_split_tunnel" { + description = "Enable split tunnel (only route VPC traffic through VPN)" + type = bool + default = true +} + +variable "vpn_authentication_type" { + description = "VPN authentication type (certificate-authentication recommended)" + type = string + default = "certificate-authentication" +} + +variable "vpn_dns_servers" { + description = "DNS servers for VPN clients (leave empty to use VPC DNS)" + type = list(string) + default = [] +} + +variable "vpn_transport_protocol" { + description = "VPN transport protocol (udp recommended for better performance)" + type = string + default = "udp" +} + +variable "vpn_port" { + description = "VPN port (443 or 1194)" + type = number + default = 443 +} + +variable "vpn_session_timeout_hours" { + description = "VPN session timeout in hours (8-24)" + type = number + default = 24 +} + +variable "vpn_client_login_banner" { + description = "Banner text to display on VPN client login" + type = string + default = "Welcome to Code-Server VPN. Authorized access only." +} diff --git a/terraform/modules/vpn/main.tf b/terraform/modules/vpn/main.tf new file mode 100644 index 000000000..cf31644f9 --- /dev/null +++ b/terraform/modules/vpn/main.tf @@ -0,0 +1,182 @@ +# VPN Module for Code-Server +# Creates AWS Client VPN endpoint for secure access to private resources + +# CloudWatch Log Group for VPN connection logs +resource "aws_cloudwatch_log_group" "vpn" { + name = "/aws/vpn/${var.name_prefix}" + retention_in_days = var.log_retention_days + + tags = var.tags +} + +resource "aws_cloudwatch_log_stream" "vpn" { + name = "vpn-connection-logs" + log_group_name = aws_cloudwatch_log_group.vpn.name +} + +# Client VPN Endpoint +resource "aws_ec2_client_vpn_endpoint" "main" { + description = "Client VPN endpoint for ${var.name_prefix}" + server_certificate_arn = var.server_certificate_arn + client_cidr_block = var.client_cidr_block + split_tunnel = var.split_tunnel + vpc_id = var.vpc_id + + # Authentication using certificate-based or Active Directory + authentication_options { + type = var.authentication_type + root_certificate_chain_arn = var.authentication_type == "certificate-authentication" ? var.client_certificate_arn : null + + # For Active Directory authentication + active_directory_id = var.authentication_type == "directory-service-authentication" ? var.active_directory_id : null + } + + # Additional authentication option for MFA (optional) + dynamic "authentication_options" { + for_each = var.enable_saml_authentication ? [1] : [] + content { + type = "federated-authentication" + saml_provider_arn = var.saml_provider_arn + self_service_saml_provider_arn = var.self_service_saml_provider_arn + } + } + + # Connection logging + connection_log_options { + enabled = true + cloudwatch_log_group = aws_cloudwatch_log_group.vpn.name + cloudwatch_log_stream = aws_cloudwatch_log_stream.vpn.name + } + + # DNS servers to use + dns_servers = var.dns_servers + + # Transport protocol + transport_protocol = var.transport_protocol + + # VPN port + vpn_port = var.vpn_port + + # Session timeout + session_timeout_hours = var.session_timeout_hours + + # Client connect options (for custom authorization) + dynamic "client_connect_options" { + for_each = var.enable_client_connect_handler ? [1] : [] + content { + enabled = true + lambda_function_arn = var.client_connect_lambda_arn + } + } + + # Client login banner + dynamic "client_login_banner_options" { + for_each = var.client_login_banner != "" ? [1] : [] + content { + enabled = true + banner_text = var.client_login_banner + } + } + + tags = merge( + var.tags, + { + Name = "${var.name_prefix}-vpn-endpoint" + } + ) +} + +# Associate VPN endpoint with subnets +resource "aws_ec2_client_vpn_network_association" "main" { + count = length(var.subnet_ids) + client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.main.id + subnet_id = var.subnet_ids[count.index] + + lifecycle { + # The issue why we are ignoring changes is that on the first resource creation, its terraform-aws-client-vpn-endpoint + # This will change on AWS's next apply to the instance ID. + ignore_changes = [subnet_id] + } +} + +# Authorization rule to allow access to VPC CIDR +resource "aws_ec2_client_vpn_authorization_rule" "vpc_access" { + client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.main.id + target_network_cidr = var.vpc_cidr + authorize_all_groups = var.authorize_all_groups + access_group_id = var.authorize_all_groups ? null : var.access_group_id + description = "Allow access to VPC" +} + +# Additional authorization rules for specific CIDRs +resource "aws_ec2_client_vpn_authorization_rule" "additional" { + count = length(var.additional_authorization_rules) + client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.main.id + target_network_cidr = var.additional_authorization_rules[count.index].cidr + authorize_all_groups = var.additional_authorization_rules[count.index].authorize_all_groups + access_group_id = var.additional_authorization_rules[count.index].access_group_id + description = var.additional_authorization_rules[count.index].description +} + +# Route to direct traffic to the VPC +resource "aws_ec2_client_vpn_route" "vpc_route" { + count = var.split_tunnel ? 1 : 0 + client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.main.id + destination_cidr_block = var.vpc_cidr + target_vpc_subnet_id = aws_ec2_client_vpn_network_association.main[0].subnet_id + description = "Route to VPC" + + depends_on = [aws_ec2_client_vpn_network_association.main] +} + +# Additional routes for specific networks +resource "aws_ec2_client_vpn_route" "additional" { + count = length(var.additional_routes) + client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.main.id + destination_cidr_block = var.additional_routes[count.index].cidr + target_vpc_subnet_id = aws_ec2_client_vpn_network_association.main[0].subnet_id + description = var.additional_routes[count.index].description + + depends_on = [aws_ec2_client_vpn_network_association.main] +} + +# Security group for VPN endpoint +resource "aws_security_group" "vpn" { + name_prefix = "${var.name_prefix}-vpn-" + description = "Security group for Client VPN endpoint" + vpc_id = var.vpc_id + + ingress { + description = "VPN traffic" + from_port = var.vpn_port + to_port = var.vpn_port + protocol = var.transport_protocol == "tcp" ? "tcp" : "udp" + cidr_blocks = var.vpn_ingress_cidr_blocks + } + + egress { + description = "Allow all outbound" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = merge( + var.tags, + { + Name = "${var.name_prefix}-vpn-sg" + } + ) + + lifecycle { + create_before_destroy = true + } +} + +# Apply security group to VPN endpoint +resource "aws_ec2_client_vpn_endpoint_security_group_association" "main" { + count = var.apply_security_group ? 1 : 0 + client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.main.id + security_group_id = aws_security_group.vpn.id +} diff --git a/terraform/modules/vpn/outputs.tf b/terraform/modules/vpn/outputs.tf new file mode 100644 index 000000000..1d56705c4 --- /dev/null +++ b/terraform/modules/vpn/outputs.tf @@ -0,0 +1,36 @@ +# VPN Module Outputs + +output "vpn_endpoint_id" { + description = "ID of the Client VPN endpoint" + value = aws_ec2_client_vpn_endpoint.main.id +} + +output "vpn_endpoint_arn" { + description = "ARN of the Client VPN endpoint" + value = aws_ec2_client_vpn_endpoint.main.arn +} + +output "vpn_endpoint_dns_name" { + description = "DNS name of the Client VPN endpoint" + value = aws_ec2_client_vpn_endpoint.main.dns_name +} + +output "vpn_security_group_id" { + description = "ID of the VPN security group" + value = aws_security_group.vpn.id +} + +output "client_cidr_block" { + description = "CIDR block assigned to VPN clients" + value = var.client_cidr_block +} + +output "vpn_endpoint_status" { + description = "Status of the Client VPN endpoint" + value = aws_ec2_client_vpn_endpoint.main.status +} + +output "cloudwatch_log_group" { + description = "CloudWatch Log Group for VPN connections" + value = aws_cloudwatch_log_group.vpn.name +} diff --git a/terraform/modules/vpn/variables.tf b/terraform/modules/vpn/variables.tf new file mode 100644 index 000000000..f2255b162 --- /dev/null +++ b/terraform/modules/vpn/variables.tf @@ -0,0 +1,192 @@ +# VPN Module Variables + +variable "name_prefix" { + description = "Prefix for resource names" + type = string +} + +variable "vpc_id" { + description = "ID of the VPC" + type = string +} + +variable "vpc_cidr" { + description = "CIDR block of the VPC" + type = string +} + +variable "subnet_ids" { + description = "List of subnet IDs to associate with VPN endpoint" + type = list(string) +} + +variable "server_certificate_arn" { + description = "ARN of the server certificate in ACM" + type = string +} + +variable "client_certificate_arn" { + description = "ARN of the client root certificate in ACM (for certificate authentication)" + type = string + default = "" +} + +variable "client_cidr_block" { + description = "CIDR block for VPN clients" + type = string + default = "172.16.0.0/22" +} + +variable "split_tunnel" { + description = "Enable split tunnel (only route VPC traffic through VPN)" + type = bool + default = true +} + +variable "authentication_type" { + description = "Authentication type (certificate-authentication, directory-service-authentication, or federated-authentication)" + type = string + default = "certificate-authentication" + + validation { + condition = contains(["certificate-authentication", "directory-service-authentication", "federated-authentication"], var.authentication_type) + error_message = "Authentication type must be certificate-authentication, directory-service-authentication, or federated-authentication." + } +} + +variable "active_directory_id" { + description = "ID of Active Directory (for directory-service-authentication)" + type = string + default = null +} + +variable "enable_saml_authentication" { + description = "Enable SAML-based federated authentication as second factor" + type = bool + default = false +} + +variable "saml_provider_arn" { + description = "ARN of the SAML provider (for federated-authentication)" + type = string + default = null +} + +variable "self_service_saml_provider_arn" { + description = "ARN of the IAM SAML identity provider for self-service portal" + type = string + default = null +} + +variable "dns_servers" { + description = "DNS servers for VPN clients" + type = list(string) + default = [] +} + +variable "transport_protocol" { + description = "Transport protocol (tcp or udp)" + type = string + default = "udp" + + validation { + condition = contains(["tcp", "udp"], var.transport_protocol) + error_message = "Transport protocol must be tcp or udp." + } +} + +variable "vpn_port" { + description = "VPN port (443 or 1194)" + type = number + default = 443 + + validation { + condition = contains([443, 1194], var.vpn_port) + error_message = "VPN port must be 443 or 1194." + } +} + +variable "session_timeout_hours" { + description = "Maximum VPN session duration in hours (8-24)" + type = number + default = 24 + + validation { + condition = var.session_timeout_hours >= 8 && var.session_timeout_hours <= 24 + error_message = "Session timeout must be between 8 and 24 hours." + } +} + +variable "authorize_all_groups" { + description = "Authorize all groups for VPC access" + type = bool + default = true +} + +variable "access_group_id" { + description = "Access group ID for authorization (Active Directory group)" + type = string + default = null +} + +variable "additional_authorization_rules" { + description = "Additional authorization rules for specific CIDRs" + type = list(object({ + cidr = string + authorize_all_groups = bool + access_group_id = string + description = string + })) + default = [] +} + +variable "additional_routes" { + description = "Additional routes for VPN clients" + type = list(object({ + cidr = string + description = string + })) + default = [] +} + +variable "log_retention_days" { + description = "Number of days to retain VPN connection logs" + type = number + default = 30 +} + +variable "enable_client_connect_handler" { + description = "Enable client connect handler (Lambda function for custom authorization)" + type = bool + default = false +} + +variable "client_connect_lambda_arn" { + description = "ARN of Lambda function for client connect handler" + type = string + default = null +} + +variable "client_login_banner" { + description = "Text to display in client login banner" + type = string + default = "" +} + +variable "vpn_ingress_cidr_blocks" { + description = "CIDR blocks allowed to connect to VPN" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "apply_security_group" { + description = "Apply security group to VPN endpoint" + type = bool + default = true +} + +variable "tags" { + description = "Tags to apply to resources" + type = map(string) + default = {} +} diff --git a/terraform/scripts/export-vpn-config.sh b/terraform/scripts/export-vpn-config.sh new file mode 100755 index 000000000..0d15a1a14 --- /dev/null +++ b/terraform/scripts/export-vpn-config.sh @@ -0,0 +1,157 @@ +#!/bin/bash +# Export AWS Client VPN configuration +# This script downloads the VPN client configuration file + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +echo_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +echo_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +echo_step() { + echo -e "${BLUE}[STEP]${NC} $1" +} + +# Check prerequisites +if ! command -v aws &> /dev/null; then + echo_error "AWS CLI is not installed. Please install AWS CLI first." + exit 1 +fi + +if ! aws sts get-caller-identity &> /dev/null; then + echo_error "AWS credentials are not configured. Please configure AWS credentials first." + exit 1 +fi + +# Get VPN endpoint ID from Terraform output or as parameter +if [ -z "$1" ]; then + echo_info "No VPN endpoint ID provided, attempting to get from Terraform..." + + # Try to get from terraform output + if [ -f "terraform.tfstate" ]; then + VPN_ENDPOINT_ID=$(terraform output -raw vpn_endpoint_id 2>/dev/null || echo "") + fi + + if [ -z "$VPN_ENDPOINT_ID" ]; then + echo_error "Could not find VPN endpoint ID." + echo_error "Usage: $0 [region] [output-dir]" + echo_error "Or run this script from the terraform deployment directory" + exit 1 + fi +else + VPN_ENDPOINT_ID="$1" +fi + +REGION="${2:-us-east-1}" +OUTPUT_DIR="${3:-./vpn-config}" +CERT_DIR="${4:-./vpn-certificates}" + +echo_info "VPN Endpoint ID: ${VPN_ENDPOINT_ID}" +echo_info "AWS Region: ${REGION}" +echo_info "Output Directory: ${OUTPUT_DIR}" +echo "" + +# Create output directory +mkdir -p "${OUTPUT_DIR}" + +echo_step "Step 1: Export VPN client configuration" +echo_info "Downloading VPN configuration from AWS..." + +# Export the configuration +aws ec2 export-client-vpn-client-configuration \ + --client-vpn-endpoint-id "${VPN_ENDPOINT_ID}" \ + --region "${REGION}" \ + --output text > "${OUTPUT_DIR}/client-config.ovpn" + +echo_info "VPN configuration downloaded to: ${OUTPUT_DIR}/client-config.ovpn" +echo "" + +echo_step "Step 2: Add client certificate and key to configuration" + +# Check if certificate files exist +if [ ! -f "${CERT_DIR}/client.crt" ] || [ ! -f "${CERT_DIR}/client.key" ]; then + echo_warn "Client certificates not found in ${CERT_DIR}" + echo_warn "You'll need to manually add and sections to the .ovpn file" + echo_warn "Or specify the correct certificate directory as 4th parameter" +else + echo_info "Adding client certificate and key to configuration..." + + # Append certificate and key to the configuration + echo "" >> "${OUTPUT_DIR}/client-config.ovpn" + echo "" >> "${OUTPUT_DIR}/client-config.ovpn" + cat "${CERT_DIR}/client.crt" >> "${OUTPUT_DIR}/client-config.ovpn" + echo "" >> "${OUTPUT_DIR}/client-config.ovpn" + echo "" >> "${OUTPUT_DIR}/client-config.ovpn" + echo "" >> "${OUTPUT_DIR}/client-config.ovpn" + cat "${CERT_DIR}/client.key" >> "${OUTPUT_DIR}/client-config.ovpn" + echo "" >> "${OUTPUT_DIR}/client-config.ovpn" + + echo_info "Client certificate and key added to configuration" +fi + +echo "" + +echo_step "Step 3: Create platform-specific configurations" + +# Copy for different platforms +cp "${OUTPUT_DIR}/client-config.ovpn" "${OUTPUT_DIR}/code-server-vpn.ovpn" + +echo_info "Configuration files created:" +echo " ${OUTPUT_DIR}/client-config.ovpn - Original configuration" +echo " ${OUTPUT_DIR}/code-server-vpn.ovpn - Ready to import" +echo "" + +echo_step "Installation Instructions:" +echo "" +echo "📱 macOS:" +echo " 1. Install Tunnelblick: https://tunnelblick.net/downloads.html" +echo " 2. Double-click code-server-vpn.ovpn" +echo " 3. Click 'Connect'" +echo "" +echo "🪟 Windows:" +echo " 1. Install OpenVPN Connect: https://openvpn.net/client-connect-vpn-for-windows/" +echo " 2. Import code-server-vpn.ovpn" +echo " 3. Click 'Connect'" +echo "" +echo "🐧 Linux:" +echo " 1. Install OpenVPN:" +echo " sudo apt-get install openvpn # Debian/Ubuntu" +echo " sudo yum install openvpn # RHEL/CentOS" +echo " 2. Connect using:" +echo " sudo openvpn --config ${OUTPUT_DIR}/code-server-vpn.ovpn" +echo "" +echo "📱 iOS:" +echo " 1. Install OpenVPN Connect from App Store" +echo " 2. Transfer code-server-vpn.ovpn to your device" +echo " 3. Import and connect" +echo "" +echo "🤖 Android:" +echo " 1. Install OpenVPN for Android from Play Store" +echo " 2. Transfer code-server-vpn.ovpn to your device" +echo " 3. Import and connect" +echo "" + +echo_info "✅ VPN configuration export complete!" +echo_warn "🔒 Please distribute this configuration securely to authorized users only" +echo "" + +echo_info "To test the VPN connection:" +echo " 1. Connect to VPN using the configuration file" +echo " 2. Access code-server at the private ALB address" +echo " 3. Check CloudWatch Logs for VPN connection logs:" +echo " aws logs tail /aws/vpn/ --follow" diff --git a/terraform/scripts/generate-vpn-certificates.sh b/terraform/scripts/generate-vpn-certificates.sh new file mode 100755 index 000000000..b4da182a8 --- /dev/null +++ b/terraform/scripts/generate-vpn-certificates.sh @@ -0,0 +1,177 @@ +#!/bin/bash +# Generate certificates for AWS Client VPN +# This script creates server and client certificates required for VPN authentication + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +echo_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +echo_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +echo_step() { + echo -e "${BLUE}[STEP]${NC} $1" +} + +# Check prerequisites +check_prerequisites() { + echo_info "Checking prerequisites..." + + if ! command -v openssl &> /dev/null; then + echo_error "OpenSSL is not installed. Please install OpenSSL first." + exit 1 + fi + + if ! command -v aws &> /dev/null; then + echo_error "AWS CLI is not installed. Please install AWS CLI first." + exit 1 + fi + + if ! aws sts get-caller-identity &> /dev/null; then + echo_error "AWS credentials are not configured. Please configure AWS credentials first." + exit 1 + fi + + echo_info "All prerequisites met!" +} + +# Configuration +CERT_DIR="${1:-./vpn-certificates}" +REGION="${2:-us-east-1}" +COMMON_NAME="${3:-code-server-vpn}" + +echo_info "Certificate Directory: ${CERT_DIR}" +echo_info "AWS Region: ${REGION}" +echo_info "Common Name: ${COMMON_NAME}" +echo "" + +# Create certificate directory +mkdir -p "${CERT_DIR}" +cd "${CERT_DIR}" + +echo_step "Step 1: Generate CA private key and certificate" +echo_info "Creating Certificate Authority (CA)..." + +# Generate CA private key +openssl genrsa -out ca.key 2048 + +# Generate CA certificate +openssl req -new -x509 -days 3650 -key ca.key -out ca.crt -subj "/C=US/ST=State/L=City/O=Organization/OU=IT/CN=${COMMON_NAME}-ca" + +echo_info "CA certificate created: ca.crt" +echo "" + +echo_step "Step 2: Generate server private key and certificate" +echo_info "Creating server certificate..." + +# Generate server private key +openssl genrsa -out server.key 2048 + +# Generate server certificate signing request (CSR) +openssl req -new -key server.key -out server.csr -subj "/C=US/ST=State/L=City/O=Organization/OU=IT/CN=${COMMON_NAME}-server" + +# Sign server certificate with CA +openssl x509 -req -days 3650 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt + +echo_info "Server certificate created: server.crt" +echo "" + +echo_step "Step 3: Generate client private key and certificate" +echo_info "Creating client certificate..." + +# Generate client private key +openssl genrsa -out client.key 2048 + +# Generate client certificate signing request (CSR) +openssl req -new -key client.key -out client.csr -subj "/C=US/ST=State/L=City/O=Organization/OU=IT/CN=${COMMON_NAME}-client" + +# Sign client certificate with CA +openssl x509 -req -days 3650 -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt + +echo_info "Client certificate created: client.crt" +echo "" + +echo_step "Step 4: Upload certificates to AWS Certificate Manager" +echo_info "Uploading server certificate to ACM..." + +# Upload server certificate +SERVER_CERT_ARN=$(aws acm import-certificate \ + --certificate fileb://server.crt \ + --private-key fileb://server.key \ + --certificate-chain fileb://ca.crt \ + --region ${REGION} \ + --tags Key=Name,Value=${COMMON_NAME}-server Key=Purpose,Value=VPN-Server \ + --query CertificateArn \ + --output text) + +echo_info "Server certificate uploaded: ${SERVER_CERT_ARN}" + +# Upload client certificate (root CA) +echo_info "Uploading client root certificate to ACM..." +CLIENT_CERT_ARN=$(aws acm import-certificate \ + --certificate fileb://ca.crt \ + --private-key fileb://ca.key \ + --region ${REGION} \ + --tags Key=Name,Value=${COMMON_NAME}-client-root Key=Purpose,Value=VPN-Client-Root \ + --query CertificateArn \ + --output text) + +echo_info "Client root certificate uploaded: ${CLIENT_CERT_ARN}" +echo "" + +echo_step "Step 5: Save certificate ARNs to file" + +cat > certificate-arns.txt < terraform-vars.txt <