soobook
NETWORK

Packet Filtering Layers

AWS Security Group, iptables, ufw는 각각 어느 계층에서 패킷을 막는가, 그리고 무엇이 먼저 막히는가

방화벽이라 불리는 것들은 한 계층이 아니다. AWS Security Group은 패킷이 instance에 도달하기 전에 AWS 네트워크에서 거르고, iptables와 nftables는 게스트 커널의 Netfilter hook에서 거른다. ufw와 firewalld는 별도의 방화벽이 아니라 그 Netfilter 규칙을 대신 써 주는 frontend다.

Intro

EC2에서 서비스를 하나 띄우면 트래픽을 막을 수 있는 자리가 생각보다 많다. Security Group, Network ACL, iptables, ufw. 전부 “방화벽”이라 불리는데, 서로 어떤 관계인지는 잘 안 보인다. 그래서 이런 질문이 생긴다.

  • ufw에서 막는 것과 iptables에서 막는 것은 뭐가 다른가?
  • Security Group은 이 중 어디에 해당하나?
  • 여러 개가 겹쳐 있으면 무엇이 먼저 막히나?

결론부터 말하면 이 넷은 두 계층으로 나뉜다. Security Group과 Network ACL은 호스트 밖, 즉 AWS 네트워크 인프라에서 동작한다. iptables, nftables, ufw, firewalld는 전부 호스트 안, 게스트 커널의 같은 지점에서 동작한다. 이 구분이 서면 나머지 질문은 자연스럽게 풀린다.

호스트 안쪽부터 시작하자.

Netfilter: Hooks in the Kernel

Linux 커널은 패킷이 지나가는 길목에 검문소를 미리 박아 두었다. 이 검문소 프레임워크가 Netfilter고, 각 검문소를 hook이라 부른다. hook이란 커널이 “이 지점을 패킷이 지날 때 등록된 규칙을 실행하겠다”고 약속해 둔 호출 지점을 말한다.

hook은 다섯 개다.

flowchart TB
    subgraph P1["이 host가 목적지인 트래픽"]
        direction LR
        B1[prerouting] --> C1[input] --> D1[local process]
    end
    subgraph P2["이 host가 만든 트래픽"]
        direction LR
        D2[local process] --> C2[output] --> B2[postrouting]
    end
    subgraph P3["이 host를 거쳐 가는 트래픽"]
        direction LR
        B3[prerouting] --> C3[forward] --> E3[postrouting]
    end
    P1 ~~~ P2 ~~~ P3

패킷이 어느 hook을 지나는지는 경로에 따라 갈린다. 이 host가 목적지인 트래픽은 prerouting을 지나 input에서 끝나고, 로컬 프로세스가 만든 트래픽은 output과 postrouting을 지나 나간다. 이 host를 거쳐 다른 곳으로 가는 트래픽은 prerouting, forward, postrouting을 지난다.

“Traffic flowing to the local machine in the input path sees the prerouting and input hooks.”

여기서 중요한 사실 하나. iptables와 nftables는 둘 다 이 Netfilter hook에 규칙을 거는 도구다.

서로 다른 검문소가 아니라, 같은 검문소에 규칙 목록을 걸어 두는 두 가지 방식이다.

iptables의 INPUT, FORWARD, OUTPUT 같은 chain 이름이 hook 이름과 겹치는 게 우연이 아니다.

iptables and nftables

iptables는 hook마다 chain을 미리 정의해 둔다. filter table의 INPUT chain, nat table의 PREROUTING chain처럼 table과 chain의 조합이 고정되어 있고, 사용자는 그 안에 규칙을 줄 세운다. 규칙은 위에서 아래로 평가되고 먼저 매치된 규칙의 target(ACCEPT, DROP, REJECT)이 적용된다.

# 10.0.0.0/8에서 오는 SSH만 허용하고 나머지 SSH는 버린다
iptables -A INPUT -p tcp --dport 22 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j DROP

nftables는 iptables의 공식 대체재다.

“nftables replaces the popular {ip,ip6,arp,eb}tables”

Linux kernel 3.13부터 커널에 들어 있다. 새 패킷 분류 엔진과 nft라는 새 CLI를 제공하지만, hook 인프라, connection tracking, NAT 엔진은 기존 Netfilter 것을 그대로 재사용한다.

그래서 전환기를 사는 지금은 iptables 명령이 두 갈래다.

  • iptables-legacy: 옛 x_tables 커널 서브시스템에 규칙을 넣는 원래 구현.
  • iptables-nft: 문법은 iptables 그대로 받되, 규칙을 nf_tables 서브시스템에 넣는 호환 레이어.

최근 배포판에서 iptables를 치면 대부분 후자다. 어느 쪽인지는 버전 문자열로 확인한다.

$ iptables --version
iptables v1.8.10 (nf_tables)   # nf_tables 백엔드를 쓰는 중

두 도구를 섞어 쓰면 x_tables와 nf_tables 양쪽에 규칙이 나뉘어 들어가 예상 밖의 결과가 난다. nftables wiki도 “Beware of using both the nft and the legacy tools at the same time”이라고 경고한다. 규칙이 분명히 있는데 iptables -L에 안 보인다면 반대쪽 백엔드를 의심하자.

ufw and firewalld Are Frontends

ufw는 Ubuntu의 기본 방화벽 설정 도구다. ufw allow 22/tcp 한 줄로 포트가 열리니 iptables와는 다른 무언가처럼 보인다.

그런데 Ubuntu manpage의 정의는 “program for managing a netfilter firewall”이라고 명시되어있다.

ufw-framework 문서는 더 직접적으로 “ufw is in part a front-end for iptables-restore”라고 쓴다.

ufw는 별도의 방화벽 계층이 아니라 iptables 규칙을 대신 작성해 주는 관리 도구라는 뜻이다.

그래서 첫 번째 질문의 답이 나온다. ufw에서 막는 것과 iptables에서 막는 것은 막히는 위치가 같다. 둘 다 Netfilter hook에서 막힌다.

ufw allow 22/tcp를 실행하면 ufw는 그 규칙을 자기 rules 파일에 기록해 두었다가 iptables-restore로 커널에 적재한다.

파일은 세 개고 평가 순서가 고정되어 있다: before.rules가 먼저, user.rules가 다음, after.rules가 마지막이다.

실제로 ufw를 켠 뒤 iptables -L을 열어 보면 ufw가 만든 user-defined chain들이 INPUT chain 아래에 줄줄이 걸려 있다.

$ sudo iptables -L INPUT
Chain INPUT (policy DROP)
target         prot opt source     destination
ufw-before-input   all  --  anywhere   anywhere
ufw-after-input    all  --  anywhere   anywhere
ufw-track-input    all  --  anywhere   anywhere

그렇다면 차이는 무엇인가. 규칙을 소유하고 관리하는 주체다.

  • iptables 명령으로 직접 넣은 규칙은 재부팅하면 사라진다. 영속화는 별도 도구(iptables-persistent 등)의 몫이다.
  • ufw 규칙은 파일로 관리되어 재부팅 후에도 복원되고, ufw status로 사람이 읽기 좋은 형태로 조회된다.
  • 두 방식을 섞으면 충돌한다. ufw는 자기 rules 파일 전체를 iptables-restore로 다시 적재하므로, 손으로 끼워 넣은 iptables 규칙은 ufw reload 시점에 사라질 수 있다.

반대로 iptables -I INPUT 1 ...로 ufw chain보다 앞에 끼워 넣은 규칙은 ufw 규칙보다 먼저 평가된다. 같은 검문소 안에서도 줄 서는 순서는 있다.

firewalld는 RHEL, Fedora 계열의 같은 자리다. zone이라는 개념으로 인터페이스를 신뢰 수준별로 묶고(내부망 zone은 느슨하게, 외부망 zone은 엄격하게), 설정을 runtime과 permanent로 분리해 실험 후 저장하는 흐름을 지원한다. 백엔드는 v0.6.0(2018년)부터 nftables가 기본이고, 모든 규칙을 firewalld 전용 table로 격리해서 다른 도구와의 충돌을 줄인다.

정리하면 호스트 안의 그림은 단순하다. 검문소는 Netfilter 하나고, iptables와 nftables는 거기에 규칙을 거는 손이고, ufw와 firewalld는 그 손을 대신 움직여 주는 관리자다.

ufw는 iptables-restore로, firewalld는 nft 호출로 각각 iptables와 nftables에 규칙을 쓰지만, 두 도구 모두 커널의 같은 Netfilter hook에 규칙을 건다

Security Groups and Network ACLs

이제 호스트 밖으로 나가자. AWS에서 트래픽을 거르는 장치는 두 개인데, 걸리는 위치가 다르다.

Security Group은 instance에 붙는 가상 방화벽이다.

정확히는 ENI(Elastic Network Interface) 단위로 연결된다.

“The only traffic that reaches the instance is the traffic allowed by the security group rules”

허용된 트래픽만 instance에 도달한다. 게스트 OS 입장에서 중요한 지점이다.

Security Group이 버린 패킷은 게스트에 아예 도착하지 않는다. 게스트 커널의 Netfilter도, tcpdump도 그 패킷을 볼 기회가 없다.

Security Group의 동작 방식은 세 가지 특징으로 요약된다.

  • stateful: 방화벽이 connection의 왕복 상태를 기억한다는 뜻으로, 나가는 요청을 허용했다면 그에 대한 응답은 inbound 규칙과 무관하게 들어온다.
  • no-deny-rule: allow 규칙만 쓸 수 있고 deny 규칙이 없다. 명시적으로 허용하지 않은 트래픽이 기본 차단될 뿐이다.
  • no-order: 규칙에 순서가 없다. 연결된 모든 Security Group의 모든 규칙을 평가해서 하나라도 허용하면 통과다.

Network ACL은 subnet 경계에 걸린다. 패킷이 subnet에 들어오고 나갈 때 평가되고, subnet 내부 트래픽에는 관여하지 않는다.

성격은 Security Group과 반대로 stateless다. 왕복을 기억하지 않으므로 응답 트래픽도 별도 규칙으로 허용해야 한다.

allow와 deny를 모두 쓸 수 있고, 규칙마다 번호가 있어 낮은 번호부터 평가하다 처음 매치되는 규칙에서 멈춘다.

순서 없이 전부 평가하는 Security Group과 달리 first-match다. 특정 IP를 명시적으로 차단하고 싶을 때 Security Group에는 deny가 없으므로 Network ACL이 그 역할을 맡는다.

AWS 공식 가이드는 Security Group을 기본 수단으로 쓰고, Network ACL은 “defense in depth” 용도로 권한다. instance가 잘못된 Security Group으로 뜨더라도 subnet 차원에서 한 번 더 걸러 주는 안전망이다.

What Blocks First

이제 전체 경로를 이어 붙일 수 있다. 인터넷에서 EC2 instance 위의 애플리케이션까지, inbound 패킷이 통과해야 하는 검문소를 순서대로 늘어놓으면 이렇다.

inbound 패킷은 AWS 네트워크의 Network ACL과 Security Group을 통과한 뒤에야 EC2 instance 안의 Netfilter와 application socket에 도달한다. 앞 계층에서 막히면 뒤 계층은 패킷을 보지 못한다

Network ACL이 subnet 경계에서 먼저, Security Group이 instance 앞에서 다음, 게스트 커널의 Netfilter가 마지막이다. AWS 문서도 같은 순서로 계층을 서술한다. Internet Gateway로 들어온 트래픽이 route table을 따라 subnet으로 가고, subnet에 연결된 Network ACL이 subnet 진입을, instance에 연결된 Security Group이 instance 도달을 통제한다.

앞 계층에서 막히면 뒤 계층은 그 패킷을 구경도 못 한다. 이 성질이 디버깅에서 그대로 증상이 된다.

  • Security Group이나 Network ACL에서 막히면 게스트 안의 tcpdump에 패킷이 아예 안 잡힌다. 둘 다 조용히 버리므로 클라이언트는 connection timeout을 본다.
  • iptables의 INPUT에서 DROP되면 tcpdump에는 잡힌다. 패킷 캡처 지점이 Netfilter의 input hook보다 앞이라, 커널이 버리기 직전의 패킷까지는 보인다. 클라이언트 증상은 target에 따라 갈린다. DROP이면 timeout, REJECT면 즉시 거절 응답이 간다.
  • 모든 방화벽을 통과했는데 listen 중인 프로세스가 없으면 커널이 TCP RST를 돌려주고, 클라이언트는 connection refused를 본다.

그래서 “포트가 안 열려요”의 진단 순서는 안쪽부터가 효율적이다. instance 안에서 curl localhost:포트가 되면 애플리케이션과 Netfilter는 무죄고, 밖에서만 안 되면 Security Group과 Network ACL을 본다. tcpdump에 SYN이 보이는데 응답이 없으면 호스트 안, SYN조차 안 보이면 호스트 밖이다.

Allowing Inside, Blocking Outside

“외부 요청은 막고 내부끼리는 허용”이라는 흔한 요구를 각 계층에서 어떻게 표현하는지 보면 계층별 성격이 선명해진다.

Security Group에서는 source에 CIDR 대신 다른 Security Group의 ID를 쓸 수 있다. 예를 들어 backend용 Security Group의 inbound 규칙에 “source: sg-frontend, port 8080”을 넣으면, frontend Security Group이 붙은 instance들만 backend의 8080에 접근할 수 있다. IP가 바뀌어도 규칙이 따라오므로 cluster 내부 트래픽 제어의 기본기다.

iptables에서는 source CIDR로 표현한다. 위에서 본 예시가 정확히 이 패턴이다. 내부 대역은 ACCEPT, 그 외의 같은 포트는 DROP. ufw라면 ufw allow from 10.0.0.0/8 to any port 8080이 같은 규칙을 만들어 준다.

Kubernetes cluster라면 한 겹이 더 있다. NetworkPolicy로 “이 namespace의 pod만 이 pod에 접근”을 선언하면, 그 선언을 실제로 집행하는 것은 CNI 플러그인이고, 구현체는 결국 각 노드의 Netfilter 규칙(또는 eBPF)이다. kube-proxy도 Service 라우팅을 위해 iptables chain을 대량으로 만든다. 그래서 Kubernetes 노드에서 ufw를 무심코 켜면 pod 간 forward 트래픽이 막혀 cluster가 이상해지는 사고가 난다. pod 트래픽은 bridge와 veth를 거쳐 forward hook을 지나는데, ufw의 기본 forward 정책이 거절이기 때문이다.

계층이 여럿이라는 건 번거로움이 아니라 설계 여지다. AWS 계층에서 “누가 이 instance에 접근 가능한가”를 넓게 자르고, 호스트 계층에서 프로세스 단위의 세밀한 규칙을 얹는 식으로 역할을 나누면, 한 계층의 실수가 곧장 사고로 이어지지 않는다.

Summary

  • 커널 안의 검문소는 Netfilter hook 하나다. prerouting, input, forward, output, postrouting 다섯 지점에 규칙이 걸린다.
  • iptables와 nftables는 그 hook에 규칙을 거는 두 가지 도구다. nftables가 공식 대체재고, 요즘 배포판의 iptables 명령은 대부분 nf_tables 백엔드를 쓰는 호환 레이어다.
  • ufw와 firewalld는 별도 계층이 아니라 Netfilter 규칙을 관리해 주는 frontend다. ufw에서 막든 iptables에서 막든 패킷이 막히는 자리는 같다.
  • Security Group은 instance(ENI) 앞의 stateful 필터, Network ACL은 subnet 경계의 stateless 필터다. 둘 다 호스트 밖에서 동작하므로 여기서 막힌 패킷은 게스트에 도달하지 않는다.
  • inbound 기준 차단 순서는 Network ACL, Security Group, Netfilter 순이다. 앞에서 막히면 뒤는 패킷을 보지 못하고, 이 성질이 tcpdump 기반 진단의 근거가 된다.

References