iptables 로 패킷 필터링 관리하기

LinuxBeginner
지금 연습하기

소개

iptables는 Linux 패킷 필터링 규칙을 관리하는 명령줄 인터페이스입니다. 최신 배포판 중 상당수는 nftables 백엔드를 통해 이러한 규칙을 구현하지만, 관리자는 여전히 iptables 명령 구문을 사용하는 작업과 기존 시스템을 자주 접하게 됩니다.

초보자는 방화벽을 순서가 있는 점검 목록으로 생각하면 이해하기 쉽습니다. 패킷이 체인으로 들어오면 각 규칙이 인터페이스, 프로토콜, 포트 등의 조건과 패킷이 일치하는지 확인하고, ACCEPT 또는 REJECT 같은 대상이 그 결과를 결정합니다. INPUT 체인은 로컬 호스트를 대상으로 하는 패킷을 처리합니다. 규칙은 위에서 아래 순서로 평가되므로 규칙의 내용뿐 아니라 배치 위치도 중요합니다.

이 실습에서는 하나의 로컬 웹 서비스만 대상으로 하여 해당 TCP 포트에 꼭 필요한 범위의 변경만 수행합니다. 현재 방화벽을 확인하고, 요청을 거부하는 규칙을 추가한 뒤 실제 트래픽으로 그 효과를 검증합니다. 그런 다음 명시적인 허용 규칙으로 교체하고, 방화벽 전체를 초기화하거나 관련 없는 상태를 다시 작성하지 않은 채 실습에서 추가한 규칙만 제거합니다.

방화벽과 테스트 서비스 확인

이 단계에서는 설치된 iptables 구현을 확인하고 방화벽 규칙을 변경하기 전에 로컬 테스트 서비스가 정상적으로 작동하는지 확인합니다.

이후 모든 명령이 실습 작업 공간에서 실행되도록 프로젝트 디렉터리로 이동합니다.

cd /home/labex/project

command -v를 사용하여 셸이 실행할 실행 파일의 위치를 확인합니다.

command -v iptables

이 확인 방법은 명령이 설치되어 있다고 가정하는 것보다 유용합니다. 일반적으로 /usr/sbin/iptables와 같은 실제 실행 파일 경로를 보여 주며, 셸에서 명령을 찾지 못하면 0 이 아닌 상태 코드를 반환합니다.

버전과 백엔드 정보를 표시합니다. 이 Ubuntu 호스트에서는 출력에 일반적으로 nf_tables가 포함됩니다. 이는 익숙한 iptables 구문이 최신 커널 규칙 집합 백엔드로 변환된다는 뜻입니다.

sudo iptables --version

다음과 비슷한 출력이 표시되어야 합니다.

iptables v1.8... (nf_tables)

백엔드 이름은 이 실습에서 사용하는 명령을 바꾸지 않습니다. 계속 iptables로 규칙을 생성하고 확인하면 되며, 해당 구현이 커널 내부에서 규칙을 표현하는 방식을 처리합니다.

포트를 필터링하기 전에 상태 확인 엔드포인트를 요청합니다. 응답이 성공하면 애플리케이션이 준비된 상태라는 뜻이므로, 이후 요청이 실패할 경우 그 원인을 방화벽 규칙으로 판단할 수 있습니다.

curl --noproxy '*' --fail --silent http://127.0.0.1:18086/health

여기서 127.0.0.1은 IPv4 루프백 주소이므로 트래픽은 외부 네트워크로 나가지 않고 로컬 lo 인터페이스를 통해 나갔다가 돌아옵니다. --noproxy '*'는 프록시 설정이 요청을 다른 곳으로 우회하지 못하게 하고, --fail은 HTTP 오류 발생 시 실패 상태를 반환하며, --silent는 진행률 표시를 숨겨 서비스 응답을 쉽게 확인할 수 있게 합니다.

서비스는 다음을 반환합니다.

iptables filter service ready

INPUT 체인 확인 및 거부 규칙 추가

이 단계에서는 현재 INPUT 체인을 확인하고, 테스트 포트로 향하는 루프백 TCP 트래픽을 거부하는 정확한 규칙 하나를 삽입합니다.

-S 옵션은 규칙을 명령어와 유사한 형식으로 출력합니다. INPUT을 지정하면 이 호스트를 대상으로 하는 패킷에 해당하는 출력만 확인할 수 있습니다.

sudo iptables -S INPUT

첫 번째 줄에는 일반적으로 체인 정책이 표시됩니다. 예를 들어 -P INPUT ACCEPT와 같이 나타납니다. 이 정책은 앞선 규칙에서 최종 결정을 내리지 못했을 때만 적용되는 기본 동작입니다. -A INPUT으로 시작하는 다음 줄들은 현재 해당 체인에 추가된 규칙입니다.

운영 관점에서 확인하려면 -L로 체인을 나열하고, -n으로 느린 이름 조회를 생략하며, --line-numbers로 각 규칙의 위치를 표시합니다. 규칙 순서가 중요한 이유는 처음으로 일치하는 종료 대상이 패킷의 최종 결과를 결정하기 때문입니다.

sudo iptables -L INPUT -n --line-numbers

두 출력 방식은 서로 다른 목적에 적합합니다. 재사용 가능한 규칙 구문이 필요할 때는 -S가 편리하고, 규칙 위치와 패킷 카운터 및 운영 상태를 빠르게 확인할 때는 -L이 편리합니다.

-I INPUT 1을 사용하여 실습 규칙을 첫 번째 위치에 삽입합니다. 이 규칙은 루프백 인터페이스 lo, TCP 프로토콜, 대상 포트 18086에 일치합니다. tcp-reset과 함께 사용하는 REJECT 대상은 연결을 즉시 거부하므로, 클라이언트가 패킷 삭제로 인한 시간 초과를 기다리지 않아도 됩니다.

sudo iptables -I INPUT 1 -i lo -p tcp --dport 18086 -j REJECT --reject-with tcp-reset

명령을 왼쪽에서 오른쪽으로 읽어 보겠습니다. -i lo는 수신 인터페이스를 제한하고, -p tcp는 프로토콜을 선택하며, --dport 18086은 서비스를 선택하고, -j REJECT는 거부 대상에게 처리를 넘깁니다. -I는 지정한 위치에 규칙을 삽입합니다. 반면 -A는 규칙을 마지막에 추가하므로, 앞선 규칙이 이미 일치하면 새 규칙이 실행되지 않을 수 있습니다. 이처럼 조건을 좁게 지정하면 관련 없는 포트나 외부 인터페이스를 차단하지 않습니다.

REJECT와 DROP은 같은 동작이 아닙니다. REJECT는 오류 응답을 보내지만 DROP은 패킷을 조용히 폐기합니다. TCP 재설정은 이 로컬 테스트를 빠르고 예측 가능하게 실패시키므로, 시간 초과를 기다리는 것보다 원인을 진단하기 쉽습니다.

-C를 사용하여 정확히 동일한 규칙이 존재하는지 확인합니다. 확인에 성공하면 종료 상태는 0 이 됩니다. 이 호스트의 nftables 기반 구현에서는 정규화된 일치 규칙도 출력하지만, 다른 구현에서는 아무 출력 없이 종료할 수도 있습니다.

sudo iptables -C INPUT -i lo -p tcp --dport 18086 -j REJECT --reject-with tcp-reset

이때 이식성이 있는 결과는 화면에 텍스트가 출력되었는지가 아니라 -C의 종료 상태입니다. 따라서 동일한 명령을 셸 스크립트와 검증 절차에서도 활용할 수 있습니다.

체인을 다시 나열합니다. 새 규칙이 1 번 규칙으로 표시되고 tcp dpt:18086이 포함되어 있어야 합니다.

sudo iptables -L INPUT -n --line-numbers

차단된 트래픽 검증

이 단계에서는 앞서 실행한 요청을 다시 보내고 규칙 카운터를 확인하여 트래픽이 애플리케이션이 아니라 방화벽에 의해 거부되었음을 검증합니다.

요청을 if 문 안에서 실행합니다. 요청이 실패하는 것이 정상적인 상황이므로 else 분기에서 예상된 실패를 명확한 메시지로 바꾸고 안내된 작업을 계속 진행할 수 있게 합니다.

if curl --noproxy '*' --fail --silent http://127.0.0.1:18086/health; then
  echo "Unexpected: request succeeded"
else
  echo "Expected: iptables rejected the request"
fi

셸 조건문은 명령의 종료 상태를 기준으로 판단합니다. 종료 상태가 0 이면 then을 실행하고, 0 이 아니면 else를 실행합니다. 방화벽이 보낸 재설정으로 인해 curl은 0 이 아닌 상태를 반환하므로, 여기서는 else 분기에 도달하는 것이 예상된 결과입니다.

예상되는 메시지는 다음과 같습니다.

Expected: iptables rejected the request

이제 -v를 사용하여 자세한 카운터를 포함한 체인 정보를 확인합니다. 요청이 실행된 후 포트 18086 규칙의 패킷 및 바이트 수가 0 보다 커야 합니다.

sudo iptables -L INPUT -n -v --line-numbers

출력에서 pkts 열은 일치한 패킷 수를, bytes 열은 해당 패킷의 총 크기를 나타냅니다. 카운터는 중요한 근거가 됩니다. -C로 확인하면 규칙이 존재한다는 사실을 증명할 수 있고, 카운터 증가를 확인하면 실제 트래픽이 해당 규칙에 도달하여 일치했다는 사실을 증명할 수 있습니다. 카운터가 계속 0 이라면 애플리케이션을 의심하기 전에 인터페이스, 프로토콜, 포트, 규칙 위치를 확인하십시오.

거부 규칙을 허용 규칙으로 교체

이 단계에서는 정확한 거부 규칙을 삭제하고, 동일한 트래픽을 허용하는 명시적 규칙을 추가한 다음 접근이 다시 가능해지는지 확인합니다.

아래의 -D 형식은 전체 일치 조건과 대상을 사용하여 규칙을 식별합니다. 체인을 초기화하지 않고 실습에서 추가한 변경 사항만 제거하므로 더 안전합니다.

sudo iptables -D INPUT -i lo -p tcp --dport 18086 -j REJECT --reject-with tcp-reset

규칙은 줄 번호로도 삭제할 수 있지만, 규칙을 삽입하거나 제거할 때마다 줄 번호가 바뀝니다. 정확한 사양으로 삭제하면 반복 가능한 절차에서 의도가 더 분명해지고 인접한 다른 규칙을 잘못 제거할 가능성도 줄어듭니다. 여기서는 iptables -F를 사용하지 마십시오. 플러시하면 선택한 체인의 모든 규칙이 제거되어 이 실습과 관련 없는 규칙까지 삭제됩니다.

동일한 인터페이스, 프로토콜, 포트에 대해 ACCEPT 규칙을 삽입합니다.

sudo iptables -I INPUT 1 -i lo -p tcp --dport 18086 -j ACCEPT

ACCEPT는 일치하는 패킷이 네트워크 스택을 계속 통과하도록 허용합니다. 첫 번째 위치에 삽입했으므로 앞선 종료 규칙에 의해 결과가 가려지지 않습니다. 일치 조건은 이전과 마찬가지로 매우 제한적이며, 변경된 부분은 대상뿐입니다.

정확한 허용 규칙을 확인합니다.

sudo iptables -C INPUT -i lo -p tcp --dport 18086 -j ACCEPT

앞서 실행했던 상태 확인 요청을 다시 보냅니다.

curl --noproxy '*' --fail --silent http://127.0.0.1:18086/health

다시 다음 응답을 확인할 수 있습니다.

iptables filter service ready

실습에서 추가한 규칙 제거

이 단계에서는 임시 허용 규칙을 제거하고 이 실습에서 추가한 규칙이 더 이상 남아 있지 않은지 확인합니다.

정확한 ACCEPT 규칙을 삭제합니다. 앞에서와 마찬가지로 체인을 초기화하거나 플랫폼 또는 다른 관리자가 소유한 규칙을 건드리지 않습니다.

sudo iptables -D INPUT -i lo -p tcp --dport 18086 -j ACCEPT

명시적인 허용 규칙을 제거한다고 해서 서비스가 자동으로 차단되는 것은 아닙니다. 해당 규칙이 사라지면 남아 있는 규칙과 마지막으로 체인 정책이 패킷의 처리 결과를 결정합니다. 이 실습 환경에서는 기존 방화벽이 로컬 요청을 허용하도록 구성되어 있습니다.

규칙이 제거되었는지 다시 확인합니다. 이번에는 확인 실패가 예상되므로 iptables -C가 0 이 아닌 상태를 반환할 때만 || 오른쪽의 메시지가 실행됩니다.

sudo iptables -C INPUT -i lo -p tcp --dport 18086 -j ACCEPT 2>/dev/null || echo "Lab allow rule removed"

다음과 같이 표시되어야 합니다.

Lab allow rule removed

마지막으로 명시적 규칙을 제거해도 애플리케이션이 중단되지 않았는지 확인합니다. 이제 체인의 기존 정책과 규칙이 다시 접근 여부를 결정합니다.

curl --noproxy '*' --fail --silent http://127.0.0.1:18086/health

요약

iptables 구현과 INPUT 체인을 확인하고, 범위를 좁힌 거부 규칙을 삽입 및 검증했습니다. 또한 실제 트래픽과 카운터로 그 효과를 입증하고, 허용 규칙으로 교체한 뒤 실습에서 추가한 변경 사항만 제거했습니다. 이러한 작업을 통해 관련 없는 방화벽 상태를 초기화하지 않고도 안전하게 패킷 필터링을 관리하는 데 필요한 명령줄 실습을 수행할 수 있습니다.