为什么你需要更小巧的容器?


【编者的话】为你的容器瘦身吧!更小的容器不仅仅意味着更快的构建和更小的磁盘和网络利用率,它们还意味着更安全。

这篇文章出人意料地登上了黑客新闻的头版,并在那里引发了一场富有成效的讨论。我将试着总结一下主要的要点:

  • 漏洞扫描器往往有很多误报
    • 一些报告的发现已经可以在上游和后端修复
    • 有些可能完全不相关,因为它们特定于某些深奥的架构

  • 在镜像仓库(例如Docker Hub)中,官方基础镜像从不(或很少)更新。
  • 随着容器使用的增加,为操作系统打补丁的负担实际上从管理员和操作人员转移到了开发人员身上

  • 但并不是每个开发者都意识到这一点
    • 有些人建议在每个Dockerfile的开头添加RUN apt-get update && apt-get -y upgrade,我尝试了一下,在完全成熟的Debian 10发行版中,它提供了非常小的效果
    • 但其他人反驳说,这会导致不可复制的构建,以及由于反向端口改变依赖的默认行为而导致的潜在风险
    • 这导致了一个公平的控制源存储库的建议
    • 当然,这会让事情变得更复杂
    • 这就是为什么最典型的解决方案似乎是简单地,忽略了这个问题

  • 尽管扫描结果很好,但Alpine镜像并不总是很好
    • 因为据报道musl libcglibc慢,并不是每个依赖库都为这个平台提供构建


以下是原文。

我最近在破解容器时注意到,Docker开始在构建输出中突出Docker扫描命令。我已经忽略它的存在有一段时间了,所以是时候尝试一下了。

扫描官方Python镜像

Docker扫描命令使用一个第三方工具,称为Snyk Container。显然,这是某种漏洞扫描器。所以,我决定,主要是为了好玩,扫描我的一个镜像。这是一件非常基本的事情:
# latest stable at the time
FROM python:3.9

RUN pip install Flask

COPY server.py server.py

ENV FLASK_APP=server.py
ENV FLASK_RUN_PORT=5000
ENV FLASK_RUN_HOST=0.0.0.0

EXPOSE 5000

CMD ["flask", "run"]

我运行docker build -t python-flask,然后容器扫描python-flask。令我非常惊讶的是,产生大量的输出!下面是一段摘录:
Testing python-flask...

✗ Low severity vulnerability found in unbound/libunbound8
Description: Improper Input Validation
Info: https://snyk.io/vuln/SNYK-DEBIAN10-UNBOUND-534899
Introduced through: mysql-defaults/default-libmysqlclient-dev@1.0.5
From: mysql-defaults/default-libmysqlclient-dev@1.0.5 > mariadb-10.3/libmariadb-dev-compat@1:10.3.27-0+deb10u1 > mariadb-10.3/libmariadb-dev@1:10.3.27-0+deb10u1 > gnutls28/libgnutls28-dev@3.6.7-4+deb10u6 > gnutls28/libgnutls-dane0@3.6.7-4+deb10u6 > unbound/libunbound8@1.9.0-2+deb10u2

✗ Low severity vulnerability found in tiff/libtiff5
Description: Out-of-Bounds
Info: https://snyk.io/vuln/SNYK-DEBIAN10-TIFF-1079067
Introduced through: imagemagick@8:6.9.10.23+dfsg-2.1+deb10u1, imagemagick/libmagickcore-dev@8:6.9.10.23+dfsg-2.1+deb10u1
From: imagemagick@8:6.9.10.23+dfsg-2.1+deb10u1 > imagemagick/imagemagick-6.q16@8:6.9.10.23+dfsg-2.1+deb10u1 > imagemagick/libmagickcore-6.q16-6@8:6.9.10.23+dfsg-2.1+deb10u1 > tiff/libtiff5@4.1.0+git191117-2~deb10u2
From: imagemagick/libmagickcore-dev@8:6.9.10.23+dfsg-2.1+deb10u1 > imagemagick/libmagickcore-6.q16-dev@8:6.9.10.23+dfsg-2.1+deb10u1 > tiff/libtiff-dev@4.1.0+git191117-2~deb10u2 > tiff/libtiff5@4.1.0+git191117-2~deb10u2
From: imagemagick/libmagickcore-dev@8:6.9.10.23+dfsg-2.1+deb10u1 > imagemagick/libmagickcore-6.q16-dev@8:6.9.10.23+dfsg-2.1+deb10u1 > tiff/libtiff-dev@4.1.0+git191117-2~deb10u2 > tiff/libtiffxx5@4.1.0+git191117-2~deb10u2 > tiff/libtiff5@4.1.0+git191117-2~deb10u2
and 3 more...

...

✗ High severity vulnerability found in gcc-8
Description: Insufficient Entropy
Info: https://snyk.io/vuln/SNYK-DEBIAN10-GCC8-469413
Introduced through: gcc-defaults/g++@4:8.3.0-1, libtool@2.4.6-9, imagemagick@8:6.9.10.23+dfsg-2.1+deb10u1, meta-common-packages@meta
From: gcc-defaults/g++@4:8.3.0-1 > gcc-8@8.3.0-6
From: libtool@2.4.6-9 > gcc-8@8.3.0-6
From: gcc-defaults/g++@4:8.3.0-1 > gcc-8/g++-8@8.3.0-6 > gcc-8@8.3.0-6
and 23 more...

✗ High severity vulnerability found in djvulibre/libdjvulibre21
Description: NULL Pointer Dereference
Info: https://snyk.io/vuln/SNYK-DEBIAN10-DJVULIBRE-481572
Introduced through: imagemagick/libmagickcore-dev@8:6.9.10.23+dfsg-2.1+deb10u1
From: imagemagick/libmagickcore-dev@8:6.9.10.23+dfsg-2.1+deb10u1 > imagemagick/libmagickcore-6.q16-dev@8:6.9.10.23+dfsg-2.1+deb10u1 > djvulibre/libdjvulibre-dev@3.5.27.1-10 > djvulibre/libdjvulibre21@3.5.27.1-10
From: imagemagick/libmagickcore-dev@8:6.9.10.23+dfsg-2.1+deb10u1 > imagemagick/libmagickcore-6.q16-dev@8:6.9.10.23+dfsg-2.1+deb10u1 > imagemagick/libmagickcore-6.q16-6-extra@8:6.9.10.23+dfsg-2.1+deb10u1 > djvulibre/libdjvulibre21@3.5.27.1-10
From: imagemagick/libmagickcore-dev@8:6.9.10.23+dfsg-2.1+deb10u1 > imagemagick/libmagickcore-6.q16-dev@8:6.9.10.23+dfsg-2.1+deb10u1 > djvulibre/libdjvulibre-dev@3.5.27.1-10
and 1 more...

✗ High severity vulnerability found in bluez/libbluetooth3
Description: Double Free
Info: https://snyk.io/vuln/SNYK-DEBIAN10-BLUEZ-1018718
Introduced through: bluez/libbluetooth-dev@5.50-1.2~deb10u1
From: bluez/libbluetooth-dev@5.50-1.2~deb10u1 > bluez/libbluetooth3@5.50-1.2~deb10u1
From: bluez/libbluetooth-dev@5.50-1.2~deb10u1



Package manager:   deb
Project name:      docker-image|python-flask
Docker image:      python-flask
Platform:          linux/amd64

Tested 431 dependencies for known vulnerabilities, found 358 vulnerabilities.

For more free scans that keep your images secure, sign up to Snyk at https://dockr.ly/3ePqVcp

共发现漏洞358个,其中高级别54个,中级别48个。

仔细查看扫描报告后,我发现的大部分漏洞可能与Debian有关(参见信息:https://snyk.io/vuln/SNYK-DEBIAN10-…稍高一点,报告中很多项目都有)。

显然,python:3.9镜像是基于成熟的Debian 10发行版。
1.png

114MB的潜在安全漏洞

老实说,这是令人兴奋的!我知道容器越厚,潜在的攻击面就越高。但无论如何,我没想到会有这么大的规模。

好的,让我们试着把镜像瘦身……
FROM python:3.9-slim

RUN pip install Flask

COPY server.py server.py

ENV FLASK_APP=server.py
ENV FLASK_RUN_PORT=5000
ENV FLASK_RUN_HOST=0.0.0.0

EXPOSE 5000

CMD ["flask", "run"]

扫描python:3.9-slim的镜像给了我更好的结果:
Package manager:   deb
Project name:      docker-image|python-flask-slim
Docker image:      python-flask-slim
Platform:          linux/amd64

Tested 94 dependencies for known vulnerabilities, found 69 vulnerabilities.

共发现69个漏洞,其中高级别14个,中级别8个。
2.png

基础镜像每兆字节有一个漏洞……

我们能做得更好吗?让我们尝试python: 3.9-alpine:
FROM python:3.9-alpine

RUN pip install Flask

COPY server.py server.py

ENV FLASK_APP=server.py
ENV FLASK_RUN_PORT=5000
ENV FLASK_RUN_HOST=0.0.0.0

EXPOSE 5000

CMD ["flask", "run"]

终于,0已知的漏洞!
Package manager:   apk
Project name:      docker-image|python-flask-alpine
Docker image:      python-flask-alpine
Platform:          linux/amd64

✓ Tested 37 dependencies for known issues, no vulnerable paths found.

3.png

5.6 MB Alpine!

扫描无发布的Python镜像

另一个可能解决膨胀容器问题的方案是谷歌所谓的“非发行版”Docker镜像。项目描述说它是“语言聚焦Docker镜像,减去操作系统”。尽管这在Python的情况下很难实现,因为它的标准库依赖于一些高级操作系统功能。

我花了一段时间才想出一个可以工作的非发行版Python镜像。大多数示例都展示了如何使用一些简单的脚本。然而,安装Flask(或任何其他依赖项)比我预期的要困难得多。由于gcr,Python的无发行版容器需要多阶段的构建过程。gcr.io/distroless/python3镜像既没有PIP,也没有easy_install。首先,我试图利用构建镜像中的虚拟环境,然后将其复制到运行时镜像中,并修改PATH变量。但它并没有很好地工作,因为基本的非发行版镜像在文件系统布局上做了一些自以为是的布局:
4.png

预料之中的是,Linux distr仍然存在,但是它比debian还要薄

所以,我最终得到了以下Dockerfile,允许我在非发行版的Python镜像中安装Flask:
# Build image
FROM python:3.7-slim AS build-env

RUN python -m pip install Flask

# Runtime image
FROM gcr.io/distroless/python3

COPY --from=build-env /usr/local/bin/flask /usr/local/bin/flask
COPY --from=build-env /usr/local/lib/python3.7/site-packages /usr/local/lib/python3.7/site-packages

WORKDIR /app

COPY server.py server.py

# Important line!
ENV PYTHONPATH=/usr/local/lib/python3.7/site-packages

ENV FLASK_APP=server.py
ENV FLASK_RUN_PORT=5000
ENV FLASK_RUN_HOST=0.0.0.0

EXPOSE 5000

CMD ["/usr/local/bin/flask", "run"]

扫描它与Docker扫描输出以下结果:
Package manager:   deb
Project name:      docker-image|python-flask-distroless
Docker image:      python-flask-distroless
Platform:          linux/amd64

Tested 25 dependencies for known vulnerabilities, found 37 vulnerabilities.

所以,只有37个漏洞:6个严重程度高,8个中等。听起来好像比原始的python:3.9镜像减少了90%!它甚至比python:3.9-slim中的漏洞更少。

因此,我们的基于Alpine的Python镜像是一个赢家!

扫描Go镜像

我喜欢完全从零开始构建容器镜像的想法,避免将任何distr内容放入其中。但为此,你需要一种对静态构建有深刻支持的语言,例如Go:
FROM scratch

COPY hello /
CMD ["/hello"]

得出以下结果……没错,这就是它!
Testing go-scratch...

Package manager:   linux
Project name:      docker-image|go-scratch
Docker image:      go-scratch
Platform:          linux/amd64

✓ Tested go-scratch for known vulnerabilities, no vulnerable paths found.

没有distr并不意味着没有漏洞。但它确实降低了攻击的可能性。
5.png

相反的结论

根据我的经验,膨胀的镜像通常是使用默认的From bloated_base,或者因为人们为了将来的调试/故障排除故意将额外的工具放入镜像的结果。

第一种可能是Docker过去大规模营销活动的结果。为了普及容器,你需要给人们一些看起来很酷和方便的东西。而且能够在一秒钟内从你的开发机器上启动一个成熟的Linux distr(某种程度上),即使在今天看起来也很酷。一些本地的修补和/或实验从docker run -it ubuntu中获益良多。然而,我希望它从来没有被用于生产。但是从一个成熟的Debian,CentOS,或者Ubuntu开始,Dockerfile的例子实在是太多了,至少要避免将其中一些渗透到我们的生产环境中。

第二个原因看起来更合理。但我有一种感觉,这只是第一眼看到的感觉。理想情况下,应该有另一种方法来调试你的容器化服务。这种方法不需要将所有潜在需要的工具打包到一个容器中。最近添加的kubectl调试功能很好地证明了这一假设。它允许将临时容器注入运行中的Pod中,这样的容器可以拥有调试所需的所有东西。

更小的容器不仅仅意味着更快的构建和更小的磁盘和网络利用率,它们还意味着更安全。

原文链接:The Need For Slimmer Containers

译者:Mr.lzc,软件工程师、DevOpsDays、HDZ深圳核心组织者,目前供职于华为,从事云计算工作,专注于K8s、微服务领域。

0 个评论

要回复文章请先登录注册