4장. 사내 네트워크 보안과 원격 접속 (실전: ZTNA·VPN 도입과 설치)
학습 목표
- 전통적인 경계형 VPN(성곽과 해자 모델)이 가진 횡적 이동(Lateral Movement) 위험을 설명하고, NIST SP 800-207 제로트러스트 아키텍처(PE·PA·PEP)의 핵심 원칙과 비교한다.
- 인바운드 방화벽 포트를 하나도 열지 않는 아웃바운드 전용 커넥터(
cloudflaredCloudflare Tunnel)의 작동 원리와 고가용성 구성을 설계한다. - WireGuard 기반 메시 VPN(Tailscale 등)의 제어 평면·데이터 평면 분리, 직접 연결(NAT Traversal)과 DERP 중계, 서브넷 라우터(Subnet Router), 최소 권한 ACL을 설명한다.
- 전사 단말 배포 시 분할 터널링(Split Tunneling: 포함 대 제외 모드)과 가정용 공유기 대역(
192.168.x.x등 RFC1918) 충돌 문제를 진단하고 해결책을 수립한다. - 사내 전용 도메인이 외부 공인 DNS에 노출되지 않도록 분할 시야 DNS(Split-Horizon DNS)와 조건부 포워더를 설계한다.
- IdP 연동(SSO·MFA·SCIM 자동 계정 회수)과 디바이스 포스처(OS 패치, 디스크 암호화, MDM/EDR) 검사를 결합해 접속 조건을 강제한다.
- 사내 ZTNA TLS 검사 루트 CA 인증서를 개발자 노트북에 배포할 때
NODE_TLS_REJECT_UNAUTHORIZED=0같은 보안 무력화 없이 CLI·코딩 에이전트(NODE_EXTRA_CA_CERTS,SSL_CERT_FILE)가 정상 동작하도록 구성한다.
들어가며
AI 서비스를 만드는 회사의 진짜 보물은 고객용 공개 웹사이트에만 있지 않습니다. 개발자들이 매일 접속하는 사내 스테이징 데이터베이스, 프롬프트와 평가 데이터셋이 담긴 내부 관리 도구, 자체 호스팅한 vLLM/Ollama 추론 서버, 사내 위키와 배포 파이프라인 대시보드가 모두 사내망 안에 있습니다. 재택근무하는 개발자, 외주 데이터 라벨러, 해외 출장 중인 PM이 이 사내 자산들에 어떻게 접속하게 해야 할까요?
가장 게으른 방법은 사내 서버에 공인 IP를 달아 두고 방화벽에서 포트(SSH 22, DB 5432, 웹 8080)를 열어 둔 채 비밀번호만 걸어 두는 것입니다. 이는 전 세계 자동화 스캐너에게 "우리 서버를 해킹해 주세요"라고 광고하는 것이나 다름없습니다. 그다음으로 흔한 방식이 20년 넘게 쓰여 온 **전통적인 경계형 VPN(Virtual Private Network)**입니다. 사내 방화벽에 VPN 장비 하나를 두고, 직원이 VPN 클라이언트로 접속하면 사내 내부망 IP를 하나 내어주는 방식입니다.
그런데 어느 날 외주 작업자의 개인 노트북이 악성코드에 감염된 채 사내 VPN에 접속했습니다. 기존 VPN은 "일단 VPN 로그인만 통과하면 사내망 안에 들어온 같은 식구"로 취급했기 때문에, 공격자는 외주 작업자가 볼 일도 없는 재무팀 파일 서버와 운영 데이터베이스 포트까지 내부망을 타고 옆으로 자유롭게 돌아다니며(횡적 이동, Lateral Movement) 회사를 장악했습니다. 설상가상으로 보안팀이 이를 막겠다며 차세대 ZTNA 에이전트를 전사 노트북에 일괄 설치했더니, 다음 날 아침 개발자 전원이 "집 와이파이 프린터가 안 돼요", "사내 도메인이 안 열려요", "Claude Code와 npm install, python이 UNABLE_TO_GET_ISSUER_CERT_LOCALLY 인증서 오류를 내며 전부 멈췄어요!"라고 아우성을 쳤습니다.
이 장은 바로 이 두 가지 질문에 답합니다. "어떻게 해야 인바운드 포트를 열지 않고도 최소 권한으로 사내 리소스를 보호할 것인가?" 그리고 **"실제 조직의 개발자·임직원 단말에 VPN과 ZTNA를 도입·설치할 때 터지는 네트워크·DNS·인증서 충돌을 어떻게 깔끔하게 해결할 것인가?"**입니다.
핵심 개념
전통 경계형 VPN의 한계와 제로트러스트(NIST SP 800-207)
전통적인 네트워크 보안은 '성곽과 해자(Castle-and-Moat)' 모델이었습니다. 회사 바깥 인터넷은 위험한 곳, 회사 안쪽 사내망(VPC, 사옥 LAN)은 안전한 성안이라고 가정합니다. 원격 근무자는 성문(VPN 게이트웨이)에서 신분증을 보여 주고 성안으로 들어옵니다.
이 모델이 무너진 이유는 세 가지입니다.
- 과도한 암묵적 신뢰와 횡적 이동(Lateral Movement): 성문(VPN)만 한 번 통과하면 사용자의 기기가 사내 네트워크 계층(L3/L4) 자체에 편입됩니다. 마케팅 인턴이 웹 대시보드 하나만 보면 되는데도, 같은 서브넷에 있는 개발 서버의 SSH 포트나 데이터베이스 포트를 스캔하고 찔러 볼 수 있습니다. 계정 하나만 털려도 성안 전체가 뚫립니다.
- VPN 장비 자체의 인바운드 공격 표면: 원격 접속을 받으려면 VPN 장비가 인터넷을 향해 공인 IP와 인바운드 포트(예: UDP 500/4500, TCP 443)를 항상 열어 두고 있어야 합니다. 실제로 최근 수년간 가장 치명적인 기업 침해 사고의 상당수가 노후 VPN 어플라이언스의 제로데이 취약점을 통해 일어났습니다.
- 클라우드·원격 근무 시대의 헤어핀(Hairpinning) 병목: 사내 자산은 이미 AWS 서울 리전과 GCP 도쿄 리전에 흩어져 있는데, 직원의 모든 트래픽을 본사 사무실의 물리 VPN 장비로 모았다가 다시 클라우드로 보내면(백홀링, Backhauling) 속도가 느려지고 VPN 장비가 단일 장애점(SPOF)이 됩니다.
미국 국립표준기술연구소(NIST)의 SP 800-207 (Zero Trust Architecture) 표준은 이 한계를 극복하기 위해 제로트러스트의 핵심 명제를 다음과 같이 정의합니다.
"네트워크의 물리적 위치나 논리적 대역(사내망 IP 여부)만으로 그 어떤 주체에게도 암묵적 신뢰(Implicit Trust)를 부여하지 않는다. 모든 접근은 네트워크 전체가 아니라 개별 리소스(애플리케이션·서비스) 단위로, 매 세션마다 신원과 기기 상태·맥락을 검증해 최소 권한으로만 허가한다."
NIST SP 800-207은 제로트러스트 시스템을 세 가지 논리 구성 요소로 설명합니다.
| NIST SP 800-207 구성 요소 | 역할 | 실무 대응 예 |
|---|---|---|
| 정책 엔진 (Policy Engine, PE) | 사용자의 신원(IdP), 기기 상태(MDM/EDR), 시간·위치·위험 신호를 종합해 "이 요청을 허용할지 거부할지" 최종 판단을 내리는 두뇌 | Cloudflare Access 정책 평가기, Tailscale/Okta 정책 결정 엔진 |
| 정책 관리자 (Policy Administrator, PA) | 정책 엔진의 결정에 따라 실제 통신 경로를 열거나 닫도록 집행 지점에 명령(토큰·세션 키 발급 또는 차단)을 전달 | ZTNA 컨트롤 플레인, 코디네이션 서버 |
| 정책 집행 지점 (Policy Enforcement Point, PEP) | 사용자 단말과 리소스 사이에서 실제로 패킷·연결을 가로막고 검증된 세션만 통과시키는 관문 | 엣지 ZTNA 프록시, cloudflared 커넥터, WireGuard 피어 인터페이스 |
전통 VPN과 ZTNA(Zero Trust Network Access)의 차이를 한눈에 비교하면 다음과 같습니다.
| 비교 기준 | 전통적 경계형 VPN | 제로트러스트 네트워크 접근 (ZTNA) |
|---|---|---|
| 접근 부여 단위 | 네트워크/서브넷 전체 (예: 10.10.0.0/16 대역 진입) |
개별 애플리케이션·호스트·포트 단위 (예: admin.internal의 HTTPS만 허용) |
| 횡적 이동 위험 | 높음 (내부망 진입 후 인접 서버 스캔 가능) | 극히 낮음 (허가되지 않은 리소스는 존재 자체도 보이지 않음) |
| 인바운드 방화벽 포트 | VPN 게이트웨이 공인 IP 및 인바운드 포트 개방 필수 | 아웃바운드 터널 사용 시 인바운드 오픈 포트 0개 |
| 검증 시점과 조건 | 최초 터널 연결 시 ID/비밀번호(및 OTP) 1회 확인 | 매 리소스 접근마다 SSO 신원 + MFA + 기기 보안 상태(Device Posture) 지속 확인 |
아웃바운드 전용 커넥터: cloudflared (Cloudflare Tunnel)와 인바운드 포트 제거
사내 VPC나 온프레미스 서버에 떠 있는 내부 관리 웹앱(http://localhost:8080)이나 개발용 SSH 서버를 외부의 허가된 직원에게만 열어 주려면 방화벽 인바운드 포트를 어떻게 설정해야 할까요? 정답은 **"인바운드 포트를 단 하나도 열지 않는다"**입니다.
어떻게 밖에서 안으로 들어오는데 인바운드 포트를 하나도 열지 않을 수 있을까요? 비밀은 연결을 맺는 **방향을 뒤집는 것(Outbound-only Connector)**에 있습니다. 대표 주자가 바로 Cloudflare의 **cloudflared (Cloudflare Tunnel)**입니다.
작동 원리를 단계별로 따라가 봅시다.
- 사내 서버(또는 프라이빗 서브넷의 컨테이너)의 방화벽(Security Group)은 인바운드 규칙을 완전히 비워 둡니다(Inbound allow: 없음). 대신 외부 인터넷으로 나가는 **아웃바운드(Outbound, 포트 443/7844 QUIC 또는 HTTP/2)**만 허용합니다.
- 사내 서버 안에서 경량 데몬인
cloudflared를 실행하면,cloudflared가 먼저 사내에서 바깥쪽(Cloudflare의 가까운 엣지 데이터센터 여러 곳)을 향해 암호화된 아웃바운드 터널 연결을 4개 맺어 두고 대기합니다. - 이제 외부의 직원이 브라우저에서
https:// staging-admin.example.com에 접속합니다. - 요청은 먼저 Cloudflare 엣지(Cloudflare Access, 즉 PEP)에 도착합니다. Cloudflare는 이 직원이 사내 IdP(Google Workspace, Okta 등)로 로그인했는지, MFA와 기기 상태를 통과했는지 먼저 검사합니다.
- 검사를 통과한 정상 요청만 이미 사내
cloudflared가 안쪽에서 바깥쪽으로 열어 두었던 그 터널을 타고 사내 서버의http://localhost:8080으로 전달됩니다!
이 구조가 주는 보안 및 운영상의 이점은 막대합니다.
- 공인 IP와 인바운드 포트의 완전한 소멸: 사내 서버는 공인 IP 자체가 없어도(NAT 게이트웨이 뒤의 프라이빗 서브넷이어도) 작동하며, 인터넷에서 스캔할 수 있는 열린 포트가 0개이므로 포트 스캔이나 직접 디도스 공격이 물리적으로 불가능합니다.
- 인증 선행(Pre-authentication): 인증되지 않은 공격자의 패킷은 Cloudflare 엣지에서 즉시 버려지므로 사내 서버의 웹 프레임워크까지 닿지도 못합니다.
- 고가용성(HA) 구성: 단일
cloudflared프로세스가 죽으면 터널이 끊기므로, 프로덕션이나 핵심 사내망에서는 서로 다른 가용 영역(AZ)이나 두 대 이상의 호스트에 동일한 터널을 구동하는cloudflared복제본(Replica, 커넥터)을 2개 이상 띄워 무중단 이중화를 구성합니다.
메시 VPN(WireGuard·Tailscale): 서브넷 라우터, DERP 릴레이와 최소 권한 ACL
웹 애플리케이션이나 단일 호스트는 Cloudflare Tunnel 같은 리버스 프록시형 ZTNA로 공개하기 좋지만, 개발자들이 로컬 IDE에서 여러 마이크로서비스, 내부 gRPC 서버, 개발용 쿠버네티스 클러스터, 사내 추론 노드를 마치 한 사무실 LAN에 있는 것처럼 낮은 지연 시간으로 연결해야 할 때는 **WireGuard 기반의 현대적 메시(Mesh) 오버레이 네트워크(예: Tailscale, Netbird 등)**가 큰 힘을 발휘합니다.
전통적 중앙 집중형 VPN(Hub-and-Spoke)과 메시 VPN의 아키텍처 차이는 뚜렷합니다.
| 구분 | 전통적 중앙 집중형 VPN (OpenVPN/IPsec 장비) | 현대적 WireGuard 메시 VPN (Tailscale 등) |
|---|---|---|
| 트래픽 경로 | 모든 패킷이 중앙 VPN 게이트웨이 한 곳을 거쳐감 (병목 발생) | 제어 평면(열쇠 교환·정책 배포)만 중앙 서버가 맡고, 실제 암호화 데이터 패킷은 단말과 대상 서버가 1:1 직접(Peer-to-Peer) 통신 |
| 암호화 프로토콜 | 무겁고 복잡한 레거시 협상 | 최신 경량 암호화 WireGuard (커널/고성능 유저스페이스, 빠른 로밍) |
| IP 할당 체계 | 접속할 때마다 바뀌는 사설 IP 풀(10.8.0.x) |
기기·노드마다 고정된 CGNAT 대역(100.64.0.0/10) 고유 IP 및 MagicDNS 이름 부여 |
메시 VPN을 실무에 도입할 때 반드시 이해해야 할 세 가지 핵심 메커니즘이 있습니다.
- NAT 통과(NAT Traversal)와 DERP 릴레이 폴백: 두 노드가 각각 공유기(NAT) 뒤에 있어도 STUN/ICE 기법으로 방화벽에 구멍을 뚫어(UDP Hole Punching) 직접 UDP 연결을 맺습니다. 만약 사내 방화벽이나 호텔·카페 망이 UDP를 완전히 차단해 직접 연결이 불가능할 때는 연결이 끊기는 대신 **암호화된 상태 그대로 HTTPS(TCP 443) 기반의 중계 서버(DERP, Designated Encrypted Relay for Packets)**를 거쳐 통신을 이어 갑니다. 이때도 종단 간 WireGuard 암호화 키는 두 단말만 가지고 있으므로 중계 서버도 패킷 내용을 볼 수 없습니다.
- 서브넷 라우터(Subnet Router, 바스티온 브리지): AWS RDS 관리형 데이터베이스나 사내 네트워크 프린터, 구형 장비에는 VPN 에이전트 프로그램을 직접 설치할 수 없습니다. 이때 같은 VPC 프라이빗 서브넷 안에 작은 리눅스 인스턴스(또는 컨테이너)를 띄워 서브넷 라우터로 지정하고 DB 서브넷 대역(예:
10.50.10.0/24)만 광고(--advertise-routes=10.50.10.0/24)하게 하면, 에이전트를 설치할 수 없는 RDS에도 메시 망을 통해 안전하게 도달할 수 있습니다(고가용성을 위해 서브넷 라우터도 2대 이상으로 구성). - 태그·역할 기반 최소 권한 ACL(Access Control Lists): 메시 VPN에 연결되었다고 해서 모든 노드가 서로 통신하게 두면 전통 VPN의 횡적 이동 문제가 재발합니다. 따라서 기본 정책은 **전체 거부(Default Deny)**로 두고, 신원 그룹과 서버 태그(
tag:staging-db,tag:gpu-worker)를 기준으로 누가(Source) 어느 태그의(Destination) 몇 번 포트(Port)에만 접근할 수 있는지 JSON/HuJSON 코드로 엄격히 정의합니다.
{
"acls": [
{
"action": "accept",
"src": ["group:backend-devs"],
"dst": ["tag:staging-db:5432", "tag:ai-inference:8000"]
},
{
"action": "accept",
"src": ["group:security-admins"],
"dst": ["tag:prod-bastion:22"]
}
]
}
이처럼 선언된 ACL이 있으면 백엔드 개발자는 스테이징 DB의 5432 포트와 추론 서버의 8000 포트에만 패킷을 보낼 수 있을 뿐, 같은 망에 있는 다른 직원의 노트북이나 운영 바스티온의 22번 포트에는 패킷조차 닿지 않습니다.
분할 터널링(Split Tunneling)과 가정용 공유기 대역(RFC1918) 충돌 해결
사내 VPN/ZTNA 클라이언트를 전 직원 노트북에 배포한 첫날, 헬프데스크가 마비되는 가장 흔한 원인은 라우팅 대역 설계와 분할 터널링(Split Tunneling) 실수입니다.
터널 라우팅 방식은 크게 두 가지로 나뉩니다.
- 전체 터널링 (Full Tunnel): 직원의 노트북에서 나가는 **모든 인터넷 트래픽(
0.0.0.0/0)**을 사내 VPN 게이트웨이나 보안 웹 게이트웨이(SWG)로 보냅니다. 보안 통제는 강력하지만, 직원이 유튜브나 화상회의(Zoom), 대용량 Docker 이미지를 받을 때까지 모두 사내 관문을 거치므로 사내 대역폭이 폭발하고 지연 시간이 길어집니다. - 분할 터널링 (Split Tunnel): 사내 리소스로 가는 트래픽만 터널로 태우고, 일반 외부 인터넷 트래픽은 직원 집의 인터넷 회선으로 직접 나가게 분리합니다. 설정 방식에는 두 가지 모드가 있습니다.
- 포함 모드 (Include Mode): 지정한 사내 IP 대역(예:
10.200.0.0/16)이나 사내 도메인만 터널로 보내고 나머지는 모두 로컬로 내보냅니다. 사내 리소스 접속이 목적일 때 가장 깔끔하고 충돌이 적습니다. - 제외 모드 (Exclude Mode): 기본적으로 트래픽을 터널로 보내되, 로컬 LAN 대역이나 화상회의 대역 등 지정한 대역만 터널에서 제외합니다.
- 포함 모드 (Include Mode): 지정한 사내 IP 대역(예:
여기서 실무자를 괴롭히는 악명 높은 함정이 바로 **RFC1918 사설 IP 대역 충돌(Overlapping Subnet Collision)**입니다!
어떤 회사가 클라우드 VPC를 만들면서 아무 생각 없이 192.168.0.0/16이나 192.168.1.0/24(또는 흔한 10.0.0.0/8 전체)를 사내 VPC 대역으로 잡고 VPN 포함 목록에 넣었다고 해 봅시다.
재택근무하는 개발자의 집 와이파이 공유기, 카페 공유기, 스마트폰 핫스팟의 대다수도 기본 대역으로 192.168.0.0/24나 192.168.1.0/24(게이트웨이 192.168.1.1)를 씁니다!
그 결과 VPN을 켜는 순간 두 가지 참사 중 하나가 일어납니다.
- 노트북의 운영체제 라우팅 테이블에서 더 구체적인 로컬 서브넷 경로가 우선되면, 개발자가 사내 서버(
192.168.1.50)로 보낸 패킷이 터널로 가지 않고 개발자 집 거실의 공유기나 스마트 TV로 날아가 접속이 안 됩니다. - 반대로 VPN 클라이언트가 강제로
192.168.1.0/24를 가로채면, 개발자 집의 무선 프린터, NAS, 로컬 개발 기기 연결이 전부 끊기거나 아예 공유기 게이트웨이와 통신이 두절됩니다.
따라서 사내 인프라와 VPN/ZTNA를 설계할 때는 다음의 대역 충돌 방지 3원칙을 반드시 지켜야 합니다.
| 원칙 | 잘못된 설계 (충돌 유발) | 올바른 실무 설계 |
|---|---|---|
| 1. 사내 VPC CIDR 선정 | 가정·공유기 기본값인 192.168.0.0/24, 192.168.1.0/24, 10.0.0.0/24 사용 또는 10.0.0.0/8 통째로 라우팅 |
가정용 공유기와 절대 겹치지 않는 상위/특정 대역(예: 10.240.0.0/16, 172.28.0.0/16)을 필요한 크기만큼만 할당 |
| 2. 분할 터널 포함 범위 | 사내 서버는 /24 하나뿐인데 편의상 10.0.0.0/8 전체를 VPN 터널로 납치 |
실제 사내 리소스가 존재하는 정밀한 CIDR(10.240.10.0/24)만 Include 모드에 등록 |
| 3. 이미 대역이 겹친 레거시 망 연결 | 전 직원 공유기 설정을 바꾸라고 공지 (불가능) | IP 직접 라우팅 대신 도메인 기반 ZTNA 프록시(Cloudflare Tunnel), CGNAT(100.64.0.0/10) 가상 주소 매핑, 또는 서브넷 변환(NAT/4via6)으로 우회 |
분할 시야 DNS(Split-Horizon DNS)와 사내 도메인 노출 방지
사내 개발 데이터베이스의 주소가 db.staging.internal.example.com(사설 IP 10.240.10.15)이라고 합시다. 이 도메인을 직원들이 외우기 쉽게 하려고 외부 누구나 조회할 수 있는 공인 DNS(Route 53 Public Hosted Zone 등)에 A 10.240.10.15로 그대로 등록해도 될까요?
절대 권장하지 않습니다. 공인 DNS에 사내 서버 이름과 사설 IP 구조를 적어 두면, 외부 공격자가 DNS 열거(Zone Enumeration)나 인증서 투명성 로그(Certificate Transparency Logs)를 통해 git-internal, payroll-db, vllm-gpu-01 같은 사내 핵심 시스템의 존재와 내부 네트워크 토폴로지(대역 구조)를 손바닥 보듯 파악할 수 있기 때문입니다(정찰 단계의 정보 노출).
해법은 **분할 시야 DNS(Split-Horizon DNS, 또는 Split-Brain DNS)**입니다. 질문하는 사람이 누구냐(어느 경로로 물어보느냐)에 따라 DNS 응답을 다르게 주거나 아예 분리하는 기법입니다.
- 내부 전용 호스팅 영역 분리: AWS Route 53 Private Hosted Zone(VPC 내부에서만 응답)이나 사내 전용 DNS 리졸버에만
*.internal.example.com레코드를 등록합니다. 외부 공인 DNS에서 이 도메인을 물어보면NXDOMAIN(존재하지 않는 도메인)이 반환됩니다. - ZTNA·VPN 클라이언트의 조건부 DNS 포워딩(Conditional DNS Forwarding / Split DNS): 원격 근무자의 노트북에 설치된 ZTNA/VPN 클라이언트에 **"오직
*.internal.example.com으로 끝나는 도메인 질의만 터널 안쪽의 사내 DNS 리졸버(예:10.240.0.2)로 보내고, 나머지 일반 도메인(google.com,naver.com) 질의는 원래 쓰던 로컬 DNS로 보내라"**고 설정합니다.
이렇게 구성하면 사내 망에 인증된 직원만 사내 도메인을 사설 IP로 해석할 수 있고, 외부 인터넷에는 사내 호스트 이름 자체가 노출되지 않으며, 직원의 일반 웹 서핑 DNS 속도도 전혀 느려지지 않습니다.
IdP 연동(SSO·MFA)과 SCIM 퇴사자 자동 회수
VPN이나 ZTNA를 도입할 때 "사내 위키에 공용 VPN 아이디/비밀번호(vpnuser / company1234!)나 공용 .ovpn 설정 파일을 올려 두고 다 같이 내려받아 쓰게 하는 것"은 보안을 포기하는 행위입니다. 누가 언제 접속했는지 감사(Audit)할 수도 없고, 직원 한 명이 퇴사할 때마다 전 직원의 VPN 비밀번호를 다 바꾸지 않는 한 퇴사자가 집에서 사내망에 계속 들어올 수 있기 때문입니다.
따라서 엔터프라이즈 원격 접속은 반드시 **중앙 IdP(Identity Provider: Okta, Google Workspace, Microsoft Entra ID 등)**와 한 몸으로 묶여야 합니다. OIDC나 SAML 기반 SSO(Single Sign-On)와 피싱 저항성 MFA(WebAuthn 패스키, 하드웨어 보안 키)로 접속자의 신원을 검증하고, 인사(HR)·IdP 시스템에서 퇴사나 계약 종료가 처리되는 즉시 SCIM(System for Cross-domain Identity Management) 프로토콜이 ZTNA/VPN 계정을 자동으로 비활성화하며 현재 열려 있는 활성 터널 세션과 토큰까지 즉시 강제 폐기(Revoke)하도록 구성해야 합니다.
디바이스 포스처(Device Posture)와 단말 보안 상태 강제
신원(Identity)이 맞다고 해서 그 기기(Device)까지 안전한 것은 아닙니다. 직원이 본인의 정상 아이디와 MFA로 로그인했더라도, 접속에 사용한 기기가 보안 패치가 안 된 집의 공용 PC이거나 백신(EDR)이 꺼진 채 악성코드에 감염된 노트북이라면 사내망 문을 열어 주어서는 안 됩니다.
**디바이스 포스처(Device Posture)**는 ZTNA 클라이언트가 연결을 맺기 전과 세션 유지 중에 단말의 보안 건강 상태를 실시간으로 점검해 정책 엔진(PE)에 보고하는 메커니즘입니다.
| 통제 축 | 필수 구성 요소 | 막아내는 실제 위협 |
|---|---|---|
| SSO와 강력한 MFA | OIDC/SAML 연동 + 피싱 저항성 MFA(WebAuthn 패스키, 하드웨어 보안 키) | 유출된 비밀번호나 피싱 사이트를 통한 원격 접속 탈취 차단 |
| SCIM 자동 프로비저닝·디프로비저닝 | 인사/IdP 시스템에서 퇴사·휴직 처리 시 SCIM 프로토콜로 ZTNA/VPN 계정을 즉시 자동 비활성화 및 활성 세션 강제 종료 | 퇴사자의 계정이 방치(Orphaned Account)되어 사내 자원에 계속 접근하는 사고 원천 차단 |
| 디바이스 포스처 (Device Posture) | 접속 허용 전 단말의 OS 최신 보안 패치 여부, 디스크 전체 암호화(FileVault/BitLocker), 화면 잠금, 회사 MDM 등록 증명서, EDR(백신) 실행 여부 검사 | 직원의 본래 계정이 맞더라도 보안 패치가 안 된 개인 PC나 감염된 기기에서 사내망으로 접속하는 것 차단 |
| 짧은 세션 수명과 재인증 | 터널 토큰 만료 주기(예: 12~24시간) 및 민감 리소스(운영 DB·SSH) 접근 시 Step-up 재인증 | 노트북 분실이나 장기 방치 세션을 통한 무단 접근 제한 |
단말 루트 CA 인증서 배포와 개발자 CLI·코딩 에이전트 TLS 오류 해결
많은 기업이 ZTNA와 보안 웹 게이트웨이(SWG, 예: Cloudflare Gateway TLS Inspection, Zscaler 등)를 도입하면서 외부로 나가는 HTTPS 트래픽 안에 기밀 데이터 유출(DLP)이나 악성코드가 없는지 검사하기 위해 **TLS 복호화 검사(TLS Inspection)**를 켭니다.
이때 게이트웨이는 중간에서 사내 전용 기업 루트 CA(Enterprise Root CA) 인증서로 웹사이트 인증서를 새로 서명해 단말로 내려보냅니다. 따라서 MDM(Jamf, Intune 등)을 통해 전 직원의 macOS 키체인(Keychain)이나 Windows 인증서 저장소에 이 '사내 루트 CA 인증서'를 신뢰할 수 있는 루트로 배포합니다. 크롬이나 사파리 같은 웹 브라우저는 운영체제의 기본 인증서 저장소를 읽으므로 아무 문제 없이 잘 열립니다.
그런데 왜 개발자들의 터미널에서는 난리가 날까요?
Node.js, Python(requests, urllib, pip), curl, git, Docker 컨테이너, 그리고 이를 기반으로 돌아가는 AI 코딩 에이전트(Claude Code, Codex CLI 등)는 운영체제의 시스템 키체인을 읽지 않고 자기만의 내장 CA 번들 파일(Mozilla CA 목록 등)만 믿는 경우가 대부분이기 때문입니다! 그 결과 개발자가 터미널에서 패키지를 설치하거나 코딩 에이전트를 실행하면 사내 ZTNA 게이트웨이가 제시한 사내 CA 인증서를 "모르는 기관이 서명한 가짜 인증서"로 판정해 UNABLE_TO_GET_ISSUER_CERT_LOCALLY 또는 SELF_SIGNED_CERT_IN_CHAIN 오류를 내며 멈춰 섭니다.
이때 개발자들이 검색해서 가장 흔히 저지르는 최악의 금기 행동이 바로 다음 설정입니다.
// 절대 금지! (중간자 공격에 대한 모든 TLS 검증을 완전히 끄는 위험한 설정)
export NODE_TLS_REJECT_UNAUTHORIZED=0
git config --global http.sslVerify false
pip install --trusted-host pypi.org ...
curl -k https://...
NODE_TLS_REJECT_UNAUTHORIZED=0이나 sslVerify false를 켜는 순간, 사내 CA뿐만 아니라 카페 공용 와이파이에서 해커가 건네주는 가짜 인증서까지 묻지도 따지지도 않고 다 믿어 버립니다.
올바른 정석 해법은 TLS 검증을 끄는 것이 아니라, 각 런타임과 CLI 도구에 '사내 루트 CA 인증서 파일(.pem)'을 신뢰 목록에 정식으로 추가해 주는 것입니다.
// 정석 해법: TLS 검증은 엄격히 유지한 채 사내 루트 CA(.pem)를 신뢰 저장소에 추가
// 1) Node.js 및 Node 기반 AI 코딩 에이전트 (기존 내장 CA 목록에 사내 CA를 '추가')
export NODE_EXTRA_CA_CERTS="/etc/ssl/certs/company-ztna-root-ca.pem"
// 2) Python (requests, pip, httpx) 및 OpenSSL/curl 계열 도구
// 주의: SSL_CERT_FILE과 REQUESTS_CA_BUNDLE은 기본 번들을 '교체'하므로,
// 공인 루트 CA 번들과 사내 루트 CA를 하나로 합친(Combined) PEM 파일을 지정한다!
export SSL_CERT_FILE="/etc/ssl/certs/combined-ca-bundle.pem"
export REQUESTS_CA_BUNDLE="/etc/ssl/certs/combined-ca-bundle.pem"
export CURL_CA_BUNDLE="/etc/ssl/certs/combined-ca-bundle.pem"
// 3) Git 클라이언트
git config --global http.sslCAInfo "/etc/ssl/certs/combined-ca-bundle.pem"
여기서 시험과 실무에서 아주 중요한 디테일이 있습니다.
NODE_EXTRA_CA_CERTS는 이름 그대로 Node의 기존 공인 CA 목록에 사내 CA를 덧붙여(Extra) 주므로 사내 CA 파일 하나만 가리켜도 됩니다.- 반면
SSL_CERT_FILE이나REQUESTS_CA_BUNDLE은 기본 CA 목록 전체를 **대체(Override)**하므로, 여기에 사내 CA 인증서 달랑 하나만 들어 있는 파일을 지정하면 사내 게이트웨이를 거치지 않는 일반 HTTPS 통신이 오히려 다 깨집니다! 따라서 이 변수들에는 반드시 **기존 공인 루트 CA 번들 뒤에 사내 루트 CA를 이어 붙인 통합 번들(combined-ca-bundle.pem)**을 지정해야 합니다. 또한 인증서 핀닝(Certificate Pinning)을 엄격히 수행하는 일부 외부 바이너리나 애플/구글 공식 업데이트 도메인은 ZTNA 관리 콘솔에서 **TLS 복호화 예외(Do Not Inspect 목록)**로 등록해 마찰을 없애야 합니다.
깊이 읽기
SSH 개인키 배포 없는 단기 SSH 인증서(Short-lived SSH Certificates)와 세션 감사
사내 리눅스 서버나 GPU 클러스터에 접속할 때 여전히 각 개발자의 ~/.ssh/id_ed25519.pub 공개키를 받아 서버마다 ~/.ssh/authorized_keys 파일에 수동으로 붙여 넣고 있다면 두 가지 큰 구멍이 생깁니다. 첫째, 직원이 퇴사하거나 노트북을 잃어버렸을 때 수백 대의 서버를 돌며 그 직원의 공개키를 일일이 지우기가 어렵습니다. 둘째, 여러 사람이 같은 ubuntu나 ec2-user 계정으로 접속하면 누가 어떤 명령을 쳤는지 구분하기 힘듭니다.
현대 ZTNA 환경(Cloudflare Access for Infrastructure, Tailscale SSH, HashiCorp Vault SSH CA)은 정적 공개키 파일 대신 단기 SSH 인증서(Short-lived SSH Certificates) 또는 신원 연동 SSH를 사용합니다.
- 서버에는 개별 개발자의 공개키를 하나도 넣지 않고, 오직 조직의 SSH CA 공개키(
TrustedUserCAKeys) 단 하나만 등록합니다. - 개발자가 서버에 접속하려 할 때 ZTNA 클라이언트가 SSO와 MFA, 디바이스 포스처를 확인한 뒤 유효 기간이 불과 몇 시간(예: 4~8시간)인 단기 SSH 인증서를 즉석에서 발급해 줍니다.
- 유효 시간이 지나면 인증서는 자동으로 만료되므로 회수할 키 자체가 남지 않고, 접속 로그와 명령 감사 기록에
ubuntu뿐만 아니라 실제 SSO 이메일(minsu@company.com)이 정확히 찍힙니다.
개발자 생산성과 보안 통제의 균형: 전사 ZTNA 단계별 롤아웃 전략
아무리 훌륭한 ZTNA 솔루션이라도 어느 날 아침 전 직원에게 공지 한 줄 보내고 전체 터널(Full Tunnel)과 TLS 복호화를 한꺼번에 켜 버리면 그날 회사의 모든 개발과 영업이 멈춥니다. 실패 없는 사내 도입·설치는 반드시 다음 4단계 점진적 롤아웃을 따릅니다.
| 단계 | 대상 및 구성 | 핵심 점검 지표 |
|---|---|---|
| 1단계: 인바운드 제거와 웹 리소스 ZTNA화 | 단말 에이전트 설치 없이도 쓸 수 있는 브라우저 기반 리소스(사내 위키, 어드민)부터 cloudflared 터널 + SSO(Access) 뒤에 배치하고 공인 인바운드 포트 폐쇄 |
외부 포트 스캔 노출 0건, 브라우저 SSO 로그인 성공률 |
| 2단계: 인프라·파일럿 그룹 단말 설치 (Split Tunnel) | 보안팀·플랫폼 엔지니어 10~20명 단말에 에이전트 설치. **포함 모드(Include Split Tunnel)**로 사내 비충돌 CIDR(10.240.0.0/16)과 Split-Horizon DNS만 연결 |
가정용 공유기·프린터 충돌 0건, DERP 폴백 및 서브넷 라우터 지연 시간 |
| 3단계: 개발자 CA 번들 자동화 및 포스처 경고 모드 | MDM으로 사내 루트 CA와 통합 PEM 번들·셸 환경 변수(NODE_EXTRA_CA_CERTS 등)를 스크립트로 먼저 배포. 디바이스 포스처는 즉시 차단 대신 감사/경고(Audit/Log) 모드로 운영해 미준수 단말 사전 안내 |
CLI·Docker·코딩 에이전트 인증서 오류 0건, 포스처 충족률 98% 이상 도달 |
| 4단계: 전사 확대, 포스처 차단 전환 및 레거시 VPN 폐기 | 포스처 규칙을 강제 차단(Enforce)으로 전환하고 SCIM 자동 회수 연동 검증 후 기존 레거시 VPN 장비의 전원과 인바운드 포트를 영구히 내림 | 미인증/미준수 기기 접속 차단 100%, 레거시 VPN 장비 제거 완료 |
실무 장면
가온로보틱스: 재택근무 중 홈 네트워크 마비와 사내 도메인 노출을 해결한 Split Tunnel·DNS 재설계
물류 로봇 제어 AI를 개발하는 '가온로보틱스'는 사내 GPU 서버와 개발 데이터베이스 보안을 강화하기 위해 메시 VPN을 도입해 개발자 80명의 맥북에 설치했다. 그런데 배포 당일 오후, 재택근무 중인 개발자 절반에게서 "VPN만 켜면 집 와이파이 공유기 설정 페이지와 무선 프린터, 로컬 로봇 시뮬레이터(192.168.0.x)가 전부 먹통이 된다"는 불만이 쏟아졌다. 동시에 보안팀은 외부 버그바운티 연구원으로부터 "공인 DNS를 조회해 보니 gpu-h100-01.gaon.io, hr-db.gaon.io 같은 사내 서버 이름과 사설 IP 목록이 외부에서 전부 보인다"는 제보를 받았다.
인프라 팀이 확인해 보니 두 가지 설계 실수가 있었다. 첫째, 과거 온프레미스 서버실 대역이 192.168.0.0/24였다는 이유로 서브넷 라우터가 192.168.0.0/16 전체를 VPN으로 라우팅하도록 설정되어 있어 개발자 집의 공유기 대역(192.168.0.0/24)과 정면으로 충돌하고 있었다. 둘째, 사내 전용 서버의 A 레코드들을 편의상 외부 공인 DNS 영역에 그대로 적어 두고 있었다.
팀은 주말 동안 네트워크와 DNS를 다음과 같이 바로잡았다.
- 비충돌 대역 전환 및 정밀 Include Split Tunnel: 클라우드 VPC 대역은
10.220.0.0/16으로 정리하고, 구 서버실의 장비들은 VPN의 고유 CGNAT 주소(100.64.x.x) 및 전용 비충돌 서브넷 라우팅으로만 접근하게 바꾸어192.168.0.0/16경로 광고를 완전히 제거했다. 분할 터널링은 오직10.220.0.0/16과100.64.0.0/10만 터널을 타는 포함(Include) 모드로 고정했다. - Split-Horizon DNS 구성: 외부 공인 DNS에서 사내 전용
A레코드 40여 개를 전량 삭제했다. 대신 사내 VPC 내부의 Private DNS에만*.corp.gaon.io를 등록하고, VPN 클라이언트의 Split DNS 기능으로corp.gaon.io도메인 질의만 터널 내부 DNS 리졸버로 전달하게 설정했다. - 최소 권한 ACL 적용: 모든 개발자가 모든 서버의 모든 포트에 접근하던 기본 허용 규칙을 지우고, 로봇 펌웨어 팀은 시뮬레이션 서버에, AI 모델 팀은 GPU 클러스터(
tag:gpu-cluster)에만 접근하도록 역할별 ACL을 분리했다.
월요일 아침, 재택근무 개발자들의 로컬 장비와 프린터는 VPN을 켠 상태에서도 완벽하게 작동했고, 외부 인터넷에서 사내 서버 이름을 조회하면 아무 정보도 노출되지 않게 되었다.
누리클라우드: ZTNA 도입 시 터진 코딩 에이전트 SSL 오류와 퇴사자 세션 차단
엔터프라이즈 AI 솔루션을 구축하는 '누리클라우드'는 ISMS-P 및 고객사 보안 감사 기준을 맞추기 위해 Cloudflare Zero Trust(Tunnel + Access + Gateway TLS 검사)를 도입했다. 사내 어드민 웹페이지들은 cloudflared 아웃바운드 터널로 옮겨 AWS 보안 그룹의 인바운드 포트를 모두 닫는 데 성공했다.
그러나 개발자 노트북에 ZTNA 클라이언트를 켜고 TLS 검사를 활성화하자마자 개발팀 전체가 멈춰 섰다. 크롬 브라우저에서는 모든 사이트가 잘 열렸지만, 터미널에서 돌리는 Node.js 서버, Python pip/poetry, 그리고 개발자들이 매일 쓰는 AI 코딩 에이전트 CLI가 외부 LLM API를 부를 때마다 Error: unable to get local issuer certificate를 내며 실패한 것이다. 일부 급한 개발자들이 .zshrc에 export NODE_TLS_REJECT_UNAUTHORIZED=0을 넣기 시작하자 보안팀에 비상이 걸렸다.
보안 엔지니어와 플랫폼 팀은 즉시 표준 단말 구성 패키지를 만들어 MDM으로 배포했다.
| 문제 지점 | 임시방편(금지) | 누리클라우드의 표준 해결책 |
|---|---|---|
| Node.js 및 코딩 에이전트 CLI | NODE_TLS_REJECT_UNAUTHORIZED=0 (TLS 검증 무력화) |
MDM으로 배포된 사내 루트 CA 경로를 가리키도록 표준 셸 프로파일에 export NODE_EXTRA_CA_CERTS="/Library/Application Support/NuriZTNA/nuri-root-ca.pem" 설정 |
| Python·curl·Git 도구 | pip --trusted-host, git http.sslVerify=false |
시스템 기본 CA 번들과 사내 루트 CA를 결합한 nuri-combined-ca-bundle.pem을 자동 생성해 SSL_CERT_FILE, REQUESTS_CA_BUNDLE, git http.sslCAInfo에 지정 |
| 인증서 핀닝(Pinning) 앱 | ZTNA 에이전트 자체를 종료함 | Apple OS 업데이트, Docker 레지스트리 일부 등 엄격한 인증서 핀닝 도메인을 ZTNA 콘솔의 Do Not Inspect(복호화 예외) 목록에 정식 등록 |
| 외주 인력 계약 종료 시 권한 회수 | 퇴사 일주일 뒤 수동으로 계정 정리 | 사내 IdP(Okta)와 ZTNA를 SCIM으로 연동해 인사 시스템에서 퇴사 처리되는 즉시 진행 중인 터널 세션과 Access 토큰이 1분 안에 자동 폐기되게 구성 |
이 조치로 개발자들은 단 한 줄의 보안 우회 옵션도 쓰지 않은 채 평소처럼 코딩 에이전트와 패키지 매니저를 사용할 수 있었고, 보안 감사의 단말 통제·퇴사자 즉시 회수 항목도 만점으로 통과했다.
자주 틀리는 지점
- "사내 VPN에 접속한 사용자의 IP는 사내 대역(
10.x.x.x)이니 모든 사내 서버 방화벽을 그 대역 전체에 열어 두어도 안전하다." 이것이 바로 전통 경계형 VPN의 가장 큰 약점인 암묵적 신뢰와 횡적 이동(Lateral Movement) 위험입니다. 단말 하나만 침해되어도 내부망 전체가 뚫리므로, 제로트러스트 원칙에 따라 신원·기기 상태를 검증하고 필요한 리소스·포트에만 최소 권한(ACL)으로 접근을 열어야 합니다. - "외부에서 사내 웹 서버에 접속하게 하려면 Cloudflare Tunnel을 쓰더라도 공유기나 보안 그룹에서 인바운드 443 포트는 반드시 열어야 한다." 틀렸습니다.
cloudflared는 사내 서버 안에서 바깥쪽 Cloudflare 엣지로 먼저 아웃바운드 연결을 맺어 두고 그 터널로 요청을 받아오므로, 사내 방화벽의 인바운드 포트는 단 하나도 열 필요가 없습니다. - "사내 VPC 대역으로
192.168.0.0/16이나192.168.1.0/24를 쓰면 친숙해서 관리하기 좋다." 가정용 공유기와 카페 와이파이의 대다수가192.168.0.0/24또는192.168.1.0/24를 쓰기 때문에, 재택근무자가 VPN을 켜는 순간 로컬 네트워크와 사내 대역이 충돌해 사내 서버나 로컬 기기 접속이 먹통이 됩니다. - "사내 전용 서버 도메인도 사설 IP(
10.x.x.x)를 가리키기만 하면 외부에서 접속은 못 하니 공인 DNS에 등록해 두어도 아무 문제 없다." 사설 IP로 직접 접속은 못 하더라도, 공인 DNS에 사내 호스트 이름과 IP 대역 구조를 노출하면 공격자에게 내부 자산 목록과 네트워크 지도를 공짜로 알려 주는 셈이 됩니다. 반드시 Split-Horizon DNS(프라이빗 DNS + 조건부 포워딩)로 숨겨야 합니다. - "사내 ZTNA 도입 후 Node.js나 Python에서 SSL 인증서 오류가 나면
NODE_TLS_REJECT_UNAUTHORIZED=0이나verify=False를 켜서 해결한다." 이는 인증서 검증 자체를 꺼 버려 중간자 공격에 무방비로 노출시키는 치명적 보안 위반입니다. 반드시 사내 루트 CA 인증서를NODE_EXTRA_CA_CERTS나 통합 번들(SSL_CERT_FILE,REQUESTS_CA_BUNDLE)에 등록해 정상 검증을 통과하게 해야 합니다. - "퇴사자의 이메일 계정만 지우면 이미 노트북에 연결되어 있는 VPN 터널도 알아서 즉시 끊긴다." 장기 인증서나 별도 로컬 계정을 쓰는 VPN은 이메일이 지워져도 기존 터널이나 인증서가 만료될 때까지 살아 있을 수 있습니다. IdP와 ZTNA를 SSO·SCIM으로 연동하고 짧은 세션 주기와 즉시 세션 폐기(Revoke)가 작동하도록 구성해야 합니다.
한눈에 정리
| 핵심 주제 | 시험과 실무에서 기억할 핵심 문장 |
|---|---|
| 전통 VPN vs ZTNA | 전통 VPN은 네트워크 진입 후 횡적 이동(Lateral Movement)에 취약하다. ZTNA(NIST SP 800-207)는 위치가 아닌 신원·포스처 기반으로 개별 리소스만 최소 권한 허용한다 |
아웃바운드 터널 (cloudflared) |
사내에서 엣지로 먼저 아웃바운드 연결을 맺어 인바운드 오픈 포트 0개로 서비스를 연결한다. 운영 환경은 2개 이상 커넥터 복제본으로 HA를 구성한다 |
| 메시 VPN (WireGuard·Tailscale) | 제어 평면과 데이터 평면(P2P, 막히면 DERP 암호화 중계)을 분리하고, 에이전트 미설치 자원(RDS 등)은 서브넷 라우터로 연결하며 태그 기반 ACL로 포트를 제한한다 |
| 분할 터널과 RFC1918 충돌 | 가정용 공유기 대역(192.168.0.0/24, 192.168.1.0/24)과 겹치지 않는 사내 CIDR(10.240.0.0/16 등)을 쓰고, 필요한 대역만 Include 모드로 라우팅한다 |
| Split-Horizon DNS | 사내 전용 도메인(*.internal)은 프라이빗 DNS에만 등록하고 ZTNA 클라이언트의 조건부 포워더로만 질의해 외부 노출을 막는다 |
| IdP·SCIM·디바이스 포스처 | SSO + 피싱 저항 MFA로 신원을 확인하고, OS 패치·디스크 암호화·MDM/EDR 포스처를 강제하며, 퇴사 시 SCIM으로 계정도 세션도 즉시 자동 회수한다 |
| 단말 CA 인증서와 CLI 도구 | NODE_TLS_REJECT_UNAUTHORIZED=0 등 검증 비활성화는 절대 금지. NODE_EXTRA_CA_CERTS(추가)와 통합 PEM 번들을 가리키는 SSL_CERT_FILE·REQUESTS_CA_BUNDLE을 배포한다 |
공식 문서로 확인하기
- NIST — SP 800-207 Zero Trust Architecture: 제로트러스트 7대 강령과 PE·PA·PEP 논리 아키텍처 표준.
- Cloudflare Zero Trust — Cloudflare Tunnel (
cloudflared): 아웃바운드 전용 터널 작동 원리와 고가용성 복제본 구성. - Cloudflare Zero Trust — Split Tunnels & Local Domain Fallback: Include/Exclude 분할 터널링과 사내 DNS 조건부 라우팅.
- Cloudflare Zero Trust — Install Root Certificate for Developers: Node.js, Python, Git 등 개발자 도구에 사내 루트 CA를 등록하는 공식 절차.
- Tailscale Docs — How Tailscale Works, Subnet Routers & ACLs: WireGuard 메시 데이터 평면, DERP 중계, 서브넷 라우터와 ACL 설계.
셀프 체크
4-1. 전통적인 경계형 VPN과 NIST SP 800-207 기반 제로트러스트 네트워크 접근(ZTNA)이 "인증 통과 후 부여하는 접근 범위"에서 어떻게 다른지 비교하고, 단말 한 대가 침해되었을 때 전통 VPN에서 발생하는 대표적인 확산 위험을 쓰라.
4-2. 사내 프라이빗 서브넷에 있는 관리 웹 서버를 외부의 허가된 직원에게만 열려고 할 때, cloudflared(Cloudflare Tunnel)를 사용하면 왜 사내 방화벽(보안 그룹)의 인바운드 포트를 단 하나도 열지 않아도 외부 접속이 가능한지 연결 수립 방향으로 설명하라.
4-3. 사내 클라우드 VPC 대역을 192.168.1.0/24로 만들고 전 직원 노트북에 VPN을 배포했더니, 재택근무 중인 직원들이 사내 서버에 접속하지 못하거나 집의 무선 프린터가 끊기는 현상이 발생했다. 이 현상의 원인과 근본적인 네트워크 대역 설계 해법을 설명하라.
4-4. 사내 전용 개발 데이터베이스 도메인(db.dev.internal.corp)을 외부 공인 DNS에 사설 IP로 등록해 두었을 때의 보안 문제점을 설명하고, 이를 해결하는 분할 시야 DNS(Split-Horizon DNS) 구성을 두 문장으로 설명하라.
4-5. 사내 ZTNA의 TLS 검사(TLS Inspection)를 켠 뒤 개발자 노트북에서 Node.js 기반 AI 코딩 에이전트와 Python requests가 인증서 오류로 실패할 때, 왜 NODE_TLS_REJECT_UNAUTHORIZED=0을 쓰면 안 되는지 설명하고, Node.js와 Python에 각각 설정해야 하는 올바른 환경 변수 이름과 파일 구성 차이를 쓰라.
4-6. 설계 과제: 외주 개발자 15명에게 사내 스테이징 PostgreSQL(에이전트 직접 설치 불가한 AWS RDS, 포트 5432)에 대한 접근 권한만 부여하고, 다른 사내 서버(SSH 22 등)에는 접근하지 못하게 하며, 계약 종료 시 즉시 접속을 끊으려 한다. 메시 VPN 또는 ZTNA의 (1) RDS 연결 구성 요소, (2) 포트·대상 제한 수단, (3) 퇴사 시 자동 회수 프로토콜을 각각 무엇으로 설계해야 하는지 서술하라.
실습 과제
실습
사내 ZTNA·메시 VPN 전환 설계도와 최소 권한 ACL 작성하기 (50분)
- 현재 사내에 다음 세 가지 내부 자산이 있다고 가정한다:
- 사내 운영 어드민 웹 (
http://10.240.10.20:3000, 브라우저로만 접속) - 스테이징 RDS PostgreSQL (
10.240.20.50:5432, 에이전트 설치 불가) - AI 추론 GPU 서버 (
10.240.30.10, SSH 22 및 API 8000)
- 사내 운영 어드민 웹 (
- 각 자산을 인바운드 공인 포트 개방 없이 연결하기 위해
cloudflared터널과 메시 VPN 서브넷 라우터 중 무엇을 어떻게 배치할지(고가용성 복제본 포함) 표로 설계한다. group:ops(운영 어드민),group:backend(RDS 5432 + 추론 API 8000),group:ml-infra(GPU 서버 SSH 22 + API 8000) 세 그룹에 대해 최소 권한만 허용하는 ACL 규칙(JSON 형식)을 작성한다.
완료 기준: 세 자산 모두 공인 인바운드 포트가 0개이며, RDS는 서브넷 라우터(또는 프라이빗 터널)를 통해 연결되고, ACL에서 group:backend가 GPU 서버의 22번 포트(SSH)에는 접근할 수 없도록 분리되어 있다.
실습
전사 단말 ZTNA 배포 체크리스트와 개발자 CA 환경 스크립트 만들기 (40분)
- 재택근무자의 홈 네트워크(
192.168.0.0/16)와 충돌하지 않도록 분할 터널링(Include 모드)에 넣을 CIDR 목록과 Split-Horizon DNS 도메인(*.internal.company.io) 포워딩 규칙을 정의한다. - 접속 허용 전 검사할 디바이스 포스처(Device Posture) 필수 항목 4가지와 IdP 연동(SSO, MFA, SCIM) 조건을 적는다.
- 사내 루트 CA(
company-root-ca.pem)를 시스템 기본 번들(/etc/ssl/cert.pem)과 합쳐combined-ca-bundle.pem을 만들고, Node.js, Python, curl, Git이 TLS 검증을 끄지 않고도 이 인증서를 신뢰하게 설정하는 macOS/Linux 셸 스크립트(setup-dev-ca.sh)를 작성한다.
완료 기준: 스크립트에 NODE_TLS_REJECT_UNAUTHORIZED=0이나 -k 같은 보안 무력화 코드가 전혀 없고, NODE_EXTRA_CA_CERTS와 통합 번들 기반 SSL_CERT_FILE/REQUESTS_CA_BUNDLE/http.sslCAInfo가 정확히 구분되어 있다.