보안 점검에서 "일단 포트를 다 열어두자"는 임시 조치가 그대로 운영에 남아 있었습니다. 장애 때는 빠른 해결처럼 보였지만, 나중에는 접근 경로를 설명할 수 없는 위험이 됩니다.
방화벽 정책은 명령어보다 기준이 먼저입니다. 무엇을 누구에게 왜 열었는지 ACL로 표현할 수 있어야 합니다.
방화벽 정책 수립과 ACL
네트워크 보안의 첫 번째 방어선인 방화벽은 단순한 포트 차단을 넘어, 세션 상태 추적과 세밀한 접근 제어 목록(ACL)을 통해 서버를 보호한다. 이 챕터에서는 Stateful Inspection 원리부터 firewalld를 이용한 실전 정책 수립까지 단계적으로 학습한다.
신규 서비스 배포 전 보안팀에서 "방화벽 정책 신청서"를 요구하는 경우가 많다. 개발자는 어떤 포트를, 어떤 출발지 IP 범위에서, 어떤 프로토콜로 허용해야 하는지 명확히 알아야 한다. 잘못된 정책은 서비스 장애 또는 보안 사고로 이어진다. 클라우드 환경에서는 Security Group이 동일한 역할을 하므로, 방화벽 개념을 이해하면 AWS/GCP/Azure 보안 설정도 자연스럽게 이해된다.
방화벽의 두 가지 방향: Inbound와 Outbound
모든 방화벽 규칙은 트래픽의 방향을 기준으로 분류된다.
- 1Inbound/Outbound 트래픽 방향과 기본 차단 정책을 설명할 수 있다
- 2Stateful Inspection과 conntrack으로 세션 상태를 추적할 수 있다
- 3firewalld Zone으로 신뢰 수준을 분리할 수 있다
- 4Rich Rule로 출발지 IP·포트·프로토콜 ACL을 작성할 수 있다
- 5최소 권한 원칙(Default Deny)에 따라 방화벽 정책을 설계할 수 있다
- 6방화벽 규칙 변경 시 기존 세션 끊김 부작용을 관리할 수 있다
sudo dnf install -y firewalld && sudo systemctl enable --now firewalldsudo firewall-cmd --get-active-zones && sudo firewall-cmd --list-allsudo dnf install -y conntrack-toolssudo firewall-cmd --runtime-to-permanent 으로 변경 전 현재 상태를 저장해 두면 롤백이 용이합니다
Inbound와 Outbound 방향 이해
보안 감사 결과 서버 방화벽이 "모두 허용" 상태였습니다. 포트를 하나씩 막으며 최소 허용 정책으로 바꾸려는데 서비스가 하나씩 죽기 시작합니다. 어떤 방향의 어떤 포트가 왜 필요한지 정리가 안 된 채로 규칙을 수정하면 이런 혼란이 반복됩니다. 인바운드와 아웃바운드를 구분하고 방향별 필요 포트를 이해하는 것이 방화벽 정책 설계의 출발점입니다.
확대
방화벽 규칙을 설정할 때 가장 먼저 정해야 하는 것은 "어느 방향의 트래픽을 제어하는가"입니다. 서버는 외부 요청을 받기도 하고(Inbound), 외부로 직접 통신을 시작하기도 합니다(Outbound). 이 두 방향에 다른 기본 정책이 적용됩니다.
Inbound(인바운드): 외부에서 서버로 들어오는 트래픽
- 예시: 클라이언트가 웹 서버 80포트에 접속하는 요청
- 기본 정책: 대부분의 서버에서 기본 차단(Default Deny) 후 필요한 포트만 허용
Outbound(아웃바운드): 서버에서 외부로 나가는 트래픽
- 예시: 서버가 패키지 업데이트를 위해 외부 리포지토리에 접속
- 기본 정책: 보안 수준에 따라 허용하거나, 폐쇄망에서는 차단
방향성이 중요한 이유:
- 웹 서버는 인바운드 80/443만 허용해도 동작하지만, DB 서버는 인바운드를 애플리케이션 서버 IP로만 제한해야 한다
- 아웃바운드 제한은 악성코드의 C2(Command & Control) 서버 통신을 차단하는 데 효과적이다
트래픽 방향을 도식으로 보면 규칙이 어느 지점에서 검사되는지 명확해집니다.
확대
Stateful Inspection과 conntrack
현대 방화벽의 핵심은 단순한 패킷 필터링이 아닌 세션 상태 추적이다.
Stateful Inspection과 conntrack 테이블
방화벽 규칙에 인바운드 TCP 80을 허용했는데 HTTP 응답이 안 돌아옵니다. 요청은 들어오는데 응답 패킷이 차단된 겁니다. Stateless 방화벽은 패킷 하나하나를 독립적으로 판단하기 때문에, 내가 먼저 요청한 응답이라도 별도 허용 규칙이 없으면 막습니다. Stateful Inspection이 어떻게 세션 상태를 추적해서 이 문제를 해결하는지 알아야, 왜 현대 방화벽이 응답 트래픽을 따로 허용하지 않아도 동작하는지 납득이 됩니다.
Stateless 패킷 필터링의 문제점:
- 각 패킷을 독립적으로 검사하므로 응답 패킷도 별도로 허용해야 한다
- HTTP 요청에 대한 응답(TCP ACK, 데이터 패킷)을 모두 허용 규칙으로 나열해야 한다
확대
Stateful Inspection:
Linux 커널의 conntrack(Connection Tracking) 모듈이 세션 테이블을 관리한다.
| 패킷 | conntrack 상태 변화 |
|---|---|
| 클라이언트 → 서버:80 (SYN) | NEW 상태로 기록 |
| 서버 → 클라이언트 (SYN-ACK) | ESTABLISHED 상태 |
| 클라이언트 → 서버 (ACK, 데이터) | ESTABLISHED (자동 허용) |
conntrack 상태값 — conntrack이 추적하는 연결 상태 유형과 의미입니다:
| 상태 | 설명 |
|---|---|
| NEW | 새 연결 시도 (첫 번째 패킷) |
| ESTABLISHED | 양방향 통신이 확인된 연결 |
| RELATED | FTP 데이터 채널처럼 기존 연결에서 파생된 연결 |
| INVALID | 어떤 상태에도 속하지 않는 비정상 패킷 |
실습: conntrack 테이블 확인 — 현재 커널이 추적 중인 연결 목록을 조회합니다:
# 실습 디렉토리 준비
mkdir -p /tmp/networking/part4/exam_21 && cd /tmp/networking/part4/exam_21
# conntrack 테이블 조회
conntrack -L
# 특정 포트 필터링
conntrack -L | grep dport=80
# 실시간 이벤트 모니터링
conntrack -E
- Chain 기본 정책 — DROP vs ACCEPT—iptables -L -v 출력에서 Chain INPUT (policy DROP) 처럼 기본 정책을 먼저 확인 — DROP이면 명시적 ACCEPT 없는 패킷은 모두 차단, ACCEPT면 명시적 DROP 없는 패킷은 모두 통과
- pkts/bytes 카운터로 룰 적중 여부 확인—-v 옵션의 pkts 컬럼이 계속 0이면 해당 룰에 도달하는 트래픽이 없는 것 — 룰 순서가 잘못됐거나 출발지 IP·포트 조건이 실제 패킷과 불일치함을 의미
- REJECT vs DROP 응답 차이—REJECT는 클라이언트에게 즉시 거부 메시지를 보내 연결 시도가 빠르게 종료됨 — DROP은 응답이 없어 클라이언트가 타임아웃까지 대기, 외부 공격자에게 포트 존재를 숨길 때 DROP 사용
장점: 인바운드 허용 규칙만 작성해도, 해당 세션의 응답 패킷은 자동으로 허용된다.
ACL: 출발지/목적지/포트/프로토콜 기반 제어
ACL(Access Control List)은 네트워크 접근을 세밀하게 제어하는 규칙 목록이다.
ACL이 패킷을 허용·거부하는 순서 — 5-tuple 추출부터 암묵적 거부까지
ACL에 규칙을 다 넣었는데 특정 트래픽만 막히거나, 반대로 막았어야 할 게 뚫립니다. ACL은 패킷의 5-tuple을 뽑아 규칙을 위에서부터 대조하고, 첫 매칭에서 판정을 끝낸 뒤, 아무 데도 안 걸리면 암묵적으로 거부합니다. 이 순서를 알면 "왜 이 규칙이 안 먹지"가 대부분 순서·범위 문제임이 보입니다.
[패킷 도착] 예: 10.0.1.9 → 10.0.1.20 : 5432 / TCP
│
① 5-tuple 추출: 출발지 IP·포트, 목적지 IP·포트, 프로토콜
│ stateless ACL은 이 다섯 값만 보고 판단
│
② ACL 규칙을 위에서부터 1번씩 대조
│ 각 규칙의 조건(소스·목적지·포트·프로토콜)과 5-tuple 비교
│
③ 첫 매칭 규칙의 액션 적용 → 즉시 종료
│ ALLOW면 통과, DENY면 차단 (뒤 규칙은 보지 않음)
│
④ 어떤 규칙에도 안 맞으면 → 맨 끝의 암묵적 거부(implicit deny)
▼
[③에서 ALLOW된 경우만] 목적지 서비스로
각 단계에서 무슨 일이 일어나고, 어긋나면 어떤 증상인가:
| 단계 | 하는 일 | 여기서 어긋나면 |
|---|---|---|
| ① 5-tuple 추출 | 소스·목적 IP와 포트, 프로토콜을 읽어 판단 재료로 삼음 | stateless ACL은 이게 전부라 응답 패킷도 따로 열어야 함 / stateful(conntrack)은 세션을 기억해 ESTABLISHED 응답을 자동 허용 |
| ② 순차 대조 | 규칙을 위에서 아래로 하나씩 검사 | 넓은 허용(ANY ALLOW·0-65535)이 위에 있으면 아래의 세밀한 DENY가 무력화(too broad·최소 권한 위반) |
| ③ 첫 매칭 적용 | 맞는 첫 규칙의 액션을 실행하고 종료 | DENY를 ALLOW보다 아래 두면 차단 의도가 안 먹음 / 위치가 곧 우선순위라 순서만 바꿔도 결과가 뒤집힘 |
| ④ 암묵적 거부 | 아무 규칙에도 안 맞은 패킷을 기본 차단 | 필요한 포트 규칙을 빠뜨리면 implicit deny에 걸려 조용히 막힘(규칙이 "없어서" 막힌 것) — 안전판이자 흔한 누락 원인 |
즉 ACL 진단은 **"이 패킷의 5-tuple에 어느 규칙이 먼저 맞았나"**를 순서대로 짚는 일입니다. 특정 서버만 막히면 그 출발지 IP가 허용 규칙 조건에 실제로 드는지, 더 위에 넓은 DENY나 순서 역전이 없는지 확인합니다. 나가는 요청은 되는데 응답만 막히면 stateful 허용(ESTABLISHED,RELATED)이 빠졌는지, 아무 규칙에도 안 걸려 막히면 implicit deny 누락을 의심합니다.
mkdir -p /tmp/networking/part4/firewall && cd /tmp/networking/part4/firewall
cat > policy.txt << 'EOF'
# 방화벽 정책 정의서
# 형식: 방향 소스 목적지 포트 프로토콜 액션
# 인바운드 허용
INBOUND ANY THIS_HOST 22 TCP ALLOW # SSH
INBOUND ANY THIS_HOST 80 TCP ALLOW # HTTP
INBOUND ANY THIS_HOST 443 TCP ALLOW # HTTPS
INBOUND 10.0.0.0/8 THIS_HOST ANY ANY ALLOW # 내부망
# 아웃바운드 허용
OUTBOUND THIS_HOST ANY 80 TCP ALLOW # HTTP
OUTBOUND THIS_HOST ANY 443 TCP ALLOW # HTTPS
OUTBOUND THIS_HOST ANY 53 UDP ALLOW # DNS
# 기본 거부
DEFAULT DROP
EOF
이 명령은 방화벽 정책을 변경해 현재 접속 중인 세션이나 운영 트래픽에 즉시 영향을 줄 수 있습니다. 적용 전 허용 대상 IP·포트와 롤백 명령을 확인하세요.
# firewalld 설치 (CentOS/RHEL/Rocky)
sudo dnf install -y firewalld
# firewalld 시작 및 자동 시작 설정
sudo systemctl start firewalld
sudo systemctl enable firewalld
# 현재 방화벽 상태 전체 확인
sudo firewall-cmd --state
sudo firewall-cmd --list-all
# 활성화된 Zone 확인
sudo firewall-cmd --get-active-zones
# 모든 Zone 목록 확인
sudo firewall-cmd --get-zones
예상 출력:
running
public (active)
target: default
icmp-block-inversion: no
interfaces: eth0
sources:
services: cockpit dhcpv6-client ssh
ports:
protocols:
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
firewalld는 사전 정의된 서비스 이름으로 포트를 쉽게 관리할 수 있다.
이 명령은 방화벽 정책을 변경해 현재 접속 중인 세션이나 운영 트래픽에 즉시 영향을 줄 수 있습니다. 적용 전 허용 대상 IP·포트와 롤백 명령을 확인하세요.
# HTTP(80) 허용
sudo firewall-cmd --zone=public --add-service=http --permanent
# HTTPS(443) 허용
sudo firewall-cmd --zone=public --add-service=https --permanent
# SSH(22) 허용 확인 (기본 포함됨)
sudo firewall-cmd --zone=public --list-services
# 사용자 정의 포트 허용 (예: 8080/TCP)
sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent
# 포트 범위 허용 (예: 8000-8100/TCP)
sudo firewall-cmd --zone=public --add-port=8000-8100/tcp --permanent
# 설정 적용 (--permanent 옵션 후 반드시 필요)
sudo firewall-cmd --reload
# 적용 확인
sudo firewall-cmd --zone=public --list-all
주의: --permanent 없이 추가하면 재부팅 시 사라진다. --permanent로 추가하면 --reload가 필요하다.
Rich Rule을 사용하면 출발지 IP, 포트, 프로토콜을 조합한 정밀한 규칙을 만들 수 있다.
이 명령은 방화벽 정책을 변경해 현재 접속 중인 세션이나 운영 트래픽에 즉시 영향을 줄 수 있습니다. 적용 전 허용 대상 IP·포트와 롤백 명령을 확인하세요.
# 특정 IP에서 SSH만 허용 (관리자 IP만 SSH 접속 허용)
sudo firewall-cmd --zone=public --add-rich-rule='
rule family="ipv4"
source address="203.0.113.10/32"
service name="ssh"
accept' --permanent
# 내부 네트워크(192.168.1.0/24)에서 MySQL(3306) 허용
sudo firewall-cmd --zone=public --add-rich-rule='
rule family="ipv4"
source address="192.168.1.0/24"
port port="3306" protocol="tcp"
accept' --permanent
# 특정 IP 차단 (블랙리스트)
sudo firewall-cmd --zone=public --add-rich-rule='
rule family="ipv4"
source address="10.0.0.5/32"
reject' --permanent
# 연결 시도를 로그로 기록하면서 차단
sudo firewall-cmd --zone=public --add-rich-rule='
rule family="ipv4"
source address="10.0.0.0/8"
port port="3306" protocol="tcp"
log prefix="DB_ACCESS_ATTEMPT " level="warning"
reject' --permanent
sudo firewall-cmd --reload
# Rich Rule 목록 확인
sudo firewall-cmd --zone=public --list-rich-rules
firewalld Zone 관리
firewalld Zone 개념과 활용
서버에 인터페이스가 두 개 있습니다. 하나는 내부 관리망, 다른 하나는 외부 공개망에 연결됩니다. 두 인터페이스에 같은 방화벽 규칙을 적용하면 관리망에서나 외부에서나 동일하게 취급되어 보안 구분이 사라집니다. Zone은 인터페이스나 출발지 IP에 따라 신뢰 수준을 다르게 적용하는 개념으로, 이것을 이해해야 "내부망은 관대하게, 외부망은 엄격하게"라는 정책을 firewalld에서 제대로 구현할 수 있습니다.
Zone은 네트워크 인터페이스나 출발지 IP에 따라 다른 신뢰 수준을 부여하는 개념이다.
확대
주요 Zone 비교 — Zone마다 신뢰 수준이 다르며, 인터페이스에 Zone을 할당해 정책을 적용합니다:
| Zone | 신뢰 수준 | 설명 |
|---|---|---|
| trusted | 최고 | 모든 트래픽 허용 (사내 관리망) |
| internal | 높음 | 내부 네트워크용 |
| home | 중간 | 홈 네트워크 |
| public | 낮음 | 외부 인터페이스 기본값 |
| dmz | 낮음 | DMZ 구간 서버 |
| block | 없음 | 모든 인바운드 차단 (ICMP 거부) |
| drop | 없음 | 모든 인바운드 무시 (응답 없음) |
Zone 활용 예시 - 3-tier 아키텍처 — 각 계층에 서로 다른 Zone을 적용해 트래픽 흐름을 제어합니다:
확대
# 인터페이스를 특정 Zone에 할당
sudo firewall-cmd --zone=internal --change-interface=eth1 --permanent
# 출발지 IP 대역을 Zone에 바인딩
sudo firewall-cmd --zone=trusted --add-source=10.0.0.0/8 --permanent
# Zone별 규칙 확인
sudo firewall-cmd --zone=internal --list-all
sudo firewall-cmd --zone=trusted --list-all
프로덕션 서버에 최소 권한 원칙을 적용한다.
이 명령은 방화벽 정책을 변경해 현재 접속 중인 세션이나 운영 트래픽에 즉시 영향을 줄 수 있습니다. 적용 전 허용 대상 IP·포트와 롤백 명령을 확인하세요.
# 현재 public Zone의 불필요한 서비스 제거
# cockpit(웹 콘솔)과 dhcpv6-client 제거
sudo firewall-cmd --zone=public --remove-service=cockpit --permanent
sudo firewall-cmd --zone=public --remove-service=dhcpv6-client --permanent
# 필요한 서비스만 명시적 허용
# 1. 관리자 IP에서만 SSH 허용 (기본 SSH 서비스 제거 후 Rich Rule로 교체)
sudo firewall-cmd --zone=public --remove-service=ssh --permanent
sudo firewall-cmd --zone=public --add-rich-rule='
rule family="ipv4"
source address="YOUR_ADMIN_IP/32"
service name="ssh"
accept' --permanent
# 2. 웹 서비스 허용
sudo firewall-cmd --zone=public --add-service=http --permanent
sudo firewall-cmd --zone=public --add-service=https --permanent
# 3. 기본 정책 확인 (default는 REJECT)
sudo firewall-cmd --zone=public --get-target
# 적용
sudo firewall-cmd --reload
# 최종 확인
sudo firewall-cmd --zone=public --list-all
결과: SSH는 관리자 IP에서만, HTTP/HTTPS는 전체에서, 나머지는 모두 차단된 최소 권한 정책이 완성된다.
기존 세션 끊김 부작용
증상: firewall-cmd --reload를 실행하자마자 SSH 터미널이 응답 없음 상태가 되었다.
원인 분석 — reload 전후 conntrack 상태를 비교해 세션 초기화 여부를 확인합니다:
# 변경 전 conntrack 상태 확인
conntrack -L | grep dport=22
# 출력: tcp 6 86398 ESTABLISHED src=203.0.113.10 dst=10.0.1.5 sport=52431 dport=22 ...
# --reload는 규칙을 완전히 재로드하며, 일부 배포판에서 conntrack 테이블이 초기화됨
# 또는 새 규칙이 기존 ESTABLISHED 세션을 다시 검사
해결 방법:
방법 1: --reload 대신 --runtime-to-permanent 사용
# 임시 규칙을 먼저 적용 (reload 없이 즉시 반영)
sudo firewall-cmd --zone=public --add-service=ssh # --permanent 없이
# 동작 확인 후 영구 저장
sudo firewall-cmd --runtime-to-permanent
방법 2: 새 SSH 세션 열어둔 채로 작업
# 작업 전 다른 터미널에서 SSH 접속을 열어둔다
# 규칙 변경 후 새 세션으로 접속 테스트 → 성공 확인 후 기존 세션 종료
방법 3: at 명령으로 자동 롤백 예약
이 명령은 방화벽 정책을 변경해 현재 접속 중인 세션이나 운영 트래픽에 즉시 영향을 줄 수 있습니다. 적용 전 허용 대상 IP·포트와 롤백 명령을 확인하세요.
# 5분 후 방화벽 규칙 초기화 예약 (실수 대비 안전망)
echo "firewall-cmd --reload" | sudo at now + 5 minutes
# 방화벽 규칙 적용
sudo firewall-cmd --zone=public --remove-service=ssh --permanent
sudo firewall-cmd --reload
# 접속 테스트 성공 시 at 작업 취소
sudo atq # 작업 번호 확인
sudo atrm [작업번호]
예방: 프로덕션 방화벽 변경 시 항상 Maintenance Window를 잡고, 콘솔 접근(IPMI, iDRAC) 또는 Out-of-Band 채널을 확보한다.
증상: 웹 서버 80포트는 접속되는데, 애플리케이션에서 외부 API를 호출하면 타임아웃이 발생한다.
원인 분석 — 아웃바운드 규칙과 실시간 conntrack 세션을 순서대로 점검합니다:
# 아웃바운드 규칙 확인
sudo firewall-cmd --zone=public --list-all
# → outbound 제한이 보임
# 서버에서 외부 API 직접 테스트
curl -v https://api.example.com/v1/test
# → 연결 타임아웃
# 방화벽 로그 확인
sudo journalctl -u firewalld -f
# conntrack으로 아웃바운드 세션 추적
conntrack -L | grep dport=443
해결 — 아웃바운드 HTTPS를 rich-rule로 명시적으로 허용합니다:
이 명령은 방화벽 정책을 변경해 현재 접속 중인 세션이나 운영 트래픽에 즉시 영향을 줄 수 있습니다. 적용 전 허용 대상 IP·포트와 롤백 명령을 확인하세요.
# 아웃바운드 HTTPS 허용 (기본적으로 아웃바운드는 허용이지만,
# 일부 강화된 정책에서는 차단될 수 있음)
sudo firewall-cmd --zone=public --add-rich-rule='
rule family="ipv4"
destination address="0.0.0.0/0"
port port="443" protocol="tcp"
accept' --permanent
# 또는 nftables 직접 확인
sudo nft list ruleset | grep -A5 "output"
sudo firewall-cmd --reload
교훈: 방화벽 정책은 인바운드뿐 아니라 아웃바운드도 고려해야 하며, 특히 서버가 외부 의존성(API, DNS, NTP)을 갖는 경우 아웃바운드 규칙을 명시적으로 관리해야 한다.
심화 — firewalld는 규칙을 어떤 순서로 고르는가
심화: source zone 우선순위와 rich rule priority — 규칙을 다 넣었는데 의도와 다른 이유
규칙을 전부 맞게 넣은 것 같은데 의도와 달리 뚫리거나 막힌다면, 대개 firewalld가 '어느 zone을, 어떤 순서로' 적용하는지를 놓친 것입니다. firewalld는 iptables처럼 규칙을 위에서 아래로 나열해 읽지 않고, 먼저 zone을 고르고 그 안에서 판단합니다.
- source-based zone이 interface zone을 이긴다: 패킷이 도착하면 firewalld는 먼저 '출발지 IP가 어떤 zone의 source로 바인딩돼 있는가'를 봅니다. 바인딩돼 있으면 그 패킷이 어느 인터페이스로 들어오든 인터페이스의 zone이 아니라 그 source zone을 적용합니다. 그래서
firewall-cmd --add-source=10.0.0.0/8을 trusted에 붙이면, 그 대역은 public 인터페이스로 들어와도 trusted(target ACCEPT)로 취급돼 열어둔 적 없는 포트까지 전부 열립니다. '편의상 대역을 trusted에' 넣는 것이 그 대역에 한해 방화벽을 끄는 것과 같습니다. - rich rule은 priority로 순서를 준다: 같은 zone 안에서 accept와 reject rich rule이 겹칠 때,
priority를 지정하지 않으면 '먼저 특정 IP를 차단하고 그다음 대역을 허용' 같은 의도한 순서가 보장되지 않습니다. priority 값이 낮을수록(음수 포함) 먼저 평가되므로, 차단 규칙을 허용보다 앞세우려면 priority를 명시해야 합니다. - 마지막은 zone의 target: 어느 rich rule·service·port에도 안 걸린 나머지는 zone의 기본 target(default/ACCEPT/DROP/REJECT)이 결정합니다. trusted처럼 target이 ACCEPT인 zone은 '나머지 전부 허용'이 기본값입니다.
즉 firewalld에서 '왜 이 IP만 다르게 동작하지'라는 의문은 대개 어느 zone으로 분류됐는가에서 시작해야 풀립니다.
상황: public 인터페이스에는 http/https만 허용해 뒀는데, 모니터링 편의를 위해 사내 대역 10.0.0.0/8을 trusted zone의 source로 추가한 뒤부터, 그 대역에서는 5432(DB)·9090 등 연 적 없는 포트까지 다 접속됩니다. 인터페이스는 여전히 public인데도 그렇습니다.
원인: firewalld는 출발지 IP가 특정 zone에 바인딩돼 있으면, 그 패킷이 어느 인터페이스로 들어오든 인터페이스 zone(public)이 아니라 source zone(trusted)을 적용합니다. trusted는 target이 ACCEPT라 모든 포트를 허용합니다. 결국 '편의상 trusted에 대역 추가'가 그 대역에 한해 방화벽을 사실상 끈 셈입니다.
진단: firewall-cmd --get-active-zones로 그 대역이 어느 zone에 source로 붙었는지 확인합니다. firewall-cmd --zone=trusted --list-all에서 sources에 10.0.0.0/8이 있고 target이 ACCEPT인지 봅니다. 특정 IP가 어느 zone으로 처리되는지 헷갈릴 때는 인터페이스 zone이 아니라 소스 바인딩부터 확인합니다.
해결: 대역을 trusted가 아니라, 필요한 서비스만 허용하는 internal(또는 전용) zone에 붙이고 거기에 rich rule로 꼭 필요한 포트만 엽니다. 전면 신뢰가 정말 필요한 극소수 관리 호스트만 개별 IP로 trusted에 두고, 대역 단위로는 trusted를 쓰지 않습니다(CentOS/Ubuntu 서버 방화벽(firewalld / ufw) 포트 열기에서 zone별 정책 구성을 확인).
실전 방화벽 정책 설계
3-tier 웹 서비스의 애플리케이션 서버에 적합한 방화벽 정책을 완성한다.
이 명령은 방화벽 정책을 변경해 현재 접속 중인 세션이나 운영 트래픽에 즉시 영향을 줄 수 있습니다. 적용 전 허용 대상 IP·포트와 롤백 명령을 확인하세요.
#!/bin/bash
# app-server-firewall.sh
# 애플리케이션 서버 방화벽 정책 설정 스크립트
# 변수 정의
ADMIN_IP="203.0.113.10" # 관리자 IP
WEB_SERVER_IP="10.0.1.10" # 웹 서버 (프론트엔드)
DB_SERVER_IP="10.0.1.20" # DB 서버
# 1. 기본 불필요 서비스 제거
firewall-cmd --zone=public --remove-service=cockpit --permanent
firewall-cmd --zone=public --remove-service=dhcpv6-client --permanent
firewall-cmd --zone=public --remove-service=ssh --permanent
# 2. 관리자만 SSH 허용
firewall-cmd --zone=public --add-rich-rule="
rule family=ipv4
source address=${ADMIN_IP}/32
service name=ssh
accept" --permanent
# 3. 웹 서버에서 앱 포트(8080) 허용
firewall-cmd --zone=public --add-rich-rule="
rule family=ipv4
source address=${WEB_SERVER_IP}/32
port port=8080 protocol=tcp
accept" --permanent
# 4. 앱 서버 → DB 서버 아웃바운드 허용 (나머지 아웃바운드는 제한)
firewall-cmd --zone=public --add-rich-rule="
rule family=ipv4
destination address=${DB_SERVER_IP}/32
port port=5432 protocol=tcp
accept" --permanent
# 5. ICMP (ping) - 네트워크 진단용으로 내부에서만 허용
firewall-cmd --zone=public --add-rich-rule="
rule family=ipv4
source address=10.0.0.0/8
icmp-type name=echo-request
accept" --permanent
# 6. 그 외 모든 트래픽 차단 (기본 target: default = REJECT)
firewall-cmd --zone=public --set-target=default --permanent
firewall-cmd --reload
# 최종 정책 확인
echo "=== 최종 방화벽 정책 ==="
firewall-cmd --zone=public --list-all
# 스크립트 실행
chmod +x app-server-firewall.sh
sudo ./app-server-firewall.sh
# 정책 검증
# 1. 외부에서 SSH 시도 → 거부 확인
# 2. 관리자 IP에서 SSH 접속 → 성공 확인
# 3. 웹 서버에서 8080 포트 접속 → 성공 확인
# 4. 다른 IP에서 8080 포트 접속 → 거부 확인
방화벽 정책 감사와 모니터링
# 방화벽 드롭 패킷 로그 활성화
sudo firewall-cmd --zone=public --add-rich-rule='
rule family="ipv4"
source address="0.0.0.0/0"
log prefix="FIREWALL_DROP " level="info" limit value="5/m"
drop' --permanent
# 로그 실시간 모니터링
sudo journalctl -f | grep FIREWALL_DROP
# 또는 커널 로그에서 확인
sudo dmesg | grep FIREWALL_DROP
# nftables 규칙 전체 확인 (firewalld 백엔드)
sudo nft list ruleset
# 규칙 통계 (패킷/바이트 카운트)
sudo nft list ruleset | grep -E "packets|bytes"
# iptables 호환 모드로 현재 규칙 확인 (구버전 호환)
sudo iptables -L -n -v --line-numbers
정리
이 챕터에서 학습한 핵심 내용:
- 방향성: Inbound는 기본 차단, Outbound는 정책에 따라 관리
- Stateful Inspection: conntrack이 세션 상태를 추적해 응답 패킷을 자동 허용
- ACL 구성 요소: 출발지 IP + 목적지 포트 + 프로토콜 조합으로 세밀한 제어
- firewalld Zone: trusted/public 등 신뢰 수준별 Zone으로 인터페이스 관리
- 최소 권한: 기본 차단 후 필요한 것만 허용 (Default Deny)
- 부작용 관리: 규칙 변경 시 기존 세션 끊김 가능성을 고려한 안전한 작업 절차
명령어·단축키 빠른 참조
이 모듈에서 다룬 방화벽 정책·conntrack 명령을 실전 옵션과 함께 모았습니다. "예" 열의 조합을 그대로 써도 됩니다.
| 명령어/단축키 | 용도 | 자주 쓰는 예 |
|---|---|---|
firewall-cmd --list-all | 현재 zone 규칙 전체 확인 | sudo firewall-cmd --zone=public --list-all |
firewall-cmd --get-active-zones | 인터페이스·source의 zone 바인딩 | source zone 우선순위 함정 진단 시 |
firewall-cmd --add-service / --add-port | 서비스·포트 허용 | --zone=public --add-port=8080/tcp --permanent |
firewall-cmd --add-rich-rule | 출발지 IP+포트+프로토콜 ACL | rule family=ipv4 source address=203.0.113.10/32 service name=ssh accept |
firewall-cmd --add-source | 대역을 zone에 바인딩 | --zone=internal --add-source=10.0.0.0/8 (trusted 금지) |
firewall-cmd --reload | --permanent 규칙 적용 | 세션 끊김 주의 — 대안은 아래 |
firewall-cmd --runtime-to-permanent | 런타임 규칙을 무중단 영구화 | reload 없이 즉시 반영 후 저장 |
firewall-cmd --list-rich-rules | 등록된 rich rule 확인 | --zone=public --list-rich-rules |
conntrack -L | 추적 중인 세션 조회 | conntrack -L | grep dport=22 |
conntrack -C | 현재 추적 연결 수 | nf_conntrack_max의 80%↑면 증설 신호 |
nft list ruleset | firewalld 백엔드 규칙 전체 | sudo nft list ruleset | grep -A5 output |
iptables -L -n -v | 체인 기본정책·룰 적중 카운터 | iptables -L -n -v --line-numbers (pkts 0=미적중) |
... | at now + 5 minutes | 실수 대비 자동 롤백 예약 | echo "firewall-cmd --reload" | sudo at now + 5 minutes |
관련 모듈로 더 깊이:
- CentOS/Ubuntu 서버 방화벽(firewalld / ufw) 포트 열기 — firewalld/ufw로 ACL 정책을 OS에서 실제 구현하는 법
- wget/curl로 외부 연동 방화벽 차단 여부 판별법 — 아웃바운드 정책이 막은 경로를 점검하는 법
- 운영 서버 접속 장애 진단: iptables INPUT 체인 완전 분석 — ACL 규칙이 동작하는 iptables 체인의 내부 구조
다음 챕터에서는 이 방화벽 앞단에서 트래픽을 분산하는 로드밸런서를 학습한다.