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 是返回值,合法的取值有:
successopen_errsymbol_errservice_errsystem_errbuf_errperm_deniedauth_errcred_insufficientauthinfo_unavailuser_unknownmaxtriesnew_authtok_reqdacct_expiredsession_errcred_unavailcred_expiredcred_errno_module_dataconv_errauthtok_errauthtok_recover_errauthtok_lock_busyauthtok_disable_agingtry_againignoreabortauthtok_expiredmodule_unknownbad_itemconv_againincompletedefault
这些返回值(除了 success)具体的意义则是 Module 相关的,可以通过阅读文档或源代码得到。
act 则是采取的动作,合法的取值有:
ignorebaddieokdone- 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_UNDEFok时,当r为PAM_IGNORE时,不处理;否则,当impression为_PAM_UNDEF时,更新impression为_PAM_POSITIVE,并将status更新为r;当impression已经是_PAM_POSITIVE且status是PAM_SUCCESS时,将status更新为rdone时,同ok,若impression为_PAM_POSITIVE,则终止处理,转 11bad时,若 impression 已经是_PAM_NEGATIVE,则不作处理;否则将 impression 置为_PAM_NEGATIVE,若r是PAM_IGNORE则将status置为PAM_PERM_DENIED,否则将status置为rdie时,同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 配置。