개요
- 같은 공인 IP에 PSK 기반 dialup 터널을 여러 개 두면 FortiGate가 어느 터널로 보낼지 구분할 수 없다. 첫 번째로 매칭되는 터널이 가져간다.
- 구분 수단으로 network-id가 있지만, 실질적으로 EMS 관리 환경을 전제한다.
- EMS 없이 가능한 방법: 인터페이스에 보조 공인 IP를 추가하고 터널별로 local-gw를 분리한다.
- 그룹 분기는 여전히 RADIUS + IP 풀로 한다. (Part 7)
이 글은 누구를 위한 글인가
같은 방화벽에서 직원용과 외부 파트너용 원격접속 터널을 분리하고 싶은 사람. 특히 EMS가 없는 환경.
읽고 나면 왜 터널을 하나 더 만드는 것만으로는 안 되는지, 그리고 실제로 무엇이 필요한지 알게 된다.
1. 하려던 것
직원용 터널(ra-ipsec)은 이미 동작하고 있었다. 여기에 외부 파트너용 터널을 추가하려 했다.
분리하려는 이유가 명확했다.
| PSK 분리 | 파트너사에 사내 프로파일의 PSK를 주지 않는다 |
| 정책 분리 | 파트너 트래픽을 별개 터널 인터페이스로 받아 정책을 완전히 분리 |
| 사고 격리 | 파트너 측 사고 시 해당 터널만 내린다 |
| 로그 분리 | 감사 시 구분이 명확 |
세 번째가 실무적으로 가장 중요했다. PSK가 유출됐을 때 사내 사용자 전원의 프로파일을 재배포하지 않고 파트너 터널만 갈아치울 수 있어야 한다.
2. 첫 시도와 실패
두 번째 Phase1을 만들었다. 같은 인터페이스, 같은 IP, 다른 PSK.
config vpn ipsec phase1-interface
edit "ra-ipsec-partner"
set type dynamic
set interface "port1"
set local-gw 203.0.113.10 ← 직원용과 동일
set ike-version 2
set peertype any
set net-device disable
set mode-cfg enable
set authmethod psk
set psksecret <다른PSK>
set eap enable
set authusrgrp "partner-grp"
next
end
파트너용 프로파일로 접속하니 실패했다. IKE 로그를 보면:
... peer identifier not verified
... negotiation failure
또는 상황에 따라 직원용 터널로 붙으려 시도하다 PSK 불일치로 실패했다.
3. 왜 이게 안 되는가
IKEv2의 dialup 시나리오를 생각하면 당연하다.
클라이언트가 IKE_SA_INIT을 보내는 시점에 FortiGate가 가진 정보는 출발지 IP와 목적지 IP뿐이다. 목적지 IP가 두 터널 모두 동일하면, 어느 터널의 설정으로 협상해야 할지 판단 근거가 없다.
PSK 검증은 IKE_AUTH 단계, 즉 터널을 이미 선택한 뒤에 일어난다. 그러니 "PSK가 다르니까 구분되겠지"는 순서가 거꾸로다.
클라이언트 → IKE_SA_INIT (목적지 203.0.113.10)
↓
FortiGate: 이 IP에 dialup 터널이 두 개 있다.
어느 쪽 설정으로 협상하지?
↓
첫 번째로 매칭되는 것을 선택
↓
IKE_AUTH에서 PSK 검증 → 불일치 → 실패
인증서 기반이라면 다르다. 클라이언트가 보내는 인증서의 식별자로 peerid를 매칭할 수 있다. PSK에는 그런 식별자가 없다.
4. network-id — 있지만 전제조건이 있다
FortiOS에 network-id라는 파라미터가 있다.
config vpn ipsec phase1-interface
edit "ra-ipsec-partner"
set network-overlay enable
set network-id 2
next
end
이건 IKE 협상 시 네트워크 ID를 교환해서 같은 IP상의 여러 터널을 구분한다. 원하던 것과 정확히 일치하는 기능이다.
문제는 클라이언트 쪽이다. 클라이언트가 자기 network-id를 보내야 하는데, 이 값을 프로파일에 넣는 방식이 실질적으로 EMS 관리 환경을 전제한다. 무료 VPN-only 클라이언트에 이걸 안정적으로 주입하는 방법을 찾지 못했다.
시도한 것과 결과:
| XML에 관련 태그 추측해서 추가 | 무시됨 (Part 6의 교훈: 오류 없이 무시된다) |
| 내보낸 프로파일에서 해당 필드 탐색 | 존재하지 않음 |
| GUI에서 설정 가능한지 확인 | 없음 |
그래서 이 경로를 포기했다. EMS가 있다면 이게 정답일 것이다.
5. 채택한 방법 — 보조 공인 IP
간단한 결론에 도달했다. 터널을 IP로 구분할 수 없으면, IP를 하나 더 쓴다.
5.1 인터페이스에 보조 IP 추가
config system interface
edit "port1"
set ip 203.0.113.10 255.255.255.0
# 왜: 파트너 터널 전용 게이트웨이 IP
set secondary-IP enable
config secondaryip
edit 1
set ip 203.0.113.20 255.255.255.0
set allowaccess ping
next
end
next
end
allowaccess에는 필요한 최소만 넣는다. 관리 프로토콜(HTTPS, SSH)을 파트너가 도달하는 IP에 열지 않는다.
5.2 실제로 붙었는지 확인
이 단계를 반드시 한다. 설정이 들어갔다고 IP가 살아 있는 건 아니다.
diagnose ip address list | grep -A2 203.0.113.20
출력에 해당 IP가 인덱스와 함께 나와야 한다. 안 나오면 secondary-IP enable이 빠졌거나 서브넷 설정이 충돌한 것이다.
외부에서도 확인한다.
# 외부 호스트에서
ping 203.0.113.20
5.3 파트너 터널의 local-gw를 분리
config vpn ipsec phase1-interface
edit "ra-ipsec-partner"
set type dynamic
set interface "port1"
# 왜: 이 한 줄이 터널 구분의 전부다
set local-gw 203.0.113.20
set ike-version 2
set peertype any
set net-device disable
set mode-cfg enable
set proposal aes256gcm-prfsha384 aes256-sha256
set dhgrp 20 14
set dpd on-idle
set authmethod psk
set psksecret <REDACTED>
set eap enable
set eap-identity send-request
set authusrgrp "partner-grp"
set assign-ip-from usrgrp
set fragmentation enable
set fragmentation-mtu 900
# 왜: Part 8의 결론. 파트너 터널도 UDP 기본.
set transport udp
set npu-offload disable
next
end
config vpn ipsec phase2-interface
edit "ra-ipsec-partner-p2"
set phase1name "ra-ipsec-partner"
set proposal aes256gcm aes256-sha256
set pfs enable
set dhgrp 20 14
set keepalive enable
next
end
Phase2를 잊지 않는다. (Part 5의 원인 2)
파트너 프로파일의 server는 새 IP를 가리킨다.
<server>203.0.113.20</server>
또는 별도 FQDN을 두는 게 낫다. partner-vpn.example.com. IP가 바뀔 때 프로파일 재배포를 피할 수 있다.
6. RADIUS 쪽 — 그룹 분기와 IP 풀
파트너 계정에 그룹 속성을 부여한다.
partner_a_01 Cleartext-Password := "<REDACTED>"
Fortinet-Group-Name = "partner-a",
Fallthrough = no
partner_b_01 Cleartext-Password := "<REDACTED>"
Fortinet-Group-Name = "partner-b",
Fallthrough = no
계정이 여러 개라면 스크립트로 일괄 처리한다. 수동 편집은 반드시 오타를 낸다.
authorize 파일 같은 중요한 파일을 일괄 편집할 때 쓰는 절차:
# 1. 원본을 디렉터리 밖으로 백업 (Part 2의 교훈: 설정 디렉터리 안에 백업 금지)
cp /etc/raddb/authorize /root/backup/authorize.$(date +%F-%H%M)
# 2. awk로 수정본을 별도 파일에 생성
awk '/^partner_a_/ { print; print " Fortinet-Group-Name = \"partner-a\","; next } { print }' \
/etc/raddb/authorize > /tmp/authorize.new
# 3. diff로 반드시 검토 — 이 단계를 건너뛰지 않는다
diff /etc/raddb/authorize /tmp/authorize.new
# 4. 검토 통과 후 교체
cp /tmp/authorize.new /etc/raddb/authorize
# 5. 문법 검증 후 리로드
radiusd -XC && systemctl reload radiusd
3번을 건너뛰면 안 된다. awk 한 줄이 예상과 다르게 동작해서 파일 절반을 날린 경험이 있다.
IP 풀은 Part 3의 설계대로 대역을 분리한다.
| 파트너사 A | 10.10.60.160 – 175 (/28) |
| 파트너사 B | 10.10.60.176 – 183 (/29) |
7. 정책 — /32로 조인다
Part 7에서 확인한 대로 set groups는 쓸 수 없다. IP 대역으로 한다.
config firewall address
edit "VPN-PartnerA"
set subnet 10.10.60.160 255.255.255.240
next
edit "SRV-PartnerA-App"
# 왜: 서버 단위 제한. 대역이 아니라 /32
set subnet 10.20.30.41 255.255.255.255
next
end
config firewall policy
edit 0
set name "PartnerA-App-Only"
set srcintf "ra-ipsec-partner"
set dstintf "internal"
set srcaddr "VPN-PartnerA"
set dstaddr "SRV-PartnerA-App"
set action accept
set schedule "always"
# 왜: ALL이 아니라 필요한 서비스만
set service "HTTPS"
set logtraffic all
next
end
config firewall policy
edit 0
set name "Partner-DenyAll-Explicit"
set srcintf "ra-ipsec-partner"
set dstintf "any"
set srcaddr "all"
set dstaddr "all"
set action deny
# 왜: 암묵적 거부에 의존하지 않는다. 로그가 남아야 시도가 보인다.
set logtraffic all
next
end
마지막 명시적 거부 정책이 중요하다. 이건 접근제어가 아니라 관측성 때문이다. 파트너사 장비가 예상 외의 곳을 스캔하면 그게 로그에 남는다.
8. 검증 절차
# 1. 보조 IP가 실제로 살아 있는지
diagnose ip address list | grep -A2 203.0.113.20
# 2. 두 터널이 서로 다른 IP를 잡고 있는지
get vpn ipsec phase1-interface ra-ipsec | grep local-gw
get vpn ipsec phase1-interface ra-ipsec-partner | grep local-gw
# 3. 파트너 접속 시 올바른 터널로 들어오는지
diagnose vpn ike gateway list | grep -E 'name:|local:|remote:|transport:'
# 4. 할당된 IP가 파트너 대역인지
diagnose vpn tunnel list | grep -A5 ra-ipsec-partner
# 5. 정책 매칭 확인
diagnose debug flow filter addr 10.10.60.163
diagnose debug flow trace start 20
# 6. 격리 확인 — 허용하지 않은 목적지가 실제로 막히는지
# 파트너 계정으로 접속한 뒤 다른 내부 서버로 접근 시도
# → Partner-DenyAll-Explicit 정책에 로그가 남아야 정상
6번을 반드시 한다. "허용이 되는가"만 확인하고 "차단이 되는가"를 확인하지 않으면 절반만 검증한 것이다.
9. 배경 노이즈에 대해
새 공인 IP를 열자마자 IKE 관련 로그가 늘었다. 처음엔 파트너 접속 실패로 착각했다.
확인해보니 인터넷 전역 스캐너의 정기 탐지였다. 연구 목적의 스캔 프로젝트들이 공개 IP 범위를 주기적으로 훑는다.
# 실패 로그의 출발지 IP 분포 확인
diagnose vpn ike log filter clear
# → 특정 대역에서 반복적으로 오는 패턴이면 스캐너
실제 사용자 실패와 배경 노이즈를 구분하는 방법:
| 출발지 IP | 알려진 스캐너 대역, 반복 | 사용자 회선 |
| 시각 분포 | 균일, 야간 포함 | 업무 시간대 집중 |
| 계정 | 없거나 무의미 | 실제 계정명 |
| 반복 | 정기적 (일/주 단위) | 실패 시 즉시 재시도 |
이걸 구분해두지 않으면 로그가 항상 시끄러워서 진짜 문제를 놓친다. 스캐너 대역을 별도로 필터링하거나, 최소한 "이건 노이즈"라고 문서에 적어둔다.
노이즈 자체가 위험한 건 아니지만, 이게 알려주는 게 있다. 공인 IP에 IKE를 열면 즉시 발견된다. 그러니 PSK 강도, 계정 잠금 정책, 로그 알림 임계값이 이 상태를 전제로 설정돼 있어야 한다.
10. 여기서 배운 것
- PSK 기반 dialup 터널은 IP로만 구분된다. PSK 검증은 터널 선택 후에 일어나므로 구분 수단이 아니다.
- 기능이 문서에 있다고 본인 환경에서 쓸 수 있다는 뜻은 아니다. network-id는 존재하지만 클라이언트 측 전제조건이 있었다.
- 우아한 방법이 막히면 자원을 하나 더 쓰는 단순한 방법을 검토한다. 공인 IP 하나 추가가 몇 시간이면 끝났고, network-id 경로 탐색은 며칠이 걸렸다.
- 차단이 실제로 동작하는지 확인한다. 허용 확인만 하면 절반만 검증한 것이다.
- 명시적 거부 정책에 로그를 켠다. 암묵적 거부는 조용하고, 조용한 것은 관측할 수 없다.
- 새 공인 IP를 열면 즉시 스캔된다. 이걸 사고로 오인하지 않도록 노이즈 기준선을 먼저 파악한다.
다음 편에서는
Part 11에서 기술이 아닌 부분을 다룬다. 실제로 150명을 어떻게 옮겼는가. 공지, 저항, 그리고 롤백 계획.
'네트워크' 카테고리의 다른 글
| 150명을 어떻게 옮겼는가 — 기술보다 어려웠던 부분 (Part 11) (0) | 2026.07.27 |
|---|---|
| VPN이 아니라 무선 드라이버였다 — 서버만 보다가 놓친 것 (Part 9) (1) | 2026.07.26 |
| IKE-over-TCP로 절반이 실패했다 — 설정으로는 고칠 수 없는 것이었다 (Part 8) (0) | 2026.07.26 |
| 다시 한다면 무엇을 바꿀까 — 그리고 아직 끝나지 않은 것들 (Part 12) (0) | 2026.07.25 |
| authusrgrp를 쓰면 방화벽 정책의 그룹 매칭이 죽는다 — 문서에 없던 상호작용 (Part 7) (0) | 2026.07.25 |