SELinux

1. 引言

安全增強式 Security-Enhanced Linux(SELinux)是一個在內核中實踐的強制存取控制(MAC)安全性機制。SELinux 首先在 CentOS 4 出現,並在其後的 CentOS 發行版本獲得重大改善。這些改善代表用 SELinux 解決問題的方法亦隨著時間而改變。

1.1. 部份問題所在

要更瞭解 SELinux 為何是重要、及能夠為你做甚麼,最簡單的方法就是參考一些例子。在未啟用 SELinux 的情況下,要控制用戶的檔案存取權,唯有透過酌情存取控制(DAC)方法如檔案權限或存取控制清單(ACL)。不論用戶或程式都可以將不安全的檔案權限賦予其他人,或反過來存取系統在正常運作下無須存取的部份。舉個例說:

基本上在傳統 DAC 模式只在兩個權限級別,root 及用戶,而當中無法簡易地實施最小權限的理念。很多由 root 啟動1的進程在後期會撇除它們的權限並以受限制的用戶身份來運行,有些則會在 chroot 的情況下執行,但這些安全措施都是酌情的。

1.2. 解決方案

SELinux 更能遵從最小權限的理念。在預設的 enforcing 情況下,一切均被拒絕,接著有一系列例外的政策來允許系統的每個元素(服務、程式、用戶)運作時所需的存取權。當一項服務、程式或用戶嘗試存取或修改一個它不須用的檔案或資源時,它的請求會遭拒絕,而這個行動會被記錄下來。

由於 SELinux 是在內核中實踐的,應用程式無須被特別編寫或重寫便可以採用 SELinux。當然,如果一個程式特別留意稍後所提及的 SELinux 錯誤碼,它的運作可能會更暢順。假若 SELinux 攔阻了一個行動,它會以一個標準的(至少是常規的)「拒絕存取」類錯誤來匯報給該應用程式。然而,很多應用程式不會測試系統函數所傳回的錯誤碼,因此它們也許不會輸出訊息解釋問題所在,或者輸出錯誤訊息。

不過請留意,那些理論上能提供更高安全度的案例,如:局限只有某些獲授權的程式可讀取用戶的 ~/.ssh/ 目錄,防止郵件派發程式(Mail Delivery Agent)更改擁有群組、群組設定或其它讀檔權限,或阻止瀏覧器讀取用戶的主目錄,這些都未被加進行 CentOS 6 或以下版本的 SELinux 政策內。CentOS 6 及 7 有限量地如上述局限用戶程式,但所覆蓋的範圍不及那些專屬的系統服務。要是有系統管理員想更改預設沒限制的登入設定,他們可以參閱下文的 基於角色的存取控制 部份。

2. SELinux 模式

SELinux 擁有三個基本的操作模式,當中 Enforcing 是預設的模式。此外,它還有一個 targetedmls 的修飾語。這管制 SELinux 規則的應用有多廣泛,當中 targeted 是較寬鬆的級別。

SELinux 的模式可以透過 Adminstration 選單裡的 SELinux 圖像管理介面、或者在指令行執行 system-config-selinux 來檢視及更改(SELinux 圖像管理介面是 policycoreutils-gui 套件的一部份,預設是不會被安裝的)。

較喜歡指令行的用戶可使用 sestatus 這個指令來檢視現時的 SELinux 狀況:

# sestatus
SELinux status:                 enabled
SELinuxfs mount:                /selinux
Current mode:                   enforcing
Mode from config file:          enforcing
Policy version:                 21
Policy from config file:        targeted

setenforce 這個指令可以即時切換 EnforcingPermissive 這兩個模式,但請注意這些改動在系統重新開機時不會被保留。

要令改動過渡系統開機,請在 /etc/selinux/config 內修改 SELINUX= 這一行為 enforcing、permissive 或 disabled。例如:SELINUX=permissive。

註: 當你由 Diabled 切換至 PermissiveEnforcing 模式時,我們強烈建議你重新啟動系統並重新標籤檔案系統。

3. SELinux 政策

正如我們所說,SELinux 遵從最小權限這個理念;在預設情況下一切均被拒絕,而系統的每部份有一個政策來允許它存取所需的功能。這個描述用來形容 strict(嚴格型) 政策最為貼切。不過要令這類政策適用於企業級 Linux 可能會應用到的各個環境,編寫方面是困難的。後果可能是 SELinux 會為系統管理員及用戶產生很多問題,而系統管理員索性停用 SELinux 而不解決這些問題,最後還是違背了內置的保護。

SELinux 的設計允許調配不同類型的政策。CentOS 內的預設政策是 targeted(針對型) 政策,專門「針對」和規限選定的系統進程。CentOS 4 只定義了 15 個目標(包括 http、named、dhcpd、mysqld)。隨後在 CentOS 5 這個數字已升超過 200 個目標。

系統內其它一切系統進程、用戶程式和所有自製應用程式都在 unconfined(不受規限) 的區域下運行,並且不納入 SELinux 的保護範圍內。

這樣做的目標之一,是要令每個已安裝及在開機時執行的進程,都預設在一個受規限的區域下運行。 targeted 政策在設計時儘量保護最多的主要進程而不會對用戶的經驗產生不利影響,所以多數用戶甚至乎不應察覺 SELinux 正在運行中。

4. SELinux 存取控制

CentOS 的 targeted SELinux 政策包含四種存取控制方法:

所有進程及檔案都擁有一個 SELinux 的安全性脈絡。讓我們檢視 Apache 的主頁,/var/www/html/index.html 的 SELinux 安全性脈絡來看看它們如何運作:

$ ls -Z /var/www/html/index.html -rw-r--r--  username username system_u:object_r:httpd_sys_content_t /var/www/html/index.html 

註: -Z 這個標旗在多數工具內都可用來顯示 SELinux 安全性脈絡(例如:ls -Z、 ps axZ 等)。

除了標準的檔案權限及擁有權,我們更可以看到 SELinux 脈絡欄:system_u:object_r:httpd_sys_content_t。

這是建基於「用戶:角色:類別:多層保障」。在上述例子裡,「用戶:角色:類別」欄都有顯示,而「多層保障」是隱藏的。在預設的 targeted 政策裡,類別是用來實施「強制類別」的重要欄位,在這裡它是 httpd_sys_content_t。

現在讓我們看看 Apache 網頁伺服器,httpd,這個進程的 SELinux 安全性脈絡:

$ ps axZ | grep httpd
system_u:system_r:httpd_t        3234 ?        Ss     0:00 /usr/sbin/httpd

從類別欄我們看出 Apache 在 httpd_t 這個類別區域內運行。

最後,讓我們看看位於我們的主目錄內的一個檔案的安全性脈絡:

$ ls -Z /home/username/myfile.txt
-rw-r--r--  username username user_u:object_r:user_home_t      /home/username/myfile.txt

它的類別是 user_home_t,這是位於每個戶主目錄內的檔案的預設類別。

唯有相似的類別才可互相存取,因此以 httpd_t 運行的 Apache 可以讀取擁有 httpd_sys_content_t 類別的 /var/www/html/index.html。由於 Apache 在 httpd_t 這個區域內運行但不屬 username 這個用戶,縱使 /home/username/myfile.txt 可供任何人讀取,Apache 卻無法存取該檔案,因為它的 SELinux 安全性脈絡並不是 httpd_t 類別。倘若 Apache 被人佔用,又假設它仍未取得更改 SELinux 標籤至另一個脈絡的 root 權限,它將會無法啟動 httpd_t 區域外的進程(藉此防止權限升級),或存取與 httpd_t 區域不相關的檔案。

4.1. 基於角色的存取控制(RBAC)

雖然 targeted 政策的預設值不限制登入,管理員卻可以輕易地改用 基於角色的存取控制 模式。這個模式在用戶範疇亦轉用「嚴格」模式,以便能針對個別程式。若要啟用此功能,請利用 semanage-login 為你的用戶新增登入對照:

semanage login -a -s "staff_u" -r "s0-s0:c0.c1023" <username>

semanage-login 這個指令將一位 Linux 用戶對照對 staff_u 的 SELinux 用戶,及設定 MLS/MCS 範圍為 s0-s0:c0.c1023。從此,登入後執行 id -Z 將會顯示 staff_u:staff_r:staff_t:s0-s0:c0.c1023,而不再是 unconfined_u:unconfined_r:unconfined_t:s0。雖然 staff_r 不是留作管理用的角色,但它卻容許用戶轉換至其它角色。當一位系統管理員想執行管理系統的工作時,他應該在 sudo 內以 -r 參數轉換至 sysadm_r 的角色。

sudo -r sysadm_r -i

你可以在 /etc/sudoers.d/ 內新增設定檔,自動把用戶對照至預設的管理員身份。

%wheel ALL=(ALL) TYPE=sysadm_t ROLE=sysadm_r ALL

你仍可以通過 newrole 登入為成為不受限制的用戶或轉換至不受限制的角色,但這樣做會損失局限用戶範疇帶來的益處。你亦可移除這個功能,做法是新增一個只扮演某些角色的 SELinux 用戶:

semanage user -a -R "staff_r sysadm_r system_r -r "s0-s0:c0.c1023" my_staff_u

現在以 my_staff_u 取代 semanage-login 指令中的 staff_u。當你嘗試轉換至 unconfined_r ⻆色時,便會出現 AVCSELINUX_ERR 的信息。假若系統管理員有意阻止所有不受限制用戶的登入,他可以把 __default__ 這個登入對照到一個更合適的 SELinux 用戶,做法依然是透過 semanage-login。

semanage login -m -s "user_u" -r "s0" __default__

要是一位用戶以別於慣常的角色登入,這功能就視乎登入程式。SSH 支援以另一個 SELinux 角色登入,做法是指定於登入身份內(例如:staff 用戶以 unconfined_r 角色登入):

ssh <username>/unconfined_r@hostname.net

從最小權限的角度來看,基於角色的存取控制下的嚴格模式仍未完善;利用政策分析程式進行快速搜尋,我們便會發現仍有數個受限制的程式依然能存取用戶的 SSH 私鑰。

sesearch -ACS -t ssh_home_t -c file -p read
Found 132 semantic av rules:
   allow snapperd_t file_type : file { ioctl read getattr lock open } ;
   allow oddjob_mkhomedir_t user_home_type : file { ioctl read write create getattr setattr lock append unlink link rename open } ;
   allow mplayer_t non_security_file_type : file { ioctl read getattr lock open } ;
   allow sendmail_t user_home_type : file { ioctl read getattr lock open } ;
   allow systemd_tmpfiles_t non_auth_file_type : file { ioctl read write create getattr setattr lock relabelfrom relabelto append unlink link rename open } ;
   allow login_pgm ssh_home_t : file { ioctl read getattr lock open } ;
   allow ssh_keygen_t ssh_home_t : file { ioctl read write create getattr setattr lock append unlink link rename open } ;
   allow colord_t user_home_type : file { read getattr } ;
   ... snip ...

mplayer_t 大概不必讀取 SSH 私鑰,但由於要容讓用戶能從檔案系統內的任何位置播放媒體,因此它有權限讀取被視為 non_security_file_type 的非安全性相關檔案。SELinux 的基本政策可以進一步局限這些存取權,但一如上文所說,這不是上游發行商的主要焦點。

除了嚴格模式外,基於角色的存取控制亦提供一個機制,局限用戶以 sudo 轉成 root 後可以做的事情。一般來說,以特定角色如資料庫管理員或審計員給予用戶最小權限是可取的做法,而 targeted 政策內含有一些適用於此用途的角色,它們的文檔已收錄在 政策文檔 的相關頁內。

seinfo -r

Roles: 14
   auditadm_r
   dbadm_r
   guest_r
   staff_r
   user_r
   logadm_r
   object_r
   secadm_r
   sysadm_r
   system_r
   webadm_r
   xguest_r
   nx_server_r
   unconfined_r

若要把用戶對照到其中一個管理員角色,你可利用上述的 semanage-user 指令聯繫一位新增的 SELinux 用戶到所需的角色,然後利用 semanage-login 把 Linux 的登入聯繫到該 SELinux 用戶。要是該用戶亦需要從他們的用戶範疇啟動他們所管理的系統服務(例如:從指令殼以 dbadm_r 角色啟動 mysql 供偵錯用),那麼 system_r 亦應是他們所聯系的角色之一。

semanage user -a -R "staff_r system_r auditadm_r" -r "s0-s0:c0.c1023" auditor_u
semanage login -a -s "auditor_u" -r "s0-s0:c0.c1023" <username>

4.2. 多種類保障(MCS)

多種類保障提供方法把 間隔 子集或系列與 SELinux 脈絡聯繫。targeted 政策負責把與 mcs_constrained_type 有聯系的類別進行分隔。若要明白這是如何運作,你必須懂得如何檢視 MLS 部份的安全性脈絡。那是 用戶:角色:類別 後面那部份,它包括一個範圍,所表達的是低及高安全度級別。

system_u:system_r:httpd_t:s0 - s0:c0.c5
                          ▼ ▼
                  低安全級別,高安全級別,還有是
                  已聯繫及未曾聯繫的間隔
                  間隔:c0、c1、c2、c3、c4 及 c5。

以上值得留意的就是 low 安全度別沒有間隔,還有就是兩個安全級別是相同的。第一點是 MCS 模式在 targeted 政策下的實踐細節。當計算一個與 mcs_constrained_type 有聯繫的進程的存取權時,只有高安全級別的 MCS 間隔會用來作比較。第二點是源於 MLS 並未被應用。

上述安全性脈絡的間隔部份是一個 種類範圍,但它也可以是一組以逗號分隔的種類。運用種類子集會令到脈絡廣泛地與該範圍內的種類聯系。如要理解如何運用種類子集來運算兩個進程的存取權,你必須檢視 SELinux 存取級別的 dominance 規則(唯有當來源的高安全級別 支配 目標的高安全級別)。這些規則如下(只考慮種類,不計 MLS 安全級別)

本著這些規則,我們知道擁有 c0.c5 種類子集的脈絡將會獲允許存取 c0,c3 子集的脈絡,但不獲允許存取 c0,c6c0.c1024 子集的脈絡。正由於此規則,sVirt 產生隨機的種類子集,好叫虛擬區域與區域之間不會重疊。Android 計劃亦採用同樣做法,把應用程式放進被隔離的區域內。

MCS 的其中一個例子就是利用擁有多個 vhost 的 NGINX 連接到以 httpd 區域(例如 PHP-FRM)運行的後端伺服器。正常來說,基於強制類別的規則,這些後端伺服器都可以互相更改及管理對方的區域。要是它們被聯繫到種類的話,只有當一個伺服器支配其它伺服器才能進行修改。由於 NGINX 本身屬 HTTPD 區域,它支配所有後端伺服器,因此要是 HTTPD 區域含有 c0 到 c5 等種類,我們便應該以 system_u:system_r:httpd_t:s0-s0:c0.c5 這個脈絡來執行 NGINX,好叫它能連線到上游伺服器。每個後端伺服器只需以 c0-c5 中的一個種類來運行,並擁有 system_u:system_r:httpd_t:s0-s0:c1 等脈絡。

在達至此目標前,有以下先決條件。首先,httpd_t 必須與 mcs_constrained_type 聯繫,而這個特性在 CentOS 7 上暫時只聯繫下列類別:

seinfo -xamcs_constrained_type
   mcs_constrained_type
      netlabel_peer_t
      openshift_t
      openshift_app_t
      sandbox_min_t
      sandbox_x_t
      sandbox_web_t
      sandbox_net_t
      svirt_t
      svirt_tcg_t
      svirt_lxc_net_t
      svirt_qemu_net_t
      svirt_kvm_net_t

若要在此清單上加入一個類別,你必須建立一個本地政策塊把所需的類別聯繫至該特性。這是透過 typeattribute 陳述語句,並可如此編寫:

policy_module(httpd_mcs, 1.0)
gen_require(`
    type httpd_t;
    attribute mcs_constrained_type;
')

typeattribute httpd_t mcs_constrained_type;

自訂本地政策 有關如何建立政策模塊。

當該類別與 mcs_constrained_type 聯繫後,你必須重新標籤每台後端伺服器的內容,令它們的檔案脈絡內含有適切的種類。做法是在 /etc/selinux/targeted/contexts/customizable_types 內加入檔案類別,但這個有機會在更新政策後失效。另一個做法是利用 semanage-fcontext 新增含有種類的檔案脈絡規格:

semanage fcontext -a -t httpd_sys_content_t -r "s0-s0:c1" "/srv/backend1(/.*)?"
semanage fcontext -a -t httpd_sys_content_t -r "s0-s0:c2" "/srv/backend2(/.*)?"

下一步是確保後端伺服器以正確的安全性脈絡開機。在 CentOS 7 你可以在 systemd 的項目檔加入 SELinuxContext= 這個選項來達至目標;在舊版本你可以利用 runcon 這個指令達至同一目的。

runcon "system_u:system_r:httpd_t:s0-s0:c1" "/usr/local/bin/backend-server"

至於 systemd 的項目檔:

SELinuxContext=system_u:system_r:httpd_t:s0-s0:c1

現在每個後端伺服器都會被互相隔離,但 NGINX 仍然有權管理及發放訊息給它們。

5. 排除 SELinux 疑難

你終有一天會被 SELinux 阻止你存取所需的東西,而且要解決這個問題。SELinux 拒絕某個檔案、進程或資源被存取的基要原因有數個:

頭三個情況我們可以處理,而發出警報及通告正正是第四個情況的預期表現。

日誌檔是排除任何疑難的關鍵,而 SELinux 亦不例外。SELinux 預設會透過 Linux 審計系統 auditd 將日誌寫在 /var/log/audit/audit.log 內,而該務服預設為啟用的。假若 auditd 長駐程式並未運行,信息將會被寫進 /var/log/messages。SELinux 的日誌都以 AVC 這個關鍵字作標籤,讓 grep 等程式可輕易地把它們從其它信息中過濾出來。

由 CentOS 5 起,你可以用 SELinux 排除疑難工具協助你分析日誌檔,將它們轉換為供人閱讀的格式。這個工具包含一個以可讀格式顯示信息及解決方案的圖像介面、一個桌面通報圖示、與及一個長駐進程,setroubleshootd,它負責查閱新的 SELinux AVC 警告並傳送至通報圖示。不運行 X 伺服器的用戶可設置以電郵通報。SELinux 排除疑難工具是由 setroubleshoot 套件所提供的。這個工具可以從 X 視窗圖像管理員的「系統」選單或從指令行啟動:

sealert -b 

不運行 X 伺服器的人可以透過指令行產生供人閱讀的報告:

sealert -a /var/log/audit/audit.log > /path/to/mylogfile.txt 

5.1. SELinux 與 Auditd

如上文所描述,SELinux 通過 auditd 產生訊息,有助審計系統及排除問題。審計日誌中來自 SELinux 的最常見訊息類別是 AVC。它們描述被 SELinux 安全性伺服器所拒絕(或獲允許)的操作。雖然 AVC 很普遍,它們並非唯一被 SELinux 產生及傳送到審計系統的訊息類別。

Auditd 記錄類別

描述

AVC

每當存取權獲允許或拒絕時,由內核產生的訊息。由 auditallow 政策規則所允許的存取權會產生審計訊息,而 dontaudit 規則可防止存取權被拒絕時產生訊息。

USER_AVC

類似 AVC 訊息,但它們是由採用 SELinux 安全性伺服器的用家程式所產生的。這些程式又稱為用家物件管家,當中包括 D-Bus 及 system。

SELINUX_ERR

當操作失效但 AVC 的描述有不足時,SELinux 安全性伺服器便會發出這些錯誤。這些情況多數是在 systemd 嘗試化身成一個服務時發生的,它的服務檔內含有 NoNewPrivileges=True,但 SELinux 的政策內並未將 systemd 與自己的類別聯繫起來。當一個進程過用 setcon(3) 更改它的執行緒時,這個訊息有可能會出現。如果一個程式嘗試設定無效的脈絡時,這個問題亦會出現,例如:PAM 利用 setcon(3) 更改登入用戶的區域,而錯誤設置的 SELinux 登入有可能導致 SELinux 傳回不正確的脈絡。

USER_SELINUX_ERR

類似 SELINUX_ERR,當用家物件管家出現 SELinux 的錯誤時,但會產生這類訊息。

|| MAC_POLICY_LOAD, USER_MAC_POLICY_LOAD || 載入 SELinux 政策時所產生的訊息。USER 變種亦會由用家物件管家產生來宣佈已經處理載入的政策。

MAC_CONFIG_CHANGE

當管理員利用 setsebool 更改 SELinux 兩元值時所產生的訊息。

MAC_STATUS

當 SELinux 的狀況由 enforcing 轉為 permissive 或反向轉換。

USER_ROLE_CHANGE

當用戶在登入,或通過 sudonewrole(1) 指令更改角色時所產生的訊息。只適用於 基於角色的存取控制

USER_LABEL_EXPORT

當用戶透過 CUPS 匯出有標籤的物件時所產生的日誌。

註:還有一些與 IPSec/NetLabel 相關的 SELinux 審計事件未收錄在此。這些事件計有: MAC_UNLBL_ALLOWMAC_UNLBL_STCADDMAC_UNLBL_STCDELMAC_MAP_ADDMAC_MAP_DELMAC_IPSEC_EVENTMAC_CIPSOV4_ADDMAC_CIPSOV4_DEL

縱使 sealert 對理解 AVC 記錄有一定的幫助,審計工具能更有效地為管理員展示審計日誌。ausearch 可用來搜尋日誌內的特定事件,並提供一系列處理審計記錄的選項。因此除了以 sealert 來演繹訊息,你更可利用 ausearch 來檢視 SELinux 問題的可能成因。排除問題時,我們多數會檢視 AVCUSER_AVCSELINUX_ERRUSER_SELINUX_ERRausearch 提供一個 -m 選項來指定以逗號分隔的審計記錄過濾類別,還有一個 -i 選項讓系統以個別方式演繹數目字為字串。這包括轉換 UID/GID 為用戶及群組名稱,及對照系統函式和架構為系統函式名稱:

ausearch -m AVC,USER_AVC,SELINUX_ERR,USER_SELINUX_ERR -i

若要搜尋記錄並建立一個檢查點,以便下次搜尋時不再出現已檢查的審計記錄:

ausearch --checkpoint="./audit-checkpoint" -m AVC,USER_AVC,SELINUX_ERR,USER_SELINUX_ERR -i

根據進程的 指令名稱,即是內核的工作項目的執行名稱搜尋記錄:

ausearch -c "httpd" -m AVC,USER_AVC,SELINUX_ERR,USER_SELINUX_ERR -i

尋找管理員(或程式)更改 SELinux 強制設定的事件:

ausearch -m MAC_STATUS -i

由於 dontaudit 可防止審計記錄出現於日誌內,有可能部份 AVCUSER_AVC 不獲顯示。若是要避免 dontaudit 規則隱藏這些訊息,管理員可執行 semodule -DB 來重建不包含 dontaudit 規則的 SELinux 政策。重建後,日誌內將會有更多訊息,還有一些與問題無關的記錄(noatsecurerlimitinhsiginh 是執行程式時循例必檢查的,因此一般可忽略)。檢視完被 dontaudit 隱藏的記錄後,請執行 semodule -B 重建含有 dontaudit 規則的政策。

與此剛好相反的就是透過 auditallow 記錄成功的操作。這此記錄可以通過過濾成功的操作而搜尋到(雖然這樣做亦會傳回在 permissive 模式下成功的事件):

ausearch -m AVC,USER_AVC -i --success yes

5.2. 重新標籤檔案

chcon 這個指令可以用來更改一個或多個檔案與目錄的 SELinux 安全性脈絡,正如 'chown' 或 'chmod' 可以用來更改一個檔案的擁有者或標準權限。

讓我們看一些例子。

就以 Apache 為例,假設你想修改 DocumentRoot 以另一個位置來伺服網頁,替代預設的 /var/www/html 目錄。譬如說我們在 /html 建立了一個目錄(又或者掛載點),然後在那裡建立一個 index.html 檔:

# mkdir /html
# touch /html/index.html
# ls -Z /html/index.html
-rw-r--r--  root root user_u:object_r:default_t        /html/index.html
# ls -Z | grep html
drwxr-xr-x  root root user_u:object_r:default_t        html 

我們可以見到 /html 這個目錄以及 /html/index.html 這個檔案都擁有預設的 default_t 安全性脈絡類型。如果我們開啟瀏覽器並嘗試檢視該頁,SELinux 將會正確地拒絕它們被存取並記錄錯誤,因為該目錄與檔案擁有不正確的安全性脈絡。我們須要設定供 Apache 使用的 httpd_sys_content_t 正確安全性脈絡。

# chcon -v --type=httpd_sys_content_t /html
context of /html changed to user_u:object_r:httpd_sys_content_t
# chcon -v --type=httpd_sys_content_t /html/index.html
context of /html/index.html changed to user_u:object_r:httpd_sys_content_t
# ls -Z /html/index.html
-rw-r--r--  root root user_u:object_r:httpd_sys_content_t    /html/index.html
# ls -Z | grep html
drwxr-xr-x  root root user_u:object_r:httpd_sys_content_t    html 

我們同樣也可以利用 -R 這個迴遞標旗同時將它們的脈絡設定:

# chcon -Rv --type=httpd_sys_content_t /html 

以這個方式更改安全性脈絡在系統重新開機後仍會獲保留,直至該部份檔案系統被重新標籤。這個動作也算頗常,因此正確的解決方法,就是於測試後編寫一條自訂的規則(所謂的政策模塊),並把它與本地的規則進行合併。它將會是原有的 200 多條規則外的一條規則。要作出永久性、能過渡檔案系統重新標籤的安全性脈絡改動,我們可以採用 SELinux 管理工具,或者在指令行執行 semanage 這個指令:

semanage fcontext -a -t httpd_sys_content_t "/html(/.*)?" 

如此便會將 /html 以下的一切加入 httpd_sys_content_t 這個檔案脈絡類型。

5.3. 還原預設的安全性脈絡

restorecon 這個指令可以用來還原為檔案預設的安全性脈絡。

讓我們再次以 Apache 作為範例。設假有位用戶在他的主目錄內編輯了一個 index.html 檔並將該檔案遷移(mv)至 DocumentRoot 的 /var/www/html 內。縱使複製(cp)這個指令普遍會沿用目標目錄或檔案的安全性脈絡,遷移(mv)指令則會保留來源檔的安全性脈絡。我們可以利用 chcon 這個指令來更改問題檔案的安全性脈絡,但由於這些檔案已經位於 Apache 預設的 DocumentRoot(/var/www/html)內,我們只須還原這個目錄或檔案的安全性脈絡便成了。要單單還原 index.html 檔的脈絡,我們可以利用:

# restorecon -v /var/www/html/index.html 

如果要以迴遞的方式還原整個目錄的預設安全性脈絡:

# restorecon -Rv /var/www/html 

除此之外,如果我們只想檢查 /var/www/html 目錄內有哪些檔案的安全性脈絡需要被還原,我們在採用 restorecon 時可以應用 -n 這個標旗來防止重新標籤的行動:

# restorecon -Rv -n /var/www/html 

在某些情況下,用戶也許移動了擁有 /etc/selinux/targeted/contexts/customizable_types 內的類別的檔案。restorecon 會特別對待這些類別,而 restorecon 一般是不會重新標籤它們的,除非你額外加上 -F 這個標旗。這些類別要不是與 種類 有關連(見「多重種類保障),就是用戶為內容自訂的類別。若是要重新標籤擁有自訂類別的內容,請在執行 restorecon 時加上額外標旗:

# restorecon -RvF /var/www/html 

5.4. 重新標籤整個檔案系統

有時我們也許會需要將整個檔案系統重新標籤,雖然這是在啟用已停用的 SELinux 時,或在 SELinux 政策由預設的 targeted 改為 strict 時才有真正需要。要在重新開機後自動將整個檔案系統重新標籤,請執行:

# touch /.autorelabel
# reboot 

假若一個系統在升級至 CentOS-5.2 時停用了 SELinux,然後 SELinux 被啟用,重新標籤整個檔案系統時或許會失敗。如果以上的步驟無法正確地將整個檔案系統重新標籤,請嘗試先執行 genhomedircon 這個指令:

# genhomedircon
# touch /.autorelabel
# reboot 

5.5. 允許存取某個連接埠

我們或許會想容讓 Apache 連結至某個非標準的埠並聆聽對內的連線。SELinux 的政策預設只允許每個服務存取被公認與該服務有關的連接埠。如果我們想容讓 Apache 在 tcp 埠 81 上聆聽,我們可以利用 semanage 這個指令來新增一條規則批准此事:

# semanage port -a -t http_port_t -p tcp 81 

你可以這樣令 SELinux 完整地列出每個服務可存取的連接埠:

# semanage port -l 

5.6. 在 Permissive 模式下搜集審計日誌

當一個程式重複地被 SELinux 拒絕某個操作,有時在 permissive 模式下進行偵錯會較容易。一般的做法是 setenforce 0,但這樣做會導致所有區域都進行 permissive 模式,而不限於有問題的進程。為了避免這個情況,SELinux 支援 permissive 類別,讓管理員可選擇只將一個區域放進 permissive 模式,而不是整個系統。

若要根據審計日誌的記錄來設定,請查閱 scontext 欄內的類別:

type=AVC msg=audit(1218128130.653:334): avc: denied { connectto } for pid=9111 comm="smtpd" path="/var/spool/postfix/postgrey/socket"
scontext=system_u:system_r:postfix_smtpd_t:s0 tcontext=system_u:system_r:initrc_t:s0 tclass=unix_stream_socket

這個是:postfix_smtpd_t。我們可以利用 semanage 新增一個 permissive 類別,以便能臨時允許此區域的所有操作:

semanage permissive -a postfix_smtpd_t

接著我們可觀察日誌,看看 postfix_smtpd_t 需要那些允準許才能成功地操作。完成後,我們可以再次將該類別還原為 enforcing 模式:

semanage permissive -d postfix_smtpd_t

這樣做便可避免 setenforce 0 的霸道手段,並容許系統上的其它服務繼續受惠於 SELinux 的存取控制。

5.7. 運用分析工具

很多時候當 SELinux 拒絕某個操作時,該操作其實是政策所允許的,但檔案的標籤卻不正確,或進程未轉換至正確的區域。你適宜匯報這些問題給政策的作者或維護者,但利用 setools-console 套件內的分析工具來弄個清楚亦非天方夜譚。

針對審計日誌內的某個 AVC 記錄,我們可以利用 sesearch 辨認任何允許存取所需目標的強制類別規則,與及經二元值設定後允許存取的規則。AVC 記錄中值得留意的部份包括有 scontexttcontext、`tclassdenied: <允許權> 訊息中所徵求的權限。

就以 scontext=antivirus_ttcontext=antivirus_ttclass=process 附帶 { denied: execmem  訊息的被拒 AVC 記錄為例,它很可能源自一個防毒軟件嘗試進行 JIT 編譯,而我們可辨認出允許存取權的強制類別規則及相關二元值:

sesearch -AC -s antivirus_t -t antivirus_t -c process -p execmem
Found 2 semantic av rules:
DT allow antivirus_t antivirus_t : process execmem ; [ antivirus_use_jit ]
DT allow antivirus_t antivirus_t : process execmem ; [ antivirus_use_jit ]

註: 此處所採用的選項已記載於 sesearch(1) 手冊內。-s-t 指定來源及目標類別,-c 指定 SELinux 物件級別,-p 可指定要搜尋的規則的不同權限,-A 搜尋允許的規則,而 -C 更延伸為搜尋附帶條件的充許規則。

這是一個簡單的例子。方括號內的字串是允許存取的二元值名稱,而規則的 DT 字首意味著它們現正被停用。我們可以利用 setsebool -P antivirus_use_jit=1 來啟用這些規則,但我們也許應先檢視這個二元值所允許的權限,而 sesearch 這個工具可再次為我們服務:

sesearch -AC -b antivirus_use_jit
Found 2 semantic av rules:
DT allow antivirus_t antivirus_t : process execmem ; [ antivirus_use_jit ]
DT allow antivirus_t antivirus_t : process execmem ; [ antivirus_use_jit ]

在這個例子中 antivirus_use_jit 這個二元值單單允許我們所需的存取權,但日後我們可以用這個方法,通過 sesearch 來辨認兩個提供相同存取權,但其中一個卻允許其它不理想操作的二元值。舉個例說,我們想寫進標籤為 httpd_sys_content_t 的 HTTPD 內容。建議的做法是啟用 httpd_unified 二元值,因此我們可以檢視它能如何協助我們:

sesearch -AC -b httpd_unified -c file -p write
Found 5 semantic av rules:
DT allow httpd_sys_script_t httpdcontent : file { ioctl read write create getattr setattr lock append unlink link rename open } ; [ httpd_enable_cgi httpd_unified && ]
DT allow httpd_user_script_t httpd_user_content_t : file { ioctl read write create getattr setattr lock append unlink link rename open } ; [ httpd_enable_cgi httpd_unified && ]
DT allow httpd_t httpd_sys_rw_content_t : file { ioctl read write create getattr setattr lock append unlink link rename open } ; [ httpd_enable_cgi httpd_unified && httpd_builtin_scripting && ]
DT allow httpd_user_script_t httpd_user_ra_content_t : file { ioctl read write create getattr setattr lock append unlink link rename open } ; [ httpd_enable_cgi httpd_unified && ]
DT allow httpd_t httpdcontent : file { ioctl read write create getattr setattr lock append unlink link rename open } ; [ httpd_enable_cgi httpd_unified && httpd_builtin_scripting && ]

由於只有少量與此二元值有關的規則是關乎寫入檔案的,因此要分析它所允許的是很容易。上述規則告訴我們雖然此二元值允許我們寫進 AVC 記錄內涉及的檔案,它亦容許我們讀寫本來只支援增生及唯讀的內容。從上面我們看見有一個 httpd_sys_rw_content_t 類別,根據其名稱很有機會能解決我們的問題。然而,我們亦可以從中發現 httpd_t 可寫進的檔案類別。留意 httpd 間接地透過屬性或停用的二元值有可能寫進很多不同類別的內容,因此我們利用 -R 表示我們所提供的稱號是正規表示式,藉此過濾以 httpd 起首的 目標 類別。

sesearch -ACR -s httpd_t -t "httpd.*" -c file -p write
DT allow httpd_t httpd_sys_rw_content_t : file { ioctl read write create getattr setattr lock append unlink link rename open } ; [ httpd_enable_cgi httpd_unified && httpd_builtin_scripting && ]
ET allow httpd_t httpd_sys_rw_content_t : file { ioctl read write create getattr setattr lock append unlink link rename open } ; [ httpd_builtin_scripting ]
DT allow httpd_t httpd_sys_rw_content_t : lnk_file { ioctl read write create getattr setattr lock append unlink link rename } ; [ httpd_enable_cgi httpd_unified && httpd_builtin_scripting && ]
ET allow httpd_t httpd_sys_rw_content_t : lnk_file { ioctl read write create getattr setattr lock append unlink link rename } ; [ httpd_builtin_scripting ]
ET allow httpd_t httpd_sys_rw_content_t : sock_file { read write getattr append open } ; [ httpd_builtin_scripting ]

這裡有數條規則已停用,但其中 3 條預設是啟用的。因此根據這一切我們可歸納在 HTTPD 區域中,讀寫內容的類別應是 httpd_sys_rw_content_t。帶著這份見解,我們可以沿用相同程序辨別出那些區域能存取其它目標類別,這有助辨認出那些程式在錯誤的脈絡下運行。

6. 自訂 SELinux 政策

透過設定選項的二元值,你可以微調 SELinux 政策而不必重新編譯政策的源代碼。這些選項包括允許用戶在 Samba 下分享他們的主目錄,或者允許 Apache 從用戶的主目錄伺服檔案。否則這些都會被 SELinux 政策所拒絕。

有另一版 Wiki 頁是關於二元值的。

7. 利用 audit2allow 建立自訂 SELinux 政策模塊

在某些情形下,上述方法都無法解決問題,而我們必須通過建立自訂的政策模塊來延伸 SELinux 政策,允許一組特定的狀態出理。其中一個例子就是在 smtp 郵件伺服器上增加 postgrey 服務。我們的 smtp 伺服器須要透過一個 Unix 通訊端與 postgrey 溝通,但預設的 SELinux 政策卻禁止我們的 smtp 伺服器如此做。結果該服務會被 SELinux 所封鎖。這個問題無法透過更改或還原檔案的安全性脈絡來解決,也沒有可供切換二元值。我們可以通過一個二元值來停止 SELinux 保護 smtp 伺服器,這樣總比完全停用 SELinux 好,但不太理想。

如果我們將 SELinux 切換至 Permissive 模式並讓郵件伺服器運行一段時間,我們便可以在允許存取的情況下記錄 SELinux 的問題(正如 在 Permissive 模式收集審計日誌 所提及)。檢視日誌時,我們會看見以下 SELinux AVC 信息:

type=AVC msg=audit(1218128130.653:334): avc:  denied  { connectto } for  pid=9111 comm="smtpd" path="/var/spool/postfix/postgrey/socket"
scontext=system_u:system_r:postfix_smtpd_t:s0 tcontext=system_u:system_r:initrc_t:s0 tclass=unix_stream_socket
type=AVC msg=audit(1218128130.653:334): avc:  denied  { write } for  pid=9111 comm="smtpd" name="socket" dev=sda6 ino=39977017
scontext=system_u:system_r:postfix_smtpd_t:s0 tcontext=system_u:object_r:postfix_spool_t:s0 tclass=sock_file 

接著我們可以用 audit2allow 來產生一組允許所需行動的政策規則。我們我可建立一個本地的 postgrey 強制類別政策檔(postgreylocal.te):

# grep smtpd_t /var/log/audit/audit.log | audit2allow -m postgreylocal > postgreylocal.te
# cat postgreylocal.te
module postgreylocal 1.0;
require {
        type postfix_smtpd_t;
        type postfix_spool_t;
        type initrc_t;
        class sock_file write;
        class unix_stream_socket connectto;
}
#============= postfix_smtpd_t ==============
allow postfix_smtpd_t initrc_t:unix_stream_socket connectto;
allow postfix_smtpd_t postfix_spool_t:sock_file write; 

在上面我們看見如何從 audit.log 篩選有關 smtp 伺服器的問題,並將這些問題導向 audit2allow,讓它產生一組規則,是它認為可用來允許被 SELinux 政策所封鎖的行動。查閱這些規則時,我們可發現該 smtp 伺服器想連線及寫進一個 Unix 通訊端,而從日誌裡我們看見這個 Unix 通訊端正正是 postgrey 服務所聆聽的那個。既然這一些都合情合理,我們可以續繼用 audit2allow 建立一個自訂的政策模塊,允許這些行動:

# grep smtpd_t /var/log/audit/audit.log | audit2allow -M postgreylocal 

接著我們利用 semodule 這個指令將我們的 postgrey 政策模塊載入現有的 SELinux 政策內:

semodule -i postgreylocal.pp 

如此便會將我們的 postgrey 政策模塊新增到 /etc/selinux/targeted/modules/active/modules/postgreylocal.pp。我們可以透過 semodule -l 來檢查該政策模塊已被正確地載入。

然後我們可以繼續監視 SELinux 的日誌來確定自訂的政策模塊有效用。滿意時,我們便可以重新啟用 SELinux 的 Enforcing 模式,讓功能已全備的 smtp 伺服器再次享有 SELinux 的保障。

7.1. 手動式自訂政策模塊

audit2allow 在大多數情況下都可以自動建立一個自訂政策模塊來解決某個特定問題,但有時它未能完全正確,而我們也許會想以人手編輯並編譯該政策模塊。就以下列的 AVC 審計日誌為例:

Summary:
SELinux is preventing postdrop (postfix_postdrop_t) "getattr" to
/var/log/httpd/error_log (httpd_log_t).
Detailed Description:
SELinux denied access requested by postdrop. It is not expected that this access
is required by postdrop and this access may signal an intrusion attempt. It is
also possible that the specific version or configuration of the application is
causing it to require additional access.
Allowing Access:
Sometimes labeling problems can cause SELinux denials. You could try to restore
the default system file context for /var/log/httpd/error_log,
restorecon -v '/var/log/httpd/error_log'
If this does not work, there is currently no automatic way to allow this access.
Instead, you can generate a local policy module to allow this access - see FAQ
(http://fedora.redhat.com/docs/selinux-faq-fc5/#id2961385) Or you can disable
SELinux protection altogether. Disabling SELinux protection is not recommended.
Please file a bug report (http://bugzilla.redhat.com/bugzilla/enter_bug.cgi)
against this package.
Additional Information:
Source Context                system_u:system_r:postfix_postdrop_t
Target Context                root:object_r:httpd_log_t
Target Objects                /var/log/httpd/error_log [ file ]
Source                        postdrop
Source Path                   /usr/sbin/postdrop
Port                          <Unknown>
Host                          sanitized
Source RPM Packages           postfix-2.3.3-2
Target RPM Packages
Policy RPM                    selinux-policy-2.4.6-137.1.el5
Selinux Enabled               True
Policy Type                   targeted
MLS Enabled                   True
Enforcing Mode                Enforcing
Plugin Name                   catchall_file
Host Name                     sanitized
Platform                      Linux sanitized 2.6.18-53.1.21.el5 #1 SMP Tue
                              May 20 09:35:07 EDT 2008 x86_64 x86_64
Alert Count                   599
First Seen                    Wed Jul  2 08:27:15 2008
Last Seen                     Sun Aug 10 22:47:52 2008
Local ID                      c303a4ea-8e7a-4acc-9118-9cc61c6a2ec8
Line Numbers
Raw Audit Messages
host=sanitized type=AVC msg=audit(1218397672.372:352): avc:  denied  { getattr } for  pid=4262 comm="postdrop"
path="/var/log/httpd/error_log" dev=md2 ino=117005 scontext=system_u:system_r:postfix_postdrop_t:s0
tcontext=root:object_r:httpd_log_t:s0 tclass=file
host=sanitized type=SYSCALL msg=audit(1218397672.372:352): arch=c000003e syscall=5 success=no exit=-13 a0=2
a1=7fffd6febca0 a2=7fffd6febca0 a3=0 items=0 ppid=4261 pid=4262 auid=4294967295 uid=48 gid=48 euid=48 suid=48
fsuid=48 egid=90 sgid=90 fsgid=90 tty=(none) comm="postdrop" exe="/usr/sbin/postdrop"
subj=system_u:system_r:postfix_postdrop_t:s0 key=(null) 

就上述錯誤執行 audit2allow 並查閱所得的 postfixlocal.te 政策檔時,我們會看見:

# grep postdrop /var/log/audit/audit.log | audit2allow -M postfixlocal
# cat postfixlocal.te
    module postfixlocal 1.0;
    require {
            type httpd_log_t;
            type postfix_postdrop_t;
            class dir getattr;
            class file { read getattr };
    }
    #============= postfix_postdrop_t ==============
    allow postfix_postdrop_t httpd_log_t:file getattr; 

我希望第一件引起你關注的事,就是 postdrop 為何須要存取 /var/log/httpd/error_log?這應該在我們預料之外,因此我們便要評估應否允許這個行動。我們有數個選擇:我們可以忽略這個錯誤並容讓 SELinux 繼續封鎖及記錄這些存取的行動,又或者我們可以允許這些行動並建立 audit2allow 所建議的自訂政策模塊。我們也可以選擇編輯這個自訂政策模塊的 .te 檔並防止審計這個特定錯誤,並讓 SELinux 繼續封鎖該存取權。我們可以編輯 allow 這一行,並改為 dontaudit

    #============= postfix_postdrop_t ==============
    dontaudit postfix_postdrop_t httpd_log_t:file getattr; 

現在我們可以手動地編譯及載入已修改的自訂政策模塊:

# checkmodule -M -m -o postfixlocal.mod postfixlocal.te
# semodule_package -o postfixlocal.pp -m postfixlocal.mod
# semodule -i postfixlocal.pp 

postdrop 存取 /var/log/httpd/error_log 時仍然會被 SELinux 封鎖,但我們再不會在發生封鎖時接收到警報,或在日誌內填滿錯誤信息。

8. 政策文檔

很多 SELinux 政策是利用 GNU m4 巨集編寫出來的,亦正是如此,你必須利用 devel 套件來建立那些依賴現有政策 API 的新模塊。若要列出已安裝的政策下的巨集及模塊,你可安裝 selinux-policy-doc 套件把 HTML 文檔放進 /usr/share/doc/selinux-policy/html/。它亦為每個 SELinux 模塊提供說明,當中包含每個設定值的簡介,檔案脈絡及模塊所定義的類別。說明頁的名稱採用 <模塊>_selinux 這個格式。舉例說,HTTPD 模塊的文檔名為 httpd_selinux(8),而 audit administrator 這個角色被收錄於 auditadm_selinux(8)

9. 總結

這篇文章嘗試向 SELinux 的新用戶簡介如何應用它。SELinux 預設會被安裝及啟用,對多數用戶來說,它會默默地提高系統的安全性。SELinux 適用於各類型的安裝,包括:伺服器、工作台、桌面電腦、及筆記型電腦。

雖然 SELinux 對不熟識它的用戶來說似乎很複雜及令人生畏,但這不是在安裝時停用它的一個理由。若果 SELinux 確實帶來問題,你可以簡單地將它切換至 Permissive 模式,到時它便會將問題記錄下來,而不會進行封鎖。當問題出現時,這篇文章所示範的技巧可以用來排除疑難及解決它們。

10. 額外資源

https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/SELinux_Users_and_Administrators_Guide/

http://fedoraproject.org/wiki/SELinux

http://docs.fedoraproject.org/en-US/Fedora/13/html-single/Security-Enhanced_Linux/

http://danwalsh.livejournal.com/

11. 用戶備註及陷阱

此部份由一位靠此頁學懂 SELinux 的用戶所提供。此文檔是一個優越及詳盡的資源。不過,它有點枯燥。它違漏了數個實際的重點,令我嘗試正式做事時感到很沮喪。留意這裡針對 CentOS 6。

  1. semanage 收錄在 policycoreutils-python 這個套件內,它預設是不被安裝的。留意 policycoreutils 是另一個套件。CentOS 7 似乎已經預設安裝 semanage。它依然位於 policycoreutils-python 套件內。

  2. 在管理系統時,尋找適用的脈絡是困難的。其中一個起始點是 ls -Z。檢視一個套件所安裝的目錄及數據,然後複製所採用的脈絡。下一個工具是 seinfo -t,它會列出系統現正應用的所有脈絡。grep 你的應用程式的名稱。

  3. 某些情況可透過運用 public_content_rw_t 脈絡來解決。一位用戶有個目錄要由 NFS、Samba 及 Apache 共用。此脈絡允許這樣做。它也可構成一個安全性漏洞,因此在重視安全的系統上請特別留意。

  4. 不要忘記 chcon 的 -t 參變。它單單設定脈絡種類,這也是你普要做的事情,而且較指定 ls -Z 所匯報的整句字串更為容易。

  5. 應用 audit2allow 比這裡的介紹來得簡單。當兩個脈絡出現衝突時,請從 audit.log 找出錯誤信息並將它們放入獨立的文字檔內。然後這樣把錯誤傳給 audit2allow

audit2allow -M mynewpolicyname <errors.txt

module mynewpolicy 1.0;

require {
        type spamc_t;
        type postfix_pipe_tmp_t;
        class file { read write };
}

#============= spamc_t ==============
allow spamc_t postfix_pipe_tmp_t:file { read write };

Translation of revision 39

zh-tw/HowTos/SELinux (last edited 2017-06-27 13:16:31 by TimothyLee)