docker 原理之 user namespace(下)

莲花生 2021-05-04 14:52:12
docker 原理 namespace user



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 中指定用户身份有两种方式:

  1. Dockerfile 中指定用户身份
  2. 命令行参数指定用户身份

这里介绍第二种命令行参数指定用户身份:

# 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 已经实现了相关功能,参看 这里 进行配置使用,本文就不赘述啦~

版权声明
本文为[莲花生]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/xingzheanan/p/14729462.html

  1. Help, Java how to get all the current processes of the system
  2. Has anyone ever used JMeter or written tests in httpUnit????
  3. Living in a mountain village in late spring
  4. Partridge day, spring of HKUST
  5. JavaScript异步编程4——Promise错误处理
  6. 海康摄像SDK开发笔记(一):海康威视网络摄像头SDK介绍与模块功能
  7. JavaScript asynchronous programming 4 -- promise error handling
  8. Haikang video SDK development notes (1): introduction and module functions of Hikvision webcam SDK
  9. JOP:用于FPGA的嵌入式实时系统中的Java优化处理器内核
  10. Spring Boot源码:使用MongoDB MongoTemplate公开REST在几分钟内实现CRUD功能
  11. Spring Boot应用程序事件教程 - reflectoring
  12. 带有Resilience4j断路器的Spring云网关 - rome
  13. 经验分享:Apache Kafka的缺点与陷阱 - Emil Koutanov
  14. 通过Spring Boot Webflux实现Reactor Kafka
  15. 从Java 8升级到Java 11应该注意的问题
  16. Jop: Java optimized processor core for FPGA embedded real time system
  17. Spring boot source code: use mongodb mongotemplate to open rest to realize crud function in a few minutes
  18. Spring boot application event tutorial - reflecting
  19. Spring cloud gateway with resilience4j circuit breaker - ROM
  20. Experience sharing: shortcomings and pitfalls of Apache Kafka - Emil koutanov
  21. Realization of reactor Kafka through spring boot Webflux
  22. RPC框架设计----Socket与I/0模型
  23. Problems in upgrading from Java 8 to Java 11
  24. RPC framework design -- socket and I / 0 model
  25. RPC框架设计----I/0模型
  26. RPC framework design: I / 0 model
  27. RPC框架设计----NIO编程缓冲区Buffer
  28. RPC框架设计----NIO编程缓冲区Buffer
  29. RPC framework design -- NiO programming buffer
  30. RPC framework design -- NiO programming buffer
  31. Java多线程基础
  32. Java multithreading Foundation
  33. 码农飞升记-00-Java发展历程
  34. Development history of coder-00-java
  35. 码农飞升记-00-Java发展历程
  36. Development history of coder-00-java
  37. Spring and Autumn Moon
  38. Node.js与Spring Boot比较? - Ryan Gleason
  39. Spring WebFlux的明显陷阱 - ŁukaszKyć
  40. Spring创始人Rod大叔对YAML的真实想法
  41. Compare node.js with spring boot- Ryan Gleason
  42. Obvious pitfalls of spring Webflux- Ł ukaszKy ć
  43. Spring founder uncle rod's real thoughts on yaml
  44. 码农飞升记-02-OracleJDK是什么?OracleJDK的版本怎么选择?
  45. What is manong feisheng-02-oracle JDK? How to choose the version of Oracle JDK?
  46. Spring tide surging Xinanjiang
  47. Linux内核软中断
  48. Linux kernel soft interrupt
  49. Linux内核软中断
  50. Linux kernel soft interrupt
  51. Java multithreading Foundation
  52. The construction of Maven private library nexus
  53. I / O stream in Java
  54. JDK 16:Java 16的新功能 - InfoWorld
  55. 在Java中本地进行线程间数据传输的三种方式和源码展示
  56. jdon导致cpu 99%最后tomcat死掉---banq给予回复
  57. 用领域事件模拟AOP注入
  58. JDK 16: new function of Java 16 - InfoWorld
  59. Cartoon: from JVM lock to redis distributed lock
  60. Spring 3.1 终于加入了Cache支持