user namespace是所有namespace中实现最复杂的一个,也是最晚引入的一个,是在linux2.6版本中才引入。因为涉及到权限机制,跟capability有着比较密切的关系。
创建user namespace
在clone或者unshare系统调用使用CLONE_NEWUSER参数后,在子进程中看到的uid和gid跟父进程中的不一样,子进程中找不到uid时,会显示最大的uid 65534(在/proc/sys/kernel/overflowuid中设置)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| vagrant@ubuntu-xenial:/tmp$ id uid=1000(vagrant) gid=1000(vagrant) groups=1000(vagrant) vagrant@ubuntu-xenial:/tmp$ readlink /proc/$$/ns/user user:[4026531837]
# 使用unshare命令创建新的user namespace vagrant@ubuntu-xenial:/tmp$ unshare --user /bin/bash nobody@ubuntu-xenial:/tmp$ readlink /proc/$$/ns/user user:[4026532145] # 新的user namespace没有映射关系,默认使用/proc/sys/kernel/overflowuid中定义的user id和/proc/sys/kernel/overflowgid中的group id nobody@ubuntu-xenial:/tmp$ id uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
#--------------------------第二个shell窗口---------------------- # 在父user namespace上创建文件夹,可以看到用户为vagrant vagrant@ubuntu-xenial:/tmp$ mkdir test vagrant@ubuntu-xenial:/tmp$ ll /tmp | grep test drwxrwxr-x 2 vagrant vagrant 4096 Sep 23 17:11 test/
#--------------------------第一个shell窗口---------------------- # 在新创建的user namespace下用户显示为nobody nobody@ubuntu-xenial:/tmp$ ll /tmp | grep test drwxrwxr-x 2 nobody nogroup 4096 Sep 23 17:11 test/
|
映射user id和group id到新的user namespace
创建完新的user namespace后,通常会先映射user id和group id,方法为添加映射关系到/proc/${pid}/uid_map和/proc/${pid}/gid_map中。
user namespace被创建以后,第一个进程被赋予了该namespace的所有权限,但该进程并不拥有父namespace的任何权限。利用该机制可以做到一个用户在父user namespace中是普通用户,在子user namespace中是超级用户的功能。
为了将容器中的uid和父user namespace上的uid和gid进行 关联起来,可通过/proc//uid_map和/proc//gid_map来进行映射的。这两个文件的格式为:
1
| ID-inside-ns ID-outside-ns length
|
第一个字段ID-inside-ns表示在容器显示的UID或GID,
第二个字段ID-outside-ns表示容器外映射的真实的UID或GID。
第三个字段表示映射的范围,一般填1,表示一一对应。
0 1000 256
这个配置的含义为父user namespace的1000-1256映射到新user namespace的0-256.
创建子进程在没有指定CLONE_NEWUSER时文件内容如下,子进程跟父进程的用户完全一致:
1 2 3
| # 表示把namespace内部从0开始的uid映射到外部从0开始的uid,其最大范围是无符号32位整形 [root@centos7 1325]# cat uid_map 0 0 4294967295
|
要想实现以普通用户运行程序,在子进程中以root用户执行,仅需要将uid_map文件修改为普通用户映射到子进程中的0即可,因为uid为0表示root用户。
那么谁拥有写入该文件的权限呢?
/proc/${pid}/[u|g]id的拥有者为创建新user namespace的用户,拥有map文件写入权限的仅有两个用户:和该用户在同一个user namespace中的root用户,创建新的user namespace的用户。创建新的user namespace有没有写入map文件的权限,还要取决于capability中的CAP_SETUID和CAP_SETGID两个权限。
为了方便写入/proc/${pid}/uid_map和/proc/${pid}/gid_map文件,可以使用newuidmap和newgidmap命令来完成。
继续上述例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #--------------------------第一个shell窗口---------------------- # 在新user namespace中获取当前的进程号 nobody@ubuntu-xenial:/tmp$ echo $$ 13226
#--------------------------第二个shell窗口---------------------- vagrant@ubuntu-xenial:/tmp$ ll /proc/13226/uid_map /proc/13226/gid_map -rw-r--r-- 1 vagrant vagrant 0 Sep 23 17:31 /proc/13226/gid_map -rw-r--r-- 1 vagrant vagrant 0 Sep 23 17:31 /proc/13226/uid_map # 提示当前进程没有权限写入 vagrant@ubuntu-xenial:/tmp$ echo '0 1000 100' > /proc/13226/uid_map -bash: echo: write error: Operation not permitted
# 查看当前bash没有任何capability vagrant@ubuntu-xenial:/tmp$ cat /proc/$$/status | egrep 'Cap(Inh|Prm|Eff)' CapInh: 0000000000000000 CapPrm: 0000000000000000 CapEff: 0000000000000000
# 使用root权限给/bin/bash可执行文件增加cap_setgid和cap_setuid vagrant@ubuntu-xenial:/tmp$ sudo setcap cap_setgid,cap_setuid+ep /bin/bash # 启动新的bash后capability会生效 vagrant@ubuntu-xenial:/tmp$ bash vagrant@ubuntu-xenial:/tmp$ cat /proc/$$/status | egrep 'Cap(Inh|Prm|Eff)' CapInh: 0000000000000000 CapPrm: 00000000000000c0 CapEff: 00000000000000c0
# 重新写入 vagrant@ubuntu-xenial:/tmp$ echo '0 1000 100' > /proc/13226/uid_map vagrant@ubuntu-xenial:/tmp$ echo '0 1000 100' > /proc/13226/gid_map
# 第二次再写入会失败,仅允许写入一次 vagrant@ubuntu-xenial:/tmp$ echo '0 1000 100' > /proc/13226/uid_map bash: echo: write error: Operation not permitted
# 将刚才设置的capability取消 vagrant@ubuntu-xenial:/tmp$ sudo setcap cap_setgid,cap_setuid-ep /bin/bash vagrant@ubuntu-xenial:/tmp$ getcap /bin/bash /bin/bash = vagrant@ubuntu-xenial:/tmp$ exit exit
#--------------------------第一个shell窗口---------------------- # 第一个窗口中userid已经变更为0了 nobody@ubuntu-xenial:/tmp$ id uid=0(root) gid=0(root) groups=0(root) # 重新执行一个新的bash,会发现提示符已经变更为root了 nobody@ubuntu-xenial:/tmp$ bash root@ubuntu-xenial:/tmp#
# 可以看到新的bash已经拥有的所有的capability,但也仅限于当前的user namespace中 root@ubuntu-xenial:/tmp# cat /proc/$$/status | egrep 'Cap(Inh|Prm|Eff)' CapInh: 0000000000000000 CapPrm: 0000003fffffffff CapEff: 0000003fffffffff
|
问题
user namespace在linux3.8内核版本上才实现,存在一定的安全问题。在redhat和centos系统下,user namespace作为了一个实验feature,默认情况下未开启。
执行如下命令sudo grubby --args="user_namespace.enable=1" --update-kernel="$(grubby --default-kernel)"
并重启系统后可以就可以打开user namespace feature了。执行grubby --remove-args="user_namespace.enable=1" --update-kernel="$(grubby --default-kernel)"
可关闭user namespace feature。
经实验,上述操作未生效,后续待查该问题。
ref