From 4eba970ea894fd6631c881b69c32f6873ec07cae Mon Sep 17 00:00:00 2001 From: kappa Date: Wed, 8 Apr 2026 22:21:36 +0900 Subject: [PATCH] =?UTF-8?q?anomaly-detect:=20=EC=8B=A0=EA=B7=9C=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20(VictoriaLogs=20+=20olla?= =?UTF-8?q?ma=20gemma4=20+=20CrowdSec)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - incus-hp2의 anomaly-detect 컨테이너에 5분 주기 systemd timer로 가동 - 흐름: vlogs LogsQL → 통계 게이트 → ollama gemma4:e4b (kaffa-macmini) → CrowdSec LAPI alert POST → profiles.yaml 자동 ban - ddos-detect 폐기 후속작 (60s 폴링 + Claude CLI 동기 호출 → 5분 + 로컬 LLM 게이트로 개선) - LAPI machine 등록 시 cscli machines add --auto가 local_api_credentials.yaml 덮어쓰는 함정 기록 - end-to-end smoke test 검증 완료 (alert POST 201 + decision 등록 + cleanup) --- infra/anomaly-detect.md | 132 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 infra/anomaly-detect.md diff --git a/infra/anomaly-detect.md b/infra/anomaly-detect.md new file mode 100644 index 0000000..451e30c --- /dev/null +++ b/infra/anomaly-detect.md @@ -0,0 +1,132 @@ +--- +title: anomaly-detect (VictoriaLogs + ollama 기반 이상 트래픽 감지) +updated: 2026-04-08 신규 +tags: [security, crowdsec, victorialogs, ollama, gemma, anomaly] +--- + +# anomaly-detect + +[[crowdsec-safeline#~~ddos-detect (AI 행위 분석)~~ — 폐기 (2026-04-08)|폐기된 ddos-detect]] 후속. [[victorialogs|VictoriaLogs]]에 적재된 K3s 서울 APISIX access log를 5분마다 분석하여 봇/공격성 IP를 [[crowdsec-safeline|CrowdSec]]에 자동 ban으로 등록한다. + +## 위치 / 사양 + +| 항목 | 값 | +|------|-----| +| 호스트 | incus-hp2 | +| 컨테이너 | `anomaly-detect` (default 프로젝트, Debian 13 trixie) | +| IP | 10.100.2.164 | +| 사양 | 1 vCPU, 512MB RAM, 5GB | +| 설치 경로 | `/opt/anomaly-detect/{venv,analyzer.py}`, `/etc/anomaly-detect/lapi.yaml`, `/var/lib/anomaly-detect/dedup.json` | +| systemd | `anomaly-detect.service` (oneshot) + `anomaly-detect.timer` (`OnCalendar=*:0/5`, `Persistent=true`, `RandomizedDelaySec=20`) | + +## 데이터 흐름 + +``` +[5분 주기 systemd timer] + ↓ +analyzer.py + ├─ 1) https://vl.inouter.com — LogsQL: program:apisix log_type:access 지난 5분 + ├─ 2) per-IP 통계 게이트 (count/4xx/5xx/499/distinct paths) + ├─ 3) 후보 N개 (default max 5) + ├─ 4) 각 후보 → http://100.87.221.126:11434/api/generate (kaffa-macmini ollama) + │ 모델: gemma4:e4b (Q4_K_M, 8.0B), format=json + ├─ 5) verdict=yes → CrowdSec LAPI alert POST + │ http://10.253.100.240:8080/v1/alerts + │ profiles.yaml의 default_ip_remediation이 자동 ban 생성 + └─ 6) dedup.json에 처리 IP + 타임스탬프 기록 (24h 내 재처리 안 함) +``` + +## CrowdSec LAPI 등록 + +`anomaly-detect`라는 watcher machine을 jp1 crowdsec에 등록하고, credentials를 컨테이너 안 `/etc/anomaly-detect/lapi.yaml`에 저장: + +```yaml +url: http://10.253.100.240:8080 +login: anomaly-detect +password: +``` + +> [!warning] cscli machines add 함정 +> `cscli machines add NAME --auto`는 default로 `/etc/crowdsec/local_api_credentials.yaml`을 덮어씀 — 이건 jp1 crowdsec **daemon 자체의 LAPI 클라이언트 설정**이라 덮어쓰면 daemon이 새 password로 LAPI 인증을 시도하면서 동기화가 깨짐. 반드시 `--file <별도 경로>` 옵션을 줘야 한다. 만약 실수로 덮어썼다면 `cscli machines add --auto --force --file /etc/crowdsec/local_api_credentials.yaml`로 default machine 새 credentials 발급 후 `systemctl reload crowdsec`로 복구. + +## 통계 게이트 (환경변수로 조정) + +| 변수 | default | 의미 | +|------|---------|------| +| `WINDOW_MIN` | 5 | LogsQL 윈도우 (분) | +| `GATE_MIN_REQS` | 30 | 윈도우 내 최소 요청 수 | +| `GATE_MIN_4XX_RATIO` | 0.5 | 4xx 비율 (count >= MIN_REQS와 AND) | +| `GATE_MIN_5XX_COUNT` | 10 | 5xx 최소 발생 | +| `GATE_MIN_499_COUNT` | 15 | 499 최소 발생 | +| `GATE_MIN_DISTINCT_PATH` | 20 | 서로 다른 path 최소 | +| `MAX_CANDIDATES` | 5 | 한 사이클에 ollama로 분석할 IP 상한 | +| `BAN_DURATION` | 4h | profiles.yaml이 아닌 alert 자체에서 전달하는 ban 기간 | +| `DEDUP_HOURS` | 24 | 같은 IP 재분석 방지 윈도우 | + +게이트 통과 조건 (OR): +1. count ≥ MIN_REQS AND 4xx 비율 ≥ MIN_4XX_RATIO +2. 5xx ≥ MIN_5XX_COUNT +3. 499 ≥ MIN_499_COUNT +4. distinct path ≥ MIN_DISTINCT_PATH + +사설망 IP(10/8, 127/8, 192.168/16, 172.16/12)는 자동 제외. + +## ollama prompt + +`format=json`으로 강제 + 명확한 schema: + +```json +{"verdict": "yes" | "no", "reason": "<한 문장 한국어>"} +``` + +판단 근거는 system prompt에 명시 (반복 패턴, 머신 속도, 4xx/5xx 비율, path 열거, 알려진 스캐너 UA, 로그인 brute force, 비정상 rate). 정상 브라우저 패턴은 "no"로 분류. + +## CrowdSec 자동 ban + +profile 기반: + +``` +alert (machine=anomaly-detect, source.scope=Ip, remediation=true) + → /etc/crowdsec/profiles.yaml의 default_ip_remediation에 매치 + → 자동으로 ban decision 4h 생성 + → bouncers (BunnyCDN, APISIX 서울/오사카, netbis-cf 등)가 다음 pull에 적용 +``` + +## 운영 명령 + +```bash +# 컨테이너 진입 +ssh incus-hp2 'incus exec anomaly-detect -- bash' + +# 수동 1회 실행 +incus exec anomaly-detect -- /opt/anomaly-detect/venv/bin/python /opt/anomaly-detect/analyzer.py + +# 상태 +incus exec anomaly-detect -- systemctl status anomaly-detect.timer +incus exec anomaly-detect -- journalctl -u anomaly-detect.service --since "30 minutes ago" + +# dedup 초기화 (모든 IP 재분석 허용) +incus exec anomaly-detect -- sh -c 'echo "{}" > /var/lib/anomaly-detect/dedup.json' + +# 게이트/모델/주기 변경 → /etc/systemd/system/anomaly-detect.service의 Environment= +``` + +## 검증 (최초 배포) + +- `cscli machines list` → `anomaly-detect` 등록 확인 +- `curl https://vl.inouter.com/select/logsql/query?query=program:apisix\&limit=1` 200 OK (컨테이너 내부) +- `curl http://100.87.221.126:11434/api/tags` → `gemma4:e4b` 노출 +- `curl http://10.253.100.240:8080/v1/decisions` → 403 (인증 필요, 네트워크 OK) +- 더미 IP `198.51.100.99`로 alert POST → 201 + decision 등록 → cleanup 확인 (smoke test) + +## 향후 작업 + +- [ ] 처음 1주는 dry_run 없이 자동 ban이지만 임계값 조정 필요 시 보수적으로 시작 → 모니터링 후 점진 강화 +- [ ] Discord webhook 알림 추가 (`secret/apps/discord` Vault에서 가져오기) +- [ ] gemma4:e4b 한국어 reason 품질 평가 → 모델 변경 검토 (`gemma3:12b`, `qwen2.5:7b`, `llama3.1:8b` 등) +- [ ] 게이트 통과 후 후보 0건이 며칠 지속되면 임계값 완화 +- [ ] 코드를 Gitea repo로 분리 (`gitea.inouter.com/kaffa/anomaly-detect`) + +## 폐기된 전임자 + +- [[crowdsec-safeline#~~ddos-detect (AI 행위 분석)~~ — 폐기 (2026-04-08)|ddos-detect]] (Go, jp1 crowdsec 컨테이너 안, 60s 폴링, Claude CLI sonnet 호출). 폐기 사유: 60s 폴링 + 동기 Claude CLI 구조 한계. 이번 anomaly-detect는 5분 주기 + 통계 게이트 + 로컬 LLM(ollama gemma4)으로 비용/지연 동시 개선.