서론
AWS에서 트래픽을 거르는 보안 그룹과 NACL이 각각 어디에 적용되는지, ENI부터 시작해서 VPC 전체, 그리고 여러 VPC를 연결하는 구간까지 점차 범위를 넓혀가며 정리해봤다. 그다음에는 NAT 게이트웨이나 Bastion, Transit Gateway를 통과하는 트래픽이 어떤 식으로 검사되는지, 그 통신 흐름을 따라가 보았다. 그 검사 결과는 VPC Flow Logs에 남게 되고, 이 로그를 SIEM으로 모으는 방법과, Wazuh가 이 과정에서 어떤 역할을 하는지도 함께 살펴봤다.
네트워크 계층과 방화벽의 적용 위치
ENI와 보안 그룹
노트북에 랜 포트가 없는 경우, 한 번쯤 USB 랜 동글을 연결해서 써본 적이 있을 것이다. 동글을 뽑아서 다른 노트북에 꽂으면, 그 노트북도 바로 유선 네트워크에 연결된다. EC2 역시 이런 동글처럼, 별도의 부품을 붙여 네트워크에 접속한다.
EC2에서는 ENI, 즉 탄력적 네트워크 인터페이스를 통해 VPC 네트워크와 데이터를 주고받는다. ENI는 VPC 안에 존재하는 가상 네트워크 카드로, MAC 주소와 프라이빗 IP, 할당되어 있다면 퍼블릭 IP, 그리고 보안 그룹 정보까지 담고 있다. 인스턴스를 만들 때 생기는 기본 ENI는 뗄 수 없다. 보조 ENI를 만들어 붙여 두면 장애가 생겼을 때 이 ENI를 기존 인스턴스에서 떼어내 다른 인스턴스에 붙일 수 있다. 이 과정에서 IP와 보안 그룹 정보도 함께 옮겨가므로, IP와 보안 그룹을 다시 잡지 않아도 된다.
flowchart TB
subgraph ec2["EC2"]
eni["ENI"]
end서버를 네트워크에 붙였으면 이제 방화벽을 켤 차례다. 리눅스에서는 iptables로 규칙을 거는데, 터미널을 열고 이 규칙을 관리하는 건 무척이나 번거롭다. 우분투의 ufw 명령어가 왜 생겼는지 한번 찾아보면 바로 답이 나온다. 이름부터 Uncomplicated Firewall이고, Ubuntu 8.04 LTS부터 기본으로 들어 있다. 그런데 서버가 열 대, 스무 대로 늘어나면? 서버마다 들어가서 ufw를 치고 있을 수는 없다.
보안 그룹은 이 방화벽 설정을 서버 밖으로 꺼내 ENI에 붙여 둔 것이다. 규칙은 콘솔이나 API에서 한 번에 관리한다. 그래서 EC2 말고도 ENI를 가진 RDS, ALB, VPC에 연결한 Lambda, 인터페이스형 VPC 엔드포인트, EFS 마운트 타깃에 보안 그룹을 붙일 수 있다.
flowchart TB
subgraph ec2["EC2"]
subgraph sg["보안 그룹"]
eni["ENI"]
end
end보안 그룹은 상태 저장(Stateful) 방식이다. 인바운드로 허용한 요청의 응답은 아웃바운드 규칙과 관계없이 나가고, 아웃바운드로 나간 요청의 응답도 인바운드 규칙과 관계없이 들어온다. 규칙은 허용만 쓸 수 있고, 새로 만든 보안 그룹은 인바운드를 모두 막고 아웃바운드를 모두 허용한다.
sequenceDiagram
participant ext as 외부
participant sg as ENI의 보안 그룹
participant ec2 as EC2
ext->>+sg: 요청
sg->>sg: 인바운드 규칙 확인
sg-x ec2: 허용 규칙이 없으면 차단
sg->>-ec2: 허용 규칙이 있으면 통과
ec2->>+sg: 응답
sg->>sg: 연결 기록 확인
sg->>-ext: 규칙 없이 통과서브넷과 NACL
서비스 규모를 조금 키워 보자. 이제 쇼핑몰 웹 서버와 주문 내역을 저장하는 DB가 따로 생겼다. 가게로 치면 주문을 빠짐없이 적어 두는 장부다. 그렇다면 이 둘을 같은 네트워크에 그냥 연결해도 괜찮을까? 만약 웹 서버 하나가 뚫리면, 바로 옆에 있던 DB까지 위험해질 수 있다. 그래서 온프렘 환경에서는 주로 네트워크를 분리하고, 네트워크 사이에 라우터를 두어 ACL로 오갈 수 있는 대상을 제한한다.
클라우드에서는 이 네트워크 분리가 '서브넷'으로 이뤄지고, 라우터의 ACL 역할을 하는 것이 'NACL'이다. EC2 인스턴스가 놓이는 서브넷에는 바로 이 NACL이 적용된다. 서브넷은 VPC 안에서 IP 대역별로 나뉘는 영역이며, 하나의 가용 영역(AZ) 안에만 만들 수 있다.
예전에 NACL이 VPC에도 적용되는지 헷갈렸는데, NACL은 VPC 단위로 만들어지고 적용은 서브넷 단위로 된다. VPC를 만들면 모든 트래픽을 허용하는 기본 NACL이 하나 생기고, 서브넷은 따로 지정하지 않으면 이 기본 NACL에 연결된다.
그러니 서브넷을 나눴다고 웹 서버에서 DB로 가는 길이 막히는 건 아니다. 같은 VPC의 서브넷끼리는 라우팅 테이블의 로컬 경로로 서로 통신하고, 기본 NACL도 모두 허용한다. 막으려면 NACL에 규칙을 넣거나, DB의 보안 그룹에서 백엔드의 보안 그룹만 DB 포트로 들어오게 허용해야 한다. 그래도 백엔드는 DB를 읽고 써야 하니, 백엔드가 뚫리면 허용된 그 포트로는 여전히 DB에 닿는다.
NACL의 검사는 트래픽이 서브넷 경계선을 넘을 때 이뤄진다. 즉, 서로 다른 서브넷끼리 통신할 경우, 데이터를 보내는 서브넷의 아웃바운드와 받는 서브넷의 인바운드 규칙 모두를 통과해야 한다. 반면, 같은 서브넷 내에서 통신한다면 경계를 넘지 않으니 NACL 검사가 따로 일어나지 않는다.
flowchart TB
subgraph subnet["서브넷"]
subgraph nacl["NACL"]
subgraph ec2a["EC2 A"]
subgraph sga["보안 그룹"]
enia["ENI"]
end
end
subgraph ec2b["EC2 B"]
subgraph sgb["보안 그룹"]
enib["ENI"]
end
end
enia <--> enib
end
endNACL은 상태 비저장(Stateless) 방식이라 들어온 요청을 허용했더라도 응답이 아웃바운드 규칙에 없으면 막힌다. 허용과 거부를 모두 쓸 수 있고, 규칙 번호가 낮은 것부터 평가한다.
응답은 요청을 보낸 쪽이 연 임시 포트(Ephemeral Port)로 돌아온다. 이 대역은 요청을 보낸 쪽의 OS나 장비마다 달라서 보통 1024~65535를 연다. 요청을 받는 서브넷이면 아웃바운드에, 요청을 보내는 서브넷이면 인바운드에 이 대역을 열어 둬야 응답이 오간다.
ㅤ | 보안 그룹 | NACL |
적용 위치 | ENI | 서브넷 |
규칙 | 허용만 | 허용, 거부 |
평가 방식 | 모든 규칙을 종합 | 번호가 낮은 규칙부터 |
응답 트래픽 | 자동 허용 | 규칙 필요 |
같은 서브넷 내 통신 | 적용됨 | 적용 안 됨 |
VPC와 인터넷 게이트웨이
아파트 한 동, 단독주택이라면 이 근방 블록의 집들은 같은 통신사 장비 하나에 물려 있다는 걸 어렴풋이 직감할 수 있다. 그렇다고 옆집 사람이 우리 집 내부망에 들어와 거실 스마트 TV, IoT 냉장고, 내 데스크탑, 가족 스마트폰에 로컬로 접속할 수 있을까? VPC도 마찬가지다. AWS의 물리 장비는 여러 고객이 함께 쓰고, 우리는 그 위에서 논리적으로 격리된 우리 집 네트워크를 하나 받는다.
VPC를 만들 때는
10.0.0.0/16 같은 사설 CIDR 대역을 지정하고, 이를 서브넷으로 나눠 쓴다.우리 집 네트워크가 인터넷으로 나가는 길은 공유기 뒤편 WAN 포트 하나다. 통신사에서 들어온 선이 거기 꽂힌다. VPC에서 그 WAN 포트 자리에 있는 게 인터넷 게이트웨이다. VPC 하나에 하나를 붙이고, AWS가 수평 확장과 이중화를 맡아서 대역폭 제한이나 가용성을 따로 신경 쓰지 않아도 된다. 인터넷 게이트웨이 자체에는 요금이 없다.
인터넷 게이트웨이는 퍼블릭 IPv4 주소 변환도 맡는다. 변환은 일대일이라, 인스턴스에 붙은 퍼블릭 IP 하나가 프라이빗 IP 하나와 짝을 이룬다. 인스턴스 OS에는 프라이빗 IP만 보이고, 인터넷 게이트웨이가 나가는 트래픽의 출발지를 인스턴스의 퍼블릭 IP나 Elastic IP로 바꾸고 들어오는 트래픽의 목적지를 다시 프라이빗 IP로 바꾼다. 그래서 인터넷과 통신하려면 서브넷의 라우팅 테이블에
0.0.0.0/0을 인터넷 게이트웨이로 보내는 경로가 있고, 인스턴스에 퍼블릭 IP나 Elastic IP가 있어야 한다. 이 경로가 있는 서브넷이 퍼블릭 서브넷, 없는 서브넷이 프라이빗 서브넷이다.flowchart TB
internet(["인터넷"])
subgraph vpc["VPC"]
igw["인터넷 게이트웨이"]
subgraph pub["퍼블릭 서브넷"]
subgraph nacl["NACL"]
subgraph ec2["EC2"]
subgraph sg["보안 그룹"]
eni["ENI"]
end
end
end
end
end
internet<--> igw
igw <--> eni인터넷 게이트웨이에는 보안 그룹도 NACL도 붙지 않는다. 인터넷에서 들어온 트래픽은 목적지 서브넷의 NACL과 ENI의 보안 그룹에서 걸러진다.
그럼 우리 가게의 장부인 DB는 어디에 둬야 할까? 장부는 주문을 처리하는 백엔드하고만 이야기하면 된다. 인터넷에 얼굴을 내밀 이유가 없다. 바깥 손님은 대변인 역할을 하는 백엔드가 맞고, DB는 안쪽에서 대변인하고만 소통하면 되지 않을까? 온프렘에서는 이렇게 외부에 공개하는 서버를 DMZ라는 완충 구역에 두고, DB는 내부망 깊숙이 둔다. 클라우드에서 DMZ 자리는 퍼블릭 서브넷, DB 자리는 프라이빗 서브넷이다.
NAT 게이트웨이
그런데 프라이빗 서브넷에 숨긴 DB도 보안 패치는 받아야 한다. 공인 IP도 없는 서버가 어떻게 인터넷에 나갈까?
IPv4 주소가 바닥났다는 이야기는 한 번쯤 들어 봤을 것이다. 약 43억 개로는 세상 모든 기기에 공인 IP를 나눠 줄 수 없어서 사설 IP가 등장했다.
10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16처럼 인터넷에서 쓰지 않기로 약속한 대역을 집이나 회사 안에서 마음껏 쓰고, 밖으로 나갈 때 공인 IP로 바꿔 나간다. 이 변환이 NAT(Network Address Translation)다. 그렇다면 이 변환을 맡아 줄 게이트웨이도 필요하지 않을까?NAT 게이트웨이가 그 역할을 한다. 여러 리소스가 NAT 게이트웨이의 공인 IP 하나를 나눠 쓴다. 프라이빗 서브넷의 리소스는 퍼블릭 서브넷에 둔 NAT 게이트웨이를 거쳐 외부로 나간다. 프라이빗 서브넷의 라우팅 테이블이
0.0.0.0/0을 NAT 게이트웨이로 보내면, NAT 게이트웨이가 출발지 주소를 자신의 Elastic IP로 바꿔 인터넷 게이트웨이로 내보내고 돌아온 응답을 원래 리소스에 돌려준다. 외부에서 먼저 시작한 연결은 안쪽으로 전달되지 않는다.NAT 게이트웨이도 내부에 ENI가 있다. 그런데 여기에는 보안 그룹을 붙일 수 없어서, NAT 게이트웨이를 드나드는 트래픽에는 퍼블릭 서브넷의 NACL이 적용된다.
flowchart TB
internet(["인터넷"])
subgraph vpc["VPC"]
igw["인터넷 게이트웨이"]
subgraph pub["퍼블릭 서브넷"]
subgraph pubnacl["NACL"]
subgraph nat["NAT 게이트웨이"]
nateni["ENI"]
end
end
end
subgraph pri["프라이빗 서브넷"]
subgraph prinacl["NACL"]
subgraph ec2["EC2"]
subgraph sg["보안 그룹"]
eni["ENI"]
end
end
end
end
end
eni --> nateni
nateni --> igw
igw --> internetIPv6는 사정이 다르다. 주소가 넉넉해서 인스턴스마다 공인 주소를 주고, 바꿀 주소가 없으니 NAT도 필요 없다. 그래도 밖에서 먼저 들어오는 연결은 막고 싶다면 송신 전용 인터넷 게이트웨이(Egress-only Internet Gateway)를 쓴다. 주소는 그대로 두고 연결 상태를 기억해서, 안에서 나간 요청의 응답만 들여보낸다. 송신 전용 인터넷 게이트웨이는 자체 요금이 없고 데이터 전송 요금만 붙는다. NAT 게이트웨이는 켜 둔 시간과 처리한 데이터 양만큼 요금이 붙으니, IPv6로 나갈 수 있는 트래픽이라면 비용을 크게 줄일 수 있다.
NAT 인스턴스
주소 변환은 리눅스 서버 한 대로도 할 수 있다. iptables에
MASQUERADE 규칙을 걸면 서버가 NAT 장비처럼 동작한다. 클라우드에서도 NAT 게이트웨이 대신 퍼블릭 서브넷의 EC2를 NAT로 쓸 수 있고, 이를 NAT 인스턴스라고 한다. EC2이므로 ENI에 보안 그룹을 붙일 수 있어서, NAT를 지나는 트래픽을 NACL과 보안 그룹 양쪽에서 제어한다.flowchart TB
internet(["인터넷"])
subgraph vpc["VPC"]
igw["인터넷 게이트웨이"]
subgraph pub["퍼블릭 서브넷"]
subgraph pubnacl["NACL"]
subgraph natgw["NAT 게이트웨이"]
natgw_eni["ENI"]
end
subgraph natinst["NAT 인스턴스 (EC2)"]
subgraph natsg["보안 그룹"]
natinst_eni["ENI"]
end
end
end
end
subgraph pri["프라이빗 서브넷"]
subgraph prinacl["NACL"]
subgraph priec2_a["프라이빗 EC2 A"]
subgraph prisg_a["보안 그룹"]
prieni_a["ENI"]
end
end
subgraph priec2_b["프라이빗 EC2 B"]
subgraph prisg_b["보안 그룹"]
prieni_b["ENI"]
end
end
end
end
end
%% 트래픽 아웃바운드 흐름
prieni_a --> natgw_eni
prieni_b --> natinst_eni
natgw_eni --> igw
natinst_eni --> igw
igw --> internet
%% AWS 스타일 컬러 테마 적용
classDef vpc fill:#fdfdfd,stroke:#8C4FFF,stroke-width:2px;
classDef pub fill:#f1fdf1,stroke:#248814,stroke-width:2px;
classDef pri fill:#f1f8fd,stroke:#00A4A6,stroke-width:2px;
classDef nacl fill:none,stroke:#666666,stroke-width:2px,stroke-dasharray: 4 4;
classDef sg fill:#fff0f0,stroke:#ff4d4d,stroke-width:2px,stroke-dasharray: 5 5;
classDef ec2 fill:#ff9900,stroke:#232F3E,stroke-width:2px,color:#fff;
classDef natgw fill:#8C4FFF,stroke:#232F3E,stroke-width:2px,color:#fff;
classDef igw fill:#8C4FFF,stroke:#232F3E,stroke-width:2px,color:#fff;
classDef eni fill:#e6e6e6,stroke:#333,stroke-width:1px,color:#000;
%% 컴포넌트에 테마 매핑
class vpc vpc;
class pub pub;
class pri pri;
class pubnacl,prinacl nacl;
class natsg,prisg_a,prisg_b sg;
class natinst,priec2_a,priec2_b ec2;
class natgw,igw natgw;
class natgw_eni,natinst_eni,prieni_a,prieni_b eni;구성은 NAT 게이트웨이와 비슷하다. 인스턴스에 퍼블릭 IP나 Elastic IP를 주고, 프라이빗 서브넷의 라우팅 테이블이 인터넷으로 가는 트래픽을 이 인스턴스로 보내게 한다. EC2는 자신이 출발지나 목적지가 아닌 트래픽을 버리기 때문에 소스 및 대상 확인(Source/Destination Check)을 꺼야 한다.
NAT 인스턴스는 트래픽이 적은 환경에서 비용이 적게 들고, EC2라서 할 수 있는 일이 많다. NAT 인스턴스는 인스턴스 요금만 내고, NAT 게이트웨이처럼 켜 둔 시간과 처리한 데이터 양에 따라 붙는 요금이 없다. 호스트에 Wazuh 같은 SIEM 에이전트를 올려 로그를 받거나 iptables 로그와 패킷 캡처로 지나가는 트래픽을 직접 볼 수 있고, Bastion을 겸하거나 포트 포워딩을 설정할 수도 있다.
운영은 사용자가 맡는다. OS 패치와 장애 조치를 직접 해야 하고, 대역폭은 인스턴스 유형에 따라 정해진다. AMI는 fck-nat 같은 오픈소스 AMI를 쓰거나 최신 Amazon Linux에 iptables를 설정해 만든다.
ㅤ | NAT 게이트웨이 | NAT 인스턴스 |
보안 그룹 | 붙일 수 없음 | 붙일 수 있음 |
NACL | 서브넷 NACL 적용 | 서브넷 NACL 적용 |
관리 | AWS | 사용자 (OS 패치, 장애 조치) |
대역폭 | 최대 100Gbps까지 확장 | 인스턴스 유형에 따름 |
Bastion 겸용 | 불가 | 가능 |
포트 포워딩 | 불가 | 직접 설정 |
여러 VPC의 연결
서비스가 커지면 개발 환경과 운영 환경, 서비스별로 VPC를 여러 개 나눠 쓰게 된다. 이제 두 VPC는 서로 남남이다. 어떻게 이어야 할까? VPC끼리 통신하려면 VPC 피어링이나 Transit Gateway로 연결해야 한다. 연결한 뒤에도 보안 그룹과 NACL은 VPC 안에서와 같은 자리에서 트래픽을 검사한다.
VPC 피어링
가장 쉬운 방법은 옆집과 벽에 문을 하나 내는 것이다. 문을 내면 두 집은 서로 드나들 수 있다. 그런데 옆집이 반대편 옆집과도 문을 냈다면, 우리 집에서 옆집을 지나 그 집까지 갈 수 있을까? 피어링에서는 못 간다.
VPC 피어링은 두 VPC를 일대일로 잇는 연결이다. 다른 계정이나 다른 리전의 VPC와도 맺을 수 있고, CIDR이 겹치는 VPC끼리는 맺을 수 없다. 연결을 만든 뒤에는 양쪽 VPC의 라우팅 테이블에 상대 VPC 대역을 피어링 연결로 보내는 경로를 직접 넣어야 한다.
피어링은 전이되지 않는다. A와 B, A와 C가 피어링되어 있어도 B와 C는 A를 거쳐 통신할 수 없어서 따로 피어링을 맺어야 한다. 상대 VPC의 인터넷 게이트웨이, NAT 게이트웨이, 게이트웨이 엔드포인트도 빌려 쓸 수 없다.
flowchart LR
a["VPC A"]
b["VPC B"]
c["VPC C"]
a <-->|피어링| b
a <-->|피어링| c같은 리전의 피어링이면 보안 그룹 규칙에 상대 VPC의 보안 그룹 ID를 쓸 수 있고, 리전이 다르면 IP 대역으로 써야 한다.
Transit Gateway
옆집까지는 문 하나면 되니 상식적이다. VPC가 셋, 넷으로 늘어나면 어떻게 될까? 집마다 서로 문을 다 내다 보면 금방 감당이 안 된다. 가운데 복도를 하나 두고 모든 집이 복도로 문을 내면 새 집은 문 하나만 내면 된다. Transit Gateway가 이 복도다. Transit Gateway는 리전 단위의 가상 라우터로, 여러 VPC를 한곳에 연결하는 허브다. VPC마다 Transit Gateway 연결(Attachment)을 만들면 Transit Gateway 라우팅 테이블을 따라 연결된 VPC끼리 통신한다. A에서 Transit Gateway를 거쳐 B로 가는 전이 라우팅이 되고, VPN, Direct Connect, 다른 리전의 Transit Gateway도 같은 방식으로 붙인다.
VPC를 연결하면 Transit Gateway가 가용 영역마다 지정한 서브넷에 ENI를 만들고, 이 ENI가 있는 서브넷의 NACL도 트래픽을 검사한다.
flowchart TB
subgraph vpca["VPC A"]
subgraph suba["서브넷"]
subgraph nacla["NACL"]
ec2a["EC2"]
end
end
subgraph tsuba["Transit Gateway 서브넷"]
subgraph tnacla["NACL"]
tenia["ENI"]
end
end
end
tgw["Transit Gateway"]
subgraph vpcb["VPC B"]
subgraph tsubb["Transit Gateway 서브넷"]
subgraph tnaclb["NACL"]
tenib["ENI"]
end
end
subgraph subb["서브넷"]
subgraph naclb["NACL"]
ec2b["EC2"]
end
end
end
vpcc["VPC C"]
ec2a <--> tenia
tenia <--> tgw
tgw <--> tenib
tenib <--> ec2b
tgw <--> vpcc
%% AWS 스타일 컬러 테마 적용
classDef vpc fill:#fdfdfd,stroke:#8C4FFF,stroke-width:2px;
classDef subnet fill:#f1f8fd,stroke:#00A4A6,stroke-width:2px;
classDef tsubnet fill:#f1fdf1,stroke:#248814,stroke-width:2px;
classDef nacl fill:none,stroke:#666666,stroke-width:2px,stroke-dasharray: 4 4;
classDef ec2 fill:#ff9900,stroke:#232F3E,stroke-width:2px,color:#fff;
classDef eni fill:#e6e6e6,stroke:#333,stroke-width:1px,color:#000;
classDef tgw fill:#8C4FFF,stroke:#232F3E,stroke-width:2px,color:#fff;
%% 컴포넌트에 테마 매핑
class vpca,vpcb,vpcc vpc;
class suba,subb subnet;
class tsuba,tsubb tsubnet;
class nacla,tnacla,naclb,tnaclb nacl;
class ec2a,ec2b ec2;
class tenia,tenib eni;
class tgw tgVPC A에서 나가는 트래픽은 Transit Gateway 서브넷 NACL의 인바운드 규칙으로 검사된다.
sequenceDiagram
box VPC A
participant ec2 as EC2
participant sg as ENI의 보안 그룹
participant nacl as 서브넷 NACL
participant tnacl as Transit Gateway 서브넷 NACL
end
participant tgw as Transit Gateway
ec2->>sg: 요청
sg->>nacl: 아웃바운드 규칙 통과
nacl->>tnacl: 아웃바운드 규칙 통과
tnacl->>tgw: 인바운드 규칙 통과VPC B로 들어오는 트래픽은 Transit Gateway 서브넷 NACL의 아웃바운드 규칙으로 검사된다.
sequenceDiagram
participant tgw as Transit Gateway
box VPC B
participant tnacl as Transit Gateway 서브넷 NACL
participant nacl as 서브넷 NACL
participant sg as ENI의 보안 그룹
participant ec2 as EC2
end
tgw->>tgw: 라우팅 테이블 조회
tgw->>tnacl: 요청
tnacl->>nacl: 아웃바운드 규칙 통과
nacl->>+sg: 인바운드 규칙 통과
sg->>sg: 인바운드 규칙 확인
sg->>-ec2: 통과그래서 보통 Transit Gateway 연결용 서브넷을 따로 두고 그 NACL은 열어 둔 채, 통제는 EC2 서브넷의 NACL과 보안 그룹에서 한다.
보안 그룹은 2024년 9월부터 Transit Gateway를 거쳐서도 같은 리전 안에서 상대 VPC의 보안 그룹을 참조할 수 있다. Transit Gateway에서 이 기능을 켜야 하고, 참조는 인바운드 규칙에서만 된다.
ㅤ | VPC 피어링 | Transit Gateway |
연결 구조 | 두 VPC를 일대일로 연결 | 여러 VPC를 허브에 연결 |
전이 라우팅 | 안 됨 | 됨 |
보안 그룹 참조 | 같은 리전, 인바운드와 아웃바운드 규칙 | 같은 리전, 인바운드 규칙 |
추가 ENI와 서브넷 | 없음 | 가용 영역마다 필요 |
요금 | 연결 자체는 무료, 데이터 전송 요금 | 연결 시간과 처리한 데이터 양 |
통신 경로별 검사
NAT 게이트웨이를 거치는 외부 통신
프라이빗 EC2가 NAT 게이트웨이를 거쳐 외부와 통신하려면 EC2 ENI의 보안 그룹과 두 서브넷의 NACL이 모두 열려 있어야 한다. NAT 게이트웨이에는 보안 그룹이 없다.
sequenceDiagram
box 프라이빗 서브넷
participant ec2 as EC2
participant sg as ENI의 보안 그룹
participant pri as NACL
end
box 퍼블릭 서브넷
participant pub as NACL
participant nat as NAT 게이트웨이
end
participant igw as 인터넷 게이트웨이
participant srv as 외부 서버
ec2->>sg: 요청
sg->>pri: 아웃바운드 규칙 통과
pri->>pub: 아웃바운드 규칙 통과
pub->>nat: 인바운드 규칙 통과
nat->>nat: 주소 변환
nat->>pub: 요청
pub->>igw: 아웃바운드 규칙 통과
igw->>srv: 요청응답은 EC2와 NAT 게이트웨이가 연 임시 포트로 돌아온다. 퍼블릭 서브넷과 프라이빗 서브넷의 NACL 인바운드에 이 대역이 없으면 요청은 나가도 응답이 들어오지 못한다.
sequenceDiagram
box 프라이빗 서브넷
participant ec2 as EC2
participant sg as ENI의 보안 그룹
participant pri as NACL
end
box 퍼블릭 서브넷
participant pub as NACL
participant nat as NAT 게이트웨이
end
participant igw as 인터넷 게이트웨이
participant srv as 외부 서버
srv->>igw: 응답
igw->>pub: 응답
pub->>nat: 인바운드 규칙 통과
nat->>nat: 주소 복원
nat->>pub: 응답
pub->>pri: 아웃바운드 규칙 통과
pri->>sg: 인바운드 규칙 통과
sg->>ec2: 규칙 없이 통과Bastion을 거치는 관리 접속
앞에서 DB는 프라이빗 서브넷에 숨겨 두었다. 그런데 새벽에 장애가 터져서 DB에 직접 쿼리를 날려야 하는 끔찍한 상황이 오면? 밖에서는 DB로 들어갈 길이 없다. 이때 거쳐 가는 서버가 Bastion 호스트다.
Bastion 호스트는 외부의 관리자가 프라이빗 EC2에 SSH나 RDP로 접속할 때 거치는 EC2다. 퍼블릭 서브넷에 퍼블릭 IP(또는 Elastic IP)를 가진 인스턴스로 두고, 프라이빗 EC2의 보안 그룹 인바운드에는 IP 대역 대신 Bastion의 보안 그룹 ID를 출발지로 넣는다. 이를 SG 체이닝이라 하고, Bastion을 거쳐 온 트래픽만 허용된다.
구간 | 검사 지점 | 허용 조건 |
관리자 PC에서 Bastion | 퍼블릭 서브넷 NACL 인바운드 | 22번 포트 |
ㅤ | Bastion 보안 그룹 인바운드 | 관리자 IP |
Bastion에서 프라이빗 EC2 | Bastion 보안 그룹 아웃바운드 | ㅤ |
ㅤ | 퍼블릭 서브넷 NACL 아웃바운드 | 22번 포트 |
ㅤ | 프라이빗 서브넷 NACL 인바운드 | 22번 포트 |
ㅤ | 프라이빗 EC2 보안 그룹 인바운드 | Bastion 보안 그룹 |
응답은 보안 그룹에서는 자동으로 통과하고, NACL에서는 임시 포트로 돌아가는 규칙이 두 서브넷에 모두 있어야 한다.
하나의 서브넷이 침해되면 안전할까?
웹 서버가 있는 퍼블릭 서브넷이 뚫렸다. DB는 프라이빗 서브넷에 있으니 안전할까? 공격자는 웹 서버를 발판 삼아 옆으로 움직인다.
같은 서브넷 안에서는 NACL이 끼어들지 않아서 보안 그룹만 남는다. VPC의 기본 보안 그룹에는 같은 그룹끼리 모든 트래픽을 허용하는 인바운드 규칙이 들어 있다. 서버마다 기본 보안 그룹을 그대로 붙여 뒀다면, 웹 서버에서 옆 서버의 어느 포트로든 들어갈 수 있다.
프라이빗 서브넷의 DB도 멀지 않다. 서브넷끼리는 로컬 경로로 이어져 있고, DB의 보안 그룹은 백엔드가 쓰는 DB 포트를 열어 두었다. 웹 서버 설정 파일에 DB 접속 정보까지 있다면, 공격자는 그 포트로 정상 요청처럼 DB를 읽는다.
flowchart TB
subgraph pub["퍼블릭 서브넷"]
web["웹 서버"]
peer["EC2"]
end
subgraph pri["프라이빗 서브넷"]
db["DB"]
end
web --> peer
web -->|DB 포트| db
%% AWS 스타일 컬러 테마
classDef pub fill:#f1fdf1,stroke:#248814,stroke-width:2px
classDef pri fill:#f1f8fd,stroke:#00A4A6,stroke-width:2px
classDef ec2 fill:#ff9900,stroke:#232F3E,stroke-width:2px,color:#fff
classDef db fill:#3366cc,stroke:#232F3E,stroke-width:2px,color:#fff
class pub pub
class pri pri
class web,peer ec2
class db db네트워크 규칙만으로는 모든 경로를 완전히 막을 수 없다. 예를 들어, 웹 서버에서 인스턴스 메타데이터(
169.254.169.254)에 접근해 IAM 역할의 임시 자격 증명을 빼내는 일이 가능하다. 이때 이 요청은 보안 그룹이나 NACL로 차단할 수 없다.그렇다면 이렇게 읽어낸 데이터는 어디로 빠져나갈까? DB가 프라이빗 서브넷에 있으면 외부와 끊겨 있다고 생각하기 쉽다. 하지만 실제로 DB는 조회 요청을 보낸 서버에게 결과만 돌려줄 뿐이고, 이 데이터를 외부로 내보내는 건 결국 그 서버다. 만약 공격자가 이미 EC2 권한을 손에 넣었다면, 그 EC2를 그대로 이용할 수 있다. 퍼블릭 서브넷에 있는 웹 서버라면 인터넷 게이트웨이를 통해 데이터를 내보낼 수 있고, 프라이빗 서브넷에 있는 EC2(Github Actions Self-hosted runner등)도 라우팅 테이블에 NAT 경로만 있다면 NAT 게이트웨이를 거쳐 밖으로 빠져나갈 수 있다. 더군다나 빼낸 자격 증명에 EC2 생성 권한까지 있다면 상황은 더 쉬워진다. 퍼블릭 서브넷에 퍼블릭 IP가 달린 EC2를 새로 만들고, DB 접근이 허용된 보안 그룹을 붙인 뒤 DB에서 직접 데이터를 읽고 바깥으로 내보낼 수 있다.
flowchart TB
subgraph pri["프라이빗 서브넷"]
db["DB"]
backend["프라이빗 안에 있던 기존 EC2"]
end
subgraph pub["퍼블릭 서브넷"]
web["웹 서버"]
newec2["🥷 새로 만든 EC2"]
nat["NAT 게이트웨이"]
end
ext(["외부"])
%% [탈취 경로 1] 권한 악용해 새로 만든 자원을 통한 유출 (0, 1번 선)
db -->|조회 결과| newec2
newec2 -->|인터넷 게이트웨이| ext
%% [탈취 경로 2] 장악된 기존 자원을 통한 유출 (2, 3, 4, 5, 6번 선)
db -->|조회 결과| web
web -->|인터넷 게이트웨이| ext
db -->|🥷 정보 유출| backend
backend --> nat
nat --> ext
%% 새로 만든 자원 유출 경로 (빨간색)
linkStyle 0,1 stroke:#ff4d4d,stroke-width:3px,color:#ff4d4d
%% 기존 자원 유출 경로 (주황색)
linkStyle 5,6 stroke:#ff8c00,stroke-width:3px,color:#ff8c00
%% AWS 스타일 컬러 테마
classDef pub fill:#f1fdf1,stroke:#248814,stroke-width:2px
classDef pri fill:#f1f8fd,stroke:#00A4A6,stroke-width:2px
classDef ec2 fill:#ff9900,stroke:#232F3E,stroke-width:2px,color:#fff
classDef db fill:#3366cc,stroke:#232F3E,stroke-width:2px,color:#fff
classDef natgw fill:#8C4FFF,stroke:#232F3E,stroke-width:2px,color:#fff
classDef ext fill:#fdfdfd,stroke:#333,stroke-width:2px
class pub pub
class pri pri
class web,backend,newec2 ec2
class db db
class nat natgw
class ext ext이러한 사고를 방지하기 위해서는 DB를 조회할 수 있는 서버가 외부로 접근하는 범위부터 줄여야 한다. DB를 조회할 수 있는 서버가 외부로 접근하는 범위부터 줄여야 한다. 웹 서버나 백엔드 EC2의 보안 그룹도 아웃바운드를 무작정 모두 허용하지 말고, 꼭 필요한 목적지와 포트만 열어두는 게 좋다. 외부로 나갈 일이 없는 서브넷이라면 라우팅 테이블에서 NAT 경로를 아예 빼버리고, S3에 있는 Amazon Linux 패키지 저장소나 다른 AWS 서비스는 VPC 엔드포인트를 연결해서 사용한다. DB 보안 그룹 역시 VPC 전체가 아니라 백엔드 보안 그룹만 허용해두면, 새로 만든 EC2 인스턴스가 그 보안 그룹을 달지 않는 이상 DB에 접근할 수 없다. 인스턴스 역할도 최소한의 권한만 주고, EC2 생성이나 보안 그룹 변경 권한 등은 빼두도록 한다. 만약 서버에서 평소와 다른 외부 IP로 접속 시도가 있으면, 그런 흐름도 그대로 Flow Logs에 기록이 남는다.
WAF
WAF는 HTTP 요청 내용을 보고 SQL Injection이나 XSS 같은 공격을 걸러내는 7계층 방화벽이다. CloudFront, ALB, API Gateway에 연결한다.
VPC Flow Logs
방화벽이 차단한 트래픽은 아무 흔적 없이 사라진다. 그래서 누군가 우리 서버에 접근을 시도했는지, 아니면 설정 오류로 정상 요청까지 막힌 건 아닌지 확인하려면 로그가 꼭 필요하다. 지금까지 살펴본 검사의 결과는 ENI별로 VPC Flow Logs에 남으며, 이 로그는 CloudWatch Logs나 S3로 보낼 수 있다. Flow Logs는 VPC, 서브넷, ENI 중 원하는 단위로 설정할 수 있다. VPC 전체에 설정하면 그 안의 서브넷과 ENI의 기록도 함께 모여 관리가 편하다. 또한, Transit Gateway에도 Flow Logs를 켜면, 여러 VPC 사이를 오가는 트래픽을 Transit Gateway 기준으로도 한눈에 파악할 수 있다.

CloudWatch 로그 그룹에서 ENI 하나의 기록을 열면, 여러 줄의 로그가 차례로 쌓여 있는 걸 볼 수 있다. 한 줄이 곧 하나의 네트워크 흐름을 나타낸다. 각 줄은 앞에서부터 버전, 계정 ID, ENI ID, 출발지 IP, 목적지 IP, 출발지 포트, 목적지 포트, 프로토콜, 패킷 수, 바이트 수, 시작 시각, 끝 시각 순서로 정보가 적혀 있고, 마지막 두 칸에는 action과 로그 상태가 담겨 있다. 시각은 1970년을 기준으로 센 유닉스 시간으로 표기된다.
예를 들어 첫 줄을 보면, 83.234.179.125라는 IP가 이 ENI(172.31.22.145)의 80번 포트로 TCP(프로토콜 6)를 이용해 접속을 시도했지만, REJECT로 기록되어 있다. 셋째와 넷째 줄도 다른 외부 IP가 마찬가지로 80번 포트를 두드렸지만 모두 막혔다. 80번 포트를 열지 않았으니 당연히 차단된 것이고, 인터넷에 노출된 IP에는 이런 스캔 시도가 끊임없이 들어오는 게 흔하다.
둘째 줄은 ACCEPT로 기록되어 있다. 91.189.89.198의 123번 포트에서 UDP(프로토콜 17)로 들어온 트래픽인데, 123번은 보통 시간 동기화(NTP)에 사용되는 포트다. 이 경우는 인스턴스가 시간 서버에 먼저 요청을 보냈고, 그 응답이 다시 들어온 것이다. 보안 그룹은 기존 연결이 있는 경우 그 응답을 자동으로 허용하므로, 인바운드 규칙에 123번을 따로 열지 않아도 통과한다.
action이 ACCEPT면 보안 그룹과 NACL을 모두 통과했다는 의미이고, REJECT면 둘 중 하나 이상에서 차단된 것이다. 다만, 어느 쪽의 몇 번 규칙에서 막혔는지는 로그에 나오지 않기 때문에 출발지 IP, 목적지 포트, 방향 등을 설정된 규칙과 일일이 비교하면서 확인해야 한다.
AWS가 제공하는 DNS 서버(Route 53 Resolver)로 가는 트래픽, 인스턴스 메타데이터(
169.254.169.254) 요청, Windows 라이선스 활성화 트래픽, DHCP 트래픽은 Flow Logs에 남지 않는다. DHCP는 기기가 네트워크에 연결될 때 IP, 서브넷 마스크, 게이트웨이, DNS 서버 주소를 자동으로 받아 오는 프로토콜이다. IP는 일정 기간 임대되고 기기가 떠나면 회수된다. EC2에서 IP를 직접 설정하지 않아도 통신이 되는 것은 AWS가 VPC마다 DHCP를 운영하기 때문이고, 이 설정은 DHCP 옵션 세트로 바꾼다.막힌 위치를 확인하는 도구
REJECT가 어느 규칙에서 났는지 확인하려면 라우팅, 보안 그룹, NACL 설정을 함께 봐야 하고, 이를 도와주는 도구는 다음과 같다.구분 | 도구 | 방식 |
AWS 네이티브 | VPC Reachability Analyzer | 출발지와 목적지를 지정하면 실제 패킷 없이 설정으로 경로를 계산하고, 막히는 컴포넌트와 규칙을 알려 줌 |
ㅤ | VPC Network Access Analyzer | 인터넷에서 프라이빗 DB 서브넷으로 들어오는 경로처럼 조건에 맞는 경로를 전체 설정에서 찾음 |
CNAPP, CSPM | Wiz, Prisma Cloud, Orca Security | 자산과 네트워크 흐름을 그래프로 만들어 REJECT와 설정을 연결해 보여 줌 |
NSPM | Tufin, AlgoSec, FireMon | 온프렘과 클라우드 방화벽 룰셋을 모아 흐름을 막은 룰을 찾고, 룰 변경 시 충돌을 검사 |
SIEM | Splunk ES, Elastic Security | Flow Logs와 AWS Config 설정 데이터를 쿼리로 조인해 원인 규칙을 함께 표시 |
SIEM으로 모으는 네트워크 로그
Flow Logs는 SIEM으로 모으는 네트워크 로그 중 하나이고, 보통 다음 로그와 함께 모은다.
로그 | 기록 내용 | 탐지 예시 |
VPC Flow Logs | ENI를 오간 IP 트래픽과 허용, 거부 결과 | 포트 스캔, 비정상 외부 IP 통신, Bastion 무차별 대입 |
CloudTrail | AWS API 호출 기록 | 보안 그룹 22번 포트 전체 개방, NACL 규칙 삭제, 라우팅 테이블 변경 |
WAF 로그, ALB 액세스 로그 | 7계층 차단 내역, HTTP 요청 상세 | 웹 공격 시도 |
Route 53 Resolver 쿼리 로그 | VPC 안에서 조회한 도메인 | 악성 C&C 서버 통신 |
실시간 탐지가 필요하면 로그를 CloudWatch Logs로 보내고, 구독 필터로 Amazon Data Firehose(구 Kinesis Data Firehose)에 넘겨 SIEM으로 전달한다. 비용 절감이나 장기 보관이 목적이면 S3에 모아 두고 SIEM이 주기적으로 가져가게 하며, S3 객체 잠금(Object Lock)을 걸어 로그 삭제를 막는다.
Flow Logs는 양이 많아서
REJECT만 기록하도록 줄이기도 한다. GuardDuty를 켜면 Flow Logs와 CloudTrail을 분석한 탐지 결과가 나오므로, 원본 대신 이 결과를 SIEM으로 보내 비용을 줄일 수도 있다.Wazuh
Wazuh는 오픈소스 SIEM이자 XDR 플랫폼이다. 상용 SIEM 대신 각종 로그를 수집하고 분석하는 데 쓸 수 있다. AWS 모듈(
aws-s3)을 사용하면 에이전트 설치 없이 S3에 저장된 VPC Flow Logs, CloudTrail, GuardDuty, WAF 로그를 가져올 수 있다. 그리고 디코더를 통해 IP, 포트, 프로토콜 등 주요 필드도 파싱한다.Wazuh는 다양한 로그를 시간순으로 묶어서 위협 여부를 판단한다. 예를 들어, 룰을 작성하면 짧은 시간에 여러 포트에서
REJECT가 발생할 때 포트 스캔 가능성을 알리고, 같은 외부 IP에서 CloudTrail 로그에 AccessDenied가 함께 기록되면 더 높은 수준의 위협으로 인식하게 할 수 있다. 이런 탐지가 이루어지면, Active Response 기능으로 직접 작성한 스크립트를 실행해서 공격 IP를 AWS API로 NACL 거부 규칙에 추가할 수 있다. 다만 REJECT가 어느 보안 그룹이나 NACL 규칙에서 발생했는지까지 보여 주는 기능은 제공되지 않는다.
