跳到内容

Docker 安全备忘单

简介

Docker 是最流行的容器化技术。如果使用得当,与直接在主机系统上运行应用程序相比,它可以增强安全性。然而,某些错误配置可能会降低安全级别或引入新的漏洞。

本备忘单旨在提供一个常见的安全错误和最佳实践的直观列表,以帮助保护您的 Docker 容器。

规则

规则 #0 - 保持主机和 Docker 最新

为了防范已知的容器逃逸漏洞,例如通常导致攻击者获取主机 root 权限的 Leaky Vessels,保持主机和 Docker 最新至关重要。这包括定期更新主机内核以及 Docker Engine。

这是因为容器共享主机的内核。如果主机内核存在漏洞,容器也存在漏洞。例如,在良好隔离的容器内执行的内核提权漏洞 Dirty COW,仍然会导致在易受攻击的主机上获得 root 权限。

规则 #1 - 不要暴露 Docker 守护进程套接字(即使是对容器)

Docker 套接字 /var/run/docker.sock 是 Docker 监听的 UNIX 套接字。这是 Docker API 的主要入口点。此套接字的所有者是 root。允许他人访问它等同于授予对主机的无限制 root 权限。

不要启用 tcp Docker 守护进程套接字。 如果您使用 -H tcp://0.0.0.0:XXX 或类似方式运行 docker 守护进程,则您正在暴露对 Docker 守护进程的未加密和未认证的直接访问,如果主机已连接到互联网,这意味着您计算机上的 docker 守护进程可以被公共互联网上的任何人使用。如果您确实,确实必须这样做,您应该对其进行保护。请参阅 Docker 官方文档以了解如何操作。

不要将 /var/run/docker.sock 暴露给其他容器。如果您使用 -v /var/run/docker.sock://var/run/docker.sock 或类似方式运行 docker 镜像,您应该更改它。请记住,以只读方式挂载套接字并非解决方案,只会使利用变得更困难。在 docker compose 文件中等效于以下内容:

volumes:
  - "/var/run/docker.sock:/var/run/docker.sock"

规则 #2 - 设置用户

将容器配置为使用非特权用户是防止权限提升攻击的最佳方式。这可以通过以下三种不同方式实现:

  1. 运行时使用 docker run 命令的 -u 选项,例如:
docker run -u 4000 alpine
  1. 构建时。只需在 Dockerfile 中添加用户并使用它。例如:
FROM alpine
RUN groupadd -r myuser && useradd -r -g myuser myuser
#    <HERE DO WHAT YOU HAVE TO DO AS A ROOT USER LIKE INSTALLING PACKAGES ETC.>
USER myuser
  1. Docker 守护进程中启用用户命名空间支持 (--userns-remap=default)

有关此主题的更多信息可以在 Docker 官方文档中找到。为了提高安全性,您还可以以无根模式运行,这将在规则 #11 中讨论。

在 Kubernetes 中,这可以在 安全上下文 (Security Context) 中使用 runAsUser 字段和用户 ID 进行配置,例如:

apiVersion: v1
kind: Pod
metadata:
  name: example
spec:
  containers:
    - name: example
      image: gcr.io/google-samples/node-hello:1.0
      securityContext:
        runAsUser: 4000 # <-- This is the pod user ID

作为 Kubernetes 集群管理员,您可以使用内置的 Pod 安全准入控制器 (Pod Security admission controller) 配置 受限级别的强化默认设置;如果需要更大的定制性,请考虑使用 准入 Webhooks (Admission Webhooks)第三方替代方案

规则 #3 - 限制能力(仅授予容器所需的特定能力)

Linux 内核能力 (Linux kernel capabilities) 是一组特权,可供特权用户使用。Docker 默认只运行一部分能力。您可以更改它并放弃一些能力(使用 --cap-drop)以强化您的 Docker 容器,或者在需要时添加一些能力(使用 --cap-add)。请记住不要使用 --privileged 标志运行容器——这将为容器添加所有 Linux 内核能力。

最安全的设置是删除所有能力 --cap-drop all,然后只添加所需的。例如:

docker run --cap-drop all --cap-add CHOWN alpine

请记住:不要使用 --privileged 标志运行容器!!!

在 Kubernetes 中,这可以在 安全上下文 (Security Context) 中使用 capabilities 字段进行配置,例如:

apiVersion: v1
kind: Pod
metadata:
  name: example
spec:
  containers:
    - name: example
      image: gcr.io/google-samples/node-hello:1.0
      securityContext:
        capabilities:
          drop:
            - ALL
          add: ["CHOWN"]

作为 Kubernetes 集群管理员,您可以使用内置的 Pod 安全准入控制器 (Pod Security admission controller) 配置 受限级别的强化默认设置;如果需要更大的定制性,请考虑使用 准入 Webhooks (Admission Webhooks)第三方替代方案

规则 #4 - 防止容器内权限提升

始终使用 --security-opt=no-new-privileges 运行您的 Docker 镜像,以防止权限提升。这将阻止容器通过 setuidsetgid 二进制文件获取新权限。

在 Kubernetes 中,这可以在 安全上下文 (Security Context) 中使用 allowPrivilegeEscalation 字段进行配置,例如。

apiVersion: v1
kind: Pod
metadata:
  name: example
spec:
  containers:
    - name: example
      image: gcr.io/google-samples/node-hello:1.0
      securityContext:
        allowPrivilegeEscalation: false

作为 Kubernetes 集群管理员,您可以使用内置的 Pod 安全准入控制器 (Pod Security admission controller) 配置 受限级别的强化默认设置;如果需要更大的定制性,请考虑使用 准入 Webhooks (Admission Webhooks)第三方替代方案

规则 #5 - 注意容器间连接

容器间连接 (icc) 默认启用,允许所有容器通过 docker0 桥接网络相互通信。与其使用 Docker 守护进程的 --icc=false 标志来完全禁用容器间通信,不如考虑定义特定的网络配置。这可以通过创建自定义 Docker 网络并指定哪些容器应该连接到它们来实现。这种方法可以更细粒度地控制容器通信。

有关配置 Docker 网络以进行容器通信的详细指南,请参阅 Docker 文档

在 Kubernetes 环境中,网络策略 (Network Policies) 可用于定义规则,以调节集群内 Pod 的交互。这些策略提供了一个强大的框架来控制 Pod 之间以及 Pod 与其他网络端点之间的通信。此外,网络策略编辑器 (Network Policy Editor) 简化了网络策略的创建和管理,通过用户友好的界面使定义复杂的网络规则更加便捷。

规则 #6 - 使用 Linux 安全模块 (seccomp, AppArmor, 或 SELinux)

首先,不要禁用默认的安全配置文件!

考虑使用像 seccompAppArmor 这样的安全配置文件。

如何在 Kubernetes 内部执行此操作的说明可以在 为 Pod 或容器配置安全上下文 (Configure a Security Context for a Pod or Container) 中找到。

规则 #7 - 限制资源(内存、CPU、文件描述符、进程、重启次数)

避免 DoS 攻击的最佳方法是限制资源。您可以限制内存CPU、最大重启次数 (--restart=on-failure:<number_of_restarts>)、最大文件描述符数量 (--ulimit nofile=<number>) 和最大进程数量 (--ulimit nproc=<number>)。

查看文档以获取更多关于 ulimits 的详细信息

您也可以为 Kubernetes 执行此操作:为容器和 Pod 分配内存资源为容器和 Pod 分配 CPU 资源为容器分配扩展资源

规则 #8 - 将文件系统和卷设置为只读

使用 --read-only 标志运行具有只读文件系统的容器。 例如:

docker run --read-only alpine sh -c 'echo "whatever" > /tmp'

如果容器内的应用程序必须临时保存某些内容,请将 --read-only 标志与 --tmpfs 结合使用,如下所示:

docker run --read-only --tmpfs /tmp alpine sh -c 'echo "whatever" > /tmp/file'

Docker Compose compose.yml 中的等效配置为:

version: "3"
services:
  alpine:
    image: alpine
    read_only: true

在 Kubernetes 安全上下文 (Security Context) 中的等效配置为:

apiVersion: v1
kind: Pod
metadata:
  name: example
spec:
  containers:
    - name: example
      image: gcr.io/google-samples/node-hello:1.0
      securityContext:
        readOnlyRootFilesystem: true

此外,如果卷仅用于读取,请将其挂载为只读。可以通过在 -v 后附加 :ro 来实现,如下所示:

docker run -v volume-name:/path/in/container:ro alpine

或使用 --mount 选项

docker run --mount source=volume-name,destination=/path/in/container,readonly alpine

规则 #9 - 将容器扫描工具集成到您的 CI/CD 流水线中

CI/CD 流水线是软件开发生命周期中至关重要的一部分,应包含各种安全检查,例如 lint 检查、静态代码分析和容器扫描。

通过在编写 Dockerfile 时遵循一些最佳实践,可以避免许多问题。然而,将安全 linter 作为构建流水线中的一个步骤可以大大避免进一步的麻烦。一些常见的检查问题是:

  • 确保指定了 USER 指令
  • 确保基础镜像版本已固定
  • 确保操作系统软件包版本已固定
  • 避免使用 ADD,优先使用 COPY
  • 避免在 RUN 指令中进行 curl bashing

参考资料

容器扫描工具作为成功的安全策略的重要组成部分尤为重要。它们可以检测容器镜像中的已知漏洞、秘密和错误配置,并提供发现报告以及如何修复它们的建议。一些流行的容器扫描工具示例如下:

用于检测镜像中的秘密信息

用于检测 Kubernetes 中的错误配置

用于检测 Docker 中的错误配置

规则 #10 - 将 Docker 守护进程的日志级别保持为 info

默认情况下,Docker 守护进程配置为基础日志级别为 info。这可以通过检查守护进程配置文件 /etc/docker/daemon.json 中的 log-level 键来验证。如果该键不存在,则默认日志级别为 info。此外,如果 Docker 守护进程使用 --log-level 选项启动,则配置文件中 log-level 键的值将被覆盖。要检查 Docker 守护进程是否以不同的日志级别运行,您可以使用以下命令:

ps aux | grep '[d]ockerd.*--log-level' | awk '{for(i=1;i<=NF;i++) if ($i ~ /--log-level/) print $i}'

设置适当的日志级别,配置 Docker 守护进程记录您以后需要查看的事件。基础日志级别为“info”及以上将捕获除调试日志之外的所有日志。除非需要,否则您不应以“debug”日志级别运行 Docker 守护进程。

规则 #11 - 以无根模式运行 Docker

无根模式确保 Docker 守护进程和容器以非特权用户身份运行,这意味着即使攻击者突破容器,他们也不会在主机上拥有 root 权限,这反过来大大限制了攻击面。这与用户命名空间重映射 (userns-remap) 模式不同,后者中守护进程仍以 root 权限运行。

评估您的环境的 特定要求安全态势,以确定无根模式是否是您的最佳选择。对于将安全性视为首要关注点且 无根模式的限制 不会干扰操作要求的环境,强烈建议采用此配置。另外,考虑使用 Podman 作为 Docker 的替代方案。

无根模式允许以非 root 用户身份运行 Docker 守护程序和容器,以缓解守护程序和容器运行时中潜在的漏洞。只要满足 先决条件,无根模式甚至在安装 Docker 守护程序期间也不需要 root 权限。

Docker 文档 页面上阅读更多关于无根模式及其限制、安装和使用说明的信息。

规则 #12 - 利用 Docker Secrets 进行敏感数据管理

Docker Secrets 提供了一种安全存储和管理敏感数据(如密码、令牌和 SSH 密钥)的方式。使用 Docker Secrets 有助于避免在容器镜像或运行时命令中暴露敏感数据。

docker secret create my_secret /path/to/super-secret-data.txt
docker service create --name web --secret my_secret nginx:latest

或者对于 Docker Compose

version: "3.8"
secrets:
  my_secret:
    file: ./super-secret-data.txt
services:
  web:
    image: nginx:latest
    secrets:
      - my_secret

虽然 Docker Secrets 通常提供了一种安全管理 Docker 环境中敏感数据的方式,但此方法不建议用于 Kubernetes,因为 Kubernetes 中 secrets 默认以纯文本形式存储。在 Kubernetes 中,请考虑使用额外的安全措施,如 etcd 加密或第三方工具。有关更多信息,请参阅 秘密管理备忘单

规则 #13 - 增强供应链安全

规则 #9 的原则基础上,增强供应链安全涉及实施额外措施,以保护容器镜像从创建到部署的整个生命周期。一些关键实践包括:

  • 镜像来源:记录容器镜像的来源和历史,以确保可追溯性和完整性。
  • SBOM 生成:为每个镜像创建软件物料清单 (SBOM),详细说明所有组件、库和依赖项,以实现透明度和漏洞管理。
  • 镜像签名:对镜像进行数字签名,以验证其完整性和真实性,从而建立对其安全性的信任。
  • 可信注册表:将已记录、已签名的镜像及其 SBOM 存储在安全的注册表中,该注册表强制执行严格的访问控制并支持元数据管理。
  • 安全部署:实施安全部署策略,例如镜像验证、运行时安全和持续监控,以确保已部署镜像的安全性。

Podman 作为 Docker 的替代品

Podman 是由 Red Hat 开发的符合 OCI 规范的开源容器管理工具,它提供了与 Docker 兼容的命令行界面和桌面应用程序来管理容器。它被设计为 Docker 的一个更安全、更轻量的替代品,尤其适用于偏好安全默认设置的环境。Podman 的一些安全优势包括:

  1. 无守护进程架构:与 Docker 需要一个中心守护进程 (dockerd) 来创建、运行和管理容器不同,Podman 直接采用 fork-exec 模型。当用户请求启动容器时,Podman 从当前进程 fork,然后子进程 exec 进入容器的运行时。
  2. 无根容器:fork-exec 模型有助于 Podman 在不需要 root 权限的情况下运行容器。当非 root 用户启动容器时,Podman 在该用户的权限下 fork 并 exec。
  3. SELinux 集成:Podman 旨在与 SELinux 协同工作,这通过对容器及其与主机系统的交互强制实施强制访问控制来提供额外的安全层。

参考资料和进一步阅读

OWASP Docker Top 10 Docker 安全最佳实践 Docker Engine 安全 Kubernetes 安全备忘单 SLSA - 软件制品供应链级别 Sigstore Docker 构建证明 Docker 内容信任