Add AWS Client VPN support for secure private access to code-server

This commit adds comprehensive VPN infrastructure to enable secure,
certificate-based access to code-server deployments. VPN provides an
additional security layer by requiring network-level authentication
before accessing internal resources.

Features:
- AWS Client VPN endpoint with certificate-based authentication
- Split tunnel support (route only VPC traffic through VPN)
- CloudWatch logging for all VPN connections
- Multi-platform client support (Windows, macOS, Linux, iOS, Android)
- Automatic certificate generation and ACM upload
- Client configuration export scripts
- Integration with both EC2 and EKS deployments

New Terraform Module:
- modules/vpn: Complete AWS Client VPN infrastructure
  - VPN endpoint with configurable authentication
  - Network associations for HA across multiple AZs
  - Authorization rules for VPC access
  - Security groups for VPN traffic
  - CloudWatch log groups and streams
  - Support for SAML/federated authentication

Scripts:
- scripts/generate-vpn-certificates.sh: Generate and upload VPN certificates
  - Creates CA, server, and client certificates
  - Automatically uploads to AWS Certificate Manager
  - Outputs certificate ARNs for Terraform configuration
- scripts/export-vpn-config.sh: Export client VPN configuration
  - Downloads VPN config from AWS
  - Embeds client certificates
  - Creates platform-ready .ovpn files

Deployment Updates:
- EC2 and EKS deployments now support optional VPN
- New variables for VPN configuration
- Updated outputs to include VPN endpoint information
- Example configurations with VPN settings

Documentation:
- VPN-SETUP-GUIDE.md: Comprehensive VPN setup guide
  - Certificate generation process
  - Terraform configuration
  - Client setup for all major platforms
  - Testing and troubleshooting
  - Advanced configuration options
  - Cost considerations and optimization

Configuration Options:
- Certificate-based or SAML/SSO authentication
- Split tunnel (recommended) or full tunnel
- UDP (faster) or TCP (more reliable) transport
- Configurable session timeout (8-24 hours)
- Custom DNS servers
- Client login banner
- Multiple authorization rules

Security Features:
- X.509 certificate authentication
- Private subnet associations
- Network-level access control
- Session logging and audit trail
- Support for multi-factor (VPN cert + OAuth2/SAML)

Cost: ~$216/month base + ~$0.40/user/day for active connections
This commit is contained in:
Claude 2025-11-15 17:40:23 +00:00
parent b8094ac6a0
commit 369f459203
No known key found for this signature in database
14 changed files with 1545 additions and 0 deletions

View file

@ -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 <vpn-endpoint-id> [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://<alb-dns-name>
# EKS
curl -I http://<alb-dns-name>
```
**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/<prefix> --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 <vpn-endpoint-id> 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 <endpoint-id>
```
**Check CloudWatch Logs:**
```bash
aws logs tail /aws/vpn/<prefix> --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 <endpoint-id>
```
**3. Check Security Groups:**
```bash
# Verify VPN security group allows traffic to ALB
aws ec2 describe-security-groups --group-ids <sg-id>
```
**4. Test DNS Resolution:**
```bash
nslookup <alb-dns-name>
dig <alb-dns-name>
```
### 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)

View file

@ -115,3 +115,26 @@ module "code_server_ec2" {
tags = local.common_tags 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
}

View file

@ -40,6 +40,21 @@ output "kms_key_arn" {
value = module.security.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" { output "next_steps" {
description = "Next steps to complete the setup" description = "Next steps to complete the setup"
value = <<-EOT value = <<-EOT
@ -73,5 +88,6 @@ output "next_steps" {
- ALB is ${var.internal_alb ? "internal (private network only)" : "public"} - ALB is ${var.internal_alb ? "internal (private network only)" : "public"}
- Data is encrypted at rest using KMS - Data is encrypted at rest using KMS
- VPC Flow Logs are enabled for monitoring - 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 EOT
} }

View file

@ -45,3 +45,16 @@ oauth2_allowed_emails = [
# "user1@example.com", # "user1@example.com",
# "user2@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."

View file

@ -145,3 +145,70 @@ variable "oauth2_allowed_emails" {
type = list(string) type = list(string)
default = [] 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."
}

View file

@ -243,3 +243,26 @@ resource "kubernetes_storage_class" "gp3" {
depends_on = [module.eks] 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
}

View file

@ -40,6 +40,21 @@ output "kms_key_arn" {
value = module.security.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" { output "configure_kubectl" {
description = "Command to configure kubectl" description = "Command to configure kubectl"
value = "aws eks update-kubeconfig --region ${var.aws_region} --name ${module.eks.cluster_id}" 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 - Data is encrypted at rest using KMS
- VPC Flow Logs are enabled for monitoring - VPC Flow Logs are enabled for monitoring
- IRSA (IAM Roles for Service Accounts) is enabled - 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: Useful Commands:
- Scale nodes: kubectl scale deployment code-server -n code-server --replicas=3 - Scale nodes: kubectl scale deployment code-server -n code-server --replicas=3

View file

@ -36,3 +36,16 @@ oauth2_client_secret = "your-client-secret-from-idp"
# Generate cookie secret with: # Generate cookie secret with:
# python -c 'import os,base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())' # python -c 'import os,base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())'
oauth2_cookie_secret = "generate-random-secret-here" 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."

View file

@ -137,3 +137,70 @@ variable "oauth2_cookie_secret" {
sensitive = true sensitive = true
default = "" 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."
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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 = {}
}

View file

@ -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 <vpn-endpoint-id> [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 <cert> and <key> 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 "<cert>" >> "${OUTPUT_DIR}/client-config.ovpn"
cat "${CERT_DIR}/client.crt" >> "${OUTPUT_DIR}/client-config.ovpn"
echo "</cert>" >> "${OUTPUT_DIR}/client-config.ovpn"
echo "" >> "${OUTPUT_DIR}/client-config.ovpn"
echo "<key>" >> "${OUTPUT_DIR}/client-config.ovpn"
cat "${CERT_DIR}/client.key" >> "${OUTPUT_DIR}/client-config.ovpn"
echo "</key>" >> "${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/<prefix> --follow"

View file

@ -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 <<EOF
Server Certificate ARN: ${SERVER_CERT_ARN}
Client Root Certificate ARN: ${CLIENT_CERT_ARN}
EOF
cat > terraform-vars.txt <<EOF
# Add these to your terraform.tfvars file:
enable_vpn = true
vpn_server_certificate_arn = "${SERVER_CERT_ARN}"
vpn_client_certificate_arn = "${CLIENT_CERT_ARN}"
EOF
echo_info "Certificate ARNs saved to certificate-arns.txt"
echo ""
echo_step "Summary of Generated Files:"
echo " ca.key - CA private key (keep secure!)"
echo " ca.crt - CA certificate"
echo " server.key - Server private key (keep secure!)"
echo " server.crt - Server certificate"
echo " client.key - Client private key (distribute to VPN users)"
echo " client.crt - Client certificate (distribute to VPN users)"
echo ""
echo_step "Important Notes:"
echo " 1. Store ca.key and server.key securely (never share these!)"
echo " 2. Distribute client.key and client.crt to VPN users"
echo " 3. Add the certificate ARNs to your terraform.tfvars:"
cat terraform-vars.txt
echo ""
echo_info "✅ Certificate generation complete!"
echo_warn "🔒 Please backup the ${CERT_DIR} directory securely"
echo ""
echo_info "Next steps:"
echo " 1. Add the certificate ARNs to terraform.tfvars"
echo " 2. Set enable_vpn = true in terraform.tfvars"
echo " 3. Run terraform apply"
echo " 4. Export VPN client configuration using export-vpn-config.sh"