基于角色的访问控制(Role-based access control,RBAC)是通用的安全模型,可以通过把角色分配给用户然后把权限分配给这些角色来简化管理。RBAC 在 Security-Enhanced Linux (SELinux) 中用作用户与底层类型增强(Type Enforcement,TE)模型之间的抽象层,用于提供细粒度的访问控制,但是并不是针对简化管理。了解如何将 SELinux 环境的三个部分(策略、内核和用户空间)结合使用以增强 RBAC 并把 Linux® 用户绑定到 TE 策略中。
在 Security-Enhanced Linux (SELinux) 中实现的安全策略是位于基于角色的访问控制 (RBAC) 层之下的类型增强 (TE)(SELinux 还将垂直实现多层安全性 (MLS),这超出了本文的讨论范围)。TE 最为常见,也因而最为人熟知,由于服务器实施细粒度权限:当由于意外的访问拒绝而引起中断时,TE 最有可能对此负责。在 TE 中,进程的安全域(对系统的影响域)是由任务的历史记录和当前执行的程序确定。
RBAC 的概念并不像 TE 一样经常讨论,并且由于与 TE 集成在一起,因此十分容易混淆。一般情况下,可以将 RBAC 视为指定某些角色下的用户接收的访问。但是,SELinux 将按照 TE 指定基于角色的访问,因此 RBAC 在 SELinux 中的目的在于允许根据授权用户可能采用的角色管理权限,然后通过指定用于把角色与有效上下文绑定在一起的 TE 域来限定角色可以进入的影响域。
要了解上述原理如何工作,请查看使用 SELinux 提供安全保障的一个非常简单的收银机结算系统。您将在两个截然不同的环境中查看同一个解决方案(要查看适用于这两种环境的代码,请参阅 下载):
- 查阅 developerWorks 文章 “从头开始生成 SELinux” 了解如何从头开始构建 SELinux 系统。此系统将演示如何将内核与用户空间中的内容绑定在一起。
- Fedora Core 8 系统。Fedora Core 8 系统(撰写本文时的最新版本)将展示 SELinux 与 RBAC 是如何紧密集成在一起的。
使用角色
假定一家商店委托您来实现一个安全的收银机结算会计系统。对于每台收银机,最终金额必须由收银员和经理一起计算。首先定义两个角色,收银员和经理。对员工分配角色并要求在收银员倒班时有一名收银员和一名经理同时确认收银机中的总额。
虽然两个系统中的策略文件可能显得有些不同,但是两个系统中使用了相同的数据布局和收银系统。所有数据都存储在 /data 目录下并且只能通过 /bin/register.py 程序访问。经理和收银员都可以使用 register.py 程序来存储值。为了使代码保持简单,它的功能比较有限。收银员只能存储当天自己使用的收银机值。经理可以存储其他员工的值,并且当经理和收银员都为当天的同一台收银机存储了相同的值时,经理可以提交这些值。
当收银员 Bob 使用 register.py bob 109.95 于 2007 年 12 月 12 日下午 9:00 存储值时,系统将创建内容为 “bob 09:00 109.95” 的文件 /data/cashier_r/bob/12-12-2007。接下来,经理 Mary 在下午 9:25 存储相同值,结果系统将创建内容为 “mary 09:25 109.95” 的文件 /data/mgr_r/bob/12-12-2007。最后,Mary 在下午 9:27 使用 register.py bob commit 命令提交这些值,这条命令将创建内容为 “mary 09:27 bob mary 109.95” 的文件 /data/final/bob/12-12-2007。
如果 Bob 和 Mary 的值不一致,则 Mary 将无法提交 Bob 的值。Mary 则必须通知 Bob,两人重新计算直至达成一致,然后可以再次运行以上 /bin/register.py 命令输入相同的值。新值将被追加到先前值中以辅助店主稍后查看,并且 /bin/register.py bob commit 命令将使用最后一次输入的值,而不是 /data/cashier_r/bob/12-12-2007 和 /data/mgr_r/bob/12-12-2007 中的值。
注:此处讨论的代码示例使用 SELinux 处理所有访问控制需求。我们的示例允许系统中的所有用户对 /data 下的所有文件和目录拥有完全的写访问权。但是,在理想部署中,您将需要应用一些 DAC 权限来实现进一步防御。所有经理有时候希望在 /data/mgr_r/bob/ 和 /data/final/bob/ 下创建文件,包括精确地使用 UNIX® 组权限。但是为了简单起见,让我们完全依赖 SELinux 来实施访问控制。
首先,确保经理和收银员只能通过 register.py 程序访问 /data 下的任何文件。例如,Bob 将登录到 cashier_t 类型中的 cashier_r 角色。但是 cashier_t 无法读取 /data 下的数据。为此,他必须进入 cashier_register_t 类型,而他只能通过执行 /bin/register.py 来进入该类型。类似地,Mary 将登录到 mgr_t 类型中的 mgr_r 角色,但是必须执行 /bin/register.py 来进入 mgr_register_t,才能拥有对 /data 下数据的任何访问权。
当 PAM 模块决定 Bob 必须登录到 cashier_r 角色中时,将实际发生访问控制的第一部分。当内核中的 SELinux 类型增强服务器拒绝 bob_u:cashier_r:cashier_t 进入 bob_u:cashier_r:cashier_register_t 时(除非执行 cashier_exec_t 文件类型,这是管理员只分配给 /bin/register.py 的类型),将继续进行访问控制。
当 register.py 拒绝允许收银员提交值或存储另一个用户的值时,将进一步对 register.py 本身进行增强。这是通过 SELinux 策略以及内核代码的策略增强实现的,这种增强不允许 cashier_register_t 访问 /data/mgr_r 或 /data/final 下的文件。
实现收银机系统
收银机结算系统的第一个实现是在前面介绍的从头创建的 SELinux 系统之上实现的(可以在 下载 中获得)。下面介绍了在完全从头创建的系统之上实现收银机结算系统的步骤。
挂载磁盘映像(关闭了 qemu 映像):
mount -oloop,offset=32256 -t ext2 gentoo.img /mnt |
从 下载 部分获取 code_for_fromscratch.tgz 文件,并通过执行以下操作将其解压缩到磁盘映像下:
tar zxf selinuxregister.tgz -C /mnt umount /mnt |
现在启动 qemu 映像:
qemu -hda gentoo.img -m 512 -vnc :3 -kernel bzImage \ -append "ro root=/dev/hda1 -p" |
登录后,必须编译并安装新策略,同时安装新的 PAM 模块:
cd /usr/src checkpolicy -c 19 -o policy.bin policy.conf cp policy.bin /etc/ rc-update add selinuxenforce default cp /etc/pam.d/system-auth /etc/pam.d/system-auth.orig cp /etc/pam.d/system-auth.new /etc/pam.d/system-auth |
SELinux 用户是在策略中创建的,但是必须创建与 SELinux 用户相对应的 Linux 用户:
adduser mary passwd mary (passwd) mkdir /home/mary adduser boss passwd boss (passwd) mkdir /home/boss adduser bob passwd bob (passwd) mkdir /home/bob |
然后为数据存储创建目录结构:
mkdir /data mkdir /data/cashier_r mkdir /data/mgr_r mkdir /data/final chmod 777 /data/* |
最后,重新设置文件系统的标签:
setfiles /usr/src/filecontexts / poweroff |
现在准备好了映像。在不使用 -p 标志的情况下重新启动映像,以便装载 SELinux 策略:
qemu -hda gentoo.img -m 512 -vnc :3 -kernel bzImage \ -append "ro root=/dev/hda1" |
然后以根用户身份登录,尝试执行以下命令:
ls /data |
权限被拒绝。登出,然后以收银员 bob 的身份登录。记录一个值,例如:
register bob 25.22 |
然后尝试执行以下操作欺骗系统:
register bob commit |
不起作用。登出,然后重新以经理 Mary 的身份登录:
register bob commit |
Mary 首先需要输入她自己的值:
register bob 27 register bob commit |
值不匹配。想知道 Bob 提交了什么值?
cat /data/cashier_r/bob/(day) |
系统不允许查看该值。您必须去与 Bob 讨论该值。您可能重新计算收银机中的总额并发现 Bob 输入的值无误。因此:
register bob 25.22 register bob commit |
这次可以了。现在可以以根用户身份登录并且:
cat /data/final/bob/(day) |
这将显示已经输入的所有值。
进一步了解类型增强
使用同一个 /bin/register 程序的不同用户能够对不同文件执行读写操作,这些文件必须通过程序访问。这是类型增强的核心概念之一:应当结合授权的用户上下文与正被执行的代码确定结果进程对系统(或 TE 域)的 “影响范围”。
图 1 显示了系统中的域和类型:
图 1. 域和类型
但是怎样阻止 Bob 以经理身份登录或者 Mary 以收银员身份登录?更有趣的是,老板如何以两种身份登录?
第一部分是由一个新的 PAM 模块完成的。简言之,PAM(可插入认证模块)允许在各个认证步骤中执行小段代码,还允许灵活地指定哪些模块何时执行。我已经介绍了一个新模块 pam_ctx.so,该模块的代码位于 /usr/src/pam_ctx 下。它将搜索 /usermap.conf 文件查找正被验证的用户名并查找此用户的默认上下文。对于 Bob,此默认上下文是 cashier_u:cashier_r:cashier_t。对于 Mary,它是 mgr_u:mgr_r:mgr_t。而对于老板,它是 full_u:mgr_r:mgr_t。注意,所有环境都是由用冒号分隔的三个部分组成的:
- 最后一部分是域,它将最终决定用户在系统中的权限。
- 第二部分是角色,它将限制用户可以进入的域。
- 第一部分是 “SELinux 用户”。类似于角色和域,这部分将限制进程可以进入的角色。
PAM 模块设置上下文的方式为:把上下文写入 /proc/$$/attr/exec 文件,然后执行对于新域具有有效登录类型的 shell。查看策略源代码,您将看到 mgr_r 只能与 mgr_t、mgr_register_t 和 rolechange_t 域关联在一起。cashier_r 角色只能与 cashier_t 和 cashier_register_t 域关联在一起。类似地,SELinux 用户 mgr_u 只能与 mgr_r 角色关联在一起,cashier_u 只能与 cashier_r 关联在一起。用户 full_u 可以与 mgr_r 或 cashier_r 关联在一起。
图 2 显示了所有这些部分如何相互联系。
图 2. 各部分如何联系
位于顶部的一行显示了 SELinux 用户,位于中间的一行列出了角色,而位于底部的一行列出了域。只要连接了三行,就可以使用任意一行中的条目构造一个有效的安全上下文。在策略中,用户定义:
user full_u roles { mgr_r cashier_r }; |
将定义一个用户及其与角色的连接,而角色定义:
role cashier_r types { cashier_t cashier_register_t }; |
将定义中间行条目及其与底部行的连接。
但是如何阻止 Bob 将 “full_u:mgr_r:mgr_t” 写入 /proc/$$/attr/exec?有很多方法。其中之一就是类型增强。回头查看图 1,当您在 cashier_t 类型中后,就只能进入 cashier_write_t。另外,SELinux 策略将指定允许哪些角色转换。因此使用:
allow mgr_r cashier_r; |
指定角色 mgr_r 中的进程可以切换到角色 cashier_r 中的有效上下文。但是没有下面的一行:
allow cashier_r mgr_r; |
因此 Bob 不能转换到 cashier_r 中的任何上下文。
我们还拒绝让 Mary 进入 cashier_t 域。查看 图 1,域转换本身实际上是被允许的。并且策略行:
allow mgr_r cashier_r; |
还允许角色转换。但是,要实现角色转换,策略指定必须通过 /bin/role_change 入口点首先进入 rolechange_t。此程序将不会覆盖其上下文的 SELinux 用户部分。因此,登录到 mgr_u:mgr_r:mgr_t 中后,除了作为由 /usermap.conf 文件授权并由 pam_ctx.so 模块执行的有效 full_u 用户身份重新登录之外,没有其他方法可以转换到 cashier_r 角色。
有一点没有在策略中禁止。注意,对 SELinux 用户转换没有任何固有控制。所有这类控制都必须通过把 SELinux 用户与角色和域绑定在一起实现,这样,角色和域转换不允许转换到其他 SELinux 用户的任何有效上下文 —— 如果这是我们的需求。
具体来讲,让我们尝试以下操作。以根用户身份登录并修改 /bin/register.py,获悉它的上下文。我们将把一些行添加到新文件 addme 中,然后把该文件插入到 /bin/register.py 中靠近顶部的位置。
echo 0 > /selinux/enforce cat > /root/addme << EOF f=open("/proc/self/attr/current", "r") print f.readlines() f.close() EOF nano /bin/register.py |
现在使用向下箭头键把光标移到 import 行下,然后为 Read File 键入 Control-r,然后键入 /root/addme。现在键入 Control-O 把数据写入文件,返回确认文件名,然后键入 Control-X 退出。最后,把 SELinux 设置回强制模式。
echo 1 > /selinux/enforce logout |
以 bob 的身份登录,显式要求 SELinux 进行域转换,然后运行 register.py:
echo "full_u:cashier_r:cashier_register_t" > /proc/self/attr/exec /bin/register.py bob 25 |
现在 register.py 将作为 full_u:cashier_r:cashier_register_t 运行!把上下文回传到 /proc/pid/attr/exec 文件中,这将导致 SELinux 在下一次执行时尝试转换到该上下文中。当然,只有在转换有效时才会成功。在本例中,转换是有效的,因为没有更改角色,并且允许执行 cashier_exec_t 类型文件的 cashier_t 转换到 cashier_register_t。如果尝试:
echo "full_u:mgr_r:mgr_register_t" > /proc/self/attr/exec /bin/register.py bob 25 |
您将注意到权限被拒绝。最后,您可以尝试:
echo "full_u:mgr_r:cashier_register_t" > /proc/self/attr/exec /bin/register.py bob 25 |
这一次,权限没有被拒绝,但是上下文被报告为 cashier_u:cashier_r:cashier_register_t。为什么行为不同?由于 full_u:mgr_r:mgr_register_t 是一个有效的上下文,因此下一次执行将实际尝试域转换并失败。但是,full_u:mgr_r:cashier_register_t 甚至不是一个有效的上下文,因为 mgr_r 可能未与 cashier_register_t 关联在一起。如果在把上下文回传到 /proc/self/attr/exec 后查看返回值,则将发现它已经失败。
echo "full_u:mgr_r:cashier_register_t" > /proc/self/attr/exec echo $? 1 |
因此当您下一次运行 register.py 时,register.py 不会尝试请求的域转换,而是简单地继续默认域转换并成功。
此时,您可能认为通过严格地使用 TE 而不使用角色或 SELinux 用户就可以实现我们的目标。但是,使用角色和用户可以使将来的系统管理更加简单。当您接下来了解如何在 Fedora Core 8 中实现此系统时,将更清楚地明白这一点。
使用 Fedora Core 8
Fedora 8 默认启用了 SELinux。它集成了全新的 SELinux 技术,使用可装载策略模块简化策略定制并使用 semanage 简化用户和 RBAC 管理(Semanage 用于配置 SELinux 策略的某些元素,而无需对策略源代码进行修改或重新编译)。
让我们首先执行一次默认安装。首先下载 Fedora-8-i386-DVD.iso(请查阅下面的 参考资料 中的链接)。为了方便起见,您可以将其重命名为 f8.img。您将使用它作为 cdrom 映像以将 Fedora 8 安装到 qemu 下:
dd if=/dev/zero of=f8.img bs=1G seek=10 count=1 qemu -hda f8.img -cdrom f8.iso -boot d -m 1024 -vnc 3 |
然后,启动 VNCviewer:
vncviewer :3 |
在 VNC 窗口中,遵循几乎全部的默认安装设置,惟一的例外是:当询问要安装的包时,请取消选中 “Office and Productivity”,然后选中 “Software Development”。
安装完成后,在重新启动后继续遵循设置说明并选择要创建的非根用户帐户。最后,当映像就绪后,以该用户身份登录。
打开本文的浏览器窗口并下载 Fedora 8 的源 tarball(请查阅 参考资料 中的链接)。把它保存到主目录中的 ~myuser/cash_register_f8.tgz。
在左上角的 “Applications” 菜单中,选择 System Tools > Terminal。然后键入 su - 并输入根用户密码以打开根用户 shell。现在您已经准备好安装安全的收银机结算系统。
(注:如果无法忍受系统过慢的显示速度,您可以通过输入 runlevel 3 来退出 X 窗口。您可以通过键入 /sbin/init 3 来完成这项操作。您始终可以通过使用 /sbin/init 5 重新输入 runlevel 5 来重新启动 X 窗口。在 runlevel 3,只需在终端以根用户身份登录)。
首先,强制退出后台守护进程,以便您可以手动运行 yum:
killall -9 yum-updatesd |
接下来安装 SELinux 策略模块开发包:
yum install selinux-policy-devel.noarch |
现在复制示例策略模块目录,把收银机策略文件复制到示例目录中,然后编译这些文件:
cd /usr/share/selinux/ cp -r devel cash_register cd cash_register rm example.* tar zxf ~myuser/cash_register_f8.tgz mv register.py /bin make |
策略被编译成 cash_register.pp 文件中的二进制策略模块。要装载它,请执行以下代码:
semodule -i cash_register.pp |
接下来,创建用户 mary 和 bob:
adduser bob adduser mary passwd bob passwd mary |
现在用户已经存在,请设置 RBAC 以将这些用户登录到相应的角色中:
semanage user -a -R cashier_r -P cashier bob_u semanage login -a -s bob_u bob semanage user -a -R mgr_r -P mgr mary_u semanage login -a -s mary_u mary |
semanage user 命令将创建一个新 SELinux 用户。SELinux 用户不是 Linux 用户名,而是附加到进程和文件的 SELinux 上下文(由 id -Z 返回)的一部分。如果在终端中键入 id -Z,您可能会看到 system_u 或 unconfined_u。虽然 Linux 用户名和 SELinux 用户名可能相同,但是它们本身并没有联系。但是,登录进程将使用 Linux 用户名来为安全上下文选择 SELinux 用户。如上一节所述,SELinux 用户被限定到可以关联的角色中,同样,SELinux 角色被限定到关联的 SELinux 域(类型)中。
您将使用 semanage user 创建两个 SELinux 用户 mary_u 和 bob_u。同时,您将指定这两个用户可以关联的角色。用户 bob_u 只能使用 cashier_r 角色,而 mary_u 只能使用 mgr_r。您还必须为用户的主目录类型指定一个前缀。对于 Mary,请指定 mgr,并且将主目录扩展为 mgr_home_dir_t,并将 mgr_home_t 用于该目录中的文件。
semanage login 命令将把 Linux 用户名与 SELinux 用户绑定在一起。指定 mary 将以 mary_u 的身份登录,bob 将以 bob_u 的身份登录。
我们仍然需要为收银机值创建基本目录结构:
mkdir /data mkdir /data/final mkdir /data/cashier_r mkdir /data/mgr_r chmod 777 /data/cashier_r chmod 777 /data/mgr_r chmod 777 /data/final |
最后,为新用户重新标记已创建和已安装的所有文件以及主目录:
fixfiles -f relabel /data /bin/register.py /home |
注意这一次我们并未在策略中定义 SELinux 用户。而是用 semanage 命令创建了用户并把这些用户与相应的角色关联起来。
如果只希望允许 bob 以 cashier_r 的身份登录而 mary 以 mgr_r 的身份登录,那么不需要进行什么修改。但是,您可能需要让用户 charlie 能够以 mgr_r 或 cashier_r 的身份登录。这样做将要求进行更多更改。首先,创建用户:
adduser charlie passwd charlie semanage user -a -R mgr_r -R cashier_r -P mgr charlie_u semanage login -a -s charlie_u charlie |
现在告诉 PAM 应当询问 charlie 想要以哪个角色的身份登录。首先,打开 /etc/pam.d/login 并把下列行:
session required pam_selinux.so open |
替换为:
session required pam_selinux.so open select_context |
这样做将告诉 pam_selinux.so 模块用户应当能够在登录时选择默认上下文。接下来,告诉系统要为角色 charlie_r 使用的默认类型。在登录时,系统将允许 Charlie 指定角色而不使用默认值(mgr_r,因为我们把它列在 semanage user 命令中的第一位)。可供选择的选项是您在创建用户时用 -R 标志指定的所有角色。SElinux 随后将使用与被请求角色相关的默认类型,因此您必须为 cashier_r 指定一个默认类型:
echo "cashier_r:cashier_t" >> \ /etc/selinux/targeted/contexts/default_type |
现在当 Charlie 登录到控制台(或者按 Ctrl-Alt-F2 组合键登录到控制台,或者如前述输入 runlevel 3)时,系统将询问他要以哪个角色的身份登录。默认值将是 mgr_r,但是他也可以选择进入 cashier_r。如果他选择 cashier_r,他将能够以收银员的身份存储值,但是根据已经定义的策略,他不能够读取 Charlie 主目录中的任何文件。
注意,register.py 不对 Charlie 以经理和收银员的身份为自己的收银机存储的值进行保护。当然,这样的更改将十分容易实现。