深入 Linux PAM 体系结构
本文阐述了 Linux-PAM 的概念, 同时还与读者一道分析了 Linux-PAM 的体系结构, 作者希望籍此以加深读者对 Linux-PAM
的理解, 以便对其有更深层的把握.
1. 什么是 Linux-PAM
为安全起见, 计算机系统只有经过授权的合法用户才能访问, 在这里 如何正确认证用户的真实身份 是一个关键的问题. 所谓用户认证, 就是用户向系统以一种安全的方式提交自己的身份证明, 然后由系统确认用户的身份是否属实的过程. 换句话说, 用户认证是系统的门户, 每个用户进入到系统中都必须经过认证这一道关.
最初, Linux系统的用户认证过程就像各种Unix系统的一样: 系统管理员为用户建立一个帐号并为其指定一个口令, 用户用此指定的口令登录后重新设置自己的口令, 这样用户就具有了一个只有他自己知道的秘密口令.一般情况下, 用户的口令经过加密处理后存放于 /etc/passwd
文件中.用户登录时, 登录服务程序提示用户输入其用户名和口令, 然后将口令加密并与 /etc/passwd
文件中对应帐号的加密口令进行比较, 如果口令相匹配, 说明用户的身份属实并允许此用户访问系统.这种思想基于只有用户自己知道他的口令, 所以输入的口令是正确的话, 那么系统就认定他是所声称的那个人.
后来, 还采用了许多其他的认证用户的方法, 如用于网络环境的 Kerberos 以及基于智能卡的认证系统等. 但是这些认证方案有一个通病: 实现认证功能的代码通常作为应用程序的一部分而一起编译,这样问题就来了 - 如果发现所用算法存在某些缺陷或想采用另一种认证方法时, 用户不得不重写(修改或替换)然后重新编译原程序. 很明显, 我们原先的认证方案缺乏灵活性, 这里的牵一发而动全身的情形很是让人恼火.
鉴于以上原因, 人们开始寻找一种更佳的替代方案: 一方面, 将认证功能从应用中独立出来, 单独进行模块化设计, 实现和维护; 另一方面, 为这些认证模块建立标准 API, 以便各应用程序能方便的使用它们提供的各种功能; 同时, 认证机制对其上层用户(包括应用程序和最终用户)是透明的.
直到 1995 年, SUN 的研究人员提出了一种满足以上需求的方案 - 插件式认证模块(PAM - Pluggable Authentication Modules)
机制并首次在其操作系统 Solaris 2.3
上部分实现. PAM机制采用模块化设计和插件功能, 使得我们可以轻易地在应用程序中插入新的认证模块或替换原先的组件, 而不必对应用程序做任何修改, 从而使软件的定制, 维持和升级更加轻松 - 因为认证机制与应用程序之间相对独立.应用程序可以通过 PAM API 方便的使用 PAM 提供的各种认证功能, 而不必了解太多的底层细节. 此外, PAM的易用性也较强, 主要表现在它对上层屏蔽了认证的具体细节, 所以用户不必被迫学习各种各样的认证方式, 也不必记住多个口令; 又由于它实现了多认证机制的集成问题, 所以单个程序可以轻易集成多种认证机制如 Kerberos 认证机制和 Diffie - Hellman 认证机制等, 但用户仍可以用同一个口令登录而感觉不到采取了各种不同认证方法.
在广大开发人员的努力下, 各版本的 UNIX 系统陆续提供对 PAM 的支持. 其中, Linux-PAM
是专门为 Linux 机器实现的, 包括 Caldera 1.3, 2.2
, Debian 2.2
, Turbo Linux 3.6
, Red Hat 5.0
以及 SuSE 6.2
及它们的后续版本都提供对 PAM 的支持. FreeBSD 从 3.1 版开始支持 PAM. 需要注意的是, 除了具体实现不同外, 各种版本 Unix 系统上的 PAM 的框架是相同的, 所以我们在这里介绍的 Linux-PAM
框架知识具有普遍性. 因此在下文介绍其框架的过程中可以看到, 我们并没有刻意区分 PAM
与 Linux-PAM
这两个术语.
2. PAM 的分层体系结构
PAM 为了实现其插件功能和易用性, 它采取了分层设计思想: 让各认证模块从应用程序中独立出来, 然后通过PAM API作为两者联系的纽带, 这样应用程序就可以根据需要灵活地在其中"插入"所需认证功能模块, 从而真正实现了 认证功能, 随需应变
. 实际上, 这一思路非常符合软件设计中的"高内聚, 低耦合"这一重要思想, PAM 的体系如下简图所示:
从上图可以看出, PAM API
起着承上启下的作用, 它是应用程序和认证模块之间联系的纽带: 当应用程序调用 PAM API 时, 应用接口层按照配置文件 pam.conf
的规定, 加载相应的认证模块. 然后把请求(即从应用程序那里得到的参数)传递给底层的认证模块, 这时认证模块就可以根据要求执行具体的认证操作了. 当认证模块执行完相应操作后, 将结果返回给应用接口层, 然后由接口层根据配置的具体情况将来自认证模块的应答返回给应用程序.
上面描述了 PAM 的各个组成部分, 以及它们作为整体的运作机理. 下面将对 PAM 的关键的低二层分别加以介绍.
2.1 第一层: 模块层
模块层处于整个结构的最底层, 它向上为接口层提供用户认证等服务, 也就是说所有具体的认证工作都是由该层的模块来完成的.对于应用程序, 有些不但需要验证用户的口令, 还可能要求验证用户的帐户是否已经过期. 此外,有些应用程序也许还会要求记录当前会话的有关信息或改变口令等, 所以 PAM 在模块层除了提供认证模块外, 同时提供了支持 帐户管理
, 会话管理
以及 口令管理
功能的模块. 当然,这四种模块并不是所有应用程序所必需的,而是根据需要灵活取舍,比如虽然 login
可能要求访问所有四种模块,但是 su
可能仅仅需要使用认证组件即可. 至于如何取舍则涉及到接口层的 PAM API
和 配置文件
, 这部分内容将在下文中加以介绍.
2.2 第二层: 应用接口层
应用接口层位于 PAM 结构的中间部分, 它向上为应用程序屏蔽了用户认证等过程的具体细节, 向下调用模块层中的具体模块所提供的特定服务.由图1可以看出, 它主要由 PAM API 和配置文件两部分组成, 下面将逐一介绍.
PAM API 可以分为两类, 一类是用于调用下层特定模块的接口, 这类接口与底层的模块相对应:
- 认证类接口: pam_authenticate()用于认证用户, pam_setcred()用于修改用户的秘密信息.
- 帐号类接口: pam_acct_mgmt()检查受认证的用户所持帐户是否有权登陆系统, 以及该帐户是否已过期等.
- 会话类接口: 包括用于会话管理和记帐的 pam_open_session()和 pam_close_session()函数.
- 口令类接口: 包括用于修改用户口令的 pam_chauthtok().
第二类接口通常并不与底层模块一一对应, 它们的作用是对底层模块提供支持以及实现应用程序与模块之间的通信等.具体如下:
-
管理性接口: 每组 PAM 事务从 pam_start()开始, 结束于 pam_end()函数.接口 pam_get_item()和 pam_set_item()用来读写与 PAM 事务有关的状态信息.同时, 能够用 pam_str()输出 PAM 接口的出错信息.
-
应用程序与模块间的通讯接口: 在应用程序初始化期间,某些诸如用户名之类的数据可以通过 pam_start()将其存放在PAM接口层中, 以备将来底层模块使用.另外, 底层模块还可以使用 pam_putenv()向应用程序传递特定的环境变量, 然后应用程序利用 pam_getenv() 和 pam_getenvlist() 读取这些变量.
-
用户与模块间的通讯接口: pam_start()函数可以通过会话式的回调函数, 让底层模块通过它们读写模块相关的认证信息, 比如以应用程序所规定的方式提示用户输入口令.
-
模块间通讯接口: 尽管各模块是独立的,但是他们仍然能够通过 pam_get_item()和 pam_set_item()接口共享某些与认证会话有关的公用信息, 诸如用户名, 服务名, 口令等.此外, 这些API还可以用于在调用 pam_start()之后, 让应用程序修改状态信息.
-
读写模块状态信息的接口: 接口 pam_get_data()和 pam_set_data()用以按照PAM句柄要求访问和更新特定模块的信息.此外, 还可以在这些模块后附加一个清除数据函数, 以便当调用 pam_end()时清除现场.
由于 PAM 模块随需加载,所以各模块始化任务在第一次调用时完成.如果某些模块的清除任务必须在认证会话结束时完成,则它们应该使用 pam_set_data()规定清除函数, 这些执行清除任务的函数将在应用程序调用 pam_end()接口时被调用.
3. 配置文件
我们注意到, 配置文件也放在了在应用接口层中, 它与 PAM API
配合使用, 从而达到了在应用中灵活插入所需认证模块的目的.它的作用主要是为应用选定具体的认证模块, 模块间的组合以及规定模块的行为.下面是一个示例配置文件:
service module_type control_flag module_path options
----------------------------------------------------------------------
login auth required pam_unix_auth.so nowarn
login session required pam_unix_session.so
login account required pam_unix_account.so
ftp auth required pam_skey_auth.so debug
ftp session required pam_unix_session.so
telnet session required pam_unix_session.so
login password required pam_unix_passwd.so
passwd password required pam_unix_passwd.so
OTHER auth required pam_unix_auth.so
OTHER account required pam_unix_account.so
我们可以看到, 配置文件有许多登记项(每行对应一个登记项)组成, 每一行又分为五列(每列对应一栏), 详细解释如下:
第一栏: service
表示使用PAM的应用程序, 比如login
, passwd
, rlogin
等.
这一栏中的OTHER
表示所有没在该文件中显式列出的应用.也就是说, 如果所有程序具有相同的需求, 整个配置文件只需要一行且该行的第一栏为OTHER即可. 本例中, 因为所有应用程序使用相同的会话模块,所以实际上可以用单行, 即
OTHER auth required pam_unix_auth.so
来代替文件中的这些行:
login session required pam_unix_session.so
ftp session required pam_unix_session.so
telnet session required pam_unix_session.so
第二栏: module_type
指明程序所用PAM底层模块的类型:
- auth: 表示认证类模块;
- account: 表示帐户类模块;
- session: 表示会话类模块;
- password: 表示口令类模块;
注意, 每行只能指定一种类型模块, 如果程序需要多种模块的话, 可在多行中分别规定.
第三栏: control_flag
规定如何处理模块的成功和失败情况.
单个应用程序可以调用多种底层模块, 这通常称为"堆叠", 对应于某程序的按照配置文件中出现顺序执行的所有模块成为"堆", 堆中的各模块的地位与出错时的处理由control_flag栏的取值决定, 它的五种可能的取值分别为 required
, Requisite
, sufficient
或 optional
, 现介绍如下:
required
: 表示该模块的成功是用户通过认证的必要条件. 换句话说, 只有当对应于应用程序的所有带required标记的模块全部成功后, 该程序才能通过认证.同时, 如果任何带required标记的模块出现了错误, PAM并不立刻将错误消息返回给应用程序, 而是在所有模块都调用完毕后才将错误消息返回调用它的程序.requisite
: 与required相仿, 只有带此标记的模块返回成功后, 用户才能通过认证, 不同之处在于其一旦失败就不再执行堆中后面的其它模块, 并且认证过程到此结束.optional
: 表示即便该模块失败, 用户仍能通过认证.在PAM体系中, 带有该标记的模块失败后将继续处理下一模块.sufficient
: 表示该模块取得成功是用户通过认证的充分条件,也就是说只要标记为sufficient的模块一旦成功, 那么PAM便立即向应用程序返回成功而不必尝试任何其他模块.当标记为sufficient的模块失败时, sufficient模块当做optional对待.
第四栏: module_path
指出PAM模块的位置.
CentOS下, 在/lib64/security
目录下有各种pam_*.so
文件, 也可以去/lib/security
下找找, ubuntu下没找到, 可能不在这个位置吧.
第五栏: options
用于向特定模块传递相关的选项, 然后由模块分析解释这些任选项.
比如使用此栏打开模块调试, 或向某模块传递诸如超时值之类的参数等. 另外, 它还用于支持下文所述的 口令映射技术
.
如果任一栏出现错误, 或某模块没有找到, 那么所在行被忽略并将其作为严重错误进行记录.
本例中, login程序使用 UNIX口令模块
进行认证, 而ftp程序却使用 S/Key模块
进行认证. 如我们想改变ftp程序的认证方法, 比如也用UNIX口令模块进行认证, 那么我们不必改动源程序, 只需将配置文件中的
ftp auth required pam_skey_auth.so debug
改为
ftp auth required pam_unix_auth.so debug
这样, 当用户使用ftp时, 将使用传统的UNIX口令认证方式来验证其身份. 由此可见, 在PAM体制下为应用程序改变认证机制是一件轻松的事情. 另外, PAM体制的堆叠功能还使得应用程序能够支持多种认证机制, 如下例中的login程序在配置文件中先后出现了三项与认证有关的登记项:
service module_type control_flag module_path options
----------------------------------------------------------------------
login auth required pam_unix_auth.so nowarn
login auth required pam_kerb.so use_mapped_pass
login auth optional pam_rsa.so use_first_pass
当login程序执行时, 先用 pam_unix.so
模块(传统的UNIX口令方式)认证用户, 然后再调用 pam_kerb.so
模块(Kerberos方式)对用户进行认证, 最后用 pam_rsa.so
模块(RSA方式)认证用户.
在按上述顺序认证用户的过程中, 如果 pam_unix.so
模块认证失败, 它将继续调用下面的模块进行认证而非立刻向login程序返回错误消息; pam_kerb.so
模块也按同样方式处理, 直到顺序处理完最后一个 pam_rsa.so
模块后, PAM才将前面出现的错误信息返回给login程序.对于该配置, 即使 pam_rsa.so
模块顺利通过, 只要 pam_unix.so
模块和 pam_kerb.so
模块中有一个出现错误, 用户就不能通过认证; 相反, 即使 pam_rsa.so
模块失败, 只要 pam_unix.so
模块和 pam_kerb.so
模块都通过了, 用户也能通过认证.
4. 口令映射
在同一个机器上使用多个认证机制, 尤其是一个应用程序集成多种认证机制可能导致用户需要记忆多个口令, 这会让用户觉得很不舒服.虽然可以让所有机制使用相同的口令来获取易用性, 但是这将削弱系统的安全性 - 如果其中任何一个机制的口令泄露了, 则所有机制都会受到牵累.此外,不同的认证机制在 口令长度
, 容许的字符
, 更新间隔
, 有效期
等方面可能具有他们特有的要求, 这些要求也是为多认证机制使用同一个口令必须考虑的一个问题.
PAM为我们提供了这样一种解决方案: 它不排除为所有认证机制共用一个口令, 同时允许通过口令映射技术让每个机制使用不同的口令.该方案用用户的"主口令"加密其他的"副口令",并且将这些经过加密的副口令存放在一个用户能访问的地方.主口令一旦经过验证, 认证模块就能用它解密那些加密的副口令从而获得相应口令, 然后将所需口令传递给认证模块.这称为 口令映射
.如果口令映射出现错误, 或如果映射不存在, 那么各认证模块应该提示用户输入口令.为支持口令映射, 主口令应保存在PAM第二层并且在需要时由其提供给堆叠的各个认证模块.同时, 口令要在 pam_authenticate
函数返回之前清除.为了保障口令映射的安全, 主口令必须足够强壮, 可以考虑使其的长度更长, 组成口令字符的类型多样化并使用混合类型的字符组成口令等有效措施.
口令如何加密及其存储完全取决于具体的实现: 它能够将加密的副口令(也称作"映射口令")存储在可靠或不可靠的地方, 诸如智能卡, 本地文件或目录服务. 当然, 如果加密的口令保存在一个不可靠的允许公共访问的地方,会留下受到字典攻击的隐患.
为实现口令映射, 所有认证模块应支持以下四个映射选项∶
use_first_pass
∶ 它表示当该模块执行时不提示用户输入口令, 而将该模块之前的提示用户输入的主口令作为它们的公共口令进行验证. 如果用户没能通过主口令的认证, 则该模块不提示用户输入口令.此选项一般说来在系统管理员想强制用同一个口令通过多模块时使用.try_first_pass
∶ 除了如果主口令不正确, 将提示用户输入口令之外, 它的用法与use_first_pass相同.use_mapped_pass
∶ 它表示使用口令映射技术得到此模块的有效口令.也就是说, 该模块执行时不提示用户输入口令, 而是用映射口令即用主口令解密得到的该模块的副口令作为本模块的口令输入进行验证.如果在此之前用户没能通过主口令的认证, 则该模块也不会提示输入口令.try_mapped_pass
∶ 除了当主口令不正确时, 它将提示用户输入口令之外, 该项与use_mapped_pass
用法相同.
当口令更换后,PAM会保存所有新旧口令, 并且使有关模块能够访问到它们.其他模块能够使用此信息更新加密的口令而不必强制用户再次输入口令.
现以下面的配置文件为例讲解口令映射:
service module_type control_flag module_path options
----------------------------------------------------------------------
login auth required pam_unix_auth.so nowarn
login auth required pam_kerb.so use_mapped_pass
login auth optional pam_rsa.so use_first_pass
在这里login程序集成了三种认证方式: 传统UNIX口令认证
, Kerberos
和RSA认证
, 但通常情况下用户仅输入一次口令便能通过认证了.
当程序调用 pam_unix.so
模块时, PAM提示用户输入他们的UNIX口令, 然后由 pam_kerb.so
模块对用户输入的UNIX口令进行认证. 继而调用 pam_kerb.so
模块, 由于该模块的选项为 use_mapped_pass
, 它将利用口令映射机制进行认证, 也就是说, 如果UNIX口令认证通过的话, 就将其作为 pam_kerb.so
模块的主口令来解密其对应的映射口令从而进行Kerberos认证.如果 pam_unix.so
模块所需口令没能通过验证, 则无法进行口令映射, 那么PAM将直接调用下一认证模块而不提示用户输入其Kerberos口令. 最后一个模块的选项为 use_first_pass
, 所以 pam_rsa.so
模块将使用前边输入的主口令来认证用户, 如果口令错误也不提示用户输入RSA口令. 所以, 只要第一次输入的口令是正确的, 并且映射口令存在, 则一个口令便足以通过认证.
5. 结束语
Linux-PAM是一种使用灵活功能强大的用户认证机制, 本文对它的组成结构以及各部分之间的关系进行了相应的分析, 希望对读者理解PAM的机制有所帮助.