加密靜態保密資料

Kubernetes 中所有允許你寫入持久 API 資源資料的 API 都支援靜態加密。例如,你可以為 Secret 啟用靜態加密。此靜態加密是 etcd 叢集或執行 kube-apiserver 的主機檔案系統任何系統級加密的補充。

本頁面展示瞭如何啟用和配置靜態 API 資料加密。

準備工作

  • 你需要有一個 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` 陣列項來實現,其中包含你想要豁免的資源名稱,然後是一個 `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

可用提供商

在為叢集 Kubernetes API 中的資料配置靜態加密之前,你需要選擇要使用的提供程式。

下表描述了每個可用提供程式。

Kubernetes 靜態加密提供商
名稱加密強度速度金鑰長度
身份不適用不適用不適用
資源按原樣寫入,不加密。當設定為第一個提供程式時,資源將在寫入新值時被解密。現有的加密資源**不會**自動用純文字資料覆蓋。身份如果你沒有另行指定,則提供程式是預設值。
aescbc帶有 PKCS#7 填充的 AES-CBC16、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 提供程式
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)}))

複製加密金鑰

使用安全的檔案傳輸機制,將加密金鑰的副本提供給所有其他控制平面主機。

至少,使用傳輸中加密——例如,安全 shell (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` 在 `etcd` 和 `EncryptionConfiguration` 中使用。

  4. 驗證透過 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,然後用相同的資料更新它們,以應用伺服器端加密。

防止純文字檢索

如果你想確保對特定 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 伺服器的情況下輪換加密金鑰。

下一步

最後修改於 2025 年 5 月 9 日太平洋時間下午 12:28:Sync encryption docs reviewers with kubernetes sig-auth-encryption-at-rest-reviewers (4e42436cf9)