14 KiB
title, updated
| title | updated |
|---|---|
| CrowdSec 및 SafeLine WAF | 2026-04-10 |
CrowdSec LAPI
| 항목 | 값 |
|---|---|
| 위치 | jp1 Incus crowdsec 컨테이너 |
| LAPI | http://10.253.100.240:8080 |
| 관리 | ssh incus-jp1 "incus exec crowdsec -- cscli ..." |
로그 수집 (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/traefik-logs (Hub, JSON 모드) |
APISIX → VictoriaLogs → CrowdSec (서울+오사카 통합)
서울 APISIX (K3s) stdout → Vector DaemonSet → VictoriaLogs (ES bulk API)
오사카 APISIX (Docker) stdout → Vector (Docker) → VictoriaLogs (ES bulk API)
→ CrowdSec victorialogs acquisition (tail 모드, 실시간)
→ custom/apisix-json-logs 파서
+ anomaly-detect (5분 폴링, AI 분석)
| 항목 | 값 |
|---|---|
| VictoriaLogs | vl.inouter.com (K3s logging ns, Traefik IngressRoute) |
| CrowdSec acquisition | /etc/crowdsec/acquis.d/victorialogs-apisix.yaml (source: victorialogs, mode: tail, query: program:apisix log_type:access) |
| 서울 Vector | K3s DaemonSet (Helm vector/vector), parse_apisix transform → vlogs ES sink |
| 오사카 Vector | Docker timberio/vector:0.45.0-debian, /etc/vector/vector.yaml, docker_logs source → parse_apisix → vlogs ES sink. location: osaka 필드 추가 |
| 파서 | custom/apisix-json-logs (로컬) |
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 포워딩, 타임스탬프 밀리초 보정 |
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에 설치):
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 기반으로 재설계 예정.
제거된 항목:
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 repokaffa/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: APISIXlimit-req글로벌 룰(20 req/s burst 10)을 통과한 트래픽만 도달 → 1차 통과 후 sustained pattern을 잡는 2차 layer.- 현재 모두
remediation: true(즉시 ban). dry-run으로 시작 안 함 — false positive 발생 시 임계값 또는 ban duration 조정.
과거 인시던트 및 변경 이력은 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.
발견 사항: K3s APISIX 글로벌 limit-req
{
"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 |
| 보호 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로:
# 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 깨짐. 반드시 전체 파일 덮어쓰기:
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 (Cloudflare Worker — Netbis 계정)
Kappa 계정용 cs-cf-worker-bouncer와 별도 컨테이너로 분리 운영. 상세: netbis
| 항목 | 값 |
|---|---|
| 위치 | jp1 Incus netbis-cf-bouncer 컨테이너 (10.253.103.33) |
| 바운서 이름 | netbis-cf |
| 설정 | /etc/crowdsec/bouncers/crowdsec-cloudflare-worker-bouncer.yaml |
| 동기화 | 10초 |
| CF 계정 | Netbis (netbis@netbis.io, Account ID 8fcf3c7876332aba33e974cbbfdad951) |
| 보호 zone | fall-vip.com, fall-mvp.com, fall-vip7.com, psd777.com, rss-555.com, rss-7790.com |
| Turnstile | 6개 zone managed 모드, 168시간 secret key 로테이션 |
| 로그 소스 | sandbox-tokyo APISIX → CrowdSec http-logger (8085/apisix-logs) |
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) → CrowdSecvictorialogsacquisition (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)로 통합·재명명됨.
참고
- BunnyCDN WAF 차단 시 오리진에 로그 안 옴 → CrowdSec에 미수신
- OpenWrt CrowdSec firewall bouncer는 DNAT 구조라 리얼 IP 매칭 불가
- chaitin-waf 플러그인은
plugin_attr이 아닌 **plugin_metadata(etcd)**에서 detector 노드를 읽음 - Incus 컨테이너에서
sed -i로 설정 수정 시 파일 손상 주의 (전체 파일 push 사용)