서론

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
    end

NACL은 상태 비저장(Stateless) 방식이라 들어온 요청을 허용했더라도 응답이 아웃바운드 규칙에 없으면 막힌다. 허용과 거부를 모두 쓸 수 있고, 규칙 번호가 낮은 것부터 평가한다.