基于角色的访问控制(RBAC)是常用的简化管理的安全模式,通过给用户指定角色,然后再给角色授权来实现,RBAC在SELinux中扮演用户与基础类型加强模式(TE)之间的抽象层,它提供非常细粒度的访问控制,但是管理难度并没有降低,研究如何将SELinux上下文的3部分(策略、内核和用户空间)协同工作以强制使用RBAC和限制Linux用户在TE策略中。

在SELinux下安全策略的实施是在基于角色的访问控制(RBAC)下的类型增强(TE),(SELinux也能实现多级安全(MLS)的实施,MLS在本文范围之外),类型增强(TE)是最常见的,但也是最不容易理解的,因为它强制实施更细粒度的许可:当某些东西因为异常访问拒绝而遭到破坏时,类型增强(TE)是最可靠的,在TE中,一个进程的安全域(它的影响超过了系统)是由任务历史和当前执行的程序决定的

RBAC的概念并不象TE一样被经常提到,并且因为他与TE集成的原因,总是让人难以理解,你通常会认为RBAC是按照用户被分配的角色进行访问的,从TE方面来说SELinux明确指出了基于角色的访问,因此SELinux下RBAC的目标是允许基于角色的权限管理,然后限制一个角色的权利域,合成一个有效的安全上下文。

要知道这是如何工作的,请看一个简单的现金帐簿会计系统是如何使用SELinux提供安全保护的,你将在两个完全不同的环境(看这两个环境相关的下载,附录A)下看到一样的解决方案:

在developerWorks网站上有一篇文章“从零开始SELinux”介绍了如何从零开始一个SELinux系统,这个系统显示在内核和用户空间里的部分是绑定在一起的。

Fedora Core 8 系统,Fedora Core 8 系统(写本文时的最新版本)展示了SELinux与RBAC是如何高度地集成在一起的。

与角色一起工作

假设你已经要求一个库房部门部署一套安全登记库存会计系统,因为每天都进行现金帐簿,最终数量必须同时被出纳和经理统计,因此我们首先定义两个角色,出纳和经理,然后我们将角色赋予了需要它的雇员,即赋予出纳和经理。

在两个系统上的策略文件或许会稍微不同,在两个系统上都使用现金帐簿系统,数据一样,所有数据存储在/data目录下,并且只能通过/bin/register.py程序进行访问,register.py能同时被经理和出纳使用,用于存储数值,为了保持代码简洁,缺少一些特点,出纳每天为他自己存储现金帐簿值,经理能为其他雇员存储数值,当出纳与经理为同一个出纳存储相同的数值时,经理能够提交这个数值。

在2007/12/12晚上9点,当出纳Bob使用register.py存储了一个数值109.95时,文件/data/cashier_r/bob/12-12-2007被创建,内容是"bob 09:00 109.95.",后来,经理Mary在9:25存储了一个相同的数值,结果文件/data/cashier_r/bob/12-12-2007被覆盖,内容为"mary 09:25 109.95.",最后,Mary在晚上9:27提交了这个数值,bob使用register.py提交后创建了文件/data/final/bob/12-12-2007,内容为"mary 09:27 bob mary 109.95."。

如果Bob和Mary对他们的数值都不同意,Mary将不能为Bob提交数值了,他不得不去找Bob协商,除非他们两个都同意,否则这两个数值都必须重新计算,重新运行register.py命令用前面的数值再计算一次,新的结果将附加到先前的结果后以便仓库管理员稍后容易阅读,bob使用/bin/register.py命令提交时将使用最后的那个数值而不是/data/cashier_r/bob/12-12-2007和/data/mgr_r/bob/12-12-2007。

注意:在这个例子中讨论的代码都为访问控制需要使用了SELinux,我们的例子简单地允许所有拥护都能完全往/data目录下的文件和目录中进行写入访问,在一个真实的部署环境中,你可能会想到使用DAC[译者注:即传统的linux权限控制,使用访问控制列表ACL]许可来进行深度防御,所有经理都希望在/data/mgr_r/bob/和/data/final/bob/目录下创建文件,牵涉到UNIX组权限的小心使用,但是更简单的办法是,完全依靠SELinux来实现强制访问控制。

首先阻止经理和出纳访问/data目录下的任何文件,除非通过register.py程序,实际上,Bob将以cashier_t类型登陆到角色cashier_r,但是cashier_t不能读取/data目录下的内容,为了能读取,他必须进入cashier_register_t类型,进入后也只能执行/bin/register.py程序,类似地,Mary将以mgr_t登陆到角色mgr_r,但是他必须进入mgr_register_t类型后才能执行/bin/register.py程序来访问/data目录下的内容。

第一个访问控制实际上发生在登陆的时候,PAM模块认为Bob必须登陆到cashier_r角色,当在内核中的SELinux类型增强服务器拒绝同意bob_u:cashier_r:cashier_t进入bob_u:cashier_r:cashier_register_t时也会发生,除了执行cashier_exec_t类型的文件,cashier_exec_t是管理员仅仅为/bin/register.py指定的类型。

当register.py拒绝同意出纳提交数据或为另一个用户存储数据时也会发生,将来通过SELinux策略进行加强,并且内核代码也会加强策略的控制,它不允许cashier_register_t访问/data/mgr_r或/data/final目录下的文件。

部署现金帐簿系统

 

部署现金帐簿会计系统的第一步建立在前面提及到的从零开始一个SELinux系统(可以在http://www.ibm.com/developerworks/linux/library/l-rbac-selinux/index.html?ca=drs-#download下载)之上,下面的步骤将展示完成一个从零开始的系统。

加载要使用的磁盘镜像(关闭qemu镜像):

mount -o loop,offset=32256 -t ext2 gentoo.img /mnt

http://www.ibm.com/developerworks/linux/library/l-rbac-selinux/index.html?ca=drs-#download下载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

在策略中的用户被创建,但是你必须创建与之对应的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"

再以root登陆并再次尝试:
ls /data

权限被拒绝,注销root然后用bob登陆,我们的出纳,登记一个数值,例如:
register bob 25.22

然后尝试欺骗系统:
register bob commit

不能工作,注销bob然后用Mary登陆:
register bob commit

哦,Mary首先需要敲入她自己的数值:
register bob 27
register bob commit

数值不匹配,想知道Bob提交后的结果吗?
cat /data/cashier_r/bob/(day)

哦,你未被允许查看它,你将不得不去找Bob进行协商,或许你要重新统计你的帐簿并查出他是对的,因此:
register bob 25.22
register bob commit

这下没有问题了,现在你以root用户登陆并:
cat /data/final/bob/(day)

这下将显示所有输入的数值了。

更进一步研究类型增强

 

不同用户使用相同的/bin/register程序读/写没有这个程序他们就不能访问的不同文件,这就是类型增强的核心概念:用户经过认证的上下文和正在被执行的代码将一起决定进程的“权利域”或“TE域”。

图1展示了我们系统中的域和类型:
 

在SELinux中基于角色的访问控制_linux
图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,注意所有的上下文都由冒号分隔的3部分组成:

◆最后一部分是域,它最终决定用户在系统上的权限
◆第二部分是角色,它限制了用户可以进入的域
◆第一部分是SELinux用户,与角色和域相似,它限制了进程可以进入的角色

PAM模块通过将上下文写入文件/proc/$$/attr/exec来设置上下文,然后再对新域执行一个有效登记类型的shell脚本,查看策略的代码,你会看到mgr_r或许是与域mgr_t,mgr_register_t, and 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展示了所有这些是如何联系在一起的

在SELinux中基于角色的访问控制_角色_02
图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里写入呢?有几个原因,其中一个就是类型增强(TE),查看图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用户上下文。

特别地,让我们尝试下面的内容,作为root用户登陆,并修改/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行下,然后按Ctrl-r读取文件,敲入/root/addme,再按Ctrl-O写这个文件,回车确认文件名,然后按Ctrl-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_t执行一个cashier_exec_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是一个有效的上下文,因此下次执行实际上试图进行域转换时失败了,但是,自从mgr_r与cashier_register_t建立联系以来,full_u: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时,它不会试图要求域转换了,但是要简单地执行默认的域转换,这会是成功的。

在这一点上,你可能会认为我们的目标通过严格使用TE而不利用角色或SELinux用户已经达到了,但是,使用角色和用户在未来将会使系统管理变得更容易,当你看了如何在Fedora Core 8上实施后将会有更清醒的认识。

使用Fedora Core 8

 

默认情况下,Fedora 8是将SELinux起用了的,它集成了最新的SELinux技术,可载入的策略模块,她使得自定义,semanage和RBAC管理更容易。(semanage用于配置SELinux策略必须的元素,它不需要改变或重新从策略代码编译)

让我们从非常无趣的几乎是默认的安装开始,首先从http://fedoraproject.org/get-fedora下载Fedora-8-i386-DVD.iso,为了方便你可以将起重新命名为f8.img,在qemu下你可以将其作为一个cdrom镜像来安装Fedora 8:

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窗口里,选择默认的安装目录,但有一个例外:当询问安装什么包时,反选“办公和生产力”,选择“软件开发”。

当安装完毕后,重新启动后,创建一个非root用户,最后,当镜像准备好了后,用这个用户登陆。

打开一个浏览器窗口然后下载代码包,地址是http://www.ibm.com/developerworks/views/download.jsp?contentid=288774&filename=code_for_f8.tgz&method=http&locale=worldwide,将其保存到你的home目录~myuser/cash_register_f8.tgz。

从顶层左方的菜单“应用程序”开始,选择系统工具终端,然后输入su - ,回车再输入root用户的密码打开一个root shell,现在你已经为安装安全帐簿会计系统作好准备了。
(注意:如果系统太慢以至于不能忍受,你可以退出X-window,将系统运行在级别3下,可以敲入/sbin/init 3来实现,你也可以用/sbin/init 5来重新启动X-window重新进入级别5,在级别3下,用root登陆)

首先,强制一个后台进程yum退出,因为你可以手工运行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用户管理命令创建了一个新的SELinux用户,SELinux用户不是Linux用户名,但是SELinux上下文的第一部分(id –Z返回的内容)是附加到一个进程和文件的,如果你在终端里敲入id –Z,将会看到system_u或unconfined_u,你的Linux用户名和SELinux用户名是可以相同的,他们不是单独联系的,但是,登陆进程为你的安全上下文使用Linux用户名来选择一个SELinux用户,与前面章节讨论的一样,SELinux用户是受与其有关联的角色限制的,同样,SELinux角色也受与其关联的SELinux域(类型)限制。

 

你正在使用semanage创建两个SELinux用户,mary_u和bob_u,同时,你也正在指定与其关联的角色,用户bob_u或许仅仅使用cashier_r角色,mary_u仅仅使用mgr_r角色,你还必须给用户home目录类型指定一个前缀。对mary而言,指定mgr,它将为她的home目录扩展为mgr_home_dir_t,并为那里面的文件扩大为mgr_home_t。

Semanage登陆命令把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既能以cashier_r登陆,也能以mgr_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命令中列出来了)之外再指定一个角色,当你创建用户时可以使用-R标记指定任何一个角色,SELinux将使用需要的角色与其关联的默然类型,因此你必须为cashier_r指定一个默认的类型:
echo "cashier_r:cashier_t" >> \
/etc/selinux/targeted/contexts/default_type

现在Charlie登陆到终端(按Ctrl-Alt-F2转到一个终端下或按前面叙述的进入级别3),他将被问及要以什么角色登陆,默认的将是mgr_r,但是他可以用cashier_r登陆,如果他这样做了,他将能作为一个出纳存储他自己的数据,但是由于我们已经定义了策略,他将不能读取他home目录下的任何文件。

注意register.py不能防止Charlie为他自己的现今帐簿存储数据,不管是作为经理还是出纳登陆,他都可以提交数据,当然,这样的一个改变将非常容易实施。