firewalld

firewalld는 D-Bus 기반 동적 방화벽(Firewall) 관리 데몬으로, 존(Zone) 기반 정책 모델 위에서 nftables/iptables 백엔드(Backend) 변환을 수행합니다. rich rule, D-Bus API, NetworkManager/Docker/systemd 통합, 커널 수준 nftables 체인(Chain) 매핑, 디버깅(Debugging)과 성능(Performance) 분석을 실무 관점에서 설명합니다.

전제 조건: Netfilter 프레임워크, NAT 문서를 먼저 읽으세요. firewalld는 커널 Netfilter/nftables 위에서 동작하는 유저스페이스(Userspace) 데몬이므로, 패킷 훅(Hook)과 체인 구조를 알아야 firewalld의 백엔드 변환을 정확히 이해할 수 있습니다.
일상 비유: firewalld는 건물 보안 시스템과 비슷합니다. 건물에는 로비(public), 사무 공간(work), VIP 라운지(internal), 서버실(dmz) 같은 보안 구역(Zone)이 있고, 각 구역마다 출입 규칙이 다릅니다. 방문자(패킷)가 도착하면 "어느 문(인터페이스)으로 들어왔는가" 또는 "누구인가(출발지 주소)"에 따라 보안 구역이 결정되고, 해당 구역의 규칙에 따라 통과·차단이 결정됩니다. 보안 관리자(firewall-cmd)는 건물 관리 시스템(D-Bus)을 통해 규칙을 즉시 변경할 수 있으며, 건물을 재시작하지 않고도 정책을 업데이트할 수 있습니다.

핵심 요약

  • 존(Zone) — 네트워크 인터페이스나 출발지 주소에 할당되는 신뢰 수준(Trust Level) 단위입니다. drop부터 trusted까지 9단계가 기본 제공됩니다.
  • 서비스(Service) — HTTP, SSH 같은 사전 정의된 포트/프로토콜 묶음으로, 포트 번호를 외우지 않아도 됩니다.
  • 런타임(Runtime) vs 영구(Permanent) — 런타임 변경은 재시작하면 사라지고, --permanent 플래그로 영구 저장 후 --reload합니다.
  • D-Bus 인터페이스 — firewalld는 D-Bus 위에서 동작하여, CLI·GUI·프로그래밍 API 모두 같은 채널을 사용합니다.
  • nftables 백엔드 — 현대 firewalld(0.6+)는 nftables를 기본 백엔드로 사용하며, table inet firewalld 아래에 체인과 규칙을 생성합니다.
  • rich rule — 단순 허용/차단을 넘어 출발지, 목적지, 로깅, 속도 제한, 시간 기반 제어 등 세밀한 규칙을 정의합니다.

단계별 이해

  1. 존 구조를 파악
    기본 존(public, home, work 등)의 신뢰 수준과 기본 허용 서비스를 먼저 이해합니다. 대부분의 시스템에서 기본 존은 public입니다.
  2. 인터페이스/출발지 할당을 확인
    firewall-cmd --get-active-zones로 어떤 인터페이스가 어떤 존에 할당되었는지 확인합니다. 출발지 기반 할당이 인터페이스 기반보다 우선합니다.
  3. 서비스와 포트를 관리
    firewall-cmd --add-service=http --zone=public처럼 서비스 단위로 허용하거나, --add-port=8080/tcp로 개별 포트를 열 수 있습니다.
  4. 영구 설정과 리로드를 분리
    테스트할 때는 런타임만 변경하고, 확인되면 --permanent로 저장 후 --reload합니다. 실수해도 재시작하면 이전 상태로 돌아옵니다.
  5. 백엔드 규칙을 검증
    nft list table inet firewalld로 firewalld가 실제로 어떤 nftables 규칙을 생성했는지 확인합니다. 추상화 계층 아래의 실제 동작을 이해하면 문제 해결이 빨라집니다.
문서 기준: 이 문서는 2026년 3월 기준 firewalld 2.3.x(nftables 백엔드 기본), kernel stable 6.19.x 기반입니다. firewalld 0.6.0 이후 nftables가 기본 백엔드이며, 0.9.0 이후 정책 객체(Policy Object)가 도입되었습니다. iptables 백엔드는 레거시 호환 용도로만 설명합니다.
관련 표준/사양: firewalld는 freedesktop.org D-Bus 사양, Netfilter nftables(nft) 인터페이스, Red Hat/Fedora 네트워킹 스택 위에서 동작합니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

firewalld 아키텍처(Architecture)

firewalld는 리눅스 네트워킹 스택에서 유저스페이스 방화벽 관리 계층을 담당합니다. 전통적인 iptables 직접 조작 방식과 달리, firewalld는 추상화 계층을 제공하여 존 기반 정책 모델을 nftables/iptables 백엔드 규칙으로 변환합니다.

주요 컴포넌트

firewalld 시스템은 크게 네 계층으로 구분됩니다.

계층컴포넌트역할
사용자 인터페이스firewall-cmd (CLI), firewall-config (GUI), firewall-applet (트레이)관리자가 정책을 입력하는 프론트엔드
통신 버스D-Bus (org.fedoraproject.FirewallD1)프론트엔드와 데몬 간 IPC 채널
데몬firewalld (Python 데몬)존/서비스/규칙 관리, 설정 파일 파싱, 백엔드 변환
백엔드nftables (nft) 또는 iptables (iptables-nft/iptables-legacy)커널 Netfilter에 실제 규칙을 적재

설정 경로

firewalld는 두 개의 설정 디렉터리를 사용합니다. 시스템 기본값은 /usr/lib/firewalld/에, 관리자 커스터마이징은 /etc/firewalld/에 저장됩니다. /etc/firewalld/ 파일이 동일 이름의 시스템 파일보다 우선합니다.

# 설정 디렉터리 구조
/etc/firewalld/
├── firewalld.conf          # 전역 설정 (DefaultZone, FirewallBackend 등)
├── zones/                  # 사용자 정의/수정된 존
│   ├── public.xml
│   └── custom-dmz.xml
├── services/               # 사용자 정의 서비스
│   └── myapp.xml
├── ipsets/                  # 사용자 정의 IP 세트
├── icmptypes/              # 사용자 정의 ICMP 유형
├── policies/               # 정책 객체 (0.9+)
├── helpers/                # conntrack 헬퍼 정의
└── direct.xml              # Direct 규칙 (레거시)

/usr/lib/firewalld/         # 패키지 기본값 (수정 금지)
├── zones/
├── services/
├── ipsets/
├── icmptypes/
└── helpers/

데몬 내부 구조

firewalld 데몬은 Python으로 작성되었으며, gi.repository (GObject Introspection)를 통해 D-Bus와 통신합니다. 내부적으로 다음 모듈이 핵심 역할을 합니다.

모듈경로역할
core/fw.pyfirewall/core/fw.py방화벽 엔진 핵심: 존/서비스/규칙 상태 관리
core/fw_zone.pyfirewall/core/fw_zone.py존 로직: 인터페이스 바인딩, 규칙 적용
core/nftables.pyfirewall/core/nftables.pynftables 백엔드: nft 명령 생성/실행
core/ipXtables.pyfirewall/core/ipXtables.pyiptables/ip6tables 백엔드 (레거시)
server/firewalld.pyfirewall/server/firewalld.pyD-Bus 서비스 객체 (메서드/시그널 노출)
firewalld 아키텍처 계층 사용자 인터페이스 계층 firewall-cmd (CLI) firewall-config (GUI) firewall-applet 커스텀 D-Bus 클라이언트 (Python 등) D-Bus (org.fedoraproject.FirewallD1) firewalld 데몬 (Python) Zone 엔진 Service 파서 Rich Rule 컴파일러 Policy 엔진 fw_zone.py fw_service.py rich.py fw_policy.py /etc/firewalld/ *.xml, *.conf nftables 백엔드 (기본) iptables 백엔드 (레거시) 커널 Netfilter 프레임워크 PREROUTING INPUT FORWARD OUTPUT POSTROUTING
firewalld 아키텍처: 사용자 도구 → D-Bus → firewalld 데몬 → nftables/iptables 백엔드 → 커널 Netfilter 훅
핵심 포인트: firewalld는 규칙 생성기입니다. 패킷 필터링 자체는 커널 Netfilter가 수행하며, firewalld는 존 기반 정책을 nftables 규칙으로 변환하여 커널에 적재하는 역할만 합니다. 따라서 firewalld 데몬이 중지되어도 이미 적재된 규칙은 계속 동작합니다.

존(Zone) 모델

존은 firewalld의 핵심 추상화 단위입니다. 각 존은 신뢰 수준(Trust Level)을 나타내며, 해당 존에 할당된 인터페이스나 출발지 주소에서 오는 트래픽에 적용할 규칙 집합을 정의합니다. 존 모델의 핵심 아이디어는 "네트워크 환경에 따라 방화벽 정책을 빠르게 전환할 수 있다"는 것입니다.

기본 제공 존 9종

firewalld는 drop(최소 신뢰)부터 trusted(최대 신뢰)까지 9개의 기본 존을 제공합니다. 각 존의 기본 동작(target)과 허용 서비스가 다릅니다.

target기본 허용설명
dropDROP없음모든 수신 패킷을 무응답 폐기. 가장 제한적인 존. 응답이 없으므로 포트 스캔에도 정보를 노출하지 않음
block%%REJECT%%없음모든 수신 패킷을 ICMP 거부 응답과 함께 차단. drop과 달리 발신자에게 "거부됨"을 알림
publicdefaultssh, dhcpv6-client공용 네트워크용 기본 존. 최소한의 서비스만 허용. 대부분의 배포판에서 기본 존(DefaultZone)으로 설정
externaldefaultssh외부 네트워크 게이트웨이용. 마스커레이드(NAT)가 기본 활성화됨
dmzdefaultssh비무장지대(DMZ) 네트워크용. 내부망에서 접근 가능하지만 외부 접근은 제한적
workdefaultssh, dhcpv6-client업무 환경 네트워크용. 동료를 어느 정도 신뢰하는 환경
homedefaultssh, mdns, samba-client, dhcpv6-client가정 네트워크용. 홈 네트워크 디바이스(프린터, NAS 등)와 통신을 허용
internaldefaultssh, mdns, samba-client, dhcpv6-client내부 네트워크용. home과 유사하지만 조직 내부망에 사용
trustedACCEPT전부모든 네트워크 연결을 신뢰. 모든 트래픽 허용. 루프백(lo) 등 완전히 신뢰할 수 있는 인터페이스에만 사용
target 의미: default는 "존의 서비스/포트/rich rule에 명시적으로 매칭되지 않으면 거부(REJECT)"를 의미합니다. DROP은 무응답 폐기, %%REJECT%%는 ICMP 에러 응답과 함께 거부, ACCEPT는 무조건 허용입니다.

커스텀 존 생성

# 새 존 생성
firewall-cmd --permanent --new-zone=myapp-tier

# 존에 서비스 추가
firewall-cmd --permanent --zone=myapp-tier --add-service=https
firewall-cmd --permanent --zone=myapp-tier --add-port=8443/tcp

# 존 설명 추가
firewall-cmd --permanent --zone=myapp-tier --set-description="앱 서버 전용 존"

# 존 target 설정 (default, ACCEPT, DROP, %%REJECT%%)
firewall-cmd --permanent --zone=myapp-tier --set-target=default

# 리로드 후 확인
firewall-cmd --reload
firewall-cmd --info-zone=myapp-tier

커스텀 존 XML 파일은 /etc/firewalld/zones/myapp-tier.xml에 저장됩니다.

<!-- /etc/firewalld/zones/myapp-tier.xml -->
<?xml version="1.0" encoding="utf-8"?>
<zone target="default">
  <short>myapp-tier</short>
  <description>앱 서버 전용 존</description>
  <service name="https"/>
  <port port="8443" protocol="tcp"/>
</zone>
존 할당 결정 흐름과 신뢰 수준 스펙트럼 패킷 도착 출발지 주소가 존에 바인딩? 출발지 존 사용 아니오 수신 인터페이스가 존에 바인딩? 인터페이스 존 사용 아니오 기본 존(DefaultZone) 존 규칙 평가 신뢰 수준 스펙트럼 drop block public external dmz work home internal trusted 최소 신뢰 최대 신뢰 존 할당 우선순위: 출발지(Source) > 인터페이스(Interface) > 기본 존(Default) 하나의 패킷은 정확히 하나의 존에서만 평가됨
존 할당 결정 흐름: 출발지 → 인터페이스 → 기본 존 순서로 매칭. 하단은 9개 기본 존의 신뢰 수준 스펙트럼

존 할당 규칙과 우선순위

firewalld에서 패킷이 어떤 존에서 평가될지 결정하는 우선순위는 명확합니다. 이 순서를 모르면 "규칙을 추가했는데 왜 적용이 안 되지?"라는 문제에 자주 부딪힙니다.

할당 우선순위

  1. 출발지(Source) 기반 할당 — 패킷의 출발지 IP/CIDR이 특정 존의 source에 매칭되면 해당 존 사용. 가장 높은 우선순위
  2. 인터페이스(Interface) 기반 할당 — 패킷이 도착한 네트워크 인터페이스가 존에 바인딩되어 있으면 해당 존 사용
  3. 기본 존(DefaultZone) — 위 두 가지에 모두 매칭되지 않으면 firewalld.confDefaultZone 사용
# 현재 활성 존 확인
firewall-cmd --get-active-zones
# 출력 예:
# public
#   interfaces: eth0
# internal
#   sources: 192.168.1.0/24

# 기본 존 확인
firewall-cmd --get-default-zone
# 출력: public

# 기본 존 변경
firewall-cmd --set-default-zone=home

출발지 기반 존 할당

출발지 기반 할당은 특정 IP 대역에서 오는 트래픽을 다른 존으로 보내야 할 때 사용합니다. 예를 들어, 관리 네트워크(10.0.0.0/8)의 트래픽은 trusted 존에서, 나머지는 public 존에서 처리하는 구성이 가능합니다.

# 출발지 주소를 존에 바인딩
firewall-cmd --permanent --zone=trusted --add-source=10.0.0.0/8
firewall-cmd --permanent --zone=internal --add-source=192.168.1.0/24

# 확인
firewall-cmd --get-active-zones
# trusted
#   sources: 10.0.0.0/8
# internal
#   sources: 192.168.1.0/24
# public
#   interfaces: eth0 eth1

# 출발지 제거
firewall-cmd --permanent --zone=trusted --remove-source=10.0.0.0/8
firewall-cmd --reload

인터페이스 기반 존 할당

# 인터페이스를 존에 바인딩
firewall-cmd --permanent --zone=dmz --add-interface=eth1
firewall-cmd --permanent --zone=internal --add-interface=eth2

# 인터페이스의 존 확인
firewall-cmd --get-zone-of-interface=eth1
# 출력: dmz

# 인터페이스의 존 변경
firewall-cmd --permanent --zone=work --change-interface=eth1
firewall-cmd --reload

다중 활성 존

firewalld에서는 여러 존이 동시에 활성화될 수 있습니다. 각 존은 독립적으로 동작하며, 패킷은 우선순위에 따라 정확히 하나의 존에서만 평가됩니다.

주의: 하나의 인터페이스를 여러 존에 동시 바인딩할 수 없습니다. 하지만 출발지 기반 존과 인터페이스 기반 존은 별도로 존재할 수 있으며, 같은 인터페이스로 들어온 패킷이라도 출발지 주소에 따라 다른 존에서 평가될 수 있습니다.
# 실무 예: 웹 서버 구성
# eth0: 외부 인터넷 → public 존
# eth1: 내부 네트워크 → internal 존
# 관리 IP 대역 → trusted 존 (출발지 기반)

firewall-cmd --permanent --zone=public --add-interface=eth0
firewall-cmd --permanent --zone=internal --add-interface=eth1
firewall-cmd --permanent --zone=trusted --add-source=10.10.0.0/24

# public 존에 웹 서비스만 허용
firewall-cmd --permanent --zone=public --add-service=http
firewall-cmd --permanent --zone=public --add-service=https

# internal 존에 추가 서비스 허용
firewall-cmd --permanent --zone=internal --add-service=ssh
firewall-cmd --permanent --zone=internal --add-service=nfs

firewall-cmd --reload

nftables 백엔드와 iptables 백엔드

firewalld는 두 가지 백엔드를 지원합니다. nftables(기본, 권장)와 iptables(레거시 호환). 백엔드 설정은 /etc/firewalld/firewalld.confFirewallBackend 옵션으로 제어합니다.

백엔드 설정

# /etc/firewalld/firewalld.conf
# 백엔드 설정 (nftables 또는 iptables)
FirewallBackend=nftables

# 현재 백엔드 확인
firewall-cmd --get-log-denied
grep FirewallBackend /etc/firewalld/firewalld.conf

nftables 백엔드 구조

nftables 백엔드를 사용하면 firewalld는 table inet firewalld 아래에 모든 체인과 규칙을 생성합니다. inet family를 사용하므로 IPv4와 IPv6 규칙이 통합됩니다.

# firewalld가 생성한 nftables 테이블 확인
nft list table inet firewalld

# 주요 체인 구조:
# table inet firewalld {
#   chain mangle_PREROUTING { type filter hook prerouting priority mangle; }
#   chain filter_PREROUTING { type filter hook prerouting priority filter + 10; }
#   chain filter_INPUT { type filter hook input priority filter + 10; }
#   chain filter_FORWARD { type filter hook forward priority filter + 10; }
#   chain filter_OUTPUT { type filter hook output priority filter + 10; }
#   chain nat_PREROUTING { type nat hook prerouting priority dstnat; }
#   chain nat_POSTROUTING { type nat hook postrouting priority srcnat; }
#   chain filter_INPUT_ZONES { ... }  # 존 디스패치
#   chain filter_IN_public { ... }    # public 존 규칙
#   chain filter_IN_public_allow { ... } # public 존 허용 규칙
#   ...
# }

iptables 백엔드 (레거시)

iptables 백엔드는 iptables/ip6tables/ebtables 명령을 직접 호출합니다. IPv4와 IPv6가 별도 테이블로 관리되며, nftables 대비 성능과 유연성이 떨어집니다.

비교 항목nftables 백엔드iptables 백엔드
IPv4/IPv6inet family로 통합iptables/ip6tables 별도 관리
규칙 적재원자적(atomic) 트랜잭션규칙별 순차 적재
리로드 속도빠름 (flush + batch load)느림 (규칙별 삭제/추가)
세트(Set) 지원네이티브 nft setipset 외부 의존
규칙 충돌table inet firewalld로 격리다른 iptables 도구와 충돌 가능
Direct 규칙지원 (변환)네이티브
권장 여부권장 (기본)레거시 호환 전용

iptables에서 nftables로 마이그레이션

# 1. 현재 백엔드 확인
grep FirewallBackend /etc/firewalld/firewalld.conf

# 2. 백엔드 변경
sed -i 's/FirewallBackend=iptables/FirewallBackend=nftables/' /etc/firewalld/firewalld.conf

# 3. Direct 규칙이 있다면 확인 (nftables에서도 변환 지원)
firewall-cmd --direct --get-all-rules

# 4. firewalld 재시작
systemctl restart firewalld

# 5. 규칙 확인
nft list table inet firewalld
firewall-cmd --state
마이그레이션 팁: Direct 규칙(--direct)을 사용 중이라면 nftables 백엔드에서도 동작하지만, 가능하면 rich rule이나 정책 객체로 전환하는 것이 좋습니다. Direct 규칙은 firewalld 향후 버전에서 지원 중단(deprecated) 예정입니다.
firewalld 추상화 → nftables 백엔드 변환 매핑 firewalld 추상화 Zone "public" (eth0) Service: http, https Rich Rule: rate limit ssh Port Forward: 80→8080 Masquerade: external zone 변환 nftables 규칙 (table inet firewalld) chain filter_INPUT_ZONES: iifname "eth0" jump filter_IN_public chain filter_IN_public_allow: tcp dport {80, 443} accept chain filter_IN_public_allow: tcp dport 22 ct state new limit rate 3/minute accept chain nat_PRE_public_allow: tcp dport 80 dnat to :8080 chain nat_POST_external_allow: oifname != "lo" masquerade firewalld 추상화(존, 서비스, rich rule 등)는 nftables 체인과 규칙으로 1:1 변환됩니다
firewalld 추상화 → nftables 백엔드 변환: 존은 디스패치 체인, 서비스는 포트 매칭, rich rule은 복합 nft 규칙으로 변환

서비스(Service)와 포트(Port) 관리

firewalld의 서비스는 포트/프로토콜 조합을 이름으로 추상화한 것입니다. "포트 80번을 열어라" 대신 "HTTP 서비스를 허용하라"고 지시하면, firewalld가 해당 서비스의 정의(포트 80/tcp)를 읽어 규칙을 생성합니다.

사전 정의 서비스

firewalld는 수백 개의 사전 정의 서비스를 /usr/lib/firewalld/services/에 XML 파일로 제공합니다.

# 사용 가능한 서비스 목록
firewall-cmd --get-services

# 특정 서비스 정보 확인
firewall-cmd --info-service=http
# http
#   ports: 80/tcp
#   protocols:
#   source-ports:
#   modules:
#   destination:

# SSH 서비스의 XML 정의 보기
cat /usr/lib/firewalld/services/ssh.xml
# <?xml version="1.0" encoding="utf-8"?>
# <service>
#   <short>SSH</short>
#   <description>...</description>
#   <port protocol="tcp" port="22"/>
# </service>

커스텀 서비스 정의

<!-- /etc/firewalld/services/myapp.xml -->
<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>MyApp</short>
  <description>커스텀 애플리케이션 서비스 (API 8080 + WebSocket 8443)</description>
  <port protocol="tcp" port="8080"/>
  <port protocol="tcp" port="8443"/>
  <helper name="ftp"/>       <!-- conntrack 헬퍼 (필요 시) -->
  <destination ipv4="239.255.0.0/16"/>  <!-- 멀티캐스트 제한 (선택) -->
</service>
# CLI로 커스텀 서비스 생성
firewall-cmd --permanent --new-service=myapp
firewall-cmd --permanent --service=myapp --set-description="커스텀 앱 서비스"
firewall-cmd --permanent --service=myapp --add-port=8080/tcp
firewall-cmd --permanent --service=myapp --add-port=8443/tcp
firewall-cmd --reload

# 존에 서비스 추가
firewall-cmd --permanent --zone=public --add-service=myapp
firewall-cmd --reload

런타임 vs 영구 설정

firewalld의 모든 변경은 기본적으로 런타임에만 적용됩니다. 영구 저장하려면 --permanent 플래그를 사용합니다.

옵션적용 시점지속성사용 시나리오
(기본, 플래그 없음)즉시재시작/리로드 시 소멸테스트, 임시 허용
--permanent리로드 후영구 저장프로덕션 설정
--permanent + --reload즉시 (리로드 후)영구 저장즉시 반영 + 영구 저장
--runtime-to-permanent이미 적용됨현재 런타임을 영구로 복사테스트 후 영구화
# 패턴 1: 테스트 후 영구화
firewall-cmd --zone=public --add-service=http          # 런타임만
# ... 테스트 후 문제 없으면 ...
firewall-cmd --runtime-to-permanent                     # 런타임 → 영구

# 패턴 2: 즉시 영구 저장
firewall-cmd --permanent --zone=public --add-service=http
firewall-cmd --reload

# 런타임과 영구 설정 차이 확인
diff <(firewall-cmd --zone=public --list-all) \
     <(firewall-cmd --permanent --zone=public --list-all)

포트 직접 관리

# 단일 포트 허용
firewall-cmd --permanent --zone=public --add-port=3000/tcp

# 포트 범위 허용
firewall-cmd --permanent --zone=public --add-port=5000-5100/tcp

# UDP 포트 허용
firewall-cmd --permanent --zone=public --add-port=51820/udp

# 프로토콜 허용 (포트 없이)
firewall-cmd --permanent --zone=public --add-protocol=gre
firewall-cmd --permanent --zone=public --add-protocol=ospf

# 출발지 포트(source port) 제한
firewall-cmd --permanent --zone=public --add-source-port=1024-65535/tcp

# 현재 존의 모든 설정 확인
firewall-cmd --zone=public --list-all
# public (active)
#   target: default
#   icmp-block-inversion: no
#   interfaces: eth0
#   sources:
#   services: dhcpv6-client http https ssh
#   ports: 3000/tcp 5000-5100/tcp
#   protocols: gre
#   masquerade: no
#   forward-ports:
#   source-ports:
#   icmp-blocks:
#   rich rules:

Rich Rule

Rich rule은 firewalld에서 단순한 서비스/포트 허용을 넘어 복잡한 규칙을 정의할 수 있는 표현력 있는 규칙 언어입니다. 출발지/목적지, 서비스/포트, 로깅, 감사, 속도 제한, 우선순위를 하나의 규칙에 조합할 수 있습니다.

Rich Rule 문법

rule [family="ipv4|ipv6"]
     [priority="값"]
     [source [NOT] address="주소[/마스크]" | mac="MAC" | ipset="이름"]
     [destination [NOT] address="주소[/마스크]" | ipset="이름"]
     [service name="서비스" | port port="포트[-포트]" protocol="프로토콜"
      | protocol value="프로토콜" | icmp-block name="유형"
      | icmp-type name="유형" | masquerade
      | forward-port port="포트[-포트]" protocol="프로토콜"
        [to-port="포트[-포트]"] [to-addr="주소"]
      | source-port port="포트[-포트]" protocol="프로토콜"]
     [log [prefix="접두사"] [level="수준"] [limit value="속도"]]
     [audit]
     [accept | reject [type="유형"] | drop | mark set="값"]

Rich Rule 우선순위

firewalld 0.9+에서 priority 속성을 사용하여 rich rule의 평가 순서를 제어할 수 있습니다. 값 범위는 -32768 ~ 32767이며, 낮은 값이 먼저 평가됩니다.

우선순위 범위평가 시점용도
-32768 ~ -1존 허용 규칙 이전특정 트래픽 선제 차단/허용
0 (기본)존 허용 규칙과 동일 수준일반 rich rule
1 ~ 32767존 허용 규칙 이후fallback 규칙, 로깅

실무 예제

# 1. SSH 접속 속도 제한 (분당 3회)
firewall-cmd --permanent --zone=public --add-rich-rule='
  rule family="ipv4"
  service name="ssh"
  log prefix="SSH_RATE_LIMIT" level="info"
  limit value="3/m"
  accept'

# 2. 특정 IP 대역에서 HTTP 허용
firewall-cmd --permanent --zone=public --add-rich-rule='
  rule family="ipv4"
  source address="192.168.1.0/24"
  service name="http"
  accept'

# 3. 특정 IP 차단 (높은 우선순위)
firewall-cmd --permanent --zone=public --add-rich-rule='
  rule family="ipv4"
  priority="-100"
  source address="10.0.0.50"
  drop'

# 4. 로깅 후 차단 (의심스러운 포트 접근)
firewall-cmd --permanent --zone=public --add-rich-rule='
  rule family="ipv4"
  port port="23" protocol="tcp"
  log prefix="TELNET_ATTEMPT" level="warning" limit value="5/m"
  reject type="icmp-port-unreachable"'

# 5. MAC 주소 기반 허용
firewall-cmd --permanent --zone=internal --add-rich-rule='
  rule source mac="AA:BB:CC:DD:EE:FF"
  service name="ssh"
  accept'

# 6. 목적지 제한 (멀티캐스트만 허용)
firewall-cmd --permanent --zone=public --add-rich-rule='
  rule family="ipv4"
  destination address="224.0.0.0/4"
  protocol value="igmp"
  accept'

# 7. ipset과 조합 (대량 IP 차단)
firewall-cmd --permanent --zone=public --add-rich-rule='
  rule family="ipv4"
  priority="-200"
  source ipset="blocklist"
  log prefix="BLOCKED_IP" level="info" limit value="1/m"
  drop'

# rich rule 목록 확인
firewall-cmd --zone=public --list-rich-rules

Rich Rule 요소 참조표

요소문법설명
familyfamily="ipv4|ipv6"IP 버전 제한. 생략 시 양쪽 모두 적용
prioritypriority="-100"평가 순서. 낮을수록 먼저 (0.9+)
sourcesource address="IP/CIDR"출발지 주소. NOT 키워드로 반전 가능
destinationdestination address="IP/CIDR"목적지 주소. NOT 키워드로 반전 가능
serviceservice name="ssh"사전 정의/커스텀 서비스
portport port="8080" protocol="tcp"개별 포트/범위
protocolprotocol value="gre"IP 프로토콜
icmp-blockicmp-block name="echo-reply"ICMP 유형 차단
masquerademasquerade출발지 NAT
forward-portforward-port port="80" protocol="tcp" to-port="8080"포트 포워딩
loglog prefix="TAG" level="info" limit value="5/m"syslog 로깅
auditaudit감사 로그 기록
acceptaccept허용 verdict
rejectreject type="icmp-port-unreachable"거부 (ICMP 응답)
dropdrop무응답 폐기
markmark set="0x1"패킷 마킹 (라우팅/QoS용)

마스커레이드(Masquerade)와 포트 포워딩(Port Forwarding)

마스커레이드는 출발지 NAT(SNAT)의 동적 형태로, 내부 네트워크의 사설 IP를 외부 인터페이스의 공인 IP로 변환합니다. 포트 포워딩은 목적지 NAT(DNAT)으로, 외부에서 특정 포트로 들어온 트래픽을 내부 서버로 전달합니다.

마스커레이드 활성화

# 존에 마스커레이드 활성화
firewall-cmd --permanent --zone=external --add-masquerade

# 확인
firewall-cmd --zone=external --query-masquerade
# yes

# IP 포워딩도 활성화 (커널 레벨)
sysctl -w net.ipv4.ip_forward=1
# 영구화: /etc/sysctl.d/99-forward.conf에 net.ipv4.ip_forward=1 추가
마스커레이드 vs SNAT: 마스커레이드는 패킷이 나갈 때마다 인터페이스의 현재 IP를 확인합니다. DHCP로 IP가 변하는 환경에 적합합니다. 고정 IP 환경에서는 nft 직접 규칙으로 SNAT를 쓰는 것이 미세하게 효율적이지만, firewalld 추상화에서는 마스커레이드가 표준입니다.

포트 포워딩

# 로컬 포트 포워딩: 80 → 8080 (같은 호스트)
firewall-cmd --permanent --zone=public \
  --add-forward-port=port=80:proto=tcp:toport=8080

# 원격 포트 포워딩: 80 → 내부 서버 192.168.1.100:8080
firewall-cmd --permanent --zone=external \
  --add-forward-port=port=80:proto=tcp:toaddr=192.168.1.100:toport=8080

# 포트 범위 포워딩
firewall-cmd --permanent --zone=external \
  --add-forward-port=port=5000-5010:proto=tcp:toaddr=192.168.1.200

# 포워딩 규칙 확인
firewall-cmd --zone=external --list-forward-ports

# 포워딩 규칙 제거
firewall-cmd --permanent --zone=public \
  --remove-forward-port=port=80:proto=tcp:toport=8080
firewall-cmd --reload

nftables 매핑

마스커레이드와 포트 포워딩은 nftables의 NAT 체인으로 변환됩니다.

# 마스커레이드 → nftables 변환 결과
# chain nat_POST_external_allow {
#   oifname != "lo" masquerade
# }

# 포트 포워딩 → nftables 변환 결과
# chain nat_PRE_public_allow {
#   tcp dport 80 dnat to 192.168.1.100:8080
# }

# 실제 확인
nft list chain inet firewalld nat_POSTROUTING
nft list chain inet firewalld nat_PREROUTING

다중 인터페이스 마스커레이드

여러 내부 인터페이스가 하나의 외부 인터페이스를 통해 나가는 환경에서는 각 내부 인터페이스를 적절한 존에 할당하고 외부 존에서만 마스커레이드를 활성화합니다.

# 시나리오: eth0(외부), eth1(내부 사무실), eth2(내부 게스트)
# 각 인터페이스를 적절한 존에 할당
firewall-cmd --permanent --zone=external --add-interface=eth0
firewall-cmd --permanent --zone=internal --add-interface=eth1
firewall-cmd --permanent --zone=dmz --add-interface=eth2

# external 존에서만 마스커레이드 활성화
firewall-cmd --permanent --zone=external --add-masquerade

# 내부 → 외부 포워딩을 위한 정책
firewall-cmd --permanent --new-policy=office-to-internet
firewall-cmd --permanent --policy=office-to-internet --set-ingress-zone=internal
firewall-cmd --permanent --policy=office-to-internet --set-egress-zone=external
firewall-cmd --permanent --policy=office-to-internet --set-target=ACCEPT

# 게스트 → 외부는 HTTP/HTTPS만 허용
firewall-cmd --permanent --new-policy=guest-to-internet
firewall-cmd --permanent --policy=guest-to-internet --set-ingress-zone=dmz
firewall-cmd --permanent --policy=guest-to-internet --set-egress-zone=external
firewall-cmd --permanent --policy=guest-to-internet --add-service=http
firewall-cmd --permanent --policy=guest-to-internet --add-service=https
firewall-cmd --permanent --policy=guest-to-internet --add-service=dns

firewall-cmd --reload

조건부 포트 포워딩

Rich rule을 사용하면 특정 소스 IP에서만 포트 포워딩을 허용하는 조건부 규칙을 만들 수 있습니다.

# 관리 네트워크(10.0.0.0/24)에서만 SSH 포워딩 허용
firewall-cmd --permanent --zone=external --add-rich-rule='
  rule family="ipv4"
  source address="10.0.0.0/24"
  forward-port port="2222" protocol="tcp"
  to-port="22" to-addr="192.168.1.10"'

# UDP 포워딩 (DNS 릴레이)
firewall-cmd --permanent --zone=external --add-rich-rule='
  rule family="ipv4"
  forward-port port="53" protocol="udp"
  to-port="53" to-addr="192.168.1.53"'

# 특정 시간대에만 포워딩 (rich rule + cron 조합)
# rich rule은 시간 조건을 직접 지원하지 않으므로
# cron + firewall-cmd로 runtime 규칙 활성화/비활성화
NAT 변환 확인: 포트 포워딩이 동작하려면 (1) 마스커레이드 또는 커널 IP 포워딩이 활성화되어야 하고, (2) conntrack이 RELATED/ESTABLISHED 응답 패킷을 역변환합니다. conntrack -L로 NAT 매핑 상태를 확인할 수 있습니다.

ipset 통합

firewalld는 ipset(IP 세트)을 통해 대량의 IP 주소, 네트워크, MAC 주소를 효율적으로 관리할 수 있습니다. 개별 규칙을 수백 개 추가하는 대신, 세트 하나에 주소를 넣고 rich rule이나 존 소스에서 참조하면 됩니다.

지원 세트 유형

유형저장 항목사용 예
hash:ip개별 IP 주소블랙리스트, 화이트리스트
hash:net네트워크 CIDR국가별 IP 대역 차단
hash:macMAC 주소디바이스 기반 접근 제어
hash:ip,portIP + 포트 조합서비스별 세분화
hash:ip,markIP + 마크 조합QoS/라우팅 마킹
hash:net,net소스/목적지 네트워크 쌍양방향 정책
hash:net,iface네트워크 + 인터페이스 조합인터페이스별 정책

ipset 관리

# ipset 생성
firewall-cmd --permanent --new-ipset=blocklist --type=hash:ip
firewall-cmd --permanent --new-ipset=trusted-nets --type=hash:net

# ipset에 항목 추가
firewall-cmd --permanent --ipset=blocklist --add-entry=10.0.0.50
firewall-cmd --permanent --ipset=blocklist --add-entry=10.0.0.51
firewall-cmd --permanent --ipset=trusted-nets --add-entry=192.168.1.0/24
firewall-cmd --permanent --ipset=trusted-nets --add-entry=10.10.0.0/16

# 파일에서 대량 로드
# blocklist-entries.txt 내용 (한 줄에 하나씩):
# 10.0.0.50
# 10.0.0.51
# 10.0.0.52
firewall-cmd --permanent --ipset=blocklist \
  --add-entries-from-file=blocklist-entries.txt

# ipset 내용 확인
firewall-cmd --permanent --ipset=blocklist --get-entries

# ipset 정보
firewall-cmd --permanent --info-ipset=blocklist

# 항목 제거
firewall-cmd --permanent --ipset=blocklist --remove-entry=10.0.0.50

firewall-cmd --reload

ipset 활용

# 1. 존 소스로 ipset 사용
firewall-cmd --permanent --zone=drop --add-source=ipset:blocklist
# blocklist의 모든 IP에서 오는 트래픽 → drop 존

# 2. rich rule에서 ipset 사용
firewall-cmd --permanent --zone=public --add-rich-rule='
  rule family="ipv4"
  source ipset="trusted-nets"
  service name="ssh"
  accept'

# 3. 차단 + 로깅
firewall-cmd --permanent --zone=public --add-rich-rule='
  rule family="ipv4"
  priority="-200"
  source ipset="blocklist"
  log prefix="BLOCKLIST" level="warning" limit value="1/m"
  drop'
성능: ipset은 해시 테이블 기반이므로 수만 개의 항목이 있어도 O(1)에 가까운 매칭 성능을 제공합니다. 개별 rich rule을 수백 개 추가하는 것보다 훨씬 효율적입니다. nftables 백엔드에서는 nft set으로 변환되어 커널 내 해시 테이블에서 직접 매칭합니다.

ICMP 유형 제어

firewalld는 존별로 특정 ICMP 유형을 차단하거나 허용할 수 있습니다. 보안상 불필요한 ICMP를 차단하되, 네트워크 진단에 필수적인 유형은 유지하는 균형이 중요합니다.

지원 ICMP 유형

# 사용 가능한 ICMP 유형 목록
firewall-cmd --get-icmptypes
# address-unreachable communication-prohibited destination-unreachable
# echo-reply echo-request fragmentation-needed host-prohibited
# host-redirect host-unknown host-unreachable network-prohibited
# network-redirect network-unknown network-unreachable no-route
# packet-too-big parameter-problem port-unreachable protocol-unreachable
# redirect router-advertisement router-solicitation source-quench
# time-exceeded timestamp-reply timestamp-request

ICMP 차단 구성

# 특정 ICMP 유형 차단
firewall-cmd --permanent --zone=public --add-icmp-block=echo-reply
firewall-cmd --permanent --zone=public --add-icmp-block=timestamp-request
firewall-cmd --permanent --zone=public --add-icmp-block=timestamp-reply

# 차단된 ICMP 유형 확인
firewall-cmd --zone=public --list-icmp-blocks

# ICMP 차단 반전 (block inversion)
# 활성화하면: 목록에 있는 유형만 허용, 나머지 모두 차단
# 비활성화(기본): 목록에 있는 유형만 차단, 나머지 모두 허용
firewall-cmd --permanent --zone=public --add-icmp-block-inversion
firewall-cmd --zone=public --query-icmp-block-inversion

# 차단 제거
firewall-cmd --permanent --zone=public --remove-icmp-block=echo-reply
firewall-cmd --reload
진단 영향: echo-request(ping)를 차단하면 기본적인 네트워크 진단이 불가능해집니다. destination-unreachable을 차단하면 Path MTU Discovery가 동작하지 않아 연결 문제가 발생할 수 있습니다. 보안과 운영 편의성의 균형을 고려하세요.
ICMP 유형차단 권장이유
echo-request상황별ping 차단. 보안 vs 진단 트레이드오프
timestamp-request/reply차단 권장시스템 시간 정보 노출 방지
redirect차단 권장라우팅 테이블 조작 공격 방지
destination-unreachable허용 권장Path MTU Discovery에 필수
time-exceeded허용 권장traceroute에 필수
parameter-problem허용 권장프로토콜 오류 진단에 필요

ICMP 차단 판단 매트릭스

환경권장 차단 유형반드시 허용할 유형근거
공인 웹 서버timestamp-*, redirect, source-quenchdestination-unreachable, echo-request, time-exceededPMTUD 유지, 기본 진단 허용
내부 서버redirect, source-quench모든 진단 유형내부 네트워크에서 진단 우선
방화벽/게이트웨이timestamp-*, source-quenchdestination-unreachable, time-exceeded, echo-*라우팅 진단과 PMTUD 필수
최대 보안(DMZ)echo-*, timestamp-*, redirect, source-quenchdestination-unreachable (fragmentation-needed)최소 노출, PMTUD만 유지

IPv6 ICMP(ICMPv6) 고려 사항

IPv6 환경에서는 ICMPv6가 NDP(Neighbor Discovery Protocol)에 필수적이므로, IPv4보다 더 신중하게 차단해야 합니다. Router Advertisement(RA), Router Solicitation(RS), Neighbor Solicitation(NS), Neighbor Advertisement(NA)를 차단하면 IPv6 네트워킹이 완전히 중단됩니다.

ICMPv6 유형차단 가능 여부이유
router-advertisement (134)절대 차단 금지IPv6 주소 자동 설정(SLAAC)에 필수
router-solicitation (133)절대 차단 금지라우터 발견에 필수
neighbour-solicitation (135)절대 차단 금지IPv4 ARP에 해당, 주소 해석 필수
neighbour-advertisement (136)절대 차단 금지주소 해석 응답, DAD(Duplicate Address Detection)
echo-request (128)상황별 판단ping6 진단 도구
packet-too-big (2)절대 차단 금지IPv6 Path MTU Discovery 필수 (IPv6는 라우터 단편화 없음)
IPv6 PMTUD 필수: IPv4와 달리 IPv6에서는 중간 라우터가 패킷을 단편화하지 않습니다. packet-too-big ICMPv6를 차단하면 MTU보다 큰 패킷이 전달 불가능해져 "블랙홀" 현상이 발생합니다. 이 유형은 어떤 환경에서도 반드시 허용해야 합니다.

정책 객체(Policy Objects)

firewalld 0.9.0에서 도입된 정책 객체는 존과 존 사이의 트래픽(inter-zone traffic)을 제어합니다. 기존 존 규칙은 "외부에서 존으로 들어오는 트래픽"만 제어했지만, 정책 객체는 "존 A에서 존 B로 가는 트래픽"을 명시적으로 정의할 수 있습니다.

정책 개념

정책 객체는 ingress-zone(트래픽 출발 존)과 egress-zone(트래픽 도착 존)을 지정하여, 존 간 트래픽 흐름을 제어합니다. 특수 키워드 HOST는 방화벽 호스트 자체를, ANY는 모든 존을 의미합니다.

# 정책 객체 생성
firewall-cmd --permanent --new-policy=internal-to-external

# ingress/egress 존 설정
firewall-cmd --permanent --policy=internal-to-external \
  --set-ingress-zone=internal
firewall-cmd --permanent --policy=internal-to-external \
  --set-egress-zone=external

# 정책에 규칙 추가
firewall-cmd --permanent --policy=internal-to-external \
  --add-service=http
firewall-cmd --permanent --policy=internal-to-external \
  --add-service=https
firewall-cmd --permanent --policy=internal-to-external \
  --add-service=dns

# 정책 target 설정 (default, ACCEPT, DROP, REJECT)
firewall-cmd --permanent --policy=internal-to-external \
  --set-target=default

# 리로드 및 확인
firewall-cmd --reload
firewall-cmd --info-policy=internal-to-external

실무 정책 예제

# 예 1: 내부 → 외부 웹 트래픽만 허용
firewall-cmd --permanent --new-policy=int-to-ext-web
firewall-cmd --permanent --policy=int-to-ext-web --set-ingress-zone=internal
firewall-cmd --permanent --policy=int-to-ext-web --set-egress-zone=external
firewall-cmd --permanent --policy=int-to-ext-web --add-service=http
firewall-cmd --permanent --policy=int-to-ext-web --add-service=https
firewall-cmd --permanent --policy=int-to-ext-web --add-service=dns

# 예 2: DMZ → 내부 DB 서버 접근
firewall-cmd --permanent --new-policy=dmz-to-int-db
firewall-cmd --permanent --policy=dmz-to-int-db --set-ingress-zone=dmz
firewall-cmd --permanent --policy=dmz-to-int-db --set-egress-zone=internal
firewall-cmd --permanent --policy=dmz-to-int-db --add-rich-rule='
  rule family="ipv4"
  destination address="192.168.1.50"
  port port="5432" protocol="tcp"
  accept'

# 예 3: 호스트 자체에서 외부로 나가는 트래픽 제어
firewall-cmd --permanent --new-policy=host-to-external
firewall-cmd --permanent --policy=host-to-external --set-ingress-zone=HOST
firewall-cmd --permanent --policy=host-to-external --set-egress-zone=external
firewall-cmd --permanent --policy=host-to-external --add-service=dns
firewall-cmd --permanent --policy=host-to-external --add-service=ntp
firewall-cmd --permanent --policy=host-to-external --set-target=REJECT

# 정책 목록 확인
firewall-cmd --get-policies
firewall-cmd --reload
정책 vs 존: 존 규칙은 "이 존에 들어오는 트래픽을 어떻게 처리할 것인가"를 정의합니다. 정책 객체는 "존 A에서 존 B로 흐르는 트래픽을 어떻게 처리할 것인가"를 정의합니다. 포워딩 게이트웨이에서 존 간 트래픽 세밀 제어에 필수적입니다.

Direct 규칙과 Passthrough

Direct 인터페이스는 firewalld 추상화를 우회하여 iptables/nftables 규칙을 직접 삽입하는 기능입니다. firewalld의 존/서비스 모델로 표현할 수 없는 복잡한 규칙이 필요할 때 사용했지만, 향후 deprecated 예정이므로 가능한 rich rule이나 정책 객체로 대체해야 합니다.

Direct 규칙

# Direct 규칙 추가 (iptables 문법)
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 \
  -p tcp --dport 9090 -j ACCEPT

# Direct 체인 추가
firewall-cmd --permanent --direct --add-chain ipv4 filter CUSTOM_CHAIN

# Direct 규칙 목록
firewall-cmd --direct --get-all-rules
firewall-cmd --direct --get-rules ipv4 filter INPUT

# Direct 규칙 제거
firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 0 \
  -p tcp --dport 9090 -j ACCEPT
firewall-cmd --reload

Passthrough 규칙

Passthrough는 Direct보다 더 낮은 수준의 인터페이스로, iptables 명령줄을 그대로 전달합니다.

# Passthrough로 iptables 명령 직접 전달
firewall-cmd --permanent --direct --passthrough ipv4 \
  -A INPUT -p tcp --dport 9090 -m state --state NEW -j ACCEPT

# Passthrough 목록
firewall-cmd --direct --get-all-passthroughs
Deprecated 경고: firewalld 1.0 (2022)부터 Direct 인터페이스는 공식적으로 deprecated 상태입니다. nftables 백엔드에서는 Direct 규칙이 별도 iptables 호환 체인에 삽입되어 firewalld의 nftables 테이블과 분리됩니다. firewalld 2.0+에서 완전 제거될 수 있습니다.

Direct → Rich Rule 마이그레이션

기존 Direct 규칙을 rich rule이나 정책 객체로 전환하는 대표적인 패턴입니다.

기존 Direct 규칙대체 방법변환 예제
--direct --add-rule ipv4 filter INPUT 0 -p tcp --dport 9090 -j ACCEPT Rich Rule --add-rich-rule='rule family="ipv4" port port="9090" protocol="tcp" accept'
--direct --add-rule ipv4 filter INPUT 0 -s 10.0.0.0/8 -p tcp --dport 22 -j ACCEPT Rich Rule --add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" service name="ssh" accept'
--direct --add-rule ipv4 filter FORWARD 0 -i eth1 -o eth0 -j ACCEPT 정책 객체 ingress-zone=internal, egress-zone=external, target=ACCEPT
--direct --add-rule ipv4 mangle PREROUTING 0 -j MARK --set-mark 0x1 별도 nftables 테이블 nft add table inet custom; nft add chain inet custom premark ...
# Direct 규칙을 rich rule로 변환하는 절차

# 1. 현재 Direct 규칙 목록 추출
firewall-cmd --direct --get-all-rules > /tmp/direct-rules-backup.txt

# 2. 각 규칙을 분석하여 rich rule로 변환
# 예: -p tcp --dport 8443 -j ACCEPT
firewall-cmd --permanent --zone=public --add-rich-rule='
  rule family="ipv4" port port="8443" protocol="tcp" accept'

# 3. 변환 완료 후 Direct 규칙 제거
firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 0 \
  -p tcp --dport 8443 -j ACCEPT

# 4. firewalld가 관리할 수 없는 규칙은 별도 nftables 테이블 사용
# firewalld 테이블(inet firewalld)과 충돌하지 않음
nft add table inet custom
nft add chain inet custom input '{ type filter hook input priority -10; policy accept; }'
nft add rule inet custom input tcp dport 9999 counter accept

firewall-cmd --reload
별도 nftables 테이블 공존: firewalld는 table inet firewalld만 관리합니다. 사용자가 별도 테이블(예: table inet custom)을 생성하면 firewalld 리로드와 무관하게 규칙이 유지됩니다. 이 방법은 mangle, raw 테이블 규칙이나 firewalld로 표현할 수 없는 고급 nftables 기능(ct helper, meter, quota 등)에 적합합니다.

nftables 체인/규칙 매핑 상세

firewalld의 추상화가 실제로 어떤 nftables 규칙으로 변환되는지 이해하면, 문제 해결과 성능 최적화에 큰 도움이 됩니다.

테이블 및 체인 구조

# firewalld nftables 테이블 전체 구조 확인
nft list table inet firewalld

# 주요 체인 이름 규칙:
#
# 훅 체인 (base chain):
#   filter_PREROUTING   → prerouting 훅
#   filter_INPUT        → input 훅
#   filter_FORWARD      → forward 훅
#   filter_OUTPUT       → output 훅
#   nat_PREROUTING      → prerouting 훅 (DNAT)
#   nat_POSTROUTING     → postrouting 훅 (SNAT)
#
# 존 디스패치 체인:
#   filter_INPUT_ZONES      → INPUT에서 존별로 분기
#   filter_FORWARD_IN_ZONES → FORWARD 입력 방향 존 분기
#   filter_FORWARD_OUT_ZONES → FORWARD 출력 방향 존 분기
#
# 존 규칙 체인:
#   filter_IN_public         → public 존 INPUT 규칙
#   filter_IN_public_pre     → priority < 0 rich rule
#   filter_IN_public_log     → 로깅 규칙
#   filter_IN_public_deny    → 거부 규칙
#   filter_IN_public_allow   → 허용 규칙
#   filter_IN_public_post    → priority > 0 rich rule
#   filter_FWDI_public       → public 존 FORWARD 규칙
#   nat_PRE_public_allow     → public 존 DNAT 규칙
#   nat_POST_public_allow    → public 존 SNAT 규칙

존 디스패치 체인 상세

# filter_INPUT_ZONES 체인 — 존 디스패치 로직
nft list chain inet firewalld filter_INPUT_ZONES
# chain filter_INPUT_ZONES {
#   ip saddr 192.168.1.0/24 goto filter_IN_internal   ← 출발지 기반 (우선)
#   iifname "eth0" goto filter_IN_public               ← 인터페이스 기반
#   iifname "eth1" goto filter_IN_dmz                  ← 인터페이스 기반
#   goto filter_IN_public                               ← 기본 존 (fallback)
# }

규칙 변환 예제

firewalld 추상화nftables 규칙
--add-service=httptcp dport 80 ct state { new, untracked } accept
--add-port=8080/tcptcp dport 8080 ct state { new, untracked } accept
--add-masqueradeoifname != "lo" masquerade
--add-forward-port=port=80:proto=tcp:toport=8080tcp dport 80 dnat to :8080
rich rule: source 10.0.0.0/8 dropip saddr 10.0.0.0/8 drop
rich rule: log limit 5/mlog prefix "..." limit rate 5/minute
--add-source=ipset:blocklistip saddr @firewalld_blocklist goto filter_IN_drop
# 서비스 허용이 실제로 어떻게 변환되는지 추적
# 1. firewall-cmd로 서비스 추가
firewall-cmd --zone=public --add-service=postgresql

# 2. nftables에서 규칙 확인
nft list chain inet firewalld filter_IN_public_allow
# chain filter_IN_public_allow {
#   tcp dport 22 ct state { new, untracked } accept   ← ssh
#   tcp dport 80 ct state { new, untracked } accept    ← http
#   tcp dport 443 ct state { new, untracked } accept   ← https
#   tcp dport 5432 ct state { new, untracked } accept  ← postgresql (새로 추가)
# }

# 3. 서비스 제거 후 확인
firewall-cmd --zone=public --remove-service=postgresql
nft list chain inet firewalld filter_IN_public_allow
# postgresql 규칙이 사라짐

패킷 처리 흐름

firewalld를 통한 패킷 처리 흐름을 이해하려면 커널 Netfilter 훅과 firewalld의 nftables 체인이 어떻게 연결되는지 알아야 합니다. 패킷은 Netfilter 훅을 지나면서 firewalld의 table inet firewalld 체인을 통과합니다.

firewalld 패킷 처리 흐름 (nftables 백엔드) 패킷 수신 PREROUTING nat_PREROUTING (DNAT) 라우팅 결정 로컬 수신 filter_INPUT → filter_INPUT_ZONES → filter_IN_{zone}_allow → 서비스/포트/rich rule 매칭 로컬 프로세스 포워딩 filter_FORWARD → filter_FORWARD_IN_ZONES → filter_FWDI_{zone}/정책 → filter_FORWARD_OUT_ZONES filter_OUTPUT POSTROUTING nat_POSTROUTING (SNAT/마스커레이드) 패킷 송신 존 규칙 평가 순서 (filter_IN_{zone} 내부) _pre priority < 0 _log 로깅 규칙 _deny 거부 규칙 _allow 서비스/포트 허용 _post priority > 0 target 모든 경로에서 conntrack 상태가 established/related이면 존 규칙 평가 없이 즉시 accept 핵심 포인트: 1. conntrack 상태가 established/related인 패킷은 firewalld 존 체인을 거치지 않고 바로 accept (성능 최적화) 2. 새 연결(new/untracked)만 존 규칙을 평가. 이후 패킷은 conntrack이 처리 3. 존 디스패치(filter_INPUT_ZONES)에서 출발지 기반 규칙이 인터페이스 기반보다 먼저 매칭 4. target이 default인 존은 매칭되지 않은 new 패킷을 REJECT. DROP/ACCEPT target은 해당 동작 수행
firewalld 패킷 처리 흐름: Netfilter 훅 경로와 존 규칙 평가 순서. established/related 패킷은 존 규칙 없이 즉시 허용

INPUT 경로 상세

로컬 프로세스로 향하는 패킷의 처리 순서입니다.

  1. nat_PREROUTING — DNAT(포트 포워딩) 적용
  2. 라우팅 결정 — 로컬 수신 확인
  3. filter_INPUT — conntrack 상태 확인 (established/related → accept)
  4. filter_INPUT_ZONES — 출발지/인터페이스 기반 존 디스패치
  5. filter_IN_{zone} — 존 규칙 체인 진입
  6. filter_IN_{zone}_pre — 우선순위 < 0 rich rule
  7. filter_IN_{zone}_log — 로깅 규칙
  8. filter_IN_{zone}_deny — 거부 규칙
  9. filter_IN_{zone}_allow — 서비스/포트 허용 규칙
  10. filter_IN_{zone}_post — 우선순위 > 0 rich rule
  11. 존 target — 매칭 안 된 패킷에 대한 최종 처리

FORWARD 경로 상세

포워딩(라우팅되는) 패킷의 처리 순서입니다. 정책 객체가 이 경로에서 존 간 트래픽을 제어합니다.

  1. nat_PREROUTING — DNAT 적용
  2. 라우팅 결정 — 포워딩 확인
  3. filter_FORWARD — conntrack 상태 확인
  4. filter_FORWARD_IN_ZONES — 입력 존 디스패치
  5. filter_FWDI_{zone} — 입력 존 포워딩 규칙
  6. 정책 체인 — 정책 객체 규칙 평가
  7. filter_FORWARD_OUT_ZONES — 출력 존 디스패치
  8. filter_FWDO_{zone} — 출력 존 포워딩 규칙
  9. nat_POSTROUTING — SNAT/마스커레이드 적용

Connection Tracking 상태와 firewalld

firewalld의 모든 규칙 평가는 커널 conntrack(Connection Tracking) 위에서 동작합니다. 이미 추적 중인 연결(ESTABLISHED/RELATED)은 존 규칙을 다시 평가하지 않고 빠르게 통과합니다. 새 연결(NEW)만 존별 서비스/포트/rich rule 체인을 거칩니다.

conntrack 상태 머신과 firewalld 규칙 평가 패킷 도착 NF_INET_PRE... conntrack 조회 nf_conntrack_in() ct state? (conntrack 상태) NEW → 존 규칙 평가 filter_IN_zone_allow 서비스/포트/rich rule 검사 NEW ESTABLISHED / RELATED → 즉시 ACCEPT ct state established,related accept EST/REL INVALID → DROP ct state invalid drop INVALID UNTRACKED → 존 규칙 평가 conntrack bypass UNTRACKED conntrack 상태별 동작 요약 ● NEW — 연결의 첫 패킷. 존 디스패치 → zone_allow/deny/log 체인 전체 순회. 가장 비용이 큼 ● ESTABLISHED/RELATED — 이미 허용된 연결의 후속 패킷. 최상위 체인에서 즉시 ACCEPT. 대부분의 트래픽 ● INVALID — conntrack이 추적할 수 없는 패킷 (시퀀스 번호 불일치, 상태 부적합). 즉시 DROP ● UNTRACKED — conntrack에서 명시적으로 제외된 패킷 (CT target NOTRACK). 매번 존 규칙 평가
그림 5. conntrack 상태 머신 — firewalld는 ESTABLISHED/RELATED 패킷을 최상위 체인에서 즉시 수락하여 성능을 확보합니다.

존별 conntrack 동작

firewalld의 nftables 규칙에서 conntrack 상태 검사는 존 디스패치보다 먼저 실행됩니다. 이는 이미 허용된 연결의 응답 패킷이 존 규칙을 다시 통과할 필요가 없음을 의미합니다.

# nftables에서 conntrack 규칙 확인
nft list chain inet firewalld filter_INPUT
# → ct state established,related accept  (최우선)
# → ct state invalid drop
# → iifname "lo" accept
# → jump filter_INPUT_ZONES  (NEW 패킷만 여기 도달)

# conntrack 타임아웃 확인
sysctl net.netfilter.nf_conntrack_tcp_timeout_established
# 432000 (5일, 기본값)
sysctl net.netfilter.nf_conntrack_udp_timeout_stream
# 120 (2분, 기본값)

# conntrack 항목 확인
conntrack -L -p tcp --state ESTABLISHED 2>/dev/null | head -5
# tcp  6 431999 ESTABLISHED src=192.168.1.100 dst=93.184.216.34 ...

# RELATED 상태: FTP 데이터 연결, ICMP 오류 등
conntrack -L --state RELATED 2>/dev/null | head -5

conntrack 타임아웃 튜닝

파라미터기본값권장값 (고트래픽)설명
nf_conntrack_tcp_timeout_established432000 (5일)3600~86400활성 TCP 연결 유지 시간
nf_conntrack_tcp_timeout_time_wait12030TIME_WAIT 상태 유지 시간
nf_conntrack_tcp_timeout_close_wait6030CLOSE_WAIT 유지 시간
nf_conntrack_udp_timeout3030UDP 단일 패킷 타임아웃
nf_conntrack_udp_timeout_stream12060UDP 스트림 타임아웃
nf_conntrack_icmp_timeout3010ICMP 타임아웃
성능 영향: conntrack 테이블이 가득 차면(nf_conntrack_max 도달) 새 연결이 모두 거부됩니다. dmesgnf_conntrack: table full, dropping packet 메시지가 나타납니다. 이 경우 nf_conntrack_max를 늘리거나, 타임아웃을 줄여 오래된 항목을 빠르게 정리하거나, 특정 트래픽에 NOTRACK을 적용하여 conntrack을 우회하세요.

프로토콜 헬퍼(Helper)와 ALG

FTP, SIP, H.323 등 일부 프로토콜은 제어 채널과 데이터 채널이 별도 포트를 사용합니다. conntrack 헬퍼(ALG, Application Layer Gateway)는 제어 채널의 페이로드를 분석하여 동적으로 열릴 데이터 포트를 예측하고, RELATED 상태로 자동 허용합니다.

헬퍼 동작 원리

firewalld에서 서비스를 허용하면, 해당 서비스에 연결된 헬퍼 모듈이 자동으로 활성화됩니다. 예를 들어 ftp 서비스를 허용하면 nf_conntrack_ftp 헬퍼가 FTP 제어 연결(21번 포트)을 감시하여 PORT/PASV 명령에서 데이터 포트를 추출하고, 해당 데이터 연결을 RELATED로 허용합니다.

# 사용 가능한 헬퍼 모듈 확인
lsmod | grep nf_conntrack
# nf_conntrack_ftp     — FTP PORT/PASV
# nf_conntrack_sip     — SIP INVITE (VoIP)
# nf_conntrack_tftp    — TFTP
# nf_conntrack_amanda  — Amanda Backup
# nf_conntrack_h323    — H.323 (VoIP)
# nf_conntrack_pptp    — PPTP VPN
# nf_conntrack_irc     — IRC DCC

# firewalld에서 FTP 서비스 허용 (헬퍼 자동 연결)
firewall-cmd --permanent --zone=public --add-service=ftp
firewall-cmd --reload

# nftables에서 헬퍼 규칙 확인
nft list table inet firewalld | grep -A2 helper
# ct helper set "helper-ftp-tcp"

커스텀 헬퍼 XML 정의

<!-- /etc/firewalld/helpers/my-ftp.xml -->
<?xml version="1.0" encoding="utf-8"?>
<helper module="nf_conntrack_ftp" family="ipv4">
  <short>My FTP Helper</short>
  <description>커스텀 FTP 헬퍼 (비표준 포트)</description>
  <port protocol="tcp" port="2121"/>
</helper>
<!-- 서비스에 헬퍼 연결: /etc/firewalld/services/custom-ftp.xml -->
<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>Custom FTP</short>
  <description>비표준 포트 FTP 서비스</description>
  <port protocol="tcp" port="2121"/>
  <helper name="my-ftp"/>
</service>

헬퍼 보안 고려 사항

자동 헬퍼 비활성화: 보안상의 이유로 커널 4.7부터 nf_conntrack_helper=0이 기본값이 되어, 헬퍼가 자동으로 모든 연결에 적용되지 않습니다. firewalld는 서비스 정의에 명시된 헬퍼만 해당 포트에 적용하므로 이 기본 동작과 호환됩니다. 만약 레거시 환경에서 헬퍼가 동작하지 않으면 서비스 XML에 <helper> 태그가 포함되어 있는지 확인하세요.
프로토콜헬퍼 모듈동작보안 위험
FTPnf_conntrack_ftpPORT/PASV에서 데이터 포트 추출평문 제어 채널 → 중간자 공격 가능
SIPnf_conntrack_sipSDP에서 RTP 포트 추출SIP 헤더 조작 → 임의 포트 개방 가능
H.323nf_conntrack_h323H.245에서 미디어 채널 추출복잡한 프로토콜 → 파싱 취약점
TFTPnf_conntrack_tftp클라이언트의 임의 포트 허용인증 없는 프로토콜
PPTPnf_conntrack_pptpGRE 터널 RELATED 추적deprecated 프로토콜, 보안 취약

IPv6 고려 사항

firewalld는 IPv4와 IPv6를 동시에 관리합니다. --add-service--add-port 명령은 기본적으로 양쪽 모두에 적용됩니다. 하지만 IPv6에는 고유한 동작과 주의사항이 있습니다.

IPv6 필수 ICMPv6 허용 흐름 IPv6 패킷 도착 ICMPv6? 일반 존 규칙 평가 아니오 NDP 필수? RA/RS/NS/NA 무조건 ACCEPT ip6 nexthdr icmpv6 icmpv6 type {133-136} accept Packet Too Big? 아니오 PMTUD 필수 ACCEPT icmpv6 type packet-too-big accept 존 ICMP 차단 규칙 평가 아니오 IPv6 필수 ICMPv6 유형 133 Router Solicitation 134 Router Advertisement 135 Neighbor Solicitation 136 Neighbor Advertisement 2 Packet Too Big (PMTUD 필수)
그림 6. IPv6 ICMPv6 필수 유형 — NDP(133-136)와 Packet Too Big(2)은 존 규칙과 관계없이 항상 허용됩니다.

IPv6_rpfilter

IPv6_rpfilter은 firewalld가 기본 활성화하는 역방향 경로 필터링(Reverse Path Filtering) 기능입니다. 소스 IP로 해당 인터페이스를 통해 라우팅될 수 있는지 검사하여, 스푸핑된 소스 주소를 가진 패킷을 차단합니다.

# /etc/firewalld/firewalld.conf에서 IPv6_rpfilter 설정
# IPv6_rpfilter=yes  (기본값, 활성화)
# IPv6_rpfilter=no   (비활성화)

# nftables에서 확인
nft list chain inet firewalld raw_PREROUTING
# → fib saddr . iif oif 0 drop  (소스 주소 역방향 검증)

# 비대칭 라우팅 환경에서는 rpfilter가 정상 패킷을 차단할 수 있음
# 이 경우 비활성화 필요
# IPv6_rpfilter=no

듀얼 스택(Dual-Stack) 운영

# rich rule에서 family 지정으로 IPv4/IPv6 분리 제어

# IPv4만 적용
firewall-cmd --permanent --zone=public --add-rich-rule='
  rule family="ipv4" source address="10.0.0.0/8" service name="ssh" accept'

# IPv6만 적용
firewall-cmd --permanent --zone=public --add-rich-rule='
  rule family="ipv6" source address="fd00::/8" service name="ssh" accept'

# family를 지정하지 않으면 IPv4/IPv6 모두 적용
firewall-cmd --permanent --zone=public --add-rich-rule='
  rule service name="http" accept'

# 서비스/포트 명령은 항상 양쪽 적용
firewall-cmd --zone=public --add-service=http
# → IPv4 + IPv6 모두 80/tcp 허용
IPv6 전용 서비스: DHCPv6 클라이언트(dhcpv6-client)는 대부분의 존에 기본 허용됩니다. 이 서비스는 IPv6 주소 자동 설정(Stateful DHCPv6)에 필요합니다. firewall-cmd --zone=public --list-services에서 dhcpv6-client가 포함되어 있는지 확인하고, 제거하면 DHCPv6 주소 할당이 실패할 수 있습니다.

D-Bus 인터페이스

firewalld의 모든 기능은 D-Bus 인터페이스를 통해 접근할 수 있습니다. firewall-cmd도 내부적으로 D-Bus를 사용하므로, 프로그래밍 언어에서 직접 D-Bus를 호출하면 동일한 기능을 자동화할 수 있습니다.

D-Bus 객체 구조

버스 이름객체 경로인터페이스용도
org.fedoraproject.FirewallD1/org/fedoraproject/FirewallD1org.fedoraproject.FirewallD1전역 메서드 (reload, getDefaultZone 등)
(동일)(동일)org.fedoraproject.FirewallD1.zone런타임 존 관리
(동일)(동일)org.fedoraproject.FirewallD1.directDirect 규칙 관리
(동일)(동일)org.fedoraproject.FirewallD1.policies정책 (lockdown, whitelist)
(동일)(동일)org.fedoraproject.FirewallD1.ipsetipset 관리
(동일)/org/fedoraproject/FirewallD1/configorg.fedoraproject.FirewallD1.config영구 설정 관리

Python D-Bus 예제

import dbus

# D-Bus 시스템 버스 연결
bus = dbus.SystemBus()

# firewalld 프록시 객체
fw_proxy = bus.get_object(
    'org.fedoraproject.FirewallD1',
    '/org/fedoraproject/FirewallD1'
)

# 인터페이스 가져오기
fw = dbus.Interface(fw_proxy, 'org.fedoraproject.FirewallD1')
fw_zone = dbus.Interface(fw_proxy, 'org.fedoraproject.FirewallD1.zone')
fw_config = dbus.Interface(
    bus.get_object(
        'org.fedoraproject.FirewallD1',
        '/org/fedoraproject/FirewallD1/config'
    ),
    'org.fedoraproject.FirewallD1.config'
)

# 기본 존 확인
default_zone = fw.getDefaultZone()
print(f"기본 존: {default_zone}")

# 활성 존 목록
active_zones = fw_zone.getActiveZones()
print(f"활성 존: {active_zones}")

# 런타임에 서비스 추가
fw_zone.addService('public', 'http', 0)  # 0 = timeout 없음

# 서비스 목록 확인
services = fw_zone.getServices('public')
print(f"public 존 서비스: {services}")

# 리로드
fw.reload()

D-Bus 시그널 (이벤트 수신)

import dbus
from dbus.mainloop.glib import DBusGMainLoop
from gi.repository import GLib

# 메인 루프 설정
DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()

def on_reload():
    print("firewalld가 리로드되었습니다.")

def on_service_added(zone, service, timeout):
    print(f"서비스 추가: zone={zone}, service={service}")

def on_service_removed(zone, service):
    print(f"서비스 제거: zone={zone}, service={service}")

# 시그널 핸들러 등록
bus.add_signal_receiver(
    on_reload,
    signal_name='Reloaded',
    dbus_interface='org.fedoraproject.FirewallD1'
)
bus.add_signal_receiver(
    on_service_added,
    signal_name='ServiceAdded',
    dbus_interface='org.fedoraproject.FirewallD1.zone'
)
bus.add_signal_receiver(
    on_service_removed,
    signal_name='ServiceRemoved',
    dbus_interface='org.fedoraproject.FirewallD1.zone'
)

# 이벤트 루프 실행
loop = GLib.MainLoop()
loop.run()
Polkit 권한: D-Bus 메서드 호출 시 Polkit(PolicyKit) 인증이 필요합니다. root가 아닌 사용자는 org.fedoraproject.FirewallD1 관련 Polkit 정책에 따라 권한이 결정됩니다. /usr/share/polkit-1/actions/org.fedoraproject.FirewallD1.policy에서 정책을 확인/수정할 수 있습니다.

D-Bus 명령줄 도구

# dbus-send로 기본 존 확인
dbus-send --system --print-reply \
  --dest=org.fedoraproject.FirewallD1 \
  /org/fedoraproject/FirewallD1 \
  org.fedoraproject.FirewallD1.getDefaultZone

# gdbus로 메서드 호출
gdbus call --system \
  --dest org.fedoraproject.FirewallD1 \
  --object-path /org/fedoraproject/FirewallD1 \
  --method org.fedoraproject.FirewallD1.getDefaultZone

# busctl (systemd 도구)로 인터페이스 탐색
busctl introspect org.fedoraproject.FirewallD1 \
  /org/fedoraproject/FirewallD1

NetworkManager 통합

NetworkManager는 firewalld와 긴밀하게 통합되어 있습니다. 네트워크 연결(connection)마다 존을 지정할 수 있으며, 연결이 활성화되면 해당 인터페이스가 자동으로 지정된 존에 할당됩니다.

연결별 존 설정

# nmcli로 연결의 존 확인
nmcli connection show "유선 연결 1" | grep connection.zone

# 연결에 존 설정
nmcli connection modify "유선 연결 1" connection.zone internal
nmcli connection up "유선 연결 1"

# 또는 nmtui (TUI 도구)에서 설정
# 프로필 편집 → 일반 → Firewall Zone 필드

# 존 설정 제거 (기본 존 사용)
nmcli connection modify "유선 연결 1" connection.zone ""

NetworkManager 디스패처 스크립트

NetworkManager는 네트워크 이벤트(연결 활성화, IP 변경 등) 시 디스패처 스크립트를 실행합니다. 이를 활용하여 네트워크 변경에 따른 방화벽 정책 동적 조정이 가능합니다.

# /etc/NetworkManager/dispatcher.d/99-firewalld-zone
#!/bin/bash
# 인터페이스와 액션을 인자로 받음
INTERFACE=$1
ACTION=$2

case "$ACTION" in
  up)
    # VPN 인터페이스가 올라오면 trusted 존으로
    if [[ "$INTERFACE" == tun* ]]; then
      firewall-cmd --zone=trusted --change-interface="$INTERFACE"
    fi
    ;;
  down)
    # 인터페이스가 내려가면 정리
    firewall-cmd --zone=trusted --remove-interface="$INTERFACE" 2>/dev/null
    ;;
esac
# 스크립트 실행 권한 부여
chmod 755 /etc/NetworkManager/dispatcher.d/99-firewalld-zone

# 디스패처 서비스 상태 확인
systemctl status NetworkManager-dispatcher

자동 존 할당 동작

NetworkManager가 연결을 활성화할 때 존 할당 순서입니다.

  1. 연결 프로필에 connection.zone이 설정되어 있으면 해당 존 사용
  2. 없으면 firewalld의 ifcfg-* 파일에서 ZONE= 확인 (레거시)
  3. 둘 다 없으면 firewalld의 기본 존(DefaultZone) 사용
순서 보장: NetworkManager는 부팅 시 firewalld가 먼저 시작된 후에 인터페이스를 활성화합니다. firewalld.serviceNetworkManager.service보다 먼저 시작되도록 systemd 의존성이 설정되어 있습니다. firewalld가 비활성화된 상태에서 NetworkManager가 시작되면 존 할당이 이루어지지 않습니다.

Wi-Fi/VPN 프로파일별 존 전략

네트워크 유형에 따라 자동으로 적절한 존을 적용하는 실무 패턴입니다.

# Wi-Fi 프로파일별 존 설정
# 회사 Wi-Fi → work 존
nmcli connection modify "Corp-WiFi" connection.zone work

# 공공 Wi-Fi → public 존 (최소 권한)
nmcli connection modify "Coffee-Shop" connection.zone public

# 집 Wi-Fi → home 존
nmcli connection modify "Home-Network" connection.zone home

# VPN 연결 → trusted 존
nmcli connection modify "Company-VPN" connection.zone trusted

# 유선(이더넷) → internal 존
nmcli connection modify "Wired connection 1" connection.zone internal
# 고급 디스패처: 연결 유형에 따라 서비스 동적 추가/제거
# /etc/NetworkManager/dispatcher.d/50-dynamic-firewall
#!/bin/bash
INTERFACE=$1
ACTION=$2

case "$ACTION" in
  up)
    # WireGuard/OpenVPN 터널이 올라오면 내부 서비스 허용
    if [[ "$INTERFACE" == wg* ]] || [[ "$INTERFACE" == tun* ]]; then
      firewall-cmd --zone=trusted --change-interface="$INTERFACE"
      # 터널 연결 시 Samba/NFS 공유 활성화
      firewall-cmd --zone=trusted --add-service=samba
      firewall-cmd --zone=trusted --add-service=nfs
    fi
    ;;
  down)
    if [[ "$INTERFACE" == wg* ]] || [[ "$INTERFACE" == tun* ]]; then
      firewall-cmd --zone=trusted --remove-interface="$INTERFACE" 2>/dev/null
      firewall-cmd --zone=trusted --remove-service=samba 2>/dev/null
      firewall-cmd --zone=trusted --remove-service=nfs 2>/dev/null
    fi
    ;;
esac
존 전환 시 주의: connection.zone을 변경하면 연결을 다시 활성화(nmcli connection up)해야 새 존이 적용됩니다. 이미 활성화된 연결의 존을 즉시 변경하려면 firewall-cmd --zone=새존 --change-interface=인터페이스를 사용하세요(런타임 변경, 재부팅 시 원래 존 복귀).

Docker와 컨테이너(Container) 환경

Docker와 firewalld의 공존은 오랫동안 문제가 되어 왔습니다. Docker는 자체적으로 iptables 규칙을 생성하여 컨테이너 네트워킹(NAT, 포트 포워딩)을 구성하는데, firewalld 리로드 시 Docker의 규칙이 덮어씌워지는 문제가 발생합니다.

Docker-firewalld 충돌 원인

문제원인증상
리로드 후 컨테이너 네트워크 단절firewall-cmd --reload가 Docker의 iptables/nftables 규칙을 flush컨테이너에서 외부 접근 불가
포트 바인딩 무시Docker가 DOCKER 체인에 규칙을 추가하지만 firewalld가 인식하지 못함-p 8080:80이 방화벽에 의해 차단
docker0 인터페이스 존 미설정Docker 브릿지가 기본 존에 할당되어 의도치 않은 제한컨테이너 간 통신 문제

nftables 백엔드 해결책

# 1. Docker의 iptables 비활성화 (Docker가 nftables와 호환되도록)
# /etc/docker/daemon.json
{
  "iptables": false
}

# 2. firewalld에서 Docker 네트워크 구성
# docker0 인터페이스를 trusted 존에 할당
firewall-cmd --permanent --zone=trusted --add-interface=docker0

# 3. Docker 컨테이너 포트를 firewalld로 관리
firewall-cmd --permanent --zone=public --add-port=8080/tcp

# 4. 마스커레이드 활성화 (컨테이너 → 외부)
firewall-cmd --permanent --zone=public --add-masquerade

# 5. IP 포워딩 확인
sysctl net.ipv4.ip_forward
# 1이 아니면 활성화
sysctl -w net.ipv4.ip_forward=1

firewall-cmd --reload
systemctl restart docker

Docker 전용 존 구성

# Docker 전용 존 생성
firewall-cmd --permanent --new-zone=docker

# 존 설정
firewall-cmd --permanent --zone=docker --set-target=ACCEPT
firewall-cmd --permanent --zone=docker --add-interface=docker0

# 컨테이너 네트워크 대역 추가
firewall-cmd --permanent --zone=docker --add-source=172.17.0.0/16

# Docker 간 자유 통신 + 마스커레이드
firewall-cmd --permanent --zone=docker --add-masquerade

# 정책 객체로 Docker → 외부 제어 (0.9+)
firewall-cmd --permanent --new-policy=docker-to-external
firewall-cmd --permanent --policy=docker-to-external --set-ingress-zone=docker
firewall-cmd --permanent --policy=docker-to-external --set-egress-zone=public
firewall-cmd --permanent --policy=docker-to-external --add-masquerade
firewall-cmd --permanent --policy=docker-to-external --set-target=ACCEPT

firewall-cmd --reload

Podman 및 Kubernetes 고려 사항

Podman: Podman은 rootless 모드에서 slirp4netns를 사용하므로 firewalld와 직접 충돌하지 않습니다. rootful 모드에서는 Docker와 유사한 문제가 발생할 수 있습니다.
Kubernetes: kube-proxy는 iptables/nftables/IPVS 모드에서 서비스 라우팅 규칙을 생성합니다. firewalld와 kube-proxy가 동시에 실행되면 규칙 충돌이 발생할 수 있습니다. 프로덕션 Kubernetes 노드에서는 firewalld를 비활성화하고 Kubernetes 네트워크 정책(NetworkPolicy)으로 보안을 관리하는 것이 일반적입니다.

systemd 통합과 의존성(Dependency)

firewalld는 systemd 서비스로 관리됩니다. 부팅 순서, 의존성, 리로드/재시작 동작을 이해하면 운영에서 발생하는 많은 문제를 예방할 수 있습니다.

firewalld.service 유닛

# 서비스 상태 확인
systemctl status firewalld

# 서비스 시작/중지/재시작
systemctl start firewalld
systemctl stop firewalld
systemctl restart firewalld

# 부팅 시 자동 시작 설정
systemctl enable firewalld
systemctl disable firewalld

# 유닛 파일 확인
systemctl cat firewalld.service
# [Unit]
# Description=firewalld - dynamic firewall daemon
# Before=network-pre.target
# Wants=network-pre.target
# After=dbus.service
# After=polkit.service
# Conflicts=iptables.service ip6tables.service ebtables.service ipset.service nftables.service
# ...

리로드 vs 재시작

명령동작영향
firewall-cmd --reload영구 설정을 다시 읽어 런타임에 적용기존 연결 유지. 런타임 전용 규칙은 사라짐
firewall-cmd --complete-reload모든 규칙 flush 후 재적재기존 연결 포함 모든 상태 초기화. 순간 방화벽 공백 발생
systemctl restart firewalld데몬 프로세스 종료 후 재시작complete-reload와 동일 효과 + 데몬 재초기화
systemctl reload firewalld--reload와 동일기존 연결 유지
주의: --complete-reloadsystemctl restart는 모든 nftables 규칙을 flush합니다. 이 순간 방화벽이 없는 상태가 되므로, 프로덕션에서는 --reload를 사용하세요. Docker/Kubernetes 환경에서는 --reload도 문제를 일으킬 수 있으므로 주의해야 합니다.

firewall-offline-cmd

firewall-offline-cmd는 firewalld 데몬이 실행 중이지 않은 상태에서 영구 설정을 수정할 수 있는 도구입니다. 시스템 설치 스크립트(kickstart, Ansible 등)에서 주로 사용됩니다.

# firewalld가 중지된 상태에서 설정 변경
systemctl stop firewalld

# offline 명령으로 설정 (--permanent 불필요)
firewall-offline-cmd --zone=public --add-service=http
firewall-offline-cmd --zone=public --add-service=https
firewall-offline-cmd --set-default-zone=public

# 설정 후 시작
systemctl start firewalld

부팅 순서 의존성

dbus.service → polkit.service → firewalld.service → network-pre.target → NetworkManager.service
                                                                        → docker.service
                                                                        → libvirtd.service

firewalld는 Conflicts로 다음 서비스와 동시 실행을 금지:
  iptables.service, ip6tables.service, ebtables.service, ipset.service, nftables.service

설정 파일 구조

주 설정 파일: firewalld.conf

# /etc/firewalld/firewalld.conf 주요 옵션

# 기본 존
DefaultZone=public

# 백엔드 (nftables 또는 iptables)
FirewallBackend=nftables

# 최소 마크 값 (충돌 방지용)
MinimalMark=100

# 정리 시 모듈 언로드 여부
CleanupModulesOnExit=yes

# IPv6 RPFILTER (역경로 필터링)
IPv6_rpfilter=yes

# 거부된 패킷 로깅 (off, all, unicast, broadcast, multicast)
LogDenied=off

# Nftables 카운터 활성화
NftablesCounters=no

# 방화벽 정책 (IPv4/IPv6 개별)
IndividualCalls=no

# nftables flowtable 사용
NftablesFlowtable=off

존 XML 스키마

<!-- /etc/firewalld/zones/custom.xml -->
<?xml version="1.0" encoding="utf-8"?>
<zone target="default">
  <short>Custom</short>
  <description>커스텀 존 설명</description>

  <!-- 인터페이스 바인딩 -->
  <interface name="eth0"/>

  <!-- 출발지 바인딩 -->
  <source address="192.168.1.0/24"/>
  <source ipset="trusted-ips"/>

  <!-- 서비스 허용 -->
  <service name="ssh"/>
  <service name="http"/>
  <service name="https"/>

  <!-- 포트 허용 -->
  <port port="8080" protocol="tcp"/>
  <port port="5000-5100" protocol="tcp"/>

  <!-- 프로토콜 허용 -->
  <protocol value="gre"/>

  <!-- 포트 포워딩 -->
  <forward-port port="80" protocol="tcp" to-port="8080"/>
  <forward-port port="443" protocol="tcp" to-port="8443" to-addr="192.168.1.100"/>

  <!-- ICMP 차단 -->
  <icmp-block name="timestamp-request"/>
  <icmp-block name="redirect"/>

  <!-- 마스커레이드 -->
  <masquerade/>

  <!-- Rich Rule -->
  <rule family="ipv4">
    <source address="10.0.0.0/8"/>
    <service name="ssh"/>
    <log prefix="ADMIN_SSH" level="info"/>
    <limit value="3/m"/>
    <accept/>
  </rule>
</zone>

서비스 XML 스키마

<!-- /etc/firewalld/services/myapp.xml -->
<?xml version="1.0" encoding="utf-8"?>
<service version="1.0">
  <short>MyApp</short>
  <description>커스텀 애플리케이션 서비스</description>

  <!-- 포트 정의 -->
  <port protocol="tcp" port="8080"/>
  <port protocol="tcp" port="8443"/>
  <port protocol="udp" port="9090"/>

  <!-- 모듈 (conntrack 헬퍼) -->
  <module name="nf_conntrack_ftp"/>

  <!-- 목적지 제한 (선택) -->
  <destination ipv4="239.0.0.0/8"/>

  <!-- 출발지 포트 (선택) -->
  <source-port port="1024-65535" protocol="tcp"/>

  <!-- 헬퍼 (선택) -->
  <helper name="ftp"/>

  <!-- 부가 포트 (선택, helper 관련 추가 포트) -->
  <include service="ftp"/>
</service>

백업 전략

# 전체 설정 백업
tar czf firewalld-backup-$(date +%Y%m%d).tar.gz /etc/firewalld/

# 런타임 규칙 덤프 (nftables)
nft list table inet firewalld > firewalld-nft-rules-$(date +%Y%m%d).txt

# 설정 복원
tar xzf firewalld-backup-20260323.tar.gz -C /
firewall-cmd --reload

# 특정 존만 백업/복원
cp /etc/firewalld/zones/public.xml /backup/public.xml.bak
cp /backup/public.xml.bak /etc/firewalld/zones/public.xml
firewall-cmd --reload

디버깅(Debugging)과 문제 해결

firewalld 문제의 대부분은 "규칙이 적용되지 않는다" 또는 "예상과 다른 트래픽이 차단/허용된다"로 귀결됩니다. 체계적인 디버깅 절차를 따르면 빠르게 원인을 찾을 수 있습니다.

로깅 활성화

# firewalld 데몬 디버그 로깅
# /etc/firewalld/firewalld.conf에서:
# 또는 시작 시 옵션:
firewalld --debug=10  # 0=off, 1=최소, 10=최대

# systemd 저널에서 firewalld 로그 확인
journalctl -u firewalld -f
journalctl -u firewalld --since "10 minutes ago"

# 거부된 패킷 로깅 활성화
firewall-cmd --set-log-denied=all
# 옵션: off, all, unicast, broadcast, multicast

# dmesg에서 차단 로그 확인
dmesg | grep -i "FINAL_REJECT\|REJECT_"

# nftables 트레이싱 (패킷 경로 추적)
nft monitor trace

자주 발생하는 문제와 해결

증상가능한 원인확인 방법해결
서비스 추가했는데 접근 불가런타임만 추가, 리로드 후 소멸firewall-cmd --permanent --zone=public --list-services--permanent로 재추가 후 --reload
기본 존에 규칙 추가했는데 미적용출발지 기반으로 다른 존에 매칭firewall-cmd --get-active-zones올바른 존에 규칙 추가
포트 포워딩 미동작IP 포워딩 비활성화sysctl net.ipv4.ip_forwardsysctl -w net.ipv4.ip_forward=1
Docker 컨테이너 접근 불가리로드가 Docker 규칙을 덮어씀nft list table inet firewalld에서 Docker 체인 확인Docker iptables 비활성화 + firewalld로 관리
rich rule 무시됨priority 충돌 또는 문법 오류firewall-cmd --zone=public --list-rich-rulespriority 조정, 문법 확인
firewalld 시작 실패XML 파싱 오류journalctl -u firewalld -e문제 XML 파일 수정/제거
nftables 규칙과 firewalld 불일치외부 도구가 nftables를 직접 수정nft list rulesetfirewall-cmd 비교firewall-cmd --complete-reload

디버깅 체크리스트

# 1. firewalld 상태 확인
firewall-cmd --state
systemctl status firewalld

# 2. 기본 존과 활성 존 확인
firewall-cmd --get-default-zone
firewall-cmd --get-active-zones

# 3. 대상 존의 전체 설정 확인
firewall-cmd --zone=public --list-all

# 4. 런타임과 영구 설정 차이 확인
diff <(firewall-cmd --zone=public --list-all) \
     <(firewall-cmd --permanent --zone=public --list-all)

# 5. nftables 실제 규칙 확인
nft list table inet firewalld

# 6. 특정 체인의 규칙 확인
nft list chain inet firewalld filter_INPUT_ZONES
nft list chain inet firewalld filter_IN_public_allow

# 7. conntrack 상태 확인
conntrack -L | head -20

# 8. 패킷 카운터 확인 (NftablesCounters=yes 필요)
nft list table inet firewalld | grep -A1 "counter"

# 9. 커널 로그에서 차단 메시지 확인
dmesg | tail -20
journalctl -k --since "5 minutes ago" | grep -i reject

# 10. 네트워크 연결 테스트
ss -tlnp | grep :80              # 포트 수신 확인
curl -v http://localhost:80       # 로컬 접근 테스트
nmap -p 80 <서버IP>               # 외부 접근 테스트

nftables 트레이싱으로 패킷 경로 추적

# 1. 트레이스 규칙 추가 (특정 포트로 제한)
nft add rule inet firewalld filter_PREROUTING tcp dport 80 \
  meta nftrace set 1

# 2. 트레이스 모니터링
nft monitor trace
# trace id 1234 inet firewalld filter_PREROUTING packet: ...
# trace id 1234 inet firewalld filter_INPUT rule ...
# trace id 1234 inet firewalld filter_INPUT_ZONES rule ...
# trace id 1234 inet firewalld filter_IN_public rule ...
# trace id 1234 inet firewalld filter_IN_public_allow rule tcp dport 80 accept

# 3. 트레이스 규칙 제거 (디버깅 완료 후)
nft delete rule inet firewalld filter_PREROUTING handle <핸들번호>
# 핸들번호는 nft -a list chain inet firewalld filter_PREROUTING 으로 확인

로깅(Logging)

방화벽 로그는 보안 모니터링, 침해 탐지, 규칙 디버깅에 핵심입니다. firewalld는 LogDenied 전역 설정과 rich rule의 log 액션으로 세밀한 로깅을 제공합니다.

LogDenied 전역 설정

# 거부된 패킷 로깅 활성화
firewall-cmd --set-log-denied=all
# 옵션: all, unicast, broadcast, multicast, off (기본)

# 현재 설정 확인
firewall-cmd --get-log-denied

# unicast만 로깅 (broadcast/multicast 노이즈 제거)
firewall-cmd --set-log-denied=unicast

Rich Rule 로깅

# 로깅 + 거부 (prefix로 grep 필터링 가능)
firewall-cmd --permanent --zone=public --add-rich-rule='
  rule family="ipv4"
  source address="192.168.0.0/16"
  service name="ssh"
  log prefix="SSH-INTERNAL: " level="info" limit value="5/m"
  accept'

# 드롭되는 패킷 로깅 (디버깅용)
firewall-cmd --permanent --zone=public --add-rich-rule='
  rule family="ipv4"
  port port="443" protocol="tcp"
  log prefix="HTTPS-DROP: " level="warning" limit value="3/m"
  drop'

# 감사(audit) 액션: auditd로 전달
firewall-cmd --permanent --zone=public --add-rich-rule='
  rule family="ipv4"
  source address="0.0.0.0/0"
  port port="22" protocol="tcp"
  audit limit value="1/m"
  accept'
rate limit 필수: 로깅 규칙에는 반드시 limit value="N/m"(분당 N건) 또는 limit value="N/s"(초당 N건)를 지정하세요. rate limit 없이 로깅하면 DDoS 공격 시 로그가 디스크를 가득 채우고, 로깅 자체가 성능 병목이 됩니다.

rsyslog 필터링

# /etc/rsyslog.d/firewalld.conf
# firewalld 로그를 별도 파일로 분리
:msg, contains, "SSH-INTERNAL:" /var/log/firewalld-ssh.log
:msg, contains, "HTTPS-DROP:" /var/log/firewalld-https.log

# 커널 Netfilter 로그 전체를 별도 파일로
:msg, regex, "IN=.*OUT=.*SRC=.*DST=" /var/log/firewall.log
& stop

# rsyslog 재시작
systemctl restart rsyslog

journald 통합

# firewalld 데몬 로그 (D-Bus 동작, 에러)
journalctl -u firewalld --since "1 hour ago"

# 커널 Netfilter 로그 (패킷 거부/드롭)
journalctl -k | grep -E "IN=.*OUT=.*SRC="

# prefix로 필터링
journalctl -k | grep "SSH-INTERNAL:"

# 실시간 모니터링
journalctl -kf | grep --line-buffered "HTTPS-DROP:"

로그 분석 패턴

# 거부된 소스 IP Top 10
journalctl -k --since "24 hours ago" | \
  grep "IN=.*OUT=.*SRC=" | \
  grep -oP 'SRC=\K[^ ]+' | \
  sort | uniq -c | sort -rn | head -10

# 거부된 목적지 포트 Top 10
journalctl -k --since "24 hours ago" | \
  grep "IN=.*OUT=.*DPT=" | \
  grep -oP 'DPT=\K[^ ]+' | \
  sort | uniq -c | sort -rn | head -10

# 시간대별 거부 패킷 수
journalctl -k --since "24 hours ago" | \
  grep "IN=.*OUT=.*SRC=" | \
  awk '{print $1, $2, substr($3,1,2)":00"}' | \
  sort | uniq -c

성능(Performance) 고려 사항

firewalld 자체는 규칙 생성/관리만 담당하므로 패킷 처리 성능에 직접적인 영향은 없습니다. 실제 패킷 필터링은 커널 nftables가 수행합니다. 하지만 firewalld의 설계 선택이 간접적으로 성능에 영향을 줄 수 있습니다.

리로드 비용

작업소요 시간 (예상)영향
firewall-cmd --reload0.5~2초 (규칙 수에 비례)런타임 전용 규칙 소멸, 기존 연결 유지
firewall-cmd --complete-reload1~5초모든 연결 초기화, 순간 방화벽 공백
systemctl restart firewalld3~10초데몬 재시작 포함, 전체 초기화
런타임 규칙 추가 (--add-service)<0.1초해당 규칙만 추가, 기존 규칙 무영향

firewalld vs 직접 nftables

비교 항목firewalld직접 nftables
패킷 처리 속도커널 nftables와 동일동일
규칙 적재 속도D-Bus + Python 오버헤드nft -f로 원자적 로드
리로드 시 연결 유지--reload로 유지 가능nft -f로 원자적 교체 가능
체인/규칙 수존별 체인 생성으로 많아짐필요한 만큼만 최적화 가능
관리 편의성존/서비스 추상화로 편리직접 작성, 유연하지만 복잡
자동화/통합D-Bus API, Ansible 모듈 지원스크립트 기반

높은 PPS 환경 고려

# 1. nftables 카운터 비활성화 (성능 미세 개선)
# /etc/firewalld/firewalld.conf
NftablesCounters=no

# 2. conntrack 테이블 크기 조정
sysctl -w net.netfilter.nf_conntrack_max=1000000
sysctl -w net.netfilter.nf_conntrack_buckets=250000

# 3. conntrack 타임아웃 최적화
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=3600
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=30

# 4. 불필요한 존 제거 (체인 수 감소)
# 사용하지 않는 존은 활성화하지 않음으로써 디스패치 체인 감소

# 5. ipset 사용으로 규칙 수 축소
# 개별 rich rule 100개 대신 ipset 1개로 대체

# 6. flowtable 활용 (포워딩 환경)
# /etc/firewalld/firewalld.conf
NftablesFlowtable=off
# → nftables 직접 flowtable 설정이 더 유연함
성능 벤치마크: 10Gbps 환경에서 firewalld의 nftables 규칙은 직접 nftables 규칙과 패킷 처리 성능 차이가 거의 없습니다 (동일한 커널 경로 사용). 차이가 나는 것은 규칙 적재/리로드 시간과 체인 수(존당 5~6개 추가 체인)뿐입니다. 10만 PPS 이하의 일반 서버에서는 firewalld 오버헤드가 무시 가능합니다.

성능 모니터링

# nftables 규칙별 카운터 확인 (NftablesCounters=yes 필요)
nft list table inet firewalld | grep "counter packets"

# conntrack 통계
conntrack -S
# cpu=0  found=0 invalid=0 insert=0 insert_failed=0 drop=0 ...

# conntrack 현재 항목 수
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max

# 네트워크 스택 통계
cat /proc/net/netfilter/nf_conntrack_stat

# firewalld 리로드 시간 측정
time firewall-cmd --reload

커널 파라미터 튜닝

firewalld가 관리하는 규칙의 실제 동작은 커널 파라미터에 크게 의존합니다. conntrack 테이블 크기, 역방향 경로 필터링, 로깅 레벨 등을 적절히 튜닝해야 프로덕션 환경에서 안정적으로 동작합니다.

conntrack 테이블 튜닝

# conntrack 최대 항목 수 (기본: 65536 또는 메모리 기반 자동 계산)
sysctl net.netfilter.nf_conntrack_max
sysctl -w net.netfilter.nf_conntrack_max=524288

# 해시 테이블 크기 (모듈 로드 시 결정, 재로드 필요)
# 권장: nf_conntrack_max / 4
echo 131072 > /sys/module/nf_conntrack/parameters/hashsize

# 현재 사용량 모니터링
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
# count가 max의 80%에 도달하면 증설 필요

# 메모리 사용량 (항목당 약 320바이트)
# 524288 항목 ≈ 160MB

역방향 경로 필터링 (rp_filter)

# IPv4: rp_filter 확인
sysctl net.ipv4.conf.all.rp_filter
# 0 = 비활성, 1 = 엄격(strict), 2 = 느슨(loose)

# 비대칭 라우팅 환경: loose 모드 사용
sysctl -w net.ipv4.conf.all.rp_filter=2

# IPv6: firewalld의 IPv6_rpfilter가 별도 관리
# /etc/firewalld/firewalld.conf → IPv6_rpfilter=yes

# 영구 설정
cat >> /etc/sysctl.d/99-firewall.conf << 'EOF'
net.netfilter.nf_conntrack_max = 524288
net.ipv4.conf.all.rp_filter = 2
net.ipv4.ip_forward = 1
net.ipv4.conf.all.log_martians = 1
EOF
sysctl -p /etc/sysctl.d/99-firewall.conf

주요 커널 파라미터 요약

파라미터기본값권장값용도
nf_conntrack_max65536환경별 (262144~1048576)conntrack 테이블 최대 크기
nf_conntrack_buckets16384nf_conntrack_max / 4해시 버킷 수
ip_forward01 (게이트웨이)IP 포워딩 활성화
rp_filter1 (strict)2 (비대칭 라우팅 시)역방향 경로 필터링
log_martians01불가능한 소스 주소 패킷 로깅
tcp_syncookies11SYN Flood 방어
accept_redirects10ICMP redirect 수신 거부
send_redirects10 (비라우터)ICMP redirect 송신 비활성화
firewalld와 sysctl 분리: firewalld는 nf_conntrack_maxip_forward를 직접 관리하지 않습니다. 이 파라미터들은 /etc/sysctl.d/에서 별도로 설정해야 합니다. firewalld 리로드 시 sysctl 값은 변경되지 않으므로, 커널 파라미터와 방화벽 규칙은 독립적으로 관리됩니다.

Lockdown 모드

Lockdown 모드는 firewalld 설정 변경을 특정 애플리케이션이나 사용자로 제한하는 보안 기능입니다. 활성화하면 화이트리스트에 없는 프로세스는 D-Bus를 통한 firewalld 수정이 거부됩니다.

# Lockdown 모드 활성화
firewall-cmd --lockdown-on

# Lockdown 모드 상태 확인
firewall-cmd --query-lockdown

# Lockdown 모드 비활성화
firewall-cmd --lockdown-off

# 화이트리스트 관리
# 명령 화이트리스트 추가
firewall-cmd --lockdown-whitelist-add-command="/usr/bin/firewall-cmd"

# 사용자 화이트리스트
firewall-cmd --lockdown-whitelist-add-user=admin

# UID 화이트리스트
firewall-cmd --lockdown-whitelist-add-uid=0

# 컨텍스트 화이트리스트 (SELinux)
firewall-cmd --lockdown-whitelist-add-context=\
  "system_u:system_r:NetworkManager_t:s0"

# 화이트리스트 확인
firewall-cmd --lockdown-whitelist-list-commands
firewall-cmd --lockdown-whitelist-list-users
firewall-cmd --lockdown-whitelist-list-uids
주의: Lockdown 모드를 활성화한 후 화이트리스트 설정을 잘못하면 관리자도 firewalld를 수정할 수 없게 됩니다. 반드시 /usr/bin/firewall-cmd와 root(uid=0)을 화이트리스트에 포함하세요. 긴급 시에는 /etc/firewalld/firewalld.conf에서 Lockdown=no로 직접 수정 후 systemctl restart firewalld로 해제할 수 있습니다.

Polkit 정책과 세밀한 권한 제어

Lockdown 모드의 화이트리스트보다 세밀한 제어가 필요하면 Polkit(PolicyKit) 규칙을 사용합니다. firewalld는 D-Bus 작업마다 Polkit 인증을 요구하며, 커스텀 규칙으로 특정 사용자/그룹에게 특정 작업만 허용할 수 있습니다.

// /etc/polkit-1/rules.d/10-firewalld.rules
// 'firewall-admins' 그룹에게 firewalld 전체 권한 부여
polkit.addRule(function(action, subject) {
    if (action.id.indexOf("org.fedoraproject.FirewallD1") === 0 &&
        subject.isInGroup("firewall-admins")) {
        return polkit.Result.YES;
    }
});

// 일반 사용자에게는 zone 정보 조회만 허용
polkit.addRule(function(action, subject) {
    if (action.id === "org.fedoraproject.FirewallD1.zone.getZones" &&
        subject.isInGroup("users")) {
        return polkit.Result.YES;
    }
});
Polkit Action ID설명기본 권한
org.fedoraproject.FirewallD1.config영구 설정 변경auth_admin
org.fedoraproject.FirewallD1.directDirect 규칙 관리auth_admin
org.fedoraproject.FirewallD1.zone존 규칙 런타임 변경auth_admin_keep
org.fedoraproject.FirewallD1.policy정책 객체 관리auth_admin
org.fedoraproject.FirewallD1.ipsetipset 관리auth_admin
SELinux 연동: SELinux가 활성화된 환경에서 firewalld는 firewalld_t SELinux 도메인으로 실행됩니다. Lockdown 화이트리스트의 --lockdown-whitelist-add-context는 SELinux 컨텍스트를 기반으로 접근을 제한합니다. 이를 통해 특정 SELinux 도메인(예: NetworkManager_t)의 프로세스만 firewalld를 수정할 수 있도록 제한할 수 있습니다.

자동화(Automation) 통합

대규모 인프라에서 firewalld를 수동으로 관리하는 것은 비현실적입니다. Ansible, Puppet, Chef 등 구성 관리 도구와의 통합이 필수입니다.

Ansible firewalld 모듈

# Ansible playbook 예제
- name: 웹 서버 방화벽 구성
  hosts: webservers
  become: yes
  tasks:
    - name: firewalld가 실행 중인지 확인
      ansible.builtin.service:
        name: firewalld
        state: started
        enabled: yes

    - name: HTTP 서비스 허용
      ansible.posix.firewalld:
        service: http
        zone: public
        permanent: yes
        state: enabled

    - name: HTTPS 서비스 허용
      ansible.posix.firewalld:
        service: https
        zone: public
        permanent: yes
        state: enabled

    - name: 커스텀 포트 허용
      ansible.posix.firewalld:
        port: 8080/tcp
        zone: public
        permanent: yes
        state: enabled

    - name: 관리 네트워크 SSH 허용 (rich rule)
      ansible.posix.firewalld:
        rich_rule: 'rule family="ipv4" source address="10.0.0.0/8" service name="ssh" accept'
        zone: public
        permanent: yes
        state: enabled

    - name: 마스커레이드 활성화
      ansible.posix.firewalld:
        masquerade: yes
        zone: external
        permanent: yes
        state: enabled

    - name: firewalld 리로드
      ansible.builtin.command: firewall-cmd --reload

셸 스크립트 자동화

#!/bin/bash
# firewalld 초기 구성 스크립트
set -euo pipefail

# 기본 존 설정
firewall-cmd --set-default-zone=public

# 기본 서비스 구성
SERVICES="ssh http https"
for svc in $SERVICES; do
    firewall-cmd --permanent --zone=public --add-service="$svc"
done

# 관리 네트워크 설정
ADMIN_NET="10.10.0.0/24"
firewall-cmd --permanent --zone=trusted --add-source="$ADMIN_NET"

# 불필요한 ICMP 차단
ICMP_BLOCKS="timestamp-request timestamp-reply redirect"
for icmp in $ICMP_BLOCKS; do
    firewall-cmd --permanent --zone=public --add-icmp-block="$icmp"
done

# 거부 패킷 로깅 활성화
firewall-cmd --set-log-denied=unicast

# 리로드
firewall-cmd --reload

# 결과 확인
echo "=== 활성 존 ==="
firewall-cmd --get-active-zones
echo "=== public 존 상세 ==="
firewall-cmd --zone=public --list-all

Puppet 모듈

# Puppet: puppetlabs/firewalld 모듈 사용
class { 'firewalld':
  default_zone => 'public',
}

firewalld_zone { 'public':
  ensure           => present,
  target           => 'default',
  purge_rich_rules => true,
  purge_services   => true,
  purge_ports      => true,
  services         => ['ssh', 'http', 'https'],
  ports            => [
    { 'port'     => '8080',
      'protocol' => 'tcp', },
  ],
  rich_rules       => [
    { 'family'   => 'ipv4',
      'source'   => '10.0.0.0/8',
      'service'  => 'ssh',
      'action'   => 'accept', },
  ],
}

firewalld_zone { 'internal':
  ensure     => present,
  interfaces => ['eth1'],
}

CI/CD 파이프라인 패턴

방화벽 규칙 변경을 코드로 관리(Infrastructure as Code)하고 자동 배포하는 패턴입니다.

#!/bin/bash
# deploy-firewall.sh — 멱등성 보장 방화벽 배포 스크립트
set -euo pipefail

# 원하는 상태 정의
ZONE="public"
DESIRED_SERVICES="ssh http https"
DESIRED_PORTS="8080/tcp 8443/tcp"

# 현재 상태 확인
CURRENT_SERVICES=$(firewall-cmd --zone=$ZONE --list-services)
CURRENT_PORTS=$(firewall-cmd --zone=$ZONE --list-ports)

# 서비스 동기화 (추가해야 할 것만 추가)
for svc in $DESIRED_SERVICES; do
    if ! echo "$CURRENT_SERVICES" | grep -qw "$svc"; then
        echo "[ADD] service: $svc"
        firewall-cmd --permanent --zone=$ZONE --add-service="$svc"
    fi
done

# 불필요한 서비스 제거
for svc in $CURRENT_SERVICES; do
    if ! echo "$DESIRED_SERVICES" | grep -qw "$svc"; then
        echo "[REMOVE] service: $svc"
        firewall-cmd --permanent --zone=$ZONE --remove-service="$svc"
    fi
done

# 포트 동기화 (같은 패턴)
for port in $DESIRED_PORTS; do
    if ! echo "$CURRENT_PORTS" | grep -qw "$port"; then
        echo "[ADD] port: $port"
        firewall-cmd --permanent --zone=$ZONE --add-port="$port"
    fi
done

firewall-cmd --reload
echo "=== 배포 완료 ==="
firewall-cmd --zone=$ZONE --list-all
멱등성 검증: CI/CD에서 방화벽 변경을 배포할 때는 반드시 멱등성(같은 스크립트를 여러 번 실행해도 동일한 결과)을 보장해야 합니다. --query-service, --query-port 등의 쿼리 명령으로 현재 상태를 확인한 후 차이가 있을 때만 변경하세요. Ansible의 ansible.posix.firewalld 모듈은 내부적으로 이 멱등성을 자동 처리합니다.

참고 자료

ℹ️
  • 공식 문서: firewalld.org Documentation — firewalld 공식 문서 포털입니다
  • Rich Rule 문법: firewalld Rich Language — rich rule 문법을 상세히 설명합니다
  • Red Hat 가이드 (RHEL 9): RHEL 9 - Configuring firewalls and packet filters — RHEL 9 방화벽 구성 공식 가이드입니다
  • Red Hat 가이드 (RHEL 8): RHEL 8 - Using and configuring firewalld — RHEL 8 환경에서의 firewalld 구성 방법을 다룹니다
  • SUSE 가이드: SUSE SLES 15 SP5 - Security: Firewall — SUSE Linux Enterprise 환경의 방화벽 설정 가이드입니다
  • Fedora 위키: Fedora Wiki - FirewallD — Fedora 커뮤니티의 firewalld 문서입니다
  • ArchWiki: ArchWiki - Firewalld — Arch Linux 커뮤니티에서 관리하는 firewalld 문서입니다
  • CentOS/Rocky Linux: CentOS Stream 및 Rocky Linux 환경에서도 동일한 firewalld 구성을 적용하여 운영할 수 있습니다
  • D-Bus API 참조: firewalld.dbus man page — D-Bus 인터페이스를 통한 프로그래밍 방식의 방화벽 제어를 설명합니다
  • firewall-cmd man page: man firewall-cmd, 온라인 버전 — 명령줄 도구의 전체 옵션을 참조할 수 있습니다
  • firewall-offline-cmd: firewall-offline-cmd man page — firewalld 데몬 없이 오프라인 상태에서 방화벽을 설정할 때 사용합니다
  • Netfilter 프로젝트: netfilter.org — firewalld의 백엔드인 Netfilter 공식 프로젝트 사이트입니다
  • nftables 위키: nftables.org Wiki — nftables 전반에 대한 커뮤니티 문서입니다
  • nftables 빠른 참조: nftables in 10 minutes — nftables 규칙 작성을 빠르게 익힐 수 있는 튜토리얼입니다
  • 커널 Netfilter 문서: nf_conntrack sysctl — 커널 연결 추적 모듈의 sysctl 파라미터를 설명합니다
  • 컨테이너 네트워크 연동: Podman Network — Podman/Docker 컨테이너 네트워크와 firewalld 연동 시 참고할 수 있습니다
  • Kubernetes NetworkPolicy: Kubernetes 환경에서 NetworkPolicy와 firewalld를 함께 운영할 때 노드 수준 방화벽 규칙 관리에 참고합니다
  • Ansible 모듈: ansible.posix.firewalld — Ansible을 통한 firewalld 자동화 모듈입니다
  • 자동화 도구 연동: Terraform provider 또는 Puppet 모듈을 활용하여 firewalld 규칙을 코드로 관리(IaC)할 수 있습니다
  • GitHub 소스: firewalld/firewalld — firewalld 오픈소스 저장소입니다

firewalld와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.