Linux namespace之:user namespace
目录:
- Linux namespace概述
- Linux namespace之:uts namespace
- Linux namespace之:mount namespace
- Linux namespace之:pid namespace
- Linux namespace之:network namespace
- Linux namespace之:user namespace
理解user namespace
user namespace涉及namespace的权限和安全问题,是内容最多也最复杂的一种namespace。本文不深入太多理论细节,而是只介绍user namespace机制导致的现象,这样可以足够简单地了解user namespace,也可以控制好篇幅。
user namespace是唯一一种不要求root权限就可以创建的namespace。换句话说,如果是非root用户,可以不使用sudo创建user namespace。
1 | # --user或-U表示创建user namespace |
用户A创建user namespace ns1后,在ns1中活动的仍然是用户A。但是,ns1中的用户A有点特殊:它的用户名将变成nobody,UID和GID都为65534,65534对应的用户名和组名分别是nobody、nogroup。
1 | $ echo $USER |
不仅如此,此时ns1中的文件、目录的owner/group也全都是nobody/nogroup。
1 | $ echo $HOME |
也就是说,从文件、目录的所有者以及所属组来看,当前的用户(nobody)在ns1中是具有对应权限的。但实际上,user namespace中的权限非常受限:只具备创建user namespace的用户(即longshuai)权限。
1 | $ pwd |
所以,原namespace中的用户和当前user namespace中的用户是有对应关系的:默认情况下,创建user namespace的用户映射为新建user namespace中的用户。
其实,用户可以指定如何映射用户,而且这通常也是创建user namespace后应该做的第一件事。
UID和GID映射涉及到的文件分别为/proc/<PID>/uid_map
和/proc/<PID>/gid_map
,它们的格式一样,都只能写入一次,第二次写入将报错。在某些老版本(内核4.14及之前的版本)里,这两个文件可能不存在,且最多只能写入5行。
这两个文件的格式为:
1 | ID_child ID_parent length |
以/proc/13333/uid_map
文件为例,假如向该文件写入0 1000 500
,这表示将父级user namespace中1000-1500之间的UID逐一映射到PID=13333所在user namespace中0-500的UID。GID的映射方式也一样如此。
尽管这两个map文件的owner和group可能正是当前用户,但是非root用户却无法直接修改该文件。要修改该文件,需要具备cap_setuid和cap_setgid能力(capability)或者直接使用具有最大权限的root用户。
1 | # 在第一个窗口创建user namespace ns1 |
只要完成了uid和gid的映射,在user namespace中的用户名和组就会改变,其有能力修改的的文件所有者以及所属组也都会改变。
例如上面的示例中是将uid=1000的longshuai用户映射到user namespace中的uid=0(即root)用户。
1 | # 在 user namespace中 |
再做一个简单的实验,不要让映射起始UID正好是创建user namespace的用户。
例如,使用UID=1200的xiaofang用户创建user namespace:
1 | $ sudo useradd -u 1200 -m xiaofang |
再设置如下映射方式0 1000 500
:
1 | # 新建一个会话窗口,在父级user namespace中执行 |
如此设置映射后,user namespace中的用户将变为哪个呢?实际上,谁创建user namespace,谁就是这个user namespace中的活动用户。也就是说,映射之前的UID=1200的xiaofang是user namespace当前的用户。
所以,父级user namespace中UID=1200映射到新的user namespace中就是UID=200:
1 | # 在新建的user namespace中执行 |
从id的输出结果看,uid=200的用户不存在,它没有对应的用户名,同理组名也一样不存在。
1 | # 在新建的user namespace中执行 |
从这个现象可以分析,在映射用户和组时,最可能也最合理的映射方式是将user namespace的创建者映射为UID=0的root。
因此,对于UID=x的用户创建的user namespace来说,可能需要如下方式映射UID:
1 | 0 x <Length> |
对于这种最常见的映射方式,unshare提供了一个快捷选项-r, --map-root-user
,可以帮助用户在创建user namespace时自动将当前用户映射为user namespace内UID=0的root。
1 | $ unshare -U -r /bin/bash |
当将创建user namespace的用户A映射为root后,在这个新的user namespace中它将具有当前namespace的【所有权】,比如可以直接创建所有类型的namespace。
1 | $ unshare -U -r /bin/bash |
但实际上这个root权限是非常受限的,因为在和其他user namespace交互时(比如修改父级user namespace的内容),内核仍然使用用户A来做权限判断。
例如,即使映射为root,也无法修改主机名:
1 | $ unshare -U -r /bin/bash |
之所以在user namespace中已经映射为root仍然无法修改主机名,是因为主机名资源是父级uts namespace的内容。其实,任何一个非user namespace类型的namespace,都有一个与之关联的user namespace,这样才能管理这些user namespace的权限。
比如系统启动后,所有初始的非user namespace的namespace,它们所关联的user namespace就是与之同层次的root user namespace。
再例如,在系统启动后创建的任意非user namespace类型的namespace,它们关联的user namespace都是root uset namespace。
再比如,在user namespace ns1中创建uts namespace ns2,ns2关联的user namespace是ns1。
因此可做出总结:创建非user namespace类型的namespace时,这些namespace关联的user namespace是创建者所属的user namespace。
因此,如果想要在user namespace中修改主机名,需要在创建user namespace的时候创建uts namespace,或者在user namespace内部创建uts namespace,这样一来,uts namespace所关联的user namespace就是这个新建的user namespace:
1 | $ unshare -u -U -r /bin/bash |
最后,用户创建的user namespace中无法挂载块设备,块设备的挂载操作只能在关联了初始root user namespace的namespace中挂载。也就是说,只要某个namespace的祖先有一个是用户创建的user namespace,都将无法挂载块设备。但允许挂载以下类型的文件系统:
1 | /proc (since Linux 3.8) |