如何隔离docker容器中的用户的方法

笔者在前文《理解 docker 容器中的 uid 和 gid》介绍了 docker 容器中的用户与宿主机上用户的关系,得出的结论是:docker 默认没有隔离宿主机用户和容器中的用户。如果你已经了解了 Linux 的 user namespace 技术(参考《Linux Namespace : User》),那么自然会问:docker 为什么不利用 Linux user namespace 实现用户的隔离呢?事实上,docker 已经实现了相关的功能,只是默认没有启用而已。笔者将在本文中介绍如何配置 docker 来隔离容器中的用户。
说明:本文的演示环境为 ubuntu 16.04。

了解 Linux user namespace

Linux user namespace 为正在运行的进程提供安全相关的隔离(其中包括 uid 和 gid),限制它们对系统资源的访问,而这些进程却感觉不到这些限制的存在。关于 Linux User Namespace 的介绍请参考笔者的《Linux Namespace : User》一文。

对于容器而言,阻止权限提升攻击(privilege-escalation attacks)的最好方法就是使用普通用户权限运行容器的应用程序。
然而有些应用必须在容器中以 root 用户来运行,这就是我们使用 user namespace 的最佳场景。我们通过 user namespace 技术,把宿主机中的一个普通用户(只有普通权限的用户)映射到容器中的 root 用户。在容器中,该用户在自己的 user namespace 中认为自己就是 root,也具有 root 的各种权限,但是对于宿主机上的资源,它只有很有限的访问权限(普通用户)。

User namespace 的用户映射

在配置 docker daemon 启用 user namespace 前,我需要先来了解一些关于从属(subordinate)用户/组和映射(remapping)的概念。从属用户和组的映射由两个配置文件来控制,分别是 /etc/subuid 和 /etc/subgid。看下它们的默认内容:在配置 docker daemon 启用 user namespace 前,我需要先来了解一些关于从属(subordinate)用户/组和映射(remapping)的概念:

对于 subuid,这一行记录的含义为:

用户 nick,在当前的 user namespace 中具有 65536 个从属用户,用户 ID 为 100000-165535,在一个子 user namespace 中,这些从属用户被映射成 ID 为 0-65535 的用户。subgid 的含义和 subuid 相同。

比如说用户 nick 在宿主机上只是一个具有普通权限的用户。我们可以把他的一个从属 ID(比如 100000 )分配给容器所属的 user namespace,并把 ID 100000 映射到该 user namespace 中的 uid 0。此时即便容器中的进程具有 root 权限,但也仅仅是在容器所在的 user namespace 中,一旦到了宿主机中,你顶多也就有 nick 用户的权限而已。

当开启 docker 对 user namespace 的支持时(docker 的 userns-remap 功能),我们可以指定不同的用户映射到容器中。比如我们专门创建一个用户 dockeruser,然后手动设置其 subuid 和 subgid:

nick:100000:65536
dockeruser:165536:65536

并把它指定给 docker daemon:

{
 "userns-remap": "dockeruser"
}

请注意 subuid 的设置信息,我们为 dockeruser 设置的从属 ID 和 nick 用户是不重叠的,实际上任何用户的从属 ID 设置都是不能重叠的。

或者一切从简,让 docker 为我们包办这些繁琐的事情,直接把 docker daemon 的 userns-rempa 参数指定为 "default":

{
 "userns-remap": "default"
}

这时,docker 会自动完成其它的配置。

配置 docker daemon 启用用户隔离

这里笔者采取简单的方式,让 docker 创建默认的用户用于 user namespace。我们需要先创建 /etc/docker/daemon.json 文件:

$ sudo touch /etc/docker/daemon.json

然后编辑其内容如下(如果该文件已经存在,仅添加下面的配置项即可),并重启 docker 服务:

{
 "userns-remap": "default"
}
$ sudo systemctl restart docker.service

下面我们来验证几个关于用户隔离的几个点。

首先验证 docker 创建了一个名为 dockremap 的用户:

然后查看 /etc/subuid 和 /etc/subgid 文件中是否添加了新用户 dockremap 相关的项:

接下来我们发现在 /var/lib/docker 目录下新建了一个目录: 165536.165536,查看该目录的权限:

165536 是由用户 dockremap 映射出来的一个 uid。查看 165536.165536 目录的内容:

与 /var/lib/docker 目录下的内容基本一致,说明启用用户隔离后文件相关的内容都会放在新建的 165536.165536 目录下。

通过上面的检查,我们可以确认 docker daemon 已经启用了用户隔离的功能。

宿主机中的 uid 与容器中 uid

在 docker daemon 启用了用户隔离的功能后,让我们看看宿主机中的 uid 与容器中 uid 的变化。

$ docker run -d --name sleepme ubuntu sleep infinity

uid 165536 是用户 dockremap 的一个从属 ID,在宿主机中并没有什么特殊权限。然而容器中的用户却是 root,这样的结果看上去很完美:

新创建的容器会创建 user namespace

在 docker daemon 启用用户隔离的功能前,新创建的容器进程和宿主机上的进程在相同的 user namespace 中。也就是说 docker 并没有为容器创建新的 user namespace:

上图中的容器进程 sleep 和宿主机上的进程在相同的 user namespace 中(没有开启用户隔离功能的场景)。

在 docker daemon 启用用户隔离的功能后,让我们查看容器中进程的 user namespace:

上图中的 4404 就是我们刚启动的容器中 sleep 进程的 PID。可以看出,docker 为容器创建了新的 user namespace。在这个 user namespace 中,容器中的用户 root 就是天神,拥有至高无上的权力!

访问数据卷中的文件

我们可以通过访问数据卷中的文件来证明容器中 root 用户究竟具有什么样的权限?创建四个文件,分别属于用户 root 、165536 和 nick。rootfile 只有 root 用户可以读写,用户 nick 具有 nickfile 的读写权限,uid 165536 具有文件 165536file 的读写权限,任何用户都可以读写 testfile 文件:

下面把这几个文件以数据卷的方式挂载到容器中,并检查从容器中访问它们的权限:

$ docker run -it --name test -w=/testv -v $(pwd)/testv:/testv ubuntu

容器中的 root 用户只能访问 165536file 和 testfile,说明这个用户在宿主机中只有非常有限的权限。

在容器中禁用 user namespace

一旦为 docker daemon 设置了 "userns-remap" 参数,所有的容器默认都会启用用户隔离的功能(默认创建一个新的 user namespace)。有些情况下我们可能需要回到没有开启用户隔离的场景,这时可以通过 --userns=host 参数为单个的容器禁用用户隔离功能。--userns=host 参数主要给下面三个命令使用:

docker container create
docker container run
docker container exec

比如执行下的命令:

$ docker run -d --userns=host --name sleepme ubuntu sleep infinity

查看进程信息:

进程的有效用户又成 root 了,并且也没有为进程创建新的 user namespace:

已知问题

User namespace 属于比较高级的功能,目前 docker 对它的支持还算不上完美,下面是已知的几个和现有功能不兼容的问题:

  • 共享主机的 PID 或 NET namespace(--pid=host or --network=host)
  • 外部的存储、数据卷驱动可能不兼容、不支持 user namespace
  • 使用 --privileged 而不指定 --userns=host

总结

Docker 是支持 user namespace 的,并且配置的方式也非常简便。在开启 user namespace 之后我们享受到了安全性的提升,但同时也会因为种种限制让其它的个别功能出现问题。这时我们需要作出选择,告别一刀切的决策,让合适的功能出现的合适的场景中。

参考:

Understanding how uid and gid work in Docker containers
Introduction to User Namespaces in Docker Engine
Isolate containers with a user namespace

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 详解Docker 端口映射与容器互联

    1.端口映射实现访问容器 1.从外部访问容器应用 在启动容器的时候,如果不指定对应的参数,在容器外部是无法通过网络来访问容器内部的网络应用和服务的. 当容器中运行一些网络应用,要让外部访问这些应用时,可以通过-p或-P参数来指定端口映射.当使用-P(大写P)标记时,Docker会随机映射一个端口到内部容器开放的网络端口(端口范围在Linux系统使用的端口之外,一般都过万): [root@docker ~]# docker run -d --name nginx_1 -P nginx:latest

  • Docker下Tomcat容器中使用Mysql的方法

    在这里我们使用Tomcat容器来运行war包,不过作为一个网站一个程序,如果需要用到数据库的,那么Tomcat容器中如何连接Mysql呢? 已经pull了tomcat和mysql镜像,可以使用docker的连接系统(–link)连接多个容器方式解决tomcat容器中使用mysql [root@izbp1b5k5bjps0dw8owk7tz ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE tomcat latest 2d084b1116

  • Centos7下安装与卸载docker应用容器引擎的方法

    Docker 是一个开源的应用容器引擎,基于Go 语言并遵从Apache2.0协议开源. Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级.可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化. 容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app),更重要的是容器性能开销极低. Docker的应用场景 Web 应用的自动化打包和发布. 自动化测试和持续集成.发布. 在服务型环境中部署和调整数据库或其他的后台应用. 从头编译或者扩展现

  • Docker容器之内网独立IP访问的方法

    实验介绍 今天在学习Docker容器的时候,在虚拟机中创建并开启了一个nginx的container,然后通过端口映射的方法,即将container的80端口映射到虚拟机的80端口,然后在宿主机上通过访问虚拟机的IP从而访问到Docker容器的Nginx服务. 然后开始思考,能否通过分配给container一个独立的IP,然后在宿主机上访问这个独立IP从而访问Nginx服务.通过各种探索,百度,最终实现,下面开始实验,这里用的是虚拟机的NAT模式. 实验图解 实验实施 安装docker服务 [r

  • Docker容器使用jenkins部署web项目(总结)

    (1)需要安装Docker容器,在Docker容器内安装jenkins,gogs,tomcat.   新建maven项目,添加findbugs plugin. 使用docker启动jenkins,gogs,Tomcat的命令gogs : 复制代码 代码如下: docker run -itd -p 10022:22 -p 10080:3000 --restart=always --privileged=true --name=gogs -v /var/gogs:/data gogs/gogs je

  • Docker创建运行多个mysql容器的方法示例

    1.使用mysql/mysql-server:latest镜像快速启动一个Mysql实例 docker run --name ilink_user_01 -e MYSQL_ROOT_PASSWORD=123456 0d 0p 3307:3306 mysql/mysql-server:latest ilink_user_01是容器名称,通过--name指令指定 123456为数据库root的密码,通过-e指定环境MYSQL_ROOT_PASSWORD为123456,-e (指定容器内的环境变量)

  • Docker容器自启动的实现方法

    容器自启动 Docker提供了restart policy机制,可以在容器退出或者Docker重启时控制容器能够自启动.这种Restart policy可以保证相关容器按照正确顺序启动.虽然也可以通过进程监控的方式(如systemd)来完成这种动作,但Docker还是建议尽量避免使用进程监控的方式来 "自启动" 容器. Docker的 Restart policy与dockerd命令的--live-restore启动标志还有区别:--live-restore标志可以在Docker升级的

  • 详解将本地docker容器迁移到服务端

    从docker容器生成镜像 docker commit -a "zhaoolee" -m "完成uwsgi的配置" qs zhaoolee-qs:1.0 参数说明: -a为作者署名, -m为本次提交的内容说明, qs为容器的名称, zhaoolee-qs:1.0表示新生成的镜像名为zhaoolee-qs,镜像zhaoolee-qs版本号为1.0 从容器生成镜像的时间可以会比较长, 需要耐心等待 将docker镜像导出为静态文件 docker save zhaoole

  • Docker容器中Mysql数据的导入/导出详解

    前言 Mysql数据的导入导出我们都知道一个mysqldump命令就能够解决,但如果是运行在docker环境下的mysql呢? 解决办法其实还是用mysqldump命令,但是我们需要进入docker的mysql容器内去执行它,并且通过配置volumes让导出的数据文件可以拷贝到宿主机的磁盘上 所以操作步骤就可以分为: 配置docker的volumes 进入docker的mysql容器,导出数据文件 至于数据导入,太过简单,就不说了 先来看看mysqldump命令常见选项: --all-datab

  • docker容器启动后添加端口映射

    概要 网上有许多人在查找关于容器启动后能否进行端口映射的问题.我曾经也问过度娘,很遗憾我没找到.本文就这个问题给出一个解决方法,旨在抛砖引玉.本文的思路是使用iptables的端口转发,这也是docker端口映射内部的实现机制,只不过我是显示地写出来罢了,为的就是让查找这个问题的人对docker的端口映射有一个直观的了解. 结论:容器启动后是可以添加端口映射的,但不建议手工添加,最好使用docker提供的功能. 步骤 创建两个容器并进行了端口映射,结果如图所示: 假如,我start一个容器,其内

  • Docker容器内不能联网的6种解决方案

    Docker容器内不能联网的6种解决方案 注:下面的方法是在容器内能ping通公网IP的解决方案,如果连公网IP都ping不通,那主机可能也上不了网(尝试ping 8.8.8.8) 1.使用–net:host选项 sudo docker run --net:host --name ubuntu_bash -i -t ubuntu:latest /bin/bash 2.使用–dns选项 sudo docker run --dns 8.8.8.8 --dns 8.8.4.4 --name ubunt

  • Docker教程:使用容器(简单示例)

    如果你是Docker的新手,看一下你应该学习的一些基础命令,以便开始管理容器. Docker对软件开发生命周期产生了巨大影响,使得大规模软件部署变得简单而且安全.这个教程将介绍运行.启动.停止和删除Docker容器的基础知识. Docker使得在不同的操作系统上使用不同的编程语言很容易,而所有这些都在同一主机上实现. 在Docker之后部署你的软件变得更加容易,你不必担心错过系统配置或先决条件. Docker与虚拟机 如果你正使用虚拟运行你的软件,为什么还需要Docker来取代呢? 它们之间的主

随机推荐