LabHub
배우기 러닝패스 코스

Network Fundamentals — Hands-on in a Linux VM

Block and Rewrite — netfilter Hooks, Conntrack, NAT

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

nftables 규칙은 패킷이 지나는 다섯 훅(prerouting·input·forward·output·postrouting) 중 하나에 걸린다. 상태 추적(conntrack)이 "이미 열린 연결의 패킷" 을 알아보므로 응답은 한 줄로 허용하고, NAT 는 같은 conntrack 위에서 주소를 바꾸고 되돌린다.

Concept map: 기본값과 순서 · 들어오는 · 내보내는 · 전

왜 이게 필요했나

방화벽은 규칙 목록이 아니라 기본값과 순서다. 정책이 drop 이면 빠뜨린 것이 막히고 accept 면 빠뜨린 것이 열린다. 그리고 규칙은 방향이 있다 — 서버로 들어오는 SYN 을 열었다고 서버가 내보내는 응답이 자동으로 열리지 않는다. 옛날엔 그래서 규칙을 두 배로 썼고, 그것이 상태 추적이 생긴 이유다. ct state established,related accept 한 줄이 "이미 열린 연결은 통과" 를 뜻하므로, 처음 여는 SYN 만 고르면 된다.

사설 주소는 인터넷에서 라우팅되지 않는다. 그런데 집·회사·클라우드·쿠버네티스 파드가 전부 사설 주소다. 이들이 바깥과 이야기하려면 경계에서 주소를 바꾸고 응답이 오면 되돌려야 한다. 되돌리려면 "이 응답이 원래 누구 것인지" 를 기억해야 하고, 그 기억이 conntrack 이다. NAT 장비가 재시작되면 모든 연결이 끊기는 이유다.

어떻게 동작하나

훅은 위치다. 자기에게 온 패킷은 prerouting → input, 자기가 보내는 패킷은 output → postrouting, 남의 것을 넘길 때는 prerouting → forward → postrouting 을 지난다. 서버를 잠그는 규칙은 input, 라우터의 규칙은 forward 에 둔다. 라우터 자신의 input 을 잠그면 관리 접속까지 막힌다.

netfilter 의 다섯 훅. 들어온 패킷은 prerouting 을 지나 라우팅 판정에서 갈려, 자기 것이면 input 을 거쳐 로컬 프로세스로 가고 남의 것이면 forward 를 거쳐 postrouting 으로 나간다. 자기가 보내는 패킷은 output 에서 시작해 postrouting 으로 나간다. DNAT 은 라우팅 전이어야 하므로 prerouting 에, SNAT 과 masquerade 는 나갈 인터페이스가 정해진 뒤여야 하므로 postrouting 에 둔다

체인은 type filter hook input priority 0; policy drop 처럼 만든다. 규칙은 위에서부터 보고 첫 매치의 판정(accept/drop)으로 끝난다. counter 를 붙이면 몇 개가 걸렸는지 세고, log 를 붙이면 커널 로그에 남긴다(네임스페이스 안이면 nf_log_all_netns=1 이 필요하다).

마스커레이드는 postrouting 에 둔다 — 라우팅이 끝나 어느 인터페이스로 나가는지 아는 자리라서다. ip saddr 10.30.0.0/24 oifname "enp1s0" masquerade 는 그 대역이 uplink 로 나갈 때 출발지를 uplink 주소로 바꾼다. DNAT 는 prerouting 에 둔다 — 라우팅 에 목적지를 바꿔야 바뀐 목적지로 경로를 찾는다. 도커의 -p 8080:80 이 만드는 것이 이 규칙이다. NAT 체인은 연결의 첫 패킷만 타고, 나머지는 conntrack 이 처리한다.

현장에서 만나는 모습

자기 잠금. SSH 로 들어가 policy drop 을 켰다. 세션이 얼어붙는다. established 허용을 먼저 넣지 않아 자기 세션의 응답이 죽은 것이다. 순서가 곧 안전이다 — 허용을 먼저, 정책을 나중에.

NAT 는 되는데 안 나간다. 마스커레이드 규칙을 넣었는데 사설망이 인터넷에 못 나간다. forward 체인의 정책이 drop 이다. NAT 는 주소를 바꿀 뿐 통과를 허락하지 않는다 — 두 체인은 다른 일이다. ip_forward 가 0 인 경우도 같은 증상이다.

방화벽 규칙을 다루는 규율

규칙 자체보다 규칙을 어떻게 바꾸느냐가 사고를 가른다. 앞에서 본 자기 잠금은 절차 하나로 거의 없앨 수 있다.

되돌릴 장치를 먼저 건다. 원격에서 규칙을 바꿀 때는, 몇 분 뒤에 자동으로 옛 규칙으로 돌아가는 예약을 먼저 걸어 두고 시작한다. 잘 되면 취소하고, 잠기면 기다렸다가 다시 들어간다. 이 습관 하나가 "서버에 못 들어가서 사람을 보냈다" 를 없앤다.

규칙은 파일로 관리하고 통째로 적용한다. 손으로 한 줄씩 넣으면 지금 상태가 무엇인지 아무도 모르게 되고, 재부팅하면 사라진다. 전체 규칙을 담은 파일을 두고 원자적으로 갈아 끼우면, 지금 도는 것과 파일이 언제나 같다.

로그를 먼저 켜고 막는다. 새 규칙으로 무엇이 막힐지 확신이 없을 때는, 막는 대신 counterlog 만 붙여 며칠 관찰한다. 걸리는 것이 없으면 그때 판정을 drop 으로 바꾼다. 앞에서 본 Pod Security Admission 의 warn → enforce 와 같은 순서다.

그리고 방화벽이 어디에 있는지를 늘 의식해야 한다. 요즘 시스템에서 패킷은 여러 겹의 필터를 지난다. 클라우드의 보안 그룹, 호스트의 nftables, 컨테이너 런타임이 만든 규칙, 그리고 서비스 메시의 정책. 어느 층에서 막혔는지 모른 채 한 층만 고치면 아무 변화가 없고, 그러다 "일단 다 열어 보자" 로 가게 된다. 앞에서 본 대로 refused 인지 타임아웃인지, 그리고 각 층의 카운터가 올라가는지를 보면 어느 층인지 좁혀진다.

마지막으로 규칙에 주석을 남긴다. nftables 는 규칙에 comment 를 붙일 수 있다. 왜 열었는지가 적혀 있지 않은 규칙은 아무도 지우지 못하고, 그렇게 쌓인 규칙 사이에서 정말 필요한 것을 찾기가 점점 어려워진다.

다음 실습에서 할 것

네임스페이스 안의 서버에 input 체인을 만들어 기본 drop 에서 시작해 lo·established·icmp·80·출처 제한 2222 를 하나씩 열고, counter 로 버려지는 것을 센다. 그다음 VM 을 라우터로 삼아 사설 네임스페이스를 마스커레이드로 인터넷에 내보내고, 양쪽 인터페이스에서 출발지가 바뀌는 것을 잡고, DNAT 로 포트를 안쪽 서버에 넘긴다.