容器以及编排工具(例如Kubernetes)开创了应用开发的新时代,让微服务架构以及CI/CD的实现成为了可能。Docker是迄今为止最主要的容器运行时引擎。然而,使用Docker容器构建应用也引入了新的安全挑战和风险。本文主要介绍了Docker容器面临的8大安全风险和挑战,以及在构建和部署阶段确保环境安全、在运行时阶段确保Docker容器安全的26个最佳实践。
Docker必须解决的8大安全挑战
过去,公司一般是在虚拟机(VM)或物理服务器上部署应用。而容器化的采用,带来了几个新的必须解决的新挑战。
1. 容器的采用让微服务成为了可能,这增加了数据通信以及网络和访问控制的复杂性。
2. 容器的构建依赖于基础镜像,而了解镜像来源是否安全并不是个简单的事情。如果镜像存在漏洞,所有使用这个镜像的容器都会出现问题。
3. 容器的寿命很短,所以,监控容器(特别是在运行时阶段)是非常困难的。由此引起的另一个安全风险是对不断变化的容器环境缺乏可见性。
4. 一个容器被入侵可能会导致其他容器也被入侵。
5. 容器化环境比传统的虚拟机所包含的组件更多,包括Kubernetes编排工具(编排工具自身就存在一些安全挑战)。你清楚哪些部署或集群受到高危漏洞的影响吗?如果一个特定的漏洞被利用,影响范围是多大?容器是在生产环境还是在开发/测试环境中运行?
6. 容器配置也会带来一些安全风险。镜像是否包含了不必要的服务,增加了攻击面?镜像中是否存储了密钥信息?
7. 合规是安全的一个重要驱动因素,但由于容器环境变化非常快,因此,在合规方面也会存在很大挑战。许多证明安全合规的传统工具,如防火墙规则,在Docker环境并不一定有效。
8. 现有的工作负载安全解决方案无法有效应对容器的安全挑战和风险。
Docker安全的26个最佳实践
下面是我们根据行业标准和客户要求,总结出的对于安全配置Docker容器和镜像的一些最佳实践。
1. 使用最新的Docker版本。例如,在容器中发现runC漏洞之后,Docker 18.09.2很快对此作出了修补。
2. 确保Docker组中只有受信任的用户,从而确保只有受信任的用户可以控制Docker守护程序。
3. 针对下列文件制定并落实相应的审计追踪规则:
a. Docker守护程序
b. Docker文件和目录
i. /var/lib/docker
ii. /etc/docker
iii. Docker.service
iv. Docker.socket
v. /etc/default/docker
vi. /etc/docker/daemon.json
vii. /etc/sysconfig/docker
viii. /usr/bin/containerd
ix. /usr/sbin/runc
4. 确保所有Docker文件和目录的安全,确保这些文件和目录归具有相应权限的用户(通常是root用户)所有。
5. 使用具有有效证书的镜像仓库或使用TLS的镜像仓库,以尽量减少流量拦截造成的风险。
6. 如果没有在镜像中明确定义容器用户,用户在使用容器时应该启用用户命名空间,这样可以重新将容器用户映射到主机用户。
7. 禁止容器获得新的权限。默认情况下,容器可以获得新的权限,所以这个配置必须另行设置。另一个做法是删除镜像中的setuid和setgid权限,以尽量减少权限升级攻击。
8. 以非root用户(UID不是0)身份运行容器。默认情况下,容器是以容器内的根用户权限运行的。
9. 在构建容器时,只使用受信任的基础镜像。这个建议可能大家都很清楚,但第三方镜像仓库往往没有对存储在其中的镜像实施任何治理策略。知道哪些镜像可以在Docker主机上使用,了解它们的出处,并审查其中的内容,这一点很重要。用户还应该启用Docker的内容信任来验证镜像,并且只将经过验证的软件包安装在镜像中。
10. 使用最小的基础镜像,删除不必要的软件包,缩小攻击面。减少容器中的组件可以减少攻击载体的数量。BusyBox和Apline是构建最小基础镜像的两个不错选择。
11. 实施有效的治理策略,确保对镜像进行频繁扫描。过期的镜像或近期没有被扫描的镜像在进入构建阶段之前,应该被拒绝使用或重新进行扫描。
12. 建立一个工作流程,定期识别并从主机上删除过期或未使用的镜像和容器。
13. 不要在镜像/Docker文件中存储密钥。默认情况下,可以将密钥存储在Dockerfile文件中,但如果在镜像中存储密钥,任何可以访问镜像的用户都可以访问密钥。如果需要使用密钥信息时,建议采用密钥管理工具。
14. 运行容器时,要移除所有不需要的功能。可以使用Docker的CAP DROP功能来删除容器的某个特定功能(也称为Linux功能),也可以使用CAP ADD来只添加容器正常运行所需的功能。
15. 不要给容器添加”–privileged”容器标签,因为特权容器拥有底层主机的大部分功能,明确标记特权容器会被攻击者恶意利用。而且,这个标签也会覆盖掉用CAP DROP或CAP ADD设置的任何规则。
16. 不要在容器上挂载敏感的主机系统目录,特别是在可写模式下,这可能会导致主机系统目录被恶意修改,从而导致主机失陷。
17. 不要在容器内运行sshd。默认情况下,ssh守护程序不会在容器中运行,不要为了简化SSH服务器的安全管理就安装ssh守护程序。
18. 不要在容器内映射任何低于1024的端口,因为这些端口是有特权的端口,可以传输敏感数据。默认情况下,Docker会将容器端口映射到49153-65525范围内的端口。
19. 除非必要,不要共享主机的网络命名空间、进程命名空间、IPC命名空间、用户命名空间或UTS命名空间,以确保对Docker容器和底层主机之间进行适当隔离。
20. 指定容器运行所需的内存和CPU大小。默认情况下,Docker容器是共享资源,没有限制。
21. 将容器的根文件系统设置为只读。容器开始运行后,就不得对根文件系统进行修改。任何对根文件系统的变更行为都可能是出于恶意的目的。为了保护容器的不可改变性(不要对新容器打补丁,而是拉取镜像重建一个容器),根文件系统不能设置为可写格式。
22. 对PID进行限制。容器的一个优势是严格控制进程标识符(PID)。内核中的每个进程都有一个唯一的PID,容器根据Linux的PID命名空间,为每个容器提供一个独立的PID层次结构。对PID进行限制,可以有效地限制每个容器中运行的进程数量。限制容器中的进程数量可以防止过度地产生新的进程和潜在的恶意横向移动。
23. 不要将挂载传播规则配置为共享。共享挂载传播意味着,对挂载作出任何变更都会传播到该挂载的所有实例中。相反,应该将挂载传播设置为从属模式或私有模式,这样对存储卷的必要修改就不会传播到不需要进行此类修改的容器中。
24. 在使用docker exec命令时,不要使用特权容器或user=root选项,因为这种设置可能会让容器拥有扩展的Linux Capbilities。
25. 不要使用默认的”docker0″网桥。使用默认的网桥容易受到ARP欺骗和MAC洪泛攻击。容器应该使用用户自定义的网络,而不是默认的 “docker0″网桥。
26. 不要将Docker Socket挂载在容器内,因为这可能会让容器内的进程有权执行命令,完全控制主机。
写在最后
Docker作为当今最流行的容器运行时引擎,其安全性不容忽视。本文中介绍的26个关于Docker安全的最佳实践,是确保Docker环境安全和关键业务应用安全的关键所在。