1. user namespace
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 先執行。
2. docker 容器中的 uid 和 gid
docker 默認並沒有使用 user namespace,它創建的容器和宿主機是同一 user namespace。意味着,docker 並未隔離宿主機和容器的用戶。
在 docker 中指定用戶身份有兩種方式:
- Dockerfile 中指定用戶身份
- 命令行參數指定用戶身份
這里介紹第二種命令行參數指定用戶身份:
# 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,因為它開放了讀權限給不屬於其用戶組的其它用戶。
2.1 kubernetes 指定 uid 和 gid 方式
在 kubernetes 中通過配置 security Context 來配置 Pod 或容器 container 的 uid 和 gid,kubernetes 默認也是不使用 user namespace 的。
如下創建 container 所屬 uid 和 gid:
spec:
securityContext:
runAsUser: 9999
runAsGroup: 9999
...
詳細信息看 這里
3. docker 和 user namespace
上節說了 docker 默認不開啟 user namespace,實際上 docker 已經實現了相關功能,參看 這里 進行配置使用,本文就不贅述啦~