feat(stage): nginx 加內網 IP 直連 server block(demo 用)
agent 本機 demo 需直連內網 192.168.0.130:9527 建 tunnel(繞過公網邊界
proxy — 那台剝 WS upgrade header、碰不到)。但直連 IP 時 Host=192.168.0.130
不命中 stage-9527 白名單被 444 擋。
加一個 server_name 192.168.0.130 的 server block(複製 stage-9527 block、
XFP 改 http),8 個 location 全對齊含 /tunnel/connect WS upgrade。現有兩
block(default 白名單 + stage-9527)一字未動。
安全:公網經 proxy 進來 Host 固定 stage-9527、偽造 Host 打不進;IP block
只內網可達;default 444 白名單未削弱。⚠️ demo 專用、正式上線前應移除或
加 allow/deny(註解已標)。
搭配 .env.stage VISIONA_RELAY_PUBLIC_URL 改內網 ws(部署時設、缺一不可)。
Reviewer 通過 0C/0M/0Mi。stage 純 demo 無正式使用者。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ab50691f8a
commit
58313169c2
@ -346,3 +346,175 @@ server {
|
|||||||
proxy_cache off;
|
proxy_cache off;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 內網 IP 直連 server block(demo 用)
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# 目的:讓 visionA Agent / 內網瀏覽器以 http://192.168.0.130:9527
|
||||||
|
# 直連 container(繞過公網邊界 proxy,那層剝 WS upgrade header、
|
||||||
|
# 碰不到 /tunnel/connect)。
|
||||||
|
#
|
||||||
|
# 為什麼安全(見 spec §4 完整評估):
|
||||||
|
# - 公網邊界 proxy 進來的 Host 一律被改寫成 stage-9527.innovedus.com
|
||||||
|
# → 永遠走上面那個 block,外部偽造 Host:192.168.0.130 也打不進這條。
|
||||||
|
# - 這條只有「能直接連到 130:9527 的內網來源」可命中。
|
||||||
|
# - default_server 仍是 server_name _ 那條,本 block 不加 default_server。
|
||||||
|
#
|
||||||
|
# DRY 取捨:務實複製 stage-9527 block 的 location。WS upgrade map
|
||||||
|
# $connection_upgrade 在 http context 定義(檔頭 38-41 行),本 block 直接繼承。
|
||||||
|
#
|
||||||
|
# X-Forwarded-Proto:實際是 http 進來,但 backend 不讀 XFP(已 code 確認),
|
||||||
|
# 設 http 或 https 都不影響。這裡設 http 以符合實際(避免 backend 未來
|
||||||
|
# 萬一改讀 XFP 時組出錯誤的 https absolute URL)。
|
||||||
|
#
|
||||||
|
# ⚠️ demo 專用,正式上線前應移除,或改用 allow <內網網段>; deny all; 限制來源。
|
||||||
|
# ============================================================
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name 192.168.0.130;
|
||||||
|
|
||||||
|
# 與 stage-9527 block 對齊的全域行為
|
||||||
|
client_max_body_size 100M;
|
||||||
|
proxy_read_timeout 3600s;
|
||||||
|
proxy_send_timeout 3600s;
|
||||||
|
|
||||||
|
gzip on;
|
||||||
|
gzip_min_length 1024;
|
||||||
|
gzip_types
|
||||||
|
text/plain text/css text/xml text/javascript
|
||||||
|
application/json application/javascript application/xml
|
||||||
|
image/svg+xml;
|
||||||
|
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
|
||||||
|
# ── 淺層健康檢查 ──
|
||||||
|
location = /healthz {
|
||||||
|
access_log off;
|
||||||
|
return 200 "ok\n";
|
||||||
|
add_header Content-Type text/plain;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── /api/* → api-server :3721(含 /api/pairing/exchange、models、devices)──
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://visiona_api;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_buffering off;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto http;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
|
||||||
|
proxy_no_cache 1;
|
||||||
|
proxy_cache_bypass 1;
|
||||||
|
add_header Cache-Control "no-store, no-cache, must-revalidate, private" always;
|
||||||
|
add_header Pragma "no-cache" always;
|
||||||
|
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── /storage/* → api-server :3721(presigned 下載)──
|
||||||
|
location /storage/ {
|
||||||
|
proxy_pass http://visiona_api;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_buffering off;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Proto http;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
|
||||||
|
proxy_no_cache 1;
|
||||||
|
proxy_cache_bypass 1;
|
||||||
|
add_header Cache-Control "private, no-store" always;
|
||||||
|
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── /preset-models/* → api-server :3721(B8 預設模型 .nef)──
|
||||||
|
location /preset-models/ {
|
||||||
|
proxy_pass http://visiona_api;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_buffering off;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Proto http;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
|
||||||
|
expires 1d;
|
||||||
|
add_header Cache-Control "public, max-age=86400" always;
|
||||||
|
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── /tunnel/connect — WebSocket upgrade(最關鍵)──
|
||||||
|
# 完整複製 stage-9527 block 的 WS 設定:http/1.1 + Upgrade + Connection
|
||||||
|
# + 86400s timeout + buffering off。$connection_upgrade 繼承自 http-context map。
|
||||||
|
location /tunnel/connect {
|
||||||
|
proxy_pass http://visiona_tunnel;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
|
||||||
|
proxy_read_timeout 86400s;
|
||||||
|
proxy_send_timeout 86400s;
|
||||||
|
|
||||||
|
proxy_buffering off;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto http;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Next.js hashed static(內網瀏覽器用)──
|
||||||
|
location /_next/static/ {
|
||||||
|
proxy_pass http://visiona_frontend;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── 其他靜態資源 ──
|
||||||
|
location ~* ^/(favicon\.ico|.*\.(?:png|jpg|jpeg|gif|svg|webp|ico|woff|woff2|ttf|eot))$ {
|
||||||
|
proxy_pass http://visiona_frontend;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
|
||||||
|
expires 1d;
|
||||||
|
add_header Cache-Control "public, max-age=86400" always;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── catch-all → Next.js frontend :3000(內網瀏覽器用)──
|
||||||
|
location / {
|
||||||
|
proxy_pass http://visiona_frontend;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto http;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_cache off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user