Files
obsidian/infra/security/crowdsec-safeline.md

388 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: CrowdSec 및 SafeLine WAF
updated: 2026-04-25
---
## CrowdSec LAPI
| 항목 | 값 |
|------|-----|
| 위치 | jp1 Incus `crowdsec` 컨테이너 |
| LAPI | `http://10.253.100.240:8080` |
| 관리 | `ssh incus-jp1 "incus exec crowdsec -- cscli ..."` |
| 버전 | v1.7.7 (최신, 2026-04-17 확인) |
## 로그 수집 (Acquisition)
### Traefik → CrowdSec (Vector)
```
Traefik DaemonSet (stdout JSON accessLog)
→ Vector Agent DaemonSet (K3s logging ns, kubernetes_logs source)
→ VRL transform (access log만 필터, non-JSON abort)
→ HTTP sink (배치 50건, 5초)
→ CrowdSec HTTP acquisition (:8086/traefik-logs)
→ crowdsecurity/traefik-logs 파서
```
| 항목 | 값 |
|------|-----|
| Vector | Helm `vector/vector` 0.51.0, Agent DaemonSet (3노드) |
| Values | `~/k8s/vector/values.yaml` |
| CrowdSec 포트 | 8086 |
| 인증 | `Authorization: traefik-crowdsec-log-2024` |
| 파서 | `crowdsecurity/nginx-logs` (Hub, 표준 nginx combined). Vector에서 모든 로그를 표준 포맷으로 변환 후 VictoriaLogs 저장 |
### APISIX + Traefik + NPM → VictoriaLogs → CrowdSec (통합 acquisition, 2026-04-25)
```
서울 APISIX (K3s) stdout → Vector DaemonSet ─┐
오사카 APISIX (Docker) stdout → Vector ────────┤
K3s Traefik stdout → Vector DaemonSet ────────┤→ VictoriaLogs (ES bulk API)
Netbis NPM 6대 nginx file → Vector → zlambda─┘
CrowdSec victorialogs acquisition (tail, 실시간, 단일 query OR로 통합)
→ custom/apisix-json-logs 파서 + crowdsecurity/nginx-logs (NPM 호환)
```
> [!note] anomaly-detect 분기 제거 (2026-04-25)
> 기존 `+ anomaly-detect (5분 폴링, AI 분석)` 분기는 폐기. [[../platform/anomaly-detect|anomaly-detect]] 인스턴스 완전 제거. [[../../history/2026-04-25-anomaly-detect-removal|history]]
| 항목 | 값 |
|------|-----|
| VictoriaLogs | `vl.inouter.com` (K3s logging ns, Traefik IngressRoute) |
| 통합 acquisition | `/etc/crowdsec/acquis.d/victorialogs-nginx.yaml` (`source: victorialogs`, query: `(program:apisix AND log_type:access) OR program:traefik OR (program:npm AND log_type:access)`, `labels: type: nginx`) |
| 이전 분리 파일 | `victorialogs-apisix.yaml.bak`, `victorialogs-traefik.yaml.bak`, `victorialogs-npm-netbis.yaml.bak` (롤백용 보존) |
| 서울 Vector | K3s DaemonSet (Helm `vector/vector`), `parse_apisix` / `parse_traefik` transform → `vlogs` ES sink |
| 오사카 Vector | Docker `timberio/vector:0.45.0-debian`, `parse_apisix``vlogs`. `location: osaka` 필드 추가 |
| Netbis NPM 경로 | NPM 호스트 Vector → zlambda vector-relay → VL `npm-netbis` 인덱스. nginx-logs parser가 NPM proxy format 호환 (cscli explain 검증) |
| 파서 | `crowdsecurity/nginx-logs` (Vector가 표준 nginx combined로 변환). NPM은 raw proxy format도 grok으로 핵심 필드 추출 정상 |
### APISIX → log-collector → CrowdSec (sandbox-tokyo)
```
sandbox-tokyo APISIX http-logger
→ log-collector (jp1 crowdsec 컨테이너, :8087)
→ SQLite (/var/lib/log-collector/requests.db)
→ CrowdSec HTTP acquisition (:8085/apisix-logs) 포워딩
```
| 항목 | 값 |
|------|-----|
| log-collector | Go HTTP 서버, `/usr/local/bin/log-collector` (jp1 crowdsec 컨테이너) |
| 소스 | jp1 `~/log-collector/main.go` |
| 수신 포트 | 8087 |
| 인증 | `Authorization: apisix-crowdsec-log-2024` |
| SQLite | `/var/lib/log-collector/requests.db` |
| 기능 | APISIX 로그 수신 → SQLite 저장 → CrowdSec 포워딩, 타임스탬프 밀리초 보정 |
### Netbis NPM → zlambda → VictoriaLogs (6대 오리진, 2026-04-23 수집 / 2026-04-25 CrowdSec 연동)
```
NPM-1..6 (Linode Tokyo public) Vector 0.55 file→http
→ zlambda(Tailscale+public) Vector-relay 0.45 http_server→elasticsearch
→ vl.inouter.com (index `npm-netbis`)
→ CrowdSec victorialogs acquisition (위 통합 파일에 query OR로 합류, 2026-04-25 연동)
→ nginx-logs parser → 시나리오 매칭 → LAPI decision (origin: crowdsec)
```
Netbis 오리진(NPM) nginx access/error 로그 수집. VL 은 LAN-only(192.168.9.53) 이므로 public NPM이 도달할 수 없어 zlambda 를 HTTP 중계로 투입. 상세: [[../../services/netbis#로그-수집-vector-→-zlambda-→-victorialogs|netbis]], [[../../history/2026-04-23-netbis-npm-vl-collection|history]]
**2026-04-25 CrowdSec 연동 완료.** 통합 acquisition `/etc/crowdsec/acquis.d/victorialogs-nginx.yaml` 의 query에 `(program:npm AND log_type:access)` 추가. **단**: 초기 `cscli explain` 결과로 "nginx-logs parser가 NPM proxy format도 lenient 호환"이라 적었던 부분은 잘못된 검증이었음 — 실제로는 `child-crowdsec/nginx-logs` unparsed 84%로 grok 미매칭. 정정 사항은 아래 `_msg` 재합성 서브섹션 참조.
#### `_msg` 재합성 (proxy_v2 → nginx combined, 2026-04-25)
CrowdSec `crowdsecurity/nginx-logs` Hub 파서는 표준 `NGINXACCESS` grok 만 매칭한다. NPM 의 `proxy_v2` raw 포맷(`[Client x] [Real y] ...`)은 grok 미지원이라 `child-crowdsec/nginx-logs` unparsed 84% 발생. **해결**: NPM Vector remap에 `_msg` 재작성 블록 추가 — 이미 추출된 메타필드(client_ip/method/path/status/bytes/user_agent/referer/log_time)로 표준 nginx combined 포맷을 합성해 `.message` 에 덮어쓰기. 원본은 `.original_message` 에 보존. 검증: `cscli explain --type nginx` 결과 `s01-parse: crowdsecurity/nginx-logs (+23 ~2)` 통과 + 시나리오(`crowdsecurity/http-crawl-non_statics`, `custom/apisix-high-rate-per-ip`, `custom/apisix-single-path-flood`) 매칭 확인. 상세: [[../../history/2026-04-25-netbis-npm-vector-msg-rewrite|history]]
`_msg` 가 이제 표준 nginx combined 포맷이라 hub 파서 그대로 동작 — 통합 acquisition의 `(program:npm AND log_type:access)` 분기에서 LAPI decision까지 자연스럽게 흐름.
### SafeLine → CrowdSec (실시간, PG LISTEN/NOTIFY)
```
SafeLine WAF 차단 → mgt_detect_log_basic INSERT
→ PG 트리거 (notify_detect_log) → pg_notify('safeline_detect', JSON)
→ safeline-listener (kr2, Go) → detail 테이블 enrichment (x-real-ip, user-agent)
→ CrowdSec HTTP acquisition (:8088/safeline-logs)
→ custom/safeline-http-logs 파서
→ custom/safeline-waf-blocked 시나리오 (trigger, 즉시 밴)
```
| 항목 | 값 |
|------|-----|
| safeline-listener | Go, `/usr/local/bin/safeline-listener` (kr2) |
| 소스 | kr2 `~/safeline-listener/main.go` |
| systemd | `safeline-listener.service` (kr2) |
| PG DSN | SafeLine CE DB (10.43.8.243:5432, safeline-ce) |
| CrowdSec 포트 | 8088 |
| 인증 | `Authorization: safeline-crowdsec-2026` |
| acquis 설정 | `/etc/crowdsec/acquis.d/safeline-http.yaml` |
| 파서 | `custom/safeline-http-logs` (`/etc/crowdsec/parsers/s01-parse/safeline-http-logs.yaml`) |
| 시나리오 | `custom/safeline-waf-blocked` (trigger 타입, 즉시 밴) |
| enrichment | req_header에서 `x-real-ip`, `user-agent` 추출, detail 테이블에서 method/payload 조회 |
PG 트리거 (SafeLine CE DB에 설치):
```sql
CREATE OR REPLACE FUNCTION notify_detect_log() RETURNS trigger AS $$
BEGIN
PERFORM pg_notify('safeline_detect', json_build_object(
'id', NEW.id, 'ts', NEW.created_at,
'src_ip', NEW.src_ip, 'host', NEW.host,
'url_path', NEW.url_path, 'attack_type', NEW.attack_type
)::text);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_detect_log
AFTER INSERT ON mgt_detect_log_basic
FOR EACH ROW EXECUTE FUNCTION notify_detect_log();
```
### ~~ddos-detect (AI 행위 분석)~~ — 폐기 (2026-04-08)
> [!warning] 폐기됨
> 2026-04-08 분석기 서비스 정지 + 삭제. "이 방식으로는 안 될 것 같다"는 판단 (60초 폴링 + Claude CLI 동기 호출 구조의 한계). 새로운 AI 분석 아키텍처는 [[victorialogs|VictoriaLogs]] 기반으로 재설계 예정.
>
> **제거된 항목**: `ddos-detect.service` (systemd), `/var/lib/log-collector/ddos-detect/` Go 바이너리 + 소스, `ddos-detect.sh`, `extract_behavior.py`, `ddos-logs/` (분석 결과 markdown). `requests.db`는 별개의 `log-collector` 데몬이 사용 중이라 보존. Gitea repo `kaffa/ddos-detect`는 보존 (코드 reference).
이전 동작 (참고):
| 항목 | 값 |
|------|-----|
| 위치 | jp1 crowdsec 컨테이너 `/var/lib/log-collector/ddos-detect/` |
| Gitea | `gitea.inouter.com/kaffa/ddos-detect` |
| 기능 | SQLite 폴링 → IP별 행위 패턴 추출 → Claude AI 분석 → cscli 자동 밴 |
| 설정 | `config.yaml` (poll 60s, Claude sonnet, ban 4h, max_workers 3) |
### 리얼 IP 처리
| 경로 | 설정 | source IP 추출 |
|------|------|---------------|
| BunnyCDN → HAProxy → **Traefik** | `forwardedHeaders.trustedIPs: 127/8, 10/8, 172.16/12, 192.168/16` + `externalTrafficPolicy: Local` | `ClientHost` (X-Forwarded-For에서 추출) |
| BunnyCDN → HAProxy → **APISIX** | `real_ip_header: X-Forwarded-For` + `real_ip_from: 127/8, 10.42/16, 10.43/16, 192.168.9/24, 100.64/10` | `client_ip` (nginx real_ip 모듈) |
Traefik values: `~/k8s/traefik/values.yaml`
## 시나리오
| 유형 | 시나리오 | 출처 |
|------|---------|------|
| HTTP | http-probing, http-crawl-non_statics, http-sensitive-files, http-backdoors-attempts, http-sqli-probing, http-xss-probing 등 | Hub |
| SafeLine | custom/safeline-waf-blocked (trigger), custom/safeline-waf-repeated (3+/5min) | 로컬 |
| **APISIX** (2026-04-08 신규) | custom/apisix-high-rate-per-ip, custom/apisix-499-burst, custom/apisix-single-path-flood, custom/apisix-5xx-burst | 로컬 |
| CAPI | http:dos, http:scan, ssh:bruteforce (커뮤니티) | 자동 |
### APISIX 시나리오 (4종, 2026-04-08 작성)
ddos-detect AI 분석기 폐기 후 deterministic 패턴 매칭으로 대체. 모두 leaky bucket, ban 4h. **K3s 서울 APISIX 트래픽만 보고 판단** (osaka/zlambda는 http-logger 송신 안 함).
| 시나리오 | 매개변수 | 의도 | filter |
|---|---|---|---|
| `apisix-high-rate-per-ip` | capacity 1000, leakspeed 100ms (≈ sustained 10 req/s, burst 1000) | 일반 HTTP flood. APISIX 자체 limit-req(20 req/s)에 안 걸리는 sustained 패턴 | `log_type=http_access-log`, groupby `source_ip` |
| `apisix-499-burst` | capacity 30, leakspeed 2s | connection exhaustion / Slowloris (클라이언트 강제 끊김) | `http_status == "499"` |
| `apisix-single-path-flood` | capacity 50, leakspeed 600ms | 동일 path 반복 (CAPTCHA 우회, login bf, 동일 자원 폭격) | groupby `source_ip + ":" + http_path` |
| `apisix-5xx-burst` | capacity 20, leakspeed 3s | 백엔드 부하 유발성 공격, 취약점 스캔 | `http_status startsWith "5"` |
⚠️ **운영 주의**:
- `5xx-burst`: 백엔드 장애 시 false positive 가능. 운영 알람과 분리해서 검토.
- `499-burst`: 모바일 클라이언트 끊김으로 정상 발생할 수 있어 30 임계값을 며칠 관찰 후 조정 권장.
- `high-rate-per-ip`: APISIX `limit-req` 글로벌 룰(20 req/s burst 10)을 통과한 트래픽만 도달 → 1차 통과 후 sustained pattern을 잡는 2차 layer.
- 현재 모두 `remediation: true` (즉시 ban). dry-run으로 시작 안 함 — false positive 발생 시 임계값 또는 ban duration 조정.
### 분산 DDoS 시나리오 (Hub, 도입 검토)
per-IP 시나리오는 분산(여러 IP 합산) 패턴을 못 잡음. CrowdSec Hub에서 제공하는 공식 시나리오로 보완 가능.
| 시나리오 | groupby | distinct | capacity | leakspeed | 의도 |
|---|---|---|---|---|---|
| `crowdsecurity/http-ddos-by-ASN` | `evt.Meta.ASNNumber` | `evt.Meta.source_ip` | 20 | 10s | 동일 ASN에서 distinct IP 20개가 10초 안에 도달 |
| `crowdsecurity/http-ddos-by-cn` | `evt.Meta.IsoCode` | `evt.Meta.source_ip` | 50 | 30s | 동일 국가에서 distinct IP 50개가 30초 안에 도달 |
공식 필터에 false positive 회피책이 미리 깔려 있음:
- `http_status == '200'` → 백엔드 5xx 장애 시 자폭 차단
- `static_ressource == 'false'` → CDN miss/이미지 burst 제외
- `distinct: source_ip` → 단일 IP 반복이 아닌 IP 다양성으로 측정
CrowdSec 공식 가이드 명시 사항 ([blog](https://www.crowdsec.net/blog/mitigate-ddos-with-crowdsec)):
> "banning these given countries will mean false positives and collateral damage."
**분산 시나리오는 ban이 아니라 `captcha` decision으로 emit하라는 게 공식 권장.** Hub 페이지에도 dos 계열은 "proper testing is advised" 경고가 직접 표기됨 (예: `http-dos-bypass-cache`).
**도입 전 점검 필요 (2026-04-25 현재 상태)**:
- profile.yaml은 ban only — captcha decision을 emit하려면 분기 추가 필수 (아래 [#알림-notification] 참조).
- bouncer 측 액션 매핑은 이미 captcha 쪽이라 ban으로 emit해도 사용자 체감은 캡챠와 동일 (아래 bouncer 절 표 참조). 하지만 의미 일관성을 위해 profile에 captcha 분기 도입 권장.
과거 인시던트 및 변경 이력은 `history/` 참조. 예: `history/2026-04-10-edge-cleanup.md` (cf-audit-cleanup-2 3-incident chain, Turnstile sitekey 교체, 미들웨어 64811 `/__captcha/verify` 버그 수정 등), [[2026-04-15-apisix-http-logger-removal|2026-04-15 APISIX http-logger 레거시 제거]].
### 발견 사항: K3s APISIX 글로벌 limit-req
```json
{
"key_type": "var",
"key": "remote_addr",
"rate": 20,
"burst": 10,
"rejected_code": 429
}
```
`/apisix/global_rules/limit-req` (etcd). 모든 라우트에 적용. CrowdSec 시나리오는 이 1차 차단을 통과한 트래픽만 본다는 점을 고려해서 임계값 설계.
## Bouncer
> [!note] Bouncer 단일화 (2026-04-26)
> CrowdSec bouncer를 `netbis-cf-firewall` 단일로 통합. 아래 ~~strikethrough~~ 항목 3종 (cs-cf-worker-bouncer / apisix-waf-bouncer / bunny-cdn-bouncer) 모두 폐기. 사유: 운영 단순화 + 비용 구조 비효율 + Worker bouncer Turnstile 위젯 168h rotation 부담. SafeLine WAF / APISIX limit-req / netbis-cf-firewall 보호는 그대로 유지.
>
> **사라진 enforce 영역**: kappa zone(keepanker.cv, actions.it.com, ironclad.it.com, servidor.it.com) CrowdSec ban / BunnyCDN 풀존(iron-jp, iron-kr) 엣지 차단 / APISIX 인스턴스 chaitin-waf 보완용 IP ban.
>
> 상세: [[../../history/2026-04-26-bouncer-consolidation|history]]
### ~~cs-cf-worker-bouncer (Cloudflare Worker)~~ — 폐기 (2026-04-26)
[[../../history/2026-04-26-bouncer-consolidation|history]] 참조. jp1 incus `cs-cf-worker-bouncer` 컨테이너 + 설정 파일 + LAPI 등록 모두 제거. CF Worker 스크립트 / KV namespace / Turnstile 위젯 4개 (`crowdsec-cloudflare-worker-bouncer-widget`)는 Syn에 위임 정리.
> **Netbis CF 바운서 (2026-04-23 폐기 → 2026-04-25 Firewall Rule 단독 재구축)**
>
> 2026-04-23: Worker + Firewall Rule 두 종류 모두 폐기 (Worker/KV 비용 + CF IP List 10k 한도). [[../../history/2026-04-23-netbis-bouncer-removal|history]]
>
> 2026-04-25: **`netbis-cf-firewall` 만 재구축**. 폐기 사유였던 10k 한도는 **origin filter `[crowdsec, cscli]`** 적용으로 회피 (CAPI/lists 30k+ 무시, 로컬 시나리오 발동만 푸시). Worker bouncer는 트래픽 비례 비용 구조라 origin filter로 회피 불가, **재구축 안 함**.
>
> 자세한 배경 및 구성: [[../../history/2026-04-25-netbis-cf-firewall-rebuild|history]], [[../../services/netbis#cloudflare-firewall-bouncer-재구축-2026-04-25|netbis 정본]]
### netbis-cf-firewall (Cloudflare Firewall Rule Bouncer, 재구축 2026-04-25)
| 항목 | 값 |
|------|-----|
| 컨테이너 | jp1 incus `crowdsec` (LAPI와 같은 컨테이너에 동거) |
| 패키지 | `crowdsec-cloudflare-bouncer 0.3.0` (apt) |
| 동작 | LAPI 폴링(10s) → CF Account IP List + Zone Firewall Rule 갱신 |
| LAPI bouncer 이름 | `cs-cloudflare-bouncer-1777082222` (자동 생성, rename 보류) |
| **origin filter** | `only_include_decisions_from: [crowdsec, cscli]` (CAPI/lists 30k+ 무시 → CF List 10k 한도 회피) |
| **액션 매핑** | `default_action: managed_challenge` (모든 6 zone 동일). LAPI decision type 무관하게 CF managed_challenge 액션으로 적용 → 사용자는 CF 캡챠 페이지 통과 후 진입. 분산 DDoS 시나리오 오탐 시에도 ban이 아니라 캡챠 |
| CF 리소스 | IP List `crowdsec_managed_challenge` (`f728ad9d4653467396d32466902c9e52`), 6 zone × Firewall Rule (managed_challenge 액션) 자동 생성 |
| 적용 zone | fall-vip / fall-mvp / fall-vip7 / psd777 / rss-555 / rss-7790 (Account ID `8fcf3c7876332aba33e974cbbfdad951`) |
| API token | Vault `secret/cloud/cloudflare-netbis` (`firewall_bouncer_token`, `firewall_bouncer_token_id`). 권한: Account Firewall Access Rules Write + Account Rule Lists Write + Zone Firewall Services Write |
| 인증 방식 | Bearer (`Authorization: Bearer cfut_...`). global_api_key는 `Invalid request headers (6003)`**사용 불가** — 신규 token 발급 필수 |
| config | `/etc/crowdsec/bouncers/crowdsec-cloudflare-bouncer.yaml` |
| systemd | `crowdsec-cloudflare-bouncer.service` |
| 비용 | $0 (Free plan 영역, IP List + Firewall Rule). Worker bouncer 대비 비용 구조 우월 |
| 한계 | CF 공식 deprecation 표기 ("isn't actively supported anymore"). 동작은 정상이나 향후 호환성 변화 가능 |
cs-cf-worker-bouncer (kappa 계정용)는 별도 컨테이너에서 그대로 운영. netbis는 비용 구조 차이로 firewall bouncer만 사용.
### ~~bunny-cdn-bouncer (BunnyCDN Edge Script)~~ — 폐기 (2026-04-26)
[[../../history/2026-04-26-bouncer-consolidation|history]] 참조. jp1 `infra-tool` 컨테이너 cron + `/opt/crowdsec-bouncer/bouncer.py` 제거. LAPI 등록 삭제. BunnyCDN Edge Script 64811 (`crowdsec-bouncer-middleware`) 두 풀존(iron-jp 5555247 / iron-kr 5555227) middleware 해제 + 스크립트 삭제는 Syn 위임. Vault `secret/infra/crowdsec-bunny-bouncer` 시크릿 보존(별도 폐기 결정).
### ~~apisix-waf-bouncer (APISIX plugin)~~ — 폐기 (2026-04-26)
`apisix-plugin-crowdsec-bouncer 0.1` — APISIX 인스턴스(가능성: apisix-osaka)의 chaitin-waf 보완용 IP ban 플러그인. LAPI 등록 삭제 완료. APISIX plugin 비활성화 + plugin_metadata/route 정리는 Syn 위임. K3s 서울 / zlambda APISIX 는 미사용 확인 완료. 상세: [[../../history/2026-04-26-bouncer-consolidation|history]]
## 보안 구조 (2026-04-26 bouncer 단일화 이후)
```
클라이언트 → BunnyCDN WAF (OWASP CRS, 1차) — 일반 도메인 경로
→ Traefik / APISIX + SafeLine WAF (2차)
→ CrowdSec 로그 분석 (3차) → netbis-cf-firewall (Netbis 6 zone CF Firewall Rule 피드백 루프)
netbis 전용 enforce: CF Account IP List + 6 zone Firewall Rule managed_challenge (netbis-cf-firewall)
일반 zone enforce 사라짐 — kappa zone CrowdSec ban 미적용. BunnyCDN Edge bouncer / Worker bouncer / APISIX bouncer 모두 폐기 (2026-04-26)
```
### 1차: BunnyCDN WAF (OWASP CRS)
- CDN 에지에서 오리진 도달 전 차단
- 차단: SQLi, XSS, CMDi, SSRF, Shellshock, Log4j
- 비활성화: DATA LEAKAGES SQL (id=911) — NocoDB 오탐 방지
- 통과: Request Smuggling, NoSQLi, 경로 스캔
### 2차: SafeLine WAF (chaitin-waf)
- APISIX plugin_metadata → detector `10.43.253.244:8000`
- SafeLine v9.3.2, K3s safeline ns
- 관리: safeline.inouter.com, API 헤더 `X-SLCE-API-TOKEN`
- 토큰: Vault `secret/infra/safeline`
- tengine 미사용, APISIX 직접 연동
### 3차: CrowdSec 로그 분석
- Traefik / APISIX 로그: Vector → VictoriaLogs(`vl.inouter.com`) → CrowdSec `victorialogs` acquisition (tail 모드)
- SafeLine 차단: PG NOTIFY → safeline-listener → CrowdSec HTTP acquisition(`:8088`)
- sandbox-tokyo APISIX: http-logger → log-collector(`:8087`) → CrowdSec
- HTTP 시나리오 매칭 → decision → bouncer 피드백
## iron-kr-waf BunnyCDN Pull Zone (구 waf-kr)
APISIX 외부 인입 경로 (juiceshop SafeLine 통합 테스트 용도). APISIX 자체는 독립 LoadBalancer gateway (MetalLB 192.168.9.50) 이지 "SafeLine 전용" 이 아님 — 현재 1 route 가 SafeLine 용일 뿐.
| 항목 | 값 |
|------|-----|
| Zone ID | 5555224 |
| Name | iron-kr-waf |
| Origin | https://220.120.65.245:9443 |
| 경로 | 인터넷 → BunnyCDN(iron-kr-waf) → OpenWrt HAProxy(:9443) → MetalLB 192.168.9.50:443 → APISIX(K3s 서울) → chaitin-waf → SafeLine detector → K3s svc |
등록 호스트: `iron-kr-waf.b-cdn.net`, `juiceshop.keepanker.cv` (WAF 테스트용). ApisixRoute `juiceshop` (chaitin-waf block) → juiceshop:3000.
> 옛 메모의 `waf-kr (5554681)` 는 더 이상 존재하지 않음 — `iron-kr-waf (5555224)` 로 통합·재명명됨.
## 화이트리스트
| 파서 | 대역 | 이유 |
|------|------|------|
| `crowdsecurity/whitelists` (Hub) | 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12, ::1, 127.0.0.0/8 | RFC1918 사설 IP |
| `custom/tailscale-whitelist` (로컬) | 100.64.0.0/10 | Tailscale CGNAT |
2026-04-17 추가: 기존에 `crowdsecurity/whitelists` 파서가 미설치 상태여서 내부 IP(192.168.9.1 OpenWrt)가 반복 밴됨. 설치 후 해결.
## Profile (decision 발급)
`/etc/crowdsec/profiles.yaml` — 시나리오 매칭 시 LAPI에 emit할 decision type을 결정하는 단계.
```yaml
name: default_ip_remediation
filters: [Alert.Remediation == true && Alert.GetScope() == "Ip"]
decisions: [{type: ban, duration: 4h}]
notifications: [http_default]
on_success: break
---
name: default_range_remediation
filters: [Alert.Remediation == true && Alert.GetScope() == "Range"]
decisions: [{type: ban, duration: 4h}]
notifications: [http_default]
on_success: break
```
**현재 상태: ban-only**. captcha/throttle decision은 emit되지 않음. ASN/Country scope 핸들러도 없음 — 분산 DDoS 시나리오를 import하면 매칭은 되지만 어떤 profile에도 안 잡혀서 decision 자체가 발급 안 될 수 있음 (`on_success: break` 없는 fallthrough 동작 확인 필요).
분산 DDoS 시나리오 도입 시 **상단에 captcha 분기 추가 필수** (예시):
```yaml
name: distributed_ddos_captcha
filters:
- Alert.Remediation == true && Alert.GetScenario() matches "(?i)ddos-by-(asn|cn|country|range)"
decisions:
- type: captcha
duration: 1h
on_success: break
---
# 기존 default_ip_remediation, default_range_remediation
```
## 알림 (Notification)
| 항목 | 값 |
|------|-----|
| 타입 | HTTP (Discord Bot API) |
| 이름 | `http_default` |
| 채널 | Discord `#heimdall` (1488119168145555486) |
| 트리거 | remediation=true인 모든 decision (IP, Range) |
| 그룹 | 30초 대기, 5건 이상 즉시 |
| Bot 토큰 | Vault `secret/apps/discord``bot_token` |
| 설정 | `/etc/crowdsec/notifications/http.yaml` |
## 참고
- BunnyCDN WAF 차단 시 오리진에 로그 안 옴 → CrowdSec에 미수신
- OpenWrt CrowdSec firewall bouncer는 DNAT 구조라 리얼 IP 매칭 불가
- chaitin-waf 플러그인은 `plugin_attr`이 아닌 **`plugin_metadata`(etcd)**에서 detector 노드를 읽음
- Incus 컨테이너에서 `sed -i`로 설정 수정 시 파일 손상 주의 (전체 파일 push 사용)