Credentials、備份還原、Projects 與資安硬化
把 flow、credentials(認證資料)、設定、套件清冊與解密材料分層保護,並以停用所有外部副作用的隔離還原演練,證明備份真的能用。本章只做離線檢查,不連線、不部署。
為何需要本章
備份不是把 flows.json 複製出去就完成,資安也不是只替編輯器加一道登入。這個 Add-on 同時持有 flow、加密後的 credentials、Home Assistant 與 Supervisor 能力,還能存取多個映射目錄與主機網路。任何一層外洩、誤刪或錯配,都可能讓自動化停擺或把原本受控的能力交給不受信任的節點。
本章以 Add-on 22.0.1、內嵌 Node-RED 5.0.2 與 HA WebSocket nodes 0.80.3 為固定邊界。你會建立「資料、金鑰、版本、暴露面、權限」五層威脅模型,並用唯讀清冊與離線還原演練確認可復原性。涉及網路、Home Assistant、檔案寫入、套件安裝或硬體的範例都只作辨識,不會在本章執行。
核心觀念
五層威脅模型
| 層級 | 主要資產 | 典型失效 | 最低控制 |
|---|---|---|---|
| 資料 | flows、credentials、settings、context | 遺失、損毀、錯版覆蓋 | 加密備份、保留週期、還原演練 |
| 金鑰 | credential_secret、Project credential secret、TLS 私鑰 | 與資料一起外洩,或遺失而無法解密 | 分離保管、不可任意輪替、雙人取用紀錄 |
| 版本 | Add-on、Node-RED、節點與套件版本 | 還原後出現 unknown node 或 schema 不相容 | 保存固定版本清冊與套件來源 |
| 暴露面 | Ingress、direct port、HTTP、Dashboard、MQTT | 把編輯器登入錯當成所有 endpoint 的驗證 | 逐 route 驗證 auth、TLS 與來源限制 |
| 權限 | Supervisor、HA API、host network、UART、映射目錄 | 第三方節點取得超出用途的能力 | 最少節點、最少目的地、最少可寫路徑 |
資料路徑要連同語意一起備份
Add-on wrapper 固定 userDir=/config/、nodesDir=/config/nodes、flowFile=flows.json,後端只聽 127.0.0.1:46836,flow HTTP root 固定為 /endpoint。因此「設定目錄」至少涵蓋 /config/flows.json、同目錄的 credentials 檔、/config/settings.js、/config/package.json、/config/nodes/ 與可能存在的 context storage。實際 credentials 檔名由 flow 檔名推導,不應只靠猜檔名挑選備份。
addon_config:rw 將這些持久資料映射到 /config。homeassistant_config:rw、media:rw、share:rw 是額外能力,不代表 Node-RED 必然會寫入;ssl 掛載供 direct TLS 讀取,內含私鑰時更不能複製到 flow、log 或一般共享區。
建立可還原且不觸發動作的流程
- 建立唯讀資產清冊。
記錄 Add-on 22.0.1、Node-RED 5.0.2、HA WebSocket 0.80.3、每個額外套件的固定版本、Projects 是否啟用、context store,以及哪些映射目錄真的被 flow 使用。不要在清冊放 token、密碼、私鑰、真實 URL、entity/device/area ID、MQTT broker、client ID 或 topic。
- 分開封存資料與解密材料。
使用受控的 Home Assistant 備份機制保存 Add-on 資料;另在核准的秘密管理處保存
credential_secret。Project 若啟用,另保存該 Project 的 credential secret。兩份材料不可只存在同一部主機,也不可提交到 Git。 - 確認備份排除與重建來源。
Add-on manifest 明確以
backup_exclude排除node_modules。所以要保存package.json、自訂npm_packages與system_packages的固定版本/名稱清冊;不要宣稱備份含完整 dependency tree。 - 先在隔離環境做還原演練。
隔離環境不得連到正式 HA、MQTT、HTTP、TCP、UDP、WebSocket、資料庫或 UART。所有 Action、API、Fire Event、Update Config、檔案輸出、email、Cast、Modbus、serial 與 network 節點保持停用;自動排程也先停用。
- 以唯讀檢查驗證。
比對 flow 數量、節點 type、套件版本、credentials 是否能解密,以及 settings 與 context store 是否存在。只使用手動 Inject 接純資料處理鏈,Debug 限定必要欄位;不 Deploy 任何對外或寫入節點。
- 記錄演練結果與復原點。
記下備份時間、還原版本、缺少的 dependency、驗證者及回復方式,不記秘密值。只有隔離驗證通過,才另行排定正式變更窗口;本章不執行正式還原、網路連線或 Deploy。
章末離線驗收
| 產物 | 預期內容 | 失敗即停止 |
|---|---|---|
| 復原清冊 | Add-on/Node-RED/HA WebSocket 與額外套件版本、flow/node type 數量、備份 hash、三個秘密管理參照欄位、缺少的 dependencies,以及外部與寫入 I/O 啟用數為 0 | 版本或數量無法比對、hash/參照缺失、credentials 無法解密、unknown node 未釐清,或任何正式 HA/MQTT/HTTP/檔案/硬體 I/O 可作用 |
Credentials、匯出與金鑰生命週期
credential_secret 必須可恢復且維持不變
Node-RED 以秘密金鑰加密儲存 credentials。Add-on 的 credential_secret 會映射成 runtime 的 credentialSecret。官方文件明確要求安全保存;設定後不可任意更換,否則既有 credentials 將無法解密。這不代表加密檔可以公開:密文與金鑰都屬敏感資產,必須分開控管。
復原參照清冊(不是 Add-on 設定,不可執行)
Add-on credential secret reference = PLACEHOLDER_SECRET_RECORD_ID
Project credential secret reference = PLACEHOLDER_PROJECT_SECRET_RECORD_ID
Backup reference = PLACEHOLDER_BACKUP_ID
清冊只保存參照 ID;實際 secret 值只能存在核准的 secret manager,不得寫進這份清冊、Flow、Git、備份說明或工單。
credential_secret 雖然仍為必要選項,Node-RED 會忽略它;Project credentials 使用 Project 自己的 secret。兩者不能互相替代。匯出前做內容清理,而不是相信 credentials 面板
Flow JSON 匯出、Library 項目與 Subflow 可以帶出伺服器設定參照、環境識別資訊或節點屬性;Subflow instance properties、environment 與 parent flow context 也要列入檢查。分享前逐欄檢查並替換為完整 placeholder,例如 YOUR_HA_SERVER、YOUR_ENTITY_ID、YOUR_DEVICE_ID、YOUR_AREA_ID、YOUR_MQTT_BROKER 與 YOUR_MQTT_TOPIC。同時移除 credentials block、Authorization、Cookie、webhook ID、zone/tag、位置資料、檔案路徑和 Debug 擷取內容。
Render Template、API、Webhook、Zone、deprecated Entity,以及 Server、Device Config、Entity Config、Update Config 等 HA 節點都可能接觸敏感設定或環境 ID。尤其 Update Config 會更新 companion entity 的設定/狀態中繼資料;匯出範例時保持節點停用並只留 placeholder。更多 HA 節點角色見第 8 章,HTTP/Webhook 邊界見第 19 章。
備份內容、不可變性與還原演練
| 項目 | 固定位置或來源 | 備份/還原注意事項 |
|---|---|---|
| Flow | /config/flows.json | 先離線比對;還原後所有副作用節點仍停用。 |
| Credentials | 與 flow 檔配對的 credentials storage | 密文與正確 secret 缺一不可;不得貼到工單。 |
| Runtime 設定 | /config/settings.js | wrapper 會覆寫 backend、paths、auth、TLS 等特定鍵,不可把 upstream 預設直接套用。 |
| 節點宣告 | /config/package.json、Add-on options 清冊 | node_modules 不在備份內,應由可信固定來源重建。 |
| 本機節點 | /config/nodes | 檢查來源與程式碼;本機模組以 Node-RED process 權限執行。 |
| Context | 設定的 memory/localfilesystem store | memory 不具跨重啟持久性;磁碟 store 也不能假設每次 assignment 都立即 durable。 |
| 外部狀態 | HA、MQTT、InfluxDB、裝置端 | 不屬 Node-RED 備份;還原不得重播命令或假設外部狀態同步。 |
備份不可變性(immutability)
至少保留一份無法由 Node-RED 執行帳號改寫的版本,並使用保存期限、完整性雜湊與取用紀錄。不可變不是「永遠不刪」;它是讓遭入侵的 runtime 無法同時破壞所有復原點。定期以新備份取代到期版本,並依資料分類安全銷毀。
事故後不是立刻 Deploy
先隔離、保存時間線與已去識別的 log,再從已知良好備份重建。若 credentials、Project secret、HA token、MQTT 帳密、HTTP Basic Auth、TLS private key 或 webhook ID 可能外洩,依各系統機制撤銷並輪替。先輪替外部系統憑證,再更新 Node-RED credentials,最後以停用 flow 驗證;不要把更換 credential_secret 當成一般 token rotation,因為它會影響既有密文解密。
Projects、Git 與私人儲存庫邊界
Add-on 的 settings.js 預設 editorTheme.projects.enabled=false;Projects 不是開箱即用的備份。手動啟用後,它提供 Git-backed project 工作方式,但 Git commit、remote 可用性與完整 Add-on 備份是三件不同的事。
Projects 要區分三項資產:實際 plaintext credentials、由 Project secret 加密後且可由 Git 追蹤的 Project credential file,以及不在 repository 內的 Project secret。標準工作方式可把密文檔提交到限制成員與權限的私人 repository;加密不代表密文可以公開,而解密 secret 必須在核准的秘密管理處分開保護。
- 私人儲存庫仍不等於秘密管理:最小化成員、deploy key 和 automation token 權限;永不提交 plaintext credentials、Project secret、TLS key、備份檔、真實 endpoint 或環境 ID。
- Project secret 獨立保存:它不由 Add-on 的
credential_secret取代,也不隨 encrypted Project credential file 進 Git。還原 Project 前,先確認你持有匹配的 secret,且不要用「重設金鑰」嘗試碰運氣。 - 明確選擇密文政策:若組織允許追蹤 encrypted Project credential file,僅提交到受限私人 repository;若政策連密文也排除,Git 就無法還原 Project credentials,必須另備一份受保護且已驗證可還原的 credential-file backup。
- 提交前看差異:檢查 flow JSON、Project metadata、套件版本、密文檔政策及刪除項。匯出 scrub 規則同樣適用於 Git。
- 遠端只是一層:另保留 Add-on 備份,因為 settings、非 Project flow、context、Add-on options 與 credentials 生命週期不一定都由 Git 涵蓋。
如果 remote 無法使用,先保存本機 Project 目錄的唯讀副本與狀態,不要 force push、不要刪除 .git、不要重新初始化覆蓋歷史。待身分驗證與目標 remote 都確認後,再由有權限的維護者處理。
暴露面、能力與最小權限
Ingress、direct 與 endpoint 不是同一層
| 表面 | 22.0.1 行為 | 必要控制 |
|---|---|---|
| Ingress | ingress=true、動態 ingress_port=0、ingress_stream=true;NGINX listener 只允許 Supervisor ingress source。 | 仍要以 HA 帳號最小權限管理;不可推論 direct endpoint 也受相同保護。 |
| Direct editor | 容器 80/tcp 可映射 host 1880;/ 預設透過 Supervisor auth API。 | 只在必要時映射;leave_front_door_open 保持 false/省略。 |
| TLS | ssl 預設 true,只影響 direct listener;certfile 與 keyfile 從 /ssl 讀取。 | 驗證配對、期限與權限;這些選項不會簽發或自動續期,Ingress 也不受它控制。 |
| Flow HTTP | /endpoint/ 不走 direct editor 的 Supervisor auth;http_node 提供 Basic Auth。 | 使用獨立強密碼、TLS、輸入大小/schema 限制;不要與 editor auth 混淆。 |
| Static | http_static 只保護 static content。 | 不能外推成 HTTP nodes、editor、WebSocket 或其他 route 的保護。 |
| Dashboard | FlowFuse Dashboard 2 @flowfuse/[email protected] 是選裝且非內建,宣告 Node >=14、Node-RED >=3.0.0;route 由 Node-RED 提供。 | 相容 metadata 不取代目標環境測試;逐 route 驗證 HTTP/WebSocket auth。legacy node-red-dashboard 與 deprecated @flowforge scope 只做遷移,不新裝。 |
| MQTT/其他網路 | MQTT、HTTP、WebSocket、TCP、UDP 與 TLS config 節點都可產生外部 I/O。 | 使用 broker ACL、獨立 client、TLS 驗證、topic 最小權限;範例維持 placeholder 且節點停用。 |
Manifest grant 是風險上限,不是使用需求
hassio_api=true 搭配 hassio_role=manager 授予高權限 Supervisor 能力,homeassistant_api=true 授予 HA Core API,auth_api=true 讓 wrapper 使用 Supervisor auth。host_network=true 擴大本機網路可達面,uart=true 允許序列硬體能力。不要把 Supervisor token 或 HA token 寫入 flow/Debug;限制額外節點、外連目的地與可寫資料夾,並讓 File、File In、Watch、HTTP In/Response/Request/Proxy、MQTT、TLS config、TCP、UDP、WebSocket、serial 等節點只取得工作所需範圍。
homeassistant_config:rw、media:rw、share:rw 都可被流程寫入。檔名與路徑必須 allowlist,避免 traversal、覆寫及把秘密放進共用目錄。UART 與 Modbus 寫入可能造成實體副作用,永遠先在無硬體連線的隔離環境驗證。
自訂程式碼與預裝套件同樣在信任邊界內
啟動順序先安裝 system_packages,再以 npm install --omit=dev --omit=optional 安裝 npm_packages,最後逐行以 eval 執行 init_commands;任一步驟失敗會中止初始化。init_commands 每次啟動都會執行,必須保持空白,除非經變更審查、可重複執行且不含秘密。本章不提供也不執行任何命令。
| 固定套件 | 主要風險與限制 |
|---|---|
[email protected] | wrapper 用於 HTTP Basic Auth hash;hash 不是可公開的密碼替代物。 |
[email protected]、[email protected] | 可控制裝置或外寄;還原演練保持節點停用。 |
[email protected] | 資料庫 credentials、查詢範圍與 retention 分開控管。 |
[email protected]、[email protected] | OT/序列讀寫可能有實體副作用;搭配 UART 能力時風險更高。 |
[email protected] | 持久狀態與還原後外部真實狀態可能不一致。 |
[email protected]、[email protected] | 讀取不可信內容或探測網路;限制來源、大小、頻率與目的地。 |
[email protected]、[email protected] | Base64 不是加密,random 節點不可作密碼學秘密。 |
首次啟動會建立 /config;舊資料可由 /homeassistant/node-red 遷移,舊 dark theme 會遷移到 dark-modern,並嘗試移除三個衝突的舊 HA packages。這些是 wrapper 行為,不是你應手動執行的刪除指令。選裝擴充與 MQTT 的供應鏈界線見第 20 章。
安全地判斷備份與存取問題
- 還原後 credentials 無法使用:保持所有 flow 停用,核對備份與 secret 的版本/Project 邊界。不要反覆改
credential_secret,不要把密文或 secret 貼到 log;若無匹配 secret,依事故流程重新簽發外部 credentials。 - Ingress 可開、
/endpoint/卻未受預期驗證:先辨認你走 Ingress 或 direct host port,再以不含真實資料的唯讀請求確認 route。檢查http_node、TLS 與 port mapping;不要用leave_front_door_open當修復。 - 還原後出現 unknown nodes:不要 Deploy 或刪除 unknown node。從清冊找出匹配的固定套件,先在隔離環境重建被排除的
node_modules,確認來源與版本後再載入。 - Project remote 或 credentials 出錯:保留本機資料與 Git 狀態;核對 Project secret、remote 身分與最小權限。不要 force push、重設 secret 或重新初始化 Project。
- 懷疑權杖或私鑰外洩:先隔離 Add-on、保存已去識別時間線,再從來源系統撤銷/輪替。不要只刪 Debug 訊息就恢復服務;還要檢查備份、Git、共享目錄與外部 log 的曝露範圍。
固定版本來源
本章技術邊界只以以下精確提交與同版官方文件為準:
常見問題
事故中發現 secret-manager 參照與備份時間不相符,應該先試哪一把金鑰嗎?
Projects 的私人 Git repo 能取代 Add-on 備份嗎?
可以定期更換 credential_secret 嗎?
Ingress 登入是否自動保護 HTTP、Dashboard 與 MQTT?
/endpoint/、static、Dashboard route、WebSocket 與 MQTT 是不同邊界。逐一配置 endpoint auth、TLS、broker ACL 與來源限制。