跳到主要內容
節省工具箱Jason Tools
經驗分享

OPNsense 權限全選導致設定無法儲存

Jason Cheng 發布 瀏覽 1 次
OPNsense 權限全選導致設定無法儲存
本文目錄
  1. 問題徵狀
  2. 原因:全選勾到禁止寫入權限
  3. 判斷被鎖住的帳號
  4. 從 shell 移除禁止寫入權限
  5. 權限設定建議
  6. 結論
  7. 參考資料

你有沒有遇過,明明用 root 登入防火牆,卻連一個設定都存不了?

我在一台 OPNsense 上替 jt-ipam 建立專用帳號與 API key,準備測試憑證推送。結果建立 API key 失敗、新增帳號失敗、改使用者權限也失敗,每一個寫入動作都跳出同一個錯誤。原因是 root 自己的 Privileges 欄位被按過「全選」,連「禁止寫入設定」這個權限也一起勾上了。

這篇記錄判斷方式,以及網頁上已經沒有帳號能存設定時,怎麼從 shell 解鎖。

問題徵狀

在「系統 → 權限 → 使用者」替帳號建立 API key 時,畫面跳出錯誤:

General access
User root denied for write access (user-config-readonly set)
建立 API key 時出現 user-config-readonly 錯誤
建立 API key 時出現 user-config-readonly 錯誤

雖然出錯,瀏覽器仍然下載了一個金鑰檔,內容是:

key=undefined
secret=undefined

其他寫入動作也一樣:新增帳號、編輯 root 把 Privileges 清空後按「儲存」,都是同一個錯誤。當下的印象是「每個管理員帳號都壞了」。

把 root 的 Privileges 清空後儲存,一樣被拒絕
把 root 的 Privileges 清空後儲存,一樣被拒絕

原因:全選勾到禁止寫入權限

OPNsense 的權限清單裡,除了「允許使用某個頁面」的一般權限,還有一個作用相反的權限:

畫面上的名稱內部名稱作用
System: Deny config writeuser-config-readonly擁有這個權限的帳號,在會檢查它的存檔流程中不能儲存設定

它跟一般的頁面權限放在同一份下拉清單裡,按「全選」就會一起被勾進去。root 的 Privileges 原本應該是空的,事後看到的卻是「All pages, CrowdSec, Dashboard: ET Pro Telemetry…」一長串,看起來是被按過全選。

root 的 Privileges 掛了一長串權限
root 的 Privileges 掛了一長串權限

查 OPNsense 原始碼,判斷規則是這樣的:

  • 權限是「使用者本身或所屬群組有,就算有」
    帳號自己身上有 user-config-readonly,就算 admins 群組沒有,也照樣被判定為唯讀。群組給的 All pages 蓋不掉它。
  • 新式頁面連 root 也擋
    使用者、群組、API key 這些頁面走 MVC API,儲存前會檢查這個權限,對 root 沒有例外。只有部分舊式頁面的存檔函式會略過 root。
  • 金鑰檔是前端照樣下載的
    建立 API key 時,後端在儲存設定這一步被拒絕,金鑰沒有存進設定,也沒有回傳 key 和 secret。前端沒有檢查結果就把回應組成檔案下載,所以內容是 undefined。這個檔案沒有用,可以直接刪掉。

判斷被鎖住的帳號

錯誤訊息的格式固定是 User <帳號> denied for write access,這裡的帳號是目前登入的人,不是正在編輯的帳號。我每次失敗看到的都是 User root,代表這些操作都是以 root 身分被擋。「每個管理員都壞了」其實是一直用 root 登入、去編輯其他帳號,所以每次都被擋;其他帳號有沒有受影響,要另外檢查它們自己和群組的權限。

接著排除群組。如果這個權限掛在 admins 群組上,群組裡所有帳號都會被鎖。檢查 admins 群組,Privileges 只有「All pages」,沒有問題。

admins 群組的 Privileges 只有 All pages(成員名稱已遮蔽)
admins 群組的 Privileges 只有 All pages(成員名稱已遮蔽)

照理說,換另一個沒被鎖的管理員登入,就能把 root 改回來。我遇到的情況是另一個管理員帳號的密碼不在手邊,網頁上已經沒有能存設定的帳號,只能從 shell 處理。

從 shell 移除禁止寫入權限

主控台選單有兩個看起來相關的選項,但都不是最好的做法:

  • 3) Reset the root password 解決不了
    它會重設密碼、解除停用、還原登入 shell,驗證來源不是本機資料庫時還會詢問要不要改回 Local Database,但不會動 Privileges。
  • 13) Restore a backup 能解決,但影響範圍大
    可以還原到按全選之前的設定版本,但那之後做的其他修改也會一起消失,不建議當成第一個選擇。

比較乾淨的做法是只把這一個權限從設定檔拿掉。從主控台或 SSH 用 root 登入,選 8) Shell。shell 不受網頁權限限制。直接改設定檔時,請確認沒有其他人或自動化程式同時在存設定,避免互相覆蓋。

注意:選單 8 進入的是 csh,不支援 $( ),下面的指令用反引號。

步驟一:備份設定檔

cp -n /conf/config.xml /root/config.xml.bak-`date +%Y%m%d%H%M%S`
ls -l /root/config.xml.bak-*

步驟二:找出權限掛在哪個帳號或群組

先看有幾處、在第幾行:

grep -n "user-config-readonly" /conf/config.xml

OPNsense 25.1 起使用者頁面改成 MVC,一個帳號的所有權限寫在同一個 <priv>,用逗號隔開。我的結果只有一行:

257:      <priv>page-all,page-user-crowdsec,…,user-config-readonly,page-system-firmware-manualupdate,…</priv>

這一行看不出屬於誰。OPNsense 一定有 PHP,可以用它列出帶著這個權限的帳號與群組名稱:

php -r '$x=simplexml_load_file("/conf/config.xml"); foreach(["user","group"] as $t) foreach($x->system->$t as $n) foreach($n->priv as $p) if(is_int(strpos((string)$p,"user-config-readonly"))) echo "$t {$n->name}\n";'

我的輸出只有 user root,確認只有 root 被鎖。這段指令沒有用到 !,因為 csh 在單引號裡也會把它當成歷史指令展開。

步驟三:只移除目標帳號的權限

如果還有其他帳號或群組是特地設成唯讀的,不能整份檔案一起替換,否則它們的限制也會被拿掉。用步驟二查到的行號,把替換限定在那一行:

sed -i '' -e '257s/,user-config-readonly//' -e '257s/user-config-readonly,//' -e '257s#<priv>user-config-readonly</priv>##' /conf/config.xml

257 換成你自己查到的行號。三段規則分別處理它在清單中間或結尾、在開頭、單獨一個的情況,同一行的其他權限都保留。FreeBSD 的 sed 用 -i 時要接備份副檔名,這裡已經在步驟一手動備份,所以給空字串 '',不讓 sed 另外建立備份。

步驟四:確認並重新登入

先確認三件事:設定檔還是合法的 XML、跟備份比只差這一個權限、步驟二的指令不再列出 root。

php -r 'var_dump((bool)simplexml_load_file("/conf/config.xml"));'
diff /root/config.xml.bak-<時間> /conf/config.xml

第一行要輸出 bool(true),diff 應該只有第 257 行不同、少了 user-config-readonly。回到網頁登出再用 root 登入,就能建立帳號與 API key,下載的檔案也有真正的 key= 與 secret=。萬一改壞,用步驟一的備份蓋回去即可:

cp /root/config.xml.bak-<時間> /conf/config.xml

權限設定建議

  • 管理員帳號身上不要另外掛權限
    完整權限一律由 admins 群組提供。清除前先確認帳號確實在 admins 群組、群組有 All pages、來源網路沒有限制到你的連線位置;再逐一把個人 Privileges 按「全部清除」後儲存,每清一個就重新登入確認。
  • 不要對 Privileges 按「全選」
    需要完整權限時,把帳號加進 admins 群組即可。
  • 保留一個能救援的管理員
    確認至少有兩個管理員帳號的密碼是可用的,一個被鎖時還能用另一個修。
  • 唯讀帳號照最小權限設定
    只做讀取或備份的帳號移出 admins 群組,只給需要的頁面權限,再加上「System: Deny config write」。這樣才不會因為唯讀設定影響到管理員。這個權限擋的是儲存設定,不是所有操作,例如啟動、停止服務的 API 就不檢查它,給頁面權限時要一併考慮。
  • 要寫入設定的整合帳號不能勾禁止寫入
    API key 屬於帳號,用 API key 呼叫時一樣受這個權限限制。jt-ipam 推送憑證需要寫入設定,所以它的帳號不能勾。

結論

這次的問題只是一個「全選」,但它把 root 鎖成唯讀,而且 root 沒辦法在使用者頁面替自己解鎖。其他管理員的密碼又不在手邊,只能改從 shell 修復。

判斷的關鍵是看清楚錯誤訊息寫的是哪個帳號,它指的永遠是目前登入的人。之後我的管理員帳號一律不掛個人權限,權限統一由群組給。

參考資料

Jason Cheng

Jason Cheng

企業開源技術顧問,專注架構規劃與實務導入

關於作者 →

延伸閱讀