Files
haproxy-mcp/docs/waf-integration.md
kaffa 02a17a62b4 docs: Add WAF integration docs and ignore lock files
- Add YARA guide, WAF integration docs, and webshell rules
- Ignore *.lock files in .gitignore

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 04:34:49 +00:00

30 KiB

HAProxy WAF 통합 가이드

ModSecurity와 CrowdSec을 HAProxy와 연동하여 웹 애플리케이션 방화벽(WAF)을 구성하는 방법을 설명합니다.

목차

  1. 개요
  2. 아키텍처
  3. ModSecurity 구성
  4. CrowdSec 구성
  5. HAProxy 연동
  6. 별도 서버 구성
  7. 테스트 방법
  8. 운영 명령어

개요

ModSecurity vs CrowdSec

구분 ModSecurity CrowdSec
역할 WAF (웹 애플리케이션 방화벽) 침입 탐지/차단 시스템 (IDS/IPS)
분석 대상 HTTP 요청/응답 내용 IP 기반 행위 패턴
탐지 방식 시그니처 기반 (OWASP CRS) 행위 기반 + 커뮤니티 위협 인텔리전스
차단 범위 개별 악성 요청 IP 단위 (반복 공격자)
주요 탐지 SQL Injection, XSS, RCE, LFI/RFI 브루트포스, 스캐닝, DDoS, 알려진 악성 IP
성능 영향 높음 (모든 요청 검사) 낮음 (IP 조회만)

권장 조합

CrowdSec (먼저) → ModSecurity (나중)
  • CrowdSec: 알려진 악성 IP를 빠르게 차단 (낮은 지연)
  • ModSecurity: 나머지 요청에 대해 상세 분석 (깊은 검사)

아키텍처

동일 서버 구성 (권장)

┌─────────────────────────────────────────────────────────────────────────┐
│                              Host Server                                 │
│                                                                          │
│  ┌──────────────────────────────────────────────────────────────────┐   │
│  │                    HAProxy (host network)                         │   │
│  │                    TCP 80/443 + UDP 443                           │   │
│  │                                                                   │   │
│  │    ┌─────────────┐      ┌─────────────┐                          │   │
│  │    │ SPOE Agent  │      │ SPOE Agent  │                          │   │
│  │    │ (CrowdSec)  │      │(ModSecurity)│                          │   │
│  │    └──────┬──────┘      └──────┬──────┘                          │   │
│  └───────────┼───────────────────┼──────────────────────────────────┘   │
│              │                   │                                       │
│              │ TCP :8888         │ TCP :12345                           │
│              ▼                   ▼                                       │
│  ┌───────────────────┐  ┌───────────────────┐  ┌───────────────────┐   │
│  │  CrowdSec Bouncer │  │ ModSecurity SPOA  │  │  CrowdSec Engine  │   │
│  │  (haproxy bouncer)│  │ (modsecurity-spoa)│  │  (crowdsec)       │   │
│  │  Podman Container │  │  Podman Container │  │  Podman Container │   │
│  │                   │  │                   │  │                   │   │
│  │  - API Key 검증   │  │  - OWASP CRS 3.x  │  │  - 로그 분석      │   │
│  │  - LAPI 연동     │  │  - 요청 검사       │  │  - 시나리오 탐지  │   │
│  └─────────┬─────────┘  └───────────────────┘  │  - 알림 발송      │   │
│            │                                    └─────────┬─────────┘   │
│            │ TCP :8080                                    │              │
│            └──────────────────────────────────────────────┘              │
│                           (LAPI 연동)                                    │
└─────────────────────────────────────────────────────────────────────────┘

별도 서버 구성

┌──────────────────────┐          ┌──────────────────────┐
│    HAProxy Server    │          │   Security Server    │
│                      │          │                      │
│  ┌────────────────┐  │          │  ┌────────────────┐  │
│  │    HAProxy     │  │  TCP     │  │   CrowdSec     │  │
│  │  (host network)│──┼──:8888───┼─▶│    Bouncer     │  │
│  │                │  │          │  └───────┬────────┘  │
│  │                │──┼─:12345───┼─▶│                │  │
│  └────────────────┘  │          │  │ ModSecurity    │  │
│                      │          │  │     SPOA       │  │
│                      │          │  └────────────────┘  │
│                      │          │                      │
│                      │          │  ┌────────────────┐  │
│                      │          │  │   CrowdSec     │  │
│                      │          │  │    Engine      │  │
│                      │          │  └────────────────┘  │
└──────────────────────┘          └──────────────────────┘

ModSecurity 구성

디렉토리 구조

mkdir -p /opt/haproxy/modsecurity/{conf,rules,logs}
/opt/haproxy/modsecurity/
├── conf/
│   ├── modsecurity.conf      # ModSecurity 메인 설정
│   └── crs-setup.conf        # OWASP CRS 설정
├── rules/
│   └── (OWASP CRS 룰셋)
├── logs/
│   └── modsec_audit.log
└── Dockerfile

Dockerfile

# /opt/haproxy/modsecurity/Dockerfile
FROM alpine:3.19

# 빌드 의존성 설치
RUN apk add --no-cache \
    git \
    build-base \
    autoconf \
    automake \
    libtool \
    pcre2-dev \
    yajl-dev \
    curl-dev \
    libxml2-dev \
    lmdb-dev \
    lua5.4-dev \
    geoip-dev \
    libmaxminddb-dev \
    linux-headers \
    haproxy

# ModSecurity v3 빌드
WORKDIR /build
RUN git clone --depth 1 https://github.com/owasp-modsecurity/ModSecurity.git && \
    cd ModSecurity && \
    git submodule init && \
    git submodule update && \
    ./build.sh && \
    ./configure --with-pcre2 --with-lmdb && \
    make -j$(nproc) && \
    make install

# SPOA 에이전트 빌드
RUN git clone --depth 1 https://github.com/haproxy/spoa-modsecurity.git && \
    cd spoa-modsecurity && \
    make MODSEC_INC=/usr/local/modsecurity/include \
         MODSEC_LIB=/usr/local/modsecurity/lib \
         APACHE2_INC=/usr/include/apache2

# OWASP CRS 다운로드
RUN git clone --depth 1 https://github.com/coreruleset/coreruleset.git /opt/owasp-crs && \
    cp /opt/owasp-crs/crs-setup.conf.example /opt/owasp-crs/crs-setup.conf

# 런타임 이미지
FROM alpine:3.19

RUN apk add --no-cache \
    pcre2 \
    yajl \
    libcurl \
    libxml2 \
    lmdb \
    lua5.4-libs \
    geoip \
    libmaxminddb

# 빌드 결과 복사
COPY --from=0 /usr/local/modsecurity /usr/local/modsecurity
COPY --from=0 /build/spoa-modsecurity/modsecurity /usr/local/bin/modsecurity-spoa
COPY --from=0 /opt/owasp-crs /opt/owasp-crs

# 라이브러리 경로 설정
ENV LD_LIBRARY_PATH=/usr/local/modsecurity/lib

# 설정 디렉토리
VOLUME ["/etc/modsecurity", "/var/log/modsecurity"]

# SPOA 포트
EXPOSE 12345

# 실행
ENTRYPOINT ["/usr/local/bin/modsecurity-spoa"]
CMD ["-f", "/etc/modsecurity/modsecurity.conf", "-a", "0.0.0.0", "-p", "12345", "-n", "4"]

ModSecurity 설정 파일

# /opt/haproxy/modsecurity/conf/modsecurity.conf

# 기본 설정
SecRuleEngine On
SecRequestBodyAccess On
SecResponseBodyAccess Off

# 요청 본문 설정
SecRequestBodyLimit 13107200
SecRequestBodyNoFilesLimit 131072
SecRequestBodyLimitAction Reject

# 임시 파일 경로
SecTmpDir /tmp/
SecDataDir /tmp/

# 로깅 설정
SecAuditEngine RelevantOnly
SecAuditLogRelevantStatus "^(?:5|4(?!04))"
SecAuditLogParts ABIJDEFHZ
SecAuditLogType Serial
SecAuditLog /var/log/modsecurity/modsec_audit.log

# 디버그 로그 (문제 해결시 활성화)
# SecDebugLog /var/log/modsecurity/debug.log
# SecDebugLogLevel 3

# 응답 상태
SecStatusEngine On

# 유니코드 매핑
SecUnicodeMapFile unicode.mapping 20127

# PCRE 설정
SecPcreMatchLimit 100000
SecPcreMatchLimitRecursion 100000

# OWASP CRS 포함
Include /opt/owasp-crs/crs-setup.conf
Include /opt/owasp-crs/rules/*.conf

OWASP CRS 설정

# /opt/haproxy/modsecurity/conf/crs-setup.conf

# Paranoia Level (1-4, 높을수록 엄격)
SecAction \
    "id:900000,\
    phase:1,\
    pass,\
    t:none,\
    nolog,\
    setvar:tx.blocking_paranoia_level=1"

# 탐지 Paranoia Level
SecAction \
    "id:900001,\
    phase:1,\
    pass,\
    t:none,\
    nolog,\
    setvar:tx.detection_paranoia_level=1"

# 차단 임계값 설정
SecAction \
    "id:900110,\
    phase:1,\
    pass,\
    t:none,\
    nolog,\
    setvar:tx.inbound_anomaly_score_threshold=5,\
    setvar:tx.outbound_anomaly_score_threshold=4"

# 특정 규칙 제외 (오탐 방지)
# SecRuleRemoveById 920350
# SecRuleRemoveById 942100

# 특정 경로 제외
SecRule REQUEST_URI "@beginsWith /api/upload" \
    "id:1001,\
    phase:1,\
    pass,\
    t:none,\
    nolog,\
    ctl:ruleRemoveById=920420"

# 허용 IP (내부망)
SecRule REMOTE_ADDR "@ipMatch 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,100.64.0.0/10" \
    "id:1002,\
    phase:1,\
    pass,\
    t:none,\
    nolog,\
    ctl:ruleEngine=DetectionOnly"

ModSecurity Quadlet

# /etc/containers/systemd/modsecurity.container

[Unit]
Description=ModSecurity SPOA Agent
After=network-online.target
Wants=network-online.target

[Container]
Image=localhost/modsecurity-spoa:latest
ContainerName=modsecurity
AutoUpdate=local

# 네트워크 설정
PublishPort=12345:12345

# 볼륨 마운트
Volume=/opt/haproxy/modsecurity/conf:/etc/modsecurity:ro,Z
Volume=/opt/haproxy/modsecurity/logs:/var/log/modsecurity:Z

# 환경 변수
Environment=LD_LIBRARY_PATH=/usr/local/modsecurity/lib

# 리소스 제한
Memory=512M
CPUQuota=100%

[Service]
Restart=always
RestartSec=5
TimeoutStartSec=60

[Install]
WantedBy=multi-user.target

ModSecurity 이미지 빌드

# 이미지 빌드
cd /opt/haproxy/modsecurity
podman build -t localhost/modsecurity-spoa:latest .

# Quadlet 활성화
systemctl daemon-reload
systemctl start modsecurity
systemctl enable modsecurity

# 상태 확인
systemctl status modsecurity
podman logs -f modsecurity

CrowdSec 구성

디렉토리 구조

mkdir -p /opt/haproxy/crowdsec/{config,data,hub}
mkdir -p /opt/haproxy/crowdsec-bouncer
/opt/haproxy/crowdsec/
├── config/
│   ├── config.yaml           # CrowdSec 메인 설정
│   ├── acquis.yaml           # 로그 수집 설정
│   └── local_api_credentials.yaml
├── data/
│   └── (데이터베이스)
└── hub/
    └── (시나리오, 파서)

/opt/haproxy/crowdsec-bouncer/
└── config.yaml               # HAProxy Bouncer 설정

CrowdSec Engine Quadlet

# /etc/containers/systemd/crowdsec.container

[Unit]
Description=CrowdSec Security Engine
After=network-online.target
Wants=network-online.target

[Container]
Image=docker.io/crowdsecurity/crowdsec:latest
ContainerName=crowdsec
AutoUpdate=registry

# 네트워크 설정
PublishPort=8080:8080
PublishPort=6060:6060

# 볼륨 마운트
Volume=/opt/haproxy/crowdsec/config:/etc/crowdsec:Z
Volume=/opt/haproxy/crowdsec/data:/var/lib/crowdsec/data:Z
Volume=/opt/haproxy/crowdsec/hub:/var/lib/crowdsec/hub:Z

# HAProxy 로그 접근 (호스트 로그)
Volume=/var/log:/var/log:ro

# 환경 변수
Environment=COLLECTIONS="crowdsecurity/haproxy crowdsecurity/http-cve crowdsecurity/linux"
Environment=GID=1000

[Service]
Restart=always
RestartSec=10
TimeoutStartSec=120

[Install]
WantedBy=multi-user.target

CrowdSec 설정 파일

# /opt/haproxy/crowdsec/config/config.yaml

common:
  daemonize: false
  log_media: stdout
  log_level: info
  log_dir: /var/log/
  working_dir: /var/lib/crowdsec/data/

config_paths:
  config_dir: /etc/crowdsec/
  data_dir: /var/lib/crowdsec/data/
  simulation_path: /etc/crowdsec/simulation.yaml
  hub_dir: /var/lib/crowdsec/hub/
  index_path: /var/lib/crowdsec/hub/.index.json
  notification_dir: /etc/crowdsec/notifications/
  plugin_dir: /usr/local/lib/crowdsec/plugins/

crowdsec_service:
  acquisition_path: /etc/crowdsec/acquis.yaml
  parser_routines: 1

cscli:
  output: human

db_config:
  log_level: info
  type: sqlite
  db_path: /var/lib/crowdsec/data/crowdsec.db

api:
  client:
    insecure_skip_verify: false
    credentials_path: /etc/crowdsec/local_api_credentials.yaml
  server:
    log_level: info
    listen_uri: 0.0.0.0:8080
    profiles_path: /etc/crowdsec/profiles.yaml
    online_client:
      credentials_path: /etc/crowdsec/online_api_credentials.yaml

prometheus:
  enabled: true
  level: full
  listen_addr: 0.0.0.0
  listen_port: 6060

로그 수집 설정

# /opt/haproxy/crowdsec/config/acquis.yaml

# HAProxy 로그 (호스트에서 rsyslog/journald로 수집)
filenames:
  - /var/log/haproxy.log
labels:
  type: haproxy
---
# syslog에서 HAProxy 로그 수집
filenames:
  - /var/log/syslog
  - /var/log/messages
labels:
  type: syslog
---
# HAProxy 컨테이너 로그 (journald)
source: journalctl
journalctl_filter:
  - "_SYSTEMD_UNIT=haproxy.service"
labels:
  type: haproxy

HAProxy Bouncer Quadlet

# /etc/containers/systemd/crowdsec-bouncer.container

[Unit]
Description=CrowdSec HAProxy Bouncer
After=crowdsec.service
Requires=crowdsec.service

[Container]
Image=docker.io/crowdsecurity/crowdsec-haproxy-bouncer:latest
ContainerName=crowdsec-bouncer
AutoUpdate=registry

# 네트워크 설정 (HAProxy가 연결할 포트)
PublishPort=8888:8888

# 설정 파일
Volume=/opt/haproxy/crowdsec-bouncer:/etc/crowdsec:Z

# 환경 변수 (LAPI 연결 정보)
Environment=CROWDSEC_LAPI_URL=http://host.containers.internal:8080
Environment=CROWDSEC_LAPI_KEY=

[Service]
Restart=always
RestartSec=5
TimeoutStartSec=30
# ExecStartPre에서 API 키 설정
ExecStartPre=/bin/sh -c 'podman exec crowdsec cscli bouncers add haproxy-bouncer -o raw > /opt/haproxy/crowdsec-bouncer/api_key.txt 2>/dev/null || true'

[Install]
WantedBy=multi-user.target

Bouncer 설정 파일

# /opt/haproxy/crowdsec-bouncer/config.yaml

# SPOE 서버 설정
listen_addr: 0.0.0.0:8888

# CrowdSec LAPI 연결
crowdsec_lapi_url: http://host.containers.internal:8080
crowdsec_lapi_key: "${API_KEY}"  # 자동 생성됨

# 캐시 설정 (성능 최적화)
cache:
  enabled: true
  ttl: 60s
  size: 10000

# 로깅
log_level: info
log_mode: stdout

# 차단 응답
ban_response_code: 403
ban_response_msg: "Access Denied by CrowdSec"

# Captcha (선택)
captcha:
  enabled: false

CrowdSec 초기 설정

# CrowdSec 시작
systemctl daemon-reload
systemctl start crowdsec
systemctl enable crowdsec

# 컬렉션 설치 확인
podman exec crowdsec cscli collections list

# HAProxy 컬렉션 설치 (필요시)
podman exec crowdsec cscli collections install crowdsecurity/haproxy
podman exec crowdsec cscli collections install crowdsecurity/http-cve

# Bouncer API 키 생성
podman exec crowdsec cscli bouncers add haproxy-bouncer -o raw

# 생성된 키를 bouncer 설정에 추가
# /opt/haproxy/crowdsec-bouncer/config.yaml의 crowdsec_lapi_key에 설정

# Bouncer 시작
systemctl start crowdsec-bouncer
systemctl enable crowdsec-bouncer

# 연결 확인
podman exec crowdsec cscli bouncers list

HAProxy 연동

SPOE 설정 파일

CrowdSec SPOE

# /opt/haproxy/conf/crowdsec-spoe.conf

[crowdsec]
spoe-agent crowdsec-agent
    messages check-client-ip
    option var-prefix crowdsec
    timeout hello      2s
    timeout idle       2m
    timeout processing 10ms
    use-backend crowdsec-backend

spoe-message check-client-ip
    args src=src dst=dst method=method path=path query=query version=req.ver headers=req.hdrs body=req.body
    event on-frontend-http-request

ModSecurity SPOE

# /opt/haproxy/conf/modsecurity-spoe.conf

[modsecurity]
spoe-agent modsecurity-agent
    messages check-request
    option var-prefix modsec
    timeout hello      100ms
    timeout idle       30s
    timeout processing 1s
    use-backend modsecurity-backend

spoe-message check-request
    args unique-id=unique-id src-ip=src dst-ip=dst dst-port=dst_port method=method path=path query=query version=req.ver headers=req.hdrs body=req.body
    event on-frontend-http-request

HAProxy 설정 수정

아래 내용을 /opt/haproxy/conf/haproxy.cfg에 추가합니다.

방법 1: CrowdSec만 사용

# haproxy.cfg에 추가 (global 섹션 아래)

# =============================================================================
# CrowdSec 연동 (IP 기반 차단)
# =============================================================================

# CrowdSec Bouncer 백엔드
backend crowdsec-backend
    mode tcp
    server crowdsec 127.0.0.1:8888 check

# HTTPS Frontend에 SPOE 필터 추가
frontend https_front
    # ... 기존 설정 유지 ...

    # CrowdSec SPOE 필터
    filter spoe engine crowdsec config /usr/local/etc/haproxy/crowdsec-spoe.conf

    # CrowdSec 차단 (crowdsec.action이 설정되면 차단)
    http-request deny deny_status 403 if { var(sess.crowdsec.action) -m str ban }
    http-request deny deny_status 403 if { var(sess.crowdsec.action) -m str captcha }

방법 2: ModSecurity만 사용

# haproxy.cfg에 추가 (global 섹션 아래)

# =============================================================================
# ModSecurity 연동 (WAF)
# =============================================================================

# ModSecurity SPOA 백엔드
backend modsecurity-backend
    mode tcp
    server modsec 127.0.0.1:12345 check

# HTTPS Frontend에 SPOE 필터 추가
frontend https_front
    # ... 기존 설정 유지 ...

    # 유니크 ID 생성 (ModSecurity 트랜잭션 추적용)
    unique-id-format %{+X}o\ %ci:%cp_%fi:%fp_%Ts_%rt:%pid
    unique-id-header X-Unique-ID

    # ModSecurity SPOE 필터
    filter spoe engine modsecurity config /usr/local/etc/haproxy/modsecurity-spoe.conf

    # ModSecurity 차단 (modsec.code가 0이 아니면 차단)
    http-request deny deny_status 403 if { var(txn.modsec.code) -m int gt 0 }

방법 3: CrowdSec + ModSecurity 함께 사용 (권장)

# /opt/haproxy/conf/haproxy.cfg

global
    # ... 기존 global 설정 유지 ...
    log stdout format raw local0

defaults
    # ... 기존 defaults 설정 유지 ...

# =============================================================================
# WAF 백엔드 (CrowdSec + ModSecurity)
# =============================================================================

# CrowdSec Bouncer 백엔드
backend crowdsec-backend
    mode tcp
    server crowdsec 127.0.0.1:8888 check

# ModSecurity SPOA 백엔드
backend modsecurity-backend
    mode tcp
    server modsec 127.0.0.1:12345 check

# =============================================================================
# HTTPS Frontend (WAF 통합)
# =============================================================================

frontend https_front
    bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
    bind quic4@:443 ssl crt /etc/haproxy/certs/ alpn h3
    http-response set-header alt-svc "h3=\":443\"; ma=86400"

    # 유니크 ID 생성
    unique-id-format %{+X}o\ %ci:%cp_%fi:%fp_%Ts_%rt:%pid
    unique-id-header X-Unique-ID

    # =========================================================================
    # WAF 필터 (순서 중요: CrowdSec 먼저, ModSecurity 나중)
    # =========================================================================

    # 1. CrowdSec SPOE (IP 기반 빠른 차단)
    filter spoe engine crowdsec config /usr/local/etc/haproxy/crowdsec-spoe.conf

    # 2. ModSecurity SPOE (상세 요청 분석)
    filter spoe engine modsecurity config /usr/local/etc/haproxy/modsecurity-spoe.conf

    # =========================================================================
    # 차단 규칙 (순서 중요)
    # =========================================================================

    # CrowdSec 차단 (알려진 악성 IP)
    http-request deny deny_status 403 content-type "text/plain" string "Access denied by CrowdSec" if { var(sess.crowdsec.action) -m str ban }
    http-request deny deny_status 403 if { var(sess.crowdsec.action) -m str captcha }

    # ModSecurity 차단 (WAF 규칙 위반)
    http-request deny deny_status 403 content-type "text/plain" string "Access denied by WAF" if { var(txn.modsec.code) -m int gt 0 }

    # =========================================================================
    # 내부 IP 우회 (선택사항)
    # =========================================================================

    # Tailscale IP는 WAF 우회
    acl is_tailscale src 100.64.0.0/10
    # http-request set-var(txn.skip_waf) bool(true) if is_tailscale

    # =========================================================================
    # 기존 라우팅 설정 (변경 없음)
    # =========================================================================

    # MCP 인증
    acl is_mcp hdr(host) -i mcp.inouter.com
    acl valid_token req.hdr(Authorization) -m str "Bearer dcb7963ab3ef705f6b780818f78942a100efa3b55e3d2f99c4560b65da64c426"
    http-request deny deny_status 401 if is_mcp !valid_token !is_tailscale

    # Map 기반 라우팅
    use_backend %[req.hdr(host),lower,map_dom(/usr/local/etc/haproxy/domains.map)] if { req.hdr(host),lower,map_dom(/usr/local/etc/haproxy/domains.map) -m found }

    default_backend default_backend

# ... 나머지 backend 설정 유지 ...

HAProxy 설정 적용

# SPOE 설정 파일 복사 (이미 conf 디렉토리에 있다면 생략)
# HAProxy 컨테이너에서 /usr/local/etc/haproxy/로 마운트됨

# 설정 검증
podman exec haproxy haproxy -c -f /usr/local/etc/haproxy/haproxy.cfg

# HAProxy 리로드 (무중단)
systemctl reload haproxy

# 또는 완전 재시작
systemctl restart haproxy

별도 서버 구성

대규모 트래픽이나 보안 격리가 필요한 경우, CrowdSec과 ModSecurity를 별도 서버에서 운영할 수 있습니다.

HAProxy 서버 설정

# haproxy.cfg (HAProxy 서버)

# CrowdSec Bouncer (원격 서버)
backend crowdsec-backend
    mode tcp
    server crowdsec 10.0.0.100:8888 check

# ModSecurity SPOA (원격 서버)
backend modsecurity-backend
    mode tcp
    server modsec 10.0.0.100:12345 check

보안 서버 Quadlet

# /etc/containers/systemd/crowdsec-bouncer.container (보안 서버)

[Container]
# ...
# 모든 인터페이스에서 수신
PublishPort=0.0.0.0:8888:8888

방화벽 설정

# 보안 서버 방화벽
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="HAProxy서버IP" port port="8888" protocol="tcp" accept'
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="HAProxy서버IP" port port="12345" protocol="tcp" accept'
firewall-cmd --reload

테스트 방법

CrowdSec 테스트

# 1. Bouncer 연결 확인
podman exec crowdsec cscli bouncers list

# 2. 현재 차단 목록 확인
podman exec crowdsec cscli decisions list

# 3. 테스트 IP 수동 차단
podman exec crowdsec cscli decisions add --ip 1.2.3.4 --reason "test ban" --duration 1h

# 4. 차단 확인 (403 응답)
curl -I https://yourdomain.com --resolve yourdomain.com:443:1.2.3.4

# 5. 테스트 차단 해제
podman exec crowdsec cscli decisions delete --ip 1.2.3.4

# 6. HAProxy SPOE 통계 확인
echo "show stat" | nc localhost 9999 | grep crowdsec

ModSecurity 테스트

# 1. SQL Injection 테스트
curl -I "https://yourdomain.com/?id=1' OR '1'='1"
# 예상: 403 Forbidden

# 2. XSS 테스트
curl -I "https://yourdomain.com/?q=<script>alert(1)</script>"
# 예상: 403 Forbidden

# 3. Path Traversal 테스트
curl -I "https://yourdomain.com/../../../etc/passwd"
# 예상: 403 Forbidden

# 4. 로그 확인
tail -f /opt/haproxy/modsecurity/logs/modsec_audit.log

# 5. HAProxy SPOE 통계 확인
echo "show stat" | nc localhost 9999 | grep modsec

통합 테스트

# 1. 정상 요청 테스트
curl -I https://yourdomain.com
# 예상: 200 OK

# 2. nikto 스캔 시뮬레이션 (CrowdSec 탐지)
for i in {1..100}; do
    curl -s -o /dev/null "https://yourdomain.com/admin" &
done

# 3. 차단 확인
podman exec crowdsec cscli decisions list

# 4. 메트릭 확인
curl http://localhost:6060/metrics | grep crowdsec

운영 명령어

CrowdSec 관리

# =====================
# 차단 목록 관리
# =====================

# 현재 차단 목록 조회
podman exec crowdsec cscli decisions list

# 특정 IP 차단 정보 조회
podman exec crowdsec cscli decisions list --ip 1.2.3.4

# IP 수동 차단
podman exec crowdsec cscli decisions add --ip 1.2.3.4 --reason "manual ban" --duration 24h

# IP 대역 차단
podman exec crowdsec cscli decisions add --range 1.2.3.0/24 --reason "subnet ban" --duration 24h

# 차단 해제
podman exec crowdsec cscli decisions delete --ip 1.2.3.4

# 모든 차단 해제
podman exec crowdsec cscli decisions delete --all

# =====================
# 알림 및 시나리오
# =====================

# 최근 알림 조회
podman exec crowdsec cscli alerts list

# 시나리오 목록
podman exec crowdsec cscli scenarios list

# 파서 목록
podman exec crowdsec cscli parsers list

# =====================
# Hub 관리 (컬렉션/시나리오)
# =====================

# 업데이트 확인
podman exec crowdsec cscli hub update

# 모든 업그레이드
podman exec crowdsec cscli hub upgrade

# 컬렉션 설치
podman exec crowdsec cscli collections install crowdsecurity/nginx
podman exec crowdsec cscli collections install crowdsecurity/http-cve

# =====================
# Bouncer 관리
# =====================

# Bouncer 목록
podman exec crowdsec cscli bouncers list

# Bouncer 추가
podman exec crowdsec cscli bouncers add my-bouncer

# Bouncer 삭제
podman exec crowdsec cscli bouncers delete my-bouncer

# =====================
# 메트릭 및 상태
# =====================

# 엔진 상태
podman exec crowdsec cscli metrics

# Prometheus 메트릭
curl http://localhost:6060/metrics

ModSecurity 관리

# =====================
# 로그 확인
# =====================

# 감사 로그 실시간 확인
tail -f /opt/haproxy/modsecurity/logs/modsec_audit.log

# 차단된 요청만 필터링
grep -A 20 "403" /opt/haproxy/modsecurity/logs/modsec_audit.log

# 특정 규칙 ID로 필터링
grep "942100" /opt/haproxy/modsecurity/logs/modsec_audit.log

# =====================
# 규칙 관리
# =====================

# OWASP CRS 업데이트
cd /opt/haproxy/modsecurity
podman exec modsecurity git -C /opt/owasp-crs pull

# 규칙 비활성화 (crs-setup.conf 또는 별도 파일에 추가)
# SecRuleRemoveById 942100

# 특정 경로 제외
# SecRule REQUEST_URI "@beginsWith /api/webhook" "id:1003,phase:1,pass,nolog,ctl:ruleEngine=Off"

# =====================
# 서비스 관리
# =====================

# 컨테이너 재시작
systemctl restart modsecurity

# 로그 확인
podman logs -f modsecurity

HAProxy WAF 상태 확인

# SPOE 에이전트 상태
echo "show stat" | nc localhost 9999 | grep -E "(crowdsec|modsec)"

# 백엔드 상태
echo "show servers state" | nc localhost 9999 | grep -E "(crowdsec|modsec)"

# 필터 통계
echo "show stat" | nc localhost 9999 | column -t -s ','

화이트리스트 관리

# CrowdSec 화이트리스트 추가
podman exec crowdsec cscli decisions add --ip 10.0.0.1 --type whitelist --duration 87600h --reason "Internal server"

# ModSecurity 화이트리스트 (modsecurity.conf에 추가)
# SecRule REMOTE_ADDR "@ipMatch 10.0.0.1" "id:1,phase:1,pass,nolog,ctl:ruleEngine=Off"

문제 해결

CrowdSec 연결 오류

# Bouncer 연결 확인
podman exec crowdsec cscli bouncers list

# API 키 재생성
podman exec crowdsec cscli bouncers delete haproxy-bouncer
podman exec crowdsec cscli bouncers add haproxy-bouncer -o raw

# Bouncer 재시작
systemctl restart crowdsec-bouncer

ModSecurity 오탐 처리

# 1. 감사 로그에서 규칙 ID 확인
grep "id \"" /opt/haproxy/modsecurity/logs/modsec_audit.log | tail -20

# 2. 규칙 비활성화 (crs-setup.conf)
# SecRuleRemoveById 942100

# 3. 또는 특정 경로만 제외
# SecRule REQUEST_URI "@beginsWith /api/" "id:1,phase:1,pass,nolog,ctl:ruleRemoveById=942100"

# 4. 컨테이너 재시작
systemctl restart modsecurity

HAProxy SPOE 타임아웃

# SPOE 타임아웃 조정 (haproxy.cfg)
spoe-agent crowdsec-agent
    timeout processing 100ms  # 기본 10ms에서 증가

성능 튜닝

CrowdSec Bouncer 캐시

# /opt/haproxy/crowdsec-bouncer/config.yaml
cache:
  enabled: true
  ttl: 60s      # 캐시 TTL
  size: 50000   # 캐시 크기 (IP 수)

ModSecurity 최적화

# modsecurity.conf
SecRequestBodyLimit 1048576        # 1MB로 제한
SecRequestBodyNoFilesLimit 65536   # 64KB
SecPcreMatchLimit 50000            # PCRE 매칭 제한
SecPcreMatchLimitRecursion 50000

HAProxy SPOE 워커

# SPOE 설정
spoe-agent modsecurity-agent
    option async               # 비동기 처리
    option pipelining          # 파이프라이닝
    max-frame-size 16384       # 프레임 크기