commit 1c3c06f120f2029900c3da85639d317528bfe445 Author: bumpsoo Date: Sat Jan 31 13:48:57 2026 +0000 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1453827 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +**/.terraform/* +*.tfstate +*.tfstate.* +crash.log +crash.*.log +*.tfvars +*.tfvars.json +override.tf +override.tf.json +*_override.tf +*_override.tf.json +.terraformrc +terraform.rc diff --git a/.terraform.lock.hcl b/.terraform.lock.hcl new file mode 100644 index 0000000..f7100af --- /dev/null +++ b/.terraform.lock.hcl @@ -0,0 +1,18 @@ +# This file is maintained automatically by "tofu init". +# Manual edits may be lost in future updates. + +provider "registry.opentofu.org/hashicorp/google" { + version = "7.17.0" + hashes = [ + "h1:55qzQsJnDJWg4+xuWuKKM7RykN2C52Fe5VjrQZxTyzY=", + "zh:54a635037ba6849fedb5e9a4e45c038fe8692da9d05d786633a222c866b1bbd3", + "zh:5703da54cc1e5eccc47678cfdd37a6108618299455f9eb97b2e078fcab73459d", + "zh:63841ec08783970b6632d626bbd03b2280e683877ec65522430f10d507bc8de0", + "zh:8dc39e425b456be1543f55598fe15ae94b2cacce13fddfe5412fa6086c39340f", + "zh:9d2cc02b387d3256800c35099ec034bb4b05bf819a50e28182536a1dc030f8e4", + "zh:b379befb2a6389df66bb957fe56cc5eeda795fec28d663d8e80879706fce6e0b", + "zh:e67c1f491c222aecd5c477b9a92d1efecea9ed8ebda9d886db983c6f81e8697a", + "zh:f18c5559f16de7ed0e2ec633f04d7bc65ca5977a8711118207b4d77e4345bcf3", + "zh:f80311efc8139381713a8946f6f1924ecfe533366ada0bf590b4426534861754", + ] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..b3a0a1c --- /dev/null +++ b/README.md @@ -0,0 +1,155 @@ + +# GCP to Local Home Network VPN Setup + +This project uses OpenTofu (or Terraform) to configure a VPC and VPN on Google Cloud Platform (GCP), establishing a Site-to-Site VPN connection with a home server (e.g., Raspberry Pi). + +## Architecture Overview + +- **GCP Side**: + - **VPC Network**: `rpi-vpn-test-vpc` + - **Subnet**: `rpi-vpn-subnet` (`10.10.0.0/24`) + - **VPN Gateway**: Classic VPN Gateway + - **Firewall Rules**: + - Allow VPN and internal traffic (ICMP, TCP, UDP) + - Allow SSH access (for test VM) + - **Compute Engine**: `vpn-test-vm` (e2-micro instance for connection testing) + +- **Local Side (User Environment)**: + - Raspberry Pi (or home server) behind a router running a VPN daemon (e.g., StrongSwan, Libreswan) + - Port Forwarding required: UDP `500`, `4500` -> Raspberry Pi internal IP + +## Prerequisites + +1. **GCP Account & Project**: A billing-enabled GCP project is required. +2. **OpenTofu/Terraform Installed**: Refer to the [OpenTofu Installation Guide](https://opentofu.org/docs/intro/install/). +3. **GCP Authentication**: Authenticate locally via terminal. + ```bash + gcloud auth application-default login + ``` +4. **Check Home Public IP**: Identify your current public IP address. + +## Usage + +### 1. Configure Variables (`terraform.tfvars`) + +Create a `terraform.tfvars` file in the project root and configure it according to your environment. + +```hcl +project_id = "YOUR_GCP_PROJECT_ID" +home_public_ip = "123.123.123.123" +vpn_psk = "your-secret-password" +home_internal_cidr = "192.168.0.0/24" +ssh_public_key = "ssh-rsa AAA..." +``` + +### 2. Initialize and Apply + +```bash +# Initialize +tofu init + +# Plan +tofu plan + +# Apply +tofu apply +``` + +### 3. Check Results + +Upon completion of `tofu apply`, the following information will be output: + +- `gcp_vpn_ip`: The public IP of the GCP VPN Gateway (target for Raspberry Pi connection). +- `vm_public_ip`: The public IP of the test VM. + +## Raspberry Pi Configuration Guide (StrongSwan with swanctl) + +This guide uses the modern `swanctl` (VICI protocol) provided by StrongSwan 6.x+. + +1. **Install StrongSwan and Plugins**: + ```bash + sudo apt-get update + sudo apt-get install -y strongswan libcharon-extra-plugins libstrongswan-extra-plugins libstrongswan-standard-plugins strongswan-pki + ``` + +2. **Configure `/etc/swanctl/swanctl.conf`**: + Replace the content of `/etc/swanctl/swanctl.conf` with the following configuration. + + > **Note**: Replace `YOUR_HOME_PUBLIC_IP`, `GCP_VPN_IP`, `YOUR_PSK`, and `HOME_CIDR` with your actual values. + + ```conf + connections { + gcp-vpn { + remote_addrs = GCP_VPN_IP + + local { + auth = psk + id = YOUR_HOME_PUBLIC_IP + } + + remote { + auth = psk + id = GCP_VPN_IP + } + + children { + gcp-net { + local_ts = 192.168.0.0/24 # Your Home Network CIDR (e.g., 192.168.2.0/24) + remote_ts = 10.10.0.0/24 # GCP Network CIDR + + esp_proposals = aes256-sha1-modp2048 + start_action = start + } + } + + version = 2 + proposals = aes256-sha1-modp2048 + } + } + + secrets { + ike-gcp { + id = GCP_VPN_IP + secret = "YOUR_PSK" + } + } + ``` + +3. **Apply & Start**: + ```bash + # Load configuration + sudo swanctl --load-all + + # Check status (should show ESTABLISHED) + sudo swanctl --list-sas + ``` + +4. **Enable IP Forwarding**: + For the Raspberry Pi to act as a gateway and forward traffic to other devices, you must enable packet forwarding. + + Edit `/etc/sysctl.conf` and uncomment (or add) the following line: + ```conf + net.ipv4.ip_forward=1 + ``` + + Apply changes: + ```bash + sudo sysctl -p + ``` + +- **Important**: Ensure UDP `500` and `4500` ports are port-forwarded to the Raspberry Pi's internal IP in your router settings. +- **Routing Setup (Home Router)**: You must configure a **Static Route** on your home router so that other devices in your home network can reach the GCP network. + - **Destination Network**: `10.10.0.0` + - **Subnet Mask**: `255.255.255.0` (or `/24`) + - **Gateway**: The internal IP of your Raspberry Pi (e.g., `192.168.2.x`) + + Without this, only the Raspberry Pi itself can access the GCP network. Other devices (like your PC) won't know that traffic for `10.10.0.x` should go through the Raspberry Pi. + +## Connection Test + +1. Verify the tunnel status is "Established" in the [VPN](https://console.cloud.google.com/hybrid/vpn/gateways) menu of the GCP Console. +2. SSH into the test VM (`vpn-test-vm`). +3. Ping a device in the home internal network (e.g., Raspberry Pi). + ```bash + ping 192.168.0.x + ``` diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..98d57a2 --- /dev/null +++ b/main.tf @@ -0,0 +1,133 @@ +provider "google" { + project = var.project_id + region = var.region +} + +data "google_compute_zones" "available" { + region = var.region +} + +resource "google_compute_network" "vpc" { + name = "rpi-vpn-test-vpc" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "subnet" { + name = "rpi-vpn-subnet" + ip_cidr_range = "10.10.0.0/24" + region = var.region + network = google_compute_network.vpc.id +} + +resource "google_compute_firewall" "allow_internal" { + name = "allow-vpn-internal" + network = google_compute_network.vpc.name + + allow { + protocol = "icmp" + } + allow { + protocol = "tcp" + ports = ["0-65535"] + } + allow { + protocol = "udp" + ports = ["0-65535"] + } + + source_ranges = ["10.10.0.0/24", var.home_internal_cidr] +} + +resource "google_compute_firewall" "allow_ssh" { + name = "allow-ssh-from-my-ip" + network = google_compute_network.vpc.name + + allow { + protocol = "tcp" + ports = ["22"] + } + + target_tags = ["vpn-test-vm"] + source_ranges = ["0.0.0.0/0"] +} + +resource "google_compute_vpn_gateway" "target_gateway" { + name = "rpi-vpn-gateway" + network = google_compute_network.vpc.id + region = var.region +} + +resource "google_compute_address" "vpn_static_ip" { + name = "vpn-static-ip" + region = var.region +} + +resource "google_compute_vpn_tunnel" "tunnel" { + name = "rpi-vpn-tunnel" + peer_ip = var.home_public_ip + shared_secret = var.vpn_psk + target_vpn_gateway = google_compute_vpn_gateway.target_gateway.id + + local_traffic_selector = ["10.10.0.0/24"] + remote_traffic_selector = [var.home_internal_cidr] + + depends_on = [ + google_compute_forwarding_rule.fr_esp, + google_compute_forwarding_rule.fr_udp500, + google_compute_forwarding_rule.fr_udp4500, + ] +} + +resource "google_compute_forwarding_rule" "fr_esp" { + name = "fr-esp" + ip_protocol = "ESP" + ip_address = google_compute_address.vpn_static_ip.address + target = google_compute_vpn_gateway.target_gateway.id +} + +resource "google_compute_forwarding_rule" "fr_udp500" { + name = "fr-udp500" + ip_protocol = "UDP" + port_range = "500" + ip_address = google_compute_address.vpn_static_ip.address + target = google_compute_vpn_gateway.target_gateway.id +} + +resource "google_compute_forwarding_rule" "fr_udp4500" { + name = "fr-udp4500" + ip_protocol = "UDP" + port_range = "4500" + ip_address = google_compute_address.vpn_static_ip.address + target = google_compute_vpn_gateway.target_gateway.id +} + +resource "google_compute_route" "route_to_home" { + name = "route-to-home" + network = google_compute_network.vpc.name + dest_range = var.home_internal_cidr + priority = 1000 + next_hop_vpn_tunnel = google_compute_vpn_tunnel.tunnel.id +} + +resource "google_compute_instance" "test_vm" { + name = "vpn-test-vm" + machine_type = "e2-micro" + zone = data.google_compute_zones.available.names[0] + tags = ["vpn-test-vm"] + + boot_disk { + initialize_params { + image = "debian-cloud/debian-12" + } + } + + network_interface { + subnetwork = google_compute_subnetwork.subnet.id + access_config { + } + } + + metadata = { + ssh-keys = "${var.ssh_user}:${var.ssh_public_key}" + } +} \ No newline at end of file diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..7420e7a --- /dev/null +++ b/outputs.tf @@ -0,0 +1,6 @@ +output "gcp_vpn_ip" { + value = google_compute_address.vpn_static_ip.address +} +output "vm_public_ip" { + value = google_compute_instance.test_vm.network_interface.0.access_config.0.nat_ip +} \ No newline at end of file diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..3b68d34 --- /dev/null +++ b/variables.tf @@ -0,0 +1,36 @@ +variable "project_id" { + description = "GCP Project ID" + type = string +} + +variable "region" { + default = "asia-northeast3" +} + +variable "home_public_ip" { + description = "Public IP of the home router" + type = string +} + +variable "home_internal_cidr" { + description = "Home internal network CIDR (e.g., 192.168.0.0/24)" + type = string + default = "192.168.0.0/24" +} + +variable "vpn_psk" { + description = "VPN Pre-Shared Key" + type = string + sensitive = true +} + +variable "ssh_user" { + description = "SSH Username" + type = string + default = "bumpsoo" +} + +variable "ssh_public_key" { + description = "SSH Public Key content" + type = string +} \ No newline at end of file