security: gate fifa2026 production deploys
Some checks failed
2026 World Cup Quant Platform - Production Deployment / Code Quality, Security Gate & Testing (push) Failing after 2m24s
2026 World Cup Quant Platform - Production Deployment / Deploy to Production VM via Gitea CD (push) Has been skipped

This commit is contained in:
QuantBot
2026-06-18 11:26:20 +08:00
parent 45127904a2
commit 076b5c1bc3
4 changed files with 148 additions and 69 deletions

View File

@@ -7,39 +7,92 @@ on:
jobs: jobs:
test-and-lint: test-and-lint:
name: Code Quality & Testing name: Code Quality, Security Gate & Testing
runs-on: ewoooc-dedicated-runner runs-on: ewoooc-dedicated-runner
steps: steps:
- name: Checkout Code - name: Checkout Code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Security policy gate
run: |
set -euo pipefail
echo "== 檢查禁止進入正式部署的臨時維運腳本 =="
forbidden_files="
iwooos_javae_monitor.sh
iwooos_autopatch.py
fix_guardian.py
fix_register.sh
fix_wazuh.sh
ops/harden-host.sh
"
for file in $forbidden_files; do
if [ -e "$file" ]; then
echo "禁止部署:$file 不得存在於正式產品 repo。"
exit 1
fi
done
echo "== 檢查硬編碼密碼、手工 SSH 修補與挖礦 IOC =="
if grep -RInE '(sshpass|sudo -S|WAZUH_PASS\s*=|YOUR_BOT_TOKEN|xmrig|kinsing|kdevtmpfsi|stratum|pool\.supportxmr\.com|221\.156\.167\.200|0936223270|Wooo-0936223270)' \
--exclude-dir=.git \
--exclude-dir=.gitea \
--exclude-dir=node_modules \
--exclude-dir=.next \
--exclude=package-lock.json \
--exclude='*.md' \
.; then
echo "禁止部署:偵測到硬編碼密碼、挖礦 IOC 或手工 SSH 修補痕跡。"
exit 1
fi
- name: Setup Python Environment - name: Setup Python Environment
run: | run: |
apt-get update -qq apt-get update -qq
apt-get install -y -qq python3-pip python3-venv curl apt-get install -y -qq python3-pip python3-venv
python3 -m venv venv python3 -m venv venv
echo "PATH=$PWD/venv/bin:$PATH" >> $GITHUB_ENV echo "PATH=$PWD/venv/bin:$PATH" >> $GITHUB_ENV
- name: Install Backend Dependencies - name: Install Backend Dependencies
run: | run: |
pip install -r platform/backend/requirements.txt pytest pip install -r platform/backend/requirements.txt pytest pip-audit
- name: Python dependency audit
run: pip-audit -r platform/backend/requirements.txt
- name: Run Backend Quant Engine Tests - name: Run Backend Quant Engine Tests
run: pytest platform/backend/app/analytics/ || true run: pytest platform/backend/app/analytics/
- name: Setup Node.js Environment - name: Setup Node.js Environment
uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm
cache-dependency-path: platform/web/package-lock.json
- name: Install Frontend Dependencies
run: | run: |
curl -fsSL https://deb.nodesource.com/setup_18.x | bash - cd platform/web
apt-get install -y nodejs npm ci --legacy-peer-deps
- name: Frontend dependency audit
run: |
cd platform/web
npm audit --audit-level=high
- name: Run Frontend Linting - name: Run Frontend Linting
run: | run: |
cd platform/web cd platform/web
npm install --legacy-peer-deps npm run lint
npm run lint || true
- name: Validate Docker Compose
env:
DB_PASSWORD: ci-placeholder-db-password
REDIS_PASSWORD: ci-placeholder-redis-password
NEXTAUTH_SECRET: ci-placeholder-nextauth-secret
run: docker compose -f docker-compose.prod.yml config -q
deploy-docker: deploy-docker:
name: Deploy to Production VM via Rsync name: Deploy to Production VM via Gitea CD
needs: test-and-lint needs: test-and-lint
runs-on: ewoooc-dedicated-runner runs-on: ewoooc-dedicated-runner
if: github.ref == 'refs/heads/main' if: github.ref == 'refs/heads/main'
@@ -60,7 +113,8 @@ jobs:
- name: Sync Files to Production - name: Sync Files to Production
run: | run: |
rsync -avz --ignore-errors --inplace -e "ssh -i ~/.ssh/id_deploy" \ printf "%s\n" "${{ github.sha }}" > REVISION
rsync -az --delete --delay-updates -e "ssh -i ~/.ssh/id_deploy" \
--exclude='.git/' \ --exclude='.git/' \
--exclude='.gitea/' \ --exclude='.gitea/' \
--exclude='node_modules/' \ --exclude='node_modules/' \
@@ -77,8 +131,19 @@ jobs:
username: ${{ secrets.PROD_SERVER_USER }} username: ${{ secrets.PROD_SERVER_USER }}
key: ${{ secrets.PROD_SSH_PRIVATE_KEY }} key: ${{ secrets.PROD_SSH_PRIVATE_KEY }}
script: | script: |
set -euo pipefail
echo "🚀 [Deploy] Starting deployment for 2026fifa.wooo.work" echo "🚀 [Deploy] Starting deployment for 2026fifa.wooo.work"
cd /opt/fifa2026/current cd /opt/fifa2026/current
docker compose -f docker-compose.prod.yml up --build -d
for file in iwooos_javae_monitor.sh iwooos_autopatch.py fix_guardian.py fix_register.sh fix_wazuh.sh ops/harden-host.sh; do
if [ -e "$file" ]; then
echo "❌ [Deploy] Forbidden emergency script still exists on production: $file"
exit 1
fi
done
docker compose -f docker-compose.prod.yml config -q
docker compose -f docker-compose.prod.yml build --pull
docker compose -f docker-compose.prod.yml up -d --remove-orphans
docker image prune -f docker image prune -f
echo "✅ [Deploy] Deployment completed successfully!" echo "✅ [Deploy] Deployment completed successfully!"

View File

@@ -29,7 +29,7 @@ services:
- DAC_OVERRIDE # postgres needs these minimal caps - DAC_OVERRIDE # postgres needs these minimal caps
environment: environment:
POSTGRES_USER: fifa_user POSTGRES_USER: fifa_user
POSTGRES_PASSWORD: ${DB_PASSWORD:-change_me} POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required}
POSTGRES_DB: fifa2026 POSTGRES_DB: fifa2026
volumes: volumes:
- pg-data:/var/lib/postgresql/data - pg-data:/var/lib/postgresql/data
@@ -56,13 +56,13 @@ services:
--appendonly yes --appendonly yes
--protected-mode yes --protected-mode yes
--bind 0.0.0.0 --bind 0.0.0.0
--requirepass ${REDIS_PASSWORD:-change_me_redis} --requirepass ${REDIS_PASSWORD:?REDIS_PASSWORD is required}
volumes: volumes:
- redis-data:/data - redis-data:/data
networks: networks:
- wc2026-net - wc2026-net
healthcheck: healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-change_me_redis}", "ping"] test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:?REDIS_PASSWORD is required}", "ping"]
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5
@@ -77,8 +77,8 @@ services:
extra_hosts: extra_hosts:
- "host.docker.internal:host-gateway" - "host.docker.internal:host-gateway"
environment: environment:
- DATABASE_URL=postgresql+asyncpg://fifa_user:${DB_PASSWORD:-change_me}@fifa2026-postgres:5432/fifa2026 - DATABASE_URL=postgresql+asyncpg://fifa_user:${DB_PASSWORD:?DB_PASSWORD is required}@fifa2026-postgres:5432/fifa2026
- REDIS_URL=redis://:${REDIS_PASSWORD:-change_me_redis}@fifa2026-redis:6379/0 - REDIS_URL=redis://:${REDIS_PASSWORD:?REDIS_PASSWORD is required}@fifa2026-redis:6379/0
- THE_ODDS_API_KEY=${THE_ODDS_API_KEY:-} - THE_ODDS_API_KEY=${THE_ODDS_API_KEY:-}
- THE_ODDS_BASE=${THE_ODDS_BASE:-https://api.the-odds-api.com} - THE_ODDS_BASE=${THE_ODDS_BASE:-https://api.the-odds-api.com}
- THE_ODDS_SPORT_KEY=${THE_ODDS_SPORT_KEY:-soccer_fifa_world_cup} - THE_ODDS_SPORT_KEY=${THE_ODDS_SPORT_KEY:-soccer_fifa_world_cup}
@@ -121,7 +121,7 @@ services:
<<: *backend-security <<: *backend-security
command: ["python", "-m", "app.analytics.worldcup_seed"] command: ["python", "-m", "app.analytics.worldcup_seed"]
environment: environment:
- DATABASE_URL=postgresql+asyncpg://fifa_user:${DB_PASSWORD:-change_me}@fifa2026-postgres:5432/fifa2026 - DATABASE_URL=postgresql+asyncpg://fifa_user:${DB_PASSWORD:?DB_PASSWORD is required}@fifa2026-postgres:5432/fifa2026
- TZ=Asia/Taipei - TZ=Asia/Taipei
depends_on: depends_on:
fifa2026-postgres: fifa2026-postgres:
@@ -136,8 +136,8 @@ services:
<<: *security-defaults <<: *security-defaults
command: ["python", "-m", "app.analytics.crawler"] command: ["python", "-m", "app.analytics.crawler"]
environment: environment:
- DATABASE_URL=postgresql+asyncpg://fifa_user:${DB_PASSWORD:-change_me}@fifa2026-postgres:5432/fifa2026 - DATABASE_URL=postgresql+asyncpg://fifa_user:${DB_PASSWORD:?DB_PASSWORD is required}@fifa2026-postgres:5432/fifa2026
- REDIS_URL=redis://:${REDIS_PASSWORD:-change_me_redis}@fifa2026-redis:6379/0 - REDIS_URL=redis://:${REDIS_PASSWORD:?REDIS_PASSWORD is required}@fifa2026-redis:6379/0
- THE_ODDS_API_KEY=${THE_ODDS_API_KEY:-} - THE_ODDS_API_KEY=${THE_ODDS_API_KEY:-}
- THE_ODDS_BASE=${THE_ODDS_BASE:-https://api.the-odds-api.com} - THE_ODDS_BASE=${THE_ODDS_BASE:-https://api.the-odds-api.com}
- THE_ODDS_SPORT_KEY=${THE_ODDS_SPORT_KEY:-soccer_fifa_world_cup} - THE_ODDS_SPORT_KEY=${THE_ODDS_SPORT_KEY:-soccer_fifa_world_cup}
@@ -166,7 +166,7 @@ services:
<<: *security-defaults <<: *security-defaults
command: ["python", "-m", "app.analytics.news_worker"] command: ["python", "-m", "app.analytics.news_worker"]
environment: environment:
- REDIS_URL=redis://:${REDIS_PASSWORD:-change_me_redis}@fifa2026-redis:6379/0 - REDIS_URL=redis://:${REDIS_PASSWORD:?REDIS_PASSWORD is required}@fifa2026-redis:6379/0
- NEWS_POLL_INTERVAL_SECONDS=${NEWS_POLL_INTERVAL_SECONDS:-900} - NEWS_POLL_INTERVAL_SECONDS=${NEWS_POLL_INTERVAL_SECONDS:-900}
- NEWS_QUERY=${NEWS_QUERY:-2026 FIFA World Cup OR 2026 世界盃 OR 世界盃 2026} - NEWS_QUERY=${NEWS_QUERY:-2026 FIFA World Cup OR 2026 世界盃 OR 世界盃 2026}
- GEMINI_API_KEY=${GEMINI_API_KEY:-} - GEMINI_API_KEY=${GEMINI_API_KEY:-}
@@ -188,8 +188,8 @@ services:
extra_hosts: extra_hosts:
- "host.docker.internal:host-gateway" - "host.docker.internal:host-gateway"
environment: environment:
- DATABASE_URL=postgresql+asyncpg://fifa_user:${DB_PASSWORD:-change_me}@fifa2026-postgres:5432/fifa2026 - DATABASE_URL=postgresql+asyncpg://fifa_user:${DB_PASSWORD:?DB_PASSWORD is required}@fifa2026-postgres:5432/fifa2026
- REDIS_URL=redis://:${REDIS_PASSWORD:-change_me_redis}@fifa2026-redis:6379/0 - REDIS_URL=redis://:${REDIS_PASSWORD:?REDIS_PASSWORD is required}@fifa2026-redis:6379/0
- GEMINI_API_KEY=${GEMINI_API_KEY:-} - GEMINI_API_KEY=${GEMINI_API_KEY:-}
- GEMINI_MODEL=${GEMINI_MODEL:-gemini-3-flash-preview} - GEMINI_MODEL=${GEMINI_MODEL:-gemini-3-flash-preview}
- GEMINI_REVIEW_MODEL=${GEMINI_REVIEW_MODEL:-gemini-2.5-flash} - GEMINI_REVIEW_MODEL=${GEMINI_REVIEW_MODEL:-gemini-2.5-flash}
@@ -226,8 +226,8 @@ services:
<<: *security-defaults <<: *security-defaults
command: ["python", "-m", "app.analytics.daily_card_calendar_worker"] command: ["python", "-m", "app.analytics.daily_card_calendar_worker"]
environment: environment:
- DATABASE_URL=postgresql+asyncpg://fifa_user:${DB_PASSWORD:-change_me}@fifa2026-postgres:5432/fifa2026 - DATABASE_URL=postgresql+asyncpg://fifa_user:${DB_PASSWORD:?DB_PASSWORD is required}@fifa2026-postgres:5432/fifa2026
- REDIS_URL=redis://:${REDIS_PASSWORD:-change_me_redis}@fifa2026-redis:6379/0 - REDIS_URL=redis://:${REDIS_PASSWORD:?REDIS_PASSWORD is required}@fifa2026-redis:6379/0
- DAILY_CARD_CALENDAR_POLL_INTERVAL_SECONDS=${DAILY_CARD_CALENDAR_POLL_INTERVAL_SECONDS:-300} - DAILY_CARD_CALENDAR_POLL_INTERVAL_SECONDS=${DAILY_CARD_CALENDAR_POLL_INTERVAL_SECONDS:-300}
- DAILY_CARD_CALENDAR_CACHE_TTL_SECONDS=${DAILY_CARD_CALENDAR_CACHE_TTL_SECONDS:-420} - DAILY_CARD_CALENDAR_CACHE_TTL_SECONDS=${DAILY_CARD_CALENDAR_CACHE_TTL_SECONDS:-420}
- DAILY_CARD_CALENDAR_START_DATE=${DAILY_CARD_CALENDAR_START_DATE:-2026-06-11} - DAILY_CARD_CALENDAR_START_DATE=${DAILY_CARD_CALENDAR_START_DATE:-2026-06-11}
@@ -250,8 +250,8 @@ services:
<<: *security-defaults <<: *security-defaults
command: ["python", "-m", "app.analytics.fixtures_worker"] command: ["python", "-m", "app.analytics.fixtures_worker"]
environment: environment:
- DATABASE_URL=postgresql+asyncpg://fifa_user:${DB_PASSWORD:-change_me}@fifa2026-postgres:5432/fifa2026 - DATABASE_URL=postgresql+asyncpg://fifa_user:${DB_PASSWORD:?DB_PASSWORD is required}@fifa2026-postgres:5432/fifa2026
- REDIS_URL=redis://:${REDIS_PASSWORD:-change_me_redis}@fifa2026-redis:6379/0 - REDIS_URL=redis://:${REDIS_PASSWORD:?REDIS_PASSWORD is required}@fifa2026-redis:6379/0
- FIXTURES_JSON_URL=${FIXTURES_JSON_URL:-https://www.thestatsapi.com/world-cup/data/fixtures.json} - FIXTURES_JSON_URL=${FIXTURES_JSON_URL:-https://www.thestatsapi.com/world-cup/data/fixtures.json}
- FIXTURES_POLL_INTERVAL_SECONDS=${FIXTURES_POLL_INTERVAL_SECONDS:-21600} - FIXTURES_POLL_INTERVAL_SECONDS=${FIXTURES_POLL_INTERVAL_SECONDS:-21600}
- TZ=Asia/Taipei - TZ=Asia/Taipei
@@ -277,8 +277,8 @@ services:
- NEXT_PUBLIC_WS_URL=wss://2026fifa.wooo.work/ws/matches - NEXT_PUBLIC_WS_URL=wss://2026fifa.wooo.work/ws/matches
- ANALYTICS_BACKEND_URL=http://fifa2026-backend:8000 - ANALYTICS_BACKEND_URL=http://fifa2026-backend:8000
- NEXTAUTH_URL=https://2026fifa.wooo.work - NEXTAUTH_URL=https://2026fifa.wooo.work
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET:-replace-me-in-production} - NEXTAUTH_SECRET=${NEXTAUTH_SECRET:?NEXTAUTH_SECRET is required}
- DATABASE_URL=postgresql://fifa_user:${DB_PASSWORD:-change_me}@fifa2026-postgres:5432/fifa2026 - DATABASE_URL=postgresql://fifa_user:${DB_PASSWORD:?DB_PASSWORD is required}@fifa2026-postgres:5432/fifa2026
- TZ=Asia/Taipei - TZ=Asia/Taipei
depends_on: depends_on:
fifa2026-backend: fifa2026-backend:
@@ -292,7 +292,7 @@ services:
restart: always restart: always
<<: *security-defaults <<: *security-defaults
environment: environment:
- REDIS_URL=redis://:${REDIS_PASSWORD:-change_me_redis}@fifa2026-redis:6379/0 - REDIS_URL=redis://:${REDIS_PASSWORD:?REDIS_PASSWORD is required}@fifa2026-redis:6379/0
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN:-} - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN:-}
- TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID:-} - TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID:-}
depends_on: depends_on:

View File

@@ -0,0 +1,54 @@
# 2026-06-18 188 主機挖礦木馬疑慮清查紀錄
## 結論摘要
截至 2026-06-18 11:20Asia/Taipei的第一輪清查尚未在 `current-fifa2026-web-1` 或 FIFA 相關容器內確認正在執行的挖礦程序。
實際確認到的高風險問題是:正式產品 repo 與正式部署目錄曾混入多個手工維運與緊急防禦腳本。這些腳本包含硬編碼密碼、跨主機 `sshpass`、自動修改正式機檔案、手動重建容器、直接刪除暫存 ELF、以及非 CI/CD 管控的自動 kill 流程。這類做法不可再進入正式環境。
## 188 現場觀察
- CPU 熱點主要是 `signoz-clickhouse``falco`、VibeWork/Momo 相關服務與監控元件。
- FIFA 相關容器 CPU 佔用很低,`current-fifa2026-web-1` 在抽樣時為 0% 左右。
- `current-fifa2026-web-1` 容器內只看到 `next-server`,未看到 `javae``npm start``xmrig``kinsing``kdevtmpfsi``/tmp` 可疑執行檔。
- `/tmp``/var/tmp``/dev/shm` 抽樣未看到典型挖礦 ELF。
- `ss` 抽樣未看到連往常見挖礦連接埠或已封鎖 IOC 的現行連線。
- Falco 主要警報來自 Wazuh 與 Momo/Postgres healthcheck 讀取敏感檔案造成的告警噪音,需另行調整規則與 healthcheck。
## 已確認高風險檔案
下列檔案不得存在於正式產品 repo也不得由手工 SSH 推進到正式環境:
- `iwooos_javae_monitor.sh`
- `iwooos_autopatch.py`
- `fix_guardian.py`
- `fix_register.sh`
- `fix_wazuh.sh`
- `ops/harden-host.sh`
風險原因:
- 含硬編碼密碼或手工 sudo 流程。
- 含跨主機 sshpass 與自動修改遠端主機設定。
- 含自動修改 `package.json`、重建 Docker、直接操作正式容器。
- 含可能刪除證據的 `/tmp` ELF 清除流程。
- 含與 Docker Compose 狀態不一致的手動 `docker run`
## 已套用的 repo 修正
- 移除上述 6 個高風險腳本。
- 將 Wazuh API route 改成只使用環境變數,不再硬編碼帳密。
- Wazuh 未設定或讀取失敗時,明確回傳「未接入真實資料」,不再輸出假告警、假弱點統計或假事件。
- Gitea CD 新增安全閘門,阻擋硬編碼密碼、手工 SSH 修補、挖礦 IOC、`sshpass` 與禁止檔案。
- Gitea CD 移除會吞錯的 `|| true`測試、lint、稽核失敗即停止部署。
- Gitea CD 改用 `rsync --delete`,讓正式機同步清掉已從 repo 移除的高風險腳本。
- Docker Compose 改成要求正式環境必須提供 `DB_PASSWORD``REDIS_PASSWORD``NEXTAUTH_SECRET`,不得再使用 `change_me` 類預設值。
## 後續必做事項
- 輪換所有曾出現在腳本、環境檔、shell 歷史或部署目錄中的密碼與 API key。
- 由 Gitea Secrets 管控正式部署密鑰,不再把密鑰寫進 repo 或腳本。
- 移除 188 上 `/home/ollama/scripts/iwooos_javae_monitor.sh` 的 cron。
- 清查 192.168.0.112 guardian/Wazuh 來源,確認反覆 SSH 進 188 執行防火牆檢查的流程是否仍需要保留。
- 調整 Falco/Wazuh 規則,降低 healthcheck 造成的敏感檔案誤報。
- 收斂 188 對外服務埠,只保留必要入口。

View File

@@ -1,40 +0,0 @@
#!/bin/bash
# AWOOOI - javae Mining Trojan Monitor
# This script monitors the current-fifa2026-web-1 container for suspicious processes.
# It looks for 'javae', 'npm start', and 'entrypoint.sh' running as unexpected users or with high CPU.
CONTAINER_NAME="current-fifa2026-web-1"
LOG_FILE="/home/ollama/logs/iwooos_javae_monitor.log"
TELEGRAM_BOT_TOKEN="YOUR_BOT_TOKEN" # Can be injected or ignored if Wazuh is used
CHAT_ID="YOUR_CHAT_ID"
mkdir -p /home/ollama/logs
# Check if container is running
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
exit 0
fi
# Find suspicious PIDs
# nextjs user (uid 1001) should ONLY be running "node node_modules/.bin/next start"
# Any process containing "npm" or "javae" or "entrypoint" is suspicious
SUSPICIOUS_PIDS=$(docker exec ${CONTAINER_NAME} ps aux 2>/dev/null | awk 'NR>1 && $11~/npm|javae|entrypoint\.sh/ {print $2}')
if [ ! -z "$SUSPICIOUS_PIDS" ]; then
DATE=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$DATE] 🚨 SUSPICIOUS PROCESS DETECTED in $CONTAINER_NAME!" >> $LOG_FILE
for PID in $SUSPICIOUS_PIDS; do
CMD=$(docker exec ${CONTAINER_NAME} ps -p $PID -o cmd= 2>/dev/null)
echo "[$DATE] Killing PID $PID : $CMD" >> $LOG_FILE
# Send alert to Wazuh / Telegram
# For now, just log and kill
docker exec -u 0 ${CONTAINER_NAME} kill -9 $PID 2>/dev/null
done
# Send an alert to Telegram if script exists
if [ -f "/home/ollama/bin/send_telegram.py" ]; then
/usr/bin/python3 /home/ollama/bin/send_telegram.py "🚨 警告:在 $CONTAINER_NAME 容器內偵測到挖礦木馬 (javae/npm start)!已經自動將其阻斷。"
fi
fi