您正在查看 Kubernetes 版本的文档: v1.18

Kubernetes v1.18 版本的文档已不再维护。您现在看到的版本来自于一份静态的快照。如需查阅最新文档,请点击 最新版本。

Edit This Page

静态加密 Secret 数据

本文展示如何启用和配置静态 Secret 数据的加密

准备开始

  • 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。 如果你还没有集群,你可以通过 Minikube 构建一 个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:

    要获知版本信息,请输入 kubectl version.
  • 需要 etcd v3 或者更高版本

配置并确定是否已启用静态数据加密

kube-apiserver 的参数 --experimental-encryption-provider-config 控制 API 数据在 etcd 中的加密方式。 下面提供一个配置示例。

理解静态数据加密

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
    - secrets
    providers:
    - identity: {}
    - 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 数组项目是一个单独的完整的配置。 resources.resources 字段是要加密的 Kubernetes 资源名称(resourceresource.group)的数组。 providers 数组是可能的加密 provider 的有序列表。 每个条目只能指定一个 provider 类型(可以是 identityaescbc,但不能在同一个项目中同时指定)。

列表中的第一个 provider 用于加密进入存储的资源。 当从存储器读取资源时,与存储的数据匹配的所有 provider 将按顺序尝试解密数据。 如果由于格式或密钥不匹配而导致没有 provider 能够读取存储的数据,则会返回一个错误,以防止客户端访问该资源。

注意: 重要: 如果通过加密配置无法读取资源(因为密钥已更改),唯一的方法是直接从底层 etcd 中删除该密钥。 任何尝试读取资源的调用将会失败,直到它被删除或提供有效的解密密钥。

Providers:

Kubernetes 静态数据加密的 Providers
名称 加密类型 强度 速度 密钥长度 其它事项
identity N/A N/A N/A 不加密写入的资源。当设置为第一个 provider 时,资源将在新值写入时被解密。
aescbc 填充 PKCS#7 的 AES-CBC 最强 32字节 建议使用的加密项,但可能比 secretbox 稍微慢一些。
secretbox XSalsa20 和 Poly1305 更快 32字节 较新的标准,在需要高度评审的环境中可能不被接受。
aesgcm 带有随机数的 AES-GCM 必须每 200k 写入一次 最快 16, 24 或者 32字节 建议不要使用,除非实施了自动密钥循环方案。
kms 使用信封加密方案:数据使用带有 PKCS#7 填充的 AES-CBC 通过数据加密密钥(DEK)加密,DEK 根据 Key Management Service(KMS)中的配置通过密钥加密密钥(Key Encryption Keys,KEK)加密 最强 32字节 建议使用第三方工具进行密钥管理。为每个加密生成新的 DEK,并由用户控制 KEK 轮换来简化密钥轮换。配置 KMS 提供程序

每个 provider 都支持多个密钥 - 在解密时会按顺序使用密钥,如果是第一个 provider,则第一个密钥用于加密。

在 EncryptionConfig 中保存原始的加密密钥与不加密相比只会略微地提升安全级别。 请使用 kms 驱动以获得更强的安全性。 默认情况下,identity 驱动被用来对 etcd 中的 Secret 提供保护, 而这个驱动不提供加密能力。 EncryptionConfiguration 的引入是为了能够使用本地管理的密钥来在本地加密 Secret 数据。

使用本地管理的密钥来加密 Secret 能够保护数据免受 etcd 破坏的影响,不过无法针对 主机被侵入提供防护。 这是因为加密的密钥保存在主机上的 EncryptionConfig YAML 文件中,有经验的入侵者 仍能访问该文件并从中提取出加密密钥。

封套加密(Envelope Encryption)引入了对独立密钥的依赖,而这个密钥并不保存在 Kubernetes 中。 在这种情况下下,入侵者需要攻破 etcd、kube-apiserver 和第三方的 KMS 驱动才能获得明文数据,因而这种方案提供了比本地保存加密密钥更高的安全级别。

加密你的数据

创建一个新的加密配置文件:

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

遵循如下步骤来创建一个新的 secret:

  1. 生成一个 32 字节的随机密钥并进行 base64 编码。如果你在 Linux 或 Mac OS X 上,请运行以下命令:

    head -c 32 /dev/urandom | base64
    
  1. 将这个值放入到 secret 字段中。
  2. 设置 kube-apiserver--experimental-encryption-provider-config 参数,将其指向 配置文件所在位置。
  3. 重启你的 API server。
注意:

你的配置文件包含可以解密 etcd 内容的密钥,因此你必须正确限制主控节点的访问权限, 以便只有能运行 kube-apiserver 的用户才能读取它。

注意:
  1. 使用 etcdctl 命令行,从 etcd 中读取 secret:

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


<!--
where `[...]` must be the additional arguments for connecting to the etcd server.
-->
这里的 `[...]` 是用来连接 etcd 服务的额外参数。

<!-- 3. Verify the stored secret is prefixed with k8s:enc:aescbc:v1: which indicates the aescbc provider has encrypted the resulting data. 4. Verify the secret is correctly decrypted when retrieved via the API: --> 3. 验证存储的密钥前缀是否为 k8s:enc:aescbc:v1:,这表明 aescbc provider 已加密结果数据。 4. 通过 API 检索,验证 secret 是否被正确解密:

kubectl describe secret secret1 -n default
</code></pre>   <!--
   should match `mykey: mydata`, mydata is encoded, check [decoding a secret](/docs/concepts/configuration/secret#decoding-a-secret) to
   completely decode the secret.
   -->
<p>其输出应该是 <code>mykey: bXlkYXRh</code>,<code>mydata</code> 数据是被加密过的,请参阅
<a href="/zh/docs/concepts/configuration/secret#decoding-a-secret">解密 Secret</a>
了解如何完全解码 Secret 内容。</p>
<!--
## Ensure all secrets are encrypted

Since secrets are encrypted on write, performing an update on a secret will encrypt that content. --> <h2 id="确保所有-secret-都被加密">确保所有 Secret 都被加密</h2> <p>由于 Secret 是在写入时被加密,因此对 Secret 执行更新也会加密该内容。</p> <pre><code>kubectl get secrets --all-namespaces -o json | kubectl replace -f - </code></pre><!-- The command above reads all secrets and then updates them to apply server side encryption. --> <p>上面的命令读取所有 Secret,然后使用服务端加密来更新其内容。</p> <!-- If an error occurs due to a conflicting write, retry the command. For larger clusters, you may wish to subdivide the secrets by namespace or script an update. --> <blockquote class="note"> <div><strong>说明:</strong> 如果由于冲突写入而发生错误,请重试该命令。 对于较大的集群,你可能希望通过命名空间或更新脚本来对 Secret 进行划分。</div> </blockquote> <!-- ## Rotating a decryption key

Changing the secret without incurring downtime requires a multi step operation, especially in the presence of a highly available deployment where multiple </span>kube-apiserver<span style="color:#b44"> processes are running.

  1. Generate a new key and add it as the second key entry for the current provider on all servers
  2. Restart all </span>kube-apiserver<span style="color:#b44"> processes to ensure each server can decrypt using the new key
  3. Make the new key the first entry in the </span>keys<span style="color:#b44"> array so that it is used for encryption in the config
  4. Restart all </span>kube-apiserver<span style="color:#b44"> processes to ensure each server now encrypts using the new key
  5. Run </span>kubectl get secrets -all-namespaces -o json | kubectl replace -f -<span style="color:#b44"> to encrypt all existing secrets with the new key
  6. Remove the old decryption key from the config after you back up etcd with the new key in use and update all secrets

With a single </span>kube-apiserver<span style="color:#b44">, step 2 may be skipped. --> <h2 id="轮换解密密钥">轮换解密密钥</h2> <p>在不发生停机的情况下更改 Secret 需要多步操作,特别是在有多个 <code>kube-apiserver</code> 进程正在运行的 高可用环境中。</p> <ol> <li>生成一个新密钥并将其添加为所有服务器上当前提供程序的第二个密钥条目</li> <li>重新启动所有 <code>kube-apiserver</code> 进程以确保每台服务器都可以使用新密钥进行解密</li> <li>将新密钥设置为 <code>keys</code> 数组中的第一个条目,以便在配置中使用其进行加密</li> <li>重新启动所有 <code>kube-apiserver</code> 进程以确保每个服务器现在都使用新密钥进行加密</li> <li>运行 <code>kubectl get secrets --all-namespaces -o json | kubectl replace -f -</code> 以用新密钥加密所有现有的秘密</li> <li>在使用新密钥备份 etcd 后,从配置中删除旧的解密密钥并更新所有密钥</li> </ol> <p>如果只有一个 <code>kube-apiserver</code>,第 2 步可能可以忽略。</p> <!-- ## Decrypting all data

To disable encryption at rest place the </span>identity<span style="color:#b44"> provider as the first entry in the config: --> <h2 id="解密所有数据">解密所有数据</h2> <p>要禁用 rest 加密,请将 <code>identity</code> provider 作为配置中的第一个条目:</p> <div class="highlight"><pre style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#a2f;font-weight:bold">apiVersion</span>:<span style="color:#bbb"> </span>apiserver.config.k8s.io/v1<span style="color:#bbb"> </span><span style="color:#bbb"></span><span style="color:#a2f;font-weight:bold">kind</span>:<span style="color:#bbb"> </span>EncryptionConfiguration<span style="color:#bbb"> </span><span style="color:#bbb"></span><span style="color:#a2f;font-weight:bold">resources</span>:<span style="color:#bbb"> </span><span style="color:#bbb"> </span>- <span style="color:#a2f;font-weight:bold">resources</span>:<span style="color:#bbb"> </span><span style="color:#bbb"> </span>- secrets<span style="color:#bbb"> </span><span style="color:#bbb"> </span><span style="color:#a2f;font-weight:bold">providers</span>:<span style="color:#bbb"> </span><span style="color:#bbb"> </span>- <span style="color:#a2f;font-weight:bold">identity</span>:<span style="color:#bbb"> </span>{}<span style="color:#bbb"> </span><span style="color:#bbb"> </span>- <span style="color:#a2f;font-weight:bold">aescbc</span>:<span style="color:#bbb"> </span><span style="color:#bbb"> </span><span style="color:#a2f;font-weight:bold">keys</span>:<span style="color:#bbb"> </span><span style="color:#bbb"> </span>- <span style="color:#a2f;font-weight:bold">name</span>:<span style="color:#bbb"> </span>key1<span style="color:#bbb"> </span><span style="color:#bbb"> </span><span style="color:#a2f;font-weight:bold">secret</span>:<span style="color:#bbb"> </span>&lt;BASE<span style="color:#bbb"> </span><span style="color:#666">64</span><span style="color:#bbb"> </span>ENCODED<span style="color:#bbb"> </span>SECRET&gt;<span style="color:#bbb"> </span></code></pre></div><!-- and restart all </span>kube-apiserver<span style="color:#b44"> processes. Then run --> <p>并重新启动所有 <code>kube-apiserver</code> 进程。然后运行:</p> <pre><code>kubectl get secrets -all-namespaces -o json | kubectl replace -f -` </code></pre><!-- to force all secrets to be decrypted. --> <p>以强制解密所有 secret。</p> </div> </blockquote>