Auth pillar 從 OAuth 2.0 resource server 改成 pre-shared API key (visionA ↔ converter 1:1 internal trust)。新增 GET /api/v1/jobs/:id/result streaming endpoint 給 visionA backend 中轉 NEF 下載。 Phase A(auth 切換): - 新增 apiKeyMiddleware(constant-time compare、tokenFingerprint、4 audit events) - 砍 OAuth middleware + JWKS(保留 oauthClient 供 promote → FAA 使用) - 4 個 endpoint 換掛 requireApiKey - 加 TRUST_PROXY env + Express trust proxy 設定(forensic source_ip) Phase B(/result endpoint): - streaming NEF download with 5min timeout + concurrent cap 10 - Two-tier rate limit(burst 5/10s + sustained 20/min) - Bandwidth quota(1 GB/hr + 6 GB/24hr)by token_fingerprint - Range header silently ignored + Accept-Ranges: none - filename quote-escape + RFC 5987 fallback + sanitize - 8 個 /result audit events(forensic 完整) 設計演進記錄:docs/TODO-visionA-integration-v2.md(5/2 OAuth → 5/16 API key → 5/16 download via converter;對應 visionA repo ADR-015/016) Tests: 597 → 666 (+69)、29 suites all pass Security: APPROVE WITH CONDITIONS(單 instance 部署、6 新 env、24hr 監控) npm audit: 3 vuln → 0(transitive AWS SDK xml chain) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
285 lines
13 KiB
Plaintext
285 lines
13 KiB
Plaintext
###############################################################################
|
||
# Task Scheduler 環境變數範本(Phase 0.8b 收斂版)
|
||
#
|
||
# 三類分區(依顯示順序):
|
||
# 1. 必填(production 必須設真實值)— 缺漏會 fail-fast,process exit code 1
|
||
# 2. 可選(合理預設)— 不設會用程式內 default
|
||
# 3. 開發 placeholder — 用 RFC 2606 `.invalid` TLD 確保不會誤連到真實服務
|
||
#
|
||
# 部署準則:
|
||
# - 切勿 commit `.env`(已在 .gitignore;歷史 commit 待 D7 處理)
|
||
# - production 用 secret manager(Vault / AWS Secrets Manager),不要直接設環境變數
|
||
# - 任何含 `REPLACE-ME` 字樣或 `.invalid` TLD 的值,部署前必須替換
|
||
#
|
||
# Phase 0.8b A4 砍除(visionA → converter 改用 API key、不再走 OAuth/JWKS):
|
||
# - MEMBER_CENTER_ISSUER / MEMBER_CENTER_JWKS_URL
|
||
# - KNERON_CONVERTER_AUDIENCE / CONVERTER_TENANT_ID
|
||
# - CONVERTER_SCOPE_WRITE / CONVERTER_SCOPE_READ
|
||
# - JWKS_CACHE_MAX_AGE_MS / JWKS_COOLDOWN_MS / JWT_CLOCK_TOLERANCE_SEC
|
||
# converter → FAA OAuth client 鏈條保留不動(MEMBER_CENTER_TOKEN_URL /
|
||
# KNERON_CONVERTER_CLIENT_* / FILE_ACCESS_AGENT_* / OAUTH_TOKEN_*),那條與
|
||
# visionA → converter 對外認證無關。
|
||
###############################################################################
|
||
|
||
|
||
# =============================================================================
|
||
# 1. 應用基本設定
|
||
# =============================================================================
|
||
|
||
# 監聽 port(必填,但有合理預設)
|
||
PORT=4000
|
||
|
||
# Node 環境(development / staging / production)
|
||
# - production 時 FILE_ACCESS_AGENT_BASE_URL 強制 HTTPS
|
||
NODE_ENV=development
|
||
|
||
# Log 等級(debug / info / warn / error)
|
||
LOG_LEVEL=info
|
||
|
||
|
||
# =============================================================================
|
||
# 2. Redis(必填)
|
||
# =============================================================================
|
||
# - 不設會用 default,但實際部署需指向真實 Redis
|
||
# - 帶 password:redis://:password@host:6379
|
||
|
||
REDIS_URL=redis://localhost:6379
|
||
|
||
|
||
# =============================================================================
|
||
# 3. Job 資料目錄(local storage 用)
|
||
# =============================================================================
|
||
# - STORAGE_BACKEND=local 時,此目錄為 worker / scheduler 共用 volume
|
||
# - STORAGE_BACKEND=minio 時,仍會用此目錄存暫時檔(如 health check)
|
||
|
||
JOB_DATA_DIR=/data/jobs
|
||
|
||
|
||
# =============================================================================
|
||
# 4. CORS(必填)
|
||
# =============================================================================
|
||
|
||
FRONTEND_URL=http://localhost:3000
|
||
|
||
|
||
# =============================================================================
|
||
# 5. Storage backend(必填)
|
||
# =============================================================================
|
||
# - "local":用 JOB_DATA_DIR 共用 volume(單機開發 / docker-compose)
|
||
# - "minio":用 MinIO / S3-compatible(production 推薦;POST /api/v1/jobs 必須 minio)
|
||
|
||
STORAGE_BACKEND=local
|
||
|
||
|
||
# =============================================================================
|
||
# 6. MinIO / S3 設定
|
||
# =============================================================================
|
||
# - STORAGE_BACKEND=minio 時為必填
|
||
# - STORAGE_BACKEND=local 時可留空
|
||
# - 注意:production 不要把真實 secret 寫在這裡,改用 secret manager
|
||
|
||
MINIO_ENDPOINT_URL=http://192.168.0.130:9000
|
||
MINIO_BUCKET=convertet-working-space
|
||
MINIO_ACCESS_KEY=convuser
|
||
MINIO_SECRET_KEY=REPLACE-ME-IN-PRODUCTION
|
||
MINIO_REGION=us-east-1
|
||
# bucket lifecycle(天)— 上傳後 N 天自動清,避免 orphan 累積
|
||
MINIO_LIFECYCLE_DAYS=7
|
||
|
||
|
||
# =============================================================================
|
||
# 7. visionA → converter API Key 認證(Phase 0.8b 必填)
|
||
# =============================================================================
|
||
#
|
||
# visionA-backend ↔ converter 為 1:1 internal trust,採 pre-shared API key
|
||
# 取代 OAuth client_credentials(ADR-015 v2.1)。converter 端只需這一把 key,
|
||
# 無 issuer / audience / scope / tenant 概念。
|
||
#
|
||
# 產生:
|
||
# $ openssl rand -hex 32
|
||
# # 輸出 64 hex chars(128 bits 熵),對齊 NIST SP 800-131A
|
||
#
|
||
# 部署:
|
||
# - converter `.env`:CONVERTER_API_KEY=<產出字串>
|
||
# - visionA `.env.stage`:VISIONA_CONVERTER_API_KEY=<相同字串>
|
||
# - **兩端必須完全相同**;rotate 時雙端同時更新
|
||
#
|
||
# 安全:
|
||
# - 絕不 commit 進 git(`.env` 已在 .gitignore)
|
||
# - 絕不寫入 log / Slack / email / 對話
|
||
# - 每個環境獨立 key(dev / stage / prod 各自 `openssl rand -hex 32`)
|
||
# - 詳見 docs/autoflow/04-architecture/auth.md §1 + §4
|
||
#
|
||
# 為什麼是 optional(不 requireEnv):
|
||
# - dev 環境可能還沒設 key(local 跑 legacy Web UI 路徑也應該能啟動)
|
||
# - apiKeyMiddleware 自己會做 fail-fast(env 未設時對外 API 一律回 503)
|
||
# - 啟動時會印 warn log 提醒,避免無聲問題
|
||
|
||
CONVERTER_API_KEY=
|
||
|
||
|
||
# =============================================================================
|
||
# 8. Member Center Token Endpoint(converter → FAA OAuth client 用,保留)
|
||
# =============================================================================
|
||
#
|
||
# 給 oauthClient.js 取 service token 用(promote 流程把結果檔 PUT 到 FAA 之前要
|
||
# 先用 client_credentials grant 取 token)。visionA → converter 路線**不再經此**。
|
||
#
|
||
# ⚠️ `.invalid` 為 RFC 2606 保留 TLD,DNS 永不解析。本地開發跑「不需 promote 的
|
||
# legacy /jobs 流程」可直接照抄;production 部署前務必替換為真實 Member Center
|
||
# URL,否則 promote 階段取 token 會 DNS 失敗。
|
||
|
||
MEMBER_CENTER_TOKEN_URL=https://auth.example.invalid/oauth/token
|
||
|
||
|
||
# =============================================================================
|
||
# 9. Converter OAuth Client 身份(converter → FAA 用,保留)
|
||
# =============================================================================
|
||
#
|
||
# Converter 在 promote 階段以 OAuth client 身份去 Member Center 取
|
||
# `files:upload.write` scope token,再 PUT 到 File Access Agent。
|
||
# 兩者必須成對出現。
|
||
|
||
KNERON_CONVERTER_CLIENT_ID=kneron_converter_dev
|
||
KNERON_CONVERTER_CLIENT_SECRET=REPLACE-ME-IN-PRODUCTION
|
||
|
||
|
||
# =============================================================================
|
||
# 10. File Access Agent(必填)
|
||
# =============================================================================
|
||
#
|
||
# Promote 時 Converter 把產出 stream PUT 到 FAA。
|
||
# - URL 必須是合法 http(s) URL;NODE_ENV=production 強制 https
|
||
# - 本地開發可用 placeholder(.invalid TLD),不影響非 promote 流程
|
||
|
||
FILE_ACCESS_AGENT_BASE_URL=https://files.example.invalid
|
||
FILE_ACCESS_AGENT_AUDIENCE=file_access_api
|
||
|
||
|
||
# =============================================================================
|
||
# 11. Promote 行為(可選)
|
||
# =============================================================================
|
||
# 單檔 PUT timeout(毫秒)。預設 300000(300s,覆蓋 500MB @ 5MB/s 最壞)。
|
||
# 部署環境檔案普遍較小可調低;GB 級檔案可調高。
|
||
|
||
# PROMOTE_TIMEOUT_MS=300000
|
||
|
||
|
||
# =============================================================================
|
||
# 12. OAuth Client cache(converter → FAA 用,可選)
|
||
# =============================================================================
|
||
|
||
# OAUTH_TOKEN_REFRESH_SKEW_MS=60000 # token 距 expiresAt 還剩多少 ms 主動 refresh
|
||
# OAUTH_TOKEN_TIMEOUT_MS=10000 # 取 token timeout(10s)
|
||
|
||
|
||
# =============================================================================
|
||
# 13. Multipart 上傳上限(可選,T10 修 D5)
|
||
# =============================================================================
|
||
#
|
||
# 為什麼用 env:
|
||
# 不同部署環境記憶體配額差異大(dev 容器 2GB / prod 16GB),固定 500MB 不夠彈性。
|
||
# 調這些值不需改原始碼。
|
||
#
|
||
# 三個值都必須 > 0;非法值會 fail-fast。
|
||
|
||
# MULTIPART_MODEL_MAX_BYTES=524288000 # 500MB(model 檔案上限)
|
||
# MULTIPART_REF_IMAGE_MAX_BYTES=10485760 # 10MB(單張 ref_image 上限)
|
||
# MULTIPART_REF_IMAGES_MAX_COUNT=100 # ref_images 張數上限
|
||
|
||
|
||
# =============================================================================
|
||
# 14. Upload concurrency(可選,T10 修 D5)
|
||
# =============================================================================
|
||
#
|
||
# 為什麼需要:
|
||
# multer memoryStorage 把整份 multipart load 進 buffer,每個並發 upload 吃掉
|
||
# model size 大小的 heap。5 並發 × 500MB ≈ 2.5GB heap,4GB 容器有風險。
|
||
# per-process counter 限制同時間 multipart parse + handler 進行中的請求數量。
|
||
#
|
||
# 超過上限時:直接 503 service_busy + Retry-After header(不 queue),讓 client 主動 backoff。
|
||
|
||
# MAX_CONCURRENT_UPLOADS=5 # 同時間最多 5 個 upload 進行中
|
||
# UPLOAD_RETRY_AFTER_SECONDS=30 # 503 response 的 Retry-After(秒)
|
||
|
||
|
||
# =============================================================================
|
||
# 15. Per-client_id rate limit(可選,T3 起)
|
||
# =============================================================================
|
||
# 對 /api/v1/* 套用,window 內每個 client_id 最多 max 個 request。
|
||
# 預設 5min / 300 req(對齊 TDD §1.1)。
|
||
#
|
||
# Phase 0.8b A3 後,clientId 在 API key 路線下寫死為 `'visionA-service'`;
|
||
# rate limit 仍然套用、實質為 visionA → converter 整體上限。
|
||
|
||
# API_V1_RATE_LIMIT_WINDOW_MS=300000
|
||
# API_V1_RATE_LIMIT_MAX=300
|
||
|
||
|
||
# =============================================================================
|
||
# 16. Trust Proxy(可選,Phase 0.8b A7)
|
||
# =============================================================================
|
||
# 給 Express `app.set('trust proxy', ...)` 用,影響 `req.ip` 取得真實 caller IP。
|
||
# 直接影響 audit log 中 `source_ip` 欄位的 forensic 價值。
|
||
#
|
||
# 為什麼需要:
|
||
# converter 跑在 Nginx / cloud LB 後面時,Node 看到的 remote address 永遠是
|
||
# 反代理的內網 IP;必須信任 `X-Forwarded-For` header 才能取真實 caller IP。
|
||
#
|
||
# ⚠️ 安全提醒(極度重要):
|
||
# - 設過寬(如 `true`)→ attacker 可偽造 `X-Forwarded-For` 欺騙 audit log
|
||
# - 設過嚴(如 stage / prod 留 'loopback')→ source_ip 永遠是反代理 IP、forensic 失效
|
||
# - 必須跟實際部署架構一致;不確定時請問 DevOps / SRE
|
||
#
|
||
# 部署建議:
|
||
# - local dev / 測試環境:留空(預設 `loopback`,只信任 127.0.0.1 / ::1)
|
||
# - stage / prod(前面 1 層 Nginx):TRUST_PROXY=1
|
||
# - stage / prod(cloud LB + Nginx 兩層):TRUST_PROXY=2
|
||
# - 明確 CIDR:TRUST_PROXY="loopback, 10.0.0.0/8"
|
||
#
|
||
# 接受值:boolean(`true` / `false`)/ 整數 / 字串(含 'loopback' / 'linklocal'
|
||
# / 'uniquelocal' / CIDR)。詳見 Express 文件:
|
||
# https://expressjs.com/en/guide/behind-proxies.html
|
||
|
||
# TRUST_PROXY=1
|
||
|
||
|
||
# =============================================================================
|
||
# 17. GET /api/v1/jobs/:id/result(Phase 0.8b Phase B 新增)
|
||
# =============================================================================
|
||
#
|
||
# `/result` 端點給 visionA-backend streaming proxy NEF binary。所有限制
|
||
# 都以 `token_fingerprint`(sha256(api_key).slice(0,12))為 bucket key。
|
||
#
|
||
# 設計:docs/autoflow/04-architecture/api/api-result.md §9 / §15
|
||
#
|
||
# 為什麼 single instance 部署可接受 in-memory counter:
|
||
# - Phase 0.8b 部署是單 Node process;counter 在 process 內 atomic
|
||
# - Phase 2 多 instance 部署前必切 Redis(否則 limit 會被「乘以 instance 數」
|
||
# 放鬆);見 security.md 候補 #8(HIGH)
|
||
|
||
# Stream response timeout(AC-7)— 預設 5 分鐘(300_000 ms)
|
||
# 5 min 最低 throughput ≈ 1.7 MB/s;合法 client 即使中等網路也能拿完 500MB
|
||
# RESULT_STREAM_TIMEOUT_MS=300000
|
||
|
||
# Concurrent stream cap(AC-4)— 預設 10 個同時 stream(per-instance)
|
||
# 超過 → 503 service_busy + Retry-After: 30
|
||
# 平衡:normal load P95 < 5、留 2× headroom;blast radius 可控
|
||
# MAX_CONCURRENT_RESULT_STREAMS=10
|
||
|
||
# Burst rate limit(AC-2)— 預設 5 req / 10 sec(per token_fingerprint)
|
||
# 阻擋短時間 burst 攻擊;允許 visionA retry pattern
|
||
# RESULT_RATE_LIMIT_BURST_PER_10S=5
|
||
# RESULT_RATE_LIMIT_BURST_WINDOW_MS=10000
|
||
|
||
# Sustained rate limit(AC-2)— 預設 20 req / 1 min(per token_fingerprint)
|
||
# 涵蓋 visionA P95 normal load + 1.7× headroom;阻擋持續 mass request
|
||
# RESULT_RATE_LIMIT_SUSTAINED_PER_MIN=20
|
||
# RESULT_RATE_LIMIT_SUSTAINED_WINDOW_MS=60000
|
||
|
||
# Bandwidth quota(AC-3)— 預設 1 GB / hr + 6 GB / 24hr(per token_fingerprint)
|
||
# 阻擋 attacker 用「剛好踩線 req count」配大檔的 bandwidth abuse
|
||
# 1 GB = 1073741824 / 6 GB = 6442450944
|
||
# RESULT_BANDWIDTH_QUOTA_PER_HOUR_BYTES=1073741824
|
||
# RESULT_BANDWIDTH_QUOTA_PER_DAY_BYTES=6442450944
|