🔥 Security Group: 인스턴스 레벨 방화벽

1836자
22분

순백색 배경 위에 한 대의 EC2 서버 아이콘이 가운데 놓여 있고, 그 주위를 AWS 오렌지(#ff9900) 색의 반투명 보호막 링이 둘러싸고 있다. 보호막 바깥에서 굵은 다섯 개의 화살표가 안쪽으로 들이친다. 다섯 화살표 모두 보호막에 부딪혀 멈춰 있고, 화살표 끝마다 가벼운 빨강 코랄 톤의 작은 차단 표식이 붙어 있어 외부에서 들어오는 트래픽이 모두 차단된 상태를 한눈에 보여 준다. 같은 보호막 안쪽에서는 같은 SG에 묶인 또 한 대의 서버 아이콘이 첫 서버 쪽으로 에메랄드 그린(#10b981) 화살표를 보내고 있고, 그 화살표 한 줄만이 막힘 없이 서버에 도달한다. 보호막 둘레에는 작은 회색 태그가 매달려 있어 'allow only'라는 짧은 라벨을 적어 두었다. 슬레이트 그레이의 깔끔한 산세리프 글꼴, 둥근 모서리와 절제된 그림자가 사용된 표지 일러스트레이션

보안 그룹은 deny를 적을 곳이 없다. 콘솔에서 처음 인바운드 규칙 화면을 열고 한참 막혔던 적이 있다. 한 줄을 통째로 막아야 할 것 같은 일이 생기면 어디에 적느냐고 물었다. 답은 적을 곳이 없다, 그게 곧 설계라는 사실이었다.

AWS 공식 문서 한 줄이 이 설계를 그대로 적어 둔다. "You can specify allow rules, but not deny rules." 보안 그룹은 허용만 적는다. 차단은 허용을 적지 않는 것으로 한다. 이게 정책 평가 흐름: Allow와 Deny가 만나면에서 다룬 IAM 정책의 Implicit Deny / Explicit Deny 비대칭과 겹치는 대목이긴 한데, 그쪽보다 더 단순하다. Explicit Deny가 없다. 이게 SG에 대한 첫 인상이다.

순백색 배경 위에 한 줄짜리 흐름도. 왼쪽에서 패킷 한 개가 오른쪽으로 들어오고, AWS 오렌지(#ff9900) 색의 둥근 사각형 'inbound rules' 박스 앞에서 두 갈래로 갈린다. 위쪽 갈래는 에메랄드 그린(#10b981) 화살표로 'allow rule matches → enter'라고 적혀 있어 통과되고, 아래쪽 갈래는 코랄 톤으로 'no match → drop'이라 적혀 막힌다. 통과한 화살표 옆에는 작은 메모 카드가 핀으로 꽂혀 있어 'connection tracked'라고 적혀 있다. 그 다음 박스에서 응답 패킷이 반대 방향으로 흐르며, 옆에 회색으로 흐릿하게 처리된 'outbound rules' 박스를 지나치는 모습으로 그려져 있고, 화살표에는 'response auto-allowed (stateful)'이라는 라벨이 붙어 있다. 다이어그램 하단의 작은 회색 캡션 박스에 'allow only: no deny rules'라는 한 줄이 적혀 있어 이 글의 첫 결론을 한 장에 담아 두었다

어디에 매달려 있는가, 인스턴스 ENI 레벨

보안 그룹이 연결되는 위치는 인스턴스 전체가 아니라 인스턴스의 ENI(네트워크 인터페이스) 한 장이다. EC2를 만들면 ENI가 따라 붙고 그 ENI에 SG를 묶는다. ENI 하나당 SG는 기본 5개까지, 한도를 늘리면 16개까지 묶을 수 있다.

Subnet: Public과 Private의 진짜 차이에서 본 것처럼 Subnet의 public/private 분류는 라우팅 테이블이 결정했다. SG는 그 위에 한 겹을 더 더하는 셈이다. Subnet에 들어왔다, 즉 라우팅 테이블이 "이 Subnet으로 보내라"를 통과시킨 다음, 그 다음에 SG가 ENI 앞에서 한 번 더 검사한다. 이게 SG의 동작 위치다.

같은 Subnet 안의 두 인스턴스 사이에도 SG가 작용한다. 내부 트래픽이라 안전하다는 가정은 SG에 통하지 않는다. 같은 Subnet인지가 아니라, 받는 ENI에 매달린 SG가 그 출발지로부터의 트래픽을 허용한다고 적어 놨는지가 전부다.

새로 만든 SG가 기본으로 갖는 구조

여기서 한 번 함정에 걸린 적이 있다. 새로 만든 보안 그룹과 VPC가 기본으로 들고 다니는 보안 그룹(default security group)이 같은 줄 알았는데 둘은 다르다.

새로 만들면, 그러니까 콘솔에서 Create security group을 누르면, 구성이 이렇다.

  • 인바운드 규칙: 0줄. 아무것도 안 들어온다.
  • 아웃바운드 규칙: 0.0.0.0/0 모든 포트 모든 프로토콜로 전부 나가는 한 줄(VPC가 IPv6 CIDR을 갖고 있으면 ::/0도 같은 형식으로 한 줄을 더 추가한다).

이 둘이 묘하게 비대칭이다. 받는 쪽은 닫혀 있고 나가는 쪽은 활짝 열려 있다. 현실적으로 외부 호출이 전혀 필요 없는 인스턴스는 사실상 드무니까, 만든 직후의 SG도 곧장 어디든 부를 수는 있다. 단지 외부에서 누가 와서 두드릴 수는 없는 상태.

VPC가 기본으로 들고 있는 default SG는 구성이 또 다르다.

  • 인바운드 한 줄: source가 자기 자신의 SG ID. 즉, default SG에 묶인 인스턴스끼리는 모든 포트로 서로 통신할 수 있다.
  • 아웃바운드: 0.0.0.0/0로 전부 나가는 한 줄(VPC가 IPv6 CIDR을 갖고 있으면 ::/0도 같은 형식으로 한 줄을 더 추가한다).

이 self-referencing 한 줄이 의외로 자주 사고를 친다. 학습용 default VPC에서는 편하다는 이유로 아무 인스턴스에나 default SG를 박았다가, 그게 운영으로 흘러가면 같은 SG에 묶인 인스턴스 전부가 전 포트로 서로 통신할 수 있게 된다. AWS 공식 가이드도 default SG에 기대지 말고 별도의 보안 그룹을 만들어 쓰라고 권한다. SG를 명시하지 않은 채 인스턴스를 띄우면 default SG가 자동으로 붙어 버리기 때문이다.

순백색 배경 위에 큰 둥근 사각형 'VPC' 한 개가 그려져 있고, 그 안에 한 단계 작은 둥근 사각형 'default security group'이 AWS 오렌지(#ff9900) 색으로 표시되어 있다. 안쪽 박스 안에는 슬레이트 그레이 EC2 서버 아이콘 세 대가 균등하게 배치되어 있고, 세 대 사이를 잇는 짧은 곡선 화살표 세 개가 같은 박스로 되돌아오는 self-reference 형태로 그려진다. 그 화살표 옆에는 작은 라벨 'source = own SG ID'가 적혀 있어 default SG에 묶인 인스턴스끼리는 자유롭게 통신한다는 사실을 보여 준다. 박스 오른쪽 끝에서 굵은 한 줄 화살표가 외부로 빠져나가고 그 화살표 위에는 '0.0.0.0/0 → all out'이라는 라벨이 적혀 있다. 박스 위쪽에는 작은 회색 차단 아이콘과 함께 'allow only, no inbound from external'이라는 한 줄 메모가 매달려 있다. 둥근 모서리와 옅은 그림자, 슬레이트 그레이의 깔끔한 산세리프 글꼴이 사용된 다이어그램

Stateful이라는 한 줄의 뜻

SG가 stateful이라는 말은, 한 번 인바운드 규칙을 거친 패킷의 짝(response)은 SG가 아웃바운드 규칙을 다시 검사하지 않고 그대로 보낸다는 뜻이다. 반대 방향도 마찬가지. 내가 인스턴스에서 어딘가로 요청을 던지면 그 응답은 인바운드 규칙과 무관하게 들어온다.

이 동작을 가능하게 해 주는 것이 connection tracking. AWS Nitro 시스템이 흐름의 상태를 기억해 두는 메커니즘이고, 응답 패킷이 도착하면 SG가 그 기억과 매칭해 그대로 내보낸다. 정확한 내부 구조까지 공식 문서가 공개하지는 않지만, 흐름 단위로 한 항목이 잠시 잡혀 있다가 idle timeout 이후 AWS가 항목을 비운다고 명시한다.

타임아웃은 protocol마다 다르다 (verified 2026-04-26).

  • TCP established: 기본 350초(Nitrov6 인스턴스 타입, P6e-GB200 제외) / 432,000초 = 5일(그 외 Nitro 타입). 범위 60초 ~ 5일.
  • UDP unidirectional(한 쪽만 흐른 UDP 흐름): 기본 30초, 범위 30~60초.
  • UDP stream(양쪽이 다 흐른 UDP): 기본 180초, 범위 60~180초.
  • ICMP: AWS 표현 그대로 "ICMP traffic is always tracked." 즉 outbound 규칙이 ICMP를 막고 있어도 들어온 인바운드 ICMP의 응답은 통과한다.
  • TCP/UDP/ICMP가 아닌 프로토콜(예: ESP): IP와 프로토콜 번호만 추적. 같은 짝 트래픽이 600초 안에 도착하면 SG는 인바운드 규칙과 무관하게 응답을 수용한다.

이 타임아웃들 중 TCP established / UDP stream / UDP unidirectional 셋은 2023-11-20부터 ENI 단위로 조정할 수 있게 됐다 (Nitro 인스턴스 한정, ENI의 ConnectionTrackingSpecification 항목). NAT Gateway: Private Subnet의 외부 연결에서 본 350초 idle timeout은 NAT GW 자체의 고정값이고, 이쪽은 ENI 단위 conntrack이라 작동하는 box가 다른 위치에 있다. 둘이 둘 다 stateful이라 헷갈리는데, 연결되어 동작하는 위치가 다르다는 점만 잡고 있으면 된다.

한 가지 미묘한 예외가 있다. untracked connection이라 부르는 경우. 인바운드와 아웃바운드 양쪽 모두 0.0.0.0/0 (또는 ::/0) × 모든 포트로 활짝 열려 있고, 흐름이 그 규칙과 정확히 일치한다면, 그 흐름은 conntrack에 등록되지 않는다. 등록되지 않았다는 건, 규칙을 수정하거나 지우면 진행 중인 흐름까지 그 즉시 함께 멈춘다는 뜻이다. 한도 가까이 conntrack을 쓰는 워크로드에서 의도적으로 untracked로 빼는 패턴이 있긴 한데, 운영에서는 부작용을 정확히 알고 써야 하는 패턴이다.

SG를 SG로 참조하는 패턴이 운영의 주력

SG의 source/destination 칸에는 CIDR도 들어가지만, 다른 SG의 ID도 들어간다. 이게 운영 SG의 진짜 주력 패턴이다.

두 방식을 나란히 두면 차이가 명확하다. 웹 서버 ASG와 RDS 사이에서 RDS는 웹 서버에서 오는 5432 포트만 받는다고 적고 싶다. 두 가지 길이 있다.

  • CIDR로 박기: RDS의 SG 인바운드에 10.0.1.0/24, TCP, 5432 한 줄. 그런데 웹 서버 Subnet이 늘어나거나, 다른 AZ로 ASG가 확장되면 그때마다 사람이 CIDR을 추가해야 한다.
  • SG로 박기: RDS의 SG 인바운드에 sg-web, TCP, 5432 한 줄. ASG가 새 인스턴스를 띄우면 그 인스턴스에는 sg-web이 자동으로 매달리고, RDS 룰은 자동으로 그 인스턴스를 받아준다. 룰 자체는 한 번도 손대지 않는다.

SG 참조는 같은 VPC 안에서 자유롭게 쓰이고, VPC Peering으로 연결된 다른 VPC에서도 인바운드와 아웃바운드 양쪽으로 가능하다. Transit Gateway로 묶인 VPC들 사이에서는 SG 참조가 인바운드 룰에서만 지원되니 비대칭이라는 점만 잡고 있으면 된다. Cross-account는 123456789012/sg-1a2b3c4d 형식으로 적는다. 다만 공인 IP 트래픽에는 적용되지 않는다. 이건 사설 통신 한정의 약속이다.

지금까지 반복해서 짚는 관리형이 감추는 비용 흐름에서 보면, SG 참조는 그 비용을 살짝 줄이는 한 가지 방법이다. SG 자체는 무료지만, ENI당 5개 SG × 60줄 인바운드 × 60줄 아웃바운드 한도가 생각보다 빨리 찬다. 한 줄을 인스턴스 IP로 적는 대신 SG로 적으면, 그 한 줄이 인스턴스 N개를 커버한다. 운영에서 룰 갱신을 사람이 따라가지 않아도 되는 운영 비용 절감이 SG 참조의 진짜 값.

두 개의 패널이 가운데 세로 줄로 나뉜 비교 다이어그램. 왼쪽 패널은 'CIDR로 박기'라는 작은 헤더 아래에 동일한 EC2 서버 아이콘 세 대가 가로로 배치되어 있고, 각 서버 아래에 '10.0.1.5', '10.0.1.6', '10.0.1.7'이라는 IP 라벨이 붙어 있다. 그 세 대에서 굵은 화살표 한 줄이 오른쪽의 RDS 데이터베이스 아이콘으로 향하고, 화살표에는 'inbound: 10.0.1.0/24, TCP 5432'라는 라벨이 적혀 있다. 화살표 위에는 코랄 톤의 작은 경고 배지가 떠 있어 'manual update needed when scaling'이라는 글자가 표시되어 있다. 오른쪽 패널은 'SG로 박기'라는 헤더 아래에 같은 세 대의 EC2 서버를 AWS 오렌지(#ff9900) 색 반투명 보호막으로 함께 묶어 'sg-web' 라벨을 매달았다. 그 묶음에서 한 줄짜리 화살표가 오른쪽의 RDS로 향하고, 화살표에는 'inbound: sg-web, TCP 5432'라는 라벨이 적혀 있다. 화살표 위에는 에메랄드 그린(#10b981)의 체크 배지가 'auto-adapts'라는 한 줄을 표시한다. 슬레이트 그레이 텍스트와 둥근 모서리, 절제된 그림자가 사용된 다이어그램

한도, 곱셈으로 빨리 차는 항목

숫자는 외워둘 만하다 (verified 2026-04-26).

  • 리전당 VPC 보안 그룹: 2,500개 (조정 가능. VPC당이 아니라 리전당이 정확한 표현)
  • SG당 인바운드 룰: 60줄 (조정 가능)
  • SG당 아웃바운드 룰: 60줄 (조정 가능, 인바운드와 별개로 카운트)
  • ENI당 SG: 5개, 한도 늘리면 16개까지 (조정 가능)
  • 단, SGs per ENI × rules per SG ≤ 1,000이 하드 한도

기본값이면 ENI 하나당 5 × 60 = 300줄. 한도를 둘 다 늘려도 1,000줄까지가 한계다. 마이크로서비스가 늘어나면서 룰을 CIDR로 박아 두면 이 한도가 의외로 빨리 닿는다. 그래서 AWS와 운영자 모두 SG 참조 패턴을 권장한다.

룰을 적을 때 prefix list (관리형 IP 묶음)를 source로 쓰는 옵션도 있는데, prefix list 한 줄은 그 안의 최대 엔트리 수만큼 룰 카운트에 함께 들어간다. AWS 관리형 prefix list 중 weight가 10이면 그 한 줄이 10줄 분량으로 룰 카운트를 차지한다. 이걸 모르고 박았다가 한도에 걸려 SG 생성이 실패하는 일이 있다.

SG와 NACL이 다른 일을 한다는 점

세 가지가 다르다.

  • 적용 레벨: SG는 ENI. Network ACL(NACL)은 Subnet.
  • 상태: SG는 stateful. NACL은 stateless라서 응답을 자동으로 통과시키지 않는다.
  • 룰 종류: SG는 allow only. NACL은 allow와 deny 모두.

여기서 언제 SG가 아닌 NACL이 필요한가가 자연스럽게 나온다. 한 CIDR 자체를 Subnet 단위로 통째로 차단하고 싶을 때, 예컨대 알려진 악성 IP 대역을 막아야 할 때, SG는 deny가 없으므로 못한다. NACL 인바운드에 deny 한 줄이 들어가야 그게 가능하다.

이 두 박스가 어떻게 함께 쌓이는지를 다음 편에서 짚는다. 우선 이번 글에서 잡을 것은 이렇다. SG는 ENI에 매달린 stateful, allow-only 방화벽이고, 허용하지 않은 것은 들어오지 않는다. 거기까지가 인스턴스 레벨에서 SG가 책임지는 전부다.

두 개의 세로 컬럼이 나란히 놓인 비교 다이어그램. 왼쪽 컬럼의 큰 헤더는 'Security Group'. 그 아래 세 줄 행이 차례로 'Level: ENI (instance)' 옆에 작은 서버 아이콘, 'State: Stateful' 옆에 에메랄드 그린(#10b981) 체크 아이콘, 'Rules: Allow only' 옆에 녹색 플러스 아이콘이 표시되어 있다. 오른쪽 컬럼의 큰 헤더는 'Network ACL'. 그 아래 세 줄 행이 'Level: Subnet' 옆에 서브넷 박스 아이콘, 'State: Stateless' 옆에 슬레이트 그레이 회전 화살표 아이콘, 'Rules: Allow + Deny' 옆에 녹색 플러스와 빨강 마이너스 아이콘이 함께 그려져 있다. 두 컬럼 아래로 한 줄짜리 가로 띠가 가로지르며, 왼쪽에서 패킷 한 개가 들어와 첫 번째 게이트 'NACL (subnet)'을 무상태 게이트로 통과하고, 이어 두 번째 게이트 'SG (ENI)'를 stateful 게이트로 통과한 뒤 오른쪽 끝의 서버 아이콘에 도착하는 layered 흐름을 보여 준다. AWS 오렌지(#ff9900)는 SG 강조에, 슬레이트 그레이는 NACL 강조에, 에메랄드 그린과 빨강 코랄은 상태 기호에 사용된, 둥근 모서리와 절제된 그림자가 있는 다이어그램

운영에 들어가면 매번 같이 적는 두 줄이 있다. 하나는 새 SG는 만들자마자 outbound 0.0.0.0/0을 좁힌다. 그 한 줄을 그대로 두면 어디든 콜이 새 나간다. 둘은 RDS·내부 서비스 SG의 인바운드는 SG 참조로 적는다, CIDR로 박지 않는다. 이 두 줄이 SG의 운영 비용을 절반으로 깎는다.

YouTube 영상

채널 보기
행렬의 가장 중요한 연산 - 행렬 곱셈 | 선형대수학
숫자 하나가 AI 모델의 운명을 바꾼다? | 선형대수학
Trie 자료구조 파이썬 구현: Search와 Starts With 연산 | Trie 자료구조 이야기
AI를 위한 선형대수학 - 소개 | 선형대수학
스칼라 곱셈과 내적의 기하학적 의미 | 선형대수학
AI는 왜 수백 차원의 벡터를 사용할까? 고차원 공간과 행렬 | 선형대수학
투영과 예측, 그리고 선형 결합 | 선형대수학
트라이(Trie)를 이용한 자동 완성 알고리즘 | Trie 자료구조 이야기