哈嘍大家好,我是鹹魚 今天我們來看一個關於 `Keepalived` 檢測腳本無法執行的問題 一位粉絲後臺私信我,說他部署的 `keepalived` 集群 `vrrp_script` 模塊中的腳本執行失敗了,但是手動執行這個腳本卻沒有任何問題 ![image](https://img2023.cn ...
哈嘍大家好,我是鹹魚
今天我們來看一個關於 Keepalived
檢測腳本無法執行的問題
一位粉絲後臺私信我,說他部署的 keepalived
集群 vrrp_script
模塊中的腳本執行失敗了,但是手動執行這個腳本卻沒有任何問題
這個問題也是鹹魚第一次遇到,為了能讓更多的小伙伴以後不會踩這個坑,便有了今天這篇文章
前言
在正式開始之前,我們先來簡單複習一下 Keepalived
中的資源檢測功能
vrrp_script 模塊
在 Keepalived
中,vrrp_script
模塊是用於定義和配置虛擬路由冗餘協議(VRRP)的自定義腳本檢查,這個模塊專門用於對集群中的服務資源進行監控
與 vrrp_script
模塊搭配使用的是 track_script
模塊,這個模塊中可以引入監控腳本、命令組合、Shell 語句來實現對服務資源的監控
track_script
通過調用vrrp_script
,可以靈活地定義需要監測的服務或資源,例如網路連接、服務狀態、系統資源等當監測到故障時,Keepalived 可以觸髮狀態轉移,將主節點切換到備用節點,以確保服務的高可用性
- 通過
killall -l
命令監測
killall
命令會發送一個信號給進程,以信號 0 為例,如果發現進程關閉或者異常,將返回狀態碼 1,反之進程運行正常,狀態碼返回0
vrrp_script nginx_check {
script "killall -0 nginx"
interval 2
}
track_script {
nginx_check
}
- 通過埠監測
檢測埠的運行狀態也是較常見的監控方式
vrrp_script nginx_check {
script "</dev/tcp/127.0.0.1/80"
interval 2
fall 2
rise 1
}
track_script {
nginx_check
}
其中 fall
表示檢測到失敗的最大次數(如果請求失敗兩次,就認為該節點發生了故障)
rise
表示如果請求一次成功,就認為該節點恢復正常
- 通過 shell 語句監測
vrrp_script nginx_check {
script "if [ $(pidof nginx | wc -l) -eq 0 ]; then exit 1; else exit 0; fi"
interval 2
fall 2
rise 1
}
track_script {
nginx_check
}
- 通過腳本監測
vrrp_script nginx_check {
script "/etc/keepalived/nginx_check.sh"
interval 2
fall 2
rise 1
}
track_script {
nginx_check
}
問題
在介紹完了 keepalived
的監測功能之後,我們來看下這個問題
我根據他的描述復現了這個場景:keepalived
通過監測上游網路來判斷該節點是否正常運行,如果到上游的網路不通,則認為該節點發生了故障
檢測腳本如下:
[root@localhost ~]# cat /etc/keepalived/check.sh
#!/bin/bash
# 檢查上行鏈路
check_route="192.168.149.135"
ping -c 3 $check_route >/dev/null 2>&1
result=$?
echo "${date}----checking..... result:${result}" >> /tools/log.log
if [ $? -eq 0 ]; then
exit 0 # 正常
else
exit 1 # 鏈路異常
fi
一切準備就緒之後啟動 keepalived
服務發現報 Keepalived_vrrp[2653]: /etc/keepalived/check.sh exited with status 1
[root@localhost ~]# systemctl status keepalived.service
● keepalived.service - LVS and VRRP High Availability Monitor
Loaded: loaded (/usr/lib/systemd/system/keepalived.service; disabled; vendor preset: disabled)
Active: active (running) since 三 2023-08-14 23:53:49 CST; 4min 56s ago
...
8月 14 23:53:49 localhost.localdomain Keepalived_vrrp[2653]: /etc/keepalived/check.sh exited with status 1
...
說明腳本沒有執行成功,返回狀態碼 1 了,我嘗試著手動執行,發現腳本沒有任何問題
[root@localhost ~]# sh /etc/keepalived/check.sh
[root@localhost ~]# echo $?
0
排查
首先看一下 /var/log/messages
(如果 keepalived
沒有專門指定日誌文件路徑,這個便是預設的日誌文件路徑)
...
Aug 14 23:53:49 localhost Keepalived_vrrp[17889]: SECURITY VIOLATION - scripts are being executed but script_security not enabled
...
Aug 14 23:53:49 localhost Keepalived_vrrp[17889]: /etc/keepalived/check.sh exited with status 1
...
SECURITY VIOLATION - scripts are being executed but script_security not enabled
這條信息引起了我的註意
”安全違規-腳本正在執行,但 script_security 未啟用“,看輸出應該是 keepalived
進程想要執行該腳本,但是受到了安全限制
既然是跟系統安全相關的,我們就先來看看這個腳本的許可權吧
# 查看腳本許可權
[root@localhost ~]# ll /etc/keepalived/check.sh
-rwxr-xr-x. 1 root root 281 8月 9 15:52 /etc/keepalived/check.sh
# 查看是 keepalived 進程的屬主
[root@localhost ~]# ps -ef | grep keep
root 19163 1 0 01:00 ? 00:00:00 /usr/sbin/keepalived -D
...
由上面的輸出我們可以得知 keepalived
進程的屬主是 root
,而 root
用戶是可以去執行這個腳本的(有 x
許可權)
許可權沒問題,我們再來查看下 /var/log/audit/audit.log
/var/log/audit/audit.log
是一個存儲系統審計日誌的文件這個文件記錄了系統中發生的各種安全事件、用戶操作和系統行為,以及與安全相關的信息
系統審計日誌是用來監控系統活動、檢測潛在的安全問題和追蹤系統事件的重要工具之一
...
type=SYSCALL msg=audit(1692033018.624:12555): arch=c000003e syscall=4 success=no exit=-13 a0=190abc0 a1=7ffd028eafc0 a2=7ffd028eafc0 a3=7ffd028eaae0 items=0 ppid=19472 pid=19473 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="check.sh" exe="/usr/bin/bash" subj=system_u:system_r:keepalived_t:s0 key=(null)
type=AVC msg=audit(1692033018.624:12555): avc: denied { getattr } for pid=19473 comm="check.sh" path="/usr/bin/ping" dev="dm-0" ino=50555278 scontext=system_u:system_r:keepalived_t:s0 tcontext=system_u:object_r:ping_exec_t:s0 tclass=file permissive=0
...
我們現在來看一下上面日誌輸出表示什麼
先看第一段:
type=SYSCALL
: 這個部分是系統調用事件類型。arch=c000003e syscall=4 success=no exit=-13
: 這裡描述了系統調用的詳細信息,包括系統調用號、是否成功等
subj=system_u:system_r:keepalived_t:s0
:描述了進程的安全上下文信息- 後面內容則是一些更多與系統調用相關的信息,如參數、進程信息、用戶信息等
再看第二段:
type=AVC
: 這個部分指示了事件類型為 AVC(Access Vector Cache),是 SELinux 的訪問控制事件,
avc: denied { getattr }
: 表示操作是一個getattr
(獲取屬性)操作,但是被拒絕。pid=19473 comm="check.sh"
: 進程 ID 是 19473,進程名稱是check.sh
。path="/usr/bin/ping"
: 文件路徑是/usr/bin/ping
,表示被操作的文件。scontext=system_u:system_r:keepalived_t:s0
: 發起操作的進程的安全上下文。tcontext=system_u:object_r:ping_exec_t:s0
: 被操作文件的安全上下文。tclass=file
: 被操作對象的類型是文件。permissive=0
: 表示 SELinux 不是處於寬鬆模式
總結一下:
這個是 SELinux 的審計日誌,這兩條日誌記錄了一個操作,即 keepalived 進程試圖通過執行 check.sh
" 腳本訪問 /usr/bin/ping
文件,但被 SELinux 拒絕了
解決
排查到這思路就逐漸清晰起來了,這是因為 SELinux 許可權導致的,在解決這個問題之前,我們先來簡單複習一下 SELinux (後面我會專門寫一篇文章來介紹 SELinux)
SELinux(Security Enhanced Linux,安全強化的 Linux) ,由美國國家安全局(NSA)開發的
為什麼要開發 SELinux
我們知道系統的賬戶主要分為系統管理員(root)和普通用戶,這兩種身份能否使用系統上面的文件資源則與 rwx 許可權設置有關
所以當某個進程想要對文件進行讀寫操作時,系統就會根據該進程的屬主和屬組去比對文件許可權,只有通過許可權檢查,才能夠進一步操作文件
這種方式被稱為自主訪問控制(Discretionary Access Control, DAC),DAC 是 Linux 操作系統中的一種基本許可權控制機制,用於限制用戶對系統資源的訪問許可權
但是 DAC 的一大問題就是當用戶獲取到進程之後,他可以通過這個進程與自己所獲得的許可權去處理對應的文件資源
萬一用戶對系統不熟悉,就很容易導致資源誤用的問題出現
舉個例子,你們公司的新人為了自身方便,將網頁所在目錄
/var/www/html
目錄的許可權設置成了 777,則代表所有進程都可以對該目錄進行讀寫如果黑客接觸到了某個進程,就極有可能向你的系統裡面寫入某些東西
為了避免 DAC 的問題,便有了 SELinux
SELinux MAC 機制
SELinux 引入了強制訪問控制(Mandatory Access Control, MAC)機制
強制訪問控制(MAC,Mandatory Access Control)是 Linux 操作系統中一種更加嚴格和細粒度的訪問控制機制,用於加強對系統資源的保護和控制
MAC 有趣的地方在於它可以針對特定的進程與特定的文件資源來管理許可權,即使你是 root 用戶,在使用不同的進程時你所能獲取的許可權也不一定是 root
安全上下文
前面我們知道,SELinux 是通過 MAC 的方式來管理進程的許可權的
它控制的主體是【進程】,而【目標】則是該【進程】能否讀取的文件資源
- 主體
SELinux 主要管理的就是進程,進程和主體可以畫上等號
- 目標
主體能否讀寫的目標一般就是文件資源
除了策略指定之外,主體與目標的安全上下文必須一致才能夠順利讀寫
安全上下文有點類似於文件系統的 rwx
,如果設置錯誤,主體就無法讀寫目標資源了
安全上下文存放在文件的 inode 內,可以通過 ll -Z
命令去查看(前提是 SELinux 是開啟著的)
回到我們的案例上來,日誌輸出說進程 check.sh
試圖獲取 /usr/bin/ping
的屬性(getattr
)操作,但被 SELinux 拒絕了
但是我們發現沒有這個進程
[root@localhost ~]# ps -ef | grep check
也就是說,由於 SELinux 許可權問題 ,keepalived 一開始想要調用 check.sh
腳本就失敗了
我們分別來看一下 keepalived
進程和 check.sh
的安全上下文
[root@localhost ~]# ps -eZ | grep keepalived
system_u:system_r:keepalived_t:s0 19609 ? 00:00:00 keepalived
[root@localhost ~]# ll -Z /etc/keepalived/check.sh
-rwxr-xr-x. root root system_u:object_r:etc_t:s0 /etc/keepalived/check.sh
可以看到 keepalived
進程的安全上下文類型為 keepalived_t
;而 check.sh
的安全上下文是 etc_t
即——主體與目標的安全上下文並不一致,就算有 rwx
許可權也無法執行
- 解決方案一:關閉 SELinux
簡單粗暴,直接關閉 SELinux ,就沒有這麼多亂七八糟的限制了
# 以 CentOS 7 為例
# 臨時關閉
[root@localhost ~]# setenforce 0
#永久關閉,將 SELINUX=enforcing 改為 SELINUX=disabled,然後保存退出
[root@localhost ~]# sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
- 解決方案二(失敗了):修改
check.sh
的安全上下文與keepalived
一致
我想把 check.sh
的安全上下文改成與 keepalived
一致,可是失敗了,有小伙伴知道原因嗎
[root@localhost ~]# chcon -t keepalived_t /etc/keepalived/check.sh
chcon: 改變"/etc/keepalived/check.sh" 的環境到"system_u:object_r:keepalived_t:s0" 失敗: 許可權不夠
- 解決方案三:修改
check.sh
的安全上下文
依舊是修改 check.sh
的安全上下文,不過這次參考了資料
[root@localhost ~]# chcon -t keepalived_unconfined_script_exec_t /etc/keepalived/check.sh
keepalived_unconfined_script_exec_t
是一個在 SELinux 中用於標識 Keepalived 執行未限制腳本的上下文,這個上下文允許 Keepalived 進程在執行腳本時繞過一些 SELinux 限制,從而可以在需要的情況下執行腳本
- 解決方案四:將腳本轉移到
/usr/libexec/keepalived
目錄中
# keepalived 配置
vrrp_script nginx_check {
script "/usr/libexec/keepalived/check.sh"
interval 2
fall 2
rise 1
}
這個目錄的安全上下文是 keepalived_unconfined_script_exec_t
,與解決方案三同理
[root@localhost ~]# ll -Z /usr/libexec/keepalived/ -d
drwxr-xr-x. root root system_u:object_r:keepalived_unconfined_script_exec_t:s0 /usr/libexec/keepalived/
- 解決方案五:
keepalived
全局配置添加enable_script_security
欄位
加了這個欄位意味著如果腳本路徑的任一部分對於非 root 用戶來說,都具有可寫許可權,則不會以 root 身份運行腳本
以非 root 身份運行的腳本就能夠通過 SELinux 的審查嗎?這一塊我不太懂,有懂的小伙伴可以告訴我
global_defs {
...
enable_script_security
...
}
參考資料:
https://github.com/acassen/keepalived/issues/1322
https://serverfault.com/questions/709428/track-script-doesnt-work-after-keepalived-update
https://www.mankier.com/8/keepalived_selinux
https://linux.vbird.org/linux_basic/centos7/0440processcontrol.php#selinux