docker文件夹映射权限问题思考及可能解决方法
背景分析
在多用户服务器上公用docker搭建环境时,初步设想为宿主机用户为普通用户,容器内用户为root,
用户在容器内自由搭建自己所需的环境,而不影响其他人以及容器外的环境.
但当用户在使用docker挂载磁盘的时候,由于容器内使用root运行程序,会导致挂载中产生的文件属于root:root。而容器外用户并不是root,会使的文件共享甚至阅读日志产生权限上的问题,导致不可读或不可写之类的问题。
本文旨在宿主机使用普通用户,而容器内仍然使用root
用户,且不会产生文件权限的问题.
案例截图:
可以看到创建映射的 srs
和srsRAN
文件夹 因为是由docker run
命令自动创建的,权限被化为root
普通用户在这个文件夹下没有权限!,
需求
容器外使用普通用户(没有sudo权限等管理员权限)
容器内使用带管理员权限的root用户
文件挂载原理
简而言之,docker的文件挂载中,容器内外uid与gid相同。
最简单的挂载磁盘,例如:docker run -v $PWD:/data bash
,就是将当前目录挂载到容器内的/data
目录。文件的权限可以通过ls -l
查看。
容器内可能有使用root用户和非root用户运行程序的两种情况。
- 若容器内使用root运行程序,可以想象,产生的文件属于0:0,也就是root:root。容器外文件也会属于0:0,同样是root:root。这样产生的文件就需要容器外的root权限才可以操作。
- 若容器内使用非root运行程序,例如
demo:x:1000:1000::/home/blog:/bin/bash
,产生的文件属于1000:1000。容器外文件也会属于1000:1000,而容器外uid为1000的用户可能是test,这是若碰巧你就是test,你就可以顺利读取这个文件。但若你是uid为1001的test2,而且t是个test2讨厌鬼,你就告别这个文件了。甚至容器外根本没有uid为1000的用户,那这个文件就不属于任何人。
docker用户组
docker用户组的出现,目的在于当普通用户既无sudo
权限,有想运行docker
命令时出现.
一般来说,在linux
环境下安装完成docker
,系统会自动创建id=999
的docker组
. 此时只需要将普通用户加入到docker组
中即可.
具体 docker组的id, 请自行查看
方式有两种(需要在拥有sudo权限的账号下修改):
-
使用
usermod
命令$ sudo usermod -aG docker <用户名> # e.g sudo usermod -aG docker test1
-
直接修改
/etc/group
中docker
所在组$ sudo vim /etc/group # 在docker所在行,按格式在后面添加用户名即可
修改完成后,重启ssh或重新登即可, 使用
id <用户名>
检查是否在docker所在组
三种可能解决方法
1. 限定开启用户
即运行 docker run -u <xxx>:<xxx>
但这个方法无法解决上述的需求
2.使用子用户
若容器内外文件的uid:gid可以不同,这个问题就可以很容易解决。的确有这么一个方案,让容器内所有的uid:gid以一定的规则映射,我推荐使用这一方案。文章的方案演示节中我会演示这一方案。
在官方文档中,这个方案本身是为了解决安全问题,见这里。发生容器挂载磁盘root权限提权也不是一次两次了,这算是一个官方的解决方案。
https://docs.docker.com/engine/security/userns-remap/
但这个方案会有一些限制:
- 不可使用
--pid=host
或--network=host
- 不可使用无法识别或使用用户映射的挂载磁盘
- 若使用
docker run --privileged
则必须同时使用--userns=host
都是不大的限制,使用的时候留个心眼即可。
解释:
因为docker
权限映射的原理是, 容器内外uid与gid相同,
那么我是否可以 通过 userns-remap
命令实现, 将容器内的root 映射到容器外宿主机上一个具有管理员(sudo)的用户(称为siz
)上呢,而其他普通用户(test<xxx>
)只需要在这个siz
所在的组即可. 而这个组的小组管理员即为siz
.
但此时一个新的问题出现了: 当普通用户创建文件夹映射时, 此文件夹的所属者为siz
,但小组权限仅为xr
即所在组的成员没有修改的权限,只有读和运行的权限, 并且此时普通用户(test<xxx
> 并没有sudo
权限,也就无法通过chmod g+w
等命令来实现提权. 此时该如何解决呢?
答: 通过进入容器内提权实现,
因为容器内的用户为root
,即对应的权限为小组管理员siz
, 而小组管理员拥有(sudo
) 权限, 又因为文件映射是容器内外相同的, 在容器内修改了文件权限, 那么在容器外普通用户就有了读写执行
的权限.
操作实现
-
修改
/etc/subuid
与/etc/subgid
查看小组的管理员id
$ id siz uid=1000(siz) gid=1000(siz) groups=1000(siz) # 得到 siz的id 为 1000
使用带
sudo
的权限账号修改/etc/subuid
与/etc/subgid
文件格式为: <name>:start:number
对应方式: 如果组管理员
id
为1000
, 则对应容器内的root
为1
, 即<name>:1000:1
如果组管理员
id
为1003
,则对应容器内的root
为1+(1003-1000)=4
即<name>:1003:4
根据组管理员
id
对应设置好映射例如本文组管理员
siz
的id
为1000 , 则修改为如下样式, 并保存修改# sudo vim /etc/subuid siz:1000:1 siz:100000:65536 test1:165536:65536 test2:231072:65536 demo:296608:65536 # sudo vim /etc/subgid siz:1000:1 siz:100000:65536 test1:165536:65536 test2:231072:65536 demo:296608:65536
-
修改
docker
守护程序/etc/docker/daemon.json
注意修改
/etc/docker/daemon.json
文件后, 会导致之前的 镜像与容器不被识别, 所以请做好备份.容器->打包成镜像-> 推送到hub
Enabling
userns-remap
effectively masks existing image and container layers, as well as other Docker objects within/var/lib/docker/
. This is because Docker needs to adjust the ownership of these resources and actually stores them in a subdirectory within/var/lib/docker/
. It is best to enable this feature on a new Docker installation rather than an existing one.# 没有这个文件的化, 请创建 # sudo vim /etc/docker/daemon.json { "userns-remap":"<组管理员名称>" } # sudo service docker restart # 查看docker 状态 sudo systemctl status docker.service
这里 `“userns-remap”:"<组管理员名称>"的格式 参考官方文档
https://docs.docker.com/engine/security/userns-remap/
Edit
/etc/docker/daemon.json
. Assuming the file was previously empty, the following entry enablesuserns-remap
using user and group calledtestuser
. You can address the user and group by ID or name. You only need to specify the group name or ID if it is different from the user name or ID. If you provide both the user and group name or ID, separate them by a colon (:
) character. The following formats all work for the value, assuming the UID and GID oftestuser
are1001
:testuser
testuser:testuser
1001
1001:1001
testuser:1001
1001:testuser
-
修改普通用户的组
即组内普通用户创建的文件在容器中因为没有相应的`uid:gid` 映射, 并且容器中的`root`用户的权限很低时(组管理员拥有sudo权限,但需要输入sudo才可以; 而容器中时没有sudo命令的,所有说这里的`root`权限很低)在这种情况下: 就会出现普通用户创建的文件,在容器中被识别为: `nobody ,nogroups 现象当在进行测试的时候发现一个新的问题:
暂时的解决方法
普通用户删除私有组,即让普通用户的主组为(docker容器映射的管理员所在的组)
例如: 本文演示小组管理员为
siz
, 组名为(siz
) 则让 普通用户test<xxx>
的主组为siz
# 命令: sudo usermod -g <小组管理员名称> <普通用户名称> # 将普通用户的主组从私有组改为小组管理员所在的组 $ sudo usermod -g test1 siz
- 对宿主机(容器外)组成员中普通用户创建的文件或文件夹提升组权限
目的: 使容器内
root
获得对这个文件夹拥有修改(w)
权限$ chmod g+wxr <xxx>
- 对容器内
root
创建的文件或文件夹提升组权限
目的: 使宿主机(容器外) 组成员普通用户获得对这个文件夹拥有修改
(w)
权限$ chmod g+wxr <xxx>
缺陷:
当内外创建新的文件和文件夹使, 内外要不断的使用
chmod
命令进行授权,
演示:
组成员
-
组管理员(
siz
) 进行映射测试: -
组成员
test1
进行映射测试
-
使用
entrypoint.sh
定制脚本这个过程是在编写
Dockerfile
文件时使用的,目的是让容器内外对应的用户相等来解决文件夹映射目录权限的问题.
主要的思路是,从主机中读取当前用户的
id
, 然后再创建容器时相应的在容器内部新建一个对应的用户,这样保证了容器内外的
uid:gid
相等.但在这个里不符合我们的需求, 故不讨论.
总结
为了解决让宿主机使用普通用户来使用docker,而在容器中相对给与较高的root
权限用户需求.
本文采用了修改docker守护程序的方法,其官方文件本意是docker
为了解决容器内root
升权的问题
所谓升权, 即不做任何修改情况下, 容器内
root
与宿主机root
相对应, 虽然这个容器内的权限是有一定限制的root
权限, 但可以通过映射宿主机根目录,将所有内容映射到容器中, 实现在容器中获得所有文件的权限,来实现恶意攻击.
通过映射用户, 容器中的root
权限与宿主机被映射的用户权限相等,
可以思考这样一个问题: 如果我映射的宿主机用户是一个普通用户(没有sudo
)权限,那么容器里的root
有没有sudo
权限呢?
答案
root
仅仅是个名称, 它可以叫做superhero
也可以叫做 tom
, 只是个名字,并无所谓,真正相关的是uid:gid