靜態加密機密資料

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

本頁說明如何啟用和設定靜態加密 API 資料。

事前準備

  • 您需要有一個 Kubernetes 叢集,並且必須設定 kubectl 命令列工具以與您的叢集通訊。 建議在至少有兩個節點且未充當控制平面主機的叢集上執行本教學課程。 如果您還沒有叢集,可以使用 minikube 建立一個,或者您可以使用這些 Kubernetes playground 之一

  • 此工作假設您將 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 資源名稱 (resourceresource.group) 的陣列,應像密鑰、ConfigMap 或其他資源一樣進行加密。

如果將自訂資源新增至 EncryptionConfiguration 且叢集版本為 1.26 或更新版本,則任何新建立的 EncryptionConfiguration 中提及的自訂資源都將被加密。 在該版本和組態之前存在於 etcd 中的任何自訂資源都將保持未加密狀態,直到下次寫入儲存為止。 這與內建資源的行為相同。 請參閱確保所有密鑰都已加密章節。

providers 陣列是要用於您列出的 API 的可能加密提供者的排序清單。 每個提供者都支援多個金鑰 - 金鑰會依序嘗試解密,如果提供者是第一個提供者,則第一個金鑰會用於加密。

每個項目只能指定一種提供者類型 (可以提供 identityaescbc,但不能在同一個項目中同時提供)。 清單中的第一個提供者用於加密寫入儲存體的資源。 從儲存體讀取資源時,每個符合儲存資料的提供者都會依序嘗試解密資料。 如果沒有提供者可以由於格式或密鑰不符而讀取儲存的資料,則會傳回錯誤,這會阻止用戶端存取該資源。

EncryptionConfiguration 支援使用萬用字元來指定應加密的資源。使用 '*.<group>' 來加密群組內的所有資源(例如,上方範例中的 '*.apps'),或使用 '*.*' 來加密所有資源。 '*.' 可用於加密核心群組中的所有資源。 '*.*' 將加密所有資源,甚至是在 API 伺服器啟動後新增的自訂資源。

如果您有一個涵蓋資源的萬用字元,並且想要針對特定種類的資源選擇不進行靜態加密,您可以透過新增一個單獨的 resources 陣列項目來達成,該項目包含您想要排除的資源名稱,然後是一個 providers 陣列項目,您在其中指定 identity 提供者。您將此項目新增到清單中,使其出現在您指定加密的設定(不是 identity 的提供者)之前。

例如,如果啟用 '*.*',並且您想要針對 Events 和 ConfigMaps 選擇不進行加密,請在 resources 中新增一個較早的項目,然後新增 providers 陣列項目,並將提供者設為 identity。更具體的條目必須在萬用字元條目之前。

新的項目看起來會類似於

  ...
  - 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 靜態加密的提供者
名稱加密強度速度金鑰長度
identity不適用不適用不適用
資源以未加密的方式寫入。當設定為第一個提供者時,資源將在寫入新值時解密。現有的加密資源不會自動以純文字資料覆寫。此identity提供者是預設值,如果您未另行指定。
aescbc使用 PKCS#7 填充的 AES-CBC32 位元組
由於 CBC 容易受到填充 oracle 攻擊,因此不建議使用。金鑰材料可從控制平面主機存取。
aesgcm具有隨機 nonce 的 AES-GCM必須每 200,000 次寫入輪換一次最快16、24 或 32 位元組
除非實作自動金鑰輪換方案,否則不建議使用。金鑰材料可從控制平面主機存取。
kmsv1 (自 Kubernetes v1.28 起已棄用)每個資源使用 DEK 的信封加密方案。最強慢(相較於 kms 版本 232 位元組
資料由資料加密金鑰 (DEK) 使用 AES-GCM 加密;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)}))

複製加密金鑰

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

至少,在傳輸中使用加密 - 例如,安全外殼 (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 的 manifest 檔案:/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 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. 驗證透過 API 檢索 Secret 時是否正確解密

    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 程序的 HA 部署中。

  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 年 9 月 13 日上午 9:33 PST:修正 markdown 檔案中的一些超連結 (e6855623c7)