최근 회사에서 서버 이전 작업을 하면서 헷갈렸던 개념들을 정리해본다.
FW vs WAF — 왜 방화벽이 있는데 또 필요한가
이름이 둘 다 “방화벽”이라 헷갈리지만 검사하는 계층이 다르다.
| FW (방화벽) | WAF (Web Application Firewall) | |
|---|---|---|
| 보는 계층 | L3/L4 (IP, 포트) | L7 (HTTP 내용) |
| 판단 기준 | 이 IP/포트를 통과시킬까? | 이 HTTP 요청 내용이 공격인가? |
| 막는 것 | 허용 안 된 포트·IP, 비정상 연결 | SQL Injection, XSS, 경로 조작 등 |
| 비유 | 건물 출입 게이트 (출입증 확인) | 소지품 검사대 (가방 안 내용물 검사) |
예를 들어 FW는 “443 포트로 들어오는 HTTPS 허용”까지만 본다. 그 정상 443 트래픽 안에 ?id=1' OR '1'='1 같은 SQL 인젝션 문자열이 들어있어도 FW는 못 잡는다. 포트·IP는 정상이니까. 이걸 HTTP 요청 본문/파라미터를 까서 잡는 것이 WAF다.
FW = 누가 들어오나(주소/포트), WAF = 무엇을 들고 들어오나(HTTP 페이로드). 그래서 외부 대면 웹 서비스는 보통 둘 다 둔다.
WAF를 한 곳에 모을까, 서비스마다 둘까
WAF를 거치는 것 자체는 정석이다. 다만 위치는 설계 선택이다.
- 중앙 집중형: WAF를 경계 한 곳에 두고 모든 웹 트래픽을 모아서 검사. 관리·정책 일관성·라이선스 면에서 유리하지만, 한 곳이 병목·단일 장애점이 될 수 있고 내부에서 발생하는 east-west 공격은 못 본다.
- 분산/앞단 배치: 각 서비스 앞단마다 WAF/LB를 둠. 부하 분산·세분화에 유리하지만 관리가 복잡하고 비용이 늘어난다.
내부·관리용 웹까지 외부 대면 WAF를 강제로 거치게 만들면 라우팅이 꼬일 수 있으니, “WAF를 거친다”는 정상이되 “하나의 중앙 WAF로 전부 몰아넣는다”는 트레이드오프임을 의식해야 한다.
SNAT vs DNAT — 무엇을 바꾸느냐
NAT는 패킷의 IP를 바꿔치기하는 기술이다. 출발지를 바꾸느냐 목적지를 바꾸느냐가 다르다.
SNAT (Source NAT) — 출발지 변환, 나갈 때(내부→외부)
내부 서버의 사설 IP(10.x.x.x)는 인터넷에서 못 쓴다. 밖으로 나갈 때 출발지를 공인 IP로 바꿔치기한다. 회사에서 편지 보낼 때 보내는 사람 주소를 회사 대표주소로 적는 것.
1
내부 서버(10.x.x.x) → [SNAT] → 출발지를 공인IP로 변경 → Internet
DNAT (Destination NAT) — 목적지 변환, 들어올 때(외부→내부)
외부 사용자는 공인 IP 하나로 접속하는데, 실제 목적지인 내부 서버의 사설 IP로 목적지를 바꿔 전달한다. 회사 대표주소로 온 편지를 실제 담당 부서로 다시 배달하는 것.
1
Internet(공인IP로 접속) → [DNAT] → 목적지를 내부 WEB(10.x.x.x)으로 변경 → WEB 서버
들어올 땐 DNAT, 나갈 땐 SNAT이 한 쌍으로 동작한다. 인입을 “1:1 DNAT (to WEB)”, 외부로 나가는 걸 “SNAT”으로 표기한 구성도를 자주 본다.
ingress / egress 라는 용어
- ingress (인그레스) = 들어오는 트래픽 (외부→내부). 어원: in-gress(입장).
- egress (이그레스) = 나가는 트래픽 (내부→외부). 어원: e(x)-gress(퇴장).
Security Group의 inbound/outbound rule이 각각 ingress/egress다. “중앙집중형 egress”는 내부에서 밖으로 나가는 트래픽을 한 곳으로 모아 내보낸다는 뜻이다.
VPC, 서브넷, Security Group — 다른 계층의 얘기
VPC ⊃ 서브넷 (포함 관계)
구성도에서 10.x.32.0/20 같은 표기를 보고 “서브넷 하나”로 오해하기 쉬운데, 이건 VPC의 주소 범위(CIDR) 인 경우가 많다.
- VPC = 큰 사설 IP 범위(CIDR)를 가진 가상 네트워크
- 서브넷 = 그 VPC 범위를 잘게 쪼갠 조각
/20은 주소가 4,096개 들어가는 큰 범위라, 보통 한 덩어리로 안 쓰고 안에서 용도별로 더 나눈다.
1
2
3
4
VPC: 10.x.32.0/20 ← VPC 전체 범위 (주소 4096개)
├─ 10.x.32.0/24 ← WEB 서브넷
├─ 10.x.33.0/24 ← Proxy 서브넷
└─ 10.x.34.0/24 ← FTP 서브넷
VPC = 한 층(/20), 서브넷 = 그 층을 나눈 방들(/24). VPC 1개에 서브넷 N개.
Security Group은 같은 서브넷이어도 적용된다
“같은 서브넷이면 SG 없이 자유롭게 통신하고, 다른 서브넷이면 SG로 관리한다”는 오해가 흔한데 틀렸다. 라우팅과 필터링은 별개 계층이고 둘 다 항상 동작한다.
| 서브넷 / 라우팅 | Security Group | |
|---|---|---|
| 역할 | 패킷이 어디로 갈지 길을 정함 (도달성) | 도달한 패킷을 통과시킬지 결정 (허용/차단) |
| 계층 | L3 라우팅 | L3/L4 상태기반 필터 (인스턴스 NIC 단) |
| 비유 | 그 집까지 가는 길이 있나 | 그 집 문 앞에서 들여보내 줄까 |
SG는 인스턴스(NIC) 바로 앞에 붙는 필터다. 같은 서브넷이든 다른 서브넷이든, 그 인스턴스로 들어가려면 무조건 SG를 통과한다.
1
2
같은 서브넷: A ──(라우팅 불필요, L2 직통)──> [B의 SG] ─> B
다른 서브넷: A ──> [VR 라우팅] ──> [B의 SG] ─> B
차이는 “B 앞의 SG”가 아니라 그 앞단(라우팅)에 있다. 같은 서브넷이면 라우터를 안 거치고 바로 도달할 뿐, SG 규칙에서 막으면 같은 서브넷이라도 못 간다. (서브넷 단위 필터인 NACL은 또 한 겹 위에 있다.)
VR, 피어링 게이트웨이
- VR (가상 라우터): 서브넷·네트워크 사이에서 패킷의 길을 정하는 라우터. 각 서브넷의 기본 게이트웨이이자, 외부행/타 VPC행 경로가 모이는 라우팅 중심.
- 피어링 게이트웨이: 서로 격리된 두 VPC를 인터넷 없이 사설로 직접 잇는 관문. 두 건물 사이의 전용 구름다리.
VR = 한 네트워크 안/밖으로의 길잡이(라우팅), 피어링GW = 다른 VPC와 이어주는 다리(연결).
hub-and-spoke 구조
hub-and-spoke는 자전거 바퀴에서 온 말이다. 가운데 축(hub)에 여러 바퀴살(spoke)이 모두 꽂히고, 살끼리는 직접 안 닿고 축을 통해서만 이어진다. 네트워크에선 중앙 허브 VPC 하나에 여러 서비스 VPC(스포크)를 각각 피어링으로 붙이고, 스포크끼리의 통신도 허브를 경유시키는 구조다.
1
2
3
4
5
[경계/관리 허브 VPC]
(인터넷GW·FW·WAF·egress SNAT)
╱ │ ╲
VPC A VPC B VPC C ... (서비스 spoke VPC들)
(스포크끼리도 직접 X, 허브를 거쳐 통신)
허브에는 모든 스포크가 공유하는 공통 기능을 모아둔다 — 인터넷 출입구(게이트웨이·FIP), 경계 FW·WAF, 중앙 egress SNAT, 공유 관리도구 등. 스포크는 자기 서비스 자원만 갖고, 외부로 나가거나 다른 스포크와 통신할 땐 허브를 거친다.
- 왜 이렇게 하나: 공통 보안·출입 기능을 허브 한 곳에만 두면 되니 관리·정책 일관성·비용이 유리하다. 스포크마다 방화벽·인터넷 출구를 따로 두지 않아도 된다.
- 대안(full-mesh)과 비교: 모든 VPC를 서로 직접 연결하면 VPC가 N개일 때 연결이
N(N-1)/2개로 폭증하고 정책도 흩어진다. 허브를 두면 연결이 N개(각 스포크↔허브)로 줄고 통제점이 하나로 모인다.
앞서 나온 “중앙집중형 egress”·”중앙 WAF”가 다 이 hub-and-spoke의 허브에 기능을 몰아둔 결과다. 대신 허브가 병목·단일 장애점이 되므로 FW·게이트웨이를 이중화한다.
중앙집중형 egress — 각 VPC가 직접 안 나간다
외부로 나가는 길은 당연히 있다. 다만 각 VPC가 각자 출구를 갖는 게 아니라 경계 허브로 모아서 한 곳으로 내보낸다. 허브의 FW가 보통 둘로 나뉜다.
| Internal FW | DMZ FW | |
|---|---|---|
| 담당 트래픽 | 내부끼리(east-west), Legacy 연동, 관리 | 외부 대면(north-south) |
| 연결 방향 | 사설 경로 / 피어링 허브 | 인터넷 게이트웨이 / WAF |
외부로 나가는 흐름:
1
2
3
4
5
6
서비스 VPC의 서버
↓ (피어링 게이트웨이)
경계 허브의 FW (검사/정책)
↓ SNAT (출발지를 공인IP로 변환)
↓
인터넷 게이트웨이 → Internet
들어올 때(DNAT → WEB)와 방향만 반대고 같은 관문을 공유한다. 이렇게 모으는 이유는 단일 통제점(한 곳에서 검사·로깅·차단), 공인 IP 절약(SNAT 한 곳에서 공유), 정책 일관성이다. 대신 그 한 곳이 병목·단일 장애점이 되므로 FW를 이중화한다.
VIP와 FIP, 그리고 2단 NAT
먼저 헷갈리기 쉬운 두 가상 IP를 구분하자. VIP와 FIP는 다른 레이어다.
- VIP (Virtual IP): LB가 백엔드 풀 앞에 내거는 LB 자신의 서비스 주소(부하분산용). 사설일 수도, 공인일 수도 있다.
- FIP (Floating IP): 인터넷을 마주보는 엣지(인터넷 게이트웨이 / 가상 라우터) 에 붙는 대외 공인 NAT 주소. 공인 IP 풀에서 꺼내 특정 내부 자원에 붙였다 뗐다 할 수 있어 “floating”이다(서버가 죽으면 떼서 다른 자원에 다시 붙인다). FIP는 LB의 IP가 아니다 — 엣지에 앉아
1:1 DNAT으로 내부와 연결될 뿐이다.
그래서 공인 노출 방식이 두 갈래다.
- LB의 VIP 자체를 공인으로 얹는다. 그 공인이 사내 백본에 내부 경로가 있는 “대내 공인” 이면, 사내에선 사설처럼 경계를 안 넘고 통신한다.
- LB는 사설 VIP만 갖고, 엣지의 FIP가 공인 노출을 대신한다. 사설 스포크 VPC엔 자체 인터넷 게이트웨이가 없어(공인 노출은 허브 엣지에서만 가능) 이 구조가 사실상 강제된다.
2번 구조에서 외부 인입은 곧바로 LB로 꽂히는 게 아니라 여러 단을 거친다.
1
2
3
4
5
6
7
8
인터넷 유저 ──▶ FIP(대외 공인, 엣지)
│ 1:1 DNAT (목적지만 내부로, src 보존)
▼
FW ─▶ WAF (L7 검사)
▼
LB (사설 VIP) ── SSL 종단
▼
nginx (listen 443 평문)
SSL 종단은 이 예에선 LB가 맡는다(TERMINATED_SSL 리스너). 그래서 nginx는 평문 443으로 받는다. (WAF는 L7 웹공격 검사를 담당하며, WAF가 어느 지점에서 복호화해 검사하는지는 배치에 따라 다르다.)
이렇게 외부에서 들어올 때 NAT가 두 단계로 겹칠 수 있다. (예시 IP: 클라이언트 1.2.3.4, FIP 203.0.113.10, LB 사설 VIP 10.x.32.100, nginx 10.x.32.20)
1
2
3
4
5
6
7
8
① 클라이언트가 공인 FIP로 접속
src 1.2.3.4 → dst 203.0.113.10
② 엣지가 1:1 DNAT (Floating IP 매핑)
dst 203.0.113.10 → 내부(FW/WAF 경유), src 1.2.3.4 유지
③ LB가 SSL 종단 + 백엔드 선택 (새 커넥션)
src → 10.x.32.100 (LB 사설 VIP), dst → 10.x.32.20 (nginx)
③에서 L7 LB는 클라이언트와의 TLS를 종단하고 백엔드로 새 커넥션을 맺으므로, nginx 로그엔 실제 클라이언트가 아니라 LB 사설 VIP가 찍힌다. (L4 LB라면 출발지 보존도 가능하지만, 리턴 경로 보장을 위해 SNAT를 걸면 마찬가지로 LB IP가 찍힌다. SNAT를 안 하면 nginx가 진짜 클라이언트로 직접 응답해 LB를 우회하고, 비대칭 경로(asymmetric routing)가 되어 LB 연결 추적이 깨진다.)
진짜 클라이언트 IP를 복원하려면:
- L7(HTTP) LB:
X-Forwarded-For헤더 사용1 2
set_real_ip_from 10.x.32.0/24; # LB가 있는 대역 real_ip_header X-Forwarded-For;
- L4(TCP) LB: Proxy Protocol 사용
1 2
listen 80 proxy_protocol; real_ip_header proxy_protocol;
헷갈렸던 현상: 같은 공인 IP 대역인데 한쪽은 사설 IP, 한쪽은 NAT IP가 찍힌다
먼저: 경계(perimeter)와 ifconfig.me
경계(perimeter) 는 사내망(내부)과 그 바깥(인터넷)을 가르는 문이다. 보통 방화벽이 여기 있고, 안에서 밖으로 나가는 트래픽만 이 문을 통과한다. 나가는 순간 출발지가 회사 대표 공인 IP로 바뀐다(egress SNAT).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌── 사내망 (내부) ──────────────────────────────
│
│ 내 PC (사설 10.x)
│ │
│ ├─ 목적지에 내부 경로 있음 (사설 / 대내 공인)
│ │ └─> 사내 백본 -> 서버
│ │ = 경계 안 넘음 · SNAT 없음 (사설 IP 보존)
│ │
│ └─ 목적지에 내부 경로 없음 (대외 공인 FIP)
│ │
└──────────────────┼───────────────────────────
▼
┌───────────────────────────────┐
│ 경계 = 사내 경계 FW │ ← 이 "문"에서 출발지가 바뀜
│ egress SNAT : 사설 -> 공인 │
└───────────────┬───────────────┘
│ src 10.x -> 211.1.2.3 (= ifconfig.me)
▼
🌐 인터넷 -> 대외 공인 FIP (클라우드 정문)
경계를 넘느냐는 목적지로 가는 내부 경로가 있느냐로 갈린다. 내부 경로가 있으면(사설·대내 공인) 백본으로 직행해 경계를 안 넘고, 없으면(대외 공인 FIP) 밖으로 나가야 해서 경계를 넘는다. 비유하면 회사 정문 — 사내 사람끼리는 정문을 안 거치고 오가지만, 밖으로 나갈 땐 정문을 통과하며 “회사 대표 명의(공인 IP)”로 바뀐다.
ifconfig.me 는 접속한 클라이언트의 공인 IP를 그대로 되돌려주는 웹 서비스다. curl ifconfig.me 하면 내 요청이 인터넷에 도달했을 때의 출발지 IP, 즉 경계에서 egress SNAT된 그 회사 공인 IP가 찍힌다. (같은 역할: curl ipinfo.io/ip, curl ifconfig.co) 그래서 “nginx 로그의 그 IP = ifconfig.me 결과”라는 건 곧 “그 트래픽이 경계를 넘어 SNAT됐다”는 신호다.
내부 경로 있음/없음은 누가 판단하나 — 라우팅 테이블
판단 주체는 사람이 아니라 경로상의 라우터가 가진 라우팅 테이블이다. 패킷이 라우터에 도착할 때마다 목적지 IP를 테이블과 대조하는 longest-prefix match(가장 구체적인 경로 우선) 로 자동 결정된다.
1
2
3
4
5
6
7
목적지 = 198.51.100.10 (대내 공인) 일 때 라우팅 테이블:
198.51.100.0/24 → 사내 백본 인터페이스 ← 구체적 내부 경로 있음! 이쪽으로
0.0.0.0/0 → 경계 FW (default)
목적지 = 203.0.113.10 (대외 공인 FIP) 일 때:
(203.0.113.0/24 로 가는 구체적 경로 없음)
0.0.0.0/0 → 경계 FW (default) ← 매칭되는 게 default뿐 → 경계로 나감
- 구체적 내부 경로 있음 → 그 백본 인터페이스로 직행 → 경계 안 넘음 → SNAT 없음
- 없음 → 매칭되는 게
0.0.0.0/0(default route)뿐 → default가 가리키는 경계 FW로 감 → 경계 넘음 → egress SNAT
그럼 그 라우팅 테이블은 누가 채우나? 네트워크 운영자(이 케이스에선 클라우드 제공사) 다. 정적 경로(static route)를 직접 넣거나, 동적 라우팅(BGP·OSPF 등)으로 “이 대역은 내부에 있다”고 광고(advertise)한다. 그래서 대내 공인은 자기 공인 대역을 내부 백본에도 경로로 심어둔 것이고, 대외 공인은 그런 내부 경로 없이 인터넷 대면 엣지에만 존재하는 것 — 같은 회사 소유 공인이어도 이 라우팅 설계 차이 하나로 대내/대외가 갈린다.
정리: “내부 경로 있음”은 라우터의 라우팅 테이블(longest-prefix match)이 매 홉 자동 판단하고, 그 테이블에 내부 경로를 심을지는 네트워크 운영자가 정한다.
같은 회사 소유 공인 IP 대역인데, 같은 내부망 PC에서 어떤 공인 주소 A로 접속하면 nginx access log에 내 PC 사설 IP가 그대로 찍히고, 다른 공인 주소 B로 접속하면 사설 IP도 LB IP도 아닌, 그 PC에서
curl ifconfig.me했을 때 보이는 egress NAT IP가 찍힌다. 둘 다 공인인데 왜 다르지?
핵심은 둘 다 공인이지만 성격이 다르다는 것이다.
- A = 대내 공인(사내 백본에 내부 경로가 있는 공인). 경계를 안 넘고 사내에선 사설처럼 통신 → SNAT가 없어 nginx에 PC 사설 IP가 그대로 보존된다. (LB에 대내 공인 VIP를 직접 얹은 경우)
- B = 대외 공인 FIP(내부 경로가 없는 공인). 사내 백본으로 못 가고 경계(perimeter)를 넘어 인터넷 쪽으로 나가야 한다 → 나가는 순간 내부망 출구에서 egress SNAT가 걸려 출발지가 공인 NAT IP(
ifconfig.meIP)로 바뀐다.
즉 SNAT를 가르는 건 “공인이냐 사설이냐”가 아니라 “대내냐 대외냐(내부 경로 유무)” 다. 같은 공인 IP 대역이어도 내부 경로가 있으면 사설처럼, 없으면 인터넷 대면처럼 취급된다.
아래는 B(대외 공인 FIP) 경로에서 NAT IP가 찍히는 과정이다. (예시: 내부 PC 192.168.10.5, 그 출구 NAT 211.1.2.3(=ifconfig.me), 대외 공인 FIP 203.0.113.10)
1
2
3
4
5
6
7
8
9
10
11
12
① PC가 공인 FIP로 접속
src 192.168.10.5 → dst 203.0.113.10
② 내부망 출구에서 SNAT ← curl ifconfig.me 와 똑같은 그 NAT
src 192.168.10.5 → 211.1.2.3
인터넷으로: src 211.1.2.3, dst 203.0.113.10
③ 게이트웨이 DNAT (Floating IP)
dst 203.0.113.10 → 10.x.32.100 (LB), src 211.1.2.3 유지
④ LB → nginx (소스 IP 보존 모드)
nginx access log src = 211.1.2.3 ← ifconfig.me 와 같은 IP!
두 가지가 합쳐진 결과다. (1) 공인 FIP로 접속해 내부망 출구 SNAT를 거쳤기 때문에 PC 사설 IP가 아니라 공인 NAT IP, (2) 이 LB는 소스 IP를 보존(SNAT 안 함)하는 모드라 LB IP가 아니라 그 공인 IP가 그대로 도달.
같은 PC·같은 nginx라도 무엇으로 부르냐가 어느 문으로 들어가냐를 결정한다. 내부 경로가 있는 주소(사설 또는 대내 공인)로 부르면 경계를 안 넘는 내부 경로, 대외 공인 FIP로 부르면 경계를 넘는 인터넷 인입(DMZ) 경로.