這個 Nginx proxy 安裝在 TAMS 主機上,統一處理 HTTPS/WSS,並依網域將流量送往 TAMS、HMI 與 rosbridge。

網址目的地
https://tams.hospital.internalTAMS frontend、backend、Swagger、Socket.IO
https://hmi-amr701.hospital.internalHMI frontend、backend、Swagger、Socket.IO
wss://amr701.hospital.internalAMR rosbridge

以下命令都從 PolyMedX workspace 根目錄執行。

前置條件

  • Docker 與 Docker Compose 已啟動。
  • TAMS 主機可連到 AMR 的 517330009090
  • 主機的 80443 尚未被其他程式占用;測試時也可改用 80808443
  • TAMS backend 與 frontend 使用同一個 tams-network

建立設定

make edge-init

這會建立 workspace-config/edge-proxy/.env;若檔案已存在則保留原檔,不會覆寫。

開發環境範例:

SSL_BASE_DOMAIN=hospital.internal

TAMS_DOMAIN=tams.hospital.internal
HMI_DOMAIN=hmi-amr701.hospital.internal
AMR_DOMAIN=amr701.hospital.internal

TAMS_APP_UPSTREAM=frontend:80
TAMS_API_UPSTREAM=backend:3000
TAMS_API_PREFIX=/api

AMR_IP=172.18.35.29
HMI_APP_UPSTREAM=${AMR_IP}:5173
HMI_API_UPSTREAM=${AMR_IP}:3000
ROS_BRIDGE_UPSTREAM=${AMR_IP}:9090

SELF_SIGNED=true
HSTS_MAX_AGE=0

HTTP_PORT=80
HTTPS_PORT=443

注意:

  • 本機開發 Compose 的 backend 通常使用 backend:3000
  • 正式 Compose 的 backend 使用 backend:9000
  • TAMS_API_PREFIX 必須與 backend 的 DEFAULT_API_ROUTER_PREFIX 相同。
  • 容器中的 localhost 是容器自己;主機上的服務要使用 host.docker.internal:<port>

設定測試網域

不需要先購買網域。假設 TAMS 主機 IP 是 192.168.1.100,在每台測試用戶端的 /etc/hosts 加入:

192.168.1.100 tams.hospital.internal
192.168.1.100 hmi-amr701.hospital.internal
192.168.1.100 amr701.hospital.internal

三個名稱可以指向同一個 IP;Nginx 會根據 hostname 決定 upstream。不要只使用 https://192.168.1.100,否則無法區分 TAMS、HMI 與 rosbridge。

只用 curl 測試時,可以不修改 /etc/hosts

curl -k \
  --resolve tams.hospital.internal:443:192.168.1.100 \
  https://tams.hospital.internal/

啟動 upstream 服務

開發環境:

make build-up
make ps

正式環境:

make prod-build-up
make prod-ps

另外確認 HMI frontend、HMI backend 與 rosbridge 已在 AMR 上啟動。

檢查 Docker network:

docker network inspect tams-network

TAMS backend、frontend 與 nginx-ssl 最終都應連到 tams-network

驗證設定

make edge-config
make edge-validate
  • edge-config:檢查 Compose 展開結果。
  • edge-validate:建置 image、產生 Nginx 設定並執行 nginx -t

edge-validate 不要求 upstream 已在線;它會在語法檢查時暫時使用 loopback IP。

啟動 edge proxy

make edge-build-up
make edge-ps
make edge-logs

正常啟動後測試:

make edge-smoke

從另一台電腦測試 proxy:

EDGE_PROXY_HOST=192.168.1.100 make edge-smoke

Smoke test 會檢查:

  • HTTP 是否轉址到 HTTPS。
  • TAMS 與 HMI frontend。
  • TAMS API 是否錯誤落入 SPA。
  • Socket.IO polling handshake。
  • rosbridge WSS upgrade。

信任開發憑證

SELF_SIGNED=true 產生的是開發憑證。瀏覽器出現 ERR_CERT_AUTHORITY_INVALID 是正常現象;憑證必須安裝在開啟網頁的用戶端,而不只是 TAMS 主機。

macOS 先確認憑證:

openssl x509 \
  -in workspace-config/edge-proxy/certs/server.crt \
  -noout -subject -issuer -dates -ext subjectAltName -fingerprint -sha256

確認是自己產生的憑證後,加入 System Keychain:

sudo security add-trusted-cert \
  -d \
  -r trustRoot \
  -k /Library/Keychains/System.keychain \
  workspace-config/edge-proxy/certs/server.crt

完全關閉並重新開啟 Chrome,再訪問:

https://tams.hospital.internal

不要在不確定來源時信任憑證。正式環境應使用醫院內部 CA 或可信任 CA 簽發的憑證。

常用命令

命令用途
make edge-init安全建立 .env
make edge-config展開 Compose 設定
make edge-validate建置並執行 nginx -t
make edge-build只建置 image,不啟動服務
make edge-up啟動既有 image
make edge-build-up建置並啟動
make edge-restart重新啟動
make edge-down停止並移除 container
make edge-ps查看狀態
make edge-logs持續顯示 nginx-ssl log
make edge-logs SERVICE=<服務名稱>持續顯示指定服務的 log
make edge-smoke測試 proxy 與 upstream 路由

若正式設定放在 workspace 外:

make edge-validate EDGE_ENV=/etc/polymedx/edge-proxy.env
make edge-build-up EDGE_ENV=/etc/polymedx/edge-proxy.env

常見問題

host not found in upstream "backend"

表示 Nginx 無法透過 Docker DNS 找到 backend。依序檢查:

make ps
docker network inspect tams-network
make edge-config
make edge-restart
make edge-logs

確認 backend 已啟動並加入 tams-network。若 backend 在主機上,改用:

TAMS_API_UPSTREAM=host.docker.internal:9000

若 backend 在另一台主機,使用可達 IP:

TAMS_API_UPSTREAM=192.168.1.20:9000

502 Bad Gateway

DNS 已解析,但 upstream 沒有回應。檢查 IP、port、防火牆以及服務實際監聽位置。開發環境常見原因是把 backend:3000 誤設為 backend:9000

ERR_CERT_AUTHORITY_INVALID

網域通常沒有問題,而是用戶端尚未信任 self-signed certificate。依「信任開發憑證」章節安裝,或改用醫院 CA 憑證。

正式環境

Sectigo 憑證檢查結果

目前取得的三個檔案是一組完整、可用的 Sectigo 憑證材料:

檔案內容
STAR_csh.org.tw.crt*.csh.org.tw 網站憑證,共 1 張
SOV-Bundle.crtIntermediate CA 與 Root CA,共 2 張
STAR_csh.org.tw.key與網站憑證匹配的 private key

完整憑證鏈已通過驗證,鏈結如下:

*.csh.org.tw
Sectigo RSA Organization Validation Secure Server CA
USERTrust RSA Certification Authority

網站憑證有效期限至 2026-10-02,應在到期前完成換發與部署。這是公開 CA 憑證;主流作業系統與瀏覽器通常已信任 USERTrust Root CA,不需要像 self-signed certificate 一樣手動安裝 Root CA。

修正 private key 權限

若三個原始檔案目前都是 666-rw-rw-rw-),代表同一台主機的其他使用者可以讀取或修改 private key,必須先修正:

chmod 600 STAR_csh.org.tw.key
chmod 644 STAR_csh.org.tw.crt
chmod 644 SOV-Bundle.crt

STAR_csh.org.tw.key 沒有 passphrase 時尤其依賴檔案權限保護。不要將 private key、憑證或 .env 提交到 Git,也不要把 private key 的內容貼到 log、工單或聊天訊息。

建立 Nginx full chain

SOV-Bundle.crt 同時包含 Intermediate CA 與 Root CA。Nginx 提供給用戶端的 server.crt 一般只需要依序包含:

網站憑證 + Intermediate CA

不需要把 Root CA 一併送出。先從 bundle 取出第一張 Intermediate CA:

awk '
  /-----BEGIN CERTIFICATE-----/ { count++ }
  count == 1 { print }
  /-----END CERTIFICATE-----/ && count == 1 { exit }
' SOV-Bundle.crt > sectigo-intermediate.crt

建立 Nginx 使用的 full chain,並複製 private key:

cat STAR_csh.org.tw.crt \
    sectigo-intermediate.crt \
    > server.crt

cp STAR_csh.org.tw.key server.key
chmod 600 server.key
chmod 644 server.crt

驗證網站憑證能否透過 bundle 建立信任鏈:

openssl verify \
  -CAfile SOV-Bundle.crt \
  STAR_csh.org.tw.crt

預期結果為:

STAR_csh.org.tw.crt: OK

安全存放與掛載

正式主機建議將部署用憑證放在 workspace 外的 root 管理目錄:

/etc/polymedx/tls/server.crt
/etc/polymedx/tls/server.key

目錄與 private key 應限制存取,並將 Docker volume 設為唯讀:

volumes:
  - /etc/polymedx/tls:/etc/nginx/certs:ro

主機路徑是 /etc/polymedx/tls;以下 SSL_CERTSSL_KEY 則是 Nginx 容器內看到的路徑。

Edge proxy 設定

正式 .env 範例:

SSL_BASE_DOMAIN=csh.org.tw

TAMS_DOMAIN=tams.csh.org.tw
HMI_DOMAIN=hmi-amr701.csh.org.tw
AMR_DOMAIN=amr701.csh.org.tw

SSL_CERT=/etc/nginx/certs/server.crt
SSL_KEY=/etc/nginx/certs/server.key

SELF_SIGNED=false
HSTS_MAX_AGE=0

先保持 HSTS_MAX_AGE=0,檢查 Compose 展開結果、Nginx 設定與實際憑證鏈後再啟用長期 HSTS:

make edge-config
make edge-validate
make edge-build-up
make edge-ps
make edge-logs

確認所有正式網域都能以可信任憑證正常連線後,才考慮改成:

HSTS_MAX_AGE=31536000

HSTS 會讓瀏覽器在有效期間內強制使用 HTTPS;憑證或 HTTPS 設定若仍有問題,過早啟用會增加復原難度。

DNS 與院外測試

正式環境應由醫院 IT 將 DNS 指向 TAMS edge proxy,例如:

*.csh.org.tw -> <TAMS host IP>

正式 DNS 尚未切換時,可先在測試機的 /etc/hosts 將三個名稱指向 proxy IP:

192.168.1.100 tams.csh.org.tw
192.168.1.100 hmi-amr701.csh.org.tw
192.168.1.100 amr701.csh.org.tw

測試公開 CA 憑證時不要使用 -k,否則會略過憑證驗證:

curl \
  --resolve tams.csh.org.tw:443:192.168.1.100 \
  https://tams.csh.org.tw/

只要 server.crt 正確包含 Intermediate CA,而且測試機的公開 Root CA trust store 正常,即使測試機沒有外網,也應能完成 TLS 信任鏈驗證。

架構圖

flowchart LR
    Browser["瀏覽器"] ==>|"HTTPS/WSS 加密"| Nginx

        Nginx["Nginx Edge Proxy<br/>*.csh.org.tw"]

    subgraph Docker["TAMS Docker Network"]
        Frontend["TAMS Frontend"]
        Backend["TAMS Backend"]
        RMF["RMF API"]
        Redis["Redis"]
    end


    MongoDB["MongoDB"]

    subgraph AMRHost["AMR 主機"]
        HMI["HMI Frontend/Backend"]
        Rosbridge["rosbridge"]
    end

    Nginx -.->|"HTTP"| Frontend
    Nginx -.->|"HTTP"| Backend
    Nginx -.->|"HTTP"| HMI
    Nginx -.->|"WebSocket"| Rosbridge

    Backend -.->|"HTTP"| RMF
    Backend -.->|"Redis Protocol"| Redis
    Backend -->|"MongoDB Protocol"| MongoDB

    classDef secure fill:#d8f3dc,stroke:#2d6a4f
    classDef plain fill:#fff3bf,stroke:#e67700
    classDef optional fill:#e7f5ff,stroke:#1971c2

    class Nginx secure
    class Frontend,Backend,RMF,Redis,HMI,Rosbridge plain
    class MongoDB optional

重點:

目前 TLS 加密只涵蓋「用戶端到 Nginx Edge Proxy」這一段;Nginx 完成 TLS termination 後,到各 upstream 仍使用 HTTP/WS 明文連線。

  • 瀏覽器到 Nginx:HTTPS/WSS 加密。
  • Nginx 到各服務:目前為 HTTP/WS 明文。
  • TAMS Backend 到 RMF API、Redis:Docker Network 內明文。
  • MongoDB:是否啟用 TLS 由連線設定與 MongoDB Server 共同決定。