결과 먼저
약 150명이 IPsec VPN으로 전환됐다. 추가 라이선스 비용은 0원이었다. 2단계 인증은 유지됐다.
그리고 하나의 문제는 아직 해결되지 않았다. TCP transport 경로의 실패는 회피 중이고 근본 원인은 벤더 대응 대기 상태다.
이 편은 자랑도 반성문도 아니다. 순서를 바꿨어야 했던 것과, 남은 부채의 목록이다.
1. 다시 한다면 순서를 바꿀 것
1순위 — 클라이언트 환경 조사를 먼저 한다
이게 가장 큰 후회다.
서버 구축부터 시작했다. 그게 재미있는 부분이니까. 그런데 실제 문제의 상당 부분은 클라이언트에서 나왔다. 6년 전 무선 드라이버, 전력 절감 옵션, 인증서 미설치, 중복 가상 어댑터. (Part 5, 9)
전부 사전 조사로 발견 가능했다. 그리고 사전에 발견하면 일괄 처리가 가능한데, 사후에 발견하면 개별 대응이 된다. 150명 규모에서 이 차이는 크다.
[ ] OS 버전 분포
[ ] 무선 어댑터 모델 및 드라이버 버전 분포
[ ] 드라이버 2년 이상 구형 장비 목록 → 사전 업데이트
[ ] 어댑터 전력 절감 옵션 표준값 정의 (GPO 강제 가능하면 그렇게)
[ ] 기존 VPN 클라이언트 잔여물 정리 절차
[ ] 사내 CA 인증서 배포 상태
원격접속 마이그레이션에서 사용자가 100명을 넘으면, 클라이언트 환경 조사가 서버 구축보다 우선순위가 높다.
2순위 — UDP를 기본으로 세우고 안정화한다
우리는 TCP를 기본으로 시작했다. 기존 SSL VPN이 TCP 443이었으니 같은 조건을 재현하는 게 안전해 보였다.
이 판단이 Part 8의 원인이었다. 그리고 더 나쁜 건, 비교 기준선이 없었다는 점이다.
UDP 경로가 확실히 동작하는 상태를 먼저 만들어뒀다면, TCP에서 문제가 생겼을 때 "UDP는 되는데 TCP만 안 된다"를 즉시 확인할 수 있었다. 실제로는 이 사실을 알아내기까지 며칠이 걸렸고, 그 사이 인증 계층을 헛되게 뒤졌다.
새 시스템은 가장 단순한 경로부터 안정화한다. 목표 구성으로 바로 가면, 문제가 생겼을 때 판단 근거가 없다.
3순위 — 변경 이력을 처음부터 기록한다
Part 5에서 원인이 다섯 개였는데, 하나를 고칠 때마다 증상이 바뀌어서 방금 한 변경을 되돌린 적이 있다. 이미 고친 것을 되돌린 것이다.
간단한 로그만 있었으면 피할 수 있었다.
2026-xx-xx 14:20 Phase2 추가 → 증상 변화: 인증 후 IP 미할당 → IP 할당됨
2026-xx-xx 15:05 sites-enabled/default.bak 제거 → 증상 변화: 무작위 실패 → 일관된 실패
2026-xx-xx 16:30 방화벽 정책 88 추가 → 증상 변화: 통신 불가 → 정상
"증상 변화"를 같이 적는 게 핵심이다. 변경 목록만 있으면 무엇이 효과였는지 모른다.
4순위 — FreeRADIUS를 처음부터 SQL 백엔드로
파일 기반으로 시작했다. 단순해서 좋았고, 실제로 잘 동작한다.
하지만 이건 시작하는 순간부터 이전 계획이 있는 선택이었다. 서버가 2대가 되는 순간 파일 동기화가 문제가 된다. 나중에 옮기려면 이전 작업이 별도 프로젝트가 된다.
다시 한다면 처음부터 rlm_sql로 갔을 것이다. 초기 학습 비용은 있지만, 나중에 이전하는 비용보다 확실히 작다.
2. 남은 기술 부채
정직하게 나열한다.
| 1 | TCP transport 실패 | 회피 중 (UDP 기본 + 프래그먼트 축소) | 특정 환경 사용자 불편 |
| 2 | FreeRADIUS 단일 장애점 | 서버 1대 | 인증 전체 중단 가능 |
| 3 | 파일 기반 사용자 저장소 | 유지 중 | 다중화 불가 |
| 4 | 정책에 사용자 신원 없음 | IP 대역으로 대체 (Part 7) | 감사 시 조인 필요 |
| 5 | 인증서 기반 인증 미도입 | PSK + EAP 유지 | 비밀번호 기반 위험 잔존 |
| 6 | 파트너 터널 마무리 | 일부 계정 미등록 | 진행 중 |
2번이 가장 크다. FreeRADIUS가 죽으면 아무도 접속하지 못한다. 방화벽은 HA로 이중화했는데 인증 서버는 단일이다.
이 상태를 그냥 두는 이유는 하나다. 파일 기반 저장소로는 진짜 이중화가 안 된다. 서버를 하나 더 세워도 사용자 파일 동기화 문제가 남고, 동기화가 깨진 상태의 이중화는 이중화가 아니라 위험 요소다.
그래서 순서가 이렇게 된다.
사용자 저장소를 SQL로 이전 (3번)
↓
FreeRADIUS 다중화 (2번)
3번을 먼저 해야 2번이 가능하다. 이걸 알기까지 시간이 걸렸다. "서버 하나 더 세우면 되지"라고 생각했던 기간이 있었다.
3. 업그레이드 경로 판단 — 지금은 올리지 않는다
TCP 문제를 상위 버전에서 고칠 수 있는지 검토했다. 결론은 현재는 보류다.
FortiOS 7.4 최신 패치
릴리즈 노트를 확인했다. 이 문제에 대응하는 명시적 수정 항목을 찾지 못했다. 명시된 항목이 없는 업그레이드로 문제 해결을 기대하는 건 근거가 없다. 다른 이유로 올릴 가치는 있지만, 이 문제의 해결책으로는 아니다.
FortiOS 7.6 계열
7.6은 매력적인 요소가 있다.
- IPsec transport에 Auto 모드가 있다. UDP를 먼저 시도하고 실패하면 TCP로 폴백한다. 우리 이원화 전략을 프로파일 하나로 대체할 수 있다.
- SSL VPN 제거가 이미 반영된 브랜치이므로 언젠가는 가야 한다.
그런데 확인 과정에서 발견한 것이 있다.
send-cert-chain 회귀 이슈. Phase1의 send-cert-chain은 기본값이 enable이고, IKE_AUTH 응답에 CA 인증서 체인을 포함시키는 역할을 한다. 그런데 펌웨어 업그레이드 후 이 값이 enable인데도 체인이 포함되지 않는 사례가 보고됐다.
중요한 건 이게 처음이 아니라는 점이다. 7.4 브랜치에서 이미 같은 종류의 회귀가 발생한 이력이 있고, 7.6.7에서 같은 계열의 문제가 다시 보고됐다.
증상은 이렇게 나타난다. 클라이언트가 리프 인증서에서 신뢰하는 루트까지 경로를 세울 수 없어서 검증에 실패한다. 중간 CA를 클라이언트 신뢰 저장소에 직접 넣으면 해결된다 — 즉 서버가 주지 않는 링크를 클라이언트에 로컬로 공급하는 방식이다.
이게 우리에게 중요한 이유: 우리 구성은 EAP-TTLS이므로 클라이언트가 서버 인증서를 검증한다. 이 회귀에 정면으로 노출되는 구조다.
확인 방법:
# 값이 enable인지 확인
get vpn ipsec phase1-interface <터널명> | grep send-cert-chain
# enable인데도 체인이 안 나가면 회귀다 (설정 문제가 아니다)
config vpn ipsec phase1-interface
edit "<터널명>"
set send-cert-chain enable
next
end
서버 측 우회로: 중간 CA를 로컬 인증서 자체에 번들로 묶는 방법이 있다. 서버가 체인을 별도 페이로드로 보내지 않아도 리프 인증서에 이미 포함되게 하는 것이다.
클라이언트 측 우회로: 중간 CA를 관리 도구(GPO 등)로 클라이언트 신뢰 저장소에 배포한다. 관리형 장비라면 확장성이 있다.
그리고 하나 더: IKE TCP 기본 포트 변경
FortiOS 7.6.1부터 신규 배포 시 IKE TCP 기본 포트가 443으로 변경됐다.
이게 문제가 될 수 있는 상황: GUI 관리 포트도 443인 환경. IPsec 터널에 바인딩된 인터페이스의 GUI 접근에 영향이 있을 수 있다.
업그레이드 전에 확인할 것:
# 관리 포트 확인
get system global | grep admin-sport
# IKE TCP 포트 확인
get system settings | grep ike
이게 중요한 이유가 있다. 원격 관리만 가능한 상황에서 업그레이드했다가 GUI에 못 들어가면 그 자체가 사고다. 우리는 온사이트 대응이 가능한 시점에만 펌웨어 작업을 한다는 원칙을 세웠다.
결론
| 7.4 최신 패치 | 이 문제 해결 근거 없음. 다른 이유로는 검토 가치 있음 |
| 7.6 계열 | Auto 모드는 매력적이나 인증서 회귀 리스크가 우리 구성에 직접 영향 |
| 현재 방침 | 벤더 대응 우선. 업그레이드는 회귀 이슈 정리 후. |
업그레이드로 문제를 해결하려 할 때는, 그 버전이 가져오는 새 문제를 같은 무게로 검토한다. 릴리즈 노트의 "수정됨" 목록만 보고 "알려진 문제" 목록과 커뮤니티 보고를 안 보면 반쪽만 보는 것이다.
4. 이 시리즈가 확인해준 것
시리즈를 쓰면서 알게 된 것 하나를 남긴다.
같은 증상에 여러 원인이 있다.
Part 8에서 커뮤니티에 가장 많이 추천되는 조치(auto-asic-offload disable)가 우리에게 통하지 않았다. 다른 사람들에게는 통했다. 어떤 사람은 클라이언트 DH 그룹으로, 어떤 사람은 중복 가상 어댑터 제거로, 어떤 사람은 포트를 바꿨다 되돌리는 방법으로 해결했다.
전부 같은 증상을 보고 있었다.
그런데 대부분의 기록에는 "이렇게 해서 해결됐습니다"만 남아 있었다. 어떻게 그 원인에 도달했는지가 없었다. 그래서 읽는 사람은 자기 문제가 그 문제와 같은지 판단할 수 없다. 그냥 순서대로 시도해보는 것 말고 방법이 없다.
이 시리즈에서 규칙 하나를 지켰다.
틀린 가설을 지우지 않는다. 통하지 않은 조치도 기록한다.
되돌아보면 이 규칙이 글의 가치 대부분을 만들었다. "우리에게는 이게 효과가 없었다"는 정보가, 읽는 사람에게 "내 문제는 다른 문제구나"를 30초에 알려준다. 그 판단을 스스로 하는 데는 며칠이 걸린다.
5. 전체 시리즈에서 남는 원칙 열 개
12편에서 반복해서 나온 것들을 정리한다.
- 증상이 시도마다 달라지면 원인을 센다. 하나를 찾으려는 접근이 실패한다. (Part 5)
- 재현성 없음은 다른 모든 디버깅의 전제를 무너뜨린다. 무작위성 제거가 최우선이다. (Part 5)
- 설정 파일에 써 있는 값과 실제 적용된 값은 다를 수 있다. 런타임에서 확인한다. (Part 6)
- "부분 개선"은 해결의 신호가 아니라 회피의 신호일 수 있다. 실패율이 0이 되지 않으면 원인은 그대로 있다. (Part 8)
- 파라미터를 바꿔서 증상이 사라지지 않고 이동하면 원인을 못 찾은 것이다. (Part 6, 8)
- 직전 경험이 다음 가설을 편향시킨다. 로그가 다른 곳을 가리키면 직관보다 로그를 믿는다. (Part 8)
- "특정 장비에서만"은 거의 항상 클라이언트 상태 차이다. 서버를 아무리 봐도 안 나온다. (Part 5, 9)
- 문법을 여러 형태로 바꿨는데 전부 같은 결과면 문법 문제가 아니다. 구조를 의심한다. (Part 7)
- 초기 설계의 여유가 나중에 유일한 우회로가 된다. IP 대역 설계 30분이 그랬다. (Part 3, 7)
- 관측과 추정을 문서에서 분리한다. 이 구분이 없으면 읽는 사람이 오진한다. (Part 8)
6. 아직 끝나지 않았다
- TCP transport 근본 원인: 벤더 대응 진행 중
- 사용자 저장소 SQL 이전: 계획 단계
- FreeRADIUS 이중화: SQL 이전 후
- 파트너 터널: 일부 계정 등록 진행 중
- 업그레이드 경로: 회귀 이슈 정리 후 재검토
진행 상황이 생기면 해당 편에 추가한다. "완료" 상태로 끝나지 않는 것도 정직한 기록의 일부다.
시리즈 전체 목차
구축편
- Part 0 — 벤더가 터널 모드를 없앤다고 했다
- Part 1 — 왜 IKEv2 + EAP-TTLS + FreeRADIUS + TOTP인가
- Part 2 — FreeRADIUS 3.0.20에 TOTP를 붙이기
- Part 3 — Phase1/Phase2와 IP 풀 설계
- Part 4 — EMS 없이 150명에게 프로파일 배포하기
트러블슈팅편
- Part 5 — IKE는 붙는데 인증이 안 된다: 원인이 다섯 개였다
- Part 6 — transport_mode="tcp"는 조용히 무시된다
- Part 7 — authusrgrp를 쓰면 정책의 그룹 매칭이 죽는다
- Part 8 — IKE-over-TCP로 절반이 실패했다
- Part 9 — VPN이 아니라 무선 드라이버였다
운영편
- Part 10 — 하나의 게이트웨이에 PSK 터널 여러 개
- Part 11 — 150명을 어떻게 옮겼는가
- Part 12 — 다시 한다면, 그리고 남은 것 (이 글)
'네트워크' 카테고리의 다른 글
| VPN이 아니라 무선 드라이버였다 — 서버만 보다가 놓친 것 (Part 9) (1) | 2026.07.26 |
|---|---|
| IKE-over-TCP로 절반이 실패했다 — 설정으로는 고칠 수 없는 것이었다 (Part 8) (0) | 2026.07.26 |
| authusrgrp를 쓰면 방화벽 정책의 그룹 매칭이 죽는다 — 문서에 없던 상호작용 (Part 7) (0) | 2026.07.25 |
| transport_mode="tcp"는 조용히 무시된다 — 우연히 동작했던 프로파일의 정체 (Part 6) (0) | 2026.07.25 |
| IKE는 붙는데 인증이 안 된다 — 원인이 하나가 아니라 다섯 개였다 (Part 5) (0) | 2026.07.25 |