1.PAM简介
PAM(Pluggable Authentication Modules,可插拔认证模块)是Sun公司于1995年开发的一种与认证相关的通用框架机制。这种机制使得统管理员可以灵活地根据需要给不同的服务配置不同的认证方式,而无需更改服务程序。
PAM是一种认证框架,自身不做认证。<---- PS:注意理解这里
由于本文是针对pam_unix的源码进行分析,这里就不再对pam的背景及相关功能做过多阐述,感兴趣的同学可以上官网上自行查阅相关资料。
2.PAM核心功能 
如上图所示,pam的核心能力层包含四个功能模块:
● 认证管理模块:认证管理,验证使用者身份,账号和密码。
● 账号管理模块:用户认证,基于用户表,时间或密码有效期来决定是否允许访问。
● 会话管理模块:会话管理,进行日志记录,或者限制用户登陆的次数,资源限制。。
● 密码管理模块:密码(口令),认证管理 进制用户方法尝试登陆,在变更密码是进行密码复杂性控制。
3. pam_unix分析 (Linux-PAM-1.5.2)
3.1代码目录结构
如上图所示,源码版本Linux-PAM-1.5.2中modules目录下pam_unix模块主要是由am_unix_acct.c、pam_unix_auth.c、pam_unix_passwd.c和 pam_unix_sess.c功能模块组成,分别对应者pam 的四个核心能力:账号管理、认证管理、密码管理和会话管理。
3.1核心数据结构:pam_handle_t *pamh
PAM的核心数据结构为pam_handle_t *pamh:
该结构体struct pam_handle 用于在PAM框架和各个认证模块之间传递信息。它包含了认证过程中所需的各种关键数据成员,具体功能如下:
*char authtok: 当前用户的认证令牌(如密码),在认证过程中使用。
unsigned caller_is: 标识调用者的身份,比如应用程序或PAM自身。
*struct pam_conv pam_conversation: 指向pam_conv结构的指针,定义了与用户交互的方法,如提示输入密码。
*char oldauthtok: 用户的旧认证令牌,用于密码更改操作。
*char prompt: 用于pam_get_user()函数的提示信息,通常用于请求用户输入用户名。
*char service_name: 正在请求认证的服务名称,如login、su等。
*char user: 用户名,标识正在尝试认证的用户。
*char rhost: 远程主机名,记录用户连接来源。
*char ruser: 远程用户名,如果适用的话,表示用户在远程系统上的身份。
*char tty: 终端设备名,指示用户登录的终端设备。
*char xdisplay: X显示服务器的信息,与图形界面认证相关。
*char authtok_type: 认证令牌的类型,如密码或令牌等。
*struct pam_data data: 保留给模块使用的私有数据。
*struct pam_environ env: 环境变量列表的结构体,用于维护环境变量。
struct _pam_fail_delay fail_delay: 辅助函数,用于在认证失败时轻松实现延迟。
struct pam_xauth_data xauth: 包含X显示认证信息的数据结构。
struct service handlers: 定义了PAM操作(如认证、账户管理等)的处理函数。
struct _pam_former_state former: 支持事件驱动应用的库状态信息。
*const char mod_name: 当前执行的模块名称。
**int mod_argc, char mod_argv: 模块的参数计数和参数数组,允许向模块传递命令行参数。
int choice: 表示从模块调用的函数类型。
int authtok_verified: 标记认证令牌是否已经验证。
*char confdir: PAM配置目录的路径。
#ifdef HAVE_LIBAUDIT...: 如果支持审计库,则包含审计状态信息。
这个结构体是PAM框架的核心,所有的PAM操作都会通过它来传递必要的上下文信息,从而使得认证模块能够基于这些信息做出相应的认证决策。
理解并正确使用struct pam_handle的每个字段对于开发高效、安全的PAM模块至关重要。开发者需要根据应用场景,合理地读取、修改这些字段,实现认证逻辑和其他PAM相关的功能。
3.3对外提供的接口
pam_unix对外提供如下五个接口:
-
- int pam_sm_authenticate(pam_handle_t pamh, int flags, int argc, const char argv); /* 用户验证 */
- int pam_sm_setcred (pam_handle_t pamh, int flags, int argc, const char argv); / * 账户管理 */
- int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char *argv); /* 认证管理*/
- int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv); /* 打开会话管理*/
- int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv);/*关闭会话管理*/
各个接口代码详细处理逻辑如下图所示:
通过对接口代码处理逻辑进行分析,可以看到在用户密码验证的时候,pam自身是不做验证的,而是调用了crypt接口(该接口是由libxcrypt提供的)。这里也充分说明了PAM仅是一种认证框架,自身不做认证。PAM统一接管了操作系统中身份验证相关的功能,大大提高了系统的安全性,同时也解决了业务程序过更多关注用户身份认证的问题。
4.针对PAM的一些理解
本文对pam_unix.so库的源码进行了梳理分析,同时也在开发过程中深刻体会到了PAM功能的重要性,同时也觉得PAM确实存在以下几点问题:
1. 配置复杂性
PAM的配置文件使用了一种特定的语法,允许系统管理员通过堆叠不同的模块来定义认证流程。每个模块可以有不同的控制标志(如required, requisite, sufficient, optional),这些控制标志决定了模块的执行逻辑和对整个认证流程的影响。理解这些控制标志的含义及其相互作用,以及如何正确排列模块顺序,对于构建既安全又灵活的认证策略至关重要。然而,这种灵活性也带来了配置的复杂性,尤其是对于没有经验的管理员而言,容易出现配置错误,导致认证失败或安全漏洞。
2. 兼容性和版本差异PAM作为一个跨多个Linux发行版的标准组件,其核心功能相对稳定,但不同版本的PAM库可能引入新的模块、功能或废弃旧的API。这意味着,在一个发行版上工作的配置可能在另一个发行版上不工作。此外,不同发行版对PAM配置文件的默认位置和命名约定可能有所不同,增加了迁移和维护的难度。管理员需要密切关注所使用的PAM版本的文档,并在升级系统或更换发行版时仔细测试PAM配置。
3. 安全风险安全是PAM配置中最关键的考量之一。不恰当的配置,比如过度宽松的密码策略、缺少账户锁定机制、未启用密码强度检查或未限制认证失败尝试次数,都可能被恶意用户利用。此外,如果PAM配置不当,可能导致敏感信息(如密码提示或错误消息)泄露给未经授权的用户。因此,需要仔细设计和测试PAM策略,确保它们符合组织的安全标准,并定期进行安全审计。
4. 调试和错误报告当PAM配置出错时,错误信息往往出现在系统日志中,但这些信息可能不够直观,难以直接指出问题所在。例如,错误可能仅表明认证失败,而不具体说明失败的原因。管理员需要熟悉日志阅读技巧,结合PAM的配置和模块文档,逐步排查问题。此外,使用调试工具和启用更详细的日志级别(如果PAM和系统支持)可以提供更多的诊断信息。
5. 模块依赖和缺失PAM模块可能依赖于特定的系统库或服务。例如,使用LDAP认证模块需要系统正确配置了LDAP客户端库。如果依赖的软件包未安装或版本不兼容,PAM模块可能无法加载或正常工作。管理员在部署新服务或模块前,必须确保所有依赖项都已正确安装并配置好。
6. 自动化和标准化
在大型企业环境中,手动管理数百或数千台服务器的PAM配置不仅耗时,还容易出错。自动化工具(如Ansible、Chef或Puppet)和配置管理系统可以协助统一和标准化PAM配置,确保所有系统遵循相同的策略。此外,使用模板和版本控制系统来管理配置文件,可以提高变更的可追溯性和一致性。
7. 文档和社区支持尽管PAM有着广泛的使用基础,但某些特定配置场景或高级功能的文档可能不完整或过时。此外,由于PAM的复杂性,社区论坛和邮件列表中可能缺乏针对特定问题的快速响应。管理员可能需要深入研究源代码、参与开发者论坛或寻求专业咨询来解决特定问题。因此,建立和维护良好的社区关系,以及积极贡献文档和案例,对于改善这一状况至关重要。
以上几点总结起来就是:经过长时间的版本迭代,当前pam配置对系统管理员、源码修订系统软件开发者的作要求极高。