Linux PAM 认证机制使用详解
2021-02-17 20:34:40 阿炯

Linux 通常会通过 login 进程完成登陆,最开始时只是简单的提示用户输入用户名和密码,然后校验用户是否存在、密码是否正确,如果都正常,那么就会直接完成登陆,进入到 Shell 程序运行。PAM 提供了独立于具体程序配置机制,可以更加灵活的鉴权方案,这里详细介绍其使用方式。

本文转自悟空小饭的博客,感谢原作者。

简介

通常在用户登陆时,需要有一套的验证授权机制,最开始的时候,这一整套的验证机制是硬编码到程序中的,这样当程序有 bug 或者需要修改验证策略时,只能修改源程序。

为了改善这些问题,人们开始思考其他的方法,也就是所谓的 Pluggable Authentication Modules, PAM 应运而生了。

PAM 提供了一整套的鉴权、授权、密码管理、会话管理机制等,只需要程序支持 PAM 框架,用户就可以在完全不修改程序的条件下,动态修改鉴权机制,例如除了常规的用户名密码登陆,还可以使用指纹、One-Time-Password 等机制。

运行机制

如下是一个最常见的 login 示例程序,其中包括了二进制 login 可执行程序,该程序会动态链接 libpam.so 库,该库会读取 /etc/pam.d/login 配置文件,并根据配置文件中的内容,按照顺序生成不同栈。



然后,会根据不同的栈以及配置执行相关的动作。

相关文件

在 64 位系统中,与 PAM 相关的文件包含了如下几类:
/usr/lib64/libpam.so* 核心库,使用 PAM 机制的应用会链接到该库上。
/etc/pam.conf /etc/pam.d/* 配置文件,配置内容基本类似,前者为全局配置,通过第一列标识应用程序,而后者则以文件名标识应用程序,结构层次更加明确,也更常见。
/usr/lib64/security/pam_*.so 可以动态加载的模块,在配置文件中可以直接通过文件名引用。

如果一个应用程序 (例如 login) 想使用 PAM 提供的机制,那么需要链接到 libpam.so 库,否则就不支持 PAM 机制,可以通过如下命令查看:
$ ldd /usr/bin/login | grep pam
libpam.so.0 => /lib64/libpam.so.0 (0x00007fc79c03e000)
libpam_misc.so.0 => /lib64/libpam_misc.so.0 (0x00007fc79be3a000)

默认程序名与相关的 PAM 配置文件是相同的,当然,也允许通过配置文件进行配置,指定不同的配置文件名称。

验证时会按照顺序检查,也就是所谓的流程栈 (Stack) ,是认证时执行步骤、规则的堆叠,体现了自上而下的执行顺序,而且可以被引用。

配置文件

对于配置文件来说,如上所述,有两种方式,一种是全局的配置文件,也就是 /etc/pam.conf,其内容如下,第一个表示服务的名称:
ftpd auth required pam_unix.so nullok

还有一种是将相关服务的配置保存在 /etc/pam.d/ 目录下,文件名就对应了应用名,例如 login、sshd 等。在配置文件中包含了四列,与上述的后四列相同,分别为:
1) 模块类型;
2) 控制模式;
3) 模块名称;
4) 模块参数。

配置详解

PAM 会根据配置中的模块名称从 /usr/lib64/security 目录下查找,所以通常直接使用文件名称即可;而不同的插件其参数也有所区别,所以,这里主要介绍前两个配置项。

1. 模块类型

PAM 有四种模块类型,代表不同的任务,一个类型可能有多行,按顺序依次由 PAM 模块调用:
认证管理,auth
用来对用户的身份进行识别,如提示用户输入密码、判断用户是否为 root 等。

账号管理,account
对帐号的各项属性进行检查,如是否允许登录、是否达到最大用户数、root 用户是否允许在这个终端登录等。

会话管理,session
定义用户登录前的及用户退出后所要进行的操作,如登录连接信息、用户数据的打开与关闭、挂载文件系统等。

密码管理,password
使用用户信息来更新,如修改用户密码。

如果在模块类型的开头有个短横线 - ,意味着,如果找不到这个模块导致无法加载,这一事件不会被记录在日志中,适用于哪些非必须的验证功能。

2. 控制标记

通过控制标记来处理和判断各个模块的返回值。

required
即使某个模块对用户的验证失败,也要等所有的模块都执行完毕后才返回错误信息。

requisite
如果某个模块返回失败,则立刻返回失败,不再进行同类型后面的操作。

sufficient
如果验证通过,则立即返回验证成功消息,无论前面模块是否有失败,而且也不再执行后面模块。如果验证失败,sufficient 的作用和 optional 相同 。

optional
即使指定的模块验证失败,也允许用户接受应用程序提供的服务,一般返回 PAM_IGNORE 。

include
包含一个新的配置文件进行验证。
    
substack
与 include 类似,区别是,include 调用文件执行时有 die 或者 bad 则立即返回调用处,而 substack 则等待文件执行完。

常用模块

pam_unix.so
auth       提示用户输入密码,并与/etc/shadow文件相比对,匹配返回0。
account    检查用户的账号信息(如是否过期),帐号可用时返回0。
password   修改用户的密码,将用户输入的密码,作为用户的新密码更新shadow文件。

pam_shells.so
auth account 如果用户想登录系统,那么它的shell必须是在/etc/shells中。

pam_deny.so
auth account password session 用来拒绝访问。

pam_permit.so
auth account password session 任何时候都返回成功。

pam_cracklib.so
password 提示用户输入密码,并与系统中的字典进行比对,检查密码的强度。

pam_securetty.so
auth 用户要以root登录时,则登录的tty必须在/etc/securetty中。

示例程序

如下程序从命令行接收一个用户名作为参数,然后对这个用户名进行 auth 和 account 验证。

#include <stdio.h>
#include <security/pam_appl.h>
#include <security/pam_misc.h>
#include <security/pam_modules.h>

int main(int argc, char *argv[]){
    pam_handle_t *pamh=NULL;
    int retval;
    const char *user="nobody";
    struct pam_conv conv = { misc_conv, NULL };

    if(argc == 2)
        user = argv[1];
    if(argc > 2){
        fprintf(stderr, "Usage: %s [username]\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    printf("user: %s\n",user);
    retval = 0;

    retval = pam_start("pamtest", user, &conv, &pamh);
    if (retval == PAM_SUCCESS){
        retval = pam_authenticate(pamh, 0); // 进行auth类型认证
    }else{ // 认证出错,则输出错误信息
        printf("pam_authenticate(): %d\n", retval);
        printf("%s\n", pam_strerror(pamh, retval));
    }

    if (retval == PAM_SUCCESS){
        retval = pam_acct_mgmt(pamh, 0); // 进行account类型认证
    }else{
        printf("pam_acct_mgmt() : %d\n", retval);
        printf("%s\n", pam_strerror( pamh, retval));
    }

    if (retval == PAM_SUCCESS){
        fprintf(stdout, "Authenticated\n");
    }else{
        fprintf(stdout, "Not Authenticated\n");
    }

    if (pam_end(pamh,retval) != PAM_SUCCESS){ /* close Linux-PAM */
        pamh = NULL;
        fprintf(stderr, "pamtest0: failed to release authenticator\n");
        exit(EXIT_FAILURE);
    }

    return ( retval == PAM_SUCCESS ? 0:1 ); /* indicate success */
}

添加如下的配置文件,并同时通过如下命令编译执行。

# cat << EOF > /etc/pam.d/pamtest
# 提示用户输入密码
auth     required   pam_unix.so
# 验证用户账号是否可用
account  required   pam_unix.so
# 向系统日志输出一条信息
account  required   pam_warn.so
EOF

$ gcc -o pamtest pamtest.c -lpam -lpam_misc -Wall



认证服务也是由库文件完成,库文件在/lib/security或者/lib64/security下,配置文件在/etc/pam.d下以login举例。

auth:认证用户所输入的帐号密码是否匹配
account:审核用户帐号是否有效
password:用户修改密码是否被允许,密码是否符合要求
session:会话定义操作过程相关属性

多行是指每一种认证有多种手段。
/etc/pam.d/service(必须小写)格式
type  control  module-path  module-arguments

type:有auth、account、password、session
control:当某一种类型有多个时,多行之间如何建立关系(require:必须的过,如果没通过则不通过但是还要一定要检查同组其他的,requisite如果不过,后面不需要在检查,一定不过,sufficient:如果过了则过来,后面不需要检查,optional:可选,include:权利移交给其它模块)
module-path:完成功能的模块

几个常用模块
1、pam_unix在password,shadow验证用户时用到,选项nullok(运行为空)shadow(基于shadow格式密码) md5(加密算法)
2、pam_permit:运行访问
3、pam_deny:拒绝访问
4、pam_cracklib.so:检查密码通过字典查看是否容易破解,选项minlen:密码最短长度,difok:密码与此前是否相同,dcredit=N,包含记为数字,ucredit=N,包含几位大写字母 lcredit=N,小写字母 ocredit=N,特殊字符,retry=N,最多尝试多少次
5、pam_shell:用户登录默认shell为/etc/shells,检测合法shell
6、pam_securetty:限定管理员只能特殊设备登陆, 设备在/etc/securetty中写明
7、pam_listfile:到某一文件验证某一服务用户帐号的允许与拒绝item=tty/user/ruser/group/shell/rhost
sense=allow/deny  file=/path/filename onen(出现故障)=[succeed(通过)/fail(不通过)] [apply=[user/@group]][quiet]
8、pam_rootok:只要root用户就可以
9、pam_limits:一次会话里面能够使用系统资源的限定。配置文件在/etc/security/limits.conf或者是/etc/security/limits.d/,普通用户只能软限制,普通用户用命令ulimit来调整 -u用户所能打开最大进程数,-n修改打开最大文件数
10、pam_env在用户登录根据/etc/security/pam_env.conf来为用户设置环境变量。
11、pam_wheel用来设定哪些用户可以su到root,只要wheel中用户才可以
12、pam_succeed_if:检查用户的特性来看用户是否可以登录
13、pam_time:根据用户设定登录系统的时间,配置文件/etc/security/time.conf,格式:服务 终端 用户 时间

module-arguments:模块参数

/etc/pam.d下还有个other文件,定义默认规则(没有对应文件)

实验:
/etc/pam.d/login只对本地管用。

操作前先备份下/etc/pam.d/system-auth-ac文件。在第二行加入:
auth        required      pam_listfile.so item=group sense=allow file=/etc/pam_allowgroups

建立文件,并添加内容
vim /etc/pam_allowgroups
root
allowgrp

groupadd allowgrp

查看freeoa这个用户
# id freeoa
uid=2527(freeoa) gid=227(freeoa) 组=227(freeoa)

不在allowgrp这个组里,试着让freeoa登录,会发现无法登录。

现在将freeoa加入到组中去
usermod -a -G freeoa allowgrp

会发现可以登录了。



参考

官方的相关文档 www.linux-pam.orgThe Linux-PAM System Administrators' GuideThe Linux-PAM Application Developers' GuideThe Linux-PAM Module Writers' Guide
一个 Solaris 的开发指南,可以供参考 编写 PAM 应用程序和服务