Hyper-V 홈랩으로 ADFS/WAP DMZ 격리 구축기 (OPNsense) — NLB VIP가 L3 방화벽을 못 넘는 이유까지
값은 전부 예시(lab.local / 10.10.10.x / 192.168.219.x)로 치환함. 실제 환경과 무관함. 이미지 플레이스홀더는
형태로 삽입함.
0. 왜 DMZ를 “진짜로” 나누는가
기존 홈랩은 DC·ADFS·SQL·CA·WAP를 전부 한 평면망(192.168.219.0/24)에 올려둔 상태였음. WAP를 도메인 미가입으로만 두고 “DMZ”라 불렀지만, 사실 같은 서브넷이라 WAP가 마음만 먹으면 DC·SQL에 다 접근됨. 명목상 DMZ이지 격리가 아님.
실무 DMZ의 본질은 “WAP가 뚫려도 내부(AD)까지 못 넘어오게 폭발 반경을 봉쇄”하는 것임. 그러려면 네트워크를 물리적으로(가상 스위치 + 방화벽) 나눠야 함. 이번 글은 그걸 OPNsense로 구현하고 검증한 기록임.
핵심 개념 하나 — DMZ 격리에는 두 계층이 있음.
| 계층 | 역할 | 이번 글 |
|---|---|---|
| ① 네트워크 방화벽 | 서버 간 트래픽 격리 (L3/L4) | OPNsense (이번 구축) |
| ② 접근제어(PAM) | 사람의 관리 접속 통제 | 개념만 (상용 PAM 영역) |
여기선 ①(방화벽/격리)만 다룸. ②(PAM)는 실무에서 관리자가 서버에 접속할 때 거치는 관문인데, 이건 별개 계층이라 이번 범위 밖임.
1. 목표 아키텍처
(외부 시뮬레이션 생략)
│
┌────────── DMZ (10.10.10.0/24) ──────────┐
│ WAP01 10.10.10.10 (workgroup) │ ← 도메인 미가입
│ gw → 10.10.10.1 │
└───────────┬──────────────────────────────┘
│ OPNsense LAN(hn1) = 10.10.10.1
┌────┴────┐
│ OPNsense │ 방화벽 VM (NIC 2개)
└────┬────┘
│ OPNsense WAN(hn0) = 192.168.219.250
┌────────── 내부망 (192.168.219.0/24) ─────┐
│ ADFS NLB VIP .240 / ADFS01 .241 / .242 │
│ DC01 .191 / SQL / CA .232 │ ← 신뢰 구역
└──────────────────────────────────────────┘
허용 규칙(내부 방화벽):
DMZ → ADFS(.240) : 443 ADFS 프록시 중계 (본질)
DMZ → ADFS(.240) : 49443 certauth (클라이언트 인증서 인증용)
그 외 DMZ → 내부 전부 차단
설계 선택 — A안(기존 External을 내부망으로 재사용): 이미 완성한 ADFS 팜·SQL AG·NLB가 전부 기존 스위치에 물려 있어서, 이걸 옮기면(B안) 어렵게 세운 인프라가 깨질 side-effect가 큼. 그래서 WAP만 새 DMZ 세그먼트로 분리하고 나머지는 그대로 둠. 격리 학습 목적엔 이걸로 충분함.
2. OPNsense 방화벽 구축
2-1. 왜 pfSense/OPNsense인가 (Windows RRAS가 아니라)
Windows Defender 방화벽은 host-based임. 자기 호스트로 드나드는 트래픽만 거르고, 호스트를 통과(라우팅)하는 전송 트래픽은 못 막음. DMZ↔내부 사이를 지나가는 트래픽 필터링이 DMZ 격리의 본질인데, 이건 통과 트래픽이라 일반 Windows 방화벽 정책으론 안 됨.
RRAS 정적 패킷 필터로 억지로 가능은 하지만 구식이고 실무 표준이 아님. 그래서 전용 방화벽 OS(OPNsense/pfSense) 로 감. 실무 경계 방화벽도 Fortinet·Palo Alto 같은 전용 어플라이언스(non-Windows)라, 오히려 이게 실무와 일치함.
pfSense CE는 Netgate 스토어 경유라 계정·유료 혼동이 있어서, 계정 없이 ISO 직다운 가능한 OPNsense로 선회함. 규칙 개념은 pfSense와 사실상 동일.

2-2. VM 생성 (Hyper-V)
- 가상 스위치 2개: 기존 스위치(내부망/WAN측) + 신규
DMZ-Switch(Internal) - OPNsense VM: Gen1, RAM 2GB, Disk 16GB, NIC 2개 (WAN→기존, LAN→DMZ-Switch)
- 두 vNIC 모두 MAC 주소 스푸핑 ON + 각 MAC 메모(콘솔 인터페이스 할당 대조용)
New-VMSwitch -Name "DMZ-Switch" -SwitchType Internal
# VM 생성 후 NIC 2개 연결 + MAC 스푸핑
Set-VMNetworkAdapter -VMName "OPNsense-FW" -Name "WAN" -MacAddressSpoofing On
Set-VMNetworkAdapter -VMName "OPNsense-FW" -Name "LAN" -MacAddressSpoofing On
2-3. 설치 & 인터페이스 할당
설치 후 콘솔에서 인터페이스 할당 — hn0/hn1 순서가 애매하므로 MAC 끝자리로 대조:
hn0(…74:19) → WAN (내부망측)hn1(…74:1a) → LAN (DMZ측)
IP 설정:
- WAN =
192.168.219.250/24, gateway192.168.219.1 - LAN =
10.10.10.1/24, gateway 없음, DHCP 서버 끔
인터페이스·IP까지 잡히면 WAN IP로 웹 GUI에 접속해 로그인:

2-4. WAN 사설망 차단 해제 (필수 관문)
Interfaces → WAN에서 Block private networks / Block bogon networks 해제. WAN이 실제론 사설망(192.168.219.x)이라, 안 끄면 RFC1918을 전부 드롭해서 통신이 0이 됨.

2-5. 방화벽 규칙 — 최소 권한
Firewall → Rules → LAN(=DMZ측)에서 허용 규칙만 명시하고 나머지는 기본 deny:
| Action | Proto | Source | Destination | Port | Desc |
|---|---|---|---|---|---|
| Pass | TCP | LAN net | 192.168.219.240 /32 | 443 | ADFS |
| Pass | TCP | LAN net | 192.168.219.240 /32 | 49443 | certauth |
그리고 기본 “Default allow LAN to any” 규칙을 비활성화. first-match 방식이라 이 규칙이 맨 위에 살아있으면 모든 트래픽이 “다 허용”으로 걸려서 격리가 안 됨.

왜 Destination이 /24가 아니라 /32인가 —
/24를 넣으면 내부망 전체(DC·SQL·CA 포함)의 443이 열려서 격리가 무너짐. WAP가 실제 필요한 건 ADFS 하나뿐이므로 딱 그 호스트만(/32) 여는 게 최소 권한임. Source(LAN net)는 넓게, Destination은 좁게 — 이게 격리 규칙의 정석.
왜 49443까지 열었나 — ADFS SSL 인증서 SAN에
certauth.sts가 포함돼 있음. 이건 TLS 클라이언트 인증서 인증 전용 엔드포인트로 443과 포트를 분리해 운영됨. 지금은 폼 기반 SSO만 검증하므로 당장 필수는 아니지만, 인증서에 이미 경로가 준비돼 있어 미리 열어둠. 반면 pki CRL(80)은 최소 권한 원칙에 따라 생략 — CA 체인은 WAP에 직접 반입하고 CRL 오프라인을 허용할 것이므로 pki 접근 없이도 검증이 통과됨. “인증서가 준비된 경로는 열고, 실제로 안 쓰는 경로는 닫는다”는 선택.
ADFS(.240) 443 Pass 규칙 등록:

certauth(.240) 49443 Pass 규칙 등록:

3. WAP01 DMZ 이전 + 검증 준비
WAP01(WS2022, workgroup)을 처음부터 DMZ-Switch에 생성. DMZ엔 DHCP가 없으므로 고정 IP를 수동 설정:
# WAP01
New-NetIPAddress -InterfaceAlias "Ethernet" -IPAddress 10.10.10.10 -PrefixLength 24 -DefaultGateway 10.10.10.1

내부 노드가 DMZ(10.10.10.x)로 돌아가는 길을 알도록 ADFS 노드에 return-route 추가:
# ADFS01
route add 10.10.10.0 mask 255.255.255.0 192.168.219.250 -p

실제로는 OPNsense가 Automatic Outbound NAT를 걸어서 WAP 소스를 WAN IP(.250)로 변환하므로 return-route 없이도 됐지만, 명시적으로 걸어두면 경로가 명확해짐.
4. 트러블슈팅 — 여기가 이 글의 본론
격리 규칙까지 다 맞췄는데도 WAP01 → ADFS:443이 안 됨. 구간을 하나씩 끊어가며 원인을 추적한 기록임.
트러블 #1 — WAN IP가 DHCP로 되돌아감
증상: 콘솔에서 WAN을 .250 고정으로 잡았는데, Interfaces → Overview엔 .166(DHCP값)으로 표시. OPNsense ping에서 bind: Can't assign requested address (OPNsense가 .250을 자기 IP로 인식 못 함).
원인: DHCP→고정 전환 직후 인터페이스 재초기화 과정에서 이전 DHCP lease가 다시 잡힘.
조치: Interfaces → [WAN]에서 static .250 재저장 + Apply, 반영 안 되면 재부팅. 재부팅 후 Overview에서 .250 고정 확정.
트러블 #2 — NLB VIP(.240)가 “Host is down”
증상: OPNsense에서 .241(ADFS01 실제 IP)은 ping loss 0%인데, .240(NLB VIP)만 sendto: Host is down (ARP 단계 실패).
원인: NLB가 Multicast 모드라 VIP를 멀티캐스트 MAC(03-bf-...)으로 announce함. L3 장비(OPNsense/라우터)는 “유니캐스트 IP에 멀티캐스트 MAC” 매핑을 ARP로 못 배워서 VIP에 못 닿음. 같은 L2의 Windows 노드끼리는 되지만 라우터 너머에서 막히는 전형적 케이스.
조치: NLB VIP의 멀티캐스트 MAC을 계산 후 OPNsense에 정적 ARP 등록.
# NLB VIP 192.168.219.240 → 16진수 C0.A8.DB.F0
# Multicast NLB MAC = 03-bf-<VIP hex> = 03-bf-c0-a8-db-f0
# (nlb display의 ClusterNetworkAddress로 검증)
# OPNsense 쉘
arp -s 192.168.219.240 03:bf:c0:a8:db:f0
결과 — ICMP 관통 성공:
ping -c 3 192.168.219.240
64 bytes from 192.168.219.240: ... time=0.383 ms
64 bytes from 192.168.219.240: ... (DUP!) ← NLB 양 노드 응답(정상)
0.0% packet loss
(DUP!)는 에러가 아니라 멀티캐스트 NLB에서 두 노드가 다 응답해서 나오는 정상 현상. 오히려 NLB가 도는 증거.
트러블 #3 — ICMP는 뚫렸는데 TCP 443은 여전히 안 됨 (핵심)
정적 ARP로 ping은 됐는데, WAP01 → .240:443은 계속 실패. 여기서 tcpdump로 전 구간을 캡처함.
① WAP → OPNsense 도착 확인 (hn1):
tcpdump -ni hn1 port 443
IP 10.10.10.10.50420 > 192.168.219.240.443: Flags [S] ← SYN 도착
IP 10.10.10.10.50420 > 192.168.219.240.443: Flags [S] ← 재전송(응답 없음)
→ WAP의 SYN은 OPNsense에 잘 도착함.
② OPNsense → WAN 송출 확인 (hn0):
tcpdump -ni hn0 host 192.168.219.240 and port 443
IP 192.168.219.250.57444 > 192.168.219.240.443: Flags [S] ← NAT돼서 송출됨
IP 192.168.219.250.57444 > 192.168.219.240.443: Flags [S] ← 재전송(응답 없음)
→ OPNsense는 NAT(.250)해서 WAN으로 정상 송출함. 방화벽·NAT·라우팅 전부 정상. SYN-ACK만 안 돌아옴.
③ Real IP(.241)로 비교 (hn0):
IP 192.168.219.250.8594 > 192.168.219.241.443: Flags [S] ← SYN
IP 192.168.219.241.443 > 192.168.219.250.8594: Flags [S.E] ← SYN-ACK (ADFS01 응답!)
IP 192.168.219.250.8594 > 192.168.219.241.443: Flags [.] ← ACK (연결 성립)
→ TcpTestSucceeded : True
결론: .241(Real IP)은 3-way 핸드셰이크 정상, .240(NLB VIP)은 SYN 도달/SYN-ACK 미반환. 경로는 완벽하고, 순수하게 NLB VIP가 NAT된 세션의 응답(SYN-ACK)을 반환하지 못하는 문제로 확정.
원인 규명: Multicast NLB VIP는 L3 방화벽 + NAT 너머로 들어온 TCP 세션의 응답 경로에서 비대칭이 발생함. ICMP(무연결)는 정적 ARP로 관통되지만, TCP(연결 상태)는 NLB의 노드 배정·응답 반환 과정에서 SYN-ACK이 유실됨. Real IP는 노드가 직접 받고 직접 응답하므로 정상.
조치(이번 회차): 격리 검증 자체는 NLB와 무관하므로, 방화벽 규칙 Destination을 .241(Real IP)로 두고 검증 완료. NLB VIP의 L3+NAT 응답 비대칭은 별도 재검증 과제로 분리(affinity None / Unicast 모드 실험은 ADFS 팜 영향을 봐야 하므로 신중히).
실무 관점: DMZ의 WAP가 내부 ADFS 팜에 붙을 때 NLB VIP로 가는 게 정석이 맞음. 다만 가상화(Hyper-V) + L3 NAT 환경에선 Multicast NLB의 응답 비대칭이 걸림돌이 될 수 있어, 실무에서도 이 구간은 로드밸런서 앞단 설계나 노드 직접 지정 등으로 우회하는 경우가 있음. 이번엔 원인을 tcpdump로 정확히 규명한 것에 의미를 둠.
5. 격리 검증 — 오늘의 결과
WAP01에서 “열려야 할 것 / 막혀야 할 것”을 한 번에 확인:
# 열려야 하는 것
Test-NetConnection 192.168.219.241 -Port 443 # ADFS → True
# 막혀야 하는 것 (격리 증명)
Test-NetConnection 192.168.219.191 -Port 389 # LDAP → False
Test-NetConnection 192.168.219.241 -Port 3389 # RDP → False
Test-NetConnection 192.168.219.191 -Port 445 # SMB → False
Test-NetConnection 192.168.219.232 -Port 80 # pki → False
결과:
RemoteAddress RemotePort TcpTestSucceeded
192.168.219.241 443 True ← 허용한 것만 열림
192.168.219.191 389 False ← LDAP 차단
192.168.219.241 3389 False ← RDP 차단
192.168.219.191 445 False ← SMB 차단
192.168.219.232 80 False ← pki 차단
“허용한 443만 열리고 나머지 내부 접근은 전부 차단” — DMZ 격리가 의도대로 동작함을 확인함.

6. 정리 & 다음
오늘 완성
- OPNsense 방화벽 VM으로 DMZ 세그먼트(10.10.10.0/24) 물리적 분리
- WAP(DMZ) → ADFS 443만 허용, 내부 나머지 전부 차단
- 전 구간 tcpdump 검증 (도착 / NAT 송출 / 핸드셰이크)
재검증 과제 (다음 회차)
- NLB VIP(.240) L3+NAT 응답 비대칭 — affinity None / Unicast 모드 실험
- 정적 ARP 영구화 — 재부팅 시
arp -s가 사라지므로 부팅 스크립트 자동화 - Part C — WAP 역할 설치 +
Install-WebApplicationProxy(격리된 상태 위에서 ADFS 프록시 구성)
이번 회차 배운 것
- DMZ 격리는 host-based 방화벽으론 불가 — 전송 트래픽 필터가 필요, 전용 방화벽 OS의 존재 이유
- 최소 권한은 /32로 — 필요한 호스트 하나만 열기
- NLB VIP + L3 + NAT는 ICMP와 TCP가 다르게 동작 — ping 뚫렸다고 통신 되는 게 아님. tcpdump로 SYN/SYN-ACK을 봐야 진짜 원인이 보임
- 문제 구간은 끊어서 좁힌다 — WAP→OPN→WAN→ADFS를 단계별 캡처로 격리하니 원인이 명확해짐
본 글의 IP/도메인/MAC은 예시값으로 치환함. 실제 운영 환경과 무관함.
Comments