PAM诞生自1995年,最先由SUN提出并应用于Solaris2.3上。在这之后,经过广大开发人员的不懈努力,各版本的UNIX系统陆续提供了对PAM的支持,包括FreeBSD和Linux。其中专门针对Linux实现的PAM,通常被称为Linux-PAM。这些不同的PAM,除了具体的实现不同外,框架和标准API都是相同的。因为这些知识具有普适性,所以本书并没有特别指明要介绍的是 Linux-PAM。
为了实现“可拔插”性,又要兼顾易用性,PAM采用了分层的体系结构:让各认证模块从应用中独立出来,然后通过PAM API作为两者联系的纽带,应用程序可根据实际功能需要,灵活地在其中“插入”所需类别的认证功能模块。所以,PAM可以被划分为三层:应用层、接口层和认证模块层,如上图。
PAM API处在中间位置,负责应用与各模块之间的通信。当用程序调用PAM API时,相关的API实现代码会按照“配置文件”的规定,加载并调用应的模块所提供的功能来执行具体的认证操作。这样,只要修改“配置文件”的相关项就以改变具体应用的认证方式。而且,也可以根据实际需要,任意添加新的认证模块来实特殊的认证方式。
PAM为了提供更为细粒度的认证控制,给模块划分了四种类型,分别代表四种不同认证任务。它们是:auth、account、session 和 password。这些模块类型前可以有-,表示该模块的认证过程不计入日志,反之则计入。
auth 类型的模块用来执行实际的证工作,比如:提示用户输入密码或判断用户是否为root 等;
account 类型的模块负责对用户的各种属性进行检查,比如:某个用户的密码是否到期、root 用户是否允许在这个终端登录等;
session 类型的模块用于执行用户登录前、退出后的一些操作,以及对每个会话进行跟踪和记录,比如:给新用户初始化home目录、记录用户登录的时间等。
password类型的模块实现了用户密码的细粒度管理,比如: 设定密码的有效期、允许重复输入的次数等。
这四类模块可以堆叠使用。也就是说,一个认证动作可以同时使用多个相同类型的模块共同去完成。这就像“陪审团”去判断一个用户是否“有罪”一样,只要达到相应的“比例”就可以作出结论。而且每个具体的模块也不会仅限于一种类型,它们的“身份”也会随时变化。具体应该怎样,一切由配置文件说了算。
PAM的配置文件通常是pam.conf文件或pam.d目录,它们都会保存在/etc目录下。具体是使用pam.conf还是 pam.d目录,不同的发行版可能不同:centos、redhat、suse不存在pam.conf,debian和Ubuntu,同时存在pam.conf和pam.d目录。
pam.conf和pam.d目录一般不会同时出现,即便同时出现,pam.conf也不会生效。在pam.conf文件中会有如下提示:
NOTE: Most program use a file under the /etc/pam.d/ directory to setup their PAM service modules. This file is used only if that directory does not exist.(注意:大多数程序使用/etc/pam.d/ 目录下的文件来设置PAM服务模块。仅当该目录不存在时才使用此文件。)
在使用pam.conf文件的时候,所有配置信息都会保存在这个文件中,使用pam.d文件的时候,每个应用会有一个与它对应的配置文件,对应方式就是具体的配置文件名会与对应的应用名相同。
一条记录为一行,并且使用空白字符(包括 Tab)将其划分成多个字段。格式如下:
pam.conf配置格式:
[应用名] [模块类型] [控制标识] [模块路径] [模块参数1 模块参数2 ... 模块参数n]
serv.name [-]module.type ctrl.flag module.[path] ...[args..]
pam.d/下文件配置格式:
[模块类型] [控制标识] [模块路径] [模块参数1 模块参数2 ... 模块参数n]
[-]module.type ctrl.flag module.[path] ...[args..]# 举例
cat /etc/pam.d/system-auth#%PAM-1.0 # This file is auto-generated. # User changes will be destroyed the next time authconfig is run. auth required pam_env.so auth required pam_faillock.so preauth silent audit deny=5 unlock_time=900 even_deny_root auth required pam_faildelay.so delay=2000000 auth sufficient pam_unix.so nullok try_first_pass auth requisite pam_succeed_if.so uid >= 1000 quiet_success auth [default=die] pam_faillock.so authfail audit deny=5 unlock_time=10 even_deny_root auth required pam_deny.so
每个应用都会配置多个相同类型的模块,这就是模块的堆叠使用。具体到执行阶段,同等类型的模块会被按照其出现的顺序来执行。
应用名为other代表了所有未明确指定配置项的应用,实质上并不存在叫other的应用。在/etc/pam.d/下也有一个命名为other的文件。
一般情况下,other对应的所有pam配置都应该是指向pam_deny.so的,拒绝所有请求。
假设A是条件,B是结论:
由A可以推出B
由B可以推出A~~则A是B的充分且必要条件由A可以推出B
由B不可推出A~~则A是B的充分不必要条件由A不可推出B
由B可以推出A~~则A是B的必要不充分条件由A不可推出B
由B不可推出A~~则A是B的不充分不必要条件由条件能推出结论,但由结论推不出这个条件,这个条件就是充分条件。
如果能由结论推出条件,但由条件推不出结论。此条件为必要条件。
如果既能由结论推出条件,又能有条件 推出结论。此条件为充要条件。
表示该模块的认证成功是用户通过认证的必要条件。也就是说,只要有一个被标明为 required 的模块认证失败,用户就一定不会通过认证。但是,即便这类模块认证失败,PAM 也不会立即将错误消息返回给应用,而是继续将其他模块都调用完毕之后才返回。这样做的目的就是为了麻痹“敌人”,让他们搞不清楚到底是哪里认证失败的。所以,required 是最常用的控制标记
与required 差不多,但是只要用它标记的模块认证失败,就会立即返回给应用。具体的返回值与第一个认证失败的模块有关。一般被requisite标记的模块多数用来判定当前用户所处的环境,如果环境不够安全,即便是合法的用户也不会通过验证这是最严苛的要求,一般很少使用。但是 requisite 能够降低黑客利用不安全媒介获得输入密码的机会。
表示该模块验证成功是用户通过认证的充分条件。只要这个模块验证成功了,就代表没有必要继续去认证这个用户了。那么相应的行为就是只要被suficient标记的模块一旦认证成功,就会立刻返回验证成功,把控制权交回应用程序。其后相关模块的所有控制都将会比忽略;但是需要注意的是sufficient的优先级低于required,那么如果有required失败,则最终的结果也是失败的;当sufficient 认证失败时,相当于optional,对认证结果不起作用。
这表示即便该模块认证失败,用户也可能通过认证,除非别无它选。换句话说它仅供参考。而实际应用中,optional 所标记的模块只是显示些信息,根本不去做认证工作。
include 属于控制标记,只会对“模块类型”字段所标记的一类模块起作用。即便system-aut可能包含了全部四种类型的模块,也只有与当前记录所对应的那一类模块的那些记录被包含了进来。原文:
include include all lines of given type from the configuration file specified as an argument to this control.
substack 和include类似,不同之处在于,对子堆中的done和die的行为的评估不会导致跳过整个模块堆栈的其余部分,而只会跳过子模块。原文:
substack include all lines of given type from the configuration file specified as an argument to this control. This differs from include in that evaluation of the done and die actions in a substack does not cause skipping the rest of the complete module stack, but only of the substack.
Jumps in a substack also can not make evaluation jump out of it, and the whole substack is counted as one module when the jump is done in a parent stack.
The reset action will reset the state of a module stack to the state it was in as of beginning of the substack evaluation.
翻译:
包含作为此控件的参数指定的配置文件中给定类型的所有行。这与include不同,在子堆栈中评估done和die动作不会导致跳过整个模块堆栈的其余部分,而只会跳过子堆栈。
在子堆栈中的跳转(比如:自堆栈中也存在include或substack控制标识)也不会做出跳出它自身的评估,当在父堆栈中完成跳转时,整个子堆栈被算作一个模块。
reset操作将子堆栈重置为开始时的状态。
我们完全可以把substack当成用户认证阶段的“子过程”。
这是一种比较复杂的语法来设置控制标志,它由一组value=action形式的标记组成,标记之间以空格分开:
[value1=action1 value2=action2 ...]valueN可以是Linux-PAM函数库中任何一个函数的返回值,返回值包括: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, and default.最后一个(default)能够用来设置上面的返回值无法表达的行为。
状态 | 说明 |
---|---|
success | 认证成功 |
auth_err | 认证失败 |
new_authtok_reqd | 说明需要新的认证令牌。有三种情况会返回这个状态:一是根据安全策略需要更改密码;二是密码为空;三是密码已经过期 |
ignore | 忽略底层account类型模块的返回状态,而且无论是否被required、sufficient和optional修饰。出于安全性考虑,这个返回状态通常要被忽略掉,这样其他模块的返回状态就会被参考 |
user_unknown | 未知用户 |
try_again | 已经通过了密码服务的初步检测 |
default | 所有没有注明的返回状态 |
标记 | 说明 |
---|---|
ignore | 忽略这个返回状态,不会对验证结果产生影响;如果使用层叠模块,这个模块的返回状态将不会对应用程序获取的返回值产生影响。 |
bad | 表示这个返回状态代表验证失败,余下的模块调用也永远失败;它表示这个返回码应该被看作是模块验证失败。如果这个模块是层叠模块的第一个验证失败的模块,那么它的状态值就是整个层叠模块的状态值。 |
die | 与bad相同,但是应该立即返回到应用,不再继续调用余下模块;对层叠模块最终结果的影响相当于bad,并且立刻反回到应用程序。 |
ok | 表示这个返回状态应该被看作是验证成功,继续调用余下模块 |
done | 与ok相同,但是应该立即返回到应用,不再继续调用余下模块 |
N(一个整数) | 与ok相同,但是要跳过N个模块才能继续(就表示需要忽略后面堆叠的n个同样类型的模块。) |
reset | 清除所有的返回状态,继续调用余下模块 |
required [success=ok new_authtok_reqd=ok ignore=ignore default=bad]
requisite [success=ok new_authtok_reqd=ok ignore=ignore default=die]
sufficient [success=done new_authtok_reqd=done default=ignore]
optional [success=ok new_authtok_reqd=ok default=ignore]