❓VPN을 도입하는 이유
현재 클라우드 아키텍처에서 주요 데이터를 처리하는 RDS와 API 서버 컨테이너가 기동 중인 EC2 인스턴스는 모두 Private Subnet에 위치해 있어, 외부로부터의 직접적인 접근이 원천 차단되어 있습니다.
하지만 내부 트래픽을 외부로 라우팅하기 위해 groble-monitor-instance를 NAT 인스턴스로 사용하고 있으며, 비용 절감을 위해 해당 인스턴스가 Bastion Host의 역할까지 겸하고 있습니다.
즉, 외부 개발자가 Private Subnet 내 EC2 인스턴스에 접속하기 위해서는 groble-monitor-instance의 22번 포트를 통해 SSH 접속을 해야 합니다.
이러한 구조는 주요 인스턴스를 Private Subnet에 배치해 외부 공격으로부터 보호하고 있음에도 불구하고, Public Subnet에 위치한 Bastion Host가 22번 포트로 외부에 노출되어 있다는 점에서 보안상 잠재적인 취약점을 내포하고 있습니다.
이에 따라 저는 groble-monitor-instance를 보다 안전하게 보호하기 위한 추가적인 보안 정책의 필요성을 인식하게 되었습니다.

🔐 Wireguard VPN을 선택한 이유
제가 고려한 선택지는 두 가지였습니다. 하나는 접속 가능한 IP 대역을 고정된 작업 공간으로 좁히는 방법이고, 다른 하나는 VPN 같은 인증 기반 솔루션을 도입하는 방법이었죠. 그런데 개발자들이 장소를 자주 옮기는 상황이라 첫 번째 방법은 현실적이지 않다고 판단했습니다. 따라서 groble-monitoring-instance에 VPN을 깔아서 인증된 개발자만 접근하도록 만드는 쪽으로 방향을 잡았습니다.
가장 오래되고 널리 사용되는 OpenVPN, 엔터프라이즈 환경에서 자주 쓰이는 IPsec/IKEv2, 그리고 개인 프라이버시 보호용으로 활용되는 상용 VPN 서비스과 같이 여러 가지 VPN 솔루션 선택지가 있었습니다. 하지만 저는 최신 오픈소스 VPN 프로토콜인 WireGuard를 선택했습니다. VPN을 적용하면 일반적인 SSH 직접 접속보다는 다소 느려질 수밖에 없습니다. 그러나 개발자들이 원활하게 응답을 주고받는 것이 중요했기 때문에, 성능과 보안을 모두 만족시키는 경량 VPN 솔루션이 필요했습니다. 이 점에서 WireGuard는 가장 적합했습니다.
WireGuard는 다른 VPN에 비해 코드와 설정 구조가 매우 간단하여 오버헤드가 적고, UDP 기반 통신으로 낮은 레이턴시를 제공합니다.
또한 Curve25519(키 교환), ChaCha20(대칭 암호화), Poly1305(인증) 등 검증된 최신 암호 알고리즘만을 사용하기 때문에, 보안 측면에서도 높은 신뢰성을 확보할 수 있습니다. 결과적으로, 저는 가볍고 빠르며 안전한 WireGuard VPN을 선택하게 되었습니다.
⚙️ Wiregurad VPN의 동작원리
VPN은 클라이언트와 서버 간에 암호화된 터널을 생성하여, 네트워크 트래픽을 안전하게 전송할 수 있도록 합니다. 이 터널을 통해 주고받는 모든 데이터는 암호화된 형태로 이동하기 때문에, 중간에서 트래픽이 가로채이더라도 내용을 해독할 수 없습니다. WireGuard 역시 이러한 암호화된 터널링 방식을 기반으로 동작합니다.
클라이언트(개발자 PC)와 서버(EC2) 사이의 통신 흐름을 단계별로 살펴보면 다음과 같습니다.
- 개발자 PC에서 SSH 연결 시도
SSH 패킷이 생성되고, WireGuard 클라이언트가 해당 패킷을 암호화합니다. - 암호화된 패킷 전송
암호화된 SSH 패킷이 인터넷을 통해 서버의 WireGuard 포트(기본값: 51820/UDP)로 전송됩니다. - 서버 수신 및 복호화
EC2 인스턴스의 eth0 인터페이스가 암호화된 패킷을 수신하고, WireGuard 커널 모듈이 이를 가로채 복호화합니다. - 인증 및 복호화 처리
WireGuard는 해당 패킷이 인증된 클라이언트로부터 왔는지 검증한 뒤, ChaCha20 알고리즘으로 복호화하고 Poly1305로 무결성을 확인합니다. - 내부 트래픽 전달
복호화된 SSH 패킷은 wg0 인터페이스를 통해 로컬 네트워크(예: localhost:22) 로 전달됩니다. - SSH 데몬 처리
결국 SSH 데몬이 정상적인 요청으로 인식하고 연결을 허용합니다.
즉, 개발자의 SSH 요청은 VPN 터널을 통해 암호화된 상태로 전송 → 커널 단에서 복호화 → 내부 네트워크로 전달되는 구조입니다.
🪜Wiregurad VPN의 동작 계층
WireGuard는 OSI 7 계층 중 네트워크 계층(Layer 3)에서 작동합니다. 그렇기 때문에 애플리케이션 종류와 관계없이 모든 트래픽을 보호할 수 있다는 장점이 있습니다.
실제 패킷 처리 과정을 OSI 계층별로 단순화하면 다음과 같습니다:
- L1/L2: 물리적인 수신 → 이더넷 프레임 처리
- L3 초입: IP 패킷 확인 → 51820 포트로 온 패킷임을 확인
- WireGuard 커널 모듈: 패킷 가로채기 → 인증/무결성 검증
- 복호화: ChaCha20으로 복호화 → 새로운 IP 패킷으로 재조립
- L3 재진입: 복호화된 패킷을 일반 네트워크 스택으로 전달
- L4~L7: SSH 등 상위 프로토콜 정상 처리
이 모든 과정은 사용자 공간(user space)이 아닌 Linux 커널 공간(kernel space)에서 수행됩니다. 즉, 암호화와 복호화가 네트워크 스택 내부에서 이루어지므로 속도 손실이 적고 오버헤드가 최소화됩니다.
🏗️ Wireguard VPN 구축 과정
<Wireguard 설치>
- EC2에 SSH 접속하여 Wireguard를 설치합니다.
# Ubuntu/Debian
sudo apt install wireguard
# 설치 확인
wg --version
<서버 키 페어 생성>
- Wireguard는 공개키 암호화 방식을 사용합니다. 따라서, 서버가 자신을 식별하고 클라이언트와 안전하게 통신하기 위해서는 고유한 키 페어(개인키 + 공개키)가 필요합니다.
- 개인키(Private Key): 서버만 보관하며 절대 외부에 공개하지 않음
- 공개키(Public Key): 클라이언트들이 서버를 인증할 때 사용
# Wireguard 설정 디렉토리로 이동
cd /etc/wireguard
# 서버 개인키 생성
wg genkey | tee server_private.key | wg pubkey > server_public.key
# 개인키 파일 권한 설정 (보안 중요!)
chmod 600 server_private.key
# 생성된 키 확인
cat server_private.key
cat server_public.key
- chmod 600은 파일 권한을 소유자(owner)에게만 읽기(read)와 쓰기(write)를 허용합니다.
<서버 설정 파일 작성>
- Wireguard 서버가 어떻게 동작할지 정의하는 설정 파일을 만듭니다.
- VPN 네트워크의 IP 대역 설정
- 어떤 포트에서 연결을 받을지 지정
- IP 포워딩 규칙 설정 (VPN을 통해 인터넷 또는 내부 네트워크 접근 허용)
vim /etc/wireguard/wg0.conf
[Interface]
# EC2의 개인키
PrivateKey = {위 단계에서 생성한 개인키}
Address = 10.6.0.1/24
ListenPort = 51820
SaveConfig = true
# IP 포워딩 규칙 (VPN 트래픽이 EC2를 통해 라우팅되도록)
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
- 10.6.0.1: Wireguard 서버가 VPN 내부에서 사용할 가상 IP 주소
- /24: 이 VPN 네트워크의 IP 대역 (10.6.0.0 ~ 10.6.0.255)
- Wireguard가 만드는 가상의 네트워크 공간
- 서버: 10.6.0.1
- 클라이언트 1:10.6.0.2
- 클라이언트 2:10.6.0.3
- ... 최대 254개 클라이언트까지 할당 가능
- PostUp / PostDown 이란
- PostUp: Wireguard 인터페이스(wg0)가 시작될 때 실행할 명령
- PostDown: Wireguard 인터페이스가 중지될 때 실행할 명령
- 각 iptables 규칙 설명
- iptables -A FORWARD -i wg0 -j ACCEPT
- wg0 인터페이스(VPN)에서 들어오는 패킷의 포워딩을 허용
- VPN 클라이언트가 EC2를 통해 다른 네트워크(인터넷 또는 내부 네트워크)로 패킷을 보낼 수 있게 함
- iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
- VPN에서 나가는 트래픽의 출발지 IP를 EC2의 IP로 변환 (NAT)
- VPN 클라이언트(10.6.0.2)가 인터넷에 접속할 때, 패킷의 출발지를 EC2의 공인 IP로 바꿔줌. 그래야 응답 패킷이 다시 EC2로 돌아올 수 있음
- iptables -A FORWARD -i wg0 -j ACCEPT
<IP 포워딩 활성화>
- Linux 커널 레벨에서 IP 포워딩 기능을 활성화해 줍니다.
- EC2가 VPN 클라이언트의 패킷을 다른 네트워크로 전달(라우팅)할 수 있음
- iptables 규칙만으로는 부족하고, 커널 설정도 함께 변경해야 함
# 현재 IP 포워딩 상태 확인 (0이면 비활성화, 1이면 활성화)
cat /proc/sys/net/ipv4/ip_forward
# 일시적으로 활성화 (재부팅 시 초기화됨)
sysctl -w net.ipv4.ip_forward=1
# 영구적으로 활성화 (재부팅 후에도 유지)
echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.conf
# 설정 적용 확인
sysctl -p
<클라이언트 키 페어 생성>
- 각 클라이언트마다 고유한 키 페어가 필요로 합니다. 이 키로 해당 클라이언트를 식별하고 인증할 수 있습니다.
- 서버(EC2)에서 클라이언트 키 생성
- 클라이언트마다 별도의 키 생성
- 나중에 특정 클라이언트 접속을 차단하려면 해당 키만 서버에서 제거하면 됨 (소규모 환경에서 적합)
- 개인키를 네트워크로 전달해야 함 (보안상 조금 더 신경 써야 함)
# 작업 디렉토리 유지
cd /etc/wireguard
# 클라이언트1 키 페어 생성
wg genkey | tee client1_private.key | wg pubkey > client1_public.key
# 생성된 키 확인
cat client1_private.key
cat client1_public.key
<서버에 클라이언트 추가>
- 서버가 클라이언트를 인식하고 VPN 연결을 허용하도록 설정합니다.
- 서버 설정 파일에 클라이언트의 공개키와 VPN IP를 등록
vim /etc/wireguard/wg0.conf
[Peer]
# 클라이언트의 공개키
PublicKey = {위에서 생성한 클라이언트의 공개키}
AllowedIPs = 10.6.0.2/32
<Wireguard 서비스 시작>
- 지금까지 설정한 내용을 활성화하여 Wireguard VPN 서버를 실제로 구동합니다.
- wg show 명령에서 인터페이스 정보와 Peer(클라이언트) 정보가 보여야 합니다.
- systemctl status에서 active (running) 상태 확인
# Wireguard 인터페이스 시작
wg-quick up wg0
# 상태 확인
wg show
# 부팅 시 자동 시작 설정
systemctl enable wg-quick@wg0
# 서비스 상태 확인
systemctl status wg-quick@wg0
<클라이언트 설정 파일 생성>
- 클라이언트(실제 접속할 PC/노트북)에서 사용할 설정 파일을 만듭니다. 이 파일에는 클라이언트의 개인키와 서버 정보가 포함됩니다.
sudo vim /etc/wireguard/client1.conf
[Interface]
# 클라이언트의 개인키
PrivateKey = {클라이언트의 개인키}
Address = 10.6.0.2/32
DNS = 8.8.8.8
[Peer]
# EC2의 공개키
PublicKey = {EC2의 공개키}
Endpoint = {서버의 공인IP}:51820
AllowedIPs = 10.0.0.0/16
PersistentKeepalive = 25
- PrivateKey: 클라이언트의 개인키
- Address: 클라이언트의 VPN IP
- Endpoint: 서버의 공인 IP와 포트
- AllowedIPs = 0.0.0.0/0: 모든 트래픽을 VPN으로 (split tunnel 원하면 수정 가능)
- PersistentKeepalive = 25: NAT 유지를 위해 25초마다 keepalive 패킷 전송
<클라이언트에서 Wireguard 연결>
- 클라이언트(실제 접속할 PC/노트북)에서 Wireguard VPN에 실제로 연결하여 설정이 제대로 되었는지 테스트합니다.
brew install wireguard-tools
# VPN 연결 시작
sudo wg-quick up /etc/wireguard/client1.conf
# 연결 상태 확인
sudo wg show
# VPN IP 확인
ifconfig utun6
- VPN 연결 해제 방법
sudo wg-quick down /etc/wireguard/client1.conf
<EC2 인스턴스 보안 강화 및 접속 확인>
- 보안 그룹(Security Group) 설정
- SSH (22): 10.0.6.0/24 ← VPN 네트워크에서만 접근 가능
- UDP (51820): 0.0.0.0/0 ← VPN 연결은 허용
맥북 공인 IP: 211.215.222.128
→ VPN 연결 (UDP 51820)
→ VPN IP 획득: 10.6.0.2
→ Security Group: SSH는 10.6.0.0/24만 허용
→ 10.6.0.2는 10.6.0.0/24에 포함 ✓
→ SSH 접속 성공! ✓
🔒 결론: WireGuard VPN으로 강화된 Zero Trust 접근 제어
WireGuard VPN을 구축한 이후, 기존의 외부에서 모든 접근을 허용한 Bastion Host 기반 접근 방식에서 벗어나 Zero Trust 접근 모델을 도입할 수 있었습니다.
이제는 VPN을 통해 네트워크 레벨에서 먼저 인증을 통과한 사용자만 Bastion Host에 접근할 수 있도록 설계되었습니다. 이를 통해 접근 제어를 훨씬 더 엄격하게 관리할 수 있었으며, WireGuard 클라이언트 인증서를 보유한 개발자만 VPN에 접속할 수 있도록 제한함으로써 세밀하고 확실한 접근 통제가 가능해졌습니다. 이제 팀원별로 공개키와 개인키 쌍을 생성해 Bastion Host에 대한 접근 권한을 부여할 수 있게 되었으며, 팀원이 탈퇴할 경우 EC2 인스턴스에서 해당 팀원의 공개키를 제거하여 접근 권한을 손쉽게 제어할 수 있게 되었습니다.
또한 공개키 기반 인증 구조 덕분에 권한이 없는 사용자는 애초에 VPN 연결 자체가 불가능해졌고, SSH 포트가 외부 인터넷에 직접 노출되지 않음으로써 브루트포스 공격이나 취약점 스캐닝의 위협으로부터 완전히 벗어날 수 있었습니다. 결과적으로, WireGuard VPN을 도입함으로써
- Bastion Host에 대한 접근을 최소한의 신뢰 영역으로 한정하고,
- 외부 노출 포트를 제거하여 공격 표면을 줄이며,
- 개발자 인증 체계를 단순하면서도 안전하게 유지할 수 있었습니다.
이로써 단순한 네트워크 분리 수준을 넘어, Zero Trust 보안 접근 제어를 구현할 수 있었습니다.
'Infra' 카테고리의 다른 글
| [Infra] 실무에서 사용해본 유용한 Docker Swarm 기능들 (6) | 2025.06.26 |
|---|---|
| [Infra] 고가용성 Redis HA 클러스터 구축 (Redis Sentinel) (0) | 2025.05.01 |
| [CI/CD] 배포에 관한 생각 (0) | 2025.04.05 |
| [Infra] 모니터링 서버 구축 (feat. Prometheus, Grafana) (3) | 2024.12.21 |
| [Infra] 데이터베이스 다중화 (1) | 2024.12.20 |