English | 繁體中文
惡意附件分析 Pipeline 系統
User → GitHub Pages (React) → Nginx Reverse Proxy
│
┌──────────┴──────────┐
│ │
POST /api/v1/files GET /api/v1/**
│ │
▼ ▼
Go Ingest Service FastAPI (Backend)
(chi + pgx + minio-go (SQLAlchemy + asyncpg)
+ amqp091-go) │
│ │
┌─────────┼─────────┐ │
▼ ▼ ▼ │
MinIO PostgreSQL RabbitMQ │
(檔案) (metadata) │ │
▼ │
Worker(s) ◄────────────┘
clamscan / yara CLI
│
▼
Supabase PostgreSQL
(reports)
資料流:
- 使用者透過前端上傳檔案 → Nginx 依據路由分流
- Go Ingest Service 處理檔案上傳:存入 MinIO、寫入 job metadata 至 PostgreSQL、發布任務至 RabbitMQ
- Worker(s) 從 RabbitMQ 消費任務,執行 ClamAV / YARA 掃描,將分析報告寫回資料庫
- FastAPI Backend 提供 job 狀態查詢與報告取得 API
- VirtualBox + Ubuntu Server VM(用於 k3s)
- Supabase 專案(免費方案即可)
- GitHub 帳號(用於 GHCR 和 GitHub Pages)
⚠️ VirtualBox 網路設定注意事項VirtualBox 預設使用 NAT 模式,這會導致外部無法連線到 VM(包括
http://VM_IP:30080)。解決方法: 將 VirtualBox 網路介面卡改為「Bridged Adapter(橋接介面卡)」
設定步驟:VM 設定 → 網路 → 介面卡 1 → 附加到:選擇「Bridged Adapter」
git clone https://github.com/YOUR_USERNAME/MalScanWorker.git
cd MalScanWorker- 前往 repo Settings → Pages
- Source 選擇 GitHub Actions
前往 Settings → Secrets and variables → Actions → Variables
新增以下變數:
| 變數名稱 | 值範例 |
|---|---|
API_BASE_URL |
http://YOUR_VM_IP:30080 |
# 安裝 k3s
curl -sfL https://get.k3s.io | sh -
# 驗證安裝
sudo kubectl get nodes專案已配置 GitHub Actions 自動建構並推送到 GHCR。 你只需要確保 CI/CD 通過,images 會自動建立在:
ghcr.io/YOUR_USERNAME/malscan-api:latestghcr.io/YOUR_USERNAME/malscan-worker:latestghcr.io/YOUR_USERNAME/malscan-ingest:latest
# Backend API
cd backend
docker build -t ghcr.io/YOUR_USERNAME/malscan-api:latest .
docker push ghcr.io/YOUR_USERNAME/malscan-api:latest
# Worker
cd ../worker
docker build -t ghcr.io/YOUR_USERNAME/malscan-worker:latest .
docker push ghcr.io/YOUR_USERNAME/malscan-worker:latest
# Ingest (Go)
cd ../ingest
docker build -t ghcr.io/YOUR_USERNAME/malscan-ingest:latest .
docker push ghcr.io/YOUR_USERNAME/malscan-ingest:latestsudo kubectl apply -f k8s/namespace.yamlsudo kubectl create secret generic malscan-secrets \
--namespace=malscan \
--from-literal=DATABASE_URL="postgresql://postgres:YOUR_PASSWORD@YOUR_SUPABASE_HOST:5432/postgres" \
--from-literal=MINIO_ACCESS_KEY="minioadmin" \
--from-literal=MINIO_SECRET_KEY="YOUR_MINIO_SECRET" \
--from-literal=RABBITMQ_URL="amqp://guest:guest@rabbitmq:5672/"# MinIO 和 RabbitMQ 需要持久化儲存
sudo mkdir -p /data/malscan/minio
sudo mkdir -p /data/malscan/rabbitmq
sudo chmod 777 /data/malscan/minio /data/malscan/rabbitmq編輯以下檔案,將 OWNER 替換為你的 GitHub 帳號:
k8s/api/deployment.yamlk8s/worker/deployment.yamlk8s/ingest/deployment.yaml
# 使用 sed 批次替換
sed -i 's/OWNER/YOUR_USERNAME/g' k8s/api/deployment.yaml
sed -i 's/OWNER/YOUR_USERNAME/g' k8s/worker/deployment.yaml
sed -i 's/OWNER/YOUR_USERNAME/g' k8s/ingest/deployment.yaml# 1. 建立 namespace 和 configmap
sudo kubectl apply -f k8s/namespace.yaml
sudo kubectl apply -f k8s/configmap.yaml
# 2. 建立 PersistentVolume(需要在 namespace 建立前)
sudo kubectl apply -f k8s/minio/pv.yaml
sudo kubectl apply -f k8s/rabbitmq/pv.yaml
# 3. 部署基礎服務
sudo kubectl apply -f k8s/minio/pvc.yaml
sudo kubectl apply -f k8s/minio/deployment.yaml
sudo kubectl apply -f k8s/rabbitmq/pvc.yaml
sudo kubectl apply -f k8s/rabbitmq/deployment.yaml
# 4. 部署應用服務
sudo kubectl apply -f k8s/yara-rules/
sudo kubectl apply -f k8s/ingest/
sudo kubectl apply -f k8s/api/
sudo kubectl apply -f k8s/worker/
sudo kubectl apply -f k8s/nginx/sudo kubectl get pods -n malscan
sudo kubectl get svc -n malscan| 服務 | URL |
|---|---|
| 前端 | https://YOUR_USERNAME.github.io/MalScanWorker/ |
| API | http://VM_IP:30080 |
| MinIO Console | http://VM_IP:NodePort (查 kubectl get svc) |
| RabbitMQ Management | http://VM_IP:NodePort |
⚠️ 為什麼需要這個?GitHub Pages 使用 HTTPS,而瀏覽器的安全策略(Mixed Content)會阻止從 HTTPS 頁面向 HTTP API 發送請求。 使用 Cloudflare Tunnel 可以免費為你的 VM API 提供 HTTPS 端點。
# Debian/Ubuntu
curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared.deb
# 或使用 snap
sudo snap install cloudflared# 將 30080 替換為你的 API NodePort
sudo cloudflared tunnel --url http://localhost:30080成功後會顯示類似以下的公開 URL:
Your quick Tunnel has been created! Visit it at:
https://random-words-here.trycloudflare.com
💡 注意: Quick Tunnel 每次重啟 URL 都會改變。如果需要固定 URL,請參考 Named Tunnels。
- 前往 repo Settings → Secrets and variables → Actions → Variables
- 編輯
API_BASE_URL變數,將值改為 Cloudflare Tunnel 提供的 HTTPS URL:https://random-words-here.trycloudflare.com⚠️ 注意: URL 末尾不要加斜杠/
觸發 GitHub Pages 重新部署(以下任一方式):
- 推送任何 commit 到
main分支 - 前往 Actions → Frontend Deploy → Run workflow
- 打開 https://YOUR_USERNAME.github.io/MalScanWorker/
- 上傳一個測試檔案
- 應該看到上傳成功並進入分析進度頁面
🔍 除錯提示: 如果遇到問題,打開瀏覽器 DevTools (F12) → Network 標籤,檢查 API 請求的 URL 和響應。
開發時,我們需要兩個部分:
- 基礎設施(Infra):PostgreSQL, MinIO, RabbitMQ, ClamAV
- 應用程式(App):前端、Ingest 服務、後端 API、Worker
⚠️ 避免「幽靈消費者」問題 (Ghost Consumer)如果你打算使用
poetry run在本機啟動 Worker 進行開發除錯,絕對不要使用docker compose up -d啟動全部服務! 因為docker-compose.yml中也包含了一個worker服務,若同時啟動,會導致 RabbitMQ 上出現兩個 consumers,造成部分任務被 Docker 中的舊版 Worker 搶走,導致分析結果異常。正確的做法:只啟動基礎設施容器,然後在本機手動啟動應用程式。
在專案根目錄執行,僅啟動所需的基礎設施,不啟動 API、Ingest 和 Worker:
docker compose up -d postgres minio rabbitmq clamav後端與 Worker 需要連線至本機建立的基礎設施。請切換目錄建立對應的 .env 檔案:
cd backend
cat <<EOF > .env
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/malscan
MINIO_ENDPOINT=localhost:9000
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin
RABBITMQ_URL=amqp://guest:guest@localhost:5672/
EOFcd ../worker
cat <<EOF > .env
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/malscan
MINIO_ENDPOINT=localhost:9000
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin
RABBITMQ_URL=amqp://guest:guest@localhost:5672/
CLAMAV_HOST=localhost
CLAMAV_PORT=3310
SANDBOX_MOCK=true
EOFcd ../ingest
cat <<EOF > .env
LISTEN_ADDR=:8080
DATABASE_URL=postgres://postgres:postgres@localhost:5432/malscan?sslmode=disable
MINIO_ENDPOINT=localhost:9000
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin
MINIO_USE_SSL=false
MINIO_BUCKET=uploads
RABBITMQ_URL=amqp://guest:guest@localhost:5672/
EOF💡 資料庫表格會在後端和 Worker 啟動時自動建立,無需手動執行 migration。
cd frontend
npm install
npm run devcd ingest
go run ./cmd/ingestcd backend
poetry install
poetry run uvicorn malscan.main:app --reloadcd worker
poetry install
poetry run python -m malscan_worker.main若你完成開發,想在本機以 完整容器化 的方式測試整個系統:
- 請先確保關閉所有本機正在運行的
poetry run和go run(API、Ingest 和 Worker),以避免潛在的連線或消費者衝突。 - 執行以下指令,強制重新編譯修改過的 Worker、API 和 Ingest image,確保容器使用的是最新的程式碼:
# 在專案根目錄下
docker compose up -d --build當上傳的壓縮檔(ZIP、7z、RAR)需要密碼時,系統會自動暫停分析並提示輸入密碼。
- ZIP: ZipCrypto / AES-256(WZ_AES)
- 7z: AES-256
- RAR: AES-128 / AES-256
- 上傳有密碼的壓縮檔 → 系統自動偵測並進入
password_required狀態(前端顯示「需要解壓密碼」) - 在工作狀態頁輸入密碼 → 系統重新嘗試解壓並繼續分析
- 最多可重試 3 次
- 密碼正確:正常解壓,分析內部檔案
- 3 次皆錯誤:產生最終報告,明確標示「解壓縮失敗」
- 解壓成功:報告顯示解壓縮資訊(檔案數、子任務數、解壓縮大小)
- 解壓失敗:報告顯示紅色橫幅提示,提醒解壓縮失敗
| 方法 | 路徑 | 處理服務 | 說明 |
|---|---|---|---|
| POST | /api/v1/files |
Go Ingest Service | 上傳檔案進行分析 |
| GET | /api/v1/jobs/{job_id} |
FastAPI | 查詢分析狀態 |
| POST | /api/v1/jobs/{job_id}/password |
FastAPI | 提交壓縮檔密碼(最多 3 次) |
| GET | /api/v1/reports/{job_id} |
FastAPI | 取得分析報告 |
- 前端: React 18 + TypeScript + Vite
- Ingest: Go 1.25 + chi + pgx + minio-go + amqp091-go(檔案接收層)
- 後端: FastAPI + SQLAlchemy + asyncpg
- Worker: Python + clamscan CLI + yara CLI + pyzipper (AES ZIP) + py7zr + rarfile
- 反向代理: Nginx(路由分流至 Ingest / Backend)
- 佇列: RabbitMQ
- 儲存: MinIO + Supabase PostgreSQL
- 容器: k3s + GHCR
- CI/CD: GitHub Actions
MIT