가장 안전한 포트는 닫힌 포트입니다. 그리고 가장 안전한 서버는, 인터넷에서 그 존재조차 보이지 않는 서버입니다.
들어가며: 문이 없으면 두드릴 수 없다
5장에서 우리는 공격면을 지도로 그렸고, 그 지도의 목적이 축소임을 확인했습니다. 이 장은 공격면 축소의 가장 극적이고 강력한 형태를 다룹니다. 인터넷에 열린 인바운드 포트를 0개로 만드는 것입니다.
이것이 왜 그토록 강력한지 다시 짚어 보겠습니다. 1장에서 본 모든 무차별 공격 — SSH 무차별 대입, 포트 스캔, 웹 취약점 탐색 — 은 전제가 하나 있습니다. 공격자가 두드릴 수 있는 열린 포트가 있어야 한다는 것입니다. 22번 포트가 인터넷에 열려 있으니 SSH 무차별 대입이 가능하고, 80·443번이 열려 있으니 웹 공격이 도달합니다.
그렇다면 발상을 뒤집어 봅시다. 만약 서버가 인터넷을 향해 단 하나의 포트도 열지 않는다면? 공격자의 스캐너는 닫힌 문 앞에 설 뿐입니다. 두드릴 문이 없으니 무차별 대입이 성립하지 않고, 응답하는 서비스가 없으니 취약점 탐색도 헛수고가 됩니다. 서버는 인터넷에서 사실상 보이지 않는 존재가 됩니다.
2026년 현재, 새로 노출된 IP가 자동 스캐너에 발견되어 첫 공격 트래픽을 받기까지는 보통 몇 분이면 충분합니다. 인터넷 전체는 쉬지 않고 훑여지고 있고, "아직 아무도 내 작은 서버를 모를 것"이라는 가정은 더 이상 성립하지 않습니다. 그래서 "포트를 닫는다"는 이 장의 처방은 유행을 타지 않는, 가장 확실한 공격면 축소입니다.
"하지만 웹 서비스를 하려면 포트를 열어야 하지 않나?"라고 물으실 것입니다. 바로 여기서 Cloudflare Tunnel의 발상이 빛납니다. 핵심 아이디어는 이것입니다. 외부에서 안으로 들어오는 연결(인바운드)을 받는 대신, 서버가 먼저 바깥으로 나가는 연결(아웃바운드)을 맺어 두는 것입니다. 서버는 인터넷에서 오는 연결을 기다리지 않습니다. 대신 서버 스스로가 Cloudflare로 나가는 안전한 통로를 만들고, 모든 트래픽이 그 통로를 통해 흐르게 합니다.
이 장에서는 이 발상을 실제로 구현하는 방법을 단계별로 다룹니다. Cloudflare Tunnel로 웹 서비스를 포트 노출 없이 제공하는 법, Zero Trust Access로 SSH 22번을 인터넷에서 완전히 제거하는 법, 그리고 무엇보다 — 이 모든 과정에서 자기 자신을 서버에서 잠가 버리지 않는 안전한 적용 순서를 다룹니다. 마지막 항목은 단순한 주의사항이 아니라, 이 장에서 가장 중요한 실전 지식입니다.
이 장 전체에 적용되는 안전 원칙: 방화벽과 원격 접속 설정을 변경하는 작업은, 한 번의 실수로 자기 자신을 서버에서 영구히 잠가 버릴 수 있습니다. 이 장의 모든 변경은 반드시 별도의 작동하는 접속 경로를 확보한 상태에서, 되돌릴 수 있는 안전장치를 켜 두고 진행해야 합니다. 구체적인 방법은 6.5에서 상세히 다룹니다. 그 절을 읽기 전에는 실제 변경을 시작하지 마십시오.
6.1 왜 인바운드 포트가 위험한가
먼저 우리가 없애려는 것의 정체를 분명히 하겠습니다. 인바운드 포트, 즉 외부에서 서버로 들어오는 연결을 받기 위해 열어 둔 포트는 왜 본질적으로 위험할까요?
열린 포트는 영구적인 초대장이다
인터넷을 향해 열린 포트는, 전 세계 누구에게나 보내는 영구적인 초대장과 같습니다. "여기 연결을 받습니다"라고 24시간 광고하는 것입니다. 1장에서 보았듯, 이 초대장은 즉시 발견되고, 발견되는 순간 자동화된 공격이 도착합니다. 당신이 의도한 정당한 사용자뿐 아니라, 전 세계의 모든 봇과 공격자가 그 초대에 응합니다.
문제는, 포트 뒤에서 응답하는 서비스가 인증보다 먼저 노출된다는 점입니다. SSH를 예로 들면, 공격자가 비밀번호를 모르더라도, SSH 서비스 자체와는 이미 대화를 시작할 수 있습니다. 그 대화 과정에서 SSH 소프트웨어의 취약점을 노릴 수 있고, 무차별 대입으로 인증을 시도할 수 있으며, 서비스의 버전 정보를 수집할 수 있습니다. 인증은 마지막 관문일 뿐, 그 앞에서 이미 많은 공격면이 노출됩니다.
웹 서비스도 마찬가지입니다. 80·443번 포트가 열려 있으면, 인증을 요구하는 관리 페이지조차 일단 그 존재가 노출되고, 로그인 시도와 취약점 탐색의 대상이 됩니다.
이것이 추상적인 걱정이 아님은 실제 사건이 증명합니다. 인터넷에 노출된 서비스에서 심각한 취약점이 터지면, 공격은 전 세계로 동시에 번집니다. MOVEit Transfer의 취약점(CVE-2023-34362)은 Cl0p 랜섬웨어 조직이 대량으로 악용해 수많은 기관의 데이터를 한꺼번에 털어 갔고, Citrix Bleed(CVE-2023-4966)는 패치가 이미 나와 있었는데도 미적용 장비들이 대규모로 침해됐습니다. 두 사건의 공통점은 단순합니다 — 그 서비스가 인터넷에서 닿을 수 있었다는 것. 어떤 취약점이 내일 공개될지는 아무도 모르지만, 닫힌 포트 뒤의 서비스는 그 폭풍이 와도 직접 닿지 않습니다. 실제로 악용 중인 취약점은 미국 CISA가 KEV(실제 악용 취약점 목록)으로 공개하는데, 그 목록의 상당수가 "인터넷에 노출된 게이트웨이·전송 솔루션"이라는 점은 우연이 아닙니다.
"포트를 옮기면 되지 않나"의 한계
흔한 대응으로, SSH 포트를 22번이 아닌 다른 번호로 옮기는 방법이 있습니다. 이것은 1장에서 본 "모호함을 통한 보안"의 전형입니다. 자동화된 봇이 22번만 노린다면, 다른 포트로 옮기면 그 무차별 대입의 상당수를 피할 수 있는 것은 사실입니다.
그러나 이것은 근본적 해결이 아닙니다. 5장에서 보았듯, 전수 스캐너는 모든 포트를 훑을 수 있고, 서비스의 지문을 채취하여 SSH가 어느 포트에 있든 찾아냅니다. 포트를 옮기는 것은 가장 게으른 봇을 떨어내는 작은 잡음 감소일 뿐, 결연한 공격자나 정교한 스캐너 앞에서는 무력합니다. 문의 번호를 바꾼다고 문 자체가 사라지는 것은 아닙니다.
근본적 해결은 문을 옮기는 것이 아니라 문을 없애는 것입니다. 인터넷을 향한 인바운드 포트 자체를 닫아 버리는 것. 그것이 이 장의 목표입니다.
6.2 Cloudflare Tunnel의 원리: 아웃바운드로 뒤집기
이제 핵심 기술인 Cloudflare Tunnel의 작동 원리를 이해해 보겠습니다. 설정 방법을 익히기 전에 원리를 정확히 아는 것이 중요합니다. 원리를 알아야 응용할 수 있고, 문제가 생겼을 때 진단할 수 있기 때문입니다.
연결의 방향을 뒤집는다
전통적인 구조에서 트래픽의 흐름은 이렇습니다. 외부 사용자가 당신의 서버 IP로 연결을 시도하고, 서버는 열린 포트에서 그 연결을 받습니다. 즉, 연결의 시작점이 외부이고, 서버는 받는 쪽입니다. 이 구조에서는 서버가 인바운드 포트를 열어 두어야 하고, 그 포트가 1장의 공격에 노출됩니다.
Cloudflare Tunnel은 이 방향을 뒤집습니다. 서버에서 cloudflared라는 작은 프로그램(데몬 — 화면 없이 백그라운드에서 늘 돌아가는 상주 프로그램)을 실행하면, 이 프로그램이 서버에서 Cloudflare를 향해 바깥으로 연결을 맺습니다. 즉, 연결의 시작점이 서버 자신이 됩니다. 이 연결은 아웃바운드(나가는 방향)이므로, 인바운드 포트를 열 필요가 없습니다. 이 기능 일체는 Cloudflare가 무료 등급을 포함해 제공하며, 공식 문서는 Cloudflare One(Zero Trust) 공식 문서에 정리되어 있습니다(이 장의 모든 설정의 1차 출처).
이렇게 서버와 Cloudflare 사이에 안전한 통로(터널)가 만들어지면, 외부 사용자의 요청은 다음 경로로 흐릅니다. 사용자는 Cloudflare에 접속하고, Cloudflare는 미리 맺어 둔 터널을 통해 그 요청을 당신의 서버로 전달하며, 서버의 응답도 같은 터널을 거쳐 사용자에게 돌아갑니다. 사용자는 당신의 서버와 직접 연결하지 않습니다. 항상 Cloudflare를 거칩니다.
무엇이 달성되는가
이 구조가 달성하는 것은 세 가지입니다.
첫째, 인바운드 포트가 0개가 됩니다. 서버는 외부에서 오는 연결을 전혀 받지 않습니다. 오직 자신이 시작한 아웃바운드 터널만 유지합니다. 따라서 인터넷을 향해 열린 포트가 하나도 없게 됩니다. 5장의 도구로 외부에서 포트 스캔을 해도, 열린 포트가 보이지 않습니다.
둘째, 오리진 IP가 숨겨집니다. 사용자가 항상 Cloudflare를 거치므로, 당신의 실제 서버 IP(오리진 IP)는 외부에 드러나지 않습니다. 외부에서 보이는 것은 Cloudflare의 IP뿐입니다. 이것은 6.4에서 다룰 오리진 IP 보호의 핵심입니다.
셋째, Cloudflare의 보호 계층을 자동으로 얻습니다. 모든 트래픽이 Cloudflare를 거치므로, Cloudflare가 제공하는 보호 — 대규모 트래픽 공격(DDoS) 완화, 악성 트래픽 필터링 등 — 가 당신의 서버 앞에 자연스럽게 놓입니다.
무엇을 신뢰하게 되는가
이 구조의 트레이드오프도 정직하게 짚어야 합니다. 이 방식은 Cloudflare를 신뢰 경로의 중심에 두는 것입니다. 모든 트래픽이 Cloudflare를 거치므로, Cloudflare가 트래픽을 볼 수 있는 위치에 있게 됩니다. 이것은 대부분의 조직에게 합리적인 트레이드오프입니다. 직접 운영하는 것보다 전문 업체의 인프라가 더 안전한 경우가 많고, 인바운드 포트 0개와 오리진 IP 은폐라는 이득이 크기 때문입니다. 다만 극도로 민감한 데이터를 다루어 어떤 중간 경로도 허용할 수 없는 특수한 경우라면, 이 트레이드오프를 별도로 검토해야 합니다. 보안 설계는 항상 자신의 위협 모델(2장)에 비추어 판단해야 합니다.
6.3 Cloudflare Tunnel 설정: 웹 서비스부터
이제 실제 설정으로 들어갑니다. 가장 일반적인 경우인 웹 서비스를 터널로 제공하는 것부터 다루겠습니다. 다만 6.5의 안전 원칙을 먼저 숙지하지 않았다면, 실제 적용 전에 그 절을 반드시 읽으십시오. 특히 SSH를 터널로 옮기는 작업은 안전 순서가 생명입니다.
사전 준비
Cloudflare Tunnel을 쓰려면, 도메인이 Cloudflare를 통해 관리되고 있어야 합니다(도메인의 네임서버가 Cloudflare를 향하도록 설정). 그리고 서버에 cloudflared를 설치합니다. 설치 방법은 운영체제에 따라 다르며, 패키지 버전이 자주 바뀌므로 Cloudflare One 공식 문서의 최신 설치 안내를 그대로 따르는 것이 가장 정확합니다(아래 명령들도 버전에 따라 옵션이 달라질 수 있으니, 막히면 항상 공식 문서를 기준으로 삼으십시오). 설치 후 다음과 같은 흐름으로 진행합니다.
인증과 터널 생성
# 1) cloudflared를 Cloudflare 계정에 인증
# 브라우저가 열리며 로그인 및 도메인 선택을 요청합니다.
cloudflared tunnel login
# 2) 새 터널 생성 (이름은 원하는 대로)
cloudflared tunnel create my-tunnel
# 위 명령은 터널의 고유 ID와 자격 증명 파일을 생성합니다.
# 자격 증명 파일의 경로를 기억해 두십시오 (보통 ~/.cloudflared/ 아래).
# 3) 생성된 터널 목록 확인
cloudflared tunnel list
터널을 생성하면, 그 터널을 인증하는 자격 증명 파일이 만들어집니다. 이 파일은 터널의 열쇠이므로 안전하게 보관해야 합니다. 외부에 노출되거나 유출되면 안 됩니다.
터널 설정 파일 작성
터널이 어떤 요청을 서버의 어떤 로컬 서비스로 전달할지를 설정 파일에 정의합니다. 예를 들어 config.yml은 다음과 같은 형태입니다.
# ~/.cloudflared/config.yml
tunnel: <터널 ID>
credentials-file: /home/user/.cloudflared/<터널 ID>.json
ingress:
# example.com으로 오는 요청을 로컬 웹 서버(127.0.0.1:8080)로 전달
- hostname: example.com
service: http://127.0.0.1:8080
# 그 외 모든 요청은 거부 (필수: 마지막에 catch-all 규칙)
- service: http_status:404
여기서 핵심은, 서버의 웹 서비스가 이제 로컬 인터페이스(127.0.0.1)에만 바인딩되어도 된다는 점입니다. 외부에서 직접 접근할 필요가 없으므로, 웹 서버가 0.0.0.0이 아니라 127.0.0.1에서만 듣게 하면, 설령 방화벽에 구멍이 있어도 외부에서 직접 닿을 수 없습니다. 이것은 5장에서 강조한 바인딩 점검의 적용입니다.
DNS 연결과 터널 실행
# 4) 도메인을 터널로 라우팅 (DNS 레코드 자동 생성)
cloudflared tunnel route dns my-tunnel example.com
# 5) 터널 실행 (테스트)
cloudflared tunnel run my-tunnel
# 6) 정상 동작을 확인했다면, 서비스로 등록하여 상시 실행
sudo cloudflared service install
이 단계까지 마치면, example.com으로 오는 요청이 Cloudflare를 거쳐 터널을 통해 당신의 로컬 웹 서버로 전달됩니다. 그리고 이제 — 이것이 핵심입니다 — 서버의 방화벽에서 80·443번 인바운드 포트를 닫을 수 있습니다. 외부 트래픽은 모두 아웃바운드 터널을 통해 들어오므로, 인바운드 웹 포트가 더 이상 필요 없기 때문입니다. (단, 포트를 닫기 전에 6.5의 안전 절차를 따르십시오.)
6.4 오리진 IP 숨기기: 터널을 우회당하지 않기
Cloudflare Tunnel을 쓰면 오리진 IP가 숨겨진다고 했습니다. 그러나 이 은폐는 자동으로 완벽해지지 않습니다. 오리진 IP가 다른 경로로 새어 나가면, 공격자는 Cloudflare를 우회하여 서버를 직접 공격할 수 있습니다. 이것을 막는 것이 이 절의 주제입니다.
왜 오리진 IP 은폐가 중요한가
오리진 IP가 노출되면, 6.2에서 얻은 보호가 무너집니다. 공격자가 실제 서버 IP를 알면, Cloudflare를 거치지 않고 서버에 직접 연결을 시도할 수 있습니다. 그러면 인바운드 포트를 닫아 둔 것이 첫 번째 방어선이 되지만, 만약 어떤 포트라도 직접 접근이 가능하다면 Cloudflare의 보호(DDoS 완화, 필터링)를 모두 건너뛰고 공격할 수 있게 됩니다.
오리진 IP가 새는 흔한 경로
1장 1.1의 패시브 DNS에서 이미 한 경로를 보았습니다. 여기서는 흔한 누출 경로를 정리합니다.
- 과거 DNS 기록. Cloudflare를 적용하기 전에 도메인이 실제 서버 IP를 직접 가리켰다면, 그 기록이 패시브 DNS에 영구히 남아 있을 수 있습니다.
- 이메일 헤더. 서버에서 직접 발송한 이메일의 헤더에 서버의 실제 IP가 담길 수 있습니다.
- 서버가 시작하는 외부 연결. 서버가 외부로 보내는 요청(웹훅, 외부 API 호출 등)의 출발지 IP가 노출될 수 있습니다.
- 별도 서브도메인. 메인 도메인은 Cloudflare 뒤에 있지만, 어떤 서브도메인이 Cloudflare를 거치지 않고 직접 서버 IP를 가리키고 있을 수 있습니다(5장의 그림자 서브도메인).
- SSL 인증서와 콘텐츠 단서. 서버에 직접 접속했을 때 응답하는 인증서나 고유한 콘텐츠가, Shodan·Censys 같은 인터넷 전수 검색 엔진을 통해 IP와 연결될 수 있습니다. 발급된 인증서 자체도 crt.sh에서 누구나 조회할 수 있어, 무심코 만든 서브도메인 인증서 하나가 오리진을 가리키는 단서가 되기도 합니다.
오리진 IP를 지키는 방법
가장 근본적인 방어는, 서버가 Cloudflare의 트래픽만 받도록 강제하는 것입니다. 즉, 설령 누군가 오리진 IP를 알아내더라도, 서버가 Cloudflare 이외의 출처에서 오는 직접 연결을 거부하게 만드는 것입니다.
Tunnel을 쓰는 경우에는 이것이 자연스럽게 달성됩니다. 인바운드 포트를 모두 닫고 아웃바운드 터널만 유지하면, 오리진 IP를 알아도 직접 연결할 포트가 없기 때문입니다. 이것이 Tunnel 방식의 가장 큰 장점 중 하나입니다.
만약 Tunnel이 아니라 전통적인 방식(Cloudflare를 프록시로 두되 서버가 인바운드 포트를 여는 방식)을 쓴다면, 방화벽에서 Cloudflare의 IP 대역에서 오는 연결만 허용하고 그 외 모든 직접 연결을 차단해야 합니다. 이렇게 하면 오리진 IP가 노출되어도 공격자의 직접 연결이 방화벽에서 막힙니다. Cloudflare는 자사의 IP 대역 목록을 공개하므로, 이를 방화벽 허용 규칙에 반영할 수 있습니다.
추가로, 위에서 본 누출 경로들을 점검하고 막아야 합니다. 과거 DNS 기록이 노출된 정황이 있다면 서버 IP 변경을 고려하고, 이메일은 별도의 발송 서비스를 통해 보내 서버 IP가 헤더에 남지 않게 하며, 모든 서브도메인이 빠짐없이 Cloudflare를 거치는지 5장의 방법으로 확인하십시오.
6.5 자기 자신을 잠그지 않는 안전한 적용 순서
이제 이 장에서 가장 중요한 부분에 도달했습니다. 지금까지 배운 것을 실제로 적용할 때, 자기 자신을 서버에서 영구히 잠가 버리는 사고를 어떻게 막을 것인가입니다.
이것은 과장된 우려가 아닙니다. 원격 서버의 방화벽이나 SSH 설정을 변경하다가, 자신의 접속 경로를 끊어 버려 더 이상 서버에 들어갈 수 없게 되는 일은 실제로 매우 흔하게 일어납니다. 특히 물리적 접근이 불가능한 클라우드 서버에서, 유일한 접속 경로였던 SSH를 잘못 건드리면, 복구가 매우 번거롭거나 최악의 경우 서버를 새로 만들어야 할 수도 있습니다. 솔직히 고백하면, 나도 실무 초년에 방화벽 규칙 한 줄을 적용하는 순간 내 SSH 세션이 그대로 얼어붙는 경험을 한 적이 있습니다. 그 뒤로는 예외 없이 아래의 자동 롤백 안전장치를 먼저 걸고 작업합니다. 이 절은 그 비싼 교훈의 압축본입니다.
다행히, 몇 가지 원칙만 지키면 이 사고를 거의 완전히 예방할 수 있습니다.
원칙 1: 새 경로를 먼저 만들고, 옛 경로는 나중에 닫는다
가장 중요한 원칙입니다. 절대로 작동을 확인하지 않은 새 방법으로 갈아타면서 옛 방법을 먼저 닫지 마십시오. 순서가 핵심입니다.
올바른 순서는 항상 이렇습니다. 먼저 새로운 접속 경로(예: Cloudflare Tunnel을 통한 SSH)를 구축하고, 그것이 실제로 작동하는지 완전히 확인한 뒤에, 비로소 옛 경로(예: 직접 SSH 포트)를 닫습니다. 새 경로가 검증되기 전에 옛 경로를 닫으면, 새 경로마저 문제가 있을 때 둘 다 막혀 버립니다.
원칙 2: 두 번째 터미널을 열어 둔다
방화벽이나 SSH 설정을 변경할 때는, 변경을 수행하는 세션과 별도로, 이미 연결된 또 하나의 세션을 열어 두십시오. 만약 변경 후 새로 접속이 안 되더라도, 이미 열려 있는 두 번째 세션을 통해 변경을 되돌릴 수 있습니다.
핵심은, 변경 직후 그 두 번째 세션은 그대로 둔 채, 새로운 세 번째 세션으로 접속이 되는지 확인하는 것입니다. 새 접속이 성공하면 변경이 안전한 것이고, 실패하면 열어 둔 두 번째 세션으로 즉시 되돌립니다. 절대로 모든 세션을 닫은 채 "다음에 접속되겠지" 하고 기대하지 마십시오.
원칙 3: 자동 롤백 안전장치를 건다
더 강력한 안전장치는, 변경이 잘못되었을 때 일정 시간 후 자동으로 원상 복구되도록 미리 장치를 걸어 두는 것입니다.
예를 들어, 방화벽 규칙을 바꾸기 전에 "N분 뒤에 방화벽을 원래대로 되돌리는" 예약 작업을 걸어 둡니다. 변경 후 새 접속이 정상임을 확인하면 그 예약 작업을 취소합니다. 만약 변경이 잘못되어 접속이 끊겼다면, 아무것도 하지 않아도 N분 뒤 방화벽이 자동으로 복구되어 다시 들어갈 수 있게 됩니다.
주의: 아래는 방화벽을 직접 건드리는 명령입니다. 반드시 이미 열려 있는 두 번째 세션(원칙 2)을 띄워 둔 상태에서, 백업·예약·확인 순서를 그대로 지키며 실행하십시오. 한 줄이라도 순서가 어긋나면 자기 자신이 잠길 수 있습니다.
# 예시: 방화벽 변경 전, 10분 뒤 자동 복구 예약
# (변경이 성공하면 이 예약 작업을 반드시 취소해야 합니다)
# 1) 현재 방화벽 규칙을 백업
sudo iptables-save > /root/iptables.backup
# 2) 10분 뒤 백업을 복원하는 예약 작업 등록
echo "iptables-restore < /root/iptables.backup" | sudo at now + 10 minutes
# 3) 이제 방화벽 변경을 수행하고, 새 접속을 테스트
# - 새 접속이 정상이면: 예약 작업을 취소 (atq로 작업 번호 확인 후 atrm)
# - 새 접속이 안 되면: 아무것도 하지 않으면 10분 뒤 자동 복구됨
이 자동 롤백 패턴은 원격 서버에서 위험한 변경을 할 때 가장 신뢰할 수 있는 안전망입니다. "내가 실수하더라도 시스템이 스스로 되돌아온다"는 보장이 있으면, 변경을 훨씬 안전하게 시도할 수 있습니다.
원칙 4: 클라우드 콘솔의 비상 접속 경로를 확인해 둔다
클라우드 서버를 쓴다면, SSH가 막혔을 때 사용할 수 있는 비상 접속 경로(클라우드 제공자가 제공하는 콘솔 접속, 시리얼 콘솔 등)가 있는지 미리 확인하고, 그 사용법을 익혀 두십시오. 이것은 마지막 안전망입니다. SSH가 완전히 막혀도 이 경로로 들어가 복구할 수 있다면, 최악의 상황을 피할 수 있습니다.
안전한 SSH-over-Tunnel 전환의 전체 순서
위 원칙들을 종합하여, SSH를 Cloudflare Tunnel로 안전하게 전환하는 권장 순서를 정리하면 다음과 같습니다.
- 현재의 직접 SSH 접속을 유지한 채, Cloudflare Tunnel을 통한 SSH 접근을 추가로 구성합니다(Zero Trust 설정 포함, 6.6 참고).
- 별도의 새 세션으로 터널을 통한 SSH 접속이 정상 작동하는지 완전히 확인합니다. 이때 기존 직접 SSH 세션은 그대로 열어 둡니다.
- 터널 SSH가 확실히 작동함을 확인한 뒤, 방화벽 자동 롤백 안전장치(원칙 3)를 걸어 둡니다.
- 방화벽에서 직접 SSH 포트(22번)의 인바운드를 차단합니다.
- 또 다른 새 세션으로 터널 SSH가 여전히 작동하는지 확인합니다. 직접 SSH는 이제 막혔어야 합니다(외부에서 시도해 확인).
- 모든 것이 정상이면 자동 롤백 안전장치를 취소합니다. 문제가 있으면 안전장치가 작동하기를 기다리거나 비상 콘솔로 복구합니다.
- 최종적으로, 외부에서 포트 스캔(5장)을 수행하여 22번 포트가 더 이상 보이지 않음을 확인합니다. 직접 스캔은 Nmap으로, "남이 보는 내 모습"은 Shodan에서 내 IP를 조회해 교차 확인하면 좋습니다(자기 소유 서버·자기 IP에 한해서만).
이 순서를 지키면, 각 단계에서 항상 작동하는 접속 경로가 보장되며, 어느 단계에서 실패하더라도 되돌아갈 수 있습니다. 서두르지 마십시오. 각 확인 단계를 건너뛰지 마십시오. 보안 강화를 위한 작업이 자기 자신을 가두는 사고로 이어지는 것만큼 허무한 일은 없습니다.
6.6 Zero Trust Access: 신원으로 지키는 마지막 관문
Cloudflare Tunnel이 "어떻게 연결되는가"를 다룬다면, Zero Trust Access는 "누가 연결할 수 있는가"를 다룹니다. 이 둘을 결합하면, SSH 22번을 인터넷에서 완전히 제거하면서도 정당한 사용자는 안전하게 접속할 수 있습니다.
Zero Trust의 핵심 사상
Zero Trust(제로 트러스트)는 이 책의 두 번째 원칙, "침해를 가정하라"와 직결되는 사상입니다. 전통적인 보안은 "경계"를 신뢰했습니다. 방화벽 안쪽 네트워크에 들어온 사용자는 신뢰하고, 바깥은 신뢰하지 않는 방식입니다. 그러나 이 모델은 경계가 한 번 뚫리면 내부 전체가 노출된다는 치명적 약점이 있습니다(2장의 측면 이동을 떠올리십시오).
Zero Trust는 이 전제를 버립니다. 네트워크의 위치를 신뢰의 근거로 삼지 않습니다. 안쪽이든 바깥쪽이든, 모든 접근 요청을 동등하게 검증합니다. 핵심 질문은 "어디에서 왔는가"가 아니라 "누구이며, 이 접근이 허용되는가"입니다. 모든 요청에 대해 신원을 확인하고, 그 신원이 해당 자원에 접근할 권한이 있는지를 매번 검증합니다.
SSH를 Zero Trust 뒤에 두기
Cloudflare의 Zero Trust Access를 SSH에 적용하면, 다음과 같은 구조가 만들어집니다. SSH 접근은 더 이상 인터넷에 노출된 22번 포트를 통하지 않습니다. 대신 Cloudflare의 Zero Trust 계층을 거칩니다. 사용자가 SSH에 접속하려면, 먼저 신원을 증명해야 합니다 — 예를 들어 인증된 이메일 계정으로 로그인하고, 정책에 따라 다단계 인증(MFA — 비밀번호 외에 일회용 코드·인증 앱 등 두 번째 증거를 추가로 요구하는 방식)을 통과하는 식입니다. 이 신원 검증을 통과한 사용자만이 터널을 통해 SSH에 도달할 수 있습니다. 구체적인 설정 절차(Access 애플리케이션 정의, 정책 작성, 신원 공급자 연결)는 Cloudflare One 공식 문서에 단계별로 안내되어 있습니다.
이 구조의 이점은 명확합니다.
- 22번 포트가 인터넷에서 사라집니다. 6.5의 순서로 직접 SSH 포트를 닫았으므로, 인터넷에는 SSH가 보이지 않습니다. 1장의 SSH 무차별 대입은 두드릴 문을 찾지 못합니다.
- 신원 기반 접근 제어가 적용됩니다. SSH 키나 비밀번호를 아는 것만으로는 부족하고, 인증된 신원이 있어야 접근할 수 있습니다. 접근 권한을 사람 단위로 부여하고 회수할 수 있으며, 누가 언제 접근했는지 기록이 남습니다.
- 정책을 중앙에서 관리합니다. 어떤 사용자가, 어떤 조건에서(예: 특정 국가, 특정 기기 상태), 어떤 자원에 접근할 수 있는지를 정책으로 정의하고 중앙에서 관리할 수 있습니다.
다층 방어로서의 의미
여기서 중요한 점은, Zero Trust Access가 SSH 자체의 보안(다음 장에서 다룰 공개키 인증 등)을 대체하는 것이 아니라 그 앞에 한 겹을 더하는 것이라는 점입니다. 이것이 원칙 3, "깊이로 방어하라"입니다.
이제 SSH에 도달하기 위해 공격자는 두 개의 독립된 관문을 모두 통과해야 합니다. 먼저 Zero Trust의 신원 검증을 통과해야 하고(그런데 인증된 신원이 없으면 여기서 막힙니다), 설령 그것을 통과하더라도 SSH 자체의 공개키 인증을 통과해야 합니다(7장). 한 겹이 뚫려도 다음 겹이 막습니다. 그리고 인터넷에 노출된 포트가 없으므로, 무차별 자동 공격은 첫 관문에조차 도달하지 못합니다.
6.7 이 장이 남기는 교훈
이 장에서 확인한 것을 압축합니다.
첫째, 가장 안전한 포트는 닫힌 포트입니다. 인바운드 포트는 전 세계를 향한 영구적 초대장이며, 1장의 모든 무차별 공격은 열린 포트를 전제로 합니다. 포트를 옮기는 것(모호함)은 근본 해결이 아니며, 포트를 없애는 것이 답입니다.
둘째, Cloudflare Tunnel은 연결의 방향을 뒤집습니다. 인바운드를 받는 대신 서버가 아웃바운드 터널을 맺어, 인바운드 포트를 0개로 만들고, 오리진 IP를 숨기며, Cloudflare의 보호 계층을 얻습니다.
셋째, 오리진 IP 은폐는 자동으로 완벽해지지 않습니다. 과거 DNS 기록, 이메일 헤더, 직접 가리키는 서브도메인 등으로 IP가 샐 수 있습니다. Tunnel로 인바운드를 모두 닫으면 IP를 알아도 직접 연결이 불가능해지는 것이 가장 근본적인 방어입니다.
넷째, 안전한 적용 순서가 이 장의 생명입니다. 새 경로를 먼저 검증하고 옛 경로를 나중에 닫으며, 두 번째 세션을 열어 두고, 자동 롤백 안전장치를 걸며, 비상 콘솔을 확인해 두십시오. 보안 강화가 자기 자신을 가두는 사고로 이어져서는 안 됩니다.
다섯째, Zero Trust는 위치가 아니라 신원으로 지킵니다. SSH를 Zero Trust 뒤에 두면, 22번 포트를 인터넷에서 제거하면서도 인증된 사용자만 접근하게 할 수 있습니다. 이것은 SSH 자체 보안을 대체하지 않고 그 앞에 한 겹을 더합니다(깊이 방어).
이 장에서 우리는 SSH에 도달하는 경로 자체를 인터넷에서 숨겼습니다. 그러나 침해를 가정하는 사고(원칙 2)에 따르면, 우리는 여기서 멈추지 않습니다. 만약 어떤 이유로든 SSH에 접근이 시도된다면 — 내부에서든, 우회를 통해서든 — SSH 그 자체도 최대한 단단해야 합니다. 다음 장에서는 SSH 자체를 하드닝합니다. 비밀번호를 끄고 공개키로만 접속하며, 그 공개키를 안전하게 관리하는 법을 다룹니다. 두 번째 관문을 강철로 만드는 작업입니다.
이 장의 실행 항목
- 6.5의 안전 원칙을 먼저 숙지하십시오. 실제 변경 전에, 새 경로 우선·두 번째 세션·자동 롤백·비상 콘솔을 반드시 준비하십시오. (6.5)
- 웹 서비스를 Cloudflare Tunnel로 전환하십시오. 그 후 80·443 인바운드 포트를 닫으십시오(안전 절차 준수). (6.3)
- 백엔드 서비스를 로컬(
127.0.0.1)에만 바인딩하십시오. 외부 직접 접근의 여지를 없애십시오. (6.3, 5장) - 오리진 IP 누출 경로를 점검하고 막으십시오. 과거 DNS, 이메일 헤더, 직접 가리키는 서브도메인을 확인하십시오. (6.4)
- SSH를 Zero Trust Access 뒤로 옮기십시오. 6.5의 7단계 순서를 그대로 따르십시오. (6.5, 6.6)
- 전환 후 외부 포트 스캔으로 검증하십시오. 22번을 포함한 인바운드 포트가 더 이상 보이지 않음을 확인하십시오. (5장, 6.5)
다음 장: 7장 — SSH 하드닝. 비밀번호를 끄고, 공개키로만, 그리고 그 키를 안전하게. 두 번째 관문을 강철로.