

帅哥(靓仔)、美女,点个关注后续不迷路!
本章目录

0x00 前言简述
1.容器安全概述
2.容器安全机制
内核能力机制
服务端防护机制
辅助安全机制
3.容器安全风险
0x01 Docker 镜像安全最佳实践
1.选用最小化基础镜像
2.设定最小权限的 USER 运行容器
3.签名和校验镜像,防范中间人攻击
4.检测、修正和监控开源漏洞
5.容器镜像中禁止包含机密信息
6.设定镜像的标签,保证镜像的不可更改性
7.镜像构建更加安全、快速、精简
8.使用静态分析工具,Dockerfile编写指导

描述: 在企业中信息系统安全与业务是同样重要, 随着传统运维方式向着容器化运维方式的转变,当下企业里通常都会采用Docker来进行容器化部署和承载业务, 由于运维人员或者开发人员对容器安全的关注较少, 只是简单认为容器是有隔离和限制的, 就算是容器被黑客攻击了, 也单单是容器内部受到影响, 而对宿主的 Linux 系统和网络都不会产生太大影响。其实不然Docker容器安全也是重中之重, 它关乎着应用与数据安全,其中也关乎着宿主机的安全。
所以在本章之中,我将讲述如何更安全配置使用Docker容器环境,优化Docker镜像的安全构建过程,以使我们能够在最短构建时间内构建最小、最安全的满足生产需求的Docker镜像。
温馨提示: 本文中的技巧与示例应该可以提高您的Docker容器安全知识, 并可以改善所使用的Docker镜像质量,希望读者从中有所收获。
Docker 安全问题公告: https://docs.docker.com/security/
在正式学习Docker容器安全之前,先解释一下本章节会涉及的相关概念,它可以帮助你扫除概念障碍,以及更好的理解容器安全应该从那几个方面入手。
Docker 宿主机: 安装Docker服务的操作系统主机。
Docker 服务: 即Docker提供的相关功能以及在宿主机中的Docker进程,Docker进程是可以直接与Linux内核进行通信的。
Docekr 镜像: 通过Dockerfile文件构建得到的产物。
Docker 容器: 通过镜像创建运行多个Docker容器,即业务是运行在容器之中,注意Docker容器是运行于Docker服务之上。
Docker 宿主机安全 描述: 宿主机作为Docker服务运行的基础环境, 其重要性是无可置疑的,一个安全的基础环境是保证Docker服务安全的基础设施,所以在安装使用docker前我们需要针对其宿主机操作系统进行一系列的加固配置,具体如何进行宿主机的安全加固设置,可以参考我前面所写的Windows与Linux系统加固脚本。
Docker 服务安全 描述: Docker 服务的安全同样在容器安全中占据着重要地位,如果一旦Docker服务由于脆弱性配置被攻击者控制,将会影响所有运行在该Docker服务下的所有容器,将不能保证业务与数据的安全,攻击者完全可以通过Docker Server提供的REST API接口进行管理容器或者查看容器内某些重要的配置文件。
Docekr 镜像安全 描述: Docker 镜像安全也是在容器安全中占有一席之地,如果一旦镜像系统或者服务中存在可以被攻击者利用的漏洞,在使用该镜像创建并运行容器后便可能反弹shell进行内网穿透,从而对容器中运行的业务、业务数据产生影响,并且还可能会影响Docker宿主机的安全。所以说一个安全的Docker镜像也是保证容器安全的重要一环,即我们需在构建镜像时选择较小的操作系统并只安装业务需要的服务与软件即可,减少攻击面从而增加攻击难度。
Docekr 容器安全 描述: Docker 容器作为承载业务的地方,是运维或开发人员接触最多的对象之一,它也是容器安全里的重要一环,如果在创建容器时未对某些功能做相应的限制,一旦docker容器中承载的业务产生漏洞或者包含的动态调用的脚本程序出现问题,都有可能严重危害到容器或者宿主机的安全。所以说为了保证docker容器安全我们不但要从docker安全入手,更要从业务安全入手。
从上面四大方面可以看出,容器安全并不是简单在某一环做出相应的安全配置就可以的,我们需要考虑四个主要方面,一是内核的内在安全性及其对命名空间和 cgroup 的支持,二是Docker 守护进程本身的攻击面,三是容器配置文件中的漏洞,四是内核的“强化”安全特性以及它们如何与容器交互。所以我们必须从基础操作系统环境、容器服务、容器镜像以及业务与运维开发人员分别入手,才能提高容器的安全性,减少被攻击的可能性。
描述: Docker 容器与 LXC 容器非常相似,并且具有相似的安全特性,当您使用 docker run创建或启动容器时,Docker 服务为了防止黑客在控制容器后能够对宿主机进行攻击,提供了三个主要的隔离机制,其分别是Namespace 机制、Capabilities 机制和 CGroups 机制。
通过隔离机制能起到对容器一定的保护,但并不是绝对的,如果攻击对宿主机产生了影响,就说明入侵者已经突破了Docker服务的保护,这就是容器安全中常说的Docker容器逃逸。
描述: 通过前面的学习我们知道Docker服务进程是直接与Linux 内核进行通信的,而Linux内核从从2.6.24版本起提供了一个强大的特性,就是可以提供细粒度的权限访问控制操作能力,既可以用在进程上,也可以作用在文件上,使用内核能力机制对加强 Docker 容器的安全有很多好处;
默认情况下,Docker 启动的容器被严格限制只允许使用内核的一部分能力。并且Docker采用白名单机制,禁用必需功能之外的其它权限
例如,一个 Web 服务进程只需要绑定一个低于 1024 的端口的权限,并不需要 root 权限。那么它只需要被授权 net_bind_service 能力即可。
为了加强安全,容器可以禁用一些没必要的权限。
完全禁止任何 mount 操作;
禁止直接访问本地主机的套接字;
禁止访问一些文件系统的操作,比如创建新的设备、修改文件属性等;
禁止模块加载。
这样就算攻击者在容器中取得了 root 权限,也不能获得本地主机的较高权限,能进行的破坏也有限。
Namespace 机制 描述: Namespace 即名称空间,是Linux内核提供的一种标签机制,Linux内核会针对不同Namespace之间的进程做隔离,避免不同的进程之间互相产生影响,所以Docker服务会为每一个 Docker容器创建一个单独的 Namespace 空间。 由于不同容器之间、容器和系统之间都是不同名称的Namespace,所以在一个容器中运行的进程无法看到运行在另一个容器或主机系统中的进程,并且每个容器还拥有自己的网络堆栈,这意味着一个容器无法获得对另一个容器的套接字或接口的特权访问,但通常Docker主机上的所有容器都是默认位于桥接接口docker0上,除非在创建容器之初指定了其他网络模式。
虽然 Namespace 实现了容器和宿主机环境的"伪隔离", 但是由于输入、输出、硬件设备的需要,容器仍然可以修改宿主机中部分文件,所以我们又需要一种可以限制对象操作的权限的机制,即下面要说的Capabilities机制。
Capabilities 机制 描述: Capabilities 提供了更细粒度的授权机制,它定义了主体对象能够进行的某一类操作。例如当一个容器的Web服务需要绑定 到80端口,但是80端口的绑定是需要ROOT权限的。而Docker为了防止 ROOT 权限滥用会通过 Capabilities 机制,给予该容器Web 服务对象 net_bind_service 的权限(其允许绑定到小于 1024 的端口)。
同样地Docker服务对容器中的ROOT权限用户添加了很多默认的限制,比如:拒绝所有的挂载操作、拒绝部分文件的操作(如修改文件所有者等)、拒绝内核模块加载;
虽然 Capabilities 可以最大程度解决容器安全问题, 但Capabilities对容器可进行操作的限制程度是难以把控的,如果设置过松会导致 Docker 容器影响宿主机系统,让 Docker 隔离失效。而如果设置过为严格的话会让容器以及容器内的服务功能受限,导致Docker容器无法正常运行。
所以在默认情况下,Docker 会采用白名单机制(白名单列表你可以在 Docker 源码中查看)进行限制,即只允许 Docker 容器拥有几个默认的能力。那有了白名单限制,即使攻击者成功拿到了容器中的 ROOT 权限,能够对宿主机造成的影响也相对较小,所以“Docker 逃逸”并不是一件不容易的事。
CGroups 机制 描述: CGroups 提供了资源限制的作用能力,Docker 服务可以利用 CGroups 机制来实现对容器中内存、CPU处理和磁盘IO份额等的限制,减少Docker容器由于业务或者攻击从而过多占用宿主机资源,对宿主机以及其他容器产生影响。
比如,通过下面的命令就可以限制 Docker 容器只使用 2 个 CPU 和 200MB 的内存来运行。
docker run -it --cpus=2 --memory="200m" debian:latest /bin/bash
虽然 CGroups 可以解决给每一个容器弹性地分配 CPU 、内存或者其他资源。同样地该限制既不能过松,也不能过为严格,如果设置过松会导致某一 Docker容器耗尽宿主机资源。而如果设置过严又会使得容器内的服务得不到足够的资源支持,可能无法运行。所以此时需要我们自己根据业务的压测结果来进行配置,没有默认的安全机制可以辅助。
Docker 服务的运行目前需要 root 权限,因此其安全性十分关键,由于运行一个容器或应用程序的核心是通过 Docker 服务端。
Docker 允许用户在主机和容器间共享文件夹,同时不需要限制容器的访问权限,这就容易让容器突破资源限制; 例如:恶意用户启动容器的时候将主机的根目录/映射到容器的 /host 目录中,那么容器理论上就可以对主机的文件系统进行任意修改了 因此当提供容器创建服务时(例如通过一个 web 服务器),要更加注意进行参数的安全检查,防止恶意的用户用特定参数来创建一些破坏性的容器。
Docker 的安全特性:
首先,确保只有可信的用户才可以访问 Docker 服务(理论上由于攻击层出不穷)。
其次, 在容器内不使用 root 权限来运行进程的话。
确保只有可信的网络或 VPN,或证书保护机制(例如受保护的 stunnel 和 ssl 认证)下的访问可以进行。
将容器的 root 用户映射到本地主机上的非 root 用户,减轻容器和主机之间因权限提升而引起的安全问题;
允许 Docker 服务端在非 root 权限下运行,利用安全可靠的子进程来代理执行需要特权权限的操作。这些子进程将只允许在限定范围内进行操作,例如仅仅负责虚拟网络设定或文件系统管理、配置操作等。
描述: 除此之外,我们还可以利用一些现有的安全机制来增强使用 Docker 的安全性,例如 TOMOYO, AppArmor, SELinux, GRSEC 等。 Docker 当前默认只启用了能力机制,用户可以采用多种方案来加强 Docker 主机的安全,例如:
在内核中启用 GRSEC 和 PAX,这将增加很多编译和运行时的安全检查;通过地址随机化避免恶意探测等。并且启用该特性不需要 Docker 进行任何配置。
使用一些有增强安全特性的容器模板,比如带 AppArmor 的模板和 Redhat 带 SELinux 策略的模板。这些模板提供了额外的安全特性。
用户可以自定义访问控制机制来定制安全策略。 跟其它添加到 Docker 容器的第三方工具一样(比如网络拓扑和文件系统共享),有很多类似的机制,在不改变 Docker 内核情况下就可以加固现有的容器。
默认情况下,如果运行容器内以非特权用户身份运行进程时,容器通常是相对安全的,但是您仍然可以通过启用 AppArmor、SELinux、GRSEC 或其他适当的组件服务系统来添加额外的安全层,来更进一步来保证容器的安全。
描述: 本小节将针对容器化环境中,您有可能会遇到安全风险进行一一罗列,其主要分为如下几方面。
Docker 容器安全常见问题
宿主机问题 例如,宿主机上其他服务漏洞导致的网络穿透代理访问,内网探测、Docker守护进程Socket文件读取。
自身漏洞问题 例如,代码执行、权限提升、信息泄漏、runC。
架构缺陷与安全机制未配置问题 例如,Namespaces 导致的:容器之间的局域网攻击、共享root、未隔离的文件系统、默认放通所有。 例如,CGroups 导致的: DDoS攻击耗尽资源。
镜像源问题 例如,恶意镜像、存在漏洞的镜像、容器逃逸。
生态问题 例如,Containerd 相关漏洞、Kubernetes相关漏洞。
Docker 容器安全防护基线
内核级别的:Namespaces、Cgroup、SElinux
主机级别的:服务最小化、禁止挂载宿主机敏感目录、挂载目录权限设置为644
网络级别的:禁止映射特权端口、通过iptable设定规则并禁止容器之间的网络流量
镜像级别的:创建本地镜像服务器、使用可信镜像、使用镜像扫描、合理管理镜像标签
容器级别的:容器以单一主进程方式运行、禁止运行SSH等高危服务、以只读方式挂载根目录系统
描述: Docker 安全管理要趁早,下面分别列举了几个诀窍和指南,确保为测试与生产环境提供更安全和更高质量的 Docker 镜像。
描述: 运维人员在编写项目的 Dockerfile 时,经常使用一个通用的 Docker 容器镜像作为基础例如From Node,而Node 镜像实际上是以一个完整安装的 Debian Stretch 发行版为基础,这意味着构建得到的项目容器镜像将包含一个完整的操作系统。所以如果该项目不需要任何通用的系统库或者系统工具应用,最好不要使用完整的操作系统作为基础镜像。
Synx 发布的《开源安全报告-2019[3]》指出,Docker Hub 上流行的很多容器镜像,都用到了包含大量已知安全漏洞的基础镜像。例如执行 docker pull node ,下载并使用 Node 镜像,相当于在应用中引入了一个包含 580 个已知漏洞的操作系统。

安全实践: 选用最小化基础镜像,即只包含项目确实需要的系统工具和库的镜像,就能最小化系统的攻击面,确保所用操作系统是安全的。
描述: 如果 Dockerfile 中没有指定 USER ,Docker 默认将会以超级用户 root 的身份运行容器,容器所属的命名空间(namespace)因此映射为 root 所有,这意味着容器有可能获取 Docker 宿主机的超级管理权限。不仅如此以 root 用户身份运行容器,还扩大了攻击面,如果容器应用中存在安全漏洞,很容易造成权限提升。
在实践中一般不需要容器拥有 root 权限。为了尽量降低安全威胁,创建专门的用户和用户组,在 Dockerfile 中使用 USER 指定用户,确保以最小权限的用户身份运行容器应用。
安全实践:
1.如果基础镜像中不包含专门的用户,那么就在 Dockerfile 中直接创建。
FROM ubuntu
RUN mkdir /app && \
groupadd -r weiyigeek && \
useradd -r -s /bin/false -g weiyigeek weiyigeek
WORKDIR /app
COPY . /app
RUN chown -R weiyigeek:weiyigeek /app
USER weiyigeek
CMD node index.js
# 关键命令解释
# - 创建一个系统用户( -r 选项),没有密码、没有主目录且没有 shell,并将该用户添加到前面(使用 groupadd )创建的用户组; 2.如果你使用的是 Node.js 和 alpine 镜像,已经包含了一个用户 node,直接使用即可:
FROM node:10-alpine
RUN mkdir /app
COPY . /app
RUN chown -R node:node /app
USER node
CMD ["node", "index.js"]
描述: Docker 镜像的认证颇具挑战性。在生产环境使用这些镜像运行我们的代码,意味着我们对这些镜像的极大信任。因此必须保证我们拉取的容器镜像确实是发布者发布的镜像,没有被任何人篡改。发生镜像篡改,有可能是因为 Docker 客户端和镜像中心之间的中间人攻击,或者是发布者的身份被人盗用并在镜像中心发布了恶意镜像。
安全实践:
1校验 Docker 镜像: Docker 默认直接拉取容器镜像,不会校验镜像的来源和发布者。这意味着你有可能使用来源和发布者不明的任何镜像。无论采用何种策略,最佳实践都是先校验容器镜像,通过验证后再拉取镜像。
# 1.为了体验镜像校验功能我们暂时开启 Docker Content Trust ,可以在系统环境变量中进行执行命令并且可以加入到/etc/profile文件中。
export DOCKER_CONTENT_TRUST=1
# 2.现在尝试拉取一个没有签名的容器镜像——请求会被拒绝不会拉取镜像。
$ docker pull kacha886/buysbox
Using default tag: latest # 默认latest标记是没有签名的
Error: remote trust data does not exist for docker.io/kacha886/buysbox: notary.docker.io does not have trust data for docker.io/kacha886/buysbox
$ docker pull busybox:1.33.1 # 本地不存在busybox 1.33.1的信任数据(没有签名信息)
No valid trust data for 1.33.1
# 3.我们在设置容器校验的环境中可通过 --disable-content-trust 标志关闭内容信任后在标记镜像上运行单独的操作即可正常拉取、构建、上传非签名镜像。
docker pull --disable-content-trust busybox:1.33.1
docker build --disable-content-trust -t weiyigeek/buysbox:notrust .
docker login -u weiyigeek
docker push --disable-content-trust weiyigeek/busybox:1.33.1
# 4.推送一个有签名的可信的镜像到仓库中
docker push weiyigeek/buysbox:trust
# The push refers to a repository [docker.io/weiyigeek/trust] (len: 1)
# 9a61b6b1315e: Image already exists
# 902b87aaaec9: Image already exists
# latest: digest: sha256:d02adacee0ac7a5be140adb94fa1dae64f4e71a68696e7f8e7cbf9db8dd49418 size: 3220
# Signing and pushing trust metadata
# You are about to create a new root signing key passphrase. This passphrase
# will be used to protect the most sensitive key in your signing system. Please
# choose a long, complex passphrase and be careful to keep the password and the
# key file itself secure and backed up. It is highly recommended that you use a
# password manager to generate the passphrase and keep it safe. There will be no
# way to recover this key. You can find the key in your config directory.
# Enter passphrase for new root key with id a1d96fb:
# Repeat passphrase for new root key with id a1d96fb:
# Enter passphrase for new repository key with id docker.io/weiyigeek/trust (3a932f1):
# Repeat passphrase for new repository key with id docker.io/weiyigeek/trust (3a932f1):
# Finished initializing "docker.io/weiyigeek/trust"
2.签名 Docker 镜像: Docker 支持镜像签名,提供了额外一层的保护,使用 Docker Notary 签名镜像Notary 会检验镜像的签名,如果签名不合法,它会阻止运行该镜像。并在选择基础镜像时优先使用 Docker 认证的镜像,即这些镜像来自经过 Docker Hub 检查和选择的可信提供者,一定不要使用无法检验来源和发布者的容器镜像。
如果开启了 Docker Content Trust ,构建 Docker 镜像的同时也会对镜像签名,例如推送可信标记镜像到仓库流程。
- 提示需要创建一个新的根密钥
- 请求根密钥的密码
- 在 ~/.docker/trust 目录中生成一个根密钥
- 请求仓库密钥的密码
- 在 ~/.docker/trust 目录中生成一个仓库密钥 示例演示:
# 1.基础镜像拉取
$ docker pull --disable-content-trust busybox:1.33.1
# 1.33.1: Pulling from library/busybox
# b71f96345d44: Pull complete
# Digest: sha256:930490f97e5b921535c153e0e7110d251134cc4b72bbb8133c6a5065cc68580d
# Status: Downloaded newer image for busybox:1.33.1
# docker.io/library/busybox:1.33.1
# 2.构建自定义镜像
$ tee dockertrust/dockerfile <<'EOF'
FROM busybox:1.33.1
MAINTAINER weiyigeek master@weiyigeek.top
CMD ["echo","Welcome to Visited www.weiyigeek.top"]
EOF
$ docker build --disable-content-trust -t weiyigeek/buysbox:1.33.1-trust .
# 3.运行自定义镜像的容器
$ docker run --disable-content-trust weiyigeek/buysbox:1.33.1-trust
# Welcome to Visited www.weiyigeek.top
# 4.登录到docker hub仓库之中(https://hub.docker.com)
$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you dont have a Docker ID, head over to https://hub.docker.com to create one.
Username: weiyigeek
Password: xxx
# 5.上传可信赖的镜像到docker hub仓库之中。
$ docker push weiyigeek/buysbox:1.33.1-trust
The push refers to repository [docker.io/weiyigeek/buysbox]
5b8c72934dfc: Mounted from library/busybox
1.33.1-trust: digest: sha256:99959645871654685ef84dbc4f3cb541a4f5332505752fbb89f0af80e6c84662 size: 527
Signing and pushing trust metadata
You are about to create a new root signing key passphrase. This passphrase
will be used to protect the most sensitive key in your signing system. Please
choose a long, complex passphrase and be careful to keep the password and the
key file itself secure and backed up. It is highly recommended that you use a
password manager to generate the passphrase and keep it safe. There will be no
way to recover this key. You can find the key in your config directory.
Enter passphrase for new root key with ID 7fcbe50:
Repeat passphrase for new root key with ID 7fcbe50:
Enter passphrase for new repository key with ID 0d40411:
Repeat passphrase for new repository key with ID 0d40411:
Finished initializing "docker.io/weiyigeek/buysbox"
Successfully signed docker.io/weiyigeek/buysbox:1.33.1-trust
# 6.查看生成的根私有密钥和仓库密钥
$ ls ~/.docker/trust/private/
0d4041172f5d7286c5199510ad25b795a65bfc774d6e01ce2948c5e217df553f.key 7fcbe50705b0e48526d35b8f928bc52054e899ff08edb44f4fb023fb50979cf1.key
# 7.删除可信赖的镜像后并通过远程仓库拉取可信赖镜像
$ docker rmi -f weiyigeek/buysbox:1.33.1-trust
$ docker pull docker.io/weiyigeek/buysbox:1.33.1-trust
Pull (1 of 1): weiyigeek/buysbox:1.33.1-trust@sha256:99959645871654685ef84dbc4f3cb541a4f5332505752fbb89f0af80e6c84662 # 关键点拉取时可以看到内容信任后的特征
sha256:99959645871654685ef84dbc4f3cb541a4f5332505752fbb89f0af80e6c84662: Pulling from weiyigeek/buysbox
Digest: sha256:99959645871654685ef84dbc4f3cb541a4f5332505752fbb89f0af80e6c84662
Status: Downloaded newer image for weiyigeek/buysbox@sha256:99959645871654685ef84dbc4f3cb541a4f5332505752fbb89f0af80e6c84662
Tagging weiyigeek/buysbox@sha256:99959645871654685ef84dbc4f3cb541a4f5332505752fbb89f0af80e6c84662 as weiyigeek/buysbox:1.33.1-trust
docker.io/weiyigeek/buysbox:1.33.1-trust Tips: 在镜像存储库可以包含同时具有已签名标签和未签名标签的镜像,例如 Mongo 镜像仓库latest 标签是未签名的而 3.1.6 标签是签名的。 Tips: 首次调用使用内容信任的操作时会创建密钥集,而密钥集由以下几类密钥组成, 1)作为镜像标记的内容信任的根的脱机密钥 2)签名标签的仓库或标签密钥 3)服务器管理的密钥,如时间戳密钥,为仓库提供最新的安全保证 Tips: 如果是第一次签名Docker 会为当前用户生成一个私钥,保存在 ~/docker/trust后续所有的镜像都会使用这个私钥签名,丢失根密钥非常难以恢复纠正这种损失需要 Docker 支持人员的干预来重置仓库状态,所以最好是将应该将根密钥备份到安全的地方。
描述: 在指定容器的基础镜像时,同时也引入了该镜像包含的操作系统及系统库有可能存在的所有安全风险。前面我们提到过最好选用能够正常运行应用代码的最小化镜像,这有助于减少攻击面,因为限制了可能的安全漏洞数量。不过这么做并没有对镜像进行安全审计,也不能防范将来发现的新漏洞。
最佳安全实践,我们应该尽可能早地在开发过程中集成安全性,我们越早开始进行安全检查,对组织来说就越便宜,更有效,所以我们要在构建镜像时进行采用辅助扫描工具和安全漏洞库进行集成扫描,例如Snyk、Trivy等工具,其中使用较多的是Snyk引擎。
Snyk : 它是一个开发者优先选择的云原生安全工具,该工具可以扫描并监控您的项目构建的基础镜像是否存在安全漏洞,其主要功能是查找并自动修复开源漏洞、实时查找并修复应用程序代码中的漏洞、查找并修复容器镜像和 Kubernetes 应用程序中的漏洞以及查找并修复 Terraform 和 Kubernetes 代码中的不安全配置 ,并且Docker和Snyk建立了合作伙伴关系,以提供容器漏洞扫描以及将Snyk扫描引擎直接集成到docker-cli和Docker Desktop客户端中,但是在此之前我们必须需要将我们构建好的基础环镜像推送到镜像仓库中,才能进行漏洞的扫描,而现在我们可以在上传到仓库前进行镜像漏洞检测。 项目地址: https://github.com/snyk/snyk
Trivy: 它是是一款简单而全面的扫描仪,用于检查容器映像、文件系统和Git存储库中的漏洞,以及配置问题,它已经被集成到Harbor项目之中为镜像提供安全扫描服务。其主要功能是可以检测操作系统软件包(Alpine、RHEL、CentOS等)和特定语言软件包(Bundler、Composer、npm、Thread等)的漏洞,此外Trivy会将基础设施扫描为代码(IaC)文件,如Terraform、Dockerfile和Kubernetes,以检测可能导致部署面临攻击风险的潜在配置问题,其优点是安装简单,开箱即用。项目地址: https://github.com/aquasecurity/trivy/
1.Snyk扫描引擎安装与使用
步骤01.参考Snyk在github项目地址,我们可以快速进行了解和使用。
# docker已经集成安装snyk引擎,所以已安装docker的用户无法再次安装snyk。
docker scan
注意: Docker Scan依赖于对第三方提供商Snyk的访问,每月只免费提供10次扫描的限制,如超过此限额使用则可以执行docker scan --login命令进行登陆或者注册Snyk。
步骤02.在本地使用docker scan命令的好处是,我们可以在推送任何代码之前在本地捕获安全漏洞。
# 1.使用前需要先登陆docker.io
$ docker login
# 2.snyk 扫描引擎版本查看
$ docker scan --version
Version: v0.12.0
Git commit: 1074dd0
Provider: Snyk (1.790.0 (standalone))
步骤03.构建镜像以及扫描构建的镜像存在的安全问题,扫描结果如下图所示。
# 1.构建并标记镜像
docker build -t weiyigeek/go-webserver:v2.0 .
# 2.扫描镜像
docker scan weiyigeek/go-webserver:v2.0
# Mongo镜像进行扫描
docker scan mongo:latest

Tips: Snyk 和 Docker 宣布达成合作关系,以帮助开发人员安全地构建和使用容器以及开源,Docker在2.3.6.0或更高版本中包括了一个名为的新命令docker scan。运行docker scan命令时将根据Snyk安全引擎扫描本地镜像,从而使您可以安全查看本地Dockerfile和本地镜像。
2.Trivy 扫描引擎安装与使用
步骤 01.快速进行 Trivy 镜像安全扫描仪安装,主要有两种方式一种是常规的二进制文件方式,第二种则是通过带有该工具的镜像。
# 方式1
# 1.从Github 的releases中拉取 Trivy 最新的二进制执行文件。
wget https://github.com/aquasecurity/trivy/releases/download/v0.23.0/trivy_0.23.0_Linux-64bit.tar.gz -P /tmp
# 2.解压并设置快捷执行方式。
tar -zxvf /tmp/trivy_0.23.0_Linux-64bit.tar.gz -C /usr/local/bin
# 3.执行权限赋予给trivy二进制文件
chmod +x /usr/local/bin/trivy
# 4.查看trivy版本
trivy --version
Version: 0.23.0
# 方式2.拉取带有Trivy 执行环境的镜像
docker pull aquasec/trivy:0.23.0
docker pull ghcr.io/aquasecurity/trivy:0.23.0
docker pull public.ecr.aws/aquasecurity/trivy:0.23.0
步骤 02.我们可以执行扫描镜像命令,注意首次扫描镜像会自动更新下载漏洞库,反馈的扫描结果,如下图所示、
# 首次扫描可能需要更新漏洞库
$ trivy image weiyigeek/go-webserver:v1.0
2022-02-24T11:58:30.219+0800 INFO Need to update DB
2022-02-24T11:58:30.219+0800 INFO Downloading DB...
$ trivy image weiyigeek/go-webserver:v2.0
步骤 03.除此之外,我们可以利用其扫描本地目录中的Dockerfile镜像配置文件和远程仓库中的Dockerfile文件扫描,它会针对我们编写的Dockerfile进行检查并给出合理的处理建议,例如。
# 此处,利用多阶段构建的Dockerfile文件进行演示trivy针对构建文件内容扫描。
$ ls
Dockerfile webserver.go
# 扫描本地配置文件
$ trivy config Dockerfile
# 扫描git远程仓库配置文件
$ trivy repo https://github.com/knqyf263/trivy-ci-test
# 扫描本地文件系统以查找特定于语言的依赖项和配置文件
$ trivy fs /path/to/project

温馨提示: 除了synk和Trivy,其实还有多种工具可以执行镜像安全扫描,例如由Anchore Inc.开发的Anchore和 由Quay开发的Clair等
描述: 运维人员在构建包含应用的容器镜像时可能需要用到一些机密信息,例如从私有仓库拉取代码所需的 SSH 私钥,或者安全私有软件包所需的令牌。如果 Dockerfile 中包含复制机密信息的命令,构建镜像时,这行命令对应的中间容器会被缓存,导致机密数据也被缓存,有可能造成机密信息泄漏。因此像令牌和密钥这样的机密信息必须保存在 Dockerfile 之外,所以为了避免机密信息的泄露我们可以采用使用多阶段构建、使用 Docker 的 secret 管理功能、避免无意中复制机密信息等三种方式进行联合使用。
安全实践
1.使用多阶段构建: 利用 Docker 的多阶段构建功能,用一个中间镜像层获取和管理机密信息,然后清除中间镜像,这样在应用镜像构建阶段不涉及敏感数据。
# 例子使用代码将机密信息添加到中间层
FROM: ubuntu as intermediate
WORKDIR /app
COPY secret/key /tmp/
RUN scp -i /tmp/key build@weiyigeek.top/files .
FROM ubuntu
WORKDIR /app
COPY --from intermediate /app . 2.使用 Docker 的 secret 管理功能:加载敏感信息文件且不会缓存这些信息
# syntax = docker/dockerfile:1.0-experimental
FROM alpine
# shows secret from default secret location
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecre
# shows secret from custom secret location
RUN --mount=type=secret,id=mysecret,dst=/foobar cat /foobar 3.避免无意中复制机密信息: 在构建镜像时通常都会采用COPY . .的指令将整个构建上下文文件夹复制到 Docker 镜像,但也有可能把敏感文件也复制进去了。所以如果文件夹中有敏感文件,要么先移除这些文件,要么将这些文件包含在.dockerignore中复制时会忽略这些文件:
cat .dockerignore <<'EOF'
private.key
appsetting.config
appsettings.json
EOF
描述: 每个 Docker 镜像可以有多个标签(tag),代表该镜像的不同变体。最常见的标签是 latest 表示这是该镜像的最新版本。镜像标签是可更改的,也就是说镜像的作者可以多次发布相同标签的镜像。 因此,即使你的 Dockerfile 明确指定了使用的基础镜像及其标签,这次镜像构建和下次镜像构建仍然可能用到了不同的基础镜像。
安全实践:
1.优先选用最详细的镜像标签。例如,镜像有8、:8.0.1 和 :8.0.1-alpine 等标签,选择最后这个,因为它提供了最详细的信息,不要建议使用像 latest 这样过于泛泛的标签。
2.发布者有可能删除镜像的某个标签。应该提前把该镜像复制到私有镜像中心或者公有镜像中心的私人账户下。
3.使用比签名更具体的 SHA256 引用指明要使用的镜像。好处是能保证每次拉取都是相同内容的镜像,缺点是如果镜像发生改变需要及时的更新SHA256 引用(散列值)。
描述: 在实际的生产场景中我们常常需要对基础的镜像根据实际需求编写 dockerfile 进行 build 重构建, 除了前面提到过的最小的基础镜像外,还需要注意下面的几个方面囊括了LABEL、COPY与ADD等指令
安全实践:
(1) 使用 LABEL 指定镜像元数据: 镜像元数据有助于用户更好地理解和使用该镜像并且除了镜像的维护者信息,添加其他你认为重要的元数据,包括提交对象的散列值、相关构建的链接、质量状态(通过所有测试了吗?)、源代码链接、SECURITY.TXT 文件的位置等。
MAINTAINER
LABEL maintainer="test@weiyigeek.top"
LABEL securitytxt="https://www.example.com/.well-known/security.txt"
(2) 尽量使用COPY而非ADD指令: 从而为了避免可能导致的安全问题请记住 COPY 和 ADD 的不同
COPY - 将本地文件或者目录(递归)复制到容器镜像中的目标目录,复制来源和目标都必须明确指定。
ADD - 1) 如果复制来源是本地压缩文件,ADD 将把该文件解压缩到目标目录; 2) ADD 也可以将远程 URL 指定的文件下载到目标目录。
Q: 使用COPY指令相比较于ADD指令的优点及安全性?
使用 ADD 从远程 URL 下载文件,存在中间人攻击的风险,文件内容有可能因此被篡改。必须确保远程 URL 必须是安全的 TLS 链接,校验远程 URL 的来源和身份。译者注:实际上,官方文档并不鼓励使用 ADD 添加远程文件。 如果复制的是本地压缩文件,ADD 自动将它解压缩到目标目录,这有可能触发 zip 炸弹或者 zip 任意文件覆盖漏洞。 相比较而言使用 COPY 复制文件或目录,会创建一个缓存的中间镜像层,优化镜像构建的速度。
(3) 善用RUN指令: 在dockerfile之中我们常常能看见RUN指令的身影, 所以为了减少镜像构建时的Layer的数量, 我们可以通过将所有RUN命令合并成为一条命令。
# 示例1:添加用户并加入到用户组里并创建一个app目录设置weiyigeek为所属者我们只需要一个RUN指令
RUN groupadd weiyigeek && \
useradd weiyigeek -r -s /bin/false -g weiyigeek &&
mkdir /app && \
chown weiyigeek:weiyigeek -R /app
# 示例2
# Bad, Creates 4 layers
RUN yum --disablerepo=* --enablerepo="epel"
RUN yum update
RUN yum install -y httpd
RUN yum clean all -y
# Good, creates only 1 layer
RUN yum --disablerepo=* --enablerepo="epel" && \
yum update && \
yum install -y httpd && \
yum clean all -y (4) 缓存以加快构建速度: 镜像的构建时间大都花在系统软件包和应用程序依赖包的下载和安装。但是,这些通常不会经常变更,因此推荐进行缓存。
export DOCKER_BUILDKIT=1
# 使用命令--mount选项RUN来选择缓存目录
FROM python:3.8
COPY pom.xml ./pom.xml # Java
RUN --mount=type=cache,target=/root/.m2 mvn dependency:go-offline -B # Java
FROM openjdk:15.0.1
COPY requirements.txt ./requirements.txt # Python
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt # Python
# 镜像构建
docker build -t python:3.8-test . (5) 使用多阶段构建小而安全的镜像: 使用 Dockerfile 构建应用容器镜像时,会生成很多只是构建时需要的镜像层,包括编译时所需的开发工具和库,运行单元测试所需的依赖、临时文件、机密信息等等, 如果保留这些镜像层,不仅会增加镜像的大小,影响镜像下载速度,而且会因为安装更多软件包而面临更大的攻击危险。所以在实践环境中我们常常将软件构建阶段所需的镜像存放到内部的镜像仓库之中,并不会将它用来作为运行应用的环境。
Docker 为我们提供了多阶段构建的功能,允许在构建过程中使用多个临时镜像,只保留最后一个镜像,因此用户可以得到两个镜像:
第一个镜像——非常大的镜像,包含了构建应用和运行测试所需的所有依赖;
第二个镜像——非常小的镜像,只包含运行应用所需的极少数依赖。
# 第一阶段
FROM golang as builder
WORKDIR /go/src/app
COPY . .
# Static build is required so that we can safely use 'scratch' base image
RUN CGO_ENABLED=0 go install -ldflags '-extldflags "-static"'
# 第二阶段
FROM scratch
COPY --from=builder /go/bin/app /app
ENTRYPOINT ["/app"]
描述: 使用静态分析工具,能够避免常见的错误,建立工程师自动遵循的最佳实践指南,在集成开发环境(IDE)中使用 hadolint 更好。
例如,使用hadolint工具分析 Dockerfile 并列出不符合最佳实践规则的地方,或者在 VS Code 安装的 hadolint 扩展后,编写 Dockerfile 时边写边检查既快又好。
docker run --rm -i hadolint/hadolint < Dockerfile


至此本节完毕,敬请期待下一小节内容。
Docker容器技术入门实践系列历史已发布文章(点击即可进入):
4.我在B站学云原生之Docker容器数据持久化介绍与实践
6.我在B站学云原生之Docker容器Registry私有镜像仓库搭建实践
7.我在B站学云原生之Docker容器Dockerfile镜像构建浅析与实践
8.我在B站学云原生之Docker容器镜像构建最佳实践浅析
9.我在B站学云原生之Docker容器优化镜像体积缩小技巧实践
11.我在B站学云原生之Docker容器编排工具docker-compose安装使用实践
13.我在B站学云原生之Docker容器镜像构建存储原理浅析与实践
14.我在B站学云原生之Docker容器Registry私有镜像仓库安全配置与GC回收实践

欢迎各位志同道合的朋友一起学习交流,如文章有误请在下方留下您宝贵的经验知识,个人邮箱地址【master#weiyigeek.top】或者 个人公众号【WeiyiGeek】联系我。

更多文章来源于【WeiyiGeek Blog - 为了能到远方,脚下的每一步都不能少】 个人博客。
博客地址: https://weiyigeek.top

人生为棋,我愿为卒,行动虽慢,可谁又曾看见我后退一步
专栏书写不易,如果您觉得这个专栏还不错的,请给这篇专栏【点个赞、投个币、收个藏、关个注,转个发】,这将对我的肯定,谢谢!。
echo "【点个赞】,动动你那粗壮的拇指或者芊芊玉手,亲!"
printf("%s", "【投个币】,万水千山总是情,投个硬币行不行,亲!")
fmt.Printf("【收个藏】,阅后即焚不吃灰,亲!")
System.out.println("【关个注】,后续浏览查看不迷路哟,亲!")
console.info("【转个发】,让更多的志同道合的朋友一起学习交流,亲!")