แผนยกระดับจาก demo (stdlib HTTP + in-memory) ไปสู่ระบบ Security Log RAG + Automation ระดับ production
ต้องมี Python 3.10+ — local mode ใช้ stdlib ล้วน ไม่ต้องลง dependency
# Local mode (keyword RAG + local fallback) — ใช้ได้ทันที
python3 server.py # เปิด http://127.0.0.1:8765
# เปิด LLM summary ผ่าน OpenRouter
export OPENROUTER_API_KEY="your_key"
python3 server.py
# Vector RAG จริง (build index ก่อน)
python3 build_vector_index.py --limit 10000 --batch-size 32
python3 server.py
# รันกับชุด 50k
LOGRAG_DATA_FILE=Data/files/security_logs_extended_50k.jsonl python3 server.py
เข้าใจข้อจำกัดก่อน เพื่อรู้ว่าต้องเปลี่ยนอะไร
| ด้าน | ของเดิม (demo) | ปัญหาตอนขึ้นจริง |
|---|---|---|
| Web server | http.server + ThreadingHTTPServer | ไม่มี async/connection pool, ไม่เหมาะ traffic จริง, ไม่มี auth |
| Data store | โหลด JSONL ทั้งไฟล์เข้า RAM ตอน start | ไม่ scale, restart ช้า, query ใหม่ไม่ได้, ไม่มี retention |
| Index | สร้าง TF-IDF/vector ใน memory | หายเมื่อ restart, ไม่ incremental, single process |
| Logic ซ้ำ | server.py กับ api/_lograg.py ซ้ำกันเกือบหมด | แก้ rule ต้องแก้ 2 ที่ — แหล่ง bug |
| Ingestion | ไม่มี — ใช้ไฟล์สังเคราะห์ static | ไม่มีทางรับ log จริงแบบ real-time |
| Automation | หน้าเว็บ poll /api/alerts ทุก 15 วิ | ไม่มี action จริง (block IP, ticket, notify) |
| Secrets/Auth | ไม่มี authentication เลย | เปิด public = ใครก็เรียก API ได้ |
detect_rule, query expansion ไทย/อังกฤษ, โครงสร้าง API — ทั้งหมดนี้ port ไป FastAPI ได้ตรงๆ
แยกเป็นชั้นชัดเจน: ingest → process → store → RAG/API → automation → notify
หลักการ: ทุก critical alert ที่ classifier จับได้ → ยิงเข้า n8n → enrich ด้วย threat intel → เรียก RAG API สรุปเป็นภาษาไทย → สร้าง ticket + แจ้งเตือน + (ออปชัน) block IP อัตโนมัติ
เลือกแบบ self-hostable ก่อน (ลง Docker ได้หมด) แล้วค่อยสลับไป managed service ทีหลัง
| ชั้น | เลือกใช้ | ทางเลือก | เฟส |
|---|---|---|---|
| Log collector | Vector.dev | Fluent Bit, Filebeat | NEXT |
| Message queue | Redpanda (Kafka API) | Redis Streams, NATS, Kafka | NEXT |
| API / RAG service | FastAPI + Uvicorn | Litestar, Flask | NOW |
| Task worker | ARQ / Celery + Redis | Dramatiq, RQ | NEXT |
| ชนิด | เลือกใช้ | ใช้เก็บอะไร |
|---|---|---|
| Relational | PostgreSQL 16 | alerts, incidents, users, audit, runbook, rule config |
| Vector DB | Qdrant (โค้ดเดิมรองรับอยู่แล้ว) | embeddings ของ log message สำหรับ RAG |
| Log search | OpenSearch (หรือ ClickHouse) | full-text + time-series log จำนวนมหาศาล |
| Cache / queue | Redis | rate-limit, task queue, dedup alert key |
pgvector บน PostgreSQL แทน Qdrant ได้ในเฟสแรก เหลือ DB เดียว — แต่ Qdrant scale ดีกว่าเมื่อ vector เยอะ. ClickHouse เหมาะกว่า OpenSearch ถ้าเน้น analytics/ราคาถูก
| หน้าที่ | เลือกใช้ | ทางเลือก |
|---|---|---|
| Embeddings | OpenRouter (เดิม) | OpenAI, Ollama (local: nomic-embed-text) |
| LLM สรุป | OpenRouter (เดิม) | Ollama (llama3/qwen), vLLM self-host |
| RAG framework | เขียนเอง (เบา, คุมได้) | LlamaIndex, Haystack |
n8n (self-host) — orchestrate alert → enrich → action
คงหน้า HTML เดิมได้ หรือย้ายไป Next.js + Tailwind เมื่อต้อง auth/role
Slack / Discord / Email (SMTP) / PagerDuty / LINE Notify
AbuseIPDB, VirusTotal, GreyNoise, MaxMind GeoIP
Prometheus + Grafana + Loki
Traefik / Caddy + OAuth2-proxy หรือ Keycloak
Doppler / Infisical / .env + SOPS
Docker Compose → Kubernetes, GitHub Actions
เส้นทางของ log ตั้งแต่เข้าระบบจนเกิด action
logs.rawlogs.raw → รัน detect_rule() (port มาจากของเดิม) → ใส่ category/severity/MITRE → เขียนลง PostgreSQL + OpenSearchalert_key (logic เดิม) → publish เข้า topic alerts.critical + insert ตาราง alerts/api/ask: retrieve จาก Qdrant (vector) + OpenSearch (keyword) → hybrid rank → ส่ง context ให้ LLM → ตอบภาษาไทยlogs.raw ──> [classify worker] ──> PostgreSQL (structured + alerts)
│ └─> OpenSearch (full-text search)
└─> [embed worker] ──> Qdrant (vector RAG)
severity=critical ──> alerts.critical (queue) ──> n8n webhook ──> enrich+RAG+action
User/Frontend ──> FastAPI /api/ask ──> hybrid retrieve (Qdrant + OpenSearch)
└─> LLM (OpenRouter/Ollama) ──> Thai answer
หัวใจของ automation — แต่ละ workflow คือ DAG ของ node
alerts.critical (หรือ Schedule poll /api/alerts?since=)src_ip, MaxMind GeoIP หาประเทศ/api/ask ด้วย context_rows ของ alert → ได้สรุปไทย + runbookpriv_esc/data_exfil = แจ้ง on-call ทันที (PagerDuty); อื่นๆ = ticket ปกติSchedule (เช้า 9 โมง) → query /api/stats + top IPs → RAG สรุปภาพรวม 24 ชม. → ส่งรายงานเข้า Slack channel
เมื่อ analyst กด "confirm threat" บน dashboard → webhook → n8n update firewall rule + บันทึก audit ลง PostgreSQL + (ออปชัน) ใส่ IP เข้า blocklist ที่ feed กลับ collector
โครงเริ่มต้น — เก็บ structured log, alert grouping, audit trail
-- log ที่ผ่าน classify แล้ว (hot 30-90 วัน, retention policy แยก)
CREATE TABLE log_events (
id BIGSERIAL PRIMARY KEY,
ts TIMESTAMPTZ NOT NULL,
source TEXT NOT NULL, -- auth/nginx/firewall/system
category TEXT NOT NULL, -- brute_force, c2_beacon, ...
severity TEXT NOT NULL, -- info/medium/high/critical
mitre TEXT,
risk_score SMALLINT,
src_ip INET,
dst_ip INET,
"user" TEXT,
message TEXT NOT NULL,
raw JSONB NOT NULL, -- เก็บ field ดิบทั้งหมด
embedded BOOLEAN DEFAULT FALSE -- ส่งเข้า Qdrant แล้วหรือยัง
);
CREATE INDEX ON log_events (ts DESC);
CREATE INDEX ON log_events (category, severity);
CREATE INDEX ON log_events (src_ip);
-- alert ที่ group แล้ว (จาก alert_key เดิม)
CREATE TABLE alerts (
id TEXT PRIMARY KEY, -- ALT-xxxxxx
alert_key TEXT NOT NULL,
title TEXT NOT NULL,
category TEXT NOT NULL,
severity TEXT NOT NULL DEFAULT 'critical',
mitre TEXT,
status TEXT NOT NULL DEFAULT 'new', -- new/ack/resolved/false_positive
event_count INT NOT NULL,
first_seen TIMESTAMPTZ NOT NULL,
last_seen TIMESTAMPTZ NOT NULL,
src_ip INET, dst_ip INET, "user" TEXT,
rag_summary TEXT, -- คำตอบจาก LLM
enrichment JSONB, -- ผล GeoIP/threat intel
assigned_to TEXT,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
-- audit ทุก action (โดยเฉพาะ auto-block)
CREATE TABLE audit_log (
id BIGSERIAL PRIMARY KEY,
ts TIMESTAMPTZ DEFAULT now(),
actor TEXT NOT NULL, -- user / n8n / system
action TEXT NOT NULL, -- block_ip / create_ticket / ack
target TEXT,
alert_id TEXT REFERENCES alerts(id),
detail JSONB
);
-- rule config (ย้าย CATEGORY_META ออกจาก hardcode มาเป็น data)
CREATE TABLE rule_config (
category TEXT PRIMARY KEY,
label TEXT, mitre TEXT, risk SMALLINT, runbook TEXT,
enabled BOOLEAN DEFAULT TRUE
);
CATEGORY_META จาก hardcode ใน Python มาเป็นตาราง rule_config → แก้ runbook/risk ได้โดยไม่ต้อง deploy ใหม่ และเลิกปัญหา logic ซ้ำ 2 ไฟล์
ไฟล์อ้างอิง — ปรับ version/secret ตอนทำจริง
FROM python:3.12-slim
WORKDIR /app
ENV PYTHONUNBUFFERED=1 PIP_NO_CACHE_DIR=1
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 8000
# ใช้ uvicorn worker จริง (อย่าใช้ http.server เดิม)
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
services:
api:
build: .
ports: ["8000:8000"]
env_file: .env
depends_on: [postgres, qdrant, redis]
worker: # classify + embed consumer
build: .
command: python -m app.worker
env_file: .env
depends_on: [postgres, qdrant, redis, redpanda]
postgres:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: ${PG_PASSWORD}
POSTGRES_DB: roglag
volumes: ["pgdata:/var/lib/postgresql/data"]
ports: ["5432:5432"]
qdrant:
image: qdrant/qdrant:latest
volumes: ["qdrant:/qdrant/storage"]
ports: ["6333:6333"]
redis:
image: redis:7-alpine
ports: ["6379:6379"]
redpanda: # Kafka-compatible, เบากว่า Kafka
image: redpandadata/redpanda:latest
command: redpanda start --smp 1 --overprovisioned --node-id 0
ports: ["9092:9092"]
n8n:
image: n8nio/n8n:latest
environment:
N8N_HOST: localhost
WEBHOOK_URL: http://n8n:5678/
volumes: ["n8n:/home/node/.n8n"]
ports: ["5678:5678"]
vector: # log collector
image: timberio/vector:latest-alpine
volumes: ["./vector.toml:/etc/vector/vector.toml:ro"]
depends_on: [redpanda]
volumes:
pgdata: {}
qdrant: {}
n8n: {}
ทำเป็นเฟส — แต่ละเฟสรันได้จริงก่อนไปต่อ
server.py + api/_lograg.py เป็น package เดียว ลบ logic ซ้ำ