乐趣区

关于容器:云原生时代下的容器镜像安全上

大家好,我是张晋涛。

Kubernetes 作为云原生的基石,为咱们带来了极大的便利性,越来越多的公司也都将 Kubernetes 利用到了生产环境中。然而,在享受其带来的便利性的同时,咱们也须要关注其中的一些安全隐患。

本篇,我将为你重点介绍 容器镜像平安 相干的内容。

通常状况下,咱们提到 容器镜像平安,次要是指以下两个方面:

  • 镜像本身内容的平安;
  • 镜像散发过程的平安;

镜像本身内容的平安

要聊镜像本身内容的平安,那咱们就须要晓得镜像到底是什么,以及它其中的内容是什么。

镜像是什么

咱们以 debian镜像为例,pull 最新的镜像,并将其保留为 tar 文件,之后进行解压:

➜  ~ mkdir -p debian-image
➜  ~ docker pull debian
Using default tag: latest
latest: Pulling from library/debian
647acf3d48c2: Pull complete 
Digest: sha256:e8c184b56a94db0947a9d51ec68f42ef5584442f20547fa3bd8cbd00203b2e7a
Status: Downloaded newer image for debian:latest
docker.io/library/debian:latest
➜  ~ docker image save -o debian-image/debian.tar debian
➜  ~ ls debian-image 
debian.tar
➜  ~ tar -C debian-image -xf debian-image/debian.tar 
➜  ~ tree -I debian.tar debian-image 
debian-image
├── 827e5611389abf13dad1057e92f163b771febc0bcdb19fa2d634a7eb0641e0cc.json
├── b331057b5d32f835ac4b051f6a08af6e9beedb99ec9aba5c029105abe360bbda
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── manifest.json
└── repositories

1 directory, 6 files

解压实现后,咱们看到它是一堆 json 文件和 layer.tar文件的组合,咱们再次对其中的 layer.tar进行解压:

➜  ~ tar -C debian-image/b331057b5d32f835ac4b051f6a08af6e9beedb99ec9aba5c029105abe360bbda -xf debian-image/b331057b5d32f835ac4b051f6a08af6e9beedb99ec9aba5c029105abe360bbda/layer.tar
➜  ~ tree -I 'layer.tar|json|VERSION'  -L 1 debian-image/b331057b5d32f835ac4b051f6a08af6e9beedb99ec9aba5c029105abe360bbda
debian-image/b331057b5d32f835ac4b051f6a08af6e9beedb99ec9aba5c029105abe360bbda
├── bin
├── boot
├── dev
├── etc
├── home
├── lib
├── lib64
├── media
├── mnt
├── opt
├── proc
├── root
├── run
├── sbin
├── srv
├── sys
├── tmp
├── usr
└── var

19 directories, 0 files

解压后的目录构造想必你曾经很相熟了,是的,这是 rootfs的目录构造。

如果咱们应用的是本人构建的一些利用镜像的话,通过几次解压,你也会在其中找到应用程序绝对应的文件。

镜像本身内容平安如何保障

后面咱们曾经看到了容器镜像就是 rootfs和应用程序,以及一些配置文件的组合。所以要保障它本身内容的安全性,次要从以下几个方面来思考:

rootfs平安

对应到咱们的理论状况,rootfs通常是由咱们应用的根底(零碎)镜像提供的,或者也能够认为是咱们构建镜像时,DockerfileFROM字段所配置的镜像提供的。

在这个方面想要做到安全性就须要咱们:

  • 应用可信起源的镜像,比方 Docker 官网保护的镜像;
  • 对根底镜像继续的进行破绽扫描和降级;
  • 也能够思考应用 Distroless镜像,这样也能够肯定水平上免受攻打;

应用程序

应用程序其实是咱们本人提供的,在这方面想要做到安全性,那么就须要咱们:

  • 继续的进行软件的破绽扫描;
  • 对依赖及时的进行更新;
  • 能够思考从 SDL(Security Development Lifecycle)过渡到 DevSecOps;

配置文件

镜像中所蕴含的那些配置文件是由镜像构建工具所提供的,个别状况下,只有咱们保障应用的镜像构建工具未被篡改或者留下什么破绽,那么这里基本上不会有什么大的问题。

综合来看,咱们能够间接应用相似 Trivy 或者 Anchore Engine 等镜像破绽扫描工具来帮忙咱们保障镜像内容的平安。此外,一些镜像仓库,比方 Harbor 等都曾经内置了镜像平安的扫描工具,或者能够应用 docker scan命令进行镜像的平安扫描。

镜像散发平安

镜像如何散发

咱们首先来看看,容器镜像是怎么样从构建到部署到咱们的 Kubernetes 环境中的。

图 1,容器镜像自创立到公布部署的简要过程示意图

开发者在编写完代码后,推送代码到代码仓库。由此来触发 CI 进行构建,在此过程中会进行镜像的构建,以及将镜像推送至镜像仓库中。

在 CD 的环节中,则会应用镜像仓库中的镜像,部署至指标 Kubernetes 集群中。

那么在此过程中,攻击者如何进行攻打呢?

镜像散发中的平安问题

图 2,镜像散发部署平安示例

如图,在镜像散发部署的环节中其上游是镜像仓库,上游是 Kubernetes 集群。对于镜像仓库而言,即便是内网的自建环境,因为咱们的观点已从基于边界的平安转变为零信赖平安,所以,咱们对立以公共仓库为例来解说。

攻击者能够通过一些伎俩进行劫持、替换成歹意的镜像,包含间接攻打镜像仓库等。

要保障部署到 Kubernetes 集群中镜像的安全性起源以及完整性,其实是须要在两个次要的环节上进行:

  • 构建镜像时进行镜像的签名;
  • 镜像散发部署时进行签名的校验;(下一篇内容持续)

咱们来别离看一下。

镜像的标签和摘要

咱们通常在应用容器镜像时有两种抉择:

  • 标签,比方 alpine:3.14.3
  • 摘要,比方 alpine@sha256:635f0aa53d99017b38d1a0aa5b2082f7812b03e3cdb299103fe77b5c8a07f1d2

大多数场景下,咱们会间接应用标签,因为它的可读性更好。然而镜像内容可能会随着工夫的推移而变动,因为咱们可能会为不同内容的镜像应用雷同的标签,最常见的就是 :latest标签,每次新版本公布的时候,新版本的镜像都会持续沿用 :latest标签,但其中的应用程序版本曾经降级到了最新。

应用摘要的次要弊病是它的可读性不好,然而,每个镜像的摘要都是惟一的,摘要是镜像内容的 SHA256 的哈希值。所以咱们能够通过摘要来保障镜像的唯一性。

通过以下示例能够间接看到标签和摘要信息:

➜  ~ docker pull alpine:3.14.3                                                                          
3.14.3: Pulling from library/alpine
Digest: sha256:635f0aa53d99017b38d1a0aa5b2082f7812b03e3cdb299103fe77b5c8a07f1d2
Status: Image is up to date for alpine:3.14.3
docker.io/library/alpine:3.14.3
➜  ~ docker image inspect alpine:3.14.3 | jq -r '.[] | {RepoTags: .RepoTags, RepoDigests: .RepoDigests}'
{
  "RepoTags": ["alpine:3.14.3"],
  "RepoDigests": ["alpine@sha256:635f0aa53d99017b38d1a0aa5b2082f7812b03e3cdb299103fe77b5c8a07f1d2"]
}

那么如何来保障镜像的正确性 / 安全性呢?这就是镜像签名解决的次要问题了。

镜像签名解决方案

数字签名是一种家喻户晓的办法,用于保护在网络上传输的任何数据的完整性。对于容器镜像签名,咱们有几种比拟通用的计划。

Docker Content Trust (DCT)

在传输个别文件时,可能有过相似的经验,比方因为网络起因导致下载的文件不残缺;或是遭逢中间人的攻打导致文件被篡改、替换等。

镜像在散发过程中其实也可能会遇到相似的问题,这就是此处咱们要探讨的重点,也就是 Docker Content Trust(DCT)次要解决的问题。

Docker Content Trust 应用数字签名,并且容许客户端或运行时验证特定镜像标签的完整性和发布者。对于应用而言也就是 docker trust 命令所提供的相干性能。留神:这须要 Docker CE 17.12 及以上版本。

后面咱们提到了,镜像记录能够有一些标签,格局如下:

[REGISTRY_HOST[:REGISTRY_PORT]/]REPOSITORY[:TAG]

以标签为例,DCT 会与标签的一部分相关联。每个镜像仓库都有一组密钥,镜像发布者应用这些密钥对镜像标签进行签名。(镜像发布者能够自行决定要签订哪些标签)镜像仓库能够同时蕴含多个带有已签名标签和未签名标签的镜像。

这里须要阐明下,如果镜像发布者先推送签名的 latest 镜像,再推送未签名的 latest 镜像,那么后一个镜像不会影响前一个镜像的内容(区别于上文中标签笼罩的中央)。

图 4,DCT 镜像签名示例(图中简略了登录镜像仓库的认证过程)

在生产中,咱们能够启用 DCT 确保应用的镜像都已签名。如果启用了 DCT,那么只能对受信赖的镜像(已签名并可验证的镜像)进行拉取、运行或构建。

启用 DCT 有点像对镜像仓库利用“过滤器”,即,只能看到已签名的镜像标签,看不到未签名的镜像标签。如果客户端没有启用 DCT,那么它能够看到所有的镜像。

这里咱们来疾速的看一下 DCT 的工作过程

它对镜像标签的信赖是通过应用签名密钥来治理的。在咱们首次开启 DCT 并应用的时候会创立密钥集。一个密钥集由以下几类密钥组成:

  • 一个离线密钥 offline key,它是镜像标签 DCT 的根(失落根密钥很难复原
  • 对标签进行签名的存储库或标记密钥 tag key
  • 服务器治理的密钥,例如工夫戳密钥

图 5,镜像签名密钥示例

刚从咱们提到客户端应用 DCT 也就是咱们的 docker trust命令,它是建设在 Notary v1 上的。默认状况下,Docker 客户端中禁用 DCT。要启用须要设置 DOCKER_CONTENT_TRUST=1 环境变量。

成果如下:

➜  ~ DOCKER_CONTENT_TRUST=1 docker pull alpine:3.12
Pull (1 of 1): alpine:3.12@sha256:c0e9560cda118f9ec63ddefb4a173a2b2a0347082d7dff7dc14272e7841a5b5a
docker.io/library/alpine@sha256:c0e9560cda118f9ec63ddefb4a173a2b2a0347082d7dff7dc14272e7841a5b5a: Pulling from library/alpine
188c0c94c7c5: Already exists        
Digest: sha256:c0e9560cda118f9ec63ddefb4a173a2b2a0347082d7dff7dc14272e7841a5b5a
Status: Downloaded newer image for alpine@sha256:c0e9560cda118f9ec63ddefb4a173a2b2a0347082d7dff7dc14272e7841a5b5a
Tagging alpine@sha256:c0e9560cda118f9ec63ddefb4a173a2b2a0347082d7dff7dc14272e7841a5b5a as alpine:3.12 
docker.io/library/alpine:3.12

Notary v1

后面咱们提到 DCT 是基于 Notary v1 实现的,不过这不是咱们本篇的重点,所以这里仅对 Notary v1 做个简略的介绍。Notary 我的项目地址:https://github.com/notaryproj…

图 6,Notary 客户端、服务器和签名相干的交互流程

过程 1 – 身份认证,任何没有令牌的连贯将被重定向到受权服务器(Docker Registry v2 身份认证);

过程 2 – 客户端将通过 HTTPS 上的身份验证登录到受权服务器,获取令牌;

过程 3 – 当客户端上传新的元数据文件时,服务器会依据以前的版本查看它们是否存在抵触,并验证上传的元数据的签名、校验和和有效性;

过程 4 – 一旦所有上传的元数据都通过验证,服务器会生成工夫戳(可能还有快照),而后将它们发送给 sign 进行签名;

过程 5 – sign 从其数据库中检索加密私钥,解密密钥,并应用它们进行签名,并发送回服务器;

过程 6 – 服务器将客户端上传和服务器生成的元数据存储在 TUF 库中。生成的工夫戳和快照元数据证实客户端上传的元数据是该可信汇合的最新版本。之后,服务器会告诉客户端 – 上传胜利;

过程 7 – 客户端当初能够立刻从服务器下载最新的元数据了。在工夫戳过期的状况下,服务器将遍历整个序列,生成新的工夫戳,申请 sign 签名,将新签名的工夫戳存储在数据库中。而后,它将这个新的工夫戳连同其余存储的元数据一起发送给申请客户端;

这个我的项目因为是一个平安我的项目,尽管用处很大,但整体并不沉闷。当初正在进行 v2 版本的开发,有趣味的小伙伴欢送退出。

sigstore 和 Cosign

这里介绍另一个来自 Linux 基金会的我的项目,叫做 sigstore 它次要是为了提供一些规范的库 / 工具,便于更好的进行签名和校验。当然,目前 sigstore 曾经汇聚了包含 Cosign,Fulcio 和 Rekor 等开源我的项目,波及到镜像镜像签名校验和供应链等方面。

图 7,sigstore 简介

Cosign 是 sigstore 的工具之一,用于 OCI registry 中创立、存储和验证容器镜像签名。Cosign v1.0 已于往年下半年公布,是否能稳固用于生产环境,还有待考验。截止目前,Cosign 曾经公布了 v1.3.1 版本,具体变更请参考其 ReleaseNote:https://github.com/sigstore/c…

咱们这里看下它如何进行镜像的签名

➜  cosign cosign generate-key-pair                                                                                              
Enter password for private key: 
Enter password for private key again: 
Private key written to cosign.key
Public key written to cosign.pub
➜  cosign cosign sign --key cosign.key ghcr.io/tao12345666333/argo-cd-demo/argo-cd-demo:fa5714f419b3d11dee6ac795e38356e9c3c439cb
Enter password for private key: %   
➜  cosign cosign verify --key cosign.pub  ghcr.io/tao12345666333/argo-cd-demo/argo-cd-demo:fa5714f419b3d11dee6ac795e38356e9c3c439cb 

Verification for ghcr.io/tao12345666333/argo-cd-demo/argo-cd-demo:fa5714f419b3d11dee6ac795e38356e9c3c439cb --
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
  - Any certificates were verified against the Fulcio roots.

[{"critical":{"identity":{"docker-reference":"ghcr.io/tao12345666333/argo-cd-demo/argo-cd-demo"},"image":{"docker-manifest-digest":"sha256:768845efa2a32bc5c5d83a6f7ec668b98f5db46585dd1918afc9695a9e653d2d"},"type":"cosign container image signature"},"optional":null}]

看起来还是比较简单的。

总结

以上就是对于镜像本身内容平安,以及镜像散发平安中的镜像签名校验局部的内容。

下一篇我将为大家介绍如何在镜像散发及部署时进行签名的校验,以及如何爱护 Kubernetes 集群免受未签名或不可信起源镜像的攻打,敬请期待!


欢送订阅我的文章公众号【MoeLove】

退出移动版