但是,如果大家使用的是非官方镜像、提供文件或者运行应用程序,情况就完全不同了。在这类场景中,我们必须对 Docker 安全性建立起充分的了解。
我们的主要安全目标,在于防止恶意用户获取有价值信息或者造成严重破坏。为此,我们将立足多个关键领域分享 Docker 安全最佳实践。通过本文,大家将掌握重要的 Docker 安全心得!
在第一节中,我们主要关注以下三个方面:
Access management(访问管理)
Image safety(镜像安全)
Management of secrets(保密信息管理)
首字母相连,就是 AIM。
首先,我们来聊聊容器访问限制问题。
在启动一套容器后,Docker 会创建一个命名空间组。命名空间的作用是防止容器中的进程被主机上的其他用户 / 容器看到或者影响。换言之,命名空间正是 Docker 实现不同容器间隔离的主要方式。
Docker 还提供私有容器网络,用于防止同一主机上其它容器的网络接口错误获得访问权限。
由此,Docker 容器建立起了自己的隔离体系——但是,您的用例仍然算不得高枕无忧。
良好的安全性,意味着必须遵循最低权限原则。您的容器应该具备完成任务的必要功能,但除此之外再无其它功能。不过麻烦在于,一旦对能够在容器中运行的进程加以限制,容器很可能无法完成正常任务所需要的各项操作。
目前,我们可以通过多种方式调整容器权限。首先,避免以 root 身份运行(或者在必须使用 root 身份时,避免 re-map)。第二,利用—cap-drop 以及—cap-add 进行功能调整。
避免 root 并调整功能,已经基本可以满足大家对于权限限制的需求。但也有一部分高级用户可能希望调整默认的 AppArmor 以及 seccomp 配置文件。受本文篇幅所限,这里就不深入讨论这种情况啦。
Docker 的默认设置认为用户在镜像中以 root 身份进行操作。但大多数人并没有意识到这种情况的危险性。这意味着攻击者能够轻松访问敏感信息甚至是系统内核。
作为一项通行的最佳实践,请千万不要以 root 身份运行容器。
“防止容器内权限提升攻击的最佳方法,就是将容器应用配置为以非高权限用户身份运行。”——Docker 说明文档
大家可以在 build 时通过以下命令指定一个非 root 用户 ID:
docker run -u 1000 my_image
其中的—user 或者 -u 标记,用于指定用户名或者用户 ID。只要用户 ID 还不存在,这条命令就能顺利起效。
继续看这段命令,其中的 1000 是任意取值的非高权限用户标识。在 Linux 中,用户标识的一般取值在 0 到 499 之间。这里之所以选择超过 500 的标识,是为了避免其被选定为默认系统用户。
相较于通过命令行设置用户,更好的办法是通过 root 对镜像中的用户进行变更。如此一来,大家就不必在 build 时重复设置。在执行完要求使用需要 root 权限功能的 Dockerfile 指令后,我们接下来只需要在镜像里烧录 USER Dockerfile 指令即可。
换言之,首先安装必要的软件包,而后切换用户。例如:
FROM alpine:latest
RUN apk update && apk add --no-cache git
USER 1000
…
如果大家必须以 root 用户身份在容器内运行某些进程,则可以在 Docker 主机上将 root 身份 re-map 至某个权限相对较低的用户。具体请参阅 Docker说明文档。
您可以通过变更功能的方式为用户授权必要的权限。
功能,是指一组获准进程的集合。
我们可以通过命令行利用—cap-drop 以及—cap-add 进行功能调整。最佳策略是利用—cap-drop all 清空容器的所有权限,再利用—cap-add 逐一添加必要功能。
大家可以在运行时中调整容器的功能。例如,要删除使用 kill 命令停止容器运行的功能,大家可以通过以下命令移除该默认功能:
docker run --cap-drop=Kill my_image
请避免为进程提供 SYS_ADMIN 及 SETUID 权限,因为这两种权限会带来巨大的影响。有这两种功能在,用户几乎相当于拥有了 root 权限(移除这些功能的意义与避免使用 root 权限相同)。
另外,请禁止容器使用 1 到 1023 之间的端口,因为大多数网络服务都运行在这一范围之内。未授权用户可以监听登录等内容,并运行未经授权的服务器应用程序。此外,编号较低的端口在运行中还需要获取 root 身份,或者被明确赋予 CAP_NET_BIND_SERVICE 功能。
要查找容器中是否存在高权限端口访问,大家可以使用 inspect。只需要运行 docker container inspect my_container_name 命令,我们就能看到关于已分配资源与容器安全配置文件的大量细节信息。
与 Docker 中的大多数设置一样,我们最好是以自动以及自我记录的方式通过文件进行容器配置。利用 Docker Compose,大家可以在服务配置当中指定某项功能,例如:
cap_drop: ALL
或者,大家也可以在 Kubernetes 文件中进行调整。
另外,我们也可以限制容器对系统资源(例如内存以及 CPU)的访问。如果没有资源限制,容器会很快耗尽一切可用的内存。万一发生这种情况,Linux 主机内核将提示内存意外耗尽,并终止内核进程。这可能导致系统整体崩溃。大家可以想象,不少攻击者都利用这种方式尝试关闭目标应用程序。
如果我们在同一台计算机上运行多套容器,则应精心限制各个容器所能使用的内存与 CPU 资源。如果容器内存不足,其将关闭;容器关闭可能导致应用程序崩溃,并直接影响到用户的体验。因此,我们有必要采取隔离机制,防止主机内存不足引发的容器环境崩溃。
Docker Desktop CE for Mac v2.1.0 提供默认资源限制功能。您可以在 Docker 图标 ->首选项下访问该功能。接下来,您可以点击“资源”选项卡,并通过滑块调整资源限制水平。
或者,大家也可以通过指定—memory 标识或者 -m 通过命令行进行资源限制,命令后面跟上数字加度量单位即可。
4m 代表着 4 MiB,这是容器所能设置的最低分配内存量。MiB 要比 MB 略大一点。目前说明文档的内容有误,希望维护人员在读到本文时已经接受我的修复补丁。
To see what resources your containers are using, enter the command docker stats in a new terminal window. You'll see running container statistics regularly refreshed.
在引擎盖下面,Docker 正使用 Linux 控制组(cgroups)实现资源限制。这项技术已经得到了无数实践场景的验证,相当可靠。感兴趣的朋友可以 点击此处 了解 Docker 中的资源限制机制。
从 Docker Hub 当中提取镜像,就像是邀请外人到自家作客。虽然不太安全,但有时候却非常必要。
镜像安全的第一原则,就是只使用您信任的镜像。那么,我们如何判断哪些镜像值得信赖?
官方镜像无疑是个理想的安全保障选项。这类镜像包括 alpine、ubuntu、python、golang、redis、busybox 以及 node。这些镜像都已经拥有上千万的下载量,而且无数双眼睛在密切关注它们的动向。
Docker 解释称:
Docker 赞助了支专门的团队,负责审查并发布官方镜像中的所有内容。该团队与上游软件维护人员、安全专家以及更广泛的 Docker 社区开展合作,以确保这些镜像安全可靠。
类似于使用官方镜像,大家也可以选择使用最小基础镜像。
由于代码量更少,因此存在安全漏洞的可能性就更低。较小、复杂度较低的基础镜像,其透明度也更高。
了解 Alpine 镜像里有些什么,要比了解您朋友的镜像里有什么更简单,而这又比弄清朋友的朋友使用的镜像里有什么更简单。总之,关系越单纯,解决起来越容易。
同样的,只安装您真正需要的软件包。这样能够减少攻击面,并加快镜像的下载与构建速度。
您也可以使用 Docker 内容信任功能对镜像进行签名。
Docker 内容信任功能强制要求用户使用包含签名的镜像,无签名镜像将无法使用。可信的来源包括由 Docker Hub 提供的官方 Docker 镜像,以及来自用户的可信来源签名图像。
Docker 在默认情况下会禁用内容信任功能。要启用这项功能,请将 DOCKER_CONTENT_TRUST 环境变量设置为 1。在命令行中运行以下命令:
export DOCKER_CONTENT_TRUST=1
现在,我尝试从 Docker Hub 下载自己未签名的镜像时,系统即会阻止这项操作。
Error: remote trust data does not exist for docker.io/discdiver/frames: notary.docker.io does not have trust data for docker.io/discdiver/frames
这是一种保护自己的有效方式。感兴趣的朋友可以 点击此处 了解内容信任功能。
Docker 通过对内容进行加密校验的方式存储及访问镜像。这项超酷的内置安全功能,可以防止攻击者创建冲突镜像。
您的访问受到限制,您的镜像也安全可靠,接下来就是照顾好保密信息了。
管理敏感信息的头号原则:不要将其纳入镜像。恶意人士能够比较轻松地从代码库、日志以及其它位置找到这些未经加密的敏感信息。
原则二:不要在敏感信息中使用环境变量。任何可以通过 docker inspect 或者 exec 进入容器的家伙,都将能够窥探你的秘密。任何人也都可以通过 root 身份运行这些命令。虽然之前提到了应该严格控制 root 身份的分发,但我们得留点冗余空间,这也是实现良好安全性的必要一步。一般来讲,日志会转储环境变量值,大家肯定不希望自己的敏感信息被到处传播。
Docke 存储卷在这方面表现出色。Docker 说明文档也推荐利用它访问敏感信息。存储卷能够去除 docker inspect 与日志记录带来的风险。但是,root 用户仍然能够窥探到你的秘密。总体来说,存储卷是个相当不错的解决方案。
但比存储卷更好的,是使用 Docker secrets。这里的保密信息都将接受加密。
某些 Docker 说明文档中提到,我们只能在 Docker Swarm 中使用 secrets 功能。但实际情况并非如此,没有 Swarm,我们也能享受 Docker 中的 secrets 功能。
如果大家只需要把保密信息存放在镜像里,则可以使用 BUildKit。BuildKit 是一款远超现有 Docker 镜像构建工具的出色后端。它能够显著缩短构建时长,并提供其它一系列出色的功能——包括 build-time secrets 支持。
BuildKit 是一款相对较新的工具——直到 Docker Engine 18.09 版本才第一次出现。我们可以通过三种方式将 BuildKit 指定为后端。未来,BuildKit 将成为 Docker 的默认后端选项。
利用 export DOCKER_BUILDKIT=1 命令将其设置为环境变量。
在使用 build 或者 run 命令时加上 DOCKER_BUILDKIT=1。
默认启用 BuildKit。将 /etc/docker/daemon.json 中的配置设置为 true:{ "features": { "buildkit": true } }。而后重启 Docker。
接下来,您就可以在构建时通过—secret 标识使用 secrets 功能了:
docker build --secret my_key=my_value ,src=path/to/my_secret_file .
您的文件将把保密信息指定为键值对的形式。
这些保密信息并不会被保存在最终镜像中,同时也不会出现在图像构建缓存当中。安全第一嘛!
如果大家需要将保密信息引入运行中的容器(而不只是镜像构建过程),则可使用 Docker Compose 或者 Kubernetes。
利用 Docker Compose,我们可以将保密信息的键值对添加至某项服务中,并指定对应的 secret 文件。以下示例来自 Stack Exchange 上关于 Docker Compose secrets提示 的相关素材。例如将保密信息放置在 docker-compose.yml 当中:
version: "3.7"
services:
my_service:
image: centos:7
entrypoint: "cat /run/secrets/my_secret"
secrets:
- my_secret
secrets:
my_secret:
file: ./my_secret_file.txt
之后即可利用 docker-compose up --build my_service 正常启动 Compose。
如果大家使用 Kubernetes,它也支持 secrets 管理功能。Helm-Secrets 能够帮助我们更轻松地在 K8s 当中实现保密信息管理。此外,K8s 还提供基于角色的访问控制(RBAC)功能,这一点与 Docker Enterprise 相同。RBAC 使得 Secrets 访问流程更具可管理性,同时也为团队提供了更强的安全保障。
保密信息的另一项最佳管理实践是利用 Vault 等 secret 管理服务。Vault 是一项由 HashiCorp 推出的服务,专门用于管理保密信息。其还能为保密信息设置时间限定。感兴趣的朋友可以 点击此处 了解更多关于 Vault Docker 镜像的细节信息。
AWS Secrets Manager 以及其他云服务供应商提供的类似产品,也能帮助大家在云端保护自己的保密信息。
请记住,管理保密信息的要诀,在于保持其保密性质。绝对不要将其烧录至镜像当中,或者转换为环境变量。
与其它代码一样,我们应当保证镜像中的语言与库的及时更新,从而享受最新安全修复补丁带来的保障。
如果大家在镜像当中引用基础镜像版本,请确保该基础版本始终保持更新。
同样的,大家还应及时更新自己的 Docker 版本,以便修复并增强功能,进而实现新的安全功能。
最后,保证您的主机服务器软件及时更新。如果在托管服务上运行容器系统,服务商应该会替您完成更新工作。
更高的安全性,源自更及时的更新维护。
如果您的组织当中有很多员工和一大堆 Docker 容器,那么 Docker Enterprise 可能更适合您。管理员可以借此为所有用户设置策略限制。Docker Enterprise 提供的 RBAC、监控与日志记录功能也将使您的团队更轻松地实现安全管理。
在 Enterprise 的帮助下,您还可以在 Docker Trusted Registry 中行托管您的镜像。Docker 提供内置安全扫描,可确保您的镜像中不存在已知漏洞。
Kubernetes 虽然也免费提供一系列类似的功能,但 Docker Enterprise 还具有其它与容器及镜像相关的附加安全功能。最重要的是,Docker Enterprise 3.0 发布于 2019 年 7 月,其中包含“具有合理安全默认设置”的 Docker Kubernetes 服务。
不要以 -- privileged 的方式运行容器,除非您需要在 Docker 容器之内运行 Docker,而且很清楚自己到底在干什么。
在 Dockerfile 中,使用 COPY 而非 ADD。ADD 会自动提取压缩文件,并可以从 URL 处复制文件。COPY 没有这些功能。尽量避免使用 ADD,这能帮助您免受远程 URL 与 Zip 文件攻击的影响。
如果您在同一服务器上运行有其它进程,务必将其运行在 Docker 容器当中。
如果您使用 Web 服务器与 API 来创建容器,请认真检查各项参数,以确保不会意外创建不需要的容器。
如果您公开 REST API,请使用 HTTPS 或者 SSH 保护 API 端点。
考虑使用 Docker Bench for Security 进行检查,以了解您的容器是否遵循安全准则以及具体遵循程度。
仅在存储卷中保存敏感数据,绝不要将其保存在容器中。
如果使用具有网络功能的单主机应用程序,请不要使用默认桥接网络。其存在技术缺陷,不适用用于生产。一旦公布端口,则桥接网络上的所有容器都将可供外部人士访问。
使用 Lets Encrypt for HTTPS 进行证书交付。点击此处 查看 NGINX 示例。
如果只需要读取存储卷,注意将其挂载为只读状态。点击此处 查看具体操作方式。
大家可以了解到多种能够提升 Docker 容器安全水平的方法。安全性不是那种一次部署、无需再管的机制。我们需要时刻保持警惕,方可实现镜像与容器的长治久安。
考虑安全性时,别忘了 AIM 。
Access management(访问管理)
避免以 root 身份运行。如果必须使用 root,请配合 re-map。
先删除所有功能,再添加必要功能。
如果需要细粒度权限调节,请深入了解 AppArmor。
资源限制。
Image safety(镜像安全)
使用官方、高人气以及最小基础镜像。
不要安装任何您不需要的东西。
要求对镜像进行签名。
保持 Docker、Docker 镜像以及其它软件及时更新。
Management of secrets(保密信息管理)
使用 secrets 或者存储卷。
考虑使用 Vault 等 secrets 管理器。
保证 Docker 容器安全的实质,就是利用 AIM 建立安全体系。
别忘了更新 Docker、语言与库、镜像以及主机软件。最后,如果您需要在团队当中使用 Docker,可以考虑选择 Docker Enterprise。
原文链接:
https://towardsdatascience.com/top-20-docker-security-tips-81c41dd06f57
活动推荐
与无人驾驶(或辅助驾驶)技术类似,AIOps 目标是通过数值驱动手段,借助算法、建模、推理等方法辅助 DevOps 提升效率,把经验问题转变为一个算力问题。在12月6日北京ArchSummit会议上,届时会邀请阿里等公司技术专家来分享AI探索经验,欢迎点击“阅读原文”了解细节。