加密静态保密数据
Kubernetes 中所有允许你写入持久 API 资源数据的 API 都支持静态加密。例如,你可以为 Secret 启用静态加密。此静态加密是 etcd 集群或运行 kube-apiserver 的主机文件系统任何系统级加密的补充。
本页面展示了如何启用和配置静态 API 数据加密。
注意
此任务涵盖使用 Kubernetes API 存储的资源数据加密。例如,你可以加密 Secret 对象,包括它们包含的键值数据。
如果你想加密挂载到容器中的文件系统中的数据,你需要
- 使用提供加密卷的存储集成
- 在你自己的应用程序中加密数据
准备工作
你需要有一个 Kubernetes 集群,并且必须配置 kubectl 命令行工具以与你的集群通信。建议在至少有两个不是控制平面主机的节点上运行本教程。如果你还没有集群,可以通过使用 minikube 创建一个,或者你可以使用这些 Kubernetes 操场中的一个
此任务假设你将 Kubernetes API 服务器作为 静态 Pod 运行在每个控制平面节点上。
你的集群控制平面 **必须** 使用 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 资源名称(`resource` 或 `resource.group`)数组,这些资源(如 Secret、ConfigMap 或其他资源)应被加密。
如果将自定义资源添加到 `EncryptionConfiguration` 并且集群版本为 1.26 或更高版本,则 `EncryptionConfiguration` 中提及的任何新创建的自定义资源都将被加密。在修改此版本和配置之前已存在于 etcd 中的任何自定义资源将保持未加密状态,直到它们下次写入存储时才会被加密。这与内置资源的行为相同。请参阅 确保所有 Secret 都已加密 部分。
`providers` 数组是用于你列出的 API 的可能加密提供程序的有序列表。每个提供程序支持多个密钥——密钥按顺序尝试解密,如果该提供程序是第一个提供程序,则第一个密钥用于加密。
每个条目只能指定一种提供程序类型(可以提供 `identity` 或 `aescbc`,但不能在同一个条目中同时提供)。列表中的第一个提供程序用于加密写入存储的资源。从存储中读取资源时,每个与存储数据匹配的提供程序都会按顺序尝试解密数据。如果由于格式或密钥不匹配而导致没有提供程序可以读取存储的数据,则会返回错误,阻止客户端访问该资源。
`EncryptionConfiguration` 支持使用通配符来指定应加密的资源。使用“`*.<group>`”加密组中的所有资源(例如,上面示例中的“`*.apps`”)或“`*.*`”加密所有资源。“`*.`”可用于加密核心组中的所有资源。“`*.*`”将加密所有资源,甚至是在 API 服务器启动后添加的自定义资源。
注意
不允许在同一资源列表内或跨多个条目使用重叠的通配符,因为部分配置将无效。`resources` 列表的处理顺序和优先级由其在配置中列出的顺序决定。如果你有一个涵盖资源的通配符,并且想要选择退出特定类型资源的静态加密,你可以通过添加一个单独的 `resources` 数组项来实现,其中包含你想要豁免的资源名称,然后是一个 `providers` 数组项,其中指定 `identity` 提供程序。你将此项添加到列表中,使其出现在你指定加密的配置(不是 `identity` 的提供程序)之前。
例如,如果启用了“`*.*`”并且你想要选择退出事件(Events)和配置映射(ConfigMaps)的加密,请在 `resources` 中添加一个**更靠前**的新条目,后跟提供程序数组项,其中 `identity` 作为提供程序。更具体的条目必须在通配符条目之前。
新项目看起来类似于
...
- resources:
- configmaps. # specifically from the core API group,
# because of trailing "."
- events
providers:
- identity: {}
# and then other entries in resources
确保豁免在 `resources` 数组中通配符 `*.*` 项*之前*列出,以赋予其优先级。
有关 `EncryptionConfiguration` 结构的详细信息,请参阅 加密配置 API。
注意
如果任何资源无法通过加密配置读取(因为密钥已更改),并且你无法恢复工作配置,则唯一的办法是直接从底层 etcd 删除该条目。
任何尝试读取该资源的 Kubernetes API 调用都将失败,直到该资源被删除或提供了有效的解密密钥。
可用提供商
在为集群 Kubernetes API 中的数据配置静态加密之前,你需要选择要使用的提供程序。
下表描述了每个可用提供程序。
名称 | 加密 | 强度 | 速度 | 密钥长度 |
---|---|---|---|---|
身份 | 无 | 不适用 | 不适用 | 不适用 |
资源按原样写入,不加密。当设置为第一个提供程序时,资源将在写入新值时被解密。现有的加密资源**不会**自动用纯文本数据覆盖。身份如果你没有另行指定,则提供程序是默认值。 | ||||
aescbc | 带有 PKCS#7 填充的 AES-CBC | 弱 | 快 | 16、24 或 32 字节 |
不建议使用,因为 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 字节 |
数据通过数据加密密钥(DEK)使用 AES-GCM 进行加密;DEK 根据密钥管理服务(KMS)中的配置通过密钥加密密钥(KEK)进行加密。Kubernetes 从一个秘密种子为每次加密生成一个新的 DEK。只要 KEK 轮换,种子也会轮换。 如果使用第三方工具进行密钥管理,这是一个不错的选择。从 Kubernetes v1.29 开始稳定可用。 阅读如何配置 KMS V2 提供程序。 | ||||
secretbox | XSalsa20 和 Poly1305 | 强 | 更快 | 32 字节 |
使用相对较新的加密技术,在需要高水平审查的环境中可能不被接受。密钥材料可从控制平面主机访问。 |
如果你没有另行指定,则 `identity` 提供程序是默认值。**`identity` 提供程序不加密存储数据,也**不**提供任何额外的保密保护。**
密钥存储
本地密钥存储
使用本地管理的密钥加密敏感数据可以防止 etcd 被入侵,但无法防止主机被入侵。由于加密密钥存储在主机上的 EncryptionConfiguration YAML 文件中,熟练的攻击者可以访问该文件并提取加密密钥。
托管(KMS)密钥存储
KMS 提供程序使用**信封加密**:Kubernetes 使用数据密钥加密资源,然后使用托管加密服务加密该数据密钥。Kubernetes 为每个资源生成一个唯一的数据密钥。API 服务器将加密的数据密钥版本与密文一起存储在 etcd 中;读取资源时,API 服务器调用托管加密服务并提供密文和(加密的)数据密钥。在托管加密服务中,提供程序使用**密钥加密密钥**解密数据密钥,解密数据密钥,最后恢复纯文本。控制平面和 KMS 之间的通信需要传输中保护,例如 TLS。
使用信封加密会产生对密钥加密密钥的依赖,而密钥加密密钥并未存储在 Kubernetes 中。在 KMS 的情况下,意图未经授权访问明文值的攻击者需要同时入侵 etcd **和**第三方 KMS 提供程序。
加密密钥的保护
你应采取适当措施保护允许解密的机密信息,无论是本地加密密钥,还是允许 API 服务器调用 KMS 的身份验证令牌。
即使你依赖提供商来管理主加密密钥(或多个密钥)的使用和生命周期,你仍然有责任确保托管加密服务的访问控制和其他安全措施符合你的安全需求。
加密你的数据
生成加密密钥
以下步骤假定你不使用 KMS,因此这些步骤也假定你需要生成加密密钥。如果你已有加密密钥,请跳到 编写加密配置文件。
注意
与不加密相比,在 EncryptionConfig 中存储原始加密密钥仅适度提高你的安全态势。
为了增加机密性,请考虑使用 `kms` 提供程序,因为它依赖于 Kubernetes 集群之外持有的密钥。`kms` 的实现可以与硬件安全模块或由云提供商管理的加密服务一起使用。
要了解如何使用 KMS 设置静态加密,请参阅 使用 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)}))
注意
对加密密钥保密,包括在生成期间,理想情况下即使在你不再积极使用它之后也要保密。复制加密密钥
使用安全的文件传输机制,将加密密钥的副本提供给所有其他控制平面主机。
至少,使用传输中加密——例如,安全 shell (SSH)。为了更高的安全性,在主机之间使用非对称加密,或者更改你使用的方法,以便依赖 KMS 加密。
编写加密配置文件
注意
加密配置文件可能包含可以解密 etcd 中内容的密钥。如果配置文件包含任何密钥材料,你必须正确限制所有控制平面主机上的权限,以便只有运行 kube-apiserver 的用户才能读取此配置。创建一个新的加密配置文件。内容应类似于
---
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。以下是如何执行此操作的示例
将新的加密配置文件保存到控制平面节点上的 `/etc/kubernetes/enc/enc.yaml`。
编辑 `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 ...
重新启动你的 API 服务器。
注意
你的配置文件包含可以解密 etcd 中内容的密钥,因此你必须正确限制控制平面节点上的权限,以便只有运行 `kube-apiserver` 的用户才能读取它。你现在已为**一个**控制平面主机设置了加密。典型的 Kubernetes 集群有多个控制平面主机,因此还有更多工作要做。
重新配置其他控制平面主机
如果你的集群中有多个 API 服务器,则应依次向每个 API 服务器部署更改。
注意
对于具有两个或更多控制平面节点的集群配置,每个控制平面节点上的加密配置应相同。
如果控制平面节点之间的加密提供程序配置存在差异,这种差异可能意味着 kube-apiserver 无法解密数据。
当你计划更新集群的加密配置时,请计划此操作,以便控制平面中的 API 服务器始终能够解密存储的数据(即使在滚动部署更改过程中)。
确保在每个控制平面主机上使用**相同**的加密配置。
验证新写入的数据是否已加密
数据在写入 etcd 时会被加密。重新启动 `kube-apiserver` 后,任何新创建或更新的 Secret(或 `EncryptionConfiguration` 中配置的其他资源类型)在存储时都应被加密。
要检查这一点,你可以使用 `etcdctl` 命令行程序来检索你的 secret 数据的内容。
此示例展示了如何检查 Secret API 的加密。
在 `default` 命名空间中创建一个名为 `secret1` 的新 Secret
kubectl create secret generic secret1 -n default --from-literal=mykey=mydata
使用 `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
验证存储的 Secret 是否以 `k8s:enc:aescbc:v1:` 为前缀,这表示 `aescbc` 提供程序已加密了结果数据。确认 `etcd` 中显示的密钥名称与上面提到的 `EncryptionConfiguration` 中指定的密钥名称匹配。在此示例中,你可以看到加密密钥 `key1` 在 `etcd` 和 `EncryptionConfiguration` 中使用。
验证通过 API 检索时 Secret 是否正确解密
kubectl get secret secret1 -n default -o yaml
输出应包含 `mykey: bXlkYXRh`,其中 `mydata` 的内容使用 base64 编码;请阅读 解码 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,然后用相同的数据更新它们,以应用服务器端加密。
注意
如果由于写入冲突而发生错误,请重试该命令。多次运行该命令是安全的。
对于较大的集群,你可能希望按命名空间细分 Secret,或编写更新脚本。
防止纯文本检索
如果你想确保对特定 API 类型的唯一访问是通过加密完成的,你可以移除 API 服务器读取该 API 后端数据作为纯文本的能力。
警告
进行此更改可防止 API 服务器检索标记为静态加密但实际以明文存储的资源。
当你为某个 API(例如:核心 API 组中的 `Secret` 资源,代表 `secrets` 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` 进程的高可用性部署中。
- 生成一个新密钥,并将其添加为所有控制平面节点上当前提供程序的第二个密钥条目。
- 重新启动**所有** `kube-apiserver` 进程,以确保每个服务器都能解密使用新密钥加密的任何数据。
- 安全地备份新的加密密钥。如果你丢失了此密钥的所有副本,你将需要删除所有在丢失密钥下加密的资源,并且在静态加密被破坏期间,工作负载可能无法按预期运行。
- 将新密钥作为 `keys` 数组的第一个条目,以便它用于新写入的静态加密
- 重新启动所有 `kube-apiserver` 进程,以确保每个控制平面主机现在都使用新密钥进行加密
- 作为特权用户,运行 `kubectl get secrets --all-namespaces -o json | kubectl replace -f -` 以使用新密钥加密所有现有 Secret
- 将所有现有 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 服务器的情况下轮换加密密钥。
下一步
- 阅读有关 解密已静态存储的数据 的信息
- 了解更多关于 EncryptionConfiguration 配置 API (v1)。