Linux模块-SELinux和PAM库

SELinux (Secure Enhanced Linux)
PAM (Pluggable Authentication Modules)

PAM

PAM是一套程序编程接口, 它提供了一连串的验证机制, 只要将需求交给PAM, 然后PAM就会将验证的结果告知客户端. 就像这样:

PAM 与其他程序的相关性

有没有想到什么?对了这个就有一点像OAuth对不, 我们只要发起申请, 接着等到结果就行了, 当然OAuth是多对多, 和这个也是有区别的.但是他们的思想都是一样的, 使得账号密码或者其他的方式验证具有一致的结果, 使得程序员方便处理验证的问题.

如图, PAM作为一个独立的API存在, 用来进行验证的部分叫做模块. 各个模块负责的事情也不一样. 比如在进行密码设置的时候, 会有检测密码强度的提示, 这个就来源于PAM的pam_cracklib.so模块 .

我们就直接以一次密码验证的过程来说说程序调用PAM的工作流程:

  1. 用户开始执行passwd程序, 输入密码
  2. PAM到/etc/pam.d/寻找与程序同名的配置文件
  3. 根据/etc/pam.d/passwd内的设置, 引用相关的PAM模块逐步进行验证分析
  4. 将验证的结果(成功,失败,其他信息)返回给调用者(passwd)
  5. passwd根据返回的结果进行下一步的操作.

对于客户端而言, 编码过程和OAuth几乎是一样的.

那么看起来最重要的即使/etc/pam.d/下面的配置文件了, 我们来看一看:

1
2
3
4
5
6
7
[root@WWW pam.d]# cat passwd
#%PAM-1.0
auth include system-auth
account include system-auth
password substack system-auth
-password optional pam_gnome_keyring.so use_authtok
password substack postlogin

挨 又是自己的文件结构. 我们来逐行看一下

除了第一行表示PAM的版本以外, 只要后面出现的#都会被视作注释. 显然易见的PAM将配置分成三个栏目, 分别是:

  • 验证类型 ( type )
  • 控制标准 ( control )
  • PAM的模块和模块参数

下面就结合这三个参数来说一下

第一个字段:: 验证类型

主要分成四种:

auth: 是authentication的缩写, 这种类型主要用来进行用户的身份验证,通常是使用的密码, 所以后面的模块是用来检验用户的身份的

account: 大部分是用来进行授权的, 主要用于进行用户是否具有正确的权限

session: 这个说来应该大家会很熟悉, 只要你经常使用su或者sudo, 当你使用这个进入sudoer的状态, 或者进入其他用户的状态而没有退出的时候, 你就处在一个session里面, 可以去/var/log/secure里面看一下, 会有很多session open, session close的记录(close就说明你logout或者exit了)

password: 就是再说密码, 当进行密码的修改时候就是这个模块发挥威力的时候了

一般情况下, 这些验证到的调用是有顺序的( 因为正常情况下, 你总是要先验证身份, 再进行授权, 接着才有登录, 而在运行时候改变密码 )

第二个字段:: 验证的控制标志

这些标志说白了就是验证的通过标准, 说之前提一下上面例子中的那个include, 这个不是控制标志哦, 只是说这个时候使用后面指定的配置文件的配置. 所以include不是控制标志.

有这些控制标志:

required: 如果验证成功, 就带上一个success的标识, 如果验证失败则带上failure的标识, 但是不论验证结果是成功还是失败都继续验证下去, 这样对数据的日志有利. 所以PAM采用的最多的就是这个标志.

requisite: 如果失败立即返回failure的标志, 直接终止后序的验证流程, 如果成功就继续. 和required唯一的不同就在于处理失败

sufficient: 只要成功就立即回传success的标识, 同时结束验证过程. 但是验证失败的话,会继续过程. 和requisite正好相反.

optional: 不作具体的验证, 而是用来显示信息.

用一张图来表示的话就是这样:

PAM 控制旗标所造成的回报流程

在新版的CentOS7上, 预装的PAM进行了更新, 多了这个控制位:

substack :运行其他配置文件中的流程,并将整个运行结果作为该行的结果进行输出.该模式和 include 的不同点在于认证结果的作用域:如果某个流程栈 include 了一个带 requisite 的栈,这个 requisite 失败将直接导致认证失败,同时退出栈;而某个流程栈 substack 了同样的栈时,requisite 的失败只会导致这个子栈返回失败信号,母栈并不会在此退出.

上面的这些叫做简单机制, 而还有一种叫做复杂机制, 也就是使用一个或者多个status=action形式的组合表示.可以根据不同的返回状态来决定不同的action. 不过说实话啊, 这个机制真心..运维不需要手动的写吧. 能看就行了.

行了, 这样我们就可以看懂这些配置文件了. 在系统的启动中我们说过了getmintty这个程序调用login来提供登录, 那么这个login会不会就在我们的配置文件中呢

1
2
3
4
5
6
7
8
9
[root@WWW pam.d]# cat login 
#%PAM-1.0
auth [user_unknown=ignore success=ok ignore=ignore default=bad] pam_securetty.so
auth substack system-auth
auth include postlogin
account required pam_nologin.so
account include system-auth
password include system-auth
...(omitted)

login也调用了多次system-auth. 迫不及待的打开system-auth:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@WWW pam.d]# cat 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 sufficient pam_unix.so nullok try_first_pass
auth requisite pam_succeed_if.so uid >= 1000 quiet_success
auth required pam_deny.so

account required pam_unix.so
account sufficient pam_localuser.so
account sufficient pam_succeed_if.so uid < 1000 quiet
account required pam_permit.so

password requisite pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
password sufficient pam_unix.so sha512 shadow nullok try_first_pass use_authtok
password required pam_deny.so

session optional pam_keyinit.so revoke
session required pam_limits.so
-session optional pam_systemd.so
session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session required pam_unix.so

这么多库, 他们都放置在/lib64/security/*里面 如果想要具体了解某些库提供的功能是什么, 还是要查看文档啊. 还是就以这个login为一个例子, 说一下相关的库吧.

模块名 功能和用途
pam_securetty.so 限制root用户只能从安全的终端登入系统(安全终端就是在/etc/securetty里说明的)
pam_nologin.so 就是那个大名鼎鼎的nologin文件, 只要存在所有的用户(除了root和已经登录的)就不能再登录进系统了.
pam_selinux.so 可以将selinux(后面会说)暂时关闭, 由PAM来进行验证, 通过了之后就会恢复启动
pam_console.log 可以使用一些特殊的终端接口来登录系统
pam_loginuid.so 规范登录进系统的用户的UID(区分系统账号和用户账号)
pam_env.so 用来设定环境变量的库
pam_UNIX.so 这是一个复杂的模块了, 可以用于验证阶段的认证功能, 也可以在授权阶段进行账号的许可证管理, 并且在session阶段也可以进行日志的记录, 甚至可以进行密码的更新
pam_cracklib.so 之前也说过了, 这个是用来进行密码强度的检验的
pam_limits.so ulimits的神奇能力都依靠于这个库

行了, 那么就可以来把login的过程讲清楚了.

  1. 验证阶段(auth):首先,(a)会先经过pam_securetty.so 判断,如果使用者是root 时,则会参考/etc/securetty 的设定; 接下来(b)经过pam_env.so 设定额外的环境变数;再(c)透过pam_unix.so 检验密码,若通过则回报login 程式;若不通过则(d)继续往下以pam_succeed_if.so 判断UID 是否大于1000 ,若小于1000则回报失败,否则再往下(e)以pam_deny.so 拒绝连接.
  2. 授权阶段(account):(a)先以pam_nologin.so 判断/etc/nologin 是否存在,若存在则不许一般使用者登入; (b)接下来以pam_unix.so 及pam_localuser.so 进行帐号管理,再以(c) pam_succeed_if.so 判断UID 是否小于1000 ,若小于1000 则不记录登录资讯.(d)最后以pam_permit.so 允许该帐号登录.
  3. 密码阶段(password):(a)先以pam_pwquality.so 设定密码仅能尝试错误3 次;(b)接下来以pam_unix.so 透过sha512, shadow 等功能进行密码检验,若通过则回报login 程序,若不通过则(c)以pam_deny.so 拒绝登录.
  4. 会话阶段(session):(a)先以pam_selinux.so 暂时关闭SELinux;(b)使用pam_limits.so 设定好使用者能够操作的系统资源; (c)登入成功后开始记录相关信息在登录文件中; (d)以pam_loginuid.so 规范不同的UID 权限;(e)开启pam_selinux.so 的功能.

最后再说一句, 如果 关于这个出现了什么难以解决的不明问题, 最好的解决方法个人认为就是去查看日志, 也即是/var/log/messages和/var/log/secure.(不同发行版不同哦,不确定的话查看rsyslog的配置吧)

SELinux

在开头说过了, SELinux是指安全加强的Linux. 他的来头很大,是美国国家安全委员会负责研发的.

然而在实际的生产环境中却没有**1%**的人选择使用.

美国国家安全委员会对操作系统进行了安全评分, 从最安全的A到最次的D. Windows Server的安全评级为C2, 也就是仅次于最差的D, 那么Linux呢? 和WinServer一样, 都是C2. 有些人总说使用Linux是因为安全, 其实这样是不合理的.只是说由于Linux的学习曲线陡峭导致Linux的用户都花了精力研究过的. 而Windows由于用户中多自然针对的攻击脚本也多.

Selinux工作在内核中, 使用了一种强制访问控制(MAC). 原来的Linux使用的是一种自主访问控制.(DAC)自主访问控制就是说当你的文件想要让别人可以进行写操作的时候, 可以进行chmod, 这种操作Linux系统是不加限制的. 在之前说到进程的时候.说到了一个安全执行上下文的东西, 就是说决定进程能访问那些文件的时候. 我们首先看的是进程的属主, 检查文件的属主和进程的属主是否为同一个, 如果是应用属主权限否则就是其他用户权限.简单的说, 只要用户有权限那么进程就可以访问(有权限)

这一点就给我们的系统带来了风险, 这样的设计已经违反了操作系统中安全法则中的最根本的: 最小权限法则 也就是说一个进程在运行的时候, 如果一共仅需要访问10个文件, 那么最多他所获得的权限就是这10个文件的权限. 就好像说现在的沙箱, 即便进程脱离了我们的控制, 他所能够达到的范围就是这个沙箱的范围.这就没问题了, 但是难度就在于, 我们怎么根据不同的进程来创建不同的沙箱呢? 而且有可能由于SUID等原因, 进程也可能会从A沙箱切换到B沙箱, 因此这么一个安全机制是很麻烦复杂的.

但是并不是所有的程序都需要进行这样的监控.一般像ls这样的程序被劫持的可能性几乎没有, 但是如果是 httpd就不一定了, 作为这种的服务进程应该是被重点关注的对象. 对于SELinux有两种种工作级别.

  • strict 严格模式, 每一个进程都收到SELinux的监视
  • targeted: 仅有限个进程收到SELinux的控制

我们 知道进程也就是进行文件的读写操作, 但是进程是可以操作进程的就像是这样的:

1
2
3
4
5
subject operate object
-subject: 进程
-object: 文件, 进程
-operate: 根据被操作对象的种类, 操作方式自然也是不同的.
文件: open, read, write, close, chown, chmod

我们可以考虑给进程加上一个域(domain)的概念; 接着再给object一个类型的概念.

现在找一个非空的目录试一试, 使用ls- Z 会看到一列复杂的字串, 这个就是SELinux给文件分配的安全上下文环境, 也可以直接就说是文件的一个标签.同样使用ps Z也可以看到进程的标签.

被冒号分割实际上一共有5项但只有前三个比较重要, 所以就先忽略最后面的两个吧.

一个安全标签的基本组成是这样的:

1
2
user:role:type:...(omitted)
这里的user不是在说Linux的系统用户呢, 是指SELinux的user

所以简单的说, SELinux的工作原理就是基于沙箱, 尽管你有权限访问该用户权限下的所有的文件, 但是你们的标签不符, 因此就会直接拒绝访问.对于进程而言, 第三个type就是进程的域.

那么类型和域是怎么进行匹配的呢?那些类型和域又存在兼容呢?

这些都依靠SELinux的规则库, SELinux事先有一些生成的规则, 你也可以进行规则的自定义.再说这个之前, 还是先谈一下SELinux的验证过程:

当一个进程要去访问一个文件的时候会先开始进行属主的检查, 如果属主都没有权限访问就直接拒绝了, 接着开始进行规则集的匹配, 尝试进行类型和域的匹配. 匹配时还是会针对每一个操作进行权限检查(可以类比数据库) 如果该操作有匹配, 则可以进行 否则直接拒绝, 并进行日志记录.

还有一条, 如果没有规则记录, 那么不论什么情况直接拒绝.

那么就直接来看SELinux的配置吧 主要有这些设定: SELinux是否启用 给文件重新就打标签, 设定某些布尔型特性.

SELinux的启用和关闭

这个之前就提到过了, 在/etc/selinux下的config中, 可以配置三种状态:

1
2
3
4
# SELINUX= can take one of these three values:
# enforcing - SELinux security policy is enforced.
# permissive - SELinux prints warnings instead of enforcing.
# disabled - No SELinux policy is loaded.
  • enforcing; 强制, 每一个受限的进程都必然受限
  • permissive: 启用, 每一个受限的进程违规操作都不会被禁, 但会被记录于审计日志
  • disabled: 关闭

只要从disabled到其他任意的状态, 都需要进行重新启动系统(因为要进行重新标签)

相关的命令:

1
2
[root@WWW ~]$ getenforce # 获取SELinux的状态
[root@WWW ~]$ setenforce # 设置SELinux的状态 0 | 1

这样设定的方法只有当时有效, 重启后会重新变成配置文件中的状态. 另外每次进行配置文件的修改都只有重启系统后才会生效.

安全上下文的修改

现在就将注意点聚焦到之前的类型吧. 一般是要到的情况也只是修改这个文件的类型. 相关的命令就是:

1
2
[root@WWW ~]$ chcon # 就是change context的意思
# chcon [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE...

那么修改这个类型有什么用处呢 ?

我们直接来看一个完整的实例就知道了:

1
2
3
4
5
6
7
8
9
10
11
[root@WWW html]# ps auxZ | grep httpd
system_u:system_r:httpd_t:s0 root 2440 0.0 0.1 228204 5104 ? Ss 03:38 0:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0 apache 2441 0.0 0.0 228340 3744 ? S 03:38 0:00 /usr/sbin/httpd -DFOREGROUND
[root@WWW html]# ll -Z
drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 .
drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 ..
-rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 index.html
# 显然这是个httpd的子类型
[root@WWW html]# ll -Z ~/.bashrc
-rw-r--r--. root root system_u:object_r:admin_home_t:s0 /root/.bashrc
# root用户的家目录内是这样的

现在直接访问的样子是这样的:

selinux1

好, 一切正常, 那么现在将他改成root家目录下的类型:

1
[root@WWW html]# chcon -t admin_home_t index.html

刷新页面!

selinux2

出现了403! httpd进程没有访问index.html的权限了.

如果你的页面没有发生变化, 就检查一下SELinux的开启状态(getenforce), 并且注意, 你的SELinux的状态必须是1才行.

把他再改回去:

1
[root@WWW html]# chcon -t httpd_sys_content_t index.html 

就可以访问了.

如果需要递归的进行打标, 就加上-R参数就行了.

如果要还原文件的默认标签就使用: restorecon SELinux为一些文件都存了默认的标签值, 因此可以很快的进行回复. 同样, 递归的回复的话 就使用-R/-r就行了

接着是布尔型规则的设定, 他的设定和获取就是:

1
2
3
4
5
6
7
8
9
10
11
12
[root@WWW ~]$ getsebool [-a] [boolean]
# e.g:
[root@WWW html]# getsebool tftp_home_dir
tftp_home_dir --> off
# 要查看所有的boolean值就直接加上-a就行了
setsebool [ -PNV ] boolean value | bool1=val1 bool2=val2 ... # 支持多布尔值同时设定
# P 写入磁盘
# N 仅写入磁盘, 不让内核重载(话说我觉得这个选项没啥用啊..)
[root@WWW html]# setsebool tftp_home_dir on # 也可使用 1 或者 true
[root@WWW html]# getsebool tftp_home_dir
tftp_home_dir --> on
# 如果要永久加上就加上 -P 选项就行了 会比较卡,因为会在策略库中遍历,找到之后还要转成二进制再写入进去

SELinux的日志文件都在/var/log/audit里面.

由于现在使用SELuinx的场景越来越小了, 所以我也就不在展开了吧, 更多的东西就这里吧:

鸟哥!!

更新 17.10.05:

nsswitch

除了上面说过的框架级别的认证服务PAM, 现在再介绍一个框架级别的服务, 即nsswitch 即网络服务交换. 这个服务主要负责进行名称解析 ( name:id )

和PAM一样, 或者说和所有框架级别的服务一样, 当我们需要进行解析的时候, 我们知道有很多种介质, 例如文件解析, MySQL数据库解析, LDAP解析, DNS解析等等. 如果想要支持这些解析, 那么就需要程序员在自己的程序中内置这些解析介质的驱动. 这显然并不好, 所以为了提供一个大家都能够进行解析的平台, 就这样出现了这样的一个通用层, 中间层. 也就是这个通用框架 – nsswitch. 但是我不一定会使用到所有的解析介质. 因此, nsswitch提供一个配置文件来指明解析顺序, 方式. 这样就会通用且灵活. 这种概念在整个计算机科学中各个地方都有体现.

关于nsswitch服务的模块在/usr/lib64/下可以找到, 我们来看一眼:

1
2
3
4
5
6
[root@WWW lib64]# ls libnss*
libnss3.so libnss_db-2.17.so libnss_dns-2.17.so libnss_files.so.2 libnss_mymachines.so.2 libnss_nis.so libnss_winbind.so
libnssckbi.so libnssdbm3.chk libnss_dns.so libnss_hesiod-2.17.so libnss_nis-2.17.so libnss_nis.so.2 libnss_winbind.so.2
libnss_compat-2.17.so libnssdbm3.so libnss_dns.so.2 libnss_hesiod.so libnss_nisplus-2.17.so libnsspem.so libnss_wins.so
libnss_compat.so libnss_db.so libnss_files-2.17.so libnss_hesiod.so.2 libnss_nisplus.so libnsssysinit.so libnss_wins.so.2
libnss_compat.so.2 libnss_db.so.2 libnss_files.so libnss_myhostname.so.2 libnss_nisplus.so.2 libnssutil3.so

其中有dns, nis, nisplus, wins, winbind等等等..

直接切进nsswitch的配置文件吧, 一个条目占一行: 意思也很好懂, 就是什么的解析使用什么方式, 按照优先级(从左到右)来尝试.

1
2
3
4
5
6
hosts:      files dns myhostname
ethers: files
netmasks: files
networks: files
protocols: files
rpc: files

这里我们可以把这个和数据库做类比, 把左边的部分当做数据库名字, 右边的当做键. 所以, 尽管键都是files, 但他们对应的文件是不一样的, 因为数据库不同.

其中有个命令可以来获得对应数据库的某个特定键的值, 例如:

1
2
3
4
5
6
[root@WWW ~]# getent passwd root
root:x:0:0:root:/root:/bin/bash
[root@WWW ~]# getent services http
http 80/tcp www www-http
[root@WWW ~]# getent services ftp
ftp 21/tcp

如果不加上特定的键, 就会将整个数据库的值输出. getent的意味: get entry

当然, 找不到是有可能的. 那么找不到怎么办呢, 当然是返回了:

1
2
每种存储中查找的几种状态: STATUS => success | notfound | unavail | tryagain
对应于每种状态所采取的行为: ACTION => return | continue

这样的一种应用是:

1
hosts file nis [NOTFOUND=return] dns

意思是说, 只要nis解析服务可用, 如果没有找到就不去找dns服务了, 而nis挂掉了, 那么就直接去找dns(因为此时nis的返回应该是unavail)

关于nsswitch就是这样吧, 其实没什么