반응형
목차
목표: 사내 VPC(10.0.0.0/16) 에 있는 프라이빗 서브넷들과 외부 단말(노트북 등)을 OpenVPN 으로 안전하게 연결하되, 인터넷 트래픽은 로컬로 내보내는 스플릿 터널을 구성한다. 실제로 겪은 증상과 해결 과정을 그대로 정리했다.
핵심 요약
- VPN 풀 대역은 VPC CIDR과 겹치면 안 된다. (예: VPC=10.0.0.0/16이면 VPN 풀=10.8.0.0/24, 10.99.0.0/24 등)
- 양방향 라우팅이 성립해야 통신된다.
- 프라이빗 서브넷의 라우트 테이블에 VPN 풀 → OpenVPN ENI 경로 추가 (리턴 경로)
- OpenVPN 인스턴스의 Source/Destination Check 비활성화
- 0.0.0.0/0 으로 열면 되는데 10.8.0.0/24로 좁히면 안 되는 이유는 SNAT 때문이다.
- 해결: VPC로 향하는 트래픽에 한해 SNAT 예외를 넣고, 프라이빗 RTB에 리턴 라우트 추가.
- VPC 내부에서 traceroute가 * * *로 보이는 건 흔하다. 연결성 평가는 ping/Test-NetConnection/ncat 등으로 한다.
아키텍처와 용어
- VPC CIDR: 10.0.0.0/16
- 프라이빗 서브넷 예시: 10.0.11.0/24, 10.0.12.0/24, 10.0.21.0/24, 10.0.22.0/24
- VPN 풀(가상 IP): 기본 10.8.0.0/24 (권장: VPC와 비겹침 대역)
- OpenVPN 서버: 퍼블릭 서브넷 예: 10.0.1.28 (NIC: ens5), TUN 인터페이스: tun0 (10.8.0.1)
OpenVPN 설치 & 기본 스플릿 터널 설정
표준 자동 스크립트(angristan/openvpn-install)를 사용한 뒤 스플릿 터널을 위해 옵션을 조정한다.
1) 설치 스크립트 실행 (예시)
# 필수 패키지
yum update -y
yum install -y wget curl unzip awscli dnsmasq
# 환경변수로 무인 설치
export AUTO_INSTALL=y
export APPROVE_INSTALL=y
export APPROVE_IP=y
export IPV6_SUPPORT=n
export PORT=1194
export PROTOCOL=udp
export DNS=1
export COMPRESSION_ENABLED=n
export CUSTOMIZE_ENC=n
export CLIENT=client
export CLIENT_NAME=client
curl -s https://raw.githubusercontent.com/angristan/openvpn-install/master/openvpn-install.sh | bash
2) server.conf 스플릿 터널화
# 강제 전 트래픽 리다이렉트 제거
sed -i '/^push "redirect-gateway/d' /etc/openvpn/server.conf
# 내부망 라우트 푸시 (예시)
cat <<'EOF' >> /etc/openvpn/server.conf
push "route 10.0.11.0 255.255.255.0"
push "route 10.0.12.0 255.255.255.0"
push "route 10.0.21.0 255.255.255.0"
push "route 10.0.22.0 255.255.255.0"
EOF
# topology 권장
grep -q '^topology ' /etc/openvpn/server.conf || echo 'topology subnet' >> /etc/openvpn/server.conf
3) Split DNS (dnsmasq)
# /etc/dnsmasq.conf 예시
server=/corp.local/10.0.0.2
server=8.8.8.8
server=8.8.4.4
listen-address=127.0.0.1,10.8.0.1
log-queries
systemctl enable dnsmasq
systemctl restart dnsmasq
4) 클라이언트 프로파일 (client.ovpn)
# 서버에서 redirect-gateway를 제거했다면, 클라에서 확실히 분리하려면
route-nopull
route 10.0.11.0 255.255.255.0
route 10.0.12.0 255.255.255.0
route 10.0.21.0 255.255.255.0
route 10.0.22.0 255.255.255.0
AWS 쪽 필수 작업 (양방향 라우팅 성립)
- OpenVPN EC2의 Source/Dest Check 비활성화
- aws ec2 modify-instance-attribute \ --instance-id i-VPNSERVER \ --source-dest-check '{"Value": false}'
- 프라이빗 서브넷 라우트 테이블에 리턴 경로 추가
- 대상: 10.8.0.0/24 (또는 실제 VPN 풀 대역)
- 대상지: OpenVPN 인스턴스 ENI (또는 인스턴스 ID)
aws ec2 create-route \ --route-table-id rtb-PRIVATE-A \ --destination-cidr-block 10.8.0.0/24 \ --network-interface-id eni-VPN - 보안그룹/네트워크 ACL
- 대상 인스턴스 SG 인바운드에 소스=VPN 풀 대역(10.8.0.0/24) 으로 필요한 포트(22/80/443/DB 등)와 테스트용 ICMP 허용
- NACL은 응답용 Ephemeral(1024–65535) 허용 확인
증상: 0.0.0.0/0로 열면 되는데 10.8.0.0/24로는 안 됨
원인
- 설치 스크립트가 기본으로 SNAT(MASQUERADE) 를 잡는다.
- iptables-save 결과 예: -A POSTROUTING -s 10.8.0.0/24 -o ens5 -j MASQUERADE
- 그래서 내부 대상 입장에선 소스 IP가 10.8.x.x가 아니라 OpenVPN 서버 ENI IP(예: 10.0.1.28) 로 보인다.
- SG를 10.8.0.0/24로 좁히면 매치가 안 돼 차단.
해결 A: NAT 유지(빠른 응급처치)
- 대상 SG에서 소스를 OpenVPN 서버의 SG(권장) 또는 서버 ENI IP(10.0.1.28/32) 로 허용한다.
- 단, 모든 클라이언트가 내부에서 서버 IP로만 보이는 단점이 있다.
해결 B: VPC로 갈 때만 SNAT 끄기(권장)
- SNAT 예외 규칙(POSTROUTING 최상단) 추가
# VPC(10.0.0.0/16) 목적지는 NAT하지 않음
iptables -t nat -I POSTROUTING 1 -s 10.8.0.0/24 -d 10.0.0.0/16 -j ACCEPT
# 기존 MASQUERADE는 유지(인터넷 등 외부 목적지용)
# -A POSTROUTING -s 10.8.0.0/24 -o ens5 -j MASQUERADE
2. 프라이빗 RTB에 리턴 라우트 추가(필수)
aws ec2 create-route \
--route-table-id rtb-PRIVATE-A \
--destination-cidr-block 10.8.0.0/24 \
--network-interface-id eni-VPN
3. 커널 포워딩 & rp_filter 완화
echo 1 > /proc/sys/net/ipv4/ip_forward
sysctl -w net.ipv4.conf.all.rp_filter=2
sysctl -w net.ipv4.conf.default.rp_filter=2
4. 동작 확인
# 카운터로 확인
iptables -t nat -Z POSTROUTING
# (클라→내부로 트래픽 발생 후)
iptables -t nat -L POSTROUTING -v -n
# tcpdump로 소스 IP가 유지되는지 확인
tcpdump -ni tun0 host 10.0.11.209
tcpdump -ni ens5 host 10.0.11.209
기대 결과: VPC 목적지 트래픽은 MASQUERADE 카운터가 증가하지 않음, ens5에서 본 패킷의 src=10.8.x.x 유지.
영구화
- iptables-services로 저장하거나, systemd 유닛으로 iptables-restore 자동 적용
흔한 오해/함정
- VPN 풀을 VPC와 겹치게(예: 10.0.0.0/24) 쓰면?
- 내부 인스턴스가 응답을 “로컬”로 처리해 리턴 경로 붕괴. 정상 라우팅이 어렵다.
- 편법: 서버에서 SNAT(모든 클라가 서버 IP로 보임). 가급적 비겹침 대역을 쓰자.
- VPC에서 traceroute가 전부 *: 중간 홉에서 ICMP Time Exceeded를 주지 않는 경우가 많다. 이건 연결 실패의 증거가 아니다.
- Windows ping 옵션: ping -n 3 10.0.11.209 (-c 아님)
진단 체크리스트
- 서버 라우팅 확인
ip route get 10.0.11.209
# 예: via 10.0.1.1 dev ens5 src 10.0.1.28
2. 서버→대상 연결성
ping -c3 10.0.11.209 || true # ICMP가 막혀 있을 수 있음
ncat -vz 10.0.11.209 22 || true # TCP 포트로 검증(SSH 예시)
3. 클라→대상 연결성(Windows)
ping -n 3 10.0.11.209
Test-NetConnection 10.0.11.209 -Port 22
4. 프라이빗 RTB 리턴 경로: 10.8.0.0/24 → OpenVPN ENI 존재?
5. OpenVPN EC2: Source/Dest Check 해제?
6. SG/NACL:
- 대상 SG에 소스 10.8.0.0/24 허용(또는 NAT 유지 시 서버 SG/IP)
- NACL로 Ephemeral 허용
7. SNAT 여부
iptables -t nat -S POSTROUTING
iptables -t nat -L POSTROUTING -v -n
conntrack -L | grep -E '10\.8\.|10\.0\.'
대안: VPN 풀을 아예 다른 대역으로(예: 10.99.0.0/24)
- 장점: 의미가 명확하고 충돌 리스크 최소화.
- 변경 예시
# server.conf
sed -i 's/^server .*/server 10.99.0.0 255.255.255.0/' /etc/openvpn/server.conf
sed -i 's/push "dhcp-option DNS .*/push "dhcp-option DNS 10.99.0.1"/' /etc/openvpn/server.conf
# dnsmasq
sed -i 's/^listen-address=.*/listen-address=127.0.0.1,10.99.0.1/' /etc/dnsmasq.conf
systemctl restart dnsmasq
# 프라이빗 RTB: 10.99.0.0/24 → OpenVPN ENI 경로 추가
Terraform/CLI 스니펫 모음
# Terraform (리턴 경로)
resource "aws_route" "vpn_return" {
route_table_id = var.private_rtb_id
destination_cidr_block = var.vpn_pool_cidr # e.g. "10.8.0.0/24"
network_interface_id = var.vpn_eni_id
}
# AWS CLI (리턴 경로)
aws ec2 create-route \
--route-table-id rtb-XXXX \
--destination-cidr-block 10.8.0.0/24 \
--network-interface-id eni-YYYY
# 소스/목적지 체크 해제
aws ec2 modify-instance-attribute \
--instance-id i-ZZZZ \
--source-dest-check '{"Value": false}'
마무리
- 스플릿 터널 OpenVPN을 VPC에 붙일 때 가장 중요한 건 대역 충돌 방지와 양방향 라우팅(리턴 경로) 이다.
- 보안그룹을 세밀하게 제한하려면 SNAT 예외 + 리턴 라우트 조합으로 클라이언트 원본 IP(10.8.x.x) 를 내부에 보존하자.
- 위 체크리스트만 지키면, traceroute의 별표에 흔들리지 않고 빠르게 원인을 찾을 수 있다.
반응형
'CLOUD > AWS' 카테고리의 다른 글
| AWS CloudFront 대체 도메인(Alternate Domain Names)와 Route 53 A 레코드 정리 (0) | 2025.08.20 |
|---|---|
| AWS Route 53 도메인에 SSL 인증서 발급 및 여러 리전 관리 방법 (0) | 2025.08.14 |
| AWS EKS에서 externalTrafficPolicy 설정에 따른 NLB 헬스체크 동작 차이: Ingress Controller vs 일반 서비스 (0) | 2025.08.07 |
| AWS EKS NGINX Ingress Controller - hostNetwork에 따른 NLB 헬스체크 정상/비정상 분석 (0) | 2025.08.06 |
| api gateway 에서 root_method(/) 와 proxy_method({proxy+}) (1) | 2025.08.02 |
댓글