Files
obsidian/infra/anomaly-detect.md
kappa 4eba970ea8 anomaly-detect: 신규 컴포넌트 (VictoriaLogs + ollama gemma4 + CrowdSec)
- 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)
2026-04-08 22:21:36 +09:00

6.0 KiB

title, updated, tags
title updated tags
anomaly-detect (VictoriaLogs + ollama 기반 이상 트래픽 감지) 2026-04-08 신규
security
crowdsec
victorialogs
ollama
gemma
anomaly

anomaly-detect

[[crowdsec-safeline#ddos-detect (AI 행위 분석) — 폐기 (2026-04-08)|폐기된 ddos-detect]] 후속. victorialogs에 적재된 K3s 서울 APISIX access log를 5분마다 분석하여 봇/공격성 IP를 crowdsec-safeline에 자동 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에 저장:

url: http://10.253.100.240:8080
login: anomaly-detect
password: <vault: secret/apps/anomaly-detect>

[!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 <default-machine-name> --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:

{"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에 적용

운영 명령

# 컨테이너 진입
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 listanomaly-detect 등록 확인
  • curl https://vl.inouter.com/select/logsql/query?query=program:apisix\&limit=1 200 OK (컨테이너 내부)
  • curl http://100.87.221.126:11434/api/tagsgemma4: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)으로 비용/지연 동시 개선.