对容器映像中的数据和代码保密
在过去的几年中,云行业已经发生了重大转变,从在虚拟机中部署单片应用程序到将应用程序拆分为较小的组件(微服务)并将其包装到容器中。如今,容器化的普及很大程度上是由Docker的工作驱动的。Docker是已成为容器背后主要驱动力的公司:它提供了易于使用的工具来构建和运行Docker容器,并提供了Docker容器注册表来应对发行方面的挑战。
集装箱技术的成功主要取决于集装箱生命周期各个阶段的安全性。安全问题之一是各个容器内部存在漏洞。为了识别它们,用于创建容器的DevOps管道还添加了扫描程序,这些扫描程序可以在容器中查找可能存在漏洞的软件包,并在发现所有者时提醒其所有者或技术人员。IBM Cloud上的Vulnerability Advisor是此类实用程序的一个示例。
安全性的另一方面是确保要启动的容器是您想要的容器,并且尚未对其进行修改。通过使用存储在公证人中的数字签名解决了此问题,该签名将保护容器免受任何修改。Docker Notary是存储映像签名的公共存储库的示例。客户可以使用Notary验证容器映像的签名,以确保自从使用所有者或服务技术人员密钥对容器映像进行签名以来,容器映像没有被更改。
另一个潜在的安全问题是容器隔离。 Linux运行时安全技术(例如名称空间,cgroup,Linux功能以及SELinux,AppArmor和Seccomp配置文件)有助于限制容器进程,并在运行时将容器彼此隔离。
本文解决了有关容器映像中的数据和代码隐私问题的热门企业安全问题。使用容器映像时,主要的安全目标是允许创建和分发加密的容器映像,以便仅对特定的一组收件人可用。在这种情况下,其他人可能可以访问这些映像,但是他们将无法运行它们或查看其中的敏感数据。容器加密基于现有的加密技术,例如Rivest-Shamir-Adleman(RSA)加密技术,椭圆曲线和高级加密标准(AES),也称为Rijndael,这是一种对称块加密算法。
介绍性
为了充分利用本文,您应该熟悉Linux容器和容器映像,并了解安全性基础。
有关加密和容器的相关工作
据我们所知,在加密容器映像方面没有任何工作。但是,有许多实现和产品支持通过文件系统,块设备或硬件加密进行数据保密和防盗加密。后者是使用自加密磁盘实现的。也有加密的虚拟机映像。
加密的文件系统存在于企业中的许多操作系统上,并且可以支持装入加密的分区和目录。加密文件系统甚至可以支持从加密启动驱动器启动。 Linux支持使用dm-encrypt驱动程序进行块设备加密。 ecryptfs是加密文件系统的一个示例。适用于Linux的其他文件加密解决方案开源。在Windows中,NTFS v3.0文件系统支持加密。此外,许多制造商还创建自加密光盘。对于虚拟机映像,有一种类似于加密磁盘的解决方案。开源QEMU Machine(PC)模拟器和VMware虚拟化产品支持加密的虚拟机映像。
数据加密通常旨在在系统离线时防止数据被盗。一种相关技术是使用客户端和Docker Notary服务器提供的密钥对容器映像进行签名。 Docker Notary服务器在容器映像注册表附近工作。 Docker客户端工具用户可以选择对容器映像进行签名,并通过Docker Notary将签名上传到他们的帐户。在此过程中,签名通过图像及其版本的路径名绑定到容器图像。使用哈希函数创建签名,该哈希函数是基于对图像整个内容的描述而计算的。此描述称为容器映像清单。容器图像签名技术解决了保护容器图像免遭未经授权的访问的问题,并有助于确定容器图像的来源。
结构体
Docker生态系统演变为使用开放容器倡议(OCI)组标准来标准化容器映像格式,该组现在可以控制容器运行时格式(runtime-spec)和容器映像格式(image-spec)。由于团队的工作需要扩展现有容器图像格式,因此我们确定了对标准的扩展,以支持加密图像。以下各节描述了现有的容器映像和扩展名格式。
在顶层,容器可以由JavaScript对象表示法(JSON)文档组成,该文档是图像清单的列表。例如,当多个体系结构或平台用于容器映像时,可以使用清单清单。清单清单包含指向容器清单的链接,每个链接对应于体系结构和操作系统的每种组合。例如,受支持的体系结构包括amd64,arm和ppc64le,受支持的操作系统包括Linux或Windows。以下清单中的示例显示了清单清单的示例:
mediaType字段描述了指定文档的确切格式。此清单清单允许将来扩展和选择适用于所涉及文档的解析器。
清单清单下面的级别是清单。清单也是JSON文档,包含对图像图层的引用的有序列表。这些链接包含描述图层格式的mediaType。该格式可以描述是否压缩图层,如果压缩,则如何压缩。例如,每个级别可以保存为.tar文件,其中包含在Dockerfile中运行docker build时在特定阶段添加的文件。图层通常使用压缩的.gzip文件打包,以提高存储效率。以下屏幕快照显示了清单文件的示例:
如图所示,清单和图层是通过“摘要”引用的,该摘要通常是JSON文档中的sha256哈希函数。清单和图层通常作为文件存储在文件系统上。文件名通常是内容上的哈希函数,使它们更易于查找和加载。这种哈希方法的结果是,对引用文档的细微更改都会导致引用该文档的所有文档(包括清单)发生更改。
作为我们团队项目的一部分,我们基于混合加密方案使用公共和对称密钥创建了图像加密。对称密钥用于批量数据加密(用于多级加密),公共密钥用于打包对称密钥。我们使用了三种不同的公钥加密技术:OpenPGP,JSON Web加密(JWE)和PKCS#7。
OpenPGP
OpenPGP是一种加密和签名技术,通常用于加密和签名电子邮件。开源社区还经常使用它来签名git存储库中的源代码提交(标记)。它是IETF在RFC480中定义的互联网标准,可以看作是以前的专有PGP技术的开放版本。
OpenPGP具有自己的RSA密钥格式。密钥通常存储在密钥环文件中,并且可以从简单的OpenPGP密钥文件中导入。 OpenPGP密钥环最方便的方面是可以将公共密钥链接到其所有者的电子邮件地址。您可以通过按电子邮件地址选择收件人列表,然后将其显示在这些收件人的公共密钥中,从而与消息的多个收件人一起工作。此外,围绕该技术已创建了一个信任网络:您可以找到许多用户的公共密钥,并按其电子邮件地址进行排序。例如,这些键通常用于签名git标签。
您可以使用OpenPGP加密邮件格式对发送给多个收件人的批量邮件进行加密。 OpenPGP邮件头为每个收件人包含一个块。每个块包含一个64位密钥标识符,该标识符告诉解密算法在哪里尝试解密相应的私钥。块中的加密Blob解密后,它显示可用于解密批量消息的对称密钥。每个收件人的加密公共密钥Blob都显示相同的对称密钥。
我们以类似的方式使用OpenPGP,但在这种情况下,它发送的加密消息不是一个层。相反,它包含一个JSON文档,该文档又包含一个对称密钥,该对称密钥用于加密和解密层和初始化向量。我们将此密钥称为层加密密钥(LEK),是数据加密密钥的一种形式。这种方法的优点是我们只需要一个LEK。使用LEK,我们可以为一个或多个收件人加密该图层。每个收件人(容器映像)可以具有不同的密钥类型,并且不必是OpenPGP密钥。例如,它可能是一个简单的RSA密钥。只要我们能够使用此RSA密钥对LEK进行加密,我们就可以与具有不同类型密钥的多个收件人一起使用。
JSON Web加密(JWE)
JSON Web加密,也称为JWE,是另一个IETF Internet标准,在RFC7516中定义。这是比OpenPGP更新的加密标准,因此使用了旨在满足更严格的加密要求的最新低级密码。
在更大范围内,JWE的工作方式与OpenPGP相似,它还维护收件人列表和使用对称密钥加密的邮件的批量邮件,该对称密钥可供该邮件的每个收件人访问。 JWE消息的收件人可以具有不同类型的密钥,例如RSA密钥,用于加密的特定椭圆曲线密钥类型和对称密钥。由于这是一个较新的标准,因此仍然可以扩展JWE,以使用PKCS#11或密钥管理和互操作性协议(KMIP)接口来支持TPM或硬件安全模块(HSM)等硬件设备中的密钥。如果接收者具有RSA密钥或椭圆曲线,则JWE与OpenPGP的使用方式相似。将来,我们可以扩展它以支持HSM内部的KMIP等对称密钥。
PKCS#7
在IEFT RFC5652中定义了PKCS#7,也称为加密消息语法(CMS)。根据有关CMS的维基百科的说法,“它可以用于数字签名,摘要,认证或加密任何形式的数字数据。”
它与先前描述的两种技术相似,因为它允许多个收件人和批量消息的加密。因此,我们像其他技术一样使用它,但仅用于提供加密密钥证书的收件人。
为了支持先前描述的加密技术,我们扩展了清单文件以包括以下信息:
- OpenPGP,JWE和PKCS#7消息存储在注释清单中,该清单是清单的一部分。
- 每个指定的图层包含一张地图。注释图基本上是一个字典,以字符串为键,以字符串为值(键-值对)。
为了支持图像加密,我们定义了以下密钥:
- org.opencontainers.image.enc.keys.openpgp
- org.opencontainers.image.enc.keys.jwe
- org.opencontainers.image.enc.keys.pkcs7
每个密钥引用的值包含一个或多个用于相应加密技术的加密消息。由于这些消息可以采用二进制格式,因此它们是base64编码的。加密层必须至少具有一个这样的注释,但是如果接收者具有足够数量的不同类型的密钥,则它可以具有所有注释。
为了确定该层是使用LEK加密的,我们扩展了现有媒体类型,其后缀为“ +加密”,如以下示例所示:
- 应用程序/ vnd.docker.image.rootfs.diff.tar +加密
- 应用程序/ vnd.docker.image.rootfs.diff.tar.gzip +加密
这些示例表明,该层已压缩为.tar文件并已加密-或都压缩为.tar文件并已压缩为.gzip文件并已加密。以下屏幕截图显示了链接到加密层的清单的示例。它还显示了包含加密的JWE消息的注释映射。
使用对称密钥的分层加密
对于使用LEK的对称加密,我们的团队选择了一种支持身份验证加密的密码,该密码基于具有128位和256位密钥的AES加密标准。
示例实现:容器化
我们在名为containerd的新容器运行时项目中实现了变体。可以通过以下链接查看其在golang上的源代码。 Docker守护进程使用容器化运行其某些服务,而Kubernetes有一个插件可以直接使用容器化。因此,我们希望支持加密容器映像的扩展对两者都有用。
使用LEK进行多级加密的实现是扩展的最低体系结构级别。实现要求之一是容纳几GB的容量层,同时使在该层上执行加密操作的进程所占用的内存量只有几兆字节。
在Golang中, 对经过身份验证的加密算法的支持将字节数组作为输入,并执行对其进行加密(密封)或解密(打开)的整个阶段,从而防止向流中传输和添加其他数组。由于此加密API需要将整个层加载到内存中,或者发明了某种方案来更改每个块的初始化矢量(IV),因此我们决定不使用具有链接数据支持(AEAD)的golang身份验证加密。相反,我们使用了错误的golang加密库,该库在流上支持AEAD(块),并实施自己的方案来更改每个块中的IV。在我们的实现中,我们将层分成1 MB的块,然后将其逐一传输以进行加密。这种方法使您可以减少使用经过身份验证的密码时的内存量。在解密方面,我们采取相反的操作,并注意Open()函数返回的错误,以确保未篡改加密块。
在对称加密之上,非对称密码方案对LEK级别和初始化向量(IV)进行加密。要添加或删除密码方案,我们注册每个非对称密码实现。当调用非对称加密代码API进行层加密时,我们会逐个调用注册的加密处理程序,并传递接收者的公钥。在所有接收者密钥都用于加密之后,我们以非对称密码算法标识符作为映射密钥以及包含以OpenPGP,JWE和PKCS#7编码的消息的值的返回到注释映射。每个消息都包含打包的LEK和IV。注释映射存储在清单文档中,如上一个屏幕截图所示。
我们还可以将收件人添加到已经加密的图像中。如果图像作者在列表中,则添加他们。私钥用于收件人列表,这是解压缩LEK和IV级别所必需的。然后,我们使用新的收件人密钥将LEK和IV打包到新消息中,并将此消息添加到注释映射中。
对于不同类型的密钥,我们使用了三种类型的非对称加密方案。我们使用OpenPGP密钥对OpenPGP消息进行加密。我们正在使用的PKCS#7需要x.509证书作为加密密钥。JWE处理所有其他密钥类型,例如简单的RSA密钥,椭圆曲线和对称密钥。我们为JWE设计了扩展原型,该扩展允许使用KMIP服务器管理的密钥进行加密操作。
容器化运行时包含一个ctr客户端工具,可以与其进行交互。我们扩展了ctr,以测试变更并提供对容器用户的访问权限。 ctr已经实现了一个支持映像操作的子命令,例如通过获取和发送映像注册表与映像注册表进行交互。
我们通过添加功能来扩展此子命令,以加密图像并使用一组特定的密钥对特定体系结构的特定层进行加密。这种方法允许用户仅加密包含敏感数据的那些层,而使其他层保持未加密状态。后者可以进行重复数据删除,但是对于加密层来说几乎是不可能的。
同样,我们可以解密各个体系结构的各个层。我们添加了layerinfo子命令,该命令显示每个层的加密状态并显示用于该层的加密技术。对于OpenPGP,我们还可以显示解密所需的密钥ID,或使用密钥环将其转换为收件人的电子邮件地址。
此外,您可以导出和导入容器图像。我们已经实现了对导出时的层加密和导入时的解密的支持。即使我们解密这些层以创建容器的rootfs文件系统,也将保留加密的层和原始元数据文件(如清单)。当用户要使用加密图像启动容器时,此方法允许您导出加密图像并执行授权检查。
从注册表中检索一个普通(未加密)的映像时,它将自动解压缩并解压缩,以便可以立即从中创建容器。为了使加密图像更容易,我们建议您将私钥传递给拆包团队,以便他们可以在拆包之前对图层进行解密。如果使用多个密钥对映像进行加密,则可以将多个密钥传递给pull命令。也支持此传输。从注册表成功提取加密的映像后,任何有权使用containerd的人都可以从该映像创建一个容器。为了确认用户有权使用容器映像,我们建议他提供用于解密容器的私钥。我们使用密钥来检查用户的授权,是否可以将其用于解密每个加密级别的LEK,如果确认,我们将允许容器启动。
使用容器加密的分步指南
在本节中,我们将演示在命令行上使用ctr应用于containderd的加密步骤。我们将向您展示如何加密和解密容器映像。
首先,您需要克隆git containerd / imgcrypt仓库,这是一个子项目,可以加密/解密容器映像。然后,您需要构建容器并运行它。要完成这些步骤,您需要了解golang开发环境的设置方式:
imgcrypt需要容器版本1.3或更高版本。
构建并安装imgcrypt:
# make
# sudo make install
运行包含以下示例中所示的配置文件的容器。为避免容器冲突,请在目录中使用/ tmp目录。还可以从源代码构建容器化的1.3版,但不要安装它。
# cat config.toml
disable_plugins = ["cri"]
root = "/tmp/var/lib/containerd"
state = "/tmp/run/containerd"
[grpc]
address = "/tmp/run/containerd/containerd.sock"
uid = 0
gid = 0
[stream_processors]
[stream_processors."io.containerd.ocicrypt.decoder.v1.tar.gzip"]
accepts = ["application/vnd.oci.image.layer.v1.tar+gzip+encrypted"]
returns = "application/vnd.oci.image.layer.v1.tar+gzip"
path = "/usr/local/bin/ctd-decoder"
[stream_processors."io.containerd.ocicrypt.decoder.v1.tar"]
accepts = ["application/vnd.oci.image.layer.v1.tar+encrypted"]
returns = "application/vnd.oci.image.layer.v1.tar"
path = "/usr/local/bin/ctd-decoder"
# sudo ~/src/github.com/containerd/containerd/bin/containerd -c config.toml
使用openssl命令行工具创建RSA密钥对并加密映像:
# openssl genrsa --out mykey.pem
Generating RSA private key, 2048 bit long modulus (2 primes)
...............................................+++++
............................+++++
e is 65537 (0x010001)
# openssl rsa -in mykey.pem -pubout -out mypubkey.pem
writing RSA key
# sudo chmod 0666 /tmp/run/containerd/containerd.sock
# CTR="/usr/local/bin/ctr-enc -a /tmp/run/containerd/containerd.sock"
# $CTR images pull --all-platforms docker.io/library/bash:latest
[...]
# $CTR images layerinfo --platform linux/amd64 docker.io/library/bash:latest
# DIGEST PLATFORM SIZE ENCRYPTION RECIPIENTS
0 sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609 linux/amd64 2789669
1 sha256:7dd01fd971d4ec7058c5636a505327b24e5fc8bd7f62816a9d518472bd9b15c0 linux/amd64 3174665
2 sha256:691cfbca522787898c8b37f063dd20e5524e7d103e1a3b298bd2e2b8da54faf5 linux/amd64 340
# $CTR images encrypt --recipient jwe:mypubkey.pem --platform linux/amd64 docker.io/library/bash:latest bash.enc:latest
Encrypting docker.io/library/bash:latest to bash.enc:latest
$ $CTR images layerinfo --platform linux/amd64 bash.enc:latest
# DIGEST PLATFORM SIZE ENCRYPTION RECIPIENTS
0 sha256:360be141b01f69b25427a9085b36ba8ad7d7a335449013fa6b32c1ecb894ab5b linux/amd64 2789669 jwe [jwe]
1 sha256:ac601e66cdd275ee0e10afead03a2722e153a60982122d2d369880ea54fe82f8 linux/amd64 3174665 jwe [jwe]
2 sha256:41e47064fd00424e328915ad2f7f716bd86ea2d0d8315edaf33ecaa6a2464530 linux/amd64 340 jwe [jwe]
启动本地映像注册表,以便可以将加密的映像上传到该注册表。要接收加密的容器映像,您需要最新的注册表版本。
# docker pull registry:latest
# docker run -d -p 5000:5000 --restart=always --name registry registry
将加密的图像上传到本地注册表,使用ctr-enc提取它,然后运行该图像:
# $CTR images tag bash.enc:latest localhost:5000/bash.enc:latest
# $CTR images push localhost:5000/bash.enc:latest
# $CTR images rm localhost:5000/bash.enc:latest bash.enc:latest
# $CTR images pull localhost:5000/bash.enc:latest
# sudo $CTR run --rm localhost:5000/bash.enc:latest test echo 'Hello World!'
ctr: you are not authorized to use this image: missing private key needed for decryption
# sudo $CTR run --rm --key mykey.pem localhost:5000/bash.enc:latest test echo 'Hello World!'
Hello World!
结论
加密容器映像是对其安全性的很好补充,它可以确保数据的机密性和存储位置处容器映像的完整性。提出的技术基于公开可用的RSA,椭圆曲线和AES加密技术。它将密钥应用于更高级别的加密方案,例如OpenPGP,JWE和PKCS#7。如果您知道如何使用OpenPGP,则可以使用他们的电子邮件地址为OpenPGP收件人加密容器图像,而简单的RSA密钥和椭圆曲线则用于JWE等加密。