codecamp

Kubernetes 静态加密Secret数据

静态加密 Secret 数据

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

在开始之前

  • 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。 建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
    • Katacoda
    • 玩转 Kubernetes

    要检查版本,请输入 ​kubectl version​。

  • 需要 etcd v3.0 或者更高版本

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

kube-apiserver​ 的参数 ​--encryption-provider-config​ 控制 API 数据在 etcd 中的加密方式。 该配置作为一个名为 ​EncryptionConfiguration​ 的 API 提供。 下面提供了一个示例配置。

Caution:对于高可用配置(有两个或多个控制平面节点),加密配置文件必须相同! 否则,​kube-apiserver​ 组件无法解密存储在 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 资源名称(​resource ​或 ​resource.group​)的数组。 ​providers ​数组是可能的加密 provider 的有序列表。

每个条目只能指定一个 provider 类型(可以是 ​identity ​或 ​aescbc​,但不能在同一个项目中同时指定二者)。 列表中的第一个 provider 用于加密写入存储的资源。 当从存储器读取资源时,与存储的数据匹配的所有 provider 将按顺序尝试解密数据。 如果由于格式或密钥不匹配而导致没有 provider 能够读取存储的数据,则会返回一个错误,以防止客户端访问该资源。

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

Providers:

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

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

Caution: 在 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 或 macOS 上,请运行以下命令:
  2. head -c 32 /dev/urandom | base64
    
  3. 将这个值放入到 ​EncryptionConfiguration ​结构体的 ​secret ​字段中。
  4. 设置 ​kube-apiserver​ 的 ​--encryption-provider-config​ 参数,将其指向 配置文件所在位置。
  5. 重启你的 API server。

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

验证数据已被加密

数据在写入 etcd 时会被加密。重新启动你的 ​kube-apiserver​ 后,任何新创建或更新的密码在存储时都应该被加密。 如果想要检查,你可以使用 ​etcdctl ​命令行程序来检索你的加密内容。

  1. 创建一个新的 secret,名称为 ​secret1​,命名空间为 ​default​:
  2. kubectl create secret generic secret1 -n default --from-literal=mykey=mydata
    
  3. 使用 etcdctl 命令行,从 etcd 中读取 Secret:
  4. ETCDCTL_API=3 etcdctl get /registry/secrets/default/secret1 [...] | hexdump -C
    

    这里的 ​[...]​ 是用来连接 etcd 服务的额外参数。

  5. 验证存储的密钥前缀是否为 ​k8s:enc:aescbc:v1:​,这表明 ​aescbc ​provider 已加密结果数据。
  6. 通过 API 检索,验证 Secret 是否被正确解密:
  7. kubectl describe secret secret1 -n default
    

    其输出应该包含 ​mykey: bXlkYXRh​,​mydata ​的内容是被加密过的。

确保所有 Secret 都被加密

由于 Secret 是在写入时被加密,因此对 Secret 执行更新也会加密该内容。

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

上面的命令读取所有 Secret,然后使用服务端加密来更新其内容。

Note: 如果由于冲突写入而发生错误,请重试该命令。 对于较大的集群,你可能希望通过命名空间或更新脚本来对 Secret 进行划分。

轮换解密密钥

在不发生停机的情况下更改 Secret 需要多步操作,特别是在有多个 ​kube-apiserver​ 进程正在运行的 高可用环境中。

  1. 生成一个新密钥并将其添加为所有服务器上当前提供程序的第二个密钥条目
  2. 重新启动所有 ​kube-apiserver​ 进程以确保每台服务器都可以使用新密钥进行解密
  3. 将新密钥设置为 ​keys ​数组中的第一个条目,以便在配置中使用其进行加密
  4. 重新启动所有 ​kube-apiserver​ 进程以确保每个服务器现在都使用新密钥进行加密
  5. 运行 ​kubectl get secrets --all-namespaces -o json | kubectl replace -f -​ 以用新密钥加密所有现有的 Secret
  6. 在使用新密钥备份 etcd 后,从配置中删除旧的解密密钥并更新所有密钥

当只运行一个 ​kube-apiserver​ 实例时,第 2 步可能可以忽略。

解密所有数据 

要禁用静态加密,请将 ​identity ​provider 作为配置中的第一个条目并重新启动所有 ​kube-apiserver​ 进程。

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

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

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


Kubernetes 限制存储消耗
Kubernetes 为容器和Pod分配内存资源
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

Kubernetes 管理集群

Kubernetes Service

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }