1. PAM 簡介
PAM 的全稱為“可插拔認證模塊(Pluggable Authentication Modules)”。設計的初衷是將不同的底層認證機制集中到一個高層次的API中,從而省去開發人員自己去設計和實現各種繁雜的認證機制的麻煩。如果沒有 PAM ,認證功能只能寫在各個應用程序中,一旦要修改某個認證方法,開發人員可能不得不重寫程序,然后重新編譯程序并安裝;有了 PAM ,認證的工作都交給 PAM ,程序主體便可以不再關注認證問題了:“ PAM 允許你進來,那你就進來吧。”
PAM 機制最初由 Sun 公司提出,并在其 Solaris 系統上實現。后來,各個版本的 UNIX 以及 Linux 也陸續增加了對它的支持。Linux-PAM 便是 PAM 在 Linux 上的實現,獲得了幾乎所有主流 Linux 發行版的支持。Linux-PAM 的目標同 PAM 一致,都是為了給程序的開發人員提供一套統一的認證接口。本系列中對 PAM 的介紹和實驗,如無特別說明,一般都特指 Linux-PAM,實驗的環境也均是 Linux。
我們先用一個例子來直觀感受一下 PAM 。
su
是一個很常用的 Linux 命令,可以讓我們從一個用戶切換到另一個用戶。我們都知道,當用戶使用 root 賬號登錄時,su
到別的用戶是不需要密碼的,而從其他用戶 su
到 root 則需要輸入密碼。在用 su
命令切換用戶的過程中, su
做了兩件事:認證(是否是 root、不是 root 的話是否有目標用戶的密碼)和啟動相應的 Shell。
讓我們來關注一下 su
的認證功能。按照正常的邏輯, su
的開發人員很可能會自己寫出認證的功能:先判斷是不是 root,是則判定認證通過;不是則要求用戶輸入目標賬號的密碼,匹配成功則認證通過,否則不通過。這套邏輯并不復雜,開發人員開發出來便是了。
過了幾天,用戶提出了這樣的一個需求:運維團隊都屬于 wheel
組,能不能讓 wheel
組的用戶也能不輸入密碼而使用 su
切換?看起來也不是什么特別困難的需求,開發人員本可以滿足就是了。但是如果再過幾天,運維小張考慮到安全想要 su
有短信驗證碼功能,而用戶小王為了方便測試想要一個完全不用密碼的 su
。認證需求的差異化越來越明顯,開發人員的工作也變得越來越困難。
這時,PAM 出現了。PAM 對開發人員說:“認證的事情交給我,你只要告訴我你想做用戶認證就好,余下的事情由我來解決,能不能通過由我來說了算。”它又對運維人員說:“你們來我這里編寫你們想要的針對 su
的認證策略吧,我將充分保證功能的靈活。”
這下好辦了。su
的開發人員可以專注地為用戶啟動 Shell 服務,而不需要關心用戶認證的細節了;用戶或復雜、或簡單的認證需求也都得到了滿足。真是皆大歡喜。
Linux-PAM 工作的“類別”(type)
PAM 的具體工作主要有以下四種類別(type):account
,auth
,password
以及 session
。這里,我們用非定義化的語言來簡單解釋一下這四種類別。
-
account
:在用戶能不能使用某服務上具有發言權,但不負責身份認證。比如,account
這個 type 可以檢查用戶能不能在一天的某個時間段登錄系統、這個用戶有沒有過期、以及當前的登錄用戶數是否已經飽和等等。通常情況下,在登錄系統時,如果你連account
這個條件都沒滿足的話,即便有密碼也還是進不去系統的。 -
auth
:驗證“你的確是你”的 type 。一般來說,詢問你密碼的就是這個 type。假如你的驗證方式有很多,比如一次性密碼、指紋、虹膜等等,都應該添加在auth
下。auth
做的另外一件事情是權限授予,比如賦給用戶某個組的組員身份等等。 -
password
:主要負責和密碼有關的工作。修改密碼的時候有時會提示“密碼不夠長”、“密碼是個常用單詞”之類的,就是在這里設置的。在這里還設置了保存密碼時使用了哪種加密方式(比如現在常用的SHA-512
)。請注意,這里的密碼不局限于/etc/shadow
中的密碼,有關認證 token 的管理都應該在此設置:如果你使用指紋登錄 Linux,在設置新指紋時,如果希望首先驗證這是人的指紋而不是狗的指紋,也應該放在這里。 -
session
:一個“忙前忙后”的 type,它要在某個服務提供給用戶之前和之后做各種工作。比如用戶登錄之前要將用戶家目錄準備好,或者在用戶登錄之后輸出motd
等等。
請注意:PAM 不僅僅在用戶登錄時才發揮作用,sudo
命令,su
命令,passwd
命令都會用到 PAM。前文中所有提及“登錄”的地方都僅僅是舉例,您完全可以用其他需要用戶認證的服務(或者命令)去舉例,從而更全面地理解 PAM。
2. Linux-PAM 的配置文件綜述
PAM 的各個模塊一般存放在 /lib/security/
或 /lib64/security/
中,以動態庫文件的形式存在(可參閱 dlopen(3)
),文件名格式一般為 pam_*.so
。PAM 的配置文件可以是 /etc/pam.conf
這一個文件,也可以是 /etc/pam.d/
文件夾內的多個文件。如果 /etc/pam.d/
這個文件夾存在,Linux-PAM 將自動忽略 /etc/pam.conf
。
/etc/pam.conf
類型的格式如下:
服務名稱 工作類別 控制模式 模塊路徑 模塊參數
/etc/pam.d/
類型的配置文件通常以每一個使用 PAM 的程序的名稱來命令。比如 /etc/pam.d/su
,/etc/pam.d/login
等等。還有些配置文件比較通用,經常被別的配置文件引用,也放在這個文件夾下,比如 /etc/pam.d/system-auth
。這些文件的格式都保持一致:
工作類別 控制模式 模塊路徑 模塊參數
不難看出,文件夾形式的配置文件中只是沒有了服務名稱這一列:服務名稱已經是文件名了。
由于很難在時下的發行版本中找到使用 /etc/pam.conf
這一獨立文件作為 PAM 配置的例子,此處僅就 /etc/pam.d/
格式舉例。在筆者安裝的 CentOS 7.2.1511 x64 中,/etc/pam.d/login
的內容如下:
#%PAM-1.0
auth [user_unknown=ignore success=ok ignore=ignore default=bad] pam_securetty.so
auth substack system-auth
auth include postlogin
account required pam_nologin.so
account include system-auth
password include system-auth
# pam_selinux.so close should be the first session rule
session required pam_selinux.so close
session required pam_loginuid.so
session optional pam_console.so
# pam_selinux.so open should only be followed by sessions to be executed in the user context
session required pam_selinux.so open
session required pam_namespace.so
session optional pam_keyinit.so force revoke
session include system-auth
session include postlogin
-session optional pam_ck_connector.so
#
表示注釋。
每一行代表一條規則。但也可以用 \
來放在行末,來連接該行和下一行。
例子的最后一行開頭有一個短橫線 -
,意思是如果找不到這個模塊,導致無法被加載時,這一事件不會被記錄在日志中。這個功能適用于那些認證時非必需的、安裝時可能沒被安裝進系統的模塊。
工作類別(type)、流程棧(stack)和控制模式(control)
我們在[第一篇]({% post_url 2016-03-30-pam-tutorial-1-intro %})中接觸了 Linux-PAM 的四種工作類別(type)。在上面的例子中,工作類別作為第一列出現。
講到這里,我們有必要聊一聊 PAM 的流程棧(stack)概念:它是認證時執行步驟和規則的堆疊。在某個服務的配置文件中,它體現在了配置文件中的自上而下的執行順序中。棧是可以被引用的,即在一個棧(或者流程)中嵌入另一個棧。我們之后和它會有更多的接觸。
第二列為控制模式(control),用于定義各個認證模塊在給出各種結果時 PAM 的行為,或者調用在別的配置文件中定義的認證流程棧。該列有兩種形式,一種是比較常見的“關鍵字”模式,另一種則是用方括號([]
)包含的“返回值=行為”模式。
“關鍵字”模式下,有以下幾種控制模式:
-
required
:如果本條目沒有被滿足,那最終本次認證一定失敗,但認證過程不因此打斷。整個棧運行完畢之后才會返回(已經注定了的)“認證失敗”信號。 -
requisite
:如果本條目沒有被滿足,那本次認證一定失敗,而且整個棧立即中止并返回錯誤信號。 -
sufficient
:如果本條目的條件被滿足,且本條目之前沒有任何required
條目失敗,則立即返回“認證成功”信號;如果對本條目的驗證失敗,不對結果造成影響。 -
optional
:該條目僅在整個棧中只有這一個條目時才有決定性作用,否則無論該條驗證成功與否都和最終結果無關。 -
include
:將其他配置文件中的流程棧包含在當前的位置,就好像將其他配置文件中的內容復制粘貼到這里一樣。 -
substack
:運行其他配置文件中的流程,并將整個運行結果作為該行的結果進行輸出。該模式和include
的不同點在于認證結果的作用域:如果某個流程棧include
了一個帶requisite
的棧,這個requisite
失敗將直接導致認證失敗,同時退出棧;而某個流程棧substack
了同樣的棧時,requisite
的失敗只會導致這個子棧返回失敗信號,母棧并不會在此退出。
“返回值=行為”模式則更為復雜,其格式如下:
[value1=action1 value2=action2 ...]
其中,valueN
的值是各個認證模塊執行之后的返回值。有 success
、user_unknown
、new_authtok_reqd
、default
等等數十種。其中,default
代表其他所有沒有明確說明的返回值。返回值結果清單可以在 /usr/include/security/_pam_types.h
中找到,也可以查詢 pam(3)
獲取詳細描述。
流程棧中很可能有多個驗證規則,每條驗證的返回值可能不盡相同,那么到底哪一個驗證規則能作為最終的結果呢?這就需要 actionN
的值來決定了。actionN
的值有以下幾種:
-
ignore
:在一個棧中有多個認證條目的情況下,如果標記ignore
的返回值被命中,那么這條返回值不會對最終的認證結果產生影響。 -
bad
:標記bad
的返回值被命中時,最終的認證結果注定會失敗。此外,如果這條bad
的返回值是整個棧的第一個失敗項,那么整個棧的返回值一定是這個返回值,后面的認證無論結果怎樣都改變不了現狀了。 -
die
:標記die
的返回值被命中時,馬上退出棧并宣告失敗。整個返回值為這個die
的返回值。 -
ok
:在一個棧的運行過程中,如果ok
前面沒有返回值,或者前面的返回值為PAM_SUCCESS
,那么這個標記了ok
的返回值將覆蓋前面的返回值。但如果前面執行過的驗證中有最終將導致失敗的返回值,那ok
標記的值將不會起作用。 -
done
:在前面沒有bad
值被命中的情況下,done
值被命中之后將馬上被返回,并退出整個棧。 -
N
(一個自然數):功效和ok
類似,并且會跳過接下來的 N 個驗證步驟。如果N = 0
則和ok
完全相同。 -
reset
:清空之前生效的返回值,并且從下面的驗證起重新開始。
我們在前文中已經介紹了控制模式(contro)的“關鍵字”模式。實際上,“關鍵字”模式可以等效地用“返回值=行為”模式來表示。具體的對應如下:
-
required
:
[success=ok new_authtok_reqd=ok ignore=ignore default=bad]
-
requisite
:
[success=ok new_authtok_reqd=ok ignore=ignore default=die]
-
sufficient
:
[success=done new_authtok_reqd=done default=ignore]
-
optional
:
[success=ok new_authtok_reqd=ok default=ignore]
模塊路徑和模塊參數
正如前文所述,模塊一般保存在 /lib/security
或 /lib64/security
中(取決于操作系統位數)。Linux-PAM 配置文件中的模塊位置可以是相對于上述文件夾的相對路徑,也可以是文件的全路徑。
模塊參數用空格與模塊路徑相隔。該參數將只和特定模塊相關,因此某個模塊的文檔中一定包含其參數的信息。如果需要在單個參數中使用空格,可以將整個參數用方括號([]
)包裹起來。