掘金 后端 ( ) • 2024-04-24 15:37

内容节选自《containerd 原理剖析与实战》,新书内购中,点击阅读原文,限时 69.9 元购买

1. containerd 中的镜像解密

OCI 镜像规范中,一个镜像是由多层镜像层构成的,镜像层可以通过加密机制来加密机密数据或代码,以防止未经授权的访问。如下图所示。

1.jpg

_图 镜像加密原理_  

OCI 镜像加密原理主要是在原来的 OCI 镜像规范基础上,添加了一种新的 mediaType,表示数据文件被加密;同时在 annotation中添加具体加密相关信息。镜像层没加密前的原始数据如下。

"layers":[
  {
    "mediaType":"application/vnd.oci.image.layer.v1.tar+gzip",
    "digest":"sha256:7c9d20b9b6cda1c58bc4f9d6c401386786f584437abbe87e58910f8a9a15386b",
    "size":760770
  }
]

加密之后的数据如下。

"layers":[
  {
    "mediaType":"application/vnd.oci.image.layer.v1.tar+gzip+encrypted",
    "digest":"sha256:c72c69b36a886c268e0d7382a7c6d885271b6f0030ff022fda2b6346b2b274ba",
    "size":760770,
    "annotations": {
      "org.opencontainers.image.enc.keys.jwe":"eyJwcm90ZWN0Z...",
      "org.opencontainers.image.enc.pubopts":"eyJjaXBoZXIiOi..."
    }
  }
]

在启动容器时, containerd 通过解密信息来解密这些加密镜像。这些解密信息包括密钥、选项和加密元数据。这些信息配置在 CRI Plugin 的 image_decryption 配置项中。此外,还需要设置正确的密钥模型并确保已正确配置 stream processors 和 containerd imgcrypt 解码器。

下面介绍如何在 containerd 的 CRI Plugin 中配置镜像解密。在介绍 CRI Plugin 中的镜像解密前,先介绍 k8s 中的镜像加解密和 containerd 中的 stream_processor

1.k8s 生态的镜像加解密

首先介绍镜像加密模式,Kubernetes 社区共支持两种镜像加密模式:

  1. Node Key Model,将密钥放在 Kubernetes 工作节点上,以节点为粒度实现解密,参考图4.21 所示。

  2. Multi-tenancy Key Model,多租户模型,以集群粒度实现解密(当前社区还未实现)。

2.jpg

_图 镜像解密模式 Node Key Model_

containerd 中当前支持的 是 Node Key Model,如上图所示,这种模式下 containerd 会在可信的 Node 上进行拉取镜像并利用私钥进行解密镜像。具体配置如下。首先是 containerd 中配置 CRI Pluging 的 image_decryption 选项,

version = 2
[plugins."io.containerd.grpc.v1.cri".image_decryption]
  key_model = "node"

在 containerd 及以后的版本中, key_model = "node"是默认的配置,如果是 1.4 以及以前的配置,则需要手动配置上述信息并重启 containerd。除此之外,还需要配置 stream_processors 配置项。

2. containerd 中的 stream_processors

stream_processors 是 containerd 中的一种基于内容流的二进制 API。

传入的内容流通过 STDIN 传递给对应的二进制文件,二进制处理后输出 STDOUT 到 stream_processors,如下图所示。

3.jpg

_图 stream\_processors 处理流程_

streaming_processor 是对二进制的调用,如上图所示,相当于针对每层镜像都进行了 unpiz 操作,等价于:

<tar image layer>=`unpiz -d -c <tar.gzip image layer>`

其中:

  • <tar.gzip image layer> 为输入的 targzip 格式的镜像层。

  • <tar image layer> 则为执行 unpiz -d -c 之后的stdout 输出,即解压的结果。  该示例的 stream_processor 配置如下。

version = 2
[stream_processors]
  [stream_processors."io.containerd.processor.v1.pigz"]
 accepts = ["application/vnd.docker.image.rootfs.diff.tar.gzip"]
 returns = "application/vnd.oci.image.layer.v1.tar"
 path = "unpigz"
 args = ["-d", "-c"]

stream_processor 中支持的配置有:

  • ID: 即 示例中的 "io.containerd.processor.v1.pigz",通过 stream_processors.<Process ID> 来指定某个 processor 的配置。

  • accepts: 该 processor 能处理的格式。

  • returns: 该 processor 处理之后的格式。

  • path: 该 processor 对应的可执行二进制文件的路径。

  • args:该 processor 处理时所需的参数。path + args 组成该 processor 的处理步骤,例如上述示例则是 unpigz -d -c

此外,processor 还支持 env 配置,格式为 ["key1=value1","key2=value2"]

3. 配置镜像解密

containerd 中的镜像解密则是利用了 stream_processor 机制,containerd/imgcrypt (https://github.com/containerd/imgcrypt)中的二进制 ctd-decoder 对每层镜像进行解密。具体配置如下。

version = 2
[plugins."io.containerd.grpc.v1.cri".image_decryption]
  key_model = "node"
[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 = "ctd-decoder"
    args = ["--decryption-keys-path", "/etc/containerd/ocicrypt/keys"]
    env= ["OCICRYPT_KEYPROVIDER_CONFIG=/etc/containerd/ocicrypt/ocicrypt_keyprovider.conf"]
  [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 = "ctd-decoder"
    args = ["--decryption-keys-path", "/etc/containerd/ocicrypt/keys"]
    env= ["OCICRYPT_KEYPROVIDER_CONFIG=/etc/containerd/ocicrypt/ocicrypt_keyprovider.conf"]

上述配置中,利用二进制 ctd-decoder 通过参数 --decryption-keys-path 指定镜像解密私钥,分别对 tar 格式和 tar.gzip 格式进行解密。

以上内容节选自新书 《containerd 原理剖析与实战》

最后,附上本书的购买链接,新书刚刚上架原价 109,限时优惠内购 69.9 元,感兴趣的朋友可以尽快入手。内购链接点击左下角**“阅读原文**”

containerd.jpeg

本文使用 文章同步助手 同步