根據我在多個企業環境中的實踐經驗,以下是一些實用的多租戶 Kubernetes 安全策略:
強化准入控制
使用多層准入控制是防止惡意工作負載的關鍵。我建議實施以下准入控制器組合:
- PodSecurityPolicy 或 Pod Security Admission:限制 Pod 安全上下文設定
- OPA/Gatekeeper:實施自定義政策
- ImagePolicyWebhook:限制可使用的容器映像來源
以下是一個實用的 OPA 政策,用於確保所有容器映像來自受信任的倉函式庫:
package kubernetes.admission
deny[msg] {
    input.request.kind.kind == "Pod"
    container := input.request.object.spec.containers[_]
    not startswith(container.image, "trusted-registry.example.com/")
    
    msg := sprintf("容器 '%v' 使用了未授權的映像倉函式庫: %v", [container.name, container.image])
}
實施網路隔離
使用 NetworkPolicy 資源在名稱空間之間實施嚴格的網路隔離:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all-ingress
  namespace: tenant-a
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  ingress: []
限制 Pod 服務帳戶許可權
為每個租戶建立專用的服務帳戶,並限制其許可權:
apiVersion: v1
kind: ServiceAccount
metadata:
  name: tenant-a-sa
  namespace: tenant-a
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: tenant-a-role
  namespace: tenant-a
rules:
- apiGroups: [""]
  resources: ["pods", "services"]
  verbs: ["get", "list", "watch", "create", "update", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: tenant-a-rb
  namespace: tenant-a
subjects:
- kind: ServiceAccount
  name: tenant-a-sa
  namespace: tenant-a
roleRef:
  kind: Role
  name: tenant-a-role
  apiGroup: rbac.authorization.k8s.io
定期安全稽核
設定期安全稽核流程,檢查:
- 異常的 API 伺服器存取模式
- 意外授予的 RBAC 許可權
- 不符合安全最佳實踐的工作負載設定
多租戶 Kubernetes 環境的安全性是一個持續的過程,需要結合威脅模型、技術控制和操作實踐。透過將所有租戶視為潛在敵對方,我們可以構建更強大的防禦系統,有效保護叢集和工作負載的安全。
在實施多租戶 Kubernetes 環境時,安全性必須從設計階段就被納入考量。透過結合強大的准入控制、沙箱技術、網路隔離和持續監控,我們可以建立既靈活又安全的多租戶環境,滿足現代雲原生應用的需求。
Kubernetes 控制平面的安全威脅面
在開發 Kubernetes 安全架構時,控制平面元件是最關鍵與最容易被忽視的安全區域。每一個控制平面元件都有其獨特的安全考量,而這些元件間的互動更創造了複雜的信任邊界。
kubelet 的安全隱憂
每個節點上的 kubelet 都有自己對外暴露的 API 連線埠,這些連線埠在某些設定下可能允許未經認證的存取來讀取節點本地的 Pod 資訊。這個設計最初是為了讓 cAdvisor(Container Advisor)能夠請求資源和效能統計資料,現在許多觀測工具仍然使用這個端點。
這裡存在一個明顯的安全風險: 若沒有網路政策限制 Pod 存取節點網路,攻擊者可以從 Pod 發起對 kubelet 的攻擊。根據相同的邏輯,若沒有適當的網路政策限制,API Server 同樣可能遭受來自 Pod 的攻擊。
# 檢查 kubelet 安全設定
sudo systemctl cat kubelet
這個指令可以顯示 kubelet 的系統設定,包括其啟動引數和安全設定。透過檢查這些設定,我們可以確認 kubelet 是否啟用了認證和授權機制,例如是否設定了 --anonymous-auth=false 和 --authorization-mode=Webhook 等重要安全引數。
API Server 與 etcd 的安全架構
etcd:Kubernetes 的核心資料函式庫
etcd 是 Kubernetes 和許多雲原生專案的強大分散式資料儲存系統。它可以有不同的佈署方式:
- 佈署在專用叢集上
- 作為 systemd 單元在 Kubernetes 控制平面節點上執行
- 作為自託管的 Pod 在 Kubernetes 叢集內執行
在這三種佈署方式中,將 etcd 作為 Pod 託管在 Kubernetes 叢集內是風險最高的選項。這種設定為攻擊者提供了透過容器網路介面(CNI)直接存取 etcd 的機會。一個意外的 Kubernetes RBAC 錯誤設定可能會透過 etcd 篡改而暴露整個叢集。
etcd 的安全漏洞歷史
etcd 的 API 曾經出現過多個可遠端利用的 CVE 漏洞:
- CVE-2020-15115:允許遠端暴力破解使用者密碼
- CVE-2020-15106:遠端拒絕服務漏洞
- CVE-2018-1098:允許跨站請求偽造,導致許可權提升
這些歷史漏洞強調了保護 etcd 的重要性。
etcd 安全最佳實踐
為了保護 etcd,應該採取以下安全措施:
- 使用防火牆限制只有 API Server 可以存取 etcd
- 啟用所有可用的加密方法
- 將 API Server 與 KMS 或 Vault 整合,確保敏感值在到達 etcd 之前就已加密
etcd 官方發布了詳細的安全模型,建議遵循這些建議進行設定。
API Server 處理系統的核心邏輯並將其狀態持久化在 etcd 中。通常只有 API Server 需要存取 etcd,因此 etcd 不應該在 Kubernetes 叢集的網路中被廣泛存取。
安全的關鍵思維:隱藏攻擊面
每一個軟體都可能存在漏洞,因此減少這類別攻擊的最佳方法是減少攻擊面。如果攻擊者無法看到 etcd 的網路連線埠,他們就無法攻擊它。這是一個簡單但有效的安全原則。
API Server 與 etcd 的信任邊界
API Server 和 etcd 之間存在一個信任邊界。對 etcd 的 root 存取可能會危及 API Server 的資料或允許注入惡意工作負載。因此,API Server 持有一個加密金鑰:如果該金鑰被洩露,攻擊者就可以讀取 etcd 資料。
這意味著,如果 etcd 的記憶體和備份被部分加密以防止資料竊取,Secret 的值會使用 API Server 的對稱金鑰加密。這個 Secret 金鑰在啟動時透過設定 YAML 傳遞給 API Server:
--encryption-provider-config=/etc/kubernetes/encryption.yaml
這個檔案包含用於加密 etcd 中 Secret 的對稱金鑰:
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
  - secrets
  providers:
  - aescbc:
      keys:
      - name: key1
        secret: <BASE64 ENCODED SECRET ENCRYPTION KEY>
  - identity: {}
這個設定案定義了 API Server 如何加密儲存在 etcd 中的 Secret 資源。aescbc 提供者使用 AES-CBC 加密演算法和一個 base64 編碼的金鑰來加密 Secret。identity 提供者是一個不加密的選項,通常放在列表末尾作為後備。在這種設定下,所有 Secret 資料將使用 AES-CBC 加密後再儲存到 etcd。
Base64 編碼只是為了簡化二進位資料在文字連結上的傳輸,在 Kubernetes 早期版本中,這是 Secret 被「保護」的唯一方式。如果 Secret 值在靜態時未加密,那麼攻擊者可以轉儲 etcd 的記憶體並讀取 Secret 值,備份也可能被盜取以取得 Secret。
容器記憶體的安全風險
容器本質上只是程式,但主機上的 root 使用者擁有全知全能的許可權。root 使用者必須能夠看到一切,以便除錯和維護系統。轉儲程式記憶體空間中的字串是非常簡單的操作:
# 範例:轉儲程式記憶體
sudo gcore -o memory_dump <PID>
strings memory_dump | grep -i password
這個簡單的指令組合可以轉儲指定程式的記憶體,然後搜尋其中可能包含的密碼字串。gcore 指令建立程式的核心轉儲,而 strings 指令則提取其中的可讀文字。這展示了為何未加密的記憶體中的敏感資料是如此容易被發現的。
所有記憶體都可以被 root 使用者讀取,因此容器記憶體中未加密的值很容易被發現。必須偵測嘗試這種行為的攻擊者。
使用 KMS 增強 Secret 安全性
攻擊者最難竊取的 Secret 是那些隱藏在受管理的提供者託管的金鑰管理服務(KMS)中的 Secret。KMS 可以代表消費者執行加密操作。專用的實體硬體安全模組(HSM)被用來最小化雲 KMS 系統的風險。
HashiCorp Vault 等應用可以設定為 KMS 的前端,服務必須明確認證才能檢索這些 Secret。它們不在本地主機的記憶體中,不能輕易列舉,並且每個請求都會被記錄以供稽核。攻擊者即使破壞了節點,也尚未竊取該節點可以存取的所有 Secret。
KMS 整合使得從 etcd 竊取雲 Secret 變得更加困難。API Server 使用本地代理與 KMS 互動,透過它解密儲存在 etcd 中的值:
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
  - secrets
  providers:
  - kms:
      name: myKmsPlugin
      endpoint: unix:///var/kms-plugin/socket.sock
      cachesize: 100
這個設定範例展示了 API Server 如何與 KMS 外掛整合。與之前的設定不同,這裡使用 KMS 提供者而非 aescbc。endpoint 指定了與 KMS 外掛通訊的 Unix 通訊端路徑,cachesize 設定了快取大小以提高效能。這種設定下,Secret 資料會先由 KMS 加密,然後再儲存到 etcd,提供了更高階別的安全性。
排程器和控制器管理器的安全考量
控制器管理器和排程器元件很難被直接攻擊,因為它們沒有公開的網路 API。它們可以透過影響 etcd 中的資料或欺騙 API Server 來被操縱,但不接受網路輸入。
控制器管理器的最小許可權原則實踐
控制器管理器服務帳戶是「最小許可權」原則的典範實作。單個控制器管理器程式實際上執行多個獨立的控制器。為了防止許可權提升時的風險擴散,這些服務帳戶被良好隔離:
# kubectl get -n kube-system -o wide serviceaccounts | grep controller
attachdetach-controller              1         20m
calico-kube-controllers             1         20m
certificate-controller              1         20m
clusterrole-aggregation-controller  1         20m
cronjob-controller                  1         20m
daemon-set-controller               1         20m
deployment-controller               1         20m
disruption-controller               1         20m
endpoint-controller                 1         20m
endpointslice-controller            1         20m
# ... 更多控制器 ...
這個指令顯示了 kube-system 名稱空間中與控制器相關的服務帳戶。每個控制器都有自己的服務帳戶,這是最小許可權原則的體現。透過這種方式,即使某個控制器被攻擊,攻擊者也只能獲得該特定控制器的有限許可權,而不能影響整個系統。
根存取的風險
然而,對於大多數 Linux 攻擊,擁有 root 許可權的惡意使用者可以存取一切:執行程式的記憶體、磁碟上的檔案、網路介面卡和掛載的裝置。
攻擊者如果破壞了執行控制器管理器的節點,可以冒充該元件,因為它與 API Server 分享基本的金鑰和認證材料,使用主節點的檔案系統進行分享:
- command:
  - kube-controller-manager
  - --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf
  - --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf
  - --bind-address=127.0.0.1
  - --client-ca-file=/etc/kubernetes/pki/ca.crt
  - --cluster-name=kubernetes
  - --cluster-signing-cert-file=/etc/kubernetes/pki/ca.crt
  - --cluster-signing-key-file=/etc/kubernetes/pki/ca.key
  - --controllers=*,bootstrapsigner,tokencleaner
  - --kubeconfig=/etc/kubernetes/controller-manager.conf
  - --leader-elect=true
  - --port=0
  - --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
  - --root-ca-file=/etc/kubernetes/pki/ca.crt
  - --service-account-private-key-file=/etc/kubernetes/pki/sa.key
  - --use-service-account-credentials=true
這是控制器管理器的啟動命令及其引數。可以看到它需要存取多個敏感檔案,包括 CA 證書、私鑰和設定案。這些檔案對於控制器管理器正常運作是必要的,但同時也代表了攻擊者可能的目標。特別是 --cluster-signing-key-file 和 --service-account-private-key-file 這樣的私鑰檔案,如果被洩露,可能導致嚴重的安全問題。
作為控制平面主機上的 root 使用者,我們可以檢查控制器管理器,轉儲容器的檔案系統並進行探索:
# find /proc/27386/root/etc/kubernetes/
/proc/27386/root/etc/kubernetes/
/proc/27386/root/etc/kubernetes/pki
/proc/27386/root/etc/kubernetes/pki/apiserver.crt
/proc/27386/root/etc/kubernetes/pki/front-proxy-client.key
/proc/27386/root/etc/kubernetes/pki/ca.key
/proc/27386/root/etc/kubernetes/pki/ca.crt
/proc/27386/root/etc/kubernetes/pki/sa.key
/proc/27386/root/etc/kubernetes/pki/sa.pub
/proc/27386/root/etc/kubernetes/pki/front-proxy-client.crt
/proc/27386/root/etc/kubernetes/pki/apiserver-kubelet-client.crt
/proc/27386/root/etc/kubernetes/pki/front-proxy-ca.key
/proc/27386/root/etc/kubernetes/pki/apiserver-kubelet-client.key
/proc/27386/root/etc/kubernetes/pki/apiserver.key
/proc/27386/root/etc/kubernetes/pki/front-proxy-ca.crt
/proc/27386/root/etc/kubernetes/controller-manager.conf
這個指令列出了控制器管理器容器內部的 Kubernetes 設定案和證書。從輸出可以看到,容器內有多個敏感的私鑰檔案(如 ca.key、sa.key、apiserver.key 等)。這些是 Kubernetes 控制平面認證和加密系統的核心元件。如果攻擊者獲得了主機 root 存取許可權,他們可以直接讀取這些檔案,從而可能控制整個叢集。
排程器的有限許可權
與控制器管理器相比,排程器具有更少的許可權和金鑰:
containers:
- command:
  - kube-scheduler
  - --authentication-kubeconfig=/etc/kubernetes/scheduler.conf
  - --authorization-kubeconfig=/etc/kubernetes/scheduler.conf
  - --bind-address=127.0.0.1
  - --kubeconfig=/etc/kubernetes/scheduler.conf
  - --leader-elect=true
  - --port=0
  image: k8s.gcr.io/kube-scheduler:v1.20.4
排程器的設定相對簡單,它只需要基本的認證和授權設定。與控制器管理器不同,它不需要存取簽名金鑰或其他敏感的憑證。這種有限的許可權設計遵循了最小許可權原則,減少了潛在的攻擊面。
控制平面安全的實用策略
根據以上分析,玄貓建議採取以下策略來保護 Kubernetes 控制平面:
- 
隔離 etcd:將 etcd 佈署在專用節點上,並透過網路政策限制只有 API Server 可以存取它。 
- 
加密敏感資料:使用 KMS 或 Vault 來加密 etcd 中的敏感資料,特別是 Secret 資源。 
- 
保護控制平面節點:控制平面節點應該有更嚴格的安全措施,包括強化的防火牆規則、限制 SSH 存取和定期安全更新。 
- 
實施網路隔離:使用網路政策確保 Pod 無法直接存取 kubelet API 或其他控制平面元件。 
- 
監控與稽核:實施強大的監控與稽核機制,以快速檢測和回應可能的安全事件。 
- 
定期輪換憑證:定期輪換所有控制平面憑證和金鑰,減少長期憑證洩露的風險。 
- 
使用最小許可權原則:為每個元件和服務帳戶分配最小必要許可權,限制潛在的攻擊影響範圍。 
Kubernetes 控制平面的安全是一個多層次的問題,需要從架構設計到執行時保護的全面方法。瞭解各元件之間的信任關係和潛在的攻擊途徑,是建立強大安全架構的第一步。透過實施這些策略,可以顯著提高 Kubernetes 環境的安全性,即使面對複雜的攻擊也能保持韌性。
在實際佈署中,安全性與可用性需要平衡。過度限制可能導致維運困難,而過於寬鬆則可能帶來安全風險。找到適合特定環境的平衡點,並隨著威脅環境的演變不斷調整安全措施,是維護 Kubernetes 安全的關鍵。
Kubernetes 安全架構的關鍵防護層
在容器化環境中,安全並非單一層面的考量,而是需要從多個角度建立防護機制。在我多年的 Kubernetes 安全研究中,發現一個安全的 Kubernetes 環境必須同時關注控制面與資料面的防護策略,尤其是許可權管理、節點限制與叢集隔離。本文將分析這些關鍵防護層,並提供實用的安全強化建議。
控制面與雲端控制器的許可權風險
控制面是 Kubernetes 叢集的大腦,也是攻擊者的首要目標。雲端控制器管理器(Cloud Controller Manager)擁有建立服務帳號的許可權,這可能成為攻擊者橫向移動或持續存取的跳板。此外,雲端控制器具有以下關鍵許可權:
- 透過自動擴充套件控制計算節點
- 存取雲端儲存資源
- 管理網路由(例如節點間的通訊)
- 設定負載平衡器(處理外部流量進入叢集)
從歷史角度看,這個控制器曾經是 API 伺服器的一部分,這意味著叢集被入侵可能會導致雲端帳戶被入侵。將這些許可權分離可以增加攻擊者的難度,而確保控制面節點的安全則能保護這些關鍵服務。
資料面安全:工作節點的防護
工作節點信任模型與風險
Kubernetes 一旦接納工作節點加入叢集,就會信任該節點。這種信任模型帶來了重大安全風險 - 如果工作節點被入侵,其上執行的 kubelet、Pod 以及資料都將受到威脅。所有 kubelet 和工作負載的憑證都會落入攻擊者的控制範圍。
值得注意的是,kubelet 的 kubeconfig、金鑰和服務帳號詳細資訊預設並不繫結 IP,工作負載的預設服務帳號也是如此。這些身分(服務帳號 JWT)可以被竊取,並從任何能夠存取 API 伺服器的地方使用。
在入侵後,攻擊者可以在任何接受該工作負載身分的地方冒充 kubelet 的工作負載,包括:
- API 伺服器
- 其他叢集和 kubelet
- 雲端和資料中心整合系統
- 外部系統
NodeRestriction:限制 Kubelet 的許可權
預設情況下,API 伺服器使用 NodeRestriction 外掛程式和節點授權在准入控制器中。這些機制限制了 kubelet 的服務帳號憑證(必須在 system:nodes 群組中)只能存取該 kubelet 上排程的 Pod。
這一設計讓攻擊者的計劃更加困難,因為:
- 攻擊者只能提取與該 kubelet 節點上工作負載相關的 Secret
- 這些 Secret 已經從主機的檔案系統掛載到容器中,root 使用者本來就可以讀取
這種設計限制了被入侵 kubelet 的影響範圍。不過,攻擊者可能會嘗試繞過這一限制,使敏感的 Pod 排程到被入侵的節點上。
節點重新標記攻擊與防禦
被入侵的 kubelet 無法透過 API 伺服器重新排程 Pod(因為被盜的 kubelet 憑證在 kube-system 名稱空間中沒有授權),但它可以透過更改自身的標籤來偽裝成不同的主機或隔離的工作負載型別(前端、資料函式庫等)。
被入侵的 kubelet 能夠透過更新命令列引數並重啟來重新標記自己。這可能欺騙 API 伺服器將敏感的 Pod 和 Secret 排程到該節點上。例如:
# 嘗試列出 Secret,被拒絕
root@kube-node-2 # kubectl get secrets -n null
Error from server (Forbidden): secrets is forbidden: User "system:node:kube-node-2"
cannot list resource "secrets" in API group "" in the namespace "null": No Object name found
# 嘗試修改節點標籤
root@kube-node-2 # kubectl label --overwrite node kube-node-2 sublimino=was_here
node/kube-node-2 labeled
上面的命令展示了兩個關鍵操作:首先嘗試列出 Secret 資源,但被拒絕,因為 kubelet 的身分只有限許可權;接著嘗試為節點增加自定義標籤,這次操作成功了。這意味著攻擊者可以修改節點標籤,潛在地影響工作負載排程。
管理員通常使用標籤來將特定工作負載分配給特定的 kubelet 節點和名稱空間,將具有相似資料分類別的工作負載分組,或透過將網路流量保持在同一區域或資料中心來提高效能。攻擊者不應該能夠在這些敏感和隔離的名稱空間或節點之間跳轉。
NodeRestriction 的標籤保護機制
NodeRestriction 准入外掛程式防止節點重新標記自己為受信任的節點群組的一部分,它透過強制執行不可變的標籤格式來實作。例如,檔案使用監管標籤作為例子(如 example.com.node-restriction.kubernetes.io/fips=true):
# 嘗試修改受限標籤
root@kube-node-2 # kubectl label --overwrite node kube-node-2 example.com.node-restriction.kubernetes.io/fips=true
Error from server (Forbidden): nodes "kube-node-2" is forbidden: is not allowed to modify labels: example.com.node-restriction.kubernetes.io/fips
這個命令嘗試修改一個受 NodeRestriction 保護的標籤,系統回傳了禁止存取的錯誤。這展示了 NodeRestriction 如何防止節點修改特定的敏感標籤,從而保護叢集免受標籤欺騙攻擊。
沒有這個額外的控制,被入侵的 kubelet 可能會危及敏感工作負載,甚至可能危及叢集或雲端帳戶。該外掛程式仍然允許修改一些較不敏感的標籤。
 
            