425 lines
24 KiB
Markdown
425 lines
24 KiB
Markdown
---
|
||
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
|
||
|
||
### cs-cf-worker-bouncer (Cloudflare Worker)
|
||
|
||
| 항목 | 값 |
|
||
|------|-----|
|
||
| 위치 | jp1 Incus `cs-cf-worker-bouncer` 컨테이너 |
|
||
| 설정 | `/etc/crowdsec/bouncers/crowdsec-cloudflare-worker-bouncer.yaml` |
|
||
| 정본 | `gitea.inouter.com/kaffa/k8s` → `configs/crowdsec/crowdsec-cloudflare-worker-bouncer.yaml` |
|
||
| 동기화 | 10초 (decision stream polling) |
|
||
| 방식 | LAPI → bouncer → Cloudflare Worker KV (bloom filter) → Worker에서 차단/captcha |
|
||
| **액션 매핑** | `default_action: captcha` (Turnstile). LAPI decision type이 ban이든 captcha든 모두 Turnstile 페이지로 처리됨 |
|
||
| 보호 zone | keepanker.cv, actions.it.com, ironclad.it.com, servidor.it.com |
|
||
| 비보호 zone | inouter.com, anvil.it.com — DNS `proxied: false` 라 CF 엣지를 거치지 않아 bouncer 라우트가 enforce 되지 않음. 보호는 BunnyCDN 미들웨어 64811 단독 |
|
||
| Turnstile 위젯 | 4개 bouncer-managed (`crowdsec-cloudflare-worker-bouncer-widget` 이름, 168h rotation). BunnyCDN 미들웨어용 `inouter-bunny-middleware` 는 별도 수동 관리 — [[cloudflare#Turnstile 위젯]] |
|
||
| yaml writer | `cfb-manager` 단일 (K8s `default/cfb-manager` REST API). 과거 `/etc/cron.d/cf-zone-sync` + `auto-add-cf-zones.sh` 자동 쓰기는 coordination 문제로 제거됨 — 신규 zone 추가는 `cfb-manager POST /domains` 또는 수동 yaml edit + systemctl restart |
|
||
|
||
#### Decision 수동 관리
|
||
|
||
bouncer의 `lapi_key`는 **읽기 전용**. decision 추가/삭제는 `crowdsec` 컨테이너에서 `cscli`로:
|
||
|
||
```bash
|
||
# ban 추가
|
||
ssh incus-jp1 "incus exec crowdsec -- cscli decisions add --ip 1.2.3.4 --duration 2m --reason 'manual ban' --type ban"
|
||
|
||
# 확인
|
||
ssh incus-jp1 "incus exec crowdsec -- cscli decisions list --ip 1.2.3.4"
|
||
|
||
# 삭제
|
||
ssh incus-jp1 "incus exec crowdsec -- cscli decisions delete --ip 1.2.3.4"
|
||
```
|
||
|
||
bouncer 컨테이너에서 `curl POST /v1/decisions`로 직접 넣으면 등록 안 됨 — API 키 권한 제한.
|
||
|
||
#### 설정 변경 시 주의
|
||
|
||
**`sed -i` 사용 금지** — Incus 컨테이너 내에서 `sed -i`로 수정 시 이전 내용이 파일 끝에 남아 YAML 깨짐. 반드시 전체 파일 덮어쓰기:
|
||
|
||
```bash
|
||
cat updated-config.yaml | ssh incus-jp1 "incus file push - cs-cf-worker-bouncer/etc/crowdsec/bouncers/crowdsec-cloudflare-worker-bouncer.yaml"
|
||
ssh incus-jp1 "incus exec cs-cf-worker-bouncer -- systemctl restart crowdsec-cloudflare-worker-bouncer"
|
||
```
|
||
|
||
> **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)
|
||
|
||
| 항목 | 값 |
|
||
|------|-----|
|
||
| 동기화 | jp1 `infra-tool` 컨테이너, `/opt/crowdsec-bouncer/bouncer.py` |
|
||
| 크론 | 3분 delta sync, 매시 full sync |
|
||
| 방식 | LAPI → Bloom filter → BunnyCDN Edge Script 임베딩 → 퍼블리시 |
|
||
| Edge Script | ID 64811 (`crowdsec-bouncer-middleware`), FNV-1a bloom filter |
|
||
| 차단 시 | Cloudflare Turnstile CAPTCHA + LibSQL verified IP 캐싱(4h) |
|
||
| 적용 풀존 | iron-jp (5555247), iron-kr (5555227) — 두 풀존 모두 `MiddlewareScriptId: 64811` |
|
||
| API 키 | Vault `secret/infra/crowdsec-bunny-bouncer` |
|
||
| 소스 | `~/crowdsec-bunny-bouncer/` |
|
||
|
||
## 보안 구조
|
||
|
||
```
|
||
클라이언트 → BunnyCDN Edge Script (CrowdSec bouncer, 0차)
|
||
→ BunnyCDN WAF (OWASP CRS, 1차)
|
||
→ Traefik / APISIX + SafeLine WAF (2차)
|
||
→ CrowdSec 로그 분석 (3차) → bouncer 피드백 루프
|
||
```
|
||
|
||
### 0차: BunnyCDN Edge Script (CrowdSec bouncer)
|
||
- Bloom filter로 악성 IP 즉시 차단
|
||
- false positive 시 Turnstile CAPTCHA로 구제
|
||
|
||
### 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 사용)
|