GCP VM(+UDP LB) 구성하여 Wireguard VPN 설치
GCP(Google Cloud Platform)에 GCE 인스턴스를 생성하여 오픈소스 VPN 인 wireguard 을 설치하고, UDP passthrough LB 를 추가하여 구성한다.
# VPN VM 생성
GCP 콘솔에서 생성하거나 gcloud 커맨드를 통해 생성해도 관계 없지만, 어떤 방법으로 생성하든 "IP forwarding"(terraform 에서는 "can_ip_forward") 옵션을 반드시 활성화해야한다.
resource "google_compute_instance" "vpn" {
name = "test-vpn"
project = var.gcp_project
machine_type = "e2-medium"
zone = "asia-northeast3-a"
can_ip_forward = true
tags = ["vpn"]
boot_disk {
initialize_params {
image = "ubuntu-os-cloud/ubuntu-2204-lts"
size = "50"
}
}
network_interface {
network = google_compute_network.test-network.id
subnetwork = google_compute_subnetwork.test-subnetwork.id
}
metadata_startup_script = "apt-get update && apt-get upgrade -y"
service_account {
email = google_service_account.test-sa.email
scopes = ["cloud-platform"]
}
}
# test nginx 웹서버 VM
이 VM은 VPN을 통해 접속한 사용자가 접속하기 위한 프라이빗 네트워크의 웹 서버 역할을 한다.
resource "google_compute_instance" "nginx" {
name = "test-nginx"
project = var.gcp_project
machine_type = "e2-medium"
zone = "asia-northeast3-a"
tags = ["nginx"]
boot_disk {
initialize_params {
image = "ubuntu-os-cloud/ubuntu-2204-lts"
size = "50"
}
}
network_interface {
network = google_compute_network.test-network.id
subnetwork = google_compute_subnetwork.test-subnetwork.id
}
metadata_startup_script = "apt-get update && apt-get install nginx -y"
service_account {
email = google_service_account.test-sa.email
scopes = ["cloud-platform"]
}
}
# GCP VPC Firewall 설정
총 3개의 VPC Firewall 룰을 설정한다.
Terraform 기준으로 작성하였지만 GCP 콘솔이든, gcloud 커맨드든 관계는 없다.
1. UDP LB로부터 VM에 헬스체크가 가능하도록 Google LB health check 대역으로부터의 트래픽 허용(실제 트래픽은 udp 51820 포트로 받지만, 헬스 체크는 tcp 로만 가능해서 lb 의 헬스체크 아이피 대역으로부터 tcp 트래픽을 모두 허용(테스트 환경에서는 tcp 22 포트로 헬스 체크))
2. VPN 접속용 UDP 51820 포트 허용(51820 포트는 wireguard 의 서비스 포트로 사용할 예정)
3. VPN 접속 후 VPN Client 가 테스트용 웹서버에 접근하기 위한 TCP 80 포트 허용
resource "google_compute_firewall" "allow-tcp-ingress-from-tcp-lb" {
name = "allow-tcp-ingress-from-tcp-lb"
project = var.gcp_project
network = google_compute_network.test-network.id
allow {
protocol = "tcp"
}
source_ranges = [
"35.191.0.0/16",
"130.211.0.0/22",
"209.85.152.0/22",
"209.85.204.0/22",
]
}
resource "google_compute_firewall" "allow-vpn-ingress-from-any" {
name = "allow-vpn-ingress-from-any"
project = var.gcp_project
network = google_compute_network.test-network.id
allow {
protocol = "udp"
ports = ["51820"]
}
target_tags = ["vpn"]
source_ranges = [
"0.0.0.0/0",
]
}
resource "google_compute_firewall" "allow-http-ingress-from-internal" {
name = "allow-http-ingress-from-internal"
project = var.gcp_project
network = google_compute_network.test-network.id
allow {
protocol = "tcp"
ports = ["80"]
}
target_tags = ["nginx"]
source_ranges = [
"10.0.0.0/8",
]
}
# UDP passthrough LB 생성
VPN VM 앞단에 붙일 UDP passthrough LB를 생성한다.
포인트는 UDP LB 이지만 헬스 체크는 TCP로만 가능하다는 점이다.
테스트 환경에서는 TCP 22 포트로 헬스 체크를 설정하였다.
resource "google_compute_instance_group" "vpn" {
name = "vpn"
project = var.gcp_project
zone = "asia-northeast3-a"
network = google_compute_network.test-network.id
instances = [google_compute_instance.vpn.id]
}
resource "google_compute_global_address" "vpn" {
name = "external-lb-ip-for-vpn"
}
resource "google_compute_region_health_check" "vpn" {
name = "vpn"
project = var.gcp_project
region = var.gcp_region
check_interval_sec = 5
healthy_threshold = 1
timeout_sec = 5
unhealthy_threshold = 2
log_config {
enable = true
}
tcp_health_check {
port = 22
}
}
resource "google_compute_region_backend_service" "vpn" {
name = "vpn"
project = var.gcp_project
region = var.gcp_region
affinity_cookie_ttl_sec = 0
connection_draining_timeout_sec = 300
load_balancing_scheme = "EXTERNAL"
locality_lb_policy = "MAGLEV"
port_name = "http"
protocol = "UDP"
timeout_sec = 30
health_checks = [google_compute_region_health_check.vpn.id]
backend {
balancing_mode = "CONNECTION"
group = google_compute_instance_group.vpn.id
}
log_config {
enable = true
sample_rate = 1
}
}
resource "google_compute_forwarding_rule" "vpn" {
name = "vpn-forwarding-rule"
project = var.gcp_project
region = var.gcp_region
ip_protocol = "UDP"
ip_version = "IPV4"
load_balancing_scheme = "EXTERNAL"
network_tier = "PREMIUM"
ip_address = google_compute_global_address.vpn.id
backend_service = google_compute_region_backend_service.vpn.id
ports = [
"51820",
]
}
# VPN VM 에 wireguard 세팅
## 커널 파라미터 세팅 및 wireguard 패키지 설치
vim /etc/sysctl.conf
net.ipv4.ip_forward=1
커널 파라미터 적용
sysctl -p
wireguard 패키지 설치
apt-get install -y wireguard
## 서버 키페어, 클라이언트 키페어 생성
server.key 는 VPN 서버의 키이고, client01.key 는 VPN 클라이언트의 키이다.
서버 키는 하나만 생성하면 되지만 VPN 연결 시 서버와 클라이언트가 공개키/개인키로 상호 인증을 하기 때문에 클라이언트별로 별도의 클라이언트 키 생성이 필요하다.
mkdir -p /etc/wireguard/keys
wg genkey | tee /etc/wireguard/keys/server.key | wg pubkey | tee /etc/wireguard/keys/server.key.pub
mkdir -p /etc/wireguard/clients
wg genkey | tee /etc/wireguard/clients/client01.key | wg pubkey | tee /etc/wireguard/clients/client01.key.pub
## 설정 파일 생성
테스트 환경의 아이피 대역 및 아이피 정보는 아래와 같다.
설정 파일 생성 시 참고하여 자신의 환경에 맞게 설정한다.
서버가 사용할 VPN 내부 아이피 - 10.100.0.1
클라이언트가 사용할 VPN 내부 아이피 - 10.100.0.2
GCP VPC 사설 아이피 대역 - 10.0.0.0/24
서버 GCP 사설 아이피 - 10.0.0.8
웹서버 GCP 사설 아이피 - 10.0.0.9
LB 퍼블릭 아이피 - 34.64.242.162
서버의 기본 네트워크 인터페이스를 확인한다.
Ubuntu 22.04 버전의 공식 GCE 이미지에서는 "ens4"이지만, 운영 체제 별로 차이가 있을 수 있다.
서버의 기본 인터페이스 확인
ip -o -4 route show to default | awk '{print $5}'
서버 설정 파일을 작성한다.
AllowedIPs 는 클라이언트가 사용할 VPN 내부 사설 아이피이다.
vim /etc/wireguard/wg0.conf
[Interface]
Address = 10.100.0.1/24
MTU = 1380
SaveConfig = true
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o ens4 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o ens4 -j MASQUERADE
ListenPort = 51820
PrivateKey = 서버_개인키
[Peer]
PublicKey = 클라이언트_공개키
AllowedIPs = 10.100.0.2/32
만약 VM 자체에 퍼블릭 IP를 부여하여 LB 없이 VPN 서비스를 하려는 경우 위의 설정파일이면 충분하다.
하지만 GCP의 UDP LB를 사용하는 경우 위의 설정 파일로는 통신이 불가하다.
이유는 GCP의 UDP LB는 DSR(Direct Server Return) 방식으로 작동하는데 UDP 패킷의 특성 상 LB 로부터의 패킷은 stateless 하기 때문에 서버에서 아웃바운드되는 패킷의 소스 주소를 lb 의 아이피로 재작성하는 iptables 룰이 필요하기 때문이다.
이 경우 아래와 같은 iptables 룰이 추가로 삽입되도록 서버 설정 파일을 구성한다.
iptables -t nat -A POSTROUTING -j RETURN -d 10.0.0.8 -p udp --dport 51820
iptables -t nat -A PREROUTING -j DNAT --to-destination 10.0.0.8 -d 34.64.242.162 -p udp --dport 51820
위의 룰이 적용된 서버 설정 파일은 아래와 같다.
vim /etc/wireguard/wg0.conf
[Interface]
Address = 10.100.0.1/24
MTU = 1380
SaveConfig = true
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o ens4 -j MASQUERADE; iptables -t nat -A POSTROUTING -j RETURN -d 10.0.0.8 -p udp --dport 51820; iptables -t nat -A PREROUTING -j DNAT --to-destination 10.0.0.8 -d 34.64.242.162 -p udp --dport 51820
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o ens4 -j MASQUERADE; iptables -t nat -D POSTROUTING -j RETURN -d 10.0.0.8 -p udp --dport 51820; iptables -t nat -D PREROUTING -j DNAT --to-destination 10.0.0.8 -d 34.64.242.162 -p udp --dport 51820
ListenPort = 51820
PrivateKey = 서버_개인키
[Peer]
PublicKey = 클라이언트_공개키
AllowedIPs = 10.100.0.2/32
아래와 같이 클라이언트 설정 파일을 작성한다.
AllowedIPs 는 VPN 을 통해 통신할 대역을 나열한다.
AllowedIPs 에 없는 대역은 VPN을 통하지 않고, 기존의 네트워크를 통해 통신한다.
DNS 옵션은 필요하면 추가할 수 있지만, 기존 네트워크 DNS 설정이 덮어씌워지기 때문에 유의한다.
vim /etc/wireguard/client01.conf
[Interface]
PrivateKey = 클라이언트_개인키
Address = 10.100.0.2/32
MTU = 1380
# DNS = 8.8.8.8 ## 옵셔널
[Peer]
PublicKey = 서버_공개키
AllowedIPs = 10.0.0.0/24, 10.100.0.0/24
Endpoint = 34.64.242.162:51820
## VPN 실행
VPN 실행/중지
wg-quick up wg0
wg-quick down wg0
상태 확인
wg
wg show
wg show wg0
부팅 시 자동실행 설정
systemctl enable wg-quick@wg0
서비스 상태 확인 및 재시작
systemctl status wg-quick@wg0
systemctl restart wg-quick@wg0
# 모바일 클라이언트를 위한 QR 코드 생성
모바일 클라이언트는 QR 코드를 통해 클라이언트 설정을 쉽게 할 수 있다.
생성된 QR 코드는 모바일 wireguard 앱에서 스캔하여 VPN 터널 등록한다.
apt-get install -y qrencode
qrencode -t ansiutf8 < /etc/wireguard/client01.conf
# 설정 파일 퍼미션 수정
필수 사항은 아니지만 VPN 서버의 중요 정보는 일반 사용자 계정에서 볼 수 없도록 퍼미션을 수정해준다.
chmod 600 /etc/wireguard/wg0.conf
chmod 600 /etc/wireguard/keys/server.key
chmod 600 /etc/wireguard/keys/server.key.pub
chmod 600 /etc/wireguard/client01.conf
chmod 600 /etc/wireguard/clients/client01.key
chmod 600 /etc/wireguard/clients/client01.key.pub
# 참고 URL
GCP UDP passthrough LB 이슈
https://cloud.google.com/load-balancing/docs/network/udp-with-network-load-balancing?hl=ko#use_nat_policy_in_the_backend_server
GCP passthrough LB 헬스 체크 아이피 정보
https://cloud.google.com/load-balancing/docs/health-check-concepts?hl=ko#ip-ranges
인스턴스에 IP 전달 사용 설정
https://cloud.google.com/vpc/docs/using-routes?hl=ko#canipforward