Linux Capabilities 入門教程:概念篇

原文連接:Linux Capabilities 入門教程:概念篇html

Linux 是一種安全的操做系統,它把全部的系統權限都賦予了一個單一的 root 用戶,只給普通用戶保留有限的權限。root 用戶擁有超級管理員權限,能夠安裝軟件、容許某些服務、管理用戶等。linux

做爲普通用戶,若是想執行某些只有管理員纔有權限的操做,之前只有兩種辦法:一是經過 sudo 提高權限,若是用戶不少,配置管理和權限控制會很麻煩;二是經過 SUID(Set User ID on execution)來實現,它可讓普通用戶容許一個 owner 爲 root 的可執行文件時具備 root 的權限。nginx

SUID 的概念比較晦澀難懂,舉個例子就明白了,以經常使用的 passwd 命令爲例,修改用戶密碼是須要 root 權限的,但普通用戶卻能夠經過這個命令來修改密碼,這就是由於 /bin/passwd 被設置了 SUID 標識,因此普通用戶執行 passwd 命令時,進程的 owner 就是 passwd 的全部者,也就是 root 用戶。git

SUID 雖然能夠解決問題,但卻帶來了安全隱患。當運行設置了 SUID 的命令時,一般只是須要很小一部分的特權,可是 SUID 給了它 root 具備的所有權限。這些可執行文件是黑客的主要目標,若是他們發現了其中的漏洞,就很容易利用它來進行安全攻擊。簡而言之,SUID 機制增大了系統的安全攻擊面。github

爲了對 root 權限進行更細粒度的控制,實現按需受權,Linux 引入了另外一種機制叫 capabilitiesdocker

1. Linux capabilities 是什麼?

Capabilities 機制是在 Linux 內核 2.2 以後引入的,原理很簡單,就是將以前與超級用戶 root(UID=0)關聯的特權細分爲不一樣的功能組,Capabilites 做爲線程(Linux 並不真正區分進程和線程)的屬性存在,每一個功能組均可以獨立啓用和禁用。其本質上就是將內核調用分門別類,具備類似功能的內核調用被分到同一組中。shell

這樣一來,權限檢查的過程就變成了:在執行特權操做時,若是線程的有效身份不是 root,就去檢查其是否具備該特權操做所對應的 capabilities,並以此爲依據,決定是否能夠執行特權操做。安全

Capabilities 能夠在進程執行時賦予,也能夠直接從父進程繼承。因此理論上若是給 nginx 可執行文件賦予了 CAP_NET_BIND_SERVICE capabilities,那麼它就能以普通用戶運行並監聽在 80 端口上。微信

capability 名稱
描述
CAPAUDITCONTROL
啓用和禁用內核審計;改變審計過濾規則;檢索審計狀態和過濾規則
CAPAUDITREAD
容許經過 multicast netlink 套接字讀取審計日誌
CAPAUDITWRITE
將記錄寫入內核審計日誌
CAPBLOCKSUSPEND
使用能夠阻止系統掛起的特性
CAP_CHOWN
修改文件全部者的權限
CAPDACOVERRIDE
忽略文件的 DAC 訪問限制
CAPDACREAD_SEARCH
忽略文件讀及目錄搜索的 DAC 訪問限制
CAP_FOWNER
忽略文件屬主 ID 必須和進程用戶 ID 相匹配的限制
CAP_FSETID
容許設置文件的 setuid 位
CAPIPCLOCK
容許鎖定共享內存片斷
CAPIPCOWNER
忽略 IPC 全部權檢查
CAP_KILL
容許對不屬於本身的進程發送信號
CAP_LEASE
容許修改文件鎖的 FL_LEASE 標誌
CAPLINUXIMMUTABLE
容許修改文件的 IMMUTABLE 和 APPEND 屬性標誌
CAPMACADMIN
容許 MAC 配置或狀態更改
CAPMACOVERRIDE
忽略文件的 DAC 訪問限制
CAP_MKNOD
容許使用 mknod() 系統調用
CAPNETADMIN
容許執行網絡管理任務
CAPNETBIND_SERVICE 容許綁定到小於 1024 的端口
CAPNETBROADCAST
容許網絡廣播和多播訪問
CAPNETRAW
容許使用原始套接字
CAP_SETGID
容許改變進程的 GID
CAP_SETFCAP
容許爲文件設置任意的 capabilities
CAP_SETPCAP
參考 capabilities man page
CAP_SETUID
容許改變進程的 UID
CAPSYSADMIN
容許執行系統管理任務,如加載或卸載文件系統、設置磁盤配額等
CAPSYSBOOT
容許從新啓動系統
CAPSYSCHROOT
容許使用 chroot() 系統調用
CAPSYSMODULE
容許插入和刪除內核模塊
CAPSYSNICE
容許提高優先級及設置其餘進程的優先級
CAPSYSPACCT
容許執行進程的 BSD 式審計
CAPSYSPTRACE
容許跟蹤任何進程
CAPSYSRAWIO
容許直接訪問 /devport、/dev/mem、/dev/kmem 及原始塊設備
CAPSYSRESOURCE
忽略資源限制
CAPSYSTIME
容許改變系統時鐘
CAPSYSTTY_CONFIG
容許配置 TTY 設備
CAP_SYSLOG
容許使用 syslog() 系統調用
CAPWAKEALARM
容許觸發一些能喚醒系統的東西(好比 CLOCKBOOTTIMEALARM 計時器)

2. capabilities 的賦予和繼承

Linux capabilities 分爲進程 capabilities 和文件 capabilities。對於進程來講,capabilities 是細分到線程的,即每一個線程能夠有本身的capabilities。對於文件來講,capabilities 保存在文件的擴展屬性中。網絡

下面分別介紹線程(進程)的 capabilities 和文件的 capabilities。

線程的 capabilities

每個線程,具備 5 個 capabilities 集合,每個集合使用 64 位掩碼來表示,顯示爲 16 進制格式。這 5 個 capabilities 集合分別是:

  • Permitted
  • Effective
  • Inheritable
  • Bounding
  • Ambient

每一個集合中都包含零個或多個 capabilities。這5個集合的具體含義以下:

Permitted

定義了線程可以使用的 capabilities 的上限。它並不使能線程的 capabilities,而是做爲一個規定。也就是說,線程能夠經過系統調用 capset() 來從 EffectiveInheritable 集合中添加或刪除 capability,前提是添加或刪除的 capability 必須包含在 Permitted 集合中(其中 Bounding 集合也會有影響,具體參考下文)。 若是某個線程想向 Inheritable 集合中添加或刪除 capability,首先它的 Effective 集合中得包含 CAP_SETPCAP 這個 capabiliy。

Effective

內核檢查線程是否能夠進行特權操做時,檢查的對象即是 Effective 集合。如以前所說,Permitted 集合定義了上限,線程能夠刪除 Effective 集合中的某 capability,隨後在須要時,再從 Permitted 集合中恢復該 capability,以此達到臨時禁用 capability 的功能。

Inheritable

當執行exec() 系統調用時,可以被新的可執行文件繼承的 capabilities,被包含在 Inheritable 集合中。這裏須要說明一下,包含在該集合中的 capabilities 並不會自動繼承給新的可執行文件,即不會添加到新線程的 Effective 集合中,它只會影響新線程的 Permitted 集合。

Bounding

Bounding 集合是 Inheritable 集合的超集,若是某個 capability 不在 Bounding 集合中,即便它在 Permitted 集合中,該線程也不能將該 capability 添加到它的 Inheritable 集合中。

Bounding 集合的 capabilities 在執行 fork() 系統調用時會傳遞給子進程的 Bounding 集合,而且在執行 execve 系統調用後保持不變。

  • 當線程運行時,不能向 Bounding 集合中添加 capabilities。
  • 一旦某個 capability 被從 Bounding 集合中刪除,便不能再添加回來。
  • 將某個 capability 從 Bounding 集合中刪除後,若是以前 Inherited 集合包含該 capability,將繼續保留。但若是後續從 Inheritable 集合中刪除了該 capability,便不能再添加回來。

Ambient

Linux 4.3 內核新增了一個 capabilities 集合叫 Ambient ,用來彌補 Inheritable 的不足。Ambient 具備以下特性:

  • PermittedInheritable 未設置的 capabilities,Ambient 也不能設置。
  • PermittedInheritable 關閉某權限(好比 CAP_SYS_BOOT)後,Ambient 也隨之關閉對應權限。這樣就確保了下降權限後子進程也會下降權限。
  • 非特權用戶若是在 Permitted 集合中有一個 capability,那麼能夠添加到 Ambient 集合中,這樣它的子進程即可以在 AmbientPermittedEffective 集合中獲取這個 capability。如今不知道爲何也不要緊,後面會經過具體的公式來告訴你。

Ambient 的好處顯而易見,舉個例子,若是你將 CAP_NET_ADMIN 添加到當前進程的 Ambient 集合中,它即可以經過 fork()execve() 調用 shell 腳原本執行網絡管理任務,由於 CAP_NET_ADMIN 會自動繼承下去。

文件的 capabilities

文件的 capabilities 被保存在文件的擴展屬性中。若是想修改這些屬性,須要具備 CAP_SETFCAP 的 capability。文件與線程的 capabilities 共同決定了經過 execve() 運行該文件後的線程的 capabilities。

文件的 capabilities 功能,須要文件系統的支持。若是文件系統使用了 nouuid 選項進行掛載,那麼文件的 capabilities 將會被忽略。

相似於線程的 capabilities,文件的 capabilities 包含了 3 個集合:

  • Permitted
  • Inheritable
  • Effective

這3個集合的具體含義以下:

Permitted

這個集合中包含的 capabilities,在文件被執行時,會與線程的 Bounding 集合計算交集,而後添加到線程的 Permitted 集合中。

Inheritable

這個集合與線程的 Inheritable 集合的交集,會被添加到執行完 execve() 後的線程的 Permitted 集合中。

Effective

這不是一個集合,僅僅是一個標誌位。若是設置開啓,那麼在執行完 execve() 後,線程 Permitted 集合中的 capabilities 會自動添加到它的 Effective 集合中。對於一些舊的可執行文件,因爲其不會調用 capabilities 相關函數設置自身的 Effective 集合,因此能夠將可執行文件的 Effective bit 開啓,從而能夠將 Permitted 集合中的 capabilities 自動添加到 Effective 集合中。

詳情請參考 Linux capabilities 的 man page

3. 運行 execve() 後 capabilities 的變化

上面介紹了線程和文件的 capabilities,大家可能會以爲有些抽象難懂。下面經過具體的計算公式,來講明執行 execve() 後 capabilities 是如何被肯定的。

咱們用 P 表明執行 execve() 前線程的 capabilities,P' 表明執行 execve() 後線程的 capabilities,F 表明可執行文件的 capabilities。那麼:

P'(ambient) = (file is privileged) ? 0 : P(ambient)

P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & P(bounding))) | P'(ambient)

P'(effective)   = F(effective) ? P'(permitted) : P'(ambient)

P'(inheritable) = P(inheritable) [i.e., unchanged]

P'(bounding) = P(bounding) [i.e., unchanged]

咱們一條一條來解釋:

  • 若是用戶是 root 用戶,那麼執行 execve() 後線程的 Ambient 集合是空集;若是是普通用戶,那麼執行 execve() 後線程的 Ambient 集合將會繼承執行 execve() 前線程的 Ambient 集合。
  • 執行 execve() 前線程的 Inheritable 集合與可執行文件的 Inheritable 集合取交集,會被添加到執行 execve() 後線程的 Permitted 集合;可執行文件的 capability bounding 集合與可執行文件的 Permitted 集合取交集,也會被添加到執行 execve() 後線程的 Permitted 集合;同時執行 execve() 後線程的 Ambient 集合中的 capabilities 會被自動添加到該線程的 Permitted 集合中。
  • 若是可執行文件開啓了 Effective 標誌位,那麼在執行完 execve() 後,線程 Permitted 集合中的 capabilities 會自動添加到它的 Effective 集合中。
  • 執行 execve() 前線程的 Inheritable 集合會繼承給執行 execve() 後線程的 Inheritable 集合。

這裏有幾點須要着重強調:

  1. 上面的公式是針對系統調用 execve() 的,若是是 fork(),那麼子線程的 capabilities 信息徹底複製父進程的 capabilities 信息。
  2. 可執行文件的 Inheritable 集合與線程的 Inheritable 集合並無什麼關係,可執行文件 Inheritable 集合中的 capabilities 不會被添加到執行 execve() 後線程的 Inheritable 集合中。若是想讓新線程的 Inheritable 集合包含某個 capability,只能經過 capset() 將該 capability 添加到當前線程的 Inheritable 集合中(由於 P'(inheritable) = P(inheritable))。
  3. 若是想讓當前線程 Inheritable 集合中的 capabilities 傳遞給新的可執行文件,該文件的 Inheritable 集合中也必須包含這些 capabilities(由於 P'(permitted)   = (P(inheritable) & F(inheritable))|...)。
  4. 將當前線程的 capabilities 傳遞給新的可執行文件時,僅僅只是傳遞給新線程的 Permitted 集合。若是想讓其生效,新線程必須經過 capset() 將 capabilities 添加到 Effective 集合中。或者開啓新的可執行文件的 Effective 標誌位(由於 P'(effective)   = F(effective) ? P'(permitted) : P'(ambient))。
  5. 在沒有 Ambient 集合以前,若是某個腳本不能調用 capset(),但想讓腳本中的線程都能得到該腳本的 Permitted 集合中的 capabilities,只能將 Permitted 集合中的 capabilities 添加到 Inheritable 集合中(P'(permitted)  = P(inheritable) & F(inheritable)|...),同時開啓 Effective 標誌位(P'(effective)   = F(effective) ? P'(permitted) : P'(ambient))。有 有 Ambient 集合以後,事情就變得簡單多了,後續的文章會詳細解釋。
  6. 若是某個 UID 非零(普通用戶)的線程執行了 execve(),那麼 PermittedEffective 集合中的 capabilities 都會被清空。
  7. 從 root 用戶切換到普通用戶,那麼 PermittedEffective 集合中的 capabilities 都會被清空,除非設置了 SECBITKEEPCAPS 或者更寬泛的 SECBITNOSETUID_FIXUP。

關於上述計算公式的邏輯流程圖以下所示(不包括 Ambient 集合):

4. 簡單示例

下面咱們用一個例子來演示上述公式的計算邏輯,以 ping 文件爲例。若是咱們將 CAP_NET_RAW capability添加到 ping 文件的 Permitted 集合中(F(Permitted)),它就會添加到執行後的線程的 Permitted 集合中(P'(Permitted))。因爲 ping 文件具備 capabilities 意識,即可以調用 capset()capget() ,它在運行時會調用 capset()CAP_NET_RAW capability 添加到線程的 Effective 集合中。

換句話說,若是可執行文件不具備 capabilities 意識,咱們就必需要開啓 Effective 標誌位(F(Effective)),這樣就會將該 capability 自動添加到線程的 Effective 集合中。具備capabilities 意識的可執行文件更安全,由於它會限制線程使用該 capability 的時間。

咱們也能夠將 capabilities 添加到文件的 Inheritable 集合中,文件的 Inheritable 集合會與當前線程的 Inheritable 集合取交集,而後添加到新線程的 Permitted 集合中。這樣就能夠控制可執行文件的運行環境。

看起來頗有道理,但有一個問題:若是可執行文件的有效用戶是普通用戶,且沒有 Inheritable 集合,即 F(inheritable) = 0,那麼 P(inheritable) 將會被忽略(P(inheritable) & F(inheritable))。因爲絕大多數可執行文件都是這種狀況,所以 Inheritable 集合的可用性受到了限制。咱們沒法讓腳本中的線程自動繼承該腳本文件中的 capabilities,除非讓腳本具備 capabilities 意識

要想改變這種情況,可使用 Ambient 集合。Ambient 集合會自動從父線程中繼承,同時會自動添加到當前線程的 Permitted 集合中。舉個例子,在一個 Bash 環境中(例如某個正在執行的腳本),該環境所在的線程的 Ambient 集合中包含 CAP_NET_RAW capability,那麼在該環境中執行 ping 文件能夠正常工做,即便該文件是普通文件(沒有任何 capabilities,也沒有設置 SUID)。

5. 終極案例

最後拿 docker 舉例,若是你使用普通用戶來啓動官方的 nginx 容器,會出現如下錯誤:

bind() to 0.0.0.0:80 failed (13: Permission denied)複製代碼

由於 nginx 進程的 Effective 集合中不包含 CAP_NET_BIND_SERVICE capability,且不具備 capabilities 意識(普通用戶),因此啓動失敗。要想啓動成功,至少須要將該 capability 添加到 nginx 文件的 Inheritable 集合中,同時開啓 Effective 標誌位,而且在 Kubernetes Pod 的部署清單中的 securityContext --> capabilities 字段下面添加 NET_BIND_SERVICE(這個 capability 會被添加到 nginx 進程的 Bounding 集合中),最後還要將 capability 添加到 nginx 文件的 Permitted 集合中。如此一來就大功告成了,參考公式:P'(permitted) = ...|(F(permitted) & P(bounding)))|...P'(effective) = F(effective) ? P'(permitted) : P'(ambient)

若是容器開啓了 securityContext/allowPrivilegeEscalation,上述設置仍然能夠生效。若是 nginx 文件具備 capabilities 意識,那麼只須要將 CAP_NET_BIND_SERVICE capability 添加到它的 Inheritable 集合中就能夠正常工做了。

固然了,除了上述使用文件擴展屬性的方法外,還可使用 Ambient 集合來讓非 root 容器進程正常工做,但 Kubernetes 目前還不支持這個屬性,具體參考 Kubernetes 項目的 issue

雖然 Kubernetes 官方不支持,但咱們能夠本身來實現,具體實現方式能夠關注我後續的文章。

6. 參考資料

微信公衆號

掃一掃下面的二維碼關注微信公衆號,在公衆號中回覆◉加羣◉便可加入咱們的雲原生交流羣,和孫宏亮、張館長、陽明等大佬一塊兒探討雲原生技術

相關文章
相關標籤/搜索