웹 서버에 AI 액티브 방화벽을 구현한 이야기 #
Fail2Ban의 고정 임계값으로는 탐지할 수 없는 미지의 Bot 패턴이나 스팸 요청을, 로컬 LLM이 15분마다 분석하여 자동으로 차단하는 구조를 구현했다. 기존 nginx + Fail2Ban 위에 AI 분석 계층을 추가하여 방어를 3단 구조로 만들었다.
Fail2Ban은 우수하지만 “미지”에 취약하다 #
웹 서버의 Bot 대책으로서, nginx의 UA map + 속도 제한 + Fail2Ban 조합은 정석입니다. 403을 10회/분 출력한 IP를 1시간 차단한다. 간단하고 확실합니다.
하지만 이 방식에는 구조적인 약점이 있습니다.
BASTION에서는 이 “사람이라면 알아차릴 수 있지만 고정 규칙으로는 포착할 수 없는 패턴”을 로컬 LLM에게 판단시킵니다.
방어의 3단 구조 #
기존 nginx + Fail2Ban 위에 BASTION의 AI 분석 계층을 추가하여 3단 구조로 만들었습니다.
| 계층 | 담당 | 판단 방식 | 대응 속도 |
|---|---|---|---|
| 제1계층 | nginx(UA map / 속도 제한 / deny.conf) | 정적 규칙 | 즉시(요청 단위) |
| 제2계층 | Fail2Ban(firewalld 연동) | 고정 임계값(403이 10회/분) | 1분 이내 |
| 제3계층 | BASTION(LLM 분석 → SSH 경유 firewalld) | LLM이 로그의 문맥을 판단 | 15분 이내 |
각 계층은 독립적으로 동작합니다. Fail2Ban이 먼저 차단한 IP는 BASTION이 중복 체크하여 이중 차단을 방지. BASTION의 강점은 “미지의 패턴을 탐지할 수 있다”는 점입니다.
모니터링 항목 #
BASTION의 웹 서버 모니터링 템플릿은 다음 6개 카테고리를 15분마다 분석합니다.
| 카테고리 | 데이터 소스 | 탐지 내용 |
|---|---|---|
| HTTP 상태 분포 | 액세스 로그 | 403/429/5xx 집중 IP. 정상 시와의 편차 |
| Bot UA 탐지 | 액세스 로그 | 미지의 Bot UA × 고빈도 요청 |
| 스팸 요청 | 액세스 로그 | 언어 전환 연타 등 특정 파라미터 연타 |
| 악성코드 탐지 | ClamAV 로그 | 업로드 파일의 바이러스 검출 |
| SSH 인증 실패 | sshd 로그 | Failed password / Invalid user |
| 애플리케이션 오류 | PHP-FPM 로그 | FATAL/ERROR 레벨 |
차단 구조 #
왜 WAN 경계의 FW가 아닌 웹 서버 측에서 차단하는가 #
BASTION의 반응형 방어는 통상 WAN 경계의 방화벽 API(OPNsense 등)에서 공격원 IP를 차단합니다(이전 글 참조). 그러나 웹 서버가 DNAT로 공개된 구성에서는 WAN 경계 FW의 차단 리스트로 웹 서버 앞 트래픽을 포착할 수 없는 경우가 있습니다.
그래서 웹 서버 측의 firewalld + nginx deny.conf에 직접 차단 규칙을 넣는 독립적인 방어 경로를 마련했습니다.
BASTION (분석 서버) → SSH 연결(전용 사용자・공개키 인증・최소 권한 sudo) → firewall-cmd로 IP를 drop(L3/L4 레벨) → nginx deny.conf에 IP 추가 + reload(L7 레벨)
SSH 연결의 보안 #
분석 서버에서 웹 서버로의 SSH 연결은 전용 서비스 계정을 사용하며, sudo 권한은 필요 최소한의 명령만 허용하고 있습니다.
# sudoers의 허용 명령(이것 외에는 실행할 수 없음) svc-monitor ALL=(root) NOPASSWD: /usr/bin/firewall-cmd svc-monitor ALL=(root) NOPASSWD: /usr/sbin/nginx svc-monitor ALL=(root) NOPASSWD: /usr/bin/tee -a /etc/nginx/deny.d/* svc-monitor ALL=(root) NOPASSWD: /usr/bin/sed -i * /etc/nginx/deny.d/*
root 로그인이 아닌, 전용 서비스 계정을 생성하여 ISMS 심사에도 견딜 수 있는 최소 권한 설계입니다.
Fail2Ban과의 차이 #
| Fail2Ban | BASTION | |
|---|---|---|
| 판단 방식 | 고정 임계값(”403이 N회/분”) | LLM이 로그의 문맥을 읽고 판단 |
| 미지 패턴 | 탐지 불가(규칙 추가 필요) | 로그의 이상 패턴을 자동 탐지 |
| UA 위조 | 403이 나오지 않으면 탐지 불가 | 요청 빈도×경로×시간대로 판단 |
| ClamAV 연계 | 없음 | FOUND 탐지→업로드원 IP 추적→차단 |
| 알림 | 메일(MTA 의존) | Slack(즉시 알림, 대화 조작 가능) |
| 해제 | bantime으로 고정 | 24시간 자동 해제 + Slack에서 즉시 해제 가능 |
| 감사 | fail2ban.log | 구조화 감사 로그([HB_BLOCK]/[HB_UNBLOCK] 태그) |
Fail2Ban을 대체하는 것이 아니라 공존시킵니다. Fail2Ban은 “403 연발”을 1분 이내에 탐지하여 차단한다. BASTION은 “403에는 이르지 않지만 명백히 부자연스러운 패턴”을 15분마다의 LLM 분석으로 탐지한다. 담당 영역이 다르므로 둘 다 가동하면 사각지대가 줄어듭니다.
실제 동작 #
분석 리포트 #
15분마다 정기 분석에서 웹 서버 섹션이 자동으로 출력됩니다.
웹 서버 상세 분석
요청: 1,847건
상태: 200=1,650 / 403=112 / 429=45 / 5xx=0
403 집중 IP: xxx.xxx.156.12 (40건) — UA: GPTBot/1.0
Bot 의심 UA: “SomeNewBot/2.0” (180건/15분) — 미등록 UA
ClamAV: 탐지 없음
SSH 실패: 0건 / PHP-FPM ERROR: 0건
자동 차단 실행 #
🛡️ 웹 서버 차단 실행
IP: xx.xxx.156.12
이유: bot_spam
firewalld: drop 추가 완료
nginx deny: 적용 완료
수동 해제: @OpenClaw-Monitor ブロック解除 xx.xxx.156.12
ClamAV FOUND 탐지 시 자동 대응 #
ClamAV가 업로드 파일에서 악성코드를 검출한 경우, BASTION은 즉시 CRITICAL 판정을 내립니다. ClamAV 자체는 파일을 격리하는 것에 그치지만, BASTION은 추가로 액세스 로그에서 업로드원 IP를 특정하여 firewalld로 자동 차단합니다. 파일 격리(ClamAV) + 네트워크 차단(BASTION)의 이중 방어입니다.
nginx deny.conf는 “best-effort” 설계 #
firewalld 차단과 nginx deny.conf의 이중 방어를 설계했지만, nginx deny.conf 디렉터리 설정이 없는 환경에서도 firewalld 단독으로 방어가 작동하도록 설계했습니다. deny.conf를 사용할 수 없는 경우 “skipped”라고 로그에 기록하고 firewalld만으로 계속 진행합니다. 나중에 nginx의 include 설정을 추가하면 코드 변경 없이 자동으로 nginx deny도 활성화됩니다. 환경의 준비 상황에 맞춰 단계적으로 강화할 수 있는 설계입니다.
정리 #
웹 서버에 BASTION의 AI 분석 계층을 추가하여 nginx(제1계층) → Fail2Ban(제2계층) → BASTION(제3계층)의 3단 방어를 구축했습니다. 고정 임계값으로는 포착할 수 없는 미지의 Bot 패턴이나 스팸 요청을 로컬 LLM이 15분마다 자동 탐지하여 firewalld에 즉시 차단을 추가합니다.
SSH 연결은 전용 계정・최소 권한 sudo・공개키 인증. nginx deny.conf는 best-effort 설계로 환경의 준비 상황에 따라 단계적으로 활성화 가능합니다. Fail2Ban과는 공존하며 각자 다른 영역을 커버합니다.
BASTION은 폐쇄 환경에서 AI 보안 모니터링을 실현하는 서비스입니다.