靜態加密機密資料
Kubernetes 中讓您寫入持久 API 資源資料的所有 API 都支援靜態加密。 例如,您可以為 密鑰 啟用靜態加密。 此靜態加密是對 etcd 叢集或執行 kube-apiserver 的主機上檔案系統的任何系統層級加密的補充。
本頁說明如何啟用和設定靜態加密 API 資料。
注意
此工作涵蓋使用 Kubernetes 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 資源名稱 (resource
或 resource.group
) 的陣列,應像密鑰、ConfigMap 或其他資源一樣進行加密。
如果將自訂資源新增至 EncryptionConfiguration
且叢集版本為 1.26 或更新版本,則任何新建立的 EncryptionConfiguration
中提及的自訂資源都將被加密。 在該版本和組態之前存在於 etcd 中的任何自訂資源都將保持未加密狀態,直到下次寫入儲存為止。 這與內建資源的行為相同。 請參閱確保所有密鑰都已加密章節。
providers
陣列是要用於您列出的 API 的可能加密提供者的排序清單。 每個提供者都支援多個金鑰 - 金鑰會依序嘗試解密,如果提供者是第一個提供者,則第一個金鑰會用於加密。
每個項目只能指定一種提供者類型 (可以提供 identity
或 aescbc
,但不能在同一個項目中同時提供)。 清單中的第一個提供者用於加密寫入儲存體的資源。 從儲存體讀取資源時,每個符合儲存資料的提供者都會依序嘗試解密資料。 如果沒有提供者可以由於格式或密鑰不符而讀取儲存的資料,則會傳回錯誤,這會阻止用戶端存取該資源。
EncryptionConfiguration
支援使用萬用字元來指定應加密的資源。使用 '*.<group>
' 來加密群組內的所有資源(例如,上方範例中的 '*.apps
'),或使用 '*.*
' 來加密所有資源。 '*.
' 可用於加密核心群組中的所有資源。 '*.*
' 將加密所有資源,甚至是在 API 伺服器啟動後新增的自訂資源。
注意
不允許在同一個資源清單或多個條目中使用重疊的萬用字元,因為部分設定會失效。resources
清單的處理順序和優先順序取決於其在設定中列出的順序。如果您有一個涵蓋資源的萬用字元,並且想要針對特定種類的資源選擇不進行靜態加密,您可以透過新增一個單獨的 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。
注意
如果任何資源由於加密設定而無法讀取(因為金鑰已變更),並且您無法還原可運作的設定,您唯一的補救方法是直接從底層 etcd 中刪除該條目。
任何嘗試讀取該資源的 Kubernetes API 呼叫都將失敗,直到刪除該資源或提供有效的解密金鑰為止。
可用的提供者
在為叢集 Kubernetes API 中的資料配置靜態加密之前,您需要選擇您將使用的提供者。
下表描述了每個可用的提供者。
名稱 | 加密 | 強度 | 速度 | 金鑰長度 |
---|---|---|---|---|
identity | 無 | 不適用 | 不適用 | 不適用 |
資源以未加密的方式寫入。當設定為第一個提供者時,資源將在寫入新值時解密。現有的加密資源不會自動以純文字資料覆寫。此identity提供者是預設值,如果您未另行指定。 | ||||
aescbc | 使用 PKCS#7 填充的 AES-CBC | 弱 | 快 | 32 位元組 |
由於 CBC 容易受到填充 oracle 攻擊,因此不建議使用。金鑰材料可從控制平面主機存取。 | ||||
aesgcm | 具有隨機 nonce 的 AES-GCM | 必須每 200,000 次寫入輪換一次 | 最快 | 16、24 或 32 位元組 |
除非實作自動金鑰輪換方案,否則不建議使用。金鑰材料可從控制平面主機存取。 | ||||
kmsv1 (自 Kubernetes v1.28 起已棄用) | 每個資源使用 DEK 的信封加密方案。 | 最強 | 慢(相較於 kms 版本 2) | 32 位元組 |
資料由資料加密金鑰 (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 提供者。 | ||||
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)}))
注意
請保持加密金鑰的機密性,包括在您產生金鑰時,以及理想情況下,即使在您不再積極使用它之後。複製加密金鑰
使用安全的檔案傳輸機制,將該加密金鑰的副本提供給每個其他控制平面主機。
至少,在傳輸中使用加密 - 例如,安全外殼 (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 的 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 ...
重新啟動您的 API 伺服器。
注意
您的設定檔包含可用於解密 etcd 中內容的金鑰,因此您必須正確限制控制平面節點的權限,以便只有執行kube-apiserver
的使用者才能讀取它。您現在已為一個控制平面主機設定了加密。典型的 Kubernetes 叢集有多個控制平面主機,因此還有更多工作要做。
重新設定其他控制平面主機
如果您的叢集中有多個 API 伺服器,您應該依序將變更部署到每個 API 伺服器。
注意
對於具有兩個或多個控制平面節點的叢集設定,每個控制平面節點的加密設定應相同。
如果控制平面節點之間的加密提供者設定存在差異,則此差異可能表示 kube-apiserver 無法解密資料。
當您計劃更新叢集的加密設定時,請如此規劃,以便控制平面中的 API 伺服器始終可以解密儲存的資料(即使在推出變更的過程中)。
請確保在每個控制平面主機上使用相同的加密設定。
驗證新寫入的資料是否已加密
資料在寫入 etcd 時會被加密。重新啟動 kube-apiserver
後,任何新建立或更新的 Secret(或 EncryptionConfiguration
中設定的其他資源種類)在儲存時都應被加密。
若要檢查這一點,您可以使用 etcdctl
命令列程式來檢索您的密碼資料的內容。
此範例示範如何檢查 Secret API 的加密。
在
default
命名空間中建立一個名為secret1
的新 Secretkubectl create secret generic secret1 -n default --from-literal=mykey=mydata
使用
etcdctl
命令列工具,從 etcd 中讀取該 SecretETCDCTL_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
,其中包含使用 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,然後使用相同的資料更新它們,以便應用伺服器端加密。
注意
如果由於寫入衝突而發生錯誤,請重試該命令。多次執行該命令是安全的。
對於較大的叢集,您可能希望按命名空間細分 Secret,或編寫更新腳本。
防止純文字檢索
如果您想確保僅使用加密來完成對特定 API 種類的存取,您可以移除 API 伺服器以純文字形式讀取該 API 後端資料的能力。
警告
進行此變更會阻止 API 伺服器檢索標記為靜態加密,但實際上以明文儲存的資源。
當您為 API 配置靜態加密時(例如:API 種類 Secret
,代表核心 API 群組中的 secrets
資源),您必須確保此叢集中的所有這些資源確實已靜態加密。在繼續下一步之前,請檢查這一點。
一旦叢集中的所有 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 部署中。
- 產生一個新的金鑰,並將其作為目前提供者的第二個金鑰條目新增到所有控制平面節點上。
- 重新啟動所有
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 伺服器的情況下輪換加密金鑰。