伺服器架設篇 - RockyLinux 8

第三章、SELinux 初探

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

最近更新時間: 2022/05/15

說到 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 /.autolabel 呢? 這個動作不是會搞很久嘛?那就是 SELinux 要重建系統內所有檔案的 SELinux 表頭資料,而需要於各檔案的 i-node 內寫入 SELinux 的緣故! 現在懂了吧!

# 前往 VM 系統,查看一下 /usr/sbin/chronyd 的 SELinux 類型
[root@localhost ~]# stat /usr/sbin/chronyd
  File: /usr/sbin/chronyd
  Size: 351416          Blocks: 688        IO Block: 4096   普通檔案
Device: fd03h/64771d    Inode: 769828      Links: 1
Access: (0755/-rwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Context: system_u:object_r:chronyd_exec_t:s0
Access: 2021-10-12 05:01:35.000000000 +0800
Modify: 2021-10-12 05:01:35.000000000 +0800
Change: 2022-05-06 11:35:56.849000000 +0800
 Birth: 2022-05-06 11:35:56.840000000 +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 351416 10月 12  2021 /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       768  0.0  0.2 151152  4628 ?        S     5月09   0:00 /usr/sbin/chronyd

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

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

上面講的是比較概念性的運作方法,實際上,SELinux 的運作,可以參考下列的流程表。 在一個主體 (subject) 想要取用某個目標 (Object) 時,就得要經過完整的 SELinux 流程之後, 才會進入到傳統的 rwx 權限存取控制。因此,剛剛提到的 MAC 並不是用在取代 DAC 喔!而是增加了額外的控制。

SELinux 運作圖示
圖 3.2-1、程序若要讀取檔案物件時,需要經過的SELinux關卡
  • 主體 (Subject):
    一般所謂的主體指的大部分就是程序,尤其是網路方面的程序,本章談到的主體大部分都是程序。
  • 目標 (Object):
    主體程序能否存取『目標資源』的意思,一般目標指的大部分都是檔案,不過,也有例外喔!例如, 主體可能會想要啟動網路埠口 (port),這時目標就變成是埠口資源了!但是,大部分指的都是檔案就是了。
  • SELinux 模式 (Mode):
    SELinux 根據開啟與否,共有三種模式,分別是關閉 (Disabled)、寬容模式 (Permissive) 與強制模式 (Enforcing)。 根據上圖的箭頭,你可以知道 disabled, permissive 模式下,其實 SELinux 並不會真的進行程序存取的管制! 只有 enforcing 模式才會真的進行各個項目的管理!
  • 政策與規則 (Policy, rules):
    由於主題程序與目標檔案數量龐大,因此 SELinux 會依據某些服務來制訂基本的存取安全性政策。 這些政策內還會有詳細的規則 (rule) 來指定不同的服務開放某些資源的存取與否。在目前的 RockyLinux 8 裡面主要提供 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 的模式下,這個存取就會出現權限不符的錯誤訊息!那無論你的權限設定如何開放, 都無法讓程序讀取到檔案喔!

在上面整體的說明中,重點在『主體』如何取得『目標』的資源存取權限!由上圖我們可以發現, (1)主體程序必須要通過 SELinux 政策內的規則放行後,就可以與目標資源進行安全性本文的比對, (2)若比對失敗則無法存取目標,若比對成功則可以開始存取目標。問題是,最終能否存取目標還是與檔案系統的 rwx 權限設定有關喔! 如此一來,加入了 SELinux 之後,出現權限不符的情況時,你就得要一步一步的分析可能的問題了!

  • 安全本文

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

# 程序的安全本文取得
[root@localhost ~]# ps -auxZ | egrep 'bash|chronyd|LABEL'
LABEL                                                 USER    ... COMMAND
system_u:system_r:chronyd_t:s0                        chrony  ... /usr/sbin/chronyd
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  5月 12  2021 /etc/chrony.keys
-rwxr-xr-x. 1 root root   system_u:object_r:chronyd_exec_t:s0 351416 10月 12  2021 /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  5月  7 16:28 /home/vbird
-rw-------. 1 root  root  system_u:object_r:admin_home_t:s0        1439  5月  6 11:38 /root/anaconda-ks.cfg

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

Identify:role:type
身份識別:角色:類型
  • 身份識別 (Identify):相當於帳號方面的身份,可以粗分為兩種類型:
    • system_u:表示系統程序方面的識別,大部分指的就是有被管理限制的情況
    • unconfined_u:表示沒有限制的情況
  • 角色 (Role):透過角色欄位,我們可以知道這個資料是屬於程序、檔案資源還是代表使用者。一般的角色有:
    • object_r:代表的是檔案或目錄等檔案資源,這應該是最常見的囉;
    • system_r:代表的就是有被管理限制的程序啦!
    • unconfined_r:表示沒有限制的情況
  • 類型 (Type):在預設的 targeted 政策中, 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 政策、規則、安全本文比對分析所管理,若達成匹配,才能夠進入後續的 rwx 存取控制,否則,就直接回報沒有權限!
除非你的 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-4.18.0-348.23.1.el8_5.x86_64 root=UUID=8ae....
# 查詢看看有沒有 selinux=[0|1] 這個關鍵字串!

# 檢查 grub.cfg 有沒有 selinux 的關鍵字
[root@localhost ~]# grep selinux /boot/efi/EFI/rocky/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 規則的觀察與修改

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

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

查詢目前所有 SELinux 規則的狀態是開啟還是關閉,最簡單可以透過 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 boolean 的簡單 help
[root@localhost ~]# semanage boolean -h
  -l, --list            列出記錄 boolean 對像類型
  -1, --on              啟用布爾值
  -0, --off             禁用布爾值

[root@localhost ~]# semanage boolean --list
SELinux 布林值              狀態  預設值 描述
....
httpd_enable_homedirs    (關閉   ,   關閉)  Allow httpd to enable homedirs
....

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

其實,還有個名為 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  9月 10  2018 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  9月 10  2018 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  5月  6 11:38 /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  9月 10  2018 hosts
  • 使用 semanage fcontext 查詢目錄/檔案預設的類型

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

# 找到 /etc/sysconfig 相關的預設 SELinux 安全本文類型
[root@localhost ~]# semanage fcontext --list | grep /etc/sysconfig
SELinux fcontext              類型          情境
/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   類型            情境
/                  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 6  5月 14 21:44 /www
-rw-r--r--. 1 root root unconfined_u:object_r:default_t:s0  6  5月 14 21:46 /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 指令

如同圖 2.3-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 軟體才行!而且, 初次安裝完畢時,可能得要重新開機才會有作用。另外,我們前一章節安裝好的虛擬機器中, 其實預設僅有開啟 systemd-journald 的日誌服務,並沒有啟用 rsyslog!因此, 我們也得要安裝 rsyslog 並且啟動它才行喔!

[root@localhost ~]# rpm -qa | grep setrouble
setroubleshoot-plugins-3.3.14-1.el8.noarch
setroubleshoot-server-3.3.24-4.el8.x86_64
# 這樣就是安裝妥當了!沒問題!不需要重新安裝!

[root@localhost ~]# yum -y install rsyslog
[root@localhost ~]# systemctl start rsyslog
[root@localhost ~]# systemctl enable rsyslog

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

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

總之,鳥哥這種老人家,還是比較習慣查詢 /var/log/rsyslog 內的資料,而不是讓日誌直接寫入 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. 修改設定檔,大約在 45 行處,修改埠口號碼
[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 -xe" 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
....
May 15 18:01:49 localhost setroubleshoot[8326]: SELinux is preventing /usr/sbin/httpd
  from name_bind access on the tcp_socket port 377. For complete SELinux messages
  run: sealert -l e6adb6df-18f3-47df-b596-15f0d6a5efba
....
# 重點是找到 sealert 這個關鍵字!後續的指令直接執行就是答案!

# 7. 將找到的 sealert 指令執行,並依據提示處理問題
[root@localhost ~]# sealert -l e6adb6df-18f3-47df-b596-15f0d6a5efba
SELinux 防止 /usr/sbin/httpd 進行 name_bind 存取於 tcp_socket port 377 上。

*****  插件 bind_ports (99.5 信賴度) 項建議   ****************************************

若您希望允許 /usr/sbin/httpd 綁定網路連接埠 377
接著 you need to modify the port type.
執行
# semanage port -a -t PORT_TYPE -p tcp 377
    PORT_TYPE 為後述之一:http_cache_port_t, http_port_t, jboss_management_port_t, ...

*****  插件 catchall (1.49 信賴度) 項建議   ******************************************
....

其實解決問題的方案不止一種,因此,上述的 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: 2022/05/15

# 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  5月 15 20:12 /var/www/html/test.txt
# 但是權限是合理的!所以,直接懷疑是 SELinux 囉!

# 4. 檢查有沒有 SELinux 的 log 錯誤!?
[root@localhost ~]# grep setrouble /var/log/messages | grep sealert
May 15 20:14:38 localhost setroubleshoot[9014]: SELinux is preventing /usr/sbin/httpd
   from getattr access on the file /var/www/html/test.txt. For complete SELinux messages
   run: sealert -l 8e56f620-d27d-458b-af74-d63f1126623c
# 果然在最接近的時間就有一個 SELinux 的警告訊息出現了!

# 5. 執行看看 sealert 之後,設法解決問題!
[root@localhost ~]# sealert -l 8e56f620-d27d-458b-af74-d63f1126623c
SELinux 防止 /usr/sbin/httpd 進行 getattr 存取於 檔案 /var/www/html/test.txt 上。

*****  插件 restorecon (99.5 信賴度) 項建議   ****************************************

若您希望修正該標籤。
/var/www/html/test.txt 預設標籤應該為 httpd_sys_content_t。
接著 您可以執行 restorecon。存取的企圖可能已經停止,因為權限不足以存取上層目錄,此情況下請嘗試更換下列指令。
執行
# /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: 2022/05/15
# 果然就正常啦!

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

總之,你要記住,無法啟動某個服務或者是某服務無法存取某個檔案資源,先查看一下 rwx 是否正確?若正確, 先將 SELinux 變更為 permissive,然後『重複一次剛剛發生錯誤的動作』,看看有沒有正常?若有正常, 那表示問題一定來自 SELinux 了!再將 SELinux 變更為 Enforcing,之後依據 sealert 提供的方式處理錯誤, 最終,一定要『再次的重複一次剛剛發生錯誤的動作』,確定在 Enforcing 的狀態下也是正常啟動處理的! 那就沒問題了!
修改歷史:
  • 2022/05/15:原本要將 SELinux 跟前一章節放在一起,因為篇幅太小的緣故。後來想想,篇幅小也沒關係,各自獨立單元,介紹會更清楚!
2022/05/15以來統計人數
計數器
其他連結
環境工程模式篇
鳥園討論區
鳥哥舊站

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