PAM 配置简介
写这篇文章主要是想总结一下近来折腾 PAM 配置的收获和感想。
背景
PAM 是用来进行鉴定授权的一套框架,其主要目的就是分离这三个东西:
- 有鉴定需求的应用程序
- 实施的鉴定方法
- 对鉴定方法的组合策略
那么每一个这样的应用程序就是一个 PAM Application,一种鉴定方法就是一个 PAM Moudule,而由用户配置的鉴定方法组合策略,则是 PAM Configuration
基本流程
PAM 有四组相互独立的功能,分别是鉴定(Auth)、账户管理 (Account)、会话管理(Session)、密码修改(Password)
这四个功能的用途可以从名字上略知一二,不过我先按下不表。因为这四个功能是相互独立的,即 pam.conf
里是分开写的,Module 里这几个接口是分开提供的,Application 则是分开调用这些接口的。在讨论 pam 执行流程的时候,区分这些功能没有什么太大意义。
那么一个功能是怎么被使用起来的呢?这个就是要说到的基本流程了。 从应用程序的角度,PAM 工作的基本流程如下:
- Application 收集如下信息:要鉴定的用户名(username);要鉴定的服务名(如 login, sshd 等);
- Application 开启一个 PAM 事务,初始化 PAM;
- 调用某个 PAM 功能;
- 根据返回值判断功能是否成功,如果失败,则可以根据返回值判断出错原因;
- 关闭 PAM 事务;
- 结束。
就是这么简单。有人可能会问:我的密码是怎么输进去的呢?收集密码的功能是 PAM Module 具体操作的,当 PAM Module 想要收集密码的时候,会通知 PAM,PAM 则会调用事先 Application 注册的回调函数来收集密码。这个回调函数有可能是打印提示符,从标准输入读入密码(比如 login su sudo 等),也可能是向远程客户端发出收集密码的指令(比如 ssh),具体怎么收集密码,是 Application 实现的。
当 Application 去调用某个 PAM 功能时,PAM 会去依照 pam.conf
里设定的规则,依次载入并调用配置文件里记载的若干 Module,于是每个 Module 的相应功能被调用,并进行一些操作,返回一个返回值。PAM 根据一定的规则,综合这些 Module 的返回值,最终得出一个“总的”返回值,返回给 Application。
而 pam.conf
则是由系统管理员配置的,它控制着模块调用的顺序和规则,以及“总的”返回值得出的方式。
pam.conf
overview
pam.conf
一般保存在 /etc/pam.d/
下,每个 Service 的配置文件都以其名字命名。例如 /etc/pam.d/sshd
。习惯上,一个使用 PAM 的 Application 的 Service 名字一般取作其可执行文件的名字,但这并不绝对,因为一个 Application 究竟使用什么作为 Service 名字,是这个 Application 在初始化 PAM 事务的时候,作为参数传给传给 PAM 的。
配置文件举例如下:
1 | # /etc/pam.d/su |
相信你已经看出来了,这个文件每一行就是一条配置,每条配置由这么几项组成:
- 功能名,这个我之前有说过,一共有四种,分别是
auth
、account
、password
、session
;现在我们只需要知道这四种功能相互独立,处理逻辑是一致的就好了,我们暂时不需要管着四种功能具体是干什么的。 - 控制标记。这个很关键,主要用于控制模块调用的顺序和返回值的生成。取值有
optional
、sufficient
、required
、requisite
、binding
- 模块。这个就是指定的 PAM Module,如果不指定路径,则会自动在默认路径中搜素。否则使用指定的绝对路径。
- 其他参数。用空格分开的其他参数,这些参数会被全权交给 PAM Module 处理。
此外还有一种语法是:
1 | function-class include other-service-name |
作用是将其它的 service 直接包含进来。
PAM Module 调用流程
下面则是比较关键的一个环节,就是我讲了这么半天的“模块调用的顺序和返回值的生成”。
模块的调用和生成返回值遵从以下过程:
- 设定要执行的功能 (Function)
- 取出该功能相应的 PAM 配置链(chain)
- 设定返回值
ret
为PAM_SUCCESS
- 设定出错标记
fail
为false
- 设定成功次数
success
为0
- 从前向后依次遍历配置链,调用相应配置项的 PAM Module 的相应功能函数。
- PAM Module 做一些事情,给出返回值
- 获得该模块的返回值
r
- 若
r
为PAM_IGNORE
则表示该模块希望 PAM 忽略这一结果,于是转 6, 继续处理下一个配置项;若都处理完毕,则转 12 当控制标记为:
optional
时,若r
为PAM_SUCCESS
,则success
++ ;否则不处理required
时,若r
为PAM_SUCCESS
,则success
++ 。当r
为其他值时,若fail
为false
,则fail
置为true
,且将ret
置为r
;否则不处理requisite
时,若r
为PAM_SUCCESS
则同required
;否则同requisite
;并立刻终止遍历,转 12sufficient
时,若r
为PAM_SUCCESS
则success
++,同时,若fail
为false
,则终止遍历,转 12。若r
为其他值,则不作处理。binding
时,若r
为PAM_SUCCESS
则同sufficient
,反之同required
转 6,继续处理下一条配置项
- 若
success
为0
但ret
为PAM_SUCCESS
,则将ret
置为PAM_SYSTEM_ERR
- 返回
ret
总结一下,PAM 配置执行过程中有这么几个要点:
- 按照 PAM 配置文件从前向后依次调用 PAM Module
- 每个 PAM Module 都有自己的一个返回值
optional
的模块会被调用,但结果会被忽略- 一旦有一个
required
的 Module 不成功,则整条配置链注定失败,且返回值就是这个 Module 的返回值,但后边的模块都会被调用,它们的返回值会被忽略(这主要是为了避免被知道是哪一个 Module 失败了) - 一旦有一个
requisite
的 Module 不成功,则整条配置链会被立刻终止执行,但返回值视情况而定。若之前已经有required
的 Module 不成功,那么返回值取之前的那个返回值;反之则取这个 Module 的返回值 - 若一个
sufficient
的 Module 成功了,且之前没有required
的 Module 失败,则整条配置链停止执行,鉴定成功;若之前有required
的 Module 失败,则会忽略这个成功的结果,继续执行配置链 bind
的 Module 成功时,处理方法和sufficient
一致;失败时,和required
一致- 若要鉴定成功,则必须至少一个 Module 成功。
因此,某些资料有一些“sufficient 是鉴定成功的充分条件”的说法是不准确的。
四种功能
终于回到这四种功能上了。首先这四种功能,原则上,PAM 模块可以作任何想做的事情。但实际上还是有一定约定和习惯的。
auth
用于鉴定用户身份。通常来说,就是用于收集秘密信息,鉴定声称是身份的 visitor 是否是该身份的持有者。鉴定方法有各种各样的,比如要求输入密码、对某种不可复制的物品进行鉴定等。account
用于管理用户登录资格。具体而言,就是在成功鉴定了来者是他所声称的那个身份后,用于确认用户在相应的上下文中是否有资格访问系统。比如限定登录时间、限定登录位置(tty)、限定远程主机等。session
则是会话管理。在打开和关闭用户会话时调用,具体用途有记录登录日志、设定登录环境、启动和停止计费等。password
用于更改用户密码。它的特殊性在于,当应用程序尝试执行修改密码的功能时,整条配置链会被执行两次,第一次用于预判是否能够修改密码(比如判断是否有足够的写入权限、如果密码存在网络上,判断网络连接是否正常等),第二次用于修改密码。这两次执行的时候,PAM 会为 Module 传入不同的 flag,因此不会混淆。当判断修改的权限时,suffcient
会被当作optional
对待
模块参数
一般的来讲,Module 的参数是由模块全权处理的,但是不同的 Module 接受的参数还是有一定共性的约定的。下面是一些常见的参数。注意,不一定所有的 Module 都接受这些参数,这些参数的意义也有可能因 Module 的不同而有所变化,请以 Module 的文档为准。
debug
:输出调试信息use_first_pass
:意味着 Module 不提示用户输入密码,而是用上一个模块输入的密码;如果之前没有模块输入密码,则使用 Application 在调用 PAM 鉴定功能前设定的密码。如果 Application 没有设定密码,则 Module 会获知这一情况,进行相应的处理。try_first_pass
:跟use_first_pass
类似,但是当上一个模块输入的密码或 Application 提供的密码不正确或不存在时,提示用户输入密码,重新鉴定。nullok
:允许空密码,或者当不存在相应的鉴定信息文件的时候通过鉴定(前者例如pam_unix.so
;后者例如pam_google_authenticator.so
,当保存有被鉴定的用户的 OTP 信息的秘密文件不存在时,通过鉴定)。
Linux-PAM 高级语法
Linux-PAM 在控制标记字段支持一种高级的语法,即跟据 Module 返回值指派处理动作:
1 | [val1=act1 val2=act2 ... default=act3] |
其中 val
是返回值,合法的取值有:
success
open_err
symbol_err
service_err
system_err
buf_err
perm_denied
auth_err
cred_insufficient
authinfo_unavail
user_unknown
maxtries
new_authtok_reqd
acct_expired
session_err
cred_unavail
cred_expired
cred_err
no_module_data
conv_err
authtok_err
authtok_recover_err
authtok_lock_busy
authtok_disable_aging
try_again
ignore
abort
authtok_expired
module_unknown
bad_item
conv_again
incomplete
default
这些返回值(除了 success
)具体的意义则是 Module 相关的,可以通过阅读文档或源代码得到。
act
则是采取的动作,合法的取值有:
ignore
bad
die
ok
done
- N (一个无符号整数)
reset
而原有的 optional
、sufficient
、requisite
、required
则等价于:
1 | required |
而整个处理流程则变成:
- 设定要执行的功能 (Function)
- 取出该功能相应的 PAM 配置链(chain)
- 设定返回值
status
为PAM_PERM_DENIED
- 设定印象
impression
为_PAM_UNDEF
- 从前向后依次遍历配置链,调用相应配置项的 PAM Module 的相应功能函数。
- PAM Module 做一些事情,给出返回值
- 获得该模块的返回值
r
- 根据返回值
r
选取采取的动作action
当
action
为:reset
时,恢复status
为PAM_PERM_DENIED
;并恢复impression
为_PAM_UNDEF
ok
时,当r
为PAM_IGNORE
时,不处理;否则,当impression
为_PAM_UNDEF
时,更新impression
为_PAM_POSITIVE
,并将status
更新为r
;当impression
已经是_PAM_POSITIVE
且status
是PAM_SUCCESS
时,将status
更新为r
done
时,同ok
,若impression
为_PAM_POSITIVE
,则终止处理,转 11bad
时,若 impression 已经是_PAM_NEGATIVE
,则不作处理;否则将 impression 置为_PAM_NEGATIVE
,若r
是PAM_IGNORE
则将status
置为PAM_PERM_DENIED
,否则将status
置为r
die
时,同bad
,但立刻终止处理,转 11ignore
时,不处理- N(无符号整数)时,跳过 N 个配置项
转 5,继续处理下一条配置项
- 若
status
是PAM_SUCCESS
但impression
不是_PAM_POSITIVE
,则将status
覆盖为PAM_PERM_DENIED
- 返回
status
摘录要点如下:
- Linux PAM 引入了
action
来指示处理方式,这样代码就清晰了不少,同时增强了灵活性。 - Linux PAM 引入
impression
来评估当前的局面。impression
仅被action
控制,与具体的 Module 返回值没有关系。 - 除了
reset
以外,impression
只能由“未知”转向“正面”(ok
和done
),或是由任意状态转向“负面”(bad
和die
)。一旦进入了“负面”,则除了被reset
以外,不会进入别的状态。 status
取决于 Module 的返回值,同时受采取的action
和当前的impression
影响。
无论action
是什么,某个 Module 的返回值只会有两种处理方式:用于覆盖当前的status
或者被忽略。注意:Module 的返回值是不会被修改的。例如:
1
auth [success=bad ignore=ignore default=done] pam_xxx.so
这条配置的后果是,当
pam_xxx.so
失败的时候,虽然动作是ok
,但是status
会被更新为一个非PAM_SUCCESS
的值,最终导致鉴定失败。当pam_xxx.so
成功的时候,虽然status
被更新为了PAM_SUCCESS
,但是由于采取的动作是bad
,impression
会转为“负面”,最终在出口处(步骤 11)PAM 把status
覆盖为PAM_PERM_DENIED
。一旦
impression
转为“负面”(bad
或die
),则status
会被更新,且被“锁定”,之后的 Module 的所有返回值都会被忽略。- 若
impression
尚为“不确定”,则ok
和done
会接受这个返回值,更新status
;若impression
为“正面”,但status
却不是PAM_SUCCESS
,则status
不会被覆盖为PAM_SUCCESS
。 reset
则会重置impression
和status
- 无论如何
status
不会被置为PAM_IGNORE
die
无论如何都会终止执行,但done
只有在“正面”的impression
时才会终止执行。- 最后,只有当
status
为PAM_SUCCESS
且impression
是“正面”的时候才会返回PAM_SUCCESS
,否则一律不能返回PAM_SUCCESS
。 - 最后+1,只有
status
会被返回给 Application,用于其判断是否成功以及错误原因,impression
是 PAM 工作时的内部状态,不算数的。
配置举例
继续立 #flag,下篇文章会介绍 yubikey 的各种奇妙用法,会有一些有意思的 pam 配置。