# HAProxy WAF 통합 가이드 ModSecurity와 CrowdSec을 HAProxy와 연동하여 웹 애플리케이션 방화벽(WAF)을 구성하는 방법을 설명합니다. ## 목차 1. [개요](#개요) 2. [아키텍처](#아키텍처) 3. [ModSecurity 구성](#modsecurity-구성) 4. [CrowdSec 구성](#crowdsec-구성) 5. [HAProxy 연동](#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 구성 ### 디렉토리 구조 ```bash 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 ```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 설정 파일 ```apache # /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 설정 ```apache # /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 ```ini # /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 이미지 빌드 ```bash # 이미지 빌드 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 구성 ### 디렉토리 구조 ```bash 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 ```ini # /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 설정 파일 ```yaml # /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 ``` ### 로그 수집 설정 ```yaml # /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 ```ini # /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 설정 파일 ```yaml # /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 초기 설정 ```bash # 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 # 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 # 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 함께 사용 (권장) ```haproxy # /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 설정 적용 ```bash # 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 # 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 ```ini # /etc/containers/systemd/crowdsec-bouncer.container (보안 서버) [Container] # ... # 모든 인터페이스에서 수신 PublishPort=0.0.0.0:8888:8888 ``` ### 방화벽 설정 ```bash # 보안 서버 방화벽 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 테스트 ```bash # 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 테스트 ```bash # 1. SQL Injection 테스트 curl -I "https://yourdomain.com/?id=1' OR '1'='1" # 예상: 403 Forbidden # 2. XSS 테스트 curl -I "https://yourdomain.com/?q=" # 예상: 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 ``` ### 통합 테스트 ```bash # 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 관리 ```bash # ===================== # 차단 목록 관리 # ===================== # 현재 차단 목록 조회 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 관리 ```bash # ===================== # 로그 확인 # ===================== # 감사 로그 실시간 확인 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 상태 확인 ```bash # 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 ',' ``` ### 화이트리스트 관리 ```bash # 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 연결 오류 ```bash # 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 오탐 처리 ```bash # 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 캐시 ```yaml # /opt/haproxy/crowdsec-bouncer/config.yaml cache: enabled: true ttl: 60s # 캐시 TTL size: 50000 # 캐시 크기 (IP 수) ``` ### ModSecurity 최적화 ```apache # 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 # 프레임 크기 ```