12 个优化 Docker 镜像安全性的技巧( 二 )


 
使用本地开发机器进行构建的问题是,你的本地 Git 存储库的“工作树“可能是脏的 。例如,它可能包含有开发过程中需要的密钥文件,例如对中转甚至生产服务器的访问密钥 。如果没有通过.dockerignore 排除这些文件,那么 Dockerfile 中的“COPY . .“等语句可能会意外导致这些密钥泄露到最终镜像中 。
以非 root 用户身份运行 
默认情况下,当有人通过“docker run <more arguments> yourImage:yourTag“运行你的镜像时,这个容器(以及你在 ENTRYPOINT/CMD 中的程序)会以 root 用户身份运行(在容器和主机上) 。这给了一个使用某种漏洞在你的运行容器中获得 shell 权限的攻击者以下权力:
 

  • 对主机上所有显式挂载到容器中的目录的无限制写权限(因为是 root) 。
  • 能够在容器中做 Linux 根用户可以做的一切事情 。例如,攻击者可以安装他们需要的额外工具来加载更多的恶意软件,比如说通过 apt-get install(非 root 用户无法做到这一点) 。
  • 如果你的镜像容器是用 docker run --privileged 启动的,攻击者甚至可以接管整个主机 。
 
为了避免这种情况,你应该以非 root 用户(你在 docker build 过程中创建的一些用户)的身份运行你的应用程序 。在你的 Dockerfile 中的某个地方(通常是在结尾处)放置以下语句:
 
# Create a new user (including a home-directory, which is optional)RUN useradd --create-home Appuser# Switch to this userUSER appuser复制代码
 
Dockerfile 中所有在 USER appuser 语句之后的命令(如 RUN、CMD 或 ENTRYPOINT)都将以这个用户运行 。这里有一些需要注意的地方:
 
  • 在切换到非 root 用户之前,你通过 COPY 复制到镜像中的文件(或由某些 RUN 命令创建的文件)是由 root 用户拥有的,因此以非 root 用户身份运行的应用程序无法写入 。为了解决这个问题,请把创建和切换到非 root 用户的代码移到 Dockerfile 的开头 。
  • 如果这些文件是在 Dockerfile 的开头以根用户身份创建的(存储在/root/下面,而不是/home/appuser/下面),那么你的程序期望在用户的主目录中的某个地方(例如~/.cache)的文件,现在从应用程序的视角来看可能突然消失了 。
  • 如果你的应用程序监听一个 TCP/UDP 端口,就必须使用大于 1024 的端口 。小于等于 1024 的端口只能以 root 用户身份使用,或者以一些高级 Linux 能力来使用,但你不应该仅仅为了这个目的而给你的容器这些能力 。
使用最新的基础镜像构建和更新系统包 
如果你使用的基础镜像包含了某个真正的 Linux 发行版(如 Debian、Ubuntu 或 alpine 镜像)的全部工具集,其中包括一个软件包管理器,建议使用该软件包管理器来安装所有可用的软件包更新 。
背景知识 
基础镜像是由某人维护的,他配置了 CI/CD 管道计划来构建基础镜像,并定期推送到 Docker Hub 。你无法控制这个时间间隔,而且经常发生的情况是,在该管道将更新的 Docker 镜像推送到 Docker Hub 之前,Linux 发行版的包注册表(例如通过 apt)中已经有了安全补丁 。例如,即使基础镜像每周推送一次,也有可能在最近的镜像发布几小时或几天后出现安全更新 。
 
因此,最好总是运行更新本地软件包数据库和安装更新的包管理器命令,采用无人值守模式(不需要用户确认) 。每个 Linux 发行版的这个命令都不一样 。
 
例如,对于 Ubuntu、Debian 或衍生的发行版,使用 RUN apt-get update && apt-get -y upgrade
 
另一个重要的细节是,你需要告诉 Docker(或你使用的任何镜像构建工具)来刷新基础镜像 。否则,如果你引用一个基础镜像,比如 Python:3(而 Docker 在其本地镜像缓存中已经有了这样一个镜像),Docker 甚至不会检查 Docker Hub 上是否存在更新的 python:3 版本 。为了摆脱这种行为,你应该使用这个命令:
 
docker build --pull <rest of the build command>复制代码
 
这可以确保 Docker 在构建镜像之前拉取你的 Dockerfile 中 FROM 语句中提到的镜像的更新 。
 
你还应该注意 Docker 的层缓存机制,它会让你的镜像变得陈旧,因为 RUN <install apt/etc. updates>命令的层是缓存的,直到基础镜像维护者发布新版本的基础镜像才刷新 。如果你发现基础镜像的发布频率相当低(比如少于一周一次),那么定期(比如每周一次)重建你的镜像并禁用层缓存是个好主意 。你可以运行以下命令来做到这一点:


推荐阅读