1 安全性保障
评估 Docker 的安全性时,主要考虑三个方面:
- 由内核的命名空间和控制组机制提供的容器内在安全
- Docker 程序(特别是服务端)本身的抗攻击性
- 内核安全性的加强机制对容器安全性的影响
1.1 命名空间和控制组
当容器启动时,后台 Docker 为容器创建了一个独立的命名空间和控制组集合
- 命名空间提供了最基础也是最直接的隔离。在容器中运行的进程不会被运行在主机上的进程和其它容器发现和作用;每个容器都有自己独有的网络栈,所有的容器通过本地主机的网桥接口相互通信
- 控制组负责实现资源的审计和限制。确保各个容器可以公平地分享主机的内存、CPU、磁盘 IO 等资源;确保了当容器内的资源使用产生压力时不会连累主机系统;还可以防止拒绝服务(DDOS)攻击
1.2 服务端防护
Docker 服务的运行目前需要 root 权限,因此其安全性十分关键
- 确保只有可信的用户才可以访问 Docker 服务(主机和容器间可以共享文件夹)
- 注意进行参数的安全检查,防止恶意的用户用特定参数来创建一些破坏性的容器
- 使用 Unix 权限检查来加强套接字的访问安全(localhost的TCP套接字容易遭受跨站脚本攻击)
- 使用安全机制,确保只有可信的网络或 VPN,或证书保护机制下的访问可以进行
- 建议采用专用的服务器来运行 Docker 和相关的管理服务(例如管理服务比如 ssh 监控和进程监控、管理工具 nrpe、collectd 等),其它的业务服务都放到容器中去运行
Docker 最近改进的 Linux 命名空间机制将可以实现使用非 root 用户来运行全功能的容器。这将从根本上解决了容器和主机之间共享文件系统而引起的安全问题
1.3 内核能力机制
能力机制(Capability) 是 Linux 内核一个强大的特性,可以提供细粒度的权限访问控制
默认情况下,Docker 启动的容器被严格限制只允许使用内核的一部分能力,几乎所有的特权进程都由容器以外的支持系统来进行管理(比如 ssh 访问被主机上ssh服务来管理):
为了加强安全,容器可以禁用一些没必要的权限。
- 完全禁止任何 mount 操作;
- 禁止直接访问本地主机的套接字;
- 禁止访问一些文件系统的操作,比如创建新的设备、修改文件属性等;
- 禁止模块加载。
大部分情况下,容器并不需要“真正的” root 权限。默认情况下,Docker采用 白名单 机制,禁用必需功能之外的其它权限。 当然,用户也可以根据自身需求来为 Docker 容器启用额外的权限。
1.4 其他安全特性
Docker 当前默认只启用了能力机制。用户可以采用多种方案来加强 Docker 主机的安全,例如:
- 在内核中启用 GRSEC 和 PAX,这将增加很多编译和运行时的安全检查;通过地址随机化避免恶意探测等。并且,启用该特性不需要 Docker 进行任何配置。
- 使用一些有增强安全特性的容器模板,比如带 AppArmor 的模板和 Redhat 带 SELinux 策略的模板。这些模板提供了额外的安全特性。
- 用户可以自定义访问控制机制来定制安全策略。
总体来看,Docker 容器还是十分安全的。用户还可以使用现有工具,比如 Apparmor, Seccomp, SELinux, GRSEC 来增强安全性;甚至自己在内核中实现更复杂的安全机制
2 底层实现
Docker 的基本架构:
- 采用了
C/S
架构,包括客户端和服务端。Docker 守护进程 (Daemon
)作为服务端接受来自客户端的请求,并处理这些请求(创建、运行、分发容器) - 客户端和服务端既可以运行在一个机器上,也可通过
socket
或者RESTful API
来进行通信 - Docker 守护进程一般在宿主主机后台运行,等待接收来自客户端的消息
- Docker 客户端则为用户提供一系列可执行命令,用户用这些命令实现跟 Docker 守护进程交互
Docker 底层的核心技术包括 Linux 上的命名空间(Namespaces)、控制组(Control groups)、Union 文件系统(Union file systems)和容器格式(Container format)
- 命名空间是 Linux 内核一个强大的特性。每个容器都有自己单独的命名空间,运行在其中的应用都像是在独立的操作系统中运行一样。命名空间保证了容器之间彼此互不影响
- 控制组(cgroups)是 Linux 内核的一个特性,可以提供对容器的内存、CPU、磁盘 IO 等资源的限制和审计管理;避免当多个容器同时运行时的对系统资源的竞争
- 联合文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下
- Docker 最初采用了
LXC
中的容器格式。从 0.7 版本以后开始转而使用自行开发的 libcontainer,从 1.11 开始,则进一步演进为使用 runC 和 containerd - Docker 的网络实现其实就是利用了 Linux 上的网络命名空间和虚拟网络设备(特别是
veth pair
)
关于命名空间的更多内容可阅读:《Introducing Linux Network Namespaces》
veth pair
:在本地主机和容器内分别创建一个虚拟接口,并让它们彼此连通虚拟接口(转发效率较高):Linux 通过在内核中进行数据复制来实现虚拟接口之间的数据转发,发送接口的发送缓存中的数据包被直接复制到接收接口的接收缓存中