user namespace 主要隔离了安全相关的标识符和属性,包括用户 ID,用户组 ID,key 和 capabilities 等。同样一个用户 id 在不同 user namespace 中会有不同的权限。比如,进程属于一个普通用户,但是它创建的 user namespace 确属于拥有所有权限的超级用户。使用 unshare 创建 user namespace:
chunqiu@chunqiu:/root/chunqiu/docker/mount/disk1$ unshare --user -r --mount /bin/bash
root@chunqiu:/root/chunqiu/docker/mount/disk1# id
uid=0(root) gid=0(root) groups=0(root)
root@chunqiu:/root/chunqiu/docker/mount/disk1# echo $$
13905
打开另一个 shell 窗口,查看进程 13905 所属用户:
root@chunqiu:~/chunqiu/docker/mount/disk1# ps -ef | grep 13905 | grep -v grep
chunqiu 13905 13880 0 08:26 pts/0 00:00:00 /bin/bash
从上例可以看出,进程 13905 在容器(user namespace)外属于一个普通用户,但是在 user namespace 里却属于 root 用户。
继续对上例进行深挖,unshare 的 -r 选项指明了 user namespace 用户和容器外用户的映射,查看 uid_map 和 gid_map:
root@chunqiu:/root/chunqiu/docker/mount/disk1# cat /proc/13905/uid_map
0 1002 1
root@chunqiu:/root/chunqiu/docker/mount/disk1# cat /proc/13905/gid_map
0 1002 1
可以看到 user namespace 内的 root(0) 用户/组和 user namespace 外的 chunqiu(1002) 用户/组建立映射。因此,在 user namespace 内的特权用户只是 user namespace 的普通用户,无法访问“权限不够”的文件/文件夹。如:
// user namespace 外
root@chunqiu:~/chunqiu/docker/mount/disk1# ls -l
total 1
-rw-r----- 1 root root 21 May 3 08:20 rootfile
// user namespace 内
root@chunqiu:/root/chunqiu/docker/mount/disk1# ls -l
total 1
-rw-r----- 1 nobody nogroup 21 May 3 08:20 rootfile
root@chunqiu:/root/chunqiu/docker/mount/disk1# cat rootfile
cat: rootfile: Permission denied
-r 选项建立 user namespace 内外的用户映射。如果不用 -r 选项 则需手动填写 uid_map 和 gid_map,实现用户的映射。创建 user namespace 如下:
chunqiu@chunqiu:~$ unshare --user --mount /bin/bash
nobody@chunqiu:~$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
nobody@chunqiu:~$ echo $$
14641
这里 user namespace 的用户是 nobody ,是因为未建立用户映射。修改 uid_map 和 gid_map 文件,注意写这两个文件的进程必须是该 user namespace 的父 namespace 或者子 namespace:
在父 user namespace 中写文件 uid_map,gid_map:
chunqiu@chunqiu:~$ echo ‘0 1002 1‘ > /proc/14641/uid_map
-su: echo: write error: Operation not permitted
chunqiu@chunqiu:~$ echo ‘0 1002 1‘ > /proc/14641/gid_map
-su: echo: write error: Operation not permitted
chunqiu@chunqiu:~$ ls -l /proc/14641/uid_map
-rw-r--r-- 1 chunqiu chunqiu 0 May 3 08:57 /proc/14641/uid_map
chunqiu@chunqiu:~$ ls -l /proc/14641/gid_map
-rw-r--r-- 1 chunqiu chunqiu 0 May 3 08:57 /proc/14641/gid_map
尝试写入 uid_map 和 gid_map 显示没有权限,但是这两个文件确实是属于用户 chunqiu。查看当前进程的 capability:
chunqiu@chunqiu:~$ cat /proc/$$/status | egrep ‘Cap(Inh|Prm|Eff)‘
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
查看 capability 知道当前进程没 CAP_SETUID 和 CAP_SETGID 权限,为其加上权限重新写:
chunqiu@chunqiu:~$ sudo setcap cap_setgid,cap_setuid+ep /bin/bash
chunqiu@chunqiu:~$ echo ‘0 1002 1‘ > /proc/14641/uid_map
chunqiu@chunqiu:~$ echo ‘0 1002 1‘ > /proc/14641/gid_map
在子 user namespace 中执行 bash 查看用户:
nobody@chunqiu:~$ exec bash
root@chunqiu:~# id
uid=0(root) gid=0(root) groups=0(root)
可以看到 nobody 改成了用户 root,实现了user namespace 内外用户的映射。
有一点需要注意的是当 user namespace 和其它 namespace 混合使用时,依旧需要 root 权限。解决方案是先以普通用户身份运行 user namespace,然后在 user namespace 中以 root 身份运行其它 namespace。内核会保证 user namespace 先执行。
docker 默认并没有使用 user namespace,它创建的容器和宿主机是同一 user namespace。意味着,docker 并未隔离宿主机和容器的用户。
在 docker 中指定用户身份有两种方式:
这里介绍第二种命令行参数指定用户身份:
# docker run -d --user 9999:9999 --name chunqiu 32c400c35bc2 sleep infinity
c588d1c1487a802aad016d5b82080f675bebc3111c33b103852408c56ff9b2e9
[root@chunqiu ~ (Master)]# docker ps | grep chunqiu
c588d1c1487a 32c400c35bc2 "sleep infinity" 11 seconds ago Up 8 seconds chunqiu
[root@chunqiu ~ (Master)]# ps -ef | grep sleep | grep -v grep
9999 3212381 3212189 2 13:53 ? 00:00:00 /usr/bin/sleep infinity
[root@chunqiu ~ (Master)]# readlink /proc/$$/ns/user
user:[4026531837]
命令行参数中使用 --user 指定用户 id 和用户组 id。在容器外查看进程所属的用户 id 是命令行参数指定的用户 9999,进入 container 中查看用户信息:
[root@chunqiu ~ (Master)]# docker exec -it chunqiu /bin/bash
bash-5.0$ ps -ef
UID PID PPID C STIME TTY TIME CMD
9999 1 0 0 05:53 ? 00:00:00 /usr/bin/sleep infinity
9999 7 0 10 05:54 pts/0 00:00:00 /bin/bash
9999 13 7 0 05:54 pts/0 00:00:00 ps -ef
bash-5.0$ id
uid=9999 gid=9999 groups=9999
bash-5.0$ readlink /proc/$$/ns/user
user:[4026531837]
进入容器中发现 user namespace 和宿主机上 user namespace 是一样的。同时,容器使用了 PID namespace,容器外的 3212381 进程在容器是容器内 PID 为 1 的 init 进程,并且进程的所属用户是命令行参数指定的用户。
容器和宿主机共用内核,内核使用的是 uid 和 gid,而不是用户名和组名, 因此这里不指定用户名也是可以工作的。内核会将用户 9999 当作普通用户对待,建立文件查看 9999 的访问权限:
[root@chunqiu ~ (Master)]# docker exec -it chunqiu /bin/bash
bash-5.0$ ls
commonfile chunqiufile
bash-5.0$ ls -l
total 0
-rw-rw----. 1 7779 7779 0 May 3 10:18 commonfile
-rw-rw-r--. 1 7779 7779 0 Apr 30 05:31 chunqiufile
bash-5.0$ cat chunqiufile
bash-5.0$ cat commonfile
cat: commonfile: Permission denied
bash-5.0$
将文件 commonfile 和 chunqiufile mount 到容器内,文件的所属用户和用户组改成了 7779,它是宿主机上的 chunqiu 普通用户,在这里以 id 的形式显示。发现用户只能读取 chunqiufile,因为它开放了读权限给不属于其用户组的其它用户。
在 kubernetes 中通过配置 security Context 来配置 Pod 或容器 container 的 uid 和 gid,kubernetes 默认也是不使用 user namespace 的。
如下创建 container 所属 uid 和 gid:
spec:
securityContext:
runAsUser: 9999
runAsGroup: 9999
...
详细信息看 这里
上节说了 docker 默认不开启 user namespace,实际上 docker 已经实现了相关功能,参看 这里 进行配置使用,本文就不赘述啦~
原文:https://www.cnblogs.com/xingzheanan/p/14729462.html