Linux Namespace系列(07):user namespace (CLONE_NEWUSER) (第一部分)

User namespace用来隔离user权限相关的Linux资源,包括user IDs and group IDskeys , 和capabilities. html

这是目前实现的namespace中最复杂的一个,由于user和权限息息相关,而权限又事关容器的安全,因此稍有不慎,就会出安全问题。linux

user namespace能够嵌套(目前内核控制最多32层),除了系统默认的user namespace外,全部的user namespace都有一个父user namespace,每一个user namespace均可以有零到多个子user namespace。 当在一个进程中调用unshare或者clone建立新的user namespace时,当前进程原来所在的user namespace为父user namespace,新的user namespace为子user namespace.shell

在不一样的user namespace中,一样一个用户的user ID 和group ID能够不同,换句话说,一个用户能够在父user namespace中是普通用户,在子user namespace中是超级用户(超级用户只相对于子user namespace所拥有的资源,没法访问其余user namespace中须要超级用户才能访问资源)。ubuntu

从Linux 3.8开始,建立新的user namespace不须要root权限。安全

本篇全部例子都在ubuntu-server-x86_64 16.04下执行经过bash

建立user namespace

#--------------------------第一个shell窗口----------------------
#先记录下目前的id,gid和user namespace
dev@ubuntu:~$ id
uid=1000(dev) gid=1000(dev) groups=1000(dev),4(adm),24(cdrom),27(sudo)
dev@ubuntu:~$ readlink /proc/$$/ns/user
user:[4026531837]

#建立新的user namespace
dev@ubuntu:~$ unshare --user /bin/bash
nobody@ubuntu:~$ readlink /proc/$$/ns/user
user:[4026532464]
nobody@ubuntu:~$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)

很奇怪,为何上面例子中显示的用户名是nobody,它的id和gid都是65534?app

这是由于咱们尚未映射父user namespace的user ID和group ID到子user namespace中来,这一步是必须的,由于这样系统才能控制一个user namespace里的用户在其余user namespace中的权限。(好比给其余user namespace中的进程发送信号,或者访问属于其余user namespace挂载的文件)ide

若是没有映射的话,当在新的user namespace中用getuid()和getgid()获取user id和group id时,系统将返回文件/proc/sys/kernel/overflowuid中定义的user ID以及proc/sys/kernel/overflowgid中定义的group ID,它们的默认值都是65534。也就是说若是没有指定映射关系的话,会默认映射到ID65534。函数

下面看看这个user能干些什么测试

#--------------------------第一个shell窗口----------------------
#ls的结果显示/root目录属于nobody
nobody@ubuntu:~$ ls -l /|grep root
drwx------   3 nobody nogroup  4096 7月   8 18:39 root
#可是当前的nobody帐号访问不了,说明这两个nobody不是一个ID,他们之间没有映射关系
nobody@ubuntu:~$ ls /root
ls: cannot open directory '/root': Permission denied

#这里显示/home/dev目录属于nobody
nobody@ubuntu:~$ ls -l /home/
drwxr-xr-x 11 nobody nogroup 4096 7月   8 18:40 dev
#touch成功,说明虽然没有显式的映射ID,但仍是能访问父user namespace里dev帐号拥有的资源
#说明他们背后仍是有映射关系
nobody@ubuntu:~$ touch /home/dev/temp01
nobody@ubuntu:~$

映射user ID和group ID

一般状况下,建立新的user namespace后,第一件事就是映射user和group ID. 映射ID的方法是添加配置到/proc/PID/uid_map和/proc/PID/gid_map(这里的PID是新user namespace中的进程ID,刚开始时这两个文件都是空的).

这两个文件里面的配置格式以下(能够有多条):

ID-inside-ns   ID-outside-ns   length

举个例子, 0 1000 256这条配置就表示父user namespace中的1000~1256映射到新user namespace中的0~256。

系统默认的user namespace没有父user namespace,但为了保持一致,kernel提供了一个虚拟的uid和gid map文件,看起来是这样子的:
dev@ubuntu:~$ cat /proc/$$/uid_map
0 0 4294967295

那么谁能够向这个文件中写配置呢?

/proc/PID/uid_map和/proc/PID/gid_map的拥有者是建立新user namespace的这个user,因此和这个user在一个user namespace的root帐号能够写。但这个user本身有没有写map文件权限还要看它有没有CAP_SETUID和CAP_SETGID的capability。

注意:只能向map文件写一次数据,但能够一次写多条,而且最多只能5条

关于capability的详细介绍能够参考这里,简单点说,原来的Linux就分root和非root,不少操做只能root完成,好比修改一个文件的owner,后来Linux将root的一些权限分解了,变成了各类capability,只要拥有了相应的capability,就能作相应的操做,不须要root帐户的权限。

下面咱们来看看如何用dev帐号映射uid和gid

#--------------------------第一个shell窗口----------------------
#获取当前bash的pid
nobody@ubuntu:~$ echo $$
24126

#--------------------------第二个shell窗口----------------------
#dev是map文件的owner
dev@ubuntu:~$ ls -l /proc/24126/uid_map /proc/24126/gid_map
-rw-r--r-- 1 dev dev 0 7月  24 23:11 /proc/24126/gid_map
-rw-r--r-- 1 dev dev 0 7月  24 23:11 /proc/24126/uid_map

#但仍是没有权限写这个文件
dev@ubuntu:~$ echo '0 1000 100' > /proc/24126/uid_map
bash: echo: write error: Operation not permitted
dev@ubuntu:~$ echo '0 1000 100' > /proc/24126/gid_map
bash: echo: write error: Operation not permitted
#当前用户运行的bash进程没有CAP_SETUID和CAP_SETGID的权限
dev@ubuntu:~$ cat /proc/$$/status | egrep 'Cap(Inh|Prm|Eff)'
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000

#为/binb/bash设置capability,
dev@ubuntu:~$ sudo setcap cap_setgid,cap_setuid+ep /bin/bash
#从新加载bash之后咱们看到相应的capability已经有了
dev@ubuntu:~$ exec bash
dev@ubuntu:~$ cat /proc/$$/status | egrep 'Cap(Inh|Prm|Eff)'
CapInh: 0000000000000000
CapPrm: 00000000000000c0
CapEff: 00000000000000c0


#再试一次写map文件,成功了
dev@ubuntu:~$ echo '0 1000 100' > /proc/24126/uid_map
dev@ubuntu:~$ echo '0 1000 100' > /proc/24126/gid_map
dev@ubuntu:~$
#再写一次就失败了,由于这个文件只能写一次
dev@ubuntu:~$ echo '0 1000 100' > /proc/24126/uid_map
bash: echo: write error: Operation not permitted
dev@ubuntu:~$ echo '0 1000 100' > /proc/24126/gid_map
bash: echo: write error: Operation not permitted

#后续测试不须要CAP_SETUID了,将/bin/bash的capability恢复到原来的设置
dev@ubuntu:~$ sudo setcap cap_setgid,cap_setuid-ep /bin/bash
dev@ubuntu:~$ getcap /bin/bash
/bin/bash =

#--------------------------第一个shell窗口----------------------
#回到第一个窗口,id已经变成0了,说明映射成功
nobody@ubuntu:~$ id
uid=0(root) gid=0(root) groups=0(root),65534(nogroup)

#--------------------------第二个shell窗口----------------------
#回到第二个窗口,确认map文件的owner,这里24126是新user namespace中的bash
dev@ubuntu:~$ ls -l /proc/24126/
......
-rw-r--r-- 1 dev dev 0 7月  24 23:13 gid_map
dr-x--x--x 2 dev dev 0 7月  24 23:10 ns
-rw-r--r-- 1 dev dev 0 7月  24 23:13 uid_map
......

#--------------------------第一个shell窗口----------------------
#从新加载bash,提示有root权限了
nobody@ubuntu:~$ exec bash
root@ubuntu:~#

#0000003fffffffff表示当前运行的bash拥有全部的capability
root@ubuntu:~# cat /proc/$$/status | egrep 'Cap(Inh|Prm|Eff)'
CapInh: 0000000000000000
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff
#--------------------------第二个shell窗口----------------------
#回到第二个窗口,发现owner已经变了,变成了root
#目前还不清楚为何有这样的机制
dev@ubuntu:~$ ls -l /proc/24126/
......
-rw-r--r-- 1 root root 0 7月  24 23:13 gid_map
dr-x--x--x 2 root root 0 7月  24 23:10 ns
-rw-r--r-- 1 root root 0 7月  24 23:13 uid_map
......
#虽然不能看目录里有哪些文件,可是能够读里面文件的内容
dev@ubuntu:~$ ls -l /proc/24126/ns
ls: cannot open directory '/proc/24126/ns': Permission denied
dev@ubuntu:~$ readlink /proc/24126/ns/user
user:[4026532464]


#--------------------------第一个shell窗口----------------------
#和第二个窗口同样的结果
root@ubuntu:~# ls -l /proc/24126/ns
ls: cannot open directory '/proc/24126/ns': Permission denied
root@ubuntu:~# readlink /proc/24126/ns/user
user:[4026532464]

#仍然不能访问/root目录,由于他的拥有着是nobody
root@ubuntu:~# ls -l /|grep root
drwx------   3 nobody nogroup  4096 7月   8 18:39 root
root@ubuntu:~# ls /root
ls: cannot open directory '/root': Permission denied

#对于原来/home/dev下的内容,显示的owner已经映射过来了,由dev变成了新namespace中的root,
#当前root用户能够访问他里面的内容
root@ubuntu:~# ls -l /home
drwxr-xr-x 8 root root 4096 7月  21 18:35 dev
root@ubuntu:~# touch /home/dev/temp01
root@ubuntu:~#

#试试设置主机名称
root@ubuntu:~# hostname container001
hostname: you must be root to change the host name
#修改失败,说明这个新user namespace中的root帐号在父user namespace里面很差使
#这也正是user namespace所指望达到的效果,当访问其余user namespace里的资源时,
#是以其余user namespace中的相应帐号的权限来执行的,
#好比这里root对应父user namespace的帐号是dev,因此改不了系统的hostname

那是否是把系统默认user namespace的root帐号映射到新的user namespace中,新user namespace的root就能够修改默认user namespace中的hostname呢?

#--------------------------第三个shell窗口----------------------
#从新打开一个窗口
#这里再也不手动映射uid和gid,而是利用unshare命令的-r参数来帮咱们完成映射,
#指定-r参数后,unshare将会帮助咱们将当前运行unshare的帐号映射成新user namesapce的root帐号
#这里用了sudo,目的是让root帐号来运行unshare命令,
#这样就将外面的root帐号映射成新user namespace的root帐号
dev@ubuntu:~$ sudo unshare --user -r /bin/bash
root@ubuntu:~# id
uid=0(root) gid=0(root) groups=0(root)

#确认是用root映射root
root@ubuntu:~# echo $$
24283
root@ubuntu:~# cat /proc/24283/uid_map
         0          0          1
root@ubuntu:~# cat /proc/24283/gid_map
         0          0          1

#能够访问/root目录下的东西,但没法操做/home/dev/下的文件
root@ubuntu:~# ls -l / |grep root$
drwx------   6 root root  4096 8月  14 23:11 root
root@ubuntu:~# touch /root/temp01
root@ubuntu:~# ls -l /home
drwxr-xr-x 11 nobody nogroup 4096 8月  14 23:13 dev
root@ubuntu:~# touch /home/dev/temp01
touch: cannot touch '/home/dev/temp01': Permission denied

#尝试修改hostname,仍是失败
root@ubuntu:~# hostname container001
hostname: you must be root to change the host name

上面的例子中虽然是将root帐号映射到了新user namespace的root帐号上,但修改hostname、访问/home/dev下的文件依然失败,那是由于无论怎么映射,当用子user namespace的帐号访问父user namespace的资源的时候,它启动的进程的capability都为空,因此这里子user namespace的root帐号到父namespace中就至关于一个普通的帐号。

注意:对于map文件来讲,在父user namespace和子user namespac中打开子user namespace中进程的这个文件看到的都是一样的内容,但若是是在其余的user namespace中打开这个map文件,‘ID-outside-ns’表示的就是映射到当前user namespace的ID.这里听起来有点绕,看下面的例子

#--------------------------打开一个新窗口----------------------
#建立一个新的user namespace,并取名container001
dev@ubuntu:~$ unshare --user --uts -r /bin/bash
root@ubuntu:~# hostname container001
root@ubuntu:~# exec bash
#记下bash的pid
root@container001:~# echo $$
27898

#在container001里面建立新的namespace container002
root@container001:~# unshare --user --uts -r /bin/bash
root@container001:~# hostname container002
root@container001:~# exec bash
#记下bash的pid
root@container002:~# echo $$
28066

#查看本身namespace中进程的uid map文件
#这里表示父user namespace的0映射到了当前namespace的0
root@container002:~# cat /proc/28066/uid_map
         0          0          1

#--------------------------再打开一个新窗口----------------------
#在系统默认namespace中查看一样这个文件,发现和上面的显示的不同
#由于默认namespace是container002的爷爷,因此他们两个里面看到的东西有可能不同
#这里表示当前user namespace的帐号1000映射到了进程28066所在user namespace的帐号0
#固然若是上面是用root帐号建立的container001,这里显示的内容就和上面同样了
dev@ubuntu:~$ cat /proc/28066/uid_map
         0       1000          1

#咱们再进入到container001,在里面看看这个文件,发现和在ontainer002看到的结果同样
#说明对于进程28066来讲,在他本身所在的user namespace和他的父user namespace看到的map文件内容是同样的
dev@ubuntu:~$ nsenter --user --uts -t 27898 --preserve-credentials bash
root@container001:~# cat /proc/28066/uid_map
         0          0          1
#默认状况下,nsenter会调用setgroups函数去掉root group的权限,
#这里--preserve-credentials是为了让nsenter不调用setgroups函数,由于调用这个函数须要root权限

#测试完成后能够关闭这两个窗口,后面不会再用到了

user namespace的owner

当一个用户建立一个新的user namespace的时候,这个用户就是这个新user namespace的owner,在父user namespace的这个用户就会拥有新user namespace及其全部子孙user namespace的全部capabilities.

#--------------------------第四个shell窗口----------------------
#新建用户test用于测试
dev@ubuntu:~$ sudo useradd test
dev@ubuntu:~$ sudo passwd test
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully

#切换到test帐户并建立新的user namespace
#为了便于区分,同时建立新的uts namespace
dev@ubuntu:~$ su test
Password:
test@ubuntu:/home/dev$ unshare --user --uts -r /bin/bash

#设置一个容易区分的hostname
root@ubuntu:/home/dev# hostname container001
root@ubuntu:/home/dev# exec bash
root@container001:/home/dev# readlink /proc/$$/ns/user
user:[4026532463]
root@container001:/home/dev# echo $$
24419

#--------------------------第五个shell窗口----------------------
#使用dev帐号新建一个user namespace
dev@ubuntu:~$ unshare --user --uts -r /bin/bash
root@ubuntu:~# hostname container002
root@ubuntu:~# exec bash
root@container002:~# readlink /proc/$$/ns/user
user:[4026532464]
root@container002:~# echo $$
24435

#--------------------------第六个shell窗口----------------------
#用dev帐号往container002中加入新的进程/bin/bash成功,由于dev是container002的owner
dev@ubuntu:~$ nsenter --user -t 24435 --preserve-credentials --uts /bin/bash
root@container002:~# id
uid=0(root) gid=0(root) groups=0(root),65534(nogroup)
root@container002:~# readlink /proc/$$/ns/user
user:[4026532464]

#回到默认user namespace
root@container002:~# exit
exit
dev@ubuntu:~$

#由于container001的owner是test,用dev帐号往container001中加入新的进程/bin/bash失败
dev@ubuntu:~$ nsenter --user -t 24419 --preserve-credentials --uts /bin/bash
nsenter: cannot open /proc/24419/ns/user: Permission denied

#用root帐号往container001中加入新的进程/bin/bash成功
dev@ubuntu:~$ sudo nsenter --user -t 24419 --preserve-credentials --uts /bin/bash
nobody@container001:~$ readlink /proc/$$/ns/user
user:[4026532463]
#因为root帐号没有映射到container001中,因此这里在container001中看到的帐号是nobody
nobody@container001:~$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)

#退出container001,便于后续测试
nobody@container001:~$ exit
dev@ubuntu:~$
#--------------------------第五个shell窗口----------------------
#回到第5个窗口,继续建立一个新的user namespace
root@container002:~# unshare --user --uts -r /bin/bash
root@container002:~# hostname container003
root@container002:~# exec bash
root@container003:~# readlink /proc/$$/ns/user
user:[4026532471]
root@container003:~# echo $$
24533

#--------------------------第六个shell窗口----------------------
#回到第6个窗口,用dev帐号往container003(孙子user namespace)中加入新的bash进程,成功,
#说明dev拥有孙子user namespace的capabilities
dev@ubuntu:~$ nsenter --user -t 24533 --preserve-credentials --uts /bin/bash
root@container003:~# readlink /proc/$$/ns/user
user:[4026532471]

结束语

本文先介绍了user namespace的一些概念,而后介绍如何配置mapping文件,最后介绍了user namespace的owner。从上面的介绍中能够看出,user namespace仍是比较复杂的,要了解user namespace,须要对Linux下的权限有一个基本的了解。下一篇中将继续介绍user namespace和其余namespace的关系,以及一些其余的注意事项。

参考

相关文章
相关标签/搜索