伺服器架設篇 - RockyLinux 9

第三章、SELinux 初探

SELinux 不是防火牆,大致的目的是在保護由於自己 server 的用戶耍白痴可能造成的錯誤!

最近更新時間: 2023/08/04

說到 SELinux 許多人都很恨它,因為設定方面真的是很點困擾,而且經常還有許多的『例外』狀況需要處理。 不過,近期以來的 SELinux 應該算是比較穩定些,而且,大概只要知道幾個小細節,就很容易找到處理方向。 只是,如果想要用 Linux 進行類似專題、專案開發的話,其實,暫時轉成寬容模式,大概還是需要的...。 總之,對於正規網際網路服務或者是管理企業內部員工的權限來說,這東西目前還是很有用途的! 所以,還是得要讓我們了解一下這東西才行!

請注意,這一章的所有練習環境,都在前一章節建立的 VM 裡面實做喔!不要在 KVM host 裡面動作喔!

3.1、SELinux 簡介

SELinux 全名其實是『 Security Enhanced Linux 』的意思,這傢伙最早是由美國國家安全局開發出來的, 會想要做這個東西的原因,其實是早期 Unix 的系統中,如果你將某個目錄設定成為 777 (drwxrwxrwx) 之後, 那麼該目錄就變成所有人都可以存取的情境!對於內部某些敏感資料來說,很可能由於人為的設定錯誤, 導致資料可能被第三人竊取或刪除...管理員想哭都哭不出來的啦!

早期網路常常有人說:『ㄟ~我某個目錄或檔案,無法讀取耶!怎麼辦啊?』很多人就會說,那你就 chmod -R 777 /some/dir 就好了。 結果...就造成管理員很大很大的困擾了!因為這種『攻擊』不是來自於網路怪客,而是來自於自家的使用者啊! 所以說,鳥哥自己認為, SELinux 主要管理的,應該算是自家的小白使用者...

所以,SELinux 開發的目的,就是在防止上述的自由選定存取控制 (Discretionary access control, DAC) 造成的影響, 取而代之的,是利用強制存取控制 (Mandatory access control, MAC) 的方法來進行檔案的存取。 在 MAC 的存取方法中,並沒有所謂的 root 的使用者概念,而是透過安全本文來限制程序的讀寫能力。

  • SELinux 的簡單運作示意

另外,SELinux 並不是取代了傳統的 rwx 權限,而是在通過檔案權限的判定之後,『再加』一層防護, 該層 SELinux 的防護,可以『針對某些網路程序可以讀取的檔案之安全本文類型』進行限制, 因此,如果被讀寫目標檔案的安全本文設計無法與相關程序匹配,那麼該程序就無法讀寫目標檔案了。

上面的說法其實很抽象,我們來畫張圖解釋解釋好了。如下圖所示,要啟動網路服務,總是得要執行某些腳本或程式。 這時 SELinux 就開始進行防護!如果你的設定檔設計錯誤,無法符合當初 SELinux 指定的預設規則 (rule), 那該腳本或程式就無法順利載入到記憶體當中了 (藍色箭頭部份,很可能被 SELinux 抵擋)。若通過預設規則而載入軟體成為網路程序, 那該網路程序想要讀取某些系統上面的檔案時,如果網路程序與檔案的安全本文設計不符,那也無法讀到該檔案! 這也是 SELinux 最主要限制的地方喔!(紅色箭頭部份)

SELinux 運作圖示
圖 3.1-1、SELinux 運作圖示

這麼限制的好處是,因為沒有 root 的使用者概念,因此,上圖當中,你的 httpd process 被攻擊而被破解了! 那也沒關係,因為除了原本 httpd process 可以讀寫的位置之外,系統的其他目錄,httpd 是沒有訪問權的! 舉例來說, /etc 目錄的安全本文與 httpd process 能讀取的並不相同,因此你的 /etc/ 目錄,就不可能被有問題的 httpd 訪問了!這樣起碼能夠作到一定程度的保護。

  • SELinux 在哪裡?在 inode 紀錄中

從上面的簡單介紹,我們大概知道每個網路程式應該會有相對的 SELinux 安全本文,然後載入到記憶體之後, 會取得其相對的程序 SELinux 類型,而這個程序能不能讀某個檔案,也得要看該檔案的安全本文類型才行。 那麼,這些安全本文與程序的 SELinux 類型,是紀錄在哪裡呢?基本上,就是紀錄在檔案的 i-node 裡面啦! 回想一下,當初你在救援系統時,如果有進入到 rd.break 這個救援環境,離開 chroot 時,是不是需要『 touch /.autorelabel 』呢? 這個動作不是會搞很久嘛?那就是 SELinux 要重建系統內所有檔案的 SELinux 表頭資料,而需要於各檔案的 i-node 內重新建立 SELinux 安全本文的緣故! 現在懂了吧!

# 前往 VM 系統,查看一下 /usr/sbin/chronyd 的 SELinux 類型
[root@localhost ~]# stat /usr/sbin/chronyd
  File: /usr/sbin/chronyd
  Size: 353408          Blocks: 696        IO Block: 4096   regular file
Device: fd03h/64771d    Inode: 17318583    Links: 1
Access: (0755/-rwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Context: system_u:object_r:chronyd_exec_t:s0
Access: 2023-04-17 07:40:35.000000000 +0800
Modify: 2023-04-17 07:40:35.000000000 +0800
Change: 2023-08-04 13:37:38.809963808 +0800
 Birth: 2023-08-04 13:37:38.806963845 +0800

如上所示,透過 stat 取出該檔名相關的紀錄,最重要就是那個 Context 行,Context 就是安全本文囉! 因此,這個 chronyd 執行檔,安全本文就是『system_u:object_r:chronyd_exec_t:s0』用冒號 (:) 隔開, 共分數個欄位,現行主要的限制,其實僅有針對第三欄位,也就是 chronyd_exec_t 那個項目而已! 所以,我們知道這個執行檔的安全本文類型就是 chronyd_exec_t 的意思。除了這個 stat 之外,我們其實可以透過 ls 搭配 -Z 同樣可以看到這個安全本文資料:

[root@localhost ~]# ll -Z /usr/sbin/chronyd
-rwxr-xr-x. 1 root root system_u:object_r:chronyd_exec_t:s0 353408 Apr 17 07:40 /usr/sbin/chronyd

圖 3.1-1 可以看到,檔案有安全本文,那麼程序有嘛?其實也是有的! 觀察程序的安全本文,可以透過 ps -Z 的參數來查詢得到!

# 查出 chronyd 程序的 PID 含有的安全本文
[root@localhost ~]# ps auxZ | egrep 'chrony|LABEL'
LABEL                           USER   PID %CPU %MEM    VSZ   RSS TTY  STAT START TIME COMMAND
system_u:system_r:chronyd_t:s0  chrony 616  0.0  0.1  84436  3252 ?    S    16:02 0:00 /usr/sbin/chronyd -F 2

可以看到程序也是有安全本文相關資料,只是在 ps 裡面被稱為是標籤 (LABEL) 而已。同樣的, chronyd 程序的 SELinux 類型為 chronyd_t 喔!

3.2、SELinux 運作方式與安全本文

上面講的是比較概念性的運作方法,實際上,SELinux 的運作,可以參考下列的流程表。 在傳統的 DAC,亦即是傳統的 rwx 權限存取控制的分析後,若程序可以進行存取,則開始進入到 SELinux 的控制流程中。 若程序無法存取,那當然就不會進入 SELinux 的控制流程啦!

在 SELinux 的控制流程中,一個主體 (subject) 想要取用某個目標 (Object) 時,得要經過完整的 SELinux 流程之後, 最終才能夠實際讀寫到檔案的資料 (file data)。所以,MAC 的目的是增加 DAC 的不足,而不是用來取代 DAC 喔! 事實上,還是得要先通過 rwx 傳統權限的控制才行!

SELinux 運作圖示
圖 3.2-1、程序若要讀取檔案物件時,需要經過的SELinux關卡
  • 主體 (Subject):
    一般所謂的主體指的大部分就是程序,尤其是網路方面的程序,本章談到的主體大部分都是程序。
  • 目標 (Object):
    主體程序能否存取『目標資源』的意思,一般目標指的大部分都是檔案,不過,也有例外喔!例如, 主體可能會想要啟動網路埠口 (port),這時目標就變成是埠口資源了!但是,大部分指的都是檔案就是了。
  • SELinux 模式 (Mode):
    SELinux 根據開啟與否,共有三種模式,分別是關閉 (Disabled)、寬容模式 (Permissive) 與強制模式 (Enforcing)。 根據上圖的箭頭,你可以知道 disabled, permissive 模式下,其實 SELinux 並不會真的進行程序存取的管制! 只有 enforcing 模式才會真的進行各個項目的管理!
  • 政策與規則 (Policy, rules, boolean):
    由於主題程序與目標檔案數量龐大,因此 SELinux 會依據某些服務來制訂基本的存取安全性政策。 這些政策內還會有詳細的規則 (rule) 來指定不同的服務開放某些資源的存取與否,另外,某些功能是否放行 (SELinux boolean) 也是需要控制與了解的。在目前的 RockyLinux 9 裡面主要提供 3 個政策,預設為 targeted 政策。相關政策說明可以參考 /etc/selinux/config 的內容:
    • targeted:預設政策,大部分針對網路程序,對本機程序 (如bash) 限制較少。
    • minimum:最小管制政策,修改 targeted 政策,保留僅有選擇的程序才受管理。
    • mls (Multi Level Security protection):完整的 SELinux 管理,限制非常嚴格
  • 安全本文 (security context):
    每一個主體程序能夠存取的安全本文並不相同,SELinux 會規範主體程序能夠存取的安全本文類型, 當目標檔案的安全本文為主體程序可存取的類型時,此時主體程序方可進行目標檔案的存取。 這個安全本文 (security context) 有點像是 SELinux 的 rwx 的概念!如果 subject 要存取的 object 兩者的安全本文無法匹配, 那在 enforcing 的模式下,這個存取就會出現權限不符的錯誤訊息!此時,無論你的權限設定如何開放, 都無法讓程序讀取到檔案喔!

在上面整體的說明中,重點在『主體』如何取得『目標』的資源存取權限!由上圖我們可以發現, 程序的讀寫,還是需要先經過 rwx 的權限分析,如果 rwx 權限原本就被抵擋,那麼後續 SELinux 流程就不會啟動。 若通過 rwx 的權限且確定可以讀寫之後,則:SELinux 在取得主體程序與預備存取的目標資源之安全本文類型後, 開始搜尋兩者在 SELinux 當中的規則,若確認匹配可行,就予以放行,否則就予以抵擋。

  • 安全本文

再次強調,安全本文的紀錄主要在檔案的 inode 當中喔~程序也會有安全本文的紀錄~讓我們再次回想一下, 如何取得安全本文的資料呢?

# 程序的安全本文取得
[root@localhost ~]# ps -auxZ | egrep 'bash|chronyd|LABEL'
LABEL                                                 USER   ...  COMMAND
system_u:system_r:chronyd_t:s0                        chrony ...  /usr/sbin/chronyd -F 2
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 root   ...  -bash

# 檔案的安全本文觀察
[root@localhost ~]# ll -Z /usr/sbin/chronyd /etc/chrony.keys
-rw-r-----. 1 root chrony system_u:object_r:chronyd_keys_t:s0    540 Aug 29  2022 /etc/chrony.keys
-rwxr-xr-x. 1 root root   system_u:object_r:chronyd_exec_t:s0 353408 Apr 17 07:40 /usr/sbin/chronyd

[root@localhost ~]# ll -Zd /root/anaconda-ks.cfg /home/vbird
drwx------. 2 vbird vbird unconfined_u:object_r:user_home_dir_t:s0  113 Aug  4 13:43 /home/vbird
-rw-------. 1 root  root  system_u:object_r:admin_home_t:s0        1204 Aug  4 13:05 /root/anaconda-ks.cfg

大部分只要記得前面三個欄位就好!這三個欄位的功能分別是:

User_Identify:role:type
SELinux用戶別:角色:類型
  • SELinux 用戶識別(User Identify):相當於帳號方面的身份,因為主要跟 SELinux 用戶類別有關, 所以結尾都是 _u 的模樣。可以粗分為兩種類型:
    • system_u:表示系統程序方面的識別,大部分指的就是有被管理限制的情況
    • unconfined_u:表示沒有限制的情況
  • 角色 (Role):透過角色欄位,我們可以知道這個資料是屬於程序、檔案資源還是代表使用者。一般的角色有:
    • object_r:代表的是檔案或目錄等檔案資源,這應該是最常見的囉;
    • system_r:代表的就是有被管理限制的程序啦!
    • unconfined_r:表示沒有限制的情況
  • 類型 (Type):在預設的 targeted 政策中, User Identify 與 Role 欄位基本上是不重要的! 重要的在於這個類型 (type) 欄位!基本上,一個主體程序能不能讀取到這個檔案資源,與類型欄位有關! 而類型欄位在檔案與程序的定義不太相同,分別是:
    • type:在檔案資源 (Object) 上面稱為類型 (Type);
    • domain:在主體程序 (Subject) 則稱為領域 (domain) 了!

安全本文的類型資料相當多!稍等我們再來查詢安全本文的限制!大致上先了解到這裡即可。

3.3、SELinux 的三種模式

圖 3.2-1 當中,我們可以知道 SELinux 共有三種模式,分別是 disabled, permissive 與 enforcing 三種,根據圖示的樣子,我們大概可以這樣看這三種模式:

  • Disabled:其實就是關閉 SELinux,在這種模式底下,檔案系統內的所有 inode 所紀錄的 SELinux 安全本文會全部消失! 等於就是沒有 SELinux 啦!所以,不要隨便轉到這種模式!
  • Permissive:寬容模式,根據圖示,我們也知道寬容模式其實也沒有實際進行主體與目標的管制啊! 那這個模式在幹麻?仔細看圖示,你會發現到寬容模式多了個箭頭到 log 上頭!那個 log 登錄檔, 大部分就是紀錄到 /var/log/messages 裡面去!這個寬容模式很常應用在 debug 當中!當出現主體無法取得目標時, 你可以將模式更改為 permissive,並且再次重現錯誤後,就可以到 log 去看一下有沒有解決方案呢!
  • Enforcing:強制模式,主體會開始被 SELinux 政策、規則、功能條件、安全本文比對分析所管理,若達成匹配,就可以準備存取目標資源了。
除非你的 RockyLinux 主要用在內網,而且不會接觸到網際網路的情境,否則,盡量不要將 SELinux 調整成為 disabled。 這是因為 disabled 會將檔案系統內的 inode 所紀錄的安全本文全部刪除,因為核心不支援了...因此, 若要重新啟用 SELinux,你就得要重新開機,然後讓 Linux 核心重新去檔案系統裡面,將全部的檔案重新給予預設的安全本文! 這會花費相當多時間!還記得『 touch /.autorelabel 』嘛?就是在做這個動作啦!
  • SELinux 模式的觀察與轉換

寬容模式與強制模式,可以在不重新開機的情況下直接進行轉換!因此,想要判斷某個主體無法存取目標的原因是 SELinux 或是傳統權限時,就可以將 SELinux 模式暫時轉到寬容模式去測試即可。那如何觀察目前的 SELinux 模式呢? 使用 getenforce 即可。

# 觀察目前的 SELinux 模式
[root@localhost ~]# getenforce
Enforcing

# 暫時將 SElinux 模式調整成為 permissive
[root@localhost ~]# setenforce [0|1]
[root@localhost ~]# setenforce 0
[root@localhost ~]# getenforce
Permissive

# 顯示詳細的 SELinux 設定值
[root@localhost ~]# sestatus
SELinux status:                 enabled
SELinuxfs mount:                /sys/fs/selinux
SELinux root directory:         /etc/selinux
Loaded policy name:             targeted
Current mode:                   permissive
Mode from config file:          enforcing
Policy MLS status:              enabled
Policy deny_unknown status:     allowed
Memory protection checking:     actual (secure)
Max kernel policy version:      33

# 趕緊將模式轉回 Enforcing
[root@localhost ~]# setenforce 1
  • SELinux 模式的設定方式

預設的 SELinux 設定檔為 /etc/selinux/config,但是,我們也可以在開機階段,在 Linux 核心強迫開啟或關閉 SELinux 的。 所以,要觀察 SELinux 的初始設定,可能得要觀察兩個地方呢!分別是上述的設定檔,還有 grub 的設定。 當然,你也可以直接觀察目前的核心參數,以確認 SELinux 是否由核心參數所影響。

# 修改預設的設定檔,指定開機為 SELinux 模式
[root@localhost ~]# vim /etc/selinux/config
SELINUX=enforcing
SELINUXTYPE=targeted
# 其實,這個設定檔就是指定 SELinux 模式與政策的檔案

# 檢查目前的核心是否有 SELinux 的參數
[root@localhost ~]# cat /proc/cmdline
BOOT_IMAGE=(hd0,gpt3)/boot/vmlinuz-5.14.0-284.18.1.el9_2.x86_64 root=UUID=e81ba...
# 查詢看看有沒有 selinux=[0|1] 這個關鍵字串!

# 檢查 grub.cfg 有沒有 selinux 的關鍵字
[root@localhost ~]# grep selinux /boot/grub2/grub.cfg
思考例題:你的 SELinux 似乎不是在 Enforcing 的階段,如何檢查並且修改,同時未來每次都生效呢?可以這樣處理:
  • 使用 getenforce 觀察目前你的系統使用哪種 SELinux 的模式?
  • 使用 setenforce [0|1] 搭配 getenforce 來觀察 SELinux 的模式變化!
  • 觀察 /etc/selinux/config 這個檔案的內容,看如何設定預設的 SELinux 模式。
  • 觀察 /boot/grub2/grub.cfg 裡面的設定,若有 selinux=0 亦代表預設關閉 SELinux 的模式,而成為 disable 喔!

3.4、SELinux boolean 的觀察與修改

圖 3.2-1當中,進入整個 SELinux 的程序當中的第二關,除了得要參考政策內的規則外, 某些特定功能 (SELinux boolean) 是否啟動,也是相當重要的!舉例來說,看看網頁伺服器能不能提供一般帳號家目錄的讀取權限, 就是透過這裡的規範來額外指定的。如果這裡的功能規範當中,不允許網頁伺服器讀取個人家目錄, 那麼,即使個人家目錄的安全本文類型是正確的,網頁伺服器也會無法讀取喔!

  • 使用 getsebool 及 semanage boolean 查詢各個規則

查詢目前所有 SELinux boolean 功能的狀態是開啟還是關閉,最簡單可以透過 getsebool 來查詢即可:

[root@localhost ~]# getsebool -a
abrt_anon_write --> off
abrt_handle_event --> off
.....
zoneminder_anon_write --> off
zoneminder_run_sudo --> off
# 功能規範真的太多了!透過 grep 來擷取看看

# 查詢看看有沒有 http 開頭,home 後續存在的功能規範
[root@localhost ~]# getsebool -a | grep 'http.*home'
httpd_enable_homedirs --> off

# 已經知道 httpd_enable_homedirs 功能規範名稱時
[root@localhost ~]# getsebool httpd_enable_homedirs
httpd_enable_homedirs --> off

除了 getsebool 之外,我們可以透過萬用工具,就是 semanage 這個軟體,裡面的 boolean 指令來查詢!

# 需要先安裝 semanage 的軟體!名稱為 policycoreutils-pythone-utils
[root@localhost ~]# yum whatprovides '*bin/semanage'
policycoreutils-python-utils-3.5-1.el9.noarch : SELinux policy core python utilities
Repo        : appstream
Matched from:
Other       : *bin/semanage

[root@localhost ~]# yum -y install policycoreutils-python-utils

# semanage boolean 的簡單 help
[root@localhost ~]# semanage boolean -h
....
  -l, --list            List records of the boolean object type
  -1, --on              Enable the boolean
  -0, --off             Disable the boolean

[root@localhost ~]# semanage boolean --list
SELinux boolean                State  Default Description
....
httpd_enable_homedirs          (off  ,  off)  Allow httpd to enable homedirs
....

這樣也能很輕鬆的找到需要的 SELinux boolean 說明!基本上,這些功能規範說明目前你可能還看不太懂, 這是因為可能你不具備某些特定的網路服務經驗。沒關係!等到後面許多伺服器章節實做完之後, 對這些功能規範的名稱,你大概就一看就懂了!所以,不用擔心!慢慢來!

其實,還有個名為 setools-console 的軟體可以安裝,安裝完成之後,還會有 sesearch 與 seinfo 等指令可以介紹。 不過,目前的 RockyLinux 其實含有 setroubleshoot 的問題分析軟體,出現 SELinux 問題可以直接透過 /var/log/messages 的紀錄來解決~所以,漸漸的,連鳥哥都忘記這些工具的存在了!
  • 使用 setsebool -P 或 semanage boolean --[on|off] 來修改

如果發現到某些功能規範沒有啟用,想要啟用這些功能時,該怎麼辦呢?既然查看是 getsebool, 想當然爾,設定應該就是 setsebool 囉!

# 將剛剛查詢到的 httpd_enable_homedirs 設定為 on
[root@localhost ~]# setsebool -P httpd_enable_homedirs 1
[root@localhost ~]# getsebool httpd_enable_homedirs
httpd_enable_homedirs --> on
# 設定值, 1 或 on 都可以啟用, 0 或 off 都可以關閉!

特別注意的是,setsebool 預設修改的是『目前的狀況』,如果想要連同下次開機都使用相同的設定, 那直接加上 -P 的選項來處理即可!所以,將它背下來! setsebool 就是要 -P !!

# 使用 semanage boolean 關閉 httpd_enable_homedirs 測試看看
[root@localhost ~]# semanage boolean httpd_enable_homedirs --modify --off
[root@localhost ~]# getsebool httpd_enable_homedirs
httpd_enable_homedirs --> off
# 雖然比較麻煩,不過,還是稍微熟悉一下 semanage 較佳!

3.5、SELinux 安全本文的修改

前面我們談到 SELinux 安全本文,在程序上面,可以查看的指令有『 ps -Z 』之類的方式,而檔案的安全本文, 則是透過『 ll -Z 』或者是『 stat 』這個指令來查詢。那麼修改呢?該如何進行安全本文的修改? 注意喔,修改時,請修改安全本文的類型即可,不要更動到身份識別或者是角色欄位喔!

  • 使用 chcon 修改安全本文類型

最簡單的修改方式是透過 chcon 來修改即可!例如底下的範例:

[root@localhost ~]# chcon [OPTION]... [-t TYPE] FILE...
[root@localhost ~]# chcon [OPTION]... --reference=RFILE FILE...
選項與參數:
-t  :後面接安全性本文的類型欄位!例如 httpd_sys_content_t
--reference=RFILE:拿檔名為 RFILE 當範例來修改後續接的檔案的類型!

# 將 /etc/hosts 複製 /dev/shm/hosts,並修改類型為 etc_t
[root@localhost ~]# cd /dev/shm
[root@localhost shm]# cp -a /etc/hosts .
[root@localhost shm]# ll -Z hosts
-rw-r--r--. 1 root root system_u:object_r:net_conf_t:s0 158 Jun 23  2020 hosts

[root@localhost shm]# chcon -t etc_t hosts
[root@localhost shm]# ll -Z hosts
-rw-r--r--. 1 root root system_u:object_r:etc_t:s0 158 Jun 23  2020 hosts

# 將類型改成與 /var/spool/mail 相同
[root@localhost shm]# ll -Zd /var/spool/mail/
drwxrwxr-x. 2 root mail system_u:object_r:mail_spool_t:s0 19 Jul 21 15:01 /var/spool/mail/

[root@localhost shm]# chcon --reference=/var/spool/mail hosts
[root@localhost shm]# ll -Z hosts
-rw-r--r--. 1 root root system_u:object_r:mail_spool_t:s0 158 Jun 23  2020 hosts
  • 使用 semanage fcontext 查詢目錄/檔案預設的類型

不知道你會不會好奇,既然 SELinux 模式可以在 disable 與 enforcing 之間切換,只是轉為 enforcing 時, 可能會花費一段時間,讓核心對全系統的檔案進行重新設定 (relabel) 的動作!那麼表示,每個檔案/目錄, 可能都會有預設值囉!那如何查詢預設值呢?透過 semanage 吧!這樣做看看:

# 找到 /etc/sysconfig 相關的預設 SELinux 安全本文類型
[root@localhost ~]# semanage fcontext --list | grep /etc/sysconfig
SELinux fcontext              type           Context
/etc/sysconfig/.*l2tpd        regular file   system_u:object_r:l2tp_conf_t:s0
/etc/sysconfig/MailScanner    regular file   system_u:object_r:mscan_etc_t:s0
.....

很簡單就可以查看到某個檔案/目錄的預設 SELinux 安全本文類型了。好!那如果不是在正規目錄, 預設的安全本文類型又是什麼呢?基本上,看一下上述資料最前頭輸出的幾行就知道了!

[root@localhost ~]# semanage fcontext --list | head
SELinux fcontext   type          Context
/                  directory     system_u:object_r:root_t:s0
/.*                all files     system_u:object_r:default_t:s0
/[^/]+             regular file  system_u:object_r:etc_runtime_t:s0
.....

原來預設會是 default_t 喔!

  • 使用 semanage fcontext 修改/設定某目錄預設值

假設我們預計建立一個名為 /www 的目錄,這個目錄的內容主要就是給網頁伺服器使用的!因此, 主要的 SELinux 類型應該指定為與 /var/www 這個目錄相同。那該如何處理呢? 基本上,你可以這樣處理看看。

# 基本的設定語法,如下所示,只要改 type 與目錄位置即可
[root@localhost ~]# semanage fcontext -a -t type "/some/dir(/.*)?"

# 1. 先找到 /var/www 的類型為何
[root@localhost ~]# semanage fcontext -l | grep '/var/www('
/var/www(/.*)?              all files   system_u:object_r:httpd_sys_content_t:s0
/var/www(/.*)?/logs(/.*)?   all files   system_u:object_r:httpd_log_t:s0

# 2. 建立所需目錄,並且查看預設值
[root@localhost ~]# mkdir /www
[root@localhost ~]# echo check > /www/index.html
[root@localhost ~]# ll -Zd /www /www/index.html
drwxr-xr-x. 2 root root unconfined_u:object_r:default_t:s0 24 Jul 22 14:40 /www
-rw-r--r--. 1 root root unconfined_u:object_r:default_t:s0  6 Jul 22 14:40 /www/index.html
# 果然預設值是 default_t 呢!

# 3. 增加 /www 預設為 httpd_sys_content_t 的類型
[root@localhost ~]# semanage fcontext -a -t httpd_sys_content_t "/www(/.*)?"
[root@localhost ~]# semanage fcontext -l | grep '^/www'
/www(/.*)?    all files     system_u:object_r:httpd_sys_content_t:s0

透過這些步驟,很輕鬆的就完成了非正規目錄的預設 SELinux 安全本文類型設定值!

  • 使用 restorecon 復原預設值

現在,我們已經規劃好了 /www 的預設類型,那,我們還需要使用 chcon 一個一個慢慢調整 SELinux 的規範嘛? 似乎不需要呢!直接透過 restorecon 來復原即可!很輕鬆愉快喔!

# 將剛剛的 /www 安全本文類型重置一下!
[root@localhost ~]# restorecon -Rv /www
Relabeled /www from unconfined_u:object_r:default_t:s0 
  to unconfined_u:object_r:httpd_sys_content_t:s0
Relabeled /www/index.html from unconfined_u:object_r:default_t:s0 
  to unconfined_u:object_r:httpd_sys_content_t:s0

加上 -v 之後,連修改的過程都跟你說了!很簡單愉快!如果在某個特別的情況下,你想要復原全系統的 SELinux 類型, 不必進入核心功能,直接使用 restorecon 也是辦得到的!

[root@localhost ~]# restorecon -Rv /
老實說,在救援 root 密碼時,鳥哥覺得最後一動 touch /.autorelabel 之後重新開機,然後又要等好久!真的是很慢!如果是我要處理的話, 救援時,鳥哥很可能會這樣做:
  • 先將 /etc/selinux/config 設定為 permissive,然後 reboot
  • 重新開機完成之後,執行 restorecon -Rv /,速度應該是比較快!
  • 然後再次將 /etc/selinux/config 修改成 enforcing
  • 之後 setenforce 1
這樣就不用反覆重新開機了!也能夠避免因為忘記 touch /.autorelabel,導致還得要重新救援一次的困擾!
  • SELinux 服務對應埠口查詢與修改 -- 使用 semanage port 指令

如同圖 3.1-1 裡面提到的,除了主體程序要存取目標檔案需要透過 SELinux 管理之外, 程式要觸發成為程序時,可能也會經過 SELinux 的修改。系統很可能因為被值入木馬,或者是使用者不小心安裝了有問題的服務, 而這些服務很可能會開啟不明的網路埠口!因此,SELinux 確實有針對某些服務來管理預設埠口, 如果想要啟動非正規埠口,還需要這個埠口對應的功能修改正確才行。

雖然我們還沒有談到網路基礎,不過,一般常識來說,你可能會知道,網頁伺服器一般啟動的埠口會是 port 80, port 443, 因此,SELinux 可能會管制你的伺服器埠口,限制 httpd 這個網路服務程式只能開放在 port 80, 443 而已。 讓我們來查詢一下:

[root@localhost ~]# semanage port --list | grep http
http_cache_port_t  tcp  8080, 8118, 8123, 10001-10010
http_cache_port_t  udp  3130
http_port_t        tcp  80, 81, 443, 488, 8008, 8009, 8443, 9000

就是 http_port_t 那個項目!基本上使用的埠口好幾個!那如果你想要增加一個 port 98 怎麼辦? 同樣使用 semanage port 來處理!指令方式也不算太困難:

[root@localhost ~]# semanage port -a -t TYPE -p [tcp|udp] port_range
-a   新增一筆紀錄
-t   修改的埠口名稱,例如 httpd_port_t
-p   使用 tcp 或 udp 協定
port_range 使用的埠口號碼

# 加入 port 98 的支援到 http 當中
[root@localhost ~]# semanage port -a -t http_port_t -p tcp 98
[root@localhost ~]# semanage port --list | grep http
http_port_t    tcp     98, 80, 81, 443, 488, 8008, 8009, 8443, 9000

很快可以看到 port 98 也加入可讓 httpd 服務啟動的埠口了!

3.6、利用 SELinux trouble shoot 服務

基本上,透過了解 SELinux 的三種模式 (disabled, permissive, enforcing)、功能規範開放與否 (getsebool, setsebool)、 安全本文的修改 (chcon, restorecon, semanage fcontext),以及埠口規範 (semanage port) 的方法, 對於 SELinux 的管理,大概就不會差太多了!不過,有沒有更簡單的方法呢?是有的喔!

事實上,如果你的 SELinux 運作錯誤時,我們可以透過 setroubleshoot 這個軟體的功能, 它會自動分析可能的錯誤,並且將可能的解決方案直接紀錄到 /var/log/messages 裡面! 如此一來,你只要重複犯錯的動作,然後查閱 messages 檔案內容,就可以知道如何解決了!相當愉快!

  • 確認 setroubleshoot 與 rsyslog 是有安裝的

要使用 SELinux 自動錯誤克服的功能,就得要安裝 setroubleshoot 軟體才行!而且, 初次安裝完畢時,可能得要重新開機才會有作用。另外,RockyLinux 8 預設似乎沒有啟動 rsyslog,不過,RockyLinux 9 倒是預設安裝的。 如果你無法確認 rsyslog 有沒有啟動以及 setroubleshoot 有沒有安裝,沒關係,就讓我們來手動測試看看即可。

[root@localhost ~]# yum -y install setroubleshoot*
[root@localhost ~]# rpm -qa | grep setrouble
setroubleshoot-plugins-3.3.14-4.el9.noarch
setroubleshoot-server-3.3.31-2.el9_2.x86_64
setroubleshoot-3.3.31-2.el9_2.x86_64

[root@localhost ~]# systemctl status rsyslog
● rsyslog.service - System Logging Service
     Loaded: loaded (/usr/lib/systemd/system/rsyslog.service; enabled; preset: enabled)
     Active: active (running) since Fri 2023-08-04 16:02:11 CST; 6h ago
       Docs: man:rsyslogd(8)
             https://www.rsyslog.com/doc/
   Main PID: 609 (rsyslogd)
      Tasks: 3 (limit: 12243)
     Memory: 3.0M
        CPU: 521ms
     CGroup: /system.slice/rsyslog.service
             └─609 /usr/sbin/rsyslogd -n

你可能會覺得很怪異,上面安裝的軟體名稱當中有 setroubleshoot-server 這個關鍵字,但是,使用 systemctl 去檢查相關的服務時,卻找不到任何 setrouble 相關的服務名稱!這是因為 setrouble 已經整合到稽核模組 auditd 服務中! 因此, setroubleshoot 的運作方式是這樣的:

  • 先由 auditd 去呼叫 audispd 服務
  • 然後 audispd 服務去啟動 sedispatch 程式
  • sedispatch 再將原本的 auditd 訊息轉成 setroubleshootd 的訊息,進一步儲存下來的

總之,鳥哥這種老人家,還是比較習慣查詢 /var/log/messages 內的資料,而不是讓日誌直接寫入 systemd-journald 當中! 因為只寫入 systemd-journald 時,當系統重新開機,日誌可能是會遺失的呢!

  • 1. 模擬狀況,當 port 出問題時:讓 httpd 開啟在非正規埠口

基本上,http, https 的埠口分別是 port 80, port 443 的 tcp 埠口。那麼當我將這個埠口開啟到非正規的 377 埠口呢? 很可能會無法啟動喔!先來測試看看。我們依序可以這樣做:

  • 1. 網頁伺服器的軟體所需名稱為 httpd,請安裝好這個軟體
  • 2. 軟體主設定檔為 /etc/httpd/conf/httpd.conf ,內部的 Listen 設定,請改為 377
  • 3. 啟動名為 httpd 的服務,並且查看有沒有出問題?
  • 4. 若出問題,將 SELinux 模式由 enforcing 改為 permissive 測試一下
  • 5. 再次重新啟動 httpd 服務,是否能正常啟動?若可以,代表就是 SELinux 的問題。
  • 6. 前往 /var/log/messages 查詢是否有 setrouble 的關鍵字?若有,取出查閱
  • 7. 根據 sealert 的解釋,將問題克服

我們就實際在虛擬機上面惡搞一下囉!

# 1. 先安裝軟體
[root@localhost ~]# yum -y install httpd

# 2. 修改設定檔,大約在 47 行處,修改埠口號碼
[root@localhost ~]# vim /etc/httpd/conf/httpd.conf
Listen 377

# 3. 嘗試啟動 httpd 服務
[root@localhost ~]# systemctl start httpd
Job for httpd.service failed because the control process exited with error code.
See "systemctl status httpd.service" and "journalctl -xeu httpd.service" for details.
# 如上所示,系統會提示出現錯誤了!

# 4. 嘗試將 SELinux 模式改為 permissive
[root@localhost ~]# getenforce
Enforcing
[root@localhost ~]# setenforce 0
[root@localhost ~]# getenforce
Permissive

# 5. 確認一下能不能順利啟動?用來判斷問題是否出在 SELinux 的情況
[root@localhost ~]# systemctl start httpd
[root@localhost ~]# netstat -tlunp | grep httpd
tcp6    0      0 :::377    :::*    LISTEN      8324/httpd
# 出現 LISTEN 關鍵字!代表服務有正常啟動了!所以,問題一定是 SELinux 造成的!

# 6. 確認 /var/log/messages 有沒有因為啟動 httpd 而記載錯誤解決方案
[root@localhost ~]# grep setrouble /var/log/messages | grep sealert
Aug  4 22:13:38 localhost setroubleshoot[3553]: SELinux is preventing /usr/sbin/httpd
  from name_bind access on the tcp_socket port 377. For complete SELinux messages
  run: sealert -l 525fe157-a16b-47c4-ad8d-11bad86c9e9a
# 重點是找到 sealert 這個關鍵字!後續的指令直接執行就是答案!

# 7. 將找到的 sealert 指令執行,並依據提示處理問題
[root@localhost ~]# sealert -l 525fe157-a16b-47c4-ad8d-11bad86c9e9a
SELinux is preventing /usr/sbin/httpd from name_bind access on the tcp_socket port 377.

*****  Plugin bind_ports (99.5 confidence) suggests   ************************

If you want to allow /usr/sbin/httpd to bind to network port 377
Then you need to modify the port type.
Do
# semanage port -a -t PORT_TYPE -p tcp 377
    where PORT_TYPE is one of the following: http_cache_port_t, http_port_t, ...

*****  Plugin catchall (1.49 confidence) suggests   **************************
....

其實解決問題的方案不止一種,因此,上述的 sealert 提供的方式中,會有好幾個解決方案,不過,解決方案總是有輕重緩急! 所以,最好選擇信賴度最高的方案來解決較佳!所以,當然是選上面 99.5% 信賴度的啊!然後,又看到底下的指令, 就是『 semanage port -a -t PORT_TYPE -p tcp 377 』這一段,你應該會覺得很開心!因為剛剛才學過啊! 只是, PORT_TYPE 必須要選擇正確的項目才行!因為我們是在處理 http 的埠口,當然最終選擇 http_port_t! 所以,整個解決方案的處理會是這樣:

[root@localhost ~]# semanage port -a -t http_port_t -p tcp 377
[root@localhost ~]# setenforce 1
[root@localhost ~]# getenforce
Enforcing
[root@localhost ~]# systemctl restart httpd
# 最終,在 Enforcing 的模式中,再次重新啟動服務!可正常啟動才會是正確的!
範例的思考也是很重要的!上面的範例中,原本鳥哥屬意的埠口是 538 (台語諧音:有三八), 但是,這個埠口竟然已經被 semanage port 所管理了!因此無法順利找到正確的解決方案!差點崩潰! 所以,未來想要找某個非正規埠口來練習時,還是得要先用底下的方法確認,不要使用已記載的埠口為佳! 『semanage port -l | grep 538』(結果沒出現任何訊息,該埠口才好應用!)

最後,讓我們使用文字型瀏覽器來看看我們的本機 (http://localhost) 有沒有順利提供服務呢?

[root@localhost ~]# curl http://localhost:377 2> /dev/null | head
<!doctype html>
<html>
  <head>
    <meta charset='utf-8'>
    <meta name='viewport' content='width=device-width, initial-scale=1'>
    <title>HTTP Server Test Page powered by: Rocky Linux</title>
    <style type="text/css">
.....
# 有看到資料輸出,就是正確的顯示啦!文字型瀏覽器只能作到這樣解析!
  • 2. 模擬狀況,當主體程序與目標資源的安全本文類型不符時

進行這個模擬時,先有個觀念,那就是,整個網頁伺服器的資料預設是放置到 /var/www/html 裡面的! 所以,如果想要讀取 http://localhost/test.txt 時,該檔案需要放置成為 /var/www/html/test.txt 才對! 現在,讓我們模擬一個錯誤!那就是,使用者在自己家目錄建立好網頁資料,然後使用 cp -a 的方式複製到網頁伺服器上! 那個 -a 很厲害啊!可能會連同 SELinux type 都複製過去~如此一來,會變怎樣呢?

  • 1. 先以 root 的身份,去自己家目錄建立名為 test.txt 的檔案
  • 2. 使用 cp -a 的方式,將該檔案複製到 /var/www/html 網頁主目錄下
  • 3. 使用 curl 去瀏覽,看看瀏覽的結果為何,再來判斷!
  • 4. 搜查 /var/log/messages 看看有沒有最近的 setrouble 輸出結果?
  • 5. 執行 sealert 之後,依據建議修改錯誤。
# 1. 先建立名為 test.txt 的檔案
[root@localhost ~]# vim ~/test.txt
I am VBird
Today: 2023/08/04

# 2. 用 cp -a,注意,記得加上 -a 喔!在這個練習底下!不要用 -r
[root@localhost ~]# cp -a ~/test.txt /var/www/html

# 3. 使用 curl 去瀏覽,記得我們的埠口在非正規喔!
[root@localhost ~]# curl http://localhost:377/test.txt
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
</body></html>
# 注意喔,錯誤訊息是『沒有權限』而不是『找不到檔案』
# 意思是,有這個檔案存在,但是你沒有權限讀取的意思!重要重要!

[root@localhost ~]# ll /var/www/html/test.txt
-rw-r--r--. 1 root root 29 Aug  4 22:20 /var/www/html/test.txt
# 但是權限是合理的!所有人都可以讀取(r)!所以,直接懷疑是 SELinux 囉!

# 4. 檢查有沒有 SELinux 的 log 錯誤!?
[root@localhost ~]# grep setrouble /var/log/messages | grep sealert
Aug  4 22:20:44 localhost setroubleshoot[3942]: SELinux is preventing /usr/sbin/httpd
  from getattr access on the file /var/www/html/test.txt. For complete SELinux messages
  run: sealert -l 95e5f3a3-e946-4f45-9560-5787ff5083f9
# 果然在最接近的時間就有一個 SELinux 的警告訊息出現了!

# 5. 執行看看 sealert 之後,設法解決問題!
[root@localhost ~]# sealert -l 95e5f3a3-e946-4f45-9560-5787ff5083f9
SELinux is preventing /usr/sbin/httpd from getattr access on the file /var/www/html/test.txt.

*****  Plugin restorecon (99.5 confidence) suggests   ************************

If you want to fix the label.
/var/www/html/test.txt default label should be httpd_sys_content_t.
Then you can run restorecon. The access attempt may have been stopped due to insufficient permissions
  to access a parent directory in which case try to change the following command accordingly.
Do
# /sbin/restorecon -v /var/www/html/test.txt
....

[root@localhost ~]# /sbin/restorecon -v /var/www/html/test.txt
Relabeled /var/www/html/test.txt from unconfined_u:object_r:admin_home_t:s0 
  to unconfined_u:object_r:httpd_sys_content_t:s0

# 6. 最後,讓我們測試一下,到底能不能成功瀏覽到資訊了?
[root@localhost ~]# curl http://localhost:377/test.txt
I am VBird
Today: 2023/08/04
# 果然就正常啦!

這邊的兩個練習都是常見的問題!請大家務必實做一次以上啊!會很有幫助喔!加油加油!

總之,你要記住,無法啟動某個服務或者是某服務無法存取某個檔案資源,先查看一下 rwx 是否正確?若正確, 先將 SELinux 變更為 permissive,然後『重複一次剛剛發生錯誤的動作』,看看有沒有正常?若有正常, 那表示問題一定來自 SELinux 了!再將 SELinux 變更為 Enforcing,之後依據 sealert 提供的方式處理錯誤, 最終,一定要『再次的重複一次剛剛發生錯誤的動作』,確定在 Enforcing 的狀態下也是正常啟動處理的! 那就沒問題了!

3.7、修改 demo1.img 系統內容

從前一章的後半段開始,我們都用 test1.img 做測試,但其實許多的軟體與服務,似乎應該都要在 demo1.img 裡面存在比較好! 否則,未來我們都會以為好像系統都安裝妥當了...結果卻是在測試環境中進行的...那就比較傷腦筋!呵呵! 所以,現在請將 test1 系統的 history 倒出來備份好,將 test1 關機之後,再啟動 demo1 看看!

# 確認 test1 關閉之後,就啟動 demo1 吧!
[root@cloud ~]# virsh create /kvm/xml/demo1.xml
Domain 'demo1' created from /kvm/xml/demo1.xml

[root@cloud ~]# arp -n -i templan
Address         HWtype  HWaddress           Flags Mask  Iface
192.168.10.53   ether   52:54:00:ed:63:1f   C           templan

# 登入 192.168.10.53,取得控制權之後,就開始將 SELinux 以及前一章需要的軟體裝上吧!
[root@cloud ~]# ssh vbird@192.168.10.53

[vbird@localhost ~]$ sudo su -

[root@localhost ~]# yum -y install fio iperf3 policycoreutils-python-utils setroubleshoot*

[root@localhost ~]# poweroff

很簡單!這樣就好了!未來使用這個 demo1.img 作為 backing_file 所設計出來的 overlay, 裡頭就擁有 SELinux 運作所需要的 debug 軟體了!喔耶!

修改歷史:
  • 2022/05/15:原本要將 SELinux 跟前一章節放在一起,因為篇幅太小的緣故。後來想想,篇幅小也沒關係,各自獨立單元,介紹會更清楚!
  • 2022/07/12:根據討論區網友的提示,發現 Red Hat 提供的文件中,SELinux 應該是在權限判斷之後才進行的!
  • 2022/07/22:重新潤稿一次,也在 RockyLinux 9 上面重現一次!資料處理挺傷腦筋!哈哈!
  • 2023/08/04:主要是檢查看看有沒有其他錯誤!然後增加修改 demo1.img 的狀態而已。
2022/05/15以來統計人數
計數器
其他連結
環境工程模式篇
鳥園討論區
鳥哥舊站

今日 人數統計
昨日 人數統計
本月 人數統計
上月 人數統計