본문 바로가기
CLOUD/AWS

AWS VPC에서 OpenVPN 스플릿 터널 구성 & 트러블슈팅

by Rainbound-IT 2025. 8. 14.
반응형

목차

     

    목표: 사내 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 쪽 필수 작업 (양방향 라우팅 성립)

    1. OpenVPN EC2의 Source/Dest Check 비활성화
    2. aws ec2 modify-instance-attribute \ --instance-id i-VPNSERVER \ --source-dest-check '{"Value": false}'
    3. 프라이빗 서브넷 라우트 테이블리턴 경로 추가
      • 대상: 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
      
    4. 보안그룹/네트워크 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 끄기(권장)

    1. 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 아님)

    진단 체크리스트

    1. 서버 라우팅 확인
    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의 별표에 흔들리지 않고 빠르게 원인을 찾을 수 있다.
    반응형

    댓글