加密静止状态下的机密数据

Kubernetes 中所有允许您写入持久性 API 资源数据的 API 都支持静态加密。例如,您可以为 Secrets 启用静态加密。这种静态加密是 etcd 集群或运行 kube-apiserver 的主机上的文件系统(s) 的任何系统级加密的补充。

此页面展示了如何启用和配置 API 数据在静态时的加密。

开始之前

  • 您需要拥有一个 Kubernetes 集群,并且 kubectl 命令行工具必须配置为与您的集群通信。建议在至少有两个节点的集群上运行本教程,这些节点不充当控制平面主机。如果您还没有集群,可以使用 minikube 创建一个,或者可以使用以下 Kubernetes 游乐场之一

  • 此任务假设您在每个控制平面节点上以 静态 Pod 的形式运行 Kubernetes API 服务器。

  • 您的集群控制平面必须使用 etcd v3.x(主版本 3,任何次要版本)。

  • 要加密自定义资源,您的集群必须运行 Kubernetes v1.26 或更高版本。

  • 要使用通配符匹配资源,您的集群必须运行 Kubernetes v1.27 或更高版本。

要检查版本,请输入 kubectl version

确定静态加密是否已启用

默认情况下,API 服务器将资源的纯文本表示形式存储到 etcd 中,没有静态加密。

kube-apiserver 进程接受一个参数 --encryption-provider-config,它指定一个配置文件的路径。该文件的内容(如果您指定了)控制 Kubernetes API 数据在 etcd 中的加密方式。如果您在没有 --encryption-provider-config 命令行参数的情况下运行 kube-apiserver,则您没有启用静态加密。如果您在使用 --encryption-provider-config 命令行参数的情况下运行 kube-apiserver,并且它引用的文件将 identity 提供程序指定为列表中的第一个加密提供程序,那么您没有启用静态加密(默认的 identity 提供程序不提供任何机密保护。)。

如果您在使用 --encryption-provider-config 命令行参数的情况下运行 kube-apiserver,并且它引用的文件将 identity 以外的提供程序指定为列表中的第一个加密提供程序,那么您已经启用了静态加密。但是,该检查并不能告诉您先前的迁移到加密存储是否已成功。如果您不确定,请参阅 确保所有相关数据都被加密

了解静态加密配置

---
#
# CAUTION: this is an example configuration.
#          Do not use this for your own cluster!
#
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
      - configmaps
      - pandas.awesome.bears.example # a custom resource API
    providers:
      # This configuration does not provide data confidentiality. The first
      # configured provider is specifying the "identity" mechanism, which
      # stores resources as plain text.
      #
      - identity: {} # plain text, in other words NO encryption
      - aesgcm:
          keys:
            - name: key1
              secret: c2VjcmV0IGlzIHNlY3VyZQ==
            - name: key2
              secret: dGhpcyBpcyBwYXNzd29yZA==
      - aescbc:
          keys:
            - name: key1
              secret: c2VjcmV0IGlzIHNlY3VyZQ==
            - name: key2
              secret: dGhpcyBpcyBwYXNzd29yZA==
      - secretbox:
          keys:
            - name: key1
              secret: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
  - resources:
      - events
    providers:
      - identity: {} # do not encrypt Events even though *.* is specified below
  - resources:
      - '*.apps' # wildcard match requires Kubernetes 1.27 or later
    providers:
      - aescbc:
          keys:
          - name: key2
            secret: c2VjcmV0IGlzIHNlY3VyZSwgb3IgaXMgaXQ/Cg==
  - resources:
      - '*.*' # wildcard match requires Kubernetes 1.27 or later
    providers:
      - aescbc:
          keys:
          - name: key3
            secret: c2VjcmV0IGlzIHNlY3VyZSwgSSB0aGluaw==

每个 resources 数组项都是一个单独的配置,包含一个完整的配置。resources.resources 字段是一个 Kubernetes 资源名称(resourceresource.group)数组,这些名称应该被加密,例如 Secrets、ConfigMaps 或其他资源。

如果将自定义资源添加到 EncryptionConfiguration 并且集群版本为 1.26 或更高版本,则 EncryptionConfiguration 中提到的任何新创建的自定义资源都将被加密。在该版本和配置之前存在于 etcd 中的任何自定义资源都将保持未加密状态,直到它们下次写入存储。这与内置资源的行为相同。请参阅 确保所有 Secrets 都被加密 部分。

providers 数组是您列出的 API 可使用的可能加密提供程序的有序列表。每个提供程序都支持多个密钥 - 密钥按顺序尝试解密,如果提供程序是第一个提供程序,则第一个密钥用于加密。

每个条目只能指定一种提供程序类型(可以提供 identityaescbc,但不能在同一项中同时提供两者)。列表中的第一个提供程序用于加密写入存储的资源。从存储中读取资源时,每个与存储数据匹配的提供程序都会按顺序尝试解密数据。如果由于格式或密钥不匹配,没有提供程序能够读取存储的数据,则会返回错误,阻止客户端访问该资源。

EncryptionConfiguration 支持使用通配符来指定应加密的资源。使用 '*.<group>' 来加密组中的所有资源(例如,上面示例中的 '*.apps')或 '*.*' 来加密所有资源。'*.' 可用于加密核心组中的所有资源。'*.*' 将加密所有资源,即使是 API 服务器启动后添加的自定义资源。

如果您有一个覆盖资源的通配符,并且想要选择退出特定类型资源的静态加密,您可以通过添加一个单独的 resources 数组项(包含您想要豁免的资源的名称),然后添加一个 providers 数组项(在其中指定 identity 提供程序)来实现。您将此项添加到列表中,使其出现在您指定加密(不是 identity 的提供程序)的配置之前。

例如,如果启用了 '*.*' 并且您想要选择退出 Events 和 ConfigMaps 的加密,请在 resources 中添加一个新的更早的项,然后添加一个使用 identity 作为提供程序的 providers 数组项。更具体的条目必须出现在通配符条目之前。

新项将类似于

  ...
  - resources:
      - configmaps. # specifically from the core API group,
                    # because of trailing "."
      - events
    providers:
      - identity: {}
  # and then other entries in resources

确保豁免在资源数组中的通配符 '*.*' 项之前列出,以使其具有优先级。

有关 EncryptionConfiguration 结构的更详细的信息,请参阅 加密配置 API

可用提供程序

在为集群 Kubernetes API 中的数据配置静态加密之前,您需要选择将使用哪些提供程序。

下表描述了每个可用提供程序。

Kubernetes 静态加密的提供程序
名称加密强度速度密钥长度
identityN/AN/AN/A
资源按原样写入,不进行加密。当设置为第一个提供程序时,资源将在写入新值时被解密。现有的加密资源不会自动被明文数据覆盖。该identity提供程序是默认的,如果您没有另外指定。
aescbc使用 PKCS#7 填充的 AES-CBC32 字节
不推荐使用,因为 CBC 易受填充预言攻击。密钥材料可从控制平面主机访问。
aesgcm使用随机 nonce 的 AES-GCM必须每 200,000 次写入旋转一次最快16、24 或 32 字节
不推荐使用,除非实施了自动密钥旋转方案。密钥材料可从控制平面主机访问。
kmsv1 (自 Kubernetes v1.28 起已弃用)使用每个资源的 DEK 的信封加密方案。最强慢 (kms 版本 2 相比)32 字节
数据使用 AES-GCM 通过数据加密密钥 (DEK) 加密;DEK 根据密钥管理服务 (KMS) 中的配置通过密钥加密密钥 (KEK) 加密。简单的密钥旋转,为每次加密生成一个新的 DEK,并且 KEK 旋转由用户控制。
阅读如何 配置 KMS V1 提供程序
kmsv2使用每个 API 服务器的 DEK 的信封加密方案。最强32 字节
数据使用 AES-GCM 通过数据加密密钥 (DEK) 加密;DEK 根据密钥管理服务 (KMS) 中的配置通过密钥加密密钥 (KEK) 加密。Kubernetes 从秘密种子为每次加密生成一个新的 DEK。每次 KEK 旋转时,种子都会旋转。
如果您使用第三方工具进行密钥管理,这是一个不错的选择。从 Kubernetes v1.29 开始,它作为稳定版本提供。
阅读如何 配置 KMS V2 提供程序
secretboxXSalsa20 和 Poly1305更快32 字节
使用相对较新的加密技术,这些技术可能不被认为在需要高度审查的环境中是可接受的。密钥材料可从控制平面主机访问。

如果您没有指定其他内容,identity 提供程序将是默认提供程序。identity 提供程序不会加密存储的数据,并且不提供任何额外的机密性保护。

密钥存储

本地密钥存储

使用本地管理的密钥加密机密数据可以防止 etcd 被破坏,但无法防止主机被破坏。由于加密密钥存储在 EncryptionConfiguration YAML 文件中的主机上,熟练的攻击者可以访问该文件并提取加密密钥。

托管 (KMS) 密钥存储

KMS 提供程序使用信封加密:Kubernetes 使用数据密钥加密资源,然后使用托管加密服务加密该数据密钥。Kubernetes 为每个资源生成一个唯一的数据密钥。API 服务器将数据密钥的加密版本存储在 etcd 中,与密文一起;在读取资源时,API 服务器会调用托管加密服务,并提供密文和(加密的)数据密钥。在托管加密服务中,提供程序使用密钥加密密钥来解密数据密钥,解密数据密钥,最后恢复明文。控制平面和 KMS 之间的通信需要在传输过程中进行保护,例如 TLS。

使用信封加密会产生对密钥加密密钥的依赖,密钥加密密钥未存储在 Kubernetes 中。在 KMS 的情况下,打算未经授权访问明文值的攻击者需要破坏 etcd 第三方 KMS 提供程序。

加密密钥的保护

您应该采取适当的措施来保护允许解密的机密信息,无论是本地加密密钥,还是允许 API 服务器调用 KMS 的身份验证令牌。

即使您依赖提供程序来管理主加密密钥(或密钥)的使用和生命周期,您仍然有责任确保托管加密服务的访问控制和其他安全措施适合您的安全需求。

加密您的数据

生成加密密钥

以下步骤假设您没有使用 KMS,因此这些步骤还假设您需要生成加密密钥。如果您已经拥有加密密钥,请跳到编写加密配置文

首先生成一个新的加密密钥,然后使用 base64 对其进行编码

生成一个 32 字节的随机密钥并使用 base64 对其进行编码。您可以使用此命令

head -c 32 /dev/urandom | base64

如果您想使用 PC 的内置硬件熵源,可以使用/dev/hwrng 而不是/dev/urandom。并非所有 Linux 设备都提供硬件随机数生成器。

生成一个 32 字节的随机密钥并使用 base64 对其进行编码。您可以使用此命令

head -c 32 /dev/urandom | base64

生成一个 32 字节的随机密钥并使用 base64 对其进行编码。您可以使用此命令

# Do not run this in a session where you have set a random number
# generator seed.
[Convert]::ToBase64String((1..32|%{[byte](Get-Random -Max 256)}))

复制加密密钥

使用安全的机制进行文件传输,将该加密密钥的副本提供给所有其他控制平面主机。

至少使用传输中的加密 - 例如,安全外壳 (SSH)。为了获得更高的安全性,请在主机之间使用非对称加密,或更改您使用的方法,以便您依赖 KMS 加密。

编写加密配置文

创建一个新的加密配置文。内容应类似于

---
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
      - configmaps
      - pandas.awesome.bears.example
    providers:
      - aescbc:
          keys:
            - name: key1
              # See the following text for more details about the secret value
              secret: <BASE 64 ENCODED SECRET>
      - identity: {} # this fallback allows reading unencrypted secrets;
                     # for example, during initial migration

要创建一个新的加密密钥(不使用 KMS),请参阅生成加密密钥

使用新的加密配置文

您需要将新的加密配置文装载到kube-apiserver 静态 pod。以下是如何执行此操作的示例

  1. 将新的加密配置文保存到控制平面节点上的/etc/kubernetes/enc/enc.yaml

  2. 编辑kube-apiserver 静态 pod 的清单:/etc/kubernetes/manifests/kube-apiserver.yaml,使其类似于

    ---
    #
    # This is a fragment of a manifest for a static Pod.
    # Check whether this is correct for your cluster and for your API server.
    #
    apiVersion: v1
    kind: Pod
    metadata:
      annotations:
        kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 10.20.30.40:443
      creationTimestamp: null
      labels:
        app.kubernetes.io/component: kube-apiserver
        tier: control-plane
      name: kube-apiserver
      namespace: kube-system
    spec:
      containers:
      - command:
        - kube-apiserver
        ...
        - --encryption-provider-config=/etc/kubernetes/enc/enc.yaml  # add this line
        volumeMounts:
        ...
        - name: enc                           # add this line
          mountPath: /etc/kubernetes/enc      # add this line
          readOnly: true                      # add this line
        ...
      volumes:
      ...
      - name: enc                             # add this line
        hostPath:                             # add this line
          path: /etc/kubernetes/enc           # add this line
          type: DirectoryOrCreate             # add this line
      ...
    
  3. 重新启动您的 API 服务器。

您现在已为一个控制平面主机设置了加密。典型的 Kubernetes 集群有多个控制平面主机,因此还有更多工作要做。

重新配置其他控制平面主机

如果您在集群中有多个 API 服务器,您应该依次将更改部署到每个 API 服务器。

当您计划更新集群的加密配置时,请计划此操作,以便控制平面中的 API 服务器始终能够解密存储的数据(即使在滚动更新过程中)。

确保您在每个控制平面主机上使用相同的加密配置。

验证新写入的数据是否已加密

数据写入 etcd 时会加密。重新启动kube-apiserver 后,任何新创建或更新的 Secret(或EncryptionConfiguration 中配置的其他资源类型)在存储时都应加密。

要检查这一点,您可以使用etcdctl 命令行程序来检索 Secret 数据的内容。

此示例显示如何检查是否对 Secret API 进行加密。

  1. default 命名空间中创建一个名为secret1 的新 Secret

    kubectl create secret generic secret1 -n default --from-literal=mykey=mydata
    
  2. 使用etcdctl 命令行工具,从 etcd 中读取该 Secret

    ETCDCTL_API=3 etcdctl get /registry/secrets/default/secret1 [...] | hexdump -C
    

    其中[...] 必须是连接到 etcd 服务器的附加参数。

    例如

    ETCDCTL_API=3 etcdctl \
       --cacert=/etc/kubernetes/pki/etcd/ca.crt   \
       --cert=/etc/kubernetes/pki/etcd/server.crt \
       --key=/etc/kubernetes/pki/etcd/server.key  \
       get /registry/secrets/default/secret1 | hexdump -C
    

    输出类似于此(缩写)

    00000000  2f 72 65 67 69 73 74 72  79 2f 73 65 63 72 65 74  |/registry/secret|
    00000010  73 2f 64 65 66 61 75 6c  74 2f 73 65 63 72 65 74  |s/default/secret|
    00000020  31 0a 6b 38 73 3a 65 6e  63 3a 61 65 73 63 62 63  |1.k8s:enc:aescbc|
    00000030  3a 76 31 3a 6b 65 79 31  3a c7 6c e7 d3 09 bc 06  |:v1:key1:.l.....|
    00000040  25 51 91 e4 e0 6c e5 b1  4d 7a 8b 3d b9 c2 7c 6e  |%Q...l..Mz.=..|n|
    00000050  b4 79 df 05 28 ae 0d 8e  5f 35 13 2c c0 18 99 3e  |.y..(..._5.,...>|
    [...]
    00000110  23 3a 0d fc 28 ca 48 2d  6b 2d 46 cc 72 0b 70 4c  |#:..(.H-k-F.r.pL|
    00000120  a5 fc 35 43 12 4e 60 ef  bf 6f fe cf df 0b ad 1f  |..5C.N`..o......|
    00000130  82 c4 88 53 02 da 3e 66  ff 0a                    |...S..>f..|
    0000013a
    
  3. 验证存储的 Secret 是否以k8s:enc:aescbc:v1: 为前缀,这表明aescbc 提供程序已加密结果数据。确认etcd 中显示的密钥名称与上面提到的EncryptionConfiguration 中指定的密钥名称匹配。在此示例中,您可以看到名为key1 的加密密钥在etcdEncryptionConfiguration 中使用。

  4. 验证 Secret 在通过 API 检索时是否已正确解密

    kubectl get secret secret1 -n default -o yaml
    

    输出应包含mykey: bXlkYXRh,其内容为使用 base64 编码的mydata;阅读解码 Secret 以了解如何完全解码 Secret。

确保所有相关数据都已加密

确保新对象被加密通常是不够的:您还希望该加密应用于已存储的对象。

对于此示例,您已配置集群,以便在写入时对 Secret 进行加密。对每个 Secret 执行替换操作将加密静态数据,其中对象保持不变。

您可以对集群中的所有 Secret 进行此更改

# Run this as an administrator that can read and write all Secrets
kubectl get secrets --all-namespaces -o json | kubectl replace -f -

上面的命令读取所有 Secret,然后使用相同的数据更新它们,以便应用服务器端加密。

防止以明文形式检索

如果您想确保对特定 API 类型的唯一访问是使用加密进行的,则可以删除 API 服务器以明文形式读取该 API 的后备数据的能力。

一旦集群中的所有 Secret 都已加密,您就可以从加密配置中删除identity 部分。例如

---
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: <BASE 64 ENCODED SECRET>
      - identity: {} # REMOVE THIS LINE

…然后依次重新启动每个 API 服务器。此更改会阻止 API 服务器访问明文 Secret,即使是意外访问。

轮换解密密钥

更改 Kubernetes 的加密密钥而不会造成停机时间需要一个多步骤操作,尤其是在存在高度可用部署的情况下,其中多个kube-apiserver 进程正在运行。

  1. 生成一个新密钥,并将其作为当前提供程序的第二个密钥条目添加到所有控制平面节点上。
  2. 重新启动所有kube-apiserver 进程,以确保每个服务器都可以解密使用新密钥加密的任何数据。
  3. 对新的加密密钥进行安全备份。如果您丢失了此密钥的所有副本,则需要删除使用丢失的密钥加密的所有资源,并且在静态加密中断期间,工作负载可能无法按预期运行。
  4. 使新密钥成为keys 数组中的第一个条目,以便将其用于对新写入进行静态加密
  5. 重新启动所有kube-apiserver 进程,以确保每个控制平面主机现在都使用新密钥进行加密
  6. 以特权用户身份运行kubectl get secrets --all-namespaces -o json | kubectl replace -f - 以使用新密钥加密所有现有 Secret
  7. 在您已更新所有现有 Secret 以使用新密钥并已对新密钥进行安全备份后,请从配置中删除旧的解密密钥。

解密所有数据

此示例显示如何停止对 Secret API 进行静态加密。如果您正在加密其他 API 类型,请调整步骤以匹配。

要禁用静态加密,请将identity 提供程序放在加密配置文中的第一个条目中

---
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
      # list any other resources here that you previously were
      # encrypting at rest
    providers:
      - identity: {} # add this line
      - aescbc:
          keys:
            - name: key1
              secret: <BASE 64 ENCODED SECRET> # keep this in place
                                               # make sure it comes after "identity"

然后运行以下命令强制解密所有 Secret

kubectl get secrets --all-namespaces -o json | kubectl replace -f -

一旦您用不使用加密的后备数据替换了所有现有的加密资源,您就可以从kube-apiserver 中删除加密设置。

配置自动重新加载

您可以配置加密提供程序配置的自动重新加载。该设置决定API 服务器 应该只在启动时加载您为--encryption-provider-config 指定的文件一次,还是在您更改该文件时自动加载。启用此选项允许您更改静态加密的密钥,而无需重新启动 API 服务器。

要允许自动重新加载,请将 API 服务器配置为使用以下命令运行:--encryption-provider-config-automatic-reload=true。启用后,每分钟轮询一次文件更改以观察修改。apiserver_encryption_config_controller_automatic_reload_last_timestamp_seconds 指标标识新配置何时生效。这允许在不重新启动 API 服务器的情况下轮换加密密钥。

下一步

上次修改时间:2024 年 5 月 1 日 下午 9:19 PST:docs: 更新自动重新加载行为。 (87a912068c)