서비스 인프라

포트와 방화벽, 열지 않기로 한 문

포트를 연다는 말에는 몇 가지 조건이 숨어 있어요. 바인드 주소와 방화벽, 실제로 닿을 수 있는 경로를 나눠 이해해 봐요.

이 글의 목차소켓을 열 때 정하는 것은 두 가지다5
  1. 소켓을 열 때 정하는 것은 두 가지다
  2. 포트에는 두 가지 문제가 있다
  3. 기본값을 거부로 두기
  4. 경계를 어디에 둘 것인가
  5. 저는 무엇을 열지 않았나

큰 건물에는 주소가 하나지만 사무실은 여럿이에요. 로비 문을 열어 둔다고 모든 사무실 문이 함께 열리지는 않아요. 기계도 같아요. 앞선 글에서 다룬 IP 주소가 건물이라면, 그 안에서 프로그램마다 붙잡고 있는 번호가 포트(port)예요. 같은 주소로 들어온 웹 요청과 데이터베이스 접속이 섞이지 않는 것은 목적지 번호가 다르기 때문이에요.

여기까지는 대체로 알려진 이야기예요. 실제 운영에서 사고가 나는 자리는 그다음, 그러니까 그 번호를 어느 인터페이스에서 받을 것인지 정하는 대목이에요.

소켓을 열 때 정하는 것은 두 가지다

프로그램이 손님을 받으려면 운영체제에 등록을 해요. 이 주소의 이 번호로 들어오는 연결을 나에게 달라는 요청이고, 이것을 리슨(listen)이라고 불러요. 이때 정하는 값이 두 개인데, 하나만 신경 쓰는 경우가 많아요.

첫째는 포트 번호예요. 둘째는 바인드 주소, 곧 이 컴퓨터의 어느 주소에서 연결을 받을지예요. 네트워크 인터페이스는 컴퓨터가 네트워크와 만나는 접점이고, 한 컴퓨터에도 여러 개가 있을 수 있어요. 리눅스 문서는 소켓 주소를 인터페이스 주소와 포트 번호의 조합으로 설명해요. 모든 로컬 인터페이스에 바인드하면 연결을 받을 수 있는 범위가 넓어져요. 다만 인터넷에서 실제로 닿으려면 주소로 가는 경로가 있고 방화벽도 통과해야 해요. 바인드와 인터넷 공개는 같은 뜻이 아니에요.

반대로 루프백, 곧 컴퓨터 자신을 가리키는 주소에 바인드하면 같은 컴퓨터 안에서만 직접 연결할 수 있어요. 외부 주소로 온 요청을 그 소켓이 바로 받지 않는 거예요. 다만 프록시나 포트 전달 기능을 따로 두면 외부에서 그 통로를 거쳐 닿을 수 있어요. 루프백은 직접 노출을 줄이는 경계이지, 다른 프로그램이 대신 연결하는 일까지 막는 인증 장치는 아니에요.

어느 주소에서 받는지도 정해야 해요

같은 프로그램의 바인드 대상을 바꿔 직접 연결할 수 있는 범위를 보세요.

같은 컴퓨터의 요청

루프백으로 연결 가능

외부 기기의 요청

직접 연결 불가

외부 주소에서 받지 않음

루프백에만 바인드하면 외부 주소로 온 연결을 직접 받지 않아요. 모든 인터페이스에 바인드하면 받을 범위가 넓어져요.

외부 경로와 방화벽은 허용되어 있고 프록시·포트 전달은 없는 예시예요. 실제 주소 조회나 네트워크 점검은 하지 않아요.

포트에는 두 가지 문제가 있다

포트 번호는 0부터 65535까지예요. 번호를 관리하는 기구는 이 범위를 시스템 포트, 등록 포트, 동적 포트 세 구간으로 나눠 둬요. 앞쪽 1024개가 시스템 포트로, 웹처럼 널리 쓰이는 서비스의 번호가 여기 있어 클라이언트가 미리 알고 접속할 수 있어요.

바깥으로 연결을 거는 쪽도 자기 포트를 하나 써요. 보통 운영체제가 사용 가능한 번호를 잠깐 골라 주는데, 이를 임시 포트(ephemeral port)라고 해요. 리눅스의 기본 할당 범위는 시스템 설정으로 바꿀 수 있어요. 외부에서 새 연결을 기다리는 리슨 포트와는 역할이 달라요. 이미 시작한 연결의 응답은 목적지와 연결 상태를 바탕으로 그 임시 포트로 돌아와요.

임시 포트가 유한하다는 데서 두 번째 문제가 나와요. TCP 연결은 출발지 주소와 포트, 목적지 주소와 포트의 네 값으로 구별해요. 한 출발지 주소에서 같은 목적지로 연결을 아주 많이 만들면 사용할 출발지 포트가 부족해질 수 있어요. 이때 상대 서버가 멀쩡해도 새 연결은 실패해요. 목적지가 달라지면 같은 번호를 재사용할 여지도 있으니, 임시 포트 개수가 곧 서버 전체 연결 수의 한계인 것은 아니에요. 연결 재사용이 단순한 속도 개선 이상의 의미를 갖는 이유예요.

그래서 포트를 볼 때 물어야 할 것이 둘로 갈려요. 리슨하는 포트는 노출의 문제이고, 빌려 쓰는 포트는 용량의 문제예요. 앞의 것은 무엇을 열어 두었는지 세면 되고, 뒤의 것은 얼마나 빨리 쓰고 버리는지를 봐야 해요. 이 글의 나머지는 앞쪽 이야기예요.

기본값을 거부로 두기

초기 인터넷은 서로 아는 소수의 연구 기관을 잇는 망이었어요. 낯선 연결이 들어올 일이 드물었으니 기본값은 열림이었고, 잠그는 쪽이 예외였어요.

목록 기본값에 따른 누락 항목의 접근 상태 차단 목록의 누락은 기본 허용으로, 허용 목록의 누락은 기본 거부로 이어져 같은 실수의 결과를 뒤집는다. 개념 도해 목록 기본값에 따른 누락 항목의 접근 상태 누락 항목의 기본 판정 경로 차단 목록 ① 요청 항목 ② 목록 대조 목록에 없음 ③ 기본값 기본 허용 허용 목록 ① 요청 항목 ② 목록 대조 목록에 없음 ③ 기본값 기본 거부 누락 항목의 접근 상태 기본 판정 · 접근 상태 허용 거부 기본 거부선 기본 허용 열림 유지 기본 거부 닫힘 유지 차단 목록 허용 목록 · 목록 종류 차단 목록의 누락은 기본 허용으로, 허용 목록의 누락은 기본 거부로 이어져 같은 실수의 결과를 뒤집는다. SOSHIN · DEV NOTES
같은 실수라도 어느 목록을 쓰느냐에 따라 결과가 열림과 닫힘으로 갈려요.

이 기본값을 뒤집어야 한다는 지적은 인터넷이 상업화되기 훨씬 전에 나왔어요. 1975년 Jerome Saltzer와 Michael Schroeder는 정보 보호 시스템의 설계 원칙을 정리하면서 접근 결정을 배제가 아니라 허가에 근거하라고 썼어요. 차이는 목록에서 무언가를 빠뜨렸을 때 드러나요. 차단 목록에서 빠진 항목은 열린 채 남고, 허용 목록에서 빠진 항목은 닫힌 채 남아요. 어느 쪽 실수가 서비스를 멈추게 하고 어느 쪽이 사고를 만드는지는 분명해요.

원칙이 문서에 적혀 있는 것과 사람들이 그대로 움직이는 것은 다른 일이에요. 기본값을 실제로 바꾼 것은 사건이었어요. 1988년 11월 2일 저녁 인터넷에 풀린 자기 복제 프로그램은 버클리 유닉스가 도는 기계들을 차례로 감염시켰고, IETF는 이듬해 RFC 1135로 그 일을 기록에 남겼어요. 연결되어 있다는 사실 자체가 위험이 될 수 있다는 것을 넓게 보여 준 첫 사건이었어요.

진단이 뒤따랐어요. 1989년 Steven Bellovin은 TCP/IP 프로토콜 묶음 자체에 있는 보안 문제를 정리해 발표했어요. 특정 프로그램의 버그가 아니라 서로 믿는 사이를 전제로 설계된 규약의 문제라는 지적이었고, 고칠 곳이 한 군데가 아니라는 뜻이기도 했어요.

대응은 망과 망 사이에 관문을 두는 쪽으로 모였어요. 1990년 여름 Bill Cheswick은 AT&T 벨 연구소가 쓰던 게이트웨이 설계를 발표하면서, 신뢰하는 안쪽 기계와 신뢰하지 않는 바깥 기계를 나눠 바깥이 완전히 뚫려도 안쪽이 버티게 하는 구조를 설명했어요. 이런 관문에 붙은 이름이 방화벽이고, 불이 번지는 것을 막으려 세운 건축의 벽에서 온 말이에요. 클라우드에서 보안 그룹이나 네트워크 정책이라고 부르는 설정도 같은 물건이에요.

눈여겨볼 것은 같은 논문에 이 방식의 한계를 짚은 문장이 함께 있었다는 점이에요. 둘레만 단단히 하면 겉은 바삭하고 속은 물렁한 방어가 된다는 것이었어요. 관문을 세우자고 제안한 글에 관문만으로는 부족하다는 경고가 이미 들어 있었던 셈이에요.

여기서 자주 오해되는 것이 나가는 방향이에요. NIST의 방화벽 지침은 상태 기반 검사 방화벽이 연결마다 출발지와 목적지를 담은 상태 표를 유지한다고 설명해요. 안에서 시작한 연결은 그 표에 올라가므로 응답 패킷은 별도의 인바운드 규칙 없이도 돌아와요. 인바운드를 거의 다 막아 두고도 서버가 외부 API를 호출하고 패키지를 내려받는 데 문제가 없는 이유가 이것이고, 인바운드 허용 규칙은 밖에서 먼저 거는 연결에만 필요해요.

경계를 어디에 둘 것인가

바인드 주소와 네트워크 정책은 서로를 대체하지 않아요. 같은 포트라도 어디에 붙였느냐에 따라 규칙을 잘못 썼을 때의 결과가 달라져요.

바인드 대상직접 연결할 수 있는 범위남아 있는 경계
모든 로컬 인터페이스해당 주소로 경로가 있는 곳방화벽과 접근 인증
특정 사설 주소사설 주소로 경로가 이어진 곳라우팅, 방화벽과 접근 인증
루프백같은 컴퓨터 안로컬 프로세스와 별도 전달 통로

세 번째 줄이 자주 잊혀요. 관리자만 보면 되는 대시보드나 내부 지표 수집기를 굳이 네트워크에 노출할 필요가 없고, 봐야 할 때는 그때 통로를 만들면 돼요. 상시로 열어 두는 것과 필요할 때 여는 것은 같은 접근성처럼 보이지만 짊어지는 위험이 달라요.

공개된 주소에 리슨 소켓을 하나 두면 그 포트는 사람보다 먼저 자동화된 조사에 발견돼요. 훑는 비용이 거의 들지 않기 때문이에요. 2013년 공개된 ZMap은 단일 장비에서 IPv4 주소 공간 전체를 45분 안에 훑을 수 있음을 보였어요. 전수 조사가 그 정도 비용이면 열린 포트를 찾는 일은 특별한 사건이 아니라 상시로 흐르는 배경이 돼요. 그래서 점검의 순서는 쓰지 않는 포트를 닫는 쪽이 아니라 열어야 할 포트만 여는 쪽이에요.

저는 무엇을 열지 않았나

제가 만드는 앱에서 인터넷 쪽으로 리슨하는 것은 앱이 접속하는 입구뿐이에요. 서비스 상태를 보는 도구들은 제가 매일 들여다보는 화면이지만 공개 주소에 붙이지 않았어요. 같은 내부 네트워크 안에서만 서로를 부르게 두고, 확인이 필요하면 그때 관리 통로를 만들어 들어가요.

번거롭지 않다고 하면 거짓말이에요. 지표 하나 보려고 통로부터 여는 일이 귀찮을 때가 있어요. 그래도 이렇게 둔 이유는 설정 실수를 전제로 삼았기 때문이에요. 규칙을 잘못 쓰거나, 인증을 약하게 두거나, 업데이트를 미루는 일은 언젠가 반드시 생겨요. 그때 그 소켓이 애초에 외부 인터페이스에 붙어 있지 않았다면 같은 실수의 폭발 반경이 훨씬 작아요.

바인드 주소를 신경 쓰는 일은 결국 서른 해 전의 경고에 대한 답이기도 해요. 둘레에 관문을 세우는 것만으로는 속이 물렁한 채로 남는다면, 안쪽의 소켓 하나하나가 어디에 붙어 있는지가 그 물렁함을 줄이는 방법이 돼요. 보안 설계에서 실질적인 차이를 만드는 것은 대개 완벽한 규칙이 아니라, 규칙이 틀렸을 때 무엇까지 무너지는가였어요.

소신을 상징하는 까마귀 목판화 일러스트
작성자소신 · Soshin

웹과 서버를 만들고, 개발하며 배운 것들을 글로 남깁니다.

좌우로 움직여 보세요