security: gate fifa2026 production deploys
This commit is contained in:
@@ -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!"
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
54
docs/security/incident-2026-06-18-188-miner-triage.md
Normal file
54
docs/security/incident-2026-06-18-188-miner-triage.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# 2026-06-18 188 主機挖礦木馬疑慮清查紀錄
|
||||||
|
|
||||||
|
## 結論摘要
|
||||||
|
|
||||||
|
截至 2026-06-18 11:20(Asia/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 對外服務埠,只保留必要入口。
|
||||||
@@ -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
|
|
||||||
Reference in New Issue
Block a user