서비스 인프라

포트와 방화벽, 0.0.0.0/0으로 열면 생기는 일

보안 그룹 인바운드 규칙에 0.0.0.0/0을 넣으면 인터넷의 누구나 그 포트로 연결을 시도할 수 있다.
포트, 인바운드와 아웃바운드, IP 범위 표기부터 차례로 정리한다.

이 글의 목차포트: 서버 안에서 프로그램을 가리키는 번호7
  1. 포트: 서버 안에서 프로그램을 가리키는 번호
  2. 인바운드와 아웃바운드는 연결의 방향이다
  3. 0.0.0.0/0은 ‘인터넷 전체’⁠라는 뜻이다
  4. 다 열어 두면 생기는 일
  5. 방화벽이 처음부터 막아 두는 이유
  6. 0.0.0.0이 또 나온다: 바인드 주소
  7. 실무에서 여는 방법

AWS에서 EC2 인스턴스를 만들다 보면 보안 그룹을 정하는 단계가 나온다.
인바운드 규칙, 아웃바운드 규칙, 프로토콜, 포트 범위, 소스까지 처음 보면 낯선 칸이 많다.

튜토리얼을 따라 하다 보면 소스 칸에 0.0.0.0/0⁠을 넣으라는 안내가 자주 나온다.
넣으면 접속이 바로 되니 그대로 두고 넘어가기 쉽다.

하지만 이 값의 뜻을 알고 넣는 것과 모르고 넣는 것은 차이가 크다.
포트가 무엇인지부터 정리한다.

포트: 서버 안에서 프로그램을 가리키는 번호

IP 주소는 인터넷에서 서버 한 대를 찾아가는 주소다.
그런데 서버 한 대에는 보통 프로그램이 여러 개 돈다.
웹 서버, 데이터베이스, 원격 접속용 SSH가 한 기계에 함께 있는 식이다.

그래서 같은 서버로 온 연결이 어느 프로그램으로 가야 하는지 정하는 번호가 하나 더 필요하다.
이 번호가 포트다.

포트 번호는 0부터 65535까지 쓴다.
0번부터 1023번까지를 시스템 포트, 1024번부터 49151번까지를 사용자 포트, 그 위를 동적 포트라고 부른다.

자주 쓰는 서비스는 번호가 정해져 있다.
SSH는 22번, HTTP는 80번, HTTPS는 443번이다.
MySQL은 3306번, PostgreSQL은 5432번, Redis는 6379번을 기본으로 쓴다.

프로그램이 어떤 포트로 들어오는 연결을 기다리는 것을 리슨(listen)⁠이라고 한다.
서버에 접속이 된다는 것은 그 포트에서 리슨하는 프로그램이 있고, 중간의 방화벽도 그 연결을 통과시켰다는 뜻이다.

포트 번호에 따른 연결 전달 같은 주소로 온 연결은 포트 번호로 받을 프로그램이 정해지고, 그 포트에서 리슨하는 프로그램이 없으면 연결이 거부된다. 개념 도해 포트 번호에 따른 연결 전달 같은 주소로 온 연결의 전달 ① 목적지 주소 : 포트 ② 포트 번호로 구분 ③ 리슨 중인 프로그램 서버 한 대 · 주소 203.0.113.10 203.0.113.10 : 22 포트 22 SSH 연결됨 203.0.113.10 : 443 포트 443 웹 서버 연결됨 203.0.113.10 : 5432 포트 5432 데이터베이스 연결됨 203.0.113.10 : 8080 포트 8080 리슨하는 프로그램 없음 연결 거부 포트 번호의 세 구간 시스템 포트 0 – 1023 22 · 80 · 443 사용자 포트 1024 – 49151 3306 · 5432 · 6379 동적 포트 49152 – 65535 고정 배정 없음 구간 폭 · 실제 비율 아님 같은 주소로 온 연결은 포트 번호로 받을 프로그램이 정해지고, 그 포트에서 리슨하는 프로그램이 없으면 연결이 거부된다. SOSHIN · DEV NOTES
주소가 서버를 찾고, 포트 번호가 서버 안에서 받을 프로그램을 정한다.

인바운드와 아웃바운드는 연결의 방향이다

보안 그룹 규칙은 방향에 따라 두 가지로 나뉜다.

인바운드는 밖에서 서버로 들어오는 연결이다.
사용자가 웹사이트에 접속하거나, 노트북에서 SSH로 서버에 붙는 경우가 여기에 들어간다.

아웃바운드는 서버에서 밖으로 나가는 연결이다.
서버가 외부 결제 API를 부르거나 패키지를 내려받을 때가 그렇다.

보안 그룹을 처음 만들면 인바운드 규칙은 하나도 없고, 아웃바운드에는 모든 트래픽을 허용하는 규칙 하나가 들어 있다.
아무것도 바꾸지 않으면 밖에서는 못 들어오고, 안에서는 어디로든 나갈 수 있는 상태다.

여기서 흔히 생기는 의문이 있다.
인바운드를 다 막아 두면 서버가 외부 API를 불렀을 때 응답은 어떻게 들어오는가.

보안 그룹은 연결 상태를 기억한다(stateful). 안에서 내보낸 요청의 응답은 인바운드 규칙과 상관없이 들어온다.
그래서 인바운드 규칙은 밖에서 먼저 연결을 걸어오는 경우만 생각하면 된다.

응답이 돌아오는 자리도 알아 둘 만하다.
서버가 밖으로 연결을 걸 때는 운영체제가 남는 번호 하나를 잠깐 골라 출발지 포트로 쓰는데, 이것을 임시 포트(ephemeral port)⁠라고 한다.
리눅스는 기본으로 32768번부터 60999번 사이에서 이 번호를 고른다.

응답은 이 임시 포트로 돌아온다.
연결 상태를 기억하지 않는 네트워크 ACL에서는 이 번호가 따로 문제가 되는데, 그 내용은 보안 그룹과 네트워크 ACL 글에서 이어진다.

연결 방향에 따른 보안 그룹 규칙 인바운드 규칙은 밖에서 먼저 거는 연결만 정하고, 안에서 시작한 연결의 응답은 보안 그룹이 기억해 들여보낸다. 개념 도해 연결 방향에 따른 보안 그룹 규칙 처음 만든 보안 그룹의 두 방향 밖 · 인터넷 보안 그룹 규칙 안 · 서버 사용자 노트북 밖에서 먼저 연결 ① 인바운드 인바운드 규칙 처음에는 하나도 없음 막힘 서버 연결을 받지 못함 외부 결제 API 서버가 부르는 곳 아웃바운드 규칙 모든 트래픽 허용 · 기본값 ② 아웃바운드 서버 안에서 먼저 연결 안에서 시작한 연결의 응답 외부 API 목적지 포트 443 서버 출발지 임시 포트 50123 ③ 요청 · 50123 → 443 ④ 응답 · 443 → 50123 연결을 기억해 인바운드 규칙 없이 통과 임시 포트 번호 · 예시 인바운드 규칙은 밖에서 먼저 거는 연결만 정하고, 안에서 시작한 연결의 응답은 보안 그룹이 기억해 들여보낸다. SOSHIN · DEV NOTES
막히는 것은 밖에서 먼저 거는 연결이고, 서버가 먼저 건 연결의 응답은 임시 포트로 돌아온다.

0.0.0.0/0은 ‘인터넷 전체’⁠라는 뜻이다

이제 소스 칸을 볼 차례다.
소스에는 연결을 허용할 IP 주소 범위를 적는다.
203.0.113.10/32⁠처럼 주소 뒤에 슬래시와 숫자를 붙이는데, 이런 표기를 CIDR⁠이라고 한다.

슬래시 뒤의 숫자는 IPv4 주소 32비트 가운데 앞에서부터 몇 비트를 고정하는지를 뜻한다.
/32면 32비트를 전부 고정하니 주소 딱 하나다.
/24면 앞 24비트만 고정하니 마지막 자리 256개를 묶은 범위가 된다.

AWS의 보안 그룹 규칙 문서도 주소 하나만 허용하려면 /32를 붙여 적으라고 안내한다.
그렇다면 /0은 무엇일까.

고정하는 비트가 하나도 없으니, 0.0.0.0/0은 IPv4 주소 42억 9천여 개 전체를 덮는 범위가 된다.
IPv6에서 같은 뜻은 ::/0⁠이다.

그러니 인바운드 규칙의 소스가 0.0.0.0/0이면, 인터넷에 연결된 누구든 그 포트로 연결을 시도할 수 있다는 뜻이 된다.

접두사 길이에 따른 주소 범위 슬래시 뒤 숫자가 작을수록 고정하는 비트가 줄어 범위가 넓어지고, /0은 IPv4 주소 전체가 된다. 개념 도해 접두사 길이에 따른 주소 범위 앞에서부터 고정하는 비트 고정 바뀔 수 있음 IPv4 주소 32비트 203.0.113.10/32 주소 1개 203.0.113.0/24 주소 256개 0.0.0.0/0 주소 약 42억 9천만 개 한 칸이 1비트 · 8칸이 주소의 한 자리 보안 그룹 소스에 적었을 때 203.0.113.10/32 이 주소 하나에서만 연결 203.0.113.0/24 203.0.113.0 ~ 255에서 연결 0.0.0.0/0 인터넷의 누구나 연결 시도 슬래시 뒤 숫자가 작을수록 고정하는 비트가 줄어 범위가 넓어지고, /0은 IPv4 주소 전체가 된다. SOSHIN · DEV NOTES
슬래시 뒤 숫자는 고정하는 비트 수다. 0이면 고정하는 비트가 없어 모든 주소가 들어간다.

다 열어 두면 생기는 일

내 서버 주소를 누가 알겠느냐는 생각이 들 수 있다.
하지만 열린 포트를 찾는 것은 사람이 아니라 자동화된 스캐너다.

2013년에 공개된 스캐너 ZMap은 장비 한 대로 IPv4 주소 전체를 45분 안에 훑을 수 있었다.
벌써 10년도 더 지난 일이다.
공인 IP에 포트를 열어 두면 곧 누군가 두드린다고 보는 편이 맞다.

어떤 포트가 열렸느냐에 따라 위험의 크기가 다르다.
SSH 22번을 0.0.0.0/0으로 열어 두면 세계 어디서든 로그인을 시도할 수 있다.
비밀번호가 약하거나 키가 새면, 권한 없는 사람이 그 서버에서 명령을 실행하게 된다.

데이터베이스와 캐시는 더 조심해야 한다.
이런 프로그램은 애플리케이션 서버만 접속한다는 전제로 만들어진다.

Redis는 믿을 수 있는 환경 안에서 믿을 수 있는 클라이언트만 접속한다는 전제로 설계되었다.
그래서 Redis 개발진은 인터넷에 직접 노출하지 말고, EC2 같은 가상 서버라면 Redis 포트를 방화벽으로 막으라고 안내한다.

그런데도 인터넷에 그대로 노출된 Redis가 많았다.
그래서 3.2.0부터는 보호 모드가 생겼다.
비밀번호 없이 모든 인터페이스에서 리슨하면, 같은 서버가 아닌 곳에서 온 접속에는 오류로 답한다.

방화벽이 처음부터 막아 두는 이유

보안 그룹이 처음에 인바운드를 전부 막아 두는 것은 우연이 아니다.
허락할 것만 골라 적고 나머지는 막는 방식을 기본 거부(default deny)⁠라고 하는데, 이 생각은 꽤 오래되었다.

1975년 Jerome Saltzer와 Michael Schroeder는 정보 보호 시스템의 설계 원칙을 정리하면서, 접근은 막을 것을 골라내기보다 허락할 것을 골라 주는 방식으로 정해야 한다고 썼다.

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

실수했을 때의 결과가 다르기 때문이다.
막을 것을 적는 목록에서 하나를 빠뜨리면 그 항목은 열린 채로 남는다.
허락할 것을 적는 목록에서 하나를 빠뜨리면 닫힌 채로 남는다.

앞의 실수는 사고로 이어질 수 있다.
뒤의 실수는 대개 “이거 왜 안 돼요?” 하는 문의로 끝난다.

인터넷이 실제로 이쪽으로 움직인 계기는 사건이었다.
1988년 11월, 인터넷에 풀린 자기 복제 프로그램이 버클리 유닉스를 쓰는 기계들을 줄줄이 감염시켰다.

그 뒤로 망과 망 사이에 관문을 세우는 방식이 자리 잡았다.
1990년 Bill Cheswick은 AT&T 벨 연구소에서 쓰던 게이트웨이 설계를 발표하면서, 믿을 수 있는 안쪽 기계와 믿을 수 없는 바깥 기계를 나누었다.
이런 관문을 방화벽이라고 부르고, AWS의 보안 그룹도 같은 역할을 한다.

그 논문에는 바깥 경계만 단단하게 지키면 안쪽은 무방비로 남는다는 경고도 함께 들어 있다.
방화벽 하나에만 기대지 말라는 뜻이고, 서버 안에서 할 수 있는 일이 바로 다음에 나오는 바인드 주소다.

0.0.0.0이 또 나온다: 바인드 주소

헷갈리기 쉬운 것이 하나 있다.
서버 프로그램 설정에도 0.0.0.0이 자주 나오는데, 보안 그룹의 0.0.0.0/0과는 뜻이 다르다.

프로그램이 포트에서 리슨할 때는 서버의 어느 주소로 들어온 연결을 받을지도 함께 정한다.
이 주소를 바인드 주소라고 한다.
서버 한 대에도 네트워크와 만나는 접점인 네트워크 인터페이스가 여러 개 있을 수 있기 때문이다.

바인드할 때 0.0.0.0(INADDR_ANY)⁠을 지정하면 소켓이 이 서버의 모든 로컬 인터페이스에 묶인다.
보안 그룹의 0.0.0.0/0이 ‘인터넷의 모든 주소’⁠였다면, 바인드 주소의 0.0.0.0은 ‘이 서버의 모든 인터페이스’⁠다.

반대로 127.0.0.1 같은 루프백 주소에 바인드하면 같은 서버 안에서만 연결할 수 있다.
데이터베이스와 애플리케이션이 한 서버에 있다면 이렇게 두는 편이 안전하다.

어느 주소에서 받을지도 정해야 한다

같은 프로그램이라도 바인드 대상에 따라 직접 연결할 수 있는 범위가 달라진다.

같은 컴퓨터의 요청

루프백으로 연결 가능

외부 기기의 요청

직접 연결 불가

외부 주소에서 받지 않음

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

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

바인드 주소와 보안 그룹은 서로를 대신하지 못한다.
같은 포트라도 어디에 바인드했느냐에 따라, 보안 그룹을 잘못 설정했을 때 피해가 미치는 범위가 달라진다.

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

마지막 줄의 ‘별도 전달 통로’⁠는 프록시나 포트 전달처럼 다른 프로그램이 대신 연결을 넘겨주는 경우다.
이런 통로를 따로 만들면 루프백에 바인드한 프로그램도 밖에서 닿을 수 있다.

실무에서 여는 방법

웹 서버의 80번과 443번은 0.0.0.0/0으로 열어도 괜찮다.
누구나 접속하라고 만든 서비스이기 때문이다.
앞에 로드밸런서를 둔다면 로드밸런서만 이 포트를 열고, 서버는 로드밸런서에서 오는 연결만 받게 하면 된다.

SSH 22번은 0.0.0.0/0으로 열지 않는다.
꼭 열어야 한다면 내 IP 하나만 /32로 허용한다.
AWS Systems Manager의 Session Manager⁠를 쓰면 인바운드 포트를 열지 않고도 서버에 접속할 수 있다.

데이터베이스와 캐시는 소스에 IP 범위 대신 애플리케이션 서버의 보안 그룹을 적는다.
다른 보안 그룹을 소스로 지정하면, 그 보안 그룹이 붙은 인스턴스들만 사설 IP로 접속할 수 있다.

이렇게 해 두면 애플리케이션 서버를 늘리거나 바꿔도 규칙의 IP를 일일이 고칠 필요가 없다.
새 서버에 같은 보안 그룹만 붙이면 된다.

아웃바운드는 기본값인 전체 허용으로 두는 경우가 많다.
다만 민감한 데이터를 다루는 서버라면 나가는 목적지도 필요한 곳으로 좁힐 수 있다.

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

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

좌우로 움직여 보세요